
org.blinkenlights.jid3.MP3File Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of JID3 Show documentation
Show all versions of JID3 Show documentation
JID3 is a class library, written in Java and licensed under the LGPL, which provides the required functionality for editing ID3 tags commonly used in MP3 media files.
The newest version!
/*
* MP3File.java
*
* Created on 7-Oct-2003
*
* Copyright (C)2003-2005 Paul Grebenc
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* $Id: MP3File.java,v 1.20 2005/11/22 02:09:14 paul Exp $
*/
package org.blinkenlights.jid3;
import java.io.*;
import java.util.*;
import org.blinkenlights.jid3.io.*;
import org.blinkenlights.jid3.v1.*;
import org.blinkenlights.jid3.v2.*;
/**
* @author paul
*
* A class representing an MP3 file.
*/
public class MP3File extends MediaFile
{
/** Construct an object representing the MP3 file specified.
*
* @param oSourceFile a File pointing to the source MP3 file
*/
public MP3File(File oSourceFile)
{
super(oSourceFile);
}
public MP3File(IFileSource oFileSource)
{
super(oFileSource);
}
/* (non-Javadoc)
* @see org.blinkenlights.id3.MediaFile#sync()
*/
public void sync()
throws ID3Exception
{
// before we write anything, check first that if there is a v2 tag to be sync'ed, it is a valid tag to write
// (it is not valid unless it has at least one frame)
if ((m_oID3V2Tag != null) && ( ! m_oID3V2Tag.containsAtLeastOneFrame()))
{
throw new ID3Exception("This file has an ID3 V2 tag which cannot be written because it does not contain at least one frame.");
}
if (m_oID3V1Tag != null)
{
// need to update the V1 tags
v1Sync();
}
if (m_oID3V2Tag != null)
{
// need to update the V2 tags
v2Sync();
}
}
/** Update the contents of the actual MP3 file to reflect the current ID3 V1 tag settings of the object.
*
* @throws ID3Exception if an error updating the file occurs
*/
private void v1Sync()
throws ID3Exception
{
IFileSource oTmpFileSource = null;
InputStream oSourceIS = null;
OutputStream oTmpOS = null;
try
{
// open source file for reading
try
{
oSourceIS = new BufferedInputStream(m_oFileSource.getInputStream());
}
catch (Exception e)
{
throw new ID3Exception("Error opening [" + m_oFileSource.getName() + "]", e);
}
try
{
// create temporary file to work with
try
{
oTmpFileSource = m_oFileSource.createTempFile("id3.", ".tmp");
}
catch (Exception e)
{
throw new ID3Exception("Unable to create temporary file.", e);
}
// open temp file for writing
try
{
oTmpOS = oTmpFileSource.getOutputStream();
}
catch (Exception e)
{
throw new ID3Exception("Error opening temporary file for writing.", e);
}
try
{
// copy over all of the source file up to but not including the V1 tags,
// if they are present
long lFileLength = m_oFileSource.length();
// copy over all of the file up to the last 128 bytes (in 64k blocks for speed, while remaining memory efficient)
byte[] abyBuffer = new byte[65536];
long lCopied = 0;
long lTotalToCopy = lFileLength - 128;
while (lCopied < lTotalToCopy)
{
long lLeftToCopy = lTotalToCopy - lCopied;
long lToCopyNow = (lLeftToCopy >= 65536) ? 65536 : lLeftToCopy;
oSourceIS.read(abyBuffer, 0, (int)lToCopyNow);
oTmpOS.write(abyBuffer, 0, (int)lToCopyNow);
lCopied += lToCopyNow;
}
// check next three bytes of source file which indicate whether this file already
// has a V1 tag on it or not
byte[] abyCheckTag = new byte[3];
oSourceIS.read(abyCheckTag);
if ( ! ((abyCheckTag[0] == 'T') && (abyCheckTag[1] == 'A') && (abyCheckTag[2] == 'G')))
{
// no V1 tag on this file... copy the rest of it over (3 + 125 = 128 bytes)
oTmpOS.write(abyCheckTag);
for (int i=0; i < 125; i++)
{
oTmpOS.write(oSourceIS.read());
}
}
// append V1 tag information to the end of the data copied from the source file
// to the temporary file
m_oID3V1Tag.write(oTmpOS);
// we're done
oTmpOS.flush();
}
finally
{
oTmpOS.close();
}
}
finally
{
oSourceIS.close();
}
// move temp file to original source file
if (! m_oFileSource.delete())
{
//HACK: This is a hack, to get around the fact that at least some JVMs are buggy, in that files which
// have been closed are hung onto, pending garbage collection. By suggesting garbage collection,
// the next time, the delete -magically- works.
int iFails = 1;
int iDelay = 1;
while (!m_oFileSource.delete())
{
System.gc(); // this will close the open file
Thread.sleep(iDelay);
iFails++;
iDelay *= 2;
if (iFails > 10)
{
throw new ID3Exception("Unable to delete original file.");
}
}
}
if (! oTmpFileSource.renameTo(m_oFileSource))
{
throw new ID3Exception("Unable to rename temporary file " + oTmpFileSource.toString() + " to " + m_oFileSource.toString() + ".");
}
}
catch (ID3Exception e)
{
throw e;
}
catch (Exception e)
{
throw new ID3Exception("Error processing [" + m_oFileSource.getName() + "].", e);
}
}
/** Update the contents of the actual MP3 file to reflect the current ID3 V2 tag settings of the object.
*
* @throws ID3Exception if an error updating the file occurs
*/
private void v2Sync()
throws ID3Exception
{
IFileSource oTmpFileSource = null;
InputStream oSourceIS = null;
OutputStream oTmpOS = null;
// check first if this tag can be written (ie. unregistered crypto agents, etc.)
m_oID3V2Tag.sanityCheck();
try
{
// open source file for reading
try
{
oSourceIS = new BufferedInputStream(m_oFileSource.getInputStream());
}
catch (Exception e)
{
throw new ID3Exception("Error opening [" + m_oFileSource.getName() + "]", e);
}
try
{
// create temporary file to work with
try
{
oTmpFileSource = m_oFileSource.createTempFile("id3.", ".tmp");
}
catch (Exception e)
{
throw new ID3Exception("Unable to create temporary file.", e);
}
// open temp file for writing
try
{
oTmpOS = new BufferedOutputStream(oTmpFileSource.getOutputStream());
}
catch (Exception e)
{
throw new ID3Exception("Error opening temporary file for writing.", e);
}
try
{
// write tag to beginning of file (if we were going to write somewhere other than the beginning,
// we'd have to deal with that somewhere in this method)
m_oID3V2Tag.write(oTmpOS);
// copy over the MP3 data from the source file to the temp file
// (need to check if there is a V2 tag in the source file, and skip over them if they exist)
byte[] abyCheckTag = new byte[3];
oSourceIS.read(abyCheckTag);
if ((abyCheckTag[0] == 'I') && (abyCheckTag[1] == 'D') && (abyCheckTag[2] == '3'))
{
// there is a tag in this file.. skip over them
// read version information
int iVersion = oSourceIS.read();
int iPatch = oSourceIS.read();
if (iVersion > 4)
{
// close and remove temp file
oTmpOS.close();
oTmpFileSource.delete();
throw new ID3Exception("Will not overwrite tag of version greater than 2.4.0.");
}
// skip flags
oSourceIS.skip(1);
// get tag length
byte[] abyTagLength = new byte[4];
if (oSourceIS.read(abyTagLength) != 4)
{
throw new ID3Exception("Error reading existing ID3 tag.");
}
ID3DataInputStream oID3DIS = new ID3DataInputStream(new ByteArrayInputStream(abyTagLength));
long iTagLength = oID3DIS.readID3Four();
oID3DIS.close();
while (iTagLength > 0)
{
long iNumSkipped = oSourceIS.skip(iTagLength);
if (iNumSkipped == 0)
{
throw new ID3Exception("Error reading existing ID3 tag.");
}
iTagLength -= iNumSkipped;
}
}
else
{
// there is no tag in this file...
oTmpOS.write(abyCheckTag);
}
// copy over rest of the file
byte[] abyBuffer = new byte[65536];
int iNumRead;
while ((iNumRead = oSourceIS.read(abyBuffer)) != -1)
{
oTmpOS.write(abyBuffer, 0, iNumRead);
}
// we're done
oTmpOS.flush();
}
finally
{
oTmpOS.close();
}
}
finally
{
oSourceIS.close();
}
// move temp file to original source file
if (! m_oFileSource.delete())
{
//HACK: This is a hack, to get around the fact that at least some JVMs are buggy, in that files which
// have been closed are hung onto, pending garbage collection. By suggesting garbage collection,
// the next time, the delete -magically- works.
int iFails = 1;
int iDelay = 1;
while (!m_oFileSource.delete())
{
System.gc(); // this will close the open file
Thread.sleep(iDelay);
iFails++;
iDelay *= 2;
if (iFails > 10)
{
throw new ID3Exception("Unable to delete original file.");
}
}
}
if (! oTmpFileSource.renameTo(m_oFileSource))
{
throw new ID3Exception("Unable to rename temporary file " + oTmpFileSource.toString() + " to " + m_oFileSource.toString() + ".");
}
}
catch (ID3Exception e)
{
throw e;
}
catch (Exception e)
{
throw new ID3Exception("Error processing [" + m_oFileSource.getName() + "].", e);
}
}
/* (non-Javadoc)
* @see org.blinkenlights.id3.MediaFile#getTags()
*/
public ID3Tag[] getTags()
throws ID3Exception
{
List oID3TagList = new ArrayList();
// get ID3V1Tag if they exist
ID3V1Tag oID3V1Tag = getID3V1Tag();
if (oID3V1Tag != null)
{
oID3TagList.add(oID3V1Tag);
}
// get ID3V2Tag if they exist
ID3V2Tag oID3V2Tag = getID3V2Tag();
if (oID3V2Tag != null)
{
oID3TagList.add(oID3V2Tag);
}
return (ID3Tag[])oID3TagList.toArray(new ID3Tag[0]);
}
public ID3V1Tag getID3V1Tag()
throws ID3Exception
{
try
{
InputStream oSourceIS = new BufferedInputStream(m_oFileSource.getInputStream());
try
{
// copy over all of the file up to the last 128 bytes
long lFileLength = m_oFileSource.length();
oSourceIS.skip(lFileLength - 128);
// check if V1 tag is present
byte[] abyCheckTag = new byte[3];
oSourceIS.read(abyCheckTag);
if ((abyCheckTag[0] == 'T') && (abyCheckTag[1] == 'A') && (abyCheckTag[2] == 'G'))
{
// there is a tag, we must read it
ID3V1Tag oID3V1Tag = ID3V1Tag.read(oSourceIS);
return oID3V1Tag;
}
else
{
return null;
}
}
finally
{
oSourceIS.close();
}
}
catch (Exception e)
{
throw new ID3Exception(e);
}
}
public ID3V2Tag getID3V2Tag()
throws ID3Exception
{
//TODO: We're only checking for v2.3.0 tags here now. We'd otherwise have to find
// the "ID3" identifier in the file first.
try
{
InputStream oSourceIS = new BufferedInputStream(m_oFileSource.getInputStream());
ID3DataInputStream oSourceID3DIS = new ID3DataInputStream(oSourceIS);
try
{
// check if v2 tag is present
byte[] abyCheckTag = new byte[3];
oSourceID3DIS.readFully(abyCheckTag);
if ((abyCheckTag[0] == 'I') && (abyCheckTag[1] == 'D') && (abyCheckTag[2] == '3'))
{
return ID3V2Tag.read(oSourceID3DIS);
}
else
{
return null;
}
}
finally
{
oSourceID3DIS.close();
}
}
catch (ID3Exception e)
{
throw e;
}
catch (Exception e)
{
throw new ID3Exception("Error reading tags from file.", e);
}
}
public void removeTags()
throws ID3Exception
{
removeID3V1Tag();
removeID3V2Tag();
}
public void removeID3V1Tag()
throws ID3Exception
{
IFileSource oTmpFileSource = null;
InputStream oSourceIS = null;
OutputStream oTmpOS = null;
try
{
// open source file for reading
try
{
oSourceIS = new BufferedInputStream(m_oFileSource.getInputStream());
}
catch (Exception e)
{
throw new ID3Exception("Error opening [" + m_oFileSource.getName() + "]", e);
}
try
{
// create temporary file to work with
try
{
oTmpFileSource = m_oFileSource.createTempFile("id3.", ".tmp");
}
catch (Exception e)
{
throw new ID3Exception("Unable to create temporary file.", e);
}
// open temp file for writing
try
{
oTmpOS = new BufferedOutputStream(oTmpFileSource.getOutputStream());
}
catch (Exception e)
{
throw new ID3Exception("Error opening temporary file for writing.", e);
}
try
{
// copy over all of the source file up to but not including the V1 tags,
// if they are present
long lFileLength = m_oFileSource.length();
// copy over all of the file up to the last 128 bytes (in 64k blocks for speed, while remaining memory efficient)
byte[] abyBuffer = new byte[65536];
long lCopied = 0;
long lTotalToCopy = lFileLength - 128;
while (lCopied < lTotalToCopy)
{
long lLeftToCopy = lTotalToCopy - lCopied;
long lToCopyNow = (lLeftToCopy >= 65536) ? 65536 : lLeftToCopy;
oSourceIS.read(abyBuffer, 0, (int)lToCopyNow);
oTmpOS.write(abyBuffer, 0, (int)lToCopyNow);
lCopied += lToCopyNow;
}
// check next three bytes of source file which indicate whether this file already
// has a V1 tag on it or not
byte[] abyCheckTag = new byte[3];
oSourceIS.read(abyCheckTag);
if ( ! ((abyCheckTag[0] == 'T') && (abyCheckTag[1] == 'A') && (abyCheckTag[2] == 'G')))
{
// no V1 tag on this file... copy the rest of it over (3 + 125 = 128 bytes)
oTmpOS.write(abyCheckTag);
for (int i=0; i < 125; i++)
{
oTmpOS.write(oSourceIS.read());
}
}
// we're done
oTmpOS.flush();
}
finally
{
oTmpOS.close();
}
}
finally
{
oSourceIS.close();
}
// move temp file to original source file
if (! m_oFileSource.delete())
{
//HACK: This is a hack, to get around the fact that at least some JVMs are buggy, in that files which
// have been closed are hung onto, pending garbage collection. By suggesting garbage collection,
// the next time, the delete -magically- works.
int iFails = 1;
int iDelay = 1;
while (!m_oFileSource.delete())
{
System.gc(); // this will close the open file
Thread.sleep(iDelay);
iFails++;
iDelay *= 2;
if (iFails > 10)
{
throw new ID3Exception("Unable to delete original file.");
}
}
}
if (! oTmpFileSource.renameTo(m_oFileSource))
{
throw new ID3Exception("Unable to rename temporary file " + oTmpFileSource.toString() + " to " + m_oFileSource.toString() + ".");
}
}
catch (ID3Exception e)
{
throw e;
}
catch (Exception e)
{
throw new ID3Exception("Error processing [" + m_oFileSource.getName() + "].", e);
}
}
public void removeID3V2Tag()
throws ID3Exception
{
IFileSource oTmpFileSource = null;
InputStream oSourceIS = null;
OutputStream oTmpOS = null;
// create temporary file to work with
try
{
oTmpFileSource = m_oFileSource.createTempFile("id3.", ".tmp");
}
catch (Exception e)
{
throw new ID3Exception("Unable to create temporary file.", e);
}
try
{
// open source file for reading
try
{
oSourceIS = new BufferedInputStream(m_oFileSource.getInputStream());
}
catch (Exception e)
{
throw new ID3Exception("Error opening [" + m_oFileSource.getName() + "]", e);
}
try
{
// open temp file for writing
try
{
oTmpOS = new BufferedOutputStream(oTmpFileSource.getOutputStream());
}
catch (Exception e)
{
throw new ID3Exception("Error opening temporary file for writing.", e);
}
try
{
// copy over the MP3 data from the source file to the temp file
// (need to check if there is a V2 tag in the source file, and skip over them if they exist)
byte[] abyCheckTag = new byte[3];
oSourceIS.read(abyCheckTag);
if ((abyCheckTag[0] == 'I') && (abyCheckTag[1] == 'D') && (abyCheckTag[2] == '3'))
{
// there is a tag in this file.. skip over it
// read version information
int iVersion = oSourceIS.read();
int iPatch = oSourceIS.read();
// skip flags
oSourceIS.skip(1);
// get tag length
byte[] abyTagLength = new byte[4];
if (oSourceIS.read(abyTagLength) != 4)
{
throw new ID3Exception("Error reading existing ID3 tags.");
}
ID3DataInputStream oID3DIS = new ID3DataInputStream(new ByteArrayInputStream(abyTagLength));
long iTagLength = oID3DIS.readID3Four();
oID3DIS.close();
while (iTagLength > 0)
{
long iNumSkipped = oSourceIS.skip(iTagLength);
if (iNumSkipped == 0)
{
throw new ID3Exception("Error reading existing ID3 tag.");
}
iTagLength -= iNumSkipped;
}
}
else
{
// there are no tags in this file...
oTmpOS.write(abyCheckTag);
}
// copy over rest of the file
byte[] abyBuffer = new byte[65536];
int iNumRead;
while ((iNumRead = oSourceIS.read(abyBuffer)) != -1)
{
oTmpOS.write(abyBuffer, 0, iNumRead);
}
// we're done
oTmpOS.flush();
}
finally
{
oTmpOS.close();
}
}
finally
{
oSourceIS.close();
}
// move temp file to original source file
if (! m_oFileSource.delete())
{
//HACK: This is a hack, to get around the fact that at least some JVMs are buggy, in that files which
// have been closed are hung onto, pending garbage collection. By suggesting garbage collection,
// the next time, the delete -magically- works.
int iFails = 1;
int iDelay = 1;
while (!m_oFileSource.delete())
{
System.gc(); // this will close the open file
Thread.sleep(iDelay);
iFails++;
iDelay *= 2;
if (iFails > 10)
{
throw new ID3Exception("Unable to delete original file.");
}
}
}
if (! oTmpFileSource.renameTo(m_oFileSource))
{
throw new ID3Exception("Unable to rename temporary file " + oTmpFileSource.toString() + " to " + m_oFileSource.toString() + ".");
}
}
catch (ID3Exception e)
{
throw e;
}
catch (Exception e)
{
throw new ID3Exception("Error processing [" + m_oFileSource.getName() + "].", e);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy