diff --git a/movie_editor/frontend/block_sweep_config.test.js b/movie_editor/frontend/block_sweep_config.test.js index 71df4fb1..01d4a691 100644 --- a/movie_editor/frontend/block_sweep_config.test.js +++ b/movie_editor/frontend/block_sweep_config.test.js @@ -8,6 +8,19 @@ const src = fs.readFileSync(__dirname + "/store.js", "utf8"); const grab = (name) => src.match(new RegExp(`function ${name}[\\s\\S]*?\\n \\}\\n`))[0]; const { parseBlockSweepConfig } = new Function( grab("parseBlockSweepConfig") + "; return { parseBlockSweepConfig };")(); +const { _sweepFilenamePrefix } = new Function( + grab("_sweepFilenamePrefix") + "; return { _sweepFilenamePrefix };")(); + +test("the render's own filename_prefix carries the sweep config, not a generic uuid", () => { + const prefix = _sweepFilenamePrefix("10,11,13;seam;5"); + assert.match(prefix, /^funpack_sweep_10-11-13-seam-5_[a-z0-9]{6}$/); +}); + +test("filename_prefix is unique per call even for the same label", () => { + const a = _sweepFilenamePrefix("35;seam;1"); + const b = _sweepFilenamePrefix("35;seam;1"); + assert.notEqual(a, b); +}); const bsSrc = fs.readFileSync(__dirname + "/block_sweep.js", "utf8"); const grabBs = (name) => bsSrc.match(new RegExp(`function ${name}[\\s\\S]*?\\n \\}\\n`))[0]; diff --git a/movie_editor/frontend/store.js b/movie_editor/frontend/store.js index 3d9b24d7..470d138d 100644 --- a/movie_editor/frontend/store.js +++ b/movie_editor/frontend/store.js @@ -3688,6 +3688,16 @@ // restoring after and a crash mid-sweep leaves no residue. // Line format: "blocks;seam|noseam;times[;laststeps]", e.g. "10,11,13;seam;5" / // "40-41;noseam;1;2". laststeps is optional (blank/omitted = every step, the old default). + // "10,11,13;seam;5" -> "funpack_sweep_10-11-13-seam-5_" — VHS_VideoCombine's + // filename_prefix widget, overridden per run so the temp file itself is self-labelled + // instead of the generic funpack_preview_ every other run gets. Short unique suffix + // (not the raw uuid) because a shared prefix across runs made an earlier run's file stop + // resolving once a later run reused it — see builder.py's own comment on this prefix. + function _sweepFilenamePrefix(label) { + const safe = String(label).replace(/[^A-Za-z0-9]+/g, "-").replace(/^-+|-+$/g, ""); + return `funpack_sweep_${safe}_${Math.random().toString(36).slice(2, 8)}`; + } + function parseBlockSweepConfig(text) { return String(text || "").split("\n").map((l) => l.trim()).filter(Boolean).map((line) => { const [blocks, seam, times, lastSteps] = line.split(";").map((s) => (s || "").trim()); @@ -3721,6 +3731,7 @@ { node: "sampler", input: "h3_block_repeat_span_loop", value: c.spanLoop }, { node: "sampler", input: "h3_block_repeat_times", value: c.times }, { node: "sampler", input: "h3_block_repeat_last_steps", value: c.lastSteps }, + { node: "vhs", input: "filename_prefix", value: _sweepFilenamePrefix(c.label) }, ]; const ok = await _generateRun([sceneId], sceneId, `Sweep ${i + 1}/${configs.length}: ${c.label}`, false, overrides); if (!ok) {