hyperframes 0.8.192 → 0.8.194

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 (189) hide show
  1. package/dist/{add-TZU2UW7A.js → add-47UAIWY4.js} +12 -12
  2. package/dist/{animatedGifPrep-NLAXC3VV.js → animatedGifPrep-TFSQZWK6.js} +8 -7
  3. package/dist/api.js +18 -17
  4. package/dist/{asset-JAJXA6LB.js → asset-AU7PZHXA.js} +2 -2
  5. package/dist/{assetCataloger-4WTX3J4I.js → assetCataloger-HC5N6WPT.js} +8 -7
  6. package/dist/{audio-XLWC7JKS.js → audio-7AWMZWBM.js} +14 -13
  7. package/dist/{auth-5K6X3NPJ.js → auth-GXH3QNDJ.js} +1 -1
  8. package/dist/{autoUpdate-RNV7SKGB.js → autoUpdate-GVGP5O2I.js} +3 -3
  9. package/dist/{backgroundChecks-QXLVZVGX.js → backgroundChecks-IUJM2Q35.js} +3 -3
  10. package/dist/backgroundChecksWorker.js +3 -3
  11. package/dist/{batchRender-THKSPDZR.js → batchRender-3XS2Y2Y4.js} +4 -4
  12. package/dist/{beats-K7CGCFVL.js → beats-U64B4S7Z.js} +9 -9
  13. package/dist/{benchmark-PHRDIXNL.js → benchmark-2YUPAI76.js} +9 -9
  14. package/dist/{browser-GN6R5ZSK.js → browser-DBFH7KXM.js} +5 -5
  15. package/dist/{bundleWithLocalizedFonts-C325UNR4.js → bundleWithLocalizedFonts-XWL7KSQJ.js} +2 -2
  16. package/dist/{capture-BZ7APOGQ.js → capture-E4VW75NM.js} +2 -2
  17. package/dist/{capture-6UBQFHZQ.js → capture-YWH7BPBI.js} +13 -12
  18. package/dist/{captureCompositionFrame-VORSCTWC.js → captureCompositionFrame-IG6S2OHG.js} +13 -12
  19. package/dist/{catalog-PHU2RUZ3.js → catalog-LZIZLZ34.js} +12 -12
  20. package/dist/{catch-up-HCNRM46J.js → catch-up-ZVNPIACY.js} +4 -4
  21. package/dist/{check-TJ5ZLB4I.js → check-EKEOD4UM.js} +38 -37
  22. package/dist/{checkBrowser-DWP7ZXJ7.js → checkBrowser-MYH6HPHH.js} +33 -32
  23. package/dist/{chunk-XF77MK3J.js → chunk-2AJU7PZ6.js} +1 -1
  24. package/dist/chunk-2D5WP2NI.js +3901 -0
  25. package/dist/{chunk-FBVUAH7N.js → chunk-2OYYNKJ4.js} +1 -1
  26. package/dist/{chunk-R2U2G4OS.js → chunk-37IHQVNQ.js} +1 -1
  27. package/dist/{chunk-IMSI7IFW.js → chunk-3OSBSUN3.js} +18 -18
  28. package/dist/{chunk-RHYOWDI6.js → chunk-3S3CXF4W.js} +2 -2
  29. package/dist/{chunk-YMQQXPD5.js → chunk-452UK7E7.js} +5 -12
  30. package/dist/{chunk-L2YGSAOL.js → chunk-4FVNEXSV.js} +3 -3
  31. package/dist/{chunk-7UZC4N62.js → chunk-4GMDL4N5.js} +1 -1
  32. package/dist/{chunk-YWRQHRDY.js → chunk-4PIPQPCW.js} +7 -7
  33. package/dist/{chunk-J2ASTCU6.js → chunk-4VSV56F2.js} +2 -2
  34. package/dist/{chunk-NXMYHJAL.js → chunk-4VYPICIA.js} +29 -1
  35. package/dist/{chunk-QX4TB4NH.js → chunk-52YJKWV3.js} +379 -127
  36. package/dist/{chunk-GANST5K3.js → chunk-54PZ2EQZ.js} +1 -1
  37. package/dist/{chunk-5IPKQHOL.js → chunk-B3GG3HJB.js} +1 -1
  38. package/dist/{chunk-2MCXIY4B.js → chunk-B7RJV74O.js} +28 -11
  39. package/dist/{chunk-VOM2HLUI.js → chunk-BK5YCT54.js} +3 -3
  40. package/dist/{chunk-RWGASWT7.js → chunk-BL4U6U52.js} +1 -0
  41. package/dist/{chunk-T6O5OLN2.js → chunk-BYADARIC.js} +2 -2
  42. package/dist/{chunk-VNPADTDD.js → chunk-CEUBYYVJ.js} +2 -2
  43. package/dist/{chunk-JCDYDF54.js → chunk-CEZH4AQT.js} +12 -2
  44. package/dist/{chunk-BY6OQ6XP.js → chunk-CZNSLFYD.js} +3 -1
  45. package/dist/{chunk-SBBQFPCJ.js → chunk-DT3JESJ6.js} +1 -1
  46. package/dist/{chunk-3CXIX6XS.js → chunk-DVQDBWQG.js} +4 -4
  47. package/dist/{chunk-K2AKVYNR.js → chunk-EB3XXHNO.js} +2 -2
  48. package/dist/{chunk-3FV5BVWS.js → chunk-EY7GKVPS.js} +2 -2
  49. package/dist/{chunk-W24SZ5FL.js → chunk-F2ASNL2K.js} +6 -9
  50. package/dist/{chunk-ODBYZIOH.js → chunk-F3B7CE3Q.js} +14 -14
  51. package/dist/{chunk-E3P3LJG6.js → chunk-FLREUEHW.js} +28 -11
  52. package/dist/{chunk-6XVKMBUA.js → chunk-FUA22HO4.js} +1 -1
  53. package/dist/{chunk-AEXVVABM.js → chunk-GAFEU6AB.js} +13 -10
  54. package/dist/{chunk-DIKAPMMF.js → chunk-GQVTTWZF.js} +144 -39
  55. package/dist/{chunk-7LLRMYP6.js → chunk-HCLBM2LQ.js} +2 -2
  56. package/dist/{chunk-AMZJFCOX.js → chunk-HG5ECUOR.js} +5 -5
  57. package/dist/{chunk-KZOZN4PI.js → chunk-HPGQPRHY.js} +3 -3
  58. package/dist/{chunk-BPKLUHFT.js → chunk-HQD7ODQB.js} +5 -5
  59. package/dist/{chunk-SK7TR7BL.js → chunk-JU4T4VAA.js} +4 -4
  60. package/dist/{chunk-MGBUWPXX.js → chunk-JVTWX5G3.js} +33 -8
  61. package/dist/{chunk-2J3NA3OB.js → chunk-JXTPNUSO.js} +83 -51
  62. package/dist/{chunk-2VXY3PEF.js → chunk-K2VXPQKC.js} +107 -94
  63. package/dist/chunk-K4MJX7SR.js +17 -0
  64. package/dist/{chunk-XX2PA4L3.js → chunk-KDEV5RIE.js} +7 -3
  65. package/dist/{chunk-MVIJTXN5.js → chunk-LMIDZIW2.js} +12 -12
  66. package/dist/{chunk-6WNLQI63.js → chunk-LOGKDQ3G.js} +3 -3
  67. package/dist/{chunk-IW5G323T.js → chunk-LTGTN2TH.js} +8 -9
  68. package/dist/{chunk-MEE4NTPZ.js → chunk-LZ4WPR3B.js} +2 -2
  69. package/dist/{chunk-32NB4VMH.js → chunk-M2VXARYK.js} +3 -2
  70. package/dist/{chunk-7RSY7SZN.js → chunk-MCFPMUMT.js} +2 -2
  71. package/dist/{chunk-BEOK6F26.js → chunk-MGSNFWGR.js} +2 -2
  72. package/dist/{chunk-ZEMDV6W2.js → chunk-MO6RTNYD.js} +1 -1
  73. package/dist/{chunk-7JJMT6LN.js → chunk-N33FO44R.js} +1 -1
  74. package/dist/{chunk-A4OI5ZNX.js → chunk-N3M3IU6S.js} +1 -1
  75. package/dist/{chunk-CXRZBWSH.js → chunk-NVVIFW3C.js} +10 -10
  76. package/dist/{chunk-E5V6WEMK.js → chunk-O2ILONGK.js} +10 -10
  77. package/dist/{chunk-4HCJOFMB.js → chunk-OJNNKHAZ.js} +94 -22
  78. package/dist/{chunk-QWLES6YV.js → chunk-PEHST4UY.js} +3 -3
  79. package/dist/{chunk-BV6YE455.js → chunk-POFJH2IS.js} +1 -1
  80. package/dist/{chunk-AR46ND2Q.js → chunk-PXK3ICU6.js} +1 -1
  81. package/dist/{chunk-LOKCBZYF.js → chunk-Q4HKC3O7.js} +3 -3
  82. package/dist/{chunk-J2BSUF6X.js → chunk-QJQTWTM7.js} +8 -3
  83. package/dist/{chunk-QFPBRZJD.js → chunk-ROEW726S.js} +1 -1
  84. package/dist/{chunk-OQL4AJ4X.js → chunk-RUPGFW5B.js} +6 -6
  85. package/dist/{chunk-5TRFLZ5Z.js → chunk-RZWJJCI7.js} +2 -2
  86. package/dist/{chunk-2HP5GIQ3.js → chunk-SNECH6QA.js} +1 -1
  87. package/dist/{chunk-REUL2KH2.js → chunk-ST35C4K6.js} +1 -1
  88. package/dist/{chunk-4E75LHAL.js → chunk-ST4SHO3S.js} +1 -1
  89. package/dist/{chunk-ULTKB5FC.js → chunk-TDYKVJTU.js} +548 -429
  90. package/dist/{chunk-WOKVXK4J.js → chunk-VHIBBDEI.js} +1 -1
  91. package/dist/{chunk-EKGGSAWR.js → chunk-VPON6DF2.js} +5 -5
  92. package/dist/{chunk-VRAXIQ6I.js → chunk-XYGULBFV.js} +2 -2
  93. package/dist/{chunk-PFRG5P32.js → chunk-Y3BT27Y7.js} +1 -1
  94. package/dist/{clean-2RNCTDBT.js → clean-NS2SDRK7.js} +21 -20
  95. package/dist/cli.js +54 -54
  96. package/dist/{cloud-NFIKESNZ.js → cloud-K4XQPWTY.js} +4 -4
  97. package/dist/{cloudrun-7224BCX3.js → cloudrun-BKOKXTXV.js} +6 -6
  98. package/dist/{compare-HL44MXVX.js → compare-ZAV773OL.js} +31 -30
  99. package/dist/{compiler-WF5QTXV7.js → compiler-CWHFCE3H.js} +5 -5
  100. package/dist/{component-ZBZXSJBR.js → component-OXA2ZWX3.js} +3 -3
  101. package/dist/{compositions-76P7LDDX.js → compositions-NKRVWCPE.js} +12 -11
  102. package/dist/{contactSheet-43ISAESM.js → contactSheet-5APX35L3.js} +1 -1
  103. package/dist/{delete-4YZBYOHJ.js → delete-HZ2LBCLC.js} +2 -2
  104. package/dist/{deploy-UGUUGYLF.js → deploy-FNDTZFF7.js} +1 -1
  105. package/dist/{deterministicFonts-RO2TCMLZ.js → deterministicFonts-RADD3BSX.js} +2 -2
  106. package/dist/{dist-5KKCRFPT.js → dist-2YTN2UGY.js} +3 -3
  107. package/dist/{dist-QQ26RUDV.js → dist-7OCZHNLY.js} +1 -1
  108. package/dist/{doctor-GGVUK4NB.js → doctor-6QWJGO5P.js} +14 -13
  109. package/dist/{events-V2VU4J3B.js → events-HTHOBIUN.js} +3 -3
  110. package/dist/{events-OXPTHVDB.js → events-MBKHVGXX.js} +2 -2
  111. package/dist/{feedback-HFZ5SCUK.js → feedback-X5QWTLEX.js} +8 -8
  112. package/dist/{ffmpeg-3VHY7GO3.js → ffmpeg-7GESJCCT.js} +9 -8
  113. package/dist/{figma-7SEZTLBJ.js → figma-SXOHFVJI.js} +3 -3
  114. package/dist/fontLocalizeCli.js +11 -10
  115. package/dist/{get-Y2S2PXCM.js → get-T2HI5FCK.js} +2 -2
  116. package/dist/{gpuPolicy-UHPOWRV5.js → gpuPolicy-5FABKW5G.js} +8 -7
  117. package/dist/{grade-compare-4VTXSQM5.js → grade-compare-JATGHA6Q.js} +37 -35
  118. package/dist/{gsapParser-UP3PNPC2.js → gsapParser-IEUSLYDJ.js} +11 -11
  119. package/dist/{gsapParserAcorn-OOTY2MVN.js → gsapParserAcorn-LZJKCFQI.js} +1 -1
  120. package/dist/{help-HXNOACHI.js → help-G6CIKCGS.js} +1 -1
  121. package/dist/{history-XDJUI4LC.js → history-CSJUHYTD.js} +9 -8
  122. package/dist/hyperframe.manifest.json +1 -1
  123. package/dist/hyperframe.runtime.iife.js +71 -65
  124. package/dist/hyperframes-player.global.js +3 -3
  125. package/dist/hyperframes-slideshow.global.js +1 -1
  126. package/dist/{info-KYRI3EZV.js → info-MTTUMW2C.js} +8 -8
  127. package/dist/{init-IL7EWMT5.js → init-LJYNI5EK.js} +30 -29
  128. package/dist/{inspect-TBNH23UC.js → inspect-HGDM2VJE.js} +31 -30
  129. package/dist/{installer-O2YMFSI4.js → installer-3XHURUDG.js} +5 -5
  130. package/dist/{keyframes-4GF62KAJ.js → keyframes-MSU7N53X.js} +59 -33
  131. package/dist/{lambda-JFL6M4XZ.js → lambda-2I2TIUSD.js} +8 -8
  132. package/dist/{layout-7MAEFR65.js → layout-KWOXZMN7.js} +31 -30
  133. package/dist/{lint-4AYHKYAM.js → lint-N53TLHFG.js} +7 -7
  134. package/dist/{lintProject-52SLJPRC.js → lintProject-EOUZA4EG.js} +1 -1
  135. package/dist/{list-3DP3D3MW.js → list-SHYPQC7U.js} +2 -2
  136. package/dist/{login-T54WQYRQ.js → login-TEB6PA3Y.js} +4 -4
  137. package/dist/{manager-MZIJY73J.js → manager-COSJVRAE.js} +2 -2
  138. package/dist/{manager-QROK2IH3.js → manager-PLRSZH5G.js} +10 -9
  139. package/dist/{media-treatment-4CXF7MSG.js → media-treatment-XTUKMIGE.js} +15 -14
  140. package/dist/{models-IS3I6JKU.js → models-4WKAPP2L.js} +14 -13
  141. package/dist/{motionShot-DVYJZOTQ.js → motionShot-PTKB4ILH.js} +17 -16
  142. package/dist/{normalize-audio-4527JIK4.js → normalize-audio-3NG326I2.js} +6 -6
  143. package/dist/{open-5JE46SAX.js → open-6S2LMF7R.js} +12 -12
  144. package/dist/{parakeetRunner-7JIJGJJ7.js → parakeetRunner-OEI35DVO.js} +15 -14
  145. package/dist/{pipeline-YEN3WFD2.js → pipeline-543FUKH4.js} +10 -9
  146. package/dist/{play-WFX67I7M.js → play-ETEGHP3B.js} +21 -21
  147. package/dist/{preflight-D377HVUZ.js → preflight-Y5IYUF2T.js} +11 -10
  148. package/dist/{present-XTSEKAC7.js → present-ZRSLMJ6J.js} +17 -17
  149. package/dist/{preview-5XH7EK6O.js → preview-CEJ3SVTY.js} +100 -65
  150. package/dist/{producer-TFFYF4ZM.js → producer-JFVFIV26.js} +1 -1
  151. package/dist/{projectConfig-6YUJHMCL.js → projectConfig-7EK3BS72.js} +7 -7
  152. package/dist/{publication-JK33Y55E.js → publication-Y54GYFMV.js} +3 -3
  153. package/dist/{publish-JHDGXWXU.js → publish-S3UJTPCV.js} +10 -10
  154. package/dist/{remove-background-P44MYAPJ.js → remove-background-YTATNQSI.js} +11 -10
  155. package/dist/{render-RBINWWP5.js → render-IIPXMSBO.js} +35 -34
  156. package/dist/{render-RJ3I4E3A.js → render-RN3W6YL4.js} +17 -16
  157. package/dist/{render-HJ34EGMN.js → render-WNIG4B3J.js} +6 -6
  158. package/dist/{render-batch-3D6XQYUC.js → render-batch-OYS3UQDB.js} +6 -6
  159. package/dist/renderSetupWorker.js +42 -4
  160. package/dist/{resolver-HCLTFZUW.js → resolver-OVCXVEDX.js} +5 -5
  161. package/dist/{sherpa-IPOUJM6Q.js → sherpa-NCSFD7S3.js} +14 -13
  162. package/dist/skills/hyperframes/references/format.md +1 -0
  163. package/dist/skills/media-use/scripts/lib/duck.mjs +18 -2
  164. package/dist/skills/media-use/scripts/lib/duck.test.mjs +79 -0
  165. package/dist/{skills-P67QEERC.js → skills-IUV3K3X7.js} +5 -5
  166. package/dist/{skillsUpdateCheck-IFMLRVIF.js → skillsUpdateCheck-NACBXMWW.js} +3 -3
  167. package/dist/{snapshot-4YM4NNWJ.js → snapshot-7S5USJ2I.js} +52 -46
  168. package/dist/{src-DCD3YMXX.js → src-CAR6PHI4.js} +10 -9
  169. package/dist/{src-SSV66L3F.js → src-HITT6NWG.js} +19 -6
  170. package/dist/{staticProjectServer-C7PPGLEQ.js → staticProjectServer-QR6VZBPT.js} +18 -18
  171. package/dist/studio/assets/{hyperframes-player-ChN3cOr0.js → hyperframes-player-DLOUZUct.js} +1 -1
  172. package/dist/studio/assets/{index-CHu83hHM.js → index-CAQ7OcNJ.js} +163 -163
  173. package/dist/studio/assets/{index-BYbFcRR0.js → index-D9kA6JUX.js} +1 -1
  174. package/dist/studio/index.html +1 -1
  175. package/dist/{studio-api-6YZ7PIQR.js → studio-api-HVNOJILH.js} +13 -11
  176. package/dist/{studioServer-EWHSE4GC.js → studioServer-3BG5FRJL.js} +53 -52
  177. package/dist/{telemetry-GGLBHZRZ.js → telemetry-PET2GFQG.js} +3 -3
  178. package/dist/{timeline-NKHQJXJ2.js → timeline-PVIC2Z5K.js} +20 -19
  179. package/dist/{tokens-FWNNDJ6R.js → tokens-4N46PWSF.js} +2 -2
  180. package/dist/{transcribe-TRPKXZ7L.js → transcribe-2DVBS7MH.js} +19 -18
  181. package/dist/{transcribe-VD2TMMC2.js → transcribe-IDC7JTM4.js} +13 -12
  182. package/dist/{updateCheck-CUPS6LU3.js → updateCheck-QRDVXU7I.js} +2 -2
  183. package/dist/{upgrade-23XO6MEX.js → upgrade-5CNE3TBH.js} +2 -2
  184. package/dist/{urlDownloader-YOAOONCK.js → urlDownloader-P2I25DFC.js} +7 -6
  185. package/dist/{validate-BWIW24G5.js → validate-CLR5PEOV.js} +25 -24
  186. package/dist/{video-XRXJS4GC.js → video-3CKZZBOB.js} +8 -7
  187. package/dist/warmCheck.js +38 -37
  188. package/package.json +1 -1
  189. package/dist/chunk-B2XLXD7V.js +0 -3897
