Reading MP3 ID3 tags with ColdFusion (2)

So, almost a month ago I wrote about reading MP3 information using ColdFusion. I promised a follow up the next day and ended up getting a bit busy with a new job, trips, etc. The sad thing is that I wrote the code a few days afterwards and it ended up being exceptionally simple. So - first off - sorry for the delay. Let's look at the CFC I ended up with. As you can see, it is so simple I can share all the code right here:

<cfcomponent displayName="MP3" hint="Reads ID3 information from an MP3" output="false">

   <cfset variables.filename = "">
   <cfset variables.loaded = false>
   <cfset variables.id3tag = "">
   
   <cffunction name="init" access="public" returnType="mp3" output="false">
      <cfargument name="filename" type="string" required="false">
      
      <!--- create an instance of the java code --->
      <cfset variables.mp3 = createObject("java", "org.farng.mp3.MP3File")>

      <cfif structKeyExists(arguments, "filename")>
         <!--- read it in --->
         <cfset variables.filename = arguments.filename>
         <cfset read(variables.filename)>         
      </cfif>
      
      <cfreturn this>
   </cffunction>
   
   <cffunction name="checkLoaded" access="private" returnType="void" output="false"
            hint="Helper function to throw error if no mp3 loaded.">
      <cfif not variables.loaded>
         <cfthrow message="You must first read in an MP3!">
      </cfif>
   </cffunction>

   <cffunction name="getAlbumTitle" access="public" returnType="string" output="false"
            hint="Returns the album title.">
      <cfreturn variables.id3tag.getAlbumTitle()>
   </cffunction>

   <cffunction name="getSongGenre" access="public" returnType="string" output="false"
            hint="Returns the song genre.">
      <cfreturn variables.id3tag.getSongGenre()>
   </cffunction>
   
   <cffunction name="getSongTitle" access="public" returnType="string" output="false"
            hint="Returns the song title.">
      <cfreturn variables.id3tag.getSongTitle()>
   </cffunction>

   <cffunction name="getTrackNumber" access="public" returnType="string" output="false"
            hint="Returns the song title.">
      <cfreturn variables.id3tag.getTrackNumberOnAlbum()>
   </cffunction>

   <cffunction name="getYearReleased" access="public" returnType="string" output="false"
            hint="Returns the song's release date.">
      <cfreturn variables.id3tag.getYearReleased()>
   </cffunction>
   
   <cffunction name="hasID3V1" access="public" returnType="boolean" output="true"
            hint="Returns true if the mp3 has id3v1 information.">
      <cfset checkLoaded()>

      <cfreturn variables.mp3.hasID3v1Tag()>
   </cffunction>

   <cffunction name="hasID3V2" access="public" returnType="boolean" output="false"
            hint="Returns true if the mp3 has id3v2 information.">
      <cfset checkLoaded()>
      
      <cfreturn variables.mp3.hasID3v2Tag()>
   </cffunction>
   
   <cffunction name="read" access="public" returnType="void" output="false">
      <cfargument name="filename" type="string" required="true">

      <!--- does the file exist? --->   
      <cfif not fileExists(arguments.fileName)>
         <cfthrow message="#arguments.fileName# does not exist.">
      </cfif>

      <!--- copy to global scope --->
      <cfset variables.filename = arguments.filename>
      
      <cftry>
         <cfset variables.mp3.init(variables.filename)>
         <cfset variables.loaded = true>
         
         <cfif hasID3V1()>
            <cfset variables.id3tag = variables.mp3.getID3v1Tag()>
         </cfif>
         <cfif hasID3V2()>
            <cfset variables.id3tag = variables.mp3.getID3v2Tag()>
         </cfif>
         
         <cfcatch>
            <cfthrow message="Invalid MP3 file: #arguments.filename# #cfcatch.message#">
         </cfcatch>
      </cftry>
   </cffunction>
   
</cfcomponent>

So - first - a recap. In the last entry I talked about the Java ID3 Tag Library. This is the open source project that I'm wrapping with ColdFusion. ID3 tags are the encoded information in the MP3 file that tslks about the song. It contains different bits of information based on the style of ID3 tag used in the file. There are two main version of ID3, and various sub versions of each. The Java ID3 Tag Library supports working with both main styles of ID3 tags and has specific API calls to work with them.

But - and this is why I love the project so much - the author also wrote a set of simple methods that will work with any version ID3 tag. In the code above, check out getAlbumTitle and getSongTitle. While I could have used specific API calls for the two versions of ID3, I didn't have to since there were generic functions built into the code.

