#include "warb_shim.h"
#include "warb_dispatch.h"
#include "warb/src/lib.rs.h"
#include <cstdio>
#include <stdexcept>
#include <string>
#include <utility>
#ifdef WARB_TRACE
#define WARB_DBG(fmt, ...) \
do { std::fprintf(stderr, "[warb] " fmt "\n", ##__VA_ARGS__); std::fflush(stderr); } while (0)
#else
namespace warb_trace { template <class... Ts> inline void noop(Ts&&...) noexcept {} }
#define WARB_DBG(fmt, ...) warb_trace::noop(fmt, ##__VA_ARGS__)
#endif
namespace {
[[noreturn]] void throw_hr(const char* what, warb_abi::HRESULT hr) {
char buf[64];
std::snprintf(buf, sizeof(buf), " (HRESULT=0x%08x)", static_cast<uint32_t>(hr));
throw std::runtime_error(std::string(what) + buf);
}
uint64_t job_user_data(warb_abi::IBlackmagicRawJob* job) {
if (!job) return 0;
void* p = nullptr;
(void)job->GetUserData(&p);
return static_cast<uint64_t>(reinterpret_cast<uintptr_t>(p));
}
}
class CallbackDispatcher final : public warb_abi::IBlackmagicRawCallback {
public:
explicit CallbackDispatcher(rust::Box<CallbackBridge> handler) noexcept
: handler_(std::move(handler)) {}
warb_abi::HRESULT QueryInterface(warb_abi::REFIID, void** ppv) override {
if (!ppv) return warb_abi::E_POINTER;
*ppv = this;
return warb_abi::S_OK;
}
warb_abi::ULONG AddRef() override { return 1; }
warb_abi::ULONG Release() override { return 1; }
void ReadComplete(warb_abi::IBlackmagicRawJob* job,
warb_abi::HRESULT result,
warb_abi::IBlackmagicRawFrame* frame) override {
WARB_DBG("ReadComplete job=%p result=0x%x frame=%p",
(void*)job, (unsigned)result, (void*)frame);
std::unique_ptr<BrawFrame> owned;
if (frame) {
frame->AddRef();
owned = std::make_unique<BrawFrame>(frame);
}
handler_->on_read_complete(job_user_data(job),
static_cast<int32_t>(result), std::move(owned));
}
void DecodeComplete(warb_abi::IBlackmagicRawJob* job,
warb_abi::HRESULT result) override {
WARB_DBG("DecodeComplete job=%p result=0x%x", (void*)job, (unsigned)result);
handler_->on_decode_complete(job_user_data(job), static_cast<int32_t>(result));
}
void ProcessComplete(warb_abi::IBlackmagicRawJob* job,
warb_abi::HRESULT result,
warb_abi::IBlackmagicRawProcessedImage* image) override {
WARB_DBG("ProcessComplete job=%p result=0x%x image=%p",
(void*)job, (unsigned)result, (void*)image);
std::unique_ptr<BrawProcessedImage> owned;
if (image) {
image->AddRef();
owned = std::make_unique<BrawProcessedImage>(image);
}
handler_->on_process_complete(job_user_data(job),
static_cast<int32_t>(result), std::move(owned));
}
void PreparePipelineComplete(void* userData, warb_abi::HRESULT result) override {
WARB_DBG("PreparePipelineComplete ud=%p result=0x%x", userData, (unsigned)result);
handler_->on_prepare_pipeline_complete(
static_cast<uint64_t>(reinterpret_cast<uintptr_t>(userData)),
static_cast<int32_t>(result));
}
void TrimProgress(warb_abi::IBlackmagicRawJob*, float) override {}
void TrimComplete(warb_abi::IBlackmagicRawJob*, warb_abi::HRESULT) override {}
void SidecarMetadataParseWarning(warb_abi::IBlackmagicRawClip*, const char*, uint32_t, const char*) override {}
void SidecarMetadataParseError(warb_abi::IBlackmagicRawClip*, const char*, uint32_t, const char*) override {}
private:
rust::Box<CallbackBridge> handler_;
};
void BrawJob::set_user_data(uint64_t user_data) {
void* p = reinterpret_cast<void*>(static_cast<uintptr_t>(user_data));
auto hr = raw_->SetUserData(p);
if (!warb_abi::SUCCEEDED(hr)) throw_hr("IBlackmagicRawJob::SetUserData failed", hr);
}
void BrawJob::abort() {
auto hr = raw_->Abort();
if (!warb_abi::SUCCEEDED(hr)) throw_hr("IBlackmagicRawJob::Abort failed", hr);
}
void BrawFrame::set_resource_format(uint32_t format) {
auto hr = raw_->SetResourceFormat(
static_cast<warb_abi::BlackmagicRawResourceFormat>(format));
if (!warb_abi::SUCCEEDED(hr)) throw_hr("IBlackmagicRawFrame::SetResourceFormat failed", hr);
}
void BrawFrame::set_resolution_scale(uint32_t scale) {
auto hr = raw_->SetResolutionScale(
static_cast<warb_abi::BlackmagicRawResolutionScale>(scale));
if (!warb_abi::SUCCEEDED(hr)) throw_hr("IBlackmagicRawFrame::SetResolutionScale failed", hr);
}
std::unique_ptr<BrawJob> BrawFrame::create_job_decode_and_process() {
warb_abi::IBlackmagicRawJob* job = nullptr;
auto hr = raw_->CreateJobDecodeAndProcessFrame(nullptr, nullptr, &job);
if (!warb_abi::SUCCEEDED(hr) || !job) throw_hr("CreateJobDecodeAndProcessFrame failed", hr);
return std::make_unique<BrawJob>(job);
}
uint32_t BrawProcessedImage::width() const {
uint32_t v = 0; (void)raw_->GetWidth(&v); return v;
}
uint32_t BrawProcessedImage::height() const {
uint32_t v = 0; (void)raw_->GetHeight(&v); return v;
}
uint32_t BrawProcessedImage::format() const {
warb_abi::BlackmagicRawResourceFormat f{};
(void)raw_->GetResourceFormat(&f);
return static_cast<uint32_t>(f);
}
uint32_t BrawProcessedImage::size_bytes() const {
uint32_t v = 0; (void)raw_->GetResourceSizeBytes(&v); return v;
}
rust::Slice<const uint8_t> BrawProcessedImage::data() const {
uint32_t size = 0;
void* resource = nullptr;
(void)raw_->GetResourceSizeBytes(&size);
auto hr = raw_->GetResource(&resource);
if (!warb_abi::SUCCEEDED(hr) || !resource) {
return rust::Slice<const uint8_t>();
}
return rust::Slice<const uint8_t>(static_cast<const uint8_t*>(resource), size);
}
uint32_t BrawClip::width() const {
uint32_t v = 0; (void)raw_->GetWidth(&v); return v;
}
uint32_t BrawClip::height() const {
uint32_t v = 0; (void)raw_->GetHeight(&v); return v;
}
uint64_t BrawClip::frame_count() const {
uint64_t v = 0; (void)raw_->GetFrameCount(&v); return v;
}
float BrawClip::frame_rate() const {
float v = 0.0f; (void)raw_->GetFrameRate(&v); return v;
}
std::unique_ptr<BrawJob> BrawClip::create_job_read_frame(uint64_t frame_index) {
warb_abi::IBlackmagicRawJob* job = nullptr;
auto hr = raw_->CreateJobReadFrame(frame_index, &job);
if (!warb_abi::SUCCEEDED(hr) || !job) throw_hr("CreateJobReadFrame failed", hr);
return std::make_unique<BrawJob>(job);
}
BrawCodec::BrawCodec(warb_abi::IBlackmagicRawFactory* factory,
warb_abi::IBlackmagicRaw* codec,
std::unique_ptr<CallbackDispatcher> dispatcher)
: dispatcher_(std::move(dispatcher)),
factory_(factory),
codec_(codec) {}
BrawCodec::~BrawCodec() {
if (codec_) {
codec_->FlushJobs();
codec_->SetCallback(nullptr);
}
}
void BrawCodec::prepare_pipeline(uint32_t pipeline) {
auto hr = codec_->PreparePipeline(
static_cast<warb_abi::BlackmagicRawPipeline>(pipeline),
nullptr, nullptr, nullptr);
if (!warb_abi::SUCCEEDED(hr)) throw_hr("IBlackmagicRaw::PreparePipeline failed", hr);
}
std::unique_ptr<BrawClip> BrawCodec::open_clip(rust::Slice<const uint8_t> path) {
std::string path_z(reinterpret_cast<const char*>(path.data()), path.size());
warb_abi::IBlackmagicRawClip* clip = nullptr;
auto hr = codec_->OpenClip(path_z.c_str(), &clip);
if (!warb_abi::SUCCEEDED(hr) || !clip) throw_hr("IBlackmagicRaw::OpenClip failed", hr);
return std::make_unique<BrawClip>(clip);
}
void BrawCodec::flush_jobs() {
codec_->FlushJobs();
}
std::unique_ptr<BrawCodec> new_codec(rust::Box<CallbackBridge> handler) {
const auto& lib = warb_abi::load_library();
if (!lib.factory_fn) {
throw std::runtime_error("Blackmagic RAW runtime not loaded: " + lib.error);
}
warb_abi::ComPtr<warb_abi::IBlackmagicRawFactory> factory(lib.factory_fn());
if (!factory) throw std::runtime_error("CreateBlackmagicRawFactoryInstance() returned null");
warb_abi::IBlackmagicRaw* codec_raw = nullptr;
auto hr = factory->CreateCodec(&codec_raw);
if (!warb_abi::SUCCEEDED(hr) || !codec_raw) {
throw_hr("IBlackmagicRawFactory::CreateCodec failed", hr);
}
warb_abi::ComPtr<warb_abi::IBlackmagicRaw> codec(codec_raw);
auto dispatcher = std::make_unique<CallbackDispatcher>(std::move(handler));
hr = codec->SetCallback(dispatcher.get());
if (!warb_abi::SUCCEEDED(hr)) throw_hr("IBlackmagicRaw::SetCallback failed", hr);
return std::make_unique<BrawCodec>(
factory.release(), codec.release(), std::move(dispatcher));
}
void submit_job(std::unique_ptr<BrawJob> job) {
if (!job || !job->raw_) throw std::runtime_error("submit_job called with null job");
auto hr = job->raw_->Submit();
if (!warb_abi::SUCCEEDED(hr)) throw_hr("IBlackmagicRawJob::Submit failed", hr);
}