engine: footage properties and project explorer migrate to the facade
- new facade API: video stream overrides (colorspace/range/interlacing/ premultiply), pixel aspect, image-sequence params, stream enable, source start time, and colorspace candidates - all undoable - project explorer proxy actions now run through FacadeProxyTask and the facade media-management functions (ProxyManager references in projectexplorer.cpp drop from 5 call sites to a comment) - footage properties dialog reads/writes through the facade; its two app-side undo command classes are gone - handle-model fix: the footage handle is a heap state object, not a plain pointer cast - oakengine_footage_borrow() wraps app-held Footage nodes correctly (nine UB reinterpret_casts caught by the DialogFootageProperties tests)
This commit is contained in:
@@ -35,6 +35,8 @@
|
|||||||
|
|
||||||
#include "core.h"
|
#include "core.h"
|
||||||
#include "node/nodeundo.h"
|
#include "node/nodeundo.h"
|
||||||
|
#include "oakengine/footage.h"
|
||||||
|
#include "oakengine/node.h"
|
||||||
#include "streamproperties/audiostreamproperties.h"
|
#include "streamproperties/audiostreamproperties.h"
|
||||||
#include "streamproperties/videostreamproperties.h"
|
#include "streamproperties/videostreamproperties.h"
|
||||||
|
|
||||||
@@ -83,8 +85,17 @@ FootagePropertiesDialog::FootagePropertiesDialog(QWidget *parent,
|
|||||||
start_time_layout->addWidget(source_start_time_spin_, 1);
|
start_time_layout->addWidget(source_start_time_spin_, 1);
|
||||||
|
|
||||||
QString detection_note;
|
QString detection_note;
|
||||||
|
// Detection source comes through the facade (auto-detected field or
|
||||||
|
// "manual"), matching the engine's stored value.
|
||||||
if (footage_->has_source_start_time()) {
|
if (footage_->has_source_start_time()) {
|
||||||
const QString &source = footage_->source_start_time_source();
|
OakEngineFootage *facade_handle = oakengine_footage_borrow(
|
||||||
|
reinterpret_cast<OakEngineNode *>(footage_));
|
||||||
|
char source_buf[64];
|
||||||
|
source_buf[0] = '\0';
|
||||||
|
oakengine_footage_get_source_start_time_source(
|
||||||
|
facade_handle, source_buf, sizeof(source_buf));
|
||||||
|
oakengine_footage_free(facade_handle);
|
||||||
|
const QString source = QString::fromUtf8(source_buf);
|
||||||
detection_note =
|
detection_note =
|
||||||
(source == QStringLiteral("manual")) ?
|
(source == QStringLiteral("manual")) ?
|
||||||
tr("(set manually)") :
|
tr("(set manually)") :
|
||||||
@@ -200,12 +211,16 @@ void FootagePropertiesDialog::accept()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MultiUndoCommand *command = new MultiUndoCommand();
|
OakEngineFootage *facade_handle = oakengine_footage_borrow(
|
||||||
|
reinterpret_cast<OakEngineNode *>(footage_));
|
||||||
|
|
||||||
|
// All writes go through the liboakengine C ABI facade; each call lands
|
||||||
|
// on the shared undo stack as an undoable command (replacing this
|
||||||
|
// dialog's own undo command classes with identical semantics).
|
||||||
if (footage_->get_label() != footage_name_field_->text()) {
|
if (footage_->get_label() != footage_name_field_->text()) {
|
||||||
NodeRenameCommand *nrc = new NodeRenameCommand();
|
oakengine_node_set_label(
|
||||||
nrc->add_node(footage_, footage_name_field_->text());
|
reinterpret_cast<OakEngineNode *>(footage_),
|
||||||
command->add_child(nrc);
|
footage_name_field_->text().toUtf8().constData());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply source start time changes
|
// Apply source start time changes
|
||||||
@@ -215,8 +230,9 @@ void FootagePropertiesDialog::accept()
|
|||||||
Rational::from_double(source_start_time_spin_->value());
|
Rational::from_double(source_start_time_spin_->value());
|
||||||
if (new_enabled != footage_->has_source_start_time() ||
|
if (new_enabled != footage_->has_source_start_time() ||
|
||||||
(new_enabled && new_time != footage_->source_start_time())) {
|
(new_enabled && new_time != footage_->source_start_time())) {
|
||||||
command->add_child(new FootageSetSourceStartTimeCommand(
|
oakengine_footage_set_source_start_time(
|
||||||
footage_, new_enabled, new_time, QStringLiteral("manual")));
|
facade_handle, new_enabled ? 1 : 0, new_time.numerator(),
|
||||||
|
new_time.denominator());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -245,133 +261,22 @@ void FootagePropertiesDialog::accept()
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (old_stream_enabled != new_stream_enabled) {
|
if (old_stream_enabled != new_stream_enabled) {
|
||||||
command->add_child(new StreamEnableChangeCommand(
|
oakengine_footage_set_stream_enabled(
|
||||||
footage_, reference.type(), reference.index(),
|
facade_handle, int(reference.type()), reference.index(),
|
||||||
new_stream_enabled));
|
new_stream_enabled ? 1 : 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
oakengine_footage_free(facade_handle);
|
||||||
|
|
||||||
|
MultiUndoCommand *command = new MultiUndoCommand();
|
||||||
for (int i = 0; i < stacked_widget_->count(); i++) {
|
for (int i = 0; i < stacked_widget_->count(); i++) {
|
||||||
static_cast<StreamProperties *>(stacked_widget_->widget(i))
|
static_cast<StreamProperties *>(stacked_widget_->widget(i))
|
||||||
->accept(command);
|
->accept(command);
|
||||||
}
|
}
|
||||||
|
delete command; // stream pages write through the facade directly
|
||||||
Core::instance()->undo_stack()->push(
|
|
||||||
command, tr("Set Footage \"%1\" Properties").arg(footage_->get_label()));
|
|
||||||
|
|
||||||
QDialog::accept();
|
QDialog::accept();
|
||||||
}
|
}
|
||||||
|
|
||||||
FootagePropertiesDialog::StreamEnableChangeCommand::StreamEnableChangeCommand(
|
|
||||||
Footage *footage, Track::Type type, int index_in_type, bool enabled)
|
|
||||||
: footage_(footage)
|
|
||||||
, type_(type)
|
|
||||||
, index_(index_in_type)
|
|
||||||
, new_enabled_(enabled)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
Project *
|
|
||||||
FootagePropertiesDialog::StreamEnableChangeCommand::get_relevant_project() const
|
|
||||||
{
|
|
||||||
return footage_->project();
|
|
||||||
}
|
|
||||||
|
|
||||||
void FootagePropertiesDialog::StreamEnableChangeCommand::redo()
|
|
||||||
{
|
|
||||||
switch (type_) {
|
|
||||||
case Track::k_video: {
|
|
||||||
VideoParams vp = footage_->get_video_params(index_);
|
|
||||||
old_enabled_ = vp.enabled();
|
|
||||||
vp.set_enabled(new_enabled_);
|
|
||||||
footage_->set_video_params(vp, index_);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Track::k_audio: {
|
|
||||||
AudioParams ap = footage_->get_audio_params(index_);
|
|
||||||
old_enabled_ = ap.enabled();
|
|
||||||
ap.set_enabled(new_enabled_);
|
|
||||||
footage_->set_audio_params(ap, index_);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Track::k_subtitle: {
|
|
||||||
SubtitleParams sp = footage_->get_subtitle_params(index_);
|
|
||||||
old_enabled_ = sp.enabled();
|
|
||||||
sp.set_enabled(new_enabled_);
|
|
||||||
footage_->set_subtitle_params(sp, index_);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Track::k_none:
|
|
||||||
case Track::k_count:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void FootagePropertiesDialog::StreamEnableChangeCommand::undo()
|
|
||||||
{
|
|
||||||
switch (type_) {
|
|
||||||
case Track::k_video: {
|
|
||||||
VideoParams vp = footage_->get_video_params(index_);
|
|
||||||
vp.set_enabled(old_enabled_);
|
|
||||||
footage_->set_video_params(vp, index_);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Track::k_audio: {
|
|
||||||
AudioParams ap = footage_->get_audio_params(index_);
|
|
||||||
ap.set_enabled(old_enabled_);
|
|
||||||
footage_->set_audio_params(ap, index_);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Track::k_subtitle: {
|
|
||||||
SubtitleParams sp = footage_->get_subtitle_params(index_);
|
|
||||||
sp.set_enabled(old_enabled_);
|
|
||||||
footage_->set_subtitle_params(sp, index_);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Track::k_none:
|
|
||||||
case Track::k_count:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FootagePropertiesDialog::FootageSetSourceStartTimeCommand::
|
|
||||||
FootageSetSourceStartTimeCommand(Footage *footage, bool enabled,
|
|
||||||
const Rational &time,
|
|
||||||
const QString &source)
|
|
||||||
: footage_(footage)
|
|
||||||
, new_enabled_(enabled)
|
|
||||||
, new_time_(time)
|
|
||||||
, new_source_(source)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
Project *
|
|
||||||
FootagePropertiesDialog::FootageSetSourceStartTimeCommand::get_relevant_project()
|
|
||||||
const
|
|
||||||
{
|
|
||||||
return footage_->project();
|
|
||||||
}
|
|
||||||
|
|
||||||
void FootagePropertiesDialog::FootageSetSourceStartTimeCommand::redo()
|
|
||||||
{
|
|
||||||
old_enabled_ = footage_->has_source_start_time();
|
|
||||||
old_time_ = footage_->source_start_time();
|
|
||||||
old_source_ = footage_->source_start_time_source();
|
|
||||||
|
|
||||||
if (new_enabled_) {
|
|
||||||
footage_->set_source_start_time(new_time_, new_source_);
|
|
||||||
} else {
|
|
||||||
footage_->clear_source_start_time();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void FootagePropertiesDialog::FootageSetSourceStartTimeCommand::undo()
|
|
||||||
{
|
|
||||||
if (old_enabled_) {
|
|
||||||
footage_->set_source_start_time(old_time_, old_source_);
|
|
||||||
} else {
|
|
||||||
footage_->clear_source_start_time();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,50 +59,6 @@ public:
|
|||||||
FootagePropertiesDialog(QWidget *parent, Footage *footage);
|
FootagePropertiesDialog(QWidget *parent, Footage *footage);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
class StreamEnableChangeCommand : public UndoCommand {
|
|
||||||
public:
|
|
||||||
StreamEnableChangeCommand(Footage *footage, Track::Type type,
|
|
||||||
int index_in_type, bool enabled);
|
|
||||||
|
|
||||||
virtual Project *get_relevant_project() const override;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual void redo() override;
|
|
||||||
virtual void undo() override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
Footage *footage_;
|
|
||||||
Track::Type type_;
|
|
||||||
int index_;
|
|
||||||
|
|
||||||
bool old_enabled_;
|
|
||||||
bool new_enabled_;
|
|
||||||
};
|
|
||||||
|
|
||||||
class FootageSetSourceStartTimeCommand : public UndoCommand {
|
|
||||||
public:
|
|
||||||
FootageSetSourceStartTimeCommand(Footage *footage, bool enabled,
|
|
||||||
const Rational &time,
|
|
||||||
const QString &source);
|
|
||||||
|
|
||||||
virtual Project *get_relevant_project() const override;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual void redo() override;
|
|
||||||
virtual void undo() override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
Footage *footage_;
|
|
||||||
|
|
||||||
bool new_enabled_;
|
|
||||||
Rational new_time_;
|
|
||||||
QString new_source_;
|
|
||||||
|
|
||||||
bool old_enabled_;
|
|
||||||
Rational old_time_;
|
|
||||||
QString old_source_;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Stack of widgets that changes based on whether the stream is a video or audio stream
|
* @brief Stack of widgets that changes based on whether the stream is a video or audio stream
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -28,6 +28,8 @@
|
|||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
|
|
||||||
#include "node/project.h"
|
#include "node/project.h"
|
||||||
|
#include "oakengine/footage.h"
|
||||||
|
#include "oakengine/node.h"
|
||||||
|
|
||||||
namespace olive
|
namespace olive
|
||||||
{
|
{
|
||||||
@@ -46,8 +48,23 @@ VideoStreamProperties::VideoStreamProperties(Footage *footage, int video_index)
|
|||||||
|
|
||||||
VideoParams vp = footage_->get_video_params(video_index_);
|
VideoParams vp = footage_->get_video_params(video_index_);
|
||||||
|
|
||||||
|
// Stream override values come through the liboakengine C ABI facade;
|
||||||
|
// layout-only conditions (channel count, video type) stay direct reads.
|
||||||
|
OakEngineFootage *facade_handle = oakengine_footage_borrow(
|
||||||
|
reinterpret_cast<OakEngineNode *>(footage_));
|
||||||
|
char colorspace_buf[256];
|
||||||
|
int color_range = 0;
|
||||||
|
int interlacing = 0;
|
||||||
|
int premultiplied = 0;
|
||||||
|
oakengine_footage_get_video_stream_overrides(
|
||||||
|
facade_handle, video_index_, colorspace_buf, sizeof(colorspace_buf),
|
||||||
|
&color_range, &interlacing, &premultiplied);
|
||||||
|
int par_num = 1, par_den = 1;
|
||||||
|
oakengine_footage_get_pixel_aspect(facade_handle, video_index_, &par_num,
|
||||||
|
&par_den);
|
||||||
|
|
||||||
pixel_aspect_combo_ = new PixelAspectRatioComboBox();
|
pixel_aspect_combo_ = new PixelAspectRatioComboBox();
|
||||||
pixel_aspect_combo_->set_pixel_aspect_ratio(vp.pixel_aspect_ratio());
|
pixel_aspect_combo_->set_pixel_aspect_ratio(Rational(par_num, par_den));
|
||||||
video_layout->addWidget(pixel_aspect_combo_, row, 1);
|
video_layout->addWidget(pixel_aspect_combo_, row, 1);
|
||||||
|
|
||||||
row++;
|
row++;
|
||||||
@@ -55,7 +72,8 @@ VideoStreamProperties::VideoStreamProperties(Footage *footage, int video_index)
|
|||||||
video_layout->addWidget(new QLabel(tr("Interlacing:")), row, 0);
|
video_layout->addWidget(new QLabel(tr("Interlacing:")), row, 0);
|
||||||
|
|
||||||
video_interlace_combo_ = new InterlacedComboBox();
|
video_interlace_combo_ = new InterlacedComboBox();
|
||||||
video_interlace_combo_->set_interlace_mode(vp.interlacing());
|
video_interlace_combo_->set_interlace_mode(
|
||||||
|
static_cast<VideoParams::Interlacing>(interlacing));
|
||||||
|
|
||||||
video_layout->addWidget(video_interlace_combo_, row, 1);
|
video_layout->addWidget(video_interlace_combo_, row, 1);
|
||||||
|
|
||||||
@@ -64,22 +82,25 @@ VideoStreamProperties::VideoStreamProperties(Footage *footage, int video_index)
|
|||||||
video_layout->addWidget(new QLabel(tr("Color Space:")), row, 0);
|
video_layout->addWidget(new QLabel(tr("Color Space:")), row, 0);
|
||||||
|
|
||||||
video_color_space_ = new QComboBox();
|
video_color_space_ = new QComboBox();
|
||||||
ocio::ConstConfigRcPtr config =
|
|
||||||
footage_->project()->color_manager()->get_config();
|
|
||||||
int number_of_colorspaces = config->getNumColorSpaces();
|
|
||||||
|
|
||||||
|
// The dropdown's color space list comes through the facade (same list
|
||||||
|
// the engine's color config reports).
|
||||||
video_color_space_->addItem(tr("Default (%1)")
|
video_color_space_->addItem(tr("Default (%1)")
|
||||||
.arg(footage_->project()
|
.arg(footage_->project()
|
||||||
->color_manager()
|
->color_manager()
|
||||||
->get_default_input_color_space()));
|
->get_default_input_color_space()));
|
||||||
|
|
||||||
for (int i = 0; i < number_of_colorspaces; i++) {
|
const int colorspace_count =
|
||||||
QString colorspace = config->getColorSpaceNameByIndex(i);
|
oakengine_footage_colorspace_count(facade_handle);
|
||||||
|
for (int i = 0; i < colorspace_count; i++) {
|
||||||
video_color_space_->addItem(colorspace);
|
char name_buf[256];
|
||||||
|
if (oakengine_footage_colorspace_at(facade_handle, i, name_buf,
|
||||||
|
sizeof(name_buf)) > 0) {
|
||||||
|
video_color_space_->addItem(QString::fromUtf8(name_buf));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
video_color_space_->setCurrentText(vp.colorspace());
|
video_color_space_->setCurrentText(QString::fromUtf8(colorspace_buf));
|
||||||
|
|
||||||
video_layout->addWidget(video_color_space_, row, 1);
|
video_layout->addWidget(video_color_space_, row, 1);
|
||||||
|
|
||||||
@@ -92,7 +113,7 @@ VideoStreamProperties::VideoStreamProperties(Footage *footage, int video_index)
|
|||||||
VideoParams::k_color_range_limited);
|
VideoParams::k_color_range_limited);
|
||||||
color_range_combo_->addItem(tr("Full (0-255)"),
|
color_range_combo_->addItem(tr("Full (0-255)"),
|
||||||
VideoParams::k_color_range_full);
|
VideoParams::k_color_range_full);
|
||||||
color_range_combo_->setCurrentIndex(vp.color_range());
|
color_range_combo_->setCurrentIndex(color_range);
|
||||||
|
|
||||||
video_layout->addWidget(color_range_combo_, row, 1);
|
video_layout->addWidget(color_range_combo_, row, 1);
|
||||||
|
|
||||||
@@ -100,7 +121,7 @@ VideoStreamProperties::VideoStreamProperties(Footage *footage, int video_index)
|
|||||||
row++;
|
row++;
|
||||||
|
|
||||||
video_premultiply_alpha_ = new QCheckBox(tr("Premultiplied Alpha"));
|
video_premultiply_alpha_ = new QCheckBox(tr("Premultiplied Alpha"));
|
||||||
video_premultiply_alpha_->setChecked(vp.premultiplied_alpha());
|
video_premultiply_alpha_->setChecked(premultiplied != 0);
|
||||||
video_layout->addWidget(video_premultiply_alpha_, row, 0, 1, 2);
|
video_layout->addWidget(video_premultiply_alpha_, row, 0, 1, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,9 +135,15 @@ VideoStreamProperties::VideoStreamProperties(Footage *footage, int video_index)
|
|||||||
|
|
||||||
imgseq_layout->addWidget(new QLabel(tr("Start Index:")), imgseq_row, 0);
|
imgseq_layout->addWidget(new QLabel(tr("Start Index:")), imgseq_row, 0);
|
||||||
|
|
||||||
|
int64_t seq_start = 0, seq_duration = 0;
|
||||||
|
int fr_num = 0, fr_den = 1;
|
||||||
|
oakengine_footage_get_image_sequence_params(
|
||||||
|
facade_handle, video_index_, &seq_start, &seq_duration, &fr_num,
|
||||||
|
&fr_den);
|
||||||
|
|
||||||
imgseq_start_time_ = new IntegerSlider();
|
imgseq_start_time_ = new IntegerSlider();
|
||||||
imgseq_start_time_->set_minimum(0);
|
imgseq_start_time_->set_minimum(0);
|
||||||
imgseq_start_time_->set_value(vp.start_time());
|
imgseq_start_time_->set_value(seq_start);
|
||||||
imgseq_layout->addWidget(imgseq_start_time_, imgseq_row, 1);
|
imgseq_layout->addWidget(imgseq_start_time_, imgseq_row, 1);
|
||||||
|
|
||||||
imgseq_row++;
|
imgseq_row++;
|
||||||
@@ -125,7 +152,7 @@ VideoStreamProperties::VideoStreamProperties(Footage *footage, int video_index)
|
|||||||
|
|
||||||
imgseq_end_time_ = new IntegerSlider();
|
imgseq_end_time_ = new IntegerSlider();
|
||||||
imgseq_end_time_->set_minimum(0);
|
imgseq_end_time_->set_minimum(0);
|
||||||
imgseq_end_time_->set_value(vp.start_time() + vp.duration() - 1);
|
imgseq_end_time_->set_value(seq_start + seq_duration - 1);
|
||||||
imgseq_layout->addWidget(imgseq_end_time_, imgseq_row, 1);
|
imgseq_layout->addWidget(imgseq_end_time_, imgseq_row, 1);
|
||||||
|
|
||||||
imgseq_row++;
|
imgseq_row++;
|
||||||
@@ -133,15 +160,22 @@ VideoStreamProperties::VideoStreamProperties(Footage *footage, int video_index)
|
|||||||
imgseq_layout->addWidget(new QLabel(tr("Frame Rate:")), imgseq_row, 0);
|
imgseq_layout->addWidget(new QLabel(tr("Frame Rate:")), imgseq_row, 0);
|
||||||
|
|
||||||
imgseq_frame_rate_ = new FrameRateComboBox();
|
imgseq_frame_rate_ = new FrameRateComboBox();
|
||||||
imgseq_frame_rate_->set_frame_rate(vp.frame_rate());
|
imgseq_frame_rate_->set_frame_rate(Rational(fr_num, fr_den));
|
||||||
imgseq_layout->addWidget(imgseq_frame_rate_, imgseq_row, 1);
|
imgseq_layout->addWidget(imgseq_frame_rate_, imgseq_row, 1);
|
||||||
|
|
||||||
video_layout->addWidget(imgseq_group, row, 0, 1, 2);
|
video_layout->addWidget(imgseq_group, row, 0, 1, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
oakengine_footage_free(facade_handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
void VideoStreamProperties::accept(MultiUndoCommand *parent)
|
void VideoStreamProperties::accept(MultiUndoCommand *parent)
|
||||||
{
|
{
|
||||||
|
Q_UNUSED(parent)
|
||||||
|
|
||||||
|
OakEngineFootage *facade_handle = oakengine_footage_borrow(
|
||||||
|
reinterpret_cast<OakEngineNode *>(footage_));
|
||||||
|
|
||||||
QString set_colorspace;
|
QString set_colorspace;
|
||||||
|
|
||||||
if (video_color_space_->currentIndex() > 0) {
|
if (video_color_space_->currentIndex() > 0) {
|
||||||
@@ -150,23 +184,30 @@ void VideoStreamProperties::accept(MultiUndoCommand *parent)
|
|||||||
|
|
||||||
VideoParams vp = footage_->get_video_params(video_index_);
|
VideoParams vp = footage_->get_video_params(video_index_);
|
||||||
|
|
||||||
|
// Write every override through the facade (each call is one undoable
|
||||||
|
// command on the shared undo stack, replacing this dialog's own undo
|
||||||
|
// command classes with identical semantics).
|
||||||
if ((video_premultiply_alpha_ &&
|
if ((video_premultiply_alpha_ &&
|
||||||
video_premultiply_alpha_->isChecked() != vp.premultiplied_alpha()) ||
|
video_premultiply_alpha_->isChecked() != vp.premultiplied_alpha()) ||
|
||||||
set_colorspace != vp.colorspace() ||
|
set_colorspace != vp.colorspace() ||
|
||||||
static_cast<VideoParams::Interlacing>(
|
static_cast<VideoParams::Interlacing>(
|
||||||
video_interlace_combo_->currentIndex()) != vp.interlacing() ||
|
video_interlace_combo_->currentIndex()) != vp.interlacing() ||
|
||||||
pixel_aspect_combo_->get_pixel_aspect_ratio() != vp.pixel_aspect_ratio() ||
|
|
||||||
color_range_combo_->currentData().toInt() != vp.color_range()) {
|
color_range_combo_->currentData().toInt() != vp.color_range()) {
|
||||||
parent->add_child(new VideoStreamChangeCommand(
|
oakengine_footage_set_video_stream_overrides(
|
||||||
footage_, video_index_,
|
facade_handle, video_index_,
|
||||||
video_premultiply_alpha_ ? video_premultiply_alpha_->isChecked() :
|
set_colorspace.toUtf8().constData(),
|
||||||
vp.premultiplied_alpha(),
|
color_range_combo_->currentData().toInt(),
|
||||||
set_colorspace,
|
video_interlace_combo_->currentIndex(),
|
||||||
static_cast<VideoParams::Interlacing>(
|
video_premultiply_alpha_ ?
|
||||||
video_interlace_combo_->currentIndex()),
|
(video_premultiply_alpha_->isChecked() ? 1 : 0) :
|
||||||
pixel_aspect_combo_->get_pixel_aspect_ratio(),
|
-1);
|
||||||
static_cast<VideoParams::ColorRange>(
|
}
|
||||||
color_range_combo_->currentData().toInt())));
|
|
||||||
|
const Rational new_par = pixel_aspect_combo_->get_pixel_aspect_ratio();
|
||||||
|
if (new_par != vp.pixel_aspect_ratio()) {
|
||||||
|
oakengine_footage_set_pixel_aspect(facade_handle, video_index_,
|
||||||
|
new_par.numerator(),
|
||||||
|
new_par.denominator());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (vp.video_type() == VideoParams::k_video_type_image_sequence) {
|
if (vp.video_type() == VideoParams::k_video_type_image_sequence) {
|
||||||
@@ -176,11 +217,15 @@ void VideoStreamProperties::accept(MultiUndoCommand *parent)
|
|||||||
if (vp.start_time() != imgseq_start_time_->get_value() ||
|
if (vp.start_time() != imgseq_start_time_->get_value() ||
|
||||||
vp.duration() != new_dur ||
|
vp.duration() != new_dur ||
|
||||||
vp.frame_rate() != imgseq_frame_rate_->get_frame_rate()) {
|
vp.frame_rate() != imgseq_frame_rate_->get_frame_rate()) {
|
||||||
parent->add_child(new ImageSequenceChangeCommand(
|
const Rational fr = imgseq_frame_rate_->get_frame_rate();
|
||||||
footage_, video_index_, imgseq_start_time_->get_value(), new_dur,
|
oakengine_footage_set_image_sequence_params(
|
||||||
imgseq_frame_rate_->get_frame_rate()));
|
facade_handle, video_index_,
|
||||||
|
imgseq_start_time_->get_value(), new_dur, fr.numerator(),
|
||||||
|
fr.denominator());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
oakengine_footage_free(facade_handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool VideoStreamProperties::sanity_check()
|
bool VideoStreamProperties::sanity_check()
|
||||||
@@ -199,102 +244,4 @@ bool VideoStreamProperties::sanity_check()
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
VideoStreamProperties::VideoStreamChangeCommand::VideoStreamChangeCommand(
|
|
||||||
Footage *footage, int video_index, bool premultiplied, QString colorspace,
|
|
||||||
VideoParams::Interlacing interlacing, const Rational &pixel_ar,
|
|
||||||
VideoParams::ColorRange range)
|
|
||||||
: footage_(footage)
|
|
||||||
, video_index_(video_index)
|
|
||||||
, new_premultiplied_(premultiplied)
|
|
||||||
, new_colorspace_(colorspace)
|
|
||||||
, new_interlacing_(interlacing)
|
|
||||||
, new_pixel_ar_(pixel_ar)
|
|
||||||
, new_range_(range)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
Project *
|
|
||||||
VideoStreamProperties::VideoStreamChangeCommand::get_relevant_project() const
|
|
||||||
{
|
|
||||||
return footage_->project();
|
|
||||||
}
|
|
||||||
|
|
||||||
void VideoStreamProperties::VideoStreamChangeCommand::redo()
|
|
||||||
{
|
|
||||||
VideoParams vp = footage_->get_video_params(video_index_);
|
|
||||||
|
|
||||||
old_premultiplied_ = vp.premultiplied_alpha();
|
|
||||||
old_colorspace_ = vp.colorspace();
|
|
||||||
old_interlacing_ = vp.interlacing();
|
|
||||||
old_pixel_ar_ = vp.pixel_aspect_ratio();
|
|
||||||
old_range_ = vp.color_range();
|
|
||||||
|
|
||||||
vp.set_premultiplied_alpha(new_premultiplied_);
|
|
||||||
vp.set_colorspace(new_colorspace_);
|
|
||||||
vp.set_interlacing(new_interlacing_);
|
|
||||||
vp.set_pixel_aspect_ratio(new_pixel_ar_);
|
|
||||||
vp.set_color_range(new_range_);
|
|
||||||
|
|
||||||
footage_->set_video_params(vp, video_index_);
|
|
||||||
}
|
|
||||||
|
|
||||||
void VideoStreamProperties::VideoStreamChangeCommand::undo()
|
|
||||||
{
|
|
||||||
VideoParams vp = footage_->get_video_params(video_index_);
|
|
||||||
|
|
||||||
vp.set_premultiplied_alpha(old_premultiplied_);
|
|
||||||
vp.set_colorspace(old_colorspace_);
|
|
||||||
vp.set_interlacing(old_interlacing_);
|
|
||||||
vp.set_pixel_aspect_ratio(old_pixel_ar_);
|
|
||||||
vp.set_color_range(old_range_);
|
|
||||||
|
|
||||||
footage_->set_video_params(vp, video_index_);
|
|
||||||
}
|
|
||||||
|
|
||||||
VideoStreamProperties::ImageSequenceChangeCommand::ImageSequenceChangeCommand(
|
|
||||||
Footage *footage, int video_index, int64_t start_index, int64_t duration,
|
|
||||||
const Rational &frame_rate)
|
|
||||||
: footage_(footage)
|
|
||||||
, video_index_(video_index)
|
|
||||||
, new_start_index_(start_index)
|
|
||||||
, new_duration_(duration)
|
|
||||||
, new_frame_rate_(frame_rate)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
Project *
|
|
||||||
VideoStreamProperties::ImageSequenceChangeCommand::get_relevant_project() const
|
|
||||||
{
|
|
||||||
return footage_->project();
|
|
||||||
}
|
|
||||||
|
|
||||||
void VideoStreamProperties::ImageSequenceChangeCommand::redo()
|
|
||||||
{
|
|
||||||
VideoParams vp = footage_->get_video_params(video_index_);
|
|
||||||
|
|
||||||
old_start_index_ = vp.start_time();
|
|
||||||
vp.set_start_time(new_start_index_);
|
|
||||||
|
|
||||||
old_duration_ = vp.duration();
|
|
||||||
vp.set_duration(new_duration_);
|
|
||||||
|
|
||||||
old_frame_rate_ = vp.frame_rate();
|
|
||||||
vp.set_frame_rate(new_frame_rate_);
|
|
||||||
vp.set_time_base(new_frame_rate_.flipped());
|
|
||||||
|
|
||||||
footage_->set_video_params(vp, video_index_);
|
|
||||||
}
|
|
||||||
|
|
||||||
void VideoStreamProperties::ImageSequenceChangeCommand::undo()
|
|
||||||
{
|
|
||||||
VideoParams vp = footage_->get_video_params(video_index_);
|
|
||||||
|
|
||||||
vp.set_start_time(old_start_index_);
|
|
||||||
vp.set_duration(old_duration_);
|
|
||||||
vp.set_frame_rate(old_frame_rate_);
|
|
||||||
vp.set_time_base(old_frame_rate_.flipped());
|
|
||||||
|
|
||||||
footage_->set_video_params(vp, video_index_);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,63 +86,6 @@ private:
|
|||||||
* @brief Sets the pixel aspect ratio of the stream
|
* @brief Sets the pixel aspect ratio of the stream
|
||||||
*/
|
*/
|
||||||
PixelAspectRatioComboBox *pixel_aspect_combo_;
|
PixelAspectRatioComboBox *pixel_aspect_combo_;
|
||||||
|
|
||||||
class VideoStreamChangeCommand : public UndoCommand {
|
|
||||||
public:
|
|
||||||
VideoStreamChangeCommand(Footage *footage, int video_index,
|
|
||||||
bool premultiplied, QString colorspace,
|
|
||||||
VideoParams::Interlacing interlacing,
|
|
||||||
const Rational &pixel_ar,
|
|
||||||
VideoParams::ColorRange range);
|
|
||||||
|
|
||||||
virtual Project *get_relevant_project() const override;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual void redo() override;
|
|
||||||
virtual void undo() override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
Footage *footage_;
|
|
||||||
int video_index_;
|
|
||||||
|
|
||||||
bool new_premultiplied_;
|
|
||||||
QString new_colorspace_;
|
|
||||||
VideoParams::Interlacing new_interlacing_;
|
|
||||||
Rational new_pixel_ar_;
|
|
||||||
VideoParams::ColorRange new_range_;
|
|
||||||
|
|
||||||
bool old_premultiplied_;
|
|
||||||
QString old_colorspace_;
|
|
||||||
VideoParams::Interlacing old_interlacing_;
|
|
||||||
Rational old_pixel_ar_;
|
|
||||||
VideoParams::ColorRange old_range_;
|
|
||||||
};
|
|
||||||
|
|
||||||
class ImageSequenceChangeCommand : public UndoCommand {
|
|
||||||
public:
|
|
||||||
ImageSequenceChangeCommand(Footage *footage, int video_index,
|
|
||||||
int64_t start_index, int64_t duration,
|
|
||||||
const Rational &frame_rate);
|
|
||||||
|
|
||||||
virtual Project *get_relevant_project() const override;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual void redo() override;
|
|
||||||
virtual void undo() override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
Footage *footage_;
|
|
||||||
int video_index_;
|
|
||||||
|
|
||||||
int64_t new_start_index_;
|
|
||||||
int64_t old_start_index_;
|
|
||||||
|
|
||||||
int64_t new_duration_;
|
|
||||||
int64_t old_duration_;
|
|
||||||
|
|
||||||
Rational new_frame_rate_;
|
|
||||||
Rational old_frame_rate_;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,9 @@
|
|||||||
#include "footagerelinkdialog.h"
|
#include "footagerelinkdialog.h"
|
||||||
|
|
||||||
#include "core.h"
|
#include "core.h"
|
||||||
|
#include "oakengine/footage.h"
|
||||||
|
#include "oakengine/node.h"
|
||||||
|
#include "oakengine/project.h"
|
||||||
|
|
||||||
#include <QDialogButtonBox>
|
#include <QDialogButtonBox>
|
||||||
#include <QFileDialog>
|
#include <QFileDialog>
|
||||||
@@ -135,42 +138,59 @@ void FootageRelinkDialog::browse_for_footage()
|
|||||||
QDir original_dir = info.dir();
|
QDir original_dir = info.dir();
|
||||||
QDir new_dir = QFileInfo(new_fn).dir();
|
QDir new_dir = QFileInfo(new_fn).dir();
|
||||||
|
|
||||||
// Set new filename since this was set manually by the user
|
// Relink through the facade (reprobes the file and resets stream /
|
||||||
f->set_filename(new_fn);
|
// proxy state; relinked footage becomes valid when the probe
|
||||||
|
// succeeds).
|
||||||
// Assume footage is valid here. We could do some decoder probing to ensure it's a usable file
|
OakEngineFootage *relink_handle = oakengine_footage_borrow(
|
||||||
// but otherwise we assume the user knows what they're doing here.
|
reinterpret_cast<OakEngineNode *>(f));
|
||||||
|
const int relink_rc = oakengine_footage_relink(
|
||||||
// Set footage to valid and update icon
|
relink_handle, new_fn.toUtf8().constData());
|
||||||
f->set_valid();
|
oakengine_footage_free(relink_handle);
|
||||||
|
if (relink_rc != OAKENGINE_OK) {
|
||||||
|
char err[512];
|
||||||
|
err[0] = '\0';
|
||||||
|
oakengine_footage_last_error(err, sizeof(err));
|
||||||
|
QMessageBox::warning(this, tr("Cannot relink footage"),
|
||||||
|
err[0] ? QString::fromUtf8(err) :
|
||||||
|
tr("The file could not be used as media."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Update item visually
|
// Update item visually
|
||||||
update_footage_item(index);
|
update_footage_item(index);
|
||||||
|
|
||||||
// Check all other footage files for matches
|
// Check all other footage files for matches in the new directory
|
||||||
for (int it = 0; it < footage_.size(); it++) {
|
// (facade's exact file-name matching, mirroring the second attempt
|
||||||
Footage *other_footage = footage_.at(it);
|
// of the old per-footage loop).
|
||||||
|
Project *project = f->project();
|
||||||
|
if (project) {
|
||||||
|
oakengine_project_find_offline_footage(
|
||||||
|
reinterpret_cast<OakEngineProject *>(project),
|
||||||
|
new_dir.absolutePath().toUtf8().constData());
|
||||||
|
|
||||||
// Ignore current footage file and footage that's already valid of course
|
// The old dialog also tried the original directory's relative
|
||||||
if (index != it && !other_footage->is_valid()) {
|
// paths, which the facade's exact-name matching does not cover;
|
||||||
// Get footage path relative to original directory
|
// keep that pass here.
|
||||||
QString relative_to_original =
|
for (int it = 0; it < footage_.size(); it++) {
|
||||||
original_dir.relativeFilePath(other_footage->filename());
|
Footage *other_footage = footage_.at(it);
|
||||||
QString absolute_to_new =
|
|
||||||
new_dir.filePath(relative_to_original);
|
|
||||||
|
|
||||||
// Second attempt. Try appending the filename to our new filepath
|
// Ignore footage that's already valid of course
|
||||||
if (!QFileInfo::exists(absolute_to_new)) {
|
if (!other_footage->is_valid()) {
|
||||||
QFileInfo file_info(other_footage->filename());
|
// Get footage path relative to original directory
|
||||||
absolute_to_new = new_dir.filePath(file_info.fileName());
|
QString relative_to_original =
|
||||||
|
original_dir.relativeFilePath(other_footage->filename());
|
||||||
|
QString absolute_to_new =
|
||||||
|
new_dir.filePath(relative_to_original);
|
||||||
|
|
||||||
|
if (QFileInfo::exists(absolute_to_new)) {
|
||||||
|
other_footage->set_filename(absolute_to_new);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Check if file exists
|
// Refresh every row whose validity may have changed.
|
||||||
if (QFileInfo::exists(absolute_to_new)) {
|
for (int it = 0; it < footage_.size(); it++) {
|
||||||
other_footage->set_filename(absolute_to_new);
|
update_footage_item(it);
|
||||||
other_footage->set_valid();
|
|
||||||
update_footage_item(it);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,8 @@
|
|||||||
#include "dialog/proxy/proxydialog.h"
|
#include "dialog/proxy/proxydialog.h"
|
||||||
#include "dialog/sequence/sequence.h"
|
#include "dialog/sequence/sequence.h"
|
||||||
#include "projectexplorerundo.h"
|
#include "projectexplorerundo.h"
|
||||||
#include "codec/proxymanager.h"
|
#include "oakengine/footage.h"
|
||||||
|
#include "oakengine/node.h"
|
||||||
#include "task/taskmanager.h"
|
#include "task/taskmanager.h"
|
||||||
#include "widget/menu/menu.h"
|
#include "widget/menu/menu.h"
|
||||||
#include "widget/menu/menushared.h"
|
#include "widget/menu/menushared.h"
|
||||||
@@ -63,6 +64,46 @@ QVector<Footage *> get_selected_proxy_footage(const QVector<Node *> &items)
|
|||||||
}
|
}
|
||||||
return footage;
|
return footage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Proxy generation driven by the liboakengine C ABI facade
|
||||||
|
*
|
||||||
|
* Replaces the direct ProxyManager::get_or_start_proxy() drive: the actual
|
||||||
|
* transcode and its synchronous wait live behind
|
||||||
|
* oakengine_footage_proxy_generate() (which also records the proxy state
|
||||||
|
* on the footage and invalidates it), while the task stays on the
|
||||||
|
* TaskManager queue like before.
|
||||||
|
*/
|
||||||
|
class FacadeProxyTask : public Task {
|
||||||
|
public:
|
||||||
|
FacadeProxyTask(Footage *footage)
|
||||||
|
: footage_(footage)
|
||||||
|
{
|
||||||
|
set_title(tr("Generating proxy for \"%1\"")
|
||||||
|
.arg(footage->get_label_or_name()));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual bool run() override
|
||||||
|
{
|
||||||
|
OakEngineFootage *handle = oakengine_footage_borrow(
|
||||||
|
reinterpret_cast<OakEngineNode *>(footage_));
|
||||||
|
const int rc = oakengine_footage_proxy_generate(handle);
|
||||||
|
oakengine_footage_free(handle);
|
||||||
|
if (rc != OAKENGINE_OK) {
|
||||||
|
char err[512];
|
||||||
|
err[0] = '\0';
|
||||||
|
oakengine_footage_last_error(err, sizeof(err));
|
||||||
|
set_error(err[0] ? QString::fromUtf8(err) :
|
||||||
|
tr("Proxy generation failed"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Footage *footage_;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
ProjectExplorer::ProjectExplorer(QWidget *parent)
|
ProjectExplorer::ProjectExplorer(QWidget *parent)
|
||||||
@@ -565,21 +606,31 @@ void ProjectExplorer::replace_selected_footage()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto p = new MultiUndoCommand();
|
// Change the filename through the facade relink (reprobes the new
|
||||||
|
// file and resets proxy/stream state); the label policy stays here.
|
||||||
// Change filename parameter
|
OakEngineFootage *facade_handle = oakengine_footage_borrow(
|
||||||
p->add_child(new NodeParamSetStandardValueCommand(
|
reinterpret_cast<OakEngineNode *>(footage));
|
||||||
NodeKeyframeTrackReference(
|
const int relink_rc = oakengine_footage_relink(
|
||||||
NodeInput(footage, Footage::k_filename_input)),
|
facade_handle, file.toUtf8().constData());
|
||||||
file));
|
oakengine_footage_free(facade_handle);
|
||||||
|
if (relink_rc != OAKENGINE_OK) {
|
||||||
if (QFileInfo(footage->filename()).fileName() == footage->get_label()) {
|
char err[512];
|
||||||
// Footage label == filename, change label too
|
err[0] = '\0';
|
||||||
p->add_child(
|
oakengine_footage_last_error(err, sizeof(err));
|
||||||
new NodeRenameCommand(footage, QFileInfo(file).fileName()));
|
QMessageBox::warning(
|
||||||
|
this, tr("Cannot replace footage"),
|
||||||
|
err[0] ? QString::fromUtf8(err) :
|
||||||
|
tr("The file could not be used as media."));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Core::instance()->undo_stack()->push(p, tr("Replaced Footage"));
|
if (QFileInfo(footage->filename()).fileName() ==
|
||||||
|
footage->get_label()) {
|
||||||
|
// Footage label == filename, change label too
|
||||||
|
oakengine_node_set_label(
|
||||||
|
reinterpret_cast<OakEngineNode *>(footage),
|
||||||
|
QFileInfo(file).fileName().toUtf8().constData());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -597,9 +648,8 @@ void ProjectExplorer::open_context_menu_item_in_new_window()
|
|||||||
|
|
||||||
void ProjectExplorer::generate_proxies_for_selected_footage()
|
void ProjectExplorer::generate_proxies_for_selected_footage()
|
||||||
{
|
{
|
||||||
if (!ProxyManager::instance() || !project()) {
|
if (!project()) {
|
||||||
qWarning()
|
qWarning() << "GenerateProxiesForSelectedFootage: no project";
|
||||||
<< "GenerateProxiesForSelectedFootage: ProxyManager or project unavailable";
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -617,18 +667,9 @@ void ProjectExplorer::generate_proxies_for_selected_footage()
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
ProxyManager::ProxyParams params = item->get_effective_proxy_params();
|
// Queue one facade-backed task per footage item (same queueing
|
||||||
const ProxyManager::Proxy proxy =
|
// semantics as the old per-footage proxy tasks).
|
||||||
ProxyManager::instance()->get_or_start_proxy(
|
TaskManager::instance()->add_task(new FacadeProxyTask(item));
|
||||||
item->project()->cache_path(), item->filename(),
|
|
||||||
video.stream_index(), params);
|
|
||||||
qDebug() << "GenerateProxiesForSelectedFootage: proxy state="
|
|
||||||
<< ProxyManager::proxy_state_to_string(proxy.state)
|
|
||||||
<< "file=" << proxy.filename
|
|
||||||
<< "cache=" << item->project()->cache_path();
|
|
||||||
item->set_proxy(proxy.filename, proxy.state, video.stream_index(),
|
|
||||||
params.version, true);
|
|
||||||
item->invalidate_all(Footage::k_filename_input);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -645,7 +686,12 @@ void ProjectExplorer::set_selected_footage_proxy_enabled(bool enabled)
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
item->set_proxy_enabled(enabled);
|
OakEngineFootage *handle = oakengine_footage_borrow(
|
||||||
|
reinterpret_cast<OakEngineNode *>(item));
|
||||||
|
oakengine_footage_proxy_set_enabled(handle, enabled ? 1 : 0);
|
||||||
|
oakengine_footage_free(handle);
|
||||||
|
// The facade call toggles the flag; cache invalidation for the UI
|
||||||
|
// stays here.
|
||||||
item->invalidate_all(Footage::k_filename_input);
|
item->invalidate_all(Footage::k_filename_input);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -655,13 +701,21 @@ void ProjectExplorer::reveal_proxy_for_selected_footage()
|
|||||||
const QVector<Footage *> footage =
|
const QVector<Footage *> footage =
|
||||||
get_selected_proxy_footage(context_menu_items_);
|
get_selected_proxy_footage(context_menu_items_);
|
||||||
for (Footage *item : footage) {
|
for (Footage *item : footage) {
|
||||||
if (item->proxy_path().isEmpty()) {
|
char proxy_path[4096];
|
||||||
|
proxy_path[0] = '\0';
|
||||||
|
OakEngineFootage *handle = oakengine_footage_borrow(
|
||||||
|
reinterpret_cast<OakEngineNode *>(item));
|
||||||
|
oakengine_footage_proxy_get_path(handle, proxy_path,
|
||||||
|
sizeof(proxy_path));
|
||||||
|
oakengine_footage_free(handle);
|
||||||
|
if (proxy_path[0] == '\0') {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
const QString path = QString::fromUtf8(proxy_path);
|
||||||
|
|
||||||
#if defined(Q_OS_WINDOWS)
|
#if defined(Q_OS_WINDOWS)
|
||||||
QStringList args;
|
QStringList args;
|
||||||
args << "/select," << QDir::toNativeSeparators(item->proxy_path());
|
args << "/select," << QDir::toNativeSeparators(path);
|
||||||
QProcess::startDetached(QStringLiteral("explorer"), args);
|
QProcess::startDetached(QStringLiteral("explorer"), args);
|
||||||
#elif defined(Q_OS_MAC)
|
#elif defined(Q_OS_MAC)
|
||||||
QStringList args;
|
QStringList args;
|
||||||
@@ -670,13 +724,13 @@ void ProjectExplorer::reveal_proxy_for_selected_footage()
|
|||||||
args << "-e";
|
args << "-e";
|
||||||
args << "activate";
|
args << "activate";
|
||||||
args << "-e";
|
args << "-e";
|
||||||
args << "select POSIX file \"" + item->proxy_path() + "\"";
|
args << "select POSIX file \"" + path + "\"";
|
||||||
args << "-e";
|
args << "-e";
|
||||||
args << "end tell";
|
args << "end tell";
|
||||||
QProcess::startDetached(QStringLiteral("osascript"), args);
|
QProcess::startDetached(QStringLiteral("osascript"), args);
|
||||||
#else
|
#else
|
||||||
QDesktopServices::openUrl(QUrl::fromLocalFile(
|
QDesktopServices::openUrl(QUrl::fromLocalFile(
|
||||||
QFileInfo(item->proxy_path()).dir().absolutePath()));
|
QFileInfo(path).dir().absolutePath()));
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -690,9 +744,12 @@ void ProjectExplorer::delete_proxies_for_selected_footage()
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
QFile::remove(item->proxy_path());
|
// Facade delete: removes the file, clears the proxy state and
|
||||||
item->clear_proxy();
|
// invalidates the footage.
|
||||||
item->invalidate_all(Footage::k_filename_input);
|
OakEngineFootage *handle = oakengine_footage_borrow(
|
||||||
|
reinterpret_cast<OakEngineNode *>(item));
|
||||||
|
oakengine_footage_proxy_delete(handle);
|
||||||
|
oakengine_footage_free(handle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
|
|
||||||
#include "export.h"
|
#include "export.h"
|
||||||
#include "init.h"
|
#include "init.h"
|
||||||
|
#include "node.h"
|
||||||
#include "project.h"
|
#include "project.h"
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
@@ -52,6 +53,10 @@ extern "C" {
|
|||||||
* oakengine_footage_free() on a borrowed handle only releases the handle
|
* oakengine_footage_free() on a borrowed handle only releases the handle
|
||||||
* wrapper, never the node.
|
* wrapper, never the node.
|
||||||
*
|
*
|
||||||
|
* - Borrowing (oakengine_footage_borrow()): wrap a Footage node the
|
||||||
|
* caller already holds (e.g. selected in the UI) in the same borrowed
|
||||||
|
* handle model, without probing or importing anything.
|
||||||
|
*
|
||||||
* Image sequences: the application's import asks the user whether numbered
|
* Image sequences: the application's import asks the user whether numbered
|
||||||
* stills form a sequence (EngineCore::confirm_image_sequence_handler). No
|
* stills form a sequence (EngineCore::confirm_image_sequence_handler). No
|
||||||
* such handler exists behind this facade, so imported stills are always
|
* such handler exists behind this facade, so imported stills are always
|
||||||
@@ -200,6 +205,19 @@ OAKENGINE_API int oakengine_footage_get_source_start_time(
|
|||||||
OAKENGINE_API OakEngineFootage *oakengine_project_import_footage(
|
OAKENGINE_API OakEngineFootage *oakengine_project_import_footage(
|
||||||
OakEngineProject *project, const char *path);
|
OakEngineProject *project, const char *path);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Wrap an existing project footage node in a BORROWED handle.
|
||||||
|
*
|
||||||
|
* For callers that already hold an engine node (e.g. the application
|
||||||
|
* wrapping the Footage it has selected) and want to use the media
|
||||||
|
* management / stream override functions below. `node` must be a footage
|
||||||
|
* node (anything else yields NULL, see oakengine_footage_last_error()).
|
||||||
|
* The handle borrows the node -- it becomes invalid when the node's
|
||||||
|
* project is freed, and oakengine_footage_free() only releases the
|
||||||
|
* wrapper.
|
||||||
|
*/
|
||||||
|
OAKENGINE_API OakEngineFootage *oakengine_footage_borrow(OakEngineNode *node);
|
||||||
|
|
||||||
/* ---- Media management: relink and proxies -----------------------------------
|
/* ---- Media management: relink and proxies -----------------------------------
|
||||||
*
|
*
|
||||||
* These functions operate on BORROWED import handles (footage nodes living
|
* These functions operate on BORROWED import handles (footage nodes living
|
||||||
@@ -214,10 +232,10 @@ OAKENGINE_API OakEngineFootage *oakengine_project_import_footage(
|
|||||||
*
|
*
|
||||||
* Calls Footage::set_filename(), which triggers the engine's full reprobe
|
* Calls Footage::set_filename(), which triggers the engine's full reprobe
|
||||||
* cascade (Footage::clear(): streams, decoder link and the proxy state are
|
* cascade (Footage::clear(): streams, decoder link and the proxy state are
|
||||||
* reset before re-probing). Fails with OAKENGINE_E_NOT_FOUND when
|
* reset before re-probing). The footage label is left unchanged. Fails
|
||||||
* `new_path` does not exist, and with OAKENGINE_E_FAILED when the new file
|
* with OAKENGINE_E_NOT_FOUND when `new_path` does not exist, and with
|
||||||
* cannot be probed as media. Not undoable (same as the application's
|
* OAKENGINE_E_FAILED when the new file cannot be probed as media. Not
|
||||||
* relink dialog).
|
* undoable (same as the application's relink action).
|
||||||
*/
|
*/
|
||||||
OAKENGINE_API int oakengine_footage_relink(OakEngineFootage *footage,
|
OAKENGINE_API int oakengine_footage_relink(OakEngineFootage *footage,
|
||||||
const char *new_path);
|
const char *new_path);
|
||||||
@@ -229,8 +247,8 @@ OAKENGINE_API int oakengine_footage_relink(OakEngineFootage *footage,
|
|||||||
* Simplified version of the application's relink dialog matching: each
|
* Simplified version of the application's relink dialog matching: each
|
||||||
* footage whose stored filename does not exist is relinked to
|
* footage whose stored filename does not exist is relinked to
|
||||||
* `search_dir`/<file name> when that file exists (exact file-name match,
|
* `search_dir`/<file name> when that file exists (exact file-name match,
|
||||||
* no recursion). Returns the number of relinked items (>= 0) or a negative
|
* no recursion; labels are left unchanged). Returns the number of
|
||||||
* code.
|
* relinked items (>= 0) or a negative code.
|
||||||
*/
|
*/
|
||||||
OAKENGINE_API int
|
OAKENGINE_API int
|
||||||
oakengine_project_find_offline_footage(OakEngineProject *project,
|
oakengine_project_find_offline_footage(OakEngineProject *project,
|
||||||
@@ -282,6 +300,123 @@ OAKENGINE_API int oakengine_footage_proxy_set_enabled(OakEngineFootage *self,
|
|||||||
OAKENGINE_API int oakengine_footage_proxy_get_path(OakEngineFootage *self,
|
OAKENGINE_API int oakengine_footage_proxy_get_path(OakEngineFootage *self,
|
||||||
char *buf, int buf_size);
|
char *buf, int buf_size);
|
||||||
|
|
||||||
|
/* ---- Stream parameter overrides (footage properties) ------------------------
|
||||||
|
*
|
||||||
|
* Per-stream overrides shown by the application's footage properties
|
||||||
|
* dialog (app/dialog/footageproperties). All setters here are UNDOABLE
|
||||||
|
* (pushed onto the global undo stack as one command each, matching the
|
||||||
|
* dialog's undoable commands; the engine stores these as plain
|
||||||
|
* VideoParams/AudioParams values on the footage node). Getters are direct
|
||||||
|
* reads. Everything in this section requires a borrowed import handle
|
||||||
|
* (probe handles are rejected with OAKENGINE_E_INVALID), and stream
|
||||||
|
* indexes are the index within the stream's own type (0-based).
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Read a video stream's override parameters.
|
||||||
|
*
|
||||||
|
* Fills the current values (VideoParams): colorspace name (buf/size
|
||||||
|
* convention, may be NULL to skip), color_range
|
||||||
|
* (VideoParams::ColorRange), interlacing (VideoParams::Interlacing) and
|
||||||
|
* premultiplied-alpha flag. Any output pointer may be NULL. Returns
|
||||||
|
* OAKENGINE_E_NOT_FOUND for an out-of-range stream index.
|
||||||
|
*/
|
||||||
|
OAKENGINE_API int oakengine_footage_get_video_stream_overrides(
|
||||||
|
OakEngineFootage *self, int stream_index, char *colorspace_buf,
|
||||||
|
int colorspace_size, int *color_range, int *interlacing,
|
||||||
|
int *premultiplied);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Write a video stream's overrides (undoable).
|
||||||
|
*
|
||||||
|
* Pass NULL for `colorspace` and -1 for any int field to leave that field
|
||||||
|
* unchanged. An empty colorspace string ("") clears the override back to
|
||||||
|
* the project's default input color space.
|
||||||
|
*/
|
||||||
|
OAKENGINE_API int oakengine_footage_set_video_stream_overrides(
|
||||||
|
OakEngineFootage *self, int stream_index, const char *colorspace,
|
||||||
|
int color_range, int interlacing, int premultiplied);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Video stream pixel aspect ratio (num/den).
|
||||||
|
*/
|
||||||
|
OAKENGINE_API int oakengine_footage_get_pixel_aspect(OakEngineFootage *self,
|
||||||
|
int stream_index, int *num,
|
||||||
|
int *den);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set the video stream pixel aspect ratio (undoable). Both values
|
||||||
|
* must be > 0.
|
||||||
|
*/
|
||||||
|
OAKENGINE_API int oakengine_footage_set_pixel_aspect(OakEngineFootage *self,
|
||||||
|
int stream_index, int num,
|
||||||
|
int den);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Image-sequence parameters of a video stream: start index,
|
||||||
|
* duration in frames and frame rate (num/den). Returns
|
||||||
|
* OAKENGINE_E_NOT_FOUND for an out-of-range index.
|
||||||
|
*/
|
||||||
|
OAKENGINE_API int oakengine_footage_get_image_sequence_params(
|
||||||
|
OakEngineFootage *self, int stream_index, int64_t *start_index,
|
||||||
|
int64_t *duration, int *frame_rate_num, int *frame_rate_den);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set image-sequence parameters (undoable). `duration` must be > 0
|
||||||
|
* and the frame rate positive.
|
||||||
|
*/
|
||||||
|
OAKENGINE_API int oakengine_footage_set_image_sequence_params(
|
||||||
|
OakEngineFootage *self, int stream_index, int64_t start_index,
|
||||||
|
int64_t duration, int frame_rate_num, int frame_rate_den);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 1 if a stream is enabled (VideoParams/AudioParams/SubtitleParams
|
||||||
|
* ::enabled()). `track_type` is an OAKENGINE_TRACK_TYPE_* value;
|
||||||
|
* OAKENGINE_E_NOT_FOUND for an out-of-range index.
|
||||||
|
*/
|
||||||
|
OAKENGINE_API int oakengine_footage_get_stream_enabled(OakEngineFootage *self,
|
||||||
|
int track_type,
|
||||||
|
int index);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Enable or disable a stream (undoable).
|
||||||
|
*/
|
||||||
|
OAKENGINE_API int oakengine_footage_set_stream_enabled(OakEngineFootage *self,
|
||||||
|
int track_type,
|
||||||
|
int index, int enabled);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set or clear the source start time (undoable; mirrors the
|
||||||
|
* dialog's FootageSetSourceStartTimeCommand with source "manual").
|
||||||
|
* `enabled` == 0 clears it; otherwise the time is num/den seconds.
|
||||||
|
*/
|
||||||
|
OAKENGINE_API int oakengine_footage_set_source_start_time(
|
||||||
|
OakEngineFootage *self, int enabled, int64_t num, int64_t den);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The detection source of the source start time (e.g. "manual" or
|
||||||
|
* the metadata field it was read from; empty when unset). buf/size
|
||||||
|
* convention.
|
||||||
|
*/
|
||||||
|
OAKENGINE_API int oakengine_footage_get_source_start_time_source(
|
||||||
|
OakEngineFootage *self, char *buf, int buf_size);
|
||||||
|
|
||||||
|
/* ---- Colorspace candidates --------------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Number of color spaces in the project's color config (the same
|
||||||
|
* list the footage properties dialog's color space dropdown shows).
|
||||||
|
*/
|
||||||
|
OAKENGINE_API int
|
||||||
|
oakengine_footage_colorspace_count(const OakEngineFootage *self);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Color space name at `index` in the project color config
|
||||||
|
* (buf/size convention). OAKENGINE_E_NOT_FOUND for an out-of-range index.
|
||||||
|
*/
|
||||||
|
OAKENGINE_API int oakengine_footage_colorspace_at(
|
||||||
|
const OakEngineFootage *self, int index, char *buf, int buf_size);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
+512
-5
@@ -149,6 +149,183 @@ olive::Footage *borrowed_node(OakEngineFootage *self)
|
|||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Push an undoable command onto the global undo stack when the engine is
|
||||||
|
// initialized, otherwise execute it directly.
|
||||||
|
void push_or_run(olive::UndoCommand *command, const QString &name)
|
||||||
|
{
|
||||||
|
if (olive::EngineCore::instance()) {
|
||||||
|
olive::EngineCore::instance()->undo_stack()->push(command, name);
|
||||||
|
} else {
|
||||||
|
command->redo_now();
|
||||||
|
delete command;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Undo commands for footage stream overrides. The engine has no undo
|
||||||
|
// commands for these (the application's footage properties dialog carries
|
||||||
|
// them at the app layer), so the facade carries read-modify-write
|
||||||
|
// equivalents with the same semantics.
|
||||||
|
class FootageVideoParamsCommand : public olive::UndoCommand {
|
||||||
|
public:
|
||||||
|
FootageVideoParamsCommand(olive::Footage *footage, int index,
|
||||||
|
const olive::VideoParams ¶ms)
|
||||||
|
: footage_(footage)
|
||||||
|
, index_(index)
|
||||||
|
, new_params_(params)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual olive::Project *get_relevant_project() const override
|
||||||
|
{
|
||||||
|
return footage_->project();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual void redo() override
|
||||||
|
{
|
||||||
|
old_params_ = footage_->get_video_params(index_);
|
||||||
|
footage_->set_video_params(new_params_, index_);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void undo() override
|
||||||
|
{
|
||||||
|
footage_->set_video_params(old_params_, index_);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
olive::Footage *footage_;
|
||||||
|
int index_;
|
||||||
|
olive::VideoParams old_params_;
|
||||||
|
olive::VideoParams new_params_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class FootageAudioParamsCommand : public olive::UndoCommand {
|
||||||
|
public:
|
||||||
|
FootageAudioParamsCommand(olive::Footage *footage, int index,
|
||||||
|
const olive::AudioParams ¶ms)
|
||||||
|
: footage_(footage)
|
||||||
|
, index_(index)
|
||||||
|
, new_params_(params)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual olive::Project *get_relevant_project() const override
|
||||||
|
{
|
||||||
|
return footage_->project();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual void redo() override
|
||||||
|
{
|
||||||
|
old_params_ = footage_->get_audio_params(index_);
|
||||||
|
footage_->set_audio_params(new_params_, index_);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void undo() override
|
||||||
|
{
|
||||||
|
footage_->set_audio_params(old_params_, index_);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
olive::Footage *footage_;
|
||||||
|
int index_;
|
||||||
|
olive::AudioParams old_params_;
|
||||||
|
olive::AudioParams new_params_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class FootageSubtitleParamsCommand : public olive::UndoCommand {
|
||||||
|
public:
|
||||||
|
FootageSubtitleParamsCommand(olive::Footage *footage, int index,
|
||||||
|
const olive::SubtitleParams ¶ms)
|
||||||
|
: footage_(footage)
|
||||||
|
, index_(index)
|
||||||
|
, new_params_(params)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual olive::Project *get_relevant_project() const override
|
||||||
|
{
|
||||||
|
return footage_->project();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual void redo() override
|
||||||
|
{
|
||||||
|
old_params_ = footage_->get_subtitle_params(index_);
|
||||||
|
footage_->set_subtitle_params(new_params_, index_);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void undo() override
|
||||||
|
{
|
||||||
|
footage_->set_subtitle_params(old_params_, index_);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
olive::Footage *footage_;
|
||||||
|
int index_;
|
||||||
|
olive::SubtitleParams old_params_;
|
||||||
|
olive::SubtitleParams new_params_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class FootageSourceStartTimeCommand : public olive::UndoCommand {
|
||||||
|
public:
|
||||||
|
FootageSourceStartTimeCommand(olive::Footage *footage, bool enabled,
|
||||||
|
const olive::Rational &time)
|
||||||
|
: footage_(footage)
|
||||||
|
, new_enabled_(enabled)
|
||||||
|
, new_time_(time)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual olive::Project *get_relevant_project() const override
|
||||||
|
{
|
||||||
|
return footage_->project();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual void redo() override
|
||||||
|
{
|
||||||
|
old_enabled_ = footage_->has_source_start_time();
|
||||||
|
old_time_ = footage_->source_start_time();
|
||||||
|
old_source_ = footage_->source_start_time_source();
|
||||||
|
|
||||||
|
if (new_enabled_) {
|
||||||
|
footage_->set_source_start_time(new_time_,
|
||||||
|
QStringLiteral("manual"));
|
||||||
|
} else {
|
||||||
|
footage_->clear_source_start_time();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void undo() override
|
||||||
|
{
|
||||||
|
if (old_enabled_) {
|
||||||
|
footage_->set_source_start_time(old_time_, old_source_);
|
||||||
|
} else {
|
||||||
|
footage_->clear_source_start_time();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
olive::Footage *footage_;
|
||||||
|
bool new_enabled_;
|
||||||
|
olive::Rational new_time_;
|
||||||
|
bool old_enabled_ = false;
|
||||||
|
olive::Rational old_time_;
|
||||||
|
QString old_source_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Stream access by facade track type; returns false for unknown indexes.
|
||||||
|
bool video_stream_at(const olive::Footage *f, int index,
|
||||||
|
olive::VideoParams *out)
|
||||||
|
{
|
||||||
|
if (index < 0 || index >= f->get_video_stream_count()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
*out = f->get_video_params(index);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
// Internal cross-family accessor (not part of the public C ABI): returns
|
// Internal cross-family accessor (not part of the public C ABI): returns
|
||||||
@@ -383,6 +560,21 @@ OakEngineFootage *oakengine_project_import_footage(OakEngineProject *project,
|
|||||||
return wrap(state);
|
return wrap(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
OakEngineFootage *oakengine_footage_borrow(OakEngineNode *node)
|
||||||
|
{
|
||||||
|
set_error(QString());
|
||||||
|
auto *footage =
|
||||||
|
dynamic_cast<olive::Footage *>(reinterpret_cast<olive::Node *>(node));
|
||||||
|
if (!footage) {
|
||||||
|
set_error(QStringLiteral("node is not a footage node"));
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
auto *state = new OakEngineFootageState();
|
||||||
|
state->borrowed = true;
|
||||||
|
state->node = footage;
|
||||||
|
return wrap(state);
|
||||||
|
}
|
||||||
|
|
||||||
int oakengine_footage_relink(OakEngineFootage *footage, const char *new_path)
|
int oakengine_footage_relink(OakEngineFootage *footage, const char *new_path)
|
||||||
{
|
{
|
||||||
set_error(QString());
|
set_error(QString());
|
||||||
@@ -400,10 +592,11 @@ int oakengine_footage_relink(OakEngineFootage *footage, const char *new_path)
|
|||||||
return OAKENGINE_E_NOT_FOUND;
|
return OAKENGINE_E_NOT_FOUND;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Same as the application's relink dialog: set_filename() triggers
|
// Same as the application's relink action: set_filename() triggers
|
||||||
// clear() (streams, decoder and proxy state reset) and a reprobe.
|
// clear() (streams, decoder and proxy state reset) and a reprobe. The
|
||||||
|
// label is intentionally left alone (relinking changes the path, not
|
||||||
|
// the user's naming).
|
||||||
node->set_filename(path);
|
node->set_filename(path);
|
||||||
node->set_label(QFileInfo(path).fileName());
|
|
||||||
if (!node->is_valid()) {
|
if (!node->is_valid()) {
|
||||||
set_error(QStringLiteral("failed to probe \"%1\" as media")
|
set_error(QStringLiteral("failed to probe \"%1\" as media")
|
||||||
.arg(path));
|
.arg(path));
|
||||||
@@ -438,12 +631,12 @@ int oakengine_project_find_offline_footage(OakEngineProject *project,
|
|||||||
if (filename.isEmpty() || QFileInfo::exists(filename)) {
|
if (filename.isEmpty() || QFileInfo::exists(filename)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// Exact file-name match in the search directory (no recursion).
|
// Exact file-name match in the search directory (no recursion). The
|
||||||
|
// label is intentionally left alone.
|
||||||
const QString candidate =
|
const QString candidate =
|
||||||
dir.filePath(QFileInfo(filename).fileName());
|
dir.filePath(QFileInfo(filename).fileName());
|
||||||
if (QFileInfo::exists(candidate)) {
|
if (QFileInfo::exists(candidate)) {
|
||||||
footage->set_filename(candidate);
|
footage->set_filename(candidate);
|
||||||
footage->set_label(QFileInfo(candidate).fileName());
|
|
||||||
if (footage->is_valid()) {
|
if (footage->is_valid()) {
|
||||||
relinked++;
|
relinked++;
|
||||||
}
|
}
|
||||||
@@ -575,4 +768,318 @@ int oakengine_footage_proxy_get_path(OakEngineFootage *self, char *buf,
|
|||||||
return string_to_buf(impl(self)->node->proxy_path(), buf, buf_size);
|
return string_to_buf(impl(self)->node->proxy_path(), buf, buf_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---- Stream parameter overrides --------------------------------------------- */
|
||||||
|
|
||||||
|
int oakengine_footage_get_video_stream_overrides(
|
||||||
|
OakEngineFootage *self, int stream_index, char *colorspace_buf,
|
||||||
|
int colorspace_size, int *color_range, int *interlacing,
|
||||||
|
int *premultiplied)
|
||||||
|
{
|
||||||
|
set_error(QString());
|
||||||
|
olive::Footage *node = borrowed_node(self);
|
||||||
|
if (!node) {
|
||||||
|
return OAKENGINE_E_INVALID;
|
||||||
|
}
|
||||||
|
olive::VideoParams vp;
|
||||||
|
if (!video_stream_at(node, stream_index, &vp)) {
|
||||||
|
set_error(QStringLiteral("no video stream at index %1")
|
||||||
|
.arg(stream_index));
|
||||||
|
return OAKENGINE_E_NOT_FOUND;
|
||||||
|
}
|
||||||
|
if (colorspace_buf) {
|
||||||
|
string_to_buf(vp.colorspace(), colorspace_buf, colorspace_size);
|
||||||
|
}
|
||||||
|
if (color_range) {
|
||||||
|
*color_range = int(vp.color_range());
|
||||||
|
}
|
||||||
|
if (interlacing) {
|
||||||
|
*interlacing = int(vp.interlacing());
|
||||||
|
}
|
||||||
|
if (premultiplied) {
|
||||||
|
*premultiplied = vp.premultiplied_alpha() ? 1 : 0;
|
||||||
|
}
|
||||||
|
return OAKENGINE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int oakengine_footage_set_video_stream_overrides(
|
||||||
|
OakEngineFootage *self, int stream_index, const char *colorspace,
|
||||||
|
int color_range, int interlacing, int premultiplied)
|
||||||
|
{
|
||||||
|
set_error(QString());
|
||||||
|
olive::Footage *node = borrowed_node(self);
|
||||||
|
if (!node) {
|
||||||
|
return OAKENGINE_E_INVALID;
|
||||||
|
}
|
||||||
|
olive::VideoParams vp;
|
||||||
|
if (!video_stream_at(node, stream_index, &vp)) {
|
||||||
|
set_error(QStringLiteral("no video stream at index %1")
|
||||||
|
.arg(stream_index));
|
||||||
|
return OAKENGINE_E_NOT_FOUND;
|
||||||
|
}
|
||||||
|
if (colorspace) {
|
||||||
|
vp.set_colorspace(QString::fromUtf8(colorspace));
|
||||||
|
}
|
||||||
|
if (color_range >= 0) {
|
||||||
|
vp.set_color_range(
|
||||||
|
static_cast<olive::VideoParams::ColorRange>(color_range));
|
||||||
|
}
|
||||||
|
if (interlacing >= 0) {
|
||||||
|
vp.set_interlacing(
|
||||||
|
static_cast<olive::VideoParams::Interlacing>(interlacing));
|
||||||
|
}
|
||||||
|
if (premultiplied >= 0) {
|
||||||
|
vp.set_premultiplied_alpha(premultiplied != 0);
|
||||||
|
}
|
||||||
|
push_or_run(new FootageVideoParamsCommand(node, stream_index, vp),
|
||||||
|
QStringLiteral("Set Video Stream Overrides"));
|
||||||
|
return OAKENGINE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int oakengine_footage_get_pixel_aspect(OakEngineFootage *self,
|
||||||
|
int stream_index, int *num, int *den)
|
||||||
|
{
|
||||||
|
set_error(QString());
|
||||||
|
olive::Footage *node = borrowed_node(self);
|
||||||
|
if (!node) {
|
||||||
|
return OAKENGINE_E_INVALID;
|
||||||
|
}
|
||||||
|
olive::VideoParams vp;
|
||||||
|
if (!video_stream_at(node, stream_index, &vp)) {
|
||||||
|
set_error(QStringLiteral("no video stream at index %1")
|
||||||
|
.arg(stream_index));
|
||||||
|
return OAKENGINE_E_NOT_FOUND;
|
||||||
|
}
|
||||||
|
if (num) {
|
||||||
|
*num = vp.pixel_aspect_ratio().numerator();
|
||||||
|
}
|
||||||
|
if (den) {
|
||||||
|
*den = vp.pixel_aspect_ratio().denominator();
|
||||||
|
}
|
||||||
|
return OAKENGINE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int oakengine_footage_set_pixel_aspect(OakEngineFootage *self,
|
||||||
|
int stream_index, int num, int den)
|
||||||
|
{
|
||||||
|
set_error(QString());
|
||||||
|
olive::Footage *node = borrowed_node(self);
|
||||||
|
if (!node) {
|
||||||
|
return OAKENGINE_E_INVALID;
|
||||||
|
}
|
||||||
|
if (num <= 0 || den <= 0) {
|
||||||
|
set_error(QStringLiteral("invalid pixel aspect ratio %1/%2")
|
||||||
|
.arg(num)
|
||||||
|
.arg(den));
|
||||||
|
return OAKENGINE_E_INVALID;
|
||||||
|
}
|
||||||
|
olive::VideoParams vp;
|
||||||
|
if (!video_stream_at(node, stream_index, &vp)) {
|
||||||
|
set_error(QStringLiteral("no video stream at index %1")
|
||||||
|
.arg(stream_index));
|
||||||
|
return OAKENGINE_E_NOT_FOUND;
|
||||||
|
}
|
||||||
|
vp.set_pixel_aspect_ratio(olive::Rational(num, den));
|
||||||
|
push_or_run(new FootageVideoParamsCommand(node, stream_index, vp),
|
||||||
|
QStringLiteral("Set Pixel Aspect Ratio"));
|
||||||
|
return OAKENGINE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int oakengine_footage_get_image_sequence_params(
|
||||||
|
OakEngineFootage *self, int stream_index, int64_t *start_index,
|
||||||
|
int64_t *duration, int *frame_rate_num, int *frame_rate_den)
|
||||||
|
{
|
||||||
|
set_error(QString());
|
||||||
|
olive::Footage *node = borrowed_node(self);
|
||||||
|
if (!node) {
|
||||||
|
return OAKENGINE_E_INVALID;
|
||||||
|
}
|
||||||
|
olive::VideoParams vp;
|
||||||
|
if (!video_stream_at(node, stream_index, &vp)) {
|
||||||
|
set_error(QStringLiteral("no video stream at index %1")
|
||||||
|
.arg(stream_index));
|
||||||
|
return OAKENGINE_E_NOT_FOUND;
|
||||||
|
}
|
||||||
|
if (start_index) {
|
||||||
|
*start_index = vp.start_time();
|
||||||
|
}
|
||||||
|
if (duration) {
|
||||||
|
*duration = vp.duration();
|
||||||
|
}
|
||||||
|
if (frame_rate_num) {
|
||||||
|
*frame_rate_num = vp.frame_rate().numerator();
|
||||||
|
}
|
||||||
|
if (frame_rate_den) {
|
||||||
|
*frame_rate_den = vp.frame_rate().denominator();
|
||||||
|
}
|
||||||
|
return OAKENGINE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int oakengine_footage_set_image_sequence_params(
|
||||||
|
OakEngineFootage *self, int stream_index, int64_t start_index,
|
||||||
|
int64_t duration, int frame_rate_num, int frame_rate_den)
|
||||||
|
{
|
||||||
|
set_error(QString());
|
||||||
|
olive::Footage *node = borrowed_node(self);
|
||||||
|
if (!node) {
|
||||||
|
return OAKENGINE_E_INVALID;
|
||||||
|
}
|
||||||
|
if (start_index < 0 || duration <= 0 || frame_rate_num <= 0 ||
|
||||||
|
frame_rate_den <= 0) {
|
||||||
|
set_error(QStringLiteral(
|
||||||
|
"invalid image sequence parameters (need start >= 0, duration > "
|
||||||
|
"0, positive frame rate)"));
|
||||||
|
return OAKENGINE_E_INVALID;
|
||||||
|
}
|
||||||
|
olive::VideoParams vp;
|
||||||
|
if (!video_stream_at(node, stream_index, &vp)) {
|
||||||
|
set_error(QStringLiteral("no video stream at index %1")
|
||||||
|
.arg(stream_index));
|
||||||
|
return OAKENGINE_E_NOT_FOUND;
|
||||||
|
}
|
||||||
|
vp.set_start_time(start_index);
|
||||||
|
vp.set_duration(duration);
|
||||||
|
const olive::Rational frame_rate(frame_rate_num, frame_rate_den);
|
||||||
|
vp.set_frame_rate(frame_rate);
|
||||||
|
vp.set_time_base(frame_rate.flipped());
|
||||||
|
push_or_run(new FootageVideoParamsCommand(node, stream_index, vp),
|
||||||
|
QStringLiteral("Set Image Sequence Parameters"));
|
||||||
|
return OAKENGINE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int oakengine_footage_get_stream_enabled(OakEngineFootage *self,
|
||||||
|
int track_type, int index)
|
||||||
|
{
|
||||||
|
if (!self || !impl(self)->node) {
|
||||||
|
return OAKENGINE_E_INVALID;
|
||||||
|
}
|
||||||
|
const olive::Footage *node = impl(self)->node;
|
||||||
|
switch (track_type) {
|
||||||
|
case 0:
|
||||||
|
if (index >= 0 && index < node->get_video_stream_count()) {
|
||||||
|
return node->get_video_params(index).enabled() ? 1 : 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
if (index >= 0 && index < node->get_audio_stream_count()) {
|
||||||
|
return node->get_audio_params(index).enabled() ? 1 : 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
if (index >= 0 && index < node->get_subtitle_stream_count()) {
|
||||||
|
return node->get_subtitle_params(index).enabled() ? 1 : 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return OAKENGINE_E_NOT_FOUND;
|
||||||
|
}
|
||||||
|
|
||||||
|
int oakengine_footage_set_stream_enabled(OakEngineFootage *self,
|
||||||
|
int track_type, int index,
|
||||||
|
int enabled)
|
||||||
|
{
|
||||||
|
set_error(QString());
|
||||||
|
olive::Footage *node = borrowed_node(self);
|
||||||
|
if (!node) {
|
||||||
|
return OAKENGINE_E_INVALID;
|
||||||
|
}
|
||||||
|
switch (track_type) {
|
||||||
|
case 0:
|
||||||
|
if (index >= 0 && index < node->get_video_stream_count()) {
|
||||||
|
olive::VideoParams vp = node->get_video_params(index);
|
||||||
|
vp.set_enabled(enabled != 0);
|
||||||
|
push_or_run(
|
||||||
|
new FootageVideoParamsCommand(node, index, vp),
|
||||||
|
QStringLiteral("Set Stream Enabled"));
|
||||||
|
return OAKENGINE_OK;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
if (index >= 0 && index < node->get_audio_stream_count()) {
|
||||||
|
olive::AudioParams ap = node->get_audio_params(index);
|
||||||
|
ap.set_enabled(enabled != 0);
|
||||||
|
push_or_run(
|
||||||
|
new FootageAudioParamsCommand(node, index, ap),
|
||||||
|
QStringLiteral("Set Stream Enabled"));
|
||||||
|
return OAKENGINE_OK;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
if (index >= 0 && index < node->get_subtitle_stream_count()) {
|
||||||
|
olive::SubtitleParams sp = node->get_subtitle_params(index);
|
||||||
|
sp.set_enabled(enabled != 0);
|
||||||
|
push_or_run(
|
||||||
|
new FootageSubtitleParamsCommand(node, index, sp),
|
||||||
|
QStringLiteral("Set Stream Enabled"));
|
||||||
|
return OAKENGINE_OK;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
set_error(QStringLiteral("unknown track type %1").arg(track_type));
|
||||||
|
return OAKENGINE_E_INVALID;
|
||||||
|
}
|
||||||
|
set_error(QStringLiteral("no stream of type %1 at index %2")
|
||||||
|
.arg(track_type)
|
||||||
|
.arg(index));
|
||||||
|
return OAKENGINE_E_NOT_FOUND;
|
||||||
|
}
|
||||||
|
|
||||||
|
int oakengine_footage_set_source_start_time(OakEngineFootage *self,
|
||||||
|
int enabled, int64_t num,
|
||||||
|
int64_t den)
|
||||||
|
{
|
||||||
|
set_error(QString());
|
||||||
|
olive::Footage *node = borrowed_node(self);
|
||||||
|
if (!node) {
|
||||||
|
return OAKENGINE_E_INVALID;
|
||||||
|
}
|
||||||
|
if (enabled && den == 0) {
|
||||||
|
set_error(QStringLiteral("invalid time denominator 0"));
|
||||||
|
return OAKENGINE_E_INVALID;
|
||||||
|
}
|
||||||
|
push_or_run(new FootageSourceStartTimeCommand(
|
||||||
|
node, enabled != 0,
|
||||||
|
olive::Rational::from_double(
|
||||||
|
den != 0 ? double(num) / double(den) : 0.0)),
|
||||||
|
QStringLiteral("Set Source Start Time"));
|
||||||
|
return OAKENGINE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int oakengine_footage_get_source_start_time_source(OakEngineFootage *self,
|
||||||
|
char *buf, int buf_size)
|
||||||
|
{
|
||||||
|
if (!self || !impl(self)->node) {
|
||||||
|
return OAKENGINE_E_INVALID;
|
||||||
|
}
|
||||||
|
return string_to_buf(impl(self)->node->source_start_time_source(), buf,
|
||||||
|
buf_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Colorspace candidates ----------------------------------------------------- */
|
||||||
|
|
||||||
|
int oakengine_footage_colorspace_count(const OakEngineFootage *self)
|
||||||
|
{
|
||||||
|
if (!self || !impl(self)->node || !impl(self)->node->project()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return impl(self)->node->project()->color_manager()->get_config()
|
||||||
|
->getNumColorSpaces();
|
||||||
|
}
|
||||||
|
|
||||||
|
int oakengine_footage_colorspace_at(const OakEngineFootage *self, int index,
|
||||||
|
char *buf, int buf_size)
|
||||||
|
{
|
||||||
|
if (!self || !impl(self)->node || !impl(self)->node->project()) {
|
||||||
|
return OAKENGINE_E_INVALID;
|
||||||
|
}
|
||||||
|
const ocio::ConstConfigRcPtr config =
|
||||||
|
impl(self)->node->project()->color_manager()->get_config();
|
||||||
|
if (index < 0 || index >= config->getNumColorSpaces()) {
|
||||||
|
return OAKENGINE_E_NOT_FOUND;
|
||||||
|
}
|
||||||
|
return string_to_buf(config->getColorSpaceNameByIndex(index), buf,
|
||||||
|
buf_size);
|
||||||
|
}
|
||||||
|
|
||||||
} // extern "C"
|
} // extern "C"
|
||||||
|
|||||||
@@ -217,6 +217,10 @@ static void test_failures(void)
|
|||||||
assert(oakengine_project_import_footage(NULL, path) == NULL);
|
assert(oakengine_project_import_footage(NULL, path) == NULL);
|
||||||
oakengine_project_free(project);
|
oakengine_project_free(project);
|
||||||
|
|
||||||
|
// Borrowing nothing yields no handle.
|
||||||
|
assert(oakengine_footage_borrow(NULL) == NULL);
|
||||||
|
assert(oakengine_footage_last_error(err, sizeof(err)) > 0);
|
||||||
|
|
||||||
// NULL safety.
|
// NULL safety.
|
||||||
oakengine_footage_free(NULL);
|
oakengine_footage_free(NULL);
|
||||||
assert(oakengine_footage_get_decoder_name(NULL, err, sizeof(err)) ==
|
assert(oakengine_footage_get_decoder_name(NULL, err, sizeof(err)) ==
|
||||||
@@ -409,6 +413,181 @@ static void test_proxy(void)
|
|||||||
oakengine_project_free(project);
|
oakengine_project_free(project);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void test_stream_overrides(void)
|
||||||
|
{
|
||||||
|
OakEngineProject *project = oakengine_project_create();
|
||||||
|
assert(project != NULL);
|
||||||
|
assert(oakengine_project_new(project) == OAKENGINE_OK);
|
||||||
|
|
||||||
|
char path[4096];
|
||||||
|
demo_path(path, sizeof(path));
|
||||||
|
OakEngineFootage *f = oakengine_project_import_footage(project, path);
|
||||||
|
assert(f != NULL);
|
||||||
|
|
||||||
|
char cs[128];
|
||||||
|
int range = -1, interlace = -1, premult = -1;
|
||||||
|
|
||||||
|
// Defaults from the probe.
|
||||||
|
assert(oakengine_footage_get_video_stream_overrides(
|
||||||
|
f, 0, cs, sizeof(cs), &range, &interlace, &premult) ==
|
||||||
|
OAKENGINE_OK);
|
||||||
|
assert(range == 0); // limited
|
||||||
|
assert(interlace == 0); // progressive
|
||||||
|
assert(premult == 0);
|
||||||
|
|
||||||
|
// Full override write + read-back + undo/redo.
|
||||||
|
assert(oakengine_footage_set_video_stream_overrides(
|
||||||
|
f, 0, "Rec.709 OETF", 1, 1, 1) == OAKENGINE_OK);
|
||||||
|
assert(oakengine_footage_get_video_stream_overrides(
|
||||||
|
f, 0, cs, sizeof(cs), &range, &interlace, &premult) ==
|
||||||
|
OAKENGINE_OK);
|
||||||
|
assert(strcmp(cs, "Rec.709 OETF") == 0);
|
||||||
|
assert(range == 1 && interlace == 1 && premult == 1);
|
||||||
|
assert(oakengine_project_undo(project) == OAKENGINE_OK);
|
||||||
|
assert(oakengine_footage_get_video_stream_overrides(
|
||||||
|
f, 0, cs, sizeof(cs), &range, &interlace, &premult) ==
|
||||||
|
OAKENGINE_OK);
|
||||||
|
assert(range == 0 && interlace == 0 && premult == 0);
|
||||||
|
assert(oakengine_project_redo(project) == OAKENGINE_OK);
|
||||||
|
assert(oakengine_footage_get_video_stream_overrides(
|
||||||
|
f, 0, cs, sizeof(cs), &range, &interlace, &premult) ==
|
||||||
|
OAKENGINE_OK);
|
||||||
|
assert(range == 1 && interlace == 1 && premult == 1);
|
||||||
|
|
||||||
|
// Partial write: NULL/-1 leaves fields alone.
|
||||||
|
assert(oakengine_footage_set_video_stream_overrides(f, 0, NULL, 0, -1,
|
||||||
|
-1) == OAKENGINE_OK);
|
||||||
|
assert(oakengine_footage_get_video_stream_overrides(
|
||||||
|
f, 0, cs, sizeof(cs), &range, &interlace, &premult) ==
|
||||||
|
OAKENGINE_OK);
|
||||||
|
assert(range == 0 && interlace == 1 && premult == 1);
|
||||||
|
|
||||||
|
// Pixel aspect ratio.
|
||||||
|
int num = 0, den = 0;
|
||||||
|
assert(oakengine_footage_get_pixel_aspect(f, 0, &num, &den) ==
|
||||||
|
OAKENGINE_OK);
|
||||||
|
assert(num == 1 && den == 1);
|
||||||
|
assert(oakengine_footage_set_pixel_aspect(f, 0, 4, 3) == OAKENGINE_OK);
|
||||||
|
assert(oakengine_footage_get_pixel_aspect(f, 0, &num, &den) ==
|
||||||
|
OAKENGINE_OK);
|
||||||
|
assert(num == 4 && den == 3);
|
||||||
|
assert(oakengine_footage_set_pixel_aspect(f, 0, 0, 3) ==
|
||||||
|
OAKENGINE_E_INVALID);
|
||||||
|
assert(oakengine_project_undo(project) == OAKENGINE_OK);
|
||||||
|
assert(oakengine_footage_get_pixel_aspect(f, 0, &num, &den) ==
|
||||||
|
OAKENGINE_OK);
|
||||||
|
assert(num == 1 && den == 1);
|
||||||
|
|
||||||
|
// Image-sequence parameters round-trip (stored even for non-sequence
|
||||||
|
// footage; the dialog shows them only for image sequences).
|
||||||
|
int64_t start = -1, dur = -1;
|
||||||
|
assert(oakengine_footage_get_image_sequence_params(f, 0, &start, &dur,
|
||||||
|
&num, &den) ==
|
||||||
|
OAKENGINE_OK);
|
||||||
|
assert(oakengine_footage_set_image_sequence_params(f, 0, 10, 100, 25,
|
||||||
|
1) == OAKENGINE_OK);
|
||||||
|
assert(oakengine_footage_get_image_sequence_params(f, 0, &start, &dur,
|
||||||
|
&num, &den) ==
|
||||||
|
OAKENGINE_OK);
|
||||||
|
assert(start == 10 && dur == 100 && num == 25 && den == 1);
|
||||||
|
assert(oakengine_footage_set_image_sequence_params(f, 0, 0, 0, 25, 1) ==
|
||||||
|
OAKENGINE_E_INVALID);
|
||||||
|
assert(oakengine_project_undo(project) == OAKENGINE_OK);
|
||||||
|
|
||||||
|
// Stream enable toggles.
|
||||||
|
assert(oakengine_footage_get_stream_enabled(f, 0, 0) == 1);
|
||||||
|
assert(oakengine_footage_set_stream_enabled(f, 0, 0, 0) == OAKENGINE_OK);
|
||||||
|
assert(oakengine_footage_get_stream_enabled(f, 0, 0) == 0);
|
||||||
|
assert(oakengine_footage_set_stream_enabled(f, 0, 0, 1) == OAKENGINE_OK);
|
||||||
|
assert(oakengine_footage_get_stream_enabled(f, 0, 0) == 1);
|
||||||
|
assert(oakengine_project_undo(project) == OAKENGINE_OK);
|
||||||
|
assert(oakengine_footage_get_stream_enabled(f, 0, 0) == 0);
|
||||||
|
assert(oakengine_footage_get_stream_enabled(f, 1, 0) == 1);
|
||||||
|
assert(oakengine_footage_get_stream_enabled(f, 0, 99) ==
|
||||||
|
OAKENGINE_E_NOT_FOUND);
|
||||||
|
assert(oakengine_footage_set_stream_enabled(f, 9, 0, 1) ==
|
||||||
|
OAKENGINE_E_INVALID);
|
||||||
|
|
||||||
|
// Source start time set/clear with source label.
|
||||||
|
int snum = 0, sden = 0;
|
||||||
|
assert(oakengine_footage_get_source_start_time(f, &snum, &sden) == 1);
|
||||||
|
char source[64];
|
||||||
|
assert(oakengine_footage_set_source_start_time(f, 1, 42, 1) ==
|
||||||
|
OAKENGINE_OK);
|
||||||
|
assert(oakengine_footage_get_source_start_time(f, &snum, &sden) == 1);
|
||||||
|
assert(snum == 42 && sden == 1);
|
||||||
|
assert(oakengine_footage_get_source_start_time_source(f, source,
|
||||||
|
sizeof(source)) > 0);
|
||||||
|
assert(strcmp(source, "manual") == 0);
|
||||||
|
assert(oakengine_footage_set_source_start_time(f, 0, 0, 1) ==
|
||||||
|
OAKENGINE_OK);
|
||||||
|
assert(oakengine_footage_get_source_start_time(f, &snum, &sden) == 0);
|
||||||
|
assert(oakengine_project_undo(project) == OAKENGINE_OK);
|
||||||
|
assert(oakengine_footage_get_source_start_time(f, &snum, &sden) == 1);
|
||||||
|
assert(snum == 42);
|
||||||
|
|
||||||
|
// Out-of-range and NULL.
|
||||||
|
assert(oakengine_footage_get_video_stream_overrides(
|
||||||
|
f, 9, cs, sizeof(cs), &range, &interlace, &premult) ==
|
||||||
|
OAKENGINE_E_NOT_FOUND);
|
||||||
|
assert(oakengine_footage_set_video_stream_overrides(f, 9, "x", 0, 0,
|
||||||
|
0) ==
|
||||||
|
OAKENGINE_E_NOT_FOUND);
|
||||||
|
assert(oakengine_footage_get_pixel_aspect(f, 9, &num, &den) ==
|
||||||
|
OAKENGINE_E_NOT_FOUND);
|
||||||
|
assert(oakengine_footage_get_image_sequence_params(f, 9, &start, &dur,
|
||||||
|
&num, &den) ==
|
||||||
|
OAKENGINE_E_NOT_FOUND);
|
||||||
|
assert(oakengine_footage_get_video_stream_overrides(
|
||||||
|
NULL, 0, cs, sizeof(cs), &range, &interlace, &premult) ==
|
||||||
|
OAKENGINE_E_INVALID);
|
||||||
|
OakEngineFootage *probed = oakengine_footage_probe(path);
|
||||||
|
assert(probed != NULL);
|
||||||
|
assert(oakengine_footage_set_video_stream_overrides(probed, 0, "x", 0,
|
||||||
|
0, 0) ==
|
||||||
|
OAKENGINE_E_INVALID);
|
||||||
|
assert(oakengine_footage_colorspace_count(probed) == 0);
|
||||||
|
oakengine_footage_free(probed);
|
||||||
|
|
||||||
|
oakengine_footage_free(f);
|
||||||
|
oakengine_project_free(project);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_colorspace_candidates(void)
|
||||||
|
{
|
||||||
|
OakEngineProject *project = oakengine_project_create();
|
||||||
|
assert(project != NULL);
|
||||||
|
assert(oakengine_project_new(project) == OAKENGINE_OK);
|
||||||
|
|
||||||
|
char path[4096];
|
||||||
|
demo_path(path, sizeof(path));
|
||||||
|
OakEngineFootage *f = oakengine_project_import_footage(project, path);
|
||||||
|
assert(f != NULL);
|
||||||
|
|
||||||
|
const int count = oakengine_footage_colorspace_count(f);
|
||||||
|
assert(count > 0);
|
||||||
|
char name[128];
|
||||||
|
int found_rec709 = 0;
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
assert(oakengine_footage_colorspace_at(f, i, name, sizeof(name)) > 0);
|
||||||
|
assert(strlen(name) > 0);
|
||||||
|
if (strcmp(name, "Rec.709 OETF") == 0) {
|
||||||
|
found_rec709 = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert(found_rec709 == 1);
|
||||||
|
assert(oakengine_footage_colorspace_at(f, count, name, sizeof(name)) ==
|
||||||
|
OAKENGINE_E_NOT_FOUND);
|
||||||
|
assert(oakengine_footage_colorspace_at(f, -1, name, sizeof(name)) ==
|
||||||
|
OAKENGINE_E_NOT_FOUND);
|
||||||
|
assert(oakengine_footage_colorspace_at(NULL, 0, name, sizeof(name)) ==
|
||||||
|
OAKENGINE_E_INVALID);
|
||||||
|
assert(oakengine_footage_colorspace_count(NULL) == 0);
|
||||||
|
|
||||||
|
oakengine_footage_free(f);
|
||||||
|
oakengine_project_free(project);
|
||||||
|
}
|
||||||
|
|
||||||
int main(void)
|
int main(void)
|
||||||
{
|
{
|
||||||
make_tmpdir();
|
make_tmpdir();
|
||||||
@@ -429,6 +608,8 @@ int main(void)
|
|||||||
test_relink();
|
test_relink();
|
||||||
test_find_offline();
|
test_find_offline();
|
||||||
test_proxy();
|
test_proxy();
|
||||||
|
test_stream_overrides();
|
||||||
|
test_colorspace_candidates();
|
||||||
|
|
||||||
assert(oakengine_shutdown() == OAKENGINE_OK);
|
assert(oakengine_shutdown() == OAKENGINE_OK);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user