Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / MFC

An Update To the Wrapper Class for the libid3tag Library

4.07/5 (6 votes)
19 Apr 20064 min read 2   865  
An updated wrapper class to retrieve and modify id3 tag information for a given MP3 file.

Sample Image - MP3ID3Update.jpg

Introduction

In my previous article, I gave out a wrapper class that can be used to read MP3 id3tag information using libid3tag. However, that class can not be used to change the id3tag information. The completed class can be found in the attached Zip file. The reason to rewrite the article is that you need to know some necessary changes to the libid3tag library.

We will first take a quick look at how to use the CMP3ID3 class, then a brief introduction will be given regarding what happens behind the scenes when you update a tag frame.

Using the code

For how to read id3 tag information from an MP3 file, please refer to my first article: A Wrapper Class for the libid3tag Library. In this article, I will focus on how to modify the id3 tag information.

First of all, you need to insert the following files into your VS project:

  • MP3ID3.cpp
  • MP3ID3.h
  • MP3Info.h
  • Macro.h

Secondly, you need to get the libid3tag library. The latest version is 0.15.1b that was released on February 17, 2004. Once you download and extract the libid3tag-0.15.1b.tar.gz file, you need to replace file.c with the one in the Zip file. Then, build the lib file and link that library file to your project. Or, you can simply use the lib file contained in the Zip file.

To use the set_ methods, you will notice that all the set_ methods have a signature like this:

bool setXXXX(ustring text, bool bUpdateImmediately = false);

So what should be noticed here is the second argument, which tells the class whether you want your modification to be effective immediately or not. If you want to update more than one frame, for example, you are going to change the title, artist and the album, you should do like this:

...
//prepare mp3 in read/write mode instead of read only mode
mp3ID3.prepareMP3(your_mp3_file,false);
mp3ID3.setTitle((unsigned char*)"new title");
mp3ID3.setArtist((unsigned char*)"new artist");
mp3ID3.setAlbum((unsigned char*)"new album");
mp3ID3.update();
...

The first thing you should be careful about is that you must use read/write mode to prepare your MP3 file. The reason is obvious. The second thing is the last line, mp3ID3.update(), which will actually submit all your changes to the file. If you forget this method call, nothing will be changed in your MP3 file. However, if you only want to change the title, you can make it happen in one shot:

mp3ID3.setTitle((unsigned char*)"new title", true);

The bUpdateImmediately=true tells the class to update the MP3 file immediately. Therefore, no later call to update() is needed.

Code behind the scene

In the latest release of libid3tag, which is version 0.15.1b, you can update the id3tag only if there is no size change happening to the tag. Suppose you have a MP3 file with the title "Winter Light", and you decide to change it to "Winter Light blah blah". Now, you have ten more bytes added to the tag, and libid3tag will simply ignore this change. See the code fragment from file.c:

/*
 * NAME:    v2_write()
 * DESCRIPTION:    write ID3v2 tag modifications to a file
 */
static
int v2_write(struct id3_file *file,
         id3_byte_t const *data, id3_length_t length)
{
  int i;
  assert(!data || length > 0);

  if (data &&
      ((file->ntags == 1 && !(file->flags & ID3_FILE_FLAG_ID3V1)) ||
       (file->ntags == 2 &&  (file->flags & ID3_FILE_FLAG_ID3V1))) &&
      file->tags[0].length == length) {
    /* easy special case: rewrite existing tag in-place */

    if (fseek(file->iofile, file->tags[0].location, SEEK_SET) == -1 ||
    fwrite(data, length, 1, file->iofile) != 1 ||
    fflush(file->iofile) == EOF)
      return -1;

    goto done;
  }

  /* hard general case: rewrite entire file */
/* ... */
done:
   return 0;
}

The comment /* hard general case: rewrite entire file */ simply ignores this situation and returns 0 which tells you that "everything is fine, I did the change" (as a matter of fact, no change at all). Now, you need copy the following code and insert it into the line just below that comment:

if ( id3_file_rewrite(file,data,length) == -1 ) return -1;

The function id3_file_rewrite() will take care of three other cases:

  1. No id3tag exists in the original MP3 file.
  2. Only ID3V1 exists in the original MP3 file.
  3. Your change to the tag has caused a size increase.

In cases 1 and 2, a new ID3V2 tag will be added to the MP3 file. If ID3V1 exists as well, it will also be updated. Since ID3V1 will not be used in the case of the existence of ID3V2, the new information will simply be truncated at the length of 30. Case 3 will cause a file update in the original tag location.

The following is the code of the id3_file_rewrite function, it's not hard to understand with the help of comments. You need to copy&paste the code to the end of file.c and rebuild your libid3tag library.

