@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
|
@@ -68,18 +68,6 @@ def parse_duration_seconds(value):
|
|
|
68
68
|
return seconds
|
|
69
69
|
|
|
70
70
|
|
|
71
|
-
def _overlaps_authored_range(ranges, start, end):
|
|
72
|
-
"""Whether [start,end) collides with an already-accepted clip, as AUTHORED.
|
|
73
|
-
|
|
74
|
-
Overlap is judged on the agent's own in/out points, never on the padded ones.
|
|
75
|
-
`clip_padding` deliberately widens every clip by the same amount on both ends, so
|
|
76
|
-
judging padded ranges makes any two back-to-back clips (…, 10) and (10, …) look like
|
|
77
|
-
duplicate footage and hard-fails a perfectly ordinary plan. Padding is an output
|
|
78
|
-
nicety; only what the agent actually asked for defines duplication.
|
|
79
|
-
"""
|
|
80
|
-
return any(start < other_end and end > other_start for other_start, other_end in ranges)
|
|
81
|
-
|
|
82
|
-
|
|
83
71
|
def _clip_value(raw, *names):
|
|
84
72
|
for name in names:
|
|
85
73
|
if name in raw:
|
|
@@ -113,13 +101,23 @@ def _source_identities_for_plan(validated_plan, input_video=None):
|
|
|
113
101
|
return {str(Path(path)): file_identity(path) for path in sorted(paths)}
|
|
114
102
|
|
|
115
103
|
|
|
104
|
+
# Version of the picture-format rules in the edited_source encode (yuv420p, colour tags,
|
|
105
|
+
# RGB conversion; see media_geometry._output_color_tags). Bump it whenever those rules
|
|
106
|
+
# change, so a cached edited_source.mp4 rendered under older rules is rebuilt instead of
|
|
107
|
+
# reaching the final render with labels it no longer matches.
|
|
108
|
+
EDITED_SOURCE_PICTURE_RULES = "yuv420p-color-tags-v2"
|
|
109
|
+
|
|
110
|
+
|
|
116
111
|
def edited_source_render_cache_payload():
|
|
117
112
|
"""Render-affecting settings that invalidate edited_source.mp4 cache reuse.
|
|
118
113
|
|
|
119
114
|
Keep this payload limited to inputs that can change rendered media bytes.
|
|
120
115
|
Observational QC produced after validation/render is intentionally excluded.
|
|
121
116
|
"""
|
|
122
|
-
return {
|
|
117
|
+
return {
|
|
118
|
+
"clip_join_audio_fade_ms": round(CONFIG["clip_join_audio_fade_ms"], 3),
|
|
119
|
+
"picture_rules": EDITED_SOURCE_PICTURE_RULES,
|
|
120
|
+
}
|
|
123
121
|
|
|
124
122
|
|
|
125
123
|
def _write_edited_source_meta(output_path, validated_plan, input_video=None):
|
|
@@ -151,47 +149,33 @@ def should_reuse_edited_source(output_path, validated_plan, input_video=None):
|
|
|
151
149
|
)
|
|
152
150
|
|
|
153
151
|
|
|
154
|
-
|
|
155
|
-
"""
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
return sources_manifest["sources"]
|
|
159
|
-
rows = []
|
|
160
|
-
for source_id, value in sources_manifest.items():
|
|
161
|
-
if source_id in {"schema_version", "version"}:
|
|
162
|
-
continue
|
|
163
|
-
if isinstance(value, dict):
|
|
164
|
-
row = dict(value)
|
|
165
|
-
row.setdefault("source_id", source_id)
|
|
166
|
-
rows.append(row)
|
|
167
|
-
if rows:
|
|
168
|
-
return rows
|
|
169
|
-
elif isinstance(sources_manifest, list):
|
|
170
|
-
return sources_manifest
|
|
171
|
-
raise ValueError(
|
|
172
|
-
"sources manifest must be a list, a {sources:[...]} object, or a source_id map"
|
|
173
|
-
)
|
|
152
|
+
SOURCES_MANIFEST_SHAPE = (
|
|
153
|
+
'{"sources": [{"source_id": ..., "source_path": ...'
|
|
154
|
+
'[, "duration": seconds, "source_work_dir": ...]}]}'
|
|
155
|
+
)
|
|
174
156
|
|
|
175
157
|
|
|
176
158
|
def normalize_sources_manifest(sources_manifest):
|
|
177
|
-
"""
|
|
159
|
+
"""Read the one accepted manifest shape into {source_id: {source_path, duration[, source_work_dir]}}.
|
|
160
|
+
|
|
161
|
+
The shape is SOURCES_MANIFEST_SHAPE, which is what video-recap writes. A missing
|
|
162
|
+
duration is probed from the media file.
|
|
163
|
+
"""
|
|
164
|
+
rows = sources_manifest.get("sources") if isinstance(sources_manifest, dict) else None
|
|
165
|
+
if not isinstance(rows, list):
|
|
166
|
+
raise ValueError(f"sources manifest must be {SOURCES_MANIFEST_SHAPE}")
|
|
178
167
|
sources = {}
|
|
179
|
-
for idx, raw in enumerate(
|
|
168
|
+
for idx, raw in enumerate(rows):
|
|
180
169
|
if not isinstance(raw, dict):
|
|
181
|
-
raise ValueError(f"source #{idx + 1} must be an object")
|
|
182
|
-
source_id = raw.get("source_id"
|
|
170
|
+
raise ValueError(f"source #{idx + 1} must be an object; expected {SOURCES_MANIFEST_SHAPE}")
|
|
171
|
+
source_id = raw.get("source_id")
|
|
183
172
|
if source_id in (None, ""):
|
|
184
173
|
raise ValueError(f"source #{idx + 1} is missing source_id")
|
|
185
174
|
source_id = str(source_id)
|
|
186
|
-
source_path = raw.get(
|
|
187
|
-
"source_path",
|
|
188
|
-
raw.get("path", raw.get("video_path", raw.get("video", raw.get("file")))),
|
|
189
|
-
)
|
|
175
|
+
source_path = raw.get("source_path")
|
|
190
176
|
if not source_path:
|
|
191
|
-
raise ValueError(f"source {source_id} is missing source_path
|
|
192
|
-
duration = raw.get(
|
|
193
|
-
"duration", raw.get("duration_seconds", raw.get("source_duration"))
|
|
194
|
-
)
|
|
177
|
+
raise ValueError(f"source {source_id} is missing source_path")
|
|
178
|
+
duration = raw.get("duration")
|
|
195
179
|
if duration in (None, ""):
|
|
196
180
|
duration = get_video_duration(source_path)
|
|
197
181
|
try:
|
|
@@ -212,24 +196,17 @@ def normalize_sources_manifest(sources_manifest):
|
|
|
212
196
|
return sources
|
|
213
197
|
|
|
214
198
|
|
|
215
|
-
def
|
|
216
|
-
|
|
217
|
-
sources_manifest,
|
|
218
|
-
target_duration=None,
|
|
219
|
-
clip_padding=0.0,
|
|
220
|
-
min_clip_duration=0.3,
|
|
221
|
-
allow_overlap=False,
|
|
222
|
-
):
|
|
223
|
-
"""Validate a multi-source clip plan and map source_id clips to source paths/durations.
|
|
199
|
+
def _normalize_clips(raw_plan, target_duration, min_clip_duration, allow_overlap, resolve_source):
|
|
200
|
+
"""The normalize loop both plan shapes share.
|
|
224
201
|
|
|
225
|
-
|
|
202
|
+
`resolve_source(raw, idx)` returns (overlap_key, source_duration, clip_fields):
|
|
203
|
+
overlap is judged per key (None for a single source), the clip is clamped to
|
|
204
|
+
source_duration, and clip_fields are added right after `clip_id`. Clip order
|
|
205
|
+
follows the raw plan, so montage ordering is possible.
|
|
226
206
|
"""
|
|
227
|
-
sources = normalize_sources_manifest(sources_manifest)
|
|
228
207
|
if isinstance(raw_plan, dict):
|
|
229
208
|
raw_clips = raw_plan.get("clips", [])
|
|
230
|
-
plan_target = raw_plan.get("target_duration")
|
|
231
|
-
"target_duration_seconds"
|
|
232
|
-
)
|
|
209
|
+
plan_target = raw_plan.get("target_duration")
|
|
233
210
|
if target_duration is None and plan_target not in (None, ""):
|
|
234
211
|
target_duration = parse_duration_seconds(plan_target)
|
|
235
212
|
elif isinstance(raw_plan, list):
|
|
@@ -242,25 +219,16 @@ def normalize_multi_source_clip_plan(
|
|
|
242
219
|
if not isinstance(raw_clips, list):
|
|
243
220
|
raise ValueError("clip_plan.json field `clips` must be an array")
|
|
244
221
|
|
|
245
|
-
padding = max(0.0, clip_padding)
|
|
246
222
|
min_duration = max(0.05, min_clip_duration)
|
|
247
223
|
clips = []
|
|
248
|
-
|
|
224
|
+
accepted_ranges = {}
|
|
249
225
|
cursor = 0.0
|
|
250
226
|
|
|
251
227
|
for idx, raw in enumerate(raw_clips):
|
|
252
228
|
if not isinstance(raw, dict):
|
|
253
229
|
log(f" 跳过无效 clip #{idx + 1}: not an object")
|
|
254
230
|
continue
|
|
255
|
-
|
|
256
|
-
if source_id in (None, ""):
|
|
257
|
-
raise ValueError(f"clip #{idx + 1} is missing source_id")
|
|
258
|
-
source_id = str(source_id)
|
|
259
|
-
source = sources.get(source_id)
|
|
260
|
-
if not source:
|
|
261
|
-
raise ValueError(
|
|
262
|
-
f"clip #{idx + 1} references unknown source_id: {source_id}"
|
|
263
|
-
)
|
|
231
|
+
overlap_key, source_duration, clip_fields = resolve_source(raw, idx)
|
|
264
232
|
try:
|
|
265
233
|
raw_start = float(_clip_value(raw, "start", "source_start", "in"))
|
|
266
234
|
raw_end = float(_clip_value(raw, "end", "source_end", "out"))
|
|
@@ -270,155 +238,102 @@ def normalize_multi_source_clip_plan(
|
|
|
270
238
|
if raw_end - raw_start < min_duration:
|
|
271
239
|
log(f" 跳过过短 clip #{idx + 1}: {raw_start:.1f}-{raw_end:.1f}s")
|
|
272
240
|
continue
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
end = round(max(0.0, min(raw_end + padding, source_duration)), 3)
|
|
241
|
+
start = round(max(0.0, min(raw_start, source_duration)), 3)
|
|
242
|
+
end = round(max(0.0, min(raw_end, source_duration)), 3)
|
|
276
243
|
if end - start < min_duration:
|
|
277
244
|
log(f" 跳过过短 clip #{idx + 1}: {start:.1f}-{end:.1f}s")
|
|
278
245
|
continue
|
|
279
|
-
ranges =
|
|
280
|
-
if not allow_overlap and
|
|
246
|
+
ranges = accepted_ranges.setdefault(overlap_key, [])
|
|
247
|
+
if not allow_overlap and any(raw_start < e and raw_end > s for s, e in ranges):
|
|
248
|
+
where = "" if overlap_key is None else f" for source_id {overlap_key}"
|
|
281
249
|
raise ValueError(
|
|
282
|
-
f"clip #{idx + 1} overlaps an earlier source range
|
|
283
|
-
"split or remove duplicate source footage
|
|
250
|
+
f"clip #{idx + 1} overlaps an earlier source range{where}; "
|
|
251
|
+
"split or remove duplicate source footage in the clip plan"
|
|
284
252
|
)
|
|
285
253
|
ranges.append((raw_start, raw_end))
|
|
286
254
|
|
|
287
255
|
duration = round(end - start, 3)
|
|
288
|
-
|
|
256
|
+
clips.append({
|
|
289
257
|
"clip_id": len(clips),
|
|
290
|
-
|
|
291
|
-
"source_path": source["source_path"],
|
|
258
|
+
**clip_fields,
|
|
292
259
|
"source_start": start,
|
|
293
260
|
"source_end": end,
|
|
294
261
|
"output_start": round(cursor, 3),
|
|
295
262
|
"output_end": round(cursor + duration, 3),
|
|
296
263
|
"duration": duration,
|
|
297
264
|
"reason": str(raw.get("reason", raw.get("note", ""))).strip(),
|
|
298
|
-
}
|
|
299
|
-
clips.append(clip)
|
|
265
|
+
})
|
|
300
266
|
cursor += duration
|
|
301
267
|
|
|
302
268
|
if not clips:
|
|
303
269
|
raise ValueError("clip_plan.json has no valid clips")
|
|
304
270
|
|
|
305
|
-
|
|
306
|
-
plan = {
|
|
271
|
+
return {
|
|
307
272
|
"clips": clips,
|
|
308
|
-
"total_duration":
|
|
273
|
+
"total_duration": round(sum(c["duration"] for c in clips), 3),
|
|
309
274
|
"target_duration": round(target_duration, 3) if target_duration else None,
|
|
310
|
-
"sources": {
|
|
311
|
-
sid: {
|
|
312
|
-
"source_path": s["source_path"],
|
|
313
|
-
"duration": round(s["duration"], 3),
|
|
314
|
-
**(
|
|
315
|
-
{"source_work_dir": s["source_work_dir"]}
|
|
316
|
-
if s.get("source_work_dir")
|
|
317
|
-
else {}
|
|
318
|
-
),
|
|
319
|
-
}
|
|
320
|
-
for sid, s in sources.items()
|
|
321
|
-
},
|
|
322
|
-
"allow_overlap": bool(allow_overlap),
|
|
323
275
|
}
|
|
324
|
-
if target_duration and total_duration > target_duration * 1.15:
|
|
325
|
-
plan["warning"] = (
|
|
326
|
-
f"validated clips total {total_duration:.1f}s exceeds target "
|
|
327
|
-
f"{target_duration:.1f}s by more than 15%"
|
|
328
|
-
)
|
|
329
|
-
log(f"警告: {plan['warning']}")
|
|
330
|
-
return plan
|
|
331
276
|
|
|
332
277
|
|
|
333
|
-
def
|
|
278
|
+
def normalize_multi_source_clip_plan(
|
|
334
279
|
raw_plan,
|
|
335
|
-
|
|
280
|
+
sources_manifest,
|
|
336
281
|
target_duration=None,
|
|
337
|
-
clip_padding=0.0,
|
|
338
282
|
min_clip_duration=0.3,
|
|
339
283
|
allow_overlap=False,
|
|
340
284
|
):
|
|
341
|
-
"""Validate and
|
|
285
|
+
"""Validate a multi-source clip plan and map source_id clips to source paths/durations.
|
|
342
286
|
|
|
343
|
-
|
|
344
|
-
Clip order follows the agent-provided order, so montage ordering is possible.
|
|
287
|
+
Clip order follows the raw plan; overlap validation is isolated per source_id.
|
|
345
288
|
"""
|
|
346
|
-
|
|
347
|
-
raw_clips = raw_plan.get("clips", [])
|
|
348
|
-
plan_target = raw_plan.get("target_duration") or raw_plan.get(
|
|
349
|
-
"target_duration_seconds"
|
|
350
|
-
)
|
|
351
|
-
if target_duration is None and plan_target not in (None, ""):
|
|
352
|
-
target_duration = parse_duration_seconds(plan_target)
|
|
353
|
-
elif isinstance(raw_plan, list):
|
|
354
|
-
raw_clips = raw_plan
|
|
355
|
-
else:
|
|
356
|
-
raise ValueError(
|
|
357
|
-
"clip_plan.json must be a JSON array or an object with a clips array"
|
|
358
|
-
)
|
|
359
|
-
|
|
360
|
-
if not isinstance(raw_clips, list):
|
|
361
|
-
raise ValueError("clip_plan.json field `clips` must be an array")
|
|
362
|
-
|
|
363
|
-
padding = max(0.0, clip_padding)
|
|
364
|
-
min_duration = max(0.05, min_clip_duration)
|
|
365
|
-
clips = []
|
|
366
|
-
source_ranges = []
|
|
367
|
-
cursor = 0.0
|
|
289
|
+
sources = normalize_sources_manifest(sources_manifest)
|
|
368
290
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
except (TypeError, ValueError):
|
|
377
|
-
log(f" 跳过无效 clip #{idx + 1}: missing numeric start/end")
|
|
378
|
-
continue
|
|
379
|
-
if raw_end - raw_start < min_duration:
|
|
380
|
-
log(f" 跳过过短 clip #{idx + 1}: {raw_start:.1f}-{raw_end:.1f}s")
|
|
381
|
-
continue
|
|
382
|
-
start = round(max(0.0, min(raw_start - padding, video_duration)), 3)
|
|
383
|
-
end = round(max(0.0, min(raw_end + padding, video_duration)), 3)
|
|
384
|
-
if end - start < min_duration:
|
|
385
|
-
log(f" 跳过过短 clip #{idx + 1}: {start:.1f}-{end:.1f}s")
|
|
386
|
-
continue
|
|
387
|
-
if not allow_overlap and _overlaps_authored_range(source_ranges, raw_start, raw_end):
|
|
291
|
+
def resolve_source(raw, idx):
|
|
292
|
+
source_id = raw.get("source_id")
|
|
293
|
+
if source_id in (None, ""):
|
|
294
|
+
raise ValueError(f"clip #{idx + 1} is missing source_id")
|
|
295
|
+
source_id = str(source_id)
|
|
296
|
+
source = sources.get(source_id)
|
|
297
|
+
if not source:
|
|
388
298
|
raise ValueError(
|
|
389
|
-
f"clip #{idx + 1}
|
|
390
|
-
"split or remove duplicate source footage before mapping narration"
|
|
299
|
+
f"clip #{idx + 1} references unknown source_id: {source_id}"
|
|
391
300
|
)
|
|
392
|
-
|
|
301
|
+
fields = {"source_id": source_id, "source_path": source["source_path"]}
|
|
302
|
+
return source_id, source["duration"], fields
|
|
393
303
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
"
|
|
400
|
-
"
|
|
401
|
-
"
|
|
402
|
-
"reason": str(raw.get("reason", raw.get("note", ""))).strip(),
|
|
304
|
+
plan = _normalize_clips(
|
|
305
|
+
raw_plan, target_duration, min_clip_duration, allow_overlap, resolve_source
|
|
306
|
+
)
|
|
307
|
+
plan["sources"] = {
|
|
308
|
+
sid: {
|
|
309
|
+
"source_path": s["source_path"],
|
|
310
|
+
"duration": round(s["duration"], 3),
|
|
311
|
+
**({"source_work_dir": s["source_work_dir"]} if s.get("source_work_dir") else {}),
|
|
403
312
|
}
|
|
404
|
-
|
|
405
|
-
|
|
313
|
+
for sid, s in sources.items()
|
|
314
|
+
}
|
|
315
|
+
plan["allow_overlap"] = bool(allow_overlap)
|
|
316
|
+
return plan
|
|
406
317
|
|
|
407
|
-
if not clips:
|
|
408
|
-
raise ValueError("clip_plan.json has no valid clips")
|
|
409
318
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
319
|
+
def normalize_clip_plan(
|
|
320
|
+
raw_plan,
|
|
321
|
+
video_duration,
|
|
322
|
+
target_duration=None,
|
|
323
|
+
min_clip_duration=0.3,
|
|
324
|
+
allow_overlap=False,
|
|
325
|
+
):
|
|
326
|
+
"""Validate and enrich an agent-authored single-source clip plan.
|
|
327
|
+
|
|
328
|
+
Returns a dict with validated `clips`, `total_duration`, and target metadata.
|
|
329
|
+
"""
|
|
330
|
+
plan = _normalize_clips(
|
|
331
|
+
raw_plan,
|
|
332
|
+
target_duration,
|
|
333
|
+
min_clip_duration,
|
|
334
|
+
allow_overlap,
|
|
335
|
+
lambda raw, idx: (None, video_duration, {}),
|
|
336
|
+
)
|
|
337
|
+
plan["source_duration"] = round(video_duration, 3)
|
|
338
|
+
plan["allow_overlap"] = bool(allow_overlap)
|
|
424
339
|
return plan
|