hyperframes 0.8.107 → 0.8.109

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 (169) hide show
  1. package/dist/{add-DXZ3HNQI.js → add-7NAK3TQ6.js} +15 -15
  2. package/dist/{animatedGifPrep-2IQB4DGE.js → animatedGifPrep-NMGFOSIL.js} +6 -6
  3. package/dist/{asset-KZIG3MZN.js → asset-X7GUEAHC.js} +2 -2
  4. package/dist/{assetCataloger-4GE6PIUA.js → assetCataloger-FXEHOUA3.js} +2 -2
  5. package/dist/{auth-5IYF3H67.js → auth-EBWBTIGU.js} +2 -2
  6. package/dist/{autoUpdate-5C3FXM6Y.js → autoUpdate-DJBTMGAR.js} +2 -2
  7. package/dist/{backgroundChecks-75WGW4NB.js → backgroundChecks-QUMUVLYU.js} +3 -3
  8. package/dist/backgroundChecksWorker.js +3 -3
  9. package/dist/{batchRender-FKKS3YBX.js → batchRender-ZTHFMHZY.js} +3 -3
  10. package/dist/{beats-ISR36NSK.js → beats-XUYNHYGB.js} +18 -12
  11. package/dist/{benchmark-U6TQ6DS2.js → benchmark-L45UHXEE.js} +12 -12
  12. package/dist/{browser-VDS6QAVA.js → browser-EM76PRMN.js} +9 -9
  13. package/dist/{bundleWithLocalizedFonts-PMWWW5YG.js → bundleWithLocalizedFonts-4Q3ETTHW.js} +2 -2
  14. package/dist/{capture-BKA3UCZS.js → capture-4OPH4PMK.js} +7 -7
  15. package/dist/{capture-5KE34KCD.js → capture-OG6532RA.js} +2 -2
  16. package/dist/{catalog-ZXOSU27O.js → catalog-V42BPLQ4.js} +15 -15
  17. package/dist/{check-MZMZCXPN.js → check-3XQLHR2I.js} +30 -29
  18. package/dist/{checkBrowser-UIZJXYCQ.js → checkBrowser-56BV4TW3.js} +29 -28
  19. package/dist/{chunk-TI5Z4SWP.js → chunk-2IN77NP6.js} +3 -3
  20. package/dist/{chunk-WBVPQC7J.js → chunk-2JXJ5SGK.js} +1 -1
  21. package/dist/{chunk-QH6UFY2O.js → chunk-32P7FTFC.js} +4 -4
  22. package/dist/{chunk-7BI5BYMQ.js → chunk-3CPYTXZH.js} +5 -5
  23. package/dist/{chunk-OGTR33KN.js → chunk-3T4RQK6C.js} +2 -2
  24. package/dist/{chunk-HGL4WJDI.js → chunk-44UOKZIC.js} +9 -9
  25. package/dist/{chunk-KTPQ67TX.js → chunk-4PY6UPHN.js} +1 -1
  26. package/dist/{chunk-SGBOZ2HF.js → chunk-5ER3PYYP.js} +1 -1
  27. package/dist/{normalize-audio-J2PG6YXH.js → chunk-5ON5GH3X.js} +101 -209
  28. package/dist/{chunk-NHTRUAMD.js → chunk-AWUZ3AUD.js} +1 -1
  29. package/dist/{chunk-VMDEGITC.js → chunk-B25H4M4V.js} +1 -1
  30. package/dist/{chunk-QM2NUN2B.js → chunk-BAVM2XBR.js} +87 -3
  31. package/dist/{chunk-4W4UDPCH.js → chunk-C4TZBEHY.js} +2 -2
  32. package/dist/{chunk-FLUBCMNT.js → chunk-CFC3EEDQ.js} +1 -1
  33. package/dist/{chunk-3CYAZWF2.js → chunk-CPO5INJ5.js} +3 -3
  34. package/dist/{chunk-C3KEF3UA.js → chunk-CQRF7VU3.js} +1 -1
  35. package/dist/{chunk-6QWHIARJ.js → chunk-E4AYSXMS.js} +1 -1
  36. package/dist/{chunk-BRTGJ73L.js → chunk-HQGJLZB6.js} +2 -2
  37. package/dist/{chunk-HJCTKNXF.js → chunk-I5WWEBDG.js} +1 -1
  38. package/dist/{chunk-GLTHJJIF.js → chunk-ITBEN3QD.js} +2 -2
  39. package/dist/{chunk-3LBFLW7E.js → chunk-JTTAS7N6.js} +1 -1
  40. package/dist/{chunk-T4Z4UNSX.js → chunk-JWPSBANO.js} +3 -3
  41. package/dist/{chunk-P7XRDAWU.js → chunk-LAQEZHOH.js} +1 -1
  42. package/dist/{chunk-73VPF3SS.js → chunk-M224DAMG.js} +1 -1
  43. package/dist/{chunk-PU2IOWX4.js → chunk-MGFVNTIB.js} +2 -2
  44. package/dist/{chunk-NX6G66F4.js → chunk-MP25ID6H.js} +2 -2
  45. package/dist/{chunk-4DJXCVQJ.js → chunk-MR2LOG32.js} +577 -66
  46. package/dist/{chunk-3WG63KFW.js → chunk-N32ITJ7L.js} +25 -11
  47. package/dist/{chunk-D5EJCP26.js → chunk-OF3RKOPU.js} +79 -64
  48. package/dist/{chunk-J6CSEDK7.js → chunk-OORTZAJ7.js} +4 -4
  49. package/dist/{chunk-L7XDI3DT.js → chunk-RB7AHUOR.js} +1 -1
  50. package/dist/{chunk-YVEZT6WT.js → chunk-TBYUUOJG.js} +1 -1
  51. package/dist/{chunk-T3BOWXPX.js → chunk-TIZQC44K.js} +1 -1
  52. package/dist/{chunk-R4IB642V.js → chunk-U4TDAJZE.js} +2 -2
  53. package/dist/{chunk-TPPIERG3.js → chunk-WMN4NBJQ.js} +3 -3
  54. package/dist/{chunk-AO4PBESP.js → chunk-X2QJUTZL.js} +7 -7
  55. package/dist/{chunk-SKPL32FI.js → chunk-XKNYXDJN.js} +1 -1
  56. package/dist/{chunk-JNY3VJWB.js → chunk-XYVFPDNU.js} +1 -1
  57. package/dist/{chunk-Z4UAXNWV.js → chunk-YIXEH4TF.js} +2 -2
  58. package/dist/chunk-YLVI7VL5.js +3541 -0
  59. package/dist/{chunk-ADP6TMYP.js → chunk-YRTKK45P.js} +8 -8
  60. package/dist/{chunk-I73Z6OOE.js → chunk-ZKBSOLPY.js} +1 -1
  61. package/dist/{chunk-YY5J75H5.js → chunk-ZU54D6MI.js} +135 -5
  62. package/dist/{clean-46MZVA37.js → clean-45QMNXLN.js} +15 -14
  63. package/dist/cli.js +50 -50
  64. package/dist/{cloud-GAZQGPCW.js → cloud-YRYTNHMQ.js} +4 -4
  65. package/dist/{cloudrun-ZWL7SLDD.js → cloudrun-LVZ4OPPC.js} +3 -3
  66. package/dist/{compare-66AAJESM.js → compare-GODD3JD2.js} +24 -23
  67. package/dist/{compiler-3N7XQRH6.js → compiler-C32VXLE2.js} +3 -3
  68. package/dist/{component-3OCOHK2B.js → component-UMNTDLG3.js} +3 -3
  69. package/dist/{compositions-H2BP4N6F.js → compositions-EIRLTOYW.js} +11 -11
  70. package/dist/{delete-WNJLVPGO.js → delete-7P55EZOW.js} +2 -2
  71. package/dist/{dist-AY2RBEXL.js → dist-FMHQMEMX.js} +1 -1
  72. package/dist/{dist-HSEMXPG3.js → dist-YUIPB7OX.js} +8 -2
  73. package/dist/{doctor-IFOZS45N.js → doctor-UYSIF4PX.js} +10 -10
  74. package/dist/{events-6TH3FU3F.js → events-A2MYVK2Z.js} +9 -9
  75. package/dist/{events-5EKFJCCZ.js → events-LXYVOH3X.js} +8 -8
  76. package/dist/{feedback-QRQNMORT.js → feedback-46DWSEAZ.js} +10 -10
  77. package/dist/{figma-WONEPLUQ.js → figma-C5DHRI6P.js} +3 -3
  78. package/dist/fontLocalizeCli.js +8 -8
  79. package/dist/{get-C7FCYNI2.js → get-IRSUDICU.js} +2 -2
  80. package/dist/{gpuPolicy-BXXQF3VC.js → gpuPolicy-CA6KRFQQ.js} +6 -6
  81. package/dist/{grade-compare-SCCSZ5P3.js → grade-compare-BLHXB3UR.js} +24 -23
  82. package/dist/{help-FJOUNXVD.js → help-HMYF4Y3E.js} +1 -1
  83. package/dist/{history-OZY36NEC.js → history-K5BD64QP.js} +15 -14
  84. package/dist/hyperframe-runtime.js +54 -54
  85. package/dist/hyperframe.manifest.json +1 -1
  86. package/dist/hyperframe.runtime.iife.js +54 -54
  87. package/dist/hyperframes-player.global.js +3 -3
  88. package/dist/{info-RG4SZQ2C.js → info-QR2FYDJ7.js} +11 -11
  89. package/dist/{init-YGZCDKSS.js → init-L6BY7IV4.js} +22 -22
  90. package/dist/{inspect-X547EZ4I.js → inspect-D3MC4VH3.js} +25 -24
  91. package/dist/{installer-6J24NTIV.js → installer-NBEN3ZIP.js} +4 -4
  92. package/dist/{keyframes-EIWEION2.js → keyframes-TPMFVG6O.js} +12 -12
  93. package/dist/{lambda-DSZDMP7P.js → lambda-BFHJMMCG.js} +4 -4
  94. package/dist/{layout-B7KCBLBH.js → layout-AOYCSBIL.js} +25 -24
  95. package/dist/{lint-RBDG4ERJ.js → lint-L4MN7QPF.js} +12 -12
  96. package/dist/{lintProject-CMPAR7BS.js → lintProject-X7L7EZWZ.js} +1 -1
  97. package/dist/{list-BPHPHLDJ.js → list-LKXCKBRX.js} +2 -2
  98. package/dist/{login-HV7KBW2J.js → login-4WEDFAIY.js} +4 -4
  99. package/dist/{media-treatment-RS6BZSQC.js → media-treatment-H6YAFA3H.js} +12 -12
  100. package/dist/{models-K4U7GRXV.js → models-RILNQV3Z.js} +7 -7
  101. package/dist/{motionShot-5AI2DALY.js → motionShot-5F4DCMWC.js} +10 -10
  102. package/dist/normalize-audio-DFK5FTE5.js +276 -0
  103. package/dist/{pipeline-HIUESSET.js → pipeline-5UY45ELH.js} +6 -6
  104. package/dist/{play-TTLQXBAH.js → play-HFD4CPWF.js} +20 -19
  105. package/dist/{preflight-63ODQ625.js → preflight-3V5IHGWQ.js} +7 -7
  106. package/dist/{present-XKGZ26FQ.js → present-JGDYENOT.js} +16 -15
  107. package/dist/{preview-O2YEKMYJ.js → preview-GLI7KBCA.js} +17 -17
  108. package/dist/{producer-ZTQIJO7E.js → producer-7JGAPEAF.js} +1 -1
  109. package/dist/{projectConfig-VVFH7XW5.js → projectConfig-JHEWPAA5.js} +6 -6
  110. package/dist/{publication-NALPAHDC.js → publication-7XA2H7UB.js} +2 -2
  111. package/dist/{publish-G7OL45EL.js → publish-257VZMWI.js} +8 -8
  112. package/dist/{remove-background-LOLKFKLT.js → remove-background-EV6XVADN.js} +7 -7
  113. package/dist/{render-I65IU7RM.js → render-4C3YKVZI.js} +3 -3
  114. package/dist/{render-ULQLUIWD.js → render-K6YASB4G.js} +12 -12
  115. package/dist/{render-EXSRG666.js → render-Y6LOQDCD.js} +21 -21
  116. package/dist/{render-batch-RSNCREPB.js → render-batch-AFDBPDMM.js} +3 -3
  117. package/dist/{renderCancellation-2WB4SGXR.js → renderCancellation-VIZELWDD.js} +6 -6
  118. package/dist/renderSetupWorker.js +1 -1
  119. package/dist/{resolver-CVN2IM2R.js → resolver-S72XBTEQ.js} +4 -4
  120. package/dist/{sherpa-OJUI54HG.js → sherpa-BAADWCHB.js} +7 -7
  121. package/dist/{skills-CPS5E5YP.js → skills-TEVVW6TR.js} +11 -11
  122. package/dist/{skillsUpdateCheck-3IT5CJWU.js → skillsUpdateCheck-4Y3NJPB5.js} +3 -3
  123. package/dist/{snapshot-MSGC7HTA.js → snapshot-63X5B7GR.js} +25 -24
  124. package/dist/{src-SV7CNZXN.js → src-GD7RGV5U.js} +5 -5
  125. package/dist/{src-EZBJT4VH.js → src-GRC63B6L.js} +7 -7
  126. package/dist/{staticProjectServer-5JAGMYT7.js → staticProjectServer-SFINO3DE.js} +14 -13
  127. package/dist/{status-4W3EJYEH.js → status-LXQL3L3H.js} +6 -6
  128. package/dist/studio/assets/{hyperframes-player-h4mktSdm.js → hyperframes-player-CJYtikAy.js} +1 -1
  129. package/dist/studio/assets/{index-lrT-gfHa.css → index-BQZBDqMs.css} +1 -1
  130. package/dist/studio/assets/{index-zoqPmBF6.js → index-CMSgrT2F.js} +1 -1
  131. package/dist/studio/assets/{index-CM1xA_jD.js → index-CYP6dMd7.js} +212 -210
  132. package/dist/studio/assets/{index-D8GKc2D9.js → index-HTFVHUJ4.js} +1 -1
  133. package/dist/studio/{chunk-NCUBAVGK.js → chunk-5VSXRGIX.js} +4 -4
  134. package/dist/studio/chunk-5VSXRGIX.js.map +1 -0
  135. package/dist/studio/chunk-6LUNLTHR.js +611 -0
  136. package/dist/studio/chunk-6LUNLTHR.js.map +1 -0
  137. package/dist/studio/{chunk-46XP7J3B.js → chunk-PQFEBX32.js} +5 -3
  138. package/dist/studio/chunk-PQFEBX32.js.map +1 -0
  139. package/dist/studio/{chunk-4OZC426H.js → chunk-QUXZVHOJ.js} +10 -504
  140. package/dist/studio/chunk-QUXZVHOJ.js.map +1 -0
  141. package/dist/studio/{domEditingLayers-AQBDFNUC.js → domEditingLayers-JQPQGGT7.js} +3 -3
  142. package/dist/studio/index.d.ts +53 -4
  143. package/dist/studio/index.html +2 -2
  144. package/dist/studio/index.js +15746 -13173
  145. package/dist/studio/index.js.map +1 -1
  146. package/dist/studio/player.js +3 -3
  147. package/dist/studio/styles.css +1 -1
  148. package/dist/studio/ui.js +1 -1
  149. package/dist/{studio-api-L2TJ2JTK.js → studio-api-WRSCXPWS.js} +8 -7
  150. package/dist/{studioServer-UV7YRYWN.js → studioServer-HPG3FVTJ.js} +44 -38
  151. package/dist/{system-OL4MXRPK.js → system-EEWYEHYX.js} +6 -6
  152. package/dist/{telemetry-WRUU725E.js → telemetry-UIZ5O7NL.js} +9 -9
  153. package/dist/{timeline-P43A53BW.js → timeline-L56HML42.js} +41 -21
  154. package/dist/{tokens-NUHTJM2V.js → tokens-DLPYLGLW.js} +2 -2
  155. package/dist/{transcribe-FSVNIFGF.js → transcribe-FBPTYMVZ.js} +12 -12
  156. package/dist/{transcribe-TRLZJK73.js → transcribe-GCKJF5A6.js} +7 -7
  157. package/dist/{updateCheck-33TAG46I.js → updateCheck-GQBMZGU3.js} +2 -2
  158. package/dist/{upgrade-AJ6QBTH2.js → upgrade-6LR7ZK3Q.js} +2 -2
  159. package/dist/{urlDownloader-MP6GJIDV.js → urlDownloader-NYXPEBUN.js} +5 -5
  160. package/dist/{validate-VL3A6VTJ.js → validate-VY6RJRHE.js} +15 -15
  161. package/dist/{video-KO7VT63P.js → video-KWQ5TSVD.js} +6 -6
  162. package/package.json +1 -1
  163. package/dist/chunk-PMLZPNIB.js +0 -3431
  164. package/dist/studio/chunk-46XP7J3B.js.map +0 -1
  165. package/dist/studio/chunk-4OZC426H.js.map +0 -1
  166. package/dist/studio/chunk-DI4LCJHL.js +0 -102
  167. package/dist/studio/chunk-DI4LCJHL.js.map +0 -1
  168. package/dist/studio/chunk-NCUBAVGK.js.map +0 -1
  169. /package/dist/studio/{domEditingLayers-AQBDFNUC.js.map → domEditingLayers-JQPQGGT7.js.map} +0 -0
