free-short-video 5.7.4 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,15 +1,68 @@
1
1
  ---
2
2
 
3
- # What's New in v5.7.4
3
+ # What's New in v6.0.0
4
4
 
5
5
  ## What's New
6
6
 
7
+ ### Manual Mode (Human-in-the-Loop Checkpoints)
8
+
9
+ Manual Mode pauses your pipeline at the checkpoints you choose, so you can confirm, modify, or regenerate each intermediate artifact before continuing — "auto for vibe, manual for precise output."
10
+
11
+ - **Checkpoint pausing** — the pipeline stops at a checkpoint after the artifacts are produced, waits for your action, and continues from the last confirmed stage.
12
+ - **Fine-grained pause points (creative)** — up to 10 stages: image analysis, story, script & narration, character reference, end-frame prompts, end-frame generation, videos, audio, subtitle, and final composite.
13
+ - **Standard pause points (manuscript / poetry / anchor)** — 6 stages: scenes, references, videos, audio, subtitle, and final.
14
+ - **Pre-filled default pause points** — filled in automatically per task type at creation (editable; unchecked checkpoints pass through automatically).
15
+ - **Not supported** — simple (single-shot) video and simple image tasks only show the artifact list on completion.
16
+
17
+ ### Four Ways to Refine Artifacts
18
+
19
+ At each paused checkpoint you can pick any of these ways to handle the current artifacts:
20
+
21
+ - 🤖 **AI-assisted editing** — describe your change (e.g. "make scene 2 more cinematic") and the built-in model rewrites the artifact; review the diff before applying.
22
+ - ✏️ **Edit locally** — copy the artifact path and edit the file directly (JSON / SRT / images / videos), then click "modified, continue".
23
+ - 🤝 **External Agent** — copy a ready-made collaboration prompt and hand the artifact to a more capable local Agent (e.g. opencode / CodeBuddy CLI / ffmpeg / PIL), then fill the result back in.
24
+ - 💻 **Online editing** — edit text artifacts (script / narration / subtitles) in an in-page dialog with side-by-side comparison against the original.
25
+
26
+ ### Artifact-Level Dependency Graph (Rerun Only What Changed)
27
+
28
+ After any modification, the system computes affected downstream artifacts precisely and reruns only those — the rest are kept untouched:
29
+
30
+ - Edit narration → reruns audio / subtitle / final only.
31
+ - Edit scene descriptions → reruns reference images / videos / final only.
32
+ - A "before-modification" prompt lists exactly which artifacts will be regenerated vs. retained.
33
+
34
+ ### Switch Between Auto and Manual Anytime
35
+
36
+ - Switch an **auto** task to manual while running — it pauses at the next safe checkpoint.
37
+ - Switch a **manual** task back to auto while paused — pause points are cleared and it runs straight to completion.
38
+
39
+ ### Other Features & Improvements
40
+
41
+ - **Dedicated task progress page** — a focused, full-page progress view with step timeline and live status.
42
+ - **Task detail page** — opens in a new tab with official-site links and support guidance.
43
+ - **Project card pinning** — pin a task card to the top of the list; artifact previews auto-expand.
44
+ - **"Continue without changes" button** at every pause point — skip review on any channel.
45
+ - **Bilingual startup scripts** — prompts adapt to the system locale.
46
+ - **Full 22-language i18n** — missing translations completed, plus a new language-completeness checker used as a release regression gate and a language switcher in the task progress header.
47
+
48
+ ### Refactoring & Optimizations
49
+
50
+ - **Unified checkpoint mechanism across pipelines** — the same MultiScenePipeline checkpoint system now powers creative, manuscript, poetry, and anchor tasks, with per-type artifact matrices.
51
+ - **Standalone artifact dependency graph** — `core/dependency_graph.py` is a pure, declarative edge-table module (product-level + parameter-level) shared by impact prediction, approve handling, and frontend highlighting; fully unit-tested.
52
+ - **Standardized artifact manifests** — per-checkpoint `checkpoint.json` lists and task-directory `MANIFEST.md` make every intermediate artifact discoverable, readable, and refillable.
53
+ - **Resume skips completed scene-building steps** — avoids duplicate LLM calls when Creative / Anchor / Poetry tasks resume.
54
+
7
55
  ### Bug Fixes
8
56
 
