@oh-story/dsh 0.1.3 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +58 -12
- package/lib/client.js +2970 -537
- package/lib/client.js.map +4 -4
- package/lib/drama/manifest.json +218 -178
- package/lib/drama/skills/short-drama/SKILL.md +110 -117
- package/lib/drama/skills/short-drama/references/audience-reveal.md +14 -67
- package/lib/drama/skills/short-drama/references/contract-and-ownership.md +26 -77
- package/lib/drama/skills/short-drama/references/creator-documents.md +204 -0
- package/lib/drama/skills/short-drama/references/creator-workflow.md +73 -63
- package/lib/drama/skills/short-drama/references/knowhow-index.md +5 -0
- package/lib/drama/skills/short-drama/references/look-development.md +2 -3
- package/lib/drama/skills/short-drama/references/pickup-and-alternate.md +14 -48
- package/lib/drama/skills/short-drama/references/production-form-profiles.md +1 -1
- package/lib/drama/skills/short-drama/references/reference-roles.md +32 -135
- package/lib/drama/skills/short-drama/references/runtime-preflight.md +16 -19
- package/lib/drama/skills/short-drama/scripts/creator_markdown_check.py +1002 -0
- package/lib/drama/skills/short-drama/scripts/project_tool.py +485 -5
- package/lib/drama/skills/short-drama-assets/SKILL.md +59 -170
- package/lib/drama/skills/short-drama-assets/references/asset-review-checklist.md +18 -25
- package/lib/drama/skills/short-drama-assets/references/character-and-look.md +13 -15
- package/lib/drama/skills/short-drama-assets/references/continuity-delta.md +11 -14
- package/lib/drama/skills/short-drama-assets/references/continuity-lock.md +116 -0
- package/lib/drama/skills/short-drama-assets/references/identity-vs-variant.md +6 -12
- package/lib/drama/skills/short-drama-assets/references/occurrence-extraction.md +8 -11
- package/lib/drama/skills/short-drama-assets/references/prop-and-state.md +21 -2
- package/lib/drama/skills/short-drama-assets/references/stage-contract.md +12 -105
- package/lib/drama/skills/short-drama-assets/references/voice-direction.md +12 -32
- package/lib/drama/skills/short-drama-assets/scripts/asset_check.py +1 -1
- package/lib/drama/skills/short-drama-image-prompts/SKILL.md +45 -155
- package/lib/drama/skills/short-drama-image-prompts/references/character-and-look.md +12 -13
- package/lib/drama/skills/short-drama-image-prompts/references/common-recipe.md +12 -13
- package/lib/drama/skills/short-drama-image-prompts/references/edit-and-revision.md +39 -51
- package/lib/drama/skills/short-drama-image-prompts/references/location-plate.md +3 -4
- package/lib/drama/skills/short-drama-image-prompts/references/look-and-state-variant.md +15 -14
- package/lib/drama/skills/short-drama-image-prompts/references/lookdev-frame.md +11 -15
- package/lib/drama/skills/short-drama-image-prompts/references/production-sheet-recipes.md +1 -1
- package/lib/drama/skills/short-drama-image-prompts/references/prop-plate.md +4 -7
- package/lib/drama/skills/short-drama-image-prompts/references/review-and-fixtures.md +23 -23
- package/lib/drama/skills/short-drama-image-prompts/references/stage-contract.md +8 -65
- package/lib/drama/skills/short-drama-image-prompts/scripts/image_prompt_check.py +8 -8
- package/lib/drama/skills/short-drama-image-prompts/scripts/selftest.py +4 -2
- package/lib/drama/skills/short-drama-produce/SKILL.md +67 -15
- package/lib/drama/skills/short-drama-produce/references/adapter-contract.md +65 -8
- package/lib/drama/skills/short-drama-produce/references/providers/minimax-h3-video.md +85 -0
- package/lib/drama/skills/short-drama-produce/references/providers/seedance.md +27 -10
- package/lib/drama/skills/short-drama-produce/scripts/production_tool.py +362 -3
- package/lib/drama/skills/short-drama-produce/scripts/provider_adapters.py +627 -40
- package/lib/drama/skills/short-drama-produce/scripts/selftest.py +5 -4
- package/lib/drama/skills/short-drama-review/SKILL.md +61 -123
- package/lib/drama/skills/short-drama-review/references/anti-template-repair.md +10 -15
- package/lib/drama/skills/short-drama-review/references/production-quality-gates.md +15 -16
- package/lib/drama/skills/short-drama-review/references/project-calibration.md +7 -9
- package/lib/drama/skills/short-drama-review/references/review-method.md +8 -9
- package/lib/drama/skills/short-drama-review/references/rubric-assets-prompts.md +9 -13
- package/lib/drama/skills/short-drama-review/references/rubric-story-script.md +104 -4
- package/lib/drama/skills/short-drama-review/references/rubric-visual-motion.md +27 -19
- package/lib/drama/skills/short-drama-review/references/stage-contract.md +4 -63
- package/lib/drama/skills/short-drama-review/scripts/review_check.py +1 -1
- package/lib/drama/skills/short-drama-storyboard/SKILL.md +116 -238
- package/lib/drama/skills/short-drama-storyboard/assets/shot-template.jsonl +1 -1
- package/lib/drama/skills/short-drama-storyboard/references/blocking-playbooks.md +9 -34
- package/lib/drama/skills/short-drama-storyboard/references/comic-keyframe-lexicon.md +156 -0
- package/lib/drama/skills/short-drama-storyboard/references/coverage-audition.md +5 -10
- package/lib/drama/skills/short-drama-storyboard/references/keyframe-craft.md +12 -7
- package/lib/drama/skills/short-drama-storyboard/references/production-shot-grammar.md +19 -4
- package/lib/drama/skills/short-drama-storyboard/references/review-and-fixtures.md +27 -62
- package/lib/drama/skills/short-drama-storyboard/references/scene-visual-plan.md +16 -25
- package/lib/drama/skills/short-drama-storyboard/references/screenplay-to-keyframe-example.md +21 -23
- package/lib/drama/skills/short-drama-storyboard/references/shot-craft.md +8 -10
- package/lib/drama/skills/short-drama-storyboard/references/shot-revision-identity.md +9 -13
- package/lib/drama/skills/short-drama-storyboard/references/stage-contract.md +16 -71
- package/lib/drama/skills/short-drama-storyboard/scripts/storyboard_check.py +1 -1
- package/lib/drama/skills/short-drama-video-prompts/SKILL.md +123 -230
- package/lib/drama/skills/short-drama-video-prompts/assets/motion-spec.jsonl.md +6 -1
- package/lib/drama/skills/short-drama-video-prompts/assets/video-prompts.md +17 -1
- package/lib/drama/skills/short-drama-video-prompts/references/camera-audio-continuity.md +10 -8
- package/lib/drama/skills/short-drama-video-prompts/references/delivery-profile.md +24 -6
- package/lib/drama/skills/short-drama-video-prompts/references/generability.md +2 -2
- package/lib/drama/skills/short-drama-video-prompts/references/minimax-h3.md +154 -0
- package/lib/drama/skills/short-drama-video-prompts/references/motion-recipe.md +35 -27
- package/lib/drama/skills/short-drama-video-prompts/references/performance-action-timing.md +8 -11
- package/lib/drama/skills/short-drama-video-prompts/references/production-prompt-grammar.md +13 -10
- package/lib/drama/skills/short-drama-video-prompts/references/review-and-fixtures.md +34 -63
- package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.0.md +41 -0
- package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.5.md +66 -0
- package/lib/drama/skills/short-drama-video-prompts/references/stage-contract.md +17 -73
- package/lib/drama/skills/short-drama-video-prompts/references/target-model-profile.md +115 -0
- package/lib/drama/skills/short-drama-video-prompts/scripts/container_check.py +1 -1
- package/lib/drama/skills/short-drama-video-prompts/scripts/music_spec_check.py +1 -1
- package/lib/drama/skills/short-drama-write/SKILL.md +74 -268
- package/lib/drama/skills/short-drama-write/references/dialogue-craft.md +7 -0
- package/lib/drama/skills/short-drama-write/references/scene-handoff-capsule.md +17 -43
- package/lib/drama/skills/short-drama-write/references/screenplay-format.md +15 -15
- package/lib/drama/skills/short-drama-write/references/script-craft.md +208 -21
- package/lib/drama/skills/short-drama-write/references/stage-contract.md +11 -70
- package/lib/drama/skills/short-drama-write/scripts/duration_estimate.py +1 -1
- package/lib/drama/skills/short-drama-write/scripts/screenplay_index.py +2 -2
- package/lib/drama/skills/short-drama-write/scripts/voice_sheet_check.py +1 -1
- package/lib/index.js +971 -46
- package/lib/index.js.map +4 -4
- package/lib/novel-to-game/LICENSE +21 -0
- package/lib/novel-to-game/README.md +180 -0
- package/lib/novel-to-game/README_ZH.md +178 -0
- package/lib/novel-to-game/VERSION +1 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/BUILD_BRIEF.md +217 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/.vercelignore +3 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/RUN.md +130 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/compound_new_guofeng.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/finale/fate_coda.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/finale/meng.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/finale/pan.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/finale/pinger.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/finale/xuee.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/finale/yue.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/inner_court_accord_five.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/inner_court_afterglow_five.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/inner_court_alliance.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/public_day10.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/public_day15.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/public_day5.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/title_new_guofeng.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/joint/pan_pinger.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/joint/pan_xuee.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/joint/yue_meng.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/joint/yue_pan.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/joint/yue_pinger.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/meng/explicit.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/meng/prelude.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/milestone/meng.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/milestone/pan.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/milestone/pinger.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/milestone/xuee.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/milestone/yue.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/pan/explicit.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/pan/prelude.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/pinger/explicit.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/pinger/prelude.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/xuee/explicit.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/xuee/prelude.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/yue/explicit.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/yue/prelude.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/heroine/meng/night.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/heroine/pan/night.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/heroine/pinger/night.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/heroine/xuee/night.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/heroine/yue/night.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/household/li_jiaoer.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/scene/gate_collector.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/css/new-guofeng.css +1105 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/css/style.css +2539 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/index.html +18 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/assets.js +147 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/audio.js +332 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/data.js +7472 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/engine.js +13077 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/main.js +3028 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/text.js +32 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/package.json +10 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/vercel.json +23 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/art/generated-art.json +161 -0
- package/lib/novel-to-game/examples/jin-ping-mei/example.json +12 -0
- package/lib/novel-to-game/examples/jin-ping-mei/qa/README.md +31 -0
- package/lib/novel-to-game/examples/jin-ping-mei/qa/playable-model.json +99 -0
- package/lib/novel-to-game/examples/jin-ping-mei/qa/verification.json +41 -0
- package/lib/novel-to-game/examples/jin-ping-mei/qa/verify_playable_model.mjs +129 -0
- package/lib/novel-to-game/manifest.json +508 -0
- package/lib/novel-to-game/skills/game-art-direction/SKILL.md +73 -0
- package/lib/novel-to-game/skills/game-art-direction/agents/openai.yaml +4 -0
- package/lib/novel-to-game/skills/game-art-direction/references/art-direction-method.md +119 -0
- package/lib/novel-to-game/skills/game-build/SKILL.md +84 -0
- package/lib/novel-to-game/skills/game-build/agents/openai.yaml +4 -0
- package/lib/novel-to-game/skills/game-build/references/build-brief-contract.md +109 -0
- package/lib/novel-to-game/skills/game-build/references/generative-media-pipeline.md +56 -0
- package/lib/novel-to-game/skills/game-build/references/playable-model-contract.md +81 -0
- package/lib/novel-to-game/skills/game-build/references/tts-production-contract.md +37 -0
- package/lib/novel-to-game/skills/game-concept/SKILL.md +72 -0
- package/lib/novel-to-game/skills/game-concept/agents/openai.yaml +4 -0
- package/lib/novel-to-game/skills/game-concept/references/concept-method.md +94 -0
- package/lib/novel-to-game/skills/game-qa/SKILL.md +50 -0
- package/lib/novel-to-game/skills/game-qa/agents/openai.yaml +4 -0
- package/lib/novel-to-game/skills/game-qa/references/qa-contract.md +66 -0
- package/lib/novel-to-game/skills/game-qa/references/test-design-method.md +45 -0
- package/lib/novel-to-game/skills/game-world-design/SKILL.md +63 -0
- package/lib/novel-to-game/skills/game-world-design/agents/openai.yaml +4 -0
- package/lib/novel-to-game/skills/game-world-design/references/dialogue-design-method.md +59 -0
- package/lib/novel-to-game/skills/game-world-design/references/game-writing-craft.md +53 -0
- package/lib/novel-to-game/skills/game-world-design/references/narrative-design-method.md +84 -0
- package/lib/novel-to-game/skills/game-world-design/references/numeric-design-method.md +56 -0
- package/lib/novel-to-game/skills/game-world-design/references/world-design-method.md +124 -0
- package/lib/novel-to-game/skills/novel-game-analyze/SKILL.md +77 -0
- package/lib/novel-to-game/skills/novel-game-analyze/agents/openai.yaml +4 -0
- package/lib/novel-to-game/skills/novel-game-analyze/references/gameability-protocol.md +65 -0
- package/lib/novel-to-game/skills/novel-to-game/SKILL.md +71 -0
- package/lib/novel-to-game/skills/novel-to-game/agents/openai.yaml +4 -0
- package/lib/novel-to-game/skills/novel-to-game/references/intake-benchmark-reference.md +42 -0
- package/lib/novel-to-game/skills/novel-to-game/references/intake-method.md +51 -0
- package/lib/novel-to-game/skills/novel-to-game/references/pipeline-contract.md +65 -0
- package/lib/oh-story/manifest.json +419 -309
- package/lib/oh-story/roles/character-designer.md +4 -4
- package/lib/oh-story/roles/consistency-checker.md +1 -1
- package/lib/oh-story/roles/narrative-writer.md +41 -42
- package/lib/oh-story/roles/story-architect.md +19 -23
- package/lib/oh-story/roles/story-explorer.md +8 -5
- package/lib/oh-story/skills/browser-cdp/SKILL.md +1 -1
- package/lib/oh-story/skills/story/SKILL.md +16 -9
- package/lib/oh-story/skills/story/VERSION +1 -1
- package/lib/oh-story/skills/story/references/author-memory.md +163 -0
- package/lib/oh-story/skills/story/scripts/author_memory_commit.py +871 -0
- package/lib/oh-story/skills/story-cover/SKILL.md +1 -1
- package/lib/oh-story/skills/story-deslop/SKILL.md +10 -5
- package/lib/oh-story/skills/story-deslop/references/anti-ai-writing.md +2 -1
- package/lib/oh-story/skills/story-deslop/references/author-memory.md +163 -0
- package/lib/oh-story/skills/story-deslop/references/banned-words.md +6 -4
- package/lib/oh-story/skills/story-deslop/scripts/author_memory_commit.py +871 -0
- package/lib/oh-story/skills/story-deslop/scripts/check-ai-patterns.js +64 -1
- package/lib/oh-story/skills/story-import/SKILL.md +21 -8
- package/lib/oh-story/skills/story-import/references/format-and-structure.md +9 -10
- package/lib/oh-story/skills/story-import/references/structure-mapping-long.md +2 -2
- package/lib/oh-story/skills/story-import/references/structure-mapping-short.md +1 -1
- package/lib/oh-story/skills/story-import/references/tracking-transaction.md +14 -6
- package/lib/oh-story/skills/story-import/scripts/check-outline-contract.js +251 -0
- package/lib/oh-story/skills/story-import/scripts/tracking_commit.py +98 -9
- package/lib/oh-story/skills/story-import/scripts/wordcount_core.py +246 -0
- package/lib/oh-story/skills/story-long-analyze/SKILL.md +4 -4
- package/lib/oh-story/skills/story-long-scan/SKILL.md +1 -1
- package/lib/oh-story/skills/story-long-write/SKILL.md +35 -21
- package/lib/oh-story/skills/story-long-write/references/anti-ai-writing.md +2 -1
- package/lib/oh-story/skills/story-long-write/references/artifact-protocols.md +7 -3
- package/lib/oh-story/skills/story-long-write/references/author-memory.md +163 -0
- package/lib/oh-story/skills/story-long-write/references/banned-words.md +6 -4
- package/lib/oh-story/skills/story-long-write/references/cross-book-recall.md +6 -6
- package/lib/oh-story/skills/story-long-write/references/genre-prose-cards.md +1 -1
- package/lib/oh-story/skills/story-long-write/references/genre-readers.md +1 -1
- package/lib/oh-story/skills/{story-setup/references/agent-references/hooks-chapter.md → story-long-write/references/long-chapter-hooks.md} +5 -5
- package/lib/oh-story/skills/story-long-write/references/{quality-checklist.md → long-chapter-quality.md} +2 -1
- package/lib/oh-story/skills/story-long-write/references/long-format.md +42 -0
- package/lib/oh-story/skills/{story-setup/references/agent-references/genre-catalog.md → story-long-write/references/long-genre-catalog.md} +11 -97
- package/lib/oh-story/skills/story-long-write/references/{genre-core-mechanics.md → long-genre-mechanics.md} +1 -1
- package/lib/oh-story/skills/story-long-write/references/{reversal-toolkit.md → long-reversal.md} +18 -32
- package/lib/oh-story/skills/story-long-write/references/{hooks-suspense.md → long-suspense.md} +6 -6
- package/lib/oh-story/skills/story-long-write/references/opening-design.md +1 -1
- package/lib/oh-story/skills/story-long-write/references/outline-structure-theory.md +1 -1
- package/lib/oh-story/skills/story-long-write/references/style-genre-modules.md +1 -1
- package/lib/oh-story/skills/story-long-write/references/tracking-transaction.md +14 -6
- package/lib/oh-story/skills/story-long-write/references/workflow-chapter.md +32 -27
- package/lib/oh-story/skills/story-long-write/references/workflow-daily.md +5 -4
- package/lib/oh-story/skills/story-long-write/references/workflow-revision.md +2 -2
- package/lib/oh-story/skills/story-long-write/references/workflow-setup.md +17 -11
- package/lib/oh-story/skills/story-long-write/references/writing-craft.md +54 -54
- package/lib/oh-story/skills/story-long-write/scripts/author_memory_commit.py +871 -0
- package/lib/oh-story/skills/story-long-write/scripts/check-ai-patterns.js +64 -1
- package/lib/oh-story/skills/story-long-write/scripts/check-outline-contract.js +251 -0
- package/lib/oh-story/skills/story-long-write/scripts/storyctl.py +303 -0
- package/lib/oh-story/skills/story-long-write/scripts/tracking_commit.py +98 -9
- package/lib/oh-story/skills/story-long-write/scripts/wordcount_core.py +246 -0
- package/lib/oh-story/skills/story-review/SKILL.md +26 -17
- package/lib/oh-story/skills/story-review/references/anti-ai-writing.md +2 -1
- package/lib/oh-story/skills/story-review/references/author-memory.md +163 -0
- package/lib/oh-story/skills/story-review/references/banned-words.md +6 -4
- package/lib/oh-story/skills/story-review/references/{quality-checklist.md → review-quality.md} +22 -21
- package/lib/oh-story/skills/story-review/references/tracking-transaction.md +14 -6
- package/lib/oh-story/skills/story-review/scripts/author_memory_commit.py +871 -0
- package/lib/oh-story/skills/story-review/scripts/check-ai-patterns.js +64 -1
- package/lib/oh-story/skills/story-review/scripts/tracking_commit.py +98 -9
- package/lib/oh-story/skills/story-review/scripts/wordcount_core.py +246 -0
- package/lib/oh-story/skills/story-setup/SKILL.md +85 -41
- package/lib/oh-story/skills/story-setup/references/agent-references/agent-quality.md +72 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/agent-reference-profiles.md +55 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/anti-ai-writing.md +2 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/banned-words.md +6 -4
- package/lib/oh-story/skills/story-setup/references/agent-references/format-and-structure.md +9 -10
- package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards.md +1 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/genre-readers.md +1 -1
- package/lib/oh-story/skills/{story-short-analyze/references/hooks-chapter.md → story-setup/references/agent-references/long-chapter-hooks.md} +5 -5
- package/lib/oh-story/skills/story-setup/references/agent-references/long-emotional-methods.md +209 -0
- package/lib/oh-story/skills/{story-long-write/references/genre-catalog.md → story-setup/references/agent-references/long-genre-catalog.md} +11 -97
- package/lib/oh-story/skills/story-setup/references/agent-references/{genre-core-mechanics.md → long-genre-mechanics.md} +1 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/long-quality.md +40 -0
- package/lib/oh-story/skills/{story-short-write/references/reversal-toolkit.md → story-setup/references/agent-references/long-reversal.md} +18 -32
- package/lib/oh-story/skills/{story-short-write/references/hooks-suspense.md → story-setup/references/agent-references/long-suspense.md} +6 -6
- package/lib/oh-story/skills/story-setup/references/agent-references/opening-design.md +1 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/{emotional-methods.md → short-emotional-methods.md} +4 -4
- package/lib/oh-story/skills/{story-long-write/references/genre-writing-formulas.md → story-setup/references/agent-references/short-genre-formulas.md} +32 -44
- package/lib/oh-story/skills/story-setup/references/agent-references/short-quality.md +39 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/{reversal-toolkit.md → short-reversal.md} +29 -23
- package/lib/oh-story/skills/story-setup/references/agent-references/{hooks-suspense.md → short-suspense.md} +31 -31
- package/lib/oh-story/skills/story-setup/references/agent-references/style-genre-modules.md +1 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/writing-craft.md +55 -55
- package/lib/oh-story/skills/story-short-analyze/SKILL.md +28 -28
- package/lib/oh-story/skills/story-short-analyze/references/{character-basics.md → analysis-character-basics.md} +1 -1
- package/lib/oh-story/skills/story-short-analyze/references/{character-design-methods.md → analysis-character-design.md} +1 -1
- package/lib/oh-story/skills/story-short-analyze/references/{character-relations.md → analysis-character-relations.md} +1 -1
- package/lib/oh-story/skills/story-short-analyze/references/{hooks-paragraph.md → analysis-paragraph-hooks.md} +1 -1
- package/lib/oh-story/skills/story-short-analyze/references/{genre-readers.md → analysis-reader-profile.md} +2 -2
- package/lib/oh-story/skills/story-short-analyze/references/analysis-report-style.md +45 -0
- package/lib/oh-story/skills/story-short-analyze/references/analysis-short-genres.md +65 -0
- package/lib/oh-story/skills/story-short-analyze/references/analysis-short-hooks.md +77 -0
- package/lib/oh-story/skills/story-short-analyze/references/analysis-short-mechanics.md +82 -0
- package/lib/oh-story/skills/story-short-analyze/references/analysis-short-patterns.md +50 -0
- package/lib/oh-story/skills/story-short-analyze/references/analysis-short-suspense.md +98 -0
- package/lib/oh-story/skills/story-short-analyze/references/{genre-writing-techniques.md → analysis-writing-techniques.md} +21 -19
- package/lib/oh-story/skills/story-short-analyze/references/deconstruction-examples.md +1 -1
- package/lib/oh-story/skills/story-short-analyze/references/material-decomposition.md +21 -28
- package/lib/oh-story/skills/story-short-analyze/references/output-contract.md +1 -1
- package/lib/oh-story/skills/story-short-analyze/references/output-templates.md +17 -17
- package/lib/oh-story/skills/story-short-analyze/references/{quality-checklist.md → source-story-quality.md} +28 -49
- package/lib/oh-story/skills/story-short-analyze/references/zhihu-style.md +2 -2
- package/lib/oh-story/skills/story-short-scan/SKILL.md +1 -1
- package/lib/oh-story/skills/story-short-write/SKILL.md +78 -55
- package/lib/oh-story/skills/story-short-write/references/author-memory.md +163 -0
- package/lib/oh-story/skills/story-short-write/references/banned-words.md +6 -4
- package/lib/oh-story/skills/story-short-write/references/cross-book-recall.md +3 -3
- package/lib/oh-story/skills/story-short-write/references/emotional-methods.md +4 -4
- package/lib/oh-story/skills/story-short-write/references/genre-styles//344/270/226/346/203/205/346/211/223/350/204/270.md +3 -3
- package/lib/oh-story/skills/story-short-write/references/genre-styles//345/217/214/347/224/267/344/270/273.md +2 -2
- package/lib/oh-story/skills/story-short-write/references/genre-styles//345/244/215/344/273/207/346/211/223/350/204/270.md +3 -3
- package/lib/oh-story/skills/story-short-write/references/genre-styles//345/256/205/346/226/227/345/256/253/346/226/227.md +6 -6
- package/lib/oh-story/skills/story-short-write/references/genre-styles//346/200/273/350/243/201/350/261/252/351/227/250.md +3 -3
- package/lib/oh-story/skills/story-short-write/references/genre-styles//346/202/254/347/226/221.md +4 -4
- package/lib/oh-story/skills/story-short-write/references/genre-styles//346/260/221/344/277/227/346/200/252/350/260/210.md +7 -7
- package/lib/oh-story/skills/story-short-write/references/genre-styles//346/262/231/351/233/225/350/204/221/346/264/236.md +5 -5
- package/lib/oh-story/skills/story-short-write/references/genre-styles//347/224/234/345/256/240.md +4 -4
- package/lib/oh-story/skills/story-short-write/references/genre-styles//350/277/275/345/246/273/347/201/253/350/221/254/345/234/272.md +3 -3
- package/lib/oh-story/skills/story-short-write/references/genre-writing-formulas.md +32 -44
- package/lib/oh-story/skills/story-short-write/references/genre-writing-techniques.md +20 -18
- package/lib/oh-story/skills/story-short-write/references/output-contract.md +1 -1
- package/lib/oh-story/skills/story-short-write/references/short-craft.md +18 -18
- package/lib/oh-story/skills/story-short-write/references/short-deslop.md +1 -1
- package/lib/oh-story/skills/story-short-write/references/short-format.md +38 -8
- package/lib/oh-story/skills/story-short-write/references/{quality-checklist.md → short-prose-quality.md} +22 -21
- package/lib/oh-story/skills/story-short-write/references/short-reversal.md +355 -0
- package/lib/oh-story/skills/{story-short-analyze/references/hooks-suspense.md → story-short-write/references/short-suspense.md} +31 -31
- package/lib/oh-story/skills/story-short-write/references/submission-craft.md +2 -2
- package/lib/oh-story/skills/story-short-write/references/writing-workflow.md +31 -22
- package/lib/oh-story/skills/story-short-write/scripts/author_memory_commit.py +871 -0
- package/lib/oh-story/skills/story-short-write/scripts/check-ai-patterns.js +64 -1
- package/lib/oh-story/skills/story-short-write/scripts/check-delivery-contract.js +212 -0
- package/lib/oh-story/skills/story-short-write/scripts/check-phase2-contract.js +363 -0
- package/lib/types/client/drama-production-view.d.ts +34 -0
- package/lib/types/client/drama-production-view.d.ts.map +1 -0
- package/lib/types/client/drama-production.d.ts +92 -0
- package/lib/types/client/drama-production.d.ts.map +1 -0
- package/lib/types/client/file-activity.d.ts +9 -2
- package/lib/types/client/file-activity.d.ts.map +1 -1
- package/lib/types/client/index.d.ts +1 -1
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/production-intents.d.ts +9 -0
- package/lib/types/client/production-intents.d.ts.map +1 -0
- package/lib/types/client/production-prompts.d.ts +9 -0
- package/lib/types/client/production-prompts.d.ts.map +1 -0
- package/lib/types/client/production-runtime.d.ts +58 -0
- package/lib/types/client/production-runtime.d.ts.map +1 -0
- package/lib/types/client/video-studio.d.ts +41 -0
- package/lib/types/client/video-studio.d.ts.map +1 -0
- package/lib/types/client/workbench-ui.d.ts +5 -0
- package/lib/types/client/workbench-ui.d.ts.map +1 -0
- package/lib/types/game-verification.d.ts +16 -0
- package/lib/types/game-verification.d.ts.map +1 -0
- package/lib/types/index.d.ts +5 -2
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/production-intent.d.ts +21 -0
- package/lib/types/production-intent.d.ts.map +1 -0
- package/lib/types/production-tool.d.ts +5 -0
- package/lib/types/production-tool.d.ts.map +1 -0
- package/lib/types/reference-tool.d.ts +6 -0
- package/lib/types/reference-tool.d.ts.map +1 -0
- package/lib/types/role-provider.d.ts.map +1 -1
- package/lib/types/role-tool.d.ts +3 -1
- package/lib/types/role-tool.d.ts.map +1 -1
- package/lib/types/skill-provider.d.ts +7 -0
- package/lib/types/skill-provider.d.ts.map +1 -1
- package/lib/types/video-project.d.ts +42 -0
- package/lib/types/video-project.d.ts.map +1 -0
- package/lib/types/workspace-request-trust.d.ts +6 -0
- package/lib/types/workspace-request-trust.d.ts.map +1 -1
- package/lib/types/workspace-route.d.ts +20 -0
- package/lib/types/workspace-route.d.ts.map +1 -1
- package/lib/video-recap/CHANGELOG.md +282 -0
- package/lib/video-recap/LICENSE +21 -0
- package/lib/video-recap/README.en.md +257 -0
- package/lib/video-recap/README.md +255 -0
- package/lib/video-recap/manifest.json +684 -0
- package/lib/video-recap/skills/video-assemble/SKILL.md +97 -0
- package/lib/video-recap/skills/video-assemble/references/jianying/LICENSE.duo-video +21 -0
- package/lib/video-recap/skills/video-assemble/references/jianying/SOURCE.md +15 -0
- package/lib/video-recap/skills/video-assemble/references/jianying/empty_draft_meta_info.json +69 -0
- package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_combination_segment.json +84 -0
- package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_combination_video_material.json +98 -0
- package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_draft.json +204 -0
- package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_material_video.json +83 -0
- package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_meta_material_value.json +23 -0
- package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_project_info.json +133 -0
- package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_segment.json +70 -0
- package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_text_styles.json +30 -0
- package/lib/video-recap/skills/video-assemble/references/jianying/empty_yj_material_audio.json +41 -0
- package/lib/video-recap/skills/video-assemble/references/jianying/empty_yj_material_text.json +109 -0
- package/lib/video-recap/skills/video-assemble/scripts/artifacts.py +88 -0
- package/lib/video-recap/skills/video-assemble/scripts/assemble.py +345 -0
- package/lib/video-recap/skills/video-assemble/scripts/assemble_constants.py +34 -0
- package/lib/video-recap/skills/video-assemble/scripts/assembly_contract.py +305 -0
- package/lib/video-recap/skills/video-assemble/scripts/assembly_settings.py +94 -0
- package/lib/video-recap/skills/video-assemble/scripts/audio_automation.py +205 -0
- package/lib/video-recap/skills/video-assemble/scripts/audio_mix.py +555 -0
- package/lib/video-recap/skills/video-assemble/scripts/export_jianying.py +261 -0
- package/lib/video-recap/skills/video-assemble/scripts/jianying_builders.py +767 -0
- package/lib/video-recap/skills/video-assemble/scripts/jianying_model.py +111 -0
- package/lib/video-recap/skills/video-assemble/scripts/jianying_optional.py +24 -0
- package/lib/video-recap/skills/video-assemble/scripts/jianying_schema.py +109 -0
- package/lib/video-recap/skills/video-assemble/scripts/jianying_templates.py +37 -0
- package/lib/video-recap/skills/video-assemble/scripts/jianying_timeline_contract.py +191 -0
- package/lib/video-recap/skills/video-assemble/scripts/jianying_tracks.py +75 -0
- package/lib/video-recap/skills/video-assemble/scripts/jianying_writer.py +394 -0
- package/lib/video-recap/skills/video-assemble/scripts/lib.py +239 -0
- package/lib/video-recap/skills/video-assemble/scripts/media.py +243 -0
- package/lib/video-recap/skills/video-assemble/scripts/narration_audio.py +477 -0
- package/lib/video-recap/skills/video-assemble/scripts/render_preflight.py +46 -0
- package/lib/video-recap/skills/video-assemble/scripts/source_subtitles.py +486 -0
- package/lib/video-recap/skills/video-assemble/scripts/subtitle_core.py +376 -0
- package/lib/video-recap/skills/video-assemble/scripts/subtitle_render.py +88 -0
- package/lib/video-recap/skills/video-assemble/scripts/timeline.py +248 -0
- package/lib/video-recap/skills/video-assemble/scripts/timeline_emit.py +89 -0
- package/lib/video-recap/skills/video-assemble/scripts/visual_render.py +512 -0
- package/lib/video-recap/skills/video-cut/SKILL.md +90 -0
- package/lib/video-recap/skills/video-cut/scripts/cut.py +67 -0
- package/lib/video-recap/skills/video-cut/scripts/cut_cli.py +320 -0
- package/lib/video-recap/skills/video-cut/scripts/cut_contract.py +545 -0
- package/lib/video-recap/skills/video-cut/scripts/cut_render.py +348 -0
- package/lib/video-recap/skills/video-cut/scripts/lib.py +77 -0
- package/lib/video-recap/skills/video-cut/scripts/media_geometry.py +341 -0
- package/lib/video-recap/skills/video-cut/scripts/narration_mapping.py +249 -0
- package/lib/video-recap/skills/video-cut/scripts/sentence_boundaries.py +745 -0
- package/lib/video-recap/skills/video-recap/SKILL.md +216 -0
- package/lib/video-recap/skills/video-recap/references/config-playbook.md +61 -0
- package/lib/video-recap/skills/video-recap/references/creative-editing-playbook.md +225 -0
- package/lib/video-recap/skills/video-recap/references/data-schema.md +534 -0
- package/lib/video-recap/skills/video-recap/references/research-guide.md +74 -0
- package/lib/video-recap/skills/video-recap/references/shift-left-qc-schema.md +23 -0
- package/lib/video-recap/skills/video-recap/references/timeline-and-jianying.md +251 -0
- package/lib/video-recap/skills/video-recap/scripts/doctor.py +483 -0
- package/lib/video-recap/skills/video-recap/scripts/final_qc.py +737 -0
- package/lib/video-recap/skills/video-recap/scripts/lib.py +242 -0
- package/lib/video-recap/skills/video-recap/scripts/materials.py +457 -0
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc.py +95 -0
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc_client.py +18 -0
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc_contract.py +9 -0
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc_evidence.py +371 -0
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc_observations.py +201 -0
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc_payload.py +227 -0
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc_report.py +362 -0
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc_runner.py +129 -0
- package/lib/video-recap/skills/video-recap/scripts/qc_contract.py +338 -0
- package/lib/video-recap/skills/video-recap/scripts/recap.py +9 -0
- package/lib/video-recap/skills/video-recap/scripts/recap_cli.py +140 -0
- package/lib/video-recap/skills/video-recap/scripts/recap_inspect.py +550 -0
- package/lib/video-recap/skills/video-recap/scripts/recap_review.py +77 -0
- package/lib/video-recap/skills/video-recap/scripts/recap_runner.py +654 -0
- package/lib/video-recap/skills/video-recap/scripts/recap_runtime.py +335 -0
- package/lib/video-recap/skills/video-recap/scripts/recap_stage_qc.py +217 -0
- package/lib/video-recap/skills/video-recap/scripts/recap_timeline.py +795 -0
- package/lib/video-recap/skills/video-script/SKILL.md +248 -0
- package/lib/video-recap/skills/video-script/references/creative-editing-playbook.md +225 -0
- package/lib/video-recap/skills/video-script/references/research-guide.md +75 -0
- package/lib/video-recap/skills/video-script/scripts/agent_brief.py +399 -0
- package/lib/video-recap/skills/video-script/scripts/agent_text.py +410 -0
- package/lib/video-recap/skills/video-script/scripts/brief_context.py +403 -0
- package/lib/video-recap/skills/video-script/scripts/brief_inputs.py +332 -0
- package/lib/video-recap/skills/video-script/scripts/brief_timeline.py +442 -0
- package/lib/video-recap/skills/video-script/scripts/deslop_qc.py +237 -0
- package/lib/video-recap/skills/video-script/scripts/evidence_bundle.py +492 -0
- package/lib/video-recap/skills/video-script/scripts/lib.py +327 -0
- package/lib/video-recap/skills/video-script/scripts/narration.py +12 -0
- package/lib/video-recap/skills/video-script/scripts/narration_lint.py +678 -0
- package/lib/video-recap/skills/video-script/scripts/review.py +42 -0
- package/lib/video-recap/skills/video-script/scripts/review_grounding.py +262 -0
- package/lib/video-recap/skills/video-script/scripts/review_response.py +761 -0
- package/lib/video-recap/skills/video-script/scripts/review_runner.py +200 -0
- package/lib/video-recap/skills/video-script/scripts/speech_ownership.py +204 -0
- package/lib/video-recap/skills/video-script/scripts/timeline_fusion.py +199 -0
- package/lib/video-recap/skills/video-script/scripts/validate.py +164 -0
- package/lib/video-recap/skills/video-understanding/SKILL.md +75 -0
- package/lib/video-recap/skills/video-understanding/references/data-schema.md +299 -0
- package/lib/video-recap/skills/video-understanding/references/prompt-templates.md +37 -0
- package/lib/video-recap/skills/video-understanding/references/research-guide.md +74 -0
- package/lib/video-recap/skills/video-understanding/scripts/agent_brief.py +399 -0
- package/lib/video-recap/skills/video-understanding/scripts/agent_text.py +410 -0
- package/lib/video-recap/skills/video-understanding/scripts/asr.py +260 -0
- package/lib/video-recap/skills/video-understanding/scripts/brief.py +12 -0
- package/lib/video-recap/skills/video-understanding/scripts/brief_context.py +403 -0
- package/lib/video-recap/skills/video-understanding/scripts/brief_inputs.py +332 -0
- package/lib/video-recap/skills/video-understanding/scripts/brief_timeline.py +442 -0
- package/lib/video-recap/skills/video-understanding/scripts/consolidate.py +647 -0
- package/lib/video-recap/skills/video-understanding/scripts/deslop_qc.py +237 -0
- package/lib/video-recap/skills/video-understanding/scripts/detect.py +513 -0
- package/lib/video-recap/skills/video-understanding/scripts/extract.py +62 -0
- package/lib/video-recap/skills/video-understanding/scripts/lib.py +470 -0
- package/lib/video-recap/skills/video-understanding/scripts/narration_lint.py +678 -0
- package/lib/video-recap/skills/video-understanding/scripts/speech_ownership.py +204 -0
- package/lib/video-recap/skills/video-understanding/scripts/storyboard.py +507 -0
- package/lib/video-recap/skills/video-understanding/scripts/timeline_fusion.py +199 -0
- package/lib/video-recap/skills/video-understanding/scripts/understand.py +9 -0
- package/lib/video-recap/skills/video-understanding/scripts/understanding_brief.py +216 -0
- package/lib/video-recap/skills/video-understanding/scripts/understanding_cache.py +327 -0
- package/lib/video-recap/skills/video-understanding/scripts/understanding_runner.py +367 -0
- package/lib/video-recap/skills/video-understanding/scripts/understanding_storyboard.py +162 -0
- package/lib/video-recap/skills/video-understanding/scripts/vlm.py +705 -0
- package/lib/video-recap/skills/video-voiceover/SKILL.md +81 -0
- package/lib/video-recap/skills/video-voiceover/scripts/dub.py +637 -0
- package/lib/video-recap/skills/video-voiceover/scripts/fish_audio.py +109 -0
- package/lib/video-recap/skills/video-voiceover/scripts/lib.py +421 -0
- package/lib/video-recap/skills/video-voiceover/scripts/tts_audio.py +113 -0
- package/lib/video-recap/skills/video-voiceover/scripts/voiceover.py +782 -0
- package/package.json +57 -41
- package/lib/drama/skills/short-drama/references/lifecycle-commands.md +0 -130
- package/lib/oh-story/skills/story-deslop/scripts/check-outline-copy.js +0 -241
- package/lib/oh-story/skills/story-long-write/references/format-and-structure.md +0 -169
- package/lib/oh-story/skills/story-setup/references/agent-references/genre-writing-formulas.md +0 -444
- package/lib/oh-story/skills/story-setup/references/agent-references/quality-checklist.md +0 -247
- package/lib/oh-story/skills/story-short-analyze/references/anti-ai-writing.md +0 -582
- package/lib/oh-story/skills/story-short-analyze/references/banned-words.md +0 -147
- package/lib/oh-story/skills/story-short-analyze/references/genre-catalog.md +0 -490
- package/lib/oh-story/skills/story-short-analyze/references/genre-core-mechanics.md +0 -425
- package/lib/oh-story/skills/story-short-analyze/references/genre-writing-formulas.md +0 -453
- /package/lib/oh-story/skills/{story-long-write/references/hooks-chapter.md → story-setup/references/agent-references/short-chapter-hooks.md} +0 -0
- /package/lib/oh-story/skills/{story-long-write/references/hooks-paragraph.md → story-setup/references/agent-references/short-paragraph-hooks.md} +0 -0
|
@@ -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":
|