@@ -8,7 +8,7 @@ import {
8
8
  EncoderInterruptedError,
9
9
  encoderFailureError,
10
10
  prepareAnimatedGifInputs
11
- } from "./chunk-YMQQXPD5.js";
11
+ } from "./chunk-452UK7E7.js";
12
12
  import {
13
13
  GENERIC_FAMILIES,
14
14
  collectFontFamilyCustomProperties,
@@ -16,7 +16,7 @@ import {
16
16
  iterateFontFamilyDeclarations,
17
17
  normalizeSystemFontPrimaryFamilies,
18
18
  resolveFontFamilyDeclarationFamilies
19
- } from "./chunk-QFPBRZJD.js";
19
+ } from "./chunk-ROEW726S.js";
20
20
  import {
21
21
  parseEncoderOptions
22
22
  } from "./chunk-KLIKCX6Q.js";
@@ -74,6 +74,7 @@ import {
74
74
  createFrameReorderBuffer,
75
75
  createVideoFrameInjector,
76
76
  declareContentAddressedUrl,
77
+ declareProbeFactsUrl,
77
78
  decodePng,
78
79
  decodePngToRgb48le,
79
80
  deriveBeginFrameProbeTimeTicks,
@@ -137,6 +138,7 @@ import {
137
138
  readWebGlVendorInfoFromCanvas,
138
139
  recaptureDrawElementFrameForVerify,
139
140
  removeDomLayerMask,
141
+ renameWithRetry,
140
142
  resampleRgb48leObjectFit,
141
143
  resolveBrowserGpuMode,
142
144
  resolveConfig,
@@ -156,8 +158,9 @@ import {
156
158
  trackChildProcess,
157
159
  validateEngineConfigSnapshot,
158
160
  verifyDiskDrawElementSamples,
161
+ videoAudioId,
159
162
  writeCapturedFrame
160
- } from "./chunk-QX4TB4NH.js";
163
+ } from "./chunk-52YJKWV3.js";
161
164
  import {
162
165
  DISTRIBUTED_FPS,
163
166
  HF_VFX_ATTR,
@@ -172,7 +175,7 @@ import {
172
175
  parseVfxChain,
173
176
  resolveAuthoredTimingWindow,
174
177
  toFps
175
- } from "./chunk-MGBUWPXX.js";
178
+ } from "./chunk-JVTWX5G3.js";
176
179
  import {
177
180
  assignBundledRuntimeCompositionIds,
178
181
  buildVariablesByCompScript,
@@ -182,10 +185,9 @@ import {
182
185
  ensureExternalScriptTag,
183
186
  extractStandaloneEntryFromIndex,
184
187
  inlineSubCompositions,
185
- isTemplateEntry,
186
188
  parsesAsScript,
187
189
  prepareFlattenedInnerRoot
188
- } from "./chunk-AEXVVABM.js";
190
+ } from "./chunk-GAFEU6AB.js";
189
191
  import {
190
192
  COMPOSITION_SOURCE_URL,
191
193
  HF_COLOR_GRADING_ATTR,
@@ -203,17 +205,20 @@ import {
203
205
  insertBeforeCloseTag,
204
206
  isJavaScriptType,
205
207
  isRuntimeFileUrl,
208
+ isTemplateEntry,
206
209
  normalizeRateSpec,
207
210
  parseHostVariableValues,
208
211
  parseStrictFiniteTimingNumber,
209
212
  readDeclaredDefaults,
210
213
  readMediaStart,
211
214
  resolveNaturalMediaTimelineDurationFromValues,
215
+ resolveRateSpec,
212
216
  shiftRateLane,
213
217
  shouldClampResolvedMediaDuration,
214
218
  sourceTimeAt,
215
- styleElementsFor
216
- } from "./chunk-B2XLXD7V.js";
219
+ styleElementsFor,
220
+ timeAtSourceTime
221
+ } from "./chunk-2D5WP2NI.js";
217
222
  import {
218
223
  MAX_AUDIO_GAIN
219
224
  } from "./chunk-X7HBSTCZ.js";
@@ -227,7 +232,7 @@ import {
227
232
  isAspectAgnosticResolutionAlias,
228
233
  normalizeResolutionFlag,
229
234
  suggestMatchingPreset
230
- } from "./chunk-2MCXIY4B.js";
235
+ } from "./chunk-B7RJV74O.js";
231
236
  import {
232
237
  TRANSITIONS,
233
238
  convertTransfer,
@@ -243,7 +248,7 @@ import {
243
248
  import {
244
249
  lintHyperframeHtml,
245
250
  resolveReferencedStart
246
- } from "./chunk-2VXY3PEF.js";
251
+ } from "./chunk-K2VXPQKC.js";
247
252
  import {
248
253
  isUnresolvedAssetPlaceholder,
249
254
  readProjectFile,
@@ -262,6 +267,7 @@ import {
262
267
  CSS_URL_RE,
263
268
  decodedUrlPath,
264
269
  isNonRelativeUrl,
270
+ readPlaybackRate,
265
271
  rewriteAssetPaths,
266
272
  rewriteCssAssetUrls,
267
273
  rewriteInlineStyleAssetUrls
@@ -1570,16 +1576,7 @@ var RenderExecutionContext = class {
1570
1576
  };
1571
1577
 
1572
1578
  // ../producer/src/services/render/artifactTransaction.ts
1573
- import {
1574
- closeSync as closeSync2,
1575
- existsSync as existsSync5,
1576
- fstatSync as fstatSync2,
1577
- openSync as openSync2,
1578
- readSync,
1579
- readdirSync as readdirSync2,
1580
- renameSync,
1581
- rmSync as rmSync3
1582
- } from "fs";
1579
+ import { closeSync as closeSync2, existsSync as existsSync5, fstatSync as fstatSync2, openSync as openSync2, readSync, readdirSync as readdirSync2, rmSync as rmSync3 } from "fs";
1583
1580
  import { basename as basename3, dirname as dirname4, extname as extname2, join as join5, resolve as resolve4 } from "path";
1584
1581
 
1585
1582
  // ../producer/src/services/render/renderDirOwner.ts
@@ -1692,7 +1689,7 @@ function createRenderWorkDir(prefix, outputDir) {
1692
1689
  // ../producer/src/services/render/artifactTransaction.ts
1693
1690
  var defaultFileSystem = {
1694
1691
  existsSync: existsSync5,
1695
- renameSync,
1692
+ renameSync: (source, destination) => renameWithRetry(source, destination),
1696
1693
  rmSync: rmSync3
1697
1694
  };
1698
1695
  async function defaultArtifactDurationProbe(path) {
@@ -1770,8 +1767,11 @@ function assertDurationWithinTolerance(expectedSeconds, probedSeconds, tolerance
1770
1767
  `Render artifact is truncated: expected ${expectedSeconds.toFixed(3)}s, probed ${probedSeconds.toFixed(3)}s (deficit ${deficit.toFixed(3)}s exceeds tolerance ${toleranceSeconds.toFixed(3)}s). Artifact: ${stagingPath}`
1771
1768
  );
1772
1769
  }
1770
+ function hasUsableFrameCount(frames) {
1771
+ return frames !== void 0 && Number.isFinite(frames) && frames > 0;
1772
+ }
1773
1773
  function assertFrameCountWithinTolerance(expectedFrames, probedFrames, stagingPath, toleranceFrames = 1) {
1774
- if (probedFrames === void 0 || !Number.isFinite(probedFrames) || probedFrames <= 0) {
1774
+ if (!hasUsableFrameCount(probedFrames)) {
1775
1775
  return;
1776
1776
  }
1777
1777
  const shortfall = expectedFrames - probedFrames;
@@ -1791,9 +1791,10 @@ function assertSizeMatches(expected, probed, stagingPath) {
1791
1791
  );
1792
1792
  }
1793
1793
  var ArtifactTransaction = class {
1794
- constructor(destinationPath, kind, fileSystem = defaultFileSystem, durationProbe = defaultArtifactDurationProbe) {
1794
+ constructor(destinationPath, kind, fileSystem = defaultFileSystem, durationProbe = defaultArtifactDurationProbe, logger = defaultLogger) {
1795
1795
  this.kind = kind;
1796
1796
  this.fileSystem = fileSystem;
1797
+ this.logger = logger;
1797
1798
  this.destinationPath = resolve4(destinationPath);
1798
1799
  this.transactionDirectory = createSiblingTransactionDirectory(this.destinationPath);
1799
1800
  this.stagingPath = join5(this.transactionDirectory, basename3(this.destinationPath));
@@ -1802,6 +1803,7 @@ var ArtifactTransaction = class {
1802
1803
  }
1803
1804
  kind;
1804
1805
  fileSystem;
1806
+ logger;
1805
1807
  destinationPath;
1806
1808
  stagingPath;
1807
1809
  transactionDirectory;
@@ -1878,6 +1880,19 @@ var ArtifactTransaction = class {
1878
1880
  probePath
1879
1881
  );
1880
1882
  if (expected.expectedFrames !== void 0) {
1883
+ if (!hasUsableFrameCount(probed.frames)) {
1884
+ try {
1885
+ this.logger.debug(
1886
+ "[Render] Frame-count validation skipped: no usable frame count was reported.",
1887
+ {
1888
+ event: "artifact_frame_count_unavailable",
1889
+ artifact: this.destinationPath,
1890
+ expectedFrames: expected.expectedFrames
1891
+ }
1892
+ );
1893
+ } catch {
1894
+ }
1895
+ }
1881
1896
  assertFrameCountWithinTolerance(expected.expectedFrames, probed.frames, probePath);
1882
1897
  }
1883
1898
  assertSizeMatches(expected, probed, probePath);
@@ -2079,36 +2094,8 @@ function replanAfterFailure(plan2, failure) {
2079
2094
  import { existsSync as existsSync6, mkdirSync as mkdirSync3, rmSync as rmSync5, statSync as statSync2 } from "fs";
2080
2095
  import { join as join9 } from "path";
2081
2096
 
2082
- // ../producer/src/services/render/captureStageError.ts
2083
- var CaptureStageError = class extends CaptureFailure {
2084
- browserConsole;
2085
- constructor(input) {
2086
- const classified = classifyCaptureFailure(input.cause);
2087
- super({
2088
- kind: classified.kind,
2089
- message: normalizeErrorMessage(input.cause),
2090
- cause: input.cause,
2091
- workerDiagnostics: classified.workerDiagnostics
2092
- });
2093
- this.name = "CaptureStageError";
2094
- this.browserConsole = input.browserConsole.slice();
2095
- if (input.cause instanceof Error && input.cause.stack) {
2096
- this.stack = input.cause.stack;
2097
- }
2098
- }
2099
- };
2100
- function wrapCaptureStageError(error, browserConsole) {
2101
- if (error instanceof CaptureStageError || error instanceof EncoderInterruptedError) return error;
2102
- return new CaptureStageError({ cause: error, browserConsole });
2103
- }
2104
- function getCaptureStageBrowserConsole(error) {
2105
- if (error instanceof CaptureStageError) return error.browserConsole;
2106
- return [];
2107
- }
2108
-
2109
- // ../producer/src/services/hdrCompositor.ts
2110
- import { readSync as readSync2, closeSync as closeSync3 } from "fs";
2111
- import { join as join6 } from "path";
2097
+ // ../producer/src/services/render/perfSummary.ts
2098
+ import { arch, cpus, platform, totalmem } from "os";
2112
2099
 
2113
2100
  // ../producer/src/services/render/hdrPerf.ts
2114
2101
  function createHdrPerfCollector() {
@@ -2214,7 +2201,257 @@ function finalizeHdrPerf(perf) {
2214
2201
  };
2215
2202
  }
2216
2203
 
2204
+ // ../producer/src/services/render/perfSummary.ts
2205
+ function worstSubTimelineWaitOutcome(perfs) {
2206
+ const outcomes = perfs.map((p) => p.subTimelineWaitOutcome).filter((o) => !!o);
2207
+ if (outcomes.length === 0) return void 0;
2208
+ if (outcomes.includes("script_failure")) return "script_failure";
2209
+ if (outcomes.includes("timeout")) return "timeout";
2210
+ return "ready";
2211
+ }
2212
+ function pushWorkerDedupPerfs(results, sink) {
2213
+ for (const r of results) {
2214
+ if (r.perf) sink.push(r.perf);
2215
+ }
2216
+ }
2217
+ function pushWorkerPageErrors(results, sink) {
2218
+ for (const result of results) {
2219
+ for (const line of result.pageErrorLines ?? []) if (!sink.includes(line)) sink.push(line);
2220
+ }
2221
+ }
2222
+ function withWorkerPageErrors(consoleLines, workerLines) {
2223
+ return [...consoleLines, ...workerLines.filter((line) => !consoleLines.includes(line))];
2224
+ }
2225
+ function roundDb(value) {
2226
+ return value === void 0 ? void 0 : Math.round(Math.min(value, 999) * 10) / 10;
2227
+ }
2228
+ function aggregateDrawElement(perfs, de) {
2229
+ if (perfs.length === 0) return void 0;
2230
+ const modes = [...new Set(perfs.map((p) => p.captureMode).filter(Boolean))].sort();
2231
+ const gateReasons = [
2232
+ ...new Set(
2233
+ [de.probeGateReason, ...perfs.map((p) => p.deGateReason)].filter((r) => !!r)
2234
+ )
2235
+ ].sort();
2236
+ const gpuRenderers = [
2237
+ ...new Set(perfs.map((p) => p.gpuRenderer).filter((r) => !!r))
2238
+ ].sort();
2239
+ const drain = de.drainStats;
2240
+ return {
2241
+ mode: modes.join("|") || "unknown",
2242
+ compileGate: de.compileGate,
2243
+ clampReason: de.clampReason,
2244
+ workerInversion: de.workerInversion ?? "none",
2245
+ preInversionWorkers: de.preInversionWorkers,
2246
+ compositionElementCount: de.compositionElementCount,
2247
+ compositionElementCountSource: de.compositionElementCountSource,
2248
+ compositionElementTags: de.compositionElementTags,
2249
+ arollVideoCount: de.arollVideoCount,
2250
+ heygenVideoCount: de.heygenVideoCount,
2251
+ adaptersUsed: de.adaptersUsed,
2252
+ audioCount: de.audioCount,
2253
+ imageCount: de.imageCount,
2254
+ subCompositionCount: de.subCompositionCount,
2255
+ audioGroupCount: de.audioGroupCount,
2256
+ colorGradingCount: de.colorGradingCount,
2257
+ hasLut: de.hasLut,
2258
+ rootBodyMismatch: de.rootBodyMismatch,
2259
+ rootBodyDeltaPxBucket: de.rootBodyDeltaPxBucket,
2260
+ vfxHostCount: de.vfxHostCount,
2261
+ vfxCapture: de.vfxCapture,
2262
+ vfxTypes: de.vfxTypes,
2263
+ shortBand: de.shortBand,
2264
+ parallelRouter: de.parallelRouter ?? "none",
2265
+ preRouterWorkers: de.preRouterWorkers,
2266
+ gateReason: gateReasons.length > 0 ? gateReasons.join("|") : void 0,
2267
+ gpuRenderer: gpuRenderers.length > 0 ? gpuRenderers.join("|") : void 0,
2268
+ workerEncode: perfs.some((p) => p.deWorkerEncode),
2269
+ verifyArmed: perfs.reduce((sum2, p) => sum2 + (p.deVerifyArmed ?? 0), 0),
2270
+ verifyChecked: drain?.verifyChecked ?? 0,
2271
+ verifyMinDb: roundDb(drain?.verifyMinDb),
2272
+ verifyInitMs: perfs.reduce((sum2, p) => sum2 + (p.deVerifyInitMs ?? 0), 0),
2273
+ selfVerifyFallback: de.selfVerifyFallback,
2274
+ fallbackReason: de.fallbackReason,
2275
+ fallbackFailedDb: roundDb(de.fallbackFailedDb),
2276
+ fallbackFrameIndex: de.fallbackFrameIndex,
2277
+ fallbackThresholdDb: roundDb(de.fallbackThresholdDb),
2278
+ blankSuspects: drain?.blankSuspects ?? 0,
2279
+ blankDeterministicAccepts: drain?.blankDeterministicAccepts ?? 0,
2280
+ blankRecaptures: drain?.blankRecaptures ?? 0,
2281
+ boundaryFrames: perfs.reduce((sum2, p) => sum2 + (p.deBoundaryFrames ?? 0), 0),
2282
+ ncprFallbacks: perfs.reduce((sum2, p) => sum2 + (p.deNcprFallbacks ?? 0), 0),
2283
+ frameTimeouts: perfs.reduce((sum2, p) => sum2 + (p.deFrameTimeouts ?? 0), 0)
2284
+ };
2285
+ }
2286
+ function maxDefined(values) {
2287
+ const present = values.filter((v) => typeof v === "number");
2288
+ return present.length > 0 ? Math.max(...present) : void 0;
2289
+ }
2290
+ function aggregateChromeMemory(perfs) {
2291
+ const sampled = perfs.filter((p) => (p.chromeMemorySamples ?? 0) > 0);
2292
+ if (sampled.length === 0) return void 0;
2293
+ return {
2294
+ browserRssPeakMb: maxDefined(sampled.map((p) => p.chromeBrowserRssPeakMb)),
2295
+ rendererRssPeakMb: maxDefined(sampled.map((p) => p.chromeRendererRssPeakMb)),
2296
+ // Last samples are per session; summing approximates the whole fleet's
2297
+ // footprint at the end of capture (workers run concurrently).
2298
+ rssLastMb: sampled.reduce((sum2, p) => sum2 + (p.chromeRssLastMb ?? 0), 0),
2299
+ gpuProcessSeenLastSample: sampled.some((p) => p.chromeGpuProcessSeenLastSample === true),
2300
+ samples: sampled.reduce((sum2, p) => sum2 + (p.chromeMemorySamples ?? 0), 0)
2301
+ };
2302
+ }
2303
+ function aggregateDedup(perfs) {
2304
+ if (perfs.length === 0) return void 0;
2305
+ const armed = perfs.some((p) => p.staticDedupArmed);
2306
+ const skipReasons = armed ? [] : [
2307
+ ...new Set(perfs.map((p) => p.staticDedupSkipReason).filter((r) => !!r))
2308
+ ].sort();
2309
+ const verificationPerfs = perfs.filter((perf) => perf.staticDedupVerificationOutcome);
2310
+ const verificationOutcomes = [
2311
+ ...new Set(
2312
+ verificationPerfs.map((perf) => perf.staticDedupVerificationOutcome).filter((outcome) => outcome != null)
2313
+ )
2314
+ ].sort();
2315
+ return {
2316
+ enabled: perfs.some((p) => p.staticDedupEnabled),
2317
+ armed,
2318
+ predictedFrames: perfs.reduce((sum2, p) => sum2 + (p.staticDedupPredicted ?? 0), 0),
2319
+ reusedFrames: perfs.reduce((sum2, p) => sum2 + (p.staticDedupReused ?? 0), 0),
2320
+ skipReason: skipReasons.length > 0 ? skipReasons.join("|") : void 0,
2321
+ ...verificationPerfs.length === 0 ? {} : {
2322
+ verifiedFrames: verificationPerfs.reduce(
2323
+ (sum2, perf) => sum2 + (perf.staticDedupVerified ?? 0),
2324
+ 0
2325
+ ),
2326
+ verificationOutcomes,
2327
+ plannedRuns: verificationPerfs.reduce(
2328
+ (sum2, perf) => sum2 + (perf.staticDedupVerificationPlannedRuns ?? 0),
2329
+ 0
2330
+ ),
2331
+ completedRuns: verificationPerfs.reduce(
2332
+ (sum2, perf) => sum2 + (perf.staticDedupVerificationCompletedRuns ?? 0),
2333
+ 0
2334
+ ),
2335
+ screenshots: verificationPerfs.reduce(
2336
+ (sum2, perf) => sum2 + (perf.staticDedupVerificationScreenshots ?? 0),
2337
+ 0
2338
+ ),
2339
+ seeks: verificationPerfs.reduce(
2340
+ (sum2, perf) => sum2 + (perf.staticDedupVerificationSeeks ?? 0),
2341
+ 0
2342
+ ),
2343
+ comparisons: verificationPerfs.reduce(
2344
+ (sum2, perf) => sum2 + (perf.staticDedupVerificationComparisons ?? 0),
2345
+ 0
2346
+ ),
2347
+ verificationElapsedMs: verificationPerfs.reduce(
2348
+ (sum2, perf) => sum2 + (perf.staticDedupVerificationElapsedMs ?? 0),
2349
+ 0
2350
+ )
2351
+ }
2352
+ };
2353
+ }
2354
+ function aggregateBeginFrameReuse(perfs) {
2355
+ const noDamageFrames = perfs.reduce((sum2, p) => sum2 + (p.beginFrameNoDamage ?? 0), 0);
2356
+ const hasDamageFrames = perfs.reduce((sum2, p) => sum2 + (p.beginFrameHasDamage ?? 0), 0);
2357
+ if (noDamageFrames + hasDamageFrames === 0) return void 0;
2358
+ return { noDamageFrames, hasDamageFrames };
2359
+ }
2360
+ function buildRenderPerfSummary(input) {
2361
+ return {
2362
+ renderId: input.job.id,
2363
+ totalElapsedMs: input.totalElapsedMs,
2364
+ // RenderPerfSummary surfaces fps as a decimal because it lands in JSON
2365
+ // payloads (CLI telemetry, regression-harness reports) where a single
2366
+ // number is friendlier than `{num,den}`. Callers needing the rational
2367
+ // back can read `job.config.fps`.
2368
+ fps: fpsToNumber(input.job.config.fps),
2369
+ quality: input.job.config.quality,
2370
+ workers: input.workerCount,
2371
+ workerSizing: input.workerSizing,
2372
+ chunkedEncode: input.enableChunkedEncode,
2373
+ chunkSizeFrames: input.enableChunkedEncode ? input.chunkedEncodeSize : null,
2374
+ compositionDurationSeconds: input.compositionDurationSeconds,
2375
+ totalFrames: input.totalFrames,
2376
+ resolution: { width: input.outputWidth, height: input.outputHeight },
2377
+ videoCount: input.videoCount,
2378
+ audioCount: input.audioCount,
2379
+ stages: input.perfStages,
2380
+ videoExtractBreakdown: input.videoExtractBreakdown,
2381
+ tmpPeakBytes: input.tmpPeakBytes,
2382
+ captureCalibration: input.captureCalibration ? {
2383
+ sampledFrames: input.captureCalibration.samples.map((sample) => sample.frameIndex),
2384
+ p95Ms: input.captureCalibration.estimate.p95Ms,
2385
+ multiplier: input.captureCalibration.estimate.multiplier,
2386
+ reasons: input.captureCalibration.estimate.reasons
2387
+ } : void 0,
2388
+ captureAttempts: input.captureAttempts.length > 0 ? input.captureAttempts : void 0,
2389
+ hdrDiagnostics: input.hdrDiagnostics.videoExtractionFailures > 0 || input.hdrDiagnostics.imageDecodeFailures > 0 ? { ...input.hdrDiagnostics } : void 0,
2390
+ hdrPerf: input.hdrPerf ? finalizeHdrPerf(input.hdrPerf) : void 0,
2391
+ observability: input.observability,
2392
+ captureAvgMs: input.totalFrames > 0 ? Math.round(
2393
+ (input.perfStages.captureFrameMs ?? input.perfStages.captureMs ?? 0) / input.totalFrames
2394
+ ) : void 0,
2395
+ subTimelineWait: worstSubTimelineWaitOutcome(input.dedupPerfs),
2396
+ captureP50Ms: (() => {
2397
+ const withSamples = input.dedupPerfs.filter((p) => (p.p50TotalMs ?? 0) > 0);
2398
+ if (withSamples.length === 0) return void 0;
2399
+ return withSamples.reduce((a, b) => b.frames > a.frames ? b : a).p50TotalMs;
2400
+ })(),
2401
+ peakRssMb: Math.round(input.peakRssBytes / (1024 * 1024)),
2402
+ peakHeapUsedMb: Math.round(input.peakHeapUsedBytes / (1024 * 1024)),
2403
+ staticDedup: aggregateDedup(input.dedupPerfs),
2404
+ chromeMemory: aggregateChromeMemory(input.dedupPerfs),
2405
+ beginFrameReuse: aggregateBeginFrameReuse(input.dedupPerfs),
2406
+ drawElement: aggregateDrawElement(
2407
+ input.dedupPerfs,
2408
+ input.drawElement ?? { selfVerifyFallback: false }
2409
+ ),
2410
+ host: {
2411
+ platform: platform(),
2412
+ arch: arch(),
2413
+ cpuCount: cpus().length,
2414
+ totalMemMb: Math.round(totalmem() / (1024 * 1024)),
2415
+ nodeVersion: process.version,
2416
+ gpuDisabled: input.gpuDisabled
2417
+ }
2418
+ };
2419
+ }
2420
+
2421
+ // ../producer/src/services/render/captureStageError.ts
2422
+ var CaptureStageError = class extends CaptureFailure {
2423
+ browserConsole;
2424
+ constructor(input) {
2425
+ const classified = classifyCaptureFailure(input.cause);
2426
+ super({
2427
+ kind: classified.kind,
2428
+ message: normalizeErrorMessage(input.cause),
2429
+ cause: input.cause,
2430
+ workerDiagnostics: classified.workerDiagnostics,
2431
+ pageErrorLines: classified.pageErrorLines
2432
+ });
2433
+ this.name = "CaptureStageError";
2434
+ this.browserConsole = withWorkerPageErrors(input.browserConsole, [
2435
+ ...classified.pageErrorLines
2436
+ ]);
2437
+ if (input.cause instanceof Error && input.cause.stack) {
2438
+ this.stack = input.cause.stack;
2439
+ }
2440
+ }
2441
+ };
2442
+ function wrapCaptureStageError(error, browserConsole) {
2443
+ if (error instanceof CaptureStageError || error instanceof EncoderInterruptedError) return error;
2444
+ return new CaptureStageError({ cause: error, browserConsole });
2445
+ }
2446
+ function getCaptureStageBrowserConsole(error) {
2447
+ if (error instanceof CaptureStageError) return error.browserConsole;
2448
+ if (error instanceof CaptureFailure) return [...error.pageErrorLines];
2449
+ return [];
2450
+ }
2451
+
2217
2452
  // ../producer/src/services/hdrCompositor.ts
2453
+ import { readSync as readSync2, closeSync as closeSync3 } from "fs";
2454
+ import { join as join6 } from "path";
2218
2455
  function countNonZeroAlpha(rgba) {
2219
2456
  let n = 0;
2220
2457
  for (let p = 3; p < rgba.length; p += 4) {
@@ -3274,284 +3511,75 @@ function cleanupEndedHdrVideos(args) {
3274
3511
  if (frameSource) {
3275
3512
  cleanupHdrVideoFrameSource(frameSource, log);
3276
3513
  hdrVideoFrameSources.delete(videoId);
3277
- }
3278
- cleanedUpVideos.add(videoId);
3279
- }
3280
- }
3281
- }
3282
- }
3283
-
3284
- // ../producer/src/services/render/segmentPlan.ts
3285
- var DEFAULT_SEGMENT_FRAMES = 3e3;
3286
- var MIN_SEGMENT_FRAMES = 30;
3287
- function assertPositiveInteger(name, value) {
3288
- if (!Number.isInteger(value) || value <= 0) {
3289
- throw new Error(`[segmentPlan] ${name} must be a positive integer (got ${String(value)})`);
3290
- }
3291
- }
3292
- function planSegments(totalFrames, segmentFrames) {
3293
- assertPositiveInteger("totalFrames", totalFrames);
3294
- assertPositiveInteger("segmentFrames", segmentFrames);
3295
- const slices = [];
3296
- for (let start = 0, index = 0; start < totalFrames; start += segmentFrames, index += 1) {
3297
- slices.push({
3298
- index,
3299
- startFrame: start,
3300
- endFrame: Math.min(totalFrames, start + segmentFrames)
3301
- });
3302
- }
3303
- return slices;
3304
- }
3305
- function resolveSegmentFrames(env) {
3306
- const raw = env.HF_SEGMENT_FRAMES;
3307
- if (raw === void 0 || raw.trim() === "") return DEFAULT_SEGMENT_FRAMES;
3308
- const parsed = Number(raw);
3309
- if (!Number.isInteger(parsed)) return DEFAULT_SEGMENT_FRAMES;
3310
- return Math.max(MIN_SEGMENT_FRAMES, parsed);
3311
- }
3312
-
3313
- // ../producer/src/services/render/segmentQueue.ts
3314
- function createSegmentQueue(slices) {
3315
- let cursor = 0;
3316
- return {
3317
- next: () => slices[cursor++],
3318
- remaining: () => Math.max(0, slices.length - cursor)
3319
- };
3320
- }
3321
-
3322
- // ../producer/src/services/render/segmentRecycle.ts
3323
- var DEFAULT_SEGMENT_BROWSER_RECYCLE = 3;
3324
- function resolveSegmentBrowserRecycle(env) {
3325
- const raw = env.HF_SEGMENT_BROWSER_RECYCLE;
3326
- if (raw === void 0 || raw.trim() === "") return DEFAULT_SEGMENT_BROWSER_RECYCLE;
3327
- const parsed = Number(raw);
3328
- return Number.isInteger(parsed) && parsed >= 0 ? parsed : DEFAULT_SEGMENT_BROWSER_RECYCLE;
3329
- }
3330
- var TARGET_LOSS_PATTERNS = [/target closed/i, /detached frame/i, /session closed/i];
3331
- function isTargetLossError(error) {
3332
- if (!(error instanceof Error)) return false;
3333
- return TARGET_LOSS_PATTERNS.some((re) => re.test(error.message));
3334
- }
3335
-
3336
- // ../producer/src/services/render/stages/captureStreamingStage.ts
3337
- import { mkdtemp, writeFile } from "fs/promises";
3338
- import { tmpdir } from "os";
3339
- import { join as join8 } from "path";
3340
-
3341
- // ../producer/src/services/render/perfSummary.ts
3342
- import { arch, cpus, platform, totalmem } from "os";
3343
- function worstSubTimelineWaitOutcome(perfs) {
3344
- const outcomes = perfs.map((p) => p.subTimelineWaitOutcome).filter((o) => !!o);
3345
- if (outcomes.length === 0) return void 0;
3346
- if (outcomes.includes("script_failure")) return "script_failure";
3347
- if (outcomes.includes("timeout")) return "timeout";
3348
- return "ready";
3349
- }
3350
- function pushWorkerDedupPerfs(results, sink) {
3351
- for (const r of results) {
3352
- if (r.perf) sink.push(r.perf);
3353
- }
3354
- }
3355
- function roundDb(value) {
3356
- return value === void 0 ? void 0 : Math.round(Math.min(value, 999) * 10) / 10;
3357
- }
3358
- function aggregateDrawElement(perfs, de) {
3359
- if (perfs.length === 0) return void 0;
3360
- const modes = [...new Set(perfs.map((p) => p.captureMode).filter(Boolean))].sort();
3361
- const gateReasons = [
3362
- ...new Set(
3363
- [de.probeGateReason, ...perfs.map((p) => p.deGateReason)].filter((r) => !!r)
3364
- )
3365
- ].sort();
3366
- const gpuRenderers = [
3367
- ...new Set(perfs.map((p) => p.gpuRenderer).filter((r) => !!r))
3368
- ].sort();
3369
- const drain = de.drainStats;
3370
- return {
3371
- mode: modes.join("|") || "unknown",
3372
- compileGate: de.compileGate,
3373
- clampReason: de.clampReason,
3374
- workerInversion: de.workerInversion ?? "none",
3375
- preInversionWorkers: de.preInversionWorkers,
3376
- compositionElementCount: de.compositionElementCount,
3377
- compositionElementCountSource: de.compositionElementCountSource,
3378
- compositionElementTags: de.compositionElementTags,
3379
- arollVideoCount: de.arollVideoCount,
3380
- heygenVideoCount: de.heygenVideoCount,
3381
- adaptersUsed: de.adaptersUsed,
3382
- audioCount: de.audioCount,
3383
- imageCount: de.imageCount,
3384
- subCompositionCount: de.subCompositionCount,
3385
- audioGroupCount: de.audioGroupCount,
3386
- colorGradingCount: de.colorGradingCount,
3387
- hasLut: de.hasLut,
3388
- rootBodyMismatch: de.rootBodyMismatch,
3389
- rootBodyDeltaPxBucket: de.rootBodyDeltaPxBucket,
3390
- vfxHostCount: de.vfxHostCount,
3391
- vfxCapture: de.vfxCapture,
3392
- vfxTypes: de.vfxTypes,
3393
- shortBand: de.shortBand,
3394
- parallelRouter: de.parallelRouter ?? "none",
3395
- preRouterWorkers: de.preRouterWorkers,
3396
- gateReason: gateReasons.length > 0 ? gateReasons.join("|") : void 0,
3397
- gpuRenderer: gpuRenderers.length > 0 ? gpuRenderers.join("|") : void 0,
3398
- workerEncode: perfs.some((p) => p.deWorkerEncode),
3399
- verifyArmed: perfs.reduce((sum2, p) => sum2 + (p.deVerifyArmed ?? 0), 0),
3400
- verifyChecked: drain?.verifyChecked ?? 0,
3401
- verifyMinDb: roundDb(drain?.verifyMinDb),
3402
- verifyInitMs: perfs.reduce((sum2, p) => sum2 + (p.deVerifyInitMs ?? 0), 0),
3403
- selfVerifyFallback: de.selfVerifyFallback,
3404
- fallbackReason: de.fallbackReason,
3405
- fallbackFailedDb: roundDb(de.fallbackFailedDb),
3406
- fallbackFrameIndex: de.fallbackFrameIndex,
3407
- fallbackThresholdDb: roundDb(de.fallbackThresholdDb),
3408
- blankSuspects: drain?.blankSuspects ?? 0,
3409
- blankDeterministicAccepts: drain?.blankDeterministicAccepts ?? 0,
3410
- blankRecaptures: drain?.blankRecaptures ?? 0,
3411
- boundaryFrames: perfs.reduce((sum2, p) => sum2 + (p.deBoundaryFrames ?? 0), 0),
3412
- ncprFallbacks: perfs.reduce((sum2, p) => sum2 + (p.deNcprFallbacks ?? 0), 0),
3413
- frameTimeouts: perfs.reduce((sum2, p) => sum2 + (p.deFrameTimeouts ?? 0), 0)
3414
- };
3415
- }
3416
- function maxDefined(values) {
3417
- const present = values.filter((v) => typeof v === "number");
3418
- return present.length > 0 ? Math.max(...present) : void 0;
3419
- }
3420
- function aggregateChromeMemory(perfs) {
3421
- const sampled = perfs.filter((p) => (p.chromeMemorySamples ?? 0) > 0);
3422
- if (sampled.length === 0) return void 0;
3423
- return {
3424
- browserRssPeakMb: maxDefined(sampled.map((p) => p.chromeBrowserRssPeakMb)),
3425
- rendererRssPeakMb: maxDefined(sampled.map((p) => p.chromeRendererRssPeakMb)),
3426
- // Last samples are per session; summing approximates the whole fleet's
3427
- // footprint at the end of capture (workers run concurrently).
3428
- rssLastMb: sampled.reduce((sum2, p) => sum2 + (p.chromeRssLastMb ?? 0), 0),
3429
- gpuProcessSeenLastSample: sampled.some((p) => p.chromeGpuProcessSeenLastSample === true),
3430
- samples: sampled.reduce((sum2, p) => sum2 + (p.chromeMemorySamples ?? 0), 0)
3431
- };
3432
- }
3433
- function aggregateDedup(perfs) {
3434
- if (perfs.length === 0) return void 0;
3435
- const armed = perfs.some((p) => p.staticDedupArmed);
3436
- const skipReasons = armed ? [] : [
3437
- ...new Set(perfs.map((p) => p.staticDedupSkipReason).filter((r) => !!r))
3438
- ].sort();
3439
- const verificationPerfs = perfs.filter((perf) => perf.staticDedupVerificationOutcome);
3440
- const verificationOutcomes = [
3441
- ...new Set(
3442
- verificationPerfs.map((perf) => perf.staticDedupVerificationOutcome).filter((outcome) => outcome != null)
3443
- )
3444
- ].sort();
3445
- return {
3446
- enabled: perfs.some((p) => p.staticDedupEnabled),
3447
- armed,
3448
- predictedFrames: perfs.reduce((sum2, p) => sum2 + (p.staticDedupPredicted ?? 0), 0),
3449
- reusedFrames: perfs.reduce((sum2, p) => sum2 + (p.staticDedupReused ?? 0), 0),
3450
- skipReason: skipReasons.length > 0 ? skipReasons.join("|") : void 0,
3451
- ...verificationPerfs.length === 0 ? {} : {
3452
- verifiedFrames: verificationPerfs.reduce(
3453
- (sum2, perf) => sum2 + (perf.staticDedupVerified ?? 0),
3454
- 0
3455
- ),
3456
- verificationOutcomes,
3457
- plannedRuns: verificationPerfs.reduce(
3458
- (sum2, perf) => sum2 + (perf.staticDedupVerificationPlannedRuns ?? 0),
3459
- 0
3460
- ),
3461
- completedRuns: verificationPerfs.reduce(
3462
- (sum2, perf) => sum2 + (perf.staticDedupVerificationCompletedRuns ?? 0),
3463
- 0
3464
- ),
3465
- screenshots: verificationPerfs.reduce(
3466
- (sum2, perf) => sum2 + (perf.staticDedupVerificationScreenshots ?? 0),
3467
- 0
3468
- ),
3469
- seeks: verificationPerfs.reduce(
3470
- (sum2, perf) => sum2 + (perf.staticDedupVerificationSeeks ?? 0),
3471
- 0
3472
- ),
3473
- comparisons: verificationPerfs.reduce(
3474
- (sum2, perf) => sum2 + (perf.staticDedupVerificationComparisons ?? 0),
3475
- 0
3476
- ),
3477
- verificationElapsedMs: verificationPerfs.reduce(
3478
- (sum2, perf) => sum2 + (perf.staticDedupVerificationElapsedMs ?? 0),
3479
- 0
3480
- )
3514
+ }
3515
+ cleanedUpVideos.add(videoId);
3516
+ }
3481
3517
  }
3482
- };
3518
+ }
3483
3519
  }
3484
- function aggregateBeginFrameReuse(perfs) {
3485
- const noDamageFrames = perfs.reduce((sum2, p) => sum2 + (p.beginFrameNoDamage ?? 0), 0);
3486
- const hasDamageFrames = perfs.reduce((sum2, p) => sum2 + (p.beginFrameHasDamage ?? 0), 0);
3487
- if (noDamageFrames + hasDamageFrames === 0) return void 0;
3488
- return { noDamageFrames, hasDamageFrames };
3520
+
3521
+ // ../producer/src/services/render/segmentPlan.ts
3522
+ var DEFAULT_SEGMENT_FRAMES = 3e3;
3523
+ var MIN_SEGMENT_FRAMES = 30;
3524
+ function assertPositiveInteger(name, value) {
3525
+ if (!Number.isInteger(value) || value <= 0) {
3526
+ throw new Error(`[segmentPlan] ${name} must be a positive integer (got ${String(value)})`);
3527
+ }
3489
3528
  }
3490
- function buildRenderPerfSummary(input) {
3529
+ function planSegments(totalFrames, segmentFrames) {
3530
+ assertPositiveInteger("totalFrames", totalFrames);
3531
+ assertPositiveInteger("segmentFrames", segmentFrames);
3532
+ const slices = [];
3533
+ for (let start = 0, index = 0; start < totalFrames; start += segmentFrames, index += 1) {
3534
+ slices.push({
3535
+ index,
3536
+ startFrame: start,
3537
+ endFrame: Math.min(totalFrames, start + segmentFrames)
3538
+ });
3539
+ }
3540
+ return slices;
3541
+ }
3542
+ function resolveSegmentFrames(env) {
3543
+ const raw = env.HF_SEGMENT_FRAMES;
3544
+ if (raw === void 0 || raw.trim() === "") return DEFAULT_SEGMENT_FRAMES;
3545
+ const parsed = Number(raw);
3546
+ if (!Number.isInteger(parsed)) return DEFAULT_SEGMENT_FRAMES;
3547
+ return Math.max(MIN_SEGMENT_FRAMES, parsed);
3548
+ }
3549
+
3550
+ // ../producer/src/services/render/segmentQueue.ts
3551
+ function createSegmentQueue(slices) {
3552
+ let cursor = 0;
3491
3553
  return {
3492
- renderId: input.job.id,
3493
- totalElapsedMs: input.totalElapsedMs,
3494
- // RenderPerfSummary surfaces fps as a decimal because it lands in JSON
3495
- // payloads (CLI telemetry, regression-harness reports) where a single
3496
- // number is friendlier than `{num,den}`. Callers needing the rational
3497
- // back can read `job.config.fps`.
3498
- fps: fpsToNumber(input.job.config.fps),
3499
- quality: input.job.config.quality,
3500
- workers: input.workerCount,
3501
- workerSizing: input.workerSizing,
3502
- chunkedEncode: input.enableChunkedEncode,
3503
- chunkSizeFrames: input.enableChunkedEncode ? input.chunkedEncodeSize : null,
3504
- compositionDurationSeconds: input.compositionDurationSeconds,
3505
- totalFrames: input.totalFrames,
3506
- resolution: { width: input.outputWidth, height: input.outputHeight },
3507
- videoCount: input.videoCount,
3508
- audioCount: input.audioCount,
3509
- stages: input.perfStages,
3510
- videoExtractBreakdown: input.videoExtractBreakdown,
3511
- tmpPeakBytes: input.tmpPeakBytes,
3512
- captureCalibration: input.captureCalibration ? {
3513
- sampledFrames: input.captureCalibration.samples.map((sample) => sample.frameIndex),
3514
- p95Ms: input.captureCalibration.estimate.p95Ms,
3515
- multiplier: input.captureCalibration.estimate.multiplier,
3516
- reasons: input.captureCalibration.estimate.reasons
3517
- } : void 0,
3518
- captureAttempts: input.captureAttempts.length > 0 ? input.captureAttempts : void 0,
3519
- hdrDiagnostics: input.hdrDiagnostics.videoExtractionFailures > 0 || input.hdrDiagnostics.imageDecodeFailures > 0 ? { ...input.hdrDiagnostics } : void 0,
3520
- hdrPerf: input.hdrPerf ? finalizeHdrPerf(input.hdrPerf) : void 0,
3521
- observability: input.observability,
3522
- captureAvgMs: input.totalFrames > 0 ? Math.round(
3523
- (input.perfStages.captureFrameMs ?? input.perfStages.captureMs ?? 0) / input.totalFrames
3524
- ) : void 0,
3525
- subTimelineWait: worstSubTimelineWaitOutcome(input.dedupPerfs),
3526
- captureP50Ms: (() => {
3527
- const withSamples = input.dedupPerfs.filter((p) => (p.p50TotalMs ?? 0) > 0);
3528
- if (withSamples.length === 0) return void 0;
3529
- return withSamples.reduce((a, b) => b.frames > a.frames ? b : a).p50TotalMs;
3530
- })(),
3531
- peakRssMb: Math.round(input.peakRssBytes / (1024 * 1024)),
3532
- peakHeapUsedMb: Math.round(input.peakHeapUsedBytes / (1024 * 1024)),
3533
- staticDedup: aggregateDedup(input.dedupPerfs),
3534
- chromeMemory: aggregateChromeMemory(input.dedupPerfs),
3535
- beginFrameReuse: aggregateBeginFrameReuse(input.dedupPerfs),
3536
- drawElement: aggregateDrawElement(
3537
- input.dedupPerfs,
3538
- input.drawElement ?? { selfVerifyFallback: false }
3539
- ),
3540
- host: {
3541
- platform: platform(),
3542
- arch: arch(),
3543
- cpuCount: cpus().length,
3544
- totalMemMb: Math.round(totalmem() / (1024 * 1024)),
3545
- nodeVersion: process.version,
3546
- gpuDisabled: input.gpuDisabled
3547
- }
3554
+ next: () => slices[cursor++],
3555
+ remaining: () => Math.max(0, slices.length - cursor)
3548
3556
  };
3549
3557
  }
3550
3558
 
3559
+ // ../producer/src/services/render/segmentRecycle.ts
3560
+ var DEFAULT_SEGMENT_BROWSER_RECYCLE = 3;
3561
+ function resolveSegmentBrowserRecycle(env) {
3562
+ const raw = env.HF_SEGMENT_BROWSER_RECYCLE;
3563
+ if (raw === void 0 || raw.trim() === "") return DEFAULT_SEGMENT_BROWSER_RECYCLE;
3564
+ const parsed = Number(raw);
3565
+ return Number.isInteger(parsed) && parsed >= 0 ? parsed : DEFAULT_SEGMENT_BROWSER_RECYCLE;
3566
+ }
3567
+ var TARGET_LOSS_PATTERNS = [/target closed/i, /detached frame/i, /session closed/i];
3568
+ function isTargetLossError(error) {
3569
+ if (!(error instanceof Error)) return false;
3570
+ return TARGET_LOSS_PATTERNS.some((re) => re.test(error.message));
3571
+ }
3572
+
3551
3573
  // ../producer/src/services/render/stages/captureStreamingStage.ts
3574
+ import { mkdtemp, writeFile } from "fs/promises";
3575
+ import { tmpdir } from "os";
3576
+ import { join as join8 } from "path";
3552
3577
  var DEFAULT_CAPTURE_STALL_MS = 6e4;
3553
3578
  var DE_STALL_POLL_MS = 5e3;
3554
3579
  var STALL_ADVICE = "Chrome stopped answering. If another app is keeping the GPU or CPU busy (a 3D render, a game, another export), let it finish or render one job at a time, then retry.";
3580
+ function poolFailure(error, probeSession) {
3581
+ return wrapCaptureStageError(error, probeSession?.browserConsoleBuffer ?? []);
3582
+ }
3555
3583
  var ParallelCaptureStallError = class extends Error {
3556
3584
  variant;
3557
3585
  constructor(args) {
@@ -3998,7 +4026,7 @@ async function runCaptureStreamingStage(input) {
3998
4026
  }
3999
4027
  );
4000
4028
  } catch (err) {
4001
- if (parallelDrainError) throw parallelDrainError;
4029
+ if (parallelDrainError) throw poolFailure(parallelDrainError, probeSession);
4002
4030
  if (stalled && abortSignal?.aborted !== true) {
4003
4031
  throw new ParallelCaptureStallError({
4004
4032
  variant: "pool",
@@ -4009,13 +4037,15 @@ async function runCaptureStreamingStage(input) {
4009
4037
  phaseSummary: phaseSummary()
4010
4038
  });
4011
4039
  }
4012
- throw err;
4040
+ throw poolFailure(err, probeSession);
4013
4041
  } finally {
4014
4042
  clearInterval(stallTimer);
4015
4043
  abortSignal?.removeEventListener("abort", forwardParentAbort);
4016
4044
  }
4017
- if (parallelDrainError) throw parallelDrainError;
4045
+ if (parallelDrainError) throw poolFailure(parallelDrainError, probeSession);
4018
4046
  pushWorkerDedupPerfs(workerResults, dedupPerfs);
4047
+ const workerPageErrors = [];
4048
+ pushWorkerPageErrors(workerResults, workerPageErrors);
4019
4049
  if (parallelGuardRan) {
4020
4050
  deDrainStats = parallelStats;
4021
4051
  }
@@ -4025,6 +4055,7 @@ async function runCaptureStreamingStage(input) {
4025
4055
  await closeCaptureSession(probeSession);
4026
4056
  probeSession = null;
4027
4057
  }
4058
+ lastBrowserConsole = withWorkerPageErrors(lastBrowserConsole, workerPageErrors);
4028
4059
  } else {
4029
4060
  const videoInjector = createRenderVideoFrameInjector();
4030
4061
  const session = probeSession ?? await createCaptureSession(
@@ -4471,7 +4502,7 @@ async function runCaptureSegmentedStage(input) {
4471
4502
 
4472
4503
  // ../producer/src/services/render/segmentManifest.ts
4473
4504
  import { createHash as createHash2 } from "crypto";
4474
- import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync4, renameSync as renameSync2, statSync as statSync3, writeFileSync as writeFileSync3 } from "fs";
4505
+ import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync4, statSync as statSync3, writeFileSync as writeFileSync3 } from "fs";
4475
4506
  import { join as join10 } from "path";
4476
4507
  var MANIFEST_FILENAME = "segments.json";
4477
4508
  function computeSegmentPlanHash(input) {
@@ -4528,7 +4559,7 @@ function writeSegmentManifest(segmentDir, manifest) {
4528
4559
  mkdirSync4(segmentDir, { recursive: true });
4529
4560
  const tmp = join10(segmentDir, `${MANIFEST_FILENAME}.${process.pid}.tmp`);
4530
4561
  writeFileSync3(tmp, JSON.stringify(manifest, null, 2), "utf-8");
4531
- renameSync2(tmp, join10(segmentDir, MANIFEST_FILENAME));
4562
+ renameWithRetry(tmp, join10(segmentDir, MANIFEST_FILENAME));
4532
4563
  }
4533
4564
  async function probeSegmentFrameCount(path, probe) {
4534
4565
  try {
@@ -6642,7 +6673,7 @@ async function readLocalFont(absPath) {
6642
6673
  return { kind: "inline", buffer: Buffer.concat(chunks, totalBytes) };
6643
6674
  }
6644
6675
  async function embedLocalFontFaces(html, projectDir) {
6645
- const { fontToDataUri: toDataUri } = await import("./deterministicFonts-RO2TCMLZ.js");
6676
+ const { fontToDataUri: toDataUri } = await import("./deterministicFonts-RADD3BSX.js");
6646
6677
  const styleBlockRe = /<style\b[^>]*>([\s\S]*?)<\/style>/gi;
6647
6678
  const fontFaceRe = /@font-face\s*\{([^}]*)\}/gi;
6648
6679
  let result = html;
@@ -6923,6 +6954,9 @@ async function discoverMediaFromBrowser(page) {
6923
6954
  const intrinsicDuration = isImage ? 0 : htmlEl.duration;
6924
6955
  const playbackStartRaw = htmlEl.getAttribute("data-playback-start");
6925
6956
  const mediaStartRaw = htmlEl.getAttribute("data-media-start");
6957
+ const playbackRateRaw = htmlEl.getAttribute("data-playback-rate");
6958
+ const automationRaw = htmlEl.getAttribute("data-automation");
6959
+ const defaultPlaybackRate = isImage ? 1 : htmlEl.defaultPlaybackRate;
6926
6960
  const loop = htmlEl.hasAttribute("loop");
6927
6961
  const hasAudio = htmlEl.getAttribute("data-has-audio") === "true";
6928
6962
  const volume = parseFloat(htmlEl.getAttribute("data-volume") || "1");
@@ -6938,6 +6972,9 @@ async function discoverMediaFromBrowser(page) {
6938
6972
  durationInferred,
6939
6973
  playbackStartRaw,
6940
6974
  mediaStartRaw,
6975
+ playbackRateRaw,
6976
+ automationRaw,
6977
+ defaultPlaybackRate,
6941
6978
  loop,
6942
6979
  hasAudio,
6943
6980
  volume,
@@ -6947,7 +6984,17 @@ async function discoverMediaFromBrowser(page) {
6947
6984
  return results;
6948
6985
  });
6949
6986
  return elements.map(
6950
- ({ endRaw, durationRaw, intrinsicDuration, playbackStartRaw, mediaStartRaw, ...element }) => ({
6987
+ ({
6988
+ endRaw,
6989
+ durationRaw,
6990
+ intrinsicDuration,
6991
+ playbackStartRaw,
6992
+ mediaStartRaw,
6993
+ playbackRateRaw,
6994
+ automationRaw,
6995
+ defaultPlaybackRate,
6996
+ ...element
6997
+ }) => ({
6951
6998
  ...element,
6952
6999
  end: parseStrictFiniteTimingNumber(endRaw) ?? 0,
6953
7000
  duration: element.durationInferred && Number.isFinite(intrinsicDuration) && intrinsicDuration > 0 ? intrinsicDuration : parseStrictFiniteTimingNumber(durationRaw) ?? 0,
@@ -6957,7 +7004,14 @@ async function discoverMediaFromBrowser(page) {
6957
7004
  if (name === "data-media-start") return mediaStartRaw;
6958
7005
  return null;
6959
7006
  }
6960
- })
7007
+ }),
7008
+ playbackRate: resolveRateSpec(
7009
+ automationRaw,
7010
+ readPlaybackRate(
7011
+ (name) => name === "data-playback-rate" ? playbackRateRaw : null,
7012
+ defaultPlaybackRate
7013
+ )
7014
+ )
6961
7015
  })
6962
7016
  );
6963
7017
  }
@@ -7425,16 +7479,28 @@ function authoredMediaStarts(html) {
7425
7479
  }
7426
7480
  return starts;
7427
7481
  }
7428
- function adoptRuntimeMediaStart(composition, el, authored) {
7482
+ function isVideoAudio(audio, videoId) {
7483
+ return audio.type === "video" && audio.id === videoAudioId(videoId);
7484
+ }
7485
+ function adoptRuntimeMediaTiming(composition, el, authored) {
7429
7486
  const shift = el.mediaStart - authored;
7430
- if (el.mediaStart <= 0 || Math.abs(shift) <= BROWSER_MEDIA_EPSILON) return;
7431
7487
  const entries = el.tagName === "video" ? [
7432
7488
  ...composition.videos.filter((video) => video.id === el.id),
7433
- ...composition.audios.filter(
7434
- (audio) => audio.type === "video" && audio.id === `${el.id}-audio`
7435
- )
7489
+ ...composition.audios.filter((audio) => isVideoAudio(audio, el.id))
7436
7490
  ] : composition.audios.filter((audio) => audio.type !== "video" && audio.id === el.id);
7437
- for (const entry of entries) entry.mediaStart += shift;
7491
+ for (const entry of entries) {
7492
+ if (typeof entry.playbackRate !== "object") {
7493
+ if (typeof el.playbackRate === "object") {
7494
+ const consumed = Math.max(0, entry.mediaStart - authored);
7495
+ const head = timeAtSourceTime(normalizeRateSpec(entry.playbackRate), consumed);
7496
+ entry.mediaStart += sourceTimeAt(el.playbackRate, head) - consumed;
7497
+ entry.playbackRate = shiftRateLane(el.playbackRate, head);
7498
+ } else {
7499
+ entry.playbackRate = el.playbackRate;
7500
+ }
7501
+ }
7502
+ if (el.mediaStart > 0 && Math.abs(shift) > BROWSER_MEDIA_EPSILON) entry.mediaStart += shift;
7503
+ }
7438
7504
  }
7439
7505
  function reconcileBrowserMediaEnd(existingEnd, projectedEnd, sourceChanged, durationInferred) {
7440
7506
  if (projectedEnd <= 0) return existingEnd;
@@ -7718,7 +7784,7 @@ async function runProbeStage(input) {
7718
7784
  sourceChanged,
7719
7785
  el.durationInferred
7720
7786
  );
7721
- adoptRuntimeMediaStart(
7787
+ adoptRuntimeMediaTiming(
7722
7788
  composition,
7723
7789
  el,
7724
7790
  authoredStarts.get(el.id) ?? existing.mediaStart
@@ -7741,6 +7807,7 @@ async function runProbeStage(input) {
7741
7807
  start: el.start,
7742
7808
  end: resolveBrowserMediaEnd(el.start, el.end, el.duration),
7743
7809
  mediaStart: el.mediaStart,
7810
+ playbackRate: el.playbackRate,
7744
7811
  loop: el.loop,
7745
7812
  hasAudio: el.hasAudio && !el.muted
7746
7813
  });
@@ -7765,7 +7832,7 @@ async function runProbeStage(input) {
7765
7832
  sourceChanged,
7766
7833
  el.durationInferred
7767
7834
  );
7768
- adoptRuntimeMediaStart(
7835
+ adoptRuntimeMediaTiming(
7769
7836
  composition,
7770
7837
  el,
7771
7838
  authoredStarts.get(el.id) ?? existing.mediaStart
@@ -7783,6 +7850,7 @@ async function runProbeStage(input) {
7783
7850
  end: resolveBrowserMediaEnd(el.start, el.end, el.duration),
7784
7851
  mediaStart: el.mediaStart,
7785
7852
  loop: el.loop,
7853
+ playbackRate: el.playbackRate,
7786
7854
  layer: 0,
7787
7855
  volume: el.volume,
7788
7856
  type: "audio"
@@ -8380,7 +8448,7 @@ function appendAutoDetectedVideoAudio(composition, extracted) {
8380
8448
  const video = composition.videos.find((v) => v.id === ext.videoId);
8381
8449
  if (!video || !video.hasAudio || video.hidden || existingAudioSrcs.has(video.src)) continue;
8382
8450
  composition.audios.push({
8383
- id: `${video.id}-audio`,
8451
+ id: videoAudioId(video.id),
8384
8452
  src: video.src,
8385
8453
  start: video.start,
8386
8454
  end: video.end,
@@ -8401,7 +8469,7 @@ import { rmSync as rmSync7 } from "fs";
8401
8469
 
8402
8470
  // ../producer/src/services/render/audioPadTrim.ts
8403
8471
  import { spawn } from "child_process";
8404
- import { mkdtempSync as mkdtempSync3, renameSync as renameSync3, rmSync as rmSync6 } from "fs";
8472
+ import { mkdtempSync as mkdtempSync3, rmSync as rmSync6 } from "fs";
8405
8473
  import { dirname as dirname6, join as join16 } from "path";
8406
8474
 
8407
8475
  // ../producer/src/services/render/audioTruePeak.ts
@@ -8648,7 +8716,7 @@ async function enforceAacTruePeak(input) {
8648
8716
  }
8649
8717
  if (truePeakDbfs <= AAC_DELIVERY_TRUE_PEAK_DBFS) {
8650
8718
  if (measuredPath !== correctedPath) return { success: true };
8651
- renameSync3(correctedPath, input.audioPath);
8719
+ renameWithRetry(correctedPath, input.audioPath);
8652
8720
  return { success: true, loweredDb: Number((-attenuationDb).toFixed(2)) };
8653
8721
  }
8654
8722
  if (pass === MAX_TRUE_PEAK_CORRECTION_PASSES) break;
@@ -8838,6 +8906,7 @@ async function runAudioStage(input) {
8838
8906
  const result = await mixAudioForDelivery(input, duration, pcmSourcePath);
8839
8907
  let audioLoweredDb;
8840
8908
  if (result.hasAudio) {
8909
+ const guardStart = Date.now();
8841
8910
  const peak = await constrainAacTruePeak({
8842
8911
  audioPath: result.audioOutputPath,
8843
8912
  sourcePath: pcmSourcePath,
@@ -8847,6 +8916,7 @@ async function runAudioStage(input) {
8847
8916
  });
8848
8917
  if (!peak.success) throw encoderFailureError("Audio true-peak check failed", peak);
8849
8918
  audioLoweredDb = peak.loweredDb;
8919
+ input.log?.info("[audio] phases", { ...result.audioPhaseMs, guard: Date.now() - guardStart });
8850
8920
  }
8851
8921
  return { ...result, audioLoweredDb, audioProcessMs: Date.now() - stageStart };
8852
8922
  } finally {
@@ -8872,6 +8942,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
8872
8942
  let hasAudio = false;
8873
8943
  let audioError;
8874
8944
  let audioFailures;
8945
+ let audioPhaseMs;
8875
8946
  if (audios.length > 0) {
8876
8947
  let audioResult;
8877
8948
  try {
@@ -8921,6 +8992,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
8921
8992
  assertAudioNotInterrupted(audioResult.failures);
8922
8993
  hasAudio = audioResult.success;
8923
8994
  audioFailures = audioResult.failures;
8995
+ audioPhaseMs = audioResult.phaseMs;
8924
8996
  if (!hasAudio) {
8925
8997
  audioError = audioResult.error ?? "audio mix failed for an unknown reason";
8926
8998
  for (const failure of audioResult.failures ?? []) {
@@ -8939,7 +9011,8 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
8939
9011
  audioOutputPath,
8940
9012
  hasAudio,
8941
9013
  audioError,
8942
- audioFailures
9014
+ audioFailures,
9015
+ audioPhaseMs
8943
9016
  };
8944
9017
  }
8945
9018
 
@@ -9054,6 +9127,7 @@ async function runCaptureStage(input) {
9054
9127
  let { workerCount } = plan2;
9055
9128
  const { forceScreenshot } = plan2;
9056
9129
  let lastBrowserConsole = [];
9130
+ const workerPageErrors = [];
9057
9131
  let captureBeyondViewport = probeSession?.options.captureBeyondViewport;
9058
9132
  const captureCfg = cfg.forceScreenshot === forceScreenshot ? cfg : { ...cfg, forceScreenshot };
9059
9133
  if (frameRange !== void 0) {
@@ -9091,6 +9165,7 @@ async function runCaptureStage(input) {
9091
9165
  abortSignal,
9092
9166
  frameRangeStart: frameRange?.startFrame,
9093
9167
  dedupPerfs,
9168
+ pageErrorLines: workerPageErrors,
9094
9169
  onProgress: (progress) => {
9095
9170
  if (progress.latestWorkerPhase) {
9096
9171
  if (progress.capturedFrames === 0) reportWorkerStartup(job, progress, onProgress);
@@ -9111,6 +9186,11 @@ async function runCaptureStage(input) {
9111
9186
  },
9112
9187
  cfg: captureCfg,
9113
9188
  log
9189
+ }).catch((error) => {
9190
+ throw wrapCaptureStageError(
9191
+ error,
9192
+ withWorkerPageErrors(probeSession?.browserConsoleBuffer ?? [], workerPageErrors)
9193
+ );
9114
9194
  });
9115
9195
  captureAttempts.push(...attempts);
9116
9196
  const lastAttempt = attempts[attempts.length - 1];
@@ -9123,6 +9203,7 @@ async function runCaptureStage(input) {
9123
9203
  await closeCaptureSession(probeSession);
9124
9204
  probeSession = null;
9125
9205
  }
9206
+ lastBrowserConsole = withWorkerPageErrors(lastBrowserConsole, workerPageErrors);
9126
9207
  } else {
9127
9208
  const sequential = await runSequentialDiskCapture(
9128
9209
  input,
@@ -10400,7 +10481,7 @@ function videoEncoderOptions(input, engineCfg) {
10400
10481
  };
10401
10482
  }
10402
10483
  function canEncodeWhileCapturing(input, workerCount, cpuCount) {
10403
- return workerCount > 1 && workerCount < cpuCount && !input.isPngSequence && !input.isGif && !input.enableChunkedEncode && input.preset.codec === "h264" && !input.preset.hdr && input.job.config.useGpu !== true && input.captureImageFormat === "jpeg" && input.lockGopForChunkConcat !== true;
10484
+ return workerCount > 1 && workerCount < cpuCount && !input.isPngSequence && !input.isGif && !input.enableChunkedEncode && input.preset.codec === "h264" && !input.preset.hdr && input.job.config.useGpu !== true && input.captureImageFormat === "jpeg";
10404
10485
  }
10405
10486
  async function readLandedFrame(workerPath, mergedPath) {
10406
10487
  if (workerPath !== void 0) {
@@ -11055,6 +11136,7 @@ async function executeDiskCaptureWithAdaptiveRetry(options) {
11055
11136
  options.cfg
11056
11137
  );
11057
11138
  pushWorkerDedupPerfs(workerResults, options.dedupPerfs);
11139
+ if (options.pageErrorLines) pushWorkerPageErrors(workerResults, options.pageErrorLines);
11058
11140
  } finally {
11059
11141
  await mergeWorkerFrames(attemptWorkDir, tasks, options.framesDir);
11060
11142
  }
@@ -11317,13 +11399,36 @@ function colorGradingValueHasLut(rawAttributeValue) {
11317
11399
  return false;
11318
11400
  }
11319
11401
  }
11320
- async function resolveCompositionElementCount(probeSession, html) {
11402
+ var PROBE_READ_TIMEOUT_MS = 5e3;
11403
+ async function evaluateWithinBound(page, fn, what, log) {
11404
+ let timer;
11405
+ const timedOut = new Promise((_, reject) => {
11406
+ timer = setTimeout(
11407
+ () => reject(new Error(`no answer within ${PROBE_READ_TIMEOUT_MS} ms (page busy)`)),
11408
+ PROBE_READ_TIMEOUT_MS
11409
+ );
11410
+ });
11411
+ try {
11412
+ return await Promise.race([page.evaluate(fn), timedOut]);
11413
+ } catch (err) {
11414
+ log.warn(`[Render] live ${what} read skipped; using the static answer`, {
11415
+ error: err instanceof Error ? err.message : String(err)
11416
+ });
11417
+ throw err;
11418
+ } finally {
11419
+ clearTimeout(timer);
11420
+ }
11421
+ }
11422
+ async function resolveCompositionElementCount(probeSession, html, log = defaultLogger) {
11321
11423
  if (probeSession?.isInitialized) {
11322
11424
  try {
11323
- const liveCount = await probeSession.page.evaluate(
11425
+ const liveCount = await evaluateWithinBound(
11426
+ probeSession.page,
11324
11427
  // Live HTMLCollection length — avoids materializing a static NodeList
11325
11428
  // on the large-DOM comps this gate exists to catch (review nit).
11326
- () => document.getElementsByTagName("*").length
11429
+ () => document.getElementsByTagName("*").length,
11430
+ "element count",
11431
+ log
11327
11432
  );
11328
11433
  if (typeof liveCount === "number" && Number.isFinite(liveCount)) {
11329
11434
  return { count: liveCount, source: "live" };
@@ -11411,11 +11516,11 @@ function probeAdaptersUsed() {
11411
11516
  }
11412
11517
  return used;
11413
11518
  }
11414
- async function resolveAdaptersUsed(probeSession, html) {
11519
+ async function resolveAdaptersUsed(probeSession, html, log = defaultLogger) {
11415
11520
  const staticAdapters = detectAdaptersStatic(html);
11416
11521
  if (!probeSession?.isInitialized) return staticAdapters;
11417
11522
  try {
11418
- const live = await probeSession.page.evaluate(probeAdaptersUsed);
11523
+ const live = await evaluateWithinBound(probeSession.page, probeAdaptersUsed, "adapters", log);
11419
11524
  if (!Array.isArray(live)) return staticAdapters;
11420
11525
  const detected = /* @__PURE__ */ new Set([...staticAdapters, ...live]);
11421
11526
  return KNOWN_RUNTIME_ADAPTERS.filter((name) => detected.has(name));
@@ -11673,7 +11778,10 @@ async function executeRenderPipeline(input) {
11673
11778
  );
11674
11779
  const artifactTransaction = new ArtifactTransaction(
11675
11780
  outputPath,
11676
- isPngSequence || isHls ? "directory" : "file"
11781
+ isPngSequence || isHls ? "directory" : "file",
11782
+ void 0,
11783
+ void 0,
11784
+ log
11677
11785
  );
11678
11786
  const stagedOutputPath = artifactTransaction.stagingPath;
11679
11787
  const needsAlpha = outputNeedsAlpha(outputFormat);
@@ -12172,8 +12280,8 @@ async function executeRenderPipeline(input) {
12172
12280
  vfxHostCount,
12173
12281
  vfxCapture,
12174
12282
  vfxTypes
12175
- } = await resolveCompositionElementCount(probeSession, compiled.html);
12176
- const adaptersUsed = await resolveAdaptersUsed(probeSession, compiled.html);
12283
+ } = await resolveCompositionElementCount(probeSession, compiled.html, log);
12284
+ const adaptersUsed = await resolveAdaptersUsed(probeSession, compiled.html, log);
12177
12285
  const deShortBandEnabled = deShortBandMaxElements > 0;
12178
12286
  const deShortBandOpen = deShortBandEnabled && deShortBandMinFrames > 0 && compositionElementCountSource === "live" && compositionElementCount <= deShortBandMaxElements;
12179
12287
  const deShortBandRoute = process.env.HF_DE_SHORT_BAND_ROUTE === "true";
@@ -13433,7 +13541,6 @@ import {
13433
13541
  existsSync as existsSync20,
13434
13542
  linkSync,
13435
13543
  mkdirSync as mkdirSync12,
13436
- renameSync as renameSync5,
13437
13544
  rmSync as rmSync11,
13438
13545
  writeFileSync as writeFileSync8
13439
13546
  } from "fs";
@@ -14360,14 +14467,7 @@ import { writeFileSync as writeFileSync7, writeSync } from "fs";
14360
14467
  import { join as join30 } from "path";
14361
14468
 
14362
14469
  // ../producer/src/services/distributed/mp4Stitch.ts
14363
- import {
14364
- createReadStream as createReadStream4,
14365
- createWriteStream,
14366
- mkdirSync as mkdirSync10,
14367
- mkdtempSync as mkdtempSync4,
14368
- renameSync as renameSync4,
14369
- rmSync as rmSync10
14370
- } from "fs";
14470
+ import { createReadStream as createReadStream4, createWriteStream, mkdirSync as mkdirSync10, mkdtempSync as mkdtempSync4, rmSync as rmSync10 } from "fs";
14371
14471
  import { open } from "fs/promises";
14372
14472
  import { dirname as dirname11, join as join29 } from "path";
14373
14473
  import { finished, pipeline } from "stream/promises";
@@ -14741,7 +14841,7 @@ async function writeStitchedMp4(chunkPaths, outputPath, audioPath) {
14741
14841
  for (const [i, path] of sourcePaths.entries()) await copyPayload(path, layouts[i], i, out);
14742
14842
  out.end();
14743
14843
  await finished(out);
14744
- renameSync4(join29(workDir, "out.mp4"), outputPath);
14844
+ renameWithRetry(join29(workDir, "out.mp4"), outputPath);
14745
14845
  } finally {
14746
14846
  out.destroy();
14747
14847
  rmSync10(workDir, { recursive: true, force: true });
@@ -15467,7 +15567,10 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
15467
15567
  const needsAlpha = outputNeedsAlpha(config.format);
15468
15568
  const remoteFiles = Object.entries(options.remoteFiles ?? {});
15469
15569
  const remoteLocalPath = (rel) => join32(projectDir, ...rel.split("/"));
15470
- for (const [, file] of remoteFiles) declareContentAddressedUrl(file.url, file.sha256);
15570
+ for (const [, file] of remoteFiles) {
15571
+ declareContentAddressedUrl(file.url, file.sha256);
15572
+ if (file.probeFactsUrl) declareProbeFactsUrl(file.url, file.probeFactsUrl);
15573
+ }
15471
15574
  const urlByAbsentLocalPath = new Map(
15472
15575
  remoteFiles.map(([rel, file]) => [remoteLocalPath(rel), file.url])
15473
15576
  );
@@ -15542,7 +15645,7 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
15542
15645
  if (probeResult.fileServer) closeFileServerSafely(probeResult.fileServer, "plan", log);
15543
15646
  if (probeResult.probeSession) {
15544
15647
  try {
15545
- const { closeCaptureSession: closeCaptureSession2 } = await import("./src-SSV66L3F.js");
15648
+ const { closeCaptureSession: closeCaptureSession2 } = await import("./src-HITT6NWG.js");
15546
15649
  await closeCaptureSession2(probeResult.probeSession);
15547
15650
  } catch (err) {
15548
15651
  log.warn("[plan] probe session close failed", {
@@ -15589,7 +15692,8 @@ function mixPlanAudio(c, compiledDir) {
15589
15692
  audios: c.composition.audios,
15590
15693
  masterFxChain: c.composition.masterFxChain,
15591
15694
  abortSignal: c.abortSignal,
15592
- assertNotAborted: c.assertNotAborted
15695
+ assertNotAborted: c.assertNotAborted,
15696
+ log: c.log
15593
15697
  });
15594
15698
  }
15595
15699
  async function extractPlanVideos(c, deferRangeExtraction) {
@@ -15608,6 +15712,7 @@ async function extractPlanVideos(c, deferRangeExtraction) {
15608
15712
  if (extractResult.failureToEnforce) throw extractResult.failureToEnforce;
15609
15713
  if (extractResult.extractionResult) {
15610
15714
  assertVideoExtractionSucceeded(extractResult.extractionResult);
15715
+ c.log.info("[plan] extract phases", { ...extractResult.extractionResult.phaseBreakdown });
15611
15716
  }
15612
15717
  return extractResult;
15613
15718
  }
@@ -15653,12 +15758,12 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
15653
15758
  const videoSourcesDir = join32(planDir, "video-sources");
15654
15759
  rmSync11(videoSourcesDir, { recursive: true, force: true });
15655
15760
  if (existsSync20(stagedVideoFrames)) {
15656
- renameSync5(stagedVideoFrames, videoFramesDst);
15761
+ renameWithRetry(stagedVideoFrames, videoFramesDst);
15657
15762
  } else {
15658
15763
  mkdirSync12(videoFramesDst, { recursive: true });
15659
15764
  }
15660
15765
  if (existsSync20(finalCompiledDir)) rmSync11(finalCompiledDir, { recursive: true, force: true });
15661
- renameSync5(compiledDir, finalCompiledDir);
15766
+ renameWithRetry(compiledDir, finalCompiledDir);
15662
15767
  const shipVideoSource = createVideoSourceShipper({
15663
15768
  planDir,
15664
15769
  projectDir,
@@ -15761,7 +15866,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
15761
15866
  }
15762
15867
  const planAudioPath = join32(planDir, PLAN_AUDIO_RELATIVE_PATH);
15763
15868
  if (audioResult?.hasAudio && existsSync20(audioResult.audioOutputPath)) {
15764
- renameSync5(audioResult.audioOutputPath, planAudioPath);
15869
+ renameWithRetry(audioResult.audioOutputPath, planAudioPath);
15765
15870
  }
15766
15871
  if (chunksCarryAudio && existsSync20(planAudioPath)) {
15767
15872
  if (options.sliceChunkAudio) {
@@ -17193,7 +17298,7 @@ function startServer(options = {}) {
17193
17298
  });
17194
17299
  async function shutdown(signal) {
17195
17300
  log.info(`Received ${signal}, shutting down`);
17196
- const { drainBrowserPool } = await import("./src-SSV66L3F.js");
17301
+ const { drainBrowserPool } = await import("./src-HITT6NWG.js");
17197
17302
  await drainBrowserPool().catch(() => {
17198
17303
  });
17199
17304
  const handle = await Promise.race([
@@ -17237,7 +17342,6 @@ import {
17237
17342
  mkdtempSync as mkdtempSync7,
17238
17343
  readFileSync as readFileSync12,
17239
17344
  readdirSync as readdirSync9,
17240
- renameSync as renameSync7,
17241
17345
  rmSync as rmSync14,
17242
17346
  statSync as statSync9,
17243
17347
  writeFileSync as writeFileSync11
@@ -17252,7 +17356,6 @@ import {
17252
17356
  linkSync as linkSync2,
17253
17357
  mkdirSync as mkdirSync14,
17254
17358
  mkdtempSync as mkdtempSync6,
17255
- renameSync as renameSync6,
17256
17359
  rmSync as rmSync13,
17257
17360
  statSync as statSync8,
17258
17361
  writeFileSync as writeFileSync10
@@ -17345,7 +17448,7 @@ var LocalPlanV2ArtifactPublisher = class {
17345
17448
  if (!canFallbackToCopy(error)) throw error;
17346
17449
  copyFileSync5(blob.sourcePath, temporaryPath);
17347
17450
  }
17348
- renameSync6(temporaryPath, destinationPath);
17451
+ renameWithRetry(temporaryPath, destinationPath);
17349
17452
  } finally {
17350
17453
  rmSync13(stagingDir, { recursive: true, force: true });
17351
17454
  }
@@ -17359,7 +17462,7 @@ var LocalPlanV2ArtifactPublisher = class {
17359
17462
  }
17360
17463
  }
17361
17464
  writeFileSync10(join37(this.temporaryDir, "plan.json"), manifestBytes, "utf-8");
17362
- renameSync6(this.temporaryDir, this.destinationDir);
17465
+ renameWithRetry(this.temporaryDir, this.destinationDir);
17363
17466
  this.#committed = true;
17364
17467
  }
17365
17468
  async abort() {
@@ -17626,7 +17729,7 @@ function writeBlob(sourcePath, destinationPath) {
17626
17729
  const temporaryPath = join38(temporaryDir, "blob");
17627
17730
  try {
17628
17731
  copyFileSync6(sourcePath, temporaryPath);
17629
- renameSync7(temporaryPath, destinationPath);
17732
+ renameWithRetry(temporaryPath, destinationPath);
17630
17733
  } finally {
17631
17734
  rmSync14(temporaryDir, { recursive: true, force: true });
17632
17735
  }
@@ -17758,7 +17861,7 @@ function createPlanV2FromExecutionPlan(executionPlanDir, planV2Dir) {
17758
17861
  canonicalJsonStringify(publication.manifest),
17759
17862
  "utf-8"
17760
17863
  );
17761
- renameSync7(tempDir, planV2Dir);
17864
+ renameWithRetry(tempDir, planV2Dir);
17762
17865
  return resultFromManifest(planV2Dir, publication.manifest);
17763
17866
  } catch (error) {
17764
17867
  rmSync14(tempDir, { recursive: true, force: true });
@@ -18043,7 +18146,7 @@ function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}
18043
18146
  }),
18044
18147
  "utf-8"
18045
18148
  );
18046
- renameSync7(tempDir, destinationDir);
18149
+ renameWithRetry(tempDir, destinationDir);
18047
18150
  } catch (error) {
18048
18151
  rmSync14(tempDir, { recursive: true, force: true });
18049
18152
  throw error;
@@ -18121,7 +18224,6 @@ import {
18121
18224
  mkdtempSync as mkdtempSync8,
18122
18225
  readFileSync as readFileSync13,
18123
18226
  readdirSync as readdirSync10,
18124
- renameSync as renameSync8,
18125
18227
  rmSync as rmSync15,
18126
18228
  statSync as statSync10,
18127
18229
  writeFileSync as writeFileSync12
@@ -18181,7 +18283,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
18181
18283
  const finalPath = join39(workDir, `output.${format}`);
18182
18284
  const audio = audioPath !== null && existsSync26(audioPath) ? audioPath : null;
18183
18285
  await writeJoined(ctx, videoSources, audio, finalPath);
18184
- renameSync8(finalPath, outputPath);
18286
+ renameWithRetry(finalPath, outputPath);
18185
18287
  log.info("[assemble] stage timings", {
18186
18288
  ...stageMs,
18187
18289
  chunks: chunkPaths.length,
@@ -18433,6 +18535,7 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
18433
18535
  // ../producer/src/services/distributed/renderChunk.ts
18434
18536
  import { randomBytes } from "crypto";
18435
18537
  import { existsSync as existsSync27, mkdirSync as mkdirSync17, readFileSync as readFileSync14, readdirSync as readdirSync11, rmSync as rmSync16, writeFileSync as writeFileSync13 } from "fs";
18538
+ import { cpus as cpus3 } from "os";
18436
18539
  import { join as join40 } from "path";
18437
18540
  var LEGACY_DISTRIBUTED_VP9_CPU_USED = 2;
18438
18541
  function validatePlanVideosForChunk(value) {
@@ -18721,6 +18824,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
18721
18824
  let forceScreenshotForChunk = encoder.forceScreenshot;
18722
18825
  let shownBoxes;
18723
18826
  let extraction;
18827
+ const overlap = { current: null };
18724
18828
  try {
18725
18829
  if (chunkWorkerCount === 1 || !forceScreenshotForChunk) {
18726
18830
  const bootStarted = Date.now();
@@ -18808,6 +18912,59 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
18808
18912
  videoFrameLookup,
18809
18913
  frameCacheOptions(cfg, chunkWorkerCount)
18810
18914
  );
18915
+ const isPngSequence = plan2.dimensions.format === "png-sequence";
18916
+ outputKind = isPngSequence ? "frame-dir" : "file";
18917
+ const presetFormat = isPngSequence ? "mp4" : plan2.dimensions.format;
18918
+ const basePreset = getEncoderPreset(job.config.quality, presetFormat, void 0);
18919
+ const preset = resolvePresetForLockedEncoder(basePreset, encoder.encoder);
18920
+ const effectiveQuality = encoder.crf ?? preset.quality;
18921
+ const effectiveBitrate = encoder.crf != null ? void 0 : encoder.bitrate;
18922
+ const videoOnlyPath = encoder.chunksCarryAudio ? `${outputChunkPath}.video.mp4` : outputChunkPath;
18923
+ if (isPngSequence) {
18924
+ if (!existsSync27(outputChunkPath)) mkdirSync17(outputChunkPath, { recursive: true });
18925
+ } else {
18926
+ const outDir = join40(outputChunkPath, "..");
18927
+ if (!existsSync27(outDir)) mkdirSync17(outDir, { recursive: true });
18928
+ }
18929
+ const encodeInput = {
18930
+ job,
18931
+ log,
18932
+ outputPath: videoOnlyPath,
18933
+ framesDir,
18934
+ videoOnlyPath,
18935
+ width: Math.round(plan2.dimensions.width * encoder.deviceScaleFactor),
18936
+ height: Math.round(plan2.dimensions.height * encoder.deviceScaleFactor),
18937
+ needsAlpha: plan2.dimensions.format !== "mp4",
18938
+ captureImageFormat: captureOptions.format ?? "jpeg",
18939
+ // The encoder writes video only; audio joins at assemble time or in the
18940
+ // chunk that carries audio. Suppressing `hasAudio` skips the png-sequence audio
18941
+ // sidecar AND the mp4 audio mux.
18942
+ hasAudio: false,
18943
+ isPngSequence,
18944
+ // `DistributedFormat` has no "gif" member — distributed chunks are
18945
+ // always video segments (gif renders in-process only).
18946
+ isGif: false,
18947
+ preset,
18948
+ effectiveQuality,
18949
+ effectiveBitrate,
18950
+ engineConfig: {
18951
+ ffmpegEncodeTimeout: cfg.ffmpegEncodeTimeout,
18952
+ vp9CpuUsed: resolveLockedVp9CpuUsed(encoder) ?? cfg.vp9CpuUsed
18953
+ },
18954
+ // Distributed chunks emit a single ffmpeg call per chunk; the
18955
+ // in-process per-chunk-within-chunk path would re-split our
18956
+ // already-chunked work.
18957
+ enableChunkedEncode: false,
18958
+ chunkedEncodeSize: framesInChunk,
18959
+ abortSignal: void 0,
18960
+ assertNotAborted: () => {
18961
+ },
18962
+ // GOP === framesInChunk + force-keyframe at frame 0 → the chunk's
18963
+ // first frame is an IDR keyframe and concat-copy at assemble time
18964
+ // round-trips losslessly.
18965
+ lockGopForChunkConcat: !isPngSequence,
18966
+ gopSize: framesInChunk
18967
+ };
18811
18968
  const captureStarted = Date.now();
18812
18969
  captureMode = await runCaptureWithScreenshotFallback({
18813
18970
  forceScreenshot: forceScreenshotForChunk,
@@ -18840,6 +18997,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
18840
18997
  }
18841
18998
  const captureSession = session;
18842
18999
  session = null;
19000
+ overlap.current = canEncodeWhileCapturing(encodeInput, chunkWorkerCount, cpus3().length) ? encodeWhileCapturing(encodeInput, framesInChunk) : null;
18843
19001
  await runCaptureStage({
18844
19002
  fileServer,
18845
19003
  workDir,
@@ -18859,7 +19017,12 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
18859
19017
  abortSignal: void 0,
18860
19018
  assertNotAborted: () => {
18861
19019
  },
18862
- frameRange: range
19020
+ frameRange: range,
19021
+ onFrameLanded: (frame) => overlap.current?.onFrameLanded(frame)
19022
+ }).catch(async (captureError) => {
19023
+ await overlap.current?.abort();
19024
+ overlap.current = null;
19025
+ throw captureError;
18863
19026
  });
18864
19027
  return observedChunkCaptureMode(capturePerfs);
18865
19028
  },
@@ -18891,60 +19054,10 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
18891
19054
  log
18892
19055
  );
18893
19056
  framesEncoded = framesInChunk;
18894
- const isPngSequence = plan2.dimensions.format === "png-sequence";
18895
- outputKind = isPngSequence ? "frame-dir" : "file";
18896
- const presetFormat = isPngSequence ? "mp4" : plan2.dimensions.format;
18897
- const basePreset = getEncoderPreset(job.config.quality, presetFormat, void 0);
18898
- const preset = resolvePresetForLockedEncoder(basePreset, encoder.encoder);
18899
- const effectiveQuality = encoder.crf ?? preset.quality;
18900
- const effectiveBitrate = encoder.crf != null ? void 0 : encoder.bitrate;
18901
- const videoOnlyPath = encoder.chunksCarryAudio ? `${outputChunkPath}.video.mp4` : outputChunkPath;
18902
- if (isPngSequence) {
18903
- if (!existsSync27(outputChunkPath)) mkdirSync17(outputChunkPath, { recursive: true });
18904
- } else {
18905
- const outDir = join40(outputChunkPath, "..");
18906
- if (!existsSync27(outDir)) mkdirSync17(outDir, { recursive: true });
18907
- }
18908
19057
  const encodeStarted = Date.now();
18909
- await runEncodeStage({
18910
- job,
18911
- log,
18912
- outputPath: videoOnlyPath,
18913
- framesDir,
18914
- videoOnlyPath,
18915
- width: Math.round(plan2.dimensions.width * encoder.deviceScaleFactor),
18916
- height: Math.round(plan2.dimensions.height * encoder.deviceScaleFactor),
18917
- needsAlpha: plan2.dimensions.format !== "mp4",
18918
- captureImageFormat: captureOptions.format ?? "jpeg",
18919
- // The encoder writes video only; audio joins at assemble time or in the
18920
- // chunk that carries audio. Suppressing `hasAudio` skips the png-sequence audio
18921
- // sidecar AND the mp4 audio mux.
18922
- hasAudio: false,
18923
- isPngSequence,
18924
- // `DistributedFormat` has no "gif" member — distributed chunks are
18925
- // always video segments (gif renders in-process only).
18926
- isGif: false,
18927
- preset,
18928
- effectiveQuality,
18929
- effectiveBitrate,
18930
- engineConfig: {
18931
- ffmpegEncodeTimeout: cfg.ffmpegEncodeTimeout,
18932
- vp9CpuUsed: resolveLockedVp9CpuUsed(encoder) ?? cfg.vp9CpuUsed
18933
- },
18934
- // Distributed chunks emit a single ffmpeg call per chunk; the
18935
- // in-process per-chunk-within-chunk path would re-split our
18936
- // already-chunked work.
18937
- enableChunkedEncode: false,
18938
- chunkedEncodeSize: framesInChunk,
18939
- abortSignal: void 0,
18940
- assertNotAborted: () => {
18941
- },
18942
- // GOP === framesInChunk + force-keyframe at frame 0 → the chunk's
18943
- // first frame is an IDR keyframe and concat-copy at assemble time
18944
- // round-trips losslessly.
18945
- lockGopForChunkConcat: !isPngSequence,
18946
- gopSize: framesInChunk
18947
- });
19058
+ const overlapped = overlap.current;
19059
+ overlap.current = null;
19060
+ await (overlapped ? overlapped.finish() : runEncodeStage(encodeInput));
18948
19061
  if (encoder.chunksCarryAudio) {
18949
19062
  const audioScratchPath = `${outputChunkPath}.audio.m4a`;
18950
19063
  const audioChunks = options?.frameRange ? chunksWithSubRange(chunks, chunkIndex, range) : { chunks, index: chunkIndex };
@@ -18962,6 +19075,8 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
18962
19075
  }
18963
19076
  encodeStageMs = Date.now() - encodeStarted;
18964
19077
  } finally {
19078
+ await overlap.current?.abort().catch(() => {
19079
+ });
18965
19080
  await extraction?.catch(() => {
18966
19081
  });
18967
19082
  if (session) {
@@ -19074,6 +19189,10 @@ var READ_CHUNK_BYTES = 256 * 1024;
19074
19189
  import { extname as extname7 } from "path";
19075
19190
  var AUDIO_SIDECAR_MIN_BYTES = 32 * 1024 * 1024;
19076
19191
 
19192
+ // ../producer/src/services/distributed/probeFacts.ts
19193
+ import { writeFileSync as writeFileSync14 } from "fs";
19194
+ import { extname as extname8 } from "path";
19195
+
19077
19196
  export {
19078
19197
  createConsoleLogger,
19079
19198
  defaultLogger,