/*************************************************************************** * __________ __ ___. * Open \______ \ ____ ____ | | _\_ |__ _______ ___ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ * \/ \/ \/ \/ \/ * $Id$ * * Copyright (C) 2002 by Daniel Stenberg * * All files in this archive are subject to the GNU General Public License. * See the file COPYING in the source tree root for full license agreement. * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * ****************************************************************************/ /* * Parts of this code has been stolen from the Ample project and was written * by David Härdeman. It has since been extended and enhanced pretty much by * all sorts of friendly Rockbox people. * */ #include #include #include #include #include #include "file.h" #include "debug.h" #include "atoi.h" #include "id3.h" #include "mp3data.h" #define UNSYNC(b0,b1,b2,b3) (((b0 & 0x7F) << (3*7)) | \ ((b1 & 0x7F) << (2*7)) | \ ((b2 & 0x7F) << (1*7)) | \ ((b3 & 0x7F) << (0*7))) #define BYTES2INT(b0,b1,b2,b3) (((b0 & 0xFF) << (3*8)) | \ ((b1 & 0xFF) << (2*8)) | \ ((b2 & 0xFF) << (1*8)) | \ ((b3 & 0xFF) << (0*8))) /* Checks to see if the passed in string is a 16-bit wide Unicode v2 string. If it is, we attempt to convert it to a 8-bit ASCII string (for valid 8-bit ASCII characters). If it's not unicode, we leave it alone. At some point we should fully support unicode strings */ static int unicode_munge(char** string, int *len) { int tmp; bool le = false; int i; char *str = *string; char *outstr = *string; if(str[0] > 0x03) { /* Plain old string */ return 0; } /* Type 0x00 is ordinary ISO 8859-1 */ if(str[0] == 0x00) { (*len)--; (*string)++; /* Skip the encoding type byte */ return 0; } /* Unicode with or without BOM */ if(str[0] == 0x01 || str[0] == 0x02) { str++; tmp = BYTES2INT(0, 0, str[0], str[1]); /* Now check if there is a BOM (zero-width non-breaking space, 0xfeff) and if it is in little or big endian format */ if(tmp == 0xfffe) { /* Little endian? */ le = true; str += 2; } if(tmp == 0xfeff) /* Big endian? */ str += 2; i = 0; do { if(le) { if(str[1]) outstr[i++] = '.'; else outstr[i++] = str[0]; } else { if(str[0]) outstr[i++] = '.'; else outstr[i++] = str[1]; } str += 2; } while(str[0] || str[1]); *len = i; return 0; } /* If we come here, the string was of an unsupported type */ *len = 1; outstr[0] = 0; return -1; } /* * Sets the title of an MP3 entry based on its ID3v1 tag. * * Arguments: file - the MP3 file to scen for a ID3v1 tag * entry - the entry to set the title in * * Returns: true if a title was found and created, else false */ static bool setid3v1title(int fd, struct mp3entry *entry) { unsigned char buffer[128]; static char offsets[] = {3, 33, 63, 93, 125, 127}; int i, j; if (-1 == lseek(fd, -128, SEEK_END)) return false; if (read(fd, buffer, sizeof buffer) != sizeof buffer) return false; if (strncmp(buffer, "TAG", 3)) return false; entry->id3v1len = 128; entry->id3version = ID3_VER_1_0; for (i=0; i < (int)sizeof offsets; i++) { char* ptr = buffer + offsets[i]; if (i<3) { /* kill trailing space in strings */ for (j=29; j && ptr[j]==' '; j--) ptr[j] = 0; } switch(i) { case 0: strncpy(entry->id3v1buf[2], ptr, 30); entry->title = entry->id3v1buf[2]; break; case 1: strncpy(entry->id3v1buf[0], ptr, 30); entry->artist = entry->id3v1buf[0]; break; case 2: strncpy(entry->id3v1buf[1], ptr, 30); entry->album = entry->id3v1buf[1]; break; case 3: ptr[4] = 0; entry->year = atoi(ptr); break; case 4: /* id3v1.1 uses last two bytes of comment field for track number: first must be 0 and second is track num */ if (!ptr[0] && ptr[1]) { entry->tracknum = ptr[1]; entry->id3version = ID3_VER_1_1; } break; case 5: /* genre */ entry->genre = ptr[0]; break; } } return true; } /* * Sets the title of an MP3 entry based on its ID3v2 tag. * * Arguments: file - the MP3 file to scan for a ID3v2 tag * entry - the entry to set the title in * * Returns: true if a title was found and created, else false */ static void setid3v2title(int fd, struct mp3entry *entry) { int minframesize; int size; int bufferpos = 0, totframelen, framelen; char header[10]; unsigned char version; char *buffer = entry->id3v2buf; char *tracknum = NULL; int bytesread = 0; int buffersize = sizeof(entry->id3v2buf); int flags; int skip; /* Bail out if the tag is shorter than 10 bytes */ if(entry->id3v2len < 10) return; /* Read the ID3 tag version from the header */ lseek(fd, 0, SEEK_SET); if(10 != read(fd, header, 10)) return; /* Get the total ID3 tag size */ size = entry->id3v2len - 10; version = header[3]; switch ( version ) { case 2: version = ID3_VER_2_2; minframesize = 8; break; case 3: version = ID3_VER_2_3; minframesize = 12; break; case 4: version = ID3_VER_2_4; minframesize = 12; break; default: /* unsupported id3 version */ return; } entry->id3version = version; /* Skip the extended header if it is present */ if(version >= ID3_VER_2_4) { if(header[5] & 0x40) { if(4 != read(fd, header, 4)) return; framelen = UNSYNC(header[0], header[1], header[2], header[3]); lseek(fd, framelen - 4, SEEK_CUR); } } /* * We must have at least minframesize bytes left for the * remaining frames to be interesting */ while(size > minframesize) { flags = 0; /* Read frame header and check length */ if(version >= ID3_VER_2_3) { if(10 != read(fd, header, 10)) return; /* Adjust for the 10 bytes we read */ size -= 10; flags = BYTES2INT(0, 0, header[8], header[9]); if (version >= ID3_VER_2_4) { framelen = UNSYNC(header[4], header[5], header[6], header[7]); } else { /* version .3 files don't use synchsafe ints for * size */ framelen = BYTES2INT(header[4], header[5], header[6], header[7]); } } else { if(6 != read(fd, header, 6)) return; /* Adjust for the 6 bytes we read */ size -= 6; framelen = BYTES2INT(0, header[3], header[4], header[5]); } /* Keep track of the total size */ totframelen = framelen; if(framelen == 0) return; if(flags) { skip = 0; if(flags & 0x0040) /* Grouping identity */ skip++; if(flags & 0x000c) /* Compression or encryption */ { /* Skip it using the total size in case it was truncated */ size -= totframelen; lseek(fd, totframelen, SEEK_CUR); continue; } /* The Unsynchronization flag can't be trusted, so we don't check it for now... */ if(flags & 0x0001) /* Data length indicator */ skip += 4; if(skip) { lseek(fd, skip, SEEK_CUR); framelen -= skip; } } /* If the frame is larger than the remaining buffer space we try to read as much as would fit in the buffer */ if(framelen >= buffersize - bufferpos) framelen = buffersize - bufferpos - 1; DEBUGF("id3v2 frame: %.4s\n", header); /* Check for certain frame headers */ if(!strncmp(header, "TPE1", strlen("TPE1")) || !strncmp(header, "TP1", strlen("TP1"))) { bytesread = read(fd, buffer + bufferpos, framelen); entry->artist = buffer + bufferpos; unicode_munge(&entry->artist, &bytesread); entry->artist[bytesread + 1] = '\0'; bufferpos += bytesread + 2; size -= bytesread; } else if(!strncmp(header, "TIT2", strlen("TIT2")) || !strncmp(header, "TT2", strlen("TT2"))) { bytesread = read(fd, buffer + bufferpos, framelen); entry->title = buffer + bufferpos; unicode_munge(&entry->title, &bytesread); entry->title[bytesread + 1] = '\0'; bufferpos += bytesread + 2; size -= bytesread; } else if(!strncmp(header, "TALB", strlen("TALB"))) { bytesread = read(fd, buffer + bufferpos, framelen); entry->album = buffer + bufferpos; unicode_munge(&entry->album, &bytesread); entry->album[bytesread + 1] = '\0'; bufferpos += bytesread + 2; size -= bytesread; } else if(!strncmp(header, "TRCK", strlen("TRCK"))) { bytesread = read(fd, buffer + bufferpos, framelen); tracknum = buffer + bufferpos; unicode_munge(&tracknum, &bytesread); tracknum[bytesread + 1] = '\0'; entry->tracknum = atoi(tracknum); bufferpos += bytesread + 1; size -= bytesread; } else if(!strncmp(header, "TYER", 4) || !strncmp(header, "TYR", 3)) { char* ptr = buffer + bufferpos; bytesread = read(fd, ptr, framelen); unicode_munge(&ptr, &bytesread); entry->year = atoi(ptr); bufferpos += bytesread + 1; size -= bytesread; } else if(!strncmp(header, "TCON", 4)) { char* ptr = buffer + bufferpos; bytesread = read(fd, ptr, framelen); if (ptr[1] == '(' && ptr[2] != '(') entry->genre = atoi(ptr+2); bufferpos += bytesread + 1; size -= bytesread; } else { /* Unknown frame, skip it using the total size in case it was truncated */ size -= totframelen; lseek(fd, totframelen, SEEK_CUR); } } } /* * Calculates the size of the ID3v2 tag. * * Arguments: file - the file to search for a tag. * * Returns: the size of the tag or 0 if none was found */ static int getid3v2len(int fd) { char buf[6]; int offset; /* Make sure file has a ID3 tag */ if((-1 == lseek(fd, 0, SEEK_SET)) || (read(fd, buf, 6) != 6) || (strncmp(buf, "ID3", strlen("ID3")) != 0)) offset = 0; /* Now check what the ID3v2 size field says */ else if(read(fd, buf, 4) != 4) offset = 0; else offset = UNSYNC(buf[0], buf[1], buf[2], buf[3]) + 10; DEBUGF("ID3V2 Length: 0x%x\n", offset); return offset; } static int getfilesize(int fd) { int size; /* seek to the end of it */ size = lseek(fd, 0, SEEK_END); if(-1 == size) return 0; /* unknown */ return size; } /* * Calculates the length (in milliseconds) of an MP3 file. * * Modified to only use integers. * * Arguments: file - the file to calculate the length upon * entry - the entry to update with the length * * Returns: the song length in milliseconds, * 0 means that it couldn't be calculated */ static int getsonglength(int fd, struct mp3entry *entry) { unsigned int filetime = 0; struct mp3info info; int bytecount; /* Start searching after ID3v2 header */ if(-1 == lseek(fd, entry->id3v2len, SEEK_SET)) return 0; bytecount = get_mp3file_info(fd, &info); DEBUGF("Space between ID3V2 tag and first audio frame: 0x%x bytes\n", bytecount); if(bytecount < 0) return -1; bytecount += entry->id3v2len; entry->bitrate = info.bitrate; /* If the file time hasn't been established, this may be a fixed rate MP3, so just use the default formula */ filetime = info.file_time; if(filetime == 0) { /* * Now song length is * ((filesize)/(bytes per frame))*(time per frame) */ filetime = entry->filesize/info.frame_size*info.frame_time; } entry->tpf = info.frame_time; entry->bpf = info.frame_size; entry->vbr = info.is_vbr; entry->has_toc = info.has_toc; memcpy(entry->toc, info.toc, sizeof(info.toc)); entry->xing_header_pos = info.xing_header_pos; /* Update the seek point for the first playable frame */ entry->first_frame_offset = bytecount; DEBUGF("First frame is at %x\n", entry->first_frame_offset); return filetime; } /* * Checks all relevant information (such as ID3v1 tag, ID3v2 tag, length etc) * about an MP3 file and updates it's entry accordingly. * * Arguments: entry - the entry to check and update with the new information * * Returns: void */ bool mp3info(struct mp3entry *entry, char *filename) { int fd; fd = open(filename, O_RDONLY); if(-1 == fd) return true; memset(entry, 0, sizeof(struct mp3entry)); strncpy(entry->path, filename, sizeof(entry->path)); entry->title = NULL; entry->filesize = getfilesize(fd); entry->id3v2len = getid3v2len(fd); entry->tracknum = 0; entry->genre = 0xff; if (entry->id3v2len) setid3v2title(fd, entry); entry->length = getsonglength(fd, entry); /* Subtract the meta information from the file size to get the true size of the MP3 stream */ entry->filesize -= entry->first_frame_offset; /* only seek to end of file if no id3v2 tags were found */ if (!entry->id3v2len) { if(!entry->title) setid3v1title(fd, entry); } close(fd); if(!entry->length || (entry->filesize < 8 )) /* no song length or less than 8 bytes is hereby considered to be an invalid mp3 and won't be played by us! */ return true; return false; } #ifdef DEBUG_STANDALONE char *secs2str(int ms) { static char buffer[32]; int secs = ms/1000; ms %= 1000; snprintf(buffer, sizeof(buffer), "%d:%02d.%d", secs/60, secs%60, ms/100); return buffer; } int main(int argc, char **argv) { int i; for(i=1; i", mp3.artist?mp3.artist:"", mp3.album?mp3.album:"", secs2str(mp3.length), mp3.length/1000, mp3.bitrate, mp3.frequency); } return 0; } #endif