@oh-story/dsh 0.1.11 → 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.
Files changed (264) hide show
  1. package/README.md +17 -11
  2. package/lib/client.js +85 -35
  3. package/lib/client.js.map +3 -3
  4. package/lib/drama/manifest.json +41 -36
  5. package/lib/drama/skills/short-drama/scripts/creator_markdown_check.py +340 -13
  6. package/lib/drama/skills/short-drama-assets/references/voice-direction.md +4 -2
  7. package/lib/drama/skills/short-drama-edit/SKILL.md +32 -12
  8. package/lib/drama/skills/short-drama-edit/references/still-cuts.md +90 -0
  9. package/lib/drama/skills/short-drama-edit/scripts/edit_tool.py +1035 -145
  10. package/lib/drama/skills/short-drama-edit/scripts/selftest.py +48 -0
  11. package/lib/drama/skills/short-drama-produce/SKILL.md +6 -1
  12. package/lib/drama/skills/short-drama-produce/references/adapter-contract.md +12 -0
  13. package/lib/drama/skills/short-drama-produce/scripts/production_tool.py +138 -9
  14. package/lib/drama/skills/short-drama-produce/scripts/provider_adapters.py +6 -0
  15. package/lib/drama/skills/short-drama-storyboard/SKILL.md +2 -0
  16. package/lib/drama/skills/short-drama-storyboard/references/production-shot-grammar.md +3 -1
  17. package/lib/drama/skills/short-drama-video-prompts/SKILL.md +7 -0
  18. package/lib/drama/skills/short-drama-video-prompts/references/performance-action-timing.md +14 -0
  19. package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.5.md +19 -6
  20. package/lib/drama/skills/short-drama-video-prompts/references/stage-contract.md +1 -0
  21. package/lib/drama/skills/short-drama-video-prompts/references/target-model-profile.md +5 -0
  22. package/lib/drama/skills/short-drama-video-prompts/references/wan-3.0.md +4 -1
  23. package/lib/index.js +16 -7
  24. package/lib/index.js.map +2 -2
  25. package/lib/novel-to-game/README.md +98 -44
  26. package/lib/novel-to-game/README_ZH.md +100 -46
  27. package/lib/novel-to-game/VERSION +1 -1
  28. package/lib/novel-to-game/examples/jin-ping-mei/build/BUILD_BRIEF.md +27 -202
  29. package/lib/novel-to-game/examples/jin-ping-mei/build/app/RUN.md +23 -115
  30. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/chamber.webp +0 -0
  31. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/court_night.webp +0 -0
  32. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/court_rain.webp +0 -0
  33. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/garden.webp +0 -0
  34. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/hall.webp +0 -0
  35. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/kitchen.webp +0 -0
  36. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/street.webp +0 -0
  37. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/street_dawn.webp +0 -0
  38. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/temple.webp +0 -0
  39. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/yue/moon.webp +0 -0
  40. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/meng/angry.webp +0 -0
  41. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/meng/blush.webp +0 -0
  42. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/meng/neutral.webp +0 -0
  43. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/meng/sad.webp +0 -0
  44. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/meng/smile.webp +0 -0
  45. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/meng/surprised.webp +0 -0
  46. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pan/angry.webp +0 -0
  47. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pan/blush.webp +0 -0
  48. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pan/neutral.webp +0 -0
  49. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pan/sad.webp +0 -0
  50. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pan/smile.webp +0 -0
  51. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pan/surprised.webp +0 -0
  52. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pinger/angry.webp +0 -0
  53. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pinger/blush.webp +0 -0
  54. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pinger/neutral.webp +0 -0
  55. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pinger/sad.webp +0 -0
  56. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pinger/smile.webp +0 -0
  57. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pinger/surprised.webp +0 -0
  58. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/xuee/angry.webp +0 -0
  59. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/xuee/blush.webp +0 -0
  60. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/xuee/neutral.webp +0 -0
  61. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/xuee/sad.webp +0 -0
  62. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/xuee/smile.webp +0 -0
  63. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/xuee/surprised.webp +0 -0
  64. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/yue/angry.webp +0 -0
  65. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/yue/blush.webp +0 -0
  66. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/yue/neutral.webp +0 -0
  67. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/yue/sad.webp +0 -0
  68. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/yue/smile.webp +0 -0
  69. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/yue/surprised.webp +0 -0
  70. package/lib/novel-to-game/examples/jin-ping-mei/build/app/css/style.css +326 -2503
  71. package/lib/novel-to-game/examples/jin-ping-mei/build/app/index.html +71 -4
  72. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/adv.js +221 -0
  73. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/audio.js +113 -257
  74. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/main.js +511 -3026
  75. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/menus.js +277 -0
  76. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/petals.js +67 -0
  77. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/stage.js +180 -0
  78. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/store.js +70 -0
  79. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/common.js +198 -0
  80. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/common2.js +204 -0
  81. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/common3.js +198 -0
  82. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/dates.js +186 -0
  83. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/index.js +122 -0
  84. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/meng.js +190 -0
  85. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/pan.js +212 -0
  86. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/pinger.js +198 -0
  87. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/xuee.js +199 -0
  88. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/yue.js +214 -0
  89. package/lib/novel-to-game/examples/jin-ping-mei/build/app/package.json +2 -2
  90. package/lib/novel-to-game/examples/jin-ping-mei/build/art/generated-art.json +182 -45
  91. package/lib/novel-to-game/examples/jin-ping-mei/qa/README.md +7 -17
  92. package/lib/novel-to-game/examples/jin-ping-mei/qa/verification.json +9 -9
  93. package/lib/novel-to-game/manifest.json +303 -78
  94. package/lib/novel-to-game/skills/game-art-direction/references/art-direction-method.md +7 -2
  95. package/lib/novel-to-game/skills/game-concept/references/concept-method.md +2 -0
  96. package/lib/novel-to-game/skills/game-world-design/references/narrative-design-method.md +2 -0
  97. package/lib/novel-to-game/skills/game-world-design/references/world-design-method.md +9 -0
  98. package/lib/types/client/drama-production-view.d.ts +11 -0
  99. package/lib/types/client/drama-production-view.d.ts.map +1 -1
  100. package/lib/types/client/file-activity.d.ts +1 -1
  101. package/lib/types/client/file-activity.d.ts.map +1 -1
  102. package/lib/types/client/production-prompts.d.ts +7 -1
  103. package/lib/types/client/production-prompts.d.ts.map +1 -1
  104. package/lib/types/client/production-runtime.d.ts +7 -2
  105. package/lib/types/client/production-runtime.d.ts.map +1 -1
  106. package/lib/types/drama-adapters.d.ts.map +1 -1
  107. package/lib/types/skill-provider.d.ts.map +1 -1
  108. package/lib/types/video-project.d.ts.map +1 -1
  109. package/lib/video-recap/CHANGELOG.md +108 -1
  110. package/lib/video-recap/README.en.md +18 -14
  111. package/lib/video-recap/README.md +18 -14
  112. package/lib/video-recap/manifest.json +297 -321
  113. package/lib/video-recap/skills/video-assemble/SKILL.md +18 -16
  114. package/lib/video-recap/skills/video-assemble/references/explicit-audio-mix.md +3 -1
  115. package/lib/video-recap/skills/video-assemble/references/jianying/SOURCE.md +5 -5
  116. package/lib/video-recap/skills/video-assemble/references/packaging.md +4 -4
  117. package/lib/video-recap/skills/video-assemble/references/subtitle-track.md +10 -10
  118. package/lib/video-recap/skills/video-assemble/scripts/adoption/audio_mix_binding.py +13 -2
  119. package/lib/video-recap/skills/video-assemble/scripts/adoption/av_clock.py +109 -0
  120. package/lib/video-recap/skills/video-assemble/scripts/adoption/strict_publish.py +4 -1
  121. package/lib/video-recap/skills/video-assemble/scripts/artifacts.py +2 -9
  122. package/lib/video-recap/skills/video-assemble/scripts/assemble.py +188 -51
  123. package/lib/video-recap/skills/video-assemble/scripts/assemble_constants.py +0 -1
  124. package/lib/video-recap/skills/video-assemble/scripts/assembly_contract.py +32 -75
  125. package/lib/video-recap/skills/video-assemble/scripts/assembly_settings.py +8 -15
  126. package/lib/video-recap/skills/video-assemble/scripts/audio_mix.py +202 -161
  127. package/lib/video-recap/skills/video-assemble/scripts/codec_peak.py +112 -0
  128. package/lib/video-recap/skills/video-assemble/scripts/export_jianying.py +10 -101
  129. package/lib/video-recap/skills/video-assemble/scripts/jianying/builders.py +44 -475
  130. package/lib/video-recap/skills/video-assemble/scripts/jianying/model.py +1 -22
  131. package/lib/video-recap/skills/video-assemble/scripts/jianying/templates.py +0 -3
  132. package/lib/video-recap/skills/video-assemble/scripts/jianying/timeline_contract.py +36 -103
  133. package/lib/video-recap/skills/video-assemble/scripts/jianying/tracks.py +0 -8
  134. package/lib/video-recap/skills/video-assemble/scripts/jianying/writer.py +28 -203
  135. package/lib/video-recap/skills/video-assemble/scripts/lib.py +20 -34
  136. package/lib/video-recap/skills/video-assemble/scripts/loudness.py +332 -0
  137. package/lib/video-recap/skills/video-assemble/scripts/media.py +158 -38
  138. package/lib/video-recap/skills/video-assemble/scripts/narration_audio.py +30 -16
  139. package/lib/video-recap/skills/video-assemble/scripts/render_preflight.py +49 -8
  140. package/lib/video-recap/skills/video-assemble/scripts/source_subtitles.py +17 -6
  141. package/lib/video-recap/skills/video-assemble/scripts/subtitles/core.py +24 -2
  142. package/lib/video-recap/skills/video-assemble/scripts/subtitles/track.py +2 -5
  143. package/lib/video-recap/skills/video-assemble/scripts/subtitles/track_binding.py +2 -9
  144. package/lib/video-recap/skills/video-assemble/scripts/timeline.py +12 -34
  145. package/lib/video-recap/skills/video-assemble/scripts/timeline_emit.py +3 -4
  146. package/lib/video-recap/skills/video-assemble/scripts/visual_render.py +28 -2
  147. package/lib/video-recap/skills/video-cut/SKILL.md +37 -11
  148. package/lib/video-recap/skills/video-cut/references/shot-review.md +1 -1
  149. package/lib/video-recap/skills/video-cut/scripts/cut.py +2 -34
  150. package/lib/video-recap/skills/video-cut/scripts/cut_cli.py +96 -124
  151. package/lib/video-recap/skills/video-cut/scripts/cut_contract.py +97 -182
  152. package/lib/video-recap/skills/video-cut/scripts/cut_render.py +177 -166
  153. package/lib/video-recap/skills/video-cut/scripts/frame_grid.py +249 -0
  154. package/lib/video-recap/skills/video-cut/scripts/lib.py +0 -3
  155. package/lib/video-recap/skills/video-cut/scripts/media_geometry.py +173 -7
  156. package/lib/video-recap/skills/video-cut/scripts/sentence_boundaries.py +234 -151
  157. package/lib/video-recap/skills/video-cut/scripts/sentence_gate.py +189 -0
  158. package/lib/video-recap/skills/video-cut/scripts/shot_review.py +2 -6
  159. package/lib/video-recap/skills/video-recap/SKILL.md +24 -27
  160. package/lib/video-recap/skills/video-recap/references/audio-routing.md +8 -6
  161. package/lib/video-recap/skills/video-recap/references/config-playbook.md +8 -11
  162. package/lib/video-recap/skills/video-recap/references/data-schema.md +131 -74
  163. package/lib/video-recap/skills/video-recap/references/resource-library.md +17 -1
  164. package/lib/video-recap/skills/video-recap/scripts/doctor.py +51 -268
  165. package/lib/video-recap/skills/video-recap/scripts/final_qc.py +89 -254
  166. package/lib/video-recap/skills/video-recap/scripts/lib.py +81 -70
  167. package/lib/video-recap/skills/video-recap/scripts/library.py +40 -4
  168. package/lib/video-recap/skills/video-recap/scripts/materials.py +13 -80
  169. package/lib/video-recap/skills/video-recap/scripts/recap_cli.py +49 -17
  170. package/lib/video-recap/skills/video-recap/scripts/recap_inspect.py +1 -3
  171. package/lib/video-recap/skills/video-recap/scripts/recap_runner.py +86 -116
  172. package/lib/video-recap/skills/video-recap/scripts/recap_runtime.py +87 -23
  173. package/lib/video-recap/skills/video-recap/scripts/recap_source.py +0 -39
  174. package/lib/video-recap/skills/video-recap/scripts/recap_stage_qc.py +37 -135
  175. package/lib/video-recap/skills/video-recap/scripts/recap_timeline.py +118 -127
  176. package/lib/video-recap/skills/video-recap/scripts/resources/__init__.py +4 -0
  177. package/lib/video-recap/skills/video-recap/scripts/{resource_lock.py → resources/lock.py} +1 -1
  178. package/lib/video-recap/skills/video-recap/scripts/{project_binding.py → resources/project_binding.py} +78 -17
  179. package/lib/video-recap/skills/video-reference/SKILL.md +108 -0
  180. package/lib/video-recap/skills/video-reference/references/reference-schema.md +139 -0
  181. package/lib/video-recap/skills/video-reference/scripts/lib.py +85 -0
  182. package/lib/video-recap/skills/video-reference/scripts/reference.py +96 -0
  183. package/lib/video-recap/skills/video-reference/scripts/reference_check.py +553 -0
  184. package/lib/video-recap/skills/video-reference/scripts/reference_frames.py +122 -0
  185. package/lib/video-recap/skills/video-reference/scripts/reference_measure.py +253 -0
  186. package/lib/video-recap/skills/video-reference/scripts/reference_profile.py +271 -0
  187. package/lib/video-recap/skills/video-script/SKILL.md +9 -7
  188. package/lib/video-recap/skills/video-script/scripts/agent_text.py +18 -267
  189. package/lib/video-recap/skills/video-script/scripts/deslop_qc.py +12 -30
  190. package/lib/video-recap/skills/video-script/scripts/entry_suggestion.py +125 -0
  191. package/lib/video-recap/skills/video-script/scripts/evidence_bundle.py +2 -26
  192. package/lib/video-recap/skills/video-script/scripts/lib.py +5 -15
  193. package/lib/video-recap/skills/video-script/scripts/lint_summary.py +184 -0
  194. package/lib/video-recap/skills/video-script/scripts/narration_lint.py +120 -100
  195. package/lib/video-recap/skills/video-script/scripts/review.py +3 -34
  196. package/lib/video-recap/skills/video-script/scripts/review_grounding.py +20 -46
  197. package/lib/video-recap/skills/video-script/scripts/review_response.py +120 -258
  198. package/lib/video-recap/skills/video-script/scripts/review_runner.py +2 -10
  199. package/lib/video-recap/skills/video-script/scripts/speech_ownership.py +89 -5
  200. package/lib/video-recap/skills/video-script/scripts/validate.py +26 -34
  201. package/lib/video-recap/skills/video-understanding/SKILL.md +29 -4
  202. package/lib/video-recap/skills/video-understanding/references/data-schema.md +5 -22
  203. package/lib/video-recap/skills/video-understanding/scripts/agent_text.py +24 -121
  204. package/lib/video-recap/skills/video-understanding/scripts/asr.py +11 -5
  205. package/lib/video-recap/skills/video-understanding/scripts/asr_timing_evidence.py +15 -24
  206. package/lib/video-recap/skills/video-understanding/scripts/briefing/builder.py +59 -25
  207. package/lib/video-recap/skills/video-understanding/scripts/briefing/context.py +39 -3
  208. package/lib/video-recap/skills/video-understanding/scripts/briefing/inputs.py +15 -27
  209. package/lib/video-recap/skills/video-understanding/scripts/briefing/timeline.py +136 -37
  210. package/lib/video-recap/skills/video-understanding/scripts/consolidate.py +121 -37
  211. package/lib/video-recap/skills/video-understanding/scripts/detect.py +118 -16
  212. package/lib/video-recap/skills/video-understanding/scripts/index_normalize.py +359 -0
  213. package/lib/video-recap/skills/video-understanding/scripts/lib.py +62 -21
  214. package/lib/video-recap/skills/video-understanding/scripts/storyboard.py +181 -51
  215. package/lib/video-recap/skills/video-understanding/scripts/timeline_fusion.py +0 -26
  216. package/lib/video-recap/skills/video-understanding/scripts/understanding_brief.py +37 -14
  217. package/lib/video-recap/skills/video-understanding/scripts/understanding_cache.py +40 -32
  218. package/lib/video-recap/skills/video-understanding/scripts/understanding_runner.py +100 -127
  219. package/lib/video-recap/skills/video-understanding/scripts/understanding_storyboard.py +182 -30
  220. package/lib/video-recap/skills/video-understanding/scripts/vlm.py +30 -27
  221. package/lib/video-recap/skills/video-voiceover/SKILL.md +19 -12
  222. package/lib/video-recap/skills/video-voiceover/references/index-tts.md +3 -3
  223. package/lib/video-recap/skills/video-voiceover/scripts/approved_text_policy.py +3 -1
  224. package/lib/video-recap/skills/video-voiceover/scripts/dub.py +60 -135
  225. package/lib/video-recap/skills/video-voiceover/scripts/lib.py +9 -34
  226. package/lib/video-recap/skills/video-voiceover/scripts/providers/fish_audio.py +6 -1
  227. package/lib/video-recap/skills/video-voiceover/scripts/tts_audio.py +64 -1
  228. package/lib/video-recap/skills/video-voiceover/scripts/tts_cache.py +99 -0
  229. package/lib/video-recap/skills/video-voiceover/scripts/voiceover.py +177 -200
  230. package/package.json +45 -45
  231. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/inner_court_accord_five.webp +0 -0
  232. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/public_day15.webp +0 -0
  233. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/household/li_jiaoer.webp +0 -0
  234. package/lib/novel-to-game/examples/jin-ping-mei/build/app/css/new-guofeng.css +0 -1494
  235. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/assets.js +0 -147
  236. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/data.js +0 -7470
  237. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/engine.js +0 -13066
  238. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/text.js +0 -32
  239. package/lib/novel-to-game/examples/jin-ping-mei/qa/playable-model.json +0 -99
  240. package/lib/novel-to-game/examples/jin-ping-mei/qa/verify_playable_model.mjs +0 -152
  241. package/lib/video-recap/skills/video-assemble/references/foreground-compose.md +0 -118
  242. package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_combination_segment.json +0 -84
  243. package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_combination_video_material.json +0 -98
  244. package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_draft.json +0 -204
  245. package/lib/video-recap/skills/video-assemble/references/pair-media.md +0 -74
  246. package/lib/video-recap/skills/video-assemble/scripts/compose_foreground.py +0 -293
  247. package/lib/video-recap/skills/video-assemble/scripts/pair_media.py +0 -216
  248. package/lib/video-recap/skills/video-recap/references/shift-left-qc-schema.md +0 -23
  249. package/lib/video-recap/skills/video-recap/scripts/mimo_qc.py +0 -33
  250. package/lib/video-recap/skills/video-recap/scripts/qc/__init__.py +0 -1
  251. package/lib/video-recap/skills/video-recap/scripts/qc/mimo_client.py +0 -13
  252. package/lib/video-recap/skills/video-recap/scripts/qc/mimo_contract.py +0 -9
  253. package/lib/video-recap/skills/video-recap/scripts/qc/mimo_evidence.py +0 -287
  254. package/lib/video-recap/skills/video-recap/scripts/qc/mimo_observations.py +0 -175
  255. package/lib/video-recap/skills/video-recap/scripts/qc/mimo_payload.py +0 -206
  256. package/lib/video-recap/skills/video-recap/scripts/qc/mimo_report.py +0 -318
  257. package/lib/video-recap/skills/video-recap/scripts/qc/mimo_runner.py +0 -130
  258. package/lib/video-recap/skills/video-recap/scripts/qc_contract.py +0 -320
  259. package/lib/video-recap/skills/video-script/scripts/timeline_fusion.py +0 -108
  260. package/lib/video-recap/skills/video-understanding/scripts/brief.py +0 -9
  261. package/lib/video-recap/skills/video-understanding/scripts/deslop_qc.py +0 -211
  262. package/lib/video-recap/skills/video-understanding/scripts/narration_lint.py +0 -707
  263. package/lib/video-recap/skills/video-understanding/scripts/speech_ownership.py +0 -143
  264. /package/lib/video-recap/skills/video-cut/scripts/{narration_mapping.py → cut_qc.py} +0 -0
