This is an automated email from the git hooks/post-receive script.

Git pushed a commit to branch master
in repository ffmpeg.

commit eb107d87723508907f1690552b29f803f241068e
Author:     Romain Beauxis <[email protected]>
AuthorDate: Tue Dec 2 10:43:46 2025 -0600
Commit:     Romain Beauxis <[email protected]>
CommitDate: Sat Jul 18 09:59:57 2026 -0500

    libavformat/id3v2.c: Factor out support for reading lyrics and comment
    tags.
---
 libavformat/Makefile                |  19 +++--
 libavformat/id3v2.c                 | 136 ++++++++++++++++++------------------
 tests/ref/fate/id3v2-lang-xxx       |   2 +-
 tests/ref/fate/id3v2-lang-xxx-remux |   4 +-
 tests/ref/fate/id3v2-wma-comm       |   2 +-
 tests/ref/fate/id3v2-wma-comm-asf_o |   2 +-
 6 files changed, 82 insertions(+), 83 deletions(-)

diff --git a/libavformat/Makefile b/libavformat/Makefile
index 3595567464..2f85e8a2ad 100644
--- a/libavformat/Makefile
+++ b/libavformat/Makefile
@@ -8,6 +8,7 @@ HEADERS = avformat.h                                            
        \
 
 OBJS = allformats.o         \
        av1.o                \
+       avlanguage.o         \
        avformat.o           \
        avio.o               \
        aviobuf.o            \
@@ -138,11 +139,10 @@ OBJS-$(CONFIG_ARGO_BRP_DEMUXER)          += argo_brp.o 
argo_asf.o
 OBJS-$(CONFIG_ARGO_CVG_DEMUXER)          += argo_cvg.o
 OBJS-$(CONFIG_ARGO_CVG_MUXER)            += argo_cvg.o
 OBJS-$(CONFIG_ASF_DEMUXER)               += asfdec_f.o asf.o asfcrypt.o \
-                                            asf_tags.o avlanguage.o
+                                            asf_tags.o
 OBJS-$(CONFIG_ASF_O_DEMUXER)             += asfdec_o.o asf.o asfcrypt.o \
-                                            asf_tags.o avlanguage.o
-OBJS-$(CONFIG_ASF_MUXER)                 += asfenc.o asf.o asf_tags.o \
-                                            avlanguage.o
+                                            asf_tags.o
+OBJS-$(CONFIG_ASF_MUXER)                 += asfenc.o asf.o asf_tags.o
 OBJS-$(CONFIG_ASS_DEMUXER)               += assdec.o subtitles.o
 OBJS-$(CONFIG_ASS_MUXER)                 += assenc.o
 OBJS-$(CONFIG_AST_DEMUXER)               += ast.o astdec.o
@@ -150,7 +150,7 @@ OBJS-$(CONFIG_AST_MUXER)                 += ast.o astenc.o
 OBJS-$(CONFIG_AU_DEMUXER)                += au.o pcm.o
 OBJS-$(CONFIG_AU_MUXER)                  += au.o rawenc.o
 OBJS-$(CONFIG_AVI_DEMUXER)               += avidec.o
-OBJS-$(CONFIG_AVI_MUXER)                 += avienc.o mpegtsenc.o avlanguage.o 
rawutils.o
+OBJS-$(CONFIG_AVI_MUXER)                 += avienc.o mpegtsenc.o rawutils.o
 OBJS-$(CONFIG_AVM2_MUXER)                += swfenc.o swf.o
 OBJS-$(CONFIG_AVR_DEMUXER)               += avr.o pcm.o
 OBJS-$(CONFIG_AVS_DEMUXER)               += avs.o voc_packet.o voc.o
@@ -368,8 +368,8 @@ OBJS-$(CONFIG_MATROSKA_DEMUXER)          += matroskadec.o 
matroska.o  \
                                             oggparsevorbis.o vorbiscomment.o \
                                             qtpalette.o replaygain.o 
dovi_isom.o
 OBJS-$(CONFIG_MATROSKA_MUXER)            += matroskaenc.o matroska.o \
