@oh-story/dsh 0.1.12 → 0.1.13
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 +17 -11
- package/lib/client.js +85 -35
- package/lib/client.js.map +3 -3
- package/lib/drama/manifest.json +41 -36
- package/lib/drama/skills/short-drama/scripts/creator_markdown_check.py +340 -13
- package/lib/drama/skills/short-drama-assets/references/voice-direction.md +4 -2
- package/lib/drama/skills/short-drama-edit/SKILL.md +32 -12
- package/lib/drama/skills/short-drama-edit/references/still-cuts.md +90 -0
- package/lib/drama/skills/short-drama-edit/scripts/edit_tool.py +1035 -145
- package/lib/drama/skills/short-drama-edit/scripts/selftest.py +48 -0
- package/lib/drama/skills/short-drama-produce/SKILL.md +6 -1
- package/lib/drama/skills/short-drama-produce/references/adapter-contract.md +12 -0
- package/lib/drama/skills/short-drama-produce/scripts/production_tool.py +138 -9
- package/lib/drama/skills/short-drama-produce/scripts/provider_adapters.py +6 -0
- package/lib/drama/skills/short-drama-storyboard/SKILL.md +2 -0
- package/lib/drama/skills/short-drama-storyboard/references/production-shot-grammar.md +3 -1
- package/lib/drama/skills/short-drama-video-prompts/SKILL.md +7 -0
- package/lib/drama/skills/short-drama-video-prompts/references/performance-action-timing.md +14 -0
- package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.5.md +19 -6
- package/lib/drama/skills/short-drama-video-prompts/references/stage-contract.md +1 -0
- package/lib/drama/skills/short-drama-video-prompts/references/target-model-profile.md +5 -0
- package/lib/drama/skills/short-drama-video-prompts/references/wan-3.0.md +4 -1
- package/lib/index.js +16 -7
- package/lib/index.js.map +2 -2
- package/lib/novel-to-game/README.md +98 -44
- package/lib/novel-to-game/README_ZH.md +100 -46
- package/lib/novel-to-game/VERSION +1 -1
- package/lib/novel-to-game/examples/jin-ping-mei/build/BUILD_BRIEF.md +27 -202
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/RUN.md +23 -115
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/chamber.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/court_night.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/court_rain.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/garden.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/hall.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/kitchen.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/street.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/street_dawn.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/temple.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/yue/moon.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/meng/angry.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/meng/blush.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/meng/neutral.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/meng/sad.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/meng/smile.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/meng/surprised.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pan/angry.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pan/blush.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pan/neutral.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pan/sad.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pan/smile.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pan/surprised.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pinger/angry.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pinger/blush.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pinger/neutral.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pinger/sad.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pinger/smile.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pinger/surprised.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/xuee/angry.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/xuee/blush.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/xuee/neutral.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/xuee/sad.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/xuee/smile.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/xuee/surprised.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/yue/angry.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/yue/blush.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/yue/neutral.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/yue/sad.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/yue/smile.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/yue/surprised.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/css/style.css +326 -2503
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/index.html +71 -4
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/adv.js +221 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/audio.js +113 -257
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/main.js +511 -3026
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/menus.js +277 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/petals.js +67 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/stage.js +180 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/store.js +70 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/common.js +198 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/common2.js +204 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/common3.js +198 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/dates.js +186 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/index.js +122 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/meng.js +190 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/pan.js +212 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/pinger.js +198 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/xuee.js +199 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/yue.js +214 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/package.json +2 -2
- package/lib/novel-to-game/examples/jin-ping-mei/build/art/generated-art.json +182 -45
- package/lib/novel-to-game/examples/jin-ping-mei/qa/README.md +7 -17
- package/lib/novel-to-game/examples/jin-ping-mei/qa/verification.json +9 -9
- package/lib/novel-to-game/manifest.json +303 -78
- package/lib/novel-to-game/skills/game-art-direction/references/art-direction-method.md +7 -2
- package/lib/novel-to-game/skills/game-concept/references/concept-method.md +2 -0
- package/lib/novel-to-game/skills/game-world-design/references/narrative-design-method.md +2 -0
- package/lib/novel-to-game/skills/game-world-design/references/world-design-method.md +9 -0
- package/lib/types/client/drama-production-view.d.ts +11 -0
- package/lib/types/client/drama-production-view.d.ts.map +1 -1
- package/lib/types/client/file-activity.d.ts +1 -1
- package/lib/types/client/file-activity.d.ts.map +1 -1
- package/lib/types/client/production-prompts.d.ts +7 -1
- package/lib/types/client/production-prompts.d.ts.map +1 -1
- package/lib/types/client/production-runtime.d.ts +7 -2
- package/lib/types/client/production-runtime.d.ts.map +1 -1
- package/lib/types/drama-adapters.d.ts.map +1 -1
- package/lib/types/skill-provider.d.ts.map +1 -1
- package/lib/types/video-project.d.ts.map +1 -1
- package/lib/video-recap/CHANGELOG.md +108 -1
- package/lib/video-recap/README.en.md +18 -14
- package/lib/video-recap/README.md +18 -14
- package/lib/video-recap/manifest.json +297 -321
- package/lib/video-recap/skills/video-assemble/SKILL.md +18 -16
- package/lib/video-recap/skills/video-assemble/references/explicit-audio-mix.md +3 -1
- package/lib/video-recap/skills/video-assemble/references/jianying/SOURCE.md +5 -5
- package/lib/video-recap/skills/video-assemble/references/packaging.md +4 -4
- package/lib/video-recap/skills/video-assemble/references/subtitle-track.md +10 -10
- package/lib/video-recap/skills/video-assemble/scripts/adoption/audio_mix_binding.py +13 -2
- package/lib/video-recap/skills/video-assemble/scripts/adoption/av_clock.py +109 -0
- package/lib/video-recap/skills/video-assemble/scripts/adoption/strict_publish.py +4 -1
- package/lib/video-recap/skills/video-assemble/scripts/artifacts.py +2 -9
- package/lib/video-recap/skills/video-assemble/scripts/assemble.py +188 -51
- package/lib/video-recap/skills/video-assemble/scripts/assemble_constants.py +0 -1
- package/lib/video-recap/skills/video-assemble/scripts/assembly_contract.py +32 -75
- package/lib/video-recap/skills/video-assemble/scripts/assembly_settings.py +8 -15
- package/lib/video-recap/skills/video-assemble/scripts/audio_mix.py +202 -161
- package/lib/video-recap/skills/video-assemble/scripts/codec_peak.py +112 -0
- package/lib/video-recap/skills/video-assemble/scripts/export_jianying.py +10 -101
- package/lib/video-recap/skills/video-assemble/scripts/jianying/builders.py +44 -475
- package/lib/video-recap/skills/video-assemble/scripts/jianying/model.py +1 -22
- package/lib/video-recap/skills/video-assemble/scripts/jianying/templates.py +0 -3
- package/lib/video-recap/skills/video-assemble/scripts/jianying/timeline_contract.py +36 -103
- package/lib/video-recap/skills/video-assemble/scripts/jianying/tracks.py +0 -8
- package/lib/video-recap/skills/video-assemble/scripts/jianying/writer.py +28 -203
- package/lib/video-recap/skills/video-assemble/scripts/lib.py +20 -34
- package/lib/video-recap/skills/video-assemble/scripts/loudness.py +332 -0
- package/lib/video-recap/skills/video-assemble/scripts/media.py +158 -38
- package/lib/video-recap/skills/video-assemble/scripts/narration_audio.py +30 -16
- package/lib/video-recap/skills/video-assemble/scripts/render_preflight.py +49 -8
- package/lib/video-recap/skills/video-assemble/scripts/source_subtitles.py +17 -6
- package/lib/video-recap/skills/video-assemble/scripts/subtitles/core.py +24 -2
- package/lib/video-recap/skills/video-assemble/scripts/subtitles/track.py +2 -5
- package/lib/video-recap/skills/video-assemble/scripts/subtitles/track_binding.py +2 -9
- package/lib/video-recap/skills/video-assemble/scripts/timeline.py +12 -34
- package/lib/video-recap/skills/video-assemble/scripts/timeline_emit.py +3 -4
- package/lib/video-recap/skills/video-assemble/scripts/visual_render.py +28 -2
- package/lib/video-recap/skills/video-cut/SKILL.md +37 -11
- package/lib/video-recap/skills/video-cut/references/shot-review.md +1 -1
- package/lib/video-recap/skills/video-cut/scripts/cut.py +2 -34
- package/lib/video-recap/skills/video-cut/scripts/cut_cli.py +96 -124
- package/lib/video-recap/skills/video-cut/scripts/cut_contract.py +97 -182
- package/lib/video-recap/skills/video-cut/scripts/cut_render.py +177 -166
- package/lib/video-recap/skills/video-cut/scripts/frame_grid.py +249 -0
- package/lib/video-recap/skills/video-cut/scripts/lib.py +0 -3
- package/lib/video-recap/skills/video-cut/scripts/media_geometry.py +173 -7
- package/lib/video-recap/skills/video-cut/scripts/sentence_boundaries.py +234 -151
- package/lib/video-recap/skills/video-cut/scripts/sentence_gate.py +189 -0
- package/lib/video-recap/skills/video-cut/scripts/shot_review.py +2 -6
- package/lib/video-recap/skills/video-recap/SKILL.md +24 -27
- package/lib/video-recap/skills/video-recap/references/audio-routing.md +8 -6
- package/lib/video-recap/skills/video-recap/references/config-playbook.md +8 -11
- package/lib/video-recap/skills/video-recap/references/data-schema.md +131 -74
- package/lib/video-recap/skills/video-recap/references/resource-library.md +17 -1
- package/lib/video-recap/skills/video-recap/scripts/doctor.py +51 -268
- package/lib/video-recap/skills/video-recap/scripts/final_qc.py +89 -254
- package/lib/video-recap/skills/video-recap/scripts/lib.py +81 -70
- package/lib/video-recap/skills/video-recap/scripts/library.py +40 -4
- package/lib/video-recap/skills/video-recap/scripts/materials.py +13 -80
- package/lib/video-recap/skills/video-recap/scripts/recap_cli.py +49 -17
- package/lib/video-recap/skills/video-recap/scripts/recap_inspect.py +1 -3
- package/lib/video-recap/skills/video-recap/scripts/recap_runner.py +86 -116
- package/lib/video-recap/skills/video-recap/scripts/recap_runtime.py +87 -23
- package/lib/video-recap/skills/video-recap/scripts/recap_source.py +0 -39
- package/lib/video-recap/skills/video-recap/scripts/recap_stage_qc.py +37 -135
- package/lib/video-recap/skills/video-recap/scripts/recap_timeline.py +118 -127
- package/lib/video-recap/skills/video-recap/scripts/resources/__init__.py +4 -0
- package/lib/video-recap/skills/video-recap/scripts/{resource_lock.py → resources/lock.py} +1 -1
- package/lib/video-recap/skills/video-recap/scripts/{project_binding.py → resources/project_binding.py} +78 -17
- package/lib/video-recap/skills/video-reference/SKILL.md +108 -0
- package/lib/video-recap/skills/video-reference/references/reference-schema.md +139 -0
- package/lib/video-recap/skills/video-reference/scripts/lib.py +85 -0
- package/lib/video-recap/skills/video-reference/scripts/reference.py +96 -0
- package/lib/video-recap/skills/video-reference/scripts/reference_check.py +553 -0
- package/lib/video-recap/skills/video-reference/scripts/reference_frames.py +122 -0
- package/lib/video-recap/skills/video-reference/scripts/reference_measure.py +253 -0
- package/lib/video-recap/skills/video-reference/scripts/reference_profile.py +271 -0
- package/lib/video-recap/skills/video-script/SKILL.md +9 -7
- package/lib/video-recap/skills/video-script/scripts/agent_text.py +18 -267
- package/lib/video-recap/skills/video-script/scripts/deslop_qc.py +12 -30
- package/lib/video-recap/skills/video-script/scripts/entry_suggestion.py +125 -0
- package/lib/video-recap/skills/video-script/scripts/evidence_bundle.py +2 -26
- package/lib/video-recap/skills/video-script/scripts/lib.py +5 -15
- package/lib/video-recap/skills/video-script/scripts/lint_summary.py +184 -0
- package/lib/video-recap/skills/video-script/scripts/narration_lint.py +120 -100
- package/lib/video-recap/skills/video-script/scripts/review.py +3 -34
- package/lib/video-recap/skills/video-script/scripts/review_grounding.py +20 -46
- package/lib/video-recap/skills/video-script/scripts/review_response.py +120 -258
- package/lib/video-recap/skills/video-script/scripts/review_runner.py +2 -10
- package/lib/video-recap/skills/video-script/scripts/speech_ownership.py +89 -5
- package/lib/video-recap/skills/video-script/scripts/validate.py +26 -34
- package/lib/video-recap/skills/video-understanding/SKILL.md +29 -4
- package/lib/video-recap/skills/video-understanding/references/data-schema.md +5 -22
- package/lib/video-recap/skills/video-understanding/scripts/agent_text.py +24 -121
- package/lib/video-recap/skills/video-understanding/scripts/asr.py +11 -5
- package/lib/video-recap/skills/video-understanding/scripts/asr_timing_evidence.py +15 -24
- package/lib/video-recap/skills/video-understanding/scripts/briefing/builder.py +59 -25
- package/lib/video-recap/skills/video-understanding/scripts/briefing/context.py +39 -3
- package/lib/video-recap/skills/video-understanding/scripts/briefing/inputs.py +15 -27
- package/lib/video-recap/skills/video-understanding/scripts/briefing/timeline.py +136 -37
- package/lib/video-recap/skills/video-understanding/scripts/consolidate.py +121 -37
- package/lib/video-recap/skills/video-understanding/scripts/detect.py +118 -16
- package/lib/video-recap/skills/video-understanding/scripts/index_normalize.py +359 -0
- package/lib/video-recap/skills/video-understanding/scripts/lib.py +62 -21
- package/lib/video-recap/skills/video-understanding/scripts/storyboard.py +181 -51
- package/lib/video-recap/skills/video-understanding/scripts/timeline_fusion.py +0 -26
- package/lib/video-recap/skills/video-understanding/scripts/understanding_brief.py +37 -14
- package/lib/video-recap/skills/video-understanding/scripts/understanding_cache.py +40 -32
- package/lib/video-recap/skills/video-understanding/scripts/understanding_runner.py +100 -127
- package/lib/video-recap/skills/video-understanding/scripts/understanding_storyboard.py +182 -30
- package/lib/video-recap/skills/video-understanding/scripts/vlm.py +30 -27
- package/lib/video-recap/skills/video-voiceover/SKILL.md +19 -12
- package/lib/video-recap/skills/video-voiceover/references/index-tts.md +3 -3
- package/lib/video-recap/skills/video-voiceover/scripts/approved_text_policy.py +3 -1
- package/lib/video-recap/skills/video-voiceover/scripts/dub.py +60 -135
- package/lib/video-recap/skills/video-voiceover/scripts/lib.py +9 -34
- package/lib/video-recap/skills/video-voiceover/scripts/providers/fish_audio.py +6 -1
- package/lib/video-recap/skills/video-voiceover/scripts/tts_audio.py +64 -1
- package/lib/video-recap/skills/video-voiceover/scripts/tts_cache.py +99 -0
- package/lib/video-recap/skills/video-voiceover/scripts/voiceover.py +177 -200
- package/package.json +45 -45
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/inner_court_accord_five.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/public_day15.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/household/li_jiaoer.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/css/new-guofeng.css +0 -1494
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/assets.js +0 -147
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/data.js +0 -7470
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/engine.js +0 -13066
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/text.js +0 -32
- package/lib/novel-to-game/examples/jin-ping-mei/qa/playable-model.json +0 -99
- package/lib/novel-to-game/examples/jin-ping-mei/qa/verify_playable_model.mjs +0 -152
- package/lib/video-recap/skills/video-assemble/references/foreground-compose.md +0 -118
- package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_combination_segment.json +0 -84
- package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_combination_video_material.json +0 -98
- package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_draft.json +0 -204
- package/lib/video-recap/skills/video-assemble/references/pair-media.md +0 -74
- package/lib/video-recap/skills/video-assemble/scripts/compose_foreground.py +0 -293
- package/lib/video-recap/skills/video-assemble/scripts/pair_media.py +0 -216
- package/lib/video-recap/skills/video-recap/references/shift-left-qc-schema.md +0 -23
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc.py +0 -33
- package/lib/video-recap/skills/video-recap/scripts/qc/__init__.py +0 -1
- package/lib/video-recap/skills/video-recap/scripts/qc/mimo_client.py +0 -13
- package/lib/video-recap/skills/video-recap/scripts/qc/mimo_contract.py +0 -9
- package/lib/video-recap/skills/video-recap/scripts/qc/mimo_evidence.py +0 -287
- package/lib/video-recap/skills/video-recap/scripts/qc/mimo_observations.py +0 -175
- package/lib/video-recap/skills/video-recap/scripts/qc/mimo_payload.py +0 -206
- package/lib/video-recap/skills/video-recap/scripts/qc/mimo_report.py +0 -318
- package/lib/video-recap/skills/video-recap/scripts/qc/mimo_runner.py +0 -130
- package/lib/video-recap/skills/video-recap/scripts/qc_contract.py +0 -320
- package/lib/video-recap/skills/video-script/scripts/timeline_fusion.py +0 -108
- package/lib/video-recap/skills/video-understanding/scripts/brief.py +0 -9
- package/lib/video-recap/skills/video-understanding/scripts/deslop_qc.py +0 -211
- package/lib/video-recap/skills/video-understanding/scripts/narration_lint.py +0 -707
- package/lib/video-recap/skills/video-understanding/scripts/speech_ownership.py +0 -143
- /package/lib/video-recap/skills/video-cut/scripts/{narration_mapping.py → cut_qc.py} +0 -0
|
@@ -2,15 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
4
|
import os
|
|
5
|
+
import shutil
|
|
5
6
|
from pathlib import Path
|
|
6
7
|
|
|
7
8
|
import materials as material_lib
|
|
8
|
-
import project_binding
|
|
9
|
-
import resource_lock
|
|
9
|
+
import resources.project_binding as project_binding
|
|
10
|
+
import resources.lock as resource_lock
|
|
10
11
|
|
|
11
|
-
from
|
|
12
|
+
from lib import TTS_PROVIDERS, file_identity, material_id_for, source_id_for
|
|
13
|
+
from recap_cli import parse_args
|
|
12
14
|
from recap_review import run_narration_review
|
|
13
15
|
from recap_runtime import (
|
|
16
|
+
RUN_MANIFEST,
|
|
14
17
|
_analysis_settings,
|
|
15
18
|
_build_multi_source_records,
|
|
16
19
|
_coerce_videos,
|
|
@@ -26,18 +29,12 @@ from recap_runtime import (
|
|
|
26
29
|
_write_run_manifest,
|
|
27
30
|
)
|
|
28
31
|
from recap_stage_qc import (
|
|
29
|
-
_post_render_qc_metadata,
|
|
30
|
-
_prepare_mimo_qc,
|
|
31
32
|
_print_final_qc_pointer,
|
|
33
|
+
_print_render_warnings,
|
|
32
34
|
_require_final_qc,
|
|
33
|
-
_run_mimo_qc_stage,
|
|
34
|
-
_tts_qc_metadata,
|
|
35
35
|
_write_final_qc_reports,
|
|
36
|
-
_write_shift_left_stage_qc,
|
|
37
36
|
)
|
|
38
37
|
from recap_source import (
|
|
39
|
-
begin_local_adoption_qc,
|
|
40
|
-
begin_non_narration_qc,
|
|
41
38
|
extend_assemble_args,
|
|
42
39
|
load_local_assembly_evidence,
|
|
43
40
|
needs_voiceover,
|
|
@@ -58,6 +55,7 @@ from recap_timeline import (
|
|
|
58
55
|
_materials_enabled,
|
|
59
56
|
_multi_manifest_mismatches,
|
|
60
57
|
_pause_for_agent,
|
|
58
|
+
_preflight_visual_overlays,
|
|
61
59
|
_print_narration_review_pointer,
|
|
62
60
|
_read_assembly_output,
|
|
63
61
|
_read_phase_ledger,
|
|
@@ -71,8 +69,6 @@ from recap_timeline import (
|
|
|
71
69
|
_write_phase_ledger,
|
|
72
70
|
)
|
|
73
71
|
|
|
74
|
-
RUN_MANIFEST = "recap_run_manifest.json"
|
|
75
|
-
|
|
76
72
|
|
|
77
73
|
def _voiceover_args(work_dir, narration_path, args):
|
|
78
74
|
"""Build the shared full/cut invocation for video-voiceover."""
|
|
@@ -90,18 +86,11 @@ def _voiceover_args(work_dir, narration_path, args):
|
|
|
90
86
|
return result
|
|
91
87
|
|
|
92
88
|
|
|
93
|
-
def _approved_validation_args(args):
|
|
94
|
-
return ["--preserve-approved-text"] if args.preserve_approved_text else []
|
|
95
|
-
|
|
96
|
-
|
|
97
89
|
def _record_resources(work_dir, args):
|
|
98
90
|
"""Write resource_lock.json for the finished render and surface anything needing a person."""
|
|
99
|
-
library_dir = getattr(args, "material_library_dir", None) or os.environ.get(
|
|
100
|
-
"VIDEO_RECAP_MATERIAL_LIBRARY_DIR"
|
|
101
|
-
)
|
|
102
91
|
try:
|
|
103
92
|
lock = resource_lock.write_resource_lock(
|
|
104
|
-
work_dir, library_dir=
|
|
93
|
+
work_dir, library_dir=_material_library_dir(args), project=getattr(args, "resolved_project", None)
|
|
105
94
|
)
|
|
106
95
|
except (OSError, ValueError, TypeError, KeyError) as exc: # a record must never fail a finished render
|
|
107
96
|
print(f"[video-recap] ⚠ 未能写出 resource_lock.json: {type(exc).__name__}: {exc}", flush=True)
|
|
@@ -114,6 +103,7 @@ def _finish_recap(work_dir, final_output, args):
|
|
|
114
103
|
if args.require_final_qc:
|
|
115
104
|
_require_final_qc(final_qc_result, work_dir)
|
|
116
105
|
print(f"[video-recap] ✅ 完成: {final_output}")
|
|
106
|
+
_print_render_warnings(final_qc_result, work_dir, final_output)
|
|
117
107
|
_print_final_qc_pointer(final_qc_result)
|
|
118
108
|
|
|
119
109
|
|
|
@@ -128,7 +118,6 @@ def _run_local_adoption(video, work_dir, args):
|
|
|
128
118
|
(work_dir / RUN_MANIFEST).write_text(
|
|
129
119
|
json.dumps(failed, ensure_ascii=False, indent=2), encoding="utf-8"
|
|
130
120
|
)
|
|
131
|
-
begin_local_adoption_qc(work_dir, _write_shift_left_stage_qc)
|
|
132
121
|
assemble_args = [
|
|
133
122
|
str(video),
|
|
134
123
|
"--work-dir", str(work_dir),
|
|
@@ -159,16 +148,30 @@ def _run_local_adoption(video, work_dir, args):
|
|
|
159
148
|
raise
|
|
160
149
|
final_output = _read_assembly_output(work_dir)
|
|
161
150
|
_record_resources(work_dir, args)
|
|
162
|
-
_write_shift_left_stage_qc(
|
|
163
|
-
work_dir,
|
|
164
|
-
"post_render",
|
|
165
|
-
metadata=_post_render_qc_metadata(work_dir, final_output),
|
|
166
|
-
)
|
|
167
151
|
_finish_recap(work_dir, final_output, args)
|
|
168
152
|
|
|
169
153
|
|
|
170
|
-
def
|
|
171
|
-
"""
|
|
154
|
+
def _share_project_background_research(project_work_dir, source_work_dir):
|
|
155
|
+
"""Give one source of a multi-video run the project-level background_research.json.
|
|
156
|
+
|
|
157
|
+
video-understanding only reads background_research.json from its own work_dir
|
|
158
|
+
(sources/<source_id>/), while the Agent writes the research once at the project level.
|
|
159
|
+
The project file is copied in when the source has none or an older one; a per-source
|
|
160
|
+
file newer than the project file is kept. copy2 keeps the mtime, so an unchanged
|
|
161
|
+
project file leaves the understanding cache identity unchanged."""
|
|
162
|
+
shared = Path(project_work_dir) / "background_research.json"
|
|
163
|
+
if not shared.is_file():
|
|
164
|
+
return
|
|
165
|
+
target = Path(source_work_dir) / "background_research.json"
|
|
166
|
+
if target.is_file() and target.stat().st_mtime_ns >= shared.stat().st_mtime_ns:
|
|
167
|
+
return
|
|
168
|
+
shutil.copy2(shared, target)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _run_or_restore_understanding(source_record, source_work_dir, args, project_work_dir=None):
|
|
172
|
+
"""Run video-understanding for one source, or restore it from the material library.
|
|
173
|
+
|
|
174
|
+
`project_work_dir` is set for multi-video runs, whose sources share its research file."""
|
|
172
175
|
source_work_dir = Path(source_work_dir)
|
|
173
176
|
source_work_dir.mkdir(parents=True, exist_ok=True)
|
|
174
177
|
source_path = source_record["source_path"]
|
|
@@ -183,6 +186,7 @@ def _run_or_restore_understanding(source_record, source_work_dir, args):
|
|
|
183
186
|
source_path=source_path,
|
|
184
187
|
source_identity=source_identity,
|
|
185
188
|
settings=settings,
|
|
189
|
+
material_id=source_record["material_id"],
|
|
186
190
|
)
|
|
187
191
|
restored = result["restored"]
|
|
188
192
|
if restored:
|
|
@@ -196,6 +200,8 @@ def _run_or_restore_understanding(source_record, source_work_dir, args):
|
|
|
196
200
|
flush=True,
|
|
197
201
|
)
|
|
198
202
|
if not restored:
|
|
203
|
+
if project_work_dir is not None:
|
|
204
|
+
_share_project_background_research(project_work_dir, source_work_dir)
|
|
199
205
|
_run(
|
|
200
206
|
"video-understanding",
|
|
201
207
|
"understand.py",
|
|
@@ -222,14 +228,14 @@ def _run_or_restore_understanding(source_record, source_work_dir, args):
|
|
|
222
228
|
|
|
223
229
|
def _single_source_record(video, args):
|
|
224
230
|
"""Identity + settings for the one source of a single-video run."""
|
|
225
|
-
identity =
|
|
231
|
+
identity = file_identity(video)
|
|
226
232
|
return {
|
|
227
|
-
"source_id":
|
|
233
|
+
"source_id": source_id_for(video),
|
|
228
234
|
"source_path": str(video),
|
|
229
235
|
"source_name": video.name,
|
|
230
236
|
"source_video_identity": identity,
|
|
231
237
|
"settings": _analysis_settings(args),
|
|
232
|
-
"material_id":
|
|
238
|
+
"material_id": material_id_for(video, identity),
|
|
233
239
|
}
|
|
234
240
|
|
|
235
241
|
|
|
@@ -247,6 +253,20 @@ def _rebuild_understanding_brief(source_record, source_work_dir, args):
|
|
|
247
253
|
)
|
|
248
254
|
|
|
249
255
|
|
|
256
|
+
def _write_multi_source_edited_storyboard(work_dir, video):
|
|
257
|
+
"""Cut pass 2 for several sources: the same OUTPUT-timeline storyboard a single-source
|
|
258
|
+
--brief-only rebuild produces, drawn from each source's frames, with its pointer added to
|
|
259
|
+
the multi-source brief. Advisory like every storyboard: a failure warns and the pause
|
|
260
|
+
still happens."""
|
|
261
|
+
try:
|
|
262
|
+
_run(
|
|
263
|
+
"video-understanding", "understand.py", str(video),
|
|
264
|
+
"--work-dir", str(work_dir), "--edited-storyboard-only",
|
|
265
|
+
)
|
|
266
|
+
except SystemExit as exc:
|
|
267
|
+
print(f"[video-recap] ⚠ 剪后故事板未生成(建议性,继续): {exc}", flush=True)
|
|
268
|
+
|
|
269
|
+
|
|
250
270
|
def _reject_stale(mismatches, label):
|
|
251
271
|
if mismatches:
|
|
252
272
|
details = "\n - ".join(mismatches)
|
|
@@ -262,15 +282,14 @@ def _reject_stale_multi_manifest(work_dir, videos, args, source_records):
|
|
|
262
282
|
|
|
263
283
|
|
|
264
284
|
def _render_cut(video_arg, work_dir, args, *extra):
|
|
265
|
-
"""Render edited_source.mp4 from clip_plan.json and
|
|
285
|
+
"""Render edited_source.mp4 from clip_plan.json and print its cut QC line."""
|
|
266
286
|
crender = [str(video_arg), "--work-dir", str(work_dir), *extra]
|
|
267
287
|
if args.target_duration:
|
|
268
288
|
crender += ["--target-duration", args.target_duration]
|
|
269
289
|
if args.allow_duration_drift:
|
|
270
290
|
crender.append("--allow-duration-drift")
|
|
271
291
|
_run("video-cut", "cut.py", *crender)
|
|
272
|
-
|
|
273
|
-
_write_shift_left_stage_qc(work_dir, "post_cut", metadata={"cut_qc": cut_qc})
|
|
292
|
+
_surface_cut_qc(work_dir)
|
|
274
293
|
|
|
275
294
|
|
|
276
295
|
def _reject_stale_cut_narration(work_dir, clip_plan_identity):
|
|
@@ -281,12 +300,11 @@ def _reject_stale_cut_narration(work_dir, clip_plan_identity):
|
|
|
281
300
|
)
|
|
282
301
|
|
|
283
302
|
|
|
284
|
-
def _validate_cut_output_narration(work_dir
|
|
303
|
+
def _validate_cut_output_narration(work_dir):
|
|
285
304
|
output_duration = _read_video_duration_or_raise(work_dir / "edited_source.mp4")
|
|
286
305
|
_run(
|
|
287
306
|
"video-script", "validate.py", "--work-dir", work_dir,
|
|
288
307
|
"--mode", "cut_output", "--output-duration", f"{output_duration:.3f}",
|
|
289
|
-
*_approved_validation_args(args),
|
|
290
308
|
)
|
|
291
309
|
|
|
292
310
|
|
|
@@ -294,28 +312,21 @@ def _narrate(work_dir, args, timeline):
|
|
|
294
312
|
"""Review -> TTS -> visual overlays for a validated narration.json."""
|
|
295
313
|
narration_json = work_dir / "narration.json"
|
|
296
314
|
review_ran = run_narration_review(work_dir, args, run=_run, timeline=timeline)
|
|
297
|
-
_write_shift_left_stage_qc(
|
|
298
|
-
work_dir, "pre_tts", metadata={"review_ran": review_ran, "timeline": timeline}
|
|
299
|
-
)
|
|
300
315
|
_run(
|
|
301
316
|
"video-voiceover", "voiceover.py",
|
|
302
317
|
*_voiceover_args(work_dir, narration_json, args),
|
|
303
318
|
)
|
|
304
|
-
|
|
305
|
-
overlays_path = _write_canonical_visual_overlays(work_dir, narration_json)
|
|
306
|
-
_write_shift_left_stage_qc(
|
|
307
|
-
work_dir, "pre_assemble", metadata={"visual_overlays": str(overlays_path)}
|
|
308
|
-
)
|
|
309
|
-
_run_mimo_qc_stage(work_dir, args, "pre_assemble")
|
|
319
|
+
_write_canonical_visual_overlays(work_dir, narration_json)
|
|
310
320
|
return review_ran
|
|
311
321
|
|
|
312
322
|
|
|
313
323
|
def _deliver(work_dir, args, assemble_video, recap_stem, timeline, extra_assemble_args=()):
|
|
314
324
|
"""Shared tail of every full/cut run: narration (if owned) -> assemble -> final QC."""
|
|
315
325
|
project = getattr(args, "resolved_project", None)
|
|
316
|
-
if project and project["templates"]:
|
|
326
|
+
if project and any(t["role"] in project_binding.GEOMETRY_ROLES for t in project["templates"]):
|
|
317
327
|
project_binding.check_canvas(project, *_probe_display_size_or_raise(assemble_video))
|
|
318
328
|
project_binding.sync_packaging_layers(work_dir, project)
|
|
329
|
+
_preflight_visual_overlays(work_dir, narration=uses_narration(args))
|
|
319
330
|
review_ran = _narrate(work_dir, args, timeline) if uses_narration(args) else None
|
|
320
331
|
aargs = [str(assemble_video), "--work-dir", str(work_dir), "--recap-stem", recap_stem]
|
|
321
332
|
extend_assemble_args(aargs, args)
|
|
@@ -343,13 +354,6 @@ def _deliver(work_dir, args, assemble_video, recap_stem, timeline, extra_assembl
|
|
|
343
354
|
|
|
344
355
|
final_output = _read_assembly_output(work_dir)
|
|
345
356
|
_record_resources(work_dir, args)
|
|
346
|
-
_write_shift_left_stage_qc(
|
|
347
|
-
work_dir,
|
|
348
|
-
"post_render",
|
|
349
|
-
metadata=_post_render_qc_metadata(work_dir, final_output),
|
|
350
|
-
)
|
|
351
|
-
if uses_narration(args):
|
|
352
|
-
_run_mimo_qc_stage(work_dir, args, "post_render", final_output=final_output)
|
|
353
357
|
_finish_recap(work_dir, final_output, args)
|
|
354
358
|
if uses_narration(args):
|
|
355
359
|
_print_narration_review_pointer(work_dir, review_ran=review_ran)
|
|
@@ -362,6 +366,7 @@ def _run_multi_cut(videos, work_dir, args):
|
|
|
362
366
|
work_dir.mkdir(parents=True, exist_ok=True)
|
|
363
367
|
reject_unbound_narration_workdir(work_dir, args)
|
|
364
368
|
reject_unsupported_subtitle_track(work_dir, args)
|
|
369
|
+
project_binding.sync_production_reference(work_dir, getattr(args, "resolved_project", None))
|
|
365
370
|
source_records = _build_multi_source_records(videos, args)
|
|
366
371
|
narration_json = work_dir / "narration.json"
|
|
367
372
|
clip_plan_json = work_dir / "clip_plan.json"
|
|
@@ -371,12 +376,10 @@ def _run_multi_cut(videos, work_dir, args):
|
|
|
371
376
|
if (work_dir / RUN_MANIFEST).exists():
|
|
372
377
|
_reject_stale_multi_manifest(work_dir, videos, args, source_records)
|
|
373
378
|
manifest_path = _write_multi_source_manifest(work_dir, source_records)
|
|
374
|
-
if not uses_narration(args):
|
|
375
|
-
begin_non_narration_qc(work_dir, args, _write_shift_left_stage_qc)
|
|
376
379
|
if not clip_plan_json.exists():
|
|
377
380
|
for record in source_records:
|
|
378
381
|
_run_or_restore_understanding(
|
|
379
|
-
record, _source_work_dir(work_dir, record), args
|
|
382
|
+
record, _source_work_dir(work_dir, record), args, project_work_dir=work_dir
|
|
380
383
|
)
|
|
381
384
|
# Rewrite: _run_or_restore_understanding fills in material_id per record.
|
|
382
385
|
manifest_path = _write_multi_source_manifest(work_dir, source_records)
|
|
@@ -385,43 +388,38 @@ def _run_multi_cut(videos, work_dir, args):
|
|
|
385
388
|
_pause_for_agent(
|
|
386
389
|
work_dir,
|
|
387
390
|
f"{clip_plan_json}(多视频剪辑计划;每个 clip 必须带 source_id)",
|
|
388
|
-
_continuation_command(
|
|
391
|
+
_continuation_command(work_dir, args),
|
|
389
392
|
inspect_hint=f"python3 {inspect_py} --work-dir {work_dir} state",
|
|
393
|
+
project=getattr(args, "resolved_project", None),
|
|
390
394
|
)
|
|
391
395
|
return
|
|
392
396
|
|
|
393
397
|
_reject_stale_multi_manifest(work_dir, videos, args, source_records)
|
|
394
|
-
cp_identity =
|
|
398
|
+
cp_identity = file_identity(clip_plan_json)
|
|
399
|
+
# A narration written for an older clip_plan aborts before cut.py re-renders anything.
|
|
400
|
+
if uses_narration(args) and narration_json.exists():
|
|
401
|
+
_reject_stale_cut_narration(work_dir, cp_identity)
|
|
395
402
|
_render_cut(videos[0], work_dir, args, "--sources-manifest", str(manifest_path))
|
|
396
403
|
if uses_narration(args):
|
|
397
404
|
if not narration_json.exists():
|
|
398
405
|
_write_multi_source_output_brief(
|
|
399
406
|
work_dir, source_records, work_dir / "clip_plan_validated.json"
|
|
400
407
|
)
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
clip_plan_identity=cp_identity,
|
|
404
|
-
edited_source_rendered=True,
|
|
405
|
-
multi_source=True,
|
|
406
|
-
)
|
|
408
|
+
_write_multi_source_edited_storyboard(work_dir, videos[0])
|
|
409
|
+
_write_phase_ledger(work_dir, cp_identity)
|
|
407
410
|
_pause_for_agent(
|
|
408
411
|
work_dir,
|
|
409
412
|
f"{narration_json}(用成片 OUTPUT 时间轴写解说,对着 {edited_source})",
|
|
410
|
-
_continuation_command(
|
|
413
|
+
_continuation_command(work_dir, args),
|
|
411
414
|
inspect_hint=(
|
|
412
415
|
f"python3 {inspect_py} --work-dir {work_dir} "
|
|
413
416
|
"clip-map --output-start <s> --output-end <e>"
|
|
414
417
|
),
|
|
418
|
+
project=getattr(args, "resolved_project", None),
|
|
415
419
|
)
|
|
416
420
|
return
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
work_dir,
|
|
420
|
-
clip_plan_identity=cp_identity,
|
|
421
|
-
narration_written=True,
|
|
422
|
-
multi_source=True,
|
|
423
|
-
)
|
|
424
|
-
_validate_cut_output_narration(work_dir, args)
|
|
421
|
+
_write_phase_ledger(work_dir, cp_identity)
|
|
422
|
+
_validate_cut_output_narration(work_dir)
|
|
425
423
|
|
|
426
424
|
_deliver(work_dir, args, edited_source, f"multi_{videos[0].stem}", "cut_output")
|
|
427
425
|
|
|
@@ -447,12 +445,6 @@ def main():
|
|
|
447
445
|
doctor_args += ["--tts-provider", args.tts_provider]
|
|
448
446
|
_run("video-recap", "doctor.py", *doctor_args)
|
|
449
447
|
return
|
|
450
|
-
if not uses_local_adoption(args) and args.mimo_qc not in {
|
|
451
|
-
"off", "pre-assemble", "post-render", "both"
|
|
452
|
-
}:
|
|
453
|
-
ap.error(
|
|
454
|
-
"MIMO_QC/--mimo-qc must be one of: off, pre-assemble, post-render, both"
|
|
455
|
-
)
|
|
456
448
|
if not args.video:
|
|
457
449
|
ap.error("video is required (unless --doctor)")
|
|
458
450
|
validate_audio_routing(ap, args)
|
|
@@ -524,7 +516,7 @@ def main():
|
|
|
524
516
|
ap.error("多视频 cut 暂不支持全局 subtitle Y 坐标;各源字幕带可能不同")
|
|
525
517
|
if args.subtitle_y_top is not None:
|
|
526
518
|
canvas_height = _probe_display_height_or_raise(
|
|
527
|
-
videos[0],
|
|
519
|
+
videos[0], require_near_square_pixels=True
|
|
528
520
|
)
|
|
529
521
|
if args.subtitle_y_bot > canvas_height:
|
|
530
522
|
ap.error(
|
|
@@ -555,9 +547,8 @@ def _execute_pipeline(args, videos):
|
|
|
555
547
|
if args.work_dir
|
|
556
548
|
else videos[0].parent / f"work_dir_multi_{videos[0].stem}"
|
|
557
549
|
)
|
|
558
|
-
# Validate the work-directory audio policy before any
|
|
550
|
+
# Validate the work-directory audio policy before any stage writes into it.
|
|
559
551
|
reject_unbound_narration_workdir(work_dir, args)
|
|
560
|
-
_prepare_mimo_qc(work_dir, args)
|
|
561
552
|
_run_multi_cut(videos, work_dir, args)
|
|
562
553
|
return
|
|
563
554
|
|
|
@@ -570,7 +561,6 @@ def _execute_pipeline(args, videos):
|
|
|
570
561
|
work_dir.mkdir(parents=True, exist_ok=True)
|
|
571
562
|
reject_unbound_narration_workdir(work_dir, args)
|
|
572
563
|
reject_unsupported_subtitle_track(work_dir, args)
|
|
573
|
-
_prepare_mimo_qc(work_dir, args)
|
|
574
564
|
if args.edit_mode == "dub":
|
|
575
565
|
_run_dub(video, work_dir, args)
|
|
576
566
|
else:
|
|
@@ -586,13 +576,10 @@ def _run_dub(video, work_dir, args):
|
|
|
586
576
|
if not dub_script.exists():
|
|
587
577
|
_run("video-voiceover", "dub.py", "--stage", "prepare", *dub_args)
|
|
588
578
|
_write_run_manifest(work_dir, video, args)
|
|
589
|
-
cont = _continuation_command(
|
|
579
|
+
cont = _continuation_command(work_dir, args)
|
|
590
580
|
print("=" * 50)
|
|
591
581
|
print(
|
|
592
|
-
f"[video-recap] ⏸
|
|
593
|
-
)
|
|
594
|
-
print(
|
|
595
|
-
'[video-recap] 格式 [{"start": 起秒, "end": 止秒, "zh": "译文"}](按 start 升序);逐句忠实、跟原声节奏一致、保留原音色'
|
|
582
|
+
f"[video-recap] ⏸ 按 {work_dir / 'dub_brief.md'} 的要求翻译英文原声,写入 {dub_script}"
|
|
596
583
|
)
|
|
597
584
|
print(f"[video-recap] 写完后重跑继续: {cont}")
|
|
598
585
|
print("=" * 50)
|
|
@@ -604,6 +591,7 @@ def _run_dub(video, work_dir, args):
|
|
|
604
591
|
|
|
605
592
|
def _run_single(video, work_dir, args):
|
|
606
593
|
"""Single-video full or cut run; returns early at each agent pause."""
|
|
594
|
+
project_binding.sync_production_reference(work_dir, getattr(args, "resolved_project", None))
|
|
607
595
|
cut = args.edit_mode == "cut"
|
|
608
596
|
narration_json = work_dir / "narration.json"
|
|
609
597
|
clip_plan_json = work_dir / "clip_plan.json"
|
|
@@ -619,8 +607,9 @@ def _run_single(video, work_dir, args):
|
|
|
619
607
|
_pause_for_agent(
|
|
620
608
|
work_dir,
|
|
621
609
|
need_text,
|
|
622
|
-
_continuation_command(
|
|
610
|
+
_continuation_command(work_dir, args),
|
|
623
611
|
inspect_hint=inspect_hint,
|
|
612
|
+
project=getattr(args, "resolved_project", None),
|
|
624
613
|
)
|
|
625
614
|
|
|
626
615
|
def _reject_stale_manifest():
|
|
@@ -631,20 +620,14 @@ def _run_single(video, work_dir, args):
|
|
|
631
620
|
if uses_narration(args):
|
|
632
621
|
if not narration_json.exists():
|
|
633
622
|
_understand()
|
|
634
|
-
_write_run_manifest(work_dir, video, args)
|
|
635
623
|
_pause(f"{narration_json}", state_hint)
|
|
636
624
|
return
|
|
637
625
|
_reject_stale_manifest()
|
|
638
|
-
_run(
|
|
639
|
-
"video-script", "validate.py", "--work-dir", work_dir,
|
|
640
|
-
"--mode", "full", *_approved_validation_args(args),
|
|
641
|
-
)
|
|
626
|
+
_run("video-script", "validate.py", "--work-dir", work_dir, "--mode", "full")
|
|
642
627
|
elif (work_dir / RUN_MANIFEST).exists():
|
|
643
628
|
_reject_stale_manifest()
|
|
644
629
|
else:
|
|
645
630
|
_write_run_manifest(work_dir, video, args)
|
|
646
|
-
if not uses_narration(args):
|
|
647
|
-
begin_non_narration_qc(work_dir, args, _write_shift_left_stage_qc)
|
|
648
631
|
_deliver(work_dir, args, video, video.stem, "source")
|
|
649
632
|
return
|
|
650
633
|
|
|
@@ -654,41 +637,28 @@ def _run_single(video, work_dir, args):
|
|
|
654
637
|
# PASS 1: understand -> agent writes clip_plan.json ONLY.
|
|
655
638
|
if not uses_narration(args) and (work_dir / RUN_MANIFEST).exists():
|
|
656
639
|
_reject_stale_manifest()
|
|
657
|
-
if not uses_narration(args):
|
|
658
|
-
begin_non_narration_qc(work_dir, args, _write_shift_left_stage_qc)
|
|
659
640
|
_understand()
|
|
660
|
-
_write_run_manifest(work_dir, video, args)
|
|
661
641
|
_pause(f"{clip_plan_json}(只写剪辑计划;解说下一步对着剪好的成片写)", state_hint)
|
|
662
642
|
return
|
|
663
643
|
_reject_stale_manifest()
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
644
|
+
cp_identity = file_identity(clip_plan_json)
|
|
645
|
+
# A narration written for an older clip_plan aborts before cut.py re-renders anything.
|
|
646
|
+
if uses_narration(args) and narration_json.exists():
|
|
647
|
+
_reject_stale_cut_narration(work_dir, cp_identity)
|
|
667
648
|
_render_cut(video, work_dir, args)
|
|
668
649
|
if uses_narration(args):
|
|
669
650
|
if not narration_json.exists():
|
|
670
651
|
# PASS 2: rebuild the brief (now an OUTPUT-timeline variant) and pause for narration.
|
|
671
652
|
_rebuild_understanding_brief(_single_source_record(video, args), work_dir, args)
|
|
672
|
-
_write_phase_ledger(
|
|
673
|
-
work_dir, clip_plan_identity=cp_identity, edited_source_rendered=True
|
|
674
|
-
)
|
|
653
|
+
_write_phase_ledger(work_dir, cp_identity)
|
|
675
654
|
_pause(
|
|
676
655
|
f"{narration_json}(用成片 OUTPUT 时间轴写解说,对着 {edited_source})",
|
|
677
656
|
f"python3 {inspect_py} --work-dir {work_dir} "
|
|
678
657
|
"clip-map --output-start <s> --output-end <e> # 核对输出↔原片时间轴",
|
|
679
658
|
)
|
|
680
659
|
return
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
_validate_cut_output_narration(work_dir, args)
|
|
684
|
-
else:
|
|
685
|
-
_write_phase_ledger(
|
|
686
|
-
work_dir,
|
|
687
|
-
clip_plan_identity=cp_identity,
|
|
688
|
-
edited_source_rendered=True,
|
|
689
|
-
audio_mode=args.audio_mode,
|
|
690
|
-
audio_stream_index=args.audio_stream_index,
|
|
691
|
-
)
|
|
660
|
+
_write_phase_ledger(work_dir, cp_identity)
|
|
661
|
+
_validate_cut_output_narration(work_dir)
|
|
692
662
|
# let the timeline / 剪映 export reference the original clips, not edited_source.mp4
|
|
693
663
|
_deliver(
|
|
694
664
|
work_dir, args, edited_source, video.stem, "cut_output",
|