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:
parent
014003afac
commit
02fd314a0b
9 changed files with 192 additions and 44 deletions
127
apps/cuesheet.c
127
apps/cuesheet.c
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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. */
|
||||||
|
|
|
@ -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 */
|
||||||
|
|
|
@ -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];
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
/* 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,
|
len = parse_tag(name, id3->path, id3, buf, buf_remaining,
|
||||||
TAGTYPE_VORBIS);
|
TAGTYPE_VORBIS);
|
||||||
|
}
|
||||||
|
|
||||||
buf += len;
|
buf += len;
|
||||||
buf_remaining -= len;
|
buf_remaining -= len;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) */
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue