/** * @param startByte * @param endByte * @return * @throws Exception * @return true if all the bytes between in the file between startByte and endByte are null, false * otherwise */ private boolean isFilePortionNull(int startByte, int endByte) throws IOException { logger.config("Checking file portion:" + Hex.asHex(startByte) + ":" + Hex.asHex(endByte)); FileInputStream fis = null; FileChannel fc = null; try { fis = new FileInputStream(file); fc = fis.getChannel(); fc.position(startByte); ByteBuffer bb = ByteBuffer.allocateDirect(endByte - startByte); fc.read(bb); while (bb.hasRemaining()) { if (bb.get() != 0) { return false; } } } finally { if (fc != null) { fc.close(); } if (fis != null) { fis.close(); } } return true; }
/** * Creates a new MP3File dataType and parse the tag from the given file Object, files can be * opened read only if required. * * @param file MP3 file * @param loadOptions decide what tags to load * @param readOnly causes the files to be opened readonly * @throws IOException on any I/O error * @throws TagException on any exception generated by this library. * @throws org.jaudiotagger.audio.exceptions.ReadOnlyFileException * @throws org.jaudiotagger.audio.exceptions.InvalidAudioFrameException */ public MP3File(File file, int loadOptions, boolean readOnly) throws IOException, TagException, ReadOnlyFileException, InvalidAudioFrameException { RandomAccessFile newFile = null; try { this.file = file; // Check File accessibility newFile = checkFilePermissions(file, readOnly); // Read ID3v2 tag size (if tag exists) to allow audioHeader parsing to skip over tag long tagSizeReportedByHeader = AbstractID3v2Tag.getV2TagSizeIfExists(file); logger.config("TagHeaderSize:" + Hex.asHex(tagSizeReportedByHeader)); audioHeader = new MP3AudioHeader(file, tagSizeReportedByHeader); // If the audio header is not straight after the end of the tag then search from start of file if (tagSizeReportedByHeader != ((MP3AudioHeader) audioHeader).getMp3StartByte()) { logger.config("First header found after tag:" + audioHeader); audioHeader = checkAudioStart(tagSizeReportedByHeader, (MP3AudioHeader) audioHeader); } // Read v1 tags (if any) readV1Tag(file, newFile, loadOptions); // Read v2 tags (if any) readV2Tag(file, loadOptions, (int) ((MP3AudioHeader) audioHeader).getMp3StartByte()); // If we have a v2 tag use that, if we do not but have v1 tag use that // otherwise use nothing // TODO:if have both should we merge // rather than just returning specific ID3v22 tag, would it be better to return v24 version ? if (this.getID3v2Tag() != null) { tag = this.getID3v2Tag(); } else if (id3v1tag != null) { tag = id3v1tag; } } finally { if (newFile != null) { newFile.close(); } } }
/** * Regets the audio header starting from start of file, and write appropriate logging to indicate * potential problem to user. * * @param startByte * @param firstHeaderAfterTag * @return * @throws IOException * @throws InvalidAudioFrameException */ private MP3AudioHeader checkAudioStart(long startByte, MP3AudioHeader firstHeaderAfterTag) throws IOException, InvalidAudioFrameException { MP3AudioHeader headerOne; MP3AudioHeader headerTwo; logger.warning( ErrorMessage.MP3_ID3TAG_LENGTH_INCORRECT.getMsg( file.getPath(), Hex.asHex(startByte), Hex.asHex(firstHeaderAfterTag.getMp3StartByte()))); // because we cant agree on start location we reread the audioheader from the start of the file, // at least // this way we cant overwrite the audio although we might overwrite part of the tag if we write // this file // back later headerOne = new MP3AudioHeader(file, 0); logger.config("Checking from start:" + headerOne); // Although the id3 tag size appears to be incorrect at least we have found the same location // for the start // of audio whether we start searching from start of file or at the end of the alleged of file // so no real // problem if (firstHeaderAfterTag.getMp3StartByte() == headerOne.getMp3StartByte()) { logger.config( ErrorMessage.MP3_START_OF_AUDIO_CONFIRMED.getMsg( file.getPath(), Hex.asHex(headerOne.getMp3StartByte()))); return firstHeaderAfterTag; } else { // We get a different value if read from start, can't guarantee 100% correct lets do some more // checks logger.config( (ErrorMessage.MP3_RECALCULATED_POSSIBLE_START_OF_MP3_AUDIO.getMsg( file.getPath(), Hex.asHex(headerOne.getMp3StartByte())))); // Same frame count so probably both audio headers with newAudioHeader being the first one if (firstHeaderAfterTag.getNumberOfFrames() == headerOne.getNumberOfFrames()) { logger.warning( (ErrorMessage.MP3_RECALCULATED_START_OF_MP3_AUDIO.getMsg( file.getPath(), Hex.asHex(headerOne.getMp3StartByte())))); return headerOne; } // If the size reported by the tag header is a little short and there is only nulls between // the recorded value // and the start of the first audio found then we stick with the original header as more // likely that currentHeader // DataInputStream not really a header if (isFilePortionNull((int) startByte, (int) firstHeaderAfterTag.getMp3StartByte())) { return firstHeaderAfterTag; } // Skip to the next header (header 2, counting from start of file) headerTwo = new MP3AudioHeader( file, headerOne.getMp3StartByte() + headerOne.mp3FrameHeader.getFrameLength()); // It matches the header we found when doing the original search from after the ID3Tag // therefore it // seems that newAudioHeader was a false match and the original header was correct if (headerTwo.getMp3StartByte() == firstHeaderAfterTag.getMp3StartByte()) { logger.warning( (ErrorMessage.MP3_START_OF_AUDIO_CONFIRMED.getMsg( file.getPath(), Hex.asHex(firstHeaderAfterTag.getMp3StartByte())))); return firstHeaderAfterTag; } // It matches the frameCount the header we just found so lends weight to the fact that the // audio does indeed start at new header // however it maybe that neither are really headers and just contain the same data being // misrepresented as headers. if (headerTwo.getNumberOfFrames() == headerOne.getNumberOfFrames()) { logger.warning( (ErrorMessage.MP3_RECALCULATED_START_OF_MP3_AUDIO.getMsg( file.getPath(), Hex.asHex(headerOne.getMp3StartByte())))); return headerOne; } /// Doesnt match the frameCount lets go back to the original header else { logger.warning( (ErrorMessage.MP3_RECALCULATED_START_OF_MP3_AUDIO.getMsg( file.getPath(), Hex.asHex(firstHeaderAfterTag.getMp3StartByte())))); return firstHeaderAfterTag; } } }