@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
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# 制作参考:字段、枚举与示例
|
|
2
|
+
|
|
3
|
+
本文件只列字段与枚举;剧作与剪辑手法的具体规则以后续写稿阶段自己的手册为准,这里不复述。
|
|
4
|
+
|
|
5
|
+
## 标注词表
|
|
6
|
+
|
|
7
|
+
| 字段 | 取值 |
|
|
8
|
+
|---|---|
|
|
9
|
+
| `audio_spans[].owner` | `narration` · `original_dialogue` · `action_sound` · `ambience` · `music` · `silence` |
|
|
10
|
+
| `audio_spans[].narration_job`(可选,仅 narration 段) | `context` · `causal_link` · `foreshadow` · `interpretation` · `transition` |
|
|
11
|
+
| `sections[].function`(可重复出现) | `hook` · `setup` · `turn` · `escalation` · `payoff` |
|
|
12
|
+
| `dimension` | `narrative_structure` · `pacing` · `shots_editing` · `narration_subtitles` · `audio_visual` |
|
|
13
|
+
| `applies_to` | `story_plan` · `visual_audio_board` · `clip_plan` · `narration` · `style_card` |
|
|
14
|
+
|
|
15
|
+
`audio_spans` 记录**谁占有这段声音**(对白下压着的背景乐不算主导;原片自带的画外音、内心独白记 `original_dialogue`);`sections` 记录叙事功能。两者都必须按时间排序、
|
|
16
|
+
不重叠、间隙不超过 0.5 秒、覆盖整片。
|
|
17
|
+
|
|
18
|
+
## `reference_breakdown.json`(Agent 唯一要写的文件)
|
|
19
|
+
|
|
20
|
+
顶层只允许 `schema`、`labels`、`source_facts`、`methods`、`skipped_dimensions`;每一层的键都是封闭集合。
|
|
21
|
+
|
|
22
|
+
```json
|
|
23
|
+
{
|
|
24
|
+
"schema": "video-reference.breakdown.v1",
|
|
25
|
+
"labels": {
|
|
26
|
+
"audio_spans": [
|
|
27
|
+
{"start": 0, "end": 13.6, "owner": "narration", "narration_job": "context"},
|
|
28
|
+
{"start": 13.6, "end": 30.6, "owner": "original_dialogue"}
|
|
29
|
+
],
|
|
30
|
+
"sections": [{"start": 0, "end": 30.6, "function": "hook"}],
|
|
31
|
+
"subtitles": {"burned": true, "max_lines": 1, "marks_original": "「」", "evidence_t": [12.0]},
|
|
32
|
+
"basis": "5s ASR 窗口 + 故事板;切点经 frames --review / --longest 5 复核",
|
|
33
|
+
"cut_fixes": {"add": [10.8], "remove": []}
|
|
34
|
+
},
|
|
35
|
+
"source_facts": [
|
|
36
|
+
{"id": "f1", "dimension": "narrative_structure", "statement": "开场旁白交代主角身世后切入原声冲突",
|
|
37
|
+
"t": [0, 13.6], "entities": ["<原片人名>"]},
|
|
38
|
+
{"id": "f2", "dimension": "pacing", "statement": "原声段沿用原片较密的切点,旁白段切得更少",
|
|
39
|
+
"measure": ["derived.by_owner.original_dialogue.cuts_per_min", "derived.by_owner.narration.cuts_per_min"],
|
|
40
|
+
"entities": []}
|
|
41
|
+
],
|
|
42
|
+
"methods": [
|
|
43
|
+
{"id": "m1", "dimension": "audio_visual",
|
|
44
|
+
"rule": "原声段保留原片自身的剪辑节奏,旁白段换成较长的镜头承载解说,让注意力留在声音上",
|
|
45
|
+
"applies_when": "对白冲突强、需旁白补前情的剧集解说", "avoid_when": "主要靠动作声承载的段落",
|
|
46
|
+
"applies_to": "visual_audio_board", "evidence": ["f2", "measure:derived.switch_on_cut_share"],
|
|
47
|
+
"targets": {"narration_cuts_per_min": {"from": "derived.by_owner.narration.cuts_per_min"}}}
|
|
48
|
+
],
|
|
49
|
+
"skipped_dimensions": {"narration_subtitles": "ASR 不可用,未测语速"}
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
- `cut_fixes` 可选,键只有 `add`、`remove`(秒数列表):`remove` 的每个值必须在某个测得切点 ±0.1 秒内,`add` 的值不得在测得切点
|
|
54
|
+
±0.1 秒内。写了(哪怕是 `{}`)就表示看过待复核窗口;有增删时所有 `shots.*` 与切点相关的 `derived.*` 都按复核后的切点重算。
|
|
55
|
+
- `subtitles` 可选:`burned`(布尔)、`max_lines`(≥1 整数)必填,`marks_original`(字符串)、`evidence_t`(秒数列表)可选。
|
|
56
|
+
- `skipped_dimensions` 的值是一句非空的原因;它会原样导出,同样受 R8 泄漏扫描。
|
|
57
|
+
- fact:`id`、`dimension`、`statement`、`entities` 必填,`t` 与 `measure` 二选一。`entities` 写这条事实涉及的人名、地名、
|
|
58
|
+
组织名,没有就写空列表——它同时是泄漏扫描的词表。
|
|
59
|
+
- method:`id`、`dimension`、`rule`、`applies_to`、`evidence` 必填;`applies_when`、`avoid_when`、`targets` 可选。
|
|
60
|
+
- target:只写 `from`,路径根只能是 `shots`、`loudness` 或 `derived`,不得带列表下标;解析结果必须是数值叶子,或以下派生对象之一:
|
|
61
|
+
`derived.structure`、`derived.first_original_at`(只导出 `{fraction}`)、`derived.narration_jobs`、`derived.by_owner.<owner>`、
|
|
62
|
+
`derived.by_section.<function>`。`shots.cuts`、`shots.curve`、`loudness.short_term_1s` 这类逐时刻序列不能做 target。
|
|
63
|
+
- fact 的 `measure` 与 method 的 `measure:` 证据也只认这三个根,且不能落在字符串值上。
|
|
64
|
+
|
|
65
|
+
## 测量与派生值
|
|
66
|
+
|
|
67
|
+
`reference_measurements.json` 由 measure 写(`settings` 记切点规则参数,`scdet_scores` 是 ≥2 分的逐帧分数缓存,`mafd_peaks` 是帧差单侧峰,三者都不能被引用),
|
|
68
|
+
路径即 target / evidence 可引用的 `shots.*`、`loudness.*`(有 `cut_fixes` 时按复核后的切点):
|
|
69
|
+
|
|
70
|
+
| 路径 | 含义 |
|
|
71
|
+
|---|---|
|
|
72
|
+
| `shots.cuts` · `count` · `mean_s` · `median_s` · `p10_s` · `p90_s` | 切点时间与镜长分布 |
|
|
73
|
+
| `shots.review_windows` | 被压下的疑似切点与不在切点上的帧差单侧峰,合成窗口 `[[开始, 结束], …]`,给 `frames --review` 用 |
|
|
74
|
+
| `shots.share_under_1s` · `share_over_8s` · `cuts_per_min` · `curve` | 短/长镜头占比、切点密度、10 秒窗口切点曲线(每窗按自身长度折算成每分钟) |
|
|
75
|
+
| `loudness.integrated_lufs` · `lra_lu` · `true_peak_dbtp` · `short_term_1s` | 整体响度、动态范围、真峰值、逐秒短期响度(前约 2 秒为预热空值) |
|
|
76
|
+
|
|
77
|
+
`derived.*` 由 check 在内存里计算,export 写入用到的部分:
|
|
78
|
+
|
|
79
|
+
| 路径 | 含义 |
|
|
80
|
+
|---|---|
|
|
81
|
+
| `derived.by_owner.<owner>` | `seconds` · `share` · `blocks` · `block_median_s` · `cuts_per_min` · `mean_short_term_lufs` |
|
|
82
|
+
| `derived.narration_chars_per_s` | 只用被旁白覆盖 ≥80% 的 ASR 窗口;ASR 失败或跳过时 `value` 为 null |
|
|
83
|
+
| `derived.switch_on_cut_share` | 音轨归属切换点落在画面切点 ±0.25 秒内的比例 |
|
|
84
|
+
| `derived.by_section.<function>` | `seconds` · `cuts_per_min` · `narration_share` |
|
|
85
|
+
| `derived.structure` | `[{function, at:[开始占比, 结束占比], lead_owner}]` |
|
|
86
|
+
| `derived.first_original_at` | 第一段 `original_dialogue`(含原片自带画外音)的 `{s, fraction}` |
|
|
87
|
+
| `derived.narration_jobs` | 各 job 占旁白时长的比例(标注了 job 时才有) |
|
|
88
|
+
|
|
89
|
+
## `production_reference.json`(导出物,消费方只读这个)
|
|
90
|
+
|
|
91
|
+
```json
|
|
92
|
+
{
|
|
93
|
+
"schema": "video-reference.production.v1",
|
|
94
|
+
"duration_s": 302.66,
|
|
95
|
+
"canvas": {"width": 1280, "height": 676},
|
|
96
|
+
"cut_detection": {"detector": "scdet-isolated-v1", "hard_score": 10.0, "soft_score": 4.0, "isolation_ratio": 2.0,
|
|
97
|
+
"isolation_window_s": 0.3, "scaled": true, "agent_added": 3, "agent_removed": 0},
|
|
98
|
+
"profile": {
|
|
99
|
+
"shot_median_s": {"value": 2.58, "provenance": "reviewed"},
|
|
100
|
+
"cuts_per_min": {"value": 16.85, "provenance": "reviewed"},
|
|
101
|
+
"integrated_lufs": {"value": -14.8, "provenance": "measured"},
|
|
102
|
+
"narration_share": {"value": 0.65, "provenance": "labeled"},
|
|
103
|
+
"narration_chars_per_s": {"value": 4.0, "provenance": "labeled", "precision": "coarse_asr_windows"},
|
|
104
|
+
"switch_on_cut_share": {"value": 0.5, "provenance": "labeled"},
|
|
105
|
+
"first_original_at": {"value": {"fraction": 0.045}, "provenance": "labeled"}
|
|
106
|
+
},
|
|
107
|
+
"structure": [{"function": "hook", "at": [0.0, 0.1], "lead_owner": "narration"}],
|
|
108
|
+
"subtitles": {"burned": true, "max_lines": 1, "marks_original": "「」"},
|
|
109
|
+
"methods": [
|
|
110
|
+
{"id": "m1", "dimension": "audio_visual", "rule": "…", "applies_when": "…", "avoid_when": "…",
|
|
111
|
+
"applies_to": "visual_audio_board",
|
|
112
|
+
"targets": {"narration_cuts_per_min": {"value": 14.2, "provenance": "labeled"}}}
|
|
113
|
+
],
|
|
114
|
+
"skipped_dimensions": {}
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
- `provenance`:路径在 `derived.*` 下为 `labeled`(依赖 Agent 标注);`shots.*` 在写了 `cut_fixes`(含 `{}`)时为 `reviewed`,
|
|
119
|
+
否则与 `loudness.*` 一样为 `measured`。值为 null 的 profile 项不导出。
|
|
120
|
+
- `cut_detection` 让读者知道这些切点数怎么来的:规则参数与 Agent 增删的切点数。
|
|
121
|
+
- 导出时丢弃 `source_facts`、`labels`、`evidence`、`entities`、`from`、文件身份和任何路径。
|
|
122
|
+
- 保留键 `written_by`、`template` 给以后的资源库绑定副本使用,本技能不写。
|
|
123
|
+
|
|
124
|
+
## 好方法与坏方法
|
|
125
|
+
|
|
126
|
+
方法写"在什么条件下、怎么做、为什么",数字放进 `targets`。
|
|
127
|
+
|
|
128
|
+
好:
|
|
129
|
+
|
|
130
|
+
1. "原声段保留原片自身的剪辑节奏,旁白段换成较长的镜头承载解说" + `applies_when: "对白冲突强、需旁白补前情的剧集解说"`。
|
|
131
|
+
规则方向必须与 check 打印的派生值一致(这里是 `derived.by_owner.*.cuts_per_min`);R1–R8 不检查语义是否自相矛盾。
|
|
132
|
+
2. "开场先用一句旁白交代身份与处境,第一次原声冲突尽早接管" + `targets.first_original.from: "derived.first_original_at"`。
|
|
133
|
+
3. "声音归属的切换尽量落在画面硬切上,让观众用画面感知换手" + `evidence: ["measure:derived.switch_on_cut_share"]`。
|
|
134
|
+
|
|
135
|
+
坏:
|
|
136
|
+
|
|
137
|
+
1. "范闲进殿后旁白交代他的身世"——原片人名与事件,是事实不是方法(R6)。
|
|
138
|
+
2. "在 1:23 切到原声,旁白语速保持每秒 4 字"——绝对时间码(R6)与手写数字(应写 target)。
|
|
139
|
+
3. "照着这部片的结构做"——没有条件、不可迁移,也无法被后续制作采纳或拒绝。
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""Self-contained utilities for the video-reference skill (no cross-skill imports)."""
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
import subprocess
|
|
5
|
+
import tempfile
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def log(msg):
|
|
10
|
+
print(f"[video-reference] {msg}", flush=True)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def run_cmd(cmd, **kwargs):
|
|
14
|
+
"""Run a command list and return the CompletedProcess (stdout/stderr captured)."""
|
|
15
|
+
return subprocess.run(
|
|
16
|
+
[str(part) for part in cmd], capture_output=True, text=True,
|
|
17
|
+
encoding="utf-8", errors="replace", **kwargs,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def file_identity(path):
|
|
22
|
+
"""{size, mtime_ns} — the cache identity of an input file (no content read)."""
|
|
23
|
+
st = os.stat(os.fspath(path))
|
|
24
|
+
return {"size": st.st_size, "mtime_ns": st.st_mtime_ns}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _fraction(text):
|
|
28
|
+
try:
|
|
29
|
+
num, _, den = str(text).partition("/")
|
|
30
|
+
value = float(num) / float(den or 1)
|
|
31
|
+
except (ValueError, ZeroDivisionError):
|
|
32
|
+
return None
|
|
33
|
+
return round(value, 3) if value > 0 else None
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def ffprobe(video_path):
|
|
37
|
+
"""Duration, canvas, fps and soft-subtitle stream count of a media file."""
|
|
38
|
+
result = run_cmd([
|
|
39
|
+
"ffprobe", "-v", "error", "-show_entries",
|
|
40
|
+
"format=duration:stream=codec_type,width,height,avg_frame_rate,r_frame_rate",
|
|
41
|
+
"-of", "json", video_path,
|
|
42
|
+
])
|
|
43
|
+
if result.returncode != 0:
|
|
44
|
+
raise RuntimeError(f"ffprobe 失败: {result.stderr.strip()[-400:]}")
|
|
45
|
+
payload = json.loads(result.stdout or "{}")
|
|
46
|
+
streams = payload.get("streams") or []
|
|
47
|
+
video = next((s for s in streams if s.get("codec_type") == "video"), None)
|
|
48
|
+
if video is None:
|
|
49
|
+
raise RuntimeError(f"没有视频流: {video_path}")
|
|
50
|
+
duration = float((payload.get("format") or {}).get("duration") or 0.0)
|
|
51
|
+
if duration <= 0:
|
|
52
|
+
raise RuntimeError(f"无法读取时长: {video_path}")
|
|
53
|
+
return {
|
|
54
|
+
"duration_s": round(duration, 3),
|
|
55
|
+
"canvas": {"width": int(video.get("width") or 0), "height": int(video.get("height") or 0)},
|
|
56
|
+
"fps": _fraction(video.get("avg_frame_rate")) or _fraction(video.get("r_frame_rate")),
|
|
57
|
+
"subtitle_streams": sum(1 for s in streams if s.get("codec_type") == "subtitle"),
|
|
58
|
+
"audio_streams": sum(1 for s in streams if s.get("codec_type") == "audio"),
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def read_json(path, default=None):
|
|
63
|
+
"""Parsed JSON at `path`, or `default` when the file does not exist."""
|
|
64
|
+
path = Path(path)
|
|
65
|
+
if not path.is_file():
|
|
66
|
+
return default
|
|
67
|
+
return json.loads(path.read_text(encoding="utf-8"))
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def write_json(path, payload):
|
|
71
|
+
"""Atomically write `payload` as UTF-8 JSON (temp file + os.replace)."""
|
|
72
|
+
path = Path(path)
|
|
73
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
74
|
+
fd, temporary = tempfile.mkstemp(prefix=f".{path.name}.", dir=path.parent)
|
|
75
|
+
try:
|
|
76
|
+
with os.fdopen(fd, "w", encoding="utf-8") as handle:
|
|
77
|
+
json.dump(payload, handle, ensure_ascii=False, indent=2)
|
|
78
|
+
handle.write("\n")
|
|
79
|
+
os.replace(temporary, path)
|
|
80
|
+
finally:
|
|
81
|
+
try:
|
|
82
|
+
os.unlink(temporary)
|
|
83
|
+
except FileNotFoundError:
|
|
84
|
+
pass
|
|
85
|
+
return path
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""On-demand production reference from a finished video: measure → (agent looks + labels) → check → export.
|
|
3
|
+
|
|
4
|
+
python3 scripts/reference.py measure <video> --work-dir U [--hard-score 10] [--soft-score 4] [--no-scale]
|
|
5
|
+
python3 scripts/reference.py frames <video> --work-dir U (--review | --longest N | --span A,B [--step S])
|
|
6
|
+
python3 scripts/reference.py check --work-dir U [--json]
|
|
7
|
+
python3 scripts/reference.py export --work-dir U --out <dir>/production_reference.json
|
|
8
|
+
|
|
9
|
+
Exit codes: 0 ok, 1 errors found (nothing exported), 2 bad input or ffmpeg failure.
|
|
10
|
+
"""
|
|
11
|
+
import argparse
|
|
12
|
+
import json
|
|
13
|
+
import sys
|
|
14
|
+
|
|
15
|
+
from lib import read_json
|
|
16
|
+
from reference_check import run_check, run_export
|
|
17
|
+
from reference_frames import frames, legend
|
|
18
|
+
from reference_measure import DEFAULT_HARD_SCORE, DEFAULT_SOFT_SCORE, MEASUREMENTS_FILE, measure
|
|
19
|
+
|
|
20
|
+
__all__ = ["main"]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _print_report(report, as_json):
|
|
24
|
+
if as_json:
|
|
25
|
+
print(json.dumps(report, ensure_ascii=False, indent=2))
|
|
26
|
+
return
|
|
27
|
+
if report.get("derived") is not None:
|
|
28
|
+
print("== derived(只在内存里;target 用 derived.* 路径引用)==")
|
|
29
|
+
print(json.dumps(report["derived"], ensure_ascii=False, indent=2))
|
|
30
|
+
for title, key, tag in (("errors", "errors", "ERROR"), ("warnings", "warnings", "WARN")):
|
|
31
|
+
print(f"== {title} ({len(report[key])}) ==")
|
|
32
|
+
for line in report[key]:
|
|
33
|
+
print(f"{tag} {line}")
|
|
34
|
+
if report.get("written"):
|
|
35
|
+
print(f"已导出 {report['written']}")
|
|
36
|
+
print("FAIL" if report["errors"] else "OK")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def main(argv=None):
|
|
40
|
+
parser = argparse.ArgumentParser(description="把成片拆成可复用的制作参考(按需,不在生产路径上)")
|
|
41
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
42
|
+
p_measure = sub.add_parser("measure", help="一次 ffmpeg:镜头切点与响度,按文件身份缓存")
|
|
43
|
+
p_measure.add_argument("video")
|
|
44
|
+
p_measure.add_argument("--work-dir", required=True)
|
|
45
|
+
p_measure.add_argument("--hard-score", type=float, default=DEFAULT_HARD_SCORE,
|
|
46
|
+
help="scdet 分数达到它就算切点")
|
|
47
|
+
p_measure.add_argument("--soft-score", type=float, default=DEFAULT_SOFT_SCORE,
|
|
48
|
+
help="达到它且是 ±0.3s 内的孤立峰才算切点;被压下的进入待复核窗口")
|
|
49
|
+
p_measure.add_argument("--no-scale", action="store_true", help="scdet 前不缩放到 320 宽")
|
|
50
|
+
p_frames = sub.add_parser("frames", help="拼帧图看画面:待复核窗口、最长镜头或任意时间段")
|
|
51
|
+
p_frames.add_argument("video")
|
|
52
|
+
p_frames.add_argument("--work-dir", required=True)
|
|
53
|
+
mode = p_frames.add_mutually_exclusive_group(required=True)
|
|
54
|
+
mode.add_argument("--review", action="store_true", help="逐帧看 measure 压下的疑似切点")
|
|
55
|
+
mode.add_argument("--longest", type=int, metavar="N", help="看最长的 N 个镜头里有没有漏掉的切点")
|
|
56
|
+
mode.add_argument("--span", metavar="A,B", help="A 到 B 秒的接触表(没有理解产物时用它看片)")
|
|
57
|
+
p_frames.add_argument("--step", type=float, default=2.0, help="--span 的取帧间隔秒数")
|
|
58
|
+
p_check = sub.add_parser("check", help="R1–R7 校验 reference_breakdown.json 并打印派生值")
|
|
59
|
+
p_check.add_argument("--work-dir", required=True)
|
|
60
|
+
p_check.add_argument("--json", action="store_true")
|
|
61
|
+
p_export = sub.add_parser("export", help="零 error 时写 production_reference.json(R8 复扫)")
|
|
62
|
+
p_export.add_argument("--work-dir", required=True)
|
|
63
|
+
p_export.add_argument("--out", required=True)
|
|
64
|
+
p_export.add_argument("--json", action="store_true")
|
|
65
|
+
args = parser.parse_args(argv)
|
|
66
|
+
|
|
67
|
+
try:
|
|
68
|
+
if args.command == "measure":
|
|
69
|
+
payload = measure(args.video, args.work_dir, hard=args.hard_score, soft=args.soft_score,
|
|
70
|
+
scaled=not args.no_scale)
|
|
71
|
+
shots, loudness = payload["shots"], payload.get("loudness") or {}
|
|
72
|
+
print(f"镜头 {shots['count']} 个,中位 {shots['median_s']}s,{shots['cuts_per_min']} 切/分钟;"
|
|
73
|
+
f"整体响度 {loudness.get('integrated_lufs')} LUFS;待复核窗口 {len(shots['review_windows'])} 个")
|
|
74
|
+
return 0
|
|
75
|
+
if args.command == "frames":
|
|
76
|
+
span = None
|
|
77
|
+
if args.span:
|
|
78
|
+
start, _, end = args.span.partition(",")
|
|
79
|
+
span = (float(start), float(end))
|
|
80
|
+
if args.longest is not None and args.longest < 1:
|
|
81
|
+
raise ValueError("--longest 至少为 1")
|
|
82
|
+
mode = "review" if args.review else "span" if span else "longest"
|
|
83
|
+
pages = frames(args.video, args.work_dir, read_json(f"{args.work_dir}/{MEASUREMENTS_FILE}"),
|
|
84
|
+
mode=mode, longest=args.longest, span=span, step=args.step)
|
|
85
|
+
print(legend(pages) if pages else "没有要看的帧(没有待复核窗口)")
|
|
86
|
+
return 0
|
|
87
|
+
report = run_check(args.work_dir) if args.command == "check" else run_export(args.work_dir, args.out)
|
|
88
|
+
except (OSError, RuntimeError, ValueError) as exc:
|
|
89
|
+
print(f"[video-reference] {exc}", file=sys.stderr)
|
|
90
|
+
return 2
|
|
91
|
+
_print_report(report, args.json)
|
|
92
|
+
return 1 if report["errors"] else 0
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
if __name__ == "__main__":
|
|
96
|
+
raise SystemExit(main())
|