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