@hyperframes/studio-server 0.7.60 → 0.7.61

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 (40) hide show
  1. package/dist/chunk-4ETS2LXI.js +192 -0
  2. package/dist/chunk-4ETS2LXI.js.map +1 -0
  3. package/dist/chunk-6XMC64FJ.js +584 -0
  4. package/dist/chunk-6XMC64FJ.js.map +1 -0
  5. package/dist/chunk-LVXVG4V6.js +282 -0
  6. package/dist/chunk-LVXVG4V6.js.map +1 -0
  7. package/dist/chunk-W2SBTCO2.js +32 -0
  8. package/dist/chunk-W2SBTCO2.js.map +1 -0
  9. package/dist/chunk-X62ASOGO.js +30 -0
  10. package/dist/chunk-X62ASOGO.js.map +1 -0
  11. package/dist/chunk-XVQX2JHE.js +422 -0
  12. package/dist/chunk-XVQX2JHE.js.map +1 -0
  13. package/dist/chunk-YBR7MXIO.js +459 -0
  14. package/dist/chunk-YBR7MXIO.js.map +1 -0
  15. package/dist/chunk-ZUW4PULZ.js +66 -0
  16. package/dist/chunk-ZUW4PULZ.js.map +1 -0
  17. package/dist/helpers/finiteMutation.js +4 -24
  18. package/dist/helpers/finiteMutation.js.map +1 -1
  19. package/dist/helpers/manualEditsRenderScript.js +5 -577
  20. package/dist/helpers/manualEditsRenderScript.js.map +1 -1
  21. package/dist/helpers/mediaCodecMap.d.ts +100 -0
  22. package/dist/helpers/mediaCodecMap.js +25 -0
  23. package/dist/helpers/mediaCodecMap.js.map +1 -0
  24. package/dist/helpers/mediaProxyPreview.d.ts +4 -0
  25. package/dist/helpers/mediaProxyPreview.js +17 -0
  26. package/dist/helpers/mediaProxyPreview.js.map +1 -0
  27. package/dist/helpers/proxyTranscoder.d.ts +61 -0
  28. package/dist/helpers/proxyTranscoder.js +30 -0
  29. package/dist/helpers/proxyTranscoder.js.map +1 -0
  30. package/dist/helpers/screenshotClip.js +3 -22
  31. package/dist/helpers/screenshotClip.js.map +1 -1
  32. package/dist/helpers/sourceMutation.js +9 -419
  33. package/dist/helpers/sourceMutation.js.map +1 -1
  34. package/dist/helpers/studioMotionRenderScript.js +4 -186
  35. package/dist/helpers/studioMotionRenderScript.js.map +1 -1
  36. package/dist/index.d.ts +6 -202
  37. package/dist/index.js +172 -1373
  38. package/dist/index.js.map +1 -1
  39. package/dist/mediaProxyPreview-CeshDUjE.d.ts +259 -0
  40. package/package.json +15 -3
package/dist/index.js CHANGED
@@ -1,3 +1,48 @@
1
+ import {
2
+ findUnsafeDomPatchValues,
3
+ findUnsafeMutationValues
4
+ } from "./chunk-X62ASOGO.js";
5
+ import {
6
+ isHTMLElement,
7
+ patchElementInHtml,
8
+ probeElementInSource,
9
+ removeElementFromHtml,
10
+ splitElementInHtml,
11
+ unwrapElementsFromHtml,
12
+ wrapElementsInHtml
13
+ } from "./chunk-XVQX2JHE.js";
14
+ import {
15
+ getElementScreenshotClip
16
+ } from "./chunk-W2SBTCO2.js";
17
+ import {
18
+ injectMediaCodecMap,
19
+ isAutoProxyEnabled,
20
+ proxyEtagSalt,
21
+ resolvePreviewMediaCodecProbeCache
22
+ } from "./chunk-ZUW4PULZ.js";
23
+ import {
24
+ ProxyCapacityError,
25
+ ProxyTranscodeError,
26
+ resolveProxy
27
+ } from "./chunk-YBR7MXIO.js";
28
+ import {
29
+ PROXY_VARIANT_CONFIG,
30
+ decideMediaProxyEligibility,
31
+ isProxyVariantRequest,
32
+ probeAssetCodec,
33
+ probeMediaMetadata,
34
+ resolveProxyVariantRequest
35
+ } from "./chunk-LVXVG4V6.js";
36
+ import {
37
+ STUDIO_MANUAL_EDITS_PATH,
38
+ createStudioManualEditsRenderBodyScript,
39
+ createStudioPositionSeekReapplyScript
40
+ } from "./chunk-6XMC64FJ.js";
41
+ import {
42
+ STUDIO_MOTION_PATH,
43
+ createStudioMotionRenderBodyScript
44
+ } from "./chunk-4ETS2LXI.js";
45
+
1
46
  // src/createStudioApi.ts
2
47
  import { Hono } from "hono";
3
48
 
@@ -302,7 +347,7 @@ import {
302
347
  renameSync,
303
348
  readdirSync as readdirSync4
304
349
  } from "fs";
305
- import { resolve as resolve3, dirname, join as join6 } from "path";
350
+ import { resolve as resolve2, dirname, join as join6 } from "path";
306
351
 
307
352
  // src/helpers/mime.ts
