package org.cmc.music.myid3; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import org.cmc.music.common.ID3ReadException; import org.cmc.music.common.ID3WriteException; import org.cmc.music.metadata.IMusicMetadata; import org.cmc.music.metadata.MusicMetadataSet; import org.cmc.music.myid3.id3v1.MyID3v1; import org.cmc.music.myid3.id3v1.MyID3v1Constants; import org.cmc.music.myid3.id3v2.MyID3v2; import org.cmc.music.myid3.id3v2.MyID3v2Write; import org.cmc.music.util.Debug; /** * The primary interface to the MyID3 library. * <p> * Almost all of the MyID3 library's core functionality can be accessed through * it's methods. * <p> * See the source of the SampleUsage class and other classes in the * org.cmc.music.myid3.examples package for examples. * * @see org.cmc.music.myid3.examples.SampleUsage */ public class MyID3 implements MyID3v1Constants { /** * Write MP3 file with specific metadata, drawing song data from an existing * mp3 file. * <p> * * @param file * File to read non-metadata (ie. song data) from. Will be * overwritten with new mp3 file. * @param set * MusicMetadataSet, usually read from mp3 file. * @param values * IMusicMetadata, a specific group of values to write. * @see MusicMetadataSet, IMusicMetadata */ public void update(File file, MusicMetadataSet set, IMusicMetadata values) throws UnsupportedEncodingException, IOException, ID3WriteException { File temp = null; try { temp = File.createTempFile(file.getName(), ".tmp", file .getParentFile()); write(file, temp, set, values); temp.setLastModified(file.lastModified()); file.delete(); temp.renameTo(file); } finally { } } /** * Write MP3 file with specific metadata, drawing song data from an existing * mp3 file. * <p> * * @param file * File to read non-metadata (ie. song data) from. Will be * overwritten with new mp3 file. * @param set * MusicMetadataSet, usually read from mp3 file. * @param values * IMusicMetadata, a specific group of values to write. * @param filter * MyID3v2Write.Filter, can be used to prevent ID3v2 frames from * written on a case-by-case basis. * @param listener * MyID3Listener, observer of the write process. * @see MusicMetadataSet, IMusicMetadata, MyID3Listener, MyID3v2Write.Filter */ public void update(File file, MusicMetadataSet set, IMusicMetadata values, MyID3v2Write.Filter filter, MyID3Listener listener) throws UnsupportedEncodingException, IOException, ID3WriteException { File temp = null; try { temp = File.createTempFile(file.getName(), ".tmp", file .getParentFile()); write(file, temp, set, values, filter, listener); temp.setLastModified(file.lastModified()); file.delete(); temp.renameTo(file); } catch (UnsupportedEncodingException e) { if (temp != null && temp.exists() && file.exists()) temp.delete(); throw e; } catch (IOException e) { if (temp != null && temp.exists() && file.exists()) temp.delete(); throw e; } catch (ID3WriteException e) { if (temp != null && temp.exists() && file.exists()) temp.delete(); throw e; } } /** * Write MP3 file with specific metadata, drawing song data from an existing * mp3 file. * <p> * * @param src * File to read non-metadata (ie. song data) from. * @param dst * File to overwrite with new mp3 file. * @param set * MusicMetadataSet, usually read from mp3 file. * @param values * IMusicMetadata, a specific group of values to write. * @see MusicMetadataSet, IMusicMetadata */ public void write(File src, File dst, MusicMetadataSet set, IMusicMetadata values) throws UnsupportedEncodingException, IOException, ID3WriteException { write(src, dst, set, values, null, null); } /** * Write MP3 file with specific metadata, drawing song data from an existing * mp3 file. * <p> * * @param src * File to read non-metadata (ie. song data) from. * @param dst * File to overwrite with new mp3 file. * @param set * MusicMetadataSet, usually read from mp3 file. * @param values * IMusicMetadata, a specific group of values to write. * @param listener * MyID3Listener, observer of the write process. * @see MusicMetadataSet, IMusicMetadata, MyID3Listener */ public void write(File src, File dst, MusicMetadataSet set, IMusicMetadata values, MyID3Listener listener) throws UnsupportedEncodingException, IOException, ID3WriteException { write(src, dst, set, values, null, listener); } /** * Write MP3 file with specific metadata, drawing song data from an existing * mp3 file. * <p> * * @param src * File to read non-metadata (ie. song data) from. * @param dst * File to overwrite with new mp3 file. * @param set * MusicMetadataSet, usually read from mp3 file. * @param values * IMusicMetadata, a specific group of values to write. * @param filter * MyID3v2Write.Filter, can be used to prevent ID3v2 frames from * written on a case-by-case basis. * @param listener * MyID3Listener, observer of the write process. * @see MusicMetadataSet, IMusicMetadata, MyID3Listener, MyID3v2Write.Filter */ public void write(File src, File dst, MusicMetadataSet set, IMusicMetadata values, MyID3v2Write.Filter filter, MyID3Listener listener) throws UnsupportedEncodingException, IOException, ID3WriteException { if (values == null) throw new IOException(Debug.getDebug("missing values", values)); if (listener != null) listener.log(); byte id3v1Tag[] = new MyID3v1().toTag(listener, values, strict); if (listener != null) listener.log("writing id3v1Tag", id3v1Tag == null ? "null" : "" + id3v1Tag.length); byte id3v2TailTag[] = new MyID3v2Write().toTag(listener, filter, set, values, strict); if (listener != null) listener.log("writing id3v2TailTag", id3v2TailTag == null ? "null" : "" + id3v2TailTag.length); write(src, dst, id3v1Tag, id3v2TailTag, id3v2TailTag); if (listener != null) listener.log(); } /** * Removes all ID3v1 and ID3v2 tags from an mp3 file. * <p> * * @param src * File to read non-metadata (ie. song data) from. * @param dst * File to overwrite with new mp3 file. */ public void removeTags(File src, File dst) throws UnsupportedEncodingException, IOException, ID3WriteException { byte id3v1Tag[] = null; byte id3v2HeadTag[] = null; byte id3v2TailTag[] = null; write(src, dst, id3v1Tag, id3v2HeadTag, id3v2TailTag); } /** * Removes all ID3v1 and ID3v2 tags from an mp3 file. * <p> * * @param src * File to read non-metadata (ie. song data) from. * @param dst * File to overwrite with new mp3 file. */ public void rewriteTags(File src, File dst) throws UnsupportedEncodingException, IOException, ID3WriteException { byte id3v1Tag[] = null; ID3Tag tag = new MyID3v1().readID3v1(src, strict); if (null != tag) id3v1Tag = tag.bytes; byte id3v2HeadTag[] = new MyID3v2().readID3v2Head(src, strict); boolean hasId3v1 = id3v1Tag != null; byte id3v2TailTag[] = new MyID3v2() .readID3v2Tail(src, hasId3v1, strict); write(src, dst, id3v1Tag, id3v2HeadTag, id3v2TailTag); } private boolean strict = false; /** * Configures the library to not write ID3v1 tags. */ public void setStrict() { strict = true; } private boolean skipId3v1 = false; /** * Configures the library to not write ID3v1 tags. */ public void setSkipId3v1() { skipId3v1 = true; } private boolean skipId3v2 = false; /** * Configures the library to not write ID3v2 tags. */ public void setSkipId3v2() { skipId3v2 = true; } private boolean skipId3v2Head = false; /** * Configures the library to not write ID3v2 head tags. */ public void setSkipId3v2Head() { skipId3v2Head = true; } private boolean skipId3v2Tail = false; /** * Configures the library to not write ID3v2 tail tags. */ public void setSkipId3v2Tail() { skipId3v2Tail = true; } private void write(File src, File dst, byte id3v1Tag[], byte id3v2HeadTag[], byte id3v2TailTag[]) throws IOException { if (src == null || !src.exists()) throw new IOException(Debug.getDebug("missing src", src)); if (!src.getName().toLowerCase().endsWith(".mp3")) throw new IOException(Debug.getDebug("src not mp3", src)); if (dst == null) throw new IOException(Debug.getDebug("missing dst", dst)); if (dst.exists()) { dst.delete(); if (dst.exists()) throw new IOException(Debug.getDebug("could not delete dst", dst)); } boolean hasId3v1 = new MyID3v1().hasID3v1(src); long id3v1Length = hasId3v1 ? ID3_V1_TAG_LENGTH : 0; long id3v2HeadLength = new MyID3v2().findID3v2HeadLength(src); long id3v2TailLength = new MyID3v2().findID3v2TailLength(src, hasId3v1); OutputStream os = null; InputStream is = null; try { dst.getParentFile().mkdirs(); os = new FileOutputStream(dst); os = new BufferedOutputStream(os); if (!skipId3v2Head && !skipId3v2 && id3v2HeadTag != null) os.write(id3v2HeadTag); is = new FileInputStream(src); is = new BufferedInputStream(is); is.skip(id3v2HeadLength); long total_to_read = src.length(); total_to_read -= id3v1Length; total_to_read -= id3v2HeadLength; total_to_read -= id3v2TailLength; byte buffer[] = new byte[1024]; long total_read = 0; while (total_read < total_to_read) { int remainder = (int) (total_to_read - total_read); int readSize = Math.min(buffer.length, remainder); int read = is.read(buffer, 0, readSize); if (read <= 0) throw new IOException("unexpected EOF"); os.write(buffer, 0, read); total_read += read; } if (!skipId3v2Tail && !skipId3v2 && id3v2TailTag != null) os.write(id3v2TailTag); if (!skipId3v1 && id3v1Tag != null) os.write(id3v1Tag); } finally { try { if (is != null) is.close(); } catch (Throwable e) { Debug.debug(e); } try { if (os != null) os.close(); } catch (Throwable e) { Debug.debug(e); } } } /** * Reads all metadata (ID3v1 & ID3v2) from MP3 file. * <p> * * @param file * File to read metadata (ie. song data) from. * @return MusicMetadataSet, a set of IMusicMetadata value collections. * @see MusicMetadataSet, IMusicMetadata */ public MusicMetadataSet read(File file) throws IOException, ID3ReadException { return read(file, null); } /** * Reads all metadata (ID3v1 & ID3v2) from MP3 file. * <p> * * @param file * File to read metadata (ie. song data) from. * @param listener * MyID3Listener, an observer. * @return MusicMetadataSet, a set of IMusicMetadata value collections. * @see MusicMetadataSet, IMusicMetadata */ public MusicMetadataSet read(File file, MyID3Listener listener) throws IOException, ID3ReadException { try { if (file == null || !file.exists()) return null; if (!file.getName().toLowerCase().endsWith(".mp3")) return null; ID3Tag.V1 id3v1 = new MyID3v1().readID3v1(listener, file, strict); ID3Tag.V2 id3v2 = new MyID3v2().readID3v2(listener, file, id3v1 != null, strict); MusicMetadataSet result = MusicMetadataSet.factoryMethod(id3v1, id3v2, file.getName(), file.getParentFile().getName()); return result; } catch (Error e) { Debug.debug("file", file); throw e; } catch (IOException e) { Debug.debug("file", file); throw e; } } }