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