/* DeaDBeeF - ultimate music player for GNU/Linux systems with X11 Copyright (C) 2009 Alexey Yakovenko This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "junklib.h" #include #include #include #include #include "playlist.h" #define min(x,y) ((x)<(y)?(x):(y)) #define max(x,y) ((x)>(y)?(x):(y)) static uint32_t extract_i32 (unsigned char *buf) { uint32_t x; // big endian extract x = buf[0]; x <<= 8; x |= buf[1]; x <<= 8; x |= buf[2]; x <<= 8; x |= buf[3]; return x; } static inline uint32_t extract_i32_le (unsigned char *buf) { uint32_t x; // little endian extract x = buf[3]; x <<= 8; x |= buf[2]; x <<= 8; x |= buf[1]; x <<= 8; x |= buf[0]; return x; } static inline uint16_t extract_i16 (unsigned char *buf) { uint16_t x; // big endian extract x = buf[0]; x <<= 8; x |= buf[1]; x <<= 8; return x; } static inline float extract_f32 (unsigned char *buf) { float f; uint32_t *x = (uint32_t *)&f; *x = buf[0]; *x <<= 8; *x |= buf[1]; *x <<= 8; *x |= buf[2]; *x <<= 8; *x |= buf[3]; return f; } static const char *junk_genretbl[] = { "Blues", "Classic Rock", "Country", "Dance", "Disco", "Funk", "Grunge", "Hip-Hop", "Jazz", "Metal", "New Age", "Oldies", "Other", "Pop", "R&B", "Rap", "Reggae", "Rock", "Techno", "Industrial", "Alternative", "Ska", "Death Metal", "Pranks", "Soundtrack", "Euro-Techno", "Ambient", "Trip-Hop", "Vocal", "Jazz+Funk", "Fusion", "Trance", "Classical", "Instrumental", "Acid", "House", "Game", "Sound Clip", "Gospel", "Noise", "AlternRock", "Bass", "Soul", "Punk", "Space", "Meditative", "Instrumental Pop", "Instrumental Rock", "Ethnic", "Gothic", "Darkwave", "Techno-Industrial", "Electronic", "Pop-Folk", "Eurodance", "Dream", "Southern Rock", "Comedy", "Cult", "Gangsta", "Top 40", "Christian Rap", "Pop/Funk", "Jungle", "Native American", "Cabaret", "New Wave", "Psychadelic", "Rave", "Showtunes", "Trailer", "Lo-Fi", "Tribal", "Acid Punk", "Acid Jazz", "Polka", "Retro", "Musical", "Rock & Roll", "Hard Rock", "Folk", "Folk-Rock", "National Folk", "Swing", "Fast Fusion", "Bebob", "Latin", "Revival", "Celtic", "Bluegrass", "Avantgarde", "Gothic Rock", "Progressive Rock", "Psychedelic Rock", "Symphonic Rock", "Slow Rock", "Big Band", "Chorus", "Easy Listening", "Acoustic", "Humour", "Speech", "Chanson", "Opera", "Chamber Music", "Sonata", "Symphony", "Booty Bass", "Primus", "Porn Groove", "Satire", "Slow Jam", "Club", "Tango", "Samba", "Folklore", "Ballad", "Power Ballad", "Rhythmic Soul", "Freestyle", "Duet", "Punk Rock", "Drum Solo", "Acapella", "Euro-House", "Dance Hall", "Goa", "Drum & Bass", "Club-House", "Hardcore", "Terror", "Indie", "BritPop", "Negerpunk", "Polsk Punk", "Beat", "Christian Gangsta", "Heavy Metal", "Black Metal", "Crossover", "Contemporary C", "Christian Rock", "Merengue", "Salsa", "Thrash Metal", "Anime", "JPop", "SynthPop", }; static int can_be_russian (const char *str) { int latin = 0; int rus = 0; for (; *str; str++) { if ((*str >= 'A' && *str <= 'Z') || *str >= 'a' && *str <= 'z') { latin++; } else if (*str < 0) { rus++; } } if (rus > latin/2) { return 1; } return 0; } static char * convstr_id3v2_2to3 (const unsigned char* str, int sz) { static char out[2048]; const char *enc = "iso8859-1"; char *ret = out; // hack to add limited cp1251 recoding support if (*str == 1) { enc = "UCS-2"; // standard says it must have endianess header if (!((str[1] == 0xff && str[2] == 0xfe) || (str[2] == 0xff && str[1] == 0xfe))) { // fprintf (stderr, "invalid ucs-2 signature %x %x\n", (int)str[1], (int)str[2]); return NULL; } } else { if (can_be_russian (&str[1])) { enc = "cp1251"; } } str++; sz--; iconv_t cd = iconv_open ("utf8", enc); if (!cd) { // fprintf (stderr, "unknown encoding: %s\n", enc); return NULL; } else { size_t inbytesleft = sz; size_t outbytesleft = 2047; char *pin = (char*)str; char *pout = out; memset (out, 0, sizeof (out)); size_t res = iconv (cd, &pin, &inbytesleft, &pout, &outbytesleft); iconv_close (cd); ret = out; } return strdup (ret); } static char * convstr_id3v2_4 (const unsigned char* str, int sz) { static char out[2048]; const char *enc = "iso8859-1"; char *ret = out; // hack to add limited cp1251 recoding support if (*str == 0) { // iso8859-1 enc = "iso8859-1"; } else if (*str == 3) { // utf8 strncpy (out, str+1, 2047); sz--; out[min (sz, 2047)] = 0; return strdup (out); } else if (*str == 1) { enc = "UTF-16"; } else if (*str == 2) { enc = "UTF-16BE"; } else { if (can_be_russian (&str[1])) { enc = "cp1251"; } } str++; sz--; iconv_t cd = iconv_open ("utf8", enc); if (!cd) { // printf ("unknown encoding: %s\n", enc); return NULL; } else { size_t inbytesleft = sz; size_t outbytesleft = 2047; char *pin = (char*)str; char *pout = out; memset (out, 0, sizeof (out)); size_t res = iconv (cd, &pin, &inbytesleft, &pout, &outbytesleft); iconv_close (cd); ret = out; } //fprintf (stderr, "decoded %s\n", out+3); return strdup (ret); } static const char * convstr_id3v1 (const char* str, int sz) { static char out[2048]; int i; for (i = 0; i < sz; i++) { if (str[i] != ' ') { break; } } if (i == sz) { out[0] = 0; return out; } // check for utf8 (hack) iconv_t cd; cd = iconv_open ("utf8", "utf8"); size_t inbytesleft = sz; size_t outbytesleft = 2047; char *pin = (char*)str; char *pout = out; memset (out, 0, sizeof (out)); size_t res = iconv (cd, &pin, &inbytesleft, &pout, &outbytesleft); iconv_close (cd); if (res == 0) { strncpy (out, str, 2047); out[min (sz, 2047)] = 0; return out; } const char *enc = "iso8859-1"; int latin = 0; int rus = 0; for (int i = 0; i < sz; i++) { if ((str[i] >= 'A' && str[i] <= 'Z') || str[i] >= 'a' && str[i] <= 'z') { latin++; } else if (str[i] < 0) { rus++; } } if (rus > latin/2) { // might be russian enc = "cp1251"; } cd = iconv_open ("utf8", enc); if (!cd) { // fprintf (stderr, "unknown encoding: %s\n", enc); return NULL; } else { size_t inbytesleft = sz; size_t outbytesleft = 2047; char *pin = (char*)str; char *pout = out; memset (out, 0, sizeof (out)); size_t res = iconv (cd, &pin, &inbytesleft, &pout, &outbytesleft); iconv_close (cd); } return out; } static void str_trim_right (uint8_t *str, int len) { uint8_t *p = str + len - 1; while (p >= str && *p <= 0x20) { p--; } p++; *p = 0; } // should read both id3v1 and id3v1.1 int junk_read_id3v1 (playItem_t *it, FILE *fp) { if (!it || !fp) { fprintf (stderr, "bad call to junk_read_id3v1!\n"); return -1; } uint8_t buffer[128]; // try reading from end fseek (fp, -128, SEEK_END); if (fread (buffer, 1, 128, fp) != 128) { return -1; } if (strncmp (buffer, "TAG", 3)) { return -1; // no tag } char title[31]; char artist[31]; char album[31]; char year[5]; char comment[31]; uint8_t genreid; uint8_t tracknum; const char *genre; memset (title, 0, 31); memset (artist, 0, 31); memset (album, 0, 31); memset (year, 0, 5); memset (comment, 0, 31); memcpy (title, &buffer[3], 30); str_trim_right (title, 30); memcpy (artist, &buffer[3+30], 30); str_trim_right (artist, 30); memcpy (album, &buffer[3+60], 30); str_trim_right (album, 30); memcpy (year, &buffer[3+90], 4); str_trim_right (year, 4); memcpy (comment, &buffer[3+94], 30); str_trim_right (comment, 30); genreid = buffer[3+124]; tracknum = 0xff; if (comment[28] == 0) { tracknum = comment[29]; } // 255 = "None", // "CR" = "Cover" (id3v2) // "RX" = "Remix" (id3v2) if (genreid == 0xff) { genre = "None"; } else if (genreid <= 147) { genre = junk_genretbl[genreid]; } // add meta // fprintf (stderr, "%s - %s - %s - %s - %s - %s\n", title, artist, album, year, comment, genre); pl_add_meta (it, "title", convstr_id3v1 (title, strlen (title))); pl_add_meta (it, "artist", convstr_id3v1 (artist, strlen (artist))); pl_add_meta (it, "album", convstr_id3v1 (album, strlen (album))); pl_add_meta (it, "year", year); pl_add_meta (it, "comment", convstr_id3v1 (comment, strlen (comment))); pl_add_meta (it, "genre", convstr_id3v1 (genre, strlen (genre))); if (tracknum != 0xff) { char s[4]; snprintf (s, 4, "%d", tracknum); pl_add_meta (it, "track", s); } if (it->endoffset < 128) { it->endoffset = 128; } return 0; } int junk_read_ape (playItem_t *it, FILE *fp) { // fprintf (stderr, "trying to read ape tag\n"); // try to read footer, position must be already at the EOF right before // id3v1 (if present) uint8_t header[32]; if (fseek (fp, -32, SEEK_CUR) == -1) { return -1; // something bad happened } if (fread (header, 1, 32, fp) != 32) { return -1; // something bad happened } if (strncmp (header, "APETAGEX", 8)) { return -1; // no ape tag here } // end of footer must be 0 if (memcmp (&header[24], "\0\0\0\0\0\0\0\0", 8)) { return -1; } uint32_t version = extract_i32_le (&header[8]); uint32_t size = extract_i32_le (&header[12]); uint32_t numitems = extract_i32_le (&header[16]); uint32_t flags = extract_i32_le (&header[20]); // fprintf (stderr, "APEv%d, size=%d, items=%d, flags=%x\n", version, size, numitems, flags); // now seek to beginning of the tag (exluding header) if (fseek (fp, -size, SEEK_CUR) == -1) { return -1; } for (int i = 0; i < numitems; i++) { uint8_t buffer[8]; if (fread (buffer, 1, 8, fp) != 8) { return -1; } uint32_t itemsize = extract_i32_le (&buffer[0]); uint32_t itemflags = extract_i32_le (&buffer[4]); // read key until 0 (stupid and slow) char key[256]; int keysize = 0; while (keysize <= 255) { if (fread (&key[keysize], 1, 1, fp) != 1) { return -1; } if (key[keysize] == 0) { break; } if (key[keysize] < 0x20) { return -1; // non-ascii chars and chars with coded 0..0x1f not allowed in ape item keys } keysize++; } key[255] = 0; // read value char value[itemsize+1]; if (fread (value, 1, itemsize, fp) != itemsize) { return -1; } value[itemsize] = 0; // add metainfo only if it's textual int valuetype = ((itemflags & (0x3<<1)) >> 1); if (valuetype == 0) { if (!strcasecmp (key, "artist")) { pl_add_meta (it, "artist", value); } else if (!strcasecmp (key, "title")) { pl_add_meta (it, "title", value); } else if (!strcasecmp (key, "album")) { pl_add_meta (it, "album", value); } else if (!strcasecmp (key, "track")) { pl_add_meta (it, "track", value); } else if (!strcasecmp (key, "year")) { pl_add_meta (it, "year", value); } else if (!strcasecmp (key, "genre")) { pl_add_meta (it, "genre", value); } else if (!strcasecmp (key, "comment")) { pl_add_meta (it, "genre", value); } } } return 0; } static void id3v2_string_read (int version, uint8_t *out, int sz, int unsync, uint8_t **pread) { if (!unsync) { memcpy (out, *pread, sz); *pread += sz; out[sz] = 0; out[sz+1] = 0; return; } uint8_t prev = 0; while (sz > 0) { if (prev == 0xff && !*(*pread)) { prev = 0; (*pread)++; continue; } prev = *out = *(*pread); (*pread)++; out++; sz--; } *out = 0; } int junk_get_leading_size (FILE *fp) { uint8_t header[10]; int pos = ftell (fp); if (fread (header, 1, 10, fp) != 10) { fseek (fp, pos, SEEK_SET); return -1; // too short } fseek (fp, pos, SEEK_SET); if (strncmp (header, "ID3", 3)) { return -1; // no tag } // uint8_t version_major = header[3]; // uint8_t version_minor = header[4]; // if (version_major > 4 || version_major < 2) { // return -1; // unsupported // } uint8_t flags = header[5]; if (flags & 15) { return -1; // unsupported } // int unsync = (flags & (1<<7)) ? 1 : 0; // int extheader = (flags & (1<<6)) ? 1 : 0; // int expindicator = (flags & (1<<5)) ? 1 : 0; int footerpresent = (flags & (1<<4)) ? 1 : 0; // check for bad size if ((header[9] & 0x80) || (header[8] & 0x80) || (header[7] & 0x80) || (header[6] & 0x80)) { return -1; // bad header } uint32_t size = (header[9] << 0) | (header[8] << 7) | (header[7] << 14) | (header[6] << 21); //fprintf (stderr, "junklib: leading junk size %d\n", size); return size + 10 + 10 * footerpresent; } int junk_read_id3v2 (playItem_t *it, FILE *fp) { int title_added = 0; if (!it || !fp) { fprintf (stderr, "bad call to junk_read_id3v2!\n"); return -1; } rewind (fp); uint8_t header[10]; if (fread (header, 1, 10, fp) != 10) { return -1; // too short } if (strncmp (header, "ID3", 3)) { return -1; // no tag } uint8_t version_major = header[3]; uint8_t version_minor = header[4]; if (version_major > 4 || version_major < 2) { // fprintf (stderr, "id3v2.%d.%d is unsupported\n", version_major, version_minor); return -1; // unsupported } uint8_t flags = header[5]; if (flags & 15) { return -1; // unsupported } int unsync = (flags & (1<<7)) ? 1 : 0; int extheader = (flags & (1<<6)) ? 1 : 0; int expindicator = (flags & (1<<5)) ? 1 : 0; int footerpresent = (flags & (1<<4)) ? 1 : 0; // check for bad size if ((header[9] & 0x80) || (header[8] & 0x80) || (header[7] & 0x80) || (header[6] & 0x80)) { return -1; // bad header } uint32_t size = (header[9] << 0) | (header[8] << 7) | (header[7] << 14) | (header[6] << 21); int startoffset = size + 10 + 10 * footerpresent; if (startoffset > it->startoffset) { it->startoffset = startoffset; // fprintf (stderr, "id3v2 end: %x\n", startoffset); } // fprintf (stderr, "tag size: %d\n", size); // try to read full tag if size is small enough if (size > 1000000) { return -1; } uint8_t tag[size]; if (fread (tag, 1, size, fp) != size) { return -1; // bad size } uint8_t *readptr = tag; int crcpresent = 0; // fprintf (stderr, "version: 2.%d.%d, unsync: %d, extheader: %d, experimental: %d\n", version_major, version_minor, unsync, extheader, expindicator); if (extheader) { if (size < 6) { return -1; // bad size } uint32_t sz = (readptr[3] << 0) | (header[2] << 8) | (header[1] << 16) | (header[0] << 24); readptr += 4; if (size < sz) { return -1; // bad size } uint16_t extflags = (readptr[1] << 0) | (readptr[0] << 8); readptr += 2; uint32_t pad = (readptr[3] << 0) | (header[2] << 8) | (header[1] << 16) | (header[0] << 24); readptr += 4; if (extflags & 0x80000000) { crcpresent = 1; } if (crcpresent && sz != 10) { return -1; // bad header } readptr += 4; // skip crc } char * (*convstr)(const unsigned char *, int); if (version_major == 3) { convstr = convstr_id3v2_2to3; } else { convstr = convstr_id3v2_4; } char *artist = NULL; char *album = NULL; char *band = NULL; char *track = NULL; char *title = NULL; char *vendor = NULL; int err = 0; while (readptr - tag <= size - 4) { if (version_major == 3 || version_major == 4) { char frameid[5]; memcpy (frameid, readptr, 4); frameid[4] = 0; readptr += 4; if (readptr - tag >= size - 4) { err = 1; break; } uint32_t sz = (readptr[3] << 0) | (readptr[2] << 8) | (readptr[1] << 16) | (readptr[0] << 24); readptr += 4; //fprintf (stderr, "got frame %s, size %d, pos %d, tagsize %d\n", frameid, sz, readptr-tag, size); if (readptr - tag >= size - sz) { err = 1; break; // size of frame is more than size of tag } if (sz < 1) { // err = 1; break; // frame must be at least 1 byte long } uint16_t flags = (readptr[1] << 0) | (readptr[0] << 8); readptr += 2; // fprintf (stderr, "found id3v2.3 frame: %s, size=%d\n", frameid, sz); if (!strcmp (frameid, "TPE1")) { if (sz > 1000) { err = 1; break; // too large } char str[sz+2]; id3v2_string_read (version_major, &str[0], sz, unsync, &readptr); artist = convstr (str, sz); } else if (!strcmp (frameid, "TPE2")) { if (sz > 1000) { err = 1; break; // too large } char str[sz+2]; id3v2_string_read (version_major, &str[0], sz, unsync, &readptr); band = convstr (str, sz); } else if (!strcmp (frameid, "TRCK")) { if (sz > 1000) { err = 1; break; // too large } char str[sz+2]; id3v2_string_read (version_major, &str[0], sz, unsync, &readptr); track = convstr (str, sz); } else if (!strcmp (frameid, "TIT2")) { if (sz > 1000) { err = 1; break; // too large } char str[sz+2]; id3v2_string_read (version_major, &str[0], sz, unsync, &readptr); title = convstr (str, sz); } else if (!strcmp (frameid, "TALB")) { if (sz > 1000) { err = 1; break; // too large } char str[sz+2]; id3v2_string_read (version_major, &str[0], sz, unsync, &readptr); album = convstr (str, sz); } else { readptr += sz; } } else if (version_major == 2) { char frameid[4]; memcpy (frameid, readptr, 3); frameid[3] = 0; readptr += 3; if (readptr - tag >= size - 3) { break; } uint32_t sz = (readptr[2] << 0) | (readptr[1] << 8) | (readptr[0] << 16); readptr += 3; if (readptr - tag >= size - sz) { break; // size of frame is less than size of tag } if (sz < 1) { break; // frame must be at least 1 byte long } // fprintf (stderr, "found id3v2.2 frame: %s, size=%d\n", frameid, sz); if (!strcmp (frameid, "TEN")) { char str[sz+2]; memcpy (str, readptr, sz); str[sz] = 0; vendor = convstr (str, sz); } else if (!strcmp (frameid, "TT2")) { if (sz > 1000) { continue; } char str[sz+2]; memcpy (str, readptr, sz); str[sz] = 0; title = convstr (str, sz); } else if (!strcmp (frameid, "TAL")) { if (sz > 1000) { continue; } char str[sz+2]; memcpy (str, readptr, sz); str[sz] = 0; album = convstr (str, sz); } else if (!strcmp (frameid, "TP1")) { if (sz > 1000) { continue; } char str[sz+2]; memcpy (str, readptr, sz); str[sz] = 0; artist = convstr (str, sz); } else if (!strcmp (frameid, "TP2")) { if (sz > 1000) { continue; } char str[sz+2]; memcpy (str, readptr, sz); str[sz] = 0; band = convstr (str, sz); } else if (!strcmp (frameid, "TRK")) { if (sz > 1000) { continue; } char str[sz+2]; memcpy (str, readptr, sz); str[sz] = 0; track = convstr (str, sz); } readptr += sz; } else { // fprintf (stderr, "id3v2.%d (unsupported!)\n", version_minor); } } if (!err) { if (artist) { pl_add_meta (it, "artist", artist); free (artist); } if (album) { pl_add_meta (it, "album", album); free (album); } if (band) { pl_add_meta (it, "band", band); free (band); } if (track) { pl_add_meta (it, "track", track); free (track); } if (title) { pl_add_meta (it, "title", title); free (title); } if (vendor) { pl_add_meta (it, "vendor", vendor); free (vendor); } if (!title) { pl_add_meta (it, "title", NULL); } return 0; } return -1; }