@oh-story/dsh 0.1.7 → 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 +30 -15
- package/lib/client.js +93 -16
- 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,73 +1,51 @@
|
|
|
1
1
|
"""Remap cut evidence and format output-timeline brief directives."""
|
|
2
2
|
|
|
3
|
-
import importlib.util
|
|
4
|
-
|
|
5
|
-
|
|
6
3
|
import json
|
|
7
|
-
|
|
8
4
|
import math
|
|
9
|
-
|
|
10
5
|
import re
|
|
11
|
-
|
|
12
6
|
from pathlib import Path
|
|
13
7
|
|
|
14
8
|
from lib import CONFIG, stable_hash
|
|
15
9
|
|
|
16
10
|
|
|
17
|
-
try:
|
|
18
|
-
from deslop_qc import analyze_deslop_qc
|
|
19
|
-
except ModuleNotFoundError:
|
|
20
|
-
_deslop_qc_path = Path(__file__).with_name("deslop_qc.py")
|
|
21
|
-
_deslop_qc_spec = importlib.util.spec_from_file_location(
|
|
22
|
-
"deslop_qc", _deslop_qc_path
|
|
23
|
-
)
|
|
24
|
-
if _deslop_qc_spec is None or _deslop_qc_spec.loader is None:
|
|
25
|
-
raise
|
|
26
|
-
_deslop_qc_module = importlib.util.module_from_spec(_deslop_qc_spec)
|
|
27
|
-
_deslop_qc_spec.loader.exec_module(_deslop_qc_module)
|
|
28
|
-
analyze_deslop_qc = _deslop_qc_module.analyze_deslop_qc
|
|
29
|
-
|
|
30
|
-
|
|
31
11
|
def _parse_target_seconds(value):
|
|
32
12
|
"""Parse a cut-mode target duration ("30m" / "600" / "1h5m" / "00:30:00") to seconds.
|
|
33
13
|
|
|
34
|
-
|
|
35
|
-
|
|
14
|
+
None or blank means the knob is unset (None). Any other value that does not parse to
|
|
15
|
+
a positive duration is a typo in user input and raises ValueError.
|
|
36
16
|
"""
|
|
37
17
|
if value is None:
|
|
38
18
|
return None
|
|
39
19
|
if isinstance(value, (int, float)):
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
20
|
+
seconds = float(value)
|
|
21
|
+
else:
|
|
22
|
+
text = str(value).strip().lower()
|
|
23
|
+
if not text:
|
|
24
|
+
return None
|
|
45
25
|
if ":" in text:
|
|
46
26
|
parts = [float(p) for p in text.split(":")]
|
|
47
|
-
if any(p < 0 for p in parts):
|
|
48
|
-
return None
|
|
49
27
|
if len(parts) == 2:
|
|
50
28
|
seconds = parts[0] * 60 + parts[1]
|
|
51
29
|
elif len(parts) == 3:
|
|
52
30
|
seconds = parts[0] * 3600 + parts[1] * 60 + parts[2]
|
|
53
31
|
else:
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
32
|
+
raise ValueError(f"unparseable target duration: {value!r}")
|
|
33
|
+
if any(p < 0 for p in parts):
|
|
34
|
+
raise ValueError(f"unparseable target duration: {value!r}")
|
|
35
|
+
else:
|
|
36
|
+
factors = {"ms": 0.001, "s": 1, "m": 60, "h": 3600}
|
|
37
|
+
seconds = 0.0
|
|
38
|
+
pos = 0
|
|
39
|
+
for m in re.finditer(r"([0-9]+(?:\.[0-9]+)?)(ms|s|m|h)?", text):
|
|
40
|
+
if m.start() != pos:
|
|
41
|
+
break
|
|
42
|
+
pos = m.end()
|
|
43
|
+
seconds += float(m.group(1)) * factors[m.group(2) or "s"]
|
|
44
|
+
if pos == 0 or pos != len(text):
|
|
45
|
+
raise ValueError(f"unparseable target duration: {value!r}")
|
|
46
|
+
if not seconds > 0:
|
|
47
|
+
raise ValueError(f"target duration must be positive: {value!r}")
|
|
48
|
+
return seconds
|
|
71
49
|
|
|
72
50
|
|
|
73
51
|
def _format_research_directive(work_dir, substrate):
|
|
@@ -81,9 +59,9 @@ def _format_research_directive(work_dir, substrate):
|
|
|
81
59
|
"""
|
|
82
60
|
if (Path(work_dir) / "background_research.json").exists():
|
|
83
61
|
return [] # already researched; _format_background_research surfaces it
|
|
84
|
-
if
|
|
62
|
+
if substrate["level"] not in ("thin", "empty"):
|
|
85
63
|
return [] # rich enough to write from dialogue/spine; do not nag
|
|
86
|
-
context =
|
|
64
|
+
context = CONFIG["context_info"].strip()
|
|
87
65
|
return [
|
|
88
66
|
"## ⚑ Research the story FIRST (do this before writing narration)",
|
|
89
67
|
"",
|
|
@@ -114,7 +92,7 @@ def _load_cut_output_spans_for_brief(work_dir, *, required=False):
|
|
|
114
92
|
if required:
|
|
115
93
|
raise SystemExit(
|
|
116
94
|
"cut pass2 brief requires fresh clip_plan_validated.json with explicit "
|
|
117
|
-
f"
|
|
95
|
+
f"source/output spans ({reason})"
|
|
118
96
|
)
|
|
119
97
|
return None
|
|
120
98
|
|
|
@@ -124,59 +102,31 @@ def _load_cut_output_spans_for_brief(work_dir, *, required=False):
|
|
|
124
102
|
validated_path = work_dir / "clip_plan_validated.json"
|
|
125
103
|
if not validated_path.exists():
|
|
126
104
|
return fail("missing clip_plan_validated.json")
|
|
127
|
-
|
|
128
|
-
plan = json.loads(validated_path.read_text(encoding="utf-8"))
|
|
129
|
-
except (json.JSONDecodeError, OSError):
|
|
130
|
-
return fail("invalid clip_plan_validated.json")
|
|
131
|
-
if not isinstance(plan, dict):
|
|
132
|
-
return fail("clip_plan_validated.json is not an object")
|
|
105
|
+
plan = json.loads(validated_path.read_text(encoding="utf-8"))
|
|
133
106
|
raw_path = work_dir / "clip_plan.json"
|
|
134
|
-
if raw_path.exists()
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
return fail("invalid clip_plan.json")
|
|
139
|
-
if plan.get("raw_plan_fingerprint") != stable_hash(raw_plan):
|
|
140
|
-
return fail("stale clip_plan_validated.json")
|
|
141
|
-
clips = plan.get("clips")
|
|
142
|
-
if not isinstance(clips, list):
|
|
143
|
-
return fail("clips is not a list")
|
|
107
|
+
if raw_path.exists() and plan["raw_plan_fingerprint"] != stable_hash(
|
|
108
|
+
json.loads(raw_path.read_text(encoding="utf-8"))
|
|
109
|
+
):
|
|
110
|
+
return fail("stale clip_plan_validated.json")
|
|
144
111
|
spans = []
|
|
145
|
-
for clip in clips:
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
if not all(
|
|
149
|
-
key in clip
|
|
112
|
+
for clip in plan["clips"]:
|
|
113
|
+
span = {
|
|
114
|
+
key: clip[key]
|
|
150
115
|
for key in ("source_start", "source_end", "output_start", "output_end")
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
try:
|
|
154
|
-
ss = float(clip["source_start"])
|
|
155
|
-
se = float(clip["source_end"])
|
|
156
|
-
os_ = float(clip["output_start"])
|
|
157
|
-
oe = float(clip["output_end"])
|
|
158
|
-
except (TypeError, ValueError):
|
|
159
|
-
return fail("non-numeric clip span")
|
|
160
|
-
if not all(math.isfinite(x) for x in (ss, se, os_, oe)):
|
|
116
|
+
}
|
|
117
|
+
if not all(math.isfinite(value) for value in span.values()):
|
|
161
118
|
return fail("non-finite clip span")
|
|
162
|
-
if
|
|
119
|
+
if span["source_end"] <= span["source_start"] or span["output_end"] <= span["output_start"]:
|
|
163
120
|
return fail("non-positive clip span")
|
|
164
|
-
spans.append(
|
|
165
|
-
|
|
166
|
-
"source_start": ss,
|
|
167
|
-
"source_end": se,
|
|
168
|
-
"output_start": os_,
|
|
169
|
-
"output_end": oe,
|
|
170
|
-
}
|
|
171
|
-
)
|
|
172
|
-
return spans or fail("no valid clips")
|
|
121
|
+
spans.append(span)
|
|
122
|
+
return spans or fail("no clips")
|
|
173
123
|
|
|
174
124
|
|
|
175
125
|
def _source_output_overlaps_for_brief(start, end, spans):
|
|
176
126
|
overlaps = []
|
|
177
|
-
for span in spans
|
|
178
|
-
source_start = max(
|
|
179
|
-
source_end = min(
|
|
127
|
+
for span in spans:
|
|
128
|
+
source_start = max(start, span["source_start"])
|
|
129
|
+
source_end = min(end, span["source_end"])
|
|
180
130
|
if source_end <= source_start:
|
|
181
131
|
continue
|
|
182
132
|
output_start = span["output_start"] + (source_start - span["source_start"])
|
|
@@ -193,14 +143,9 @@ def _source_output_overlaps_for_brief(start, end, spans):
|
|
|
193
143
|
|
|
194
144
|
|
|
195
145
|
def _remap_frame_facts_for_brief(frame_facts, overlap):
|
|
196
|
-
if not isinstance(frame_facts, dict):
|
|
197
|
-
return frame_facts
|
|
198
146
|
out = {}
|
|
199
147
|
for raw_ts, vals in frame_facts.items():
|
|
200
|
-
|
|
201
|
-
ts = float(raw_ts)
|
|
202
|
-
except (TypeError, ValueError):
|
|
203
|
-
continue
|
|
148
|
+
ts = float(raw_ts)
|
|
204
149
|
if not (overlap["source_start"] <= ts <= overlap["source_end"]):
|
|
205
150
|
continue
|
|
206
151
|
out_ts = overlap["output_start"] + (ts - overlap["source_start"])
|
|
@@ -209,54 +154,34 @@ def _remap_frame_facts_for_brief(frame_facts, overlap):
|
|
|
209
154
|
|
|
210
155
|
|
|
211
156
|
def _remap_scenes_to_output_for_brief(scenes, spans):
|
|
212
|
-
if not spans:
|
|
213
|
-
return scenes or []
|
|
214
157
|
out = []
|
|
215
|
-
for scene in scenes
|
|
216
|
-
|
|
217
|
-
continue
|
|
218
|
-
try:
|
|
219
|
-
start = float(scene.get("start", 0))
|
|
220
|
-
end = float(scene.get("end", start))
|
|
221
|
-
except (TypeError, ValueError):
|
|
222
|
-
continue
|
|
223
|
-
overlaps = _source_output_overlaps_for_brief(start, end, spans)
|
|
158
|
+
for scene in scenes:
|
|
159
|
+
overlaps = _source_output_overlaps_for_brief(scene["start"], scene["end"], spans)
|
|
224
160
|
for part_idx, overlap in enumerate(overlaps):
|
|
225
161
|
item = dict(scene)
|
|
226
162
|
item["start"] = round(overlap["output_start"], 3)
|
|
227
163
|
item["end"] = round(overlap["output_end"], 3)
|
|
228
164
|
item["frame_facts"] = _remap_frame_facts_for_brief(
|
|
229
|
-
scene.get("frame_facts"), overlap
|
|
165
|
+
scene.get("frame_facts", {}), overlap
|
|
230
166
|
)
|
|
231
167
|
if len(overlaps) > 1:
|
|
232
|
-
item["scene_id"] = f"{scene
|
|
168
|
+
item["scene_id"] = f"{scene['scene_id']}.{part_idx}"
|
|
233
169
|
out.append(item)
|
|
234
|
-
out.sort(key=lambda x: (
|
|
170
|
+
out.sort(key=lambda x: (x["start"], x["end"]))
|
|
235
171
|
return out
|
|
236
172
|
|
|
237
173
|
|
|
238
174
|
def _remap_segments_to_output_for_brief(segments, spans):
|
|
239
|
-
if not spans:
|
|
240
|
-
return segments or []
|
|
241
175
|
out = []
|
|
242
|
-
for seg in segments
|
|
243
|
-
|
|
244
|
-
continue
|
|
245
|
-
try:
|
|
246
|
-
start = float(seg.get("start", 0))
|
|
247
|
-
end = float(seg.get("end", start))
|
|
248
|
-
except (TypeError, ValueError):
|
|
249
|
-
continue
|
|
250
|
-
if end <= start:
|
|
251
|
-
continue
|
|
252
|
-
for overlap in _source_output_overlaps_for_brief(start, end, spans):
|
|
176
|
+
for seg in segments:
|
|
177
|
+
for overlap in _source_output_overlaps_for_brief(seg["start"], seg["end"], spans):
|
|
253
178
|
item = dict(seg)
|
|
254
179
|
item["start"] = round(overlap["output_start"], 3)
|
|
255
180
|
item["end"] = round(overlap["output_end"], 3)
|
|
256
181
|
if "duration" in item:
|
|
257
182
|
item["duration"] = round(item["end"] - item["start"], 3)
|
|
258
183
|
out.append(item)
|
|
259
|
-
out.sort(key=lambda x: (
|
|
184
|
+
out.sort(key=lambda x: (x["start"], x["end"]))
|
|
260
185
|
return out
|
|
261
186
|
|
|
262
187
|
|
|
@@ -273,26 +198,27 @@ def _remap_brief_evidence_to_output_timeline(
|
|
|
273
198
|
)
|
|
274
199
|
|
|
275
200
|
|
|
201
|
+
def _read_json(path, default):
|
|
202
|
+
"""JSON artifact, or `default` when the optional file was never written."""
|
|
203
|
+
return json.loads(path.read_text(encoding="utf-8")) if path.exists() else default
|
|
204
|
+
|
|
205
|
+
|
|
276
206
|
def _sentence_entry_anchors_for_brief(work_dir, edit_mode):
|
|
277
207
|
"""Load sentence anchors and remap them to cut OUTPUT time when needed."""
|
|
278
208
|
work_dir = Path(work_dir)
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
anchors = [dict(item) for item in anchors if isinstance(item, dict)]
|
|
209
|
+
anchors = [
|
|
210
|
+
dict(item)
|
|
211
|
+
for item in _read_json(
|
|
212
|
+
work_dir / "speech_boundary_anchors.json", {"sentence_anchors": []}
|
|
213
|
+
)["sentence_anchors"]
|
|
214
|
+
]
|
|
286
215
|
if edit_mode != "cut" or not (work_dir / "edited_source.mp4").exists():
|
|
287
216
|
return anchors
|
|
288
217
|
|
|
289
218
|
spans = _load_cut_output_spans_for_brief(work_dir, required=True)
|
|
290
219
|
remapped = []
|
|
291
220
|
for anchor in anchors:
|
|
292
|
-
|
|
293
|
-
source_time = float(anchor.get("time"))
|
|
294
|
-
except (TypeError, ValueError):
|
|
295
|
-
continue
|
|
221
|
+
source_time = anchor["time"]
|
|
296
222
|
for span in spans:
|
|
297
223
|
if span["source_start"] - 0.05 <= source_time <= span["source_end"] + 0.05:
|
|
298
224
|
item = dict(anchor)
|
|
@@ -300,16 +226,10 @@ def _sentence_entry_anchors_for_brief(work_dir, edit_mode):
|
|
|
300
226
|
item["time"] = round(
|
|
301
227
|
span["output_start"] + source_time - span["source_start"], 3
|
|
302
228
|
)
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
anchor.get("pause_start", source_time - 0.12)
|
|
306
|
-
)
|
|
307
|
-
except (TypeError, ValueError):
|
|
308
|
-
source_pause_start = source_time - 0.12
|
|
309
|
-
# Preserve the measured safe pause in OUTPUT time too. Leaving pause_start
|
|
310
|
-
# in SOURCE time made cut-mode lint compare two different clocks.
|
|
229
|
+
# Preserve the measured safe pause in OUTPUT time too, so cut-mode lint
|
|
230
|
+
# compares one clock.
|
|
311
231
|
source_pause_start = max(
|
|
312
|
-
span["source_start"], min(
|
|
232
|
+
span["source_start"], min(anchor.get("pause_start", source_time - 0.12), source_time)
|
|
313
233
|
)
|
|
314
234
|
item["source_pause_start"] = round(source_pause_start, 3)
|
|
315
235
|
item["pause_start"] = round(
|
|
@@ -317,34 +237,14 @@ def _sentence_entry_anchors_for_brief(work_dir, edit_mode):
|
|
|
317
237
|
)
|
|
318
238
|
remapped.append(item)
|
|
319
239
|
|
|
320
|
-
speech_rows = [
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
raw = json.loads((work_dir / name).read_text(encoding="utf-8"))
|
|
324
|
-
except (OSError, json.JSONDecodeError):
|
|
325
|
-
continue
|
|
326
|
-
if isinstance(raw, dict):
|
|
327
|
-
raw = raw.get("segments", [])
|
|
328
|
-
if isinstance(raw, list):
|
|
329
|
-
candidate = [
|
|
330
|
-
item
|
|
331
|
-
for item in raw
|
|
332
|
-
if isinstance(item, dict) and str(item.get("text") or "").strip()
|
|
333
|
-
]
|
|
334
|
-
if candidate:
|
|
335
|
-
speech_rows = candidate
|
|
336
|
-
break
|
|
337
|
-
try:
|
|
338
|
-
raw_quiet = json.loads(
|
|
339
|
-
(work_dir / "silence_periods.json").read_text(encoding="utf-8")
|
|
340
|
-
)
|
|
341
|
-
except (OSError, json.JSONDecodeError):
|
|
342
|
-
raw_quiet = []
|
|
240
|
+
speech_rows = [
|
|
241
|
+
row for row in _read_json(work_dir / "asr_result.json", []) if row["text"]
|
|
242
|
+
]
|
|
343
243
|
quiet_rows = [
|
|
344
|
-
|
|
345
|
-
for
|
|
346
|
-
if
|
|
347
|
-
]
|
|
244
|
+
row
|
|
245
|
+
for row in _read_json(work_dir / "silence_periods.json", [])
|
|
246
|
+
if not row["has_speech"]
|
|
247
|
+
]
|
|
348
248
|
out_payload = {
|
|
349
249
|
"schema_version": 2,
|
|
350
250
|
"artifact": "speech_boundary_anchors_output.json",
|
|
@@ -353,7 +253,7 @@ def _sentence_entry_anchors_for_brief(work_dir, edit_mode):
|
|
|
353
253
|
"clip_plan_fingerprint": stable_hash(
|
|
354
254
|
json.loads((work_dir / "clip_plan_validated.json").read_text(encoding="utf-8"))
|
|
355
255
|
),
|
|
356
|
-
"sentence_anchors": sorted(remapped, key=lambda item:
|
|
256
|
+
"sentence_anchors": sorted(remapped, key=lambda item: item["time"]),
|
|
357
257
|
"speech_spans": _remap_segments_to_output_for_brief(speech_rows, spans),
|
|
358
258
|
"quiet_windows": _remap_segments_to_output_for_brief(quiet_rows, spans),
|
|
359
259
|
}
|
|
@@ -364,15 +264,11 @@ def _sentence_entry_anchors_for_brief(work_dir, edit_mode):
|
|
|
364
264
|
|
|
365
265
|
|
|
366
266
|
def _format_sentence_entry_anchors_for_brief(work_dir, edit_mode):
|
|
367
|
-
anchors = [
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
when = float(anchor.get("time"))
|
|
373
|
-
except (TypeError, ValueError):
|
|
374
|
-
continue
|
|
375
|
-
anchors.append((when, anchor))
|
|
267
|
+
anchors = [
|
|
268
|
+
anchor
|
|
269
|
+
for anchor in _sentence_entry_anchors_for_brief(work_dir, edit_mode)
|
|
270
|
+
if anchor["confidence"] in {"high", "medium"}
|
|
271
|
+
]
|
|
376
272
|
if not anchors:
|
|
377
273
|
return []
|
|
378
274
|
lines = [
|
|
@@ -384,13 +280,14 @@ def _format_sentence_entry_anchors_for_brief(work_dir, edit_mode):
|
|
|
384
280
|
'- 原声句子完整性是硬约束:切入块写 `"source_entry_policy": "sentence_boundary"`;'
|
|
385
281
|
"不存在 `intentional_interrupt` 绕过方式。没有后续可靠句末锚点时,移动、缩短或删除旁白块。",
|
|
386
282
|
]
|
|
387
|
-
for
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
283
|
+
for anchor in anchors:
|
|
284
|
+
source_suffix = (
|
|
285
|
+
f" (SOURCE {anchor['source_time']:.2f}s)" if "source_time" in anchor else ""
|
|
286
|
+
)
|
|
287
|
+
text_tail = str(anchor.get("text_tail", "")).strip()
|
|
288
|
+
lines.append(
|
|
289
|
+
f"- {anchor['time']:.2f}s [{anchor['confidence']}]{source_suffix} {text_tail}".rstrip()
|
|
290
|
+
)
|
|
394
291
|
lines.append("")
|
|
395
292
|
return lines
|
|
396
293
|
|
|
@@ -401,25 +298,13 @@ def _format_output_clip_list(work_dir):
|
|
|
401
298
|
path = Path(work_dir) / "clip_plan_validated.json"
|
|
402
299
|
if not path.exists():
|
|
403
300
|
return []
|
|
404
|
-
|
|
405
|
-
plan = json.loads(path.read_text(encoding="utf-8"))
|
|
406
|
-
except (ValueError, OSError):
|
|
407
|
-
return []
|
|
408
|
-
clips = (plan.get("clips") if isinstance(plan, dict) else plan) or []
|
|
301
|
+
plan = json.loads(path.read_text(encoding="utf-8"))
|
|
409
302
|
out = ["## Kept clips on the OUTPUT timeline", ""]
|
|
410
|
-
for c in clips:
|
|
411
|
-
|
|
412
|
-
continue
|
|
413
|
-
try:
|
|
414
|
-
os_, oe = float(c.get("output_start")), float(c.get("output_end"))
|
|
415
|
-
ss, se = float(c.get("source_start")), float(c.get("source_end"))
|
|
416
|
-
except (TypeError, ValueError):
|
|
417
|
-
continue
|
|
418
|
-
reason = str(c.get("reason", "")).strip()
|
|
419
|
-
source_id = c.get("source_id", c.get("source", "0"))
|
|
420
|
-
clip_id = c.get("id", c.get("clip_id", "?"))
|
|
303
|
+
for c in plan["clips"]:
|
|
304
|
+
reason = c.get("reason", "")
|
|
421
305
|
out.append(
|
|
422
|
-
f"- OUTPUT {
|
|
306
|
+
f"- OUTPUT {c['output_start']:.1f}–{c['output_end']:.1f}s ← SOURCE[{c.get('source_id', '0')}] "
|
|
307
|
+
f"{c['source_start']:.1f}–{c['source_end']:.1f}s (clip_id={c.get('clip_id', '?')})"
|
|
423
308
|
+ (f" — {reason}" if reason else "")
|
|
424
309
|
)
|
|
425
310
|
out.append("")
|
|
@@ -226,7 +226,7 @@ def _apply_deterministic_asr_research_fallback(
|
|
|
226
226
|
continue
|
|
227
227
|
name = str(g.get("name", "")).strip()
|
|
228
228
|
aliases = [str(a).strip() for a in (g.get("aliases") or []) if str(a).strip()]
|
|
229
|
-
terms = [t for t in [name
|
|
229
|
+
terms = [t for t in [name, *aliases] if t]
|
|
230
230
|
if not terms:
|
|
231
231
|
continue
|
|
232
232
|
matches = []
|
|
@@ -37,10 +37,11 @@ CONNECTIVE_MARKERS = ["但", "却", "而", "于是", "随后", "直到", "结果
|
|
|
37
37
|
|
|
38
38
|
|
|
39
39
|
def _sentence_pieces(text: str) -> list[str]:
|
|
40
|
-
text
|
|
40
|
+
"""Split text into sentence-like pieces while keeping terminal punctuation."""
|
|
41
|
+
text = re.sub(r"\s+", " ", text).strip()
|
|
41
42
|
if not text:
|
|
42
43
|
return []
|
|
43
|
-
parts = re.split(r"([
|
|
44
|
+
parts = re.split(r"([。!?!?;;.])", text)
|
|
44
45
|
out: list[str] = []
|
|
45
46
|
for idx in range(0, len(parts), 2):
|
|
46
47
|
body = parts[idx].strip()
|
|
@@ -51,76 +52,50 @@ def _sentence_pieces(text: str) -> list[str]:
|
|
|
51
52
|
|
|
52
53
|
|
|
53
54
|
def _text_units(text: str) -> int:
|
|
54
|
-
|
|
55
|
+
"""Length unit for prose: CJK characters, otherwise words."""
|
|
55
56
|
if re.search(r"[\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff]", text):
|
|
56
57
|
return len(re.sub(r"\s+", "", text))
|
|
57
58
|
return len(re.findall(r"\b\w+\b", text))
|
|
58
59
|
|
|
59
60
|
|
|
60
|
-
def _normalise_segments(payload: Any, *, source: str) -> list[dict[str, Any]]:
|
|
61
|
-
if
|
|
62
|
-
return [{"source": source, "index": None, "text": payload}]
|
|
63
|
-
if not isinstance(payload, list):
|
|
64
|
-
return []
|
|
61
|
+
def _normalise_segments(payload: list[dict[str, Any]], *, source: str) -> list[dict[str, Any]]:
|
|
62
|
+
key = "narration" if source == "narration" else "text"
|
|
65
63
|
segments: list[dict[str, Any]] = []
|
|
66
64
|
for idx, item in enumerate(payload):
|
|
67
|
-
|
|
68
|
-
key = "narration" if source == "narration" else "text"
|
|
69
|
-
text = str(item.get(key, item.get("text", item.get("narration", ""))) or "").strip()
|
|
70
|
-
else:
|
|
71
|
-
text = str(item or "").strip()
|
|
65
|
+
text = str(item.get(key, "")).strip()
|
|
72
66
|
if text:
|
|
73
67
|
segments.append({"source": source, "index": idx, "text": text})
|
|
74
68
|
return segments
|
|
75
69
|
|
|
76
70
|
|
|
77
|
-
def _load_json(path: Path) -> tuple[Any, str | None]:
|
|
78
|
-
try:
|
|
79
|
-
return json.loads(path.read_text(encoding="utf-8")), None
|
|
80
|
-
except Exception as exc: # intentionally broad: malformed artifacts are QC findings
|
|
81
|
-
return None, str(exc)
|
|
82
|
-
|
|
83
|
-
|
|
84
71
|
def _load_original_subtitles(work_dir: Path | None) -> list[dict[str, Any]]:
|
|
85
72
|
if work_dir is None:
|
|
86
73
|
return []
|
|
87
74
|
path = work_dir / "original_subtitles.json"
|
|
88
75
|
if not path.exists():
|
|
89
76
|
return []
|
|
90
|
-
|
|
91
|
-
if err:
|
|
92
|
-
return []
|
|
93
|
-
return _normalise_segments(data, source="original_subtitles")
|
|
77
|
+
return _normalise_segments(json.loads(path.read_text(encoding="utf-8")), source="original_subtitles")
|
|
94
78
|
|
|
95
79
|
|
|
96
80
|
def _style_card_requirement(work_dir: Path | None) -> tuple[bool, str]:
|
|
97
|
-
"""Read the explicit
|
|
98
|
-
|
|
99
|
-
Legacy/migration workspaces that do not have a valid requirements file are
|
|
100
|
-
advisory-only: they must not be hard-gated by prompt text in
|
|
101
|
-
agent_narration_brief.md.
|
|
102
|
-
"""
|
|
81
|
+
"""Read the explicit requirements contract; workspaces without one are advisory-only."""
|
|
103
82
|
if work_dir is None:
|
|
104
83
|
return False, "legacy_default"
|
|
105
84
|
path = work_dir / "deslop_qc_requirements.json"
|
|
106
85
|
if not path.exists():
|
|
107
86
|
return False, "legacy_default"
|
|
108
|
-
data
|
|
109
|
-
|
|
110
|
-
return False, "legacy_default"
|
|
111
|
-
required = data.get("style_card_required")
|
|
112
|
-
if not isinstance(required, bool):
|
|
113
|
-
return False, "legacy_default"
|
|
114
|
-
return required, "deslop_qc_requirements.json"
|
|
87
|
+
data = json.loads(path.read_text(encoding="utf-8"))
|
|
88
|
+
return data["style_card_required"], "deslop_qc_requirements.json"
|
|
115
89
|
|
|
116
90
|
|
|
117
91
|
def _style_card_issue(work_dir: Path | None, required: bool) -> dict[str, Any] | None:
|
|
118
92
|
if work_dir is None:
|
|
119
93
|
return None
|
|
94
|
+
severity = "blocker" if required else "advisory"
|
|
120
95
|
path = work_dir / "style_card.json"
|
|
121
96
|
if not path.exists():
|
|
122
97
|
return {
|
|
123
|
-
"severity":
|
|
98
|
+
"severity": severity,
|
|
124
99
|
"code": "missing_style_card",
|
|
125
100
|
"source": "style_card",
|
|
126
101
|
"index": None,
|
|
@@ -130,19 +105,18 @@ def _style_card_issue(work_dir: Path | None, required: bool) -> dict[str, Any] |
|
|
|
130
105
|
"style_card.json is absent; legacy/migration workspaces may continue, but new expression-special runs should author it"
|
|
131
106
|
),
|
|
132
107
|
}
|
|
133
|
-
data
|
|
134
|
-
if
|
|
108
|
+
data = json.loads(path.read_text(encoding="utf-8"))
|
|
109
|
+
if not isinstance(data, dict) or not data:
|
|
135
110
|
return {
|
|
136
|
-
"severity":
|
|
111
|
+
"severity": severity,
|
|
137
112
|
"code": "malformed_style_card",
|
|
138
113
|
"source": "style_card",
|
|
139
114
|
"index": None,
|
|
140
115
|
"message": (
|
|
141
|
-
"style_card.json is required but
|
|
116
|
+
"style_card.json is required but empty or not a JSON object"
|
|
142
117
|
if required else
|
|
143
|
-
"style_card.json is present but
|
|
118
|
+
"style_card.json is present but empty or not a JSON object; treat as migration warning unless the run requires it"
|
|
144
119
|
),
|
|
145
|
-
"detail": err,
|
|
146
120
|
}
|
|
147
121
|
return None
|
|
148
122
|
|
|
@@ -153,7 +127,7 @@ def _add_issue(bucket: list[dict[str, Any]], severity: str, code: str, source: s
|
|
|
153
127
|
bucket.append(issue)
|
|
154
128
|
|
|
155
129
|
|
|
156
|
-
def analyze_deslop_qc(narration: Any, *, work_dir: str | Path | None = None, original_subtitles: Any = None) -> dict[str, Any]:
|
|
130
|
+
def analyze_deslop_qc(narration: list[dict[str, Any]], *, work_dir: str | Path | None = None, original_subtitles: list[dict[str, Any]] | None = None) -> dict[str, Any]:
|
|
157
131
|
"""Return a structured report; never mutates or rewrites input text."""
|
|
158
132
|
work_path = Path(work_dir) if work_dir is not None else None
|
|
159
133
|
segments = _normalise_segments(narration, source="narration")
|
|
@@ -206,7 +180,7 @@ def analyze_deslop_qc(narration: Any, *, work_dir: str | Path | None = None, ori
|
|
|
206
180
|
|
|
207
181
|
long_segments = [seg for seg in segments if seg["source"] == "narration" and _text_units(seg["text"]) > 180]
|
|
208
182
|
if long_segments:
|
|
209
|
-
_add_issue(advisories, "advisory", "overlong_narration_block", "narration", long_segments[0]
|
|
183
|
+
_add_issue(advisories, "advisory", "overlong_narration_block", "narration", long_segments[0]["index"], "单个解说块过长,建议拆成更可听的段落", max_units=max(_text_units(seg["text"]) for seg in long_segments))
|
|
210
184
|
long_sentences = [s for s in sentences if _text_units(s) > 90]
|
|
211
185
|
if long_sentences:
|
|
212
186
|
_add_issue(advisories, "advisory", "long_paragraph", "narration", None, "长句/长段落偏多,字幕阅读压力较大", max_units=max(_text_units(s) for s in long_sentences))
|