PR #22486 opened by James Almer (jamrial)
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/22486
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/22486.patch


>From fbc2940cbbd50e5051addbc39fd08d94ad602add Mon Sep 17 00:00:00 2001
From: James Almer <[email protected]>
Date: Thu, 12 Mar 2026 15:47:22 -0300
Subject: [PATCH] fftools/ffmpeg_demux: add options to override mastering
 display and content level metadata

Signed-off-by: James Almer <[email protected]>
---
 doc/ffmpeg.texi        |  21 ++++++
 fftools/ffmpeg.h       |   2 +
 fftools/ffmpeg_demux.c | 148 +++++++++++++++++++++++++++++++++++++++++
 fftools/ffmpeg_opt.c   |   8 +++
 4 files changed, 179 insertions(+)

diff --git a/doc/ffmpeg.texi b/doc/ffmpeg.texi
index 2dae6632bc..533131adfc 100644
--- a/doc/ffmpeg.texi
+++ b/doc/ffmpeg.texi
@@ -1569,6 +1569,27 @@ Set whether on display the image should be vertically 
flipped.
 
 See the @code{-display_rotation} option for more details.
 
+@item -mastering_display[:@var{stream_specifier}] 
@var{G(%u,%u)B(%u,%u)R(%u,%u)WP(%u,%u)L(%u,%u)} (@emph{input,per-stream})
+Set video mastering display metadata.
+
+@var{G(%u,%u)B(%u,%u)R(%u,%u)WP(%u,%u)L(%u,%u)} is a string specifying
+X,Y display primaries for GBR channels and white point (WP) in units of
+0.00002, and max-min luminance (L) values in units of 0.0001 candela per
+meter square. The values are unsigned integers representing the numerator
+of a rational with an implicit denominator of 50000 for GBR and (WP), and
+implicit denominator 10000 for (L).
+
+This option overrides the mastering display metadata stored in the file,
+if any.
+
+@item -content_light[:@var{stream_specifier}] @var{%u,%u} 
(@emph{input,per-stream})
+Set video content light metadata.
+
+@var{%u,%u} is a string specifying max content light level and maximum picture
+average light level.
+
+This option overrides the content light metadata stored in the file, if any.
+
 @item -vn (@emph{input/output})
 As an input option, blocks all video streams of a file from being filtered or
 being automatically selected or mapped for any output. See @code{-discard}
