Welcome!

Welcome to the official BlackBerry Support Community Forums.

This is your resource to discuss support topics with your peers, and learn from each other.

inside custom component

Java Development

Reply
New Developer
Posts: 15
Registered: ‎07-20-2009
My Device: Not Specified
Accepted Solution

how to retrieve ID3 art Image from an .mp3 file

Hello all,

 

Is there any way to retrieve ID3 art image from an audio file. I have successfully used MetaDataControl  API to retrieve other file properties but I can't find art image specification in it.

Developer
Posts: 1,805
Registered: ‎04-28-2009
My Device: Z10 (STL100-4)-10.2.1.3253, Z10 (STL100-3)-10.3.1.997 Dev OS, Z30 (STA100-5)-10.3.1.997 Dev OS, Passport (SQW100-1)-10.3.0.1418, PlayBook (16GB)-2.1.0.1917

Re: how to retrieve ID3 art Image from an .mp3 file

http://en.wikipedia.org/wiki/ID3

http://www.id3.org/Developer_Information

 

In general read the MP3 file and search for the ID3 header. Then search through the tags until you find one for the art work. It should be in JPG, or PNG (complete images, just basically copied and pasted into the file). Read the data and load it with either Bitmap or EncodedImage. At least that's what I think you can do to get them.

---Spends time in #blackberrydev on freenode (IRC)----
Three simple rules:
1. Please use the search bar before making new posts.
2. "Like" posts that you find helpful.
3. If a solution has been found for your post, mark it as solved.
--I code too much. Well, too bad.
New Developer
Posts: 15
Registered: ‎07-20-2009
My Device: Not Specified

Re: how to retrieve ID3 art Image from an .mp3 file

thanks rcmania25 for the suggestion,

 

I have read .mp3 file containing art image in bytes, but the bytes i get are not in readable format, i don't know what exact header name i should search for? The document links you given me appears to be so confusing for me. plz try to explain about the art image header if you can.

 

Thanks in advance. 

Developer
Posts: 34
Registered: ‎09-15-2009
My Device: Not Specified

Re: how to retrieve ID3 art Image from an .mp3 file

I would like to state that album art shouldn't be in the MP3 to begin with. An external .png in the album folder would work just as good, thats how I use my whole music collection in Foobar200o and Rockbox on my iPod.

 

Putting the same image in 20 songs takes 20 times more space + you could easily open an <foldername of .mp3>/cover.png file instead of having to find a solution to this problem :smileyhappy:

Developer
Posts: 1,805
Registered: ‎04-28-2009
My Device: Z10 (STL100-4)-10.2.1.3253, Z10 (STL100-3)-10.3.1.997 Dev OS, Z30 (STA100-5)-10.3.1.997 Dev OS, Passport (SQW100-1)-10.3.0.1418, PlayBook (16GB)-2.1.0.1917

Re: how to retrieve ID3 art Image from an .mp3 file

Size wise, storing an image inside a file is a waste of space but convenience wise it is much easier to just include the image. The person who started this post asked how to get the image from a MP3 file (which is quite common) and I am working on a quick amount of code to get the image.

---Spends time in #blackberrydev on freenode (IRC)----
Three simple rules:
1. Please use the search bar before making new posts.
2. "Like" posts that you find helpful.
3. If a solution has been found for your post, mark it as solved.
--I code too much. Well, too bad.
Developer
Posts: 1,805
Registered: ‎04-28-2009
My Device: Z10 (STL100-4)-10.2.1.3253, Z10 (STL100-3)-10.3.1.997 Dev OS, Z30 (STA100-5)-10.3.1.997 Dev OS, Passport (SQW100-1)-10.3.0.1418, PlayBook (16GB)-2.1.0.1917

Re: how to retrieve ID3 art Image from an .mp3 file

[ Edited ]

Ok I was interested in how to extract the image myself, I gave you just a generalization of what to do. I wrote a "quick" and dirty function. The fuction has many types processed even if not used and I have not tested on multiple types of images and MP3s so use at your own risk. It is not optimized and the "mime" type won't return when the runction returns because I ended up creatig a new String instead of modifying an existing one.

 

Either way this should work:

