@oh-story/dsh 0.1.11 → 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
|
@@ -1,318 +0,0 @@
|
|
|
1
|
-
"""Sample frames and build aggregate MiMo QC reports."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import base64
|
|
6
|
-
import json
|
|
7
|
-
import math
|
|
8
|
-
import os
|
|
9
|
-
import subprocess
|
|
10
|
-
import tempfile
|
|
11
|
-
from pathlib import Path
|
|
12
|
-
from typing import Any
|
|
13
|
-
from collections.abc import Callable, Mapping, Sequence
|
|
14
|
-
|
|
15
|
-
import qc_contract
|
|
16
|
-
from qc.mimo_client import mimo_qc_api_call
|
|
17
|
-
from qc.mimo_evidence import (
|
|
18
|
-
_cache_evidence,
|
|
19
|
-
_effective_config,
|
|
20
|
-
_existing_final_output,
|
|
21
|
-
collect_evidence,
|
|
22
|
-
safe_mimo_config,
|
|
23
|
-
)
|
|
24
|
-
from qc.mimo_observations import normalize_observations
|
|
25
|
-
from qc.mimo_payload import _request_payload, _validated_live_output, build_payload
|
|
26
|
-
from qc.mimo_contract import (
|
|
27
|
-
ARTIFACT_NAME,
|
|
28
|
-
DEFAULT_STAGE,
|
|
29
|
-
FRAME_SAMPLER_VERSION,
|
|
30
|
-
MAX_FRAME_DIMENSION,
|
|
31
|
-
MAX_FRAMES,
|
|
32
|
-
)
|
|
33
|
-
|
|
34
|
-
JudgeCallable = Callable[
|
|
35
|
-
[Mapping[str, Any]], Mapping[str, Any] | Sequence[Mapping[str, Any]]
|
|
36
|
-
]
|
|
37
|
-
|
|
38
|
-
FrameSampler = Callable[..., Sequence[Mapping[str, Any]]]
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
def _probe_duration(path: Path) -> float:
|
|
42
|
-
"""Media duration via ffprobe; 0.0 when ffprobe cannot read it (frame sampling is advisory)."""
|
|
43
|
-
result = subprocess.run(
|
|
44
|
-
[
|
|
45
|
-
"ffprobe",
|
|
46
|
-
"-v",
|
|
47
|
-
"error",
|
|
48
|
-
"-show_entries",
|
|
49
|
-
"format=duration",
|
|
50
|
-
"-of",
|
|
51
|
-
"json",
|
|
52
|
-
str(path),
|
|
53
|
-
],
|
|
54
|
-
capture_output=True,
|
|
55
|
-
text=True,
|
|
56
|
-
timeout=20,
|
|
57
|
-
check=False,
|
|
58
|
-
)
|
|
59
|
-
if result.returncode != 0:
|
|
60
|
-
return 0.0
|
|
61
|
-
try:
|
|
62
|
-
return max(0.0, float(json.loads(result.stdout)["format"]["duration"]))
|
|
63
|
-
except (KeyError, ValueError):
|
|
64
|
-
return 0.0
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
def sample_video_frames(
|
|
68
|
-
video_path: str | Path,
|
|
69
|
-
*,
|
|
70
|
-
max_frames: int = MAX_FRAMES,
|
|
71
|
-
max_dimension: int = MAX_FRAME_DIMENSION,
|
|
72
|
-
) -> list[dict[str, Any]]:
|
|
73
|
-
"""Extract at most six bounded JPEGs; returned base64 is request-only."""
|
|
74
|
-
path = Path(video_path)
|
|
75
|
-
if not path.is_file() or max_frames <= 0:
|
|
76
|
-
return []
|
|
77
|
-
try:
|
|
78
|
-
duration = _probe_duration(path)
|
|
79
|
-
except (OSError, subprocess.SubprocessError):
|
|
80
|
-
return []
|
|
81
|
-
if duration <= 0:
|
|
82
|
-
return []
|
|
83
|
-
count = min(max_frames, max(1, math.ceil(duration / 15.0)))
|
|
84
|
-
samples = []
|
|
85
|
-
with tempfile.TemporaryDirectory(prefix="video-recap-mimo-qc-") as tmp:
|
|
86
|
-
for index in range(count):
|
|
87
|
-
timestamp = duration * (index + 0.5) / count
|
|
88
|
-
destination = Path(tmp) / f"frame-{index:02d}.jpg"
|
|
89
|
-
try:
|
|
90
|
-
result = subprocess.run(
|
|
91
|
-
[
|
|
92
|
-
"ffmpeg",
|
|
93
|
-
"-hide_banner",
|
|
94
|
-
"-loglevel",
|
|
95
|
-
"error",
|
|
96
|
-
"-y",
|
|
97
|
-
"-ss",
|
|
98
|
-
f"{timestamp:.3f}",
|
|
99
|
-
"-i",
|
|
100
|
-
str(path),
|
|
101
|
-
"-frames:v",
|
|
102
|
-
"1",
|
|
103
|
-
"-vf",
|
|
104
|
-
f"scale={max_dimension}:{max_dimension}:force_original_aspect_ratio=decrease",
|
|
105
|
-
"-q:v",
|
|
106
|
-
"4",
|
|
107
|
-
str(destination),
|
|
108
|
-
],
|
|
109
|
-
capture_output=True,
|
|
110
|
-
timeout=30,
|
|
111
|
-
check=False,
|
|
112
|
-
)
|
|
113
|
-
except (OSError, subprocess.SubprocessError):
|
|
114
|
-
continue
|
|
115
|
-
if result.returncode != 0 or not destination.is_file():
|
|
116
|
-
continue
|
|
117
|
-
raw = destination.read_bytes()
|
|
118
|
-
samples.append(
|
|
119
|
-
{
|
|
120
|
-
"data_url": "data:image/jpeg;base64,"
|
|
121
|
-
+ base64.b64encode(raw).decode("ascii"),
|
|
122
|
-
"bytes": len(raw),
|
|
123
|
-
"timestamp": round(timestamp, 3),
|
|
124
|
-
}
|
|
125
|
-
)
|
|
126
|
-
return samples
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
def _frame_metadata(samples: Sequence[Mapping[str, Any]]) -> dict[str, Any]:
|
|
130
|
-
return {
|
|
131
|
-
"count": len(samples),
|
|
132
|
-
"max_frames": MAX_FRAMES,
|
|
133
|
-
"max_dimension": MAX_FRAME_DIMENSION,
|
|
134
|
-
"sampler_version": FRAME_SAMPLER_VERSION,
|
|
135
|
-
"samples": [
|
|
136
|
-
{"bytes": sample["bytes"], "timestamp": sample["timestamp"]}
|
|
137
|
-
for sample in samples
|
|
138
|
-
],
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
def _cache_input(
|
|
143
|
-
stage: str,
|
|
144
|
-
payload: Mapping[str, Any],
|
|
145
|
-
evidence: Mapping[str, Any],
|
|
146
|
-
frames: Mapping[str, Any],
|
|
147
|
-
) -> dict[str, Any]:
|
|
148
|
-
"""Everything a stage report depends on; an existing report whose stored cache_input
|
|
149
|
-
equals this dict is reused instead of a new request."""
|
|
150
|
-
return {
|
|
151
|
-
"stage": stage,
|
|
152
|
-
"model": payload["model"],
|
|
153
|
-
"config": payload["config"],
|
|
154
|
-
"instructions": payload["instructions"],
|
|
155
|
-
"evidence": _cache_evidence(evidence),
|
|
156
|
-
"frames": frames,
|
|
157
|
-
"contract": qc_contract.SCHEMA_VERSION,
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
def _stage_reports(path: Path) -> dict[str, dict[str, Any]]:
|
|
162
|
-
"""Per-stage reports from an existing aggregate mimo_qc.json, or {} before the first stage.
|
|
163
|
-
|
|
164
|
-
The aggregate is this module's own atomically written, contract-validated report, so a
|
|
165
|
-
corrupt one raises (recap_stage_qc keeps the pipeline fail-open around the whole stage)."""
|
|
166
|
-
if not path.is_file():
|
|
167
|
-
return {}
|
|
168
|
-
return dict(json.loads(path.read_text(encoding="utf-8"))["metadata"]["stages"])
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
def _error_name(exc: Exception) -> str:
|
|
172
|
-
return (str(exc).strip() or type(exc).__name__)[:120]
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
def build_report(
|
|
176
|
-
work_dir: str | Path,
|
|
177
|
-
*,
|
|
178
|
-
stage: str = DEFAULT_STAGE,
|
|
179
|
-
fixture: Any | None = None,
|
|
180
|
-
dry_run: bool = False,
|
|
181
|
-
judge: JudgeCallable | None = None,
|
|
182
|
-
config: Mapping[str, Any] | None = None,
|
|
183
|
-
final_output: str | Path | None = None,
|
|
184
|
-
live: bool = False,
|
|
185
|
-
refresh: bool = False,
|
|
186
|
-
frame_sampler: FrameSampler | None = None,
|
|
187
|
-
existing: Mapping[str, Any] | None = None,
|
|
188
|
-
api_call: Callable[..., Any] | None = None,
|
|
189
|
-
) -> dict[str, Any]:
|
|
190
|
-
"""Build one validated stage report; all live failures remain successful QC."""
|
|
191
|
-
root = Path(work_dir)
|
|
192
|
-
evidence = collect_evidence(root, final_output=final_output)
|
|
193
|
-
payload = build_payload(evidence, stage=stage, config=config)
|
|
194
|
-
cfg = safe_mimo_config(config)
|
|
195
|
-
samples: Sequence[Mapping[str, Any]] = []
|
|
196
|
-
if stage == "post_render" and (fixture is not None or judge is not None or live):
|
|
197
|
-
output = _existing_final_output(root, final_output)
|
|
198
|
-
if output is not None:
|
|
199
|
-
sampler = frame_sampler or sample_video_frames
|
|
200
|
-
samples = list(
|
|
201
|
-
sampler(output, max_frames=MAX_FRAMES, max_dimension=MAX_FRAME_DIMENSION)
|
|
202
|
-
)[:MAX_FRAMES]
|
|
203
|
-
frame_meta = _frame_metadata(samples)
|
|
204
|
-
cache_input = _cache_input(stage, payload, evidence, frame_meta)
|
|
205
|
-
|
|
206
|
-
if (
|
|
207
|
-
not refresh
|
|
208
|
-
and existing is not None
|
|
209
|
-
and existing["metadata"]["cache_input"] == cache_input
|
|
210
|
-
):
|
|
211
|
-
cached = json.loads(json.dumps(existing))
|
|
212
|
-
cached["metadata"].update(status="cached", mode="live_cache", request_count=0)
|
|
213
|
-
return cached
|
|
214
|
-
|
|
215
|
-
status = "completed"
|
|
216
|
-
error = None
|
|
217
|
-
if fixture is not None:
|
|
218
|
-
model_output = fixture
|
|
219
|
-
mode = "fixture"
|
|
220
|
-
elif judge is not None and not dry_run:
|
|
221
|
-
mode = "injected_judge"
|
|
222
|
-
try:
|
|
223
|
-
model_output = judge(payload)
|
|
224
|
-
except Exception as exc:
|
|
225
|
-
model_output = {"observations": []}
|
|
226
|
-
status, error = "failed", _error_name(exc)
|
|
227
|
-
elif live and not dry_run:
|
|
228
|
-
mode = "live"
|
|
229
|
-
if not cfg["key_present"]:
|
|
230
|
-
model_output = {"observations": []}
|
|
231
|
-
status, error = "unavailable", "missing_key"
|
|
232
|
-
else:
|
|
233
|
-
# Fail-open boundary for the third-party request: transport errors arrive as
|
|
234
|
-
# MiMoQCRequestError (a RuntimeError), malformed responses as ValueError.
|
|
235
|
-
try:
|
|
236
|
-
response = (api_call or mimo_qc_api_call)(
|
|
237
|
-
_request_payload(payload, samples),
|
|
238
|
-
config=_effective_config(config),
|
|
239
|
-
timeout=60,
|
|
240
|
-
)
|
|
241
|
-
model_output = _validated_live_output(response)
|
|
242
|
-
except (RuntimeError, ValueError) as exc:
|
|
243
|
-
model_output = {"observations": []}
|
|
244
|
-
status, error = "failed", _error_name(exc)
|
|
245
|
-
else:
|
|
246
|
-
model_output = {"observations": []}
|
|
247
|
-
mode = "dry_run"
|
|
248
|
-
status = "dry_run"
|
|
249
|
-
|
|
250
|
-
findings = normalize_observations(
|
|
251
|
-
model_output, stage=stage, payload=payload, model_config=config
|
|
252
|
-
)
|
|
253
|
-
metadata = {
|
|
254
|
-
"mode": mode,
|
|
255
|
-
"status": status,
|
|
256
|
-
"error": error,
|
|
257
|
-
"report_only": True,
|
|
258
|
-
"auto_repair": False,
|
|
259
|
-
"pipeline_blocking": False,
|
|
260
|
-
"deterministic": False,
|
|
261
|
-
"request_count": 1
|
|
262
|
-
if mode == "live" and status in {"completed", "failed"}
|
|
263
|
-
else 0,
|
|
264
|
-
"cache_input": cache_input,
|
|
265
|
-
"frame_samples": frame_meta,
|
|
266
|
-
"evidence": evidence,
|
|
267
|
-
"payload": payload,
|
|
268
|
-
"config": cfg,
|
|
269
|
-
}
|
|
270
|
-
return qc_contract.build_report(
|
|
271
|
-
artifact=ARTIFACT_NAME,
|
|
272
|
-
stage=stage,
|
|
273
|
-
findings=findings,
|
|
274
|
-
metadata=metadata,
|
|
275
|
-
)
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
def _aggregate_reports(
|
|
279
|
-
stage_reports: Mapping[str, Mapping[str, Any]], current_stage: str
|
|
280
|
-
) -> dict[str, Any]:
|
|
281
|
-
findings = [
|
|
282
|
-
finding
|
|
283
|
-
for stage_report in stage_reports.values()
|
|
284
|
-
for finding in stage_report["findings"]
|
|
285
|
-
]
|
|
286
|
-
metadata = dict(stage_reports[current_stage]["metadata"])
|
|
287
|
-
metadata["stages"] = {
|
|
288
|
-
stage: dict(report) for stage, report in stage_reports.items()
|
|
289
|
-
}
|
|
290
|
-
return qc_contract.build_report(
|
|
291
|
-
artifact=ARTIFACT_NAME,
|
|
292
|
-
stage=current_stage,
|
|
293
|
-
findings=findings,
|
|
294
|
-
metadata=metadata,
|
|
295
|
-
)
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
def write_report(
|
|
299
|
-
work_dir: str | Path, report: Mapping[str, Any], *, output: str | Path | None = None
|
|
300
|
-
) -> tuple[Path, dict[str, Any]]:
|
|
301
|
-
"""Atomically merge one stage into mimo_qc.json and return the aggregate."""
|
|
302
|
-
path = Path(output) if output else Path(work_dir) / ARTIFACT_NAME
|
|
303
|
-
path.parent.mkdir(parents=True, exist_ok=True)
|
|
304
|
-
stages = _stage_reports(path)
|
|
305
|
-
stages[report["stage"]] = dict(report)
|
|
306
|
-
aggregate = _aggregate_reports(stages, report["stage"])
|
|
307
|
-
serialized = json.dumps(aggregate, ensure_ascii=False, indent=2) + "\n"
|
|
308
|
-
descriptor, temp_name = tempfile.mkstemp(prefix=f".{path.name}.", dir=path.parent)
|
|
309
|
-
try:
|
|
310
|
-
with os.fdopen(descriptor, "w", encoding="utf-8") as handle:
|
|
311
|
-
handle.write(serialized)
|
|
312
|
-
handle.flush()
|
|
313
|
-
os.fsync(handle.fileno())
|
|
314
|
-
os.replace(temp_name, path)
|
|
315
|
-
except BaseException:
|
|
316
|
-
Path(temp_name).unlink(missing_ok=True)
|
|
317
|
-
raise
|
|
318
|
-
return path, aggregate
|
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
"""Run and persist advisory MiMo QC stages."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import argparse
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
import json
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
from pathlib import Path
|
|
12
|
-
|
|
13
|
-
from typing import Any
|
|
14
|
-
from collections.abc import Callable, Mapping, Sequence
|
|
15
|
-
|
|
16
|
-
import qc_contract
|
|
17
|
-
|
|
18
|
-
from qc.mimo_report import _stage_reports, build_report, write_report
|
|
19
|
-
from qc.mimo_contract import ARTIFACT_NAME, DEFAULT_STAGE
|
|
20
|
-
|
|
21
|
-
JudgeCallable = Callable[
|
|
22
|
-
[Mapping[str, Any]], Mapping[str, Any] | Sequence[Mapping[str, Any]]
|
|
23
|
-
]
|
|
24
|
-
|
|
25
|
-
FrameSampler = Callable[..., Sequence[Mapping[str, Any]]]
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
def run(
|
|
29
|
-
work_dir: str | Path,
|
|
30
|
-
*,
|
|
31
|
-
stage: str = DEFAULT_STAGE,
|
|
32
|
-
fixture: Any | None = None,
|
|
33
|
-
dry_run: bool = False,
|
|
34
|
-
judge: JudgeCallable | None = None,
|
|
35
|
-
config: Mapping[str, Any] | None = None,
|
|
36
|
-
final_output: str | Path | None = None,
|
|
37
|
-
output: str | Path | None = None,
|
|
38
|
-
live: bool = False,
|
|
39
|
-
refresh: bool = False,
|
|
40
|
-
frame_sampler: FrameSampler | None = None,
|
|
41
|
-
api_call: Callable[..., Any] | None = None,
|
|
42
|
-
) -> dict[str, Any]:
|
|
43
|
-
path = Path(output) if output else Path(work_dir) / ARTIFACT_NAME
|
|
44
|
-
existing = _stage_reports(path).get(stage)
|
|
45
|
-
report = build_report(
|
|
46
|
-
work_dir,
|
|
47
|
-
stage=stage,
|
|
48
|
-
fixture=fixture,
|
|
49
|
-
dry_run=dry_run,
|
|
50
|
-
judge=judge,
|
|
51
|
-
config=config,
|
|
52
|
-
final_output=final_output,
|
|
53
|
-
live=live,
|
|
54
|
-
refresh=refresh,
|
|
55
|
-
frame_sampler=frame_sampler,
|
|
56
|
-
existing=existing,
|
|
57
|
-
api_call=api_call,
|
|
58
|
-
)
|
|
59
|
-
written_path, aggregate = write_report(work_dir, report, output=output)
|
|
60
|
-
return {"path": str(written_path), "report": aggregate}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
def clear_report(work_dir: str | Path, *, output: str | Path | None = None) -> bool:
|
|
64
|
-
path = Path(output) if output else Path(work_dir) / ARTIFACT_NAME
|
|
65
|
-
try:
|
|
66
|
-
path.unlink()
|
|
67
|
-
return True
|
|
68
|
-
except FileNotFoundError:
|
|
69
|
-
return False
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
def _load_fixture(path: str | Path | None) -> Any | None:
|
|
73
|
-
if not path:
|
|
74
|
-
return None
|
|
75
|
-
with Path(path).open("r", encoding="utf-8") as handle:
|
|
76
|
-
return json.load(handle)
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
def main(
|
|
80
|
-
argv: Sequence[str] | None = None,
|
|
81
|
-
*,
|
|
82
|
-
run_callable: Callable[..., dict[str, Any]] | None = None,
|
|
83
|
-
) -> int:
|
|
84
|
-
parser = argparse.ArgumentParser(
|
|
85
|
-
description="Write advisory MiMo QC from local recap evidence."
|
|
86
|
-
)
|
|
87
|
-
parser.add_argument("--work-dir", required=True)
|
|
88
|
-
parser.add_argument(
|
|
89
|
-
"--stage", default=DEFAULT_STAGE, choices=sorted(qc_contract.STAGES)
|
|
90
|
-
)
|
|
91
|
-
parser.add_argument("--fixture", help="offline model-response fixture JSON")
|
|
92
|
-
parser.add_argument(
|
|
93
|
-
"--live", action="store_true", help="make one live MiMo request for this stage"
|
|
94
|
-
)
|
|
95
|
-
parser.add_argument(
|
|
96
|
-
"--refresh", action="store_true", help="ignore a matching stage cache"
|
|
97
|
-
)
|
|
98
|
-
parser.add_argument(
|
|
99
|
-
"--dry-run",
|
|
100
|
-
action="store_true",
|
|
101
|
-
help="write evidence only; never access the network",
|
|
102
|
-
)
|
|
103
|
-
parser.add_argument("--model", help="override MIMO_QC_MODEL for this call")
|
|
104
|
-
parser.add_argument("--final-output")
|
|
105
|
-
parser.add_argument("--output")
|
|
106
|
-
args = parser.parse_args(argv)
|
|
107
|
-
fixture = _load_fixture(args.fixture)
|
|
108
|
-
config = {"mimo_qc_model": args.model} if args.model else None
|
|
109
|
-
result = (run_callable or run)(
|
|
110
|
-
args.work_dir,
|
|
111
|
-
stage=args.stage,
|
|
112
|
-
fixture=fixture,
|
|
113
|
-
live=args.live and fixture is None,
|
|
114
|
-
refresh=args.refresh,
|
|
115
|
-
dry_run=args.dry_run or (not args.live and fixture is None),
|
|
116
|
-
config=config,
|
|
117
|
-
final_output=args.final_output,
|
|
118
|
-
output=args.output,
|
|
119
|
-
)
|
|
120
|
-
print(
|
|
121
|
-
json.dumps(
|
|
122
|
-
{
|
|
123
|
-
"ok": result["report"]["ok"],
|
|
124
|
-
"status": result["report"]["metadata"]["status"],
|
|
125
|
-
"path": result["path"],
|
|
126
|
-
},
|
|
127
|
-
ensure_ascii=False,
|
|
128
|
-
)
|
|
129
|
-
)
|
|
130
|
-
return 0
|