@oh-story/dsh 0.1.9 → 0.1.11
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 +14 -14
- package/lib/client.js +209 -73
- package/lib/client.js.map +3 -3
- package/lib/drama/manifest.json +248 -168
- package/lib/drama/skills/short-drama/SKILL.md +16 -4
- package/lib/drama/skills/short-drama/assets/project-template/short-drama.json +3 -0
- package/lib/drama/skills/short-drama/references/creator-documents.md +3 -0
- package/lib/drama/skills/short-drama/references/knowhow-index.md +15 -0
- package/lib/drama/skills/short-drama/scripts/creator_markdown_check.py +14 -0
- package/lib/drama/skills/short-drama/scripts/project_tool.py +131 -0
- package/lib/drama/skills/short-drama-assets/SKILL.md +2 -1
- package/lib/drama/skills/short-drama-assets/references/asset-review-checklist.md +2 -0
- package/lib/drama/skills/short-drama-assets/references/character-and-look.md +10 -0
- package/lib/drama/skills/short-drama-assets/references/continuity-lock.md +6 -9
- package/lib/drama/skills/short-drama-assets/references/era-anchors.md +37 -0
- package/lib/drama/skills/short-drama-assets/references/stage-contract.md +2 -0
- package/lib/drama/skills/short-drama-assets/references/voice-direction.md +28 -0
- package/lib/drama/skills/short-drama-develop/SKILL.md +10 -4
- package/lib/drama/skills/short-drama-develop/assets/adaptation-map.example.jsonl +1 -1
- package/lib/drama/skills/short-drama-develop/assets/creative-brief.md +8 -0
- package/lib/drama/skills/short-drama-develop/references/adaptation-craft.md +80 -13
- package/lib/drama/skills/short-drama-develop/references/episode-design.md +95 -12
- package/lib/drama/skills/short-drama-develop/references/genre-and-hook-playbook.md +9 -5
- package/lib/drama/skills/short-drama-develop/references/genre-cards//345/244/215/344/273/207/346/211/223/350/204/270.md +1 -1
- package/lib/drama/skills/short-drama-develop/references/genre-cards//347/263/273/347/273/237/346/265/201.md +51 -0
- package/lib/drama/skills/short-drama-develop/references/genre-cards//351/200/206/350/242/255.md +51 -0
- package/lib/drama/skills/short-drama-develop/references/genre-cards//351/207/215/347/224/237/347/251/277/350/266/212.md +51 -0
- package/lib/drama/skills/short-drama-develop/references/genre-cards.md +10 -7
- package/lib/drama/skills/short-drama-develop/references/premise-devices.md +15 -9
- package/lib/drama/skills/short-drama-develop/references/serial-character-and-memory.md +3 -2
- package/lib/drama/skills/short-drama-develop/references/stage-contract.md +14 -7
- package/lib/drama/skills/short-drama-develop/references/story-craft.md +7 -3
- package/lib/drama/skills/short-drama-edit/SKILL.md +65 -44
- package/lib/drama/skills/short-drama-edit/assets/remotion/README.md +49 -13
- package/lib/drama/skills/short-drama-edit/assets/remotion/package.json +4 -2
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/Overlay.tsx +31 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/Root.tsx +5 -5
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/Subtitles.tsx +68 -69
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/font.ts +39 -29
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/rules.mjs +188 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/schema.ts +26 -13
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/Card.tsx +72 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/CornerChip.tsx +39 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/ScreenText.tsx +36 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/SystemPanel.tsx +44 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/TaskPanel.tsx +100 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/chrome.tsx +197 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/tokens.ts +62 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/tsconfig.json +1 -0
- package/lib/drama/skills/short-drama-edit/assets//345/211/252/350/276/221/345/215/225.md +6 -4
- package/lib/drama/skills/short-drama-edit/references/cut-craft.md +26 -67
- package/lib/drama/skills/short-drama-edit/references/delivery-verify.md +31 -56
- package/lib/drama/skills/short-drama-edit/references/generated-footage.md +42 -78
- package/lib/drama/skills/short-drama-edit/references/sound-and-subtitles.md +44 -92
- package/lib/drama/skills/short-drama-edit/references/stage-contract.md +14 -16
- package/lib/drama/skills/short-drama-edit/scripts/edit_tool.py +1272 -125
- package/lib/drama/skills/short-drama-edit/scripts/selftest.py +113 -9
- package/lib/drama/skills/short-drama-image-prompts/SKILL.md +2 -0
- package/lib/drama/skills/short-drama-image-prompts/references/common-recipe.md +11 -0
- package/lib/drama/skills/short-drama-novel-analyze/SKILL.md +3 -3
- package/lib/drama/skills/short-drama-novel-analyze/assets/episode-candidate.example.jsonl +2 -2
- package/lib/drama/skills/short-drama-novel-analyze/references/adaptation-triage.md +8 -4
- package/lib/drama/skills/short-drama-novel-analyze/references/adaptation-value.md +10 -3
- package/lib/drama/skills/short-drama-novel-analyze/references/aggregation-and-entities.md +25 -1
- package/lib/drama/skills/short-drama-novel-analyze/references/stage-contract.md +1 -0
- package/lib/drama/skills/short-drama-produce/SKILL.md +5 -1
- package/lib/drama/skills/short-drama-produce/references/adapter-contract.md +14 -20
- package/lib/drama/skills/short-drama-produce/references/providers/minimax-h3-video.md +8 -26
- package/lib/drama/skills/short-drama-produce/references/providers/minimax-speech.md +41 -0
- package/lib/drama/skills/short-drama-produce/scripts/provider_adapters.py +142 -1
- package/lib/drama/skills/short-drama-review/SKILL.md +4 -5
- package/lib/drama/skills/short-drama-review/references/production-quality-gates.md +6 -19
- package/lib/drama/skills/short-drama-review/references/review-method.md +6 -1
- package/lib/drama/skills/short-drama-review/references/rubric-assets-prompts.md +11 -0
- package/lib/drama/skills/short-drama-review/references/rubric-story-script.md +77 -3
- package/lib/drama/skills/short-drama-review/references/rubric-visual-motion.md +24 -35
- package/lib/drama/skills/short-drama-review/references/stage-contract.md +3 -0
- package/lib/drama/skills/short-drama-storyboard/SKILL.md +23 -11
- package/lib/drama/skills/short-drama-storyboard/references/blocking-playbooks.md +1 -1
- package/lib/drama/skills/short-drama-storyboard/references/lighting-craft.md +39 -0
- package/lib/drama/skills/short-drama-storyboard/references/production-shot-grammar.md +2 -2
- package/lib/drama/skills/short-drama-storyboard/references/shot-craft.md +139 -34
- package/lib/drama/skills/short-drama-storyboard/references/stage-contract.md +5 -3
- package/lib/drama/skills/short-drama-video-prompts/SKILL.md +15 -12
- package/lib/drama/skills/short-drama-video-prompts/references/camera-audio-continuity.md +29 -0
- package/lib/drama/skills/short-drama-video-prompts/references/delivery-profile.md +4 -5
- package/lib/drama/skills/short-drama-video-prompts/references/generability.md +4 -2
- package/lib/drama/skills/short-drama-video-prompts/references/minimax-h3.md +52 -39
- package/lib/drama/skills/short-drama-video-prompts/references/motion-recipe.md +24 -21
- package/lib/drama/skills/short-drama-video-prompts/references/performance-action-timing.md +9 -23
- package/lib/drama/skills/short-drama-video-prompts/references/production-prompt-grammar.md +14 -26
- package/lib/drama/skills/short-drama-video-prompts/references/review-and-fixtures.md +1 -1
- package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.0.md +2 -3
- package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.5.md +34 -9
- package/lib/drama/skills/short-drama-video-prompts/references/stage-contract.md +3 -2
- package/lib/drama/skills/short-drama-video-prompts/references/target-model-profile.md +8 -1
- package/lib/drama/skills/short-drama-video-prompts/references/wan-3.0.md +103 -0
- package/lib/drama/skills/short-drama-video-prompts/scripts/motion_timing_check.py +8 -7
- package/lib/drama/skills/short-drama-write/SKILL.md +10 -7
- package/lib/drama/skills/short-drama-write/references/dialogue-craft.md +1 -1
- package/lib/drama/skills/short-drama-write/references/script-craft.md +56 -3
- package/lib/drama/skills/short-drama-write/references/stage-contract.md +4 -0
- package/lib/drama/skills/short-drama-write/scripts/duration_estimate.py +48 -2
- package/lib/index.js +346 -98
- package/lib/index.js.map +4 -4
- package/lib/novel-to-game/README.md +299 -59
- package/lib/novel-to-game/README_ZH.md +299 -56
- package/lib/novel-to-game/VERSION +1 -1
- package/lib/novel-to-game/examples/jin-ping-mei/build/BUILD_BRIEF.md +3 -1
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/RUN.md +11 -3
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/css/new-guofeng.css +397 -8
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/css/style.css +0 -31
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/data.js +71 -73
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/engine.js +78 -89
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/main.js +37 -14
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/text.js +5 -5
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/package.json +2 -1
- package/lib/novel-to-game/examples/jin-ping-mei/qa/README.md +11 -16
- package/lib/novel-to-game/examples/jin-ping-mei/qa/verification.json +11 -11
- package/lib/novel-to-game/examples/jin-ping-mei/qa/verify_playable_model.mjs +23 -0
- package/lib/novel-to-game/manifest.json +77 -87
- package/lib/novel-to-game/skills/game-art-direction/SKILL.md +7 -12
- package/lib/novel-to-game/skills/game-art-direction/references/art-direction-method.md +27 -106
- package/lib/novel-to-game/skills/game-build/SKILL.md +13 -14
- package/lib/novel-to-game/skills/game-build/references/blender-asset-workflow.md +24 -0
- package/lib/novel-to-game/skills/game-build/references/build-brief-contract.md +45 -82
- package/lib/novel-to-game/skills/game-build/references/generative-media-pipeline.md +14 -45
- package/lib/novel-to-game/skills/game-build/references/playable-model-contract.md +30 -65
- package/lib/novel-to-game/skills/game-build/references/tts-production-contract.md +13 -27
- package/lib/novel-to-game/skills/game-concept/SKILL.md +13 -15
- package/lib/novel-to-game/skills/game-concept/references/concept-method.md +28 -75
- package/lib/novel-to-game/skills/game-qa/SKILL.md +6 -7
- package/lib/novel-to-game/skills/game-qa/references/test-design-method.md +9 -5
- package/lib/novel-to-game/skills/game-world-design/SKILL.md +12 -16
- package/lib/novel-to-game/skills/game-world-design/references/narrative-design-method.md +23 -72
- package/lib/novel-to-game/skills/game-world-design/references/numeric-design-method.md +14 -48
- package/lib/novel-to-game/skills/game-world-design/references/world-design-method.md +36 -103
- package/lib/novel-to-game/skills/novel-game-analyze/SKILL.md +4 -10
- package/lib/novel-to-game/skills/novel-game-analyze/references/gameability-protocol.md +4 -4
- package/lib/novel-to-game/skills/novel-to-game/SKILL.md +9 -21
- package/lib/novel-to-game/skills/novel-to-game/references/intake-method.md +5 -4
- package/lib/novel-to-game/skills/novel-to-game/references/pipeline-contract.md +5 -8
- package/lib/oh-story/manifest.json +607 -327
- package/lib/oh-story/roles/chapter-extractor.md +177 -237
- package/lib/oh-story/roles/character-designer.md +1 -2
- package/lib/oh-story/roles/consistency-checker.md +15 -13
- package/lib/oh-story/roles/narrative-writer.md +36 -38
- package/lib/oh-story/roles/story-architect.md +9 -61
- package/lib/oh-story/roles/story-explorer.md +20 -125
- package/lib/oh-story/roles/story-researcher.md +0 -1
- package/lib/oh-story/skills/story/SKILL.md +15 -9
- package/lib/oh-story/skills/story/VERSION +1 -1
- package/lib/oh-story/skills/story/references/author-memory-maintenance.md +75 -0
- package/lib/oh-story/skills/story/references/author-memory.md +46 -94
- package/lib/oh-story/skills/story/scripts/author_memory_commit.py +934 -170
- package/lib/oh-story/skills/story-cover/SKILL.md +10 -1
- package/lib/oh-story/skills/story-deslop/SKILL.md +59 -73
- package/lib/oh-story/skills/story-deslop/references/agent-calls.md +5 -0
- package/lib/oh-story/skills/story-deslop/references/anti-ai-writing.md +12 -11
- package/lib/oh-story/skills/story-deslop/references/author-memory-maintenance.md +75 -0
- package/lib/oh-story/skills/story-deslop/references/author-memory.md +46 -94
- package/lib/oh-story/skills/story-deslop/references/banned-words.md +2 -2
- package/lib/oh-story/skills/story-deslop/references/deslop-gates.md +22 -5
- package/lib/oh-story/skills/story-deslop/references/style-resolution.md +4 -4
- package/lib/oh-story/skills/story-deslop/scripts/author_memory_commit.py +934 -170
- package/lib/oh-story/skills/story-deslop/scripts/check-ai-patterns.js +155 -8
- package/lib/oh-story/skills/story-import/SKILL.md +64 -584
- package/lib/oh-story/skills/story-import/references/character-state-reverse.md +1 -1
- package/lib/oh-story/skills/story-import/references/deep-analysis.md +47 -0
- package/lib/oh-story/skills/story-import/references/format-and-structure.md +1 -1
- package/lib/oh-story/skills/story-import/references/import-report.md +64 -0
- package/lib/oh-story/skills/story-import/references/import-special-cases.md +34 -0
- package/lib/oh-story/skills/story-import/references/import-tracking.md +95 -0
- package/lib/oh-story/skills/story-import/references/length-routing.md +1 -1
- package/lib/oh-story/skills/story-import/references/outline-reverse-rules.md +79 -0
- package/lib/oh-story/skills/story-import/references/outline-reverse.md +24 -0
- package/lib/oh-story/skills/story-import/references/state-tracking.md +4 -69
- package/lib/oh-story/skills/story-import/references/structure-mapping-long.md +98 -92
- package/lib/oh-story/skills/story-import/references/structure-mapping-short.md +24 -12
- package/lib/oh-story/skills/story-import/references/tracking-initialization.md +1 -1
- package/lib/oh-story/skills/story-import/references/tracking-transaction.md +15 -11
- package/lib/oh-story/skills/story-import/scripts/build_outline_brief.py +332 -0
- package/lib/oh-story/skills/story-import/scripts/check-outline-contract.js +132 -27
- package/lib/oh-story/skills/story-import/scripts/tracking_commit.py +478 -15
- package/lib/oh-story/skills/story-import/scripts/wordcount_core.py +182 -23
- package/lib/oh-story/skills/story-long-analyze/SKILL.md +81 -270
- package/lib/oh-story/skills/story-long-analyze/references/author-facing.md +252 -0
- package/lib/oh-story/skills/story-long-analyze/references/deconstruction-notes.md +3 -3
- package/lib/oh-story/skills/story-long-analyze/references/final-checks.md +19 -0
- package/lib/oh-story/skills/story-long-analyze/references/index-rebuild.md +11 -0
- package/lib/oh-story/skills/story-long-analyze/references/inspiration-library.md +105 -0
- package/lib/oh-story/skills/story-long-analyze/references/pipeline-ops.md +138 -64
- package/lib/oh-story/skills/story-long-analyze/references/semantic-acceptance-fixtures.md +84 -0
- package/lib/oh-story/skills/story-long-analyze/references/stage1-golden-chapters.md +128 -0
- package/lib/oh-story/skills/story-long-analyze/references/stage2-extraction.md +229 -0
- package/lib/oh-story/skills/story-long-analyze/references/stage3-plot-rhythm.md +480 -0
- package/lib/oh-story/skills/story-long-analyze/references/stage4-characters-settings.md +195 -0
- package/lib/oh-story/skills/story-long-analyze/references/stage5-report.md +33 -0
- package/lib/oh-story/skills/story-long-analyze/references/style-profile-generator.md +55 -128
- package/lib/oh-story/skills/story-long-analyze/references/style-profile-protocol.md +6 -6
- package/lib/oh-story/skills/story-long-analyze/references/synthesis-inputs.md +39 -0
- package/lib/oh-story/skills/story-long-analyze/scripts/build_chapter_index.py +466 -0
- package/lib/oh-story/skills/story-long-analyze/scripts/inspect_existing_assets.py +702 -0
- package/lib/oh-story/skills/story-long-analyze/scripts/inspiration_index.py +864 -0
- package/lib/oh-story/skills/story-long-analyze/scripts/manage_analysis_run.py +1145 -0
- package/lib/oh-story/skills/story-long-analyze/scripts/render_relation_chart.py +425 -0
- package/lib/oh-story/skills/story-long-scan/SKILL.md +45 -194
- package/lib/oh-story/skills/story-long-scan/references/platform-ciweimao.md +19 -0
- package/lib/oh-story/skills/story-long-scan/references/platform-fanqie.md +45 -0
- package/lib/oh-story/skills/story-long-scan/references/platform-jjwxc.md +28 -0
- package/lib/oh-story/skills/story-long-scan/references/platform-qidian.md +35 -0
- package/lib/oh-story/skills/story-long-scan/references/platform-qimao.md +27 -0
- package/lib/oh-story/skills/story-long-scan/references/topic-decision.md +6 -2
- package/lib/oh-story/skills/story-long-write/SKILL.md +49 -81
- package/lib/oh-story/skills/story-long-write/references/agent-calls.md +47 -0
- package/lib/oh-story/skills/story-long-write/references/anti-ai-writing.md +12 -11
- package/lib/oh-story/skills/story-long-write/references/artifact-protocols.md +24 -202
- package/lib/oh-story/skills/story-long-write/references/author-memory-maintenance.md +75 -0
- package/lib/oh-story/skills/story-long-write/references/author-memory.md +46 -94
- package/lib/oh-story/skills/story-long-write/references/banned-words.md +2 -2
- package/lib/oh-story/skills/story-long-write/references/benchmark-recall.md +7 -11
- package/lib/oh-story/skills/story-long-write/references/character-relations.md +49 -1
- package/lib/oh-story/skills/story-long-write/references/cross-book-recall.md +26 -0
- package/lib/oh-story/skills/story-long-write/references/deslop-gates.md +171 -0
- package/lib/oh-story/skills/story-long-write/references/dialogue-mastery.md +4 -0
- package/lib/oh-story/skills/story-long-write/references/emotion-on-page.md +116 -0
- package/lib/oh-story/skills/story-long-write/references/emotional-arc-design.md +1 -114
- package/lib/oh-story/skills/story-long-write/references/emotional-methods.md +2 -2
- package/lib/oh-story/skills/story-long-write/references/genre-prose-cards.md +1 -1
- package/lib/oh-story/skills/story-long-write/references/long-chapter-quality.md +7 -15
- package/lib/oh-story/skills/story-long-write/references/long-format.md +0 -8
- package/lib/oh-story/skills/story-long-write/references/long-genre-catalog.md +1 -1
- package/lib/oh-story/skills/story-long-write/references/long-genre-mechanics.md +3 -3
- package/lib/oh-story/skills/story-long-write/references/opening-design.md +2 -2
- package/lib/oh-story/skills/story-long-write/references/outline-conflict.md +4 -4
- package/lib/oh-story/skills/story-long-write/references/outline-methods.md +7 -7
- package/lib/oh-story/skills/story-long-write/references/outline-rhythm.md +3 -3
- package/lib/oh-story/skills/story-long-write/references/outline-structure-theory.md +14 -69
- package/lib/oh-story/skills/story-long-write/references/plot-frameworks.md +1 -1
- package/lib/oh-story/skills/story-long-write/references/plot-special-topics.md +2 -2
- package/lib/oh-story/skills/story-long-write/references/project-files.md +19 -13
- package/lib/oh-story/skills/story-long-write/references/reader-contract-and-progression.md +13 -5
- package/lib/oh-story/skills/story-long-write/references/reference-index.md +5 -6
- package/lib/oh-story/skills/story-long-write/references/stop-and-fix.md +9 -0
- package/lib/oh-story/skills/story-long-write/references/style-combat-face.md +1 -58
- 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 +4 -4
- package/lib/oh-story/skills/story-long-write/references/tracking-initialization.md +1 -1
- package/lib/oh-story/skills/story-long-write/references/tracking-transaction.md +15 -11
- package/lib/oh-story/skills/story-long-write/references/workflow-chapter.md +81 -121
- package/lib/oh-story/skills/story-long-write/references/workflow-daily.md +41 -139
- package/lib/oh-story/skills/story-long-write/references/workflow-outline.md +157 -0
- package/lib/oh-story/skills/story-long-write/references/workflow-revision.md +57 -67
- package/lib/oh-story/skills/story-long-write/references/workflow-setup.md +112 -253
- package/lib/oh-story/skills/story-long-write/references/workflow-volume.md +142 -0
- package/lib/oh-story/skills/story-long-write/references/writer-prompt-fallback.md +6 -4
- package/lib/oh-story/skills/story-long-write/references/writing-craft.md +19 -65
- package/lib/oh-story/skills/story-long-write/scripts/author_memory_commit.py +934 -170
- package/lib/oh-story/skills/story-long-write/scripts/build_architect_brief.py +260 -0
- package/lib/oh-story/skills/story-long-write/scripts/build_writer_prompt.py +226 -25
- package/lib/oh-story/skills/story-long-write/scripts/check-ai-patterns.js +155 -8
- package/lib/oh-story/skills/story-long-write/scripts/check-outline-contract.js +132 -27
- package/lib/oh-story/skills/story-long-write/scripts/check-outline-copy.js +7 -2
- package/lib/oh-story/skills/story-long-write/scripts/outline_view.py +7 -4
- package/lib/oh-story/skills/story-long-write/scripts/storyctl.py +304 -46
- package/lib/oh-story/skills/story-long-write/scripts/tracking_commit.py +478 -15
- package/lib/oh-story/skills/story-long-write/scripts/wordcount_core.py +182 -23
- package/lib/oh-story/skills/story-review/SKILL.md +31 -302
- package/lib/oh-story/skills/story-review/references/agent-prompts.md +184 -0
- package/lib/oh-story/skills/story-review/references/anti-ai-writing.md +12 -11
- package/lib/oh-story/skills/story-review/references/author-memory-maintenance.md +75 -0
- package/lib/oh-story/skills/story-review/references/author-memory.md +46 -94
- package/lib/oh-story/skills/story-review/references/banned-words.md +2 -2
- package/lib/oh-story/skills/story-review/references/batch-review.md +19 -0
- package/lib/oh-story/skills/story-review/references/character-relations.md +49 -1
- package/lib/oh-story/skills/story-review/references/dialogue-mastery.md +4 -0
- package/lib/oh-story/skills/story-review/references/review-quality.md +6 -11
- package/lib/oh-story/skills/story-review/references/review-tracking.md +10 -0
- package/lib/oh-story/skills/story-review/references/solo.md +52 -0
- package/lib/oh-story/skills/story-review/references/style-resolution.md +4 -4
- package/lib/oh-story/skills/story-review/references/tracking-initialization.md +1 -1
- package/lib/oh-story/skills/story-review/references/tracking-transaction.md +15 -11
- package/lib/oh-story/skills/story-review/scripts/author_memory_commit.py +934 -170
- package/lib/oh-story/skills/story-review/scripts/check-ai-patterns.js +155 -8
- package/lib/oh-story/skills/story-review/scripts/tracking_commit.py +478 -15
- package/lib/oh-story/skills/story-review/scripts/wordcount_core.py +182 -23
- package/lib/oh-story/skills/story-setup/SKILL.md +71 -452
- package/lib/oh-story/skills/story-setup/references/agent-references/agent-quality.md +4 -4
- package/lib/oh-story/skills/story-setup/references/agent-references/anti-ai-writing.md +12 -11
- package/lib/oh-story/skills/story-setup/references/agent-references/banned-words.md +2 -2
- package/lib/oh-story/skills/story-setup/references/agent-references/benchmark-style-load.md +107 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/character-relations.md +49 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/deslop-gates.md +22 -5
- package/lib/oh-story/skills/story-setup/references/agent-references/dialogue-mastery.md +4 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/emotion-on-page.md +116 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/emotional-arc-design.md +1 -114
- package/lib/oh-story/skills/story-setup/references/agent-references/format-and-structure.md +1 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards.md +1 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/long-emotional-methods.md +2 -2
- package/lib/oh-story/skills/story-setup/references/agent-references/long-genre-catalog.md +1 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/long-genre-mechanics.md +3 -3
- package/lib/oh-story/skills/story-setup/references/agent-references/long-quality.md +7 -5
- package/lib/oh-story/skills/story-setup/references/agent-references/opening-design.md +2 -2
- package/lib/oh-story/skills/story-setup/references/agent-references/outline-conflict.md +4 -4
- package/lib/oh-story/skills/story-setup/references/agent-references/outline-methods.md +7 -7
- package/lib/oh-story/skills/story-setup/references/agent-references/outline-rhythm.md +3 -3
- package/lib/oh-story/skills/story-setup/references/agent-references/short-emotional-methods.md +1 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/short-genre-formulas.md +0 -15
- package/lib/oh-story/skills/story-setup/references/agent-references/short-reversal.md +1 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/style-combat-face.md +1 -58
- 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 +4 -4
- package/lib/oh-story/skills/story-setup/references/agent-references/writing-craft.md +19 -65
- package/lib/oh-story/skills/story-setup/references/diagnostics.md +4 -4
- package/lib/oh-story/skills/story-short-analyze/SKILL.md +56 -110
- package/lib/oh-story/skills/story-short-analyze/references/analysis-character-relations.md +1 -1
- package/lib/oh-story/skills/story-short-analyze/references/analysis-method.md +42 -0
- package/lib/oh-story/skills/story-short-analyze/references/optional-modules.md +97 -0
- package/lib/oh-story/skills/story-short-analyze/references/output-contract.md +9 -47
- package/lib/oh-story/skills/story-short-analyze/references/quality-checklist.md +80 -0
- package/lib/oh-story/skills/story-short-analyze/references/source-story-quality.md +4 -4
- package/lib/oh-story/skills/story-short-analyze/references/stage2-3-structure-emotion.md +308 -0
- package/lib/oh-story/skills/story-short-analyze/references/stage4-6-reversal-summary.md +611 -0
- package/lib/oh-story/skills/story-short-scan/SKILL.md +35 -65
- package/lib/oh-story/skills/story-short-scan/references/manual-rank-input.md +36 -0
- package/lib/oh-story/skills/story-short-scan/references/platform-dianzhong.md +24 -0
- package/lib/oh-story/skills/story-short-scan/references/platform-heiyan.md +21 -0
- package/lib/oh-story/skills/story-short-scan/references/topic-match.md +38 -0
- package/lib/oh-story/skills/story-short-write/SKILL.md +23 -175
- package/lib/oh-story/skills/story-short-write/references/agent-calls.md +13 -0
- package/lib/oh-story/skills/story-short-write/references/author-memory-maintenance.md +75 -0
- package/lib/oh-story/skills/story-short-write/references/author-memory.md +46 -94
- package/lib/oh-story/skills/story-short-write/references/banned-words.md +2 -2
- package/lib/oh-story/skills/story-short-write/references/benchmark-recall.md +1 -21
- package/lib/oh-story/skills/story-short-write/references/dialogue-mastery.md +4 -0
- package/lib/oh-story/skills/story-short-write/references/emotional-methods.md +1 -1
- package/lib/oh-story/skills/story-short-write/references/genre-writing-formulas.md +0 -15
- package/lib/oh-story/skills/story-short-write/references/output-contract.md +9 -47
- package/lib/oh-story/skills/story-short-write/references/reference-index.md +17 -47
- package/lib/oh-story/skills/story-short-write/references/scene-craft.md +40 -0
- package/lib/oh-story/skills/story-short-write/references/short-craft.md +7 -43
- package/lib/oh-story/skills/story-short-write/references/short-format.md +3 -3
- package/lib/oh-story/skills/story-short-write/references/short-prose-quality.md +2 -1
- package/lib/oh-story/skills/story-short-write/references/short-reversal.md +1 -1
- package/lib/oh-story/skills/story-short-write/references/style-resolution.md +4 -4
- package/lib/oh-story/skills/story-short-write/references/submission-craft.md +1 -1
- package/lib/oh-story/skills/story-short-write/references/villain-and-reveal.md +5 -10
- package/lib/oh-story/skills/story-short-write/references/workflow-design.md +21 -13
- package/lib/oh-story/skills/story-short-write/references/workflow-draft.md +116 -0
- package/lib/oh-story/skills/story-short-write/references/workflow-revision.md +99 -0
- package/lib/oh-story/skills/story-short-write/references/writing-workflow.md +7 -119
- package/lib/oh-story/skills/story-short-write/scripts/author_memory_commit.py +934 -170
- package/lib/oh-story/skills/story-short-write/scripts/check-ai-patterns.js +155 -8
- package/lib/oh-story/skills/story-short-write/scripts/check-outline-copy.js +7 -2
- package/lib/types/client/drama-production-view.d.ts.map +1 -1
- package/lib/types/client/file-activity.d.ts +19 -1
- package/lib/types/client/file-activity.d.ts.map +1 -1
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/production-prompts.d.ts +7 -0
- package/lib/types/client/production-prompts.d.ts.map +1 -1
- package/lib/types/client/production-runtime.d.ts +16 -0
- package/lib/types/client/production-runtime.d.ts.map +1 -1
- package/lib/types/client/video-studio.d.ts.map +1 -1
- package/lib/types/drama-adapters.d.ts +5 -3
- package/lib/types/drama-adapters.d.ts.map +1 -1
- package/lib/types/native-hooks.d.ts +53 -1
- package/lib/types/native-hooks.d.ts.map +1 -1
- package/lib/types/role-identity.d.ts +17 -0
- package/lib/types/role-identity.d.ts.map +1 -0
- package/lib/types/role-tool.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/types/workspace-route.d.ts.map +1 -1
- package/lib/video-recap/CHANGELOG.md +60 -1
- package/lib/video-recap/README.en.md +348 -137
- package/lib/video-recap/README.md +340 -136
- package/lib/video-recap/manifest.json +406 -251
- package/lib/video-recap/skills/video-assemble/SKILL.md +30 -15
- package/lib/video-recap/skills/video-assemble/references/audio-modes.md +63 -0
- package/lib/video-recap/skills/video-assemble/references/explicit-audio-mix.md +75 -0
- package/lib/video-recap/skills/video-assemble/references/foreground-compose.md +118 -0
- package/lib/video-recap/skills/video-assemble/references/narration-adoption.md +103 -0
- package/lib/video-recap/skills/video-assemble/references/packaging.md +45 -0
- package/lib/video-recap/skills/video-assemble/references/pair-media.md +74 -0
- package/lib/video-recap/skills/video-assemble/references/source-score.md +147 -0
- package/lib/video-recap/skills/video-assemble/references/subtitle-track.md +202 -0
- package/lib/video-recap/skills/video-assemble/scripts/adoption/__init__.py +1 -0
- package/lib/video-recap/skills/video-assemble/scripts/adoption/audio_mix_binding.py +279 -0
- package/lib/video-recap/skills/video-assemble/scripts/adoption/frozen_audio.py +187 -0
- package/lib/video-recap/skills/video-assemble/scripts/adoption/narration_binding.py +323 -0
- package/lib/video-recap/skills/video-assemble/scripts/adoption/strict_inputs.py +109 -0
- package/lib/video-recap/skills/video-assemble/scripts/adoption/strict_publish.py +130 -0
- package/lib/video-recap/skills/video-assemble/scripts/artifacts.py +10 -46
- package/lib/video-recap/skills/video-assemble/scripts/assemble.py +378 -119
- package/lib/video-recap/skills/video-assemble/scripts/assemble_constants.py +29 -12
- package/lib/video-recap/skills/video-assemble/scripts/assembly_contract.py +95 -44
- package/lib/video-recap/skills/video-assemble/scripts/assembly_settings.py +75 -32
- package/lib/video-recap/skills/video-assemble/scripts/audio_mix.py +11 -11
- package/lib/video-recap/skills/video-assemble/scripts/compose_foreground.py +293 -0
- package/lib/video-recap/skills/video-assemble/scripts/export_jianying.py +71 -78
- package/lib/video-recap/skills/video-assemble/scripts/jianying/__init__.py +1 -0
- package/lib/video-recap/skills/video-assemble/scripts/{jianying_builders.py → jianying/builders.py} +39 -67
- package/lib/video-recap/skills/video-assemble/scripts/{jianying_model.py → jianying/model.py} +3 -11
- package/lib/video-recap/skills/video-assemble/scripts/{jianying_optional.py → jianying/optional.py} +1 -1
- package/lib/video-recap/skills/video-assemble/scripts/jianying/schema.py +53 -0
- package/lib/video-recap/skills/video-assemble/scripts/{jianying_templates.py → jianying/templates.py} +1 -1
- package/lib/video-recap/skills/video-assemble/scripts/{jianying_timeline_contract.py → jianying/timeline_contract.py} +34 -13
- package/lib/video-recap/skills/video-assemble/scripts/{jianying_writer.py → jianying/writer.py} +7 -17
- package/lib/video-recap/skills/video-assemble/scripts/lib.py +37 -4
- package/lib/video-recap/skills/video-assemble/scripts/media.py +12 -21
- package/lib/video-recap/skills/video-assemble/scripts/narration_audio.py +64 -34
- package/lib/video-recap/skills/video-assemble/scripts/packaging.py +122 -0
- package/lib/video-recap/skills/video-assemble/scripts/pair_media.py +216 -0
- package/lib/video-recap/skills/video-assemble/scripts/render_preflight.py +1 -3
- package/lib/video-recap/skills/video-assemble/scripts/source_score.py +469 -0
- package/lib/video-recap/skills/video-assemble/scripts/source_subtitles.py +25 -9
- package/lib/video-recap/skills/video-assemble/scripts/subtitles/__init__.py +1 -0
- package/lib/video-recap/skills/video-assemble/scripts/{subtitle_core.py → subtitles/core.py} +13 -6
- package/lib/video-recap/skills/video-assemble/scripts/{subtitle_render.py → subtitles/render.py} +5 -4
- package/lib/video-recap/skills/video-assemble/scripts/subtitles/track.py +324 -0
- package/lib/video-recap/skills/video-assemble/scripts/subtitles/track_binding.py +200 -0
- package/lib/video-recap/skills/video-assemble/scripts/timeline.py +30 -46
- package/lib/video-recap/skills/video-assemble/scripts/timeline_emit.py +91 -7
- package/lib/video-recap/skills/video-assemble/scripts/visual_render.py +22 -8
- package/lib/video-recap/skills/video-cut/SKILL.md +34 -16
- package/lib/video-recap/skills/video-cut/references/shot-review.md +50 -0
- package/lib/video-recap/skills/video-cut/scripts/cut.py +2 -28
- package/lib/video-recap/skills/video-cut/scripts/cut_cli.py +58 -62
- package/lib/video-recap/skills/video-cut/scripts/cut_contract.py +21 -106
- package/lib/video-recap/skills/video-cut/scripts/cut_render.py +14 -9
- package/lib/video-recap/skills/video-cut/scripts/lib.py +39 -0
- package/lib/video-recap/skills/video-cut/scripts/narration_mapping.py +2 -155
- package/lib/video-recap/skills/video-cut/scripts/narrative_selection.py +244 -0
- package/lib/video-recap/skills/video-cut/scripts/shot_review.py +357 -0
- package/lib/video-recap/skills/video-recap/SKILL.md +86 -42
- package/lib/video-recap/skills/video-recap/references/audio-routing.md +126 -0
- package/lib/video-recap/skills/video-recap/references/config-playbook.md +12 -8
- package/lib/video-recap/skills/video-recap/references/data-schema.md +44 -33
- package/lib/video-recap/skills/video-recap/references/resource-library.md +151 -0
- package/lib/video-recap/skills/video-recap/references/shift-left-qc-schema.md +1 -1
- package/lib/video-recap/skills/video-recap/scripts/doctor.py +123 -30
- package/lib/video-recap/skills/video-recap/scripts/final_qc.py +22 -75
- package/lib/video-recap/skills/video-recap/scripts/lib.py +3 -3
- package/lib/video-recap/skills/video-recap/scripts/library.py +509 -0
- package/lib/video-recap/skills/video-recap/scripts/materials.py +63 -91
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc.py +8 -70
- package/lib/video-recap/skills/video-recap/scripts/project_binding.py +286 -0
- package/lib/video-recap/skills/video-recap/scripts/qc/__init__.py +1 -0
- package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_client.py → qc/mimo_client.py} +1 -1
- package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_evidence.py → qc/mimo_evidence.py} +33 -66
- package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_observations.py → qc/mimo_observations.py} +3 -11
- package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_payload.py → qc/mimo_payload.py} +4 -7
- package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_report.py → qc/mimo_report.py} +17 -28
- package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_runner.py → qc/mimo_runner.py} +2 -2
- package/lib/video-recap/skills/video-recap/scripts/qc_contract.py +0 -19
- package/lib/video-recap/skills/video-recap/scripts/recap_cli.py +119 -50
- package/lib/video-recap/skills/video-recap/scripts/recap_inspect.py +32 -114
- package/lib/video-recap/skills/video-recap/scripts/recap_review.py +9 -23
- package/lib/video-recap/skills/video-recap/scripts/recap_runner.py +387 -334
- package/lib/video-recap/skills/video-recap/scripts/recap_runtime.py +23 -23
- package/lib/video-recap/skills/video-recap/scripts/recap_source.py +278 -0
- package/lib/video-recap/skills/video-recap/scripts/recap_stage_qc.py +25 -12
- package/lib/video-recap/skills/video-recap/scripts/recap_timeline.py +59 -52
- package/lib/video-recap/skills/video-recap/scripts/resource_lock.py +162 -0
- package/lib/video-recap/skills/video-script/SKILL.md +16 -13
- package/lib/video-recap/skills/video-script/references/creative-editing-playbook.md +9 -5
- package/lib/video-recap/skills/video-script/references/promotional-copy.md +68 -0
- package/lib/video-recap/skills/video-script/references/research-guide.md +1 -1
- package/lib/video-recap/skills/video-script/scripts/evidence_bundle.py +24 -76
- package/lib/video-recap/skills/video-script/scripts/lib.py +10 -87
- package/lib/video-recap/skills/video-script/scripts/narration_lint.py +63 -39
- package/lib/video-recap/skills/video-script/scripts/review.py +0 -2
- package/lib/video-recap/skills/video-script/scripts/review_grounding.py +35 -59
- package/lib/video-recap/skills/video-script/scripts/review_response.py +66 -128
- package/lib/video-recap/skills/video-script/scripts/review_runner.py +12 -20
- package/lib/video-recap/skills/video-script/scripts/speech_ownership.py +4 -4
- package/lib/video-recap/skills/video-script/scripts/validate.py +53 -42
- package/lib/video-recap/skills/video-understanding/SKILL.md +6 -4
- package/lib/video-recap/skills/video-understanding/references/data-schema.md +50 -156
- package/lib/video-recap/skills/video-understanding/references/research-guide.md +4 -4
- package/lib/video-recap/skills/video-understanding/scripts/asr.py +85 -65
- package/lib/video-recap/skills/video-understanding/scripts/asr_timing_evidence.py +270 -0
- package/lib/video-recap/skills/video-understanding/scripts/brief.py +2 -5
- package/lib/video-recap/skills/video-understanding/scripts/briefing/__init__.py +1 -0
- package/lib/video-recap/skills/{video-script/scripts/agent_brief.py → video-understanding/scripts/briefing/builder.py} +38 -35
- package/lib/video-recap/skills/video-understanding/scripts/{brief_context.py → briefing/context.py} +4 -35
- package/lib/video-recap/skills/{video-script/scripts/brief_inputs.py → video-understanding/scripts/briefing/inputs.py} +9 -31
- package/lib/video-recap/skills/{video-script/scripts/brief_timeline.py → video-understanding/scripts/briefing/timeline.py} +5 -6
- package/lib/video-recap/skills/video-understanding/scripts/consolidate.py +65 -116
- package/lib/video-recap/skills/video-understanding/scripts/detect.py +31 -40
- package/lib/video-recap/skills/video-understanding/scripts/lib.py +30 -86
- package/lib/video-recap/skills/video-understanding/scripts/narration_lint.py +63 -39
- package/lib/video-recap/skills/video-understanding/scripts/speech_ownership.py +4 -4
- package/lib/video-recap/skills/video-understanding/scripts/storyboard.py +17 -79
- package/lib/video-recap/skills/video-understanding/scripts/understanding_brief.py +14 -40
- package/lib/video-recap/skills/video-understanding/scripts/understanding_cache.py +64 -46
- package/lib/video-recap/skills/video-understanding/scripts/understanding_runner.py +31 -11
- package/lib/video-recap/skills/video-understanding/scripts/understanding_storyboard.py +19 -14
- package/lib/video-recap/skills/video-understanding/scripts/vlm.py +80 -108
- package/lib/video-recap/skills/video-voiceover/SKILL.md +29 -19
- package/lib/video-recap/skills/video-voiceover/references/index-tts.md +31 -0
- package/lib/video-recap/skills/video-voiceover/scripts/approved_text_policy.py +123 -0
- package/lib/video-recap/skills/video-voiceover/scripts/dub.py +51 -59
- package/lib/video-recap/skills/video-voiceover/scripts/lib.py +8 -44
- package/lib/video-recap/skills/video-voiceover/scripts/providers/__init__.py +1 -0
- package/lib/video-recap/skills/video-voiceover/scripts/providers/index_tts.py +184 -0
- package/lib/video-recap/skills/video-voiceover/scripts/voiceover.py +212 -134
- package/package.json +45 -45
- package/lib/novel-to-game/skills/game-world-design/references/dialogue-design-method.md +0 -58
- package/lib/novel-to-game/skills/game-world-design/references/game-writing-craft.md +0 -53
- package/lib/novel-to-game/skills/novel-to-game/references/intake-benchmark-reference.md +0 -38
- package/lib/oh-story/skills/story-long-analyze/references/material-decomposition.md +0 -530
- package/lib/oh-story/skills/story-long-analyze/references/output-templates.md +0 -559
- package/lib/oh-story/skills/story-long-scan/references/scan-output-format.md +0 -207
- package/lib/oh-story/skills/story-long-write/references/state-tracking.md +0 -108
- package/lib/oh-story/skills/story-short-analyze/references/material-decomposition.md +0 -678
- package/lib/oh-story/skills/story-short-analyze/references/output-templates.md +0 -561
- package/lib/video-recap/skills/video-assemble/scripts/jianying_schema.py +0 -98
- package/lib/video-recap/skills/video-recap/references/creative-editing-playbook.md +0 -225
- package/lib/video-recap/skills/video-recap/references/research-guide.md +0 -74
- package/lib/video-recap/skills/video-recap/references/timeline-and-jianying.md +0 -250
- package/lib/video-recap/skills/video-script/scripts/brief_context.py +0 -348
- package/lib/video-recap/skills/video-script/scripts/narration.py +0 -12
- package/lib/video-recap/skills/video-understanding/scripts/agent_brief.py +0 -399
- package/lib/video-recap/skills/video-understanding/scripts/brief_inputs.py +0 -290
- package/lib/video-recap/skills/video-understanding/scripts/brief_timeline.py +0 -327
- /package/lib/video-recap/skills/video-assemble/scripts/{jianying_tracks.py → jianying/tracks.py} +0 -0
- /package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_contract.py → qc/mimo_contract.py} +0 -0
- /package/lib/video-recap/skills/video-voiceover/scripts/{fish_audio.py → providers/fish_audio.py} +0 -0
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# Source and score bed producer
|
|
2
|
+
|
|
3
|
+
`source_score.py` is a standalone producer for explicitly authored source-audio
|
|
4
|
+
intervals and one continuous score playhead. It does not mix narration, inspect old
|
|
5
|
+
masters, infer dialogue, choose music, retime sound, normalize loudness, limit peaks,
|
|
6
|
+
or publish a final video.
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
python3 scripts/source_score.py source_score_plan.json --output-dir a-new-directory
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
The output directory must not exist. Success creates:
|
|
13
|
+
|
|
14
|
+
- `source_bed.wav` — reordered/pre-gained source intervals plus explicit silence;
|
|
15
|
+
- `score_bed.wav` — one continuous raw-score window or an adopted frozen stem;
|
|
16
|
+
- `prepared_bed.wav` — source plus score, without master gain/limiting;
|
|
17
|
+
- `prepared_bed_receipt.json` — input paths, clocks, sample ranges, PCM facts and QC facts.
|
|
18
|
+
|
|
19
|
+
All three beds are `pcm_f32le`, 48 kHz, stereo. Float output avoids repeated integer
|
|
20
|
+
quantization and preserves values outside `[-1,1]`; the receipt records actual peak,
|
|
21
|
+
finite-sample status, and `FLOAT_PRESERVED_NO_MASTER`. A later explicitly adopted
|
|
22
|
+
consumer owns final master gain and delivery encoding.
|
|
23
|
+
|
|
24
|
+
## Strict plan schema v1
|
|
25
|
+
|
|
26
|
+
```json
|
|
27
|
+
{
|
|
28
|
+
"artifact": "source_score_plan",
|
|
29
|
+
"schema_version": 1,
|
|
30
|
+
"output": {"sample_rate": 48000, "channels": 2, "total_samples": 1920000},
|
|
31
|
+
"source_segments": [
|
|
32
|
+
{
|
|
33
|
+
"id": "protected-dialogue-1",
|
|
34
|
+
"path": "/local/source.mov",
|
|
35
|
+
"audio_stream": 0,
|
|
36
|
+
"source_fps": "24/1",
|
|
37
|
+
"source_start_frame": 120,
|
|
38
|
+
"source_end_frame": 180,
|
|
39
|
+
"output_start_sample": 240000,
|
|
40
|
+
"gain": 1.0,
|
|
41
|
+
"fade_in_samples": 0,
|
|
42
|
+
"fade_out_samples": 96,
|
|
43
|
+
"fade_shape": "linear",
|
|
44
|
+
"role": "protected_original"
|
|
45
|
+
}
|
|
46
|
+
],
|
|
47
|
+
"source_silence": [
|
|
48
|
+
{"output_start_sample": 0, "output_end_sample": 240000, "role": "silence"},
|
|
49
|
+
{"output_start_sample": 360000, "output_end_sample": 1920000, "role": "silence"}
|
|
50
|
+
],
|
|
51
|
+
"score": {
|
|
52
|
+
"kind": "raw",
|
|
53
|
+
"path": "/local/score.wav",
|
|
54
|
+
"audio_stream": 0,
|
|
55
|
+
"source_offset_sample": 384000,
|
|
56
|
+
"gain": 0.18,
|
|
57
|
+
"fade_in_samples": 33600,
|
|
58
|
+
"fade_out_samples": 120000,
|
|
59
|
+
"fade_shape": "half_cosine"
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Every source range is half-open in CFR picture frames. Version 1 accepts H.264 and
|
|
65
|
+
HEVC picture sources whose complete packet PTS/duration grid proves one zero-origin
|
|
66
|
+
packet per same-speed frame. Frame boundaries are projected onto the 48 kHz clock by
|
|
67
|
+
rounding the exact position half-up once, so `48000/fps` need not be an integer and a
|
|
68
|
+
broadcast rate such as 30000/1001 is accepted; every derived bound comes from those
|
|
69
|
+
rounded positions. Other codecs, incomplete packet timing, VFR, retime fields,
|
|
70
|
+
nominal time seeks, and unknown fields are rejected rather than called verified
|
|
71
|
+
(legacy `sha256` keys are ignored).
|
|
72
|
+
Each unique selected source stream is decoded from its actual PTS clock to canonical
|
|
73
|
+
48 kHz stereo exactly once. All authored intervals are then cut from that canonical
|
|
74
|
+
sample array—never independently decoded or `-ss`-seeked per edit.
|
|
75
|
+
|
|
76
|
+
The source segment duration is `(source_end_frame-source_start_frame)*48000/fps`.
|
|
77
|
+
Its output end is derived, not declared. Source segments and explicit silence ranges
|
|
78
|
+
must form an exact, nonoverlapping partition of `[0,total_samples)`. There are no
|
|
79
|
+
implicit gaps. Supported roles are `protected_original` and
|
|
80
|
+
`mixed_original_under_narration`; their gains and labels are retained unchanged.
|
|
81
|
+
|
|
82
|
+
Source fades are explicitly `linear`. A fade of `N>=2` samples uses inclusive
|
|
83
|
+
endpoints: fade-in gain is `i/(N-1)`, and fade-out gain is `(N-1-i)/(N-1)`. Thus the
|
|
84
|
+
first/last samples are exactly zero and the opposite endpoints exactly one. Zero
|
|
85
|
+
means no fade; a one-sample fade is rejected as ambiguous. Fade windows may not
|
|
86
|
+
overlap within a segment.
|
|
87
|
+
|
|
88
|
+
## Score modes
|
|
89
|
+
|
|
90
|
+
`raw` decodes the selected score stream once, advances one playhead from
|
|
91
|
+
`source_offset_sample`, and takes exactly `total_samples`. The input must be long
|
|
92
|
+
enough; there is no implicit loop. Gain and one pair of whole-score fades are applied
|
|
93
|
+
once, so score playback never resets at picture/source cuts. `linear` and
|
|
94
|
+
`half_cosine` fades use inclusive endpoints.
|
|
95
|
+
|
|
96
|
+
When the adopted source bed is already the complete soundtrack and no additional
|
|
97
|
+
music is selected, use the exact no-score form:
|
|
98
|
+
|
|
99
|
+
```json
|
|
100
|
+
{"kind":"none"}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
It accepts no other fields. `score_bed.wav` is real all-zero float PCM at the exact
|
|
104
|
+
output length, while `prepared_bed.wav` is an exact canonical PCM copy of
|
|
105
|
+
`source_bed.wav`; the receipt records `kind:none` rather than inventing a music asset.
|
|
106
|
+
|
|
107
|
+
An adopted score that already contains its chosen offset/gain/fades uses the exact
|
|
108
|
+
alternative schema:
|
|
109
|
+
|
|
110
|
+
```json
|
|
111
|
+
{"kind":"frozen","path":"/local/frozen.wav","audio_stream":0}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Frozen input must be PCM16, PCM24, or PCM float, 48 kHz stereo, and exactly
|
|
115
|
+
`total_samples`. PCM16/24 values convert exactly to float canonical representation.
|
|
116
|
+
No offset, gain, fade, loop, normalization or other processing fields are accepted.
|
|
117
|
+
The receipt records the frozen file path and its probed PCM facts.
|
|
118
|
+
|
|
119
|
+
## Receipt and failure boundary
|
|
120
|
+
|
|
121
|
+
`prepared_bed_receipt` schema version 1 records the plan path, each input path,
|
|
122
|
+
selected stream, actual CFR/audio clocks, canonical decoded PCM facts, resolved
|
|
123
|
+
input/output sample ranges, gains, fades, roles, and each output's path, byte size,
|
|
124
|
+
format, sample count, finite status and peak.
|
|
125
|
+
|
|
126
|
+
Every declared input must exist and probe as declared before work starts. A selected
|
|
127
|
+
source range must fit the actual canonical decoded samples; silence padding can never
|
|
128
|
+
conceal a short source. WAVs remain hidden staging artifacts until every output
|
|
129
|
+
validates. FFmpeg failure, invalid ranges, non-finite PCM, or an existing target
|
|
130
|
+
directory cannot publish final-named beds or a receipt.
|
|
131
|
+
Diagnostic command/log/intermediate files may remain in the unique failed directory.
|
|
132
|
+
|
|
133
|
+
To consume a completed receipt with explicitly adopted narration, use the strict
|
|
134
|
+
`--audio-mix-adoption` path in `explicit-audio-mix.md`. To deliver an already complete
|
|
135
|
+
prepared bed without narration, a dedicated prepared-audio renderer will follow in a
|
|
136
|
+
later release. Do not feed `prepared_bed.wav` through legacy source ducking or
|
|
137
|
+
ambient BGM/loudness settings.
|
|
138
|
+
|
|
139
|
+
## 与旧入口的关系(中文摘要)
|
|
140
|
+
|
|
141
|
+
原片完整解码一次再切样本,分别执行保留对白、低位原声和明确静音;渐变必须显式给定。
|
|
142
|
+
已处理的音乐轨走 `frozen`,不能再次偏移、调增益或加渐变。若采用的原声底轨本身已包含
|
|
143
|
+
完整音乐决定且不再叠加配乐,使用严格的 `score:{"kind":"none"}`;它生成真实全零 score,
|
|
144
|
+
并保持 prepared 与 source 逐字节相同,不伪造静音音乐资产。
|
|
145
|
+
|
|
146
|
+
这一步仅输出声音底轨和来源回执,不是最终视频。旧入口保留兼容行为;调用方须区分
|
|
147
|
+
“底轨已验证”“配音已验证”和“完整混音已验证”三种状态,不能相互冒充。
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
# Independent subtitle track contract (schema v1)
|
|
2
|
+
|
|
3
|
+
`scripts/subtitles/track.py` loads a subtitle track whose cue times already use
|
|
4
|
+
the final **output clock**. It validates the track against picture, edit, audio,
|
|
5
|
+
and duration facts independently supplied by the caller. It does not align
|
|
6
|
+
speech, remap source time, split text, repair cue boundaries, or prove that a
|
|
7
|
+
subtitle is perceptually synchronized.
|
|
8
|
+
|
|
9
|
+
## Loader API
|
|
10
|
+
|
|
11
|
+
```python
|
|
12
|
+
from fractions import Fraction
|
|
13
|
+
from subtitles.track import load_subtitle_track
|
|
14
|
+
|
|
15
|
+
loaded = load_subtitle_track(
|
|
16
|
+
"subtitle_track.json",
|
|
17
|
+
expected_picture_identity={
|
|
18
|
+
"path": "/project/paired.mp4",
|
|
19
|
+
"edit_plan": "/project/edit_plan.json",
|
|
20
|
+
},
|
|
21
|
+
expected_audio_identity={
|
|
22
|
+
"selected_stream": 1,
|
|
23
|
+
"sample_rate": 48000,
|
|
24
|
+
"packet_count": 4700,
|
|
25
|
+
},
|
|
26
|
+
expected_duration_seconds=Fraction(duration_ts) * stream_time_base,
|
|
27
|
+
reject_legacy_estimate=True,
|
|
28
|
+
)
|
|
29
|
+
metadata = loaded["metadata"]
|
|
30
|
+
entries = loaded["entries"]
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The input may also be an in-memory mapping. `entries` is a list of dictionaries
|
|
34
|
+
ready for the existing seconds-based subtitle renderer:
|
|
35
|
+
|
|
36
|
+
```json
|
|
37
|
+
{
|
|
38
|
+
"start": 1.0,
|
|
39
|
+
"end": 3.0,
|
|
40
|
+
"text": "example",
|
|
41
|
+
"source": "narration",
|
|
42
|
+
"source_ref": "narration:7",
|
|
43
|
+
"timing_evidence": {
|
|
44
|
+
"kind": "asr_boundary_calibrated",
|
|
45
|
+
"evidence_refs": ["alignment-run:example"],
|
|
46
|
+
"calibration": "asr_energy",
|
|
47
|
+
"word_alignment": "none"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
The loader preserves cue text and boundaries. The only conversion is exact
|
|
53
|
+
integer ticks to renderer-facing seconds. `metadata.timing_evidence_kinds`
|
|
54
|
+
reports the labels present; it is not an aggregate precision verdict.
|
|
55
|
+
|
|
56
|
+
## Schema v1
|
|
57
|
+
|
|
58
|
+
```json
|
|
59
|
+
{
|
|
60
|
+
"schema_version": 1,
|
|
61
|
+
"clock": {
|
|
62
|
+
"kind": "output",
|
|
63
|
+
"timebase": {"numerator": 1, "denominator": 30},
|
|
64
|
+
"duration_ticks": 300
|
|
65
|
+
},
|
|
66
|
+
"overlap_policy": "forbid",
|
|
67
|
+
"bindings": {
|
|
68
|
+
"picture": {
|
|
69
|
+
"path": "/project/paired.mp4",
|
|
70
|
+
"edit_plan": "/project/edit_plan.json"
|
|
71
|
+
},
|
|
72
|
+
"audio": {
|
|
73
|
+
"selected_stream": 1,
|
|
74
|
+
"sample_rate": 48000,
|
|
75
|
+
"packet_count": 4700
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
"cues": [
|
|
79
|
+
{
|
|
80
|
+
"start_tick": 30,
|
|
81
|
+
"end_tick": 90,
|
|
82
|
+
"text": "example",
|
|
83
|
+
"attribution": {"kind": "narration", "ref": "narration:7"},
|
|
84
|
+
"timing_evidence": {
|
|
85
|
+
"kind": "asr_boundary_calibrated",
|
|
86
|
+
"evidence_refs": ["alignment-run:example"],
|
|
87
|
+
"calibration": "asr_energy",
|
|
88
|
+
"word_alignment": "none"
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
]
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
- `timebase` is rational seconds per tick. Cue intervals are half-open
|
|
96
|
+
`[start_tick, end_tick)`.
|
|
97
|
+
- `duration_ticks`, cue bounds, and stream indexes are nonnegative integers
|
|
98
|
+
(booleans are not integers for this contract).
|
|
99
|
+
- Cues must be ordered, non-overlapping, nonempty, and contained by the output
|
|
100
|
+
duration. Adjacent half-open cues may touch.
|
|
101
|
+
- Distinct tick bounds must remain a positive, finite interval after conversion
|
|
102
|
+
to the renderer's float seconds. Tracks beyond that projection precision fail
|
|
103
|
+
closed rather than becoming a zero-length rendered cue.
|
|
104
|
+
- `overlap_policy` must be `forbid`. Schema v1 has no permissive overlap mode.
|
|
105
|
+
- `attribution.kind` is `source` or `narration`; `ref` identifies the source
|
|
106
|
+
utterance or narration item without changing its text.
|
|
107
|
+
- Unknown fields and schema versions other than integer `1` are rejected, so a
|
|
108
|
+
newer producer cannot be silently interpreted as v1. Legacy `sha256` /
|
|
109
|
+
`edit_sha256` binding keys are the one exception: they are ignored.
|
|
110
|
+
|
|
111
|
+
## Independent binding checks
|
|
112
|
+
|
|
113
|
+
The loader compares track declarations with the caller's current facts; it never
|
|
114
|
+
treats a track's own binding as evidence that the track is fresh.
|
|
115
|
+
|
|
116
|
+
- `picture.path` is mandatory and must resolve to the caller's current picture
|
|
117
|
+
path. `edit_plan` is optional for inputs without a separately materialized
|
|
118
|
+
edit plan; if the track contains it, the caller must supply the same path.
|
|
119
|
+
- `audio.selected_stream`, `sample_rate` and `packet_count` describe the
|
|
120
|
+
**actually adopted** audio stream, not merely a source filename or an intended
|
|
121
|
+
mix manifest. Pass the facts probed from the current media; this module
|
|
122
|
+
deliberately does not run ffprobe itself.
|
|
123
|
+
- `expected_duration_seconds` is mandatory and checked against
|
|
124
|
+
`duration_ticks * timebase`. Prefer `Fraction(duration_ts) * time_base` from
|
|
125
|
+
the actual output stream to avoid decimal/container rounding. `int`, finite
|
|
126
|
+
`float`, and finite `Decimal` are also accepted.
|
|
127
|
+
|
|
128
|
+
## Timing evidence labels
|
|
129
|
+
|
|
130
|
+
Evidence labels describe how a cue boundary was obtained. They do not change
|
|
131
|
+
the exact burn time represented by its ticks.
|
|
132
|
+
|
|
133
|
+
| `kind` | Required calibration | Required word alignment | Evidence refs |
|
|
134
|
+
|---|---|---|---|
|
|
135
|
+
| `legacy_estimate` | `none` | `none` | optional |
|
|
136
|
+
| `asr_boundary_calibrated` | `asr_energy` | `none` | required |
|
|
137
|
+
| `word_timestamps` | `none` or `asr_energy` | `asr_words` | required |
|
|
138
|
+
| `human_verified` | `human_boundary` | `none` or `human_words` | required |
|
|
139
|
+
|
|
140
|
+
`asr_boundary_calibrated` is the label for a coarse-ASR boundary adjusted with
|
|
141
|
+
ASR context and/or energy evidence. Energy onset is not proof of a phoneme, so
|
|
142
|
+
this label cannot claim word alignment or human verification. Missing evidence
|
|
143
|
+
references cannot claim any non-legacy kind. A strict caller may reject
|
|
144
|
+
`legacy_estimate`; accepting another label still does not automatically call it
|
|
145
|
+
strong alignment.
|
|
146
|
+
|
|
147
|
+
Because cue intervals are half-open, a cue is not visible at any tick before its
|
|
148
|
+
`start_tick`; the tick immediately preceding it belongs to whatever came before.
|
|
149
|
+
|
|
150
|
+
## Deliberate limits
|
|
151
|
+
|
|
152
|
+
- Schema v1 has no source-to-cut/edit map and cannot map source-clock cues.
|
|
153
|
+
- The module does not inspect media, select audio streams, or tolerate a
|
|
154
|
+
self-declared binding without current caller facts.
|
|
155
|
+
- Validation proves schema consistency and the requested bindings only. Actual
|
|
156
|
+
rendered first/last subtitle frames and perceptual speech alignment require
|
|
157
|
+
separate render/media review.
|
|
158
|
+
|
|
159
|
+
## Current assembly integration (bounded, not automatic alignment)
|
|
160
|
+
|
|
161
|
+
Place `subtitle_track.json` in `work_dir` and select
|
|
162
|
+
`assemble.py --audio-mode adopted-packet-copy`. A present track is a **complete
|
|
163
|
+
replacement** of all generated narration/original-dialogue subtitles, not a
|
|
164
|
+
partial patch and not merged with legacy subtitles. Carry every cue that should
|
|
165
|
+
remain; an empty `cues` array intentionally removes all generated subtitles.
|
|
166
|
+
Unknown patch/merge modes are rejected by schema v1. This does not detect words
|
|
167
|
+
missing from the authored full track: acoustic/coverage review remains required.
|
|
168
|
+
|
|
169
|
+
The assembly integration independently probes the chosen input stream for its
|
|
170
|
+
sample rate and packet count, resolves the input path for the picture binding,
|
|
171
|
+
and records the `{size, mtime_ns}` of the track, video and optional edit plan in
|
|
172
|
+
`subtitle_track_validation.json`. It does not use the track's own declarations
|
|
173
|
+
as current facts.
|
|
174
|
+
|
|
175
|
+
Actual decoded frame PTS are read before projection. Integer cue ticks and the
|
|
176
|
+
rational timebase are retained until each boundary is resolved to the first
|
|
177
|
+
frame at or after it. `subtitle_track_validation.json` records original ticks,
|
|
178
|
+
resolved frame indexes/PTS, quantization deltas, ASS thresholds, validation
|
|
179
|
+
schema and projector version. SRT and timeline consume resolved frame seconds;
|
|
180
|
+
ASS thresholds are chosen to switch on those same frames despite ASS's 10ms
|
|
181
|
+
clock. A cue with no visible frame, or a boundary that ASS cannot distinguish,
|
|
182
|
+
is rejected rather than silently dropped. The original author file is not
|
|
183
|
+
rewritten. Legacy subtitles keep their previous rendering behavior.
|
|
184
|
+
|
|
185
|
+
Each consumption re-checks the track/media/optional edit plan `{size, mtime_ns}`
|
|
186
|
+
and the consumer duration; a rewritten input is stale and must be prepared
|
|
187
|
+
again. Deleting the explicit track clears its previous validation record.
|
|
188
|
+
An invalid/stale track never falls back to character-proportional timing.
|
|
189
|
+
|
|
190
|
+
Current limits:
|
|
191
|
+
- Integration is for output media starting at zero with an adopted AAC track,
|
|
192
|
+
not raw-source-to-edited-output mapping or a newly mixed narration track.
|
|
193
|
+
- The low-level preparation API accepts `edit_plan_path`, but the assembly CLI
|
|
194
|
+
does not yet expose it. CLI tracks must omit `edit_plan`; that path binds the
|
|
195
|
+
input media, not edit-plan ancestry.
|
|
196
|
+
- The low-level policy `reject_legacy_estimate=True` is available. There is not
|
|
197
|
+
yet a wired commercial-profile CLI gate. The current CLI preserves timing
|
|
198
|
+
evidence labels and does not call every accepted cue precisely aligned.
|
|
199
|
+
- The actual FFmpeg regression demonstrates frame timing with a synthetic cue,
|
|
200
|
+
not phoneme alignment. `direct_listening` and `acoustic_alignment` remain
|
|
201
|
+
`NOT_CHECKED`; evidence labels are declarations, not automatically verified
|
|
202
|
+
human approval or proof that an external evidence reference is true.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Adoption family: narration/audio-mix binding + strict publish for adopted-source audio."""
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
"""Validate, render, and record an explicitly adopted prepared-bed plus narration mix."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
import subprocess
|
|
6
|
+
|
|
7
|
+
from assemble_constants import frame_clock_samples
|
|
8
|
+
from adoption.frozen_audio import probe_audio_packets
|
|
9
|
+
import adoption.narration_binding as narration_binding
|
|
10
|
+
from pair_media import probe_picture, validate_pair_timing
|
|
11
|
+
import source_score
|
|
12
|
+
from adoption.strict_inputs import (
|
|
13
|
+
read_json_bytes, require_fields, require_integer, require_local_path, require_number,
|
|
14
|
+
run_logged, without_digests, write_json_atomic,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
ARTIFACT = "audio_mix_binding"
|
|
19
|
+
FILENAME = "audio_mix_binding.json"
|
|
20
|
+
RATE = 48_000
|
|
21
|
+
CHANNELS = 2
|
|
22
|
+
CODEC = "pcm_f32le"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _picture_format(picture):
|
|
26
|
+
samples = frame_clock_samples(picture["frame_count"], picture["fps"], RATE)
|
|
27
|
+
return samples, {
|
|
28
|
+
"fps": picture["fps"], "frame_count": picture["frame_count"],
|
|
29
|
+
"duration": picture["duration"], "start": picture["start"],
|
|
30
|
+
"decoder": picture["decoder"],
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def load_adoption(path, *, input_video, narration_adoption_path, tts_segments):
|
|
35
|
+
"""Strict read-only preflight; no work artifacts are created here."""
|
|
36
|
+
adoption_path, _, value = read_json_bytes(path, "audio mix adoption")
|
|
37
|
+
value = without_digests(value, "audio mix adoption")
|
|
38
|
+
require_fields(value, ["artifact", "schema_version", "prepared_receipt", "format", "segments",
|
|
39
|
+
"master_gain_db"], "audio mix adoption")
|
|
40
|
+
if value["artifact"] != "audio_mix_adoption" or type(value["schema_version"]) is not int \
|
|
41
|
+
or value["schema_version"] != 1:
|
|
42
|
+
raise ValueError("unsupported audio_mix_adoption schema")
|
|
43
|
+
input_video = require_local_path(input_video, "picture")
|
|
44
|
+
picture = probe_picture(input_video)
|
|
45
|
+
picture_samples, picture_summary = _picture_format(picture)
|
|
46
|
+
|
|
47
|
+
narration_path = require_local_path(narration_adoption_path, "narration adoption")
|
|
48
|
+
require_fields(value["format"], ["sample_rate", "channels", "total_samples"], "mix format")
|
|
49
|
+
mix_format = value["format"]
|
|
50
|
+
if mix_format != {"sample_rate": RATE, "channels": CHANNELS,
|
|
51
|
+
"total_samples": picture_samples}:
|
|
52
|
+
raise ValueError("mix format differs from the actual picture sample clock")
|
|
53
|
+
prepared_receipt = source_score.validate_prepared_receipt(
|
|
54
|
+
value["prepared_receipt"], mix_format
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
if not isinstance(value["segments"], list) or len(value["segments"]) != len(tts_segments):
|
|
58
|
+
raise ValueError("audio mix segments must exactly cover narration segments")
|
|
59
|
+
normalized = []
|
|
60
|
+
seen = set()
|
|
61
|
+
for adopted, segment in zip(value["segments"], tts_segments):
|
|
62
|
+
adopted = without_digests(adopted, "audio mix segment")
|
|
63
|
+
require_fields(adopted, ["index", "output_start_sample", "gain"], "audio mix segment")
|
|
64
|
+
index = require_integer(adopted["index"], "audio mix segment index")
|
|
65
|
+
if index in seen or index != segment["index"]:
|
|
66
|
+
raise ValueError("audio mix segment index/order differs from narration")
|
|
67
|
+
seen.add(index)
|
|
68
|
+
normalized.append({
|
|
69
|
+
"index": index,
|
|
70
|
+
"output_start_sample": require_integer(
|
|
71
|
+
adopted["output_start_sample"], "output_start_sample"),
|
|
72
|
+
"gain": require_number(adopted["gain"], "narration gain", 0, 16),
|
|
73
|
+
})
|
|
74
|
+
return {
|
|
75
|
+
"path": str(adoption_path),
|
|
76
|
+
"picture": {"path": str(input_video), "clock": picture_summary},
|
|
77
|
+
"picture_identity": picture,
|
|
78
|
+
"prepared_receipt": prepared_receipt["reference"],
|
|
79
|
+
"prepared": prepared_receipt["outputs"],
|
|
80
|
+
"narration_adoption": {"path": str(narration_path)},
|
|
81
|
+
"format": dict(mix_format), "segments": normalized,
|
|
82
|
+
"master_gain_db": require_number(value["master_gain_db"], "master_gain_db", -24, 24),
|
|
83
|
+
"runtime": None,
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _run(command, work_dir, label):
|
|
88
|
+
run_logged(command, work_dir, label, prefix="explicit_")
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _channels(path):
|
|
92
|
+
result = subprocess.run(
|
|
93
|
+
["ffprobe", "-v", "error", "-select_streams", "a:0", "-show_entries",
|
|
94
|
+
"stream=channels", "-of", "default=nw=1:nk=1", str(path)],
|
|
95
|
+
capture_output=True, text=True, timeout=600,
|
|
96
|
+
)
|
|
97
|
+
try:
|
|
98
|
+
channels = int(result.stdout.strip())
|
|
99
|
+
except ValueError as exc:
|
|
100
|
+
raise ValueError("narration channel probe failed") from exc
|
|
101
|
+
if result.returncode or channels not in {1, 2}:
|
|
102
|
+
raise ValueError("explicit narration supports only mono or stereo input")
|
|
103
|
+
return channels
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def render_explicit_mix(context, narration_context, tts_segments, work_dir):
|
|
107
|
+
"""Render complete direct 48 kHz placements, voice bus, premaster, and fixed master."""
|
|
108
|
+
directory = Path(work_dir).resolve() / ".explicit_audio_mix"
|
|
109
|
+
directory.mkdir(parents=True, exist_ok=False)
|
|
110
|
+
by_index = {item["index"]: item for item in narration_context["segments"]}
|
|
111
|
+
segment_memory = {item["index"]: item for item in tts_segments}
|
|
112
|
+
rendered = []
|
|
113
|
+
for position, adopted in enumerate(context["segments"]):
|
|
114
|
+
source = by_index[adopted["index"]]["snapshot"]["path"]
|
|
115
|
+
channels = _channels(source)
|
|
116
|
+
placed = directory / f"placed_{position:04d}_{adopted['index']}.wav"
|
|
117
|
+
if channels == 1:
|
|
118
|
+
channel_filter = (
|
|
119
|
+
"aformat=sample_fmts=flt,"
|
|
120
|
+
"pan=stereo|c0=0.7071067811865476*c0|c1=0.7071067811865476*c0,"
|
|
121
|
+
"aresample=48000:async=0:first_pts=0,aformat=sample_fmts=flt:"
|
|
122
|
+
"sample_rates=48000:channel_layouts=stereo"
|
|
123
|
+
)
|
|
124
|
+
matrix = "mono_equal_power"
|
|
125
|
+
else:
|
|
126
|
+
channel_filter = (
|
|
127
|
+
"aresample=48000:async=0:first_pts=0,aformat=sample_fmts=flt:"
|
|
128
|
+
"sample_rates=48000:channel_layouts=stereo"
|
|
129
|
+
)
|
|
130
|
+
matrix = "stereo_identity"
|
|
131
|
+
_run(["ffmpeg", "-nostdin", "-v", "error", "-n", "-i", str(source),
|
|
132
|
+
"-map", "0:a:0", "-af", channel_filter, "-c:a", CODEC, str(placed)],
|
|
133
|
+
directory, f"place_{position:04d}")
|
|
134
|
+
facts = source_score._output_facts(placed)
|
|
135
|
+
start = adopted["output_start_sample"]
|
|
136
|
+
end = start + facts["pcm"]["samples"]
|
|
137
|
+
if end > context["format"]["total_samples"]:
|
|
138
|
+
raise ValueError("complete narration segment does not fit the adopted mix clock")
|
|
139
|
+
rendered.append({**adopted, "output_end_sample": end, "input_channels": channels,
|
|
140
|
+
"channel_matrix": matrix, "placed": facts})
|
|
141
|
+
memory = segment_memory[adopted["index"]]
|
|
142
|
+
memory.update({
|
|
143
|
+
"placed_audio_path": str(placed),
|
|
144
|
+
"narration_conversion_path": str(placed),
|
|
145
|
+
"audio_duration": facts["pcm"]["samples"] / RATE,
|
|
146
|
+
"placed_audio_duration": facts["pcm"]["samples"] / RATE,
|
|
147
|
+
"actual_place_start": start / RATE, "actual_place_end": end / RATE,
|
|
148
|
+
"output_start_sample": start, "output_end_sample": end,
|
|
149
|
+
"adopted_gain": adopted["gain"],
|
|
150
|
+
"global_narration_speed": 1.0, "segment_tempo_factor": 1.0,
|
|
151
|
+
"effective_tempo": 1.0, "fit_status": "placed", "blocking": False,
|
|
152
|
+
"truncated": False, "truncate_reason": "none",
|
|
153
|
+
})
|
|
154
|
+
ordered = sorted(rendered, key=lambda item: item["output_start_sample"])
|
|
155
|
+
if any(current["output_start_sample"] < previous["output_end_sample"]
|
|
156
|
+
for previous, current in zip(ordered, ordered[1:])):
|
|
157
|
+
raise ValueError("explicit narration placements overlap")
|
|
158
|
+
|
|
159
|
+
total = context["format"]["total_samples"]
|
|
160
|
+
voice_bus = directory / "voice_bus.wav"
|
|
161
|
+
command = ["ffmpeg", "-nostdin", "-v", "error", "-n"]
|
|
162
|
+
for item in rendered:
|
|
163
|
+
command += ["-i", item["placed"]["path"]]
|
|
164
|
+
filters = [f"anullsrc=r={RATE}:cl=stereo,atrim=end_sample={total}[base]"]
|
|
165
|
+
labels = ["[base]"]
|
|
166
|
+
for position, item in enumerate(rendered):
|
|
167
|
+
filters.append(
|
|
168
|
+
f"[{position}:a]volume={item['gain']:.17g},"
|
|
169
|
+
f"adelay={item['output_start_sample']}S:all=1[v{position}]"
|
|
170
|
+
)
|
|
171
|
+
labels.append(f"[v{position}]")
|
|
172
|
+
filters.append("".join(labels) + f"amix=inputs={len(labels)}:duration=first:normalize=0,"
|
|
173
|
+
f"atrim=end_sample={total},aformat=sample_fmts=flt:sample_rates={RATE}:"
|
|
174
|
+
"channel_layouts=stereo[out]")
|
|
175
|
+
command += ["-filter_complex", ";".join(filters), "-map", "[out]", "-c:a", CODEC,
|
|
176
|
+
str(voice_bus)]
|
|
177
|
+
_run(command, directory, "voice_bus")
|
|
178
|
+
voice_facts = source_score._output_facts(voice_bus)
|
|
179
|
+
narration_binding.seal_render_inputs(narration_context, tts_segments, voice_bus)
|
|
180
|
+
narration_context["sealed"]["narration_bus"]["consumption_status"] = \
|
|
181
|
+
"CONSUMED_BY_EXPLICIT_MIX"
|
|
182
|
+
|
|
183
|
+
prepared = context["prepared"]["prepared_bed.wav"]["path"]
|
|
184
|
+
premaster = directory / "premaster.wav"
|
|
185
|
+
_run(["ffmpeg", "-nostdin", "-v", "error", "-n", "-i", prepared, "-i", str(voice_bus),
|
|
186
|
+
"-filter_complex", f"[0:a][1:a]amix=inputs=2:duration=first:normalize=0,"
|
|
187
|
+
f"atrim=end_sample={total},aformat=sample_fmts=flt:sample_rates={RATE}:"
|
|
188
|
+
"channel_layouts=stereo[out]", "-map", "[out]", "-c:a", CODEC, str(premaster)],
|
|
189
|
+
directory, "premaster")
|
|
190
|
+
master = directory / "master.wav"
|
|
191
|
+
gain = 10 ** (context["master_gain_db"] / 20)
|
|
192
|
+
_run(["ffmpeg", "-nostdin", "-v", "error", "-n", "-i", str(premaster),
|
|
193
|
+
"-af", f"volume={gain:.17g},aformat=sample_fmts=flt:sample_rates={RATE}:"
|
|
194
|
+
"channel_layouts=stereo", "-c:a", CODEC, str(master)], directory, "master")
|
|
195
|
+
runtime = {
|
|
196
|
+
"segments": rendered, "voice_bus": voice_facts,
|
|
197
|
+
"premaster": source_score._output_facts(premaster),
|
|
198
|
+
"master": source_score._output_facts(master), "master_gain_linear": gain,
|
|
199
|
+
}
|
|
200
|
+
if any(runtime[key]["pcm"]["samples"] != total
|
|
201
|
+
for key in ("voice_bus", "premaster", "master")):
|
|
202
|
+
raise ValueError("explicit mix derivative sample count differs from picture clock")
|
|
203
|
+
context["runtime"] = runtime
|
|
204
|
+
return runtime
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def finalize_binding(context, narration_record, rendered_output, final_output, work_dir):
|
|
208
|
+
"""Probe the rendered candidate and write ``audio_mix_binding.json`` into work_dir."""
|
|
209
|
+
if not context.get("runtime"):
|
|
210
|
+
raise RuntimeError("explicit audio mix must be rendered before finalization")
|
|
211
|
+
rendered = require_local_path(rendered_output, "rendered output")
|
|
212
|
+
output_picture = probe_picture(rendered)
|
|
213
|
+
input_clock = _picture_format(context["picture_identity"])[1]
|
|
214
|
+
output_clock = _picture_format(output_picture)[1]
|
|
215
|
+
for key in ("fps", "frame_count", "duration", "start"):
|
|
216
|
+
if output_clock[key] != input_clock[key]:
|
|
217
|
+
raise ValueError("rendered output picture frame clock changed")
|
|
218
|
+
packet_identity = (
|
|
219
|
+
"EXACT" if output_picture == context["picture_identity"]
|
|
220
|
+
else "REENCODED_CLOCK_MATCH"
|
|
221
|
+
)
|
|
222
|
+
audio = probe_audio_packets(rendered, 0)
|
|
223
|
+
validate_pair_timing(output_picture, audio)
|
|
224
|
+
decoded = Path(context["runtime"]["master"]["path"]).parent / "final_aac_decoded.wav"
|
|
225
|
+
_run(["ffmpeg", "-nostdin", "-v", "error", "-n", "-i", str(rendered),
|
|
226
|
+
"-map", "0:a:0", "-af", "aformat=sample_fmts=flt:sample_rates=48000:"
|
|
227
|
+
"channel_layouts=stereo", "-c:a", CODEC, str(decoded)], decoded.parent,
|
|
228
|
+
"final_decode")
|
|
229
|
+
context["runtime"]["final_decoded_pcm"] = source_score._output_facts(decoded)
|
|
230
|
+
report = {
|
|
231
|
+
"artifact": ARTIFACT, "schema_version": 1, "status": "FINALIZED",
|
|
232
|
+
"adoption": {"path": context["path"]},
|
|
233
|
+
"picture": context["picture"],
|
|
234
|
+
"output_picture": {
|
|
235
|
+
**output_clock, "packet_identity": packet_identity,
|
|
236
|
+
},
|
|
237
|
+
"prepared_receipt": context["prepared_receipt"],
|
|
238
|
+
"prepared": context["prepared"], "format": context["format"],
|
|
239
|
+
"segments": context["runtime"]["segments"],
|
|
240
|
+
"voice_bus": context["runtime"]["voice_bus"],
|
|
241
|
+
"premaster": context["runtime"]["premaster"],
|
|
242
|
+
"master": {**context["runtime"]["master"], "gain_db": context["master_gain_db"],
|
|
243
|
+
"gain_linear": context["runtime"]["master_gain_linear"]},
|
|
244
|
+
"narration_input_binding": {"path": narration_record["path"], "status": "FINALIZED"},
|
|
245
|
+
"final_output": {
|
|
246
|
+
"path": str(Path(final_output).resolve()),
|
|
247
|
+
"decoded_pcm": context["runtime"]["final_decoded_pcm"],
|
|
248
|
+
"audio_stream": {
|
|
249
|
+
"decoder": audio["decoder"], "packet_count": audio["packet_count"],
|
|
250
|
+
"payload_bytes": audio["payload_bytes"],
|
|
251
|
+
"start_time": audio["start_time"], "duration": audio["duration"],
|
|
252
|
+
},
|
|
253
|
+
},
|
|
254
|
+
"direct_listening": "NOT_CHECKED", "normal_speed_review": "NOT_CHECKED",
|
|
255
|
+
"release_approved": False,
|
|
256
|
+
}
|
|
257
|
+
write_json_atomic(Path(work_dir).resolve() / FILENAME, report)
|
|
258
|
+
return report
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def binding_record(work_dir):
|
|
262
|
+
"""The finalized mix binding's path and status; None when absent or malformed."""
|
|
263
|
+
path = Path(work_dir) / FILENAME
|
|
264
|
+
if not path.is_file():
|
|
265
|
+
return None
|
|
266
|
+
try:
|
|
267
|
+
report = json.loads(path.read_text(encoding="utf-8"))
|
|
268
|
+
except (UnicodeDecodeError, json.JSONDecodeError):
|
|
269
|
+
return None
|
|
270
|
+
if not isinstance(report, dict) or report.get("artifact") != ARTIFACT \
|
|
271
|
+
or report.get("schema_version") != 1 or report.get("status") != "FINALIZED":
|
|
272
|
+
return None
|
|
273
|
+
final = report.get("final_output")
|
|
274
|
+
if not isinstance(final, dict) or not Path(str(final.get("path", ""))).is_file():
|
|
275
|
+
return None
|
|
276
|
+
narration = report.get("narration_input_binding")
|
|
277
|
+
if not isinstance(narration, dict) or not Path(str(narration.get("path", ""))).is_file():
|
|
278
|
+
return None
|
|
279
|
+
return {"path": str(path.resolve()), "status": "FINALIZED"}
|