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"
|
||||
|
||||
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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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. */
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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];
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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) */
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue