FLAC__bool do_major_operation_on_file(const char *filename, const CommandLineOptions *options) { FLAC__bool ok = true, needs_write = false, is_ogg = false; FLAC__Metadata_Chain *chain = FLAC__metadata_chain_new(); if(0 == chain) die("out of memory allocating chain"); /*@@@@ lame way of guessing the file type */ if(strlen(filename) >= 4 && 0 == strcmp(filename+strlen(filename)-4, ".ogg")) is_ogg = true; if(! (is_ogg? FLAC__metadata_chain_read_ogg(chain, filename) : FLAC__metadata_chain_read(chain, filename)) ) { print_error_with_chain_status(chain, "%s: ERROR: reading metadata", filename); FLAC__metadata_chain_delete(chain); return false; } switch(options->ops.operations[0].type) { case OP__LIST: ok = do_major_operation__list(options->prefix_with_filename? filename : 0, chain, options); break; case OP__APPEND: ok = do_major_operation__append(chain, options); needs_write = true; break; case OP__REMOVE: ok = do_major_operation__remove(chain, options); needs_write = true; break; case OP__REMOVE_ALL: ok = do_major_operation__remove_all(chain, options); needs_write = true; break; case OP__MERGE_PADDING: FLAC__metadata_chain_merge_padding(chain); needs_write = true; break; case OP__SORT_PADDING: FLAC__metadata_chain_sort_padding(chain); needs_write = true; break; default: FLAC__ASSERT(0); return false; } if(ok && needs_write) { if(options->use_padding) FLAC__metadata_chain_sort_padding(chain); ok = FLAC__metadata_chain_write(chain, options->use_padding, options->preserve_modtime); if(!ok) print_error_with_chain_status(chain, "%s: ERROR: writing FLAC file", filename); } FLAC__metadata_chain_delete(chain); return ok; }
FLAC__bool do_shorthand_operations_on_file(const char *filename, const CommandLineOptions *options) { unsigned i; FLAC__bool ok = true, needs_write = false, use_padding = options->use_padding; FLAC__Metadata_Chain *chain = FLAC__metadata_chain_new(); if(0 == chain) die("out of memory allocating chain"); if(!FLAC__metadata_chain_read(chain, filename)) { print_error_with_chain_status(chain, "%s: ERROR: reading metadata", filename); return false; } for(i = 0; i < options->ops.num_operations && ok; i++) { /* * Do OP__ADD_SEEKPOINT last to avoid decoding twice if both * --add-seekpoint and --import-cuesheet-from are used. */ if(options->ops.operations[i].type != OP__ADD_SEEKPOINT) ok &= do_shorthand_operation(filename, options->prefix_with_filename, chain, &options->ops.operations[i], &needs_write, options->utf8_convert); /* The following seems counterintuitive but the meaning * of 'use_padding' is 'try to keep the overall metadata * to its original size, adding or truncating extra * padding if necessary' which is why we need to turn it * off in this case. If we don't, the extra padding block * will just be truncated. */ if(options->ops.operations[i].type == OP__ADD_PADDING) use_padding = false; } /* * Do OP__ADD_SEEKPOINT last to avoid decoding twice if both * --add-seekpoint and --import-cuesheet-from are used. */ for(i = 0; i < options->ops.num_operations && ok; i++) { if(options->ops.operations[i].type == OP__ADD_SEEKPOINT) ok &= do_shorthand_operation(filename, options->prefix_with_filename, chain, &options->ops.operations[i], &needs_write, options->utf8_convert); } if(ok && needs_write) { if(use_padding) FLAC__metadata_chain_sort_padding(chain); ok = FLAC__metadata_chain_write(chain, use_padding, options->preserve_modtime); if(!ok) print_error_with_chain_status(chain, "%s: ERROR: writing FLAC file", filename); } FLAC__metadata_chain_delete(chain); return ok; }
FLAC__bool do_shorthand_operation__add_padding(const char *filename, FLAC__Metadata_Chain *chain, unsigned length, FLAC__bool *needs_write) { FLAC__StreamMetadata *padding = 0; FLAC__Metadata_Iterator *iterator = FLAC__metadata_iterator_new(); if(0 == iterator) die("out of memory allocating iterator"); FLAC__metadata_iterator_init(iterator, chain); while(FLAC__metadata_iterator_next(iterator)) ; padding = FLAC__metadata_object_new(FLAC__METADATA_TYPE_PADDING); if(0 == padding) die("out of memory allocating PADDING block"); padding->length = length; if(!FLAC__metadata_iterator_insert_block_after(iterator, padding)) { print_error_with_chain_status(chain, "%s: ERROR: adding new PADDING block to metadata", filename); FLAC__metadata_object_delete(padding); FLAC__metadata_iterator_delete(iterator); return false; } FLAC__metadata_iterator_delete(iterator); *needs_write = true; return true; }
FLAC__bool do_shorthand_operation__picture(const char *filename, FLAC__Metadata_Chain *chain, const Operation *operation, FLAC__bool *needs_write) { FLAC__bool ok = true, has_type1 = false, has_type2 = false; FLAC__StreamMetadata *picture = 0; FLAC__Metadata_Iterator *iterator = FLAC__metadata_iterator_new(); if(0 == iterator) die("out of memory allocating iterator"); FLAC__metadata_iterator_init(iterator, chain); switch(operation->type) { case OP__IMPORT_PICTURE_FROM: ok = import_pic_from(filename, &picture, operation->argument.specification.value, needs_write); if(ok) { /* append PICTURE block */ while(FLAC__metadata_iterator_next(iterator)) ; if(!FLAC__metadata_iterator_insert_block_after(iterator, picture)) { print_error_with_chain_status(chain, "%s: ERROR: adding new PICTURE block to metadata", filename); FLAC__metadata_object_delete(picture); ok = false; } } if(ok) { /* check global PICTURE constraints (max 1 block each of type=1 and type=2) */ while(FLAC__metadata_iterator_prev(iterator)) ; do { FLAC__StreamMetadata *block = FLAC__metadata_iterator_get_block(iterator); if(block->type == FLAC__METADATA_TYPE_PICTURE) { if(block->data.picture.type == FLAC__STREAM_METADATA_PICTURE_TYPE_FILE_ICON_STANDARD) { if(has_type1) { print_error_with_chain_status(chain, "%s: ERROR: FLAC stream can only have one 32x32 standard icon (type=1) PICTURE block", filename); ok = false; } has_type1 = true; } else if(block->data.picture.type == FLAC__STREAM_METADATA_PICTURE_TYPE_FILE_ICON) { if(has_type2) { print_error_with_chain_status(chain, "%s: ERROR: FLAC stream can only have one icon (type=2) PICTURE block", filename); ok = false; } has_type2 = true; } } } while(FLAC__metadata_iterator_next(iterator)); } break; case OP__EXPORT_PICTURE_TO: { const Argument_BlockNumber *a = operation->argument.export_picture_to.block_number_link; int block_number = (a && a->num_entries > 0)? (int)a->entries[0] : -1; unsigned i = 0; do { FLAC__StreamMetadata *block = FLAC__metadata_iterator_get_block(iterator); if(block->type == FLAC__METADATA_TYPE_PICTURE && (block_number < 0 || i == (unsigned)block_number)) picture = block; i++; } while(FLAC__metadata_iterator_next(iterator) && 0 == picture); if(0 == picture) { if(block_number < 0) fprintf(stderr, "%s: ERROR: FLAC file has no PICTURE block\n", filename); else fprintf(stderr, "%s: ERROR: FLAC file has no PICTURE block at block #%d\n", filename, block_number); ok = false; } else ok = export_pic_to(filename, picture, operation->argument.filename.value); } break; default: ok = false; FLAC__ASSERT(0); break; }; FLAC__metadata_iterator_delete(iterator); return ok; }
FLAC__bool do_shorthand_operation__cuesheet(const char *filename, FLAC__Metadata_Chain *chain, const Operation *operation, FLAC__bool *needs_write) { FLAC__bool ok = true; FLAC__StreamMetadata *cuesheet = 0; FLAC__Metadata_Iterator *iterator = FLAC__metadata_iterator_new(); FLAC__uint64 lead_out_offset = 0; FLAC__bool is_cdda = false; unsigned sample_rate = 0; if(0 == iterator) die("out of memory allocating iterator"); FLAC__metadata_iterator_init(iterator, chain); do { FLAC__StreamMetadata *block = FLAC__metadata_iterator_get_block(iterator); if(block->type == FLAC__METADATA_TYPE_STREAMINFO) { lead_out_offset = block->data.stream_info.total_samples; if(lead_out_offset == 0) { flac_fprintf(stderr, "%s: ERROR: FLAC file must have total_samples set in STREAMINFO in order to import/export cuesheet\n", filename); FLAC__metadata_iterator_delete(iterator); return false; } sample_rate = block->data.stream_info.sample_rate; is_cdda = (block->data.stream_info.channels == 1 || block->data.stream_info.channels == 2) && (block->data.stream_info.bits_per_sample == 16) && (sample_rate == 44100); } else if(block->type == FLAC__METADATA_TYPE_CUESHEET) cuesheet = block; } while(FLAC__metadata_iterator_next(iterator)); if(lead_out_offset == 0) { flac_fprintf(stderr, "%s: ERROR: FLAC stream has no STREAMINFO block\n", filename); FLAC__metadata_iterator_delete(iterator); return false; } switch(operation->type) { case OP__IMPORT_CUESHEET_FROM: if(0 != cuesheet) { flac_fprintf(stderr, "%s: ERROR: FLAC file already has CUESHEET block\n", filename); ok = false; } else { ok = import_cs_from(filename, &cuesheet, operation->argument.import_cuesheet_from.filename, needs_write, lead_out_offset, sample_rate, is_cdda, operation->argument.import_cuesheet_from.add_seekpoint_link); if(ok) { /* append CUESHEET block */ while(FLAC__metadata_iterator_next(iterator)) ; if(!FLAC__metadata_iterator_insert_block_after(iterator, cuesheet)) { print_error_with_chain_status(chain, "%s: ERROR: adding new CUESHEET block to metadata", filename); FLAC__metadata_object_delete(cuesheet); ok = false; } } } break; case OP__EXPORT_CUESHEET_TO: if(0 == cuesheet) { flac_fprintf(stderr, "%s: ERROR: FLAC file has no CUESHEET block\n", filename); ok = false; } else ok = export_cs_to(filename, cuesheet, operation->argument.filename.value); break; default: ok = false; FLAC__ASSERT(0); break; }; FLAC__metadata_iterator_delete(iterator); return ok; }
FLAC__bool do_shorthand_operation__add_seekpoints(const char *filename, FLAC__Metadata_Chain *chain, const char *specification, FLAC__bool *needs_write) { FLAC__bool ok = true, found_seektable_block = false; FLAC__StreamMetadata *block = 0; FLAC__Metadata_Iterator *iterator = FLAC__metadata_iterator_new(); FLAC__uint64 total_samples = 0; unsigned sample_rate = 0; if(0 == iterator) die("out of memory allocating iterator"); FLAC__metadata_iterator_init(iterator, chain); do { block = FLAC__metadata_iterator_get_block(iterator); if(block->type == FLAC__METADATA_TYPE_STREAMINFO) { sample_rate = block->data.stream_info.sample_rate; total_samples = block->data.stream_info.total_samples; } else if(block->type == FLAC__METADATA_TYPE_SEEKTABLE) found_seektable_block = true; } while(!found_seektable_block && FLAC__metadata_iterator_next(iterator)); if(total_samples == 0) { fprintf(stderr, "%s: ERROR: cannot add seekpoints because STREAMINFO block does not specify total_samples\n", filename); return false; } if(!found_seektable_block) { /* create a new block */ block = FLAC__metadata_object_new(FLAC__METADATA_TYPE_SEEKTABLE); if(0 == block) die("out of memory allocating SEEKTABLE block"); while(FLAC__metadata_iterator_prev(iterator)) ; if(!FLAC__metadata_iterator_insert_block_after(iterator, block)) { print_error_with_chain_status(chain, "%s: ERROR: adding new SEEKTABLE block to metadata", filename); FLAC__metadata_object_delete(block); return false; } /* iterator is left pointing to new block */ FLAC__ASSERT(FLAC__metadata_iterator_get_block(iterator) == block); } FLAC__metadata_iterator_delete(iterator); FLAC__ASSERT(0 != block); FLAC__ASSERT(block->type == FLAC__METADATA_TYPE_SEEKTABLE); if(!grabbag__seektable_convert_specification_to_template(specification, /*only_explicit_placeholders=*/false, total_samples, sample_rate, block, /*spec_has_real_points=*/0)) { fprintf(stderr, "%s: ERROR (internal) preparing seektable with seekpoints\n", filename); return false; } ok = populate_seekpoint_values(filename, block, needs_write); if(ok) (void) FLAC__format_seektable_sort(&block->data.seek_table); return ok; }
FLAC__bool do_shorthand_operation__vorbis_comment(const char *filename, FLAC__bool prefix_with_filename, FLAC__Metadata_Chain *chain, const Operation *operation, FLAC__bool *needs_write, FLAC__bool raw) { FLAC__bool ok = true, found_vc_block = false; FLAC__StreamMetadata *block = 0; FLAC__Metadata_Iterator *iterator = FLAC__metadata_iterator_new(); if(0 == iterator) die("out of memory allocating iterator"); FLAC__metadata_iterator_init(iterator, chain); do { block = FLAC__metadata_iterator_get_block(iterator); if(block->type == FLAC__METADATA_TYPE_VORBIS_COMMENT) found_vc_block = true; } while(!found_vc_block && FLAC__metadata_iterator_next(iterator)); if(!found_vc_block) { /* create a new block if necessary */ if(operation->type == OP__SET_VC_FIELD || operation->type == OP__IMPORT_VC_FROM) { block = FLAC__metadata_object_new(FLAC__METADATA_TYPE_VORBIS_COMMENT); if(0 == block) die("out of memory allocating VORBIS_COMMENT block"); while(FLAC__metadata_iterator_next(iterator)) ; if(!FLAC__metadata_iterator_insert_block_after(iterator, block)) { print_error_with_chain_status(chain, "%s: ERROR: adding new VORBIS_COMMENT block to metadata", filename); return false; } /* iterator is left pointing to new block */ FLAC__ASSERT(FLAC__metadata_iterator_get_block(iterator) == block); } else { FLAC__metadata_iterator_delete(iterator); return ok; } } FLAC__ASSERT(0 != block); FLAC__ASSERT(block->type == FLAC__METADATA_TYPE_VORBIS_COMMENT); switch(operation->type) { case OP__SHOW_VC_VENDOR: write_vc_field(prefix_with_filename? filename : 0, &block->data.vorbis_comment.vendor_string, raw, stdout); break; case OP__SHOW_VC_FIELD: write_vc_fields(prefix_with_filename? filename : 0, operation->argument.vc_field_name.value, block->data.vorbis_comment.comments, block->data.vorbis_comment.num_comments, raw, stdout); break; case OP__REMOVE_VC_ALL: ok = remove_vc_all(filename, block, needs_write); break; case OP__REMOVE_VC_FIELD: ok = remove_vc_field(filename, block, operation->argument.vc_field_name.value, needs_write); break; case OP__REMOVE_VC_FIRSTFIELD: ok = remove_vc_firstfield(filename, block, operation->argument.vc_field_name.value, needs_write); break; case OP__SET_VC_FIELD: ok = set_vc_field(filename, block, &operation->argument.vc_field, needs_write, raw); break; case OP__IMPORT_VC_FROM: ok = import_vc_from(filename, block, &operation->argument.filename, needs_write, raw); break; case OP__EXPORT_VC_TO: ok = export_vc_to(filename, block, &operation->argument.filename, raw); break; default: ok = false; FLAC__ASSERT(0); break; }; FLAC__metadata_iterator_delete(iterator); return ok; }