Reading MP3 ID3 tags with ColdFusion

You may have heard of a file format called MP3. It is an audio format that is small and compact and easy to share. If you don't have any MP3s, you can contact that RIAA. They seem to be pretty good at finding them. If you do have a few MP3s, you may know that some programs, like iTunes, will show you information about the MP3, like song title, artist, duration, etc. This information is stored in the MP3 file itself and is called ID3. ID3 is a format to embed information in the MP3 file itself. (For more information, see the site.) Since I have a few MP3s on my hard drive, I decided to see how hard it would be to get ColdFusion to read and parse these tags. It's been done before. Christian Cantrell released code on one of the first DRKs that did this. Since this isn't public domain though, let's start from scratch and see how hard it is.

Of course, the nice thing is that most of the work is done for us. I did a quick Google search and came across the Java ID3 Tag Library. I downloaded the JAR and copied it to $CFMX_HOME/runtime/servers/lib/. I then restarted ColdFusion. So what next? I checked the project's quick start guide and saw this Java example:

File sourceFile;
MP3File mp3file = new MP3File(sourceFile);

I knew this could be translated to ColdFusion as:

<cfset mp3file = createObject("java", "xxxx")>
<cfset mp3file.init(somefilepath)>

The problem was - what to use for the class name? I fumbled around a bit and found the API documentation. I looked up MP3File in the API and saw that it's path was org.farng.mp3.MP3File. I'm not sure how folks would be able to figure that out without documentation, but I was finally able to create an instance of the object using this path.

I then checked the API and looked into how it returned ID3 tag information. The code supports two versions of ID3 information, ID3v1 and ID3v2. (There are actually subversions for each of these two versions.) For a quick test, I wrote some code to examine one of my folders and grab the information from the ID3v1 tag:

<cfset dir = "g:\music\80s\">

<cfdirectory action="list" directory="#dir#" filter="*.mp3" name="music">

<cfloop query="music">
   <cfoutput>filename = #name#<br></cfoutput>
   
   <cftry>
      <cfset mp3 = createObject("java", "org.farng.mp3.MP3File")>
      <cfset mp3.init(dir & name)>
      
      <cfset tag = mp3.getID3v1Tag()>
      
      <cfoutput>
      artist=#tag.artist#<br>
      album=#tag.album#<br>
      comment=#tag.comment#<br>
      genre=#tag.genre#<br>
      title=#tag.title#<br>
      year=#tag.year#<br>
      has v1? #mp3.hasID3v1Tag()#<br>
      has v2? #mp3.hasID3v2Tag()#<br>
      </cfoutput>
      <cfcatch>
      bad file
      </cfcatch>
   </cftry>
   <hr>
</cfloop>

How did I know what methods and fields I could use? It all came from the API. So - not the cleanest of code, but you can see how it works. I create an instance of the MP3 code, and then fetch the ID3v1 tag information. To be safer, I should have used the convenience function, hasID3v1Tag() first. Notice the try/catch around the code. From what I could tell, there was no easy way to tell the code to check first for a valid MP3 file.

So what next? This works easily enough - but what if I switch to a different Java library? Tomorrow I'll write a ColdFusion Component and show how we can wrap this into a nicer and easier to use system. What I want to end up with is a tool that is 100% ColdFusion based. The developer using the tool should have no idea that behind the scenes I'm just using a JAR file.

Related Entries

Comments

PaulH's Gravatar i've started to use mark's javaLoader for everythng java these days: http://www.compoundtheory.com/?action=javaloader.i...

even if i can get to the classpath, this approach makes upgrading the backend java bits much easier.
# Posted By PaulH | 6/13/06 6:06 AM
Nelson Winters's Gravatar Ray,

As I understand it, .jar files are the same as .zip files. So you can just open a jar file using winzip or whatever and see the name of all the class files and their paths. The class name is just the path with the class name in dot notation.
# Posted By Nelson Winters | 6/13/06 7:05 AM
Scott Stroz's Gravatar Nice!
# Posted By Scott Stroz | 6/13/06 7:45 AM
Raymond Camden's Gravatar Nelson, yep, you are right. My issue was that I saw a lot of files, and couldn't find the mp3file one. The API was just a bit easier.

Of course, the easiest will be the CFC approach I'll write about tonight.
# Posted By Raymond Camden | 6/13/06 7:58 AM
todd's Gravatar Forgive me if I'm totally off base on this, but would it be possible to apply this concept (modified of course) to say a Word doc or Excel spreadsheet to read the metadata saved in those files? I'd love to be able to read the author, comments, etc from Coldfusion.
# Posted By todd | 6/13/06 8:10 AM
Raymond Camden's Gravatar Do a Google search for POI. It's a Java based Excel reader. I'm sure it could handle the metadata no problem. Also, CF's built in Verity will read it as well, but can't be used for simple "get one doc and return just the md" type things.

