--- ../rockbox-devel/apps/metadata.c 2006-02-01 13:52:08.000000000 -0500 +++ apps/metadata.c 2006-02-10 11:59:35.755572400 -0500 @@ -254,7 +254,8 @@ static long parse_tag(const char* name, char* value, struct mp3entry* id3, char* buf, long buf_remaining, enum tagtype type) { - long len = 0; + long value_len = 0; + long name_len = 0; char** p; if ((((strcasecmp(name, "track") == 0) && (type == TAGTYPE_APE))) @@ -296,31 +297,67 @@ { p = &(id3->composer); } + else if (strcasecmp(name, "comment") == 0) + { + p = &(id3->comment); + } else { - len = parse_replaygain(name, value, id3, buf, buf_remaining); + value_len = parse_replaygain(name, value, id3, buf, buf_remaining); p = NULL; + + /* User defined tag */ + if (value_len < 0) + { + if (id3->num_user_tags < MAX_USER_TAGS) + { + p = &(id3->user_tag[id3->num_user_tags]); + } + } } - + if (p) { - len = strlen(value); - len = MIN(len, buf_remaining - 1); - - if (len > 0) + if (value_len < 0) { - strncpy(buf, value, len); - buf[len] = 0; - *p = buf; - len++; + /* User defined tag - store the name first */ + name_len = strlen(name); + if ((buf_remaining - name_len - 3) > 0) + { + strncpy(buf, name, name_len); + buf[name_len] = 0; + *p = buf; + name_len++; + buf += name_len; + id3->num_user_tags++; + } + else + { + return 0; + } + } + + /* Store the value */ + value_len = strlen(value); + value_len = MIN(value_len, buf_remaining - 1); + + if (value_len > 0) + { + strncpy(buf, value, value_len); + buf[value_len] = 0; + if (!name_len) + { + *p = buf; + } + value_len++; } else { - len = 0; + value_len = 0; } } - - return len; + + return (name_len + value_len); } /* Read the items in an APEV2 tag. Only looks for a tag at the end of a --- ../rockbox-devel/apps/screens.c 2006-01-21 18:43:56.000000000 -0500 +++ apps/screens.c 2006-02-10 11:59:35.786822400 -0500 @@ -961,20 +961,22 @@ } #endif -int draw_id3_item(int line, int top, int header, const char* body) -{ - if (line >= top) - { #if defined(HAVE_LCD_BITMAP) - const int rows = LCD_HEIGHT / font_get(FONT_UI)->height; +#define LINES_PER_ID3_ITEM 3 #else - const int rows = 2; +#define LINES_PER_ID3_ITEM 2 #endif + +int draw_id3_item(int line, int top, const int rows, const char* header, + const char* body) +{ + if (line >= top) + { int y = line - top; if (y < rows) { - lcd_puts(0, y, str(header)); + lcd_puts(0, y, header); } if (++y < rows) @@ -984,29 +986,31 @@ } } - return line + 2; + return line + LINES_PER_ID3_ITEM; } -#if CONFIG_CODEC == SWCODEC -#define ID3_ITEMS 13 -#else -#define ID3_ITEMS 11 -#endif - bool browse_id3(void) { char buf[64]; const struct mp3entry* id3 = audio_current_track(); + +#if CONFIG_CODEC == SWCODEC + const int id3_items = 14 + id3->num_user_tags; +#else + const int id3_items = 12; +#endif + #if defined(HAVE_LCD_BITMAP) const int y_margin = lcd_getymargin(); const int line_height = font_get(FONT_UI)->height; const int rows = (LCD_HEIGHT - y_margin) / line_height; - const bool show_scrollbar = global_settings.scrollbar - && (ID3_ITEMS * 2 > rows); + const bool show_scrollbar = global_settings.scrollbar && + (id3_items * LINES_PER_ID3_ITEM > rows); #else const int rows = 2; #endif - const int top_max = (ID3_ITEMS * 2) - (rows & ~1); + const int top_max = (id3_items * LINES_PER_ID3_ITEM) - rows + + rows % LINES_PER_ID3_ITEM; int top = 0; int button; bool exit = false; @@ -1028,9 +1032,13 @@ lcd_clear_display(); gui_syncstatusbar_draw(&statusbars, true); - line = draw_id3_item(line, top, LANG_ID3_TITLE, id3->title); - line = draw_id3_item(line, top, LANG_ID3_ARTIST, id3->artist); - line = draw_id3_item(line, top, LANG_ID3_ALBUM, id3->album); + line = draw_id3_item(line, top, rows, str(LANG_ID3_TITLE), id3->title); + line = draw_id3_item(line, top, rows, str(LANG_ID3_ARTIST), + id3->artist); + line = draw_id3_item(line, top, rows, str(LANG_ID3_ALBUM), id3->album); + + /* FIXME: There should be a lang file entry for the comment string */ + line = draw_id3_item(line, top, rows, "[Comment]", id3->comment); if (id3->track_string) { @@ -1046,10 +1054,10 @@ body = NULL; } - line = draw_id3_item(line, top, LANG_ID3_TRACKNUM, body); + line = draw_id3_item(line, top, rows, str(LANG_ID3_TRACKNUM), body); body = id3->genre_string ? id3->genre_string : id3_get_genre(id3); - line = draw_id3_item(line, top, LANG_ID3_GENRE, body); + line = draw_id3_item(line, top, rows, str(LANG_ID3_GENRE), body); if (id3->year_string) { @@ -1065,37 +1073,46 @@ body = NULL; } - line = draw_id3_item(line, top, LANG_ID3_YEAR, body); + line = draw_id3_item(line, top, rows, str(LANG_ID3_YEAR), body); gui_wps_format_time(buf, sizeof(buf), id3->length); - line = draw_id3_item(line, top, LANG_ID3_LENGHT, buf); + line = draw_id3_item(line, top, rows, str(LANG_ID3_LENGHT), buf); snprintf(buf, sizeof(buf), "%d/%d", playlist_get_display_index(), playlist_amount()); - line = draw_id3_item(line, top, LANG_ID3_PLAYLIST, buf); + line = draw_id3_item(line, top, rows, str(LANG_ID3_PLAYLIST), buf); snprintf(buf, sizeof(buf), "%d kbps%s", id3->bitrate, id3->vbr ? str(LANG_ID3_VBR) : (const unsigned char*) ""); - line = draw_id3_item(line, top, LANG_ID3_BITRATE, buf); + line = draw_id3_item(line, top, rows, str(LANG_ID3_BITRATE), buf); snprintf(buf, sizeof(buf), "%ld Hz", id3->frequency); - line = draw_id3_item(line, top, LANG_ID3_FRECUENCY, buf); + line = draw_id3_item(line, top, rows, str(LANG_ID3_FRECUENCY), buf); #if CONFIG_CODEC == SWCODEC - line = draw_id3_item(line, top, LANG_ID3_TRACK_GAIN, + line = draw_id3_item(line, top, rows, str(LANG_ID3_TRACK_GAIN), id3->track_gain_string); - line = draw_id3_item(line, top, LANG_ID3_ALBUM_GAIN, + line = draw_id3_item(line, top, rows, str(LANG_ID3_ALBUM_GAIN), id3->album_gain_string); + + int i; + for (i=0; i < id3->num_user_tags; i++) + { + snprintf(buf, sizeof(buf), "[%s]", id3->user_tag[i]); + line = draw_id3_item(line, top, rows, buf, + id3->user_tag[i] + strlen(id3->user_tag[i]) + 1); + } #endif - line = draw_id3_item(line, top, LANG_ID3_PATH, id3->path); + line = draw_id3_item(line, top, rows, str(LANG_ID3_PATH), id3->path); #if defined(HAVE_LCD_BITMAP) if (show_scrollbar) { scrollbar(0, y_margin, SCROLLBAR_WIDTH - 1, rows * line_height, - ID3_ITEMS * 2 + (rows & 1), top, top + rows, VERTICAL); + id3_items * LINES_PER_ID3_ITEM + + rows % LINES_PER_ID3_ITEM, top, top + rows, VERTICAL); } #endif @@ -1118,7 +1135,7 @@ #endif if (top > 0) { - top -= 2; + top -= LINES_PER_ID3_ITEM; } else if (!(button & BUTTON_REPEAT)) { @@ -1135,7 +1152,7 @@ #endif if (top < top_max) { - top += 2; + top += LINES_PER_ID3_ITEM; } else if (!(button & BUTTON_REPEAT)) { --- ../rockbox-devel/apps/gui/gwps-common.c 2006-02-10 10:08:06.000000000 -0500 +++ apps/gui/gwps-common.c 2006-02-10 11:59:35.818072400 -0500 @@ -546,6 +546,29 @@ default: return NULL; } + + case 'm': /* id3 Comment */ + return id3->comment; + +#if CONFIG_CODEC == SWCODEC + case 'x': /* user tags: %ix"TAGNAME" */ + if ( tag[2] == '"' ) { + char* name = (char *)tag+3; + char* pos = strchr(name, '"'); + if (pos) { + int len = pos - name; + *tag_len = len + 4; + + int i; + for (i=0; i < id3->num_user_tags; i++) { + if (strncmp(name, id3->user_tag[i], len) == 0) { + return (id3->user_tag[i] + len + 1); + } + } + } + } + return NULL; +#endif } break; --- ../rockbox-devel/firmware/id3.c 2006-02-01 13:52:16.000000000 -0500 +++ firmware/id3.c 2006-02-10 16:06:24.231472400 -0500 @@ -311,8 +311,30 @@ } } +/* parse the id3v2 comment field (COMM). The first 3 characters are the + language (ignored), followed by a null terminated comment description + (also ignored), and then the comment. */ +static int parsecomment( struct mp3entry* entry, char* tag, int bufferpos ) +{ + int desc_len = strlen(tag) + 1; + + if (ID3V2_BUFSIZE - bufferpos - desc_len < 3) + return bufferpos; + + /* At least part of the comment is here. Copy just the comment text to + the beginning of tag. */ + char* value = tag + desc_len; + int value_len = strlen(value); + strncpy(tag, value, value_len); + tag[value_len] = 0; + + entry->comment = tag; + return (bufferpos + value_len + 1); +} + #if CONFIG_CODEC == SWCODEC -/* parse user defined text, looking for replaygain information. */ +/* parse user defined text fields (TXXX). Look for replaygain information + first, if not present store in the user_tag[] array. */ static int parseuser( struct mp3entry* entry, char* tag, int bufferpos ) { char* value = NULL; @@ -328,12 +350,26 @@ bufferpos - (tag - entry->id3v2buf)); } - if (value_len) { - bufferpos = tag - entry->id3v2buf + value_len; - } else { - bufferpos = tag - entry->id3v2buf; - } - + if (value_len >= 0) { + /* replaygain tag */ + desc_len = 0; + } else { + /* user defined tag (store both the tag and value) */ + if (entry->num_user_tags < MAX_USER_TAGS) { + entry->user_tag[entry->num_user_tags] = tag; + desc_len++; + value_len = strlen(value); + tag[desc_len + value_len] = 0; + value_len++; + entry->num_user_tags++; + } else { + /* skip the tag */ + desc_len = 0; + value_len = 0; + } + } + + bufferpos = tag - entry->id3v2buf + desc_len + value_len; return bufferpos; } #endif @@ -352,8 +388,10 @@ { "TCOM", 4, offsetof(struct mp3entry, composer), NULL }, { "TCON", 4, offsetof(struct mp3entry, genre_string), &parsegenre }, { "TCO", 3, offsetof(struct mp3entry, genre_string), &parsegenre }, + { "COMM", 4, offsetof(struct mp3entry, comment), &parsecomment }, #if CONFIG_CODEC == SWCODEC { "TXXX", 4, 0, &parseuser }, + { "USER", 4, 0, &parseuser }, /* dummy tag, keep this last */ #endif }; @@ -450,7 +488,7 @@ static bool setid3v1title(int fd, struct mp3entry *entry) { unsigned char buffer[128]; - static const char offsets[] = {3, 33, 63, 93, 125, 127}; + static const char offsets[] = {3, 33, 63, 93, 97, 127}; int i, j; unsigned char* utf8; @@ -487,18 +525,31 @@ break; case 3: + { + char c = ptr[4]; ptr[4] = 0; entry->year = atoi(ptr); + ptr[4] = c; 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]; + int n = 29; + if (!ptr[28] && ptr[29]) { + entry->tracknum = ptr[29]; entry->id3version = ID3_VER_1_1; + n = 27; } + /* kill trailing spaces from the comment */ + for (j=n; j && ptr[j]==' '; j--) + ptr[j] = 0; + strncpy(entry->id3v1buf[3], ptr, n + 1); + entry->comment = entry->id3v1buf[3]; break; + } case 5: /* genre */ @@ -514,7 +565,6 @@ return true; } - /* * Sets the title of an MP3 entry based on its ID3v2 tag. * @@ -749,17 +799,33 @@ continue; } + /* "USER" is the dummy tag name. This means we got to the end of + the list without finding a matching valid tag, so we'll store + the tag in the user_tag array so it can be referenced by its + header name in the wps, ie %ix"header". */ + int is_user_tag = !memcmp( "USER", tr->tag, tr->tag_length ); + int header_len = strlen(header); + /* Note that parser functions sometimes set *ptag to NULL, so * the "!*ptag" check here doesn't always have the desired * effect. Should the parser functions (parsegenre in * particular) be updated to handle the case of being called * multiple times, or should the "*ptag" check be removed? */ - if( (!ptag || !*ptag) && !memcmp( header, tr->tag, tr->tag_length ) ) { - + if( (!ptag || !*ptag) && + (!memcmp( header, tr->tag, tr->tag_length ) || is_user_tag ) ) { + /* found a tag matching one in tagList, and not yet filled */ tag = buffer + bufferpos; + /* for user tags, include the tag header first */ + if ( is_user_tag ) { + strncpy(tag, header, header_len); + tag[header_len] = 0; + header_len++; + tag += header_len; + } + if(global_unsynch && version <= ID3_VER_2_3) bytesread = read_unsynched(fd, tag, framelen); else @@ -772,7 +838,6 @@ if(unsynch || (global_unsynch && version >= ID3_VER_2_4)) bytesread = unsynchronize_frame(tag, bytesread); - /* UTF-8 could potentially be 3 times larger */ /* so we need to create a new buffer */ @@ -795,6 +860,10 @@ tag[bytesread] = 0; bufferpos += bytesread + 1; + /* for user tags, back up to where the header begins */ + if ( is_user_tag ) + tag -= header_len; + if( tr->ppFunc ) bufferpos = tr->ppFunc(entry, tag, bufferpos); --- ../rockbox-devel/firmware/replaygain.c 2005-10-03 14:35:02.000000000 -0400 +++ firmware/replaygain.c 2006-02-10 11:59:35.833697400 -0500 @@ -370,10 +370,12 @@ || ((strcasecmp(key, "rg_peak") == 0) && !entry->track_peak)) { entry->track_peak = get_replaypeak(value); + return 0; } else if (strcasecmp(key, "replaygain_album_peak") == 0) { entry->album_peak = get_replaypeak(value); + return 0; } if (p) @@ -392,5 +394,6 @@ } } - return 0; + /* Not a replaygain tag */ + return -1; } --- ../rockbox-devel/firmware/export/id3.h 2006-02-01 13:52:18.000000000 -0500 +++ firmware/export/id3.h 2006-02-10 11:59:35.849322400 -0500 @@ -48,6 +48,14 @@ AFMT_NUM_CODECS }; +/* Make the buffer a little bigger for arbitrary tags */ +#if CONFIG_CODEC == SWCODEC +#define ID3V2_BUFSIZE 400 +#define MAX_USER_TAGS 8 +#else +#define ID3V2_BUFSIZE 300 +#endif + struct mp3entry { char path[MAX_PATH]; char* title; @@ -57,6 +65,7 @@ char* track_string; char* year_string; char* composer; + char* comment; int tracknum; int version; int layer; @@ -94,8 +103,8 @@ unsigned char toc[100]; /* table of contents */ /* these following two fields are used for local buffering */ - char id3v2buf[300]; - char id3v1buf[3][92]; + char id3v2buf[ID3V2_BUFSIZE]; + char id3v1buf[4][92]; /* resume related */ unsigned long offset; /* bytes played */ @@ -123,6 +132,10 @@ long album_gain; long track_peak; /* 7.24 signed fixed point. 0 for no peak. */ long album_peak; + + /* For arbitrary tags that are catagorized above */ + int num_user_tags; + char* user_tag[MAX_USER_TAGS]; #endif };