9
- - **Blank page after v5.7.3 upgrade fixed** — during the merge of the analytics changes into `master`, git auto-merge treated the new minified JS bundle as a rename of the old one and injected `<<<<<<<<` / `>>>>>>>>` conflict markers into the shipped bundle. Browsers failed to parse the file with `SyntaxError: Unexpected end of input`, leaving a white screen. The production bundle is now restored byte-for-byte from the original build output, and the page renders normally.
57
+ - Fixed an un-clickable "open edit window" button caused by checkpoint artifact field-name mismatch.
58
+ - Fixed multi-PID port-in-use hints so the kill command is emitted as a single copyable line.
59
+ - Progress control-flow fixes — pause UI cleared immediately after resume, and queued/running tasks now show real-time progress messages.
60
+ - Character reference image generation moved into the references stage for correct ordering.
61
+ - Manual-mode UI polish — visible disabled states, artifact filter compatibility, auto-collapse of earlier stages, and resume for inactive unfinished tasks.
10
62
 
11
63
  ---
12
64
 
65
+ **Learn more about Manual Mode:** [Manual Mode Guide](https://video.lichuanyang.top/guides/manual-mode) and the in-repo `docs/public/manual_mode_guide.md`.
13
66
 
14
67
  ---
15
68
 
package/core/artifacts.py CHANGED
@@ -25,6 +25,7 @@ from models.task import (
25
25
  BaseTaskState,
26
26
  CreativeVideoTask,
27
27
  ManuscriptVideoTask,
28
+ PoetryVideoTask,
28
29
  StepStatus,
29
30
  )
30
31
 
@@ -115,6 +116,16 @@ _ANCHOR_STEPS_MODEL = [
115
116
  ("step_clip_generation", "clip_gen"),
116
117
  ]
117
118
 
119
+ # Poetry 步骤序列(v6.0 P3:与 multi_scene 模板方法步骤对齐)
120
+ _POETRY_STEPS = [
121
+ ("step_build_scenes", "build_scenes"),
122
+ ("step_reference_images", "reference_images"),
123
+ ("step_video_generation", "video_gen"),
124
+ ("step_audio", "audio"),
125
+ ("step_subtitle", "subtitle"),
126
+ ("step_concatenation", "concatenate"),
127
+ ]
128
+
118
129
 
119
130
  # ═══════════════════════════════════════════════════════════════
120
131
  # 产物定义(每种模式的产物模板)
@@ -183,6 +194,31 @@ def _manuscript_artifact_defs() -> list[dict]:
183
194
  ]
184
195
 
185
196
 
197
+ def _poetry_artifact_defs() -> list[dict]:
198
+ """Poetry 模式的产物定义模板(v6.0 P3)。
199
+
200
+ 诗词视频逐场景产物:scene_{i}/video.mp4、scene_{i}/narration.mp3、
201
+ scene_{i}/subtitle.srt。无参考图阶段(空实现跳过)。
202
+ """
203
+ return [
204
+ # 场景级:视频 / 配音 / 字幕
205
+ {"type": "video", "step_key": "video_gen", "label": "artVideo",
206
+ "category": "video", "scope": "scene", "file": "scene_{i}/video.mp4",
207
+ "scene_fields": ["video_file", "video_id"],
208
+ "extra_files": ["scene_{i}/task.json", "scene_{i}/curl.sh"]},
209
+ {"type": "audio", "step_key": "audio", "label": "artAudio",
210
+ "category": "audio", "scope": "scene", "file": "scene_{i}/narration.mp3",
211
+ "scene_fields": ["narration_audio"]},
212
+ {"type": "subtitle", "step_key": "subtitle", "label": "artSubtitle",
213
+ "category": "subtitle", "scope": "scene", "file": "scene_{i}/subtitle.srt",
214
+ "scene_fields": ["subtitle_srt"]},
215
+ # 任务级:成片
216
+ {"type": "final_video", "step_key": "concatenate", "label": "artFinalVideo",
217
+ "category": "video", "scope": "task", "file": "final_video.mp4",
218
+ "fields": ["final_video_file"]},
219
+ ]
220
+
221
+
186
222
  def _anchor_artifact_defs(is_model_mode: bool) -> list[dict]:
187
223
  """Anchor 模式的产物定义模板。"""
