Files
Mike-Solar bb40b4923e style: unify identifier naming per updated conventions
Automated with clang-tidy readability-identifier-naming (config added to
.clang-tidy) plus scripted passes, per the updated rules now documented
in CONTRIBUTING.md:

- types (class/struct/enum/alias/template params): PascalCase
- functions, variables, members: snake_case (incl. rational -> Rational)
- private/protected members: trailing underscore; static member
  variables likewise (instance_, available_themes_)
- constants and enum values: snake_case (kLinear -> k_linear,
  F32P -> f32p); ALL_CAPS reserved for macros
- macros: OAK_ prefix (OLIVE_ADD_TEST/OLIVE_ASSERT/OLIVE_CONFIG ->
  OAK_ADD_TEST/OAK_ASSERT/OAK_CONFIG, GL_PREAMBLE -> OAK_GL_PREAMBLE,
  include guards -> OAK_*)
- file names: all lowercase (Current/Plugin/OliveHost/OliveClip/
  OlivePluginInstance -> current/plugin/olivehost/oliveclip/
  oliveplugininstance)
- getters share the member name sans underscore, setters set_foo()
- Qt and third-party (OpenFX) virtual overrides and framework callbacks
  keep their original names (exempt in .clang-tidy)

Manual follow-ups required where automation could not reach:
- string-based QMetaObject/SIGNAL/SLOT references updated to renamed
  methods (AddTask, CreatedFile, DeleteSpecificFile, moveSelectionUp, ...)
- macro bodies referencing renamed methods (OLIVE_CONFIG,
  NODE_DEFAULT_DESTRUCTOR, MANAGEDDISPLAYWIDGET_*)
- self-shadowing locals renamed where signals/methods became same-named
  (size_changed, worker_count, selected_items, import param, filters)
- third_party OFX member/namespace usages restored (OFX::Host::*,
  _created, _clipPrefsDirty, createInstance, clearPersistentMessage)
- STL protocol aliases restored (const_iterator) with .clang-tidy
  ignore rules; qHash overloads restored

Full build and test suite pass: ctest 4/4, ~1960 gtest cases green.
2026-07-19 16:10:54 +08:00

185 lines
5.1 KiB
C++

