FS #12419 : Support for embedded cuesheets.

git-svn-id: svn://svn.rockbox.org/rockbox/trunk@31321 a1c6a512-1295-4272-9138-f99709370657
diff --git a/apps/cuesheet.c b/apps/cuesheet.c
index 935af60..ab4063a 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 @@
         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 @@
     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;
     }
-    if(lseek(fd, 0, SEEK_CUR) > 0)
-        utf8 = true;
+
+    /* 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;
+    }
 
     /* 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 @@
 
             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 @@
                 }
             }    
         }
+        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 @@
     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 @@
                 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 @@
 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 e8d77ca..31841da 100644
--- a/apps/cuesheet.h
+++ b/apps/cuesheet.h
@@ -51,11 +51,18 @@
     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 7479de1..8984367 100644
--- a/apps/metadata.c
+++ b/apps/metadata.c
@@ -438,6 +438,10 @@
     /* 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 3676bd8..0c6768d 100644
--- a/apps/metadata.h
+++ b/apps/metadata.h
@@ -217,6 +217,21 @@
 };
 #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 @@
 #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 dcf71f7..e9b59e0 100644
--- a/apps/metadata/id3tags.c
+++ b/apps/metadata/id3tags.c
@@ -995,6 +995,40 @@
                     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 f6d3af1..29848da 100644
--- a/apps/metadata/vorbis.c
+++ b/apps/metadata/vorbis.c
@@ -341,15 +341,29 @@
         }
 
         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 698695b..4fbc40f 100644
--- a/apps/mpeg.c
+++ b/apps/mpeg.c
@@ -2170,10 +2170,10 @@
         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 2739118..36fbd88 100644
--- a/apps/playback.c
+++ b/apps/playback.c
@@ -1485,12 +1485,12 @@
         /* 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 @@
                 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 ec2d99b..b3a5c82 100644
--- a/manual/configure_rockbox/playback_options.tex
+++ b/manual/configure_rockbox/playback_options.tex
@@ -269,10 +269,14 @@
   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