I've never used POI, but I know I've heard others recommend it.
# Posted By Raymond Camden | 6/13/06 8:19 AM
Adam's Gravatar Well, you _could_ write a CFC. Or you could just use the cfID3 cfc that you edited for DRK3. ;)
# Posted By Adam | 6/13/06 9:02 AM
Raymond Camden's Gravatar No, see my first paragraph. That's not open sourced. It wasn't my project. (I just QAed it.)
# Posted By Raymond Camden | 6/13/06 9:06 AM
Adam's Gravatar Sorry, missed that part. It wasn't Christian though... it was actually the first CFC I ever wrote (following the launch of CFMX). Your QA taught me some tremendoulsy good lessons that I still follow. Thanks.
# Posted By Adam | 6/13/06 9:44 AM
Raymond Camden's Gravatar Oh, sorry! It was so long ago. :)
# Posted By Raymond Camden | 6/13/06 9:44 AM
Mario Rodrigues's Gravatar Todd,

If you are on a windows box, you can use the dsofile.dll (from Microsoft) to read the OLE properties from Office documents.

See the following code:

objDSOFile = CreateObject("com","DSOleFile.PropertyReader");
try {
   objDocProp = objDSOFile.GetDocumentProperties(arguments.file);
   flObj.Author = objDocProp.author;
   flObj.Name = objDocProp.name;
   flObj.Title = objDocProp.title;
   flObj.Company = objDocProp.company;
   flObj.Comments = objDocProp.Comments;
} catch(any e) {
   //
}
releaseCOMObject(objDSOFile);
# Posted By Mario Rodrigues | 6/13/06 9:54 AM
Jeff Houser's Gravatar It might be worth noting the cfxid3 custom tag in the developer's exchange. It would let you read and write ID3 tags on mp3 files.

(I hope this link works )

www.adobe.com/cfusion/exchange/index.cfm#view=sn10...

I've used it for both reading and writing w/ pretty good results.
# Posted By Jeff Houser | 6/13/06 11:58 AM
Matthew Reinbold's Gravatar I was playing around with the provided code but nothing seemed to work. I ended up cfdump'ing the variables at each step and noticed that the line <cfset mp3.init(dir & name)> was actually causing a problem. If 'dir' was 'C:\music' and 'name' was 'cool.mp3' what was being passed to the instance of the java class init was 'C:\musiccool.mp3'... something that it couldn't successfully use.

