hyperframes 0.8.106 → 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.
- package/dist/{add-QRAI4CAF.js → add-OH75E7FX.js} +16 -16
- package/dist/{animatedGifPrep-RZVYVAXD.js → animatedGifPrep-ZDAWSJYL.js} +7 -7
- package/dist/{asset-WFBQ5VS6.js → asset-BVDO4DEW.js} +2 -2
- package/dist/{assetCataloger-BQREHRS2.js → assetCataloger-FXEHOUA3.js} +3 -3
- package/dist/{auth-LVD6TWYG.js → auth-K5GUKO5S.js} +2 -2
- package/dist/{autoUpdate-HGE2PUZY.js → autoUpdate-77ROWK5G.js} +2 -2
- package/dist/{backgroundChecks-63CSE4XR.js → backgroundChecks-3Q3D5WBJ.js} +3 -3
- package/dist/backgroundChecksWorker.js +3 -3
- package/dist/{batchRender-AQN5PRXM.js → batchRender-ZTHFMHZY.js} +4 -4
- package/dist/{beats-OBCN5OSQ.js → beats-HQZPR6KE.js} +19 -13
- package/dist/{benchmark-DYHJSR7W.js → benchmark-BQOWARGU.js} +13 -13
- package/dist/{browser-JLYV5T7O.js → browser-45CK7HXI.js} +10 -10
- package/dist/{bundleWithLocalizedFonts-JKJKZR2S.js → bundleWithLocalizedFonts-3M2LKRHK.js} +2 -2
- package/dist/{capture-5BCJWVC7.js → capture-6IVZXM7G.js} +2 -2
- package/dist/{capture-QYLHIYKJ.js → capture-VGEUOP7N.js} +8 -8
- package/dist/{catalog-6UYNKENR.js → catalog-VHD7B6FD.js} +16 -16
- package/dist/{check-YOGAZ6UE.js → check-UPNZZJXV.js} +32 -31
- package/dist/{checkBrowser-M73YHPNM.js → checkBrowser-KWTHKOOP.js} +31 -30
- package/dist/{chunk-JYRPUDVZ.js → chunk-5N4KKGSZ.js} +1 -1
- package/dist/{normalize-audio-H3SI5V63.js → chunk-5ON5GH3X.js} +101 -209
- package/dist/{chunk-NRMYDPFQ.js → chunk-5R5UHSXN.js} +9 -9
- package/dist/{chunk-OSZJCBZX.js → chunk-AFGDOKRI.js} +7 -7
- package/dist/{chunk-6CN2QLB5.js → chunk-AWUZ3AUD.js} +1 -1
- package/dist/{chunk-TEXPOC5V.js → chunk-B25H4M4V.js} +2 -2
- package/dist/{chunk-JXXE3SWC.js → chunk-C6XQER6H.js} +28 -14
- package/dist/{chunk-Y2CZEJPK.js → chunk-CFC3EEDQ.js} +2 -2
- package/dist/{chunk-PB2OX2JY.js → chunk-D7SCIX4D.js} +1 -1
- package/dist/{chunk-AXWBDWAV.js → chunk-FZGUZIK5.js} +3 -3
- package/dist/{chunk-4WAMN4IF.js → chunk-GYYFNAVA.js} +1 -1
- package/dist/{chunk-TYA65W4U.js → chunk-HQGJLZB6.js} +2 -2
- package/dist/{chunk-LAHK4P7Q.js → chunk-J7XPPADY.js} +3 -3
- package/dist/{chunk-I77CAR4W.js → chunk-KVOWTRD5.js} +2 -2
- package/dist/{chunk-GVZYTDOD.js → chunk-KZ5OSG4G.js} +1 -1
- package/dist/{chunk-IBCGKQB5.js → chunk-KZJ6V7RT.js} +3 -3
- package/dist/{chunk-HUHJYNST.js → chunk-LAQEZHOH.js} +1 -1
- package/dist/{chunk-HFFR7TN7.js → chunk-LPXM5L6S.js} +2 -1
- package/dist/{chunk-2IZZZJDZ.js → chunk-LTYI4E42.js} +4 -4
- package/dist/{chunk-J3Q3YC6P.js → chunk-LZPAZZZP.js} +1 -1
- package/dist/{chunk-5TBFBLIX.js → chunk-MGTECHI3.js} +2 -2
- package/dist/{chunk-4I6AQUWG.js → chunk-NGVH2QYT.js} +8 -8
- package/dist/{chunk-M4KS6MJN.js → chunk-NJLFRKHX.js} +4 -4
- package/dist/{chunk-6LPWMCPY.js → chunk-NROJESOK.js} +2 -2
- package/dist/{chunk-UVUAU3CH.js → chunk-ONY5CJXS.js} +48 -26
- package/dist/{chunk-O5TBFZ7X.js → chunk-OVRFBUXU.js} +2 -2
- package/dist/{chunk-6F6EEQNB.js → chunk-PSGDNMM6.js} +1 -1
- package/dist/{chunk-AZQLJHAO.js → chunk-PZTEY73C.js} +1 -1
- package/dist/{chunk-VW5OMJNG.js → chunk-QB6OM2BP.js} +89 -70
- package/dist/{chunk-I6M4GXMC.js → chunk-QBHT234I.js} +1 -1
- package/dist/{chunk-TMLEIURT.js → chunk-QRXICW5Q.js} +1 -1
- package/dist/{chunk-4NJZZHYT.js → chunk-RPMXMOFU.js} +1 -1
- package/dist/{chunk-XZSH5GS7.js → chunk-RSL3XFMO.js} +4 -4
- package/dist/{chunk-PUB5XZYL.js → chunk-SIVCJJHE.js} +1 -1
- package/dist/{chunk-UUE5BZGE.js → chunk-SQ42NHXP.js} +620 -74
- package/dist/{chunk-GOI4GCUB.js → chunk-THVNQFYG.js} +1 -1
- package/dist/{chunk-IQ4UCWV7.js → chunk-TIZQC44K.js} +1 -1
- package/dist/{chunk-FTXU3TQ4.js → chunk-US7SWKGC.js} +1 -1
- package/dist/{chunk-TLYU5625.js → chunk-UUDQD62L.js} +6 -6
- package/dist/{chunk-2LCIQS3K.js → chunk-VBXJSCHR.js} +3 -3
- package/dist/{chunk-DVCYGZ7R.js → chunk-VLHN34SB.js} +2 -2
- package/dist/{chunk-4Z5O7BTD.js → chunk-WEATBQTR.js} +10 -1
- package/dist/{chunk-TH4APNRP.js → chunk-X763EUBO.js} +2 -2
- package/dist/{chunk-32Y3HRS3.js → chunk-XIGK4LNU.js} +2 -2
- package/dist/{chunk-R5RF3LGS.js → chunk-XMAL2A7Y.js} +1 -1
- package/dist/{chunk-USWOTTSP.js → chunk-XYVFPDNU.js} +1 -1
- package/dist/chunk-YLVI7VL5.js +3541 -0
- package/dist/{chunk-YVWILZCO.js → chunk-ZFL6FVQZ.js} +5 -5
- package/dist/{clean-GRX3GXKR.js → clean-LCRWNIWO.js} +17 -16
- package/dist/cli.js +50 -50
- package/dist/{cloud-TQCNBM6H.js → cloud-ITRFE5UN.js} +4 -4
- package/dist/{cloudrun-ISE6GSUO.js → cloudrun-LVZ4OPPC.js} +5 -5
- package/dist/{compare-QAWB3O4U.js → compare-NPVMUILH.js} +26 -25
- package/dist/{compiler-QMEZK6CU.js → compiler-YYOV2BJ7.js} +3 -3
- package/dist/{component-NXNNSXN7.js → component-HJFMHPNW.js} +3 -3
- package/dist/{compositions-I4PIPJKO.js → compositions-I34RN5WD.js} +12 -12
- package/dist/{delete-ZC4ZRBBF.js → delete-WN3J74BD.js} +4 -4
- package/dist/{deterministicFonts-XS3PGWH5.js → deterministicFonts-A4CNCSAL.js} +2 -2
- package/dist/{dist-E44WU6LH.js → dist-WCMFH74H.js} +1 -1
- package/dist/{dist-R24TLUYO.js → dist-YUIPB7OX.js} +9 -3
- package/dist/docs/data-attributes.md +1 -1
- package/dist/{doctor-EFQ4XNT3.js → doctor-MYC2S3J3.js} +11 -11
- package/dist/{events-AVPSJNYU.js → events-7JVV4746.js} +10 -10
- package/dist/{events-COIPJKSK.js → events-AEUV2JPA.js} +9 -9
- package/dist/{feedback-3DYZDMLC.js → feedback-X7DDSZPU.js} +11 -11
- package/dist/{figma-7XPHSMNG.js → figma-ISI4PPTF.js} +3 -3
- package/dist/fontLocalizeCli.js +10 -10
- package/dist/{get-TSBHDMKL.js → get-IYALAE4I.js} +4 -4
- package/dist/{gpuPolicy-BQFPO7UA.js → gpuPolicy-KSWTVU5C.js} +7 -7
- package/dist/{grade-compare-5Y4LXQFG.js → grade-compare-YWPIWQLK.js} +26 -25
- package/dist/{gsapParser-EZGJSSV5.js → gsapParser-KVMPC2SC.js} +5 -1
- package/dist/{help-5YBRP3C4.js → help-S2KJRMAJ.js} +1 -1
- package/dist/{history-SRNXKL6D.js → history-CQDDCY4J.js} +17 -16
- package/dist/hyperframe-runtime.js +54 -54
- package/dist/hyperframe.manifest.json +1 -1
- package/dist/hyperframe.runtime.iife.js +54 -54
- package/dist/hyperframes-player.global.js +3 -3
- package/dist/{info-U4BOXV2I.js → info-4KFNUWYN.js} +12 -12
- package/dist/{init-T4JYHIBM.js → init-I7GMRHFW.js} +98 -42
- package/dist/{inspect-XYLD2BMM.js → inspect-DUCLCHSD.js} +27 -26
- package/dist/{installer-UZSVMUKT.js → installer-NBEN3ZIP.js} +5 -5
- package/dist/{keyframes-BGPXWP7O.js → keyframes-F3VDEN66.js} +13 -13
- package/dist/{lambda-U4WIJQCJ.js → lambda-BFHJMMCG.js} +6 -6
- package/dist/{layout-UREZQ37U.js → layout-RNCXJI6C.js} +27 -26
- package/dist/{lint-LSTEZZ6T.js → lint-3NKKYCMH.js} +13 -13
- package/dist/{lintProject-4P4C6AN2.js → lintProject-F2OTN2IR.js} +1 -1
- package/dist/{list-6VICFTAM.js → list-Q7SOE3NF.js} +4 -4
- package/dist/{login-MXDIDOOG.js → login-TGRGWS5E.js} +4 -4
- package/dist/{media-treatment-ZVCWEACM.js → media-treatment-5TUXWHWE.js} +13 -13
- package/dist/{models-LXUIU5DC.js → models-YJUTM6TH.js} +8 -8
- package/dist/{motionShot-2LYUQF3T.js → motionShot-5QUMZ7J3.js} +11 -11
- package/dist/normalize-audio-63BI5BPL.js +276 -0
- package/dist/{pipeline-OFFKTFXS.js → pipeline-4NN5T7Q3.js} +7 -7
- package/dist/{play-P56CWNVC.js → play-SRDESSZI.js} +22 -21
- package/dist/{preflight-TVCVP5P2.js → preflight-SVN4PSFO.js} +8 -8
- package/dist/{present-ATMOWKMH.js → present-TDYR5LRW.js} +18 -17
- package/dist/{preview-7U6HYHYE.js → preview-Z5MFRZUM.js} +18 -18
- package/dist/{producer-LGG3TOUI.js → producer-RS7CB3XJ.js} +1 -1
- package/dist/{projectConfig-V53SEFIS.js → projectConfig-JHEWPAA5.js} +7 -7
- package/dist/{publication-7RKKPKLM.js → publication-7XA2H7UB.js} +3 -3
- package/dist/{publish-TRRFCEMQ.js → publish-2R22XX77.js} +9 -9
- package/dist/{remove-background-HJFT46Z5.js → remove-background-KT2SWYCC.js} +8 -8
- package/dist/{render-TPZ5FXHS.js → render-4C3YKVZI.js} +4 -4
- package/dist/{render-YB7BARWL.js → render-7GDLEGXT.js} +15 -15
- package/dist/{render-2QZBNE2V.js → render-Y2Y7N5WD.js} +22 -22
- package/dist/{render-batch-4ZF5C2LN.js → render-batch-AFDBPDMM.js} +4 -4
- package/dist/{renderCancellation-4PYGAN5D.js → renderCancellation-RQ3UVNVJ.js} +7 -7
- package/dist/renderSetupWorker.js +1 -1
- package/dist/{resolver-4JRJPC6S.js → resolver-S72XBTEQ.js} +5 -5
- package/dist/{sherpa-MNCWRFD4.js → sherpa-PR3I4KGM.js} +10 -10
- package/dist/{skills-ETBA5B46.js → skills-BEYZZWY6.js} +12 -12
- package/dist/{skillsUpdateCheck-DQOUIYV5.js → skillsUpdateCheck-3USXDPWV.js} +3 -3
- package/dist/{snapshot-W3HTBAYH.js → snapshot-S77MQOV2.js} +27 -26
- package/dist/{src-QX5PPANW.js → src-6R4DC2YI.js} +6 -6
- package/dist/{src-QF6RYX76.js → src-IOJXK4VC.js} +9 -9
- package/dist/{staticProjectServer-WBXW2CA4.js → staticProjectServer-RXAFGDK7.js} +16 -15
- package/dist/{status-C3IDZFDG.js → status-CRS7H6FA.js} +7 -7
- package/dist/studio/assets/{hyperframes-player-dl6K6ABp.js → hyperframes-player-BakGew45.js} +1 -1
- package/dist/studio/assets/{index-ooaZZhKf.js → index-C5dyUHwk.js} +1 -1
- package/dist/studio/assets/{index-CMiYVGv5.js → index-C842tcVa.js} +203 -208
- package/dist/studio/assets/{index-DKT5NF1Y.js → index-CPe7d_wO.js} +1 -1
- package/dist/studio/assets/{index-lrT-gfHa.css → index-ncVJ5IVu.css} +1 -1
- package/dist/studio/chunk-6LUNLTHR.js +611 -0
- package/dist/studio/chunk-6LUNLTHR.js.map +1 -0
- package/dist/studio/{chunk-ICTZJPQQ.js → chunk-AOWVEVEU.js} +3 -494
- package/dist/studio/chunk-AOWVEVEU.js.map +1 -0
- package/dist/studio/{chunk-46XP7J3B.js → chunk-PQFEBX32.js} +5 -3
- package/dist/studio/chunk-PQFEBX32.js.map +1 -0
- package/dist/studio/{domEditingLayers-AQBDFNUC.js → domEditingLayers-JQPQGGT7.js} +3 -3
- package/dist/studio/index.d.ts +377 -12
- package/dist/studio/index.html +2 -2
- package/dist/studio/index.js +13445 -11874
- package/dist/studio/index.js.map +1 -1
- package/dist/studio/player.js +2 -2
- package/dist/studio/styles.css +1 -1
- package/dist/{studio-api-6IJPJVG6.js → studio-api-3FH7OSSS.js} +10 -9
- package/dist/{studioServer-77CY4XPO.js → studioServer-4E5RRPU7.js} +49 -42
- package/dist/{system-OAB2LYBV.js → system-YMANAUAM.js} +7 -7
- package/dist/{telemetry-XTN5BO5S.js → telemetry-46XSB2VN.js} +10 -10
- package/dist/templates/_shared/AGENTS.md +1 -1
- package/dist/templates/_shared/CLAUDE.md +1 -1
- package/dist/templates/from-file/index.html +3 -2
- package/dist/templates/warm-grain/index.html +2 -10
- package/dist/{timeline-YKKTGPL7.js → timeline-XKLKHGD2.js} +17 -16
- package/dist/{tokens-LU25F3IJ.js → tokens-JCZKPUPT.js} +2 -2
- package/dist/{transcribe-O7OL4RRU.js → transcribe-FIFJ3BBB.js} +13 -13
- package/dist/{transcribe-AL3E5MBZ.js → transcribe-FQ4YC2AS.js} +10 -8
- package/dist/{updateCheck-MZZML4DL.js → updateCheck-PDIH4GZW.js} +2 -2
- package/dist/{upgrade-PQRTEJJY.js → upgrade-TTGO6JCF.js} +2 -2
- package/dist/{urlDownloader-U3TNOPD5.js → urlDownloader-32JRHOPN.js} +6 -6
- package/dist/{validate-KQETGCIR.js → validate-G5F56ONL.js} +16 -16
- package/dist/{video-KAHL76HG.js → video-SGBEMEKA.js} +7 -7
- package/package.json +1 -1
- package/dist/chunk-FOKUJKBN.js +0 -3420
- package/dist/studio/chunk-46XP7J3B.js.map +0 -1
- package/dist/studio/chunk-DI4LCJHL.js +0 -102
- package/dist/studio/chunk-DI4LCJHL.js.map +0 -1
- package/dist/studio/chunk-ICTZJPQQ.js.map +0 -1
- /package/dist/studio/{domEditingLayers-AQBDFNUC.js.map → domEditingLayers-JQPQGGT7.js.map} +0 -0
|
@@ -15,11 +15,20 @@ import {
|
|
|
15
15
|
splitElementInHtml,
|
|
16
16
|
unwrapElementsFromHtml,
|
|
17
17
|
wrapElementsInHtml
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-NJLFRKHX.js";
|
|
19
19
|
import {
|
|
20
|
+
atomicTempPath,
|
|
20
21
|
createFileAtomically,
|
|
22
|
+
isAtomicTempPath,
|
|
21
23
|
replaceFileAtomically
|
|
22
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-WEATBQTR.js";
|
|
25
|
+
import {
|
|
26
|
+
DEFAULT_TARGET_LUFS,
|
|
27
|
+
absoluteLoudnessPlan,
|
|
28
|
+
measureAudio,
|
|
29
|
+
requiredFfmpeg,
|
|
30
|
+
resolveLocalAudioPath
|
|
31
|
+
} from "./chunk-5ON5GH3X.js";
|
|
23
32
|
import {
|
|
24
33
|
PROXY_PARAMS_VERSION,
|
|
25
34
|
PROXY_PENDING_RETRY_AFTER_SECONDS,
|
|
@@ -45,7 +54,7 @@ import {
|
|
|
45
54
|
shouldPrewarmProxy,
|
|
46
55
|
waitForProxy,
|
|
47
56
|
walkDir
|
|
48
|
-
} from "./chunk-
|
|
57
|
+
} from "./chunk-OVRFBUXU.js";
|
|
49
58
|
import {
|
|
50
59
|
findTimelineScript,
|
|
51
60
|
parseGsapScriptAcorn
|
|
@@ -64,24 +73,26 @@ import {
|
|
|
64
73
|
formatRenderOutputTimestamp,
|
|
65
74
|
parseFps,
|
|
66
75
|
sameInstant
|
|
67
|
-
} from "./chunk-
|
|
76
|
+
} from "./chunk-B25H4M4V.js";
|
|
68
77
|
import {
|
|
69
78
|
MagicString,
|
|
70
79
|
VALID_CANVAS_RESOLUTIONS,
|
|
71
80
|
unrollComputedTimeline
|
|
72
|
-
} from "./chunk-
|
|
81
|
+
} from "./chunk-UUDQD62L.js";
|
|
73
82
|
import {
|
|
74
83
|
addScenePartsManifest,
|
|
75
84
|
ensureHfIds
|
|
76
|
-
} from "./chunk-
|
|
85
|
+
} from "./chunk-X763EUBO.js";
|
|
77
86
|
import {
|
|
78
87
|
findStartTags,
|
|
79
88
|
injectScriptsIntoHtml,
|
|
80
89
|
injectTagsAtHeadStart,
|
|
81
90
|
insertBeforeCloseTag,
|
|
82
91
|
isFullHtmlDocument,
|
|
92
|
+
resolveRateSpec,
|
|
93
|
+
sourceTimeAt,
|
|
83
94
|
stripEmbeddedRuntimeScripts
|
|
84
|
-
} from "./chunk-
|
|
95
|
+
} from "./chunk-YLVI7VL5.js";
|
|
85
96
|
import {
|
|
86
97
|
ProjectRootMissingError,
|
|
87
98
|
folderGone,
|
|
@@ -94,12 +105,13 @@ import {
|
|
|
94
105
|
import {
|
|
95
106
|
isWithinProjectRoot,
|
|
96
107
|
readProjectFile
|
|
97
|
-
} from "./chunk-
|
|
108
|
+
} from "./chunk-ONY5CJXS.js";
|
|
98
109
|
import {
|
|
99
110
|
FFPROBE_PATH_ENV,
|
|
100
111
|
findFfBinary
|
|
101
112
|
} from "./chunk-PJ3HDUCY.js";
|
|
102
113
|
import {
|
|
114
|
+
readMediaOffsetSeconds,
|
|
103
115
|
rewriteAssetPaths,
|
|
104
116
|
rewriteCssAssetUrls,
|
|
105
117
|
rewriteInlineStyleAssetUrls
|
|
@@ -198,7 +210,7 @@ function stampFileHfIds(filePath) {
|
|
|
198
210
|
}
|
|
199
211
|
}
|
|
200
212
|
|
|
201
|
-
// ../studio-server/dist/chunk-
|
|
213
|
+
// ../studio-server/dist/chunk-KUEG5MYA.js
|
|
202
214
|
import { randomUUID as randomUUID6 } from "crypto";
|
|
203
215
|
import { mkdir as mkdir2, readdir as readdir3, rm as rm3, rmdir, writeFile } from "fs/promises";
|
|
204
216
|
import {
|
|
@@ -338,7 +350,7 @@ var STUDIO_SIGNATURE_MANIFEST_PATHS = [
|
|
|
338
350
|
];
|
|
339
351
|
function affectsProjectSignature(projectDir, changedPath) {
|
|
340
352
|
const relativePath = relative(resolve(projectDir), resolve(changedPath));
|
|
341
|
-
if (relativePath === "" || relativePath.startsWith("..") || isAbsolute(relativePath)) {
|
|
353
|
+
if (relativePath === "" || relativePath.startsWith("..") || isAbsolute(relativePath) || isAtomicTempPath(relativePath)) {
|
|
342
354
|
return false;
|
|
343
355
|
}
|
|
344
356
|
const segments = relativePath.split(sep);
|
|
@@ -354,6 +366,7 @@ function isPathWithin(parentDir, childPath) {
|
|
|
354
366
|
function isTextContentEligible(file, size) {
|
|
355
367
|
return SIGNATURE_TEXT_EXTENSIONS.has(extname(file).toLowerCase()) && size <= MAX_SIGNATURE_TEXT_BYTES;
|
|
356
368
|
}
|
|
369
|
+
var isSkippedEntry = (entry) => SIGNATURE_EXCLUDED_DIRS.has(entry) || isAtomicTempPath(entry);
|
|
357
370
|
function collectProjectSignatureFiles(projectDir, dir, files) {
|
|
358
371
|
let entries;
|
|
359
372
|
try {
|
|
@@ -362,7 +375,7 @@ function collectProjectSignatureFiles(projectDir, dir, files) {
|
|
|
362
375
|
return;
|
|
363
376
|
}
|
|
364
377
|
for (const entry of entries) {
|
|
365
|
-
if (
|
|
378
|
+
if (isSkippedEntry(entry)) continue;
|
|
366
379
|
const file = resolve(dir, entry);
|
|
367
380
|
if (!isPathWithin(projectDir, file)) continue;
|
|
368
381
|
let stat22;
|
|
@@ -491,7 +504,7 @@ async function hashFile(path) {
|
|
|
491
504
|
}
|
|
492
505
|
async function cloneOrCopy(from, to, beforeReplace) {
|
|
493
506
|
await mkdir(dirname2(to), { recursive: true });
|
|
494
|
-
const temp =
|
|
507
|
+
const temp = atomicTempPath(to);
|
|
495
508
|
try {
|
|
496
509
|
await copyFile(from, temp, constants2.COPYFILE_FICLONE);
|
|
497
510
|
beforeReplace?.();
|
|
@@ -2133,6 +2146,10 @@ var PROP_TO_GROUP2 = /* @__PURE__ */ new Map();
|
|
|
2133
2146
|
for (const [group, props] of Object.entries(PROPERTY_GROUPS2)) {
|
|
2134
2147
|
for (const p of props) PROP_TO_GROUP2.set(p, group);
|
|
2135
2148
|
}
|
|
2149
|
+
function isXYPositionWrite(a) {
|
|
2150
|
+
const xy = (props) => !!props && ("x" in props || "y" in props);
|
|
2151
|
+
return a.propertyGroup === "position" && (xy(a.properties) || xy(a.fromProperties) || !!a.keyframes?.keyframes.some((k) => xy(k.properties)));
|
|
2152
|
+
}
|
|
2136
2153
|
function classifyPropertyGroup2(prop) {
|
|
2137
2154
|
return PROP_TO_GROUP2.get(prop) ?? "other";
|
|
2138
2155
|
}
|
|
@@ -3745,7 +3762,7 @@ function dedupePositionWritesInScript(script, selector, keepId) {
|
|
|
3745
3762
|
const parsed = parseGsapScriptAcornForWrite(script);
|
|
3746
3763
|
if (!parsed) return script;
|
|
3747
3764
|
const posWrites = parsed.located.filter(
|
|
3748
|
-
(l) => l.animation.targetSelector === selector && l.animation
|
|
3765
|
+
(l) => l.animation.targetSelector === selector && isXYPositionWrite(l.animation)
|
|
3749
3766
|
);
|
|
3750
3767
|
if (posWrites.length <= 1) return script;
|
|
3751
3768
|
const keeper = posWrites.find((l) => l.id === keepId) ?? posWrites[posWrites.length - 1];
|
|
@@ -5099,16 +5116,25 @@ import { createHash as createHash32, randomUUID as randomUUID22 } from "crypto";
|
|
|
5099
5116
|
import { readFileSync as readFileSync8 } from "fs";
|
|
5100
5117
|
import { existsSync as existsSync7, readFileSync as readFileSync10, statSync as statSync7 } from "fs";
|
|
5101
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";
|
|
5102
5128
|
import { closeSync as closeSync32, constants as constants3, fstatSync as fstatSync3, openSync as openSync32, readSync } from "fs";
|
|
5103
5129
|
import { streamSSE as streamSSE2 } from "hono/streaming";
|
|
5104
|
-
import { existsSync as
|
|
5105
|
-
import { basename as basename22, dirname as
|
|
5106
|
-
import { existsSync as
|
|
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";
|
|
5107
5133
|
import { homedir as homedir2 } from "os";
|
|
5108
|
-
import { join as
|
|
5109
|
-
import { createHash as
|
|
5110
|
-
import { readFileSync as
|
|
5111
|
-
import { join as
|
|
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";
|
|
5112
5138
|
async function filterCompositionFiles(projectDir, files) {
|
|
5113
5139
|
const htmlFiles = files.filter((f) => f.endsWith(".html") && !isInHiddenOrVendorDir(f));
|
|
5114
5140
|
const checks = await Promise.all(
|
|
@@ -5505,6 +5531,29 @@ function assertExpectedVersion(mutation, current) {
|
|
|
5505
5531
|
const stale = mutation.expectedVersion === void 0 ? current !== mutation.before : fileContentVersion(current) !== mutation.expectedVersion;
|
|
5506
5532
|
if (stale) throw new FileChangedError(mutation.sourceFile);
|
|
5507
5533
|
}
|
|
5534
|
+
var FONT_STYLE_RE = /<style\b[^>]*data-hf-studio-fonts=(["'])true\1[^>]*>([\s\S]*?)<\/style>/i;
|
|
5535
|
+
var ONE_FONT_FACE_RULE = /^@font-face \{(?:[^{}<"'\\]|"(?:[^"\\<\n\r\f]|\\[^<\n\r\f])*"|'(?:[^'\\<\n\r\f]|\\[^<\n\r\f])*')*\}$/;
|
|
5536
|
+
function isStudioFontFaceCss(css) {
|
|
5537
|
+
return typeof css === "string" && ONE_FONT_FACE_RULE.test(css);
|
|
5538
|
+
}
|
|
5539
|
+
function ensureStudioFontFaceCss(html, css) {
|
|
5540
|
+
if (html.includes(css)) return html;
|
|
5541
|
+
const block = FONT_STYLE_RE.exec(html);
|
|
5542
|
+
if (block) {
|
|
5543
|
+
const next = `${(block[2] ?? "").trim()}
|
|
5544
|
+
${css}`.trim();
|
|
5545
|
+
return html.replace(block[0], () => `<style data-hf-studio-fonts="true">
|
|
5546
|
+
${next}
|
|
5547
|
+
</style>`);
|
|
5548
|
+
}
|
|
5549
|
+
const styleTag = `<style data-hf-studio-fonts="true">
|
|
5550
|
+
${css}
|
|
5551
|
+
</style>`;
|
|
5552
|
+
if (/<\/head>/i.test(html)) return html.replace(/<\/head>/i, () => ` ${styleTag}
|
|
5553
|
+
</head>`);
|
|
5554
|
+
return `${styleTag}
|
|
5555
|
+
${html}`;
|
|
5556
|
+
}
|
|
5508
5557
|
var CompositionInsertionError = class extends Error {
|
|
5509
5558
|
constructor(message, status) {
|
|
5510
5559
|
super(message);
|
|
@@ -5764,7 +5813,7 @@ function requestSubPath(url, route) {
|
|
|
5764
5813
|
return decodeURIComponent(segments.slice(start + routeSegments.length).join("/"));
|
|
5765
5814
|
}
|
|
5766
5815
|
async function loadGsapParser() {
|
|
5767
|
-
return import("./gsapParser-
|
|
5816
|
+
return import("./gsapParser-KVMPC2SC.js");
|
|
5768
5817
|
}
|
|
5769
5818
|
function isDanglingSymlinkInProject(projectDir, lexicalPath) {
|
|
5770
5819
|
if (!isSafePath(projectDir, dirname32(lexicalPath))) return false;
|
|
@@ -6122,7 +6171,7 @@ function bakeGroupTransformIntoMembers(html, groupId, members, groupCenter) {
|
|
|
6122
6171
|
const rad = grot * Math.PI / 180;
|
|
6123
6172
|
const cos = Math.cos(rad);
|
|
6124
6173
|
const sin = Math.sin(rad);
|
|
6125
|
-
const
|
|
6174
|
+
const round32 = (n) => Math.round(n * 1e3) / 1e3;
|
|
6126
6175
|
let script = block.scriptText;
|
|
6127
6176
|
for (const m of members) {
|
|
6128
6177
|
const memberSel = `#${m.id}`;
|
|
@@ -6139,27 +6188,27 @@ function bakeGroupTransformIntoMembers(html, groupId, members, groupCenter) {
|
|
|
6139
6188
|
const visY = groupCenter.cy + gscale * (sin * dx + cos * dy) + gy;
|
|
6140
6189
|
const newProps = {
|
|
6141
6190
|
...mProps,
|
|
6142
|
-
x:
|
|
6143
|
-
y:
|
|
6191
|
+
x: round32(visX - m.cx),
|
|
6192
|
+
y: round32(visY - m.cy)
|
|
6144
6193
|
};
|
|
6145
6194
|
if (gz !== 0) newProps.z = (typeof mProps.z === "number" ? mProps.z : 0) + gz;
|
|
6146
6195
|
if (grot !== 0) {
|
|
6147
|
-
newProps.rotation =
|
|
6196
|
+
newProps.rotation = round32(
|
|
6148
6197
|
(typeof mProps.rotation === "number" ? mProps.rotation : 0) + grot
|
|
6149
6198
|
);
|
|
6150
6199
|
}
|
|
6151
6200
|
if (gscale !== 1) {
|
|
6152
|
-
newProps.scale =
|
|
6201
|
+
newProps.scale = round32((typeof mProps.scale === "number" ? mProps.scale : 1) * gscale);
|
|
6153
6202
|
}
|
|
6154
6203
|
const pivoted = /* @__PURE__ */ new Set(["x", "y", "z", "rotation", "scale"]);
|
|
6155
6204
|
for (const [k, v] of Object.entries(gt)) {
|
|
6156
6205
|
if (pivoted.has(k) || typeof v !== "number") continue;
|
|
6157
6206
|
if (k === "scaleX" || k === "scaleY") {
|
|
6158
|
-
if (v !== 1) newProps[k] =
|
|
6207
|
+
if (v !== 1) newProps[k] = round32((typeof mProps[k] === "number" ? mProps[k] : 1) * v);
|
|
6159
6208
|
} else if (k === "transformPerspective") {
|
|
6160
6209
|
if (typeof mProps[k] !== "number") newProps[k] = v;
|
|
6161
6210
|
} else if (v !== 0) {
|
|
6162
|
-
newProps[k] =
|
|
6211
|
+
newProps[k] = round32((typeof mProps[k] === "number" ? mProps[k] : 0) + v);
|
|
6163
6212
|
}
|
|
6164
6213
|
}
|
|
6165
6214
|
for (const s of [...sets].reverse()) {
|
|
@@ -6330,7 +6379,7 @@ async function prepareGsapMutationScript(c, res, firstMutation) {
|
|
|
6330
6379
|
let block = extractGsapScriptBlock(html);
|
|
6331
6380
|
if (!block && (firstMutation.type === "add" || firstMutation.type === "add-with-keyframes")) {
|
|
6332
6381
|
const compId = html.match(/data-composition-id="([^"]+)"/)?.[1] ?? "main";
|
|
6333
|
-
const { GSAP_CDN } = await import("./dist-
|
|
6382
|
+
const { GSAP_CDN } = await import("./dist-YUIPB7OX.js");
|
|
6334
6383
|
const bootstrap = [
|
|
6335
6384
|
`<script src="${GSAP_CDN}"></script>`,
|
|
6336
6385
|
"<script>",
|
|
@@ -6340,6 +6389,7 @@ async function prepareGsapMutationScript(c, res, firstMutation) {
|
|
|
6340
6389
|
"</script>"
|
|
6341
6390
|
].join("\n");
|
|
6342
6391
|
html = insertBeforeCloseTag(html, "body", `${bootstrap}
|
|
6392
|
+
`) ?? insertBeforeCloseTag(html, "template", `${bootstrap}
|
|
6343
6393
|
`) ?? `${html}
|
|
6344
6394
|
${bootstrap}`;
|
|
6345
6395
|
block = extractGsapScriptBlock(html);
|
|
@@ -7668,6 +7718,10 @@ function registerFileRoutes(api, adapter) {
|
|
|
7668
7718
|
if (!Array.isArray(parsed.body.operations) || parsed.body.operations.length === 0) {
|
|
7669
7719
|
return c.json({ error: "target and operations required" }, 400);
|
|
7670
7720
|
}
|
|
7721
|
+
const { fontFaceCss } = parsed.body;
|
|
7722
|
+
if (fontFaceCss !== void 0 && !isStudioFontFaceCss(fontFaceCss)) {
|
|
7723
|
+
return c.json({ error: "fontFaceCss must be one @font-face rule" }, 400);
|
|
7724
|
+
}
|
|
7671
7725
|
const unsafeFields = findUnsafeDomPatchValues(parsed.body);
|
|
7672
7726
|
if (unsafeFields.length > 0) {
|
|
7673
7727
|
return rejectUnsafeMutationValues(c, unsafeFields);
|
|
@@ -7679,11 +7733,9 @@ function registerFileRoutes(api, adapter) {
|
|
|
7679
7733
|
} catch {
|
|
7680
7734
|
return c.json({ error: "not found" }, 404);
|
|
7681
7735
|
}
|
|
7682
|
-
const
|
|
7683
|
-
|
|
7684
|
-
|
|
7685
|
-
parsed.body.operations
|
|
7686
|
-
);
|
|
7736
|
+
const element = patchElementInHtml(originalContent, parsed.target, parsed.body.operations);
|
|
7737
|
+
const { matched } = element;
|
|
7738
|
+
const patched = matched && isStudioFontFaceCss(fontFaceCss) ? ensureStudioFontFaceCss(element.html, fontFaceCss) : element.html;
|
|
7687
7739
|
if (patched === originalContent) {
|
|
7688
7740
|
const version2 = fileContentVersion(originalContent);
|
|
7689
7741
|
c.header("ETag", version2);
|
|
@@ -8911,7 +8963,8 @@ function registerRenderRoutes(api, adapter) {
|
|
|
8911
8963
|
progress: current.progress,
|
|
8912
8964
|
status: current.status,
|
|
8913
8965
|
stage: current.stage,
|
|
8914
|
-
error: current.error
|
|
8966
|
+
error: current.error,
|
|
8967
|
+
...current.status === "complete" && current.audioLoweredDb !== void 0 ? { audioLoweredDb: current.audioLoweredDb } : {}
|
|
8915
8968
|
})
|
|
8916
8969
|
});
|
|
8917
8970
|
if (current.status !== "rendering") break;
|
|
@@ -9020,11 +9073,13 @@ function registerRenderRoutes(api, adapter) {
|
|
|
9020
9073
|
const metaPath = join10(rendersDir, `${rid}.meta.json`);
|
|
9021
9074
|
let status = "complete";
|
|
9022
9075
|
let durationMs;
|
|
9076
|
+
let audioLoweredDb;
|
|
9023
9077
|
if (existsSync6(metaPath)) {
|
|
9024
9078
|
try {
|
|
9025
9079
|
const meta = JSON.parse(readFileSync72(metaPath, "utf-8"));
|
|
9026
9080
|
if (meta.status === "failed" && !existsSync6(fp)) status = "failed";
|
|
9027
9081
|
if (meta.durationMs) durationMs = meta.durationMs;
|
|
9082
|
+
if (typeof meta.audioLoweredDb === "number") audioLoweredDb = meta.audioLoweredDb;
|
|
9028
9083
|
} catch {
|
|
9029
9084
|
}
|
|
9030
9085
|
}
|
|
@@ -9034,7 +9089,8 @@ function registerRenderRoutes(api, adapter) {
|
|
|
9034
9089
|
size: stat22.size,
|
|
9035
9090
|
createdAt: stat22.mtimeMs,
|
|
9036
9091
|
status,
|
|
9037
|
-
durationMs
|
|
9092
|
+
durationMs,
|
|
9093
|
+
...audioLoweredDb !== void 0 ? { audioLoweredDb } : {}
|
|
9038
9094
|
};
|
|
9039
9095
|
}).sort((a, b) => b.createdAt - a.createdAt);
|
|
9040
9096
|
for (const file of files) {
|
|
@@ -9067,10 +9123,10 @@ var ThumbnailGenerationCoordinator = class {
|
|
|
9067
9123
|
if (signal.aborted) return Promise.reject(new DOMException("Aborted", "AbortError"));
|
|
9068
9124
|
let entry = this.entries.get(key);
|
|
9069
9125
|
if (!entry) {
|
|
9070
|
-
let
|
|
9126
|
+
let resolve7;
|
|
9071
9127
|
let reject;
|
|
9072
9128
|
const promise = new Promise((resolvePromise, rejectPromise) => {
|
|
9073
|
-
|
|
9129
|
+
resolve7 = resolvePromise;
|
|
9074
9130
|
reject = rejectPromise;
|
|
9075
9131
|
});
|
|
9076
9132
|
entry = {
|
|
@@ -9080,7 +9136,7 @@ var ThumbnailGenerationCoordinator = class {
|
|
|
9080
9136
|
state: "queued",
|
|
9081
9137
|
work,
|
|
9082
9138
|
promise,
|
|
9083
|
-
resolve:
|
|
9139
|
+
resolve: resolve7,
|
|
9084
9140
|
reject
|
|
9085
9141
|
};
|
|
9086
9142
|
this.entries.set(key, entry);
|
|
@@ -9094,7 +9150,7 @@ var ThumbnailGenerationCoordinator = class {
|
|
|
9094
9150
|
return /* @__PURE__ */ new Set([...this.entries.keys(), ...[...this.activeEntries].map((entry) => entry.key)]);
|
|
9095
9151
|
}
|
|
9096
9152
|
lease(entry, signal) {
|
|
9097
|
-
return new Promise((
|
|
9153
|
+
return new Promise((resolve7, reject) => {
|
|
9098
9154
|
let released = false;
|
|
9099
9155
|
const release = () => {
|
|
9100
9156
|
if (released) return;
|
|
@@ -9118,7 +9174,7 @@ var ThumbnailGenerationCoordinator = class {
|
|
|
9118
9174
|
entry.promise.then(
|
|
9119
9175
|
(value) => {
|
|
9120
9176
|
release();
|
|
9121
|
-
|
|
9177
|
+
resolve7(value);
|
|
9122
9178
|
},
|
|
9123
9179
|
(reason) => {
|
|
9124
9180
|
release();
|
|
@@ -9193,14 +9249,14 @@ async function generateThumbnail(path, signal) {
|
|
|
9193
9249
|
async function resolveSource(adapter, id, url) {
|
|
9194
9250
|
const project = await adapter.resolveProject(id);
|
|
9195
9251
|
if (!project) throw new HTTPException(404);
|
|
9196
|
-
let
|
|
9252
|
+
let relative5;
|
|
9197
9253
|
try {
|
|
9198
|
-
|
|
9254
|
+
relative5 = requestSubPath(url, "projects/:id/image-thumbnail");
|
|
9199
9255
|
} catch {
|
|
9200
9256
|
throw new HTTPException(400);
|
|
9201
9257
|
}
|
|
9202
|
-
if (!/\.jpe?g$/i.test(
|
|
9203
|
-
const path = pinWithinProject(project.dir,
|
|
9258
|
+
if (!/\.jpe?g$/i.test(relative5)) throw new HTTPException(415);
|
|
9259
|
+
const path = pinWithinProject(project.dir, relative5);
|
|
9204
9260
|
if (!path) throw new HTTPException(404);
|
|
9205
9261
|
const info = await stat2(path).catch(() => {
|
|
9206
9262
|
throw new HTTPException(404);
|
|
@@ -9541,6 +9597,493 @@ function registerWaveformRoutes(api, adapter) {
|
|
|
9541
9597
|
return c.json({ peaks });
|
|
9542
9598
|
});
|
|
9543
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
|
+
}
|
|
9544
10087
|
var MAX_FONT_RESULTS = 2e3;
|
|
9545
10088
|
var GOOGLE_FONTS_METADATA_URL = "https://fonts.google.com/metadata/fonts";
|
|
9546
10089
|
var GOOGLE_FONTS_FETCH_TIMEOUT_MS = 3e3;
|
|
@@ -9568,7 +10111,7 @@ var GOOGLE_FONT_FALLBACKS = [
|
|
|
9568
10111
|
"Outfit",
|
|
9569
10112
|
"JetBrains Mono"
|
|
9570
10113
|
];
|
|
9571
|
-
function
|
|
10114
|
+
function isRecord2(value) {
|
|
9572
10115
|
return typeof value === "object" && value !== null;
|
|
9573
10116
|
}
|
|
9574
10117
|
function collectFontsFromDir(dir) {
|
|
@@ -9592,10 +10135,10 @@ function listInstalledFontFamilies() {
|
|
|
9592
10135
|
return cachedFonts;
|
|
9593
10136
|
}
|
|
9594
10137
|
function parseGoogleFontMetadata(value) {
|
|
9595
|
-
if (!
|
|
10138
|
+
if (!isRecord2(value) || !Array.isArray(value.familyMetadataList)) return [];
|
|
9596
10139
|
const families = [];
|
|
9597
10140
|
for (const entry of value.familyMetadataList) {
|
|
9598
|
-
if (!
|
|
10141
|
+
if (!isRecord2(entry) || typeof entry.family !== "string") continue;
|
|
9599
10142
|
families.push(entry.family);
|
|
9600
10143
|
}
|
|
9601
10144
|
return families;
|
|
@@ -9695,14 +10238,14 @@ function registerRegistryRoutes(api, adapter) {
|
|
|
9695
10238
|
}
|
|
9696
10239
|
});
|
|
9697
10240
|
}
|
|
9698
|
-
function
|
|
10241
|
+
function isRecord3(value) {
|
|
9699
10242
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
9700
10243
|
}
|
|
9701
10244
|
function isFiniteNumber(value) {
|
|
9702
10245
|
return typeof value === "number" && Number.isFinite(value);
|
|
9703
10246
|
}
|
|
9704
10247
|
function isStringRecord(value) {
|
|
9705
|
-
return
|
|
10248
|
+
return isRecord3(value) && Object.values(value).every((v) => typeof v === "string");
|
|
9706
10249
|
}
|
|
9707
10250
|
function hasString(value, key) {
|
|
9708
10251
|
return typeof value[key] === "string";
|
|
@@ -9720,20 +10263,20 @@ function hasOptionalNumber(value, key) {
|
|
|
9720
10263
|
return value[key] === void 0 || isFiniteNumber(value[key]);
|
|
9721
10264
|
}
|
|
9722
10265
|
function isBoundingBox(value) {
|
|
9723
|
-
return
|
|
10266
|
+
return isRecord3(value) && ["x", "y", "width", "height"].every((key) => isFiniteNumber(value[key]));
|
|
9724
10267
|
}
|
|
9725
10268
|
function isTarget(value) {
|
|
9726
|
-
if (!
|
|
10269
|
+
if (!isRecord3(value)) return false;
|
|
9727
10270
|
return hasOptionalNullableString(value, "id") && hasOptionalString(value, "hfId") && hasOptionalString(value, "selector") && hasOptionalNumber(value, "selectorIndex");
|
|
9728
10271
|
}
|
|
9729
10272
|
function isTextField(value) {
|
|
9730
|
-
return
|
|
10273
|
+
return isRecord3(value) && hasRequiredStrings(value, ["key", "label", "value", "tagName"]) && ["self", "child", "text-node"].includes(value.source);
|
|
9731
10274
|
}
|
|
9732
10275
|
function isTextFields(value) {
|
|
9733
10276
|
return Array.isArray(value) && value.every(isTextField);
|
|
9734
10277
|
}
|
|
9735
10278
|
function isSelectionSnapshot(value) {
|
|
9736
|
-
if (!
|
|
10279
|
+
if (!isRecord3(value)) return false;
|
|
9737
10280
|
const checks = [
|
|
9738
10281
|
value.schemaVersion === 1 && hasRequiredStrings(value, [
|
|
9739
10282
|
"projectId",
|
|
@@ -9751,7 +10294,7 @@ function isSelectionSnapshot(value) {
|
|
|
9751
10294
|
isStringRecord(value.inlineStyles),
|
|
9752
10295
|
isStringRecord(value.computedStyles),
|
|
9753
10296
|
isTextFields(value.textFields),
|
|
9754
|
-
|
|
10297
|
+
isRecord3(value.capabilities)
|
|
9755
10298
|
];
|
|
9756
10299
|
return checks.every(Boolean);
|
|
9757
10300
|
}
|
|
@@ -9775,7 +10318,7 @@ function registerSelectionRoutes(api, adapter) {
|
|
|
9775
10318
|
} catch {
|
|
9776
10319
|
return c.json({ error: "invalid json" }, 400);
|
|
9777
10320
|
}
|
|
9778
|
-
if (!
|
|
10321
|
+
if (!isRecord3(body) || !("selection" in body)) {
|
|
9779
10322
|
return c.json({ error: "missing selection" }, 400);
|
|
9780
10323
|
}
|
|
9781
10324
|
if (body.selection === null) {
|
|
@@ -9827,7 +10370,7 @@ function uniqueAssetPath(projectDir, assetPath, taken) {
|
|
|
9827
10370
|
const ext = extname2(assetPath);
|
|
9828
10371
|
const withoutExt = assetPath.slice(0, -ext.length);
|
|
9829
10372
|
let candidate = assetPath;
|
|
9830
|
-
for (let index = 2; taken.has(candidate) ||
|
|
10373
|
+
for (let index = 2; taken.has(candidate) || existsSync11(join15(projectDir, candidate)); index++) {
|
|
9831
10374
|
candidate = `${withoutExt}-${index}${ext}`;
|
|
9832
10375
|
}
|
|
9833
10376
|
return candidate;
|
|
@@ -9888,7 +10431,7 @@ function registerMediaRoutes(api, adapter, options = {}) {
|
|
|
9888
10431
|
}
|
|
9889
10432
|
const filePath = resolveWithinProject(project.dir, assetPath);
|
|
9890
10433
|
if (!filePath) return c.json({ error: "forbidden" }, 403);
|
|
9891
|
-
if (!
|
|
10434
|
+
if (!existsSync11(filePath)) return c.json({ error: "media not found" }, 404);
|
|
9892
10435
|
return c.json({ path: assetPath, metadata: await readMediaMetadata(filePath) });
|
|
9893
10436
|
});
|
|
9894
10437
|
api.post(
|
|
@@ -9910,7 +10453,7 @@ function registerMediaRoutes(api, adapter, options = {}) {
|
|
|
9910
10453
|
}
|
|
9911
10454
|
const inputPath = resolveWithinProject(project.dir, inputAssetPath);
|
|
9912
10455
|
if (!inputPath) return c.json({ error: "forbidden" }, 403);
|
|
9913
|
-
if (!
|
|
10456
|
+
if (!existsSync11(inputPath)) return c.json({ error: "input media not found" }, 404);
|
|
9914
10457
|
const inputIsVideo = isVideoPath(inputAssetPath);
|
|
9915
10458
|
const inputIsImage = isImagePath(inputAssetPath);
|
|
9916
10459
|
if (!inputIsVideo && !inputIsImage) {
|
|
@@ -9946,8 +10489,8 @@ function registerMediaRoutes(api, adapter, options = {}) {
|
|
|
9946
10489
|
return c.json({ error: "forbidden" }, 403);
|
|
9947
10490
|
}
|
|
9948
10491
|
}
|
|
9949
|
-
mkdirWithinProject(project.dir,
|
|
9950
|
-
if (backgroundOutputPath) mkdirWithinProject(project.dir,
|
|
10492
|
+
mkdirWithinProject(project.dir, dirname6(outputPath));
|
|
10493
|
+
if (backgroundOutputPath) mkdirWithinProject(project.dir, dirname6(backgroundOutputPath));
|
|
9951
10494
|
const jobId = makeJobId(project.id, mediaJobs);
|
|
9952
10495
|
const state = adapter.startBackgroundRemoval({
|
|
9953
10496
|
project,
|
|
@@ -10003,10 +10546,10 @@ function registerMediaRoutes(api, adapter, options = {}) {
|
|
|
10003
10546
|
});
|
|
10004
10547
|
}
|
|
10005
10548
|
function readGlobalAssets(home = homedir2()) {
|
|
10006
|
-
const manifestPath =
|
|
10007
|
-
if (!
|
|
10549
|
+
const manifestPath = join16(home, ".media", "manifest.jsonl");
|
|
10550
|
+
if (!existsSync12(manifestPath)) return [];
|
|
10008
10551
|
const out = [];
|
|
10009
|
-
for (const line of
|
|
10552
|
+
for (const line of readFileSync13(manifestPath, "utf8").split("\n")) {
|
|
10010
10553
|
if (!line.trim()) continue;
|
|
10011
10554
|
try {
|
|
10012
10555
|
const rec = JSON.parse(line);
|
|
@@ -10189,6 +10732,9 @@ function createStudioApi(adapter) {
|
|
|
10189
10732
|
registerSelectionRoutes(api, adapter);
|
|
10190
10733
|
registerMediaRoutes(api, adapter);
|
|
10191
10734
|
registerWaveformRoutes(api, adapter);
|
|
10735
|
+
registerFreezeFrameRoutes(api, adapter);
|
|
10736
|
+
registerLoudnessRoutes(api, adapter);
|
|
10737
|
+
registerPeakRoutes(api, adapter);
|
|
10192
10738
|
registerFontRoutes(api);
|
|
10193
10739
|
registerRegistryRoutes(api, adapter);
|
|
10194
10740
|
registerGlobalAssetRoutes(api);
|
|
@@ -10204,23 +10750,23 @@ function duplicateElementInHtml(source, target, newId, at) {
|
|
|
10204
10750
|
if (duration === null || numericAttribute(element, "data-start") === null) {
|
|
10205
10751
|
return { html: source, matched: false, newId: null };
|
|
10206
10752
|
}
|
|
10207
|
-
const
|
|
10753
|
+
const uniqueId2 = nextUniqueId(document2, newId);
|
|
10208
10754
|
rippleElements(document2, element, at, duration, track);
|
|
10209
10755
|
const clone = element.cloneNode(true);
|
|
10210
10756
|
if (!isHTMLElement(clone)) return { html: source, matched: false, newId: null };
|
|
10211
|
-
clone.setAttribute("id",
|
|
10757
|
+
clone.setAttribute("id", uniqueId2);
|
|
10212
10758
|
dedupeClonedCompositionId(document2, clone);
|
|
10213
10759
|
stripStableIds(clone);
|
|
10214
10760
|
clone.setAttribute("data-start", String(at));
|
|
10215
10761
|
element.parentElement.insertBefore(clone, element.nextSibling);
|
|
10216
10762
|
const html = wrappedFragment ? document2.body.innerHTML || "" : document2.toString();
|
|
10217
|
-
return { html: ensureHfIds(html), matched: true, newId:
|
|
10763
|
+
return { html: ensureHfIds(html), matched: true, newId: uniqueId2 };
|
|
10218
10764
|
}
|
|
10219
10765
|
function nextUniqueId(document2, newId) {
|
|
10220
|
-
let
|
|
10766
|
+
let uniqueId2 = newId;
|
|
10221
10767
|
let suffix = 2;
|
|
10222
|
-
while (document2.getElementById(
|
|
10223
|
-
return
|
|
10768
|
+
while (document2.getElementById(uniqueId2)) uniqueId2 = `${newId}-${suffix++}`;
|
|
10769
|
+
return uniqueId2;
|
|
10224
10770
|
}
|
|
10225
10771
|
function rippleElements(document2, element, at, duration, track) {
|
|
10226
10772
|
for (const candidate of Array.from(document2.querySelectorAll("[data-start][data-duration]"))) {
|
|
@@ -10245,13 +10791,13 @@ function numericAttribute(element, name) {
|
|
|
10245
10791
|
return Number.isFinite(value) ? value : null;
|
|
10246
10792
|
}
|
|
10247
10793
|
function createPreviewDocumentStore(projectDir, salt) {
|
|
10248
|
-
const dir =
|
|
10249
|
-
const fileFor = (key) =>
|
|
10794
|
+
const dir = join17(projectDir, ".hyperframes", "preview");
|
|
10795
|
+
const fileFor = (key) => join17(dir, `${createHash5("sha256").update(`${salt}
|
|
10250
10796
|
${key}`).digest("hex").slice(0, 32)}.html`);
|
|
10251
10797
|
return {
|
|
10252
10798
|
read(key) {
|
|
10253
10799
|
try {
|
|
10254
|
-
return
|
|
10800
|
+
return readFileSync14(fileFor(key), "utf-8");
|
|
10255
10801
|
} catch {
|
|
10256
10802
|
return null;
|
|
10257
10803
|
}
|
|
@@ -10262,7 +10808,7 @@ ${key}`).digest("hex").slice(0, 32)}.html`);
|
|
|
10262
10808
|
const file = fileFor(key);
|
|
10263
10809
|
replaceFileAtomically(file, html, 420);
|
|
10264
10810
|
for (const name of readdirSync5(dir)) {
|
|
10265
|
-
if (
|
|
10811
|
+
if (join17(dir, name) !== file) rmSync5(join17(dir, name), { force: true });
|
|
10266
10812
|
}
|
|
10267
10813
|
} catch {
|
|
10268
10814
|
}
|