hyperframes 0.8.107 → 0.8.108

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 (165) hide show
  1. package/dist/{add-DXZ3HNQI.js → add-OH75E7FX.js} +15 -15
  2. package/dist/{animatedGifPrep-2IQB4DGE.js → animatedGifPrep-ZDAWSJYL.js} +6 -6
  3. package/dist/{asset-KZIG3MZN.js → asset-BVDO4DEW.js} +2 -2
  4. package/dist/{assetCataloger-4GE6PIUA.js → assetCataloger-FXEHOUA3.js} +2 -2
  5. package/dist/{auth-5IYF3H67.js → auth-K5GUKO5S.js} +2 -2
  6. package/dist/{autoUpdate-5C3FXM6Y.js → autoUpdate-77ROWK5G.js} +2 -2
  7. package/dist/{backgroundChecks-75WGW4NB.js → backgroundChecks-3Q3D5WBJ.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-HQZPR6KE.js} +18 -12
  11. package/dist/{benchmark-U6TQ6DS2.js → benchmark-BQOWARGU.js} +12 -12
  12. package/dist/{browser-VDS6QAVA.js → browser-45CK7HXI.js} +9 -9
  13. package/dist/{bundleWithLocalizedFonts-PMWWW5YG.js → bundleWithLocalizedFonts-3M2LKRHK.js} +2 -2
  14. package/dist/{capture-5KE34KCD.js → capture-6IVZXM7G.js} +2 -2
  15. package/dist/{capture-BKA3UCZS.js → capture-VGEUOP7N.js} +7 -7
  16. package/dist/{catalog-ZXOSU27O.js → catalog-VHD7B6FD.js} +15 -15
  17. package/dist/{check-MZMZCXPN.js → check-UPNZZJXV.js} +30 -29
  18. package/dist/{checkBrowser-UIZJXYCQ.js → checkBrowser-KWTHKOOP.js} +29 -28
  19. package/dist/{chunk-L7XDI3DT.js → chunk-5N4KKGSZ.js} +1 -1
  20. package/dist/{normalize-audio-J2PG6YXH.js → chunk-5ON5GH3X.js} +101 -209
  21. package/dist/{chunk-HGL4WJDI.js → chunk-5R5UHSXN.js} +9 -9
  22. package/dist/{chunk-AO4PBESP.js → chunk-AFGDOKRI.js} +7 -7
  23. package/dist/{chunk-NHTRUAMD.js → chunk-AWUZ3AUD.js} +1 -1
  24. package/dist/{chunk-VMDEGITC.js → chunk-B25H4M4V.js} +1 -1
  25. package/dist/{chunk-3WG63KFW.js → chunk-C6XQER6H.js} +25 -11
  26. package/dist/{chunk-FLUBCMNT.js → chunk-CFC3EEDQ.js} +1 -1
  27. package/dist/{chunk-6QWHIARJ.js → chunk-D7SCIX4D.js} +1 -1
  28. package/dist/{chunk-T4Z4UNSX.js → chunk-FZGUZIK5.js} +3 -3
  29. package/dist/{chunk-73VPF3SS.js → chunk-GYYFNAVA.js} +1 -1
  30. package/dist/{chunk-BRTGJ73L.js → chunk-HQGJLZB6.js} +2 -2
  31. package/dist/{chunk-3CYAZWF2.js → chunk-J7XPPADY.js} +3 -3
  32. package/dist/{chunk-4W4UDPCH.js → chunk-KVOWTRD5.js} +2 -2
  33. package/dist/{chunk-SKPL32FI.js → chunk-KZ5OSG4G.js} +1 -1
  34. package/dist/{chunk-TI5Z4SWP.js → chunk-KZJ6V7RT.js} +3 -3
  35. package/dist/{chunk-P7XRDAWU.js → chunk-LAQEZHOH.js} +1 -1
  36. package/dist/{chunk-HJCTKNXF.js → chunk-LPXM5L6S.js} +1 -1
  37. package/dist/{chunk-J6CSEDK7.js → chunk-LTYI4E42.js} +4 -4
  38. package/dist/{chunk-KTPQ67TX.js → chunk-LZPAZZZP.js} +1 -1
  39. package/dist/{chunk-GLTHJJIF.js → chunk-MGTECHI3.js} +2 -2
  40. package/dist/{chunk-ADP6TMYP.js → chunk-NGVH2QYT.js} +8 -8
  41. package/dist/{chunk-YY5J75H5.js → chunk-NJLFRKHX.js} +3 -3
  42. package/dist/{chunk-OGTR33KN.js → chunk-NROJESOK.js} +2 -2
  43. package/dist/{chunk-QM2NUN2B.js → chunk-ONY5CJXS.js} +2 -2
  44. package/dist/{chunk-PU2IOWX4.js → chunk-OVRFBUXU.js} +2 -2
  45. package/dist/{chunk-D5EJCP26.js → chunk-QB6OM2BP.js} +79 -64
  46. package/dist/{chunk-WBVPQC7J.js → chunk-QBHT234I.js} +1 -1
  47. package/dist/{chunk-3LBFLW7E.js → chunk-QRXICW5Q.js} +1 -1
  48. package/dist/{chunk-YVEZT6WT.js → chunk-RPMXMOFU.js} +1 -1
  49. package/dist/{chunk-QH6UFY2O.js → chunk-RSL3XFMO.js} +4 -4
  50. package/dist/{chunk-C3KEF3UA.js → chunk-SIVCJJHE.js} +1 -1
  51. package/dist/{chunk-4DJXCVQJ.js → chunk-SQ42NHXP.js} +574 -61
  52. package/dist/{chunk-SGBOZ2HF.js → chunk-THVNQFYG.js} +1 -1
  53. package/dist/{chunk-T3BOWXPX.js → chunk-TIZQC44K.js} +1 -1
  54. package/dist/{chunk-I73Z6OOE.js → chunk-US7SWKGC.js} +1 -1
  55. package/dist/{chunk-TPPIERG3.js → chunk-VBXJSCHR.js} +3 -3
  56. package/dist/{chunk-NX6G66F4.js → chunk-VLHN34SB.js} +2 -2
  57. package/dist/{chunk-R4IB642V.js → chunk-X763EUBO.js} +2 -2
  58. package/dist/{chunk-Z4UAXNWV.js → chunk-XIGK4LNU.js} +2 -2
  59. package/dist/{chunk-JNY3VJWB.js → chunk-XYVFPDNU.js} +1 -1
  60. package/dist/chunk-YLVI7VL5.js +3541 -0
  61. package/dist/{chunk-7BI5BYMQ.js → chunk-ZFL6FVQZ.js} +5 -5
  62. package/dist/{clean-46MZVA37.js → clean-LCRWNIWO.js} +15 -14
  63. package/dist/cli.js +50 -50
  64. package/dist/{cloud-GAZQGPCW.js → cloud-ITRFE5UN.js} +4 -4
  65. package/dist/{cloudrun-ZWL7SLDD.js → cloudrun-LVZ4OPPC.js} +3 -3
  66. package/dist/{compare-66AAJESM.js → compare-NPVMUILH.js} +24 -23
  67. package/dist/{compiler-3N7XQRH6.js → compiler-YYOV2BJ7.js} +3 -3
  68. package/dist/{component-3OCOHK2B.js → component-HJFMHPNW.js} +3 -3
  69. package/dist/{compositions-H2BP4N6F.js → compositions-I34RN5WD.js} +11 -11
  70. package/dist/{delete-WNJLVPGO.js → delete-WN3J74BD.js} +2 -2
  71. package/dist/{dist-AY2RBEXL.js → dist-WCMFH74H.js} +1 -1
  72. package/dist/{dist-HSEMXPG3.js → dist-YUIPB7OX.js} +8 -2
  73. package/dist/{doctor-IFOZS45N.js → doctor-MYC2S3J3.js} +10 -10
  74. package/dist/{events-6TH3FU3F.js → events-7JVV4746.js} +9 -9
  75. package/dist/{events-5EKFJCCZ.js → events-AEUV2JPA.js} +8 -8
  76. package/dist/{feedback-QRQNMORT.js → feedback-X7DDSZPU.js} +10 -10
  77. package/dist/{figma-WONEPLUQ.js → figma-ISI4PPTF.js} +3 -3
  78. package/dist/fontLocalizeCli.js +8 -8
  79. package/dist/{get-C7FCYNI2.js → get-IYALAE4I.js} +2 -2
  80. package/dist/{gpuPolicy-BXXQF3VC.js → gpuPolicy-KSWTVU5C.js} +6 -6
  81. package/dist/{grade-compare-SCCSZ5P3.js → grade-compare-YWPIWQLK.js} +24 -23
  82. package/dist/{help-FJOUNXVD.js → help-S2KJRMAJ.js} +1 -1
  83. package/dist/{history-OZY36NEC.js → history-CQDDCY4J.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-4KFNUWYN.js} +11 -11
  89. package/dist/{init-YGZCDKSS.js → init-I7GMRHFW.js} +22 -22
  90. package/dist/{inspect-X547EZ4I.js → inspect-DUCLCHSD.js} +25 -24
  91. package/dist/{installer-6J24NTIV.js → installer-NBEN3ZIP.js} +4 -4
  92. package/dist/{keyframes-EIWEION2.js → keyframes-F3VDEN66.js} +12 -12
  93. package/dist/{lambda-DSZDMP7P.js → lambda-BFHJMMCG.js} +4 -4
  94. package/dist/{layout-B7KCBLBH.js → layout-RNCXJI6C.js} +25 -24
  95. package/dist/{lint-RBDG4ERJ.js → lint-3NKKYCMH.js} +12 -12
  96. package/dist/{lintProject-CMPAR7BS.js → lintProject-F2OTN2IR.js} +1 -1
  97. package/dist/{list-BPHPHLDJ.js → list-Q7SOE3NF.js} +2 -2
  98. package/dist/{login-HV7KBW2J.js → login-TGRGWS5E.js} +4 -4
  99. package/dist/{media-treatment-RS6BZSQC.js → media-treatment-5TUXWHWE.js} +12 -12
  100. package/dist/{models-K4U7GRXV.js → models-YJUTM6TH.js} +7 -7
  101. package/dist/{motionShot-5AI2DALY.js → motionShot-5QUMZ7J3.js} +10 -10
  102. package/dist/normalize-audio-63BI5BPL.js +276 -0
  103. package/dist/{pipeline-HIUESSET.js → pipeline-4NN5T7Q3.js} +6 -6
  104. package/dist/{play-TTLQXBAH.js → play-SRDESSZI.js} +20 -19
  105. package/dist/{preflight-63ODQ625.js → preflight-SVN4PSFO.js} +7 -7
  106. package/dist/{present-XKGZ26FQ.js → present-TDYR5LRW.js} +16 -15
  107. package/dist/{preview-O2YEKMYJ.js → preview-Z5MFRZUM.js} +17 -17
  108. package/dist/{producer-ZTQIJO7E.js → producer-RS7CB3XJ.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-2R22XX77.js} +8 -8
  112. package/dist/{remove-background-LOLKFKLT.js → remove-background-KT2SWYCC.js} +7 -7
  113. package/dist/{render-I65IU7RM.js → render-4C3YKVZI.js} +3 -3
  114. package/dist/{render-ULQLUIWD.js → render-7GDLEGXT.js} +12 -12
  115. package/dist/{render-EXSRG666.js → render-Y2Y7N5WD.js} +21 -21
  116. package/dist/{render-batch-RSNCREPB.js → render-batch-AFDBPDMM.js} +3 -3
  117. package/dist/{renderCancellation-2WB4SGXR.js → renderCancellation-RQ3UVNVJ.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-PR3I4KGM.js} +7 -7
  121. package/dist/{skills-CPS5E5YP.js → skills-BEYZZWY6.js} +11 -11
  122. package/dist/{skillsUpdateCheck-3IT5CJWU.js → skillsUpdateCheck-3USXDPWV.js} +3 -3
  123. package/dist/{snapshot-MSGC7HTA.js → snapshot-S77MQOV2.js} +25 -24
  124. package/dist/{src-SV7CNZXN.js → src-6R4DC2YI.js} +5 -5
  125. package/dist/{src-EZBJT4VH.js → src-IOJXK4VC.js} +7 -7
  126. package/dist/{staticProjectServer-5JAGMYT7.js → staticProjectServer-RXAFGDK7.js} +14 -13
  127. package/dist/{status-4W3EJYEH.js → status-CRS7H6FA.js} +6 -6
  128. package/dist/studio/assets/{hyperframes-player-h4mktSdm.js → hyperframes-player-BakGew45.js} +1 -1
  129. package/dist/studio/assets/{index-D8GKc2D9.js → index-C5dyUHwk.js} +1 -1
  130. package/dist/studio/assets/{index-CM1xA_jD.js → index-C842tcVa.js} +165 -165
  131. package/dist/studio/assets/{index-zoqPmBF6.js → index-CPe7d_wO.js} +1 -1
  132. package/dist/studio/assets/{index-lrT-gfHa.css → index-ncVJ5IVu.css} +1 -1
  133. package/dist/studio/chunk-6LUNLTHR.js +611 -0
  134. package/dist/studio/chunk-6LUNLTHR.js.map +1 -0
  135. package/dist/studio/{chunk-4OZC426H.js → chunk-AOWVEVEU.js} +3 -503
  136. package/dist/studio/chunk-AOWVEVEU.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/{domEditingLayers-AQBDFNUC.js → domEditingLayers-JQPQGGT7.js} +3 -3
  140. package/dist/studio/index.d.ts +11 -2
  141. package/dist/studio/index.html +2 -2
  142. package/dist/studio/index.js +12416 -11195
  143. package/dist/studio/index.js.map +1 -1
  144. package/dist/studio/player.js +2 -2
  145. package/dist/studio/styles.css +1 -1
  146. package/dist/{studio-api-L2TJ2JTK.js → studio-api-3FH7OSSS.js} +8 -7
  147. package/dist/{studioServer-UV7YRYWN.js → studioServer-4E5RRPU7.js} +44 -38
  148. package/dist/{system-OL4MXRPK.js → system-YMANAUAM.js} +6 -6
  149. package/dist/{telemetry-WRUU725E.js → telemetry-46XSB2VN.js} +9 -9
  150. package/dist/{timeline-P43A53BW.js → timeline-XKLKHGD2.js} +15 -14
  151. package/dist/{tokens-NUHTJM2V.js → tokens-JCZKPUPT.js} +2 -2
  152. package/dist/{transcribe-FSVNIFGF.js → transcribe-FIFJ3BBB.js} +12 -12
  153. package/dist/{transcribe-TRLZJK73.js → transcribe-FQ4YC2AS.js} +7 -7
  154. package/dist/{updateCheck-33TAG46I.js → updateCheck-PDIH4GZW.js} +2 -2
  155. package/dist/{upgrade-AJ6QBTH2.js → upgrade-TTGO6JCF.js} +2 -2
  156. package/dist/{urlDownloader-MP6GJIDV.js → urlDownloader-32JRHOPN.js} +5 -5
  157. package/dist/{validate-VL3A6VTJ.js → validate-G5F56ONL.js} +15 -15
  158. package/dist/{video-KO7VT63P.js → video-SGBEMEKA.js} +6 -6
  159. package/package.json +1 -1
  160. package/dist/chunk-PMLZPNIB.js +0 -3431
  161. package/dist/studio/chunk-46XP7J3B.js.map +0 -1
  162. package/dist/studio/chunk-4OZC426H.js.map +0 -1
  163. package/dist/studio/chunk-DI4LCJHL.js +0 -102
  164. package/dist/studio/chunk-DI4LCJHL.js.map +0 -1
  165. /package/dist/studio/{domEditingLayers-AQBDFNUC.js.map → domEditingLayers-JQPQGGT7.js.map} +0 -0