public static net.rim.device.api.system.EncodedImage getID3Image(java.io.InputStream fs, long fsPos, long fsLength, String mimeType)
		throws java.io.UnsupportedEncodingException, java.io.IOException
{
	/*---------------------------
     * ID3 v1 tags cannot contain images, so no need to process them. 
     * ID3 v1 tags are also placed at the end of the MP3 so checking the beginning of the file won't do anything useful.
     *---------------------------
     */

    //Read the tags, searching for the album artwork
    byte[] imageData = null;
    boolean foundImage = false;
    mimeType = null;
    while (!foundImage)
    {
    	byte[] buffer = new byte[10];
    	fs.mark(10);
    	if ((fs.read(buffer, 0, 10) != 10) || !(new String(buffer, 0, 3, "UTF-8").equals("ID3")))
        {
    		fs.reset();
			break;
        }
    	fsPos += 10;
    	//Found a ID3 version 2 or greater tag

        //Now to actually parse a tag
        int majorVersion = buffer[3] & 0xFF;
        byte minorVersion = buffer[4];
        byte[] destinationArray = new byte[4];
        System.arraycopy(buffer, 6, destinationArray, 0, 4);
        //Read a 28bit int for size
        int size = (((((destinationArray[0] & 0xFF) << 0x15) | ((destinationArray[1] & 0xFF) << 14)) | ((destinationArray[2] & 0xFF) << 7)) | (destinationArray[3] & 0xFF));
        long end = fsPos + size;
        fs.mark((int)size);
        long dataLength = end - 11L;
        
        boolean ver2 = true;

        if (majorVersion == 2)
        {
            //ID3 v2.2
            ver2 = true;
        }
        else if (majorVersion == 3 || majorVersion == 4)
        {
            //ID3 v2.3/ID3 v2.4

            //Extra data seems might exist, go through
            boolean hasExtendedHeader = (buffer[5] & 0x40) == 0x40;
            if (hasExtendedHeader)
            {
                byte[] exHeadBuf = new byte[4];
                fs.read(exHeadBuf, 0, 4);
                fsPos += 4;
                int exHeadLength = (((((exHeadBuf[0] & 0xFF) << 0x18) | ((exHeadBuf[1] & 0xFF) << 0x10)) | ((exHeadBuf[2] & 0xFF) << 8)) | (exHeadBuf[3] & 0xFF));
                byte[] exHeadData = new byte[exHeadLength + 4];
                System.arraycopy(exHeadBuf, 0, exHeadData, 4, exHeadLength);
                fs.read(exHeadData, 4, exHeadLength);
                fsPos += exHeadLength;
                //No use for this data in the pic so just ignore it
            }
            ver2 = false;
        }
        
        for (boolean flag = true; (fsPos < dataLength) && flag; )
        {
        	//Get the frame header and make sure that it is a valid frame.
            byte[] fBuf = new byte[ver2 ? 6 : 10];
            if ((fs.read(fBuf, 0, fBuf.length) != fBuf.length) || ((fBuf[0] & 0xFF) <= 0))
            {
                flag = false;
                continue;
            }
            fsPos += fBuf.length;
            String frameId = new String(fBuf, 0, ver2 ? 3 : 4, "UTF-8");
            destinationArray = new byte[ver2 ? 3 : 4];
            System.arraycopy(fBuf, destinationArray.length, destinationArray, 0, destinationArray.length);
            int frameCount = 0;
            switch (majorVersion)
            {
                case 2:
                	//24bit
                    frameCount = ((((destinationArray[0] & 0xFF) << 0x10) | ((destinationArray[1] & 0xFF) << 8)) | (destinationArray[2] & 0xFF));
                    break;
                case 3:
                	//32bit
                    frameCount = (((((destinationArray[0] & 0xFF) << 0x18) | ((destinationArray[1] & 0xFF) << 0x10)) | ((destinationArray[2] & 0xFF) << 8)) | (destinationArray[3] & 0xFF));
                    break;
                case 4:
                	//28bit
                    frameCount = (((((destinationArray[0] & 0xFF) << 0x15) | ((destinationArray[1] & 0xFF) << 14)) | ((destinationArray[2] & 0xFF) << 7)) | (destinationArray[3] & 0xFF));
                    break;
                default:
                    continue;
            }
            //Now read the data and check to see if it is a picture
            fBuf = new byte[frameCount];
            if (fs.read(fBuf, 0, frameCount) == frameCount)
            {
            	fsPos += frameCount;
            	if (frameId.equals("PIC") || frameId.equals("APIC"))
            	{
            		//Got the frame data
                    int refPoint = 0;
                    //First we get the encoding type
                    int encType = (fBuf[refPoint++] & 0xFF); //0=ISO8859, 1=Unicode,2=UnicodeBE,3=UTF8
                    //Second we get the mime type
                    int indexPoint = refPoint;
                    while (fBuf[refPoint++] != 0)
                    {
                    }
                    int mimeLength = refPoint - indexPoint;
                    if (mimeLength > 1)
                    {
                        mimeType = new String(fBuf, indexPoint, mimeLength - 1, "ISO-8859-1");
                    }
                    //Third we get the picture type
                    int picType = (fBuf[refPoint++] & 0xFF);
                    //Fourth we load the picture description
                    byte[] desBuf;
                    switch (encType)
                    {
                        case 0:
                        case 3:
                            //8bit string
                            byte num;
                            net.rim.device.api.util.ByteVector list = new net.rim.device.api.util.ByteVector();
                            while ((refPoint < fBuf.length) && ((num = fBuf[refPoint++]) != 0))
                            {
                                list.addElement(num);
                            }
                            desBuf = list.toArray();
                            break;
                        case 1:
                        case 2:
                            //16bit string
                            list = new net.rim.device.api.util.ByteVector();
                            do
                            {
                                byte item = fBuf[refPoint++];
                                byte num2 = fBuf[refPoint++];
                                if ((item == 0) && (num2 == 0))
                                {
                                    break;
                                }
                                if (((item != 0xff) || (num2 != 0xfe)) || (encType != 1))
                                {
                                    list.addElement(item);
                                    list.addElement(num2);
                                }
                            }
                            while (refPoint < (fBuf.length - 1));
                            desBuf = list.toArray();
                            break;
                        default:
                        	throw new java.io.UnsupportedEncodingException("Cannot get picture description. Frame Encoding is invalid.");
                    }
                    String description;
                    switch (encType)
                    {
                        case 0:
                            description = new String(desBuf, "ISO-8859-1");
                            break;
                        case 1:
                        	description = new String(desBuf, "UTF-16");
                            break;
                        case 2:
                        	description = new String(desBuf, "UTF-16BE");
                            break;
                        case 3:
                        	description = new String(desBuf, "UTF-8");
                            break;
                    }
                    //Finally, THE MAIN EVENT, the image data
                    int imCount = fBuf.length - refPoint;
                    imageData = new byte[imCount];
                    System.arraycopy(fBuf, refPoint, imageData, 0, imCount);
                    foundImage = true;
                    break;
            	}
            }
            continue;
        }
        fs.reset();
        continue;
    }
    if (imageData != null)
    {
        //We found the image
    	if(mimeType != null && mimeType.length() > 0)
    	{
    		//Save some time in searching for image type
    		return net.rim.device.api.system.EncodedImage.createEncodedImage(imageData, 0, imageData.length, mimeType);
    	}
    	else
    	{
    		return net.rim.device.api.system.EncodedImage.createEncodedImage(imageData, 0, imageData.length);
    	}
    }
    //No image found
    mimeType = null;
    return null;
}

 The example code I used was:

