From b6a651f4d0672d7221a03c78aa4a039237434b49 Mon Sep 17 00:00:00 2001 From: Rene Zander Date: Wed, 8 Jul 2026 07:24:21 +0000 Subject: [PATCH 01/17] feat(sync-timelines): reconcile drifted timeline mirrors from draft_content.json (#39) On CapCut >= 8.7 the app can drive the timeline from template-2.tmp / draft_info.json instead of draft_content.json, so a CLI edit that only the canonical file carries is silently ignored (#35). diagnose detects the condition but had no repair path. capcut sync-timelines compares every readable timeline target against draft_content.json (always canonical for this command; direction is never inferred from mtimes) and prints a plan of what drifted, including the pre-open mirror case where draft_info.json still carries a stale draft GUID. --apply performs the write by reusing loadDraft/saveDraft, so all targets are rewritten transactionally inside their own envelopes with .bak backups and the existing concurrency checks. It refuses to write while the editor is running unless --force-write, no-ops with exit 0 when targets agree, and reports a mirror that exists but holds no readable timeline (binary/encrypted template-2.tmp) as unreconcilable with the fixture-bundle workaround instead of pretending success. diagnose's next_actions now point at sync-timelines as the remedy instead of deferring to issue #35. --- src/command-specs.ts | 5 ++ src/index.ts | 102 +++++++++++++++++++++++++++++++- src/store.ts | 110 ++++++++++++++++++++++++++++++++++- test/sync-timelines.test.mjs | Bin 0 -> 9259 bytes 4 files changed, 214 insertions(+), 3 deletions(-) create mode 100644 test/sync-timelines.test.mjs diff --git a/src/command-specs.ts b/src/command-specs.ts index aadc1ef..e189315 100644 --- a/src/command-specs.ts +++ b/src/command-specs.ts @@ -178,6 +178,7 @@ const usages = { doctor: "capcut doctor", diagnose: "capcut diagnose [--bundle ]", fixture: "capcut fixture --out ", + "sync-timelines": "capcut sync-timelines [--apply]", restore: "capcut restore [--step | --list]", serve: "capcut serve [--queue ] [options]", decrypt: "capcut decrypt ", @@ -336,6 +337,9 @@ const optionsByCommand: Record = { concat: [OUT], diagnose: [option("bundle", ["--bundle"], "path", "Write a redacted JSON diagnostic bundle.")], fixture: [option("out", ["--out"], "path", "Output directory for the sanitized bundle.")], + "sync-timelines": [ + option("apply", ["--apply"], "boolean", "Write the reconciled timelines (default: print the plan only)."), + ], "replace-media": [ option("retime", ["--retime"], "boolean", "Fit the segment to the new clip instead of preserving in/out."), option("ffprobe_cmd", ["--ffprobe-cmd"], "path", "ffprobe binary for duration/dimension detection."), @@ -426,6 +430,7 @@ const mutating = new Set([ "prune", "relink", "replace-media", + "sync-timelines", "concat", "restore", "export", diff --git a/src/index.ts b/src/index.ts index 6199323..add839b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -89,7 +89,7 @@ import { replaceMedia } from "./replace.js"; import { serveQueue } from "./serve.js"; import { addSfx } from "./sfx.js"; import { parseSrt } from "./srt.js"; -import { diagnoseDraftStore, discoverDraftStore } from "./store.js"; +import { diagnoseDraftStore, discoverDraftStore, editorProcesses, planTimelineSync } from "./store.js"; import { formatDuration, formatTime, parseTimeInput, srtTime } from "./time.js"; import { translateDraft } from "./translate.js"; import { detectVersion } from "./version.js"; @@ -156,6 +156,7 @@ export const COMMANDS = [ "doctor", "diagnose", "fixture", + "sync-timelines", "restore", "serve", "decrypt", @@ -295,6 +296,11 @@ Maintenance & inspection: fixture --out Build a shareable, redacted compatibility bundle (timeline JSON only, no media; home paths + emails redacted) to attach to a version-support issue like #35. + sync-timelines [--apply] Reconcile timeline mirrors (template-2.tmp, + draft_info.json) that drifted from draft_content.json, so a CLI + edit is honored by CapCut >= 8.7 (issue #35). Prints the plan by + default; writes only with --apply. Refuses to write while the + editor is running unless --force-write. Animate: keyframe