Index: apps/metadata/metadata_common.c =================================================================== --- apps/metadata/metadata_common.c (révision 31047) +++ apps/metadata/metadata_common.c (copie de travail) @@ -333,6 +333,22 @@ { p = &(id3->grouping); } + else if (strcasecmp(name, "artistsort") == 0) + { + p = &(id3->artistsort); + } + else if (strcasecmp(name, "albumartistsort") == 0) + { + p = &(id3->albumartistsort); + } + else if (strcasecmp(name, "albumsort") == 0) + { + p = &(id3->albumsort); + } + else if (strcasecmp(name, "titlesort") == 0) + { + p = &(id3->titlesort); + } else if (strcasecmp(name, "musicbrainz_trackid") == 0 || strcasecmp(name, "http://musicbrainz.org") == 0 ) { Index: apps/metadata/id3tags.c =================================================================== --- apps/metadata/id3tags.c (révision 31047) +++ apps/metadata/id3tags.c (copie de travail) @@ -364,16 +364,29 @@ * parse it */ value = tag + desc_len + 1; - if (!strcasecmp(tag, "ALBUM ARTIST")) { + /* albumartistsort = tag; + } + /* id3 does not have an official tag dedicated for album artist */ + else if ((!strcmp(tag, "ALBUM ARTIST")) || (!strcmp(tag, "ALBUMARTIST"))) + { length = strlen(value) + 1; strlcpy(tag, value, length); entry->albumartist = tag; + } #if CONFIG_CODEC == SWCODEC - } else { - /* Call parse_replaygain(). */ - parse_replaygain(tag, value, entry); + else + { + /* Note: for ID3v2.4, parse_replaygain will not overwrite replaygain + values already parsed from RVA2 tags */ + parse_replaygain(tag, value, entry); + #endif - } + } } return tag - entry->id3v2buf + length; @@ -500,6 +513,14 @@ { "APIC", 4, 0, &parsealbumart, true }, { "PIC", 3, 0, &parsealbumart, true }, #endif + /* id3v2.4 versions */ + { "TSOA", 4, offsetof(struct mp3entry, albumsort), NULL, false }, + { "TSOP", 4, offsetof(struct mp3entry, artistsort), NULL, false }, + { "TSOT", 4, offsetof(struct mp3entry, titlesort), NULL, false }, + /* these may also appear */ + { "XSOA", 4, offsetof(struct mp3entry, albumsort), NULL, false }, + { "XSOP", 4, offsetof(struct mp3entry, artistsort), NULL, false }, + { "XSOT", 4, offsetof(struct mp3entry, titlesort), NULL, false }, { "TXXX", 4, 0, &parseuser, false }, #if CONFIG_CODEC == SWCODEC { "RVA2", 4, 0, &parserva2, true }, Index: apps/metadata/mp4.c =================================================================== --- apps/metadata/mp4.c (révision 31047) +++ apps/metadata/mp4.c (copie de travail) @@ -70,6 +70,10 @@ #define MP4_mp4a FOURCC('m', 'p', '4', 'a') #define MP4_mp42 FOURCC('m', 'p', '4', '2') #define MP4_qt FOURCC('q', 't', ' ', ' ') +#define MP4_soaa FOURCC('s', 'o', 'a', 'a') +#define MP4_soal FOURCC('s', 'o', 'a', 'l') +#define MP4_soar FOURCC('s', 'o', 'a', 'r') +#define MP4_sonm FOURCC('s', 'o', 'n', 'm') #define MP4_soun FOURCC('s', 'o', 'u', 'n') #define MP4_stbl FOURCC('s', 't', 'b', 'l') #define MP4_stsd FOURCC('s', 't', 's', 'd') @@ -493,6 +497,26 @@ break; #endif + case MP4_soaa: + read_mp4_tag_string(fd, size, &buffer, &buffer_left, + &id3->albumartistsort); + break; + + case MP4_soal: + read_mp4_tag_string(fd, size, &buffer, &buffer_left, + &id3->albumsort); + break; + + case MP4_soar: + read_mp4_tag_string(fd, size, &buffer, &buffer_left, + &id3->artistsort); + break; + + case MP4_sonm: + read_mp4_tag_string(fd, size, &buffer, &buffer_left, + &id3->titlesort); + break; + case MP4_extra: { char tag_name[TAG_NAME_LENGTH]; Index: apps/metadata.h =================================================================== --- apps/metadata.h (révision 31047) +++ apps/metadata.h (copie de travail) @@ -230,6 +230,10 @@ char* comment; char* albumartist; char* grouping; + char* titlesort; + char* albumsort; + char* artistsort; + char* albumartistsort; int discnum; int tracknum; int layer; Index: apps/tagcache.c =================================================================== --- apps/tagcache.c (révision 31047) +++ apps/tagcache.c (copie de travail) @@ -94,6 +94,21 @@ #define do_timed_yield() do { } while(0) #endif +/* + * Adding tags: + * - add the tags to the tag_type enum in tagcache.h + * - add the tags to: - sorted_tags[] + * - unique_tags[] + * - numeric_tags[] + * where appropriate (see comments) + * - add the representing string to *tags_str[] + * - add an "l" for each in *index_entry_ec (line 208) + * - do the obvious other stuff, e.g. updating the + * metadata information for the various formats and get + * through the add_tag stuff starting at line ~1750 + */ + + #ifndef __PCTOOL__ /* Tag Cache thread. */ static struct event_queue tagcache_queue SHAREDBSS_ATTR; @@ -121,16 +136,23 @@ (1LU << tag_genre) | (1LU << tag_composer) | (1LU << tag_comment) | \ (1LU << tag_albumartist) | (1LU << tag_grouping) | (1LU << tag_title)) +/* Used for internal sorting only */ +static const int sorting_tags[] = { + tag_titlesort, tag_albumsort, tag_artistsort, tag_albumartistsort }; + /* Uniqued tags (we can use these tags with filters and conditional clauses). */ #define TAGCACHE_UNIQUE_TAGS ((1LU << tag_artist) | (1LU << tag_album) | \ (1LU << tag_genre) | (1LU << tag_composer) | (1LU << tag_comment) | \ - (1LU << tag_albumartist) | (1LU << tag_grouping)) + (1LU << tag_albumartist) | (1LU << tag_grouping) | \ + (1LU << tag_artistsort) | (1LU << tag_albumsort) | (1LU << tag_albumartistsort) ) /* String presentation of the tags defined in tagcache.h. Must be in correct order! */ static const char *tags_str[] = { "artist", "album", "genre", "title", - "filename", "composer", "comment", "albumartist", "grouping", "year", - "discnumber", "tracknumber", "bitrate", "length", "playcount", "rating", - "playtime", "lastplayed", "commitid", "mtime", "lastoffset" }; + "filename", "composer", "comment", "albumartist", "grouping", + "titlesortorder", "albumsortorder", "performersortorder", + "albumartistsortorder", "year", "discnumber", "tracknumber", + "bitrate", "length", "playcount", "rating", "playtime", + "lastplayed", "commitid", "mtime" }; /* Status information of the tagcache. */ static struct tagcache_stat tc_stat; @@ -196,7 +218,7 @@ /** Note: This should be (1 + TAG_COUNT) amount of l's. */ -static const char *index_entry_ec = "llllllllllllllllllllll"; +static const char *index_entry_ec = "lllllllllllllllllllllllll"; static const char *tagcache_header_ec = "lll"; static const char *master_header_ec = "llllll"; @@ -245,6 +267,7 @@ struct tempbuf_searchidx { long idx_id; char *str; + char *sort_str; int seek; struct tempbuf_id_list idlist; }; @@ -287,6 +310,39 @@ return ecwrite(fd, buf, 1, index_entry_ec, tc_stat.econ); } +static bool tagcache_is_sorting_tag(int type) +{ + int i; + + for (i = 0; i < (int)ARRAYLEN(sorting_tags); i++) + { + if (type == sorting_tags[i]) + return true; + } + + return false; +} + +int tagcache_get_sort_type(int type) +{ + /* Some types of tags are actually sorted based on the information in another tag, + * such as title and titlesort. + **/ + switch (type) + { + case tag_title: + return tag_titlesort; + case tag_album: + return tag_albumsort; + case tag_artist: + return tag_artistsort; + case tag_albumartist: + return tag_albumartistsort; + default: + return type; + } +} + #ifdef HAVE_DIRCACHE /** * Returns true if specified flag is still present, i.e., dircache @@ -1739,6 +1795,12 @@ SET(id3->albumartist, tag_albumartist); SET(id3->grouping, tag_grouping); + /* Don't fill sorting tags, they're only used internally */ + id3->titlesort = + id3->albumsort = + id3->artistsort = + id3->albumartistsort = NULL; + id3->length = get_tag_numeric(entry, tag_length, idx_id); id3->playcount = get_tag_numeric(entry, tag_playcount, idx_id); id3->rating = get_tag_numeric(entry, tag_rating, idx_id); @@ -1794,11 +1856,21 @@ return length + 1; } -#define ADD_TAG(entry,tag,data) \ - /* Adding tag */ \ - entry.tag_offset[tag] = offset; \ - entry.tag_length[tag] = check_if_empty(data); \ - offset += entry.tag_length[tag] +static int add_tag(int offset, struct temp_file_entry *entry, int tag + ,char **data) +{ + entry->tag_offset[tag] = offset; + entry->tag_length[tag] = check_if_empty(data); + return entry->tag_length[tag]; +} + +#define HAVE_STR_TAG(TAG) \ + (TAG != NULL && strlen(TAG) > 0) + +#define ADD_TAG_FALLBACK(TAG,FALLBACK) \ + offset += add_tag(offset, &entry, tag_##TAG, have_##TAG \ + ? &id3.TAG : &id3.FALLBACK) + /* GCC 3.4.6 for Coldfire can choose to inline this function. Not a good * idea, as it uses lots of stack and is called from a recursive function * (check_dir). @@ -1818,8 +1890,8 @@ char tracknumfix[3]; int offset = 0; int path_length = strlen(path); - bool has_albumartist; - bool has_grouping; + bool have_albumartist, have_grouping, have_titlesort, + have_albumsort, have_artistsort, have_albumartistsort; #ifdef SIMULATOR /* Crude logging for the sim - to aid in debugging */ @@ -1917,34 +1989,39 @@ entry.tag_offset[tag_mtime] = mtime; /* String tags. */ - has_albumartist = id3.albumartist != NULL - && strlen(id3.albumartist) > 0; - has_grouping = id3.grouping != NULL - && strlen(id3.grouping) > 0; + offset += add_tag(offset, &entry, tag_filename, &path); + offset += add_tag(offset, &entry, tag_title, &id3.title); + offset += add_tag(offset, &entry, tag_artist, &id3.artist); + offset += add_tag(offset, &entry, tag_album, &id3.album); + offset += add_tag(offset, &entry, tag_genre, &id3.genre_string); + offset += add_tag(offset, &entry, tag_composer, &id3.composer); + offset += add_tag(offset, &entry, tag_comment, &id3.comment); - ADD_TAG(entry, tag_filename, &path); - ADD_TAG(entry, tag_title, &id3.title); - ADD_TAG(entry, tag_artist, &id3.artist); - ADD_TAG(entry, tag_album, &id3.album); - ADD_TAG(entry, tag_genre, &id3.genre_string); - ADD_TAG(entry, tag_composer, &id3.composer); - ADD_TAG(entry, tag_comment, &id3.comment); - if (has_albumartist) + have_albumartist = HAVE_STR_TAG(id3.albumartist); + have_grouping = HAVE_STR_TAG(id3.grouping); + have_titlesort = HAVE_STR_TAG(id3.titlesort); + have_albumsort = HAVE_STR_TAG(id3.albumsort); + have_artistsort = HAVE_STR_TAG(id3.artistsort); + have_albumartistsort = HAVE_STR_TAG(id3.albumartistsort); + + /* several fallbacks if tags are not filled */ + ADD_TAG_FALLBACK(albumartist, artist); + ADD_TAG_FALLBACK(grouping, title); + ADD_TAG_FALLBACK(titlesort, title); + ADD_TAG_FALLBACK(albumsort, album); + ADD_TAG_FALLBACK(artistsort, artist); + { - ADD_TAG(entry, tag_albumartist, &id3.albumartist); + char **albumartistsort_data = + have_albumartistsort ? &id3.albumartistsort + : have_artistsort ? &id3.artistsort + : have_albumartist ? &id3.albumartist + : &id3.artist; + + offset += add_tag(offset, &entry, tag_albumartistsort, + albumartistsort_data); } - else - { - ADD_TAG(entry, tag_albumartist, &id3.artist); - } - if (has_grouping) - { - ADD_TAG(entry, tag_grouping, &id3.grouping); - } - else - { - ADD_TAG(entry, tag_grouping, &id3.title); - } + entry.data_length = offset; /* Write the header */ @@ -1958,29 +2035,26 @@ write_item(id3.genre_string); write_item(id3.composer); write_item(id3.comment); - if (has_albumartist) - { - write_item(id3.albumartist); - } - else - { - write_item(id3.artist); - } - if (has_grouping) - { - write_item(id3.grouping); - } - else - { - write_item(id3.title); - } - total_entry_count++; + + write_item(have_albumartist? id3.albumartist : id3.artist); + write_item(have_grouping? id3.grouping : id3.title); + write_item(have_titlesort? id3.titlesort : id3.title); + write_item(have_albumsort? id3.albumsort : id3.album); + write_item(have_artistsort? id3.artistsort : id3.artist); + + write_item(have_albumartistsort ? id3.albumartistsort + : have_artistsort ? id3.artistsort + : have_albumartist ? id3.albumartist + : id3.artist); + + total_entry_count++; } -static bool tempbuf_insert(char *str, int id, int idx_id, bool unique) +static bool tempbuf_insert(char *str, char *sort_str, int id, int idx_id, bool unique) { struct tempbuf_searchidx *index = (struct tempbuf_searchidx *)tempbuf; int len = strlen(str)+1; + int sort_len; int i; unsigned crc32; unsigned *crcbuf = (unsigned *)&tempbuf[tempbuf_size-4]; @@ -2040,9 +2114,25 @@ index[tempbufidx].idlist.next = NULL; index[tempbufidx].idx_id = idx_id; index[tempbufidx].seek = -1; + + /* Display string */ index[tempbufidx].str = &tempbuf[tempbuf_pos]; memcpy(index[tempbufidx].str, str, len); tempbuf_pos += len; + + /* Sort string */ + if (sort_str) + { + sort_len = strlen(sort_str)+1; + index[tempbufidx].sort_str = &tempbuf[tempbuf_pos]; + memcpy(index[tempbufidx].sort_str, sort_str, sort_len); + tempbuf_pos += sort_len; + } + + else + { + index[tempbufidx].sort_str = index[tempbufidx].str; + } tempbufidx++; return true; @@ -2054,17 +2144,17 @@ struct tempbuf_searchidx *e1 = (struct tempbuf_searchidx *)p1; struct tempbuf_searchidx *e2 = (struct tempbuf_searchidx *)p2; - - if (strcmp(e1->str, UNTAGGED) == 0) + + if (strcmp(e1->sort_str, UNTAGGED) == 0) { - if (strcmp(e2->str, UNTAGGED) == 0) + if (strcmp(e2->sort_str, UNTAGGED) == 0) return 0; return -1; } - else if (strcmp(e2->str, UNTAGGED) == 0) + else if (strcmp(e2->sort_str, UNTAGGED) == 0) return 1; - return strncasecmp(e1->str, e2->str, TAG_MAXLEN); + return strncasecmp(e1->sort_str, e2->sort_str, TAG_MAXLEN); } static int tempbuf_sort(int fd) @@ -2437,13 +2527,18 @@ struct index_entry idxbuf[IDX_BUF_DEPTH]; int idxbuf_pos; char buf[TAG_MAXLEN+32]; - int fd = -1, masterfd; + char sort_buf[TAG_MAXLEN+32]; + int fd = -1, masterfd, sort_fd = -1; bool error = false; int init; int masterfd_pos; + int sort_type; logf("Building index: %d", index_type); + /* Get which tags should be used for sorting */ + sort_type = tagcache_get_sort_type(index_type); + /* Check the number of entries we need to allocate ram for. */ commit_entry_count = h->entry_count + 1; @@ -2523,19 +2618,38 @@ * it entirely into memory so we can resort it later for use with * chunked browsing. */ - if (TAGCACHE_IS_SORTED(index_type)) + if (TAGCACHE_IS_SORTED(index_type) || + tagcache_is_sorting_tag(index_type)) { + /* If the tag type requires external sorting information, open + * that file as well. + **/ + if (sort_type != index_type) + { + struct tagcache_header sort_tch; + logf("loading external sort tags..."); + sort_fd = open_tag_fd(&sort_tch, sort_type, true); + if (sort_fd < 0) + { + logf("no sort tags to load"); + close(fd); + return -2; + } + } + logf("loading tags..."); for (i = 0; i < tch.entry_count; i++) { struct tagfile_entry entry; int loc = lseek(fd, 0, SEEK_CUR); bool ret; + char *sort_str = sort_type != index_type? sort_buf : NULL; if (ecread_tagfile_entry(fd, &entry) != sizeof(struct tagfile_entry)) { logf("read error #7"); close(fd); + if (sort_fd >= 0) { close(sort_fd); } return -2; } @@ -2543,6 +2657,7 @@ { logf("too long tag #3"); close(fd); + if (sort_fd >= 0) { close(sort_fd); } return -2; } @@ -2550,6 +2665,7 @@ { logf("read error #8"); close(fd); + if (sort_fd >= 0) { close(sort_fd); } return -2; } @@ -2557,21 +2673,52 @@ if (buf[0] == '\0') continue; + if (sort_type != index_type) + { + struct tagfile_entry sort_entry; + if (ecread(sort_fd, &sort_entry, 1, tagfile_entry_ec, tc_stat.econ) + != sizeof(struct tagfile_entry)) + { + logf("error reading sort entry info"); + close(fd); + close(sort_fd); + return -2; + } + + if (sort_entry.tag_length >= (int)sizeof(sort_buf)) + { + logf("sort tag too long"); + close(fd); + close(sort_fd); + return -2; + } + + if (read(sort_fd, sort_buf, sort_entry.tag_length) != sort_entry.tag_length) + { + logf("error reading sort tag"); + close(fd); + close(sort_fd); + return -2; + } + } + /** * Save the tag and tag id in the memory buffer. Tag id * is saved so we can later reindex the master lookup * table when the index gets resorted. */ - ret = tempbuf_insert(buf, loc/TAGFILE_ENTRY_CHUNK_LENGTH - + commit_entry_count, entry.idx_id, + ret = tempbuf_insert(buf, sort_str, loc/TAGFILE_ENTRY_CHUNK_LENGTH + + commit_entry_count, entry.idx_id, TAGCACHE_IS_UNIQUE(index_type)); if (!ret) { close(fd); + if (sort_fd >= 0) { close(sort_fd); } return -3; } do_timed_yield(); } + close(sort_fd); logf("done"); } else @@ -2667,7 +2814,8 @@ * Load new unique tags in memory to be sorted later and added * to the master lookup file. */ - if (TAGCACHE_IS_SORTED(index_type)) + if (TAGCACHE_IS_SORTED(index_type)|| + tagcache_is_sorting_tag(index_type)) { lseek(tmpfd, sizeof(struct tagcache_header), SEEK_SET); /* h is the header of the temporary file containing new tags. */ @@ -2675,7 +2823,8 @@ for (i = 0; i < h->entry_count; i++) { struct temp_file_entry entry; - + char *sort_str = NULL; + if (read(tmpfd, &entry, sizeof(struct temp_file_entry)) != sizeof(struct temp_file_entry)) { @@ -2701,10 +2850,40 @@ goto error_exit; } - if (TAGCACHE_IS_UNIQUE(index_type)) - error = !tempbuf_insert(buf, i, -1, true); + + if (sort_type != index_type) + { + off_t orig_offset; + + /* Read how the string should be treated for sorting. */ + if (entry.tag_length[sort_type] >= (long)sizeof(sort_buf)) + { + logf("too long entry!"); + error = true; + goto error_exit; + } + + /* Look ahead for the sort string */ + orig_offset = lseek(tmpfd, 0, SEEK_CUR); + lseek(tmpfd, entry.tag_offset[sort_type] + - entry.tag_offset[index_type] + - entry.tag_length[index_type], SEEK_CUR); + + if (read(tmpfd, sort_buf, entry.tag_length[sort_type]) != + entry.tag_length[sort_type]) + { + logf("read fail #4-1"); + error = true; + goto error_exit; + } + sort_str = sort_buf; + lseek(tmpfd, orig_offset, SEEK_SET); + } + + if (TAGCACHE_IS_UNIQUE(index_type)) + error = !tempbuf_insert(buf, sort_str, i, -1, true); else - error = !tempbuf_insert(buf, i, tcmh.tch.entry_count + i, false); + error = !tempbuf_insert(buf, sort_str, i, tcmh.tch.entry_count + i, false); if (error) { @@ -2825,7 +3004,8 @@ /* Read entry headers. */ for (j = 0; j < idxbuf_pos; j++) { - if (!TAGCACHE_IS_SORTED(index_type)) + if (!(TAGCACHE_IS_SORTED(index_type) || + tagcache_is_sorting_tag(index_type))) { struct temp_file_entry entry; struct tagfile_entry fe; @@ -2905,7 +3085,7 @@ lseek(fd, 0, SEEK_SET); ecwrite(fd, &tch, 1, tagcache_header_ec, tc_stat.econ); - if (index_type != tag_filename) + if (!(index_type == tag_filename || tagcache_is_sorting_tag(index_type))) h->datasize += tch.datasize; logf("s:%d/%ld/%ld", index_type, tch.datasize, h->datasize); error_exit: @@ -3718,7 +3898,7 @@ * resurrected. */ #ifdef HAVE_TC_RAMCACHE - if (tc_stat.ramcache && tag != tag_filename) + if (tc_stat.ramcache && tag != tag_filename && !tagcache_is_sorting_tag(tag) ) { struct tagfile_entry *tfe; int32_t *seek = &ramcache_hdr->indices[idx_id].tag_seek[tag]; @@ -3768,7 +3948,7 @@ #ifdef HAVE_TC_RAMCACHE /* Delete from ram. */ - if (tc_stat.ramcache && tag != tag_filename) + if (tc_stat.ramcache && tag != tag_filename && !tagcache_is_sorting_tag(tag)) { struct tagfile_entry *tagentry = (struct tagfile_entry *)&ramcache_hdr->tags[tag][oldseek]; @@ -4056,7 +4236,7 @@ struct tagfile_entry *fe; char buf[TAG_MAXLEN+32]; - if (TAGCACHE_IS_NUMERIC(tag)) + if (TAGCACHE_IS_NUMERIC(tag) || tagcache_is_sorting_tag(tag)) continue ; //p = ((void *)p+1); @@ -4853,6 +5033,6 @@ } int tagcache_get_max_commit_step(void) { - return (int)(SORTED_TAGS_COUNT)+1; + return SORTED_TAGS_COUNT + ARRAYLEN(sorting_tags) + 1; } Index: apps/tagcache.h =================================================================== --- apps/tagcache.h (révision 31047) +++ apps/tagcache.h (copie de travail) @@ -30,10 +30,13 @@ tagcache.c and bump up the header version too. */ enum tag_type { tag_artist = 0, tag_album, tag_genre, tag_title, - tag_filename, tag_composer, tag_comment, tag_albumartist, tag_grouping, tag_year, - tag_discnumber, tag_tracknumber, tag_bitrate, tag_length, tag_playcount, tag_rating, - tag_playtime, tag_lastplayed, tag_commitid, tag_mtime, tag_lastoffset, - /* Real tags end here, count them. */ + tag_filename, tag_composer, tag_comment, + tag_albumartist, tag_grouping, + tag_artistsort, tag_albumsort, tag_titlesort, tag_albumartistsort, + tag_year, tag_discnumber, tag_tracknumber, tag_bitrate, tag_length, + tag_playcount, tag_rating, tag_playtime, tag_lastplayed, tag_commitid, + tag_mtime, tag_lastoffset, + /* Real tags end here, count them. */ TAG_COUNT, /* Virtual tags */ tag_virt_basename, tag_virt_length_min, tag_virt_length_sec, Index: apps/tagtree.c =================================================================== --- apps/tagtree.c (révision 31047) +++ apps/tagtree.c (copie de travail) @@ -314,6 +314,10 @@ {"albumartist", tag_albumartist}, {"ensemble", tag_albumartist}, {"grouping", tag_grouping}, + {"titlesort", tag_titlesort}, + {"albumsort", tag_albumsort}, + {"artistsort", tag_artistsort}, + {"albumartistsort", tag_albumartistsort}, {"genre", tag_genre}, {"length", tag_length}, {"Lm", tag_virt_length_min},