/*
 * NAME:    id3_file_rewrite()
 * DESCRIPTION: reconstruct the mp3 file since
 *              the size of id3tag has been changed
 *              In this function, only the case
 *              of increase is considered.
 *
 * Added by Yubo Dong
 */
static 
int id3_file_rewrite(struct id3_file *file,
                   id3_byte_t const *data, id3_length_t length)
{
  //No tag at all, insert new tag
  if ( data && file->ntags == 0 && length ){

     if ( id3_file_v2_insert_data(_fileno(file->iofile), 
                         data, length, 0,0 ) != 0 ) return -1;

     if (search_tags(file) != 0) return -1;
     id3_tag_options(file->primary, ID3_TAG_OPTION_ID3V1,
                 (file->flags & ID3_FILE_FLAG_ID3V1) ? ~0 : 0);
     return 0;
  }

  //only ID3V1 exists
  if (data &&
      (file->ntags == 1 && (file->flags & ID3_FILE_FLAG_ID3V1)) && 
       file->tags[0].length != length) {

     if ( id3_file_v2_insert_data(_fileno(file->iofile), 
                         data, length, 0,0 ) != 0 ) return -1;
  
     if (file->tags[0].tag) {
        id3_tag_delref(file->tags[0].tag);
        id3_tag_delete(file->tags[0].tag);
     }
     if (file->tags)  free(file->tags);
     file->ntags = 0; file->tags = 0;

     if (search_tags(file) != 0) return -1;
     id3_tag_options(file->primary, ID3_TAG_OPTION_ID3V1,
                 (file->flags & ID3_FILE_FLAG_ID3V1) ? ~0 : 0);
     return 0;
  }

  //ID3V2 modified
  if (data &&
      ((file->ntags == 1 && !(file->flags & ID3_FILE_FLAG_ID3V1)) ||
       (file->ntags == 2 &&  (file->flags & ID3_FILE_FLAG_ID3V1))) &&
       file->tags[0].length != length) {

     if ( id3_file_v2_insert_data(_fileno(file->iofile), 
                                  data, length, 
                                  file->tags[0].location, 
                                  file->tags[0].length) != 0 )
         return -1;

     return 0;
  }
  return -1;
}
/*
 * NAME:  id3_file_v2_insert_data()
 * DESCRIPTION: insert data to file. The new data
 *      will be inserted into specified location
 *      with new length. File length will be changed accordingly
 * 
 * Added by Yubo Dong
 */
static 
int id3_file_v2_insert_data(int file_id,
                            id3_byte_t const *data, id3_length_t length,
                            long location,long available_space)
{
  long increment_size = length - available_space; 
  long file_length = 0,new_file_length = 0;
  char *buffer = 0,temp_buffer[102400];
  long bytes_to_read = 0, bytes_left = 0,bytes_read = 0;
  long block_size = 102400;

  if ( (file_length = _filelength(file_id)) == -1 ) return -1;
  new_file_length = file_length + increment_size;

  bytes_left = file_length - location;
  bytes_to_read = block_size;

  if ( increment_size > 0 ){
     //insert incremented space to the end of original file
     buffer = malloc(increment_size);
     if ( !buffer ) return -1;
     memset(buffer,0,increment_size);

     if ( lseek(file_id,0,SEEK_END) == -1 ) goto fail;
     if ( write(file_id,buffer,increment_size) == -1 ) goto fail;

     free(buffer); buffer = 0;

     //move data all the way to the end, leave space for new data
     bytes_left -= bytes_to_read;
     do{
        if ( bytes_to_read >= bytes_left ){
           bytes_to_read = bytes_left;
           bytes_left = location;
        }

        if ( lseek(file_id,bytes_left,SEEK_SET) == -1 ) goto fail;
        if ( (bytes_read=read(file_id, 
                temp_buffer,bytes_to_read)) == -1 )
            goto fail;

        if ( lseek(file_id,bytes_left + increment_size,SEEK_SET) == -1 )
            goto fail;
        if ( write(file_id,temp_buffer,bytes_read) == -1 )
            goto fail;

        bytes_left -= bytes_read;
        
     }while(bytes_left > location);

     //now insert new data
     if ( lseek(file_id,location,SEEK_SET) == -1 ) goto fail;
     if ( write(file_id,data,length) == -1 ) goto fail;
     if ( _commit(file_id) == -1 ) goto fail;

     return 0;
fail:  
     if ( buffer ) free(buffer);
     return -1;
  }
  return -1;
}

Compiler Setting

You might experience a symbol redefined error when you link your project to the libid3tag library; you can simply add /NODEFAULTLIB:LIBCD.LIB or /NODEFAULTLIB:LIBCMTD.LIB depending on your project.

Reference Links

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here