-                                            flacenc_header.o avlanguage.o \
-                                            vorbiscomment.o wv.o dovi_isom.o
+                                            flacenc_header.o vorbiscomment.o \
+                                            wv.o dovi_isom.o
 OBJS-$(CONFIG_MCA_DEMUXER)               += mca.o
 OBJS-$(CONFIG_MCC_DEMUXER)               += mccdec.o subtitles.o
 OBJS-$(CONFIG_MCC_MUXER)                 += mccenc.o
@@ -424,7 +424,7 @@ OBJS-$(CONFIG_MUSX_DEMUXER)              += musx.o
 OBJS-$(CONFIG_MV_DEMUXER)                += mvdec.o
 OBJS-$(CONFIG_MVI_DEMUXER)               += mvi.o
 OBJS-$(CONFIG_MVR_DEMUXER)               += mvrdec.o
-OBJS-$(CONFIG_MXF_DEMUXER)               += mxfdec.o mxf.o avlanguage.o
+OBJS-$(CONFIG_MXF_DEMUXER)               += mxfdec.o mxf.o
 OBJS-$(CONFIG_MXF_MUXER)                 += mxfenc.o mxf.o
 OBJS-$(CONFIG_MXG_DEMUXER)               += mxg.o
 OBJS-$(CONFIG_NC_DEMUXER)                += ncdec.o
@@ -644,8 +644,7 @@ OBJS-$(CONFIG_WAVARC_DEMUXER)            += wavarc.o
 OBJS-$(CONFIG_WAV_DEMUXER)               += wavdec.o pcm.o
 OBJS-$(CONFIG_WAV_MUXER)                 += wavenc.o
 OBJS-$(CONFIG_WC3_DEMUXER)               += wc3movie.o
-OBJS-$(CONFIG_WEBM_MUXER)                += matroskaenc.o matroska.o \
-                                            avlanguage.o
+OBJS-$(CONFIG_WEBM_MUXER)                += matroskaenc.o matroska.o
 OBJS-$(CONFIG_WEBM_DASH_MANIFEST_MUXER)  += webmdashenc.o
 OBJS-$(CONFIG_WEBM_CHUNK_MUXER)          += webm_chunk.o
 OBJS-$(CONFIG_WEBP_ANIM_DEMUXER)         += webp_anim_dec.o
diff --git a/libavformat/id3v2.c b/libavformat/id3v2.c
index 21c09d13b4..3e931d2bd2 100644
--- a/libavformat/id3v2.c
+++ b/libavformat/id3v2.c
@@ -39,8 +39,10 @@
 #include "libavutil/dict.h"
 #include "libavutil/intreadwrite.h"
 #include "libavutil/mem.h"
+#include "libavutil/opt.h"
 #include "libavcodec/png.h"
 #include "avio_internal.h"
+#include "avlanguage.h"
 #include "demux.h"
 #include "id3v1.h"
 #include "id3v2.h"
@@ -374,92 +376,90 @@ static void read_ttag(AVFormatContext *s, AVIOContext 
*pb, int taglen,
         av_dict_set(metadata, key, dst, dict_flags);
 }
 
