@oh-story/dsh 0.1.3 → 0.1.5
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 +58 -12
- package/lib/client.js +2970 -537
- package/lib/client.js.map +4 -4
- package/lib/drama/manifest.json +218 -178
- package/lib/drama/skills/short-drama/SKILL.md +110 -117
- package/lib/drama/skills/short-drama/references/audience-reveal.md +14 -67
- package/lib/drama/skills/short-drama/references/contract-and-ownership.md +26 -77
- package/lib/drama/skills/short-drama/references/creator-documents.md +204 -0
- package/lib/drama/skills/short-drama/references/creator-workflow.md +73 -63
- package/lib/drama/skills/short-drama/references/knowhow-index.md +5 -0
- package/lib/drama/skills/short-drama/references/look-development.md +2 -3
- package/lib/drama/skills/short-drama/references/pickup-and-alternate.md +14 -48
- package/lib/drama/skills/short-drama/references/production-form-profiles.md +1 -1
- package/lib/drama/skills/short-drama/references/reference-roles.md +32 -135
- package/lib/drama/skills/short-drama/references/runtime-preflight.md +16 -19
- 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 +59 -170
- package/lib/drama/skills/short-drama-assets/references/asset-review-checklist.md +18 -25
- package/lib/drama/skills/short-drama-assets/references/character-and-look.md +13 -15
- package/lib/drama/skills/short-drama-assets/references/continuity-delta.md +11 -14
- 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 +6 -12
- package/lib/drama/skills/short-drama-assets/references/occurrence-extraction.md +8 -11
- package/lib/drama/skills/short-drama-assets/references/prop-and-state.md +21 -2
- package/lib/drama/skills/short-drama-assets/references/stage-contract.md +12 -105
- package/lib/drama/skills/short-drama-assets/references/voice-direction.md +12 -32
- package/lib/drama/skills/short-drama-assets/scripts/asset_check.py +1 -1
- package/lib/drama/skills/short-drama-image-prompts/SKILL.md +45 -155
- package/lib/drama/skills/short-drama-image-prompts/references/character-and-look.md +12 -13
- package/lib/drama/skills/short-drama-image-prompts/references/common-recipe.md +12 -13
- package/lib/drama/skills/short-drama-image-prompts/references/edit-and-revision.md +39 -51
- package/lib/drama/skills/short-drama-image-prompts/references/location-plate.md +3 -4
- package/lib/drama/skills/short-drama-image-prompts/references/look-and-state-variant.md +15 -14
- package/lib/drama/skills/short-drama-image-prompts/references/lookdev-frame.md +11 -15
- package/lib/drama/skills/short-drama-image-prompts/references/production-sheet-recipes.md +1 -1
- package/lib/drama/skills/short-drama-image-prompts/references/prop-plate.md +4 -7
- package/lib/drama/skills/short-drama-image-prompts/references/review-and-fixtures.md +23 -23
- package/lib/drama/skills/short-drama-image-prompts/references/stage-contract.md +8 -65
- 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 +67 -15
- 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/SKILL.md +61 -123
- package/lib/drama/skills/short-drama-review/references/anti-template-repair.md +10 -15
- package/lib/drama/skills/short-drama-review/references/production-quality-gates.md +15 -16
- package/lib/drama/skills/short-drama-review/references/project-calibration.md +7 -9
- package/lib/drama/skills/short-drama-review/references/review-method.md +8 -9
- package/lib/drama/skills/short-drama-review/references/rubric-assets-prompts.md +9 -13
- package/lib/drama/skills/short-drama-review/references/rubric-story-script.md +104 -4
- package/lib/drama/skills/short-drama-review/references/rubric-visual-motion.md +27 -19
- package/lib/drama/skills/short-drama-review/references/stage-contract.md +4 -63
- package/lib/drama/skills/short-drama-review/scripts/review_check.py +1 -1
- package/lib/drama/skills/short-drama-storyboard/SKILL.md +116 -238
- package/lib/drama/skills/short-drama-storyboard/assets/shot-template.jsonl +1 -1
- package/lib/drama/skills/short-drama-storyboard/references/blocking-playbooks.md +9 -34
- package/lib/drama/skills/short-drama-storyboard/references/comic-keyframe-lexicon.md +156 -0
- package/lib/drama/skills/short-drama-storyboard/references/coverage-audition.md +5 -10
- package/lib/drama/skills/short-drama-storyboard/references/keyframe-craft.md +12 -7
- 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 +27 -62
- package/lib/drama/skills/short-drama-storyboard/references/scene-visual-plan.md +16 -25
- package/lib/drama/skills/short-drama-storyboard/references/screenplay-to-keyframe-example.md +21 -23
- package/lib/drama/skills/short-drama-storyboard/references/shot-craft.md +8 -10
- package/lib/drama/skills/short-drama-storyboard/references/shot-revision-identity.md +9 -13
- package/lib/drama/skills/short-drama-storyboard/references/stage-contract.md +16 -71
- package/lib/drama/skills/short-drama-storyboard/scripts/storyboard_check.py +1 -1
- package/lib/drama/skills/short-drama-video-prompts/SKILL.md +123 -230
- 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/camera-audio-continuity.md +10 -8
- package/lib/drama/skills/short-drama-video-prompts/references/delivery-profile.md +24 -6
- package/lib/drama/skills/short-drama-video-prompts/references/generability.md +2 -2
- 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 +35 -27
- package/lib/drama/skills/short-drama-video-prompts/references/performance-action-timing.md +8 -11
- package/lib/drama/skills/short-drama-video-prompts/references/production-prompt-grammar.md +13 -10
- package/lib/drama/skills/short-drama-video-prompts/references/review-and-fixtures.md +34 -63
- 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 +17 -73
- 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 +74 -268
- package/lib/drama/skills/short-drama-write/references/dialogue-craft.md +7 -0
- package/lib/drama/skills/short-drama-write/references/scene-handoff-capsule.md +17 -43
- package/lib/drama/skills/short-drama-write/references/screenplay-format.md +15 -15
- package/lib/drama/skills/short-drama-write/references/script-craft.md +208 -21
- package/lib/drama/skills/short-drama-write/references/stage-contract.md +11 -70
- 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 +971 -46
- 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 +419 -309
- package/lib/oh-story/roles/character-designer.md +4 -4
- package/lib/oh-story/roles/consistency-checker.md +1 -1
- package/lib/oh-story/roles/narrative-writer.md +41 -42
- package/lib/oh-story/roles/story-architect.md +19 -23
- package/lib/oh-story/roles/story-explorer.md +8 -5
- 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 +21 -8
- 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-long-write/references/{genre-core-mechanics.md → long-genre-mechanics.md} +1 -1
- package/lib/oh-story/skills/story-long-write/references/{reversal-toolkit.md → 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 +5 -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 +17 -11
- 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 +85 -41
- 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-setup/references/agent-references/{genre-core-mechanics.md → 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-short-write/references/reversal-toolkit.md → story-setup/references/agent-references/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-setup/references/agent-references/{reversal-toolkit.md → 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 +9 -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 +5 -2
- 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/reference-tool.d.ts +6 -0
- package/lib/types/reference-tool.d.ts.map +1 -0
- package/lib/types/role-provider.d.ts.map +1 -1
- package/lib/types/role-tool.d.ts +3 -1
- 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 +57 -41
- package/lib/drama/skills/short-drama/references/lifecycle-commands.md +0 -130
- 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
|