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