try
{
	javax.microedition.io.file.FileConnection file = (javax.microedition.io.file.FileConnection)javax.microedition.io.Connector.open(path, javax.microedition.io.Connector.READ);
	if(file.exists())
	{
		java.io.InputStream fs = file.openInputStream();
		long pos = 0L;
		long length = file.fileSize();
		String mime = "";
		net.rim.device.api.system.EncodedImage image = getID3Image(fs, pos, length, mime);
		fs.close();
	}
	file.close();
}
catch(Exception e)
{
}

 

Just set the "path" value and an image will be returned.

 

Edit: Added one if statement that changed the ver2 boolean. Also the length and position probably don't matter but it was just for safety, though I specify that it might decode the description with UTF-16, the javadoc for 4.7.0 (the version I made and tested this with) says that it supports UTF-16 (Big Endian) but doesn't specify if UTF-16 (not Big Endian) is supported. Finally some optimization points could be:

  • removing the descriptions (don't know 100% if they determine if it is album art, publisher logo, etc.)
  • removing image type (picType variable)
  • removing the minor version
  • modifying (if you decide to keep it) the image description "get byte" code so it simply counts the bytes and lets Java take care of getting the bytes (similar to what the code does for the MIME type)
  • replaceing the MIME type parameter with a StringBuffer so that you can get the MIME type when the function returns.
  • removing the "if(majorVersion == 2)" block because ver2 is set to true already and doesn't need to be set again.

Edit 2: Fixed a quick oops, also found what the image type (picType variable) value defines:

  • x00     Other
  • x01     32x32 pixels 'file icon' (PNG only)
  • x02     Other file icon
  • x03     Cover (front)
  • x04     Cover (back)
  • x05     Leaflet page
  • x06     Media (e.g. lable side of CD)
  • x07     Lead artist/lead performer/soloist
  • x08     Artist/performer
  • x09     Conductor
  • x0A     Band/Orchestra
  • x0B     Composer
  • x0C     Lyricist/text writer
  • x0D     Recording Location
  • x0E     During recording
  • x0F     During performance
  • x10     Movie/video screen capture
  • x11     A bright coloured fish
  • x12     Illustration
  • x13     Band/artist logotype
  • x14     Publisher/Studio logotype

Taken from: http://www.id3.org/id3v2.3.0

---Spends time in #blackberrydev on freenode (IRC)----
Three simple rules:
1. Please use the search bar before making new posts.
2. "Like" posts that you find helpful.
3. If a solution has been found for your post, mark it as solved.
--I code too much. Well, too bad.
New Developer
Posts: 15
Registered: ‎07-20-2009
My Device: Not Specified

Re: how to retrieve ID3 art Image from an .mp3 file

Thanks alot rcmania25 for your help and support.

 

I am able to retrieve the art image with the help of your code. Although I am very poor in understanding byte operations but the code is well commented to understand. Again thanks :smileyhappy:

Developer
Posts: 1,805
Registered: ‎04-28-2009
My Device: Z10 (STL100-4)-10.2.1.3253, Z10 (STL100-3)-10.3.1.997 Dev OS, Z30 (STA100-5)-10.3.1.997 Dev OS, Passport (SQW100-1)-10.3.0.1418, PlayBook (16GB)-2.1.0.1917

Re: how to retrieve ID3 art Image from an .mp3 file

Yes some of the byte operations are odd, they were probably trying to save space when the image was added. I only tested the code with a MP3 that had a ID3v2.3 tag so I don't know how it will work with other versions but I tried to implement all versions. Glad I could help.

---Spends time in #blackberrydev on freenode (IRC)----
Three simple rules:
1. Please use the search bar before making new posts.
2. "Like" posts that you find helpful.
3. If a solution has been found for your post, mark it as solved.
--I code too much. Well, too bad.
New Developer
Posts: 1
Registered: ‎01-12-2010
My Device: Curve 8330m

Re: how to retrieve ID3 art Image from an .mp3 file

I believe your first "switch (encType)" should be "switch (picType)" ...

Developer
Posts: 1,805
Registered: ‎04-28-2009
My Device: Z10 (STL100-4)-10.2.1.3253, Z10 (STL100-3)-10.3.1.997 Dev OS, Z30 (STA100-5)-10.3.1.997 Dev OS, Passport (SQW100-1)-10.3.0.1418, PlayBook (16GB)-2.1.0.1917

Re: how to retrieve ID3 art Image from an .mp3 file

Actually it is correct (you made me worry that I messed up for a second but I then reread the ID3 format). Thank you for making me double check it.

 

It is possible to have 1 of 4 text formats (ISO8859, Unicode, UnicodeBE, UTF8), they are saved in a different manner in the ID3 tag (8bit, 16bit) so I can optimize the reader by combining text formats in the first switch statement. The second switch statement takes the read bytes and converts them to the proper string.

 

Again thanks for pointing it out but the picType is totally unrelated to the rest of the data, it is simply a piece of information.

---Spends time in #blackberrydev on freenode (IRC)----
Three simple rules:
1. Please use the search bar before making new posts.
2. "Like" posts that you find helpful.
3. If a solution has been found for your post, mark it as solved.
--I code too much. Well, too bad.