To be honest, I got lucky. This was one of the first Java libraries I found, and it just turned out to be darn easy and useful. So how could you use this? You can imagine a site that let's users upload mp3s. (Legal of course.) Instead of asking the user to enter information about the song, you can use ColdFusion to read out all the ID3 information automatically.

Anyway - let me know if you actually use this on a production site. I'd be curious to see it in use.

Related Entries

Comments

Interesting posting Ray.

I'm working on a project where I need to write ID3 tags to a MP3 file as a simple form of copyright protection. The idea is that the file would be stamped with the person who downloaded the file. Not fool proof I know but a cheap form of DRM.

How easy do you think it would be to extend your cfc to offer wrting methods?

dickbob
# Posted By dickbob | 7/10/06 5:17 AM
His library does support writing. I'd suggest going to his site and checking the API out. It isn't that hard.
# Posted By Raymond Camden | 7/10/06 6:20 AM
Check his quick start too. Notice the simple API like setSongTitle(), and then save(). Very trivial.
# Posted By Raymond Camden | 7/10/06 6:21 AM
I would have made the "read" function returntype="string" and did a <cfreturn result>, with result being either getID3v1Tag or getID3v2Tag.

OK, I'm a little slow on the uptake.
How do we use this component again?
So far I've got:

<cfset mp3Obj = createObject("Component", "mp3")>
<cfset dir = "c:\inetpub\wwwroot\mymusic\">
<cfdirectory name="music" action="list" directory="#dir#" filter="*.mp3">
<cfloop query="music">
<cfoutput>
Filename = #name#<br />
Album Title = #mp3Obj.getAlbumTitle#<br />
</cfoutput>
</cfloop>

Do I call the read function?
# Posted By Phillip Senn | 7/10/06 8:12 AM
Read is my way of saying load, so I think void makes sense.

Phillip, check the last entry. I believe I attached a simple test script that.
# Posted By Raymond Camden | 7/10/06 8:20 AM
I reread the last entry (dated June 13, 2006) but couldn't find a test script. So then I reread the last entry before this one, but it was about Upcoming Speaking Engagements.
# Posted By Phillip Senn | 7/10/06 8:25 AM
Ok, here is my test script. Forgive any freaky formatting.


<cfset mp3 = createObject("component", "mp3").init()>

<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.read(dir & name)>
      <cfoutput>
      has v1? #mp3.hasid3v1()#<br>
      has v2? #mp3.hasid3v2()#<br>
      title: #mp3.getSongTitle()#<br>
      album: #mp3.getAlbumTitle()#<br>
      genre: #mp3.getSongGenre()#<br>
      track: #mp3.getTrackNumber()#<br>
      year: #mp3.getYearReleased()#<br>
      </cfoutput>
      <cfcatch>
      bad file <cfoutput>#cfcatch.message#</cfoutput>
      </cfcatch>
   </cftry>
   <hr>
</cfloop>
# Posted By Raymond Camden | 7/10/06 8:30 AM
Many thanks for knocking this out so quickly. I did have one question. I'm getting the message:

Class not found: org.farng.mp3.MP3File

I assume I'm supposed to download the Java files from http://javamusictag.sourceforge.net/ and map the component to those files? Any ideas on how this might work in a shared environment where I have no ability to load these via the CF administrator? Sorry for the dumb question, but my CF skills aren't what they used to be.

-Jeff
# Posted By Jeff Lemmon | 7/11/06 1:26 AM
Jeff, yes, please read the first entry as I think it makes it a bit more clear. On a shared environment, you would need to contact tech support.
# Posted By Raymond Camden | 7/11/06 8:18 AM
Hi Ray - I was considering using this on http://www.ArtistServer.com - which is a site running on ColdFusion. I'm currenlty hosting 5,800 mp3s, and have 15,000 registered members. Your CFC didn't include a few things I need (bitrate, channel mode, frequency, and sample rate) so I looked in the API docs and found the methods for these settings.

Unfortunately, my modification either didn't work, or these methods are not complete in in the Java Library.

Before I try to seek help with the Java Library, I wanted to make sure there wasn't anything wrong with my CF code? Could you give this a quick look over? :)

I added this after your "checkLoaded" function:
==================================================