-static void read_uslt(AVFormatContext *s, AVIOContext *pb, int taglen,
-                      AVDictionary **metadata)
+/**
+ * Parse a lang descr tag such as COMM and USLT.
+ *
+ * COMM with a non-empty descriptor: the descriptor becomes the bare key
+ * (e.g. "MusicMatch_Bio").  USLT and all other cases produce
+ * "<base>-<descriptor>-<lang>".
+ */
+static void read_lang_descr_tag(AVFormatContext *s, AVIOContext *pb,
+                                const char *key, int taglen,
+                                AVDictionary **metadata)
 {
-    uint8_t lang[4];
-    uint8_t *descriptor = NULL; // 'Content descriptor'
-    uint8_t *text;
-    char *key;
+    char *full_key = NULL;
+    uint8_t *dst, *descriptor = NULL;
     int encoding;
-    int ok = 0;
+    char language[4] = {0};
+    int flags = AV_DICT_DONT_OVERWRITE | AV_DICT_DONT_STRDUP_VAL;
 
     if (taglen < 4)
-        goto error;
+        return;
 
     encoding = avio_r8(pb);
-    taglen--;
 
-    if (avio_read(pb, lang, 3) < 3)
-        goto error;
-    lang[3] = '\0';
-    taglen -= 3;
-
-    if (decode_str(s, pb, encoding, &descriptor, &taglen) < 0 || taglen < 0)
-        goto error;
-
-    if (decode_str(s, pb, encoding, &text, &taglen) < 0 || taglen < 0)
-        goto error;
-
-    // FFmpeg does not support hierarchical metadata, so concatenate the keys.
-    key = av_asprintf("lyrics-%s%s%s", descriptor[0] ? (char *)descriptor : "",
-                                       descriptor[0] ? "-" : "",
-                                       lang);
-    if (!key) {
-        av_free(text);
-        goto error;
+    if (avio_read(pb, language, 3) < 3) {
+        av_log(s, AV_LOG_ERROR, "Error reading %s frame language, skipped\n", 
key);
+        return;
     }
 
-    av_dict_set(metadata, key, text,
-                AV_DICT_DONT_STRDUP_KEY | AV_DICT_DONT_STRDUP_VAL);
+    for (char *p = language; *p; p++)
+        *p = av_tolower(*p);
 
-    ok = 1;
-error:
-    if (!ok)
-        av_log(s, AV_LOG_ERROR, "Error reading lyrics, skipped\n");
-    av_free(descriptor);
-}
+    // Some libraries set XXX for unknown language.
+    if (!strcmp(language, "xxx"))
+        memset(language, 0, sizeof(language));
 
-/**
- * Parse a comment tag.
- */
-static void read_comment(AVFormatContext *s, AVIOContext *pb, int taglen,
-                      AVDictionary **metadata)
-{
-    const char *key = "comment";
-    uint8_t *dst;
-    int encoding, dict_flags = AV_DICT_DONT_OVERWRITE | 
AV_DICT_DONT_STRDUP_VAL;
-    av_unused int language;
-
-    if (taglen < 4)
-        return;
-
-    encoding = avio_r8(pb);
-    language = avio_rl24(pb);
     taglen -= 4;
 
-    if (decode_str(s, pb, encoding, &dst, &taglen) < 0) {
-        av_log(s, AV_LOG_ERROR, "Error reading comment frame, skipped\n");
+    if (decode_str(s, pb, encoding, &descriptor, &taglen) < 0) {
+        av_log(s, AV_LOG_ERROR, "Error reading %s frame descriptor, 
skipped\n", key);
         return;
     }
 
-    if (dst && !*dst)
-        av_freep(&dst);
-
-    if (dst) {
-        key = (const char *) dst;
-        dict_flags |= AV_DICT_DONT_STRDUP_KEY;
-    }
-
     if (decode_str(s, pb, encoding, &dst, &taglen) < 0) {
-        av_log(s, AV_LOG_ERROR, "Error reading comment frame, skipped\n");
-        if (dict_flags & AV_DICT_DONT_STRDUP_KEY)
-            av_freep((void*)&key);
+        av_freep(&descriptor);
+        av_log(s, AV_LOG_ERROR, "Error reading %s frame, skipped\n", key);
         return;
     }
 
-    if (dst)
-        av_dict_set(metadata, key, (const char *) dst, dict_flags);
+    if (descriptor && *descriptor) {
+        if (!strcmp(key, "comment")) {
+            /* legacy COMM: non-empty descriptor becomes the metadata key */
+            flags |= AV_DICT_DONT_STRDUP_KEY;
+            key = (char *)descriptor;
+            descriptor = NULL;
+        } else {
+            /* USLT: <tag>-<descriptor>-<lang> */
+            if (av_strnlen(language, 3) > 0)
+                full_key = av_asprintf("%s-%s-%s", key, descriptor, language);
+            else if (strlen((char *)descriptor) == 3 &&
+                     ff_convert_lang_to((char *)descriptor, 
AV_LANG_ISO639_2_BIBL))
+                /* Descriptor looks like a lang code: add trailing lang to
+                 * keep the key unambiguous on the write side. */
+                full_key = av_asprintf("%s-%s-und", key, descriptor);
+            else
+                full_key = av_asprintf("%s-%s", key, descriptor);
+            if (!full_key) {
+                av_freep(&descriptor);
+                av_freep(&dst);
+                return;
+            }
+            key = full_key;
+        }
+    } else if (av_strnlen(language, 3) == 3) {
+        full_key = av_asprintf("%s-%s", key, language);
+        if (!full_key) {
+            av_freep(&descriptor);
+            av_freep(&dst);
+            return;
+        }
+        key = full_key;
+    }
+
+    av_freep(&descriptor);
+    av_dict_set(metadata, key, (const char *)dst, flags);
+    av_freep(&full_key);
 }
 
 typedef struct ExtraMetaList {
@@ -1079,9 +1079,9 @@ static void id3v2_parse(AVIOContext *pb, AVDictionary 
**metadata,
                 /* parse text tag */
                 read_ttag(s, pbx, tlen, metadata, tag);
             else if (!memcmp(tag, "USLT", 4))
-                read_uslt(s, pbx, tlen, metadata);
+                read_lang_descr_tag(s, pbx, "lyrics", tlen, metadata);
             else if (!strcmp(tag, comm_frame))
-                read_comment(s, pbx, tlen, metadata);
+                read_lang_descr_tag(s, pbx, "comment", tlen, metadata);
             else
                 /* parse special meta tag */
                 extra_func->read(s, pbx, tlen, tag, extra_meta, isv34);
diff --git a/tests/ref/fate/id3v2-lang-xxx b/tests/ref/fate/id3v2-lang-xxx
index 56538dd63c..9d9575554f 100644
--- a/tests/ref/fate/id3v2-lang-xxx
+++ b/tests/ref/fate/id3v2-lang-xxx
@@ -1,4 +1,4 @@
 ID3v2 frame COMM (18 bytes):|.XXX.Dummy comment|
 ID3v2 frame USLT (17 bytes):|.XXX.Dummy lyrics|
 comment=Dummy comment
-lyrics-XXX=Dummy lyrics
+lyrics=Dummy lyrics
diff --git a/tests/ref/fate/id3v2-lang-xxx-remux 
b/tests/ref/fate/id3v2-lang-xxx-remux
index 033e6f1fed..ee7e77b8ed 100644
--- a/tests/ref/fate/id3v2-lang-xxx-remux
+++ b/tests/ref/fate/id3v2-lang-xxx-remux
@@ -1,4 +1,4 @@
 ID3v2 frame TXXX (23 bytes):|.comment.Dummy comment.|
-ID3v2 frame TXXX (25 bytes):|.lyrics-XXX.Dummy lyrics.|
+ID3v2 frame TXXX (19 bytes):|.USLT.Dummy lyrics.|
 comment=Dummy comment
-lyrics-XXX=Dummy lyrics
+lyrics=Dummy lyrics
diff --git a/tests/ref/fate/id3v2-wma-comm b/tests/ref/fate/id3v2-wma-comm
index 59a79bef01..68d113862c 100644
--- a/tests/ref/fate/id3v2-wma-comm
+++ b/tests/ref/fate/id3v2-wma-comm
@@ -8,7 +8,7 @@ TAG:WM/GenreID=Inconnu
 TAG:track=00
 TAG:album=Tombé du Ciel
 TAG:genre=Inconnu
-TAG:comment=
+TAG:comment-eng=
 TAG:lyrics-eng=
 TAG:MusicMatch_Bio=
 TAG:TLEN=286000
diff --git a/tests/ref/fate/id3v2-wma-comm-asf_o 
b/tests/ref/fate/id3v2-wma-comm-asf_o
index 49b9b9723b..639f0a02ce 100644
--- a/tests/ref/fate/id3v2-wma-comm-asf_o
+++ b/tests/ref/fate/id3v2-wma-comm-asf_o
@@ -10,7 +10,7 @@ TAG:track=00
 TAG:artist=Higelin
 TAG:album=Tombé du Ciel
 TAG:genre=Inconnu
-TAG:comment=
+TAG:comment-eng=
 TAG:lyrics-eng=
 TAG:MusicMatch_Bio=
 TAG:TLEN=286000

_______________________________________________
ffmpeg-cvslog mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to