/** * Used by tags when writing to calculate the location of the music file * * @param file * @return the location within the file that the audio starts * @throws java.io.IOException * @throws org.jaudiotagger.audio.exceptions.InvalidAudioFrameException */ public long getMP3StartByte(File file) throws InvalidAudioFrameException, IOException { try { // Read ID3v2 tag size (if tag exists) to allow audio header parsing to skip over tag long startByte = AbstractID3v2Tag.getV2TagSizeIfExists(file); MP3AudioHeader audioHeader = new MP3AudioHeader(file, startByte); if (startByte != audioHeader.getMp3StartByte()) { logger.config("First header found after tag:" + audioHeader); audioHeader = checkAudioStart(startByte, audioHeader); } return audioHeader.getMp3StartByte(); } catch (InvalidAudioFrameException iafe) { throw iafe; } catch (IOException ioe) { throw ioe; } }
/** * Saves the tags in this dataType to the file argument. It will be saved as * TagConstants.MP3_FILE_SAVE_WRITE * * @param fileToSave file to save the this dataTypes tags to * @throws FileNotFoundException if unable to find file * @throws IOException on any I/O error */ public void save(File fileToSave) throws IOException { // Ensure we are dealing with absolute filepaths not relative ones File file = fileToSave.getAbsoluteFile(); logger.config("Saving : " + file.getPath()); // Checks before starting write precheck(file); RandomAccessFile rfile = null; try { // ID3v2 Tag if (TagOptionSingleton.getInstance().isId3v2Save()) { if (id3v2tag == null) { rfile = new RandomAccessFile(file, "rw"); (new ID3v24Tag()).delete(rfile); (new ID3v23Tag()).delete(rfile); (new ID3v22Tag()).delete(rfile); logger.config("Deleting ID3v2 tag:" + file.getName()); rfile.close(); } else { logger.config("Writing ID3v2 tag:" + file.getName()); final MP3AudioHeader mp3AudioHeader = (MP3AudioHeader) this.getAudioHeader(); final long mp3StartByte = mp3AudioHeader.getMp3StartByte(); final long newMp3StartByte = id3v2tag.write(file, mp3StartByte); if (mp3StartByte != newMp3StartByte) { logger.config("New mp3 start byte: " + newMp3StartByte); mp3AudioHeader.setMp3StartByte(newMp3StartByte); } } } rfile = new RandomAccessFile(file, "rw"); // Lyrics 3 Tag if (TagOptionSingleton.getInstance().isLyrics3Save()) { if (lyrics3tag != null) { lyrics3tag.write(rfile); } } // ID3v1 tag if (TagOptionSingleton.getInstance().isId3v1Save()) { logger.config("Processing ID3v1"); if (id3v1tag == null) { logger.config("Deleting ID3v1"); (new ID3v1Tag()).delete(rfile); } else { logger.config("Saving ID3v1"); id3v1tag.write(rfile); } } } catch (FileNotFoundException ex) { logger.log( Level.SEVERE, ErrorMessage.GENERAL_WRITE_FAILED_BECAUSE_FILE_NOT_FOUND.getMsg(file.getName()), ex); throw ex; } catch (IOException iex) { logger.log( Level.SEVERE, ErrorMessage.GENERAL_WRITE_FAILED_BECAUSE.getMsg(file.getName(), iex.getMessage()), iex); throw iex; } catch (RuntimeException re) { logger.log( Level.SEVERE, ErrorMessage.GENERAL_WRITE_FAILED_BECAUSE.getMsg(file.getName(), re.getMessage()), re); throw re; } finally { if (rfile != null) { rfile.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; } } }