@oh-story/dsh 0.1.4 → 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 +56 -12
- package/lib/client.js +2692 -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
|
@@ -13,6 +13,7 @@ import argparse
|
|
|
13
13
|
import contextlib
|
|
14
14
|
import hashlib
|
|
15
15
|
import json
|
|
16
|
+
import math
|
|
16
17
|
import os
|
|
17
18
|
import re
|
|
18
19
|
import shutil
|
|
@@ -20,7 +21,7 @@ import stat
|
|
|
20
21
|
import sys
|
|
21
22
|
import unicodedata
|
|
22
23
|
import uuid
|
|
23
|
-
from collections.abc import Callable, Iterable, Mapping
|
|
24
|
+
from collections.abc import Callable, Iterable, Mapping, Sequence
|
|
24
25
|
from datetime import datetime, timezone
|
|
25
26
|
from pathlib import Path, PurePosixPath
|
|
26
27
|
from typing import Any, Iterator
|
|
@@ -40,6 +41,15 @@ STATE_SCHEMA = "2.0"
|
|
|
40
41
|
DEFAULT_PROMPT_LANGUAGE = "en"
|
|
41
42
|
LANGUAGE_TAG_RE = re.compile(r"[A-Za-z]{2,8}(?:-[A-Za-z0-9]{1,8})*")
|
|
42
43
|
DELIVERY_SUFFIXES = {".md", ".json", ".jsonl"}
|
|
44
|
+
CREATOR_DOCUMENTS = (
|
|
45
|
+
"剧本.md",
|
|
46
|
+
"视觉设定.md",
|
|
47
|
+
"分镜.md",
|
|
48
|
+
"图片提示词.md",
|
|
49
|
+
"视频提示词.md",
|
|
50
|
+
)
|
|
51
|
+
EXPORT_MEDIA_DIRECTORY = "制作成果"
|
|
52
|
+
EXPORT_SCHEMA = "1.0"
|
|
43
53
|
EPISODE_ID_RE = re.compile(r"EP(?:[0-9]{3}|[1-9][0-9]{3,})")
|
|
44
54
|
WINDOWS_FORBIDDEN_PATH_CHARACTERS = frozenset('<>:"|?*')
|
|
45
55
|
WINDOWS_RESERVED_PATH_STEMS = frozenset(
|
|
@@ -193,12 +203,56 @@ def project_languages(project: Mapping[str, Any]) -> dict[str, str]:
|
|
|
193
203
|
if isinstance(format_block, Mapping)
|
|
194
204
|
else None
|
|
195
205
|
)
|
|
206
|
+
authority = project.get("creator_authority")
|
|
207
|
+
production_profile = (
|
|
208
|
+
authority.get("production_profile")
|
|
209
|
+
if isinstance(authority, Mapping)
|
|
210
|
+
else None
|
|
211
|
+
)
|
|
212
|
+
choices = (
|
|
213
|
+
production_profile.get("choices")
|
|
214
|
+
if isinstance(production_profile, Mapping)
|
|
215
|
+
else None
|
|
216
|
+
)
|
|
217
|
+
video_prompt_language = (
|
|
218
|
+
choices.get("video_prompt_language") if isinstance(choices, Mapping) else None
|
|
219
|
+
)
|
|
196
220
|
return {
|
|
197
221
|
"language": str(project.get("language") or "zh-CN"),
|
|
198
222
|
"prompt_language": str(prompt_language or DEFAULT_PROMPT_LANGUAGE),
|
|
223
|
+
"video_prompt_language": str(
|
|
224
|
+
video_prompt_language or prompt_language or DEFAULT_PROMPT_LANGUAGE
|
|
225
|
+
),
|
|
199
226
|
}
|
|
200
227
|
|
|
201
228
|
|
|
229
|
+
def project_video_model_profile(project: Mapping[str, Any]) -> dict[str, Any]:
|
|
230
|
+
"""Expose the accepted prompt-facing model choices without guessing defaults."""
|
|
231
|
+
authority = project.get("creator_authority")
|
|
232
|
+
production_profile = (
|
|
233
|
+
authority.get("production_profile")
|
|
234
|
+
if isinstance(authority, Mapping)
|
|
235
|
+
else None
|
|
236
|
+
)
|
|
237
|
+
if (
|
|
238
|
+
not isinstance(production_profile, Mapping)
|
|
239
|
+
or production_profile.get("status") != "accepted"
|
|
240
|
+
):
|
|
241
|
+
return {}
|
|
242
|
+
choices = production_profile.get("choices")
|
|
243
|
+
if not isinstance(choices, Mapping):
|
|
244
|
+
return {}
|
|
245
|
+
fields = (
|
|
246
|
+
"target_video_model",
|
|
247
|
+
"video_prompt_dialect",
|
|
248
|
+
"video_prompt_language",
|
|
249
|
+
"native_duration_seconds",
|
|
250
|
+
"supported_generation_modes",
|
|
251
|
+
"audio_generation",
|
|
252
|
+
)
|
|
253
|
+
return {field: choices[field] for field in fields if field in choices}
|
|
254
|
+
|
|
255
|
+
|
|
202
256
|
def initialize_project(
|
|
203
257
|
path: Path,
|
|
204
258
|
*,
|
|
@@ -206,10 +260,25 @@ def initialize_project(
|
|
|
206
260
|
language: str,
|
|
207
261
|
aspect_ratio: str,
|
|
208
262
|
prompt_language: str = DEFAULT_PROMPT_LANGUAGE,
|
|
263
|
+
episode_count: int | None = None,
|
|
264
|
+
target_seconds_per_episode: float | None = None,
|
|
209
265
|
suite_root: Path | None = None,
|
|
210
266
|
) -> dict[str, Any]:
|
|
211
267
|
language = normalize_language_tag(language, field="language")
|
|
212
268
|
prompt_language = normalize_language_tag(prompt_language, field="prompt_language")
|
|
269
|
+
if episode_count is not None and (
|
|
270
|
+
isinstance(episode_count, bool)
|
|
271
|
+
or not isinstance(episode_count, int)
|
|
272
|
+
or episode_count <= 0
|
|
273
|
+
):
|
|
274
|
+
raise ValueError("episode_count must be a positive integer")
|
|
275
|
+
if target_seconds_per_episode is not None and (
|
|
276
|
+
isinstance(target_seconds_per_episode, bool)
|
|
277
|
+
or not isinstance(target_seconds_per_episode, (int, float))
|
|
278
|
+
or not math.isfinite(target_seconds_per_episode)
|
|
279
|
+
or target_seconds_per_episode <= 0
|
|
280
|
+
):
|
|
281
|
+
raise ValueError("target_seconds_per_episode must be a positive number of seconds")
|
|
213
282
|
root = path.expanduser().resolve()
|
|
214
283
|
project_path = root / PROJECT_FILE
|
|
215
284
|
if project_path.exists():
|
|
@@ -233,6 +302,10 @@ def initialize_project(
|
|
|
233
302
|
)
|
|
234
303
|
project["format"]["aspect_ratio"] = aspect_ratio
|
|
235
304
|
project["format"]["prompt_language"] = prompt_language
|
|
305
|
+
if episode_count is not None:
|
|
306
|
+
project["format"]["episode_count"] = episode_count
|
|
307
|
+
if target_seconds_per_episode is not None:
|
|
308
|
+
project["format"]["target_seconds_per_episode"] = target_seconds_per_episode
|
|
236
309
|
state = {
|
|
237
310
|
"schema_version": STATE_SCHEMA,
|
|
238
311
|
"project_id": project["project_id"],
|
|
@@ -620,13 +693,26 @@ def _project_path(root: Path, relative: str, *, create_parent: bool = False) ->
|
|
|
620
693
|
current = root
|
|
621
694
|
for part in PurePosixPath(relative).parts[:-1]:
|
|
622
695
|
current /= part
|
|
623
|
-
|
|
696
|
+
# ``is_symlink`` misses the reparse points that are not name
|
|
697
|
+
# surrogates, and a Windows creator can make those without any extra
|
|
698
|
+
# privilege. Ask about the attribute directly instead.
|
|
699
|
+
try:
|
|
700
|
+
details = os.lstat(current)
|
|
701
|
+
except FileNotFoundError:
|
|
702
|
+
details = None
|
|
703
|
+
if details is not None and _is_link_or_reparse(details):
|
|
624
704
|
raise ProjectConflictError(f"project parent cannot be a symlink: {part}")
|
|
625
|
-
if
|
|
705
|
+
if details is not None and not stat.S_ISDIR(details.st_mode):
|
|
626
706
|
raise ProjectConflictError(f"project parent is not a directory: {part}")
|
|
627
|
-
if create_parent and
|
|
707
|
+
if create_parent and details is None:
|
|
628
708
|
current.mkdir()
|
|
629
|
-
|
|
709
|
+
try:
|
|
710
|
+
target_details = os.lstat(target)
|
|
711
|
+
except (FileNotFoundError, NotADirectoryError):
|
|
712
|
+
target_details = None
|
|
713
|
+
if target_details is not None and (
|
|
714
|
+
_is_link_or_reparse(target_details) or not stat.S_ISREG(target_details.st_mode)
|
|
715
|
+
):
|
|
630
716
|
raise ProjectConflictError(f"project target is not a regular file: {relative}")
|
|
631
717
|
if not target.parent.resolve().is_relative_to(root):
|
|
632
718
|
raise ValueError(f"path escapes project root: {relative}")
|
|
@@ -819,11 +905,14 @@ def _build_status(
|
|
|
819
905
|
if isinstance(output, str) and output:
|
|
820
906
|
ownership[output] = owner
|
|
821
907
|
languages = project_languages(project)
|
|
908
|
+
video_model_profile = project_video_model_profile(project)
|
|
822
909
|
return {
|
|
823
910
|
"project_id": project.get("project_id"),
|
|
824
911
|
"title": project.get("title"),
|
|
825
912
|
"language": languages["language"],
|
|
826
913
|
"prompt_language": languages["prompt_language"],
|
|
914
|
+
"video_prompt_language": languages["video_prompt_language"],
|
|
915
|
+
"video_model_profile": video_model_profile,
|
|
827
916
|
"project_root": project_root,
|
|
828
917
|
"last_action": state.get("last_action"),
|
|
829
918
|
"layout": dict(layout),
|
|
@@ -1007,6 +1096,113 @@ def project_path_lifecycle_at(
|
|
|
1007
1096
|
return None
|
|
1008
1097
|
|
|
1009
1098
|
|
|
1099
|
+
# The three functions below are the path-based half of the dashboard contract.
|
|
1100
|
+
# Windows has no ``openat``, so the dashboard pins a project root by verified
|
|
1101
|
+
# path there instead of by directory descriptor and calls these. They compose
|
|
1102
|
+
# the same rules the descriptor twins do -- ``_build_status``,
|
|
1103
|
+
# ``_artifact_state_from``, ``_normalize_state`` -- so the two halves cannot
|
|
1104
|
+
# drift on what a status or a lifecycle state means; they differ only in how a
|
|
1105
|
+
# file is reached.
|
|
1106
|
+
|
|
1107
|
+
|
|
1108
|
+
def _read_regular(root: Path, relative: str) -> bytes:
|
|
1109
|
+
"""Read one project file, refusing every link and reparse point on the way.
|
|
1110
|
+
|
|
1111
|
+
``os.lstat`` never follows, so a component swapped for a symlink or a
|
|
1112
|
+
junction is rejected rather than traversed. This is the Windows stand-in
|
|
1113
|
+
for opening each component with ``O_NOFOLLOW``.
|
|
1114
|
+
"""
|
|
1115
|
+
|
|
1116
|
+
pure = PurePosixPath(relative)
|
|
1117
|
+
current = root
|
|
1118
|
+
for part in pure.parts[:-1]:
|
|
1119
|
+
current = current / part
|
|
1120
|
+
details = os.lstat(current)
|
|
1121
|
+
if _is_link_or_reparse(details) or not stat.S_ISDIR(details.st_mode):
|
|
1122
|
+
raise ProjectConflictError(f"project parent is unsafe: {part}")
|
|
1123
|
+
target = current / pure.name
|
|
1124
|
+
details = os.lstat(target)
|
|
1125
|
+
if _is_link_or_reparse(details) or not stat.S_ISREG(details.st_mode):
|
|
1126
|
+
raise ProjectConflictError(f"project file is unsafe: {relative}")
|
|
1127
|
+
with open(target, "rb") as handle:
|
|
1128
|
+
return handle.read()
|
|
1129
|
+
|
|
1130
|
+
|
|
1131
|
+
def project_status_from_root(root: Path, *, project_root: str) -> dict[str, Any]:
|
|
1132
|
+
"""Report status for an already-pinned project root.
|
|
1133
|
+
|
|
1134
|
+
``project_status`` locates the project first and requires recorded state;
|
|
1135
|
+
this twin takes the root the caller pinned and tolerates a project that has
|
|
1136
|
+
never been tracked, matching ``project_status_at``.
|
|
1137
|
+
"""
|
|
1138
|
+
|
|
1139
|
+
project = json.loads(_read_regular(root, PROJECT_FILE).decode("utf-8"))
|
|
1140
|
+
try:
|
|
1141
|
+
raw_state = json.loads(
|
|
1142
|
+
_read_regular(root, STATE_FILE.as_posix()).decode("utf-8")
|
|
1143
|
+
)
|
|
1144
|
+
except FileNotFoundError:
|
|
1145
|
+
raw_state = {
|
|
1146
|
+
"schema_version": STATE_SCHEMA,
|
|
1147
|
+
"project_id": project.get("project_id"),
|
|
1148
|
+
"project_layout_mode": "auto",
|
|
1149
|
+
"last_action": "untracked",
|
|
1150
|
+
"artifacts": {},
|
|
1151
|
+
}
|
|
1152
|
+
if not isinstance(project, dict) or not isinstance(raw_state, dict):
|
|
1153
|
+
raise ValueError("project files must contain objects")
|
|
1154
|
+
state = _normalize_state(raw_state)
|
|
1155
|
+
return _build_status(
|
|
1156
|
+
project=project,
|
|
1157
|
+
state=state,
|
|
1158
|
+
layout=_project_layout_from_root(root, state),
|
|
1159
|
+
project_root=project_root,
|
|
1160
|
+
artifact_state=lambda record: _artifact_state(root, record),
|
|
1161
|
+
)
|
|
1162
|
+
|
|
1163
|
+
|
|
1164
|
+
def project_path_lifecycle(root: Path, relative: str) -> dict[str, str] | None:
|
|
1165
|
+
"""Return the one creator-facing state for a tracked path."""
|
|
1166
|
+
|
|
1167
|
+
normalized = _relative_path(relative, allow_operations=True)
|
|
1168
|
+
try:
|
|
1169
|
+
raw_state = json.loads(
|
|
1170
|
+
_read_regular(root, STATE_FILE.as_posix()).decode("utf-8")
|
|
1171
|
+
)
|
|
1172
|
+
except (OSError, ProjectConflictError, UnicodeError, json.JSONDecodeError):
|
|
1173
|
+
return None
|
|
1174
|
+
if not isinstance(raw_state, Mapping):
|
|
1175
|
+
return None
|
|
1176
|
+
state = _normalize_state(raw_state)
|
|
1177
|
+
for record in state["artifacts"].values():
|
|
1178
|
+
if isinstance(record, Mapping) and normalized in record.get("outputs", []):
|
|
1179
|
+
return {"artifact_state": _artifact_state(root, record)}
|
|
1180
|
+
return None
|
|
1181
|
+
|
|
1182
|
+
|
|
1183
|
+
@contextlib.contextmanager
|
|
1184
|
+
def coordinated_project_text_edit(
|
|
1185
|
+
root: Path, relative: str, expected_version: str
|
|
1186
|
+
) -> Iterator[None]:
|
|
1187
|
+
normalized = _relative_path(relative)
|
|
1188
|
+
if not re.fullmatch(r"[0-9a-f]{64}", expected_version):
|
|
1189
|
+
raise ValueError("expected version must be a SHA-256 digest")
|
|
1190
|
+
# The descriptor twin opens the operations directory with O_NOFOLLOW. Match
|
|
1191
|
+
# it: a lock taken through a redirected `.short-drama` would leave two
|
|
1192
|
+
# dashboards each believing they hold the project.
|
|
1193
|
+
try:
|
|
1194
|
+
operations = os.lstat(root / ".short-drama")
|
|
1195
|
+
except FileNotFoundError:
|
|
1196
|
+
operations = None
|
|
1197
|
+
if operations is not None and _is_link_or_reparse(operations):
|
|
1198
|
+
raise OSError("project operations directory is unsafe")
|
|
1199
|
+
with _project_lock(root):
|
|
1200
|
+
current = sha256_bytes(_read_regular(root, normalized))
|
|
1201
|
+
if current != expected_version:
|
|
1202
|
+
raise ProjectConflictError("file changed since it was opened")
|
|
1203
|
+
yield
|
|
1204
|
+
|
|
1205
|
+
|
|
1010
1206
|
def _validate_structured_content(relative: str, content: bytes) -> None:
|
|
1011
1207
|
try:
|
|
1012
1208
|
text = content.decode("utf-8")
|
|
@@ -1668,6 +1864,255 @@ def _manifest_problems(
|
|
|
1668
1864
|
return problems
|
|
1669
1865
|
|
|
1670
1866
|
|
|
1867
|
+
def _same_directory(left: Path, right: Path) -> bool:
|
|
1868
|
+
"""Do these two paths name the same directory on disk?
|
|
1869
|
+
|
|
1870
|
+
String comparison is not enough: a case-insensitive volume and a filesystem
|
|
1871
|
+
that stores a different Unicode normalisation both spell one directory two
|
|
1872
|
+
ways, and either spelling would otherwise slip past a containment check.
|
|
1873
|
+
"""
|
|
1874
|
+
try:
|
|
1875
|
+
return os.path.samefile(left, right)
|
|
1876
|
+
except OSError:
|
|
1877
|
+
return False
|
|
1878
|
+
|
|
1879
|
+
|
|
1880
|
+
def _require_safe_export_destination(destination: Path, root: Path) -> None:
|
|
1881
|
+
"""Refuse any destination that would take the project down with it.
|
|
1882
|
+
|
|
1883
|
+
The export replaces its destination directory wholesale, so a destination
|
|
1884
|
+
that is -- or contains -- the project being exported would delete the
|
|
1885
|
+
source. Ancestors matter as much as descendants here.
|
|
1886
|
+
"""
|
|
1887
|
+
if destination == root or root in destination.parents:
|
|
1888
|
+
raise ValueError("export destination must live outside the project root")
|
|
1889
|
+
for ancestor in (root, *root.parents):
|
|
1890
|
+
if destination == ancestor or _same_directory(destination, ancestor):
|
|
1891
|
+
raise ValueError(
|
|
1892
|
+
"export destination must not be the project root or any directory containing it"
|
|
1893
|
+
)
|
|
1894
|
+
|
|
1895
|
+
|
|
1896
|
+
def _require_replaceable_export(destination: Path) -> None:
|
|
1897
|
+
"""Only ever overwrite a directory this command itself produced.
|
|
1898
|
+
|
|
1899
|
+
`--overwrite` removes the destination tree. Without this check a mistyped
|
|
1900
|
+
path turns an ordinary directory into a deletion, and nothing else in this
|
|
1901
|
+
tool can erase a directory outside the project.
|
|
1902
|
+
"""
|
|
1903
|
+
if not destination.exists():
|
|
1904
|
+
return
|
|
1905
|
+
details = os.lstat(destination)
|
|
1906
|
+
if _is_link_or_reparse(details) or not stat.S_ISDIR(details.st_mode):
|
|
1907
|
+
raise ProjectConflictError("export destination is not a regular directory")
|
|
1908
|
+
manifest = destination / "manifest.json"
|
|
1909
|
+
try:
|
|
1910
|
+
document = json.loads(manifest.read_text(encoding="utf-8"))
|
|
1911
|
+
except (OSError, ValueError) as exc:
|
|
1912
|
+
raise ValueError(
|
|
1913
|
+
"refusing to overwrite a directory that is not a previous export: "
|
|
1914
|
+
f"{destination}"
|
|
1915
|
+
) from exc
|
|
1916
|
+
if not isinstance(document, Mapping) or document.get("kind") != "creator_export":
|
|
1917
|
+
raise ValueError(
|
|
1918
|
+
"refusing to overwrite a directory that is not a previous export: "
|
|
1919
|
+
f"{destination}"
|
|
1920
|
+
)
|
|
1921
|
+
|
|
1922
|
+
|
|
1923
|
+
def _export_regular_file(source: Path, destination: Path) -> str:
|
|
1924
|
+
"""Copy one regular project file into the export tree and return its digest."""
|
|
1925
|
+
details = os.lstat(source)
|
|
1926
|
+
if _is_link_or_reparse(details) or not stat.S_ISREG(details.st_mode):
|
|
1927
|
+
raise ProjectConflictError(f"export source is not a regular file: {source.name}")
|
|
1928
|
+
if "\n" in source.name or "\r" in source.name:
|
|
1929
|
+
# checksums.sha256 is one record per line; such a name cannot be written
|
|
1930
|
+
# in a form any verifier could read back.
|
|
1931
|
+
raise ProjectConflictError(
|
|
1932
|
+
"export source name contains a line break and cannot be checksummed"
|
|
1933
|
+
)
|
|
1934
|
+
destination.parent.mkdir(parents=True, exist_ok=True)
|
|
1935
|
+
shutil.copyfile(source, destination)
|
|
1936
|
+
return sha256_file(destination)
|
|
1937
|
+
|
|
1938
|
+
|
|
1939
|
+
def _export_episode(
|
|
1940
|
+
episode_directory: Path, target: Path, *, include_media: bool
|
|
1941
|
+
) -> tuple[list[dict[str, str]], list[str]]:
|
|
1942
|
+
files: list[dict[str, str]] = []
|
|
1943
|
+
missing: list[str] = []
|
|
1944
|
+
for name in CREATOR_DOCUMENTS:
|
|
1945
|
+
source = episode_directory / name
|
|
1946
|
+
if not source.exists():
|
|
1947
|
+
missing.append(name)
|
|
1948
|
+
continue
|
|
1949
|
+
digest = _export_regular_file(source, target / name)
|
|
1950
|
+
files.append({"path": name, "sha256": digest})
|
|
1951
|
+
media_root = episode_directory / EXPORT_MEDIA_DIRECTORY
|
|
1952
|
+
if include_media and media_root.exists():
|
|
1953
|
+
media_details = os.lstat(media_root)
|
|
1954
|
+
if _is_link_or_reparse(media_details) or not stat.S_ISDIR(media_details.st_mode):
|
|
1955
|
+
# Silently exporting no media reads as "this episode has none".
|
|
1956
|
+
raise ProjectConflictError(
|
|
1957
|
+
f"{EXPORT_MEDIA_DIRECTORY} is not a regular directory: {episode_directory.name}"
|
|
1958
|
+
)
|
|
1959
|
+
for source in sorted(media_root.rglob("*")):
|
|
1960
|
+
if source.is_dir() and not source.is_symlink():
|
|
1961
|
+
continue
|
|
1962
|
+
relative = source.relative_to(episode_directory).as_posix()
|
|
1963
|
+
digest = _export_regular_file(source, target / relative)
|
|
1964
|
+
files.append({"path": relative, "sha256": digest})
|
|
1965
|
+
return files, missing
|
|
1966
|
+
|
|
1967
|
+
|
|
1968
|
+
def _export_exclusions(root: Path, episodes_name: str) -> list[str]:
|
|
1969
|
+
"""Name every top-level project directory this export did not copy.
|
|
1970
|
+
|
|
1971
|
+
A fixed list lies twice over: it names roots a legacy-layout project does
|
|
1972
|
+
not have, and it stays silent about a published 设定集/ that the handover is
|
|
1973
|
+
missing.
|
|
1974
|
+
"""
|
|
1975
|
+
excluded: list[str] = []
|
|
1976
|
+
for entry in sorted(root.iterdir(), key=lambda item: item.name):
|
|
1977
|
+
if entry.name == episodes_name or entry.name == PROJECT_FILE:
|
|
1978
|
+
continue
|
|
1979
|
+
if entry.is_dir() or entry.is_symlink():
|
|
1980
|
+
excluded.append(entry.name)
|
|
1981
|
+
return excluded
|
|
1982
|
+
|
|
1983
|
+
|
|
1984
|
+
def _is_portable_windows_path(relative: str) -> bool:
|
|
1985
|
+
for part in PurePosixPath(relative).parts:
|
|
1986
|
+
stem = part.split(".")[0].casefold()
|
|
1987
|
+
if stem in WINDOWS_RESERVED_PATH_STEMS:
|
|
1988
|
+
return False
|
|
1989
|
+
if set(part) & WINDOWS_FORBIDDEN_PATH_CHARACTERS:
|
|
1990
|
+
return False
|
|
1991
|
+
if part.endswith((" ", ".")):
|
|
1992
|
+
return False
|
|
1993
|
+
return True
|
|
1994
|
+
|
|
1995
|
+
|
|
1996
|
+
def build_creator_export(
|
|
1997
|
+
root: Path,
|
|
1998
|
+
*,
|
|
1999
|
+
out: Path,
|
|
2000
|
+
episodes: Sequence[str] | None = None,
|
|
2001
|
+
include_media: bool = True,
|
|
2002
|
+
overwrite: bool = False,
|
|
2003
|
+
) -> dict[str, Any]:
|
|
2004
|
+
"""Copy the current creator documents and produced media into a handover tree.
|
|
2005
|
+
|
|
2006
|
+
This is a snapshot of what the project holds right now. It deliberately
|
|
2007
|
+
asserts nothing about review or creator acceptance; `package` remains the
|
|
2008
|
+
only command that speaks for an approved delivery.
|
|
2009
|
+
"""
|
|
2010
|
+
root = find_project(root)
|
|
2011
|
+
destination = out.expanduser().resolve()
|
|
2012
|
+
_require_safe_export_destination(destination, root)
|
|
2013
|
+
layout = _project_layout_from_root(root)
|
|
2014
|
+
if layout["mode"] == "mixed":
|
|
2015
|
+
raise PackageBlockedError("mixed project layouts cannot be exported")
|
|
2016
|
+
episodes_root = root / str(layout["roots"]["episodes"])
|
|
2017
|
+
available = sorted(
|
|
2018
|
+
entry.name
|
|
2019
|
+
for entry in episodes_root.iterdir()
|
|
2020
|
+
if entry.is_dir() and not entry.is_symlink()
|
|
2021
|
+
if EPISODE_ID_RE.fullmatch(entry.name)
|
|
2022
|
+
) if episodes_root.is_dir() and not episodes_root.is_symlink() else []
|
|
2023
|
+
if episodes:
|
|
2024
|
+
selected = []
|
|
2025
|
+
for episode in episodes:
|
|
2026
|
+
if EPISODE_ID_RE.fullmatch(episode) is None:
|
|
2027
|
+
raise ValueError("episode must use an EP001-style identifier")
|
|
2028
|
+
if episode not in available:
|
|
2029
|
+
raise FileNotFoundError(f"episode directory not found: {episode}")
|
|
2030
|
+
if episode not in selected:
|
|
2031
|
+
selected.append(episode)
|
|
2032
|
+
else:
|
|
2033
|
+
selected = available
|
|
2034
|
+
if not selected:
|
|
2035
|
+
raise FileNotFoundError("no episode directory to export")
|
|
2036
|
+
if destination.exists():
|
|
2037
|
+
if not overwrite:
|
|
2038
|
+
raise FileExistsError(f"export destination already exists: {destination}")
|
|
2039
|
+
_require_replaceable_export(destination)
|
|
2040
|
+
|
|
2041
|
+
project = json.loads((root / PROJECT_FILE).read_text(encoding="utf-8"))
|
|
2042
|
+
destination.parent.mkdir(parents=True, exist_ok=True)
|
|
2043
|
+
temporary = destination.parent / f".{destination.name}.{uuid.uuid4().hex}.tmp"
|
|
2044
|
+
temporary.mkdir()
|
|
2045
|
+
try:
|
|
2046
|
+
exported: list[dict[str, Any]] = []
|
|
2047
|
+
members: list[dict[str, str]] = []
|
|
2048
|
+
episodes_name = str(layout["roots"]["episodes"])
|
|
2049
|
+
for episode in selected:
|
|
2050
|
+
target = temporary / episodes_name / episode
|
|
2051
|
+
target.mkdir(parents=True)
|
|
2052
|
+
files, missing = _export_episode(
|
|
2053
|
+
episodes_root / episode, target, include_media=include_media
|
|
2054
|
+
)
|
|
2055
|
+
for entry in files:
|
|
2056
|
+
members.append(
|
|
2057
|
+
{
|
|
2058
|
+
"path": f"{episodes_name}/{episode}/{entry['path']}",
|
|
2059
|
+
"sha256": entry["sha256"],
|
|
2060
|
+
}
|
|
2061
|
+
)
|
|
2062
|
+
exported.append(
|
|
2063
|
+
{"episode": episode, "files": files, "missing_documents": missing}
|
|
2064
|
+
)
|
|
2065
|
+
config_digest = _export_regular_file(
|
|
2066
|
+
root / PROJECT_FILE, temporary / PROJECT_FILE
|
|
2067
|
+
)
|
|
2068
|
+
members.append({"path": PROJECT_FILE, "sha256": config_digest})
|
|
2069
|
+
manifest = {
|
|
2070
|
+
"schema_version": EXPORT_SCHEMA,
|
|
2071
|
+
"kind": "creator_export",
|
|
2072
|
+
"asserts_approval": False,
|
|
2073
|
+
"project_id": project.get("project_id"),
|
|
2074
|
+
"title": project.get("title"),
|
|
2075
|
+
"created_at": utc_now(),
|
|
2076
|
+
"episodes": exported,
|
|
2077
|
+
"selection": {
|
|
2078
|
+
"episodes": "all" if not episodes else list(selected),
|
|
2079
|
+
"available_episodes": list(available),
|
|
2080
|
+
"include_media": bool(include_media),
|
|
2081
|
+
},
|
|
2082
|
+
"excluded": _export_exclusions(root, episodes_name),
|
|
2083
|
+
"windows_unsafe_paths": sorted(
|
|
2084
|
+
entry["path"]
|
|
2085
|
+
for entry in members
|
|
2086
|
+
if not _is_portable_windows_path(entry["path"])
|
|
2087
|
+
),
|
|
2088
|
+
}
|
|
2089
|
+
atomic_json(temporary / "manifest.json", manifest)
|
|
2090
|
+
checksum_members = ["manifest.json", *(entry["path"] for entry in members)]
|
|
2091
|
+
checksums = "".join(
|
|
2092
|
+
f"{sha256_file(temporary / relative)} {relative}\n"
|
|
2093
|
+
for relative in sorted(checksum_members)
|
|
2094
|
+
)
|
|
2095
|
+
_atomic_bytes(temporary / "checksums.sha256", checksums.encode("utf-8"))
|
|
2096
|
+
_replace_directory(temporary, destination)
|
|
2097
|
+
finally:
|
|
2098
|
+
if temporary.exists():
|
|
2099
|
+
shutil.rmtree(temporary)
|
|
2100
|
+
return {
|
|
2101
|
+
"kind": "creator_export",
|
|
2102
|
+
"out": str(destination),
|
|
2103
|
+
"episodes": [entry["episode"] for entry in exported],
|
|
2104
|
+
"files": [entry["path"] for entry in members],
|
|
2105
|
+
"excluded": manifest["excluded"],
|
|
2106
|
+
"include_media": bool(include_media),
|
|
2107
|
+
"missing_documents": {
|
|
2108
|
+
entry["episode"]: entry["missing_documents"]
|
|
2109
|
+
for entry in exported
|
|
2110
|
+
if entry["missing_documents"]
|
|
2111
|
+
},
|
|
2112
|
+
"windows_unsafe_paths": manifest["windows_unsafe_paths"],
|
|
2113
|
+
}
|
|
2114
|
+
|
|
2115
|
+
|
|
1671
2116
|
def verify_delivery_package(root: Path, *, episode: str) -> dict[str, Any]:
|
|
1672
2117
|
if EPISODE_ID_RE.fullmatch(episode) is None:
|
|
1673
2118
|
raise ValueError("episode must use an EP001-style identifier")
|
|
@@ -1825,6 +2270,8 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
1825
2270
|
init.add_argument("--language", default="zh-CN")
|
|
1826
2271
|
init.add_argument("--prompt-language", default=DEFAULT_PROMPT_LANGUAGE)
|
|
1827
2272
|
init.add_argument("--aspect-ratio", default="9:16")
|
|
2273
|
+
init.add_argument("--episode-count", type=int)
|
|
2274
|
+
init.add_argument("--target-seconds", type=float, dest="target_seconds_per_episode")
|
|
1828
2275
|
|
|
1829
2276
|
status = commands.add_parser("status", help="Print a creator-safe project summary.")
|
|
1830
2277
|
status.add_argument("path", nargs="?", default=".")
|
|
@@ -1891,6 +2338,29 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
1891
2338
|
verify = commands.add_parser("verify", help="Re-check a delivered package checksums.")
|
|
1892
2339
|
verify.add_argument("path")
|
|
1893
2340
|
verify.add_argument("--episode", required=True)
|
|
2341
|
+
|
|
2342
|
+
export = commands.add_parser(
|
|
2343
|
+
"export",
|
|
2344
|
+
help="Copy the current creator documents and produced media for handover.",
|
|
2345
|
+
)
|
|
2346
|
+
export.add_argument("path")
|
|
2347
|
+
export.add_argument("--out", required=True, type=Path)
|
|
2348
|
+
export.add_argument(
|
|
2349
|
+
"--episode",
|
|
2350
|
+
action="append",
|
|
2351
|
+
dest="episodes",
|
|
2352
|
+
help="Export only this episode; repeat for several. Default: every episode.",
|
|
2353
|
+
)
|
|
2354
|
+
export.add_argument(
|
|
2355
|
+
"--no-media",
|
|
2356
|
+
action="store_true",
|
|
2357
|
+
help=f"Skip 剧集/<EP>/{EXPORT_MEDIA_DIRECTORY}/ and export text only.",
|
|
2358
|
+
)
|
|
2359
|
+
export.add_argument(
|
|
2360
|
+
"--overwrite",
|
|
2361
|
+
action="store_true",
|
|
2362
|
+
help="Replace an existing export directory at --out.",
|
|
2363
|
+
)
|
|
1894
2364
|
return parser
|
|
1895
2365
|
|
|
1896
2366
|
|
|
@@ -1904,6 +2374,8 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
1904
2374
|
language=args.language,
|
|
1905
2375
|
prompt_language=args.prompt_language,
|
|
1906
2376
|
aspect_ratio=args.aspect_ratio,
|
|
2377
|
+
episode_count=args.episode_count,
|
|
2378
|
+
target_seconds_per_episode=args.target_seconds_per_episode,
|
|
1907
2379
|
)
|
|
1908
2380
|
elif args.command == "status":
|
|
1909
2381
|
result = project_status(Path(args.path))
|
|
@@ -1947,6 +2419,14 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
1947
2419
|
includes=args.includes,
|
|
1948
2420
|
omissions=_parse_omissions(args.omissions),
|
|
1949
2421
|
)
|
|
2422
|
+
elif args.command == "export":
|
|
2423
|
+
result = build_creator_export(
|
|
2424
|
+
Path(args.path),
|
|
2425
|
+
out=args.out,
|
|
2426
|
+
episodes=args.episodes,
|
|
2427
|
+
include_media=not args.no_media,
|
|
2428
|
+
overwrite=args.overwrite,
|
|
2429
|
+
)
|
|
1950
2430
|
else:
|
|
1951
2431
|
result = verify_delivery_package(Path(args.path), episode=args.episode)
|
|
1952
2432
|
if result["status"] != "verified":
|
|
@@ -29,9 +29,11 @@ license: MIT
|
|
|
29
29
|
1. 只读当前范围的剧本和已决定的视觉方向。
|
|
30
30
|
2. 逐场找实体、可见描述、状态变化和含混指代,先不创建资产。
|
|
31
31
|
3. 对每项决定新建、复用、增加变体或标记未决;含混指代不猜。
|
|
32
|
-
4. 写最小可识别设定:统一使用
|
|
32
|
+
4. 写最小可识别设定:统一使用 `识别锚点`、本集状态和变化原因;会持续限制后续动作的身体—物件—空间关系写进有生效范围的状态,不当成一次姿势略过。
|
|
33
33
|
5. 检查造型/伤势、持物/所有权、道具状态、地点时段/天气/光态与跨集 outgoing。
|
|
34
|
-
6.
|
|
34
|
+
6. 给跨镜出现且观众看得出不一致的可见事实写一条连续性锁,其余保持识别锚点即可。
|
|
35
|
+
7. 项目提示词语言不是中文时,给每个人物条目写一行 `画面代称:<正文里的拼写>`;正文从不点名它就写 `画面代称:无`。地点和道具按需。
|
|
36
|
+
8. 整集请求自动处理完整集,末端一次回报关键身份决定和未决指代。
|
|
35
37
|
|
|
36
38
|
## 内容要求
|
|
37
39
|
|
|
@@ -41,6 +43,12 @@ license: MIT
|
|
|
41
43
|
- 不复制剧情摘要,只保留下游生成、分镜或连续性真正需要的事实。
|
|
42
44
|
- 文字、标识和敏感信息遵守项目政策,不擅自补造品牌或私密信息。
|
|
43
45
|
- 多集状态写清生效与持续范围;镜头内瞬态留给分镜。
|
|
46
|
+
- 已确认且持续限制动作空间的关系(身体依托、固定、负重、占位或与功能物件的接触)不是普通镜头姿势:可独立识别的物件仍建资产,关系写进状态并交给分镜逐镜投影;只有剧本明确改变时才结束。
|
|
47
|
+
- 连续性锁只锁不随剧情改变的可见事实;锁面用提示词语言写成最小名词短语,会变的部分留给状态。
|
|
48
|
+
- 「画面代称」把提示词正文里的名字对回本条目:分镜的机械核对靠它判断关键帧里点名的人是谁。条目自身的中文名称
|
|
49
|
+
始终算一个代称,所以中文正文通常不必写这一行;提示词正文语言不是中文时,人物条目必须给出结论——写出正文使用的
|
|
50
|
+
拼写,或在正文从不点名它时写 `画面代称:无`。同一个名字全集只用一个拼写,大小写也一致(`AST-10` 的同一条要求跨语言同样成立);
|
|
51
|
+
多个写法用 `、` 分隔。名字与常用词同形(`道具「手机」` 撞上「手机店」)时也写 `画面代称:无`,让核对不按名字找它。
|
|
44
52
|
|
|
45
53
|
## 按需知识
|
|
46
54
|
|
|
@@ -51,15 +59,17 @@ license: MIT
|
|
|
51
59
|
- 新身份、复用、变体或未决:[身份与变体](references/identity-vs-variant.md)
|
|
52
60
|
- 人物识别与造型层次:[人物与造型](references/character-and-look.md)
|
|
53
61
|
- 地点地理、视图和固定锚点:[地点与视图](references/location-and-view.md)
|
|
54
|
-
-
|
|
62
|
+
- 功能道具、所有权、状态变化,或画面上的可读文字与标识:[道具与状态](references/prop-and-state.md)
|
|
55
63
|
- 声音参考的身份与授权边界:[声音方向](references/voice-direction.md)
|
|
56
64
|
- 跨场、跨镜、跨集状态:[连续性变化](references/continuity-delta.md)
|
|
65
|
+
- 跨镜服装/道具颜色、材质漂移:[跨镜一致性锁](references/continuity-lock.md)
|
|
57
66
|
- 完成前做一次资产漏项检查:[资产审查清单](references/asset-review-checklist.md)
|
|
58
67
|
|
|
59
68
|
## 完成
|
|
60
69
|
|
|
61
70
|
所有明确出场都已复用、新建、变体化或标为真实未决;需要保持的视觉事实与状态变化已写入唯一
|
|
62
71
|
视觉设定文档,即完成。图片提示词、分镜、审查和生产不自动开始。
|
|
72
|
+
五份创作文档齐备后,可转 `$short-drama` 对跨文档结构做一次机械核对;内容质量仍由创作者审查。
|
|
63
73
|
|
|
64
74
|
## 安装维护
|
|
65
75
|
|