Rename all parameters and locals from 'index' to 'indx'.
[platform/upstream/flac.git] / src / share / grabbag / cuesheet.c
index 429e3bf..841b590 100644 (file)
@@ -1,26 +1,32 @@
 /* grabbag - Convenience lib for various routines common to several tools
- * Copyright (C) 2002  Josh Coalson
+ * Copyright (C) 2002,2003,2004,2005,2006,2007,2008,2009  Josh Coalson
  *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
- * of the License, or (at your option) any later version.
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
  *
- * This program is distributed in the hope that it will be useful,
+ * This library is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
  *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
  */
 
-#include "share/grabbag.h"
-#include "FLAC/assert.h"
+#if HAVE_CONFIG_H
+#  include <config.h>
+#endif
+
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include "FLAC/assert.h"
+#include "share/compat.h"
+#include "share/grabbag.h"
+#include "share/safe_str.h"
 
 unsigned grabbag__cuesheet_msf_to_frame(unsigned minutes, unsigned seconds, unsigned frames)
 {
@@ -72,10 +78,11 @@ static FLAC__int64 local__parse_int64_(const char *s)
        return ret;
 }
 
-/* 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
+/* 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
  * return sample number or <0 for error
+ * WATCHOUT: if sample rate is not evenly divisible by 75, the resulting sample number will be approximate
  */
-static FLAC__int64 local__parse_msf_(const char *s)
+static FLAC__int64 local__parse_msf_(const char *s, unsigned sample_rate)
 {
        FLAC__int64 ret, field;
        char c;
@@ -92,7 +99,7 @@ static FLAC__int64 local__parse_msf_(const char *s)
                        return -1;
        }
 
-       ret = field * 60 * 44100;
+       ret = field * 60 * sample_rate;
 
        c = *s++;
        if(c >= '0' && c <= '9')
@@ -113,7 +120,7 @@ static FLAC__int64 local__parse_msf_(const char *s)
        if(field >= 60)
                return -1;
 
-       ret += field * 44100;
+       ret += field * sample_rate;
 
        c = *s++;
        if(c >= '0' && c <= '9')
@@ -135,13 +142,52 @@ static FLAC__int64 local__parse_msf_(const char *s)
        if(field >= 75)
                return -1;
 
-       ret += field * (44100 / 75);
+       ret += field * (sample_rate / 75);
 
        return ret;
 }
 
