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.
Archived 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
His library does support writing. I'd suggest going to his site and checking the API out. It isn't that hard.
Check his quick start too. Notice the simple API like setSongTitle(), and then save(). Very trivial.
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?
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.
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.
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>
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.sourcef... 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
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.
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.sourcef...
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
You are using the wrong variable. You want to use variables.id3tag. Notice how my other methods did.
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
You need to run ColdFusion as a user who has access to that other server and then make a mapped drive for it.
Does anyone know how to read a MP3 duration using this CFC?
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.
What is going wrong? Also, if you are storing the CFC in the app scope, don't forget to refresh it.
It must be the whole app scope thing, how do I refresh that?
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
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>
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()>
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.
In looking at the API doc for mp3,
http://javamusictag.sourcef...
I do not see a getLength().
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
You got me there. I don't see a way to get the object class from the Mp3.
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/...
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.co...
Here are the methods for helliker:
http://www.jukex.org/docs/a...
Ya! Worked great for me, thanks for the tip man!
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
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.
@ 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
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.
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.c.... 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.
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>
Certainly is. I'll edit the blog entry now. Thanks Adrian.
I wondered if you or anyone could explain how to add song *duration* to the class. I read through the documentation but I have a bit of trouble understanding anything java.
Can somebody help me out with extracting 'comments' from an MP3 file. The original post by Ray included this but with that code the 'title' was only returning 30 characters. Using the CFC on this post I can now get the full title but am unable to get the comments... Thanks in advance
Lee - sorry, which CFC? It sounds like you said my code doesn't get the title right but does do comments, but then some other CFC does title right and not comments. Which CFC?
Ray, sorry for any confusion. What I mean is, when I run the code on your original post I see the comments for all MP3s, but the output for the title weirdly only shows the first 30 characters.
I'm now running the CFC on this second post, along with the example code posted in a comment by you on 10 July 2006, at 8:30 AM (a long time ago, I appreciate!) and the full title is being displayed, which is great! However, I can't see any way of extracting the comments by using this CFC.
Hope that makes sense and thanks for the quick reply!
It does make sense. I'll try to tackle this @ lunch.
Add this to the CFC:
<cffunction name="getSongComment" access="public" returnType="string" output="false"
hint="Returns the song title.">
<cfreturn variables.id3tag.getSongComment()>
</cffunction>
That's all you need. Surprised this library still works after 4 years. ;)
Add this to the CFC:
<cffunction name="getSongComment" access="public" returnType="string" output="false"
hint="Returns the song title.">
<cfreturn variables.id3tag.getSongComment()>
</cffunction>
That's all you need. Surprised this library still works after 4 years. ;)
Thanks Ray. I added this to the CFC and tried it out. The MP3 I tested it out on had a comment of 'purina.jpg' (the client stores logo info in the MP3 meta-data - the idea being that this info can be extracted and then used to display a logo). The code on your original post pulls this fine but with the addition to the CFC it pulls this -
cfmp32ecfc95810042$funcGETSONGCOMMENT@144b1056
I've got no idea where that comes from ?!
You did use parens, ie, getSongComment(), in your code, right? Instead of getSongComment.
Ooops! I've put them in. Now it's not displaying anything
Then you are out of luck. :) If the Java app doesn't see that comment, then ... it doesn't work right with that mp3.
Although wait - does it work with the pre-CFC code?
Hey Ray,
Do you know if this is limited to mp3, or could this theoretically work for all id3 tags (aac, m4p, etc)?
Did you try? :)
I will now :-)
I was just seeing if anyone else had done it before before I went down a dead end.
Ray, this is quite old now, but I have been using it since you wrote it. And it's proved very useful. I"m webmaster for a radio station and of course we do a LOT of work with music files, mostly MP3s. I'm using this CFC with a few tweaks for the following:
Library maintanance: When we load new files into our library drive, I scan through the drive periodically and read in details of any mp3s i havent indexed and load the details into the SQLServer database. It's 90% reliable - the errors are related to mp3s not being properly tagged rather than issues with the cfc.
Program generation - I have a number of scripts that scan through the hard drive pulling out details of mp3s we havent played before, or are new, or are of a particular genre to make up program playlists.
On-air assistance for show hosts - I display the song currently being played or next on the list for the host so he has something to say about the song
Timing shows: when a show is designated to be a 3 hour show, that means 3 hours, plus or minus 1 second. When the show is coming up to a commitment like the end of the show or a news satellite link or an ad break, its important to time it correctly. You can't do that before the show starts because you dont know exactly how many seconds the host is going to speak for in the hour. So about 10-15 minutes to the commitment the host clicks on a link and a script calculates which songs he should play to time up to the commitment with about 5 seconds to spare for the host to speak, e.g. "News sport and weather is next, then lots more music. Stay tuned!"
Thank you for all the many contributions you made to us ColdFusion developers in your Adobe days. I just wanted you to know that it's still useful.
Mike Kear,
Windsor, NSW, Australia.
Thank you for sharing this. :)
FYI, I'm pushing up a fix to the code formatting.