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