/* 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" #include "utf8.h" #define trace(...) { fprintf(stderr, __VA_ARGS__); } //#define trace(fmt,...) #define min(x,y) ((x)<(y)?(x):(y)) #define max(x,y) ((x)>(y)?(x):(y)) static uint32_t extract_i32 (const uint8_t *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 (const uint8_t *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 signed 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) { if (str[1] == 0xff && str[2] == 0xfe) { enc = "UCS-2LE"; } else if (str[2] == 0xff && str[1] == 0xfe) { enc = "UCS-2BE"; } else { trace ("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) { trace ("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 trace ("iso8859-1\n"); enc = "iso8859-1"; } else if (*str == 3) { // utf8 trace ("utf8\n"); strncpy (out, str+1, 2047); sz--; out[min (sz, 2047)] = 0; return strdup (out); } else if (*str == 1) { trace ("utf16\n"); enc = "UTF-16"; } else if (*str == 2) { trace ("utf16be\n"); enc = "UTF-16BE"; } else { if (can_be_russian (&str[1])) { enc = "cp1251"; } } str++; sz--; iconv_t cd = iconv_open ("utf8", enc); if (!cd) { trace ("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; } // trace ("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"; if (can_be_russian (str)) { enc = "cp1251"; } cd = iconv_open ("utf8", enc); if (!cd) { // trace ("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) { trace ("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 // trace ("%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); } // FIXME: that should be accounted for // if (it->endoffset < 128) { // it->endoffset = 128; // } return 0; } int junk_read_ape (playItem_t *it, FILE *fp) { // trace ("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_END) == -1) { return -1; // something bad happened } if (fread (header, 1, 32, fp) != 32) { return -1; // something bad happened } if (strncmp (header, "APETAGEX", 8)) { // try to skip 128 bytes backwards (id3v1) if (fseek (fp, -128-32, SEEK_END) == -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)) { // trace ("bad footer\n"); // return -1; // } uint32_t version = extract_i32_le (&header[8]); int32_t size = extract_i32_le (&header[12]); uint32_t numitems = extract_i32_le (&header[16]); uint32_t flags = extract_i32_le (&header[20]); //trace ("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) { trace ("failed to seek to tag start (-%d)\n", size); return -1; } int i; for (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 codes 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; if (!u8_valid (value, itemsize, NULL)) { strcpy (value, ""); } // 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); } else if (!strncasecmp (key, "replaygain_album_gain", 21)) { it->replaygain_album_gain = atof (value); trace ("album_gain=%s\n", value); } else if (!strncasecmp (key, "replaygain_album_peak", 21)) { it->replaygain_album_peak = atof (value); trace ("album_peak=%s\n", value); } else if (!strncasecmp (key, "replaygain_track_gain", 21)) { it->replaygain_track_gain = atof (value); trace ("track_gain=%s\n", value); } else if (!strncasecmp (key, "replaygain_track_peak", 21)) { it->replaygain_track_peak = atof (value); trace ("track_peak=%s\n", value); } } } return 0; } static void id3v2_string_read (int version, uint8_t *out, int sz, int unsync, const uint8_t *pread) { if (!unsync) { memcpy (out, pread, sz); out[sz] = 0; out[sz+1] = 0; return; } uint8_t prev = 0; while (sz > 0) { if (prev == 0xff && !(*pread)) { // trace ("found unsync 0x00 byte\n"); prev = 0; pread++; continue; } prev = *out = *pread; // trace ("%02x ", prev); pread++; out++; sz--; } // trace ("\n"); *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); //trace ("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) { trace ("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) { // trace ("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); // FIXME: that should be accounted for // int startoffset = size + 10 + 10 * footerpresent; // if (startoffset > it->startoffset) { // it->startoffset = startoffset; // } // trace ("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; trace ("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) { break; } uint32_t sz; if (version_major == 4) { sz = (readptr[3] << 0) | (readptr[2] << 7) | (readptr[1] << 14) | (readptr[0] << 21); } else if (version_major == 3) { sz = (readptr[3] << 0) | (readptr[2] << 8) | (readptr[1] << 16) | (readptr[0] << 24); } readptr += 4; trace ("got frame %s, size %d, pos %d, tagsize %d\n", frameid, sz, readptr-tag, size); if (readptr - tag >= size - sz) { trace ("frame is out of tag bounds\n"); 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 } uint8_t flags1 = readptr[0]; uint8_t flags2 = readptr[1]; readptr += 2; if (version_major == 4) { if (flags1 & 0x8f) { // unknown flags trace ("unknown status flags: %02x\n", flags1); readptr += sz; continue; } if (flags2 & 0xb0) { // unknown flags trace ("unknown format flags: %02x\n", flags2); readptr += sz; continue; } if (flags2 & 0x40) { // group id trace ("frame has group id\n"); readptr++; // skip id sz--; } if (flags2 & 0x08) { // compressed frame, ignore trace ("frame is compressed, skipping\n"); readptr += sz; continue; } if (flags2 & 0x04) { // encrypted frame, skip trace ("frame is encrypted, skipping\n"); readptr += sz; continue; } if (flags2 & 0x02) { // unsync, just do nothing } if (flags2 & 0x01) { // data size uint32_t size = extract_i32 (readptr); trace ("frame has extra size field = %d\n", size); readptr += 4; sz -= 4; } } else if (version_major == 3) { if (flags1 & 0x1F) { trace ("unknown status flags: %02x\n", flags1); readptr += sz; continue; } if (flags2 & 0x1F) { trace ("unknown format flags: %02x\n", flags2); readptr += sz; continue; } if (flags2 & 0x80) { trace ("frame is compressed, skipping\n"); readptr += sz; continue; } if (flags2 & 0x40) { trace ("frame is encrypted, skipping\n"); readptr += sz; continue; } if (flags2 & 0x20) { trace ("frame has group id\n"); readptr++; // skip id 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")) { trace ("parsing TIT2...\n"); if (sz > 1000) { err = 1; break; // too large } trace ("parsing TIT2....\n"); 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 if (!strcmp (frameid, "COMM")) { } else if (!strcmp (frameid, "TXXX")) { if (sz < 2) { trace ("TXXX frame is too short, skipped\n"); readptr += sz; // bad tag continue; } uint8_t *p = readptr; uint8_t encoding = *p; p++; uint8_t *desc = p; int desc_sz = 0; while (*p && p - readptr < sz) { p++; desc_sz++; } p++; if (p - readptr >= sz) { trace ("bad TXXX frame, skipped\n"); readptr += sz; // bad tag continue; } char desc_s[desc_sz+2]; id3v2_string_read (version_major, desc_s, desc_sz, unsync, desc); //trace ("desc=%s\n", desc_s); char value_s[readptr+sz-p+2]; id3v2_string_read (version_major, value_s, readptr+sz-p, unsync, p); //trace ("value=%s\n", value_s); if (!strcasecmp (desc_s, "replaygain_album_gain")) { it->replaygain_album_gain = atof (value_s); trace ("%s=%s (%f)\n", desc_s, value_s, it->replaygain_album_gain); } else if (!strcasecmp (desc_s, "replaygain_album_peak")) { it->replaygain_album_peak = atof (value_s); trace ("%s=%s (%f)\n", desc_s, value_s, it->replaygain_album_peak); } else if (!strcasecmp (desc_s, "replaygain_track_gain")) { it->replaygain_track_gain = atof (value_s); trace ("%s=%s (%f)\n", desc_s, value_s, it->replaygain_track_gain); } else if (!strcasecmp (desc_s, "replaygain_track_peak")) { it->replaygain_track_peak = atof (value_s); trace ("%s=%s (%f)\n", desc_s, value_s, it->replaygain_track_peak); } } 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 } // trace ("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 { // trace ("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; } else { trace ("error parsing id3v2\n"); } return -1; } const char * junk_detect_charset (const char *s) { // check if that's already utf8 if (u8_valid (s, strlen (s), NULL)) { return NULL; // means no recoding required } // check if that could be non-latin1 (too many nonascii chars) if (can_be_russian (s)) { return "cp1251"; } return "iso8859-1"; } void junk_recode (const char *in, int inlen, char *out, int outlen, const char *cs) { iconv_t cd = iconv_open ("utf8", cs); if (!cd) { return; } else { size_t inbytesleft = inlen; size_t outbytesleft = outlen; char *pin = (char*)in; char *pout = out; memset (out, 0, outlen); size_t res = iconv (cd, &pin, &inbytesleft, &pout, &outbytesleft); iconv_close (cd); } }