@oh-story/dsh 0.1.7 → 0.1.9
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 +30 -15
- package/lib/client.js +93 -16
- package/lib/client.js.map +2 -2
- package/lib/drama/manifest.json +165 -69
- package/lib/drama/skills/short-drama/SKILL.md +5 -1
- package/lib/drama/skills/short-drama/scripts/creator_markdown_check.py +126 -9
- package/lib/drama/skills/short-drama/scripts/project_tool.py +11 -0
- package/lib/drama/skills/short-drama-assets/references/character-and-look.md +18 -0
- package/lib/drama/skills/short-drama-assets/references/continuity-lock.md +10 -1
- package/lib/drama/skills/short-drama-edit/SKILL.md +148 -0
- package/lib/drama/skills/short-drama-edit/agents/openai.yaml +4 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/README.md +60 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/package.json +15 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/remotion.config.ts +6 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/Root.tsx +26 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/Subtitles.tsx +88 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/font.ts +46 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/index.ts +4 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/schema.ts +32 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/tsconfig.json +13 -0
- package/lib/drama/skills/short-drama-edit/assets//345/211/252/350/276/221/345/215/225.md +43 -0
- package/lib/drama/skills/short-drama-edit/references/cut-craft.md +81 -0
- package/lib/drama/skills/short-drama-edit/references/delivery-verify.md +58 -0
- package/lib/drama/skills/short-drama-edit/references/generated-footage.md +96 -0
- package/lib/drama/skills/short-drama-edit/references/sound-and-subtitles.md +113 -0
- package/lib/drama/skills/short-drama-edit/references/stage-contract.md +55 -0
- package/lib/drama/skills/short-drama-edit/scripts/edit_tool.py +1041 -0
- package/lib/drama/skills/short-drama-edit/scripts/selftest.py +456 -0
- package/lib/drama/skills/short-drama-image-prompts/assets/image-prompts.md +5 -0
- package/lib/drama/skills/short-drama-image-prompts/assets/lookdev-prompts.md +3 -0
- package/lib/drama/skills/short-drama-novel-analyze/references/aggregation-and-entities.md +2 -2
- package/lib/drama/skills/short-drama-novel-analyze/references/chapter-extraction.md +4 -1
- package/lib/drama/skills/short-drama-produce/SKILL.md +24 -3
- package/lib/drama/skills/short-drama-produce/references/adapter-contract.md +28 -0
- package/lib/drama/skills/short-drama-produce/references/providers/minimax-h3-video.md +24 -5
- package/lib/drama/skills/short-drama-produce/references/providers/seedance.md +7 -0
- package/lib/drama/skills/short-drama-produce/scripts/fixture_adapter.py +18 -1
- package/lib/drama/skills/short-drama-produce/scripts/production_tool.py +181 -2
- package/lib/drama/skills/short-drama-produce/scripts/provider_adapters.py +57 -2
- package/lib/drama/skills/short-drama-review/SKILL.md +3 -0
- package/lib/drama/skills/short-drama-review/references/production-quality-gates.md +20 -0
- package/lib/drama/skills/short-drama-review/references/rubric-visual-motion.md +36 -0
- package/lib/drama/skills/short-drama-storyboard/SKILL.md +7 -0
- package/lib/drama/skills/short-drama-storyboard/assets/keyframe-prompts.md +4 -0
- package/lib/drama/skills/short-drama-storyboard/references/shot-craft.md +39 -0
- package/lib/drama/skills/short-drama-storyboard/references/stage-contract.md +2 -0
- package/lib/drama/skills/short-drama-video-prompts/SKILL.md +7 -1
- package/lib/drama/skills/short-drama-video-prompts/references/minimax-h3.md +101 -6
- package/lib/drama/skills/short-drama-video-prompts/references/motion-recipe.md +7 -1
- package/lib/drama/skills/short-drama-video-prompts/references/performance-action-timing.md +35 -0
- package/lib/drama/skills/short-drama-video-prompts/references/production-prompt-grammar.md +3 -1
- package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.0.md +13 -3
- package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.5.md +35 -4
- package/lib/drama/skills/short-drama-video-prompts/references/stage-contract.md +1 -1
- package/lib/drama/skills/short-drama-write/SKILL.md +13 -2
- package/lib/drama/skills/short-drama-write/references/screenplay-format.md +6 -0
- package/lib/drama/skills/short-drama-write/references/stage-contract.md +1 -0
- package/lib/index.js +4 -2
- package/lib/index.js.map +2 -2
- package/lib/novel-to-game/VERSION +1 -1
- package/lib/novel-to-game/manifest.json +28 -28
- package/lib/novel-to-game/skills/game-art-direction/SKILL.md +6 -27
- package/lib/novel-to-game/skills/game-build/SKILL.md +12 -29
- package/lib/novel-to-game/skills/game-concept/SKILL.md +10 -26
- package/lib/novel-to-game/skills/game-qa/SKILL.md +5 -15
- package/lib/novel-to-game/skills/game-qa/references/test-design-method.md +1 -1
- package/lib/novel-to-game/skills/game-world-design/SKILL.md +19 -28
- package/lib/novel-to-game/skills/game-world-design/references/dialogue-design-method.md +1 -2
- package/lib/novel-to-game/skills/novel-game-analyze/SKILL.md +6 -9
- package/lib/novel-to-game/skills/novel-to-game/SKILL.md +6 -11
- package/lib/novel-to-game/skills/novel-to-game/references/intake-benchmark-reference.md +0 -4
- package/lib/novel-to-game/skills/novel-to-game/references/intake-method.md +1 -3
- package/lib/novel-to-game/skills/novel-to-game/references/pipeline-contract.md +3 -5
- package/lib/oh-story/manifest.json +292 -172
- package/lib/oh-story/roles/character-designer.md +1 -1
- package/lib/oh-story/roles/narrative-writer.md +56 -242
- package/lib/oh-story/roles/story-architect.md +1 -1
- package/lib/oh-story/skills/story/SKILL.md +1 -1
- package/lib/oh-story/skills/story/VERSION +1 -1
- package/lib/oh-story/skills/story/references/author-memory.md +10 -8
- package/lib/oh-story/skills/story-deslop/SKILL.md +20 -182
- package/lib/oh-story/skills/story-deslop/references/anti-ai-writing.md +19 -47
- package/lib/oh-story/skills/story-deslop/references/author-memory.md +10 -8
- package/lib/oh-story/skills/story-deslop/references/banned-words.md +3 -1
- package/lib/oh-story/skills/story-deslop/references/deslop-gates.md +154 -0
- package/lib/oh-story/skills/story-deslop/references/style-resolution.md +27 -0
- package/lib/oh-story/skills/story-deslop/scripts/check-ai-patterns.js +6 -1
- package/lib/oh-story/skills/story-deslop/scripts/check-degeneration.js +39 -20
- package/lib/oh-story/skills/story-deslop/scripts/normalize-punctuation.js +10 -5
- package/lib/oh-story/skills/story-deslop/scripts/style-whitelist.js +39 -0
- package/lib/oh-story/skills/story-import/SKILL.md +2 -2
- package/lib/oh-story/skills/story-import/references/format-and-structure.md +2 -2
- package/lib/oh-story/skills/story-import/references/tracking-initialization.md +31 -0
- package/lib/oh-story/skills/story-import/references/tracking-transaction.md +2 -29
- package/lib/oh-story/skills/story-import/scripts/check-outline-contract.js +176 -12
- package/lib/oh-story/skills/story-long-analyze/SKILL.md +1 -1
- package/lib/oh-story/skills/story-long-write/SKILL.md +20 -193
- package/lib/oh-story/skills/story-long-write/references/anti-ai-writing.md +19 -47
- package/lib/oh-story/skills/story-long-write/references/artifact-protocols.md +62 -7
- package/lib/oh-story/skills/story-long-write/references/author-memory.md +10 -8
- package/lib/oh-story/skills/story-long-write/references/banned-words.md +3 -1
- package/lib/oh-story/skills/story-long-write/references/benchmark-recall.md +20 -0
- package/lib/oh-story/skills/story-long-write/references/dialogue-mastery.md +6 -4
- package/lib/oh-story/skills/story-long-write/references/long-chapter-quality.md +2 -2
- package/lib/oh-story/skills/story-long-write/references/long-format.md +2 -0
- package/lib/oh-story/skills/story-long-write/references/outline-structure-theory.md +2 -0
- package/lib/oh-story/skills/story-long-write/references/project-files.md +108 -0
- package/lib/oh-story/skills/story-long-write/references/reference-index.md +87 -0
- package/lib/oh-story/skills/story-long-write/references/style-combat-face.md +1 -1
- package/lib/oh-story/skills/story-long-write/references/style-craft.md +9 -29
- package/lib/oh-story/skills/story-long-write/references/style-genre-modules.md +1 -1
- package/lib/oh-story/skills/story-long-write/references/style-resolution.md +27 -0
- package/lib/oh-story/skills/story-long-write/references/tracking-initialization.md +31 -0
- package/lib/oh-story/skills/story-long-write/references/tracking-transaction.md +2 -29
- package/lib/oh-story/skills/story-long-write/references/workflow-chapter.md +51 -52
- package/lib/oh-story/skills/story-long-write/references/workflow-daily.md +8 -7
- package/lib/oh-story/skills/story-long-write/references/workflow-setup.md +36 -15
- package/lib/oh-story/skills/story-long-write/references/writer-prompt-fallback.md +22 -0
- package/lib/oh-story/skills/story-long-write/references/writing-craft.md +69 -2
- package/lib/oh-story/skills/story-long-write/scripts/build_writer_prompt.py +410 -0
- package/lib/oh-story/skills/story-long-write/scripts/check-ai-patterns.js +6 -1
- package/lib/oh-story/skills/story-long-write/scripts/check-degeneration.js +39 -20
- package/lib/oh-story/skills/story-long-write/scripts/check-outline-contract.js +176 -12
- package/lib/oh-story/skills/story-long-write/scripts/check-outline-copy.js +90 -23
- package/lib/oh-story/skills/story-long-write/scripts/normalize-punctuation.js +10 -5
- package/lib/oh-story/skills/story-long-write/scripts/outline_view.py +283 -0
- package/lib/oh-story/skills/story-long-write/scripts/style-whitelist.js +39 -0
- package/lib/oh-story/skills/story-review/SKILL.md +13 -3
- package/lib/oh-story/skills/story-review/references/anti-ai-writing.md +19 -47
- package/lib/oh-story/skills/story-review/references/author-memory.md +10 -8
- package/lib/oh-story/skills/story-review/references/banned-words.md +3 -1
- package/lib/oh-story/skills/story-review/references/dialogue-mastery.md +6 -4
- package/lib/oh-story/skills/story-review/references/review-quality.md +2 -2
- package/lib/oh-story/skills/story-review/references/style-resolution.md +27 -0
- package/lib/oh-story/skills/story-review/references/tracking-initialization.md +31 -0
- package/lib/oh-story/skills/story-review/references/tracking-transaction.md +2 -29
- package/lib/oh-story/skills/story-review/scripts/check-ai-patterns.js +6 -1
- package/lib/oh-story/skills/story-review/scripts/check-degeneration.js +39 -20
- package/lib/oh-story/skills/story-review/scripts/normalize-punctuation.js +10 -5
- package/lib/oh-story/skills/story-review/scripts/style-whitelist.js +39 -0
- package/lib/oh-story/skills/story-setup/SKILL.md +17 -10
- package/lib/oh-story/skills/story-setup/references/agent-references/anti-ai-writing.md +19 -47
- package/lib/oh-story/skills/story-setup/references/agent-references/banned-words.md +3 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/deslop-gates.md +154 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/dialogue-mastery.md +6 -4
- package/lib/oh-story/skills/story-setup/references/agent-references/format-and-structure.md +2 -2
- package/lib/oh-story/skills/story-setup/references/agent-references/short-genre-formulas.md +1 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/style-combat-face.md +1 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/style-genre-modules.md +1 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/style-resolution.md +27 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/writing-craft.md +69 -2
- package/lib/oh-story/skills/story-setup/references/diagnostics.md +38 -0
- package/lib/oh-story/skills/story-short-analyze/references/analysis-writing-techniques.md +1 -1
- package/lib/oh-story/skills/story-short-analyze/references/source-story-quality.md +2 -2
- package/lib/oh-story/skills/story-short-write/SKILL.md +18 -186
- package/lib/oh-story/skills/story-short-write/references/author-memory.md +10 -8
- package/lib/oh-story/skills/story-short-write/references/banned-words.md +3 -1
- package/lib/oh-story/skills/story-short-write/references/benchmark-recall.md +60 -0
- package/lib/oh-story/skills/story-short-write/references/dialogue-mastery.md +6 -4
- package/lib/oh-story/skills/story-short-write/references/genre-styles//344/270/226/346/203/205/346/211/223/350/204/270.md +2 -2
- package/lib/oh-story/skills/story-short-write/references/genre-styles//345/217/214/347/224/267/344/270/273.md +1 -1
- package/lib/oh-story/skills/story-short-write/references/genre-styles//345/244/215/344/273/207/346/211/223/350/204/270.md +3 -3
- package/lib/oh-story/skills/story-short-write/references/genre-styles//345/256/205/346/226/227/345/256/253/346/226/227.md +3 -3
- package/lib/oh-story/skills/story-short-write/references/genre-styles//346/200/273/350/243/201/350/261/252/351/227/250.md +3 -3
- package/lib/oh-story/skills/story-short-write/references/genre-styles//346/202/254/347/226/221.md +5 -5
- package/lib/oh-story/skills/story-short-write/references/genre-styles//346/260/221/344/277/227/346/200/252/350/260/210.md +4 -4
- package/lib/oh-story/skills/story-short-write/references/genre-styles//347/224/234/345/256/240.md +5 -5
- package/lib/oh-story/skills/story-short-write/references/genre-styles//350/277/275/345/246/273/347/201/253/350/221/254/345/234/272.md +3 -3
- package/lib/oh-story/skills/story-short-write/references/genre-writing-formulas.md +1 -1
- package/lib/oh-story/skills/story-short-write/references/genre-writing-techniques.md +1 -1
- package/lib/oh-story/skills/story-short-write/references/reference-index.md +53 -0
- package/lib/oh-story/skills/story-short-write/references/short-craft.md +12 -14
- package/lib/oh-story/skills/story-short-write/references/short-deslop.md +10 -10
- package/lib/oh-story/skills/story-short-write/references/short-format.md +3 -1
- package/lib/oh-story/skills/story-short-write/references/short-prose-quality.md +2 -2
- package/lib/oh-story/skills/story-short-write/references/style-resolution.md +27 -0
- package/lib/oh-story/skills/story-short-write/references/workflow-design.md +77 -0
- package/lib/oh-story/skills/story-short-write/references/writing-workflow.md +5 -5
- package/lib/oh-story/skills/story-short-write/scripts/check-ai-patterns.js +6 -1
- package/lib/oh-story/skills/story-short-write/scripts/check-degeneration.js +39 -20
- package/lib/oh-story/skills/story-short-write/scripts/check-delivery-contract.js +34 -5
- package/lib/oh-story/skills/story-short-write/scripts/check-outline-copy.js +90 -23
- package/lib/oh-story/skills/story-short-write/scripts/normalize-punctuation.js +10 -5
- package/lib/oh-story/skills/story-short-write/scripts/style-whitelist.js +39 -0
- package/lib/types/client/drama-production-view.d.ts.map +1 -1
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/production-prompts.d.ts.map +1 -1
- package/lib/types/client/production-runtime.d.ts +17 -0
- package/lib/types/client/production-runtime.d.ts.map +1 -1
- package/lib/types/skill-provider.d.ts.map +1 -1
- package/lib/video-recap/CHANGELOG.md +31 -1
- package/lib/video-recap/manifest.json +162 -162
- package/lib/video-recap/skills/video-assemble/scripts/artifacts.py +4 -9
- package/lib/video-recap/skills/video-assemble/scripts/assemble.py +47 -60
- package/lib/video-recap/skills/video-assemble/scripts/assembly_contract.py +77 -115
- package/lib/video-recap/skills/video-assemble/scripts/assembly_settings.py +41 -40
- package/lib/video-recap/skills/video-assemble/scripts/audio_automation.py +22 -32
- package/lib/video-recap/skills/video-assemble/scripts/audio_mix.py +102 -192
- package/lib/video-recap/skills/video-assemble/scripts/jianying_builders.py +14 -35
- package/lib/video-recap/skills/video-assemble/scripts/jianying_model.py +14 -12
- package/lib/video-recap/skills/video-assemble/scripts/jianying_optional.py +8 -9
- package/lib/video-recap/skills/video-assemble/scripts/jianying_schema.py +3 -14
- package/lib/video-recap/skills/video-assemble/scripts/jianying_timeline_contract.py +23 -12
- package/lib/video-recap/skills/video-assemble/scripts/jianying_tracks.py +1 -4
- package/lib/video-recap/skills/video-assemble/scripts/jianying_writer.py +24 -25
- package/lib/video-recap/skills/video-assemble/scripts/lib.py +31 -85
- package/lib/video-recap/skills/video-assemble/scripts/media.py +100 -144
- package/lib/video-recap/skills/video-assemble/scripts/narration_audio.py +101 -195
- package/lib/video-recap/skills/video-assemble/scripts/render_preflight.py +10 -21
- package/lib/video-recap/skills/video-assemble/scripts/source_subtitles.py +90 -169
- package/lib/video-recap/skills/video-assemble/scripts/subtitle_core.py +69 -122
- package/lib/video-recap/skills/video-assemble/scripts/subtitle_render.py +10 -17
- package/lib/video-recap/skills/video-assemble/scripts/timeline.py +33 -48
- package/lib/video-recap/skills/video-assemble/scripts/timeline_emit.py +14 -20
- package/lib/video-recap/skills/video-assemble/scripts/visual_render.py +135 -213
- package/lib/video-recap/skills/video-cut/scripts/cut_cli.py +49 -57
- package/lib/video-recap/skills/video-cut/scripts/cut_contract.py +38 -74
- package/lib/video-recap/skills/video-cut/scripts/cut_render.py +46 -95
- package/lib/video-recap/skills/video-cut/scripts/lib.py +23 -25
- package/lib/video-recap/skills/video-cut/scripts/media_geometry.py +70 -173
- package/lib/video-recap/skills/video-cut/scripts/narration_mapping.py +28 -53
- package/lib/video-recap/skills/video-cut/scripts/sentence_boundaries.py +162 -322
- package/lib/video-recap/skills/video-recap/references/timeline-and-jianying.md +6 -7
- package/lib/video-recap/skills/video-recap/scripts/doctor.py +104 -124
- package/lib/video-recap/skills/video-recap/scripts/final_qc.py +255 -412
- package/lib/video-recap/skills/video-recap/scripts/lib.py +30 -52
- package/lib/video-recap/skills/video-recap/scripts/materials.py +97 -120
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc_client.py +7 -12
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc_evidence.py +46 -97
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc_observations.py +18 -36
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc_payload.py +29 -47
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc_report.py +43 -76
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc_runner.py +2 -1
- package/lib/video-recap/skills/video-recap/scripts/qc_contract.py +2 -1
- package/lib/video-recap/skills/video-recap/scripts/recap_cli.py +2 -2
- package/lib/video-recap/skills/video-recap/scripts/recap_inspect.py +129 -145
- package/lib/video-recap/skills/video-recap/scripts/recap_review.py +27 -20
- package/lib/video-recap/skills/video-recap/scripts/recap_runner.py +20 -31
- package/lib/video-recap/skills/video-recap/scripts/recap_runtime.py +31 -106
- package/lib/video-recap/skills/video-recap/scripts/recap_stage_qc.py +43 -113
- package/lib/video-recap/skills/video-recap/scripts/recap_timeline.py +196 -377
- package/lib/video-recap/skills/video-script/scripts/agent_brief.py +1 -1
- package/lib/video-recap/skills/video-script/scripts/agent_text.py +63 -172
- package/lib/video-recap/skills/video-script/scripts/brief_context.py +80 -135
- package/lib/video-recap/skills/video-script/scripts/brief_inputs.py +125 -167
- package/lib/video-recap/skills/video-script/scripts/brief_timeline.py +93 -208
- package/lib/video-recap/skills/video-script/scripts/deslop_qc.py +20 -46
- package/lib/video-recap/skills/video-script/scripts/lib.py +30 -26
- package/lib/video-recap/skills/video-script/scripts/narration_lint.py +145 -140
- package/lib/video-recap/skills/video-script/scripts/review_response.py +12 -42
- package/lib/video-recap/skills/video-script/scripts/review_runner.py +0 -6
- package/lib/video-recap/skills/video-script/scripts/speech_ownership.py +39 -100
- package/lib/video-recap/skills/video-script/scripts/timeline_fusion.py +33 -124
- package/lib/video-recap/skills/video-understanding/scripts/agent_brief.py +1 -1
- package/lib/video-recap/skills/video-understanding/scripts/agent_text.py +63 -172
- package/lib/video-recap/skills/video-understanding/scripts/asr.py +1 -2
- package/lib/video-recap/skills/video-understanding/scripts/brief_context.py +80 -135
- package/lib/video-recap/skills/video-understanding/scripts/brief_inputs.py +125 -167
- package/lib/video-recap/skills/video-understanding/scripts/brief_timeline.py +93 -208
- package/lib/video-recap/skills/video-understanding/scripts/consolidate.py +1 -1
- package/lib/video-recap/skills/video-understanding/scripts/deslop_qc.py +20 -46
- package/lib/video-recap/skills/video-understanding/scripts/lib.py +31 -26
- package/lib/video-recap/skills/video-understanding/scripts/narration_lint.py +145 -140
- package/lib/video-recap/skills/video-understanding/scripts/speech_ownership.py +39 -100
- package/lib/video-recap/skills/video-understanding/scripts/timeline_fusion.py +33 -124
- package/lib/video-recap/skills/video-understanding/scripts/vlm.py +1 -4
- package/lib/video-recap/skills/video-voiceover/scripts/dub.py +7 -13
- package/lib/video-recap/skills/video-voiceover/scripts/fish_audio.py +12 -30
- package/lib/video-recap/skills/video-voiceover/scripts/lib.py +98 -131
- package/lib/video-recap/skills/video-voiceover/scripts/tts_audio.py +17 -28
- package/lib/video-recap/skills/video-voiceover/scripts/voiceover.py +151 -240
- package/package.json +41 -41
|
@@ -11,7 +11,6 @@ from recap_runtime import (
|
|
|
11
11
|
_build_multi_source_records,
|
|
12
12
|
_coerce_videos,
|
|
13
13
|
_entry,
|
|
14
|
-
_file_fingerprint,
|
|
15
14
|
_material_settings_fingerprint,
|
|
16
15
|
_optional_env_int,
|
|
17
16
|
_preflight_burn_subtitles,
|
|
@@ -59,7 +58,7 @@ RUN_MANIFEST = "recap_run_manifest.json"
|
|
|
59
58
|
def _voiceover_args(work_dir, narration_path, args):
|
|
60
59
|
"""Build the shared full/cut invocation for video-voiceover."""
|
|
61
60
|
result = ["--work-dir", str(work_dir), "--narration", str(narration_path)]
|
|
62
|
-
if
|
|
61
|
+
if args.tts_provider != "auto":
|
|
63
62
|
result += ["--tts-provider", args.tts_provider]
|
|
64
63
|
if args.mimo_tts_voice:
|
|
65
64
|
result += ["--mimo-voice", args.mimo_tts_voice]
|
|
@@ -75,25 +74,23 @@ def _run_or_restore_understanding(source_record, source_work_dir, args):
|
|
|
75
74
|
source_work_dir = Path(source_work_dir)
|
|
76
75
|
source_work_dir.mkdir(parents=True, exist_ok=True)
|
|
77
76
|
source_fp = source_record["source_video_fingerprint"]
|
|
78
|
-
settings_fp = source_record
|
|
79
|
-
"settings_fingerprint"
|
|
80
|
-
) or _material_settings_fingerprint(args)
|
|
77
|
+
settings_fp = source_record["settings_fingerprint"]
|
|
81
78
|
lib_dir = _material_library_dir(args)
|
|
82
79
|
restored = False
|
|
83
|
-
if
|
|
80
|
+
if _materials_enabled(args):
|
|
84
81
|
result = material_lib.restore_material(
|
|
85
82
|
lib_dir,
|
|
86
83
|
source_work_dir,
|
|
87
84
|
source_fingerprint=source_fp,
|
|
88
85
|
settings_fp=settings_fp,
|
|
89
86
|
)
|
|
90
|
-
restored =
|
|
87
|
+
restored = result["restored"]
|
|
91
88
|
if restored:
|
|
92
89
|
print(
|
|
93
|
-
f"[video-recap] ♻️ 复用素材库: {result
|
|
90
|
+
f"[video-recap] ♻️ 复用素材库: {result['material_id']} → {source_work_dir}",
|
|
94
91
|
flush=True,
|
|
95
92
|
)
|
|
96
|
-
|
|
93
|
+
else:
|
|
97
94
|
print(
|
|
98
95
|
f"[video-recap] 素材库未复用 {source_record['source_name']}: {result['reason']}",
|
|
99
96
|
flush=True,
|
|
@@ -107,19 +104,19 @@ def _run_or_restore_understanding(source_record, source_work_dir, args):
|
|
|
107
104
|
)
|
|
108
105
|
|
|
109
106
|
_write_run_manifest(source_work_dir, source_record["source_path"], args)
|
|
110
|
-
if
|
|
107
|
+
if _save_materials_enabled(args):
|
|
111
108
|
meta = material_lib.save_material(
|
|
112
109
|
lib_dir,
|
|
113
110
|
source_work_dir,
|
|
114
111
|
source_record["source_path"],
|
|
115
112
|
source_fp,
|
|
116
113
|
settings_fp,
|
|
117
|
-
source_id=source_record
|
|
118
|
-
material_id=source_record
|
|
114
|
+
source_id=source_record["source_id"],
|
|
115
|
+
material_id=source_record["material_id"],
|
|
119
116
|
)
|
|
120
|
-
source_record["material_id"] = meta
|
|
117
|
+
source_record["material_id"] = meta["material_id"]
|
|
121
118
|
print(
|
|
122
|
-
f"[video-recap] 💾 已沉淀素材: {meta
|
|
119
|
+
f"[video-recap] 💾 已沉淀素材: {meta['material_id']} → {lib_dir}",
|
|
123
120
|
flush=True,
|
|
124
121
|
)
|
|
125
122
|
return restored
|
|
@@ -127,7 +124,7 @@ def _run_or_restore_understanding(source_record, source_work_dir, args):
|
|
|
127
124
|
|
|
128
125
|
def _single_source_record(video, args):
|
|
129
126
|
"""Identity + settings for the one source of a single-video run."""
|
|
130
|
-
fp =
|
|
127
|
+
fp = material_lib.file_fingerprint(video)
|
|
131
128
|
return {
|
|
132
129
|
"source_id": material_lib.source_id_from_fingerprint(fp),
|
|
133
130
|
"source_path": str(video),
|
|
@@ -209,9 +206,9 @@ def _run_multi_cut(videos, work_dir, args):
|
|
|
209
206
|
]
|
|
210
207
|
if args.target_duration:
|
|
211
208
|
crender += ["--target-duration", args.target_duration]
|
|
212
|
-
if
|
|
209
|
+
if args.allow_duration_drift:
|
|
213
210
|
crender.append("--allow-duration-drift")
|
|
214
|
-
if
|
|
211
|
+
if args.allow_sparse_cut:
|
|
215
212
|
crender.append("--allow-sparse-cut")
|
|
216
213
|
_run("video-cut", "cut.py", *crender)
|
|
217
214
|
cut_qc = _surface_cut_qc(work_dir)
|
|
@@ -301,10 +298,7 @@ def _run_multi_cut(videos, work_dir, args):
|
|
|
301
298
|
aargs.append("--jianying-no-bundle-media")
|
|
302
299
|
_run("video-assemble", "assemble.py", *aargs)
|
|
303
300
|
|
|
304
|
-
|
|
305
|
-
final_output = _read_assembly_output(work_dir) or (
|
|
306
|
-
final_dir / ("recap_" + recap_stem + ".mp4")
|
|
307
|
-
)
|
|
301
|
+
final_output = _read_assembly_output(work_dir)
|
|
308
302
|
_write_shift_left_stage_qc(
|
|
309
303
|
work_dir,
|
|
310
304
|
"post_render",
|
|
@@ -325,7 +319,7 @@ def main():
|
|
|
325
319
|
ap.error(
|
|
326
320
|
"MIMO_QC/--mimo-qc must be one of: off, pre-assemble, post-render, both"
|
|
327
321
|
)
|
|
328
|
-
if
|
|
322
|
+
if args.tts_provider not in TTS_PROVIDERS:
|
|
329
323
|
ap.error(
|
|
330
324
|
"TTS_PROVIDER/--tts-provider must be one of: auto, mimo-tts, fish-audio"
|
|
331
325
|
)
|
|
@@ -354,9 +348,7 @@ def main():
|
|
|
354
348
|
)
|
|
355
349
|
if explicit_mimo_voice and args.voice_ref:
|
|
356
350
|
ap.error("--mimo-tts-voice and --voice-ref are mutually exclusive")
|
|
357
|
-
if
|
|
358
|
-
explicit_mimo_voice or args.voice_ref
|
|
359
|
-
):
|
|
351
|
+
if args.tts_provider == "fish-audio" and (explicit_mimo_voice or args.voice_ref):
|
|
360
352
|
ap.error(
|
|
361
353
|
"--mimo-tts-voice/--voice-ref are only supported by --tts-provider mimo-tts"
|
|
362
354
|
)
|
|
@@ -532,9 +524,9 @@ def main():
|
|
|
532
524
|
crender = [str(video), "--work-dir", str(work_dir), "--no-narration-map"]
|
|
533
525
|
if args.target_duration:
|
|
534
526
|
crender += ["--target-duration", args.target_duration]
|
|
535
|
-
if
|
|
527
|
+
if args.allow_duration_drift:
|
|
536
528
|
crender.append("--allow-duration-drift")
|
|
537
|
-
if
|
|
529
|
+
if args.allow_sparse_cut:
|
|
538
530
|
crender.append("--allow-sparse-cut")
|
|
539
531
|
_run("video-cut", "cut.py", *crender)
|
|
540
532
|
cut_qc = _surface_cut_qc(work_dir)
|
|
@@ -638,10 +630,7 @@ def main():
|
|
|
638
630
|
aargs.append("--jianying-no-bundle-media")
|
|
639
631
|
_run("video-assemble", "assemble.py", *aargs)
|
|
640
632
|
|
|
641
|
-
|
|
642
|
-
final_output = _read_assembly_output(work_dir) or (
|
|
643
|
-
final_dir / ("recap_" + video.stem + ".mp4")
|
|
644
|
-
)
|
|
633
|
+
final_output = _read_assembly_output(work_dir)
|
|
645
634
|
_write_shift_left_stage_qc(
|
|
646
635
|
work_dir,
|
|
647
636
|
"post_render",
|
|
@@ -1,25 +1,17 @@
|
|
|
1
|
-
"""Provide subprocess, manifest,
|
|
2
|
-
|
|
3
|
-
import hashlib
|
|
1
|
+
"""Provide subprocess, manifest, and preflight runtime helpers."""
|
|
4
2
|
|
|
5
3
|
import json
|
|
6
|
-
|
|
7
4
|
import math
|
|
8
|
-
|
|
9
5
|
import os
|
|
10
|
-
|
|
11
6
|
import shlex
|
|
12
|
-
|
|
7
|
+
import shutil
|
|
13
8
|
import subprocess
|
|
14
|
-
|
|
15
9
|
import sys
|
|
16
|
-
|
|
17
10
|
from pathlib import Path
|
|
18
11
|
|
|
19
|
-
|
|
20
|
-
from doctor import ffmpeg_has_subtitles_filter
|
|
21
|
-
|
|
22
12
|
import materials as material_lib
|
|
13
|
+
from doctor import ffmpeg_has_subtitles_filter
|
|
14
|
+
from lib import env_bool, load_json
|
|
23
15
|
|
|
24
16
|
BUNDLE = Path(__file__).resolve().parents[2] # the skills/ directory
|
|
25
17
|
|
|
@@ -36,13 +28,6 @@ def _run(skill, script, *cli_args):
|
|
|
36
28
|
raise SystemExit(f"{skill}/{script} 失败 (exit {res.returncode})")
|
|
37
29
|
|
|
38
30
|
|
|
39
|
-
def _env_bool(name, default=False):
|
|
40
|
-
raw = os.environ.get(name)
|
|
41
|
-
if raw is None or raw == "":
|
|
42
|
-
return default
|
|
43
|
-
return raw.strip().lower() in {"1", "true", "yes", "y", "on"}
|
|
44
|
-
|
|
45
|
-
|
|
46
31
|
def _optional_env_int(name):
|
|
47
32
|
raw = os.environ.get(name)
|
|
48
33
|
if raw is None or raw.strip() == "":
|
|
@@ -73,8 +58,8 @@ def _read_video_duration_or_raise(path):
|
|
|
73
58
|
raise SystemExit(f"无法读取成片时长: {path} ({detail})")
|
|
74
59
|
try:
|
|
75
60
|
duration = float(res.stdout.strip())
|
|
76
|
-
except
|
|
77
|
-
raise SystemExit(f"无法读取成片时长: {path} (ffprobe 输出无效: {res.stdout!r})")
|
|
61
|
+
except ValueError:
|
|
62
|
+
raise SystemExit(f"无法读取成片时长: {path} (ffprobe 输出无效: {res.stdout!r})") from None
|
|
78
63
|
if not math.isfinite(duration) or duration <= 0:
|
|
79
64
|
raise SystemExit(f"无法读取成片时长: {path} (duration={duration:.3f})")
|
|
80
65
|
return duration
|
|
@@ -96,79 +81,35 @@ def _probe_display_height_or_raise(path, *, require_square_pixels=False):
|
|
|
96
81
|
]
|
|
97
82
|
res = subprocess.run(cmd, capture_output=True, text=True)
|
|
98
83
|
try:
|
|
99
|
-
stream =
|
|
84
|
+
stream = json.loads(res.stdout)["streams"][0]
|
|
100
85
|
width, height = int(stream["width"]), int(stream["height"])
|
|
101
|
-
except (IndexError, KeyError,
|
|
86
|
+
except (IndexError, KeyError, ValueError):
|
|
102
87
|
detail = (res.stderr or res.stdout or "ffprobe failed").strip()
|
|
103
|
-
raise SystemExit(f"无法读取视频画布: {path} ({detail})")
|
|
104
|
-
sar =
|
|
88
|
+
raise SystemExit(f"无法读取视频画布: {path} ({detail})") from None
|
|
89
|
+
sar = stream.get("sample_aspect_ratio", "1:1")
|
|
105
90
|
try:
|
|
106
91
|
num, den = sar.split(":", 1)
|
|
107
92
|
sar_ratio = float(num) / float(den)
|
|
108
|
-
|
|
109
|
-
except (TypeError, ValueError, ZeroDivisionError):
|
|
93
|
+
except (ValueError, ZeroDivisionError):
|
|
110
94
|
sar_ratio = math.nan
|
|
111
|
-
display_width = width
|
|
112
95
|
if require_square_pixels and (
|
|
113
96
|
not math.isfinite(sar_ratio) or abs(sar_ratio - 1.0) >= 1e-9
|
|
114
97
|
):
|
|
115
98
|
raise SystemExit(
|
|
116
99
|
f"subtitle Y coordinates currently require square-pixel video (SAR 1:1); got {sar}"
|
|
117
100
|
)
|
|
101
|
+
display_width = max(1, round(width * sar_ratio)) if math.isfinite(sar_ratio) else width
|
|
118
102
|
rotation_values = [
|
|
119
|
-
|
|
120
|
-
*(
|
|
121
|
-
item.get("rotation")
|
|
122
|
-
for item in stream.get("side_data_list") or []
|
|
123
|
-
if isinstance(item, dict)
|
|
124
|
-
),
|
|
103
|
+
stream.get("tags", {}).get("rotate"),
|
|
104
|
+
*(item.get("rotation") for item in stream.get("side_data_list", [])),
|
|
125
105
|
]
|
|
126
|
-
rotation =
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
rotation = int(round(float(value))) % 360
|
|
131
|
-
break
|
|
132
|
-
except (TypeError, ValueError):
|
|
133
|
-
pass
|
|
106
|
+
rotation = next(
|
|
107
|
+
(int(round(float(value))) % 360 for value in rotation_values if value not in (None, "")),
|
|
108
|
+
0,
|
|
109
|
+
)
|
|
134
110
|
return display_width if rotation in {90, 270} else height
|
|
135
111
|
|
|
136
112
|
|
|
137
|
-
_FILE_FINGERPRINT_MEMO = {}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
def _file_identity(path):
|
|
141
|
-
"""(device, inode, size, mtime_ns) — changes whenever the bytes could have changed."""
|
|
142
|
-
st = os.stat(os.fspath(path))
|
|
143
|
-
return (st.st_dev, st.st_ino, st.st_size, st.st_mtime_ns)
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
def _file_fingerprint(path, chunk_size=1024 * 1024):
|
|
147
|
-
"""Return a full-content fingerprint for cache-correct identity checks.
|
|
148
|
-
|
|
149
|
-
The digest covers CONTENT only — never the path or mtime — so a copied video or
|
|
150
|
-
artifact is still recognised as the same asset, while any byte change invalidates
|
|
151
|
-
the cache even if timestamps, size, head, or tail bytes are misleading.
|
|
152
|
-
|
|
153
|
-
Identity metadata is used ONLY to memoize within a single process. One understanding
|
|
154
|
-
run fingerprints the same source video 8-10 times and the whole extracted frame set
|
|
155
|
-
2-3 times; on a 40-minute video at fps=1 that is gigabytes of redundant reads before
|
|
156
|
-
any real work starts. A file rewritten in place gets a new (size, mtime_ns) and is
|
|
157
|
-
re-hashed, so the memo can never serve a stale digest.
|
|
158
|
-
"""
|
|
159
|
-
key = _file_identity(path)
|
|
160
|
-
memoized = _FILE_FINGERPRINT_MEMO.get(key)
|
|
161
|
-
if memoized is not None:
|
|
162
|
-
return memoized
|
|
163
|
-
h = hashlib.sha256()
|
|
164
|
-
with open(os.fspath(path), "rb") as f:
|
|
165
|
-
for chunk in iter(lambda: f.read(chunk_size), b""):
|
|
166
|
-
h.update(chunk)
|
|
167
|
-
digest = h.hexdigest()
|
|
168
|
-
_FILE_FINGERPRINT_MEMO[key] = digest
|
|
169
|
-
return digest
|
|
170
|
-
|
|
171
|
-
|
|
172
113
|
def _analysis_settings(args):
|
|
173
114
|
return {
|
|
174
115
|
"context": args.context,
|
|
@@ -197,7 +138,7 @@ def _run_manifest_payload(video, args):
|
|
|
197
138
|
return {
|
|
198
139
|
"schema_version": 1,
|
|
199
140
|
"source_video": str(Path(video).resolve()),
|
|
200
|
-
"source_video_fingerprint":
|
|
141
|
+
"source_video_fingerprint": material_lib.file_fingerprint(video),
|
|
201
142
|
"settings": _analysis_settings(args),
|
|
202
143
|
}
|
|
203
144
|
|
|
@@ -212,7 +153,7 @@ def _write_run_manifest(work_dir, video, args):
|
|
|
212
153
|
def _build_multi_source_records(videos, args):
|
|
213
154
|
records = []
|
|
214
155
|
for video in _coerce_videos(videos):
|
|
215
|
-
fp =
|
|
156
|
+
fp = material_lib.file_fingerprint(video)
|
|
216
157
|
records.append(
|
|
217
158
|
{
|
|
218
159
|
"source_path": str(video),
|
|
@@ -234,11 +175,11 @@ def _multi_run_manifest_payload(videos, args, source_records):
|
|
|
234
175
|
"mode": "multi_source",
|
|
235
176
|
"sources": [
|
|
236
177
|
{
|
|
237
|
-
"source_id": s
|
|
238
|
-
"source_path": s
|
|
239
|
-
"source_video_fingerprint": s
|
|
240
|
-
"source_work_dir": s
|
|
241
|
-
"material_id": s
|
|
178
|
+
"source_id": s["source_id"],
|
|
179
|
+
"source_path": s["source_path"],
|
|
180
|
+
"source_video_fingerprint": s["source_video_fingerprint"],
|
|
181
|
+
"source_work_dir": s["source_work_dir"],
|
|
182
|
+
"material_id": s["material_id"],
|
|
242
183
|
}
|
|
243
184
|
for s in source_records
|
|
244
185
|
],
|
|
@@ -265,7 +206,7 @@ def _write_multi_source_manifest(work_dir, source_records):
|
|
|
265
206
|
"source_name": s["source_name"],
|
|
266
207
|
"source_video_fingerprint": s["source_video_fingerprint"],
|
|
267
208
|
"source_work_dir": s["source_work_dir"],
|
|
268
|
-
"material_id": s
|
|
209
|
+
"material_id": s["material_id"],
|
|
269
210
|
}
|
|
270
211
|
for s in source_records
|
|
271
212
|
],
|
|
@@ -275,32 +216,18 @@ def _write_multi_source_manifest(work_dir, source_records):
|
|
|
275
216
|
|
|
276
217
|
|
|
277
218
|
def _load_run_manifest(work_dir):
|
|
219
|
+
"""The run manifest, or None before Phase A has written one."""
|
|
278
220
|
path = Path(work_dir) / RUN_MANIFEST
|
|
279
|
-
if
|
|
280
|
-
return None
|
|
281
|
-
try:
|
|
282
|
-
return json.loads(path.read_text(encoding="utf-8"))
|
|
283
|
-
except (OSError, ValueError, TypeError):
|
|
284
|
-
return None
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
def _load_json(path):
|
|
288
|
-
try:
|
|
289
|
-
return json.loads(Path(path).read_text(encoding="utf-8"))
|
|
290
|
-
except (OSError, ValueError, TypeError):
|
|
291
|
-
return None
|
|
221
|
+
return load_json(path) if path.exists() else None
|
|
292
222
|
|
|
293
223
|
|
|
294
224
|
def _burn_subtitles_intended(args):
|
|
295
225
|
"""Effective burn-subtitles state at orchestrator level. Mirrors video-assemble's
|
|
296
226
|
CONFIG default `env_bool("BURN_SUBTITLES", True)` (burn is ON by default); an explicit
|
|
297
227
|
CLI flag (--burn-subtitles / --no-burn-subtitles) overrides the env."""
|
|
298
|
-
if
|
|
299
|
-
return
|
|
300
|
-
|
|
301
|
-
if raw is None or raw == "":
|
|
302
|
-
return True
|
|
303
|
-
return raw.strip().lower() in {"1", "true", "yes", "y", "on"}
|
|
228
|
+
if args.burn_subtitles is not None:
|
|
229
|
+
return args.burn_subtitles
|
|
230
|
+
return env_bool("BURN_SUBTITLES", True)
|
|
304
231
|
|
|
305
232
|
|
|
306
233
|
def _ffmpeg_present_but_cannot_burn():
|
|
@@ -309,8 +236,6 @@ def _ffmpeg_present_but_cannot_burn():
|
|
|
309
236
|
entirely: that is a more fundamental problem that surfaces at the first stage (understand
|
|
310
237
|
calls ffprobe/ffmpeg) and is reported by doctor, so this guard stays narrow — and does
|
|
311
238
|
not fire in mocked, ffmpeg-less test environments."""
|
|
312
|
-
import shutil
|
|
313
|
-
|
|
314
239
|
if shutil.which("ffmpeg") is None:
|
|
315
240
|
return False
|
|
316
241
|
return not ffmpeg_has_subtitles_filter()
|
|
@@ -1,126 +1,71 @@
|
|
|
1
1
|
"""Write shift-left, MiMo, and final QC stage reports."""
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
|
-
|
|
5
|
-
|
|
6
4
|
from pathlib import Path
|
|
7
5
|
|
|
8
|
-
import qc_contract
|
|
9
|
-
|
|
10
6
|
import final_qc
|
|
11
|
-
|
|
12
7
|
import mimo_qc
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
from recap_runtime import _load_json
|
|
8
|
+
import qc_contract
|
|
16
9
|
|
|
17
10
|
ASSEMBLY_MANIFEST = "assembly_manifest.json"
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def _json_safe(value):
|
|
21
|
-
"""Return a JSON-serializable, secret-redacted copy for local QC metadata."""
|
|
22
|
-
|
|
23
|
-
def convert(item):
|
|
24
|
-
if isinstance(item, Path):
|
|
25
|
-
return str(item)
|
|
26
|
-
if isinstance(item, dict):
|
|
27
|
-
return {str(k): convert(v) for k, v in item.items()}
|
|
28
|
-
if isinstance(item, (list, tuple)):
|
|
29
|
-
return [convert(v) for v in item]
|
|
30
|
-
return item
|
|
31
|
-
|
|
32
|
-
return qc_contract.redact_secrets(convert(value))
|
|
11
|
+
PREFLIGHT_QC = "preflight_qc.json"
|
|
33
12
|
|
|
34
13
|
|
|
35
14
|
def _load_preflight_stage_reports(work_dir):
|
|
36
|
-
path = Path(work_dir) /
|
|
15
|
+
path = Path(work_dir) / PREFLIGHT_QC
|
|
37
16
|
if not path.exists():
|
|
38
17
|
return {}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
stages = None
|
|
44
|
-
metadata = data.get("metadata")
|
|
45
|
-
if isinstance(metadata, dict) and isinstance(metadata.get("stages"), dict):
|
|
46
|
-
stages = metadata.get("stages")
|
|
47
|
-
elif isinstance(data.get("stages"), dict):
|
|
48
|
-
stages = data.get("stages")
|
|
49
|
-
if stages is None:
|
|
50
|
-
return {data["stage"]: data}
|
|
51
|
-
stage_reports = {}
|
|
52
|
-
for stage, report in stages.items():
|
|
53
|
-
if not isinstance(report, dict):
|
|
54
|
-
raise qc_contract.QCContractError(
|
|
55
|
-
f"preflight stage report must be an object: {stage}"
|
|
56
|
-
)
|
|
57
|
-
qc_contract.validate_report(report)
|
|
58
|
-
if report.get("stage") != stage:
|
|
59
|
-
raise qc_contract.QCContractError(
|
|
60
|
-
f"preflight stage key does not match report stage: {stage}"
|
|
61
|
-
)
|
|
62
|
-
stage_reports[stage] = report
|
|
63
|
-
return stage_reports
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
def _write_shift_left_stage_qc(work_dir, stage, metadata=None, findings=None):
|
|
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):
|
|
67
22
|
"""Write/roll up local shift-left QC for one pipeline stage.
|
|
68
23
|
|
|
69
|
-
This is a local contract artifact only: no MiMo/deep eval calls, no repair, and
|
|
70
|
-
|
|
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).
|
|
71
26
|
"""
|
|
72
|
-
work_dir = Path(work_dir)
|
|
73
|
-
path = work_dir / "preflight_qc.json"
|
|
74
27
|
stage_report = qc_contract.build_report(
|
|
75
|
-
artifact=
|
|
76
|
-
stage=stage,
|
|
77
|
-
findings=findings or [],
|
|
78
|
-
metadata=_json_safe(metadata or {}),
|
|
28
|
+
artifact=PREFLIGHT_QC, stage=stage, findings=findings, metadata=metadata
|
|
79
29
|
)
|
|
80
30
|
stage_reports = _load_preflight_stage_reports(work_dir)
|
|
81
31
|
stage_reports[stage] = stage_report
|
|
82
|
-
|
|
83
|
-
for report in stage_reports.values():
|
|
84
|
-
aggregate_findings.extend(dict(f) for f in report.get("findings", []))
|
|
85
|
-
top_metadata = dict(stage_report.get("metadata") or {})
|
|
32
|
+
top_metadata = dict(stage_report["metadata"])
|
|
86
33
|
top_metadata["latest_stage"] = stage
|
|
87
34
|
top_metadata["stages"] = stage_reports
|
|
88
35
|
top_report = qc_contract.build_report(
|
|
89
|
-
artifact=
|
|
36
|
+
artifact=PREFLIGHT_QC,
|
|
90
37
|
stage=stage,
|
|
91
|
-
findings=
|
|
92
|
-
metadata=
|
|
38
|
+
findings=[f for report in stage_reports.values() for f in report["findings"]],
|
|
39
|
+
metadata=top_metadata,
|
|
93
40
|
)
|
|
94
|
-
|
|
95
|
-
path.write_text(
|
|
41
|
+
(Path(work_dir) / PREFLIGHT_QC).write_text(
|
|
96
42
|
json.dumps(top_report, ensure_ascii=False, indent=2) + "\n", encoding="utf-8"
|
|
97
43
|
)
|
|
98
|
-
qc_contract.validate_report(_load_json(path))
|
|
99
44
|
return top_report
|
|
100
45
|
|
|
101
46
|
|
|
102
47
|
def _tts_qc_metadata(work_dir):
|
|
103
48
|
work_dir = Path(work_dir)
|
|
104
49
|
metadata = {}
|
|
105
|
-
tts_meta =
|
|
106
|
-
if tts_meta
|
|
107
|
-
metadata["tts_meta"] = tts_meta
|
|
50
|
+
tts_meta = work_dir / "tts_meta.json"
|
|
51
|
+
if tts_meta.exists():
|
|
52
|
+
metadata["tts_meta"] = json.loads(tts_meta.read_text(encoding="utf-8"))
|
|
108
53
|
tts_dir = work_dir / "tts_segments"
|
|
109
|
-
if tts_dir.
|
|
54
|
+
if tts_dir.is_dir():
|
|
110
55
|
metadata["tts_segments"] = [
|
|
111
|
-
p.relative_to(work_dir).as_posix()
|
|
112
|
-
for p in sorted(tts_dir.iterdir())
|
|
113
|
-
if p.is_file()
|
|
56
|
+
p.relative_to(work_dir).as_posix() for p in sorted(tts_dir.iterdir()) if p.is_file()
|
|
114
57
|
]
|
|
115
58
|
return metadata
|
|
116
59
|
|
|
117
60
|
|
|
118
61
|
def _post_render_qc_metadata(work_dir, final_output):
|
|
119
62
|
work_dir = Path(work_dir)
|
|
120
|
-
metadata = {"final_output": str(final_output)
|
|
121
|
-
|
|
122
|
-
if
|
|
123
|
-
metadata["assembly_manifest"] =
|
|
63
|
+
metadata = {"final_output": str(final_output)}
|
|
64
|
+
manifest = work_dir / ASSEMBLY_MANIFEST
|
|
65
|
+
if manifest.exists():
|
|
66
|
+
metadata["assembly_manifest"] = json.loads(
|
|
67
|
+
manifest.read_text(encoding="utf-8")
|
|
68
|
+
)
|
|
124
69
|
return metadata
|
|
125
70
|
|
|
126
71
|
|
|
@@ -136,13 +81,11 @@ def _write_final_qc_reports(work_dir, final_output):
|
|
|
136
81
|
def _print_final_qc_pointer(result):
|
|
137
82
|
"""Surface a report-only final_qc/golden_eval FAIL so the shift-left QC is
|
|
138
83
|
not a silent no-op. Advisory only: it never changes the exit status."""
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
if isinstance(section, dict) and section.get("ok") is False:
|
|
145
|
-
problems.append(f"{key} blocker_count={section.get('blocker_count', '?')}")
|
|
84
|
+
problems = [
|
|
85
|
+
f"{key} blocker_count={result[key].get('blocker_count', '?')}"
|
|
86
|
+
for key in ("final_qc", "golden_eval")
|
|
87
|
+
if result[key].get("ok") is False
|
|
88
|
+
]
|
|
146
89
|
if problems:
|
|
147
90
|
print(
|
|
148
91
|
"[video-recap] ⚠️ 最终 QC 未通过(仅报告,不阻断): "
|
|
@@ -152,47 +95,34 @@ def _print_final_qc_pointer(result):
|
|
|
152
95
|
|
|
153
96
|
|
|
154
97
|
def _mimo_qc_stage_enabled(args, stage):
|
|
155
|
-
mode = getattr(args, "mimo_qc", "off") or "off"
|
|
156
98
|
return (
|
|
157
|
-
|
|
158
|
-
or (
|
|
159
|
-
or (
|
|
99
|
+
args.mimo_qc == "both"
|
|
100
|
+
or (args.mimo_qc == "pre-assemble" and stage == "pre_assemble")
|
|
101
|
+
or (args.mimo_qc == "post-render" and stage == "post_render")
|
|
160
102
|
)
|
|
161
103
|
|
|
162
104
|
|
|
163
105
|
def _prepare_mimo_qc(work_dir, args):
|
|
164
106
|
"""Remove an old advisory artifact when this run has MiMo QC disabled."""
|
|
165
|
-
if
|
|
107
|
+
if args.mimo_qc == "off":
|
|
166
108
|
mimo_qc.clear_report(work_dir)
|
|
167
109
|
|
|
168
110
|
|
|
169
111
|
def _print_mimo_qc_pointer(result, stage):
|
|
170
|
-
report = result
|
|
171
|
-
metadata = report
|
|
172
|
-
status = metadata
|
|
173
|
-
|
|
174
|
-
result.get("path", "mimo_qc.json")
|
|
175
|
-
if isinstance(result, dict)
|
|
176
|
-
else "mimo_qc.json"
|
|
177
|
-
)
|
|
178
|
-
stage_findings = [
|
|
179
|
-
finding
|
|
180
|
-
for finding in report.get("findings", [])
|
|
181
|
-
if isinstance(finding, dict) and finding.get("stage") == stage
|
|
182
|
-
]
|
|
112
|
+
report = result["report"]
|
|
113
|
+
metadata = report["metadata"]
|
|
114
|
+
status = metadata["status"]
|
|
115
|
+
stage_findings = [f for f in report["findings"] if f["stage"] == stage]
|
|
183
116
|
if status in {"failed", "unavailable"}:
|
|
184
|
-
reason = metadata.get("error") or "unavailable"
|
|
185
117
|
print(
|
|
186
|
-
f"[video-recap] ⚠ MiMo QC {stage}: {status} ({
|
|
118
|
+
f"[video-recap] ⚠ MiMo QC {stage}: {status} ({metadata['error']});建议性检查不可用,继续流水线"
|
|
187
119
|
)
|
|
188
120
|
return
|
|
189
121
|
print(
|
|
190
|
-
f"[video-recap] ℹ MiMo QC {stage}: {status}, {len(stage_findings)} 条建议;详见 {path}"
|
|
122
|
+
f"[video-recap] ℹ MiMo QC {stage}: {status}, {len(stage_findings)} 条建议;详见 {result['path']}"
|
|
191
123
|
)
|
|
192
124
|
for finding in stage_findings[:5]:
|
|
193
|
-
print(
|
|
194
|
-
f"[video-recap] - {finding.get('message') or finding.get('decision_reason')}"
|
|
195
|
-
)
|
|
125
|
+
print(f"[video-recap] - {finding['message']}")
|
|
196
126
|
|
|
197
127
|
|
|
198
128
|
def _run_mimo_qc_stage(work_dir, args, stage, *, final_output=None):
|
|
@@ -204,7 +134,7 @@ def _run_mimo_qc_stage(work_dir, args, stage, *, final_output=None):
|
|
|
204
134
|
work_dir,
|
|
205
135
|
stage=stage,
|
|
206
136
|
live=True,
|
|
207
|
-
refresh=
|
|
137
|
+
refresh=args.mimo_qc_refresh,
|
|
208
138
|
final_output=final_output,
|
|
209
139
|
)
|
|
210
140
|
except Exception as exc:
|