- new primitives: clip_toggle_enabled (per-block flip), clip_set_linked, sequence_add_default_transition (config-driven, sequence timebase), node_set_label_many, node_set_color_label, plus observation getters - toggle-links, default transitions, enable toggles, color labels, and block renaming now go through the facade; the stray empty undo entry from block renaming is gone along the way - nest/multicam/waveform-sync stay as documented composites: they mix redo_now intermediate state, graph surgery, and app-side computation that a single primitive cannot express faithfully - the timeline panel's command execution paths are now fully migrated
1247 lines
51 KiB
C++
1247 lines
51 KiB
C++
/***
|
|
|
|
Oak - Non-Linear Video Editor
|
|
Copyright (C) 2026 Oak Team
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
***/
|
|
|
|
// Pure C ABI test for the liboakengine timeline editing primitives:
|
|
// add_track, add_footage_clip and the clip accessors, including their
|
|
// undo/redo behavior and failure paths. Uses the real media file
|
|
// tests/demo.mp4. No GL required.
|
|
|
|
#include <assert.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#if defined(_WIN32)
|
|
#include <direct.h>
|
|
#include <windows.h>
|
|
#else
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
#include "oakengine/footage.h"
|
|
#include "oakengine/init.h"
|
|
#include "oakengine/node.h"
|
|
#include "oakengine/project.h"
|
|
#include "oakengine/timeline.h"
|
|
|
|
#ifndef OAK_TEST_SOURCE_DIR
|
|
#define OAK_TEST_SOURCE_DIR "."
|
|
#endif
|
|
|
|
static char g_tmpdir[4096];
|
|
|
|
static void make_tmpdir(void)
|
|
{
|
|
#if defined(_WIN32)
|
|
char base[MAX_PATH];
|
|
const DWORD len = GetTempPathA(MAX_PATH, base);
|
|
assert(len > 0 && len < MAX_PATH);
|
|
snprintf(g_tmpdir, sizeof(g_tmpdir), "%soakengine_tl_edit_test_%lu", base,
|
|
(unsigned long)GetCurrentProcessId());
|
|
assert(_mkdir(g_tmpdir) == 0);
|
|
#else
|
|
strcpy(g_tmpdir, "/tmp/oakengine_tl_edit_test_XXXXXX");
|
|
assert(mkdtemp(g_tmpdir) != NULL);
|
|
#endif
|
|
}
|
|
|
|
static void demo_path(char *dst, size_t cap)
|
|
{
|
|
const int n = snprintf(dst, cap, "%s/tests/demo.mp4", OAK_TEST_SOURCE_DIR);
|
|
assert(n > 0 && (size_t)n < cap);
|
|
}
|
|
|
|
static void test_add_track(OakEngineProject *project, OakEngineSequence *seq)
|
|
{
|
|
int video = -1, audio = -1, subtitle = -1;
|
|
|
|
assert(oakengine_sequence_track_count(seq, &video, &audio, &subtitle) ==
|
|
OAKENGINE_OK);
|
|
assert(video == 0 && audio == 0 && subtitle == 0);
|
|
|
|
assert(oakengine_sequence_add_track(seq, OAKENGINE_TRACK_TYPE_VIDEO) == 0);
|
|
assert(oakengine_sequence_add_track(seq, OAKENGINE_TRACK_TYPE_AUDIO) == 0);
|
|
assert(oakengine_sequence_add_track(seq, OAKENGINE_TRACK_TYPE_SUBTITLE) ==
|
|
0);
|
|
assert(oakengine_sequence_add_track(seq, OAKENGINE_TRACK_TYPE_VIDEO) == 1);
|
|
|
|
assert(oakengine_sequence_track_count(seq, &video, &audio, &subtitle) ==
|
|
OAKENGINE_OK);
|
|
assert(video == 2 && audio == 1 && subtitle == 1);
|
|
|
|
// Invalid track types are rejected.
|
|
assert(oakengine_sequence_add_track(seq, -1) == OAKENGINE_E_INVALID);
|
|
assert(oakengine_sequence_add_track(seq, 3) == OAKENGINE_E_INVALID);
|
|
assert(oakengine_sequence_add_track(NULL, OAKENGINE_TRACK_TYPE_VIDEO) ==
|
|
OAKENGINE_E_INVALID);
|
|
|
|
// Track adds are undoable: undo the second video track, then redo it.
|
|
assert(oakengine_project_undo(project) == OAKENGINE_OK);
|
|
video = audio = subtitle = -1;
|
|
assert(oakengine_sequence_track_count(seq, &video, &audio, &subtitle) ==
|
|
OAKENGINE_OK);
|
|
assert(video == 1 && audio == 1 && subtitle == 1);
|
|
assert(oakengine_project_redo(project) == OAKENGINE_OK);
|
|
video = audio = subtitle = -1;
|
|
assert(oakengine_sequence_track_count(seq, &video, &audio, &subtitle) ==
|
|
OAKENGINE_OK);
|
|
assert(video == 2 && audio == 1 && subtitle == 1);
|
|
}
|
|
|
|
static void test_add_clip(OakEngineProject *project, OakEngineSequence *seq,
|
|
const char *media_path)
|
|
{
|
|
char err[512];
|
|
|
|
// Probe handles carry no project node and cannot be placed.
|
|
OakEngineFootage *probed = oakengine_footage_probe(media_path);
|
|
assert(probed != NULL);
|
|
assert(oakengine_sequence_add_footage_clip(seq, probed,
|
|
OAKENGINE_TRACK_TYPE_VIDEO, 0,
|
|
0, 30, 0) == NULL);
|
|
assert(oakengine_sequence_last_error(err, sizeof(err)) > 0);
|
|
oakengine_footage_free(probed);
|
|
|
|
// Import the media into the project.
|
|
OakEngineFootage *footage =
|
|
oakengine_project_import_footage(project, media_path);
|
|
assert(footage != NULL);
|
|
|
|
// No subtitle clips.
|
|
assert(oakengine_sequence_add_footage_clip(seq, footage,
|
|
OAKENGINE_TRACK_TYPE_SUBTITLE,
|
|
0, 0, 30, 0) == NULL);
|
|
|
|
// Out-of-range track indexes are rejected without creating tracks.
|
|
assert(oakengine_sequence_add_footage_clip(seq, footage,
|
|
OAKENGINE_TRACK_TYPE_VIDEO, 5,
|
|
0, 30, 0) == NULL);
|
|
assert(oakengine_sequence_last_error(err, sizeof(err)) > 0);
|
|
assert(oakengine_sequence_add_footage_clip(seq, footage,
|
|
OAKENGINE_TRACK_TYPE_VIDEO, -1,
|
|
0, 30, 0) == NULL);
|
|
|
|
// Bad ranges: out <= in, negative in / media_in.
|
|
assert(oakengine_sequence_add_footage_clip(seq, footage,
|
|
OAKENGINE_TRACK_TYPE_VIDEO, 0,
|
|
30, 30, 0) == NULL);
|
|
assert(oakengine_sequence_add_footage_clip(seq, footage,
|
|
OAKENGINE_TRACK_TYPE_VIDEO, 0,
|
|
30, 10, 0) == NULL);
|
|
assert(oakengine_sequence_add_footage_clip(seq, footage,
|
|
OAKENGINE_TRACK_TYPE_VIDEO, 0,
|
|
-1, 30, 0) == NULL);
|
|
assert(oakengine_sequence_add_footage_clip(seq, footage,
|
|
OAKENGINE_TRACK_TYPE_VIDEO, 0,
|
|
0, 30, -1) == NULL);
|
|
|
|
// NULL safety.
|
|
assert(oakengine_sequence_add_footage_clip(NULL, footage,
|
|
OAKENGINE_TRACK_TYPE_VIDEO, 0,
|
|
0, 30, 0) == NULL);
|
|
assert(oakengine_sequence_add_footage_clip(seq, NULL,
|
|
OAKENGINE_TRACK_TYPE_VIDEO, 0,
|
|
0, 30, 0) == NULL);
|
|
|
|
// clip_count reports OAKENGINE_E_NOT_FOUND for a missing track.
|
|
assert(oakengine_sequence_clip_count(seq, OAKENGINE_TRACK_TYPE_VIDEO,
|
|
99) == OAKENGINE_E_NOT_FOUND);
|
|
assert(oakengine_sequence_clip_count(NULL, OAKENGINE_TRACK_TYPE_VIDEO,
|
|
0) == OAKENGINE_E_INVALID);
|
|
|
|
// Place a video clip on track 0: frames 10..40, media in-point 5.
|
|
OakEngineClip *clip = oakengine_sequence_add_footage_clip(
|
|
seq, footage, OAKENGINE_TRACK_TYPE_VIDEO, 0, 10, 40, 5);
|
|
assert(clip != NULL);
|
|
assert(oakengine_sequence_clip_count(seq, OAKENGINE_TRACK_TYPE_VIDEO,
|
|
0) == 1);
|
|
OakEngineClip *at = oakengine_sequence_clip_at(
|
|
seq, OAKENGINE_TRACK_TYPE_VIDEO, 0, 0);
|
|
assert(at == clip);
|
|
assert(oakengine_sequence_clip_at(seq, OAKENGINE_TRACK_TYPE_VIDEO, 0,
|
|
1) == NULL);
|
|
assert(oakengine_sequence_clip_at(seq, OAKENGINE_TRACK_TYPE_VIDEO, 9,
|
|
0) == NULL);
|
|
|
|
int64_t in = -1, out = -1, media_in = -1;
|
|
assert(oakengine_clip_get_range(clip, &in, &out, &media_in) ==
|
|
OAKENGINE_OK);
|
|
assert(in == 10 && out == 40 && media_in == 5);
|
|
assert(oakengine_clip_get_range(NULL, &in, &out, &media_in) ==
|
|
OAKENGINE_E_INVALID);
|
|
|
|
// And an audio clip on audio track 0 (same footage, whole range).
|
|
OakEngineClip *aclip = oakengine_sequence_add_footage_clip(
|
|
seq, footage, OAKENGINE_TRACK_TYPE_AUDIO, 0, 0, 30, 0);
|
|
assert(aclip != NULL);
|
|
assert(oakengine_sequence_clip_count(seq, OAKENGINE_TRACK_TYPE_AUDIO,
|
|
0) == 1);
|
|
|
|
// Undo/redo both clip adds (the audio clip is on top of the undo stack,
|
|
// the video clip right below it).
|
|
assert(oakengine_project_undo(project) == OAKENGINE_OK);
|
|
assert(oakengine_project_undo(project) == OAKENGINE_OK);
|
|
assert(oakengine_sequence_clip_count(seq, OAKENGINE_TRACK_TYPE_AUDIO,
|
|
0) == 0);
|
|
assert(oakengine_sequence_clip_count(seq, OAKENGINE_TRACK_TYPE_VIDEO,
|
|
0) == 0);
|
|
assert(oakengine_project_redo(project) == OAKENGINE_OK);
|
|
assert(oakengine_project_redo(project) == OAKENGINE_OK);
|
|
assert(oakengine_sequence_clip_count(seq, OAKENGINE_TRACK_TYPE_VIDEO,
|
|
0) == 1);
|
|
assert(oakengine_sequence_clip_count(seq, OAKENGINE_TRACK_TYPE_AUDIO,
|
|
0) == 1);
|
|
|
|
oakengine_footage_free(footage); // wrapper only; node stays
|
|
assert(oakengine_project_footage_count(project) == 1);
|
|
}
|
|
|
|
// Footage imported into one project must not be placed into another.
|
|
static void test_cross_project_rejected(const char *media_path)
|
|
{
|
|
OakEngineProject *a = oakengine_project_create();
|
|
OakEngineProject *b = oakengine_project_create();
|
|
assert(a != NULL && b != NULL);
|
|
assert(oakengine_project_new(a) == OAKENGINE_OK);
|
|
assert(oakengine_project_new(b) == OAKENGINE_OK);
|
|
|
|
OakEngineFootage *footage =
|
|
oakengine_project_import_footage(a, media_path);
|
|
assert(footage != NULL);
|
|
|
|
OakEngineSequence *seq_b = oakengine_sequence_new(b, "B");
|
|
assert(seq_b != NULL);
|
|
assert(oakengine_sequence_add_track(seq_b, OAKENGINE_TRACK_TYPE_VIDEO) ==
|
|
0);
|
|
|
|
char err[512];
|
|
assert(oakengine_sequence_add_footage_clip(seq_b, footage,
|
|
OAKENGINE_TRACK_TYPE_VIDEO, 0,
|
|
0, 30, 0) == NULL);
|
|
assert(oakengine_sequence_last_error(err, sizeof(err)) > 0);
|
|
assert(oakengine_sequence_clip_count(seq_b, OAKENGINE_TRACK_TYPE_VIDEO,
|
|
0) == 0);
|
|
|
|
oakengine_footage_free(footage);
|
|
oakengine_project_free(a);
|
|
oakengine_project_free(b);
|
|
}
|
|
|
|
// Round-2 primitives: split, trim, move, ripple delete (plus undo/redo),
|
|
// over a fresh project with two clips on one video track.
|
|
static void test_edit_round2(const char *media_path)
|
|
{
|
|
OakEngineProject *project = oakengine_project_create();
|
|
assert(project != NULL);
|
|
assert(oakengine_project_new(project) == OAKENGINE_OK);
|
|
OakEngineSequence *seq = oakengine_sequence_new(project, "Round2");
|
|
assert(seq != NULL);
|
|
assert(oakengine_sequence_add_track(seq, OAKENGINE_TRACK_TYPE_VIDEO) ==
|
|
0);
|
|
|
|
OakEngineFootage *footage =
|
|
oakengine_project_import_footage(project, media_path);
|
|
assert(footage != NULL);
|
|
|
|
// Two clips back to back: A [0,30] and B [30,60], both from media 0.
|
|
OakEngineClip *clip_a = oakengine_sequence_add_footage_clip(
|
|
seq, footage, OAKENGINE_TRACK_TYPE_VIDEO, 0, 0, 30, 0);
|
|
assert(clip_a != NULL);
|
|
OakEngineClip *clip_b = oakengine_sequence_add_footage_clip(
|
|
seq, footage, OAKENGINE_TRACK_TYPE_VIDEO, 0, 30, 60, 0);
|
|
assert(clip_b != NULL);
|
|
assert(oakengine_sequence_clip_count(seq, OAKENGINE_TRACK_TYPE_VIDEO,
|
|
0) == 2);
|
|
|
|
int64_t in = -1, out = -1, media_in = -1;
|
|
char err[512];
|
|
|
|
// ---- split -------------------------------------------------------------
|
|
// Bad split points: on the edges, outside, missing clip.
|
|
assert(oakengine_sequence_split_clip(seq, OAKENGINE_TRACK_TYPE_VIDEO, 0,
|
|
0, 0) == OAKENGINE_E_INVALID);
|
|
assert(oakengine_sequence_split_clip(seq, OAKENGINE_TRACK_TYPE_VIDEO, 0,
|
|
0, 30) == OAKENGINE_E_INVALID);
|
|
assert(oakengine_sequence_split_clip(seq, OAKENGINE_TRACK_TYPE_VIDEO, 0,
|
|
0, -5) == OAKENGINE_E_INVALID);
|
|
assert(oakengine_sequence_split_clip(seq, OAKENGINE_TRACK_TYPE_VIDEO, 0,
|
|
0, 99) == OAKENGINE_E_INVALID);
|
|
assert(oakengine_sequence_split_clip(seq, OAKENGINE_TRACK_TYPE_VIDEO, 0,
|
|
99, 15) == OAKENGINE_E_NOT_FOUND);
|
|
assert(oakengine_sequence_split_clip(NULL, OAKENGINE_TRACK_TYPE_VIDEO, 0,
|
|
0, 15) == OAKENGINE_E_INVALID);
|
|
|
|
// Split A [0,30] at 15 -> [0,15] + [15,30] with aligned media in-points.
|
|
assert(oakengine_sequence_split_clip(seq, OAKENGINE_TRACK_TYPE_VIDEO, 0,
|
|
0, 15) == OAKENGINE_OK);
|
|
assert(oakengine_sequence_clip_count(seq, OAKENGINE_TRACK_TYPE_VIDEO,
|
|
0) == 3);
|
|
OakEngineClip *left =
|
|
oakengine_sequence_clip_at(seq, OAKENGINE_TRACK_TYPE_VIDEO, 0, 0);
|
|
OakEngineClip *right =
|
|
oakengine_sequence_clip_at(seq, OAKENGINE_TRACK_TYPE_VIDEO, 0, 1);
|
|
assert(left == clip_a);
|
|
assert(oakengine_clip_get_range(left, &in, &out, &media_in) ==
|
|
OAKENGINE_OK);
|
|
assert(in == 0 && out == 15 && media_in == 0);
|
|
assert(oakengine_clip_get_range(right, &in, &out, &media_in) ==
|
|
OAKENGINE_OK);
|
|
assert(in == 15 && out == 30 && media_in == 15);
|
|
|
|
// Undo merges them back, redo splits again.
|
|
assert(oakengine_project_undo(project) == OAKENGINE_OK);
|
|
assert(oakengine_sequence_clip_count(seq, OAKENGINE_TRACK_TYPE_VIDEO,
|
|
0) == 2);
|
|
assert(oakengine_clip_get_range(clip_a, &in, &out, &media_in) ==
|
|
OAKENGINE_OK);
|
|
assert(in == 0 && out == 30 && media_in == 0);
|
|
assert(oakengine_project_redo(project) == OAKENGINE_OK);
|
|
assert(oakengine_sequence_clip_count(seq, OAKENGINE_TRACK_TYPE_VIDEO,
|
|
0) == 3);
|
|
right = oakengine_sequence_clip_at(seq, OAKENGINE_TRACK_TYPE_VIDEO, 0, 1);
|
|
assert(oakengine_clip_get_range(right, &in, &out, &media_in) ==
|
|
OAKENGINE_OK);
|
|
assert(in == 15 && out == 30 && media_in == 15);
|
|
|
|
// ---- trim --------------------------------------------------------------
|
|
// Bad trim ranges.
|
|
assert(oakengine_clip_trim(NULL, 0, 10) == OAKENGINE_E_INVALID);
|
|
assert(oakengine_clip_trim(right, 30, 20) == OAKENGINE_E_INVALID);
|
|
assert(oakengine_clip_trim(right, -1, 20) == OAKENGINE_E_INVALID);
|
|
|
|
// Trim the right part's in-point 15 -> 20: media_in follows 15 -> 20.
|
|
assert(oakengine_clip_trim(right, 20, 30) == OAKENGINE_OK);
|
|
assert(oakengine_clip_get_range(right, &in, &out, &media_in) ==
|
|
OAKENGINE_OK);
|
|
assert(in == 20 && out == 30 && media_in == 20);
|
|
// The leading clip is untouched (a gap absorbs the difference).
|
|
assert(oakengine_clip_get_range(clip_a, &in, &out, &media_in) ==
|
|
OAKENGINE_OK);
|
|
assert(in == 0 && out == 15 && media_in == 0);
|
|
|
|
assert(oakengine_project_undo(project) == OAKENGINE_OK);
|
|
assert(oakengine_clip_get_range(right, &in, &out, &media_in) ==
|
|
OAKENGINE_OK);
|
|
assert(in == 15 && out == 30 && media_in == 15);
|
|
assert(oakengine_project_redo(project) == OAKENGINE_OK);
|
|
assert(oakengine_clip_get_range(right, &in, &out, &media_in) ==
|
|
OAKENGINE_OK);
|
|
assert(in == 20 && out == 30 && media_in == 20);
|
|
|
|
// Trim the out-point 30 -> 25 (media in-point stays).
|
|
assert(oakengine_clip_trim(right, 20, 25) == OAKENGINE_OK);
|
|
assert(oakengine_clip_get_range(right, &in, &out, &media_in) ==
|
|
OAKENGINE_OK);
|
|
assert(in == 20 && out == 25 && media_in == 20);
|
|
assert(oakengine_project_undo(project) == OAKENGINE_OK);
|
|
assert(oakengine_clip_get_range(right, &in, &out, &media_in) ==
|
|
OAKENGINE_OK);
|
|
assert(in == 20 && out == 30 && media_in == 20);
|
|
|
|
// No-op trim is accepted and does nothing.
|
|
assert(oakengine_clip_trim(right, 20, 30) == OAKENGINE_OK);
|
|
|
|
// Both ends in one command: [20,30] -> [22,28], media_in follows.
|
|
assert(oakengine_clip_trim(right, 22, 28) == OAKENGINE_OK);
|
|
assert(oakengine_clip_get_range(right, &in, &out, &media_in) ==
|
|
OAKENGINE_OK);
|
|
assert(in == 22 && out == 28 && media_in == 22);
|
|
assert(oakengine_project_undo(project) == OAKENGINE_OK);
|
|
assert(oakengine_clip_get_range(right, &in, &out, &media_in) ==
|
|
OAKENGINE_OK);
|
|
assert(in == 20 && out == 30 && media_in == 20);
|
|
|
|
// ---- move ---------------------------------------------------------------
|
|
assert(oakengine_sequence_move_clip(seq, OAKENGINE_TRACK_TYPE_VIDEO, 0, 1,
|
|
-1) == OAKENGINE_E_INVALID);
|
|
assert(oakengine_sequence_move_clip(seq, OAKENGINE_TRACK_TYPE_VIDEO, 0,
|
|
99, 50) == OAKENGINE_E_NOT_FOUND);
|
|
|
|
// Move the second clip [20,30] to 70 (past everything): position shifts,
|
|
// length and media in-point are preserved, the other clips stay put.
|
|
assert(oakengine_sequence_move_clip(seq, OAKENGINE_TRACK_TYPE_VIDEO, 0, 1,
|
|
70) == OAKENGINE_OK);
|
|
// After the move the moved clip is the LAST one on the track.
|
|
right = oakengine_sequence_clip_at(seq, OAKENGINE_TRACK_TYPE_VIDEO, 0, 2);
|
|
assert(oakengine_clip_get_range(right, &in, &out, &media_in) ==
|
|
OAKENGINE_OK);
|
|
assert(in == 70 && out == 80 && media_in == 20);
|
|
assert(oakengine_clip_get_range(clip_a, &in, &out, &media_in) ==
|
|
OAKENGINE_OK);
|
|
assert(in == 0 && out == 15 && media_in == 0);
|
|
assert(oakengine_clip_get_range(clip_b, &in, &out, &media_in) ==
|
|
OAKENGINE_OK);
|
|
assert(in == 30 && out == 60 && media_in == 0);
|
|
|
|
assert(oakengine_project_undo(project) == OAKENGINE_OK);
|
|
right = oakengine_sequence_clip_at(seq, OAKENGINE_TRACK_TYPE_VIDEO, 0, 1);
|
|
assert(oakengine_clip_get_range(right, &in, &out, &media_in) ==
|
|
OAKENGINE_OK);
|
|
assert(in == 20 && out == 30 && media_in == 20);
|
|
assert(oakengine_project_redo(project) == OAKENGINE_OK);
|
|
right = oakengine_sequence_clip_at(seq, OAKENGINE_TRACK_TYPE_VIDEO, 0, 2);
|
|
assert(oakengine_clip_get_range(right, &in, &out, &media_in) ==
|
|
OAKENGINE_OK);
|
|
assert(in == 70 && out == 80 && media_in == 20);
|
|
|
|
// ---- ripple delete -------------------------------------------------------
|
|
assert(oakengine_sequence_ripple_delete_clip(
|
|
seq, OAKENGINE_TRACK_TYPE_VIDEO, 0, 99) ==
|
|
OAKENGINE_E_NOT_FOUND);
|
|
assert(oakengine_sequence_ripple_delete_clip(NULL,
|
|
OAKENGINE_TRACK_TYPE_VIDEO, 0,
|
|
0) == OAKENGINE_E_INVALID);
|
|
|
|
// Delete the first clip [0,15]: both following clips shift left by 15.
|
|
assert(oakengine_sequence_ripple_delete_clip(
|
|
seq, OAKENGINE_TRACK_TYPE_VIDEO, 0, 0) == OAKENGINE_OK);
|
|
assert(oakengine_sequence_clip_count(seq, OAKENGINE_TRACK_TYPE_VIDEO,
|
|
0) == 2);
|
|
right = oakengine_sequence_clip_at(seq, OAKENGINE_TRACK_TYPE_VIDEO, 0, 0);
|
|
assert(oakengine_clip_get_range(right, &in, &out, &media_in) ==
|
|
OAKENGINE_OK);
|
|
assert(in == 15 && out == 45 && media_in == 0); // clip_b was [30,60]
|
|
right = oakengine_sequence_clip_at(seq, OAKENGINE_TRACK_TYPE_VIDEO, 0, 1);
|
|
assert(oakengine_clip_get_range(right, &in, &out, &media_in) ==
|
|
OAKENGINE_OK);
|
|
assert(in == 55 && out == 65 && media_in == 20); // was [70,80]
|
|
|
|
assert(oakengine_project_undo(project) == OAKENGINE_OK);
|
|
assert(oakengine_sequence_clip_count(seq, OAKENGINE_TRACK_TYPE_VIDEO,
|
|
0) == 3);
|
|
right = oakengine_sequence_clip_at(seq, OAKENGINE_TRACK_TYPE_VIDEO, 0, 0);
|
|
assert(oakengine_clip_get_range(right, &in, &out, &media_in) ==
|
|
OAKENGINE_OK);
|
|
assert(in == 0 && out == 15 && media_in == 0);
|
|
right = oakengine_sequence_clip_at(seq, OAKENGINE_TRACK_TYPE_VIDEO, 0, 1);
|
|
assert(oakengine_clip_get_range(right, &in, &out, &media_in) ==
|
|
OAKENGINE_OK);
|
|
assert(in == 30 && out == 60 && media_in == 0);
|
|
right = oakengine_sequence_clip_at(seq, OAKENGINE_TRACK_TYPE_VIDEO, 0, 2);
|
|
assert(oakengine_clip_get_range(right, &in, &out, &media_in) ==
|
|
OAKENGINE_OK);
|
|
assert(in == 70 && out == 80 && media_in == 20);
|
|
|
|
oakengine_footage_free(footage); // wrapper only; node stays
|
|
assert(oakengine_project_footage_count(project) == 1);
|
|
}
|
|
|
|
// Footage imported into one project must not be placed into another.
|
|
|
|
// Track structure: move/height/mute/lock/remove over two video tracks,
|
|
// plus undo/redo of the undoable ones.
|
|
static void test_track_structure(const char *media_path)
|
|
{
|
|
OakEngineProject *project = oakengine_project_create();
|
|
assert(project != NULL);
|
|
assert(oakengine_project_new(project) == OAKENGINE_OK);
|
|
OakEngineSequence *seq = oakengine_sequence_new(project, "Tracks");
|
|
assert(seq != NULL);
|
|
assert(oakengine_sequence_add_track(seq, OAKENGINE_TRACK_TYPE_VIDEO) ==
|
|
0);
|
|
assert(oakengine_sequence_add_track(seq, OAKENGINE_TRACK_TYPE_VIDEO) ==
|
|
1);
|
|
|
|
OakEngineFootage *footage =
|
|
oakengine_project_import_footage(project, media_path);
|
|
assert(footage != NULL);
|
|
// Distinctive clips per track so track order is observable.
|
|
OakEngineClip *clip_a = oakengine_sequence_add_footage_clip(
|
|
seq, footage, OAKENGINE_TRACK_TYPE_VIDEO, 0, 0, 10, 0);
|
|
OakEngineClip *clip_b = oakengine_sequence_add_footage_clip(
|
|
seq, footage, OAKENGINE_TRACK_TYPE_VIDEO, 1, 20, 30, 0);
|
|
assert(clip_a != NULL && clip_b != NULL);
|
|
|
|
int64_t in = -1, out = -1, media_in = -1;
|
|
double height = 0.0;
|
|
|
|
// move_track swaps their positions; the clips move with the tracks.
|
|
assert(oakengine_sequence_move_track(seq, OAKENGINE_TRACK_TYPE_VIDEO, 0,
|
|
1) == OAKENGINE_OK);
|
|
OakEngineClip *now = oakengine_sequence_clip_at(
|
|
seq, OAKENGINE_TRACK_TYPE_VIDEO, 0, 0);
|
|
assert(now == clip_b);
|
|
assert(oakengine_clip_get_range(now, &in, &out, &media_in) ==
|
|
OAKENGINE_OK);
|
|
assert(in == 20 && out == 30);
|
|
now = oakengine_sequence_clip_at(seq, OAKENGINE_TRACK_TYPE_VIDEO, 1, 0);
|
|
assert(now == clip_a);
|
|
assert(oakengine_clip_get_range(now, &in, &out, &media_in) ==
|
|
OAKENGINE_OK);
|
|
assert(in == 0 && out == 10);
|
|
|
|
// Undo/redo restore the order both ways.
|
|
assert(oakengine_project_undo(project) == OAKENGINE_OK);
|
|
now = oakengine_sequence_clip_at(seq, OAKENGINE_TRACK_TYPE_VIDEO, 0, 0);
|
|
assert(now == clip_a);
|
|
assert(oakengine_project_redo(project) == OAKENGINE_OK);
|
|
now = oakengine_sequence_clip_at(seq, OAKENGINE_TRACK_TYPE_VIDEO, 0, 0);
|
|
assert(now == clip_b);
|
|
|
|
// No-op move and out-of-range indexes.
|
|
assert(oakengine_sequence_move_track(seq, OAKENGINE_TRACK_TYPE_VIDEO, 1,
|
|
1) == OAKENGINE_OK);
|
|
assert(oakengine_sequence_move_track(seq, OAKENGINE_TRACK_TYPE_VIDEO, 0,
|
|
5) == OAKENGINE_E_NOT_FOUND);
|
|
assert(oakengine_sequence_move_track(seq, OAKENGINE_TRACK_TYPE_VIDEO, 5,
|
|
0) == OAKENGINE_E_NOT_FOUND);
|
|
assert(oakengine_sequence_move_track(NULL, OAKENGINE_TRACK_TYPE_VIDEO, 0,
|
|
1) == OAKENGINE_E_INVALID);
|
|
|
|
// Height: default is positive, round-trip, direct (not undoable).
|
|
assert(oakengine_track_get_height(seq, OAKENGINE_TRACK_TYPE_VIDEO, 0,
|
|
&height) == OAKENGINE_OK);
|
|
const double default_height = height;
|
|
assert(default_height > 0.0);
|
|
assert(oakengine_track_set_height(seq, OAKENGINE_TRACK_TYPE_VIDEO, 0,
|
|
3.0) == OAKENGINE_OK);
|
|
assert(oakengine_track_get_height(seq, OAKENGINE_TRACK_TYPE_VIDEO, 0,
|
|
&height) == OAKENGINE_OK);
|
|
assert(height == 3.0);
|
|
assert(oakengine_track_set_height(seq, OAKENGINE_TRACK_TYPE_VIDEO, 0,
|
|
0.0) == OAKENGINE_E_INVALID);
|
|
assert(oakengine_track_get_height(seq, OAKENGINE_TRACK_TYPE_VIDEO, 99,
|
|
&height) == OAKENGINE_E_NOT_FOUND);
|
|
assert(oakengine_track_get_height(NULL, OAKENGINE_TRACK_TYPE_VIDEO, 0,
|
|
&height) == OAKENGINE_E_INVALID);
|
|
|
|
// Mute and lock toggles, direct (not undoable).
|
|
assert(oakengine_track_is_muted(seq, OAKENGINE_TRACK_TYPE_VIDEO, 0) == 0);
|
|
assert(oakengine_track_set_muted(seq, OAKENGINE_TRACK_TYPE_VIDEO, 0,
|
|
1) == OAKENGINE_OK);
|
|
assert(oakengine_track_is_muted(seq, OAKENGINE_TRACK_TYPE_VIDEO, 0) == 1);
|
|
assert(oakengine_track_set_muted(seq, OAKENGINE_TRACK_TYPE_VIDEO, 0,
|
|
0) == OAKENGINE_OK);
|
|
assert(oakengine_track_is_muted(seq, OAKENGINE_TRACK_TYPE_VIDEO, 0) == 0);
|
|
assert(oakengine_track_set_muted(seq, OAKENGINE_TRACK_TYPE_VIDEO, 99,
|
|
1) == OAKENGINE_E_NOT_FOUND);
|
|
assert(oakengine_track_is_locked(seq, OAKENGINE_TRACK_TYPE_VIDEO, 0) ==
|
|
0);
|
|
assert(oakengine_track_set_locked(seq, OAKENGINE_TRACK_TYPE_VIDEO, 0,
|
|
1) == OAKENGINE_OK);
|
|
assert(oakengine_track_is_locked(seq, OAKENGINE_TRACK_TYPE_VIDEO, 0) ==
|
|
1);
|
|
assert(oakengine_track_set_locked(seq, OAKENGINE_TRACK_TYPE_VIDEO, 0,
|
|
0) == OAKENGINE_OK);
|
|
assert(oakengine_track_set_locked(NULL, OAKENGINE_TRACK_TYPE_VIDEO, 0,
|
|
1) == OAKENGINE_E_INVALID);
|
|
|
|
// remove_track drops the track and its content; undo brings both back.
|
|
int video = -1, audio = -1, subtitle = -1;
|
|
assert(oakengine_sequence_remove_track(seq, OAKENGINE_TRACK_TYPE_VIDEO,
|
|
1) == OAKENGINE_OK);
|
|
assert(oakengine_sequence_track_count(seq, &video, &audio, &subtitle) ==
|
|
OAKENGINE_OK);
|
|
assert(video == 1);
|
|
assert(oakengine_sequence_clip_count(seq, OAKENGINE_TRACK_TYPE_VIDEO,
|
|
0) == 1);
|
|
assert(oakengine_sequence_remove_track(seq, OAKENGINE_TRACK_TYPE_VIDEO,
|
|
9) == OAKENGINE_E_NOT_FOUND);
|
|
assert(oakengine_project_undo(project) == OAKENGINE_OK);
|
|
assert(oakengine_sequence_track_count(seq, &video, &audio, &subtitle) ==
|
|
OAKENGINE_OK);
|
|
assert(video == 2);
|
|
assert(oakengine_sequence_clip_count(seq, OAKENGINE_TRACK_TYPE_VIDEO,
|
|
1) == 1);
|
|
assert(oakengine_project_redo(project) == OAKENGINE_OK);
|
|
assert(oakengine_sequence_track_count(seq, &video, &audio, &subtitle) ==
|
|
OAKENGINE_OK);
|
|
assert(video == 1);
|
|
|
|
oakengine_footage_free(footage);
|
|
oakengine_project_free(project);
|
|
}
|
|
|
|
static void test_markers(void)
|
|
{
|
|
OakEngineProject *project = oakengine_project_create();
|
|
assert(project != NULL);
|
|
assert(oakengine_project_new(project) == OAKENGINE_OK);
|
|
OakEngineSequence *seq = oakengine_sequence_new(project, "Markers");
|
|
assert(seq != NULL);
|
|
|
|
int64_t ts = -1;
|
|
char name[64];
|
|
|
|
assert(oakengine_sequence_marker_count(seq) == 0);
|
|
assert(oakengine_sequence_marker_add(seq, 30, "Chapter 1") ==
|
|
OAKENGINE_OK);
|
|
assert(oakengine_sequence_marker_count(seq) == 1);
|
|
assert(oakengine_sequence_marker_at(seq, 0, &ts, name, sizeof(name), NULL) ==
|
|
OAKENGINE_OK);
|
|
assert(ts == 30 && strcmp(name, "Chapter 1") == 0);
|
|
|
|
// Duplicate time: the engine forbids two markers at one time.
|
|
assert(oakengine_sequence_marker_add(seq, 30, "dup") ==
|
|
OAKENGINE_E_STATE);
|
|
|
|
// Earlier marker sorts in front.
|
|
assert(oakengine_sequence_marker_add(seq, 10, "Intro") == OAKENGINE_OK);
|
|
assert(oakengine_sequence_marker_count(seq) == 2);
|
|
assert(oakengine_sequence_marker_at(seq, 0, &ts, name, sizeof(name), NULL) ==
|
|
OAKENGINE_OK);
|
|
assert(ts == 10 && strcmp(name, "Intro") == 0);
|
|
assert(oakengine_sequence_marker_at(seq, 1, &ts, name, sizeof(name), NULL) ==
|
|
OAKENGINE_OK);
|
|
assert(ts == 30 && strcmp(name, "Chapter 1") == 0);
|
|
assert(oakengine_sequence_marker_at(seq, 2, &ts, name, sizeof(name), NULL) ==
|
|
OAKENGINE_E_NOT_FOUND);
|
|
|
|
// Rename with undo/redo.
|
|
assert(oakengine_sequence_marker_rename(seq, 30, "Chapter 2") ==
|
|
OAKENGINE_OK);
|
|
assert(oakengine_sequence_marker_at(seq, 1, &ts, name, sizeof(name), NULL) ==
|
|
OAKENGINE_OK);
|
|
assert(strcmp(name, "Chapter 2") == 0);
|
|
assert(oakengine_sequence_marker_rename(seq, 77, "nope") ==
|
|
OAKENGINE_E_NOT_FOUND);
|
|
assert(oakengine_project_undo(project) == OAKENGINE_OK);
|
|
assert(oakengine_sequence_marker_at(seq, 1, &ts, name, sizeof(name), NULL) ==
|
|
OAKENGINE_OK);
|
|
assert(strcmp(name, "Chapter 1") == 0);
|
|
assert(oakengine_project_redo(project) == OAKENGINE_OK);
|
|
assert(oakengine_sequence_marker_at(seq, 1, &ts, name, sizeof(name), NULL) ==
|
|
OAKENGINE_OK);
|
|
assert(strcmp(name, "Chapter 2") == 0);
|
|
|
|
// Remove with undo; removing again reports not found.
|
|
assert(oakengine_sequence_marker_remove(seq, 10) == OAKENGINE_OK);
|
|
assert(oakengine_sequence_marker_count(seq) == 1);
|
|
assert(oakengine_sequence_marker_remove(seq, 10) ==
|
|
OAKENGINE_E_NOT_FOUND);
|
|
assert(oakengine_project_undo(project) == OAKENGINE_OK);
|
|
assert(oakengine_sequence_marker_count(seq) == 2);
|
|
|
|
// NULL safety.
|
|
assert(oakengine_sequence_marker_add(NULL, 0, "x") ==
|
|
OAKENGINE_E_INVALID);
|
|
assert(oakengine_sequence_marker_remove(NULL, 0) ==
|
|
OAKENGINE_E_INVALID);
|
|
assert(oakengine_sequence_marker_rename(NULL, 0, "x") ==
|
|
OAKENGINE_E_INVALID);
|
|
assert(oakengine_sequence_remove_track(NULL, OAKENGINE_TRACK_TYPE_VIDEO,
|
|
0) == OAKENGINE_E_INVALID);
|
|
|
|
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);
|
|
}
|
|
|
|
static void test_batch_editing(const char *media_path)
|
|
{
|
|
OakEngineProject *project = oakengine_project_create();
|
|
assert(project != NULL);
|
|
assert(oakengine_project_new(project) == OAKENGINE_OK);
|
|
OakEngineSequence *seq = oakengine_sequence_new(project, "Batch");
|
|
assert(seq != NULL);
|
|
assert(oakengine_sequence_add_track(seq, OAKENGINE_TRACK_TYPE_VIDEO) ==
|
|
0);
|
|
OakEngineFootage *footage =
|
|
oakengine_project_import_footage(project, media_path);
|
|
assert(footage != NULL);
|
|
|
|
int64_t in = -1, out = -1;
|
|
|
|
// Two clips on the video track: [0, 100) and [100, 160).
|
|
OakEngineClip *c0 = oakengine_sequence_add_footage_clip(
|
|
seq, footage, OAKENGINE_TRACK_TYPE_VIDEO, 0, 0, 100, 0);
|
|
OakEngineClip *c1 = oakengine_sequence_add_footage_clip(
|
|
seq, footage, OAKENGINE_TRACK_TYPE_VIDEO, 0, 100, 160, 0);
|
|
assert(c0 != NULL && c1 != NULL);
|
|
assert(oakengine_sequence_clip_count(seq, OAKENGINE_TRACK_TYPE_VIDEO,
|
|
0) == 2);
|
|
|
|
// split_clips at 50: the first clip splits in two.
|
|
{
|
|
OakEngineClip *arr[1] = { c0 };
|
|
assert(oakengine_sequence_split_clips(seq, arr, 1, 50) ==
|
|
OAKENGINE_OK);
|
|
}
|
|
assert(oakengine_sequence_clip_count(seq, OAKENGINE_TRACK_TYPE_VIDEO,
|
|
0) == 3);
|
|
OakEngineClip *left = oakengine_sequence_clip_at(
|
|
seq, OAKENGINE_TRACK_TYPE_VIDEO, 0, 0);
|
|
OakEngineClip *right = oakengine_sequence_clip_at(
|
|
seq, OAKENGINE_TRACK_TYPE_VIDEO, 0, 1);
|
|
assert(oakengine_clip_get_range(left, &in, &out, NULL) == OAKENGINE_OK);
|
|
assert(in == 0 && out == 50);
|
|
assert(oakengine_clip_get_range(right, &in, &out, NULL) == OAKENGINE_OK);
|
|
assert(in == 50 && out == 100);
|
|
|
|
// Nothing spans 200; invalid arrays are rejected.
|
|
{
|
|
OakEngineClip *arr[2] = { left, right };
|
|
assert(oakengine_sequence_split_clips(seq, arr, 2, 200) ==
|
|
OAKENGINE_E_NOT_FOUND);
|
|
assert(oakengine_sequence_split_clips(seq, NULL, 1, 50) ==
|
|
OAKENGINE_E_INVALID);
|
|
assert(oakengine_sequence_split_clips(seq, arr, 0, 50) ==
|
|
OAKENGINE_E_INVALID);
|
|
assert(oakengine_sequence_split_clips(NULL, arr, 1, 50) ==
|
|
OAKENGINE_E_INVALID);
|
|
}
|
|
|
|
// Undo the split.
|
|
assert(oakengine_project_undo(project) == OAKENGINE_OK);
|
|
assert(oakengine_sequence_clip_count(seq, OAKENGINE_TRACK_TYPE_VIDEO,
|
|
0) == 2);
|
|
|
|
// delete_clips without ripple: a gap is left, the second clip stays.
|
|
int rippled = -1;
|
|
{
|
|
OakEngineClip *arr[1] = { c0 };
|
|
assert(oakengine_sequence_delete_clips(seq, arr, 1, 0, NULL, 0,
|
|
&rippled) == OAKENGINE_OK);
|
|
}
|
|
assert(rippled == 0);
|
|
assert(oakengine_sequence_clip_count(seq, OAKENGINE_TRACK_TYPE_VIDEO,
|
|
0) == 1);
|
|
assert(oakengine_clip_get_range(c1, &in, &out, NULL) == OAKENGINE_OK);
|
|
assert(in == 100 && out == 160);
|
|
assert(oakengine_project_undo(project) == OAKENGINE_OK);
|
|
assert(oakengine_sequence_clip_count(seq, OAKENGINE_TRACK_TYPE_VIDEO,
|
|
0) == 2);
|
|
|
|
// delete_clips with ripple (auto ranges): the second clip shifts left.
|
|
rippled = -1;
|
|
{
|
|
OakEngineClip *arr[1] = { c0 };
|
|
assert(oakengine_sequence_delete_clips(seq, arr, 1, 1, NULL, 0,
|
|
&rippled) == OAKENGINE_OK);
|
|
}
|
|
assert(rippled == 1);
|
|
assert(oakengine_clip_get_range(c1, &in, &out, NULL) == OAKENGINE_OK);
|
|
assert(in == 0 && out == 60);
|
|
assert(oakengine_project_undo(project) == OAKENGINE_OK);
|
|
assert(oakengine_clip_get_range(c1, &in, &out, NULL) == OAKENGINE_OK);
|
|
assert(in == 100 && out == 160);
|
|
|
|
// delete_clips with an explicit ripple range: same shift.
|
|
rippled = -1;
|
|
{
|
|
OakEngineClip *arr[1] = { c0 };
|
|
const int64_t ranges[4] = { OAKENGINE_TRACK_TYPE_VIDEO, 0, 0, 100 };
|
|
assert(oakengine_sequence_delete_clips(seq, arr, 1, 1, ranges, 1,
|
|
&rippled) == OAKENGINE_OK);
|
|
}
|
|
assert(rippled == 1);
|
|
assert(oakengine_clip_get_range(c1, &in, &out, NULL) == OAKENGINE_OK);
|
|
assert(in == 0 && out == 60);
|
|
assert(oakengine_project_undo(project) == OAKENGINE_OK);
|
|
assert(oakengine_sequence_clip_count(seq, OAKENGINE_TRACK_TYPE_VIDEO,
|
|
0) == 2);
|
|
|
|
// A bad explicit range coordinate is rejected without side effects.
|
|
{
|
|
OakEngineClip *arr[1] = { c0 };
|
|
const int64_t bad[4] = { OAKENGINE_TRACK_TYPE_VIDEO, 9, 0, 100 };
|
|
assert(oakengine_sequence_delete_clips(seq, arr, 1, 1, bad, 1,
|
|
NULL) == OAKENGINE_E_NOT_FOUND);
|
|
}
|
|
assert(oakengine_sequence_clip_count(seq, OAKENGINE_TRACK_TYPE_VIDEO,
|
|
0) == 2);
|
|
assert(oakengine_sequence_delete_clips(NULL, NULL, 0, 0, NULL, 0,
|
|
NULL) == OAKENGINE_E_INVALID);
|
|
|
|
// ripple_delete_range over [0, 100): the area is removed from every
|
|
// track and the following content shifts left.
|
|
assert(oakengine_sequence_ripple_delete_range(seq, 0, 100) ==
|
|
OAKENGINE_OK);
|
|
assert(oakengine_sequence_clip_count(seq, OAKENGINE_TRACK_TYPE_VIDEO,
|
|
0) == 1);
|
|
OakEngineClip *remaining = oakengine_sequence_clip_at(
|
|
seq, OAKENGINE_TRACK_TYPE_VIDEO, 0, 0);
|
|
assert(oakengine_clip_get_range(remaining, &in, &out, NULL) ==
|
|
OAKENGINE_OK);
|
|
assert(in == 0 && out == 60);
|
|
assert(oakengine_sequence_ripple_delete_range(seq, 100, 100) ==
|
|
OAKENGINE_E_INVALID);
|
|
assert(oakengine_sequence_ripple_delete_range(seq, -1, 5) ==
|
|
OAKENGINE_E_INVALID);
|
|
assert(oakengine_sequence_ripple_delete_range(NULL, 0, 1) ==
|
|
OAKENGINE_E_INVALID);
|
|
assert(oakengine_project_undo(project) == OAKENGINE_OK);
|
|
assert(oakengine_sequence_clip_count(seq, OAKENGINE_TRACK_TYPE_VIDEO,
|
|
0) == 2);
|
|
|
|
// Marker with an explicit color.
|
|
assert(oakengine_sequence_marker_add_ex(seq, 10, "colored", 5) ==
|
|
OAKENGINE_OK);
|
|
int color = -1;
|
|
int64_t ts = -1;
|
|
assert(oakengine_sequence_marker_at(seq, 0, &ts, NULL, 0, &color) ==
|
|
OAKENGINE_OK);
|
|
assert(ts == 10 && color == 5);
|
|
assert(oakengine_project_undo(project) == OAKENGINE_OK);
|
|
assert(oakengine_sequence_marker_count(seq) == 0);
|
|
|
|
oakengine_footage_free(footage);
|
|
oakengine_project_free(project);
|
|
}
|
|
|
|
static void test_batch_editing_round2(const char *media_path)
|
|
{
|
|
OakEngineProject *project = oakengine_project_create();
|
|
assert(project != NULL);
|
|
assert(oakengine_project_new(project) == OAKENGINE_OK);
|
|
OakEngineSequence *seq = oakengine_sequence_new(project, "Batch2");
|
|
assert(seq != NULL);
|
|
assert(oakengine_sequence_add_track(seq, OAKENGINE_TRACK_TYPE_VIDEO) ==
|
|
0);
|
|
OakEngineFootage *footage =
|
|
oakengine_project_import_footage(project, media_path);
|
|
assert(footage != NULL);
|
|
|
|
int64_t in = -1, out = -1;
|
|
|
|
OakEngineClip *c0 = oakengine_sequence_add_footage_clip(
|
|
seq, footage, OAKENGINE_TRACK_TYPE_VIDEO, 0, 0, 100, 0);
|
|
assert(c0 != NULL);
|
|
|
|
// trim_clips_to: out-edge to 60, then in-edge to 40 (one clip each).
|
|
assert(oakengine_sequence_trim_clips_to(seq, 1, 60) == 1);
|
|
assert(oakengine_clip_get_range(c0, &in, &out, NULL) == OAKENGINE_OK);
|
|
assert(in == 0 && out == 60);
|
|
assert(oakengine_project_undo(project) == OAKENGINE_OK);
|
|
assert(oakengine_clip_get_range(c0, &in, &out, NULL) == OAKENGINE_OK);
|
|
assert(in == 0 && out == 100);
|
|
assert(oakengine_sequence_trim_clips_to(seq, 0, 40) == 1);
|
|
assert(oakengine_clip_get_range(c0, &in, &out, NULL) == OAKENGINE_OK);
|
|
assert(in == 40 && out == 100);
|
|
assert(oakengine_project_undo(project) == OAKENGINE_OK);
|
|
|
|
// Already at the point: nothing qualifies, 0 and no command.
|
|
assert(oakengine_sequence_trim_clips_to(seq, 1, 100) == 0);
|
|
// Past the content there is no block under the point either (the
|
|
// application's edit_to only shortens blocks containing the point).
|
|
assert(oakengine_sequence_trim_clips_to(seq, 1, 200) == 0);
|
|
assert(oakengine_sequence_trim_clips_to(seq, 0, 200) == 0);
|
|
// Invalid arguments.
|
|
assert(oakengine_sequence_trim_clips_to(seq, 2, 60) ==
|
|
OAKENGINE_E_INVALID);
|
|
assert(oakengine_sequence_trim_clips_to(seq, 1, -1) ==
|
|
OAKENGINE_E_INVALID);
|
|
assert(oakengine_sequence_trim_clips_to(NULL, 1, 60) ==
|
|
OAKENGINE_E_INVALID);
|
|
assert(oakengine_clip_get_range(c0, &in, &out, NULL) == OAKENGINE_OK);
|
|
assert(in == 0 && out == 100);
|
|
|
|
// ripple_delete_in_to_out (ripple): area removed, workarea disabled,
|
|
// one undo entry restores everything including the workarea.
|
|
assert(oakengine_sequence_set_workarea(seq, 1, 20, 80) == OAKENGINE_OK);
|
|
assert(oakengine_sequence_workarea_is_enabled(seq) == 1);
|
|
assert(oakengine_sequence_ripple_delete_in_to_out(seq, 1, 20, 80) ==
|
|
OAKENGINE_OK);
|
|
assert(oakengine_sequence_workarea_is_enabled(seq) == 0);
|
|
assert(oakengine_sequence_clip_count(seq, OAKENGINE_TRACK_TYPE_VIDEO,
|
|
0) == 2);
|
|
OakEngineClip *piece0 = oakengine_sequence_clip_at(
|
|
seq, OAKENGINE_TRACK_TYPE_VIDEO, 0, 0);
|
|
OakEngineClip *piece1 = oakengine_sequence_clip_at(
|
|
seq, OAKENGINE_TRACK_TYPE_VIDEO, 0, 1);
|
|
assert(oakengine_clip_get_range(piece0, &in, &out, NULL) ==
|
|
OAKENGINE_OK);
|
|
assert(in == 0 && out == 20);
|
|
assert(oakengine_clip_get_range(piece1, &in, &out, NULL) ==
|
|
OAKENGINE_OK);
|
|
assert(in == 20 && out == 40);
|
|
assert(oakengine_project_undo(project) == OAKENGINE_OK);
|
|
assert(oakengine_sequence_workarea_is_enabled(seq) == 1);
|
|
assert(oakengine_sequence_clip_count(seq, OAKENGINE_TRACK_TYPE_VIDEO,
|
|
0) == 1);
|
|
assert(oakengine_clip_get_range(c0, &in, &out, NULL) == OAKENGINE_OK);
|
|
assert(in == 0 && out == 100);
|
|
|
|
// ripple_delete_in_to_out (no ripple): a gap fills the area, later
|
|
// content shifts right by the range length.
|
|
assert(oakengine_sequence_ripple_delete_in_to_out(seq, 0, 20, 80) ==
|
|
OAKENGINE_OK);
|
|
assert(oakengine_sequence_workarea_is_enabled(seq) == 0);
|
|
assert(oakengine_sequence_clip_count(seq, OAKENGINE_TRACK_TYPE_VIDEO,
|
|
0) == 2);
|
|
piece0 = oakengine_sequence_clip_at(seq, OAKENGINE_TRACK_TYPE_VIDEO, 0,
|
|
0);
|
|
piece1 = oakengine_sequence_clip_at(seq, OAKENGINE_TRACK_TYPE_VIDEO, 0,
|
|
1);
|
|
assert(oakengine_clip_get_range(piece0, &in, &out, NULL) ==
|
|
OAKENGINE_OK);
|
|
assert(in == 0 && out == 20);
|
|
assert(oakengine_clip_get_range(piece1, &in, &out, NULL) ==
|
|
OAKENGINE_OK);
|
|
assert(in == 80 && out == 100);
|
|
assert(oakengine_project_undo(project) == OAKENGINE_OK);
|
|
assert(oakengine_sequence_clip_count(seq, OAKENGINE_TRACK_TYPE_VIDEO,
|
|
0) == 1);
|
|
|
|
// Workarea disabled: rejected; bad range: invalid (range is validated
|
|
// before the workarea state).
|
|
assert(oakengine_sequence_set_workarea(seq, 0, 20, 80) == OAKENGINE_OK);
|
|
assert(oakengine_sequence_ripple_delete_in_to_out(seq, 1, 20, 80) ==
|
|
OAKENGINE_E_STATE);
|
|
assert(oakengine_sequence_ripple_delete_in_to_out(seq, 1, 80, 20) ==
|
|
OAKENGINE_E_INVALID);
|
|
assert(oakengine_sequence_ripple_delete_in_to_out(NULL, 1, 20, 80) ==
|
|
OAKENGINE_E_INVALID);
|
|
|
|
// delete_empty_tracks: add an empty audio and an empty subtitle track.
|
|
assert(oakengine_sequence_add_track(seq, OAKENGINE_TRACK_TYPE_AUDIO) ==
|
|
0);
|
|
assert(oakengine_sequence_add_track(seq, OAKENGINE_TRACK_TYPE_SUBTITLE) ==
|
|
0);
|
|
int v = 0, a = 0, s = 0;
|
|
assert(oakengine_sequence_track_count(seq, &v, &a, &s) == OAKENGINE_OK);
|
|
assert(v == 1 && a == 1 && s == 1);
|
|
// Type-filtered purge first: only the audio track goes.
|
|
assert(oakengine_sequence_delete_empty_tracks(
|
|
seq, OAKENGINE_TRACK_TYPE_AUDIO) == 1);
|
|
assert(oakengine_sequence_track_count(seq, &v, &a, &s) == OAKENGINE_OK);
|
|
assert(v == 1 && a == 0 && s == 1);
|
|
assert(oakengine_project_undo(project) == OAKENGINE_OK);
|
|
assert(oakengine_sequence_track_count(seq, &v, &a, &s) == OAKENGINE_OK);
|
|
assert(v == 1 && a == 1 && s == 1);
|
|
// All types at once (the application's behavior).
|
|
assert(oakengine_sequence_delete_empty_tracks(seq, -1) == 2);
|
|
assert(oakengine_sequence_track_count(seq, &v, &a, &s) == OAKENGINE_OK);
|
|
assert(v == 1 && a == 0 && s == 0);
|
|
assert(oakengine_project_undo(project) == OAKENGINE_OK);
|
|
assert(oakengine_sequence_track_count(seq, &v, &a, &s) == OAKENGINE_OK);
|
|
assert(v == 1 && a == 1 && s == 1);
|
|
// Nothing empty: 0, no command; invalid type rejected.
|
|
assert(oakengine_sequence_delete_empty_tracks(
|
|
seq, OAKENGINE_TRACK_TYPE_VIDEO) == 0);
|
|
assert(oakengine_sequence_delete_empty_tracks(seq, 3) ==
|
|
OAKENGINE_E_INVALID);
|
|
assert(oakengine_sequence_delete_empty_tracks(NULL, -1) ==
|
|
OAKENGINE_E_INVALID);
|
|
|
|
// marker_remove_many: batch delete by time, one undo entry.
|
|
assert(oakengine_sequence_marker_add_ex(seq, 10, "m1", 0) ==
|
|
OAKENGINE_OK);
|
|
assert(oakengine_sequence_marker_add_ex(seq, 20, "m2", 0) ==
|
|
OAKENGINE_OK);
|
|
assert(oakengine_sequence_marker_add_ex(seq, 30, "m3", 0) ==
|
|
OAKENGINE_OK);
|
|
assert(oakengine_sequence_marker_count(seq) == 3);
|
|
{
|
|
const int64_t times[2] = { 10, 30 };
|
|
assert(oakengine_sequence_marker_remove_many(seq, times, 2) == 2);
|
|
}
|
|
assert(oakengine_sequence_marker_count(seq) == 1);
|
|
assert(oakengine_project_undo(project) == OAKENGINE_OK);
|
|
assert(oakengine_sequence_marker_count(seq) == 3);
|
|
{
|
|
const int64_t bad[1] = { 99 };
|
|
assert(oakengine_sequence_marker_remove_many(seq, bad, 1) ==
|
|
OAKENGINE_E_NOT_FOUND);
|
|
}
|
|
assert(oakengine_sequence_marker_count(seq) == 3);
|
|
assert(oakengine_sequence_marker_remove_many(seq, NULL, 1) ==
|
|
OAKENGINE_E_INVALID);
|
|
assert(oakengine_sequence_marker_remove_many(seq, NULL, 0) == 0);
|
|
assert(oakengine_sequence_marker_remove_many(NULL, NULL, 0) ==
|
|
OAKENGINE_E_INVALID);
|
|
|
|
oakengine_footage_free(footage);
|
|
oakengine_project_free(project);
|
|
}
|
|
|
|
static void test_batch_editing_round3(const char *media_path)
|
|
{
|
|
OakEngineProject *project = oakengine_project_create();
|
|
assert(project != NULL);
|
|
assert(oakengine_project_new(project) == OAKENGINE_OK);
|
|
OakEngineSequence *seq = oakengine_sequence_new(project, "Batch3");
|
|
assert(seq != NULL);
|
|
assert(oakengine_sequence_add_track(seq, OAKENGINE_TRACK_TYPE_VIDEO) ==
|
|
0);
|
|
OakEngineFootage *footage =
|
|
oakengine_project_import_footage(project, media_path);
|
|
assert(footage != NULL);
|
|
|
|
int64_t in = -1, out = -1;
|
|
|
|
// Two adjacent clips: [0, 60) and [60, 100).
|
|
OakEngineClip *c0 = oakengine_sequence_add_footage_clip(
|
|
seq, footage, OAKENGINE_TRACK_TYPE_VIDEO, 0, 0, 60, 0);
|
|
OakEngineClip *c1 = oakengine_sequence_add_footage_clip(
|
|
seq, footage, OAKENGINE_TRACK_TYPE_VIDEO, 0, 60, 100, 0);
|
|
assert(c0 != NULL && c1 != NULL);
|
|
OakEngineClip *two[2] = { c0, c1 };
|
|
|
|
// toggle_enabled flips each clip in one undoable command.
|
|
assert(oakengine_clip_is_enabled(c0) == 1 &&
|
|
oakengine_clip_is_enabled(c1) == 1);
|
|
assert(oakengine_clip_toggle_enabled(two, 2) == 2);
|
|
assert(oakengine_clip_is_enabled(c0) == 0 &&
|
|
oakengine_clip_is_enabled(c1) == 0);
|
|
assert(oakengine_project_undo(project) == OAKENGINE_OK);
|
|
assert(oakengine_clip_is_enabled(c0) == 1 &&
|
|
oakengine_clip_is_enabled(c1) == 1);
|
|
assert(oakengine_clip_toggle_enabled(NULL, 1) == OAKENGINE_E_INVALID);
|
|
assert(oakengine_clip_toggle_enabled(two, 0) == 0);
|
|
assert(oakengine_clip_is_enabled(NULL) == 0);
|
|
|
|
// set_linked: link and unlink are one undoable command each.
|
|
assert(oakengine_clip_are_linked(c0, c1) == 0);
|
|
assert(oakengine_clip_set_linked(two, 2, 1) == OAKENGINE_OK);
|
|
assert(oakengine_clip_are_linked(c0, c1) == 1);
|
|
assert(oakengine_clip_set_linked(two, 2, 0) == OAKENGINE_OK);
|
|
assert(oakengine_clip_are_linked(c0, c1) == 0);
|
|
assert(oakengine_project_undo(project) == OAKENGINE_OK);
|
|
assert(oakengine_clip_are_linked(c0, c1) == 1);
|
|
assert(oakengine_project_undo(project) == OAKENGINE_OK);
|
|
assert(oakengine_clip_are_linked(c0, c1) == 0);
|
|
assert(oakengine_clip_are_linked(c0, NULL) == 0);
|
|
assert(oakengine_clip_set_linked(NULL, 1, 1) == OAKENGINE_E_INVALID);
|
|
assert(oakengine_clip_set_linked(two, 0, 1) == OAKENGINE_OK);
|
|
|
|
// add_default_transition: an in-transition before c0, a dual between
|
|
// the adjacent pair and an out-transition after c1 shrink both clips;
|
|
// one undo restores the exact ranges.
|
|
assert(oakengine_clip_get_range(c0, &in, &out, NULL) == OAKENGINE_OK);
|
|
const int64_t c0_in = in, c0_len = out - in;
|
|
assert(oakengine_clip_get_range(c1, &in, &out, NULL) == OAKENGINE_OK);
|
|
const int64_t c1_len = out - in;
|
|
assert(oakengine_sequence_add_default_transition(seq, two, 2) ==
|
|
OAKENGINE_OK);
|
|
assert(oakengine_clip_get_range(c0, &in, &out, NULL) == OAKENGINE_OK);
|
|
assert(in > c0_in && out - in < c0_len);
|
|
assert(oakengine_clip_get_range(c1, &in, &out, NULL) == OAKENGINE_OK);
|
|
assert(out - in < c1_len);
|
|
assert(oakengine_project_undo(project) == OAKENGINE_OK);
|
|
assert(oakengine_clip_get_range(c0, &in, &out, NULL) == OAKENGINE_OK);
|
|
assert(in == c0_in && out - in == c0_len);
|
|
assert(oakengine_clip_get_range(c1, &in, &out, NULL) == OAKENGINE_OK);
|
|
assert(out - in == c1_len);
|
|
assert(oakengine_sequence_add_default_transition(seq, NULL, 0) ==
|
|
OAKENGINE_OK);
|
|
assert(oakengine_sequence_add_default_transition(NULL, two, 2) ==
|
|
OAKENGINE_E_INVALID);
|
|
|
|
oakengine_footage_free(footage);
|
|
oakengine_project_free(project);
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
make_tmpdir();
|
|
|
|
// Sandbox the config/cache/data locations (see oakengine_init_test).
|
|
#if !defined(_WIN32)
|
|
assert(setenv("XDG_CONFIG_HOME", g_tmpdir, 1) == 0);
|
|
assert(setenv("XDG_CACHE_HOME", g_tmpdir, 1) == 0);
|
|
assert(setenv("XDG_DATA_HOME", g_tmpdir, 1) == 0);
|
|
#endif
|
|
|
|
assert(oakengine_init(OAKENGINE_INIT_HEADLESS) == OAKENGINE_OK);
|
|
|
|
OakEngineProject *project = oakengine_project_create();
|
|
assert(project != NULL);
|
|
assert(oakengine_project_new(project) == OAKENGINE_OK);
|
|
OakEngineSequence *seq = oakengine_sequence_new(project, "Edit");
|
|
assert(seq != NULL);
|
|
|
|
char path[4096];
|
|
demo_path(path, sizeof(path));
|
|
|
|
test_add_track(project, seq);
|
|
test_add_clip(project, seq, path);
|
|
test_cross_project_rejected(path);
|
|
test_edit_round2(path);
|
|
test_track_structure(path);
|
|
test_markers();
|
|
test_sequence_params();
|
|
test_batch_editing(path);
|
|
test_batch_editing_round2(path);
|
|
test_batch_editing_round3(path);
|
|
|
|
oakengine_project_free(project);
|
|
assert(oakengine_shutdown() == OAKENGINE_OK);
|
|
|
|
printf("oakengine_timeline_edit_test: all assertions passed\n");
|
|
return 0;
|
|
}
|