=== modified file 'apps/metadata.h' --- apps/metadata.h 2008-10-15 05:38:51 +0000 +++ apps/metadata.h 2009-04-19 19:00:58 +0000 @@ -170,6 +170,10 @@ char* comment; char* albumartist; char* grouping; + char* titlesort; + char* albumsort; + char* artistsort; + char* albumartistsort; int discnum; int tracknum; int version; === modified file 'apps/metadata/metadata_common.c' --- apps/metadata/metadata_common.c 2009-02-08 11:09:55 +0000 +++ apps/metadata/metadata_common.c 2009-04-19 19:00:58 +0000 @@ -285,6 +285,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 ) { === modified file 'apps/metadata/mp3.c' --- apps/metadata/mp3.c 2009-04-16 22:10:45 +0000 +++ apps/metadata/mp3.c 2009-04-20 02:37:50 +0000 @@ -307,19 +307,31 @@ */ value = tag + desc_len + 1; value_len = bufferpos - (tag - entry->id3v2buf); - - 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"))) + { strncpy(tag, value, value_len); tag[value_len - 1] = 0; entry->albumartist = tag; + } #if CONFIG_CODEC == SWCODEC - } else { + else + { + /* Note: for ID3v2.4, parse_replaygain will not overwrite replaygain + values already parsed from RVA2 tags */ value_len = parse_replaygain(tag, value, entry, tag, value_len); + } #endif - } } - return tag - entry->id3v2buf + value_len; } @@ -439,6 +451,14 @@ { "COMM", 4, offsetof(struct mp3entry, comment), NULL, false }, { "TCON", 4, offsetof(struct mp3entry, genre_string), &parsegenre, false }, { "TCO", 3, offsetof(struct mp3entry, genre_string), &parsegenre, false }, + /* 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 }, === modified file 'apps/metadata/mp4.c' --- apps/metadata/mp4.c 2009-01-04 17:41:17 +0000 +++ apps/metadata/mp4.c 2009-04-19 19:00:58 +0000 @@ -63,6 +63,10 @@ #define MP4_mp4a MP4_ID('m', 'p', '4', 'a') #define MP4_mp42 MP4_ID('m', 'p', '4', '2') #define MP4_qt MP4_ID('q', 't', ' ', ' ') +#define MP4_soaa MP4_ID('s', 'o', 'a', 'a') +#define MP4_soal MP4_ID('s', 'o', 'a', 'l') +#define MP4_soar MP4_ID('s', 'o', 'a', 'r') +#define MP4_sonm MP4_ID('s', 'o', 'n', 'm') #define MP4_soun MP4_ID('s', 'o', 'u', 'n') #define MP4_stbl MP4_ID('s', 't', 'b', 'l') #define MP4_stsd MP4_ID('s', 't', 's', 'd') @@ -453,6 +457,26 @@ } break; + 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]; === modified file 'apps/tagcache.c' --- apps/tagcache.c 2009-04-15 05:46:25 +0000 +++ apps/tagcache.c 2009-04-20 02:41:27 +0000 @@ -90,6 +90,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; @@ -115,7 +130,9 @@ /* Uniqued tags (we can use these tags with filters and conditional clauses). */ static const int unique_tags[] = { tag_artist, tag_album, tag_genre, - tag_composer, tag_comment, tag_albumartist, tag_grouping }; + tag_composer, tag_comment, tag_albumartist, tag_grouping, + tag_albumsort, tag_artistsort, tag_albumartistsort +}; /* Numeric tags (we can use these tags with conditional clauses). */ static const int numeric_tags[] = { tag_year, tag_discnumber, @@ -127,9 +144,11 @@ /* 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" }; + "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; @@ -195,7 +214,7 @@ /** Note: This should be (1 + TAG_COUNT) amount of l's. */ -static const char *index_entry_ec = "lllllllllllllllllllll"; +static const char *index_entry_ec = "lllllllllllllllllllllllll"; static const char *tagcache_header_ec = "lll"; static const char *master_header_ec = "llllll"; @@ -241,6 +260,7 @@ struct tempbuf_searchidx { long idx_id; char *str; + char *sort_str; int seek; struct tempbuf_id_list idlist; }; @@ -306,6 +326,26 @@ 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 @@ -1603,6 +1643,11 @@ id3->albumartist = get_tag_string(entry, tag_albumartist); id3->grouping = get_tag_string(entry, tag_grouping); + id3->titlesort = get_tag_string(entry, tag_titlesort); + id3->albumsort = get_tag_string(entry, tag_albumsort); + id3->artistsort = get_tag_string(entry, tag_artistsort); + id3->albumartistsort = get_tag_string(entry, tag_albumartistsort); + id3->playcount = get_tag_numeric(entry, tag_playcount); id3->rating = get_tag_numeric(entry, tag_rating); id3->lastplayed = get_tag_numeric(entry, tag_lastplayed); @@ -1648,12 +1693,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) + static void add_tagcache(char *path, unsigned long mtime #if defined(HAVE_TC_RAMCACHE) && defined(HAVE_DIRCACHE) ,const struct dirent *dc @@ -1668,9 +1722,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; if (cachefd < 0) return ; @@ -1787,38 +1840,43 @@ entry.tag_offset[tag_length] = id3.length; entry.tag_offset[tag_bitrate] = id3.bitrate; 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; - - 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) - { - ADD_TAG(entry, tag_albumartist, &id3.albumartist); - } - 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); - } + 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); + + 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); + + { + 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); + } + entry.data_length = offset; - + /* Write the header */ write(cachefd, &entry, sizeof(struct temp_file_entry)); @@ -1830,34 +1888,31 @@ 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]; char buf[TAG_MAXLEN+32]; - + for (i = 0; str[i] != '\0' && i < (int)sizeof(buf)-1; i++) buf[i] = tolower(str[i]); buf[i] = '\0'; @@ -1912,9 +1967,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; @@ -1926,17 +1997,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) @@ -2311,6 +2382,7 @@ struct index_entry idxbuf[IDX_BUF_DEPTH]; int idxbuf_pos; char buf[TAG_MAXLEN+32]; + char sort_buf[TAG_MAXLEN+32]; int fd = -1, masterfd; bool error = false; int init; @@ -2437,7 +2509,7 @@ * 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 + ret = tempbuf_insert(buf, NULL, loc/TAGFILE_ENTRY_CHUNK_LENGTH + commit_entry_count, entry.idx_id, tagcache_is_unique_tag(index_type)); if (!ret) @@ -2550,7 +2622,9 @@ for (i = 0; i < h->entry_count; i++) { struct temp_file_entry entry; - + int sort_type = tagcache_get_sort_type(index_type); + char *sort_str = NULL; + if (read(tmpfd, &entry, sizeof(struct temp_file_entry)) != sizeof(struct temp_file_entry)) { @@ -2575,11 +2649,40 @@ error = true; goto error_exit; } - + + 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_tag(index_type)) - error = !tempbuf_insert(buf, i, -1, true); + 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) { === modified file 'apps/tagcache.h' --- apps/tagcache.h 2009-03-06 15:21:58 +0000 +++ apps/tagcache.h 2009-04-19 19:00:58 +0000 @@ -29,9 +29,11 @@ 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_filename, tag_composer, tag_comment, tag_albumartist, tag_grouping, + tag_titlesort, tag_albumsort, tag_artistsort, tag_albumartistsort, + tag_year, tag_discnumber, tag_tracknumber, tag_bitrate, tag_length, + tag_playcount, tag_rating, tag_playtime, tag_lastplayed, tag_commitid, + tag_mtime, /* Real tags end here, count them. */ TAG_COUNT, /* Virtual tags */ @@ -49,7 +51,7 @@ #define IDX_BUF_DEPTH 64 /* Tag Cache Header version 'TCHxx'. Increment when changing internal structures. */ -#define TAGCACHE_MAGIC 0x5443480d +#define TAGCACHE_MAGIC 0x5443480e /* How much to allocate extra space for ramcache. */ #define TAGCACHE_RESERVE 32768 === modified file 'apps/tagtree.c' --- apps/tagtree.c 2009-03-08 19:28:15 +0000 +++ apps/tagtree.c 2009-04-20 01:39:51 +0000 @@ -218,6 +218,10 @@ MATCH(tag, buf, "albumartist", tag_albumartist); MATCH(tag, buf, "ensemble", tag_albumartist); MATCH(tag, buf, "grouping", tag_grouping); + MATCH(tag, buf, "titlesort", tag_titlesort); + MATCH(tag, buf, "albumsort", tag_albumsort); + MATCH(tag, buf, "artistsort", tag_artistsort); + MATCH(tag, buf, "albumartistsort", tag_albumartistsort); MATCH(tag, buf, "genre", tag_genre); MATCH(tag, buf, "length", tag_length); MATCH(tag, buf, "Lm", tag_virt_length_min);