From 0ed1719e1a4a9215a45fd0281c09d4e3553b9be7 Mon Sep 17 00:00:00 2001 From: wm4 Date: Tue, 19 May 2015 21:36:21 +0200 Subject: [PATCH] demux_cue: move cue parser to a separate file Preparation for the next commit. --- TOOLS/old-makefile | 1 + demux/cue.c | 206 +++++++++++++++++++++++++++++++++++++++++++++ demux/cue.h | 41 +++++++++ demux/demux_cue.c | 190 +++-------------------------------------- wscript_build.py | 1 + 5 files changed, 259 insertions(+), 180 deletions(-) create mode 100644 demux/cue.c create mode 100644 demux/cue.h diff --git a/TOOLS/old-makefile b/TOOLS/old-makefile index 1cbde2f31f..32b547894a 100644 --- a/TOOLS/old-makefile +++ b/TOOLS/old-makefile @@ -160,6 +160,7 @@ SOURCES = audio/audio.c \ common/tags.c \ common/version.c \ demux/codec_tags.c \ + demux/cue.c \ demux/demux.c \ demux/demux_edl.c \ demux/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; +} diff --git a/demux/cue.h b/demux/cue.h new file mode 100644 index 0000000000..f58ca79fe5 --- /dev/null +++ b/demux/cue.h @@ -0,0 +1,41 @@ +/* + * 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 . + */ + +#ifndef MP_CUE_H_ +#define MP_CUE_H_ + +#include + +#include "misc/bstr.h" + +struct cue_file { + struct cue_track *tracks; + int num_tracks; +}; + +struct cue_track { + double pregap_start; // corresponds to INDEX 00 + double start; // corresponds to INDEX 01 + struct bstr filename; + int source; + struct bstr title; +}; + +bool mp_probe_cue(struct bstr data); +struct cue_file *mp_parse_cue(struct bstr data); + +#endif diff --git a/demux/demux_cue.c b/demux/demux_cue.c index c493647e09..0caf66541e 100644 --- a/demux/demux_cue.c +++ b/demux/demux_cue.c @@ -33,149 +33,14 @@ #include "stream/stream.h" #include "timeline.h" +#include "cue.h" + #define PROBE_SIZE 512 -#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 }, -}; - -struct cue_track { - double pregap_start; // corresponds to INDEX 00 - double start; // corresponds to INDEX 01 - struct bstr filename; - int source; - struct bstr title; -}; struct priv { bstr data; }; -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. -static 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; -} - static void add_source(struct timeline *tl, struct demuxer *d) { MP_TARRAY_APPEND(tl, tl->sources, tl->num_sources, d); @@ -289,50 +154,15 @@ static void build_timeline(struct timeline *tl) add_source(tl, tl->demuxer); - struct bstr data = p->data; - data = skip_utf8_bom(data); - - struct cue_track *tracks = NULL; - size_t track_count = 0; - - 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: - MP_ERR(tl, "CUE: error parsing input file!\n"); - goto out; - case CUE_TRACK: { - track_count++; - tracks = talloc_realloc(ctx, tracks, struct cue_track, track_count); - cur_track = &tracks[track_count - 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; - } + struct cue_file *f = mp_parse_cue(p->data); + if (!f) { + MP_ERR(tl, "CUE: error parsing input file!\n"); + goto out; } + talloc_steal(ctx, f); + + struct cue_track *tracks = f->tracks; + size_t track_count = f->num_tracks; if (track_count == 0) { MP_ERR(tl, "CUE: no tracks found!\n"); diff --git a/wscript_build.py b/wscript_build.py index 6961c938b7..0dcb006a4d 100644 --- a/wscript_build.py +++ b/wscript_build.py @@ -167,6 +167,7 @@ def build(ctx): ## Demuxers ( "demux/codec_tags.c" ), + ( "demux/cue.c" ), ( "demux/demux.c" ), ( "demux/demux_cue.c" ), ( "demux/demux_disc.c" ),