@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.
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
@@ -1,204 +0,0 @@
1
- {
2
- "draft_cover_path": "",
3
- "type": "combination",
4
- "combination_id": "",
5
- "draft_config_path": "",
6
- "draft": {
7
- "version": 360000,
8
- "new_version": "111.0.0",
9
- "source": "default",
10
- "update_time": 0,
11
- "keyframe_graph_list": [],
12
- "duration": 86600000,
13
- "uneven_animation_template_info": {
14
- "order": "",
15
- "sub_template_info_list": [],
16
- "content": "",
17
- "composition": ""
18
- },
19
- "tracks": [],
20
- "canvas_config": {
21
- "height": 1080,
22
- "ratio": "original",
23
- "width": 1920
24
- },
25
- "relationships": [],
26
- "last_modified_platform": {
27
- "app_version": "",
28
- "os_version": "",
29
- "os": "",
30
- "hard_disk_id": "",
31
- "device_id": "",
32
- "mac_address": "",
33
- "app_id": 0,
34
- "app_source": ""
35
- },
36
- "color_space": -1,
37
- "static_cover_image_path": "",
38
- "render_index_track_mode_on": true,
39
- "keyframes": {
40
- "handwrites": [],
41
- "videos": [],
42
- "texts": [],
43
- "audios": [],
44
- "adjusts": [],
45
- "stickers": [],
46
- "filters": [],
47
- "effects": []
48
- },
49
- "platform": {
50
- "app_version": "9.6.0",
51
- "os_version": "15.1",
52
- "os": "mac",
53
- "hard_disk_id": "9d5cea4f22458a4e59d643d07162d324",
54
- "device_id": "0b02b9bd41947815545481af8a5bde46",
55
- "mac_address": "f8e784e2995a6aed158ef584d6245be8",
56
- "app_id": 3704,
57
- "app_source": "lv"
58
- },
59
- "smart_ads_info": {
60
- "page_from": "",
61
- "routine": "",
62
- "draft_url": ""
63
- },
64
- "path": "",
65
- "id": "AFCFCFAC-468E-46B6-92DC-8F2C66411CA3",
66
- "name": "",
67
- "lyrics_effects": [],
68
- "fps": 30,
69
- "config": {
70
- "use_float_render": false,
71
- "adjust_max_index": 1,
72
- "multi_language_list": [],
73
- "multi_language_main": "none",
74
- "lyrics_taskinfo": [],
75
- "lyrics_recognition_id": "",
76
- "system_font_list": [],
77
- "sticker_max_index": 1,
78
- "multi_language_mode": "none",
79
- "material_save_mode": 0,
80
- "attachment_info": [],
81
- "video_mute": false,
82
- "maintrack_adsorb": false,
83
- "multi_language_current": "none",
84
- "subtitle_sync": true,
85
- "subtitle_recognition_id": "",
86
- "combination_max_index": 1,
87
- "extract_audio_last_index": 1,
88
- "subtitle_taskinfo": [],
89
- "record_audio_last_index": 1,
90
- "lyrics_sync": true,
91
- "original_sound_last_index": 1
92
- },
93
- "create_time": 0,
94
- "draft_type": "video",
95
- "function_assistant_info": {
96
- "auto_adjust": false,
97
- "auto_caption": false,
98
- "enhande_voice": false,
99
- "auto_adjust_fixed": false,
100
- "fixed_rec_applied": false,
101
- "retouch_segid_list": [],
102
- "retouch": false,
103
- "enhande_voice_fixed": false,
104
- "enhance_quality_segid_list": [],
105
- "deflicker_segid_list": [],
106
- "auto_caption_template_id": "",
107
- "color_correction": false,
108
- "caption_opt": false,
109
- "audio_noise_segid_list": [],
110
- "auto_caption_segid_list": [],
111
- "normalize_loudness_fixed": false,
112
- "normalize_loudness_segid_list": [],
113
- "eye_correction": false,
114
- "enhance_quality_fixed": false,
115
- "color_correction_fixed_value": 50,
116
- "normalize_loudness_audio_denoise_segid_list": [],
117
- "video_noise_segid_list": [],
118
- "eye_correction_segid_list": [],
119
- "retouch_fixed": false,
120
- "auto_adjust_segid_list": [],
121
- "enhance_voice_segid_list": [],
122
- "smooth_slow_motion_fixed": false,
123
- "caption_opt_segid_list": [],
124
- "color_correction_segid_list": [],
125
- "smooth_slow_motion": false,
126
- "smart_segid_list": [],
127
- "smart_rec_applied": false,
128
- "color_correction_fixed": false,
129
- "fps": {
130
- "den": 1,
131
- "num": 0
132
- },
133
- "auto_adjust_fixed_value": 50,
134
- "normalize_loudness": false,
135
- "enhance_quality": false
136
- },
137
- "materials": {
138
- "audio_fades": [],
139
- "images": [],
140
- "flowers": [],
141
- "smart_crops": [],
142
- "common_mask": [],
143
- "time_marks": [],
144
- "shapes": [],
145
- "vocal_beautifys": [],
146
- "plugin_effects": [],
147
- "audios": [],
148
- "smart_relights": [],
149
- "video_radius": [],
150
- "vocal_separations": [],
151
- "video_effects": [],
152
- "video_shadows": [],
153
- "videos": [],
154
- "manual_deformations": [],
155
- "video_strokes": [],
156
- "audio_effects": [],
157
- "realtime_denoises": [],
158
- "handwrites": [],
159
- "texts": [],
160
- "text_templates": [],
161
- "hsl_curves": [],
162
- "tail_leaders": [],
163
- "manual_beautys": [],
164
- "material_colors": [],
165
- "multi_language_refs": [],
166
- "material_animations": [],
167
- "chromas": [],
168
- "speeds": [],
169
- "audio_pannings": [],
170
- "sound_channel_mappings": [],
171
- "ai_translates": [],
172
- "video_trackings": [],
173
- "primary_color_wheels": [],
174
- "placeholders": [],
175
- "stickers": [],
176
- "placeholder_infos": [],
177
- "beats": [],
178
- "audio_pitch_shifts": [],
179
- "color_curves": [],
180
- "green_screens": [],
181
- "drafts": [],
182
- "digital_humans": [],
183
- "effects": [],
184
- "digital_human_model_dressing": [],
185
- "audio_balances": [],
186
- "hsl": [],
187
- "log_color_wheels": [],
188
- "canvases": [],
189
- "loudnesses": [],
190
- "transitions": [],
191
- "audio_track_indexes": []
192
- },
193
- "is_drop_frame_timecode": false,
194
- "free_render_index_mode_on": false
195
- },
196
- "category_id": "",
197
- "precompile_combination": false,
198
- "combination_type": "none",
199
- "formula_id": "",
200
- "name": "",
201
- "id": "",
202
- "draft_file_path": "",
203
- "category_name": ""
204
- }
@@ -1,74 +0,0 @@
1
- # Pair rebuilt picture with a retained adopted soundtrack
2
-
3
- When the picture is rebuilt/reframed but the adopted mix must not change, use
4
- `pair_media.py` before `assemble.py --audio-mode adopted-packet-copy`. This is a
5
- real two-input stream copy, not re-encoding an old finished video's picture.
6
- It does not generate narration, align speech, design packaging, or create an
7
- end card. It does not decide which inputs the editor intended to adopt.
8
-
9
- ```json
10
- {
11
- "artifact": "media_pair",
12
- "schema_version": 1,
13
- "picture": {"path": "/project/picture.mp4"},
14
- "audio": {"path": "/project/adopted.m4a", "selected_stream": 0}
15
- }
16
- ```
17
-
18
- Both paths must be explicit local files. `selected_stream` is the audio ordinal
19
- (`a:N`), not the absolute stream index. The audio donor may be an old MP4 with
20
- unrelated video: only the selected audio is used. The picture's audio is ignored.
21
- Both files must exist when the plan is read. The plan is strict: unknown fields
22
- do not silently request unsupported retime, gain, trimming or offset operations
23
- (legacy `sha256` keys are ignored).
24
-
25
- ```bash
26
- python3 scripts/pair_media.py pair.json --output-dir new-pair-directory --plan-only
27
- # A PLANNED directory is not reusable as a rendered candidate: choose a NEW one.
28
- python3 scripts/pair_media.py pair.json --output-dir new-render-directory
29
- ```
30
-
31
- The output directory must not already exist. No existing input, final asset,
32
- current pointer, or user-approved version is overwritten. `--plan-only` checks
33
- actual inputs and timing but creates no video and invokes no mux. A rendered run
34
- provides:
35
-
36
- - `paired.mp4`: selected picture and audio, both compressed-stream copied.
37
- - `pair_run.json`: `PLANNED`, `PAIR_RENDERED`, or `FAILED`, explicit input and
38
- plan/output paths, frame count, timing tolerance and output stream numbering.
39
- - `picture_identity.json`: decoder, geometry/color, complete actual frame clock
40
- and compressed packet size/timing/side-data facts.
41
- - `adopted_audio_identity.json`: donor and output AAC packet/decoder facts.
42
- - mux command/log for reconstruction and diagnosis.
43
-
44
- The first implementation deliberately accepts H264 or HEVC in an MP4-family container,
45
- complete zero-origin CFR picture (1–120 fps), and contiguous AAC packets with
46
- known nominal sample duration. No VFR, retime, offset, format conversion, padding,
47
- trimming, `-shortest`, normalization or gain is inferred. Packet priming and skip
48
- metadata are retained. The packet clock must also agree with the declared audio
49
- stream interval (at most one nominal priming packet before the start; the packet
50
- end matches the stream end within one sample). This is checked again on the
51
- actual muxed output. Picture/audio starts and ends must differ by no more than
52
- the larger of one picture frame or one nominal AAC packet. The interval check is
53
- compatibility, **not perceptual synchronization or acoustic alignment**. A tiny
54
- container-tail tolerance does not authorize cutting a word.
55
-
56
- Muxing goes to a staging file. The output's video decoder/packet sizes and
57
- timestamps/full frame clock/geometry/color and adopted AAC packets must match
58
- the inputs, the output must contain only `v:0,a:0`, and full decode must pass
59
- before final publication. Failure leaves a FAILED record and logs, not a
60
- final `paired.mp4`. Rebuilding source geometry is certified by its own upstream
61
- render/map evidence, not by the existence of a successfully paired container.
62
-
63
- ## Subtitle integration: bind AFTER pairing
64
-
65
- The output audio ordinal is always **0**, even when the donor used `a:1`.
66
- Create the complete output-clock `subtitle_track.json` against
67
- `subtitles.track_binding.current_bindings(paired_video, 0)`, then run the existing
68
- adopted assembly path. Do not reuse a binding computed against the picture-only
69
- file or the donor's old stream ordinal. A present stale subtitle track fails rather than falling
70
- back to estimated timings. See [subtitle-track.md](subtitle-track.md).
71
-
72
- Pairing preserves the input picture, including any explicit black tail; it does
73
- not turn that black tail into a branded end card. Listening, normal-speed review,
74
- editorial intent and release approval remain separate from this mechanical proof.
@@ -1,293 +0,0 @@
1
- #!/usr/bin/env python3
2
- """Compose caller-rendered RGBA pixels over a frozen H264/CFR/AAC base.
3
-
4
- This operation validates pixels and declared inputs. It does not generate or
5
- interpret titles, dialogue, brands, typography, or release approval.
6
- """
7
-
8
- import argparse
9
- from fractions import Fraction
10
- import json
11
- from pathlib import Path
12
- import subprocess
13
-
14
- from adoption.frozen_audio import probe_audio_packets, verify_adopted_audio
15
- from pair_media import probe_picture, validate_pair_timing
16
- from adoption.strict_inputs import (
17
- canonical_fraction, require_declared_path, require_fields, require_integer, run_logged,
18
- without_digests, write_json_atomic,
19
- )
20
-
21
-
22
- PATTERN = "frame_%06d.png"
23
- ENCODING = {
24
- "video_codec": "libx264", "preset": "fast", "crf": 18,
25
- "pixel_format": "yuv420p", "color": "bt709_tv", "audio_codec": "copy",
26
- }
27
-
28
-
29
- def _fields(value, required):
30
- value = without_digests(value, "compose plan")
31
- require_fields(value, required, "compose plan")
32
- return value
33
-
34
-
35
- def _local_file(value, label):
36
- _fields(value, ["path"])
37
- return {"path": str(require_declared_path(value, label))}
38
-
39
-
40
- def _sequence_paths(directory, pattern, start, end):
41
- if pattern != PATTERN:
42
- raise ValueError(f"Only the literal simple pattern {PATTERN!r} is supported")
43
- directory = Path(directory).resolve()
44
- if not directory.is_dir():
45
- raise FileNotFoundError(f"Sequence directory missing: {directory}")
46
- expected = [directory / (pattern % index) for index in range(start, end)]
47
- actual = sorted(directory.iterdir(), key=lambda item: item.name)
48
- if [item.name for item in actual] != [item.name for item in expected]:
49
- raise ValueError("Sequence requires exact contiguous files with no missing or extra entries")
50
- if not all(path.is_file() for path in expected):
51
- raise FileNotFoundError("Sequence frame missing or not a file")
52
- return directory, expected
53
-
54
-
55
- def _probe_image(path):
56
- result = subprocess.run(
57
- ["ffprobe", "-v", "error", "-select_streams", "v:0", "-show_streams",
58
- "-show_entries", "stream=codec_name,width,height,pix_fmt", "-of", "json", str(path)],
59
- capture_output=True, text=True, timeout=600,
60
- )
61
- if result.returncode or result.stderr.strip():
62
- raise ValueError(f"Image probe failed: {result.stderr.strip()}")
63
- streams = json.loads(result.stdout).get("streams", [])
64
- if len(streams) != 1 or streams[0].get("codec_name") != "png":
65
- raise ValueError("Foreground assets must be PNG images")
66
- return streams[0]
67
-
68
-
69
- def _validate_rgba(path, width, height):
70
- image = _probe_image(path)
71
- if (image.get("width"), image.get("height")) != (width, height):
72
- raise ValueError("PNG must exactly match the full output canvas")
73
- if image.get("pix_fmt") != "rgba":
74
- raise ValueError("PNG must contain an actual RGBA pixel format")
75
-
76
-
77
- def _sequence(value, required, *, local_count, width, height):
78
- value = _fields(value, required)
79
- directory = value["directory"]
80
- if not isinstance(directory, str) or not directory or "://" in directory:
81
- raise ValueError("Sequence requires a local directory")
82
- if value["pattern"] != PATTERN:
83
- raise ValueError(f"Only the literal simple pattern {PATTERN!r} is supported")
84
- resolved, paths = _sequence_paths(directory, value["pattern"], 0, local_count)
85
- for path in paths:
86
- _validate_rgba(path, width, height)
87
- return {**value, "directory": str(resolved), "frame_count": len(paths)}, paths
88
-
89
-
90
- def validate_endcard(value, *, foreground_end, total_frames, width, height):
91
- """Validate the exact endcard union and return normalized data and bound paths."""
92
- if not isinstance(value, dict) or value.get("kind") not in {"none", "still", "sequence"}:
93
- raise ValueError("Endcard kind must be none, still, or sequence")
94
- value = without_digests(value, "endcard")
95
- if value["kind"] == "none":
96
- _fields(value, ["kind"])
97
- if foreground_end != total_frames:
98
- raise ValueError("No endcard requires foreground to cover the full frame clock")
99
- return {"kind": "none"}, []
100
- if value["kind"] == "still":
101
- required = ["kind", "path", "start_frame", "end_frame"]
102
- else:
103
- required = ["kind", "directory", "pattern", "start_frame", "end_frame"]
104
- _fields(value, required)
105
- end_start = require_integer(value["start_frame"], "endcard start_frame")
106
- end_end = require_integer(value["end_frame"], "endcard end_frame", minimum=1)
107
- if end_start >= end_end:
108
- raise ValueError("Endcard interval must be non-empty")
109
- if end_start != foreground_end or end_end != total_frames:
110
- raise ValueError("Foreground and endcard must exactly partition the base frame clock")
111
- if value["kind"] == "still":
112
- asset = _local_file({"path": value["path"]}, "endcard")
113
- _validate_rgba(asset["path"], width, height)
114
- return {**value, **asset}, [Path(asset["path"])]
115
- return _sequence(
116
- value, required, local_count=end_end - end_start, width=width, height=height,
117
- )
118
-
119
-
120
- def validate_plan(plan_path):
121
- plan_path = Path(plan_path).resolve()
122
- plan = json.loads(plan_path.read_bytes())
123
- _fields(plan, ["artifact", "schema_version", "base", "video", "foreground",
124
- "endcard", "producer_receipt"])
125
- plan = {**plan, "video": _fields(plan["video"], ["fps", "width", "height", "total_frames"])}
126
- if plan["artifact"] != "foreground_compose_plan" or type(plan["schema_version"]) is not int \
127
- or plan["schema_version"] != 1:
128
- raise ValueError("Unsupported foreground_compose_plan schema")
129
- base = _local_file(plan["base"], "base")
130
- receipt = _local_file(plan["producer_receipt"], "producer_receipt")
131
- fps = canonical_fraction(plan["video"]["fps"], "video fps")
132
- width = require_integer(plan["video"]["width"], "video width", minimum=1)
133
- height = require_integer(plan["video"]["height"], "video height", minimum=1)
134
- total = require_integer(plan["video"]["total_frames"], "video total_frames", minimum=1)
135
- _fields(plan["foreground"], ["directory", "pattern", "start_frame", "end_frame"])
136
- foreground_start = require_integer(plan["foreground"]["start_frame"], "foreground start_frame")
137
- foreground_end = require_integer(plan["foreground"]["end_frame"], "foreground end_frame", minimum=1)
138
- if foreground_start != 0 or foreground_end > total:
139
- raise ValueError("Foreground must start at frame 0 and not exceed the frame clock")
140
- foreground, foreground_paths = _sequence(
141
- plan["foreground"], ["directory", "pattern", "start_frame", "end_frame"],
142
- local_count=foreground_end, width=width, height=height,
143
- )
144
- normalized_endcard, endcard_paths = validate_endcard(
145
- plan["endcard"], foreground_end=foreground_end, total_frames=total,
146
- width=width, height=height,
147
- )
148
- picture = probe_picture(base["path"])
149
- decoder = picture["decoder"]
150
- required_color = {"codec_name": "h264", "pix_fmt": "yuv420p", "color_range": "tv",
151
- "color_space": "bt709", "color_transfer": "bt709",
152
- "color_primaries": "bt709"}
153
- if any(decoder.get(key) != value for key, value in required_color.items()):
154
- raise ValueError("Base must be H264 yuv420p with explicit BT.709 TV color metadata")
155
- if (decoder.get("width"), decoder.get("height"), picture["frame_count"],
156
- Fraction(picture["fps"])) != (width, height, total, fps):
157
- raise ValueError("Declared fps/canvas/count does not match the actual base")
158
- audio = probe_audio_packets(base["path"], 0)
159
- validate_pair_timing(picture, audio)
160
- return {
161
- "plan_path": plan_path, "base": base,
162
- "video": {"fps": str(fps), "width": width, "height": height, "total_frames": total},
163
- "foreground": foreground, "foreground_paths": foreground_paths,
164
- "endcard": normalized_endcard, "endcard_paths": endcard_paths,
165
- "producer_receipt": receipt, "picture": picture, "audio": audio,
166
- }
167
-
168
-
169
- def _run_ffmpeg(command, directory):
170
- run_logged(command, directory, "compose", timeout=600)
171
-
172
-
173
- def _verify_output(path, validated):
174
- output_picture = probe_picture(path)
175
- expected = validated["picture"]
176
- for key in ("frame_pts", "frame_count", "fps", "duration", "start"):
177
- if output_picture[key] != expected[key]:
178
- raise ValueError(f"Output picture {key} differs from the base")
179
- decoder_keys = ("codec_name", "width", "height", "pix_fmt", "sample_aspect_ratio",
180
- "field_order", "color_range", "color_space", "color_transfer",
181
- "color_primaries", "chroma_location")
182
- if {key: output_picture["decoder"].get(key) for key in decoder_keys} != \
183
- {key: expected["decoder"].get(key) for key in decoder_keys}:
184
- raise ValueError("Output codec/canvas/color metadata differs from the base")
185
- proof = verify_adopted_audio(validated["base"]["path"], path, 0, 0)
186
- if validate_pair_timing(output_picture, proof["output"]) != \
187
- validate_pair_timing(expected, validated["audio"]):
188
- raise ValueError("Output audio interval changed")
189
- result = subprocess.run(
190
- ["ffmpeg", "-v", "error", "-xerror", "-threads", "2", "-i", str(path),
191
- "-map", "0:v:0", "-map", "0:a:0", "-f", "null", "-"],
192
- capture_output=True, text=True, timeout=600,
193
- )
194
- if result.returncode or result.stderr.strip():
195
- raise ValueError("Foreground output full decode failed")
196
- return output_picture, proof
197
-
198
-
199
- def run_compose(plan_path, output_dir, *, plan_only=False):
200
- directory = Path(output_dir).resolve()
201
- directory.mkdir(parents=True, exist_ok=False)
202
- report_path = directory / "foreground_run.json"
203
- staged = directory / "foreground.rendering.mp4"
204
- output = directory / "foreground.mp4"
205
- report = {"artifact": "foreground_compose_run", "schema_version": 1,
206
- "status": "PREPARING", "direct_listening": "NOT_CHECKED",
207
- "normal_speed_review": "NOT_CHECKED", "release_approved": False,
208
- "encoding": ENCODING}
209
- write_json_atomic(report_path, report)
210
- try:
211
- value = validate_plan(plan_path)
212
- report.update(
213
- plan={"path": str(value["plan_path"])},
214
- base=value["base"], video=value["video"], foreground=value["foreground"],
215
- endcard=value["endcard"],
216
- producer_receipt={**value["producer_receipt"],
217
- "semantic_validation": "DECLARED_NOT_CHECKED"},
218
- )
219
- if plan_only:
220
- report["status"] = "PLANNED"
221
- write_json_atomic(report_path, report)
222
- return report
223
- fps = value["video"]["fps"]
224
- command = ["ffmpeg", "-nostdin", "-v", "error", "-n", "-copyts",
225
- "-i", value["base"]["path"], "-framerate", fps, "-start_number", "0",
226
- "-i", str(Path(value["foreground"]["directory"]) / PATTERN)]
227
- if value["endcard"]["kind"] == "none":
228
- filters = (
229
- "[0:v]format=rgb24[base];[1:v]format=rgba,setpts=PTS-STARTPTS[fg];"
230
- "[base][fg]overlay=0:0:eof_action=pass:format=rgb,"
231
- "scale=in_range=pc:out_range=tv:"
232
- "in_color_matrix=bt709:out_color_matrix=bt709,format=yuv420p,"
233
- f"trim=end_frame={value['video']['total_frames']}[outv]"
234
- )
235
- elif value["endcard"]["kind"] == "still":
236
- command += ["-loop", "1", "-framerate", fps, "-i", value["endcard"]["path"]]
237
- else:
238
- command += ["-framerate", fps, "-start_number", "0", "-i",
239
- str(Path(value["endcard"]["directory"]) / PATTERN)]
240
- if value["endcard"]["kind"] != "none":
241
- end_start = value["endcard"]["start_frame"]
242
- end_offset = Fraction(end_start, 1) / Fraction(fps)
243
- filters = (
244
- f"[0:v]format=rgb24[base];[1:v]format=rgba,setpts=PTS-STARTPTS[fg];"
245
- f"[2:v]format=rgba,setpts=PTS-STARTPTS+{end_offset.numerator}/"
246
- f"{end_offset.denominator}/TB[end];"
247
- f"[base][fg]overlay=0:0:eof_action=pass:format=rgb:"
248
- f"enable='lt(n,{end_start})'[body];"
249
- f"[body][end]overlay=0:0:eof_action=repeat:format=rgb:"
250
- f"enable='gte(n,{end_start})',scale=in_range=pc:out_range=tv:"
251
- "in_color_matrix=bt709:out_color_matrix=bt709,format=yuv420p,"
252
- f"trim=end_frame={value['video']['total_frames']}[outv]"
253
- )
254
- command += ["-filter_complex", filters, "-map", "[outv]", "-map", "0:a:0",
255
- "-c:v", ENCODING["video_codec"], "-preset", ENCODING["preset"],
256
- "-crf", str(ENCODING["crf"]), "-threads", "2",
257
- "-pix_fmt", ENCODING["pixel_format"],
258
- "-color_range", "tv", "-colorspace", "bt709", "-color_primaries", "bt709",
259
- "-color_trc", "bt709", "-c:a", "copy", "-movie_timescale",
260
- str(value["audio"]["sample_rate"]), "-movflags", "+faststart",
261
- str(staged)]
262
- _run_ffmpeg(command, directory)
263
- output_picture, audio_proof = _verify_output(staged, value)
264
- write_json_atomic(directory / "picture_identity.json", output_picture)
265
- write_json_atomic(directory / "adopted_audio_identity.json", audio_proof)
266
- report["output"] = {"path": str(output),
267
- "full_decode": "PASS", "frame_clock": "EXACT",
268
- "audio_packet_identity": "EXACT"}
269
- staged.rename(output)
270
- report["status"] = "FOREGROUND_RENDERED"
271
- write_json_atomic(report_path, report)
272
- return report
273
- except Exception as exc:
274
- staged.unlink(missing_ok=True)
275
- output.unlink(missing_ok=True)
276
- report.pop("output", None)
277
- report.update(status="FAILED", error=f"{type(exc).__name__}: {exc}")
278
- write_json_atomic(report_path, report)
279
- raise
280
-
281
-
282
- def main():
283
- parser = argparse.ArgumentParser(description=__doc__)
284
- parser.add_argument("plan")
285
- parser.add_argument("--output-dir", required=True)
286
- parser.add_argument("--plan-only", action="store_true")
287
- args = parser.parse_args()
288
- report = run_compose(args.plan, args.output_dir, plan_only=args.plan_only)
289
- print(json.dumps(report, ensure_ascii=False, indent=2))
290
-
291
-
292
- if __name__ == "__main__":
293
- main()