@@ -15,13 +15,20 @@ import {
15
15
  splitElementInHtml,
16
16
  unwrapElementsFromHtml,
17
17
  wrapElementsInHtml
18
- } from "./chunk-YY5J75H5.js";
18
+ } from "./chunk-NJLFRKHX.js";
19
19
  import {
20
20
  atomicTempPath,
21
21
  createFileAtomically,
22
22
  isAtomicTempPath,
23
23
  replaceFileAtomically
24
24
  } from "./chunk-WEATBQTR.js";
25
+ import {
26
+ DEFAULT_TARGET_LUFS,
27
+ absoluteLoudnessPlan,
28
+ measureAudio,
29
+ requiredFfmpeg,
30
+ resolveLocalAudioPath
31
+ } from "./chunk-5ON5GH3X.js";
25
32
  import {
26
33
  PROXY_PARAMS_VERSION,
27
34
  PROXY_PENDING_RETRY_AFTER_SECONDS,
@@ -47,7 +54,7 @@ import {
47
54
  shouldPrewarmProxy,
48
55
  waitForProxy,
49
56
  walkDir
50
- } from "./chunk-PU2IOWX4.js";
57
+ } from "./chunk-OVRFBUXU.js";
51
58
  import {
52
59
  findTimelineScript,
53
60
  parseGsapScriptAcorn
@@ -66,7 +73,7 @@ import {
66
73
  formatRenderOutputTimestamp,
67
74
  parseFps,
68
75
  sameInstant
69
- } from "./chunk-VMDEGITC.js";
76
+ } from "./chunk-B25H4M4V.js";
70
77
  import {
71
78
  MagicString,
72
79
  VALID_CANVAS_RESOLUTIONS,
@@ -75,15 +82,17 @@ import {
75
82
  import {
76
83
  addScenePartsManifest,
77
84
  ensureHfIds
78
- } from "./chunk-R4IB642V.js";
85
+ } from "./chunk-X763EUBO.js";
79
86
  import {
80
87
  findStartTags,
81
88
  injectScriptsIntoHtml,
82
89
  injectTagsAtHeadStart,
83
90
  insertBeforeCloseTag,
84
91
  isFullHtmlDocument,
92
+ resolveRateSpec,
93
+ sourceTimeAt,
85
94
  stripEmbeddedRuntimeScripts
86
- } from "./chunk-PMLZPNIB.js";
95
+ } from "./chunk-YLVI7VL5.js";
87
96
  import {
88
97
  ProjectRootMissingError,
89
98
  folderGone,
@@ -96,12 +105,13 @@ import {
96
105
  import {
97
106
  isWithinProjectRoot,
98
107
  readProjectFile
99
- } from "./chunk-QM2NUN2B.js";
108
+ } from "./chunk-ONY5CJXS.js";
100
109
  import {
101
110
  FFPROBE_PATH_ENV,
102
111
  findFfBinary
103
112
  } from "./chunk-PJ3HDUCY.js";
104
113
  import {
114
+ readMediaOffsetSeconds,
105
115
  rewriteAssetPaths,
106
116
  rewriteCssAssetUrls,
107
117
  rewriteInlineStyleAssetUrls
@@ -5106,16 +5116,25 @@ import { createHash as createHash32, randomUUID as randomUUID22 } from "crypto";
5106
5116
  import { readFileSync as readFileSync8 } from "fs";
5107
5117
  import { existsSync as existsSync7, readFileSync as readFileSync10, statSync as statSync7 } from "fs";
5108
5118
  import { join as join12, resolve as resolve52 } from "path";
5119
+ import { execFile } from "child_process";
5120
+ import { existsSync as existsSync8, readFileSync as readFileSync11, statSync as statSync8 } from "fs";
5121
+ import { dirname as dirname52, join as join13, relative as relative4, sep as sep22 } from "path";
5122
+ import { createHash as createHash42, randomBytes } from "crypto";
5123
+ import { existsSync as existsSync9 } from "fs";
5124
+ import { existsSync as existsSync10, readFileSync as readFileSync12, statSync as statSync9 } from "fs";
5125
+ import { join as join14, resolve as resolve6 } from "path";
5126
+ import { execFile as execFile2, spawn as spawn2 } from "child_process";
5127
+ import { promisify } from "util";
5109
5128
  import { closeSync as closeSync32, constants as constants3, fstatSync as fstatSync3, openSync as openSync32, readSync } from "fs";
5110
5129
  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";
5130
+ import { existsSync as existsSync11 } from "fs";
5131
+ import { basename as basename22, dirname as dirname6, extname as extname2, join as join15, posix } from "path";
5132
+ import { existsSync as existsSync12, readFileSync as readFileSync13 } from "fs";
5114
5133
  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";
5134
+ import { join as join16 } from "path";
5135
+ import { createHash as createHash5 } from "crypto";
5136
+ import { readFileSync as readFileSync14, readdirSync as readdirSync5, rmSync as rmSync5 } from "fs";
5137
+ import { join as join17 } from "path";
5119
5138
  async function filterCompositionFiles(projectDir, files) {
5120
5139
  const htmlFiles = files.filter((f) => f.endsWith(".html") && !isInHiddenOrVendorDir(f));
5121
5140
  const checks = await Promise.all(
@@ -6152,7 +6171,7 @@ function bakeGroupTransformIntoMembers(html, groupId, members, groupCenter) {
6152
6171
  const rad = grot * Math.PI / 180;
6153
6172
  const cos = Math.cos(rad);
6154
6173
  const sin = Math.sin(rad);
6155
- const round3 = (n) => Math.round(n * 1e3) / 1e3;
6174
+ const round32 = (n) => Math.round(n * 1e3) / 1e3;
6156
6175
  let script = block.scriptText;
6157
6176
  for (const m of members) {
6158
6177
  const memberSel = `#${m.id}`;
@@ -6169,27 +6188,27 @@ function bakeGroupTransformIntoMembers(html, groupId, members, groupCenter) {
6169
6188
  const visY = groupCenter.cy + gscale * (sin * dx + cos * dy) + gy;
6170
6189
  const newProps = {
6171
6190
  ...mProps,
6172
- x: round3(visX - m.cx),
6173
- y: round3(visY - m.cy)
6191
+ x: round32(visX - m.cx),
6192
+ y: round32(visY - m.cy)
6174
6193
  };
6175
6194
  if (gz !== 0) newProps.z = (typeof mProps.z === "number" ? mProps.z : 0) + gz;
6176
6195
  if (grot !== 0) {
6177
- newProps.rotation = round3(
6196
+ newProps.rotation = round32(
6178
6197
  (typeof mProps.rotation === "number" ? mProps.rotation : 0) + grot
6179
6198
  );
6180
6199
  }
6181
6200
  if (gscale !== 1) {
6182
- newProps.scale = round3((typeof mProps.scale === "number" ? mProps.scale : 1) * gscale);
6201
+ newProps.scale = round32((typeof mProps.scale === "number" ? mProps.scale : 1) * gscale);
6183
6202
  }
6184
6203
  const pivoted = /* @__PURE__ */ new Set(["x", "y", "z", "rotation", "scale"]);
6185
6204
  for (const [k, v] of Object.entries(gt)) {
6186
6205
  if (pivoted.has(k) || typeof v !== "number") continue;
6187
6206
  if (k === "scaleX" || k === "scaleY") {
6188
- if (v !== 1) newProps[k] = round3((typeof mProps[k] === "number" ? mProps[k] : 1) * v);
6207
+ if (v !== 1) newProps[k] = round32((typeof mProps[k] === "number" ? mProps[k] : 1) * v);
6189
6208
  } else if (k === "transformPerspective") {
6190
6209
  if (typeof mProps[k] !== "number") newProps[k] = v;
6191
6210
  } else if (v !== 0) {
6192
- newProps[k] = round3((typeof mProps[k] === "number" ? mProps[k] : 0) + v);
6211
+ newProps[k] = round32((typeof mProps[k] === "number" ? mProps[k] : 0) + v);
6193
6212
  }
6194
6213
  }
6195
6214
  for (const s of [...sets].reverse()) {
@@ -6360,7 +6379,7 @@ async function prepareGsapMutationScript(c, res, firstMutation) {
6360
6379
  let block = extractGsapScriptBlock(html);
6361
6380
  if (!block && (firstMutation.type === "add" || firstMutation.type === "add-with-keyframes")) {
6362
6381
  const compId = html.match(/data-composition-id="([^"]+)"/)?.[1] ?? "main";
6363
- const { GSAP_CDN } = await import("./dist-HSEMXPG3.js");
6382
+ const { GSAP_CDN } = await import("./dist-YUIPB7OX.js");
6364
6383
  const bootstrap = [
6365
6384
  `<script src="${GSAP_CDN}"></script>`,
6366
6385
  "<script>",
@@ -8944,7 +8963,8 @@ function registerRenderRoutes(api, adapter) {
8944
8963
  progress: current.progress,
8945
8964
  status: current.status,
8946
8965
  stage: current.stage,
8947
- error: current.error
8966
+ error: current.error,
8967
+ ...current.status === "complete" && current.audioLoweredDb !== void 0 ? { audioLoweredDb: current.audioLoweredDb } : {}
8948
8968
  })
8949
8969
  });
8950
8970
  if (current.status !== "rendering") break;
@@ -9053,11 +9073,13 @@ function registerRenderRoutes(api, adapter) {
9053
9073
  const metaPath = join10(rendersDir, `${rid}.meta.json`);
9054
9074
  let status = "complete";
9055
9075
  let durationMs;
9076
+ let audioLoweredDb;
9056
9077
  if (existsSync6(metaPath)) {
9057
9078
  try {
9058
9079
  const meta = JSON.parse(readFileSync72(metaPath, "utf-8"));
9059
9080
  if (meta.status === "failed" && !existsSync6(fp)) status = "failed";
9060
9081
  if (meta.durationMs) durationMs = meta.durationMs;
9082
+ if (typeof meta.audioLoweredDb === "number") audioLoweredDb = meta.audioLoweredDb;
9061
9083
  } catch {
9062
9084
  }
9063
9085
  }
@@ -9067,7 +9089,8 @@ function registerRenderRoutes(api, adapter) {
9067
9089
  size: stat22.size,
9068
9090
  createdAt: stat22.mtimeMs,
9069
9091
  status,
9070
- durationMs
9092
+ durationMs,
9093
+ ...audioLoweredDb !== void 0 ? { audioLoweredDb } : {}
9071
9094
  };
9072
9095
  }).sort((a, b) => b.createdAt - a.createdAt);
9073
9096
  for (const file of files) {
@@ -9100,10 +9123,10 @@ var ThumbnailGenerationCoordinator = class {
9100
9123
  if (signal.aborted) return Promise.reject(new DOMException("Aborted", "AbortError"));
9101
9124
  let entry = this.entries.get(key);
9102
9125
  if (!entry) {
9103
- let resolve6;
9126
+ let resolve7;
9104
9127
  let reject;
9105
9128
  const promise = new Promise((resolvePromise, rejectPromise) => {
9106
- resolve6 = resolvePromise;
9129
+ resolve7 = resolvePromise;
9107
9130
  reject = rejectPromise;
9108
9131
  });
9109
9132
  entry = {
@@ -9113,7 +9136,7 @@ var ThumbnailGenerationCoordinator = class {
9113
9136
  state: "queued",
9114
9137
  work,
9115
9138
  promise,
9116
- resolve: resolve6,
9139
+ resolve: resolve7,
9117
9140
  reject
9118
9141
  };
9119
9142
  this.entries.set(key, entry);
@@ -9127,7 +9150,7 @@ var ThumbnailGenerationCoordinator = class {
9127
9150
  return /* @__PURE__ */ new Set([...this.entries.keys(), ...[...this.activeEntries].map((entry) => entry.key)]);
9128
9151
  }
9129
9152
  lease(entry, signal) {
9130
- return new Promise((resolve6, reject) => {
9153
+ return new Promise((resolve7, reject) => {
9131
9154
  let released = false;
9132
9155
  const release = () => {
9133
9156
  if (released) return;
@@ -9151,7 +9174,7 @@ var ThumbnailGenerationCoordinator = class {
9151
9174
  entry.promise.then(
9152
9175
  (value) => {
9153
9176
  release();
9154
- resolve6(value);
9177
+ resolve7(value);
9155
9178
  },
9156
9179
  (reason) => {
9157
9180
  release();
@@ -9226,14 +9249,14 @@ async function generateThumbnail(path, signal) {
9226
9249
  async function resolveSource(adapter, id, url) {
9227
9250
  const project = await adapter.resolveProject(id);
9228
9251
  if (!project) throw new HTTPException(404);
9229
- let relative4;
9252
+ let relative5;
9230
9253
  try {
9231
- relative4 = requestSubPath(url, "projects/:id/image-thumbnail");
9254
+ relative5 = requestSubPath(url, "projects/:id/image-thumbnail");
9232
9255
  } catch {
9233
9256
  throw new HTTPException(400);
9234
9257
  }
9235
- if (!/\.jpe?g$/i.test(relative4)) throw new HTTPException(415);
9236
- const path = pinWithinProject(project.dir, relative4);
9258
+ if (!/\.jpe?g$/i.test(relative5)) throw new HTTPException(415);
9259
+ const path = pinWithinProject(project.dir, relative5);
9237
9260
  if (!path) throw new HTTPException(404);
9238
9261
  const info = await stat2(path).catch(() => {
9239
9262
  throw new HTTPException(404);
@@ -9574,6 +9597,493 @@ function registerWaveformRoutes(api, adapter) {
9574
9597
  return c.json({ peaks });
9575
9598
  });
9576
9599
  }
9600
+ var FREEZE_HOLD_SECONDS = 2;
9601
+ var LINK_ATTR = "data-link";
9602
+ var EPSILON = 1e-3;
9603
+ var round3 = (value) => Math.round(value * 1e3) / 1e3;
9604
+ function numberAttr(el, name) {
9605
+ const raw = el.getAttribute(name);
9606
+ if (raw === null || raw.trim() === "") return null;
9607
+ const value = Number(raw);
9608
+ return Number.isFinite(value) ? value : null;
9609
+ }
9610
+ function trackOf(el) {
9611
+ return numberAttr(el, "data-track-index") ?? numberAttr(el, "data-layer") ?? 0;
9612
+ }
9613
+ function freezeFrameMediaTime(input) {
9614
+ const rate = resolveRateSpec(input.automation, input.playbackRate);
9615
+ return input.mediaStart + sourceTimeAt(rate, Math.max(0, input.playhead - input.clipStart));
9616
+ }
9617
+ function freezeExtractArgs(src, mediaTime, output) {
9618
+ return ["-n", "-ss", String(round3(mediaTime)), "-i", src, "-frames:v", "1", output];
9619
+ }
9620
+ var randomStillToken = () => randomBytes(4).toString("hex");
9621
+ function freezeStillFileName(clipId, playhead, token = randomStillToken()) {
9622
+ const stem = clipId.replace(/[^A-Za-z0-9_-]/g, "_").slice(0, 48) || "clip";
9623
+ const idHash = createHash42("sha256").update(clipId).digest("hex").slice(0, 10);
9624
+ return `${stem}-${idHash}-${Math.round(playhead * 1e3)}-${token}.png`;
9625
+ }
9626
+ function readFreezeSource(html, target, playhead) {
9627
+ const { document: document2 } = parseSourceDocument(html);
9628
+ const el = findTargetElement(document2, target);
9629
+ if (!el || el.tagName.toLowerCase() !== "video") return null;
9630
+ const src = el.getAttribute("src");
9631
+ const start = numberAttr(el, "data-start");
9632
+ const duration = numberAttr(el, "data-duration");
9633
+ if (!src || start === null || duration === null) return null;
9634
+ if (playhead <= start + EPSILON || playhead >= start + duration - EPSILON) return null;
9635
+ return {
9636
+ id: el.getAttribute("id") || "clip",
9637
+ src,
9638
+ mediaTime: freezeFrameMediaTime({
9639
+ clipStart: start,
9640
+ playhead,
9641
+ mediaStart: readMediaOffsetSeconds((name) => el.getAttribute(name)),
9642
+ playbackRate: numberAttr(el, "data-playback-rate") ?? 1,
9643
+ automation: el.getAttribute("data-automation")
9644
+ })
9645
+ };
9646
+ }
9647
+ function targetOf(el) {
9648
+ const hfId = el.getAttribute("data-hf-id");
9649
+ return { ...hfId ? { hfId } : {}, id: el.getAttribute("id") };
9650
+ }
9651
+ function spans(el, time) {
9652
+ const start = numberAttr(el, "data-start");
9653
+ const duration = numberAttr(el, "data-duration");
9654
+ if (start === null || duration === null) return false;
9655
+ return time > start + EPSILON && time < start + duration - EPSILON;
9656
+ }
9657
+ function uniqueId(document2, base) {
9658
+ let id = base;
9659
+ for (let n = 2; document2.getElementById(id); n++) id = `${base}-${n}`;
9660
+ return id;
9661
+ }
9662
+ function shiftTracks(document2, tracks, from, by) {
9663
+ for (const el of document2.querySelectorAll("[data-start]")) {
9664
+ if (el.hasAttribute("data-composition-id") || !tracks.has(trackOf(el))) continue;
9665
+ const start = numberAttr(el, "data-start");
9666
+ if (start === null || start < from - EPSILON) continue;
9667
+ el.setAttribute("data-start", String(round3(start + by)));
9668
+ }
9669
+ }
9670
+ function growRootToFit(document2) {
9671
+ const root = document2.querySelector("[data-composition-id]");
9672
+ const rootDuration = root ? numberAttr(root, "data-duration") : null;
9673
+ if (!root || rootDuration === null) return;
9674
+ let end = rootDuration;
9675
+ for (const el of root.querySelectorAll("[data-start][data-duration]")) {
9676
+ const start = numberAttr(el, "data-start");
9677
+ const duration = numberAttr(el, "data-duration");
9678
+ if (start !== null && duration !== null) end = Math.max(end, start + duration);
9679
+ }
9680
+ if (end > rootDuration + EPSILON) root.setAttribute("data-duration", String(round3(end)));
9681
+ }
9682
+ var COPIED_PICTURE_ATTRS = ["class", "style", "data-color-grading"];
9683
+ function buildStill(document2, video, input) {
9684
+ const img = document2.createElement("img");
9685
+ img.setAttribute("id", input.id);
9686
+ for (const name of COPIED_PICTURE_ATTRS) {
9687
+ const value = video.getAttribute(name);
9688
+ if (value !== null) img.setAttribute(name, value);
9689
+ }
9690
+ if (!img.classList.contains("clip")) img.classList.add("clip");
9691
+ img.setAttribute("src", input.imageSrc);
9692
+ img.setAttribute("alt", "");
9693
+ img.setAttribute("data-start", String(round3(input.playhead)));
9694
+ img.setAttribute("data-duration", String(input.holdSeconds));
9695
+ img.setAttribute("data-track-index", String(trackOf(video)));
9696
+ img.setAttribute("data-timeline-label", "Freeze");
9697
+ return img;
9698
+ }
9699
+ function linkedPartners(document2, video) {
9700
+ const link = video.getAttribute(LINK_ATTR);
9701
+ if (!link) return [];
9702
+ return Array.from(document2.querySelectorAll(`[${LINK_ATTR}]`)).filter(
9703
+ (el) => el !== video && el.getAttribute(LINK_ATTR) === link
9704
+ );
9705
+ }
9706
+ function splitAll(html, elements, time) {
9707
+ const rightHalfIds = [];
9708
+ let next = html;
9709
+ for (const el of elements) {
9710
+ const split = splitElementInHtml(
9711
+ next,
9712
+ targetOf(el),
9713
+ time,
9714
+ `${el.getAttribute("id") || "clip"}-split`,
9715
+ {
9716
+ start: numberAttr(el, "data-start") ?? 0,
9717
+ duration: numberAttr(el, "data-duration") ?? 0,
9718
+ track: trackOf(el)
9719
+ }
9720
+ );
9721
+ if (!split.matched || !split.newId) return null;
9722
+ next = split.html;
9723
+ rightHalfIds.push(split.newId);
9724
+ }
9725
+ return { html: next, rightHalfIds };
9726
+ }
9727
+ function relinkRightHalves(document2, link, ids) {
9728
+ if (!link) return;
9729
+ const rightLink = uniqueLink(document2, link);
9730
+ for (const id of ids) document2.getElementById(id)?.setAttribute(LINK_ATTR, rightLink);
9731
+ }
9732
+ function applyFreezeFrameToHtml(html, input) {
9733
+ const hold = input.holdSeconds ?? FREEZE_HOLD_SECONDS;
9734
+ const { document: before } = parseSourceDocument(html);
9735
+ const video = findTargetElement(before, input.target);
9736
+ if (!video || !isHTMLElement(video) || !spans(video, input.playhead)) return null;
9737
+ const partners = linkedPartners(before, video);
9738
+ const tracks = /* @__PURE__ */ new Set([trackOf(video), ...partners.map(trackOf)]);
9739
+ const cut = splitAll(
9740
+ html,
9741
+ [video, ...partners.filter((el) => spans(el, input.playhead))],
9742
+ input.playhead
9743
+ );
9744
+ if (!cut) return null;
9745
+ const { document: document2, wrappedFragment } = parseSourceDocument(cut.html);
9746
+ relinkRightHalves(document2, video.getAttribute(LINK_ATTR), cut.rightHalfIds);
9747
+ shiftTracks(document2, tracks, input.playhead, hold);
9748
+ const leftHalf = findTargetElement(document2, input.target);
9749
+ if (!leftHalf?.parentElement) return null;
9750
+ const freezeId = uniqueId(document2, `${video.getAttribute("id") || "clip"}-freeze`);
9751
+ const still = buildStill(document2, leftHalf, {
9752
+ id: freezeId,
9753
+ imageSrc: input.imageSrc,
9754
+ playhead: input.playhead,
9755
+ holdSeconds: hold
9756
+ });
9757
+ leftHalf.parentElement.insertBefore(still, leftHalf.nextSibling);
9758
+ growRootToFit(document2);
9759
+ return {
9760
+ html: ensureHfIds(wrappedFragment ? document2.body.innerHTML || "" : document2.toString()),
9761
+ freezeId
9762
+ };
9763
+ }
9764
+ function uniqueLink(document2, base) {
9765
+ const taken = new Set(
9766
+ Array.from(document2.querySelectorAll(`[${LINK_ATTR}]`)).map((el) => el.getAttribute(LINK_ATTR))
9767
+ );
9768
+ let candidate = `${base}-2`;
9769
+ for (let n = 3; taken.has(candidate); n++) candidate = `${base}-${n}`;
9770
+ return candidate;
9771
+ }
9772
+ var EXTRACT_TIMEOUT_MS = 3e4;
9773
+ var ffmpegExtractor = (args) => {
9774
+ const ffmpeg = findFfBinary("ffmpeg", { configuredMustExist: true });
9775
+ if (!ffmpeg) return Promise.resolve({ ok: false, error: "ffmpeg not found" });
9776
+ return new Promise((resolvePromise) => {
9777
+ execFile(
9778
+ ffmpeg,
9779
+ args,
9780
+ { timeout: EXTRACT_TIMEOUT_MS, windowsHide: true },
9781
+ (error, _o, stderr) => resolvePromise(
9782
+ error ? { ok: false, error: String(stderr || error.message) } : { ok: true }
9783
+ )
9784
+ );
9785
+ });
9786
+ };
9787
+ function isFreezeFrameRequest(value) {
9788
+ if (typeof value !== "object" || value === null) return false;
9789
+ const body = value;
9790
+ 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);
9791
+ }
9792
+ var FREEZE_DIR = ["assets", "freeze"];
9793
+ function readExpected(absPath, expectedVersion) {
9794
+ let content;
9795
+ try {
9796
+ content = readFileSync11(absPath, "utf-8");
9797
+ } catch {
9798
+ return { error: "not found", status: 404 };
9799
+ }
9800
+ return fileContentVersion(content) === expectedVersion ? { content } : { error: "file conflict", status: 409 };
9801
+ }
9802
+ async function extractStill(projectDir, absPath, source, playhead, tools) {
9803
+ const fileDir = dirname52(absPath);
9804
+ const mediaPath = pinWithinProject(projectDir, relative4(projectDir, join13(fileDir, source.src)));
9805
+ if (!mediaPath) return { error: `forbidden media path: ${source.src}`, status: 403 };
9806
+ const freezeDir = join13(projectDir, ...FREEZE_DIR);
9807
+ const fileName = freezeStillFileName(source.id, playhead, tools.stillToken());
9808
+ mkdirWithinProject(projectDir, freezeDir);
9809
+ const imagePath = pinWithinProject(projectDir, join13(...FREEZE_DIR, fileName));
9810
+ if (!imagePath || dirname52(imagePath) !== freezeDir) {
9811
+ return { error: `forbidden freeze path: ${fileName}`, status: 403 };
9812
+ }
9813
+ if (existsSync8(imagePath))
9814
+ return { error: `freeze still already exists: ${fileName}`, status: 409 };
9815
+ const extracted = await tools.extract(freezeExtractArgs(mediaPath, source.mediaTime, imagePath));
9816
+ if (!extracted.ok) {
9817
+ return {
9818
+ error: `Could not extract the frame: ${extracted.error ?? "ffmpeg failed"}`,
9819
+ status: 500
9820
+ };
9821
+ }
9822
+ const depth = relative4(projectDir, fileDir).split(sep22).filter(Boolean).length;
9823
+ return { imageSrc: `${"../".repeat(depth)}${FREEZE_DIR.join("/")}/${fileName}` };
9824
+ }
9825
+ function writeFolded(projectDir, absPath, path, before, after, options) {
9826
+ if (readFileSync11(absPath, "utf-8") !== before) {
9827
+ return { error: `file conflict: ${path}`, status: 409 };
9828
+ }
9829
+ const backup = snapshotBeforeWrite(projectDir, absPath);
9830
+ if (backup.error) return { error: `Failed to create backup: ${backup.error}`, status: 500 };
9831
+ const writeToken = createWriteToken(options.token);
9832
+ replaceFileAtomically(absPath, after, statSync8(absPath).mode);
9833
+ const version = fileContentVersion(after);
9834
+ recordFileWriteReceipt(absPath, { path, version, writeToken, overwrote: before });
9835
+ return { version, writeToken, backupPath: backupPathForResponse(projectDir, backup.backupPath) };
9836
+ }
9837
+ function registerFreezeFrameRoutes(api, adapter, extract = ffmpegExtractor, stillToken = randomStillToken) {
9838
+ api.post("/projects/:id/file-mutations/freeze-frame", async (c) => {
9839
+ const body = await c.req.json().catch(() => null);
9840
+ if (!isFreezeFrameRequest(body)) {
9841
+ return c.json({ error: "path, expectedVersion, target and playhead required" }, 400);
9842
+ }
9843
+ const project = await adapter.resolveProject(c.req.param("id"));
9844
+ if (!project) return c.json({ error: "not found" }, 404);
9845
+ const absPath = pinWithinProject(project.dir, body.path);
9846
+ if (!absPath) return c.json({ error: `forbidden path: ${body.path}` }, 403);
9847
+ const read = readExpected(absPath, body.expectedVersion);
9848
+ if ("error" in read) return c.json({ error: `${read.error}: ${body.path}` }, read.status);
9849
+ const before = read.content;
9850
+ const source = readFreezeSource(before, body.target, body.playhead);
9851
+ if (!source) return c.json({ error: "Move the playhead inside a video clip to freeze" }, 400);
9852
+ const still = await extractStill(project.dir, absPath, source, body.playhead, {
9853
+ extract,
9854
+ stillToken
9855
+ });
9856
+ if ("error" in still) return c.json({ error: still.error }, still.status);
9857
+ const { imageSrc } = still;
9858
+ const folded = applyFreezeFrameToHtml(before, {
9859
+ target: body.target,
9860
+ playhead: body.playhead,
9861
+ imageSrc
9862
+ });
9863
+ if (!folded) return c.json({ error: "Freeze target was not found in the file" }, 400);
9864
+ const written = writeFolded(project.dir, absPath, body.path, before, folded.html, {
9865
+ token: body.transactionToken ?? c.req.header("X-Hyperframes-Write-Token")
9866
+ });
9867
+ if ("error" in written) return c.json({ error: written.error }, written.status);
9868
+ const { version, writeToken, backupPath } = written;
9869
+ return c.json({
9870
+ ok: true,
9871
+ path: body.path,
9872
+ before,
9873
+ after: folded.html,
9874
+ version,
9875
+ writeToken,
9876
+ backupPath,
9877
+ freezeId: folded.freezeId,
9878
+ imageSrc
9879
+ });
9880
+ });
9881
+ }
9882
+ var defaultMeasure = (file, window2) => measureAudio(requiredFfmpeg(), file, window2);
9883
+ var InvalidField = class extends Error {
9884
+ };
9885
+ function numberField(body, name, fallback, valid) {
9886
+ const raw = body[name];
9887
+ if (raw === void 0 || raw === null) return fallback;
9888
+ if (typeof raw === "number" && Number.isFinite(raw) && valid(raw)) return raw;
9889
+ throw new InvalidField(`${name} is out of range`);
9890
+ }
9891
+ function readNormalizeRequest(body) {
9892
+ const src = typeof body.src === "string" ? body.src.trim() : "";
9893
+ if (!src) throw new InvalidField("src is required");
9894
+ const duration = numberField(body, "duration", 0, (v) => v > 0);
9895
+ return {
9896
+ id: typeof body.id === "string" && body.id ? body.id : src,
9897
+ src,
9898
+ window: {
9899
+ mediaStart: numberField(body, "mediaStart", 0, (v) => v >= 0),
9900
+ duration: duration > 0 ? duration : null,
9901
+ playbackRate: numberField(body, "playbackRate", 1, (v) => v > 0)
9902
+ },
9903
+ volume: numberField(body, "volume", 1, (v) => v >= 0),
9904
+ targetLufs: numberField(body, "targetLufs", DEFAULT_TARGET_LUFS, (v) => v < 0)
9905
+ };
9906
+ }
9907
+ function parseNormalizeRequest(body) {
9908
+ try {
9909
+ return readNormalizeRequest(body);
9910
+ } catch (error) {
9911
+ if (error instanceof InvalidField) return error.message;
9912
+ throw error;
9913
+ }
9914
+ }
9915
+ function isRecord(value) {
9916
+ return typeof value === "object" && value !== null && !Array.isArray(value);
9917
+ }
9918
+ function registerLoudnessRoutes(api, adapter, measure = defaultMeasure) {
9919
+ api.post("/projects/:id/loudness/normalize", async (c) => {
9920
+ const project = await adapter.resolveProject(c.req.param("id"));
9921
+ if (!project) return c.json({ error: "not found" }, 404);
9922
+ const body = await c.req.json().catch(() => null);
9923
+ if (!isRecord(body)) return c.json({ error: "invalid body" }, 400);
9924
+ const request = parseNormalizeRequest(body);
9925
+ if (typeof request === "string") return c.json({ error: request }, 400);
9926
+ let file;
9927
+ try {
9928
+ file = resolveLocalAudioPath(project.dir, request.src);
9929
+ } catch (error) {
9930
+ return c.json({ error: error instanceof Error ? error.message : String(error) }, 400);
9931
+ }
9932
+ if (!existsSync9(file)) return c.json({ error: "file not found" }, 404);
9933
+ try {
9934
+ const measurement = await measure(file, request.window);
9935
+ const plan = absoluteLoudnessPlan(
9936
+ { id: request.id, volume: request.volume, ...measurement },
9937
+ request.targetLufs
9938
+ );
9939
+ return c.json({ plan });
9940
+ } catch (error) {
9941
+ return c.json({ error: error instanceof Error ? error.message : String(error) }, 422);
9942
+ }
9943
+ });
9944
+ }
9945
+ var PEAK_BIN_SECONDS = 0.05;
9946
+ var PEAK_SAMPLE_RATE = 48e3;
9947
+ var execFileAsync = promisify(execFile2);
9948
+ var PEAK_MAP_CACHE_VERSION = "peaks-v2";
9949
+ function createPeakAccumulator(sampleRate = PEAK_SAMPLE_RATE, binSeconds = PEAK_BIN_SECONDS, channels = 2) {
9950
+ return {
9951
+ bins: [],
9952
+ frame: 0,
9953
+ carry: Buffer.alloc(0),
9954
+ framesPerBin: Math.max(1, Math.round(sampleRate * binSeconds)),
9955
+ channels: Math.max(1, channels)
9956
+ };
9957
+ }
9958
+ function framePeak(data, offset, channels) {
9959
+ let peak = 0;
9960
+ for (let ch = 0; ch < channels; ch++) {
9961
+ peak = Math.max(peak, Math.abs(data.readFloatLE(offset + ch * 4)));
9962
+ }
9963
+ return peak;
9964
+ }
9965
+ function accumulatePeaks(acc, chunk) {
9966
+ const data = acc.carry.length > 0 ? Buffer.concat([acc.carry, chunk]) : chunk;
9967
+ const frameBytes = 4 * acc.channels;
9968
+ const whole = data.length - data.length % frameBytes;
9969
+ for (let offset = 0; offset < whole; offset += frameBytes) {
9970
+ const peak = framePeak(data, offset, acc.channels);
9971
+ const bin = Math.floor(acc.frame / acc.framesPerBin);
9972
+ if (acc.bins.length <= bin) acc.bins.push(0);
9973
+ if (peak > (acc.bins[bin] ?? 0)) acc.bins[bin] = peak;
9974
+ acc.frame += 1;
9975
+ }
9976
+ acc.carry = Buffer.from(data.subarray(whole));
9977
+ }
9978
+ function finishPeaks(acc) {
9979
+ return acc.bins.map((peak) => Number(peak.toFixed(4)));
9980
+ }
9981
+ async function probeChannels(mediaPath) {
9982
+ const { stdout } = await execFileAsync(
9983
+ findFfBinary("ffprobe") ?? "ffprobe",
9984
+ [
9985
+ "-v",
9986
+ "error",
9987
+ "-select_streams",
9988
+ "a:0",
9989
+ "-show_entries",
9990
+ "stream=channels",
9991
+ "-of",
9992
+ "csv=p=0",
9993
+ "--",
9994
+ mediaPath
9995
+ ],
9996
+ { encoding: "utf8", timeout: 3e4, windowsHide: true }
9997
+ );
9998
+ const channels = Number.parseInt(stdout.trim(), 10);
9999
+ if (!Number.isFinite(channels) || channels < 1) throw new Error("no audio stream");
10000
+ return channels;
10001
+ }
10002
+ async function decodePeakMap(mediaPath) {
10003
+ const channels = await probeChannels(mediaPath);
10004
+ return new Promise((resolvePromise, reject) => {
10005
+ const proc = spawn2(
10006
+ findFfBinary("ffmpeg") ?? "ffmpeg",
10007
+ [
10008
+ "-v",
10009
+ "error",
10010
+ "-i",
10011
+ mediaPath,
10012
+ "-map",
10013
+ "0:a:0",
10014
+ "-vn",
10015
+ "-ar",
10016
+ String(PEAK_SAMPLE_RATE),
10017
+ "-f",
10018
+ "f32le",
10019
+ "pipe:1"
10020
+ ],
10021
+ { stdio: ["ignore", "pipe", "ignore"], windowsHide: true }
10022
+ );
10023
+ const acc = createPeakAccumulator(PEAK_SAMPLE_RATE, PEAK_BIN_SECONDS, channels);
10024
+ proc.stdout?.on("data", (chunk) => accumulatePeaks(acc, chunk));
10025
+ proc.on("close", (code) => {
10026
+ if (code !== 0) {
10027
+ reject(new Error(`ffmpeg failed decoding audio (exit ${code})`));
10028
+ return;
10029
+ }
10030
+ if (acc.frame === 0) {
10031
+ reject(new Error("ffmpeg produced no audio samples"));
10032
+ return;
10033
+ }
10034
+ resolvePromise(finishPeaks(acc));
10035
+ });
10036
+ proc.on("error", reject);
10037
+ });
10038
+ }
10039
+ function buildPeakMapCacheKey(assetPath, fingerprint) {
10040
+ const name = assetPath.replace(/[/\\]/g, "_");
10041
+ return `${PEAK_MAP_CACHE_VERSION}_${name}_${fingerprint.size}-${Math.round(fingerprint.mtimeMs)}.json`;
10042
+ }
10043
+ var decodesInFlight = /* @__PURE__ */ new Map();
10044
+ function decodeOnce(cachePath, mediaPath, decode) {
10045
+ const pending = decodesInFlight.get(cachePath);
10046
+ if (pending) return pending;
10047
+ const started = decode(mediaPath).finally(() => decodesInFlight.delete(cachePath));
10048
+ decodesInFlight.set(cachePath, started);
10049
+ return started;
10050
+ }
10051
+ function readCachedBins(cacheDir, cachePath) {
10052
+ try {
10053
+ if (!isWaveformCacheDirectory(cacheDir) || !existsSync10(cachePath)) return null;
10054
+ const parsed = JSON.parse(readFileSync12(cachePath, "utf-8"));
10055
+ return Array.isArray(parsed) && parsed.every((n) => typeof n === "number") ? parsed : null;
10056
+ } catch {
10057
+ return null;
10058
+ }
10059
+ }
10060
+ function registerPeakRoutes(api, adapter, decode = decodePeakMap) {
10061
+ api.get("/projects/:id/peaks/*", async (c) => {
10062
+ const project = await adapter.resolveProject(c.req.param("id"));
10063
+ if (!project) return c.json({ error: "not found" }, 404);
10064
+ const assetPath = requestSubPath(c.req.url, "projects/:id/peaks");
10065
+ const mediaPath = resolve6(project.dir, assetPath);
10066
+ if (!isWithinProjectRoot(project.dir, mediaPath))
10067
+ return c.json({ error: "file not found" }, 404);
10068
+ const stats = statSync9(mediaPath, { throwIfNoEntry: false });
10069
+ if (!stats?.isFile()) return c.json({ error: "file not found" }, 404);
10070
+ const cacheDir = join14(project.dir, ".waveform-cache");
10071
+ const cachePath = join14(cacheDir, buildPeakMapCacheKey(assetPath, stats));
10072
+ const cached = readCachedBins(cacheDir, cachePath);
10073
+ if (cached) return c.json({ binSeconds: PEAK_BIN_SECONDS, bins: cached });
10074
+ let bins;
10075
+ try {
10076
+ bins = await decodeOnce(cachePath, mediaPath, decode);
10077
+ } catch {
10078
+ return c.json({ error: "failed to decode audio" }, 500);
10079
+ }
10080
+ try {
10081
+ writeWaveformCache(cachePath, bins);
10082
+ } catch {
10083
+ }
10084
+ return c.json({ binSeconds: PEAK_BIN_SECONDS, bins });
10085
+ });
10086
+ }
9577
10087
  var MAX_FONT_RESULTS = 2e3;
9578
10088
  var GOOGLE_FONTS_METADATA_URL = "https://fonts.google.com/metadata/fonts";
9579
10089
  var GOOGLE_FONTS_FETCH_TIMEOUT_MS = 3e3;
@@ -9601,7 +10111,7 @@ var GOOGLE_FONT_FALLBACKS = [
9601
10111
  "Outfit",
9602
10112
  "JetBrains Mono"
9603
10113
  ];
9604
- function isRecord(value) {
10114
+ function isRecord2(value) {
9605
10115
  return typeof value === "object" && value !== null;
9606
10116
  }
9607
10117
  function collectFontsFromDir(dir) {
@@ -9625,10 +10135,10 @@ function listInstalledFontFamilies() {
9625
10135
  return cachedFonts;
9626
10136
  }
9627
10137
  function parseGoogleFontMetadata(value) {
9628
- if (!isRecord(value) || !Array.isArray(value.familyMetadataList)) return [];
10138
+ if (!isRecord2(value) || !Array.isArray(value.familyMetadataList)) return [];
9629
10139
  const families = [];
9630
10140
  for (const entry of value.familyMetadataList) {
9631
- if (!isRecord(entry) || typeof entry.family !== "string") continue;
10141
+ if (!isRecord2(entry) || typeof entry.family !== "string") continue;
9632
10142
  families.push(entry.family);
9633
10143
  }
9634
10144
  return families;
@@ -9728,14 +10238,14 @@ function registerRegistryRoutes(api, adapter) {
9728
10238
  }
9729
10239
  });
9730
10240
  }
9731
- function isRecord2(value) {
10241
+ function isRecord3(value) {
9732
10242
  return typeof value === "object" && value !== null && !Array.isArray(value);
9733
10243
  }
9734
10244
  function isFiniteNumber(value) {
9735
10245
  return typeof value === "number" && Number.isFinite(value);
9736
10246
  }
9737
10247
  function isStringRecord(value) {
9738
- return isRecord2(value) && Object.values(value).every((v) => typeof v === "string");
10248
+ return isRecord3(value) && Object.values(value).every((v) => typeof v === "string");
9739
10249
  }
9740
10250
  function hasString(value, key) {
9741
10251
  return typeof value[key] === "string";
@@ -9753,20 +10263,20 @@ function hasOptionalNumber(value, key) {
9753
10263
  return value[key] === void 0 || isFiniteNumber(value[key]);
9754
10264
  }
9755
10265
  function isBoundingBox(value) {
9756
- return isRecord2(value) && ["x", "y", "width", "height"].every((key) => isFiniteNumber(value[key]));
10266
+ return isRecord3(value) && ["x", "y", "width", "height"].every((key) => isFiniteNumber(value[key]));
9757
10267
  }
9758
10268
  function isTarget(value) {
9759
- if (!isRecord2(value)) return false;
10269
+ if (!isRecord3(value)) return false;
9760
10270
  return hasOptionalNullableString(value, "id") && hasOptionalString(value, "hfId") && hasOptionalString(value, "selector") && hasOptionalNumber(value, "selectorIndex");
9761
10271
  }
9762
10272
  function isTextField(value) {
9763
- return isRecord2(value) && hasRequiredStrings(value, ["key", "label", "value", "tagName"]) && ["self", "child", "text-node"].includes(value.source);
10273
+ return isRecord3(value) && hasRequiredStrings(value, ["key", "label", "value", "tagName"]) && ["self", "child", "text-node"].includes(value.source);
9764
10274
  }
9765
10275
  function isTextFields(value) {
9766
10276
  return Array.isArray(value) && value.every(isTextField);
9767
10277
  }
9768
10278
  function isSelectionSnapshot(value) {
9769
- if (!isRecord2(value)) return false;
10279
+ if (!isRecord3(value)) return false;
9770
10280
  const checks = [
9771
10281
  value.schemaVersion === 1 && hasRequiredStrings(value, [
9772
10282
  "projectId",
@@ -9784,7 +10294,7 @@ function isSelectionSnapshot(value) {
9784
10294
  isStringRecord(value.inlineStyles),
9785
10295
  isStringRecord(value.computedStyles),
9786
10296
  isTextFields(value.textFields),
9787
- isRecord2(value.capabilities)
10297
+ isRecord3(value.capabilities)
9788
10298
  ];
9789
10299
  return checks.every(Boolean);
9790
10300
  }
@@ -9808,7 +10318,7 @@ function registerSelectionRoutes(api, adapter) {
9808
10318
  } catch {
9809
10319
  return c.json({ error: "invalid json" }, 400);
9810
10320
  }
9811
- if (!isRecord2(body) || !("selection" in body)) {
10321
+ if (!isRecord3(body) || !("selection" in body)) {
9812
10322
  return c.json({ error: "missing selection" }, 400);
9813
10323
  }
9814
10324
  if (body.selection === null) {
@@ -9860,7 +10370,7 @@ function uniqueAssetPath(projectDir, assetPath, taken) {
9860
10370
  const ext = extname2(assetPath);
9861
10371
  const withoutExt = assetPath.slice(0, -ext.length);
9862
10372
  let candidate = assetPath;
9863
- for (let index = 2; taken.has(candidate) || existsSync8(join13(projectDir, candidate)); index++) {
10373
+ for (let index = 2; taken.has(candidate) || existsSync11(join15(projectDir, candidate)); index++) {
9864
10374
  candidate = `${withoutExt}-${index}${ext}`;
9865
10375
  }
9866
10376
  return candidate;
@@ -9921,7 +10431,7 @@ function registerMediaRoutes(api, adapter, options = {}) {
9921
10431
  }
9922
10432
  const filePath = resolveWithinProject(project.dir, assetPath);
9923
10433
  if (!filePath) return c.json({ error: "forbidden" }, 403);
9924
- if (!existsSync8(filePath)) return c.json({ error: "media not found" }, 404);
10434
+ if (!existsSync11(filePath)) return c.json({ error: "media not found" }, 404);
9925
10435
  return c.json({ path: assetPath, metadata: await readMediaMetadata(filePath) });
9926
10436
  });
9927
10437
  api.post(
@@ -9943,7 +10453,7 @@ function registerMediaRoutes(api, adapter, options = {}) {
9943
10453
  }
9944
10454
  const inputPath = resolveWithinProject(project.dir, inputAssetPath);
9945
10455
  if (!inputPath) return c.json({ error: "forbidden" }, 403);
9946
- if (!existsSync8(inputPath)) return c.json({ error: "input media not found" }, 404);
10456
+ if (!existsSync11(inputPath)) return c.json({ error: "input media not found" }, 404);
9947
10457
  const inputIsVideo = isVideoPath(inputAssetPath);
9948
10458
  const inputIsImage = isImagePath(inputAssetPath);
9949
10459
  if (!inputIsVideo && !inputIsImage) {
@@ -9979,8 +10489,8 @@ function registerMediaRoutes(api, adapter, options = {}) {
9979
10489
  return c.json({ error: "forbidden" }, 403);
9980
10490
  }
9981
10491
  }
9982
- mkdirWithinProject(project.dir, dirname52(outputPath));
9983
- if (backgroundOutputPath) mkdirWithinProject(project.dir, dirname52(backgroundOutputPath));
10492
+ mkdirWithinProject(project.dir, dirname6(outputPath));
10493
+ if (backgroundOutputPath) mkdirWithinProject(project.dir, dirname6(backgroundOutputPath));
9984
10494
  const jobId = makeJobId(project.id, mediaJobs);
9985
10495
  const state = adapter.startBackgroundRemoval({
9986
10496
  project,
@@ -10036,10 +10546,10 @@ function registerMediaRoutes(api, adapter, options = {}) {
10036
10546
  });
10037
10547
  }
10038
10548
  function readGlobalAssets(home = homedir2()) {
10039
- const manifestPath = join14(home, ".media", "manifest.jsonl");
10040
- if (!existsSync9(manifestPath)) return [];
10549
+ const manifestPath = join16(home, ".media", "manifest.jsonl");
10550
+ if (!existsSync12(manifestPath)) return [];
10041
10551
  const out = [];
10042
- for (const line of readFileSync11(manifestPath, "utf8").split("\n")) {
10552
+ for (const line of readFileSync13(manifestPath, "utf8").split("\n")) {
10043
10553
  if (!line.trim()) continue;
10044
10554
  try {
10045
10555
  const rec = JSON.parse(line);
@@ -10222,6 +10732,9 @@ function createStudioApi(adapter) {
10222
10732
  registerSelectionRoutes(api, adapter);
10223
10733
  registerMediaRoutes(api, adapter);
10224
10734
  registerWaveformRoutes(api, adapter);
10735
+ registerFreezeFrameRoutes(api, adapter);
10736
+ registerLoudnessRoutes(api, adapter);
10737
+ registerPeakRoutes(api, adapter);
10225
10738
  registerFontRoutes(api);
10226
10739
  registerRegistryRoutes(api, adapter);
10227
10740
  registerGlobalAssetRoutes(api);
@@ -10237,23 +10750,23 @@ function duplicateElementInHtml(source, target, newId, at) {
10237
10750
  if (duration === null || numericAttribute(element, "data-start") === null) {
10238
10751
  return { html: source, matched: false, newId: null };
10239
10752
  }
10240
- const uniqueId = nextUniqueId(document2, newId);
10753
+ const uniqueId2 = nextUniqueId(document2, newId);
10241
10754
  rippleElements(document2, element, at, duration, track);
10242
10755
  const clone = element.cloneNode(true);
10243
10756
  if (!isHTMLElement(clone)) return { html: source, matched: false, newId: null };
10244
- clone.setAttribute("id", uniqueId);
10757
+ clone.setAttribute("id", uniqueId2);
10245
10758
  dedupeClonedCompositionId(document2, clone);
10246
10759
  stripStableIds(clone);
10247
10760
  clone.setAttribute("data-start", String(at));
10248
10761
  element.parentElement.insertBefore(clone, element.nextSibling);
10249
10762
  const html = wrappedFragment ? document2.body.innerHTML || "" : document2.toString();
10250
- return { html: ensureHfIds(html), matched: true, newId: uniqueId };
10763
+ return { html: ensureHfIds(html), matched: true, newId: uniqueId2 };
10251
10764
  }
10252
10765
  function nextUniqueId(document2, newId) {
10253
- let uniqueId = newId;
10766
+ let uniqueId2 = newId;
10254
10767
  let suffix = 2;
10255
- while (document2.getElementById(uniqueId)) uniqueId = `${newId}-${suffix++}`;
10256
- return uniqueId;
10768
+ while (document2.getElementById(uniqueId2)) uniqueId2 = `${newId}-${suffix++}`;
10769
+ return uniqueId2;
10257
10770
  }
10258
10771
  function rippleElements(document2, element, at, duration, track) {
10259
10772
  for (const candidate of Array.from(document2.querySelectorAll("[data-start][data-duration]"))) {
@@ -10278,13 +10791,13 @@ function numericAttribute(element, name) {
10278
10791
  return Number.isFinite(value) ? value : null;
10279
10792
  }
10280
10793
  function createPreviewDocumentStore(projectDir, salt) {
10281
- const dir = join15(projectDir, ".hyperframes", "preview");
10282
- const fileFor = (key) => join15(dir, `${createHash42("sha256").update(`${salt}
10794
+ const dir = join17(projectDir, ".hyperframes", "preview");
10795
+ const fileFor = (key) => join17(dir, `${createHash5("sha256").update(`${salt}
10283
10796
  ${key}`).digest("hex").slice(0, 32)}.html`);
10284
10797
  return {
10285
10798
  read(key) {
10286
10799
  try {
10287
- return readFileSync12(fileFor(key), "utf-8");
10800
+ return readFileSync14(fileFor(key), "utf-8");
10288
10801
  } catch {
10289
10802
  return null;
10290
10803
  }
@@ -10295,7 +10808,7 @@ ${key}`).digest("hex").slice(0, 32)}.html`);
10295
10808
  const file = fileFor(key);
10296
10809
  replaceFileAtomically(file, html, 420);
10297
10810
  for (const name of readdirSync5(dir)) {
10298
- if (join15(dir, name) !== file) rmSync5(join15(dir, name), { force: true });
10811
+ if (join17(dir, name) !== file) rmSync5(join17(dir, name), { force: true });
10299
10812
  }
10300
10813
  } catch {
10301
10814
  }