@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
|
@@ -0,0 +1,520 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Offline self-test for cut-list parsing and mechanical checks.
|
|
3
|
+
|
|
4
|
+
Runs without ffmpeg: every assertion here is about the document, not the media.
|
|
5
|
+
The media-dependent paths are exercised by the suite's tests instead.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import re
|
|
11
|
+
import sys
|
|
12
|
+
import tempfile
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
16
|
+
|
|
17
|
+
from edit_tool import ( # noqa: E402
|
|
18
|
+
_unaccounted_shots,
|
|
19
|
+
DEFAULT_REMOTION_CONCURRENCY,
|
|
20
|
+
REMOTION_SOURCE,
|
|
21
|
+
REMOTION_SOURCE_FILES,
|
|
22
|
+
EditError,
|
|
23
|
+
_ass_text,
|
|
24
|
+
_build_ass,
|
|
25
|
+
_shot_match_filter,
|
|
26
|
+
_subtitle_cues,
|
|
27
|
+
check_cuts,
|
|
28
|
+
parse_cut_list,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
MINIMUM_PYTHON = (3, 9)
|
|
32
|
+
if sys.version_info < MINIMUM_PYTHON:
|
|
33
|
+
raise SystemExit("selftest.py requires Python 3.9 or newer")
|
|
34
|
+
|
|
35
|
+
MOTION = """# EP001 视频提示词
|
|
36
|
+
|
|
37
|
+
## MOTION-EP001-010 · 手指停在发布键上
|
|
38
|
+
|
|
39
|
+
## MOTION-EP001-011 · 认证弹窗展开
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
SCREENPLAY = """# EP001
|
|
43
|
+
|
|
44
|
+
## EP001-SC003 内 · 房间 · 夜
|
|
45
|
+
|
|
46
|
+
角色甲:这条路我自己走。
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
CUT_LIST = """# EP001 剪辑单
|
|
50
|
+
|
|
51
|
+
- 成片目标时长:5.00 秒
|
|
52
|
+
- 交付响度:-16 LUFS
|
|
53
|
+
- 未采用镜头:MOTION-EP001-009(理由:文件缺失——尚未生产)
|
|
54
|
+
|
|
55
|
+
## CUT-EP001-001 · 手指停在发布键上
|
|
56
|
+
|
|
57
|
+
- 来源:MOTION-EP001-010 · media/010.mp4
|
|
58
|
+
- 入点:0.60
|
|
59
|
+
- 出点:3.60
|
|
60
|
+
- 时长:3.00
|
|
61
|
+
- 取舍:入点=手指已经在下压;出点=界面开始响应
|
|
62
|
+
- 声音:保留原声
|
|
63
|
+
- 字幕:无
|
|
64
|
+
|
|
65
|
+
## CUT-EP001-002 · 那句话
|
|
66
|
+
|
|
67
|
+
- 来源:MOTION-EP001-011 · media/011.mp4
|
|
68
|
+
- 入点:0.00
|
|
69
|
+
- 出点:2.00
|
|
70
|
+
- 时长:2.00
|
|
71
|
+
- 取舍:入点=文件起点;出点=句尾收音后
|
|
72
|
+
- 声音:保留原声
|
|
73
|
+
- 字幕:这条路我自己走
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def require(condition: bool, message: str) -> None:
|
|
78
|
+
if not condition:
|
|
79
|
+
raise AssertionError(message)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def build(root: Path, cut_list: str) -> Path:
|
|
83
|
+
episode = root / "剧集" / "EP001"
|
|
84
|
+
(episode / "media").mkdir(parents=True)
|
|
85
|
+
(episode / "视频提示词.md").write_text(MOTION, encoding="utf-8")
|
|
86
|
+
(episode / "剧本.md").write_text(SCREENPLAY, encoding="utf-8")
|
|
87
|
+
(episode / "剪辑单.md").write_text(cut_list, encoding="utf-8")
|
|
88
|
+
for name in ("010.mp4", "011.mp4"):
|
|
89
|
+
(episode / "media" / name).write_bytes(b"")
|
|
90
|
+
return episode
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def check_subtitle_geometry() -> None:
|
|
94
|
+
"""FontSize is already in PlayRes units; scaling it by the frame twice ruins it.
|
|
95
|
+
|
|
96
|
+
Letting ffmpeg convert an SRT hands libass a 384-high canvas and every size
|
|
97
|
+
is then multiplied by height/384 on the way to the frame. Computing the size
|
|
98
|
+
from the real height on top of that produced type a third of the frame wide,
|
|
99
|
+
sitting in the middle of the picture with both ends of the line cut off — and
|
|
100
|
+
it shipped, because the render after the style change was checked with
|
|
101
|
+
numbers instead of a frame.
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
width, height = 768, 1344
|
|
105
|
+
ass = _build_ass([(1.0, 2.0, "这条路我自己走")], width, height)
|
|
106
|
+
require(f"PlayResX: {width}" in ass, "PlayResX 必须等于画面宽")
|
|
107
|
+
require(f"PlayResY: {height}" in ass, "PlayResY 必须等于画面高")
|
|
108
|
+
style = next(line for line in ass.splitlines() if line.startswith("Style:"))
|
|
109
|
+
fields = style.split(":", 1)[1].split(",")
|
|
110
|
+
font_size = float(fields[2])
|
|
111
|
+
require(
|
|
112
|
+
height * 0.02 <= font_size <= height * 0.06,
|
|
113
|
+
f"字号 {font_size} 不在画面高度的 2%–6% 之间",
|
|
114
|
+
)
|
|
115
|
+
margin_v = float(fields[21])
|
|
116
|
+
require(margin_v < height * 0.2, f"底边距 {margin_v} 会把字幕推离安全区")
|
|
117
|
+
margin_h = float(fields[19])
|
|
118
|
+
require(margin_h > 0, "左右边距为 0 时长句会顶到画面边缘")
|
|
119
|
+
require("这条路我自己走" in ass, "台词原文必须原样进 ASS")
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def check_subtitle_timing() -> None:
|
|
123
|
+
"""Cues follow the segments that were written, not the numbers in the document.
|
|
124
|
+
|
|
125
|
+
Each segment lands on a frame boundary, so it runs a few milliseconds past
|
|
126
|
+
its declared length. Accumulating the declared numbers drifts, and the
|
|
127
|
+
subtitle leaves before the actor stops speaking.
|
|
128
|
+
"""
|
|
129
|
+
|
|
130
|
+
with tempfile.TemporaryDirectory() as scratch:
|
|
131
|
+
root = Path(scratch)
|
|
132
|
+
episode = build(root, CUT_LIST)
|
|
133
|
+
_, cuts, _ = parse_cut_list(episode / "剪辑单.md")
|
|
134
|
+
declared = [cut.end - cut.start for cut in cuts]
|
|
135
|
+
measured = [span + 0.04 for span in declared]
|
|
136
|
+
|
|
137
|
+
drifting = _subtitle_cues(cuts, declared)
|
|
138
|
+
真实 = _subtitle_cues(cuts, measured)
|
|
139
|
+
require(len(真实) == 1, f"应有一条字幕,实际 {len(真实)}")
|
|
140
|
+
require(
|
|
141
|
+
真实[0][0] > drifting[0][0],
|
|
142
|
+
"分段比声明更长时,后面的字幕必须相应后移",
|
|
143
|
+
)
|
|
144
|
+
require(
|
|
145
|
+
abs(真实[0][0] - (drifting[0][0] + 0.04)) < 1e-6,
|
|
146
|
+
"位移必须等于前面各段的实测差之和",
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def check_shot_match() -> None:
|
|
151
|
+
"""A declared correction is applied; an undeclared one never is.
|
|
152
|
+
|
|
153
|
+
Generated shots drift a stop apart, so the join reads as a mistake. The
|
|
154
|
+
correction has to be visible in the document — a tool that measured clips
|
|
155
|
+
and adjusted them on its own would be changing pictures nobody could review.
|
|
156
|
+
"""
|
|
157
|
+
|
|
158
|
+
with tempfile.TemporaryDirectory() as scratch:
|
|
159
|
+
root = Path(scratch)
|
|
160
|
+
episode = build(root, CUT_LIST)
|
|
161
|
+
_, cuts, _ = parse_cut_list(episode / "剪辑单.md")
|
|
162
|
+
require(_shot_match_filter(cuts[0]) == "", "没写画面的段不得被改动")
|
|
163
|
+
|
|
164
|
+
listed = CUT_LIST.replace(
|
|
165
|
+
"- 声音:保留原声\n- 字幕:无",
|
|
166
|
+
"- 声音:保留原声\n- 画面:亮度 +0.06;色温 -6\n- 字幕:无", 1)
|
|
167
|
+
(episode / "剪辑单.md").write_text(listed, encoding="utf-8")
|
|
168
|
+
_, cuts, _ = parse_cut_list(episode / "剪辑单.md")
|
|
169
|
+
chain = _shot_match_filter(cuts[0])
|
|
170
|
+
require("brightness=0.06" in chain, f"亮度没进滤镜链: {chain}")
|
|
171
|
+
require("colorbalance" in chain and "rm=-0.06" in chain, f"色温没进滤镜链: {chain}")
|
|
172
|
+
|
|
173
|
+
# ffmpeg 的 eq 只接受 0..3 的饱和度;负数在文档里通过、渲染时才炸。
|
|
174
|
+
negative = CUT_LIST.replace(
|
|
175
|
+
"- 声音:保留原声\n- 字幕:无",
|
|
176
|
+
"- 声音:保留原声\n- 画面:饱和 -1.5\n- 字幕:无", 1)
|
|
177
|
+
(episode / "剪辑单.md").write_text(negative, encoding="utf-8")
|
|
178
|
+
try:
|
|
179
|
+
parse_cut_list(episode / "剪辑单.md")
|
|
180
|
+
except EditError as error:
|
|
181
|
+
require("超出允许范围" in str(error), f"负饱和度报错不对: {error}")
|
|
182
|
+
else:
|
|
183
|
+
raise AssertionError("饱和 -1.5 应当被拒绝——ffmpeg 不接受负饱和度")
|
|
184
|
+
|
|
185
|
+
# 超出范围是重新调色,不是接镜,必须挡住。
|
|
186
|
+
wild = CUT_LIST.replace(
|
|
187
|
+
"- 声音:保留原声\n- 字幕:无",
|
|
188
|
+
"- 声音:保留原声\n- 画面:亮度 +0.9\n- 字幕:无", 1)
|
|
189
|
+
(episode / "剪辑单.md").write_text(wild, encoding="utf-8")
|
|
190
|
+
try:
|
|
191
|
+
parse_cut_list(episode / "剪辑单.md")
|
|
192
|
+
except EditError as error:
|
|
193
|
+
require("超出允许范围" in str(error), f"越界报错不对: {error}")
|
|
194
|
+
else:
|
|
195
|
+
raise AssertionError("亮度 +0.9 应当被拒绝")
|
|
196
|
+
|
|
197
|
+
# 写了内容却没有可执行项,是写错了,不能静默当成不校正。
|
|
198
|
+
vague = CUT_LIST.replace(
|
|
199
|
+
"- 声音:保留原声\n- 字幕:无",
|
|
200
|
+
"- 声音:保留原声\n- 画面:调暖一点\n- 字幕:无", 1)
|
|
201
|
+
(episode / "剪辑单.md").write_text(vague, encoding="utf-8")
|
|
202
|
+
try:
|
|
203
|
+
parse_cut_list(episode / "剪辑单.md")
|
|
204
|
+
except EditError as error:
|
|
205
|
+
require("没有可执行的项" in str(error), f"含混报错不对: {error}")
|
|
206
|
+
else:
|
|
207
|
+
raise AssertionError("「调暖一点」应当被拒绝")
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def check_multi_subtitle() -> None:
|
|
211
|
+
"""A shot can carry several lines, and all of them must reach the screen.
|
|
212
|
+
|
|
213
|
+
The exchange "就是什么 / 就是少了点东西 / 少了什么" is one over-shoulder shot.
|
|
214
|
+
A cut list that holds only one subtitle per cut silently drops the other
|
|
215
|
+
two: the film plays lines that never appear, and nothing reports it.
|
|
216
|
+
"""
|
|
217
|
+
|
|
218
|
+
with tempfile.TemporaryDirectory() as scratch:
|
|
219
|
+
root = Path(scratch)
|
|
220
|
+
episode = build(root, CUT_LIST)
|
|
221
|
+
(episode / "剧本.md").write_text(
|
|
222
|
+
SCREENPLAY + "\n江晨:就是什么。\n江晨:就是少了点东西。\n江晨:少了什么。\n",
|
|
223
|
+
encoding="utf-8",
|
|
224
|
+
)
|
|
225
|
+
multi = CUT_LIST.replace(
|
|
226
|
+
"- 字幕:这条路我自己走",
|
|
227
|
+
"- 字幕 1:0.10-0.60 就是什么\n"
|
|
228
|
+
"- 字幕 2:0.80-1.40 就是少了点东西\n"
|
|
229
|
+
"- 字幕 3:1.50-1.90 少了什么",
|
|
230
|
+
)
|
|
231
|
+
(episode / "剪辑单.md").write_text(multi, encoding="utf-8")
|
|
232
|
+
_, cuts, _ = parse_cut_list(episode / "剪辑单.md")
|
|
233
|
+
require(len(cuts[1].subtitles) == 3, f"三句应全部解析,实际 {len(cuts[1].subtitles)}")
|
|
234
|
+
require(cuts[1].subtitles[2][2] == "少了什么", "字幕顺序错了")
|
|
235
|
+
require(not check_cuts(episode, cuts, root, probe=False), "三句合法字幕不该报错")
|
|
236
|
+
cues = _subtitle_cues(cuts, [cut.end - cut.start for cut in cuts])
|
|
237
|
+
require(len(cues) == 3, f"三句应各自成为一条 cue,实际 {len(cues)}")
|
|
238
|
+
|
|
239
|
+
# 编号必须连续:跳号意味着有一句被漏掉了。
|
|
240
|
+
gap = CUT_LIST.replace(
|
|
241
|
+
"- 字幕:这条路我自己走",
|
|
242
|
+
"- 字幕 1:0.10-0.60 就是什么\n- 字幕 3:1.50-1.90 少了什么",
|
|
243
|
+
)
|
|
244
|
+
(episode / "剪辑单.md").write_text(gap, encoding="utf-8")
|
|
245
|
+
try:
|
|
246
|
+
parse_cut_list(episode / "剪辑单.md")
|
|
247
|
+
except EditError as error:
|
|
248
|
+
require("连续" in str(error), f"跳号报错不对: {error}")
|
|
249
|
+
else:
|
|
250
|
+
raise AssertionError("字幕编号跳号应当被拒绝")
|
|
251
|
+
|
|
252
|
+
# 两句不能同时在屏上。
|
|
253
|
+
overlap = CUT_LIST.replace(
|
|
254
|
+
"- 字幕:这条路我自己走",
|
|
255
|
+
"- 字幕 1:0.10-1.00 就是什么\n- 字幕 2:0.80-1.40 就是少了点东西",
|
|
256
|
+
)
|
|
257
|
+
(episode / "剪辑单.md").write_text(overlap, encoding="utf-8")
|
|
258
|
+
try:
|
|
259
|
+
parse_cut_list(episode / "剪辑单.md")
|
|
260
|
+
except EditError as error:
|
|
261
|
+
require("重叠" in str(error), f"重叠报错不对: {error}")
|
|
262
|
+
else:
|
|
263
|
+
raise AssertionError("字幕时间重叠应当被拒绝")
|
|
264
|
+
|
|
265
|
+
# 多句时每句必须自带时间,否则无从摆放。
|
|
266
|
+
untimed = CUT_LIST.replace(
|
|
267
|
+
"- 字幕:这条路我自己走",
|
|
268
|
+
"- 字幕 1:就是什么\n- 字幕 2:0.80-1.40 就是少了点东西",
|
|
269
|
+
)
|
|
270
|
+
(episode / "剪辑单.md").write_text(untimed, encoding="utf-8")
|
|
271
|
+
try:
|
|
272
|
+
parse_cut_list(episode / "剪辑单.md")
|
|
273
|
+
except EditError as error:
|
|
274
|
+
require("各自带时间" in str(error), f"缺时间报错不对: {error}")
|
|
275
|
+
else:
|
|
276
|
+
raise AssertionError("多句字幕缺时间应当被拒绝")
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def check_stale_window() -> None:
|
|
280
|
+
"""Regenerating a shot invalidates every subtitle window bound to it.
|
|
281
|
+
|
|
282
|
+
The windows were reverse-engineered from the old take's audio. Nothing else
|
|
283
|
+
notices the change: the numbers stay in range, the render still succeeds,
|
|
284
|
+
and the subtitle appears at a moment when nobody is speaking.
|
|
285
|
+
"""
|
|
286
|
+
|
|
287
|
+
import os
|
|
288
|
+
import time
|
|
289
|
+
|
|
290
|
+
with tempfile.TemporaryDirectory() as scratch:
|
|
291
|
+
root = Path(scratch)
|
|
292
|
+
# Anchored on the field, not on the line's wording: the fixture's dialogue
|
|
293
|
+
# is illustrative and gets rewritten, and a test that breaks when an
|
|
294
|
+
# example changes is a test nobody trusts.
|
|
295
|
+
timed = re.sub(
|
|
296
|
+
r"^(- 字幕:(?!无).+)$",
|
|
297
|
+
r"\1\n- 字幕时间:0.30-1.60",
|
|
298
|
+
CUT_LIST,
|
|
299
|
+
count=1,
|
|
300
|
+
flags=re.MULTILINE,
|
|
301
|
+
)
|
|
302
|
+
require(timed != CUT_LIST, "夹具里应当有一条带台词的字幕")
|
|
303
|
+
episode = build(root, timed)
|
|
304
|
+
_, cuts, _ = parse_cut_list(episode / "剪辑单.md")
|
|
305
|
+
require(
|
|
306
|
+
not check_cuts(episode, cuts, root, probe=False),
|
|
307
|
+
"素材不比剪辑单新时不该报陈旧",
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
media = episode / "media" / "011.mp4"
|
|
311
|
+
listing = episode / "剪辑单.md"
|
|
312
|
+
newer = listing.stat().st_mtime + 60
|
|
313
|
+
os.utime(media, (newer, newer))
|
|
314
|
+
findings = check_cuts(episode, cuts, root, probe=False)
|
|
315
|
+
require(
|
|
316
|
+
any("素材比剪辑单新" in item for item in findings),
|
|
317
|
+
f"重出后的陈旧字幕时间没抓到: {findings}",
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
# 改过剪辑单之后就不再报——那正是「重测并改」的动作。
|
|
321
|
+
time.sleep(0.01)
|
|
322
|
+
listing.write_text(timed, encoding="utf-8")
|
|
323
|
+
later = newer + 60
|
|
324
|
+
os.utime(listing, (later, later))
|
|
325
|
+
_, cuts, _ = parse_cut_list(listing)
|
|
326
|
+
require(
|
|
327
|
+
not any("素材比剪辑单新" in item for item in check_cuts(episode, cuts, root, probe=False)),
|
|
328
|
+
"改过剪辑单之后不该继续报陈旧",
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
def check_ass_escaping() -> None:
|
|
333
|
+
"""A quoted line keeps every character it had in 剧本.md.
|
|
334
|
+
|
|
335
|
+
`{`…`}` is an override block in ASS: unescaped, libass drops the braces and
|
|
336
|
+
everything between them, on screen, with no error anywhere. EDT-05 exists to
|
|
337
|
+
keep the burned text equal to the screenplay's character for character.
|
|
338
|
+
"""
|
|
339
|
+
|
|
340
|
+
line = "他念出来:{姓名},请签字。"
|
|
341
|
+
escaped = _ass_text(line)
|
|
342
|
+
require("\\{" in escaped and "\\}" in escaped, f"花括号没有转义: {escaped}")
|
|
343
|
+
require("姓名" in escaped, "转义把字弄丢了")
|
|
344
|
+
ass = _build_ass([(1.0, 2.0, line)], 1080, 1920)
|
|
345
|
+
dialogue = [row for row in ass.splitlines() if row.startswith("Dialogue")][0]
|
|
346
|
+
require(dialogue.endswith(escaped), f"Dialogue 行没有用转义后的正文: {dialogue}")
|
|
347
|
+
require(
|
|
348
|
+
_ass_text("反斜杠 \\N 不是换行").count("\\\\") == 1,
|
|
349
|
+
"反斜杠没有转义,\\N 会被当成换行",
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
def check_remotion_sources_all_shipped() -> None:
|
|
354
|
+
"""The sync list is a whitelist; a file left out of it is missing at render.
|
|
355
|
+
|
|
356
|
+
Nothing else notices until someone has already paid for that render.
|
|
357
|
+
"""
|
|
358
|
+
|
|
359
|
+
# `as_posix`, because the whitelist is written with forward slashes and
|
|
360
|
+
# `relative_to` yields backslashes on Windows.
|
|
361
|
+
on_disk = {
|
|
362
|
+
path.relative_to(REMOTION_SOURCE).as_posix()
|
|
363
|
+
for path in REMOTION_SOURCE.rglob("*")
|
|
364
|
+
if path.is_file()
|
|
365
|
+
and path.suffix in {".ts", ".tsx", ".json"}
|
|
366
|
+
and "node_modules" not in path.parts
|
|
367
|
+
}
|
|
368
|
+
listed = set(REMOTION_SOURCE_FILES)
|
|
369
|
+
require(not (on_disk - listed), f"Remotion 源文件没进同步清单: {sorted(on_disk - listed)}")
|
|
370
|
+
require(not (listed - on_disk), f"同步清单里有不存在的文件: {sorted(listed - on_disk)}")
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def check_remotion_concurrency_is_capped() -> None:
|
|
374
|
+
"""Remotion defaults to one browser per core, each holding a full frame."""
|
|
375
|
+
|
|
376
|
+
require(DEFAULT_REMOTION_CONCURRENCY >= 1, "并发上限必须是正数")
|
|
377
|
+
source = Path(__file__).resolve().with_name("edit_tool.py").read_text(encoding="utf-8")
|
|
378
|
+
require("--concurrency=" in source, "render 必须显式传 --concurrency,不能用 Remotion 的默认值")
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
def check_grain_is_a_delivery_wide_decision() -> None:
|
|
382
|
+
"""Grain is declared once for the film, and refused when it is not grain.
|
|
383
|
+
|
|
384
|
+
Per cut it would become one more thing that differs between segments, which
|
|
385
|
+
is the defect it exists to cover.
|
|
386
|
+
"""
|
|
387
|
+
|
|
388
|
+
with tempfile.TemporaryDirectory() as scratch:
|
|
389
|
+
root = Path(scratch)
|
|
390
|
+
episode = build(root, CUT_LIST)
|
|
391
|
+
path = episode / "剪辑单.md"
|
|
392
|
+
baseline = path.read_text(encoding="utf-8")
|
|
393
|
+
|
|
394
|
+
delivery, _, _ = parse_cut_list(path)
|
|
395
|
+
require(delivery.grain is None, "没声明颗粒时应当是 None")
|
|
396
|
+
|
|
397
|
+
anchor = "- 交付响度:"
|
|
398
|
+
require(anchor in baseline, "夹具里应当有交付响度那一行")
|
|
399
|
+
path.write_text(baseline.replace(anchor, "- 颗粒:6\n" + anchor, 1), encoding="utf-8")
|
|
400
|
+
delivery, _, _ = parse_cut_list(path)
|
|
401
|
+
require(delivery.grain == 6.0, f"颗粒没有解析出来: {delivery.grain}")
|
|
402
|
+
|
|
403
|
+
path.write_text(baseline.replace(anchor, "- 颗粒:无\n" + anchor, 1), encoding="utf-8")
|
|
404
|
+
delivery, _, _ = parse_cut_list(path)
|
|
405
|
+
require(delivery.grain is None, "「无」应当解析成不加颗粒")
|
|
406
|
+
|
|
407
|
+
path.write_text(baseline.replace(anchor, "- 颗粒:80\n" + anchor, 1), encoding="utf-8")
|
|
408
|
+
try:
|
|
409
|
+
parse_cut_list(path)
|
|
410
|
+
except EditError as error:
|
|
411
|
+
require("超出" in str(error), f"越界报错没说清: {error}")
|
|
412
|
+
else:
|
|
413
|
+
raise AssertionError("颗粒 80 应当被拒绝")
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
def check_missing_shots_are_reported() -> None:
|
|
417
|
+
"""A shot absent from the film must be absent on purpose.
|
|
418
|
+
|
|
419
|
+
Absence is invisible in a finished film — nobody watching can tell that an
|
|
420
|
+
episode's opening seven shots were never made. The only place it can be
|
|
421
|
+
caught is here, against the document that lists them.
|
|
422
|
+
"""
|
|
423
|
+
|
|
424
|
+
known = {f"MOTION-EP006-{i:03d}" for i in range(1, 5)}
|
|
425
|
+
|
|
426
|
+
class Stub:
|
|
427
|
+
def __init__(self, motion): self.motion = motion
|
|
428
|
+
|
|
429
|
+
used = [Stub("MOTION-EP006-003"), Stub("MOTION-EP006-004")]
|
|
430
|
+
findings = _unaccounted_shots(known, used, [])
|
|
431
|
+
require(findings, "少了两镜却没有任何 finding")
|
|
432
|
+
require("001" in findings[0] and "002" in findings[0], f"没点名缺的镜头: {findings}")
|
|
433
|
+
|
|
434
|
+
excused = _unaccounted_shots(
|
|
435
|
+
known, used, ["MOTION-EP006-001(理由:质量不可用)", "MOTION-EP006-002(理由:文件缺失)"]
|
|
436
|
+
)
|
|
437
|
+
require(not excused, f"写进未采用的镜头不该再报: {excused}")
|
|
438
|
+
|
|
439
|
+
require(not _unaccounted_shots(known, [Stub(m) for m in known], []), "全采用时不该报")
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
def main() -> int:
|
|
443
|
+
with tempfile.TemporaryDirectory() as scratch:
|
|
444
|
+
root = Path(scratch)
|
|
445
|
+
episode = build(root, CUT_LIST)
|
|
446
|
+
delivery, cuts, unused = parse_cut_list(episode / "剪辑单.md")
|
|
447
|
+
require(delivery.target_seconds == 5.0, "目标时长没有解析出来")
|
|
448
|
+
require(delivery.loudness_lufs == -16.0, "交付响度没有解析出来")
|
|
449
|
+
require(len(cuts) == 2, f"应解析出 2 段,实际 {len(cuts)}")
|
|
450
|
+
require(len(unused) == 1, "未采用镜头没有解析出来")
|
|
451
|
+
require(cuts[1].subtitles[0][2] == "这条路我自己走", "字幕文字没有解析出来")
|
|
452
|
+
|
|
453
|
+
clean = check_cuts(episode, cuts, root, probe=False)
|
|
454
|
+
require(not clean, f"完好的剪辑单不该有 findings: {clean}")
|
|
455
|
+
|
|
456
|
+
# 出点 - 入点 与声明时长不符必须抓到。
|
|
457
|
+
drifted = CUT_LIST.replace("- 时长:3.00", "- 时长:2.50")
|
|
458
|
+
(episode / "剪辑单.md").write_text(drifted, encoding="utf-8")
|
|
459
|
+
_, cuts, _ = parse_cut_list(episode / "剪辑单.md")
|
|
460
|
+
findings = check_cuts(episode, cuts, root, probe=False)
|
|
461
|
+
require(any("与「时长" in item for item in findings), f"时长漂移没抓到: {findings}")
|
|
462
|
+
|
|
463
|
+
# 字幕必须能在剧本里找到原文;转写猜出来的字不行。
|
|
464
|
+
invented = CUT_LIST.replace("字幕:这条路我自己走", "字幕:这条陆我自己走")
|
|
465
|
+
(episode / "剪辑单.md").write_text(invented, encoding="utf-8")
|
|
466
|
+
_, cuts, _ = parse_cut_list(episode / "剪辑单.md")
|
|
467
|
+
findings = check_cuts(episode, cuts, root, probe=False)
|
|
468
|
+
require(any("找不到原文" in item for item in findings), f"编造的字幕没抓到: {findings}")
|
|
469
|
+
|
|
470
|
+
# 未知 MOTION 必须抓到。
|
|
471
|
+
unknown = CUT_LIST.replace("MOTION-EP001-011", "MOTION-EP001-099")
|
|
472
|
+
(episode / "剪辑单.md").write_text(unknown, encoding="utf-8")
|
|
473
|
+
_, cuts, _ = parse_cut_list(episode / "剪辑单.md")
|
|
474
|
+
findings = check_cuts(episode, cuts, root, probe=False)
|
|
475
|
+
require(any("不在《视频提示词.md》中" in item for item in findings), f"未知 MOTION 没抓到: {findings}")
|
|
476
|
+
|
|
477
|
+
# 素材缺失必须抓到。
|
|
478
|
+
(episode / "media" / "011.mp4").unlink()
|
|
479
|
+
(episode / "剪辑单.md").write_text(CUT_LIST, encoding="utf-8")
|
|
480
|
+
_, cuts, _ = parse_cut_list(episode / "剪辑单.md")
|
|
481
|
+
findings = check_cuts(episode, cuts, root, probe=False)
|
|
482
|
+
require(any("素材不存在" in item for item in findings), f"缺素材没抓到: {findings}")
|
|
483
|
+
|
|
484
|
+
# 同一素材上的两段不得重叠。
|
|
485
|
+
overlapping = CUT_LIST.replace(
|
|
486
|
+
"- 来源:MOTION-EP001-011 · media/011.mp4", "- 来源:MOTION-EP001-011 · media/010.mp4"
|
|
487
|
+
).replace("- 入点:0.00", "- 入点:1.00").replace("- 出点:2.00", "- 出点:3.00")
|
|
488
|
+
(episode / "media" / "011.mp4").write_bytes(b"")
|
|
489
|
+
(episode / "剪辑单.md").write_text(overlapping, encoding="utf-8")
|
|
490
|
+
_, cuts, _ = parse_cut_list(episode / "剪辑单.md")
|
|
491
|
+
findings = check_cuts(episode, cuts, root, probe=False)
|
|
492
|
+
require(any("区间重叠" in item for item in findings), f"同源重叠没抓到: {findings}")
|
|
493
|
+
|
|
494
|
+
# 缺字段是文档缺陷,报出来而不是崩掉。
|
|
495
|
+
(episode / "剪辑单.md").write_text(
|
|
496
|
+
CUT_LIST.replace("- 出点:3.60\n", ""), encoding="utf-8"
|
|
497
|
+
)
|
|
498
|
+
try:
|
|
499
|
+
parse_cut_list(episode / "剪辑单.md")
|
|
500
|
+
except EditError as error:
|
|
501
|
+
require("出点" in str(error), f"缺字段的报错没点名字段: {error}")
|
|
502
|
+
else:
|
|
503
|
+
raise AssertionError("缺「出点」应当报错")
|
|
504
|
+
|
|
505
|
+
check_subtitle_geometry()
|
|
506
|
+
check_subtitle_timing()
|
|
507
|
+
check_shot_match()
|
|
508
|
+
check_ass_escaping()
|
|
509
|
+
check_multi_subtitle()
|
|
510
|
+
check_stale_window()
|
|
511
|
+
check_missing_shots_are_reported()
|
|
512
|
+
check_grain_is_a_delivery_wide_decision()
|
|
513
|
+
check_remotion_sources_all_shipped()
|
|
514
|
+
check_remotion_concurrency_is_capped()
|
|
515
|
+
print("short-drama-edit self-tests passed")
|
|
516
|
+
return 0
|
|
517
|
+
|
|
518
|
+
|
|
519
|
+
if __name__ == "__main__":
|
|
520
|
+
raise SystemExit(main())
|
|
@@ -20,6 +20,8 @@ license: MIT
|
|
|
20
20
|
|
|
21
21
|
有当前视觉设定即可直接开始;剧本只在提示词需要确认剧情状态时读取。Look Development 是可选分支。
|
|
22
22
|
先确认用途:身份板、造型/状态变体、地点板、道具板、组合 production sheet 或比较风格帧。
|
|
23
|
+
分镜需要生产尾帧时,可把已接受的「收尾关键帧提示词」落实为独立 `IMG-...` 状态图条目:
|
|
24
|
+
说明来源 SHOT 与终点,沿用视觉事实和参考范围,不重新设计动作。实际生成以该 IMG 条目为来源。
|
|
23
25
|
|
|
24
26
|
创作者可读说明跟随项目语言;可复制正文跟随 `short-drama.json#/format/prompt_language`。没有
|
|
25
27
|
`short-drama.json` 时正文默认
|
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
此文件由已接受规格生成。元信息用于核对来源;只复制引用块内的通用提示词。不要手写供应商参数。
|
|
4
4
|
|
|
5
|
+
**这份模板属于结构化管线**(`image-prompt-specs.jsonl` 一路)。creator-first 项目的《图片提示词.md》骨架以 `SKILL.md` 为准:`## IMG-... · 中文名`,字段写成不加粗的
|
|
6
|
+
`- 参考:`(取「无外部参考」或完整 `REF-...` 语法),正文标题是 `### 可复制提示词`。
|
|
7
|
+
不要把这里的加粗字段名、`参考图用途`、`本次呈现` 或 `### 可复制通用提示词` 搬进 creator-first 文档——
|
|
8
|
+
`creator_markdown_check.py` 按 `SKILL.md` 的骨架校验,照本模板写会被判「缺少参考字段」。
|
|
9
|
+
|
|
5
10
|
```markdown
|
|
6
11
|
# EP<编号> · 资产图片提示词
|
|
7
12
|
|
|
@@ -203,6 +203,17 @@
|
|
|
203
203
|
- **`taste_option`**:写实度、媒介、颗粒、色调和镜头感由视觉风格或创作者决定。
|
|
204
204
|
- 禁止用固定词表或形容词比例阻断交付;描述是否空泛,由审查者指出具体缺了什么。
|
|
205
205
|
|
|
206
|
+
### 光学与表面描述
|
|
207
|
+
|
|
208
|
+
**`craft_default`**:对需要明确透视或写实质感的画面,写明机位、取景距离和可见表面特征。
|
|
209
|
+
焦段可辅助表达,但不能据此承诺模型会精确模拟光学。
|
|
210
|
+
|
|
211
|
+
根据画面尺度选择毛孔、布料折痕或金属使用痕迹;「干净」不必等同于没有任何纹理和折痕。
|
|
212
|
+
不要给每张图添加相同的相机参数、瑕疵清单或排除词。
|
|
213
|
+
|
|
214
|
+
**`taste_option`**:表面是否平滑、是否修饰,以及写实程度由已接受的视觉风格决定。
|
|
215
|
+
结果用对照图检查;像素纹理指标只作辅助,不能代替人物与材质的视觉判断。
|
|
216
|
+
|
|
206
217
|
## 5. 输出、常见问题与检查
|
|
207
218
|
|
|
208
219
|
### 输出
|
|
@@ -69,13 +69,13 @@
|
|
|
69
69
|
| 指标 | 参考值 | 算法 | 不达标怎么办 |
|
|
70
70
|
|---|---|---|---|
|
|
71
71
|
| 归属置信 | ≥ 0.85 | 有明确归属的情节点 / 单元内情节点总数 | 低于线的单元标「待复核」,不删 |
|
|
72
|
+
| 覆盖率 | 85%–95% | 已归类情节点 / 总情节点数 | < 85% 走散落兜底;> 95% 复核是否过度归并 |
|
|
73
|
+
| 重叠率 | ≤ 35% | 跨单元共享的情节点 / 总情节点数 | > 35% 说明两个单元其实是一个,考虑合并 |
|
|
72
74
|
|
|
73
75
|
「有明确归属」指这条情节点服务于本单元的戏剧目标或对抗力量,而不只是恰好落在本单元的
|
|
74
76
|
章节范围内。按连续章节切分时,若把「落在范围内」当作归属,这个比值恒等于 1,指标失去
|
|
75
77
|
意义——所以逐条判的是**它推动的是不是这个单元的那件事**,跨单元共享的情节点计入重叠率
|
|
76
78
|
而不计入本单元的分子。
|
|
77
|
-
| 覆盖率 | 85%–95% | 已归类情节点 / 总情节点数 | < 85% 走散落兜底;> 95% 复核是否过度归并 |
|
|
78
|
-
| 重叠率 | ≤ 35% | 跨单元共享的情节点 / 总情节点数 | > 35% 说明两个单元其实是一个,考虑合并 |
|
|
79
79
|
|
|
80
80
|
**小体量素材的例外**:不足 30 章、单主线连续叙事的材料按时序切分时,覆盖率天然接近
|
|
81
81
|
100%、散落接近 0,这是线性结构的正常结果,不是过度归并。此时复核的重点是
|
|
@@ -103,9 +103,12 @@ P=$(grep -cE '^P[0-9]+ ' "$F")
|
|
|
103
103
|
grep -cE '^P[0-9]+ [^|]+\|[^|]*[^|[:space:]][^|]*\|[^|]*涉及' "$F" # 必须 == $P
|
|
104
104
|
grep -cE '^P[0-9]+ .*\|.*\|.*\|[^|]*功能:' "$F" # 必须 == $P
|
|
105
105
|
grep -hoE '^P[0-9]+ [^|]+' "$F" | awk '{print $2}' | sort -u # 必须 ⊆ 类型表
|
|
106
|
-
|
|
106
|
+
awk '/^## 载体记录/{f=1;next} /^## /{f=0} f&&/^\|/{n++} END{print n+0}' "$F" # 必须 >= 3
|
|
107
107
|
```
|
|
108
108
|
|
|
109
|
+
- 载体表计数少于 3:该章只写了表头没写内容。阈值是 3 不是 2,因为表头行与 `|---|` 分隔行
|
|
110
|
+
本身就占 2 行——按 2 计的话,一张空表照样过闸。**这一条也必须只数「载体记录」小节内的
|
|
111
|
+
表格行**:数全文的 `|` 行会把「出场人物」表算进来,同样让空载体表蒙混过关。
|
|
109
112
|
- 白描段计数少于 `$P`:有情节点缺白描,或字段顺序、分隔符不对。
|
|
110
113
|
- 功能段计数少于 `$P`:有情节点没回答改变了什么。注意全角冒号——半角会漏。
|
|
111
114
|
这一条只数情节点行第四段里的 `功能:`:早先的写法数的是全文任何位置的这四个字符,
|