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