#include <gtest/gtest.h>
#include <QDir>
#include <QFileInfo>
#include <QTemporaryDir>
#include "codec/decoder.h"
#include "codec/oiio/oiiodecoder.h"
#include "codec/oiio/oiioencoder.h"
#include "node/project/footage/footagedescription.h"
namespace
{
QString img_path()
{
return QDir(QStringLiteral(OAK_TEST_SOURCE_DIR))
.filePath(QStringLiteral("tests/img.png"));
}
}
TEST(CodecOIIO, ProbePngReportsStillImage)
{
olive::OIIODecoder decoder;
olive::FootageDescription desc = decoder.probe(img_path(), nullptr);
ASSERT_TRUE(desc.is_valid());
EXPECT_EQ(desc.decoder(), QStringLiteral("oiio"));
EXPECT_EQ(desc.get_stream_count(), 1);
const QVector<olive::VideoParams> &streams = desc.get_video_streams();
ASSERT_EQ(streams.size(), 1);
EXPECT_EQ(streams.first().width(), 1920);
EXPECT_EQ(streams.first().height(), 1080);
EXPECT_EQ(streams.first().format(), olive::core::PixelFormat::u8);
EXPECT_EQ(streams.first().channel_count(), 4);
EXPECT_EQ(streams.first().video_type(), olive::VideoParams::k_video_type_still);
EXPECT_EQ(streams.first().stream_index(), 0);
EXPECT_TRUE(streams.first().enabled());
}
TEST(CodecOIIO, ProbeUnsupportedExtensionReturnsInvalid)
{
olive::OIIODecoder decoder;
olive::FootageDescription desc =
decoder.probe(QStringLiteral("nonexistent.zzz"), nullptr);
EXPECT_FALSE(desc.is_valid());
EXPECT_TRUE(desc.get_video_streams().isEmpty());
}
TEST(CodecOIIO, DecodePngFrame)
{
const QString path = img_path();
ASSERT_TRUE(QFileInfo::exists(path));
olive::DecoderPtr decoder =
olive::Decoder::create_from_id(QStringLiteral("oiio"));
ASSERT_TRUE(decoder);
ASSERT_TRUE(decoder->open(olive::Decoder::CodecStream(path, 0, nullptr)));
olive::Decoder::RetrieveVideoParams params;
params.time = olive::core::Rational(0);
params.divider = 1;
olive::FramePtr frame = decoder->retrieve_video_frame(params);
ASSERT_TRUE(frame);
ASSERT_TRUE(frame->is_allocated());
EXPECT_EQ(frame->width(), 1920);
EXPECT_EQ(frame->height(), 1080);
// Still images are always converted to F32 by OIIODecoder
EXPECT_EQ(frame->format(), olive::core::PixelFormat::f32);
EXPECT_EQ(frame->channel_count(), 4);
EXPECT_EQ(frame->timestamp(), olive::core::Rational(0));
EXPECT_GT(frame->allocated_size(), 0);
bool has_nonzero_byte = false;
const char *data = frame->const_data();
for (int i = 0; i < frame->allocated_size(); i++) {
if (data[i] != 0) {
has_nonzero_byte = true;
break;
}
}
EXPECT_TRUE(has_nonzero_byte);
decoder->close();
}
TEST(CodecOIIO, DecodeWithDividerHalvesResolution)
{
olive::DecoderPtr decoder =
olive::Decoder::create_from_id(QStringLiteral("oiio"));
ASSERT_TRUE(decoder);
ASSERT_TRUE(
decoder->open(olive::Decoder::CodecStream(img_path(), 0, nullptr)));
olive::Decoder::RetrieveVideoParams params;
params.time = olive::core::Rational(0);
params.divider = 2;
olive::FramePtr frame = decoder->retrieve_video_frame(params);
ASSERT_TRUE(frame);
ASSERT_TRUE(frame->is_allocated());
EXPECT_EQ(frame->width(), 960);
EXPECT_EQ(frame->height(), 540);
decoder->close();
}
TEST(CodecOIIO, EncodePngAndDecodeBack)
{
QTemporaryDir dir;
ASSERT_TRUE(dir.isValid());
const QString path = dir.filePath(QStringLiteral("roundtrip.png"));
const int width = 64;
const int height = 32;
olive::EncodingParams params;
params.set_filename(path);
params.enable_video(
olive::VideoParams(width, height, olive::core::PixelFormat::u8,
olive::VideoParams::k_rgba_channel_count),
olive::ExportCodec::k_codec_png);
olive::OIIOEncoder encoder(params);
ASSERT_TRUE(encoder.open());
olive::FramePtr frame = olive::Frame::create();
frame->set_video_params(params.video_params());
ASSERT_TRUE(frame->allocate());
for (int y = 0; y < height; y++) {
uint8_t *row = reinterpret_cast<uint8_t *>(frame->data()) +
y * frame->linesize_bytes();
for (int x = 0; x < width; x++) {
row[x * 4 + 0] = uint8_t(x * 2);
row[x * 4 + 1] = uint8_t(y * 4);
row[x * 4 + 2] = 200;
row[x * 4 + 3] = 255;
}
}
EXPECT_TRUE(encoder.write_frame(frame, olive::core::Rational(0)));
encoder.close();
ASSERT_TRUE(QFileInfo::exists(path));
// Decode the file we just wrote and verify dimensions and pixels
olive::DecoderPtr decoder =
olive::Decoder::create_from_id(QStringLiteral("oiio"));
ASSERT_TRUE(decoder);
ASSERT_TRUE(decoder->open(olive::Decoder::CodecStream(path, 0, nullptr)));
olive::Decoder::RetrieveVideoParams rp;
rp.time = olive::core::Rational(0);
rp.divider = 1;
olive::FramePtr back = decoder->retrieve_video_frame(rp);
ASSERT_TRUE(back);
EXPECT_EQ(back->width(), width);
EXPECT_EQ(back->height(), height);
// U8 -> F32 conversion scales by 1/255
const float eps = 1.0f / 255.0f;
olive::core::Color c = back->get_pixel(10, 5);
EXPECT_NEAR(c.red(), 20.0f / 255.0f, eps);
EXPECT_NEAR(c.green(), 20.0f / 255.0f, eps);
EXPECT_NEAR(c.blue(), 200.0f / 255.0f, eps);
EXPECT_NEAR(c.alpha(), 1.0f, eps);
decoder->close();
}
TEST(CodecOIIO, EncoderRejectsAudioAndSubtitles)
{
olive::EncodingParams params;
olive::OIIOEncoder encoder(params);
olive::SampleBuffer buffer;
EXPECT_FALSE(encoder.write_audio(buffer));
EXPECT_FALSE(encoder.write_subtitle(nullptr));
}