308
353
  var MIME_TYPES = {
@@ -319,7 +364,13 @@ var MIME_TYPES = {
319
364
  ".webp": "image/webp",
320
365
  ".ico": "image/x-icon",
321
366
  ".mp4": "video/mp4",
367
+ ".m4v": "video/mp4",
322
368
  ".mov": "video/quicktime",
369
+ ".mkv": "video/x-matroska",
370
+ ".mxf": "video/mxf",
371
+ ".mts": "video/mp2t",
372
+ ".m2ts": "video/mp2t",
373
+ ".ts": "video/mp2t",
323
374
  ".webm": "video/webm",
324
375
  ".mp3": "audio/mpeg",
325
376
  ".wav": "audio/wav",
@@ -347,7 +398,8 @@ function isAudioFile(name) {
347
398
  // src/helpers/waveform.ts
348
399
  import { spawn } from "child_process";
349
400
  import { existsSync as existsSync2, writeFileSync, mkdirSync } from "fs";
350
- import { join as join3, resolve as resolve2 } from "path";
401
+ import { join as join3 } from "path";
402
+ import { findFfBinary } from "@hyperframes/parsers/ff-binaries";
351
403
  var SAMPLE_RATE = 4e3;
352
404
  var PEAK_COUNT = 4e3;
353
405
  var WAVEFORM_CACHE_VERSION = "v2";
@@ -370,15 +422,10 @@ function computePeaks(floats, count) {
370
422
  const maxPeak = Math.max(...peaks, 1e-3);
371
423
  return peaks.map((p) => p / maxPeak);
372
424
  }
373
- function ffmpegBinary() {
374
- const configured = process.env.HYPERFRAMES_FFMPEG_PATH?.trim();
375
- if (configured) return resolve2(configured);
376
- return "ffmpeg";
377
- }
378
425
  function decodeAudioPeaks(audioPath) {
379
426
  return new Promise((resolvePromise, reject) => {
380
427
  const proc = spawn(
381
- ffmpegBinary(),
428
+ findFfBinary("ffmpeg") ?? "ffmpeg",
382
429
  [
383
430
  "-i",
384
431
  audioPath,
@@ -430,7 +477,7 @@ import { spawnSync } from "child_process";
430
477
  import { mkdtempSync, rmSync, writeFileSync as writeFileSync2 } from "fs";
431
478
  import { tmpdir } from "os";
432
479
  import { basename, join as join4 } from "path";
433
- var VIDEO_EXT = /\.(mp4|webm|mov)$/i;
480
+ var VIDEO_EXT = /\.(mp4|webm|mov|mkv|avi|m4v|mxf|mts|m2ts|ts)$/i;
434
481
  var AUDIO_EXT = /\.(mp3|wav|ogg|m4a|aac)$/i;
435
482
  function validateUploadedMedia(filePath, runner = spawnSync) {
436
483
  const isVideo = VIDEO_EXT.test(filePath);
@@ -580,31 +627,6 @@ function consumeFileWriteReceipt(absPath) {
580
627
  return { path, version, writeToken };
581
628
  }
582
629
 
583
- // src/helpers/finiteMutation.ts
584
- function findUnsafeMutationValues(value, path = "body", options = {}) {
585
- if (value === null) {
586
- return options.allowNullPath?.(path) ? [] : [{ path, reason: "null" }];
587
- }
588
- if (typeof value === "number") {
589
- return Number.isFinite(value) ? [] : [{ path, reason: "non-finite-number" }];
590
- }
591
- if (!value || typeof value !== "object") return [];
592
- if (Array.isArray(value)) {
593
- return value.flatMap(
594
- (item, index) => findUnsafeMutationValues(item, `${path}[${index}]`, options)
595
- );
596
- }
597
- return Object.entries(value).flatMap(
598
- ([key, item]) => findUnsafeMutationValues(item, `${path}.${key}`, options)
599
- );
600
- }
601
- var DOM_PATCH_NULL_VALUE_PATH = /^body\.operations\[\d+\]\.value$/;
602
- function findUnsafeDomPatchValues(value) {
603
- return findUnsafeMutationValues(value, "body", {
604
- allowNullPath: (path) => DOM_PATCH_NULL_VALUE_PATH.test(path)
605
- });
606
- }
607
-
608
630
  // src/routes/files.ts
609
631
  import { classifyPropertyGroup } from "@hyperframes/parsers/gsap-constants";
610
632
  import { parseGsapScriptAcorn } from "@hyperframes/parsers/gsap-parser-acorn";
@@ -632,429 +654,7 @@ import {
632
654
  scalePositionsInScript,
633
655
  dedupePositionWritesInScript
634
656
  } from "@hyperframes/parsers/gsap-writer-acorn";
635
-
636
- // src/helpers/sourceMutation.ts
637
657
  import { parseHTML } from "linkedom";
638
- import postcss from "postcss";
639
- import selectorParser from "postcss-selector-parser";
640
- import { isAllowedHtmlAttribute, isSafeAttributeValue } from "@hyperframes/core/html-attr-safety";
641
- import { ensureHfIds } from "@hyperframes/parsers/hf-ids";
642
-
643
- // src/helpers/sourceStyleMutation.ts
644
- function parseStyleDecls(style) {
645
- const props = /* @__PURE__ */ new Map();
646
- const order = [];
647
- let i = 0;
648
- while (i < style.length) {
649
- let depth = 0;
650
- let inSingle = false;
651
- let inDouble = false;
652
- const start = i;
653
- while (i < style.length) {
654
- const ch = style[i];
655
- if (ch === "'" && !inDouble) inSingle = !inSingle;
656
- else if (ch === '"' && !inSingle) inDouble = !inDouble;
657
- else if (!inSingle && !inDouble) {
658
- if (ch === "(") depth++;
659
- else if (ch === ")") depth = Math.max(0, depth - 1);
660
- else if (ch === ";" && depth === 0) break;
661
- }
662
- i++;
663
- }
664
- const decl = style.slice(start, i).trim();
665
- i++;
666
- if (!decl) continue;
667
- const colon = decl.indexOf(":");
668
- if (colon < 0) continue;
669
- const key = decl.slice(0, colon).trim();
670
- const val = decl.slice(colon + 1).trim();
671
- if (!key) continue;
672
- if (!props.has(key)) order.push(key);
673
- props.set(key, val);
674
- }
675
- return { props, order };
676
- }
677
- function serializeStyleDecls(props, order) {
678
- return order.map((k) => `${k}: ${props.get(k) ?? ""}`).filter((d) => d.trim()).join("; ");
679
- }
680
- function patchStyleAttrString(style, property, value) {
681
- const { props, order } = parseStyleDecls(style);
682
- if (value === null) {
683
- props.delete(property);
684
- const idx = order.indexOf(property);
685
- if (idx >= 0) order.splice(idx, 1);
686
- } else {
687
- if (!props.has(property)) order.push(property);
688
- props.set(property, value);
689
- }
690
- return serializeStyleDecls(props, order);
691
- }
692
-
693
- // src/helpers/sourceMutation.ts
694
- function parseSourceDocument(source) {
695
- const hasDocumentShell = /<!doctype|<html[\s>]/i.test(source);
696
- if (hasDocumentShell) {
697
- return { document: parseHTML(source).document, wrappedFragment: false };
698
- }
699
- return {
700
- document: parseHTML(`<!DOCTYPE html><html><head></head><body>${source}</body></html>`).document,
701
- wrappedFragment: true
702
- };
703
- }
704
- function duplicateCssRulesForId(document2, originalId, newId) {
705
- const idToken = `#${originalId}`;
706
- const transform = selectorParser((selectors) => {
707
- selectors.walkIds((node) => {
708
- if (node.value === originalId) node.value = newId;
709
- });
710
- });
711
- for (const styleEl of document2.querySelectorAll("style")) {
712
- const css = styleEl.textContent ?? "";
713
- let root;
714
- try {
715
- root = postcss.parse(css);
716
- } catch {
717
- continue;
718
- }
719
- const clones = [];
720
- root.walkRules((rule) => {
721
- if (!rule.selector.includes(idToken)) return;
722
- const newSelector = transform.processSync(rule.selector);
723
- if (newSelector === rule.selector) return;
724
- const clone = rule.clone({ selector: newSelector });
725
- clones.push(clone);
726
- });
727
- if (clones.length > 0) {
728
- for (const c of clones) root.append(c);
729
- styleEl.textContent = root.toString();
730
- }
731
- }
732
- }
733
- function querySelectorAllWithTemplates(root, selector) {
734
- const matches = Array.from(root.querySelectorAll(selector));
735
- if (matches.length > 0) return matches;
736
- const templates = Array.from(root.querySelectorAll("template"));
737
- for (const tmpl of templates) {
738
- const inner = querySelectorAllWithTemplates(tmpl, selector);
739
- if (inner.length > 0) return inner;
740
- }
741
- return [];
742
- }
743
- function escapeCssAttrValue(value) {
744
- return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
745
- }
746
- function findByHfId(document2, hfId) {
747
- try {
748
- const matches = querySelectorAllWithTemplates(
749
- document2,
750
- `[data-hf-id="${escapeCssAttrValue(hfId)}"]`
751
- );
752
- if (matches.length > 1) {
753
- console.warn(
754
- `sourceMutation: data-hf-id "${hfId}" matched ${matches.length} elements; using the first. ids must be unique per document.`
755
- );
756
- }
757
- return matches[0] ?? null;
758
- } catch {
759
- return null;
760
- }
761
- }
762
- function findTargetElement(document2, target) {
763
- if (target.hfId) {
764
- const el = findByHfId(document2, target.hfId);
765
- if (el) return el;
766
- }
767
- if (target.id) {
768
- const byId = document2.getElementById(target.id);
769
- if (byId) return byId;
770
- }
771
- if (!target.selector) return null;
772
- try {
773
- const matches = querySelectorAllWithTemplates(document2, target.selector);
774
- return matches[target.selectorIndex ?? 0] ?? null;
775
- } catch {
776
- return null;
777
- }
778
- }
779
- function removeElementFromHtml(source, target) {
780
- const { document: document2, wrappedFragment } = parseSourceDocument(source);
781
- const element = findTargetElement(document2, target);
782
- if (!element) return source;
783
- element.remove();
784
- return wrappedFragment ? document2.body.innerHTML || "" : document2.toString();
785
- }
786
- function isHTMLElement(el) {
787
- const HTMLEl = el.ownerDocument?.defaultView?.HTMLElement;
788
- return HTMLEl ? el instanceof HTMLEl : el.nodeType === 1 && "style" in el;
789
- }
790
- function resolveOperationTarget(parent, op) {
791
- if (op.childSelector === void 0) return parent;
792
- try {
793
- const child = parent.querySelectorAll(op.childSelector)[op.childIndex ?? 0] ?? null;
794
- return child && isHTMLElement(child) ? child : null;
795
- } catch {
796
- return null;
797
- }
798
- }
799
- function patchElementInHtml(source, target, operations) {
800
- const { document: document2, wrappedFragment } = parseSourceDocument(source);
801
- const el = findTargetElement(document2, target);
802
- if (!el || !isHTMLElement(el)) return { html: source, matched: false };
803
- const htmlEl = el;
804
- const resolved = [];
805
- for (const op of operations) {
806
- const opTarget = resolveOperationTarget(htmlEl, op);
807
- if (!opTarget) return { html: source, matched: false };
808
- resolved.push({ op, target: opTarget });
809
- }
810
- for (const { op, target: opTarget } of resolved) {
811
- switch (op.type) {
812
- case "inline-style":
813
- {
814
- const raw = opTarget.getAttribute("style") ?? "";
815
- const patched = patchStyleAttrString(raw, op.property, op.value);
816
- opTarget.setAttribute("style", patched);
817
- }
818
- break;
819
- case "attribute":
820
- {
821
- const fullAttr = op.property.startsWith("data-") ? op.property : `data-${op.property}`;
822
- if (op.value != null) {
823
- opTarget.setAttribute(fullAttr, op.value);
824
- } else {
825
- opTarget.removeAttribute(fullAttr);
826
- }
827
- }
828
- break;
829
- case "html-attribute":
830
- if (!isAllowedHtmlAttribute(op.property)) break;
831
- if (op.value != null) {
832
- if (!isSafeAttributeValue(op.property, op.value)) break;
833
- opTarget.setAttribute(op.property, op.value);
834
- } else {
835
- opTarget.removeAttribute(op.property);
836
- }
837
- break;
838
- case "text-content":
839
- if (op.value != null) {
840
- const inner = opTarget.children.length === 1 ? opTarget.firstElementChild : null;
841
- const textTarget = inner && isHTMLElement(inner) ? inner : opTarget;
842
- textTarget.textContent = op.value;
843
- }
844
- break;
845
- }
846
- }
847
- return {
848
- html: wrappedFragment ? document2.body.innerHTML || "" : document2.toString(),
849
- matched: true
850
- };
851
- }
852
- function probeElementInSource(source, target) {
853
- if (!target.id && !target.hfId && !target.selector) return false;
854
- const { document: document2 } = parseSourceDocument(source);
855
- const el = findTargetElement(document2, target);
856
- return el != null && isHTMLElement(el);
857
- }
858
- function resolveElementTiming(el) {
859
- const start = parseFloat(el.getAttribute("data-start") ?? "0") || 0;
860
- const usesDataEnd = el.hasAttribute("data-end");
861
- const duration = usesDataEnd ? parseFloat(el.getAttribute("data-end") ?? "") - start || 0 : parseFloat(el.getAttribute("data-duration") ?? "0") || 0;
862
- return { start, duration, usesDataEnd };
863
- }
864
- function setElementDuration(el, start, duration, usesDataEnd) {
865
- if (usesDataEnd) {
866
- const endTime = String(Math.round((start + duration) * 1e3) / 1e3);
867
- el.setAttribute("data-end", endTime);
868
- el.removeAttribute("data-duration");
869
- } else {
870
- el.setAttribute("data-duration", String(Math.round(duration * 1e3) / 1e3));
871
- el.removeAttribute("data-end");
872
- }
873
- }
874
- function splitElementInHtml(source, target, splitTime, newId, fallbackTiming) {
875
- const { document: document2, wrappedFragment } = parseSourceDocument(source);
876
- const el = findTargetElement(document2, target);
877
- if (!el || !isHTMLElement(el)) return { html: source, matched: false, newId: null };
878
- const timing = resolveElementTiming(el);
879
- const { usesDataEnd } = timing;
880
- let { start, duration } = timing;
881
- if (duration <= 0 && fallbackTiming && fallbackTiming.duration > 0) {
882
- start = fallbackTiming.start;
883
- duration = fallbackTiming.duration;
884
- }
885
- if (duration <= 0 || splitTime <= start || splitTime >= start + duration) {
886
- return { html: source, matched: false, newId: null };
887
- }
888
- if (document2.getElementById(newId)) {
889
- let suffix = 2;
890
- const base = newId;
891
- while (document2.getElementById(newId)) {
892
- newId = `${base}-${suffix++}`;
893
- }
894
- }
895
- const firstDuration = splitTime - start;
896
- const secondDuration = duration - firstDuration;
897
- const clone = el.cloneNode(true);
898
- if (!isHTMLElement(clone)) return { html: source, matched: false, newId: null };
899
- clone.setAttribute("id", newId);
900
- clone.removeAttribute("data-hf-id");
901
- for (const node of clone.querySelectorAll("[data-hf-id]")) node.removeAttribute("data-hf-id");
902
- clone.setAttribute("data-start", String(Math.round(splitTime * 1e3) / 1e3));
903
- setElementDuration(clone, splitTime, secondDuration, usesDataEnd);
904
- const playbackStartAttr = el.hasAttribute("data-playback-start") ? "data-playback-start" : el.hasAttribute("data-media-start") ? "data-media-start" : null;
905
- if (playbackStartAttr) {
906
- const currentTrim = parseFloat(el.getAttribute(playbackStartAttr) ?? "0") || 0;
907
- const rateRaw = parseFloat(el.getAttribute("data-playback-rate") ?? "");
908
- const rate = Number.isFinite(rateRaw) ? rateRaw : 1;
909
- clone.setAttribute(
910
- playbackStartAttr,
911
- String(Math.round((currentTrim + firstDuration * rate) * 1e3) / 1e3)
912
- );
913
- }
914
- const originalId = el.getAttribute("id");
915
- if (originalId) {
916
- duplicateCssRulesForId(document2, originalId, newId);
917
- }
918
- el.setAttribute("data-start", String(Math.round(start * 1e3) / 1e3));
919
- setElementDuration(el, start, firstDuration, usesDataEnd);
920
- if (el.nextSibling) {
921
- el.parentElement.insertBefore(clone, el.nextSibling);
922
- } else {
923
- el.parentElement.appendChild(clone);
924
- }
925
- const html = wrappedFragment ? document2.body.innerHTML || "" : document2.toString();
926
- return {
927
- // The split owns its new nodes' stable ids. Leaving the clone unstamped makes
928
- // the next preview request persist different bytes after history is recorded.
929
- html: ensureHfIds(html),
930
- matched: true,
931
- newId
932
- };
933
- }
934
- function getInlineStylePx(el, property) {
935
- const style = (isHTMLElement(el) ? el.getAttribute("style") : null) ?? "";
936
- const { props } = parseStyleDecls(style);
937
- const raw = props.get(property);
938
- if (!raw) return 0;
939
- const n = parseFloat(raw);
940
- return Number.isFinite(n) ? n : 0;
941
- }
942
- function setInlineLeftTop(el, left, top) {
943
- let style = el.getAttribute("style") ?? "";
944
- style = patchStyleAttrString(style, "left", `${left}px`);
945
- style = patchStyleAttrString(style, "top", `${top}px`);
946
- el.setAttribute("style", style);
947
- }
948
- function uniqueGroupDomId(document2, groupId) {
949
- const base = groupId.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "group";
950
- let id = base;
951
- let n = 2;
952
- while (document2.getElementById(id)) {
953
- id = `${base}-${n}`;
954
- n += 1;
955
- }
956
- return id;
957
- }
958
- function wrapElementsInHtml(source, targets, groupId, bbox, rebases) {
959
- const { document: document2, wrappedFragment } = parseSourceDocument(source);
960
- if (targets.length === 0) {
961
- return { html: source, matched: false, groupId: null, error: "no targets" };
962
- }
963
- const els = [];
964
- const seen = /* @__PURE__ */ new Set();
965
- for (const target of targets) {
966
- const el = findTargetElement(document2, target);
967
- if (!el || !isHTMLElement(el) || seen.has(el)) continue;
968
- seen.add(el);
969
- els.push(el);
970
- }
971
- if (els.length === 0) {
972
- return { html: source, matched: false, groupId: null, error: "no targets matched" };
973
- }
974
- const parent = els[0]?.parentElement;
975
- if (!parent || els.some((el) => el.parentElement !== parent)) {
976
- return {
977
- html: source,
978
- matched: false,
979
- groupId: null,
980
- error: "grouped elements must share a single parent"
981
- };
982
- }
983
- const memberSet = new Set(els);
984
- const ordered = Array.from(parent.children).filter((c) => memberSet.has(c));
985
- const rebaseByEl = /* @__PURE__ */ new Map();
986
- for (const rebase of rebases) {
987
- const el = findTargetElement(document2, rebase.target);
988
- if (el) rebaseByEl.set(el, { left: rebase.left, top: rebase.top });
989
- }
990
- const wrapper = document2.createElement("div");
991
- wrapper.setAttribute("data-hf-group", groupId);
992
- wrapper.setAttribute("id", uniqueGroupDomId(document2, groupId));
993
- const memberZIndexes = ordered.map(
994
- (el) => Number.parseInt(
995
- parseStyleDecls(el.getAttribute("style") ?? "").props.get("z-index") ?? "",
996
- 10
997
- )
998
- ).filter((z) => Number.isFinite(z));
999
- const maxZ = memberZIndexes.length > 0 ? Math.max(...memberZIndexes) : null;
1000
- wrapper.setAttribute(
1001
- "style",
1002
- `position: absolute; left: ${bbox.left}px; top: ${bbox.top}px; width: ${bbox.width}px; height: ${bbox.height}px` + (maxZ !== null ? `; z-index: ${maxZ}` : "")
1003
- );
1004
- parent.insertBefore(wrapper, ordered[ordered.length - 1] ?? null);
1005
- for (const el of ordered) {
1006
- const rebase = rebaseByEl.get(el);
1007
- if (rebase) setInlineLeftTop(el, rebase.left, rebase.top);
1008
- wrapper.appendChild(el);
1009
- }
1010
- return {
1011
- html: wrappedFragment ? document2.body.innerHTML || "" : document2.toString(),
1012
- matched: true,
1013
- groupId
1014
- };
1015
- }
1016
- function unwrapElementsFromHtml(source, groupTarget) {
1017
- const { document: document2, wrappedFragment } = parseSourceDocument(source);
1018
- const group = findTargetElement(document2, groupTarget);
1019
- if (!group || !isHTMLElement(group)) return { html: source, unwrapped: false };
1020
- if (!group.hasAttribute("data-hf-group")) return { html: source, unwrapped: false };
1021
- const parent = group.parentElement;
1022
- if (!parent) return { html: source, unwrapped: false };
1023
- const wLeft = getInlineStylePx(group, "left");
1024
- const wTop = getInlineStylePx(group, "top");
1025
- const groupCenter = {
1026
- cx: wLeft + getInlineStylePx(group, "width") / 2,
1027
- cy: wTop + getInlineStylePx(group, "height") / 2
1028
- };
1029
- const members = [];
1030
- for (const child of Array.from(group.children)) {
1031
- if (isHTMLElement(child)) {
1032
- const newLeft = getInlineStylePx(child, "left") + wLeft;
1033
- const newTop = getInlineStylePx(child, "top") + wTop;
1034
- setInlineLeftTop(child, newLeft, newTop);
1035
- if (child.id) {
1036
- members.push({
1037
- id: child.id,
1038
- cx: newLeft + getInlineStylePx(child, "width") / 2,
1039
- cy: newTop + getInlineStylePx(child, "height") / 2
1040
- });
1041
- }
1042
- }
1043
- parent.insertBefore(child, group);
1044
- }
1045
- const groupId = group.id || void 0;
1046
- group.remove();
1047
- return {
1048
- html: wrappedFragment ? document2.body.innerHTML || "" : document2.toString(),
1049
- unwrapped: true,
1050
- unwrappedGroupId: groupId,
1051
- members,
1052
- groupCenter
1053
- };
1054
- }
1055
-
1056
- // src/routes/files.ts
1057
- import { parseHTML as parseHTML2 } from "linkedom";
1058
658
  function isAcornGsapWriterEnabled() {
1059
659
  const val = process.env["STUDIO_SDK_CUTOVER_ENABLED"];
1060
660
  return val === "true" || val === "1";
@@ -1244,7 +844,7 @@ function generateCopyPath(projectDir, originalPath) {
1244
844
  const cleanBase = copyMatch ? base.slice(0, -copyMatch[0].length) : base;
1245
845
  let num = copyMatch ? copyMatch[1] ? parseInt(copyMatch[1]) + 1 : 2 : 1;
1246
846
  let candidate = num === 1 ? `${cleanBase} (copy)${ext}` : `${cleanBase} (copy ${num})${ext}`;
1247
- while (existsSync3(resolve3(projectDir, candidate))) {
847
+ while (existsSync3(resolve2(projectDir, candidate))) {
1248
848
  num++;
1249
849
  candidate = `${cleanBase} (copy ${num})${ext}`;
1250
850
  }
@@ -1255,7 +855,7 @@ function walkFiles(dir, filter) {
1255
855
  for (const entry of readdirSync4(dir, { withFileTypes: true })) {
1256
856
  const full = join6(dir, entry.name);
1257
857
  if (entry.isDirectory()) {
1258
- if (entry.name === "node_modules" || entry.name === ".thumbnails" || entry.name === "renders")
858
+ if (entry.name === "node_modules" || entry.name === ".thumbnails" || entry.name === "renders" || entry.name === ".transcode-cache")
1259
859
  continue;
1260
860
  results.push(...walkFiles(full, filter));
1261
861
  } else if (filter(entry.name)) {
@@ -1282,10 +882,10 @@ function updateReferences(projectDir, oldPath, newPath) {
1282
882
  return updatedCount;
1283
883
  }
1284
884
  function extractGsapScriptBlock(html) {
1285
- const { document: document2 } = parseHTML2(html);
885
+ const { document } = parseHTML(html);
1286
886
  const scripts = [
1287
- ...document2.querySelectorAll("script:not([src])"),
1288
- ...Array.from(document2.querySelectorAll("template")).flatMap(
887
+ ...document.querySelectorAll("script:not([src])"),
888
+ ...Array.from(document.querySelectorAll("template")).flatMap(
1289
889
  (tmpl) => Array.from(tmpl.querySelectorAll("script:not([src])"))
1290
890
  )
1291
891
  ];
@@ -1294,10 +894,10 @@ function extractGsapScriptBlock(html) {
1294
894
  if (content.includes("gsap.timeline") || content.includes(".set(") || content.includes(".to(")) {
1295
895
  return {
1296
896
  scriptText: content,
1297
- document: document2,
897
+ document,
1298
898
  replaceScript(newText) {
1299
899
  script.textContent = newText;
1300
- return document2.toString();
900
+ return document.toString();
1301
901
  }
1302
902
  };
1303
903
  }
@@ -1395,11 +995,11 @@ function bakeGroupTransformIntoMembers(html, groupId, members, groupCenter) {
1395
995
  }
1396
996
  return block.replaceScript(script);
1397
997
  }
1398
- function stripStudioEditsFromTarget(document2, selector) {
998
+ function stripStudioEditsFromTarget(document, selector) {
1399
999
  if (!selector) return 0;
1400
1000
  let stripped = 0;
1401
1001
  try {
1402
- for (const el of document2.querySelectorAll(selector)) {
1002
+ for (const el of document.querySelectorAll(selector)) {
1403
1003
  if (!isHTMLElement(el)) continue;
1404
1004
  const htmlEl = el;
1405
1005
  let touched = false;
@@ -1469,11 +1069,11 @@ function resolveFinalOpacity(anim) {
1469
1069
  const num = Number(raw);
1470
1070
  return Number.isFinite(num) && num !== 0 ? num : null;
1471
1071
  }
1472
- function bakeVisibilityOnDelete(document2, anim) {
1072
+ function bakeVisibilityOnDelete(document, anim) {
1473
1073
  const opacity = resolveFinalOpacity(anim);
1474
1074
  if (opacity === null) return;
1475
1075
  try {
1476
- for (const el of document2.querySelectorAll(anim.targetSelector)) {
1076
+ for (const el of document.querySelectorAll(anim.targetSelector)) {
1477
1077
  if (isHTMLElement(el)) el.style.setProperty("opacity", String(opacity));
1478
1078
  }
1479
1079
  } catch {
@@ -2226,7 +1826,7 @@ async function processUploadedFiles(formData, targetDir, projectDir) {
2226
1826
  skipped.push(name);
2227
1827
  continue;
2228
1828
  }
2229
- const destPath = resolve3(targetDir, name);
1829
+ const destPath = resolve2(targetDir, name);
2230
1830
  if (!isSafePath(projectDir, destPath)) continue;
2231
1831
  let finalPath = destPath;
2232
1832
  let finalName = name;
@@ -2236,13 +1836,13 @@ async function processUploadedFiles(formData, targetDir, projectDir) {
2236
1836
  const base = dotIdx > 0 ? name.slice(0, dotIdx) : name;
2237
1837
  let n = 2;
2238
1838
  const MAX_COPY_INDEX = 1e4;
2239
- while (n < MAX_COPY_INDEX && existsSync3(resolve3(targetDir, `${base} (${n})${ext}`))) n++;
1839
+ while (n < MAX_COPY_INDEX && existsSync3(resolve2(targetDir, `${base} (${n})${ext}`))) n++;
2240
1840
  if (n >= MAX_COPY_INDEX) {
2241
1841
  skipped.push(name);
2242
1842
  continue;
2243
1843
  }
2244
1844
  finalName = `${base} (${n})${ext}`;
2245
- finalPath = resolve3(targetDir, finalName);
1845
+ finalPath = resolve2(targetDir, finalName);
2246
1846
  }
2247
1847
  const buffer = Buffer.from(await value.arrayBuffer());
2248
1848
  const validation = validateUploadedMediaBuffer(finalName, buffer);
@@ -2797,7 +2397,7 @@ import { injectScriptsIntoHtml, stripEmbeddedRuntimeScripts as stripEmbeddedRunt
2797
2397
  // src/helpers/subComposition.ts
2798
2398
  import { existsSync as existsSync4, readFileSync as readFileSync5 } from "fs";
2799
2399
  import { join as join7 } from "path";
2800
- import { parseHTML as parseHTML3 } from "linkedom";
2400
+ import { parseHTML as parseHTML2 } from "linkedom";
2801
2401
  import {
2802
2402
  rewriteAssetPaths,
2803
2403
  rewriteCssAssetUrls,
@@ -2845,7 +2445,7 @@ function fixDigitLeadingIdSelectors(root) {
2845
2445
  }
2846
2446
  }
2847
2447
  function extractFullDocumentParts(rawHtml, compPath) {
2848
- const { document: doc } = parseHTML3(rawHtml);
2448
+ const { document: doc } = parseHTML2(rawHtml);
2849
2449
  const rewriteTargets = [doc.head, doc.body].filter(Boolean);
2850
2450
  for (const target of rewriteTargets) {
2851
2451
  rewriteRelativePaths(target, compPath);
@@ -2859,7 +2459,7 @@ function extractFullDocumentParts(rawHtml, compPath) {
2859
2459
  return { headContent, bodyContent, htmlAttrs, bodyAttrs };
2860
2460
  }
2861
2461
  function extractTemplateInnerHtml(rawComp) {
2862
- const { document: doc } = parseHTML3(rawComp);
2462
+ const { document: doc } = parseHTML2(rawComp);
2863
2463
  const template = doc.querySelector("template");
2864
2464
  return template ? template.innerHTML : null;
2865
2465
  }
@@ -2911,7 +2511,7 @@ function buildSubCompositionHtml(projectDir, compPath, runtimeUrl, baseHref, raw
2911
2511
  let bodyAttrs = "";
2912
2512
  const templateInner = extractTemplateInnerHtml(rawComp);
2913
2513
  if (templateInner != null) {
2914
- const { document: contentDoc } = parseHTML3(
2514
+ const { document: contentDoc } = parseHTML2(
2915
2515
  `<!DOCTYPE html><html><head></head><body>${templateInner}</body></html>`
2916
2516
  );
2917
2517
  rewriteRelativePaths(contentDoc, compPath);
@@ -2925,7 +2525,7 @@ function buildSubCompositionHtml(projectDir, compPath, runtimeUrl, baseHref, raw
2925
2525
  htmlAttrs = parts.htmlAttrs;
2926
2526
  bodyAttrs = parts.bodyAttrs;
2927
2527
  } else {
2928
- const { document: contentDoc } = parseHTML3(
2528
+ const { document: contentDoc } = parseHTML2(
2929
2529
  `<!DOCTYPE html><html><head></head><body>${rawComp}</body></html>`
2930
2530
  );
2931
2531
  rewriteRelativePaths(contentDoc, compPath);
@@ -2970,198 +2570,11 @@ ${rewrittenContent}
2970
2570
  </html>`;
2971
2571
  }
2972
2572
 
2973
- // src/helpers/studioMotionRenderScript.ts
2974
- var STUDIO_MOTION_PATH = ".hyperframes/studio-motion.json";
2975
- function hasStudioMotionEntries(manifestContent) {
2976
- try {
2977
- const parsed = JSON.parse(manifestContent);
2978
- return Array.isArray(parsed.motions) && parsed.motions.length > 0;
2979
- } catch {
2980
- return false;
2981
- }
2982
- }
2983
- function createStudioMotionRenderBodyScript(manifestContent, options = {}) {
2984
- if (!manifestContent.trim() || !hasStudioMotionEntries(manifestContent)) return null;
2985
- return `(${studioMotionRenderRuntime.toString()})(${JSON.stringify(manifestContent)}, ${JSON.stringify(options.activeCompositionPath ?? null)});`;
2986
- }
2987
- function studioMotionRenderRuntime(manifestContent, activeCompositionPath) {
2988
- const STUDIO_MOTION_TIMELINE_ID = "studio-motion";
2989
- const STUDIO_MOTION_ATTR = "data-hf-studio-motion";
2990
- const ORIGINAL_TRANSFORM_ATTR = "data-hf-studio-motion-original-transform";
2991
- const ORIGINAL_OPACITY_ATTR = "data-hf-studio-motion-original-opacity";
2992
- const ORIGINAL_VISIBILITY_ATTR = "data-hf-studio-motion-original-visibility";
2993
- const objectRecord = (value) => value && typeof value === "object" ? value : null;
2994
- const finiteNumber = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
2995
- const runtimeWindow = window;
2996
- const parseMotionValues = (value) => {
2997
- const record = objectRecord(value);
2998
- if (!record) return null;
2999
- const parsed = {};
3000
- for (const key of ["x", "y", "scale", "rotation", "opacity", "autoAlpha"]) {
3001
- const next = finiteNumber(record[key]);
3002
- if (next != null) parsed[key] = next;
3003
- }
3004
- return Object.keys(parsed).length > 0 ? parsed : null;
3005
- };
3006
- const parseCustomEase = (value) => {
3007
- const record = objectRecord(value);
3008
- if (!record) return null;
3009
- const id = typeof record.id === "string" ? record.id.trim() : "";
3010
- const data = typeof record.data === "string" ? record.data.trim() : "";
3011
- if (!id || !data) return null;
3012
- return { id, data };
3013
- };
3014
- const parsedManifest = (() => {
3015
- try {
3016
- return objectRecord(JSON.parse(manifestContent));
3017
- } catch {
3018
- return null;
3019
- }
3020
- })();
3021
- const manifestMotions = Array.isArray(parsedManifest?.motions) ? parsedManifest.motions : [];
3022
- const sourceFileForElement = (element) => {
3023
- let current = element;
3024
- while (current) {
3025
- const sourceFile = current.getAttribute("data-composition-file") ?? current.getAttribute("data-composition-src");
3026
- if (sourceFile) return sourceFile;
3027
- current = current.parentElement;
3028
- }
3029
- return activeCompositionPath ?? "index.html";
3030
- };
3031
- const elementMatchesSourceFile = (element, sourceFile) => sourceFileForElement(element) === sourceFile;
3032
- const isHTMLElement2 = (element) => element instanceof HTMLElement;
3033
- const querySelectorCandidates = (selector) => {
3034
- const className = selector.match(/^\.([A-Za-z0-9_-]+)$/)?.[1];
3035
- if (className) {
3036
- return Array.from(document.getElementsByTagName("*")).filter(
3037
- (element) => isHTMLElement2(element) && element.classList.contains(className)
3038
- );
3039
- }
3040
- if (/^[A-Za-z][A-Za-z0-9-]*$/.test(selector)) {
3041
- return Array.from(document.getElementsByTagName(selector)).filter(isHTMLElement2);
3042
- }
3043
- return Array.from(document.querySelectorAll(selector)).filter(isHTMLElement2);
3044
- };
3045
- const resolveTarget = (targetRecord) => {
3046
- const sourceFile = typeof targetRecord.sourceFile === "string" ? targetRecord.sourceFile : "";
3047
- if (!sourceFile) return null;
3048
- const id = typeof targetRecord.id === "string" ? targetRecord.id : "";
3049
- if (id) {
3050
- const byId = document.getElementById(id);
3051
- if (isHTMLElement2(byId) && elementMatchesSourceFile(byId, sourceFile)) return byId;
3052
- }
3053
- const selector = typeof targetRecord.selector === "string" ? targetRecord.selector : "";
3054
- if (!selector) return null;
3055
- try {
3056
- const selectorIndex = Math.max(0, Math.floor(finiteNumber(targetRecord.selectorIndex) ?? 0));
3057
- return querySelectorCandidates(selector).filter(
3058
- (element) => elementMatchesSourceFile(element, sourceFile)
3059
- )[selectorIndex] ?? null;
3060
- } catch {
3061
- return null;
3062
- }
3063
- };
3064
- const restoreElement = (element) => {
3065
- runtimeWindow.gsap?.set?.(element, { clearProps: "transform,opacity,visibility" });
3066
- element.style.transform = element.getAttribute(ORIGINAL_TRANSFORM_ATTR) ?? "";
3067
- element.style.opacity = element.getAttribute(ORIGINAL_OPACITY_ATTR) ?? "";
3068
- element.style.visibility = element.getAttribute(ORIGINAL_VISIBILITY_ATTR) ?? "";
3069
- element.removeAttribute(STUDIO_MOTION_ATTR);
3070
- element.removeAttribute(ORIGINAL_TRANSFORM_ATTR);
3071
- element.removeAttribute(ORIGINAL_OPACITY_ATTR);
3072
- element.removeAttribute(ORIGINAL_VISIBILITY_ATTR);
3073
- };
3074
- const restoreStudioMotionElements = () => {
3075
- for (const element of Array.from(document.querySelectorAll(`[${STUDIO_MOTION_ATTR}]`))) {
3076
- if (isHTMLElement2(element)) restoreElement(element);
3077
- }
3078
- };
3079
- const readCurrentTime = () => {
3080
- try {
3081
- const playerTime = runtimeWindow.__player?.getTime?.();
3082
- if (typeof playerTime === "number" && Number.isFinite(playerTime)) {
3083
- return Math.max(0, playerTime);
3084
- }
3085
- } catch {
3086
- }
3087
- try {
3088
- const timelineTime = runtimeWindow.__timeline?.time?.();
3089
- if (typeof timelineTime === "number" && Number.isFinite(timelineTime)) {
3090
- return Math.max(0, timelineTime);
3091
- }
3092
- } catch {
3093
- }
3094
- return 0;
3095
- };
3096
- const resolveEase = (motion) => {
3097
- const fallback = typeof motion.ease === "string" && motion.ease.trim() ? motion.ease.trim() : "none";
3098
- const customEase = parseCustomEase(motion.customEase);
3099
- const customEasePlugin = runtimeWindow.CustomEase;
3100
- if (!customEase || typeof customEasePlugin?.create !== "function") return fallback;
3101
- try {
3102
- runtimeWindow.gsap?.registerPlugin?.(customEasePlugin);
3103
- customEasePlugin.create(customEase.id, customEase.data);
3104
- return customEase.id;
3105
- } catch {
3106
- return fallback;
3107
- }
3108
- };
3109
- const applyManifest = () => {
3110
- runtimeWindow.__timelines = runtimeWindow.__timelines ?? {};
3111
- runtimeWindow.__timelines[STUDIO_MOTION_TIMELINE_ID]?.kill?.();
3112
- delete runtimeWindow.__timelines[STUDIO_MOTION_TIMELINE_ID];
3113
- restoreStudioMotionElements();
3114
- const gsap = runtimeWindow.gsap;
3115
- if (!gsap?.timeline || manifestMotions.length === 0) return 0;
3116
- const timeline = gsap.timeline({ paused: true, defaults: { overwrite: "auto" } });
3117
- let applied = 0;
3118
- for (const motionValue of manifestMotions) {
3119
- const motion = objectRecord(motionValue);
3120
- if (!motion || motion.kind !== "gsap-motion") continue;
3121
- const targetRecord = objectRecord(motion.target);
3122
- if (!targetRecord) continue;
3123
- const target = resolveTarget(targetRecord);
3124
- if (!target || typeof timeline.fromTo !== "function") continue;
3125
- const start = finiteNumber(motion.start);
3126
- const duration = finiteNumber(motion.duration);
3127
- if (start == null || duration == null || start < 0 || duration <= 0) continue;
3128
- const from = parseMotionValues(motion.from);
3129
- const to = parseMotionValues(motion.to);
3130
- if (!from || !to) continue;
3131
- if (!target.hasAttribute(STUDIO_MOTION_ATTR)) {
3132
- target.setAttribute(ORIGINAL_TRANSFORM_ATTR, target.style.transform);
3133
- target.setAttribute(ORIGINAL_OPACITY_ATTR, target.style.opacity);
3134
- target.setAttribute(ORIGINAL_VISIBILITY_ATTR, target.style.visibility);
3135
- }
3136
- target.setAttribute(STUDIO_MOTION_ATTR, "true");
3137
- timeline.fromTo(
3138
- target,
3139
- from,
3140
- { ...to, duration, ease: resolveEase(motion), overwrite: "auto", immediateRender: false },
3141
- start
3142
- );
3143
- applied += 1;
3144
- }
3145
- if (applied === 0) {
3146
- timeline.kill?.();
3147
- return 0;
3148
- }
3149
- runtimeWindow.__timelines[STUDIO_MOTION_TIMELINE_ID] = timeline;
3150
- timeline.pause?.();
3151
- const currentTime = readCurrentTime();
3152
- if (typeof timeline.totalTime === "function") timeline.totalTime(currentTime, false);
3153
- else timeline.time?.(currentTime);
3154
- return applied;
3155
- };
3156
- runtimeWindow.__hfStudioMotionApply = applyManifest;
3157
- applyManifest();
3158
- }
3159
-
3160
2573
  // src/routes/preview.ts
3161
- import { ensureHfIds as ensureHfIds3 } from "@hyperframes/parsers/hf-ids";
2574
+ import { ensureHfIds as ensureHfIds2 } from "@hyperframes/parsers/hf-ids";
3162
2575
 
3163
2576
  // src/helpers/hfIdPersist.ts
3164
- import { ensureHfIds as ensureHfIds2 } from "@hyperframes/parsers/hf-ids";
2577
+ import { ensureHfIds } from "@hyperframes/parsers/hf-ids";
3165
2578
  import {
3166
2579
  closeSync as closeSync2,
3167
2580
  constants,
@@ -3173,7 +2586,7 @@ import {
3173
2586
  writeSync as writeSync2
3174
2587
  } from "fs";
3175
2588
  function persistHfIdsIfNeeded(filePath, html) {
3176
- const normalized = ensureHfIds2(html);
2589
+ const normalized = ensureHfIds(html);
3177
2590
  const idsBefore = (html.match(/\bdata-hf-id=/g) ?? []).length;
3178
2591
  const idsAfter = (normalized.match(/\bdata-hf-id=/g) ?? []).length;
3179
2592
  if (idsAfter > idsBefore) {
@@ -3207,7 +2620,7 @@ function stampFileHfIds(filePath) {
3207
2620
  try {
3208
2621
  if (!fstatSync(fd).isFile()) return null;
3209
2622
  const html = readFileSync6(fd, "utf-8");
3210
- const normalized = ensureHfIds2(html);
2623
+ const normalized = ensureHfIds(html);
3211
2624
  const idsBefore = (html.match(/\bdata-hf-id=/g) ?? []).length;
3212
2625
  const idsAfter = (normalized.match(/\bdata-hf-id=/g) ?? []).length;
3213
2626
  if (writable && idsAfter > idsBefore) {
@@ -3409,11 +2822,17 @@ async function transformPreviewHtml(html, adapter, project, activeCompositionPat
3409
2822
  function resolveProjectMainHtml(projectDir, projectId) {
3410
2823
  const indexPath = join8(projectDir, "index.html");
3411
2824
  if (existsSync5(indexPath)) {
3412
- return { html: readFileSync7(indexPath, "utf-8"), compositionPath: "index.html" };
2825
+ return {
2826
+ html: readFileSync7(indexPath, "utf-8"),
2827
+ compositionPath: "index.html"
2828
+ };
3413
2829
  }
3414
2830
  const blockHtmlPath = join8(projectDir, `${projectId}.html`);
3415
2831
  if (existsSync5(blockHtmlPath)) {
3416
- return { html: readFileSync7(blockHtmlPath, "utf-8"), compositionPath: `${projectId}.html` };
2832
+ return {
2833
+ html: readFileSync7(blockHtmlPath, "utf-8"),
2834
+ compositionPath: `${projectId}.html`
2835
+ };
3417
2836
  }
3418
2837
  return null;
3419
2838
  }
@@ -3422,6 +2841,7 @@ function registerPreviewRoutes(api, adapter) {
3422
2841
  "Cache-Control": "private, no-cache",
3423
2842
  ETag: etag
3424
2843
  });
2844
+ const mediaCodecProbeCache = resolvePreviewMediaCodecProbeCache(adapter);
3425
2845
  api.get("/projects/:id/preview", async (c) => {
3426
2846
  const resolved = await resolveProjectAndSignature(adapter, c.req.param("id"));
3427
2847
  if (!resolved) return c.json({ error: "not found" }, 404);
@@ -3432,7 +2852,10 @@ function registerPreviewRoutes(api, adapter) {
3432
2852
  const etag = `"preview:${signature}${variablesEtagSalt(vars.raw)}"`;
3433
2853
  const ifNoneMatch = c.req.header("If-None-Match");
3434
2854
  if (ifNoneMatch === etag) {
3435
- return new Response(null, { status: 304, headers: previewCacheHeaders(etag) });
2855
+ return new Response(null, {
2856
+ status: 304,
2857
+ headers: previewCacheHeaders(etag)
2858
+ });
3436
2859
  }
3437
2860
  const diskMain = resolveProjectMainHtml(project.dir, project.id);
3438
2861
  const normalizedDisk = diskMain ? persistHfIdsIfNeeded(join8(project.dir, diskMain.compositionPath), diskMain.html) : null;
@@ -3455,12 +2878,19 @@ ${runtimeTag}`;
3455
2878
  bundled = bundled.replace(/<head>/i, `<head><base href="${baseHref}">`);
3456
2879
  }
3457
2880
  bundled = injectStudioPreviewAugmentations(
3458
- ensureHfIds3(await transformPreviewHtml(bundled, adapter, project, mainCompositionPath)),
2881
+ ensureHfIds2(await transformPreviewHtml(bundled, adapter, project, mainCompositionPath)),
3459
2882
  adapter,
3460
2883
  project.dir,
3461
2884
  mainCompositionPath
3462
2885
  );
3463
2886
  if (previewVariables) bundled = injectPreviewVariables(bundled, previewVariables);
2887
+ bundled = await injectMediaCodecMap(
2888
+ bundled,
2889
+ adapter,
2890
+ project.dir,
2891
+ mainCompositionPath,
2892
+ mediaCodecProbeCache
2893
+ );
3464
2894
  return c.html(bundled, 200, previewCacheHeaders(etag));
3465
2895
  } catch {
3466
2896
  const fallback = resolveProjectMainHtml(project.dir, project.id);
@@ -3478,6 +2908,13 @@ ${runtimeTag}`;
3478
2908
  if (previewVariables) {
3479
2909
  fallbackAugmented = injectPreviewVariables(fallbackAugmented, previewVariables);
3480
2910
  }
2911
+ fallbackAugmented = await injectMediaCodecMap(
2912
+ fallbackAugmented,
2913
+ adapter,
2914
+ project.dir,
2915
+ fallback.compositionPath,
2916
+ mediaCodecProbeCache
2917
+ );
3481
2918
  return c.html(fallbackAugmented, 200, previewCacheHeaders(etag));
3482
2919
  }
3483
2920
  return c.text("not found", 404);
@@ -3504,7 +2941,10 @@ ${runtimeTag}`;
3504
2941
  const etag = `"comp:v2:${compPath}:${signature}${variablesEtagSalt(vars.raw)}"`;
3505
2942
  const ifNoneMatch = c.req.header("If-None-Match");
3506
2943
  if (ifNoneMatch === etag) {
3507
- return new Response(null, { status: 304, headers: previewCacheHeaders(etag) });
2944
+ return new Response(null, {
2945
+ status: 304,
2946
+ headers: previewCacheHeaders(etag)
2947
+ });
3508
2948
  }
3509
2949
  const stamped = pinSubCompHfIds(compFile, compPath);
3510
2950
  if (stamped === null) return c.text("not found", 404);
@@ -3517,9 +2957,10 @@ ${runtimeTag}`;
3517
2957
  stamped
3518
2958
  );
3519
2959
  if (!html) return c.text("not found", 404);
3520
- html = ensureHfIds3(await transformPreviewHtml(html, adapter, project, compPath));
2960
+ html = ensureHfIds2(await transformPreviewHtml(html, adapter, project, compPath));
3521
2961
  html = injectStudioPreviewAugmentations(html, adapter, project.dir, compPath);
3522
2962
  if (previewVariables) html = injectPreviewVariables(html, previewVariables);
2963
+ html = await injectMediaCodecMap(html, adapter, project.dir, compPath, mediaCodecProbeCache);
3523
2964
  return c.html(html, 200, previewCacheHeaders(etag));
3524
2965
  });
3525
2966
  api.get("/projects/:id/preview/*", async (c) => {
@@ -3538,15 +2979,49 @@ ${runtimeTag}`;
3538
2979
  }
3539
2980
  const contentType = getMimeType(subPath);
3540
2981
  const isText = /\.(html|css|js|json|svg|txt|md|cube)$/i.test(subPath);
3541
- const etag = `"${stat.mtimeMs.toString(36)}-${stat.size.toString(36)}"`;
3542
- const cacheHeaders = isText ? { "Cache-Control": "no-store" } : { "Cache-Control": "private, max-age=3600, must-revalidate", ETag: etag };
2982
+ const proxyParam = c.req.query("hf-proxy");
2983
+ let proxyVariant;
2984
+ if (proxyParam !== void 0) {
2985
+ if (!isProxyVariantRequest(proxyParam) || !contentType.startsWith("video/") || !isAutoProxyEnabled(adapter)) {
2986
+ return c.text("not found", 404);
2987
+ }
2988
+ const facts = await probeAssetCodec(file);
2989
+ const eligibility = decideMediaProxyEligibility(facts);
2990
+ if (!eligibility.eligible) {
2991
+ return c.text(`media proxy unavailable: ${eligibility.reason}`, 422);
2992
+ }
2993
+ if (!facts) return c.text("media proxy unavailable: unknown_codec", 422);
2994
+ proxyVariant = resolveProxyVariantRequest(proxyParam, facts) ?? void 0;
2995
+ if (!proxyVariant) {
2996
+ return c.text("media proxy variant does not match asset", 422);
2997
+ }
2998
+ }
2999
+ const etag = `"${stat.mtimeMs.toString(36)}-${stat.size.toString(36)}${proxyEtagSalt(proxyVariant)}"`;
3000
+ const cacheHeaders = isText ? { "Cache-Control": "no-store" } : {
3001
+ "Cache-Control": "private, max-age=3600, must-revalidate",
3002
+ ETag: etag
3003
+ };
3543
3004
  if (!isText) {
3544
3005
  const ifNoneMatch = c.req.header("If-None-Match");
3545
3006
  if (ifNoneMatch === etag) {
3546
3007
  return new Response(null, { status: 304, headers: cacheHeaders });
3547
3008
  }
3548
3009
  }
3549
- const buffer = isText ? Buffer.from(readFileSync7(file, "utf-8"), "utf-8") : readFileSync7(file);
3010
+ let servedPath = file;
3011
+ let servedContentType = contentType;
3012
+ if (proxyVariant !== void 0) {
3013
+ try {
3014
+ servedPath = await resolveProxy(project.dir, file, proxyVariant);
3015
+ } catch (err) {
3016
+ if (err instanceof ProxyCapacityError) {
3017
+ return c.text(err.message, 503, { "Retry-After": "5" });
3018
+ }
3019
+ const message = err instanceof ProxyTranscodeError ? err.message : "proxy transcode failed";
3020
+ return c.text(message, 502);
3021
+ }
3022
+ servedContentType = PROXY_VARIANT_CONFIG[proxyVariant].contentType;
3023
+ }
3024
+ const buffer = isText ? Buffer.from(readFileSync7(file, "utf-8"), "utf-8") : readFileSync7(servedPath);
3550
3025
  const totalSize = buffer.length;
3551
3026
  const rangeHeader = c.req.header("Range");
3552
3027
  if (rangeHeader) {
@@ -3560,7 +3035,7 @@ ${runtimeTag}`;
3560
3035
  status: 206,
3561
3036
  headers: {
3562
3037
  ...cacheHeaders,
3563
- "Content-Type": contentType,
3038
+ "Content-Type": servedContentType,
3564
3039
  "Content-Range": `bytes ${start}-${safeEnd}/${totalSize}`,
3565
3040
  "Accept-Ranges": "bytes",
3566
3041
  "Content-Length": String(chunkSize)
@@ -3571,7 +3046,7 @@ ${runtimeTag}`;
3571
3046
  return new Response(new Uint8Array(buffer), {
3572
3047
  headers: {
3573
3048
  ...cacheHeaders,
3574
- "Content-Type": contentType,
3049
+ "Content-Type": servedContentType,
3575
3050
  "Accept-Ranges": "bytes",
3576
3051
  "Content-Length": String(totalSize)
3577
3052
  }
@@ -3848,586 +3323,6 @@ function registerRenderRoutes(api, adapter) {
3848
3323
  import { existsSync as existsSync7, readFileSync as readFileSync10, writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, statSync as statSync4 } from "fs";
3849
3324
  import { join as join11 } from "path";
3850
3325
  import { createHash as createHash4 } from "crypto";
3851
-
3852
- // src/helpers/manualEditsRenderScript.ts
3853
- var STUDIO_MANUAL_EDITS_PATH = ".hyperframes/studio-manual-edits.json";
3854
- function createStudioManualEditsRenderBodyScript(manifestContent, options = {}) {
3855
- if (!manifestContent.trim()) return null;
3856
- return `(${studioManualEditsRenderRuntime.toString()})(${JSON.stringify(manifestContent)}, ${JSON.stringify(options.activeCompositionPath ?? null)});`;
3857
- }
3858
- function createStudioPositionSeekReapplyScript() {
3859
- return `(${studioPositionSeekReapplyRuntime.toString()})();`;
3860
- }
3861
- function studioPositionSeekReapplyRuntime() {
3862
- const OFFSET_X_PROP = "--hf-studio-offset-x";
3863
- const OFFSET_Y_PROP = "--hf-studio-offset-y";
3864
- const WIDTH_PROP = "--hf-studio-width";
3865
- const HEIGHT_PROP = "--hf-studio-height";
3866
- const ROTATION_PROP = "--hf-studio-rotation";
3867
- const PATH_OFFSET_ATTR = "data-hf-studio-path-offset";
3868
- const BOX_SIZE_ATTR = "data-hf-studio-box-size";
3869
- const ROTATION_ATTR = "data-hf-studio-rotation";
3870
- const ORIGINAL_TRANSLATE_ATTR = "data-hf-studio-original-translate";
3871
- const ORIGINAL_ROTATE_ATTR = "data-hf-studio-original-rotate";
3872
- const MOTION_ATTR = "data-hf-studio-motion";
3873
- const MOTION_TL_KEY = "studio-motion";
3874
- const WRAPPED_PROP = "__hfStudioPositionSeekReapplyWrapped";
3875
- if (!document.querySelector("[" + PATH_OFFSET_ATTR + '="true"]') && !document.querySelector("[" + BOX_SIZE_ATTR + '="true"]') && !document.querySelector("[" + ROTATION_ATTR + '="true"]') && !document.querySelector("[" + MOTION_ATTR + "]"))
3876
- return;
3877
- const splitTopLevelWhitespace = (value) => {
3878
- const parts = [];
3879
- let depth = 0;
3880
- let current = "";
3881
- for (const char of value.trim()) {
3882
- if (char === "(") depth += 1;
3883
- if (char === ")") depth = Math.max(0, depth - 1);
3884
- if (/\s/.test(char) && depth === 0) {
3885
- if (current) parts.push(current);
3886
- current = "";
3887
- } else {
3888
- current += char;
3889
- }
3890
- }
3891
- if (current) parts.push(current);
3892
- return parts;
3893
- };
3894
- const composeTranslate = (element, x, y) => {
3895
- const original = element.getAttribute(ORIGINAL_TRANSLATE_ATTR)?.trim();
3896
- if (!original || original === "none") return x + " " + y;
3897
- const parts = splitTopLevelWhitespace(original);
3898
- if (parts.length === 1) return "calc(" + parts[0] + " + " + x + ") " + y;
3899
- if (parts.length >= 2) {
3900
- const z = parts.length >= 3 ? " " + parts[2] : "";
3901
- return "calc(" + parts[0] + " + " + x + ") calc(" + parts[1] + " + " + y + ")" + z;
3902
- }
3903
- return x + " " + y;
3904
- };
3905
- const isSimpleRotateAngle = (value) => /^-?(?:\d+(?:\.\d+)?|\.\d+)(?:deg|rad|turn|grad)$/.test(value.trim());
3906
- const composeRotation = (element, rotationValue) => {
3907
- const original = element.getAttribute(ORIGINAL_ROTATE_ATTR)?.trim();
3908
- if (!original || original === "none" || !isSimpleRotateAngle(original)) return rotationValue;
3909
- return "calc(" + original + " + " + rotationValue + ")";
3910
- };
3911
- let lastSeekTime = 0;
3912
- let cachedMotionKey = "";
3913
- const finiteNum = (v) => typeof v === "number" && Number.isFinite(v) ? v : null;
3914
- const computeMotionKey = (motionEls) => {
3915
- let key = "";
3916
- for (let i = 0; i < motionEls.length; i++) {
3917
- const json = motionEls[i].getAttribute?.(MOTION_ATTR);
3918
- if (json) key += (key ? "\n" : "") + json;
3919
- }
3920
- return key;
3921
- };
3922
- const reapplyMotionTimeline = () => {
3923
- const motionEls = document.querySelectorAll("[" + MOTION_ATTR + "]");
3924
- if (motionEls.length === 0) {
3925
- cachedMotionKey = "";
3926
- return;
3927
- }
3928
- const win = window;
3929
- const gsap = win.gsap;
3930
- if (!gsap || typeof gsap.timeline !== "function") return;
3931
- win.__timelines = win.__timelines || {};
3932
- const motionKey = computeMotionKey(motionEls);
3933
- const existing = win.__timelines[MOTION_TL_KEY];
3934
- if (motionKey && motionKey === cachedMotionKey && existing && typeof existing.totalTime === "function") {
3935
- existing.totalTime(lastSeekTime, false);
3936
- return;
3937
- }
3938
- if (existing && typeof existing.kill === "function") existing.kill();
3939
- const tl = gsap.timeline({ paused: true, defaults: { overwrite: "auto" } });
3940
- const fromTo = tl.fromTo;
3941
- if (typeof fromTo !== "function") return;
3942
- let applied = 0;
3943
- for (let i = 0; i < motionEls.length; i++) {
3944
- const el = motionEls[i];
3945
- if (!(el instanceof HTMLElement)) continue;
3946
- const json = el.getAttribute(MOTION_ATTR);
3947
- if (!json) continue;
3948
- try {
3949
- const m = JSON.parse(json);
3950
- const start = finiteNum(m.start);
3951
- const duration = finiteNum(m.duration);
3952
- if (start == null || duration == null || duration <= 0) continue;
3953
- const ease = typeof m.ease === "string" ? m.ease : "none";
3954
- const from = m.from && typeof m.from === "object" ? m.from : {};
3955
- const to = m.to && typeof m.to === "object" ? m.to : {};
3956
- const customEase = m.customEase;
3957
- let resolvedEase = ease;
3958
- if (customEase?.id && customEase?.data && win.CustomEase?.create) {
3959
- try {
3960
- gsap.registerPlugin?.(win.CustomEase);
3961
- win.CustomEase.create(customEase.id, customEase.data);
3962
- resolvedEase = customEase.id;
3963
- } catch {
3964
- }
3965
- }
3966
- fromTo.call(
3967
- tl,
3968
- el,
3969
- { ...from },
3970
- { ...to, duration, ease: resolvedEase, overwrite: "auto", immediateRender: false },
3971
- start
3972
- );
3973
- applied += 1;
3974
- } catch {
3975
- }
3976
- }
3977
- if (applied === 0) {
3978
- cachedMotionKey = "";
3979
- if (typeof tl.kill === "function")
3980
- tl.kill();
3981
- return;
3982
- }
3983
- cachedMotionKey = motionKey;
3984
- win.__timelines[MOTION_TL_KEY] = tl;
3985
- if (typeof tl.pause === "function") tl.pause();
3986
- if (typeof tl.totalTime === "function")
3987
- tl.totalTime(lastSeekTime, false);
3988
- };
3989
- const stripGsapTranslateFromTransform = (el) => {
3990
- const transform = el.style.getPropertyValue("transform");
3991
- if (!transform || transform === "none") return;
3992
- const win = el.ownerDocument.defaultView;
3993
- const MatrixCtor = win?.DOMMatrix;
3994
- if (!MatrixCtor) return;
3995
- try {
3996
- const m = new MatrixCtor(transform);
3997
- if (m.m41 === 0 && m.m42 === 0) return;
3998
- m.m41 = 0;
3999
- m.m42 = 0;
4000
- if (m.is2D && m.a === 1 && m.b === 0 && m.c === 0 && m.d === 1) {
4001
- el.style.removeProperty("transform");
4002
- } else {
4003
- el.style.setProperty("transform", m.toString());
4004
- }
4005
- } catch {
4006
- }
4007
- };
4008
- const reapplyAll = () => {
4009
- const offsetEls = document.querySelectorAll("[" + PATH_OFFSET_ATTR + '="true"]');
4010
- for (let i = 0; i < offsetEls.length; i++) {
4011
- const el = offsetEls[i];
4012
- if (!(el instanceof HTMLElement)) continue;
4013
- const x = el.style.getPropertyValue(OFFSET_X_PROP);
4014
- const y = el.style.getPropertyValue(OFFSET_Y_PROP);
4015
- if (x || y) {
4016
- el.style.setProperty(
4017
- "translate",
4018
- composeTranslate(
4019
- el,
4020
- "var(" + OFFSET_X_PROP + ", 0px)",
4021
- "var(" + OFFSET_Y_PROP + ", 0px)"
4022
- )
4023
- );
4024
- stripGsapTranslateFromTransform(el);
4025
- }
4026
- }
4027
- const boxSizeEls = document.querySelectorAll("[" + BOX_SIZE_ATTR + '="true"]');
4028
- for (let i = 0; i < boxSizeEls.length; i++) {
4029
- const el = boxSizeEls[i];
4030
- if (!(el instanceof HTMLElement)) continue;
4031
- const w = el.style.getPropertyValue(WIDTH_PROP);
4032
- const h = el.style.getPropertyValue(HEIGHT_PROP);
4033
- if (w) el.style.setProperty("width", w);
4034
- if (h) el.style.setProperty("height", h);
4035
- }
4036
- const rotEls = document.querySelectorAll("[" + ROTATION_ATTR + '="true"]');
4037
- for (let i = 0; i < rotEls.length; i++) {
4038
- const el = rotEls[i];
4039
- if (!(el instanceof HTMLElement)) continue;
4040
- const rot = el.style.getPropertyValue(ROTATION_PROP);
4041
- if (rot) {
4042
- el.style.setProperty("rotate", composeRotation(el, "var(" + ROTATION_PROP + ", 0deg)"));
4043
- stripGsapTranslateFromTransform(el);
4044
- }
4045
- }
4046
- reapplyMotionTimeline();
4047
- };
4048
- const runtimeWindow = window;
4049
- const isWrapped = (fn) => Boolean(fn[WRAPPED_PROP]);
4050
- const markWrapped = (fn) => {
4051
- try {
4052
- Object.defineProperty(fn, WRAPPED_PROP, {
4053
- configurable: false,
4054
- enumerable: false,
4055
- value: true
4056
- });
4057
- } catch {
4058
- try {
4059
- fn[WRAPPED_PROP] = true;
4060
- } catch {
4061
- }
4062
- }
4063
- };
4064
- const wrapFn = (get, set) => {
4065
- const fn = get();
4066
- if (typeof fn !== "function") return false;
4067
- const seek = fn;
4068
- if (isWrapped(seek)) {
4069
- reapplyAll();
4070
- return true;
4071
- }
4072
- const wrapped = function(time) {
4073
- lastSeekTime = typeof time === "number" && Number.isFinite(time) ? Math.max(0, time) : 0;
4074
- const result = seek.call(this, time);
4075
- reapplyAll();
4076
- return result;
4077
- };
4078
- markWrapped(wrapped);
4079
- set(wrapped);
4080
- reapplyAll();
4081
- return true;
4082
- };
4083
- const wrapSeekFunctions = () => {
4084
- const a = wrapFn(
4085
- () => runtimeWindow.__hf?.["seek"],
4086
- (fn) => {
4087
- if (runtimeWindow.__hf) runtimeWindow.__hf["seek"] = fn;
4088
- }
4089
- );
4090
- const b = wrapFn(
4091
- () => runtimeWindow.__player?.["renderSeek"],
4092
- (fn) => {
4093
- if (runtimeWindow.__player) runtimeWindow.__player["renderSeek"] = fn;
4094
- }
4095
- );
4096
- return a || b;
4097
- };
4098
- const installSeekTrap = (obj, key, getter, setter) => {
4099
- if (!obj) return;
4100
- try {
4101
- let current = obj[key];
4102
- Object.defineProperty(obj, key, {
4103
- configurable: true,
4104
- enumerable: true,
4105
- get() {
4106
- return current;
4107
- },
4108
- set(value) {
4109
- current = value;
4110
- if (typeof value === "function" && !isWrapped(value)) {
4111
- wrapFn(getter, setter);
4112
- }
4113
- }
4114
- });
4115
- } catch {
4116
- }
4117
- };
4118
- if (document.readyState === "loading") {
4119
- document.addEventListener("DOMContentLoaded", () => reapplyAll(), { once: true });
4120
- } else {
4121
- reapplyAll();
4122
- }
4123
- wrapSeekFunctions();
4124
- installSeekTrap(
4125
- runtimeWindow.__hf,
4126
- "seek",
4127
- () => runtimeWindow.__hf?.["seek"],
4128
- (fn) => {
4129
- if (runtimeWindow.__hf) runtimeWindow.__hf["seek"] = fn;
4130
- }
4131
- );
4132
- installSeekTrap(
4133
- runtimeWindow.__player,
4134
- "renderSeek",
4135
- () => runtimeWindow.__player?.["renderSeek"],
4136
- (fn) => {
4137
- if (runtimeWindow.__player) runtimeWindow.__player["renderSeek"] = fn;
4138
- }
4139
- );
4140
- let remaining = 120;
4141
- const interval = setInterval(() => {
4142
- wrapSeekFunctions();
4143
- remaining -= 1;
4144
- if (remaining <= 0) clearInterval(interval);
4145
- }, 50);
4146
- }
4147
- function studioManualEditsRenderRuntime(manifestContent, activeCompositionPath) {
4148
- const OFFSET_X_PROP = "--hf-studio-offset-x";
4149
- const OFFSET_Y_PROP = "--hf-studio-offset-y";
4150
- const WIDTH_PROP = "--hf-studio-width";
4151
- const HEIGHT_PROP = "--hf-studio-height";
4152
- const ROTATION_PROP = "--hf-studio-rotation";
4153
- const PATH_OFFSET_ATTR = "data-hf-studio-path-offset";
4154
- const BOX_SIZE_ATTR = "data-hf-studio-box-size";
4155
- const ROTATION_ATTR = "data-hf-studio-rotation";
4156
- const ORIGINAL_TRANSLATE_ATTR = "data-hf-studio-original-translate";
4157
- const ORIGINAL_ROTATE_ATTR = "data-hf-studio-original-rotate";
4158
- const WRAPPED_SEEK_PROP = "__hfStudioManualEditsWrapped";
4159
- const ROTATION_TRANSFORM_ORIGIN = "center center";
4160
- const finiteNumber = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
4161
- const objectRecord = (value) => value && typeof value === "object" ? value : null;
4162
- const runtimeWindow = window;
4163
- const parsedManifest = (() => {
4164
- try {
4165
- return objectRecord(JSON.parse(manifestContent));
4166
- } catch {
4167
- return null;
4168
- }
4169
- })();
4170
- const manifestEdits = Array.isArray(parsedManifest?.edits) ? parsedManifest.edits : [];
4171
- if (manifestEdits.length === 0) return;
4172
- const sourceFileForElement = (element) => {
4173
- let current = element;
4174
- while (current) {
4175
- const sourceFile = current.getAttribute("data-composition-file") ?? current.getAttribute("data-composition-src");
4176
- if (sourceFile) return sourceFile;
4177
- current = current.parentElement;
4178
- }
4179
- return activeCompositionPath ?? "index.html";
4180
- };
4181
- const elementMatchesSourceFile = (element, sourceFile) => sourceFileForElement(element) === sourceFile;
4182
- const styleUsesStudioOffset = (value) => value.includes(OFFSET_X_PROP) || value.includes(OFFSET_Y_PROP);
4183
- const styleUsesStudioRotation = (value) => value.includes(ROTATION_PROP);
4184
- const splitTopLevelWhitespace = (value) => {
4185
- const parts = [];
4186
- let depth = 0;
4187
- let current = "";
4188
- for (const char of value.trim()) {
4189
- if (char === "(") depth += 1;
4190
- if (char === ")") depth = Math.max(0, depth - 1);
4191
- if (/\s/.test(char) && depth === 0) {
4192
- if (current) parts.push(current);
4193
- current = "";
4194
- } else {
4195
- current += char;
4196
- }
4197
- }
4198
- if (current) parts.push(current);
4199
- return parts;
4200
- };
4201
- const composeTranslate = (element, x, y) => {
4202
- const original = element.getAttribute(ORIGINAL_TRANSLATE_ATTR)?.trim();
4203
- if (!original || original === "none") return `${x} ${y}`;
4204
- const parts = splitTopLevelWhitespace(original);
4205
- if (parts.length === 1) return `calc(${parts[0]} + ${x}) ${y}`;
4206
- if (parts.length === 2) return `calc(${parts[0]} + ${x}) calc(${parts[1]} + ${y})`;
4207
- if (parts.length === 3) {
4208
- return `calc(${parts[0]} + ${x}) calc(${parts[1]} + ${y}) ${parts[2]}`;
4209
- }
4210
- return `${x} ${y}`;
4211
- };
4212
- const readStyleOrComputed = (element, property) => {
4213
- try {
4214
- return element.style.getPropertyValue(property) || getComputedStyle(element).getPropertyValue(property);
4215
- } catch {
4216
- return element.style.getPropertyValue(property);
4217
- }
4218
- };
4219
- const readTransformLonghandBase = (element, property) => {
4220
- const value = readStyleOrComputed(element, property).trim();
4221
- return value === "none" ? "" : value;
4222
- };
4223
- const preparePathOffsetBase = (element) => {
4224
- const currentTranslate = readTransformLonghandBase(element, "translate");
4225
- const hasMarker = element.hasAttribute(PATH_OFFSET_ATTR);
4226
- const wasResetByAnimation = !styleUsesStudioOffset(currentTranslate);
4227
- if (!hasMarker) {
4228
- element.setAttribute(ORIGINAL_TRANSLATE_ATTR, wasResetByAnimation ? currentTranslate : "");
4229
- } else if (wasResetByAnimation) {
4230
- element.setAttribute(ORIGINAL_TRANSLATE_ATTR, currentTranslate);
4231
- }
4232
- };
4233
- const prepareRotationBase = (element) => {
4234
- const currentRotate = readTransformLonghandBase(element, "rotate");
4235
- const hasMarker = element.hasAttribute(ROTATION_ATTR);
4236
- const wasResetByAnimation = !styleUsesStudioRotation(currentRotate);
4237
- if (!hasMarker) {
4238
- element.setAttribute(ORIGINAL_ROTATE_ATTR, wasResetByAnimation ? currentRotate : "");
4239
- } else if (wasResetByAnimation) {
4240
- element.setAttribute(ORIGINAL_ROTATE_ATTR, currentRotate);
4241
- }
4242
- };
4243
- const querySelectorCandidates = (selector) => {
4244
- const isCandidate = (element) => element instanceof HTMLElement;
4245
- const className = selector.match(/^\.([A-Za-z0-9_-]+)$/)?.[1];
4246
- if (className) {
4247
- return Array.from(document.getElementsByTagName("*")).filter(
4248
- (element) => isCandidate(element) && element.classList.contains(className)
4249
- );
4250
- }
4251
- if (/^[A-Za-z][A-Za-z0-9-]*$/.test(selector)) {
4252
- return Array.from(document.getElementsByTagName(selector)).filter(isCandidate);
4253
- }
4254
- return Array.from(document.querySelectorAll(selector)).filter(isCandidate);
4255
- };
4256
- const resolveTarget = (edit) => {
4257
- const targetRecord = objectRecord(edit.target);
4258
- if (!targetRecord) return null;
4259
- const sourceFile = typeof targetRecord.sourceFile === "string" ? targetRecord.sourceFile : "";
4260
- if (!sourceFile) return null;
4261
- const id = typeof targetRecord.id === "string" ? targetRecord.id : "";
4262
- if (id) {
4263
- const byId = document.getElementById(id);
4264
- if (byId instanceof HTMLElement && elementMatchesSourceFile(byId, sourceFile)) return byId;
4265
- const matchesById = [
4266
- document.documentElement,
4267
- ...Array.from(document.getElementsByTagName("*"))
4268
- ].filter(
4269
- (element) => element instanceof HTMLElement && element.id === id && elementMatchesSourceFile(element, sourceFile)
4270
- );
4271
- if (matchesById[0]) return matchesById[0];
4272
- }
4273
- const selector = typeof targetRecord.selector === "string" ? targetRecord.selector : "";
4274
- if (!selector) return null;
4275
- try {
4276
- const matches = querySelectorCandidates(selector).filter(
4277
- (element) => elementMatchesSourceFile(element, sourceFile)
4278
- );
4279
- const selectorIndex = finiteNumber(targetRecord.selectorIndex) ?? 0;
4280
- return matches[Math.max(0, Math.floor(selectorIndex))] ?? null;
4281
- } catch {
4282
- return null;
4283
- }
4284
- };
4285
- const roundRotationAngle = (angle) => Math.round(angle * 10) / 10;
4286
- const isSimpleRotateAngle = (value) => /^-?(?:\d+(?:\.\d+)?|\.\d+)(?:deg|rad|turn|grad)$/.test(value.trim());
4287
- const composeRotation = (element, rotationValue) => {
4288
- const original = element.getAttribute(ORIGINAL_ROTATE_ATTR)?.trim();
4289
- if (!original || original === "none" || !isSimpleRotateAngle(original)) {
4290
- return rotationValue;
4291
- }
4292
- return `calc(${original} + ${rotationValue})`;
4293
- };
4294
- const applyPathOffset = (element, edit) => {
4295
- const x = finiteNumber(edit.x);
4296
- const y = finiteNumber(edit.y);
4297
- if (x == null || y == null) return;
4298
- preparePathOffsetBase(element);
4299
- element.setAttribute(PATH_OFFSET_ATTR, "true");
4300
- element.style.setProperty(OFFSET_X_PROP, `${Math.round(x)}px`);
4301
- element.style.setProperty(OFFSET_Y_PROP, `${Math.round(y)}px`);
4302
- element.style.setProperty(
4303
- "translate",
4304
- composeTranslate(element, `var(${OFFSET_X_PROP}, 0px)`, `var(${OFFSET_Y_PROP}, 0px)`)
4305
- );
4306
- };
4307
- const readParentFlexBasisPixels = (element, size) => {
4308
- const parent = element.parentElement;
4309
- if (!parent) return null;
4310
- const styles = getComputedStyle(parent);
4311
- if (styles.display !== "flex" && styles.display !== "inline-flex") return null;
4312
- return Math.round(
4313
- Math.max(1, styles.flexDirection.startsWith("column") ? size.height : size.width)
4314
- );
4315
- };
4316
- const applyBoxSize = (element, edit) => {
4317
- const width = finiteNumber(edit.width);
4318
- const height = finiteNumber(edit.height);
4319
- if (width == null || height == null || width <= 0 || height <= 0) return;
4320
- const rounded = {
4321
- width: Math.round(Math.max(1, width)),
4322
- height: Math.round(Math.max(1, height))
4323
- };
4324
- element.setAttribute(BOX_SIZE_ATTR, "true");
4325
- element.style.setProperty(WIDTH_PROP, `${rounded.width}px`);
4326
- element.style.setProperty(HEIGHT_PROP, `${rounded.height}px`);
4327
- element.style.setProperty("box-sizing", "border-box");
4328
- element.style.setProperty("width", `${rounded.width}px`);
4329
- element.style.setProperty("height", `${rounded.height}px`);
4330
- element.style.setProperty("min-width", "0px");
4331
- element.style.setProperty("min-height", "0px");
4332
- element.style.setProperty("max-width", "none");
4333
- element.style.setProperty("max-height", "none");
4334
- const flexBasis = readParentFlexBasisPixels(element, rounded);
4335
- if (flexBasis != null) {
4336
- element.style.setProperty("flex-basis", `${flexBasis}px`);
4337
- element.style.setProperty("flex-grow", "0");
4338
- element.style.setProperty("flex-shrink", "0");
4339
- }
4340
- if (getComputedStyle(element).display === "inline") {
4341
- element.style.setProperty("display", "inline-block");
4342
- }
4343
- };
4344
- const applyRotation = (element, edit) => {
4345
- const angle = finiteNumber(edit.angle);
4346
- if (angle == null) return;
4347
- prepareRotationBase(element);
4348
- element.setAttribute(ROTATION_ATTR, "true");
4349
- element.style.setProperty(ROTATION_PROP, `${roundRotationAngle(angle)}deg`);
4350
- element.style.setProperty("transform-origin", ROTATION_TRANSFORM_ORIGIN);
4351
- element.style.setProperty("rotate", composeRotation(element, `var(${ROTATION_PROP}, 0deg)`));
4352
- };
4353
- const applyManifest = () => {
4354
- let applied = 0;
4355
- for (const edit of manifestEdits) {
4356
- const editRecord = objectRecord(edit);
4357
- if (!editRecord) continue;
4358
- const element = resolveTarget(editRecord);
4359
- if (!element) continue;
4360
- if (editRecord.kind === "path-offset") applyPathOffset(element, editRecord);
4361
- if (editRecord.kind === "box-size") applyBoxSize(element, editRecord);
4362
- if (editRecord.kind === "rotation") applyRotation(element, editRecord);
4363
- applied += 1;
4364
- }
4365
- return applied;
4366
- };
4367
- runtimeWindow.__hfStudioManualEditsApply = applyManifest;
4368
- const markWrapped = (fn) => {
4369
- try {
4370
- Object.defineProperty(fn, WRAPPED_SEEK_PROP, {
4371
- configurable: false,
4372
- enumerable: false,
4373
- value: true
4374
- });
4375
- } catch {
4376
- try {
4377
- fn[WRAPPED_SEEK_PROP] = true;
4378
- } catch {
4379
- }
4380
- }
4381
- };
4382
- const isWrapped = (fn) => Boolean(fn[WRAPPED_SEEK_PROP]);
4383
- const wrapFunction = (get, set) => {
4384
- const fn = get();
4385
- if (!fn) return false;
4386
- const seek = fn;
4387
- if (isWrapped(seek)) {
4388
- applyManifest();
4389
- return true;
4390
- }
4391
- const wrappedSeek = function(time) {
4392
- const result = seek.call(this, time);
4393
- applyManifest();
4394
- return result;
4395
- };
4396
- markWrapped(wrappedSeek);
4397
- set(wrappedSeek);
4398
- applyManifest();
4399
- return true;
4400
- };
4401
- const wrapSeekFunctions = () => {
4402
- const wrappedHfSeek = wrapFunction(
4403
- () => runtimeWindow.__hf?.seek,
4404
- (fn) => {
4405
- if (runtimeWindow.__hf) runtimeWindow.__hf.seek = fn;
4406
- }
4407
- );
4408
- const wrappedPlayerRenderSeek = wrapFunction(
4409
- () => runtimeWindow.__player?.renderSeek,
4410
- (fn) => {
4411
- if (runtimeWindow.__player) runtimeWindow.__player.renderSeek = fn;
4412
- }
4413
- );
4414
- return wrappedHfSeek || wrappedPlayerRenderSeek;
4415
- };
4416
- if (document.readyState === "loading") {
4417
- document.addEventListener("DOMContentLoaded", () => applyManifest(), { once: true });
4418
- } else {
4419
- applyManifest();
4420
- }
4421
- wrapSeekFunctions();
4422
- let remainingSeekWrapAttempts = 120;
4423
- const seekWrapInterval = setInterval(() => {
4424
- wrapSeekFunctions();
4425
- remainingSeekWrapAttempts -= 1;
4426
- if (remainingSeekWrapAttempts <= 0) clearInterval(seekWrapInterval);
4427
- }, 50);
4428
- }
4429
-
4430
- // src/routes/thumbnail.ts
4431
3326
  var THUMBNAIL_CACHE_VERSION = "v4";
4432
3327
  function registerThumbnailRoutes(api, adapter) {
4433
3328
  api.get("/projects/:id/thumbnail/*", async (c) => {
@@ -4824,101 +3719,28 @@ function registerSelectionRoutes(api, adapter) {
4824
3719
  // src/routes/media.ts
4825
3720
  import { streamSSE as streamSSE2 } from "hono/streaming";
4826
3721
  import { existsSync as existsSync9, mkdirSync as mkdirSync7 } from "fs";
4827
- import { basename as basename2, dirname as dirname2, extname as extname3, join as join13 } from "path";
4828
-
4829
- // src/helpers/mediaMetadata.ts
4830
- import { spawnSync as spawnSync2 } from "child_process";
4831
- import { extname as extname2 } from "path";
4832
- var VIDEO_EXT2 = /* @__PURE__ */ new Set([".mp4", ".mov", ".webm", ".mkv", ".avi", ".m4v"]);
4833
- var IMAGE_EXT = /* @__PURE__ */ new Set([".jpg", ".jpeg", ".png", ".webp", ".avif"]);
4834
- var AUDIO_EXT2 = /* @__PURE__ */ new Set([".mp3", ".wav", ".ogg", ".m4a", ".aac"]);
4835
- function lower(value) {
4836
- return value?.toLowerCase() ?? "";
4837
- }
4838
- function inferKindFromPath(path) {
4839
- const ext = extname2(path).toLowerCase();
4840
- if (VIDEO_EXT2.has(ext)) return "video";
4841
- if (IMAGE_EXT.has(ext)) return "image";
4842
- if (AUDIO_EXT2.has(ext)) return "audio";
4843
- return "unknown";
4844
- }
4845
- function colorLabel(input) {
4846
- if (input.isHdr) {
4847
- if (input.hdrTransfer === "pq") return "HDR PQ";
4848
- if (input.hdrTransfer === "hlg") return "HDR HLG";
4849
- return "HDR";
4850
- }
4851
- if (input.colorPrimaries.includes("bt709") || input.colorSpace.includes("bt709") || input.colorTransfer.includes("bt709")) {
4852
- return "SDR Rec.709";
4853
- }
4854
- return "SDR/unknown";
4855
- }
4856
- function classifyMediaColor(stream) {
4857
- const colorPrimaries = lower(stream?.color_primaries);
4858
- const colorSpace = lower(stream?.color_space);
4859
- const colorTransfer = lower(stream?.color_transfer);
4860
- const isHdr = colorPrimaries.includes("bt2020") || colorSpace.includes("bt2020") || colorTransfer === "smpte2084" || colorTransfer === "arib-std-b67";
4861
- const hdrTransfer = isHdr ? colorTransfer === "smpte2084" ? "pq" : colorTransfer === "arib-std-b67" ? "hlg" : "unknown" : null;
4862
- return {
4863
- dynamicRange: stream ? isHdr ? "hdr" : "sdr" : "unknown",
4864
- hdrTransfer,
4865
- label: stream ? colorLabel({ isHdr, hdrTransfer, colorPrimaries, colorSpace, colorTransfer }) : "Unknown",
4866
- isHdr,
4867
- codecName: stream?.codec_name,
4868
- profile: stream?.profile,
4869
- pixelFormat: stream?.pix_fmt,
4870
- colorSpace: stream?.color_space,
4871
- colorTransfer: stream?.color_transfer,
4872
- colorPrimaries: stream?.color_primaries,
4873
- bitsPerRawSample: stream?.bits_per_raw_sample
4874
- };
4875
- }
4876
- function probeMediaMetadata(filePath, runner = spawnSync2) {
4877
- const kind = inferKindFromPath(filePath);
4878
- if (kind === "audio" || kind === "unknown") {
4879
- return { kind, color: classifyMediaColor(null) };
4880
- }
4881
- const result = runner(
4882
- "ffprobe",
4883
- [
4884
- "-v",
4885
- "error",
4886
- "-show_entries",
4887
- "stream=codec_type,codec_name,profile,pix_fmt,color_space,color_transfer,color_primaries,bits_per_raw_sample",
4888
- "-of",
4889
- "json",
4890
- filePath
4891
- ],
4892
- { timeout: 15e3, maxBuffer: 1024 * 1024 }
4893
- );
4894
- if (result.error?.code === "ENOENT") {
4895
- return { kind, color: classifyMediaColor(null), probeError: "ffprobe unavailable" };
4896
- }
4897
- if (result.status !== 0) {
4898
- return { kind, color: classifyMediaColor(null), probeError: "ffprobe failed" };
4899
- }
4900
- try {
4901
- const parsed = JSON.parse(String(result.stdout || "{}"));
4902
- const stream = parsed.streams?.find(
4903
- (item) => kind === "image" ? item.codec_type === "video" : item.codec_type === kind
4904
- );
4905
- return { kind, color: classifyMediaColor(stream) };
4906
- } catch {
4907
- return { kind, color: classifyMediaColor(null), probeError: "ffprobe returned invalid json" };
4908
- }
4909
- }
4910
-
4911
- // src/routes/media.ts
4912
- var VIDEO_EXTENSIONS = /* @__PURE__ */ new Set([".mp4", ".mov", ".webm", ".mkv", ".avi"]);
3722
+ import { basename as basename2, dirname as dirname2, extname as extname2, join as join13 } from "path";
3723
+ var VIDEO_EXTENSIONS = /* @__PURE__ */ new Set([
3724
+ ".mp4",
3725
+ ".mov",
3726
+ ".webm",
3727
+ ".mkv",
3728
+ ".avi",
3729
+ ".m4v",
3730
+ ".mxf",
3731
+ ".mts",
3732
+ ".m2ts",
3733
+ ".ts"
3734
+ ]);
4913
3735
  var IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([".jpg", ".jpeg", ".png", ".webp"]);
4914
3736
  var VIDEO_OUTPUT_EXTENSIONS = /* @__PURE__ */ new Set([".webm", ".mov"]);
4915
3737
  var QUALITIES = /* @__PURE__ */ new Set(["fast", "balanced", "best"]);
4916
3738
  var DEVICES = /* @__PURE__ */ new Set(["auto", "cpu", "coreml", "cuda"]);
4917
3739
  function isVideoPath(path) {
4918
- return VIDEO_EXTENSIONS.has(extname3(path).toLowerCase());
3740
+ return VIDEO_EXTENSIONS.has(extname2(path).toLowerCase());
4919
3741
  }
4920
3742
  function isImagePath(path) {
4921
- return IMAGE_EXTENSIONS.has(extname3(path).toLowerCase());
3743
+ return IMAGE_EXTENSIONS.has(extname2(path).toLowerCase());
4922
3744
  }
4923
3745
  function normalizeProjectAssetPath(path) {
4924
3746
  return path.trim().replace(/^[.]\//, "").replace(/[?#].*$/, "");
@@ -4927,11 +3749,11 @@ function containsNullByte(path) {
4927
3749
  return path.includes("\0");
4928
3750
  }
4929
3751
  function slugFileBase(path) {
4930
- const name = basename2(path, extname3(path)).replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
3752
+ const name = basename2(path, extname2(path)).replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
4931
3753
  return name || "media";
4932
3754
  }
4933
3755
  function uniqueAssetPath(projectDir, assetPath) {
4934
- const ext = extname3(assetPath);
3756
+ const ext = extname2(assetPath);
4935
3757
  const withoutExt = assetPath.slice(0, -ext.length);
4936
3758
  let candidate = assetPath;
4937
3759
  for (let index = 2; existsSync9(join13(projectDir, candidate)); index++) {
@@ -4986,7 +3808,7 @@ function registerMediaRoutes(api, adapter, options = {}) {
4986
3808
  const filePath = resolveWithinProject(project.dir, assetPath);
4987
3809
  if (!filePath) return c.json({ error: "forbidden" }, 403);
4988
3810
  if (!existsSync9(filePath)) return c.json({ error: "media not found" }, 404);
4989
- return c.json({ path: assetPath, metadata: readMediaMetadata(filePath) });
3811
+ return c.json({ path: assetPath, metadata: await readMediaMetadata(filePath) });
4990
3812
  });
4991
3813
  api.post(
4992
3814
  "/projects/:id/media/remove-background",
@@ -5023,10 +3845,10 @@ function registerMediaRoutes(api, adapter, options = {}) {
5023
3845
  const outputAssetPath = requestedOutput ? uniqueAssetPath(project.dir, requestedOutput) : defaultOutputPath(project.dir, inputAssetPath);
5024
3846
  const outputPath = resolveWithinProject(project.dir, outputAssetPath);
5025
3847
  if (!outputPath) return c.json({ error: "forbidden" }, 403);
5026
- if (inputIsVideo && !VIDEO_OUTPUT_EXTENSIONS.has(extname3(outputAssetPath).toLowerCase())) {
3848
+ if (inputIsVideo && !VIDEO_OUTPUT_EXTENSIONS.has(extname2(outputAssetPath).toLowerCase())) {
5027
3849
  return c.json({ error: "video background removal output must be .webm or .mov" }, 400);
5028
3850
  }
5029
- if (inputIsImage && extname3(outputAssetPath).toLowerCase() !== ".png") {
3851
+ if (inputIsImage && extname2(outputAssetPath).toLowerCase() !== ".png") {
5030
3852
  return c.json({ error: "image background removal output must be .png" }, 400);
5031
3853
  }
5032
3854
  let backgroundOutputAssetPath;
@@ -5142,29 +3964,6 @@ function createStudioApi(adapter) {
5142
3964
  return api;
5143
3965
  }
5144
3966
 
5145
- // src/helpers/screenshotClip.ts
5146
- function getElementScreenshotClip(selector, selectorIndex) {
5147
- const matches = Array.from(document.querySelectorAll(selector)).filter(
5148
- (el2) => el2 instanceof HTMLElement
5149
- );
5150
- const safeIndex = Math.max(0, Math.min(matches.length - 1, Math.floor(selectorIndex ?? 0)));
5151
- const el = matches[safeIndex] ?? null;
5152
- if (!(el instanceof HTMLElement)) return void 0;
5153
- const rect = el.getBoundingClientRect();
5154
- if (rect.width < 4 || rect.height < 4) return void 0;
5155
- const pad = 8;
5156
- const x = Math.max(0, rect.left - pad);
5157
- const y = Math.max(0, rect.top - pad);
5158
- const maxWidth = window.innerWidth - x;
5159
- const maxHeight = window.innerHeight - y;
5160
- return {
5161
- x,
5162
- y,
5163
- width: Math.max(1, Math.min(rect.width + pad * 2, maxWidth)),
5164
- height: Math.max(1, Math.min(rect.height + pad * 2, maxHeight))
5165
- };
5166
- }
5167
-
5168
3967
  // src/helpers/backgroundRemovalJob.ts
5169
3968
  function createBackgroundRemovalJob(opts, render) {
5170
3969
  const state = {