@oh-story/dsh 0.1.8 → 0.1.10
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 +237 -84
- package/lib/client.js.map +3 -3
- package/lib/drama/manifest.json +183 -77
- package/lib/drama/skills/short-drama/SKILL.md +5 -1
- package/lib/drama/skills/short-drama/scripts/creator_markdown_check.py +140 -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 +28 -0
- package/lib/drama/skills/short-drama-assets/references/continuity-lock.md +8 -2
- package/lib/drama/skills/short-drama-assets/references/voice-direction.md +28 -0
- package/lib/drama/skills/short-drama-edit/SKILL.md +146 -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 +38 -0
- package/lib/drama/skills/short-drama-edit/references/delivery-verify.md +29 -0
- package/lib/drama/skills/short-drama-edit/references/generated-footage.md +54 -0
- package/lib/drama/skills/short-drama-edit/references/sound-and-subtitles.md +60 -0
- package/lib/drama/skills/short-drama-edit/references/stage-contract.md +51 -0
- package/lib/drama/skills/short-drama-edit/scripts/edit_tool.py +1156 -0
- package/lib/drama/skills/short-drama-edit/scripts/selftest.py +520 -0
- package/lib/drama/skills/short-drama-image-prompts/SKILL.md +2 -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-image-prompts/references/common-recipe.md +11 -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 +29 -4
- package/lib/drama/skills/short-drama-produce/references/adapter-contract.md +22 -0
- package/lib/drama/skills/short-drama-produce/references/providers/minimax-h3-video.md +13 -12
- package/lib/drama/skills/short-drama-produce/references/providers/minimax-speech.md +41 -0
- 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 +199 -3
- package/lib/drama/skills/short-drama-review/SKILL.md +2 -0
- package/lib/drama/skills/short-drama-review/references/production-quality-gates.md +7 -0
- package/lib/drama/skills/short-drama-review/references/rubric-visual-motion.md +10 -0
- package/lib/drama/skills/short-drama-storyboard/SKILL.md +12 -1
- package/lib/drama/skills/short-drama-storyboard/assets/keyframe-prompts.md +4 -0
- package/lib/drama/skills/short-drama-storyboard/references/lighting-craft.md +39 -0
- package/lib/drama/skills/short-drama-storyboard/references/shot-craft.md +78 -2
- package/lib/drama/skills/short-drama-storyboard/references/stage-contract.md +2 -0
- package/lib/drama/skills/short-drama-video-prompts/SKILL.md +12 -5
- package/lib/drama/skills/short-drama-video-prompts/references/camera-audio-continuity.md +17 -0
- package/lib/drama/skills/short-drama-video-prompts/references/minimax-h3.md +76 -7
- package/lib/drama/skills/short-drama-video-prompts/references/motion-recipe.md +5 -2
- package/lib/drama/skills/short-drama-video-prompts/references/performance-action-timing.md +21 -0
- package/lib/drama/skills/short-drama-video-prompts/references/production-prompt-grammar.md +6 -17
- package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.0.md +12 -3
- package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.5.md +61 -5
- package/lib/drama/skills/short-drama-video-prompts/references/stage-contract.md +3 -3
- package/lib/drama/skills/short-drama-write/SKILL.md +11 -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 +290 -90
- 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 +9 -35
- 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 +17 -35
- 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 +16 -34
- package/lib/novel-to-game/skills/game-concept/references/concept-method.md +28 -75
- package/lib/novel-to-game/skills/game-qa/SKILL.md +9 -20
- package/lib/novel-to-game/skills/game-qa/references/test-design-method.md +10 -6
- package/lib/novel-to-game/skills/game-world-design/SKILL.md +19 -32
- 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 +9 -18
- 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 +10 -27
- package/lib/novel-to-game/skills/novel-to-game/references/intake-method.md +5 -6
- package/lib/novel-to-game/skills/novel-to-game/references/pipeline-contract.md +6 -11
- package/lib/oh-story/manifest.json +419 -254
- package/lib/oh-story/roles/chapter-extractor.md +177 -237
- package/lib/oh-story/roles/character-designer.md +1 -1
- package/lib/oh-story/roles/consistency-checker.md +4 -4
- package/lib/oh-story/roles/narrative-writer.md +56 -243
- package/lib/oh-story/roles/story-architect.md +6 -6
- package/lib/oh-story/roles/story-explorer.md +11 -11
- package/lib/oh-story/skills/story/SKILL.md +13 -7
- package/lib/oh-story/skills/story/VERSION +1 -1
- package/lib/oh-story/skills/story/references/author-memory.md +61 -29
- 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 +54 -229
- package/lib/oh-story/skills/story-deslop/references/anti-ai-writing.md +29 -56
- package/lib/oh-story/skills/story-deslop/references/author-memory.md +61 -29
- 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/author_memory_commit.py +934 -170
- 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 +78 -82
- package/lib/oh-story/skills/story-import/references/character-state-reverse.md +1 -1
- package/lib/oh-story/skills/story-import/references/format-and-structure.md +2 -2
- 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 +10 -8
- package/lib/oh-story/skills/story-import/references/tracking-initialization.md +31 -0
- package/lib/oh-story/skills/story-import/references/tracking-transaction.md +9 -34
- package/lib/oh-story/skills/story-import/scripts/check-outline-contract.js +197 -15
- package/lib/oh-story/skills/story-import/scripts/tracking_commit.py +210 -2
- package/lib/oh-story/skills/story-import/scripts/wordcount_core.py +3 -0
- package/lib/oh-story/skills/story-long-analyze/SKILL.md +91 -258
- package/lib/oh-story/skills/story-long-analyze/references/author-facing.md +245 -0
- package/lib/oh-story/skills/story-long-analyze/references/deconstruction-notes.md +2 -2
- package/lib/oh-story/skills/story-long-analyze/references/inspiration-library.md +105 -0
- package/lib/oh-story/skills/story-long-analyze/references/material-decomposition.md +113 -105
- package/lib/oh-story/skills/story-long-analyze/references/output-templates.md +169 -125
- package/lib/oh-story/skills/story-long-analyze/references/pipeline-ops.md +172 -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/style-profile-generator.md +42 -132
- package/lib/oh-story/skills/story-long-analyze/references/style-profile-protocol.md +5 -5
- 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 +1106 -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 +4 -1
- package/lib/oh-story/skills/story-long-write/SKILL.md +52 -216
- package/lib/oh-story/skills/story-long-write/references/anti-ai-writing.md +29 -56
- package/lib/oh-story/skills/story-long-write/references/artifact-protocols.md +72 -12
- package/lib/oh-story/skills/story-long-write/references/author-memory.md +61 -29
- 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/cross-book-recall.md +26 -0
- package/lib/oh-story/skills/story-long-write/references/dialogue-mastery.md +10 -4
- package/lib/oh-story/skills/story-long-write/references/emotional-methods.md +2 -2
- package/lib/oh-story/skills/story-long-write/references/long-chapter-quality.md +6 -15
- 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 +110 -0
- package/lib/oh-story/skills/story-long-write/references/reader-contract-and-progression.md +12 -4
- package/lib/oh-story/skills/story-long-write/references/reference-index.md +86 -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 +9 -34
- package/lib/oh-story/skills/story-long-write/references/workflow-chapter.md +88 -118
- package/lib/oh-story/skills/story-long-write/references/workflow-daily.md +55 -119
- package/lib/oh-story/skills/story-long-write/references/workflow-revision.md +15 -11
- package/lib/oh-story/skills/story-long-write/references/workflow-setup.md +73 -54
- 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 +63 -4
- 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_writer_prompt.py +531 -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 +197 -15
- 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 +285 -0
- package/lib/oh-story/skills/story-long-write/scripts/storyctl.py +58 -0
- package/lib/oh-story/skills/story-long-write/scripts/style-whitelist.js +39 -0
- package/lib/oh-story/skills/story-long-write/scripts/tracking_commit.py +210 -2
- package/lib/oh-story/skills/story-long-write/scripts/wordcount_core.py +3 -0
- package/lib/oh-story/skills/story-review/SKILL.md +80 -87
- package/lib/oh-story/skills/story-review/references/anti-ai-writing.md +29 -56
- package/lib/oh-story/skills/story-review/references/author-memory.md +61 -29
- package/lib/oh-story/skills/story-review/references/banned-words.md +3 -1
- package/lib/oh-story/skills/story-review/references/dialogue-mastery.md +10 -4
- package/lib/oh-story/skills/story-review/references/review-quality.md +6 -12
- 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 +9 -34
- 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 +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-review/scripts/tracking_commit.py +210 -2
- package/lib/oh-story/skills/story-review/scripts/wordcount_core.py +3 -0
- package/lib/oh-story/skills/story-setup/SKILL.md +58 -43
- package/lib/oh-story/skills/story-setup/references/agent-references/anti-ai-writing.md +29 -56
- 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 +10 -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/long-emotional-methods.md +2 -2
- package/lib/oh-story/skills/story-setup/references/agent-references/long-quality.md +1 -1
- 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 +63 -4
- package/lib/oh-story/skills/story-setup/references/diagnostics.md +38 -0
- package/lib/oh-story/skills/story-short-analyze/SKILL.md +22 -3
- package/lib/oh-story/skills/story-short-analyze/references/analysis-writing-techniques.md +1 -1
- package/lib/oh-story/skills/story-short-analyze/references/material-decomposition.md +5 -1
- package/lib/oh-story/skills/story-short-analyze/references/source-story-quality.md +2 -2
- package/lib/oh-story/skills/story-short-scan/SKILL.md +5 -1
- package/lib/oh-story/skills/story-short-write/SKILL.md +15 -324
- package/lib/oh-story/skills/story-short-write/references/author-memory.md +61 -29
- 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 +10 -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 +55 -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 +78 -0
- package/lib/oh-story/skills/story-short-write/references/workflow-draft.md +126 -0
- package/lib/oh-story/skills/story-short-write/references/workflow-revision.md +32 -0
- package/lib/oh-story/skills/story-short-write/references/writing-workflow.md +5 -5
- 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 +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/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 +33 -0
- package/lib/types/client/production-runtime.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 +52 -0
- 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/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 +45 -45
- package/lib/novel-to-game/skills/game-world-design/references/dialogue-design-method.md +0 -59
- 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 -42
- package/lib/oh-story/skills/story-long-write/references/state-tracking.md +0 -108
|
@@ -10,7 +10,6 @@ from assemble_constants import (
|
|
|
10
10
|
VISUAL_OVERLAYS,
|
|
11
11
|
VISUAL_QC,
|
|
12
12
|
_SUPPORTED_VISUAL_OVERLAY_TYPES,
|
|
13
|
-
_VISUAL_DELIVERY_FORBIDDEN_KEYS,
|
|
14
13
|
)
|
|
15
14
|
from audio_automation import coalesce_duck_windows
|
|
16
15
|
from audio_mix import _seg_place_window
|
|
@@ -21,17 +20,24 @@ from source_subtitles import (
|
|
|
21
20
|
_source_subtitle_mask_policy,
|
|
22
21
|
)
|
|
23
22
|
from subtitle_core import (
|
|
23
|
+
_measured_subtitle_band,
|
|
24
24
|
_measured_subtitle_safe_area,
|
|
25
25
|
_normalize_subtitle_text,
|
|
26
26
|
_style_for_measured_subtitle_band,
|
|
27
27
|
_subtitle_style_config,
|
|
28
|
-
_validate_measured_subtitle_coordinate_domain,
|
|
29
28
|
)
|
|
30
29
|
|
|
30
|
+
_OVERFLOW_KINDS = {
|
|
31
|
+
"max_lines_exceeded": "line_count",
|
|
32
|
+
"safe_width_exceeded": "line_width",
|
|
33
|
+
"safe_height_exceeded": "safe_area",
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
31
37
|
def _visual_text_units(text):
|
|
32
38
|
"""Approximate visual text width in em units for deterministic geometry QC."""
|
|
33
39
|
units = 0.0
|
|
34
|
-
for ch in
|
|
40
|
+
for ch in text:
|
|
35
41
|
if ch.isspace():
|
|
36
42
|
units += 0.35
|
|
37
43
|
elif ord(ch) < 128:
|
|
@@ -41,51 +47,37 @@ def _visual_text_units(text):
|
|
|
41
47
|
return units
|
|
42
48
|
|
|
43
49
|
|
|
44
|
-
def _subtitle_layout_qc(entries,
|
|
50
|
+
def _subtitle_layout_qc(entries, style, safe_area=None):
|
|
45
51
|
"""Machine-check subtitle safe-area/multiline/overflow facts for visual_qc.json."""
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
max_lines = int(CONFIG.get("subtitle_max_lines", 2) or 2)
|
|
55
|
-
usable_w = max(1.0, play_x - margin_l - margin_r)
|
|
56
|
-
if safe_area:
|
|
52
|
+
play_x = int(style["play_res_x"])
|
|
53
|
+
play_y = int(style["play_res_y"])
|
|
54
|
+
margin_l = int(style["margin_l"])
|
|
55
|
+
margin_r = int(style["margin_r"])
|
|
56
|
+
margin_v = int(style["margin_v"])
|
|
57
|
+
font_size = float(style["font_size"])
|
|
58
|
+
max_lines = CONFIG["subtitle_max_lines"]
|
|
59
|
+
if safe_area is None:
|
|
57
60
|
safe_area = {
|
|
58
|
-
"x":
|
|
59
|
-
"y":
|
|
60
|
-
"width":
|
|
61
|
-
"height":
|
|
61
|
+
"x": margin_l,
|
|
62
|
+
"y": margin_v,
|
|
63
|
+
"width": max(1, play_x - margin_l - margin_r),
|
|
64
|
+
"height": max(1, play_y - 2 * margin_v),
|
|
62
65
|
"bottom_margin": margin_v,
|
|
63
66
|
}
|
|
64
|
-
|
|
65
|
-
else:
|
|
66
|
-
safe_area = {
|
|
67
|
-
"x": margin_l,
|
|
68
|
-
"y": margin_v,
|
|
69
|
-
"width": max(1, play_x - margin_l - margin_r),
|
|
70
|
-
"height": max(1, play_y - 2 * margin_v),
|
|
71
|
-
"bottom_margin": margin_v,
|
|
72
|
-
}
|
|
67
|
+
usable_w = float(safe_area["width"])
|
|
73
68
|
line_h = font_size * 1.25
|
|
74
69
|
overflow_entries = []
|
|
75
70
|
violations = []
|
|
76
71
|
multi_line_entries = []
|
|
77
72
|
max_observed_lines = 0
|
|
78
73
|
entry_facts = []
|
|
79
|
-
for i, entry in enumerate(entries
|
|
80
|
-
raw_text = _normalize_subtitle_text(entry
|
|
81
|
-
lines = [ln for ln in re.split(r"(?:\\N|\n)+", raw_text) if ln != ""]
|
|
82
|
-
if not lines:
|
|
83
|
-
lines = [""]
|
|
74
|
+
for i, entry in enumerate(entries):
|
|
75
|
+
raw_text = _normalize_subtitle_text(entry["text"])
|
|
76
|
+
lines = [ln for ln in re.split(r"(?:\\N|\n)+", raw_text) if ln != ""] or [""]
|
|
84
77
|
line_count = len(lines)
|
|
85
78
|
max_observed_lines = max(max_observed_lines, line_count)
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
band_h = line_count * line_h + float(style.get("outline", 0)) * 2 + float(style.get("shadow", 0))
|
|
79
|
+
max_w = max(_visual_text_units(line) * font_size for line in lines)
|
|
80
|
+
band_h = line_count * line_h + float(style["outline"]) * 2 + float(style["shadow"])
|
|
89
81
|
overflow_reasons = []
|
|
90
82
|
if line_count > max_lines:
|
|
91
83
|
overflow_reasons.append("max_lines_exceeded")
|
|
@@ -95,8 +87,8 @@ def _subtitle_layout_qc(entries, canvas=None, style=None, safe_area=None):
|
|
|
95
87
|
overflow_reasons.append("safe_height_exceeded")
|
|
96
88
|
fact = {
|
|
97
89
|
"index": i,
|
|
98
|
-
"start": round(float(entry
|
|
99
|
-
"end": round(float(entry
|
|
90
|
+
"start": round(float(entry["start"]), 3),
|
|
91
|
+
"end": round(float(entry["end"]), 3),
|
|
100
92
|
"line_count": line_count,
|
|
101
93
|
"max_line_width": round(max_w, 2),
|
|
102
94
|
"safe_width": round(usable_w, 2),
|
|
@@ -109,23 +101,20 @@ def _subtitle_layout_qc(entries, canvas=None, style=None, safe_area=None):
|
|
|
109
101
|
multi_line_entries.append(i)
|
|
110
102
|
if overflow_reasons:
|
|
111
103
|
overflow_entries.append(fact)
|
|
112
|
-
|
|
113
|
-
kind
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
"safe_height_exceeded": "safe_area",
|
|
117
|
-
}.get(reason, "safe_area")
|
|
118
|
-
violations.append({"index": i, "kind": kind, "reason": reason})
|
|
104
|
+
violations.extend(
|
|
105
|
+
{"index": i, "kind": _OVERFLOW_KINDS[reason], "reason": reason}
|
|
106
|
+
for reason in overflow_reasons
|
|
107
|
+
)
|
|
119
108
|
return {
|
|
120
|
-
"enabled":
|
|
121
|
-
"renderer": "ass" if CONFIG
|
|
109
|
+
"enabled": CONFIG["burn_subtitles"],
|
|
110
|
+
"renderer": "ass" if CONFIG["burn_subtitles"] else "sidecar_srt",
|
|
122
111
|
"style": {
|
|
123
112
|
"font_size": int(font_size),
|
|
124
|
-
"max_chars": int(style
|
|
113
|
+
"max_chars": int(style["max_chars"]),
|
|
125
114
|
"max_lines": max_lines,
|
|
126
115
|
"play_res_x": play_x,
|
|
127
116
|
"play_res_y": play_y,
|
|
128
|
-
"alignment": int(style
|
|
117
|
+
"alignment": int(style["alignment"]),
|
|
129
118
|
"margin_l": margin_l,
|
|
130
119
|
"margin_r": margin_r,
|
|
131
120
|
"margin_v": margin_v,
|
|
@@ -143,45 +132,35 @@ def _subtitle_layout_qc(entries, canvas=None, style=None, safe_area=None):
|
|
|
143
132
|
}
|
|
144
133
|
|
|
145
134
|
|
|
146
|
-
def _load_visual_overlays(work_dir
|
|
135
|
+
def _load_visual_overlays(work_dir):
|
|
136
|
+
"""Return (overlays, source) from the canonical visual_overlays.json handoff."""
|
|
147
137
|
path = Path(work_dir) / VISUAL_OVERLAYS
|
|
148
138
|
if not path.exists():
|
|
149
|
-
|
|
150
|
-
return result if with_source else result[0]
|
|
139
|
+
return [], {"present": False, "path": str(path), "fingerprint": None}
|
|
151
140
|
try:
|
|
152
141
|
data = json.loads(path.read_text(encoding="utf-8"))
|
|
153
|
-
except
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
142
|
+
except json.JSONDecodeError as exc:
|
|
143
|
+
raise ValueError(f"{path}: JSON 无效") from exc
|
|
144
|
+
if (
|
|
145
|
+
not isinstance(data, dict)
|
|
146
|
+
or type(data.get("schema_version")) is not int
|
|
147
|
+
or data["schema_version"] != 1
|
|
148
|
+
or not isinstance(data.get("overlays"), list)
|
|
149
|
+
or not all(isinstance(item, dict) for item in data["overlays"])
|
|
150
|
+
):
|
|
151
|
+
raise ValueError(f"{path}: visual_overlays.json schema 无效")
|
|
162
152
|
source = {
|
|
163
153
|
"present": True,
|
|
164
154
|
"path": str(path),
|
|
165
155
|
"fingerprint": _artifact_fingerprint(path),
|
|
166
|
-
"schema_version":
|
|
156
|
+
"schema_version": 1,
|
|
167
157
|
}
|
|
168
|
-
|
|
169
|
-
valid_schema_version = (
|
|
170
|
-
isinstance(schema_version, int)
|
|
171
|
-
and not isinstance(schema_version, bool)
|
|
172
|
-
and schema_version == 1
|
|
173
|
-
)
|
|
174
|
-
if isinstance(data, dict) and valid_schema_version and isinstance(data.get("overlays"), list):
|
|
175
|
-
overlays = data["overlays"]
|
|
176
|
-
else:
|
|
177
|
-
overlays = []
|
|
178
|
-
source["load_error"] = "invalid_schema"
|
|
179
|
-
return (overlays, source) if with_source else overlays
|
|
158
|
+
return data["overlays"], source
|
|
180
159
|
|
|
181
160
|
|
|
182
161
|
def _escape_drawtext_text(text):
|
|
183
162
|
return (
|
|
184
|
-
|
|
163
|
+
text
|
|
185
164
|
.replace("\\", "\\\\")
|
|
186
165
|
.replace(":", "\\:")
|
|
187
166
|
.replace("'", "\\'")
|
|
@@ -191,44 +170,30 @@ def _escape_drawtext_text(text):
|
|
|
191
170
|
|
|
192
171
|
|
|
193
172
|
def _overlay_time_window(overlay, video_duration):
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
start = 0.0
|
|
198
|
-
try:
|
|
199
|
-
end = float(overlay.get("end", video_duration) or video_duration)
|
|
200
|
-
except (TypeError, ValueError):
|
|
201
|
-
end = float(video_duration or 0.0)
|
|
202
|
-
end = max(start, end)
|
|
203
|
-
return start, end
|
|
173
|
+
start = float(overlay.get("start", 0.0))
|
|
174
|
+
end = float(overlay.get("end", video_duration))
|
|
175
|
+
return start, max(start, end)
|
|
204
176
|
|
|
205
177
|
|
|
206
178
|
def _overlay_bbox(overlay, canvas, *, default_y):
|
|
207
|
-
width =
|
|
208
|
-
height =
|
|
209
|
-
text =
|
|
210
|
-
font_size = int(overlay.get("font_size"
|
|
179
|
+
width = canvas["width"]
|
|
180
|
+
height = canvas["height"]
|
|
181
|
+
text = overlay["text"]
|
|
182
|
+
font_size = int(overlay.get("font_size", max(18, round(height * 0.045))))
|
|
211
183
|
lines = [ln for ln in text.splitlines() if ln.strip()] or [text]
|
|
212
|
-
max_w = max(
|
|
184
|
+
max_w = max(_visual_text_units(ln) * font_size for ln in lines)
|
|
213
185
|
text_h = len(lines) * font_size * 1.25
|
|
214
|
-
if overlay
|
|
186
|
+
if overlay["type"] == "top_title":
|
|
215
187
|
x = max(0.0, (width - max_w) / 2)
|
|
216
|
-
y = float(overlay.get("y", default_y)
|
|
188
|
+
y = float(overlay.get("y", default_y))
|
|
217
189
|
else:
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
x = width * 0.08
|
|
226
|
-
try:
|
|
227
|
-
y = float(raw_y)
|
|
228
|
-
if 0.0 <= y <= 1.0:
|
|
229
|
-
y *= height
|
|
230
|
-
except (TypeError, ValueError):
|
|
231
|
-
y = height * 0.25
|
|
190
|
+
# Fractions of the canvas in [0, 1] are normalized coordinates; larger values are pixels.
|
|
191
|
+
x = float(overlay.get("x", 0.08))
|
|
192
|
+
y = float(overlay.get("y", 0.25))
|
|
193
|
+
if 0.0 <= x <= 1.0:
|
|
194
|
+
x *= width
|
|
195
|
+
if 0.0 <= y <= 1.0:
|
|
196
|
+
y *= height
|
|
232
197
|
return {
|
|
233
198
|
"x": round(x, 2),
|
|
234
199
|
"y": round(y, 2),
|
|
@@ -246,19 +211,15 @@ def _visual_overlay_filters(work_dir, canvas, video_duration):
|
|
|
246
211
|
Only two semantic renderers are supported: top_title and inline_label_or_callout.
|
|
247
212
|
Unsupported types are QC-blocking and deliberately do not silently render.
|
|
248
213
|
"""
|
|
249
|
-
overlays, source = _load_visual_overlays(work_dir
|
|
250
|
-
|
|
251
|
-
default_top_y = max(24, round(height * 0.05))
|
|
214
|
+
overlays, source = _load_visual_overlays(work_dir)
|
|
215
|
+
default_top_y = max(24, round(canvas["height"] * 0.05))
|
|
252
216
|
filters = []
|
|
253
217
|
facts = []
|
|
254
218
|
unsupported = []
|
|
255
219
|
overflow = []
|
|
256
220
|
for idx, overlay in enumerate(overlays):
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
continue
|
|
260
|
-
typ = str(overlay.get("type") or "").strip()
|
|
261
|
-
text = str(overlay.get("text") or "").strip()
|
|
221
|
+
typ = overlay.get("type")
|
|
222
|
+
text = overlay.get("text", "").strip()
|
|
262
223
|
if typ not in _SUPPORTED_VISUAL_OVERLAY_TYPES:
|
|
263
224
|
unsupported.append({"index": idx, "type": typ, "reason": "unsupported_overlay_type"})
|
|
264
225
|
continue
|
|
@@ -297,9 +258,8 @@ def _visual_overlay_filters(work_dir, canvas, video_duration):
|
|
|
297
258
|
})
|
|
298
259
|
qc = {
|
|
299
260
|
"source": source,
|
|
300
|
-
"load_error": source.get("load_error"),
|
|
301
261
|
"supported_types": sorted(_SUPPORTED_VISUAL_OVERLAY_TYPES),
|
|
302
|
-
"present":
|
|
262
|
+
"present": source["present"],
|
|
303
263
|
"count": len(overlays),
|
|
304
264
|
"rendered": len(facts),
|
|
305
265
|
"facts": facts,
|
|
@@ -309,49 +269,33 @@ def _visual_overlay_filters(work_dir, canvas, video_duration):
|
|
|
309
269
|
return filters, qc
|
|
310
270
|
|
|
311
271
|
|
|
312
|
-
def _visual_qc_has_forbidden_delivery_facts(value):
|
|
313
|
-
if isinstance(value, dict):
|
|
314
|
-
for key, child in value.items():
|
|
315
|
-
if key in _VISUAL_DELIVERY_FORBIDDEN_KEYS:
|
|
316
|
-
return True
|
|
317
|
-
if _visual_qc_has_forbidden_delivery_facts(child):
|
|
318
|
-
return True
|
|
319
|
-
elif isinstance(value, list):
|
|
320
|
-
return any(_visual_qc_has_forbidden_delivery_facts(item) for item in value)
|
|
321
|
-
return False
|
|
322
|
-
|
|
323
|
-
|
|
324
272
|
def _build_visual_qc(tts_segments, work_dir, video_duration, canvas, *, overlay_qc=None, mask_filter=None):
|
|
325
273
|
entries = _combined_subtitle_entries(tts_segments, work_dir, video_duration)
|
|
326
274
|
style = _style_for_measured_subtitle_band(_subtitle_style_config(canvas), canvas)
|
|
327
275
|
subtitle_layout = _subtitle_layout_qc(
|
|
328
|
-
entries,
|
|
276
|
+
entries, style, safe_area=_measured_subtitle_safe_area(style, canvas)
|
|
329
277
|
)
|
|
330
278
|
mask = _source_subtitle_mask_policy(work_dir)
|
|
331
|
-
ratio = None
|
|
332
|
-
if mask.get("active"):
|
|
333
|
-
ratio = max(0.0, min(0.5, float(CONFIG.get("source_subtitle_mask_ratio", 0.14) or 0.0)))
|
|
334
279
|
mask.update({
|
|
335
|
-
"ratio":
|
|
280
|
+
"ratio": min(0.5, CONFIG["source_subtitle_mask_ratio"]) if mask["active"] else None,
|
|
336
281
|
"filter": "drawbox" if mask_filter else None,
|
|
337
|
-
"opacity":
|
|
338
|
-
"timing":
|
|
339
|
-
"subtitle_y_top":
|
|
340
|
-
"subtitle_y_bot":
|
|
282
|
+
"opacity": CONFIG["subtitle_mask_opacity"],
|
|
283
|
+
"timing": CONFIG["source_subtitle_mask_timing"],
|
|
284
|
+
"subtitle_y_top": CONFIG["subtitle_y_top"],
|
|
285
|
+
"subtitle_y_bot": CONFIG["subtitle_y_bot"],
|
|
341
286
|
})
|
|
342
|
-
|
|
287
|
+
if overlay_qc is None:
|
|
288
|
+
overlay_qc = _visual_overlay_filters(work_dir, canvas, video_duration)[1]
|
|
343
289
|
blocking_codes = []
|
|
344
|
-
if mask
|
|
290
|
+
if mask["blocking"]:
|
|
345
291
|
blocking_codes.append("mask_policy_not_explicit")
|
|
346
|
-
if subtitle_layout
|
|
292
|
+
if subtitle_layout["overflow"]:
|
|
347
293
|
blocking_codes.append("subtitle_overflow")
|
|
348
|
-
if overlay_qc
|
|
349
|
-
blocking_codes.append("invalid_visual_overlays_json")
|
|
350
|
-
if overlay_qc.get("unsupported"):
|
|
294
|
+
if overlay_qc["unsupported"]:
|
|
351
295
|
blocking_codes.append("unsupported_visual_overlay")
|
|
352
|
-
if overlay_qc
|
|
296
|
+
if overlay_qc["overflow"]:
|
|
353
297
|
blocking_codes.append("visual_overlay_overflow")
|
|
354
|
-
|
|
298
|
+
return {
|
|
355
299
|
"schema_version": 1,
|
|
356
300
|
"artifact": VISUAL_QC,
|
|
357
301
|
"verdict": "FAIL" if blocking_codes else "PASS",
|
|
@@ -359,36 +303,31 @@ def _build_visual_qc(tts_segments, work_dir, video_duration, canvas, *, overlay_
|
|
|
359
303
|
"blocking_codes": blocking_codes,
|
|
360
304
|
"geometry": {
|
|
361
305
|
"canvas": {
|
|
362
|
-
"width":
|
|
363
|
-
"height":
|
|
364
|
-
"fps":
|
|
306
|
+
"width": canvas["width"],
|
|
307
|
+
"height": canvas["height"],
|
|
308
|
+
"fps": canvas["fps"],
|
|
365
309
|
},
|
|
366
310
|
"storage": {
|
|
367
|
-
"width":
|
|
368
|
-
"height":
|
|
311
|
+
"width": canvas["storage_width"],
|
|
312
|
+
"height": canvas["storage_height"],
|
|
369
313
|
},
|
|
370
|
-
"rotation":
|
|
371
|
-
"sample_aspect_ratio": canvas
|
|
372
|
-
"display_aspect_ratio": canvas
|
|
314
|
+
"rotation": canvas["rotation"],
|
|
315
|
+
"sample_aspect_ratio": canvas["sample_aspect_ratio"],
|
|
316
|
+
"display_aspect_ratio": canvas["display_aspect_ratio"],
|
|
373
317
|
},
|
|
374
318
|
"subtitles": subtitle_layout,
|
|
375
319
|
"mask": mask,
|
|
376
320
|
"overlays": overlay_qc,
|
|
377
321
|
"summary": {
|
|
378
|
-
"subtitle_entries": subtitle_layout
|
|
379
|
-
"subtitle_overflow":
|
|
380
|
-
"subtitle_multi_line":
|
|
381
|
-
"mask_policy": mask
|
|
382
|
-
"mask_active":
|
|
383
|
-
"overlay_rendered":
|
|
384
|
-
"overlay_unsupported": len(overlay_qc
|
|
322
|
+
"subtitle_entries": subtitle_layout["entries"],
|
|
323
|
+
"subtitle_overflow": subtitle_layout["overflow"],
|
|
324
|
+
"subtitle_multi_line": subtitle_layout["multi_line"],
|
|
325
|
+
"mask_policy": mask["policy"],
|
|
326
|
+
"mask_active": mask["active"],
|
|
327
|
+
"overlay_rendered": overlay_qc["rendered"],
|
|
328
|
+
"overlay_unsupported": len(overlay_qc["unsupported"]),
|
|
385
329
|
},
|
|
386
330
|
}
|
|
387
|
-
if _visual_qc_has_forbidden_delivery_facts(qc):
|
|
388
|
-
qc["verdict"] = "FAIL"
|
|
389
|
-
qc["blocking"] = True
|
|
390
|
-
qc["blocking_codes"].append("visual_qc_contains_delivery_fact")
|
|
391
|
-
return qc
|
|
392
331
|
|
|
393
332
|
|
|
394
333
|
def _write_visual_qc(work_dir, qc):
|
|
@@ -427,9 +366,7 @@ def _output_downscale_filter(max_h):
|
|
|
427
366
|
return f"scale=-2:'2*trunc(min(ih,{max_h})/2)':flags=lanczos"
|
|
428
367
|
|
|
429
368
|
|
|
430
|
-
def _source_subtitle_mask_filter(
|
|
431
|
-
canvas=None, work_dir=None, tts_segments=None, video_duration=None
|
|
432
|
-
):
|
|
369
|
+
def _source_subtitle_mask_filter(canvas, work_dir, tts_segments, video_duration):
|
|
433
370
|
"""Return source-subtitle drawbox filters, optionally scoped to narration windows.
|
|
434
371
|
|
|
435
372
|
Many source videos (e.g. 庆余年) ship hardcoded subtitles; without this the recap
|
|
@@ -438,71 +375,56 @@ def _source_subtitle_mask_filter(
|
|
|
438
375
|
remain configurable.
|
|
439
376
|
"""
|
|
440
377
|
policy = _source_subtitle_mask_policy(work_dir)
|
|
441
|
-
if not policy
|
|
378
|
+
if not policy["active"]:
|
|
442
379
|
return None
|
|
443
|
-
opacity =
|
|
444
|
-
|
|
445
|
-
|
|
380
|
+
opacity = CONFIG["subtitle_mask_opacity"]
|
|
381
|
+
timing = CONFIG["source_subtitle_mask_timing"]
|
|
382
|
+
if timing not in {"all", "narration"}:
|
|
383
|
+
raise ValueError(f"SOURCE_SUBTITLE_MASK_TIMING 必须是 all 或 narration,当前为 {timing!r}")
|
|
446
384
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
if custom_band:
|
|
452
|
-
padding = int(CONFIG.get("subtitle_mask_padding", 4) or 0)
|
|
385
|
+
band = _measured_subtitle_band(canvas)
|
|
386
|
+
if band is not None:
|
|
387
|
+
y_top, y_bot = band
|
|
388
|
+
padding = CONFIG["subtitle_mask_padding"]
|
|
453
389
|
mask_top = max(0, y_top - padding)
|
|
454
|
-
mask_bot = min(
|
|
390
|
+
mask_bot = min(canvas["height"], y_bot + padding)
|
|
455
391
|
geometry = f"x=0:y={mask_top}:w=iw:h={mask_bot - mask_top}"
|
|
456
392
|
else:
|
|
457
|
-
ratio = max(0.0, min(0.5, float(CONFIG.get("source_subtitle_mask_ratio", 0.14) or 0.0)))
|
|
458
393
|
# Our subtitle cues are one line. Keep the mask large enough for that line and its
|
|
459
394
|
# margin, but never regress to the old two-line bar that hid ~23% of the image.
|
|
460
395
|
style = _subtitle_style_config(canvas)
|
|
461
|
-
play_res_y =
|
|
396
|
+
play_res_y = float(style["play_res_y"])
|
|
462
397
|
line_h = float(style["font_size"]) * 1.25
|
|
463
398
|
pad = 10.0 * play_res_y / SUBTITLE_STYLE_REF_H
|
|
464
399
|
sub_band = (float(style["margin_v"]) + line_h + pad) / play_res_y
|
|
465
|
-
ratio = min(0.5, max(
|
|
466
|
-
if ratio <= 0:
|
|
467
|
-
return None
|
|
400
|
+
ratio = min(0.5, max(CONFIG["source_subtitle_mask_ratio"], sub_band))
|
|
468
401
|
geometry = f"x=0:y=ih-ih*{ratio:.3f}:w=iw:h=ih*{ratio:.3f}"
|
|
469
402
|
|
|
470
403
|
base = f"drawbox={geometry}:color=black@{opacity:.2f}:t=fill"
|
|
471
|
-
timing = str(CONFIG.get("source_subtitle_mask_timing", "narration") or "narration").lower()
|
|
472
|
-
if timing not in {"all", "narration"}:
|
|
473
|
-
timing = "narration"
|
|
474
404
|
filters = []
|
|
475
|
-
if
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
# Avoid overlapping drawboxes: stacking two 60%-black masks would darken the overlap
|
|
491
|
-
# to 84%. Coalescing also keeps long filter chains smaller.
|
|
492
|
-
filters.extend(
|
|
493
|
-
f"{base}:enable='between(t,{start:.3f},{end:.3f})'"
|
|
494
|
-
for start, end, _ in coalesce_duck_windows(windows, bridge=0.001)
|
|
495
|
-
)
|
|
405
|
+
if opacity > 0:
|
|
406
|
+
if timing == "all":
|
|
407
|
+
filters.append(base)
|
|
408
|
+
else:
|
|
409
|
+
windows = [
|
|
410
|
+
(start, end, 0.0)
|
|
411
|
+
for start, end in map(_seg_place_window, tts_segments)
|
|
412
|
+
if end > start
|
|
413
|
+
]
|
|
414
|
+
# Avoid overlapping drawboxes: stacking two 60%-black masks would darken the
|
|
415
|
+
# overlap to 84%. Coalescing also keeps long filter chains smaller.
|
|
416
|
+
filters.extend(
|
|
417
|
+
f"{base}:enable='between(t,{start:.3f},{end:.3f})'"
|
|
418
|
+
for start, end, _ in coalesce_duck_windows(windows, bridge=0.001)
|
|
419
|
+
)
|
|
496
420
|
|
|
497
421
|
# A translucent mask deliberately leaves the source glyphs visible. Whenever we burn a
|
|
498
422
|
# replacement original-dialogue subtitle into a gap, cover that exact window opaquely first;
|
|
499
423
|
# otherwise the source hard-sub and replacement text are stacked on top of each other.
|
|
500
|
-
if
|
|
501
|
-
replacement_entries = _original_gap_subtitle_entries(
|
|
502
|
-
tts_segments or [], work_dir, video_duration
|
|
503
|
-
)
|
|
424
|
+
if not (timing == "all" and opacity >= 1.0 - 1e-9):
|
|
504
425
|
replacement_windows = [
|
|
505
|
-
(entry["start"], entry["end"], 0.0)
|
|
426
|
+
(entry["start"], entry["end"], 0.0)
|
|
427
|
+
for entry in _original_gap_subtitle_entries(tts_segments, work_dir, video_duration)
|
|
506
428
|
]
|
|
507
429
|
opaque = f"drawbox={geometry}:color=black@1.00:t=fill"
|
|
508
430
|
filters.extend(
|