Initial checkin of OCIO filter.

Initial checkin of OCIO filter.

Initial checkin of OCIO filter.

Signed-off-by: Sam.Richards@taurich.org <Sam.Richards@taurich.org>

Change for the right C++ library, should work on linux too.

Signed-off-by: Sam.Richards@taurich.org <Sam.Richards@taurich.org>

Adding inverse when using display/view.

Removed comments.

Removed code that was setting the CICP values. Hopefully this can be done through OCIO at some point.

Config cleanup - need a modified require_cpp to handle namespacing.

Switch to using require_cpp so that namespace can be used.

Adding documentation.

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: Sam.Richards@taurich.org <Sam.Richards@taurich.org>

Adding context parameters.

Signed-off-by: Sam.Richards@taurich.org <Sam.Richards@taurich.org>

Add the OCIO config parameter.

Signed-off-by: Sam.Richards@taurich.org <Sam.Richards@taurich.org>

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: Sam.Richards@taurich.org <Sam.Richards@taurich.org>

This is using ffmpeg-slicing.

Signed-off-by: Sam.Richards@taurich.org <Sam.Richards@taurich.org>

Adding OCIO filetransform.

Making sure everything is using av_log rather than std::cerr.

Signed-off-by: Sam.Richards@taurich.org <Sam.Richards@taurich.org>

Updating the tests so they would work without additional files.

Signed-off-by: Sam.Richards@taurich.org <Sam.Richards@taurich.org>

Adding the file-transform documentation.

Signed-off-by: Sam.Richards@taurich.org <Sam.Richards@taurich.org>

Adding copyright/license info.

Signed-off-by: Sam.Richards@taurich.org <Sam.Richards@taurich.org>

Removing tests, since this is optional code.

Signed-off-by: Sam.Richards@taurich.org <Sam.Richards@taurich.org>

Code cleanup.

Signed-off-by: Sam.Richards@taurich.org <Sam.Richards@taurich.org>

Typo.

Signed-off-by: Sam.Richards@taurich.org <Sam.Richards@taurich.org>

I went the wrong way, av_log is expecting \n

Signed-off-by: Sam.Richards@taurich.org <Sam.Richards@taurich.org>

Fix indenting to 4 spaces.

Signed-off-by: Sam.Richards@taurich.org <Sam.Richards@taurich.org>

Fixing lint issues and a spelling mistake.

Signed-off-by: Sam.Richards@taurich.org <Sam.Richards@taurich.org>

Code formatting cleanup to match conventions.

Signed-off-by: Sam.Richards@taurich.org <Sam.Richards@taurich.org>

Whitespace removal.

Signed-off-by: Sam.Richards@taurich.org <Sam.Richards@taurich.org>
This commit is contained in:
Sam.Richards@taurich.org 2026-01-07 20:50:17 +00:00 committed by Lynne
parent a7522f3fef
commit 677cf95ea4
7 changed files with 770 additions and 0 deletions

View file

@ -416,6 +416,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 \

View file

@ -391,6 +391,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;

View file

