PR #21079 opened by toots URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/21079 Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/21079.patch
>From e87ebff53376354ffc5902c9404e65df534a3041 Mon Sep 17 00:00:00 2001 From: Romain Beauxis <[email protected]> Date: Tue, 2 Dec 2025 10:43:46 -0600 Subject: [PATCH] Add support for COMM frames in id3v2 tags. --- libavformat/id3v2.c | 1 + libavformat/id3v2enc.c | 58 +++++++++++++++++++++++++++++++++++++++ tests/fate-run.sh | 13 +++++++++ tests/fate/id3v2.mak | 4 +++ tests/ref/fate/id3v2-comm | 5 ++++ 5 files changed, 81 insertions(+) create mode 100644 tests/ref/fate/id3v2-comm diff --git a/libavformat/id3v2.c b/libavformat/id3v2.c index 3ce2fadce8..392a7e6730 100644 --- a/libavformat/id3v2.c +++ b/libavformat/id3v2.c @@ -44,6 +44,7 @@ #include "id3v2.h" const AVMetadataConv ff_id3v2_34_metadata_conv[] = { + { "COMM", "comment" }, { "TALB", "album" }, { "TCOM", "composer" }, { "TCON", "genre" }, diff --git a/libavformat/id3v2enc.c b/libavformat/id3v2enc.c index ac907c2758..02dc3fc68a 100644 --- a/libavformat/id3v2enc.c +++ b/libavformat/id3v2enc.c @@ -25,6 +25,7 @@ #include "libavutil/dict.h" #include "libavutil/intreadwrite.h" #include "avformat.h" +#include "avlanguage.h" #include "avio.h" #include "avio_internal.h" #include "id3v2.h" @@ -58,6 +59,48 @@ static void id3v2_encode_string(AVIOContext *pb, const uint8_t *str, put(pb, str); } +/** + * Write a comment frame according to encoding (only UTF-8 or UTF-16+BOM + * supported). + * @return number of bytes written or a negative error code. + */ +static int id3v2_put_comm(ID3v2EncContext *id3, AVIOContext *avioc, const char *lang, const char *descr, + const char *comment, enum ID3v2Encoding enc) +{ + int len, ret; + uint8_t *pb; + AVIOContext *dyn_buf; + if ((ret = avio_open_dyn_buf(&dyn_buf)) < 0) + return ret; + + /* check if the strings are ASCII-only and use UTF16 only if + * they're not */ + if (enc == ID3v2_ENCODING_UTF16BOM && string_is_ascii(comment) && + (!descr || string_is_ascii(descr))) + enc = ID3v2_ENCODING_ISO8859; + + avio_w8(dyn_buf, enc); + avio_write(dyn_buf, lang && strlen(lang) == 3 ? lang : "und", 3); + if (descr) + id3v2_encode_string(dyn_buf, descr, enc); + else + avio_w8(dyn_buf, 0); + id3v2_encode_string(dyn_buf, comment, enc); + len = avio_get_dyn_buf(dyn_buf, &pb); + + avio_wb32(avioc, MKBETAG('C', 'O', 'M', 'M')); + /* ID3v2.3 frame size is not sync-safe */ + if (id3->version == 3) + avio_wb32(avioc, len); + else + id3v2_put_size(avioc, len); + avio_wb16(avioc, 0); + avio_write(avioc, pb, len); + + ffio_free_dyn_buf(&dyn_buf); + return len + ID3v2_HEADER_SIZE; +} + /** * Write a text frame with one (normal frames) or two (TXXX frames) strings * according to encoding (only UTF-8 or UTF-16+BOM supported). @@ -221,8 +264,14 @@ static int write_metadata(AVIOContext *pb, AVDictionary **metadata, ID3v2EncContext *id3, int enc) { const AVDictionaryEntry *t = NULL; + const char *lang = NULL; + const AVDictionaryEntry *lang_tag; int ret; + lang_tag = av_dict_get(*metadata, "language", NULL, 0); + if (lang_tag) + lang = ff_convert_lang_to(lang_tag->value, AV_LANG_ISO639_2_BIBL); + ff_metadata_conv(metadata, ff_id3v2_34_metadata_conv, NULL); if (id3->version == 3) id3v2_3_metadata_split_date(metadata); @@ -230,6 +279,15 @@ static int write_metadata(AVIOContext *pb, AVDictionary **metadata, ff_metadata_conv(metadata, ff_id3v2_4_metadata_conv, NULL); while ((t = av_dict_iterate(*metadata, t))) { + if (!strncmp(t->key, "COMM", 4)) { + ret = id3v2_put_comm(id3, pb, lang, NULL, t->value, enc); + if (ret < 0) + return ret; + + id3->len += ret; + continue; + } + if ((ret = id3v2_check_write_tag(id3, pb, t, ff_id3v2_tags, enc)) > 0) { id3->len += ret; continue; diff --git a/tests/fate-run.sh b/tests/fate-run.sh index 6d1fe1185c..fd564ff945 100755 --- a/tests/fate-run.sh +++ b/tests/fate-run.sh @@ -94,6 +94,19 @@ runecho(){ $target_exec $target_path/"$@" >&3 } +run_with_temp(){ + tmpfile=`mktemp` + trap 'rm -rf "$tmpfile"' EXIT + create_tmp=$1 + run "$create_tmp $tmpfile" + ret=$? + if [ $ret -ne 0 ]; then + exit $? + fi + process_tmp=$2 + run "$process_tmp $tmpfile" +} + probefmt(){ run ffprobe${PROGSUF}${EXECSUF} -bitexact -threads $threads -show_entries format=format_name -print_format default=nw=1:nk=1 "$@" } diff --git a/tests/fate/id3v2.mak b/tests/fate/id3v2.mak index 7ad4d877a4..0ff8451529 100644 --- a/tests/fate/id3v2.mak +++ b/tests/fate/id3v2.mak @@ -4,6 +4,10 @@ fate-id3v2-priv: CMD = probetags $(TARGET_SAMPLES)/id3v2/id3v2_priv.mp3 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 REMUX, MP3) += fate-id3v2-comm +fate-id3v2-comm: $(FFMPEG) $(FFPROBE) +fate-id3v2-comm: CMD = run_with_temp "$(FFMPEG) -nostdin -hide_banner -loglevel quiet -f lavfi -i sine=frequency=1000:duration=2 -id3v2_version 3 -metadata \"comment=Testing Comment\" -metadata language=eng -y" "$(FFPROBE) -bitexact -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-comm b/tests/ref/fate/id3v2-comm new file mode 100644 index 0000000000..11b48bb831 --- /dev/null +++ b/tests/ref/fate/id3v2-comm @@ -0,0 +1,5 @@ +[FORMAT] +TAG:comment=Testing Comment +TAG:language=eng +TAG:encoder=Lavf62.6.103 +[/FORMAT] -- 2.49.1 _______________________________________________ ffmpeg-devel mailing list -- [email protected] To unsubscribe send an email to [email protected]
