@oh-story/dsh 0.1.12 → 0.1.13
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 +17 -11
- package/lib/client.js +85 -35
- package/lib/client.js.map +3 -3
- package/lib/drama/manifest.json +41 -36
- package/lib/drama/skills/short-drama/scripts/creator_markdown_check.py +340 -13
- package/lib/drama/skills/short-drama-assets/references/voice-direction.md +4 -2
- package/lib/drama/skills/short-drama-edit/SKILL.md +32 -12
- package/lib/drama/skills/short-drama-edit/references/still-cuts.md +90 -0
- package/lib/drama/skills/short-drama-edit/scripts/edit_tool.py +1035 -145
- package/lib/drama/skills/short-drama-edit/scripts/selftest.py +48 -0
- package/lib/drama/skills/short-drama-produce/SKILL.md +6 -1
- package/lib/drama/skills/short-drama-produce/references/adapter-contract.md +12 -0
- package/lib/drama/skills/short-drama-produce/scripts/production_tool.py +138 -9
- package/lib/drama/skills/short-drama-produce/scripts/provider_adapters.py +6 -0
- package/lib/drama/skills/short-drama-storyboard/SKILL.md +2 -0
- package/lib/drama/skills/short-drama-storyboard/references/production-shot-grammar.md +3 -1
- package/lib/drama/skills/short-drama-video-prompts/SKILL.md +7 -0
- package/lib/drama/skills/short-drama-video-prompts/references/performance-action-timing.md +14 -0
- package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.5.md +19 -6
- 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 +5 -0
- package/lib/drama/skills/short-drama-video-prompts/references/wan-3.0.md +4 -1
- package/lib/index.js +16 -7
- package/lib/index.js.map +2 -2
- package/lib/novel-to-game/README.md +98 -44
- package/lib/novel-to-game/README_ZH.md +100 -46
- package/lib/novel-to-game/VERSION +1 -1
- package/lib/novel-to-game/examples/jin-ping-mei/build/BUILD_BRIEF.md +27 -202
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/RUN.md +23 -115
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/chamber.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/court_night.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/court_rain.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/garden.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/hall.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/kitchen.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/street.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/street_dawn.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/temple.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/yue/moon.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/meng/angry.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/meng/blush.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/meng/neutral.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/meng/sad.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/meng/smile.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/meng/surprised.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pan/angry.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pan/blush.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pan/neutral.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pan/sad.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pan/smile.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pan/surprised.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pinger/angry.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pinger/blush.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pinger/neutral.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pinger/sad.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pinger/smile.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pinger/surprised.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/xuee/angry.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/xuee/blush.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/xuee/neutral.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/xuee/sad.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/xuee/smile.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/xuee/surprised.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/yue/angry.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/yue/blush.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/yue/neutral.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/yue/sad.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/yue/smile.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/yue/surprised.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/css/style.css +326 -2503
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/index.html +71 -4
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/adv.js +221 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/audio.js +113 -257
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/main.js +511 -3026
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/menus.js +277 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/petals.js +67 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/stage.js +180 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/store.js +70 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/common.js +198 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/common2.js +204 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/common3.js +198 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/dates.js +186 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/index.js +122 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/meng.js +190 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/pan.js +212 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/pinger.js +198 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/xuee.js +199 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/yue.js +214 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/package.json +2 -2
- package/lib/novel-to-game/examples/jin-ping-mei/build/art/generated-art.json +182 -45
- package/lib/novel-to-game/examples/jin-ping-mei/qa/README.md +7 -17
- package/lib/novel-to-game/examples/jin-ping-mei/qa/verification.json +9 -9
- package/lib/novel-to-game/manifest.json +303 -78
- package/lib/novel-to-game/skills/game-art-direction/references/art-direction-method.md +7 -2
- package/lib/novel-to-game/skills/game-concept/references/concept-method.md +2 -0
- package/lib/novel-to-game/skills/game-world-design/references/narrative-design-method.md +2 -0
- package/lib/novel-to-game/skills/game-world-design/references/world-design-method.md +9 -0
- package/lib/types/client/drama-production-view.d.ts +11 -0
- package/lib/types/client/drama-production-view.d.ts.map +1 -1
- package/lib/types/client/file-activity.d.ts +1 -1
- package/lib/types/client/file-activity.d.ts.map +1 -1
- package/lib/types/client/production-prompts.d.ts +7 -1
- package/lib/types/client/production-prompts.d.ts.map +1 -1
- package/lib/types/client/production-runtime.d.ts +7 -2
- package/lib/types/client/production-runtime.d.ts.map +1 -1
- package/lib/types/drama-adapters.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/video-recap/CHANGELOG.md +108 -1
- package/lib/video-recap/README.en.md +18 -14
- package/lib/video-recap/README.md +18 -14
- package/lib/video-recap/manifest.json +297 -321
- package/lib/video-recap/skills/video-assemble/SKILL.md +18 -16
- package/lib/video-recap/skills/video-assemble/references/explicit-audio-mix.md +3 -1
- package/lib/video-recap/skills/video-assemble/references/jianying/SOURCE.md +5 -5
- package/lib/video-recap/skills/video-assemble/references/packaging.md +4 -4
- package/lib/video-recap/skills/video-assemble/references/subtitle-track.md +10 -10
- package/lib/video-recap/skills/video-assemble/scripts/adoption/audio_mix_binding.py +13 -2
- package/lib/video-recap/skills/video-assemble/scripts/adoption/av_clock.py +109 -0
- package/lib/video-recap/skills/video-assemble/scripts/adoption/strict_publish.py +4 -1
- package/lib/video-recap/skills/video-assemble/scripts/artifacts.py +2 -9
- package/lib/video-recap/skills/video-assemble/scripts/assemble.py +188 -51
- package/lib/video-recap/skills/video-assemble/scripts/assemble_constants.py +0 -1
- package/lib/video-recap/skills/video-assemble/scripts/assembly_contract.py +32 -75
- package/lib/video-recap/skills/video-assemble/scripts/assembly_settings.py +8 -15
- package/lib/video-recap/skills/video-assemble/scripts/audio_mix.py +202 -161
- package/lib/video-recap/skills/video-assemble/scripts/codec_peak.py +112 -0
- package/lib/video-recap/skills/video-assemble/scripts/export_jianying.py +10 -101
- package/lib/video-recap/skills/video-assemble/scripts/jianying/builders.py +44 -475
- package/lib/video-recap/skills/video-assemble/scripts/jianying/model.py +1 -22
- package/lib/video-recap/skills/video-assemble/scripts/jianying/templates.py +0 -3
- package/lib/video-recap/skills/video-assemble/scripts/jianying/timeline_contract.py +36 -103
- package/lib/video-recap/skills/video-assemble/scripts/jianying/tracks.py +0 -8
- package/lib/video-recap/skills/video-assemble/scripts/jianying/writer.py +28 -203
- package/lib/video-recap/skills/video-assemble/scripts/lib.py +20 -34
- package/lib/video-recap/skills/video-assemble/scripts/loudness.py +332 -0
- package/lib/video-recap/skills/video-assemble/scripts/media.py +158 -38
- package/lib/video-recap/skills/video-assemble/scripts/narration_audio.py +30 -16
- package/lib/video-recap/skills/video-assemble/scripts/render_preflight.py +49 -8
- package/lib/video-recap/skills/video-assemble/scripts/source_subtitles.py +17 -6
- package/lib/video-recap/skills/video-assemble/scripts/subtitles/core.py +24 -2
- package/lib/video-recap/skills/video-assemble/scripts/subtitles/track.py +2 -5
- package/lib/video-recap/skills/video-assemble/scripts/subtitles/track_binding.py +2 -9
- package/lib/video-recap/skills/video-assemble/scripts/timeline.py +12 -34
- package/lib/video-recap/skills/video-assemble/scripts/timeline_emit.py +3 -4
- package/lib/video-recap/skills/video-assemble/scripts/visual_render.py +28 -2
- package/lib/video-recap/skills/video-cut/SKILL.md +37 -11
- package/lib/video-recap/skills/video-cut/references/shot-review.md +1 -1
- package/lib/video-recap/skills/video-cut/scripts/cut.py +2 -34
- package/lib/video-recap/skills/video-cut/scripts/cut_cli.py +96 -124
- package/lib/video-recap/skills/video-cut/scripts/cut_contract.py +97 -182
- package/lib/video-recap/skills/video-cut/scripts/cut_render.py +177 -166
- package/lib/video-recap/skills/video-cut/scripts/frame_grid.py +249 -0
- package/lib/video-recap/skills/video-cut/scripts/lib.py +0 -3
- package/lib/video-recap/skills/video-cut/scripts/media_geometry.py +173 -7
- package/lib/video-recap/skills/video-cut/scripts/sentence_boundaries.py +234 -151
- package/lib/video-recap/skills/video-cut/scripts/sentence_gate.py +189 -0
- package/lib/video-recap/skills/video-cut/scripts/shot_review.py +2 -6
- package/lib/video-recap/skills/video-recap/SKILL.md +24 -27
- package/lib/video-recap/skills/video-recap/references/audio-routing.md +8 -6
- package/lib/video-recap/skills/video-recap/references/config-playbook.md +8 -11
- package/lib/video-recap/skills/video-recap/references/data-schema.md +131 -74
- package/lib/video-recap/skills/video-recap/references/resource-library.md +17 -1
- package/lib/video-recap/skills/video-recap/scripts/doctor.py +51 -268
- package/lib/video-recap/skills/video-recap/scripts/final_qc.py +89 -254
- package/lib/video-recap/skills/video-recap/scripts/lib.py +81 -70
- package/lib/video-recap/skills/video-recap/scripts/library.py +40 -4
- package/lib/video-recap/skills/video-recap/scripts/materials.py +13 -80
- package/lib/video-recap/skills/video-recap/scripts/recap_cli.py +49 -17
- package/lib/video-recap/skills/video-recap/scripts/recap_inspect.py +1 -3
- package/lib/video-recap/skills/video-recap/scripts/recap_runner.py +86 -116
- package/lib/video-recap/skills/video-recap/scripts/recap_runtime.py +87 -23
- package/lib/video-recap/skills/video-recap/scripts/recap_source.py +0 -39
- package/lib/video-recap/skills/video-recap/scripts/recap_stage_qc.py +37 -135
- package/lib/video-recap/skills/video-recap/scripts/recap_timeline.py +118 -127
- package/lib/video-recap/skills/video-recap/scripts/resources/__init__.py +4 -0
- package/lib/video-recap/skills/video-recap/scripts/{resource_lock.py → resources/lock.py} +1 -1
- package/lib/video-recap/skills/video-recap/scripts/{project_binding.py → resources/project_binding.py} +78 -17
- package/lib/video-recap/skills/video-reference/SKILL.md +108 -0
- package/lib/video-recap/skills/video-reference/references/reference-schema.md +139 -0
- package/lib/video-recap/skills/video-reference/scripts/lib.py +85 -0
- package/lib/video-recap/skills/video-reference/scripts/reference.py +96 -0
- package/lib/video-recap/skills/video-reference/scripts/reference_check.py +553 -0
- package/lib/video-recap/skills/video-reference/scripts/reference_frames.py +122 -0
- package/lib/video-recap/skills/video-reference/scripts/reference_measure.py +253 -0
- package/lib/video-recap/skills/video-reference/scripts/reference_profile.py +271 -0
- package/lib/video-recap/skills/video-script/SKILL.md +9 -7
- package/lib/video-recap/skills/video-script/scripts/agent_text.py +18 -267
- package/lib/video-recap/skills/video-script/scripts/deslop_qc.py +12 -30
- package/lib/video-recap/skills/video-script/scripts/entry_suggestion.py +125 -0
- package/lib/video-recap/skills/video-script/scripts/evidence_bundle.py +2 -26
- package/lib/video-recap/skills/video-script/scripts/lib.py +5 -15
- package/lib/video-recap/skills/video-script/scripts/lint_summary.py +184 -0
- package/lib/video-recap/skills/video-script/scripts/narration_lint.py +120 -100
- package/lib/video-recap/skills/video-script/scripts/review.py +3 -34
- package/lib/video-recap/skills/video-script/scripts/review_grounding.py +20 -46
- package/lib/video-recap/skills/video-script/scripts/review_response.py +120 -258
- package/lib/video-recap/skills/video-script/scripts/review_runner.py +2 -10
- package/lib/video-recap/skills/video-script/scripts/speech_ownership.py +89 -5
- package/lib/video-recap/skills/video-script/scripts/validate.py +26 -34
- package/lib/video-recap/skills/video-understanding/SKILL.md +29 -4
- package/lib/video-recap/skills/video-understanding/references/data-schema.md +5 -22
- package/lib/video-recap/skills/video-understanding/scripts/agent_text.py +24 -121
- package/lib/video-recap/skills/video-understanding/scripts/asr.py +11 -5
- package/lib/video-recap/skills/video-understanding/scripts/asr_timing_evidence.py +15 -24
- package/lib/video-recap/skills/video-understanding/scripts/briefing/builder.py +59 -25
- package/lib/video-recap/skills/video-understanding/scripts/briefing/context.py +39 -3
- package/lib/video-recap/skills/video-understanding/scripts/briefing/inputs.py +15 -27
- package/lib/video-recap/skills/video-understanding/scripts/briefing/timeline.py +136 -37
- package/lib/video-recap/skills/video-understanding/scripts/consolidate.py +121 -37
- package/lib/video-recap/skills/video-understanding/scripts/detect.py +118 -16
- package/lib/video-recap/skills/video-understanding/scripts/index_normalize.py +359 -0
- package/lib/video-recap/skills/video-understanding/scripts/lib.py +62 -21
- package/lib/video-recap/skills/video-understanding/scripts/storyboard.py +181 -51
- package/lib/video-recap/skills/video-understanding/scripts/timeline_fusion.py +0 -26
- package/lib/video-recap/skills/video-understanding/scripts/understanding_brief.py +37 -14
- package/lib/video-recap/skills/video-understanding/scripts/understanding_cache.py +40 -32
- package/lib/video-recap/skills/video-understanding/scripts/understanding_runner.py +100 -127
- package/lib/video-recap/skills/video-understanding/scripts/understanding_storyboard.py +182 -30
- package/lib/video-recap/skills/video-understanding/scripts/vlm.py +30 -27
- package/lib/video-recap/skills/video-voiceover/SKILL.md +19 -12
- package/lib/video-recap/skills/video-voiceover/references/index-tts.md +3 -3
- package/lib/video-recap/skills/video-voiceover/scripts/approved_text_policy.py +3 -1
- package/lib/video-recap/skills/video-voiceover/scripts/dub.py +60 -135
- package/lib/video-recap/skills/video-voiceover/scripts/lib.py +9 -34
- package/lib/video-recap/skills/video-voiceover/scripts/providers/fish_audio.py +6 -1
- package/lib/video-recap/skills/video-voiceover/scripts/tts_audio.py +64 -1
- package/lib/video-recap/skills/video-voiceover/scripts/tts_cache.py +99 -0
- package/lib/video-recap/skills/video-voiceover/scripts/voiceover.py +177 -200
- package/package.json +45 -45
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/inner_court_accord_five.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/public_day15.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/household/li_jiaoer.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/css/new-guofeng.css +0 -1494
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/assets.js +0 -147
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/data.js +0 -7470
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/engine.js +0 -13066
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/text.js +0 -32
- package/lib/novel-to-game/examples/jin-ping-mei/qa/playable-model.json +0 -99
- package/lib/novel-to-game/examples/jin-ping-mei/qa/verify_playable_model.mjs +0 -152
- package/lib/video-recap/skills/video-assemble/references/foreground-compose.md +0 -118
- package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_combination_segment.json +0 -84
- package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_combination_video_material.json +0 -98
- package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_draft.json +0 -204
- package/lib/video-recap/skills/video-assemble/references/pair-media.md +0 -74
- package/lib/video-recap/skills/video-assemble/scripts/compose_foreground.py +0 -293
- package/lib/video-recap/skills/video-assemble/scripts/pair_media.py +0 -216
- package/lib/video-recap/skills/video-recap/references/shift-left-qc-schema.md +0 -23
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc.py +0 -33
- package/lib/video-recap/skills/video-recap/scripts/qc/__init__.py +0 -1
- package/lib/video-recap/skills/video-recap/scripts/qc/mimo_client.py +0 -13
- package/lib/video-recap/skills/video-recap/scripts/qc/mimo_contract.py +0 -9
- package/lib/video-recap/skills/video-recap/scripts/qc/mimo_evidence.py +0 -287
- package/lib/video-recap/skills/video-recap/scripts/qc/mimo_observations.py +0 -175
- package/lib/video-recap/skills/video-recap/scripts/qc/mimo_payload.py +0 -206
- package/lib/video-recap/skills/video-recap/scripts/qc/mimo_report.py +0 -318
- package/lib/video-recap/skills/video-recap/scripts/qc/mimo_runner.py +0 -130
- package/lib/video-recap/skills/video-recap/scripts/qc_contract.py +0 -320
- package/lib/video-recap/skills/video-script/scripts/timeline_fusion.py +0 -108
- package/lib/video-recap/skills/video-understanding/scripts/brief.py +0 -9
- package/lib/video-recap/skills/video-understanding/scripts/deslop_qc.py +0 -211
- package/lib/video-recap/skills/video-understanding/scripts/narration_lint.py +0 -707
- package/lib/video-recap/skills/video-understanding/scripts/speech_ownership.py +0 -143
- /package/lib/video-recap/skills/video-cut/scripts/{narration_mapping.py → cut_qc.py} +0 -0
|
@@ -23,7 +23,7 @@ import stat
|
|
|
23
23
|
import sys
|
|
24
24
|
from pathlib import Path
|
|
25
25
|
|
|
26
|
-
from
|
|
26
|
+
from lib import file_identity
|
|
27
27
|
|
|
28
28
|
LIBRARY_ENV = "VIDEO_RECAP_MATERIAL_LIBRARY_DIR"
|
|
29
29
|
LIBRARY_SCHEMA = "video-recap.library.v1"
|
|
@@ -39,7 +39,14 @@ RESOURCE_EXTS = {
|
|
|
39
39
|
"font": {".ttf", ".otf", ".ttc"},
|
|
40
40
|
"image": {".png", ".jpg", ".jpeg", ".webp"},
|
|
41
41
|
}
|
|
42
|
-
TEMPLATE_KINDS = ("subtitle_style", "packaging")
|
|
42
|
+
TEMPLATE_KINDS = ("subtitle_style", "packaging", "production_reference")
|
|
43
|
+
# A production_reference template points at an export of the on-demand reference skill. This is
|
|
44
|
+
# a separate shape check (no import of that skill): methods only, never the source's facts.
|
|
45
|
+
REFERENCE_FILE = "production_reference.json"
|
|
46
|
+
REFERENCE_SCHEMA = "video-reference.production.v1"
|
|
47
|
+
REFERENCE_DIMENSIONS = ("narrative_structure", "pacing", "shots_editing", "narration_subtitles", "audio_visual")
|
|
48
|
+
# Same banned keys as the reference export's own re-scan: source facts plus the measurement pointers.
|
|
49
|
+
REFERENCE_FACT_KEYS = {"source_facts", "labels", "evidence", "entities", "statement", "from", "path"}
|
|
43
50
|
LICENSE_STATUSES = ("unknown", "owned", "licensed", "restricted")
|
|
44
51
|
CONSENT_STATUSES = ("unknown", "granted", "denied")
|
|
45
52
|
TEMPLATE_STATUSES = ("draft", "adopted", "retired")
|
|
@@ -90,7 +97,7 @@ class Report:
|
|
|
90
97
|
MAX_RECORD_BYTES = 2 * 1024 * 1024
|
|
91
98
|
|
|
92
99
|
|
|
93
|
-
def _load(path: Path, schema: str, keys: set, report: Report):
|
|
100
|
+
def _load(path: Path, schema: str, keys: set | None, report: Report):
|
|
94
101
|
try:
|
|
95
102
|
st = os.stat(path)
|
|
96
103
|
if not stat.S_ISREG(st.st_mode) or st.st_size > MAX_RECORD_BYTES:
|
|
@@ -106,7 +113,7 @@ def _load(path: Path, schema: str, keys: set, report: Report):
|
|
|
106
113
|
if data.get("schema") != schema:
|
|
107
114
|
report.error(path, "schema", f"schema 必须是 {schema}")
|
|
108
115
|
return None
|
|
109
|
-
unknown = sorted(set(data) - keys)
|
|
116
|
+
unknown = sorted(set(data) - keys) if keys is not None else []
|
|
110
117
|
if unknown:
|
|
111
118
|
report.error(path, "unknown_keys", f"未知字段: {', '.join(unknown)}")
|
|
112
119
|
return data
|
|
@@ -217,6 +224,33 @@ def _param_number(params, key):
|
|
|
217
224
|
return value if isinstance(value, (int, float)) and not isinstance(value, bool) and value > 0 else None
|
|
218
225
|
|
|
219
226
|
|
|
227
|
+
def _check_production_reference(path: Path, params: dict, report: Report):
|
|
228
|
+
"""Issues are filed on template.json, so a binding refuses the template when its file is bad."""
|
|
229
|
+
if params != {"reference": {"path": REFERENCE_FILE}}:
|
|
230
|
+
report.error(path, "reference_params",
|
|
231
|
+
f'production_reference 的 params 必须恰好是 {{"reference": {{"path": "{REFERENCE_FILE}"}}}}')
|
|
232
|
+
return
|
|
233
|
+
target = resolve_inside(path.parent, path.parent, REFERENCE_FILE)
|
|
234
|
+
if target is None or not target.is_file():
|
|
235
|
+
report.error(path, "file_missing", f"版本目录内没有 {REFERENCE_FILE}")
|
|
236
|
+
return
|
|
237
|
+
loaded = Report(report.root)
|
|
238
|
+
data = _load(target, REFERENCE_SCHEMA, None, loaded)
|
|
239
|
+
for item in loaded.errors:
|
|
240
|
+
report.error(path, item["code"], f"{REFERENCE_FILE}: {item['message']}")
|
|
241
|
+
if data is None:
|
|
242
|
+
return
|
|
243
|
+
methods = data.get("methods")
|
|
244
|
+
if not (isinstance(methods, list) and methods and all(
|
|
245
|
+
isinstance(m, dict) and m.get("dimension") in REFERENCE_DIMENSIONS and _nonempty_str(m.get("rule"))
|
|
246
|
+
for m in methods
|
|
247
|
+
)):
|
|
248
|
+
report.error(path, "methods", f"{REFERENCE_FILE} 的 methods 需要非空,每条有 {REFERENCE_DIMENSIONS} 之一的 dimension 与非空 rule")
|
|
249
|
+
leaked = sorted({key for _, node in _param_values(data) for key in node if key in REFERENCE_FACT_KEYS})
|
|
250
|
+
if leaked:
|
|
251
|
+
report.error(path, "source_facts", f"{REFERENCE_FILE} 只放方法,不能带原片事实字段: {', '.join(leaked)}")
|
|
252
|
+
|
|
253
|
+
|
|
220
254
|
def _check_template(path: Path, data: dict, report: Report) -> dict | None:
|
|
221
255
|
vdir = path.parent
|
|
222
256
|
tid, kind = vdir.parent.name, vdir.parent.parent.name
|
|
@@ -297,6 +331,8 @@ def _check_template(path: Path, data: dict, report: Report) -> dict | None:
|
|
|
297
331
|
_check_rect(path, f"params.layers[{index}].rect", layer.get("rect"), canvas, report)
|
|
298
332
|
if canvas and "safe_rect" in params:
|
|
299
333
|
_check_rect(path, "params.safe_rect", params["safe_rect"], canvas, report)
|
|
334
|
+
if kind == "production_reference":
|
|
335
|
+
_check_production_reference(path, params, report)
|
|
300
336
|
status = data.get("status")
|
|
301
337
|
if status not in TEMPLATE_STATUSES:
|
|
302
338
|
report.error(path, "status", f"status 必须是 {TEMPLATE_STATUSES} 之一")
|
|
@@ -16,14 +16,16 @@ written into scenes/ASR/VLM/summary JSON.
|
|
|
16
16
|
from __future__ import annotations
|
|
17
17
|
|
|
18
18
|
import json
|
|
19
|
-
import os
|
|
20
19
|
import re
|
|
21
20
|
import tempfile
|
|
22
21
|
from datetime import datetime, timezone
|
|
23
22
|
from pathlib import Path
|
|
24
23
|
|
|
25
|
-
from lib import load_json
|
|
24
|
+
from lib import load_json, material_id_for, read_json_object, source_id_for
|
|
26
25
|
|
|
26
|
+
# asr_timing_evidence.json stays out on purpose: it binds asr_result.json and audio.wav by
|
|
27
|
+
# {size, mtime_ns}, and a restore rewrites the former (redacted copy) and never copies the
|
|
28
|
+
# latter, so a restored sidecar could only ever read as MISSING_OR_STALE.
|
|
27
29
|
ALLOWED_ARTIFACTS = {
|
|
28
30
|
"scenes.json",
|
|
29
31
|
"asr_result.json",
|
|
@@ -35,10 +37,9 @@ ALLOWED_ARTIFACTS = {
|
|
|
35
37
|
"timeline_fusion.json",
|
|
36
38
|
"understanding_index.json",
|
|
37
39
|
"understanding_index.md",
|
|
40
|
+
"consolidation.status.json",
|
|
38
41
|
"agent_narration_brief.md",
|
|
39
42
|
"background_research.json",
|
|
40
|
-
"reference_profile.json",
|
|
41
|
-
"reference_match_report.json",
|
|
42
43
|
"recap_run_manifest.json",
|
|
43
44
|
}
|
|
44
45
|
# Redaction targets credential VALUE shapes, not English/Chinese dictionary words — the
|
|
@@ -67,30 +68,6 @@ def utc_now_iso() -> str:
|
|
|
67
68
|
return datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
|
|
68
69
|
|
|
69
70
|
|
|
70
|
-
def file_identity(path: str | Path) -> dict:
|
|
71
|
-
"""``{size, mtime_ns}`` of a file: the identity recap records and compares for a source
|
|
72
|
-
video or adopted artifact. A file rewritten in place gets a new mtime_ns."""
|
|
73
|
-
st = os.stat(os.fspath(path))
|
|
74
|
-
return {"size": st.st_size, "mtime_ns": st.st_mtime_ns}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
def _slug(text: str, max_len: int = 48) -> str:
|
|
78
|
-
raw = Path(text).stem.lower()
|
|
79
|
-
raw = re.sub(r"[^a-z0-9\u4e00-\u9fff._-]+", "-", raw).strip("-._")
|
|
80
|
-
return (raw or "material")[:max_len].strip("-._") or "material"
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
def _id_stem(source_path: str | Path, max_len: int = 32) -> str:
|
|
84
|
-
raw = re.sub(r"[^a-z0-9]+", "-", Path(source_path).stem.lower()).strip("-")
|
|
85
|
-
return (raw or "source")[:max_len].strip("-") or "source"
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
def source_id_for(source_path: str | Path) -> str:
|
|
89
|
-
"""``src_<stem>_<size>``: readable, stable across runs, and distinct for a different cut
|
|
90
|
-
of the same title (the size changes)."""
|
|
91
|
-
return f"src_{_id_stem(source_path)}_{os.stat(os.fspath(source_path)).st_size}"
|
|
92
|
-
|
|
93
|
-
|
|
94
71
|
def assign_source_ids(sources: list[dict]) -> list[dict]:
|
|
95
72
|
"""Assign deterministic source_id values to manifest source records.
|
|
96
73
|
|
|
@@ -115,10 +92,6 @@ def assign_source_ids(sources: list[dict]) -> list[dict]:
|
|
|
115
92
|
return assigned
|
|
116
93
|
|
|
117
94
|
|
|
118
|
-
def material_id_for(source_path: str | Path, source_identity: dict) -> str:
|
|
119
|
-
return f"{_slug(str(source_path))}-{source_identity['size']}"
|
|
120
|
-
|
|
121
|
-
|
|
122
95
|
def material_dir(library_dir: str | Path, material_id: str) -> Path:
|
|
123
96
|
return Path(library_dir) / "materials" / material_id
|
|
124
97
|
|
|
@@ -207,16 +180,8 @@ def write_material_md(path: Path, metadata: dict, summary: str, tags: list[str])
|
|
|
207
180
|
path.write_text(text, encoding="utf-8")
|
|
208
181
|
|
|
209
182
|
|
|
210
|
-
def _read_material_metadata(path: Path) -> dict | None:
|
|
211
|
-
try:
|
|
212
|
-
data = load_json(path)
|
|
213
|
-
except (OSError, ValueError):
|
|
214
|
-
return None
|
|
215
|
-
return data if isinstance(data, dict) else None
|
|
216
|
-
|
|
217
|
-
|
|
218
183
|
def _material_cache_entry(path: Path) -> dict | None:
|
|
219
|
-
data =
|
|
184
|
+
data = read_json_object(path)
|
|
220
185
|
if not data or not all(data.get(key) for key in (
|
|
221
186
|
"source_path", "updated_at", "material_id",
|
|
222
187
|
)) or not isinstance(data.get("artifacts"), list):
|
|
@@ -261,7 +226,7 @@ def save_material(
|
|
|
261
226
|
copied.append({"name": src.name, "path": f"artifacts/{src.name}", "bytes": dst.stat().st_size})
|
|
262
227
|
|
|
263
228
|
meta_path = dest / "material.json"
|
|
264
|
-
previous =
|
|
229
|
+
previous = read_json_object(meta_path)
|
|
265
230
|
created_at = previous.get("created_at") if previous else None
|
|
266
231
|
if not isinstance(created_at, str) or not created_at:
|
|
267
232
|
created_at = now
|
|
@@ -301,32 +266,6 @@ def save_material(
|
|
|
301
266
|
return metadata
|
|
302
267
|
|
|
303
268
|
|
|
304
|
-
def find_material_by_source(
|
|
305
|
-
library_dir: str | Path, source_path: str | Path, source_identity: dict
|
|
306
|
-
) -> dict | None:
|
|
307
|
-
"""The newest material saved for this exact source file (resolved path + identity)."""
|
|
308
|
-
root = Path(library_dir) / "materials"
|
|
309
|
-
if not root.exists():
|
|
310
|
-
return None
|
|
311
|
-
resolved = str(Path(source_path).resolve())
|
|
312
|
-
candidates = []
|
|
313
|
-
for meta_path in root.glob("*/material.json"):
|
|
314
|
-
data = _material_cache_entry(meta_path)
|
|
315
|
-
if (
|
|
316
|
-
data is not None
|
|
317
|
-
and data["source_path"] == resolved
|
|
318
|
-
and data["source_video_identity"] == source_identity
|
|
319
|
-
):
|
|
320
|
-
data["material_dir"] = str(meta_path.parent)
|
|
321
|
-
candidates.append(data)
|
|
322
|
-
if not candidates:
|
|
323
|
-
return None
|
|
324
|
-
# Deterministic fallback policy for legacy/manual callers that do not know
|
|
325
|
-
# the expected material_id: newest wins, then material_id for stable ties.
|
|
326
|
-
candidates.sort(key=lambda d: (d["updated_at"], d["material_id"]), reverse=True)
|
|
327
|
-
return candidates[0]
|
|
328
|
-
|
|
329
|
-
|
|
330
269
|
def restore_material(
|
|
331
270
|
library_dir: str | Path,
|
|
332
271
|
work_dir: str | Path,
|
|
@@ -334,7 +273,7 @@ def restore_material(
|
|
|
334
273
|
source_path: str | Path,
|
|
335
274
|
source_identity: dict,
|
|
336
275
|
settings: dict,
|
|
337
|
-
material_id: str
|
|
276
|
+
material_id: str,
|
|
338
277
|
overwrite: bool = True,
|
|
339
278
|
prune_stale_allowed: bool = True,
|
|
340
279
|
) -> dict:
|
|
@@ -349,17 +288,11 @@ def restore_material(
|
|
|
349
288
|
newly restored material. Non-allowed files (for example narration.json or
|
|
350
289
|
clip_plan.json) are never removed here.
|
|
351
290
|
"""
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
return {"restored": False, "reason": "material missing or invalid"}
|
|
358
|
-
meta["material_dir"] = str(meta_path.parent)
|
|
359
|
-
else:
|
|
360
|
-
meta = find_material_by_source(lib, source_path, source_identity)
|
|
361
|
-
if meta is None:
|
|
362
|
-
return {"restored": False, "reason": "material not found"}
|
|
291
|
+
meta_path = material_dir(Path(library_dir), material_id) / "material.json"
|
|
292
|
+
meta = _material_cache_entry(meta_path)
|
|
293
|
+
if meta is None:
|
|
294
|
+
return {"restored": False, "reason": "material missing or invalid"}
|
|
295
|
+
meta["material_dir"] = str(meta_path.parent)
|
|
363
296
|
if meta["source_path"] != str(Path(source_path).resolve()) \
|
|
364
297
|
or meta["source_video_identity"] != source_identity:
|
|
365
298
|
return {"restored": False, "reason": "source identity mismatch", "material_id": meta["material_id"]}
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
import argparse
|
|
4
4
|
import os
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
5
7
|
|
|
6
|
-
from lib import
|
|
8
|
+
from lib import TTS_PROVIDERS
|
|
7
9
|
from recap_source import AUDIO_MODES
|
|
8
10
|
|
|
9
|
-
TTS_PROVIDERS = ("auto", "mimo-tts", "fish-audio", "index-tts")
|
|
10
|
-
|
|
11
11
|
|
|
12
12
|
class _RecordExplicit:
|
|
13
13
|
"""Record the option argparse actually consumed, not the raw argv spelling."""
|
|
@@ -35,6 +35,46 @@ def _record_explicit_options(parser):
|
|
|
35
35
|
)
|
|
36
36
|
|
|
37
37
|
|
|
38
|
+
# Options whose value names a file or directory; the resume command makes them absolute.
|
|
39
|
+
_PATH_DESTS = frozenset({
|
|
40
|
+
"work_dir", "output_dir", "voice_ref", "material_library_dir", "project",
|
|
41
|
+
"tts_meta", "narration_adoption", "audio_mix_adoption",
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _absolute(value):
|
|
46
|
+
return str(Path(value).expanduser().resolve()) if value else value
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _replayable_argv(parser, argv):
|
|
50
|
+
"""The argv as typed, with videos and path values made absolute so it replays from any cwd.
|
|
51
|
+
|
|
52
|
+
Runs only after a successful parse, so every option token is an exact option string
|
|
53
|
+
(allow_abbrev=False) and every value-taking option is followed by its value.
|
|
54
|
+
"""
|
|
55
|
+
options = {name: action for action in parser._actions for name in action.option_strings}
|
|
56
|
+
out, tokens, positional_only = [], iter(argv), False
|
|
57
|
+
for token in tokens:
|
|
58
|
+
if positional_only or not token.startswith("-"):
|
|
59
|
+
out.append(_absolute(token))
|
|
60
|
+
continue
|
|
61
|
+
if token == "--":
|
|
62
|
+
positional_only = True
|
|
63
|
+
out.append(token)
|
|
64
|
+
continue
|
|
65
|
+
name, inline, value = token.partition("=")
|
|
66
|
+
action = options[name]
|
|
67
|
+
if action.nargs == 0:
|
|
68
|
+
out.append(token)
|
|
69
|
+
continue
|
|
70
|
+
fix = _absolute if action.dest in _PATH_DESTS else str
|
|
71
|
+
if inline:
|
|
72
|
+
out.append(f"{name}={fix(value)}")
|
|
73
|
+
else:
|
|
74
|
+
out += [token, fix(next(tokens))]
|
|
75
|
+
return out
|
|
76
|
+
|
|
77
|
+
|
|
38
78
|
def parse_args(argv=None):
|
|
39
79
|
parser = argparse.ArgumentParser(
|
|
40
80
|
description="Full video recap orchestrator (video-* skill bundle).",
|
|
@@ -102,7 +142,9 @@ def parse_args(argv=None):
|
|
|
102
142
|
"--burn-subtitles",
|
|
103
143
|
action=argparse.BooleanOptionalAction,
|
|
104
144
|
default=None,
|
|
105
|
-
help="burn narration subtitles into the video (default on;
|
|
145
|
+
help="burn narration subtitles into the video (default on; without libass the default "
|
|
146
|
+
"delivers a .srt sidecar instead, while an explicit --burn-subtitles fails; "
|
|
147
|
+
"--no-burn-subtitles to disable)",
|
|
106
148
|
)
|
|
107
149
|
voice.add_argument(
|
|
108
150
|
"--subtitle-y-top",
|
|
@@ -143,22 +185,10 @@ def parse_args(argv=None):
|
|
|
143
185
|
action="store_true",
|
|
144
186
|
help="make narration review a strict pre-TTS gate (also REQUIRE_NARRATION_REVIEW=1)",
|
|
145
187
|
)
|
|
146
|
-
review.add_argument(
|
|
147
|
-
"--mimo-qc",
|
|
148
|
-
default=os.environ.get("MIMO_QC", "off"),
|
|
149
|
-
choices=["off", "pre-assemble", "post-render", "both"],
|
|
150
|
-
help="optional advisory MiMo QC stage(s); never blocks the pipeline",
|
|
151
|
-
)
|
|
152
|
-
review.add_argument(
|
|
153
|
-
"--mimo-qc-refresh",
|
|
154
|
-
action="store_true",
|
|
155
|
-
default=env_bool("MIMO_QC_REFRESH", False),
|
|
156
|
-
help="ignore a matching MiMo QC stage cache",
|
|
157
|
-
)
|
|
158
188
|
review.add_argument(
|
|
159
189
|
"--require-final-qc",
|
|
160
190
|
action="store_true",
|
|
161
|
-
help="full/cut: require literal passing final_qc
|
|
191
|
+
help="full/cut: require a literal passing final_qc summary",
|
|
162
192
|
)
|
|
163
193
|
review.add_argument(
|
|
164
194
|
"--export-jianying",
|
|
@@ -204,6 +234,8 @@ def parse_args(argv=None):
|
|
|
204
234
|
selfcheck.add_argument("--doctor", action="store_true")
|
|
205
235
|
|
|
206
236
|
_record_explicit_options(parser)
|
|
237
|
+
argv = sys.argv[1:] if argv is None else [str(token) for token in argv]
|
|
207
238
|
args = parser.parse_args(argv)
|
|
208
239
|
args._explicit_options = frozenset(getattr(args, "_explicit_options", ()))
|
|
240
|
+
args._argv = _replayable_argv(parser, argv)
|
|
209
241
|
return parser, args
|
|
@@ -21,9 +21,7 @@ import argparse
|
|
|
21
21
|
import json
|
|
22
22
|
from pathlib import Path
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
def load_json(path):
|
|
26
|
-
return json.loads(Path(path).read_text(encoding="utf-8"))
|
|
24
|
+
from lib import load_json
|
|
27
25
|
|
|
28
26
|
|
|
29
27
|
# --- artifact catalog --------------------------------------------------------
|