FS #12419 : Support for embedded cuesheets.

git-svn-id: svn://svn.rockbox.org/rockbox/trunk@31321 a1c6a512-1295-4272-9138-f99709370657
This commit is contained in:
Nick Peskett 2011-12-16 10:09:41 +00:00
parent 014003afac
commit 02fd314a0b
9 changed files with 192 additions and 44 deletions

View file

@ -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);

View file

@ -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);

View file

@ -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. */

View file

@ -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 */

View file

@ -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];

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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) */

View file

@ -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