PR #21611 opened by richardssam URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/21611 Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/21611.patch
Opencolorio (https://opencolorio.org/) is a open source color management library that is extensively used in content creation apps such as Blender, Adobe, Autodesk and the Foundry. The VFX and animation community frequently needs the ability to convert from EXR or PNG frames to movies, using the color management defined in an OCIO file. This filter provides the ability to apply different color space transforms based on the OCIO file. Example usage using OCIO Display: ```bash ffmpeg -y -i SOURCEFRAMES.%05d.exr -vf "ocio=input=ACEScct:display=sRGB - Display:view=ACES 1.0 - SDR Video:format=rgb48,scale=in_color_matrix=bt709:out_color_matrix=bt709,format=yuv444p10" OUTPUTFILE.mov ``` Example usage using an output colorspace: ```bash ffmpeg -y -i SOURCEFRAMES.%05d.exr -vf "ocio=input=ACEScg:output=ACEScct:format=rgb48,scale=in_color_matrix=bt709:out_color_matrix=bt709,format=yuv444p10" OUTPUTFILE.mov ``` >From bd34e236c8acc413cf51732b8837b1a744be5a42 Mon Sep 17 00:00:00 2001 From: "[email protected]" <[email protected]> Date: Wed, 7 Jan 2026 20:50:17 +0000 Subject: [PATCH 01/18] Initial checkin of OCIO filter. Signed-off-by: [email protected] <[email protected]> --- configure | 21 +++ libavfilter/Makefile | 1 + libavfilter/allfilters.c | 1 + libavfilter/ocio_wrapper.cpp | 225 +++++++++++++++++++++++++++++++++ libavfilter/ocio_wrapper.hpp | 46 +++++++ libavfilter/vf_opencolorio.c | 220 ++++++++++++++++++++++++++++++++ tests/fate/filter-video.mak | 4 + tests/ref/fate/filter-ocio | 0 tests/ref/fate/filter-ocio-alt | 7 + 9 files changed, 525 insertions(+) create mode 100644 libavfilter/ocio_wrapper.cpp create mode 100644 libavfilter/ocio_wrapper.hpp create mode 100644 libavfilter/vf_opencolorio.c create mode 100644 tests/ref/fate/filter-ocio create mode 100644 tests/ref/fate/filter-ocio-alt diff --git a/configure b/configure index 99734e9d03..a1088ec9ce 100755 --- a/configure +++ b/configure @@ -259,6 +259,7 @@ External library support: --enable-libopenh264 enable H.264 encoding via OpenH264 [no] --enable-libopenjpeg enable JPEG 2000 encoding via OpenJPEG [no] --enable-libopenmpt enable decoding tracked files via libopenmpt [no] + --enable-libopencolorio enable color management via OpenColorIO [no] --enable-libopenvino enable OpenVINO as a DNN module backend for DNN based filters like dnn_processing [no] --enable-libopus enable Opus de/encoding via libopus [no] @@ -1987,6 +1988,8 @@ EXTERNAL_LIBRARY_LIST=" libmysofa liboapv libopencv + libopencolorio + libopenimage libopenh264 libopenjpeg libopenmpt @@ -4053,6 +4056,8 @@ openclsrc_filter_deps="opencl" qrencode_filter_deps="libqrencode" qrencodesrc_filter_deps="libqrencode" quirc_filter_deps="libquirc" +ocio_filter_deps="libopencolorio" +libopencolorio_filter_deps="libopencolorio" overlay_opencl_filter_deps="opencl" overlay_qsv_filter_deps="libmfx" overlay_qsv_filter_select="qsvvpp" @@ -7206,6 +7211,22 @@ enabled libopencore_amrnb && require libopencore_amrnb opencore-amrnb/interf_dec enabled libopencore_amrwb && require libopencore_amrwb opencore-amrwb/dec_if.h D_IF_init -lopencore-amrwb enabled libopencv && { check_pkg_config libopencv opencv4 opencv2/core/core_c.h cvCreateImageHeader || require libopencv opencv2/core/core_c.h cvCreateImageHeader -lopencv_core -lopencv_imgproc; } +#enabled libopencolorio && require_cpp OpenColorIO OpenColorIO/OpenColorIO.h "namespace OCIO = OCIO_NAMESPACE; OCIO::ConfigRcPtr cfg = OCIO::Config::Create();" -lOpenColorIO +#enabled libopencolorio && test_pkg_config_cpp OpenColorIO "OpenColorIO >= 2.0" OpenColorIO/OpenColorIO.h OCIO_VERSION_MAJOR && +#if enabled libopencolorio; then +# OCIO_CFLAGS=$($pkg_config --cflags OpenColorIO) +# OCIO_LIBS=$($pkg_config --libs OpenColorIO) +# add_cxxflags $OCIO_CFLAGS +# append libopencolorio_extralibs "$OCIO_LIBS" +#fi +# test_cpp_condition OpenColorIO/OpenColorIO.h && +# add_cxxflags $(pkg-config --cflags OpenColorIO) && echo OCIO CFLAGS ARE: $CXXFLAGS +#enabled libopencolorio && add_cxxflags $(pkg-config --cflags OpenColorIO) && +# set_sanitized "libopencolorio_extralibs" $($pkg_config --libs OpenColorIO) +enabled libopencolorio && add_cxxflags $(pkg-config --cflags OpenColorIO) && + OCIO_LIBS=$($pkg_config --libs OpenColorIO) && + #require_cpp OpenColorIO OpenColorIO/OpenColorIO.h OpenColorIO_v2_5::ConfigRcPtr $OCIO_LIBS -lc++ && + append libopencolorio_extralibs "$OCIO_LIBS -lc++" enabled libopenh264 && require_pkg_config libopenh264 "openh264 >= 1.3.0" wels/codec_api.h WelsGetCodecVersion enabled libopenjpeg && { check_pkg_config libopenjpeg "libopenjp2 >= 2.1.0" openjpeg.h opj_version || { require_pkg_config libopenjpeg "libopenjp2 >= 2.1.0" openjpeg.h opj_version -DOPJ_STATIC && add_cppflags -DOPJ_STATIC; } } diff --git a/libavfilter/Makefile b/libavfilter/Makefile index d56a458e45..b341ef780e 100644 --- a/libavfilter/Makefile +++ b/libavfilter/Makefile @@ -415,6 +415,7 @@ OBJS-$(CONFIG_NORMALIZE_FILTER) += vf_normalize.o OBJS-$(CONFIG_NULL_FILTER) += vf_null.o OBJS-$(CONFIG_OCR_FILTER) += vf_ocr.o OBJS-$(CONFIG_OCV_FILTER) += vf_libopencv.o +OBJS-$(CONFIG_OCIO_FILTER) += vf_opencolorio.o ocio_wrapper.o OBJS-$(CONFIG_OSCILLOSCOPE_FILTER) += vf_datascope.o OBJS-$(CONFIG_OVERLAY_FILTER) += vf_overlay.o framesync.o OBJS-$(CONFIG_OVERLAY_CUDA_FILTER) += vf_overlay_cuda.o framesync.o vf_overlay_cuda.ptx.o \ diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c index 0a3e782fe9..f7347fed72 100644 --- a/libavfilter/allfilters.c +++ b/libavfilter/allfilters.c @@ -390,6 +390,7 @@ extern const FFFilter ff_vf_null; extern const FFFilter ff_vf_ocr; extern const FFFilter ff_vf_ocv; extern const FFFilter ff_vf_oscilloscope; +extern const FFFilter ff_vf_ocio; extern const FFFilter ff_vf_overlay; extern const FFFilter ff_vf_overlay_opencl; extern const FFFilter ff_vf_overlay_qsv; diff --git a/libavfilter/ocio_wrapper.cpp b/libavfilter/ocio_wrapper.cpp new file mode 100644 index 0000000000..501084b113 --- /dev/null +++ b/libavfilter/ocio_wrapper.cpp @@ -0,0 +1,225 @@ +// libavfilter/ocio_wrapper.cpp + +#include <OpenColorIO/OpenColorIO.h> +#include <exception> +#include <iostream> + +namespace OCIO = OCIO_NAMESPACE; + +struct OCIOState { + OCIO::ConstConfigRcPtr config; + OCIO::ConstProcessorRcPtr processor; + OCIO::ConstCPUProcessorRcPtr cpu; + int channels; +}; + +extern "C" { + + +#include <libavutil/pixdesc.h> +#include "formats.h" +#include <libavutil/frame.h> +#include "ocio_wrapper.hpp" + +// Helper to map AV_PIX_FMT to OCIO BitDepth +static OCIO::BitDepth get_ocio_depth(int format) { + switch (format) { + case AV_PIX_FMT_RGB24: + case AV_PIX_FMT_RGBA: + return OCIO::BIT_DEPTH_UINT8; + + case AV_PIX_FMT_RGB48: + case AV_PIX_FMT_RGBA64: + return OCIO::BIT_DEPTH_UINT16; + + case AV_PIX_FMT_GBRP10: + case AV_PIX_FMT_GBRAP10: + return OCIO::BIT_DEPTH_UINT10; + + case AV_PIX_FMT_GBRP12: + case AV_PIX_FMT_GBRAP12: + return OCIO::BIT_DEPTH_UINT12; + + // Note: FFmpeg treats half-float as specific types often requiring casts. + // For this snippet we map F16 directly if your system supports it, + // otherwise, standard float (F32) is safer. + case AV_PIX_FMT_GBRPF16: + case AV_PIX_FMT_GBRAPF16: + return OCIO::BIT_DEPTH_F16; + + case AV_PIX_FMT_GBRPF32: + case AV_PIX_FMT_GBRAPF32: + return OCIO::BIT_DEPTH_F32; + + default: + return OCIO::BIT_DEPTH_UNKNOWN; + } +} + + +OCIOHandle ocio_create_output_colorspace_processor( + const char *config_path, + const char *input_color_space, + const char *output_color_space) +{ + std::cerr << "Starting ocio_create_output_colorspace_processor\n"; + try { + + OCIOState *s = new OCIOState(); + if (!config_path) + s->config = OCIO::Config::CreateFromEnv(); + else + s->config = OCIO::Config::CreateFromFile(config_path); + + if(!s->config ||!input_color_space || !output_color_space) + return nullptr; + + // ColorSpace Transform: InputSpace -> OutputSpace + OCIO::ColorSpaceTransformRcPtr cst = OCIO::ColorSpaceTransform::Create(); + cst->setSrc(input_color_space); + cst->setDst(output_color_space); + + s->processor = s->config->getProcessor(cst); + + return (OCIOHandle)s; + } + catch(...) { + return nullptr; + } +} + +OCIOHandle ocio_create_display_view_processor( + const char *config_path, + const char *input_color_space, + const char *display, + const char *view) +{ + std::cerr << "Starting ocio_create_display_view_processor\n"; + try { + + OCIOState *s = new OCIOState(); + if (!config_path) + s->config = OCIO::Config::CreateFromEnv(); + else + s->config = OCIO::Config::CreateFromFile(config_path); + + if(!s->config ||!input_color_space || !display || !view) + return nullptr; + + // Display/View Transform: InputSpace -> Display/View + OCIO::DisplayViewTransformRcPtr dv = OCIO::DisplayViewTransform::Create(); + dv->setSrc(input_color_space); + dv->setDisplay(display); + dv->setView(view); + + s->processor = s->config->getProcessor(dv); + + return (OCIOHandle)s; + } + catch(...) { + return nullptr; + } +} + +// In ocio_wrapper.cpp +int ocio_finalize_processor(OCIOHandle handle, int input_format, int output_format) +{ + + try { + OCIOState *s = (OCIOState*)handle; + if (!s || !s->processor) return -1; + + s->cpu = s->processor->getOptimizedCPUProcessor( + get_ocio_depth(input_format), + get_ocio_depth(output_format), + OCIO::OPTIMIZATION_DEFAULT + ); + + return 0; + } + catch (OCIO::Exception &e) { + std::cerr << "OCIO error: " << e.what() << std::endl; + return -1; + } + catch (...) { + std::cerr << "Unknown error in ocio_finalize_processor" << std::endl; + return -1; + } +} + +static OCIO::ImageDesc * AVFrame2ImageDesc(AVFrame *frame){ + OCIO::BitDepth ocio_bitdepth = get_ocio_depth(frame->format); + if (ocio_bitdepth == OCIO::BIT_DEPTH_UNKNOWN) { + throw std::runtime_error("Unsupported pixel format for OCIO processing"); + } + + int stridex = frame->linesize[0]; + const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get((enum AVPixelFormat)frame->format); + if (!desc) { + throw std::runtime_error("Invalid pixel format descriptor"); + } + + bool is_planar = desc && (desc->flags & AV_PIX_FMT_FLAG_PLANAR); + + if (is_planar){ + void *red = (void *)frame->data[2]; + void *green = (void *)frame->data[0]; + void *blue = (void *)frame->data[1]; + void *alpha = (desc->nb_components == 4) ? (void *)frame->data[3] : NULL; + return new OCIO::PlanarImageDesc( + (void*)red, (void*)green, (void*)blue, (void*)alpha, + frame->width, frame->height, + ocio_bitdepth, desc->comp[0].step, stridex + ); + } + void *data = (void *)frame->data[0]; + // Note we are assuming that these are RGB or RGBA chanel ordering. + // And are also likely to be integer. + return new OCIO::PackedImageDesc( + (void*)data, + frame->width, frame->height, desc->nb_components, + ocio_bitdepth, desc->comp[0].depth / 8, desc->comp[0].step, frame->linesize[0] + ); +} + +int ocio_apply( + OCIOHandle handle, + AVFrame *input_frame, + AVFrame *output_frame) +{ + OCIOState *s = (OCIOState*)handle; + if (!s || !s->cpu) return -1; + + try { + if (input_frame == output_frame){ + // In-place processing when input and output pix_fmts are the same. + OCIO::ImageDesc *imgDesc = AVFrame2ImageDesc(input_frame); + s->cpu->apply(*imgDesc); + delete imgDesc; + return 0; + } + OCIO::ImageDesc *input = AVFrame2ImageDesc(input_frame); + OCIO::ImageDesc *output = AVFrame2ImageDesc(output_frame); + s->cpu->apply(*input, *output); + delete input; + delete output; + return 0; + } catch(const OCIO::Exception &ex) { + std::cerr << "OCIO error: " << ex.what() << std::endl; + return -2; // or another error code + } catch(const std::exception &ex) { + std::cerr << "Standard exception: " << ex.what() << std::endl; + return -3; + } catch(...) { + std::cerr << "Unknown error in OCIO processing." << std::endl; + return -4; + } +} + +void ocio_destroy_processor(OCIOHandle handle) +{ + if(!handle) return; + delete (OCIOState*)handle; +} + +} // extern "C" diff --git a/libavfilter/ocio_wrapper.hpp b/libavfilter/ocio_wrapper.hpp new file mode 100644 index 0000000000..a760465bcd --- /dev/null +++ b/libavfilter/ocio_wrapper.hpp @@ -0,0 +1,46 @@ +// libavfilter/ocio_wrapper.hpp +#pragma once +#include <stdint.h> + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void* OCIOHandle; + +// Create an OCIO processor for Display/View transform. +// Returns NULL on failure. +OCIOHandle ocio_create_display_view_processor( + const char *config_path, + const char *input_color_space, + const char *display, + const char *view); + + +// Create an OCIO processor for output colorspace transform. +// Returns NULL on failure. +OCIOHandle ocio_create_output_colorspace_processor( + const char *config_path, + const char *input_color_space, + const char *output_color_space); + + +// Finalize OCIO processor for given bit depth. +// is_half_float: true for half-float, false for float +int ocio_finalize_processor(OCIOHandle handle, int input_format, int output_format); + +// Apply processor to planar float RGB(A). +// pixels: pointer to float samples +// w,h: image dimensions +// channels: 3 or 4 +// stride_bytes: bytes between row starts (use 0 for tightly packed) +int ocio_apply( + OCIOHandle handle,AVFrame *input_frame, AVFrame *output_frame + ); + +// Destroy OCIO processor. +void ocio_destroy_processor(OCIOHandle handle); + +#ifdef __cplusplus +} +#endif diff --git a/libavfilter/vf_opencolorio.c b/libavfilter/vf_opencolorio.c new file mode 100644 index 0000000000..eb05da6ca4 --- /dev/null +++ b/libavfilter/vf_opencolorio.c @@ -0,0 +1,220 @@ +// libavfilter/vf_opencolorio.c +#include "avfilter.h" +#include "formats.h" +#include "video.h" +#include "libavutil/pixdesc.h" +#include "libavutil/opt.h" +#include "libavutil/half2float.h" +#include "ocio_wrapper.hpp" + +typedef struct { + const AVClass *class; + char *config_path; + char *input_space; + char *output_space; + char *display; + char *view; + OCIOHandle ocio; + int output_format; + char *out_format_string; // e.g. "rgb48le" which is converted to AVPixelFormat as output_format + int channels; // 3 or 4 depending on pixfmt +} OCIOContext; + + + +static int query_formats(AVFilterContext *ctx) +{ + static const enum AVPixelFormat pix_fmts[] = { + // 8-bit + AV_PIX_FMT_RGBA, + AV_PIX_FMT_RGB24, + // 16-bit + AV_PIX_FMT_RGBA64, + AV_PIX_FMT_RGB48, + // 10-bit + AV_PIX_FMT_GBRP10, + AV_PIX_FMT_GBRAP10, + // 12-bit + AV_PIX_FMT_GBRP12, + AV_PIX_FMT_GBRAP12, + // Half-float and float + AV_PIX_FMT_GBRPF16, + AV_PIX_FMT_GBRAPF16, + // Float + AV_PIX_FMT_GBRPF32, + AV_PIX_FMT_GBRAPF32, + AV_PIX_FMT_NONE + }; + return ff_set_common_formats(ctx, ff_make_format_list(pix_fmts)); +} + +static int config_props(AVFilterLink *inlink) +{ + AVFilterContext *ctx = inlink->dst; + OCIOContext *s = ctx->priv; + const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format); + + if (!desc) { + av_log(ctx, AV_LOG_ERROR, "Invalid pixel format\n"); + return AVERROR(EINVAL); + } + + int is_half = (desc->comp[0].depth == 16 && desc->flags & AV_PIX_FMT_FLAG_FLOAT); + if (s->output_format == AV_PIX_FMT_NONE) { + // Need to set the output format now, if not known. + if (is_half){ + // If its half-float, we output float, due to a bug in ffmpeg with half-float frames + s->output_format = AV_PIX_FMT_GBRAPF32; + } else { + // If output format not set, use same as input + s->output_format = inlink->format; + } + } + + s->channels = desc->nb_components; // 3 or 4 + + av_log(ctx, AV_LOG_INFO, "Configuring OCIO for %s (bit depth: %d, channels: %d), output format: (%s)\n", + av_get_pix_fmt_name(inlink->format), desc->comp[0].depth, s->channels, av_get_pix_fmt_name(s->output_format)); + + // Now finalize the OCIO processor with the correct bit depth + int ret = ocio_finalize_processor(s->ocio, inlink->format, s->output_format); + if (ret < 0) { + av_log(ctx, AV_LOG_ERROR, "Failed to finalize OCIO processor for bit depth\n"); + return AVERROR_EXTERNAL; + } + + return 0; +} + + +static av_cold int init(AVFilterContext *ctx) +{ + OCIOContext *s = ctx->priv; + if (s->out_format_string != NULL) { + s->output_format = av_get_pix_fmt(s->out_format_string); + } else { + // Default to same as input format (see later). + s->output_format = AV_PIX_FMT_NONE; + } + av_log(ctx, AV_LOG_INFO, "Creating OCIO processor with config: %s, input: %s, display: %s, view: %s\n", s->config_path, s->input_space, s->display, s->view); + if (s->output_space && strlen(s->output_space) > 0) { + s->ocio = ocio_create_output_colorspace_processor( + s->config_path, + s->input_space, + s->output_space); + } else { + s->ocio = ocio_create_display_view_processor( + s->config_path, + s->input_space, + s->display, + s->view); + } + if(!s->ocio) { + av_log(ctx, AV_LOG_ERROR, "Failed to create OCIO processor.\n"); + return AVERROR(EINVAL); + } + + return 0; +} + +static int filter_frame(AVFilterLink *inlink, AVFrame *frame) +{ + AVFilterContext *ctx = inlink->dst; + OCIOContext *s = ctx->priv; + const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(frame->format); + + if(!desc) return AVERROR(EINVAL); + + int channels = desc->nb_components; // 3 or 4 + s->channels = channels; + + int ret; + AVFrame *output_frame; + if (s->output_format == inlink->format){ + // No conversion needed, pass through + output_frame = frame; + ret = ocio_apply(s->ocio, frame, output_frame); + } else { + // Allocate new output frame + output_frame = av_frame_alloc(); + output_frame->format = s->output_format; + output_frame->width = frame->width; + output_frame->height = frame->height; + ret = av_frame_get_buffer(output_frame, 32); + if (ret < 0) { + av_frame_free(&output_frame); + av_frame_free(&frame); + return ret; + } + av_frame_copy_props(output_frame, frame); + output_frame->color_primaries = AVCOL_PRI_BT709; + output_frame->color_trc = AVCOL_TRC_BT709; + output_frame->colorspace = AVCOL_SPC_RGB; + output_frame->color_range = AVCOL_RANGE_JPEG; // Full range for RGB + + ret = ocio_apply(s->ocio, frame, output_frame); + + av_frame_free(&frame); + } + + if(ret < 0) { + av_log(ctx, AV_LOG_ERROR, "OCIO apply failed.\n"); + return AVERROR(EINVAL); + } + + + av_log(ctx, AV_LOG_INFO, "OCIO apply succeeded.\n"); + return ff_filter_frame(ctx->outputs[0], output_frame); +} + +static av_cold void uninit(AVFilterContext *ctx) +{ + OCIOContext *s = ctx->priv; + if(s->ocio) { + ocio_destroy_processor(s->ocio); + s->ocio = NULL; + } +} + +#define OFFSET(x) offsetof(OCIOContext, x) +#define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM + +static const AVOption ocio_options[] = { + { "config", "OCIO config path, overriding OCIO environment variable.", OFFSET(config_path), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, FLAGS }, + { "input", "Input color space", OFFSET(input_space), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, FLAGS }, + { "output", "Output color space", OFFSET(output_space), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, FLAGS }, + { "display", "Output display, used insted of output color space.", OFFSET(display), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, FLAGS }, + { "view", "View, output view transform, used in combination with display.", OFFSET(view), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, FLAGS }, + { "format", "Output video format", OFFSET(out_format_string), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, FLAGS }, + { NULL } +}; + +AVFILTER_DEFINE_CLASS(ocio); + +static const AVFilterPad inputs[] = { + { + .name="default", + .type=AVMEDIA_TYPE_VIDEO, + .filter_frame = filter_frame, + .config_props = config_props, + }, +}; + +static const AVFilterPad outputs[] = { + { + .name="default", + .type=AVMEDIA_TYPE_VIDEO + } +}; + +const FFFilter ff_vf_ocio = { + .p.name = "ocio", + .p.description = NULL_IF_CONFIG_SMALL("Apply OCIO Display/View transform"), + .p.priv_class = &ocio_class, + .priv_size = sizeof(OCIOContext), + .init = init, + .uninit = uninit, + FILTER_INPUTS(inputs), + FILTER_OUTPUTS(outputs), + FILTER_QUERY_FUNC(query_formats) +}; diff --git a/tests/fate/filter-video.mak b/tests/fate/filter-video.mak index 3fe7f10476..498fe22d2d 100644 --- a/tests/fate/filter-video.mak +++ b/tests/fate/filter-video.mak @@ -474,6 +474,10 @@ FATE_FILTER_VSYNTH_PGMYUV-$(call ALLYES, SCALE_FILTER FORMAT_FILTER SPLIT_FILTER $(FATE_FILTER_ALPHAEXTRACT_ALPHAMERGE): fate-filter-alphaextract_alphamerge_%: tests/data/filtergraphs/alphamerge_alphaextract_% $(FATE_FILTER_ALPHAEXTRACT_ALPHAMERGE): CMD = framecrc -auto_conversion_filters -c:v pgmyuv -i $(SRC) -/filter_complex $(TARGET_PATH)/tests/data/filtergraphs/alphamerge_alphaextract$(@:fate-filter-alphaextract_alphamerge%=%) +FATE_FILTER-$(CONFIG_OCIO_FILTER) += fate-filter-ocio fate-filter-ocio-alt +fate-filter-ocio: CMD = framecrc -i $(TARGET_SAMPLES)/exr/rgba_slice_piz.exr -vf "ocio=config=/Users/sam/git/FFmpeg/studio-config-v1.0.0_aces-v1.3_ocio-v2.1_ns.ocio:input=ACEScg:display=sRGB\ -\ Display:view=ACES\ 1.0\ -\ SDR\ Video" +fate-filter-ocio-alt: CMD = framecrc -i $(TARGET_SAMPLES)/exr/rgba_slice_piz.exr -vf ocio=config=/Users/sam/git/FFmpeg/simpleconfig.ocio:input=ACEScg:display=sRGB:view=ACES_1.0_SDR + FATE_FILTER_VSYNTH_VIDEO_FILTER-$(CONFIG_CROP_FILTER) += fate-filter-crop fate-filter-crop: CMD = video_filter "crop=iw-100:ih-100:100:100" diff --git a/tests/ref/fate/filter-ocio b/tests/ref/fate/filter-ocio new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/ref/fate/filter-ocio-alt b/tests/ref/fate/filter-ocio-alt new file mode 100644 index 0000000000..6ff3c65629 --- /dev/null +++ b/tests/ref/fate/filter-ocio-alt @@ -0,0 +1,7 @@ +ffmpeg -i /Users/sam/git/fate-suite/exr/rgba_slice_piz.exr -vf ocio=config=/Users/sam/git/FFmpeg/simpleconfig.ocio:input=ACEScg:display=sRGB:view=ACES_1.0_SDR -bitexact -f framecrc - +#tb 0: 1/25 +#media_type 0: video +#codec_id 0: rawvideo +#dimensions 0: 587x675 +#sar 0: 1/1 +0, 0, 0, 1, 6339600, 0x275bc9bf -- 2.52.0 >From 4c378c1e13e963e4c6da07b5e5d92f4cdc39abed Mon Sep 17 00:00:00 2001 From: "[email protected]" <[email protected]> Date: Fri, 9 Jan 2026 15:33:18 +0000 Subject: [PATCH 02/18] Change for the right C++ library, should work on linux too. Signed-off-by: [email protected] <[email protected]> --- configure | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure b/configure index a1088ec9ce..f897f5e65e 100755 --- a/configure +++ b/configure @@ -7226,7 +7226,7 @@ enabled libopencv && { check_pkg_config libopencv opencv4 opencv2/core/c enabled libopencolorio && add_cxxflags $(pkg-config --cflags OpenColorIO) && OCIO_LIBS=$($pkg_config --libs OpenColorIO) && #require_cpp OpenColorIO OpenColorIO/OpenColorIO.h OpenColorIO_v2_5::ConfigRcPtr $OCIO_LIBS -lc++ && - append libopencolorio_extralibs "$OCIO_LIBS -lc++" + append libopencolorio_extralibs "$OCIO_LIBS -lstdc++" enabled libopenh264 && require_pkg_config libopenh264 "openh264 >= 1.3.0" wels/codec_api.h WelsGetCodecVersion enabled libopenjpeg && { check_pkg_config libopenjpeg "libopenjp2 >= 2.1.0" openjpeg.h opj_version || { require_pkg_config libopenjpeg "libopenjp2 >= 2.1.0" openjpeg.h opj_version -DOPJ_STATIC && add_cppflags -DOPJ_STATIC; } } -- 2.52.0 >From e0f7cb3439b97e6e5d18859ea7779a419b57e8a7 Mon Sep 17 00:00:00 2001 From: "[email protected]" <[email protected]> Date: Wed, 21 Jan 2026 16:13:15 +0000 Subject: [PATCH 03/18] Adding inverse when using display/view. --- libavfilter/ocio_wrapper.cpp | 7 +++++-- libavfilter/ocio_wrapper.hpp | 3 ++- libavfilter/vf_opencolorio.c | 5 ++++- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/libavfilter/ocio_wrapper.cpp b/libavfilter/ocio_wrapper.cpp index 501084b113..f8cb5d3620 100644 --- a/libavfilter/ocio_wrapper.cpp +++ b/libavfilter/ocio_wrapper.cpp @@ -92,9 +92,9 @@ OCIOHandle ocio_create_display_view_processor( const char *config_path, const char *input_color_space, const char *display, - const char *view) + const char *view, + int inverse) { - std::cerr << "Starting ocio_create_display_view_processor\n"; try { OCIOState *s = new OCIOState(); @@ -111,6 +111,9 @@ OCIOHandle ocio_create_display_view_processor( dv->setSrc(input_color_space); dv->setDisplay(display); dv->setView(view); + if (inverse) + dv->setDirection(OCIO::TRANSFORM_DIR_INVERSE); + s->processor = s->config->getProcessor(dv); diff --git a/libavfilter/ocio_wrapper.hpp b/libavfilter/ocio_wrapper.hpp index a760465bcd..5eb2380249 100644 --- a/libavfilter/ocio_wrapper.hpp +++ b/libavfilter/ocio_wrapper.hpp @@ -14,7 +14,8 @@ OCIOHandle ocio_create_display_view_processor( const char *config_path, const char *input_color_space, const char *display, - const char *view); + const char *view, + int inverse); // Create an OCIO processor for output colorspace transform. diff --git a/libavfilter/vf_opencolorio.c b/libavfilter/vf_opencolorio.c index eb05da6ca4..0045a96e4e 100644 --- a/libavfilter/vf_opencolorio.c +++ b/libavfilter/vf_opencolorio.c @@ -14,6 +14,7 @@ typedef struct { char *output_space; char *display; char *view; + int inverse; OCIOHandle ocio; int output_format; char *out_format_string; // e.g. "rgb48le" which is converted to AVPixelFormat as output_format @@ -107,7 +108,8 @@ static av_cold int init(AVFilterContext *ctx) s->config_path, s->input_space, s->display, - s->view); + s->view, + s->inverse); } if(!s->ocio) { av_log(ctx, AV_LOG_ERROR, "Failed to create OCIO processor.\n"); @@ -185,6 +187,7 @@ static const AVOption ocio_options[] = { { "output", "Output color space", OFFSET(output_space), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, FLAGS }, { "display", "Output display, used insted of output color space.", OFFSET(display), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, FLAGS }, { "view", "View, output view transform, used in combination with display.", OFFSET(view), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, FLAGS }, + { "inverse", "Invert output display/view transform.", OFFSET(inverse), AV_OPT_TYPE_INT, {.i64=0}, 0, 1, FLAGS }, { "format", "Output video format", OFFSET(out_format_string), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, FLAGS }, { NULL } }; -- 2.52.0 >From 9e500c9b1832c076a83258fe76e68bd2f3858fc4 Mon Sep 17 00:00:00 2001 From: "[email protected]" <[email protected]> Date: Wed, 21 Jan 2026 16:13:30 +0000 Subject: [PATCH 04/18] Removed comments. --- libavfilter/ocio_wrapper.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/libavfilter/ocio_wrapper.cpp b/libavfilter/ocio_wrapper.cpp index f8cb5d3620..9e537a7830 100644 --- a/libavfilter/ocio_wrapper.cpp +++ b/libavfilter/ocio_wrapper.cpp @@ -62,7 +62,6 @@ OCIOHandle ocio_create_output_colorspace_processor( const char *input_color_space, const char *output_color_space) { - std::cerr << "Starting ocio_create_output_colorspace_processor\n"; try { OCIOState *s = new OCIOState(); -- 2.52.0 >From f03cfe299b12faae1d6ad2e331a7e81fb1d6e31e Mon Sep 17 00:00:00 2001 From: "[email protected]" <[email protected]> Date: Wed, 21 Jan 2026 16:14:06 +0000 Subject: [PATCH 05/18] Removed code that was setting the CICP values. Hopefully this can be done through OCIO at some point. --- libavfilter/vf_opencolorio.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/libavfilter/vf_opencolorio.c b/libavfilter/vf_opencolorio.c index 0045a96e4e..076456eba7 100644 --- a/libavfilter/vf_opencolorio.c +++ b/libavfilter/vf_opencolorio.c @@ -149,10 +149,6 @@ static int filter_frame(AVFilterLink *inlink, AVFrame *frame) return ret; } av_frame_copy_props(output_frame, frame); - output_frame->color_primaries = AVCOL_PRI_BT709; - output_frame->color_trc = AVCOL_TRC_BT709; - output_frame->colorspace = AVCOL_SPC_RGB; - output_frame->color_range = AVCOL_RANGE_JPEG; // Full range for RGB ret = ocio_apply(s->ocio, frame, output_frame); -- 2.52.0 >From 749bd266ddcdb9cf6783a8f00083cb2db209b4d6 Mon Sep 17 00:00:00 2001 From: "[email protected]" <[email protected]> Date: Wed, 21 Jan 2026 16:27:37 +0000 Subject: [PATCH 06/18] Config cleanup - need a modified require_cpp to handle namespacing. --- configure | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/configure b/configure index f897f5e65e..d2094e14a3 100755 --- a/configure +++ b/configure @@ -1539,6 +1539,17 @@ check_lib(){ enable $name && eval ${name}_extralibs="\$@" } +check_lib_cpp(){ + log check_lib_cpp "$@" + name="$1" + headers="$2" + code="$3" + shift 3 + disable $name + test_code ld "$headers" "$code" cxx "$@" && + enable $name && eval ${name}_extralibs="\$@" +} + check_lib_cxx(){ log check_lib_cxx "$@" name="$1" @@ -1725,6 +1736,14 @@ require_cxx(){ check_lib_cxx "$name" "$@" || die "ERROR: $name_version not found" } +require_cpp(){ + log require_cpp "$@" + name_version="$1" + name="${1%% *}" + shift + check_lib_cpp "$name" "$@" || die "ERROR: $name_version not found" +} + require_headers(){ log require_headers "$@" headers="$1" @@ -7211,21 +7230,9 @@ enabled libopencore_amrnb && require libopencore_amrnb opencore-amrnb/interf_dec enabled libopencore_amrwb && require libopencore_amrwb opencore-amrwb/dec_if.h D_IF_init -lopencore-amrwb enabled libopencv && { check_pkg_config libopencv opencv4 opencv2/core/core_c.h cvCreateImageHeader || require libopencv opencv2/core/core_c.h cvCreateImageHeader -lopencv_core -lopencv_imgproc; } -#enabled libopencolorio && require_cpp OpenColorIO OpenColorIO/OpenColorIO.h "namespace OCIO = OCIO_NAMESPACE; OCIO::ConfigRcPtr cfg = OCIO::Config::Create();" -lOpenColorIO -#enabled libopencolorio && test_pkg_config_cpp OpenColorIO "OpenColorIO >= 2.0" OpenColorIO/OpenColorIO.h OCIO_VERSION_MAJOR && -#if enabled libopencolorio; then -# OCIO_CFLAGS=$($pkg_config --cflags OpenColorIO) -# OCIO_LIBS=$($pkg_config --libs OpenColorIO) -# add_cxxflags $OCIO_CFLAGS -# append libopencolorio_extralibs "$OCIO_LIBS" -#fi -# test_cpp_condition OpenColorIO/OpenColorIO.h && -# add_cxxflags $(pkg-config --cflags OpenColorIO) && echo OCIO CFLAGS ARE: $CXXFLAGS -#enabled libopencolorio && add_cxxflags $(pkg-config --cflags OpenColorIO) && -# set_sanitized "libopencolorio_extralibs" $($pkg_config --libs OpenColorIO) enabled libopencolorio && add_cxxflags $(pkg-config --cflags OpenColorIO) && OCIO_LIBS=$($pkg_config --libs OpenColorIO) && - #require_cpp OpenColorIO OpenColorIO/OpenColorIO.h OpenColorIO_v2_5::ConfigRcPtr $OCIO_LIBS -lc++ && + require_cxx OpenColorIO OpenColorIO/OpenColorIO.h "namespace OCIO = OCIO_NAMESPACE; OCIO::ConfigRcPtr cfg = OCIO::Config::Create();" $OCIO_LIBS -lstdc++ && append libopencolorio_extralibs "$OCIO_LIBS -lstdc++" enabled libopenh264 && require_pkg_config libopenh264 "openh264 >= 1.3.0" wels/codec_api.h WelsGetCodecVersion enabled libopenjpeg && { check_pkg_config libopenjpeg "libopenjp2 >= 2.1.0" openjpeg.h opj_version || -- 2.52.0 >From c590565bd0b34aae224e231f93639b52176356fe Mon Sep 17 00:00:00 2001 From: "[email protected]" <[email protected]> Date: Wed, 21 Jan 2026 17:06:14 +0000 Subject: [PATCH 07/18] Switch to using require_cpp so that namespace can be used. --- configure | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure b/configure index d2094e14a3..a3495759bf 100755 --- a/configure +++ b/configure @@ -7232,7 +7232,7 @@ enabled libopencv && { check_pkg_config libopencv opencv4 opencv2/core/c require libopencv opencv2/core/core_c.h cvCreateImageHeader -lopencv_core -lopencv_imgproc; } enabled libopencolorio && add_cxxflags $(pkg-config --cflags OpenColorIO) && OCIO_LIBS=$($pkg_config --libs OpenColorIO) && - require_cxx OpenColorIO OpenColorIO/OpenColorIO.h "namespace OCIO = OCIO_NAMESPACE; OCIO::ConfigRcPtr cfg = OCIO::Config::Create();" $OCIO_LIBS -lstdc++ && + require_cpp OpenColorIO OpenColorIO/OpenColorIO.h "namespace OCIO = OCIO_NAMESPACE; OCIO::ConfigRcPtr cfg = OCIO::Config::Create();" $OCIO_LIBS -lstdc++ && append libopencolorio_extralibs "$OCIO_LIBS -lstdc++" enabled libopenh264 && require_pkg_config libopenh264 "openh264 >= 1.3.0" wels/codec_api.h WelsGetCodecVersion enabled libopenjpeg && { check_pkg_config libopenjpeg "libopenjp2 >= 2.1.0" openjpeg.h opj_version || -- 2.52.0 >From 994297c2a708ced969ef2429e5fafc03639c53bf Mon Sep 17 00:00:00 2001 From: "[email protected]" <[email protected]> Date: Wed, 21 Jan 2026 18:03:57 +0000 Subject: [PATCH 08/18] Adding documentation. --- doc/filters.texi | 52 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/doc/filters.texi b/doc/filters.texi index 168ea0d2da..2396a75e68 100644 --- a/doc/filters.texi +++ b/doc/filters.texi @@ -18730,6 +18730,58 @@ normalize=blackpt=red:whitept=cyan Pass the video source unchanged to the output. +@section ocio +OpenColorIO library filter + +This filter allows you to do color management using the OpenColorIO library. +See https://opencolorio.org/ for more details. To Enable +compliation of this filter, you need to configure FFmpeg with @code{--enable-libopencolorio}. + +It accepts the following options: + +@table @option +@item input +Set the input colorspace. + +@item output +Set the output colorspace. + +@item display +Set the display colorspace, used in combination with view. + +@item view +Set the view colorspace, used in combination with display. + +@item inverse +When used in combination with display and view, this inverts the transform, so going from a display/view to the "input colorspace". + +@item format +Allow you to specify the output pix_fmt of the OCIO filter. This *has* to be a RGB colorspace, so you really are limited to rgb24, rgba, rgb48, rgba48, gbrp10, gbrp12, gbrpf32le, gbrapf32le, for most encoding we would recommend rgb48 +@end table + +@subsection Examples + +Map from ACEScg to ACEScct, this assumes the OCIO file is defined with the OCIO environment variable. +@example +input=ACEScg:output=ACEScct:format=rgb48 +@end example + +Map from ACEScg to a sRGB display using the "ACES 1.0 - SDR Video" view transform, this assumes the OCIO file is defined with the OCIO environment variable. Note you will need to wrap the argument in quotes to ensure that the spaces are interpreted correctly. +@example +input=ACEScg:display=sRGB - Display:view=ACES 1.0 - SDR Video:format=rgb48 +@end example + + +As above but using the OCIO file studio-config-v1.0.0_aces-v1.3_ocio-v2.1_ns.ocio rather than the OCIO environment variable. +@example +config=studio-config-v1.0.0_aces-v1.3_ocio-v2.1_ns.ocio:input=ACEScg:display=sRGB - Display:view=ACES 1.0 - SDR Video:format=rgb48 +@end example + +If you are converting to YCrCb you still will want to set the color matrix for the conversion. This is a good example of combining the two. +@example +ffmpeg -y -i SOURCEFRAMES.%05d.exr -c:v libx265 -vf "ocio=input=ACEScg:output=ACEScct:format=rgb48,scale=in_color_matrix=bt709:out_color_matrix=bt709,format=yuv444p10" OUTPUTFILE.mov +@end example + @section ocr Optical Character Recognition -- 2.52.0 >From 197c4ffa4729f2dc7d4dcd1b72f5cd98bd21ed1d Mon Sep 17 00:00:00 2001 From: "[email protected]" <[email protected]> Date: Thu, 22 Jan 2026 12:32:55 +0000 Subject: [PATCH 09/18] Sadly a bit of linting went in here, but more importantly added a threads option to split the image into horizontal tiles, since OCIO was running rather slow. Signed-off-by: [email protected] <[email protected]> --- libavfilter/ocio_wrapper.cpp | 390 ++++++++++++++++++++--------------- libavfilter/ocio_wrapper.hpp | 30 ++- libavfilter/vf_opencolorio.c | 328 ++++++++++++++--------------- 3 files changed, 396 insertions(+), 352 deletions(-) diff --git a/libavfilter/ocio_wrapper.cpp b/libavfilter/ocio_wrapper.cpp index 9e537a7830..786863e844 100644 --- a/libavfilter/ocio_wrapper.cpp +++ b/libavfilter/ocio_wrapper.cpp @@ -3,225 +3,271 @@ #include <OpenColorIO/OpenColorIO.h> #include <exception> #include <iostream> +#include <thread> +#include <vector> namespace OCIO = OCIO_NAMESPACE; struct OCIOState { - OCIO::ConstConfigRcPtr config; - OCIO::ConstProcessorRcPtr processor; - OCIO::ConstCPUProcessorRcPtr cpu; - int channels; + OCIO::ConstConfigRcPtr config; + OCIO::ConstProcessorRcPtr processor; + OCIO::ConstCPUProcessorRcPtr cpu; + int channels; }; extern "C" { - -#include <libavutil/pixdesc.h> #include "formats.h" -#include <libavutil/frame.h> #include "ocio_wrapper.hpp" +#include <libavutil/frame.h> +#include <libavutil/pixdesc.h> // Helper to map AV_PIX_FMT to OCIO BitDepth static OCIO::BitDepth get_ocio_depth(int format) { - switch (format) { - case AV_PIX_FMT_RGB24: - case AV_PIX_FMT_RGBA: - return OCIO::BIT_DEPTH_UINT8; - - case AV_PIX_FMT_RGB48: - case AV_PIX_FMT_RGBA64: - return OCIO::BIT_DEPTH_UINT16; + switch (format) { + case AV_PIX_FMT_RGB24: + case AV_PIX_FMT_RGBA: + return OCIO::BIT_DEPTH_UINT8; - case AV_PIX_FMT_GBRP10: - case AV_PIX_FMT_GBRAP10: - return OCIO::BIT_DEPTH_UINT10; + case AV_PIX_FMT_RGB48: + case AV_PIX_FMT_RGBA64: + return OCIO::BIT_DEPTH_UINT16; - case AV_PIX_FMT_GBRP12: - case AV_PIX_FMT_GBRAP12: - return OCIO::BIT_DEPTH_UINT12; - - // Note: FFmpeg treats half-float as specific types often requiring casts. - // For this snippet we map F16 directly if your system supports it, - // otherwise, standard float (F32) is safer. - case AV_PIX_FMT_GBRPF16: - case AV_PIX_FMT_GBRAPF16: - return OCIO::BIT_DEPTH_F16; + case AV_PIX_FMT_GBRP10: + case AV_PIX_FMT_GBRAP10: + return OCIO::BIT_DEPTH_UINT10; - case AV_PIX_FMT_GBRPF32: - case AV_PIX_FMT_GBRAPF32: - return OCIO::BIT_DEPTH_F32; - - default: - return OCIO::BIT_DEPTH_UNKNOWN; - } + case AV_PIX_FMT_GBRP12: + case AV_PIX_FMT_GBRAP12: + return OCIO::BIT_DEPTH_UINT12; + + // Note: FFmpeg treats half-float as specific types often requiring casts. + // For this snippet we map F16 directly if your system supports it, + // otherwise, standard float (F32) is safer. + case AV_PIX_FMT_GBRPF16: + case AV_PIX_FMT_GBRAPF16: + return OCIO::BIT_DEPTH_F16; + + case AV_PIX_FMT_GBRPF32: + case AV_PIX_FMT_GBRAPF32: + return OCIO::BIT_DEPTH_F32; + + default: + return OCIO::BIT_DEPTH_UNKNOWN; + } } +OCIOHandle +ocio_create_output_colorspace_processor(const char *config_path, + const char *input_color_space, + const char *output_color_space) { + try { -OCIOHandle ocio_create_output_colorspace_processor( - const char *config_path, - const char *input_color_space, - const char *output_color_space) -{ - try { + OCIOState *s = new OCIOState(); + if (!config_path) + s->config = OCIO::Config::CreateFromEnv(); + else + s->config = OCIO::Config::CreateFromFile(config_path); - OCIOState *s = new OCIOState(); - if (!config_path) - s->config = OCIO::Config::CreateFromEnv(); - else - s->config = OCIO::Config::CreateFromFile(config_path); + if (!s->config || !input_color_space || !output_color_space) + return nullptr; - if(!s->config ||!input_color_space || !output_color_space) - return nullptr; + // ColorSpace Transform: InputSpace -> OutputSpace + OCIO::ColorSpaceTransformRcPtr cst = OCIO::ColorSpaceTransform::Create(); + cst->setSrc(input_color_space); + cst->setDst(output_color_space); - // ColorSpace Transform: InputSpace -> OutputSpace - OCIO::ColorSpaceTransformRcPtr cst = OCIO::ColorSpaceTransform::Create(); - cst->setSrc(input_color_space); - cst->setDst(output_color_space); + s->processor = s->config->getProcessor(cst); - s->processor = s->config->getProcessor(cst); - - return (OCIOHandle)s; - } - catch(...) { - return nullptr; - } + return (OCIOHandle)s; + } catch (...) { + return nullptr; + } } -OCIOHandle ocio_create_display_view_processor( - const char *config_path, - const char *input_color_space, - const char *display, - const char *view, - int inverse) -{ - try { +OCIOHandle ocio_create_display_view_processor(const char *config_path, + const char *input_color_space, + const char *display, + const char *view, int inverse) { + try { - OCIOState *s = new OCIOState(); - if (!config_path) - s->config = OCIO::Config::CreateFromEnv(); - else - s->config = OCIO::Config::CreateFromFile(config_path); + OCIOState *s = new OCIOState(); + if (!config_path) + s->config = OCIO::Config::CreateFromEnv(); + else + s->config = OCIO::Config::CreateFromFile(config_path); - if(!s->config ||!input_color_space || !display || !view) - return nullptr; + if (!s->config || !input_color_space || !display || !view) + return nullptr; - // Display/View Transform: InputSpace -> Display/View - OCIO::DisplayViewTransformRcPtr dv = OCIO::DisplayViewTransform::Create(); - dv->setSrc(input_color_space); - dv->setDisplay(display); - dv->setView(view); - if (inverse) - dv->setDirection(OCIO::TRANSFORM_DIR_INVERSE); - + // Display/View Transform: InputSpace -> Display/View + OCIO::DisplayViewTransformRcPtr dv = OCIO::DisplayViewTransform::Create(); + dv->setSrc(input_color_space); + dv->setDisplay(display); + dv->setView(view); + if (inverse) + dv->setDirection(OCIO::TRANSFORM_DIR_INVERSE); - s->processor = s->config->getProcessor(dv); + s->processor = s->config->getProcessor(dv); - return (OCIOHandle)s; - } - catch(...) { - return nullptr; - } + return (OCIOHandle)s; + } catch (...) { + return nullptr; + } } // In ocio_wrapper.cpp -int ocio_finalize_processor(OCIOHandle handle, int input_format, int output_format) -{ +int ocio_finalize_processor(OCIOHandle handle, int input_format, + int output_format) { - try { - OCIOState *s = (OCIOState*)handle; - if (!s || !s->processor) return -1; + try { + OCIOState *s = (OCIOState *)handle; + if (!s || !s->processor) + return -1; - s->cpu = s->processor->getOptimizedCPUProcessor( - get_ocio_depth(input_format), - get_ocio_depth(output_format), - OCIO::OPTIMIZATION_DEFAULT - ); - + s->cpu = s->processor->getOptimizedCPUProcessor( + get_ocio_depth(input_format), get_ocio_depth(output_format), + OCIO::OPTIMIZATION_DEFAULT); + + return 0; + } catch (OCIO::Exception &e) { + std::cerr << "OCIO error: " << e.what() << std::endl; + return -1; + } catch (...) { + std::cerr << "Unknown error in ocio_finalize_processor" << std::endl; + return -1; + } +} + +static OCIO::ImageDesc *AVFrame2ImageDescSlice(AVFrame *frame, int y_start, + int height) { + OCIO::BitDepth ocio_bitdepth = get_ocio_depth(frame->format); + if (ocio_bitdepth == OCIO::BIT_DEPTH_UNKNOWN) { + throw std::runtime_error("Unsupported pixel format for OCIO processing"); + } + + int stridex = frame->linesize[0]; + const AVPixFmtDescriptor *desc = + av_pix_fmt_desc_get((enum AVPixelFormat)frame->format); + if (!desc) { + throw std::runtime_error("Invalid pixel format descriptor"); + } + + bool is_planar = desc && (desc->flags & AV_PIX_FMT_FLAG_PLANAR); + + if (is_planar) { + // For planar, we need to offset each plane + uint8_t *red = frame->data[2] + y_start * frame->linesize[2]; + uint8_t *green = frame->data[0] + y_start * frame->linesize[0]; + uint8_t *blue = frame->data[1] + y_start * frame->linesize[1]; + uint8_t *alpha = (desc->nb_components == 4) + ? (frame->data[3] + y_start * frame->linesize[3]) + : nullptr; + + return new OCIO::PlanarImageDesc( + (void *)red, (void *)green, (void *)blue, (void *)alpha, frame->width, + height, ocio_bitdepth, desc->comp[0].step, stridex); + } + + uint8_t *data = frame->data[0] + y_start * frame->linesize[0]; + // Note we are assuming that these are RGB or RGBA chanel ordering. + // And are also likely to be integer. + return new OCIO::PackedImageDesc( + (void *)data, frame->width, height, desc->nb_components, ocio_bitdepth, + desc->comp[0].depth / 8, desc->comp[0].step, frame->linesize[0]); +} + +static OCIO::ImageDesc *AVFrame2ImageDesc(AVFrame *frame) { + return AVFrame2ImageDescSlice(frame, 0, frame->height); +} + +int ocio_apply(OCIOHandle handle, AVFrame *input_frame, AVFrame *output_frame, + int threads) { + OCIOState *s = (OCIOState *)handle; + if (!s || !s->cpu) + return -1; + + try { + if (threads <= 1) { + // ... (existing single-threaded logic with timing) ... + // Re-implementing simplified to allow unified view or just keeping old + // logic effectively + if (input_frame == output_frame) { + OCIO::ImageDesc *imgDesc = AVFrame2ImageDesc(input_frame); + s->cpu->apply(*imgDesc); + delete imgDesc; return 0; - } - catch (OCIO::Exception &e) { - std::cerr << "OCIO error: " << e.what() << std::endl; - return -1; - } - catch (...) { - std::cerr << "Unknown error in ocio_finalize_processor" << std::endl; - return -1; - } -} + } -static OCIO::ImageDesc * AVFrame2ImageDesc(AVFrame *frame){ - OCIO::BitDepth ocio_bitdepth = get_ocio_depth(frame->format); - if (ocio_bitdepth == OCIO::BIT_DEPTH_UNKNOWN) { - throw std::runtime_error("Unsupported pixel format for OCIO processing"); + // Single thread copy + OCIO::ImageDesc *input = AVFrame2ImageDesc(input_frame); + OCIO::ImageDesc *output = AVFrame2ImageDesc(output_frame); + s->cpu->apply(*input, *output); + + delete input; + delete output; + return 0; } - int stridex = frame->linesize[0]; - const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get((enum AVPixelFormat)frame->format); - if (!desc) { - throw std::runtime_error("Invalid pixel format descriptor"); - } + // Threaded execution + std::vector<std::thread> pool; + std::vector<int> results(threads, 0); + int height = input_frame->height; + int slice_h = height / threads; - bool is_planar = desc && (desc->flags & AV_PIX_FMT_FLAG_PLANAR); + for (int i = 0; i < threads; i++) { + int y_start = i * slice_h; + int h = (i == threads - 1) ? (height - y_start) : slice_h; - if (is_planar){ - void *red = (void *)frame->data[2]; - void *green = (void *)frame->data[0]; - void *blue = (void *)frame->data[1]; - void *alpha = (desc->nb_components == 4) ? (void *)frame->data[3] : NULL; - return new OCIO::PlanarImageDesc( - (void*)red, (void*)green, (void*)blue, (void*)alpha, - frame->width, frame->height, - ocio_bitdepth, desc->comp[0].step, stridex - ); - } - void *data = (void *)frame->data[0]; - // Note we are assuming that these are RGB or RGBA chanel ordering. - // And are also likely to be integer. - return new OCIO::PackedImageDesc( - (void*)data, - frame->width, frame->height, desc->nb_components, - ocio_bitdepth, desc->comp[0].depth / 8, desc->comp[0].step, frame->linesize[0] - ); -} - -int ocio_apply( - OCIOHandle handle, - AVFrame *input_frame, - AVFrame *output_frame) -{ - OCIOState *s = (OCIOState*)handle; - if (!s || !s->cpu) return -1; - - try { - if (input_frame == output_frame){ - // In-place processing when input and output pix_fmts are the same. - OCIO::ImageDesc *imgDesc = AVFrame2ImageDesc(input_frame); + pool.emplace_back([=, &results]() { + try { + if (input_frame == output_frame) { + OCIO::ImageDesc *imgDesc = + AVFrame2ImageDescSlice(input_frame, y_start, h); s->cpu->apply(*imgDesc); delete imgDesc; - return 0; + } else { + OCIO::ImageDesc *input = + AVFrame2ImageDescSlice(input_frame, y_start, h); + OCIO::ImageDesc *output = + AVFrame2ImageDescSlice(output_frame, y_start, h); + s->cpu->apply(*input, *output); + delete input; + delete output; + } + results[i] = 0; + } catch (...) { + results[i] = -1; } - OCIO::ImageDesc *input = AVFrame2ImageDesc(input_frame); - OCIO::ImageDesc *output = AVFrame2ImageDesc(output_frame); - s->cpu->apply(*input, *output); - delete input; - delete output; - return 0; - } catch(const OCIO::Exception &ex) { - std::cerr << "OCIO error: " << ex.what() << std::endl; - return -2; // or another error code - } catch(const std::exception &ex) { - std::cerr << "Standard exception: " << ex.what() << std::endl; - return -3; - } catch(...) { - std::cerr << "Unknown error in OCIO processing." << std::endl; - return -4; + }); } + + for (auto &t : pool) { + t.join(); + } + + for (int res : results) { + if (res < 0) + return -2; + } + return 0; + + } catch (const OCIO::Exception &ex) { + std::cerr << "OCIO error: " << ex.what() << std::endl; + return -2; // or another error code + } catch (const std::exception &ex) { + std::cerr << "Standard exception: " << ex.what() << std::endl; + return -3; + } catch (...) { + std::cerr << "Unknown error in OCIO processing." << std::endl; + return -4; + } } -void ocio_destroy_processor(OCIOHandle handle) -{ - if(!handle) return; - delete (OCIOState*)handle; +void ocio_destroy_processor(OCIOHandle handle) { + if (!handle) + return; + delete (OCIOState *)handle; } } // extern "C" diff --git a/libavfilter/ocio_wrapper.hpp b/libavfilter/ocio_wrapper.hpp index 5eb2380249..c47de867e6 100644 --- a/libavfilter/ocio_wrapper.hpp +++ b/libavfilter/ocio_wrapper.hpp @@ -6,38 +6,34 @@ extern "C" { #endif -typedef void* OCIOHandle; +typedef void *OCIOHandle; // Create an OCIO processor for Display/View transform. // Returns NULL on failure. -OCIOHandle ocio_create_display_view_processor( - const char *config_path, - const char *input_color_space, - const char *display, - const char *view, - int inverse); - +OCIOHandle ocio_create_display_view_processor(const char *config_path, + const char *input_color_space, + const char *display, + const char *view, int inverse); // Create an OCIO processor for output colorspace transform. // Returns NULL on failure. -OCIOHandle ocio_create_output_colorspace_processor( - const char *config_path, - const char *input_color_space, - const char *output_color_space); - +OCIOHandle +ocio_create_output_colorspace_processor(const char *config_path, + const char *input_color_space, + const char *output_color_space); // Finalize OCIO processor for given bit depth. // is_half_float: true for half-float, false for float -int ocio_finalize_processor(OCIOHandle handle, int input_format, int output_format); +int ocio_finalize_processor(OCIOHandle handle, int input_format, + int output_format); // Apply processor to planar float RGB(A). // pixels: pointer to float samples // w,h: image dimensions // channels: 3 or 4 // stride_bytes: bytes between row starts (use 0 for tightly packed) -int ocio_apply( - OCIOHandle handle,AVFrame *input_frame, AVFrame *output_frame - ); +int ocio_apply(OCIOHandle handle, AVFrame *input_frame, AVFrame *output_frame, + int threads); // Destroy OCIO processor. void ocio_destroy_processor(OCIOHandle handle); diff --git a/libavfilter/vf_opencolorio.c b/libavfilter/vf_opencolorio.c index 076456eba7..f23471f99b 100644 --- a/libavfilter/vf_opencolorio.c +++ b/libavfilter/vf_opencolorio.c @@ -1,219 +1,221 @@ // libavfilter/vf_opencolorio.c #include "avfilter.h" #include "formats.h" -#include "video.h" -#include "libavutil/pixdesc.h" -#include "libavutil/opt.h" #include "libavutil/half2float.h" +#include "libavutil/opt.h" +#include "libavutil/pixdesc.h" +#include "libavutil/time.h" #include "ocio_wrapper.hpp" +#include "video.h" typedef struct { - const AVClass *class; - char *config_path; - char *input_space; - char *output_space; - char *display; - char *view; - int inverse; - OCIOHandle ocio; - int output_format; - char *out_format_string; // e.g. "rgb48le" which is converted to AVPixelFormat as output_format - int channels; // 3 or 4 depending on pixfmt + const AVClass *class; + char *config_path; + char *input_space; + char *output_space; + char *display; + char *view; + int inverse; + OCIOHandle ocio; + int output_format; + char *out_format_string; // e.g. "rgb48le" which is converted to AVPixelFormat + // as output_format + int channels; // 3 or 4 depending on pixfmt + int threads; } OCIOContext; - - -static int query_formats(AVFilterContext *ctx) -{ - static const enum AVPixelFormat pix_fmts[] = { - // 8-bit - AV_PIX_FMT_RGBA, - AV_PIX_FMT_RGB24, - // 16-bit - AV_PIX_FMT_RGBA64, - AV_PIX_FMT_RGB48, - // 10-bit - AV_PIX_FMT_GBRP10, - AV_PIX_FMT_GBRAP10, - // 12-bit - AV_PIX_FMT_GBRP12, - AV_PIX_FMT_GBRAP12, - // Half-float and float - AV_PIX_FMT_GBRPF16, - AV_PIX_FMT_GBRAPF16, - // Float - AV_PIX_FMT_GBRPF32, - AV_PIX_FMT_GBRAPF32, - AV_PIX_FMT_NONE - }; - return ff_set_common_formats(ctx, ff_make_format_list(pix_fmts)); +static int query_formats(AVFilterContext *ctx) { + static const enum AVPixelFormat pix_fmts[] = { + // 8-bit + AV_PIX_FMT_RGBA, AV_PIX_FMT_RGB24, + // 16-bit + AV_PIX_FMT_RGBA64, AV_PIX_FMT_RGB48, + // 10-bit + AV_PIX_FMT_GBRP10, AV_PIX_FMT_GBRAP10, + // 12-bit + AV_PIX_FMT_GBRP12, AV_PIX_FMT_GBRAP12, + // Half-float and float + AV_PIX_FMT_GBRPF16, AV_PIX_FMT_GBRAPF16, + // Float + AV_PIX_FMT_GBRPF32, AV_PIX_FMT_GBRAPF32, AV_PIX_FMT_NONE}; + return ff_set_common_formats(ctx, ff_make_format_list(pix_fmts)); } -static int config_props(AVFilterLink *inlink) -{ - AVFilterContext *ctx = inlink->dst; - OCIOContext *s = ctx->priv; - const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format); +static int config_props(AVFilterLink *inlink) { + AVFilterContext *ctx = inlink->dst; + OCIOContext *s = ctx->priv; + const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format); - if (!desc) { - av_log(ctx, AV_LOG_ERROR, "Invalid pixel format\n"); - return AVERROR(EINVAL); + if (!desc) { + av_log(ctx, AV_LOG_ERROR, "Invalid pixel format\n"); + return AVERROR(EINVAL); + } + + int is_half = + (desc->comp[0].depth == 16 && desc->flags & AV_PIX_FMT_FLAG_FLOAT); + if (s->output_format == AV_PIX_FMT_NONE) { + // Need to set the output format now, if not known. + if (is_half) { + // If its half-float, we output float, due to a bug in ffmpeg with + // half-float frames + s->output_format = AV_PIX_FMT_GBRAPF32; + } else { + // If output format not set, use same as input + s->output_format = inlink->format; } + } - int is_half = (desc->comp[0].depth == 16 && desc->flags & AV_PIX_FMT_FLAG_FLOAT); - if (s->output_format == AV_PIX_FMT_NONE) { - // Need to set the output format now, if not known. - if (is_half){ - // If its half-float, we output float, due to a bug in ffmpeg with half-float frames - s->output_format = AV_PIX_FMT_GBRAPF32; - } else { - // If output format not set, use same as input - s->output_format = inlink->format; - } - } - - s->channels = desc->nb_components; // 3 or 4 + s->channels = desc->nb_components; // 3 or 4 - av_log(ctx, AV_LOG_INFO, "Configuring OCIO for %s (bit depth: %d, channels: %d), output format: (%s)\n", - av_get_pix_fmt_name(inlink->format), desc->comp[0].depth, s->channels, av_get_pix_fmt_name(s->output_format)); + av_log(ctx, AV_LOG_INFO, + "Configuring OCIO for %s (bit depth: %d, channels: %d), output " + "format: (%s)\n", + av_get_pix_fmt_name(inlink->format), desc->comp[0].depth, s->channels, + av_get_pix_fmt_name(s->output_format)); - // Now finalize the OCIO processor with the correct bit depth - int ret = ocio_finalize_processor(s->ocio, inlink->format, s->output_format); - if (ret < 0) { - av_log(ctx, AV_LOG_ERROR, "Failed to finalize OCIO processor for bit depth\n"); - return AVERROR_EXTERNAL; - } + // Now finalize the OCIO processor with the correct bit depth + int ret = ocio_finalize_processor(s->ocio, inlink->format, s->output_format); + if (ret < 0) { + av_log(ctx, AV_LOG_ERROR, + "Failed to finalize OCIO processor for bit depth\n"); + return AVERROR_EXTERNAL; + } - return 0; + return 0; } +static av_cold int init(AVFilterContext *ctx) { + OCIOContext *s = ctx->priv; + if (s->out_format_string != NULL) { + s->output_format = av_get_pix_fmt(s->out_format_string); + } else { + // Default to same as input format (see later). + s->output_format = AV_PIX_FMT_NONE; + } + av_log(ctx, AV_LOG_INFO, + "Creating OCIO processor with config: %s, input: %s, display: %s, " + "view: %s\n", + s->config_path, s->input_space, s->display, s->view); + if (s->output_space && strlen(s->output_space) > 0) { + s->ocio = ocio_create_output_colorspace_processor( + s->config_path, s->input_space, s->output_space); + } else { + s->ocio = ocio_create_display_view_processor( + s->config_path, s->input_space, s->display, s->view, s->inverse); + } + if (!s->ocio) { + av_log(ctx, AV_LOG_ERROR, "Failed to create OCIO processor.\n"); + return AVERROR(EINVAL); + } -static av_cold int init(AVFilterContext *ctx) -{ - OCIOContext *s = ctx->priv; - if (s->out_format_string != NULL) { - s->output_format = av_get_pix_fmt(s->out_format_string); - } else { - // Default to same as input format (see later). - s->output_format = AV_PIX_FMT_NONE; - } - av_log(ctx, AV_LOG_INFO, "Creating OCIO processor with config: %s, input: %s, display: %s, view: %s\n", s->config_path, s->input_space, s->display, s->view); - if (s->output_space && strlen(s->output_space) > 0) { - s->ocio = ocio_create_output_colorspace_processor( - s->config_path, - s->input_space, - s->output_space); - } else { - s->ocio = ocio_create_display_view_processor( - s->config_path, - s->input_space, - s->display, - s->view, - s->inverse); - } - if(!s->ocio) { - av_log(ctx, AV_LOG_ERROR, "Failed to create OCIO processor.\n"); - return AVERROR(EINVAL); - } - - return 0; + return 0; } -static int filter_frame(AVFilterLink *inlink, AVFrame *frame) -{ - AVFilterContext *ctx = inlink->dst; - OCIOContext *s = ctx->priv; - const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(frame->format); +static int filter_frame(AVFilterLink *inlink, AVFrame *frame) { + AVFilterContext *ctx = inlink->dst; + OCIOContext *s = ctx->priv; + const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(frame->format); - if(!desc) return AVERROR(EINVAL); + if (!desc) + return AVERROR(EINVAL); - int channels = desc->nb_components; // 3 or 4 - s->channels = channels; + int channels = desc->nb_components; // 3 or 4 + s->channels = channels; - int ret; - AVFrame *output_frame; - if (s->output_format == inlink->format){ - // No conversion needed, pass through - output_frame = frame; - ret = ocio_apply(s->ocio, frame, output_frame); + int ret; + AVFrame *output_frame; + if (s->output_format == inlink->format) { + /* No pixel-format conversion needed. If the input frame is + * writable we can apply OCIO in-place, otherwise allocate a + * separate output frame to avoid mutating shared buffers. */ + if (av_frame_is_writable(frame)) { + output_frame = frame; + ret = ocio_apply(s->ocio, frame, output_frame, s->threads); } else { - // Allocate new output frame - output_frame = av_frame_alloc(); - output_frame->format = s->output_format; - output_frame->width = frame->width; - output_frame->height = frame->height; - ret = av_frame_get_buffer(output_frame, 32); - if (ret < 0) { - av_frame_free(&output_frame); - av_frame_free(&frame); - return ret; - } - av_frame_copy_props(output_frame, frame); - - ret = ocio_apply(s->ocio, frame, output_frame); - + output_frame = av_frame_alloc(); + output_frame->format = s->output_format; + output_frame->width = frame->width; + output_frame->height = frame->height; + ret = av_frame_get_buffer(output_frame, 32); + + if (ret < 0) { + av_frame_free(&output_frame); av_frame_free(&frame); - } - - if(ret < 0) { - av_log(ctx, AV_LOG_ERROR, "OCIO apply failed.\n"); - return AVERROR(EINVAL); + return ret; + } + av_frame_copy_props(output_frame, frame); + ret = ocio_apply(s->ocio, frame, output_frame, s->threads); + av_frame_free(&frame); } + } else { + // Allocate new output frame + output_frame = av_frame_alloc(); + output_frame->format = s->output_format; + output_frame->width = frame->width; + output_frame->height = frame->height; + ret = av_frame_get_buffer(output_frame, 32); + if (ret < 0) { + av_frame_free(&output_frame); + av_frame_free(&frame); + return ret; + } + av_frame_copy_props(output_frame, frame); + ret = ocio_apply(s->ocio, frame, output_frame, s->threads); + av_frame_free(&frame); + } - av_log(ctx, AV_LOG_INFO, "OCIO apply succeeded.\n"); - return ff_filter_frame(ctx->outputs[0], output_frame); + if (ret < 0) { + av_log(ctx, AV_LOG_ERROR, "OCIO apply failed.\n"); + return AVERROR(EINVAL); + } + + return ff_filter_frame(ctx->outputs[0], output_frame); } -static av_cold void uninit(AVFilterContext *ctx) -{ - OCIOContext *s = ctx->priv; - if(s->ocio) { - ocio_destroy_processor(s->ocio); - s->ocio = NULL; - } +static av_cold void uninit(AVFilterContext *ctx) { + OCIOContext *s = ctx->priv; + if (s->ocio) { + ocio_destroy_processor(s->ocio); + s->ocio = NULL; + } } #define OFFSET(x) offsetof(OCIOContext, x) -#define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM +#define FLAGS AV_OPT_FLAG_FILTERING_PARAM | AV_OPT_FLAG_VIDEO_PARAM static const AVOption ocio_options[] = { { "config", "OCIO config path, overriding OCIO environment variable.", OFFSET(config_path), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, FLAGS }, { "input", "Input color space", OFFSET(input_space), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, FLAGS }, - { "output", "Output color space", OFFSET(output_space), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, FLAGS }, + { "output", "Output color space", OFFSET(output_space), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, FLAGS }, { "display", "Output display, used insted of output color space.", OFFSET(display), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, FLAGS }, { "view", "View, output view transform, used in combination with display.", OFFSET(view), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, FLAGS }, { "inverse", "Invert output display/view transform.", OFFSET(inverse), AV_OPT_TYPE_INT, {.i64=0}, 0, 1, FLAGS }, - { "format", "Output video format", OFFSET(out_format_string), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, FLAGS }, - { NULL } + { "format", "Output video format", OFFSET(out_format_string), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, FLAGS }, + { "threads", "Number of threads", OFFSET(threads), AV_OPT_TYPE_INT, {.i64 = 1}, 0, 16, FLAGS},{ NULL } }; AVFILTER_DEFINE_CLASS(ocio); static const AVFilterPad inputs[] = { - { - .name="default", - .type=AVMEDIA_TYPE_VIDEO, - .filter_frame = filter_frame, - .config_props = config_props, + { + .name = "default", + .type = AVMEDIA_TYPE_VIDEO, + .filter_frame = filter_frame, + .config_props = config_props, }, }; static const AVFilterPad outputs[] = { - { - .name="default", - .type=AVMEDIA_TYPE_VIDEO - } -}; + {.name = "default", .type = AVMEDIA_TYPE_VIDEO}}; const FFFilter ff_vf_ocio = { - .p.name = "ocio", + .p.name = "ocio", .p.description = NULL_IF_CONFIG_SMALL("Apply OCIO Display/View transform"), - .p.priv_class = &ocio_class, - .priv_size = sizeof(OCIOContext), - .init = init, - .uninit = uninit, + .p.priv_class = &ocio_class, + .priv_size = sizeof(OCIOContext), + .init = init, + .uninit = uninit, FILTER_INPUTS(inputs), FILTER_OUTPUTS(outputs), - FILTER_QUERY_FUNC(query_formats) -}; + FILTER_QUERY_FUNC(query_formats)}; -- 2.52.0 >From dec25262ec00710ab646c8649ccb6945af69c576 Mon Sep 17 00:00:00 2001 From: "[email protected]" <[email protected]> Date: Thu, 22 Jan 2026 16:14:58 +0000 Subject: [PATCH 10/18] Adding context parameters. Signed-off-by: [email protected] <[email protected]> --- doc/filters.texi | 7 ++++ libavfilter/ocio_wrapper.cpp | 69 ++++++++++++++++++++++++++++++------ libavfilter/ocio_wrapper.hpp | 6 ++-- libavfilter/vf_opencolorio.c | 8 +++-- 4 files changed, 74 insertions(+), 16 deletions(-) diff --git a/doc/filters.texi b/doc/filters.texi index 2396a75e68..96f76d3f30 100644 --- a/doc/filters.texi +++ b/doc/filters.texi @@ -18757,6 +18757,13 @@ When used in combination with display and view, this inverts the transform, so g @item format Allow you to specify the output pix_fmt of the OCIO filter. This *has* to be a RGB colorspace, so you really are limited to rgb24, rgba, rgb48, rgba48, gbrp10, gbrp12, gbrpf32le, gbrapf32le, for most encoding we would recommend rgb48 + +@item context_params +Allow you to specify additional context parameters for the OCIO filter. This is a list of key=value pairs, separated by colons. + +@item threads +Allow you to specify the number of threads to use for the OCIO filter. + @end table @subsection Examples diff --git a/libavfilter/ocio_wrapper.cpp b/libavfilter/ocio_wrapper.cpp index 786863e844..c11dcc06ea 100644 --- a/libavfilter/ocio_wrapper.cpp +++ b/libavfilter/ocio_wrapper.cpp @@ -21,6 +21,7 @@ extern "C" { #include "ocio_wrapper.hpp" #include <libavutil/frame.h> #include <libavutil/pixdesc.h> +#include <libavutil/dict.h> // Helper to map AV_PIX_FMT to OCIO BitDepth static OCIO::BitDepth get_ocio_depth(int format) { @@ -57,10 +58,31 @@ static OCIO::BitDepth get_ocio_depth(int format) { } } +static OCIO::ConstContextRcPtr add_context_params(OCIO::ConstConfigRcPtr config, AVDictionary *params) { + + OCIO::ConstContextRcPtr context = config->getCurrentContext(); + if (!params) + return context; + if (!context) { + return nullptr; + } + OCIO::ContextRcPtr ctx = context->createEditableCopy(); + if (!ctx) { + return context; + } + const AVDictionaryEntry *e = NULL; + while ((e = av_dict_iterate(params, e))) { + std::cerr << "Setting OCIO context param " << e->key << " to " << e->value << std::endl; + ctx->setStringVar(e->key, e->value); + } + return ctx; +} + OCIOHandle ocio_create_output_colorspace_processor(const char *config_path, const char *input_color_space, - const char *output_color_space) { + const char *output_color_space, + AVDictionary *params) { try { OCIOState *s = new OCIOState(); @@ -69,18 +91,27 @@ ocio_create_output_colorspace_processor(const char *config_path, else s->config = OCIO::Config::CreateFromFile(config_path); - if (!s->config || !input_color_space || !output_color_space) - return nullptr; + if (!s->config || !input_color_space || !output_color_space) { + std::cerr << "Error: Config or color spaces invalid." << std::endl; + if (!s->config) std::cerr << "Config is null" << std::endl; + if (!input_color_space) std::cerr << "Input color space is null" << std::endl; + if (!output_color_space) std::cerr << "Output color space is null" << std::endl; + return nullptr; + } // ColorSpace Transform: InputSpace -> OutputSpace OCIO::ColorSpaceTransformRcPtr cst = OCIO::ColorSpaceTransform::Create(); cst->setSrc(input_color_space); - cst->setDst(output_color_space); - - s->processor = s->config->getProcessor(cst); + cst->setDst(output_color_space); + auto context = add_context_params(s->config, params); + s->processor = s->config->getProcessor(context, cst, OCIO::TRANSFORM_DIR_FORWARD); return (OCIOHandle)s; + } catch (OCIO::Exception &e) { + std::cerr << "OCIO Filter: Error in create_output_colorspace_processor: " << e.what() << std::endl; + return nullptr; } catch (...) { + std::cerr << "OCIO Filter: Unknown Error in create_output_colorspace_processor" << std::endl; return nullptr; } } @@ -88,7 +119,8 @@ ocio_create_output_colorspace_processor(const char *config_path, OCIOHandle ocio_create_display_view_processor(const char *config_path, const char *input_color_space, const char *display, - const char *view, int inverse) { + const char *view, int inverse, + AVDictionary *params) { try { OCIOState *s = new OCIOState(); @@ -97,21 +129,36 @@ OCIOHandle ocio_create_display_view_processor(const char *config_path, else s->config = OCIO::Config::CreateFromFile(config_path); - if (!s->config || !input_color_space || !display || !view) - return nullptr; + if (!s->config || !input_color_space || !display || !view) { + std::cerr << "Error: Config or arguments invalid." << std::endl; + if (!s->config) std::cerr << "Config is null" << std::endl; + if (!input_color_space) std::cerr << "Input color space is null" << std::endl; + if (!display) std::cerr << "Display is null" << std::endl; + if (!view) std::cerr << "View is null" << std::endl; + return nullptr; + } // Display/View Transform: InputSpace -> Display/View OCIO::DisplayViewTransformRcPtr dv = OCIO::DisplayViewTransform::Create(); dv->setSrc(input_color_space); dv->setDisplay(display); dv->setView(view); + OCIO::TransformDirection direction = OCIO::TRANSFORM_DIR_FORWARD; if (inverse) - dv->setDirection(OCIO::TRANSFORM_DIR_INVERSE); + direction = OCIO::TRANSFORM_DIR_INVERSE; + std::cerr << "Direction is " << direction << std::endl; + OCIO::ConstContextRcPtr context = add_context_params(s->config, params); + std::cerr << "Context is " << context << std::endl; + s->processor = s->config->getProcessor(context, dv, direction); + //s->processor = s->config->getProcessor(dv); - s->processor = s->config->getProcessor(dv); return (OCIOHandle)s; + } catch (OCIO::Exception &e) { + std::cerr << "OCIO Error in create_display_view_processor: " << e.what() << std::endl; + return nullptr; } catch (...) { + std::cerr << "Unknown Error in create_display_view_processor" << std::endl; return nullptr; } } diff --git a/libavfilter/ocio_wrapper.hpp b/libavfilter/ocio_wrapper.hpp index c47de867e6..eda5365206 100644 --- a/libavfilter/ocio_wrapper.hpp +++ b/libavfilter/ocio_wrapper.hpp @@ -13,14 +13,16 @@ typedef void *OCIOHandle; OCIOHandle ocio_create_display_view_processor(const char *config_path, const char *input_color_space, const char *display, - const char *view, int inverse); + const char *view, int inverse, + AVDictionary *params); // Create an OCIO processor for output colorspace transform. // Returns NULL on failure. OCIOHandle ocio_create_output_colorspace_processor(const char *config_path, const char *input_color_space, - const char *output_color_space); + const char *output_color_space, + AVDictionary *params); // Finalize OCIO processor for given bit depth. // is_half_float: true for half-float, false for float diff --git a/libavfilter/vf_opencolorio.c b/libavfilter/vf_opencolorio.c index f23471f99b..394364a52e 100644 --- a/libavfilter/vf_opencolorio.c +++ b/libavfilter/vf_opencolorio.c @@ -22,6 +22,7 @@ typedef struct { // as output_format int channels; // 3 or 4 depending on pixfmt int threads; + AVDictionary *context_params; } OCIOContext; static int query_formats(AVFilterContext *ctx) { @@ -98,10 +99,10 @@ static av_cold int init(AVFilterContext *ctx) { s->config_path, s->input_space, s->display, s->view); if (s->output_space && strlen(s->output_space) > 0) { s->ocio = ocio_create_output_colorspace_processor( - s->config_path, s->input_space, s->output_space); + s->config_path, s->input_space, s->output_space, s->context_params); } else { s->ocio = ocio_create_display_view_processor( - s->config_path, s->input_space, s->display, s->view, s->inverse); + s->config_path, s->input_space, s->display, s->view, s->inverse, s->context_params); } if (!s->ocio) { av_log(ctx, AV_LOG_ERROR, "Failed to create OCIO processor.\n"); @@ -192,7 +193,8 @@ static const AVOption ocio_options[] = { { "view", "View, output view transform, used in combination with display.", OFFSET(view), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, FLAGS }, { "inverse", "Invert output display/view transform.", OFFSET(inverse), AV_OPT_TYPE_INT, {.i64=0}, 0, 1, FLAGS }, { "format", "Output video format", OFFSET(out_format_string), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, FLAGS }, - { "threads", "Number of threads", OFFSET(threads), AV_OPT_TYPE_INT, {.i64 = 1}, 0, 16, FLAGS},{ NULL } + { "threads", "Number of threads", OFFSET(threads), AV_OPT_TYPE_INT, {.i64 = 1}, 0, 16, FLAGS}, + { "context_params", "OCIO context parameters", OFFSET(context_params), AV_OPT_TYPE_DICT, { .str = NULL }, 0, 0, FLAGS },{ NULL } }; AVFILTER_DEFINE_CLASS(ocio); -- 2.52.0 >From 0770c95188759db7fc5a0b61f1a48afbcb5bdb1a Mon Sep 17 00:00:00 2001 From: "[email protected]" <[email protected]> Date: Thu, 22 Jan 2026 16:45:48 +0000 Subject: [PATCH 11/18] Add the OCIO config parameter. Signed-off-by: [email protected] <[email protected]> --- doc/filters.texi | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/filters.texi b/doc/filters.texi index 96f76d3f30..2ed777cbef 100644 --- a/doc/filters.texi +++ b/doc/filters.texi @@ -18740,6 +18740,10 @@ compliation of this filter, you need to configure FFmpeg with @code{--enable-lib It accepts the following options: @table @option +@item config +By default the filter will use the OCIO config defined by the OCIO environment variable, but this parameter allows you to explicitly specify its location. +If you are getting started, you can use config=ocio://studio-config-v1.0.0_aces-v1.3_ocio-v2.1 which specifies one of the built in defaults. + @item input Set the input colorspace. -- 2.52.0 >From 1c52c7fb6d2ac8373e3d1ee0ddc6a9796fed9e66 Mon Sep 17 00:00:00 2001 From: "[email protected]" <[email protected]> Date: Thu, 22 Jan 2026 17:02:55 +0000 Subject: [PATCH 12/18] Make the min threads 1 for now, reserve 0 for later if we can automatically pick something. Also added a few comments. Signed-off-by: [email protected] <[email protected]> --- libavfilter/ocio_wrapper.cpp | 7 ++++--- libavfilter/vf_opencolorio.c | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/libavfilter/ocio_wrapper.cpp b/libavfilter/ocio_wrapper.cpp index c11dcc06ea..6dbe16d418 100644 --- a/libavfilter/ocio_wrapper.cpp +++ b/libavfilter/ocio_wrapper.cpp @@ -256,7 +256,8 @@ int ocio_apply(OCIOHandle handle, AVFrame *input_frame, AVFrame *output_frame, return 0; } - // Threaded execution + // Threaded execution, splitting the image into multiple horizontal strips based on the number of threads + // We make sliced ImageDesc based on the existing source and destination images. std::vector<std::thread> pool; std::vector<int> results(threads, 0); int height = input_frame->height; @@ -303,10 +304,10 @@ int ocio_apply(OCIOHandle handle, AVFrame *input_frame, AVFrame *output_frame, std::cerr << "OCIO error: " << ex.what() << std::endl; return -2; // or another error code } catch (const std::exception &ex) { - std::cerr << "Standard exception: " << ex.what() << std::endl; + std::cerr << "OCIO error: Standard exception: " << ex.what() << std::endl; return -3; } catch (...) { - std::cerr << "Unknown error in OCIO processing." << std::endl; + std::cerr << "OCIO error: Unknown error in OCIO processing." << std::endl; return -4; } } diff --git a/libavfilter/vf_opencolorio.c b/libavfilter/vf_opencolorio.c index 394364a52e..9dd6937926 100644 --- a/libavfilter/vf_opencolorio.c +++ b/libavfilter/vf_opencolorio.c @@ -193,7 +193,7 @@ static const AVOption ocio_options[] = { { "view", "View, output view transform, used in combination with display.", OFFSET(view), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, FLAGS }, { "inverse", "Invert output display/view transform.", OFFSET(inverse), AV_OPT_TYPE_INT, {.i64=0}, 0, 1, FLAGS }, { "format", "Output video format", OFFSET(out_format_string), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, FLAGS }, - { "threads", "Number of threads", OFFSET(threads), AV_OPT_TYPE_INT, {.i64 = 1}, 0, 16, FLAGS}, + { "threads", "Number of threads", OFFSET(threads), AV_OPT_TYPE_INT, {.i64 = 1}, 1, 16, FLAGS}, { "context_params", "OCIO context parameters", OFFSET(context_params), AV_OPT_TYPE_DICT, { .str = NULL }, 0, 0, FLAGS },{ NULL } }; -- 2.52.0 >From 8fd0beedd28ec1384a331ee34f56b4a173b45001 Mon Sep 17 00:00:00 2001 From: "[email protected]" <[email protected]> Date: Fri, 23 Jan 2026 10:54:51 +0000 Subject: [PATCH 13/18] This is using ffmpeg-slicing. Signed-off-by: [email protected] <[email protected]> --- doc/filters.texi | 3 -- libavfilter/ocio_wrapper.cpp | 60 +++--------------------------------- libavfilter/ocio_wrapper.hpp | 2 +- libavfilter/vf_opencolorio.c | 50 +++++++++++++++++++++++++----- 4 files changed, 48 insertions(+), 67 deletions(-) diff --git a/doc/filters.texi b/doc/filters.texi index 2ed777cbef..fae35b437a 100644 --- a/doc/filters.texi +++ b/doc/filters.texi @@ -18765,9 +18765,6 @@ Allow you to specify the output pix_fmt of the OCIO filter. This *has* to be a R @item context_params Allow you to specify additional context parameters for the OCIO filter. This is a list of key=value pairs, separated by colons. -@item threads -Allow you to specify the number of threads to use for the OCIO filter. - @end table @subsection Examples diff --git a/libavfilter/ocio_wrapper.cpp b/libavfilter/ocio_wrapper.cpp index 6dbe16d418..82476a7b0b 100644 --- a/libavfilter/ocio_wrapper.cpp +++ b/libavfilter/ocio_wrapper.cpp @@ -3,8 +3,6 @@ #include <OpenColorIO/OpenColorIO.h> #include <exception> #include <iostream> -#include <thread> -#include <vector> namespace OCIO = OCIO_NAMESPACE; @@ -229,76 +227,26 @@ static OCIO::ImageDesc *AVFrame2ImageDesc(AVFrame *frame) { } int ocio_apply(OCIOHandle handle, AVFrame *input_frame, AVFrame *output_frame, - int threads) { + int y_start, int height) { OCIOState *s = (OCIOState *)handle; if (!s || !s->cpu) return -1; try { - if (threads <= 1) { - // ... (existing single-threaded logic with timing) ... - // Re-implementing simplified to allow unified view or just keeping old - // logic effectively if (input_frame == output_frame) { - OCIO::ImageDesc *imgDesc = AVFrame2ImageDesc(input_frame); + OCIO::ImageDesc *imgDesc = AVFrame2ImageDescSlice(input_frame, y_start, height); s->cpu->apply(*imgDesc); delete imgDesc; return 0; } - // Single thread copy - OCIO::ImageDesc *input = AVFrame2ImageDesc(input_frame); - OCIO::ImageDesc *output = AVFrame2ImageDesc(output_frame); + OCIO::ImageDesc *input = AVFrame2ImageDescSlice(input_frame, y_start, height); + OCIO::ImageDesc *output = AVFrame2ImageDescSlice(output_frame, y_start, height); s->cpu->apply(*input, *output); delete input; delete output; return 0; - } - - // Threaded execution, splitting the image into multiple horizontal strips based on the number of threads - // We make sliced ImageDesc based on the existing source and destination images. - std::vector<std::thread> pool; - std::vector<int> results(threads, 0); - int height = input_frame->height; - int slice_h = height / threads; - - for (int i = 0; i < threads; i++) { - int y_start = i * slice_h; - int h = (i == threads - 1) ? (height - y_start) : slice_h; - - pool.emplace_back([=, &results]() { - try { - if (input_frame == output_frame) { - OCIO::ImageDesc *imgDesc = - AVFrame2ImageDescSlice(input_frame, y_start, h); - s->cpu->apply(*imgDesc); - delete imgDesc; - } else { - OCIO::ImageDesc *input = - AVFrame2ImageDescSlice(input_frame, y_start, h); - OCIO::ImageDesc *output = - AVFrame2ImageDescSlice(output_frame, y_start, h); - s->cpu->apply(*input, *output); - delete input; - delete output; - } - results[i] = 0; - } catch (...) { - results[i] = -1; - } - }); - } - - for (auto &t : pool) { - t.join(); - } - - for (int res : results) { - if (res < 0) - return -2; - } - return 0; } catch (const OCIO::Exception &ex) { std::cerr << "OCIO error: " << ex.what() << std::endl; diff --git a/libavfilter/ocio_wrapper.hpp b/libavfilter/ocio_wrapper.hpp index eda5365206..684144f41d 100644 --- a/libavfilter/ocio_wrapper.hpp +++ b/libavfilter/ocio_wrapper.hpp @@ -35,7 +35,7 @@ int ocio_finalize_processor(OCIOHandle handle, int input_format, // channels: 3 or 4 // stride_bytes: bytes between row starts (use 0 for tightly packed) int ocio_apply(OCIOHandle handle, AVFrame *input_frame, AVFrame *output_frame, - int threads); + int y_start, int height); // Destroy OCIO processor. void ocio_destroy_processor(OCIOHandle handle); diff --git a/libavfilter/vf_opencolorio.c b/libavfilter/vf_opencolorio.c index 9dd6937926..2893a5ef36 100644 --- a/libavfilter/vf_opencolorio.c +++ b/libavfilter/vf_opencolorio.c @@ -21,10 +21,27 @@ typedef struct { char *out_format_string; // e.g. "rgb48le" which is converted to AVPixelFormat // as output_format int channels; // 3 or 4 depending on pixfmt - int threads; AVDictionary *context_params; } OCIOContext; +typedef struct ThreadData { + AVFrame *in, *out; +} ThreadData; + +static int ocio_filter_slice(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs) +{ + OCIOContext *s = ctx->priv; + ThreadData *td = arg; + AVFrame *in = td->in; + AVFrame *out = td->out; + const int height = out->height; + const int slice_start = (height * jobnr) / nb_jobs; + const int slice_end = (height * (jobnr + 1)) / nb_jobs; + const int slice_h = slice_end - slice_start; + + return ocio_apply(s->ocio, in, out, slice_start, slice_h); +} + static int query_formats(AVFilterContext *ctx) { static const enum AVPixelFormat pix_fmts[] = { // 8-bit @@ -125,15 +142,20 @@ static int filter_frame(AVFilterLink *inlink, AVFrame *frame) { int ret; AVFrame *output_frame; + ThreadData td; + if (s->output_format == inlink->format) { /* No pixel-format conversion needed. If the input frame is * writable we can apply OCIO in-place, otherwise allocate a * separate output frame to avoid mutating shared buffers. */ if (av_frame_is_writable(frame)) { output_frame = frame; - ret = ocio_apply(s->ocio, frame, output_frame, s->threads); } else { output_frame = av_frame_alloc(); + if (!output_frame) { + av_frame_free(&frame); + return AVERROR(ENOMEM); + } output_frame->format = s->output_format; output_frame->width = frame->width; output_frame->height = frame->height; @@ -145,12 +167,14 @@ static int filter_frame(AVFilterLink *inlink, AVFrame *frame) { return ret; } av_frame_copy_props(output_frame, frame); - ret = ocio_apply(s->ocio, frame, output_frame, s->threads); - av_frame_free(&frame); } } else { // Allocate new output frame output_frame = av_frame_alloc(); + if (!output_frame) { + av_frame_free(&frame); + return AVERROR(ENOMEM); + } output_frame->format = s->output_format; output_frame->width = frame->width; output_frame->height = frame->height; @@ -162,10 +186,22 @@ static int filter_frame(AVFilterLink *inlink, AVFrame *frame) { return ret; } av_frame_copy_props(output_frame, frame); - ret = ocio_apply(s->ocio, frame, output_frame, s->threads); - av_frame_free(&frame); } + td.in = frame; + td.out = output_frame; + + // Use threads from context if set, otherwise let ffmpeg decide based on global settings or defaults + // Note: ctx->graph->nb_threads is usually the global thread count. + // ff_filter_get_nb_threads(ctx) gives the number of threads available for this filter. + + int nb_jobs = ff_filter_get_nb_threads(ctx); + + ret = ff_filter_execute(ctx, ocio_filter_slice, &td, NULL, FFMIN(output_frame->height, nb_jobs)); + + if (frame != output_frame) + av_frame_free(&frame); + if (ret < 0) { av_log(ctx, AV_LOG_ERROR, "OCIO apply failed.\n"); return AVERROR(EINVAL); @@ -193,7 +229,6 @@ static const AVOption ocio_options[] = { { "view", "View, output view transform, used in combination with display.", OFFSET(view), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, FLAGS }, { "inverse", "Invert output display/view transform.", OFFSET(inverse), AV_OPT_TYPE_INT, {.i64=0}, 0, 1, FLAGS }, { "format", "Output video format", OFFSET(out_format_string), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, FLAGS }, - { "threads", "Number of threads", OFFSET(threads), AV_OPT_TYPE_INT, {.i64 = 1}, 1, 16, FLAGS}, { "context_params", "OCIO context parameters", OFFSET(context_params), AV_OPT_TYPE_DICT, { .str = NULL }, 0, 0, FLAGS },{ NULL } }; @@ -215,6 +250,7 @@ const FFFilter ff_vf_ocio = { .p.name = "ocio", .p.description = NULL_IF_CONFIG_SMALL("Apply OCIO Display/View transform"), .p.priv_class = &ocio_class, + .p.flags = AVFILTER_FLAG_SLICE_THREADS, .priv_size = sizeof(OCIOContext), .init = init, .uninit = uninit, -- 2.52.0 >From d025fd825782eed0ac3329049520da4d7bc97a5d Mon Sep 17 00:00:00 2001 From: "[email protected]" <[email protected]> Date: Mon, 26 Jan 2026 20:17:25 +0000 Subject: [PATCH 14/18] Adding OCIO filetransform. --- libavfilter/ocio_wrapper.cpp | 29 +++++++++++++++++++++++++++++ libavfilter/ocio_wrapper.hpp | 5 +++++ libavfilter/vf_opencolorio.c | 22 +++++++++++++++++----- 3 files changed, 51 insertions(+), 5 deletions(-) diff --git a/libavfilter/ocio_wrapper.cpp b/libavfilter/ocio_wrapper.cpp index 82476a7b0b..3dc8cfd3a6 100644 --- a/libavfilter/ocio_wrapper.cpp +++ b/libavfilter/ocio_wrapper.cpp @@ -161,6 +161,35 @@ OCIOHandle ocio_create_display_view_processor(const char *config_path, } } +OCIOHandle ocio_create_file_transform_processor(const char *file_transform, + int inverse) { + try { + OCIOState *s = new OCIOState(); + + if (!file_transform) { + if (!file_transform) std::cerr << "File transform is null" << std::endl; + return nullptr; + } + + // File Transform: InputSpace -> FileTransform -> OutputSpace + OCIO::FileTransformRcPtr ft = OCIO::FileTransform::Create(); + ft->setSrc(file_transform); + OCIO::TransformDirection direction = OCIO::TRANSFORM_DIR_FORWARD; + if (inverse) + direction = OCIO::TRANSFORM_DIR_INVERSE; + s->config = OCIO::Config::Create(); + s->processor = s->config->getProcessor(ft, direction); + + return (OCIOHandle)s; + } catch (OCIO::Exception &e) { + std::cerr << "OCIO Error in create_file_transform_processor: " << e.what() << std::endl; + return nullptr; + } catch (...) { + std::cerr << "Unknown Error in create_file_transform_processor" << std::endl; + return nullptr; + } +} + // In ocio_wrapper.cpp int ocio_finalize_processor(OCIOHandle handle, int input_format, int output_format) { diff --git a/libavfilter/ocio_wrapper.hpp b/libavfilter/ocio_wrapper.hpp index 684144f41d..e6b0a0a545 100644 --- a/libavfilter/ocio_wrapper.hpp +++ b/libavfilter/ocio_wrapper.hpp @@ -24,6 +24,11 @@ ocio_create_output_colorspace_processor(const char *config_path, const char *output_color_space, AVDictionary *params); +// Create an OCIO processor for file transform. +// Returns NULL on failure. +OCIOHandle ocio_create_file_transform_processor(const char *file_transform, + int inverse); + // Finalize OCIO processor for given bit depth. // is_half_float: true for half-float, false for float int ocio_finalize_processor(OCIOHandle handle, int input_format, diff --git a/libavfilter/vf_opencolorio.c b/libavfilter/vf_opencolorio.c index 2893a5ef36..bdb6b69e5e 100644 --- a/libavfilter/vf_opencolorio.c +++ b/libavfilter/vf_opencolorio.c @@ -15,6 +15,7 @@ typedef struct { char *output_space; char *display; char *view; + char *filetransform; int inverse; OCIOHandle ocio; int output_format; @@ -110,16 +111,26 @@ static av_cold int init(AVFilterContext *ctx) { // Default to same as input format (see later). s->output_format = AV_PIX_FMT_NONE; } - av_log(ctx, AV_LOG_INFO, - "Creating OCIO processor with config: %s, input: %s, display: %s, " - "view: %s\n", - s->config_path, s->input_space, s->display, s->view); - if (s->output_space && strlen(s->output_space) > 0) { + + if (s->filetransform && strlen(s->filetransform) > 0) { + s->ocio = ocio_create_file_transform_processor( + s->filetransform, s->inverse); + av_log(ctx, AV_LOG_INFO, + "Creating OCIO processor with FileTransform: %s, Inverse: %d\n", + s->filetransform, s->inverse); + } else if (s->output_space && strlen(s->output_space) > 0) { s->ocio = ocio_create_output_colorspace_processor( s->config_path, s->input_space, s->output_space, s->context_params); + av_log(ctx, AV_LOG_INFO, + "Creating OCIO processor with config: %s, input: %s, output: %s\n", + s->config_path, s->input_space, s->output_space); } else { s->ocio = ocio_create_display_view_processor( s->config_path, s->input_space, s->display, s->view, s->inverse, s->context_params); + av_log(ctx, AV_LOG_INFO, + "Creating OCIO processor with config: %s, input: %s, display: %s, " + "view: %s, Inverse: %d\n", + s->config_path, s->input_space, s->display, s->view, s->inverse); } if (!s->ocio) { av_log(ctx, AV_LOG_ERROR, "Failed to create OCIO processor.\n"); @@ -225,6 +236,7 @@ static const AVOption ocio_options[] = { { "config", "OCIO config path, overriding OCIO environment variable.", OFFSET(config_path), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, FLAGS }, { "input", "Input color space", OFFSET(input_space), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, FLAGS }, { "output", "Output color space", OFFSET(output_space), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, FLAGS }, + { "filetransform", "Specify a File Transform", OFFSET(filetransform), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, FLAGS }, { "display", "Output display, used insted of output color space.", OFFSET(display), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, FLAGS }, { "view", "View, output view transform, used in combination with display.", OFFSET(view), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, FLAGS }, { "inverse", "Invert output display/view transform.", OFFSET(inverse), AV_OPT_TYPE_INT, {.i64=0}, 0, 1, FLAGS }, -- 2.52.0 >From 2b8a302c849e4cc7275a2019ed8550a5e3bb8429 Mon Sep 17 00:00:00 2001 From: "[email protected]" <[email protected]> Date: Thu, 29 Jan 2026 18:58:59 +0000 Subject: [PATCH 15/18] Making sure everything is using av_log rather than std::cerr. Signed-off-by: [email protected] <[email protected]> --- libavfilter/ocio_wrapper.cpp | 65 ++++++++++++++++-------------------- libavfilter/ocio_wrapper.hpp | 15 +++++---- libavfilter/vf_opencolorio.c | 12 +++---- 3 files changed, 44 insertions(+), 48 deletions(-) diff --git a/libavfilter/ocio_wrapper.cpp b/libavfilter/ocio_wrapper.cpp index 3dc8cfd3a6..42b5249618 100644 --- a/libavfilter/ocio_wrapper.cpp +++ b/libavfilter/ocio_wrapper.cpp @@ -2,7 +2,6 @@ #include <OpenColorIO/OpenColorIO.h> #include <exception> -#include <iostream> namespace OCIO = OCIO_NAMESPACE; @@ -70,14 +69,13 @@ static OCIO::ConstContextRcPtr add_context_params(OCIO::ConstConfigRcPtr config, } const AVDictionaryEntry *e = NULL; while ((e = av_dict_iterate(params, e))) { - std::cerr << "Setting OCIO context param " << e->key << " to " << e->value << std::endl; ctx->setStringVar(e->key, e->value); } return ctx; } OCIOHandle -ocio_create_output_colorspace_processor(const char *config_path, +ocio_create_output_colorspace_processor(AVFilterContext *ctx,const char *config_path, const char *input_color_space, const char *output_color_space, AVDictionary *params) { @@ -90,10 +88,10 @@ ocio_create_output_colorspace_processor(const char *config_path, s->config = OCIO::Config::CreateFromFile(config_path); if (!s->config || !input_color_space || !output_color_space) { - std::cerr << "Error: Config or color spaces invalid." << std::endl; - if (!s->config) std::cerr << "Config is null" << std::endl; - if (!input_color_space) std::cerr << "Input color space is null" << std::endl; - if (!output_color_space) std::cerr << "Output color space is null" << std::endl; + av_log(ctx, AV_LOG_ERROR, "Error: Config or color spaces invalid.\n"); + if (!s->config) av_log(ctx, AV_LOG_ERROR, "Config is null\n"); + if (!input_color_space) av_log(ctx, AV_LOG_ERROR, "Input color space is null\n"); + if (!output_color_space) av_log(ctx, AV_LOG_ERROR, "Output color space is null\n"); return nullptr; } @@ -106,15 +104,16 @@ ocio_create_output_colorspace_processor(const char *config_path, return (OCIOHandle)s; } catch (OCIO::Exception &e) { - std::cerr << "OCIO Filter: Error in create_output_colorspace_processor: " << e.what() << std::endl; + av_log(ctx, AV_LOG_ERROR, "OCIO Filter: Error in create_output_colorspace_processor: %s\n", e.what()); return nullptr; } catch (...) { - std::cerr << "OCIO Filter: Unknown Error in create_output_colorspace_processor" << std::endl; + av_log(ctx, AV_LOG_ERROR, "OCIO Filter: Unknown Error in create_output_colorspace_processor\n"); return nullptr; } } -OCIOHandle ocio_create_display_view_processor(const char *config_path, +OCIOHandle ocio_create_display_view_processor(AVFilterContext *ctx, + const char *config_path, const char *input_color_space, const char *display, const char *view, int inverse, @@ -128,11 +127,11 @@ OCIOHandle ocio_create_display_view_processor(const char *config_path, s->config = OCIO::Config::CreateFromFile(config_path); if (!s->config || !input_color_space || !display || !view) { - std::cerr << "Error: Config or arguments invalid." << std::endl; - if (!s->config) std::cerr << "Config is null" << std::endl; - if (!input_color_space) std::cerr << "Input color space is null" << std::endl; - if (!display) std::cerr << "Display is null" << std::endl; - if (!view) std::cerr << "View is null" << std::endl; + av_log(ctx, AV_LOG_ERROR, "Error: Config or arguments invalid.\n"); + if (!s->config) av_log(ctx, AV_LOG_ERROR, "Config is null\n"); + if (!input_color_space) av_log(ctx, AV_LOG_ERROR, "Input color space is null\n"); + if (!display) av_log(ctx, AV_LOG_ERROR, "Display is null\n"); + if (!view) av_log(ctx, AV_LOG_ERROR, "View is null\n"); return nullptr; } @@ -144,30 +143,28 @@ OCIOHandle ocio_create_display_view_processor(const char *config_path, OCIO::TransformDirection direction = OCIO::TRANSFORM_DIR_FORWARD; if (inverse) direction = OCIO::TRANSFORM_DIR_INVERSE; - std::cerr << "Direction is " << direction << std::endl; OCIO::ConstContextRcPtr context = add_context_params(s->config, params); - std::cerr << "Context is " << context << std::endl; s->processor = s->config->getProcessor(context, dv, direction); - //s->processor = s->config->getProcessor(dv); return (OCIOHandle)s; } catch (OCIO::Exception &e) { - std::cerr << "OCIO Error in create_display_view_processor: " << e.what() << std::endl; + av_log(ctx, AV_LOG_ERROR, "OCIO Error in create_display_view_processor: %s\n", e.what()); return nullptr; } catch (...) { - std::cerr << "Unknown Error in create_display_view_processor" << std::endl; + av_log(ctx, AV_LOG_ERROR, "Unknown Error in create_display_view_processor\n"); return nullptr; } } -OCIOHandle ocio_create_file_transform_processor(const char *file_transform, +OCIOHandle ocio_create_file_transform_processor(AVFilterContext *ctx, + const char *file_transform, int inverse) { try { OCIOState *s = new OCIOState(); if (!file_transform) { - if (!file_transform) std::cerr << "File transform is null" << std::endl; + av_log(ctx, AV_LOG_ERROR, "File transform is null\n"); return nullptr; } @@ -182,16 +179,16 @@ OCIOHandle ocio_create_file_transform_processor(const char *file_transform, return (OCIOHandle)s; } catch (OCIO::Exception &e) { - std::cerr << "OCIO Error in create_file_transform_processor: " << e.what() << std::endl; + av_log(ctx, AV_LOG_ERROR, "OCIO Error in create_file_transform_processor: %s\n", e.what()); return nullptr; } catch (...) { - std::cerr << "Unknown Error in create_file_transform_processor" << std::endl; + av_log(ctx, AV_LOG_ERROR, "Unknown Error in create_file_transform_processor\n"); return nullptr; } } // In ocio_wrapper.cpp -int ocio_finalize_processor(OCIOHandle handle, int input_format, +int ocio_finalize_processor(AVFilterContext *ctx, OCIOHandle handle, int input_format, int output_format) { try { @@ -205,10 +202,10 @@ int ocio_finalize_processor(OCIOHandle handle, int input_format, return 0; } catch (OCIO::Exception &e) { - std::cerr << "OCIO error: " << e.what() << std::endl; + av_log(ctx, AV_LOG_ERROR, "OCIO error: %s\n", e.what()); return -1; } catch (...) { - std::cerr << "Unknown error in ocio_finalize_processor" << std::endl; + av_log(ctx, AV_LOG_ERROR, "Unknown error in ocio_finalize_processor\n"); return -1; } } @@ -251,11 +248,7 @@ static OCIO::ImageDesc *AVFrame2ImageDescSlice(AVFrame *frame, int y_start, desc->comp[0].depth / 8, desc->comp[0].step, frame->linesize[0]); } -static OCIO::ImageDesc *AVFrame2ImageDesc(AVFrame *frame) { - return AVFrame2ImageDescSlice(frame, 0, frame->height); -} - -int ocio_apply(OCIOHandle handle, AVFrame *input_frame, AVFrame *output_frame, +int ocio_apply(AVFilterContext *ctx, OCIOHandle handle, AVFrame *input_frame, AVFrame *output_frame, int y_start, int height) { OCIOState *s = (OCIOState *)handle; if (!s || !s->cpu) @@ -278,18 +271,18 @@ int ocio_apply(OCIOHandle handle, AVFrame *input_frame, AVFrame *output_frame, return 0; } catch (const OCIO::Exception &ex) { - std::cerr << "OCIO error: " << ex.what() << std::endl; + av_log(ctx, AV_LOG_ERROR, "OCIO error: %s", ex.what()); return -2; // or another error code } catch (const std::exception &ex) { - std::cerr << "OCIO error: Standard exception: " << ex.what() << std::endl; + av_log(ctx, AV_LOG_ERROR, "OCIO error: Standard exception: %s", ex.what()); return -3; } catch (...) { - std::cerr << "OCIO error: Unknown error in OCIO processing." << std::endl; + av_log(ctx, AV_LOG_ERROR, "OCIO error: Unknown error in OCIO processing."); return -4; } } -void ocio_destroy_processor(OCIOHandle handle) { +void ocio_destroy_processor(AVFilterContext *ctx, OCIOHandle handle) { if (!handle) return; delete (OCIOState *)handle; diff --git a/libavfilter/ocio_wrapper.hpp b/libavfilter/ocio_wrapper.hpp index e6b0a0a545..b9da4a48b5 100644 --- a/libavfilter/ocio_wrapper.hpp +++ b/libavfilter/ocio_wrapper.hpp @@ -10,7 +10,8 @@ typedef void *OCIOHandle; // Create an OCIO processor for Display/View transform. // Returns NULL on failure. -OCIOHandle ocio_create_display_view_processor(const char *config_path, +OCIOHandle ocio_create_display_view_processor(AVFilterContext *ctx, + const char *config_path, const char *input_color_space, const char *display, const char *view, int inverse, @@ -19,19 +20,21 @@ OCIOHandle ocio_create_display_view_processor(const char *config_path, // Create an OCIO processor for output colorspace transform. // Returns NULL on failure. OCIOHandle -ocio_create_output_colorspace_processor(const char *config_path, +ocio_create_output_colorspace_processor(AVFilterContext *ctx, + const char *config_path, const char *input_color_space, const char *output_color_space, AVDictionary *params); // Create an OCIO processor for file transform. // Returns NULL on failure. -OCIOHandle ocio_create_file_transform_processor(const char *file_transform, +OCIOHandle ocio_create_file_transform_processor(AVFilterContext *ctx, + const char *file_transform, int inverse); // Finalize OCIO processor for given bit depth. // is_half_float: true for half-float, false for float -int ocio_finalize_processor(OCIOHandle handle, int input_format, +int ocio_finalize_processor(AVFilterContext *ctx, OCIOHandle handle, int input_format, int output_format); // Apply processor to planar float RGB(A). @@ -39,11 +42,11 @@ int ocio_finalize_processor(OCIOHandle handle, int input_format, // w,h: image dimensions // channels: 3 or 4 // stride_bytes: bytes between row starts (use 0 for tightly packed) -int ocio_apply(OCIOHandle handle, AVFrame *input_frame, AVFrame *output_frame, +int ocio_apply(AVFilterContext *ctx, OCIOHandle handle, AVFrame *input_frame, AVFrame *output_frame, int y_start, int height); // Destroy OCIO processor. -void ocio_destroy_processor(OCIOHandle handle); +void ocio_destroy_processor(AVFilterContext *ctx, OCIOHandle handle); #ifdef __cplusplus } diff --git a/libavfilter/vf_opencolorio.c b/libavfilter/vf_opencolorio.c index bdb6b69e5e..e944fa39c6 100644 --- a/libavfilter/vf_opencolorio.c +++ b/libavfilter/vf_opencolorio.c @@ -40,7 +40,7 @@ static int ocio_filter_slice(AVFilterContext *ctx, void *arg, int jobnr, int nb_ const int slice_end = (height * (jobnr + 1)) / nb_jobs; const int slice_h = slice_end - slice_start; - return ocio_apply(s->ocio, in, out, slice_start, slice_h); + return ocio_apply(ctx, s->ocio, in, out, slice_start, slice_h); } static int query_formats(AVFilterContext *ctx) { @@ -93,7 +93,7 @@ static int config_props(AVFilterLink *inlink) { av_get_pix_fmt_name(s->output_format)); // Now finalize the OCIO processor with the correct bit depth - int ret = ocio_finalize_processor(s->ocio, inlink->format, s->output_format); + int ret = ocio_finalize_processor(ctx, s->ocio, inlink->format, s->output_format); if (ret < 0) { av_log(ctx, AV_LOG_ERROR, "Failed to finalize OCIO processor for bit depth\n"); @@ -114,19 +114,19 @@ static av_cold int init(AVFilterContext *ctx) { if (s->filetransform && strlen(s->filetransform) > 0) { s->ocio = ocio_create_file_transform_processor( - s->filetransform, s->inverse); + ctx, s->filetransform, s->inverse); av_log(ctx, AV_LOG_INFO, "Creating OCIO processor with FileTransform: %s, Inverse: %d\n", s->filetransform, s->inverse); } else if (s->output_space && strlen(s->output_space) > 0) { s->ocio = ocio_create_output_colorspace_processor( - s->config_path, s->input_space, s->output_space, s->context_params); + ctx, s->config_path, s->input_space, s->output_space, s->context_params); av_log(ctx, AV_LOG_INFO, "Creating OCIO processor with config: %s, input: %s, output: %s\n", s->config_path, s->input_space, s->output_space); } else { s->ocio = ocio_create_display_view_processor( - s->config_path, s->input_space, s->display, s->view, s->inverse, s->context_params); + ctx, s->config_path, s->input_space, s->display, s->view, s->inverse, s->context_params); av_log(ctx, AV_LOG_INFO, "Creating OCIO processor with config: %s, input: %s, display: %s, " "view: %s, Inverse: %d\n", @@ -224,7 +224,7 @@ static int filter_frame(AVFilterLink *inlink, AVFrame *frame) { static av_cold void uninit(AVFilterContext *ctx) { OCIOContext *s = ctx->priv; if (s->ocio) { - ocio_destroy_processor(s->ocio); + ocio_destroy_processor(ctx, s->ocio); s->ocio = NULL; } } -- 2.52.0 >From 2ae790f916c3b689098c3cc94d78ef280e83510c Mon Sep 17 00:00:00 2001 From: "[email protected]" <[email protected]> Date: Fri, 30 Jan 2026 15:49:13 +0000 Subject: [PATCH 16/18] Updating the tests so they would work without additional files. Signed-off-by: [email protected] <[email protected]> --- tests/fate/filter-video.mak | 3 +-- tests/ref/fate/filter-ocio | 6 ++++++ tests/ref/fate/filter-ocio-alt | 7 ------- 3 files changed, 7 insertions(+), 9 deletions(-) delete mode 100644 tests/ref/fate/filter-ocio-alt diff --git a/tests/fate/filter-video.mak b/tests/fate/filter-video.mak index 498fe22d2d..465589ce40 100644 --- a/tests/fate/filter-video.mak +++ b/tests/fate/filter-video.mak @@ -475,8 +475,7 @@ $(FATE_FILTER_ALPHAEXTRACT_ALPHAMERGE): fate-filter-alphaextract_alphamerge_%: t $(FATE_FILTER_ALPHAEXTRACT_ALPHAMERGE): CMD = framecrc -auto_conversion_filters -c:v pgmyuv -i $(SRC) -/filter_complex $(TARGET_PATH)/tests/data/filtergraphs/alphamerge_alphaextract$(@:fate-filter-alphaextract_alphamerge%=%) FATE_FILTER-$(CONFIG_OCIO_FILTER) += fate-filter-ocio fate-filter-ocio-alt -fate-filter-ocio: CMD = framecrc -i $(TARGET_SAMPLES)/exr/rgba_slice_piz.exr -vf "ocio=config=/Users/sam/git/FFmpeg/studio-config-v1.0.0_aces-v1.3_ocio-v2.1_ns.ocio:input=ACEScg:display=sRGB\ -\ Display:view=ACES\ 1.0\ -\ SDR\ Video" -fate-filter-ocio-alt: CMD = framecrc -i $(TARGET_SAMPLES)/exr/rgba_slice_piz.exr -vf ocio=config=/Users/sam/git/FFmpeg/simpleconfig.ocio:input=ACEScg:display=sRGB:view=ACES_1.0_SDR +fate-filter-ocio: CMD = OCIO=ocio\://studio-config-v1.0.0_aces-v1.3_ocio-v2.1 framecrc -i $(TARGET_SAMPLES)/exr/rgba_slice_piz.exr -vf "ocio=input=ACEScg:output=ACEScct" FATE_FILTER_VSYNTH_VIDEO_FILTER-$(CONFIG_CROP_FILTER) += fate-filter-crop fate-filter-crop: CMD = video_filter "crop=iw-100:ih-100:100:100" diff --git a/tests/ref/fate/filter-ocio b/tests/ref/fate/filter-ocio index e69de29bb2..1b114c1b2e 100644 --- a/tests/ref/fate/filter-ocio +++ b/tests/ref/fate/filter-ocio @@ -0,0 +1,6 @@ +#tb 0: 1/25 +#media_type 0: video +#codec_id 0: rawvideo +#dimensions 0: 587x675 +#sar 0: 1/1 +0, 0, 0, 1, 6339600, 0x69b3c23f diff --git a/tests/ref/fate/filter-ocio-alt b/tests/ref/fate/filter-ocio-alt deleted file mode 100644 index 6ff3c65629..0000000000 --- a/tests/ref/fate/filter-ocio-alt +++ /dev/null @@ -1,7 +0,0 @@ -ffmpeg -i /Users/sam/git/fate-suite/exr/rgba_slice_piz.exr -vf ocio=config=/Users/sam/git/FFmpeg/simpleconfig.ocio:input=ACEScg:display=sRGB:view=ACES_1.0_SDR -bitexact -f framecrc - -#tb 0: 1/25 -#media_type 0: video -#codec_id 0: rawvideo -#dimensions 0: 587x675 -#sar 0: 1/1 -0, 0, 0, 1, 6339600, 0x275bc9bf -- 2.52.0 >From 7e26a0468aac06843b9a145d4c71517efa96c405 Mon Sep 17 00:00:00 2001 From: "[email protected]" <[email protected]> Date: Fri, 30 Jan 2026 15:52:05 +0000 Subject: [PATCH 17/18] Adding the file-transform documentation. Signed-off-by: [email protected] <[email protected]> --- doc/filters.texi | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/filters.texi b/doc/filters.texi index fae35b437a..b8df71caa4 100644 --- a/doc/filters.texi +++ b/doc/filters.texi @@ -18759,6 +18759,9 @@ Set the view colorspace, used in combination with display. @item inverse When used in combination with display and view, this inverts the transform, so going from a display/view to the "input colorspace". +@item filetransform +Allows you to specify an external file-transform to use instead of the OCIO config file. This is useful for applying a single transform without needing a full OCIO config file. + @item format Allow you to specify the output pix_fmt of the OCIO filter. This *has* to be a RGB colorspace, so you really are limited to rgb24, rgba, rgb48, rgba48, gbrp10, gbrp12, gbrpf32le, gbrapf32le, for most encoding we would recommend rgb48 -- 2.52.0 >From 0c35f0ce6a6edd416a99a53028486a88147bc942 Mon Sep 17 00:00:00 2001 From: "[email protected]" <[email protected]> Date: Fri, 30 Jan 2026 16:03:08 +0000 Subject: [PATCH 18/18] Adding copyright/license info. Signed-off-by: [email protected] <[email protected]> --- libavfilter/ocio_wrapper.cpp | 21 ++++++++++++++++++++- libavfilter/ocio_wrapper.hpp | 21 ++++++++++++++++++++- libavfilter/vf_opencolorio.c | 21 ++++++++++++++++++++- 3 files changed, 60 insertions(+), 3 deletions(-) diff --git a/libavfilter/ocio_wrapper.cpp b/libavfilter/ocio_wrapper.cpp index 42b5249618..c594bc786b 100644 --- a/libavfilter/ocio_wrapper.cpp +++ b/libavfilter/ocio_wrapper.cpp @@ -1,4 +1,23 @@ -// libavfilter/ocio_wrapper.cpp +/* + * Copyright (c) 2026 Sam Richards + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + #include <OpenColorIO/OpenColorIO.h> #include <exception> diff --git a/libavfilter/ocio_wrapper.hpp b/libavfilter/ocio_wrapper.hpp index b9da4a48b5..900e70dc72 100644 --- a/libavfilter/ocio_wrapper.hpp +++ b/libavfilter/ocio_wrapper.hpp @@ -1,4 +1,23 @@ -// libavfilter/ocio_wrapper.hpp +/* + * Copyright (c) 2026 Sam Richards + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + #pragma once #include <stdint.h> diff --git a/libavfilter/vf_opencolorio.c b/libavfilter/vf_opencolorio.c index e944fa39c6..695d603d66 100644 --- a/libavfilter/vf_opencolorio.c +++ b/libavfilter/vf_opencolorio.c @@ -1,4 +1,23 @@ -// libavfilter/vf_opencolorio.c +/* + * Copyright (c) 2026 Sam Richards + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + #include "avfilter.h" #include "formats.h" #include "libavutil/half2float.h" -- 2.52.0 _______________________________________________ ffmpeg-devel mailing list -- [email protected] To unsubscribe send an email to [email protected]
