engine: add media management (relink/proxy) to the footage facade

- relink through Footage::set_filename with the engine's natural
  clear+reprobe cascade; find_offline_footage recovers offline media
  by exact filename match under a search directory
- synchronous proxy generate (event-loop driven like the export
  family), state query, enable/disable, path, and delete, mirroring
  the app's proxy dialog assembly
- documented as non-undoable, matching the app's existing semantics
This commit is contained in:
2026-07-20 08:53:43 +08:00
parent 71b1243ad8
commit 882ce8268c
3 changed files with 473 additions and 0 deletions
+174
View File
@@ -238,6 +238,177 @@ static void test_failures(void)
OAKENGINE_E_INVALID);
}
// Copy a file in C (used to stage a deletable media copy).
static void copy_file(const char *src, const char *dst)
{
FILE *in = fopen(src, "rb");
assert(in != NULL);
FILE *out = fopen(dst, "wb");
assert(out != NULL);
char chunk[65536];
size_t n;
while ((n = fread(chunk, 1, sizeof(chunk), in)) > 0) {
assert(fwrite(chunk, 1, n, out) == n);
}
fclose(out);
fclose(in);
}
static void test_relink(void)
{
assert(oakengine_init(OAKENGINE_INIT_HEADLESS) == OAKENGINE_OK);
OakEngineProject *project = oakengine_project_create();
assert(project != NULL);
assert(oakengine_project_new(project) == OAKENGINE_OK);
char path[4096], img[4096], missing[4096], txt[4096];
demo_path(path, sizeof(path));
snprintf(img, sizeof(img), "%s/tests/img.png", OAK_TEST_SOURCE_DIR);
snprintf(missing, sizeof(missing), "%s/tests/not-there.mp4",
OAK_TEST_SOURCE_DIR);
snprintf(txt, sizeof(txt), "%s/not-media.txt", g_tmpdir);
{
FILE *f = fopen(txt, "w");
assert(f != NULL);
fputs("not media\n", f);
fclose(f);
}
OakEngineFootage *f = oakengine_project_import_footage(project, path);
assert(f != NULL);
// Missing target, non-media target, probe handles, NULL.
assert(oakengine_footage_relink(f, missing) == OAKENGINE_E_NOT_FOUND);
assert(oakengine_footage_relink(f, txt) == OAKENGINE_E_FAILED);
assert(oakengine_footage_relink(NULL, path) == OAKENGINE_E_INVALID);
OakEngineFootage *probed = oakengine_footage_probe(path);
assert(probed != NULL);
assert(oakengine_footage_relink(probed, img) == OAKENGINE_E_INVALID);
oakengine_footage_free(probed);
// Relink to the same file: still valid, still online.
assert(oakengine_footage_relink(f, path) == OAKENGINE_OK);
assert(oakengine_footage_is_online(f) == 1);
assert(oakengine_footage_get_video_stream_count(f) == 1);
// Relink to a still image: decoder/filename update.
assert(oakengine_footage_relink(f, img) == OAKENGINE_OK);
char filename[4096];
assert(oakengine_project_footage_filename(project, 0, filename,
sizeof(filename)) > 0);
assert(strcmp(filename, img) == 0);
char decoder[64];
assert(oakengine_footage_get_decoder_name(f, decoder,
sizeof(decoder)) > 0);
assert(strlen(decoder) > 0);
assert(oakengine_footage_get_video_stream_count(f) >= 1);
assert(oakengine_footage_is_online(f) == 1);
// And back to the demo file.
assert(oakengine_footage_relink(f, path) == OAKENGINE_OK);
assert(oakengine_project_footage_filename(project, 0, filename,
sizeof(filename)) > 0);
assert(strcmp(filename, path) == 0);
oakengine_footage_free(f);
oakengine_project_free(project);
}
static void test_find_offline(void)
{
OakEngineProject *project = oakengine_project_create();
assert(project != NULL);
assert(oakengine_project_new(project) == OAKENGINE_OK);
// Import a staged copy, then delete it so the footage goes offline.
char staged[4096], tests_dir[4096];
snprintf(staged, sizeof(staged), "%s/demo.mp4", g_tmpdir);
snprintf(tests_dir, sizeof(tests_dir), "%s/tests", OAK_TEST_SOURCE_DIR);
char path[4096];
demo_path(path, sizeof(path));
copy_file(path, staged);
OakEngineFootage *f = oakengine_project_import_footage(project, staged);
assert(f != NULL);
assert(oakengine_project_footage_is_online(project, 0) == 1);
assert(remove(staged) == 0);
assert(oakengine_project_footage_is_online(project, 0) == 0);
// The search directory has a file with the same name: relinked.
assert(oakengine_project_find_offline_footage(project, tests_dir) == 1);
assert(oakengine_project_footage_is_online(project, 0) == 1);
char filename[4096];
assert(oakengine_project_footage_filename(project, 0, filename,
sizeof(filename)) > 0);
assert(strcmp(filename, path) == 0);
// Nothing left to do; a missing search directory is an error.
assert(oakengine_project_find_offline_footage(project, tests_dir) == 0);
assert(oakengine_project_find_offline_footage(project,
"/definitely/not/here") ==
OAKENGINE_E_INVALID);
assert(oakengine_project_find_offline_footage(NULL, tests_dir) ==
OAKENGINE_E_INVALID);
oakengine_footage_free(f);
oakengine_project_free(project);
}
static void test_proxy(void)
{
OakEngineProject *project = oakengine_project_create();
assert(project != NULL);
assert(oakengine_project_new(project) == OAKENGINE_OK);
char path[4096];
demo_path(path, sizeof(path));
OakEngineFootage *f = oakengine_project_import_footage(project, path);
assert(f != NULL);
assert(oakengine_footage_proxy_get_state(f) == 0); // missing
assert(oakengine_footage_proxy_get_state(NULL) == OAKENGINE_E_INVALID);
// Generate synchronously (CPU transcode of the 17 s demo file).
assert(oakengine_footage_proxy_generate(f) == OAKENGINE_OK);
assert(oakengine_footage_proxy_get_state(f) == 2); // ready
char proxy_path[4096];
assert(oakengine_footage_proxy_get_path(f, proxy_path,
sizeof(proxy_path)) > 0);
FILE *pf = fopen(proxy_path, "rb");
assert(pf != NULL);
fclose(pf);
assert(oakengine_footage_proxy_is_enabled(f) == 1);
// Enable/disable round-trip.
assert(oakengine_footage_proxy_set_enabled(f, 0) == OAKENGINE_OK);
assert(oakengine_footage_proxy_is_enabled(f) == 0);
assert(oakengine_footage_proxy_set_enabled(f, 1) == OAKENGINE_OK);
assert(oakengine_footage_proxy_is_enabled(f) == 1);
// Delete: state and file are gone.
assert(oakengine_footage_proxy_delete(f) == OAKENGINE_OK);
assert(oakengine_footage_proxy_get_state(f) == 0);
assert(fopen(proxy_path, "rb") == NULL);
// Error paths.
assert(oakengine_footage_proxy_generate(NULL) == OAKENGINE_E_INVALID);
assert(oakengine_footage_proxy_delete(NULL) == OAKENGINE_E_INVALID);
assert(oakengine_footage_proxy_set_enabled(NULL, 1) ==
OAKENGINE_E_INVALID);
assert(oakengine_footage_proxy_get_path(NULL, proxy_path,
sizeof(proxy_path)) ==
OAKENGINE_E_INVALID);
assert(oakengine_footage_proxy_is_enabled(NULL) == OAKENGINE_E_INVALID);
OakEngineFootage *probed = oakengine_footage_probe(path);
assert(probed != NULL);
assert(oakengine_footage_proxy_generate(probed) == OAKENGINE_E_INVALID);
oakengine_footage_free(probed);
oakengine_footage_free(f);
oakengine_project_free(project);
}
int main(void)
{
make_tmpdir();
@@ -255,6 +426,9 @@ int main(void)
test_probe();
test_import();
test_failures();
test_relink();
test_find_offline();
test_proxy();
assert(oakengine_shutdown() == OAKENGINE_OK);