@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
|
@@ -32,6 +32,7 @@ MINIMAX_MUSIC_MODEL = "music-3.0"
|
|
|
32
32
|
OPENAI_BASE_URL = "https://api.openai.com/v1"
|
|
33
33
|
SEEDANCE_BASE_URL = "https://ark.cn-beijing.volces.com/api/v3"
|
|
34
34
|
MINIMAX_BASE_URL = "https://api.minimax.io/v1"
|
|
35
|
+
MINIMAX_VIDEO_BASE_URL = "https://api.minimax.io/v2"
|
|
35
36
|
MAX_JSON_RESPONSE = 128 * 1024 * 1024
|
|
36
37
|
MAX_OUTPUT_BYTES = 512 * 1024 * 1024
|
|
37
38
|
MAX_REFERENCE_BYTES = 50 * 1024 * 1024
|
|
@@ -39,6 +40,47 @@ MAX_MULTIPART_BYTES = 200 * 1024 * 1024
|
|
|
39
40
|
MAX_ERROR_BODY_BYTES = 64 * 1024
|
|
40
41
|
TERMINAL_FAILURES = {"failed", "cancelled", "canceled", "timeout", "expired"}
|
|
41
42
|
SEEDANCE_RATIOS = {"adaptive", "1:1", "3:4", "4:3", "9:16", "16:9", "21:9"}
|
|
43
|
+
SEEDANCE_REFERENCE_ROLES = {
|
|
44
|
+
"reference_image": "image_url",
|
|
45
|
+
"reference_video": "video_url",
|
|
46
|
+
"reference_audio": "audio_url",
|
|
47
|
+
}
|
|
48
|
+
MINIMAX_VIDEO_RATIOS = {"adaptive", "1:1", "3:4", "4:3", "9:16", "16:9", "21:9"}
|
|
49
|
+
MINIMAX_VIDEO_RESOLUTIONS = {"480P", "768P", "2K"}
|
|
50
|
+
MINIMAX_VIDEO_PROMPT_LIMIT = 7000
|
|
51
|
+
MINIMAX_VIDEO_ROLES = {
|
|
52
|
+
"first_frame": "image_url",
|
|
53
|
+
"last_frame": "image_url",
|
|
54
|
+
"reference_image": "image_url",
|
|
55
|
+
"reference_video": "video_url",
|
|
56
|
+
"reference_audio": "audio_url",
|
|
57
|
+
}
|
|
58
|
+
# Both providers document a `data:<mime>;base64,<...>` URI as an accepted media
|
|
59
|
+
# input alongside a public URL, so a local project reference needs no upload
|
|
60
|
+
# service to reach them. The caps are MiniMax's published per-modality limits;
|
|
61
|
+
# Seedance publishes no numbers, so the same conservative guard is applied there
|
|
62
|
+
# and can be raised per deployment. Base64 inflates bytes by about a third, so
|
|
63
|
+
# the request cap is checked against the encoded size.
|
|
64
|
+
INLINE_REFERENCE_LIMITS = {
|
|
65
|
+
"image_url": 30 * 1024 * 1024,
|
|
66
|
+
"video_url": 50 * 1024 * 1024,
|
|
67
|
+
"audio_url": 15 * 1024 * 1024,
|
|
68
|
+
}
|
|
69
|
+
INLINE_REFERENCE_BODY_LIMIT = 64 * 1024 * 1024
|
|
70
|
+
INLINE_REFERENCE_MIME = {
|
|
71
|
+
".png": "image/png",
|
|
72
|
+
".jpg": "image/jpeg",
|
|
73
|
+
".jpeg": "image/jpeg",
|
|
74
|
+
".webp": "image/webp",
|
|
75
|
+
".mp4": "video/mp4",
|
|
76
|
+
".mov": "video/quicktime",
|
|
77
|
+
".webm": "video/webm",
|
|
78
|
+
".wav": "audio/wav",
|
|
79
|
+
".mp3": "audio/mpeg",
|
|
80
|
+
".m4a": "audio/mp4",
|
|
81
|
+
".aac": "audio/aac",
|
|
82
|
+
".flac": "audio/flac",
|
|
83
|
+
}
|
|
42
84
|
GPT_IMAGE_MIN_PIXELS = 655_360
|
|
43
85
|
GPT_IMAGE_MAX_PIXELS = 8_294_400
|
|
44
86
|
MINIMUM_PYTHON = (3, 9)
|
|
@@ -161,6 +203,96 @@ def _require_job(job: Mapping[str, Any], modality: str) -> tuple[str, dict[str,
|
|
|
161
203
|
return prompt, dict(parameters)
|
|
162
204
|
|
|
163
205
|
|
|
206
|
+
def _prompt_with_reference_contract(
|
|
207
|
+
prompt: str,
|
|
208
|
+
job: Mapping[str, Any],
|
|
209
|
+
*,
|
|
210
|
+
prompt_language: str | None = None,
|
|
211
|
+
reference_tokens: Sequence[str] = (),
|
|
212
|
+
zh_reference_prefix: str = "参考",
|
|
213
|
+
zh_heading: str = "参考约束:",
|
|
214
|
+
) -> str:
|
|
215
|
+
bindings = job.get("reference_bindings", [])
|
|
216
|
+
references = job.get("references", [])
|
|
217
|
+
if not bindings:
|
|
218
|
+
return prompt
|
|
219
|
+
if (
|
|
220
|
+
not isinstance(bindings, list)
|
|
221
|
+
or not isinstance(references, list)
|
|
222
|
+
or len(bindings) != len(references)
|
|
223
|
+
):
|
|
224
|
+
raise ValueError("reference bindings must match job references")
|
|
225
|
+
if reference_tokens and len(reference_tokens) != len(references):
|
|
226
|
+
raise ValueError("reference tokens must match job references")
|
|
227
|
+
language = (prompt_language or "en").casefold()
|
|
228
|
+
instructions: list[str] = []
|
|
229
|
+
for index, binding in enumerate(bindings, 1):
|
|
230
|
+
if not isinstance(binding, Mapping) or binding.get("order") != index:
|
|
231
|
+
raise ValueError("reference binding order is invalid")
|
|
232
|
+
if binding.get("path") != references[index - 1]:
|
|
233
|
+
raise ValueError("reference binding path does not match job references")
|
|
234
|
+
label = binding.get("label")
|
|
235
|
+
role = binding.get("role")
|
|
236
|
+
may_control = binding.get("may_control")
|
|
237
|
+
must_not_control = binding.get("must_not_control")
|
|
238
|
+
if (
|
|
239
|
+
not isinstance(label, str)
|
|
240
|
+
or not label.strip()
|
|
241
|
+
or not isinstance(role, str)
|
|
242
|
+
or not role.strip()
|
|
243
|
+
or not isinstance(may_control, list)
|
|
244
|
+
or not may_control
|
|
245
|
+
or not all(isinstance(item, str) and item.strip() for item in may_control)
|
|
246
|
+
or not isinstance(must_not_control, list)
|
|
247
|
+
or not must_not_control
|
|
248
|
+
or not all(isinstance(item, str) and item.strip() for item in must_not_control)
|
|
249
|
+
):
|
|
250
|
+
raise ValueError("reference binding semantics are invalid")
|
|
251
|
+
values = {
|
|
252
|
+
"order": index,
|
|
253
|
+
"reference": reference_tokens[index - 1] if reference_tokens else str(index),
|
|
254
|
+
"label": label.strip(),
|
|
255
|
+
"role": role.strip(),
|
|
256
|
+
"may": ", ".join(item.strip() for item in may_control),
|
|
257
|
+
"must": ", ".join(item.strip() for item in must_not_control),
|
|
258
|
+
}
|
|
259
|
+
if language.startswith("zh"):
|
|
260
|
+
instructions.append(
|
|
261
|
+
f"{zh_reference_prefix} "
|
|
262
|
+
+ (
|
|
263
|
+
"{reference}({label}),用途 {role}。允许控制:{may}。"
|
|
264
|
+
"不得控制:{must}。".format(**values)
|
|
265
|
+
)
|
|
266
|
+
)
|
|
267
|
+
elif language.startswith("en"):
|
|
268
|
+
instructions.append(
|
|
269
|
+
"Reference {reference} ({label}), role {role}. May control: {may}. "
|
|
270
|
+
"Must not control: {must}.".format(**values)
|
|
271
|
+
)
|
|
272
|
+
else:
|
|
273
|
+
instructions.append(
|
|
274
|
+
"[REF {order} | {label} | {role}] [+] {may} [-] {must}".format(
|
|
275
|
+
**values
|
|
276
|
+
)
|
|
277
|
+
)
|
|
278
|
+
if language.startswith("zh"):
|
|
279
|
+
heading = zh_heading
|
|
280
|
+
elif language.startswith("en"):
|
|
281
|
+
heading = "Reference contract:"
|
|
282
|
+
else:
|
|
283
|
+
heading = "<REF_CONTRACT>"
|
|
284
|
+
return f"{prompt}\n\n{heading}\n" + "\n".join(instructions)
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def _pop_prompt_language(parameters: dict[str, Any]) -> str | None:
|
|
288
|
+
value = parameters.pop("prompt_language", None)
|
|
289
|
+
if value is None:
|
|
290
|
+
return None
|
|
291
|
+
if not isinstance(value, str) or not value.strip() or len(value) > 64:
|
|
292
|
+
raise ValueError("prompt_language must be a non-empty bounded language tag")
|
|
293
|
+
return value.strip()
|
|
294
|
+
|
|
295
|
+
|
|
164
296
|
def _take(parameters: dict[str, Any], allowed: set[str]) -> dict[str, Any]:
|
|
165
297
|
unknown = set(parameters) - allowed
|
|
166
298
|
if unknown:
|
|
@@ -173,6 +305,7 @@ def compile_seedance_payload(
|
|
|
173
305
|
*,
|
|
174
306
|
model: str,
|
|
175
307
|
reference_urls: Sequence[str] = (),
|
|
308
|
+
reference_roles: Sequence[str] = (),
|
|
176
309
|
allowed_ratios: Collection[str] | None = None,
|
|
177
310
|
duration_range: tuple[int, int] | None = None,
|
|
178
311
|
) -> dict[str, Any]:
|
|
@@ -187,24 +320,33 @@ def compile_seedance_payload(
|
|
|
187
320
|
references = job.get("references", [])
|
|
188
321
|
if not isinstance(references, list) or len(reference_urls) != len(references):
|
|
189
322
|
raise ValueError("Seedance reference URLs must match job references")
|
|
323
|
+
if len(reference_roles) != len(reference_urls):
|
|
324
|
+
raise ValueError("Seedance reference roles must match job references")
|
|
190
325
|
if Path(job["outputs"][0]).suffix.casefold() != ".mp4":
|
|
191
326
|
raise ValueError("Seedance adapter requires an MP4 target")
|
|
192
327
|
parameters = _take(
|
|
193
328
|
parameters,
|
|
194
|
-
{
|
|
329
|
+
{
|
|
330
|
+
"duration",
|
|
331
|
+
"ratio",
|
|
332
|
+
"generate_audio",
|
|
333
|
+
"omni_reference_task_type",
|
|
334
|
+
"prompt_language",
|
|
335
|
+
},
|
|
195
336
|
)
|
|
337
|
+
prompt_language = _pop_prompt_language(parameters)
|
|
196
338
|
duration = parameters.get("duration")
|
|
197
339
|
if duration is not None and (
|
|
198
340
|
not isinstance(duration, int)
|
|
199
341
|
or isinstance(duration, bool)
|
|
200
|
-
or not 1 <= duration <=
|
|
342
|
+
or (duration != -1 and not 1 <= duration <= 30)
|
|
201
343
|
):
|
|
202
|
-
raise ValueError("Seedance duration must be an integer from 1 to
|
|
203
|
-
if duration is not None:
|
|
344
|
+
raise ValueError("Seedance duration must be -1 or an integer from 1 to 30")
|
|
345
|
+
if duration is not None and duration != -1:
|
|
204
346
|
if duration_range is None:
|
|
205
347
|
raise ValueError("Seedance duration needs an explicit model profile")
|
|
206
348
|
minimum, maximum = duration_range
|
|
207
|
-
if not 1 <= minimum <= maximum <=
|
|
349
|
+
if not 1 <= minimum <= maximum <= 30 or not minimum <= duration <= maximum:
|
|
208
350
|
raise ValueError("Seedance duration is outside the configured model profile")
|
|
209
351
|
ratio = parameters.get("ratio")
|
|
210
352
|
if ratio is not None and (
|
|
@@ -217,38 +359,78 @@ def compile_seedance_payload(
|
|
|
217
359
|
raise ValueError("Seedance ratio needs an explicit model profile")
|
|
218
360
|
if not configured_ratios <= SEEDANCE_RATIOS or ratio.strip() not in configured_ratios:
|
|
219
361
|
raise ValueError("Seedance ratio is outside the configured model profile")
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
362
|
+
ratio = ratio.strip()
|
|
363
|
+
generate_audio = parameters.get("generate_audio")
|
|
364
|
+
if generate_audio is not None and not isinstance(generate_audio, bool):
|
|
365
|
+
raise ValueError("Seedance generate_audio must be a boolean")
|
|
366
|
+
task_type = parameters.get("omni_reference_task_type")
|
|
367
|
+
if task_type is not None and task_type not in {"auto", "reference", "edit", "extend"}:
|
|
368
|
+
raise ValueError("Seedance omni reference task type is invalid")
|
|
369
|
+
counters = {"image_url": 0, "video_url": 0, "audio_url": 0}
|
|
370
|
+
labels = {"image_url": "图片", "video_url": "视频", "audio_url": "音频"}
|
|
371
|
+
reference_tokens: list[str] = []
|
|
372
|
+
for role in reference_roles:
|
|
373
|
+
field = SEEDANCE_REFERENCE_ROLES.get(role)
|
|
374
|
+
if field is None:
|
|
375
|
+
raise ValueError(f"unsupported Seedance reference role: {role}")
|
|
376
|
+
counters[field] += 1
|
|
377
|
+
reference_tokens.append(f"@{labels[field]}{counters[field]}")
|
|
378
|
+
if task_type in {"edit", "extend"} and "reference_video" not in reference_roles:
|
|
379
|
+
raise ValueError(f"Seedance {task_type} requires a reference video")
|
|
380
|
+
if task_type == "edit" and (ratio != "adaptive" or duration != -1):
|
|
381
|
+
raise ValueError("Seedance edit requires adaptive ratio and duration -1")
|
|
382
|
+
if task_type == "extend" and ratio != "adaptive":
|
|
383
|
+
raise ValueError("Seedance extend requires an adaptive ratio")
|
|
384
|
+
text = _prompt_with_reference_contract(
|
|
385
|
+
prompt,
|
|
386
|
+
job,
|
|
387
|
+
prompt_language=prompt_language,
|
|
388
|
+
reference_tokens=reference_tokens,
|
|
389
|
+
zh_reference_prefix=(
|
|
390
|
+
"输入素材" if task_type in {"edit", "extend"} else "参考"
|
|
391
|
+
),
|
|
392
|
+
zh_heading=(
|
|
393
|
+
"输入素材约束:" if task_type in {"edit", "extend"} else "参考约束:"
|
|
394
|
+
),
|
|
395
|
+
)
|
|
225
396
|
content: list[dict[str, Any]] = [{"type": "text", "text": text}]
|
|
226
397
|
for index, url in enumerate(reference_urls):
|
|
227
398
|
if not isinstance(url, str) or not url:
|
|
228
399
|
raise ValueError("Seedance reference URL must be non-empty")
|
|
400
|
+
role = reference_roles[index]
|
|
401
|
+
field = SEEDANCE_REFERENCE_ROLES[role]
|
|
229
402
|
suffix = Path(str(references[index])).suffix.casefold()
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
403
|
+
expected = {
|
|
404
|
+
"image_url": {".png", ".jpg", ".jpeg", ".webp"},
|
|
405
|
+
"video_url": {".mp4", ".mov", ".webm"},
|
|
406
|
+
"audio_url": {".wav", ".mp3", ".m4a", ".aac", ".flac"},
|
|
407
|
+
}[field]
|
|
408
|
+
if suffix not in expected:
|
|
409
|
+
raise ValueError(f"Seedance {role} does not match the reference file type")
|
|
410
|
+
parsed = urllib.parse.urlparse(url)
|
|
411
|
+
if not (
|
|
412
|
+
(parsed.scheme == "https" and parsed.netloc)
|
|
413
|
+
or (
|
|
235
414
|
parsed.scheme == "asset"
|
|
236
415
|
and parsed.netloc.startswith("asset-")
|
|
237
416
|
and not parsed.path
|
|
238
|
-
):
|
|
239
|
-
pass
|
|
240
|
-
else:
|
|
241
|
-
raise ValueError("Seedance reference URL must be HTTPS or asset://")
|
|
242
|
-
content.append(
|
|
243
|
-
{
|
|
244
|
-
"type": "image_url",
|
|
245
|
-
"image_url": {"url": url},
|
|
246
|
-
"role": "reference_image",
|
|
247
|
-
}
|
|
248
417
|
)
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
418
|
+
or _is_inline_reference(url)
|
|
419
|
+
):
|
|
420
|
+
raise ValueError(
|
|
421
|
+
"Seedance reference URL must be HTTPS, asset:// or a base64 data URI"
|
|
422
|
+
)
|
|
423
|
+
content.append({"type": field, field: {"url": url}, "role": role})
|
|
424
|
+
body: dict[str, Any] = {"model": model.strip(), "content": content}
|
|
425
|
+
if ratio is not None:
|
|
426
|
+
body["ratio"] = ratio
|
|
427
|
+
if duration is not None:
|
|
428
|
+
body["duration"] = duration
|
|
429
|
+
if generate_audio is not None:
|
|
430
|
+
body["generate_audio"] = generate_audio
|
|
431
|
+
if task_type is not None:
|
|
432
|
+
body["omni_reference_task_type"] = task_type
|
|
433
|
+
return body
|
|
252
434
|
|
|
253
435
|
|
|
254
436
|
def _seedance_runtime_profile(
|
|
@@ -288,7 +470,7 @@ def _seedance_runtime_profile(
|
|
|
288
470
|
code="invalid_model_profile",
|
|
289
471
|
)
|
|
290
472
|
if duration_range is not None and not (
|
|
291
|
-
1 <= duration_range[0] <= duration_range[1] <=
|
|
473
|
+
1 <= duration_range[0] <= duration_range[1] <= 30
|
|
292
474
|
):
|
|
293
475
|
raise AdapterFailure(
|
|
294
476
|
"Seedance duration profile is invalid",
|
|
@@ -298,12 +480,198 @@ def _seedance_runtime_profile(
|
|
|
298
480
|
return allowed_ratios, duration_range
|
|
299
481
|
|
|
300
482
|
|
|
483
|
+
def compile_minimax_h3_payload(
|
|
484
|
+
job: Mapping[str, Any],
|
|
485
|
+
*,
|
|
486
|
+
model: str,
|
|
487
|
+
reference_urls: Sequence[str] = (),
|
|
488
|
+
reference_roles: Sequence[str] = (),
|
|
489
|
+
allowed_ratios: Collection[str] | None = None,
|
|
490
|
+
allowed_resolutions: Collection[str] | None = None,
|
|
491
|
+
duration_range: tuple[int, int] | None = None,
|
|
492
|
+
) -> dict[str, Any]:
|
|
493
|
+
"""Compile a video job into the official MiniMax video-generation task body.
|
|
494
|
+
|
|
495
|
+
``model`` is mandatory for the same reason it is for Seedance: which release
|
|
496
|
+
is enabled, and what it accepts, is deployment configuration, never an
|
|
497
|
+
assumption made here.
|
|
498
|
+
"""
|
|
499
|
+
prompt, parameters = _require_job(job, "video")
|
|
500
|
+
if not isinstance(model, str) or not model.strip():
|
|
501
|
+
raise ValueError("MiniMax video model must be explicitly configured")
|
|
502
|
+
references = job.get("references", [])
|
|
503
|
+
if not isinstance(references, list) or len(reference_urls) != len(references):
|
|
504
|
+
raise ValueError("MiniMax reference URLs must match job references")
|
|
505
|
+
if len(reference_roles) != len(reference_urls):
|
|
506
|
+
raise ValueError("MiniMax reference roles must match job references")
|
|
507
|
+
if Path(job["outputs"][0]).suffix.casefold() != ".mp4":
|
|
508
|
+
raise ValueError("MiniMax video adapter requires an MP4 target")
|
|
509
|
+
parameters = _take(
|
|
510
|
+
parameters, {"duration", "ratio", "resolution", "prompt_language"}
|
|
511
|
+
)
|
|
512
|
+
prompt_language = _pop_prompt_language(parameters)
|
|
513
|
+
|
|
514
|
+
duration = parameters.get("duration")
|
|
515
|
+
if not isinstance(duration, int) or isinstance(duration, bool):
|
|
516
|
+
raise ValueError("MiniMax video duration must be an integer number of seconds")
|
|
517
|
+
if duration_range is None:
|
|
518
|
+
raise ValueError("MiniMax video duration needs an explicit model profile")
|
|
519
|
+
minimum, maximum = duration_range
|
|
520
|
+
if not 1 <= minimum <= maximum or not minimum <= duration <= maximum:
|
|
521
|
+
raise ValueError("MiniMax video duration is outside the configured model profile")
|
|
522
|
+
|
|
523
|
+
resolution = parameters.get("resolution")
|
|
524
|
+
if not isinstance(resolution, str) or resolution.strip() not in MINIMAX_VIDEO_RESOLUTIONS:
|
|
525
|
+
raise ValueError("MiniMax video resolution is outside the supported profile")
|
|
526
|
+
configured_resolutions = set(allowed_resolutions or ())
|
|
527
|
+
if not configured_resolutions:
|
|
528
|
+
raise ValueError("MiniMax video resolution needs an explicit model profile")
|
|
529
|
+
if (
|
|
530
|
+
not configured_resolutions <= MINIMAX_VIDEO_RESOLUTIONS
|
|
531
|
+
or resolution.strip() not in configured_resolutions
|
|
532
|
+
):
|
|
533
|
+
raise ValueError("MiniMax video resolution is outside the configured model profile")
|
|
534
|
+
|
|
535
|
+
ratio = parameters.get("ratio")
|
|
536
|
+
if ratio is not None:
|
|
537
|
+
if not isinstance(ratio, str) or ratio.strip() not in MINIMAX_VIDEO_RATIOS:
|
|
538
|
+
raise ValueError("MiniMax video ratio is outside the supported profile")
|
|
539
|
+
configured_ratios = set(allowed_ratios or ())
|
|
540
|
+
if not configured_ratios:
|
|
541
|
+
raise ValueError("MiniMax video ratio needs an explicit model profile")
|
|
542
|
+
if (
|
|
543
|
+
not configured_ratios <= MINIMAX_VIDEO_RATIOS
|
|
544
|
+
or ratio.strip() not in configured_ratios
|
|
545
|
+
):
|
|
546
|
+
raise ValueError("MiniMax video ratio is outside the configured model profile")
|
|
547
|
+
|
|
548
|
+
counters = {"image_url": 0, "video_url": 0, "audio_url": 0}
|
|
549
|
+
labels = {"image_url": "Picture", "video_url": "Video", "audio_url": "Audio"}
|
|
550
|
+
reference_tokens: list[str] = []
|
|
551
|
+
for role in reference_roles:
|
|
552
|
+
field = MINIMAX_VIDEO_ROLES.get(role)
|
|
553
|
+
if field is None:
|
|
554
|
+
raise ValueError(f"unsupported MiniMax reference role: {role}")
|
|
555
|
+
counters[field] += 1
|
|
556
|
+
reference_tokens.append(f"<{labels[field]} {counters[field]}>")
|
|
557
|
+
text = _prompt_with_reference_contract(
|
|
558
|
+
prompt,
|
|
559
|
+
job,
|
|
560
|
+
prompt_language=prompt_language,
|
|
561
|
+
reference_tokens=reference_tokens,
|
|
562
|
+
)
|
|
563
|
+
if len(text) > MINIMAX_VIDEO_PROMPT_LIMIT:
|
|
564
|
+
raise ValueError("MiniMax video prompt exceeds the provider limit")
|
|
565
|
+
content: list[dict[str, Any]] = [{"type": "text", "text": text}]
|
|
566
|
+
seen_roles: list[str] = []
|
|
567
|
+
for index, url in enumerate(reference_urls):
|
|
568
|
+
role = reference_roles[index]
|
|
569
|
+
if role not in MINIMAX_VIDEO_ROLES:
|
|
570
|
+
raise ValueError(f"unsupported MiniMax reference role: {role}")
|
|
571
|
+
if role in {"first_frame", "last_frame"} and role in seen_roles:
|
|
572
|
+
raise ValueError(f"MiniMax accepts one {role} reference")
|
|
573
|
+
seen_roles.append(role)
|
|
574
|
+
if not isinstance(url, str) or not url:
|
|
575
|
+
raise ValueError("MiniMax reference URL must be non-empty")
|
|
576
|
+
parsed = urllib.parse.urlparse(url)
|
|
577
|
+
if not (
|
|
578
|
+
(parsed.scheme == "https" and parsed.netloc)
|
|
579
|
+
or (parsed.scheme == "mm_file" and parsed.netloc and not parsed.path)
|
|
580
|
+
or _is_inline_reference(url)
|
|
581
|
+
):
|
|
582
|
+
raise ValueError(
|
|
583
|
+
"MiniMax reference URL must be HTTPS, mm_file:// or a base64 data URI"
|
|
584
|
+
)
|
|
585
|
+
field = MINIMAX_VIDEO_ROLES[role]
|
|
586
|
+
content.append({"type": field, field: {"url": url}, "role": role})
|
|
587
|
+
frame_roles = {"first_frame", "last_frame"}
|
|
588
|
+
reference_roles_present = {"reference_image", "reference_video", "reference_audio"}
|
|
589
|
+
if frame_roles.intersection(seen_roles) and reference_roles_present.intersection(
|
|
590
|
+
seen_roles
|
|
591
|
+
):
|
|
592
|
+
raise ValueError(
|
|
593
|
+
"MiniMax frame conditioning cannot be mixed with reference conditioning"
|
|
594
|
+
)
|
|
595
|
+
if ratio is None and len(content) == 1:
|
|
596
|
+
raise ValueError("MiniMax text-to-video requires an explicit ratio")
|
|
597
|
+
body: dict[str, Any] = {
|
|
598
|
+
"model": model.strip(),
|
|
599
|
+
"content": content,
|
|
600
|
+
"duration": duration,
|
|
601
|
+
"resolution": resolution.strip(),
|
|
602
|
+
}
|
|
603
|
+
if ratio is not None:
|
|
604
|
+
if ratio.strip() == "adaptive" and len(content) == 1:
|
|
605
|
+
raise ValueError("MiniMax text-to-video cannot use an adaptive ratio")
|
|
606
|
+
body["ratio"] = ratio.strip()
|
|
607
|
+
return body
|
|
608
|
+
|
|
609
|
+
|
|
610
|
+
def _minimax_video_runtime_profile() -> tuple[
|
|
611
|
+
frozenset[str] | None, frozenset[str] | None, tuple[int, int] | None
|
|
612
|
+
]:
|
|
613
|
+
def _set(name: str, supported: Collection[str]) -> frozenset[str] | None:
|
|
614
|
+
raw = os.environ.get(name)
|
|
615
|
+
if raw is None:
|
|
616
|
+
return None
|
|
617
|
+
values = frozenset(item.strip() for item in raw.split(",") if item.strip())
|
|
618
|
+
if not values or not values <= set(supported):
|
|
619
|
+
raise AdapterFailure(
|
|
620
|
+
f"MiniMax video profile is invalid: {name}",
|
|
621
|
+
category="configuration",
|
|
622
|
+
code="invalid_model_profile",
|
|
623
|
+
)
|
|
624
|
+
return values
|
|
625
|
+
|
|
626
|
+
allowed_ratios = _set("MINIMAX_VIDEO_RATIOS", MINIMAX_VIDEO_RATIOS)
|
|
627
|
+
allowed_resolutions = _set("MINIMAX_VIDEO_RESOLUTIONS", MINIMAX_VIDEO_RESOLUTIONS)
|
|
628
|
+
minimum_raw = os.environ.get("MINIMAX_VIDEO_MIN_DURATION")
|
|
629
|
+
maximum_raw = os.environ.get("MINIMAX_VIDEO_MAX_DURATION")
|
|
630
|
+
if (minimum_raw is None) != (maximum_raw is None):
|
|
631
|
+
raise AdapterFailure(
|
|
632
|
+
"MiniMax video duration profile is incomplete",
|
|
633
|
+
category="configuration",
|
|
634
|
+
code="invalid_model_profile",
|
|
635
|
+
)
|
|
636
|
+
try:
|
|
637
|
+
duration_range = (
|
|
638
|
+
(int(minimum_raw), int(maximum_raw))
|
|
639
|
+
if minimum_raw is not None and maximum_raw is not None
|
|
640
|
+
else None
|
|
641
|
+
)
|
|
642
|
+
except ValueError as exc:
|
|
643
|
+
raise AdapterFailure(
|
|
644
|
+
"MiniMax video duration profile is invalid",
|
|
645
|
+
category="configuration",
|
|
646
|
+
code="invalid_model_profile",
|
|
647
|
+
) from exc
|
|
648
|
+
if duration_range is not None and not 1 <= duration_range[0] <= duration_range[1]:
|
|
649
|
+
raise AdapterFailure(
|
|
650
|
+
"MiniMax video duration profile is invalid",
|
|
651
|
+
category="configuration",
|
|
652
|
+
code="invalid_model_profile",
|
|
653
|
+
)
|
|
654
|
+
return allowed_ratios, allowed_resolutions, duration_range
|
|
655
|
+
|
|
656
|
+
|
|
301
657
|
def compile_gpt_image_2_payload(job: Mapping[str, Any]) -> dict[str, Any]:
|
|
302
658
|
"""Compile an image job into GPT Image 2 generation/edit fields."""
|
|
303
659
|
prompt, parameters = _require_job(job, "image")
|
|
304
660
|
parameters = _take(
|
|
305
661
|
parameters,
|
|
306
|
-
{
|
|
662
|
+
{
|
|
663
|
+
"width",
|
|
664
|
+
"height",
|
|
665
|
+
"size",
|
|
666
|
+
"quality",
|
|
667
|
+
"background",
|
|
668
|
+
"moderation",
|
|
669
|
+
"prompt_language",
|
|
670
|
+
},
|
|
671
|
+
)
|
|
672
|
+
prompt_language = _pop_prompt_language(parameters)
|
|
673
|
+
prompt = _prompt_with_reference_contract(
|
|
674
|
+
prompt, job, prompt_language=prompt_language
|
|
307
675
|
)
|
|
308
676
|
if len(prompt) > 32000:
|
|
309
677
|
raise ValueError("GPT Image prompt exceeds the provider limit")
|
|
@@ -499,7 +867,9 @@ def _read_reference(path: Path) -> bytes:
|
|
|
499
867
|
raise ValueError("reference is not a regular file")
|
|
500
868
|
if before.st_size > MAX_REFERENCE_BYTES:
|
|
501
869
|
raise ValueError("reference exceeds the provider input size limit")
|
|
502
|
-
|
|
870
|
+
# Windows opens files in text mode unless told otherwise, which stops the
|
|
871
|
+
# read at the first 0x1A byte -- the seventh byte of every PNG signature.
|
|
872
|
+
flags = os.O_RDONLY | getattr(os, "O_BINARY", 0) | getattr(os, "O_NOFOLLOW", 0)
|
|
503
873
|
descriptor = os.open(path, flags)
|
|
504
874
|
try:
|
|
505
875
|
opened = os.fstat(descriptor)
|
|
@@ -573,6 +943,97 @@ def _reference_paths(job: Mapping[str, Any]) -> list[Path]:
|
|
|
573
943
|
return result
|
|
574
944
|
|
|
575
945
|
|
|
946
|
+
def _binding_roles(
|
|
947
|
+
job: Mapping[str, Any], *, allowed: Mapping[str, str], provider: str
|
|
948
|
+
) -> list[str]:
|
|
949
|
+
"""The provider role of each reference, taken from the confirmed job.
|
|
950
|
+
|
|
951
|
+
`role` is the production-side translation of the creator document's 用途, so
|
|
952
|
+
it is the job -- not this adapter -- that decides what a picture is for.
|
|
953
|
+
"""
|
|
954
|
+
bindings = job.get("reference_bindings", [])
|
|
955
|
+
references = job.get("references", [])
|
|
956
|
+
if not isinstance(bindings, list) or len(bindings) != len(references):
|
|
957
|
+
raise AdapterFailure(
|
|
958
|
+
f"{provider} references need one reference_bindings entry each, "
|
|
959
|
+
"carrying the provider role for that file",
|
|
960
|
+
category="configuration",
|
|
961
|
+
code="missing_reference_roles",
|
|
962
|
+
)
|
|
963
|
+
roles: list[str] = []
|
|
964
|
+
for binding in bindings:
|
|
965
|
+
role = binding.get("role") if isinstance(binding, Mapping) else None
|
|
966
|
+
if not isinstance(role, str) or role not in allowed:
|
|
967
|
+
raise AdapterFailure(
|
|
968
|
+
f"{provider} reference role must be one of "
|
|
969
|
+
+ ", ".join(sorted(allowed))
|
|
970
|
+
+ f"; got {role!r}",
|
|
971
|
+
category="configuration",
|
|
972
|
+
code="invalid_reference_role",
|
|
973
|
+
)
|
|
974
|
+
roles.append(role)
|
|
975
|
+
return roles
|
|
976
|
+
|
|
977
|
+
|
|
978
|
+
def _inline_reference_urls(
|
|
979
|
+
paths: Sequence[Path], roles: Sequence[str], *, allowed: Mapping[str, str], provider: str
|
|
980
|
+
) -> list[str]:
|
|
981
|
+
"""Encode local project references as `data:` URIs the provider accepts."""
|
|
982
|
+
urls: list[str] = []
|
|
983
|
+
total = 0
|
|
984
|
+
for path, role in zip(paths, roles):
|
|
985
|
+
field = allowed[role]
|
|
986
|
+
suffix = path.suffix.casefold()
|
|
987
|
+
mime = INLINE_REFERENCE_MIME.get(suffix)
|
|
988
|
+
expected = field.split("_", 1)[0]
|
|
989
|
+
if mime is None or not mime.startswith(expected):
|
|
990
|
+
raise AdapterFailure(
|
|
991
|
+
f"{provider} {role} does not accept a {suffix or 'suffixless'} file",
|
|
992
|
+
category="configuration",
|
|
993
|
+
code="invalid_reference_type",
|
|
994
|
+
)
|
|
995
|
+
try:
|
|
996
|
+
content = path.read_bytes()
|
|
997
|
+
except OSError as exc:
|
|
998
|
+
raise AdapterFailure(
|
|
999
|
+
f"{provider} could not read a project reference",
|
|
1000
|
+
category="configuration",
|
|
1001
|
+
code="unreadable_reference",
|
|
1002
|
+
) from exc
|
|
1003
|
+
if not content:
|
|
1004
|
+
raise AdapterFailure(
|
|
1005
|
+
f"{provider} project reference is empty",
|
|
1006
|
+
category="configuration",
|
|
1007
|
+
code="empty_reference",
|
|
1008
|
+
)
|
|
1009
|
+
# The bytes have to be the media type the extension claims, or the
|
|
1010
|
+
# provider rejects a request this adapter said was well formed.
|
|
1011
|
+
_validate_media_content(path.name, content)
|
|
1012
|
+
limit = INLINE_REFERENCE_LIMITS[field]
|
|
1013
|
+
if len(content) > limit:
|
|
1014
|
+
raise AdapterFailure(
|
|
1015
|
+
f"{provider} reference exceeds the {limit // (1024 * 1024)}MB inline "
|
|
1016
|
+
"limit; host it and bind an HTTPS URL instead",
|
|
1017
|
+
category="configuration",
|
|
1018
|
+
code="reference_too_large",
|
|
1019
|
+
)
|
|
1020
|
+
encoded = base64.b64encode(content).decode("ascii")
|
|
1021
|
+
total += len(encoded)
|
|
1022
|
+
if total > INLINE_REFERENCE_BODY_LIMIT:
|
|
1023
|
+
raise AdapterFailure(
|
|
1024
|
+
f"{provider} inline references exceed the request body limit; "
|
|
1025
|
+
"host the largest ones and bind HTTPS URLs instead",
|
|
1026
|
+
category="configuration",
|
|
1027
|
+
code="reference_body_too_large",
|
|
1028
|
+
)
|
|
1029
|
+
urls.append(f"data:{mime};base64,{encoded}")
|
|
1030
|
+
return urls
|
|
1031
|
+
|
|
1032
|
+
|
|
1033
|
+
def _is_inline_reference(url: str) -> bool:
|
|
1034
|
+
return bool(re.fullmatch(r"data:[\w.+-]+/[\w.+-]+;base64,[A-Za-z0-9+/]+=*", url))
|
|
1035
|
+
|
|
1036
|
+
|
|
576
1037
|
def _validate_media_content(target: str, content: bytes) -> None:
|
|
577
1038
|
suffix = Path(target).suffix.casefold()
|
|
578
1039
|
signatures = {
|
|
@@ -677,14 +1138,20 @@ def _run_seedance(job: Mapping[str, Any]) -> tuple[Path, str]:
|
|
|
677
1138
|
token = _credential("ARK_API_KEY")
|
|
678
1139
|
model = os.environ.get("SEEDANCE_MODEL", "")
|
|
679
1140
|
references = _reference_paths(job)
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
1141
|
+
reference_roles = (
|
|
1142
|
+
_binding_roles(job, allowed=SEEDANCE_REFERENCE_ROLES, provider="Seedance")
|
|
1143
|
+
if references
|
|
1144
|
+
else []
|
|
1145
|
+
)
|
|
1146
|
+
reference_urls = _inline_reference_urls(
|
|
1147
|
+
references, reference_roles, allowed=SEEDANCE_REFERENCE_ROLES, provider="Seedance"
|
|
1148
|
+
)
|
|
684
1149
|
allowed_ratios, duration_range = _seedance_runtime_profile()
|
|
685
1150
|
body = compile_seedance_payload(
|
|
686
1151
|
job,
|
|
687
1152
|
model=model,
|
|
1153
|
+
reference_urls=reference_urls,
|
|
1154
|
+
reference_roles=reference_roles,
|
|
688
1155
|
allowed_ratios=allowed_ratios,
|
|
689
1156
|
duration_range=duration_range,
|
|
690
1157
|
)
|
|
@@ -861,6 +1328,94 @@ def _run_minimax(job: Mapping[str, Any]) -> tuple[Path, str | None]:
|
|
|
861
1328
|
)
|
|
862
1329
|
|
|
863
1330
|
|
|
1331
|
+
def _run_minimax_video(job: Mapping[str, Any]) -> tuple[Path, str]:
|
|
1332
|
+
token = _credential("MINIMAX_API_KEY")
|
|
1333
|
+
model = os.environ.get("MINIMAX_VIDEO_MODEL", "")
|
|
1334
|
+
references = _reference_paths(job)
|
|
1335
|
+
reference_roles = (
|
|
1336
|
+
_binding_roles(job, allowed=MINIMAX_VIDEO_ROLES, provider="MiniMax")
|
|
1337
|
+
if references
|
|
1338
|
+
else []
|
|
1339
|
+
)
|
|
1340
|
+
reference_urls = _inline_reference_urls(
|
|
1341
|
+
references, reference_roles, allowed=MINIMAX_VIDEO_ROLES, provider="MiniMax"
|
|
1342
|
+
)
|
|
1343
|
+
ratios, resolutions, duration_range = _minimax_video_runtime_profile()
|
|
1344
|
+
body = compile_minimax_h3_payload(
|
|
1345
|
+
job,
|
|
1346
|
+
model=model,
|
|
1347
|
+
reference_urls=reference_urls,
|
|
1348
|
+
reference_roles=reference_roles,
|
|
1349
|
+
allowed_ratios=ratios,
|
|
1350
|
+
allowed_resolutions=resolutions,
|
|
1351
|
+
duration_range=duration_range,
|
|
1352
|
+
)
|
|
1353
|
+
base = _base_url("MINIMAX_VIDEO_BASE_URL", MINIMAX_VIDEO_BASE_URL)
|
|
1354
|
+
created, _ = _request_json(
|
|
1355
|
+
f"{base}/video_generation",
|
|
1356
|
+
provider="minimax-h3",
|
|
1357
|
+
body=body,
|
|
1358
|
+
token=token,
|
|
1359
|
+
)
|
|
1360
|
+
task_id = created.get("task_id")
|
|
1361
|
+
if not isinstance(task_id, str) or not task_id:
|
|
1362
|
+
raise AdapterFailure(
|
|
1363
|
+
"MiniMax did not return a task id",
|
|
1364
|
+
code=_provider_code(created) or "missing_task_id",
|
|
1365
|
+
)
|
|
1366
|
+
try:
|
|
1367
|
+
interval = float(os.environ.get("MINIMAX_VIDEO_POLL_INTERVAL", "5"))
|
|
1368
|
+
deadline = time.monotonic() + float(
|
|
1369
|
+
os.environ.get("MINIMAX_VIDEO_TIMEOUT_SECONDS", "1800")
|
|
1370
|
+
)
|
|
1371
|
+
except ValueError as exc:
|
|
1372
|
+
raise AdapterFailure("MiniMax polling configuration is invalid") from exc
|
|
1373
|
+
if interval <= 0 or deadline <= time.monotonic():
|
|
1374
|
+
raise AdapterFailure("MiniMax polling configuration is invalid")
|
|
1375
|
+
while time.monotonic() < deadline:
|
|
1376
|
+
document, _ = _request_json(
|
|
1377
|
+
f"{base}/query/video_generation/"
|
|
1378
|
+
+ urllib.parse.quote(task_id, safe=""),
|
|
1379
|
+
provider="minimax-h3",
|
|
1380
|
+
method="GET",
|
|
1381
|
+
token=token,
|
|
1382
|
+
)
|
|
1383
|
+
task = document.get("task")
|
|
1384
|
+
status = task.get("status") if isinstance(task, Mapping) else None
|
|
1385
|
+
if status == "succeeded":
|
|
1386
|
+
content = task.get("content") if isinstance(task, Mapping) else None
|
|
1387
|
+
url = content.get("url") if isinstance(content, Mapping) else None
|
|
1388
|
+
if not isinstance(url, str):
|
|
1389
|
+
raise AdapterFailure(
|
|
1390
|
+
"MiniMax succeeded without a video URL",
|
|
1391
|
+
code="missing_video_url",
|
|
1392
|
+
request_id=task_id,
|
|
1393
|
+
)
|
|
1394
|
+
return _download(
|
|
1395
|
+
job, url, job["outputs"][0], provider="minimax-h3"
|
|
1396
|
+
), task_id
|
|
1397
|
+
if isinstance(status, str) and status.casefold() in TERMINAL_FAILURES:
|
|
1398
|
+
raise AdapterFailure(
|
|
1399
|
+
"MiniMax video task failed",
|
|
1400
|
+
code="task_" + status.casefold(),
|
|
1401
|
+
request_id=task_id,
|
|
1402
|
+
)
|
|
1403
|
+
if status not in {"queued", "running", "processing", "pending", "in_progress"}:
|
|
1404
|
+
raise AdapterFailure(
|
|
1405
|
+
"MiniMax returned an unknown task status",
|
|
1406
|
+
code="unknown_task_status",
|
|
1407
|
+
request_id=task_id,
|
|
1408
|
+
)
|
|
1409
|
+
time.sleep(interval)
|
|
1410
|
+
raise AdapterFailure(
|
|
1411
|
+
"MiniMax video task polling timed out",
|
|
1412
|
+
category="timeout",
|
|
1413
|
+
code="task_poll_timeout",
|
|
1414
|
+
request_id=task_id,
|
|
1415
|
+
retryable=True,
|
|
1416
|
+
)
|
|
1417
|
+
|
|
1418
|
+
|
|
864
1419
|
def _selftest() -> None:
|
|
865
1420
|
image = {
|
|
866
1421
|
"modality": "image", "prompt": "portrait", "references": [],
|
|
@@ -884,8 +1439,30 @@ def _selftest() -> None:
|
|
|
884
1439
|
)
|
|
885
1440
|
if seedance["model"] != "configured-model":
|
|
886
1441
|
raise RuntimeError("Seedance model self-test failed")
|
|
887
|
-
if
|
|
888
|
-
raise RuntimeError("Seedance
|
|
1442
|
+
if seedance.get("ratio") != "9:16" or seedance.get("duration") != 5:
|
|
1443
|
+
raise RuntimeError("Seedance parameter self-test failed")
|
|
1444
|
+
minimax_video = compile_minimax_h3_payload(
|
|
1445
|
+
{**video, "parameters": {"duration": 6, "ratio": "9:16", "resolution": "768P"}},
|
|
1446
|
+
model="configured-video-model",
|
|
1447
|
+
allowed_ratios={"9:16"},
|
|
1448
|
+
allowed_resolutions={"768P"},
|
|
1449
|
+
duration_range=(4, 15),
|
|
1450
|
+
)
|
|
1451
|
+
if minimax_video["model"] != "configured-video-model":
|
|
1452
|
+
raise RuntimeError("MiniMax video model self-test failed")
|
|
1453
|
+
if minimax_video["duration"] != 6 or minimax_video["resolution"] != "768P":
|
|
1454
|
+
raise RuntimeError("MiniMax video parameter self-test failed")
|
|
1455
|
+
try:
|
|
1456
|
+
compile_minimax_h3_payload(
|
|
1457
|
+
{**video, "parameters": {"duration": 6, "resolution": "768P"}},
|
|
1458
|
+
model="configured-video-model",
|
|
1459
|
+
allowed_resolutions={"768P"},
|
|
1460
|
+
duration_range=(4, 15),
|
|
1461
|
+
)
|
|
1462
|
+
except ValueError:
|
|
1463
|
+
pass
|
|
1464
|
+
else:
|
|
1465
|
+
raise AssertionError("MiniMax text-to-video without a ratio was accepted")
|
|
889
1466
|
compiled_music = compile_minimax_music_payload(music)
|
|
890
1467
|
if compiled_music["model"] != MINIMAX_MUSIC_MODEL:
|
|
891
1468
|
raise RuntimeError("MiniMax model self-test failed")
|
|
@@ -908,16 +1485,21 @@ def _selftest() -> None:
|
|
|
908
1485
|
invalid_reference,
|
|
909
1486
|
model="configured-model",
|
|
910
1487
|
reference_urls=["asset://asset-example-clip"],
|
|
1488
|
+
reference_roles=["reference_image"],
|
|
911
1489
|
)
|
|
912
1490
|
except ValueError:
|
|
913
1491
|
pass
|
|
914
1492
|
else:
|
|
915
|
-
raise AssertionError("
|
|
1493
|
+
raise AssertionError("mismatched Seedance reference role was accepted")
|
|
916
1494
|
|
|
917
1495
|
|
|
918
1496
|
def main() -> int:
|
|
919
1497
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
920
|
-
parser.add_argument(
|
|
1498
|
+
parser.add_argument(
|
|
1499
|
+
"provider",
|
|
1500
|
+
nargs="?",
|
|
1501
|
+
choices=("seedance", "gpt-image-2", "minimax-music", "minimax-h3"),
|
|
1502
|
+
)
|
|
921
1503
|
parser.add_argument("--selftest", action="store_true")
|
|
922
1504
|
args = parser.parse_args()
|
|
923
1505
|
if args.selftest:
|
|
@@ -929,7 +1511,12 @@ def main() -> int:
|
|
|
929
1511
|
job = json.load(sys.stdin.buffer)
|
|
930
1512
|
if not isinstance(job, Mapping):
|
|
931
1513
|
raise ValueError("adapter input must be an object")
|
|
932
|
-
runners = {
|
|
1514
|
+
runners = {
|
|
1515
|
+
"seedance": _run_seedance,
|
|
1516
|
+
"gpt-image-2": _run_openai,
|
|
1517
|
+
"minimax-music": _run_minimax,
|
|
1518
|
+
"minimax-h3": _run_minimax_video,
|
|
1519
|
+
}
|
|
933
1520
|
path, provider_job_id = runners[args.provider](job)
|
|
934
1521
|
response: dict[str, Any] = {
|
|
935
1522
|
"outputs": [{"target": job["outputs"][0], "source": str(path)}]
|