From 5cc6ad6afeffe14ab264bebeee920140333a55ad Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Fri, 29 Aug 2025 01:29:57 +0200 Subject: [PATCH] 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 --- scripts/startup/bl_ui/properties_output.py | 30 +- source/blender/imbuf/IMB_colormanagement.hh | 12 - .../blender/imbuf/intern/colormanagement.cc | 29 -- .../blender/imbuf/movie/intern/movie_read.cc | 61 ++-- .../blender/imbuf/movie/intern/movie_write.cc | 262 ++++++++---------- .../intern/libocio/libocio_colorspace.cc | 3 + source/blender/makesdna/DNA_scene_types.h | 8 +- source/blender/makesrna/intern/rna_scene.cc | 23 -- .../ffmpeg/media/generate/README.txt | 5 +- .../hdr_simple_export_hlg_12bit.blend | 4 +- .../generate/hdr_simple_export_pq_12bit.blend | 4 +- .../sdr_simple_export_p3_aces_10bit.blend | 3 + .../media/sdr_simple_export_p3_aces_10bit.mov | 3 + .../reference/sdr_input_p3_aces_10bit.png | 3 + .../ffmpeg/sdr_input_p3_aces_10bit.blend | 3 + .../video_output_hlg_12bit_agx_mov.png | 4 +- .../reference/video_output_hlg_12bit_mov.png | 4 +- .../video_output_p3_10bit_aces_mov.png | 3 + .../video_output_pq_12bit_agx_mov.png | 4 +- .../reference/video_output_pq_12bit_mov.png | 4 +- .../video_output_hlg_12bit_agx_mov.blend | 4 +- .../video_output_hlg_12bit_mov.blend | 4 +- .../video_output_p3_10bit_aces_mov.blend | 3 + .../video_output_pq_12bit_agx_mov.blend | 4 +- .../video_output_pq_12bit_mov.blend | 4 +- tests/python/sequencer_input_colorspace.py | 5 + tests/python/sequencer_render_tests.py | 1 + 27 files changed, 227 insertions(+), 270 deletions(-) create mode 100644 tests/files/sequence_editing/ffmpeg/media/generate/sdr_simple_export_p3_aces_10bit.blend create mode 100644 tests/files/sequence_editing/ffmpeg/media/sdr_simple_export_p3_aces_10bit.mov create mode 100644 tests/files/sequence_editing/ffmpeg/reference/sdr_input_p3_aces_10bit.png create mode 100644 tests/files/sequence_editing/ffmpeg/sdr_input_p3_aces_10bit.blend create mode 100644 tests/files/sequence_editing/video_output/reference/video_output_p3_10bit_aces_mov.png create mode 100644 tests/files/sequence_editing/video_output/video_output_p3_10bit_aces_mov.blend diff --git a/scripts/startup/bl_ui/properties_output.py b/scripts/startup/bl_ui/properties_output.py index bb6de4ab843..6aef3744192 100644 --- a/scripts/startup/bl_ui/properties_output.py +++ b/scripts/startup/bl_ui/properties_output.py @@ -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") diff --git a/source/blender/imbuf/IMB_colormanagement.hh b/source/blender/imbuf/IMB_colormanagement.hh index d0be8819072..67266d6d8ae 100644 --- a/source/blender/imbuf/IMB_colormanagement.hh +++ b/source/blender/imbuf/IMB_colormanagement.hh @@ -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); diff --git a/source/blender/imbuf/intern/colormanagement.cc b/source/blender/imbuf/intern/colormanagement.cc index 1802115cc22..a6b4083cb29 100644 --- a/source/blender/imbuf/intern/colormanagement.cc +++ b/source/blender/imbuf/intern/colormanagement.cc @@ -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 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); diff --git a/source/blender/imbuf/movie/intern/movie_read.cc b/source/blender/imbuf/movie/intern/movie_read.cc index e56cbad0ffd..28ea0f602c6 100644 --- a/source/blender/imbuf/movie/intern/movie_read.cc +++ b/source/blender/imbuf/movie/intern/movie_read.cc @@ -16,7 +16,6 @@ #include #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 */ } diff --git a/source/blender/imbuf/movie/intern/movie_write.cc b/source/blender/imbuf/movie/intern/movie_write.cc index ea8371f909c..0344fd9f464 100644 --- a/source/blender/imbuf/movie/intern/movie_write.cc +++ b/source/blender/imbuf/movie/intern/movie_write.cc @@ -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(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); diff --git a/source/blender/imbuf/opencolorio/intern/libocio/libocio_colorspace.cc b/source/blender/imbuf/opencolorio/intern/libocio/libocio_colorspace.cc index 05ba10eb6c9..d22b29da55d 100644 --- a/source/blender/imbuf/opencolorio/intern/libocio/libocio_colorspace.cc +++ b/source/blender/imbuf/opencolorio/intern/libocio/libocio_colorspace.cc @@ -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"; } diff --git a/source/blender/makesdna/DNA_scene_types.h b/source/blender/makesdna/DNA_scene_types.h index f94c165e992..09b05694865 100644 --- a/source/blender/makesdna/DNA_scene_types.h +++ b/source/blender/makesdna/DNA_scene_types.h @@ -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 diff --git a/source/blender/makesrna/intern/rna_scene.cc b/source/blender/makesrna/intern/rna_scene.cc index a169a3973ab..438d8f67cef 100644 --- a/source/blender/makesrna/intern/rna_scene.cc +++ b/source/blender/makesrna/intern/rna_scene.cc @@ -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); diff --git a/tests/files/sequence_editing/ffmpeg/media/generate/README.txt b/tests/files/sequence_editing/ffmpeg/media/generate/README.txt index 81d076fc71a..0036f5b5d62 100644 --- a/tests/files/sequence_editing/ffmpeg/media/generate/README.txt +++ b/tests/files/sequence_editing/ffmpeg/media/generate/README.txt @@ -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. diff --git a/tests/files/sequence_editing/ffmpeg/media/generate/hdr_simple_export_hlg_12bit.blend b/tests/files/sequence_editing/ffmpeg/media/generate/hdr_simple_export_hlg_12bit.blend index c2b7e3e7fca..2c914c92013 100644 --- a/tests/files/sequence_editing/ffmpeg/media/generate/hdr_simple_export_hlg_12bit.blend +++ b/tests/files/sequence_editing/ffmpeg/media/generate/hdr_simple_export_hlg_12bit.blend @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:981c1e83f0d6d9065f04acca51c9e74094d1975d7d554677a9178c6668602050 -size 314536 +oid sha256:9d6ac027fb8d03d4777efa876ed77ae4f1b003b67020b26f37c33fbc799fbce5 +size 89657 diff --git a/tests/files/sequence_editing/ffmpeg/media/generate/hdr_simple_export_pq_12bit.blend b/tests/files/sequence_editing/ffmpeg/media/generate/hdr_simple_export_pq_12bit.blend index c0903804616..766609494a8 100644 --- a/tests/files/sequence_editing/ffmpeg/media/generate/hdr_simple_export_pq_12bit.blend +++ b/tests/files/sequence_editing/ffmpeg/media/generate/hdr_simple_export_pq_12bit.blend @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0c3f1b7419fd2a445ee0e555c6db5026417b8cc4651aa48415ae98ef05d0a6e9 -size 314536 +oid sha256:fa748ad062a26a2e3b4d4c02ac2df3ea40550788851405e5cf86dfb872802c0a +size 89563 diff --git a/tests/files/sequence_editing/ffmpeg/media/generate/sdr_simple_export_p3_aces_10bit.blend b/tests/files/sequence_editing/ffmpeg/media/generate/sdr_simple_export_p3_aces_10bit.blend new file mode 100644 index 00000000000..1f8e37a9392 --- /dev/null +++ b/tests/files/sequence_editing/ffmpeg/media/generate/sdr_simple_export_p3_aces_10bit.blend @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d6f369bf8037bee8246caa77b2179aec59203d5a34933e468e464342cf3fda33 +size 89473 diff --git a/tests/files/sequence_editing/ffmpeg/media/sdr_simple_export_p3_aces_10bit.mov b/tests/files/sequence_editing/ffmpeg/media/sdr_simple_export_p3_aces_10bit.mov new file mode 100644 index 00000000000..3e75cab16d1 --- /dev/null +++ b/tests/files/sequence_editing/ffmpeg/media/sdr_simple_export_p3_aces_10bit.mov @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fe04e475b2713d23f0b4543e5038658e4394a76a75983c78dbac2ca03f564e5d +size 21715 diff --git a/tests/files/sequence_editing/ffmpeg/reference/sdr_input_p3_aces_10bit.png b/tests/files/sequence_editing/ffmpeg/reference/sdr_input_p3_aces_10bit.png new file mode 100644 index 00000000000..053ed3120d9 --- /dev/null +++ b/tests/files/sequence_editing/ffmpeg/reference/sdr_input_p3_aces_10bit.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:60d7a34fc586b0d55914ebff7b6e5693a281b3be1b40b0f3174caef66c3ffbcf +size 533775 diff --git a/tests/files/sequence_editing/ffmpeg/sdr_input_p3_aces_10bit.blend b/tests/files/sequence_editing/ffmpeg/sdr_input_p3_aces_10bit.blend new file mode 100644 index 00000000000..4dc2e2dda2a --- /dev/null +++ b/tests/files/sequence_editing/ffmpeg/sdr_input_p3_aces_10bit.blend @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:115d78313dfab8acd0139f26228fa729e0073b21f47305873d76b2d46dce7333 +size 88582 diff --git a/tests/files/sequence_editing/video_output/reference/video_output_hlg_12bit_agx_mov.png b/tests/files/sequence_editing/video_output/reference/video_output_hlg_12bit_agx_mov.png index fbdb3c447e1..9ff874bcd84 100644 --- a/tests/files/sequence_editing/video_output/reference/video_output_hlg_12bit_agx_mov.png +++ b/tests/files/sequence_editing/video_output/reference/video_output_hlg_12bit_agx_mov.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:65c379238a5611f32187358300162178d59f188d91924e9f8b8b1041e0a5d6b3 -size 164248 +oid sha256:8553b7e0d54c850b315af1956dee9d518ee4f40e470f96dd1341747d2551730b +size 163515 diff --git a/tests/files/sequence_editing/video_output/reference/video_output_hlg_12bit_mov.png b/tests/files/sequence_editing/video_output/reference/video_output_hlg_12bit_mov.png index e0c0cde004b..7835dab3453 100644 --- a/tests/files/sequence_editing/video_output/reference/video_output_hlg_12bit_mov.png +++ b/tests/files/sequence_editing/video_output/reference/video_output_hlg_12bit_mov.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:99fa779482012f420251c2c251050a4f5b2df0bb764bcd2e20425718218f6c37 -size 172909 +oid sha256:2a5c72c1b4361eebc131db9f66f526efb1da4e28f7c0f83bb8d166449b8048f6 +size 173101 diff --git a/tests/files/sequence_editing/video_output/reference/video_output_p3_10bit_aces_mov.png b/tests/files/sequence_editing/video_output/reference/video_output_p3_10bit_aces_mov.png new file mode 100644 index 00000000000..90dc2daf4e3 --- /dev/null +++ b/tests/files/sequence_editing/video_output/reference/video_output_p3_10bit_aces_mov.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c9a229fb9d356b657782d85eb894c2d05feeeb94cb35daed30244db8727a2dc5 +size 178637 diff --git a/tests/files/sequence_editing/video_output/reference/video_output_pq_12bit_agx_mov.png b/tests/files/sequence_editing/video_output/reference/video_output_pq_12bit_agx_mov.png index c93806aa1d0..cf71169dd40 100644 --- a/tests/files/sequence_editing/video_output/reference/video_output_pq_12bit_agx_mov.png +++ b/tests/files/sequence_editing/video_output/reference/video_output_pq_12bit_agx_mov.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:497b9a1a83caf49af366e64f7d06164105b57a3238655618c57e465931af5da1 -size 151337 +oid sha256:10ea33f3dcb82ec35ff3d0f1150fda647b411164ddf811aeb48eb7566a801860 +size 151136 diff --git a/tests/files/sequence_editing/video_output/reference/video_output_pq_12bit_mov.png b/tests/files/sequence_editing/video_output/reference/video_output_pq_12bit_mov.png index 2b8b895a83e..b885013b6f0 100644 --- a/tests/files/sequence_editing/video_output/reference/video_output_pq_12bit_mov.png +++ b/tests/files/sequence_editing/video_output/reference/video_output_pq_12bit_mov.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b32f68219d5a0ef354a5f927bdc67e407bcf6567a197a9021643a9f0dff40520 -size 156812 +oid sha256:5be2c75762c1510b793bcfbaeb1bd5ad4c3d75d10f5fae81a1e630c93a35b40a +size 157363 diff --git a/tests/files/sequence_editing/video_output/video_output_hlg_12bit_agx_mov.blend b/tests/files/sequence_editing/video_output/video_output_hlg_12bit_agx_mov.blend index e1d3481a9e0..8bc413f96c6 100644 --- a/tests/files/sequence_editing/video_output/video_output_hlg_12bit_agx_mov.blend +++ b/tests/files/sequence_editing/video_output/video_output_hlg_12bit_agx_mov.blend @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:634c9c5680a3d61c647c9178ca22e7ce0bc8635a64a3a0fd935fa3e095894794 -size 310316 +oid sha256:fc07c905708f1281965a6fa9635db16119e42bea7b266158411da53617d18062 +size 89656 diff --git a/tests/files/sequence_editing/video_output/video_output_hlg_12bit_mov.blend b/tests/files/sequence_editing/video_output/video_output_hlg_12bit_mov.blend index 62244a1929b..c2d4a285e55 100644 --- a/tests/files/sequence_editing/video_output/video_output_hlg_12bit_mov.blend +++ b/tests/files/sequence_editing/video_output/video_output_hlg_12bit_mov.blend @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:034e8d9d81ad321291b3c2c3887271aa6df17ec10b68703e9fdceb1ef8c066ec -size 313840 +oid sha256:9c2dd069226769c86d1a1f188da71bd98ddf655a22039ad157fe15418862e97b +size 89520 diff --git a/tests/files/sequence_editing/video_output/video_output_p3_10bit_aces_mov.blend b/tests/files/sequence_editing/video_output/video_output_p3_10bit_aces_mov.blend new file mode 100644 index 00000000000..29cf0e8e7f6 --- /dev/null +++ b/tests/files/sequence_editing/video_output/video_output_p3_10bit_aces_mov.blend @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d7f6b730f76bcb427bb676d5016d8e4625ad3150131289127ab3705b146a031d +size 89206 diff --git a/tests/files/sequence_editing/video_output/video_output_pq_12bit_agx_mov.blend b/tests/files/sequence_editing/video_output/video_output_pq_12bit_agx_mov.blend index 527e73a47a8..b5039e16e58 100644 --- a/tests/files/sequence_editing/video_output/video_output_pq_12bit_agx_mov.blend +++ b/tests/files/sequence_editing/video_output/video_output_pq_12bit_agx_mov.blend @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:11665f820006e987c4034dc0cafaa7355669dcaa3029eb28c06c5cb3de64228e -size 310052 +oid sha256:9a7b90a0ef5bc97e2eb1980306df888fd3cb689745b3a11a23fdf4a9943e9f28 +size 89556 diff --git a/tests/files/sequence_editing/video_output/video_output_pq_12bit_mov.blend b/tests/files/sequence_editing/video_output/video_output_pq_12bit_mov.blend index 2772d9e7c08..a87761307ba 100644 --- a/tests/files/sequence_editing/video_output/video_output_pq_12bit_mov.blend +++ b/tests/files/sequence_editing/video_output/video_output_pq_12bit_mov.blend @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:01fce87094967babfd14a51b5f988ba3a8cc48f0e5d6a63ec1cf38fc8aa630f4 -size 313576 +oid sha256:939983d9a28f701201d80179be151e60c45b99d1dd1cf2324b29307cf36a4001 +size 89190 diff --git a/tests/python/sequencer_input_colorspace.py b/tests/python/sequencer_input_colorspace.py index ae73686f758..579ddef587c 100644 --- a/tests/python/sequencer_input_colorspace.py +++ b/tests/python/sequencer_input_colorspace.py @@ -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 diff --git a/tests/python/sequencer_render_tests.py b/tests/python/sequencer_render_tests.py index 41a464820de..534bd1ca473 100644 --- a/tests/python/sequencer_render_tests.py +++ b/tests/python/sequencer_render_tests.py @@ -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", ]