@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,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
4
|
import math
|
|
5
|
+
import os
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
from pathlib import Path
|
|
@@ -10,6 +11,7 @@ from lib import CONFIG, get_video_duration, log
|
|
|
10
11
|
import shot_review
|
|
11
12
|
|
|
12
13
|
from cut_contract import (
|
|
14
|
+
SOURCES_MANIFEST_SHAPE,
|
|
13
15
|
_write_edited_source_meta,
|
|
14
16
|
load_clip_plan,
|
|
15
17
|
normalize_clip_plan,
|
|
@@ -17,25 +19,46 @@ from cut_contract import (
|
|
|
17
19
|
parse_duration_seconds,
|
|
18
20
|
should_reuse_edited_source,
|
|
19
21
|
)
|
|
20
|
-
from cut_render import
|
|
21
|
-
|
|
22
|
-
update_delivery_qc,
|
|
23
|
-
write_cut_delivery_qc,
|
|
24
|
-
)
|
|
22
|
+
from cut_render import build_edited_source_video
|
|
23
|
+
from frame_grid import record_frame_grid, source_frame_grids
|
|
25
24
|
from media_geometry import _has_audio_stream, _select_output_geometry
|
|
26
25
|
from narrative_selection import check_required_evidence
|
|
27
|
-
from
|
|
28
|
-
from sentence_boundaries import
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
)
|
|
26
|
+
from cut_qc import update_cut_qc
|
|
27
|
+
from sentence_boundaries import snap_multi_source_clips, snap_source_clips
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _write_validated_plan(path, plan, raw_plan_paths):
|
|
31
|
+
"""Write clip_plan_validated.json unless the file already holds exactly this plan.
|
|
32
|
+
|
|
33
|
+
Downstream output-clock evidence binds to this file's {size, mtime_ns}, so a resumed
|
|
34
|
+
run that re-validates an unchanged plan must leave it alone. The file is still
|
|
35
|
+
rewritten when any raw plan is newer, so "validated older than raw = stale" holds."""
|
|
36
|
+
text = json.dumps(plan, ensure_ascii=False, indent=2)
|
|
37
|
+
if path.exists() and path.read_text(encoding="utf-8") == text:
|
|
38
|
+
written = path.stat().st_mtime_ns
|
|
39
|
+
if all(not raw.exists() or raw.stat().st_mtime_ns <= written for raw in raw_plan_paths):
|
|
40
|
+
return
|
|
41
|
+
path.write_text(text, encoding="utf-8")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _required_ranges_by_source(raw_plan, source_paths):
|
|
45
|
+
"""{plan source path: [(start, end)]} of declared required-evidence nodes.
|
|
46
|
+
|
|
47
|
+
Only a hint for frame snapping; check_required_evidence validates the contract itself.
|
|
48
|
+
"""
|
|
49
|
+
nodes = raw_plan.get("required_evidence") if isinstance(raw_plan, dict) else None
|
|
50
|
+
nodes = nodes.get("nodes") if isinstance(nodes, dict) else None
|
|
51
|
+
by_realpath = {os.path.realpath(path): path for path in source_paths}
|
|
52
|
+
ranges = {}
|
|
53
|
+
for node in nodes if isinstance(nodes, list) else []:
|
|
54
|
+
try:
|
|
55
|
+
path = by_realpath.get(os.path.realpath(node["source"]))
|
|
56
|
+
start, end = float(node["start"]), float(node["end"])
|
|
57
|
+
except (KeyError, TypeError, ValueError):
|
|
58
|
+
continue
|
|
59
|
+
if path is not None:
|
|
60
|
+
ranges.setdefault(path, []).append((start, end))
|
|
61
|
+
return ranges
|
|
39
62
|
|
|
40
63
|
|
|
41
64
|
def main():
|
|
@@ -58,19 +81,13 @@ def main():
|
|
|
58
81
|
parser.add_argument(
|
|
59
82
|
"--sources-manifest",
|
|
60
83
|
default=None,
|
|
61
|
-
help="multi-source manifest json
|
|
84
|
+
help=f"multi-source manifest json: {SOURCES_MANIFEST_SHAPE}",
|
|
62
85
|
)
|
|
63
86
|
parser.add_argument(
|
|
64
87
|
"--target-duration",
|
|
65
88
|
default=None,
|
|
66
89
|
help="target output duration, e.g. 10m / 600 / 00:10:00",
|
|
67
90
|
)
|
|
68
|
-
parser.add_argument(
|
|
69
|
-
"--clip-padding",
|
|
70
|
-
type=float,
|
|
71
|
-
default=None,
|
|
72
|
-
help="seconds to pad each clip on both ends (default: CLIP_PADDING env, else 0)",
|
|
73
|
-
)
|
|
74
91
|
parser.add_argument(
|
|
75
92
|
"--allow-overlap",
|
|
76
93
|
action="store_true",
|
|
@@ -79,8 +96,8 @@ def main():
|
|
|
79
96
|
parser.add_argument(
|
|
80
97
|
"--normalize-only",
|
|
81
98
|
action="store_true",
|
|
82
|
-
help="
|
|
83
|
-
"
|
|
99
|
+
help="normalize, snap and QC the clip plan, write clip_plan_validated.json, then exit "
|
|
100
|
+
"without rendering",
|
|
84
101
|
)
|
|
85
102
|
parser.add_argument(
|
|
86
103
|
"--review-shots", action="store_true",
|
|
@@ -110,13 +127,6 @@ def main():
|
|
|
110
127
|
):
|
|
111
128
|
parser.error("--shot-roi requires --review-shots, nonnegative X/Y and positive WIDTH/HEIGHT")
|
|
112
129
|
|
|
113
|
-
# CLIP_PADDING is declared in every skill's CONFIG, but video-cut is the only place that
|
|
114
|
-
# implements padding — and it used to read the CLI flag alone, so setting the env var did
|
|
115
|
-
# nothing at all while `clip_padding_source: "env"` reported otherwise. CLI still wins.
|
|
116
|
-
clip_padding = (
|
|
117
|
-
args.clip_padding if args.clip_padding is not None else CONFIG["clip_padding"]
|
|
118
|
-
)
|
|
119
|
-
|
|
120
130
|
work_dir = Path(args.work_dir)
|
|
121
131
|
work_dir.mkdir(parents=True, exist_ok=True)
|
|
122
132
|
clip_plan_path = (
|
|
@@ -132,90 +142,68 @@ def main():
|
|
|
132
142
|
if args.sources_manifest
|
|
133
143
|
else None
|
|
134
144
|
)
|
|
135
|
-
|
|
136
|
-
|
|
145
|
+
# Visual shot-change cleanup runs first; the sentence/quiet pass is the final authority,
|
|
146
|
+
# so a prettier edit point never moves a boundary back inside a spoken sentence.
|
|
147
|
+
snap_options = {
|
|
148
|
+
"line_max_extend": CONFIG["clip_snap_max_extend"],
|
|
149
|
+
"scene_margin": CONFIG["scene_cut_snap_margin"],
|
|
150
|
+
"scene_threshold": CONFIG["scene_cut_detect_threshold"],
|
|
151
|
+
"start_max_prepend": CONFIG["clip_start_snap_max_prepend"],
|
|
152
|
+
"start_max_trim": CONFIG["clip_start_snap_max_trim"],
|
|
153
|
+
"do_line_snap": CONFIG["snap_clip_line_end"],
|
|
154
|
+
"do_scene_snap": CONFIG["scene_cut_snap"],
|
|
155
|
+
}
|
|
156
|
+
if sources_manifest is None:
|
|
157
|
+
video_duration = get_video_duration(args.video)
|
|
158
|
+
validated_plan = normalize_clip_plan(
|
|
137
159
|
raw_plan,
|
|
138
|
-
|
|
160
|
+
video_duration,
|
|
139
161
|
target_duration=target_seconds,
|
|
140
|
-
clip_padding=clip_padding,
|
|
141
162
|
allow_overlap=args.allow_overlap,
|
|
142
163
|
)
|
|
143
|
-
video_duration = None
|
|
144
164
|
else:
|
|
145
|
-
|
|
146
|
-
validated_plan = normalize_clip_plan(
|
|
165
|
+
validated_plan = normalize_multi_source_clip_plan(
|
|
147
166
|
raw_plan,
|
|
148
|
-
|
|
167
|
+
sources_manifest,
|
|
149
168
|
target_duration=target_seconds,
|
|
150
|
-
clip_padding=clip_padding,
|
|
151
169
|
allow_overlap=args.allow_overlap,
|
|
152
170
|
)
|
|
153
|
-
|
|
154
|
-
#
|
|
155
|
-
#
|
|
156
|
-
#
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
validated_plan
|
|
160
|
-
args.video,
|
|
161
|
-
margin=CONFIG["scene_cut_snap_margin"],
|
|
162
|
-
threshold=CONFIG["scene_cut_detect_threshold"],
|
|
171
|
+
# The canvas (and so the output frame rate) is chosen before snapping because the
|
|
172
|
+
# frame-grid pass snaps clip lengths to whole output frames; the same geometry is
|
|
173
|
+
# recorded in clip_plan_validated.json and used for the render.
|
|
174
|
+
# Single-source clips carry no source_path; the CLI video is the only input.
|
|
175
|
+
source_paths = list(
|
|
176
|
+
dict.fromkeys(
|
|
177
|
+
clip["source_path"] for clip in validated_plan["clips"] if "source_path" in clip
|
|
163
178
|
)
|
|
164
|
-
|
|
179
|
+
) or [str(args.video)]
|
|
180
|
+
_, _, _, geometry_qc = _select_output_geometry(source_paths, validated_plan["clips"])
|
|
181
|
+
frame_grids = source_frame_grids(geometry_qc)
|
|
182
|
+
for path, ranges in _required_ranges_by_source(raw_plan, source_paths).items():
|
|
183
|
+
frame_grids[path]["keep_ranges"] = ranges
|
|
165
184
|
if sources_manifest is None:
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
185
|
+
validated_plan = snap_source_clips(
|
|
186
|
+
validated_plan, args.video, video_duration, work_dir,
|
|
187
|
+
frame_grid=frame_grids[str(args.video)], **snap_options,
|
|
169
188
|
)
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
validated_plan,
|
|
173
|
-
safe_boundaries,
|
|
174
|
-
video_duration,
|
|
175
|
-
CONFIG["clip_start_snap_max_prepend"],
|
|
176
|
-
max_trim=CONFIG["clip_start_snap_max_trim"],
|
|
177
|
-
)
|
|
178
|
-
validated_plan = snap_clip_ends_to_lines(
|
|
179
|
-
validated_plan,
|
|
180
|
-
safe_boundaries,
|
|
181
|
-
video_duration,
|
|
182
|
-
CONFIG["clip_snap_max_extend"],
|
|
183
|
-
)
|
|
184
|
-
validated_plan = enforce_clip_sentence_boundaries(
|
|
185
|
-
validated_plan,
|
|
186
|
-
safe_boundaries,
|
|
187
|
-
_load_source_speech_spans(work_dir),
|
|
188
|
-
video_duration,
|
|
189
|
-
)
|
|
190
|
-
|
|
191
|
-
# Multi-source: snap each clip against ITS OWN source's pauses/shot-changes (single-source
|
|
192
|
-
# snaps above can't, since silence_periods.json and args.video are per-project, not per-source).
|
|
193
|
-
if sources_manifest is not None:
|
|
189
|
+
else:
|
|
190
|
+
# Each clip snaps against ITS OWN source's pauses, shot changes and frame grid.
|
|
194
191
|
validated_plan = snap_multi_source_clips(
|
|
195
|
-
validated_plan,
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
do_scene_snap=CONFIG["scene_cut_snap"],
|
|
203
|
-
start_max_prepend=CONFIG["clip_start_snap_max_prepend"],
|
|
204
|
-
start_max_trim=CONFIG["clip_start_snap_max_trim"],
|
|
192
|
+
validated_plan, validated_plan["sources"], work_dir,
|
|
193
|
+
frame_grids={
|
|
194
|
+
sid: frame_grids[source["source_path"]]
|
|
195
|
+
for sid, source in validated_plan["sources"].items()
|
|
196
|
+
if source["source_path"] in frame_grids
|
|
197
|
+
},
|
|
198
|
+
**snap_options,
|
|
205
199
|
)
|
|
206
200
|
|
|
207
201
|
validated_plan.setdefault("qc", {})["join_fade_ms"] = round(
|
|
208
202
|
CONFIG["clip_join_audio_fade_ms"], 3
|
|
209
203
|
)
|
|
210
|
-
# Single-source clips carry no source_path; the CLI video is the only input.
|
|
211
|
-
source_paths = list(
|
|
212
|
-
dict.fromkeys(
|
|
213
|
-
clip["source_path"] for clip in validated_plan["clips"] if "source_path" in clip
|
|
214
|
-
)
|
|
215
|
-
) or [str(args.video)]
|
|
216
|
-
_, _, _, geometry_qc = _select_output_geometry(source_paths, validated_plan["clips"])
|
|
217
204
|
validated_plan["qc"]["output_geometry"] = geometry_qc
|
|
218
205
|
validated_plan["qc"]["output_geometry_reason"] = geometry_qc["reason"]
|
|
206
|
+
record_frame_grid(validated_plan, geometry_qc)
|
|
219
207
|
update_cut_qc(
|
|
220
208
|
validated_plan,
|
|
221
209
|
allow_duration_drift=bool(args.allow_duration_drift),
|
|
@@ -223,8 +211,6 @@ def main():
|
|
|
223
211
|
)
|
|
224
212
|
if isinstance(raw_plan, dict) and 'required_evidence' in raw_plan:
|
|
225
213
|
# Re-evaluate the final snapped ranges even when the media cache can be reused.
|
|
226
|
-
# A prior rendered receipt must not survive a failed revision preflight.
|
|
227
|
-
(work_dir / 'cut_delivery_qc.json').unlink(missing_ok=True)
|
|
228
214
|
contract = raw_plan['required_evidence']
|
|
229
215
|
plan_sources = {str(Path(path).resolve()): path for path in source_paths}
|
|
230
216
|
source_audio = {}
|
|
@@ -240,14 +226,16 @@ def main():
|
|
|
240
226
|
validated_plan['qc']['required_evidence'] = {**report, 'contract': contract}
|
|
241
227
|
if report['selection_status'] == 'BLOCK':
|
|
242
228
|
validated_plan['qc'].setdefault('blocking', []).extend(report['findings'])
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
229
|
+
plan_path = work_dir / "clip_plan_validated.json"
|
|
230
|
+
raw_plan_paths = {clip_plan_path, work_dir / "clip_plan.json"}
|
|
231
|
+
edited_source_path = work_dir / "edited_source.mp4"
|
|
232
|
+
reuse = (
|
|
233
|
+
not validated_plan["qc"].get("blocking")
|
|
234
|
+
and not args.normalize_only
|
|
235
|
+
and should_reuse_edited_source(edited_source_path, validated_plan, args.video)
|
|
250
236
|
)
|
|
237
|
+
if not reuse:
|
|
238
|
+
_write_validated_plan(plan_path, validated_plan, raw_plan_paths)
|
|
251
239
|
if validated_plan["qc"].get("blocking"):
|
|
252
240
|
raise SystemExit(
|
|
253
241
|
"clip_plan QC blocking: fix required source evidence, unsafe sentence boundaries or target-duration drift. "
|
|
@@ -255,9 +243,6 @@ def main():
|
|
|
255
243
|
"sentence truncation is never allowed. See clip_plan_validated.json['qc']."
|
|
256
244
|
)
|
|
257
245
|
if args.normalize_only:
|
|
258
|
-
# normalize-only produces planned delivery facts in clip_plan_validated.json, but no
|
|
259
|
-
# rendered/reused media exists in this run, so remove any stale final delivery artifact.
|
|
260
|
-
(work_dir / "cut_delivery_qc.json").unlink(missing_ok=True)
|
|
261
246
|
print(
|
|
262
247
|
json.dumps(
|
|
263
248
|
{
|
|
@@ -270,27 +255,14 @@ def main():
|
|
|
270
255
|
)
|
|
271
256
|
return
|
|
272
257
|
|
|
273
|
-
|
|
274
|
-
if should_reuse_edited_source(edited_source_path, validated_plan, args.video):
|
|
258
|
+
if reuse:
|
|
275
259
|
log(f"复用剪辑源视频: {edited_source_path}")
|
|
276
|
-
update_delivery_qc(
|
|
277
|
-
validated_plan,
|
|
278
|
-
source_paths=source_paths,
|
|
279
|
-
output_path=edited_source_path,
|
|
280
|
-
rendered=True,
|
|
281
|
-
)
|
|
282
|
-
write_cut_delivery_qc(work_dir, validated_plan)
|
|
283
260
|
_write_edited_source_meta(edited_source_path, validated_plan, args.video)
|
|
284
|
-
(work_dir / "clip_plan_validated.json").write_text(
|
|
285
|
-
json.dumps(validated_plan, ensure_ascii=False, indent=2), encoding="utf-8"
|
|
286
|
-
)
|
|
287
261
|
else:
|
|
288
262
|
build_edited_source_video(
|
|
289
263
|
args.video, validated_plan, work_dir, edited_source_path
|
|
290
264
|
)
|
|
291
|
-
|
|
292
|
-
json.dumps(validated_plan, ensure_ascii=False, indent=2), encoding="utf-8"
|
|
293
|
-
)
|
|
265
|
+
_write_validated_plan(plan_path, validated_plan, raw_plan_paths)
|
|
294
266
|
|
|
295
267
|
if args.review_shots:
|
|
296
268
|
review_options = {"plan_path": work_dir / "clip_plan_validated.json"}
|