/* 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)
{
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;
return -1;
}
- ret = field * 60 * 44100;
+ ret = field * 60 * sample_rate;
c = *s++;
if(c >= '0' && c <= '9')
if(field >= 60)
return -1;
- ret += field * 44100;
+ ret += field * sample_rate;
c = *s++;
if(c >= '0' && c <= '9')
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);
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) {
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
#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;
(*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;
}
*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")) {
*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;
}
*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;
}
*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;
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;
*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")) {
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;
}
*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;
}
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;
}
*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;
}
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;
}
}
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)) {
#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;
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;
}
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);
}