@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
|
@@ -6,12 +6,16 @@ from pathlib import Path
|
|
|
6
6
|
|
|
7
7
|
from asr_timing_evidence import EVIDENCE_FILENAME, validate_asr_timing_evidence
|
|
8
8
|
from extract import FRAME_TIME_CONVENTION_VERSION
|
|
9
|
-
from lib import
|
|
9
|
+
from lib import (
|
|
10
|
+
CONFIG,
|
|
11
|
+
file_identity,
|
|
12
|
+
is_usable_overview_chunk,
|
|
13
|
+
log,
|
|
14
|
+
offline_ignored_settings,
|
|
15
|
+
)
|
|
10
16
|
|
|
11
17
|
|
|
12
|
-
from vlm import
|
|
13
|
-
_is_mimo_chunk_usable,
|
|
14
|
-
)
|
|
18
|
+
from vlm import vlm_prompt_payload
|
|
15
19
|
|
|
16
20
|
|
|
17
21
|
def _fresh(out, *inputs):
|
|
@@ -45,9 +49,19 @@ def _artifact_identity(path):
|
|
|
45
49
|
return file_identity(path) if path.exists() else None
|
|
46
50
|
|
|
47
51
|
|
|
48
|
-
def
|
|
52
|
+
def _without_settings(meta, keys):
|
|
53
|
+
settings = meta.get("settings")
|
|
54
|
+
if not keys or not isinstance(settings, dict):
|
|
55
|
+
return meta
|
|
56
|
+
return {**meta, "settings": {k: v for k, v in settings.items() if k not in keys}}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _stage_cache_valid(artifact_path, expected_meta, *, ignore_settings=()):
|
|
49
60
|
"""A stage output is reusable when it exists, is non-empty, its sidecar equals the
|
|
50
|
-
expected inputs/settings dict, and the output itself is the one the sidecar recorded.
|
|
61
|
+
expected inputs/settings dict, and the output itself is the one the sidecar recorded.
|
|
62
|
+
|
|
63
|
+
`ignore_settings` names `settings` keys left out of the comparison on both sides (an
|
|
64
|
+
endpoint URL that only reflects which credential is set, or a retired legacy key)."""
|
|
51
65
|
artifact_path = Path(artifact_path)
|
|
52
66
|
if not artifact_path.exists() or artifact_path.stat().st_size == 0:
|
|
53
67
|
return False
|
|
@@ -62,7 +76,9 @@ def _stage_cache_valid(artifact_path, expected_meta):
|
|
|
62
76
|
return False
|
|
63
77
|
expected = dict(expected_meta)
|
|
64
78
|
expected["artifact"] = meta["artifact"]
|
|
65
|
-
return meta ==
|
|
79
|
+
return _without_settings(meta, ignore_settings) == _without_settings(
|
|
80
|
+
expected, ignore_settings
|
|
81
|
+
)
|
|
66
82
|
|
|
67
83
|
|
|
68
84
|
def _write_stage_meta(artifact_path, meta):
|
|
@@ -129,7 +145,7 @@ def _merge_overview_into_scenes(scenes, overview_path):
|
|
|
129
145
|
by_scene = {}
|
|
130
146
|
for chunk in overview["chunks"]:
|
|
131
147
|
content = chunk["content"].strip()
|
|
132
|
-
if
|
|
148
|
+
if is_usable_overview_chunk(content):
|
|
133
149
|
by_scene.setdefault(chunk["scene_id"], []).append(content)
|
|
134
150
|
if not by_scene:
|
|
135
151
|
return scenes
|
|
@@ -206,7 +222,6 @@ def _asr_cache_payload(video_path, *, skip_asr=False):
|
|
|
206
222
|
"inputs": {"video": _video_input(video_path)},
|
|
207
223
|
"settings": {
|
|
208
224
|
"skip_asr": bool(skip_asr),
|
|
209
|
-
"mimo_asr_api_key_present": bool(CONFIG.get("mimo_asr_api_key")),
|
|
210
225
|
"mimo_asr_api_url": CONFIG.get("mimo_asr_api_url"),
|
|
211
226
|
"mimo_asr_model": CONFIG.get("mimo_asr_model"),
|
|
212
227
|
"mimo_asr_language": CONFIG.get("mimo_asr_language"),
|
|
@@ -216,21 +231,29 @@ def _asr_cache_payload(video_path, *, skip_asr=False):
|
|
|
216
231
|
}
|
|
217
232
|
|
|
218
233
|
|
|
234
|
+
# Sidecars written before 0.6.1 recorded whether an ASR key was set. Key presence is not an
|
|
235
|
+
# output setting: a missing key only meant the run wrote an UNAVAILABLE_NO_KEY placeholder,
|
|
236
|
+
# which the evidence status below already refuses to reuse.
|
|
237
|
+
_LEGACY_ASR_SETTINGS = ("mimo_asr_api_key_present",)
|
|
238
|
+
|
|
239
|
+
|
|
219
240
|
def _asr_cache_state(artifact_path, expected_meta, video_path):
|
|
220
241
|
"""Classify an ASR cache without upgrading stale evidence into new authority."""
|
|
221
242
|
artifact_path = Path(artifact_path)
|
|
222
|
-
|
|
243
|
+
ignored = _LEGACY_ASR_SETTINGS + offline_ignored_settings(
|
|
244
|
+
CONFIG.get("mimo_asr_api_key"), "mimo_asr_api_url"
|
|
245
|
+
)
|
|
246
|
+
if not _stage_cache_valid(artifact_path, expected_meta, ignore_settings=ignored):
|
|
223
247
|
return "MISS"
|
|
224
248
|
evidence_path = artifact_path.parent / EVIDENCE_FILENAME
|
|
225
|
-
if not evidence_path.exists():
|
|
226
|
-
return "LEGACY_UNVERIFIED"
|
|
227
249
|
if validate_asr_timing_evidence(evidence_path, video_path, artifact_path):
|
|
228
250
|
evidence = _load_json(evidence_path)
|
|
229
|
-
if evidence.get("status") == "LEGACY_UNVERIFIED":
|
|
230
|
-
return "LEGACY_UNVERIFIED"
|
|
231
251
|
# An all-empty transcription is an unexplained outcome, not proven silence; treating it
|
|
232
|
-
# as fresh would make one bad run a permanent cache hit.
|
|
233
|
-
|
|
252
|
+
# as fresh would make one bad run a permanent cache hit. A key-less placeholder is
|
|
253
|
+
# retried too, so setting the key later transcribes for real.
|
|
254
|
+
if evidence.get("status") in {
|
|
255
|
+
"UNAVAILABLE_NO_DURATION", "EMPTY_UNKNOWN", "UNAVAILABLE_NO_KEY"
|
|
256
|
+
}:
|
|
234
257
|
return "MISS"
|
|
235
258
|
return "FRESH"
|
|
236
259
|
return "MISS"
|
|
@@ -263,21 +286,6 @@ def _silence_cache_payload(video_path, asr_json):
|
|
|
263
286
|
}
|
|
264
287
|
|
|
265
288
|
|
|
266
|
-
def _vlm_prompt_payload():
|
|
267
|
-
"""The exact prompt/context text the VLM stage sends; compared by equality."""
|
|
268
|
-
prompt = load_prompt("VLM_DEPTH_PROMPT")
|
|
269
|
-
if not prompt:
|
|
270
|
-
prompt = (
|
|
271
|
-
"仔细观察这些视频帧。分两部分输出:\n"
|
|
272
|
-
"【描述】不超过80字,描述画面中正在发生什么。\n"
|
|
273
|
-
"【深层分析】不超过120字,分析角色情绪、关系动态、潜台词。"
|
|
274
|
-
)
|
|
275
|
-
context = CONFIG.get("context_info", "")
|
|
276
|
-
if context:
|
|
277
|
-
prompt = f"已知信息:{context}\n\n{prompt}"
|
|
278
|
-
return {"prompt_text": prompt, "context_info": context}
|
|
279
|
-
|
|
280
|
-
|
|
281
289
|
def _vlm_cache_payload(video_path, work_dir, scenes_json, frames):
|
|
282
290
|
return {
|
|
283
291
|
"schema_version": 1,
|
|
@@ -290,7 +298,7 @@ def _vlm_cache_payload(video_path, work_dir, scenes_json, frames):
|
|
|
290
298
|
),
|
|
291
299
|
},
|
|
292
300
|
"frames": _frame_cache_payload(video_path, CONFIG.get("fps"), frames),
|
|
293
|
-
"prompt":
|
|
301
|
+
"prompt": vlm_prompt_payload(),
|
|
294
302
|
"settings": {
|
|
295
303
|
"fps": CONFIG.get("fps"),
|
|
296
304
|
"vlm_model": CONFIG.get("vlm_model"),
|
|
@@ -7,14 +7,14 @@ import json
|
|
|
7
7
|
|
|
8
8
|
from pathlib import Path
|
|
9
9
|
|
|
10
|
-
from lib import CONFIG, log, get_video_duration, api_call
|
|
10
|
+
from lib import CONFIG, log, get_video_duration, api_call, offline_ignored_settings
|
|
11
11
|
|
|
12
12
|
from extract import extract_frames
|
|
13
13
|
|
|
14
|
-
from detect import detect_scenes, detect_silence_periods,
|
|
14
|
+
from detect import detect_scenes, detect_silence_periods, ensure_speech_boundary_anchors
|
|
15
15
|
|
|
16
16
|
from asr import transcribe_audio
|
|
17
|
-
from asr_timing_evidence import write_asr_timing_evidence
|
|
17
|
+
from asr_timing_evidence import write_asr_timing_evidence
|
|
18
18
|
|
|
19
19
|
from vlm import (
|
|
20
20
|
analyze_scenes,
|
|
@@ -22,10 +22,12 @@ from vlm import (
|
|
|
22
22
|
mimo_video_overview_cache_fresh,
|
|
23
23
|
)
|
|
24
24
|
|
|
25
|
-
from
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
25
|
+
from understanding_brief import (
|
|
26
|
+
_finish_brief,
|
|
27
|
+
_research_context,
|
|
28
|
+
_write_brief_from_existing_artifacts,
|
|
29
|
+
)
|
|
30
|
+
from understanding_storyboard import _write_edited_storyboard_only
|
|
29
31
|
from understanding_cache import (
|
|
30
32
|
_asr_cache_payload,
|
|
31
33
|
_asr_cache_state,
|
|
@@ -43,11 +45,28 @@ from understanding_cache import (
|
|
|
43
45
|
_write_mimo_overview_status,
|
|
44
46
|
_write_stage_meta,
|
|
45
47
|
)
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _refuse_keyless_overwrite(asr_json):
|
|
51
|
+
"""Stop before a key-less ASR miss replaces a real transcript with the [] placeholder.
|
|
52
|
+
|
|
53
|
+
The cache can miss for reasons that say nothing about the transcript, e.g. a work_dir
|
|
54
|
+
copied without keeping mtimes changes the recorded artifact identity."""
|
|
55
|
+
if CONFIG["mimo_asr_api_key"] or not asr_json.exists():
|
|
56
|
+
return
|
|
57
|
+
try:
|
|
58
|
+
existing = json.loads(asr_json.read_text(encoding="utf-8"))
|
|
59
|
+
except (OSError, ValueError):
|
|
60
|
+
return
|
|
61
|
+
if isinstance(existing, list) and existing:
|
|
62
|
+
raise SystemExit(
|
|
63
|
+
"ASR 缓存与当前视频、设置或文件时间不匹配(常见原因:复制 work_dir 或视频时没保留"
|
|
64
|
+
"修改时间),且未设置 MIMO_ASR_API_KEY / MIMO_API_KEY,无法重新转写;"
|
|
65
|
+
f"已保留现有 asr_result.json({len(existing)} 段),未覆盖。"
|
|
66
|
+
"请用保留时间的方式重新复制(cp -p / cp -Rp / rsync -t),"
|
|
67
|
+
"或设置 MIMO_ASR_API_KEY 或 MIMO_API_KEY 后重跑(会重新转写)。"
|
|
68
|
+
"不要用 --skip-asr 绕过:它会把现有转写替换成 []。"
|
|
69
|
+
)
|
|
51
70
|
|
|
52
71
|
|
|
53
72
|
def main():
|
|
@@ -82,6 +101,14 @@ def main():
|
|
|
82
101
|
action="store_true",
|
|
83
102
|
help="rebuild agent_narration_brief.md from existing artifacts only; no extraction/API",
|
|
84
103
|
)
|
|
104
|
+
ap.add_argument(
|
|
105
|
+
"--edited-storyboard-only",
|
|
106
|
+
action="store_true",
|
|
107
|
+
help=(
|
|
108
|
+
"write storyboard/edited_storyboard.* from clip_plan_validated.json (single- or "
|
|
109
|
+
"multi-source) and add its pointer to the existing brief; no extraction/API"
|
|
110
|
+
),
|
|
111
|
+
)
|
|
85
112
|
ap.add_argument(
|
|
86
113
|
"--consolidate",
|
|
87
114
|
action=argparse.BooleanOptionalAction,
|
|
@@ -94,6 +121,8 @@ def main():
|
|
|
94
121
|
help="also clean the ASR transcript (Pass A)",
|
|
95
122
|
)
|
|
96
123
|
args = ap.parse_args()
|
|
124
|
+
if args.brief_only and args.edited_storyboard_only:
|
|
125
|
+
ap.error("--brief-only and --edited-storyboard-only are mutually exclusive")
|
|
97
126
|
|
|
98
127
|
video = args.video
|
|
99
128
|
work_dir = Path(args.work_dir)
|
|
@@ -125,6 +154,9 @@ def main():
|
|
|
125
154
|
if args.brief_only:
|
|
126
155
|
_write_brief_from_existing_artifacts(video, work_dir, args, video_duration)
|
|
127
156
|
return
|
|
157
|
+
if args.edited_storyboard_only:
|
|
158
|
+
_write_edited_storyboard_only(video, work_dir)
|
|
159
|
+
return
|
|
128
160
|
|
|
129
161
|
scenes_json = work_dir / "scenes.json"
|
|
130
162
|
asr_json = work_dir / "asr_result.json"
|
|
@@ -149,6 +181,17 @@ def main():
|
|
|
149
181
|
scenes = detect_scenes(video, work_dir, CONFIG["scene_threshold"])
|
|
150
182
|
_write_stage_meta(scenes_json, scenes_meta)
|
|
151
183
|
|
|
184
|
+
# The VLM cache does not depend on ASR, so an offline run that would stop at Step 4 stops
|
|
185
|
+
# here instead, before Step 3 can rewrite the transcript or the silence windows.
|
|
186
|
+
vlm_meta = _vlm_cache_payload(video, work_dir, scenes_json, frames)
|
|
187
|
+
vlm_offline = offline_ignored_settings(CONFIG["api_key"], "api_url")
|
|
188
|
+
vlm_cached = not args.force and _stage_cache_valid(
|
|
189
|
+
vlm_json, vlm_meta, ignore_settings=vlm_offline
|
|
190
|
+
)
|
|
191
|
+
if not vlm_cached and not CONFIG["api_key"]:
|
|
192
|
+
key_name = CONFIG["api_env_var"]
|
|
193
|
+
raise SystemExit(f"请设置 {key_name} 环境变量(VLM 画面分析需要)")
|
|
194
|
+
|
|
152
195
|
# Step 3: ASR
|
|
153
196
|
asr_meta = _asr_cache_payload(video, skip_asr=args.skip_asr)
|
|
154
197
|
cache_state = None
|
|
@@ -164,22 +207,11 @@ def main():
|
|
|
164
207
|
work_dir, video, "EXPLICITLY_SKIPPED", final_segments=asr_result
|
|
165
208
|
)
|
|
166
209
|
log("跳过 ASR(--skip-asr)")
|
|
167
|
-
elif cache_state
|
|
168
|
-
"FRESH",
|
|
169
|
-
"LEGACY_UNVERIFIED",
|
|
170
|
-
}:
|
|
210
|
+
elif cache_state == "FRESH":
|
|
171
211
|
asr_result = _load_json(asr_json)
|
|
172
|
-
|
|
173
|
-
write_asr_timing_evidence(
|
|
174
|
-
work_dir,
|
|
175
|
-
video,
|
|
176
|
-
"LEGACY_UNVERIFIED",
|
|
177
|
-
final_segments=asr_result,
|
|
178
|
-
)
|
|
179
|
-
log(f"复用旧 ASR({len(asr_result)} 段;时间/声学证据未经验证)")
|
|
180
|
-
else:
|
|
181
|
-
log(f"跳过 ASR(证据匹配,已存在 {len(asr_result)} 段)")
|
|
212
|
+
log(f"跳过 ASR(证据匹配,已存在 {len(asr_result)} 段)")
|
|
182
213
|
else:
|
|
214
|
+
_refuse_keyless_overwrite(asr_json)
|
|
183
215
|
try:
|
|
184
216
|
asr_result = transcribe_audio(video, work_dir)
|
|
185
217
|
except Exception as e:
|
|
@@ -195,21 +227,16 @@ def main():
|
|
|
195
227
|
if not args.force and _stage_cache_valid(silence_json, silence_meta):
|
|
196
228
|
silence_periods = _load_json(silence_json)
|
|
197
229
|
log(f"跳过静音检测(已存在 {len(silence_periods)} 个窗口)")
|
|
198
|
-
|
|
199
|
-
detect_speech_boundary_anchors(work_dir, asr_result)
|
|
230
|
+
ensure_speech_boundary_anchors(work_dir, asr_result)
|
|
200
231
|
else:
|
|
201
232
|
silence_periods = detect_silence_periods(video, work_dir, asr_result)
|
|
202
233
|
_write_stage_meta(silence_json, silence_meta)
|
|
203
234
|
|
|
204
235
|
# Step 4: VLM analysis (the only stage that requires the chat API key)
|
|
205
|
-
|
|
206
|
-
if not args.force and _stage_cache_valid(vlm_json, vlm_meta):
|
|
236
|
+
if vlm_cached:
|
|
207
237
|
vlm_analysis = _load_json(vlm_json)
|
|
208
238
|
log(f"跳过 VLM 分析(已存在 {len(vlm_analysis)} 个场景)")
|
|
209
239
|
else:
|
|
210
|
-
if not CONFIG["api_key"]:
|
|
211
|
-
key_name = CONFIG["api_env_var"]
|
|
212
|
-
raise SystemExit(f"请设置 {key_name} 环境变量(VLM 画面分析需要)")
|
|
213
240
|
log("VLM API 连通性预检...")
|
|
214
241
|
api_call(
|
|
215
242
|
{
|
|
@@ -224,7 +251,14 @@ def main():
|
|
|
224
251
|
# Step 4.1: optional MiMo scene-chunk video understanding
|
|
225
252
|
overview_path = work_dir / "mimo_video_overview.json"
|
|
226
253
|
if CONFIG["mimo_video_overview"]:
|
|
227
|
-
if not CONFIG["mimo_video_api_key"]
|
|
254
|
+
if not CONFIG["mimo_video_api_key"] and mimo_video_overview_cache_fresh(
|
|
255
|
+
overview_path, video, scenes
|
|
256
|
+
):
|
|
257
|
+
log("未设置 MIMO_API_KEY,复用已缓存的 MiMo 分片视频概览")
|
|
258
|
+
_write_mimo_overview_status(
|
|
259
|
+
work_dir, "cached", "未设置 MIMO_API_KEY,复用缓存", overview_path.name
|
|
260
|
+
)
|
|
261
|
+
elif not CONFIG["mimo_video_api_key"]:
|
|
228
262
|
log("跳过 MiMo 分片视频概览:未设置 MIMO_API_KEY")
|
|
229
263
|
overview_path.unlink(missing_ok=True)
|
|
230
264
|
_write_mimo_overview_status(
|
|
@@ -265,123 +299,62 @@ def main():
|
|
|
265
299
|
vlm_analysis = _merge_overview_into_scenes(vlm_analysis, overview_path)
|
|
266
300
|
|
|
267
301
|
# optional consolidation (整理): build the understanding index before the brief folds it in
|
|
268
|
-
|
|
302
|
+
enabled = bool(args.consolidate or args.consolidate_asr)
|
|
303
|
+
status, message, artifacts = "disabled", "consolidation 未启用", []
|
|
304
|
+
if enabled:
|
|
269
305
|
from consolidate import consolidate
|
|
270
306
|
|
|
307
|
+
failure = None
|
|
308
|
+
result = {}
|
|
271
309
|
try:
|
|
272
|
-
consolidate(
|
|
310
|
+
result = consolidate(
|
|
273
311
|
work_dir, do_asr=args.consolidate_asr, do_index=args.consolidate
|
|
274
312
|
)
|
|
275
313
|
except Exception as e:
|
|
276
314
|
log(f"consolidate 跳过(忽略): {e}")
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
do_asr=args.consolidate_asr,
|
|
283
|
-
do_index=args.consolidate,
|
|
284
|
-
)
|
|
315
|
+
failure = e
|
|
316
|
+
artifacts = _present_consolidation_artifacts(work_dir)
|
|
317
|
+
no_key = result.get("skipped_no_key") or []
|
|
318
|
+
if failure is not None:
|
|
319
|
+
status, message = "failed", failure
|
|
285
320
|
else:
|
|
286
321
|
expected = []
|
|
287
322
|
skipped = []
|
|
288
|
-
if args.consolidate:
|
|
323
|
+
if args.consolidate and "index" not in no_key:
|
|
289
324
|
if vlm_analysis:
|
|
290
325
|
expected.append("understanding_index.json")
|
|
291
326
|
else:
|
|
292
327
|
skipped.append("无 vlm_analysis,跳过 index")
|
|
293
|
-
if args.consolidate_asr:
|
|
328
|
+
if args.consolidate_asr and "asr" not in no_key:
|
|
294
329
|
if asr_result:
|
|
295
330
|
expected.append("asr_clean.json")
|
|
296
331
|
else:
|
|
297
332
|
skipped.append("无 ASR 文本,跳过 ASR 清洗")
|
|
298
|
-
artifacts = _present_consolidation_artifacts(work_dir)
|
|
299
333
|
missing = [name for name in expected if name not in artifacts]
|
|
300
334
|
if missing:
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
do_asr=args.consolidate_asr,
|
|
307
|
-
do_index=args.consolidate,
|
|
335
|
+
status, message = "failed", f"未产出预期 artifact: {', '.join(missing)}"
|
|
336
|
+
elif no_key:
|
|
337
|
+
status, message = (
|
|
338
|
+
"skipped_no_key",
|
|
339
|
+
f"未设置 {CONFIG['api_env_var']},consolidation({', '.join(no_key)})未发送请求",
|
|
308
340
|
)
|
|
309
341
|
elif expected:
|
|
310
|
-
|
|
311
|
-
work_dir,
|
|
312
|
-
"ok",
|
|
313
|
-
"consolidation 完成",
|
|
314
|
-
artifacts,
|
|
315
|
-
do_asr=args.consolidate_asr,
|
|
316
|
-
do_index=args.consolidate,
|
|
317
|
-
)
|
|
342
|
+
status, message = "ok", "consolidation 完成"
|
|
318
343
|
else:
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
"skipped",
|
|
322
|
-
";".join(skipped) or "无可整理输入",
|
|
323
|
-
artifacts,
|
|
324
|
-
do_asr=args.consolidate_asr,
|
|
325
|
-
do_index=args.consolidate,
|
|
326
|
-
)
|
|
327
|
-
else:
|
|
328
|
-
_write_consolidation_status(
|
|
329
|
-
work_dir,
|
|
330
|
-
"disabled",
|
|
331
|
-
"consolidation 未启用",
|
|
332
|
-
[],
|
|
333
|
-
enabled=False,
|
|
334
|
-
do_asr=False,
|
|
335
|
-
do_index=False,
|
|
336
|
-
)
|
|
337
|
-
|
|
338
|
-
# Storyboard contact sheets (advisory, never blocking). Source uses scene anchors over the
|
|
339
|
-
# source timeline; edited is gated on clip_plan_validated.json file-presence (NOT edit_mode —
|
|
340
|
-
# recap.py forwards --edit-mode cut in BOTH passes, so the validated plan is the only reliable
|
|
341
|
-
# pass2 signal). Both cache via _write_stage_meta/_stage_cache_valid with fps + frame-set in the key.
|
|
342
|
-
source_storyboard = _generate_source_storyboard(
|
|
343
|
-
work_dir, Path(video), scenes, scenes_json, force=args.force
|
|
344
|
-
)
|
|
345
|
-
edited_storyboard = _generate_edited_storyboard(work_dir, video, force=args.force)
|
|
346
|
-
cut_mode = (work_dir / "clip_plan_validated.json").exists()
|
|
347
|
-
|
|
348
|
-
# understanding substrate warning + writing brief
|
|
349
|
-
substrate = assess_understanding_substrate(vlm_analysis, asr_result)
|
|
350
|
-
if substrate["level"] != "rich":
|
|
351
|
-
banner = "理解素材为空" if substrate["level"] == "empty" else "理解素材偏薄"
|
|
352
|
-
log(
|
|
353
|
-
f"⚠️ {banner}:ASR {substrate['asr_chars']} 字 | 场景 {substrate['scene_count']} | "
|
|
354
|
-
f"带 frame_facts 的场景 {substrate['scenes_with_frame_facts']} | 平均画面描述 {substrate['avg_description_len']} 字"
|
|
355
|
-
)
|
|
356
|
-
brief_path = build_agent_brief(
|
|
357
|
-
vlm_analysis,
|
|
358
|
-
asr_result,
|
|
359
|
-
silence_periods,
|
|
360
|
-
video_duration,
|
|
344
|
+
status, message = "skipped", ";".join(skipped) or "无可整理输入"
|
|
345
|
+
_write_consolidation_status(
|
|
361
346
|
work_dir,
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
# byte-identical. Prepends a storyboard header pointing the agent at the sheet(s).
|
|
369
|
-
_prepend_storyboard_brief_header(
|
|
370
|
-
brief_path, source_storyboard, edited_storyboard, cut_mode=cut_mode
|
|
347
|
+
status,
|
|
348
|
+
message,
|
|
349
|
+
artifacts,
|
|
350
|
+
enabled=enabled,
|
|
351
|
+
do_asr=args.consolidate_asr,
|
|
352
|
+
do_index=args.consolidate,
|
|
371
353
|
)
|
|
372
354
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
"status": "analyzed",
|
|
379
|
-
"work_dir": str(work_dir),
|
|
380
|
-
"brief": str(brief_path),
|
|
381
|
-
"substrate": substrate["level"],
|
|
382
|
-
"scenes": len(scenes),
|
|
383
|
-
"asr_segments": len(asr_result),
|
|
384
|
-
},
|
|
385
|
-
ensure_ascii=False,
|
|
386
|
-
)
|
|
355
|
+
_finish_brief(
|
|
356
|
+
video, work_dir, args, video_duration,
|
|
357
|
+
storyboard_scenes=scenes, brief_scenes=vlm_analysis,
|
|
358
|
+
asr_result=asr_result, silence_periods=silence_periods,
|
|
359
|
+
status="analyzed", done_label="理解完成", force=args.force,
|
|
387
360
|
)
|