refactor(node): de-Qt oaknode and wrap it in a pure C ABI

- copy engine/node (188 files) to src/node/src, de-Qt in waves:
  core infra (Node/Param/Value/Variant/mathtypes), project/serializer,
  block/output, color, effect leaves, generator, gizmo/plugins
- strip QObject/signals/slots: notifications move to the facade's
  oakengine_event channel, ownership becomes explicit (unique_ptr,
  add_keyframe/add_gizmo), sender() replaced by current_gizmo
- QVariant replaced by olive::Variant, Qt math types by POD mathtypes,
  QXmlStreamReader/Writer by oakcommon's expat-based classes
- sink VideoParams/SubtitleParams/LoopMode/ColorTransform to oakcommon
  (M3.5); polygon/text rasterization behind backend hooks
- pure C ABI in include/node + src/node/c_api (oaknode_ prefix,
  OAKNODE_E_* codes, undoable variants take OakUndoCommand out-params)
- fix Project::clear() root_ reset + disconnect assert, Sequence
  TrackList leak
- 96 gtest cases green in standalone build (build-oaknode)
- docs: signal/slot handling strategy + M3 implementation status
This commit is contained in:
2026-08-05 23:55:54 +08:00
parent c50017127b
commit d77348ad9f
375 changed files with 59682 additions and 1 deletions
+3
View File
@@ -1,6 +1,9 @@
target_sources(oakcommon PRIVATE
colortransform.cpp
commandlineparser.cpp
current.cpp
subtitleparams.cpp
videoparams.cpp
debug.cpp
dropworkflowbehavior.cpp
ffmpegutils.cpp
+125
View File
@@ -0,0 +1,125 @@
/***
Oak Video Editor - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include "common/colortransform.h"
#include <cstring>
#include "../src/colortransform.h"
struct OakCommonColorTransform {
olive::ColorTransform impl;
};
static int copy_string(const std::string &value, char *buf, int buf_size)
{
int needed = (int)value.size() + 1;
if (buf && buf_size >= needed)
memcpy(buf, value.c_str(), needed);
return needed;
}
OakCommonColorTransform *oakcommon_colortransform_init_output(
const char *output)
{
if (!output)
return nullptr;
try {
return new OakCommonColorTransform{
olive::ColorTransform(std::string(output))};
} catch (...) {
return nullptr;
}
}
OakCommonColorTransform *oakcommon_colortransform_init_display(
const char *display, const char *view, const char *look)
{
if (!display || !view || !look)
return nullptr;
try {
return new OakCommonColorTransform{olive::ColorTransform(
std::string(display), std::string(view), std::string(look))};
} catch (...) {
return nullptr;
}
}
void oakcommon_colortransform_free(OakCommonColorTransform *transform)
{
delete transform;
}
int oakcommon_colortransform_is_display(OakCommonColorTransform *transform,
int *is_display)
{
if (!transform || !is_display)
return OAKCOMMON_E_INVALID;
*is_display = transform->impl.is_display() ? 1 : 0;
return OAKCOMMON_OK;
}
int oakcommon_colortransform_get_display(OakCommonColorTransform *transform,
char *buf, int buf_size)
{
if (!transform)
return OAKCOMMON_E_INVALID;
try {
return copy_string(transform->impl.display(), buf, buf_size);
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
int oakcommon_colortransform_get_output(OakCommonColorTransform *transform,
char *buf, int buf_size)
{
if (!transform)
return OAKCOMMON_E_INVALID;
try {
return copy_string(transform->impl.output(), buf, buf_size);
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
int oakcommon_colortransform_get_view(OakCommonColorTransform *transform,
char *buf, int buf_size)
{
if (!transform)
return OAKCOMMON_E_INVALID;
try {
return copy_string(transform->impl.view(), buf, buf_size);
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
int oakcommon_colortransform_get_look(OakCommonColorTransform *transform,
char *buf, int buf_size)
{
if (!transform)
return OAKCOMMON_E_INVALID;
try {
return copy_string(transform->impl.look(), buf, buf_size);
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
+223
View File
@@ -0,0 +1,223 @@
/***
Oak Video Editor - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include "common/subtitleparams.h"
#include <cstring>
#include "../src/subtitleparams.h"
struct OakCommonSubtitleParams {
olive::SubtitleParams impl;
};
namespace
{
int copy_string(const std::string &value, char *buf, int buf_size)
{
int needed = (int)value.size() + 1;
if (buf && buf_size >= needed)
memcpy(buf, value.c_str(), needed);
return needed;
}
} // namespace
OakCommonSubtitleParams *oakcommon_subtitleparams_init(void)
{
try {
return new OakCommonSubtitleParams{olive::SubtitleParams()};
} catch (...) {
return nullptr;
}
}
void oakcommon_subtitleparams_free(OakCommonSubtitleParams *params)
{
delete params;
}
int oakcommon_subtitleparams_get_stream_index(
OakCommonSubtitleParams *params, int *index)
{
if (!params || !index)
return OAKCOMMON_E_INVALID;
*index = params->impl.stream_index();
return OAKCOMMON_OK;
}
int oakcommon_subtitleparams_set_stream_index(
OakCommonSubtitleParams *params, int index)
{
if (!params)
return OAKCOMMON_E_INVALID;
params->impl.set_stream_index(index);
return OAKCOMMON_OK;
}
int oakcommon_subtitleparams_get_enabled(OakCommonSubtitleParams *params,
int *enabled)
{
if (!params || !enabled)
return OAKCOMMON_E_INVALID;
*enabled = params->impl.enabled() ? 1 : 0;
return OAKCOMMON_OK;
}
int oakcommon_subtitleparams_set_enabled(OakCommonSubtitleParams *params,
int enabled)
{
if (!params)
return OAKCOMMON_E_INVALID;
params->impl.set_enabled(enabled != 0);
return OAKCOMMON_OK;
}
int oakcommon_subtitleparams_is_valid(OakCommonSubtitleParams *params,
int *is_valid)
{
if (!params || !is_valid)
return OAKCOMMON_E_INVALID;
*is_valid = params->impl.is_valid() ? 1 : 0;
return OAKCOMMON_OK;
}
int oakcommon_subtitleparams_count(OakCommonSubtitleParams *params, int *count)
{
if (!params || !count)
return OAKCOMMON_E_INVALID;
*count = (int)params->impl.size();
return OAKCOMMON_OK;
}
int oakcommon_subtitleparams_duration(OakCommonSubtitleParams *params,
int *numerator, int *denominator)
{
if (!params || !numerator || !denominator)
return OAKCOMMON_E_INVALID;
olive::core::Rational d = params->impl.duration();
*numerator = d.numerator();
*denominator = d.denominator();
return OAKCOMMON_OK;
}
int oakcommon_subtitleparams_add_subtitle(OakCommonSubtitleParams *params,
int in_num, int in_den, int out_num,
int out_den, const char *text)
{
if (!params || !text)
return OAKCOMMON_E_INVALID;
try {
params->impl.push_back(olive::Subtitle(
olive::core::TimeRange(olive::core::Rational(in_num, in_den),
olive::core::Rational(out_num, out_den)),
text));
return OAKCOMMON_OK;
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
int oakcommon_subtitleparams_clear(OakCommonSubtitleParams *params)
{
if (!params)
return OAKCOMMON_E_INVALID;
params->impl.clear();
return OAKCOMMON_OK;
}
int oakcommon_subtitleparams_get_subtitle(OakCommonSubtitleParams *params,
int index, int *in_num, int *in_den,
int *out_num, int *out_den)
{
if (!params || !in_num || !in_den || !out_num || !out_den)
return OAKCOMMON_E_INVALID;
if (index < 0 || index >= (int)params->impl.size())
return OAKCOMMON_E_NOT_FOUND;
const olive::Subtitle &s = params->impl.at(index);
olive::core::Rational in = s.time().in();
olive::core::Rational out = s.time().out();
*in_num = in.numerator();
*in_den = in.denominator();
*out_num = out.numerator();
*out_den = out.denominator();
return OAKCOMMON_OK;
}
int oakcommon_subtitleparams_get_subtitle_text(OakCommonSubtitleParams *params,
int index, char *buf,
int buf_size)
{
if (!params)
return OAKCOMMON_E_INVALID;
if (index < 0 || index >= (int)params->impl.size())
return OAKCOMMON_E_NOT_FOUND;
try {
return copy_string(params->impl.at(index).text(), buf, buf_size);
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
int oakcommon_subtitleparams_generate_ass_header(char *buf, int buf_size)
{
try {
return copy_string(olive::SubtitleParams::generate_ass_header(), buf,
buf_size);
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
int oakcommon_subtitleparams_load_xml(OakCommonSubtitleParams *params,
const char *xml)
{
if (!params || !xml)
return OAKCOMMON_E_INVALID;
try {
olive::XmlStreamReader reader(xml);
if (reader.has_error())
return OAKCOMMON_E_FAILED;
// Position on the root element; load() consumes its children.
if (!olive::xml_read_next_start_element(&reader))
return OAKCOMMON_E_FAILED;
params->impl.load(&reader);
return OAKCOMMON_OK;
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
int oakcommon_subtitleparams_save_xml(OakCommonSubtitleParams *params,
char *buf, int buf_size)
{
if (!params)
return OAKCOMMON_E_INVALID;
try {
olive::XmlStreamWriter writer;
writer.write_start_element("subtitleparams");
params->impl.save(&writer);
writer.write_end_element();
return copy_string(writer.output(), buf, buf_size);
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
+478
View File
@@ -0,0 +1,478 @@
/***
Oak Video Editor - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include "common/videoparams.h"
#include <cstring>
#include "../src/videoparams.h"
struct OakCommonVideoParams {
olive::VideoParams impl;
};
namespace
{
int copy_string(const std::string &value, char *buf, int buf_size)
{
int needed = (int)value.size() + 1;
if (buf && buf_size >= needed)
memcpy(buf, value.c_str(), needed);
return needed;
}
int get_rational(const olive::core::Rational &r, int *numerator,
int *denominator)
{
if (!numerator || !denominator)
return OAKCOMMON_E_INVALID;
*numerator = r.numerator();
*denominator = r.denominator();
return OAKCOMMON_OK;
}
} // namespace
OakCommonVideoParams *oakcommon_videoparams_init(void)
{
try {
return new OakCommonVideoParams{olive::VideoParams()};
} catch (...) {
return nullptr;
}
}
OakCommonVideoParams *oakcommon_videoparams_init_basic(
int width, int height, int pixel_format, int nb_channels,
int pixel_aspect_num, int pixel_aspect_den, int interlacing, int divider)
{
try {
return new OakCommonVideoParams{olive::VideoParams(
width, height,
static_cast<olive::core::PixelFormat::Format>(pixel_format),
nb_channels,
olive::core::Rational(pixel_aspect_num, pixel_aspect_den),
static_cast<olive::VideoParams::Interlacing>(interlacing),
divider)};
} catch (...) {
return nullptr;
}
}
OakCommonVideoParams *oakcommon_videoparams_init_with_time_base(
int width, int height, int time_base_num, int time_base_den,
int pixel_format, int nb_channels, int pixel_aspect_num,
int pixel_aspect_den, int interlacing, int divider)
{
try {
return new OakCommonVideoParams{olive::VideoParams(
width, height,
olive::core::Rational(time_base_num, time_base_den),
static_cast<olive::core::PixelFormat::Format>(pixel_format),
nb_channels,
olive::core::Rational(pixel_aspect_num, pixel_aspect_den),
static_cast<olive::VideoParams::Interlacing>(interlacing),
divider)};
} catch (...) {
return nullptr;
}
}
void oakcommon_videoparams_free(OakCommonVideoParams *params)
{
delete params;
}
#define OAKCOMMON_VIDEOPARAMS_INT_GETTER(name, expr) \
int oakcommon_videoparams_get_##name(OakCommonVideoParams *params, \
int *out) \
{ \
if (!params || !out) \
return OAKCOMMON_E_INVALID; \
*out = (expr); \
return OAKCOMMON_OK; \
}
#define OAKCOMMON_VIDEOPARAMS_INT_SETTER(name, stmt) \
int oakcommon_videoparams_set_##name(OakCommonVideoParams *params, \
int value) \
{ \
if (!params) \
return OAKCOMMON_E_INVALID; \
stmt; \
return OAKCOMMON_OK; \
}
OAKCOMMON_VIDEOPARAMS_INT_GETTER(width, params->impl.width())
OAKCOMMON_VIDEOPARAMS_INT_SETTER(width, params->impl.set_width(value))
OAKCOMMON_VIDEOPARAMS_INT_GETTER(height, params->impl.height())
OAKCOMMON_VIDEOPARAMS_INT_SETTER(height, params->impl.set_height(value))
OAKCOMMON_VIDEOPARAMS_INT_GETTER(depth, params->impl.depth())
OAKCOMMON_VIDEOPARAMS_INT_SETTER(depth, params->impl.set_depth(value))
OAKCOMMON_VIDEOPARAMS_INT_GETTER(is_3d, params->impl.is_3d() ? 1 : 0)
OAKCOMMON_VIDEOPARAMS_INT_GETTER(format, static_cast<int>(params->impl.format()))
OAKCOMMON_VIDEOPARAMS_INT_SETTER(
format, params->impl.set_format(
static_cast<olive::core::PixelFormat::Format>(value)))
OAKCOMMON_VIDEOPARAMS_INT_GETTER(channel_count, params->impl.channel_count())
OAKCOMMON_VIDEOPARAMS_INT_SETTER(channel_count,
params->impl.set_channel_count(value))
OAKCOMMON_VIDEOPARAMS_INT_GETTER(interlacing,
static_cast<int>(params->impl.interlacing()))
OAKCOMMON_VIDEOPARAMS_INT_SETTER(
interlacing,
params->impl.set_interlacing(
static_cast<olive::VideoParams::Interlacing>(value)))
OAKCOMMON_VIDEOPARAMS_INT_GETTER(divider, params->impl.divider())
OAKCOMMON_VIDEOPARAMS_INT_SETTER(divider, params->impl.set_divider(value))
OAKCOMMON_VIDEOPARAMS_INT_GETTER(enabled, params->impl.enabled() ? 1 : 0)
OAKCOMMON_VIDEOPARAMS_INT_SETTER(enabled, params->impl.set_enabled(value != 0))
OAKCOMMON_VIDEOPARAMS_INT_GETTER(stream_index, params->impl.stream_index())
OAKCOMMON_VIDEOPARAMS_INT_SETTER(stream_index,
params->impl.set_stream_index(value))
OAKCOMMON_VIDEOPARAMS_INT_GETTER(video_type,
static_cast<int>(params->impl.video_type()))
OAKCOMMON_VIDEOPARAMS_INT_SETTER(
video_type,
params->impl.set_video_type(static_cast<olive::VideoParams::Type>(value)))
OAKCOMMON_VIDEOPARAMS_INT_GETTER(premultiplied_alpha,
params->impl.premultiplied_alpha() ? 1 : 0)
OAKCOMMON_VIDEOPARAMS_INT_SETTER(premultiplied_alpha,
params->impl.set_premultiplied_alpha(
value != 0))
OAKCOMMON_VIDEOPARAMS_INT_GETTER(color_range,
static_cast<int>(params->impl.color_range()))
OAKCOMMON_VIDEOPARAMS_INT_SETTER(
color_range, params->impl.set_color_range(
static_cast<olive::VideoParams::ColorRange>(value)))
OAKCOMMON_VIDEOPARAMS_INT_GETTER(color_primaries,
params->impl.color_primaries())
OAKCOMMON_VIDEOPARAMS_INT_SETTER(color_primaries,
params->impl.set_color_primaries(value))
OAKCOMMON_VIDEOPARAMS_INT_GETTER(color_transfer, params->impl.color_transfer())
OAKCOMMON_VIDEOPARAMS_INT_SETTER(color_transfer,
params->impl.set_color_transfer(value))
OAKCOMMON_VIDEOPARAMS_INT_GETTER(square_pixel_width,
params->impl.square_pixel_width())
OAKCOMMON_VIDEOPARAMS_INT_GETTER(effective_width,
params->impl.effective_width())
OAKCOMMON_VIDEOPARAMS_INT_GETTER(effective_height,
params->impl.effective_height())
OAKCOMMON_VIDEOPARAMS_INT_GETTER(effective_depth,
params->impl.effective_depth())
OAKCOMMON_VIDEOPARAMS_INT_GETTER(is_valid, params->impl.is_valid() ? 1 : 0)
OAKCOMMON_VIDEOPARAMS_INT_GETTER(bytes_per_channel,
params->impl.get_bytes_per_channel())
OAKCOMMON_VIDEOPARAMS_INT_GETTER(bytes_per_pixel,
params->impl.get_bytes_per_pixel())
OAKCOMMON_VIDEOPARAMS_INT_GETTER(buffer_size, params->impl.get_buffer_size())
int oakcommon_videoparams_get_x(OakCommonVideoParams *params, float *x)
{
if (!params || !x)
return OAKCOMMON_E_INVALID;
*x = params->impl.x();
return OAKCOMMON_OK;
}
int oakcommon_videoparams_set_x(OakCommonVideoParams *params, float x)
{
if (!params)
return OAKCOMMON_E_INVALID;
params->impl.set_x(x);
return OAKCOMMON_OK;
}
int oakcommon_videoparams_get_y(OakCommonVideoParams *params, float *y)
{
if (!params || !y)
return OAKCOMMON_E_INVALID;
*y = params->impl.y();
return OAKCOMMON_OK;
}
int oakcommon_videoparams_set_y(OakCommonVideoParams *params, float y)
{
if (!params)
return OAKCOMMON_E_INVALID;
params->impl.set_y(y);
return OAKCOMMON_OK;
}
int oakcommon_videoparams_get_start_time(OakCommonVideoParams *params,
int64_t *start_time)
{
if (!params || !start_time)
return OAKCOMMON_E_INVALID;
*start_time = params->impl.start_time();
return OAKCOMMON_OK;
}
int oakcommon_videoparams_set_start_time(OakCommonVideoParams *params,
int64_t start_time)
{
if (!params)
return OAKCOMMON_E_INVALID;
params->impl.set_start_time(start_time);
return OAKCOMMON_OK;
}
int oakcommon_videoparams_get_duration(OakCommonVideoParams *params,
int64_t *duration)
{
if (!params || !duration)
return OAKCOMMON_E_INVALID;
*duration = params->impl.duration();
return OAKCOMMON_OK;
}
int oakcommon_videoparams_set_duration(OakCommonVideoParams *params,
int64_t duration)
{
if (!params)
return OAKCOMMON_E_INVALID;
params->impl.set_duration(duration);
return OAKCOMMON_OK;
}
int oakcommon_videoparams_get_time_base(OakCommonVideoParams *params,
int *numerator, int *denominator)
{
if (!params)
return OAKCOMMON_E_INVALID;
return get_rational(params->impl.time_base(), numerator, denominator);
}
int oakcommon_videoparams_set_time_base(OakCommonVideoParams *params,
int numerator, int denominator)
{
if (!params)
return OAKCOMMON_E_INVALID;
params->impl.set_time_base(olive::core::Rational(numerator, denominator));
return OAKCOMMON_OK;
}
int oakcommon_videoparams_get_frame_rate(OakCommonVideoParams *params,
int *numerator, int *denominator)
{
if (!params)
return OAKCOMMON_E_INVALID;
return get_rational(params->impl.frame_rate(), numerator, denominator);
}
int oakcommon_videoparams_set_frame_rate(OakCommonVideoParams *params,
int numerator, int denominator)
{
if (!params)
return OAKCOMMON_E_INVALID;
params->impl.set_frame_rate(olive::core::Rational(numerator, denominator));
return OAKCOMMON_OK;
}
int oakcommon_videoparams_frame_rate_as_time_base(OakCommonVideoParams *params,
int *numerator,
int *denominator)
{
if (!params)
return OAKCOMMON_E_INVALID;
return get_rational(params->impl.frame_rate_as_time_base(), numerator,
denominator);
}
int oakcommon_videoparams_get_pixel_aspect_ratio(OakCommonVideoParams *params,
int *numerator,
int *denominator)
{
if (!params)
return OAKCOMMON_E_INVALID;
return get_rational(params->impl.pixel_aspect_ratio(), numerator,
denominator);
}
int oakcommon_videoparams_set_pixel_aspect_ratio(OakCommonVideoParams *params,
int numerator, int denominator)
{
if (!params)
return OAKCOMMON_E_INVALID;
params->impl.set_pixel_aspect_ratio(
olive::core::Rational(numerator, denominator));
return OAKCOMMON_OK;
}
int oakcommon_videoparams_get_colorspace(OakCommonVideoParams *params,
char *buf, int buf_size)
{
if (!params)
return OAKCOMMON_E_INVALID;
try {
return copy_string(params->impl.colorspace(), buf, buf_size);
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
int oakcommon_videoparams_set_colorspace(OakCommonVideoParams *params,
const char *colorspace)
{
if (!params || !colorspace)
return OAKCOMMON_E_INVALID;
params->impl.set_colorspace(colorspace);
return OAKCOMMON_OK;
}
int oakcommon_videoparams_get_time_in_timebase_units(
OakCommonVideoParams *params, int time_num, int time_den,
int64_t *timestamp)
{
if (!params || !timestamp)
return OAKCOMMON_E_INVALID;
*timestamp = params->impl.get_time_in_timebase_units(
olive::core::Rational(time_num, time_den));
return OAKCOMMON_OK;
}
int oakcommon_videoparams_equals(OakCommonVideoParams *params,
OakCommonVideoParams *other, int *equal)
{
if (!params || !other || !equal)
return OAKCOMMON_E_INVALID;
*equal = (params->impl == other->impl) ? 1 : 0;
return OAKCOMMON_OK;
}
int oakcommon_videoparams_load_xml(OakCommonVideoParams *params,
const char *xml)
{
if (!params || !xml)
return OAKCOMMON_E_INVALID;
try {
olive::XmlStreamReader reader(xml);
if (reader.has_error())
return OAKCOMMON_E_FAILED;
// Position on the root element; load() consumes its children.
if (!olive::xml_read_next_start_element(&reader))
return OAKCOMMON_E_FAILED;
params->impl.load(&reader);
return OAKCOMMON_OK;
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
int oakcommon_videoparams_save_xml(OakCommonVideoParams *params, char *buf,
int buf_size)
{
if (!params)
return OAKCOMMON_E_INVALID;
try {
olive::XmlStreamWriter writer;
writer.write_start_element("videoparams");
params->impl.save(&writer);
writer.write_end_element();
return copy_string(writer.output(), buf, buf_size);
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
int oakcommon_videoparams_get_bytes_per_channel_for_format(int pixel_format)
{
return olive::VideoParams::get_bytes_per_channel(
static_cast<olive::core::PixelFormat::Format>(pixel_format));
}
int oakcommon_videoparams_get_bytes_per_pixel_for_format(int pixel_format,
int channels)
{
return olive::VideoParams::get_bytes_per_pixel(
static_cast<olive::core::PixelFormat::Format>(pixel_format), channels);
}
int oakcommon_videoparams_calculate_buffer_size(int width, int height,
int pixel_format, int channels)
{
return olive::VideoParams::get_buffer_size(
width, height,
static_cast<olive::core::PixelFormat::Format>(pixel_format), channels);
}
int oakcommon_videoparams_format_is_float(int pixel_format)
{
return olive::VideoParams::format_is_float(
static_cast<olive::core::PixelFormat::Format>(pixel_format)) ?
1 :
0;
}
int oakcommon_videoparams_generate_auto_divider(int64_t width, int64_t height)
{
return olive::VideoParams::generate_auto_divider(width, height);
}
int oakcommon_videoparams_get_scaled_dimension(int dimension, int divider)
{
return olive::VideoParams::get_scaled_dimension(dimension, divider);
}
int oakcommon_videoparams_get_divider_for_target_resolution(int src_width,
int src_height,
int dst_width,
int dst_height)
{
return olive::VideoParams::get_divider_for_target_resolution(
src_width, src_height, dst_width, dst_height);
}
int oakcommon_videoparams_get_name_for_divider(int divider, char *buf,
int buf_size)
{
try {
return copy_string(olive::VideoParams::get_name_for_divider(divider),
buf, buf_size);
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
int oakcommon_videoparams_get_format_name(int pixel_format, char *buf,
int buf_size)
{
try {
return copy_string(olive::VideoParams::get_format_name(
static_cast<olive::core::PixelFormat::Format>(
pixel_format)),
buf, buf_size);
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
int oakcommon_videoparams_frame_rate_to_string(int numerator, int denominator,
char *buf, int buf_size)
{
try {
return copy_string(olive::VideoParams::frame_rate_to_string(
olive::core::Rational(numerator, denominator)),
buf, buf_size);
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
+6
View File
@@ -18,6 +18,7 @@
add_library(oakcommon SHARED
commandlineparser.cpp
commandlineparser.h
colortransform.h
current.cpp
current.h
debug.cpp
@@ -25,6 +26,11 @@ add_library(oakcommon SHARED
decibel.h
define.h
dropworkflowbehavior.h
loopmode.h
subtitleparams.cpp
subtitleparams.h
videoparams.cpp
videoparams.h
ffmpegutils.cpp
ffmpegutils.h
filefunctions.cpp
+91
View File
@@ -0,0 +1,91 @@
/***
Oak Video Editor - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#ifndef OAK_COLORTRANSFORM_H
#define OAK_COLORTRANSFORM_H
#include <string>
namespace olive
{
/**
* @brief Describes a color transform: either a plain output colorspace or a
* display/view/look triplet (sunk from engine/render/colortransform.h,
* QString replaced with std::string).
*/
class ColorTransform {
public:
ColorTransform()
{
is_display_ = false;
}
ColorTransform(const std::string &output)
{
is_display_ = false;
output_ = output;
}
ColorTransform(const std::string &display, const std::string &view,
const std::string &look)
{
is_display_ = true;
output_ = display;
view_ = view;
look_ = look;
}
bool is_display() const
{
return is_display_;
}
const std::string &display() const
{
return output_;
}
const std::string &output() const
{
return output_;
}
const std::string &view() const
{
return view_;
}
const std::string &look() const
{
return look_;
}
private:
std::string output_;
bool is_display_;
std::string view_;
std::string look_;
};
}
#endif // OAK_COLORTRANSFORM_H
+34
View File
@@ -0,0 +1,34 @@
/***
Oak Video Editor - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#ifndef OAK_LOOPMODE_H
#define OAK_LOOPMODE_H
namespace olive
{
/**
* @brief Playback loop mode (sunk from engine/render/loopmode.h).
*/
enum class LoopMode { k_loop_mode_off, k_loop_mode_loop, k_loop_mode_clamp };
}
#endif // OAK_LOOPMODE_H
+172
View File
@@ -0,0 +1,172 @@
/***
Oak Video Editor - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include "subtitleparams.h"
#include <cstdio>
namespace olive
{
std::string SubtitleParams::generate_ass_header()
{
// NOTE: We'll probably implement more customization as we support ASS better. Right now, we only
// natively support SRT and only make this header because FFmpeg requires it.
static const int k_ass_default_play_res_x = 384;
static const int k_ass_default_play_res_y = 288;
static const char *k_ass_default_font = "Arial";
static const int k_ass_default_font_size = 16;
static const int k_ass_default_primary_color = 0xFFFFFF; // White
static const int k_ass_default_secondary_color = 0xFFFFFF; // White
static const int k_ass_default_outline_color = 0x000000; // Black
static const int k_ass_default_back_color = 0x000000; // Black
static const int k_ass_bold = 0;
static const int k_ass_italic = 0;
static const int k_ass_underline = 0;
static const int k_ass_strike = 0;
static const int k_ass_border_style = 1;
static const int k_ass_alignment = 2;
std::string ass_code;
char buf[256];
// Header info
// De-Qt note: the original used QCoreApplication::applicationName()/
// applicationVersion(); oakcommon has no application object, so a fixed
// generator name is emitted instead.
ass_code.append("[Script Info]\r\n");
ass_code.append("; Script generated by Oak\r\n");
ass_code.append("ScriptType: v4.00+\r\n");
std::snprintf(buf, sizeof(buf), "PlayResX: %d\r\n", k_ass_default_play_res_x);
ass_code.append(buf);
std::snprintf(buf, sizeof(buf), "PlayResY: %d\r\n", k_ass_default_play_res_y);
ass_code.append(buf);
ass_code.append("ScaledBorderAndShadow: yes\r\n");
ass_code.append("\r\n");
// ASSv4 header
ass_code.append("[V4+ Styles]\r\n");
ass_code.append("Format: Name, ");
ass_code.append("Fontname, Fontsize, ");
ass_code.append("PrimaryColour, SecondaryColour, OutlineColour, BackColour, ");
ass_code.append("Bold, Italic, Underline, StrikeOut, ");
ass_code.append("ScaleX, ScaleY, ");
ass_code.append("Spacing, Angle, ");
ass_code.append("BorderStyle, Outline, Shadow, ");
ass_code.append("Alignment, MarginL, MarginR, MarginV, ");
ass_code.append("Encoding\r\n");
ass_code.append("Style: ");
// Name
ass_code.append("Default,");
// Font{name,size}
std::snprintf(buf, sizeof(buf), "%s,%d,", k_ass_default_font,
k_ass_default_font_size);
ass_code.append(buf);
// {Primary,Secondary,Outline,Back}Colour
std::snprintf(buf, sizeof(buf), "&H%X,&H%X,&H%X,&H%X,",
k_ass_default_primary_color, k_ass_default_secondary_color,
k_ass_default_outline_color, k_ass_default_back_color);
ass_code.append(buf);
// Bold, Italic, Underline, StrikeOut
std::snprintf(buf, sizeof(buf), "%d,%d,%d,%d,", k_ass_bold, k_ass_italic,
k_ass_underline, k_ass_strike);
ass_code.append(buf);
// Scale{X,Y}
ass_code.append("100,100,");
// Spacing, Angle
ass_code.append("0,0,");
// BorderStyle, Outline, Shadow
std::snprintf(buf, sizeof(buf), "%d,1,0,", k_ass_border_style);
ass_code.append(buf);
// Alignment, Margin[LRV]
std::snprintf(buf, sizeof(buf), "%d,10,10,10,", k_ass_alignment);
ass_code.append(buf);
// Encoding
ass_code.append("0\r\n");
ass_code.append("\r\n");
ass_code.append("[Events]\r\n");
ass_code.append(
"Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text\r\n");
return ass_code;
}
void SubtitleParams::load(XmlStreamReader *reader)
{
this->clear();
while (xml_read_next_start_element(reader)) {
const std::string &name = reader->name();
if (name == "streamindex") {
set_stream_index(std::stoi(reader->read_element_text()));
} else if (name == "enabled") {
set_enabled(std::stoi(reader->read_element_text()));
} else if (name == "subtitles") {
while (xml_read_next_start_element(reader)) {
if (reader->name() == "subtitle") {
Rational in, out;
for (const XmlStreamAttribute &attr : reader->attributes()) {
if (attr.name == "in") {
in = Rational::from_string(attr.value);
} else if (attr.name == "out") {
out = Rational::from_string(attr.value);
}
}
std::string text = reader->read_element_text();
this->push_back(Subtitle(TimeRange(in, out), text));
} else {
reader->skip_current_element();
}
}
} else {
reader->skip_current_element();
}
}
}
void SubtitleParams::save(XmlStreamWriter *writer) const
{
writer->write_text_element("streamindex", std::to_string(stream_index_));
writer->write_text_element("enabled", std::to_string(enabled_));
writer->write_start_element("subtitles");
for (auto it = this->cbegin(); it != this->cend(); it++) {
writer->write_start_element("subtitle");
writer->write_attribute("in", it->time().in().to_string());
writer->write_attribute("out", it->time().out().to_string());
writer->write_characters(it->text());
writer->write_end_element(); // subtitle
}
writer->write_end_element(); // subtitles
}
}
+142
View File
@@ -0,0 +1,142 @@
/***
Oak Video Editor - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#ifndef OAK_SUBTITLEPARAMS_H
#define OAK_SUBTITLEPARAMS_H
#include <string>
#include <vector>
#include <olive/core/util/timerange.h>
#include "xmlutils.h"
using namespace olive::core;
namespace olive
{
/**
* @brief A single subtitle entry (sunk from engine/render/subtitleparams.h,
* QString replaced with std::string).
*/
class Subtitle {
public:
Subtitle() = default;
Subtitle(const TimeRange &time, const std::string &text)
: range_(time)
, text_(text)
{
}
const TimeRange &time() const
{
return range_;
}
void set_time(const TimeRange &t)
{
range_ = t;
}
const std::string &text() const
{
return text_;
}
void set_text(const std::string &t)
{
text_ = t;
}
// Value equality (time range + text). Required by olive::Variant's
// custom-type operator== (SubtitleParams is stored as a Variant value).
bool operator==(const Subtitle &rhs) const
{
return range_ == rhs.range_ && text_ == rhs.text_;
}
bool operator!=(const Subtitle &rhs) const
{
return !(*this == rhs);
}
private:
TimeRange range_;
std::string text_;
};
/**
* @brief Subtitle track parameter set (sunk from engine/render/subtitleparams.h).
*/
class SubtitleParams : public std::vector<Subtitle> {
public:
SubtitleParams()
{
stream_index_ = 0;
enabled_ = true;
}
static std::string generate_ass_header();
void load(XmlStreamReader *reader);
void save(XmlStreamWriter *writer) const;
bool is_valid() const
{
return !this->empty();
}
Rational duration() const
{
if (this->empty()) {
return 0;
} else {
return back().time().out();
}
}
int stream_index() const
{
return stream_index_;
}
void set_stream_index(int i)
{
stream_index_ = i;
}
bool enabled() const
{
return enabled_;
}
void set_enabled(bool e)
{
enabled_ = e;
}
private:
int stream_index_;
bool enabled_;
};
}
#endif // OAK_SUBTITLEPARAMS_H
+466
View File
@@ -0,0 +1,466 @@
/***
Oak Video Editor - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include "videoparams.h"
#include <cmath>
#include <cstdio>
#include <olive/core/util/timecodefunctions.h>
#include "ofxImageEffect.h"
namespace olive
{
const int VideoParams::k_internal_channel_count = k_rgba_channel_count;
const Rational VideoParams::k_pixel_aspect_square(1);
const Rational VideoParams::k_pixel_aspect_ntsc_standard(8, 9);
const Rational VideoParams::k_pixel_aspect_ntsc_widescreen(32, 27);
const Rational VideoParams::k_pixel_aspect_pal_standard(16, 15);
const Rational VideoParams::k_pixel_aspect_pal_widescreen(64, 45);
const Rational VideoParams::k_pixel_aspect1080_anamorphic(4, 3);
const std::vector<Rational> VideoParams::k_supported_frame_rates = {
Rational(10, 1), // 10 FPS
Rational(15, 1), // 15 FPS
Rational(24000, 1001), // 23.976 FPS
Rational(24, 1), // 24 FPS
Rational(25, 1), // 25 FPS
Rational(30000, 1001), // 29.97 FPS
Rational(30, 1), // 30 FPS
Rational(48000, 1001), // 47.952 FPS
Rational(48, 1), // 48 FPS
Rational(50, 1), // 50 FPS
Rational(60000, 1001), // 59.94 FPS
Rational(60, 1) // 60 FPS
};
const std::vector<int> VideoParams::k_supported_dividers = {
1, 2, 3, 4, 6, 8, 12, 16
};
const std::vector<Rational> VideoParams::k_standard_pixel_aspects = {
VideoParams::k_pixel_aspect_square,
VideoParams::k_pixel_aspect_ntsc_standard,
VideoParams::k_pixel_aspect_ntsc_widescreen,
VideoParams::k_pixel_aspect_pal_standard,
VideoParams::k_pixel_aspect_pal_widescreen,
VideoParams::k_pixel_aspect1080_anamorphic
};
VideoParams::VideoParams()
: width_(0)
, height_(0)
, depth_(0)
, time_base_(0)
, format_(PixelFormat::invalid)
, channel_count_(0)
, pixel_aspect_ratio_(1)
, interlacing_(Interlacing::k_interlace_none)
, divider_(1)
{
calculate_effective_size();
validate_pixel_aspect_ratio();
set_defaults_for_footage();
}
VideoParams::VideoParams(int width, int height, PixelFormat format,
int nb_channels, const Rational &pixel_aspect_ratio,
Interlacing interlacing, int divider)
: width_(width)
, height_(height)
, depth_(1)
, format_(format)
, channel_count_(nb_channels)
, pixel_aspect_ratio_(pixel_aspect_ratio)
, interlacing_(interlacing)
, divider_(divider)
{
calculate_effective_size();
validate_pixel_aspect_ratio();
set_defaults_for_footage();
}
VideoParams::VideoParams(int width, int height, int depth, PixelFormat format,
int nb_channels, const Rational &pixel_aspect_ratio,
VideoParams::Interlacing interlacing, int divider)
: width_(width)
, height_(height)
, depth_(depth)
, format_(format)
, channel_count_(nb_channels)
, pixel_aspect_ratio_(pixel_aspect_ratio)
, interlacing_(interlacing)
, divider_(divider)
{
calculate_effective_size();
validate_pixel_aspect_ratio();
set_defaults_for_footage();
}
void VideoParams::set_channel_count(const std::string &ofx_component)
{
if (ofx_component == kOfxImageComponentAlpha) {
channel_count_ = 1;
} else if (ofx_component == kOfxImageComponentRGB) {
channel_count_ = k_rgb_channel_count;
} else if (ofx_component == kOfxImageComponentRGBA) {
channel_count_ = k_rgba_channel_count;
}
}
VideoParams::VideoParams(int width, int height, const Rational &time_base,
PixelFormat format, int nb_channels,
const Rational &pixel_aspect_ratio,
Interlacing interlacing, int divider)
: width_(width)
, height_(height)
, depth_(1)
, time_base_(time_base)
, format_(format)
, channel_count_(nb_channels)
, pixel_aspect_ratio_(pixel_aspect_ratio)
, interlacing_(interlacing)
, divider_(divider)
, frame_rate_(time_base.flipped())
{
calculate_effective_size();
validate_pixel_aspect_ratio();
set_defaults_for_footage();
}
int VideoParams::generate_auto_divider(int64_t width, int64_t height)
{
const int target_res = 1280 * 720;
int64_t megapixels = width * height;
double squared_divider = double(megapixels) / double(target_res);
double divider = std::sqrt(squared_divider);
if (divider <= k_supported_dividers.front()) {
return k_supported_dividers.front();
} else if (divider >= k_supported_dividers.back()) {
return k_supported_dividers.back();
} else {
for (size_t i = 1; i < k_supported_dividers.size(); i++) {
int prev_divider = k_supported_dividers.at(i - 1);
int next_divider = k_supported_dividers.at(i);
if (divider >= prev_divider && divider <= next_divider) {
double prev_diff = std::fabs(prev_divider - divider);
double next_diff = std::fabs(next_divider - divider);
if (prev_diff < next_diff) {
return prev_divider;
} else {
return next_divider;
}
}
}
// Fallback
return 1;
}
}
bool VideoParams::operator==(const VideoParams &rhs) const
{
return width() == rhs.width() && height() == rhs.height() &&
depth() == rhs.depth() && interlacing() == rhs.interlacing() &&
time_base() == rhs.time_base() && format() == rhs.format() &&
pixel_aspect_ratio() == rhs.pixel_aspect_ratio() &&
divider() == rhs.divider() && channel_count() == rhs.channel_count();
}
bool VideoParams::operator!=(const VideoParams &rhs) const
{
return !(*this == rhs);
}
int VideoParams::get_bytes_per_channel(PixelFormat format)
{
switch (format) {
case PixelFormat::invalid:
case PixelFormat::count:
break;
case PixelFormat::u8:
return 1;
case PixelFormat::u10:
return 0; // packed format, use get_bytes_per_pixel instead
case PixelFormat::u16:
case PixelFormat::f16:
return 2;
case PixelFormat::f32:
return 4;
}
return 0;
}
int VideoParams::get_bytes_per_pixel(PixelFormat format, int channels)
{
if (format == PixelFormat::u10) {
// Packed 10-bit RGBA10A2: 4 bytes per RGBA pixel regardless of channel count
return channels == VideoParams::k_rgba_channel_count ? 4 : 0;
}
return get_bytes_per_channel(format) * channels;
}
std::string VideoParams::get_name_for_divider(int div)
{
if (div == 1) {
return std::string("Full");
} else {
return std::string("1/") + std::to_string(div);
}
}
std::string VideoParams::get_format_name(PixelFormat format)
{
switch (format) {
case PixelFormat::u8:
return "8-bit";
case PixelFormat::u10:
return "10-bit Packed";
case PixelFormat::u16:
return "16-bit Integer";
case PixelFormat::f16:
return "Half-Float (16-bit)";
case PixelFormat::f32:
return "Full-Float (32-bit)";
case PixelFormat::invalid:
case PixelFormat::count:
break;
}
char buf[64];
std::snprintf(buf, sizeof(buf), "Unknown (0x%X)",
static_cast<int>(format));
return buf;
}
int VideoParams::get_divider_for_target_resolution(int src_width, int src_height,
int dst_width, int dst_height)
{
int divider = 0;
int test_width, test_height;
do {
divider++;
test_width = VideoParams::get_scaled_dimension(src_width, divider);
test_height = VideoParams::get_scaled_dimension(src_height, divider);
} while (test_width > dst_width || test_height > dst_height);
return divider;
}
void VideoParams::calculate_effective_size()
{
effective_width_ = get_scaled_dimension(width(), divider_);
effective_height_ = get_scaled_dimension(height(), divider_);
effective_depth_ = (depth() == 1) ? depth() :
get_scaled_dimension(depth(), divider_);
calculate_square_pixel_width();
}
void VideoParams::validate_pixel_aspect_ratio()
{
if (pixel_aspect_ratio_.isNull()) {
pixel_aspect_ratio_ = 1;
}
calculate_square_pixel_width();
}
void VideoParams::set_defaults_for_footage()
{
enabled_ = true;
stream_index_ = 0;
video_type_ = k_video_type_video;
start_time_ = 0;
duration_ = 0;
premultiplied_alpha_ = false;
x_ = 0;
y_ = 0;
color_range_ = k_color_range_default;
}
void VideoParams::calculate_square_pixel_width()
{
if (pixel_aspect_ratio_.denominator() != 0) {
par_width_ = int(std::lround(width_ * pixel_aspect_ratio_.to_double()));
} else {
par_width_ = width_;
}
}
bool VideoParams::is_valid() const
{
return (width() > 0 && height() > 0 && !pixel_aspect_ratio_.isNull() &&
format_ > PixelFormat::invalid && format_ < PixelFormat::count &&
channel_count_ > 0);
}
std::string VideoParams::frame_rate_to_string(const Rational &frame_rate)
{
char buf[64];
std::snprintf(buf, sizeof(buf), "%g FPS", frame_rate.to_double());
return buf;
}
std::vector<std::string> VideoParams::get_standard_pixel_aspect_ratio_names()
{
std::vector<std::string> strings = {
"Square Pixels (%1)",
"NTSC Standard (%1)",
"NTSC Widescreen (%1)",
"PAL Standard (%1)",
"PAL Widescreen (%1)",
"HD Anamorphic 1080 (%1)"
};
// Format each
for (size_t i = 0; i < strings.size(); i++) {
strings.at(i) = format_pixel_aspect_ratio_string(
strings.at(i), k_standard_pixel_aspects.at(i));
}
return strings;
}
std::string VideoParams::format_pixel_aspect_ratio_string(
const std::string &format, const Rational &ratio)
{
char number[64];
std::snprintf(number, sizeof(number), "%.4f", ratio.to_double());
std::string result = format;
std::string::size_type pos = result.find("%1");
if (pos != std::string::npos) {
result.replace(pos, 2, number);
}
return result;
}
int VideoParams::get_scaled_dimension(int dim, int divider)
{
return dim / divider;
}
int64_t VideoParams::get_time_in_timebase_units(const Rational &time) const
{
if (time_base_.isNull()) {
return INT64_MIN; // AV_NOPTS_VALUE
}
return Timecode::time_to_timestamp(time, time_base_) + start_time_;
}
void VideoParams::load(XmlStreamReader *reader)
{
while (xml_read_next_start_element(reader)) {
const std::string &name = reader->name();
if (name == "width") {
set_width(std::stoi(reader->read_element_text()));
} else if (name == "height") {
set_height(std::stoi(reader->read_element_text()));
} else if (name == "depth") {
set_depth(std::stoi(reader->read_element_text()));
} else if (name == "timebase") {
set_time_base(Rational::from_string(reader->read_element_text()));
} else if (name == "format") {
set_format(static_cast<PixelFormat::Format>(
std::stoi(reader->read_element_text())));
} else if (name == "channelcount") {
set_channel_count(std::stoi(reader->read_element_text()));
} else if (name == "pixelaspectratio") {
set_pixel_aspect_ratio(
Rational::from_string(reader->read_element_text()));
} else if (name == "interlacing") {
set_interlacing(static_cast<VideoParams::Interlacing>(
std::stoi(reader->read_element_text())));
} else if (name == "divider") {
set_divider(std::stoi(reader->read_element_text()));
} else if (name == "enabled") {
set_enabled(std::stoi(reader->read_element_text()));
} else if (name == "x") {
set_x(std::stof(reader->read_element_text()));
} else if (name == "y") {
set_y(std::stof(reader->read_element_text()));
} else if (name == "streamindex") {
set_stream_index(std::stoi(reader->read_element_text()));
} else if (name == "videotype") {
set_video_type(static_cast<VideoParams::Type>(
std::stoi(reader->read_element_text())));
} else if (name == "framerate") {
set_frame_rate(Rational::from_string(reader->read_element_text()));
} else if (name == "starttime") {
set_start_time(std::stoll(reader->read_element_text()));
} else if (name == "duration") {
set_duration(std::stoll(reader->read_element_text()));
} else if (name == "premultipliedalpha") {
set_premultiplied_alpha(std::stoi(reader->read_element_text()));
} else if (name == "colorspace") {
set_colorspace(reader->read_element_text());
} else if (name == "colorrange") {
set_color_range(
static_cast<ColorRange>(std::stoi(reader->read_element_text())));
} else if (name == "colorprimaries") {
set_color_primaries(std::stoi(reader->read_element_text()));
} else if (name == "colortransfer") {
set_color_transfer(std::stoi(reader->read_element_text()));
} else {
reader->skip_current_element();
}
}
}
void VideoParams::save(XmlStreamWriter *writer) const
{
writer->write_text_element("width", std::to_string(width_));
writer->write_text_element("height", std::to_string(height_));
writer->write_text_element("depth", std::to_string(depth_));
writer->write_text_element("timebase", time_base_.to_string());
writer->write_text_element("format", std::to_string(format_));
writer->write_text_element("channelcount", std::to_string(channel_count_));
writer->write_text_element("pixelaspectratio",
pixel_aspect_ratio_.to_string());
writer->write_text_element("interlacing", std::to_string(interlacing_));
writer->write_text_element("divider", std::to_string(divider_));
writer->write_text_element("enabled", std::to_string(enabled_));
writer->write_text_element("x", std::to_string(x_));
writer->write_text_element("y", std::to_string(y_));
writer->write_text_element("streamindex", std::to_string(stream_index_));
writer->write_text_element("videotype", std::to_string(video_type_));
writer->write_text_element("framerate", frame_rate_.to_string());
writer->write_text_element("starttime", std::to_string(start_time_));
writer->write_text_element("duration", std::to_string(duration_));
writer->write_text_element("premultipliedalpha",
std::to_string(premultiplied_alpha_));
writer->write_text_element("colorspace", colorspace_);
writer->write_text_element("colorrange", std::to_string(color_range_));
writer->write_text_element("colorprimaries", std::to_string(color_primaries_));
writer->write_text_element("colortransfer", std::to_string(color_transfer_));
}
}
+458
View File
@@ -0,0 +1,458 @@
/***
Oak Video Editor - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#ifndef OAK_VIDEOPARAMS_H
#define OAK_VIDEOPARAMS_H
#include <cstdint>
#include <string>
#include <vector>
#include <olive/core/render/pixelformat.h>
#include <olive/core/util/rational.h>
#include "xmlutils.h"
namespace olive
{
using namespace core;
/**
* @brief Pure-data video parameter set (sunk from engine/render/videoparams.h).
*
* De-Qt port: QString -> std::string, QVector -> std::vector,
* QXmlStreamReader/Writer -> olive::XmlStreamReader/Writer. The QVector2D
* convenience getters (resolution/square_resolution/offset) were dropped;
* use width()/height()/x()/y() directly. Translated UI strings are returned
* as plain English text.
*/
class VideoParams {
public:
enum Interlacing {
k_interlace_none,
k_interlaced_top_first,
k_interlaced_bottom_first
};
enum Type { k_video_type_video, k_video_type_still, k_video_type_image_sequence };
enum ColorRange {
k_color_range_limited, // 16-235
k_color_range_full, // 0-255
k_color_range_default = k_color_range_limited
};
VideoParams();
VideoParams(int width, int height, PixelFormat format, int nb_channels,
const Rational &pixel_aspect_ratio = 1,
Interlacing interlacing = k_interlace_none, int divider = 1);
VideoParams(int width, int height, int depth, PixelFormat format,
int nb_channels, const Rational &pixel_aspect_ratio = 1,
Interlacing interlacing = k_interlace_none, int divider = 1);
VideoParams(int width, int height, const Rational &time_base,
PixelFormat format, int nb_channels,
const Rational &pixel_aspect_ratio = 1,
Interlacing interlacing = k_interlace_none, int divider = 1);
int width() const
{
return width_;
}
void set_width(int width)
{
width_ = width;
calculate_effective_size();
}
/**
* @brief Returns width multiplied by pixel aspect ratio where applicable
*/
int square_pixel_width() const
{
return par_width_;
}
int height() const
{
return height_;
}
void set_height(int height)
{
height_ = height;
calculate_effective_size();
}
int depth() const
{
return depth_;
}
void set_depth(int depth)
{
depth_ = depth;
calculate_effective_size();
}
bool is_3d() const
{
return depth_ > 1;
}
const Rational &time_base() const
{
return time_base_;
}
void set_time_base(const Rational &r)
{
time_base_ = r;
}
Rational frame_rate_as_time_base() const
{
return frame_rate_.flipped();
}
int divider() const
{
return divider_;
}
void set_divider(int d)
{
divider_ = d;
calculate_effective_size();
}
int effective_width() const
{
return effective_width_;
}
int effective_height() const
{
return effective_height_;
}
int effective_depth() const
{
return effective_depth_;
}
PixelFormat format() const
{
return format_;
}
void set_format(PixelFormat f)
{
format_ = f;
}
int channel_count() const
{
return channel_count_;
}
void set_channel_count(int c)
{
channel_count_ = c;
}
void set_channel_count(const std::string &ofx_component);
const Rational &pixel_aspect_ratio() const
{
return pixel_aspect_ratio_;
}
void set_pixel_aspect_ratio(const Rational &r)
{
pixel_aspect_ratio_ = r;
validate_pixel_aspect_ratio();
}
Interlacing interlacing() const
{
return interlacing_;
}
void set_interlacing(Interlacing i)
{
interlacing_ = i;
}
static int generate_auto_divider(int64_t width, int64_t height);
bool is_valid() const;
bool operator==(const VideoParams &rhs) const;
bool operator!=(const VideoParams &rhs) const;
static int get_bytes_per_channel(PixelFormat format);
int get_bytes_per_channel() const
{
return get_bytes_per_channel(format_);
}
static int get_bytes_per_pixel(PixelFormat format, int channels);
int get_bytes_per_pixel() const
{
return get_bytes_per_pixel(format_, channel_count_);
}
static int get_buffer_size(int width, int height, PixelFormat format,
int channels)
{
return width * height * get_bytes_per_pixel(format, channels);
}
int get_buffer_size() const
{
return get_buffer_size(width_, height_, format_, channel_count_);
}
static std::string get_name_for_divider(int div);
static bool format_is_float(PixelFormat format)
{
return format.is_float();
}
static std::string get_format_name(PixelFormat format);
static int get_divider_for_target_resolution(int src_width, int src_height,
int dst_width, int dst_height);
static const int k_internal_channel_count;
static const Rational k_pixel_aspect_square;
static const Rational k_pixel_aspect_ntsc_standard;
static const Rational k_pixel_aspect_ntsc_widescreen;
static const Rational k_pixel_aspect_pal_standard;
static const Rational k_pixel_aspect_pal_widescreen;
static const Rational k_pixel_aspect1080_anamorphic;
static const std::vector<Rational> k_supported_frame_rates;
static const std::vector<Rational> k_standard_pixel_aspects;
static const std::vector<int> k_supported_dividers;
static const int k_hsv_channel_count = 3;
static const int k_rgb_channel_count = 3;
static const int k_rgba_channel_count = 4;
/**
* @brief Convert Rational frame rate (i.e. flipped timebase) to a user-friendly string
*/
static std::string frame_rate_to_string(const Rational &frame_rate);
static std::vector<std::string> get_standard_pixel_aspect_ratio_names();
static std::string format_pixel_aspect_ratio_string(const std::string &format,
const Rational &ratio);
static int get_scaled_dimension(int dim, int divider);
bool enabled() const
{
return enabled_;
}
void set_enabled(bool e)
{
enabled_ = e;
}
float x() const
{
return x_;
}
void set_x(float x)
{
x_ = x;
}
float y() const
{
return y_;
}
void set_y(float y)
{
y_ = y;
}
int stream_index() const
{
return stream_index_;
}
void set_stream_index(int s)
{
stream_index_ = s;
}
Type video_type() const
{
return video_type_;
}
void set_video_type(Type t)
{
video_type_ = t;
}
const Rational &frame_rate() const
{
return frame_rate_;
}
void set_frame_rate(const Rational &frame_rate)
{
frame_rate_ = frame_rate;
}
int64_t start_time() const
{
return start_time_;
}
void set_start_time(int64_t start_time)
{
start_time_ = start_time;
}
int64_t duration() const
{
return duration_;
}
void set_duration(int64_t duration)
{
duration_ = duration;
}
bool premultiplied_alpha() const
{
return premultiplied_alpha_;
}
void set_premultiplied_alpha(bool premultiplied_alpha)
{
premultiplied_alpha_ = premultiplied_alpha;
}
const std::string &colorspace() const
{
return colorspace_;
}
void set_colorspace(const std::string &c)
{
colorspace_ = c;
}
const ColorRange &color_range() const
{
return color_range_;
}
void set_color_range(const ColorRange &color_range)
{
color_range_ = color_range;
}
/**
* @brief Color primaries/transfer as reported by the media
*
* Raw FFmpeg AVColorPrimaries/AVColorTransferCharacteristic values
* (0 = unset, 2 = unspecified). Used to auto-detect the input
* colorspace when no explicit colorspace has been set.
*/
int color_primaries() const
{
return color_primaries_;
}
void set_color_primaries(int p)
{
color_primaries_ = p;
}
int color_transfer() const
{
return color_transfer_;
}
void set_color_transfer(int t)
{
color_transfer_ = t;
}
int64_t get_time_in_timebase_units(const Rational &time) const;
void load(XmlStreamReader *reader);
void save(XmlStreamWriter *writer) const;
private:
void calculate_effective_size();
void validate_pixel_aspect_ratio();
void set_defaults_for_footage();
void calculate_square_pixel_width();
int width_;
int height_;
int depth_;
Rational time_base_;
PixelFormat format_;
int channel_count_;
Rational pixel_aspect_ratio_;
Interlacing interlacing_;
int divider_;
// Cached values
int effective_width_;
int effective_height_;
int effective_depth_;
int par_width_;
bool enabled_;
int stream_index_;
Type video_type_;
Rational frame_rate_;
int64_t start_time_;
int64_t duration_;
bool premultiplied_alpha_;
std::string colorspace_;
float x_;
float y_;
ColorRange color_range_;
int color_primaries_ = 0;
int color_transfer_ = 0;
};
}
#endif // OAK_VIDEOPARAMS_H
+3
View File
@@ -19,6 +19,7 @@ find_package(GTest REQUIRED)
include(GoogleTest)
add_executable(oakcommon-gtest
colortransform_test.cpp
commandlineparser_test.cpp
current_test.cpp
debug_test.cpp
@@ -31,7 +32,9 @@ add_executable(oakcommon-gtest
oiioutils_test.cpp
power_test.cpp
qtutils_test.cpp
subtitleparams_test.cpp
threadsafemap_test.cpp
videoparams_test.cpp
xmlutils_test.cpp
)
+121
View File
@@ -0,0 +1,121 @@
/***
Oak Video Editor - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include <gtest/gtest.h>
#include <cstring>
#include <string>
#include <vector>
#include "common/colortransform.h"
namespace
{
std::string read_string(int (*fn)(OakCommonColorTransform *, char *, int),
OakCommonColorTransform *t)
{
int needed = fn(t, nullptr, 0);
EXPECT_GT(needed, 0);
std::vector<char> buf(needed);
EXPECT_EQ(fn(t, buf.data(), needed), needed);
return std::string(buf.data());
}
} // namespace
TEST(CommonColorTransformCApi, InitOutput)
{
OakCommonColorTransform *t =
oakcommon_colortransform_init_output("sRGB");
ASSERT_NE(t, nullptr);
int is_display = -1;
EXPECT_EQ(oakcommon_colortransform_is_display(t, &is_display),
OAKCOMMON_OK);
EXPECT_EQ(is_display, 0);
EXPECT_EQ(read_string(oakcommon_colortransform_get_output, t), "sRGB");
EXPECT_EQ(read_string(oakcommon_colortransform_get_display, t), "sRGB");
oakcommon_colortransform_free(t);
}
TEST(CommonColorTransformCApi, InitOutputNullString)
{
EXPECT_EQ(oakcommon_colortransform_init_output(nullptr), nullptr);
}
TEST(CommonColorTransformCApi, InitDisplay)
{
OakCommonColorTransform *t =
oakcommon_colortransform_init_display("sRGB", "Studio", "None");
ASSERT_NE(t, nullptr);
int is_display = 0;
EXPECT_EQ(oakcommon_colortransform_is_display(t, &is_display),
OAKCOMMON_OK);
EXPECT_EQ(is_display, 1);
EXPECT_EQ(read_string(oakcommon_colortransform_get_display, t), "sRGB");
EXPECT_EQ(read_string(oakcommon_colortransform_get_view, t), "Studio");
EXPECT_EQ(read_string(oakcommon_colortransform_get_look, t), "None");
oakcommon_colortransform_free(t);
}
TEST(CommonColorTransformCApi, InitDisplayNullString)
{
EXPECT_EQ(oakcommon_colortransform_init_display(nullptr, "v", "l"),
nullptr);
EXPECT_EQ(oakcommon_colortransform_init_display("d", nullptr, "l"),
nullptr);
EXPECT_EQ(oakcommon_colortransform_init_display("d", "v", nullptr),
nullptr);
}
TEST(CommonColorTransformCApi, FreeNull)
{
oakcommon_colortransform_free(nullptr);
}
TEST(CommonColorTransformCApi, NullHandleErrors)
{
int i = 0;
char buf[16];
EXPECT_EQ(oakcommon_colortransform_is_display(nullptr, &i),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_colortransform_get_display(nullptr, buf, sizeof(buf)),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_colortransform_get_output(nullptr, buf, sizeof(buf)),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_colortransform_get_view(nullptr, buf, sizeof(buf)),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_colortransform_get_look(nullptr, buf, sizeof(buf)),
OAKCOMMON_E_INVALID);
}
TEST(CommonColorTransformCApi, NullOutParam)
{
OakCommonColorTransform *t =
oakcommon_colortransform_init_output("sRGB");
ASSERT_NE(t, nullptr);
EXPECT_EQ(oakcommon_colortransform_is_display(t, nullptr),
OAKCOMMON_E_INVALID);
oakcommon_colortransform_free(t);
}
+251
View File
@@ -0,0 +1,251 @@
/***
Oak Video Editor - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include <gtest/gtest.h>
#include <cstring>
#include <string>
#include <vector>
#include "common/subtitleparams.h"
namespace
{
std::string read_indexed_string(
int (*fn)(OakCommonSubtitleParams *, int, char *, int),
OakCommonSubtitleParams *p, int index)
{
int needed = fn(p, index, nullptr, 0);
EXPECT_GT(needed, 0);
std::vector<char> buf(needed);
EXPECT_EQ(fn(p, index, buf.data(), needed), needed);
return std::string(buf.data());
}
std::string read_static_string(int (*fn)(char *, int))
{
int needed = fn(nullptr, 0);
EXPECT_GT(needed, 0);
std::vector<char> buf(needed);
EXPECT_EQ(fn(buf.data(), needed), needed);
return std::string(buf.data());
}
} // namespace
TEST(CommonSubtitleParamsCApi, InitFree)
{
OakCommonSubtitleParams *p = oakcommon_subtitleparams_init();
ASSERT_NE(p, nullptr);
int index = -1;
EXPECT_EQ(oakcommon_subtitleparams_get_stream_index(p, &index),
OAKCOMMON_OK);
EXPECT_EQ(index, 0);
int enabled = 0;
EXPECT_EQ(oakcommon_subtitleparams_get_enabled(p, &enabled),
OAKCOMMON_OK);
EXPECT_EQ(enabled, 1);
oakcommon_subtitleparams_free(p);
}
TEST(CommonSubtitleParamsCApi, FreeNull)
{
oakcommon_subtitleparams_free(nullptr);
}
TEST(CommonSubtitleParamsCApi, SetStreamIndex)
{
OakCommonSubtitleParams *p = oakcommon_subtitleparams_init();
ASSERT_NE(p, nullptr);
EXPECT_EQ(oakcommon_subtitleparams_set_stream_index(p, 3), OAKCOMMON_OK);
int index = 0;
EXPECT_EQ(oakcommon_subtitleparams_get_stream_index(p, &index),
OAKCOMMON_OK);
EXPECT_EQ(index, 3);
oakcommon_subtitleparams_free(p);
}
TEST(CommonSubtitleParamsCApi, SetStreamIndexNullHandle)
{
EXPECT_EQ(oakcommon_subtitleparams_set_stream_index(nullptr, 3),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_subtitleparams_get_stream_index(nullptr, nullptr),
OAKCOMMON_E_INVALID);
}
TEST(CommonSubtitleParamsCApi, SetEnabled)
{
OakCommonSubtitleParams *p = oakcommon_subtitleparams_init();
ASSERT_NE(p, nullptr);
EXPECT_EQ(oakcommon_subtitleparams_set_enabled(p, 0), OAKCOMMON_OK);
int enabled = 1;
EXPECT_EQ(oakcommon_subtitleparams_get_enabled(p, &enabled),
OAKCOMMON_OK);
EXPECT_EQ(enabled, 0);
oakcommon_subtitleparams_free(p);
}
TEST(CommonSubtitleParamsCApi, SetEnabledNullHandle)
{
EXPECT_EQ(oakcommon_subtitleparams_set_enabled(nullptr, 0),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_subtitleparams_get_enabled(nullptr, nullptr),
OAKCOMMON_E_INVALID);
}
TEST(CommonSubtitleParamsCApi, AddAndReadSubtitles)
{
OakCommonSubtitleParams *p = oakcommon_subtitleparams_init();
ASSERT_NE(p, nullptr);
int is_valid = 1;
EXPECT_EQ(oakcommon_subtitleparams_is_valid(p, &is_valid), OAKCOMMON_OK);
EXPECT_EQ(is_valid, 0);
EXPECT_EQ(oakcommon_subtitleparams_add_subtitle(p, 1, 2, 3, 2, "Hello"),
OAKCOMMON_OK);
EXPECT_EQ(oakcommon_subtitleparams_add_subtitle(p, 2, 1, 3, 1, "World"),
OAKCOMMON_OK);
EXPECT_EQ(oakcommon_subtitleparams_is_valid(p, &is_valid), OAKCOMMON_OK);
EXPECT_EQ(is_valid, 1);
int count = 0;
EXPECT_EQ(oakcommon_subtitleparams_count(p, &count), OAKCOMMON_OK);
EXPECT_EQ(count, 2);
int in_num, in_den, out_num, out_den;
EXPECT_EQ(oakcommon_subtitleparams_get_subtitle(p, 0, &in_num, &in_den,
&out_num, &out_den),
OAKCOMMON_OK);
EXPECT_EQ(in_num, 1);
EXPECT_EQ(in_den, 2);
EXPECT_EQ(out_num, 3);
EXPECT_EQ(out_den, 2);
EXPECT_EQ(read_indexed_string(
oakcommon_subtitleparams_get_subtitle_text, p, 0),
"Hello");
int num, den;
EXPECT_EQ(oakcommon_subtitleparams_duration(p, &num, &den), OAKCOMMON_OK);
EXPECT_EQ(num, 3);
EXPECT_EQ(den, 1);
EXPECT_EQ(oakcommon_subtitleparams_clear(p), OAKCOMMON_OK);
EXPECT_EQ(oakcommon_subtitleparams_count(p, &count), OAKCOMMON_OK);
EXPECT_EQ(count, 0);
oakcommon_subtitleparams_free(p);
}
TEST(CommonSubtitleParamsCApi, SubtitleErrorPaths)
{
OakCommonSubtitleParams *p = oakcommon_subtitleparams_init();
ASSERT_NE(p, nullptr);
int i = 0;
char buf[16];
EXPECT_EQ(oakcommon_subtitleparams_add_subtitle(nullptr, 0, 1, 1, 1, "x"),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_subtitleparams_add_subtitle(p, 0, 1, 1, 1, nullptr),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_subtitleparams_get_subtitle(p, 0, &i, &i, &i, &i),
OAKCOMMON_E_NOT_FOUND);
EXPECT_EQ(oakcommon_subtitleparams_get_subtitle(nullptr, 0, &i, &i, &i,
&i),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_subtitleparams_get_subtitle_text(p, 5, buf,
sizeof(buf)),
OAKCOMMON_E_NOT_FOUND);
EXPECT_EQ(oakcommon_subtitleparams_get_subtitle_text(nullptr, 0, buf,
sizeof(buf)),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_subtitleparams_is_valid(nullptr, &i),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_subtitleparams_count(nullptr, &i),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_subtitleparams_duration(nullptr, &i, &i),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_subtitleparams_clear(nullptr), OAKCOMMON_E_INVALID);
oakcommon_subtitleparams_free(p);
}
TEST(CommonSubtitleParamsCApi, GenerateAssHeader)
{
std::string header =
read_static_string(oakcommon_subtitleparams_generate_ass_header);
EXPECT_NE(header.find("[Script Info]"), std::string::npos);
EXPECT_NE(header.find("[V4+ Styles]"), std::string::npos);
EXPECT_NE(header.find("[Events]"), std::string::npos);
}
TEST(CommonSubtitleParamsCApi, XmlRoundTrip)
{
OakCommonSubtitleParams *p = oakcommon_subtitleparams_init();
ASSERT_NE(p, nullptr);
EXPECT_EQ(oakcommon_subtitleparams_set_stream_index(p, 2), OAKCOMMON_OK);
EXPECT_EQ(oakcommon_subtitleparams_add_subtitle(p, 1, 25, 2, 25,
"First <line>"),
OAKCOMMON_OK);
int needed = oakcommon_subtitleparams_save_xml(p, nullptr, 0);
ASSERT_GT(needed, 0);
std::vector<char> buf(needed);
ASSERT_EQ(oakcommon_subtitleparams_save_xml(p, buf.data(), needed), needed);
OakCommonSubtitleParams *q = oakcommon_subtitleparams_init();
ASSERT_NE(q, nullptr);
ASSERT_EQ(oakcommon_subtitleparams_load_xml(q, buf.data()), OAKCOMMON_OK);
int index = 0;
EXPECT_EQ(oakcommon_subtitleparams_get_stream_index(q, &index),
OAKCOMMON_OK);
EXPECT_EQ(index, 2);
int count = 0;
EXPECT_EQ(oakcommon_subtitleparams_count(q, &count), OAKCOMMON_OK);
EXPECT_EQ(count, 1);
EXPECT_EQ(read_indexed_string(
oakcommon_subtitleparams_get_subtitle_text, q, 0),
"First <line>");
oakcommon_subtitleparams_free(p);
oakcommon_subtitleparams_free(q);
}
TEST(CommonSubtitleParamsCApi, XmlErrorPaths)
{
OakCommonSubtitleParams *p = oakcommon_subtitleparams_init();
ASSERT_NE(p, nullptr);
char buf[16];
EXPECT_EQ(oakcommon_subtitleparams_load_xml(nullptr, "<a/>"),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_subtitleparams_load_xml(p, nullptr),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_subtitleparams_load_xml(p, "not xml"),
OAKCOMMON_E_FAILED);
EXPECT_EQ(oakcommon_subtitleparams_save_xml(nullptr, buf, sizeof(buf)),
OAKCOMMON_E_INVALID);
oakcommon_subtitleparams_free(p);
}
+525
View File
@@ -0,0 +1,525 @@
/***
Oak Video Editor - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include <gtest/gtest.h>
#include <cstdint>
#include <cstring>
#include <string>
#include <vector>
#include "common/videoparams.h"
TEST(CommonVideoParamsCApi, InitDefaults)
{
OakCommonVideoParams *p = oakcommon_videoparams_init();
ASSERT_NE(p, nullptr);
int v = -1;
EXPECT_EQ(oakcommon_videoparams_get_width(p, &v), OAKCOMMON_OK);
EXPECT_EQ(v, 0);
EXPECT_EQ(oakcommon_videoparams_get_is_valid(p, &v), OAKCOMMON_OK);
EXPECT_EQ(v, 0);
oakcommon_videoparams_free(p);
}
TEST(CommonVideoParamsCApi, FreeNull)
{
oakcommon_videoparams_free(nullptr);
}
TEST(CommonVideoParamsCApi, InitBasic)
{
OakCommonVideoParams *p = oakcommon_videoparams_init_basic(
1920, 1080, OAKCOMMON_PIXEL_FORMAT_U8,
4, 1, 1, OAKCOMMON_VIDEO_INTERLACE_NONE, 2);
ASSERT_NE(p, nullptr);
int v;
EXPECT_EQ(oakcommon_videoparams_get_width(p, &v), OAKCOMMON_OK);
EXPECT_EQ(v, 1920);
EXPECT_EQ(oakcommon_videoparams_get_height(p, &v), OAKCOMMON_OK);
EXPECT_EQ(v, 1080);
EXPECT_EQ(oakcommon_videoparams_get_depth(p, &v), OAKCOMMON_OK);
EXPECT_EQ(v, 1);
EXPECT_EQ(oakcommon_videoparams_get_is_3d(p, &v), OAKCOMMON_OK);
EXPECT_EQ(v, 0);
EXPECT_EQ(oakcommon_videoparams_get_format(p, &v), OAKCOMMON_OK);
EXPECT_EQ(v, OAKCOMMON_PIXEL_FORMAT_U8);
EXPECT_EQ(oakcommon_videoparams_get_channel_count(p, &v), OAKCOMMON_OK);
EXPECT_EQ(v, 4);
EXPECT_EQ(oakcommon_videoparams_get_divider(p, &v), OAKCOMMON_OK);
EXPECT_EQ(v, 2);
EXPECT_EQ(oakcommon_videoparams_get_interlacing(p, &v), OAKCOMMON_OK);
EXPECT_EQ(v, OAKCOMMON_VIDEO_INTERLACE_NONE);
EXPECT_EQ(oakcommon_videoparams_get_is_valid(p, &v), OAKCOMMON_OK);
EXPECT_EQ(v, 1);
// Effective size is divided by the divider
EXPECT_EQ(oakcommon_videoparams_get_effective_width(p, &v), OAKCOMMON_OK);
EXPECT_EQ(v, 960);
EXPECT_EQ(oakcommon_videoparams_get_effective_height(p, &v), OAKCOMMON_OK);
EXPECT_EQ(v, 540);
EXPECT_EQ(oakcommon_videoparams_get_square_pixel_width(p, &v),
OAKCOMMON_OK);
EXPECT_EQ(v, 1920);
// u8 RGBA: 4 bytes per pixel
EXPECT_EQ(oakcommon_videoparams_get_bytes_per_channel(p, &v),
OAKCOMMON_OK);
EXPECT_EQ(v, 1);
EXPECT_EQ(oakcommon_videoparams_get_bytes_per_pixel(p, &v), OAKCOMMON_OK);
EXPECT_EQ(v, 4);
EXPECT_EQ(oakcommon_videoparams_get_buffer_size(p, &v), OAKCOMMON_OK);
EXPECT_EQ(v, 1920 * 1080 * 4);
oakcommon_videoparams_free(p);
}
TEST(CommonVideoParamsCApi, InitWithTimeBase)
{
OakCommonVideoParams *p = oakcommon_videoparams_init_with_time_base(
1280, 720, 1001, 30000, OAKCOMMON_PIXEL_FORMAT_F32, 4, 1, 1,
OAKCOMMON_VIDEO_INTERLACE_NONE, 1);
ASSERT_NE(p, nullptr);
int num, den;
EXPECT_EQ(oakcommon_videoparams_get_time_base(p, &num, &den),
OAKCOMMON_OK);
EXPECT_EQ(num, 1001);
EXPECT_EQ(den, 30000);
// Frame rate is the flipped time base
EXPECT_EQ(oakcommon_videoparams_get_frame_rate(p, &num, &den),
OAKCOMMON_OK);
EXPECT_EQ(num, 30000);
EXPECT_EQ(den, 1001);
EXPECT_EQ(oakcommon_videoparams_frame_rate_as_time_base(p, &num, &den),
OAKCOMMON_OK);
EXPECT_EQ(num, 1001);
EXPECT_EQ(den, 30000);
oakcommon_videoparams_free(p);
}
TEST(CommonVideoParamsCApi, ScalarSetters)
{
OakCommonVideoParams *p = oakcommon_videoparams_init();
ASSERT_NE(p, nullptr);
int v;
EXPECT_EQ(oakcommon_videoparams_set_width(p, 640), OAKCOMMON_OK);
EXPECT_EQ(oakcommon_videoparams_set_height(p, 480), OAKCOMMON_OK);
EXPECT_EQ(oakcommon_videoparams_set_depth(p, 4), OAKCOMMON_OK);
EXPECT_EQ(oakcommon_videoparams_get_is_3d(p, &v), OAKCOMMON_OK);
EXPECT_EQ(v, 1);
EXPECT_EQ(oakcommon_videoparams_set_format(p, OAKCOMMON_PIXEL_FORMAT_U16),
OAKCOMMON_OK);
EXPECT_EQ(oakcommon_videoparams_set_channel_count(p, 3), OAKCOMMON_OK);
EXPECT_EQ(oakcommon_videoparams_set_interlacing(
p, OAKCOMMON_VIDEO_INTERLACED_TOP_FIRST),
OAKCOMMON_OK);
EXPECT_EQ(oakcommon_videoparams_set_divider(p, 4), OAKCOMMON_OK);
EXPECT_EQ(oakcommon_videoparams_set_enabled(p, 0), OAKCOMMON_OK);
EXPECT_EQ(oakcommon_videoparams_set_stream_index(p, 5), OAKCOMMON_OK);
EXPECT_EQ(oakcommon_videoparams_set_video_type(
p, OAKCOMMON_VIDEO_TYPE_IMAGE_SEQUENCE),
OAKCOMMON_OK);
EXPECT_EQ(oakcommon_videoparams_set_premultiplied_alpha(p, 1),
OAKCOMMON_OK);
EXPECT_EQ(oakcommon_videoparams_set_color_range(
p, OAKCOMMON_COLOR_RANGE_FULL),
OAKCOMMON_OK);
EXPECT_EQ(oakcommon_videoparams_set_color_primaries(p, 9), OAKCOMMON_OK);
EXPECT_EQ(oakcommon_videoparams_set_color_transfer(p, 16), OAKCOMMON_OK);
EXPECT_EQ(oakcommon_videoparams_set_time_base(p, 1, 25), OAKCOMMON_OK);
EXPECT_EQ(oakcommon_videoparams_set_frame_rate(p, 25, 1), OAKCOMMON_OK);
EXPECT_EQ(oakcommon_videoparams_set_pixel_aspect_ratio(p, 4, 3),
OAKCOMMON_OK);
float f;
EXPECT_EQ(oakcommon_videoparams_set_x(p, 1.5f), OAKCOMMON_OK);
EXPECT_EQ(oakcommon_videoparams_set_y(p, -2.5f), OAKCOMMON_OK);
EXPECT_EQ(oakcommon_videoparams_get_x(p, &f), OAKCOMMON_OK);
EXPECT_FLOAT_EQ(f, 1.5f);
EXPECT_EQ(oakcommon_videoparams_get_y(p, &f), OAKCOMMON_OK);
EXPECT_FLOAT_EQ(f, -2.5f);
int64_t i64;
EXPECT_EQ(oakcommon_videoparams_set_start_time(p, 100), OAKCOMMON_OK);
EXPECT_EQ(oakcommon_videoparams_set_duration(p, 250), OAKCOMMON_OK);
EXPECT_EQ(oakcommon_videoparams_get_start_time(p, &i64), OAKCOMMON_OK);
EXPECT_EQ(i64, 100);
EXPECT_EQ(oakcommon_videoparams_get_duration(p, &i64), OAKCOMMON_OK);
EXPECT_EQ(i64, 250);
EXPECT_EQ(oakcommon_videoparams_get_width(p, &v), OAKCOMMON_OK);
EXPECT_EQ(v, 640);
EXPECT_EQ(oakcommon_videoparams_get_effective_width(p, &v), OAKCOMMON_OK);
EXPECT_EQ(v, 160);
EXPECT_EQ(oakcommon_videoparams_get_interlacing(p, &v), OAKCOMMON_OK);
EXPECT_EQ(v, OAKCOMMON_VIDEO_INTERLACED_TOP_FIRST);
EXPECT_EQ(oakcommon_videoparams_get_enabled(p, &v), OAKCOMMON_OK);
EXPECT_EQ(v, 0);
EXPECT_EQ(oakcommon_videoparams_get_stream_index(p, &v), OAKCOMMON_OK);
EXPECT_EQ(v, 5);
EXPECT_EQ(oakcommon_videoparams_get_video_type(p, &v), OAKCOMMON_OK);
EXPECT_EQ(v, OAKCOMMON_VIDEO_TYPE_IMAGE_SEQUENCE);
EXPECT_EQ(oakcommon_videoparams_get_premultiplied_alpha(p, &v),
OAKCOMMON_OK);
EXPECT_EQ(v, 1);
EXPECT_EQ(oakcommon_videoparams_get_color_range(p, &v), OAKCOMMON_OK);
EXPECT_EQ(v, OAKCOMMON_COLOR_RANGE_FULL);
EXPECT_EQ(oakcommon_videoparams_get_color_primaries(p, &v), OAKCOMMON_OK);
EXPECT_EQ(v, 9);
EXPECT_EQ(oakcommon_videoparams_get_color_transfer(p, &v), OAKCOMMON_OK);
EXPECT_EQ(v, 16);
int num, den;
EXPECT_EQ(oakcommon_videoparams_get_pixel_aspect_ratio(p, &num, &den),
OAKCOMMON_OK);
EXPECT_EQ(num, 4);
EXPECT_EQ(den, 3);
// Square pixel width = 640 * 4/3 = 853.33 -> 853
EXPECT_EQ(oakcommon_videoparams_get_square_pixel_width(p, &v),
OAKCOMMON_OK);
EXPECT_EQ(v, 853);
EXPECT_EQ(oakcommon_videoparams_get_effective_depth(p, &v), OAKCOMMON_OK);
EXPECT_EQ(v, 1);
oakcommon_videoparams_free(p);
}
TEST(CommonVideoParamsCApi, NullHandleErrors)
{
int i;
float f;
int64_t i64;
char buf[16];
EXPECT_EQ(oakcommon_videoparams_get_width(nullptr, &i),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_set_width(nullptr, 1),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_get_height(nullptr, &i),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_set_height(nullptr, 1),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_get_depth(nullptr, &i),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_set_depth(nullptr, 1),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_get_is_3d(nullptr, &i),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_get_time_base(nullptr, &i, &i),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_set_time_base(nullptr, 1, 1),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_get_frame_rate(nullptr, &i, &i),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_set_frame_rate(nullptr, 1, 1),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_frame_rate_as_time_base(nullptr, &i, &i),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_get_pixel_aspect_ratio(nullptr, &i, &i),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_set_pixel_aspect_ratio(nullptr, 1, 1),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_get_format(nullptr, &i),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_set_format(nullptr, 0),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_get_channel_count(nullptr, &i),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_set_channel_count(nullptr, 1),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_get_interlacing(nullptr, &i),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_set_interlacing(nullptr, 0),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_get_divider(nullptr, &i),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_set_divider(nullptr, 1),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_get_enabled(nullptr, &i),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_set_enabled(nullptr, 1),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_get_x(nullptr, &f), OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_set_x(nullptr, 0.0f), OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_get_y(nullptr, &f), OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_set_y(nullptr, 0.0f), OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_get_stream_index(nullptr, &i),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_set_stream_index(nullptr, 0),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_get_video_type(nullptr, &i),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_set_video_type(nullptr, 0),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_get_start_time(nullptr, &i64),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_set_start_time(nullptr, 0),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_get_duration(nullptr, &i64),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_set_duration(nullptr, 0),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_get_premultiplied_alpha(nullptr, &i),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_set_premultiplied_alpha(nullptr, 0),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_get_color_range(nullptr, &i),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_set_color_range(nullptr, 0),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_get_color_primaries(nullptr, &i),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_set_color_primaries(nullptr, 0),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_get_color_transfer(nullptr, &i),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_set_color_transfer(nullptr, 0),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_get_colorspace(nullptr, buf, sizeof(buf)),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_set_colorspace(nullptr, "x"),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_get_square_pixel_width(nullptr, &i),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_get_effective_width(nullptr, &i),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_get_effective_height(nullptr, &i),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_get_effective_depth(nullptr, &i),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_get_is_valid(nullptr, &i),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_get_bytes_per_channel(nullptr, &i),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_get_bytes_per_pixel(nullptr, &i),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_get_buffer_size(nullptr, &i),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_get_time_in_timebase_units(nullptr, 0, 1,
&i64),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_load_xml(nullptr, "<a/>"),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_save_xml(nullptr, buf, sizeof(buf)),
OAKCOMMON_E_INVALID);
}
TEST(CommonVideoParamsCApi, NullOutParamErrors)
{
OakCommonVideoParams *p = oakcommon_videoparams_init();
ASSERT_NE(p, nullptr);
EXPECT_EQ(oakcommon_videoparams_get_width(p, nullptr),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_get_time_base(p, nullptr, nullptr),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_set_colorspace(p, nullptr),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_load_xml(p, nullptr), OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_load_xml(p, "not xml"),
OAKCOMMON_E_FAILED);
oakcommon_videoparams_free(p);
}
TEST(CommonVideoParamsCApi, Colorspace)
{
OakCommonVideoParams *p = oakcommon_videoparams_init();
ASSERT_NE(p, nullptr);
EXPECT_EQ(oakcommon_videoparams_set_colorspace(p, "rec709"),
OAKCOMMON_OK);
int needed = oakcommon_videoparams_get_colorspace(p, nullptr, 0);
ASSERT_GT(needed, 0);
std::vector<char> buf(needed);
EXPECT_EQ(oakcommon_videoparams_get_colorspace(p, buf.data(), needed),
needed);
EXPECT_STREQ(buf.data(), "rec709");
oakcommon_videoparams_free(p);
}
TEST(CommonVideoParamsCApi, TimeInTimebaseUnits)
{
OakCommonVideoParams *p = oakcommon_videoparams_init_with_time_base(
1920, 1080, 1, 25, OAKCOMMON_PIXEL_FORMAT_U8, 4, 1, 1,
OAKCOMMON_VIDEO_INTERLACE_NONE, 1);
ASSERT_NE(p, nullptr);
int64_t ts = -1;
EXPECT_EQ(oakcommon_videoparams_get_time_in_timebase_units(p, 2, 1, &ts),
OAKCOMMON_OK);
EXPECT_EQ(ts, 50); // 2 seconds at 25 fps
// Without a time base the result is AV_NOPTS_VALUE
OakCommonVideoParams *q = oakcommon_videoparams_init();
ASSERT_NE(q, nullptr);
EXPECT_EQ(oakcommon_videoparams_get_time_in_timebase_units(q, 2, 1, &ts),
OAKCOMMON_OK);
EXPECT_EQ(ts, INT64_MIN);
oakcommon_videoparams_free(p);
oakcommon_videoparams_free(q);
}
TEST(CommonVideoParamsCApi, Equals)
{
OakCommonVideoParams *a = oakcommon_videoparams_init_basic(
1920, 1080, OAKCOMMON_PIXEL_FORMAT_U8, 4, 1, 1,
OAKCOMMON_VIDEO_INTERLACE_NONE, 1);
OakCommonVideoParams *b = oakcommon_videoparams_init_basic(
1920, 1080, OAKCOMMON_PIXEL_FORMAT_U8, 4, 1, 1,
OAKCOMMON_VIDEO_INTERLACE_NONE, 1);
ASSERT_NE(a, nullptr);
ASSERT_NE(b, nullptr);
int equal = 0;
EXPECT_EQ(oakcommon_videoparams_equals(a, b, &equal), OAKCOMMON_OK);
EXPECT_EQ(equal, 1);
EXPECT_EQ(oakcommon_videoparams_set_divider(b, 2), OAKCOMMON_OK);
EXPECT_EQ(oakcommon_videoparams_equals(a, b, &equal), OAKCOMMON_OK);
EXPECT_EQ(equal, 0);
EXPECT_EQ(oakcommon_videoparams_equals(nullptr, b, &equal),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_equals(a, nullptr, &equal),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_equals(a, b, nullptr),
OAKCOMMON_E_INVALID);
oakcommon_videoparams_free(a);
oakcommon_videoparams_free(b);
}
TEST(CommonVideoParamsCApi, XmlRoundTrip)
{
OakCommonVideoParams *p = oakcommon_videoparams_init_with_time_base(
1920, 1080, 1, 25, OAKCOMMON_PIXEL_FORMAT_F16, 4, 4, 3,
OAKCOMMON_VIDEO_INTERLACED_BOTTOM_FIRST, 2);
ASSERT_NE(p, nullptr);
EXPECT_EQ(oakcommon_videoparams_set_colorspace(p, "rec709"),
OAKCOMMON_OK);
EXPECT_EQ(oakcommon_videoparams_set_color_primaries(p, 9), OAKCOMMON_OK);
int needed = oakcommon_videoparams_save_xml(p, nullptr, 0);
ASSERT_GT(needed, 0);
std::vector<char> buf(needed);
ASSERT_EQ(oakcommon_videoparams_save_xml(p, buf.data(), needed), needed);
OakCommonVideoParams *q = oakcommon_videoparams_init();
ASSERT_NE(q, nullptr);
ASSERT_EQ(oakcommon_videoparams_load_xml(q, buf.data()), OAKCOMMON_OK);
int equal = 0;
EXPECT_EQ(oakcommon_videoparams_equals(p, q, &equal), OAKCOMMON_OK);
EXPECT_EQ(equal, 1);
int num, den;
EXPECT_EQ(oakcommon_videoparams_get_frame_rate(q, &num, &den),
OAKCOMMON_OK);
EXPECT_EQ(num, 25);
EXPECT_EQ(den, 1);
int cneeded = oakcommon_videoparams_get_colorspace(q, nullptr, 0);
std::vector<char> cbuf(cneeded);
EXPECT_EQ(oakcommon_videoparams_get_colorspace(q, cbuf.data(), cneeded),
cneeded);
EXPECT_STREQ(cbuf.data(), "rec709");
int v;
EXPECT_EQ(oakcommon_videoparams_get_color_primaries(q, &v), OAKCOMMON_OK);
EXPECT_EQ(v, 9);
oakcommon_videoparams_free(p);
oakcommon_videoparams_free(q);
}
TEST(CommonVideoParamsCApi, StaticHelpers)
{
EXPECT_EQ(oakcommon_videoparams_get_bytes_per_channel_for_format(
OAKCOMMON_PIXEL_FORMAT_U8),
1);
EXPECT_EQ(oakcommon_videoparams_get_bytes_per_channel_for_format(
OAKCOMMON_PIXEL_FORMAT_F32),
4);
EXPECT_EQ(oakcommon_videoparams_get_bytes_per_channel_for_format(
OAKCOMMON_PIXEL_FORMAT_INVALID),
0);
EXPECT_EQ(oakcommon_videoparams_get_bytes_per_pixel_for_format(
OAKCOMMON_PIXEL_FORMAT_U10, 4),
4);
EXPECT_EQ(oakcommon_videoparams_get_bytes_per_pixel_for_format(
OAKCOMMON_PIXEL_FORMAT_U10, 3),
0);
EXPECT_EQ(oakcommon_videoparams_get_bytes_per_pixel_for_format(
OAKCOMMON_PIXEL_FORMAT_U16, 3),
6);
EXPECT_EQ(oakcommon_videoparams_calculate_buffer_size(
100, 50, OAKCOMMON_PIXEL_FORMAT_U8, 4),
20000);
EXPECT_EQ(oakcommon_videoparams_format_is_float(
OAKCOMMON_PIXEL_FORMAT_F16),
1);
EXPECT_EQ(oakcommon_videoparams_format_is_float(OAKCOMMON_PIXEL_FORMAT_U8),
0);
EXPECT_EQ(oakcommon_videoparams_generate_auto_divider(1920, 1080), 2);
EXPECT_EQ(oakcommon_videoparams_generate_auto_divider(640, 480), 1);
EXPECT_EQ(oakcommon_videoparams_generate_auto_divider(16000, 16000), 16);
EXPECT_EQ(oakcommon_videoparams_get_scaled_dimension(1920, 2), 960);
EXPECT_EQ(oakcommon_videoparams_get_divider_for_target_resolution(
3840, 2160, 1920, 1080),
2);
}
TEST(CommonVideoParamsCApi, StaticStrings)
{
char buf[64];
int needed = oakcommon_videoparams_get_name_for_divider(1, nullptr, 0);
ASSERT_GT(needed, 0);
EXPECT_EQ(oakcommon_videoparams_get_name_for_divider(1, buf, sizeof(buf)),
needed);
EXPECT_STREQ(buf, "Full");
EXPECT_GT(oakcommon_videoparams_get_name_for_divider(4, buf, sizeof(buf)),
0);
EXPECT_STREQ(buf, "1/4");
EXPECT_GT(oakcommon_videoparams_get_format_name(OAKCOMMON_PIXEL_FORMAT_U8,
buf, sizeof(buf)),
0);
EXPECT_STREQ(buf, "8-bit");
EXPECT_GT(oakcommon_videoparams_get_format_name(
OAKCOMMON_PIXEL_FORMAT_INVALID, buf, sizeof(buf)),
0);
EXPECT_NE(std::string(buf).find("Unknown"), std::string::npos);
EXPECT_GT(oakcommon_videoparams_frame_rate_to_string(25, 1, buf,
sizeof(buf)),
0);
EXPECT_STREQ(buf, "25 FPS");
}