I changed the line to read <cfset mp3.init(dir & '\' & name)> and all is well.

I also used Mark's javaloader to handle grabbing and managing the *.jar file - very handy and useful for those of us on hosted servers which don't allow access to the Admin control panel but enable the cfobject tag.
# Posted By Matthew Reinbold | 6/13/06 10:41 PM
Raymond Camden's Gravatar I'm glad you got it working. In the CFC version, which is done btw, I have code in there that verified the file exists. It would have caught that earlier for you. I plan on writing about the CFC either today or tomorrow. I will say one thing - the author did a darn good job cuz my job was incredibly easy.
# Posted By Raymond Camden | 6/14/06 6:41 AM
Raymond Camden's Gravatar Just an FYI guys. I did write the CFC for part 2. I just haven't written the article yet. The good news is that it will be short and sweet as the code that the Java project supplied was -super- helpful.
# Posted By Raymond Camden | 6/17/06 10:04 AM
Jeff Lemmon's Gravatar Hi Ray, have you had a chance to write the CFC that reads ID3 tags yet? I'm working on a project and would love to integrate this CFC.
# Posted By Jeff Lemmon | 6/29/06 8:57 PM
Raymond Camden's Gravatar Ugh. Sorry for taking so long. If I don't get this published next week, someone shoot me, ok?
# Posted By Raymond Camden | 6/30/06 6:12 AM
Joe P's Gravatar *bang*
# Posted By Joe P | 7/7/06 3:39 PM
Jeff Lemmon's Gravatar I hate to keep busting your chops about this! I'd love to use the existing java tool, but it doesn't work in a shared environment. Thanks man!
# Posted By Jeff Lemmon | 7/9/06 9:08 PM
Raymond Camden's Gravatar Posted. Sorry guys.
# Posted By Raymond Camden | 7/9/06 9:43 PM
Tim Gill's Gravatar This presumably works for mp3 files with version 1 tags.

I have files from iTunes on my server. They return mp3.hasID3v1Tag() as false and mp3.hasID3v2Tag() as true. So this code creates an error when you access tag.artists because tag is undefined because getID3v1Tag fails.

The obvious solution was replacing tag = mp3.getID3v1Tag() with tag = mp3.getID3v2Tag(). That "works" though what is returned is a string (with some interesting gibberish) rather than a structure.

Any ideas? Maybe I'm just being dense.
# Posted By Tim Gill | 7/13/06 2:20 PM
Raymond Camden's Gravatar Tom, check out my second blog entry. I talk about the API the author created to let you talk to v1 or v2 tags in a simple fashion.
# Posted By Raymond Camden | 7/13/06 2:31 PM
Chris's Gravatar Is there any easy way to write mp3s with Java? I have two mp3s that I want to overlay into a single mp3, but I can't find any libraries for doing this. I've seen the JMF, but it appears to be a dead project, and doesn't look like it offers much support anyways.
# Posted By Chris | 5/1/07 12:50 PM
Raymond Camden's Gravatar I haven't seen anything like that. _Maybe_ ffmpeg could do it.
# Posted By Raymond Camden | 5/1/07 8:52 PM
Mike Guzzo's Gravatar How can I use this with my server. I don't have ColdFusion admin rights but I do have admin rights to my domain space. I am at the point where I need to reference the class:

<cfset mp3 = createObject("java", "org.farng.mp3.MP3File")>
# Posted By Mike Guzzo | 3/21/08 2:27 PM
PaulH's Gravatar see the first comment.
# Posted By PaulH | 3/21/08 9:18 PM
JakeE's Gravatar The code read ID3 info from mp3's perfectly does anyone know how it could be modified to read id3 info from m4a or m4b files ?
# Posted By JakeE | 3/27/08 1:31 PM
Ric's Gravatar is there a way to do this with a WMV file? anyone know of a way to read the artist or metadata from a movie file?
# Posted By Ric | 6/2/08 5:57 PM
Raymond Camden's Gravatar @Ric: I'd recommend looking at ffmpeg.
# Posted By Raymond Camden | 6/2/08 7:48 PM
Leon Chalnick's Gravatar Ray (or anyone else) - you mentioned being able to determine the MP3 duration using this library. I can't find the method for doing that. I think I understand how to approximate the calculation if you have the file size and bit rate...but I can't get getBitRate() to return anything other than 0...? Any tips?
# Posted By Leon Chalnick | 2/3/09 11:03 AM
Raymond Camden's Gravatar If you have CF8, you can cheat. Got this from an experts exchange site - which I hate, but Google showed the code while EE hid it. I just don't want to take credit.


<cfset myfile = "/Volumes/MEDIA 2/Music/Gothic/Lycia - Bare.MP3">
      
<cfset mp3File = createObject("java", "coldfusion.util.MP3File").init(myfile)>
<cfoutput>mp3File.getDuration() : #mp3File.getDuration()#</cfoutput>
# Posted By Raymond Camden | 2/3/09 11:37 AM
Leon Chalnick's Gravatar Thanks Ray; that does seem to work...mostly. I'm not sure where it's coming up with the duration; while it often does correspond to what my MP3 player says, about 10/15% of the time, it doesn't.

I've actually have found references (in your blog and in houseoffusion) to another library, helliker.id3, that apparently provides similar functionality, but I can't find a download copy.
# Posted By Leon Chalnick | 2/3/09 12:39 PM
todd sharp's Gravatar @Leon:

You could get the duration by using FFMPEG (called with cfexecute).
# Posted By todd sharp | 2/3/09 1:14 PM
todd sharp's Gravatar ....as ray already pointed out (sorry ray, haven't been paying 100% attention to the thread)
# Posted By todd sharp | 2/3/09 1:14 PM
Leon Chalnick's Gravatar Thanks Todd. FFMPEG was mentioned in the context of writing mpeg files...didn't realize it could be used in this way too. I'll check it out.
# Posted By Leon Chalnick | 2/3/09 1:20 PM
Raymond Camden's Gravatar ffmpeg can do almost anything audio/video related. Some cool example - going into a video and grabbing stills at certain points.

The drawback is that _using_ ffmpeg can be extremely difficult. The commands are very complex imho. Once you figure it out though, it just plain works.
# Posted By Raymond Camden | 2/3/09 1:24 PM
Leon Chalnick's Gravatar Yeah, looks interesting...but the notion of installing it on my server...and then running cfexecute is well, not making me warm and fuzzy, heheh.
# Posted By Leon Chalnick | 2/3/09 1:27 PM
Raymond Camden's Gravatar Two things:

1) Any media related operation won't be super zippy. But, ffmpeg does run rather quick. I'd say... use with caution, but confidence. You want to ensure you thread/cache/handle errors.

2) There was a bug with CF+CFExecute+FFMPEG. This was fixed with a ... wait for it... hot fix. Check Adobe's site and ensure you have it.
# Posted By Raymond Camden | 2/3/09 1:29 PM
Mwoods's Gravatar Ray (or anyone),

Have you tried this on a network drive? I have even mapped the drive to my server but I still can't get it to read. It would be nice if I can use this code and dump it all to a database where I can search on etc.

Any help is appreciated!
# Posted By Mwoods | 6/3/09 12:54 PM
Raymond Camden's Gravatar You have to run the CF service as a user that has permissions for the network drive.
# Posted By Raymond Camden | 6/3/09 12:59 PM
Mwoods's Gravatar Ahhh Thanks I forgot about that. I will try that this evening!
# Posted By Mwoods | 6/3/09 2:30 PM