diff options
-rw-r--r-- | deadbeef.h | 7 | ||||
-rw-r--r-- | junklib.c | 216 | ||||
-rw-r--r-- | junklib.h | 9 | ||||
-rw-r--r-- | playlist.c | 5 | ||||
-rw-r--r-- | plugins.c | 7 | ||||
-rw-r--r-- | plugins/mpgmad/mpgmad.c | 168 |
6 files changed, 364 insertions, 48 deletions
@@ -423,6 +423,8 @@ typedef struct { // junk reading/writing int (*junk_id3v1_read) (DB_playItem_t *it, DB_FILE *fp); int (*junk_id3v1_find) (DB_FILE *fp); + int (*junk_id3v1_write) (FILE *fp, DB_playItem_t *it); + int (*junk_id3v2_find) (DB_FILE *fp, int *psize); int (*junk_id3v2_read) (DB_playItem_t *it, DB_FILE *fp); int (*junk_id3v2_read_full) (DB_playItem_t *it, DB_id3v2_tag_t *tag, DB_FILE *fp); int (*junk_id3v2_convert_24_to_23) (DB_id3v2_tag_t *tag24, DB_id3v2_tag_t *tag23); @@ -434,7 +436,12 @@ typedef struct { DB_id3v2_frame_t *(*junk_id3v2_add_text_frame_24) (DB_id3v2_tag_t *tag, const char *frame_id, const char *value); int (*junk_id3v2_remove_frames) (DB_id3v2_tag_t *tag, const char *frame_id); int (*junk_apev2_read) (DB_playItem_t *it, DB_FILE *fp); + int (*junk_apev2_read_full) (DB_playItem_t *it, DB_apev2_tag_t *tag_store, DB_FILE *fp); int (*junk_apev2_find) (DB_FILE *fp, int32_t *psize, uint32_t *pflags, uint32_t *pnumitems); + int (*junk_apev2_remove_frames) (DB_apev2_tag_t *tag, const char *frame_id); + DB_apev2_frame_t * (*junk_apev2_add_text_frame) (DB_apev2_tag_t *tag, const char *frame_id, const char *value); + void (*junk_apev2_free) (DB_apev2_tag_t *tag); + int (*junk_apev2_write) (FILE *fp, DB_apev2_tag_t *tag, int write_header, int write_footer); int (*junk_get_leading_size) (DB_FILE *fp); int (*junk_get_leading_size_stdio) (FILE *fp); void (*junk_copy) (DB_playItem_t *from, DB_playItem_t *first, DB_playItem_t *last); @@ -105,6 +105,37 @@ extract_f32 (unsigned char *buf) { *x |= buf[3]; return f; } + +int +junk_iconv (const char *in, int inlen, char *out, int outlen, const char *cs_in, const char *cs_out) { + iconv_t cd = iconv_open (cs_out, cs_in); + if (cd == (iconv_t)-1) { + return -1; + } +#ifdef __linux__ + char *pin = (char*)in; +#else + const char *pin = value; +#endif + + size_t inbytesleft = inlen; + size_t outbytesleft = outlen; + + char *pout = out; + memset (out, 0, outbytesleft); + + size_t res = iconv (cd, &pin, &inbytesleft, &pout, &outbytesleft); + int err = errno; + iconv_close (cd); + + trace ("iconv -f %s -t %s '%s': returned %d, inbytes %d/%d, outbytes %d/%d, errno=%d\n", cs_in, cs_out, in, res, inlen, inbytesleft, outlen, outbytesleft, err); + if (res == -1) { + return -1; + } + return pout - out; +} + + static const char *junk_genretbl[] = { "Blues", "Classic Rock", @@ -254,6 +285,7 @@ static const char *junk_genretbl[] = { "Anime", "JPop", "SynthPop", + NULL }; static int @@ -573,6 +605,98 @@ junk_id3v1_read (playItem_t *it, DB_FILE *fp) { } int +junk_id3v1_write (FILE *fp, playItem_t *it) { + char title[30] = ""; + char artist[30] = ""; + char album[30] = ""; + char year[4] = ""; + char comment[28] = ""; + uint8_t genreid = 0xff; + uint8_t tracknum = 0; + + const char *meta; + +#define conv(name, store) {\ + memset (store, 0x20, sizeof (store));\ + meta = pl_find_meta (it, name);\ + if (meta) {\ + char temp[1000];\ + int l = junk_iconv (meta, strlen (meta), temp, sizeof (temp), UTF8, "ASCII");\ + if (l == -1) {\ + memset (store, 0, sizeof (store));\ + }\ + else {\ + strncpy (store, temp, sizeof (store));\ + }\ + }\ +} + + conv ("title", title); + conv ("artist", artist); + conv ("album", album); + conv ("year", year); + conv ("comment", comment); + +#undef conv + + // tracknum + meta = pl_find_meta (it, "track"); + if (meta) { + tracknum = atoi (meta); + } + + // find genre + meta = pl_find_meta (it, "genre"); + if (meta) { + for (int i = 0; junk_genretbl[i]; i++) { + if (!strcasecmp (meta, junk_genretbl[i])) { + genreid = i; + break; + } + } + } + + if (fwrite ("TAG", 1, 3, fp) != 3) { + trace ("junk_id3v1_write: failed to write signature\n"); + return -1; + } + if (fwrite (title, 1, sizeof (title), fp) != sizeof (title)) { + trace ("junk_id3v1_write: failed to write title\n"); + return -1; + } + if (fwrite (artist, 1, sizeof (artist), fp) != sizeof (artist)) { + trace ("junk_id3v1_write: failed to write artist\n"); + return -1; + } + if (fwrite (album, 1, sizeof (album), fp) != sizeof (album)) { + trace ("junk_id3v1_write: failed to write album\n"); + return -1; + } + if (fwrite (year, 1, sizeof (year), fp) != sizeof (year)) { + trace ("junk_id3v1_write: failed to write year\n"); + return -1; + } + if (fwrite (comment, 1, sizeof (comment), fp) != sizeof (comment)) { + trace ("junk_id3v1_write: failed to write comment\n"); + return -1; + } + uint8_t zero = 0; + if (fwrite (&zero, 1, 1, fp) != 1) { + trace ("junk_id3v1_write: failed to write id3v1.1 marker\n"); + return -1; + } + if (fwrite (&tracknum, 1, 1, fp) != 1) { + trace ("junk_id3v1_write: failed to write track\n"); + return -1; + } + if (fwrite (&genreid, 1, 1, fp) != 1) { + trace ("junk_id3v1_write: failed to write genre\n"); + return -1; + } + return 0; +} + +int junk_id3v1_find (DB_FILE *fp) { uint8_t buffer[3]; if (deadbeef->fseek (fp, -128, SEEK_END) == -1) { @@ -584,7 +708,7 @@ junk_id3v1_find (DB_FILE *fp) { if (memcmp (buffer, "TAG", 3)) { return -1; // no tag } - return 128; + return deadbeef->ftell (fp) - 3; } int @@ -861,6 +985,40 @@ id3v2_string_read (int version, uint8_t *out, int sz, int unsync, const uint8_t } int +junk_id3v2_find (DB_FILE *fp, int *psize) { + if (deadbeef->fseek (fp, 0, SEEK_SET) == -1) { + trace ("junk_id3v2_find: seek error\n"); + return -1; + } + uint8_t header[10]; + int pos = deadbeef->ftell (fp); + if (pos == -1) { + trace ("junk_id3v2_find: ftell error\n"); + return -1; + } + if (deadbeef->fread (header, 1, 10, fp) != 10) { + trace ("junk_id3v2_find: read error\n"); + return -1; // too short + } + if (strncmp (header, "ID3", 3)) { + return -1; // no tag + } + uint8_t flags = header[5]; + if (flags & 15) { + return -1; // unsupported + } + 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); + *psize = size + 10 + 10 * footerpresent; + return pos; +} + +int junk_get_leading_size_stdio (FILE *fp) { uint8_t header[10]; int pos = ftell (fp); @@ -913,36 +1071,6 @@ junk_get_leading_size (DB_FILE *fp) { //trace ("junklib: leading junk size %d\n", size); return size + 10 + 10 * footerpresent; } - -int -junk_iconv (const char *in, int inlen, char *out, int outlen, const char *cs_in, const char *cs_out) { - iconv_t cd = iconv_open (cs_out, cs_in); - if (cd == (iconv_t)-1) { - return -1; - } -#ifdef __linux__ - char *pin = (char*)in; -#else - const char *pin = value; -#endif - - size_t inbytesleft = inlen; - size_t outbytesleft = outlen; - - char *pout = out; - memset (out, 0, outbytesleft); - - size_t res = iconv (cd, &pin, &inbytesleft, &pout, &outbytesleft); - int err = errno; - iconv_close (cd); - - trace ("iconv -f %s -t %s '%s': returned %d, inbytes %d/%d, outbytes %d/%d, errno=%d\n", cs_in, cs_out, in, res, inlen, inbytesleft, outlen, outbytesleft, err); - if (res == -1) { - return -1; - } - return pout - out; -} - int junk_id3v2_unsync (uint8_t *out, int len, int maxlen) { uint8_t buf [maxlen]; @@ -1655,6 +1783,28 @@ junk_id3v2_convert_22_to_24 (DB_id3v2_tag_t *tag22, DB_id3v2_tag_t *tag24) { return 0; } +int +junk_apev2_remove_frames (DB_apev2_tag_t *tag, const char *frame_id) { + DB_apev2_frame_t *prev = NULL; + for (DB_apev2_frame_t *f = tag->frames; f; ) { + DB_apev2_frame_t *next = f->next; + if (!strcmp (f->key, frame_id)) { + if (prev) { + prev->next = f->next; + } + else { + tag->frames = f->next; + } + free (f); + } + else { + prev = f; + } + f = next; + } + return 0; +} + DB_apev2_frame_t * junk_apev2_add_text_frame (DB_apev2_tag_t *tag, const char *frame_id, const char *value) { DB_apev2_frame_t *tail = tag->frames; @@ -1771,6 +1921,9 @@ junk_apev2_write (FILE *fp, DB_apev2_tag_t *tag, int write_header, int write_foo numframes++; f = f->next; } + size += 32; + + trace ("junk_apev2_write: writing apev2 tag, size=%d, numframes=%d\n", size, numframes); if (write_header) { @@ -1822,6 +1975,7 @@ junk_apev2_write (FILE *fp, DB_apev2_tag_t *tag, int write_header, int write_foo trace ("junk_apev2_write_i32_le: failed to write apev2 item value\n"); goto error; } + f = f->next; } if (write_footer) { @@ -30,6 +30,12 @@ int junk_id3v1_find (DB_FILE *fp); int +junk_id3v1_write (FILE *fp, struct playItem_s *it); + +int +junk_id3v2_find (DB_FILE *fp, int *psize); + +int junk_id3v2_read_full (struct playItem_s *it, DB_id3v2_tag_t *tag, DB_FILE *fp); int @@ -74,6 +80,9 @@ junk_apev2_find (DB_FILE *fp, int32_t *psize, uint32_t *pflags, uint32_t *pnumit DB_apev2_frame_t * junk_apev2_add_text_frame (DB_apev2_tag_t *tag, const char *frame_id, const char *value); +int +junk_apev2_remove_frames (DB_apev2_tag_t *tag, const char *frame_id); + void junk_apev2_free (DB_apev2_tag_t *tag); @@ -2205,6 +2205,11 @@ pl_format_title (playItem_t *it, int idx, char *s, int size, int id, const char if (text) { strncpy (s, text, size); UNLOCK; + for (ss = s; *ss; ss++) { + if (*ss == '\n') { + *ss = ';'; + } + } return strlen (s); } else { @@ -181,6 +181,8 @@ static DB_functions_t deadbeef_api = { // junk reading .junk_id3v1_read = (int (*)(DB_playItem_t *it, DB_FILE *fp))junk_id3v1_read, .junk_id3v1_find = junk_id3v1_find, + .junk_id3v1_write = (int (*) (FILE *, DB_playItem_t *))junk_id3v1_write, + .junk_id3v2_find = junk_id3v2_find, .junk_id3v2_read = (int (*)(DB_playItem_t *it, DB_FILE *fp))junk_id3v2_read, .junk_id3v2_read_full = (int (*)(DB_playItem_t *, DB_id3v2_tag_t *tag, DB_FILE *fp))junk_id3v2_read_full, .junk_id3v2_convert_24_to_23 = junk_id3v2_convert_24_to_23, @@ -192,7 +194,12 @@ static DB_functions_t deadbeef_api = { .junk_id3v2_add_text_frame_23 = junk_id3v2_add_text_frame_23, .junk_id3v2_add_text_frame_24 = junk_id3v2_add_text_frame_24, .junk_apev2_read = (int (*)(DB_playItem_t *it, DB_FILE *fp))junk_apev2_read, + .junk_apev2_read_full = (int (*) (DB_playItem_t *it, DB_apev2_tag_t *tag_store, DB_FILE *fp))junk_apev2_read_full, .junk_apev2_find = junk_apev2_find, + .junk_apev2_remove_frames = junk_apev2_remove_frames, + .junk_apev2_add_text_frame = junk_apev2_add_text_frame, + .junk_apev2_free = junk_apev2_free, + .junk_apev2_write = junk_apev2_write, .junk_get_leading_size = junk_get_leading_size, .junk_get_leading_size_stdio = junk_get_leading_size_stdio, .junk_detect_charset = junk_detect_charset, diff --git a/plugins/mpgmad/mpgmad.c b/plugins/mpgmad/mpgmad.c index a109985c..d88abe6f 100644 --- a/plugins/mpgmad/mpgmad.c +++ b/plugins/mpgmad/mpgmad.c @@ -342,9 +342,11 @@ cmp3_scan_stream (buffer_t *buffer, int sample) { } valid_frames++; +#if 0 if (nframe < 1000) { - trace ("frame %d bitrate %d\n", nframe, bitrate); + trace ("frame %d bitrate %d\n", nframe, bitrate); } +#endif if (sample != 0 || nframe == 0) { buffer->version = ver; @@ -964,7 +966,7 @@ cmp3_read_int16 (DB_fileinfo_t *_info, char *bytes, int size) { static int cmp3_read_float32 (DB_fileinfo_t *_info, char *bytes, int size) { mpgmad_info_t *info = (mpgmad_info_t *)_info; - trace ("cmp3_read_float32 readsize=%d, nchannels=%d\n", size, _info->channels); +// trace ("cmp3_read_float32 readsize=%d, nchannels=%d\n", size, _info->channels); info->buffer.readsize = size; info->buffer.out = bytes; cmp3_decode_float32 (info); @@ -1194,9 +1196,11 @@ cmp3_write_metadata (DB_playItem_t *it) { return -1; } - int id3v2_end = deadbeef->junk_get_leading_size (fp); - if (id3v2_end == -1) { - id3v2_end = 0; + int fsize = deadbeef->fgetlength (fp); + int id3v2_size = 0; + int id3v2_start = deadbeef->junk_id3v2_find (fp, &id3v2_size); + if (id3v2_start == -1) { + id3v2_size = -1; } int32_t apev2_size; @@ -1206,13 +1210,24 @@ cmp3_write_metadata (DB_playItem_t *it) { apev2_start = 0; } + if (!strip_apev2 && !write_apev2) { + apev2_start = 0; + } + int id3v1_start = deadbeef->junk_id3v1_find (fp); if (id3v1_start == -1) { id3v1_start = 0; } - int header = id3v2_end; - int footer = min (id3v1_start, apev2_start); + int header = id3v2_start + id3v2_size; + int footer = fsize; + + if (id3v1_start > 0) { + footer = id3v1_start; + } + if (apev2_start > 0) { + footer = min (footer, apev2_start); + } // mapping between ddb metadata names and id3v2/apev2 names const char *md[] = { @@ -1226,6 +1241,10 @@ cmp3_write_metadata (DB_playItem_t *it) { "vendor", "TENC", NULL, "performer", "TPE3", NULL, "composer", "TCOM", "Composer", + "year", NULL, "Year", + "comment", NULL, "Comment", + "copyright", NULL, "Copyright", + "cuesheet", NULL, "Cuesheet", NULL }; // "TRCK" -- special case @@ -1249,12 +1268,30 @@ cmp3_write_metadata (DB_playItem_t *it) { memset (&id3v2, 0, sizeof (id3v2)); memset (&apev2, 0, sizeof (id3v2)); - if (!strip_id3v2 && !write_id3v2) { - id3v2_end = 0; + if (!strip_id3v2 && !write_id3v2 && id3v2_size > 0) { + if (deadbeef->fseek (fp, id3v2_start, SEEK_SET) == -1) { + trace ("cmp3_write_metadata: failed to seek to original id3v2 tag position in %s\n", it->fname); + goto error; + } + uint8_t *buf = malloc (id3v2_size); + if (!buf) { + trace ("cmp3_write_metadata: failed to alloc %d bytes for id3v2 tag\n", id3v2_size); + goto error; + } + if (deadbeef->fread (buf, 1, id3v2_size, fp) != id3v2_size) { + trace ("cmp3_write_metadata: failed to read original id3v2 tag from %s\n", it->fname); + free (buf); + goto error; + } + if (fwrite (buf, 1, id3v2_size, out) != id3v2_size) { + trace ("cmp3_write_metadata: failed to copy original id3v2 tag from %s to temp file\n", it->fname); + free (buf); + goto error; + } + free (buf); } else if (write_id3v2) { - if (!id3v2_end || deadbeef->junk_id3v2_read_full (NULL, &id3v2, fp) != 0) { - trace ("failed to read id3v2 from file %s\n", it->fname); + if (id3v2_size <= 0 || strip_id3v2 || deadbeef->junk_id3v2_read_full (NULL, &id3v2, fp) != 0) { deadbeef->junk_id3v2_free (&id3v2); memset (&id3v2, 0, sizeof (id3v2)); id3v2.version[0] = id3v2_version; @@ -1297,26 +1334,45 @@ cmp3_write_metadata (DB_playItem_t *it) { add_frame = deadbeef->junk_id3v2_add_text_frame_24; } - // add all known frames + // add all basic frames for (int i = 0; md[i]; i += 3) { if (md[i+1]) { const char *val = deadbeef->pl_find_meta (it, md[i]); if (val) { + deadbeef->junk_id3v2_remove_frames (&id3v2, md[i+1]); add_frame (&id3v2, md[i+1], val); } } } + + // add tracknumber/totaltracks const char *track = deadbeef->pl_find_meta (it, "track"); const char *totaltracks = deadbeef->pl_find_meta (it, "numtracks"); if (track && totaltracks) { char s[100]; snprintf (s, sizeof (s), "%s/%s", track, totaltracks); + deadbeef->junk_id3v2_remove_frames (&id3v2, "TRCK"); add_frame (&id3v2, "TRCK", s); } else if (track) { + deadbeef->junk_id3v2_remove_frames (&id3v2, "TRCK"); add_frame (&id3v2, "TRCK", track); } + // add year/date + const char *year = deadbeef->pl_find_meta (it, "year"); + if (year) { + // FIXME: format check + if (id3v2.version[0] == 3) { + deadbeef->junk_id3v2_remove_frames (&id3v2, "TYER"); + add_frame (&id3v2, "TYER", year); + } + else { + deadbeef->junk_id3v2_remove_frames (&id3v2, "TDRC"); + add_frame (&id3v2, "TDRC", year); + } + } + // write tag if (deadbeef->junk_id3v2_write (out, &id3v2) != 0) { trace ("cmp3_write_metadata: failed to write id3v2 tag to %s\n", it->fname) @@ -1327,12 +1383,12 @@ cmp3_write_metadata (DB_playItem_t *it) { // now write audio data buffer = malloc (8192); deadbeef->fseek (fp, header, SEEK_SET); - int writesize = deadbeef->fgetlength (fp); + int writesize = fsize; if (footer > 0) { - writesize -= (writesize - footer); + writesize -= (fsize - footer); } writesize -= header; - trace ("writesize: %d\n", writesize); + trace ("writesize: %d, id3v1_start: %d, apev2_start: %d, footer: %d\n", writesize, id3v1_start, apev2_start, footer); while (writesize > 0) { int rb = min (8192, writesize); @@ -1345,14 +1401,92 @@ cmp3_write_metadata (DB_playItem_t *it) { fprintf (stderr, "junk_write_id3v2: error writing output file\n"); goto error; } - trace ("written %d bytes\n", rb); if (rb == 0) { break; // eof } writesize -= rb; } - // TODO: apev2 and id3v1 + if (!write_apev2 && !strip_apev2 && apev2_start != 0) { + if (deadbeef->fseek (fp, apev2_start, SEEK_SET) == -1) { + trace ("cmp3_write_metadata: failed to seek to original apev2 tag position in %s\n", it->fname); + goto error; + } + uint8_t *buf = malloc (apev2_size); + if (!buf) { + trace ("cmp3_write_metadata: failed to alloc %d bytes for apev2 tag\n", apev2_size); + goto error; + } + if (deadbeef->fread (buf, 1, apev2_size, fp) != apev2_size) { + trace ("cmp3_write_metadata: failed to read original apev2 tag from %s\n", it->fname); + free (buf); + goto error; + } + if (fwrite (buf, 1, apev2_size, out) != apev2_size) { + trace ("cmp3_write_metadata: failed to copy original apev2 tag from %s to temp file\n", it->fname); + free (buf); + goto error; + } + free (buf); + } + else if (write_apev2) { + if (!strip_apev2 || deadbeef->junk_apev2_read_full (NULL, &apev2, fp) != 0) { + deadbeef->junk_apev2_free (&apev2); + memset (&apev2, 0, sizeof (apev2)); + } + // add all basic frames + for (int i = 0; md[i]; i += 3) { + if (md[i+2]) { + const char *val = deadbeef->pl_find_meta (it, md[i]); + if (val) { + deadbeef->junk_apev2_remove_frames (&apev2, md[i+2]); + deadbeef->junk_apev2_add_text_frame (&apev2, md[i+2], val); + } + } + } + + // add tracknumber/totaltracks + const char *track = deadbeef->pl_find_meta (it, "track"); + const char *totaltracks = deadbeef->pl_find_meta (it, "numtracks"); + if (track && totaltracks) { + char s[100]; + snprintf (s, sizeof (s), "%s/%s", track, totaltracks); + deadbeef->junk_apev2_remove_frames (&apev2, "Track"); + deadbeef->junk_apev2_add_text_frame (&apev2, "Track", s); + } + else if (track) { + deadbeef->junk_apev2_remove_frames (&apev2, "Track"); + deadbeef->junk_apev2_add_text_frame (&apev2, "Track", track); + } + + // write tag + if (deadbeef->junk_apev2_write (out, &apev2, 0, 1) != 0) { + trace ("cmp3_write_metadata: failed to write apev2 tag to %s\n", it->fname) + goto error; + } + } + + if (!write_id3v1 && !strip_id3v1 && id3v1_start != 0) { + if (deadbeef->fseek (fp, id3v1_start, SEEK_SET) == -1) { + trace ("cmp3_write_metadata: failed to seek to original id3v1 tag position in %s\n", it->fname); + goto error; + } + char buf[128]; + if (deadbeef->fread (buf, 1, 128, fp) != 128) { + trace ("cmp3_write_metadata: failed to read original id3v1 tag from %s\n", it->fname); + goto error; + } + if (fwrite (buf, 1, 128, out) != 128) { + trace ("cmp3_write_metadata: failed to copy id3v1 tag from %s to temp file\n", it->fname); + goto error; + } + } + else if (write_id3v1) { + if (deadbeef->junk_id3v1_write (out, it) != 0) { + trace ("cmp3_write_metadata: failed to write id3v1 tag to %s\n", it->fname) + goto error; + } + } err = 0; error: |