fftools/ffmpeg_demux: add options to override mastering display and content light level metadata

Signed-off-by: James Almer <jamrial@gmail.com>
This commit is contained in:
James Almer
2026-03-12 15:47:22 -03:00
parent 8172be423e
commit 1d65e985b3
4 changed files with 178 additions and 6 deletions

View File

@@ -1569,6 +1569,27 @@ Set whether on display the image should be vertically flipped.
See the @code{-display_rotation} option for more details. 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}) @item -vn (@emph{input/output})
As an input option, blocks all video streams of a file from being filtered or 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} being automatically selected or mapped for any output. See @code{-discard}

View File

@@ -218,6 +218,8 @@ typedef struct OptionsContext {
SpecifierOptList display_rotations; SpecifierOptList display_rotations;
SpecifierOptList display_hflips; SpecifierOptList display_hflips;
SpecifierOptList display_vflips; SpecifierOptList display_vflips;
SpecifierOptList mastering_displays;
SpecifierOptList content_lights;
SpecifierOptList rc_overrides; SpecifierOptList rc_overrides;
SpecifierOptList intra_matrices; SpecifierOptList intra_matrices;
SpecifierOptList inter_matrices; SpecifierOptList inter_matrices;

View File

@@ -28,6 +28,7 @@
#include "libavutil/display.h" #include "libavutil/display.h"
#include "libavutil/error.h" #include "libavutil/error.h"
#include "libavutil/intreadwrite.h" #include "libavutil/intreadwrite.h"
#include "libavutil/mastering_display_metadata.h"
#include "libavutil/mem.h" #include "libavutil/mem.h"
#include "libavutil/opt.h" #include "libavutil/opt.h"
#include "libavutil/parseutils.h" #include "libavutil/parseutils.h"
@@ -68,6 +69,8 @@ typedef struct DemuxStream {
int autorotate; int autorotate;
int apply_cropping; int apply_cropping;
int force_display_matrix; int force_display_matrix;
int force_mastering_display;
int force_content_light;
int drop_changed; int drop_changed;
@@ -1248,6 +1251,125 @@ static int add_display_matrix_to_stream(const OptionsContext *o,
return 0; 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) static const char *input_stream_item_name(void *obj)
{ {
const DemuxStream *ds = obj; const DemuxStream *ds = obj;
@@ -1301,6 +1423,7 @@ static int ist_add(const OptionsContext *o, Demuxer *d, AVStream *st, AVDictiona
const char *bsfs = NULL; const char *bsfs = NULL;
char *next; char *next;
const char *discard_str = NULL; const char *discard_str = NULL;
AVBPrint bp;
int ret; int ret;
ds = demux_stream_alloc(d, st); ds = demux_stream_alloc(d, st);
@@ -1366,6 +1489,14 @@ static int ist_add(const OptionsContext *o, Demuxer *d, AVStream *st, AVDictiona
if (ret < 0) if (ret < 0)
return ret; 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->hwaccels, ic, st, &hwaccel);
opt_match_per_stream_str(ist, &o->hwaccel_output_formats, ic, st, opt_match_per_stream_str(ist, &o->hwaccel_output_formats, ic, st,
&hwaccel_output_format); &hwaccel_output_format);
@@ -1483,15 +1614,27 @@ static int ist_add(const OptionsContext *o, Demuxer *d, AVStream *st, AVDictiona
av_dict_set_int(&ds->decoder_opts, "apply_cropping", av_dict_set_int(&ds->decoder_opts, "apply_cropping",
ds->apply_cropping && ds->apply_cropping != CROP_CONTAINER, 0); ds->apply_cropping && ds->apply_cropping != CROP_CONTAINER, 0);
av_bprint_init(&bp, 0, AV_BPRINT_SIZE_AUTOMATIC);
if (ds->force_display_matrix) { if (ds->force_display_matrix) {
char buf[32];
if (av_dict_get(ds->decoder_opts, "side_data_prefer_packet", NULL, 0)) if (av_dict_get(ds->decoder_opts, "side_data_prefer_packet", NULL, 0))
buf[0] = ','; av_bprintf(&bp, ",");
else av_bprintf(&bp, "displaymatrix");
buf[0] = '\0';
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) {
if (bp.len || av_dict_get(ds->decoder_opts, "side_data_prefer_packet", NULL, 0))
av_bprintf(&bp, ",");
av_bprintf(&bp, "mastering_display_metadata");
}
if (ds->force_content_light) {
if (bp.len || av_dict_get(ds->decoder_opts, "side_data_prefer_packet", NULL, 0))
av_bprintf(&bp, ",");
av_bprintf(&bp, "content_light_level");
}
if (bp.len) {
av_bprint_finalize(&bp, NULL);
av_dict_set(&ds->decoder_opts, "side_data_prefer_packet", bp.str, AV_DICT_APPEND);
}
/* Attached pics are sparse, therefore we would not want to delay their decoding /* Attached pics are sparse, therefore we would not want to delay their decoding
* till EOF. */ * till EOF. */
if (ist->st->disposition & AV_DISPOSITION_ATTACHED_PIC) if (ist->st->disposition & AV_DISPOSITION_ATTACHED_PIC)

View File

@@ -1940,6 +1940,12 @@ const OptionDef options[] = {
{ .off = OFFSET(display_vflips) }, { .off = OFFSET(display_vflips) },
"set display vertical flip for stream(s) " "set display vertical flip for stream(s) "
"(overrides any display rotation if it is not set)"}, "(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) },
"set SMPTE2084 mastering display color volume info" },
{ "content_light", OPT_TYPE_STRING, OPT_VIDEO | OPT_PERSTREAM | OPT_INPUT | OPT_EXPERT,
{ .off = OFFSET(content_lights) },
"set SMPTE2084 Max CLL and Max FALL values" },
{ "vn", OPT_TYPE_BOOL, OPT_VIDEO | OPT_OFFSET | OPT_INPUT | OPT_OUTPUT, { "vn", OPT_TYPE_BOOL, OPT_VIDEO | OPT_OFFSET | OPT_INPUT | OPT_OUTPUT,
{ .off = OFFSET(video_disable) }, { .off = OFFSET(video_disable) },
"disable video" }, "disable video" },