2007-02-14 14:40:24 +00:00
|
|
|
/***************************************************************************
|
|
|
|
* __________ __ ___.
|
|
|
|
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
|
|
|
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
|
|
|
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
|
|
|
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
|
|
|
* \/ \/ \/ \/ \/
|
2007-07-26 14:23:08 +00:00
|
|
|
* $Id$
|
2007-02-14 14:40:24 +00:00
|
|
|
*
|
|
|
|
* Copyright (C) 2007 Nicolas Pennequin, Jonathan Gordon
|
|
|
|
*
|
2008-06-28 18:10:04 +00:00
|
|
|
* This program is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU General Public License
|
|
|
|
* as published by the Free Software Foundation; either version 2
|
|
|
|
* of the License, or (at your option) any later version.
|
2007-02-14 14:40:24 +00:00
|
|
|
*
|
|
|
|
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
|
|
|
* KIND, either express or implied.
|
|
|
|
*
|
|
|
|
****************************************************************************/
|
|
|
|
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <stdbool.h>
|
2007-05-07 13:32:56 +00:00
|
|
|
#include <ctype.h>
|
2007-02-14 14:40:24 +00:00
|
|
|
#include <string.h>
|
|
|
|
#include "system.h"
|
|
|
|
#include "audio.h"
|
|
|
|
#include "kernel.h"
|
|
|
|
#include "logf.h"
|
|
|
|
#include "misc.h"
|
|
|
|
#include "screens.h"
|
|
|
|
#include "list.h"
|
|
|
|
#include "action.h"
|
|
|
|
#include "lang.h"
|
|
|
|
#include "debug.h"
|
|
|
|
#include "settings.h"
|
|
|
|
#include "plugin.h"
|
|
|
|
#include "playback.h"
|
|
|
|
#include "cuesheet.h"
|
2010-01-06 14:48:42 +00:00
|
|
|
#include "gui/wps.h"
|
2007-02-14 14:40:24 +00:00
|
|
|
|
2007-05-28 23:18:31 +00:00
|
|
|
#define CUE_DIR ROCKBOX_DIR "/cue"
|
|
|
|
|
2013-07-17 12:06:06 +00:00
|
|
|
static bool search_for_cuesheet(const char *path, struct cuesheet_file *cue_file)
|
2007-02-14 14:40:24 +00:00
|
|
|
{
|
2014-01-11 17:01:22 +00:00
|
|
|
size_t len;
|
2007-02-14 14:40:24 +00:00
|
|
|
char cuepath[MAX_PATH];
|
2014-01-11 17:01:22 +00:00
|
|
|
char *dot, *slash, *slash_cuepath;
|
2008-02-05 20:00:14 +00:00
|
|
|
|
2011-12-16 10:09:41 +00:00
|
|
|
cue_file->pos = 0;
|
|
|
|
cue_file->size = 0;
|
|
|
|
cue_file->path[0] = '\0';
|
2013-07-17 12:06:06 +00:00
|
|
|
slash = strrchr(path, '/');
|
2011-12-16 10:09:41 +00:00
|
|
|
if (!slash)
|
|
|
|
return false;
|
2013-07-17 12:06:06 +00:00
|
|
|
len = strlcpy(cuepath, path, MAX_PATH);
|
|
|
|
slash_cuepath = &cuepath[slash - path];
|
2014-01-11 17:01:22 +00:00
|
|
|
dot = strrchr(slash_cuepath, '.');
|
|
|
|
if (dot)
|
2022-11-14 16:32:34 +00:00
|
|
|
strmemccpy(dot, ".cue", MAX_PATH - (dot-cuepath));
|
2007-02-14 14:40:24 +00:00
|
|
|
|
2014-01-11 17:01:22 +00:00
|
|
|
if (!dot || !file_exists(cuepath))
|
2007-02-14 14:40:24 +00:00
|
|
|
{
|
2007-05-28 23:18:31 +00:00
|
|
|
strcpy(cuepath, CUE_DIR);
|
2014-01-11 17:24:48 +00:00
|
|
|
if (strlcat(cuepath, slash, MAX_PATH) >= MAX_PATH)
|
2021-10-21 20:42:01 +00:00
|
|
|
goto skip; /* overflow */
|
2022-11-12 21:34:02 +00:00
|
|
|
dot = strrchr(cuepath, '.');
|
2007-05-28 23:18:31 +00:00
|
|
|
strcpy(dot, ".cue");
|
2008-04-16 19:51:43 +00:00
|
|
|
if (!file_exists(cuepath))
|
2014-01-03 15:30:26 +00:00
|
|
|
{
|
2014-01-11 17:24:48 +00:00
|
|
|
skip:
|
2014-01-11 17:01:22 +00:00
|
|
|
if ((len+4) >= MAX_PATH)
|
|
|
|
return false;
|
2022-11-14 16:32:34 +00:00
|
|
|
strmemccpy(cuepath, path, MAX_PATH);
|
2014-01-03 15:30:26 +00:00
|
|
|
strlcat(cuepath, ".cue", MAX_PATH);
|
|
|
|
if (!file_exists(cuepath))
|
|
|
|
return false;
|
|
|
|
}
|
2007-02-14 14:40:24 +00:00
|
|
|
}
|
2008-04-16 19:51:43 +00:00
|
|
|
|
2022-11-14 16:32:34 +00:00
|
|
|
strmemccpy(cue_file->path, cuepath, MAX_PATH);
|
2007-05-28 23:18:31 +00:00
|
|
|
return true;
|
2007-02-14 14:40:24 +00:00
|
|
|
}
|
|
|
|
|
2013-07-17 12:06:06 +00:00
|
|
|
bool look_for_cuesheet_file(struct mp3entry *track_id3, struct cuesheet_file *cue_file)
|
|
|
|
{
|
|
|
|
/* DEBUGF("look for cue file\n"); */
|
|
|
|
if (track_id3->has_embedded_cuesheet)
|
|
|
|
{
|
|
|
|
cue_file->pos = track_id3->embedded_cuesheet.pos;
|
|
|
|
cue_file->size = track_id3->embedded_cuesheet.size;
|
|
|
|
cue_file->encoding = track_id3->embedded_cuesheet.encoding;
|
2022-11-14 16:32:34 +00:00
|
|
|
strmemccpy(cue_file->path, track_id3->path, MAX_PATH);
|
2013-07-17 12:06:06 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return search_for_cuesheet(track_id3->path, cue_file);
|
|
|
|
}
|
|
|
|
|
2007-03-28 19:43:03 +00:00
|
|
|
static char *get_string(const char *line)
|
|
|
|
{
|
|
|
|
char *start, *end;
|
|
|
|
|
|
|
|
start = strchr(line, '"');
|
|
|
|
if (!start)
|
|
|
|
{
|
|
|
|
start = strchr(line, ' ');
|
|
|
|
|
|
|
|
if (!start)
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
end = strchr(++start, '"');
|
|
|
|
if (end)
|
|
|
|
*end = '\0';
|
|
|
|
|
|
|
|
return start;
|
|
|
|
}
|
|
|
|
|
2021-07-29 11:26:40 +00:00
|
|
|
static unsigned long parse_cue_index(const char *line)
|
|
|
|
{
|
|
|
|
/* assumes strncmp(line, "INDEX 01", 8) & NULL terminated string */
|
|
|
|
/* INDEX 01 MM:SS:FF\0 (00:00:00\0 - 99:99:99\0)*/
|
|
|
|
const unsigned field_m[3] = {60 * 1000, 1000, 13}; /* MM:SS:~FF*/
|
2022-04-07 09:08:50 +00:00
|
|
|
const unsigned field_max[3] = {30000, 59, 74}; /* MM, SS, FF */
|
2021-07-29 11:26:40 +00:00
|
|
|
const char f_sep = ':';
|
|
|
|
int field = -1;
|
|
|
|
unsigned long offset = 0; /* ms from start of track */
|
|
|
|
unsigned long value = 0;
|
|
|
|
while (*line)
|
|
|
|
{
|
|
|
|
if (!isdigit(*line)) /* search for numbers */
|
|
|
|
{
|
|
|
|
line++;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
while (isdigit(*line))
|
|
|
|
{
|
|
|
|
value = 10 * value + (*line - '0');
|
2022-04-07 09:08:50 +00:00
|
|
|
if (value > field_max[field]) /* Sanity check bail early */
|
2021-07-29 11:26:40 +00:00
|
|
|
return 0;
|
|
|
|
line++;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (field < 0) /*Filter INDEX 01*/
|
|
|
|
{
|
|
|
|
/* safe to assume value == 1 */
|
|
|
|
}
|
|
|
|
else if (field <= 2)
|
|
|
|
{
|
|
|
|
while(*line && *line != f_sep)
|
|
|
|
line++;
|
|
|
|
|
|
|
|
if (*line || field == 2) /* if *line valid we found f_sep */
|
|
|
|
offset += (unsigned long) field_m[field] * value;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
break;
|
|
|
|
|
|
|
|
value = 0;
|
|
|
|
field++;
|
|
|
|
}
|
|
|
|
|
|
|
|
return offset;
|
|
|
|
}
|
|
|
|
|
2022-03-13 21:11:50 +00:00
|
|
|
enum eCS_SUPPORTED_TAGS {
|
|
|
|
eCS_TRACK = 0, eCS_INDEX_01, eCS_TITLE,
|
|
|
|
eCS_PERFORMER, eCS_SONGWRITER, eCS_FILE,
|
|
|
|
eCS_COUNT_TAGS_COUNT, eCS_NOTFOUND = -1
|
|
|
|
};
|
|
|
|
|
|
|
|
static enum eCS_SUPPORTED_TAGS cuesheet_tag_get_option(const char *option)
|
|
|
|
{
|
|
|
|
#define CS_OPTN(str) {str, sizeof(str)-1}
|
|
|
|
static const struct cs_option_t {
|
|
|
|
const char *str;
|
|
|
|
const int len;
|
|
|
|
} cs_options[eCS_COUNT_TAGS_COUNT + 1] = {
|
|
|
|
[eCS_TRACK] = CS_OPTN("TRACK"),
|
2022-03-18 21:34:11 +00:00
|
|
|
[eCS_INDEX_01] = CS_OPTN("INDEX 01"),
|
2022-03-13 21:11:50 +00:00
|
|
|
[eCS_TITLE] =CS_OPTN("TITLE"),
|
|
|
|
[eCS_PERFORMER] =CS_OPTN("PERFORMER"),
|
|
|
|
[eCS_SONGWRITER] =CS_OPTN("SONGWRITER"),
|
|
|
|
[eCS_FILE] =CS_OPTN("FILE"),
|
|
|
|
[eCS_COUNT_TAGS_COUNT] = {NULL, 0} /*SENTINEL*/
|
|
|
|
};
|
|
|
|
|
|
|
|
const struct cs_option_t *op;
|
|
|
|
for (int i=0; ((op=&cs_options[i]))->str != NULL; i++)
|
|
|
|
{
|
|
|
|
if (strncmp(option, op->str, op->len) == 0)
|
|
|
|
return i;
|
|
|
|
}
|
|
|
|
return eCS_NOTFOUND;
|
|
|
|
#undef CS_OPTN
|
|
|
|
}
|
|
|
|
|
2011-12-16 10:09:41 +00:00
|
|
|
/* parse cuesheet "cue_file" and store the information in "cue" */
|
|
|
|
bool parse_cuesheet(struct cuesheet_file *cue_file, struct cuesheet *cue)
|
2007-02-14 14:40:24 +00:00
|
|
|
{
|
|
|
|
char line[MAX_PATH];
|
2007-03-28 19:43:03 +00:00
|
|
|
char *s;
|
2011-12-16 10:09:41 +00:00
|
|
|
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(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;
|
|
|
|
}
|
2007-03-28 19:43:03 +00:00
|
|
|
|
2011-12-16 10:09:41 +00:00
|
|
|
/* Look for a Unicode BOM */
|
|
|
|
unsigned char bom_read = 0;
|
2022-03-18 22:57:55 +00:00
|
|
|
if (read(fd, line, BOM_UTF_8_SIZE) > 0)
|
2007-02-14 14:40:24 +00:00
|
|
|
{
|
2022-03-18 22:57:55 +00:00
|
|
|
if(!memcmp(line, BOM_UTF_8, BOM_UTF_8_SIZE))
|
|
|
|
{
|
|
|
|
char_enc = CHAR_ENC_UTF_8;
|
|
|
|
bom_read = BOM_UTF_8_SIZE;
|
|
|
|
}
|
|
|
|
else if(!memcmp(line, BOM_UTF_16_LE, BOM_UTF_16_SIZE))
|
|
|
|
{
|
|
|
|
char_enc = CHAR_ENC_UTF_16_LE;
|
|
|
|
bom_read = BOM_UTF_16_SIZE;
|
|
|
|
}
|
|
|
|
else if(!memcmp(line, BOM_UTF_16_BE, BOM_UTF_16_SIZE))
|
|
|
|
{
|
|
|
|
char_enc = CHAR_ENC_UTF_16_BE;
|
|
|
|
bom_read = BOM_UTF_16_SIZE;
|
|
|
|
}
|
2011-12-16 10:09:41 +00:00
|
|
|
}
|
2022-03-18 22:57:55 +00:00
|
|
|
|
2011-12-20 08:15:36 +00:00
|
|
|
if (bom_read < BOM_UTF_8_SIZE)
|
2011-12-16 10:09:41 +00:00
|
|
|
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;
|
2007-02-14 14:40:24 +00:00
|
|
|
}
|
|
|
|
|
2007-03-28 19:43:03 +00:00
|
|
|
/* Initialization */
|
2007-02-14 14:40:24 +00:00
|
|
|
memset(cue, 0, sizeof(struct cuesheet));
|
2011-12-16 10:09:41 +00:00
|
|
|
strcpy(cue->path, cue_file->path);
|
2007-02-14 14:40:24 +00:00
|
|
|
cue->curr_track = cue->tracks;
|
|
|
|
|
2013-07-17 12:06:06 +00:00
|
|
|
if (is_embedded)
|
|
|
|
strcpy(cue->file, cue->path);
|
|
|
|
|
2011-12-16 10:09:41 +00:00
|
|
|
while ((line_len = read_line(fd, line, read_bytes)) > 0
|
|
|
|
&& cue->track_count < MAX_TRACKS )
|
2007-02-14 14:40:24 +00:00
|
|
|
{
|
2011-12-16 10:09:41 +00:00
|
|
|
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);
|
|
|
|
}
|
2007-02-14 14:40:24 +00:00
|
|
|
s = skip_whitespace(line);
|
2007-03-28 19:43:03 +00:00
|
|
|
|
2022-03-13 21:11:50 +00:00
|
|
|
/* RECOGNIZED TAGS ***********************
|
|
|
|
* eCS_TRACK = 0, eCS_INDEX_01, eCS_TITLE,
|
|
|
|
* eCS_PERFORMER, eCS_SONGWRITER, eCS_FILE,
|
|
|
|
*/
|
|
|
|
enum eCS_SUPPORTED_TAGS option = cuesheet_tag_get_option(s);
|
|
|
|
if (option == eCS_TRACK)
|
2007-02-14 14:40:24 +00:00
|
|
|
{
|
|
|
|
cue->track_count++;
|
|
|
|
}
|
2022-03-13 21:11:50 +00:00
|
|
|
else if (option == eCS_INDEX_01)
|
2007-02-14 14:40:24 +00:00
|
|
|
{
|
2021-07-29 11:26:40 +00:00
|
|
|
#if 0
|
2007-02-14 14:40:24 +00:00
|
|
|
s = strchr(s,' ');
|
|
|
|
s = skip_whitespace(s);
|
|
|
|
s = strchr(s,' ');
|
|
|
|
s = skip_whitespace(s);
|
|
|
|
cue->tracks[cue->track_count-1].offset = 60*1000 * atoi(s);
|
|
|
|
s = strchr(s,':') + 1;
|
|
|
|
cue->tracks[cue->track_count-1].offset += 1000 * atoi(s);
|
|
|
|
s = strchr(s,':') + 1;
|
|
|
|
cue->tracks[cue->track_count-1].offset += 13 * atoi(s);
|
2021-07-29 11:26:40 +00:00
|
|
|
#else
|
|
|
|
cue->tracks[cue->track_count-1].offset = parse_cue_index(s);
|
|
|
|
#endif
|
2007-02-14 14:40:24 +00:00
|
|
|
}
|
2022-03-13 21:11:50 +00:00
|
|
|
else if (option != eCS_NOTFOUND)
|
2007-03-28 19:43:03 +00:00
|
|
|
{
|
2007-03-28 19:55:41 +00:00
|
|
|
char *dest = NULL;
|
|
|
|
char *string = get_string(s);
|
2007-03-28 19:43:03 +00:00
|
|
|
if (!string)
|
|
|
|
break;
|
|
|
|
|
2013-07-17 12:06:06 +00:00
|
|
|
size_t count = MAX_NAME*3 + 1;
|
|
|
|
size_t count8859 = MAX_NAME;
|
|
|
|
|
2022-03-13 21:11:50 +00:00
|
|
|
switch (option)
|
2007-03-28 19:43:03 +00:00
|
|
|
{
|
2022-03-13 21:11:50 +00:00
|
|
|
case eCS_TITLE: /* TITLE */
|
2007-03-28 19:43:03 +00:00
|
|
|
dest = (cue->track_count <= 0) ? cue->title :
|
|
|
|
cue->tracks[cue->track_count-1].title;
|
|
|
|
break;
|
|
|
|
|
2022-03-13 21:11:50 +00:00
|
|
|
case eCS_PERFORMER: /* PERFORMER */
|
2007-03-28 19:43:03 +00:00
|
|
|
dest = (cue->track_count <= 0) ? cue->performer :
|
|
|
|
cue->tracks[cue->track_count-1].performer;
|
|
|
|
break;
|
|
|
|
|
2022-03-13 21:11:50 +00:00
|
|
|
case eCS_SONGWRITER: /* SONGWRITER */
|
2007-03-29 18:39:04 +00:00
|
|
|
dest = (cue->track_count <= 0) ? cue->songwriter :
|
2007-03-28 19:43:03 +00:00
|
|
|
cue->tracks[cue->track_count-1].songwriter;
|
2007-03-28 19:55:41 +00:00
|
|
|
break;
|
2013-07-17 12:06:06 +00:00
|
|
|
|
2022-03-13 21:11:50 +00:00
|
|
|
case eCS_FILE: /* FILE */
|
2013-07-17 12:06:06 +00:00
|
|
|
if (is_embedded || cue->track_count > 0)
|
|
|
|
break;
|
|
|
|
|
|
|
|
dest = cue->file;
|
|
|
|
count = MAX_PATH;
|
|
|
|
count8859 = MAX_PATH/3;
|
|
|
|
break;
|
2022-03-13 21:11:50 +00:00
|
|
|
case eCS_TRACK:
|
|
|
|
/*Fall-Through*/
|
|
|
|
case eCS_INDEX_01:
|
|
|
|
/*Fall-Through*/
|
|
|
|
case eCS_COUNT_TAGS_COUNT:
|
|
|
|
/*Fall-Through*/
|
|
|
|
case eCS_NOTFOUND: /*Shouldn't happen*/
|
|
|
|
logf(HZ * 2, "Bad Tag %d @ %s", (int) option, __func__);
|
|
|
|
dest = NULL;
|
|
|
|
break;
|
2007-03-28 19:43:03 +00:00
|
|
|
}
|
|
|
|
|
2021-10-21 20:42:01 +00:00
|
|
|
if (dest)
|
2009-01-26 11:24:11 +00:00
|
|
|
{
|
2011-12-16 10:09:41 +00:00
|
|
|
if (char_enc == CHAR_ENC_ISO_8859_1)
|
2009-01-26 11:24:11 +00:00
|
|
|
{
|
2011-12-16 10:09:41 +00:00
|
|
|
dest = iso_decode(string, dest, -1,
|
2013-07-17 12:06:06 +00:00
|
|
|
MIN(strlen(string), count8859));
|
2009-01-26 11:24:11 +00:00
|
|
|
*dest = '\0';
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2022-11-14 16:32:34 +00:00
|
|
|
strmemccpy(dest, string, count);
|
2009-01-26 11:24:11 +00:00
|
|
|
}
|
2021-10-21 20:42:01 +00:00
|
|
|
}
|
2007-03-28 19:43:03 +00:00
|
|
|
}
|
2022-03-13 21:11:50 +00:00
|
|
|
|
2011-12-16 10:09:41 +00:00
|
|
|
if (is_embedded)
|
|
|
|
{
|
|
|
|
bytes_left -= line_len;
|
|
|
|
if (bytes_left <= 0)
|
|
|
|
break;
|
|
|
|
if (bytes_left < read_bytes)
|
|
|
|
read_bytes = bytes_left;
|
|
|
|
}
|
2007-02-14 14:40:24 +00:00
|
|
|
}
|
|
|
|
close(fd);
|
|
|
|
|
2013-07-17 12:06:06 +00:00
|
|
|
/* If just a filename, add path information from cuesheet path */
|
|
|
|
if (*cue->file && !strrchr(cue->file, '/'))
|
|
|
|
{
|
|
|
|
strcpy(line, cue->file);
|
|
|
|
strcpy(cue->file, cue->path);
|
|
|
|
char *slash = strrchr(cue->file, '/');
|
|
|
|
if (!slash++) slash = cue->file;
|
2022-11-14 16:32:34 +00:00
|
|
|
strmemccpy(slash, line, MAX_PATH - (slash - cue->file));
|
2013-07-17 12:06:06 +00:00
|
|
|
}
|
|
|
|
|
2007-02-14 14:40:24 +00:00
|
|
|
/* If some songs don't have performer info, we copy the cuesheet performer */
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < cue->track_count; i++)
|
|
|
|
{
|
|
|
|
if (*(cue->tracks[i].performer) == '\0')
|
2022-11-14 16:32:34 +00:00
|
|
|
strmemccpy(cue->tracks[i].performer, cue->performer, MAX_NAME*3);
|
2007-03-29 18:39:04 +00:00
|
|
|
|
|
|
|
if (*(cue->tracks[i].songwriter) == '\0')
|
2022-11-14 16:32:34 +00:00
|
|
|
strmemccpy(cue->tracks[i].songwriter, cue->songwriter, MAX_NAME*3);
|
2007-02-14 14:40:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* takes care of seeking to a track in a playlist
|
|
|
|
* returns false if audio isn't playing */
|
2007-02-16 18:48:36 +00:00
|
|
|
static bool seek(unsigned long pos)
|
2007-02-14 14:40:24 +00:00
|
|
|
{
|
|
|
|
if (!(audio_status() & AUDIO_STATUS_PLAY))
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
audio_pre_ff_rewind();
|
2007-02-28 23:44:09 +00:00
|
|
|
audio_ff_rewind(pos);
|
2007-02-14 14:40:24 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* returns the index of the track currently being played
|
|
|
|
and updates the information about the current track. */
|
|
|
|
int cue_find_current_track(struct cuesheet *cue, unsigned long curpos)
|
|
|
|
{
|
|
|
|
int i=0;
|
|
|
|
while (i < cue->track_count-1 && cue->tracks[i+1].offset < curpos)
|
|
|
|
i++;
|
2007-03-29 18:39:04 +00:00
|
|
|
|
2007-02-14 14:40:24 +00:00
|
|
|
cue->curr_track_idx = i;
|
|
|
|
cue->curr_track = cue->tracks + i;
|
|
|
|
return i;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* callback that gives list item titles for the cuesheet browser */
|
2009-08-20 16:47:44 +00:00
|
|
|
static const char* list_get_name_cb(int selected_item,
|
|
|
|
void *data,
|
|
|
|
char *buffer,
|
|
|
|
size_t buffer_len)
|
2007-02-14 14:40:24 +00:00
|
|
|
{
|
|
|
|
struct cuesheet *cue = (struct cuesheet *)data;
|
|
|
|
|
|
|
|
if (selected_item & 1)
|
2022-11-14 16:32:34 +00:00
|
|
|
strmemccpy(buffer, cue->tracks[selected_item/2].title, buffer_len);
|
2007-02-14 14:40:24 +00:00
|
|
|
else
|
2008-04-09 15:25:17 +00:00
|
|
|
snprintf(buffer, buffer_len, "%02d. %s", selected_item/2+1,
|
2007-02-14 14:40:24 +00:00
|
|
|
cue->tracks[selected_item/2].performer);
|
2007-03-29 18:39:04 +00:00
|
|
|
|
2007-02-14 14:40:24 +00:00
|
|
|
return buffer;
|
|
|
|
}
|
|
|
|
|
2007-05-14 17:34:52 +00:00
|
|
|
void browse_cuesheet(struct cuesheet *cue)
|
2007-02-14 14:40:24 +00:00
|
|
|
{
|
|
|
|
struct gui_synclist lists;
|
|
|
|
int action;
|
|
|
|
bool done = false;
|
|
|
|
char title[MAX_PATH];
|
2018-07-25 17:32:25 +00:00
|
|
|
int len;
|
2018-07-25 12:14:45 +00:00
|
|
|
|
2011-12-16 10:09:41 +00:00
|
|
|
struct cuesheet_file cue_file;
|
2007-02-14 14:40:24 +00:00
|
|
|
struct mp3entry *id3 = audio_current_track();
|
|
|
|
|
2018-07-25 12:14:45 +00:00
|
|
|
len = snprintf(title, sizeof(title), "%s: %s", cue->performer, cue->title);
|
|
|
|
|
|
|
|
if ((unsigned) len > sizeof(title))
|
2018-12-08 10:43:44 +00:00
|
|
|
title[sizeof(title) - 2] = '~'; /* give indication of truncation */
|
|
|
|
|
2018-07-25 12:14:45 +00:00
|
|
|
|
2008-03-26 03:35:24 +00:00
|
|
|
gui_synclist_init(&lists, list_get_name_cb, cue, false, 2, NULL);
|
2007-02-14 14:40:24 +00:00
|
|
|
gui_synclist_set_nb_items(&lists, 2*cue->track_count);
|
|
|
|
gui_synclist_set_title(&lists, title, 0);
|
|
|
|
|
|
|
|
|
2009-07-20 05:18:18 +00:00
|
|
|
if (id3)
|
2007-02-14 14:40:24 +00:00
|
|
|
{
|
|
|
|
gui_synclist_select_item(&lists,
|
|
|
|
2*cue_find_current_track(cue, id3->elapsed));
|
|
|
|
}
|
|
|
|
|
|
|
|
while (!done)
|
|
|
|
{
|
|
|
|
gui_synclist_draw(&lists);
|
|
|
|
action = get_action(CONTEXT_LIST,TIMEOUT_BLOCK);
|
2022-09-19 11:48:15 +00:00
|
|
|
if (gui_synclist_do_button(&lists, &action))
|
2007-02-14 14:40:24 +00:00
|
|
|
continue;
|
|
|
|
switch (action)
|
|
|
|
{
|
|
|
|
case ACTION_STD_OK:
|
2013-07-17 12:06:06 +00:00
|
|
|
{
|
|
|
|
bool startit = true;
|
|
|
|
unsigned long elapsed =
|
|
|
|
cue->tracks[gui_synclist_get_sel_pos(&lists)/2].offset;
|
|
|
|
|
2007-02-14 14:40:24 +00:00
|
|
|
id3 = audio_current_track();
|
2013-07-17 12:06:06 +00:00
|
|
|
if (id3 && *id3->path)
|
2007-02-14 14:40:24 +00:00
|
|
|
{
|
2011-12-16 10:09:41 +00:00
|
|
|
look_for_cuesheet_file(id3, &cue_file);
|
2013-07-17 12:06:06 +00:00
|
|
|
if (!strcmp(cue->path, cue_file.path))
|
|
|
|
startit = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!startit)
|
|
|
|
startit = !seek(elapsed);
|
|
|
|
|
|
|
|
if (!startit || !*cue->file)
|
|
|
|
break;
|
|
|
|
|
|
|
|
/* check that this cue is the same one that would be found by
|
|
|
|
a search from playback */
|
|
|
|
char file[MAX_PATH];
|
2022-11-14 16:32:34 +00:00
|
|
|
strmemccpy(file, cue->file, MAX_PATH);
|
2013-07-17 12:06:06 +00:00
|
|
|
|
|
|
|
if (!strcmp(cue->path, file) || /* if embedded */
|
|
|
|
(search_for_cuesheet(file, &cue_file) &&
|
|
|
|
!strcmp(cue->path, cue_file.path)))
|
|
|
|
{
|
|
|
|
char *fname = strrsplt(file, '/');
|
|
|
|
char *dirname = fname <= file + 1 ? "/" : file;
|
|
|
|
bookmark_play(dirname, 0, elapsed, 0, current_tick, fname);
|
2007-02-14 14:40:24 +00:00
|
|
|
}
|
|
|
|
break;
|
2013-07-17 12:06:06 +00:00
|
|
|
} /* ACTION_STD_OK */
|
|
|
|
|
2007-02-14 14:40:24 +00:00
|
|
|
case ACTION_STD_CANCEL:
|
|
|
|
done = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool display_cuesheet_content(char* filename)
|
|
|
|
{
|
2007-04-21 18:38:25 +00:00
|
|
|
size_t bufsize = 0;
|
2011-12-16 10:09:41 +00:00
|
|
|
struct cuesheet_file cue_file;
|
2007-02-14 14:40:24 +00:00
|
|
|
struct cuesheet *cue = (struct cuesheet *)plugin_get_buffer(&bufsize);
|
2007-04-03 18:18:35 +00:00
|
|
|
if (!cue || bufsize < sizeof(struct cuesheet))
|
2007-02-14 14:40:24 +00:00
|
|
|
return false;
|
|
|
|
|
2022-11-14 16:32:34 +00:00
|
|
|
strmemccpy(cue_file.path, filename, MAX_PATH);
|
2011-12-16 10:09:41 +00:00
|
|
|
cue_file.pos = 0;
|
|
|
|
cue_file.size = 0;
|
|
|
|
|
|
|
|
if (!parse_cuesheet(&cue_file, cue))
|
2007-02-14 14:40:24 +00:00
|
|
|
return false;
|
|
|
|
|
|
|
|
browse_cuesheet(cue);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* skips backwards or forward in the current cuesheet
|
|
|
|
* the return value indicates whether we're still in a cusheet after skipping
|
|
|
|
* it also returns false if we weren't in a cuesheet.
|
|
|
|
* direction should be 1 or -1.
|
|
|
|
*/
|
2009-07-24 01:13:30 +00:00
|
|
|
bool curr_cuesheet_skip(struct cuesheet *cue, int direction, unsigned long curr_pos)
|
2007-02-14 14:40:24 +00:00
|
|
|
{
|
2009-07-24 01:13:30 +00:00
|
|
|
int track = cue_find_current_track(cue, curr_pos);
|
2021-10-21 20:42:01 +00:00
|
|
|
|
2009-07-24 01:13:30 +00:00
|
|
|
if (direction >= 0 && track == cue->track_count - 1)
|
2007-02-14 14:40:24 +00:00
|
|
|
{
|
|
|
|
/* we want to get out of the cuesheet */
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (!(direction <= 0 && track == 0))
|
2010-01-06 14:48:42 +00:00
|
|
|
{
|
|
|
|
/* If skipping forward, skip to next cuesheet segment. If skipping
|
2022-10-02 18:45:20 +00:00
|
|
|
backward before DEFAULT_SKIP_THRESH milliseconds have elapsed, skip
|
2010-01-06 14:48:42 +00:00
|
|
|
to previous cuesheet segment. If skipping backward after
|
2022-10-02 18:45:20 +00:00
|
|
|
DEFAULT_SKIP_THRESH seconds have elapsed, skip to the start of the
|
2010-01-06 14:48:42 +00:00
|
|
|
current cuesheet segment */
|
2021-10-21 20:42:01 +00:00
|
|
|
if (direction == 1 ||
|
2022-10-02 18:45:20 +00:00
|
|
|
((curr_pos - cue->tracks[track].offset) < DEFAULT_SKIP_THRESH))
|
2010-01-06 14:48:42 +00:00
|
|
|
{
|
|
|
|
track += direction;
|
|
|
|
}
|
|
|
|
}
|
2007-02-14 14:40:24 +00:00
|
|
|
|
2009-07-24 01:13:30 +00:00
|
|
|
seek(cue->tracks[track].offset);
|
2007-02-14 14:40:24 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline void draw_veritcal_line_mark(struct screen * screen,
|
|
|
|
int x, int y, int h)
|
|
|
|
{
|
|
|
|
screen->set_drawmode(DRMODE_COMPLEMENT);
|
|
|
|
screen->vline(x, y, y+h-1);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* draw the cuesheet markers for a track of length "tracklen",
|
2010-03-07 12:51:23 +00:00
|
|
|
between (x,y) and (x+w,y) */
|
2009-07-24 01:13:30 +00:00
|
|
|
void cue_draw_markers(struct screen *screen, struct cuesheet *cue,
|
|
|
|
unsigned long tracklen,
|
2010-03-07 12:51:23 +00:00
|
|
|
int x, int y, int w, int h)
|
2007-02-14 14:40:24 +00:00
|
|
|
{
|
|
|
|
int i,xi;
|
2011-08-26 16:46:23 +00:00
|
|
|
unsigned long tracklen_seconds = tracklen/1000; /* duration in seconds */
|
2020-07-17 14:31:31 +00:00
|
|
|
|
2009-07-24 01:13:30 +00:00
|
|
|
for (i=1; i < cue->track_count; i++)
|
2007-02-14 14:40:24 +00:00
|
|
|
{
|
2011-08-26 16:46:23 +00:00
|
|
|
/* Convert seconds prior to multiplication to avoid overflow. */
|
|
|
|
xi = x + (w * (cue->tracks[i].offset/1000)) / tracklen_seconds;
|
2007-02-14 14:40:24 +00:00
|
|
|
draw_veritcal_line_mark(screen, xi, y, h);
|
|
|
|
}
|
|
|
|
}
|
2009-07-20 05:18:18 +00:00
|
|
|
|
|
|
|
bool cuesheet_subtrack_changed(struct mp3entry *id3)
|
|
|
|
{
|
|
|
|
struct cuesheet *cue = id3->cuesheet;
|
|
|
|
if (cue && (id3->elapsed < cue->curr_track->offset
|
|
|
|
|| (cue->curr_track_idx < cue->track_count - 1
|
|
|
|
&& id3->elapsed >= (cue->curr_track+1)->offset)))
|
|
|
|
{
|
|
|
|
cue_find_current_track(cue, id3->elapsed);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|