Video: Save colorspace metadata based on display, remove HDR option
Now that there are Rec.2100 PQ and HLG displays, the additional HDR option for video export is redundant. Typically you would now select a HDR display early on and do all your video editing with it enabled. For saving a HDR video, the encoding panel will now show the name of the color space, and warn when the video codec or color depth is incompatible. Since this is now based on interop IDs for the dislpay color spaces, we can map more of those to the appropriate CICP code. This works fine for Display P3, in my tests it looks identical to sRGB except that the wide gamut colors are preserved. However Rec.1886 and Rec.2020 are problematic regarding the transfer function, although the latter at least has the correct primaries now. So it should be a net improvement and this could be looked at later if anyone wants. --- Background: * Rec.1886 and Rec.2020 display color spaces in Blender use gamma 2.4. * BT.709 trc is almost the same as gamma 2.4, so seems like the correct choice. * We already write sRGB with BT.709 trc, which seems wrong. * Yet sRGB matches exactly between Blender display and QuickTime, while Rec.1886 and Rec.2020 do not. * Display P3 with BT.709 trc matches sRGB with BT.709 trc, just adding the wide gamut colors. So that is what is used for now. Also using the sRGB trc the file is not recognized by QuickTime. There is apparently a well known "QuickTime gamma shift" issue, where the interpretation of the BT.709 trc is different than other platforms. And you need to do workarounds like writing gamma 2.4 metadata outside of CICP to get things to display properly on macOS. Not that QuickTime is necessarily the reference we should target, but just to explain that changing the previous behavior would have consequences, and so it this commit leaves that unchanged. Pull Request: https://projects.blender.org/blender/blender/pulls/145373
This commit is contained in:
@@ -536,12 +536,30 @@ class RENDER_PT_encoding_video(RenderOutputButtonsPanel, Panel):
|
||||
if use_bpp:
|
||||
layout.prop(image_settings, "color_depth", expand=True)
|
||||
|
||||
# HDR options.
|
||||
use_hdr = needs_codec and ffmpeg.codec in {
|
||||
'H265', 'AV1'} and image_settings.color_depth in {
|
||||
'10', '12'} and image_settings.color_mode != 'BW'
|
||||
if use_hdr:
|
||||
layout.prop(ffmpeg, "video_hdr")
|
||||
# Color space
|
||||
if image_settings.color_management == 'OVERRIDE':
|
||||
display_settings = image_settings.display_settings
|
||||
view_settings = image_settings.view_settings
|
||||
else:
|
||||
display_settings = context.scene.display_settings
|
||||
view_settings = context.scene.view_settings
|
||||
|
||||
split = layout.split(factor=0.4)
|
||||
col = split.column()
|
||||
col.alignment = 'RIGHT'
|
||||
col.label(text="Color Space")
|
||||
|
||||
col = split.column()
|
||||
row = col.row()
|
||||
row.enabled = False
|
||||
row.prop(display_settings, "display_device", text="")
|
||||
|
||||
# HDR compatibility
|
||||
if view_settings.is_hdr:
|
||||
if not needs_codec or ffmpeg.codec not in {'H265', 'AV1'}:
|
||||
col.label(text="HDR needs H.265 or AV1", icon='ERROR')
|
||||
elif image_settings.color_depth not in {'10', '12'}:
|
||||
col.label(text="HDR needs 10 or 12 bits", icon='ERROR')
|
||||
|
||||
if ffmpeg.codec == 'DNXHD':
|
||||
layout.prop(ffmpeg, "use_lossless_output")
|
||||
|
||||
@@ -62,18 +62,6 @@ const char *IMB_colormanagement_get_float_colorspace(const ImBuf *ibuf);
|
||||
const char *IMB_colormanagement_get_rect_colorspace(const ImBuf *ibuf);
|
||||
const char *IMB_colormanagement_space_from_filepath_rules(const char *filepath);
|
||||
|
||||
/* Get colorspace name used for Rec.2100 PQ Display conversion.
|
||||
*
|
||||
* Searches for one of the color spaces or aliases: Rec.2100-PQ, Rec.2100-PQ - Display, rec2100_pq,
|
||||
* rec2100_pq_display. If none found returns nullptr. */
|
||||
const char *IMB_colormanagement_get_rec2100_pq_display_colorspace();
|
||||
|
||||
/* Get colorspace name used for Rec.2100 HLG Display conversion.
|
||||
*
|
||||
* Searches for one of the color spaces or aliases: Rec.2100-HLG, Rec.2100-HLG - Display,
|
||||
* rec2100_hlg, rec2100_hlg_display. If none found returns nullptr. */
|
||||
const char *IMB_colormanagement_get_rec2100_hlg_display_colorspace();
|
||||
|
||||
const ColorSpace *IMB_colormanagement_space_get_named(const char *name);
|
||||
bool IMB_colormanagement_space_is_data(const ColorSpace *colorspace);
|
||||
bool IMB_colormanagement_space_is_scene_linear(const ColorSpace *colorspace);
|
||||
|
||||
@@ -1235,35 +1235,6 @@ const char *IMB_colormanagement_space_from_filepath_rules(const char *filepath)
|
||||
return g_config->get_color_space_from_filepath(filepath);
|
||||
}
|
||||
|
||||
static const char *get_first_resolved_colorspace_name(const blender::Span<const char *> names)
|
||||
{
|
||||
for (const char *name : names) {
|
||||
const ColorSpace *colorspace = IMB_colormanagement_space_get_named(name);
|
||||
if (colorspace) {
|
||||
return colorspace->name().c_str();
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const char *IMB_colormanagement_get_rec2100_pq_display_colorspace()
|
||||
{
|
||||
return get_first_resolved_colorspace_name({"Rec.2100-PQ",
|
||||
"Rec.2100-PQ - Display",
|
||||
"rec2100_pq",
|
||||
"rec2100_pq_display",
|
||||
"pq_rec2020_display"});
|
||||
}
|
||||
|
||||
const char *IMB_colormanagement_get_rec2100_hlg_display_colorspace()
|
||||
{
|
||||
return get_first_resolved_colorspace_name({"Rec.2100-HLG",
|
||||
"Rec.2100-HLG - Display",
|
||||
"rec2100_hlg",
|
||||
"rec2100_hlg_display",
|
||||
"hlg_rec2020_display"});
|
||||
}
|
||||
|
||||
const ColorSpace *IMB_colormanagement_space_get_named(const char *name)
|
||||
{
|
||||
return g_config->get_color_space(name);
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "BLI_path_utils.hh"
|
||||
#include "BLI_span.hh"
|
||||
#include "BLI_string.h"
|
||||
#include "BLI_string_utf8.h"
|
||||
#include "BLI_task.hh"
|
||||
@@ -124,28 +123,56 @@ static void probe_video_colorspace(MovieReader *anim, char r_colorspace_name[IM_
|
||||
|
||||
#ifdef WITH_FFMPEG
|
||||
const AVColorTransferCharacteristic color_trc = anim->pCodecCtx->color_trc;
|
||||
const AVColorSpace colorspace = anim->pCodecCtx->colorspace;
|
||||
const AVColorPrimaries color_primaries = anim->pCodecCtx->color_primaries;
|
||||
|
||||
if (color_trc == AVCOL_TRC_ARIB_STD_B67 && color_primaries == AVCOL_PRI_BT2020 &&
|
||||
colorspace == AVCOL_SPC_BT2020_NCL)
|
||||
/* ASWF Color Interop Forum defined display spaces. The CICP codes there match the enum
|
||||
* values defined by ffmpeg. Keep in sync with movie_write.cc.
|
||||
*
|
||||
* Note that pCodecCtx->color_space is ignored because it is only about choice of YUV
|
||||
* encoding for best compression, and ffmpeg will decode to RGB for us. */
|
||||
blender::StringRefNull interop_id;
|
||||
|
||||
if (color_primaries == AVCOL_PRI_BT2020 && color_trc == AVCOL_TRC_SMPTEST2084) {
|
||||
interop_id = "pq_rec2020_display";
|
||||
}
|
||||
else if (color_primaries == AVCOL_PRI_BT2020 && color_trc == AVCOL_TRC_ARIB_STD_B67) {
|
||||
interop_id = "hlg_rec2020_display";
|
||||
}
|
||||
else if ((color_primaries == AVCOL_PRI_SMPTE432 && color_trc == AVCOL_TRC_IEC61966_2_1) ||
|
||||
(color_primaries == AVCOL_PRI_SMPTE432 && color_trc == AVCOL_TRC_BT709))
|
||||
{
|
||||
const char *hlg_name = IMB_colormanagement_get_rec2100_hlg_display_colorspace();
|
||||
if (hlg_name) {
|
||||
BLI_strncpy_utf8(r_colorspace_name, hlg_name, IM_MAX_SPACE);
|
||||
}
|
||||
interop_id = "srgb_p3d65_display";
|
||||
}
|
||||
else if (color_primaries == AVCOL_PRI_SMPTE432 && color_trc == AVCOL_TRC_SMPTEST2084) {
|
||||
interop_id = "pq_p3d65_display";
|
||||
}
|
||||
else if (color_primaries == AVCOL_PRI_SMPTE432 && color_trc == AVCOL_TRC_SMPTE428) {
|
||||
interop_id = "g26_p3d65_display";
|
||||
}
|
||||
else if (color_primaries == AVCOL_PRI_BT709 && color_trc == AVCOL_TRC_GAMMA22) {
|
||||
interop_id = "g22_rec709_display";
|
||||
}
|
||||
else if (color_primaries == AVCOL_PRI_BT2020 && color_trc == AVCOL_TRC_BT709) {
|
||||
interop_id = "g24_rec2020_display";
|
||||
}
|
||||
else if (color_primaries == AVCOL_PRI_BT709 && color_trc == AVCOL_TRC_IEC61966_2_1) {
|
||||
interop_id = "srgb_rec709_display";
|
||||
}
|
||||
else if (color_primaries == AVCOL_PRI_BT709 && color_trc == AVCOL_TRC_BT709) {
|
||||
/* Arguably this should be g24_rec709_display, but we write sRGB like this. */
|
||||
interop_id = "srgb_rec709_display";
|
||||
}
|
||||
|
||||
if (interop_id.is_empty()) {
|
||||
return;
|
||||
}
|
||||
const ColorSpace *colorspace = IMB_colormanagement_space_from_interop_id(interop_id);
|
||||
if (colorspace == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (color_trc == AVCOL_TRC_SMPTEST2084 && color_primaries == AVCOL_PRI_BT2020 &&
|
||||
colorspace == AVCOL_SPC_BT2020_NCL)
|
||||
{
|
||||
const char *pq_name = IMB_colormanagement_get_rec2100_pq_display_colorspace();
|
||||
if (pq_name) {
|
||||
BLI_strncpy_utf8(r_colorspace_name, pq_name, IM_MAX_SPACE);
|
||||
}
|
||||
return;
|
||||
}
|
||||
BLI_strncpy_utf8(
|
||||
r_colorspace_name, IMB_colormanagement_colorspace_get_name(colorspace), IM_MAX_SPACE);
|
||||
#endif /* WITH_FFMPEG */
|
||||
}
|
||||
|
||||
|
||||
@@ -212,7 +212,7 @@ static bool write_video_frame(MovieWriter *context, AVFrame *frame, ReportList *
|
||||
*
|
||||
* No color space conversion is performed. The result float buffer might be in a non-linear space
|
||||
* denoted by the float_buffer.colorspace. */
|
||||
static ImBuf *alloc_imbuf_for_hdr_transform(const ImBuf *input_ibuf)
|
||||
static ImBuf *alloc_imbuf_for_colorspace_transform(const ImBuf *input_ibuf)
|
||||
{
|
||||
if (!input_ibuf) {
|
||||
return nullptr;
|
||||
@@ -261,103 +261,22 @@ static ImBuf *alloc_imbuf_for_hdr_transform(const ImBuf *input_ibuf)
|
||||
return result_ibuf;
|
||||
}
|
||||
|
||||
static ImBuf *do_pq_transform(const ImBuf *input_ibuf)
|
||||
{
|
||||
ImBuf *ibuf = alloc_imbuf_for_hdr_transform(input_ibuf);
|
||||
if (!ibuf) {
|
||||
/* Error in input or allocation has failed. */
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/* Get `Rec.2100-PQ Display` or its alias from the OpenColorIO configuration. */
|
||||
const char *rec2100_pq_colorspace = IMB_colormanagement_get_rec2100_pq_display_colorspace();
|
||||
if (!rec2100_pq_colorspace) {
|
||||
/* TODO(sergey): Error reporting if the colorspace is not found. */
|
||||
return ibuf;
|
||||
}
|
||||
|
||||
/* Convert from the current floating point buffer colorspace to Rec.2100-PQ. */
|
||||
IMB_colormanagement_transform_float(ibuf->float_buffer.data,
|
||||
ibuf->x,
|
||||
ibuf->y,
|
||||
ibuf->channels,
|
||||
IMB_colormanagement_get_float_colorspace(input_ibuf),
|
||||
rec2100_pq_colorspace,
|
||||
IMB_alpha_affects_rgb(ibuf));
|
||||
|
||||
return ibuf;
|
||||
}
|
||||
|
||||
static ImBuf *do_hlg_transform(const ImBuf *input_ibuf)
|
||||
{
|
||||
ImBuf *ibuf = alloc_imbuf_for_hdr_transform(input_ibuf);
|
||||
if (!ibuf) {
|
||||
/* Error in input or allocation has failed. */
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/* Get `Rec.2100-HLG Display` or its alias from the OpenColorIO configuration.
|
||||
* The color space is supposed to be Rec.2100-HLG, 1000 nit. */
|
||||
const char *rec2100_hlg_colorspace = IMB_colormanagement_get_rec2100_hlg_display_colorspace();
|
||||
if (!rec2100_hlg_colorspace) {
|
||||
/* TODO(sergey): Error reporting if the colorspace is not found. */
|
||||
return ibuf;
|
||||
}
|
||||
|
||||
/* Convert from the current floating point buffer colorspace to Rec.2100-HLG, 1000 nit. */
|
||||
IMB_colormanagement_transform_float(ibuf->float_buffer.data,
|
||||
ibuf->x,
|
||||
ibuf->y,
|
||||
ibuf->channels,
|
||||
IMB_colormanagement_get_float_colorspace(input_ibuf),
|
||||
rec2100_hlg_colorspace,
|
||||
IMB_alpha_affects_rgb(ibuf));
|
||||
|
||||
return ibuf;
|
||||
}
|
||||
|
||||
static const ImBuf *do_hdr_transform_if_needed(MovieWriter *context, const ImBuf *input_ibuf)
|
||||
{
|
||||
if (!input_ibuf) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!context || !context->video_codec) {
|
||||
return input_ibuf;
|
||||
}
|
||||
|
||||
const AVCodecContext &codec = *context->video_codec;
|
||||
|
||||
const AVColorTransferCharacteristic color_trc = codec.color_trc;
|
||||
const AVColorSpace colorspace = codec.colorspace;
|
||||
const AVColorPrimaries color_primaries = codec.color_primaries;
|
||||
|
||||
if (color_trc == AVCOL_TRC_SMPTEST2084 && color_primaries == AVCOL_PRI_BT2020 &&
|
||||
colorspace == AVCOL_SPC_BT2020_NCL)
|
||||
{
|
||||
return do_pq_transform(input_ibuf);
|
||||
}
|
||||
|
||||
if (color_trc == AVCOL_TRC_ARIB_STD_B67 && color_primaries == AVCOL_PRI_BT2020 &&
|
||||
colorspace == AVCOL_SPC_BT2020_NCL)
|
||||
{
|
||||
return do_hlg_transform(input_ibuf);
|
||||
}
|
||||
|
||||
return input_ibuf;
|
||||
}
|
||||
|
||||
/* read and encode a frame of video from the buffer */
|
||||
static AVFrame *generate_video_frame(MovieWriter *context, const ImBuf *input_ibuf)
|
||||
{
|
||||
const ImBuf *image = do_hdr_transform_if_needed(context, input_ibuf);
|
||||
/* Use float input if needed. */
|
||||
const bool use_float =
|
||||
context->img_convert_frame != nullptr &&
|
||||
!(context->img_convert_frame->format == AV_PIX_FMT_RGBA &&
|
||||
ELEM(context->img_convert_frame->colorspace, AVCOL_SPC_RGB, AVCOL_SPC_UNSPECIFIED));
|
||||
|
||||
const ImBuf *image = (use_float && input_ibuf->float_buffer.data == nullptr) ?
|
||||
alloc_imbuf_for_colorspace_transform(input_ibuf) :
|
||||
input_ibuf;
|
||||
|
||||
const uint8_t *pixels = image->byte_buffer.data;
|
||||
const float *pixels_fl = image->float_buffer.data;
|
||||
|
||||
/* Use float input if needed. */
|
||||
const bool use_float = context->img_convert_frame != nullptr &&
|
||||
context->img_convert_frame->format != AV_PIX_FMT_RGBA;
|
||||
if ((!use_float && (pixels == nullptr)) || (use_float && (pixels_fl == nullptr))) {
|
||||
if (image != input_ibuf) {
|
||||
IMB_freeImBuf(const_cast<ImBuf *>(image));
|
||||
@@ -800,6 +719,81 @@ static void set_quality_rate_options(const MovieWriter *context,
|
||||
}
|
||||
}
|
||||
|
||||
static void set_colorspace_options(AVCodecContext *c, blender::StringRefNull interop_id)
|
||||
{
|
||||
const AVPixFmtDescriptor *pix_fmt_desc = av_pix_fmt_desc_get(c->pix_fmt);
|
||||
const bool is_rgb_format = (pix_fmt_desc->flags & AV_PIX_FMT_FLAG_RGB);
|
||||
|
||||
/* Full range for most color spaces. */
|
||||
c->color_range = AVCOL_RANGE_JPEG;
|
||||
|
||||
/* ASWF Color Interop Forum defined display spaces. The CICP codes there match the enum
|
||||
* values defined by ffmpeg. Keep in sync with movie_read.cc. */
|
||||
if (interop_id == "pq_rec2020_display") {
|
||||
c->color_primaries = AVCOL_PRI_BT2020;
|
||||
c->color_trc = AVCOL_TRC_SMPTEST2084;
|
||||
c->colorspace = AVCOL_SPC_BT2020_NCL;
|
||||
}
|
||||
else if (interop_id == "hlg_rec2020_display") {
|
||||
c->color_primaries = AVCOL_PRI_BT2020;
|
||||
c->color_trc = AVCOL_TRC_ARIB_STD_B67;
|
||||
c->colorspace = AVCOL_SPC_BT2020_NCL;
|
||||
}
|
||||
else if (interop_id == "pq_p3d65_display") {
|
||||
c->color_primaries = AVCOL_PRI_SMPTE432;
|
||||
c->color_trc = AVCOL_TRC_SMPTEST2084;
|
||||
c->colorspace = AVCOL_SPC_BT2020_NCL;
|
||||
}
|
||||
else if (interop_id == "g26_p3d65_display") {
|
||||
c->color_primaries = AVCOL_PRI_SMPTE432;
|
||||
c->color_trc = AVCOL_TRC_SMPTE428;
|
||||
c->colorspace = AVCOL_SPC_BT709;
|
||||
}
|
||||
else if (interop_id == "g22_rec709_display") {
|
||||
c->color_primaries = AVCOL_PRI_BT709;
|
||||
c->color_trc = AVCOL_TRC_GAMMA22;
|
||||
c->colorspace = AVCOL_SPC_BT709;
|
||||
}
|
||||
else if (interop_id == "g24_rec2020_display") {
|
||||
/* There is no gamma 2.4 trc, but BT.709 is supposed to be close. But it's not
|
||||
* clear this is right, as we use the same trc for sRGB which is clearly different. */
|
||||
c->color_primaries = AVCOL_PRI_BT2020;
|
||||
c->color_trc = AVCOL_TRC_BT709;
|
||||
c->colorspace = AVCOL_SPC_BT2020_NCL;
|
||||
}
|
||||
else if (interop_id == "g24_rec709_display") {
|
||||
/* There is no gamma 2.4 trc, but BT.709 is supposed to be close. But now this
|
||||
* is identical to how we write sRGB so at least of the two must be wrong? */
|
||||
c->color_primaries = AVCOL_PRI_BT709;
|
||||
c->color_trc = AVCOL_TRC_BT709;
|
||||
c->colorspace = AVCOL_SPC_BT709;
|
||||
}
|
||||
else if (interop_id == "srgb_p3d65_display" || interop_id == "srgbx_p3d65_display") {
|
||||
c->color_primaries = AVCOL_PRI_SMPTE432;
|
||||
/* This should be AVCOL_TRC_IEC61966_2_1, but Quicktime refuses to open the file.
|
||||
* And we're currently also writing srgb_rec709_display the same way. */
|
||||
c->color_trc = AVCOL_TRC_BT709;
|
||||
c->colorspace = AVCOL_SPC_BT709;
|
||||
}
|
||||
/* Don't write sRGB as we weren't doing it before either, but maybe we should. */
|
||||
# if 0
|
||||
else if (interop_id == "srgb_rec709_display") {
|
||||
c->color_primaries = AVCOL_PRI_BT709;
|
||||
c->color_trc = AVCOL_TRC_IEC61966_2_1;
|
||||
c->colorspace = AVCOL_SPC_BT709;
|
||||
}
|
||||
# endif
|
||||
/* If we're not writing RGB, we must write a colorspace to define how
|
||||
* the conversion to YUV happens. */
|
||||
else if (!is_rgb_format) {
|
||||
c->color_primaries = AVCOL_PRI_BT709;
|
||||
c->color_trc = AVCOL_TRC_BT709;
|
||||
c->colorspace = AVCOL_SPC_BT709;
|
||||
/* TODO(sergey): Consider making the range an option to cover more use-cases. */
|
||||
c->color_range = AVCOL_RANGE_MPEG;
|
||||
}
|
||||
}
|
||||
|
||||
static AVStream *alloc_video_stream(MovieWriter *context,
|
||||
const RenderData *rd,
|
||||
const ImageFormatData *imf,
|
||||
@@ -932,14 +926,6 @@ static AVStream *alloc_video_stream(MovieWriter *context,
|
||||
const bool is_12_bpp = imf->depth == R_IMF_CHAN_DEPTH_12;
|
||||
const bool is_16_bpp = imf->depth == R_IMF_CHAN_DEPTH_16;
|
||||
|
||||
eFFMpegVideoHdr hdr = eFFMpegVideoHdr(rd->ffcodecdata.video_hdr);
|
||||
/* Never use HDR for non-10/12 bpp or grayscale outputs. */
|
||||
if ((!is_10_bpp && !is_12_bpp) || rd->im_format.planes == R_IMF_PLANES_BW) {
|
||||
hdr = FFM_VIDEO_HDR_NONE;
|
||||
}
|
||||
const bool is_hdr_pq = hdr == FFM_VIDEO_HDR_REC2100_PQ;
|
||||
const bool is_hdr_hlg = hdr == FFM_VIDEO_HDR_REC2100_HLG;
|
||||
|
||||
if (is_10_bpp) {
|
||||
c->pix_fmt = AV_PIX_FMT_YUV420P10LE;
|
||||
}
|
||||
@@ -1083,29 +1069,14 @@ static AVStream *alloc_video_stream(MovieWriter *context,
|
||||
c->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
|
||||
}
|
||||
|
||||
/* If output pixel format is not RGB(A), setup colorspace metadata. */
|
||||
const AVPixFmtDescriptor *pix_fmt_desc = av_pix_fmt_desc_get(c->pix_fmt);
|
||||
const bool set_bt709 = (pix_fmt_desc->flags & AV_PIX_FMT_FLAG_RGB) == 0;
|
||||
if (is_hdr_pq) {
|
||||
/* TODO(sergey): Consider making the range an option to cover more use-cases. */
|
||||
c->color_range = AVCOL_RANGE_JPEG;
|
||||
c->color_primaries = AVCOL_PRI_BT2020;
|
||||
c->color_trc = AVCOL_TRC_SMPTEST2084;
|
||||
c->colorspace = AVCOL_SPC_BT2020_NCL;
|
||||
}
|
||||
else if (is_hdr_hlg) {
|
||||
/* TODO(sergey): Consider making the range an option to cover more use-cases. */
|
||||
c->color_range = AVCOL_RANGE_JPEG;
|
||||
c->color_primaries = AVCOL_PRI_BT2020;
|
||||
c->color_trc = AVCOL_TRC_ARIB_STD_B67;
|
||||
c->colorspace = AVCOL_SPC_BT2020_NCL;
|
||||
}
|
||||
else if (set_bt709) {
|
||||
c->color_range = AVCOL_RANGE_MPEG;
|
||||
c->color_primaries = AVCOL_PRI_BT709;
|
||||
c->color_trc = AVCOL_TRC_BT709;
|
||||
c->colorspace = AVCOL_SPC_BT709;
|
||||
}
|
||||
/* Set colorspace based on display space of image. */
|
||||
const ColorSpace *display_colorspace = IMB_colormangement_display_get_color_space(
|
||||
&imf->display_settings);
|
||||
const blender::StringRefNull interop_id = (display_colorspace) ?
|
||||
IMB_colormanagement_space_get_interop_id(
|
||||
display_colorspace) :
|
||||
"";
|
||||
set_colorspace_options(c, interop_id);
|
||||
|
||||
/* xasp & yasp got float lately... */
|
||||
|
||||
@@ -1144,46 +1115,29 @@ static AVStream *alloc_video_stream(MovieWriter *context,
|
||||
/* FFMPEG expects its data in the output pixel format. */
|
||||
context->current_frame = alloc_frame(c->pix_fmt, c->width, c->height);
|
||||
|
||||
if (c->pix_fmt == AV_PIX_FMT_RGBA) {
|
||||
/* Output pixel format is the same we use internally, no conversion necessary. */
|
||||
if (c->pix_fmt == AV_PIX_FMT_RGBA && ELEM(c->colorspace, AVCOL_SPC_RGB, AVCOL_SPC_UNSPECIFIED)) {
|
||||
/* Output pixel format and colorspace is the same we use internally, no conversion needed. */
|
||||
context->img_convert_frame = nullptr;
|
||||
context->img_convert_ctx = nullptr;
|
||||
}
|
||||
else {
|
||||
/* Output pixel format is different, allocate frame for conversion.
|
||||
* Setup RGB->YUV conversion with proper coefficients (depending on whether it is SDR BT.709,
|
||||
* or HDR BT.2020). */
|
||||
* Setup RGB->YUV conversion with proper coefficients, depending on range and colorspace. */
|
||||
const AVPixelFormat src_format = is_10_bpp || is_12_bpp || is_16_bpp ? AV_PIX_FMT_GBRAPF32LE :
|
||||
AV_PIX_FMT_RGBA;
|
||||
context->img_convert_frame = alloc_frame(src_format, c->width, c->height);
|
||||
if (is_hdr_pq || is_hdr_hlg) {
|
||||
/* Special conversion for the Rec.2100 PQ and HLG output: the result color space is BT.2020,
|
||||
* and also use full range. */
|
||||
context->img_convert_ctx = ffmpeg_sws_get_context(c->width,
|
||||
c->height,
|
||||
src_format,
|
||||
true,
|
||||
-1,
|
||||
c->width,
|
||||
c->height,
|
||||
c->pix_fmt,
|
||||
true,
|
||||
AVCOL_SPC_BT2020_NCL,
|
||||
SWS_BICUBIC);
|
||||
}
|
||||
else {
|
||||
context->img_convert_ctx = ffmpeg_sws_get_context(c->width,
|
||||
c->height,
|
||||
src_format,
|
||||
false,
|
||||
-1,
|
||||
c->width,
|
||||
c->height,
|
||||
c->pix_fmt,
|
||||
false,
|
||||
set_bt709 ? AVCOL_SPC_BT709 : -1,
|
||||
SWS_BICUBIC);
|
||||
}
|
||||
context->img_convert_ctx = ffmpeg_sws_get_context(
|
||||
c->width,
|
||||
c->height,
|
||||
src_format,
|
||||
true,
|
||||
-1,
|
||||
c->width,
|
||||
c->height,
|
||||
c->pix_fmt,
|
||||
c->color_range == AVCOL_RANGE_JPEG,
|
||||
c->colorspace != AVCOL_SPC_RGB ? c->colorspace : -1,
|
||||
SWS_BICUBIC);
|
||||
}
|
||||
|
||||
avcodec_parameters_from_context(st->codecpar, c);
|
||||
|
||||
@@ -159,6 +159,9 @@ LibOCIOColorSpace::LibOCIOColorSpace(const int index,
|
||||
else if (alias == "rec2100_hlg_display") {
|
||||
interop_id_ = "hlg_rec2020_display";
|
||||
}
|
||||
else if (alias == "st2084_p3d65_display") {
|
||||
interop_id_ = "pq_p3d65_display";
|
||||
}
|
||||
else if (alias == "lin_rec709_srgb" || alias == "lin_rec709") {
|
||||
interop_id_ = "lin_rec709_scene";
|
||||
}
|
||||
|
||||
@@ -155,12 +155,6 @@ typedef enum IMB_Ffmpeg_Codec_ID {
|
||||
FFMPEG_CODEC_ID_OPUS = 86076,
|
||||
} IMB_Ffmpeg_Codec_ID;
|
||||
|
||||
typedef enum eFFMpegVideoHdr {
|
||||
FFM_VIDEO_HDR_NONE = 0,
|
||||
FFM_VIDEO_HDR_REC2100_HLG = 1,
|
||||
FFM_VIDEO_HDR_REC2100_PQ = 2,
|
||||
} eFFMpegVideoHdr;
|
||||
|
||||
typedef struct FFMpegCodecData {
|
||||
int type;
|
||||
int codec; /* Use `codec_id_get()` instead! IMB_Ffmpeg_Codec_ID */
|
||||
@@ -184,7 +178,7 @@ typedef struct FFMpegCodecData {
|
||||
int rc_buffer_size;
|
||||
int mux_packet_size;
|
||||
int mux_rate;
|
||||
int video_hdr; /* eFFMpegVideoHdr */
|
||||
int _pad;
|
||||
|
||||
#ifdef __cplusplus
|
||||
IMB_Ffmpeg_Codec_ID codec_id_get() const
|
||||
|
||||
@@ -6566,21 +6566,6 @@ static void rna_def_scene_ffmpeg_settings(BlenderRNA *brna)
|
||||
{0, nullptr, 0, nullptr, nullptr},
|
||||
};
|
||||
|
||||
static const EnumPropertyItem ffmpeg_hdr_items[] = {
|
||||
{FFM_VIDEO_HDR_NONE, "NONE", 0, "None", "No High Dynamic Range"},
|
||||
{FFM_VIDEO_HDR_REC2100_PQ,
|
||||
"REQ2100_PQ",
|
||||
0,
|
||||
"Rec.2100 PQ",
|
||||
"Rec.2100 color space with Perceptual Quantizer HDR encoding"},
|
||||
{FFM_VIDEO_HDR_REC2100_HLG,
|
||||
"REQ2100_HLG",
|
||||
0,
|
||||
"Rec.2100 HLG",
|
||||
"Rec.2100 color space with Hybrid-Log Gamma HDR encoding"},
|
||||
{0, nullptr, 0, nullptr, nullptr},
|
||||
};
|
||||
|
||||
static const EnumPropertyItem ffmpeg_audio_codec_items[] = {
|
||||
{FFMPEG_CODEC_ID_NONE,
|
||||
"NONE",
|
||||
@@ -6643,14 +6628,6 @@ static void rna_def_scene_ffmpeg_settings(BlenderRNA *brna)
|
||||
RNA_def_property_ui_text(prop, "Bitrate", "Video bitrate (kbit/s)");
|
||||
RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, nullptr);
|
||||
|
||||
prop = RNA_def_property(srna, "video_hdr", PROP_ENUM, PROP_NONE);
|
||||
RNA_def_property_enum_sdna(prop, nullptr, "video_hdr");
|
||||
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
|
||||
RNA_def_property_enum_items(prop, ffmpeg_hdr_items);
|
||||
RNA_def_property_enum_default(prop, FFM_VIDEO_HDR_NONE);
|
||||
RNA_def_property_ui_text(prop, "HDR", "High Dynamic Range options");
|
||||
RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, nullptr);
|
||||
|
||||
prop = RNA_def_property(srna, "minrate", PROP_INT, PROP_NONE);
|
||||
RNA_def_property_int_sdna(prop, nullptr, "rc_min_rate");
|
||||
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
.blend files used to generate:
|
||||
- hdr_simple_export_hlg_12bit.mov
|
||||
- hdr_simple_export_pq_12bit.mov
|
||||
- sdr_simple_export_p3_aces_10bit.mov
|
||||
|
||||
Step 1:
|
||||
Open and render hdr_simple_still_test_file.blend
|
||||
It will generate hdr_simple_still_test_file.exr file
|
||||
|
||||
Step 2:
|
||||
Open and render hdr_simple_export_hlg_12bit.blend and hdr_simple_export_pq_12bit.blend.
|
||||
Open and render other blend files.
|
||||
These files generate videos in the out/ folder.
|
||||
|
||||
Step 3:
|
||||
Copy files from the out/ folder to their resired final destination.
|
||||
Copy files from the out/ folder to their desired final destination.
|
||||
|
||||
BIN
tests/files/sequence_editing/ffmpeg/media/generate/hdr_simple_export_hlg_12bit.blend
(Stored with Git LFS)
BIN
tests/files/sequence_editing/ffmpeg/media/generate/hdr_simple_export_hlg_12bit.blend
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/sequence_editing/ffmpeg/media/generate/hdr_simple_export_pq_12bit.blend
(Stored with Git LFS)
BIN
tests/files/sequence_editing/ffmpeg/media/generate/hdr_simple_export_pq_12bit.blend
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/sequence_editing/ffmpeg/media/generate/sdr_simple_export_p3_aces_10bit.blend
(Stored with Git LFS)
Normal file
BIN
tests/files/sequence_editing/ffmpeg/media/generate/sdr_simple_export_p3_aces_10bit.blend
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
tests/files/sequence_editing/ffmpeg/media/sdr_simple_export_p3_aces_10bit.mov
(Stored with Git LFS)
Normal file
BIN
tests/files/sequence_editing/ffmpeg/media/sdr_simple_export_p3_aces_10bit.mov
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
tests/files/sequence_editing/ffmpeg/reference/sdr_input_p3_aces_10bit.png
(Stored with Git LFS)
Normal file
BIN
tests/files/sequence_editing/ffmpeg/reference/sdr_input_p3_aces_10bit.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
tests/files/sequence_editing/ffmpeg/sdr_input_p3_aces_10bit.blend
(Stored with Git LFS)
Normal file
BIN
tests/files/sequence_editing/ffmpeg/sdr_input_p3_aces_10bit.blend
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
tests/files/sequence_editing/video_output/reference/video_output_hlg_12bit_agx_mov.png
(Stored with Git LFS)
BIN
tests/files/sequence_editing/video_output/reference/video_output_hlg_12bit_agx_mov.png
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/sequence_editing/video_output/reference/video_output_hlg_12bit_mov.png
(Stored with Git LFS)
BIN
tests/files/sequence_editing/video_output/reference/video_output_hlg_12bit_mov.png
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/sequence_editing/video_output/reference/video_output_p3_10bit_aces_mov.png
(Stored with Git LFS)
Normal file
BIN
tests/files/sequence_editing/video_output/reference/video_output_p3_10bit_aces_mov.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
tests/files/sequence_editing/video_output/reference/video_output_pq_12bit_agx_mov.png
(Stored with Git LFS)
BIN
tests/files/sequence_editing/video_output/reference/video_output_pq_12bit_agx_mov.png
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/sequence_editing/video_output/reference/video_output_pq_12bit_mov.png
(Stored with Git LFS)
BIN
tests/files/sequence_editing/video_output/reference/video_output_pq_12bit_mov.png
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/sequence_editing/video_output/video_output_hlg_12bit_agx_mov.blend
(Stored with Git LFS)
BIN
tests/files/sequence_editing/video_output/video_output_hlg_12bit_agx_mov.blend
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/sequence_editing/video_output/video_output_hlg_12bit_mov.blend
(Stored with Git LFS)
BIN
tests/files/sequence_editing/video_output/video_output_hlg_12bit_mov.blend
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/sequence_editing/video_output/video_output_p3_10bit_aces_mov.blend
(Stored with Git LFS)
Normal file
BIN
tests/files/sequence_editing/video_output/video_output_p3_10bit_aces_mov.blend
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
tests/files/sequence_editing/video_output/video_output_pq_12bit_agx_mov.blend
(Stored with Git LFS)
BIN
tests/files/sequence_editing/video_output/video_output_pq_12bit_agx_mov.blend
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/sequence_editing/video_output/video_output_pq_12bit_mov.blend
(Stored with Git LFS)
BIN
tests/files/sequence_editing/video_output/video_output_pq_12bit_mov.blend
(Stored with Git LFS)
Binary file not shown.
@@ -37,6 +37,11 @@ class FFmpegHDRColorspace(MovieInputTest):
|
||||
|
||||
self.assertEqual(self.get_movie_colorspace(prefix / "hdr_simple_export_hlg_12bit.mov"), "Rec.2100-HLG")
|
||||
|
||||
def test_p3(self):
|
||||
prefix = TEST_DIR / Path("ffmpeg") / "media"
|
||||
|
||||
self.assertEqual(self.get_movie_colorspace(prefix / "sdr_simple_export_p3_aces_10bit.mov"), "Display P3")
|
||||
|
||||
|
||||
def main():
|
||||
global TEST_DIR
|
||||
|
||||
@@ -12,6 +12,7 @@ from pathlib import Path
|
||||
BLOCKLIST = [
|
||||
"hdr_simple_export_hlg_12bit.blend",
|
||||
"hdr_simple_export_pq_12bit.blend",
|
||||
"sdr_simple_export_p3_aces_10bit.blend",
|
||||
"hdr_simple_still_test_file.blend",
|
||||
]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user