@oh-story/dsh 0.1.8 → 0.1.10
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 +14 -14
- package/lib/client.js +237 -84
- package/lib/client.js.map +3 -3
- package/lib/drama/manifest.json +183 -77
- package/lib/drama/skills/short-drama/SKILL.md +5 -1
- package/lib/drama/skills/short-drama/scripts/creator_markdown_check.py +140 -9
- package/lib/drama/skills/short-drama/scripts/project_tool.py +11 -0
- package/lib/drama/skills/short-drama-assets/references/character-and-look.md +28 -0
- package/lib/drama/skills/short-drama-assets/references/continuity-lock.md +8 -2
- package/lib/drama/skills/short-drama-assets/references/voice-direction.md +28 -0
- package/lib/drama/skills/short-drama-edit/SKILL.md +146 -0
- package/lib/drama/skills/short-drama-edit/agents/openai.yaml +4 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/README.md +60 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/package.json +15 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/remotion.config.ts +6 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/Root.tsx +26 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/Subtitles.tsx +88 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/font.ts +46 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/index.ts +4 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/schema.ts +32 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/tsconfig.json +13 -0
- package/lib/drama/skills/short-drama-edit/assets//345/211/252/350/276/221/345/215/225.md +43 -0
- package/lib/drama/skills/short-drama-edit/references/cut-craft.md +38 -0
- package/lib/drama/skills/short-drama-edit/references/delivery-verify.md +29 -0
- package/lib/drama/skills/short-drama-edit/references/generated-footage.md +54 -0
- package/lib/drama/skills/short-drama-edit/references/sound-and-subtitles.md +60 -0
- package/lib/drama/skills/short-drama-edit/references/stage-contract.md +51 -0
- package/lib/drama/skills/short-drama-edit/scripts/edit_tool.py +1156 -0
- package/lib/drama/skills/short-drama-edit/scripts/selftest.py +520 -0
- package/lib/drama/skills/short-drama-image-prompts/SKILL.md +2 -0
- package/lib/drama/skills/short-drama-image-prompts/assets/image-prompts.md +5 -0
- package/lib/drama/skills/short-drama-image-prompts/assets/lookdev-prompts.md +3 -0
- package/lib/drama/skills/short-drama-image-prompts/references/common-recipe.md +11 -0
- package/lib/drama/skills/short-drama-novel-analyze/references/aggregation-and-entities.md +2 -2
- package/lib/drama/skills/short-drama-novel-analyze/references/chapter-extraction.md +4 -1
- package/lib/drama/skills/short-drama-produce/SKILL.md +29 -4
- package/lib/drama/skills/short-drama-produce/references/adapter-contract.md +22 -0
- package/lib/drama/skills/short-drama-produce/references/providers/minimax-h3-video.md +13 -12
- package/lib/drama/skills/short-drama-produce/references/providers/minimax-speech.md +41 -0
- package/lib/drama/skills/short-drama-produce/references/providers/seedance.md +7 -0
- package/lib/drama/skills/short-drama-produce/scripts/fixture_adapter.py +18 -1
- package/lib/drama/skills/short-drama-produce/scripts/production_tool.py +181 -2
- package/lib/drama/skills/short-drama-produce/scripts/provider_adapters.py +199 -3
- package/lib/drama/skills/short-drama-review/SKILL.md +2 -0
- package/lib/drama/skills/short-drama-review/references/production-quality-gates.md +7 -0
- package/lib/drama/skills/short-drama-review/references/rubric-visual-motion.md +10 -0
- package/lib/drama/skills/short-drama-storyboard/SKILL.md +12 -1
- package/lib/drama/skills/short-drama-storyboard/assets/keyframe-prompts.md +4 -0
- package/lib/drama/skills/short-drama-storyboard/references/lighting-craft.md +39 -0
- package/lib/drama/skills/short-drama-storyboard/references/shot-craft.md +78 -2
- package/lib/drama/skills/short-drama-storyboard/references/stage-contract.md +2 -0
- package/lib/drama/skills/short-drama-video-prompts/SKILL.md +12 -5
- package/lib/drama/skills/short-drama-video-prompts/references/camera-audio-continuity.md +17 -0
- package/lib/drama/skills/short-drama-video-prompts/references/minimax-h3.md +76 -7
- package/lib/drama/skills/short-drama-video-prompts/references/motion-recipe.md +5 -2
- package/lib/drama/skills/short-drama-video-prompts/references/performance-action-timing.md +21 -0
- package/lib/drama/skills/short-drama-video-prompts/references/production-prompt-grammar.md +6 -17
- package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.0.md +12 -3
- package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.5.md +61 -5
- package/lib/drama/skills/short-drama-video-prompts/references/stage-contract.md +3 -3
- package/lib/drama/skills/short-drama-write/SKILL.md +11 -2
- package/lib/drama/skills/short-drama-write/references/screenplay-format.md +6 -0
- package/lib/drama/skills/short-drama-write/references/stage-contract.md +1 -0
- package/lib/index.js +290 -90
- package/lib/index.js.map +4 -4
- package/lib/novel-to-game/README.md +299 -59
- package/lib/novel-to-game/README_ZH.md +299 -56
- package/lib/novel-to-game/VERSION +1 -1
- package/lib/novel-to-game/examples/jin-ping-mei/build/BUILD_BRIEF.md +3 -1
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/RUN.md +11 -3
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/css/new-guofeng.css +397 -8
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/css/style.css +0 -31
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/data.js +71 -73
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/engine.js +78 -89
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/main.js +37 -14
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/text.js +5 -5
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/package.json +2 -1
- package/lib/novel-to-game/examples/jin-ping-mei/qa/README.md +11 -16
- package/lib/novel-to-game/examples/jin-ping-mei/qa/verification.json +11 -11
- package/lib/novel-to-game/examples/jin-ping-mei/qa/verify_playable_model.mjs +23 -0
- package/lib/novel-to-game/manifest.json +77 -87
- package/lib/novel-to-game/skills/game-art-direction/SKILL.md +9 -35
- package/lib/novel-to-game/skills/game-art-direction/references/art-direction-method.md +27 -106
- package/lib/novel-to-game/skills/game-build/SKILL.md +17 -35
- package/lib/novel-to-game/skills/game-build/references/blender-asset-workflow.md +24 -0
- package/lib/novel-to-game/skills/game-build/references/build-brief-contract.md +45 -82
- package/lib/novel-to-game/skills/game-build/references/generative-media-pipeline.md +14 -45
- package/lib/novel-to-game/skills/game-build/references/playable-model-contract.md +30 -65
- package/lib/novel-to-game/skills/game-build/references/tts-production-contract.md +13 -27
- package/lib/novel-to-game/skills/game-concept/SKILL.md +16 -34
- package/lib/novel-to-game/skills/game-concept/references/concept-method.md +28 -75
- package/lib/novel-to-game/skills/game-qa/SKILL.md +9 -20
- package/lib/novel-to-game/skills/game-qa/references/test-design-method.md +10 -6
- package/lib/novel-to-game/skills/game-world-design/SKILL.md +19 -32
- package/lib/novel-to-game/skills/game-world-design/references/narrative-design-method.md +23 -72
- package/lib/novel-to-game/skills/game-world-design/references/numeric-design-method.md +14 -48
- package/lib/novel-to-game/skills/game-world-design/references/world-design-method.md +36 -103
- package/lib/novel-to-game/skills/novel-game-analyze/SKILL.md +9 -18
- package/lib/novel-to-game/skills/novel-game-analyze/references/gameability-protocol.md +4 -4
- package/lib/novel-to-game/skills/novel-to-game/SKILL.md +10 -27
- package/lib/novel-to-game/skills/novel-to-game/references/intake-method.md +5 -6
- package/lib/novel-to-game/skills/novel-to-game/references/pipeline-contract.md +6 -11
- package/lib/oh-story/manifest.json +419 -254
- package/lib/oh-story/roles/chapter-extractor.md +177 -237
- package/lib/oh-story/roles/character-designer.md +1 -1
- package/lib/oh-story/roles/consistency-checker.md +4 -4
- package/lib/oh-story/roles/narrative-writer.md +56 -243
- package/lib/oh-story/roles/story-architect.md +6 -6
- package/lib/oh-story/roles/story-explorer.md +11 -11
- package/lib/oh-story/skills/story/SKILL.md +13 -7
- package/lib/oh-story/skills/story/VERSION +1 -1
- package/lib/oh-story/skills/story/references/author-memory.md +61 -29
- package/lib/oh-story/skills/story/scripts/author_memory_commit.py +934 -170
- package/lib/oh-story/skills/story-cover/SKILL.md +10 -1
- package/lib/oh-story/skills/story-deslop/SKILL.md +54 -229
- package/lib/oh-story/skills/story-deslop/references/anti-ai-writing.md +29 -56
- package/lib/oh-story/skills/story-deslop/references/author-memory.md +61 -29
- package/lib/oh-story/skills/story-deslop/references/banned-words.md +3 -1
- package/lib/oh-story/skills/story-deslop/references/deslop-gates.md +154 -0
- package/lib/oh-story/skills/story-deslop/references/style-resolution.md +27 -0
- package/lib/oh-story/skills/story-deslop/scripts/author_memory_commit.py +934 -170
- package/lib/oh-story/skills/story-deslop/scripts/check-ai-patterns.js +6 -1
- package/lib/oh-story/skills/story-deslop/scripts/check-degeneration.js +39 -20
- package/lib/oh-story/skills/story-deslop/scripts/normalize-punctuation.js +10 -5
- package/lib/oh-story/skills/story-deslop/scripts/style-whitelist.js +39 -0
- package/lib/oh-story/skills/story-import/SKILL.md +78 -82
- package/lib/oh-story/skills/story-import/references/character-state-reverse.md +1 -1
- package/lib/oh-story/skills/story-import/references/format-and-structure.md +2 -2
- package/lib/oh-story/skills/story-import/references/state-tracking.md +4 -69
- package/lib/oh-story/skills/story-import/references/structure-mapping-long.md +10 -8
- package/lib/oh-story/skills/story-import/references/tracking-initialization.md +31 -0
- package/lib/oh-story/skills/story-import/references/tracking-transaction.md +9 -34
- package/lib/oh-story/skills/story-import/scripts/check-outline-contract.js +197 -15
- package/lib/oh-story/skills/story-import/scripts/tracking_commit.py +210 -2
- package/lib/oh-story/skills/story-import/scripts/wordcount_core.py +3 -0
- package/lib/oh-story/skills/story-long-analyze/SKILL.md +91 -258
- package/lib/oh-story/skills/story-long-analyze/references/author-facing.md +245 -0
- package/lib/oh-story/skills/story-long-analyze/references/deconstruction-notes.md +2 -2
- package/lib/oh-story/skills/story-long-analyze/references/inspiration-library.md +105 -0
- package/lib/oh-story/skills/story-long-analyze/references/material-decomposition.md +113 -105
- package/lib/oh-story/skills/story-long-analyze/references/output-templates.md +169 -125
- package/lib/oh-story/skills/story-long-analyze/references/pipeline-ops.md +172 -64
- package/lib/oh-story/skills/story-long-analyze/references/semantic-acceptance-fixtures.md +84 -0
- package/lib/oh-story/skills/story-long-analyze/references/style-profile-generator.md +42 -132
- package/lib/oh-story/skills/story-long-analyze/references/style-profile-protocol.md +5 -5
- package/lib/oh-story/skills/story-long-analyze/scripts/build_chapter_index.py +466 -0
- package/lib/oh-story/skills/story-long-analyze/scripts/inspect_existing_assets.py +702 -0
- package/lib/oh-story/skills/story-long-analyze/scripts/inspiration_index.py +864 -0
- package/lib/oh-story/skills/story-long-analyze/scripts/manage_analysis_run.py +1106 -0
- package/lib/oh-story/skills/story-long-analyze/scripts/render_relation_chart.py +425 -0
- package/lib/oh-story/skills/story-long-scan/SKILL.md +4 -1
- package/lib/oh-story/skills/story-long-write/SKILL.md +52 -216
- package/lib/oh-story/skills/story-long-write/references/anti-ai-writing.md +29 -56
- package/lib/oh-story/skills/story-long-write/references/artifact-protocols.md +72 -12
- package/lib/oh-story/skills/story-long-write/references/author-memory.md +61 -29
- package/lib/oh-story/skills/story-long-write/references/banned-words.md +3 -1
- package/lib/oh-story/skills/story-long-write/references/benchmark-recall.md +20 -0
- package/lib/oh-story/skills/story-long-write/references/cross-book-recall.md +26 -0
- package/lib/oh-story/skills/story-long-write/references/dialogue-mastery.md +10 -4
- package/lib/oh-story/skills/story-long-write/references/emotional-methods.md +2 -2
- package/lib/oh-story/skills/story-long-write/references/long-chapter-quality.md +6 -15
- package/lib/oh-story/skills/story-long-write/references/long-format.md +2 -0
- package/lib/oh-story/skills/story-long-write/references/outline-structure-theory.md +2 -0
- package/lib/oh-story/skills/story-long-write/references/project-files.md +110 -0
- package/lib/oh-story/skills/story-long-write/references/reader-contract-and-progression.md +12 -4
- package/lib/oh-story/skills/story-long-write/references/reference-index.md +86 -0
- package/lib/oh-story/skills/story-long-write/references/style-combat-face.md +1 -1
- package/lib/oh-story/skills/story-long-write/references/style-craft.md +9 -29
- package/lib/oh-story/skills/story-long-write/references/style-genre-modules.md +1 -1
- package/lib/oh-story/skills/story-long-write/references/style-resolution.md +27 -0
- package/lib/oh-story/skills/story-long-write/references/tracking-initialization.md +31 -0
- package/lib/oh-story/skills/story-long-write/references/tracking-transaction.md +9 -34
- package/lib/oh-story/skills/story-long-write/references/workflow-chapter.md +88 -118
- package/lib/oh-story/skills/story-long-write/references/workflow-daily.md +55 -119
- package/lib/oh-story/skills/story-long-write/references/workflow-revision.md +15 -11
- package/lib/oh-story/skills/story-long-write/references/workflow-setup.md +73 -54
- package/lib/oh-story/skills/story-long-write/references/writer-prompt-fallback.md +22 -0
- package/lib/oh-story/skills/story-long-write/references/writing-craft.md +63 -4
- package/lib/oh-story/skills/story-long-write/scripts/author_memory_commit.py +934 -170
- package/lib/oh-story/skills/story-long-write/scripts/build_writer_prompt.py +531 -0
- package/lib/oh-story/skills/story-long-write/scripts/check-ai-patterns.js +6 -1
- package/lib/oh-story/skills/story-long-write/scripts/check-degeneration.js +39 -20
- package/lib/oh-story/skills/story-long-write/scripts/check-outline-contract.js +197 -15
- package/lib/oh-story/skills/story-long-write/scripts/check-outline-copy.js +90 -23
- package/lib/oh-story/skills/story-long-write/scripts/normalize-punctuation.js +10 -5
- package/lib/oh-story/skills/story-long-write/scripts/outline_view.py +285 -0
- package/lib/oh-story/skills/story-long-write/scripts/storyctl.py +58 -0
- package/lib/oh-story/skills/story-long-write/scripts/style-whitelist.js +39 -0
- package/lib/oh-story/skills/story-long-write/scripts/tracking_commit.py +210 -2
- package/lib/oh-story/skills/story-long-write/scripts/wordcount_core.py +3 -0
- package/lib/oh-story/skills/story-review/SKILL.md +80 -87
- package/lib/oh-story/skills/story-review/references/anti-ai-writing.md +29 -56
- package/lib/oh-story/skills/story-review/references/author-memory.md +61 -29
- package/lib/oh-story/skills/story-review/references/banned-words.md +3 -1
- package/lib/oh-story/skills/story-review/references/dialogue-mastery.md +10 -4
- package/lib/oh-story/skills/story-review/references/review-quality.md +6 -12
- package/lib/oh-story/skills/story-review/references/style-resolution.md +27 -0
- package/lib/oh-story/skills/story-review/references/tracking-initialization.md +31 -0
- package/lib/oh-story/skills/story-review/references/tracking-transaction.md +9 -34
- package/lib/oh-story/skills/story-review/scripts/author_memory_commit.py +934 -170
- package/lib/oh-story/skills/story-review/scripts/check-ai-patterns.js +6 -1
- package/lib/oh-story/skills/story-review/scripts/check-degeneration.js +39 -20
- package/lib/oh-story/skills/story-review/scripts/normalize-punctuation.js +10 -5
- package/lib/oh-story/skills/story-review/scripts/style-whitelist.js +39 -0
- package/lib/oh-story/skills/story-review/scripts/tracking_commit.py +210 -2
- package/lib/oh-story/skills/story-review/scripts/wordcount_core.py +3 -0
- package/lib/oh-story/skills/story-setup/SKILL.md +58 -43
- package/lib/oh-story/skills/story-setup/references/agent-references/anti-ai-writing.md +29 -56
- package/lib/oh-story/skills/story-setup/references/agent-references/banned-words.md +3 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/deslop-gates.md +154 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/dialogue-mastery.md +10 -4
- package/lib/oh-story/skills/story-setup/references/agent-references/format-and-structure.md +2 -2
- package/lib/oh-story/skills/story-setup/references/agent-references/long-emotional-methods.md +2 -2
- package/lib/oh-story/skills/story-setup/references/agent-references/long-quality.md +1 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/short-genre-formulas.md +1 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/style-combat-face.md +1 -1
- 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/style-resolution.md +27 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/writing-craft.md +63 -4
- package/lib/oh-story/skills/story-setup/references/diagnostics.md +38 -0
- package/lib/oh-story/skills/story-short-analyze/SKILL.md +22 -3
- package/lib/oh-story/skills/story-short-analyze/references/analysis-writing-techniques.md +1 -1
- package/lib/oh-story/skills/story-short-analyze/references/material-decomposition.md +5 -1
- package/lib/oh-story/skills/story-short-analyze/references/source-story-quality.md +2 -2
- package/lib/oh-story/skills/story-short-scan/SKILL.md +5 -1
- package/lib/oh-story/skills/story-short-write/SKILL.md +15 -324
- package/lib/oh-story/skills/story-short-write/references/author-memory.md +61 -29
- package/lib/oh-story/skills/story-short-write/references/banned-words.md +3 -1
- package/lib/oh-story/skills/story-short-write/references/benchmark-recall.md +60 -0
- package/lib/oh-story/skills/story-short-write/references/dialogue-mastery.md +10 -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 +2 -2
- package/lib/oh-story/skills/story-short-write/references/genre-styles//345/217/214/347/224/267/344/270/273.md +1 -1
- 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 +3 -3
- 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 +5 -5
- package/lib/oh-story/skills/story-short-write/references/genre-styles//346/260/221/344/277/227/346/200/252/350/260/210.md +4 -4
- package/lib/oh-story/skills/story-short-write/references/genre-styles//347/224/234/345/256/240.md +5 -5
- 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 +1 -1
- package/lib/oh-story/skills/story-short-write/references/genre-writing-techniques.md +1 -1
- package/lib/oh-story/skills/story-short-write/references/reference-index.md +55 -0
- package/lib/oh-story/skills/story-short-write/references/short-craft.md +12 -14
- package/lib/oh-story/skills/story-short-write/references/short-deslop.md +10 -10
- package/lib/oh-story/skills/story-short-write/references/short-format.md +3 -1
- package/lib/oh-story/skills/story-short-write/references/short-prose-quality.md +2 -2
- package/lib/oh-story/skills/story-short-write/references/style-resolution.md +27 -0
- package/lib/oh-story/skills/story-short-write/references/workflow-design.md +78 -0
- package/lib/oh-story/skills/story-short-write/references/workflow-draft.md +126 -0
- package/lib/oh-story/skills/story-short-write/references/workflow-revision.md +32 -0
- package/lib/oh-story/skills/story-short-write/references/writing-workflow.md +5 -5
- package/lib/oh-story/skills/story-short-write/scripts/author_memory_commit.py +934 -170
- package/lib/oh-story/skills/story-short-write/scripts/check-ai-patterns.js +6 -1
- package/lib/oh-story/skills/story-short-write/scripts/check-degeneration.js +39 -20
- package/lib/oh-story/skills/story-short-write/scripts/check-delivery-contract.js +34 -5
- package/lib/oh-story/skills/story-short-write/scripts/check-outline-copy.js +90 -23
- package/lib/oh-story/skills/story-short-write/scripts/normalize-punctuation.js +10 -5
- package/lib/oh-story/skills/story-short-write/scripts/style-whitelist.js +39 -0
- package/lib/types/client/drama-production-view.d.ts.map +1 -1
- package/lib/types/client/file-activity.d.ts +19 -1
- package/lib/types/client/file-activity.d.ts.map +1 -1
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/production-prompts.d.ts +7 -0
- package/lib/types/client/production-prompts.d.ts.map +1 -1
- package/lib/types/client/production-runtime.d.ts +33 -0
- package/lib/types/client/production-runtime.d.ts.map +1 -1
- package/lib/types/drama-adapters.d.ts +5 -3
- package/lib/types/drama-adapters.d.ts.map +1 -1
- package/lib/types/native-hooks.d.ts +52 -0
- package/lib/types/native-hooks.d.ts.map +1 -1
- package/lib/types/role-identity.d.ts +17 -0
- package/lib/types/role-identity.d.ts.map +1 -0
- package/lib/types/role-tool.d.ts.map +1 -1
- package/lib/types/skill-provider.d.ts.map +1 -1
- package/lib/video-recap/CHANGELOG.md +31 -1
- package/lib/video-recap/manifest.json +162 -162
- package/lib/video-recap/skills/video-assemble/scripts/artifacts.py +4 -9
- package/lib/video-recap/skills/video-assemble/scripts/assemble.py +47 -60
- package/lib/video-recap/skills/video-assemble/scripts/assembly_contract.py +77 -115
- package/lib/video-recap/skills/video-assemble/scripts/assembly_settings.py +41 -40
- package/lib/video-recap/skills/video-assemble/scripts/audio_automation.py +22 -32
- package/lib/video-recap/skills/video-assemble/scripts/audio_mix.py +102 -192
- package/lib/video-recap/skills/video-assemble/scripts/jianying_builders.py +14 -35
- package/lib/video-recap/skills/video-assemble/scripts/jianying_model.py +14 -12
- package/lib/video-recap/skills/video-assemble/scripts/jianying_optional.py +8 -9
- package/lib/video-recap/skills/video-assemble/scripts/jianying_schema.py +3 -14
- package/lib/video-recap/skills/video-assemble/scripts/jianying_timeline_contract.py +23 -12
- package/lib/video-recap/skills/video-assemble/scripts/jianying_tracks.py +1 -4
- package/lib/video-recap/skills/video-assemble/scripts/jianying_writer.py +24 -25
- package/lib/video-recap/skills/video-assemble/scripts/lib.py +31 -85
- package/lib/video-recap/skills/video-assemble/scripts/media.py +100 -144
- package/lib/video-recap/skills/video-assemble/scripts/narration_audio.py +101 -195
- package/lib/video-recap/skills/video-assemble/scripts/render_preflight.py +10 -21
- package/lib/video-recap/skills/video-assemble/scripts/source_subtitles.py +90 -169
- package/lib/video-recap/skills/video-assemble/scripts/subtitle_core.py +69 -122
- package/lib/video-recap/skills/video-assemble/scripts/subtitle_render.py +10 -17
- package/lib/video-recap/skills/video-assemble/scripts/timeline.py +33 -48
- package/lib/video-recap/skills/video-assemble/scripts/timeline_emit.py +14 -20
- package/lib/video-recap/skills/video-assemble/scripts/visual_render.py +135 -213
- package/lib/video-recap/skills/video-cut/scripts/cut_cli.py +49 -57
- package/lib/video-recap/skills/video-cut/scripts/cut_contract.py +38 -74
- package/lib/video-recap/skills/video-cut/scripts/cut_render.py +46 -95
- package/lib/video-recap/skills/video-cut/scripts/lib.py +23 -25
- package/lib/video-recap/skills/video-cut/scripts/media_geometry.py +70 -173
- package/lib/video-recap/skills/video-cut/scripts/narration_mapping.py +28 -53
- package/lib/video-recap/skills/video-cut/scripts/sentence_boundaries.py +162 -322
- package/lib/video-recap/skills/video-recap/references/timeline-and-jianying.md +6 -7
- package/lib/video-recap/skills/video-recap/scripts/doctor.py +104 -124
- package/lib/video-recap/skills/video-recap/scripts/final_qc.py +255 -412
- package/lib/video-recap/skills/video-recap/scripts/lib.py +30 -52
- package/lib/video-recap/skills/video-recap/scripts/materials.py +97 -120
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc_client.py +7 -12
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc_evidence.py +46 -97
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc_observations.py +18 -36
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc_payload.py +29 -47
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc_report.py +43 -76
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc_runner.py +2 -1
- package/lib/video-recap/skills/video-recap/scripts/qc_contract.py +2 -1
- package/lib/video-recap/skills/video-recap/scripts/recap_cli.py +2 -2
- package/lib/video-recap/skills/video-recap/scripts/recap_inspect.py +129 -145
- package/lib/video-recap/skills/video-recap/scripts/recap_review.py +27 -20
- package/lib/video-recap/skills/video-recap/scripts/recap_runner.py +20 -31
- package/lib/video-recap/skills/video-recap/scripts/recap_runtime.py +31 -106
- package/lib/video-recap/skills/video-recap/scripts/recap_stage_qc.py +43 -113
- package/lib/video-recap/skills/video-recap/scripts/recap_timeline.py +196 -377
- package/lib/video-recap/skills/video-script/scripts/agent_brief.py +1 -1
- package/lib/video-recap/skills/video-script/scripts/agent_text.py +63 -172
- package/lib/video-recap/skills/video-script/scripts/brief_context.py +80 -135
- package/lib/video-recap/skills/video-script/scripts/brief_inputs.py +125 -167
- package/lib/video-recap/skills/video-script/scripts/brief_timeline.py +93 -208
- package/lib/video-recap/skills/video-script/scripts/deslop_qc.py +20 -46
- package/lib/video-recap/skills/video-script/scripts/lib.py +30 -26
- package/lib/video-recap/skills/video-script/scripts/narration_lint.py +145 -140
- package/lib/video-recap/skills/video-script/scripts/review_response.py +12 -42
- package/lib/video-recap/skills/video-script/scripts/review_runner.py +0 -6
- package/lib/video-recap/skills/video-script/scripts/speech_ownership.py +39 -100
- package/lib/video-recap/skills/video-script/scripts/timeline_fusion.py +33 -124
- package/lib/video-recap/skills/video-understanding/scripts/agent_brief.py +1 -1
- package/lib/video-recap/skills/video-understanding/scripts/agent_text.py +63 -172
- package/lib/video-recap/skills/video-understanding/scripts/asr.py +1 -2
- package/lib/video-recap/skills/video-understanding/scripts/brief_context.py +80 -135
- package/lib/video-recap/skills/video-understanding/scripts/brief_inputs.py +125 -167
- package/lib/video-recap/skills/video-understanding/scripts/brief_timeline.py +93 -208
- package/lib/video-recap/skills/video-understanding/scripts/consolidate.py +1 -1
- package/lib/video-recap/skills/video-understanding/scripts/deslop_qc.py +20 -46
- package/lib/video-recap/skills/video-understanding/scripts/lib.py +31 -26
- package/lib/video-recap/skills/video-understanding/scripts/narration_lint.py +145 -140
- package/lib/video-recap/skills/video-understanding/scripts/speech_ownership.py +39 -100
- package/lib/video-recap/skills/video-understanding/scripts/timeline_fusion.py +33 -124
- package/lib/video-recap/skills/video-understanding/scripts/vlm.py +1 -4
- package/lib/video-recap/skills/video-voiceover/scripts/dub.py +7 -13
- package/lib/video-recap/skills/video-voiceover/scripts/fish_audio.py +12 -30
- package/lib/video-recap/skills/video-voiceover/scripts/lib.py +98 -131
- package/lib/video-recap/skills/video-voiceover/scripts/tts_audio.py +17 -28
- package/lib/video-recap/skills/video-voiceover/scripts/voiceover.py +151 -240
- package/package.json +45 -45
- package/lib/novel-to-game/skills/game-world-design/references/dialogue-design-method.md +0 -59
- package/lib/novel-to-game/skills/game-world-design/references/game-writing-craft.md +0 -53
- package/lib/novel-to-game/skills/novel-to-game/references/intake-benchmark-reference.md +0 -42
- package/lib/oh-story/skills/story-long-write/references/state-tracking.md +0 -108
|
@@ -16,7 +16,8 @@ license: MIT
|
|
|
16
16
|
取出本次提示词,建立一个有边界的运行 job。creator-first job 的 `source` 必须指向拥有这条提示词的
|
|
17
17
|
当前 Markdown,`source_entry` 必须点名该文档允许的二级标题:`图片提示词.md` 用 `IMG-*`,
|
|
18
18
|
`视频提示词.md` 用 `MOTION-*`,`分镜.md` 用 `SHOT-*`(modality 为 `image`,正文取该镜的
|
|
19
|
-
`###
|
|
19
|
+
`### 冻结关键帧提示词`)。此选择器只提取首帧;尾帧需先由图片提示词阶段建立独立 `IMG-*` 状态图条目,
|
|
20
|
+
再从 `图片提示词.md` 准备 image job。创作者也可以提供已有起始帧——
|
|
20
21
|
在自己的工具里出图再放进项目同样成立,图片始终不进项目时走分镜的 `PLAN-...`;
|
|
21
22
|
产出落在 `剧集/<EP>/制作成果/images/` 后,分镜 owner 才能把它绑成 `用途:起始帧` 的 `REF-...`。存在真实参考图时,
|
|
22
23
|
还必须逐张填写 `reference_bindings` 的槽位、顺序、路径、中文名、用途以及允许/禁止控制范围;
|
|
@@ -75,11 +76,13 @@ python3 <本技能目录>/scripts/production_tool.py prepare <project> --job <jo
|
|
|
75
76
|
python3 <本技能目录>/scripts/production_tool.py confirm <project> --job-id <id> --confirmation "CONFIRM <id> <code>"
|
|
76
77
|
python3 <本技能目录>/scripts/production_tool.py run <project> --job-id <id> --adapter-config <outside-project-config.json>
|
|
77
78
|
python3 <本技能目录>/scripts/production_tool.py status <project> --job-id <id>
|
|
79
|
+
python3 <本技能目录>/scripts/production_tool.py collect <project> --job-id <id> --adapter-config <outside-project-config.json>
|
|
78
80
|
python3 <本技能目录>/scripts/production_tool.py audit <project>
|
|
79
81
|
```
|
|
80
82
|
|
|
81
83
|
`prepare` 只验证并预览,不生产。`confirm` 只保存与当前 job 指纹绑定的一次性确认。
|
|
82
|
-
`run` 才启动 adapter。`
|
|
84
|
+
`run` 才启动 adapter。`collect` 只取回**已经提交过、已经计费**的那次任务的结果,
|
|
85
|
+
不重新提交、也不需要新的确认——见下面「中断不等于要重跑」。`audit` 只对账本地任务历史、失败后恢复、重复内容尝试和当前输出字节,
|
|
83
86
|
不会调用供应商,也不把技术成功、文件存在或哈希一致写成媒体质量结论。同一 job 存在未决
|
|
84
87
|
`running` attempt 时禁止重新 prepare、confirm 或 run;先等待完成或排查遗留 attempt。
|
|
85
88
|
|
|
@@ -97,8 +100,8 @@ python3 <本技能目录>/scripts/production_tool.py audit <project>
|
|
|
97
100
|
- **tts**:从 `剧本.md` 读取原句与表演要求,声音参考由用户或现有媒体明确提供。不得在生产 job
|
|
98
101
|
中改词,也不为 TTS 新建第六份创作文档。
|
|
99
102
|
- **music**:读取 `视频提示词.md` 中创作者已确认的时间线音乐章节;主题曲使用已确认歌词,纯配乐
|
|
100
|
-
|
|
101
|
-
|
|
103
|
+
不携带歌词。供应商不能精确承诺时长时,生成源音轨后仍由 `$short-drama-edit` 按文档里的混音意图完成落点、
|
|
104
|
+
循环、淡入淡出和对白 ducking。
|
|
102
105
|
|
|
103
106
|
一个 job 不混合 modality。大批量工作拆成创作者能看清数量和成本边界的小 job;不为方便把整季
|
|
104
107
|
隐式塞进一次确认。
|
|
@@ -124,6 +127,9 @@ adapter 配置必须在项目外,只包含 argv 命令和超时;凭据由 ad
|
|
|
124
127
|
固定高保真引用并校验尺寸、格式与透明背景限制。
|
|
125
128
|
- [MiniMax Music](references/providers/minimax-music.md):使用 `music-3.0` 与 hex 结果,区分主题曲
|
|
126
129
|
和纯配乐,不伪造时长请求字段。
|
|
130
|
+
- [MiniMax 语音](references/providers/minimax-speech.md):`tts` 模态;模型与 `voice_id` 都必须
|
|
131
|
+
由账号与文档显式给出,不内置音色清单。只从预置音色合成,不做声音克隆——克隆是授权问题,
|
|
132
|
+
创作者授权的参考录音按普通创作者输入走 `输入/`。
|
|
127
133
|
- [MiniMax H3 视频](references/providers/minimax-h3-video.md):模型 ID、分辨率集合与时长区间必须
|
|
128
134
|
由账号显式配置;提示词进 `content` 的 text 项,参考图按显式 role 绑定,本地参考在没有可信上传时
|
|
129
135
|
fail closed。该模型与画面同一次生成声音,写法影响见视频提示词技能的目标模型能力档案。
|
|
@@ -133,6 +139,23 @@ adapter 配置必须在项目外,只包含 argv 命令和超时;凭据由 ad
|
|
|
133
139
|
|
|
134
140
|
仓库自带 `fixture_adapter.py` 只用于离线测试,不代表真实生成质量或默认生产 adapter。
|
|
135
141
|
|
|
142
|
+
## 中断不等于要重跑
|
|
143
|
+
|
|
144
|
+
视频任务在**提交那一刻**就已经计费,不是在拿到结果时。提交之后的一切——轮询几分钟、下载——
|
|
145
|
+
都可能被进程被杀、断网、机器休眠打断。内置 adapter 因此在拿到供应商任务 ID 的第一时间就把它
|
|
146
|
+
写进 `handle_path`(早于第一次轮询),这个路径不随本次尝试一起删除。
|
|
147
|
+
|
|
148
|
+
于是中断之后有三条确定的动作,不必再花一次钱:
|
|
149
|
+
|
|
150
|
+
1. `audit` 会把带着任务 ID 的未完成尝试报成 `orphaned_provider_job`,
|
|
151
|
+
`action` 是 `collect_before_retry`;
|
|
152
|
+
2. `collect` 用那个 ID 取回结果并把这次尝试标成成功;
|
|
153
|
+
3. 只有在 `collect` 也确认那边确实失败之后,才走重新确认与重投的老路。
|
|
154
|
+
|
|
155
|
+
**不要在 `audit` 报出 `orphaned_provider_job` 时直接重投**——那是在为同一个镜头付第二次钱。
|
|
156
|
+
`collect` 不走确认闸门是有意的:闸门防的是意外花钱,而 collect 不花钱;
|
|
157
|
+
如果它也要求重新确认,那么中断之后最省事的路径就变成再付一次,正好是闸门要防的事。
|
|
158
|
+
|
|
136
159
|
## 结果与复核
|
|
137
160
|
|
|
138
161
|
成功后回报实际输出路径、媒体类型和运行状态;不要把“adapter 返回成功”写成质量结论。
|
|
@@ -145,6 +168,8 @@ adapter 配置必须在项目外,只包含 argv 命令和超时;凭据由 ad
|
|
|
145
168
|
构图与角色一致、画面本身合规的图;音频被拒重录那句台词。改动写进新的 job 重新 prepare,让创
|
|
146
169
|
作者在预览里看到改的是哪一项再确认;原样重投的那次确认不产生修复,只产生一笔费用。重复内容
|
|
147
170
|
缺陷回到对应 prompt/spec owner。
|
|
171
|
+
生产结束后不自动进入剪辑。素材要装配成成片时由用户点名 `$short-drama-edit`;它只取舍已有帧,
|
|
172
|
+
不回头改本阶段的 job,也不生成新素材。
|
|
148
173
|
如需质量复核,报告可把已有结果另行交给 `$short-drama-review`;不要在生产调用中自动启动复核。
|
|
149
174
|
Dashboard 只负责展示这些文件和运行摘要,不提供 adapter 设置或生产按钮。
|
|
150
175
|
|
|
@@ -99,6 +99,12 @@ Keep this file outside the project:
|
|
|
99
99
|
`command` is an argv array, never a shell string. Timeout is 1–3600 seconds. Do not put credentials in this file;
|
|
100
100
|
let the adapter read its environment or operating-system credential store.
|
|
101
101
|
|
|
102
|
+
## Capability sources
|
|
103
|
+
|
|
104
|
+
Use the provider's current API reference for roles, limits and supported combinations.
|
|
105
|
+
A relay can expose a different subset, reject inputs itself or transform requests.
|
|
106
|
+
Record the tested execution path and distinguish its observations from the native API contract.
|
|
107
|
+
|
|
102
108
|
## Adapter stdin
|
|
103
109
|
|
|
104
110
|
The adapter receives one UTF-8 JSON document as raw stdin bytes. Read the
|
|
@@ -113,6 +119,22 @@ on the machine locale. The document contains the confirmed job plus:
|
|
|
113
119
|
production tool. Every adapter output source must be a direct regular-file
|
|
114
120
|
child of this directory. The whole directory is deleted after success or
|
|
115
121
|
failure.
|
|
122
|
+
- `handle_path`: absolute path where the adapter records the provider's task id
|
|
123
|
+
**as soon as it has one, before it starts polling** — write
|
|
124
|
+
`{"provider_job_id": "<id>"}`, atomically. Unlike `output_root` this path
|
|
125
|
+
survives the attempt.
|
|
126
|
+
- `collect_provider_job_id`: present only on a collect call. When it is set the
|
|
127
|
+
adapter must **not submit anything**; it polls and downloads that existing
|
|
128
|
+
task and returns its outputs as usual.
|
|
129
|
+
|
|
130
|
+
## Recovering a submitted task
|
|
131
|
+
|
|
132
|
+
Record the provider task ID before polling so an interrupted task can be collected without resubmission.
|
|
133
|
+
A local handle-write failure is non-fatal after submission. The tool copies the handle into the attempt
|
|
134
|
+
record on success or failure; `audit` reports unfinished tasks as `orphaned_provider_job` with
|
|
135
|
+
`action: collect_before_retry`.
|
|
136
|
+
|
|
137
|
+
`production_tool.py collect` retrieves the existing task without new submission or confirmation.
|
|
116
138
|
|
|
117
139
|
It may translate provider-neutral parameters into its chosen SDK/API. Optional
|
|
118
140
|
provider adapters under `scripts/` document and implement known translations;
|
|
@@ -31,6 +31,12 @@ required and are only accepted when the runtime profile above permits their valu
|
|
|
31
31
|
optional for reference-conditioned jobs and **required** for text-to-video, where `adaptive` is
|
|
32
32
|
refused because there is no reference frame to adapt to.
|
|
33
33
|
|
|
34
|
+
The adapter validates and forwards `duration`; it does not estimate speech length or retime dialogue.
|
|
35
|
+
Before preparing a speaking shot, compare the accepted shot duration, the prompt's spoken timeline,
|
|
36
|
+
and the job's `duration`. Resolve insufficient speaking time in the storyboard and regenerate the
|
|
37
|
+
dependent prompt before preparing the job. Prompt timestamps alone do not change the request duration.
|
|
38
|
+
The official video API has no separate speech-rate parameter.
|
|
39
|
+
|
|
34
40
|
The prompt is compiled into one `text` item of the multimodal `content` array and is refused above
|
|
35
41
|
7000 characters. Each declared reference becomes one further `content` item carrying an explicit
|
|
36
42
|
`role`: `first_frame`, `last_frame`, `reference_image`, `reference_video`, or `reference_audio`.
|
|
@@ -68,18 +74,13 @@ structured: base/first-frame/first-last-frame tasks use `integrated_multimodal_d
|
|
|
68
74
|
form. Chinese dialogue remains exact inside `<d>[Chinese] ...</d>`. Full details live in the
|
|
69
75
|
video-prompt skill's `references/minimax-h3.md`.
|
|
70
76
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
- an unstated audio channel is not a silent one. A shot that says nothing about music or speech can
|
|
77
|
-
come back with invented score and invented lines, so the shot's own sound intent, including what
|
|
78
|
-
must **not** be produced, has to be in the copyable body;
|
|
79
|
-
- readable on-screen text is likewise produced unless it is excluded, so a shot with no readable-text
|
|
80
|
-
obligation states that exclusion instead of relying on the model to omit it.
|
|
77
|
+
State the intended sound layers in the prompt. For no non-diegetic music, the
|
|
78
|
+
H3 dialect uses `non_diegetic_music: N/A`; small probes observed unwanted music
|
|
79
|
+
when this layer was omitted. They did not establish that omitted speech or
|
|
80
|
+
negative clauses cause extra dialogue.
|
|
81
81
|
|
|
82
|
-
|
|
83
|
-
the
|
|
82
|
+
Follow the project's subtitle and in-scene-text policy. Prompt wording belongs
|
|
83
|
+
to the video-prompt skill and the declared language, not to adapter-generated
|
|
84
|
+
boilerplate.
|
|
84
85
|
|
|
85
86
|
Protocol reference: [MiniMax video generation API](https://platform.minimax.io/docs/api-reference/video-generation-v2-create).
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# MiniMax Speech adapter
|
|
2
|
+
|
|
3
|
+
Adapter command:
|
|
4
|
+
|
|
5
|
+
```json
|
|
6
|
+
{"command": ["python3", "/absolute/path/provider_adapters.py", "minimax-speech"], "timeout_seconds": 600}
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Required environment: `MINIMAX_API_KEY`. `MINIMAX_BASE_URL` optionally overrides the default
|
|
10
|
+
`https://api.minimax.io/v1` and must remain HTTPS.
|
|
11
|
+
|
|
12
|
+
The job uses the suite's `tts` modality and must have exactly one output. The production prompt is the
|
|
13
|
+
line to be spoken, verbatim — not a description of it. Supported public parameters are `model`,
|
|
14
|
+
`voice_id`, `emotion`, `speed`, `vol`, `pitch`, `sample_rate`, `bitrate`, and `format`. The requested
|
|
15
|
+
format must match the target extension and be `mp3` or `wav`.
|
|
16
|
+
|
|
17
|
+
`model` and `voice_id` are both required and neither has a default. The model is an account-enabled
|
|
18
|
+
endpoint, exactly as for the video providers. The voice is a creative decision recorded in
|
|
19
|
+
`视觉设定.md` under the character's 声音方向, and it reaches the adapter through the confirmed job.
|
|
20
|
+
|
|
21
|
+
## Voice selection
|
|
22
|
+
|
|
23
|
+
Read the provider's voice listing and confirm availability for the chosen model and account.
|
|
24
|
+
The adapter validates the ID format, not catalogue membership. Record the selected ID in the
|
|
25
|
+
character's 声音方向 and pass it through the confirmed job.
|
|
26
|
+
|
|
27
|
+
This workflow selects preset voices. The adapter does not enrol or clone a voice;
|
|
28
|
+
any supplied recording retains its documented authorization and usage scope.
|
|
29
|
+
|
|
30
|
+
## Request shape
|
|
31
|
+
|
|
32
|
+
The request is non-streaming with `output_format: hex`, `POST /t2a_v2`, and carries
|
|
33
|
+
`voice_setting` (`voice_id`, and any of `emotion`, `speed`, `vol`, `pitch`) alongside `audio_setting`
|
|
34
|
+
(`sample_rate`, `bitrate`, `format`). The adapter validates `base_resp.status_code`, decodes
|
|
35
|
+
`data.audio` as hexadecimal bytes, and writes it to a private temporary file.
|
|
36
|
+
|
|
37
|
+
`emotion` accepts the provider's seven values: `happy`, `sad`, `angry`, `fearful`, `disgusted`,
|
|
38
|
+
`surprised`, `neutral`. Anything outside that set is refused rather than passed through — a rejected
|
|
39
|
+
emotion is a typo caught before it is paid for.
|
|
40
|
+
|
|
41
|
+
Protocol reference: [MiniMax Text to Speech](https://platform.minimax.io/docs/api-reference/speech-t2a-http).
|
|
@@ -29,6 +29,13 @@ profile above explicitly permits their values, then sent as the API's top-level
|
|
|
29
29
|
`generate_audio` is a boolean. The broad compiler envelope remains configurable rather than proving
|
|
30
30
|
that every Seedance release supports every value.
|
|
31
31
|
|
|
32
|
+
The compiler also forwards `duration: -1` without applying the positive-integer range. For ordinary
|
|
33
|
+
Seedance 2.0/2.5 generation this asks the model to choose within its native range; for 2.5 `edit` it
|
|
34
|
+
preserves the source video's duration instead. The adapter does not estimate spoken dialogue or
|
|
35
|
+
lengthen a short job. Fixed-duration delivery needs an explicit accepted duration; auto-duration
|
|
36
|
+
output needs its actual media length and speech checked before timing is accepted. The API response's
|
|
37
|
+
integer `duration` rounds down the frame-based duration, so it is not a precise media measurement.
|
|
38
|
+
|
|
32
39
|
`compile_seedance_payload` accepts explicit `reference_image`, `reference_video`, and
|
|
33
40
|
`reference_audio` roles with matching HTTPS, `asset://` or base64 data URI values. It emits the matching multimodal
|
|
34
41
|
content type and uses `@图片N`、`@视频N`、`@音频N` in its appended reference contract. The bundled
|
|
@@ -27,8 +27,22 @@ PNG = base64.b64decode(
|
|
|
27
27
|
def main() -> int:
|
|
28
28
|
parser = argparse.ArgumentParser()
|
|
29
29
|
parser.add_argument("--fail", action="store_true")
|
|
30
|
+
# A real video provider bills at submission and can be interrupted at any
|
|
31
|
+
# point afterwards. `--submit-then-fail` reproduces exactly that: record the
|
|
32
|
+
# handle the way a live adapter does, then die before returning anything.
|
|
33
|
+
parser.add_argument("--submit-then-fail", action="store_true")
|
|
30
34
|
args = parser.parse_args()
|
|
31
35
|
job = json.load(sys.stdin.buffer)
|
|
36
|
+
collecting = job.get("collect_provider_job_id")
|
|
37
|
+
if args.submit_then_fail and not collecting:
|
|
38
|
+
destination = job.get("handle_path")
|
|
39
|
+
if isinstance(destination, str) and destination:
|
|
40
|
+
path = Path(destination)
|
|
41
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
42
|
+
path.write_text(
|
|
43
|
+
json.dumps({"provider_job_id": "fixture-task-1"}), encoding="utf-8"
|
|
44
|
+
)
|
|
45
|
+
return 7
|
|
32
46
|
if args.fail:
|
|
33
47
|
return 7
|
|
34
48
|
directory_raw = job.get("output_root")
|
|
@@ -52,7 +66,10 @@ def main() -> int:
|
|
|
52
66
|
handle.setframerate(8000)
|
|
53
67
|
handle.writeframes(struct.pack("<h", 0) * 80)
|
|
54
68
|
outputs.append({"target": target, "source": str(path)})
|
|
55
|
-
|
|
69
|
+
response = {"outputs": outputs, "provider_job_id": "fixture-local"}
|
|
70
|
+
if isinstance(collecting, str) and collecting:
|
|
71
|
+
response["provider_job_id"] = collecting
|
|
72
|
+
json.dump(response, sys.stdout)
|
|
56
73
|
return 0
|
|
57
74
|
|
|
58
75
|
|
|
@@ -1547,6 +1547,37 @@ def _active_run(root: Path, job_id: str) -> dict[str, Any] | None:
|
|
|
1547
1547
|
return running[-1] if running else None
|
|
1548
1548
|
|
|
1549
1549
|
|
|
1550
|
+
def _read_provider_handle(root: Path, job_id: str, run_id: str) -> str | None:
|
|
1551
|
+
"""The provider job id an adapter recorded before it started polling.
|
|
1552
|
+
|
|
1553
|
+
A video task is billed the moment it is submitted, not when it is collected.
|
|
1554
|
+
If the adapter is then interrupted — killed, disconnected, the machine
|
|
1555
|
+
sleeps — the run record would otherwise say `running` with no way back to a
|
|
1556
|
+
task that is alive and already paid for, and the only move left is to submit
|
|
1557
|
+
(and pay) again. The adapter writes the id here as soon as it has one, into
|
|
1558
|
+
project metadata rather than the attempt's temporary directory, so it
|
|
1559
|
+
outlives the process that created it.
|
|
1560
|
+
"""
|
|
1561
|
+
|
|
1562
|
+
try:
|
|
1563
|
+
document = _metadata_read_json(
|
|
1564
|
+
root, ("handles", _job_key(job_id)), f"{run_id}.json",
|
|
1565
|
+
maximum=MAX_RUN_RECORD_BYTES,
|
|
1566
|
+
)
|
|
1567
|
+
except (FileNotFoundError, ValueError, json.JSONDecodeError):
|
|
1568
|
+
return None
|
|
1569
|
+
if not isinstance(document, Mapping):
|
|
1570
|
+
return None
|
|
1571
|
+
value = document.get("provider_job_id")
|
|
1572
|
+
if isinstance(value, str) and 0 < len(value) <= 200:
|
|
1573
|
+
return value
|
|
1574
|
+
return None
|
|
1575
|
+
|
|
1576
|
+
|
|
1577
|
+
def _handle_metadata_path(root: Path, job_id: str, run_id: str) -> Path:
|
|
1578
|
+
return root / PRODUCTION_ROOT / "handles" / _job_key(job_id) / f"{run_id}.json"
|
|
1579
|
+
|
|
1580
|
+
|
|
1550
1581
|
def _write_run(root: Path, job_id: str, run: Mapping[str, Any]) -> None:
|
|
1551
1582
|
_metadata_atomic_json(
|
|
1552
1583
|
root,
|
|
@@ -1620,6 +1651,9 @@ def run_job(root: Path, *, job_id: str, adapter_config: Path) -> dict[str, Any]:
|
|
|
1620
1651
|
"run_id": run_id,
|
|
1621
1652
|
"project_root": str(snapshot_root),
|
|
1622
1653
|
"output_root": str(output_root),
|
|
1654
|
+
# Where to record the provider job id the instant it exists, so
|
|
1655
|
+
# an interrupted attempt can be collected instead of re-billed.
|
|
1656
|
+
"handle_path": str(_handle_metadata_path(root, job_id, run_id)),
|
|
1623
1657
|
}
|
|
1624
1658
|
)
|
|
1625
1659
|
try:
|
|
@@ -1640,11 +1674,20 @@ def run_job(root: Path, *, job_id: str, adapter_config: Path) -> dict[str, Any]:
|
|
|
1640
1674
|
"sha256": digest,
|
|
1641
1675
|
}
|
|
1642
1676
|
)
|
|
1677
|
+
if _run_status(root, job_id, run_id) == "succeeded":
|
|
1678
|
+
# `collect` reached the same provider task first and its
|
|
1679
|
+
# outputs are already in the project. Leave that record
|
|
1680
|
+
# alone rather than replacing a collected attempt.
|
|
1681
|
+
raise RuntimeError(
|
|
1682
|
+
"这次尝试已经被 collect 取回并标为成功;本次运行不覆盖它"
|
|
1683
|
+
)
|
|
1643
1684
|
run["status"] = "succeeded"
|
|
1644
1685
|
run["finished_at"] = utc_now()
|
|
1645
1686
|
run["outputs"] = written
|
|
1646
|
-
provider_job_id = response.get("provider_job_id")
|
|
1647
|
-
|
|
1687
|
+
provider_job_id = response.get("provider_job_id") or _read_provider_handle(
|
|
1688
|
+
root, job_id, run_id
|
|
1689
|
+
)
|
|
1690
|
+
if isinstance(provider_job_id, str) and 0 < len(provider_job_id) <= 200:
|
|
1648
1691
|
run["provider_job_id"] = provider_job_id
|
|
1649
1692
|
_write_run(root, job_id, run)
|
|
1650
1693
|
except Exception as exc:
|
|
@@ -1653,6 +1696,12 @@ def run_job(root: Path, *, job_id: str, adapter_config: Path) -> dict[str, Any]:
|
|
|
1653
1696
|
run["finished_at"] = utc_now()
|
|
1654
1697
|
if isinstance(exc, AdapterError) and exc.public_error is not None:
|
|
1655
1698
|
run["error"] = exc.public_error
|
|
1699
|
+
# A timeout or a killed adapter does not cancel the provider's
|
|
1700
|
+
# task. Carry the handle onto the failed record so `audit` can
|
|
1701
|
+
# say "collect this" instead of the creator paying twice.
|
|
1702
|
+
orphan = _read_provider_handle(root, job_id, run_id)
|
|
1703
|
+
if orphan is not None:
|
|
1704
|
+
run["provider_job_id"] = orphan
|
|
1656
1705
|
_write_run(root, job_id, run)
|
|
1657
1706
|
raise
|
|
1658
1707
|
return {
|
|
@@ -1663,6 +1712,101 @@ def run_job(root: Path, *, job_id: str, adapter_config: Path) -> dict[str, Any]:
|
|
|
1663
1712
|
}
|
|
1664
1713
|
|
|
1665
1714
|
|
|
1715
|
+
def _run_status(root: Path, job_id: str, run_id: str) -> str | None:
|
|
1716
|
+
"""The status one attempt currently has on disk, re-read rather than cached."""
|
|
1717
|
+
|
|
1718
|
+
for run in _read_run_history(root, job_id):
|
|
1719
|
+
if str(run.get("run_id")) == run_id:
|
|
1720
|
+
status = run.get("status")
|
|
1721
|
+
return status if isinstance(status, str) else None
|
|
1722
|
+
return None
|
|
1723
|
+
|
|
1724
|
+
|
|
1725
|
+
def collect_job(root: Path, *, job_id: str, adapter_config: Path) -> dict[str, Any]:
|
|
1726
|
+
"""Collect an already-submitted task's result without submitting a new one.
|
|
1727
|
+
|
|
1728
|
+
This is deliberately outside the confirmation gate. The gate exists to stop
|
|
1729
|
+
an unintended charge; collecting costs nothing, because the charge already
|
|
1730
|
+
happened when the task was submitted. Requiring a fresh confirmation here
|
|
1731
|
+
would mean the cheapest way out of an interrupted attempt is to pay again,
|
|
1732
|
+
which is exactly the outcome the gate is meant to prevent.
|
|
1733
|
+
"""
|
|
1734
|
+
|
|
1735
|
+
root = find_project(root)
|
|
1736
|
+
with tempfile.TemporaryDirectory(prefix="short-drama-collect-") as directory:
|
|
1737
|
+
output_root = Path(directory) / "outputs"
|
|
1738
|
+
output_root.mkdir(parents=True)
|
|
1739
|
+
with _project_lock(root):
|
|
1740
|
+
job = _read_job(root, job_id)
|
|
1741
|
+
command, timeout = _load_adapter(adapter_config, str(job["adapter"]), root)
|
|
1742
|
+
target_run = None
|
|
1743
|
+
for run in reversed(_read_run_history(root, job_id)):
|
|
1744
|
+
if run.get("status") == "succeeded":
|
|
1745
|
+
continue
|
|
1746
|
+
handle = run.get("provider_job_id") or _read_provider_handle(
|
|
1747
|
+
root, job_id, str(run["run_id"])
|
|
1748
|
+
)
|
|
1749
|
+
if handle:
|
|
1750
|
+
target_run = (run, handle)
|
|
1751
|
+
break
|
|
1752
|
+
if target_run is None:
|
|
1753
|
+
raise RuntimeError(
|
|
1754
|
+
"no unfinished attempt of this job carries a provider job id; "
|
|
1755
|
+
"there is nothing to collect"
|
|
1756
|
+
)
|
|
1757
|
+
run, provider_job_id = target_run
|
|
1758
|
+
|
|
1759
|
+
payload = {key: job[key] for key in ALLOWED_JOB_KEYS if key in job}
|
|
1760
|
+
payload.update(
|
|
1761
|
+
{
|
|
1762
|
+
"run_id": str(run["run_id"]),
|
|
1763
|
+
"output_root": str(output_root),
|
|
1764
|
+
"collect_provider_job_id": provider_job_id,
|
|
1765
|
+
}
|
|
1766
|
+
)
|
|
1767
|
+
response = _run_adapter(command, timeout, payload, root)
|
|
1768
|
+
adapter_outputs = _validate_adapter_outputs(job, response, output_root)
|
|
1769
|
+
written: list[dict[str, Any]] = []
|
|
1770
|
+
with _project_lock(root):
|
|
1771
|
+
# A live `run_job` on the same provider task is indistinguishable
|
|
1772
|
+
# from a dead one — both sit at `running`. Re-read before writing,
|
|
1773
|
+
# or two writers race onto the same bytes and one record is lost.
|
|
1774
|
+
if _run_status(root, job_id, str(run["run_id"])) == "succeeded":
|
|
1775
|
+
raise RuntimeError(
|
|
1776
|
+
"这次尝试在取回期间已经自己完成了,产物已经落进项目;"
|
|
1777
|
+
"没有覆盖它——先看 status,确认无误就不需要再做别的"
|
|
1778
|
+
)
|
|
1779
|
+
for target_name, source in adapter_outputs:
|
|
1780
|
+
target = _project_file(root, target_name, create_parent=True)
|
|
1781
|
+
digest, size = _copy_output(
|
|
1782
|
+
source, target, overwrite=bool(job["overwrite"])
|
|
1783
|
+
)
|
|
1784
|
+
written.append(
|
|
1785
|
+
{
|
|
1786
|
+
"path": target_name,
|
|
1787
|
+
"media_type": MEDIA_TYPES[PurePosixPath(target_name).suffix.casefold()],
|
|
1788
|
+
"bytes": size,
|
|
1789
|
+
"sha256": digest,
|
|
1790
|
+
}
|
|
1791
|
+
)
|
|
1792
|
+
collected = dict(run)
|
|
1793
|
+
collected["status"] = "succeeded"
|
|
1794
|
+
collected["finished_at"] = utc_now()
|
|
1795
|
+
collected["outputs"] = written
|
|
1796
|
+
collected["provider_job_id"] = provider_job_id
|
|
1797
|
+
collected["collected"] = True
|
|
1798
|
+
collected.pop("error", None)
|
|
1799
|
+
_write_run(root, job_id, collected)
|
|
1800
|
+
return {
|
|
1801
|
+
"job_id": job_id,
|
|
1802
|
+
"run_id": str(run["run_id"]),
|
|
1803
|
+
"state": "succeeded",
|
|
1804
|
+
"collected": True,
|
|
1805
|
+
"provider_job_id": provider_job_id,
|
|
1806
|
+
"outputs": written,
|
|
1807
|
+
}
|
|
1808
|
+
|
|
1809
|
+
|
|
1666
1810
|
def job_status(root: Path, *, job_id: str) -> dict[str, Any]:
|
|
1667
1811
|
root = find_project(root)
|
|
1668
1812
|
job = _read_job(root, job_id)
|
|
@@ -1816,6 +1960,28 @@ def audit_project(root: Path) -> dict[str, Any]:
|
|
|
1816
1960
|
attempts_failed += sum(run["status"] == "failed" for run in completed)
|
|
1817
1961
|
attempts_running += len(running)
|
|
1818
1962
|
attempts_superseded += len(history) - len(current)
|
|
1963
|
+
for run in current:
|
|
1964
|
+
if run.get("status") == "succeeded":
|
|
1965
|
+
continue
|
|
1966
|
+
handle = run.get("provider_job_id") or _read_provider_handle(
|
|
1967
|
+
root, job_id, str(run["run_id"])
|
|
1968
|
+
)
|
|
1969
|
+
if not handle:
|
|
1970
|
+
continue
|
|
1971
|
+
problems.append(
|
|
1972
|
+
{
|
|
1973
|
+
"code": "orphaned_provider_job",
|
|
1974
|
+
"job_id": job_id,
|
|
1975
|
+
"run_id": run["run_id"],
|
|
1976
|
+
"provider_job_id": handle,
|
|
1977
|
+
"action": "collect_before_retry",
|
|
1978
|
+
# A live attempt and a dead one both sit at `running`, so
|
|
1979
|
+
# this finding cannot tell them apart. Collecting is still
|
|
1980
|
+
# the right first move — it is the only one that costs
|
|
1981
|
+
# nothing — and it refuses safely if the attempt finished.
|
|
1982
|
+
"note": "status 为 running 时它可能仍在正常轮询;collect 会先确认再写",
|
|
1983
|
+
}
|
|
1984
|
+
)
|
|
1819
1985
|
if running:
|
|
1820
1986
|
running_jobs += 1
|
|
1821
1987
|
for run in running:
|
|
@@ -1986,6 +2152,13 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
1986
2152
|
run.add_argument("project")
|
|
1987
2153
|
run.add_argument("--job-id", required=True)
|
|
1988
2154
|
run.add_argument("--adapter-config", required=True)
|
|
2155
|
+
collect = commands.add_parser(
|
|
2156
|
+
"collect",
|
|
2157
|
+
help="Fetch the result of an already-submitted provider task without paying again.",
|
|
2158
|
+
)
|
|
2159
|
+
collect.add_argument("project")
|
|
2160
|
+
collect.add_argument("--job-id", required=True)
|
|
2161
|
+
collect.add_argument("--adapter-config", required=True)
|
|
1989
2162
|
status = commands.add_parser("status", help="Show one media job state.")
|
|
1990
2163
|
status.add_argument("project")
|
|
1991
2164
|
status.add_argument("--job-id", required=True)
|
|
@@ -2011,6 +2184,12 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
2011
2184
|
job_id=args.job_id,
|
|
2012
2185
|
adapter_config=Path(args.adapter_config),
|
|
2013
2186
|
)
|
|
2187
|
+
elif args.command == "collect":
|
|
2188
|
+
result = collect_job(
|
|
2189
|
+
Path(args.project),
|
|
2190
|
+
job_id=args.job_id,
|
|
2191
|
+
adapter_config=Path(args.adapter_config),
|
|
2192
|
+
)
|
|
2014
2193
|
elif args.command == "status":
|
|
2015
2194
|
result = job_status(Path(args.project), job_id=args.job_id)
|
|
2016
2195
|
else:
|