-static char *local__get_field_(char **s)
+/* 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
+ * return sample number or <0 for error
+ * WATCHOUT: depending on the sample rate, the resulting sample number may be approximate with fractional seconds
+ */
+static FLAC__int64 local__parse_ms_(const char *s, unsigned sample_rate)
 {
+       FLAC__int64 ret, field;
+       double x;
+       char c, *end;
+
+       c = *s++;
+       if(c >= '0' && c <= '9')
+               field = (c - '0');
+       else
+               return -1;
+       while(':' != (c = *s++)) {
+               if(c >= '0' && c <= '9')
+                       field = field * 10 + (c - '0');
+               else
+                       return -1;
+       }
+
+       ret = field * 60 * sample_rate;
+
+       s++; /* skip the ':' */
+       if(strspn(s, "0123456789.") != strlen(s))
+               return -1;
+       x = strtod(s, &end);
+       if(*end || end == s)
+               return -1;
+       if(x < 0.0 || x >= 60.0)
+               return -1;
+
+       ret += (FLAC__int64)(x * sample_rate);
+
+       return ret;
+}
+
+static char *local__get_field_(char **s, FLAC__bool allow_quotes)
+{
+       FLAC__bool has_quote = false;
        char *p;
 
        FLAC__ASSERT(0 != s);
@@ -153,12 +199,33 @@ static char *local__get_field_(char **s)
        while(**s && 0 != strchr(" \t\r\n", **s))
                (*s)++;
 
-       if(**s == 0)
+       if(**s == 0) {
                *s = 0;
+               return 0;
+       }
+
+       if(allow_quotes && (**s == '"')) {
+               has_quote = true;
+               (*s)++;
+               if(**s == 0) {
+                       *s = 0;
+                       return 0;
+               }
+       }
 
        p = *s;
 
-       if(p) {
+       if(has_quote) {
+               *s = strchr(*s, '\"');
+               /* if there is no matching end quote, it's an error */
+               if(0 == *s)
+                       p = *s = 0;
+               else {
+                       **s = '\0';
+                       (*s)++;
+               }
+       }
+       else {
                while(**s && 0 == strchr(" \t\r\n", **s))
                        (*s)++;
                if(**s) {
@@ -172,7 +239,7 @@ static char *local__get_field_(char **s)
        return p;
 }
 
-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)
+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)
 {
 #if defined _MSC_VER || defined __MINGW32__
 #define FLAC__STRCASECMP stricmp
@@ -180,12 +247,19 @@ static FLAC__bool local__cuesheet_parse_(FILE *file, const char **error_message,
 #define FLAC__STRCASECMP strcasecmp
 #endif
        char buffer[4096], *line, *field;
-       unsigned linelen, forced_leadout_track_num = 0;
+       unsigned forced_leadout_track_num = 0;
        FLAC__uint64 forced_leadout_track_offset = 0;
        int in_track_num = -1, in_index_num = -1;
-       FLAC__bool disc_has_catalog = false, track_has_flags, track_has_isrc, has_forced_leadout = false;
+       FLAC__bool disc_has_catalog = false, track_has_flags = false, track_has_isrc = false, has_forced_leadout = false;
        FLAC__StreamMetadata_CueSheet *cs = &cuesheet->data.cue_sheet;
 
+       FLAC__ASSERT(!is_cdda || sample_rate == 44100);
+       /* double protection */
+       if(is_cdda && sample_rate != 44100) {
+               *error_message = "CD-DA cuesheet only allowed with 44.1kHz sample rate";
+               return false;
+       }
+
        cs->lead_in = is_cdda? 2 * 44100 /* The default lead-in size for CD-DA */ : 0;
        cs->is_cd = is_cdda;
 
@@ -193,19 +267,21 @@ static FLAC__bool local__cuesheet_parse_(FILE *file, const char **error_message,
                (*last_line_read)++;
                line = buffer;
 
-               linelen = strlen(line);
-               if(line[linelen-1] != '\n') {
-                       *error_message = "line too long";
-                       return false;
+               {
+                       size_t linelen = strlen(line);
+                       if((linelen == sizeof(buffer)-1) && line[linelen-1] != '\n') {
+                               *error_message = "line too long";
+                               return false;
+                       }
                }
 
-               if(0 != (field = local__get_field_(&line))) {
+               if(0 != (field = local__get_field_(&line, /*allow_quotes=*/false))) {
                        if(0 == FLAC__STRCASECMP(field, "CATALOG")) {
                                if(disc_has_catalog) {
                                        *error_message = "found multiple CATALOG commands";
                                        return false;
                                }
-                               if(0 == (field = local__get_field_(&line))) {
+                               if(0 == (field = local__get_field_(&line, /*allow_quotes=*/true))) {
                                        *error_message = "CATALOG is missing catalog number";
                                        return false;
                                }
@@ -217,7 +293,7 @@ static FLAC__bool local__cuesheet_parse_(FILE *file, const char **error_message,
                                        *error_message = "CD-DA CATALOG number must be 13 decimal digits";
                                        return false;
                                }
-                               strcpy(cs->media_catalog_number, field);
+                               safe_strncpy(cs->media_catalog_number, field, sizeof(cs->media_catalog_number));
                                disc_has_catalog = true;
                        }
                        else if(0 == FLAC__STRCASECMP(field, "FLAGS")) {
@@ -229,7 +305,7 @@ static FLAC__bool local__cuesheet_parse_(FILE *file, const char **error_message,
                                        *error_message = "FLAGS command must come after TRACK but before INDEX";
                                        return false;
                                }
-                               while(0 != (field = local__get_field_(&line))) {
+                               while(0 != (field = local__get_field_(&line, /*allow_quotes=*/false))) {
                                        if(0 == FLAC__STRCASECMP(field, "PRE"))
                                                cs->tracks[cs->num_tracks-1].pre_emphasis = 1;
                                }
@@ -242,7 +318,7 @@ static FLAC__bool local__cuesheet_parse_(FILE *file, const char **error_message,
                                        *error_message = "found INDEX before any TRACK";
                                        return false;
                                }
-                               if(0 == (field = local__get_field_(&line))) {
+                               if(0 == (field = local__get_field_(&line, /*allow_quotes=*/false))) {
                                        *error_message = "INDEX is missing index number";
                                        return false;
                                }
@@ -269,23 +345,34 @@ static FLAC__bool local__cuesheet_parse_(FILE *file, const char **error_message,
                                        *error_message = "CD-DA INDEX number must be between 0 and 99, inclusive";
                                        return false;
                                }
-                               /*@@@@ search for duplicate track number? */
-                               if(0 == (field = local__get_field_(&line))) {
+                               /*@@@ search for duplicate track number? */
+                               if(0 == (field = local__get_field_(&line, /*allow_quotes=*/false))) {
                                        *error_message = "INDEX is missing an offset after the index number";
                                        return false;
                                }
-                               xx = local__parse_msf_(field);
+                               /* first parse as minute:second:frame format */
+                               xx = local__parse_msf_(field, sample_rate);
                                if(xx < 0) {
+                                       /* CD-DA must use only MM:SS:FF format */
                                        if(is_cdda) {
                                                *error_message = "illegal INDEX offset (not of the form MM:SS:FF)";
                                                return false;
                                        }
-                                       xx = local__parse_int64_(field);
+                                       /* as an extension for non-CD-DA we allow MM:SS.SS or raw sample number */
+                                       xx = local__parse_ms_(field, sample_rate);
                                        if(xx < 0) {
-                                               *error_message = "illegal INDEX offset";
-                                               return false;
+                                               xx = local__parse_int64_(field);
+                                               if(xx < 0) {
+                                                       *error_message = "illegal INDEX offset";
+                                                       return false;
+                                               }
                                        }
                                }
+                               else if(sample_rate % 75 && xx) {
+                                        /* only sample zero is exact */
+                                       *error_message = "illegal INDEX offset (MM:SS:FF form not allowed if sample rate is not a multiple of 75)";
+                                       return false;
+                               }
                                if(is_cdda && cs->num_tracks == 1 && cs->tracks[0].num_indices == 0 && xx != 0) {
                                        *error_message = "first INDEX of first TRACK must have an offset of 00:00:00";
                                        return false;
@@ -312,6 +399,7 @@ static FLAC__bool local__cuesheet_parse_(FILE *file, const char **error_message,
                                track->indices[track->num_indices-1].number = in_index_num;
                        }
                        else if(0 == FLAC__STRCASECMP(field, "ISRC")) {
+                               char *l, *r;
                                if(track_has_isrc) {
                                        *error_message = "found multiple ISRC commands";
                                        return false;
@@ -320,15 +408,21 @@ static FLAC__bool local__cuesheet_parse_(FILE *file, const char **error_message,
                                        *error_message = "ISRC command must come after TRACK but before INDEX";
                                        return false;
                                }
-                               if(0 == (field = local__get_field_(&line))) {
+                               if(0 == (field = local__get_field_(&line, /*allow_quotes=*/false))) {
                                        *error_message = "ISRC is missing ISRC number";
                                        return false;
                                }
+                               /* strip out dashes */
+                               for(l = r = field; *r; r++) {
+                                       if(*r != '-')
+                                               *l++ = *r;
+                               }
+                               *l = '\0';
                                if(strlen(field) != 12 || strspn(field, "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") < 5 || strspn(field+5, "1234567890") != 7) {
                                        *error_message = "invalid ISRC number";
                                        return false;
                                }
-                               strcpy(cs->tracks[cs->num_tracks-1].isrc, field);
+                               safe_strncpy(cs->tracks[cs->num_tracks-1].isrc, field, sizeof(cs->tracks[cs->num_tracks-1].isrc));
                                track_has_isrc = true;
                        }
                        else if(0 == FLAC__STRCASECMP(field, "TRACK")) {
@@ -350,7 +444,7 @@ static FLAC__bool local__cuesheet_parse_(FILE *file, const char **error_message,
                                                return false;
                                        }
                                }
-                               if(0 == (field = local__get_field_(&line))) {
+                               if(0 == (field = local__get_field_(&line, /*allow_quotes=*/false))) {
                                        *error_message = "TRACK is missing track number";
                                        return false;
                                }
@@ -363,16 +457,28 @@ static FLAC__bool local__cuesheet_parse_(FILE *file, const char **error_message,
                                        *error_message = "TRACK number must be greater than 0";
                                        return false;
                                }
-                               if(is_cdda && in_track_num > 99) {
-                                       *error_message = "CD-DA TRACK number must be between 1 and 99, inclusive";
-                                       return false;
+                               if(is_cdda) {
+                                       if(in_track_num > 99) {
+                                               *error_message = "CD-DA TRACK number must be between 1 and 99, inclusive";
+                                               return false;
+                                       }
+                               }
+                               else {
+                                       if(in_track_num == 255) {
+                                               *error_message = "TRACK number 255 is reserved for the lead-out";
+                                               return false;
+                                       }
+                                       else if(in_track_num > 255) {
+                                               *error_message = "TRACK number must be between 1 and 254, inclusive";
+                                               return false;
+                                       }
                                }
                                if(is_cdda && cs->num_tracks > 0 && in_track_num != cs->tracks[cs->num_tracks-1].number + 1) {
                                        *error_message = "CD-DA TRACK numbers must be sequential";
                                        return false;
                                }
-                               /*@@@@ search for duplicate track number? */
-                               if(0 == (field = local__get_field_(&line))) {
+                               /*@@@ search for duplicate track number? */
+                               if(0 == (field = local__get_field_(&line, /*allow_quotes=*/false))) {
                                        *error_message = "TRACK is missing a track type after the track number";
                                        return false;
                                }
@@ -381,16 +487,16 @@ static FLAC__bool local__cuesheet_parse_(FILE *file, const char **error_message,
                                        return false;
                                }
                                cs->tracks[cs->num_tracks-1].number = in_track_num;
-                               cs->tracks[cs->num_tracks-1].type = (0 == FLAC__STRCASECMP(field, "AUDIO"))? 0 : 1; /*@@@@ should we be more strict with the value here? */
+                               cs->tracks[cs->num_tracks-1].type = (0 == FLAC__STRCASECMP(field, "AUDIO"))? 0 : 1; /*@@@ should we be more strict with the value here? */
                                in_index_num = -1;
                                track_has_flags = false;
                                track_has_isrc = false;
                        }
                        else if(0 == FLAC__STRCASECMP(field, "REM")) {
-                               if(0 != (field = local__get_field_(&line))) {
+                               if(0 != (field = local__get_field_(&line, /*allow_quotes=*/false))) {
                                        if(0 == strcmp(field, "FLAC__lead-in")) {
                                                FLAC__int64 xx;
-                                               if(0 == (field = local__get_field_(&line))) {
+                                               if(0 == (field = local__get_field_(&line, /*allow_quotes=*/false))) {
                                                        *error_message = "FLAC__lead-in is missing offset";
                                                        return false;
                                                }
@@ -412,7 +518,7 @@ static FLAC__bool local__cuesheet_parse_(FILE *file, const char **error_message,
                                                        *error_message = "multiple FLAC__lead-out commands";
                                                        return false;
                                                }
-                                               if(0 == (field = local__get_field_(&line))) {
+                                               if(0 == (field = local__get_field_(&line, /*allow_quotes=*/false))) {
                                                        *error_message = "FLAC__lead-out is missing track number";
                                                        return false;
                                                }
@@ -422,8 +528,8 @@ static FLAC__bool local__cuesheet_parse_(FILE *file, const char **error_message,
                                                        return false;
                                                }
                                                forced_leadout_track_num = (unsigned)track_num;
-                                               /*@@@@ search for duplicate track number? */
-                                               if(0 == (field = local__get_field_(&line))) {
+                                               /*@@@ search for duplicate track number? */
+                                               if(0 == (field = local__get_field_(&line, /*allow_quotes=*/false))) {
                                                        *error_message = "FLAC__lead-out is missing offset";
                                                        return false;
                                                }
@@ -468,7 +574,7 @@ static FLAC__bool local__cuesheet_parse_(FILE *file, const char **error_message,
        }
 
        if(!has_forced_leadout) {
-               forced_leadout_track_num = is_cdda? 170 : cs->num_tracks;
+               forced_leadout_track_num = is_cdda? 170 : 255;
                forced_leadout_track_offset = lead_out_offset;
        }
        if(!FLAC__metadata_object_cuesheet_insert_blank_track(cuesheet, cs->num_tracks)) {
@@ -486,7 +592,7 @@ static FLAC__bool local__cuesheet_parse_(FILE *file, const char **error_message,
 #undef FLAC__STRCASECMP
 }
 
-FLAC__StreamMetadata *grabbag__cuesheet_parse(FILE *file, const char **error_message, unsigned *last_line_read, FLAC__bool is_cdda, FLAC__uint64 lead_out_offset)
+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)
 {
        FLAC__StreamMetadata *cuesheet;
 
@@ -502,7 +608,7 @@ FLAC__StreamMetadata *grabbag__cuesheet_parse(FILE *file, const char **error_mes
                return 0;
        }
 
-       if(!local__cuesheet_parse_(file, error_message, last_line_read, cuesheet, is_cdda, lead_out_offset)) {
+       if(!local__cuesheet_parse_(file, error_message, last_line_read, cuesheet, sample_rate, is_cdda, lead_out_offset)) {
                FLAC__metadata_object_delete(cuesheet);
                return 0;
        }
@@ -536,20 +642,20 @@ void grabbag__cuesheet_emit(FILE *file, const FLAC__StreamMetadata *cuesheet, co
                        fprintf(file, "    ISRC %s\n", track->isrc);
 
                for(index_num = 0; index_num < track->num_indices; index_num++) {
-                       const FLAC__StreamMetadata_CueSheet_Index *index = track->indices + index_num;
+                       const FLAC__StreamMetadata_CueSheet_Index *indx = track->indices + index_num;
 
-                       fprintf(file, "    INDEX %02u ", (unsigned)index->number);
+                       fprintf(file, "    INDEX %02u ", (unsigned)indx->number);
                        if(cs->is_cd) {
-                               const unsigned logical_frame = (track->offset + index->offset) / (44100 / 75);
+                               const unsigned logical_frame = (unsigned)((track->offset + indx->offset) / (44100 / 75));
                                unsigned m, s, f;
                                grabbag__cuesheet_frame_to_msf(logical_frame, &m, &s, &f);
                                fprintf(file, "%02u:%02u:%02u\n", m, s, f);
                        }
                        else
-                               fprintf(file, "%llu\n", track->offset + index->offset);
+                               fprintf(file, "%" PRIu64 "\n", (track->offset + indx->offset));
                }
        }
 
-       fprintf(file, "REM FLAC__lead-in %llu\n", cs->lead_in);
-       fprintf(file, "REM FLAC__lead-out %u %llu\n", (unsigned)cs->tracks[track_num].number, cs->tracks[track_num].offset);
+       fprintf(file, "REM FLAC__lead-in %" PRIu64 "\n", cs->lead_in);
+       fprintf(file, "REM FLAC__lead-out %u %" PRIu64 "\n", (unsigned)cs->tracks[track_num].number, cs->tracks[track_num].offset);
 }