@oh-story/dsh 0.1.3 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +58 -12
- package/lib/client.js +2970 -537
- package/lib/client.js.map +4 -4
- package/lib/drama/manifest.json +218 -178
- package/lib/drama/skills/short-drama/SKILL.md +110 -117
- package/lib/drama/skills/short-drama/references/audience-reveal.md +14 -67
- package/lib/drama/skills/short-drama/references/contract-and-ownership.md +26 -77
- package/lib/drama/skills/short-drama/references/creator-documents.md +204 -0
- package/lib/drama/skills/short-drama/references/creator-workflow.md +73 -63
- package/lib/drama/skills/short-drama/references/knowhow-index.md +5 -0
- package/lib/drama/skills/short-drama/references/look-development.md +2 -3
- package/lib/drama/skills/short-drama/references/pickup-and-alternate.md +14 -48
- package/lib/drama/skills/short-drama/references/production-form-profiles.md +1 -1
- package/lib/drama/skills/short-drama/references/reference-roles.md +32 -135
- package/lib/drama/skills/short-drama/references/runtime-preflight.md +16 -19
- package/lib/drama/skills/short-drama/scripts/creator_markdown_check.py +1002 -0
- package/lib/drama/skills/short-drama/scripts/project_tool.py +485 -5
- package/lib/drama/skills/short-drama-assets/SKILL.md +59 -170
- package/lib/drama/skills/short-drama-assets/references/asset-review-checklist.md +18 -25
- package/lib/drama/skills/short-drama-assets/references/character-and-look.md +13 -15
- package/lib/drama/skills/short-drama-assets/references/continuity-delta.md +11 -14
- package/lib/drama/skills/short-drama-assets/references/continuity-lock.md +116 -0
- package/lib/drama/skills/short-drama-assets/references/identity-vs-variant.md +6 -12
- package/lib/drama/skills/short-drama-assets/references/occurrence-extraction.md +8 -11
- package/lib/drama/skills/short-drama-assets/references/prop-and-state.md +21 -2
- package/lib/drama/skills/short-drama-assets/references/stage-contract.md +12 -105
- package/lib/drama/skills/short-drama-assets/references/voice-direction.md +12 -32
- package/lib/drama/skills/short-drama-assets/scripts/asset_check.py +1 -1
- package/lib/drama/skills/short-drama-image-prompts/SKILL.md +45 -155
- package/lib/drama/skills/short-drama-image-prompts/references/character-and-look.md +12 -13
- package/lib/drama/skills/short-drama-image-prompts/references/common-recipe.md +12 -13
- package/lib/drama/skills/short-drama-image-prompts/references/edit-and-revision.md +39 -51
- package/lib/drama/skills/short-drama-image-prompts/references/location-plate.md +3 -4
- package/lib/drama/skills/short-drama-image-prompts/references/look-and-state-variant.md +15 -14
- package/lib/drama/skills/short-drama-image-prompts/references/lookdev-frame.md +11 -15
- package/lib/drama/skills/short-drama-image-prompts/references/production-sheet-recipes.md +1 -1
- package/lib/drama/skills/short-drama-image-prompts/references/prop-plate.md +4 -7
- package/lib/drama/skills/short-drama-image-prompts/references/review-and-fixtures.md +23 -23
- package/lib/drama/skills/short-drama-image-prompts/references/stage-contract.md +8 -65
- package/lib/drama/skills/short-drama-image-prompts/scripts/image_prompt_check.py +8 -8
- package/lib/drama/skills/short-drama-image-prompts/scripts/selftest.py +4 -2
- package/lib/drama/skills/short-drama-produce/SKILL.md +67 -15
- package/lib/drama/skills/short-drama-produce/references/adapter-contract.md +65 -8
- package/lib/drama/skills/short-drama-produce/references/providers/minimax-h3-video.md +85 -0
- package/lib/drama/skills/short-drama-produce/references/providers/seedance.md +27 -10
- package/lib/drama/skills/short-drama-produce/scripts/production_tool.py +362 -3
- package/lib/drama/skills/short-drama-produce/scripts/provider_adapters.py +627 -40
- package/lib/drama/skills/short-drama-produce/scripts/selftest.py +5 -4
- package/lib/drama/skills/short-drama-review/SKILL.md +61 -123
- package/lib/drama/skills/short-drama-review/references/anti-template-repair.md +10 -15
- package/lib/drama/skills/short-drama-review/references/production-quality-gates.md +15 -16
- package/lib/drama/skills/short-drama-review/references/project-calibration.md +7 -9
- package/lib/drama/skills/short-drama-review/references/review-method.md +8 -9
- package/lib/drama/skills/short-drama-review/references/rubric-assets-prompts.md +9 -13
- package/lib/drama/skills/short-drama-review/references/rubric-story-script.md +104 -4
- package/lib/drama/skills/short-drama-review/references/rubric-visual-motion.md +27 -19
- package/lib/drama/skills/short-drama-review/references/stage-contract.md +4 -63
- package/lib/drama/skills/short-drama-review/scripts/review_check.py +1 -1
- package/lib/drama/skills/short-drama-storyboard/SKILL.md +116 -238
- package/lib/drama/skills/short-drama-storyboard/assets/shot-template.jsonl +1 -1
- package/lib/drama/skills/short-drama-storyboard/references/blocking-playbooks.md +9 -34
- package/lib/drama/skills/short-drama-storyboard/references/comic-keyframe-lexicon.md +156 -0
- package/lib/drama/skills/short-drama-storyboard/references/coverage-audition.md +5 -10
- package/lib/drama/skills/short-drama-storyboard/references/keyframe-craft.md +12 -7
- package/lib/drama/skills/short-drama-storyboard/references/production-shot-grammar.md +19 -4
- package/lib/drama/skills/short-drama-storyboard/references/review-and-fixtures.md +27 -62
- package/lib/drama/skills/short-drama-storyboard/references/scene-visual-plan.md +16 -25
- package/lib/drama/skills/short-drama-storyboard/references/screenplay-to-keyframe-example.md +21 -23
- package/lib/drama/skills/short-drama-storyboard/references/shot-craft.md +8 -10
- package/lib/drama/skills/short-drama-storyboard/references/shot-revision-identity.md +9 -13
- package/lib/drama/skills/short-drama-storyboard/references/stage-contract.md +16 -71
- package/lib/drama/skills/short-drama-storyboard/scripts/storyboard_check.py +1 -1
- package/lib/drama/skills/short-drama-video-prompts/SKILL.md +123 -230
- package/lib/drama/skills/short-drama-video-prompts/assets/motion-spec.jsonl.md +6 -1
- package/lib/drama/skills/short-drama-video-prompts/assets/video-prompts.md +17 -1
- package/lib/drama/skills/short-drama-video-prompts/references/camera-audio-continuity.md +10 -8
- package/lib/drama/skills/short-drama-video-prompts/references/delivery-profile.md +24 -6
- package/lib/drama/skills/short-drama-video-prompts/references/generability.md +2 -2
- package/lib/drama/skills/short-drama-video-prompts/references/minimax-h3.md +154 -0
- package/lib/drama/skills/short-drama-video-prompts/references/motion-recipe.md +35 -27
- package/lib/drama/skills/short-drama-video-prompts/references/performance-action-timing.md +8 -11
- package/lib/drama/skills/short-drama-video-prompts/references/production-prompt-grammar.md +13 -10
- package/lib/drama/skills/short-drama-video-prompts/references/review-and-fixtures.md +34 -63
- package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.0.md +41 -0
- package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.5.md +66 -0
- package/lib/drama/skills/short-drama-video-prompts/references/stage-contract.md +17 -73
- package/lib/drama/skills/short-drama-video-prompts/references/target-model-profile.md +115 -0
- package/lib/drama/skills/short-drama-video-prompts/scripts/container_check.py +1 -1
- package/lib/drama/skills/short-drama-video-prompts/scripts/music_spec_check.py +1 -1
- package/lib/drama/skills/short-drama-write/SKILL.md +74 -268
- package/lib/drama/skills/short-drama-write/references/dialogue-craft.md +7 -0
- package/lib/drama/skills/short-drama-write/references/scene-handoff-capsule.md +17 -43
- package/lib/drama/skills/short-drama-write/references/screenplay-format.md +15 -15
- package/lib/drama/skills/short-drama-write/references/script-craft.md +208 -21
- package/lib/drama/skills/short-drama-write/references/stage-contract.md +11 -70
- package/lib/drama/skills/short-drama-write/scripts/duration_estimate.py +1 -1
- package/lib/drama/skills/short-drama-write/scripts/screenplay_index.py +2 -2
- package/lib/drama/skills/short-drama-write/scripts/voice_sheet_check.py +1 -1
- package/lib/index.js +971 -46
- package/lib/index.js.map +4 -4
- package/lib/novel-to-game/LICENSE +21 -0
- package/lib/novel-to-game/README.md +180 -0
- package/lib/novel-to-game/README_ZH.md +178 -0
- package/lib/novel-to-game/VERSION +1 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/BUILD_BRIEF.md +217 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/.vercelignore +3 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/RUN.md +130 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/compound_new_guofeng.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/finale/fate_coda.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/finale/meng.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/finale/pan.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/finale/pinger.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/finale/xuee.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/finale/yue.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/inner_court_accord_five.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/inner_court_afterglow_five.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/inner_court_alliance.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/public_day10.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/public_day15.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/public_day5.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/title_new_guofeng.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/joint/pan_pinger.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/joint/pan_xuee.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/joint/yue_meng.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/joint/yue_pan.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/joint/yue_pinger.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/meng/explicit.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/meng/prelude.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/milestone/meng.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/milestone/pan.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/milestone/pinger.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/milestone/xuee.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/milestone/yue.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/pan/explicit.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/pan/prelude.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/pinger/explicit.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/pinger/prelude.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/xuee/explicit.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/xuee/prelude.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/yue/explicit.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/yue/prelude.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/heroine/meng/night.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/heroine/pan/night.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/heroine/pinger/night.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/heroine/xuee/night.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/heroine/yue/night.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/household/li_jiaoer.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/scene/gate_collector.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/css/new-guofeng.css +1105 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/css/style.css +2539 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/index.html +18 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/assets.js +147 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/audio.js +332 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/data.js +7472 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/engine.js +13077 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/main.js +3028 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/text.js +32 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/package.json +10 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/vercel.json +23 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/art/generated-art.json +161 -0
- package/lib/novel-to-game/examples/jin-ping-mei/example.json +12 -0
- package/lib/novel-to-game/examples/jin-ping-mei/qa/README.md +31 -0
- package/lib/novel-to-game/examples/jin-ping-mei/qa/playable-model.json +99 -0
- package/lib/novel-to-game/examples/jin-ping-mei/qa/verification.json +41 -0
- package/lib/novel-to-game/examples/jin-ping-mei/qa/verify_playable_model.mjs +129 -0
- package/lib/novel-to-game/manifest.json +508 -0
- package/lib/novel-to-game/skills/game-art-direction/SKILL.md +73 -0
- package/lib/novel-to-game/skills/game-art-direction/agents/openai.yaml +4 -0
- package/lib/novel-to-game/skills/game-art-direction/references/art-direction-method.md +119 -0
- package/lib/novel-to-game/skills/game-build/SKILL.md +84 -0
- package/lib/novel-to-game/skills/game-build/agents/openai.yaml +4 -0
- package/lib/novel-to-game/skills/game-build/references/build-brief-contract.md +109 -0
- package/lib/novel-to-game/skills/game-build/references/generative-media-pipeline.md +56 -0
- package/lib/novel-to-game/skills/game-build/references/playable-model-contract.md +81 -0
- package/lib/novel-to-game/skills/game-build/references/tts-production-contract.md +37 -0
- package/lib/novel-to-game/skills/game-concept/SKILL.md +72 -0
- package/lib/novel-to-game/skills/game-concept/agents/openai.yaml +4 -0
- package/lib/novel-to-game/skills/game-concept/references/concept-method.md +94 -0
- package/lib/novel-to-game/skills/game-qa/SKILL.md +50 -0
- package/lib/novel-to-game/skills/game-qa/agents/openai.yaml +4 -0
- package/lib/novel-to-game/skills/game-qa/references/qa-contract.md +66 -0
- package/lib/novel-to-game/skills/game-qa/references/test-design-method.md +45 -0
- package/lib/novel-to-game/skills/game-world-design/SKILL.md +63 -0
- package/lib/novel-to-game/skills/game-world-design/agents/openai.yaml +4 -0
- package/lib/novel-to-game/skills/game-world-design/references/dialogue-design-method.md +59 -0
- package/lib/novel-to-game/skills/game-world-design/references/game-writing-craft.md +53 -0
- package/lib/novel-to-game/skills/game-world-design/references/narrative-design-method.md +84 -0
- package/lib/novel-to-game/skills/game-world-design/references/numeric-design-method.md +56 -0
- package/lib/novel-to-game/skills/game-world-design/references/world-design-method.md +124 -0
- package/lib/novel-to-game/skills/novel-game-analyze/SKILL.md +77 -0
- package/lib/novel-to-game/skills/novel-game-analyze/agents/openai.yaml +4 -0
- package/lib/novel-to-game/skills/novel-game-analyze/references/gameability-protocol.md +65 -0
- package/lib/novel-to-game/skills/novel-to-game/SKILL.md +71 -0
- package/lib/novel-to-game/skills/novel-to-game/agents/openai.yaml +4 -0
- package/lib/novel-to-game/skills/novel-to-game/references/intake-benchmark-reference.md +42 -0
- package/lib/novel-to-game/skills/novel-to-game/references/intake-method.md +51 -0
- package/lib/novel-to-game/skills/novel-to-game/references/pipeline-contract.md +65 -0
- package/lib/oh-story/manifest.json +419 -309
- package/lib/oh-story/roles/character-designer.md +4 -4
- package/lib/oh-story/roles/consistency-checker.md +1 -1
- package/lib/oh-story/roles/narrative-writer.md +41 -42
- package/lib/oh-story/roles/story-architect.md +19 -23
- package/lib/oh-story/roles/story-explorer.md +8 -5
- package/lib/oh-story/skills/browser-cdp/SKILL.md +1 -1
- package/lib/oh-story/skills/story/SKILL.md +16 -9
- package/lib/oh-story/skills/story/VERSION +1 -1
- package/lib/oh-story/skills/story/references/author-memory.md +163 -0
- package/lib/oh-story/skills/story/scripts/author_memory_commit.py +871 -0
- package/lib/oh-story/skills/story-cover/SKILL.md +1 -1
- package/lib/oh-story/skills/story-deslop/SKILL.md +10 -5
- package/lib/oh-story/skills/story-deslop/references/anti-ai-writing.md +2 -1
- package/lib/oh-story/skills/story-deslop/references/author-memory.md +163 -0
- package/lib/oh-story/skills/story-deslop/references/banned-words.md +6 -4
- package/lib/oh-story/skills/story-deslop/scripts/author_memory_commit.py +871 -0
- package/lib/oh-story/skills/story-deslop/scripts/check-ai-patterns.js +64 -1
- package/lib/oh-story/skills/story-import/SKILL.md +21 -8
- package/lib/oh-story/skills/story-import/references/format-and-structure.md +9 -10
- package/lib/oh-story/skills/story-import/references/structure-mapping-long.md +2 -2
- package/lib/oh-story/skills/story-import/references/structure-mapping-short.md +1 -1
- package/lib/oh-story/skills/story-import/references/tracking-transaction.md +14 -6
- package/lib/oh-story/skills/story-import/scripts/check-outline-contract.js +251 -0
- package/lib/oh-story/skills/story-import/scripts/tracking_commit.py +98 -9
- package/lib/oh-story/skills/story-import/scripts/wordcount_core.py +246 -0
- package/lib/oh-story/skills/story-long-analyze/SKILL.md +4 -4
- package/lib/oh-story/skills/story-long-scan/SKILL.md +1 -1
- package/lib/oh-story/skills/story-long-write/SKILL.md +35 -21
- package/lib/oh-story/skills/story-long-write/references/anti-ai-writing.md +2 -1
- package/lib/oh-story/skills/story-long-write/references/artifact-protocols.md +7 -3
- package/lib/oh-story/skills/story-long-write/references/author-memory.md +163 -0
- package/lib/oh-story/skills/story-long-write/references/banned-words.md +6 -4
- package/lib/oh-story/skills/story-long-write/references/cross-book-recall.md +6 -6
- package/lib/oh-story/skills/story-long-write/references/genre-prose-cards.md +1 -1
- package/lib/oh-story/skills/story-long-write/references/genre-readers.md +1 -1
- package/lib/oh-story/skills/{story-setup/references/agent-references/hooks-chapter.md → story-long-write/references/long-chapter-hooks.md} +5 -5
- package/lib/oh-story/skills/story-long-write/references/{quality-checklist.md → long-chapter-quality.md} +2 -1
- package/lib/oh-story/skills/story-long-write/references/long-format.md +42 -0
- package/lib/oh-story/skills/{story-setup/references/agent-references/genre-catalog.md → story-long-write/references/long-genre-catalog.md} +11 -97
- package/lib/oh-story/skills/story-long-write/references/{genre-core-mechanics.md → long-genre-mechanics.md} +1 -1
- package/lib/oh-story/skills/story-long-write/references/{reversal-toolkit.md → long-reversal.md} +18 -32
- package/lib/oh-story/skills/story-long-write/references/{hooks-suspense.md → long-suspense.md} +6 -6
- package/lib/oh-story/skills/story-long-write/references/opening-design.md +1 -1
- package/lib/oh-story/skills/story-long-write/references/outline-structure-theory.md +1 -1
- package/lib/oh-story/skills/story-long-write/references/style-genre-modules.md +1 -1
- package/lib/oh-story/skills/story-long-write/references/tracking-transaction.md +14 -6
- package/lib/oh-story/skills/story-long-write/references/workflow-chapter.md +32 -27
- package/lib/oh-story/skills/story-long-write/references/workflow-daily.md +5 -4
- package/lib/oh-story/skills/story-long-write/references/workflow-revision.md +2 -2
- package/lib/oh-story/skills/story-long-write/references/workflow-setup.md +17 -11
- package/lib/oh-story/skills/story-long-write/references/writing-craft.md +54 -54
- package/lib/oh-story/skills/story-long-write/scripts/author_memory_commit.py +871 -0
- package/lib/oh-story/skills/story-long-write/scripts/check-ai-patterns.js +64 -1
- package/lib/oh-story/skills/story-long-write/scripts/check-outline-contract.js +251 -0
- package/lib/oh-story/skills/story-long-write/scripts/storyctl.py +303 -0
- package/lib/oh-story/skills/story-long-write/scripts/tracking_commit.py +98 -9
- package/lib/oh-story/skills/story-long-write/scripts/wordcount_core.py +246 -0
- package/lib/oh-story/skills/story-review/SKILL.md +26 -17
- package/lib/oh-story/skills/story-review/references/anti-ai-writing.md +2 -1
- package/lib/oh-story/skills/story-review/references/author-memory.md +163 -0
- package/lib/oh-story/skills/story-review/references/banned-words.md +6 -4
- package/lib/oh-story/skills/story-review/references/{quality-checklist.md → review-quality.md} +22 -21
- package/lib/oh-story/skills/story-review/references/tracking-transaction.md +14 -6
- package/lib/oh-story/skills/story-review/scripts/author_memory_commit.py +871 -0
- package/lib/oh-story/skills/story-review/scripts/check-ai-patterns.js +64 -1
- package/lib/oh-story/skills/story-review/scripts/tracking_commit.py +98 -9
- package/lib/oh-story/skills/story-review/scripts/wordcount_core.py +246 -0
- package/lib/oh-story/skills/story-setup/SKILL.md +85 -41
- package/lib/oh-story/skills/story-setup/references/agent-references/agent-quality.md +72 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/agent-reference-profiles.md +55 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/anti-ai-writing.md +2 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/banned-words.md +6 -4
- package/lib/oh-story/skills/story-setup/references/agent-references/format-and-structure.md +9 -10
- package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards.md +1 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/genre-readers.md +1 -1
- package/lib/oh-story/skills/{story-short-analyze/references/hooks-chapter.md → story-setup/references/agent-references/long-chapter-hooks.md} +5 -5
- package/lib/oh-story/skills/story-setup/references/agent-references/long-emotional-methods.md +209 -0
- package/lib/oh-story/skills/{story-long-write/references/genre-catalog.md → story-setup/references/agent-references/long-genre-catalog.md} +11 -97
- package/lib/oh-story/skills/story-setup/references/agent-references/{genre-core-mechanics.md → long-genre-mechanics.md} +1 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/long-quality.md +40 -0
- package/lib/oh-story/skills/{story-short-write/references/reversal-toolkit.md → story-setup/references/agent-references/long-reversal.md} +18 -32
- package/lib/oh-story/skills/{story-short-write/references/hooks-suspense.md → story-setup/references/agent-references/long-suspense.md} +6 -6
- package/lib/oh-story/skills/story-setup/references/agent-references/opening-design.md +1 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/{emotional-methods.md → short-emotional-methods.md} +4 -4
- package/lib/oh-story/skills/{story-long-write/references/genre-writing-formulas.md → story-setup/references/agent-references/short-genre-formulas.md} +32 -44
- package/lib/oh-story/skills/story-setup/references/agent-references/short-quality.md +39 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/{reversal-toolkit.md → short-reversal.md} +29 -23
- package/lib/oh-story/skills/story-setup/references/agent-references/{hooks-suspense.md → short-suspense.md} +31 -31
- package/lib/oh-story/skills/story-setup/references/agent-references/style-genre-modules.md +1 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/writing-craft.md +55 -55
- package/lib/oh-story/skills/story-short-analyze/SKILL.md +28 -28
- package/lib/oh-story/skills/story-short-analyze/references/{character-basics.md → analysis-character-basics.md} +1 -1
- package/lib/oh-story/skills/story-short-analyze/references/{character-design-methods.md → analysis-character-design.md} +1 -1
- package/lib/oh-story/skills/story-short-analyze/references/{character-relations.md → analysis-character-relations.md} +1 -1
- package/lib/oh-story/skills/story-short-analyze/references/{hooks-paragraph.md → analysis-paragraph-hooks.md} +1 -1
- package/lib/oh-story/skills/story-short-analyze/references/{genre-readers.md → analysis-reader-profile.md} +2 -2
- package/lib/oh-story/skills/story-short-analyze/references/analysis-report-style.md +45 -0
- package/lib/oh-story/skills/story-short-analyze/references/analysis-short-genres.md +65 -0
- package/lib/oh-story/skills/story-short-analyze/references/analysis-short-hooks.md +77 -0
- package/lib/oh-story/skills/story-short-analyze/references/analysis-short-mechanics.md +82 -0
- package/lib/oh-story/skills/story-short-analyze/references/analysis-short-patterns.md +50 -0
- package/lib/oh-story/skills/story-short-analyze/references/analysis-short-suspense.md +98 -0
- package/lib/oh-story/skills/story-short-analyze/references/{genre-writing-techniques.md → analysis-writing-techniques.md} +21 -19
- package/lib/oh-story/skills/story-short-analyze/references/deconstruction-examples.md +1 -1
- package/lib/oh-story/skills/story-short-analyze/references/material-decomposition.md +21 -28
- package/lib/oh-story/skills/story-short-analyze/references/output-contract.md +1 -1
- package/lib/oh-story/skills/story-short-analyze/references/output-templates.md +17 -17
- package/lib/oh-story/skills/story-short-analyze/references/{quality-checklist.md → source-story-quality.md} +28 -49
- package/lib/oh-story/skills/story-short-analyze/references/zhihu-style.md +2 -2
- package/lib/oh-story/skills/story-short-scan/SKILL.md +1 -1
- package/lib/oh-story/skills/story-short-write/SKILL.md +78 -55
- package/lib/oh-story/skills/story-short-write/references/author-memory.md +163 -0
- package/lib/oh-story/skills/story-short-write/references/banned-words.md +6 -4
- package/lib/oh-story/skills/story-short-write/references/cross-book-recall.md +3 -3
- package/lib/oh-story/skills/story-short-write/references/emotional-methods.md +4 -4
- package/lib/oh-story/skills/story-short-write/references/genre-styles//344/270/226/346/203/205/346/211/223/350/204/270.md +3 -3
- package/lib/oh-story/skills/story-short-write/references/genre-styles//345/217/214/347/224/267/344/270/273.md +2 -2
- package/lib/oh-story/skills/story-short-write/references/genre-styles//345/244/215/344/273/207/346/211/223/350/204/270.md +3 -3
- package/lib/oh-story/skills/story-short-write/references/genre-styles//345/256/205/346/226/227/345/256/253/346/226/227.md +6 -6
- package/lib/oh-story/skills/story-short-write/references/genre-styles//346/200/273/350/243/201/350/261/252/351/227/250.md +3 -3
- package/lib/oh-story/skills/story-short-write/references/genre-styles//346/202/254/347/226/221.md +4 -4
- package/lib/oh-story/skills/story-short-write/references/genre-styles//346/260/221/344/277/227/346/200/252/350/260/210.md +7 -7
- package/lib/oh-story/skills/story-short-write/references/genre-styles//346/262/231/351/233/225/350/204/221/346/264/236.md +5 -5
- package/lib/oh-story/skills/story-short-write/references/genre-styles//347/224/234/345/256/240.md +4 -4
- package/lib/oh-story/skills/story-short-write/references/genre-styles//350/277/275/345/246/273/347/201/253/350/221/254/345/234/272.md +3 -3
- package/lib/oh-story/skills/story-short-write/references/genre-writing-formulas.md +32 -44
- package/lib/oh-story/skills/story-short-write/references/genre-writing-techniques.md +20 -18
- package/lib/oh-story/skills/story-short-write/references/output-contract.md +1 -1
- package/lib/oh-story/skills/story-short-write/references/short-craft.md +18 -18
- package/lib/oh-story/skills/story-short-write/references/short-deslop.md +1 -1
- package/lib/oh-story/skills/story-short-write/references/short-format.md +38 -8
- package/lib/oh-story/skills/story-short-write/references/{quality-checklist.md → short-prose-quality.md} +22 -21
- package/lib/oh-story/skills/story-short-write/references/short-reversal.md +355 -0
- package/lib/oh-story/skills/{story-short-analyze/references/hooks-suspense.md → story-short-write/references/short-suspense.md} +31 -31
- package/lib/oh-story/skills/story-short-write/references/submission-craft.md +2 -2
- package/lib/oh-story/skills/story-short-write/references/writing-workflow.md +31 -22
- package/lib/oh-story/skills/story-short-write/scripts/author_memory_commit.py +871 -0
- package/lib/oh-story/skills/story-short-write/scripts/check-ai-patterns.js +64 -1
- package/lib/oh-story/skills/story-short-write/scripts/check-delivery-contract.js +212 -0
- package/lib/oh-story/skills/story-short-write/scripts/check-phase2-contract.js +363 -0
- package/lib/types/client/drama-production-view.d.ts +34 -0
- package/lib/types/client/drama-production-view.d.ts.map +1 -0
- package/lib/types/client/drama-production.d.ts +92 -0
- package/lib/types/client/drama-production.d.ts.map +1 -0
- package/lib/types/client/file-activity.d.ts +9 -2
- package/lib/types/client/file-activity.d.ts.map +1 -1
- package/lib/types/client/index.d.ts +1 -1
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/production-intents.d.ts +9 -0
- package/lib/types/client/production-intents.d.ts.map +1 -0
- package/lib/types/client/production-prompts.d.ts +9 -0
- package/lib/types/client/production-prompts.d.ts.map +1 -0
- package/lib/types/client/production-runtime.d.ts +58 -0
- package/lib/types/client/production-runtime.d.ts.map +1 -0
- package/lib/types/client/video-studio.d.ts +41 -0
- package/lib/types/client/video-studio.d.ts.map +1 -0
- package/lib/types/client/workbench-ui.d.ts +5 -0
- package/lib/types/client/workbench-ui.d.ts.map +1 -0
- package/lib/types/game-verification.d.ts +16 -0
- package/lib/types/game-verification.d.ts.map +1 -0
- package/lib/types/index.d.ts +5 -2
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/production-intent.d.ts +21 -0
- package/lib/types/production-intent.d.ts.map +1 -0
- package/lib/types/production-tool.d.ts +5 -0
- package/lib/types/production-tool.d.ts.map +1 -0
- package/lib/types/reference-tool.d.ts +6 -0
- package/lib/types/reference-tool.d.ts.map +1 -0
- package/lib/types/role-provider.d.ts.map +1 -1
- package/lib/types/role-tool.d.ts +3 -1
- package/lib/types/role-tool.d.ts.map +1 -1
- package/lib/types/skill-provider.d.ts +7 -0
- package/lib/types/skill-provider.d.ts.map +1 -1
- package/lib/types/video-project.d.ts +42 -0
- package/lib/types/video-project.d.ts.map +1 -0
- package/lib/types/workspace-request-trust.d.ts +6 -0
- package/lib/types/workspace-request-trust.d.ts.map +1 -1
- package/lib/types/workspace-route.d.ts +20 -0
- package/lib/types/workspace-route.d.ts.map +1 -1
- package/lib/video-recap/CHANGELOG.md +282 -0
- package/lib/video-recap/LICENSE +21 -0
- package/lib/video-recap/README.en.md +257 -0
- package/lib/video-recap/README.md +255 -0
- package/lib/video-recap/manifest.json +684 -0
- package/lib/video-recap/skills/video-assemble/SKILL.md +97 -0
- package/lib/video-recap/skills/video-assemble/references/jianying/LICENSE.duo-video +21 -0
- package/lib/video-recap/skills/video-assemble/references/jianying/SOURCE.md +15 -0
- package/lib/video-recap/skills/video-assemble/references/jianying/empty_draft_meta_info.json +69 -0
- package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_combination_segment.json +84 -0
- package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_combination_video_material.json +98 -0
- package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_draft.json +204 -0
- package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_material_video.json +83 -0
- package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_meta_material_value.json +23 -0
- package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_project_info.json +133 -0
- package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_segment.json +70 -0
- package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_text_styles.json +30 -0
- package/lib/video-recap/skills/video-assemble/references/jianying/empty_yj_material_audio.json +41 -0
- package/lib/video-recap/skills/video-assemble/references/jianying/empty_yj_material_text.json +109 -0
- package/lib/video-recap/skills/video-assemble/scripts/artifacts.py +88 -0
- package/lib/video-recap/skills/video-assemble/scripts/assemble.py +345 -0
- package/lib/video-recap/skills/video-assemble/scripts/assemble_constants.py +34 -0
- package/lib/video-recap/skills/video-assemble/scripts/assembly_contract.py +305 -0
- package/lib/video-recap/skills/video-assemble/scripts/assembly_settings.py +94 -0
- package/lib/video-recap/skills/video-assemble/scripts/audio_automation.py +205 -0
- package/lib/video-recap/skills/video-assemble/scripts/audio_mix.py +555 -0
- package/lib/video-recap/skills/video-assemble/scripts/export_jianying.py +261 -0
- package/lib/video-recap/skills/video-assemble/scripts/jianying_builders.py +767 -0
- package/lib/video-recap/skills/video-assemble/scripts/jianying_model.py +111 -0
- package/lib/video-recap/skills/video-assemble/scripts/jianying_optional.py +24 -0
- package/lib/video-recap/skills/video-assemble/scripts/jianying_schema.py +109 -0
- package/lib/video-recap/skills/video-assemble/scripts/jianying_templates.py +37 -0
- package/lib/video-recap/skills/video-assemble/scripts/jianying_timeline_contract.py +191 -0
- package/lib/video-recap/skills/video-assemble/scripts/jianying_tracks.py +75 -0
- package/lib/video-recap/skills/video-assemble/scripts/jianying_writer.py +394 -0
- package/lib/video-recap/skills/video-assemble/scripts/lib.py +239 -0
- package/lib/video-recap/skills/video-assemble/scripts/media.py +243 -0
- package/lib/video-recap/skills/video-assemble/scripts/narration_audio.py +477 -0
- package/lib/video-recap/skills/video-assemble/scripts/render_preflight.py +46 -0
- package/lib/video-recap/skills/video-assemble/scripts/source_subtitles.py +486 -0
- package/lib/video-recap/skills/video-assemble/scripts/subtitle_core.py +376 -0
- package/lib/video-recap/skills/video-assemble/scripts/subtitle_render.py +88 -0
- package/lib/video-recap/skills/video-assemble/scripts/timeline.py +248 -0
- package/lib/video-recap/skills/video-assemble/scripts/timeline_emit.py +89 -0
- package/lib/video-recap/skills/video-assemble/scripts/visual_render.py +512 -0
- package/lib/video-recap/skills/video-cut/SKILL.md +90 -0
- package/lib/video-recap/skills/video-cut/scripts/cut.py +67 -0
- package/lib/video-recap/skills/video-cut/scripts/cut_cli.py +320 -0
- package/lib/video-recap/skills/video-cut/scripts/cut_contract.py +545 -0
- package/lib/video-recap/skills/video-cut/scripts/cut_render.py +348 -0
- package/lib/video-recap/skills/video-cut/scripts/lib.py +77 -0
- package/lib/video-recap/skills/video-cut/scripts/media_geometry.py +341 -0
- package/lib/video-recap/skills/video-cut/scripts/narration_mapping.py +249 -0
- package/lib/video-recap/skills/video-cut/scripts/sentence_boundaries.py +745 -0
- package/lib/video-recap/skills/video-recap/SKILL.md +216 -0
- package/lib/video-recap/skills/video-recap/references/config-playbook.md +61 -0
- package/lib/video-recap/skills/video-recap/references/creative-editing-playbook.md +225 -0
- package/lib/video-recap/skills/video-recap/references/data-schema.md +534 -0
- package/lib/video-recap/skills/video-recap/references/research-guide.md +74 -0
- package/lib/video-recap/skills/video-recap/references/shift-left-qc-schema.md +23 -0
- package/lib/video-recap/skills/video-recap/references/timeline-and-jianying.md +251 -0
- package/lib/video-recap/skills/video-recap/scripts/doctor.py +483 -0
- package/lib/video-recap/skills/video-recap/scripts/final_qc.py +737 -0
- package/lib/video-recap/skills/video-recap/scripts/lib.py +242 -0
- package/lib/video-recap/skills/video-recap/scripts/materials.py +457 -0
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc.py +95 -0
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc_client.py +18 -0
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc_contract.py +9 -0
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc_evidence.py +371 -0
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc_observations.py +201 -0
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc_payload.py +227 -0
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc_report.py +362 -0
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc_runner.py +129 -0
- package/lib/video-recap/skills/video-recap/scripts/qc_contract.py +338 -0
- package/lib/video-recap/skills/video-recap/scripts/recap.py +9 -0
- package/lib/video-recap/skills/video-recap/scripts/recap_cli.py +140 -0
- package/lib/video-recap/skills/video-recap/scripts/recap_inspect.py +550 -0
- package/lib/video-recap/skills/video-recap/scripts/recap_review.py +77 -0
- package/lib/video-recap/skills/video-recap/scripts/recap_runner.py +654 -0
- package/lib/video-recap/skills/video-recap/scripts/recap_runtime.py +335 -0
- package/lib/video-recap/skills/video-recap/scripts/recap_stage_qc.py +217 -0
- package/lib/video-recap/skills/video-recap/scripts/recap_timeline.py +795 -0
- package/lib/video-recap/skills/video-script/SKILL.md +248 -0
- package/lib/video-recap/skills/video-script/references/creative-editing-playbook.md +225 -0
- package/lib/video-recap/skills/video-script/references/research-guide.md +75 -0
- package/lib/video-recap/skills/video-script/scripts/agent_brief.py +399 -0
- package/lib/video-recap/skills/video-script/scripts/agent_text.py +410 -0
- package/lib/video-recap/skills/video-script/scripts/brief_context.py +403 -0
- package/lib/video-recap/skills/video-script/scripts/brief_inputs.py +332 -0
- package/lib/video-recap/skills/video-script/scripts/brief_timeline.py +442 -0
- package/lib/video-recap/skills/video-script/scripts/deslop_qc.py +237 -0
- package/lib/video-recap/skills/video-script/scripts/evidence_bundle.py +492 -0
- package/lib/video-recap/skills/video-script/scripts/lib.py +327 -0
- package/lib/video-recap/skills/video-script/scripts/narration.py +12 -0
- package/lib/video-recap/skills/video-script/scripts/narration_lint.py +678 -0
- package/lib/video-recap/skills/video-script/scripts/review.py +42 -0
- package/lib/video-recap/skills/video-script/scripts/review_grounding.py +262 -0
- package/lib/video-recap/skills/video-script/scripts/review_response.py +761 -0
- package/lib/video-recap/skills/video-script/scripts/review_runner.py +200 -0
- package/lib/video-recap/skills/video-script/scripts/speech_ownership.py +204 -0
- package/lib/video-recap/skills/video-script/scripts/timeline_fusion.py +199 -0
- package/lib/video-recap/skills/video-script/scripts/validate.py +164 -0
- package/lib/video-recap/skills/video-understanding/SKILL.md +75 -0
- package/lib/video-recap/skills/video-understanding/references/data-schema.md +299 -0
- package/lib/video-recap/skills/video-understanding/references/prompt-templates.md +37 -0
- package/lib/video-recap/skills/video-understanding/references/research-guide.md +74 -0
- package/lib/video-recap/skills/video-understanding/scripts/agent_brief.py +399 -0
- package/lib/video-recap/skills/video-understanding/scripts/agent_text.py +410 -0
- package/lib/video-recap/skills/video-understanding/scripts/asr.py +260 -0
- package/lib/video-recap/skills/video-understanding/scripts/brief.py +12 -0
- package/lib/video-recap/skills/video-understanding/scripts/brief_context.py +403 -0
- package/lib/video-recap/skills/video-understanding/scripts/brief_inputs.py +332 -0
- package/lib/video-recap/skills/video-understanding/scripts/brief_timeline.py +442 -0
- package/lib/video-recap/skills/video-understanding/scripts/consolidate.py +647 -0
- package/lib/video-recap/skills/video-understanding/scripts/deslop_qc.py +237 -0
- package/lib/video-recap/skills/video-understanding/scripts/detect.py +513 -0
- package/lib/video-recap/skills/video-understanding/scripts/extract.py +62 -0
- package/lib/video-recap/skills/video-understanding/scripts/lib.py +470 -0
- package/lib/video-recap/skills/video-understanding/scripts/narration_lint.py +678 -0
- package/lib/video-recap/skills/video-understanding/scripts/speech_ownership.py +204 -0
- package/lib/video-recap/skills/video-understanding/scripts/storyboard.py +507 -0
- package/lib/video-recap/skills/video-understanding/scripts/timeline_fusion.py +199 -0
- package/lib/video-recap/skills/video-understanding/scripts/understand.py +9 -0
- package/lib/video-recap/skills/video-understanding/scripts/understanding_brief.py +216 -0
- package/lib/video-recap/skills/video-understanding/scripts/understanding_cache.py +327 -0
- package/lib/video-recap/skills/video-understanding/scripts/understanding_runner.py +367 -0
- package/lib/video-recap/skills/video-understanding/scripts/understanding_storyboard.py +162 -0
- package/lib/video-recap/skills/video-understanding/scripts/vlm.py +705 -0
- package/lib/video-recap/skills/video-voiceover/SKILL.md +81 -0
- package/lib/video-recap/skills/video-voiceover/scripts/dub.py +637 -0
- package/lib/video-recap/skills/video-voiceover/scripts/fish_audio.py +109 -0
- package/lib/video-recap/skills/video-voiceover/scripts/lib.py +421 -0
- package/lib/video-recap/skills/video-voiceover/scripts/tts_audio.py +113 -0
- package/lib/video-recap/skills/video-voiceover/scripts/voiceover.py +782 -0
- package/package.json +57 -41
- package/lib/drama/skills/short-drama/references/lifecycle-commands.md +0 -130
- package/lib/oh-story/skills/story-deslop/scripts/check-outline-copy.js +0 -241
- package/lib/oh-story/skills/story-long-write/references/format-and-structure.md +0 -169
- package/lib/oh-story/skills/story-setup/references/agent-references/genre-writing-formulas.md +0 -444
- package/lib/oh-story/skills/story-setup/references/agent-references/quality-checklist.md +0 -247
- package/lib/oh-story/skills/story-short-analyze/references/anti-ai-writing.md +0 -582
- package/lib/oh-story/skills/story-short-analyze/references/banned-words.md +0 -147
- package/lib/oh-story/skills/story-short-analyze/references/genre-catalog.md +0 -490
- package/lib/oh-story/skills/story-short-analyze/references/genre-core-mechanics.md +0 -425
- package/lib/oh-story/skills/story-short-analyze/references/genre-writing-formulas.md +0 -453
- /package/lib/oh-story/skills/{story-long-write/references/hooks-chapter.md → story-setup/references/agent-references/short-chapter-hooks.md} +0 -0
- /package/lib/oh-story/skills/{story-long-write/references/hooks-paragraph.md → story-setup/references/agent-references/short-paragraph-hooks.md} +0 -0
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
"""Remap cut evidence and format output-timeline brief directives."""
|
|
2
|
+
|
|
3
|
+
import importlib.util
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
import json
|
|
7
|
+
|
|
8
|
+
import math
|
|
9
|
+
|
|
10
|
+
import re
|
|
11
|
+
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
from lib import CONFIG, stable_hash
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
try:
|
|
18
|
+
from deslop_qc import analyze_deslop_qc
|
|
19
|
+
except ModuleNotFoundError:
|
|
20
|
+
_deslop_qc_path = Path(__file__).with_name("deslop_qc.py")
|
|
21
|
+
_deslop_qc_spec = importlib.util.spec_from_file_location(
|
|
22
|
+
"deslop_qc", _deslop_qc_path
|
|
23
|
+
)
|
|
24
|
+
if _deslop_qc_spec is None or _deslop_qc_spec.loader is None:
|
|
25
|
+
raise
|
|
26
|
+
_deslop_qc_module = importlib.util.module_from_spec(_deslop_qc_spec)
|
|
27
|
+
_deslop_qc_spec.loader.exec_module(_deslop_qc_module)
|
|
28
|
+
analyze_deslop_qc = _deslop_qc_module.analyze_deslop_qc
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _parse_target_seconds(value):
|
|
32
|
+
"""Parse a cut-mode target duration ("30m" / "600" / "1h5m" / "00:30:00") to seconds.
|
|
33
|
+
|
|
34
|
+
Mirrors the local clip-plan contract closely enough to size the brief; returns None
|
|
35
|
+
on unparseable input so the brief simply falls back to the source duration.
|
|
36
|
+
"""
|
|
37
|
+
if value is None:
|
|
38
|
+
return None
|
|
39
|
+
if isinstance(value, (int, float)):
|
|
40
|
+
return float(value) if value > 0 else None
|
|
41
|
+
text = str(value).strip().lower()
|
|
42
|
+
if not text:
|
|
43
|
+
return None
|
|
44
|
+
try:
|
|
45
|
+
if ":" in text:
|
|
46
|
+
parts = [float(p) for p in text.split(":")]
|
|
47
|
+
if any(p < 0 for p in parts):
|
|
48
|
+
return None
|
|
49
|
+
if len(parts) == 2:
|
|
50
|
+
seconds = parts[0] * 60 + parts[1]
|
|
51
|
+
elif len(parts) == 3:
|
|
52
|
+
seconds = parts[0] * 3600 + parts[1] * 60 + parts[2]
|
|
53
|
+
else:
|
|
54
|
+
return None
|
|
55
|
+
return seconds if seconds > 0 else None
|
|
56
|
+
factors = {"ms": 0.001, "s": 1, "m": 60, "h": 3600}
|
|
57
|
+
seconds = 0.0
|
|
58
|
+
matched = False
|
|
59
|
+
pos = 0
|
|
60
|
+
for m in re.finditer(r"([0-9]+(?:\.[0-9]+)?)(ms|s|m|h)?", text):
|
|
61
|
+
if m.start() != pos:
|
|
62
|
+
break
|
|
63
|
+
pos = m.end()
|
|
64
|
+
matched = True
|
|
65
|
+
seconds += float(m.group(1)) * factors[m.group(2) or "s"]
|
|
66
|
+
if not matched or pos != len(text):
|
|
67
|
+
return None
|
|
68
|
+
return seconds if seconds > 0 else None
|
|
69
|
+
except (ValueError, TypeError):
|
|
70
|
+
return None
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _format_research_directive(work_dir, substrate):
|
|
74
|
+
"""A loud, actionable research-first directive — but ONLY when the substrate is too thin
|
|
75
|
+
for real commentary (no dialogue/story spine) and no background_research.json exists yet.
|
|
76
|
+
|
|
77
|
+
The narration's quality ceiling is how much story context the agent has: with only frame
|
|
78
|
+
descriptions it can only narrate pixels, so research the title FIRST (see
|
|
79
|
+
references/research-guide.md). Fires only for thin/empty substrate — NOT merely because a
|
|
80
|
+
title was given — so a dialogue-rich titled run is never nagged.
|
|
81
|
+
"""
|
|
82
|
+
if (Path(work_dir) / "background_research.json").exists():
|
|
83
|
+
return [] # already researched; _format_background_research surfaces it
|
|
84
|
+
if not (substrate and substrate.get("level") in ("thin", "empty")):
|
|
85
|
+
return [] # rich enough to write from dialogue/spine; do not nag
|
|
86
|
+
context = str(CONFIG.get("context_info") or "").strip()
|
|
87
|
+
return [
|
|
88
|
+
"## ⚑ Research the story FIRST (do this before writing narration)",
|
|
89
|
+
"",
|
|
90
|
+
"Reason: the understanding substrate is thin — no dialogue/story spine, only frame",
|
|
91
|
+
"descriptions, so without research the narration can only describe pixels.",
|
|
92
|
+
"1. Pull the title/keywords from `--context`, the filename, or the user's description"
|
|
93
|
+
+ (f" (context: {context})." if context else "."),
|
|
94
|
+
"2. Use any available web-search/browser tool to look up synopsis, characters, and",
|
|
95
|
+
" relationships (see `references/research-guide.md`).",
|
|
96
|
+
"3. Write `work_dir/background_research.json`, then re-read this brief and write narration",
|
|
97
|
+
" that names people and reads the picture through the plot.",
|
|
98
|
+
"4. If no tool/network or nothing found: skip — keep beats sparse and strictly grounded in",
|
|
99
|
+
" the visible ASR/frame evidence rather than inventing drama.",
|
|
100
|
+
"",
|
|
101
|
+
]
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _load_cut_output_spans_for_brief(work_dir, *, required=False):
|
|
105
|
+
"""Load fresh source→output spans for cut pass 2 brief evidence.
|
|
106
|
+
|
|
107
|
+
Pass 2 narration is authored against edited_source.mp4's OUTPUT timeline, so
|
|
108
|
+
ASR chunks and timeline_fusion must use the same OUTPUT clock. Before pass 2
|
|
109
|
+
(no edited_source.mp4 yet), callers may fall back to source-time evidence; once
|
|
110
|
+
pass 2 exists, missing/stale validated spans are a hard contract failure.
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
def fail(reason):
|
|
114
|
+
if required:
|
|
115
|
+
raise SystemExit(
|
|
116
|
+
"cut pass2 brief requires fresh clip_plan_validated.json with explicit "
|
|
117
|
+
f"finite source/output spans ({reason})"
|
|
118
|
+
)
|
|
119
|
+
return None
|
|
120
|
+
|
|
121
|
+
work_dir = Path(work_dir)
|
|
122
|
+
if not (work_dir / "edited_source.mp4").exists():
|
|
123
|
+
return None
|
|
124
|
+
validated_path = work_dir / "clip_plan_validated.json"
|
|
125
|
+
if not validated_path.exists():
|
|
126
|
+
return fail("missing clip_plan_validated.json")
|
|
127
|
+
try:
|
|
128
|
+
plan = json.loads(validated_path.read_text(encoding="utf-8"))
|
|
129
|
+
except (json.JSONDecodeError, OSError):
|
|
130
|
+
return fail("invalid clip_plan_validated.json")
|
|
131
|
+
if not isinstance(plan, dict):
|
|
132
|
+
return fail("clip_plan_validated.json is not an object")
|
|
133
|
+
raw_path = work_dir / "clip_plan.json"
|
|
134
|
+
if raw_path.exists():
|
|
135
|
+
try:
|
|
136
|
+
raw_plan = json.loads(raw_path.read_text(encoding="utf-8"))
|
|
137
|
+
except (json.JSONDecodeError, OSError):
|
|
138
|
+
return fail("invalid clip_plan.json")
|
|
139
|
+
if plan.get("raw_plan_fingerprint") != stable_hash(raw_plan):
|
|
140
|
+
return fail("stale clip_plan_validated.json")
|
|
141
|
+
clips = plan.get("clips")
|
|
142
|
+
if not isinstance(clips, list):
|
|
143
|
+
return fail("clips is not a list")
|
|
144
|
+
spans = []
|
|
145
|
+
for clip in clips:
|
|
146
|
+
if not isinstance(clip, dict):
|
|
147
|
+
continue
|
|
148
|
+
if not all(
|
|
149
|
+
key in clip
|
|
150
|
+
for key in ("source_start", "source_end", "output_start", "output_end")
|
|
151
|
+
):
|
|
152
|
+
return fail("clip missing source/output span fields")
|
|
153
|
+
try:
|
|
154
|
+
ss = float(clip["source_start"])
|
|
155
|
+
se = float(clip["source_end"])
|
|
156
|
+
os_ = float(clip["output_start"])
|
|
157
|
+
oe = float(clip["output_end"])
|
|
158
|
+
except (TypeError, ValueError):
|
|
159
|
+
return fail("non-numeric clip span")
|
|
160
|
+
if not all(math.isfinite(x) for x in (ss, se, os_, oe)):
|
|
161
|
+
return fail("non-finite clip span")
|
|
162
|
+
if se <= ss or oe <= os_:
|
|
163
|
+
return fail("non-positive clip span")
|
|
164
|
+
spans.append(
|
|
165
|
+
{
|
|
166
|
+
"source_start": ss,
|
|
167
|
+
"source_end": se,
|
|
168
|
+
"output_start": os_,
|
|
169
|
+
"output_end": oe,
|
|
170
|
+
}
|
|
171
|
+
)
|
|
172
|
+
return spans or fail("no valid clips")
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def _source_output_overlaps_for_brief(start, end, spans):
|
|
176
|
+
overlaps = []
|
|
177
|
+
for span in spans or []:
|
|
178
|
+
source_start = max(float(start), span["source_start"])
|
|
179
|
+
source_end = min(float(end), span["source_end"])
|
|
180
|
+
if source_end <= source_start:
|
|
181
|
+
continue
|
|
182
|
+
output_start = span["output_start"] + (source_start - span["source_start"])
|
|
183
|
+
output_end = span["output_start"] + (source_end - span["source_start"])
|
|
184
|
+
overlaps.append(
|
|
185
|
+
{
|
|
186
|
+
"source_start": source_start,
|
|
187
|
+
"source_end": source_end,
|
|
188
|
+
"output_start": output_start,
|
|
189
|
+
"output_end": output_end,
|
|
190
|
+
}
|
|
191
|
+
)
|
|
192
|
+
return overlaps
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def _remap_frame_facts_for_brief(frame_facts, overlap):
|
|
196
|
+
if not isinstance(frame_facts, dict):
|
|
197
|
+
return frame_facts
|
|
198
|
+
out = {}
|
|
199
|
+
for raw_ts, vals in frame_facts.items():
|
|
200
|
+
try:
|
|
201
|
+
ts = float(raw_ts)
|
|
202
|
+
except (TypeError, ValueError):
|
|
203
|
+
continue
|
|
204
|
+
if not (overlap["source_start"] <= ts <= overlap["source_end"]):
|
|
205
|
+
continue
|
|
206
|
+
out_ts = overlap["output_start"] + (ts - overlap["source_start"])
|
|
207
|
+
out[f"{out_ts:.3f}"] = vals
|
|
208
|
+
return out
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def _remap_scenes_to_output_for_brief(scenes, spans):
|
|
212
|
+
if not spans:
|
|
213
|
+
return scenes or []
|
|
214
|
+
out = []
|
|
215
|
+
for scene in scenes or []:
|
|
216
|
+
if not isinstance(scene, dict):
|
|
217
|
+
continue
|
|
218
|
+
try:
|
|
219
|
+
start = float(scene.get("start", 0))
|
|
220
|
+
end = float(scene.get("end", start))
|
|
221
|
+
except (TypeError, ValueError):
|
|
222
|
+
continue
|
|
223
|
+
overlaps = _source_output_overlaps_for_brief(start, end, spans)
|
|
224
|
+
for part_idx, overlap in enumerate(overlaps):
|
|
225
|
+
item = dict(scene)
|
|
226
|
+
item["start"] = round(overlap["output_start"], 3)
|
|
227
|
+
item["end"] = round(overlap["output_end"], 3)
|
|
228
|
+
item["frame_facts"] = _remap_frame_facts_for_brief(
|
|
229
|
+
scene.get("frame_facts"), overlap
|
|
230
|
+
)
|
|
231
|
+
if len(overlaps) > 1:
|
|
232
|
+
item["scene_id"] = f"{scene.get('scene_id', '?')}.{part_idx}"
|
|
233
|
+
out.append(item)
|
|
234
|
+
out.sort(key=lambda x: (float(x.get("start", 0)), float(x.get("end", 0))))
|
|
235
|
+
return out
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def _remap_segments_to_output_for_brief(segments, spans):
|
|
239
|
+
if not spans:
|
|
240
|
+
return segments or []
|
|
241
|
+
out = []
|
|
242
|
+
for seg in segments or []:
|
|
243
|
+
if not isinstance(seg, dict):
|
|
244
|
+
continue
|
|
245
|
+
try:
|
|
246
|
+
start = float(seg.get("start", 0))
|
|
247
|
+
end = float(seg.get("end", start))
|
|
248
|
+
except (TypeError, ValueError):
|
|
249
|
+
continue
|
|
250
|
+
if end <= start:
|
|
251
|
+
continue
|
|
252
|
+
for overlap in _source_output_overlaps_for_brief(start, end, spans):
|
|
253
|
+
item = dict(seg)
|
|
254
|
+
item["start"] = round(overlap["output_start"], 3)
|
|
255
|
+
item["end"] = round(overlap["output_end"], 3)
|
|
256
|
+
if "duration" in item:
|
|
257
|
+
item["duration"] = round(item["end"] - item["start"], 3)
|
|
258
|
+
out.append(item)
|
|
259
|
+
out.sort(key=lambda x: (float(x.get("start", 0)), float(x.get("end", 0))))
|
|
260
|
+
return out
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def _remap_brief_evidence_to_output_timeline(
|
|
264
|
+
work_dir, scenes_analysis, asr_result, silence_periods, *, required=False
|
|
265
|
+
):
|
|
266
|
+
spans = _load_cut_output_spans_for_brief(work_dir, required=required)
|
|
267
|
+
if not spans:
|
|
268
|
+
return scenes_analysis, asr_result, silence_periods
|
|
269
|
+
return (
|
|
270
|
+
_remap_scenes_to_output_for_brief(scenes_analysis, spans),
|
|
271
|
+
_remap_segments_to_output_for_brief(asr_result, spans),
|
|
272
|
+
_remap_segments_to_output_for_brief(silence_periods, spans),
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def _sentence_entry_anchors_for_brief(work_dir, edit_mode):
|
|
277
|
+
"""Load sentence anchors and remap them to cut OUTPUT time when needed."""
|
|
278
|
+
work_dir = Path(work_dir)
|
|
279
|
+
source_path = work_dir / "speech_boundary_anchors.json"
|
|
280
|
+
try:
|
|
281
|
+
payload = json.loads(source_path.read_text(encoding="utf-8"))
|
|
282
|
+
except (OSError, json.JSONDecodeError):
|
|
283
|
+
payload = {}
|
|
284
|
+
anchors = payload.get("sentence_anchors", []) if isinstance(payload, dict) else []
|
|
285
|
+
anchors = [dict(item) for item in anchors if isinstance(item, dict)]
|
|
286
|
+
if edit_mode != "cut" or not (work_dir / "edited_source.mp4").exists():
|
|
287
|
+
return anchors
|
|
288
|
+
|
|
289
|
+
spans = _load_cut_output_spans_for_brief(work_dir, required=True)
|
|
290
|
+
remapped = []
|
|
291
|
+
for anchor in anchors:
|
|
292
|
+
try:
|
|
293
|
+
source_time = float(anchor.get("time"))
|
|
294
|
+
except (TypeError, ValueError):
|
|
295
|
+
continue
|
|
296
|
+
for span in spans:
|
|
297
|
+
if span["source_start"] - 0.05 <= source_time <= span["source_end"] + 0.05:
|
|
298
|
+
item = dict(anchor)
|
|
299
|
+
item["source_time"] = round(source_time, 3)
|
|
300
|
+
item["time"] = round(
|
|
301
|
+
span["output_start"] + source_time - span["source_start"], 3
|
|
302
|
+
)
|
|
303
|
+
try:
|
|
304
|
+
source_pause_start = float(
|
|
305
|
+
anchor.get("pause_start", source_time - 0.12)
|
|
306
|
+
)
|
|
307
|
+
except (TypeError, ValueError):
|
|
308
|
+
source_pause_start = source_time - 0.12
|
|
309
|
+
# Preserve the measured safe pause in OUTPUT time too. Leaving pause_start
|
|
310
|
+
# in SOURCE time made cut-mode lint compare two different clocks.
|
|
311
|
+
source_pause_start = max(
|
|
312
|
+
span["source_start"], min(source_pause_start, source_time)
|
|
313
|
+
)
|
|
314
|
+
item["source_pause_start"] = round(source_pause_start, 3)
|
|
315
|
+
item["pause_start"] = round(
|
|
316
|
+
span["output_start"] + source_pause_start - span["source_start"], 3
|
|
317
|
+
)
|
|
318
|
+
remapped.append(item)
|
|
319
|
+
|
|
320
|
+
speech_rows = []
|
|
321
|
+
for name in ("asr_result.json", "asr_clean.json"):
|
|
322
|
+
try:
|
|
323
|
+
raw = json.loads((work_dir / name).read_text(encoding="utf-8"))
|
|
324
|
+
except (OSError, json.JSONDecodeError):
|
|
325
|
+
continue
|
|
326
|
+
if isinstance(raw, dict):
|
|
327
|
+
raw = raw.get("segments", [])
|
|
328
|
+
if isinstance(raw, list):
|
|
329
|
+
candidate = [
|
|
330
|
+
item
|
|
331
|
+
for item in raw
|
|
332
|
+
if isinstance(item, dict) and str(item.get("text") or "").strip()
|
|
333
|
+
]
|
|
334
|
+
if candidate:
|
|
335
|
+
speech_rows = candidate
|
|
336
|
+
break
|
|
337
|
+
try:
|
|
338
|
+
raw_quiet = json.loads(
|
|
339
|
+
(work_dir / "silence_periods.json").read_text(encoding="utf-8")
|
|
340
|
+
)
|
|
341
|
+
except (OSError, json.JSONDecodeError):
|
|
342
|
+
raw_quiet = []
|
|
343
|
+
quiet_rows = [
|
|
344
|
+
item
|
|
345
|
+
for item in raw_quiet
|
|
346
|
+
if isinstance(item, dict) and not bool(item.get("has_speech", False))
|
|
347
|
+
] if isinstance(raw_quiet, list) else []
|
|
348
|
+
out_payload = {
|
|
349
|
+
"schema_version": 2,
|
|
350
|
+
"artifact": "speech_boundary_anchors_output.json",
|
|
351
|
+
"timeline": "cut_output",
|
|
352
|
+
"source_artifact": "speech_boundary_anchors.json",
|
|
353
|
+
"clip_plan_fingerprint": stable_hash(
|
|
354
|
+
json.loads((work_dir / "clip_plan_validated.json").read_text(encoding="utf-8"))
|
|
355
|
+
),
|
|
356
|
+
"sentence_anchors": sorted(remapped, key=lambda item: float(item["time"])),
|
|
357
|
+
"speech_spans": _remap_segments_to_output_for_brief(speech_rows, spans),
|
|
358
|
+
"quiet_windows": _remap_segments_to_output_for_brief(quiet_rows, spans),
|
|
359
|
+
}
|
|
360
|
+
(work_dir / "speech_boundary_anchors_output.json").write_text(
|
|
361
|
+
json.dumps(out_payload, ensure_ascii=False, indent=2), encoding="utf-8"
|
|
362
|
+
)
|
|
363
|
+
return out_payload["sentence_anchors"]
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
def _format_sentence_entry_anchors_for_brief(work_dir, edit_mode):
|
|
367
|
+
anchors = []
|
|
368
|
+
for anchor in _sentence_entry_anchors_for_brief(work_dir, edit_mode):
|
|
369
|
+
if anchor.get("confidence") not in {"high", "medium"}:
|
|
370
|
+
continue
|
|
371
|
+
try:
|
|
372
|
+
when = float(anchor.get("time"))
|
|
373
|
+
except (TypeError, ValueError):
|
|
374
|
+
continue
|
|
375
|
+
anchors.append((when, anchor))
|
|
376
|
+
if not anchors:
|
|
377
|
+
return []
|
|
378
|
+
lines = [
|
|
379
|
+
"## 原声句末安全切入点",
|
|
380
|
+
"",
|
|
381
|
+
"这些时间是 ASR 句末标点与短声学停顿对齐后的旁白安全入口。旁白在原声已开始后切入时,"
|
|
382
|
+
"必须从其中一个点开始;否则会在 TTS 前被 `interrupts_source_sentence` 硬阻断。",
|
|
383
|
+
"- 调整方式:优先把 `start` 移到建议锚点;放不下时缩短文本、移动整块或删除该旁白,不能让脚本静默挪音频。",
|
|
384
|
+
'- 原声句子完整性是硬约束:切入块写 `"source_entry_policy": "sentence_boundary"`;'
|
|
385
|
+
"不存在 `intentional_interrupt` 绕过方式。没有后续可靠句末锚点时,移动、缩短或删除旁白块。",
|
|
386
|
+
]
|
|
387
|
+
for when, anchor in anchors:
|
|
388
|
+
tail = str(anchor.get("text_tail") or "").strip()
|
|
389
|
+
confidence = anchor.get("confidence", "unknown")
|
|
390
|
+
source_suffix = ""
|
|
391
|
+
if "source_time" in anchor:
|
|
392
|
+
source_suffix = f" (SOURCE {float(anchor['source_time']):.2f}s)"
|
|
393
|
+
lines.append(f"- {when:.2f}s [{confidence}]{source_suffix} {tail}")
|
|
394
|
+
lines.append("")
|
|
395
|
+
return lines
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
def _format_output_clip_list(work_dir):
|
|
399
|
+
"""List the kept clips on the OUTPUT timeline (cut-first/narrate-second pass 2), so the
|
|
400
|
+
agent narrates against the real rendered cut instead of the source timeline."""
|
|
401
|
+
path = Path(work_dir) / "clip_plan_validated.json"
|
|
402
|
+
if not path.exists():
|
|
403
|
+
return []
|
|
404
|
+
try:
|
|
405
|
+
plan = json.loads(path.read_text(encoding="utf-8"))
|
|
406
|
+
except (ValueError, OSError):
|
|
407
|
+
return []
|
|
408
|
+
clips = (plan.get("clips") if isinstance(plan, dict) else plan) or []
|
|
409
|
+
out = ["## Kept clips on the OUTPUT timeline", ""]
|
|
410
|
+
for c in clips:
|
|
411
|
+
if not isinstance(c, dict):
|
|
412
|
+
continue
|
|
413
|
+
try:
|
|
414
|
+
os_, oe = float(c.get("output_start")), float(c.get("output_end"))
|
|
415
|
+
ss, se = float(c.get("source_start")), float(c.get("source_end"))
|
|
416
|
+
except (TypeError, ValueError):
|
|
417
|
+
continue
|
|
418
|
+
reason = str(c.get("reason", "")).strip()
|
|
419
|
+
source_id = c.get("source_id", c.get("source", "0"))
|
|
420
|
+
clip_id = c.get("id", c.get("clip_id", "?"))
|
|
421
|
+
out.append(
|
|
422
|
+
f"- OUTPUT {os_:.1f}–{oe:.1f}s ← SOURCE[{source_id}] {ss:.1f}–{se:.1f}s (clip_id={clip_id})"
|
|
423
|
+
+ (f" — {reason}" if reason else "")
|
|
424
|
+
)
|
|
425
|
+
out.append("")
|
|
426
|
+
return out if len(out) > 2 else []
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
def _write_deslop_qc_requirements(work_dir):
|
|
430
|
+
"""Write the stable deslop QC contract consumed by deslop_qc.py.
|
|
431
|
+
|
|
432
|
+
style_card_required defaults to False (advisory): a missing style_card.json is a
|
|
433
|
+
warning, not a render-blocking error. A future opt-in run can set it True to make
|
|
434
|
+
style_card.json a hard requirement — deslop_qc.py reads that field.
|
|
435
|
+
"""
|
|
436
|
+
payload = {
|
|
437
|
+
"schema_version": 1,
|
|
438
|
+
"style_card_required": False,
|
|
439
|
+
}
|
|
440
|
+
path = Path(work_dir) / "deslop_qc_requirements.json"
|
|
441
|
+
path.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
|
|
442
|
+
return path
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
"""Deterministic narration readability/QC scanner.
|
|
2
|
+
|
|
3
|
+
This module is intentionally report-only. It flags local readability and
|
|
4
|
+
packaging hygiene risks for video narration; it is not an AIGC detector and it
|
|
5
|
+
never rewrites text.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
import re
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
CONTRACT = (
|
|
16
|
+
"Local readability/QC report only: this is not an AIGC detector, does not "
|
|
17
|
+
"claim AI-generation accuracy, and never rewrites text. Corrections remain "
|
|
18
|
+
"human/agent rewrite work."
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
EM_DASH_RE = re.compile(r"——|—")
|
|
22
|
+
NEGATIVE_FLIP_RE = re.compile(r"并?不是[^。!?!?;;\.]{0,40}而是")
|
|
23
|
+
# Catch verbatim copy of the brief's example scaffolding without over-matching real text.
|
|
24
|
+
# The bracketed role token 【主角】 is the placeholder; anchor the pronoun signal to the
|
|
25
|
+
# example's exact phrase "TA要赌上" so the legitimate gender-neutral pronoun "TA" (他/她) +
|
|
26
|
+
# 要 (idiomatic 解说 suspense device "凶手就在其中,TA要做的下一件事…") is NOT hard-blocked.
|
|
27
|
+
PLACEHOLDER_RE = re.compile(r"【主角】|TA要赌上")
|
|
28
|
+
|
|
29
|
+
CLICHE_TERMS = [
|
|
30
|
+
"命运的齿轮", "殊不知", "与此同时", "一场阴谋", "背后真相", "不为人知",
|
|
31
|
+
"故事就此展开", "命运", "真相", "秘密", "危机", "反转", "救赎",
|
|
32
|
+
]
|
|
33
|
+
ABSTRACT_TERMS = ["人性", "成长", "救赎", "命运", "时代", "宿命", "选择", "意义", "价值", "情感"]
|
|
34
|
+
REASONING_MARKERS = ["因为", "所以", "因此", "也就是说", "换句话说", "这意味着", "原因是", "可见"]
|
|
35
|
+
METAPHOR_MARKERS = ["像", "仿佛", "好似", "犹如", "如同", "一把", "一场", "一张", "棋局", "风暴"]
|
|
36
|
+
CONNECTIVE_MARKERS = ["但", "却", "而", "于是", "随后", "直到", "结果", "因为", "所以", "这时", "最后"]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _sentence_pieces(text: str) -> list[str]:
|
|
40
|
+
text = re.sub(r"\s+", " ", str(text or "")).strip()
|
|
41
|
+
if not text:
|
|
42
|
+
return []
|
|
43
|
+
parts = re.split(r"([。!?!?;;\.])", text)
|
|
44
|
+
out: list[str] = []
|
|
45
|
+
for idx in range(0, len(parts), 2):
|
|
46
|
+
body = parts[idx].strip()
|
|
47
|
+
punct = parts[idx + 1] if idx + 1 < len(parts) else ""
|
|
48
|
+
if body or punct:
|
|
49
|
+
out.append((body + punct).strip())
|
|
50
|
+
return out or [text]
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _text_units(text: str) -> int:
|
|
54
|
+
text = str(text or "")
|
|
55
|
+
if re.search(r"[\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff]", text):
|
|
56
|
+
return len(re.sub(r"\s+", "", text))
|
|
57
|
+
return len(re.findall(r"\b\w+\b", text))
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _normalise_segments(payload: Any, *, source: str) -> list[dict[str, Any]]:
|
|
61
|
+
if isinstance(payload, str):
|
|
62
|
+
return [{"source": source, "index": None, "text": payload}]
|
|
63
|
+
if not isinstance(payload, list):
|
|
64
|
+
return []
|
|
65
|
+
segments: list[dict[str, Any]] = []
|
|
66
|
+
for idx, item in enumerate(payload):
|
|
67
|
+
if isinstance(item, dict):
|
|
68
|
+
key = "narration" if source == "narration" else "text"
|
|
69
|
+
text = str(item.get(key, item.get("text", item.get("narration", ""))) or "").strip()
|
|
70
|
+
else:
|
|
71
|
+
text = str(item or "").strip()
|
|
72
|
+
if text:
|
|
73
|
+
segments.append({"source": source, "index": idx, "text": text})
|
|
74
|
+
return segments
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _load_json(path: Path) -> tuple[Any, str | None]:
|
|
78
|
+
try:
|
|
79
|
+
return json.loads(path.read_text(encoding="utf-8")), None
|
|
80
|
+
except Exception as exc: # intentionally broad: malformed artifacts are QC findings
|
|
81
|
+
return None, str(exc)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _load_original_subtitles(work_dir: Path | None) -> list[dict[str, Any]]:
|
|
85
|
+
if work_dir is None:
|
|
86
|
+
return []
|
|
87
|
+
path = work_dir / "original_subtitles.json"
|
|
88
|
+
if not path.exists():
|
|
89
|
+
return []
|
|
90
|
+
data, err = _load_json(path)
|
|
91
|
+
if err:
|
|
92
|
+
return []
|
|
93
|
+
return _normalise_segments(data, source="original_subtitles")
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _style_card_requirement(work_dir: Path | None) -> tuple[bool, str]:
|
|
97
|
+
"""Read the explicit stable requirements contract.
|
|
98
|
+
|
|
99
|
+
Legacy/migration workspaces that do not have a valid requirements file are
|
|
100
|
+
advisory-only: they must not be hard-gated by prompt text in
|
|
101
|
+
agent_narration_brief.md.
|
|
102
|
+
"""
|
|
103
|
+
if work_dir is None:
|
|
104
|
+
return False, "legacy_default"
|
|
105
|
+
path = work_dir / "deslop_qc_requirements.json"
|
|
106
|
+
if not path.exists():
|
|
107
|
+
return False, "legacy_default"
|
|
108
|
+
data, err = _load_json(path)
|
|
109
|
+
if err is not None or not isinstance(data, dict):
|
|
110
|
+
return False, "legacy_default"
|
|
111
|
+
required = data.get("style_card_required")
|
|
112
|
+
if not isinstance(required, bool):
|
|
113
|
+
return False, "legacy_default"
|
|
114
|
+
return required, "deslop_qc_requirements.json"
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _style_card_issue(work_dir: Path | None, required: bool) -> dict[str, Any] | None:
|
|
118
|
+
if work_dir is None:
|
|
119
|
+
return None
|
|
120
|
+
path = work_dir / "style_card.json"
|
|
121
|
+
if not path.exists():
|
|
122
|
+
return {
|
|
123
|
+
"severity": "blocker" if required else "advisory",
|
|
124
|
+
"code": "missing_style_card",
|
|
125
|
+
"source": "style_card",
|
|
126
|
+
"index": None,
|
|
127
|
+
"message": (
|
|
128
|
+
"style_card.json is required by this expression/packaging run but is missing"
|
|
129
|
+
if required else
|
|
130
|
+
"style_card.json is absent; legacy/migration workspaces may continue, but new expression-special runs should author it"
|
|
131
|
+
),
|
|
132
|
+
}
|
|
133
|
+
data, err = _load_json(path)
|
|
134
|
+
if err is not None or not isinstance(data, dict) or not data:
|
|
135
|
+
return {
|
|
136
|
+
"severity": "blocker" if required else "advisory",
|
|
137
|
+
"code": "malformed_style_card",
|
|
138
|
+
"source": "style_card",
|
|
139
|
+
"index": None,
|
|
140
|
+
"message": (
|
|
141
|
+
"style_card.json is required but missing, malformed, empty, or not a JSON object"
|
|
142
|
+
if required else
|
|
143
|
+
"style_card.json is present but malformed/empty; treat as migration warning unless the run requires it"
|
|
144
|
+
),
|
|
145
|
+
"detail": err,
|
|
146
|
+
}
|
|
147
|
+
return None
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def _add_issue(bucket: list[dict[str, Any]], severity: str, code: str, source: str, index: int | None, message: str, **extra: Any) -> None:
|
|
151
|
+
issue = {"severity": severity, "code": code, "source": source, "index": index, "message": message}
|
|
152
|
+
issue.update({k: v for k, v in extra.items() if v is not None})
|
|
153
|
+
bucket.append(issue)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def analyze_deslop_qc(narration: Any, *, work_dir: str | Path | None = None, original_subtitles: Any = None) -> dict[str, Any]:
|
|
157
|
+
"""Return a structured report; never mutates or rewrites input text."""
|
|
158
|
+
work_path = Path(work_dir) if work_dir is not None else None
|
|
159
|
+
segments = _normalise_segments(narration, source="narration")
|
|
160
|
+
if original_subtitles is None:
|
|
161
|
+
segments.extend(_load_original_subtitles(work_path))
|
|
162
|
+
else:
|
|
163
|
+
segments.extend(_normalise_segments(original_subtitles, source="original_subtitles"))
|
|
164
|
+
|
|
165
|
+
blockers: list[dict[str, Any]] = []
|
|
166
|
+
advisories: list[dict[str, Any]] = []
|
|
167
|
+
|
|
168
|
+
required, requirement_source = _style_card_requirement(work_path)
|
|
169
|
+
style_issue = _style_card_issue(work_path, required)
|
|
170
|
+
if style_issue:
|
|
171
|
+
(blockers if style_issue["severity"] == "blocker" else advisories).append(style_issue)
|
|
172
|
+
|
|
173
|
+
all_text = "\n".join(seg["text"] for seg in segments)
|
|
174
|
+
total_units = max(1, _text_units(all_text))
|
|
175
|
+
sentences: list[str] = []
|
|
176
|
+
for seg in segments:
|
|
177
|
+
text = seg["text"]
|
|
178
|
+
if EM_DASH_RE.search(text):
|
|
179
|
+
_add_issue(blockers, "blocker", "em_dash", seg["source"], seg["index"], "破折号(—/——)不得出现在 narration/original_subtitles 中", matches=EM_DASH_RE.findall(text))
|
|
180
|
+
if PLACEHOLDER_RE.search(text):
|
|
181
|
+
_add_issue(blockers, "blocker", "placeholder_leakage", seg["source"], seg["index"], "示例占位内容泄漏到成稿中(如【主角】/TA要赌上)", matches=PLACEHOLDER_RE.findall(text))
|
|
182
|
+
for sentence in _sentence_pieces(text):
|
|
183
|
+
sentences.append(sentence)
|
|
184
|
+
if NEGATIVE_FLIP_RE.search(sentence):
|
|
185
|
+
_add_issue(advisories, "advisory", "negative_positive_flip", seg["source"], seg["index"], "“不是…而是…”模板化转折偏多,建议改成更具体的因果/行动表达", sentence=sentence[:120])
|
|
186
|
+
|
|
187
|
+
def term_count(terms: list[str]) -> int:
|
|
188
|
+
return sum(all_text.count(term) for term in terms)
|
|
189
|
+
|
|
190
|
+
cliche_count = term_count(CLICHE_TERMS)
|
|
191
|
+
abstract_count = term_count(ABSTRACT_TERMS)
|
|
192
|
+
reasoning_count = term_count(REASONING_MARKERS)
|
|
193
|
+
metaphor_count = term_count(METAPHOR_MARKERS)
|
|
194
|
+
connective_count = term_count(CONNECTIVE_MARKERS)
|
|
195
|
+
|
|
196
|
+
if cliche_count >= 4 or (cliche_count / total_units) > 0.025:
|
|
197
|
+
_add_issue(advisories, "advisory", "cliche_density", "narration", None, "套话/高频抽象词偏密,建议换成具体行动、选择和后果", count=cliche_count)
|
|
198
|
+
if abstract_count >= 5 or (abstract_count / total_units) > 0.03:
|
|
199
|
+
_add_issue(advisories, "advisory", "abstract_summary", "narration", None, "抽象总结词偏多,容易像概括论文;补足画面证据和具体抉择", count=abstract_count)
|
|
200
|
+
if reasoning_count >= 4:
|
|
201
|
+
_add_issue(advisories, "advisory", "reasoning_chain", "narration", None, "解释链标记偏多,建议减少“因为/所以/这意味着”等讲理口吻", count=reasoning_count)
|
|
202
|
+
if metaphor_count >= 4:
|
|
203
|
+
_add_issue(advisories, "advisory", "metaphor_markers", "narration", None, "比喻/包装化标记偏多,确认没有遮住剧情事实", count=metaphor_count)
|
|
204
|
+
if total_units >= 180 and connective_count <= 1:
|
|
205
|
+
_add_issue(advisories, "advisory", "low_connective_density", "narration", None, "长文本缺少转折/因果连接,可能像平铺摘要", connective_count=connective_count)
|
|
206
|
+
|
|
207
|
+
long_segments = [seg for seg in segments if seg["source"] == "narration" and _text_units(seg["text"]) > 180]
|
|
208
|
+
if long_segments:
|
|
209
|
+
_add_issue(advisories, "advisory", "overlong_narration_block", "narration", long_segments[0].get("index"), "单个解说块过长,建议拆成更可听的段落", max_units=max(_text_units(seg["text"]) for seg in long_segments))
|
|
210
|
+
long_sentences = [s for s in sentences if _text_units(s) > 90]
|
|
211
|
+
if long_sentences:
|
|
212
|
+
_add_issue(advisories, "advisory", "long_paragraph", "narration", None, "长句/长段落偏多,字幕阅读压力较大", max_units=max(_text_units(s) for s in long_sentences))
|
|
213
|
+
|
|
214
|
+
return {
|
|
215
|
+
"ok": not blockers,
|
|
216
|
+
"contract": CONTRACT,
|
|
217
|
+
"scanner": "deslop_qc.py",
|
|
218
|
+
"style_card_required": required,
|
|
219
|
+
"style_card_requirement_source": requirement_source,
|
|
220
|
+
"blocker_count": len(blockers),
|
|
221
|
+
"advisory_count": len(advisories),
|
|
222
|
+
"blockers": blockers,
|
|
223
|
+
"advisories": advisories,
|
|
224
|
+
"metrics": {
|
|
225
|
+
"segments_scanned": len(segments),
|
|
226
|
+
"text_units": total_units if all_text else 0,
|
|
227
|
+
"sentence_count": len(sentences),
|
|
228
|
+
"cliche_count": cliche_count,
|
|
229
|
+
"abstract_count": abstract_count,
|
|
230
|
+
"reasoning_marker_count": reasoning_count,
|
|
231
|
+
"metaphor_marker_count": metaphor_count,
|
|
232
|
+
"connective_count": connective_count,
|
|
233
|
+
},
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
__all__ = ["CONTRACT", "analyze_deslop_qc"]
|