/* ** id3.c ** $Id: id3.c,v 1.21 2002/05/20 00:45:56 brian Exp $ ** This is something I really did not want to write. ** ** It was pretty annoying that I had to write this ** but I could not find one single ID3 library ** that had a BSD license and was written in C. */ #include "mod_mp3.h" #include "id3.h" #include const char * genre_string(int genre) { if(genre < GENRE_MAX) return mp3_genres[genre]; return NULL; } void clean_string(char *string, int length, int max) { int x = 0; int last = 0; unsigned char *clean = NULL; for(x = 0; x < length; x++) { if(!ap_isprint(string[x])) { string[x] = ' '; } else { if(!ap_isspace(string[x])) last = x; } } if(last > (length - 1)) { string[x] = '\0'; clean = &string[x]; memset(clean, 0, (max - x)); } else if(last == 0) { memset(string, 0, max); } else { string[last + 1] = '\0'; clean = &string[last + 1]; memset(clean, 0, (max - last)); } } int id3_size(unsigned char* buffer) { unsigned long size = 0; size = (buffer[3] & 0x7f) + ((buffer[2] & 0x7f) << 7) + ((buffer[1] & 0x7f) << 14) + ((buffer[0] & 0x7f) << 21); return size; } int id3_size2(unsigned char* buffer) { unsigned long size = 0; size = (buffer[2] & 0x7f) + ((buffer[1] & 0x7f) << 7) + ((buffer[0] & 0x7f) << 14); return size; } unsigned int get_framesize(unsigned char *buffer) { /* An ID3v2.3 frame length is a 4 byte unsigned number. Naturally, we're not going to handle anything that large. We should handle large frames intelligently, but for now I'm going to pretend that length is only 2 bytes long */ return ((buffer[6] << 8) + (buffer[7])) + 10; } int get_id3v1_tag (pool *p, int fd, mp3_data *bank){ id3 id3_tag; id3 *p_id3 = &id3_tag; char buffer[HUGE_STRING_LEN]; char *ptr_buffer = NULL; memset(buffer, 0, HUGE_STRING_LEN); memset(p_id3, 0, sizeof(id3_tag)); if (lseek(fd,-128,SEEK_END)>0){ if (read(fd, buffer, 128) == 128){ if(!strncmp(buffer, "TAG", 3)) { /* Paranoid, not all systems are made equally */ ptr_buffer = &buffer[0]; ptr_buffer +=3; memcpy(p_id3->songname, ptr_buffer, 30 ); clean_string(p_id3->songname, 30, 30); ptr_buffer +=30; memcpy(p_id3->artist, ptr_buffer, 30 ); clean_string(p_id3->artist, 30, 30); ptr_buffer +=30; memcpy(p_id3->album, ptr_buffer, 30 ); clean_string(p_id3->album, 30, 30); ptr_buffer +=30; memcpy(p_id3->year, ptr_buffer, 4 ); clean_string(p_id3->year, 4, 30); ptr_buffer +=4; memcpy(p_id3->comment, ptr_buffer, 30 ); clean_string(p_id3->comment, 30, 30); ptr_buffer +=30; p_id3->genre = ptr_buffer[0]; bank->name = ap_pstrndup(p, p_id3->songname,30); bank->artist = ap_pstrndup(p, p_id3->artist,30); bank->album = ap_pstrndup(p, p_id3->album,30); bank->comment = ap_pstrndup(p, p_id3->comment,30); bank->year = ap_pstrndup(p, p_id3->year,4); bank->genre = (char *)genre_string(p_id3->genre); return 1; } } } return 0; } void id_2_2(pool *p, int fd, mp3_data *bank, unsigned int totalsize) { unsigned long size = 0; int len = 0; int readlen = 0; unsigned char buffer[HUGE_STRING_LEN]; while (lseek(fd, 0, SEEK_CUR) < totalsize) { memset(buffer, 0, HUGE_STRING_LEN); readlen = read(fd, buffer, 6); if (readlen == 0) continue; /* Basically, if we find garbage we quit looking */ if (!isframeid(buffer[0]) || !isframeid(buffer[1]) || !isframeid(buffer[2])) break; /* if the size byte is more that 0x80, bail. */ if ((buffer[0] > 0x7f) || (buffer[1] > 0x7f) || (buffer[2] > 0x7f)) break; if(buffer[0] == 0 && buffer[1] == 0 && buffer[2] == 0) break; size = id3_size2(buffer+3); memset(buffer, 0, HUGE_STRING_LEN); len = read(fd, buffer, size); clean_string(buffer, len, HUGE_STRING_LEN); if(!strncmp("TP1", buffer, 3)){ bank->artist = ap_pstrndup(p, buffer, len); } else if (!strncmp("TT2", buffer, 3)) { bank->name = ap_pstrndup(p, buffer, len); } else if (!strncmp("TAL", buffer, 3)) { bank->album = ap_pstrndup(p, buffer, len); } else if (!strncmp("TRK", buffer, 3)) { bank->track = ap_pstrndup(p, buffer, len); } else if (!strncmp("TYE", buffer, 3)) { bank->year = ap_pstrndup(p, buffer, len); } else if (!strncmp("COM", buffer, 3)) { bank->comment = ap_pstrndup(p, buffer, len); } else if (!strncmp("TCO", buffer, 3)) { bank->genre = ap_pstrndup(p, buffer, len); } } } void id_2_3(pool *p, unsigned char *buffer, mp3_data *bank, unsigned long tagsize) { unsigned int framesize = 0; unsigned long processedsize = 0; while (tagsize > processedsize) { if (!strncmp("TPE1",buffer,4)) { framesize = get_framesize(buffer); bank->artist = ap_pstrndup(p, (buffer+11), (framesize-11)); buffer += framesize; processedsize += framesize; } else if (!strncmp("TIT2",buffer,4)) { framesize = get_framesize(buffer); bank->name = ap_pstrndup(p, (buffer+11), (framesize-11)); buffer += framesize; processedsize += framesize; } else if (!strncmp("TALB",buffer,4)) { framesize = get_framesize(buffer); bank->album = ap_pstrndup(p, (buffer+11), (framesize-11)); buffer += framesize; processedsize += framesize; } else if (!strncmp("TRCK",buffer,4)) { framesize = get_framesize(buffer); bank->track = ap_pstrndup(p, (buffer+11), (framesize-11)); buffer += framesize; processedsize += framesize; } else if (!strncmp("TYER",buffer,4)) { framesize = get_framesize(buffer); bank->year = ap_pstrndup(p, (buffer+11), (framesize-11)); buffer += framesize; processedsize += framesize; } else if (!strncmp("COMM",buffer,4)) { framesize = get_framesize(buffer); bank->comment = ap_pstrndup(p, (buffer+14), (framesize-14)); buffer += framesize; processedsize += framesize; } else if (!strncmp("TCON",buffer,4)) { framesize = get_framesize(buffer); bank->genre = ap_pstrndup(p, (buffer+11), (framesize-11)); buffer += framesize; processedsize += framesize; } else { /*We don't recognize this frame, let's get rid of it */ framesize = get_framesize(buffer); buffer += framesize; processedsize += framesize; } } } void process_extended_header(pool *p, unsigned char *buffer, mp3_data *bank, unsigned long tagsize) { unsigned long CRC = 0; unsigned long paddingsize = 0; /*Shortcut. The extended header size will be either 6 or 10 bytes. If it's ten bytes, it means that there's CRC data (though we check the flag anyway). I'm gonna save it, though I'll be damned if I know what to do with it.*/ if ((buffer[3] == 0x0A) && (buffer[4])) { CRC = (buffer[10] << 24) + (buffer[11] << 16) + (buffer[12] << 8) + (buffer[13]); } paddingsize = (buffer[6] << 24) + (buffer[7] << 16) + (buffer[8] << 8) + (buffer[9]); /*subtract the size of the padding from the size of the tag */ tagsize -= paddingsize; /*continue decoding the frames */ id_2_3(p, buffer, bank, tagsize); } int get_id3v2_tag (pool *p, int fd, mp3_data *bank) { unsigned char buffer[HUGE_STRING_LEN]; unsigned long tagsize; int unsynchronized = 0; int hasExtendedHeader = 0; int experimental = 0; int version = 0; int i,j; /*index*/ lseek(fd, 0, SEEK_SET); memset(buffer, 0, HUGE_STRING_LEN); read(fd, buffer, 10); if(!strncmp(buffer, "ID3", 3)) { tagsize = id3_size(buffer+6); version = buffer[3]; if (version == 3) { if ((buffer[5] & 0x80) >> 7) { unsynchronized = 1; } if ((buffer[5] & 0x40) >> 6) { hasExtendedHeader = 1; } /*Present, but not very useful*/ if ((buffer[5] & 0x20) >> 5) { experimental = 1; } } if (tagsize > HUGE_STRING_LEN) { /*Tag is very large. Screw it.*/ return 0; } if (read(fd, buffer, tagsize) < tagsize) { /*Hmmm. That shouldn't happen*/ return 0; } if (unsynchronized) { /*Replace every instance of '0xFF 0x00' with '0xFF'*/ for(i=0; i < tagsize; i++) { if (buffer[i] == 0xFF && buffer[i+1] == 0x00) { for(j=i+1; i < tagsize; i++) { buffer[j] = buffer[j+1]; } } } } /*If the tag has an extended header, parse it*/ if (hasExtendedHeader) { process_extended_header(p, buffer, bank, tagsize); } else if (version == 2) { id_2_2(p, fd, bank, tagsize); } else if (version == 3) { id_2_3(p, buffer, bank, tagsize); } } return 0; } MP3_EXPORT(int) get_id3_tag(pool *p, int fd, mp3_data *bank) { if (get_id3v2_tag(p, fd, bank)){ return 1; } else if (get_id3v1_tag(p, fd, bank)) { return 1; } return 0; }