PR #22624 opened by toots URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/22624 Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/22624.patch
>From 14cbfefef8438cb291eec4072fe9ca66bd6e91c8 Mon Sep 17 00:00:00 2001 From: Romain Beauxis <[email protected]> Date: Wed, 25 Mar 2026 23:46:18 -0500 Subject: [PATCH] fftools/ffmpeg_mux_init.c: delete encoder-specific metadata when re-encoding. --- doc/ffmpeg.texi | 14 ++++++ fftools/ffmpeg.h | 1 + fftools/ffmpeg_mux_init.c | 46 +++++++++++++++++++ fftools/ffmpeg_opt.c | 3 ++ tests/fate/id3v2.mak | 9 ++++ tests/ref/fate/id3v2-itunes-remux | 19 ++++++++ tests/ref/fate/id3v2-reenc-delete-metadata | 16 +++++++ .../ref/fate/id3v2-reenc-delete-metadata-keep | 17 +++++++ 8 files changed, 125 insertions(+) create mode 100644 tests/ref/fate/id3v2-itunes-remux create mode 100644 tests/ref/fate/id3v2-reenc-delete-metadata create mode 100644 tests/ref/fate/id3v2-reenc-delete-metadata-keep diff --git a/doc/ffmpeg.texi b/doc/ffmpeg.texi index 533131adfc..c2384ee755 100644 --- a/doc/ffmpeg.texi +++ b/doc/ffmpeg.texi @@ -920,6 +920,20 @@ To set the language of the first audio stream: ffmpeg -i INPUT -metadata:s:a:0 language=eng OUTPUT @end example +@item -keep_metadata[:metadata_specifier] @var{key} (@emph{output,per-metadata}) +When re-encoding a stream, certain metadata keys that are likely to become stale +(such as @code{iTunSMPB} or @code{encoded_by}) are automatically discarded. +This option prevents a specific key from being discarded, preserving its original +value from the input. + +For example, to keep @code{iTunSMPB} when re-encoding to MP3: +@example +ffmpeg -i in.mp3 -c:a mp3 -keep_metadata iTunSMPB out.mp3 +@end example + +Note that this option has no effect when copying streams without re-encoding, +as metadata is always preserved in that case. + @item -disposition[:stream_specifier] @var{value} (@emph{output,per-stream}) Sets the disposition flags for a stream. diff --git a/fftools/ffmpeg.h b/fftools/ffmpeg.h index 3a19e5878d..45001efa45 100644 --- a/fftools/ffmpeg.h +++ b/fftools/ffmpeg.h @@ -206,6 +206,7 @@ typedef struct OptionsContext { AVDictionary *streamid; SpecifierOptList metadata; + SpecifierOptList keep_metadata; SpecifierOptList max_frames; SpecifierOptList bitstream_filters; SpecifierOptList codec_tags; diff --git a/fftools/ffmpeg_mux_init.c b/fftools/ffmpeg_mux_init.c index e0eecf78f2..099b8d5444 100644 --- a/fftools/ffmpeg_mux_init.c +++ b/fftools/ffmpeg_mux_init.c @@ -3017,6 +3017,23 @@ static int copy_metadata(Muxer *mux, AVFormatContext *ic, return 0; } +static const char * const reenc_delete_keys[] = { + "iTunPGAP", "encoded_by", "iTunSMPB", NULL, +}; + +static int reenc_delete_metadata_key(const OptionsContext *o, const char *key) +{ + for (int i = 0; i < o->keep_metadata.nb_opt; i++) + if (!strcmp(key, o->keep_metadata.opt[i].u.str)) + return 0; + for (int i = 0; i < o->metadata.nb_opt; i++) { + size_t klen = strcspn(o->metadata.opt[i].u.str, "="); + if (!strncmp(key, o->metadata.opt[i].u.str, klen) && key[klen] == '\0') + return 0; + } + return 1; +} + static int copy_meta(Muxer *mux, const OptionsContext *o) { OutputFile *of = &mux->of; @@ -3076,6 +3093,22 @@ static int copy_meta(Muxer *mux, const OptionsContext *o) av_dict_set(&oc->metadata, "company_name", NULL, 0); av_dict_set(&oc->metadata, "product_name", NULL, 0); av_dict_set(&oc->metadata, "product_version", NULL, 0); + + for (int i = 0; i < of->nb_streams; i++) { + if (of->streams[i]->enc) { + for (int j = 0; reenc_delete_keys[j]; j++) { + if (reenc_delete_metadata_key(o, reenc_delete_keys[j]) && + av_dict_get(oc->metadata, reenc_delete_keys[j], NULL, 0)) { + av_log(mux, AV_LOG_WARNING, + "Discarding format metadata '%s' because a stream is being " + "re-encoded. Use '-keep_metadata %s' to keep it.\n", + reenc_delete_keys[j], reenc_delete_keys[j]); + av_dict_set(&oc->metadata, reenc_delete_keys[j], NULL, 0); + } + } + break; + } + } } if (!metadata_streams_manual) for (int i = 0; i < of->nb_streams; i++) { @@ -3084,6 +3117,19 @@ static int copy_meta(Muxer *mux, const OptionsContext *o) if (!ost->ist) /* this is true e.g. for attached files */ continue; av_dict_copy(&ost->st->metadata, ost->ist->st->metadata, AV_DICT_DONT_OVERWRITE); + if (!ost->enc) + continue; + + for (int j = 0; reenc_delete_keys[j]; j++) { + if (reenc_delete_metadata_key(o, reenc_delete_keys[j]) && + av_dict_get(ost->st->metadata, reenc_delete_keys[j], NULL, 0)) { + av_log(ost, AV_LOG_WARNING, + "Discarding stream metadata '%s' because the stream is being " + "re-encoded. Use '-keep_metadata %s' to keep it.\n", + reenc_delete_keys[j], reenc_delete_keys[j]); + av_dict_set(&ost->st->metadata, reenc_delete_keys[j], NULL, 0); + } + } } return 0; diff --git a/fftools/ffmpeg_opt.c b/fftools/ffmpeg_opt.c index 48e6816c19..1ab722d5b1 100644 --- a/fftools/ffmpeg_opt.c +++ b/fftools/ffmpeg_opt.c @@ -1701,6 +1701,9 @@ const OptionDef options[] = { { "metadata", OPT_TYPE_STRING, OPT_SPEC | OPT_OUTPUT, { .off = OFFSET(metadata) }, "add metadata", "key=value" }, + { "keep_metadata", OPT_TYPE_STRING, OPT_SPEC | OPT_OUTPUT, + { .off = OFFSET(keep_metadata) }, + "keep metadata key from input when re-encoding", "key" }, { "program", OPT_TYPE_STRING, OPT_SPEC | OPT_EXPERT | OPT_OUTPUT, { .off = OFFSET(program) }, "add program with specified streams", "title=string:st=number..." }, diff --git a/tests/fate/id3v2.mak b/tests/fate/id3v2.mak index a3040e9759..34bf54831f 100644 --- a/tests/fate/id3v2.mak +++ b/tests/fate/id3v2.mak @@ -5,9 +5,18 @@ FATE_ID3V2_FFMPEG-$(CONFIG_MP3_DEMUXER) += fate-id3v2-invalid-tags fate-id3v2-invalid-tags: CMD = run $(FFMPEG) -nostdin -hide_banner -i $(TARGET_SAMPLES)/id3v2/invalid-tags.mp3 -f null - || true fate-id3v2-invalid-tags: CMP = null +FATE_ID3V2_FFMPEG_FFPROBE-$(call REMUX, MP3) += fate-id3v2-itunes-remux +fate-id3v2-itunes-remux: CMD = transcode mp3 $(TARGET_SAMPLES)/gapless/gapless-itunes.mp3 mp3 "-c copy" "-c copy -t 0.1" "-show_entries format_tags" + FATE_ID3V2_FFMPEG_FFPROBE-$(call REMUX, MP3) += fate-id3v2-priv-remux fate-id3v2-priv-remux: CMD = transcode mp3 $(TARGET_SAMPLES)/id3v2/id3v2_priv.mp3 mp3 "-c copy" "-c copy -t 0.1" "-show_entries format_tags" +FATE_ID3V2_FFMPEG_FFPROBE-$(call ENCDEC, LIBMP3LAME MP3, MP3) += fate-id3v2-reenc-delete-metadata +fate-id3v2-reenc-delete-metadata: CMD = transcode mp3 $(TARGET_SAMPLES)/gapless/gapless-itunes.mp3 mp3 "-c:a mp3 -bitexact" "-c copy -t 0.1" "-show_entries format_tags" + +FATE_ID3V2_FFMPEG_FFPROBE-$(call ENCDEC, LIBMP3LAME MP3, MP3) += fate-id3v2-reenc-delete-metadata-keep +fate-id3v2-reenc-delete-metadata-keep: CMD = transcode mp3 $(TARGET_SAMPLES)/gapless/gapless-itunes.mp3 mp3 "-c:a mp3 -bitexact -keep_metadata iTunSMPB" "-c copy -t 0.1" "-show_entries format_tags" + FATE_ID3V2_FFMPEG_FFPROBE-$(call REMUX, AIFF, WAV_DEMUXER) += fate-id3v2-chapters fate-id3v2-chapters: CMD = transcode wav $(TARGET_SAMPLES)/wav/200828-005.wav aiff "-c copy -metadata:c:0 description=foo -metadata:c:0 date=2021 -metadata:c copyright=none -metadata:c:1 genre=nonsense -write_id3v2 1" "-c copy -t 0.05" "-show_entries format_tags:chapters" diff --git a/tests/ref/fate/id3v2-itunes-remux b/tests/ref/fate/id3v2-itunes-remux new file mode 100644 index 0000000000..ecbf7a45ad --- /dev/null +++ b/tests/ref/fate/id3v2-itunes-remux @@ -0,0 +1,19 @@ +263462774b77f2b20a9cb5cbe492f910 *tests/data/fate/id3v2-itunes-remux.mp3 +192072 tests/data/fate/id3v2-itunes-remux.mp3 +#tb 0: 1/14112000 +#media_type 0: audio +#codec_id 0: mp3 +#sample_rate 0: 44100 +#channel_layout_name 0: stereo +0, -169273, -169273, 368640, 522, 0xbe74f65c, S=1, Skip Samples, 10, 0x00bc0013 +0, 199367, 199367, 368640, 523, 0xbba5059f +0, 568007, 568007, 368640, 522, 0xd8a2153d +0, 936647, 936647, 368640, 523, 0xae220034 +0, 1305287, 1305287, 368640, 522, 0xf661f22c +[FORMAT] +TAG:title=7rk +TAG:iTunPGAP=0 +TAG:encoded_by=iTunes 12.7.0.166 +TAG:iTunNORM= 00000362 000004C0 0000308F 00003CC5 00000DAC 00000DAC 00007D14 00007AC9 000007C1 0000175E +TAG:iTunSMPB= 00000000 00000210 0000086A 0000000000066486 00000000 0002DA9D 00000000 00000000 00000000 00000000 00000000 00000000 +[/FORMAT] diff --git a/tests/ref/fate/id3v2-reenc-delete-metadata b/tests/ref/fate/id3v2-reenc-delete-metadata new file mode 100644 index 0000000000..c981443f84 --- /dev/null +++ b/tests/ref/fate/id3v2-reenc-delete-metadata @@ -0,0 +1,16 @@ +81fb5e219394c97f48a0f2598595add1 *tests/data/fate/id3v2-reenc-delete-metadata.mp3 +153745 tests/data/fate/id3v2-reenc-delete-metadata.mp3 +#tb 0: 1/14112000 +#media_type 0: audio +#codec_id 0: mp3 +#sample_rate 0: 44100 +#channel_layout_name 0: stereo +0, -353604, -353604, 368640, 417, 0xdf4cd4db, S=1, Skip Samples, 10, 0x034e0055 +0, 15036, 15036, 368640, 418, 0x8011cd14 +0, 383676, 383676, 368640, 418, 0x6424c5e0 +0, 752316, 752316, 368640, 418, 0xa358bbae +0, 1120956, 1120956, 368640, 418, 0xfb1ac3ae +[FORMAT] +TAG:title=7rk +TAG:iTunNORM= 00000362 000004C0 0000308F 00003CC5 00000DAC 00000DAC 00007D14 00007AC9 000007C1 0000175E +[/FORMAT] diff --git a/tests/ref/fate/id3v2-reenc-delete-metadata-keep b/tests/ref/fate/id3v2-reenc-delete-metadata-keep new file mode 100644 index 0000000000..00e1e13c47 --- /dev/null +++ b/tests/ref/fate/id3v2-reenc-delete-metadata-keep @@ -0,0 +1,17 @@ +19738194dc7418362789ba85f299721f *tests/data/fate/id3v2-reenc-delete-metadata-keep.mp3 +153882 tests/data/fate/id3v2-reenc-delete-metadata-keep.mp3 +#tb 0: 1/14112000 +#media_type 0: audio +#codec_id 0: mp3 +#sample_rate 0: 44100 +#channel_layout_name 0: stereo +0, -353604, -353604, 368640, 417, 0xdf4cd4db, S=1, Skip Samples, 10, 0x034e0055 +0, 15036, 15036, 368640, 418, 0x8011cd14 +0, 383676, 383676, 368640, 418, 0x6424c5e0 +0, 752316, 752316, 368640, 418, 0xa358bbae +0, 1120956, 1120956, 368640, 418, 0xfb1ac3ae +[FORMAT] +TAG:title=7rk +TAG:iTunSMPB= 00000000 00000210 0000086A 0000000000066486 00000000 0002DA9D 00000000 00000000 00000000 00000000 00000000 00000000 +TAG:iTunNORM= 00000362 000004C0 0000308F 00003CC5 00000DAC 00000DAC 00007D14 00007AC9 000007C1 0000175E +[/FORMAT] -- 2.52.0 _______________________________________________ ffmpeg-devel mailing list -- [email protected] To unsubscribe send an email to [email protected]