@@ -478,6 +478,53 @@ def check_screen_text_and_effects() -> None:
478
478
  require(any("[画面文字]" in item for item in findings), f"编造的画面文字没抓到: {findings}")
479
479
 
480
480
 
481
+ def check_still_cuts() -> None:
482
+ """The static route: keyframes with a camera move and voice lines, every shot accounted for."""
483
+
484
+ with tempfile.TemporaryDirectory() as scratch:
485
+ root = Path(scratch)
486
+ episode = build(root, CUT_LIST)
487
+ (episode / "分镜.md").write_text(
488
+ "## SHOT-EP001-010 · 甲\n\n## SHOT-EP001-011 · 乙\n", encoding="utf-8"
489
+ )
490
+ for name in ("010.png", "line.wav"):
491
+ (episode / "media" / name).write_bytes(b"")
492
+ still = (
493
+ "- 画幅与帧率:1080×1920 · 24fps\n\n"
494
+ "## CUT-EP001-001 · 定格\n\n"
495
+ "- 来源:SHOT-EP001-010 · media/010.png\n"
496
+ "- 入点:0.00\n- 出点:2.00\n- 时长:2.00\n"
497
+ "- 取舍:入点=起;出点=止\n- 声音:配音\n"
498
+ "- 运镜:推近 8%\n- 配音:0.30 media/line.wav\n"
499
+ )
500
+ (episode / "剪辑单.md").write_text(still, encoding="utf-8")
501
+ delivery, cuts, unused = parse_cut_list(episode / "剪辑单.md")
502
+ require(cuts[0].still and cuts[0].move[:2] == ("推近", 8), f"静帧与运镜没有解析出来: {cuts[0]}")
503
+ require(len(cuts[0].voices) == 1, "配音没有解析出来")
504
+ findings = check_cuts(episode, cuts, root, probe=False, unused=unused, delivery=delivery)
505
+ require(
506
+ any("SHOT-EP001-011" in item for item in findings),
507
+ f"用了 SHOT 静帧之后,没用也没写进未采用的镜头必须报: {findings}",
508
+ )
509
+
510
+ # A line can start inside its file and before its picture (a J-cut).
511
+ (episode / "剪辑单.md").write_text(
512
+ still.replace("- 配音:0.30 media/line.wav", "- 配音:-0.40 media/line.wav(增益:-2;起点:0.10)"),
513
+ encoding="utf-8",
514
+ )
515
+ _, cuts, _ = parse_cut_list(episode / "剪辑单.md")
516
+ voice = cuts[0].voices[0]
517
+ require((voice.start, voice.gain_db, voice.offset) == (-0.4, -2.0, 0.1), f"起点与增益没有解析出来: {voice}")
518
+
519
+ (episode / "剪辑单.md").write_text(still + "- 运镜:旋转 8%\n", encoding="utf-8")
520
+ try:
521
+ parse_cut_list(episode / "剪辑单.md")
522
+ except EditError:
523
+ pass
524
+ else:
525
+ raise AssertionError("运镜写了两遍应当被拒绝")
526
+
527
+
481
528
  def main() -> int:
