static int EncoderSession_finish_error(EncoderSession *e);
static FLAC__bool EncoderSession_init_encoder(EncoderSession *e, encode_options_t options, unsigned channels, unsigned bps, unsigned sample_rate);
static FLAC__bool EncoderSession_process(EncoderSession *e, const FLAC__int32 * const buffer[], unsigned samples);
-static FLAC__bool convert_to_seek_table_template(const char *requested_seek_points, int num_requested_seek_points, EncoderSession *e);
+static FLAC__bool convert_to_seek_table_template(const char *requested_seek_points, int num_requested_seek_points, FLAC__StreamMetadata *cuesheet, EncoderSession *e);
static void format_input(FLAC__int32 *dest[], unsigned wide_samples, FLAC__bool is_big_endian, FLAC__bool is_unsigned_samples, unsigned channels, unsigned bps);
#ifdef FLAC__HAS_OGG
static FLAC__StreamEncoderWriteStatus ogg_stream_encoder_write_callback(const OggFLAC__StreamEncoder *encoder, const FLAC__byte buffer[], unsigned bytes, unsigned samples, unsigned current_frame, void *client_data);
static FLAC__StreamEncoderWriteStatus flac_stream_encoder_write_callback(const FLAC__StreamEncoder *encoder, const FLAC__byte buffer[], unsigned bytes, unsigned samples, unsigned current_frame, void *client_data);
static void flac_stream_encoder_metadata_callback(const FLAC__StreamEncoder *encoder, const FLAC__StreamMetadata *metadata, void *client_data);
static void flac_file_encoder_progress_callback(const FLAC__FileEncoder *encoder, FLAC__uint64 bytes_written, FLAC__uint64 samples_written, unsigned frames_written, unsigned total_frames_estimate, void *client_data);
+static FLAC__bool parse_cuesheet_(FLAC__StreamMetadata **cuesheet, const char *cuesheet_filename, const char *inbasefilename, FLAC__uint64 lead_out_offset);
static void print_stats(const EncoderSession *encoder_session);
static void print_error_with_state(const EncoderSession *e, const char *message);
static void print_verify_error(EncoderSession *e);
FLAC__bool EncoderSession_init_encoder(EncoderSession *e, encode_options_t options, unsigned channels, unsigned bps, unsigned sample_rate)
{
unsigned num_metadata;
- FLAC__StreamMetadata padding;
- FLAC__StreamMetadata *metadata[3];
+ FLAC__StreamMetadata padding, *cuesheet = 0;
+ FLAC__StreamMetadata *metadata[4];
e->replay_gain = options.replay_gain;
e->channels = channels;
if(channels != 2)
options.do_mid_side = options.loose_mid_side = false;
- if(!convert_to_seek_table_template(options.requested_seek_points, options.num_requested_seek_points, e)) {
+ if(!parse_cuesheet_(&cuesheet, options.cuesheet_filename, e->inbasefilename, e->total_samples_to_encode))
+ return false;
+
+ if(!convert_to_seek_table_template(options.requested_seek_points, options.num_requested_seek_points, options.cued_seekpoints? cuesheet : 0, e)) {
fprintf(stderr, "%s: ERROR allocating memory for seek table\n", e->inbasefilename);
+ if(0 != cuesheet)
+ free(cuesheet);
return false;
}
num_metadata = 0;
- metadata[num_metadata++] = options.vorbis_comment;
if(e->seek_table_template->data.seek_table.num_points > 0) {
e->seek_table_template->is_last = false; /* the encoder will set this for us */
metadata[num_metadata++] = e->seek_table_template;
}
+ if(0 != cuesheet)
+ metadata[num_metadata++] = cuesheet;
+ metadata[num_metadata++] = options.vorbis_comment;
if(options.padding > 0) {
padding.is_last = false; /* the encoder will set this for us */
padding.type = FLAC__METADATA_TYPE_PADDING;
if(OggFLAC__stream_encoder_init(e->encoder.ogg.stream) != FLAC__STREAM_ENCODER_OK) {
print_error_with_state(e, "ERROR initializing encoder");
+ if(0 != cuesheet)
+ free(cuesheet);
return false;
}
}
if(FLAC__stream_encoder_init(e->encoder.flac.stream) != FLAC__STREAM_ENCODER_OK) {
print_error_with_state(e, "ERROR initializing encoder");
+ if(0 != cuesheet)
+ free(cuesheet);
return false;
}
}
if(FLAC__file_encoder_init(e->encoder.flac.file) != FLAC__FILE_ENCODER_OK) {
print_error_with_state(e, "ERROR initializing encoder");
+ if(0 != cuesheet)
+ free(cuesheet);
return false;
}
}
+ if(0 != cuesheet)
+ free(cuesheet);
+
return true;
}
}
}
-FLAC__bool convert_to_seek_table_template(const char *requested_seek_points, int num_requested_seek_points, EncoderSession *e)
+FLAC__bool convert_to_seek_table_template(const char *requested_seek_points, int num_requested_seek_points, FLAC__StreamMetadata *cuesheet, EncoderSession *e)
{
FLAC__bool only_placeholders;
FLAC__bool has_real_points;
- if(num_requested_seek_points == 0)
+ if(num_requested_seek_points == 0 && 0 == cuesheet)
return true;
- if(num_requested_seek_points < 0)
+ if(num_requested_seek_points < 0) {
requested_seek_points = "100x;";
+ num_requested_seek_points = 1;
+ }
if(e->is_stdout)
only_placeholders = true;
else
only_placeholders = false;
- if(!grabbag__seektable_convert_specification_to_template(requested_seek_points, only_placeholders, e->total_samples_to_encode, e->sample_rate, e->seek_table_template, &has_real_points))
- return false;
+ if(num_requested_seek_points > 0) {
+ if(!grabbag__seektable_convert_specification_to_template(requested_seek_points, only_placeholders, e->total_samples_to_encode, e->sample_rate, e->seek_table_template, &has_real_points))
+ return false;
+ }
+
+ if(0 != cuesheet) {
+ unsigned i, j;
+ const FLAC__StreamMetadata_CueSheet *cs = &cuesheet->data.cue_sheet;
+ for(i = 0; i < cs->num_tracks; i++) {
+ const FLAC__StreamMetadata_CueSheet_Track *tr = cs->tracks+i;
+ for(j = 0; j < tr->num_indices; j++) {
+ if(!FLAC__metadata_object_seektable_template_append_point(e->seek_table_template, tr->offset + tr->indices[j].offset))
+ return false;
+ has_real_points = true;
+ }
+ }
+ if(has_real_points)
+ if(!FLAC__metadata_object_seektable_template_sort(e->seek_table_template, /*compact=*/true))
+ return false;
+ }
if(has_real_points) {
if(e->is_stdout)
print_stats(encoder_session);
}
+FLAC__bool parse_cuesheet_(FLAC__StreamMetadata **cuesheet, const char *cuesheet_filename, const char *inbasefilename, FLAC__uint64 lead_out_offset)
+{
+ FILE *f;
+ unsigned last_line_read;
+ const char *error_message;
+
+ if(0 == cuesheet_filename)
+ return true;
+
+ if(lead_out_offset == 0) {
+ fprintf(stderr, "%s: ERROR cannot import cuesheet when the number of input samples to encode is unknown\n", inbasefilename);
+ return false;
+ }
+
+ if(0 == (f = fopen(cuesheet_filename, "r"))) {
+ fprintf(stderr, "%s: ERROR opening cuesheet \"%s\" for reading\n", inbasefilename, cuesheet_filename);
+ return false;
+ }
+
+ *cuesheet = grabbag__cuesheet_parse(f, &error_message, &last_line_read, /*@@@@is_cdda=*/true, lead_out_offset);
+
+ fclose(f);
+
+ if(0 == *cuesheet) {
+ fprintf(stderr, "%s: ERROR parsing cuesheet \"%s\" on line %u: %s\n", inbasefilename, cuesheet_filename, last_line_read, error_message);
+ return false;
+ }
+
+ return true;
+}
+
void print_stats(const EncoderSession *encoder_session)
{
const FLAC__uint64 samples_written = min(encoder_session->total_samples_to_encode, encoder_session->samples_written);
/*
* encoding options
*/
+ { "cuesheet", 1, 0, 0 },
+ { "no-cued-seekpoints", 0, 0, 0 },
{ "tag", 1, 0, 'T' },
{ "compression-level-0", 0, 0, '0' },
{ "compression-level-1", 0, 0, '1' },
{ "compression-level-9", 0, 0, '9' },
{ "best", 0, 0, '8' },
{ "fast", 0, 0, '0' },
- { "super-secret-impractical-compression-level", 0, 0, 0 },
+ { "super-secret-totally-impractical-compression-level", 0, 0, 0 },
{ "verify", 0, 0, 'V' },
{ "force-raw-format", 0, 0, 0 },
{ "lax", 0, 0, 0 },
int min_residual_partition_order;
int max_residual_partition_order;
int rice_parameter_search_dist;
- char requested_seek_points[50000]; /* @@@ bad MAGIC NUMBER */
+ char requested_seek_points[50000]; /* @@@ bad MAGIC NUMBER but buffer overflow is checked */
int num_requested_seek_points; /* -1 => no -S options were given, 0 => -S- was given */
+ const char *cuesheet_filename;
+ FLAC__bool cued_seekpoints;
unsigned num_files;
char **filenames;
if(option_values.cmdline_forced_outfilename && option_values.output_prefix) {
return usage_error("ERROR: --output-prefix conflicts with -o/--output-name\n");
}
+ if(!option_values.mode_decode && 0 != option_values.cuesheet_filename && option_values.num_files > 1) {
+ return usage_error("ERROR: --cuesheet cannot be used when encoding multiple files\n");
+ }
}
if(option_values.verbose) {
fprintf(stderr, "\n");
option_values.rice_parameter_search_dist = -1;
option_values.requested_seek_points[0] = '\0';
option_values.num_requested_seek_points = -1;
+ option_values.cuesheet_filename = 0;
+ option_values.cued_seekpoints = true;
option_values.num_files = 0;
option_values.filenames = 0;
FLAC__ASSERT(0 != option_argument);
option_values.skip = (FLAC__uint64)atoi(option_argument); /* @@@ takes a pretty damn big file to overflow atoi() here, but it could happen */
}
- else if(0 == strcmp(long_option, "super-secret-impractical-compression-level")) {
+ else if(0 == strcmp(long_option, "cuesheet")) {
+ FLAC__ASSERT(0 != option_argument);
+ option_values.cuesheet_filename = option_argument;
+ }
+ else if(0 == strcmp(long_option, "no-cued-seekpoints")) {
+ option_values.cued_seekpoints = false;
+ }
+ else if(0 == strcmp(long_option, "super-secret-totally-impractical-compression-level")) {
+ option_values.lax = true;
option_values.do_exhaustive_model_search = true;
option_values.do_escape_coding = true;
option_values.do_mid_side = true;
if(option_values.num_requested_seek_points < 0)
option_values.num_requested_seek_points = 0;
option_values.num_requested_seek_points++;
- strcat(option_values.requested_seek_points, option_argument);
- strcat(option_values.requested_seek_points, ";");
+ if(strlen(option_values.requested_seek_points)+strlen(option_argument)+2 >= sizeof(option_values.requested_seek_points)) {
+ return usage_error("ERROR: too many seekpoints requested\n");
+ }
+ else {
+ strcat(option_values.requested_seek_points, option_argument);
+ strcat(option_values.requested_seek_points, ";");
+ }
break;
case 'P':
FLAC__ASSERT(0 != option_argument);
printf(" --lax Allow encoder to generate non-Subset files\n");
printf(" --sector-align Align multiple files on sector boundaries\n");
printf(" --replay-gain Calculate ReplayGain & store in Vorbis comments\n");
+ printf(" --cuesheet=FILENAME Import cuesheet and store in CUESHEET block\n");
+ printf(" -T, --tag=FIELD=VALUE Add a Vorbis comment; may appear multiple times\n");
printf(" -S, --seekpoint={#|X|#x|#s} Add seek point(s)\n");
printf(" -P, --padding=# Write a PADDING block of length #\n");
- printf(" -T, --tag=FIELD=VALUE Add a Vorbis comment; may appear multiple times\n");
printf(" -0, --compression-level-0, --fast Synonymous with -l 0 -b 1152 -r 2,2\n");
printf(" -1, --compression-level-1 Synonymous with -l 0 -b 1152 -M -r 2,2\n");
printf(" -2, --compression-level-2 Synonymous with -l 0 -b 1152 -m -r 3\n");
printf(" one of 8, 11.025, 12, 16, 22.05, 24, 32, 44.1,\n");
printf(" or 48 kHz. NOTE: this option may also leave a\n");
printf(" few extra bytes in the PADDING block.\n");
+ printf(" --cuesheet=FILENAME Import the given cuesheet file and store it in\n");
+ printf(" a CUESHEET metadata block. This option may only\n");
+ printf(" be used when encoding a single file. A\n");
+ printf(" seekpoint will be added for each index point in\n");
+ printf(" the cuesheet to the SEEKTABLE unless\n");
+ printf(" --no-cued-seekpoints is specified.\n");
+ printf(" -T, --tag=FIELD=VALUE Add a Vorbis comment. Make sure to quote the\n");
+ printf(" comment if necessary. This option may appear\n");
+ printf(" more than once to add several comments. NOTE:\n");
+ printf(" all tags will be added to all encoded files.\n");
printf(" -S, --seekpoint={#|X|#x|#s} Include a point or points in a SEEKTABLE\n");
printf(" # : a specific sample number for a seek point\n");
printf(" X : a placeholder point (always goes at the end of the SEEKTABLE)\n");
printf(" 576, 1152, 2304, 4608, 256, 512, 1024, 2048,\n");
printf(" 4096, 8192, 16384, or 32768 (unless --lax is\n");
printf(" used)\n");
- printf(" -T, --tag=FIELD=VALUE Add a Vorbis comment. Make sure to quote the\n");
- printf(" comment if necessary. This option may appear\n");
- printf(" more than once to add several comments. NOTE:\n");
- printf(" all tags will be added to all encoded files.\n");
printf(" -0, --compression-level-0, --fast Synonymous with -l 0 -b 1152 -r 2,2\n");
printf(" -1, --compression-level-1 Synonymous with -l 0 -b 1152 -M -r 2,2\n");
printf(" -2, --compression-level-2 Synonymous with -l 0 -b 1152 -m -r 3\n");
common_options.padding = option_values.padding;
common_options.requested_seek_points = option_values.requested_seek_points;
common_options.num_requested_seek_points = option_values.num_requested_seek_points;
+ common_options.cuesheet_filename = option_values.cuesheet_filename;
+ common_options.cued_seekpoints = option_values.cued_seekpoints;
common_options.is_first_file = is_first_file;
common_options.is_last_file = is_last_file;
common_options.align_reservoir = align_reservoir;