@oh-story/dsh 0.1.8 → 0.1.9
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 +35 -14
- package/lib/client.js.map +2 -2
- package/lib/drama/manifest.json +165 -69
- package/lib/drama/skills/short-drama/SKILL.md +5 -1
- package/lib/drama/skills/short-drama/scripts/creator_markdown_check.py +126 -9
- package/lib/drama/skills/short-drama/scripts/project_tool.py +11 -0
- package/lib/drama/skills/short-drama-assets/references/character-and-look.md +18 -0
- package/lib/drama/skills/short-drama-assets/references/continuity-lock.md +10 -1
- package/lib/drama/skills/short-drama-edit/SKILL.md +148 -0
- package/lib/drama/skills/short-drama-edit/agents/openai.yaml +4 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/README.md +60 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/package.json +15 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/remotion.config.ts +6 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/Root.tsx +26 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/Subtitles.tsx +88 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/font.ts +46 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/index.ts +4 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/schema.ts +32 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/tsconfig.json +13 -0
- package/lib/drama/skills/short-drama-edit/assets//345/211/252/350/276/221/345/215/225.md +43 -0
- package/lib/drama/skills/short-drama-edit/references/cut-craft.md +81 -0
- package/lib/drama/skills/short-drama-edit/references/delivery-verify.md +58 -0
- package/lib/drama/skills/short-drama-edit/references/generated-footage.md +96 -0
- package/lib/drama/skills/short-drama-edit/references/sound-and-subtitles.md +113 -0
- package/lib/drama/skills/short-drama-edit/references/stage-contract.md +55 -0
- package/lib/drama/skills/short-drama-edit/scripts/edit_tool.py +1041 -0
- package/lib/drama/skills/short-drama-edit/scripts/selftest.py +456 -0
- package/lib/drama/skills/short-drama-image-prompts/assets/image-prompts.md +5 -0
- package/lib/drama/skills/short-drama-image-prompts/assets/lookdev-prompts.md +3 -0
- package/lib/drama/skills/short-drama-novel-analyze/references/aggregation-and-entities.md +2 -2
- package/lib/drama/skills/short-drama-novel-analyze/references/chapter-extraction.md +4 -1
- package/lib/drama/skills/short-drama-produce/SKILL.md +24 -3
- package/lib/drama/skills/short-drama-produce/references/adapter-contract.md +28 -0
- package/lib/drama/skills/short-drama-produce/references/providers/minimax-h3-video.md +24 -5
- package/lib/drama/skills/short-drama-produce/references/providers/seedance.md +7 -0
- package/lib/drama/skills/short-drama-produce/scripts/fixture_adapter.py +18 -1
- package/lib/drama/skills/short-drama-produce/scripts/production_tool.py +181 -2
- package/lib/drama/skills/short-drama-produce/scripts/provider_adapters.py +57 -2
- package/lib/drama/skills/short-drama-review/SKILL.md +3 -0
- package/lib/drama/skills/short-drama-review/references/production-quality-gates.md +20 -0
- package/lib/drama/skills/short-drama-review/references/rubric-visual-motion.md +36 -0
- package/lib/drama/skills/short-drama-storyboard/SKILL.md +7 -0
- package/lib/drama/skills/short-drama-storyboard/assets/keyframe-prompts.md +4 -0
- package/lib/drama/skills/short-drama-storyboard/references/shot-craft.md +39 -0
- package/lib/drama/skills/short-drama-storyboard/references/stage-contract.md +2 -0
- package/lib/drama/skills/short-drama-video-prompts/SKILL.md +7 -1
- package/lib/drama/skills/short-drama-video-prompts/references/minimax-h3.md +101 -6
- package/lib/drama/skills/short-drama-video-prompts/references/motion-recipe.md +7 -1
- package/lib/drama/skills/short-drama-video-prompts/references/performance-action-timing.md +35 -0
- package/lib/drama/skills/short-drama-video-prompts/references/production-prompt-grammar.md +3 -1
- package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.0.md +13 -3
- package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.5.md +35 -4
- package/lib/drama/skills/short-drama-video-prompts/references/stage-contract.md +1 -1
- package/lib/drama/skills/short-drama-write/SKILL.md +13 -2
- package/lib/drama/skills/short-drama-write/references/screenplay-format.md +6 -0
- package/lib/drama/skills/short-drama-write/references/stage-contract.md +1 -0
- package/lib/index.js +4 -2
- package/lib/index.js.map +2 -2
- package/lib/novel-to-game/VERSION +1 -1
- package/lib/novel-to-game/manifest.json +28 -28
- package/lib/novel-to-game/skills/game-art-direction/SKILL.md +6 -27
- package/lib/novel-to-game/skills/game-build/SKILL.md +12 -29
- package/lib/novel-to-game/skills/game-concept/SKILL.md +10 -26
- package/lib/novel-to-game/skills/game-qa/SKILL.md +5 -15
- package/lib/novel-to-game/skills/game-qa/references/test-design-method.md +1 -1
- package/lib/novel-to-game/skills/game-world-design/SKILL.md +19 -28
- package/lib/novel-to-game/skills/game-world-design/references/dialogue-design-method.md +1 -2
- package/lib/novel-to-game/skills/novel-game-analyze/SKILL.md +6 -9
- package/lib/novel-to-game/skills/novel-to-game/SKILL.md +6 -11
- package/lib/novel-to-game/skills/novel-to-game/references/intake-benchmark-reference.md +0 -4
- package/lib/novel-to-game/skills/novel-to-game/references/intake-method.md +1 -3
- package/lib/novel-to-game/skills/novel-to-game/references/pipeline-contract.md +3 -5
- package/lib/oh-story/manifest.json +292 -172
- package/lib/oh-story/roles/character-designer.md +1 -1
- package/lib/oh-story/roles/narrative-writer.md +56 -242
- package/lib/oh-story/roles/story-architect.md +1 -1
- package/lib/oh-story/skills/story/SKILL.md +1 -1
- package/lib/oh-story/skills/story/VERSION +1 -1
- package/lib/oh-story/skills/story/references/author-memory.md +10 -8
- package/lib/oh-story/skills/story-deslop/SKILL.md +20 -182
- package/lib/oh-story/skills/story-deslop/references/anti-ai-writing.md +19 -47
- package/lib/oh-story/skills/story-deslop/references/author-memory.md +10 -8
- package/lib/oh-story/skills/story-deslop/references/banned-words.md +3 -1
- package/lib/oh-story/skills/story-deslop/references/deslop-gates.md +154 -0
- package/lib/oh-story/skills/story-deslop/references/style-resolution.md +27 -0
- package/lib/oh-story/skills/story-deslop/scripts/check-ai-patterns.js +6 -1
- package/lib/oh-story/skills/story-deslop/scripts/check-degeneration.js +39 -20
- package/lib/oh-story/skills/story-deslop/scripts/normalize-punctuation.js +10 -5
- package/lib/oh-story/skills/story-deslop/scripts/style-whitelist.js +39 -0
- package/lib/oh-story/skills/story-import/SKILL.md +2 -2
- package/lib/oh-story/skills/story-import/references/format-and-structure.md +2 -2
- package/lib/oh-story/skills/story-import/references/tracking-initialization.md +31 -0
- package/lib/oh-story/skills/story-import/references/tracking-transaction.md +2 -29
- package/lib/oh-story/skills/story-import/scripts/check-outline-contract.js +176 -12
- package/lib/oh-story/skills/story-long-analyze/SKILL.md +1 -1
- package/lib/oh-story/skills/story-long-write/SKILL.md +20 -193
- package/lib/oh-story/skills/story-long-write/references/anti-ai-writing.md +19 -47
- package/lib/oh-story/skills/story-long-write/references/artifact-protocols.md +62 -7
- package/lib/oh-story/skills/story-long-write/references/author-memory.md +10 -8
- package/lib/oh-story/skills/story-long-write/references/banned-words.md +3 -1
- package/lib/oh-story/skills/story-long-write/references/benchmark-recall.md +20 -0
- package/lib/oh-story/skills/story-long-write/references/dialogue-mastery.md +6 -4
- package/lib/oh-story/skills/story-long-write/references/long-chapter-quality.md +2 -2
- package/lib/oh-story/skills/story-long-write/references/long-format.md +2 -0
- package/lib/oh-story/skills/story-long-write/references/outline-structure-theory.md +2 -0
- package/lib/oh-story/skills/story-long-write/references/project-files.md +108 -0
- package/lib/oh-story/skills/story-long-write/references/reference-index.md +87 -0
- package/lib/oh-story/skills/story-long-write/references/style-combat-face.md +1 -1
- package/lib/oh-story/skills/story-long-write/references/style-craft.md +9 -29
- package/lib/oh-story/skills/story-long-write/references/style-genre-modules.md +1 -1
- package/lib/oh-story/skills/story-long-write/references/style-resolution.md +27 -0
- package/lib/oh-story/skills/story-long-write/references/tracking-initialization.md +31 -0
- package/lib/oh-story/skills/story-long-write/references/tracking-transaction.md +2 -29
- package/lib/oh-story/skills/story-long-write/references/workflow-chapter.md +51 -52
- package/lib/oh-story/skills/story-long-write/references/workflow-daily.md +8 -7
- package/lib/oh-story/skills/story-long-write/references/workflow-setup.md +36 -15
- package/lib/oh-story/skills/story-long-write/references/writer-prompt-fallback.md +22 -0
- package/lib/oh-story/skills/story-long-write/references/writing-craft.md +69 -2
- package/lib/oh-story/skills/story-long-write/scripts/build_writer_prompt.py +410 -0
- package/lib/oh-story/skills/story-long-write/scripts/check-ai-patterns.js +6 -1
- package/lib/oh-story/skills/story-long-write/scripts/check-degeneration.js +39 -20
- package/lib/oh-story/skills/story-long-write/scripts/check-outline-contract.js +176 -12
- package/lib/oh-story/skills/story-long-write/scripts/check-outline-copy.js +90 -23
- package/lib/oh-story/skills/story-long-write/scripts/normalize-punctuation.js +10 -5
- package/lib/oh-story/skills/story-long-write/scripts/outline_view.py +283 -0
- package/lib/oh-story/skills/story-long-write/scripts/style-whitelist.js +39 -0
- package/lib/oh-story/skills/story-review/SKILL.md +13 -3
- package/lib/oh-story/skills/story-review/references/anti-ai-writing.md +19 -47
- package/lib/oh-story/skills/story-review/references/author-memory.md +10 -8
- package/lib/oh-story/skills/story-review/references/banned-words.md +3 -1
- package/lib/oh-story/skills/story-review/references/dialogue-mastery.md +6 -4
- package/lib/oh-story/skills/story-review/references/review-quality.md +2 -2
- package/lib/oh-story/skills/story-review/references/style-resolution.md +27 -0
- package/lib/oh-story/skills/story-review/references/tracking-initialization.md +31 -0
- package/lib/oh-story/skills/story-review/references/tracking-transaction.md +2 -29
- package/lib/oh-story/skills/story-review/scripts/check-ai-patterns.js +6 -1
- package/lib/oh-story/skills/story-review/scripts/check-degeneration.js +39 -20
- package/lib/oh-story/skills/story-review/scripts/normalize-punctuation.js +10 -5
- package/lib/oh-story/skills/story-review/scripts/style-whitelist.js +39 -0
- package/lib/oh-story/skills/story-setup/SKILL.md +17 -10
- package/lib/oh-story/skills/story-setup/references/agent-references/anti-ai-writing.md +19 -47
- package/lib/oh-story/skills/story-setup/references/agent-references/banned-words.md +3 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/deslop-gates.md +154 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/dialogue-mastery.md +6 -4
- package/lib/oh-story/skills/story-setup/references/agent-references/format-and-structure.md +2 -2
- package/lib/oh-story/skills/story-setup/references/agent-references/short-genre-formulas.md +1 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/style-combat-face.md +1 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/style-genre-modules.md +1 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/style-resolution.md +27 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/writing-craft.md +69 -2
- package/lib/oh-story/skills/story-setup/references/diagnostics.md +38 -0
- package/lib/oh-story/skills/story-short-analyze/references/analysis-writing-techniques.md +1 -1
- package/lib/oh-story/skills/story-short-analyze/references/source-story-quality.md +2 -2
- package/lib/oh-story/skills/story-short-write/SKILL.md +18 -186
- package/lib/oh-story/skills/story-short-write/references/author-memory.md +10 -8
- package/lib/oh-story/skills/story-short-write/references/banned-words.md +3 -1
- package/lib/oh-story/skills/story-short-write/references/benchmark-recall.md +60 -0
- package/lib/oh-story/skills/story-short-write/references/dialogue-mastery.md +6 -4
- package/lib/oh-story/skills/story-short-write/references/genre-styles//344/270/226/346/203/205/346/211/223/350/204/270.md +2 -2
- package/lib/oh-story/skills/story-short-write/references/genre-styles//345/217/214/347/224/267/344/270/273.md +1 -1
- package/lib/oh-story/skills/story-short-write/references/genre-styles//345/244/215/344/273/207/346/211/223/350/204/270.md +3 -3
- package/lib/oh-story/skills/story-short-write/references/genre-styles//345/256/205/346/226/227/345/256/253/346/226/227.md +3 -3
- package/lib/oh-story/skills/story-short-write/references/genre-styles//346/200/273/350/243/201/350/261/252/351/227/250.md +3 -3
- package/lib/oh-story/skills/story-short-write/references/genre-styles//346/202/254/347/226/221.md +5 -5
- package/lib/oh-story/skills/story-short-write/references/genre-styles//346/260/221/344/277/227/346/200/252/350/260/210.md +4 -4
- package/lib/oh-story/skills/story-short-write/references/genre-styles//347/224/234/345/256/240.md +5 -5
- package/lib/oh-story/skills/story-short-write/references/genre-styles//350/277/275/345/246/273/347/201/253/350/221/254/345/234/272.md +3 -3
- package/lib/oh-story/skills/story-short-write/references/genre-writing-formulas.md +1 -1
- package/lib/oh-story/skills/story-short-write/references/genre-writing-techniques.md +1 -1
- package/lib/oh-story/skills/story-short-write/references/reference-index.md +53 -0
- package/lib/oh-story/skills/story-short-write/references/short-craft.md +12 -14
- package/lib/oh-story/skills/story-short-write/references/short-deslop.md +10 -10
- package/lib/oh-story/skills/story-short-write/references/short-format.md +3 -1
- package/lib/oh-story/skills/story-short-write/references/short-prose-quality.md +2 -2
- package/lib/oh-story/skills/story-short-write/references/style-resolution.md +27 -0
- package/lib/oh-story/skills/story-short-write/references/workflow-design.md +77 -0
- package/lib/oh-story/skills/story-short-write/references/writing-workflow.md +5 -5
- package/lib/oh-story/skills/story-short-write/scripts/check-ai-patterns.js +6 -1
- package/lib/oh-story/skills/story-short-write/scripts/check-degeneration.js +39 -20
- package/lib/oh-story/skills/story-short-write/scripts/check-delivery-contract.js +34 -5
- package/lib/oh-story/skills/story-short-write/scripts/check-outline-copy.js +90 -23
- package/lib/oh-story/skills/story-short-write/scripts/normalize-punctuation.js +10 -5
- package/lib/oh-story/skills/story-short-write/scripts/style-whitelist.js +39 -0
- package/lib/types/client/drama-production-view.d.ts.map +1 -1
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/production-prompts.d.ts.map +1 -1
- package/lib/types/client/production-runtime.d.ts +17 -0
- package/lib/types/client/production-runtime.d.ts.map +1 -1
- package/lib/types/skill-provider.d.ts.map +1 -1
- package/lib/video-recap/CHANGELOG.md +31 -1
- package/lib/video-recap/manifest.json +162 -162
- package/lib/video-recap/skills/video-assemble/scripts/artifacts.py +4 -9
- package/lib/video-recap/skills/video-assemble/scripts/assemble.py +47 -60
- package/lib/video-recap/skills/video-assemble/scripts/assembly_contract.py +77 -115
- package/lib/video-recap/skills/video-assemble/scripts/assembly_settings.py +41 -40
- package/lib/video-recap/skills/video-assemble/scripts/audio_automation.py +22 -32
- package/lib/video-recap/skills/video-assemble/scripts/audio_mix.py +102 -192
- package/lib/video-recap/skills/video-assemble/scripts/jianying_builders.py +14 -35
- package/lib/video-recap/skills/video-assemble/scripts/jianying_model.py +14 -12
- package/lib/video-recap/skills/video-assemble/scripts/jianying_optional.py +8 -9
- package/lib/video-recap/skills/video-assemble/scripts/jianying_schema.py +3 -14
- package/lib/video-recap/skills/video-assemble/scripts/jianying_timeline_contract.py +23 -12
- package/lib/video-recap/skills/video-assemble/scripts/jianying_tracks.py +1 -4
- package/lib/video-recap/skills/video-assemble/scripts/jianying_writer.py +24 -25
- package/lib/video-recap/skills/video-assemble/scripts/lib.py +31 -85
- package/lib/video-recap/skills/video-assemble/scripts/media.py +100 -144
- package/lib/video-recap/skills/video-assemble/scripts/narration_audio.py +101 -195
- package/lib/video-recap/skills/video-assemble/scripts/render_preflight.py +10 -21
- package/lib/video-recap/skills/video-assemble/scripts/source_subtitles.py +90 -169
- package/lib/video-recap/skills/video-assemble/scripts/subtitle_core.py +69 -122
- package/lib/video-recap/skills/video-assemble/scripts/subtitle_render.py +10 -17
- package/lib/video-recap/skills/video-assemble/scripts/timeline.py +33 -48
- package/lib/video-recap/skills/video-assemble/scripts/timeline_emit.py +14 -20
- package/lib/video-recap/skills/video-assemble/scripts/visual_render.py +135 -213
- package/lib/video-recap/skills/video-cut/scripts/cut_cli.py +49 -57
- package/lib/video-recap/skills/video-cut/scripts/cut_contract.py +38 -74
- package/lib/video-recap/skills/video-cut/scripts/cut_render.py +46 -95
- package/lib/video-recap/skills/video-cut/scripts/lib.py +23 -25
- package/lib/video-recap/skills/video-cut/scripts/media_geometry.py +70 -173
- package/lib/video-recap/skills/video-cut/scripts/narration_mapping.py +28 -53
- package/lib/video-recap/skills/video-cut/scripts/sentence_boundaries.py +162 -322
- package/lib/video-recap/skills/video-recap/references/timeline-and-jianying.md +6 -7
- package/lib/video-recap/skills/video-recap/scripts/doctor.py +104 -124
- package/lib/video-recap/skills/video-recap/scripts/final_qc.py +255 -412
- package/lib/video-recap/skills/video-recap/scripts/lib.py +30 -52
- package/lib/video-recap/skills/video-recap/scripts/materials.py +97 -120
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc_client.py +7 -12
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc_evidence.py +46 -97
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc_observations.py +18 -36
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc_payload.py +29 -47
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc_report.py +43 -76
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc_runner.py +2 -1
- package/lib/video-recap/skills/video-recap/scripts/qc_contract.py +2 -1
- package/lib/video-recap/skills/video-recap/scripts/recap_cli.py +2 -2
- package/lib/video-recap/skills/video-recap/scripts/recap_inspect.py +129 -145
- package/lib/video-recap/skills/video-recap/scripts/recap_review.py +27 -20
- package/lib/video-recap/skills/video-recap/scripts/recap_runner.py +20 -31
- package/lib/video-recap/skills/video-recap/scripts/recap_runtime.py +31 -106
- package/lib/video-recap/skills/video-recap/scripts/recap_stage_qc.py +43 -113
- package/lib/video-recap/skills/video-recap/scripts/recap_timeline.py +196 -377
- package/lib/video-recap/skills/video-script/scripts/agent_brief.py +1 -1
- package/lib/video-recap/skills/video-script/scripts/agent_text.py +63 -172
- package/lib/video-recap/skills/video-script/scripts/brief_context.py +80 -135
- package/lib/video-recap/skills/video-script/scripts/brief_inputs.py +125 -167
- package/lib/video-recap/skills/video-script/scripts/brief_timeline.py +93 -208
- package/lib/video-recap/skills/video-script/scripts/deslop_qc.py +20 -46
- package/lib/video-recap/skills/video-script/scripts/lib.py +30 -26
- package/lib/video-recap/skills/video-script/scripts/narration_lint.py +145 -140
- package/lib/video-recap/skills/video-script/scripts/review_response.py +12 -42
- package/lib/video-recap/skills/video-script/scripts/review_runner.py +0 -6
- package/lib/video-recap/skills/video-script/scripts/speech_ownership.py +39 -100
- package/lib/video-recap/skills/video-script/scripts/timeline_fusion.py +33 -124
- package/lib/video-recap/skills/video-understanding/scripts/agent_brief.py +1 -1
- package/lib/video-recap/skills/video-understanding/scripts/agent_text.py +63 -172
- package/lib/video-recap/skills/video-understanding/scripts/asr.py +1 -2
- package/lib/video-recap/skills/video-understanding/scripts/brief_context.py +80 -135
- package/lib/video-recap/skills/video-understanding/scripts/brief_inputs.py +125 -167
- package/lib/video-recap/skills/video-understanding/scripts/brief_timeline.py +93 -208
- package/lib/video-recap/skills/video-understanding/scripts/consolidate.py +1 -1
- package/lib/video-recap/skills/video-understanding/scripts/deslop_qc.py +20 -46
- package/lib/video-recap/skills/video-understanding/scripts/lib.py +31 -26
- package/lib/video-recap/skills/video-understanding/scripts/narration_lint.py +145 -140
- package/lib/video-recap/skills/video-understanding/scripts/speech_ownership.py +39 -100
- package/lib/video-recap/skills/video-understanding/scripts/timeline_fusion.py +33 -124
- package/lib/video-recap/skills/video-understanding/scripts/vlm.py +1 -4
- package/lib/video-recap/skills/video-voiceover/scripts/dub.py +7 -13
- package/lib/video-recap/skills/video-voiceover/scripts/fish_audio.py +12 -30
- package/lib/video-recap/skills/video-voiceover/scripts/lib.py +98 -131
- package/lib/video-recap/skills/video-voiceover/scripts/tts_audio.py +17 -28
- package/lib/video-recap/skills/video-voiceover/scripts/voiceover.py +151 -240
- package/package.json +41 -41
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"""Load measured source-speech evidence and classify narration ownership."""
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
|
-
import math
|
|
5
4
|
from pathlib import Path
|
|
6
5
|
|
|
7
6
|
from lib import CONFIG, stable_hash
|
|
@@ -17,36 +16,16 @@ def _empty_evidence(mode):
|
|
|
17
16
|
|
|
18
17
|
|
|
19
18
|
def _read_json(path):
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
except (OSError, json.JSONDecodeError):
|
|
23
|
-
return None
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
def _timed_rows(payload, key):
|
|
27
|
-
rows = payload.get(key, []) if isinstance(payload, dict) else []
|
|
28
|
-
out = []
|
|
29
|
-
for row in rows if isinstance(rows, list) else []:
|
|
30
|
-
if not isinstance(row, dict):
|
|
31
|
-
continue
|
|
32
|
-
try:
|
|
33
|
-
start, end = float(row.get("start")), float(row.get("end"))
|
|
34
|
-
except (TypeError, ValueError):
|
|
35
|
-
continue
|
|
36
|
-
if math.isfinite(start) and math.isfinite(end) and end > start:
|
|
37
|
-
out.append({**row, "start": start, "end": end})
|
|
38
|
-
return out
|
|
19
|
+
"""JSON artifact, or None when the optional file was never written."""
|
|
20
|
+
return json.loads(path.read_text(encoding="utf-8")) if path.exists() else None
|
|
39
21
|
|
|
40
22
|
|
|
41
23
|
def _output_payload_is_current(payload, work_dir):
|
|
42
|
-
if not isinstance(payload, dict):
|
|
43
|
-
return False
|
|
44
|
-
if payload.get("schema_version") != 2 or payload.get("timeline") != "cut_output":
|
|
45
|
-
return False
|
|
46
24
|
plan = _read_json(Path(work_dir) / "clip_plan_validated.json")
|
|
47
|
-
return
|
|
48
|
-
|
|
49
|
-
and
|
|
25
|
+
return (
|
|
26
|
+
payload is not None
|
|
27
|
+
and plan is not None
|
|
28
|
+
and payload["clip_plan_fingerprint"] == stable_hash(plan)
|
|
50
29
|
)
|
|
51
30
|
|
|
52
31
|
|
|
@@ -55,55 +34,31 @@ def load_source_sentence_evidence(work_dir, mode="full"):
|
|
|
55
34
|
if work_dir is None:
|
|
56
35
|
return _empty_evidence(mode)
|
|
57
36
|
work_dir = Path(work_dir)
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
break
|
|
84
|
-
raw_quiet = _read_json(work_dir / "silence_periods.json")
|
|
85
|
-
candidate = {
|
|
86
|
-
"quiet_windows": [
|
|
87
|
-
row
|
|
88
|
-
for row in raw_quiet
|
|
89
|
-
if isinstance(row, dict) and not bool(row.get("has_speech", False))
|
|
90
|
-
]
|
|
91
|
-
} if isinstance(raw_quiet, list) else {}
|
|
92
|
-
quiet_windows = _timed_rows(candidate, "quiet_windows")
|
|
93
|
-
|
|
94
|
-
anchors = []
|
|
95
|
-
for anchor in payload.get("sentence_anchors", []):
|
|
96
|
-
if not isinstance(anchor, dict) or anchor.get("confidence") not in {
|
|
97
|
-
"high",
|
|
98
|
-
"medium",
|
|
99
|
-
}:
|
|
100
|
-
continue
|
|
101
|
-
try:
|
|
102
|
-
when = float(anchor.get("time"))
|
|
103
|
-
except (TypeError, ValueError):
|
|
104
|
-
continue
|
|
105
|
-
if math.isfinite(when) and when >= 0:
|
|
106
|
-
anchors.append({**anchor, "time": round(when, 3)})
|
|
37
|
+
if mode == "full":
|
|
38
|
+
payload = _read_json(work_dir / "speech_boundary_anchors.json") or {
|
|
39
|
+
"sentence_anchors": []
|
|
40
|
+
}
|
|
41
|
+
speech_spans = [
|
|
42
|
+
row for row in _read_json(work_dir / "asr_result.json") or [] if row["text"]
|
|
43
|
+
]
|
|
44
|
+
quiet_windows = [
|
|
45
|
+
row
|
|
46
|
+
for row in _read_json(work_dir / "silence_periods.json") or []
|
|
47
|
+
if not row["has_speech"]
|
|
48
|
+
]
|
|
49
|
+
else:
|
|
50
|
+
payload = _read_json(work_dir / "speech_boundary_anchors_output.json")
|
|
51
|
+
if mode == "cut_output" and not _output_payload_is_current(payload, work_dir):
|
|
52
|
+
return _empty_evidence(mode)
|
|
53
|
+
if payload is None:
|
|
54
|
+
payload = {"sentence_anchors": [], "speech_spans": [], "quiet_windows": []}
|
|
55
|
+
speech_spans = payload["speech_spans"]
|
|
56
|
+
quiet_windows = payload["quiet_windows"]
|
|
57
|
+
anchors = [
|
|
58
|
+
anchor
|
|
59
|
+
for anchor in payload["sentence_anchors"]
|
|
60
|
+
if anchor["confidence"] in {"high", "medium"}
|
|
61
|
+
]
|
|
107
62
|
return {
|
|
108
63
|
"anchors": sorted(anchors, key=lambda item: item["time"]),
|
|
109
64
|
"speech_spans": speech_spans,
|
|
@@ -147,16 +102,9 @@ def _speech_overlap_excluding_quiet(start, end, speech, quiet):
|
|
|
147
102
|
|
|
148
103
|
def segment_overlaps_source_speech(seg, evidence):
|
|
149
104
|
"""Classify aggregate mix ownership across the complete narration interval."""
|
|
150
|
-
|
|
151
|
-
start, end = float(seg.get("start")), float(seg.get("end"))
|
|
152
|
-
except (AttributeError, TypeError, ValueError):
|
|
153
|
-
return bool(getattr(seg, "get", lambda *_: True)("overlaps_speech", True))
|
|
154
|
-
duration = max(0.0, end - start)
|
|
105
|
+
start, end = seg["start"], seg["end"]
|
|
155
106
|
quiet = evidence["quiet_windows"]
|
|
156
|
-
quiet_min = max(
|
|
157
|
-
0.3,
|
|
158
|
-
duration * float(CONFIG.get("quiet_overlap_min_ratio", 0.8) or 0.8),
|
|
159
|
-
)
|
|
107
|
+
quiet_min = max(0.3, (end - start) * CONFIG["quiet_overlap_min_ratio"])
|
|
160
108
|
speech = evidence["speech_spans"]
|
|
161
109
|
if speech:
|
|
162
110
|
return _speech_overlap_excluding_quiet(start, end, speech, quiet) > 0.05
|
|
@@ -167,12 +115,8 @@ def segment_overlaps_source_speech(seg, evidence):
|
|
|
167
115
|
return bool(seg.get("overlaps_speech", True))
|
|
168
116
|
|
|
169
117
|
|
|
170
|
-
def entry_overlaps_source_speech(
|
|
118
|
+
def entry_overlaps_source_speech(start, evidence, *, authored_overlap=True, tolerance=0.05):
|
|
171
119
|
"""Classify the entry instant; later quiet time cannot erase an unsafe start."""
|
|
172
|
-
try:
|
|
173
|
-
start = float(seg.get("start"))
|
|
174
|
-
except (AttributeError, TypeError, ValueError):
|
|
175
|
-
return True
|
|
176
120
|
if any(
|
|
177
121
|
row["start"] - tolerance <= start <= row["end"] + tolerance
|
|
178
122
|
for row in evidence["quiet_windows"]
|
|
@@ -187,18 +131,13 @@ def entry_overlaps_source_speech(seg, evidence, tolerance=0.05):
|
|
|
187
131
|
return False
|
|
188
132
|
if evidence["anchors"] or evidence["require_measured"]:
|
|
189
133
|
return True
|
|
190
|
-
return bool(
|
|
134
|
+
return bool(authored_overlap)
|
|
191
135
|
|
|
192
136
|
|
|
193
137
|
def measure_narration_speech_ownership(narration, work_dir, mode="full"):
|
|
194
138
|
"""Return narration copies with aggregate ownership derived from evidence."""
|
|
195
139
|
evidence = load_source_sentence_evidence(work_dir, mode=mode)
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
continue
|
|
201
|
-
item = dict(seg)
|
|
202
|
-
item["overlaps_speech"] = segment_overlaps_source_speech(item, evidence)
|
|
203
|
-
measured.append(item)
|
|
204
|
-
return measured
|
|
140
|
+
return [
|
|
141
|
+
{**seg, "overlaps_speech": segment_overlaps_source_speech(seg, evidence)}
|
|
142
|
+
for seg in narration
|
|
143
|
+
]
|
|
@@ -1,88 +1,49 @@
|
|
|
1
1
|
"""Fuse scenes, ASR, and quiet windows for narration planning."""
|
|
2
2
|
|
|
3
|
-
import importlib.util
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
from pathlib import Path
|
|
7
|
-
|
|
8
3
|
from lib import CONFIG
|
|
9
|
-
|
|
10
|
-
|
|
11
4
|
from agent_text import _overlap_seconds, _recommended_char_budget
|
|
12
5
|
from narration_lint import _validate_narration_budget
|
|
13
6
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
_deslop_qc_path = Path(__file__).with_name("deslop_qc.py")
|
|
18
|
-
_deslop_qc_spec = importlib.util.spec_from_file_location(
|
|
19
|
-
"deslop_qc", _deslop_qc_path
|
|
20
|
-
)
|
|
21
|
-
if _deslop_qc_spec is None or _deslop_qc_spec.loader is None:
|
|
22
|
-
raise
|
|
23
|
-
_deslop_qc_module = importlib.util.module_from_spec(_deslop_qc_spec)
|
|
24
|
-
_deslop_qc_spec.loader.exec_module(_deslop_qc_module)
|
|
25
|
-
analyze_deslop_qc = _deslop_qc_module.analyze_deslop_qc
|
|
7
|
+
|
|
8
|
+
def _quiet_windows(silence_periods):
|
|
9
|
+
return [qp for qp in silence_periods if not qp["has_speech"]]
|
|
26
10
|
|
|
27
11
|
|
|
28
12
|
def _align_narration_to_quiet(narration, scenes_analysis, silence_periods):
|
|
29
13
|
"""Recompute overlaps_speech from real quiet windows; keep the agent's timing.
|
|
30
14
|
|
|
31
15
|
The dense continuous-bed design places narration ON the pictured beat over a
|
|
32
|
-
ducked original bed, so
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
correct the overlaps_speech flag that the ducking stage consumes, leaving the
|
|
36
|
-
agent's start/end (and text) intact.
|
|
37
|
-
"""
|
|
38
|
-
if not silence_periods:
|
|
39
|
-
for n in narration:
|
|
40
|
-
n["overlaps_speech"] = True
|
|
41
|
-
return _validate_narration_budget(narration, scenes_analysis)
|
|
16
|
+
ducked original bed, so segments are never relocated into silence gaps. Only
|
|
17
|
+
the overlaps_speech flag that the ducking stage consumes is corrected, leaving
|
|
18
|
+
the agent's start/end (and text) intact.
|
|
42
19
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
quiet_ratio_min = float(CONFIG.get("quiet_overlap_min_ratio", 0.8) or 0.8)
|
|
47
|
-
|
|
48
|
-
# Run budget/dedup FIRST, then set the flag on the final (possibly merged) spans,
|
|
49
|
-
# so a dedup-merged beat's overlaps_speech reflects its extended timing, not its
|
|
50
|
-
# original shorter span (which the ducking stage in assemble.py would mis-duck).
|
|
20
|
+
Budget/dedup runs FIRST so a dedup-merged beat's overlaps_speech reflects its
|
|
21
|
+
extended timing, not its original shorter span.
|
|
22
|
+
"""
|
|
51
23
|
aligned = _validate_narration_budget(narration, scenes_analysis)
|
|
24
|
+
quiet_windows = _quiet_windows(silence_periods)
|
|
25
|
+
quiet_ratio_min = CONFIG["quiet_overlap_min_ratio"]
|
|
52
26
|
for n in aligned:
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
continue
|
|
59
|
-
seg_dur = max(0.0, seg_end - seg_start)
|
|
60
|
-
quiet_overlap = _quiet_overlap_seconds(seg_start, seg_end, quiet_windows)
|
|
27
|
+
seg_dur = n["end"] - n["start"]
|
|
28
|
+
quiet_overlap = sum(
|
|
29
|
+
_overlap_seconds(n["start"], n["end"], qw["start"], qw["end"])
|
|
30
|
+
for qw in quiet_windows
|
|
31
|
+
)
|
|
61
32
|
n["overlaps_speech"] = quiet_overlap < max(0.3, seg_dur * quiet_ratio_min)
|
|
62
|
-
|
|
63
33
|
return aligned
|
|
64
34
|
|
|
65
35
|
|
|
66
36
|
def _scene_asr_lines(asr_result, scene):
|
|
67
37
|
lines = []
|
|
68
|
-
for seg in asr_result
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
end = float(seg.get("end", start))
|
|
72
|
-
except (TypeError, ValueError):
|
|
73
|
-
continue
|
|
74
|
-
if scene["start"] < end and scene["end"] > start:
|
|
75
|
-
text = str(seg.get("text", "")).strip()
|
|
76
|
-
if text:
|
|
77
|
-
lines.append(f" [{start:.1f}-{end:.1f}] {text}")
|
|
38
|
+
for seg in asr_result:
|
|
39
|
+
if scene["start"] < seg["end"] and scene["end"] > seg["start"] and seg["text"]:
|
|
40
|
+
lines.append(f" [{seg['start']:.1f}-{seg['end']:.1f}] {seg['text']}")
|
|
78
41
|
return lines
|
|
79
42
|
|
|
80
43
|
|
|
81
44
|
def _quiet_windows_for_scene(silence_periods, scene):
|
|
82
45
|
windows = []
|
|
83
|
-
for qp in silence_periods
|
|
84
|
-
if not _silence_window_is_usable_quiet(qp):
|
|
85
|
-
continue
|
|
46
|
+
for qp in _quiet_windows(silence_periods):
|
|
86
47
|
if qp["start"] < scene["end"] and qp["end"] > scene["start"]:
|
|
87
48
|
start = max(qp["start"], scene["start"])
|
|
88
49
|
end = min(qp["end"], scene["end"])
|
|
@@ -91,87 +52,35 @@ def _quiet_windows_for_scene(silence_periods, scene):
|
|
|
91
52
|
return windows
|
|
92
53
|
|
|
93
54
|
|
|
94
|
-
def _silence_window_is_usable_quiet(qp):
|
|
95
|
-
if not isinstance(qp, dict):
|
|
96
|
-
return False
|
|
97
|
-
reason = str(qp.get("has_speech_reason", "")).lower()
|
|
98
|
-
granularity = str(qp.get("asr_granularity", "")).lower()
|
|
99
|
-
if reason in {
|
|
100
|
-
"coarse_asr_overlap_ignored",
|
|
101
|
-
"asr_overlap_low_confidence_quiet",
|
|
102
|
-
"coarse_asr_no_overlap",
|
|
103
|
-
}:
|
|
104
|
-
return True
|
|
105
|
-
if granularity == "coarse_grid" and qp.get("has_speech") is True:
|
|
106
|
-
return True
|
|
107
|
-
return not qp.get("has_speech", False)
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
def _quiet_overlap_seconds(start, end, quiet_windows):
|
|
111
|
-
overlap_seconds = 0.0
|
|
112
|
-
for qw in quiet_windows:
|
|
113
|
-
try:
|
|
114
|
-
if isinstance(qw, dict):
|
|
115
|
-
q_start = float(qw.get("start", 0))
|
|
116
|
-
q_end = float(qw.get("end", q_start))
|
|
117
|
-
else:
|
|
118
|
-
q_start, q_end = qw
|
|
119
|
-
q_start = float(q_start)
|
|
120
|
-
q_end = float(q_end)
|
|
121
|
-
except (TypeError, ValueError):
|
|
122
|
-
continue
|
|
123
|
-
overlap_seconds += _overlap_seconds(start, end, q_start, q_end)
|
|
124
|
-
return overlap_seconds
|
|
125
|
-
|
|
126
|
-
|
|
127
55
|
def _build_timeline_fusion(scenes, asr_segments, silence_periods):
|
|
128
56
|
"""Fuse VLM scenes, ASR dialogue and quiet narration slots on one timeline."""
|
|
129
57
|
fusion = []
|
|
130
|
-
quiet_windows =
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
for scene in scenes or []:
|
|
134
|
-
try:
|
|
135
|
-
start = float(scene.get("start", 0))
|
|
136
|
-
end = float(scene.get("end", start))
|
|
137
|
-
except (TypeError, ValueError):
|
|
138
|
-
continue
|
|
58
|
+
quiet_windows = _quiet_windows(silence_periods)
|
|
59
|
+
for scene in scenes:
|
|
60
|
+
start, end = scene["start"], scene["end"]
|
|
139
61
|
dialogue_segments = []
|
|
140
62
|
dialogue_overlap = 0.0
|
|
141
|
-
for seg in asr_segments
|
|
142
|
-
|
|
143
|
-
continue
|
|
144
|
-
try:
|
|
145
|
-
seg_start = float(seg.get("start", 0))
|
|
146
|
-
seg_end = float(seg.get("end", seg_start))
|
|
147
|
-
except (TypeError, ValueError):
|
|
148
|
-
continue
|
|
149
|
-
overlap = _overlap_seconds(start, end, seg_start, seg_end)
|
|
63
|
+
for seg in asr_segments:
|
|
64
|
+
overlap = _overlap_seconds(start, end, seg["start"], seg["end"])
|
|
150
65
|
if overlap <= 0:
|
|
151
66
|
continue
|
|
152
|
-
text = str(seg.get("text", "")).strip()
|
|
153
67
|
dialogue_overlap += overlap
|
|
154
68
|
dialogue_segments.append(
|
|
155
69
|
{
|
|
156
|
-
"start": round(
|
|
157
|
-
"end": round(
|
|
70
|
+
"start": round(seg["start"], 2),
|
|
71
|
+
"end": round(seg["end"], 2),
|
|
158
72
|
"overlap_seconds": round(overlap, 2),
|
|
159
|
-
"text": text,
|
|
73
|
+
"text": seg["text"],
|
|
160
74
|
}
|
|
161
75
|
)
|
|
162
76
|
|
|
163
77
|
narration_slots = []
|
|
164
78
|
for window in quiet_windows:
|
|
165
|
-
|
|
166
|
-
w_start = float(window.get("start", 0))
|
|
167
|
-
w_end = float(window.get("end", w_start))
|
|
168
|
-
except (TypeError, ValueError):
|
|
169
|
-
continue
|
|
170
|
-
overlap = _overlap_seconds(start, end, w_start, w_end)
|
|
79
|
+
overlap = _overlap_seconds(start, end, window["start"], window["end"])
|
|
171
80
|
if overlap <= 0:
|
|
172
81
|
continue
|
|
173
|
-
slot_start = max(start,
|
|
174
|
-
slot_end = min(end,
|
|
82
|
+
slot_start = max(start, window["start"])
|
|
83
|
+
slot_end = min(end, window["end"])
|
|
175
84
|
narration_slots.append(
|
|
176
85
|
{
|
|
177
86
|
"start": round(slot_start, 2),
|
|
@@ -183,9 +92,9 @@ def _build_timeline_fusion(scenes, asr_segments, silence_periods):
|
|
|
183
92
|
|
|
184
93
|
fusion.append(
|
|
185
94
|
{
|
|
186
|
-
"scene_id": scene
|
|
95
|
+
"scene_id": scene["scene_id"],
|
|
187
96
|
"time_range": [round(start, 2), round(end, 2)],
|
|
188
|
-
"visual_description": scene
|
|
97
|
+
"visual_description": scene["description"],
|
|
189
98
|
"depth_analysis": scene.get("depth_analysis", ""),
|
|
190
99
|
"frame_facts": scene.get("frame_facts", {}),
|
|
191
100
|
"dialogue_segments": dialogue_segments,
|
|
@@ -24,10 +24,7 @@ def _parse_vlm_depth_response(raw_text):
|
|
|
24
24
|
|
|
25
25
|
# 提取【描述】
|
|
26
26
|
desc_match = re.search(r'【描述】\s*\n?(.*?)(?=【帧标签】|【深层分析】|$)', raw_text, re.DOTALL)
|
|
27
|
-
if desc_match
|
|
28
|
-
description = desc_match.group(1).strip()
|
|
29
|
-
else:
|
|
30
|
-
description = raw_text.strip()
|
|
27
|
+
description = desc_match.group(1).strip() if desc_match else raw_text.strip()
|
|
31
28
|
|
|
32
29
|
# 提取【帧标签】
|
|
33
30
|
frame_facts = {}
|
|
@@ -89,13 +89,13 @@ DUB_ARTIFACT_SCHEMAS = {
|
|
|
89
89
|
|
|
90
90
|
|
|
91
91
|
def _chars_per_second(text, room):
|
|
92
|
-
room = max(
|
|
92
|
+
room = max(room, 0.001)
|
|
93
93
|
effective = 0
|
|
94
|
-
for ch in
|
|
94
|
+
for ch in text:
|
|
95
95
|
if ch.isspace():
|
|
96
96
|
continue
|
|
97
97
|
cat = unicodedata.category(ch)
|
|
98
|
-
if cat.startswith("P"
|
|
98
|
+
if cat.startswith(("P", "S")):
|
|
99
99
|
continue
|
|
100
100
|
effective += 1
|
|
101
101
|
return effective / room
|
|
@@ -104,15 +104,13 @@ def _chars_per_second(text, room):
|
|
|
104
104
|
def _issue(severity, code, line, message, start=None, end=None):
|
|
105
105
|
item = {"severity": severity, "code": code, "line": line, "message": message}
|
|
106
106
|
if start is not None:
|
|
107
|
-
item["start"] = round(
|
|
107
|
+
item["start"] = round(start, 3)
|
|
108
108
|
if end is not None:
|
|
109
|
-
item["end"] = round(
|
|
109
|
+
item["end"] = round(end, 3)
|
|
110
110
|
return item
|
|
111
111
|
|
|
112
112
|
|
|
113
113
|
def _normalize_dub_script(script):
|
|
114
|
-
if not isinstance(script, list):
|
|
115
|
-
raise ValueError("dub_script.json must be a list")
|
|
116
114
|
lines = []
|
|
117
115
|
for i, item in enumerate(script):
|
|
118
116
|
if not isinstance(item, dict):
|
|
@@ -212,10 +210,7 @@ def build_dub_review(script, transcript, lint=None):
|
|
|
212
210
|
"""Script-level review scaffold: deterministic timing/naturalness signals for agent review."""
|
|
213
211
|
duration = float(transcript.get("duration", 0.0))
|
|
214
212
|
lint = lint or lint_dub_script(script, duration)
|
|
215
|
-
|
|
216
|
-
lines = _normalize_dub_script(script)
|
|
217
|
-
except ValueError:
|
|
218
|
-
lines = [] # non-list script already FAILs lint; review is just a scaffold here
|
|
213
|
+
lines = _normalize_dub_script(script) if isinstance(script, list) else []
|
|
219
214
|
edits = []
|
|
220
215
|
for issue in lint.get("issues", []):
|
|
221
216
|
if issue["severity"] in {"error", "warning"}:
|
|
@@ -286,8 +281,7 @@ def _strip_reasoning_residue(text):
|
|
|
286
281
|
"""
|
|
287
282
|
text = re.sub(r"(?is)<think\b.*?</think\s*>", "", text) # full <think>…</think> block
|
|
288
283
|
text = re.sub(r"(?is)<think\b.*\Z", "", text) # unclosed/truncated <think tail
|
|
289
|
-
|
|
290
|
-
return text
|
|
284
|
+
return re.sub(r"(?i)^\s*<?/?think\s*>\s*", "", text) # leading orphan/residual think tag
|
|
291
285
|
|
|
292
286
|
|
|
293
287
|
def _run_asr(wav_path, lang="en"):
|
|
@@ -10,62 +10,50 @@ import urllib.error
|
|
|
10
10
|
import urllib.request
|
|
11
11
|
from pathlib import Path
|
|
12
12
|
|
|
13
|
-
from lib import
|
|
14
|
-
CONFIG,
|
|
15
|
-
DEFAULT_FISH_TTS_API_URL,
|
|
16
|
-
DEFAULT_FISH_TTS_MODEL,
|
|
17
|
-
_sanitize_api_error,
|
|
18
|
-
)
|
|
13
|
+
from lib import CONFIG, _sanitize_api_error
|
|
19
14
|
|
|
20
15
|
|
|
21
|
-
def _fish_payload(text, rate
|
|
22
|
-
try:
|
|
23
|
-
speed = 1.0 + float(str(rate).rstrip("%")) / 100.0
|
|
24
|
-
except (TypeError, ValueError):
|
|
25
|
-
speed = 1.0
|
|
16
|
+
def _fish_payload(text, rate):
|
|
26
17
|
payload = {
|
|
27
18
|
"text": text,
|
|
28
19
|
"format": "wav",
|
|
29
20
|
"normalize": True,
|
|
30
21
|
"prosody": {
|
|
31
|
-
"speed":
|
|
22
|
+
"speed": 1.0 + float(rate.rstrip("%")) / 100.0,
|
|
32
23
|
"volume": 0,
|
|
33
24
|
"normalize_loudness": True,
|
|
34
25
|
},
|
|
35
26
|
}
|
|
36
|
-
reference_id =
|
|
27
|
+
reference_id = CONFIG["fish_tts_reference_id"]
|
|
37
28
|
if reference_id:
|
|
38
29
|
payload["reference_id"] = reference_id
|
|
39
30
|
return payload
|
|
40
31
|
|
|
41
32
|
|
|
42
|
-
def synthesize_fish_audio(text, output_path, rate="+0%"
|
|
33
|
+
def synthesize_fish_audio(text, output_path, rate="+0%"):
|
|
43
34
|
"""Synthesize one narration block and atomically write the WAV response."""
|
|
44
|
-
api_key =
|
|
35
|
+
api_key = CONFIG["fish_api_key"]
|
|
45
36
|
if not api_key:
|
|
46
37
|
raise RuntimeError("请设置 FISH_API_KEY 用于 Fish Audio TTS")
|
|
47
38
|
|
|
48
|
-
endpoint = str(CONFIG.get("fish_tts_api_url") or DEFAULT_FISH_TTS_API_URL)
|
|
49
|
-
model = str(CONFIG.get("fish_tts_model") or DEFAULT_FISH_TTS_MODEL)
|
|
50
39
|
body = json.dumps(_fish_payload(text, rate), ensure_ascii=False).encode("utf-8")
|
|
51
40
|
request = urllib.request.Request(
|
|
52
|
-
|
|
41
|
+
CONFIG["fish_tts_api_url"],
|
|
53
42
|
data=body,
|
|
54
43
|
headers={
|
|
55
44
|
"Authorization": f"Bearer {api_key}",
|
|
56
45
|
"Content-Type": "application/json",
|
|
57
46
|
"Accept": "audio/wav",
|
|
58
|
-
"model":
|
|
47
|
+
"model": CONFIG["fish_tts_model"],
|
|
59
48
|
"User-Agent": "video-recap/1.0",
|
|
60
49
|
},
|
|
61
50
|
method="POST",
|
|
62
51
|
)
|
|
63
52
|
|
|
64
|
-
timeout = max(1, int(CONFIG.get("tts_timeout", 300) or 300))
|
|
65
53
|
try:
|
|
66
|
-
with urllib.request.urlopen(request, timeout=
|
|
54
|
+
with urllib.request.urlopen(request, timeout=CONFIG["tts_timeout"]) as response:
|
|
67
55
|
audio = response.read()
|
|
68
|
-
content_type =
|
|
56
|
+
content_type = response.headers.get("Content-Type", "").lower()
|
|
69
57
|
except urllib.error.HTTPError as exc:
|
|
70
58
|
detail = _sanitize_api_error(
|
|
71
59
|
exc.read().decode("utf-8", errors="replace"),
|
|
@@ -99,11 +87,5 @@ def synthesize_fish_audio(text, output_path, rate="+0%", **_ignored):
|
|
|
99
87
|
|
|
100
88
|
output = Path(output_path)
|
|
101
89
|
partial = Path(str(output) + ".part")
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
os.replace(partial, output)
|
|
105
|
-
finally:
|
|
106
|
-
try:
|
|
107
|
-
partial.unlink(missing_ok=True)
|
|
108
|
-
except OSError:
|
|
109
|
-
pass
|
|
90
|
+
partial.write_bytes(audio)
|
|
91
|
+
os.replace(partial, output)
|