@hyperframes/studio-server 0.8.142 → 0.8.143

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/index.js CHANGED
@@ -55,16 +55,19 @@ import {
55
55
  import {
56
56
  DEFAULT_TARGET_LUFS,
57
57
  absoluteLoudnessPlan,
58
+ decodeWellFormedEscapes,
58
59
  measureAudio,
60
+ requestSubPath,
61
+ requestedProjectPath,
59
62
  requiredFfmpeg,
60
63
  resolveLocalAudioPath
61
- } from "./chunk-DSVQ2U7J.js";
64
+ } from "./chunk-32IH7WBY.js";
62
65
  import {
63
66
  injectMediaCodecMap,
64
67
  isAutoProxyEnabled,
65
68
  proxyEtagSalt,
66
69
  resolvePreviewMediaCodecProbeCache
67
- } from "./chunk-5VCIZYUE.js";
70
+ } from "./chunk-AYKLBPP2.js";
68
71
  import {
69
72
  CACHE_DIR_NAME,
70
73
  PROXY_PENDING_RETRY_AFTER_SECONDS,
@@ -75,7 +78,7 @@ import {
75
78
  proxyActivityMark,
76
79
  resolveProxy,
77
80
  waitForProxy
78
- } from "./chunk-QP7LKFU2.js";
81
+ } from "./chunk-6EFY2WPN.js";
79
82
  import {
80
83
  PROXY_VARIANT_CONFIG,
81
84
  decideMediaProxyEligibility,
@@ -84,7 +87,7 @@ import {
84
87
  probeMediaMetadata,
85
88
  recordProxyRequest,
86
89
  resolveProxyVariantRequest
87
- } from "./chunk-74JVVCWL.js";
90
+ } from "./chunk-RN33UALN.js";
88
91
  import {
89
92
  folderGone,
90
93
  isInHiddenOrVendorDir,
@@ -158,6 +161,12 @@ function registerProjectRoutes(api, adapter) {
158
161
  }
159
162
 
160
163
  // src/routes/files.ts
164
+ import { encodeUrlPath, decodedUrlPath, decodeCssEscapes } from "@hyperframes/parsers/asset-paths";
165
+ import {
166
+ scanHtmlOpeningTags,
167
+ decodeAuthoredAttribute,
168
+ HTML_ATTRIBUTE_ENTITIES
169
+ } from "@hyperframes/parsers";
161
170
  import { bodyLimit } from "hono/body-limit";
162
171
  import {
163
172
  closeSync,
@@ -174,7 +183,7 @@ import {
174
183
  readdirSync as readdirSync2,
175
184
  realpathSync
176
185
  } from "fs";
177
- import { resolve as resolve2, dirname as dirname3, join as join6 } from "path";
186
+ import { resolve as resolve2, dirname as dirname3, join as join6, sep as sep2 } from "path";
178
187
 
179
188
  // src/helpers/mime.ts
180
189
  var MIME_TYPES = {
@@ -1191,14 +1200,6 @@ function resolveGsapWriter(env) {
1191
1200
  throw new Error(`Invalid ${GSAP_WRITER_MIGRATION.flag}=${configured}; expected recast or acorn`);
1192
1201
  }
1193
1202
 
1194
- // src/helpers/requestSubPath.ts
1195
- function requestSubPath(url, route) {
1196
- const routeSegments = route.split("/");
1197
- const segments = new URL(url).pathname.split("/");
1198
- const start = segments.indexOf(routeSegments[0] ?? "");
1199
- return decodeURIComponent(segments.slice(start + routeSegments.length).join("/"));
1200
- }
1201
-
1202
1203
  // src/routes/files.ts
1203
1204
  import {
1204
1205
  hasCompositionOutsideTemplates,
@@ -1505,15 +1506,114 @@ var escapeRegExp = (text2) => text2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1505
1506
  var SEPARATOR = String.raw`\\{0,2}[\\/]`;
1506
1507
  var REFERENCE_START = String.raw`(?<![\w./\\+-])`;
1507
1508
  var FILE_END = String.raw`(?![\w-]|\.\w)`;
1509
+ var ENTITY_SPELLINGS = HTML_ATTRIBUTE_ENTITIES;
1510
+ function referenceSpellings(char) {
1511
+ const spellings = [escapeRegExp(char)];
1512
+ const encoded = encodeUrlPath(char);
1513
+ if (encoded !== char)
1514
+ spellings.push(encoded.replace(/[A-F]/g, (hex) => `[${hex}${hex.toLowerCase()}]`));
1515
+ if (ENTITY_SPELLINGS[char]) spellings.push(...ENTITY_SPELLINGS[char]);
1516
+ if (["'", '"', "`", "$", " ", "(", ")"].includes(char)) {
1517
+ spellings.push(escapeRegExp(`&#92;${char}`), escapeRegExp(`&#92;${escapeHtmlReference(char)}`));
1518
+ }
1519
+ const scriptSpelling = escapeQuotedReference(char, '"');
1520
+ if (scriptSpelling !== char) spellings.push(escapeRegExp(scriptSpelling));
1521
+ if (["'", "`", "$", " ", "(", ")"].includes(char)) spellings.push(escapeRegExp(`\\${char}`));
1522
+ if (/[\t\n\f\r =<>`]/.test(char)) spellings.push(`&#${char.codePointAt(0)};`);
1523
+ if (char.charCodeAt(0) < 32 || char.charCodeAt(0) === 127) {
1524
+ spellings.push(escapeRegExp(`\\${char.charCodeAt(0).toString(16)} `));
1525
+ }
1526
+ return spellings.length === 1 ? spellings[0] : `(?:${spellings.join("|")})`;
1527
+ }
1528
+ function escapeHtmlReference(path) {
1529
+ return path.replace(/[&"']/g, (char) => ENTITY_SPELLINGS[char][0]);
1530
+ }
1531
+ function escapeQuotedReference(path, quote) {
1532
+ const escaped = JSON.stringify(path).slice(1, -1);
1533
+ return quote === '"' ? escaped : escaped.replace(new RegExp(escapeRegExp(quote), "g"), `\\${quote}`);
1534
+ }
1535
+ function escapeRawUrlPath(path) {
1536
+ const encodeSpaces = path.startsWith(" ") || path.endsWith(" ");
1537
+ return path.replace(/[\s\S]/g, (char) => {
1538
+ if (char.charCodeAt(0) < 32 || ":?%#\\".includes(char) || encodeSpaces && char === " ") {
1539
+ return encodeUrlPath(char);
1540
+ }
1541
+ return char;
1542
+ });
1543
+ }
1544
+ function spellLike(reference, path, oldPath, context) {
1545
+ const { syntax, quote, url, encodeAll } = context;
1546
+ if (url && (encodeAll || reference !== oldPath && /%[0-9A-Fa-f]{2}/.test(reference)))
1547
+ return encodeUrlPath(path);
1548
+ switch (syntax) {
1549
+ case "html": {
1550
+ const escaped = escapeHtmlReference(url ? escapeRawUrlPath(path) : path);
1551
+ return ["'", '"'].includes(quote) ? escaped : escaped.replace(/[\t\n\f\r =<>`]/g, (char) => `&#${char.codePointAt(0)};`);
1552
+ }
1553
+ case "script": {
1554
+ if (!["'", '"', "`"].includes(quote)) return path;
1555
+ const escaped = escapeQuotedReference(path, quote);
1556
+ return quote === "`" ? escaped.replace(/\$\{/g, "\\${") : escaped;
1557
+ }
1558
+ case "css":
1559
+ return url ? encodeUrlPath(path) : path;
1560
+ case "plain":
1561
+ return path;
1562
+ }
1563
+ }
1564
+ function referenceSyntax(file) {
1565
+ if (/\.html$/i.test(file)) return "html";
1566
+ if (/\.css$/i.test(file)) return "css";
1567
+ if (/\.(?:[cm]?js|jsx|tsx?|json)$/i.test(file)) return "script";
1568
+ return "plain";
1569
+ }
1570
+ function decodeQuotedReference(text2, syntax) {
1571
+ if (syntax === "css") return decodeCssEscapes(text2);
1572
+ if (syntax === "script") {
1573
+ const controls = { b: "\b", f: "\f", n: "\n", r: "\r", t: " " };
1574
+ return text2.replace(
1575
+ /\\(?:u([0-9a-fA-F]{4})|([bfnrt"'`$\\/]))/g,
1576
+ (_escaped, hex, char) => {
1577
+ if (hex !== void 0) return String.fromCharCode(Number.parseInt(hex, 16));
1578
+ return controls[char] ?? char;
1579
+ }
1580
+ );
1581
+ }
1582
+ return text2;
1583
+ }
1508
1584
  function referencePattern(oldPath, isDirectory) {
1509
- const name = oldPath.split("/").map(escapeRegExp).join(SEPARATOR);
1585
+ const name = oldPath.split("/").map((segment) => Array.from(segment, referenceSpellings).join("")).join(SEPARATOR);
1510
1586
  const end = isDirectory ? `(?=${SEPARATOR})` : FILE_END;
1511
1587
  return new RegExp(
1512
1588
  String.raw`${REFERENCE_START}(?<lead>(?:\.{1,2}${SEPARATOR}|${SEPARATOR}){0,4})${name}${end}`,
1513
1589
  "g"
1514
1590
  );
1515
1591
  }
1592
+ function rewriteEntityAttributes(text2, rewrite) {
1593
+ const parts = [];
1594
+ let cursor = 0;
1595
+ for (const tag of scanHtmlOpeningTags(text2)) {
1596
+ for (const attr of tag.attributes) {
1597
+ if (attr.kind !== "value") continue;
1598
+ const decoded = decodeAuthoredAttribute(attr.value);
1599
+ if (decoded === attr.value) continue;
1600
+ let value = escapeHtmlReference(decoded);
1601
+ if (!attr.quote)
1602
+ value = value.replace(/[\t\n\f\r =<>`]/g, (char) => `&#${char.codePointAt(0)};`);
1603
+ const source = `<x ${attr.name}=${attr.quote}${value}${attr.quote}>`;
1604
+ const updated = rewrite(source);
1605
+ if (updated === source) continue;
1606
+ const replacement = scanHtmlOpeningTags(updated)[0].attributes[0];
1607
+ if (replacement.kind !== "value") throw new Error("Reference rewrite removed its attribute");
1608
+ parts.push(text2.slice(cursor, attr.valueStart), replacement.value);
1609
+ cursor = attr.valueEnd;
1610
+ }
1611
+ }
1612
+ parts.push(text2.slice(cursor));
1613
+ return parts.join("");
1614
+ }
1516
1615
  function referenceRewriter(oldPath, newPath, isDirectory, existing = []) {
1616
+ const oldIdentity = sep2 === "\\" ? oldPath.replace(/\\/g, "/") : oldPath;
1517
1617
  const pattern = referencePattern(oldPath, isDirectory);
1518
1618
  const around = /* @__PURE__ */ new Map();
1519
1619
  const afterLengths = /* @__PURE__ */ new Set();
@@ -1527,24 +1627,123 @@ function referenceRewriter(oldPath, newPath, isDirectory, existing = []) {
1527
1627
  before
1528
1628
  );
1529
1629
  afterLengths.add(after.length);
1530
- window = Math.max(window, path.length * 3);
1531
- }
1532
- }
1533
- const normalized = (text2) => text2.replace(/\\{0,2}[\\/]/g, "/");
1534
- return (text2) => text2.replace(pattern, (...args) => {
1535
- const [match, offset] = [args[0], args.at(-3)];
1536
- const { lead } = args.at(-1);
1537
- const at = offset + lead.length;
1538
- const head = normalized(text2.slice(Math.max(0, at - window), at));
1539
- const tail = normalized(text2.slice(offset + match.length, offset + match.length + window));
1540
- const inLonger = [...afterLengths].some((after) => {
1541
- const befores = tail.length >= after && around.get(tail.slice(0, after));
1542
- return befores && [...befores].some(
1543
- ([length, set]) => head.length >= length && set.has(head.slice(head.length - length))
1630
+ window = Math.max(window, path.length * 9);
1631
+ }
1632
+ }
1633
+ const normalized = (text2, context, full = false) => {
1634
+ let value = text2;
1635
+ if (context.syntax === "html" || context.htmlAttribute) {
1636
+ value = decodeAuthoredAttribute(value);
1637
+ }
1638
+ value = decodeQuotedReference(value, context.syntax);
1639
+ if (context.url) {
1640
+ value = value.replace(/\\/g, "/");
1641
+ value = full ? decodedUrlPath(value) : decodeWellFormedEscapes(value);
1642
+ }
1643
+ return sep2 === "\\" ? value.replace(/\\/g, "/") : value;
1644
+ };
1645
+ return function rewriteText(text2, syntax = "html", normalizeEntities = true) {
1646
+ if (syntax === "html" && normalizeEntities) {
1647
+ text2 = rewriteEntityAttributes(text2, (source) => rewriteText(source, "html", false));
1648
+ }
1649
+ pattern.lastIndex = 0;
1650
+ if (!pattern.test(text2)) return text2;
1651
+ pattern.lastIndex = 0;
1652
+ const tags = syntax === "html" ? scanHtmlOpeningTags(text2) : [];
1653
+ const inlineRegions = tags.filter(
1654
+ (tag) => tag.closed && ["script", "style"].includes(tag.name) && tag.rawTextEnd !== null
1655
+ ).map((tag) => ({
1656
+ start: tag.end,
1657
+ end: tag.rawTextEnd,
1658
+ syntax: tag.name === "style" ? "css" : "script"
1659
+ }));
1660
+ const attributes = tags.flatMap(
1661
+ (tag) => tag.attributes.flatMap((attr) => attr.kind === "value" ? [attr] : [])
1662
+ );
1663
+ const cssContexts = syntax === "css" ? [{ start: 0, end: text2.length, htmlAttribute: false }] : [
1664
+ ...inlineRegions.filter((region) => region.syntax === "css").map((region) => ({ ...region, htmlAttribute: false })),
1665
+ ...attributes.filter((attr) => attr.name === "style").map((attr) => ({ ...attr, htmlAttribute: true }))
1666
+ ];
1667
+ const cssFields = (pattern2) => cssContexts.flatMap(
1668
+ (context) => [...text2.slice(context.start, context.end).matchAll(pattern2)].map((match) => {
1669
+ const prefix = /^(?:url\(\s*|@import\s+)/i.exec(match[0])[0];
1670
+ const rest = match[0].slice(prefix.length);
1671
+ const delimiter = context.htmlAttribute ? /^(?:["']|&quot;|&#39;|&apos;)/.exec(rest)?.[0] : /^["']/.exec(rest)?.[0];
1672
+ const start = context.start + match.index + prefix.length + (delimiter?.length ?? 0);
1673
+ return {
1674
+ start,
1675
+ end: context.start + match.index + match[0].length,
1676
+ syntax: "css",
1677
+ htmlAttribute: context.htmlAttribute,
1678
+ encodeAll: true
1679
+ };
1680
+ })
1681
+ );
1682
+ const urlRegions = [
1683
+ ...attributes.filter((attr) => ["src", "href", "poster", "xlink:href"].includes(attr.name)).map((attr) => ({
1684
+ start: attr.valueStart + (/^[\t\n\f\r ]*/.exec(attr.value)?.[0].length ?? 0),
1685
+ end: attr.valueEnd,
1686
+ syntax: "html",
1687
+ htmlAttribute: true,
1688
+ encodeAll: false
1689
+ })),
1690
+ ...attributes.filter((attr) => attr.name === "srcset").flatMap(
1691
+ (attr) => [...attr.value.matchAll(/(^|,)[\t\n\f\r ]*([^,\t\n\f\r ]+)/g)].map((match) => ({
1692
+ start: attr.valueStart + match.index + match[0].length - match[2].length,
1693
+ end: attr.valueStart + match.index + match[0].length,
1694
+ syntax: "html",
1695
+ htmlAttribute: true,
1696
+ encodeAll: true
1697
+ }))
1698
+ ),
1699
+ ...cssFields(
1700
+ /\burl\((?:\s*"(?:\\.|[^"\\])*"\s*|\s*'(?:\\.|[^'\\])*'\s*|(?:\\.|[^)\\])*)\)/gi
1701
+ ),
1702
+ ...cssFields(/@import\s+(?:"[^"]*"|'[^']*')/gi)
1703
+ ].sort((a, b) => a.start - b.start);
1704
+ let urlIndex = 0;
1705
+ let regionIndex = 0;
1706
+ return text2.replace(pattern, (...args) => {
1707
+ const [match, offset] = [args[0], args.at(-3)];
1708
+ const { lead } = args.at(-1);
1709
+ while (inlineRegions[regionIndex] && inlineRegions[regionIndex].end <= offset) regionIndex++;
1710
+ const region = inlineRegions[regionIndex];
1711
+ const matchSyntax = region && offset >= region.start ? region.syntax : syntax;
1712
+ while (urlRegions[urlIndex] && urlRegions[urlIndex].end <= offset) urlIndex++;
1713
+ const urlRegion = urlRegions[urlIndex];
1714
+ const url = matchSyntax !== "script" && !!urlRegion && offset >= urlRegion.start;
1715
+ const context = {
1716
+ syntax: url ? urlRegion.syntax : matchSyntax,
1717
+ url,
1718
+ htmlAttribute: url && urlRegion.htmlAttribute,
1719
+ encodeAll: url && urlRegion.encodeAll,
1720
+ quote: text2[offset - 1] ?? ""
1721
+ };
1722
+ if (url && offset !== urlRegion.start) return match;
1723
+ const reference = match.slice(lead.length);
1724
+ if (normalized(reference, context, true) !== oldIdentity) return match;
1725
+ if (isDirectory && !normalized(
1726
+ text2.slice(offset + match.length, offset + match.length + 4),
1727
+ context
1728
+ ).startsWith("/"))
1729
+ return match;
1730
+ const at = offset + lead.length;
1731
+ const head = normalized(text2.slice(Math.max(0, at - window), at), context);
1732
+ const tail = normalized(
1733
+ text2.slice(offset + match.length, offset + match.length + window),
1734
+ context,
1735
+ context.url
1544
1736
  );
1737
+ const inLonger = [...afterLengths].some((after) => {
1738
+ const befores = tail.length >= after && around.get(tail.slice(0, after));
1739
+ return befores && [...befores].some(
1740
+ ([length, set]) => head.length >= length && set.has(head.slice(head.length - length))
1741
+ );
1742
+ });
1743
+ if (inLonger) return match;
1744
+ return `${lead}${spellLike(reference, newPath, oldPath, context)}`;
1545
1745
  });
1546
- return inLonger ? match : `${lead}${newPath}`;
1547
- });
1746
+ };
1548
1747
  }
1549
1748
  function occurrences(text2, part) {
1550
1749
  const at = [];
@@ -1577,7 +1776,7 @@ function updateReferences(projectDir, oldPath, newPath, isDirectory) {
1577
1776
  if (!isSafePath(projectDir, file) || isPrivateProjectFile(projectDir, file)) continue;
1578
1777
  const content = readableText(file);
1579
1778
  if (content === null) continue;
1580
- const updated = rewrite(content);
1779
+ const updated = rewrite(content, referenceSyntax(file));
1581
1780
  if (updated !== content) {
1582
1781
  replaceFileAtomically2(file, updated, statSync3(file).mode);
1583
1782
  updatedCount++;
@@ -3454,10 +3653,11 @@ function registerFileRoutes(api, adapter) {
3454
3653
  const res = await resolveProjectFile(c, adapter, { mustExist: true });
3455
3654
  if ("error" in res) return res.error;
3456
3655
  const body = await c.req.json();
3457
- if (!body.newPath || body.newPath.includes("\0")) {
3656
+ const newPath = body !== null && typeof body === "object" && "newPath" in body ? body.newPath : void 0;
3657
+ if (typeof newPath !== "string" || !newPath || newPath.includes("\0") || /[\uD800-\uDFFF]/u.test(newPath)) {
3458
3658
  return c.json({ error: "newPath required" }, 400);
3459
3659
  }
3460
- const newAbs = resolveWithinProject(res.project.dir, body.newPath);
3660
+ const newAbs = resolveWithinProject(res.project.dir, newPath);
3461
3661
  if (!newAbs) {
3462
3662
  return c.json({ error: "forbidden" }, 403);
3463
3663
  }
@@ -3467,8 +3667,8 @@ function registerFileRoutes(api, adapter) {
3467
3667
  const isDirectory = statSync3(res.absPath, { throwIfNoEntry: false })?.isDirectory() ?? false;
3468
3668
  ensureDir(res.project.dir, newAbs);
3469
3669
  renameSync2(res.absPath, newAbs);
3470
- const updatedFiles = updateReferences(res.project.dir, res.filePath, body.newPath, isDirectory);
3471
- return c.json({ ok: true, path: body.newPath, updatedReferences: updatedFiles });
3670
+ const updatedFiles = updateReferences(res.project.dir, res.filePath, newPath, isDirectory);
3671
+ return c.json({ ok: true, path: newPath, updatedReferences: updatedFiles });
3472
3672
  });
3473
3673
  api.post("/projects/:id/duplicate-file", async (c) => {
3474
3674
  const project = await adapter.resolveProject(c.req.param("id"));
@@ -4760,12 +4960,7 @@ async function generateThumbnail(path, signal) {
4760
4960
  async function resolveSource(adapter, id, url) {
4761
4961
  const project = await adapter.resolveProject(id);
4762
4962
  if (!project) throw new HTTPException(404);
4763
- let relative5;
4764
- try {
4765
- relative5 = requestSubPath(url, "projects/:id/image-thumbnail");
4766
- } catch {
4767
- throw new HTTPException(400);
4768
- }
4963
+ const relative5 = requestSubPath(url, "projects/:id/image-thumbnail");
4769
4964
  if (!/\.jpe?g$/i.test(relative5)) throw new HTTPException(415);
4770
4965
  const path = pinWithinProject(project.dir, relative5);
4771
4966
  if (!path) throw new HTTPException(404);
@@ -5079,7 +5274,7 @@ function registerWaveformRoutes(api, adapter) {
5079
5274
  // src/routes/freezeFrame.ts
5080
5275
  import { execFile } from "child_process";
5081
5276
  import { readFileSync as readFileSync11, rmSync as rmSync5, statSync as statSync8 } from "fs";
5082
- import { basename as basename2, dirname as dirname6, join as join13, relative as relative4, sep as sep2 } from "path";
5277
+ import { basename as basename2, dirname as dirname6, join as join13, relative as relative4, sep as sep3 } from "path";
5083
5278
  import { findFfBinary as findFfBinary3 } from "@hyperframes/parsers/ff-binaries";
5084
5279
  import {
5085
5280
  atomicTempPath,
@@ -5343,7 +5538,7 @@ async function extractStill(projectDir, absPath, source, playhead, tools) {
5343
5538
  imagePath
5344
5539
  );
5345
5540
  if (failed) return failed;
5346
- const depth = relative4(projectDir, fileDir).split(sep2).filter(Boolean).length;
5541
+ const depth = relative4(projectDir, fileDir).split(sep3).filter(Boolean).length;
5347
5542
  const stillPath = `${FREEZE_DIR.join("/")}/${fileName}`;
5348
5543
  return { imageSrc: `${"../".repeat(depth)}${stillPath}`, stillPath, imagePath };
5349
5544
  }
@@ -5920,9 +6115,6 @@ function isVideoPath(path) {
5920
6115
  function isImagePath(path) {
5921
6116
  return IMAGE_EXTENSIONS.has(extname(path).toLowerCase());
5922
6117
  }
5923
- function normalizeProjectAssetPath(path) {
5924
- return path.trim().replace(/^[.]\//, "").replace(/[?#].*$/, "");
5925
- }
5926
6118
  function containsNullByte(path) {
5927
6119
  return path.includes("\0");
5928
6120
  }
@@ -5987,7 +6179,7 @@ function registerMediaRoutes(api, adapter, options = {}) {
5987
6179
  api.get("/projects/:id/media/metadata", async (c) => {
5988
6180
  const project = await adapter.resolveProject(c.req.param("id"));
5989
6181
  if (!project) return c.json({ error: "not found" }, 404);
5990
- const assetPath = normalizeProjectAssetPath(c.req.query("path") ?? "");
6182
+ const assetPath = requestedProjectPath(project.dir, c.req.query("path") ?? "");
5991
6183
  if (!assetPath) return c.json({ error: "path required" }, 400);
5992
6184
  if (containsNullByte(assetPath)) return c.json({ error: "forbidden" }, 403);
5993
6185
  if (/^(?:https?:|data:|blob:)/i.test(assetPath)) {
@@ -6009,7 +6201,7 @@ function registerMediaRoutes(api, adapter, options = {}) {
6009
6201
  const project = await adapter.resolveProject(c.req.param("id"));
6010
6202
  if (!project) return c.json({ error: "not found" }, 404);
6011
6203
  const body = await c.req.json().catch(() => ({}));
6012
- const inputAssetPath = body.inputPath ? normalizeProjectAssetPath(body.inputPath) : "";
6204
+ const inputAssetPath = body.inputPath ? requestedProjectPath(project.dir, body.inputPath) : "";
6013
6205
  if (!inputAssetPath) return c.json({ error: "inputPath required" }, 400);
6014
6206
  if (containsNullByte(inputAssetPath)) return c.json({ error: "forbidden" }, 403);
6015
6207
  if (/^(?:https?:|data:|blob:)/i.test(inputAssetPath)) {
@@ -6023,7 +6215,7 @@ function registerMediaRoutes(api, adapter, options = {}) {
6023
6215
  if (!inputIsVideo && !inputIsImage) {
6024
6216
  return c.json({ error: "background removal supports video or image assets only" }, 400);
6025
6217
  }
6026
- const requestedOutput = body.outputPath ? posix.normalize(normalizeProjectAssetPath(body.outputPath)) : "";
6218
+ const requestedOutput = body.outputPath ? posix.normalize(requestedProjectPath(project.dir, body.outputPath)) : "";
6027
6219
  if (requestedOutput && containsNullByte(requestedOutput)) {
6028
6220
  return c.json({ error: "forbidden" }, 403);
6029
6221
  }
@@ -6497,6 +6689,7 @@ export {
6497
6689
  createStudioManualEditsRenderBodyScript,
6498
6690
  createStudioMotionRenderBodyScript,
6499
6691
  createStudioPositionSeekReapplyScript,
6692
+ decodeWellFormedEscapes,
6500
6693
  dedupeClonedCompositionId,
6501
6694
  duplicateElementInHtml,
6502
6695
  fileContentVersion,