1
0
mirror of https://github.com/mpv-player/mpv synced 2024-12-17 12:25:03 +00:00
mpv/demux/cue.c
wm4 ae7212963e cue: read more metadata
Make handling of metadata slightly more generic, and add reading of the
"PERFORMER" fields. There are some more fields, but for now let's leave
it at this.

TRACK-specific PERFORMER fields have to be read from the per-chapter
metadata (somewhat obscure).

Fixes #2328.
2015-10-01 21:57:35 +02:00

221 lines
6.2 KiB
C

/*
* This file is part of mpv.
*
* mpv 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.
*
* mpv is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with mpv. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <inttypes.h>
#include "talloc.h"
#include "misc/bstr.h"
#include "common/common.h"
#include "common/tags.h"
#include "cue.h"
#define SECS_PER_CUE_FRAME (1.0/75.0)
enum cue_command {
CUE_ERROR = -1, // not a valid CUE command, or an unknown extension
CUE_EMPTY, // line with whitespace only
CUE_UNUSED, // valid CUE command, but ignored by this code
CUE_FILE,
CUE_TRACK,
CUE_INDEX,
CUE_TITLE,
CUE_PERFORMER,
};
static const struct {
enum cue_command command;
const char *text;
} cue_command_strings[] = {
{ CUE_FILE, "FILE" },
{ CUE_TRACK, "TRACK" },
{ CUE_INDEX, "INDEX" },
{ CUE_TITLE, "TITLE" },
{ CUE_UNUSED, "CATALOG" },
{ CUE_UNUSED, "CDTEXTFILE" },
{ CUE_UNUSED, "FLAGS" },
{ CUE_UNUSED, "ISRC" },
{ CUE_PERFORMER, "PERFORMER" },
{ CUE_UNUSED, "POSTGAP" },
{ CUE_UNUSED, "PREGAP" },
{ CUE_UNUSED, "REM" },
{ CUE_UNUSED, "SONGWRITER" },
{ CUE_UNUSED, "MESSAGE" },
{ -1 },
};
static enum cue_command read_cmd(struct bstr *data, struct bstr *out_params)
{
struct bstr line = bstr_strip_linebreaks(bstr_getline(*data, data));
line = bstr_lstrip(line);
if (line.len == 0)
return CUE_EMPTY;
for (int n = 0; cue_command_strings[n].command != -1; n++) {
struct bstr name = bstr0(cue_command_strings[n].text);
if (bstr_startswith(line, name)) {
struct bstr rest = bstr_cut(line, name.len);
if (rest.len && !strchr(WHITESPACE, rest.start[0]))
continue;
if (out_params)
*out_params = rest;
return cue_command_strings[n].command;
}
}
return CUE_ERROR;
}
static bool eat_char(struct bstr *data, char ch)
{
if (data->len && data->start[0] == ch) {
*data = bstr_cut(*data, 1);
return true;
} else {
return false;
}
}
static char *read_quoted(void *talloc_ctx, struct bstr *data)
{
*data = bstr_lstrip(*data);
if (!eat_char(data, '"'))
return NULL;
int end = bstrchr(*data, '"');
if (end < 0)
return NULL;
struct bstr res = bstr_splice(*data, 0, end);
*data = bstr_cut(*data, end + 1);
return bstrto0(talloc_ctx, res);
}
// Read a 2 digit unsigned decimal integer.
// Return -1 on failure.
static int read_int_2(struct bstr *data)
{
*data = bstr_lstrip(*data);
if (data->len && data->start[0] == '-')
return -1;
struct bstr s = *data;
int res = (int)bstrtoll(s, &s, 10);
if (data->len == s.len || data->len - s.len > 2)
return -1;
*data = s;
return res;
}
static double read_time(struct bstr *data)
{
struct bstr s = *data;
bool ok = true;
double t1 = read_int_2(&s);
ok = eat_char(&s, ':') && ok;
double t2 = read_int_2(&s);
ok = eat_char(&s, ':') && ok;
double t3 = read_int_2(&s);
ok = ok && t1 >= 0 && t2 >= 0 && t3 >= 0;
return ok ? t1 * 60.0 + t2 + t3 * SECS_PER_CUE_FRAME : 0;
}
static struct bstr skip_utf8_bom(struct bstr data)
{
return bstr_startswith0(data, "\xEF\xBB\xBF") ? bstr_cut(data, 3) : data;
}
// Check if the text in data is most likely CUE data. This is used by the
// demuxer code to check the file type.
// data is the start of the probed file, possibly cut off at a random point.
bool mp_probe_cue(struct bstr data)
{
bool valid = false;
data = skip_utf8_bom(data);
for (;;) {
enum cue_command cmd = read_cmd(&data, NULL);
// End reached. Since the line was most likely cut off, don't use the
// result of the last parsing call.
if (data.len == 0)
break;
if (cmd == CUE_ERROR)
return false;
if (cmd != CUE_EMPTY)
valid = true;
}
return valid;
}
struct cue_file *mp_parse_cue(struct bstr data)
{
struct cue_file *f = talloc_zero(NULL, struct cue_file);
f->tags = talloc_zero(f, struct mp_tags);
data = skip_utf8_bom(data);
char *filename = NULL;
// Global metadata, and copied into new tracks.
struct cue_track proto_track = {0};
struct cue_track *cur_track = NULL;
while (data.len) {
struct bstr param;
int cmd = read_cmd(&data, &param);
switch (cmd) {
case CUE_ERROR:
talloc_free(f);
return NULL;
case CUE_TRACK: {
MP_TARRAY_GROW(f, f->tracks, f->num_tracks);
f->num_tracks += 1;
cur_track = &f->tracks[f->num_tracks - 1];
*cur_track = proto_track;
cur_track->tags = talloc_zero(f, struct mp_tags);
break;
}
case CUE_TITLE:
case CUE_PERFORMER: {
static const char *metanames[] = {
[CUE_TITLE] = "title",
[CUE_PERFORMER] = "performer",
};
struct mp_tags *tags = cur_track ? cur_track->tags : f->tags;
mp_tags_set_bstr(tags, bstr0(metanames[cmd]), param);
break;
}
case CUE_INDEX: {
int type = read_int_2(&param);
double time = read_time(&param);
if (cur_track) {
if (type == 1) {
cur_track->start = time;
cur_track->filename = filename;
} else if (type == 0) {
cur_track->pregap_start = time;
}
}
break;
}
case CUE_FILE:
// NOTE: FILE comes before TRACK, so don't use cur_track->filename
filename = read_quoted(f, &param);
break;
}
}
return f;
}