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