I've been having all kinds of fun with libid3tag lately. However there
were two things I couldn't do:
1. Add tags to files that do not already have ID3 tags.
2. Remove tags from files.
This seem to stem from the fact that it's a concieved as a modification
library only, not a creation or destruction library... So I wrote external
code for adding and removing tags.
Now I thought that this could perhaps be included in libid3tag. Would this
be good? If you like the idea, I could adopt my code for libid3tag (follow
your code standards etc) but just in case you think it's worth it. Below
are copies of the routines, which are written for the glib portability
library (will have to be changed of course) for inspection.
static gboolean addid3tag(gchar *path)
{
// File descriptors...
gint f1, f2, f3;
gboolean retval = FALSE;
gchar template[128];
// g_print("Adding a new ID3 tag to %s\n", path);
f1 = (gint) open(path, O_RDONLY, 0);
if (f1 < 0)
return FALSE;
// g_print("Opened file f1...\n");
// template = g_build_filename(g_get_tmp_dir(), "gnomadXXXXXX");
strcpy(template, g_get_tmp_dir());
template[strlen(template)+1] = '\0';
template[strlen(template)] = G_DIR_SEPARATOR;
strcat(template, "gnomadXXXXXX");
// Temporary file
f2 = g_mkstemp(template);
// g_print("Opened temporary file f2...\n");
if (f2 >= 0) {
register gchar *buffer;
gchar blankv2tag[0x0c0a];
guint bufsiz;
// A blank ID3v2 tag with title set to "x"
gchar v2head[] = {0x49, 0x44, 0x33, 0x03, 0x00, 0x00, 0x00, 0x00,
0x0c, 0x00, // size 0x0100
0x54, 0x50, 0x45, 0x31, 0x00, 0x00, 0x00, 0x0a,
0x00, 0x00, 0x00, 0x78};
gchar blankv1tag[] = {'T', 'A', 'G',
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00 };
// Allocate a copying buffer
for (bufsiz = 0x8000; bufsiz >= 128; bufsiz >>= 1) {
buffer = (gchar *) g_malloc(bufsiz);
if (buffer)
break;
}
// g_print("Allocated a buffer of %d bytes...\n", bufsiz);
// Add a dummy tag
// g_print("Adding a dummy ID3v2 tag...\n");
memset(blankv2tag, 0, 0x0c0a);
memcpy(blankv2tag, v2head, 22);
if (0x0c0a == write(f2,blankv2tag,0x0c0a)) {
// Copy the entire file
//g_print("Copying the original file...\n");
while (1) {
register guint n;
n = read(f1,buffer,bufsiz);
if (n == -1)
break;
if (n == 0) {
retval = TRUE;
break;
}
if (n != write(f2,buffer,n))
break;
}
}
// g_print("Adding a dummy ID3v1 tag...\n");
write(f2,blankv1tag,128);
if (retval) {
close(f1);
// Rewind the temporary file
lseek(f2, 0, SEEK_SET);
// Then copy the file back again
// g_print("Copying the file back...\n");
f3 = (gint) creat(path, (mode_t) CREATE_FILEMODE);
if (f3) {
// g_print("Creat() on original file succeeded...\n");
while (1) {
register guint n;
n = read(f2,buffer,bufsiz);
if (n == -1)
break;
if (n == 0) {
retval = TRUE;
break;
}
if (n != write(f3,buffer,n))
break;
}
close(f3);
} else {
// g_print("Error copying back!\n");
retval = FALSE;
}
close(f2);
} else {
close(f1);
close(f2);
}
// g_print("Deleting %s\n", template);
unlink(template);
// Free the buffer
g_free(buffer);
return retval;
}
close(f1);
return FALSE;
}
void remove_tag_from_mp3file(gchar *path)
{
// File descriptors...
gint f1, f2, f3;
gchar template[128];
guint header_taglength = 0;
guint footer_taglength = 0;
guint filelength = 0;
register gchar *buffer;
guint bufsiz;
gboolean retval = FALSE; // not used yet
// template = g_build_filename(g_get_tmp_dir(), "gnomadXXXXXX"); //FEL
strcpy(template, g_get_tmp_dir());
template[strlen(template)+1] = '\0';
template[strlen(template)] = G_DIR_SEPARATOR;
strcat(template, "gnomadXXXXXX");
// g_print("Removing ID3 tags from %s\n", path);
f1 = (gint) open(path, O_RDONLY, 0);
if (f1 < 0) {
return;
}
// g_print("Opened file f1...\n");
// Allocate a copying buffer
for (bufsiz = 0x8000; bufsiz >= 128; bufsiz >>= 1) {
buffer = (gchar *) g_malloc(bufsiz);
if (buffer)
break;
}
// Temporary file
f2 = g_mkstemp(template);
// g_print("Opened temporary file f2...\n");
if (f2 >= 0) {
guchar tag[10];
gint n;
// g_print("Allocated a buffer of %d bytes...\n", bufsiz);
// g_print("Looking for ID3v2 header tag\n");
n = read(f1,tag,10);
if (n == 10 &&
tag[0] == 'I' &&
tag[1] == 'D' &&
tag[2] == '3') {
// g_print("Found ID3v2 tag header...\n");
// Get tag length from the tag
header_taglength = (tag[6] << 24) + (tag[7] << 16) + (tag[8] << 8) + tag[9];
// This needs some heuristics... first scan until only zeroes are found.
// while ()
n = read(f1,buffer,bufsiz);
if (n > 4) {
guint m = 0;
gboolean foundzeros = FALSE;
gboolean founddata = FALSE;
// Look for 8 consecutive zeroes
while (!foundzeros && (m < n)) {
if (buffer[m] == 0x00 &&
buffer[m+1] == 0x00 &&
buffer[m+2] == 0x00 &&
buffer[m+3] == 0x00 &&
buffer[m+4] == 0x00 &&
buffer[m+5] == 0x00 &&
buffer[m+6] == 0x00 &&
buffer[m+7] == 0x00) {
foundzeros = TRUE;
}
m += 8;
}
// g_print("Found zeros\n");
// Then look for first non-zero
while (!founddata && (m < n)) {
if (buffer[m] == '3' &&
buffer[m+1] == 'D' &&
buffer[m+2] == 'I') {
// We found a footer, wind past it and say that we found data.
m += 10;
founddata = TRUE;
} else if (buffer[m] != 0x00) {
founddata = TRUE;
// Tag is now m bytes long!
} else {
m++;
}
}
// If length was properly detected, we now know the length.
if (founddata) {
// g_print("Found data\n");
if (header_taglength != m) {
g_print("Bad header tag! Given length: %d, detected length: %d\n", header_taglength, m);
}
// Adjust header taglength
header_taglength = m;
// Wind past any RIFF header too
header_taglength += riff_header_size(&buffer[m], n-m);
}
// At last, include the header too.
header_taglength += 10;
// g_print("ID3v2 header (and any RIFF header) %d bytes\n", header_taglength);
}
} else {
// Any plain RIFF header is removed too.
gint n;
n = read(f1,buffer,bufsiz);
header_taglength = riff_header_size(&buffer[0], n);
}
// As we move to the end of the file, detect the filelength
filelength = lseek(f1, 0, SEEK_END);
if (filelength > 0) {
// Then detect the length of any ID3v1 tag
// g_print("Detecting ID3v1 tag\n");
if (lseek(f1, -128, SEEK_END) > 0) {
guchar tag[3];
gint n;
n = read(f1,tag,3);
if (n == 3 &&
tag[0] == 'T' &&
tag[1] == 'A' &&
tag[2] == 'G') {
// g_print("Found ID3v1 tag footer...\n");
footer_taglength = 128;
}
}
// Then detect the length of any ID3v2 footer tag
// g_print("Detecting ID3v2 footer tag\n");
if (lseek(f1, -10-footer_taglength, SEEK_END) > 0) {
guchar tag[10];
guint footlen;
gint n;
n = read(f1,tag,10);
if (n == 10 &&
tag[0] == '3' &&
tag[1] == 'D' &&
tag[2] == 'I') {
// g_print("Found ID3v2 footer tag\n");
footlen = (tag[6] << 24) + (tag[7] << 16) + (tag[8] << 8) + tag[9];
// First remove the tag headers
footer_taglength += 20;
// Then remove the indicated length (no looking for bad tag here)
footer_taglength += footlen;
}
}
// g_print("Header %d bytes, footer %d bytes to be removed.\n", header_taglength, footer_taglength);
// Next skip past the header, and copy until we reach the footer
if (lseek(f1, header_taglength, SEEK_SET) > 0) {
guint remain = filelength - header_taglength - footer_taglength;
while (remain) {
register guint n;
if (remain > bufsiz) {
n = read(f1,buffer,bufsiz);
} else {
n = read(f1,buffer,remain);
}
if (n == -1)
break;
if (n == 0) {
retval = TRUE;
break;
}
if (n != write(f2,buffer,n))
break;
remain -= n;
}
}
}
close(f1);
// Then copy back the stripped file
// Rewind the temporary file
lseek(f2, 0, SEEK_SET);
// g_print("Copying the file back...\n");
f3 = (gint) creat(path, (mode_t) CREATE_FILEMODE);
if (f3) {
// g_print("Creat() on original file succeeded...\n");
while (1) {
register guint n;
n = read(f2,buffer,bufsiz);
if (n == -1)
break;
if (n == 0) {
retval = TRUE;
break;
}
if (n != write(f3,buffer,n))
break;
}
close(f3);
} else {
g_print("Error copying file back!\n");
retval = FALSE;
}
close(f2);
// g_print("Deleting %s\n", template);
unlink(template);
} else {
// In case we couldn't open f2
retval = FALSE;
close(f1);
}
g_free(buffer);
}