/*** 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 . ***/ // Pure C ABI test for the liboakengine viewer facade (oakengine/viewer.h) // and the viewer events (oakengine/events.h ids 100-110). Exercises every // function of the family on a Sequence (a ViewerOutput subclass): handle // validation, input ids, playhead/length, stream params, enabled streams, // workarea, parameter setup, waveform and the change notifications. No GL // required (headless init, CPU only). Uses tests/demo.mp4 to give the // sequence real content length. #include #include #include #include #include #if defined(_WIN32) #include #include #else #include #endif #include "oakengine/events.h" #include "oakengine/footage.h" #include "oakengine/init.h" #include "oakengine/node.h" #include "oakengine/project.h" #include "oakengine/timeline.h" #include "oakengine/viewer.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); EXPECT_TRUE(len > 0 && len < MAX_PATH); snprintf(g_tmpdir, sizeof(g_tmpdir), "%soakengine_viewer_test_%lu", base, (unsigned long)GetCurrentProcessId()); EXPECT_TRUE(_mkdir(g_tmpdir) == 0); #else strcpy(g_tmpdir, "/tmp/oakengine_viewer_test_XXXXXX"); EXPECT_TRUE(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); EXPECT_TRUE(n > 0 && (size_t)n < cap); } // A sequence handle is the same engine object pointer as its node handle // (all facade handles are reinterpreted engine pointers; see the wrap() // helpers in src/capi/timeline.cpp). static OakEngineNode *as_node(OakEngineSequence *seq) { return (OakEngineNode *)seq; } // ---- Handle validation / constants ---------------------------------------- static void test_from_node(OakEngineProject *project, OakEngineSequence *seq) { OakEngineNode *seq_node = as_node(seq); OakEngineNode *solid = oakengine_project_add_node( project, "org.olivevideoeditor.Olive.solidgenerator"); EXPECT_TRUE(solid != NULL); EXPECT_TRUE(oakengine_viewer_from_node(NULL) == NULL); EXPECT_TRUE(oakengine_viewer_from_node(solid) == NULL); EXPECT_TRUE(oakengine_viewer_from_node(seq_node) == seq_node); EXPECT_TRUE(oakengine_viewer_from_const_node(NULL) == NULL); EXPECT_TRUE(oakengine_viewer_from_const_node((const OakEngineNode *)solid) == NULL); EXPECT_TRUE(oakengine_viewer_from_const_node((const OakEngineNode *)seq_node) == (const OakEngineNode *)seq_node); // The input id constants are static, non-empty strings. EXPECT_TRUE(oakengine_viewer_video_params_input_id() != NULL); EXPECT_TRUE(oakengine_viewer_video_params_input_id()[0] != '\0'); EXPECT_TRUE(oakengine_viewer_audio_params_input_id()[0] != '\0'); EXPECT_TRUE(oakengine_viewer_subtitle_params_input_id()[0] != '\0'); EXPECT_TRUE(oakengine_viewer_texture_input_id()[0] != '\0'); EXPECT_TRUE(oakengine_viewer_samples_input_id()[0] != '\0'); EXPECT_TRUE(oakengine_viewer_default_sample_format() >= 0); } // ---- Playhead / length ------------------------------------------------------ static void test_playhead(OakEngineSequence *seq) { int64_t num = -1, den = -1; EXPECT_TRUE(oakengine_viewer_get_playhead(NULL, &num, &den) == OAKENGINE_E_INVALID); EXPECT_TRUE(oakengine_viewer_get_playhead(as_node(seq), &num, &den) == OAKENGINE_OK); EXPECT_TRUE(num == 0); EXPECT_TRUE(oakengine_viewer_set_playhead(NULL, 1, 1) == OAKENGINE_E_INVALID); EXPECT_TRUE(oakengine_viewer_set_playhead(as_node(seq), 2, 1) == OAKENGINE_OK); num = den = -1; EXPECT_TRUE(oakengine_viewer_get_playhead(as_node(seq), &num, &den) == OAKENGINE_OK); EXPECT_TRUE(num == 2 && den == 1); } static void test_lengths(OakEngineSequence *seq) { int64_t num = -1, den = -1; EXPECT_TRUE(oakengine_viewer_get_length(NULL, &num, &den) == OAKENGINE_E_INVALID); EXPECT_TRUE(oakengine_viewer_get_length(as_node(seq), &num, &den) == OAKENGINE_OK); EXPECT_TRUE(num == 0); EXPECT_TRUE(oakengine_viewer_get_video_length(as_node(seq), &num, &den) == OAKENGINE_OK); EXPECT_TRUE(num == 0); EXPECT_TRUE(oakengine_viewer_get_audio_length(as_node(seq), &num, &den) == OAKENGINE_OK); EXPECT_TRUE(num == 0); } // ---- Stream parameters -------------------------------------------------------- static void test_stream_params(OakEngineSequence *seq) { const OakEngineNode *node = (const OakEngineNode *)as_node(seq); oak_video_params vp; int sr = -1, format = -1; uint64_t layout = 1; EXPECT_TRUE(oakengine_viewer_get_video_params(NULL, 0, &vp) == OAKENGINE_E_INVALID); EXPECT_TRUE(oakengine_viewer_get_video_params(node, 0, NULL) == OAKENGINE_E_INVALID); // A fresh sequence has one video and one audio stream, no subtitles. EXPECT_TRUE(oakengine_viewer_get_video_stream_count(NULL) == 0); EXPECT_TRUE(oakengine_viewer_get_video_stream_count(node) == 1); EXPECT_TRUE(oakengine_viewer_get_audio_stream_count(node) == 1); EXPECT_TRUE(oakengine_viewer_get_subtitle_stream_count(node) == 0); // In-range video params come from the sequence defaults. EXPECT_TRUE(oakengine_viewer_get_video_params(node, 0, &vp) == OAKENGINE_OK); EXPECT_TRUE(vp.width > 0 && vp.height > 0); EXPECT_TRUE(vp.time_base_num > 0 && vp.time_base_den > 0); // Out-of-range yields a zeroed struct (documented in viewer.h). memset(&vp, 0xFF, sizeof(vp)); EXPECT_TRUE(oakengine_viewer_get_video_params(node, 99, &vp) == OAKENGINE_OK); EXPECT_TRUE(vp.width == 0 && vp.height == 0); // Audio params; out-of-range yields 0/0/0. EXPECT_TRUE(oakengine_viewer_get_audio_params(NULL, 0, &sr, &layout, &format) == OAKENGINE_E_INVALID); EXPECT_TRUE(oakengine_viewer_get_audio_params(node, 0, &sr, &layout, &format) == OAKENGINE_OK); EXPECT_TRUE(sr > 0 && layout != 0); sr = -1; layout = 1; format = -1; EXPECT_TRUE(oakengine_viewer_get_audio_params(node, 99, &sr, &layout, &format) == OAKENGINE_OK); EXPECT_TRUE(sr == 0 && layout == 0 && format == 0); // Per-stream enabled flags: video/audio stream 0 are enabled by // default; subtitle has no stream 0. EXPECT_TRUE(oakengine_viewer_get_stream_enabled(NULL, OAKENGINE_TRACK_TYPE_VIDEO, 0) == OAKENGINE_E_INVALID); EXPECT_TRUE(oakengine_viewer_get_stream_enabled(node, OAKENGINE_TRACK_TYPE_VIDEO, 0) == 1); EXPECT_TRUE(oakengine_viewer_get_stream_enabled(node, OAKENGINE_TRACK_TYPE_AUDIO, 0) == 1); EXPECT_TRUE(oakengine_viewer_get_stream_enabled( node, OAKENGINE_TRACK_TYPE_SUBTITLE, 0) == 0); EXPECT_TRUE(oakengine_viewer_get_stream_enabled(node, 99, 0) == OAKENGINE_E_INVALID); // Subtitle access: no subtitle streams on a fresh sequence. EXPECT_TRUE(oakengine_viewer_get_subtitle_count(NULL, 0) == OAKENGINE_E_INVALID); EXPECT_TRUE(oakengine_viewer_get_subtitle_count(node, 0) == OAKENGINE_E_INVALID); EXPECT_TRUE(oakengine_viewer_get_subtitle_at(NULL, 0, 0) == NULL); EXPECT_TRUE(oakengine_viewer_get_subtitle_at(node, 0, 0) == NULL); } static void test_enabled_streams(OakEngineSequence *seq) { const OakEngineNode *node = (const OakEngineNode *)as_node(seq); oak_video_params vp; EXPECT_TRUE(oakengine_viewer_has_enabled_streams(NULL, OAKENGINE_TRACK_TYPE_VIDEO) == 0); EXPECT_TRUE(oakengine_viewer_has_enabled_streams(node, OAKENGINE_TRACK_TYPE_VIDEO) == 1); EXPECT_TRUE(oakengine_viewer_has_enabled_streams(node, OAKENGINE_TRACK_TYPE_AUDIO) == 1); EXPECT_TRUE(oakengine_viewer_has_enabled_streams( node, OAKENGINE_TRACK_TYPE_SUBTITLE) == 0); EXPECT_TRUE(oakengine_viewer_has_enabled_streams(node, 99) == 0); EXPECT_TRUE(oakengine_viewer_get_first_enabled_video_stream(NULL, &vp) == OAKENGINE_E_INVALID); EXPECT_TRUE(oakengine_viewer_get_first_enabled_video_stream(node, &vp) == OAKENGINE_OK); EXPECT_TRUE(vp.width > 0 && vp.height > 0); // Enabled stream references: video:0 and audio:0. EXPECT_TRUE(oakengine_viewer_get_enabled_stream_count(NULL) == 0); const int count = oakengine_viewer_get_enabled_stream_count(node); EXPECT_TRUE(count == 2); // Query form (max = 0, NULL arrays) returns the total count. EXPECT_TRUE(oakengine_viewer_get_enabled_streams(node, NULL, NULL, 0) == count); int types[8]; int indices[8]; memset(types, -1, sizeof(types)); memset(indices, -1, sizeof(indices)); EXPECT_TRUE(oakengine_viewer_get_enabled_streams(node, types, indices, 8) == count); int saw_video = 0, saw_audio = 0; for (int i = 0; i < count; i++) { EXPECT_TRUE(indices[i] == 0); if (types[i] == OAKENGINE_TRACK_TYPE_VIDEO) { saw_video = 1; } else if (types[i] == OAKENGINE_TRACK_TYPE_AUDIO) { saw_audio = 1; } else { EXPECT_TRUE(0); // unexpected stream type } } EXPECT_TRUE(saw_video && saw_audio); // A smaller max truncates the write but still returns the total. types[0] = types[1] = -1; indices[0] = indices[1] = -1; EXPECT_TRUE(oakengine_viewer_get_enabled_streams(node, types, indices, 1) == count); EXPECT_TRUE(types[0] != -1 && types[1] == -1); } // ---- Workarea ------------------------------------------------------------------ static void test_workarea(OakEngineSequence *seq) { OakEngineNode *node = as_node(seq); oakengine_viewer_workarea wa; EXPECT_TRUE(oakengine_viewer_get_workarea(NULL, &wa) == OAKENGINE_E_INVALID); EXPECT_TRUE(oakengine_viewer_get_workarea(node, NULL) == OAKENGINE_E_INVALID); memset(&wa, 0xFF, sizeof(wa)); EXPECT_TRUE(oakengine_viewer_get_workarea(node, &wa) == OAKENGINE_OK); EXPECT_TRUE(wa.enabled == 0); EXPECT_TRUE(oakengine_viewer_set_workarea_range(NULL, 0, 1, 1, 1) == OAKENGINE_E_INVALID); EXPECT_TRUE(oakengine_viewer_set_workarea_range(node, 1, 1, 5, 1) == OAKENGINE_OK); EXPECT_TRUE(oakengine_viewer_set_workarea_enabled(NULL, 1) == OAKENGINE_E_INVALID); EXPECT_TRUE(oakengine_viewer_set_workarea_enabled(node, 1) == OAKENGINE_OK); memset(&wa, 0, sizeof(wa)); EXPECT_TRUE(oakengine_viewer_get_workarea(node, &wa) == OAKENGINE_OK); EXPECT_TRUE(wa.in_num == 1 && wa.in_den == 1); EXPECT_TRUE(wa.out_num == 5 && wa.out_den == 1); EXPECT_TRUE(wa.enabled == 1); EXPECT_TRUE(oakengine_viewer_set_workarea_enabled(node, 0) == OAKENGINE_OK); EXPECT_TRUE(oakengine_viewer_get_workarea(node, &wa) == OAKENGINE_OK); EXPECT_TRUE(wa.enabled == 0); } // ---- Parameter setup / waveform ------------------------------------------------- static void test_parameter_setup(OakEngineProject *project, OakEngineSequence *seq) { OakEngineNode *node = as_node(seq); EXPECT_TRUE(oakengine_viewer_set_default_parameters(NULL) == OAKENGINE_E_INVALID); EXPECT_TRUE(oakengine_viewer_set_default_parameters(node) == OAKENGINE_OK); // set_parameters_from_footage accepts any viewer handles; a second // sequence stands in for the footage array here. OakEngineSequence *other = oakengine_sequence_new(project, "Other"); EXPECT_TRUE(other != NULL); OakEngineNode *other_node = as_node(other); EXPECT_TRUE(oakengine_viewer_set_parameters_from_footage(NULL, &other_node, 1) == OAKENGINE_E_INVALID); EXPECT_TRUE(oakengine_viewer_set_parameters_from_footage(node, NULL, 1) == OAKENGINE_E_INVALID); // An empty array is a valid no-op. EXPECT_TRUE(oakengine_viewer_set_parameters_from_footage(node, NULL, 0) == OAKENGINE_OK); // One invalid element rejects the whole call. OakEngineNode *solid = oakengine_project_add_node( project, "org.olivevideoeditor.Olive.solidgenerator"); EXPECT_TRUE(solid != NULL); OakEngineNode *mixed[2] = { other_node, solid }; EXPECT_TRUE(oakengine_viewer_set_parameters_from_footage(node, mixed, 2) == OAKENGINE_E_INVALID); // All viewers: OK, and the params are adopted. OakEngineNode *viewers[1] = { other_node }; EXPECT_TRUE(oakengine_viewer_set_parameters_from_footage(node, viewers, 1) == OAKENGINE_OK); // Waveform toggle; nothing is connected to the samples input, so the // connected waveform is NULL. EXPECT_TRUE(oakengine_viewer_set_waveform_enabled(NULL, 1) == OAKENGINE_E_INVALID); EXPECT_TRUE(oakengine_viewer_set_waveform_enabled(node, 1) == OAKENGINE_OK); EXPECT_TRUE(oakengine_viewer_set_waveform_enabled(node, 0) == OAKENGINE_OK); EXPECT_TRUE(oakengine_viewer_get_connected_waveform(NULL) == NULL); EXPECT_TRUE(oakengine_viewer_get_connected_waveform( (const OakEngineNode *)node) == NULL); } // ---- Events --------------------------------------------------------------------- struct EventLog { int playhead_events; int64_t playhead_num; int64_t playhead_den; int length_events; int64_t length_num; int64_t length_den; int size_events; int64_t size_w; int64_t size_h; int video_params_events; int audio_params_events; int sample_rate_events; int64_t sample_rate; int texture_events; int frame_rate_events; int pixel_aspect_events; int interlacing_events; int64_t interlacing_mode; int waveform_events; }; static void record_event(const oakengine_event *event, void *userdata) { struct EventLog *log = (struct EventLog *)userdata; EXPECT_TRUE(event != NULL); switch (event->id) { case OAKENGINE_EVENT_VIEWER_PLAYHEAD_CHANGED: log->playhead_events++; log->playhead_num = event->a; log->playhead_den = event->b; break; case OAKENGINE_EVENT_VIEWER_LENGTH_CHANGED: log->length_events++; log->length_num = event->a; log->length_den = event->b; break; case OAKENGINE_EVENT_VIEWER_SIZE_CHANGED: log->size_events++; log->size_w = event->a; log->size_h = event->b; break; case OAKENGINE_EVENT_VIEWER_VIDEO_PARAMS_CHANGED: log->video_params_events++; break; case OAKENGINE_EVENT_VIEWER_AUDIO_PARAMS_CHANGED: log->audio_params_events++; break; case OAKENGINE_EVENT_VIEWER_SAMPLE_RATE_CHANGED: log->sample_rate_events++; log->sample_rate = event->a; break; case OAKENGINE_EVENT_VIEWER_TEXTURE_INPUT_CHANGED: log->texture_events++; break; case OAKENGINE_EVENT_VIEWER_FRAME_RATE_CHANGED: log->frame_rate_events++; break; case OAKENGINE_EVENT_VIEWER_PIXEL_ASPECT_CHANGED: log->pixel_aspect_events++; break; case OAKENGINE_EVENT_VIEWER_INTERLACING_CHANGED: log->interlacing_events++; log->interlacing_mode = event->a; break; case OAKENGINE_EVENT_VIEWER_CONNECTED_WAVEFORM_CHANGED: log->waveform_events++; break; default: EXPECT_TRUE(0); // unexpected event id on this subscription } } static int64_t subscribe_checked(OakEngineNode *node, int32_t id, struct EventLog *log) { const int64_t sub = oakengine_event_subscribe(node, id, record_event, log); EXPECT_TRUE(sub > 0); return sub; } static void test_events(OakEngineProject *project, OakEngineSequence *seq, const char *media_path) { struct EventLog log; memset(&log, 0, sizeof(log)); OakEngineNode *node = as_node(seq); // Family mismatch: a viewer event on a non-viewer node must fail. OakEngineNode *solid = oakengine_project_add_node( project, "org.olivevideoeditor.Olive.solidgenerator"); EXPECT_TRUE(solid != NULL); EXPECT_TRUE(oakengine_event_subscribe(solid, OAKENGINE_EVENT_VIEWER_PLAYHEAD_CHANGED, record_event, &log) == 0); int64_t subs[16]; int n = 0; subs[n++] = subscribe_checked(node, OAKENGINE_EVENT_VIEWER_LENGTH_CHANGED, &log); subs[n++] = subscribe_checked(node, OAKENGINE_EVENT_VIEWER_PLAYHEAD_CHANGED, &log); subs[n++] = subscribe_checked( node, OAKENGINE_EVENT_VIEWER_FRAME_RATE_CHANGED, &log); subs[n++] = subscribe_checked(node, OAKENGINE_EVENT_VIEWER_SIZE_CHANGED, &log); subs[n++] = subscribe_checked( node, OAKENGINE_EVENT_VIEWER_PIXEL_ASPECT_CHANGED, &log); subs[n++] = subscribe_checked( node, OAKENGINE_EVENT_VIEWER_INTERLACING_CHANGED, &log); subs[n++] = subscribe_checked( node, OAKENGINE_EVENT_VIEWER_VIDEO_PARAMS_CHANGED, &log); subs[n++] = subscribe_checked( node, OAKENGINE_EVENT_VIEWER_AUDIO_PARAMS_CHANGED, &log); subs[n++] = subscribe_checked( node, OAKENGINE_EVENT_VIEWER_TEXTURE_INPUT_CHANGED, &log); subs[n++] = subscribe_checked( node, OAKENGINE_EVENT_VIEWER_SAMPLE_RATE_CHANGED, &log); subs[n++] = subscribe_checked( node, OAKENGINE_EVENT_VIEWER_CONNECTED_WAVEFORM_CHANGED, &log); // Playhead. EXPECT_TRUE(oakengine_viewer_set_playhead(node, 3, 1) == OAKENGINE_OK); EXPECT_TRUE(log.playhead_events == 1); EXPECT_TRUE(log.playhead_num == 3 && log.playhead_den == 1); // Length: placing a real clip makes verify_length() emit // length_changed with the new content length. OakEngineFootage *footage = oakengine_project_import_footage(project, media_path); EXPECT_TRUE(footage != NULL); EXPECT_TRUE(oakengine_sequence_add_track(seq, OAKENGINE_TRACK_TYPE_VIDEO) == 0); OakEngineClip *clip = oakengine_sequence_add_footage_clip( seq, footage, OAKENGINE_TRACK_TYPE_VIDEO, 0, 0, 30, 0); EXPECT_TRUE(clip != NULL); EXPECT_TRUE(log.length_events >= 1); EXPECT_TRUE(log.length_num > 0 && log.length_den > 0); int64_t len_num = -1, len_den = -1; EXPECT_TRUE(oakengine_viewer_get_length(node, &len_num, &len_den) == OAKENGINE_OK); EXPECT_TRUE(len_num == log.length_num && len_den == log.length_den); // Video params: changing the size emits size_changed (with the new // dimensions as a/b) and video_params_changed. EXPECT_TRUE(oakengine_sequence_set_video_params(seq, 1280, 720, -1, -1, -1, -1, -1, -1, 0) == OAKENGINE_OK); EXPECT_TRUE(log.size_events == 1); EXPECT_TRUE(log.size_w == 1280 && log.size_h == 720); EXPECT_TRUE(log.video_params_events == 1); EXPECT_TRUE(log.frame_rate_events == 0); EXPECT_TRUE(log.pixel_aspect_events == 0); EXPECT_TRUE(log.interlacing_events == 0); // Pixel aspect and interlacing changes fire their own events. EXPECT_TRUE(oakengine_sequence_set_video_params(seq, -1, -1, -1, -1, 4, 3, -1, -1, 0) == OAKENGINE_OK); EXPECT_TRUE(log.pixel_aspect_events == 1); EXPECT_TRUE(oakengine_sequence_set_video_params(seq, -1, -1, -1, -1, -1, -1, 1, -1, 0) == OAKENGINE_OK); EXPECT_TRUE(log.interlacing_events == 1); EXPECT_TRUE(log.interlacing_mode == 1); // Audio params: a new sample rate emits sample_rate_changed (a = rate) // and audio_params_changed. EXPECT_TRUE(oakengine_sequence_set_audio_params(seq, 44100, 0, 0) == OAKENGINE_OK); EXPECT_TRUE(log.sample_rate_events == 1); EXPECT_TRUE(log.sample_rate == 44100); EXPECT_TRUE(log.audio_params_events == 1); // Texture input: the placed clip's track auto-connected the viewer's // texture input, so disconnect it first, then connect a node and check // that texture_input_changed fired. EXPECT_TRUE(oakengine_node_disconnect(node, oakengine_viewer_texture_input_id()) == OAKENGINE_OK); EXPECT_TRUE(oakengine_node_connect(solid, node, oakengine_viewer_texture_input_id()) == OAKENGINE_OK); EXPECT_TRUE(log.texture_events >= 1); EXPECT_TRUE(oakengine_node_disconnect(node, oakengine_viewer_texture_input_id()) == OAKENGINE_OK); // connected_waveform_changed requires a connected sample output with a // waveform cache; there is no audio-producing node chain in this test, // so only the subscription itself is exercised above. while (n > 0) { EXPECT_TRUE(oakengine_event_unsubscribe(subs[--n]) == OAKENGINE_OK); } } TEST(OakEngineViewer, Main) { make_tmpdir(); // Sandbox the config/cache/data locations. #if !defined(_WIN32) EXPECT_TRUE(setenv("XDG_CONFIG_HOME", g_tmpdir, 1) == 0); EXPECT_TRUE(setenv("XDG_CACHE_HOME", g_tmpdir, 1) == 0); EXPECT_TRUE(setenv("XDG_DATA_HOME", g_tmpdir, 1) == 0); #endif EXPECT_TRUE(oakengine_init(OAKENGINE_INIT_HEADLESS) == OAKENGINE_OK); OakEngineProject *project = oakengine_project_create(); EXPECT_TRUE(project != NULL); EXPECT_TRUE(oakengine_project_new(project) == OAKENGINE_OK); OakEngineSequence *seq = oakengine_sequence_new(project, "ViewerSeq"); EXPECT_TRUE(seq != NULL); test_from_node(project, seq); test_playhead(seq); test_lengths(seq); test_stream_params(seq); test_enabled_streams(seq); test_workarea(seq); test_parameter_setup(project, seq); char media[4096]; demo_path(media, sizeof(media)); // test_parameter_setup() called set_default_parameters(), which reads // the (empty, sandboxed) user config and may leave invalid params // behind (same hazard oakengine_sequence_new() backfills against). // Restore known-good params so the clip/timebase paths work. EXPECT_TRUE(oakengine_sequence_set_video_params(seq, 1920, 1080, 30000, 1001, 1, 1, 0, -1, 0) == OAKENGINE_OK); EXPECT_TRUE(oakengine_sequence_set_audio_params(seq, 48000, 3, 0) == OAKENGINE_OK); test_events(project, seq, media); oakengine_project_free(project); EXPECT_TRUE(oakengine_shutdown() == OAKENGINE_OK); }