@hyperframes/studio-server 0.8.115 → 0.8.117

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
@@ -591,6 +591,7 @@ import {
591
591
  addMotionPathToScript,
592
592
  removeArcPathFromScript,
593
593
  addAnimationWithKeyframesToScript,
594
+ replaceTweenWithKeyframesInScript,
594
595
  splitAnimationsInScript,
595
596
  splitIntoPropertyGroupsFromScript,
596
597
  shiftPositionsInScript,
@@ -1462,6 +1463,21 @@ function bakeVisibilityOnDelete(document, anim) {
1462
1463
  } catch {
1463
1464
  }
1464
1465
  }
1466
+ function replaceInPlaceUnlessSetOrUnknown(scriptText, body) {
1467
+ const edit = { ...body, easeEach: resolveReplacementEaseEach(scriptText, body) };
1468
+ const inPlace = replaceTweenWithKeyframesInScript(scriptText, body.animationId, edit);
1469
+ if (inPlace !== null) return inPlace;
1470
+ const script = removeAnimationFromScript(scriptText, body.animationId);
1471
+ return addAnimationWithKeyframesToScript(
1472
+ script,
1473
+ body.targetSelector,
1474
+ body.position,
1475
+ body.duration,
1476
+ body.keyframes,
1477
+ body.ease,
1478
+ edit.easeEach
1479
+ ).script;
1480
+ }
1465
1481
  function resolveReplacementEaseEach(scriptText, request) {
1466
1482
  if (request.easeEach !== void 0) return request.easeEach;
1467
1483
  const original = parseGsapScriptAcorn(scriptText).animations.find(
@@ -1868,17 +1884,7 @@ function executeGsapMutationAcorn(body, block, respond) {
1868
1884
  if (keyframesWritePosition(body.keyframes) || keyframesWriteRotation(body.keyframes)) {
1869
1885
  stripStudioEditsFromTarget(block.document, body.targetSelector);
1870
1886
  }
1871
- const script = removeAnimationFromScript(block.scriptText, body.animationId);
1872
- const added = addAnimationWithKeyframesToScript(
1873
- script,
1874
- body.targetSelector,
1875
- body.position,
1876
- body.duration,
1877
- body.keyframes,
1878
- body.ease,
1879
- resolveReplacementEaseEach(block.scriptText, body)
1880
- );
1881
- return added.script;
1887
+ return replaceInPlaceUnlessSetOrUnknown(block.scriptText, body);
1882
1888
  }
1883
1889
  case "split-animations": {
1884
1890
  if (typeof body.originalId !== "string" || !body.originalId || typeof body.newId !== "string" || !body.newId || typeof body.splitTime !== "number" || !Number.isFinite(body.splitTime) || typeof body.elementStart !== "number" || !Number.isFinite(body.elementStart) || typeof body.elementDuration !== "number" || !Number.isFinite(body.elementDuration) || body.elementDuration <= 0) {
@@ -2200,17 +2206,7 @@ async function executeGsapMutationRecast(body, block, respond) {
2200
2206
  if (keyframesWritePosition(body.keyframes) || keyframesWriteRotation(body.keyframes)) {
2201
2207
  stripStudioEditsFromTarget(block.document, body.targetSelector);
2202
2208
  }
2203
- const script = removeAnimationFromScript2(block.scriptText, body.animationId);
2204
- const added = addAnimationWithKeyframesToScript2(
2205
- script,
2206
- body.targetSelector,
2207
- body.position,
2208
- body.duration,
2209
- body.keyframes,
2210
- body.ease,
2211
- resolveReplacementEaseEach(block.scriptText, body)
2212
- );
2213
- return added.script;
2209
+ return replaceInPlaceUnlessSetOrUnknown(block.scriptText, body);
2214
2210
  }
2215
2211
  case "split-animations": {
2216
2212
  if (typeof body.originalId !== "string" || !body.originalId || typeof body.newId !== "string" || !body.newId || typeof body.splitTime !== "number" || !Number.isFinite(body.splitTime) || typeof body.elementStart !== "number" || !Number.isFinite(body.elementStart) || typeof body.elementDuration !== "number" || !Number.isFinite(body.elementDuration) || body.elementDuration <= 0) {
@@ -4894,7 +4890,7 @@ function registerWaveformRoutes(api, adapter) {
4894
4890
 
4895
4891
  // src/routes/freezeFrame.ts
4896
4892
  import { execFile } from "child_process";
4897
- import { existsSync as existsSync8, readFileSync as readFileSync11, statSync as statSync8 } from "fs";
4893
+ import { closeSync as closeSync3, openSync as openSync3, readFileSync as readFileSync11, rmSync as rmSync5, statSync as statSync8 } from "fs";
4898
4894
  import { dirname as dirname5, join as join13, relative as relative4, sep as sep2 } from "path";
4899
4895
  import { findFfBinary as findFfBinary3 } from "@hyperframes/parsers/ff-binaries";
4900
4896
  import { replaceFileAtomically as replaceFileAtomically3 } from "@hyperframes/core/atomic-file";
@@ -4922,7 +4918,7 @@ function freezeFrameMediaTime(input) {
4922
4918
  return input.mediaStart + sourceTimeAt(rate, Math.max(0, input.playhead - input.clipStart));
4923
4919
  }
4924
4920
  function freezeExtractArgs(src, mediaTime, output) {
4925
- return ["-n", "-ss", String(round3(mediaTime)), "-i", src, "-frames:v", "1", output];
4921
+ return ["-y", "-ss", String(round3(mediaTime)), "-i", src, "-frames:v", "1", output];
4926
4922
  }
4927
4923
  var randomStillToken = () => randomBytes(4).toString("hex");
4928
4924
  function freezeStillFileName(clipId, playhead, token = randomStillToken()) {
@@ -5095,6 +5091,15 @@ function readExpected(absPath, expectedVersion) {
5095
5091
  }
5096
5092
  return fileContentVersion(content) === expectedVersion ? { content } : { error: "file conflict", status: 409 };
5097
5093
  }
5094
+ function claimStillNameBeforeExtracting(path) {
5095
+ try {
5096
+ closeSync3(openSync3(path, "wx"));
5097
+ return true;
5098
+ } catch (error) {
5099
+ if (error.code === "EEXIST") return false;
5100
+ throw error;
5101
+ }
5102
+ }
5098
5103
  async function extractStill(projectDir, absPath, source, playhead, tools) {
5099
5104
  const fileDir = dirname5(absPath);
5100
5105
  const mediaPath = pinWithinProject(projectDir, relative4(projectDir, join13(fileDir, source.src)));
@@ -5106,17 +5111,18 @@ async function extractStill(projectDir, absPath, source, playhead, tools) {
5106
5111
  if (!imagePath || dirname5(imagePath) !== freezeDir) {
5107
5112
  return { error: `forbidden freeze path: ${fileName}`, status: 403 };
5108
5113
  }
5109
- if (existsSync8(imagePath))
5114
+ if (!claimStillNameBeforeExtracting(imagePath))
5110
5115
  return { error: `freeze still already exists: ${fileName}`, status: 409 };
5111
5116
  const extracted = await tools.extract(freezeExtractArgs(mediaPath, source.mediaTime, imagePath));
5112
- if (!extracted.ok) {
5113
- return {
5114
- error: `Could not extract the frame: ${extracted.error ?? "ffmpeg failed"}`,
5115
- status: 500
5116
- };
5117
+ const partialOrNoFrame = !extracted.ok || statSync8(imagePath).size === 0;
5118
+ if (partialOrNoFrame) {
5119
+ rmSync5(imagePath, { force: true });
5120
+ const reason = extracted.ok ? "no frame at this time" : extracted.error ?? "ffmpeg failed";
5121
+ return { error: `Could not extract the frame: ${reason}`, status: 500 };
5117
5122
  }
5118
5123
  const depth = relative4(projectDir, fileDir).split(sep2).filter(Boolean).length;
5119
- return { imageSrc: `${"../".repeat(depth)}${FREEZE_DIR.join("/")}/${fileName}` };
5124
+ const stillPath = `${FREEZE_DIR.join("/")}/${fileName}`;
5125
+ return { imageSrc: `${"../".repeat(depth)}${stillPath}`, stillPath, imagePath };
5120
5126
  }
5121
5127
  function writeFolded(projectDir, absPath, path, before, after, options) {
5122
5128
  if (readFileSync11(absPath, "utf-8") !== before) {
@@ -5150,17 +5156,21 @@ function registerFreezeFrameRoutes(api, adapter, extract = ffmpegExtractor, stil
5150
5156
  stillToken
5151
5157
  });
5152
5158
  if ("error" in still) return c.json({ error: still.error }, still.status);
5153
- const { imageSrc } = still;
5159
+ const { imageSrc, stillPath, imagePath } = still;
5154
5160
  const folded = applyFreezeFrameToHtml(before, {
5155
5161
  target: body.target,
5156
5162
  playhead: body.playhead,
5157
5163
  imageSrc
5158
5164
  });
5159
- if (!folded) return c.json({ error: "Freeze target was not found in the file" }, 400);
5165
+ const failAndRemoveStill = (error, status) => {
5166
+ rmSync5(imagePath, { force: true });
5167
+ return c.json({ error }, status);
5168
+ };
5169
+ if (!folded) return failAndRemoveStill("Freeze target was not found in the file", 400);
5160
5170
  const written = writeFolded(project.dir, absPath, body.path, before, folded.html, {
5161
5171
  token: body.transactionToken ?? c.req.header("X-Hyperframes-Write-Token")
5162
5172
  });
5163
- if ("error" in written) return c.json({ error: written.error }, written.status);
5173
+ if ("error" in written) return failAndRemoveStill(written.error, written.status);
5164
5174
  const { version, writeToken, backupPath } = written;
5165
5175
  return c.json({
5166
5176
  ok: true,
@@ -5171,13 +5181,14 @@ function registerFreezeFrameRoutes(api, adapter, extract = ffmpegExtractor, stil
5171
5181
  writeToken,
5172
5182
  backupPath,
5173
5183
  freezeId: folded.freezeId,
5174
- imageSrc
5184
+ imageSrc,
5185
+ stillPath
5175
5186
  });
5176
5187
  });
5177
5188
  }
5178
5189
 
5179
5190
  // src/routes/loudness.ts
5180
- import { existsSync as existsSync9 } from "fs";
5191
+ import { existsSync as existsSync8 } from "fs";
5181
5192
  var defaultMeasure = (file, window) => measureAudio(requiredFfmpeg(), file, window);
5182
5193
  var InvalidField = class extends Error {
5183
5194
  };
@@ -5228,7 +5239,7 @@ function registerLoudnessRoutes(api, adapter, measure = defaultMeasure) {
5228
5239
  } catch (error) {
5229
5240
  return c.json({ error: error instanceof Error ? error.message : String(error) }, 400);
5230
5241
  }
5231
- if (!existsSync9(file)) return c.json({ error: "file not found" }, 404);
5242
+ if (!existsSync8(file)) return c.json({ error: "file not found" }, 404);
5232
5243
  try {
5233
5244
  const measurement = await measure(file, request.window);
5234
5245
  const plan = absoluteLoudnessPlan(
@@ -5243,7 +5254,7 @@ function registerLoudnessRoutes(api, adapter, measure = defaultMeasure) {
5243
5254
  }
5244
5255
 
5245
5256
  // src/routes/peaks.ts
5246
- import { existsSync as existsSync10, readFileSync as readFileSync12, statSync as statSync9 } from "fs";
5257
+ import { existsSync as existsSync9, readFileSync as readFileSync12, statSync as statSync9 } from "fs";
5247
5258
  import { join as join14, resolve as resolve6 } from "path";
5248
5259
  import { isWithinProjectRoot as isWithinProjectRoot3 } from "@hyperframes/parsers/asset-resolution";
5249
5260
 
@@ -5361,7 +5372,7 @@ function decodeOnce(cachePath, mediaPath, decode) {
5361
5372
  }
5362
5373
  function readCachedBins(cacheDir, cachePath) {
5363
5374
  try {
5364
- if (!isWaveformCacheDirectory(cacheDir) || !existsSync10(cachePath)) return null;
5375
+ if (!isWaveformCacheDirectory(cacheDir) || !existsSync9(cachePath)) return null;
5365
5376
  const parsed = JSON.parse(readFileSync12(cachePath, "utf-8"));
5366
5377
  return Array.isArray(parsed) && parsed.every((n) => typeof n === "number") ? parsed : null;
5367
5378
  } catch {
@@ -5397,7 +5408,7 @@ function registerPeakRoutes(api, adapter, decode = decodePeakMap) {
5397
5408
  }
5398
5409
 
5399
5410
  // src/routes/fonts.ts
5400
- import { closeSync as closeSync3, constants, fstatSync as fstatSync3, openSync as openSync3, readSync } from "fs";
5411
+ import { closeSync as closeSync4, constants, fstatSync as fstatSync3, openSync as openSync4, readSync } from "fs";
5401
5412
  import {
5402
5413
  collectFontFileEntries,
5403
5414
  fontDirectories,
@@ -5504,7 +5515,7 @@ function registerFontRoutes(api) {
5504
5515
  if (!located) return c.json({ error: "font not found" }, 404);
5505
5516
  let fd;
5506
5517
  try {
5507
- fd = openSync3(located.path, constants.O_RDONLY | constants.O_NOFOLLOW);
5518
+ fd = openSync4(located.path, constants.O_RDONLY | constants.O_NOFOLLOW);
5508
5519
  } catch {
5509
5520
  return c.json({ error: "font file not accessible" }, 404);
5510
5521
  }
@@ -5526,7 +5537,7 @@ function registerFontRoutes(api) {
5526
5537
  } catch {
5527
5538
  return c.json({ error: "failed to read font file" }, 500);
5528
5539
  } finally {
5529
- closeSync3(fd);
5540
+ closeSync4(fd);
5530
5541
  }
5531
5542
  });
5532
5543
  }
@@ -5662,7 +5673,7 @@ function registerSelectionRoutes(api, adapter) {
5662
5673
 
5663
5674
  // src/routes/media.ts
5664
5675
  import { streamSSE as streamSSE2 } from "hono/streaming";
5665
- import { existsSync as existsSync11 } from "fs";
5676
+ import { existsSync as existsSync10 } from "fs";
5666
5677
  import { basename as basename2, dirname as dirname6, extname, join as join15, posix } from "path";
5667
5678
  var VIDEO_EXTENSIONS = /* @__PURE__ */ new Set([
5668
5679
  ".mp4",
@@ -5700,7 +5711,7 @@ function uniqueAssetPath(projectDir, assetPath, taken) {
5700
5711
  const ext = extname(assetPath);
5701
5712
  const withoutExt = assetPath.slice(0, -ext.length);
5702
5713
  let candidate = assetPath;
5703
- for (let index = 2; taken.has(candidate) || existsSync11(join15(projectDir, candidate)); index++) {
5714
+ for (let index = 2; taken.has(candidate) || existsSync10(join15(projectDir, candidate)); index++) {
5704
5715
  candidate = `${withoutExt}-${index}${ext}`;
5705
5716
  }
5706
5717
  return candidate;
@@ -5761,7 +5772,7 @@ function registerMediaRoutes(api, adapter, options = {}) {
5761
5772
  }
5762
5773
  const filePath = resolveWithinProject(project.dir, assetPath);
5763
5774
  if (!filePath) return c.json({ error: "forbidden" }, 403);
5764
- if (!existsSync11(filePath)) return c.json({ error: "media not found" }, 404);
5775
+ if (!existsSync10(filePath)) return c.json({ error: "media not found" }, 404);
5765
5776
  return c.json({ path: assetPath, metadata: await readMediaMetadata(filePath) });
5766
5777
  });
5767
5778
  api.post(
@@ -5783,7 +5794,7 @@ function registerMediaRoutes(api, adapter, options = {}) {
5783
5794
  }
5784
5795
  const inputPath = resolveWithinProject(project.dir, inputAssetPath);
5785
5796
  if (!inputPath) return c.json({ error: "forbidden" }, 403);
5786
- if (!existsSync11(inputPath)) return c.json({ error: "input media not found" }, 404);
5797
+ if (!existsSync10(inputPath)) return c.json({ error: "input media not found" }, 404);
5787
5798
  const inputIsVideo = isVideoPath(inputAssetPath);
5788
5799
  const inputIsImage = isImagePath(inputAssetPath);
5789
5800
  if (!inputIsVideo && !inputIsImage) {
@@ -5877,12 +5888,12 @@ function registerMediaRoutes(api, adapter, options = {}) {
5877
5888
  }
5878
5889
 
5879
5890
  // src/routes/globalAssets.ts
5880
- import { existsSync as existsSync12, readFileSync as readFileSync13 } from "fs";
5891
+ import { existsSync as existsSync11, readFileSync as readFileSync13 } from "fs";
5881
5892
  import { homedir } from "os";
5882
5893
  import { join as join16 } from "path";
5883
5894
  function readGlobalAssets(home = homedir()) {
5884
5895
  const manifestPath = join16(home, ".media", "manifest.jsonl");
5885
- if (!existsSync12(manifestPath)) return [];
5896
+ if (!existsSync11(manifestPath)) return [];
5886
5897
  const out = [];
5887
5898
  for (const line of readFileSync13(manifestPath, "utf8").split("\n")) {
5888
5899
  if (!line.trim()) continue;
@@ -6136,7 +6147,7 @@ function numericAttribute(element, name) {
6136
6147
 
6137
6148
  // src/helpers/previewDocumentStore.ts
6138
6149
  import { createHash as createHash6 } from "crypto";
6139
- import { readFileSync as readFileSync14, readdirSync as readdirSync5, rmSync as rmSync5 } from "fs";
6150
+ import { readFileSync as readFileSync14, readdirSync as readdirSync5, rmSync as rmSync6 } from "fs";
6140
6151
  import { join as join17 } from "path";
6141
6152
  import { replaceFileAtomically as replaceFileAtomically4 } from "@hyperframes/core/atomic-file";
6142
6153
  function createPreviewDocumentStore(projectDir, salt) {
@@ -6157,7 +6168,7 @@ ${key}`).digest("hex").slice(0, 32)}.html`);
6157
6168
  const file = fileFor(key);
6158
6169
  replaceFileAtomically4(file, html, 420);
6159
6170
  for (const name of readdirSync5(dir)) {
6160
- if (join17(dir, name) !== file) rmSync5(join17(dir, name), { force: true });
6171
+ if (join17(dir, name) !== file) rmSync6(join17(dir, name), { force: true });
6161
6172
  }
6162
6173
  } catch {
6163
6174
  }