1
0
mirror of https://github.com/mpv-player/mpv synced 2024-12-22 06:42:03 +00:00

demux_cue: move cue parser to a separate file

Preparation for the next commit.
This commit is contained in:
wm4 2015-05-19 21:36:21 +02:00
parent 1919f1e05b
commit 0ed1719e1a
5 changed files with 259 additions and 180 deletions

View File

@ -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 \

206
demux/cue.c Normal file
View File

@ -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 <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 "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, &param)) {
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(&param);
break;
case CUE_INDEX: {
int type = read_int_2(&param);
double time = read_time(&param);
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(&param);
break;
}
}
return f;
}

41
demux/cue.h Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef MP_CUE_H_
#define MP_CUE_H_
#include <stdbool.h>
#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

View File

@ -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, &param)) {
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(&param);
break;
case CUE_INDEX: {
int type = read_int_2(&param);
double time = read_time(&param);
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(&param);
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");

View File

@ -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" ),