@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
|
@@ -19,7 +19,6 @@ VALID_STATUSES = {
|
|
|
19
19
|
"FAILED_AUDIO_EXTRACTION",
|
|
20
20
|
"FAILED_PROVIDER",
|
|
21
21
|
"EMPTY_UNKNOWN",
|
|
22
|
-
"LEGACY_UNVERIFIED",
|
|
23
22
|
}
|
|
24
23
|
_TOP_KEYS = {
|
|
25
24
|
"schema_version", "status", "source_video", "audio", "asr_result",
|
|
@@ -63,10 +62,8 @@ def load_glossary_names(work_dir):
|
|
|
63
62
|
)
|
|
64
63
|
|
|
65
64
|
|
|
66
|
-
def _glossary_binding(work_dir
|
|
67
|
-
"""The glossary names that could have corrected this transcription
|
|
68
|
-
if legacy:
|
|
69
|
-
return {"names": None, "name_count": None}
|
|
65
|
+
def _glossary_binding(work_dir):
|
|
66
|
+
"""The glossary names that could have corrected this transcription."""
|
|
70
67
|
names = load_glossary_names(work_dir)
|
|
71
68
|
return {"names": names, "name_count": len(names)}
|
|
72
69
|
|
|
@@ -87,12 +84,12 @@ def _atomic_json_write(path, payload):
|
|
|
87
84
|
pass
|
|
88
85
|
|
|
89
86
|
|
|
90
|
-
def _window_evidence(observed_segments, final_segments
|
|
87
|
+
def _window_evidence(observed_segments, final_segments):
|
|
91
88
|
observed_segments = observed_segments or []
|
|
92
89
|
windows = []
|
|
93
90
|
for index, final in enumerate(final_segments or []):
|
|
94
91
|
observed = observed_segments[index] if index < len(observed_segments) else None
|
|
95
|
-
observed_text = None if
|
|
92
|
+
observed_text = None if observed is None else str(observed.get("text") or "")
|
|
96
93
|
final_text = str(final.get("text") or "")
|
|
97
94
|
windows.append({
|
|
98
95
|
"index": index,
|
|
@@ -111,20 +108,19 @@ def write_asr_timing_evidence(
|
|
|
111
108
|
audio_path=None,
|
|
112
109
|
):
|
|
113
110
|
"""Write a sidecar recording which source/audio/result files it describes, without
|
|
114
|
-
changing the
|
|
111
|
+
changing the result itself."""
|
|
115
112
|
if status not in VALID_STATUSES:
|
|
116
113
|
raise ValueError(f"unknown ASR timing evidence status: {status}")
|
|
117
114
|
work_dir = Path(work_dir)
|
|
118
|
-
legacy = status == "LEGACY_UNVERIFIED"
|
|
119
115
|
payload = {
|
|
120
116
|
"schema_version": SCHEMA_VERSION,
|
|
121
117
|
"status": status,
|
|
122
118
|
"source_video": _identity(video_path),
|
|
123
119
|
"audio": _identity(audio_path) if audio_path else None,
|
|
124
120
|
"asr_result": _identity(work_dir / "asr_result.json"),
|
|
125
|
-
"glossary": _glossary_binding(work_dir
|
|
121
|
+
"glossary": _glossary_binding(work_dir),
|
|
126
122
|
"precision": dict(_PRECISION),
|
|
127
|
-
"windows": _window_evidence(observed_segments, final_segments
|
|
123
|
+
"windows": _window_evidence(observed_segments, final_segments),
|
|
128
124
|
}
|
|
129
125
|
path = work_dir / EVIDENCE_FILENAME
|
|
130
126
|
_atomic_json_write(path, payload)
|
|
@@ -146,11 +142,9 @@ def _is_identity(value):
|
|
|
146
142
|
)
|
|
147
143
|
|
|
148
144
|
|
|
149
|
-
def _valid_glossary(payload, work_dir
|
|
145
|
+
def _valid_glossary(payload, work_dir):
|
|
150
146
|
if not isinstance(payload, dict) or set(payload) != _GLOSSARY_KEYS:
|
|
151
147
|
return False
|
|
152
|
-
if legacy:
|
|
153
|
-
return payload == _glossary_binding(work_dir, legacy=True)
|
|
154
148
|
return payload == _glossary_binding(work_dir) and _is_int(payload.get("name_count"))
|
|
155
149
|
|
|
156
150
|
|
|
@@ -162,8 +156,8 @@ def _valid_status_relationships(status, result, audio):
|
|
|
162
156
|
return bool(result) and not any(texts) and _is_identity(audio)
|
|
163
157
|
if status == "UNAVAILABLE_NO_DURATION":
|
|
164
158
|
return result == [] and _is_identity(audio)
|
|
165
|
-
if status in {"EXPLICITLY_SKIPPED", "UNAVAILABLE_NO_KEY"
|
|
166
|
-
return audio is None and
|
|
159
|
+
if status in {"EXPLICITLY_SKIPPED", "UNAVAILABLE_NO_KEY"}:
|
|
160
|
+
return audio is None and result == []
|
|
167
161
|
return False
|
|
168
162
|
|
|
169
163
|
|
|
@@ -199,8 +193,7 @@ def validate_asr_timing_evidence(evidence_path, video_path, asr_result_path):
|
|
|
199
193
|
return False
|
|
200
194
|
if not _valid_status_relationships(status, result, audio):
|
|
201
195
|
return False
|
|
202
|
-
|
|
203
|
-
if not _valid_glossary(evidence.get("glossary"), evidence_path.parent, legacy):
|
|
196
|
+
if not _valid_glossary(evidence.get("glossary"), evidence_path.parent):
|
|
204
197
|
return False
|
|
205
198
|
|
|
206
199
|
if audio is not None:
|
|
@@ -240,10 +233,7 @@ def validate_asr_timing_evidence(evidence_path, video_path, asr_result_path):
|
|
|
240
233
|
or window.get("text_availability") != ("AVAILABLE" if text else "UNAVAILABLE_UNKNOWN")
|
|
241
234
|
):
|
|
242
235
|
return False
|
|
243
|
-
if
|
|
244
|
-
if observed is not None or modified is not None:
|
|
245
|
-
return False
|
|
246
|
-
elif (
|
|
236
|
+
if (
|
|
247
237
|
not isinstance(observed, str) or not isinstance(modified, bool)
|
|
248
238
|
or modified != (observed != text)
|
|
249
239
|
):
|
|
@@ -265,6 +255,7 @@ def asr_evidence_summary_for_brief(work_dir, video_path):
|
|
|
265
255
|
if valid:
|
|
266
256
|
payload = json.loads(evidence_path.read_text(encoding="utf-8"))
|
|
267
257
|
return {"status": payload["status"],
|
|
268
|
-
"glossary_modifications":
|
|
269
|
-
|
|
258
|
+
"glossary_modifications": sum(
|
|
259
|
+
w["glossary_modified"] is True for w in payload["windows"]
|
|
260
|
+
)}
|
|
270
261
|
return {"status": "MISSING_OR_STALE", "glossary_modifications": None}
|
|
@@ -9,10 +9,12 @@ from briefing.context import (
|
|
|
9
9
|
_format_asr_chunks_for_brief,
|
|
10
10
|
_format_background_research,
|
|
11
11
|
_format_consolidation,
|
|
12
|
+
_format_moderation_refusals,
|
|
12
13
|
_format_substrate_warning,
|
|
13
14
|
_format_timeline_fusion_for_brief,
|
|
14
15
|
_load_background_research,
|
|
15
16
|
_load_consolidation,
|
|
17
|
+
_scene_number,
|
|
16
18
|
_write_json_artifact,
|
|
17
19
|
assess_understanding_substrate,
|
|
18
20
|
)
|
|
@@ -28,7 +30,6 @@ from briefing.timeline import (
|
|
|
28
30
|
_load_cut_output_spans_for_brief,
|
|
29
31
|
_parse_target_seconds,
|
|
30
32
|
_remap_brief_evidence_to_output_timeline,
|
|
31
|
-
_write_deslop_qc_requirements,
|
|
32
33
|
)
|
|
33
34
|
from timeline_fusion import (
|
|
34
35
|
_build_timeline_fusion,
|
|
@@ -36,6 +37,24 @@ from timeline_fusion import (
|
|
|
36
37
|
_scene_asr_lines,
|
|
37
38
|
)
|
|
38
39
|
|
|
40
|
+
|
|
41
|
+
def _duration_label(seconds):
|
|
42
|
+
"""'45s' / '2min' / '1m42s' / '2h' / '2h01m' / '2h01m05s', rounded to whole seconds;
|
|
43
|
+
a unit is dropped only when it and everything below it are zero.
|
|
44
|
+
|
|
45
|
+
A minute-rounded label hid the real length: a 90 s target read '~2min'."""
|
|
46
|
+
whole = int(seconds + 0.5)
|
|
47
|
+
if whole < 60:
|
|
48
|
+
return f"{whole}s"
|
|
49
|
+
minutes, secs = divmod(whole, 60)
|
|
50
|
+
if minutes < 60:
|
|
51
|
+
return f"{minutes}min" if secs == 0 else f"{minutes}m{secs:02d}s"
|
|
52
|
+
hours, minutes = divmod(minutes, 60)
|
|
53
|
+
if minutes == 0 and secs == 0:
|
|
54
|
+
return f"{hours}h"
|
|
55
|
+
return f"{hours}h{minutes:02d}m" if secs == 0 else f"{hours}h{minutes:02d}m{secs:02d}s"
|
|
56
|
+
|
|
57
|
+
|
|
39
58
|
def build_agent_brief(
|
|
40
59
|
scenes_analysis,
|
|
41
60
|
asr_result,
|
|
@@ -87,16 +106,8 @@ def build_agent_brief(
|
|
|
87
106
|
beat_count_phrase = (
|
|
88
107
|
f"at most ~{target_count}" if thin_substrate else f"roughly {target_count}"
|
|
89
108
|
)
|
|
90
|
-
output_label = (
|
|
91
|
-
|
|
92
|
-
if output_seconds >= 60
|
|
93
|
-
else f"{output_seconds:.0f}s"
|
|
94
|
-
)
|
|
95
|
-
source_label = (
|
|
96
|
-
f"{video_duration / 60:.0f}min"
|
|
97
|
-
if video_duration >= 60
|
|
98
|
-
else f"{video_duration:.0f}s"
|
|
99
|
-
)
|
|
109
|
+
output_label = _duration_label(output_seconds)
|
|
110
|
+
source_label = _duration_label(video_duration)
|
|
100
111
|
|
|
101
112
|
lines = [
|
|
102
113
|
"# Agent Narration Brief",
|
|
@@ -111,8 +122,7 @@ def build_agent_brief(
|
|
|
111
122
|
f"- Target duration (cut mode): {target_duration}",
|
|
112
123
|
f"- Effective speech budget: {effective_rate:.2f} Chinese chars/sec after {breath_sec:.2f}s pause allowance",
|
|
113
124
|
]
|
|
114
|
-
#
|
|
115
|
-
# has no producer dependency and must not invent an available ASR status.
|
|
125
|
+
# A caller that passes no ASR evidence must not get an invented available status.
|
|
116
126
|
if asr_evidence is None:
|
|
117
127
|
asr_evidence = {"status": "MISSING_OR_STALE", "glossary_modifications": None}
|
|
118
128
|
|
|
@@ -173,7 +183,7 @@ def build_agent_brief(
|
|
|
173
183
|
"- `visual_audio_board.json` owns the exact picture/performance/reaction, entry/exit reason, original-audio anchor, `audio_owner`, and `narration_job` for each beat.",
|
|
174
184
|
"- Only after the story/edit decisions are coherent, author `style_card.json` from `--style`, evidence, and user preference. It owns voice and pacing, not story structure, and is not a preset enum, fixed taxonomy, title plan, or packaging promise.",
|
|
175
185
|
"- `packaging_plan.json` is optional and deferred until content lock unless the user explicitly asks for packaging. It must express the story's truthful promise, never drive or distort it.",
|
|
176
|
-
"- `
|
|
186
|
+
"- `narration_lint.json.deslop_qc` is deterministic report-only tool QC written by validation: do not treat it as an AIGC detector, and do not auto-rewrite from it. Corrections remain human/agent rewrite work guided by objective blockers and advisory readability signals.",
|
|
177
187
|
"",
|
|
178
188
|
]
|
|
179
189
|
)
|
|
@@ -194,6 +204,7 @@ def build_agent_brief(
|
|
|
194
204
|
)
|
|
195
205
|
)
|
|
196
206
|
lines.extend(_format_substrate_warning(substrate))
|
|
207
|
+
lines.extend(_format_moderation_refusals(scenes_analysis))
|
|
197
208
|
lines.extend(_format_research_directive(work_dir, substrate))
|
|
198
209
|
lines.extend(_format_background_research(_load_background_research(work_dir)))
|
|
199
210
|
lines.extend(_format_consolidation(consolidation_index))
|
|
@@ -205,7 +216,8 @@ def build_agent_brief(
|
|
|
205
216
|
asr_result,
|
|
206
217
|
silence_periods,
|
|
207
218
|
)
|
|
208
|
-
|
|
219
|
+
cut_pass2 = edit_mode == "cut" and (Path(work_dir) / "edited_source.mp4").exists()
|
|
220
|
+
if cut_pass2:
|
|
209
221
|
chunk_scenes, chunk_asr, _ = _remap_brief_evidence_to_output_timeline(
|
|
210
222
|
work_dir, scenes_analysis, asr_for_chunks, [], required=True
|
|
211
223
|
)
|
|
@@ -237,7 +249,7 @@ def build_agent_brief(
|
|
|
237
249
|
cut_target_example = (
|
|
238
250
|
target_duration if target_duration != "(not set)" else "30m"
|
|
239
251
|
)
|
|
240
|
-
if not
|
|
252
|
+
if not cut_pass2:
|
|
241
253
|
# PASS 1 of 2 (cut-first): pick the footage. Narration comes AFTER the cut is
|
|
242
254
|
# rendered, so it can be written against the real OUTPUT timeline — no source->output
|
|
243
255
|
# mapping, no silent drop/clamp, no desync.
|
|
@@ -259,7 +271,7 @@ def build_agent_brief(
|
|
|
259
271
|
"- Clip length follows the moment. Vary pace; after any cold-open, order clips by causality so the cut reads as one coherent story, not a flat highlights reel.",
|
|
260
272
|
"- Inspect dense scene-change candidates before locking boundaries. For source-authored cuts, delete irrelevant short shots and extend relevant shots to a complete action/reaction; for edit-created joins, move boundaries, restore same-source motion, or merge clips so the artificial cut disappears where possible. Do not hide a bad join with a transition.",
|
|
261
273
|
"- Preserve complete spoken lines, but do not infer completeness from ASR window ends or quiet-window suggestions. Directly listen and inspect picture around each proposed boundary, then place the cut after the verified utterance/reaction; automatic snapping is only an advisory candidate.",
|
|
262
|
-
"- Advisory does not mean absent: the cut CLI still snaps clip ends to sentence boundaries by default and blocks a cut
|
|
274
|
+
"- Advisory does not mean absent: the cut CLI still snaps clip ends to sentence boundaries by default and blocks a cut inside ASR-detected speech unless it lands in a quiet window or a sentence-end pause estimate, using `silence_periods.json` and `speech_boundary_anchors.json` as the executable safety net under your listening.",
|
|
263
275
|
"",
|
|
264
276
|
"### clip_plan.json shape (original source timestamps)",
|
|
265
277
|
"",
|
|
@@ -366,22 +378,45 @@ def build_agent_brief(
|
|
|
366
378
|
'- ✗ "一个蒙眼的男人抱着一个篮子走在雨里。" (just describes the frame)',
|
|
367
379
|
'- ✓ "护送者本可以独自离开,却为了保护那个孩子,主动把追兵引向自己。" (who, why, stakes)',
|
|
368
380
|
"",
|
|
369
|
-
"## Scene timing guide",
|
|
370
|
-
"",
|
|
371
381
|
]
|
|
372
382
|
)
|
|
383
|
+
# Cut pass 2 narrates edited_source.mp4, so its guide lists only the kept footage on the
|
|
384
|
+
# OUTPUT clock; the source-time guide (cut-away scenes, credits) belongs to pass 1.
|
|
385
|
+
if cut_pass2:
|
|
386
|
+
guide_scenes, guide_asr, guide_silence = fusion_scenes, fusion_asr, fusion_silence
|
|
387
|
+
lines.extend(
|
|
388
|
+
[
|
|
389
|
+
"## Scene timing guide (OUTPUT time)",
|
|
390
|
+
"",
|
|
391
|
+
"Only footage kept in `edited_source.mp4`, timed on its OUTPUT timeline (0 .. total); "
|
|
392
|
+
"scenes the cut left out are not listed.",
|
|
393
|
+
"",
|
|
394
|
+
]
|
|
395
|
+
)
|
|
396
|
+
else:
|
|
397
|
+
guide_scenes, guide_asr, guide_silence = scenes_analysis, asr_result, silence_periods
|
|
398
|
+
lines.extend(["## Scene timing guide", ""])
|
|
373
399
|
|
|
374
|
-
for scene in
|
|
400
|
+
for scene in guide_scenes:
|
|
375
401
|
duration = scene["end"] - scene["start"]
|
|
376
402
|
max_chars = max(5, int(max(1.0, duration - breath_sec) * effective_rate))
|
|
377
|
-
quiets = _quiet_windows_for_scene(
|
|
403
|
+
quiets = _quiet_windows_for_scene(guide_silence, scene)
|
|
378
404
|
quiet_text = ", ".join(f"{s:.1f}-{e:.1f}s" for s, e in quiets) or "none"
|
|
405
|
+
span = f"{scene['start']:.1f}-{scene['end']:.1f}s"
|
|
379
406
|
lines.extend(
|
|
380
407
|
[
|
|
381
|
-
f"###
|
|
408
|
+
f"### OUTPUT {span} (source scene {_scene_number(scene['scene_id'])})"
|
|
409
|
+
if cut_pass2
|
|
410
|
+
else f"### Scene {_scene_number(scene['scene_id'])}: {span}",
|
|
382
411
|
f"- Duration: {duration:.1f}s; max budget if fully narrated: {max_chars} chars",
|
|
383
412
|
f"- Quiet windows: {quiet_text}",
|
|
384
|
-
f"- Description: {scene.get('description', '')}"
|
|
413
|
+
f"- Description: {scene.get('description', '')}"
|
|
414
|
+
+ (
|
|
415
|
+
" [moderation_refused]"
|
|
416
|
+
if scene.get("analysis_status") == "moderation_refused"
|
|
417
|
+
and scene.get("description_source") != "mimo_video_overview"
|
|
418
|
+
else ""
|
|
419
|
+
),
|
|
385
420
|
]
|
|
386
421
|
)
|
|
387
422
|
if scene.get("depth_analysis"):
|
|
@@ -389,7 +424,7 @@ def build_agent_brief(
|
|
|
389
424
|
facts = _format_frame_facts(scene)
|
|
390
425
|
if facts:
|
|
391
426
|
lines.append(facts.rstrip())
|
|
392
|
-
asr_lines = _scene_asr_lines(
|
|
427
|
+
asr_lines = _scene_asr_lines(guide_asr, scene)
|
|
393
428
|
if asr_lines:
|
|
394
429
|
lines.append("- ASR overlap:")
|
|
395
430
|
lines.extend(asr_lines[:8])
|
|
@@ -397,6 +432,5 @@ def build_agent_brief(
|
|
|
397
432
|
|
|
398
433
|
brief_path = Path(work_dir) / "agent_narration_brief.md"
|
|
399
434
|
brief_path.write_text("\n".join(lines), encoding="utf-8")
|
|
400
|
-
_write_deslop_qc_requirements(work_dir)
|
|
401
435
|
log(f"已写入 Agent 解说写作 brief: {brief_path}")
|
|
402
436
|
return brief_path
|
|
@@ -4,9 +4,17 @@ import json
|
|
|
4
4
|
import re
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
|
|
7
|
+
from index_normalize import normalize_index
|
|
7
8
|
from lib import CONFIG, file_identity
|
|
8
9
|
|
|
9
10
|
|
|
11
|
+
def _scene_number(scene_id):
|
|
12
|
+
"""1-based scene number for every human-facing label in the brief (JSON keeps the
|
|
13
|
+
0-based `scene_id`); a scene split across kept clips ("3.1") reads "4 part 2"."""
|
|
14
|
+
base, _, part = str(scene_id).partition(".")
|
|
15
|
+
return f"{int(base) + 1} part {int(part) + 1}" if part else str(int(base) + 1)
|
|
16
|
+
|
|
17
|
+
|
|
10
18
|
def _load_background_research(work_dir):
|
|
11
19
|
"""Load the agent-authored background_research.json ({} when absent)."""
|
|
12
20
|
path = Path(work_dir) / "background_research.json"
|
|
@@ -158,6 +166,31 @@ def _format_substrate_warning(assessment):
|
|
|
158
166
|
]
|
|
159
167
|
|
|
160
168
|
|
|
169
|
+
def _format_moderation_refusals(scenes_analysis):
|
|
170
|
+
"""One brief line naming the scenes MiMo moderation refused, so blank descriptions read as
|
|
171
|
+
refusals (vlm.analyze_scenes sets analysis_status), not as empty footage."""
|
|
172
|
+
refused = [
|
|
173
|
+
scene for scene in scenes_analysis or []
|
|
174
|
+
if isinstance(scene, dict) and scene.get("analysis_status") == "moderation_refused"
|
|
175
|
+
]
|
|
176
|
+
if not refused:
|
|
177
|
+
return []
|
|
178
|
+
labels = ", ".join(_scene_number(scene["scene_id"]) for scene in refused[:12])
|
|
179
|
+
more = f" …(+{len(refused) - 12})" if len(refused) > 12 else ""
|
|
180
|
+
covered = sum(
|
|
181
|
+
1 for scene in refused if scene.get("description_source") == "mimo_video_overview"
|
|
182
|
+
)
|
|
183
|
+
overview_note = f"; {covered} of them are described by the MiMo overview instead" if covered else ""
|
|
184
|
+
return [
|
|
185
|
+
f"- Moderation-refused scenes: {len(refused)}/{len(scenes_analysis)} "
|
|
186
|
+
f"(Scene {labels}{more}) — MiMo refused the frame VLM request, so their frame "
|
|
187
|
+
f"description is blank and they carry no frame_facts{overview_note}. This is a "
|
|
188
|
+
"content-moderation refusal, not empty footage: ground them in ASR, burned-in "
|
|
189
|
+
"subtitles, or background research, or skip them in cut mode.",
|
|
190
|
+
"",
|
|
191
|
+
]
|
|
192
|
+
|
|
193
|
+
|
|
161
194
|
def _write_json_artifact(work_dir, name, payload):
|
|
162
195
|
path = Path(work_dir) / name
|
|
163
196
|
path.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
|
|
@@ -175,7 +208,7 @@ def _format_asr_chunks_for_brief(chunks, max_chunks=24):
|
|
|
175
208
|
"",
|
|
176
209
|
]
|
|
177
210
|
for chunk in chunks[:max_chunks]:
|
|
178
|
-
scene_ids = ",".join(
|
|
211
|
+
scene_ids = ", ".join(_scene_number(sid) for sid in chunk["scene_ids"]) or "n/a"
|
|
179
212
|
text = chunk["text"]
|
|
180
213
|
if len(text) > 900:
|
|
181
214
|
text = text[:897] + "..."
|
|
@@ -223,7 +256,7 @@ def _format_timeline_fusion_for_brief(fusion, max_items=40):
|
|
|
223
256
|
)
|
|
224
257
|
lines.extend(
|
|
225
258
|
[
|
|
226
|
-
f"### Fusion scene {item['scene_id']}: {start:.1f}-{end:.1f}s ({item['recommended_mode']})",
|
|
259
|
+
f"### Fusion scene {_scene_number(item['scene_id'])}: {start:.1f}-{end:.1f}s ({item['recommended_mode']})",
|
|
227
260
|
f"- Visual: {item['visual_description']}",
|
|
228
261
|
f"- Dialogue overlap: {item['dialogue_overlap_seconds']:.1f}s | {dialogue_text}",
|
|
229
262
|
f"- Narration slots: {slot_text}",
|
|
@@ -278,7 +311,10 @@ def _load_consolidation(work_dir, scenes_analysis):
|
|
|
278
311
|
return {}
|
|
279
312
|
if not all(isinstance(item, dict) for item in index["relationships"]):
|
|
280
313
|
return {}
|
|
281
|
-
|
|
314
|
+
# An index written before the deterministic repairs existed is repaired here too, so a
|
|
315
|
+
# --brief-only run does not wait for the next full understand.py run (pure, no model call).
|
|
316
|
+
# No duration cap: the brief's scenes may be an edited timeline, plot times are source times.
|
|
317
|
+
return normalize_index(index)[0]
|
|
282
318
|
|
|
283
319
|
|
|
284
320
|
def _format_consolidation(index):
|
|
@@ -3,24 +3,18 @@
|
|
|
3
3
|
import json
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
|
|
6
|
-
from lib import
|
|
6
|
+
from lib import (
|
|
7
|
+
CONFIG,
|
|
8
|
+
file_identity,
|
|
9
|
+
is_usable_overview_chunk,
|
|
10
|
+
offline_ignored_settings,
|
|
11
|
+
settings_match,
|
|
12
|
+
)
|
|
7
13
|
from briefing.context import _consolidation_model
|
|
8
14
|
|
|
9
|
-
#
|
|
10
|
-
# skill ships no consolidate.py). Keep both literals in sync.
|
|
15
|
+
# Same literal as consolidate._ASR_SPAN_TOL; test_asr_span_tol_matches_across_files pins them.
|
|
11
16
|
_ASR_SPAN_TOL = 0.05
|
|
12
17
|
|
|
13
|
-
_MIMO_REJECTION_MARKERS = (
|
|
14
|
-
"request was rejected",
|
|
15
|
-
"considered high risk",
|
|
16
|
-
"high risk",
|
|
17
|
-
"content policy",
|
|
18
|
-
"cannot process",
|
|
19
|
-
"无法处理",
|
|
20
|
-
"内容审核",
|
|
21
|
-
"违规",
|
|
22
|
-
)
|
|
23
|
-
|
|
24
18
|
|
|
25
19
|
def _load_clean_asr(work_dir, asr_result):
|
|
26
20
|
"""Return consolidate.py's cleaned ASR segments, or None to fall back to raw asr_result.
|
|
@@ -70,15 +64,6 @@ def _load_clean_asr(work_dir, asr_result):
|
|
|
70
64
|
return segments
|
|
71
65
|
|
|
72
66
|
|
|
73
|
-
def _is_mimo_chunk_usable(content):
|
|
74
|
-
"""A chunk is usable only if MiMo returned real analysis (not empty / a moderation refusal)."""
|
|
75
|
-
text = (content or "").strip()
|
|
76
|
-
if not text:
|
|
77
|
-
return False
|
|
78
|
-
low = text.lower()
|
|
79
|
-
return not any(marker in low for marker in _MIMO_REJECTION_MARKERS)
|
|
80
|
-
|
|
81
|
-
|
|
82
67
|
def _mimo_video_settings():
|
|
83
68
|
"""Non-secret MiMo video-overview settings that affect generated content."""
|
|
84
69
|
return {
|
|
@@ -139,7 +124,7 @@ def _mimo_overview_matches_current_inputs(overview, scenes, video_path=None):
|
|
|
139
124
|
if not isinstance(chunks, list) or not all(
|
|
140
125
|
isinstance(chunk, dict)
|
|
141
126
|
and isinstance(chunk.get("content"), str)
|
|
142
|
-
and
|
|
127
|
+
and is_usable_overview_chunk(chunk["content"])
|
|
143
128
|
for chunk in chunks
|
|
144
129
|
):
|
|
145
130
|
return False
|
|
@@ -153,7 +138,8 @@ def _mimo_overview_matches_current_inputs(overview, scenes, video_path=None):
|
|
|
153
138
|
cached_keys = [_mimo_chunk_cache_key(chunk) for chunk in chunks]
|
|
154
139
|
except (KeyError, TypeError, ValueError):
|
|
155
140
|
return False
|
|
156
|
-
|
|
141
|
+
ignored = offline_ignored_settings(CONFIG["mimo_video_api_key"], "mimo_video_api_url")
|
|
142
|
+
return settings_match(overview.get("settings"), settings, ignored) and cached_keys == expected_keys
|
|
157
143
|
|
|
158
144
|
|
|
159
145
|
def _load_mimo_overview_for_brief(work_dir, scenes, enabled=None, video_path=None):
|
|
@@ -242,10 +228,12 @@ def _format_optional_stage_warnings(
|
|
|
242
228
|
work_dir, "consolidation.status.json"
|
|
243
229
|
)
|
|
244
230
|
if consolidation_status is not None and consolidation_status["enabled"]:
|
|
245
|
-
if consolidation_status["status"]
|
|
231
|
+
if consolidation_status["status"] in {"failed", "skipped_no_key"}:
|
|
246
232
|
warnings.append(
|
|
247
233
|
_optional_stage_warning(
|
|
248
|
-
"consolidation",
|
|
234
|
+
"consolidation",
|
|
235
|
+
consolidation_status["status"],
|
|
236
|
+
consolidation_status["message"],
|
|
249
237
|
)
|
|
250
238
|
)
|
|
251
239
|
elif consolidation_status.get("do_index") is True and not consolidation_index:
|