<!--- MP3 File Attributes --->
<cffunction name="getMp3BitRate" access="public" returnType="string" output="false"
hint="Returns the mp3 bit rate.">
<cfreturn variables.mp3.getBitRate()>
</cffunction>
<cffunction name="getMp3Frequency" access="public" returnType="any" output="false"
hint="Returns the mp3 frequency.">
<cfreturn variables.mp3.getFrequency()>
</cffunction>
<cffunction name="getMp3Mode" access="public" returnType="string" output="false"
hint="Returns the mp3 channel mode.">
<cfreturn variables.mp3.getMode()>
</cffunction>
<cffunction name="getMp3Version" access="public" returnType="string" output="false"
hint="Returns the mp3 Mpeg version.">
<cfreturn variables.mp3.getMpegVersion()>
</cffunction>
<!--- MP3 File Attributes --->

This is the API documentation page with these methods:
http://javamusictag.sourceforge.net/api/org/farng/...

This is what I get back for a
stereo 44100 128k mp3 file:
======================

bitrate: 0
frequency: cfmp32ecfc1980346658$funcGETMP3FREQUENCY@6df9bc
channel mode: 0
mpeg version: 0

===================
And this is what I added to the demo code, after 'year':

<div style="background-color:##F5F5F5">
    bitrate: #mp3.getMp3BitRate()#<br />
    frequency: #mp3.getMp3Frequency#<br />
    channel mode: #mp3.getMp3Mode()#<br />
    mpeg version: #mp3.getMp3Version()#
</div>


Thank you for your time and your effort on this code.

- Gideon
# Posted By Gideon Marken | 7/11/06 7:19 PM
You are using the wrong variable. You want to use variables.id3tag. Notice how my other methods did.
# Posted By Raymond Camden | 7/11/06 8:40 PM
this is a lame question:
ok, so all my mp3's are on another server... how do (or can I) use cfdirectory on another server's file system? I know this would be crazy on a production environment but just on my home servers so i can play around
# Posted By Michael White | 7/11/06 10:35 PM
You need to run ColdFusion as a user who has access to that other server and then make a mapped drive for it.
# Posted By Raymond Camden | 7/12/06 8:14 AM
Does anyone know how to read a MP3 duration using this CFC?
# Posted By Jeff Lemmon | 7/12/06 6:26 PM
On the surface it looks like adding some other fields and functions to the CFC would be a real no brainer but I am having a devil of a time trying to get songlength, bitrate, genre etc! Can someone post a sample or a revised cfc or even directions on how in the world you can pull some more info from the ID3 tag?

Thanks, and BTW this is a pretty cool cfc, I have been looking everywhere for this.
# Posted By Anthony Webb | 8/1/06 9:20 PM
What is going wrong? Also, if you are storing the CFC in the app scope, don't forget to refresh it.
# Posted By Raymond Camden | 8/1/06 9:24 PM
It must be the whole app scope thing, how do I refresh that?
# Posted By Anthony Webb | 8/2/06 5:20 PM
When I call the mp3.getMp3BitRate() I get the following repeated all the way down the page.

bitrate: bad file The selected method getBitRate was not found
# Posted By Anthony Webb | 8/2/06 5:26 PM
Sorry, and my function is:

<cffunction name="getMp3BitRate" access="public" returnType="string" output="false"
   hint="Returns the mp3 bit rate.">
   <cfreturn variables.id3tag.getBitRate()>
   </cffunction>
# Posted By Anthony Webb | 8/2/06 5:27 PM
If you are caching the creation of the CFC, then you want to stop that whole you test.

Oh - I found your issue. The Mp3 bit rate is NOT a method of the ID3 object. It is a method of the mp3 object.

This _should_ work inside a new getBitRate() method that you would add yourself to the CFC.

<cfreturn variables.mp3.getBitRate()>
# Posted By Raymond Camden | 8/3/06 8:52 AM
Ray,

You were spot on for the getBitrate method! My test files show a '0' for bitrate, but that may be an issue with my test files. I am just glad it doesnt error out. Now I am trying to get the length and have hit some issues that are very similar. I have:

<!--- MP3 File Attributes --->
   <cffunction name="getMp3BitRate" access="public" returnType="string" output="false"
   hint="Returns the mp3 bit rate.">
   <cfreturn variables.mp3.getBitRate()>
   </cffunction>
   <cffunction name="getMp3Length" access="public" returnType="string" output="false"
   hint="Returns the mp3 length.">
   <cfreturn variables.mp3.getLength()>
   </cffunction>
   <!--- MP3 File Attributes --->

But the getMP3length returns an error saying the method path was not found. Any ideas on this? I tried out the cfx id3 tag mentioned above which works great and is very very fast but it bombs on some files and your cfc reads them great. Problem is the cfc lacks some prety critical fields for me like length.

