@oh-story/dsh 0.1.4 → 0.1.6
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 +56 -12
- package/lib/client.js +2707 -268
- package/lib/client.js.map +4 -4
- package/lib/drama/manifest.json +136 -101
- package/lib/drama/skills/short-drama/SKILL.md +61 -1
- package/lib/drama/skills/short-drama/references/contract-and-ownership.md +7 -4
- package/lib/drama/skills/short-drama/references/creator-documents.md +133 -8
- package/lib/drama/skills/short-drama/references/creator-workflow.md +38 -2
- package/lib/drama/skills/short-drama/references/knowhow-index.md +3 -0
- package/lib/drama/skills/short-drama/references/reference-roles.md +10 -7
- package/lib/drama/skills/short-drama/references/runtime-preflight.md +3 -0
- package/lib/drama/skills/short-drama/scripts/creator_markdown_check.py +1002 -0
- package/lib/drama/skills/short-drama/scripts/project_tool.py +485 -5
- package/lib/drama/skills/short-drama-assets/SKILL.md +13 -3
- package/lib/drama/skills/short-drama-assets/references/continuity-lock.md +116 -0
- package/lib/drama/skills/short-drama-assets/references/identity-vs-variant.md +2 -1
- package/lib/drama/skills/short-drama-assets/references/prop-and-state.md +19 -0
- package/lib/drama/skills/short-drama-assets/references/stage-contract.md +2 -0
- package/lib/drama/skills/short-drama-assets/scripts/asset_check.py +1 -1
- package/lib/drama/skills/short-drama-image-prompts/SKILL.md +6 -2
- package/lib/drama/skills/short-drama-image-prompts/references/common-recipe.md +4 -3
- package/lib/drama/skills/short-drama-image-prompts/references/lookdev-frame.md +1 -1
- package/lib/drama/skills/short-drama-image-prompts/references/review-and-fixtures.md +1 -1
- package/lib/drama/skills/short-drama-image-prompts/references/stage-contract.md +3 -1
- package/lib/drama/skills/short-drama-image-prompts/scripts/image_prompt_check.py +8 -8
- package/lib/drama/skills/short-drama-image-prompts/scripts/selftest.py +4 -2
- package/lib/drama/skills/short-drama-produce/SKILL.md +46 -10
- package/lib/drama/skills/short-drama-produce/references/adapter-contract.md +65 -8
- package/lib/drama/skills/short-drama-produce/references/providers/minimax-h3-video.md +85 -0
- package/lib/drama/skills/short-drama-produce/references/providers/seedance.md +27 -10
- package/lib/drama/skills/short-drama-produce/scripts/production_tool.py +362 -3
- package/lib/drama/skills/short-drama-produce/scripts/provider_adapters.py +627 -40
- package/lib/drama/skills/short-drama-produce/scripts/selftest.py +5 -4
- package/lib/drama/skills/short-drama-review/references/rubric-visual-motion.md +12 -0
- package/lib/drama/skills/short-drama-review/scripts/review_check.py +1 -1
- package/lib/drama/skills/short-drama-storyboard/SKILL.md +76 -7
- package/lib/drama/skills/short-drama-storyboard/assets/shot-template.jsonl +1 -1
- package/lib/drama/skills/short-drama-storyboard/references/keyframe-craft.md +6 -2
- package/lib/drama/skills/short-drama-storyboard/references/production-shot-grammar.md +19 -4
- package/lib/drama/skills/short-drama-storyboard/references/review-and-fixtures.md +5 -2
- package/lib/drama/skills/short-drama-storyboard/references/screenplay-to-keyframe-example.md +16 -0
- package/lib/drama/skills/short-drama-storyboard/references/stage-contract.md +8 -3
- package/lib/drama/skills/short-drama-storyboard/scripts/storyboard_check.py +1 -1
- package/lib/drama/skills/short-drama-video-prompts/SKILL.md +81 -13
- package/lib/drama/skills/short-drama-video-prompts/assets/motion-spec.jsonl.md +6 -1
- package/lib/drama/skills/short-drama-video-prompts/assets/video-prompts.md +17 -1
- package/lib/drama/skills/short-drama-video-prompts/references/delivery-profile.md +21 -1
- package/lib/drama/skills/short-drama-video-prompts/references/minimax-h3.md +154 -0
- package/lib/drama/skills/short-drama-video-prompts/references/motion-recipe.md +24 -6
- package/lib/drama/skills/short-drama-video-prompts/references/production-prompt-grammar.md +11 -7
- package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.0.md +41 -0
- package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.5.md +66 -0
- package/lib/drama/skills/short-drama-video-prompts/references/stage-contract.md +5 -2
- package/lib/drama/skills/short-drama-video-prompts/references/target-model-profile.md +115 -0
- package/lib/drama/skills/short-drama-video-prompts/scripts/container_check.py +1 -1
- package/lib/drama/skills/short-drama-video-prompts/scripts/music_spec_check.py +1 -1
- package/lib/drama/skills/short-drama-write/SKILL.md +3 -2
- package/lib/drama/skills/short-drama-write/scripts/duration_estimate.py +1 -1
- package/lib/drama/skills/short-drama-write/scripts/screenplay_index.py +2 -2
- package/lib/drama/skills/short-drama-write/scripts/voice_sheet_check.py +1 -1
- package/lib/index.js +865 -33
- package/lib/index.js.map +4 -4
- package/lib/novel-to-game/LICENSE +21 -0
- package/lib/novel-to-game/README.md +180 -0
- package/lib/novel-to-game/README_ZH.md +178 -0
- package/lib/novel-to-game/VERSION +1 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/BUILD_BRIEF.md +217 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/.vercelignore +3 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/RUN.md +130 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/compound_new_guofeng.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/finale/fate_coda.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/finale/meng.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/finale/pan.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/finale/pinger.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/finale/xuee.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/finale/yue.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/inner_court_accord_five.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/inner_court_afterglow_five.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/inner_court_alliance.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/public_day10.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/public_day15.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/public_day5.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/title_new_guofeng.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/joint/pan_pinger.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/joint/pan_xuee.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/joint/yue_meng.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/joint/yue_pan.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/joint/yue_pinger.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/meng/explicit.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/meng/prelude.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/milestone/meng.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/milestone/pan.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/milestone/pinger.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/milestone/xuee.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/milestone/yue.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/pan/explicit.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/pan/prelude.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/pinger/explicit.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/pinger/prelude.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/xuee/explicit.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/xuee/prelude.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/yue/explicit.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/yue/prelude.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/heroine/meng/night.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/heroine/pan/night.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/heroine/pinger/night.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/heroine/xuee/night.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/heroine/yue/night.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/household/li_jiaoer.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/scene/gate_collector.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/css/new-guofeng.css +1105 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/css/style.css +2539 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/index.html +18 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/assets.js +147 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/audio.js +332 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/data.js +7472 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/engine.js +13077 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/main.js +3028 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/text.js +32 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/package.json +10 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/vercel.json +23 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/art/generated-art.json +161 -0
- package/lib/novel-to-game/examples/jin-ping-mei/example.json +12 -0
- package/lib/novel-to-game/examples/jin-ping-mei/qa/README.md +31 -0
- package/lib/novel-to-game/examples/jin-ping-mei/qa/playable-model.json +99 -0
- package/lib/novel-to-game/examples/jin-ping-mei/qa/verification.json +41 -0
- package/lib/novel-to-game/examples/jin-ping-mei/qa/verify_playable_model.mjs +129 -0
- package/lib/novel-to-game/manifest.json +508 -0
- package/lib/novel-to-game/skills/game-art-direction/SKILL.md +73 -0
- package/lib/novel-to-game/skills/game-art-direction/agents/openai.yaml +4 -0
- package/lib/novel-to-game/skills/game-art-direction/references/art-direction-method.md +119 -0
- package/lib/novel-to-game/skills/game-build/SKILL.md +84 -0
- package/lib/novel-to-game/skills/game-build/agents/openai.yaml +4 -0
- package/lib/novel-to-game/skills/game-build/references/build-brief-contract.md +109 -0
- package/lib/novel-to-game/skills/game-build/references/generative-media-pipeline.md +56 -0
- package/lib/novel-to-game/skills/game-build/references/playable-model-contract.md +81 -0
- package/lib/novel-to-game/skills/game-build/references/tts-production-contract.md +37 -0
- package/lib/novel-to-game/skills/game-concept/SKILL.md +72 -0
- package/lib/novel-to-game/skills/game-concept/agents/openai.yaml +4 -0
- package/lib/novel-to-game/skills/game-concept/references/concept-method.md +94 -0
- package/lib/novel-to-game/skills/game-qa/SKILL.md +50 -0
- package/lib/novel-to-game/skills/game-qa/agents/openai.yaml +4 -0
- package/lib/novel-to-game/skills/game-qa/references/qa-contract.md +66 -0
- package/lib/novel-to-game/skills/game-qa/references/test-design-method.md +45 -0
- package/lib/novel-to-game/skills/game-world-design/SKILL.md +63 -0
- package/lib/novel-to-game/skills/game-world-design/agents/openai.yaml +4 -0
- package/lib/novel-to-game/skills/game-world-design/references/dialogue-design-method.md +59 -0
- package/lib/novel-to-game/skills/game-world-design/references/game-writing-craft.md +53 -0
- package/lib/novel-to-game/skills/game-world-design/references/narrative-design-method.md +84 -0
- package/lib/novel-to-game/skills/game-world-design/references/numeric-design-method.md +56 -0
- package/lib/novel-to-game/skills/game-world-design/references/world-design-method.md +124 -0
- package/lib/novel-to-game/skills/novel-game-analyze/SKILL.md +77 -0
- package/lib/novel-to-game/skills/novel-game-analyze/agents/openai.yaml +4 -0
- package/lib/novel-to-game/skills/novel-game-analyze/references/gameability-protocol.md +65 -0
- package/lib/novel-to-game/skills/novel-to-game/SKILL.md +71 -0
- package/lib/novel-to-game/skills/novel-to-game/agents/openai.yaml +4 -0
- package/lib/novel-to-game/skills/novel-to-game/references/intake-benchmark-reference.md +42 -0
- package/lib/novel-to-game/skills/novel-to-game/references/intake-method.md +51 -0
- package/lib/novel-to-game/skills/novel-to-game/references/pipeline-contract.md +65 -0
- package/lib/oh-story/manifest.json +415 -305
- package/lib/oh-story/roles/consistency-checker.md +1 -1
- package/lib/oh-story/roles/narrative-writer.md +32 -33
- package/lib/oh-story/roles/story-architect.md +16 -26
- package/lib/oh-story/skills/browser-cdp/SKILL.md +1 -1
- package/lib/oh-story/skills/story/SKILL.md +16 -9
- package/lib/oh-story/skills/story/VERSION +1 -1
- package/lib/oh-story/skills/story/references/author-memory.md +163 -0
- package/lib/oh-story/skills/story/scripts/author_memory_commit.py +871 -0
- package/lib/oh-story/skills/story-cover/SKILL.md +1 -1
- package/lib/oh-story/skills/story-deslop/SKILL.md +10 -5
- package/lib/oh-story/skills/story-deslop/references/anti-ai-writing.md +2 -1
- package/lib/oh-story/skills/story-deslop/references/author-memory.md +163 -0
- package/lib/oh-story/skills/story-deslop/references/banned-words.md +6 -4
- package/lib/oh-story/skills/story-deslop/scripts/author_memory_commit.py +871 -0
- package/lib/oh-story/skills/story-deslop/scripts/check-ai-patterns.js +64 -1
- package/lib/oh-story/skills/story-import/SKILL.md +19 -10
- package/lib/oh-story/skills/story-import/references/format-and-structure.md +9 -10
- package/lib/oh-story/skills/story-import/references/structure-mapping-long.md +2 -2
- package/lib/oh-story/skills/story-import/references/structure-mapping-short.md +1 -1
- package/lib/oh-story/skills/story-import/references/tracking-transaction.md +14 -6
- package/lib/oh-story/skills/story-import/scripts/check-outline-contract.js +251 -0
- package/lib/oh-story/skills/story-import/scripts/tracking_commit.py +98 -9
- package/lib/oh-story/skills/story-import/scripts/wordcount_core.py +246 -0
- package/lib/oh-story/skills/story-long-analyze/SKILL.md +4 -4
- package/lib/oh-story/skills/story-long-scan/SKILL.md +1 -1
- package/lib/oh-story/skills/story-long-write/SKILL.md +35 -21
- package/lib/oh-story/skills/story-long-write/references/anti-ai-writing.md +2 -1
- package/lib/oh-story/skills/story-long-write/references/artifact-protocols.md +7 -3
- package/lib/oh-story/skills/story-long-write/references/author-memory.md +163 -0
- package/lib/oh-story/skills/story-long-write/references/banned-words.md +6 -4
- package/lib/oh-story/skills/story-long-write/references/cross-book-recall.md +6 -6
- package/lib/oh-story/skills/story-long-write/references/genre-prose-cards.md +1 -1
- package/lib/oh-story/skills/story-long-write/references/genre-readers.md +1 -1
- package/lib/oh-story/skills/{story-setup/references/agent-references/hooks-chapter.md → story-long-write/references/long-chapter-hooks.md} +5 -5
- package/lib/oh-story/skills/story-long-write/references/{quality-checklist.md → long-chapter-quality.md} +2 -1
- package/lib/oh-story/skills/story-long-write/references/long-format.md +42 -0
- package/lib/oh-story/skills/{story-setup/references/agent-references/genre-catalog.md → story-long-write/references/long-genre-catalog.md} +11 -97
- package/lib/oh-story/skills/{story-setup/references/agent-references/genre-core-mechanics.md → story-long-write/references/long-genre-mechanics.md} +1 -1
- package/lib/oh-story/skills/{story-short-write/references/reversal-toolkit.md → story-long-write/references/long-reversal.md} +18 -32
- package/lib/oh-story/skills/story-long-write/references/{hooks-suspense.md → long-suspense.md} +6 -6
- package/lib/oh-story/skills/story-long-write/references/opening-design.md +1 -1
- package/lib/oh-story/skills/story-long-write/references/outline-structure-theory.md +1 -1
- package/lib/oh-story/skills/story-long-write/references/style-genre-modules.md +1 -1
- package/lib/oh-story/skills/story-long-write/references/tracking-transaction.md +14 -6
- package/lib/oh-story/skills/story-long-write/references/workflow-chapter.md +32 -27
- package/lib/oh-story/skills/story-long-write/references/workflow-daily.md +4 -4
- package/lib/oh-story/skills/story-long-write/references/workflow-revision.md +2 -2
- package/lib/oh-story/skills/story-long-write/references/workflow-setup.md +14 -14
- package/lib/oh-story/skills/story-long-write/references/writing-craft.md +54 -54
- package/lib/oh-story/skills/story-long-write/scripts/author_memory_commit.py +871 -0
- package/lib/oh-story/skills/story-long-write/scripts/check-ai-patterns.js +64 -1
- package/lib/oh-story/skills/story-long-write/scripts/check-outline-contract.js +251 -0
- package/lib/oh-story/skills/story-long-write/scripts/storyctl.py +303 -0
- package/lib/oh-story/skills/story-long-write/scripts/tracking_commit.py +98 -9
- package/lib/oh-story/skills/story-long-write/scripts/wordcount_core.py +246 -0
- package/lib/oh-story/skills/story-review/SKILL.md +26 -17
- package/lib/oh-story/skills/story-review/references/anti-ai-writing.md +2 -1
- package/lib/oh-story/skills/story-review/references/author-memory.md +163 -0
- package/lib/oh-story/skills/story-review/references/banned-words.md +6 -4
- package/lib/oh-story/skills/story-review/references/{quality-checklist.md → review-quality.md} +22 -21
- package/lib/oh-story/skills/story-review/references/tracking-transaction.md +14 -6
- package/lib/oh-story/skills/story-review/scripts/author_memory_commit.py +871 -0
- package/lib/oh-story/skills/story-review/scripts/check-ai-patterns.js +64 -1
- package/lib/oh-story/skills/story-review/scripts/tracking_commit.py +98 -9
- package/lib/oh-story/skills/story-review/scripts/wordcount_core.py +246 -0
- package/lib/oh-story/skills/story-setup/SKILL.md +74 -33
- package/lib/oh-story/skills/story-setup/references/agent-references/agent-quality.md +72 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/agent-reference-profiles.md +55 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/anti-ai-writing.md +2 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/banned-words.md +6 -4
- package/lib/oh-story/skills/story-setup/references/agent-references/format-and-structure.md +9 -10
- 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/genre-readers.md +1 -1
- package/lib/oh-story/skills/{story-short-analyze/references/hooks-chapter.md → story-setup/references/agent-references/long-chapter-hooks.md} +5 -5
- package/lib/oh-story/skills/story-setup/references/agent-references/long-emotional-methods.md +209 -0
- package/lib/oh-story/skills/{story-long-write/references/genre-catalog.md → story-setup/references/agent-references/long-genre-catalog.md} +11 -97
- package/lib/oh-story/skills/{story-long-write/references/genre-core-mechanics.md → story-setup/references/agent-references/long-genre-mechanics.md} +1 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/long-quality.md +40 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/{reversal-toolkit.md → long-reversal.md} +18 -32
- package/lib/oh-story/skills/{story-short-write/references/hooks-suspense.md → story-setup/references/agent-references/long-suspense.md} +6 -6
- package/lib/oh-story/skills/story-setup/references/agent-references/opening-design.md +1 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/{emotional-methods.md → short-emotional-methods.md} +4 -4
- package/lib/oh-story/skills/{story-long-write/references/genre-writing-formulas.md → story-setup/references/agent-references/short-genre-formulas.md} +32 -44
- package/lib/oh-story/skills/story-setup/references/agent-references/short-quality.md +39 -0
- package/lib/oh-story/skills/{story-long-write/references/reversal-toolkit.md → story-setup/references/agent-references/short-reversal.md} +29 -23
- package/lib/oh-story/skills/story-setup/references/agent-references/{hooks-suspense.md → short-suspense.md} +31 -31
- 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/writing-craft.md +55 -55
- package/lib/oh-story/skills/story-short-analyze/SKILL.md +28 -28
- package/lib/oh-story/skills/story-short-analyze/references/{character-basics.md → analysis-character-basics.md} +1 -1
- package/lib/oh-story/skills/story-short-analyze/references/{character-design-methods.md → analysis-character-design.md} +1 -1
- package/lib/oh-story/skills/story-short-analyze/references/{character-relations.md → analysis-character-relations.md} +1 -1
- package/lib/oh-story/skills/story-short-analyze/references/{hooks-paragraph.md → analysis-paragraph-hooks.md} +1 -1
- package/lib/oh-story/skills/story-short-analyze/references/{genre-readers.md → analysis-reader-profile.md} +2 -2
- package/lib/oh-story/skills/story-short-analyze/references/analysis-report-style.md +45 -0
- package/lib/oh-story/skills/story-short-analyze/references/analysis-short-genres.md +65 -0
- package/lib/oh-story/skills/story-short-analyze/references/analysis-short-hooks.md +77 -0
- package/lib/oh-story/skills/story-short-analyze/references/analysis-short-mechanics.md +82 -0
- package/lib/oh-story/skills/story-short-analyze/references/analysis-short-patterns.md +50 -0
- package/lib/oh-story/skills/story-short-analyze/references/analysis-short-suspense.md +98 -0
- package/lib/oh-story/skills/story-short-analyze/references/{genre-writing-techniques.md → analysis-writing-techniques.md} +21 -19
- package/lib/oh-story/skills/story-short-analyze/references/deconstruction-examples.md +1 -1
- package/lib/oh-story/skills/story-short-analyze/references/material-decomposition.md +21 -28
- package/lib/oh-story/skills/story-short-analyze/references/output-contract.md +1 -1
- package/lib/oh-story/skills/story-short-analyze/references/output-templates.md +17 -17
- package/lib/oh-story/skills/story-short-analyze/references/{quality-checklist.md → source-story-quality.md} +28 -49
- package/lib/oh-story/skills/story-short-analyze/references/zhihu-style.md +2 -2
- package/lib/oh-story/skills/story-short-scan/SKILL.md +1 -1
- package/lib/oh-story/skills/story-short-write/SKILL.md +78 -55
- package/lib/oh-story/skills/story-short-write/references/author-memory.md +163 -0
- package/lib/oh-story/skills/story-short-write/references/banned-words.md +6 -4
- package/lib/oh-story/skills/story-short-write/references/cross-book-recall.md +3 -3
- package/lib/oh-story/skills/story-short-write/references/emotional-methods.md +4 -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 +3 -3
- package/lib/oh-story/skills/story-short-write/references/genre-styles//345/217/214/347/224/267/344/270/273.md +2 -2
- 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 +6 -6
- 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 +4 -4
- package/lib/oh-story/skills/story-short-write/references/genre-styles//346/260/221/344/277/227/346/200/252/350/260/210.md +7 -7
- package/lib/oh-story/skills/story-short-write/references/genre-styles//346/262/231/351/233/225/350/204/221/346/264/236.md +5 -5
- package/lib/oh-story/skills/story-short-write/references/genre-styles//347/224/234/345/256/240.md +4 -4
- 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 +32 -44
- package/lib/oh-story/skills/story-short-write/references/genre-writing-techniques.md +20 -18
- package/lib/oh-story/skills/story-short-write/references/output-contract.md +1 -1
- package/lib/oh-story/skills/story-short-write/references/short-craft.md +18 -18
- package/lib/oh-story/skills/story-short-write/references/short-deslop.md +1 -1
- package/lib/oh-story/skills/story-short-write/references/short-format.md +38 -8
- package/lib/oh-story/skills/story-short-write/references/{quality-checklist.md → short-prose-quality.md} +22 -21
- package/lib/oh-story/skills/story-short-write/references/short-reversal.md +355 -0
- package/lib/oh-story/skills/{story-short-analyze/references/hooks-suspense.md → story-short-write/references/short-suspense.md} +31 -31
- package/lib/oh-story/skills/story-short-write/references/submission-craft.md +2 -2
- package/lib/oh-story/skills/story-short-write/references/writing-workflow.md +31 -22
- package/lib/oh-story/skills/story-short-write/scripts/author_memory_commit.py +871 -0
- package/lib/oh-story/skills/story-short-write/scripts/check-ai-patterns.js +64 -1
- package/lib/oh-story/skills/story-short-write/scripts/check-delivery-contract.js +212 -0
- package/lib/oh-story/skills/story-short-write/scripts/check-phase2-contract.js +363 -0
- package/lib/types/client/drama-production-view.d.ts +34 -0
- package/lib/types/client/drama-production-view.d.ts.map +1 -0
- package/lib/types/client/drama-production.d.ts +92 -0
- package/lib/types/client/drama-production.d.ts.map +1 -0
- package/lib/types/client/file-activity.d.ts +4 -2
- package/lib/types/client/file-activity.d.ts.map +1 -1
- package/lib/types/client/index.d.ts +1 -1
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/production-intents.d.ts +9 -0
- package/lib/types/client/production-intents.d.ts.map +1 -0
- package/lib/types/client/production-prompts.d.ts +9 -0
- package/lib/types/client/production-prompts.d.ts.map +1 -0
- package/lib/types/client/production-runtime.d.ts +58 -0
- package/lib/types/client/production-runtime.d.ts.map +1 -0
- package/lib/types/client/video-studio.d.ts +41 -0
- package/lib/types/client/video-studio.d.ts.map +1 -0
- package/lib/types/client/workbench-ui.d.ts +5 -0
- package/lib/types/client/workbench-ui.d.ts.map +1 -0
- package/lib/types/game-verification.d.ts +16 -0
- package/lib/types/game-verification.d.ts.map +1 -0
- package/lib/types/index.d.ts +3 -1
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/production-intent.d.ts +21 -0
- package/lib/types/production-intent.d.ts.map +1 -0
- package/lib/types/production-tool.d.ts +5 -0
- package/lib/types/production-tool.d.ts.map +1 -0
- package/lib/types/role-tool.d.ts.map +1 -1
- package/lib/types/skill-provider.d.ts +7 -0
- package/lib/types/skill-provider.d.ts.map +1 -1
- package/lib/types/video-project.d.ts +42 -0
- package/lib/types/video-project.d.ts.map +1 -0
- package/lib/types/workspace-request-trust.d.ts +6 -0
- package/lib/types/workspace-request-trust.d.ts.map +1 -1
- package/lib/types/workspace-route.d.ts +20 -0
- package/lib/types/workspace-route.d.ts.map +1 -1
- package/lib/video-recap/CHANGELOG.md +282 -0
- package/lib/video-recap/LICENSE +21 -0
- package/lib/video-recap/README.en.md +257 -0
- package/lib/video-recap/README.md +255 -0
- package/lib/video-recap/manifest.json +684 -0
- package/lib/video-recap/skills/video-assemble/SKILL.md +97 -0
- package/lib/video-recap/skills/video-assemble/references/jianying/LICENSE.duo-video +21 -0
- package/lib/video-recap/skills/video-assemble/references/jianying/SOURCE.md +15 -0
- package/lib/video-recap/skills/video-assemble/references/jianying/empty_draft_meta_info.json +69 -0
- package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_combination_segment.json +84 -0
- package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_combination_video_material.json +98 -0
- package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_draft.json +204 -0
- package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_material_video.json +83 -0
- package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_meta_material_value.json +23 -0
- package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_project_info.json +133 -0
- package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_segment.json +70 -0
- package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_text_styles.json +30 -0
- package/lib/video-recap/skills/video-assemble/references/jianying/empty_yj_material_audio.json +41 -0
- package/lib/video-recap/skills/video-assemble/references/jianying/empty_yj_material_text.json +109 -0
- package/lib/video-recap/skills/video-assemble/scripts/artifacts.py +88 -0
- package/lib/video-recap/skills/video-assemble/scripts/assemble.py +345 -0
- package/lib/video-recap/skills/video-assemble/scripts/assemble_constants.py +34 -0
- package/lib/video-recap/skills/video-assemble/scripts/assembly_contract.py +305 -0
- package/lib/video-recap/skills/video-assemble/scripts/assembly_settings.py +94 -0
- package/lib/video-recap/skills/video-assemble/scripts/audio_automation.py +205 -0
- package/lib/video-recap/skills/video-assemble/scripts/audio_mix.py +555 -0
- package/lib/video-recap/skills/video-assemble/scripts/export_jianying.py +261 -0
- package/lib/video-recap/skills/video-assemble/scripts/jianying_builders.py +767 -0
- package/lib/video-recap/skills/video-assemble/scripts/jianying_model.py +111 -0
- package/lib/video-recap/skills/video-assemble/scripts/jianying_optional.py +24 -0
- package/lib/video-recap/skills/video-assemble/scripts/jianying_schema.py +109 -0
- package/lib/video-recap/skills/video-assemble/scripts/jianying_templates.py +37 -0
- package/lib/video-recap/skills/video-assemble/scripts/jianying_timeline_contract.py +191 -0
- package/lib/video-recap/skills/video-assemble/scripts/jianying_tracks.py +75 -0
- package/lib/video-recap/skills/video-assemble/scripts/jianying_writer.py +394 -0
- package/lib/video-recap/skills/video-assemble/scripts/lib.py +239 -0
- package/lib/video-recap/skills/video-assemble/scripts/media.py +243 -0
- package/lib/video-recap/skills/video-assemble/scripts/narration_audio.py +477 -0
- package/lib/video-recap/skills/video-assemble/scripts/render_preflight.py +46 -0
- package/lib/video-recap/skills/video-assemble/scripts/source_subtitles.py +486 -0
- package/lib/video-recap/skills/video-assemble/scripts/subtitle_core.py +376 -0
- package/lib/video-recap/skills/video-assemble/scripts/subtitle_render.py +88 -0
- package/lib/video-recap/skills/video-assemble/scripts/timeline.py +248 -0
- package/lib/video-recap/skills/video-assemble/scripts/timeline_emit.py +89 -0
- package/lib/video-recap/skills/video-assemble/scripts/visual_render.py +512 -0
- package/lib/video-recap/skills/video-cut/SKILL.md +90 -0
- package/lib/video-recap/skills/video-cut/scripts/cut.py +67 -0
- package/lib/video-recap/skills/video-cut/scripts/cut_cli.py +320 -0
- package/lib/video-recap/skills/video-cut/scripts/cut_contract.py +545 -0
- package/lib/video-recap/skills/video-cut/scripts/cut_render.py +348 -0
- package/lib/video-recap/skills/video-cut/scripts/lib.py +77 -0
- package/lib/video-recap/skills/video-cut/scripts/media_geometry.py +341 -0
- package/lib/video-recap/skills/video-cut/scripts/narration_mapping.py +249 -0
- package/lib/video-recap/skills/video-cut/scripts/sentence_boundaries.py +745 -0
- package/lib/video-recap/skills/video-recap/SKILL.md +216 -0
- package/lib/video-recap/skills/video-recap/references/config-playbook.md +61 -0
- package/lib/video-recap/skills/video-recap/references/creative-editing-playbook.md +225 -0
- package/lib/video-recap/skills/video-recap/references/data-schema.md +534 -0
- package/lib/video-recap/skills/video-recap/references/research-guide.md +74 -0
- package/lib/video-recap/skills/video-recap/references/shift-left-qc-schema.md +23 -0
- package/lib/video-recap/skills/video-recap/references/timeline-and-jianying.md +251 -0
- package/lib/video-recap/skills/video-recap/scripts/doctor.py +483 -0
- package/lib/video-recap/skills/video-recap/scripts/final_qc.py +737 -0
- package/lib/video-recap/skills/video-recap/scripts/lib.py +242 -0
- package/lib/video-recap/skills/video-recap/scripts/materials.py +457 -0
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc.py +95 -0
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc_client.py +18 -0
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc_contract.py +9 -0
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc_evidence.py +371 -0
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc_observations.py +201 -0
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc_payload.py +227 -0
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc_report.py +362 -0
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc_runner.py +129 -0
- package/lib/video-recap/skills/video-recap/scripts/qc_contract.py +338 -0
- package/lib/video-recap/skills/video-recap/scripts/recap.py +9 -0
- package/lib/video-recap/skills/video-recap/scripts/recap_cli.py +140 -0
- package/lib/video-recap/skills/video-recap/scripts/recap_inspect.py +550 -0
- package/lib/video-recap/skills/video-recap/scripts/recap_review.py +77 -0
- package/lib/video-recap/skills/video-recap/scripts/recap_runner.py +654 -0
- package/lib/video-recap/skills/video-recap/scripts/recap_runtime.py +335 -0
- package/lib/video-recap/skills/video-recap/scripts/recap_stage_qc.py +217 -0
- package/lib/video-recap/skills/video-recap/scripts/recap_timeline.py +795 -0
- package/lib/video-recap/skills/video-script/SKILL.md +248 -0
- package/lib/video-recap/skills/video-script/references/creative-editing-playbook.md +225 -0
- package/lib/video-recap/skills/video-script/references/research-guide.md +75 -0
- package/lib/video-recap/skills/video-script/scripts/agent_brief.py +399 -0
- package/lib/video-recap/skills/video-script/scripts/agent_text.py +410 -0
- package/lib/video-recap/skills/video-script/scripts/brief_context.py +403 -0
- package/lib/video-recap/skills/video-script/scripts/brief_inputs.py +332 -0
- package/lib/video-recap/skills/video-script/scripts/brief_timeline.py +442 -0
- package/lib/video-recap/skills/video-script/scripts/deslop_qc.py +237 -0
- package/lib/video-recap/skills/video-script/scripts/evidence_bundle.py +492 -0
- package/lib/video-recap/skills/video-script/scripts/lib.py +327 -0
- package/lib/video-recap/skills/video-script/scripts/narration.py +12 -0
- package/lib/video-recap/skills/video-script/scripts/narration_lint.py +678 -0
- package/lib/video-recap/skills/video-script/scripts/review.py +42 -0
- package/lib/video-recap/skills/video-script/scripts/review_grounding.py +262 -0
- package/lib/video-recap/skills/video-script/scripts/review_response.py +761 -0
- package/lib/video-recap/skills/video-script/scripts/review_runner.py +200 -0
- package/lib/video-recap/skills/video-script/scripts/speech_ownership.py +204 -0
- package/lib/video-recap/skills/video-script/scripts/timeline_fusion.py +199 -0
- package/lib/video-recap/skills/video-script/scripts/validate.py +164 -0
- package/lib/video-recap/skills/video-understanding/SKILL.md +75 -0
- package/lib/video-recap/skills/video-understanding/references/data-schema.md +299 -0
- package/lib/video-recap/skills/video-understanding/references/prompt-templates.md +37 -0
- package/lib/video-recap/skills/video-understanding/references/research-guide.md +74 -0
- package/lib/video-recap/skills/video-understanding/scripts/agent_brief.py +399 -0
- package/lib/video-recap/skills/video-understanding/scripts/agent_text.py +410 -0
- package/lib/video-recap/skills/video-understanding/scripts/asr.py +260 -0
- package/lib/video-recap/skills/video-understanding/scripts/brief.py +12 -0
- package/lib/video-recap/skills/video-understanding/scripts/brief_context.py +403 -0
- package/lib/video-recap/skills/video-understanding/scripts/brief_inputs.py +332 -0
- package/lib/video-recap/skills/video-understanding/scripts/brief_timeline.py +442 -0
- package/lib/video-recap/skills/video-understanding/scripts/consolidate.py +647 -0
- package/lib/video-recap/skills/video-understanding/scripts/deslop_qc.py +237 -0
- package/lib/video-recap/skills/video-understanding/scripts/detect.py +513 -0
- package/lib/video-recap/skills/video-understanding/scripts/extract.py +62 -0
- package/lib/video-recap/skills/video-understanding/scripts/lib.py +470 -0
- package/lib/video-recap/skills/video-understanding/scripts/narration_lint.py +678 -0
- package/lib/video-recap/skills/video-understanding/scripts/speech_ownership.py +204 -0
- package/lib/video-recap/skills/video-understanding/scripts/storyboard.py +507 -0
- package/lib/video-recap/skills/video-understanding/scripts/timeline_fusion.py +199 -0
- package/lib/video-recap/skills/video-understanding/scripts/understand.py +9 -0
- package/lib/video-recap/skills/video-understanding/scripts/understanding_brief.py +216 -0
- package/lib/video-recap/skills/video-understanding/scripts/understanding_cache.py +327 -0
- package/lib/video-recap/skills/video-understanding/scripts/understanding_runner.py +367 -0
- package/lib/video-recap/skills/video-understanding/scripts/understanding_storyboard.py +162 -0
- package/lib/video-recap/skills/video-understanding/scripts/vlm.py +705 -0
- package/lib/video-recap/skills/video-voiceover/SKILL.md +81 -0
- package/lib/video-recap/skills/video-voiceover/scripts/dub.py +637 -0
- package/lib/video-recap/skills/video-voiceover/scripts/fish_audio.py +109 -0
- package/lib/video-recap/skills/video-voiceover/scripts/lib.py +421 -0
- package/lib/video-recap/skills/video-voiceover/scripts/tts_audio.py +113 -0
- package/lib/video-recap/skills/video-voiceover/scripts/voiceover.py +782 -0
- package/package.json +55 -40
- package/lib/oh-story/skills/story-deslop/scripts/check-outline-copy.js +0 -241
- package/lib/oh-story/skills/story-long-write/references/format-and-structure.md +0 -169
- package/lib/oh-story/skills/story-setup/references/agent-references/genre-writing-formulas.md +0 -444
- package/lib/oh-story/skills/story-setup/references/agent-references/quality-checklist.md +0 -247
- package/lib/oh-story/skills/story-short-analyze/references/anti-ai-writing.md +0 -582
- package/lib/oh-story/skills/story-short-analyze/references/banned-words.md +0 -147
- package/lib/oh-story/skills/story-short-analyze/references/genre-catalog.md +0 -490
- package/lib/oh-story/skills/story-short-analyze/references/genre-core-mechanics.md +0 -425
- package/lib/oh-story/skills/story-short-analyze/references/genre-writing-formulas.md +0 -453
- /package/lib/oh-story/skills/{story-long-write/references/hooks-chapter.md → story-setup/references/agent-references/short-chapter-hooks.md} +0 -0
- /package/lib/oh-story/skills/{story-long-write/references/hooks-paragraph.md → story-setup/references/agent-references/short-paragraph-hooks.md} +0 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"""Thin internal model used at the JianYing adapter boundary."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
from typing import Callable
|
|
6
|
+
|
|
7
|
+
from jianying_schema import us
|
|
8
|
+
from jianying_tracks import TrackAllocator
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
ProbeFn = Callable[[str], tuple[int, int, int]]
|
|
12
|
+
NewIdFn = Callable[[], str]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class DraftBuildContext:
|
|
17
|
+
"""Normalized draft build state and overlap-safe track collection."""
|
|
18
|
+
|
|
19
|
+
width: int
|
|
20
|
+
height: int
|
|
21
|
+
fps: float
|
|
22
|
+
total_us: int
|
|
23
|
+
new_id: NewIdFn
|
|
24
|
+
probe: ProbeFn
|
|
25
|
+
resource_packages: dict = field(default_factory=dict)
|
|
26
|
+
style_presets: dict = field(default_factory=dict)
|
|
27
|
+
materials: dict[str, list] = field(
|
|
28
|
+
default_factory=lambda: {
|
|
29
|
+
key: []
|
|
30
|
+
for key in (
|
|
31
|
+
"audios",
|
|
32
|
+
"chromas",
|
|
33
|
+
"common_mask",
|
|
34
|
+
"drafts",
|
|
35
|
+
"effects",
|
|
36
|
+
"masks",
|
|
37
|
+
"speeds",
|
|
38
|
+
"stickers",
|
|
39
|
+
"text_templates",
|
|
40
|
+
"texts",
|
|
41
|
+
"transitions",
|
|
42
|
+
"video_effects",
|
|
43
|
+
"videos",
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
)
|
|
47
|
+
tracks: list[dict] = field(default_factory=list)
|
|
48
|
+
notes: list[str] = field(default_factory=list)
|
|
49
|
+
track_allocator: TrackAllocator = field(default_factory=TrackAllocator)
|
|
50
|
+
|
|
51
|
+
@classmethod
|
|
52
|
+
def from_timeline(cls, timeline, new_id, probe):
|
|
53
|
+
canvas = timeline.get("canvas", {})
|
|
54
|
+
return cls(
|
|
55
|
+
width=int(canvas.get("width", 1920)),
|
|
56
|
+
height=int(canvas.get("height", 1080)),
|
|
57
|
+
fps=float(canvas.get("fps", 30)),
|
|
58
|
+
total_us=us(timeline.get("duration", 0)),
|
|
59
|
+
new_id=new_id,
|
|
60
|
+
probe=probe,
|
|
61
|
+
resource_packages=dict(timeline.get("resource_packages") or {}),
|
|
62
|
+
style_presets=dict(timeline.get("style_presets") or {}),
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
def media_duration(self, path, fallback_us):
|
|
66
|
+
if path and os.path.exists(path):
|
|
67
|
+
duration_us, width, height = self.probe(path)
|
|
68
|
+
return duration_us or fallback_us, width, height
|
|
69
|
+
return fallback_us, 0, 0
|
|
70
|
+
|
|
71
|
+
def add_segment(self, kind, base_name, start_us, duration_us, segment):
|
|
72
|
+
allocated = self.track_allocator.allocate(kind, base_name, start_us, duration_us)
|
|
73
|
+
track = next(
|
|
74
|
+
(
|
|
75
|
+
item for item in self.tracks
|
|
76
|
+
if item["_semantic_kind"] == allocated.kind
|
|
77
|
+
and item["name"] == allocated.name
|
|
78
|
+
),
|
|
79
|
+
None,
|
|
80
|
+
)
|
|
81
|
+
if track is None:
|
|
82
|
+
track = {
|
|
83
|
+
"attribute": 0,
|
|
84
|
+
"flag": 0,
|
|
85
|
+
"id": self.new_id(),
|
|
86
|
+
"is_default_name": True,
|
|
87
|
+
"name": allocated.name,
|
|
88
|
+
"segments": [],
|
|
89
|
+
"type": allocated.track_type,
|
|
90
|
+
"_semantic_kind": allocated.kind,
|
|
91
|
+
"_layout_order": allocated.layout_order,
|
|
92
|
+
}
|
|
93
|
+
self.tracks.append(track)
|
|
94
|
+
track["segments"].append(segment)
|
|
95
|
+
return track
|
|
96
|
+
|
|
97
|
+
def finalize_tracks(self):
|
|
98
|
+
# Python's stable sort preserves the timeline's authored order inside
|
|
99
|
+
# one semantic band (for example narration before BGM).
|
|
100
|
+
self.tracks.sort(key=lambda item: item.get("_layout_order", 120_000))
|
|
101
|
+
type_counts = {}
|
|
102
|
+
for track in self.tracks:
|
|
103
|
+
count = type_counts.get(track["type"], 0)
|
|
104
|
+
track["flag"] = 0 if count == 0 else 2
|
|
105
|
+
type_counts[track["type"]] = count + 1
|
|
106
|
+
track.pop("_semantic_kind", None)
|
|
107
|
+
track.pop("_layout_order", None)
|
|
108
|
+
return self.tracks
|
|
109
|
+
|
|
110
|
+
def note(self, message):
|
|
111
|
+
self.notes.append(message)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Failure-isolated optional JianYing export invoked after canonical rendering."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from lib import CONFIG, log
|
|
6
|
+
|
|
7
|
+
def _maybe_export_jianying(work_dir, out_dir, stem):
|
|
8
|
+
"""Lazy-import the optional 剪映 exporter and write a draft from timeline.json."""
|
|
9
|
+
timeline_path = Path(work_dir) / "timeline.json"
|
|
10
|
+
if not timeline_path.exists():
|
|
11
|
+
log(" ⚠️ 跳过剪映导出:未找到 timeline.json")
|
|
12
|
+
return
|
|
13
|
+
try:
|
|
14
|
+
from export_jianying import export_timeline_to_jianying
|
|
15
|
+
from timeline import load_timeline
|
|
16
|
+
parent = out_dir or CONFIG.get("jianying_draft_dir") or str(work_dir)
|
|
17
|
+
draft_dir, notes = export_timeline_to_jianying(
|
|
18
|
+
load_timeline(timeline_path), parent, draft_name=f"recap_{stem}",
|
|
19
|
+
bundle_media=CONFIG.get("jianying_bundle_media", False))
|
|
20
|
+
for n in notes:
|
|
21
|
+
log(f" 注意: {n}")
|
|
22
|
+
log(f"剪映草稿已导出: {draft_dir}")
|
|
23
|
+
except Exception as exc: # optional feature must never fail the render
|
|
24
|
+
log(f" ⚠️ 剪映导出失败(不影响成片): {exc}")
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""Schema constants, skeleton factories, and material registry for JianYing export.
|
|
2
|
+
|
|
3
|
+
This module is intentionally data-oriented: it owns draft version metadata, the
|
|
4
|
+
full `materials` parallel-array shape, and the implemented material dispatch.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from jianying_templates import template
|
|
8
|
+
|
|
9
|
+
# The full 剪映 materials object: ~45 parallel arrays. Only arrays backed by a
|
|
10
|
+
# production builder are populated; retaining the full shape preserves compatibility.
|
|
11
|
+
MATERIAL_KEYS = (
|
|
12
|
+
"ai_translates audio_balances audio_effects audio_fades audio_track_indexes audios "
|
|
13
|
+
"beats canvases chromas color_curves digital_humans drafts effects flowers green_screens "
|
|
14
|
+
"handwrites hsl images log_color_wheels loudnesses manual_deformations masks common_mask "
|
|
15
|
+
"material_animations material_colors multi_language_refs placeholders plugin_effects "
|
|
16
|
+
"primary_color_wheels realtime_denoises shapes smart_crops smart_relights "
|
|
17
|
+
"sound_channel_mappings speeds stickers tail_leaders text_templates texts time_marks "
|
|
18
|
+
"transitions video_effects video_trackings videos vocal_beautifys vocal_separations"
|
|
19
|
+
).split()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def us(seconds):
|
|
23
|
+
"""Seconds (float) -> integer microseconds. The single seconds->µs boundary."""
|
|
24
|
+
return int(round(float(seconds) * 1_000_000))
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def full_materials(filled):
|
|
28
|
+
"""Return a complete JianYing `materials` object with all known arrays."""
|
|
29
|
+
out = {k: [] for k in MATERIAL_KEYS}
|
|
30
|
+
out.update(filled)
|
|
31
|
+
return out
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def scrub_platform_identity(project):
|
|
35
|
+
"""Remove hardware fingerprints carried by the pinned upstream templates."""
|
|
36
|
+
for platform_key in ("last_modified_platform", "platform"):
|
|
37
|
+
platform = project.get(platform_key)
|
|
38
|
+
if not isinstance(platform, dict):
|
|
39
|
+
continue
|
|
40
|
+
for identity_key in ("device_id", "hard_disk_id", "mac_address"):
|
|
41
|
+
platform[identity_key] = ""
|
|
42
|
+
return project
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def draft_content_skeleton(draft_id, width, height, fps, total_us, materials, tracks):
|
|
46
|
+
"""Build the root `draft_content.json` / `draft_info.json` skeleton."""
|
|
47
|
+
content = template("project")
|
|
48
|
+
scrub_platform_identity(content)
|
|
49
|
+
content["canvas_config"] = {"width": width, "height": height, "ratio": "original"}
|
|
50
|
+
content["duration"] = int(total_us)
|
|
51
|
+
content["fps"] = float(fps)
|
|
52
|
+
content["id"] = draft_id
|
|
53
|
+
content["materials"] = full_materials(materials)
|
|
54
|
+
content["tracks"] = tracks
|
|
55
|
+
return content
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def meta_info(draft_id, total_us):
|
|
59
|
+
"""Build the companion `draft_meta_info.json` skeleton."""
|
|
60
|
+
meta = template("meta")
|
|
61
|
+
meta["draft_id"] = draft_id
|
|
62
|
+
meta["draft_timeline_materials_size_"] = 0
|
|
63
|
+
meta["tm_duration"] = int(total_us)
|
|
64
|
+
return meta
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def material_category_registry():
|
|
68
|
+
"""Material category support table inspired by duo-video's MaterialTypeEnum.
|
|
69
|
+
|
|
70
|
+
Keyframes, bundling, and path rewrite are exporter behavior, not material
|
|
71
|
+
categories, so they intentionally do not appear here.
|
|
72
|
+
"""
|
|
73
|
+
return {
|
|
74
|
+
"video": {"status": "supported", "materials_key": "videos", "track_type": "video"},
|
|
75
|
+
"audio": {"status": "supported", "materials_key": "audios", "track_type": "audio"},
|
|
76
|
+
"text": {"status": "supported", "materials_key": "texts", "track_type": "text"},
|
|
77
|
+
"subtitle": {"status": "supported", "materials_key": "texts", "track_type": "text"},
|
|
78
|
+
"speed": {"status": "supported_auxiliary", "materials_key": "speeds", "track_type": None},
|
|
79
|
+
# JianYing represents photos in materials.videos with material.type=photo.
|
|
80
|
+
"image": {"status": "supported", "materials_key": "videos", "track_type": "video"},
|
|
81
|
+
"sticker": {"status": "supported_offline_payload", "materials_key": "stickers", "track_type": "sticker"},
|
|
82
|
+
"sound": {"status": "supported_offline_payload", "materials_key": "audios", "track_type": "audio"},
|
|
83
|
+
"text_template": {"status": "supported_offline_payload", "materials_key": "text_templates", "track_type": "text"},
|
|
84
|
+
"lut": {"status": "supported_offline_payload", "materials_key": "effects", "track_type": "video"},
|
|
85
|
+
"transition": {"status": "supported_offline_payload", "materials_key": "transitions", "track_type": "video"},
|
|
86
|
+
"video_effect": {"status": "supported_offline_payload", "materials_key": "video_effects", "track_type": "effect"},
|
|
87
|
+
"face_effect": {"status": "supported_offline_payload", "materials_key": "video_effects", "track_type": "effect"},
|
|
88
|
+
"mask": {"status": "supported_offline_payload", "materials_key": "masks", "track_type": "video"},
|
|
89
|
+
"chroma": {"status": "supported_offline_payload", "materials_key": "chromas", "track_type": "video"},
|
|
90
|
+
"green_screen": {"status": "supported", "materials_key": "drafts", "track_type": "video"},
|
|
91
|
+
"compound": {"status": "supported", "materials_key": "drafts", "track_type": "video"},
|
|
92
|
+
# duo-video style is authoring input rather than a direct material array.
|
|
93
|
+
"style": {"status": "supported", "materials_key": None, "track_type": None},
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def validate_material_category(category):
|
|
98
|
+
"""Return deterministic support metadata for a public or future material kind."""
|
|
99
|
+
normalized = (category or "").strip().lower()
|
|
100
|
+
registry = material_category_registry()
|
|
101
|
+
info = dict(registry.get(normalized, {"status": "unsupported", "materials_key": None, "track_type": None}))
|
|
102
|
+
info["category"] = normalized
|
|
103
|
+
info["supported"] = info["status"] in {
|
|
104
|
+
"supported", "supported_auxiliary", "supported_offline_payload",
|
|
105
|
+
}
|
|
106
|
+
if not info["supported"]:
|
|
107
|
+
status = "保留但暂未实现" if info["status"] == "reserved" else "未知"
|
|
108
|
+
info["note"] = f"暂不支持的 JianYing material category: {normalized}({status}),已跳过以避免生成无效草稿"
|
|
109
|
+
return info
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""Load the pinned duo-video JianYing protocol templates."""
|
|
2
|
+
|
|
3
|
+
import copy
|
|
4
|
+
import json
|
|
5
|
+
from functools import lru_cache
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
_TEMPLATE_DIR = Path(__file__).resolve().parent.parent / "references" / "jianying"
|
|
10
|
+
_TEMPLATE_FILES = {
|
|
11
|
+
"project": "empty_jy_project_info.json",
|
|
12
|
+
"video": "empty_jy_material_video.json",
|
|
13
|
+
"audio": "empty_yj_material_audio.json",
|
|
14
|
+
"text": "empty_yj_material_text.json",
|
|
15
|
+
"text_style": "empty_jy_text_styles.json",
|
|
16
|
+
"segment": "empty_jy_segment.json",
|
|
17
|
+
"draft": "empty_jy_draft.json",
|
|
18
|
+
"combination_segment": "empty_jy_combination_segment.json",
|
|
19
|
+
"combination_video": "empty_jy_combination_video_material.json",
|
|
20
|
+
"meta": "empty_draft_meta_info.json",
|
|
21
|
+
"meta_material": "empty_jy_meta_material_value.json",
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@lru_cache(maxsize=None)
|
|
26
|
+
def _read_template(name):
|
|
27
|
+
try:
|
|
28
|
+
filename = _TEMPLATE_FILES[name]
|
|
29
|
+
except KeyError as exc:
|
|
30
|
+
raise ValueError(f"unknown JianYing template: {name}") from exc
|
|
31
|
+
with open(_TEMPLATE_DIR / filename, encoding="utf-8") as source:
|
|
32
|
+
return json.load(source)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def template(name):
|
|
36
|
+
"""Return a mutable deep copy of a pinned protocol template."""
|
|
37
|
+
return copy.deepcopy(_read_template(name))
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"""Timeline validation and migration at the JianYing adapter boundary."""
|
|
2
|
+
|
|
3
|
+
import copy
|
|
4
|
+
import math
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
CURRENT_SCHEMA_VERSION = 2
|
|
8
|
+
RESOURCE_TRACK_KINDS = {
|
|
9
|
+
"face_effect", "sound", "sticker", "text_template", "video_effect",
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _error(path, expectation):
|
|
14
|
+
raise ValueError(f"invalid timeline {path}: {expectation}")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _is_number(value):
|
|
18
|
+
if not isinstance(value, (int, float)) or isinstance(value, bool):
|
|
19
|
+
return False
|
|
20
|
+
try:
|
|
21
|
+
return math.isfinite(value)
|
|
22
|
+
except OverflowError:
|
|
23
|
+
return False
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _field_path(path, key):
|
|
27
|
+
return f"{path}.{key}" if path else key
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _require_number(container, key, path, *, minimum=None):
|
|
31
|
+
field_path = _field_path(path, key)
|
|
32
|
+
if key not in container or not _is_number(container[key]):
|
|
33
|
+
_error(field_path, "must be a finite number")
|
|
34
|
+
value = container[key]
|
|
35
|
+
if minimum is not None and value < minimum:
|
|
36
|
+
_error(field_path, f"must be >= {minimum}")
|
|
37
|
+
return value
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _require_string(container, key, path):
|
|
41
|
+
value = container.get(key)
|
|
42
|
+
if not isinstance(value, str) or not value:
|
|
43
|
+
_error(_field_path(path, key), "must be a non-empty string")
|
|
44
|
+
return value
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _validate_span(item, path, start_key="timeline_start", end_key="timeline_end"):
|
|
48
|
+
start = _require_number(item, start_key, path, minimum=0)
|
|
49
|
+
end = _require_number(item, end_key, path, minimum=0)
|
|
50
|
+
if end <= start:
|
|
51
|
+
_error(f"{path}.{end_key}", f"must be greater than {start_key}")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _validate_video_clip(clip, path):
|
|
55
|
+
if not isinstance(clip, dict):
|
|
56
|
+
_error(path, "must be an object")
|
|
57
|
+
_require_string(clip, "source_path", path)
|
|
58
|
+
_validate_span(clip, path)
|
|
59
|
+
_validate_span(clip, path, "source_start", "source_end")
|
|
60
|
+
if "audio" in clip and not isinstance(clip["audio"], dict):
|
|
61
|
+
_error(f"{path}.audio", "must be an object")
|
|
62
|
+
if "speed" in clip:
|
|
63
|
+
speed = _require_number(clip, "speed", path)
|
|
64
|
+
if speed <= 0:
|
|
65
|
+
_error(f"{path}.speed", "must be greater than 0")
|
|
66
|
+
source_duration = float(clip["source_end"]) - float(clip["source_start"])
|
|
67
|
+
target_duration = float(clip["timeline_end"]) - float(clip["timeline_start"])
|
|
68
|
+
expected_source_duration = target_duration * float(speed)
|
|
69
|
+
if not math.isclose(source_duration, expected_source_duration, rel_tol=1e-6, abs_tol=1e-4):
|
|
70
|
+
_error(
|
|
71
|
+
path,
|
|
72
|
+
"source duration must equal target duration multiplied by speed "
|
|
73
|
+
f"({source_duration} != {target_duration} * {speed})",
|
|
74
|
+
)
|
|
75
|
+
if "reverse" in clip and not isinstance(clip["reverse"], bool):
|
|
76
|
+
_error(f"{path}.reverse", "must be a boolean")
|
|
77
|
+
if clip.get("reverse"):
|
|
78
|
+
_require_string(clip, "reverse_path", path)
|
|
79
|
+
for field in ("scale", "position", "flip"):
|
|
80
|
+
if field in clip and not isinstance(clip[field], dict):
|
|
81
|
+
_error(f"{path}.{field}", "must be an object")
|
|
82
|
+
for field in ("transition", "mask", "lut", "chroma"):
|
|
83
|
+
if field in clip and not isinstance(clip[field], (dict, str)):
|
|
84
|
+
_error(f"{path}.{field}", "must be an object or resource-package name")
|
|
85
|
+
if "green_background" in clip and not isinstance(clip["green_background"], dict):
|
|
86
|
+
_error(f"{path}.green_background", "must be a local media object")
|
|
87
|
+
if "compound" in clip and not isinstance(clip["compound"], bool):
|
|
88
|
+
_error(f"{path}.compound", "must be a boolean")
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _validate_segment(segment, path, kind):
|
|
92
|
+
if not isinstance(segment, dict):
|
|
93
|
+
_error(path, "must be an object")
|
|
94
|
+
_validate_span(segment, path)
|
|
95
|
+
if "speed" in segment:
|
|
96
|
+
speed = _require_number(segment, "speed", path)
|
|
97
|
+
if speed <= 0:
|
|
98
|
+
_error(f"{path}.speed", "must be greater than 0")
|
|
99
|
+
if kind in {"audio", "image"}:
|
|
100
|
+
_require_string(segment, "source_path", path)
|
|
101
|
+
elif kind == "text" and not isinstance(segment.get("text"), str):
|
|
102
|
+
_error(f"{path}.text", "must be a string")
|
|
103
|
+
elif kind in RESOURCE_TRACK_KINDS:
|
|
104
|
+
sources = [key for key in ("material", "resource_config", "resource_package") if key in segment]
|
|
105
|
+
if len(sources) != 1:
|
|
106
|
+
_error(path, "must define exactly one of material, resource_config, or resource_package")
|
|
107
|
+
source = segment[sources[0]]
|
|
108
|
+
if sources[0] == "resource_package":
|
|
109
|
+
if not isinstance(source, str) or not source:
|
|
110
|
+
_error(f"{path}.resource_package", "must be a non-empty string")
|
|
111
|
+
elif sources[0] == "material" and not isinstance(source, dict):
|
|
112
|
+
_error(f"{path}.material", "must be an object")
|
|
113
|
+
elif sources[0] == "resource_config" and not isinstance(source, (dict, str)):
|
|
114
|
+
_error(f"{path}.resource_config", "must be an object or JSON file path")
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _validate_track(track, path):
|
|
118
|
+
if not isinstance(track, dict):
|
|
119
|
+
_error(path, "must be an object")
|
|
120
|
+
kind = _require_string(track, "kind", path)
|
|
121
|
+
if "name" in track and (not isinstance(track["name"], str) or not track["name"]):
|
|
122
|
+
_error(f"{path}.name", "must be a non-empty string")
|
|
123
|
+
|
|
124
|
+
if kind == "video":
|
|
125
|
+
clips = track.get("clips")
|
|
126
|
+
if not isinstance(clips, list):
|
|
127
|
+
_error(f"{path}.clips", "must be an array")
|
|
128
|
+
for index, clip in enumerate(clips):
|
|
129
|
+
_validate_video_clip(clip, f"{path}.clips[{index}]")
|
|
130
|
+
return
|
|
131
|
+
|
|
132
|
+
if kind in {"audio", "image", "text"} | RESOURCE_TRACK_KINDS:
|
|
133
|
+
segments = track.get("segments")
|
|
134
|
+
if not isinstance(segments, list):
|
|
135
|
+
_error(f"{path}.segments", "must be an array")
|
|
136
|
+
if kind == "audio":
|
|
137
|
+
if "role" in track and not isinstance(track["role"], str):
|
|
138
|
+
_error(f"{path}.role", "must be a string")
|
|
139
|
+
if "loop" in track and not isinstance(track["loop"], bool):
|
|
140
|
+
_error(f"{path}.loop", "must be a boolean")
|
|
141
|
+
for index, segment in enumerate(segments):
|
|
142
|
+
_validate_segment(segment, f"{path}.segments[{index}]", kind)
|
|
143
|
+
return
|
|
144
|
+
|
|
145
|
+
_error(f"{path}.kind", f"unsupported track kind {kind!r}")
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _validate_v2(timeline):
|
|
149
|
+
canvas = timeline.get("canvas")
|
|
150
|
+
if not isinstance(canvas, dict):
|
|
151
|
+
_error("canvas", "must be an object")
|
|
152
|
+
for dimension in ("width", "height"):
|
|
153
|
+
value = canvas.get(dimension)
|
|
154
|
+
if not isinstance(value, int) or isinstance(value, bool) or value <= 0:
|
|
155
|
+
_error(f"canvas.{dimension}", "must be a positive integer")
|
|
156
|
+
fps = _require_number(canvas, "fps", "canvas")
|
|
157
|
+
if fps <= 0:
|
|
158
|
+
_error("canvas.fps", "must be greater than 0")
|
|
159
|
+
|
|
160
|
+
_require_number(timeline, "duration", "", minimum=0)
|
|
161
|
+
for registry_name in ("resource_packages", "style_presets"):
|
|
162
|
+
if registry_name in timeline and not isinstance(timeline[registry_name], dict):
|
|
163
|
+
_error(registry_name, "must be an object")
|
|
164
|
+
tracks = timeline.get("tracks")
|
|
165
|
+
if not isinstance(tracks, list):
|
|
166
|
+
_error("tracks", "must be an array")
|
|
167
|
+
for index, track in enumerate(tracks):
|
|
168
|
+
_validate_track(track, f"tracks[{index}]")
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def normalize_timeline(timeline):
|
|
172
|
+
"""Return a validated schema-v2 copy, migrating schema v1 when necessary."""
|
|
173
|
+
if not isinstance(timeline, dict):
|
|
174
|
+
_error("root", "must be an object")
|
|
175
|
+
schema_version = timeline.get("schema_version")
|
|
176
|
+
if not isinstance(schema_version, int) or isinstance(schema_version, bool):
|
|
177
|
+
_error("schema_version", "must be integer 1 or 2")
|
|
178
|
+
if schema_version not in {1, CURRENT_SCHEMA_VERSION}:
|
|
179
|
+
raise ValueError(
|
|
180
|
+
f"unsupported timeline schema_version {schema_version}; "
|
|
181
|
+
f"supported versions are 1 and {CURRENT_SCHEMA_VERSION}"
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
normalized = copy.deepcopy(timeline)
|
|
185
|
+
if schema_version == 1:
|
|
186
|
+
# Schema v2 is an additive extension of v1 (local image tracks). The
|
|
187
|
+
# migration therefore preserves all authored v1 fields and only advances
|
|
188
|
+
# the version before applying the current contract.
|
|
189
|
+
normalized["schema_version"] = CURRENT_SCHEMA_VERSION
|
|
190
|
+
_validate_v2(normalized)
|
|
191
|
+
return normalized
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""Semantic track ordering and overlap-safe allocation for JianYing export.
|
|
2
|
+
|
|
3
|
+
The order mirrors duo-video's authoring layout. It is used only to order track
|
|
4
|
+
objects; JianYing segment ``render_index`` remains the schema default and must
|
|
5
|
+
not be confused with this semantic layout value.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass(frozen=True)
|
|
12
|
+
class TrackBand:
|
|
13
|
+
kind: str
|
|
14
|
+
track_type: str
|
|
15
|
+
layout_order: int
|
|
16
|
+
description: str
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
SEGMENT_RENDER_INDEX = 2
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
TRACK_LAYOUT_BANDS = {
|
|
23
|
+
"sound": TrackBand("sound", "audio", 10_000, "sound effects"),
|
|
24
|
+
"audio": TrackBand("audio", "audio", 20_000, "narration, music, and general audio"),
|
|
25
|
+
"green_screen": TrackBand("green_screen", "video", 30_000, "green-screen background"),
|
|
26
|
+
"video": TrackBand("video", "video", 40_000, "base video"),
|
|
27
|
+
"image": TrackBand("image", "video", 50_000, "image and photo overlays"),
|
|
28
|
+
"mask": TrackBand("mask", "video", 60_000, "masks"),
|
|
29
|
+
"effect": TrackBand("effect", "effect", 70_000, "video effects"),
|
|
30
|
+
"video_effect": TrackBand("video_effect", "effect", 70_000, "video effects"),
|
|
31
|
+
"face_effect": TrackBand("face_effect", "effect", 70_010, "face effects"),
|
|
32
|
+
"sticker": TrackBand("sticker", "sticker", 80_000, "stickers"),
|
|
33
|
+
"subtitle": TrackBand("subtitle", "text", 90_000, "subtitles"),
|
|
34
|
+
"text": TrackBand("text", "text", 100_000, "plain text"),
|
|
35
|
+
"text_template": TrackBand("text_template", "text", 110_000, "text templates"),
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass(frozen=True)
|
|
40
|
+
class AllocatedTrack:
|
|
41
|
+
kind: str
|
|
42
|
+
name: str
|
|
43
|
+
track_type: str
|
|
44
|
+
layout_order: int
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class TrackAllocator:
|
|
48
|
+
"""Allocate deterministic suffix tracks when same-name segments overlap.
|
|
49
|
+
|
|
50
|
+
Intervals are half-open, so adjacent segments reuse a track while true
|
|
51
|
+
overlap creates ``name-1``, ``name-2``, and so on.
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
def __init__(self):
|
|
55
|
+
self._occupied = {}
|
|
56
|
+
|
|
57
|
+
@staticmethod
|
|
58
|
+
def _overlaps(start_us, duration_us, existing):
|
|
59
|
+
end_us = int(start_us) + int(duration_us)
|
|
60
|
+
return any(int(start_us) < old_end and end_us > old_start for old_start, old_end in existing)
|
|
61
|
+
|
|
62
|
+
def allocate(self, kind, base_name, start_us, duration_us):
|
|
63
|
+
band = TRACK_LAYOUT_BANDS.get(kind)
|
|
64
|
+
if band is None:
|
|
65
|
+
band = TrackBand(kind, "video", 120_000, "unregistered fallback")
|
|
66
|
+
base_name = base_name or kind
|
|
67
|
+
suffix = 0
|
|
68
|
+
while True:
|
|
69
|
+
name = base_name if suffix == 0 else f"{base_name}-{suffix}"
|
|
70
|
+
key = (kind, name)
|
|
71
|
+
occupied = self._occupied.setdefault(key, [])
|
|
72
|
+
if not self._overlaps(start_us, duration_us, occupied):
|
|
73
|
+
occupied.append((int(start_us), int(start_us) + int(duration_us)))
|
|
74
|
+
return AllocatedTrack(kind, name, band.track_type, band.layout_order + suffix)
|
|
75
|
+
suffix += 1
|