188
224
  artifacts = [
@@ -228,6 +264,8 @@ def _get_steps_for_state(state: BaseTaskState) -> list[tuple[Optional[str], str]
228
264
  if state.audio_source == "model":
229
265
  return _ANCHOR_STEPS_MODEL
230
266
  return _ANCHOR_STEPS_POST_STITCH
267
+ elif isinstance(state, PoetryVideoTask):
268
+ return _POETRY_STEPS
231
269
  return []
232
270
 
233
271
 
@@ -254,6 +292,8 @@ _SCHEMA_HINTS = {
254
292
  "anchor_image": "数字人形象 PNG。可替换后影响数字人视频。",
255
293
  "clip_prompts": "数字人视频 prompt JSON(prompts.json)。",
256
294
  "clip": "数字人循环视频 MP4。",
295
+ "narration_audio": "诗词场景朗诵音频 MP3(scene_{i}/narration.mp3)。逐场景修改后重跑该场景字幕与成片。",
296
+ "subtitle_srt": "诗词场景字幕 SRT(scene_{i}/subtitle.srt)。",
257
297
  }
258
298
 
259
299
 
@@ -270,6 +310,8 @@ def _get_artifact_defs(state: BaseTaskState) -> list[dict]:
270
310
  return _manuscript_artifact_defs()
271
311
  elif isinstance(state, AnchorVideoTask):
272
312
  return _anchor_artifact_defs(state.audio_source == "model")
313
+ elif isinstance(state, PoetryVideoTask):
314
+ return _poetry_artifact_defs()
273
315
  return []
274
316
 
275
317
 
@@ -324,6 +366,8 @@ def list_artifacts(state: BaseTaskState, task_dir: str) -> list[ArtifactDescript
324
366
  scope_count = len(state.paragraphs)
325
367
  elif isinstance(state, AnchorVideoTask):
326
368
  scope_count = len(state.paragraphs) if state.paragraphs else 0
369
+ elif isinstance(state, PoetryVideoTask):
370
+ scope_count = len(state.scenes)
327
371
  else:
328
372
  scope_count = 0
329
373
 
@@ -611,6 +655,25 @@ def apply_cascade_plan(state: BaseTaskState, plan: CascadePlan) -> dict:
611
655
  state.status = StepStatus.PENDING
612
656
  update_kwargs["status"] = StepStatus.PENDING
613
657
 
658
+ # 6. v6.1:删除前置环节产物后,重置受影响检查点的"已批准"状态
659
+ # 级联重置了某检查点对应步骤 → 该检查点不再视为已确认,
660
+ # 恢复执行时 _maybe_pause 会重新在此暂停等待用户(否则会静默跳过)。
661
+ mc = getattr(state, "manual_config", None)
662
+ if mc is not None:
663
+ approved = list(mc.approved_checkpoints or [])
664
+ removed: list[str] = []
665
+ for cp in list(approved):
666
+ step_field = _checkpoint_to_step_field(cp, state)
667
+ if step_field and step_field in plan.steps_to_reset:
668
+ approved.remove(cp)
669
+ removed.append(cp)
670
+ if removed:
671
+ mc.approved_checkpoints = approved
672
+ update_kwargs["manual_config"] = mc
673
+ logger.info(
674
+ "[Artifacts] Cascade reset un-approved checkpoints: %s", removed
675
+ )
676
+
614
677
  return update_kwargs
615
678
 
616
679
 
@@ -698,7 +761,177 @@ def sweep_stale_tasks(age_days: int = 7,
698
761
  # ═══════════════════════════════════════════════════════════════
699
762
 
700
763
  # 清单自身文件名(扫描文件树时排除)
701
- _MANIFEST_FILES = {"manifest.json", "MANIFEST.md", "task_state.json"}
764
+ _MANIFEST_FILES = {"manifest.json", "MANIFEST.md", "task_state.json", "checkpoint.json"}
765
+
766
+
767
+ # ═══════════════════════════════════════════════════════════════
768
+ # v6.0 检查点分组(PRD §4.3 产物矩阵)
769
+ # ═══════════════════════════════════════════════════════════════
770
+
771
+ # 产物 type → 检查点名(与 dependency_graph._TYPE_TO_CHECKPOINT 语义一致)
772
+ # creative:细粒度检查点(每个有产物的环节独立,v6.1)
773
+ _ARTIFACT_TO_CHECKPOINT_FINE: dict[str, str] = {
774
+ "image_analysis": "image_analysis",
775
+ "story": "story",
776
+ "script": "script",
777
+ "character_ref": "character_ref",
778
+ "end_frame_prompts": "end_frame_prompts",
779
+ "end_frame": "end_frame_gen",
780
+ "video": "videos",
781
+ "audio": "audio",
782
+ "subtitle": "subtitle",
783
+ "final_video": "final",
784
+ }
785
+ # 非 creative(manuscript/poetry/anchor):粗粒度合并检查点
786
+ _ARTIFACT_TO_CHECKPOINT_COARSE: dict[str, str] = {
787
+ "story": "scenes",
788
+ "script": "scenes",
789
+ "end_frame_prompts": "scenes",
790
+ "character_ref": "references",
791
+ "end_frame": "references",
792
+ "video": "videos",
793
+ "audio": "audio",
794
+ "subtitle": "subtitle",
795
+ "final_video": "final",
796
+ "scene_prompts": "scenes",
797
+ "anchor_image": "references",
798
+ "clip_prompts": "scenes",
799
+ "clip": "videos",
800
+ }
801
+
802
+ # 检查点展示顺序(creative 细粒度;其余粗粒度)
803
+ _CHECKPOINT_ORDER_FINE = [
804
+ "image_analysis", "story", "script", "character_ref",
805
+ "end_frame_prompts", "end_frame_gen", "videos", "audio", "subtitle", "final",
806
+ ]
807
+ _CHECKPOINT_ORDER = ["scenes", "references", "videos", "audio", "subtitle", "final"]
808
+
809
+
810
+ def _checkpoint_order_for(state: BaseTaskState) -> list[str]:
811
+ """按任务类型返回检查点展示顺序。"""
812
+ if isinstance(state, CreativeVideoTask):
813
+ return _CHECKPOINT_ORDER_FINE
814
+ return _CHECKPOINT_ORDER
815
+
816
+
817
+ def checkpoint_for_artifact(artifact_id: str, task_type: Optional[str] = None) -> str:
818
+ """返回产物 id 所属检查点名(未知产物 → "other")。
819
+
820
+ Args:
821
+ artifact_id: 产物 id(含任务类型前缀,如 ``creative:script``)。
822
+ task_type: 任务类型(缺省时从产物 id 前缀解析)。
823
+ """
824
+ parts = artifact_id.split(":")
825
+ if len(parts) >= 2:
826
+ t = task_type or parts[0]
827
+ mapping = _ARTIFACT_TO_CHECKPOINT_FINE if t == "creative" else _ARTIFACT_TO_CHECKPOINT_COARSE
828
+ return mapping.get(parts[1], "other")
829
+ return "other"
830
+
831
+
832
+ def build_checkpoint_manifest(state: BaseTaskState, task_dir: str) -> dict:
833
+ """构建检查点级产物清单(checkpoint.json 数据)。
834
+
835
+ 按检查点分组展示产物(PRD §4.3 / §4.8),供手动模式检查点等待页
836
+ 与外部 Agent 使用。每组含该检查点的产物列表(复用 manifest 的产物条目)。
837
+
838
+ Returns:
839
+ {
840
+ "format_version": "1.0",
841
+ "task_id": ...,
842
+ "task_type": ...,
843
+ "current_checkpoint": ...,
844
+ "checkpoints": {
845
+ "scenes": { "artifacts": [ ...产物条目... ], "status": "completed|pending" },
846
+ "videos": { "artifacts": [ ... ], "status": ... },
847
+ ...
848
+ }
849
+ }
850
+ """
851
+ task_dir = safe_join(get_working_dir(), task_dir)
852
+ manifest = build_manifest(state, task_dir)
853
+ order = _checkpoint_order_for(state)
854
+
855
+ groups: dict[str, dict] = {cp: {"artifacts": []} for cp in order}
856
+ for a in manifest.get("artifacts", []):
857
+ cp = checkpoint_for_artifact(a["artifact_id"], state.task_type.value)
858
+ if cp in groups:
859
+ groups[cp]["artifacts"].append(a)
860
+
861
+ # 状态:检查点对应的 step 字段状态(复用 step 状态)
862
+ for cp in order:
863
+ step_field = _checkpoint_to_step_field(cp, state)
864
+ status = "pending"
865
+ if step_field:
866
+ val = getattr(state, step_field, None)
867
+ if val is not None:
868
+ status = val.value
869
+ groups[cp]["status"] = status
870
+
871
+ current = ""
872
+ manual_cfg = getattr(state, "manual_config", None)
873
+ if manual_cfg is not None:
874
+ current = manual_cfg.current_checkpoint or ""
875
+
876
+ return {
877
+ "format_version": "1.0",
878
+ "task_id": state.task_id,
879
+ "task_type": state.task_type.value,
880
+ "current_checkpoint": current,
881
+ "checkpoints": groups,
882
+ "files": manifest.get("files", []),
883
+ }
884
+
885
+
886
+ def _checkpoint_to_step_field(checkpoint: str, state: BaseTaskState) -> Optional[str]:
887
+ """检查点名 → 步骤字段名(按任务类型)。"""
888
+ if isinstance(state, CreativeVideoTask):
889
+ mapping = {
890
+ "image_analysis": "step_image_analysis",
891
+ "story": "step_story",
892
+ "script": "step_script",
893
+ "character_ref": "step_character_ref",
894
+ "end_frame_prompts": "step_end_frame_prompts",
895
+ "end_frame_gen": "step_end_frame_generation",
896
+ "videos": "step_video_generation",
897
+ "audio": "step_audio",
898
+ "subtitle": "step_subtitle",
899
+ "final": "step_concatenation",
900
+ }
901
+ return mapping.get(checkpoint)
902
+ if isinstance(state, ManuscriptVideoTask):
903
+ mapping = {
904
+ "scenes": "step_scene_prompts",
905
+ "videos": "step_video_generation",
906
+ "audio": "step_audio",
907
+ "subtitle": "step_subtitle",
908
+ "final": "step_concatenation",
909
+ }
910
+ return mapping.get(checkpoint)
911
+ if isinstance(state, AnchorVideoTask):
912
+ mapping = {
913
+ "scenes": "step_generate_anchor",
914
+ "references": "step_generate_anchor",
915
+ "videos": "step_clip_generation",
916
+ "audio": "step_audio",
917
+ "subtitle": "step_subtitle",
918
+ "final": "step_concatenation",
919
+ }
920
+ return mapping.get(checkpoint)
921
+ return None
922
+
923
+
924
+ def write_checkpoint_manifest(state: BaseTaskState, task_dir: str) -> str:
925
+ """将检查点清单落盘为 ``checkpoint.json``,返回路径;失败返回空串。"""
926
+ try:
927
+ manifest = build_checkpoint_manifest(state, task_dir)
928
+ path = os.path.join(task_dir, "checkpoint.json")
929
+ with open(path, "w", encoding="utf-8") as f:
930
+ json.dump(manifest, f, ensure_ascii=False, indent=2)
931
+ return path
932
+ except Exception as e:
933
+ logger.warning("[Artifacts] failed to write checkpoint.json: %s", e)
934
+ return ""
702
935
 
703
936
 
704
937
  def _scan_task_files(task_dir: str) -> list[dict]:
@@ -759,11 +992,24 @@ def build_manifest(state: BaseTaskState, task_dir: str) -> dict:
759
992
  ),
760
993
  })
761
994
 
995
+ # v6.0 手动模式:暴露执行模式与当前检查点(供前端判断暂停态 / 渲染依赖图)
996
+ manual_cfg = getattr(state, "manual_config", None)
997
+ current_checkpoint = ""
998
+ current_mode = "auto"
999
+ if manual_cfg is not None:
1000
+ current_mode = "manual" if manual_cfg.enabled else "auto"
1001
+ current_checkpoint = manual_cfg.current_checkpoint or ""
1002
+
762
1003
  return {
763
1004
  "format_version": "1.0",
764
1005
  "task_id": state.task_id,
765
1006
  "task_type": state.task_type.value,
766
1007
  "task_status": state.status.value if state.status else "pending",
1008
+ "current_mode": current_mode,
1009
+ "current_checkpoint": current_checkpoint,
1010
+ "manual_config": (
1011
+ manual_cfg.model_dump() if manual_cfg is not None else {}
1012
+ ),
767
1013
  "dir_name": os.path.basename(task_dir.rstrip(os.sep)),
768
1014
  "working_dir": task_dir,
769
1015
  "artifacts": artifacts,