engine: sequence parameters dialog migrates to the facade

- new facade API: video params ex (dimensions/rate/par/interlacing/
  preview format/divider), audio params, preview divider, and
  undoable-flagged setters mirroring the dialog's dual undo/no-undo
  modes; label setting gains node_set_label_ex
- the dialog's own SequenceParamCommand is gone; accept now issues
  facade calls (per-field commands, unchanged fields skipped)
- preset system stays UI-side by design: its flat XML schema never
  touches engine objects
- the auto-cache checkbox maps to the engine's existing stub (no undo
  noise)
This commit is contained in:
2026-07-20 12:42:54 +08:00
parent 456060ef3e
commit 0a25d43218
8 changed files with 652 additions and 130 deletions
+32 -80
View File
@@ -31,10 +31,10 @@
#include <QVBoxLayout>
#include "config/config.h"
#include "core.h"
#include "common/qtutils.h"
#include "dialog/msgbox.h"
#include "undo/undostack.h"
#include "oakengine/node.h"
#include "oakengine/timeline.h"
namespace olive
{
@@ -139,40 +139,36 @@ void SequenceDialog::accept()
}
}
// Generate video and audio parameter structs from data
VideoParams video_params =
VideoParams(parameter_tab_->get_selected_video_width(),
parameter_tab_->get_selected_video_height(),
parameter_tab_->get_selected_video_frame_rate().flipped(),
parameter_tab_->get_selected_preview_format(),
VideoParams::k_internal_channel_count,
parameter_tab_->get_selected_video_pixel_aspect(),
parameter_tab_->get_selected_video_interlacing_mode(),
parameter_tab_->get_selected_preview_resolution());
AudioParams audio_params =
AudioParams(parameter_tab_->get_selected_audio_sample_rate(),
parameter_tab_->get_selected_audio_channel_layout(),
Sequence::k_default_sample_format);
if (make_undoable_) {
// Make undoable command to change the parameters
SequenceParamCommand *param_command = new SequenceParamCommand(
sequence_, video_params, audio_params, name_field_->text(),
parameter_tab_->get_selected_preview_auto_cache());
Core::instance()->undo_stack()->push(
param_command,
tr("Set Sequence Parameters For \"%1\"").arg(sequence_->get_label()));
} else {
// Set sequence values directly with no undo command
sequence_->set_video_params(video_params);
sequence_->set_audio_params(audio_params);
sequence_->set_label(name_field_->text());
sequence_->set_video_auto_cache_enabled(
parameter_tab_->get_selected_preview_auto_cache());
}
// All parameter writes go through the liboakengine C ABI facade; in
// undoable mode each call lands on the shared undo stack as one command,
// otherwise it applies directly (the flag mirrors the old
// SequenceParamCommand / direct-set split).
const int undoable = make_undoable_ ? 1 : 0;
OakEngineSequence *facade_handle =
reinterpret_cast<OakEngineSequence *>(sequence_);
const Rational frame_rate = parameter_tab_->get_selected_video_frame_rate();
const Rational pixel_aspect =
parameter_tab_->get_selected_video_pixel_aspect();
oakengine_sequence_set_video_params(
facade_handle, parameter_tab_->get_selected_video_width(),
parameter_tab_->get_selected_video_height(), frame_rate.numerator(),
frame_rate.denominator(), pixel_aspect.numerator(),
pixel_aspect.denominator(),
int(parameter_tab_->get_selected_video_interlacing_mode()),
int(parameter_tab_->get_selected_preview_format()), undoable);
oakengine_sequence_set_preview_divider(
facade_handle, parameter_tab_->get_selected_preview_resolution(),
undoable);
oakengine_sequence_set_audio_params(
facade_handle, parameter_tab_->get_selected_audio_sample_rate(),
parameter_tab_->get_selected_audio_channel_layout(), undoable);
oakengine_node_set_label_ex(
reinterpret_cast<OakEngineNode *>(sequence_),
name_field_->text().toUtf8().constData(), undoable);
oakengine_sequence_set_video_auto_cache(
facade_handle, parameter_tab_->get_selected_preview_auto_cache() ? 1 :
0,
undoable);
QDialog::accept();
}
@@ -201,48 +197,4 @@ void SequenceDialog::set_as_default_clicked()
}
}
SequenceDialog::SequenceParamCommand::SequenceParamCommand(
Sequence *s, const VideoParams &video_params,
const AudioParams &audio_params, const QString &name, bool autocache)
: sequence_(s)
, new_video_params_(video_params)
, new_audio_params_(audio_params)
, new_name_(name)
, new_autocache_(autocache)
, old_video_params_(s->get_video_params())
, old_audio_params_(s->get_audio_params())
, old_name_(s->get_label())
, old_autocache_(s->is_video_auto_cache_enabled())
{
}
Project *SequenceDialog::SequenceParamCommand::get_relevant_project() const
{
return sequence_->project();
}
void SequenceDialog::SequenceParamCommand::redo()
{
if (sequence_->get_video_params() != new_video_params_) {
sequence_->set_video_params(new_video_params_);
}
if (sequence_->get_audio_params() != new_audio_params_) {
sequence_->set_audio_params(new_audio_params_);
}
sequence_->set_label(new_name_);
sequence_->set_video_auto_cache_enabled(new_autocache_);
}
void SequenceDialog::SequenceParamCommand::undo()
{
if (sequence_->get_video_params() != old_video_params_) {
sequence_->set_video_params(old_video_params_);
}
if (sequence_->get_audio_params() != old_audio_params_) {
sequence_->set_audio_params(old_audio_params_);
}
sequence_->set_label(old_name_);
sequence_->set_video_auto_cache_enabled(old_autocache_);
}
}
-30
View File
@@ -29,7 +29,6 @@
#include "node/project/sequence/sequence.h"
#include "sequencedialogparametertab.h"
#include "sequencedialogpresettab.h"
#include "undo/undocommand.h"
namespace olive
{
@@ -101,35 +100,6 @@ private:
QLineEdit *name_field_;
/**
* @brief An UndoCommand for setting the parameters on a sequence
*/
class SequenceParamCommand : public UndoCommand {
public:
SequenceParamCommand(Sequence *s, const VideoParams &video_params,
const AudioParams &audio_params,
const QString &name, bool autocache);
virtual Project *get_relevant_project() const override;
protected:
virtual void redo() override;
virtual void undo() override;
private:
Sequence *sequence_;
VideoParams new_video_params_;
AudioParams new_audio_params_;
QString new_name_;
bool new_autocache_;
VideoParams old_video_params_;
AudioParams old_audio_params_;
QString old_name_;
bool old_autocache_;
};
private slots:
void set_as_default_clicked();
};
@@ -23,6 +23,8 @@
#include <QPushButton>
#include <QVBoxLayout>
#include "oakengine/timeline.h"
namespace olive
{
@@ -100,19 +102,35 @@ SequenceDialogParameterTab::SequenceDialogParameterTab(Sequence *sequence,
layout->addWidget(preview_group);
// Set values based on input sequence
VideoParams vp = sequence->get_video_params();
AudioParams ap = sequence->get_audio_params();
width_slider_->set_value(vp.width());
height_slider_->set_value(vp.height());
framerate_combo_->set_frame_rate(vp.time_base().flipped());
pixelaspect_combo_->set_pixel_aspect_ratio(vp.pixel_aspect_ratio());
interlacing_combo_->set_interlace_mode(vp.interlacing());
preview_resolution_field_->set_divider(vp.divider());
preview_format_field_->set_pixel_format(vp.format());
preview_autocache_field_->setChecked(sequence->is_video_auto_cache_enabled());
audio_sample_rate_field_->set_sample_rate(ap.sample_rate());
audio_channels_field_->set_channel_layout(ap.channel_layout());
// Set values based on input sequence; the reads go through the
// liboakengine C ABI facade (the sequence handle is the engine node
// pointer in this family).
OakEngineSequence *facade_handle =
reinterpret_cast<OakEngineSequence *>(sequence);
int width = 0, height = 0, fps_num = 0, fps_den = 1, par_num = 1,
par_den = 1, interlacing = 0, format = 0, divider = 1;
oakengine_sequence_get_video_params_ex(facade_handle, &width, &height,
&fps_num, &fps_den, &par_num,
&par_den, &interlacing, &format,
&divider);
int sample_rate = 0;
uint64_t channel_layout = 0;
oakengine_sequence_get_audio_params(facade_handle, &sample_rate,
&channel_layout);
width_slider_->set_value(width);
height_slider_->set_value(height);
framerate_combo_->set_frame_rate(Rational(fps_num, fps_den));
pixelaspect_combo_->set_pixel_aspect_ratio(Rational(par_num, par_den));
interlacing_combo_->set_interlace_mode(
static_cast<VideoParams::Interlacing>(interlacing));
preview_resolution_field_->set_divider(divider);
preview_format_field_->set_pixel_format(
static_cast<PixelFormat::Format>(format));
preview_autocache_field_->setChecked(
oakengine_sequence_get_video_auto_cache(facade_handle) != 0);
audio_sample_rate_field_->set_sample_rate(sample_rate);
audio_channels_field_->set_channel_layout(channel_layout);
connect(
preview_resolution_field_,
+11
View File
@@ -140,6 +140,17 @@ OAKENGINE_API int oakengine_node_get_label(const OakEngineNode *self,
OAKENGINE_API int oakengine_node_set_label(OakEngineNode *self,
const char *label);
/**
* @brief Set the node's user label with an explicit undoable flag.
*
* `undoable` != 0 behaves like oakengine_node_set_label() (one undoable
* NodeRenameCommand on the global undo stack, degrading to a direct
* rename when the engine is not initialized); 0 renames directly with no
* undo entry.
*/
OAKENGINE_API int oakengine_node_set_label_ex(OakEngineNode *self,
const char *label, int undoable);
/* ---- Input introspection ---------------------------------------------------- */
/**
+98 -4
View File
@@ -37,10 +37,10 @@ extern "C" {
* @brief C ABI for sequences (Oak timelines)
*
* An OakEngineSequence wraps the engine's olive::Sequence node
* (engine/node/project/sequence/sequence.h, a ViewerOutput). This family is
* intentionally read-mostly this round: playhead and workarea are simple
* writes, everything else is inspection. Clip/track editing is a later
* milestone.
* (engine/node/project/sequence/sequence.h, a ViewerOutput). Most of the
* family is inspection; playhead, workarea, the sequence parameters
* (video/audio/preview/auto-cache, see below) and clip/track editing are
* writes, undoable unless documented otherwise.
*
* Handles are borrowed from their owning OakEngineProject (Qt QObject parent
* chain; sequences are added to the project graph which becomes their
@@ -118,6 +118,100 @@ OAKENGINE_API int
oakengine_sequence_get_video_params(const OakEngineSequence *self, int *width,
int *height, int *par_num, int *par_den);
/* ---- Sequence parameters (sequence dialog) ------------------------------------
*
* The parameter set shown by the application's sequence dialog
* (app/dialog/sequence): video size/frame rate/pixel aspect/interlacing,
* preview pixel format and resolution divider, audio sample rate/channel
* layout, and the video auto-cache flag. Setters take an `undoable` flag:
* 1 pushes one undoable command onto the global undo stack (direct
* application when the engine is not initialized), 0 applies directly with
* no undo entry -- mirroring the dialog's two modes (SetUndoable()).
* Fields pass -1 (ints/rationals) or <= 0 / 0 (audio) to leave them
* unchanged; a call that changes nothing pushes no command and returns
* OAKENGINE_OK. The video channel count and the audio sample format are
* internal constants in the engine (VideoParams::k_internal_channel_count,
* ViewerOutput::k_default_sample_format) and are kept as-is.
*/
/**
* @brief Full read of the sequence's video parameters
* (ViewerOutput::get_video_params()). Any output pointer may be NULL.
* `fps_*` is the frame rate (the time base flipped), `format` a
* PixelFormat::Format value, `divider` the preview resolution divider.
*/
OAKENGINE_API int oakengine_sequence_get_video_params_ex(
const OakEngineSequence *self, int *width, int *height, int *fps_num,
int *fps_den, int *par_num, int *par_den, int *interlacing, int *format,
int *divider);
/**
* @brief Write the sequence's video parameters (see the section comment
* for the undoable/unchanged conventions).
*
* -1 leaves a field unchanged; both fps and pixel-aspect must be given as
* num/den pairs (both -1 or both > 0). `interlacing` is a
* VideoParams::Interlacing value (0..2), `format` a PixelFormat::Format
* value (0..PixelFormat::count-1). Invalid values yield
* OAKENGINE_E_INVALID. The frame rate is stored as its flipped time base
* exactly like the application's dialog (VideoParams constructor), so the
* derived effective size and frame_rate stay in sync.
*/
OAKENGINE_API int oakengine_sequence_set_video_params(
OakEngineSequence *self, int width, int height, int fps_num, int fps_den,
int par_num, int par_den, int interlacing, int format, int undoable);
/**
* @brief Sequence audio sample rate and channel layout
* (ViewerOutput::get_audio_params()). Any output pointer may be NULL.
*/
OAKENGINE_API int oakengine_sequence_get_audio_params(
const OakEngineSequence *self, int *sample_rate,
uint64_t *channel_layout);
/**
* @brief Write the sequence's audio parameters (undoable flag as above).
* `sample_rate` <= 0 or `channel_layout` == 0 leaves the field unchanged.
* The sample format is kept unchanged (the dialog always uses
* ViewerOutput::k_default_sample_format).
*/
OAKENGINE_API int oakengine_sequence_set_audio_params(
OakEngineSequence *self, int sample_rate, uint64_t channel_layout,
int undoable);
/**
* @brief Preview resolution divider (VideoParams::divider()). 0 on a NULL
* handle.
*/
OAKENGINE_API int
oakengine_sequence_get_preview_divider(const OakEngineSequence *self);
/**
* @brief Set the preview resolution divider (undoable flag as above).
* `divider` < 1 yields OAKENGINE_E_INVALID.
*/
OAKENGINE_API int oakengine_sequence_set_preview_divider(
OakEngineSequence *self, int divider, int undoable);
/**
* @brief 1 when video auto-cache is enabled
* (ViewerOutput::is_video_auto_cache_enabled()). 0 on a NULL handle.
*/
OAKENGINE_API int
oakengine_sequence_get_video_auto_cache(const OakEngineSequence *self);
/**
* @brief Enable or disable video auto-cache.
*
* The engine's auto-cache accessors are currently stubs (the read always
* reports 0, the write is ignored; the application's dialog has the
* checkbox TEMP-disabled for the same reason). This forwards to the stub
* without an undo command so the ABI is ready when the engine
* implementation lands. `undoable` is accepted for symmetry and ignored.
*/
OAKENGINE_API int oakengine_sequence_set_video_auto_cache(
OakEngineSequence *self, int enabled, int undoable);
/**
* @brief Number of tracks per track type (Sequence::track_list(type)->
* get_track_count()). Any of `video`/`audio`/`subtitle` may be NULL.
+14 -3
View File
@@ -487,15 +487,26 @@ int oakengine_node_get_label(const OakEngineNode *self, char *buf,
}
int oakengine_node_set_label(OakEngineNode *self, const char *label)
{
return oakengine_node_set_label_ex(self, label, 1);
}
int oakengine_node_set_label_ex(OakEngineNode *self, const char *label,
int undoable)
{
set_error(QString());
if (!self) {
set_error(QStringLiteral("invalid node"));
return OAKENGINE_E_INVALID;
}
push_or_run(new olive::NodeRenameCommand(
impl(self), QString::fromUtf8(label ? label : "")),
QStringLiteral("Rename Node"));
olive::UndoCommand *command = new olive::NodeRenameCommand(
impl(self), QString::fromUtf8(label ? label : ""));
if (undoable) {
push_or_run(command, QStringLiteral("Rename Node"));
} else {
command->redo_now();
delete command;
}
return OAKENGINE_OK;
}
+296
View File
@@ -152,6 +152,88 @@ void push_or_run(olive::UndoCommand *command, const QString &name)
}
}
// Apply a command honoring an explicit undoable flag: 1 pushes through
// push_or_run(), 0 applies directly with no undo entry (the sequence
// dialog's non-undoable mode for freshly created sequences).
void apply_or_push(olive::UndoCommand *command, const QString &name,
int undoable)
{
if (undoable) {
push_or_run(command, name);
} else {
command->redo_now();
delete command;
}
}
// Undo commands for sequence parameter writes. The engine has no undo
// commands for these (the application's sequence dialog carries its own
// SequenceParamCommand at the app layer), so the facade carries
// read-modify-write equivalents with the same semantics.
class SequenceVideoParamsCommand : public olive::UndoCommand {
public:
SequenceVideoParamsCommand(olive::Sequence *sequence,
const olive::VideoParams &params)
: sequence_(sequence)
, new_params_(params)
{
}
virtual olive::Project *get_relevant_project() const override
{
return sequence_->project();
}
protected:
virtual void redo() override
{
old_params_ = sequence_->get_video_params();
sequence_->set_video_params(new_params_);
}
virtual void undo() override
{
sequence_->set_video_params(old_params_);
}
private:
olive::Sequence *sequence_;
olive::VideoParams old_params_;
olive::VideoParams new_params_;
};
class SequenceAudioParamsCommand : public olive::UndoCommand {
public:
SequenceAudioParamsCommand(olive::Sequence *sequence,
const olive::AudioParams &params)
: sequence_(sequence)
, new_params_(params)
{
}
virtual olive::Project *get_relevant_project() const override
{
return sequence_->project();
}
protected:
virtual void redo() override
{
old_params_ = sequence_->get_audio_params();
sequence_->set_audio_params(new_params_);
}
virtual void undo() override
{
sequence_->set_audio_params(old_params_);
}
private:
olive::Sequence *sequence_;
olive::AudioParams old_params_;
olive::AudioParams new_params_;
};
// The clip at (track_index, clip_index) within the given track list,
// skipping gap blocks; nullptr when out of range.
olive::ClipBlock *clip_at_index(olive::TrackList *list, int track_index,
@@ -309,6 +391,220 @@ int oakengine_sequence_get_video_params(const OakEngineSequence *self,
return OAKENGINE_OK;
}
/* ---- Sequence parameters (sequence dialog) ------------------------------------ */
int oakengine_sequence_get_video_params_ex(
const OakEngineSequence *self, int *width, int *height, int *fps_num,
int *fps_den, int *par_num, int *par_den, int *interlacing, int *format,
int *divider)
{
if (!self) {
return OAKENGINE_E_INVALID;
}
const olive::VideoParams params = impl(self)->get_video_params();
if (width) {
*width = params.width();
}
if (height) {
*height = params.height();
}
const olive::Rational frame_rate = params.frame_rate();
if (fps_num) {
*fps_num = frame_rate.numerator();
}
if (fps_den) {
*fps_den = frame_rate.denominator();
}
const olive::Rational par = params.pixel_aspect_ratio();
if (par_num) {
*par_num = par.numerator();
}
if (par_den) {
*par_den = par.denominator();
}
if (interlacing) {
*interlacing = int(params.interlacing());
}
if (format) {
*format = int(params.format());
}
if (divider) {
*divider = params.divider();
}
return OAKENGINE_OK;
}
int oakengine_sequence_set_video_params(
OakEngineSequence *self, int width, int height, int fps_num, int fps_den,
int par_num, int par_den, int interlacing, int format, int undoable)
{
set_seq_error(QString());
if (!self) {
set_seq_error(QStringLiteral("invalid sequence"));
return OAKENGINE_E_INVALID;
}
// -1 leaves a field unchanged; 0 / out-of-range values are rejected.
if (width < -1 || width == 0 || height < -1 || height == 0) {
set_seq_error(QStringLiteral("invalid video size %1x%2")
.arg(width)
.arg(height));
return OAKENGINE_E_INVALID;
}
if ((fps_num == -1) != (fps_den == -1) || fps_num < -1 || fps_num == 0 ||
fps_den < -1 || fps_den == 0) {
set_seq_error(QStringLiteral("invalid frame rate %1/%2")
.arg(fps_num)
.arg(fps_den));
return OAKENGINE_E_INVALID;
}
if ((par_num == -1) != (par_den == -1) || par_num < -1 || par_num == 0 ||
par_den < -1 || par_den == 0) {
set_seq_error(QStringLiteral("invalid pixel aspect %1/%2")
.arg(par_num)
.arg(par_den));
return OAKENGINE_E_INVALID;
}
if (interlacing < -1 ||
interlacing > int(olive::VideoParams::k_interlaced_bottom_first)) {
set_seq_error(
QStringLiteral("invalid interlacing %1").arg(interlacing));
return OAKENGINE_E_INVALID;
}
if (format < -1 || format >= int(olive::PixelFormat::count)) {
set_seq_error(QStringLiteral("invalid pixel format %1").arg(format));
return OAKENGINE_E_INVALID;
}
olive::Sequence *sequence = impl(self);
const olive::VideoParams current = sequence->get_video_params();
// Rebuild through the same constructor the application's dialog uses so
// the frame rate (flipped time base) and effective size stay in sync.
const olive::VideoParams updated(
width >= 0 ? width : current.width(),
height >= 0 ? height : current.height(),
fps_num >= 0 ? olive::Rational(fps_den, fps_num) : current.time_base(),
format >= 0 ? olive::PixelFormat::Format(format) :
olive::PixelFormat::Format(current.format()),
current.channel_count(),
par_num >= 0 ? olive::Rational(par_num, par_den) :
current.pixel_aspect_ratio(),
interlacing >= 0 ? olive::VideoParams::Interlacing(interlacing) :
current.interlacing(),
current.divider());
if (updated == current) {
return OAKENGINE_OK;
}
apply_or_push(new SequenceVideoParamsCommand(sequence, updated),
QStringLiteral("Set Sequence Video Parameters"), undoable);
return OAKENGINE_OK;
}
int oakengine_sequence_get_audio_params(const OakEngineSequence *self,
int *sample_rate,
uint64_t *channel_layout)
{
if (!self) {
return OAKENGINE_E_INVALID;
}
const olive::AudioParams params = impl(self)->get_audio_params();
if (sample_rate) {
*sample_rate = params.sample_rate();
}
if (channel_layout) {
*channel_layout = params.channel_layout();
}
return OAKENGINE_OK;
}
int oakengine_sequence_set_audio_params(OakEngineSequence *self,
int sample_rate,
uint64_t channel_layout, int undoable)
{
set_seq_error(QString());
if (!self) {
set_seq_error(QStringLiteral("invalid sequence"));
return OAKENGINE_E_INVALID;
}
olive::Sequence *sequence = impl(self);
const olive::AudioParams current = sequence->get_audio_params();
if (sample_rate <= 0) {
sample_rate = current.sample_rate();
}
if (channel_layout == 0) {
channel_layout = current.channel_layout();
}
const olive::AudioParams updated(sample_rate, channel_layout,
current.format());
if (updated == current) {
return OAKENGINE_OK;
}
apply_or_push(new SequenceAudioParamsCommand(sequence, updated),
QStringLiteral("Set Sequence Audio Parameters"), undoable);
return OAKENGINE_OK;
}
int oakengine_sequence_get_preview_divider(const OakEngineSequence *self)
{
if (!self) {
return 0;
}
return impl(self)->get_video_params().divider();
}
int oakengine_sequence_set_preview_divider(OakEngineSequence *self,
int divider, int undoable)
{
set_seq_error(QString());
if (!self) {
set_seq_error(QStringLiteral("invalid sequence"));
return OAKENGINE_E_INVALID;
}
if (divider < 1) {
set_seq_error(QStringLiteral("invalid preview divider %1")
.arg(divider));
return OAKENGINE_E_INVALID;
}
olive::Sequence *sequence = impl(self);
const olive::VideoParams current = sequence->get_video_params();
if (current.divider() == divider) {
return OAKENGINE_OK;
}
// Same constructor rebuild as in set_video_params (keeps the derived
// effective size in sync).
const olive::VideoParams updated(
current.width(), current.height(), current.time_base(),
current.format(), current.channel_count(),
current.pixel_aspect_ratio(), current.interlacing(), divider);
apply_or_push(new SequenceVideoParamsCommand(sequence, updated),
QStringLiteral("Set Sequence Preview Divider"), undoable);
return OAKENGINE_OK;
}
int oakengine_sequence_get_video_auto_cache(const OakEngineSequence *self)
{
if (!self) {
return 0;
}
return impl(self)->is_video_auto_cache_enabled() ? 1 : 0;
}
int oakengine_sequence_set_video_auto_cache(OakEngineSequence *self,
int enabled, int undoable)
{
set_seq_error(QString());
Q_UNUSED(undoable)
if (!self) {
set_seq_error(QStringLiteral("invalid sequence"));
return OAKENGINE_E_INVALID;
}
// The engine's auto-cache accessors are stubs for now (the read always
// returns false, the write is a no-op; the application's dialog has the
// checkbox TEMP-disabled accordingly). Forwarded without an undo
// command so the facade surface is ready when the engine lands it.
impl(self)->set_video_auto_cache_enabled(enabled != 0);
return OAKENGINE_OK;
}
int oakengine_sequence_track_count(const OakEngineSequence *self, int *video,
int *audio, int *subtitle)
{
@@ -37,6 +37,7 @@
#include "oakengine/footage.h"
#include "oakengine/init.h"
#include "oakengine/node.h"
#include "oakengine/project.h"
#include "oakengine/timeline.h"
@@ -641,6 +642,174 @@ static void test_markers(void)
oakengine_project_free(project);
}
static void test_sequence_params(void)
{
OakEngineProject *project = oakengine_project_create();
assert(project != NULL);
assert(oakengine_project_new(project) == OAKENGINE_OK);
OakEngineSequence *seq = oakengine_sequence_new(project, "Params");
assert(seq != NULL);
int w = 0, h = 0, fn = 0, fd = 0, pn = 0, pd = 0, il = -1, fmt = -1,
div = 0;
assert(oakengine_sequence_get_video_params_ex(seq, &w, &h, &fn, &fd, &pn,
&pd, &il, &fmt,
&div) == OAKENGINE_OK);
const int base_w = w, base_h = h, base_div = div;
assert(base_w > 0 && base_h > 0 && fn > 0 && fd > 0 && div >= 1);
assert(oakengine_sequence_get_video_params_ex(NULL, &w, &h, &fn, &fd,
&pn, &pd, &il, &fmt,
&div) == OAKENGINE_E_INVALID);
// Full video parameter write (f32 = 4, bottom-first = 2) + readback.
assert(oakengine_sequence_set_video_params(seq, 1280, 720, 24, 1, 1, 1, 2,
4, 1) == OAKENGINE_OK);
assert(oakengine_sequence_get_video_params_ex(seq, &w, &h, &fn, &fd, &pn,
&pd, &il, &fmt,
&div) == OAKENGINE_OK);
assert(w == 1280 && h == 720 && fn == 24 && fd == 1);
assert(pn == 1 && pd == 1 && il == 2 && fmt == 4 && div == base_div);
int gn = 0, gd = 0;
assert(oakengine_sequence_get_frame_rate(seq, &gn, &gd) == OAKENGINE_OK);
assert(gn == 24 && gd == 1);
// Partial update: -1 leaves the other fields unchanged.
assert(oakengine_sequence_set_video_params(seq, 640, -1, -1, -1, -1, -1,
-1, -1, 1) == OAKENGINE_OK);
assert(oakengine_sequence_get_video_params_ex(seq, &w, &h, &fn, &fd, &pn,
&pd, &il, &fmt,
&div) == OAKENGINE_OK);
assert(w == 640 && h == 720 && fn == 24 && il == 2);
// Invalid values: zero size, half a rational pair, out-of-range enum.
assert(oakengine_sequence_set_video_params(seq, 0, 720, -1, -1, -1, -1,
-1, -1,
1) == OAKENGINE_E_INVALID);
assert(oakengine_sequence_set_video_params(seq, -1, -1, 24, -1, -1, -1,
-1, -1,
1) == OAKENGINE_E_INVALID);
assert(oakengine_sequence_set_video_params(seq, -1, -1, -1, -1, -1, -1, 3,
-1,
1) == OAKENGINE_E_INVALID);
assert(oakengine_sequence_set_video_params(seq, -1, -1, -1, -1, -1, -1,
-1, 5,
1) == OAKENGINE_E_INVALID);
assert(oakengine_sequence_set_video_params(NULL, 1, 1, -1, -1, -1, -1, -1,
-1,
1) == OAKENGINE_E_INVALID);
// Undo restores step by step (partial write, then full write).
assert(oakengine_project_undo(project) == OAKENGINE_OK);
assert(oakengine_sequence_get_video_params_ex(seq, &w, &h, &fn, &fd, &pn,
&pd, &il, &fmt,
&div) == OAKENGINE_OK);
assert(w == 1280 && h == 720);
assert(oakengine_project_undo(project) == OAKENGINE_OK);
assert(oakengine_sequence_get_video_params_ex(seq, &w, &h, &fn, &fd, &pn,
&pd, &il, &fmt,
&div) == OAKENGINE_OK);
assert(w == base_w && h == base_h);
// Audio round trip; 0 / <= 0 leaves the field unchanged.
int sr = 0;
uint64_t layout = 0;
assert(oakengine_sequence_get_audio_params(seq, &sr, &layout) ==
OAKENGINE_OK);
assert(sr > 0 && layout != 0);
const int base_sr = sr;
const uint64_t base_layout = layout;
assert(oakengine_sequence_set_audio_params(seq, 44100, 3, 1) ==
OAKENGINE_OK);
assert(oakengine_sequence_get_audio_params(seq, &sr, &layout) ==
OAKENGINE_OK);
assert(sr == 44100 && layout == 3);
assert(oakengine_sequence_set_audio_params(seq, 48000, 0, 1) ==
OAKENGINE_OK);
assert(oakengine_sequence_get_audio_params(seq, &sr, &layout) ==
OAKENGINE_OK);
assert(sr == 48000 && layout == 3);
assert(oakengine_sequence_set_audio_params(NULL, 48000, 3, 1) ==
OAKENGINE_E_INVALID);
assert(oakengine_sequence_get_audio_params(NULL, &sr, &layout) ==
OAKENGINE_E_INVALID);
assert(oakengine_project_undo(project) == OAKENGINE_OK);
assert(oakengine_project_undo(project) == OAKENGINE_OK);
assert(oakengine_sequence_get_audio_params(seq, &sr, &layout) ==
OAKENGINE_OK);
assert(sr == base_sr && layout == base_layout);
// Preview divider; the other video params stay untouched.
assert(oakengine_sequence_get_preview_divider(seq) == base_div);
assert(oakengine_sequence_set_preview_divider(seq, 4, 1) == OAKENGINE_OK);
assert(oakengine_sequence_get_preview_divider(seq) == 4);
assert(oakengine_sequence_set_preview_divider(seq, 0, 1) ==
OAKENGINE_E_INVALID);
assert(oakengine_sequence_set_preview_divider(NULL, 1, 1) ==
OAKENGINE_E_INVALID);
assert(oakengine_sequence_get_preview_divider(NULL) == 0);
assert(oakengine_sequence_get_video_params_ex(seq, &w, &h, &fn, &fd, &pn,
&pd, &il, &fmt,
&div) == OAKENGINE_OK);
assert(w == base_w && h == base_h && div == 4);
assert(oakengine_project_undo(project) == OAKENGINE_OK);
assert(oakengine_sequence_get_preview_divider(seq) == base_div);
// Auto-cache flag: the engine accessors are stubs (read always 0, write
// ignored), so there is nothing to undo here.
assert(oakengine_sequence_get_video_auto_cache(seq) == 0);
assert(oakengine_sequence_set_video_auto_cache(seq, 1, 1) == OAKENGINE_OK);
assert(oakengine_sequence_get_video_auto_cache(seq) == 0);
assert(oakengine_sequence_get_video_auto_cache(NULL) == 0);
assert(oakengine_sequence_set_video_auto_cache(NULL, 1, 1) ==
OAKENGINE_E_INVALID);
// Label through the node family with the undoable flag.
assert(oakengine_node_set_label_ex((OakEngineNode *)seq, "Renamed", 1) ==
OAKENGINE_OK);
char name[64];
assert(oakengine_sequence_name(seq, name, sizeof(name)) > 0);
assert(strcmp(name, "Renamed") == 0);
assert(oakengine_project_undo(project) == OAKENGINE_OK);
assert(oakengine_sequence_name(seq, name, sizeof(name)) > 0);
assert(strcmp(name, "Params") == 0);
oakengine_project_free(project);
// undoable == 0 applies directly with no undo entry: on a fresh project
// the creation is undone first so the stack is empty (the node stays
// alive under its creation command).
OakEngineProject *p2 = oakengine_project_create();
assert(p2 != NULL);
assert(oakengine_project_new(p2) == OAKENGINE_OK);
OakEngineSequence *s2 = oakengine_sequence_new(p2, "Direct");
assert(s2 != NULL);
assert(oakengine_project_undo(p2) == OAKENGINE_OK);
assert(oakengine_project_can_undo(p2) == 0);
assert(oakengine_sequence_set_video_params(s2, 800, 600, -1, -1, -1, -1,
-1, -1,
0) == OAKENGINE_OK);
assert(oakengine_sequence_get_video_params_ex(s2, &w, &h, &fn, &fd, &pn,
&pd, &il, &fmt,
&div) == OAKENGINE_OK);
assert(w == 800 && h == 600);
assert(oakengine_project_can_undo(p2) == 0);
assert(oakengine_sequence_set_audio_params(s2, 22050, 0, 0) ==
OAKENGINE_OK);
assert(oakengine_sequence_get_audio_params(s2, &sr, &layout) ==
OAKENGINE_OK);
assert(sr == 22050);
assert(oakengine_project_can_undo(p2) == 0);
assert(oakengine_sequence_set_preview_divider(s2, 2, 0) == OAKENGINE_OK);
assert(oakengine_sequence_get_preview_divider(s2) == 2);
assert(oakengine_project_can_undo(p2) == 0);
assert(oakengine_node_set_label_ex((OakEngineNode *)s2, "NoUndo", 0) ==
OAKENGINE_OK);
assert(oakengine_sequence_name(s2, name, sizeof(name)) > 0);
assert(strcmp(name, "NoUndo") == 0);
assert(oakengine_project_can_undo(p2) == 0);
oakengine_project_free(p2);
}
int main(void)
{
make_tmpdir();
@@ -669,6 +838,7 @@ int main(void)
test_edit_round2(path);
test_track_structure(path);
test_markers();
test_sequence_params();
oakengine_project_free(project);
assert(oakengine_shutdown() == OAKENGINE_OK);