@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
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
4
|
import math
|
|
5
|
+
import os
|
|
5
6
|
import shlex
|
|
6
7
|
import shutil
|
|
7
8
|
import subprocess
|
|
@@ -9,8 +10,7 @@ import sys
|
|
|
9
10
|
from pathlib import Path
|
|
10
11
|
|
|
11
12
|
import materials as material_lib
|
|
12
|
-
from
|
|
13
|
-
from lib import env_bool, env_int, load_json
|
|
13
|
+
from lib import env_bool, env_int, ffmpeg_has_subtitles_filter, file_identity, load_json, material_id_for
|
|
14
14
|
from recap_source import audio_binding
|
|
15
15
|
|
|
16
16
|
BUNDLE = Path(__file__).resolve().parents[2] # the skills/ directory
|
|
@@ -23,7 +23,9 @@ MULTI_SOURCE_MANIFEST = "multi_source_manifest.json"
|
|
|
23
23
|
def _run(skill, script, *cli_args):
|
|
24
24
|
cmd = [sys.executable, str(_entry(skill, script)), *map(str, cli_args)]
|
|
25
25
|
print(f"[video-recap] ▶ {skill}/{script}", flush=True)
|
|
26
|
-
|
|
26
|
+
# Stage scripts log Chinese; on Windows a piped stdout defaults to cp1252 and the
|
|
27
|
+
# first log line would crash the child with UnicodeEncodeError.
|
|
28
|
+
res = subprocess.run(cmd, env={**os.environ, "PYTHONIOENCODING": "utf-8"})
|
|
27
29
|
if res.returncode != 0:
|
|
28
30
|
raise SystemExit(f"{skill}/{script} 失败 (exit {res.returncode})")
|
|
29
31
|
|
|
@@ -60,12 +62,18 @@ def _read_video_duration_or_raise(path):
|
|
|
60
62
|
return duration
|
|
61
63
|
|
|
62
64
|
|
|
63
|
-
|
|
65
|
+
# Subtitle Y coordinates are display-canvas rows; a near-square SAR only changes the canvas
|
|
66
|
+
# width (or, rotated, the row scale the renderer converts exactly). Same value as the
|
|
67
|
+
# renderer's and the measuring tool's copies (parity-tested).
|
|
68
|
+
SUBTITLE_BAND_SAR_TOLERANCE = 0.02
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _probe_display_height_or_raise(path, *, require_near_square_pixels=False):
|
|
64
72
|
"""Return ffmpeg's display-coordinate height, accounting for rotation and SAR."""
|
|
65
|
-
return _probe_display_size_or_raise(path,
|
|
73
|
+
return _probe_display_size_or_raise(path, require_near_square_pixels=require_near_square_pixels)[1]
|
|
66
74
|
|
|
67
75
|
|
|
68
|
-
def _probe_display_size_or_raise(path, *,
|
|
76
|
+
def _probe_display_size_or_raise(path, *, require_near_square_pixels=False):
|
|
69
77
|
"""Return ffmpeg's display-coordinate (width, height), accounting for rotation and SAR."""
|
|
70
78
|
cmd = [
|
|
71
79
|
"ffprobe",
|
|
@@ -92,11 +100,14 @@ def _probe_display_size_or_raise(path, *, require_square_pixels=False):
|
|
|
92
100
|
sar_ratio = float(num) / float(den)
|
|
93
101
|
except (ValueError, ZeroDivisionError):
|
|
94
102
|
sar_ratio = math.nan
|
|
95
|
-
if
|
|
96
|
-
|
|
103
|
+
if sar in {"0:1", "N/A"}:
|
|
104
|
+
sar_ratio = 1.0 # unspecified SAR: ffmpeg (and the renderer's canvas) treat it as square
|
|
105
|
+
if require_near_square_pixels and (
|
|
106
|
+
not math.isfinite(sar_ratio) or abs(sar_ratio - 1.0) > SUBTITLE_BAND_SAR_TOLERANCE + 1e-9
|
|
97
107
|
):
|
|
98
108
|
raise SystemExit(
|
|
99
|
-
|
|
109
|
+
"subtitle Y coordinates require square or near-square pixels "
|
|
110
|
+
f"(SAR within {SUBTITLE_BAND_SAR_TOLERANCE:.0%} of 1:1); got {sar}"
|
|
100
111
|
)
|
|
101
112
|
display_width = max(1, round(width * sar_ratio)) if math.isfinite(sar_ratio) else width
|
|
102
113
|
rotation_values = [
|
|
@@ -134,14 +145,43 @@ def _run_manifest_payload(video, args):
|
|
|
134
145
|
return {
|
|
135
146
|
"schema_version": 1,
|
|
136
147
|
"source_video": str(Path(video).resolve()),
|
|
137
|
-
"source_video_identity":
|
|
148
|
+
"source_video_identity": file_identity(video),
|
|
138
149
|
"settings": _analysis_settings(args),
|
|
139
150
|
"audio": audio_binding(args),
|
|
140
151
|
}
|
|
141
152
|
|
|
142
153
|
|
|
154
|
+
# Settings main() can take from the environment rather than argv, with their no-env value.
|
|
155
|
+
# A resume shell may not carry the same environment, so the resume argv pins them.
|
|
156
|
+
_ENV_FILLED = (
|
|
157
|
+
("edit_mode", "full"),
|
|
158
|
+
("target_duration", None),
|
|
159
|
+
("tts_provider", "auto"),
|
|
160
|
+
("voice_ref", None),
|
|
161
|
+
("subtitle_y_top", None),
|
|
162
|
+
("subtitle_y_bot", None),
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _resume_argv(work_dir, args):
|
|
167
|
+
"""The argv that resumes this run: what the user typed (paths made absolute by
|
|
168
|
+
recap_cli), plus the work dir and any environment-filled setting it did not spell out.
|
|
169
|
+
Values a --project binding filled in are re-derived from --project on resume."""
|
|
170
|
+
argv = list(args._argv)
|
|
171
|
+
explicit = args._explicit_options
|
|
172
|
+
bound = getattr(args, "_bound_from_project", frozenset())
|
|
173
|
+
if "--work-dir" not in explicit:
|
|
174
|
+
argv += ["--work-dir", str(work_dir)]
|
|
175
|
+
for dest, unset in _ENV_FILLED:
|
|
176
|
+
flag = "--" + dest.replace("_", "-")
|
|
177
|
+
value = getattr(args, dest)
|
|
178
|
+
if flag not in explicit and dest not in bound and value != unset:
|
|
179
|
+
argv += [flag, str(value)]
|
|
180
|
+
return argv
|
|
181
|
+
|
|
182
|
+
|
|
143
183
|
def _write_run_manifest(work_dir, video, args):
|
|
144
|
-
payload = _run_manifest_payload(video, args)
|
|
184
|
+
payload = {**_run_manifest_payload(video, args), "argv": _resume_argv(work_dir, args)}
|
|
145
185
|
(work_dir / RUN_MANIFEST).write_text(
|
|
146
186
|
json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8"
|
|
147
187
|
)
|
|
@@ -151,14 +191,14 @@ def _write_run_manifest(work_dir, video, args):
|
|
|
151
191
|
def _build_multi_source_records(videos, args):
|
|
152
192
|
records = []
|
|
153
193
|
for video in _coerce_videos(videos):
|
|
154
|
-
identity =
|
|
194
|
+
identity = file_identity(video)
|
|
155
195
|
records.append(
|
|
156
196
|
{
|
|
157
197
|
"source_path": str(video),
|
|
158
198
|
"source_name": video.name,
|
|
159
199
|
"source_video_identity": identity,
|
|
160
200
|
"settings": _analysis_settings(args),
|
|
161
|
-
"material_id":
|
|
201
|
+
"material_id": material_id_for(video, identity),
|
|
162
202
|
}
|
|
163
203
|
)
|
|
164
204
|
records = material_lib.assign_source_ids(records)
|
|
@@ -188,7 +228,10 @@ def _multi_run_manifest_payload(videos, args, source_records):
|
|
|
188
228
|
|
|
189
229
|
|
|
190
230
|
def _write_project_run_manifest(work_dir, videos, args, source_records):
|
|
191
|
-
payload =
|
|
231
|
+
payload = {
|
|
232
|
+
**_multi_run_manifest_payload(videos, args, source_records),
|
|
233
|
+
"argv": _resume_argv(work_dir, args),
|
|
234
|
+
}
|
|
192
235
|
(work_dir / RUN_MANIFEST).write_text(
|
|
193
236
|
json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8"
|
|
194
237
|
)
|
|
@@ -240,20 +283,41 @@ def _ffmpeg_present_but_cannot_burn():
|
|
|
240
283
|
return not ffmpeg_has_subtitles_filter()
|
|
241
284
|
|
|
242
285
|
|
|
286
|
+
def _burn_subtitles_explicit(args):
|
|
287
|
+
"""Burn was asked for, not inherited: --burn-subtitles, or a truthy BURN_SUBTITLES."""
|
|
288
|
+
if args.burn_subtitles is not None:
|
|
289
|
+
return args.burn_subtitles
|
|
290
|
+
return "BURN_SUBTITLES" in os.environ and env_bool("BURN_SUBTITLES", True)
|
|
291
|
+
|
|
292
|
+
|
|
243
293
|
def _preflight_burn_subtitles(args):
|
|
244
|
-
"""
|
|
245
|
-
|
|
246
|
-
`-vf subtitles=` step
|
|
247
|
-
|
|
294
|
+
"""Settle subtitle burn-in BEFORE any understanding/VLM/ASR/TTS spend when this ffmpeg
|
|
295
|
+
lacks libass. An explicit request fails fast here, since otherwise the run only dies at
|
|
296
|
+
the final assemble `-vf subtitles=` step. The default burn only warns: assemble then
|
|
297
|
+
delivers the .srt sidecar and records `subtitle_burn_degraded` in visual_qc.json, which
|
|
298
|
+
final_qc.json carries in metadata.warnings. Dub renders through its own script, which
|
|
299
|
+
never burns subtitles, so it is exempt."""
|
|
248
300
|
if args.edit_mode == "dub" or not _burn_subtitles_intended(args):
|
|
249
301
|
return
|
|
250
|
-
if _ffmpeg_present_but_cannot_burn():
|
|
302
|
+
if not _ffmpeg_present_but_cannot_burn():
|
|
303
|
+
return
|
|
304
|
+
doctor = f"python3 {shlex.quote(str(_entry('video-recap', 'doctor.py')))}"
|
|
305
|
+
if _burn_subtitles_explicit(args):
|
|
251
306
|
raise SystemExit(
|
|
252
|
-
"
|
|
253
|
-
"
|
|
254
|
-
"
|
|
255
|
-
|
|
307
|
+
"已显式要求烧录字幕(--burn-subtitles 或 BURN_SUBTITLES),但当前 ffmpeg 不支持"
|
|
308
|
+
" subtitles/libass 滤镜,整条流程会跑到最后渲染才失败。\n"
|
|
309
|
+
" 解决其一:(1) 安装带 libass 的 ffmpeg;(2) 去掉显式烧录要求,默认运行会改为输出"
|
|
310
|
+
" .srt 外挂字幕;(3) 加 --no-burn-subtitles。\n"
|
|
311
|
+
f" 自检:{doctor}"
|
|
256
312
|
)
|
|
313
|
+
print(
|
|
314
|
+
"[video-recap] ⚠ 当前 ffmpeg 不支持 subtitles/libass 滤镜:本次成片不烧录字幕。"
|
|
315
|
+
"有字幕条目时改为在成片旁输出同名 .srt 外挂字幕(final_qc.json 的 metadata.warnings"
|
|
316
|
+
" 会记录 subtitle_burn_degraded);没有字幕条目的运行(如 source 音频模式且没有"
|
|
317
|
+
" user_subtitles.*)不输出 .srt,也不记警告。要烧录字幕请安装带 libass 的 ffmpeg;"
|
|
318
|
+
f"自检:{doctor}",
|
|
319
|
+
flush=True,
|
|
320
|
+
)
|
|
257
321
|
|
|
258
322
|
|
|
259
323
|
def _entry(skill, script):
|
|
@@ -24,8 +24,6 @@ _LOCAL_ADOPTION_OPTIONS = (
|
|
|
24
24
|
)
|
|
25
25
|
|
|
26
26
|
_LOCAL_ADOPTION_CONFLICTS = _TTS_OPTIONS | frozenset({
|
|
27
|
-
"--mimo-qc",
|
|
28
|
-
"--mimo-qc-refresh",
|
|
29
27
|
"--export-jianying",
|
|
30
28
|
"--jianying-bundle-media",
|
|
31
29
|
"--jianying-no-bundle-media",
|
|
@@ -106,7 +104,6 @@ def validate_local_adoption(parser, args):
|
|
|
106
104
|
args.tts_provider = "auto"
|
|
107
105
|
args.mimo_tts_voice = None
|
|
108
106
|
args.voice_ref = None
|
|
109
|
-
args.mimo_qc = "off"
|
|
110
107
|
|
|
111
108
|
|
|
112
109
|
def load_local_assembly_evidence(work_dir):
|
|
@@ -215,8 +212,6 @@ def validate_audio_routing(parser, args):
|
|
|
215
212
|
if conflicts:
|
|
216
213
|
detail = ", ".join(conflicts)
|
|
217
214
|
parser.error(f"--audio-mode {mode} cannot use TTS/strict narration options: {detail}")
|
|
218
|
-
if args.mimo_qc != "off":
|
|
219
|
-
parser.error(f"--audio-mode {mode} currently requires --mimo-qc off")
|
|
220
215
|
if args.edit_mode == "cut" and stream != 0:
|
|
221
216
|
parser.error("cut audio modes currently require --audio-stream-index 0")
|
|
222
217
|
if args.export_jianying and stream != 0:
|
|
@@ -242,37 +237,3 @@ def reject_unsupported_subtitle_track(work_dir, args):
|
|
|
242
237
|
"source-mix 当前不能绑定显式 subtitle_track.json;请使用新的 work_dir,"
|
|
243
238
|
"或选择 adopted-packet-copy"
|
|
244
239
|
)
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
def begin_non_narration_qc(work_dir, args, write_stage):
|
|
248
|
-
"""Start a clean run-local QC ledger without reading or deleting old TTS."""
|
|
249
|
-
(Path(work_dir) / "preflight_qc.json").unlink(missing_ok=True)
|
|
250
|
-
return write_stage(
|
|
251
|
-
work_dir,
|
|
252
|
-
"pre_assemble",
|
|
253
|
-
metadata={
|
|
254
|
-
"audio_mode": args.audio_mode,
|
|
255
|
-
"selected_audio_stream_index": args.audio_stream_index,
|
|
256
|
-
"tts": "not_applicable",
|
|
257
|
-
"narration_validation": "not_applicable",
|
|
258
|
-
"narration_review": "not_applicable",
|
|
259
|
-
"visual_overlays": "preserved_not_authored_by_this_run",
|
|
260
|
-
},
|
|
261
|
-
)
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
def begin_local_adoption_qc(work_dir, write_stage):
|
|
265
|
-
"""Record that authoring was intentionally skipped for adopted local assets."""
|
|
266
|
-
return write_stage(
|
|
267
|
-
work_dir,
|
|
268
|
-
"pre_assemble",
|
|
269
|
-
metadata={
|
|
270
|
-
"audio_mode": "narration",
|
|
271
|
-
"selected_audio_stream_index": 0,
|
|
272
|
-
"tts": "adopted_local_not_generated",
|
|
273
|
-
"narration_validation": "not_run",
|
|
274
|
-
"narration_review": "not_run",
|
|
275
|
-
"semantic_validation": "video-assemble",
|
|
276
|
-
"visual_overlays": "not_authored_not_present",
|
|
277
|
-
},
|
|
278
|
-
)
|
|
@@ -1,72 +1,13 @@
|
|
|
1
|
-
"""Write
|
|
1
|
+
"""Write final_qc.json after render and enforce --require-final-qc."""
|
|
2
2
|
|
|
3
|
-
import json
|
|
4
3
|
from pathlib import Path
|
|
5
4
|
|
|
6
5
|
import final_qc
|
|
7
|
-
import
|
|
8
|
-
import qc_contract
|
|
9
|
-
|
|
10
|
-
ASSEMBLY_MANIFEST = "assembly_manifest.json"
|
|
11
|
-
PREFLIGHT_QC = "preflight_qc.json"
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def _load_preflight_stage_reports(work_dir):
|
|
15
|
-
path = Path(work_dir) / PREFLIGHT_QC
|
|
16
|
-
if not path.exists():
|
|
17
|
-
return {}
|
|
18
|
-
return json.loads(path.read_text(encoding="utf-8"))["metadata"]["stages"]
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def _write_shift_left_stage_qc(work_dir, stage, metadata, findings=None):
|
|
22
|
-
"""Write/roll up local shift-left QC for one pipeline stage.
|
|
23
|
-
|
|
24
|
-
This is a local contract artifact only: no MiMo/deep eval calls, no repair, and no
|
|
25
|
-
credential persistence (qc_contract redacts every report it builds).
|
|
26
|
-
"""
|
|
27
|
-
stage_report = qc_contract.build_report(
|
|
28
|
-
artifact=PREFLIGHT_QC, stage=stage, findings=findings, metadata=metadata
|
|
29
|
-
)
|
|
30
|
-
stage_reports = _load_preflight_stage_reports(work_dir)
|
|
31
|
-
stage_reports[stage] = stage_report
|
|
32
|
-
top_metadata = dict(stage_report["metadata"])
|
|
33
|
-
top_metadata["latest_stage"] = stage
|
|
34
|
-
top_metadata["stages"] = stage_reports
|
|
35
|
-
top_report = qc_contract.build_report(
|
|
36
|
-
artifact=PREFLIGHT_QC,
|
|
37
|
-
stage=stage,
|
|
38
|
-
findings=[f for report in stage_reports.values() for f in report["findings"]],
|
|
39
|
-
metadata=top_metadata,
|
|
40
|
-
)
|
|
41
|
-
(Path(work_dir) / PREFLIGHT_QC).write_text(
|
|
42
|
-
json.dumps(top_report, ensure_ascii=False, indent=2) + "\n", encoding="utf-8"
|
|
43
|
-
)
|
|
44
|
-
return top_report
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
def _tts_qc_metadata(work_dir):
|
|
48
|
-
"""Called after video-voiceover exited 0, which always writes tts_meta.json."""
|
|
49
|
-
work_dir = Path(work_dir)
|
|
50
|
-
metadata = {"tts_meta": json.loads((work_dir / "tts_meta.json").read_text(encoding="utf-8"))}
|
|
51
|
-
tts_dir = work_dir / "tts_segments"
|
|
52
|
-
if tts_dir.is_dir():
|
|
53
|
-
metadata["tts_segments"] = [
|
|
54
|
-
p.relative_to(work_dir).as_posix() for p in sorted(tts_dir.iterdir()) if p.is_file()
|
|
55
|
-
]
|
|
56
|
-
return metadata
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
def _post_render_qc_metadata(work_dir, final_output):
|
|
60
|
-
"""Called after video-assemble exited 0, which always writes assembly_manifest.json."""
|
|
61
|
-
manifest = Path(work_dir) / ASSEMBLY_MANIFEST
|
|
62
|
-
return {
|
|
63
|
-
"final_output": str(final_output),
|
|
64
|
-
"assembly_manifest": json.loads(manifest.read_text(encoding="utf-8")),
|
|
65
|
-
}
|
|
6
|
+
from lib import read_json_object
|
|
66
7
|
|
|
67
8
|
|
|
68
9
|
def _write_final_qc_reports(work_dir, final_output):
|
|
69
|
-
"""Write report-only
|
|
10
|
+
"""Write the report-only final_qc.json after render.
|
|
70
11
|
|
|
71
12
|
final_qc.run converts ffprobe unavailability/failure into deterministic
|
|
72
13
|
blockers; only unexpected schema/write errors propagate.
|
|
@@ -75,86 +16,47 @@ def _write_final_qc_reports(work_dir, final_output):
|
|
|
75
16
|
|
|
76
17
|
|
|
77
18
|
def _print_final_qc_pointer(result):
|
|
78
|
-
"""Surface a report-only final_qc
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
for key in ("final_qc", "golden_eval")
|
|
83
|
-
if result[key].get("ok") is False
|
|
84
|
-
]
|
|
85
|
-
if problems:
|
|
19
|
+
"""Surface a report-only final_qc FAIL so the final QC is not a silent
|
|
20
|
+
no-op. Advisory only: it never changes the exit status."""
|
|
21
|
+
summary = result["final_qc"]
|
|
22
|
+
if summary.get("ok") is False:
|
|
86
23
|
print(
|
|
87
24
|
"[video-recap] ⚠️ 最终 QC 未通过(仅报告,不阻断): "
|
|
88
|
-
|
|
89
|
-
|
|
25
|
+
f"final_qc blocker_count={summary.get('blocker_count', '?')}"
|
|
26
|
+
";详见 final_qc.json"
|
|
90
27
|
)
|
|
91
28
|
|
|
92
29
|
|
|
93
30
|
def _require_final_qc(result, work_dir):
|
|
94
|
-
"""Fail closed unless
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
blockers = summary.get("blocker_count") if isinstance(summary, dict) else None
|
|
101
|
-
if not isinstance(summary, dict) or summary.get("ok") is not True or \
|
|
102
|
-
type(blockers) is not int or blockers != 0:
|
|
103
|
-
invalid.append(name)
|
|
31
|
+
"""Fail closed unless the final_qc summary is a literal blocker-free pass."""
|
|
32
|
+
print(f"[video-recap] 最终 QC 报告: {Path(work_dir) / 'final_qc.json'}")
|
|
33
|
+
summary = result.get("final_qc") if isinstance(result, dict) else None
|
|
34
|
+
blockers = summary.get("blocker_count") if isinstance(summary, dict) else None
|
|
35
|
+
invalid = not isinstance(summary, dict) or summary.get("ok") is not True or \
|
|
36
|
+
type(blockers) is not int or blockers != 0
|
|
104
37
|
if invalid:
|
|
105
|
-
raise SystemExit(
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
)
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
""
|
|
120
|
-
if args.mimo_qc == "off":
|
|
121
|
-
mimo_qc.clear_report(work_dir)
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
def _print_mimo_qc_pointer(result, stage):
|
|
125
|
-
report = result["report"]
|
|
126
|
-
metadata = report["metadata"]
|
|
127
|
-
status = metadata["status"]
|
|
128
|
-
stage_findings = [f for f in report["findings"] if f["stage"] == stage]
|
|
129
|
-
if status in {"failed", "unavailable"}:
|
|
38
|
+
raise SystemExit("严格最终 QC 未通过或摘要格式无效: final_qc")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _print_render_warnings(result, work_dir, final_output):
|
|
42
|
+
"""Relay non-blocking render warnings (final_qc metadata.warnings) and the subtitle
|
|
43
|
+
sidecar, so a degraded run is never reported as a plain success. The sidecar comes from
|
|
44
|
+
assembly_manifest.json only when that manifest rendered `final_output` (dub never
|
|
45
|
+
writes one, so a stale manifest from an earlier run must not be quoted)."""
|
|
46
|
+
codes = (result.get("final_qc") or {}).get("warnings") or []
|
|
47
|
+
manifest = read_json_object(Path(work_dir) / "assembly_manifest.json") or {}
|
|
48
|
+
rendered = manifest.get("final_output")
|
|
49
|
+
same_render = isinstance(rendered, str) and \
|
|
50
|
+
Path(rendered).resolve() == Path(final_output).resolve()
|
|
51
|
+
sidecar = manifest.get("subtitle_sidecar") if same_render else None
|
|
52
|
+
if "subtitle_burn_degraded" in codes:
|
|
130
53
|
print(
|
|
131
|
-
|
|
54
|
+
"[video-recap] ⚠️ 字幕未烧录进画面(当前 ffmpeg 缺 libass):"
|
|
55
|
+
f"字幕以外挂文件交付 {sidecar};详见 final_qc.json 的 metadata.warnings"
|
|
132
56
|
)
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
print(f"[video-recap] - {finding['message']}")
|
|
139
|
-
|
|
57
|
+
elif sidecar:
|
|
58
|
+
print(f"[video-recap] 外挂字幕: {sidecar}")
|
|
59
|
+
for code in codes:
|
|
60
|
+
if code != "subtitle_burn_degraded":
|
|
61
|
+
print(f"[video-recap] ⚠️ 渲染警告: {code};详见 final_qc.json 的 metadata.warnings")
|
|
140
62
|
|
|
141
|
-
def _run_mimo_qc_stage(work_dir, args, stage, *, final_output=None):
|
|
142
|
-
"""Run one selected advisory stage and never propagate a failure."""
|
|
143
|
-
if not _mimo_qc_stage_enabled(args, stage):
|
|
144
|
-
return None
|
|
145
|
-
try:
|
|
146
|
-
result = mimo_qc.run(
|
|
147
|
-
work_dir,
|
|
148
|
-
stage=stage,
|
|
149
|
-
live=True,
|
|
150
|
-
refresh=args.mimo_qc_refresh,
|
|
151
|
-
final_output=final_output,
|
|
152
|
-
)
|
|
153
|
-
except Exception as exc:
|
|
154
|
-
print(
|
|
155
|
-
f"[video-recap] ⚠ MiMo QC {stage}: {type(exc).__name__};"
|
|
156
|
-
"建议性检查失败,继续流水线"
|
|
157
|
-
)
|
|
158
|
-
return None
|
|
159
|
-
_print_mimo_qc_pointer(result, stage)
|
|
160
|
-
return result
|