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