FileDocCategorySizeDatePackage
MyID3.javaAPI DocMyID3 for Android19451Wed Oct 08 18:12:02 BST 2008org.cmc.music.myid3

MyID3.java

/*
 * Modified By Romulus U. Ts'ai
 * On Oct 6, 2008
 * 
 * Removed all Debug executions
 * 
 */

package org.cmc.music.myid3;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
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 java.util.Vector;

import org.cmc.music.common.ID3WriteException;
import org.cmc.music.metadata.MusicMetadata;
import org.cmc.music.metadata.MusicMetadataSet;

/**
 * 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
{
	/**
	 * 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
	 *            MusicMetadata, a specific group of values to write.
	 * @see MusicMetadataSet, MusicMetadata
	 */
	public void update(File file, MusicMetadataSet set, MusicMetadata 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
	 *            MusicMetadata, 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, MusicMetadata, MyID3Listener, MyID3v2Write.Filter
	 */
	public void update(File file, MusicMetadataSet set, MusicMetadata 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
	 *            MusicMetadata, a specific group of values to write.
	 * @see MusicMetadataSet, MusicMetadata
	 */
	public void write(File src, File dst, MusicMetadataSet set,
			MusicMetadata 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
	 *            MusicMetadata, a specific group of values to write.
	 * @param listener
	 *            MyID3Listener, observer of the write process.
	 * @see MusicMetadataSet, MusicMetadata, MyID3Listener
	 */
	public void write(File src, File dst, MusicMetadataSet set,
			MusicMetadata 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
	 *            MusicMetadata, 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, MusicMetadata, MyID3Listener, MyID3v2Write.Filter
	 */
	public void write(File src, File dst, MusicMetadataSet set,
			MusicMetadata values, MyID3v2Write.Filter filter,
			MyID3Listener listener) throws UnsupportedEncodingException,
			IOException, ID3WriteException
	{
		if (values == null)


		if (listener != null)
			listener.log();

		byte id3v1Tag[] = new MyID3v1().toTag(values);
		if (listener != null)
			listener.log("writing id3v1Tag", id3v1Tag == null ? "null" : ""
					+ id3v1Tag.length);

		byte id3v2TailTag[] = new MyID3v2Write().toTag(filter, set, values);
		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 = readID3v1(src);
		if (null != tag)
			id3v1Tag = tag.bytes;

		byte id3v2HeadTag[] = readID3v2Head(src);

		boolean hasId3v1 = id3v1Tag != null;
		byte id3v2TailTag[] = readID3v2Tail(src, hasId3v1);

		write(src, dst, id3v1Tag, id3v2HeadTag, id3v2TailTag);
	}

	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())


		if (!src.getName().toLowerCase().endsWith(".mp3"))


		if (dst == null)


		if (dst.exists())
		{
			dst.delete();

		}

		boolean hasId3v1 = hasID3v1(src);

		long id3v1Length = hasId3v1 ? 128 : 0;
		long id3v2HeadLength = findID3v2HeadLength(src);
		long id3v2TailLength = 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, 8192);

			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)
			{

			}
			try
			{
				if (os != null)
					os.close();
			} catch (Throwable e)
			{

			}
		}
	}

	private final byte[] readArray(InputStream is, int length)
			throws IOException
	{
		byte result[] = new byte[length];
		int total = 0;
		while (total < length)
		{
			int read = is.read(result, total, length - total);
			if (read < 0)
				throw new IOException("bad read");
			total += read;
		}
		return result;
	}

	/**
	 * Reads all metadata (ID3v1 & ID3v2) from MP3 file.
	 * <p>
	 * 
	 * @param file
	 *            File to read metadata (ie. song data) from.
	 * @return MusicMetadataSet, a set of MusicMetadata value collections.
	 * @see MusicMetadataSet, MusicMetadata
	 */
	public MusicMetadataSet read(File file) throws IOException
	{
		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 MusicMetadata value collections.
	 * @see MusicMetadataSet, MusicMetadata
	 */
	public MusicMetadataSet read(File file, MyID3Listener listener)
			throws IOException
	{
		try
		{
			if (file == null || !file.exists())
				return null;

			if (!file.getName().toLowerCase().endsWith(".mp3"))
				return null;
			
			ID3Tag id3v1 = readID3v1(listener, file);
			ID3Tag.V2 id3v2 = readID3v2(listener, file, id3v1 != null);

			MusicMetadataSet result = MusicMetadataSet.factoryMethod(id3v1,
					id3v2, file.getName(), file.getParentFile().getName());

			return result;
		} catch (Error e)
		{

			throw e;
		} catch (IOException e)
		{

			throw e;
		}
	}

	private ID3Tag readID3v1(File file) throws IOException
	{
		return readID3v1(null, file);
	}

	private ID3Tag readID3v1(MyID3Listener listener, File file)
			throws IOException
	{
		if (file == null || !file.exists())
			return null;

		long length = file.length();

		if (length < 128)
			return null;

		byte bytes[];
		InputStream is = null;
		try
		{
			is = new FileInputStream(file);
			is = new BufferedInputStream(is, 8192);

			is.skip(length - 128);

			bytes = readArray(is, 128);
		} finally
		{
			try
			{
				if (is != null)
					is.close();
			} catch (IOException e)
			{

			}
		}

		if (bytes[0] != 'T')
			return null;
		if (bytes[1] != 'A')
			return null;
		if (bytes[2] != 'G')
			return null;

		if (null != listener)
			listener.log("ID3v1 tag found.");

		MyID3v1 id3v1 = new MyID3v1();
		MusicMetadata tags = id3v1.parseTags(listener, bytes);

		return new ID3Tag.V1(bytes, tags);
	}

	private boolean hasID3v1(File file) throws IOException
	{
		if (file == null || !file.exists())
			return false;

		long length = file.length();

		if (length < 128)
			return false;

		byte bytes[];
		InputStream is = null;
		try
		{
			is = new FileInputStream(file);
			is = new BufferedInputStream(is, 8192);

			is.skip(length - 128);

			bytes = readArray(is, 128);
		} finally
		{
			try
			{
				if (is != null)
					is.close();
			} catch (IOException e)
			{

			}
		}

		if (bytes[0] != 'T')
			return false;
		if (bytes[1] != 'A')
			return false;
		if (bytes[2] != 'G')
			return false;

		return true;
	}

	private static final int ID3v2_HEADER_LENGTH = 10;

	private byte[] readID3v2Head(File file) throws IOException
	{

		if (file == null || !file.exists())
			return null;

		long length = file.length();

		if (length < ID3v2_HEADER_LENGTH)
			return null;

		InputStream is = null;
		try
		{
			is = new FileInputStream(file);
			is = new BufferedInputStream(is, 8192);

			byte header[];
			header = readArray(is, ID3v2_HEADER_LENGTH);

			if (header[0] != 0x49) // I
				return null;
			if (header[1] != 0x44) // D
				return null;
			if (header[2] != 0x33) // 3
				return null;

			int flags = header[5];
			boolean has_footer = (flags & (1 << 4)) > 0;

			Number tagLength = MyID3v2Read.readSynchsafeInt(header, 6);
			if (tagLength == null)
				return null;

			int bodyLength = tagLength.intValue();
			if (has_footer)
				bodyLength += ID3v2_HEADER_LENGTH;

			if (ID3v2_HEADER_LENGTH + bodyLength > length)
				return null;

			byte body[] = readArray(is, bodyLength);

			byte result[] = new byte[header.length + body.length];

			System.arraycopy(header, 0, result, 0, header.length);
			System.arraycopy(body, 0, result, header.length, body.length);

			return result;
		} finally
		{
			try
			{
				if (is != null)
					is.close();
			} catch (IOException e)
			{

			}
		}
	}

	private long findID3v2HeadLength(File file) throws IOException
	{
		if (file == null || !file.exists())
			return 0;

		long length = file.length();

		if (length < ID3v2_HEADER_LENGTH)
			return 0;

		InputStream is = null;
		try
		{
			is = new FileInputStream(file);
			is = new BufferedInputStream(is, 8192);

			byte header[];
			header = readArray(is, ID3v2_HEADER_LENGTH);

			if (header[0] != 0x49) // I
				return 0;
			if (header[1] != 0x44) // D
				return 0;
			if (header[2] != 0x33) // 3
				return 0;

			int flags = header[5];
			boolean has_footer = (flags & (1 << 4)) > 0;

			Number tagLength = MyID3v2Read.readSynchsafeInt(header, 6);
			if (tagLength == null)
				return 0;

			int totalLength = ID3v2_HEADER_LENGTH + tagLength.intValue();
			if (has_footer)
				totalLength += ID3v2_HEADER_LENGTH;

			return totalLength;
		} finally
		{
			try
			{
				if (is != null)
					is.close();
			} catch (IOException e)
			{

			}
		}
	}

	private int findID3v2TailLength(File file, boolean hasId3v1)
			throws IOException
	{
		if (file == null || !file.exists())
			return 0;

		long length = file.length();

		int index = hasId3v1 ? 128 : 0;
		index += ID3v2_HEADER_LENGTH;

		if (index > length)
			return 0;

		InputStream is = null;
		try
		{
			is = new FileInputStream(file);
			is = new BufferedInputStream(is, 8192);

			is.skip(length - index);

			byte footer[];
			footer = readArray(is, ID3v2_HEADER_LENGTH);

			if (footer[0] != 0x33) // 3
				return 0;
			if (footer[1] != 0x44) // D
				return 0;
			if (footer[2] != 0x49) // I
				return 0;

			Number tagLength = MyID3v2Read.readSynchsafeInt(footer, 6);
			if (tagLength == null)
				return 0;

			int totalLength = ID3v2_HEADER_LENGTH + ID3v2_HEADER_LENGTH
					+ tagLength.intValue();

			return totalLength;
		} finally
		{
			try
			{
				if (is != null)
					is.close();
			} catch (IOException e)
			{

			}
		}
	}

	private byte[] readID3v2Tail(File file, boolean hasId3v1)
			throws IOException
	{
		if (file == null || !file.exists())
			return null;

		long length = file.length();

		int index = hasId3v1 ? 128 : 0;
		index += ID3v2_HEADER_LENGTH;

		if (index > length)
			return null;

		InputStream is = null;
		try
		{
			is = new FileInputStream(file);
			is = new BufferedInputStream(is, 8192);

			is.skip(length - index);

			byte footer[];
			footer = readArray(is, ID3v2_HEADER_LENGTH);

			if (footer[2] != 0x33) // 3
				return null;
			if (footer[1] != 0x44) // D
				return null;
			if (footer[0] != 0x49) // I
				return null;

			Number tagLength = MyID3v2Read.readSynchsafeInt(footer, 6);
			if (tagLength == null)
				return null;

			int bodyLength = tagLength.intValue();
			if (index + bodyLength > length)
				return null;

			is.close();
			is = null;

			is = new FileInputStream(file);
			is = new BufferedInputStream(is, 8192);

			long skip = length;
			skip -= ID3v2_HEADER_LENGTH;
			skip -= bodyLength;
			skip -= ID3v2_HEADER_LENGTH;
			if (hasId3v1)
				skip -= 128;
			is.skip(skip);

			byte header_and_body[] = readArray(is, ID3v2_HEADER_LENGTH
					+ bodyLength + ID3v2_HEADER_LENGTH);

			byte result[] = header_and_body;

			return result;
		} finally
		{
			try
			{
				if (is != null)
					is.close();
			} catch (IOException e)
			{

			}
		}
	}

	private ID3Tag.V2 readID3v2(MyID3Listener listener, File file,
			boolean hasId3v1) throws IOException
	{
		if (file == null || !file.exists())
			return null;

		byte bytes[] = null;
		bytes = readID3v2Tail(file, hasId3v1);
		if (bytes == null)
			bytes = readID3v2Head(file);

		if (bytes == null)
			return null;

		if (null != listener)
			listener.log("ID3v2 tag found: " + bytes.length + " bytes");

		MyID3v2Read parser = new MyID3v2Read(listener,
				new ByteArrayInputStream(bytes), false);
		while (!parser.isComplete())
		{
			parser.iteration();
		}
		if (parser.isError())
		{
			if (listener != null)
				listener.log("id3v2 error", parser.getErrorMessage());
			
			parser.dump();
			return null;
		}

		if (!parser.hasTags())
			return null;

		Vector tags = parser.getTags();

		MusicMetadata values = new ID3v2DataMapping().process(tags);

		byte version_major = parser.getVersionMajor();
		byte version_minor = parser.getVersionMinor();

		if (null != listener)
			listener.log();

		return new ID3Tag.V2(version_major, version_minor, bytes, values, tags);
	}

}