@ -0,0 +1,316 @@
/*
* 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>
namespace OCIO = OCIO_NAMESPACE;
struct OCIOState {
OCIO::ConstConfigRcPtr config;
OCIO::ConstProcessorRcPtr processor;
OCIO::ConstCPUProcessorRcPtr cpu;
int channels;
};
extern "C" {
#include "formats.h"
#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)
{
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;
}
}
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))) {
ctx->setStringVar(e->key, e->value);
}
return ctx;
}
OCIOHandle
ocio_create_output_colorspace_processor(AVFilterContext *ctx, const char *config_path,
const char *input_color_space,
const char *output_color_space,
AVDictionary *params)
{
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) {
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");
delete s;
return nullptr;
}
// ColorSpace Transform: InputSpace -> OutputSpace
OCIO::ColorSpaceTransformRcPtr cst = OCIO::ColorSpaceTransform::Create();
cst->setSrc(input_color_space);
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) {
av_log(ctx, AV_LOG_ERROR, "OCIO Filter: Error in create_output_colorspace_processor: %s\n", e.what());
return nullptr;
} catch (...) {
av_log(ctx, AV_LOG_ERROR, "OCIO Filter: Unknown Error in create_output_colorspace_processor\n");
return nullptr;
}
}
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,
AVDictionary *params)
{
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) {
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");
delete s;
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)
direction = OCIO::TRANSFORM_DIR_INVERSE;
OCIO::ConstContextRcPtr context = add_context_params(s->config, params);
s->processor = s->config->getProcessor(context, dv, direction);
return (OCIOHandle)s;
} catch (OCIO::Exception &e) {
av_log(ctx, AV_LOG_ERROR, "OCIO Error in create_display_view_processor: %s\n", e.what());
return nullptr;
} catch (...) {
av_log(ctx, AV_LOG_ERROR, "Unknown Error in create_display_view_processor\n");
return nullptr;
}
}
OCIOHandle ocio_create_file_transform_processor(AVFilterContext *ctx,
const char *file_transform,
int inverse)
{
try {
if (!file_transform) {
av_log(ctx, AV_LOG_ERROR, "File transform is null\n");
return nullptr;
}
OCIOState *s = new OCIOState();
// 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) {
av_log(ctx, AV_LOG_ERROR, "OCIO Error in create_file_transform_processor: %s\n", e.what());
return nullptr;
} catch (...) {
av_log(ctx, AV_LOG_ERROR, "Unknown Error in create_file_transform_processor\n");
return nullptr;
}
}
// In ocio_wrapper.cpp
int ocio_finalize_processor(AVFilterContext *ctx, 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) {
av_log(ctx, AV_LOG_ERROR, "OCIO error: %s\n", e.what());
return -1;
} catch (...) {
av_log(ctx, AV_LOG_ERROR, "Unknown error in ocio_finalize_processor\n");
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 channel 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]);
}
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)
return -1;
try {
if (input_frame == output_frame) {
OCIO::ImageDesc *imgDesc = AVFrame2ImageDescSlice(input_frame, y_start, height);
s->cpu->apply(*imgDesc);
delete imgDesc;
return 0;
}
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;
} catch (const OCIO::Exception &ex) {
av_log(ctx, AV_LOG_ERROR, "OCIO error: %s\n", ex.what());
return -2; // or another error code
} catch (const std::exception &ex) {
av_log(ctx, AV_LOG_ERROR, "OCIO error: Standard exception: %s\n", ex.what());
return -3;
} catch (...) {
av_log(ctx, AV_LOG_ERROR, "OCIO error: Unknown error in OCIO processing.\n");
return -4;
}
}
void ocio_destroy_processor(AVFilterContext *ctx, OCIOHandle handle)
{
if (!handle)
return;
delete (OCIOState *)handle;
}
} // extern "C"

View file

@ -0,0 +1,72 @@
/*
* 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>
#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(AVFilterContext *ctx,
const char *config_path,
const char *input_color_space,
const char *display,
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(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(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(AVFilterContext *ctx, 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(AVFilterContext *ctx, OCIOHandle handle, AVFrame *input_frame, AVFrame *output_frame,
int y_start, int height);
// Destroy OCIO processor.
void ocio_destroy_processor(AVFilterContext *ctx, OCIOHandle handle);
#ifdef __cplusplus
}
#endif

View file

@ -0,0 +1,289 @@
/*
* 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"
#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;
char *filetransform;
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
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(ctx, s->ocio, in, out, slice_start, slice_h);
}
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(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");
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
s->output_format = AV_PIX_FMT_NONE; // Default to same as input format (see later).
if (s->filetransform && strlen(s->filetransform) > 0) {
s->ocio = ocio_create_file_transform_processor(
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(
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(
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",
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;
}
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 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;
} 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;
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);
}
} 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;
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);
}
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);
}
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(ctx, 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 },
{ "filetransform", "Specify a File Transform", OFFSET(filetransform), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, FLAGS },
{ "display", "Output display, used instead 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 },
{ "context_params", "OCIO context parameters", OFFSET(context_params), AV_OPT_TYPE_DICT, { .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,
.p.flags = AVFILTER_FLAG_SLICE_THREADS,
.priv_size = sizeof(OCIOContext),
.init = init,
.uninit = uninit,
FILTER_INPUTS(inputs),
FILTER_OUTPUTS(outputs),
FILTER_QUERY_FUNC(query_formats)};