From 02fd314a0b426d6d445e2c9b167681ade6b0c1d2 Mon Sep 17 00:00:00 2001 From: Nick Peskett Date: Fri, 16 Dec 2011 10:09:41 +0000 Subject: [PATCH] FS #12419 : Support for embedded cuesheets. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@31321 a1c6a512-1295-4272-9138-f99709370657 --- apps/cuesheet.c | 127 ++++++++++++++---- apps/cuesheet.h | 15 ++- apps/metadata.c | 4 + apps/metadata.h | 16 +++ apps/metadata/id3tags.c | 34 +++++ apps/metadata/vorbis.c | 20 ++- apps/mpeg.c | 6 +- apps/playback.c | 6 +- manual/configure_rockbox/playback_options.tex | 8 +- 9 files changed, 192 insertions(+), 44 deletions(-) diff --git a/apps/cuesheet.c b/apps/cuesheet.c index 935af60898..ab4063a66a 100644 --- a/apps/cuesheet.c +++ b/apps/cuesheet.c @@ -42,21 +42,29 @@ #define CUE_DIR ROCKBOX_DIR "/cue" -bool look_for_cuesheet_file(const char *trackpath, char *found_cue_path) +bool look_for_cuesheet_file(struct mp3entry *track_id3, struct cuesheet_file *cue_file) { /* DEBUGF("look for cue file\n"); */ char cuepath[MAX_PATH]; char *dot, *slash; - slash = strrchr(trackpath, '/'); - if (!slash) + if (track_id3->embed_cuesheet.present) { - found_cue_path = NULL; - return false; + cue_file->pos = track_id3->embed_cuesheet.pos; + cue_file->size = track_id3->embed_cuesheet.size; + cue_file->encoding = track_id3->embed_cuesheet.encoding; + strlcpy(cue_file->path, track_id3->path, MAX_PATH); + return true; } - strlcpy(cuepath, trackpath, MAX_PATH); + cue_file->pos = 0; + cue_file->size = 0; + cue_file->path[0] = '\0'; + slash = strrchr(track_id3->path, '/'); + if (!slash) + return false; + strlcpy(cuepath, track_id3->path, MAX_PATH); dot = strrchr(cuepath, '.'); strcpy(dot, ".cue"); @@ -67,15 +75,10 @@ bool look_for_cuesheet_file(const char *trackpath, char *found_cue_path) char *dot = strrchr(cuepath, '.'); strcpy(dot, ".cue"); if (!file_exists(cuepath)) - { - if (found_cue_path) - found_cue_path = NULL; return false; - } } - if (found_cue_path) - strlcpy(found_cue_path, cuepath, MAX_PATH); + strlcpy(cue_file->path, cuepath, MAX_PATH); return true; } @@ -99,29 +102,81 @@ static char *get_string(const char *line) return start; } -/* parse cuesheet "file" and store the information in "cue" */ -bool parse_cuesheet(char *file, struct cuesheet *cue) +/* parse cuesheet "cue_file" and store the information in "cue" */ +bool parse_cuesheet(struct cuesheet_file *cue_file, struct cuesheet *cue) { char line[MAX_PATH]; char *s; - bool utf8 = false; + unsigned char char_enc = CHAR_ENC_ISO_8859_1; + bool is_embedded = false; + int line_len; + int bytes_left = 0; + int read_bytes = MAX_PATH; + unsigned char utf16_buf[MAX_PATH]; - int fd = open_utf8(file,O_RDONLY); - if (fd < 0) - { - /* couln't open the file */ + int fd = open(cue_file->path, O_RDONLY, 0644); + if(fd < 0) return false; + if (cue_file->pos > 0) + { + is_embedded = true; + lseek(fd, cue_file->pos, SEEK_SET); + bytes_left = cue_file->size; + char_enc = cue_file->encoding; + } + + /* Look for a Unicode BOM */ + unsigned char bom_read = 0; + read(fd, line, 3); + if(!memcmp(line, "\xef\xbb\xbf", 3)) + { + char_enc = CHAR_ENC_UTF_8; + bom_read = 3; + } + else if(!memcmp(line, "\xff\xfe", 2)) + { + char_enc = CHAR_ENC_UTF_16_LE; + bom_read = 2; + } + else if(!memcmp(line, "\xfe\xff", 2)) + { + char_enc = CHAR_ENC_UTF_16_BE; + bom_read = 2; + } + if (bom_read < 3 ) + lseek(fd, cue_file->pos + bom_read, SEEK_SET); + if (is_embedded) + { + if (bom_read > 0) + bytes_left -= bom_read; + if (read_bytes > bytes_left) + read_bytes = bytes_left; } - if(lseek(fd, 0, SEEK_CUR) > 0) - utf8 = true; /* Initialization */ memset(cue, 0, sizeof(struct cuesheet)); - strcpy(cue->path, file); + strcpy(cue->path, cue_file->path); cue->curr_track = cue->tracks; - while ( read_line(fd,line,MAX_PATH) && cue->track_count < MAX_TRACKS ) + while ((line_len = read_line(fd, line, read_bytes)) > 0 + && cue->track_count < MAX_TRACKS ) { + if (char_enc == CHAR_ENC_UTF_16_LE) + { + s = utf16LEdecode(line, utf16_buf, line_len); + /* terminate the string at the newline */ + *s = '\0'; + strcpy(line, utf16_buf); + /* chomp the trailing 0 after the newline */ + lseek(fd, 1, SEEK_CUR); + line_len++; + } + else if (char_enc == CHAR_ENC_UTF_16_BE) + { + s = utf16BEdecode(line, utf16_buf, line_len); + *s = '\0'; + strcpy(line, utf16_buf); + } s = skip_whitespace(line); if (!strncmp(s, "TRACK", 5)) @@ -169,9 +224,10 @@ bool parse_cuesheet(char *file, struct cuesheet *cue) if (dest) { - if (!utf8) + if (char_enc == CHAR_ENC_ISO_8859_1) { - dest = iso_decode(string, dest, -1, MIN(strlen(string), MAX_NAME)); + dest = iso_decode(string, dest, -1, + MIN(strlen(string), MAX_NAME)); *dest = '\0'; } else @@ -180,6 +236,14 @@ bool parse_cuesheet(char *file, struct cuesheet *cue) } } } + if (is_embedded) + { + bytes_left -= line_len; + if (bytes_left <= 0) + break; + if (bytes_left < read_bytes) + read_bytes = bytes_left; + } } close(fd); @@ -256,7 +320,7 @@ void browse_cuesheet(struct cuesheet *cue) bool done = false; int sel; char title[MAX_PATH]; - char cuepath[MAX_PATH]; + struct cuesheet_file cue_file; struct mp3entry *id3 = audio_current_track(); snprintf(title, MAX_PATH, "%s: %s", cue->performer, cue->title); @@ -283,8 +347,8 @@ void browse_cuesheet(struct cuesheet *cue) id3 = audio_current_track(); if (id3 && *id3->path && strcmp(id3->path, "No file!")) { - look_for_cuesheet_file(id3->path, cuepath); - if (id3->cuesheet && !strcmp(cue->path, cuepath)) + look_for_cuesheet_file(id3, &cue_file); + if (id3->cuesheet && !strcmp(cue->path, cue_file.path)) { sel = gui_synclist_get_sel_pos(&lists); seek(cue->tracks[sel/2].offset); @@ -300,11 +364,16 @@ void browse_cuesheet(struct cuesheet *cue) bool display_cuesheet_content(char* filename) { size_t bufsize = 0; + struct cuesheet_file cue_file; struct cuesheet *cue = (struct cuesheet *)plugin_get_buffer(&bufsize); if (!cue || bufsize < sizeof(struct cuesheet)) return false; - if (!parse_cuesheet(filename, cue)) + strlcpy(cue_file.path, filename, MAX_PATH); + cue_file.pos = 0; + cue_file.size = 0; + + if (!parse_cuesheet(&cue_file, cue)) return false; browse_cuesheet(cue); diff --git a/apps/cuesheet.h b/apps/cuesheet.h index e8d77ca3a9..31841dacf6 100644 --- a/apps/cuesheet.h +++ b/apps/cuesheet.h @@ -51,11 +51,18 @@ struct cuesheet { struct cue_track_info *curr_track; }; -/* looks if there is a cuesheet file that has a name matching "trackpath" */ -bool look_for_cuesheet_file(const char *trackpath, char *found_cue_path); +struct cuesheet_file { + char path[MAX_PATH]; + int size; + off_t pos; + enum character_encoding encoding; +}; -/* parse cuesheet "file" and store the information in "cue" */ -bool parse_cuesheet(char *file, struct cuesheet *cue); +/* looks if there is a cuesheet file with a name matching path of "track_id3" */ +bool look_for_cuesheet_file(struct mp3entry *track_id3, struct cuesheet_file *cue_file); + +/* parse cuesheet_file "cue_file" and store the information in "cue" */ +bool parse_cuesheet(struct cuesheet_file *cue_file, struct cuesheet *cue); /* reads a cuesheet to find the audio track associated to it */ bool get_trackname_from_cuesheet(char *filename, char *buf); diff --git a/apps/metadata.c b/apps/metadata.c index 7479de105f..898436781b 100644 --- a/apps/metadata.c +++ b/apps/metadata.c @@ -438,6 +438,10 @@ bool get_metadata(struct mp3entry* id3, int fd, const char* trackname) /* Take our best guess at the codec type based on file extension */ id3->codectype = probe_file_format(trackname); + /* default values for embedded cuesheets */ + id3->embed_cuesheet.present = false; + id3->embed_cuesheet.pos = 0; + entry = &audio_formats[id3->codectype]; /* Load codec specific track tag information and confirm the codec type. */ diff --git a/apps/metadata.h b/apps/metadata.h index 3676bd8e24..0c6768d3d9 100644 --- a/apps/metadata.h +++ b/apps/metadata.h @@ -217,6 +217,21 @@ struct mp3_albumart { }; #endif +enum character_encoding { + CHAR_ENC_ISO_8859_1 = 1, + CHAR_ENC_UTF_8, + CHAR_ENC_UTF_16_LE, + CHAR_ENC_UTF_16_BE, +}; + +/* cache embedded cuesheet details */ +struct embed_cuesheet { + bool present; + int size; + off_t pos; + enum character_encoding encoding; +}; + struct mp3entry { char path[MAX_PATH]; char* title; @@ -307,6 +322,7 @@ struct mp3entry { #endif /* Cuesheet support */ + struct embed_cuesheet embed_cuesheet; struct cuesheet *cuesheet; /* Musicbrainz Track ID */ diff --git a/apps/metadata/id3tags.c b/apps/metadata/id3tags.c index dcf71f71bf..e9b59e012a 100644 --- a/apps/metadata/id3tags.c +++ b/apps/metadata/id3tags.c @@ -995,6 +995,40 @@ void setid3v2title(int fd, struct mp3entry *entry) if(bytesread >= buffersize - bufferpos) bytesread = buffersize - bufferpos - 1; + if ( /* Is it an embedded cuesheet? */ + (tr->tag_length == 4 && !memcmp(header, "TXXX", 4)) && + (bytesread >= 14 && !strncmp(utf8buf, "CUESHEET", 8)) + ) { + unsigned char char_enc = 0; + /* 0CUESHEET0 = 10 bytes */ + unsigned char cuesheet_offset = 10; + switch (tag[0]) { + case 0x00: + char_enc = CHAR_ENC_ISO_8859_1; + break; + case 0x01: + char_enc = CHAR_ENC_UTF_16_LE; + cuesheet_offset += cuesheet_offset+1; + break; + case 0x02: + char_enc = CHAR_ENC_UTF_16_BE; + cuesheet_offset += cuesheet_offset+1; + break; + case 0x03: + char_enc = CHAR_ENC_UTF_8; + break; + } + if (char_enc > 0) { + entry->embed_cuesheet.present = true; + entry->embed_cuesheet.pos = lseek(fd, 0, SEEK_CUR) + - framelen + cuesheet_offset; + entry->embed_cuesheet.size = totframelen + - cuesheet_offset; + entry->embed_cuesheet.encoding = char_enc; + } + break; + } + for (j = 0; j < bytesread; j++) tag[j] = utf8buf[j]; diff --git a/apps/metadata/vorbis.c b/apps/metadata/vorbis.c index f6d3af1cef..29848daa19 100644 --- a/apps/metadata/vorbis.c +++ b/apps/metadata/vorbis.c @@ -341,15 +341,29 @@ long read_vorbis_tags(int fd, struct mp3entry *id3, } len -= read_len; + read_len = file_read_string(&file, id3->path, sizeof(id3->path), -1, len); - if (file_read_string(&file, id3->path, sizeof(id3->path), -1, len) < 0) + if (read_len < 0) { return 0; } logf("Vorbis comment %d: %s=%s", i, name, id3->path); - len = parse_tag(name, id3->path, id3, buf, buf_remaining, - TAGTYPE_VORBIS); + + /* Is it an embedded cuesheet? */ + if (!strcasecmp(name, "CUESHEET")) + { + id3->embed_cuesheet.present = true; + id3->embed_cuesheet.pos = lseek(file.fd, 0, SEEK_CUR) - read_len; + id3->embed_cuesheet.size = len; + id3->embed_cuesheet.encoding = CHAR_ENC_UTF_8; + } + else + { + len = parse_tag(name, id3->path, id3, buf, buf_remaining, + TAGTYPE_VORBIS); + } + buf += len; buf_remaining -= len; } diff --git a/apps/mpeg.c b/apps/mpeg.c index 698695b72d..4fbc40ff1e 100644 --- a/apps/mpeg.c +++ b/apps/mpeg.c @@ -2170,10 +2170,10 @@ struct mp3entry* audio_current_track(void) if (!checked_for_cuesheet && curr_cuesheet && id3->cuesheet == NULL) { checked_for_cuesheet = true; /* only check once per track */ - char cuepath[MAX_PATH]; + struct cuesheet_file cue_file; - if (look_for_cuesheet_file(id3->path, cuepath) && - parse_cuesheet(cuepath, curr_cuesheet)) + if (look_for_cuesheet_file(id3, &cue_file)) && + parse_cuesheet(&cue_file, curr_cuesheet)) { id3->cuesheet = curr_cuesheet; } diff --git a/apps/playback.c b/apps/playback.c index 2739118aeb..36fbd88832 100644 --- a/apps/playback.c +++ b/apps/playback.c @@ -1485,12 +1485,12 @@ static bool audio_load_cuesheet(struct track_info *info, /* If error other than a full buffer, then mark it "unsupported" to avoid reloading attempt */ int hid = ERR_UNSUPPORTED_TYPE; - char cuepath[MAX_PATH]; + struct cuesheet_file cue_file; #ifdef HAVE_IO_PRIORITY buf_back_off_storage(true); #endif - if (look_for_cuesheet_file(track_id3->path, cuepath)) + if (look_for_cuesheet_file(track_id3, &cue_file)) { hid = bufalloc(NULL, sizeof (struct cuesheet), TYPE_CUESHEET); @@ -1499,7 +1499,7 @@ static bool audio_load_cuesheet(struct track_info *info, void *cuesheet = NULL; bufgetdata(hid, sizeof (struct cuesheet), &cuesheet); - if (parse_cuesheet(cuepath, (struct cuesheet *)cuesheet)) + if (parse_cuesheet(&cue_file, (struct cuesheet *)cuesheet)) { /* Indicate cuesheet is present (while track remains buffered) */ diff --git a/manual/configure_rockbox/playback_options.tex b/manual/configure_rockbox/playback_options.tex index ec2d99b8da..b3a5c82ee6 100644 --- a/manual/configure_rockbox/playback_options.tex +++ b/manual/configure_rockbox/playback_options.tex @@ -269,10 +269,14 @@ you to configure settings related to audio playback. effect. Cuesheet files should have the same file name as the audio file they - reference, except with the extension \fname{.cue}. This file can either reside in - the same directory as the audio file (checked first), or within the + reference, except with the extension \fname{.cue}. This file can either + reside in the same directory as the audio file (checked first), or within the \fname{.rockbox/cue} directory. + The contents of a cuesheet file can also be embedded within the metadata of + an audio file. There is currently support for the FLAC tag/ Vorbis comment + \emph{CUESHEET} or the ID3v2 \emph{TXXX CUESHEET} tag. + \section{Skip Length}\index{Skip Length} Designed to speed up navigation when listening to long audio tracks, \setting{Skip Length} changes the behaviour of