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:
...
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:
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) {
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;
}
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:
- No id3tag exists in the original MP3 file.
- Only ID3V1 exists in the original MP3 file.
- 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.
static
int id3_file_rewrite(struct id3_file *file,
id3_byte_t const *data, id3_length_t length)
{
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;
}
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;
}
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;
}
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 ){
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;
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);
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