@@ -5,23 +5,33 @@ var require = __hf_createRequire(import.meta.url);
5
5
  var __filename = __hf_fileURLToPath(import.meta.url);
6
6
  var __dirname = __hf_dirname(__filename);
7
7
  import {
8
+ MEDIA_LINK_ATTR,
8
9
  dedupeClonedCompositionId,
9
10
  findTargetElement,
10
11
  isHTMLElement,
11
12
  parseSourceDocument,
12
13
  patchElementInHtml,
13
14
  probeElementInSource,
15
+ relinkSplitHalves,
16
+ relinkSplitHalvesInHtml,
14
17
  removeElementFromHtml,
15
18
  splitElementInHtml,
16
19
  unwrapElementsFromHtml,
17
20
  wrapElementsInHtml
18
- } from "./chunk-YY5J75H5.js";
21
+ } from "./chunk-ZU54D6MI.js";
19
22
  import {
20
23
  atomicTempPath,
21
24
  createFileAtomically,
22
25
  isAtomicTempPath,
23
26
  replaceFileAtomically
24
27
  } from "./chunk-WEATBQTR.js";
28
+ import {
29
+ DEFAULT_TARGET_LUFS,
30
+ absoluteLoudnessPlan,
31
+ measureAudio,
32
+ requiredFfmpeg,
33
+ resolveLocalAudioPath
34
+ } from "./chunk-5ON5GH3X.js";
25
35
  import {
26
36
  PROXY_PARAMS_VERSION,
27
37
  PROXY_PENDING_RETRY_AFTER_SECONDS,
@@ -47,7 +57,7 @@ import {
47
57
  shouldPrewarmProxy,
48
58
  waitForProxy,
49
59
  walkDir
50
- } from "./chunk-PU2IOWX4.js";
60
+ } from "./chunk-MGFVNTIB.js";
51
61
  import {
52
62
  findTimelineScript,
53
63
  parseGsapScriptAcorn
@@ -66,7 +76,7 @@ import {
66
76
  formatRenderOutputTimestamp,
67
77
  parseFps,
68
78
  sameInstant
69
- } from "./chunk-VMDEGITC.js";
79
+ } from "./chunk-B25H4M4V.js";
70
80
  import {
71
81
  MagicString,
72
82
  VALID_CANVAS_RESOLUTIONS,
@@ -75,15 +85,17 @@ import {
75
85
  import {
76
86
  addScenePartsManifest,
77
87
  ensureHfIds
78
- } from "./chunk-R4IB642V.js";
88
+ } from "./chunk-U4TDAJZE.js";
79
89
  import {
80
90
  findStartTags,
81
91
  injectScriptsIntoHtml,
82
92
  injectTagsAtHeadStart,
83
93
  insertBeforeCloseTag,
84
94
  isFullHtmlDocument,
95
+ resolveRateSpec,
96
+ sourceTimeAt,
85
97
  stripEmbeddedRuntimeScripts
86
- } from "./chunk-PMLZPNIB.js";
98
+ } from "./chunk-YLVI7VL5.js";
87
99
  import {
88
100
  ProjectRootMissingError,
89
101
  folderGone,
@@ -96,12 +108,13 @@ import {
96
108
  import {
97
109
  isWithinProjectRoot,
98
110
  readProjectFile
99
- } from "./chunk-QM2NUN2B.js";
111
+ } from "./chunk-BAVM2XBR.js";
100
112
  import {
101
113
  FFPROBE_PATH_ENV,
102
114
  findFfBinary
103
115
  } from "./chunk-PJ3HDUCY.js";
104
116
  import {
117
+ readMediaOffsetSeconds,
105
118
  rewriteAssetPaths,
106
119
  rewriteCssAssetUrls,
107
120
  rewriteInlineStyleAssetUrls
@@ -200,7 +213,7 @@ function stampFileHfIds(filePath) {
200
213
  }
201
214
  }
202
215
 
203
- // ../studio-server/dist/chunk-KUEG5MYA.js
216
+ // ../studio-server/dist/chunk-EPL4YKGI.js
204
217
  import { randomUUID as randomUUID6 } from "crypto";
205
218
  import { mkdir as mkdir2, readdir as readdir3, rm as rm3, rmdir, writeFile } from "fs/promises";
206
219
  import {
@@ -261,15 +274,21 @@ function onFileOverwritten(listener) {
261
274
  overwriteListeners.add(listener);
262
275
  return () => overwriteListeners.delete(listener);
263
276
  }
264
- function recordFileWriteReceipt(filePath, { overwrote, ...receipt }) {
277
+ function recordFileWriteReceipt(filePath, {
278
+ overwrote,
279
+ path,
280
+ version,
281
+ writeToken
282
+ }) {
283
+ const receipt = { path, version, writeToken };
265
284
  const absPath = realFilePath(filePath);
266
285
  if (overwrote !== void 0)
267
286
  for (const listener of overwriteListeners) listener(absPath, receipt.version, overwrote);
268
287
  const now = Date.now();
269
- for (const [path, list] of receipts) {
288
+ for (const [path2, list] of receipts) {
270
289
  const live = list.filter((entry) => now - entry.recordedAt < RECEIPT_TTL_MS);
271
- if (live.length > 0) receipts.set(path, live);
272
- else receipts.delete(path);
290
+ if (live.length > 0) receipts.set(path2, live);
291
+ else receipts.delete(path2);
273
292
  }
274
293
  const from = overwrote === void 0 ? void 0 : fileContentVersion(overwrote);
275
294
  receipts.set(absPath, [
@@ -5106,16 +5125,25 @@ import { createHash as createHash32, randomUUID as randomUUID22 } from "crypto";
5106
5125
  import { readFileSync as readFileSync8 } from "fs";
5107
5126
  import { existsSync as existsSync7, readFileSync as readFileSync10, statSync as statSync7 } from "fs";
5108
5127
  import { join as join12, resolve as resolve52 } from "path";
5128
+ import { execFile } from "child_process";
5129
+ import { existsSync as existsSync8, readFileSync as readFileSync11, statSync as statSync8 } from "fs";
5130
+ import { dirname as dirname52, join as join13, relative as relative4, sep as sep22 } from "path";
5131
+ import { createHash as createHash42, randomBytes } from "crypto";
5132
+ import { existsSync as existsSync9 } from "fs";
5133
+ import { existsSync as existsSync10, readFileSync as readFileSync12, statSync as statSync9 } from "fs";
5134
+ import { join as join14, resolve as resolve6 } from "path";
5135
+ import { execFile as execFile2, spawn as spawn2 } from "child_process";
5136
+ import { promisify } from "util";
5109
5137
  import { closeSync as closeSync32, constants as constants3, fstatSync as fstatSync3, openSync as openSync32, readSync } from "fs";
5110
5138
  import { streamSSE as streamSSE2 } from "hono/streaming";
5111
- import { existsSync as existsSync8 } from "fs";
5112
- import { basename as basename22, dirname as dirname52, extname as extname2, join as join13, posix } from "path";
5113
- import { existsSync as existsSync9, readFileSync as readFileSync11 } from "fs";
5139
+ import { existsSync as existsSync11 } from "fs";
5140
+ import { basename as basename22, dirname as dirname6, extname as extname2, join as join15, posix } from "path";
5141
+ import { existsSync as existsSync12, readFileSync as readFileSync13 } from "fs";
5114
5142
  import { homedir as homedir2 } from "os";
5115
- import { join as join14 } from "path";
5116
- import { createHash as createHash42 } from "crypto";
5117
- import { readFileSync as readFileSync12, readdirSync as readdirSync5, rmSync as rmSync5 } from "fs";
5118
- import { join as join15 } from "path";
5143
+ import { join as join16 } from "path";
5144
+ import { createHash as createHash5 } from "crypto";
5145
+ import { readFileSync as readFileSync14, readdirSync as readdirSync5, rmSync as rmSync5 } from "fs";
5146
+ import { join as join17 } from "path";
5119
5147
  async function filterCompositionFiles(projectDir, files) {
5120
5148
  const htmlFiles = files.filter((f) => f.endsWith(".html") && !isInHiddenOrVendorDir(f));
5121
5149
  const checks = await Promise.all(
@@ -6152,7 +6180,7 @@ function bakeGroupTransformIntoMembers(html, groupId, members, groupCenter) {
6152
6180
  const rad = grot * Math.PI / 180;
6153
6181
  const cos = Math.cos(rad);
6154
6182
  const sin = Math.sin(rad);
6155
- const round3 = (n) => Math.round(n * 1e3) / 1e3;
6183
+ const round32 = (n) => Math.round(n * 1e3) / 1e3;
6156
6184
  let script = block.scriptText;
6157
6185
  for (const m of members) {
6158
6186
  const memberSel = `#${m.id}`;
@@ -6169,27 +6197,27 @@ function bakeGroupTransformIntoMembers(html, groupId, members, groupCenter) {
6169
6197
  const visY = groupCenter.cy + gscale * (sin * dx + cos * dy) + gy;
6170
6198
  const newProps = {
6171
6199
  ...mProps,
6172
- x: round3(visX - m.cx),
6173
- y: round3(visY - m.cy)
6200
+ x: round32(visX - m.cx),
6201
+ y: round32(visY - m.cy)
6174
6202
  };
6175
6203
  if (gz !== 0) newProps.z = (typeof mProps.z === "number" ? mProps.z : 0) + gz;
6176
6204
  if (grot !== 0) {
6177
- newProps.rotation = round3(
6205
+ newProps.rotation = round32(
6178
6206
  (typeof mProps.rotation === "number" ? mProps.rotation : 0) + grot
6179
6207
  );
6180
6208
  }
6181
6209
  if (gscale !== 1) {
6182
- newProps.scale = round3((typeof mProps.scale === "number" ? mProps.scale : 1) * gscale);
6210
+ newProps.scale = round32((typeof mProps.scale === "number" ? mProps.scale : 1) * gscale);
6183
6211
  }
6184
6212
  const pivoted = /* @__PURE__ */ new Set(["x", "y", "z", "rotation", "scale"]);
6185
6213
  for (const [k, v] of Object.entries(gt)) {
6186
6214
  if (pivoted.has(k) || typeof v !== "number") continue;
6187
6215
  if (k === "scaleX" || k === "scaleY") {
6188
- if (v !== 1) newProps[k] = round3((typeof mProps[k] === "number" ? mProps[k] : 1) * v);
6216
+ if (v !== 1) newProps[k] = round32((typeof mProps[k] === "number" ? mProps[k] : 1) * v);
6189
6217
  } else if (k === "transformPerspective") {
6190
6218
  if (typeof mProps[k] !== "number") newProps[k] = v;
6191
6219
  } else if (v !== 0) {
6192
- newProps[k] = round3((typeof mProps[k] === "number" ? mProps[k] : 0) + v);
6220
+ newProps[k] = round32((typeof mProps[k] === "number" ? mProps[k] : 0) + v);
6193
6221
  }
6194
6222
  }
6195
6223
  for (const s of [...sets].reverse()) {
@@ -6360,7 +6388,7 @@ async function prepareGsapMutationScript(c, res, firstMutation) {
6360
6388
  let block = extractGsapScriptBlock(html);
6361
6389
  if (!block && (firstMutation.type === "add" || firstMutation.type === "add-with-keyframes")) {
6362
6390
  const compId = html.match(/data-composition-id="([^"]+)"/)?.[1] ?? "main";
6363
- const { GSAP_CDN } = await import("./dist-HSEMXPG3.js");
6391
+ const { GSAP_CDN } = await import("./dist-YUIPB7OX.js");
6364
6392
  const bootstrap = [
6365
6393
  `<script src="${GSAP_CDN}"></script>`,
6366
6394
  "<script>",
@@ -7098,6 +7126,7 @@ async function foldAtomicCutFile(c, file, absPath, before, writer) {
7098
7126
  let after = before;
7099
7127
  let splitCount = 0;
7100
7128
  const skippedSelectors = /* @__PURE__ */ new Set();
7129
+ const rightHalfIds = [];
7101
7130
  const respond = (data, status) => status ? c.json(data, status) : c.json(data);
7102
7131
  const orderedTargets = file.targets.map((cut, index) => ({ cut, index })).sort((left, right) => {
7103
7132
  const locatorKey = (entry) => !entry.target.id && !entry.target.hfId && entry.target.selector ? entry.target.selector : null;
@@ -7128,6 +7157,7 @@ async function foldAtomicCutFile(c, file, absPath, before, writer) {
7128
7157
  }
7129
7158
  after = split.html;
7130
7159
  splitCount++;
7160
+ rightHalfIds.push(split.newId);
7131
7161
  if (!cut.originalId) continue;
7132
7162
  const block = extractGsapScriptBlock(after);
7133
7163
  if (!block) continue;
@@ -7154,6 +7184,7 @@ async function foldAtomicCutFile(c, file, absPath, before, writer) {
7154
7184
  after = block.replaceScript(script);
7155
7185
  }
7156
7186
  }
7187
+ after = relinkSplitHalvesInHtml(after, rightHalfIds);
7157
7188
  return {
7158
7189
  path: file.path,
7159
7190
  absPath,
@@ -8944,7 +8975,8 @@ function registerRenderRoutes(api, adapter) {
8944
8975
  progress: current.progress,
8945
8976
  status: current.status,
8946
8977
  stage: current.stage,
8947
- error: current.error
8978
+ error: current.error,
8979
+ ...current.status === "complete" && current.audioLoweredDb !== void 0 ? { audioLoweredDb: current.audioLoweredDb } : {}
8948
8980
  })
8949
8981
  });
8950
8982
  if (current.status !== "rendering") break;
@@ -9053,11 +9085,13 @@ function registerRenderRoutes(api, adapter) {
9053
9085
  const metaPath = join10(rendersDir, `${rid}.meta.json`);
9054
9086
  let status = "complete";
9055
9087
  let durationMs;
9088
+ let audioLoweredDb;
9056
9089
  if (existsSync6(metaPath)) {
9057
9090
  try {
9058
9091
  const meta = JSON.parse(readFileSync72(metaPath, "utf-8"));
9059
9092
  if (meta.status === "failed" && !existsSync6(fp)) status = "failed";
9060
9093
  if (meta.durationMs) durationMs = meta.durationMs;
9094
+ if (typeof meta.audioLoweredDb === "number") audioLoweredDb = meta.audioLoweredDb;
9061
9095
  } catch {
9062
9096
  }
9063
9097
  }
@@ -9067,7 +9101,8 @@ function registerRenderRoutes(api, adapter) {
9067
9101
  size: stat22.size,
9068
9102
  createdAt: stat22.mtimeMs,
9069
9103
  status,
9070
- durationMs
9104
+ durationMs,
9105
+ ...audioLoweredDb !== void 0 ? { audioLoweredDb } : {}
9071
9106
  };
9072
9107
  }).sort((a, b) => b.createdAt - a.createdAt);
9073
9108
  for (const file of files) {
@@ -9100,10 +9135,10 @@ var ThumbnailGenerationCoordinator = class {
9100
9135
  if (signal.aborted) return Promise.reject(new DOMException("Aborted", "AbortError"));
9101
9136
  let entry = this.entries.get(key);
9102
9137
  if (!entry) {
9103
- let resolve6;
9138
+ let resolve7;
9104
9139
  let reject;
9105
9140
  const promise = new Promise((resolvePromise, rejectPromise) => {
9106
- resolve6 = resolvePromise;
9141
+ resolve7 = resolvePromise;
9107
9142
  reject = rejectPromise;
9108
9143
  });
9109
9144
  entry = {
@@ -9113,7 +9148,7 @@ var ThumbnailGenerationCoordinator = class {
9113
9148
  state: "queued",
9114
9149
  work,
9115
9150
  promise,
9116
- resolve: resolve6,
9151
+ resolve: resolve7,
9117
9152
  reject
9118
9153
  };
9119
9154
  this.entries.set(key, entry);
@@ -9127,7 +9162,7 @@ var ThumbnailGenerationCoordinator = class {
9127
9162
  return /* @__PURE__ */ new Set([...this.entries.keys(), ...[...this.activeEntries].map((entry) => entry.key)]);
9128
9163
  }
9129
9164
  lease(entry, signal) {
9130
- return new Promise((resolve6, reject) => {
9165
+ return new Promise((resolve7, reject) => {
9131
9166
  let released = false;
9132
9167
  const release = () => {
9133
9168
  if (released) return;
@@ -9151,7 +9186,7 @@ var ThumbnailGenerationCoordinator = class {
9151
9186
  entry.promise.then(
9152
9187
  (value) => {
9153
9188
  release();
9154
- resolve6(value);
9189
+ resolve7(value);
9155
9190
  },
9156
9191
  (reason) => {
9157
9192
  release();
@@ -9226,14 +9261,14 @@ async function generateThumbnail(path, signal) {
9226
9261
  async function resolveSource(adapter, id, url) {
9227
9262
  const project = await adapter.resolveProject(id);
9228
9263
  if (!project) throw new HTTPException(404);
9229
- let relative4;
9264
+ let relative5;
9230
9265
  try {
9231
- relative4 = requestSubPath(url, "projects/:id/image-thumbnail");
9266
+ relative5 = requestSubPath(url, "projects/:id/image-thumbnail");
9232
9267
  } catch {
9233
9268
  throw new HTTPException(400);
9234
9269
  }
9235
- if (!/\.jpe?g$/i.test(relative4)) throw new HTTPException(415);
9236
- const path = pinWithinProject(project.dir, relative4);
9270
+ if (!/\.jpe?g$/i.test(relative5)) throw new HTTPException(415);
9271
+ const path = pinWithinProject(project.dir, relative5);
9237
9272
  if (!path) throw new HTTPException(404);
9238
9273
  const info = await stat2(path).catch(() => {
9239
9274
  throw new HTTPException(404);
@@ -9574,6 +9609,479 @@ function registerWaveformRoutes(api, adapter) {
9574
9609
  return c.json({ peaks });
9575
9610
  });
9576
9611
  }
9612
+ var FREEZE_HOLD_SECONDS = 2;
9613
+ var EPSILON = 1e-3;
9614
+ var round3 = (value) => Math.round(value * 1e3) / 1e3;
9615
+ function numberAttr(el, name) {
9616
+ const raw = el.getAttribute(name);
9617
+ if (raw === null || raw.trim() === "") return null;
9618
+ const value = Number(raw);
9619
+ return Number.isFinite(value) ? value : null;
9620
+ }
9621
+ function trackOf(el) {
9622
+ return numberAttr(el, "data-track-index") ?? numberAttr(el, "data-layer") ?? 0;
9623
+ }
9624
+ function freezeFrameMediaTime(input) {
9625
+ const rate = resolveRateSpec(input.automation, input.playbackRate);
9626
+ return input.mediaStart + sourceTimeAt(rate, Math.max(0, input.playhead - input.clipStart));
9627
+ }
9628
+ function freezeExtractArgs(src, mediaTime, output) {
9629
+ return ["-n", "-ss", String(round3(mediaTime)), "-i", src, "-frames:v", "1", output];
9630
+ }
9631
+ var randomStillToken = () => randomBytes(4).toString("hex");
9632
+ function freezeStillFileName(clipId, playhead, token = randomStillToken()) {
9633
+ const stem = clipId.replace(/[^A-Za-z0-9_-]/g, "_").slice(0, 48) || "clip";
9634
+ const idHash = createHash42("sha256").update(clipId).digest("hex").slice(0, 10);
9635
+ return `${stem}-${idHash}-${Math.round(playhead * 1e3)}-${token}.png`;
9636
+ }
9637
+ function readFreezeSource(html, target, playhead) {
9638
+ const { document: document2 } = parseSourceDocument(html);
9639
+ const el = findTargetElement(document2, target);
9640
+ if (!el || el.tagName.toLowerCase() !== "video") return null;
9641
+ const src = el.getAttribute("src");
9642
+ const start = numberAttr(el, "data-start");
9643
+ const duration = numberAttr(el, "data-duration");
9644
+ if (!src || start === null || duration === null) return null;
9645
+ if (playhead <= start + EPSILON || playhead >= start + duration - EPSILON) return null;
9646
+ return {
9647
+ id: el.getAttribute("id") || "clip",
9648
+ src,
9649
+ mediaTime: freezeFrameMediaTime({
9650
+ clipStart: start,
9651
+ playhead,
9652
+ mediaStart: readMediaOffsetSeconds((name) => el.getAttribute(name)),
9653
+ playbackRate: numberAttr(el, "data-playback-rate") ?? 1,
9654
+ automation: el.getAttribute("data-automation")
9655
+ })
9656
+ };
9657
+ }
9658
+ function targetOf(el) {
9659
+ const hfId = el.getAttribute("data-hf-id");
9660
+ return { ...hfId ? { hfId } : {}, id: el.getAttribute("id") };
9661
+ }
9662
+ function spans(el, time) {
9663
+ const start = numberAttr(el, "data-start");
9664
+ const duration = numberAttr(el, "data-duration");
9665
+ if (start === null || duration === null) return false;
9666
+ return time > start + EPSILON && time < start + duration - EPSILON;
9667
+ }
9668
+ function uniqueId(document2, base) {
9669
+ let id = base;
9670
+ for (let n = 2; document2.getElementById(id); n++) id = `${base}-${n}`;
9671
+ return id;
9672
+ }
9673
+ function shiftTracks(document2, tracks, from, by) {
9674
+ for (const el of document2.querySelectorAll("[data-start]")) {
9675
+ if (el.hasAttribute("data-composition-id") || !tracks.has(trackOf(el))) continue;
9676
+ const start = numberAttr(el, "data-start");
9677
+ if (start === null || start < from - EPSILON) continue;
9678
+ el.setAttribute("data-start", String(round3(start + by)));
9679
+ }
9680
+ }
9681
+ function growRootToFit(document2) {
9682
+ const root = document2.querySelector("[data-composition-id]");
9683
+ const rootDuration = root ? numberAttr(root, "data-duration") : null;
9684
+ if (!root || rootDuration === null) return;
9685
+ let end = rootDuration;
9686
+ for (const el of root.querySelectorAll("[data-start][data-duration]")) {
9687
+ const start = numberAttr(el, "data-start");
9688
+ const duration = numberAttr(el, "data-duration");
9689
+ if (start !== null && duration !== null) end = Math.max(end, start + duration);
9690
+ }
9691
+ if (end > rootDuration + EPSILON) root.setAttribute("data-duration", String(round3(end)));
9692
+ }
9693
+ var COPIED_PICTURE_ATTRS = ["class", "style", "data-color-grading"];
9694
+ function buildStill(document2, video, input) {
9695
+ const img = document2.createElement("img");
9696
+ img.setAttribute("id", input.id);
9697
+ for (const name of COPIED_PICTURE_ATTRS) {
9698
+ const value = video.getAttribute(name);
9699
+ if (value !== null) img.setAttribute(name, value);
9700
+ }
9701
+ if (!img.classList.contains("clip")) img.classList.add("clip");
9702
+ img.setAttribute("src", input.imageSrc);
9703
+ img.setAttribute("alt", "");
9704
+ img.setAttribute("data-start", String(round3(input.playhead)));
9705
+ img.setAttribute("data-duration", String(input.holdSeconds));
9706
+ img.setAttribute("data-track-index", String(trackOf(video)));
9707
+ img.setAttribute("data-timeline-label", "Freeze");
9708
+ return img;
9709
+ }
9710
+ function linkedPartners(document2, video) {
9711
+ const link = video.getAttribute(MEDIA_LINK_ATTR);
9712
+ if (!link) return [];
9713
+ return Array.from(document2.querySelectorAll(`[${MEDIA_LINK_ATTR}]`)).filter(
9714
+ (el) => el !== video && el.getAttribute(MEDIA_LINK_ATTR) === link
9715
+ );
9716
+ }
9717
+ function splitAll(html, elements, time) {
9718
+ const rightHalfIds = [];
9719
+ let next = html;
9720
+ for (const el of elements) {
9721
+ const split = splitElementInHtml(
9722
+ next,
9723
+ targetOf(el),
9724
+ time,
9725
+ `${el.getAttribute("id") || "clip"}-split`,
9726
+ {
9727
+ start: numberAttr(el, "data-start") ?? 0,
9728
+ duration: numberAttr(el, "data-duration") ?? 0,
9729
+ track: trackOf(el)
9730
+ }
9731
+ );
9732
+ if (!split.matched || !split.newId) return null;
9733
+ next = split.html;
9734
+ rightHalfIds.push(split.newId);
9735
+ }
9736
+ return { html: next, rightHalfIds };
9737
+ }
9738
+ function applyFreezeFrameToHtml(html, input) {
9739
+ const hold = input.holdSeconds ?? FREEZE_HOLD_SECONDS;
9740
+ const { document: before } = parseSourceDocument(html);
9741
+ const video = findTargetElement(before, input.target);
9742
+ if (!video || !isHTMLElement(video) || !spans(video, input.playhead)) return null;
9743
+ const partners = linkedPartners(before, video);
9744
+ const tracks = /* @__PURE__ */ new Set([trackOf(video), ...partners.map(trackOf)]);
9745
+ const cut = splitAll(
9746
+ html,
9747
+ [video, ...partners.filter((el) => spans(el, input.playhead))],
9748
+ input.playhead
9749
+ );
9750
+ if (!cut) return null;
9751
+ const { document: document2, wrappedFragment } = parseSourceDocument(cut.html);
9752
+ relinkSplitHalves(document2, cut.rightHalfIds);
9753
+ shiftTracks(document2, tracks, input.playhead, hold);
9754
+ const leftHalf = findTargetElement(document2, input.target);
9755
+ if (!leftHalf?.parentElement) return null;
9756
+ const freezeId = uniqueId(document2, `${video.getAttribute("id") || "clip"}-freeze`);
9757
+ const still = buildStill(document2, leftHalf, {
9758
+ id: freezeId,
9759
+ imageSrc: input.imageSrc,
9760
+ playhead: input.playhead,
9761
+ holdSeconds: hold
9762
+ });
9763
+ leftHalf.parentElement.insertBefore(still, leftHalf.nextSibling);
9764
+ growRootToFit(document2);
9765
+ return {
9766
+ html: ensureHfIds(wrappedFragment ? document2.body.innerHTML || "" : document2.toString()),
9767
+ freezeId
9768
+ };
9769
+ }
9770
+ var EXTRACT_TIMEOUT_MS = 3e4;
9771
+ var ffmpegExtractor = (args) => {
9772
+ const ffmpeg = findFfBinary("ffmpeg", { configuredMustExist: true });
9773
+ if (!ffmpeg) return Promise.resolve({ ok: false, error: "ffmpeg not found" });
9774
+ return new Promise((resolvePromise) => {
9775
+ execFile(
9776
+ ffmpeg,
9777
+ args,
9778
+ { timeout: EXTRACT_TIMEOUT_MS, windowsHide: true },
9779
+ (error, _o, stderr) => resolvePromise(
9780
+ error ? { ok: false, error: String(stderr || error.message) } : { ok: true }
9781
+ )
9782
+ );
9783
+ });
9784
+ };
9785
+ function isFreezeFrameRequest(value) {
9786
+ if (typeof value !== "object" || value === null) return false;
9787
+ const body = value;
9788
+ return typeof body.path === "string" && body.path.length > 0 && typeof body.expectedVersion === "string" && typeof body.target === "object" && body.target !== null && typeof body.playhead === "number" && Number.isFinite(body.playhead);
9789
+ }
9790
+ var FREEZE_DIR = ["assets", "freeze"];
9791
+ function readExpected(absPath, expectedVersion) {
9792
+ let content;
9793
+ try {
9794
+ content = readFileSync11(absPath, "utf-8");
9795
+ } catch {
9796
+ return { error: "not found", status: 404 };
9797
+ }
9798
+ return fileContentVersion(content) === expectedVersion ? { content } : { error: "file conflict", status: 409 };
9799
+ }
9800
+ async function extractStill(projectDir, absPath, source, playhead, tools) {
9801
+ const fileDir = dirname52(absPath);
9802
+ const mediaPath = pinWithinProject(projectDir, relative4(projectDir, join13(fileDir, source.src)));
9803
+ if (!mediaPath) return { error: `forbidden media path: ${source.src}`, status: 403 };
9804
+ const freezeDir = join13(projectDir, ...FREEZE_DIR);
9805
+ const fileName = freezeStillFileName(source.id, playhead, tools.stillToken());
9806
+ mkdirWithinProject(projectDir, freezeDir);
9807
+ const imagePath = pinWithinProject(projectDir, join13(...FREEZE_DIR, fileName));
9808
+ if (!imagePath || dirname52(imagePath) !== freezeDir) {
9809
+ return { error: `forbidden freeze path: ${fileName}`, status: 403 };
9810
+ }
9811
+ if (existsSync8(imagePath))
9812
+ return { error: `freeze still already exists: ${fileName}`, status: 409 };
9813
+ const extracted = await tools.extract(freezeExtractArgs(mediaPath, source.mediaTime, imagePath));
9814
+ if (!extracted.ok) {
9815
+ return {
9816
+ error: `Could not extract the frame: ${extracted.error ?? "ffmpeg failed"}`,
9817
+ status: 500
9818
+ };
9819
+ }
9820
+ const depth = relative4(projectDir, fileDir).split(sep22).filter(Boolean).length;
9821
+ return { imageSrc: `${"../".repeat(depth)}${FREEZE_DIR.join("/")}/${fileName}` };
9822
+ }
9823
+ function writeFolded(projectDir, absPath, path, before, after, options) {
9824
+ if (readFileSync11(absPath, "utf-8") !== before) {
9825
+ return { error: `file conflict: ${path}`, status: 409 };
9826
+ }
9827
+ const backup = snapshotBeforeWrite(projectDir, absPath);
9828
+ if (backup.error) return { error: `Failed to create backup: ${backup.error}`, status: 500 };
9829
+ const writeToken = createWriteToken(options.token);
9830
+ replaceFileAtomically(absPath, after, statSync8(absPath).mode);
9831
+ const version = fileContentVersion(after);
9832
+ recordFileWriteReceipt(absPath, { path, version, writeToken, overwrote: before });
9833
+ return { version, writeToken, backupPath: backupPathForResponse(projectDir, backup.backupPath) };
9834
+ }
9835
+ function registerFreezeFrameRoutes(api, adapter, extract = ffmpegExtractor, stillToken = randomStillToken) {
9836
+ api.post("/projects/:id/file-mutations/freeze-frame", async (c) => {
9837
+ const body = await c.req.json().catch(() => null);
9838
+ if (!isFreezeFrameRequest(body)) {
9839
+ return c.json({ error: "path, expectedVersion, target and playhead required" }, 400);
9840
+ }
9841
+ const project = await adapter.resolveProject(c.req.param("id"));
9842
+ if (!project) return c.json({ error: "not found" }, 404);
9843
+ const absPath = pinWithinProject(project.dir, body.path);
9844
+ if (!absPath) return c.json({ error: `forbidden path: ${body.path}` }, 403);
9845
+ const read = readExpected(absPath, body.expectedVersion);
9846
+ if ("error" in read) return c.json({ error: `${read.error}: ${body.path}` }, read.status);
9847
+ const before = read.content;
9848
+ const source = readFreezeSource(before, body.target, body.playhead);
9849
+ if (!source) return c.json({ error: "Move the playhead inside a video clip to freeze" }, 400);
9850
+ const still = await extractStill(project.dir, absPath, source, body.playhead, {
9851
+ extract,
9852
+ stillToken
9853
+ });
9854
+ if ("error" in still) return c.json({ error: still.error }, still.status);
9855
+ const { imageSrc } = still;
9856
+ const folded = applyFreezeFrameToHtml(before, {
9857
+ target: body.target,
9858
+ playhead: body.playhead,
9859
+ imageSrc
9860
+ });
9861
+ if (!folded) return c.json({ error: "Freeze target was not found in the file" }, 400);
9862
+ const written = writeFolded(project.dir, absPath, body.path, before, folded.html, {
9863
+ token: body.transactionToken ?? c.req.header("X-Hyperframes-Write-Token")
9864
+ });
9865
+ if ("error" in written) return c.json({ error: written.error }, written.status);
9866
+ const { version, writeToken, backupPath } = written;
9867
+ return c.json({
9868
+ ok: true,
9869
+ path: body.path,
9870
+ before,
9871
+ after: folded.html,
9872
+ version,
9873
+ writeToken,
9874
+ backupPath,
9875
+ freezeId: folded.freezeId,
9876
+ imageSrc
9877
+ });
9878
+ });
9879
+ }
9880
+ var defaultMeasure = (file, window2) => measureAudio(requiredFfmpeg(), file, window2);
9881
+ var InvalidField = class extends Error {
9882
+ };
9883
+ function numberField(body, name, fallback, valid) {
9884
+ const raw = body[name];
9885
+ if (raw === void 0 || raw === null) return fallback;
9886
+ if (typeof raw === "number" && Number.isFinite(raw) && valid(raw)) return raw;
9887
+ throw new InvalidField(`${name} is out of range`);
9888
+ }
9889
+ function readNormalizeRequest(body) {
9890
+ const src = typeof body.src === "string" ? body.src.trim() : "";
9891
+ if (!src) throw new InvalidField("src is required");
9892
+ const duration = numberField(body, "duration", 0, (v) => v > 0);
9893
+ return {
9894
+ id: typeof body.id === "string" && body.id ? body.id : src,
9895
+ src,
9896
+ window: {
9897
+ mediaStart: numberField(body, "mediaStart", 0, (v) => v >= 0),
9898
+ duration: duration > 0 ? duration : null,
9899
+ playbackRate: numberField(body, "playbackRate", 1, (v) => v > 0)
9900
+ },
9901
+ volume: numberField(body, "volume", 1, (v) => v >= 0),
9902
+ targetLufs: numberField(body, "targetLufs", DEFAULT_TARGET_LUFS, (v) => v < 0)
9903
+ };
9904
+ }
9905
+ function parseNormalizeRequest(body) {
9906
+ try {
9907
+ return readNormalizeRequest(body);
9908
+ } catch (error) {
9909
+ if (error instanceof InvalidField) return error.message;
9910
+ throw error;
9911
+ }
9912
+ }
9913
+ function isRecord(value) {
9914
+ return typeof value === "object" && value !== null && !Array.isArray(value);
9915
+ }
9916
+ function registerLoudnessRoutes(api, adapter, measure = defaultMeasure) {
9917
+ api.post("/projects/:id/loudness/normalize", async (c) => {
9918
+ const project = await adapter.resolveProject(c.req.param("id"));
9919
+ if (!project) return c.json({ error: "not found" }, 404);
9920
+ const body = await c.req.json().catch(() => null);
9921
+ if (!isRecord(body)) return c.json({ error: "invalid body" }, 400);
9922
+ const request = parseNormalizeRequest(body);
9923
+ if (typeof request === "string") return c.json({ error: request }, 400);
9924
+ let file;
9925
+ try {
9926
+ file = resolveLocalAudioPath(project.dir, request.src);
9927
+ } catch (error) {
9928
+ return c.json({ error: error instanceof Error ? error.message : String(error) }, 400);
9929
+ }
9930
+ if (!existsSync9(file)) return c.json({ error: "file not found" }, 404);
9931
+ try {
9932
+ const measurement = await measure(file, request.window);
9933
+ const plan = absoluteLoudnessPlan(
9934
+ { id: request.id, volume: request.volume, ...measurement },
9935
+ request.targetLufs
9936
+ );
9937
+ return c.json({ plan });
9938
+ } catch (error) {
9939
+ return c.json({ error: error instanceof Error ? error.message : String(error) }, 422);
9940
+ }
9941
+ });
9942
+ }
9943
+ var PEAK_BIN_SECONDS = 0.05;
9944
+ var PEAK_SAMPLE_RATE = 48e3;
9945
+ var execFileAsync = promisify(execFile2);
9946
+ var PEAK_MAP_CACHE_VERSION = "peaks-v2";
9947
+ function createPeakAccumulator(sampleRate = PEAK_SAMPLE_RATE, binSeconds = PEAK_BIN_SECONDS, channels = 2) {
9948
+ return {
9949
+ bins: [],
9950
+ frame: 0,
9951
+ carry: Buffer.alloc(0),
9952
+ framesPerBin: Math.max(1, Math.round(sampleRate * binSeconds)),
9953
+ channels: Math.max(1, channels)
9954
+ };
9955
+ }
9956
+ function framePeak(data, offset, channels) {
9957
+ let peak = 0;
9958
+ for (let ch = 0; ch < channels; ch++) {
9959
+ peak = Math.max(peak, Math.abs(data.readFloatLE(offset + ch * 4)));
9960
+ }
9961
+ return peak;
9962
+ }
9963
+ function accumulatePeaks(acc, chunk) {
9964
+ const data = acc.carry.length > 0 ? Buffer.concat([acc.carry, chunk]) : chunk;
9965
+ const frameBytes = 4 * acc.channels;
9966
+ const whole = data.length - data.length % frameBytes;
9967
+ for (let offset = 0; offset < whole; offset += frameBytes) {
9968
+ const peak = framePeak(data, offset, acc.channels);
9969
+ const bin = Math.floor(acc.frame / acc.framesPerBin);
9970
+ if (acc.bins.length <= bin) acc.bins.push(0);
9971
+ if (peak > (acc.bins[bin] ?? 0)) acc.bins[bin] = peak;
9972
+ acc.frame += 1;
9973
+ }
9974
+ acc.carry = Buffer.from(data.subarray(whole));
9975
+ }
9976
+ function finishPeaks(acc) {
9977
+ return acc.bins.map((peak) => Number(peak.toFixed(4)));
9978
+ }
9979
+ async function probeChannels(mediaPath) {
9980
+ const { stdout } = await execFileAsync(
9981
+ findFfBinary("ffprobe") ?? "ffprobe",
9982
+ [
9983
+ "-v",
9984
+ "error",
9985
+ "-select_streams",
9986
+ "a:0",
9987
+ "-show_entries",
9988
+ "stream=channels",
9989
+ "-of",
9990
+ "csv=p=0",
9991
+ "--",
9992
+ mediaPath
9993
+ ],
9994
+ { encoding: "utf8", timeout: 3e4, windowsHide: true }
9995
+ );
9996
+ const channels = Number.parseInt(stdout.trim(), 10);
9997
+ if (!Number.isFinite(channels) || channels < 1) throw new Error("no audio stream");
9998
+ return channels;
9999
+ }
10000
+ async function decodePeakMap(mediaPath) {
10001
+ const channels = await probeChannels(mediaPath);
10002
+ return new Promise((resolvePromise, reject) => {
10003
+ const proc = spawn2(
10004
+ findFfBinary("ffmpeg") ?? "ffmpeg",
10005
+ [
10006
+ "-v",
10007
+ "error",
10008
+ "-i",
10009
+ mediaPath,
10010
+ "-map",
10011
+ "0:a:0",
10012
+ "-vn",
10013
+ "-ar",
10014
+ String(PEAK_SAMPLE_RATE),
10015
+ "-f",
10016
+ "f32le",
10017
+ "pipe:1"
10018
+ ],
10019
+ { stdio: ["ignore", "pipe", "ignore"], windowsHide: true }
10020
+ );
10021
+ const acc = createPeakAccumulator(PEAK_SAMPLE_RATE, PEAK_BIN_SECONDS, channels);
10022
+ proc.stdout?.on("data", (chunk) => accumulatePeaks(acc, chunk));
10023
+ proc.on("close", (code) => {
10024
+ if (code !== 0) {
10025
+ reject(new Error(`ffmpeg failed decoding audio (exit ${code})`));
10026
+ return;
10027
+ }
10028
+ if (acc.frame === 0) {
10029
+ reject(new Error("ffmpeg produced no audio samples"));
10030
+ return;
10031
+ }
10032
+ resolvePromise(finishPeaks(acc));
10033
+ });
10034
+ proc.on("error", reject);
10035
+ });
10036
+ }
10037
+ function buildPeakMapCacheKey(assetPath, fingerprint) {
10038
+ const name = assetPath.replace(/[/\\]/g, "_");
10039
+ return `${PEAK_MAP_CACHE_VERSION}_${name}_${fingerprint.size}-${Math.round(fingerprint.mtimeMs)}.json`;
10040
+ }
10041
+ var decodesInFlight = /* @__PURE__ */ new Map();
10042
+ function decodeOnce(cachePath, mediaPath, decode) {
10043
+ const pending = decodesInFlight.get(cachePath);
10044
+ if (pending) return pending;
10045
+ const started = decode(mediaPath).finally(() => decodesInFlight.delete(cachePath));
10046
+ decodesInFlight.set(cachePath, started);
10047
+ return started;
10048
+ }
10049
+ function readCachedBins(cacheDir, cachePath) {
10050
+ try {
10051
+ if (!isWaveformCacheDirectory(cacheDir) || !existsSync10(cachePath)) return null;
10052
+ const parsed = JSON.parse(readFileSync12(cachePath, "utf-8"));
10053
+ return Array.isArray(parsed) && parsed.every((n) => typeof n === "number") ? parsed : null;
10054
+ } catch {
10055
+ return null;
10056
+ }
10057
+ }
10058
+ function registerPeakRoutes(api, adapter, decode = decodePeakMap) {
10059
+ api.get("/projects/:id/peaks/*", async (c) => {
10060
+ const project = await adapter.resolveProject(c.req.param("id"));
10061
+ if (!project) return c.json({ error: "not found" }, 404);
10062
+ const assetPath = requestSubPath(c.req.url, "projects/:id/peaks");
10063
+ const mediaPath = resolve6(project.dir, assetPath);
10064
+ if (!isWithinProjectRoot(project.dir, mediaPath))
10065
+ return c.json({ error: "file not found" }, 404);
10066
+ const stats = statSync9(mediaPath, { throwIfNoEntry: false });
10067
+ if (!stats?.isFile()) return c.json({ error: "file not found" }, 404);
10068
+ const cacheDir = join14(project.dir, ".waveform-cache");
10069
+ const cachePath = join14(cacheDir, buildPeakMapCacheKey(assetPath, stats));
10070
+ const cached = readCachedBins(cacheDir, cachePath);
10071
+ if (cached) return c.json({ binSeconds: PEAK_BIN_SECONDS, bins: cached });
10072
+ let bins;
10073
+ try {
10074
+ bins = await decodeOnce(cachePath, mediaPath, decode);
10075
+ } catch {
10076
+ return c.json({ error: "failed to decode audio" }, 500);
10077
+ }
10078
+ try {
10079
+ writeWaveformCache(cachePath, bins);
10080
+ } catch {
10081
+ }
10082
+ return c.json({ binSeconds: PEAK_BIN_SECONDS, bins });
10083
+ });
10084
+ }
9577
10085
  var MAX_FONT_RESULTS = 2e3;
9578
10086
  var GOOGLE_FONTS_METADATA_URL = "https://fonts.google.com/metadata/fonts";
9579
10087
  var GOOGLE_FONTS_FETCH_TIMEOUT_MS = 3e3;
@@ -9601,7 +10109,7 @@ var GOOGLE_FONT_FALLBACKS = [
9601
10109
  "Outfit",
9602
10110
  "JetBrains Mono"
9603
10111
  ];
9604
- function isRecord(value) {
10112
+ function isRecord2(value) {
9605
10113
  return typeof value === "object" && value !== null;
9606
10114
  }
9607
10115
  function collectFontsFromDir(dir) {
@@ -9625,10 +10133,10 @@ function listInstalledFontFamilies() {
9625
10133
  return cachedFonts;
9626
10134
  }
9627
10135
  function parseGoogleFontMetadata(value) {
9628
- if (!isRecord(value) || !Array.isArray(value.familyMetadataList)) return [];
10136
+ if (!isRecord2(value) || !Array.isArray(value.familyMetadataList)) return [];
9629
10137
  const families = [];
9630
10138
  for (const entry of value.familyMetadataList) {
9631
- if (!isRecord(entry) || typeof entry.family !== "string") continue;
10139
+ if (!isRecord2(entry) || typeof entry.family !== "string") continue;
9632
10140
  families.push(entry.family);
9633
10141
  }
9634
10142
  return families;
@@ -9728,14 +10236,14 @@ function registerRegistryRoutes(api, adapter) {
9728
10236
  }
9729
10237
  });
9730
10238
  }
9731
- function isRecord2(value) {
10239
+ function isRecord3(value) {
9732
10240
  return typeof value === "object" && value !== null && !Array.isArray(value);
9733
10241
  }
9734
10242
  function isFiniteNumber(value) {
9735
10243
  return typeof value === "number" && Number.isFinite(value);
9736
10244
  }
9737
10245
  function isStringRecord(value) {
9738
- return isRecord2(value) && Object.values(value).every((v) => typeof v === "string");
10246
+ return isRecord3(value) && Object.values(value).every((v) => typeof v === "string");
9739
10247
  }
9740
10248
  function hasString(value, key) {
9741
10249
  return typeof value[key] === "string";
@@ -9753,20 +10261,20 @@ function hasOptionalNumber(value, key) {
9753
10261
  return value[key] === void 0 || isFiniteNumber(value[key]);
9754
10262
  }
9755
10263
  function isBoundingBox(value) {
9756
- return isRecord2(value) && ["x", "y", "width", "height"].every((key) => isFiniteNumber(value[key]));
10264
+ return isRecord3(value) && ["x", "y", "width", "height"].every((key) => isFiniteNumber(value[key]));
9757
10265
  }
9758
10266
  function isTarget(value) {
9759
- if (!isRecord2(value)) return false;
10267
+ if (!isRecord3(value)) return false;
9760
10268
  return hasOptionalNullableString(value, "id") && hasOptionalString(value, "hfId") && hasOptionalString(value, "selector") && hasOptionalNumber(value, "selectorIndex");
9761
10269
  }
9762
10270
  function isTextField(value) {
9763
- return isRecord2(value) && hasRequiredStrings(value, ["key", "label", "value", "tagName"]) && ["self", "child", "text-node"].includes(value.source);
10271
+ return isRecord3(value) && hasRequiredStrings(value, ["key", "label", "value", "tagName"]) && ["self", "child", "text-node"].includes(value.source);
9764
10272
  }
9765
10273
  function isTextFields(value) {
9766
10274
  return Array.isArray(value) && value.every(isTextField);
9767
10275
  }
9768
10276
  function isSelectionSnapshot(value) {
9769
- if (!isRecord2(value)) return false;
10277
+ if (!isRecord3(value)) return false;
9770
10278
  const checks = [
9771
10279
  value.schemaVersion === 1 && hasRequiredStrings(value, [
9772
10280
  "projectId",
@@ -9784,7 +10292,7 @@ function isSelectionSnapshot(value) {
9784
10292
  isStringRecord(value.inlineStyles),
9785
10293
  isStringRecord(value.computedStyles),
9786
10294
  isTextFields(value.textFields),
9787
- isRecord2(value.capabilities)
10295
+ isRecord3(value.capabilities)
9788
10296
  ];
9789
10297
  return checks.every(Boolean);
9790
10298
  }
@@ -9808,7 +10316,7 @@ function registerSelectionRoutes(api, adapter) {
9808
10316
  } catch {
9809
10317
  return c.json({ error: "invalid json" }, 400);
9810
10318
  }
9811
- if (!isRecord2(body) || !("selection" in body)) {
10319
+ if (!isRecord3(body) || !("selection" in body)) {
9812
10320
  return c.json({ error: "missing selection" }, 400);
9813
10321
  }
9814
10322
  if (body.selection === null) {
@@ -9860,7 +10368,7 @@ function uniqueAssetPath(projectDir, assetPath, taken) {
9860
10368
  const ext = extname2(assetPath);
9861
10369
  const withoutExt = assetPath.slice(0, -ext.length);
9862
10370
  let candidate = assetPath;
9863
- for (let index = 2; taken.has(candidate) || existsSync8(join13(projectDir, candidate)); index++) {
10371
+ for (let index = 2; taken.has(candidate) || existsSync11(join15(projectDir, candidate)); index++) {
9864
10372
  candidate = `${withoutExt}-${index}${ext}`;
9865
10373
  }
9866
10374
  return candidate;
@@ -9921,7 +10429,7 @@ function registerMediaRoutes(api, adapter, options = {}) {
9921
10429
  }
9922
10430
  const filePath = resolveWithinProject(project.dir, assetPath);
9923
10431
  if (!filePath) return c.json({ error: "forbidden" }, 403);
9924
- if (!existsSync8(filePath)) return c.json({ error: "media not found" }, 404);
10432
+ if (!existsSync11(filePath)) return c.json({ error: "media not found" }, 404);
9925
10433
  return c.json({ path: assetPath, metadata: await readMediaMetadata(filePath) });
9926
10434
  });
9927
10435
  api.post(
@@ -9943,7 +10451,7 @@ function registerMediaRoutes(api, adapter, options = {}) {
9943
10451
  }
9944
10452
  const inputPath = resolveWithinProject(project.dir, inputAssetPath);
9945
10453
  if (!inputPath) return c.json({ error: "forbidden" }, 403);
9946
- if (!existsSync8(inputPath)) return c.json({ error: "input media not found" }, 404);
10454
+ if (!existsSync11(inputPath)) return c.json({ error: "input media not found" }, 404);
9947
10455
  const inputIsVideo = isVideoPath(inputAssetPath);
9948
10456
  const inputIsImage = isImagePath(inputAssetPath);
9949
10457
  if (!inputIsVideo && !inputIsImage) {
@@ -9979,8 +10487,8 @@ function registerMediaRoutes(api, adapter, options = {}) {
9979
10487
  return c.json({ error: "forbidden" }, 403);
9980
10488
  }
9981
10489
  }
9982
- mkdirWithinProject(project.dir, dirname52(outputPath));
9983
- if (backgroundOutputPath) mkdirWithinProject(project.dir, dirname52(backgroundOutputPath));
10490
+ mkdirWithinProject(project.dir, dirname6(outputPath));
10491
+ if (backgroundOutputPath) mkdirWithinProject(project.dir, dirname6(backgroundOutputPath));
9984
10492
  const jobId = makeJobId(project.id, mediaJobs);
9985
10493
  const state = adapter.startBackgroundRemoval({
9986
10494
  project,
@@ -10036,10 +10544,10 @@ function registerMediaRoutes(api, adapter, options = {}) {
10036
10544
  });
10037
10545
  }
10038
10546
  function readGlobalAssets(home = homedir2()) {
10039
- const manifestPath = join14(home, ".media", "manifest.jsonl");
10040
- if (!existsSync9(manifestPath)) return [];
10547
+ const manifestPath = join16(home, ".media", "manifest.jsonl");
10548
+ if (!existsSync12(manifestPath)) return [];
10041
10549
  const out = [];
10042
- for (const line of readFileSync11(manifestPath, "utf8").split("\n")) {
10550
+ for (const line of readFileSync13(manifestPath, "utf8").split("\n")) {
10043
10551
  if (!line.trim()) continue;
10044
10552
  try {
10045
10553
  const rec = JSON.parse(line);
@@ -10222,6 +10730,9 @@ function createStudioApi(adapter) {
10222
10730
  registerSelectionRoutes(api, adapter);
10223
10731
  registerMediaRoutes(api, adapter);
10224
10732
  registerWaveformRoutes(api, adapter);
10733
+ registerFreezeFrameRoutes(api, adapter);
10734
+ registerLoudnessRoutes(api, adapter);
10735
+ registerPeakRoutes(api, adapter);
10225
10736
  registerFontRoutes(api);
10226
10737
  registerRegistryRoutes(api, adapter);
10227
10738
  registerGlobalAssetRoutes(api);
@@ -10237,23 +10748,23 @@ function duplicateElementInHtml(source, target, newId, at) {
10237
10748
  if (duration === null || numericAttribute(element, "data-start") === null) {
10238
10749
  return { html: source, matched: false, newId: null };
10239
10750
  }
10240
- const uniqueId = nextUniqueId(document2, newId);
10751
+ const uniqueId2 = nextUniqueId(document2, newId);
10241
10752
  rippleElements(document2, element, at, duration, track);
10242
10753
  const clone = element.cloneNode(true);
10243
10754
  if (!isHTMLElement(clone)) return { html: source, matched: false, newId: null };
10244
- clone.setAttribute("id", uniqueId);
10755
+ clone.setAttribute("id", uniqueId2);
10245
10756
  dedupeClonedCompositionId(document2, clone);
10246
10757
  stripStableIds(clone);
10247
10758
  clone.setAttribute("data-start", String(at));
10248
10759
  element.parentElement.insertBefore(clone, element.nextSibling);
10249
10760
  const html = wrappedFragment ? document2.body.innerHTML || "" : document2.toString();
10250
- return { html: ensureHfIds(html), matched: true, newId: uniqueId };
10761
+ return { html: ensureHfIds(html), matched: true, newId: uniqueId2 };
10251
10762
  }
10252
10763
  function nextUniqueId(document2, newId) {
10253
- let uniqueId = newId;
10764
+ let uniqueId2 = newId;
10254
10765
  let suffix = 2;
10255
- while (document2.getElementById(uniqueId)) uniqueId = `${newId}-${suffix++}`;
10256
- return uniqueId;
10766
+ while (document2.getElementById(uniqueId2)) uniqueId2 = `${newId}-${suffix++}`;
10767
+ return uniqueId2;
10257
10768
  }
10258
10769
  function rippleElements(document2, element, at, duration, track) {
10259
10770
  for (const candidate of Array.from(document2.querySelectorAll("[data-start][data-duration]"))) {
@@ -10278,13 +10789,13 @@ function numericAttribute(element, name) {
10278
10789
  return Number.isFinite(value) ? value : null;
10279
10790
  }
10280
10791
  function createPreviewDocumentStore(projectDir, salt) {
10281
- const dir = join15(projectDir, ".hyperframes", "preview");
10282
- const fileFor = (key) => join15(dir, `${createHash42("sha256").update(`${salt}
10792
+ const dir = join17(projectDir, ".hyperframes", "preview");
10793
+ const fileFor = (key) => join17(dir, `${createHash5("sha256").update(`${salt}
10283
10794
  ${key}`).digest("hex").slice(0, 32)}.html`);
10284
10795
  return {
10285
10796
  read(key) {
10286
10797
  try {
10287
- return readFileSync12(fileFor(key), "utf-8");
10798
+ return readFileSync14(fileFor(key), "utf-8");
10288
10799
  } catch {
10289
10800
  return null;
10290
10801
  }
@@ -10295,7 +10806,7 @@ ${key}`).digest("hex").slice(0, 32)}.html`);
10295
10806
  const file = fileFor(key);
10296
10807
  replaceFileAtomically(file, html, 420);
10297
10808
  for (const name of readdirSync5(dir)) {
10298
- if (join15(dir, name) !== file) rmSync5(join15(dir, name), { force: true });
10809
+ if (join17(dir, name) !== file) rmSync5(join17(dir, name), { force: true });
10299
10810
  }
10300
10811
  } catch {
10301
10812
  }