@oh-story/dsh 0.1.12 → 0.1.14
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 +18 -12
- package/lib/client.js +1003 -652
- package/lib/client.js.map +4 -4
- 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 +17 -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/canvas-viewport.d.ts +31 -0
- package/lib/types/client/canvas-viewport.d.ts.map +1 -0
- package/lib/types/client/drama-production-view.d.ts +14 -2
- 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/index.d.ts.map +1 -1
- package/lib/types/client/production-canvas.d.ts +24 -0
- package/lib/types/client/production-canvas.d.ts.map +1 -0
- 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 +123 -1
- package/lib/video-recap/README.en.md +20 -16
- package/lib/video-recap/README.md +20 -16
- 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 +28 -29
- package/lib/video-recap/skills/video-recap/references/audio-routing.md +8 -6
- package/lib/video-recap/skills/video-recap/references/config-playbook.md +10 -13
- 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 +55 -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 +96 -117
- 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 +53 -24
- 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 +12 -11
- 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 +51 -18
- 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 +81 -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
|
|
|
@@ -436,6 +434,8 @@ def main():
|
|
|
436
434
|
if (args.doctor or needs_voiceover(args)) and args.tts_provider not in TTS_PROVIDERS:
|
|
437
435
|
ap.error("TTS_PROVIDER/--tts-provider must be one of: " + ", ".join(TTS_PROVIDERS))
|
|
438
436
|
|
|
437
|
+
if args.edit_mode != "dub" and args.confirm_voice_rights:
|
|
438
|
+
ap.error("--confirm-voice-rights only applies to --edit-mode dub")
|
|
439
439
|
if args.doctor:
|
|
440
440
|
if any(
|
|
441
441
|
getattr(args, field) is not None
|
|
@@ -447,12 +447,6 @@ def main():
|
|
|
447
447
|
doctor_args += ["--tts-provider", args.tts_provider]
|
|
448
448
|
_run("video-recap", "doctor.py", *doctor_args)
|
|
449
449
|
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
450
|
if not args.video:
|
|
457
451
|
ap.error("video is required (unless --doctor)")
|
|
458
452
|
validate_audio_routing(ap, args)
|
|
@@ -509,6 +503,12 @@ def main():
|
|
|
509
503
|
)
|
|
510
504
|
if args.edit_mode == "dub" and args.burn_subtitles:
|
|
511
505
|
ap.error("--burn-subtitles is only supported in full/cut modes; dub never burns subtitles")
|
|
506
|
+
if args.edit_mode == "dub" and not args.confirm_voice_rights:
|
|
507
|
+
ap.error(
|
|
508
|
+
"--edit-mode dub requires --confirm-voice-rights: dub sends the source audio to "
|
|
509
|
+
"MiMo ASR and clones the original speaker's voice with MiMo voiceclone; confirm only "
|
|
510
|
+
"when the user has the rights to this video and the speaker's consent to clone the voice"
|
|
511
|
+
)
|
|
512
512
|
if (args.subtitle_y_top is None) != (args.subtitle_y_bot is None):
|
|
513
513
|
ap.error("--subtitle-y-top and --subtitle-y-bot must be provided together")
|
|
514
514
|
if args.subtitle_y_top is not None:
|
|
@@ -524,7 +524,7 @@ def main():
|
|
|
524
524
|
ap.error("多视频 cut 暂不支持全局 subtitle Y 坐标;各源字幕带可能不同")
|
|
525
525
|
if args.subtitle_y_top is not None:
|
|
526
526
|
canvas_height = _probe_display_height_or_raise(
|
|
527
|
-
videos[0],
|
|
527
|
+
videos[0], require_near_square_pixels=True
|
|
528
528
|
)
|
|
529
529
|
if args.subtitle_y_bot > canvas_height:
|
|
530
530
|
ap.error(
|
|
@@ -555,9 +555,8 @@ def _execute_pipeline(args, videos):
|
|
|
555
555
|
if args.work_dir
|
|
556
556
|
else videos[0].parent / f"work_dir_multi_{videos[0].stem}"
|
|
557
557
|
)
|
|
558
|
-
# Validate the work-directory audio policy before any
|
|
558
|
+
# Validate the work-directory audio policy before any stage writes into it.
|
|
559
559
|
reject_unbound_narration_workdir(work_dir, args)
|
|
560
|
-
_prepare_mimo_qc(work_dir, args)
|
|
561
560
|
_run_multi_cut(videos, work_dir, args)
|
|
562
561
|
return
|
|
563
562
|
|
|
@@ -570,7 +569,6 @@ def _execute_pipeline(args, videos):
|
|
|
570
569
|
work_dir.mkdir(parents=True, exist_ok=True)
|
|
571
570
|
reject_unbound_narration_workdir(work_dir, args)
|
|
572
571
|
reject_unsupported_subtitle_track(work_dir, args)
|
|
573
|
-
_prepare_mimo_qc(work_dir, args)
|
|
574
572
|
if args.edit_mode == "dub":
|
|
575
573
|
_run_dub(video, work_dir, args)
|
|
576
574
|
else:
|
|
@@ -582,17 +580,15 @@ def _run_dub(video, work_dir, args):
|
|
|
582
580
|
One pause: prepare (ASR + sentence-seg + reference) -> agent writes the Chinese
|
|
583
581
|
translation (dub_script.json) -> render (clone TTS + full-replace mux)."""
|
|
584
582
|
dub_script = work_dir / "dub_script.json"
|
|
585
|
-
|
|
583
|
+
# main() has already required --confirm-voice-rights for dub; forward it to both stages.
|
|
584
|
+
dub_args = ["--video", str(video), "--work-dir", str(work_dir), "--confirm-voice-rights"]
|
|
586
585
|
if not dub_script.exists():
|
|
587
586
|
_run("video-voiceover", "dub.py", "--stage", "prepare", *dub_args)
|
|
588
587
|
_write_run_manifest(work_dir, video, args)
|
|
589
|
-
cont = _continuation_command(
|
|
588
|
+
cont = _continuation_command(work_dir, args)
|
|
590
589
|
print("=" * 50)
|
|
591
590
|
print(
|
|
592
|
-
f"[video-recap] ⏸
|
|
593
|
-
)
|
|
594
|
-
print(
|
|
595
|
-
'[video-recap] 格式 [{"start": 起秒, "end": 止秒, "zh": "译文"}](按 start 升序);逐句忠实、跟原声节奏一致、保留原音色'
|
|
591
|
+
f"[video-recap] ⏸ 按 {work_dir / 'dub_brief.md'} 的要求翻译英文原声,写入 {dub_script}"
|
|
596
592
|
)
|
|
597
593
|
print(f"[video-recap] 写完后重跑继续: {cont}")
|
|
598
594
|
print("=" * 50)
|
|
@@ -604,6 +600,7 @@ def _run_dub(video, work_dir, args):
|
|
|
604
600
|
|
|
605
601
|
def _run_single(video, work_dir, args):
|
|
606
602
|
"""Single-video full or cut run; returns early at each agent pause."""
|
|
603
|
+
project_binding.sync_production_reference(work_dir, getattr(args, "resolved_project", None))
|
|
607
604
|
cut = args.edit_mode == "cut"
|
|
608
605
|
narration_json = work_dir / "narration.json"
|
|
609
606
|
clip_plan_json = work_dir / "clip_plan.json"
|
|
@@ -619,8 +616,9 @@ def _run_single(video, work_dir, args):
|
|
|
619
616
|
_pause_for_agent(
|
|
620
617
|
work_dir,
|
|
621
618
|
need_text,
|
|
622
|
-
_continuation_command(
|
|
619
|
+
_continuation_command(work_dir, args),
|
|
623
620
|
inspect_hint=inspect_hint,
|
|
621
|
+
project=getattr(args, "resolved_project", None),
|
|
624
622
|
)
|
|
625
623
|
|
|
626
624
|
def _reject_stale_manifest():
|
|
@@ -631,20 +629,14 @@ def _run_single(video, work_dir, args):
|
|
|
631
629
|
if uses_narration(args):
|
|
632
630
|
if not narration_json.exists():
|
|
633
631
|
_understand()
|
|
634
|
-
_write_run_manifest(work_dir, video, args)
|
|
635
632
|
_pause(f"{narration_json}", state_hint)
|
|
636
633
|
return
|
|
637
634
|
_reject_stale_manifest()
|
|
638
|
-
_run(
|
|
639
|
-
"video-script", "validate.py", "--work-dir", work_dir,
|
|
640
|
-
"--mode", "full", *_approved_validation_args(args),
|
|
641
|
-
)
|
|
635
|
+
_run("video-script", "validate.py", "--work-dir", work_dir, "--mode", "full")
|
|
642
636
|
elif (work_dir / RUN_MANIFEST).exists():
|
|
643
637
|
_reject_stale_manifest()
|
|
644
638
|
else:
|
|
645
639
|
_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
640
|
_deliver(work_dir, args, video, video.stem, "source")
|
|
649
641
|
return
|
|
650
642
|
|
|
@@ -654,41 +646,28 @@ def _run_single(video, work_dir, args):
|
|
|
654
646
|
# PASS 1: understand -> agent writes clip_plan.json ONLY.
|
|
655
647
|
if not uses_narration(args) and (work_dir / RUN_MANIFEST).exists():
|
|
656
648
|
_reject_stale_manifest()
|
|
657
|
-
if not uses_narration(args):
|
|
658
|
-
begin_non_narration_qc(work_dir, args, _write_shift_left_stage_qc)
|
|
659
649
|
_understand()
|
|
660
|
-
_write_run_manifest(work_dir, video, args)
|
|
661
650
|
_pause(f"{clip_plan_json}(只写剪辑计划;解说下一步对着剪好的成片写)", state_hint)
|
|
662
651
|
return
|
|
663
652
|
_reject_stale_manifest()
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
653
|
+
cp_identity = file_identity(clip_plan_json)
|
|
654
|
+
# A narration written for an older clip_plan aborts before cut.py re-renders anything.
|
|
655
|
+
if uses_narration(args) and narration_json.exists():
|
|
656
|
+
_reject_stale_cut_narration(work_dir, cp_identity)
|
|
667
657
|
_render_cut(video, work_dir, args)
|
|
668
658
|
if uses_narration(args):
|
|
669
659
|
if not narration_json.exists():
|
|
670
660
|
# PASS 2: rebuild the brief (now an OUTPUT-timeline variant) and pause for narration.
|
|
671
661
|
_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
|
-
)
|
|
662
|
+
_write_phase_ledger(work_dir, cp_identity)
|
|
675
663
|
_pause(
|
|
676
664
|
f"{narration_json}(用成片 OUTPUT 时间轴写解说,对着 {edited_source})",
|
|
677
665
|
f"python3 {inspect_py} --work-dir {work_dir} "
|
|
678
666
|
"clip-map --output-start <s> --output-end <e> # 核对输出↔原片时间轴",
|
|
679
667
|
)
|
|
680
668
|
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
|
-
)
|
|
669
|
+
_write_phase_ledger(work_dir, cp_identity)
|
|
670
|
+
_validate_cut_output_narration(work_dir)
|
|
692
671
|
# let the timeline / 剪映 export reference the original clips, not edited_source.mp4
|
|
693
672
|
_deliver(
|
|
694
673
|
work_dir, args, edited_source, video.stem, "cut_output",
|