ce188d4d |
1 | /* grabbag - Convenience lib for various routines common to several tools |
2 | * Copyright (C) 2002-2009 Josh Coalson |
3 | * Copyright (C) 2011-2016 Xiph.Org Foundation |
4 | * |
5 | * This library is free software; you can redistribute it and/or |
6 | * modify it under the terms of the GNU Lesser General Public |
7 | * License as published by the Free Software Foundation; either |
8 | * version 2.1 of the License, or (at your option) any later version. |
9 | * |
10 | * This library is distributed in the hope that it will be useful, |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
13 | * Lesser General Public License for more details. |
14 | * |
15 | * You should have received a copy of the GNU Lesser General Public |
16 | * License along with this library; if not, write to the Free Software |
17 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
18 | */ |
19 | |
20 | #ifdef HAVE_CONFIG_H |
21 | # include <config.h> |
22 | #endif |
23 | |
24 | #include <stdio.h> |
25 | #include <stdlib.h> |
26 | #include <string.h> |
27 | #include "FLAC/assert.h" |
28 | #include "share/compat.h" |
29 | #include "share/grabbag.h" |
30 | #include "share/safe_str.h" |
31 | |
32 | unsigned grabbag__cuesheet_msf_to_frame(unsigned minutes, unsigned seconds, unsigned frames) |
33 | { |
34 | return ((minutes * 60) + seconds) * 75 + frames; |
35 | } |
36 | |
37 | void grabbag__cuesheet_frame_to_msf(unsigned frame, unsigned *minutes, unsigned *seconds, unsigned *frames) |
38 | { |
39 | *frames = frame % 75; |
40 | frame /= 75; |
41 | *seconds = frame % 60; |
42 | frame /= 60; |
43 | *minutes = frame; |
44 | } |
45 | |
46 | /* since we only care about values >= 0 or error, returns < 0 for any illegal string, else value */ |
47 | static int local__parse_int_(const char *s) |
48 | { |
49 | int ret = 0; |
50 | char c; |
51 | |
52 | if(*s == '\0') |
53 | return -1; |
54 | |
55 | while('\0' != (c = *s++)) |
56 | if(c >= '0' && c <= '9') |
57 | ret = ret * 10 + (c - '0'); |
58 | else |
59 | return -1; |
60 | |
61 | return ret; |
62 | } |
63 | |
64 | /* since we only care about values >= 0 or error, returns < 0 for any illegal string, else value */ |
65 | static FLAC__int64 local__parse_int64_(const char *s) |
66 | { |
67 | FLAC__int64 ret = 0; |
68 | char c; |
69 | |
70 | if(*s == '\0') |
71 | return -1; |
72 | |
73 | while('\0' != (c = *s++)) |
74 | if(c >= '0' && c <= '9') |
75 | ret = ret * 10 + (c - '0'); |
76 | else |
77 | return -1; |
78 | |
79 | return ret; |
80 | } |
81 | |
82 | /* accept minute:second:frame syntax of '[0-9]+:[0-9][0-9]?:[0-9][0-9]?', but max second of 59 and max frame of 74, e.g. 0:0:0, 123:45:67 |
83 | * return sample number or <0 for error |
84 | * WATCHOUT: if sample rate is not evenly divisible by 75, the resulting sample number will be approximate |
85 | */ |
86 | static FLAC__int64 local__parse_msf_(const char *s, unsigned sample_rate) |
87 | { |
88 | FLAC__int64 ret, field; |
89 | char c; |
90 | |
91 | c = *s++; |
92 | if(c >= '0' && c <= '9') |
93 | field = (c - '0'); |
94 | else |
95 | return -1; |
96 | while(':' != (c = *s++)) { |
97 | if(c >= '0' && c <= '9') |
98 | field = field * 10 + (c - '0'); |
99 | else |
100 | return -1; |
101 | } |
102 | |
103 | ret = field * 60 * sample_rate; |
104 | |
105 | c = *s++; |
106 | if(c >= '0' && c <= '9') |
107 | field = (c - '0'); |
108 | else |
109 | return -1; |
110 | if(':' != (c = *s++)) { |
111 | if(c >= '0' && c <= '9') { |
112 | field = field * 10 + (c - '0'); |
113 | c = *s++; |
114 | if(c != ':') |
115 | return -1; |
116 | } |
117 | else |
118 | return -1; |
119 | } |
120 | |
121 | if(field >= 60) |
122 | return -1; |
123 | |
124 | ret += field * sample_rate; |
125 | |
126 | c = *s++; |
127 | if(c >= '0' && c <= '9') |
128 | field = (c - '0'); |
129 | else |
130 | return -1; |
131 | if('\0' != (c = *s++)) { |
132 | if(c >= '0' && c <= '9') { |
133 | field = field * 10 + (c - '0'); |
134 | c = *s++; |
135 | } |
136 | else |
137 | return -1; |
138 | } |
139 | |
140 | if(c != '\0') |
141 | return -1; |
142 | |
143 | if(field >= 75) |
144 | return -1; |
145 | |
146 | ret += field * (sample_rate / 75); |
147 | |
148 | return ret; |
149 | } |
150 | |
151 | /* accept minute:second syntax of '[0-9]+:[0-9][0-9]?{,.[0-9]+}', but second < 60, e.g. 0:0.0, 3:5, 15:31.731 |
152 | * return sample number or <0 for error |
153 | * WATCHOUT: depending on the sample rate, the resulting sample number may be approximate with fractional seconds |
154 | */ |
155 | static FLAC__int64 local__parse_ms_(const char *s, unsigned sample_rate) |
156 | { |
157 | FLAC__int64 ret, field; |
158 | double x; |
159 | char c, *end; |
160 | |
161 | c = *s++; |
162 | if(c >= '0' && c <= '9') |
163 | field = (c - '0'); |
164 | else |
165 | return -1; |
166 | while(':' != (c = *s++)) { |
167 | if(c >= '0' && c <= '9') |
168 | field = field * 10 + (c - '0'); |
169 | else |
170 | return -1; |
171 | } |
172 | |
173 | ret = field * 60 * sample_rate; |
174 | |
175 | s++; /* skip the ':' */ |
176 | if(strspn(s, "0123456789.") != strlen(s)) |
177 | return -1; |
178 | x = strtod(s, &end); |
179 | if(*end || end == s) |
180 | return -1; |
181 | if(x < 0.0 || x >= 60.0) |
182 | return -1; |
183 | |
184 | ret += (FLAC__int64)(x * sample_rate); |
185 | |
186 | return ret; |
187 | } |
188 | |
189 | static char *local__get_field_(char **s, FLAC__bool allow_quotes) |
190 | { |
191 | FLAC__bool has_quote = false; |
192 | char *p; |
193 | |
194 | FLAC__ASSERT(0 != s); |
195 | |
196 | if(0 == *s) |
197 | return 0; |
198 | |
199 | /* skip leading whitespace */ |
200 | while(**s && 0 != strchr(" \t\r\n", **s)) |
201 | (*s)++; |
202 | |
203 | if(**s == 0) { |
204 | *s = 0; |
205 | return 0; |
206 | } |
207 | |
208 | if(allow_quotes && (**s == '"')) { |
209 | has_quote = true; |
210 | (*s)++; |
211 | if(**s == 0) { |
212 | *s = 0; |
213 | return 0; |
214 | } |
215 | } |
216 | |
217 | p = *s; |
218 | |
219 | if(has_quote) { |
220 | *s = strchr(*s, '\"'); |
221 | /* if there is no matching end quote, it's an error */ |
222 | if(0 == *s) |
223 | p = *s = 0; |
224 | else { |
225 | **s = '\0'; |
226 | (*s)++; |
227 | } |
228 | } |
229 | else { |
230 | while(**s && 0 == strchr(" \t\r\n", **s)) |
231 | (*s)++; |
232 | if(**s) { |
233 | **s = '\0'; |
234 | (*s)++; |
235 | } |
236 | else |
237 | *s = 0; |
238 | } |
239 | |
240 | return p; |
241 | } |
242 | |
243 | static FLAC__bool local__cuesheet_parse_(FILE *file, const char **error_message, unsigned *last_line_read, FLAC__StreamMetadata *cuesheet, unsigned sample_rate, FLAC__bool is_cdda, FLAC__uint64 lead_out_offset) |
244 | { |
245 | char buffer[4096], *line, *field; |
246 | unsigned forced_leadout_track_num = 0; |
247 | FLAC__uint64 forced_leadout_track_offset = 0; |
248 | int in_track_num = -1, in_index_num = -1; |
249 | FLAC__bool disc_has_catalog = false, track_has_flags = false, track_has_isrc = false, has_forced_leadout = false; |
250 | FLAC__StreamMetadata_CueSheet *cs = &cuesheet->data.cue_sheet; |
251 | |
252 | FLAC__ASSERT(!is_cdda || sample_rate == 44100); |
253 | /* double protection */ |
254 | if(is_cdda && sample_rate != 44100) { |
255 | *error_message = "CD-DA cuesheet only allowed with 44.1kHz sample rate"; |
256 | return false; |
257 | } |
258 | |
259 | cs->lead_in = is_cdda? 2 * 44100 /* The default lead-in size for CD-DA */ : 0; |
260 | cs->is_cd = is_cdda; |
261 | |
262 | while(0 != fgets(buffer, sizeof(buffer), file)) { |
263 | (*last_line_read)++; |
264 | line = buffer; |
265 | |
266 | { |
267 | size_t linelen = strlen(line); |
268 | if((linelen == sizeof(buffer)-1) && line[linelen-1] != '\n') { |
269 | *error_message = "line too long"; |
270 | return false; |
271 | } |
272 | } |
273 | |
274 | if(0 != (field = local__get_field_(&line, /*allow_quotes=*/false))) { |
275 | if(0 == FLAC__STRCASECMP(field, "CATALOG")) { |
276 | if(disc_has_catalog) { |
277 | *error_message = "found multiple CATALOG commands"; |
278 | return false; |
279 | } |
280 | if(0 == (field = local__get_field_(&line, /*allow_quotes=*/true))) { |
281 | *error_message = "CATALOG is missing catalog number"; |
282 | return false; |
283 | } |
284 | if(strlen(field) >= sizeof(cs->media_catalog_number)) { |
285 | *error_message = "CATALOG number is too long"; |
286 | return false; |
287 | } |
288 | if(is_cdda && (strlen(field) != 13 || strspn(field, "0123456789") != 13)) { |
289 | *error_message = "CD-DA CATALOG number must be 13 decimal digits"; |
290 | return false; |
291 | } |
292 | safe_strncpy(cs->media_catalog_number, field, sizeof(cs->media_catalog_number)); |
293 | disc_has_catalog = true; |
294 | } |
295 | else if(0 == FLAC__STRCASECMP(field, "FLAGS")) { |
296 | if(track_has_flags) { |
297 | *error_message = "found multiple FLAGS commands"; |
298 | return false; |
299 | } |
300 | if(in_track_num < 0 || in_index_num >= 0) { |
301 | *error_message = "FLAGS command must come after TRACK but before INDEX"; |
302 | return false; |
303 | } |
304 | while(0 != (field = local__get_field_(&line, /*allow_quotes=*/false))) { |
305 | if(0 == FLAC__STRCASECMP(field, "PRE")) |
306 | cs->tracks[cs->num_tracks-1].pre_emphasis = 1; |
307 | } |
308 | track_has_flags = true; |
309 | } |
310 | else if(0 == FLAC__STRCASECMP(field, "INDEX")) { |
311 | FLAC__int64 xx; |
312 | FLAC__StreamMetadata_CueSheet_Track *track = &cs->tracks[cs->num_tracks-1]; |
313 | if(in_track_num < 0) { |
314 | *error_message = "found INDEX before any TRACK"; |
315 | return false; |
316 | } |
317 | if(0 == (field = local__get_field_(&line, /*allow_quotes=*/false))) { |
318 | *error_message = "INDEX is missing index number"; |
319 | return false; |
320 | } |
321 | in_index_num = local__parse_int_(field); |
322 | if(in_index_num < 0) { |
323 | *error_message = "INDEX has invalid index number"; |
324 | return false; |
325 | } |
326 | FLAC__ASSERT(cs->num_tracks > 0); |
327 | if(track->num_indices == 0) { |
328 | /* it's the first index point of the track */ |
329 | if(in_index_num > 1) { |
330 | *error_message = "first INDEX number of a TRACK must be 0 or 1"; |
331 | return false; |
332 | } |
333 | } |
334 | else { |
335 | if(in_index_num != track->indices[track->num_indices-1].number + 1) { |
336 | *error_message = "INDEX numbers must be sequential"; |
337 | return false; |
338 | } |
339 | } |
340 | if(is_cdda && in_index_num > 99) { |
341 | *error_message = "CD-DA INDEX number must be between 0 and 99, inclusive"; |
342 | return false; |
343 | } |
344 | /*@@@ search for duplicate track number? */ |
345 | if(0 == (field = local__get_field_(&line, /*allow_quotes=*/false))) { |
346 | *error_message = "INDEX is missing an offset after the index number"; |
347 | return false; |
348 | } |
349 | /* first parse as minute:second:frame format */ |
350 | xx = local__parse_msf_(field, sample_rate); |
351 | if(xx < 0) { |
352 | /* CD-DA must use only MM:SS:FF format */ |
353 | if(is_cdda) { |
354 | *error_message = "illegal INDEX offset (not of the form MM:SS:FF)"; |
355 | return false; |
356 | } |
357 | /* as an extension for non-CD-DA we allow MM:SS.SS or raw sample number */ |
358 | xx = local__parse_ms_(field, sample_rate); |
359 | if(xx < 0) { |
360 | xx = local__parse_int64_(field); |
361 | if(xx < 0) { |
362 | *error_message = "illegal INDEX offset"; |
363 | return false; |
364 | } |
365 | } |
366 | } |
367 | else if(sample_rate % 75 && xx) { |
368 | /* only sample zero is exact */ |
369 | *error_message = "illegal INDEX offset (MM:SS:FF form not allowed if sample rate is not a multiple of 75)"; |
370 | return false; |
371 | } |
372 | if(is_cdda && cs->num_tracks == 1 && cs->tracks[0].num_indices == 0 && xx != 0) { |
373 | *error_message = "first INDEX of first TRACK must have an offset of 00:00:00"; |
374 | return false; |
375 | } |
376 | if(is_cdda && track->num_indices > 0 && (FLAC__uint64)xx <= track->indices[track->num_indices-1].offset) { |
377 | *error_message = "CD-DA INDEX offsets must increase in time"; |
378 | return false; |
379 | } |
380 | /* fill in track offset if it's the first index of the track */ |
381 | if(track->num_indices == 0) |
382 | track->offset = (FLAC__uint64)xx; |
383 | if(is_cdda && cs->num_tracks > 1) { |
384 | const FLAC__StreamMetadata_CueSheet_Track *prev = &cs->tracks[cs->num_tracks-2]; |
385 | if((FLAC__uint64)xx <= prev->offset + prev->indices[prev->num_indices-1].offset) { |
386 | *error_message = "CD-DA INDEX offsets must increase in time"; |
387 | return false; |
388 | } |
389 | } |
390 | if(!FLAC__metadata_object_cuesheet_track_insert_blank_index(cuesheet, cs->num_tracks-1, track->num_indices)) { |
391 | *error_message = "memory allocation error"; |
392 | return false; |
393 | } |
394 | track->indices[track->num_indices-1].offset = (FLAC__uint64)xx - track->offset; |
395 | track->indices[track->num_indices-1].number = in_index_num; |
396 | } |
397 | else if(0 == FLAC__STRCASECMP(field, "ISRC")) { |
398 | char *l, *r; |
399 | if(track_has_isrc) { |
400 | *error_message = "found multiple ISRC commands"; |
401 | return false; |
402 | } |
403 | if(in_track_num < 0 || in_index_num >= 0) { |
404 | *error_message = "ISRC command must come after TRACK but before INDEX"; |
405 | return false; |
406 | } |
407 | if(0 == (field = local__get_field_(&line, /*allow_quotes=*/true))) { |
408 | *error_message = "ISRC is missing ISRC number"; |
409 | return false; |
410 | } |
411 | /* strip out dashes */ |
412 | for(l = r = field; *r; r++) { |
413 | if(*r != '-') |
414 | *l++ = *r; |
415 | } |
416 | *l = '\0'; |
417 | if(strlen(field) != 12 || strspn(field, "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") < 5 || strspn(field+5, "1234567890") != 7) { |
418 | *error_message = "invalid ISRC number"; |
419 | return false; |
420 | } |
421 | safe_strncpy(cs->tracks[cs->num_tracks-1].isrc, field, sizeof(cs->tracks[cs->num_tracks-1].isrc)); |
422 | track_has_isrc = true; |
423 | } |
424 | else if(0 == FLAC__STRCASECMP(field, "TRACK")) { |
425 | if(cs->num_tracks > 0) { |
426 | const FLAC__StreamMetadata_CueSheet_Track *prev = &cs->tracks[cs->num_tracks-1]; |
427 | if( |
428 | prev->num_indices == 0 || |
429 | ( |
430 | is_cdda && |
431 | ( |
432 | (prev->num_indices == 1 && prev->indices[0].number != 1) || |
433 | (prev->num_indices == 2 && prev->indices[0].number != 1 && prev->indices[1].number != 1) |
434 | ) |
435 | ) |
436 | ) { |
437 | *error_message = is_cdda? |
438 | "previous TRACK must specify at least one INDEX 01" : |
439 | "previous TRACK must specify at least one INDEX"; |
440 | return false; |
441 | } |
442 | } |
443 | if(0 == (field = local__get_field_(&line, /*allow_quotes=*/false))) { |
444 | *error_message = "TRACK is missing track number"; |
445 | return false; |
446 | } |
447 | in_track_num = local__parse_int_(field); |
448 | if(in_track_num < 0) { |
449 | *error_message = "TRACK has invalid track number"; |
450 | return false; |
451 | } |
452 | if(in_track_num == 0) { |
453 | *error_message = "TRACK number must be greater than 0"; |
454 | return false; |
455 | } |
456 | if(is_cdda) { |
457 | if(in_track_num > 99) { |
458 | *error_message = "CD-DA TRACK number must be between 1 and 99, inclusive"; |
459 | return false; |
460 | } |
461 | } |
462 | else { |
463 | if(in_track_num == 255) { |
464 | *error_message = "TRACK number 255 is reserved for the lead-out"; |
465 | return false; |
466 | } |
467 | else if(in_track_num > 255) { |
468 | *error_message = "TRACK number must be between 1 and 254, inclusive"; |
469 | return false; |
470 | } |
471 | } |
472 | if(is_cdda && cs->num_tracks > 0 && in_track_num != cs->tracks[cs->num_tracks-1].number + 1) { |
473 | *error_message = "CD-DA TRACK numbers must be sequential"; |
474 | return false; |
475 | } |
476 | /*@@@ search for duplicate track number? */ |
477 | if(0 == (field = local__get_field_(&line, /*allow_quotes=*/false))) { |
478 | *error_message = "TRACK is missing a track type after the track number"; |
479 | return false; |
480 | } |
481 | if(!FLAC__metadata_object_cuesheet_insert_blank_track(cuesheet, cs->num_tracks)) { |
482 | *error_message = "memory allocation error"; |
483 | return false; |
484 | } |
485 | cs->tracks[cs->num_tracks-1].number = in_track_num; |
486 | cs->tracks[cs->num_tracks-1].type = (0 == FLAC__STRCASECMP(field, "AUDIO"))? 0 : 1; /*@@@ should we be more strict with the value here? */ |
487 | in_index_num = -1; |
488 | track_has_flags = false; |
489 | track_has_isrc = false; |
490 | } |
491 | else if(0 == FLAC__STRCASECMP(field, "REM")) { |
492 | if(0 != (field = local__get_field_(&line, /*allow_quotes=*/false))) { |
493 | if(0 == strcmp(field, "FLAC__lead-in")) { |
494 | FLAC__int64 xx; |
495 | if(0 == (field = local__get_field_(&line, /*allow_quotes=*/false))) { |
496 | *error_message = "FLAC__lead-in is missing offset"; |
497 | return false; |
498 | } |
499 | xx = local__parse_int64_(field); |
500 | if(xx < 0) { |
501 | *error_message = "illegal FLAC__lead-in offset"; |
502 | return false; |
503 | } |
504 | if(is_cdda && xx % 588 != 0) { |
505 | *error_message = "illegal CD-DA FLAC__lead-in offset, must be even multiple of 588 samples"; |
506 | return false; |
507 | } |
508 | cs->lead_in = (FLAC__uint64)xx; |
509 | } |
510 | else if(0 == strcmp(field, "FLAC__lead-out")) { |
511 | int track_num; |
512 | FLAC__int64 offset; |
513 | if(has_forced_leadout) { |
514 | *error_message = "multiple FLAC__lead-out commands"; |
515 | return false; |
516 | } |
517 | if(0 == (field = local__get_field_(&line, /*allow_quotes=*/false))) { |
518 | *error_message = "FLAC__lead-out is missing track number"; |
519 | return false; |
520 | } |
521 | track_num = local__parse_int_(field); |
522 | if(track_num < 0) { |
523 | *error_message = "illegal FLAC__lead-out track number"; |
524 | return false; |
525 | } |
526 | forced_leadout_track_num = (unsigned)track_num; |
527 | /*@@@ search for duplicate track number? */ |
528 | if(0 == (field = local__get_field_(&line, /*allow_quotes=*/false))) { |
529 | *error_message = "FLAC__lead-out is missing offset"; |
530 | return false; |
531 | } |
532 | offset = local__parse_int64_(field); |
533 | if(offset < 0) { |
534 | *error_message = "illegal FLAC__lead-out offset"; |
535 | return false; |
536 | } |
537 | forced_leadout_track_offset = (FLAC__uint64)offset; |
538 | if(forced_leadout_track_offset != lead_out_offset) { |
539 | *error_message = "FLAC__lead-out offset does not match end-of-stream offset"; |
540 | return false; |
541 | } |
542 | has_forced_leadout = true; |
543 | } |
544 | } |
545 | } |
546 | } |
547 | } |
548 | |
549 | if(cs->num_tracks == 0) { |
550 | *error_message = "there must be at least one TRACK command"; |
551 | return false; |
552 | } |
553 | else { |
554 | const FLAC__StreamMetadata_CueSheet_Track *prev = &cs->tracks[cs->num_tracks-1]; |
555 | if( |
556 | prev->num_indices == 0 || |
557 | ( |
558 | is_cdda && |
559 | ( |
560 | (prev->num_indices == 1 && prev->indices[0].number != 1) || |
561 | (prev->num_indices == 2 && prev->indices[0].number != 1 && prev->indices[1].number != 1) |
562 | ) |
563 | ) |
564 | ) { |
565 | *error_message = is_cdda? |
566 | "previous TRACK must specify at least one INDEX 01" : |
567 | "previous TRACK must specify at least one INDEX"; |
568 | return false; |
569 | } |
570 | } |
571 | |
572 | if(!has_forced_leadout) { |
573 | forced_leadout_track_num = is_cdda? 170 : 255; |
574 | forced_leadout_track_offset = lead_out_offset; |
575 | } |
576 | if(!FLAC__metadata_object_cuesheet_insert_blank_track(cuesheet, cs->num_tracks)) { |
577 | *error_message = "memory allocation error"; |
578 | return false; |
579 | } |
580 | cs->tracks[cs->num_tracks-1].number = forced_leadout_track_num; |
581 | cs->tracks[cs->num_tracks-1].offset = forced_leadout_track_offset; |
582 | |
583 | if(!feof(file)) { |
584 | *error_message = "read error"; |
585 | return false; |
586 | } |
587 | return true; |
588 | } |
589 | |
590 | FLAC__StreamMetadata *grabbag__cuesheet_parse(FILE *file, const char **error_message, unsigned *last_line_read, unsigned sample_rate, FLAC__bool is_cdda, FLAC__uint64 lead_out_offset) |
591 | { |
592 | FLAC__StreamMetadata *cuesheet; |
593 | |
594 | FLAC__ASSERT(0 != file); |
595 | FLAC__ASSERT(0 != error_message); |
596 | FLAC__ASSERT(0 != last_line_read); |
597 | |
598 | *last_line_read = 0; |
599 | cuesheet = FLAC__metadata_object_new(FLAC__METADATA_TYPE_CUESHEET); |
600 | |
601 | if(0 == cuesheet) { |
602 | *error_message = "memory allocation error"; |
603 | return 0; |
604 | } |
605 | |
606 | if(!local__cuesheet_parse_(file, error_message, last_line_read, cuesheet, sample_rate, is_cdda, lead_out_offset)) { |
607 | FLAC__metadata_object_delete(cuesheet); |
608 | return 0; |
609 | } |
610 | |
611 | return cuesheet; |
612 | } |
613 | |
614 | void grabbag__cuesheet_emit(FILE *file, const FLAC__StreamMetadata *cuesheet, const char *file_reference) |
615 | { |
616 | const FLAC__StreamMetadata_CueSheet *cs; |
617 | unsigned track_num, index_num; |
618 | |
619 | FLAC__ASSERT(0 != file); |
620 | FLAC__ASSERT(0 != cuesheet); |
621 | FLAC__ASSERT(cuesheet->type == FLAC__METADATA_TYPE_CUESHEET); |
622 | |
623 | cs = &cuesheet->data.cue_sheet; |
624 | |
625 | if(*(cs->media_catalog_number)) |
626 | fprintf(file, "CATALOG %s\n", cs->media_catalog_number); |
627 | fprintf(file, "FILE %s\n", file_reference); |
628 | |
629 | for(track_num = 0; track_num < cs->num_tracks-1; track_num++) { |
630 | const FLAC__StreamMetadata_CueSheet_Track *track = cs->tracks + track_num; |
631 | |
632 | fprintf(file, " TRACK %02u %s\n", (unsigned)track->number, track->type == 0? "AUDIO" : "DATA"); |
633 | |
634 | if(track->pre_emphasis) |
635 | fprintf(file, " FLAGS PRE\n"); |
636 | if(*(track->isrc)) |
637 | fprintf(file, " ISRC %s\n", track->isrc); |
638 | |
639 | for(index_num = 0; index_num < track->num_indices; index_num++) { |
640 | const FLAC__StreamMetadata_CueSheet_Index *indx = track->indices + index_num; |
641 | |
642 | fprintf(file, " INDEX %02u ", (unsigned)indx->number); |
643 | if(cs->is_cd) { |
644 | const unsigned logical_frame = (unsigned)((track->offset + indx->offset) / (44100 / 75)); |
645 | unsigned m, s, f; |
646 | grabbag__cuesheet_frame_to_msf(logical_frame, &m, &s, &f); |
647 | fprintf(file, "%02u:%02u:%02u\n", m, s, f); |
648 | } |
649 | else |
650 | fprintf(file, "%" PRIu64 "\n", (track->offset + indx->offset)); |
651 | } |
652 | } |
653 | |
654 | fprintf(file, "REM FLAC__lead-in %" PRIu64 "\n", cs->lead_in); |
655 | fprintf(file, "REM FLAC__lead-out %u %" PRIu64 "\n", (unsigned)cs->tracks[track_num].number, cs->tracks[track_num].offset); |
656 | } |