From 0ed1719e1a4a9215a45fd0281c09d4e3553b9be7 Mon Sep 17 00:00:00 2001 From: wm4 Date: Tue, 19 May 2015 21:36:21 +0200 Subject: demux_cue: move cue parser to a separate file Preparation for the next commit. --- demux/cue.c | 206 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 206 insertions(+) create mode 100644 demux/cue.c (limited to 'demux/cue.c') diff --git a/demux/cue.c b/demux/cue.c new file mode 100644 index 0000000000..832d78b215 --- /dev/null +++ b/demux/cue.c @@ -0,0 +1,206 @@ +/* + * 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 . + */ + +#include +#include +#include +#include + +#include "talloc.h" + +#include "misc/bstr.h" +#include "common/common.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, +}; + +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_UNUSED, "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 struct bstr read_quoted(struct bstr *data) +{ + *data = bstr_lstrip(*data); + if (!eat_char(data, '"')) + return (struct bstr) {0}; + int end = bstrchr(*data, '"'); + if (end < 0) + return (struct bstr) {0}; + struct bstr res = bstr_splice(*data, 0, end); + *data = bstr_cut(*data, end + 1); + return 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); + + data = skip_utf8_bom(data); + + struct bstr filename = {0}; + // Global metadata, and copied into new tracks. + struct cue_track proto_track = {0}; + struct cue_track *cur_track = &proto_track; + + while (data.len) { + struct bstr param; + switch (read_cmd(&data, ¶m)) { + 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; + break; + } + case CUE_TITLE: + cur_track->title = read_quoted(¶m); + break; + case CUE_INDEX: { + int type = read_int_2(¶m); + double time = read_time(¶m); + 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(¶m); + break; + } + } + + return f; +} -- cgit v1.2.3