Line | Count | Source |
1 | | /* |
2 | | * This file is part of mpv. |
3 | | * |
4 | | * mpv is free software; you can redistribute it and/or |
5 | | * modify it under the terms of the GNU Lesser General Public |
6 | | * License as published by the Free Software Foundation; either |
7 | | * version 2.1 of the License, or (at your option) any later version. |
8 | | * |
9 | | * mpv 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 Lesser General Public License for more details. |
13 | | * |
14 | | * You should have received a copy of the GNU Lesser General Public |
15 | | * License along with mpv. If not, see <http://www.gnu.org/licenses/>. |
16 | | */ |
17 | | |
18 | | #include <stdlib.h> |
19 | | #include <stdbool.h> |
20 | | #include <string.h> |
21 | | #include <inttypes.h> |
22 | | |
23 | | #include "mpv_talloc.h" |
24 | | |
25 | | #include "misc/bstr.h" |
26 | | #include "common/common.h" |
27 | | #include "common/tags.h" |
28 | | |
29 | | #include "cue.h" |
30 | | |
31 | 0 | #define SECS_PER_CUE_FRAME (1.0/75.0) |
32 | | |
33 | | enum cue_command { |
34 | | CUE_ERROR = -1, // not a valid CUE command, or an unknown extension |
35 | | CUE_EMPTY, // line with whitespace only |
36 | | CUE_UNUSED, // valid CUE command, but ignored by this code |
37 | | CUE_FILE, |
38 | | CUE_TRACK, |
39 | | CUE_INDEX, |
40 | | CUE_TITLE, |
41 | | CUE_PERFORMER, |
42 | | }; |
43 | | |
44 | | static const struct { |
45 | | enum cue_command command; |
46 | | const char *text; |
47 | | } cue_command_strings[] = { |
48 | | { CUE_FILE, "FILE" }, |
49 | | { CUE_TRACK, "TRACK" }, |
50 | | { CUE_INDEX, "INDEX" }, |
51 | | { CUE_TITLE, "TITLE" }, |
52 | | { CUE_UNUSED, "CATALOG" }, |
53 | | { CUE_UNUSED, "CDTEXTFILE" }, |
54 | | { CUE_UNUSED, "FLAGS" }, |
55 | | { CUE_UNUSED, "ISRC" }, |
56 | | { CUE_PERFORMER, "PERFORMER" }, |
57 | | { CUE_UNUSED, "POSTGAP" }, |
58 | | { CUE_UNUSED, "PREGAP" }, |
59 | | { CUE_UNUSED, "REM" }, |
60 | | { CUE_UNUSED, "SONGWRITER" }, |
61 | | { CUE_UNUSED, "MESSAGE" }, |
62 | | { CUE_UNUSED, "MCN" }, |
63 | | { -1 }, |
64 | | }; |
65 | | |
66 | | static const uint8_t spaces[] = {' ', '\f', '\n', '\r', '\t', '\v', 0xA0}; |
67 | | |
68 | | static struct bstr lstrip_whitespace(struct bstr data) |
69 | 232k | { |
70 | 573k | while (data.len) { |
71 | 531k | bstr rest = data; |
72 | 531k | int code = bstr_decode_utf8(data, &rest); |
73 | 531k | if (code < 0) { |
74 | | // Tolerate Latin1 => probing works (which doesn't convert charsets). |
75 | 27.2k | code = data.start[0]; |
76 | 27.2k | rest.start += 1; |
77 | 27.2k | rest.len -= 1; |
78 | 27.2k | } |
79 | 3.13M | for (size_t n = 0; n < MP_ARRAY_SIZE(spaces); n++) { |
80 | 2.94M | if (spaces[n] == code) { |
81 | 341k | data = rest; |
82 | 341k | goto next; |
83 | 341k | } |
84 | 2.94M | } |
85 | 190k | break; |
86 | 341k | next: ; |
87 | 341k | } |
88 | 232k | return data; |
89 | 232k | } |
90 | | |
91 | | static enum cue_command read_cmd(struct bstr *data, struct bstr *out_params) |
92 | 192k | { |
93 | 192k | struct bstr line = bstr_strip_linebreaks(bstr_getline(*data, data)); |
94 | 192k | line = lstrip_whitespace(line); |
95 | 192k | if (line.len == 0) |
96 | 39.0k | return CUE_EMPTY; |
97 | 2.15M | for (int n = 0; cue_command_strings[n].command != -1; n++) { |
98 | 2.02M | struct bstr name = bstr0(cue_command_strings[n].text); |
99 | 2.02M | if (bstr_case_startswith(line, name)) { |
100 | 22.4k | struct bstr rest = bstr_cut(line, name.len); |
101 | 22.4k | struct bstr par = lstrip_whitespace(rest); |
102 | 22.4k | if (rest.len && par.len == rest.len) |
103 | 188 | continue; |
104 | 22.2k | if (out_params) |
105 | 17.8k | *out_params = par; |
106 | 22.2k | return cue_command_strings[n].command; |
107 | 22.4k | } |
108 | 2.02M | } |
109 | 130k | return CUE_ERROR; |
110 | 153k | } |
111 | | |
112 | | static bool eat_char(struct bstr *data, char ch) |
113 | 12.7k | { |
114 | 12.7k | if (data->len && data->start[0] == ch) { |
115 | 3.10k | *data = bstr_cut(*data, 1); |
116 | 3.10k | return true; |
117 | 9.64k | } else { |
118 | 9.64k | return false; |
119 | 9.64k | } |
120 | 12.7k | } |
121 | | |
122 | | static char *read_quoted(void *talloc_ctx, struct bstr *data) |
123 | 7.56k | { |
124 | 7.56k | *data = lstrip_whitespace(*data); |
125 | 7.56k | if (!eat_char(data, '"')) |
126 | 6.93k | return NULL; |
127 | 636 | int end = bstrchr(*data, '"'); |
128 | 636 | if (end < 0) |
129 | 259 | return NULL; |
130 | 377 | struct bstr res = bstr_splice(*data, 0, end); |
131 | 377 | *data = bstr_cut(*data, end + 1); |
132 | 377 | return bstrto0(talloc_ctx, res); |
133 | 636 | } |
134 | | |
135 | | static struct bstr strip_quotes(struct bstr data) |
136 | 3.15k | { |
137 | 3.15k | bstr s = data; |
138 | 3.15k | if (bstr_eatstart0(&s, "\"") && bstr_eatend0(&s, "\"")) |
139 | 914 | return s; |
140 | 2.24k | return data; |
141 | 3.15k | } |
142 | | |
143 | | // Read an unsigned decimal integer. |
144 | | // Optionally check if it is 2 digit. |
145 | | // Return -1 on failure. |
146 | | static int read_int(struct bstr *data, bool two_digit) |
147 | 10.3k | { |
148 | 10.3k | *data = lstrip_whitespace(*data); |
149 | 10.3k | if (data->len && data->start[0] == '-') |
150 | 735 | return -1; |
151 | 9.62k | struct bstr s = *data; |
152 | 9.62k | int res = (int)bstrtoll(s, &s, 10); |
153 | 9.62k | if (data->len == s.len || (two_digit && data->len - s.len > 2)) |
154 | 7.25k | return -1; |
155 | 2.37k | *data = s; |
156 | 2.37k | return res; |
157 | 9.62k | } |
158 | | |
159 | | static double read_time(struct bstr *data) |
160 | 2.59k | { |
161 | 2.59k | struct bstr s = *data; |
162 | 2.59k | bool ok = true; |
163 | 2.59k | double t1 = read_int(&s, false); |
164 | 2.59k | ok = eat_char(&s, ':') && ok; |
165 | 2.59k | double t2 = read_int(&s, true); |
166 | 2.59k | ok = eat_char(&s, ':') && ok; |
167 | 2.59k | double t3 = read_int(&s, true); |
168 | 2.59k | ok = ok && t1 >= 0 && t2 >= 0 && t3 >= 0; |
169 | 2.59k | return ok ? t1 * 60.0 + t2 + t3 * SECS_PER_CUE_FRAME : 0; |
170 | 2.59k | } |
171 | | |
172 | | static struct bstr skip_utf8_bom(struct bstr data) |
173 | 131k | { |
174 | 131k | return bstr_startswith0(data, "\xEF\xBB\xBF") ? bstr_cut(data, 3) : data; |
175 | 131k | } |
176 | | |
177 | | // Check if the text in data is most likely CUE data. This is used by the |
178 | | // demuxer code to check the file type. |
179 | | // data is the start of the probed file, possibly cut off at a random point. |
180 | | bool mp_probe_cue(struct bstr data) |
181 | 130k | { |
182 | 130k | bool valid = false; |
183 | 130k | data = skip_utf8_bom(data); |
184 | 159k | for (;;) { |
185 | 159k | enum cue_command cmd = read_cmd(&data, NULL); |
186 | | // End reached. Since the line was most likely cut off, don't use the |
187 | | // result of the last parsing call. |
188 | 159k | if (data.len == 0) |
189 | 81.8k | break; |
190 | 78.0k | if (cmd == CUE_ERROR) |
191 | 49.0k | return false; |
192 | 29.0k | if (cmd != CUE_EMPTY) |
193 | 4.01k | valid = true; |
194 | 29.0k | } |
195 | 81.8k | return valid; |
196 | 130k | } |
197 | | |
198 | | struct cue_file *mp_parse_cue(struct bstr data) |
199 | 597 | { |
200 | 597 | struct cue_file *f = talloc_zero(NULL, struct cue_file); |
201 | 597 | f->tags = talloc_zero(f, struct mp_tags); |
202 | | |
203 | 597 | data = skip_utf8_bom(data); |
204 | | |
205 | 597 | char *filename = NULL; |
206 | | // Global metadata, and copied into new tracks. |
207 | 597 | struct cue_track proto_track = {0}; |
208 | 597 | struct cue_track *cur_track = NULL; |
209 | | |
210 | 32.3k | while (data.len) { |
211 | 32.2k | struct bstr param; |
212 | 32.2k | int cmd = read_cmd(&data, ¶m); |
213 | 32.2k | switch (cmd) { |
214 | 491 | case CUE_ERROR: |
215 | 491 | talloc_free(f); |
216 | 491 | return NULL; |
217 | 1.93k | case CUE_TRACK: { |
218 | 1.93k | if (bstr_find0(param, "AUDIO") == -1) |
219 | 1.16k | break; |
220 | 773 | MP_TARRAY_GROW(f, f->tracks, f->num_tracks); |
221 | 773 | f->num_tracks += 1; |
222 | 773 | cur_track = &f->tracks[f->num_tracks - 1]; |
223 | 773 | *cur_track = proto_track; |
224 | 773 | cur_track->tags = talloc_zero(f, struct mp_tags); |
225 | 773 | break; |
226 | 1.93k | } |
227 | 3.15k | case CUE_TITLE: |
228 | 3.15k | case CUE_PERFORMER: { |
229 | 3.15k | static const char *metanames[] = { |
230 | 3.15k | [CUE_TITLE] = "title", |
231 | 3.15k | [CUE_PERFORMER] = "performer", |
232 | 3.15k | }; |
233 | 3.15k | struct mp_tags *tags = cur_track ? cur_track->tags : f->tags; |
234 | 3.15k | mp_tags_set_bstr(tags, bstr0(metanames[cmd]), strip_quotes(param)); |
235 | 3.15k | break; |
236 | 3.15k | } |
237 | 2.59k | case CUE_INDEX: { |
238 | 2.59k | int type = read_int(¶m, true); |
239 | 2.59k | double time = read_time(¶m); |
240 | 2.59k | if (cur_track) { |
241 | 50 | if (type == 1) { |
242 | 7 | cur_track->start = time; |
243 | 7 | cur_track->filename = filename; |
244 | 43 | } else if (type == 0) { |
245 | 3 | cur_track->pregap_start = time; |
246 | 3 | } |
247 | 50 | } |
248 | 2.59k | break; |
249 | 3.15k | } |
250 | 7.56k | case CUE_FILE: |
251 | | // NOTE: FILE comes before TRACK, so don't use cur_track->filename |
252 | 7.56k | filename = read_quoted(f, ¶m); |
253 | 7.56k | break; |
254 | 32.2k | } |
255 | 32.2k | } |
256 | | |
257 | 106 | return f; |
258 | 597 | } |
259 | | |
260 | | int mp_check_embedded_cue(struct cue_file *f) |
261 | 2 | { |
262 | 2 | if (f->num_tracks == 0) |
263 | 2 | return -1; |
264 | 0 | char *fn0 = f->tracks[0].filename; |
265 | 0 | for (int n = 1; n < f->num_tracks; n++) { |
266 | 0 | char *fn = f->tracks[n].filename; |
267 | | // both filenames have the same address (including NULL) |
268 | 0 | if (fn0 == fn) |
269 | 0 | continue; |
270 | | // only one filename is NULL, or the strings don't match |
271 | 0 | if (!fn0 || !fn || strcmp(fn0, fn) != 0) |
272 | 0 | return -1; |
273 | 0 | } |
274 | 0 | return 0; |
275 | 0 | } |