I am so close.

Thanks again for dealing with some dumb questions.
# Posted By anthony webb | 8/3/06 11:46 AM
In looking at the API doc for mp3,

http://javamusictag.sourceforge.net/api/org/farng/...

I do not see a getLength().
# Posted By Raymond Camden | 8/3/06 11:51 AM
I saw this:
getLength() - Method in class org.farng.mp3.object.ObjectNumberFixedLength

So I tried:
variables.object.getLength()

But that didnt work either :( I'm sure I am way off, I just have never dealt with java before so reading these docs is greek to me.

-Anthony
# Posted By anthony webb | 8/3/06 12:03 PM
You got me there. I don't see a way to get the object class from the Mp3.
# Posted By Raymond Camden | 8/3/06 1:36 PM
I found a java class called helliker that can pull the duration from an MP3. I actually found it when I downloaded the wimpyplayer:

http://www.wimpyplayer.com/downloads/index.php

Get the demo CF version and extract the helliker directory, placing it into C:\CFusionMX7\wwwroot\WEB-INF\classes. Restart CF. Then:

<CFOBJECT type="JAVA" action="CREATE" name="MP3File" class="helliker.id3.MP3File">
<CFSET ret = MP3File.init("C:\MP3\test.mp3")>
<CFOUTPUT>
Exists? #MP3File.id3v1Exists()#<BR>
Is MP3? #MP3File.isMP3()#<BR>
File Size? <CFSET sizeMB = Round(((MP3File.getFileSize()/ 1024)/1024))>#sizeMB# MB<BR>
File Name? #MP3File.getFileName()#<BR>
Play Time? #MP3File.getPlayingTimeString()#<BR>
Bitrate? #MP3File.getBitRate()#<BR>
</CFOUTPUT>

I pulled this little gem from here :-)

http://www.houseoffusion.com/groups/CF-Talk/messag...
# Posted By Jeff Lemmon | 8/7/06 8:28 PM
Here are the methods for helliker:

http://www.jukex.org/docs/api/helliker/id3/MP3File...
# Posted By Jeff Lemmon | 8/7/06 10:44 PM
Ya! Worked great for me, thanks for the tip man!
# Posted By Anthony Webb | 8/25/06 3:28 AM
Hi, Ray. I am trying to implement this on my server and am having problems returning the Genre for just some .mp3s. This doesn't happen to all, but some show like (17) for Rock, etc. The ID3 data looks fine everywhere else...I don't know what to do for this. Anyone else experienced this?

Thanks,
Lance
# Posted By Lance | 12/18/06 2:09 PM
If I remember the spec right - Genres _are_ numbers, or maybe they were for the older ID3 spec. So 1=Country,2=Rap, etc. You may be seeing that.
# Posted By Raymond Camden | 12/18/06 3:20 PM
@ Jeff Lemmon

Wow, great post man, i have been looking for this and infact i am torturing the clients to add the mp3 file duration themself, lol,
really you made my day :) thumbs up , million stars
# Posted By Riyaz | 1/17/08 5:44 AM
Has anyone out there had any luck figuring out how to write. I looked at the API documentation but I'm having a hard time figuring out where to even start. If anyone can help me out that would be awesome.
# Posted By Writing to an MP4 | 8/7/08 2:03 AM
I was not able to figure out how to write with this java library. However, I found a great ID3 library named MyID3 at http://www.fightingquaker.com/myid3/. And it worked the first time!. Very easy to use you will just need to download the Jakarta Regexp and Nanoxml jars as well.

Head over to my blog (http://flexdojo.blogspot.com/) for a simple mp3Manager cfc that uses the MyID3 library.
# Posted By simspace | 10/15/08 12:50 PM
Is this a mistake maybe?

If you pass the filename into via the init method, you don't set VARIABLES.filename before attempting to use it in the read() method.

So:

<cfif structKeyExists(ARGUMENTS, "filename")>
   <!--- read it in --->
   <cfset read(VARIABLES.filename)>
</cfif>

should be:

<cfif structKeyExists(ARGUMENTS, "filename")>
   <!--- read it in --->
   <cfset VARIABLES.filename = ARGUMENTS.filename>
   <cfset read(VARIABLES.filename)>
</cfif>
# Posted By Adrian Lynch | 10/16/08 7:58 AM
Certainly is. I'll edit the blog entry now. Thanks Adrian.
# Posted By Raymond Camden | 10/16/08 1:56 PM