@oh-story/dsh 0.1.10 → 0.1.11
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 +7 -7
- package/lib/client.js +7 -3
- package/lib/client.js.map +2 -2
- package/lib/drama/manifest.json +212 -142
- package/lib/drama/skills/short-drama/SKILL.md +14 -2
- package/lib/drama/skills/short-drama/assets/project-template/short-drama.json +3 -0
- package/lib/drama/skills/short-drama/references/creator-documents.md +3 -0
- package/lib/drama/skills/short-drama/references/knowhow-index.md +15 -0
- package/lib/drama/skills/short-drama/scripts/project_tool.py +131 -0
- package/lib/drama/skills/short-drama-assets/SKILL.md +2 -1
- package/lib/drama/skills/short-drama-assets/references/asset-review-checklist.md +2 -0
- package/lib/drama/skills/short-drama-assets/references/era-anchors.md +37 -0
- package/lib/drama/skills/short-drama-assets/references/stage-contract.md +2 -0
- package/lib/drama/skills/short-drama-develop/SKILL.md +10 -4
- package/lib/drama/skills/short-drama-develop/assets/adaptation-map.example.jsonl +1 -1
- package/lib/drama/skills/short-drama-develop/assets/creative-brief.md +8 -0
- package/lib/drama/skills/short-drama-develop/references/adaptation-craft.md +80 -13
- package/lib/drama/skills/short-drama-develop/references/episode-design.md +95 -12
- package/lib/drama/skills/short-drama-develop/references/genre-and-hook-playbook.md +9 -5
- package/lib/drama/skills/short-drama-develop/references/genre-cards//345/244/215/344/273/207/346/211/223/350/204/270.md +1 -1
- package/lib/drama/skills/short-drama-develop/references/genre-cards//347/263/273/347/273/237/346/265/201.md +51 -0
- package/lib/drama/skills/short-drama-develop/references/genre-cards//351/200/206/350/242/255.md +51 -0
- package/lib/drama/skills/short-drama-develop/references/genre-cards//351/207/215/347/224/237/347/251/277/350/266/212.md +51 -0
- package/lib/drama/skills/short-drama-develop/references/genre-cards.md +10 -7
- package/lib/drama/skills/short-drama-develop/references/premise-devices.md +15 -9
- package/lib/drama/skills/short-drama-develop/references/serial-character-and-memory.md +3 -2
- package/lib/drama/skills/short-drama-develop/references/stage-contract.md +14 -7
- package/lib/drama/skills/short-drama-develop/references/story-craft.md +7 -3
- package/lib/drama/skills/short-drama-edit/SKILL.md +43 -20
- package/lib/drama/skills/short-drama-edit/assets/remotion/README.md +49 -13
- package/lib/drama/skills/short-drama-edit/assets/remotion/package.json +4 -2
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/Overlay.tsx +31 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/Root.tsx +5 -5
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/Subtitles.tsx +68 -69
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/font.ts +39 -29
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/rules.mjs +188 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/schema.ts +26 -13
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/Card.tsx +72 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/CornerChip.tsx +39 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/ScreenText.tsx +36 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/SystemPanel.tsx +44 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/TaskPanel.tsx +100 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/chrome.tsx +197 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/tokens.ts +62 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/tsconfig.json +1 -0
- package/lib/drama/skills/short-drama-edit/assets//345/211/252/350/276/221/345/215/225.md +4 -2
- package/lib/drama/skills/short-drama-edit/references/cut-craft.md +3 -1
- package/lib/drama/skills/short-drama-edit/references/delivery-verify.md +6 -2
- package/lib/drama/skills/short-drama-edit/references/generated-footage.md +12 -6
- package/lib/drama/skills/short-drama-edit/references/sound-and-subtitles.md +13 -8
- package/lib/drama/skills/short-drama-edit/references/stage-contract.md +2 -0
- package/lib/drama/skills/short-drama-edit/scripts/edit_tool.py +1153 -121
- package/lib/drama/skills/short-drama-edit/scripts/selftest.py +49 -9
- package/lib/drama/skills/short-drama-novel-analyze/SKILL.md +3 -3
- package/lib/drama/skills/short-drama-novel-analyze/assets/episode-candidate.example.jsonl +2 -2
- package/lib/drama/skills/short-drama-novel-analyze/references/adaptation-triage.md +8 -4
- package/lib/drama/skills/short-drama-novel-analyze/references/adaptation-value.md +10 -3
- package/lib/drama/skills/short-drama-novel-analyze/references/aggregation-and-entities.md +25 -1
- package/lib/drama/skills/short-drama-novel-analyze/references/stage-contract.md +1 -0
- package/lib/drama/skills/short-drama-review/SKILL.md +2 -2
- package/lib/drama/skills/short-drama-review/references/review-method.md +6 -1
- package/lib/drama/skills/short-drama-review/references/rubric-assets-prompts.md +11 -0
- package/lib/drama/skills/short-drama-review/references/rubric-story-script.md +77 -3
- package/lib/drama/skills/short-drama-review/references/rubric-visual-motion.md +15 -0
- package/lib/drama/skills/short-drama-review/references/stage-contract.md +3 -0
- package/lib/drama/skills/short-drama-storyboard/SKILL.md +13 -5
- package/lib/drama/skills/short-drama-storyboard/references/blocking-playbooks.md +1 -1
- package/lib/drama/skills/short-drama-storyboard/references/production-shot-grammar.md +2 -2
- package/lib/drama/skills/short-drama-storyboard/references/shot-craft.md +82 -14
- package/lib/drama/skills/short-drama-storyboard/references/stage-contract.md +4 -2
- package/lib/drama/skills/short-drama-video-prompts/SKILL.md +5 -3
- package/lib/drama/skills/short-drama-video-prompts/references/camera-audio-continuity.md +12 -0
- package/lib/drama/skills/short-drama-video-prompts/references/delivery-profile.md +4 -5
- package/lib/drama/skills/short-drama-video-prompts/references/generability.md +4 -2
- package/lib/drama/skills/short-drama-video-prompts/references/minimax-h3.md +40 -1
- package/lib/drama/skills/short-drama-video-prompts/references/motion-recipe.md +20 -14
- package/lib/drama/skills/short-drama-video-prompts/references/performance-action-timing.md +1 -1
- package/lib/drama/skills/short-drama-video-prompts/references/production-prompt-grammar.md +8 -7
- package/lib/drama/skills/short-drama-video-prompts/references/review-and-fixtures.md +1 -1
- package/lib/drama/skills/short-drama-video-prompts/references/stage-contract.md +1 -0
- package/lib/drama/skills/short-drama-video-prompts/references/target-model-profile.md +8 -1
- package/lib/drama/skills/short-drama-video-prompts/references/wan-3.0.md +103 -0
- package/lib/drama/skills/short-drama-video-prompts/scripts/motion_timing_check.py +8 -7
- package/lib/drama/skills/short-drama-write/SKILL.md +8 -3
- package/lib/drama/skills/short-drama-write/references/dialogue-craft.md +1 -1
- package/lib/drama/skills/short-drama-write/references/script-craft.md +56 -3
- package/lib/drama/skills/short-drama-write/references/stage-contract.md +4 -0
- package/lib/drama/skills/short-drama-write/scripts/duration_estimate.py +48 -2
- package/lib/index.js +71 -21
- package/lib/index.js.map +3 -3
- package/lib/oh-story/manifest.json +545 -310
- package/lib/oh-story/roles/chapter-extractor.md +11 -11
- package/lib/oh-story/roles/character-designer.md +1 -2
- package/lib/oh-story/roles/consistency-checker.md +11 -9
- package/lib/oh-story/roles/narrative-writer.md +24 -25
- package/lib/oh-story/roles/story-architect.md +9 -61
- package/lib/oh-story/roles/story-explorer.md +20 -125
- package/lib/oh-story/roles/story-researcher.md +0 -1
- package/lib/oh-story/skills/story/SKILL.md +6 -6
- package/lib/oh-story/skills/story/VERSION +1 -1
- package/lib/oh-story/skills/story/references/author-memory-maintenance.md +75 -0
- package/lib/oh-story/skills/story/references/author-memory.md +40 -118
- package/lib/oh-story/skills/story/scripts/author_memory_commit.py +3 -3
- package/lib/oh-story/skills/story-deslop/SKILL.md +24 -25
- package/lib/oh-story/skills/story-deslop/references/agent-calls.md +5 -0
- package/lib/oh-story/skills/story-deslop/references/anti-ai-writing.md +1 -1
- package/lib/oh-story/skills/story-deslop/references/author-memory-maintenance.md +75 -0
- package/lib/oh-story/skills/story-deslop/references/author-memory.md +40 -118
- package/lib/oh-story/skills/story-deslop/references/banned-words.md +2 -2
- package/lib/oh-story/skills/story-deslop/references/deslop-gates.md +21 -4
- package/lib/oh-story/skills/story-deslop/references/style-resolution.md +4 -4
- package/lib/oh-story/skills/story-deslop/scripts/author_memory_commit.py +3 -3
- package/lib/oh-story/skills/story-deslop/scripts/check-ai-patterns.js +155 -8
- package/lib/oh-story/skills/story-import/SKILL.md +63 -579
- package/lib/oh-story/skills/story-import/references/deep-analysis.md +47 -0
- package/lib/oh-story/skills/story-import/references/format-and-structure.md +1 -1
- package/lib/oh-story/skills/story-import/references/import-report.md +64 -0
- package/lib/oh-story/skills/story-import/references/import-special-cases.md +34 -0
- package/lib/oh-story/skills/story-import/references/import-tracking.md +95 -0
- package/lib/oh-story/skills/story-import/references/length-routing.md +1 -1
- package/lib/oh-story/skills/story-import/references/outline-reverse-rules.md +79 -0
- package/lib/oh-story/skills/story-import/references/outline-reverse.md +24 -0
- package/lib/oh-story/skills/story-import/references/structure-mapping-long.md +95 -91
- package/lib/oh-story/skills/story-import/references/structure-mapping-short.md +24 -12
- package/lib/oh-story/skills/story-import/references/tracking-initialization.md +1 -1
- package/lib/oh-story/skills/story-import/references/tracking-transaction.md +11 -9
- package/lib/oh-story/skills/story-import/scripts/build_outline_brief.py +332 -0
- package/lib/oh-story/skills/story-import/scripts/check-outline-contract.js +101 -14
- package/lib/oh-story/skills/story-import/scripts/tracking_commit.py +278 -23
- package/lib/oh-story/skills/story-import/scripts/wordcount_core.py +179 -23
- package/lib/oh-story/skills/story-long-analyze/SKILL.md +28 -50
- package/lib/oh-story/skills/story-long-analyze/references/author-facing.md +12 -5
- package/lib/oh-story/skills/story-long-analyze/references/deconstruction-notes.md +1 -1
- package/lib/oh-story/skills/story-long-analyze/references/final-checks.md +19 -0
- package/lib/oh-story/skills/story-long-analyze/references/index-rebuild.md +11 -0
- package/lib/oh-story/skills/story-long-analyze/references/pipeline-ops.md +21 -55
- package/lib/oh-story/skills/story-long-analyze/references/stage1-golden-chapters.md +128 -0
- package/lib/oh-story/skills/story-long-analyze/references/stage2-extraction.md +229 -0
- package/lib/oh-story/skills/story-long-analyze/references/stage3-plot-rhythm.md +480 -0
- package/lib/oh-story/skills/story-long-analyze/references/stage4-characters-settings.md +195 -0
- package/lib/oh-story/skills/story-long-analyze/references/stage5-report.md +33 -0
- package/lib/oh-story/skills/story-long-analyze/references/style-profile-generator.md +17 -0
- package/lib/oh-story/skills/story-long-analyze/references/style-profile-protocol.md +1 -1
- package/lib/oh-story/skills/story-long-analyze/references/synthesis-inputs.md +39 -0
- package/lib/oh-story/skills/story-long-analyze/scripts/manage_analysis_run.py +47 -8
- package/lib/oh-story/skills/story-long-scan/SKILL.md +41 -193
- package/lib/oh-story/skills/story-long-scan/references/platform-ciweimao.md +19 -0
- package/lib/oh-story/skills/story-long-scan/references/platform-fanqie.md +45 -0
- package/lib/oh-story/skills/story-long-scan/references/platform-jjwxc.md +28 -0
- package/lib/oh-story/skills/story-long-scan/references/platform-qidian.md +35 -0
- package/lib/oh-story/skills/story-long-scan/references/platform-qimao.md +27 -0
- package/lib/oh-story/skills/story-long-scan/references/topic-decision.md +6 -2
- package/lib/oh-story/skills/story-long-write/SKILL.md +32 -73
- package/lib/oh-story/skills/story-long-write/references/agent-calls.md +47 -0
- package/lib/oh-story/skills/story-long-write/references/anti-ai-writing.md +1 -1
- package/lib/oh-story/skills/story-long-write/references/artifact-protocols.md +18 -201
- package/lib/oh-story/skills/story-long-write/references/author-memory-maintenance.md +75 -0
- package/lib/oh-story/skills/story-long-write/references/author-memory.md +40 -118
- package/lib/oh-story/skills/story-long-write/references/banned-words.md +2 -2
- package/lib/oh-story/skills/story-long-write/references/benchmark-recall.md +6 -10
- package/lib/oh-story/skills/story-long-write/references/character-relations.md +49 -1
- package/lib/oh-story/skills/story-long-write/references/deslop-gates.md +171 -0
- package/lib/oh-story/skills/story-long-write/references/emotion-on-page.md +116 -0
- package/lib/oh-story/skills/story-long-write/references/emotional-arc-design.md +1 -114
- package/lib/oh-story/skills/story-long-write/references/genre-prose-cards.md +1 -1
- package/lib/oh-story/skills/story-long-write/references/long-chapter-quality.md +3 -2
- package/lib/oh-story/skills/story-long-write/references/long-format.md +0 -8
- package/lib/oh-story/skills/story-long-write/references/long-genre-catalog.md +1 -1
- package/lib/oh-story/skills/story-long-write/references/long-genre-mechanics.md +3 -3
- package/lib/oh-story/skills/story-long-write/references/opening-design.md +2 -2
- package/lib/oh-story/skills/story-long-write/references/outline-conflict.md +4 -4
- package/lib/oh-story/skills/story-long-write/references/outline-methods.md +7 -7
- package/lib/oh-story/skills/story-long-write/references/outline-rhythm.md +3 -3
- package/lib/oh-story/skills/story-long-write/references/outline-structure-theory.md +14 -69
- package/lib/oh-story/skills/story-long-write/references/plot-frameworks.md +1 -1
- package/lib/oh-story/skills/story-long-write/references/plot-special-topics.md +2 -2
- package/lib/oh-story/skills/story-long-write/references/project-files.md +10 -6
- package/lib/oh-story/skills/story-long-write/references/reader-contract-and-progression.md +1 -1
- package/lib/oh-story/skills/story-long-write/references/reference-index.md +4 -4
- package/lib/oh-story/skills/story-long-write/references/stop-and-fix.md +9 -0
- package/lib/oh-story/skills/story-long-write/references/style-combat-face.md +1 -58
- 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 +4 -4
- package/lib/oh-story/skills/story-long-write/references/tracking-initialization.md +1 -1
- package/lib/oh-story/skills/story-long-write/references/tracking-transaction.md +11 -9
- package/lib/oh-story/skills/story-long-write/references/workflow-chapter.md +33 -44
- package/lib/oh-story/skills/story-long-write/references/workflow-daily.md +18 -51
- package/lib/oh-story/skills/story-long-write/references/workflow-outline.md +157 -0
- package/lib/oh-story/skills/story-long-write/references/workflow-revision.md +53 -67
- package/lib/oh-story/skills/story-long-write/references/workflow-setup.md +112 -251
- package/lib/oh-story/skills/story-long-write/references/workflow-volume.md +142 -0
- package/lib/oh-story/skills/story-long-write/references/writer-prompt-fallback.md +5 -3
- package/lib/oh-story/skills/story-long-write/references/writing-craft.md +18 -56
- package/lib/oh-story/skills/story-long-write/scripts/author_memory_commit.py +3 -3
- package/lib/oh-story/skills/story-long-write/scripts/build_architect_brief.py +260 -0
- package/lib/oh-story/skills/story-long-write/scripts/build_writer_prompt.py +96 -16
- package/lib/oh-story/skills/story-long-write/scripts/check-ai-patterns.js +155 -8
- package/lib/oh-story/skills/story-long-write/scripts/check-outline-contract.js +101 -14
- package/lib/oh-story/skills/story-long-write/scripts/check-outline-copy.js +7 -2
- package/lib/oh-story/skills/story-long-write/scripts/outline_view.py +2 -1
- package/lib/oh-story/skills/story-long-write/scripts/storyctl.py +250 -50
- package/lib/oh-story/skills/story-long-write/scripts/tracking_commit.py +278 -23
- package/lib/oh-story/skills/story-long-write/scripts/wordcount_core.py +179 -23
- package/lib/oh-story/skills/story-review/SKILL.md +19 -273
- package/lib/oh-story/skills/story-review/references/agent-prompts.md +184 -0
- package/lib/oh-story/skills/story-review/references/anti-ai-writing.md +1 -1
- package/lib/oh-story/skills/story-review/references/author-memory-maintenance.md +75 -0
- package/lib/oh-story/skills/story-review/references/author-memory.md +40 -118
- package/lib/oh-story/skills/story-review/references/banned-words.md +2 -2
- package/lib/oh-story/skills/story-review/references/batch-review.md +19 -0
- package/lib/oh-story/skills/story-review/references/character-relations.md +49 -1
- package/lib/oh-story/skills/story-review/references/review-quality.md +2 -1
- package/lib/oh-story/skills/story-review/references/review-tracking.md +10 -0
- package/lib/oh-story/skills/story-review/references/solo.md +52 -0
- package/lib/oh-story/skills/story-review/references/style-resolution.md +4 -4
- package/lib/oh-story/skills/story-review/references/tracking-initialization.md +1 -1
- package/lib/oh-story/skills/story-review/references/tracking-transaction.md +11 -9
- package/lib/oh-story/skills/story-review/scripts/author_memory_commit.py +3 -3
- package/lib/oh-story/skills/story-review/scripts/check-ai-patterns.js +155 -8
- package/lib/oh-story/skills/story-review/scripts/tracking_commit.py +278 -23
- package/lib/oh-story/skills/story-review/scripts/wordcount_core.py +179 -23
- package/lib/oh-story/skills/story-setup/SKILL.md +69 -458
- package/lib/oh-story/skills/story-setup/references/agent-references/agent-quality.md +4 -4
- package/lib/oh-story/skills/story-setup/references/agent-references/anti-ai-writing.md +1 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/banned-words.md +2 -2
- package/lib/oh-story/skills/story-setup/references/agent-references/benchmark-style-load.md +107 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/character-relations.md +49 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/deslop-gates.md +21 -4
- package/lib/oh-story/skills/story-setup/references/agent-references/emotion-on-page.md +116 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/emotional-arc-design.md +1 -114
- package/lib/oh-story/skills/story-setup/references/agent-references/format-and-structure.md +1 -1
- 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/long-genre-catalog.md +1 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/long-genre-mechanics.md +3 -3
- package/lib/oh-story/skills/story-setup/references/agent-references/long-quality.md +7 -5
- package/lib/oh-story/skills/story-setup/references/agent-references/opening-design.md +2 -2
- package/lib/oh-story/skills/story-setup/references/agent-references/outline-conflict.md +4 -4
- package/lib/oh-story/skills/story-setup/references/agent-references/outline-methods.md +7 -7
- package/lib/oh-story/skills/story-setup/references/agent-references/outline-rhythm.md +3 -3
- package/lib/oh-story/skills/story-setup/references/agent-references/short-emotional-methods.md +1 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/short-genre-formulas.md +0 -15
- package/lib/oh-story/skills/story-setup/references/agent-references/short-reversal.md +1 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/style-combat-face.md +1 -58
- 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 +4 -4
- package/lib/oh-story/skills/story-setup/references/agent-references/writing-craft.md +18 -56
- package/lib/oh-story/skills/story-setup/references/diagnostics.md +3 -3
- package/lib/oh-story/skills/story-short-analyze/SKILL.md +37 -110
- package/lib/oh-story/skills/story-short-analyze/references/analysis-character-relations.md +1 -1
- package/lib/oh-story/skills/story-short-analyze/references/analysis-method.md +42 -0
- package/lib/oh-story/skills/story-short-analyze/references/optional-modules.md +97 -0
- package/lib/oh-story/skills/story-short-analyze/references/output-contract.md +9 -47
- package/lib/oh-story/skills/story-short-analyze/references/quality-checklist.md +80 -0
- package/lib/oh-story/skills/story-short-analyze/references/source-story-quality.md +4 -4
- package/lib/oh-story/skills/story-short-analyze/references/stage2-3-structure-emotion.md +308 -0
- package/lib/oh-story/skills/story-short-analyze/references/{material-decomposition.md → stage4-6-reversal-summary.md} +303 -374
- package/lib/oh-story/skills/story-short-scan/SKILL.md +30 -64
- package/lib/oh-story/skills/story-short-scan/references/manual-rank-input.md +36 -0
- package/lib/oh-story/skills/story-short-scan/references/platform-dianzhong.md +24 -0
- package/lib/oh-story/skills/story-short-scan/references/platform-heiyan.md +21 -0
- package/lib/oh-story/skills/story-short-scan/references/topic-match.md +38 -0
- package/lib/oh-story/skills/story-short-write/SKILL.md +23 -34
- package/lib/oh-story/skills/story-short-write/references/agent-calls.md +13 -0
- package/lib/oh-story/skills/story-short-write/references/author-memory-maintenance.md +75 -0
- package/lib/oh-story/skills/story-short-write/references/author-memory.md +40 -118
- package/lib/oh-story/skills/story-short-write/references/banned-words.md +2 -2
- package/lib/oh-story/skills/story-short-write/references/benchmark-recall.md +1 -21
- package/lib/oh-story/skills/story-short-write/references/emotional-methods.md +1 -1
- package/lib/oh-story/skills/story-short-write/references/genre-writing-formulas.md +0 -15
- package/lib/oh-story/skills/story-short-write/references/output-contract.md +9 -47
- package/lib/oh-story/skills/story-short-write/references/reference-index.md +17 -49
- package/lib/oh-story/skills/story-short-write/references/scene-craft.md +40 -0
- package/lib/oh-story/skills/story-short-write/references/short-craft.md +7 -43
- package/lib/oh-story/skills/story-short-write/references/short-format.md +3 -3
- package/lib/oh-story/skills/story-short-write/references/short-prose-quality.md +2 -1
- package/lib/oh-story/skills/story-short-write/references/short-reversal.md +1 -1
- package/lib/oh-story/skills/story-short-write/references/style-resolution.md +4 -4
- package/lib/oh-story/skills/story-short-write/references/submission-craft.md +1 -1
- package/lib/oh-story/skills/story-short-write/references/villain-and-reveal.md +5 -10
- package/lib/oh-story/skills/story-short-write/references/workflow-design.md +19 -12
- package/lib/oh-story/skills/story-short-write/references/workflow-draft.md +22 -32
- package/lib/oh-story/skills/story-short-write/references/workflow-revision.md +84 -17
- package/lib/oh-story/skills/story-short-write/references/writing-workflow.md +7 -119
- package/lib/oh-story/skills/story-short-write/scripts/author_memory_commit.py +3 -3
- package/lib/oh-story/skills/story-short-write/scripts/check-ai-patterns.js +155 -8
- package/lib/oh-story/skills/story-short-write/scripts/check-outline-copy.js +7 -2
- package/lib/types/client/video-studio.d.ts.map +1 -1
- package/lib/types/native-hooks.d.ts +2 -2
- package/lib/types/native-hooks.d.ts.map +1 -1
- package/lib/types/role-tool.d.ts.map +1 -1
- package/lib/types/skill-provider.d.ts.map +1 -1
- package/lib/types/video-project.d.ts.map +1 -1
- package/lib/types/workspace-route.d.ts.map +1 -1
- package/lib/video-recap/CHANGELOG.md +60 -1
- package/lib/video-recap/README.en.md +348 -137
- package/lib/video-recap/README.md +340 -136
- package/lib/video-recap/manifest.json +406 -251
- package/lib/video-recap/skills/video-assemble/SKILL.md +30 -15
- package/lib/video-recap/skills/video-assemble/references/audio-modes.md +63 -0
- package/lib/video-recap/skills/video-assemble/references/explicit-audio-mix.md +75 -0
- package/lib/video-recap/skills/video-assemble/references/foreground-compose.md +118 -0
- package/lib/video-recap/skills/video-assemble/references/narration-adoption.md +103 -0
- package/lib/video-recap/skills/video-assemble/references/packaging.md +45 -0
- package/lib/video-recap/skills/video-assemble/references/pair-media.md +74 -0
- package/lib/video-recap/skills/video-assemble/references/source-score.md +147 -0
- package/lib/video-recap/skills/video-assemble/references/subtitle-track.md +202 -0
- package/lib/video-recap/skills/video-assemble/scripts/adoption/__init__.py +1 -0
- package/lib/video-recap/skills/video-assemble/scripts/adoption/audio_mix_binding.py +279 -0
- package/lib/video-recap/skills/video-assemble/scripts/adoption/frozen_audio.py +187 -0
- package/lib/video-recap/skills/video-assemble/scripts/adoption/narration_binding.py +323 -0
- package/lib/video-recap/skills/video-assemble/scripts/adoption/strict_inputs.py +109 -0
- package/lib/video-recap/skills/video-assemble/scripts/adoption/strict_publish.py +130 -0
- package/lib/video-recap/skills/video-assemble/scripts/artifacts.py +10 -46
- package/lib/video-recap/skills/video-assemble/scripts/assemble.py +378 -119
- package/lib/video-recap/skills/video-assemble/scripts/assemble_constants.py +29 -12
- package/lib/video-recap/skills/video-assemble/scripts/assembly_contract.py +95 -44
- package/lib/video-recap/skills/video-assemble/scripts/assembly_settings.py +75 -32
- package/lib/video-recap/skills/video-assemble/scripts/audio_mix.py +11 -11
- package/lib/video-recap/skills/video-assemble/scripts/compose_foreground.py +293 -0
- package/lib/video-recap/skills/video-assemble/scripts/export_jianying.py +71 -78
- package/lib/video-recap/skills/video-assemble/scripts/jianying/__init__.py +1 -0
- package/lib/video-recap/skills/video-assemble/scripts/{jianying_builders.py → jianying/builders.py} +39 -67
- package/lib/video-recap/skills/video-assemble/scripts/{jianying_model.py → jianying/model.py} +3 -11
- package/lib/video-recap/skills/video-assemble/scripts/{jianying_optional.py → jianying/optional.py} +1 -1
- package/lib/video-recap/skills/video-assemble/scripts/jianying/schema.py +53 -0
- package/lib/video-recap/skills/video-assemble/scripts/{jianying_templates.py → jianying/templates.py} +1 -1
- package/lib/video-recap/skills/video-assemble/scripts/{jianying_timeline_contract.py → jianying/timeline_contract.py} +34 -13
- package/lib/video-recap/skills/video-assemble/scripts/{jianying_writer.py → jianying/writer.py} +7 -17
- package/lib/video-recap/skills/video-assemble/scripts/lib.py +37 -4
- package/lib/video-recap/skills/video-assemble/scripts/media.py +12 -21
- package/lib/video-recap/skills/video-assemble/scripts/narration_audio.py +64 -34
- package/lib/video-recap/skills/video-assemble/scripts/packaging.py +122 -0
- package/lib/video-recap/skills/video-assemble/scripts/pair_media.py +216 -0
- package/lib/video-recap/skills/video-assemble/scripts/render_preflight.py +1 -3
- package/lib/video-recap/skills/video-assemble/scripts/source_score.py +469 -0
- package/lib/video-recap/skills/video-assemble/scripts/source_subtitles.py +25 -9
- package/lib/video-recap/skills/video-assemble/scripts/subtitles/__init__.py +1 -0
- package/lib/video-recap/skills/video-assemble/scripts/{subtitle_core.py → subtitles/core.py} +13 -6
- package/lib/video-recap/skills/video-assemble/scripts/{subtitle_render.py → subtitles/render.py} +5 -4
- package/lib/video-recap/skills/video-assemble/scripts/subtitles/track.py +324 -0
- package/lib/video-recap/skills/video-assemble/scripts/subtitles/track_binding.py +200 -0
- package/lib/video-recap/skills/video-assemble/scripts/timeline.py +30 -46
- package/lib/video-recap/skills/video-assemble/scripts/timeline_emit.py +91 -7
- package/lib/video-recap/skills/video-assemble/scripts/visual_render.py +22 -8
- package/lib/video-recap/skills/video-cut/SKILL.md +34 -16
- package/lib/video-recap/skills/video-cut/references/shot-review.md +50 -0
- package/lib/video-recap/skills/video-cut/scripts/cut.py +2 -28
- package/lib/video-recap/skills/video-cut/scripts/cut_cli.py +58 -62
- package/lib/video-recap/skills/video-cut/scripts/cut_contract.py +21 -106
- package/lib/video-recap/skills/video-cut/scripts/cut_render.py +14 -9
- package/lib/video-recap/skills/video-cut/scripts/lib.py +39 -0
- package/lib/video-recap/skills/video-cut/scripts/narration_mapping.py +2 -155
- package/lib/video-recap/skills/video-cut/scripts/narrative_selection.py +244 -0
- package/lib/video-recap/skills/video-cut/scripts/shot_review.py +357 -0
- package/lib/video-recap/skills/video-recap/SKILL.md +86 -42
- package/lib/video-recap/skills/video-recap/references/audio-routing.md +126 -0
- package/lib/video-recap/skills/video-recap/references/config-playbook.md +12 -8
- package/lib/video-recap/skills/video-recap/references/data-schema.md +44 -33
- package/lib/video-recap/skills/video-recap/references/resource-library.md +151 -0
- package/lib/video-recap/skills/video-recap/references/shift-left-qc-schema.md +1 -1
- package/lib/video-recap/skills/video-recap/scripts/doctor.py +123 -30
- package/lib/video-recap/skills/video-recap/scripts/final_qc.py +22 -75
- package/lib/video-recap/skills/video-recap/scripts/lib.py +3 -3
- package/lib/video-recap/skills/video-recap/scripts/library.py +509 -0
- package/lib/video-recap/skills/video-recap/scripts/materials.py +63 -91
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc.py +8 -70
- package/lib/video-recap/skills/video-recap/scripts/project_binding.py +286 -0
- package/lib/video-recap/skills/video-recap/scripts/qc/__init__.py +1 -0
- package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_client.py → qc/mimo_client.py} +1 -1
- package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_evidence.py → qc/mimo_evidence.py} +33 -66
- package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_observations.py → qc/mimo_observations.py} +3 -11
- package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_payload.py → qc/mimo_payload.py} +4 -7
- package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_report.py → qc/mimo_report.py} +17 -28
- package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_runner.py → qc/mimo_runner.py} +2 -2
- package/lib/video-recap/skills/video-recap/scripts/qc_contract.py +0 -19
- package/lib/video-recap/skills/video-recap/scripts/recap_cli.py +119 -50
- package/lib/video-recap/skills/video-recap/scripts/recap_inspect.py +32 -114
- package/lib/video-recap/skills/video-recap/scripts/recap_review.py +9 -23
- package/lib/video-recap/skills/video-recap/scripts/recap_runner.py +387 -334
- package/lib/video-recap/skills/video-recap/scripts/recap_runtime.py +23 -23
- package/lib/video-recap/skills/video-recap/scripts/recap_source.py +278 -0
- package/lib/video-recap/skills/video-recap/scripts/recap_stage_qc.py +25 -12
- package/lib/video-recap/skills/video-recap/scripts/recap_timeline.py +59 -52
- package/lib/video-recap/skills/video-recap/scripts/resource_lock.py +162 -0
- package/lib/video-recap/skills/video-script/SKILL.md +16 -13
- package/lib/video-recap/skills/video-script/references/creative-editing-playbook.md +9 -5
- package/lib/video-recap/skills/video-script/references/promotional-copy.md +68 -0
- package/lib/video-recap/skills/video-script/references/research-guide.md +1 -1
- package/lib/video-recap/skills/video-script/scripts/evidence_bundle.py +24 -76
- package/lib/video-recap/skills/video-script/scripts/lib.py +10 -87
- package/lib/video-recap/skills/video-script/scripts/narration_lint.py +63 -39
- package/lib/video-recap/skills/video-script/scripts/review.py +0 -2
- package/lib/video-recap/skills/video-script/scripts/review_grounding.py +35 -59
- package/lib/video-recap/skills/video-script/scripts/review_response.py +66 -128
- package/lib/video-recap/skills/video-script/scripts/review_runner.py +12 -20
- package/lib/video-recap/skills/video-script/scripts/speech_ownership.py +4 -4
- package/lib/video-recap/skills/video-script/scripts/validate.py +53 -42
- package/lib/video-recap/skills/video-understanding/SKILL.md +6 -4
- package/lib/video-recap/skills/video-understanding/references/data-schema.md +50 -156
- package/lib/video-recap/skills/video-understanding/references/research-guide.md +4 -4
- package/lib/video-recap/skills/video-understanding/scripts/asr.py +85 -65
- package/lib/video-recap/skills/video-understanding/scripts/asr_timing_evidence.py +270 -0
- package/lib/video-recap/skills/video-understanding/scripts/brief.py +2 -5
- package/lib/video-recap/skills/video-understanding/scripts/briefing/__init__.py +1 -0
- package/lib/video-recap/skills/video-understanding/scripts/{agent_brief.py → briefing/builder.py} +38 -35
- package/lib/video-recap/skills/{video-script/scripts/brief_context.py → video-understanding/scripts/briefing/context.py} +4 -35
- package/lib/video-recap/skills/{video-script/scripts/brief_inputs.py → video-understanding/scripts/briefing/inputs.py} +9 -31
- package/lib/video-recap/skills/video-understanding/scripts/{brief_timeline.py → briefing/timeline.py} +5 -6
- package/lib/video-recap/skills/video-understanding/scripts/consolidate.py +65 -116
- package/lib/video-recap/skills/video-understanding/scripts/detect.py +31 -40
- package/lib/video-recap/skills/video-understanding/scripts/lib.py +30 -86
- package/lib/video-recap/skills/video-understanding/scripts/narration_lint.py +63 -39
- package/lib/video-recap/skills/video-understanding/scripts/speech_ownership.py +4 -4
- package/lib/video-recap/skills/video-understanding/scripts/storyboard.py +17 -79
- package/lib/video-recap/skills/video-understanding/scripts/understanding_brief.py +14 -40
- package/lib/video-recap/skills/video-understanding/scripts/understanding_cache.py +64 -46
- package/lib/video-recap/skills/video-understanding/scripts/understanding_runner.py +31 -11
- package/lib/video-recap/skills/video-understanding/scripts/understanding_storyboard.py +19 -14
- package/lib/video-recap/skills/video-understanding/scripts/vlm.py +80 -108
- package/lib/video-recap/skills/video-voiceover/SKILL.md +29 -19
- package/lib/video-recap/skills/video-voiceover/references/index-tts.md +31 -0
- package/lib/video-recap/skills/video-voiceover/scripts/approved_text_policy.py +123 -0
- package/lib/video-recap/skills/video-voiceover/scripts/dub.py +51 -59
- package/lib/video-recap/skills/video-voiceover/scripts/lib.py +8 -44
- package/lib/video-recap/skills/video-voiceover/scripts/providers/__init__.py +1 -0
- package/lib/video-recap/skills/video-voiceover/scripts/providers/index_tts.py +184 -0
- package/lib/video-recap/skills/video-voiceover/scripts/voiceover.py +212 -134
- package/package.json +1 -1
- package/lib/oh-story/skills/story-long-analyze/references/material-decomposition.md +0 -538
- package/lib/oh-story/skills/story-long-analyze/references/output-templates.md +0 -603
- package/lib/oh-story/skills/story-long-scan/references/scan-output-format.md +0 -207
- package/lib/oh-story/skills/story-short-analyze/references/output-templates.md +0 -561
- package/lib/video-recap/skills/video-assemble/scripts/jianying_schema.py +0 -98
- package/lib/video-recap/skills/video-recap/references/creative-editing-playbook.md +0 -225
- package/lib/video-recap/skills/video-recap/references/research-guide.md +0 -74
- package/lib/video-recap/skills/video-recap/references/timeline-and-jianying.md +0 -250
- package/lib/video-recap/skills/video-script/scripts/agent_brief.py +0 -399
- package/lib/video-recap/skills/video-script/scripts/brief_timeline.py +0 -327
- package/lib/video-recap/skills/video-script/scripts/narration.py +0 -12
- package/lib/video-recap/skills/video-understanding/scripts/brief_context.py +0 -348
- package/lib/video-recap/skills/video-understanding/scripts/brief_inputs.py +0 -290
- /package/lib/video-recap/skills/video-assemble/scripts/{jianying_tracks.py → jianying/tracks.py} +0 -0
- /package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_contract.py → qc/mimo_contract.py} +0 -0
- /package/lib/video-recap/skills/video-voiceover/scripts/{fish_audio.py → providers/fish_audio.py} +0 -0
|
@@ -9,18 +9,17 @@ Optional, synchronous, in-pipeline. Two independent LLM passes over the video's
|
|
|
9
9
|
TEXT per segment index only; each segment's original start/end is re-attached here, so a
|
|
10
10
|
cleanup pass can never shift the timing-bearing spans downstream chunking depends on.
|
|
11
11
|
|
|
12
|
-
Both passes
|
|
13
|
-
|
|
12
|
+
Both passes are idempotent (a fresh artifact is reused); a failure raises and the runner
|
|
13
|
+
records it in consolidation.status.json. Mirrors review.py's pure-seam + thin-driver shape so it is
|
|
14
14
|
unit-testable with a mocked api_call. NON-required: the pipeline runs unchanged without it.
|
|
15
15
|
"""
|
|
16
16
|
|
|
17
17
|
import argparse
|
|
18
|
-
import hashlib
|
|
19
18
|
import json
|
|
20
19
|
import re
|
|
21
20
|
from pathlib import Path
|
|
22
21
|
|
|
23
|
-
from lib import CONFIG, log, api_call
|
|
22
|
+
from lib import CONFIG, log, api_call, file_identity, load_background_research
|
|
24
23
|
from understanding_cache import _fresh
|
|
25
24
|
|
|
26
25
|
# Shared tolerance for the per-segment span check. The brief-side gate inlines the SAME
|
|
@@ -59,9 +58,7 @@ INDEX_PROMPT = """你在根据逐场景画面分析、ASR对白、background_res
|
|
|
59
58
|
def build_clean_messages(asr_result):
|
|
60
59
|
lines = []
|
|
61
60
|
for i, seg in enumerate(asr_result or []):
|
|
62
|
-
|
|
63
|
-
continue
|
|
64
|
-
lines.append(f"{i}. {str(seg.get('text', '')).strip()}")
|
|
61
|
+
lines.append(f"{i}. {seg['text'].strip()}")
|
|
65
62
|
user = f"{CLEAN_PROMPT}\n\n## 逐段转写(共 {len(lines)} 段)\n" + "\n".join(lines)
|
|
66
63
|
return [{"role": "user", "content": user}]
|
|
67
64
|
|
|
@@ -69,7 +66,7 @@ def build_clean_messages(asr_result):
|
|
|
69
66
|
def parse_clean_response(text, asr_result):
|
|
70
67
|
"""Zip cleaned TEXT onto the original segments (start/end preserved by construction).
|
|
71
68
|
Returns the original asr_result UNCHANGED on any parse / shape / count problem."""
|
|
72
|
-
base =
|
|
69
|
+
base = list(asr_result or [])
|
|
73
70
|
if not base:
|
|
74
71
|
return asr_result
|
|
75
72
|
data = _extract_json(text)
|
|
@@ -87,8 +84,8 @@ def parse_clean_response(text, asr_result):
|
|
|
87
84
|
out = []
|
|
88
85
|
for i, seg in enumerate(base):
|
|
89
86
|
cleaned = by_index.get(i, {})
|
|
90
|
-
new_text = str(cleaned.get("text", "")).strip() or
|
|
91
|
-
merged = {"start": seg
|
|
87
|
+
new_text = str(cleaned.get("text", "")).strip() or seg["text"]
|
|
88
|
+
merged = {"start": seg["start"], "end": seg["end"], "text": new_text}
|
|
92
89
|
speaker = str(cleaned.get("speaker", "")).strip()
|
|
93
90
|
if speaker:
|
|
94
91
|
merged["speaker"] = speaker
|
|
@@ -96,17 +93,10 @@ def parse_clean_response(text, asr_result):
|
|
|
96
93
|
return out
|
|
97
94
|
|
|
98
95
|
|
|
99
|
-
def
|
|
96
|
+
def _input_identity(work_dir, name):
|
|
97
|
+
"""{size, mtime_ns} of one input artifact, or None when it is absent."""
|
|
100
98
|
path = Path(work_dir) / name
|
|
101
|
-
return
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
def _asr_clean_source_md5(work_dir):
|
|
105
|
-
return _json_md5_file(work_dir, "asr_clean.json")
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
def _research_source_md5(work_dir):
|
|
109
|
-
return _json_md5_file(work_dir, "background_research.json")
|
|
99
|
+
return file_identity(path) if path.exists() else None
|
|
110
100
|
|
|
111
101
|
|
|
112
102
|
def _research_glossary_from_context(background_research):
|
|
@@ -148,23 +138,22 @@ def _research_glossary_from_context(background_research):
|
|
|
148
138
|
return glossary
|
|
149
139
|
|
|
150
140
|
|
|
141
|
+
def _dialogue_source(asr_result, asr_clean):
|
|
142
|
+
"""asr_clean.json segments when present (consolidate's own output), else raw asr_result rows."""
|
|
143
|
+
return asr_clean["segments"] if asr_clean else (asr_result or [])
|
|
144
|
+
|
|
145
|
+
|
|
151
146
|
def _dialogue_segments_for_index(asr_result=None, asr_clean=None):
|
|
152
|
-
clean_segments = (
|
|
153
|
-
(asr_clean or {}).get("segments") if isinstance(asr_clean, dict) else None
|
|
154
|
-
)
|
|
155
|
-
source = clean_segments if isinstance(clean_segments, list) else (asr_result or [])
|
|
156
147
|
out = []
|
|
157
|
-
for i, seg in enumerate(
|
|
158
|
-
|
|
159
|
-
continue
|
|
160
|
-
text = str(seg.get("text", "")).strip()
|
|
148
|
+
for i, seg in enumerate(_dialogue_source(asr_result, asr_clean)):
|
|
149
|
+
text = seg["text"].strip()
|
|
161
150
|
if not text:
|
|
162
151
|
continue
|
|
163
152
|
out.append(
|
|
164
153
|
{
|
|
165
154
|
"id": f"asr:{i}",
|
|
166
|
-
"start": seg
|
|
167
|
-
"end": seg
|
|
155
|
+
"start": seg["start"],
|
|
156
|
+
"end": seg["end"],
|
|
168
157
|
"text": text,
|
|
169
158
|
}
|
|
170
159
|
)
|
|
@@ -280,44 +269,34 @@ def build_index_messages(
|
|
|
280
269
|
):
|
|
281
270
|
lines = []
|
|
282
271
|
for i, scene in enumerate(vlm_analysis or []):
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
desc = str(scene.get("description", "")).strip().replace("\n", " ")
|
|
289
|
-
facts = scene.get("frame_facts")
|
|
272
|
+
sid = scene["scene_id"]
|
|
273
|
+
start = float(scene["start"])
|
|
274
|
+
end = float(scene["end"])
|
|
275
|
+
desc = scene["description"].strip().replace("\n", " ")
|
|
276
|
+
facts = scene.get("frame_facts") # vlm.py only writes the key when non-empty
|
|
290
277
|
fact_txt = ""
|
|
291
|
-
if
|
|
278
|
+
if facts:
|
|
292
279
|
actions = []
|
|
293
280
|
|
|
294
281
|
def fact_key(x):
|
|
282
|
+
# timestamps are regex-captured from the VLM reply ("[\d.]+"), so "1.2.3" is possible
|
|
295
283
|
try:
|
|
296
284
|
return (0, float(x))
|
|
297
|
-
except
|
|
285
|
+
except ValueError:
|
|
298
286
|
return (1, str(x))
|
|
299
287
|
|
|
300
288
|
for ts in sorted(facts.keys(), key=fact_key):
|
|
301
|
-
|
|
302
|
-
actions.extend(vals if isinstance(vals, list) else [str(vals)])
|
|
289
|
+
actions.extend(facts[ts])
|
|
303
290
|
if actions:
|
|
304
291
|
fact_txt = " | 帧实: " + ";".join(a for a in actions[:6] if a)
|
|
305
292
|
lines.append(f"[visual:{i} 场景{sid} {start:.0f}-{end:.0f}s] {desc}{fact_txt}")
|
|
306
293
|
asr_lines = []
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
)
|
|
310
|
-
source_asr = (
|
|
311
|
-
clean_segments if isinstance(clean_segments, list) else (asr_result or [])
|
|
312
|
-
)
|
|
313
|
-
for i, seg in enumerate(source_asr or []):
|
|
314
|
-
if not isinstance(seg, dict):
|
|
315
|
-
continue
|
|
316
|
-
text = str(seg.get("text", "")).strip()
|
|
294
|
+
for i, seg in enumerate(_dialogue_source(asr_result, asr_clean)):
|
|
295
|
+
text = seg["text"].strip()
|
|
317
296
|
if not text:
|
|
318
297
|
continue
|
|
319
298
|
asr_lines.append(
|
|
320
|
-
f"[asr:{i} {float(seg
|
|
299
|
+
f"[asr:{i} {float(seg['start']):.0f}-{float(seg['end']):.0f}s] {text}"
|
|
321
300
|
)
|
|
322
301
|
glossary = _research_glossary_from_context(background_research)
|
|
323
302
|
glossary_lines = []
|
|
@@ -432,44 +411,31 @@ def _extract_json(text):
|
|
|
432
411
|
return None
|
|
433
412
|
|
|
434
413
|
|
|
435
|
-
def _asr_source_md5(work_dir):
|
|
436
|
-
"""Provenance: md5 of the on-disk asr_result.json BYTES (writer + reader hash the same thing)."""
|
|
437
|
-
path = Path(work_dir) / "asr_result.json"
|
|
438
|
-
return hashlib.md5(path.read_bytes()).hexdigest() if path.exists() else ""
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
def _vlm_source_md5(work_dir):
|
|
442
|
-
"""Provenance: md5 of the on-disk vlm_analysis.json bytes."""
|
|
443
|
-
path = Path(work_dir) / "vlm_analysis.json"
|
|
444
|
-
return hashlib.md5(path.read_bytes()).hexdigest() if path.exists() else ""
|
|
445
|
-
|
|
446
|
-
|
|
447
414
|
def _index_meta_path(work_dir):
|
|
448
415
|
return Path(work_dir) / "understanding_index.json.meta.json"
|
|
449
416
|
|
|
450
417
|
|
|
451
|
-
def
|
|
452
|
-
|
|
418
|
+
def _index_meta(work_dir, vlm_analysis):
|
|
419
|
+
"""Provenance of understanding_index.json: `source` (vlm_analysis.json identity) + `model`
|
|
420
|
+
are what brief consumers check; the other inputs and the prompt text are the producer's
|
|
421
|
+
own rebuild gate."""
|
|
422
|
+
return {
|
|
423
|
+
"schema_version": INDEX_SCHEMA_VERSION,
|
|
424
|
+
"source": _input_identity(work_dir, "vlm_analysis.json"),
|
|
425
|
+
"inputs": {
|
|
426
|
+
"asr_result": _input_identity(work_dir, "asr_result.json"),
|
|
427
|
+
"asr_clean": _input_identity(work_dir, "asr_clean.json"),
|
|
428
|
+
"background_research": _input_identity(work_dir, "background_research.json"),
|
|
429
|
+
},
|
|
430
|
+
"scene_count": len([s for s in (vlm_analysis or []) if isinstance(s, dict)]),
|
|
431
|
+
"model": CONFIG["vlm_model"],
|
|
432
|
+
"prompt": INDEX_PROMPT,
|
|
433
|
+
}
|
|
453
434
|
|
|
454
435
|
|
|
455
436
|
def _write_index_meta(work_dir, vlm_analysis):
|
|
456
437
|
_index_meta_path(work_dir).write_text(
|
|
457
|
-
json.dumps(
|
|
458
|
-
{
|
|
459
|
-
"schema_version": INDEX_SCHEMA_VERSION,
|
|
460
|
-
"source_md5": _vlm_source_md5(work_dir),
|
|
461
|
-
"asr_md5": _asr_source_md5(work_dir),
|
|
462
|
-
"asr_clean_md5": _asr_clean_source_md5(work_dir),
|
|
463
|
-
"research_md5": _research_source_md5(work_dir),
|
|
464
|
-
"scene_count": len(
|
|
465
|
-
[s for s in (vlm_analysis or []) if isinstance(s, dict)]
|
|
466
|
-
),
|
|
467
|
-
"model": CONFIG.get("vlm_model", ""),
|
|
468
|
-
"prompt_md5": _prompt_fingerprint(INDEX_PROMPT),
|
|
469
|
-
},
|
|
470
|
-
ensure_ascii=False,
|
|
471
|
-
indent=2,
|
|
472
|
-
),
|
|
438
|
+
json.dumps(_index_meta(work_dir, vlm_analysis), ensure_ascii=False, indent=2),
|
|
473
439
|
encoding="utf-8",
|
|
474
440
|
)
|
|
475
441
|
|
|
@@ -482,31 +448,18 @@ def _index_cache_matches(work_dir, vlm_analysis):
|
|
|
482
448
|
meta = json.loads(meta_path.read_text(encoding="utf-8"))
|
|
483
449
|
except (json.JSONDecodeError, OSError):
|
|
484
450
|
return False
|
|
485
|
-
return (
|
|
486
|
-
isinstance(meta, dict)
|
|
487
|
-
and meta.get("schema_version") == INDEX_SCHEMA_VERSION
|
|
488
|
-
and meta.get("source_md5") == _vlm_source_md5(work_dir)
|
|
489
|
-
and meta.get("asr_md5", "") == _asr_source_md5(work_dir)
|
|
490
|
-
and meta.get("asr_clean_md5", "") == _asr_clean_source_md5(work_dir)
|
|
491
|
-
and meta.get("research_md5", "") == _research_source_md5(work_dir)
|
|
492
|
-
and meta.get("scene_count")
|
|
493
|
-
== len([s for s in (vlm_analysis or []) if isinstance(s, dict)])
|
|
494
|
-
and meta.get("model") == CONFIG.get("vlm_model", "")
|
|
495
|
-
and meta.get("prompt_md5") == _prompt_fingerprint(INDEX_PROMPT)
|
|
496
|
-
)
|
|
451
|
+
return meta == _index_meta(work_dir, vlm_analysis)
|
|
497
452
|
|
|
498
453
|
|
|
499
454
|
# ── thin drivers (I/O + api_call) ─────────────────────────────────────────────
|
|
500
455
|
|
|
501
456
|
|
|
502
457
|
def _load(work_dir, name):
|
|
458
|
+
"""Read one of this skill's own JSON artifacts: absent → None, corrupt → raise."""
|
|
503
459
|
path = Path(work_dir) / name
|
|
504
460
|
if not path.exists():
|
|
505
461
|
return None
|
|
506
|
-
|
|
507
|
-
return json.loads(path.read_text(encoding="utf-8"))
|
|
508
|
-
except (ValueError, OSError):
|
|
509
|
-
return None
|
|
462
|
+
return json.loads(path.read_text(encoding="utf-8"))
|
|
510
463
|
|
|
511
464
|
|
|
512
465
|
def consolidate_transcript(work_dir):
|
|
@@ -519,16 +472,16 @@ def consolidate_transcript(work_dir):
|
|
|
519
472
|
if _fresh(out_path, work_dir / "asr_result.json"):
|
|
520
473
|
existing = _load(work_dir, "asr_clean.json") or {}
|
|
521
474
|
if (
|
|
522
|
-
existing.get("
|
|
523
|
-
and existing.get("model") == CONFIG
|
|
524
|
-
and existing.get("
|
|
475
|
+
existing.get("source") == _input_identity(work_dir, "asr_result.json")
|
|
476
|
+
and existing.get("model") == CONFIG["vlm_model"]
|
|
477
|
+
and existing.get("prompt") == CLEAN_PROMPT
|
|
525
478
|
and existing.get("postprocess_version") == ASR_CLEAN_POSTPROCESS_VERSION
|
|
526
479
|
):
|
|
527
480
|
log("consolidate(asr): asr_clean.json 已最新,跳过")
|
|
528
481
|
return existing
|
|
529
482
|
resp = api_call(
|
|
530
483
|
{
|
|
531
|
-
"model": CONFIG
|
|
484
|
+
"model": CONFIG["vlm_model"],
|
|
532
485
|
"messages": build_clean_messages(asr_result),
|
|
533
486
|
"max_tokens": 4000,
|
|
534
487
|
"temperature": 0.2,
|
|
@@ -537,9 +490,9 @@ def consolidate_transcript(work_dir):
|
|
|
537
490
|
content = _response_text(resp)
|
|
538
491
|
segments = parse_clean_response(content, asr_result)
|
|
539
492
|
payload = {
|
|
540
|
-
"
|
|
541
|
-
"model": CONFIG
|
|
542
|
-
"
|
|
493
|
+
"source": _input_identity(work_dir, "asr_result.json"),
|
|
494
|
+
"model": CONFIG["vlm_model"],
|
|
495
|
+
"prompt": CLEAN_PROMPT,
|
|
543
496
|
"postprocess_version": ASR_CLEAN_POSTPROCESS_VERSION,
|
|
544
497
|
"segments": segments,
|
|
545
498
|
}
|
|
@@ -564,10 +517,10 @@ def consolidate_index(work_dir):
|
|
|
564
517
|
return _load(work_dir, "understanding_index.json")
|
|
565
518
|
asr_result = _load(work_dir, "asr_result.json") or []
|
|
566
519
|
asr_clean = _load(work_dir, "asr_clean.json") or {}
|
|
567
|
-
background_research =
|
|
520
|
+
background_research = load_background_research(work_dir)
|
|
568
521
|
resp = api_call(
|
|
569
522
|
{
|
|
570
|
-
"model": CONFIG
|
|
523
|
+
"model": CONFIG["vlm_model"],
|
|
571
524
|
"messages": build_index_messages(
|
|
572
525
|
vlm_analysis,
|
|
573
526
|
asr_result=asr_result,
|
|
@@ -599,18 +552,14 @@ def consolidate_index(work_dir):
|
|
|
599
552
|
|
|
600
553
|
|
|
601
554
|
def consolidate(work_dir, do_asr=False, do_index=True):
|
|
602
|
-
"""Default = index-only (Pass B, zero timing risk). Pass A (asr) is opt-in.
|
|
555
|
+
"""Default = index-only (Pass B, zero timing risk). Pass A (asr) is opt-in.
|
|
556
|
+
|
|
557
|
+
Failures propagate; understanding_runner catches them and writes the "failed" status."""
|
|
603
558
|
result = {}
|
|
604
559
|
if do_asr:
|
|
605
|
-
|
|
606
|
-
result["asr_clean"] = consolidate_transcript(work_dir)
|
|
607
|
-
except Exception as e:
|
|
608
|
-
log(f"consolidate(asr) 跳过(忽略): {e}")
|
|
560
|
+
result["asr_clean"] = consolidate_transcript(work_dir)
|
|
609
561
|
if do_index:
|
|
610
|
-
|
|
611
|
-
result["index"] = consolidate_index(work_dir)
|
|
612
|
-
except Exception as e:
|
|
613
|
-
log(f"consolidate(index) 跳过(忽略): {e}")
|
|
562
|
+
result["index"] = consolidate_index(work_dir)
|
|
614
563
|
return result
|
|
615
564
|
|
|
616
565
|
|
|
@@ -6,7 +6,7 @@ from concurrent.futures import ThreadPoolExecutor
|
|
|
6
6
|
from pathlib import Path
|
|
7
7
|
|
|
8
8
|
from lib import CONFIG
|
|
9
|
-
from lib import log, run_cmd, get_video_duration,
|
|
9
|
+
from lib import log, run_cmd, get_video_duration, file_identity
|
|
10
10
|
|
|
11
11
|
# ── Step 2: 场景检测 ──────────────────────────────────────────────────
|
|
12
12
|
|
|
@@ -48,10 +48,10 @@ def detect_scenes(video_path, work_dir, threshold=None):
|
|
|
48
48
|
|
|
49
49
|
# 先过滤黑/白帧过渡场景,再合并短场景
|
|
50
50
|
# (合并会保留短场景的 start,若黑场被并入长场景,单点采样会误删整段,故顺序在前)
|
|
51
|
-
if CONFIG
|
|
51
|
+
if CONFIG["scene_junk_filter"]:
|
|
52
52
|
scenes = _filter_junk_scenes(scenes, video_path)
|
|
53
53
|
# 合并短场景(< 3s 合并到相邻场景)
|
|
54
|
-
scenes = _merge_short_scenes(scenes, min_duration=CONFIG
|
|
54
|
+
scenes = _merge_short_scenes(scenes, min_duration=CONFIG["scene_merge_min"])
|
|
55
55
|
|
|
56
56
|
# 保存
|
|
57
57
|
scenes_file = work_dir / "scenes.json"
|
|
@@ -115,9 +115,9 @@ def _sample_frame_luma(video_path, timestamp, sample_size=64):
|
|
|
115
115
|
|
|
116
116
|
def _is_junk_scene(video_path, timestamp, threshold_dark=None, threshold_bright=None):
|
|
117
117
|
"""Return True for near-black or near-white scene-start frames."""
|
|
118
|
-
threshold_dark = CONFIG
|
|
119
|
-
threshold_bright = CONFIG
|
|
120
|
-
pixel_ratio = min(1.0,
|
|
118
|
+
threshold_dark = CONFIG["scene_junk_dark_luma"] if threshold_dark is None else threshold_dark
|
|
119
|
+
threshold_bright = CONFIG["scene_junk_bright_luma"] if threshold_bright is None else threshold_bright
|
|
120
|
+
pixel_ratio = min(1.0, float(CONFIG["scene_junk_pixel_ratio"]))
|
|
121
121
|
try:
|
|
122
122
|
lumas = _sample_frame_luma(video_path, timestamp)
|
|
123
123
|
except Exception as exc:
|
|
@@ -157,7 +157,7 @@ def _filter_junk_scenes(scenes, video_path):
|
|
|
157
157
|
# 每个探点都是一次 ffmpeg 进程(seek + 解一帧),一部 30 分钟片有几百个场景,串行下来
|
|
158
158
|
# 光进程启动就要几十秒。按场景并行:每个 worker 内部仍然短路,所以既保留了「多数场景只
|
|
159
159
|
# 探一次」,又把进程启动开销摊平。判定逻辑与探点完全不变。
|
|
160
|
-
workers =
|
|
160
|
+
workers = int(CONFIG["scene_junk_workers"])
|
|
161
161
|
with ThreadPoolExecutor(max_workers=min(workers, len(scenes))) as executor:
|
|
162
162
|
verdicts = list(executor.map(lambda s: _scene_is_junk(s, video_path), scenes))
|
|
163
163
|
filtered = []
|
|
@@ -203,7 +203,7 @@ def annotate_quiet_windows_with_asr(periods, asr_result=None, *, video_duration=
|
|
|
203
203
|
Coarse grid ASR (large synthetic windows from chunking) must not turn every quiet
|
|
204
204
|
window into speech. The return value is (annotated_periods, qc). Inputs are copied.
|
|
205
205
|
"""
|
|
206
|
-
out = [dict(p) for p in (periods or [])
|
|
206
|
+
out = [dict(p) for p in (periods or [])]
|
|
207
207
|
qc = {"coarse_asr_windows": 0, "low_confidence_speech_flags": 0, "asr_granularity": "none"}
|
|
208
208
|
for qp in out:
|
|
209
209
|
qp.setdefault("speech_overlap_ratio", 0.0)
|
|
@@ -216,20 +216,15 @@ def annotate_quiet_windows_with_asr(periods, asr_result=None, *, video_duration=
|
|
|
216
216
|
|
|
217
217
|
valid_segments = []
|
|
218
218
|
for seg in asr_result:
|
|
219
|
-
|
|
220
|
-
continue
|
|
221
|
-
try:
|
|
222
|
-
ss, se = float(seg.get("start", 0)), float(seg.get("end", 0))
|
|
223
|
-
except (TypeError, ValueError):
|
|
224
|
-
continue
|
|
219
|
+
ss, se = float(seg["start"]), float(seg["end"])
|
|
225
220
|
if se > ss:
|
|
226
221
|
valid_segments.append((ss, se))
|
|
227
222
|
asr_coverage = sum(se - ss for ss, se in valid_segments)
|
|
228
223
|
avg_seg_dur = asr_coverage / len(valid_segments) if valid_segments else 0
|
|
229
224
|
if video_duration is None:
|
|
230
|
-
ends = [float(p
|
|
225
|
+
ends = [float(p["end"]) for p in out] + [se for _, se in valid_segments]
|
|
231
226
|
video_duration = max(ends or [0.0])
|
|
232
|
-
configured = float(configured_segment_seconds if configured_segment_seconds is not None else
|
|
227
|
+
configured = float(configured_segment_seconds if configured_segment_seconds is not None else CONFIG["asr_segment_seconds"])
|
|
233
228
|
coarse_asr = (
|
|
234
229
|
(len(valid_segments) <= 5 and asr_coverage > float(video_duration) * 0.8) or
|
|
235
230
|
asr_coverage > float(video_duration) * 1.5 or
|
|
@@ -243,7 +238,7 @@ def annotate_quiet_windows_with_asr(periods, asr_result=None, *, video_duration=
|
|
|
243
238
|
overlap_seconds = 0.0
|
|
244
239
|
for ss, se in valid_segments:
|
|
245
240
|
overlap_seconds += max(0.0, min(float(qp["end"]), se) - max(float(qp["start"]), ss))
|
|
246
|
-
ratio = overlap_seconds / max(0.001, float(qp
|
|
241
|
+
ratio = overlap_seconds / max(0.001, float(qp["duration"]))
|
|
247
242
|
qp["asr_overlap_seconds"] = round(overlap_seconds, 3)
|
|
248
243
|
qp["speech_overlap_ratio"] = round(ratio, 4)
|
|
249
244
|
qp["asr_granularity"] = granularity
|
|
@@ -333,7 +328,7 @@ def detect_silence_periods(video_path, work_dir, asr_result=None):
|
|
|
333
328
|
"duration": round(total_dur - dur_start, 2)})
|
|
334
329
|
|
|
335
330
|
# 合并相邻静音段(间隔 < merge_gap 的合并为一个大窗口)
|
|
336
|
-
merge_gap = CONFIG
|
|
331
|
+
merge_gap = CONFIG["silence_merge_gap"]
|
|
337
332
|
merged = []
|
|
338
333
|
for rp in sorted(raw_periods, key=lambda x: x["start"]):
|
|
339
334
|
if merged and rp["start"] - merged[-1]["end"] < merge_gap:
|
|
@@ -352,7 +347,7 @@ def detect_silence_periods(video_path, work_dir, asr_result=None):
|
|
|
352
347
|
periods,
|
|
353
348
|
asr_result,
|
|
354
349
|
video_duration=get_video_duration(str(audio_path)) if asr_result else None,
|
|
355
|
-
configured_segment_seconds=CONFIG
|
|
350
|
+
configured_segment_seconds=CONFIG["asr_segment_seconds"],
|
|
356
351
|
)
|
|
357
352
|
|
|
358
353
|
(work_dir / "silence_periods.qc.json").write_text(
|
|
@@ -368,12 +363,6 @@ def detect_silence_periods(video_path, work_dir, asr_result=None):
|
|
|
368
363
|
return periods
|
|
369
364
|
|
|
370
365
|
|
|
371
|
-
def _asr_segments(asr_result):
|
|
372
|
-
if isinstance(asr_result, dict):
|
|
373
|
-
asr_result = asr_result.get("segments", [])
|
|
374
|
-
return [item for item in (asr_result or []) if isinstance(item, dict)]
|
|
375
|
-
|
|
376
|
-
|
|
377
366
|
def detect_speech_boundary_anchors(work_dir, asr_result):
|
|
378
367
|
"""Write sentence-end entry anchors by aligning ASR punctuation to short pauses.
|
|
379
368
|
|
|
@@ -385,14 +374,14 @@ def detect_speech_boundary_anchors(work_dir, asr_result):
|
|
|
385
374
|
work_dir = Path(work_dir)
|
|
386
375
|
audio_path = work_dir / "audio.wav"
|
|
387
376
|
out_path = work_dir / "speech_boundary_anchors.json"
|
|
388
|
-
segments =
|
|
377
|
+
segments = list(asr_result or [])
|
|
389
378
|
report = {
|
|
390
379
|
"schema_version": 1,
|
|
391
380
|
"artifact": "speech_boundary_anchors.json",
|
|
392
381
|
"status": "completed",
|
|
393
382
|
"detector": {
|
|
394
|
-
"noise_threshold": CONFIG
|
|
395
|
-
"min_pause_seconds": float(CONFIG
|
|
383
|
+
"noise_threshold": CONFIG["source_boundary_noise_threshold"],
|
|
384
|
+
"min_pause_seconds": float(CONFIG["source_boundary_min_pause"]),
|
|
396
385
|
"alignment": "terminal_punctuation_to_nearest_acoustic_pause",
|
|
397
386
|
},
|
|
398
387
|
"sentence_anchors": [],
|
|
@@ -408,8 +397,8 @@ def detect_speech_boundary_anchors(work_dir, asr_result):
|
|
|
408
397
|
"ffmpeg", "-hide_banner", "-nostats", "-i", str(audio_path),
|
|
409
398
|
"-af", (
|
|
410
399
|
"silencedetect="
|
|
411
|
-
f"noise={CONFIG
|
|
412
|
-
f"d={float(CONFIG
|
|
400
|
+
f"noise={CONFIG['source_boundary_noise_threshold']}:"
|
|
401
|
+
f"d={float(CONFIG['source_boundary_min_pause'])}"
|
|
413
402
|
),
|
|
414
403
|
"-f", "null", "-",
|
|
415
404
|
], timeout=120)
|
|
@@ -434,16 +423,13 @@ def detect_speech_boundary_anchors(work_dir, asr_result):
|
|
|
434
423
|
})
|
|
435
424
|
report["acoustic_pauses"] = pauses
|
|
436
425
|
|
|
437
|
-
max_error = float(CONFIG
|
|
426
|
+
max_error = float(CONFIG["source_boundary_max_alignment_error"])
|
|
438
427
|
used = set()
|
|
439
428
|
anchors = []
|
|
440
429
|
for asr_index, segment in enumerate(segments):
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
except (TypeError, ValueError):
|
|
445
|
-
continue
|
|
446
|
-
text = str(segment.get("text") or "").strip()
|
|
430
|
+
seg_start = float(segment["start"])
|
|
431
|
+
seg_end = float(segment["end"])
|
|
432
|
+
text = segment["text"].strip()
|
|
447
433
|
if seg_end <= seg_start or not text:
|
|
448
434
|
continue
|
|
449
435
|
last_midpoint = seg_start - 1e-6
|
|
@@ -496,17 +482,22 @@ def _audio_cache_matches(audio_path, video_path):
|
|
|
496
482
|
except (OSError, ValueError, TypeError):
|
|
497
483
|
return False
|
|
498
484
|
try:
|
|
499
|
-
expected =
|
|
485
|
+
expected = file_identity(video_path)
|
|
500
486
|
except OSError:
|
|
501
487
|
return False
|
|
502
|
-
return
|
|
488
|
+
return (
|
|
489
|
+
isinstance(meta, dict)
|
|
490
|
+
and meta.get("source_video") == str(Path(video_path).resolve())
|
|
491
|
+
and meta.get("source_video_identity") == expected
|
|
492
|
+
)
|
|
503
493
|
|
|
504
494
|
|
|
505
495
|
def _write_audio_meta(work_dir, video_path):
|
|
506
496
|
_audio_meta_path(work_dir).write_text(
|
|
507
497
|
json.dumps({
|
|
508
498
|
"schema_version": 1,
|
|
509
|
-
"
|
|
499
|
+
"source_video": str(Path(video_path).resolve()),
|
|
500
|
+
"source_video_identity": file_identity(video_path),
|
|
510
501
|
"audio": "audio.wav",
|
|
511
502
|
}, ensure_ascii=False, indent=2),
|
|
512
503
|
encoding="utf-8",
|