diff --git a/src/metaflac/main.c b/src/metaflac/main.c index f231534e..ba3c74b8 100644 --- a/src/metaflac/main.c +++ b/src/metaflac/main.c @@ -69,6 +69,7 @@ static struct FLAC__share__option long_options_[] = { { "remove-vc-field", 1, 0, 0 }, { "remove-vc-firstfield", 1, 0, 0 }, { "set-vc-field", 1, 0, 0 }, + { "import-vc-from", 1, 0, 0 }, { "add-padding", 1, 0, 0 }, /* major operations */ { "help", 0, 0, 0 }, @@ -105,6 +106,7 @@ typedef enum { OP__REMOVE_VC_FIELD, OP__REMOVE_VC_FIRSTFIELD, OP__SET_VC_FIELD, + OP__IMPORT_VC_FROM, OP__ADD_PADDING, OP__LIST, OP__APPEND, @@ -134,6 +136,10 @@ typedef struct { char *field_value; } Argument_VcField; +typedef struct { + char *file_name; +} Argument_VcImportFile; + typedef struct { unsigned num_entries; unsigned *entries; @@ -169,6 +175,7 @@ typedef struct { Argument_VcFieldName remove_vc_field; Argument_VcFieldName remove_vc_firstfield; Argument_VcField set_vc_field; + Argument_VcImportFile import_vc_from; Argument_AddPadding add_padding; } argument; } Operation; @@ -225,6 +232,7 @@ static void show_version(); static int short_usage(const char *message, ...); static int long_usage(const char *message, ...); static char *local_strdup(const char *source); +static FLAC__bool parse_filename(const char *src, char **dest); static FLAC__bool parse_vorbis_comment_field(const char *field_ref, char **field, char **name, char **value, unsigned *length, const char **violation); static FLAC__bool parse_vorbis_comment_field_name(const char *field_ref, char **name, const char **violation); static FLAC__bool parse_add_padding(const char *in, unsigned *out); @@ -253,6 +261,7 @@ static FLAC__bool remove_vc_all(const char *filename, FLAC__StreamMetadata *bloc static FLAC__bool remove_vc_field(FLAC__StreamMetadata *block, const char *field_name, FLAC__bool *needs_write); static FLAC__bool remove_vc_firstfield(const char *filename, FLAC__StreamMetadata *block, const char *field_name, FLAC__bool *needs_write); static FLAC__bool set_vc_field(const char *filename, FLAC__StreamMetadata *block, const Argument_VcField *field, FLAC__bool *needs_write, FLAC__bool raw); +static FLAC__bool import_vc_from(const char *filename, FLAC__StreamMetadata *block, const Argument_VcImportFile *file, FLAC__bool *needs_write, FLAC__bool raw); static FLAC__bool field_name_matches_entry(const char *field_name, unsigned field_name_length, const FLAC__StreamMetadata_VorbisComment_Entry *entry); static void hexdump(const char *filename, const FLAC__byte *buf, unsigned bytes, const char *indent); @@ -466,6 +475,14 @@ FLAC__bool parse_option(int option_index, const char *option_argument, CommandLi ok = false; } } + else if(0 == strcmp(opt, "import-vc-from")) { + op = append_shorthand_operation(options, OP__IMPORT_VC_FROM); + FLAC__ASSERT(0 != option_argument); + if(!parse_filename(option_argument, &(op->argument.import_vc_from.file_name))) { + fprintf(stderr, "ERROR: missing filename\n"); + ok = false; + } + } else if(0 == strcmp(opt, "add-padding")) { op = append_shorthand_operation(options, OP__ADD_PADDING); FLAC__ASSERT(0 != option_argument); @@ -576,6 +593,10 @@ void free_options(CommandLineOptions *options) if(0 != op->argument.set_vc_field.field_value) free(op->argument.set_vc_field.field_value); break; + case OP__IMPORT_VC_FROM: + if(0 != op->argument.import_vc_from.file_name) + free(op->argument.import_vc_from.file_name); + break; default: break; } @@ -791,6 +812,11 @@ int long_usage(const char *message, ...) fprintf(out, " the Vorbis comment spec, of the form \"NAME=VALUE\". If\n"); fprintf(out, " there is currently no VORBIS_COMMENT block, one will be\n"); fprintf(out, " created.\n"); + fprintf(out, "--import-vc-from=file Import Vorbis comments from a file. Use '-' for stdin.\n"); + fprintf(out, " Each line should be of the form NAME=VALUE. Multi-\n"); + fprintf(out, " line comments are currently not supported. Specify\n"); + fprintf(out, " --remove-vc-all and/or --no-utf8-convert before\n"); + fprintf(out, " --import-vc-from if necessary.\n"); fprintf(out, "--add-padding=length Add a padding block of the given length (in bytes).\n"); fprintf(out, " The overall length of the new block will be 4 + length;\n"); fprintf(out, " the extra 4 bytes is for the metadata block header.\n"); @@ -895,6 +921,14 @@ char *local_strdup(const char *source) return ret; } +FLAC__bool parse_filename(const char *src, char **dest) +{ + if(0 == src || strlen(src) == 0) + return false; + *dest = strdup(src); + return true; +} + FLAC__bool parse_vorbis_comment_field(const char *field_ref, char **field, char **name, char **value, unsigned *length, const char **violation) { static const char * const violations[] = { @@ -1338,6 +1372,7 @@ FLAC__bool do_shorthand_operation(const char *filename, FLAC__Metadata_Chain *ch case OP__REMOVE_VC_FIELD: case OP__REMOVE_VC_FIRSTFIELD: case OP__SET_VC_FIELD: + case OP__IMPORT_VC_FROM: ok = do_shorthand_operation__vorbis_comment(filename, chain, operation, needs_write, !utf8_convert); break; case OP__ADD_PADDING: @@ -1496,6 +1531,9 @@ FLAC__bool do_shorthand_operation__vorbis_comment(const char *filename, FLAC__Me case OP__SET_VC_FIELD: ok = set_vc_field(filename, block, &operation->argument.set_vc_field, needs_write, raw); break; + case OP__IMPORT_VC_FROM: + ok = import_vc_from(filename, block, &operation->argument.import_vc_from, needs_write, raw); + break; default: ok = false; FLAC__ASSERT(0); @@ -1754,6 +1792,63 @@ FLAC__bool set_vc_field(const char *filename, FLAC__StreamMetadata *block, const } } +FLAC__bool import_vc_from(const char *filename, FLAC__StreamMetadata *block, const Argument_VcImportFile *file, FLAC__bool *needs_write, FLAC__bool raw) +{ + FILE *f; + char line[65536]; + FLAC__bool ret; + + if(0 == file->file_name || strlen(file->file_name) == 0) { + fprintf(stderr, "%s: ERROR: empty import file name\n", filename); + return false; + } + if(0 == strcmp(file->file_name, "-")) + f = stdin; + else + f = fopen(file->file_name, "r"); + + if(0 == f) { + fprintf(stderr, "%s: ERROR: can't open import file %s\n", filename, file->file_name); + return false; + } + + ret = true; + while(ret && !feof(f)) { + fgets(line, sizeof(line), f); + if(!feof(f)) { + char *p = strchr(line, '\n'); + if(0 == p) { + fprintf(stderr, "%s: ERROR: line too long, aborting\n", file->file_name); + ret = false; + } + else { + const char *violation; + Argument_VcField field; + *p = '\0'; + memset(&field, 0, sizeof(Argument_VcField)); + if(!parse_vorbis_comment_field(line, &field.field, &field.field_name, &field.field_value, &field.field_value_length, &violation)) { + FLAC__ASSERT(0 != violation); + fprintf(stderr, "%s: ERROR: malformed vorbis comment field \"%s\",\n %s\n", file->file_name, line, violation); + ret = false; + } + else { + ret = set_vc_field(filename, block, &field, needs_write, raw); + } + if(0 != field.field) + free(field.field); + if(0 != field.field_name) + free(field.field_name); + if(0 != field.field_value) + free(field.field_value); + } + } + }; + + if(f != stdin) + fclose(f); + return ret; +} + FLAC__bool field_name_matches_entry(const char *field_name, unsigned field_name_length, const FLAC__StreamMetadata_VorbisComment_Entry *entry) { const FLAC__byte *eq = memchr(entry->entry, '=', entry->length); diff --git a/test/test_metaflac.sh b/test/test_metaflac.sh index cb891077..2d32ed29 100755 --- a/test/test_metaflac.sh +++ b/test/test_metaflac.sh @@ -231,4 +231,22 @@ check_flac (set -x && metaflac --list --except-block-type=STREAMINFO $flacfile) check_exit +(set -x && echo "TITLE=Tittle" | metaflac --import-vc-from=- $flacfile) +check_exit +check_flac +(set -x && metaflac --list --block-type=VORBIS_COMMENT $flacfile) +check_exit + +cat > vc.txt << EOF +artist=Fartist +artist=artits +EOF +(set -x && metaflac --import-vc-from=vc.txt $flacfile) +check_exit +check_flac +(set -x && metaflac --list --block-type=VORBIS_COMMENT $flacfile) +check_exit + +rm vc.txt + exit 0