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); }