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