diff --git a/fftools/ffmpeg.h b/fftools/ffmpeg.h
index b4d3a2c2ac..3a19e5878d 100644
--- a/fftools/ffmpeg.h
+++ b/fftools/ffmpeg.h
@@ -218,6 +218,8 @@ typedef struct OptionsContext {
     SpecifierOptList display_rotations;
     SpecifierOptList display_hflips;
     SpecifierOptList display_vflips;
+    SpecifierOptList mastering_displays;
+    SpecifierOptList content_lights;
     SpecifierOptList rc_overrides;
     SpecifierOptList intra_matrices;
     SpecifierOptList inter_matrices;
diff --git a/fftools/ffmpeg_demux.c b/fftools/ffmpeg_demux.c
index 7c708ff0f3..76a6020cd6 100644
--- a/fftools/ffmpeg_demux.c
+++ b/fftools/ffmpeg_demux.c
@@ -28,6 +28,7 @@
 #include "libavutil/display.h"
 #include "libavutil/error.h"
 #include "libavutil/intreadwrite.h"
+#include "libavutil/mastering_display_metadata.h"
 #include "libavutil/mem.h"
 #include "libavutil/opt.h"
 #include "libavutil/parseutils.h"
@@ -68,6 +69,8 @@ typedef struct DemuxStream {
     int                      autorotate;
     int                      apply_cropping;
     int                      force_display_matrix;
+    int                      force_mastering_display;
+    int                      force_content_light;
     int                      drop_changed;
 
 
@@ -1248,6 +1251,125 @@ static int add_display_matrix_to_stream(const 
OptionsContext *o,
     return 0;
 }
 
+static int add_mastering_display_to_stream(const OptionsContext *o,
+                                           AVFormatContext *ctx, InputStream 
*ist)
+{
+    AVStream *st = ist->st;
+    DemuxStream *ds = ds_from_ist(ist);
+    AVMasteringDisplayMetadata *master_display;
+    AVPacketSideData *sd;
+    const char *p = NULL;
+    const int chroma_den = 50000;
+    const int luma_den = 10000;
+    size_t size;
+    int ret;
+
+    opt_match_per_stream_str(ist, &o->mastering_displays, ctx, st, &p);
+
+    if (!p)
+        return 0;
+
+    master_display = av_mastering_display_metadata_alloc_size(&size);
+    if (!master_display)
+        return AVERROR(ENOMEM);
+
+    ret = sscanf(p,
+                 "G(%u,%u)B(%u,%u)R(%u,%u)WP(%u,%u)L(%u,%u)",
+                 (unsigned*)&master_display->display_primaries[1][0].num,
+                 (unsigned*)&master_display->display_primaries[1][1].num,
+                 (unsigned*)&master_display->display_primaries[2][0].num,
+                 (unsigned*)&master_display->display_primaries[2][1].num,
+                 (unsigned*)&master_display->display_primaries[0][0].num,
+                 (unsigned*)&master_display->display_primaries[0][1].num,
+                 (unsigned*)&master_display->white_point[0].num,
+                 (unsigned*)&master_display->white_point[1].num,
+                 (unsigned*)&master_display->max_luminance.num,
+                 (unsigned*)&master_display->min_luminance.num);
+
+    if (ret != 10 ||
+        (unsigned)(master_display->display_primaries[1][0].num | 
master_display->display_primaries[1][1].num |
+                   master_display->display_primaries[2][0].num | 
master_display->display_primaries[2][1].num |
+                   master_display->display_primaries[0][0].num | 
master_display->display_primaries[0][1].num |
+                   master_display->white_point[0].num | 
master_display->white_point[1].num) > UINT16_MAX ||
+        (unsigned)(master_display->max_luminance.num  | 
master_display->min_luminance.num) > INT_MAX ||
+                   master_display->min_luminance.num  > 
master_display->max_luminance.num) {
+        av_freep(&master_display);
+        av_log(ist, AV_LOG_ERROR, "Failed to parse mastering display 
option\n");
+        return AVERROR(EINVAL);
+    }
+
+    master_display->display_primaries[1][0].den = chroma_den;
+    master_display->display_primaries[1][1].den = chroma_den;
+    master_display->display_primaries[2][0].den = chroma_den;
+    master_display->display_primaries[2][1].den = chroma_den;
+    master_display->display_primaries[0][0].den = chroma_den;
+    master_display->display_primaries[0][1].den = chroma_den;
+    master_display->white_point[0].den = chroma_den;
+    master_display->white_point[1].den = chroma_den;
+    master_display->max_luminance.den = luma_den;
+    master_display->min_luminance.den = luma_den;
+
+    master_display->has_primaries = 1;
+    master_display->has_luminance = 1;
+
+    sd = av_packet_side_data_add(&st->codecpar->coded_side_data,
+                                 &st->codecpar->nb_coded_side_data,
+                                 AV_PKT_DATA_MASTERING_DISPLAY_METADATA,
+                                 (uint8_t *)master_display, size, 0);
+    if (!sd) {
+        av_freep(&master_display);
+        return AVERROR(ENOMEM);
+    }
+
+    ds->force_mastering_display = 1;
+
+    return 0;
+}
+
+static int add_content_light_to_stream(const OptionsContext *o,
+                                       AVFormatContext *ctx, InputStream *ist)
+{
+    AVStream *st = ist->st;
+    DemuxStream *ds = ds_from_ist(ist);
+    AVContentLightMetadata *cll;
+    AVPacketSideData *sd;
+    const char *p = NULL;
+    size_t size;
+    int ret;
+
+    opt_match_per_stream_str(ist, &o->content_lights, ctx, st, &p);
+
+    if (!p)
+        return 0;
+
+    cll = av_content_light_metadata_alloc(&size);
+    if (!cll)
+        return AVERROR(ENOMEM);
+
+    ret = sscanf(p, "%u,%u",
+                 (unsigned*)&cll->MaxCLL,
+                 (unsigned*)&cll->MaxFALL);
+
+    if (ret != 2 || (unsigned)(cll->MaxCLL | cll->MaxFALL) > UINT16_MAX) {
+        av_freep(&cll);
+        av_log(ist, AV_LOG_ERROR, "Failed to parse content light option\n");
+        return AVERROR(EINVAL);
+    }
+
+    sd = av_packet_side_data_add(&st->codecpar->coded_side_data,
+                                 &st->codecpar->nb_coded_side_data,
+                                 AV_PKT_DATA_CONTENT_LIGHT_LEVEL,
+                                 (uint8_t *)cll, size, 0);
+    if (!sd) {
+        av_freep(&cll);
+        return AVERROR(ENOMEM);
+    }
+
+    ds->force_content_light = 1;
+
+    return 0;
+}
+
 static const char *input_stream_item_name(void *obj)
 {
     const DemuxStream *ds = obj;
@@ -1366,6 +1488,14 @@ static int ist_add(const OptionsContext *o, Demuxer *d, 
AVStream *st, AVDictiona
         if (ret < 0)
             return ret;
 
+        ret = add_mastering_display_to_stream(o, ic, ist);
+        if (ret < 0)
+            return ret;
+
+        ret = add_content_light_to_stream(o, ic, ist);
+        if (ret < 0)
+            return ret;
+
         opt_match_per_stream_str(ist, &o->hwaccels, ic, st, &hwaccel);
         opt_match_per_stream_str(ist, &o->hwaccel_output_formats, ic, st,
                                        &hwaccel_output_format);
@@ -1492,6 +1622,24 @@ static int ist_add(const OptionsContext *o, Demuxer *d, 
AVStream *st, AVDictiona
         av_strlcat(buf, "displaymatrix", sizeof(buf));
         av_dict_set(&ds->decoder_opts, "side_data_prefer_packet", buf, 
AV_DICT_APPEND);
     }
+    if (ds->force_mastering_display) {
+        char buf[32];
+        if (av_dict_get(ds->decoder_opts, "side_data_prefer_packet", NULL, 0))
+            buf[0] = ',';
+        else
+            buf[0] = '\0';
+        av_strlcat(buf, "mastering_display_metadata", sizeof(buf));
+        av_dict_set(&ds->decoder_opts, "side_data_prefer_packet", buf, 
AV_DICT_APPEND);
+    }
+    if (ds->force_content_light) {
+        char buf[32];
+        if (av_dict_get(ds->decoder_opts, "side_data_prefer_packet", NULL, 0))
+            buf[0] = ',';
+        else
+            buf[0] = '\0';
+        av_strlcat(buf, "content_light_level", sizeof(buf));
+        av_dict_set(&ds->decoder_opts, "side_data_prefer_packet", buf, 
AV_DICT_APPEND);
+    }
     /* Attached pics are sparse, therefore we would not want to delay their 
decoding
      * till EOF. */
     if (ist->st->disposition & AV_DISPOSITION_ATTACHED_PIC)
diff --git a/fftools/ffmpeg_opt.c b/fftools/ffmpeg_opt.c
index a0664e2964..310bd9b33a 100644
--- a/fftools/ffmpeg_opt.c
+++ b/fftools/ffmpeg_opt.c
@@ -1940,6 +1940,14 @@ const OptionDef options[] = {
         { .off = OFFSET(display_vflips) },
         "set display vertical flip for stream(s) "
         "(overrides any display rotation if it is not set)"},
+    { "mastering_display",          OPT_TYPE_STRING, OPT_VIDEO | OPT_PERSTREAM 
| OPT_INPUT | OPT_EXPERT,
+        { .off = OFFSET(mastering_displays) },
+        "mastering_display",
+        "mdcm" },
+    { "content_light",              OPT_TYPE_STRING, OPT_VIDEO | OPT_PERSTREAM 
| OPT_INPUT | OPT_EXPERT,
+        { .off = OFFSET(content_lights) },
+        "content_lights",
+        "cll" },
     { "vn",                         OPT_TYPE_BOOL,   OPT_VIDEO | OPT_OFFSET | 
OPT_INPUT | OPT_OUTPUT,
         { .off = OFFSET(video_disable) },
         "disable video" },
-- 
2.52.0

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

Reply via email to