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