482
529
  with tempfile.TemporaryDirectory() as scratch:
483
530
  root = Path(scratch)
@@ -547,6 +594,7 @@ def main() -> int:
547
594
  check_ass_escaping()
548
595
  check_multi_subtitle()
549
596
  check_screen_text_and_effects()
597
+ check_still_cuts()
550
598
  check_stale_window()
551
599
  check_missing_shots_are_reported()
552
600
  check_grain_is_a_delivery_wide_decision()
@@ -25,6 +25,9 @@ license: MIT
25
25
  (MiniMax 为 `first_frame`/`last_frame`/`reference_image`/`reference_video`/`reference_audio`,
26
26
  Seedance 为三个 `reference_*`);带参考图却没有绑定的 job 会直接失败,不替它猜一个 role。
27
27
  本地图片由内置 adapter 按 base64 data URI 直接送出,不需要自建上传服务。
28
+ `MOTION-*` 的「参考音频」行同样逐条进 `reference_bindings`,排在图片之后、role 固定为 `reference_audio`、`character` 照抄该行的 `角色`;
29
+ adapter profile 没有声明接受 `reference_audio` 时 `run` 在提交前失败,见
30
+ [adapter-contract.md](references/adapter-contract.md#adapter-config)。
28
31
  条目的「输入参考图」写成 `PLAN-...` 时,那些图片在创作者自己的工具里,项目内没有文件可送:
29
32
  `prepare` 直接失败并说明先把真实文件放进项目、改写成 `REF-...`,不拿计划当输入,也不静默丢掉参考。
30
33
  `references` 可以省略并由绑定顺序生成,也可以作为相同顺序的显式镜像。输出放在
@@ -98,7 +101,9 @@ python3 <本技能目录>/scripts/production_tool.py audit <project>
98
101
  输入统一译为 `reference_video + reference_image`,不能混成 `reference_video + first_frame`;不以
99
102
  计划尾帧或文字描述代替真实文件。
100
103
  - **tts**:从 `剧本.md` 读取原句与表演要求,声音参考由用户或现有媒体明确提供。不得在生产 job
101
- 中改词,也不为 TTS 新建第六份创作文档。
104
+ 中改词,也不为 TTS 新建第六份创作文档。表演要求来自这句台词的括注和上下文:执行端接受自然语言语气时,
105
+ 写成一句语气说明放在台词前(「憋屈地压低声音,咬着牙应一声:是。」),同一角色每句挂同一段参考音色;
106
+ 只接受枚举情绪时映射到最近的一档,再用强度、音调细调。只给音色、不给语气时,短剧台词会念成播音腔。
102
107
  - **music**:读取 `视频提示词.md` 中创作者已确认的时间线音乐章节;主题曲使用已确认歌词,纯配乐
103
108
  不携带歌词。供应商不能精确承诺时长时,生成源音轨后仍由 `$short-drama-edit` 按文档里的混音意图完成落点、
104
109
  循环、淡入淡出和对白 ducking。
@@ -58,6 +58,12 @@
58
58
  `reference_video`, `reference_audio` for MiniMax, and the three `reference_*`
59
59
  values for Seedance. A video job carrying references without bindings fails
60
60
  closed there rather than having a role guessed for it.
61
+ A `MOTION-*` entry's `参考音频` line binds after the pictures: its slots follow
62
+ the `输入参考图` bindings in audio `顺序`, continuing the job's `order`, each with
63
+ role `reference_audio` and `character` copied exactly from its `角色` field. Only those
64
+ slots may use that role. Leaving a declared voice or its character out, or swapping
65
+ characters, fails `prepare`. Adapters must preserve this character binding; bundled
66
+ compilers derive the voice label from `character`, not the free-form asset `label`.
61
67
  - `references`: zero to sixteen current project files actually sent to production.
62
68
  It may be omitted when `reference_bindings` is present, in which case the paths
63
69
  are derived in binding order. If both are present, they must match exactly.
@@ -99,6 +105,12 @@ Keep this file outside the project:
99
105
  `command` is an argv array, never a shell string. Timeout is 1–3600 seconds. Do not put credentials in this file;
100
106
  let the adapter read its environment or operating-system credential store.
101
107
 
108
+ A profile may add `"reference_roles": [...]`, the roles its configured model accepts. When present,
109
+ `run` refuses any binding whose role is not listed. A `reference_audio` binding always needs it:
110
+ a model without an audio input can drop the voice without an error. Either way `run` stops before
111
+ the confirmation is spent or the adapter starts. List `reference_audio` only for a model you know
112
+ accepts reference audio.
113
+
102
114
  ## Capability sources
103
115
 
104
116
  Use the provider's current API reference for roles, limits and supported combinations.
@@ -149,6 +149,17 @@ REFERENCE_LINE_RE = re.compile(
149
149
  re.IGNORECASE,
150
150
  )
151
151
 
152
+ # A MOTION's `- 参考音频:` line: the speaking character's accepted voice. It
153
+ # compiles to role `reference_audio` after the picture bindings, so the audio
154
+ # 顺序 stays the provider's own audio numbering (`@音频N`, `<Audio N>`).
155
+ AUDIO_REFERENCE_ROLE = "reference_audio"
156
+ AUDIO_REFERENCE_LINE_RE = re.compile(
157
+ r"(REF-[A-Z0-9][A-Z0-9-]{0,79})(顺序:([1-9]\d*))· "
158
+ r"([^;\n]+?\.(?:wav|mp3|m4a|aac|flac))《([^》\n]+)》"
159
+ r"(用途:[^;)\n]+;角色:([^;)\n]+);控制:([^;)\n]+);不得控制:([^)\n]+))",
160
+ re.IGNORECASE,
161
+ )
162
+
152
163
 
153
164
  class ConfirmationRequiredError(RuntimeError):
154
165
  """The exact current job has not been explicitly confirmed."""
@@ -705,7 +716,7 @@ def _normalize_reference_bindings(value: object) -> list[dict[str, Any]]:
705
716
  normalized: list[dict[str, Any]] = []
706
717
  for index, binding in enumerate(value, 1):
707
718
  label = f"reference_bindings[{index}]"
708
- if not isinstance(binding, Mapping) or set(binding) != {
719
+ if not isinstance(binding, Mapping) or set(binding) - {"character"} != {
709
720
  "slot_id",
710
721
  "order",
711
722
  "path",
@@ -732,6 +743,12 @@ def _normalize_reference_bindings(value: object) -> list[dict[str, Any]]:
732
743
  raise ValueError(f"{label} label must contain Chinese text")
733
744
  if not isinstance(role, str) or not role.strip() or len(role) > 80:
734
745
  raise ValueError(f"{label} role is invalid")
746
+ character = binding.get("character")
747
+ if "character" in binding and (
748
+ role != AUDIO_REFERENCE_ROLE or not isinstance(character, str)
749
+ or not character.strip() or len(character) > 200
750
+ ):
751
+ raise ValueError(f"{label} character is invalid")
735
752
  may_control = _scope_list(
736
753
  binding.get("may_control"), label=f"{label}.may_control"
737
754
  )
@@ -749,6 +766,7 @@ def _normalize_reference_bindings(value: object) -> list[dict[str, Any]]:
749
766
  "path": _relative_path(binding.get("path")),
750
767
  "label": raw_label.strip(),
751
768
  "role": role.strip(),
769
+ **({"character": character.strip()} if isinstance(character, str) else {}),
752
770
  "may_control": may_control,
753
771
  "must_not_control": must_not_control,
754
772
  }
@@ -817,12 +835,25 @@ def _contains_plan_token(value: str) -> bool:
817
835
  return "plan-" in value.casefold()
818
836
 
819
837
 
838
+ def _field_values(section: str, field_name: str) -> list[str]:
839
+ """Every value of one `- 字段:值` field, read the way the creator checker reads it.
840
+
841
+ `- **参考音频**:…` is the same field to a human and to
842
+ creator_markdown_check.py; reading it any other way here would let the
843
+ checker validate a binding that production then silently omits.
844
+ """
845
+ values = []
846
+ for key, value in re.findall(r"^- ([^:\n]+):(.+)$", section, re.MULTILINE):
847
+ key = re.sub(r"^(?:\*\*|__|\*|_)(.+?)(?:\*\*|__|\*|_)$", r"\1", key.strip())
848
+ if key == field_name:
849
+ values.append(value)
850
+ return values
851
+
852
+
820
853
  def _markdown_reference_bindings(
821
854
  section: str, *, field_name: str, creator_supplied_ok: bool = False
822
855
  ) -> list[dict[str, Any]]:
823
- lines = re.findall(
824
- rf"^- {re.escape(field_name)}:(.+)$", section, re.MULTILINE
825
- )
856
+ lines = _field_values(section, field_name)
826
857
  if not lines:
827
858
  raise ValueError(f"source entry has no {field_name} declaration")
828
859
  if len(lines) != 1:
@@ -879,6 +910,45 @@ def _markdown_reference_bindings(
879
910
  ]
880
911
 
881
912
 
913
+ def _markdown_audio_bindings(section: str) -> list[dict[str, Any]]:
914
+ """The MOTION's 参考音频 slots in audio order; empty when there is none."""
915
+ lines = _field_values(section, "参考音频")
916
+ if not lines:
917
+ return []
918
+ if len(lines) != 1:
919
+ raise ValueError("source entry has duplicate 参考音频 declarations")
920
+ value = lines[0].strip()
921
+ if not _contains_ref_token(value) and re.fullmatch(r"无(?:([^)\n]+))?。?", value):
922
+ return []
923
+ matches = list(AUDIO_REFERENCE_LINE_RE.finditer(value))
924
+ cursor = 0
925
+ for index, match in enumerate(matches):
926
+ if value[cursor : match.start()] != ("" if index == 0 else ";"):
927
+ matches = []
928
+ break
929
+ cursor = match.end()
930
+ if not matches or value[cursor:] not in {"", "。"}:
931
+ raise ValueError("source entry 参考音频 declaration is invalid")
932
+ bindings = sorted(
933
+ (
934
+ {
935
+ "slot_id": match.group(1),
936
+ "order": int(match.group(2)),
937
+ "path": _relative_path(match.group(3)),
938
+ "label": match.group(4).strip(),
939
+ "character": match.group(5).strip(),
940
+ "may_control": _scope_items(match.group(6)),
941
+ "must_not_control": _scope_items(match.group(7)),
942
+ }
943
+ for match in matches
944
+ ),
945
+ key=lambda binding: binding["order"],
946
+ )
947
+ if [binding["order"] for binding in bindings] != list(range(1, len(bindings) + 1)):
948
+ raise ValueError("source entry 参考音频 order must be contiguous from 1")
949
+ return bindings
950
+
951
+
882
952
  def _verify_markdown_source(
883
953
  root: Path,
884
954
  *,
@@ -902,6 +972,25 @@ def _verify_markdown_source(
902
972
  # reference field records creative intent, not this job's inputs.
903
973
  creator_supplied_ok=source_path.name == "分镜.md",
904
974
  )
975
+ # Voices follow the pictures, so neither numbering moves the other. A
976
+ # declared voice missing from the job fails here instead of being dropped
977
+ # on the way to the provider.
978
+ audio = (
979
+ _markdown_audio_bindings(section)
980
+ if source_path.name == "视频提示词.md"
981
+ else []
982
+ )
983
+ declared = declared + [
984
+ {**binding, "order": len(declared) + binding["order"]} for binding in audio
985
+ ]
986
+ audio_slots = {binding["slot_id"] for binding in audio}
987
+ for binding in bindings:
988
+ if (binding["slot_id"] in audio_slots) != (
989
+ binding["role"] == AUDIO_REFERENCE_ROLE
990
+ ):
991
+ raise ValueError(
992
+ "a 参考音频 slot binds role reference_audio, and only it does"
993
+ )
905
994
  comparable = [
906
995
  {key: binding[key] for key in (
907
996
  "slot_id",
@@ -913,6 +1002,9 @@ def _verify_markdown_source(
913
1002
  )}
914
1003
  for binding in bindings
915
1004
  ]
1005
+ for binding, comparison in zip(bindings, comparable):
1006
+ if "character" in binding:
1007
+ comparison["character"] = binding["character"]
916
1008
  if declared != comparable:
917
1009
  raise ValueError("job reference bindings do not match the selected source entry")
918
1010
 
@@ -1236,14 +1328,18 @@ def _inputs_current(root: Path, job: Mapping[str, Any]) -> bool:
1236
1328
  return False
1237
1329
 
1238
1330
 
1239
- def _load_adapter(config_path: Path, profile: str, root: Path) -> tuple[list[str], int]:
1331
+ def _load_adapter(config_path: Path, profile: str, root: Path) -> tuple[list[str], int, frozenset[str] | None]:
1240
1332
  resolved = config_path.expanduser().resolve()
1241
1333
  if resolved.is_relative_to(root):
1242
1334
  raise ValueError("adapter config must live outside the project")
1243
1335
  document = json.loads(resolved.read_text(encoding="utf-8"))
1244
1336
  adapters = document.get("adapters") if isinstance(document, Mapping) else None
1245
1337
  selected = adapters.get(profile) if isinstance(adapters, Mapping) else None
1246
- if not isinstance(selected, Mapping) or set(selected) - {"command", "timeout_seconds"}:
1338
+ if not isinstance(selected, Mapping) or set(selected) - {
1339
+ "command",
1340
+ "timeout_seconds",
1341
+ "reference_roles",
1342
+ }:
1247
1343
  raise ValueError(f"adapter profile is missing or invalid: {profile}")
1248
1344
  command = _string_list(selected.get("command"), label="adapter command", limit=32)
1249
1345
  if not command or any(not part for part in command):
@@ -1251,7 +1347,39 @@ def _load_adapter(config_path: Path, profile: str, root: Path) -> tuple[list[str
1251
1347
  timeout = selected.get("timeout_seconds", 300)
1252
1348
  if not isinstance(timeout, int) or isinstance(timeout, bool) or not 1 <= timeout <= MAX_TIMEOUT_SECONDS:
1253
1349
  raise ValueError(f"adapter timeout must be 1-{MAX_TIMEOUT_SECONDS} seconds")
1254
- return command, timeout
1350
+ roles = selected.get("reference_roles")
1351
+ declared = (
1352
+ frozenset(_string_list(roles, label="adapter reference_roles"))
1353
+ if roles is not None
1354
+ else None
1355
+ )
1356
+ return command, timeout, declared
1357
+
1358
+
1359
+ def _require_adapter_roles(
1360
+ job: Mapping[str, Any], declared: frozenset[str] | None
1361
+ ) -> None:
1362
+ """Refuse, before anything is submitted, a role the target model cannot take.
1363
+
1364
+ A profile may list the `reference_roles` its configured model accepts. A
1365
+ voice reference always needs that declaration: a model without the input
1366
+ can drop it without an error, and the drift is only heard in the take.
1367
+ """
1368
+ unsupported = sorted(
1369
+ {
1370
+ str(binding["role"])
1371
+ for binding in job.get("reference_bindings", [])
1372
+ if (declared is not None and binding["role"] not in declared)
1373
+ or (declared is None and binding["role"] == AUDIO_REFERENCE_ROLE)
1374
+ }
1375
+ )
1376
+ if unsupported:
1377
+ raise ValueError(
1378
+ f"adapter profile {job['adapter']} does not declare reference_roles "
1379
+ + ", ".join(unsupported)
1380
+ + "; nothing was submitted. Declare them only if this profile's model "
1381
+ "accepts them, or remove the binding (参考音频) from the source entry"
1382
+ )
1255
1383
 
1256
1384
 
1257
1385
  def _generic_adapter_error(
@@ -1599,7 +1727,8 @@ def run_job(root: Path, *, job_id: str, adapter_config: Path) -> dict[str, Any]:
1599
1727
  if _active_run(root, job_id) is not None:
1600
1728
  raise RuntimeError("this job is already running")
1601
1729
  job = _read_job(root, job_id)
1602
- command, timeout = _load_adapter(adapter_config, str(job["adapter"]), root)
1730
+ command, timeout, roles = _load_adapter(adapter_config, str(job["adapter"]), root)
1731
+ _require_adapter_roles(job, roles)
1603
1732
  try:
1604
1733
  receipt = _metadata_read_json(
1605
1734
  root,
@@ -1738,7 +1867,7 @@ def collect_job(root: Path, *, job_id: str, adapter_config: Path) -> dict[str, A
1738
1867
  output_root.mkdir(parents=True)
1739
1868
  with _project_lock(root):
1740
1869
  job = _read_job(root, job_id)
1741
- command, timeout = _load_adapter(adapter_config, str(job["adapter"]), root)
1870
+ command, timeout, _ = _load_adapter(adapter_config, str(job["adapter"]), root)
1742
1871
  target_run = None
1743
1872
  for run in reversed(_read_run_history(root, job_id)):
1744
1873
  if run.get("status") == "succeeded":
@@ -256,6 +256,12 @@ def _prompt_with_reference_contract(
256
256
  or not all(isinstance(item, str) and item.strip() for item in must_not_control)
257
257
  ):
258
258
  raise ValueError("reference binding semantics are invalid")
259
+ character = binding.get("character")
260
+ if character is not None:
261
+ if role != "reference_audio" or not isinstance(character, str) or not character.strip():
262
+ raise ValueError("reference audio character is invalid")
263
+ # The source's explicit speaker wins over the free-form asset label.
264
+ label = f"{character.strip()}音色参考"
259
265
  values = {
260
266
  "order": index,
261
267
  "reference": reference_tokens[index - 1] if reference_tokens else str(index),
@@ -102,6 +102,8 @@ license: MIT
102
102
  - 复杂群戏先锁空间锚点和屏幕方向,再决定景别变化。
103
103
  - 起终边界除了逐人姿态,还要写清本镜依赖的身体—物件—空间关系。关系在镜内不变时两端都保留;确需改变时,过程与终点必须有剧本依据。
104
104
  - 冻结关键帧只写该瞬间能看见的内容;文字、手指、反射和遮挡要可生成。
105
+ - 近景类关键帧挂着地点板生成时,图片常被拉宽成中远景;正文写具体取景(画面切在哪、脸在画面哪里、机位距离),出图仍偏宽就这一镜不挂地点板,把背景写进正文。
106
+ - 一集的关键帧要像同一部片:每条正文用同一句画风(写实就写写实,不混用「漫剧」「插画」这类会把一部分图拉向插画脸的词),每个场景写同一句光与色温;生成时先出该场景一张中远景作定调图并接受,其余镜头把它作为最后一张参考、只取色调、光线与质感,不取构图。
105
107
  - 《视觉设定.md》的连续性锁在生效镜头上原样写进冻结关键帧正文;锁面不因“参考图会交代”而省略。
106
108
  - 检查发现遗漏就直接修正文档,不生成覆盖表来证明自己检查过。镜头块里的「视觉依据」是本镜的依据字段,不是这里说的覆盖表。
107
109
 
@@ -54,7 +54,7 @@
54
54
  |---|---|
55
55
  | 一拍 = 一句台词 + 一个动作 | 若同一镜头目的和边界可承担,可保留一镜;不是等式 |
56
56
  | 一拍含多次行动转向或听者后果 | 按议程变化、听者反应与可执行语速拆镜;不使用固定句数/字数配额 |
57
- | 一拍是爆发动作 | 若单镜过载,可按起势/接触/结果等力学阶段拆,切点绑定精确状态 |
57
+ | 一拍是爆发动作 | 若单镜过载,可按起势/接触/结果等力学阶段拆,切点绑定精确状态;H3 的掌掴/推搡小样本中,掌掴一镜写全更可读,推搡拆成「到接触为止」与「从接触后状态起」才推退人物。H3 可据此先试;其他动作(拉住、带离)或模型须另测,不据此强制拆镜 |
58
58
  | 一拍是纯反应/沉默 | 只有承担反应或选择后果时独立成镜;停留由后果与节奏决定 |
59
59
 
60
60
  **建立镜头要自己挣到位置**。新地点确实可能需要让观众定位人物、入口或冲突区域,但
@@ -261,6 +261,8 @@
261
261
  - **单镜成立条件**:路径、接触、道具归属和退路都可读,且已接受时长足以完成。
262
262
  - **拆镜条件**:遮挡使钥匙易手不可读,或退到门边需要新的空间边界;切点绑定钥匙
263
263
  在谁手中、双方位置和朝向,而不是套“起势—发力—结果”固定链。
264
+ - **拆开后的代价**:两镜各自生成时背景、服装细节和脸型可能漂,前一镜也可能越过接触点才结束。
265
+ 两镜用同一组身份与地点参考,后一镜从已接受的接触后关键帧起,生成后核对切点两侧。
264
266
 
265
267
  ### 保持不动也可能是有效表演
266
268
 
@@ -55,6 +55,13 @@ license: MIT
55
55
  跟随本镜已解析的视频提示词语言并原样进入正文;正文每个非空行都以 `>` 组成 Markdown 引用块。
56
56
  重写内容不能改名、另造替代字段或改成普通段落。
57
57
 
58
+ 本镜说话的人物要保持音色、且目标模型接受参考音频时,在「输入参考图」下一行加
59
+ `- 参考音频:REF-<slot>(顺序:<n>)· <音频路径>《<中文名称>》(用途:音色;角色:<人物名>;控制:<范围>;不得控制:<范围>)`,
60
+ 多条用 `;` 连接(VID-27)。路径照抄该人物在《视觉设定.md》登记的 `- 声音参考:`,角色必须在本镜正文里说出
61
+ 《剧本.md》记在其名下的台词,本镜来源场次里别人也说过同一句时,在引文前的分句里点名说话人;
62
+ `不得控制` 至少写 `台词、语气、情绪`(逐项用顿号或逗号分隔,不用「与」连接)。音频的 `顺序` 与图片分开从 1 数,就是正文里的
63
+ `@音频N`、`<Audio N>` 或 `音频N`。音频不进「输入参考图」,分镜也不写这一行。
64
+
58
65
  ## 工作流
59
66
 
60
67
  1. 先检查每镜「输入参考图」。用户未手工点名图片时,根据本镜冻结关键帧和可见人物、地点、关键道具的一致性需求,在用户输入、
@@ -111,6 +111,20 @@
111
111
  晚半拍才让路、故意制造噪声掩饰,或看见别人停顿后才理解变化。所有差异都要来自角色立场、
112
112
  已接受信息和空间关系。
113
113
 
114
+ ### H3 反应镜:先点名,再给两处可见泄露(`craft_default`)
115
+
116
+ 目标为 H3、且一镜的职责就是接收一个消息(震惊、被打脸、得意、压怒)时,按这个顺序写:触发 → 一个情绪词点名
117
+ 「他立刻震惊」→ 两处当前景别读得到的泄露(目光停住、拇指悬住;笑容停住、手臂收紧)→ 落点并定住。
118
+ 反应在触发后一秒半内落下,剩下的时间给落点。
119
+
120
+ H3 实测四个反应节拍,这种写法盲评清晰度 3.9/5、自然度 4.0/5,四拍全部排第一或并列第一;只写情绪词会演过头
121
+ (强度 4.3、自然度 2.8),把可见反应逐段写满又太弱(强度 1.9),「不动声色的得意」这类反应尤其会被读错。
122
+ 情绪词在 H3 这套写法里是给表演定调的锚,不是可删的形容词;链条仍要写,但只写两步。
123
+
124
+ Seedance 2.5 样片档的同轮对照以逐段可见反应链最好,保留上文完整的触发、处理、可见反应与落点,
125
+ 不套用 H3 的两处泄露与 1.5 秒默认;具体见 `seedance-2.5.md`。其他模型沿用可见表演原则并实测,
126
+ 这轮证据不支持把 H3 配方外推成通用约束。
127
+
114
128
  只在注意真正交接时,简写“谁/什么拥有注意 → 哪个可见或可听触发 → 注意转给谁/什么”;没有交接
115
129
  就不补这一段。交接两端可以是角色、道具、证据、声源或已建立的空间区域;使用它们在
116
130
  `视觉设定.md` / `分镜.md` 中的可见 ID,不要为了套人物格式把道具或空间伪装成角色。
@@ -30,6 +30,12 @@
30
30
  一次生成本身。按当前参考输入检查结果,不把该次观察变成禁用规则。
31
31
  - 多镜容器包含切口;一镜到底是连续画面。按叙事目的选择,并保留生成后剪辑的余地。
32
32
 
33
+ ### 反应镜:保留可见反应链(`craft_default`)
34
+
35
+ 样片档同轮对照里,逐段可见反应链的盲评最好,只写情绪词最差。按触发 → 人物处理 → 当前景别读得到的
36
+ 可见反应 → 落点展开,不套用 H3 的「情绪点名+两处泄露+1.5 秒内落下」默认。
37
+ 仍按角色立场与镜头目的选动作;这是小样本起点,生成后核对可读性与自然度,不保证正式档同样表现。
38
+
33
39
  ### 两端都锚:`用途:起始帧` 与 `用途:结束帧`
34
40
 
35
41
  接口提供 `first_frame` 与 `last_frame` 两个位置,各自只收一张。
@@ -38,6 +44,9 @@
38
44
  按分镜已接受的终点制作尾帧并绑定 `用途:结束帧`;套件内生产入口见
39
45
  `$short-drama-storyboard` 的「首尾成对」。未提供尾帧时,在正文中说明终点。
40
46
 
47
+ 开场构图要锁住时用 `first_frame`,不要只在参考模式里写「@图片1 是开场画面」:样片档实测以上一段真实尾帧
48
+ 这样挂成参考,3 次开场与它的相似度只有 0.09–0.46。
49
+
41
50
  两个帧位仍然各占一个图片序号,所以 `@图片N` 的编号按素材出现顺序连续往下数,不跳过它们。
42
51
 
43
52
  首尾帧与其他参考能否混用,按目标接口核对并验证。H3 的互斥规则不能直接套到 2.5,
@@ -45,14 +54,14 @@
45
54
 
46
55
  ### 音色参考
47
56
 
48
- 目标接口支持 `reference_audio` 时,可用已接受的角色参考音频辅助约束声音。
57
+ 目标接口支持 `reference_audio` 时,可用已接受的角色参考音频辅助约束声音:在本镜写一行
58
+ `参考音频`(写法见本技能 SKILL 的字段说明),正文照 `顺序` 写「江晨说话时音色参考 @音频1」。
49
59
  参考可以来自预置音色合成或已授权录音,选型由视觉设定记录,生成后回听确认。
60
+ 样片档实测:绑一段该角色另一句话的合成做参考,青年角色 6 镜里 5 镜的声音明显靠近参考,中年角色 3 镜没有变化;
61
+ 把本句台词的合成挂上去,模型仍重新发声,不会原样播放。所以绑定只是提高命中,逐镜回听不能省。
62
+
63
+ `输入参考图` 仍只接受图片;音频须单独写在 `参考音频` 字段,不能仅凭正文提到音频就声称已经绑定。
50
64
 
51
- 当前 creator-first 的 `输入参考图` 只接受图片,封闭用途表也没有「音色」。
52
- 不要在该字段塞入音频或写 `用途:音色`;这会被校验器拒绝。
53
- 内置 adapter 可处理音频 role,但从五文档准备带音频的 job 尚无完整绑定路径。
54
- 需要该能力时使用已支持音频绑定的外部执行流程,并记录角色、音频来源和实际 role;
55
- 不能仅凭正文提到音频就声称已经绑定。
56
65
 
57
66
  ## 编辑与延长不是普通参考
58
67
 
@@ -63,6 +72,8 @@
63
72
  参数必须是 `ratio: adaptive`、`duration: -1`。
64
73
  - `extend`:写“在 @视频1 的基础上续写 N 秒”,再从输入视频的实际终点描述唯一动作和新终点;
65
74
  API 使用 `ratio: adaptive`。连续创作可另绑实际尾帧为 `@图片1`,只控制新段开场可见状态。
75
+ 实测延长的输出把原段完整带出,接缝处没有跳变,新段人声与原段接近;动作要无缝接下去时优先用延长,
76
+ 而不是另起一段再挂尾帧。
66
77
 
67
78
  若提示词写的是编辑而 job 标成参考,或反过来,2.5 仍会根据语义复判并报任务类型不匹配;因此任务
68
79
  类型是创作选择,不由 adapter 从若干关键词猜。
@@ -90,6 +101,8 @@
90
101
  声音时间线末尾直接写“对白直接从指定中文首字开始;不生成外语前导、语气词、额外人声或重复对白”,
91
102
  只写一次。没有这些上游边界时不自行追加固定负面后缀。
92
103
  - 默认禁字幕时明确写“全程无字幕、无对白文字叠加”,且正文不出现 `【】`;画内获准文字仍独立说明。
104
+ - 手机收到通知这类画面,2.5 常把通知画成浮在画面上的气泡或计数器(实测正式档 2/2),数字还会乱码。
105
+ 写明「通知只在手机屏幕里亮起,画面上不出现浮层」,生成后检查;要显示的数字交给剪辑叠加。
93
106
 
94
107
  ## 连续容器
95
108
 
@@ -43,6 +43,7 @@
43
43
  | VID-24 | structural_invariant | An image-to-video shot carries the storyboard's `REF-...` and `PLAN-...` slots unchanged, each with its 用途, so the motion document itself answers which start frame and which identity, geography, and prop pictures this shot sends — and, for `PLAN-...`, in which order the creator attaches them. The reference mode — first frame, first and last frame, or multi-slot reference — is read from that 用途 combination and translated by the matched model dialect; where a model makes frame and reference conditioning mutually exclusive, the start frame joins the reference group rather than splitting the request. |
44
44
  | VID-25 | structural_invariant | Quoted Chinese in a copyable body is delivered, not authored. Every run of four or more Han characters inside a `<d>` tag or quotation marks resolves, ignoring punctuation and spacing, to text that already exists in `剧本.md`, `视觉设定.md` or `分镜.md`. A line the shot wishes the character had said belongs upstream in the screenplay, not in the prompt. Runs of three characters or fewer are in-frame labels and interjections far more often than lines, so they stay with review rather than the mechanical check. |
45
45
  | VID-26 | craft_default | Absent a director's choice, performance intensity follows form and genre. At motion-comic and wish-fulfilment peaks, key poses and expressions go outward—an expression change readable at a glance, whole-arm gestures, emotion carried by the upper body and the whole face—still written as common whole-body actions, never with the plot resting on a micro-expression. AI live action keeps a subtler realism but still gives each peak one readable performance beat. An inferred inner state is not deleted but translated into a visible expression or movement. |
46
+ | VID-27 | structural_invariant | A voice reference is bound on its own `参考音频` line of a `MOTION-...` entry, never inside `输入参考图` and never on a `SHOT-...`. Each slot names a `人物` entry of `视觉设定.md` who speaks in this copyable body a line the screenplay gives that character, points at a readable project audio file equal to the path that character's `声音参考` records, has 用途 `音色`, and numbers audio 顺序 from 1 separately from pictures. Whether the bound voice actually carried into the take is heard after generation, not asserted here. |
46
47
 
47
48
  ### `CON`
48
49
 
@@ -18,6 +18,11 @@
18
18
  模型可以由本技能的版本化方言文件提供推荐值。每一条取值都由创作者按自己接入的执行端声明一次,
19
19
  本集共用。生产端具体接哪个供应商、用什么字段,属于 `$short-drama-produce` 的 adapter 档案,不进入创作文档。
20
20
 
21
+ 还没选定 `target_video_model` 时,一组小样本对照(同一起始帧与镜头说明,每类每模型 2 次,盲评排序)可作先试哪个的参考:
22
+ 没有一个模型在所有镜头类型上领先。H3 在建立镜推近、多人同框只一人说话时最好;Seedance 2.5 在反应镜、
23
+ 走位跟摇、手部插入与夜景情绪近景时最好;Wan 3.0 多数类型居中或靠后;单人对白近景三者持平。
24
+ 本集最多的是哪类镜头,就先拿那一类试。
25
+
21
26
  **`craft_default`**: 能力轴是**声明**出来的,不是推断出来的。没有声明就按“未声明时怎么写”写;
22
27
  不要因为某个执行端“据说支持”某种写法就改写已接受的镜头边界、时长或起止状态。
23
28
 
@@ -66,7 +66,10 @@ Wan 3.0 用中文自然指令,素材写成 `图1`、`视频1`、`音频1`。
66
66
  4.6 个可发声字/秒:21 字配 5 秒、41 字配 10 秒都以常速说完。41 字塞进 5 秒仍说完,但赶到
67
67
  8.2–8.7 字/秒;66 字塞进 5 秒时只说出前 21–24 字,其余静默丢掉,接口不报错。所以超出容量时
68
68
  看不到失败提示,生成后逐字核对台词是否说完。
69
- - 说话人的声音要跟参考音频走时,写「图1 中的女子说话时,音色参考音频1」;这是官方写法,本套件未实测。
69
+ - 说话人的声音要跟参考音频走时,写「图1 中的女子说话时,音色参考音频1」。实测挂一段该角色另一句话的合成做参考,
70
+ 两个角色 9 镜的声音都明显靠向参考,口型与不挂参考时接近;这是跨镜保持角色音色的默认做法。
71
+ - 把这句台词本身的合成挂成音频1、写「口型与音频1 逐字同步」时,生成的人声几乎就是这段音频,音色最稳;
72
+ 代价是口型略差(句中停住、提前张嘴更常见)。音色必须一致时用它,生成后逐镜看口型。
70
73
  - 双人镜里不说话的人按 [对白、口型与声音](camera-audio-continuity.md#精确引用) 的顺序处理;Wan 3.0 的
71
74
  小样本太小,写不写「他不说话、嘴唇闭合」没有看出差别。
72
75