1
by Alessandro Ghedini
Import upstream version 0.1.4 |
1 |
/*
|
2 |
* This file is part of mplayer2.
|
|
3 |
*
|
|
4 |
* mplayer2 is free software; you can redistribute it and/or modify
|
|
5 |
* it under the terms of the GNU General Public License as published by
|
|
6 |
* the Free Software Foundation; either version 2 of the License, or
|
|
7 |
* (at your option) any later version.
|
|
8 |
*
|
|
9 |
* mplayer2 is distributed in the hope that it will be useful,
|
|
10 |
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11 |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12 |
* GNU General Public License for more details.
|
|
13 |
*
|
|
14 |
* You should have received a copy of the GNU General Public License along
|
|
15 |
* with mplayer2; if not, write to the Free Software Foundation, Inc.,
|
|
16 |
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
17 |
*/
|
|
18 |
||
19 |
#include <dirent.h> |
|
20 |
#include <stdlib.h> |
|
21 |
#include <stdbool.h> |
|
22 |
#include <inttypes.h> |
|
23 |
#include <ctype.h> |
|
24 |
||
25 |
#include "talloc.h" |
|
26 |
||
27 |
#include "mpvcore/mp_core.h" |
|
28 |
#include "mpvcore/mp_msg.h" |
|
29 |
#include "demux/demux.h" |
|
30 |
#include "mpvcore/path.h" |
|
31 |
#include "mpvcore/bstr.h" |
|
32 |
#include "mpvcore/mp_common.h" |
|
33 |
#include "stream/stream.h" |
|
34 |
||
35 |
// used by demuxer_cue.c
|
|
36 |
bool mp_probe_cue(struct bstr data); |
|
37 |
||
38 |
#define SECS_PER_CUE_FRAME (1.0/75.0)
|
|
39 |
||
40 |
enum cue_command { |
|
41 |
CUE_ERROR = -1, // not a valid CUE command, or an unknown extension |
|
42 |
CUE_EMPTY, // line with whitespace only |
|
43 |
CUE_UNUSED, // valid CUE command, but ignored by this code |
|
44 |
CUE_FILE, |
|
45 |
CUE_TRACK, |
|
46 |
CUE_INDEX, |
|
47 |
CUE_TITLE, |
|
48 |
};
|
|
49 |
||
50 |
static const struct { |
|
51 |
enum cue_command command; |
|
52 |
const char *text; |
|
53 |
} cue_command_strings[] = { |
|
54 |
{ CUE_FILE, "FILE" }, |
|
55 |
{ CUE_TRACK, "TRACK" }, |
|
56 |
{ CUE_INDEX, "INDEX" }, |
|
57 |
{ CUE_TITLE, "TITLE" }, |
|
58 |
{ CUE_UNUSED, "CATALOG" }, |
|
59 |
{ CUE_UNUSED, "CDTEXTFILE" }, |
|
60 |
{ CUE_UNUSED, "FLAGS" }, |
|
61 |
{ CUE_UNUSED, "ISRC" }, |
|
62 |
{ CUE_UNUSED, "PERFORMER" }, |
|
63 |
{ CUE_UNUSED, "POSTGAP" }, |
|
64 |
{ CUE_UNUSED, "PREGAP" }, |
|
65 |
{ CUE_UNUSED, "REM" }, |
|
66 |
{ CUE_UNUSED, "SONGWRITER" }, |
|
67 |
{ CUE_UNUSED, "MESSAGE" }, |
|
68 |
{ -1 }, |
|
69 |
};
|
|
70 |
||
71 |
struct cue_track { |
|
72 |
double pregap_start; // corresponds to INDEX 00 |
|
73 |
double start; // corresponds to INDEX 01 |
|
74 |
struct bstr filename; |
|
75 |
int source; |
|
76 |
struct bstr title; |
|
77 |
};
|
|
78 |
||
79 |
static enum cue_command read_cmd(struct bstr *data, struct bstr *out_params) |
|
80 |
{
|
|
81 |
struct bstr line = bstr_strip_linebreaks(bstr_getline(*data, data)); |
|
82 |
line = bstr_lstrip(line); |
|
83 |
if (line.len == 0) |
|
84 |
return CUE_EMPTY; |
|
85 |
for (int n = 0; cue_command_strings[n].command != -1; n++) { |
|
86 |
struct bstr name = bstr0(cue_command_strings[n].text); |
|
87 |
if (bstr_startswith(line, name)) { |
|
88 |
struct bstr rest = bstr_cut(line, name.len); |
|
89 |
if (rest.len && !strchr(WHITESPACE, rest.start[0])) |
|
90 |
continue; |
|
91 |
if (out_params) |
|
92 |
*out_params = rest; |
|
93 |
return cue_command_strings[n].command; |
|
94 |
}
|
|
95 |
}
|
|
96 |
return CUE_ERROR; |
|
97 |
}
|
|
98 |
||
99 |
static bool eat_char(struct bstr *data, char ch) |
|
100 |
{
|
|
101 |
if (data->len && data->start[0] == ch) { |
|
102 |
*data = bstr_cut(*data, 1); |
|
103 |
return true; |
|
104 |
} else { |
|
105 |
return false; |
|
106 |
}
|
|
107 |
}
|
|
108 |
||
109 |
static struct bstr read_quoted(struct bstr *data) |
|
110 |
{
|
|
111 |
*data = bstr_lstrip(*data); |
|
112 |
if (!eat_char(data, '"')) |
|
113 |
return (struct bstr) {0}; |
|
114 |
int end = bstrchr(*data, '"'); |
|
115 |
if (end < 0) |
|
116 |
return (struct bstr) {0}; |
|
117 |
struct bstr res = bstr_splice(*data, 0, end); |
|
118 |
*data = bstr_cut(*data, end + 1); |
|
119 |
return res; |
|
120 |
}
|
|
121 |
||
122 |
// Read a 2 digit unsigned decimal integer.
|
|
123 |
// Return -1 on failure.
|
|
124 |
static int read_int_2(struct bstr *data) |
|
125 |
{
|
|
126 |
*data = bstr_lstrip(*data); |
|
127 |
if (data->len && data->start[0] == '-') |
|
128 |
return -1; |
|
129 |
struct bstr s = *data; |
|
130 |
int res = (int)bstrtoll(s, &s, 10); |
|
131 |
if (data->len == s.len || data->len - s.len > 2) |
|
132 |
return -1; |
|
133 |
*data = s; |
|
134 |
return res; |
|
135 |
}
|
|
136 |
||
137 |
static double read_time(struct bstr *data) |
|
138 |
{
|
|
139 |
struct bstr s = *data; |
|
140 |
bool ok = true; |
|
141 |
double t1 = read_int_2(&s); |
|
142 |
ok = eat_char(&s, ':') && ok; |
|
143 |
double t2 = read_int_2(&s); |
|
144 |
ok = eat_char(&s, ':') && ok; |
|
145 |
double t3 = read_int_2(&s); |
|
146 |
ok = ok && t1 >= 0 && t2 >= 0 && t3 >= 0; |
|
147 |
return ok ? t1 * 60.0 + t2 + t3 * SECS_PER_CUE_FRAME : 0; |
|
148 |
}
|
|
149 |
||
150 |
static struct bstr skip_utf8_bom(struct bstr data) |
|
151 |
{
|
|
152 |
return bstr_startswith0(data, "\xEF\xBB\xBF") ? bstr_cut(data, 3) : data; |
|
153 |
}
|
|
154 |
||
155 |
// Check if the text in data is most likely CUE data. This is used by the
|
|
156 |
// demuxer code to check the file type.
|
|
157 |
// data is the start of the probed file, possibly cut off at a random point.
|
|
158 |
bool mp_probe_cue(struct bstr data) |
|
159 |
{
|
|
160 |
bool valid = false; |
|
161 |
data = skip_utf8_bom(data); |
|
162 |
for (;;) { |
|
163 |
enum cue_command cmd = read_cmd(&data, NULL); |
|
164 |
// End reached. Since the line was most likely cut off, don't use the
|
|
165 |
// result of the last parsing call.
|
|
166 |
if (data.len == 0) |
|
167 |
break; |
|
168 |
if (cmd == CUE_ERROR) |
|
169 |
return false; |
|
170 |
if (cmd != CUE_EMPTY) |
|
171 |
valid = true; |
|
172 |
}
|
|
173 |
return valid; |
|
174 |
}
|
|
175 |
||
176 |
static void add_source(struct MPContext *mpctx, struct demuxer *d) |
|
177 |
{
|
|
178 |
MP_TARRAY_APPEND(NULL, mpctx->sources, mpctx->num_sources, d); |
|
179 |
}
|
|
180 |
||
181 |
static bool try_open(struct MPContext *mpctx, char *filename) |
|
182 |
{
|
|
183 |
struct bstr bfilename = bstr0(filename); |
|
184 |
// Avoid trying to open itself or another .cue file. Best would be
|
|
185 |
// to check the result of demuxer auto-detection, but the demuxer
|
|
186 |
// API doesn't allow this without opening a full demuxer.
|
|
187 |
if (bstr_case_endswith(bfilename, bstr0(".cue")) |
|
188 |
|| bstrcasecmp(bstr0(mpctx->demuxer->filename), bfilename) == 0) |
|
189 |
return false; |
|
190 |
||
191 |
struct stream *s = stream_open(filename, mpctx->opts); |
|
192 |
if (!s) |
|
193 |
return false; |
|
194 |
struct demuxer *d = demux_open(s, NULL, NULL, mpctx->opts); |
|
195 |
// Since .bin files are raw PCM data with no headers, we have to explicitly
|
|
196 |
// open them. Also, try to avoid to open files that are most likely not .bin
|
|
197 |
// files, as that would only play noise. Checking the file extension is
|
|
198 |
// fragile, but it's about the only way we have.
|
|
199 |
// TODO: maybe also could check if the .bin file is a multiple of the Audio
|
|
200 |
// CD sector size (2352 bytes)
|
|
201 |
if (!d && bstr_case_endswith(bfilename, bstr0(".bin"))) { |
|
202 |
mp_msg(MSGT_CPLAYER, MSGL_WARN, "CUE: Opening as BIN file!\n"); |
|
203 |
d = demux_open(s, "rawaudio", NULL, mpctx->opts); |
|
204 |
}
|
|
205 |
if (d) { |
|
206 |
add_source(mpctx, d); |
|
207 |
return true; |
|
208 |
}
|
|
209 |
mp_msg(MSGT_CPLAYER, MSGL_ERR, "Could not open source '%s'!\n", filename); |
|
210 |
free_stream(s); |
|
211 |
return false; |
|
212 |
}
|
|
213 |
||
214 |
static bool open_source(struct MPContext *mpctx, struct bstr filename) |
|
215 |
{
|
|
216 |
void *ctx = talloc_new(NULL); |
|
217 |
bool res = false; |
|
218 |
||
219 |
struct bstr dirname = mp_dirname(mpctx->demuxer->filename); |
|
220 |
||
221 |
struct bstr base_filename = bstr0(mp_basename(bstrdup0(ctx, filename))); |
|
222 |
if (!base_filename.len) { |
|
223 |
mp_msg(MSGT_CPLAYER, MSGL_WARN, |
|
224 |
"CUE: Invalid audio filename in .cue file!\n"); |
|
225 |
} else { |
|
226 |
char *fullname = mp_path_join(ctx, dirname, base_filename); |
|
227 |
if (try_open(mpctx, fullname)) { |
|
228 |
res = true; |
|
229 |
goto out; |
|
230 |
}
|
|
231 |
}
|
|
232 |
||
233 |
// Try an audio file with the same name as the .cue file (but different
|
|
234 |
// extension).
|
|
235 |
// Rationale: this situation happens easily if the audio file or both files
|
|
236 |
// are renamed.
|
|
237 |
||
238 |
struct bstr cuefile = |
|
239 |
bstr_strip_ext(bstr0(mp_basename(mpctx->demuxer->filename))); |
|
240 |
||
241 |
DIR *d = opendir(bstrdup0(ctx, dirname)); |
|
242 |
if (!d) |
|
243 |
goto out; |
|
244 |
struct dirent *de; |
|
245 |
while ((de = readdir(d))) { |
|
246 |
char *dename0 = de->d_name; |
|
247 |
struct bstr dename = bstr0(dename0); |
|
248 |
if (bstr_case_startswith(dename, cuefile)) { |
|
249 |
mp_msg(MSGT_CPLAYER, MSGL_WARN, "CUE: No useful audio filename " |
|
250 |
"in .cue file found, trying with '%s' instead!\n", |
|
251 |
dename0); |
|
252 |
if (try_open(mpctx, mp_path_join(ctx, dirname, dename))) { |
|
253 |
res = true; |
|
254 |
break; |
|
255 |
}
|
|
256 |
}
|
|
257 |
}
|
|
258 |
closedir(d); |
|
259 |
||
260 |
out: |
|
261 |
talloc_free(ctx); |
|
262 |
if (!res) |
|
263 |
mp_msg(MSGT_CPLAYER, MSGL_ERR, "CUE: Could not open audio file!\n"); |
|
264 |
return res; |
|
265 |
}
|
|
266 |
||
267 |
// return length of the source in seconds, or -1 if unknown
|
|
268 |
static double source_get_length(struct demuxer *demuxer) |
|
269 |
{
|
|
270 |
double get_time_ans; |
|
271 |
// <= 0 means DEMUXER_CTRL_NOTIMPL or DEMUXER_CTRL_DONTKNOW
|
|
272 |
if (demuxer && demux_control(demuxer, DEMUXER_CTRL_GET_TIME_LENGTH, |
|
273 |
(void *) &get_time_ans) > 0) |
|
274 |
{
|
|
275 |
return get_time_ans; |
|
276 |
} else { |
|
277 |
return -1; |
|
278 |
}
|
|
279 |
}
|
|
280 |
||
281 |
void build_cue_timeline(struct MPContext *mpctx) |
|
282 |
{
|
|
283 |
void *ctx = talloc_new(NULL); |
|
284 |
||
285 |
struct bstr data = mpctx->demuxer->file_contents; |
|
286 |
data = skip_utf8_bom(data); |
|
287 |
||
288 |
struct cue_track *tracks = NULL; |
|
289 |
size_t track_count = 0; |
|
290 |
||
291 |
struct bstr filename = {0}; |
|
292 |
// Global metadata, and copied into new tracks.
|
|
293 |
struct cue_track proto_track = {0}; |
|
294 |
struct cue_track *cur_track = &proto_track; |
|
295 |
||
296 |
while (data.len) { |
|
297 |
struct bstr param; |
|
298 |
switch (read_cmd(&data, ¶m)) { |
|
299 |
case CUE_ERROR: |
|
300 |
mp_msg(MSGT_CPLAYER, MSGL_ERR, "CUE: error parsing input file!\n"); |
|
301 |
goto out; |
|
302 |
case CUE_TRACK: { |
|
303 |
track_count++; |
|
304 |
tracks = talloc_realloc(ctx, tracks, struct cue_track, track_count); |
|
305 |
cur_track = &tracks[track_count - 1]; |
|
306 |
*cur_track = proto_track; |
|
307 |
break; |
|
308 |
}
|
|
309 |
case CUE_TITLE: |
|
310 |
cur_track->title = read_quoted(¶m); |
|
311 |
break; |
|
312 |
case CUE_INDEX: { |
|
313 |
int type = read_int_2(¶m); |
|
314 |
double time = read_time(¶m); |
|
315 |
if (type == 1) { |
|
316 |
cur_track->start = time; |
|
317 |
cur_track->filename = filename; |
|
318 |
} else if (type == 0) { |
|
319 |
cur_track->pregap_start = time; |
|
320 |
}
|
|
321 |
break; |
|
322 |
}
|
|
323 |
case CUE_FILE: |
|
324 |
// NOTE: FILE comes before TRACK, so don't use cur_track->filename
|
|
325 |
filename = read_quoted(¶m); |
|
326 |
break; |
|
327 |
}
|
|
328 |
}
|
|
329 |
||
330 |
if (track_count == 0) { |
|
331 |
mp_msg(MSGT_CPLAYER, MSGL_ERR, "CUE: no tracks found!\n"); |
|
332 |
goto out; |
|
333 |
}
|
|
334 |
||
335 |
// Remove duplicate file entries. This might be too sophisticated, since
|
|
336 |
// CUE files usually use either separate files for every single track, or
|
|
337 |
// only one file for all tracks.
|
|
338 |
||
339 |
struct bstr *files = 0; |
|
340 |
size_t file_count = 0; |
|
341 |
||
342 |
for (size_t n = 0; n < track_count; n++) { |
|
343 |
struct cue_track *track = &tracks[n]; |
|
344 |
track->source = -1; |
|
345 |
for (size_t file = 0; file < file_count; file++) { |
|
346 |
if (bstrcmp(files[file], track->filename) == 0) { |
|
347 |
track->source = file; |
|
348 |
break; |
|
349 |
}
|
|
350 |
}
|
|
351 |
if (track->source == -1) { |
|
352 |
file_count++; |
|
353 |
files = talloc_realloc(ctx, files, struct bstr, file_count); |
|
354 |
files[file_count - 1] = track->filename; |
|
355 |
track->source = file_count - 1; |
|
356 |
}
|
|
357 |
}
|
|
358 |
||
359 |
for (size_t i = 0; i < file_count; i++) { |
|
360 |
if (!open_source(mpctx, files[i])) |
|
361 |
goto out; |
|
362 |
}
|
|
363 |
||
364 |
struct timeline_part *timeline = talloc_array_ptrtype(NULL, timeline, |
|
365 |
track_count + 1); |
|
366 |
struct chapter *chapters = talloc_array_ptrtype(NULL, chapters, |
|
367 |
track_count); |
|
368 |
double starttime = 0; |
|
369 |
for (int i = 0; i < track_count; i++) { |
|
370 |
struct demuxer *source = mpctx->sources[1 + tracks[i].source]; |
|
371 |
double duration; |
|
372 |
if (i + 1 < track_count && tracks[i].source == tracks[i + 1].source) { |
|
373 |
duration = tracks[i + 1].start - tracks[i].start; |
|
374 |
} else { |
|
375 |
duration = source_get_length(source); |
|
376 |
// Two cases: 1) last track of a single-file cue, or 2) any track of
|
|
377 |
// a multi-file cue. We need to do this for 1) only because the
|
|
378 |
// timeline needs to be terminated with the length of the last
|
|
379 |
// track.
|
|
380 |
duration -= tracks[i].start; |
|
381 |
}
|
|
382 |
if (duration < 0) { |
|
383 |
mp_msg(MSGT_CPLAYER, MSGL_WARN, |
|
384 |
"CUE: Can't get duration of source file!\n"); |
|
385 |
// xxx: do something more reasonable
|
|
386 |
duration = 0.0; |
|
387 |
}
|
|
388 |
timeline[i] = (struct timeline_part) { |
|
389 |
.start = starttime, |
|
390 |
.source_start = tracks[i].start, |
|
391 |
.source = source, |
|
392 |
};
|
|
393 |
chapters[i] = (struct chapter) { |
|
394 |
.start = timeline[i].start, |
|
395 |
// might want to include other metadata here
|
|
396 |
.name = bstrdup0(chapters, tracks[i].title), |
|
397 |
};
|
|
398 |
starttime += duration; |
|
399 |
}
|
|
400 |
||
401 |
// apparently we need this to give the last part a non-zero length
|
|
402 |
timeline[track_count] = (struct timeline_part) { |
|
403 |
.start = starttime, |
|
404 |
// perhaps unused by the timeline code
|
|
405 |
.source_start = 0, |
|
406 |
.source = timeline[0].source, |
|
407 |
};
|
|
408 |
||
409 |
mpctx->timeline = timeline; |
|
410 |
// the last part is not included it in the count
|
|
411 |
mpctx->num_timeline_parts = track_count + 1 - 1; |
|
412 |
mpctx->chapters = chapters; |
|
413 |
mpctx->num_chapters = track_count; |
|
414 |
||
415 |
out: |
|
416 |
talloc_free(ctx); |
|
417 |
}
|