1
0
Fork 0
forked from len0rd/rockbox

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" #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"); */ /* DEBUGF("look for cue file\n"); */
char cuepath[MAX_PATH]; char cuepath[MAX_PATH];
char *dot, *slash; char *dot, *slash;
slash = strrchr(trackpath, '/'); if (track_id3->embed_cuesheet.present)
if (!slash)
{ {
found_cue_path = NULL; cue_file->pos = track_id3->embed_cuesheet.pos;
return false; 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, '.'); dot = strrchr(cuepath, '.');
strcpy(dot, ".cue"); strcpy(dot, ".cue");
@ -67,15 +75,10 @@ bool look_for_cuesheet_file(const char *trackpath, char *found_cue_path)
char *dot = strrchr(cuepath, '.'); char *dot = strrchr(cuepath, '.');
strcpy(dot, ".cue"); strcpy(dot, ".cue");
if (!file_exists(cuepath)) if (!file_exists(cuepath))
{
if (found_cue_path)
found_cue_path = NULL;
return false; return false;
}
} }
if (found_cue_path) strlcpy(cue_file->path, cuepath, MAX_PATH);
strlcpy(found_cue_path, cuepath, MAX_PATH);
return true; return true;
} }
@ -99,29 +102,81 @@ static char *get_string(const char *line)
return start; return start;
} }
/* parse cuesheet "file" and store the information in "cue" */ /* parse cuesheet "cue_file" and store the information in "cue" */
bool parse_cuesheet(char *file, struct cuesheet *cue) bool parse_cuesheet(struct cuesheet_file *cue_file, struct cuesheet *cue)
{ {
char line[MAX_PATH]; char line[MAX_PATH];
char *s; 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); int fd = open(cue_file->path, O_RDONLY, 0644);
if (fd < 0) if(fd < 0)
{
/* couln't open the file */
return false; 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 */ /* Initialization */
memset(cue, 0, sizeof(struct cuesheet)); memset(cue, 0, sizeof(struct cuesheet));
strcpy(cue->path, file); strcpy(cue->path, cue_file->path);
cue->curr_track = cue->tracks; 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); s = skip_whitespace(line);
if (!strncmp(s, "TRACK", 5)) if (!strncmp(s, "TRACK", 5))
@ -169,9 +224,10 @@ bool parse_cuesheet(char *file, struct cuesheet *cue)
if (dest) 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'; *dest = '\0';
} }
else 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); close(fd);
@ -256,7 +320,7 @@ void browse_cuesheet(struct cuesheet *cue)
bool done = false; bool done = false;
int sel; int sel;
char title[MAX_PATH]; char title[MAX_PATH];
char cuepath[MAX_PATH]; struct cuesheet_file cue_file;
struct mp3entry *id3 = audio_current_track(); struct mp3entry *id3 = audio_current_track();
snprintf(title, MAX_PATH, "%s: %s", cue->performer, cue->title); snprintf(title, MAX_PATH, "%s: %s", cue->performer, cue->title);
@ -283,8 +347,8 @@ void browse_cuesheet(struct cuesheet *cue)
id3 = audio_current_track(); id3 = audio_current_track();
if (id3 && *id3->path && strcmp(id3->path, "No file!")) if (id3 && *id3->path && strcmp(id3->path, "No file!"))
{ {
look_for_cuesheet_file(id3->path, cuepath); look_for_cuesheet_file(id3, &cue_file);
if (id3->cuesheet && !strcmp(cue->path, cuepath)) if (id3->cuesheet && !strcmp(cue->path, cue_file.path))
{ {
sel = gui_synclist_get_sel_pos(&lists); sel = gui_synclist_get_sel_pos(&lists);
seek(cue->tracks[sel/2].offset); seek(cue->tracks[sel/2].offset);
@ -300,11 +364,16 @@ void browse_cuesheet(struct cuesheet *cue)
bool display_cuesheet_content(char* filename) bool display_cuesheet_content(char* filename)
{ {
size_t bufsize = 0; size_t bufsize = 0;
struct cuesheet_file cue_file;
struct cuesheet *cue = (struct cuesheet *)plugin_get_buffer(&bufsize); struct cuesheet *cue = (struct cuesheet *)plugin_get_buffer(&bufsize);
if (!cue || bufsize < sizeof(struct cuesheet)) if (!cue || bufsize < sizeof(struct cuesheet))
return false; 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; return false;
browse_cuesheet(cue); browse_cuesheet(cue);

View file

@ -51,11 +51,18 @@ struct cuesheet {
struct cue_track_info *curr_track; struct cue_track_info *curr_track;
}; };
/* looks if there is a cuesheet file that has a name matching "trackpath" */ struct cuesheet_file {
bool look_for_cuesheet_file(const char *trackpath, char *found_cue_path); char path[MAX_PATH];
int size;
off_t pos;
enum character_encoding encoding;
};
/* parse cuesheet "file" and store the information in "cue" */ /* looks if there is a cuesheet file with a name matching path of "track_id3" */
bool parse_cuesheet(char *file, struct cuesheet *cue); 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 */ /* reads a cuesheet to find the audio track associated to it */
bool get_trackname_from_cuesheet(char *filename, char *buf); 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 */ /* Take our best guess at the codec type based on file extension */
id3->codectype = probe_file_format(trackname); 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]; entry = &audio_formats[id3->codectype];
/* Load codec specific track tag information and confirm the codec type. */ /* Load codec specific track tag information and confirm the codec type. */

View file

@ -217,6 +217,21 @@ struct mp3_albumart {
}; };
#endif #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 { struct mp3entry {
char path[MAX_PATH]; char path[MAX_PATH];
char* title; char* title;
@ -307,6 +322,7 @@ struct mp3entry {
#endif #endif
/* Cuesheet support */ /* Cuesheet support */
struct embed_cuesheet embed_cuesheet;
struct cuesheet *cuesheet; struct cuesheet *cuesheet;
/* Musicbrainz Track ID */ /* Musicbrainz Track ID */

View file

@ -995,6 +995,40 @@ void setid3v2title(int fd, struct mp3entry *entry)
if(bytesread >= buffersize - bufferpos) if(bytesread >= buffersize - bufferpos)
bytesread = buffersize - bufferpos - 1; 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++) for (j = 0; j < bytesread; j++)
tag[j] = utf8buf[j]; tag[j] = utf8buf[j];

View file

@ -341,15 +341,29 @@ long read_vorbis_tags(int fd, struct mp3entry *id3,
} }
len -= read_len; 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; return 0;
} }
logf("Vorbis comment %d: %s=%s", i, name, id3->path); 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 += len;
buf_remaining -= 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) if (!checked_for_cuesheet && curr_cuesheet && id3->cuesheet == NULL)
{ {
checked_for_cuesheet = true; /* only check once per track */ 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) && if (look_for_cuesheet_file(id3, &cue_file)) &&
parse_cuesheet(cuepath, curr_cuesheet)) parse_cuesheet(&cue_file, curr_cuesheet))
{ {
id3->cuesheet = 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 /* If error other than a full buffer, then mark it "unsupported" to
avoid reloading attempt */ avoid reloading attempt */
int hid = ERR_UNSUPPORTED_TYPE; int hid = ERR_UNSUPPORTED_TYPE;
char cuepath[MAX_PATH]; struct cuesheet_file cue_file;
#ifdef HAVE_IO_PRIORITY #ifdef HAVE_IO_PRIORITY
buf_back_off_storage(true); buf_back_off_storage(true);
#endif #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); hid = bufalloc(NULL, sizeof (struct cuesheet), TYPE_CUESHEET);
@ -1499,7 +1499,7 @@ static bool audio_load_cuesheet(struct track_info *info,
void *cuesheet = NULL; void *cuesheet = NULL;
bufgetdata(hid, sizeof (struct cuesheet), &cuesheet); 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 /* Indicate cuesheet is present (while track remains
buffered) */ buffered) */

View file

@ -269,10 +269,14 @@ you to configure settings related to audio playback.
effect. effect.
Cuesheet files should have the same file name as the audio file they 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 reference, except with the extension \fname{.cue}. This file can either
the same directory as the audio file (checked first), or within the reside in the same directory as the audio file (checked first), or within the
\fname{.rockbox/cue} directory. \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} \section{Skip Length}\index{Skip Length}
Designed to speed up navigation when listening to long audio tracks, Designed to speed up navigation when listening to long audio tracks,
\setting{Skip Length} changes the behaviour of \setting{Skip Length} changes the behaviour of