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]
