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.
Comments
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?
Phillip, check the last entry. I believe I attached a simple test script that.
<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>
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
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
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
Thanks, and BTW this is a pretty cool cfc, I have been looking everywhere for this.
bitrate: bad file The selected method getBitRate was not found
<cffunction name="getMp3BitRate" access="public" returnType="string" output="false"
hint="Returns the mp3 bit rate.">
<cfreturn variables.id3tag.getBitRate()>
</cffunction>
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()>
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.
http://javamusictag.sourceforge.net/api/org/farng/...
I do not see a getLength().
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
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...
Thanks,
Lance
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
Head over to my blog (http://flexdojo.blogspot.com/) for a simple mp3Manager cfc that uses the MyID3 library.
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>


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