@oh-story/dsh 0.1.10 → 0.1.12
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 +11 -11
- 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 +41 -41
- 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
|
@@ -88,7 +88,9 @@ def coverage_policy_v1(
|
|
|
88
88
|
"coverage_policy_version": COVERAGE_POLICY_VERSION,
|
|
89
89
|
"selected_ranges": [],
|
|
90
90
|
"dropped_ranges": [],
|
|
91
|
+
"dropped_range_count": 0,
|
|
91
92
|
"duration": 0.0,
|
|
93
|
+
"baseline_range_count_before_narration": 0,
|
|
92
94
|
}
|
|
93
95
|
width = min(float(window_seconds), max(5.0, duration / 8.0))
|
|
94
96
|
ranges = [
|
|
@@ -345,72 +347,21 @@ def filter_evidence_by_ranges(vlm_analysis, asr_result, ranges, *, timeline="sou
|
|
|
345
347
|
|
|
346
348
|
|
|
347
349
|
def build_review_coverage_metadata(bundle):
|
|
348
|
-
"""
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
metadata = bundle.get("metadata", {}) if isinstance(bundle, dict) else {}
|
|
350
|
+
"""Summarize a build_evidence_bundle() bundle's coverage contract."""
|
|
351
|
+
coverage = bundle["coverage"]
|
|
352
|
+
metadata = bundle["metadata"]
|
|
352
353
|
return {
|
|
353
|
-
"coverage_policy_version": coverage
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
"
|
|
357
|
-
"
|
|
358
|
-
"
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
"scene_count": metadata.get(
|
|
362
|
-
"reviewed_scene_count",
|
|
363
|
-
sum(1 for item in items if item.get("source") == "visual"),
|
|
364
|
-
),
|
|
365
|
-
"asr_count": metadata.get(
|
|
366
|
-
"reviewed_asr_count",
|
|
367
|
-
sum(1 for item in items if item.get("source") == "asr"),
|
|
368
|
-
),
|
|
369
|
-
"dropped_scene_count": metadata.get("dropped_scene_count", 0),
|
|
370
|
-
"dropped_asr_count": metadata.get("dropped_asr_count", 0),
|
|
354
|
+
"coverage_policy_version": coverage["coverage_policy_version"],
|
|
355
|
+
"time_ranges": coverage["selected_ranges"],
|
|
356
|
+
"dropped_ranges": coverage["dropped_ranges"],
|
|
357
|
+
"dropped_range_count": coverage["dropped_range_count"],
|
|
358
|
+
"scene_count": metadata["reviewed_scene_count"],
|
|
359
|
+
"asr_count": metadata["reviewed_asr_count"],
|
|
360
|
+
"dropped_scene_count": metadata["dropped_scene_count"],
|
|
361
|
+
"dropped_asr_count": metadata["dropped_asr_count"],
|
|
371
362
|
}
|
|
372
363
|
|
|
373
364
|
|
|
374
|
-
def validate_public_evidence_contract(bundle):
|
|
375
|
-
"""Pure compatibility seam: validate the public evidence bundle shape.
|
|
376
|
-
|
|
377
|
-
Returns a non-throwing report so callers can use it in tests or advisory QC paths.
|
|
378
|
-
"""
|
|
379
|
-
errors = []
|
|
380
|
-
warnings = []
|
|
381
|
-
if not isinstance(bundle, dict):
|
|
382
|
-
return {"valid": False, "errors": ["bundle must be a dict"], "warnings": []}
|
|
383
|
-
if bundle.get("schema_version") != EVIDENCE_CONTRACT_VERSION:
|
|
384
|
-
errors.append("unsupported schema_version")
|
|
385
|
-
if bundle.get("clock") not in ("source", "output"):
|
|
386
|
-
errors.append("clock must be source or output")
|
|
387
|
-
for idx, item in enumerate(bundle.get("items") or []):
|
|
388
|
-
if not isinstance(item, dict):
|
|
389
|
-
errors.append(f"items[{idx}] must be a dict")
|
|
390
|
-
continue
|
|
391
|
-
if item.get("source") not in ("visual", "asr"):
|
|
392
|
-
errors.append(f"items[{idx}].source must be visual or asr")
|
|
393
|
-
if item.get("clock") not in ("source", "output"):
|
|
394
|
-
errors.append(f"items[{idx}].clock must be source or output")
|
|
395
|
-
if item.get("support") != "direct":
|
|
396
|
-
warnings.append(f"items[{idx}].support is not direct")
|
|
397
|
-
start, end = item.get("start"), item.get("end")
|
|
398
|
-
try:
|
|
399
|
-
if float(end) <= float(start):
|
|
400
|
-
errors.append(f"items[{idx}] has non-positive time range")
|
|
401
|
-
except (TypeError, ValueError):
|
|
402
|
-
errors.append(f"items[{idx}] has invalid time range")
|
|
403
|
-
for idx, item in enumerate(bundle.get("context_items") or []):
|
|
404
|
-
if not isinstance(item, dict):
|
|
405
|
-
errors.append(f"context_items[{idx}] must be a dict")
|
|
406
|
-
continue
|
|
407
|
-
if item.get("clock") is not None:
|
|
408
|
-
errors.append(f"context_items[{idx}].clock must be null")
|
|
409
|
-
if item.get("support") != "context_only":
|
|
410
|
-
errors.append(f"context_items[{idx}].support must be context_only")
|
|
411
|
-
return {"valid": not errors, "errors": errors, "warnings": warnings}
|
|
412
|
-
|
|
413
|
-
|
|
414
365
|
def build_evidence_bundle(
|
|
415
366
|
vlm_analysis,
|
|
416
367
|
asr_result,
|
|
@@ -448,30 +399,27 @@ def build_evidence_bundle(
|
|
|
448
399
|
|
|
449
400
|
|
|
450
401
|
def render_evidence_bundle(bundle, *, limit_items=220):
|
|
451
|
-
clock =
|
|
402
|
+
clock = bundle["clock"].upper()
|
|
452
403
|
lines = [f"## Timeline evidence (clock={clock}; source=visual/asr)"]
|
|
453
|
-
items = bundle
|
|
404
|
+
items = bundle["items"]
|
|
454
405
|
if not items:
|
|
455
406
|
lines.append("(无 timeline evidence)")
|
|
456
407
|
for item in items[:limit_items]:
|
|
457
|
-
label = "画面" if item
|
|
408
|
+
label = "画面" if item["source"] == "visual" else "对白"
|
|
458
409
|
backref = ""
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
backref = f" ← SOURCE {float(item['source_start']):.1f}-{float(item['source_end']):.1f}s"
|
|
465
|
-
if item.get("output_segment_index") is not None:
|
|
466
|
-
backref += f" clip#{item.get('output_segment_index')}"
|
|
410
|
+
# Remapped cut_output evidence carries its source-time provenance.
|
|
411
|
+
if "source_start" in item:
|
|
412
|
+
backref = f" ← SOURCE {item['source_start']:.1f}-{item['source_end']:.1f}s"
|
|
413
|
+
if "output_segment_index" in item:
|
|
414
|
+
backref += f" clip#{item['output_segment_index']}"
|
|
467
415
|
lines.append(
|
|
468
|
-
f"[{clock} {
|
|
416
|
+
f"[{clock} {item['start']:.1f}-{item['end']:.1f}s {label} id={item['id']}{backref}] {item['text']}"
|
|
469
417
|
)
|
|
470
418
|
if len(items) > limit_items:
|
|
471
419
|
lines.append(
|
|
472
420
|
f"... dropped from prompt: {len(items) - limit_items} items (artifact metadata keeps counts)"
|
|
473
421
|
)
|
|
474
|
-
context = bundle
|
|
422
|
+
context = bundle["context_items"]
|
|
475
423
|
lines.extend(
|
|
476
424
|
[
|
|
477
425
|
"",
|
|
@@ -482,7 +430,7 @@ def render_evidence_bundle(bundle, *, limit_items=220):
|
|
|
482
430
|
lines.append("(无 context-only evidence)")
|
|
483
431
|
for item in context[:40]:
|
|
484
432
|
lines.append(
|
|
485
|
-
f"[clock=null {item
|
|
433
|
+
f"[clock=null {item['source']} support=context_only id={item['id']}] {item['text']}"
|
|
486
434
|
)
|
|
487
435
|
return "\n".join(lines)
|
|
488
436
|
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"""Self-contained config + utilities for this skill (no cross-skill imports).
|
|
2
2
|
Merged from the shared core; reads the same env vars as the rest of the bundle."""
|
|
3
3
|
import json
|
|
4
|
-
import hashlib
|
|
5
4
|
import math
|
|
6
5
|
import os
|
|
7
6
|
import re
|
|
@@ -22,8 +21,6 @@ MIMO_TOKEN_PLAN_API_URLS = {
|
|
|
22
21
|
"ams": "https://token-plan-ams.xiaomimimo.com/v1",
|
|
23
22
|
}
|
|
24
23
|
DEFAULT_MIMO_MODEL = "mimo-v2.5" # VLM / chat (vision understanding)
|
|
25
|
-
DEFAULT_MIMO_ASR_MODEL = "mimo-v2.5-asr" # speech-to-text
|
|
26
|
-
DEFAULT_MIMO_TTS_MODEL = "mimo-v2.5-tts" # text-to-speech
|
|
27
24
|
|
|
28
25
|
|
|
29
26
|
def normalize_api_url(raw_url):
|
|
@@ -99,32 +96,13 @@ def env_float(name, default, *, minimum=None):
|
|
|
99
96
|
# are optional and fall back to MIMO_API_KEY / MIMO_API_URL. Token-Plan keys (tp-*) auto-
|
|
100
97
|
# route to the Token-Plan cluster base URL; pay-as-you-go keys use api.xiaomimimo.com.
|
|
101
98
|
_mimo_api_key = os.environ.get("MIMO_API_KEY", "")
|
|
102
|
-
_mimo_video_api_key = os.environ.get("MIMO_VIDEO_API_KEY", "") or _mimo_api_key
|
|
103
99
|
_raw_api_url = os.environ.get("MIMO_API_URL") or default_mimo_api_url(is_mimo_token_plan_key(_mimo_api_key))
|
|
104
|
-
_raw_mimo_video_api_url = (
|
|
105
|
-
os.environ.get("MIMO_VIDEO_API_URL")
|
|
106
|
-
or os.environ.get("MIMO_API_URL")
|
|
107
|
-
or default_mimo_api_url(is_mimo_token_plan_key(_mimo_video_api_key))
|
|
108
|
-
)
|
|
109
100
|
|
|
110
101
|
CONFIG = {
|
|
111
102
|
"api_url": normalize_api_url(_raw_api_url),
|
|
112
103
|
"api_key": _mimo_api_key,
|
|
113
|
-
"
|
|
114
|
-
"mimo_video_api_url": normalize_api_url(_raw_mimo_video_api_url),
|
|
115
|
-
"mimo_video_model": os.environ.get("MIMO_VIDEO_MODEL") or os.environ.get("MIMO_MODEL", DEFAULT_MIMO_MODEL),
|
|
104
|
+
"api_env_var": "MIMO_API_KEY",
|
|
116
105
|
"vlm_model": os.environ.get("MIMO_MODEL", DEFAULT_MIMO_MODEL),
|
|
117
|
-
"mimo_media_resolution": os.environ.get("MIMO_MEDIA_RESOLUTION", "default"),
|
|
118
|
-
"mimo_video_overview": env_bool("MIMO_VIDEO_OVERVIEW", False), # opt-in (--mimo-video-overview / =1); when on it becomes the PRIMARY per-scene description, frames stay the anchor/fallback
|
|
119
|
-
"mimo_video_fps": env_float("MIMO_VIDEO_FPS", 3.0, minimum=0.1),
|
|
120
|
-
"mimo_video_chunk_max_seconds": env_float("MIMO_VIDEO_CHUNK_MAX_SECONDS", 20.0, minimum=1.0),
|
|
121
|
-
"mimo_video_chunk_min_seconds": env_float("MIMO_VIDEO_CHUNK_MIN_SECONDS", 1.0, minimum=0.2),
|
|
122
|
-
"mimo_video_base64_max_mb": env_float("MIMO_VIDEO_BASE64_MAX_MB", 45.0, minimum=1.0),
|
|
123
|
-
"mimo_video_prompt": os.environ.get(
|
|
124
|
-
"MIMO_VIDEO_PROMPT",
|
|
125
|
-
"请用中文分析这个视频分片的主要人物、场景变化、关键动作、情绪走向和剧情冲突,"
|
|
126
|
-
"重点提取适合写短视频解说的故事线索。不要泛泛复述画面,要标出对后续写稿有用的信息。",
|
|
127
|
-
),
|
|
128
106
|
"mimo_disable_thinking": env_bool("MIMO_DISABLE_THINKING", True),
|
|
129
107
|
# TTS 语速(字符/秒)。实测 mimo-tts 冰糖音色中位 ~3.9 字/秒,可用 SPEECH_RATE 覆盖
|
|
130
108
|
# 生成解说时使用 speech_rate * safety_margin 作为约束
|
|
@@ -134,7 +112,6 @@ CONFIG = {
|
|
|
134
112
|
"narration_coverage_target": 0.7, # rough first-draft/diagnostic fallback; content-led audio decisions may differ (not a quota)
|
|
135
113
|
"narration_coverage_max": 0.85, # above this coverage → no_original_blocks (narration is wall-to-wall)
|
|
136
114
|
"narration_coverage_min": 0.5, # below this coverage → under_narrated
|
|
137
|
-
"narration_block_seconds": 9.0, # block cadence used to derive target block count
|
|
138
115
|
"original_block_min_seconds": 2.5, # a deliberate original-audio gap must be at least this long
|
|
139
116
|
"narration_block_min_chars": 16, # below this avg block size → fragmented_beats
|
|
140
117
|
"breath_ms": 250, # 段间呼吸空间(ms);block recap 块内连贯、块间留原声呼吸
|
|
@@ -145,73 +122,19 @@ CONFIG = {
|
|
|
145
122
|
"visual_beat_max_facts": 3, # 单段解说最多建议覆盖的 frame_facts 锚点数量
|
|
146
123
|
"asr_chunk_min_chars": env_int("ASR_CHUNK_MIN_CHARS", 500, minimum=1), # brief 中 ASR 写作分块最小字数/词数
|
|
147
124
|
"asr_chunk_max_chars": env_int("ASR_CHUNK_MAX_CHARS", 800, minimum=1), # brief 中 ASR 写作分块最大字数/词数
|
|
148
|
-
"context_info": "", # 额外上下文(节目名、角色名等)
|
|
149
125
|
"edit_mode": os.environ.get("EDIT_MODE", "full"), # full | cut
|
|
150
|
-
"target_duration": os.environ.get("TARGET_DURATION", ""), # cut 模式目标成片时长,如 10m
|
|
151
126
|
}
|
|
152
127
|
|
|
153
|
-
def narration_tempo_budget(tts_rate_offset=0.0, *, config=None):
|
|
154
|
-
"""Return the canonical tempo budget shared by voiceover and assemble."""
|
|
155
|
-
cfg = config or CONFIG
|
|
156
|
-
global_speed = max(0.01, float(cfg.get("narration_speed", 1.0) or 1.0))
|
|
157
|
-
rate_factor = max(0.01, 1.0 + float(tts_rate_offset or 0.0))
|
|
158
|
-
cumulative_max = max(1.0, float(cfg.get("narration_cumulative_tempo_max", 1.35) or 1.35))
|
|
159
|
-
hard_max = max(cumulative_max, float(cfg.get("narration_cumulative_tempo_hard_max", 1.40) or 1.40))
|
|
160
|
-
legacy_segment_cap = max(1.0, float(cfg.get("tts_segment_tempo_max", 1.20) or 1.20))
|
|
161
|
-
segment_tempo_max = max(1.0, min(legacy_segment_cap, cumulative_max / (global_speed * rate_factor)))
|
|
162
|
-
return {
|
|
163
|
-
"global_narration_speed": global_speed,
|
|
164
|
-
"tts_rate_factor": rate_factor,
|
|
165
|
-
"cumulative_tempo_max": cumulative_max,
|
|
166
|
-
"cumulative_tempo_hard_max": hard_max,
|
|
167
|
-
"segment_tempo_max": segment_tempo_max,
|
|
168
|
-
"max_raw_duration_factor": global_speed * segment_tempo_max,
|
|
169
|
-
}
|
|
170
|
-
|
|
171
128
|
def log(msg):
|
|
172
129
|
print(f"[video-recap] {msg}", flush=True)
|
|
173
130
|
|
|
174
|
-
def
|
|
175
|
-
"""
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
def stable_hash(value):
|
|
179
|
-
"""Return an md5 digest for deterministic JSON-serializable values."""
|
|
180
|
-
return hashlib.md5(stable_json_dumps(value).encode("utf-8")).hexdigest()
|
|
181
|
-
|
|
182
|
-
_FILE_FINGERPRINT_MEMO = {}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
def _file_identity(path):
|
|
186
|
-
"""(device, inode, size, mtime_ns) — changes whenever the bytes could have changed."""
|
|
131
|
+
def file_identity(path):
|
|
132
|
+
"""{size, mtime_ns} of an input file: the identity brief consumers compare against
|
|
133
|
+
the provenance a producer stored, instead of re-hashing bytes."""
|
|
187
134
|
st = os.stat(os.fspath(path))
|
|
188
|
-
return
|
|
189
|
-
|
|
135
|
+
return {"size": st.st_size, "mtime_ns": st.st_mtime_ns}
|
|
190
136
|
|
|
191
|
-
def file_fingerprint(path, chunk_size=1024 * 1024):
|
|
192
|
-
"""Return a full-content fingerprint for cache-correct identity checks.
|
|
193
137
|
|
|
194
|
-
The digest covers CONTENT only — never the path or mtime — so a copied video or
|
|
195
|
-
artifact is still recognised as the same asset, while any byte change invalidates
|
|
196
|
-
the cache even if timestamps, size, head, or tail bytes are misleading.
|
|
197
|
-
|
|
198
|
-
Identity metadata is used ONLY to memoize within a single process. One understanding
|
|
199
|
-
run fingerprints the same source video 8-10 times and the whole extracted frame set
|
|
200
|
-
2-3 times; on a 40-minute video at fps=1 that is gigabytes of redundant reads before
|
|
201
|
-
any real work starts. A file rewritten in place gets a new (size, mtime_ns) and is
|
|
202
|
-
re-hashed, so the memo can never serve a stale digest.
|
|
203
|
-
"""
|
|
204
|
-
key = _file_identity(path)
|
|
205
|
-
memoized = _FILE_FINGERPRINT_MEMO.get(key)
|
|
206
|
-
if memoized is not None:
|
|
207
|
-
return memoized
|
|
208
|
-
h = hashlib.sha256()
|
|
209
|
-
with open(os.fspath(path), "rb") as f:
|
|
210
|
-
for chunk in iter(lambda: f.read(chunk_size), b""):
|
|
211
|
-
h.update(chunk)
|
|
212
|
-
digest = h.hexdigest()
|
|
213
|
-
_FILE_FINGERPRINT_MEMO[key] = digest
|
|
214
|
-
return digest
|
|
215
138
|
def _retry_after_seconds(value, fallback):
|
|
216
139
|
"""Parse Retry-After seconds or HTTP-date; return fallback on malformed input."""
|
|
217
140
|
if not value:
|
|
@@ -245,7 +168,7 @@ def _sanitize_api_error(value, limit=500):
|
|
|
245
168
|
def _api_headers(api_provider=None, api_url=None, api_key=None):
|
|
246
169
|
"""Build MiMo auth headers (OpenAI-compatible chat/completions with an api-key header)."""
|
|
247
170
|
del api_provider, api_url # MiMo is the only provider; signature kept for call sites
|
|
248
|
-
key = CONFIG
|
|
171
|
+
key = CONFIG["api_key"] if api_key is None else api_key
|
|
249
172
|
return {
|
|
250
173
|
"Content-Type": "application/json",
|
|
251
174
|
"User-Agent": "video-recap/1.0",
|
|
@@ -258,9 +181,9 @@ def _prepare_api_payload(payload, api_provider=None, api_url=None):
|
|
|
258
181
|
normalized = dict(payload)
|
|
259
182
|
if "max_tokens" in normalized and "max_completion_tokens" not in normalized:
|
|
260
183
|
normalized["max_completion_tokens"] = normalized.pop("max_tokens")
|
|
261
|
-
model =
|
|
184
|
+
model = normalized["model"]
|
|
262
185
|
if (
|
|
263
|
-
CONFIG
|
|
186
|
+
CONFIG["mimo_disable_thinking"]
|
|
264
187
|
and not model.endswith(("-tts", "-asr"))
|
|
265
188
|
and "thinking" not in normalized
|
|
266
189
|
):
|
|
@@ -269,7 +192,7 @@ def _prepare_api_payload(payload, api_provider=None, api_url=None):
|
|
|
269
192
|
normalized["thinking"] = {"type": "disabled"}
|
|
270
193
|
return normalized
|
|
271
194
|
|
|
272
|
-
def api_call(payload, max_retries=8, *, api_provider=None, api_url=None, api_key=None,
|
|
195
|
+
def api_call(payload, max_retries=8, *, api_provider=None, api_url=None, api_key=None, api_env_var=None):
|
|
273
196
|
"""调用 OpenAI-compatible API,带重试。
|
|
274
197
|
|
|
275
198
|
集群的 429 限流是常态而非错误,所以重试更耐心(更多次数 + 退避封顶 60s + 遵从 Retry-After),
|
|
@@ -292,7 +215,7 @@ def api_call(payload, max_retries=8, *, api_provider=None, api_url=None, api_key
|
|
|
292
215
|
wait = _retry_after_seconds(retry_after, max(wait, 10))
|
|
293
216
|
log(f"API 速率限制 (尝试 {attempt+1}/{max_retries}), 等待 {wait}s")
|
|
294
217
|
elif e.code == 401:
|
|
295
|
-
key_name =
|
|
218
|
+
key_name = api_env_var or CONFIG["api_env_var"]
|
|
296
219
|
raise RuntimeError(f"API 认证失败 (401)。请检查 {key_name} 和 API URL 是否匹配。")
|
|
297
220
|
elif e.code == 403:
|
|
298
221
|
hint = "API 访问被拒绝 (403)。"
|
|
@@ -5,6 +5,7 @@ timing and text checks live here, and every later consumer trusts its result.
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
import json
|
|
8
|
+
import math
|
|
8
9
|
from pathlib import Path
|
|
9
10
|
|
|
10
11
|
from lib import CONFIG, log
|
|
@@ -32,7 +33,11 @@ def _lint_issue(level, index, code, message, **extra):
|
|
|
32
33
|
|
|
33
34
|
|
|
34
35
|
def _is_number(value):
|
|
35
|
-
return
|
|
36
|
+
return (
|
|
37
|
+
isinstance(value, (int, float))
|
|
38
|
+
and not isinstance(value, bool)
|
|
39
|
+
and math.isfinite(value)
|
|
40
|
+
)
|
|
36
41
|
|
|
37
42
|
|
|
38
43
|
def _visual_overlay_issues(index, seg):
|
|
@@ -115,17 +120,13 @@ def _clip_span(clip):
|
|
|
115
120
|
return clip["start"], clip["end"]
|
|
116
121
|
|
|
117
122
|
|
|
118
|
-
def _clip_matches_for_segment(seg, clip_plan):
|
|
123
|
+
def _clip_matches_for_segment(seg, clip_plan, requested_clip_id):
|
|
119
124
|
if not clip_plan:
|
|
120
125
|
return []
|
|
121
126
|
clips = clip_plan["clips"]
|
|
122
127
|
midpoint = (seg["start"] + seg["end"]) / 2
|
|
123
|
-
if
|
|
124
|
-
|
|
125
|
-
requested = int(seg["source_clip_id"])
|
|
126
|
-
except (TypeError, ValueError):
|
|
127
|
-
return []
|
|
128
|
-
clips = [clip for clip in clips if clip.get("clip_id") == requested]
|
|
128
|
+
if requested_clip_id is not None:
|
|
129
|
+
clips = [clip for clip in clips if clip.get("clip_id") == requested_clip_id]
|
|
129
130
|
return [
|
|
130
131
|
clip
|
|
131
132
|
for clip in clips
|
|
@@ -196,9 +197,13 @@ def _has_connected_predecessor(narration, idx, start):
|
|
|
196
197
|
|
|
197
198
|
|
|
198
199
|
def lint_narration(
|
|
199
|
-
narration, scenes_analysis=None, *, clip_plan=None, mode="full", work_dir=None
|
|
200
|
+
narration, scenes_analysis=None, *, clip_plan=None, mode="full", work_dir=None,
|
|
201
|
+
require_chronological=False,
|
|
200
202
|
):
|
|
201
|
-
"""Preflight-check agent narration before TTS; write narration_lint.json when work_dir is set.
|
|
203
|
+
"""Preflight-check agent narration before TTS; write narration_lint.json when work_dir is set.
|
|
204
|
+
|
|
205
|
+
Segments are sorted by start for the timing checks; ``require_chronological`` (the
|
|
206
|
+
--preserve-approved-text contract) additionally rejects input that is not already in order."""
|
|
202
207
|
scenes_analysis = scenes_analysis or []
|
|
203
208
|
errors = []
|
|
204
209
|
warnings = []
|
|
@@ -215,6 +220,7 @@ def lint_narration(
|
|
|
215
220
|
)
|
|
216
221
|
)
|
|
217
222
|
else:
|
|
223
|
+
previous_start = None
|
|
218
224
|
for idx, seg in enumerate(narration):
|
|
219
225
|
if not isinstance(seg, dict):
|
|
220
226
|
errors.append(
|
|
@@ -230,33 +236,47 @@ def lint_narration(
|
|
|
230
236
|
if not _is_number(start) or not _is_number(end):
|
|
231
237
|
errors.append(
|
|
232
238
|
_lint_issue(
|
|
233
|
-
"error", idx, "invalid_time", "start/end must be
|
|
239
|
+
"error", idx, "invalid_time", "start/end must be finite numbers"
|
|
234
240
|
)
|
|
235
241
|
)
|
|
236
242
|
continue
|
|
237
|
-
|
|
238
|
-
try:
|
|
239
|
-
pause = int(seg.get("pause_after_ms", CONFIG["breath_ms"]))
|
|
240
|
-
except (TypeError, ValueError):
|
|
243
|
+
if require_chronological and previous_start is not None and start < previous_start:
|
|
241
244
|
errors.append(
|
|
242
245
|
_lint_issue(
|
|
243
246
|
"error",
|
|
244
247
|
idx,
|
|
245
|
-
"
|
|
246
|
-
"
|
|
248
|
+
"out_of_order",
|
|
249
|
+
"Segments must be listed in chronological order",
|
|
250
|
+
start=start,
|
|
251
|
+
previous_start=previous_start,
|
|
252
|
+
)
|
|
253
|
+
)
|
|
254
|
+
previous_start = start
|
|
255
|
+
text = seg.get("narration")
|
|
256
|
+
if not isinstance(text, str):
|
|
257
|
+
errors.append(
|
|
258
|
+
_lint_issue(
|
|
259
|
+
"error",
|
|
260
|
+
idx,
|
|
261
|
+
"invalid_narration",
|
|
262
|
+
"narration must be a string",
|
|
263
|
+
start=start,
|
|
264
|
+
end=end,
|
|
247
265
|
)
|
|
248
266
|
)
|
|
249
267
|
continue
|
|
250
|
-
|
|
251
|
-
|
|
268
|
+
text = text.strip()
|
|
269
|
+
pause = seg.get("pause_after_ms", CONFIG["breath_ms"])
|
|
270
|
+
if not isinstance(pause, int) or isinstance(pause, bool) or pause < 0:
|
|
271
|
+
errors.append(
|
|
252
272
|
_lint_issue(
|
|
253
|
-
"
|
|
273
|
+
"error",
|
|
254
274
|
idx,
|
|
255
|
-
"
|
|
256
|
-
"pause_after_ms
|
|
257
|
-
pause_after_ms=pause,
|
|
275
|
+
"invalid_pause",
|
|
276
|
+
"pause_after_ms must be a non-negative integer number of milliseconds",
|
|
258
277
|
)
|
|
259
278
|
)
|
|
279
|
+
continue
|
|
260
280
|
errors.extend(_visual_overlay_issues(idx, seg))
|
|
261
281
|
if end <= start:
|
|
262
282
|
errors.append(
|
|
@@ -388,7 +408,21 @@ def lint_narration(
|
|
|
388
408
|
)
|
|
389
409
|
|
|
390
410
|
if mode == "cut":
|
|
391
|
-
|
|
411
|
+
requested_clip_id = seg.get("source_clip_id")
|
|
412
|
+
if requested_clip_id is not None:
|
|
413
|
+
try:
|
|
414
|
+
requested_clip_id = int(requested_clip_id)
|
|
415
|
+
except (TypeError, ValueError):
|
|
416
|
+
errors.append(
|
|
417
|
+
_lint_issue(
|
|
418
|
+
"error",
|
|
419
|
+
idx,
|
|
420
|
+
"invalid_source_clip_id",
|
|
421
|
+
"source_clip_id must be an integer",
|
|
422
|
+
)
|
|
423
|
+
)
|
|
424
|
+
continue
|
|
425
|
+
matches = _clip_matches_for_segment(seg, clip_plan, requested_clip_id)
|
|
392
426
|
if not matches:
|
|
393
427
|
errors.append(
|
|
394
428
|
_lint_issue(
|
|
@@ -400,7 +434,7 @@ def lint_narration(
|
|
|
400
434
|
end=end,
|
|
401
435
|
)
|
|
402
436
|
)
|
|
403
|
-
elif len(matches) > 1 and
|
|
437
|
+
elif len(matches) > 1 and requested_clip_id is None:
|
|
404
438
|
errors.append(
|
|
405
439
|
_lint_issue(
|
|
406
440
|
"error",
|
|
@@ -426,18 +460,6 @@ def lint_narration(
|
|
|
426
460
|
clip_end=round(clip_end, 3),
|
|
427
461
|
)
|
|
428
462
|
)
|
|
429
|
-
if seg.get("source_clip_id") is not None:
|
|
430
|
-
try:
|
|
431
|
-
int(seg["source_clip_id"])
|
|
432
|
-
except (TypeError, ValueError):
|
|
433
|
-
errors.append(
|
|
434
|
-
_lint_issue(
|
|
435
|
-
"error",
|
|
436
|
-
idx,
|
|
437
|
-
"invalid_source_clip_id",
|
|
438
|
-
"source_clip_id must be an integer",
|
|
439
|
-
)
|
|
440
|
-
)
|
|
441
463
|
|
|
442
464
|
normalized.append(
|
|
443
465
|
{"index": idx, "start": start, "end": end, "char_count": char_count}
|
|
@@ -620,10 +642,12 @@ def lint_narration(
|
|
|
620
642
|
|
|
621
643
|
|
|
622
644
|
def validate_narration_or_raise(
|
|
623
|
-
narration, scenes_analysis=None, *, clip_plan=None, mode="full", work_dir=None
|
|
645
|
+
narration, scenes_analysis=None, *, clip_plan=None, mode="full", work_dir=None,
|
|
646
|
+
require_chronological=False,
|
|
624
647
|
):
|
|
625
648
|
report = lint_narration(
|
|
626
|
-
narration, scenes_analysis, clip_plan=clip_plan, mode=mode, work_dir=work_dir
|
|
649
|
+
narration, scenes_analysis, clip_plan=clip_plan, mode=mode, work_dir=work_dir,
|
|
650
|
+
require_chronological=require_chronological,
|
|
627
651
|
)
|
|
628
652
|
if report["errors"]:
|
|
629
653
|
sample = "; ".join(
|
|
@@ -7,7 +7,6 @@ from evidence_bundle import (
|
|
|
7
7
|
coverage_policy_v1,
|
|
8
8
|
filter_evidence_by_ranges,
|
|
9
9
|
render_evidence_bundle,
|
|
10
|
-
validate_public_evidence_contract,
|
|
11
10
|
)
|
|
12
11
|
from review_grounding import remap_grounding_to_output_timeline
|
|
13
12
|
from review_response import (
|
|
@@ -34,7 +33,6 @@ __all__ = [
|
|
|
34
33
|
"remap_grounding_to_output_timeline",
|
|
35
34
|
"render_evidence_bundle",
|
|
36
35
|
"review_narration",
|
|
37
|
-
"validate_public_evidence_contract",
|
|
38
36
|
"write_grounding_qc",
|
|
39
37
|
]
|
|
40
38
|
|
|
@@ -1,22 +1,33 @@
|
|
|
1
1
|
"""Load and remap source evidence for narration review."""
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
|
+
from pathlib import Path
|
|
4
5
|
|
|
5
|
-
import math
|
|
6
6
|
|
|
7
|
+
def _load(work_dir, name):
|
|
8
|
+
"""Pipeline-written artifact: None when never written; corrupt JSON raises."""
|
|
9
|
+
path = Path(work_dir) / name
|
|
10
|
+
if not path.exists():
|
|
11
|
+
return None
|
|
12
|
+
return json.loads(path.read_text(encoding="utf-8"))
|
|
7
13
|
|
|
8
|
-
from pathlib import Path
|
|
9
14
|
|
|
10
|
-
|
|
15
|
+
def _load_required(work_dir, name, hint):
|
|
16
|
+
"""Pipeline-written artifact the stage cannot run without; corrupt JSON raises."""
|
|
17
|
+
path = Path(work_dir) / name
|
|
18
|
+
if not path.exists():
|
|
19
|
+
raise SystemExit(f"缺少 {path};{hint}")
|
|
20
|
+
return json.loads(path.read_text(encoding="utf-8"))
|
|
11
21
|
|
|
12
22
|
|
|
13
|
-
def
|
|
23
|
+
def _load_agent_optional(work_dir, name):
|
|
24
|
+
"""Optional agent-authored artifact: missing or unparseable both fail open to None."""
|
|
14
25
|
path = Path(work_dir) / name
|
|
15
26
|
if not path.exists():
|
|
16
27
|
return None
|
|
17
28
|
try:
|
|
18
29
|
return json.loads(path.read_text(encoding="utf-8"))
|
|
19
|
-
except
|
|
30
|
+
except ValueError:
|
|
20
31
|
return None
|
|
21
32
|
|
|
22
33
|
|
|
@@ -79,19 +90,6 @@ def _load_review_grounding(work_dir):
|
|
|
79
90
|
)
|
|
80
91
|
|
|
81
92
|
|
|
82
|
-
def _source_fingerprint(work_dir, name):
|
|
83
|
-
path = Path(work_dir) / name
|
|
84
|
-
if not path.exists():
|
|
85
|
-
return ""
|
|
86
|
-
try:
|
|
87
|
-
return stable_hash(json.loads(path.read_text(encoding="utf-8")))
|
|
88
|
-
except (ValueError, OSError):
|
|
89
|
-
try:
|
|
90
|
-
return stable_hash(path.read_text(encoding="utf-8"))
|
|
91
|
-
except OSError:
|
|
92
|
-
return ""
|
|
93
|
-
|
|
94
|
-
|
|
95
93
|
def _load_cut_clip_spans(work_dir):
|
|
96
94
|
"""Load explicit source→output spans from the validated cut plan only.
|
|
97
95
|
|
|
@@ -102,50 +100,28 @@ def _load_cut_clip_spans(work_dir):
|
|
|
102
100
|
"""
|
|
103
101
|
work_dir = Path(work_dir)
|
|
104
102
|
plan = _load(work_dir, "clip_plan_validated.json")
|
|
105
|
-
if
|
|
103
|
+
if plan is None:
|
|
106
104
|
return None
|
|
107
|
-
|
|
108
|
-
if
|
|
109
|
-
|
|
105
|
+
raw_path = work_dir / "clip_plan.json"
|
|
106
|
+
if (
|
|
107
|
+
raw_path.exists()
|
|
108
|
+
and (work_dir / "clip_plan_validated.json").stat().st_mtime_ns
|
|
109
|
+
< raw_path.stat().st_mtime_ns
|
|
110
110
|
):
|
|
111
111
|
return None
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
source_start = float(clip["source_start"])
|
|
126
|
-
source_end = float(clip["source_end"])
|
|
127
|
-
output_start = float(clip["output_start"])
|
|
128
|
-
output_end = float(clip["output_end"])
|
|
129
|
-
except (TypeError, ValueError):
|
|
130
|
-
return None
|
|
131
|
-
values = (source_start, source_end, output_start, output_end)
|
|
132
|
-
if not all(math.isfinite(value) for value in values):
|
|
133
|
-
return None
|
|
134
|
-
if source_end <= source_start or output_end <= output_start:
|
|
135
|
-
return None
|
|
136
|
-
spans.append(
|
|
137
|
-
{
|
|
138
|
-
"source_start": source_start,
|
|
139
|
-
"source_end": source_end,
|
|
140
|
-
"output_start": output_start,
|
|
141
|
-
"output_end": output_end,
|
|
142
|
-
"source_id": str(clip.get("source_id", clip.get("source", "0"))),
|
|
143
|
-
"source_clip_id": clip.get(
|
|
144
|
-
"source_clip_id", clip.get("id", clip.get("clip_id"))
|
|
145
|
-
),
|
|
146
|
-
"output_segment_index": len(spans),
|
|
147
|
-
}
|
|
148
|
-
)
|
|
112
|
+
spans = [
|
|
113
|
+
{
|
|
114
|
+
"source_start": clip["source_start"],
|
|
115
|
+
"source_end": clip["source_end"],
|
|
116
|
+
"output_start": clip["output_start"],
|
|
117
|
+
"output_end": clip["output_end"],
|
|
118
|
+
# video-cut labels source_id only for multi-source plans.
|
|
119
|
+
"source_id": str(clip.get("source_id", "0")),
|
|
120
|
+
"source_clip_id": clip["clip_id"],
|
|
121
|
+
"output_segment_index": index,
|
|
122
|
+
}
|
|
123
|
+
for index, clip in enumerate(plan["clips"])
|
|
124
|
+
]
|
|
149
125
|
return spans or None
|
|
150
126
|
|
|
151
127
|
|