Export im Hintergrund mit Fortschritt, Abbruch und Download
Exporte laufen nacheinander. Der Fortschritt steht im Speicher; ein nach einem Neustart verwaister Export wird als unterbrochen gemeldet. Nach Änderungen an Einstellungen oder Dateien gilt ein fertiges Video als veraltet. Der Download trägt den Zeitraum im Dateinamen. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
03bfda09b2
commit
6e497ffc45
4 files changed
+183
-8
No files matched your search
+63
-1
@@ -5,11 +5,24 @@ import path from 'node:path';
|
||||
import { after, before, test } from 'node:test';
|
||||
import { createApp } from '../server/app.js';
|
||||
import { createProjectStore } from '../server/projects.js';
|
||||
import { createRenderQueue } from '../server/renderQueue.js';
|
||||
|
||||
let server;
|
||||
let baseUrl;
|
||||
let cookie;
|
||||
let dataDir;
|
||||
let releaseRender;
|
||||
|
||||
// Steuerbare Attrappe: meldet halben Fortschritt und wartet auf releaseRender().
|
||||
async function fakeRender({ output, onProgress, signal }) {
|
||||
onProgress(0.5);
|
||||
await new Promise((resolve, reject) => {
|
||||
releaseRender = resolve;
|
||||
signal.addEventListener('abort', () => reject(new Error('Abgebrochen.')));
|
||||
});
|
||||
await fs.writeFile(output, 'VIDEO');
|
||||
onProgress(1);
|
||||
}
|
||||
|
||||
const fakeMedia = { duration: 42, hasVideo: true, width: 1080, height: 1920, rotation: 0, fps: 30, hasAudio: true };
|
||||
|
||||
@@ -21,7 +34,8 @@ before(async () => {
|
||||
return fakeMedia;
|
||||
};
|
||||
const store = createProjectStore({ dataDir, probe });
|
||||
const app = createApp({ config: { pin: '4711' }, secret: 'geheim', store });
|
||||
const queue = createRenderQueue({ store, render: fakeRender });
|
||||
const app = createApp({ config: { pin: '4711' }, secret: 'geheim', store, queue });
|
||||
server = app.listen(0);
|
||||
await new Promise((resolve) => server.once('listening', resolve));
|
||||
baseUrl = `http://127.0.0.1:${server.address().port}`;
|
||||
@@ -187,3 +201,51 @@ test('Zusatzdateien anmelden, hochladen, ersetzen und löschen', async () => {
|
||||
res = await call('DELETE', `${base}/voice-0`);
|
||||
assert.deepEqual((await res.json()).assets, {});
|
||||
});
|
||||
|
||||
async function waitForRender(id, status) {
|
||||
for (let i = 0; i < 100; i += 1) {
|
||||
const project = await (await call('GET', `projects/${id}`)).json();
|
||||
if (project.render.status === status) return project;
|
||||
await new Promise((resolve) => setTimeout(resolve, 20));
|
||||
}
|
||||
throw new Error(`Status ${status} nicht erreicht`);
|
||||
}
|
||||
|
||||
test('Export ohne fertige Clips wird abgelehnt', async () => {
|
||||
const project = await createProject([clip('a.mp4', 5)]);
|
||||
assert.equal(project.render.status, 'idle');
|
||||
assert.equal((await call('POST', `projects/${project.id}/render`)).status, 400);
|
||||
assert.equal((await call('GET', `projects/${project.id}/output`)).status, 404);
|
||||
});
|
||||
|
||||
test('Export läuft, meldet Fortschritt, liefert die Datei und erkennt Änderungen', async () => {
|
||||
const project = await createProject([clip('a.mp4', 5)]);
|
||||
await upload(project.id, 0, 0, 'abcde');
|
||||
|
||||
assert.equal((await call('POST', `projects/${project.id}/render`)).status, 202);
|
||||
const running = await waitForRender(project.id, 'running');
|
||||
assert.equal(running.render.progress, 0.5);
|
||||
assert.equal((await call('POST', `projects/${project.id}/render`)).status, 409);
|
||||
|
||||
releaseRender();
|
||||
const done = await waitForRender(project.id, 'done');
|
||||
assert.equal(done.render.outdated, false);
|
||||
|
||||
const download = await call('GET', `projects/${project.id}/output?download=1`);
|
||||
assert.equal(await download.text(), 'VIDEO');
|
||||
assert.match(download.headers.get('content-disposition'), /FuerDieCuts_2026-09-21_bis_2026-09-25\.mp4/);
|
||||
|
||||
const changed = await (await call('PATCH', `projects/${project.id}/settings`, { speedUp: false })).json();
|
||||
assert.equal(changed.render.outdated, true);
|
||||
});
|
||||
|
||||
test('Laufender Export lässt sich abbrechen', async () => {
|
||||
const project = await createProject([clip('a.mp4', 5)]);
|
||||
await upload(project.id, 0, 0, 'abcde');
|
||||
await call('POST', `projects/${project.id}/render`);
|
||||
await waitForRender(project.id, 'running');
|
||||
assert.equal((await call('DELETE', `projects/${project.id}/render`)).status, 202);
|
||||
const cancelled = await waitForRender(project.id, 'cancelled');
|
||||
assert.equal(cancelled.render.error, null);
|
||||
assert.equal((await call('DELETE', `projects/${project.id}/render`)).status, 404);
|
||||
});
|
||||
Reference in new issue
Block a user