@hyperframes/studio-server 0.8.116 → 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
@@ -4890,7 +4890,7 @@ function registerWaveformRoutes(api, adapter) {
4890
4890
 
4891
4891
  // src/routes/freezeFrame.ts
4892
4892
  import { execFile } from "child_process";
4893
- 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";
4894
4894
  import { dirname as dirname5, join as join13, relative as relative4, sep as sep2 } from "path";
4895
4895
  import { findFfBinary as findFfBinary3 } from "@hyperframes/parsers/ff-binaries";
4896
4896
  import { replaceFileAtomically as replaceFileAtomically3 } from "@hyperframes/core/atomic-file";
@@ -4918,7 +4918,7 @@ function freezeFrameMediaTime(input) {
4918
4918
  return input.mediaStart + sourceTimeAt(rate, Math.max(0, input.playhead - input.clipStart));
4919
4919
  }
4920
4920
  function freezeExtractArgs(src, mediaTime, output) {
4921
- 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];
4922
4922
  }
4923
4923
  var randomStillToken = () => randomBytes(4).toString("hex");
4924
4924
  function freezeStillFileName(clipId, playhead, token = randomStillToken()) {
@@ -5091,6 +5091,15 @@ function readExpected(absPath, expectedVersion) {
5091
5091
  }
5092
5092
  return fileContentVersion(content) === expectedVersion ? { content } : { error: "file conflict", status: 409 };
5093
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
+ }
5094
5103
  async function extractStill(projectDir, absPath, source, playhead, tools) {
5095
5104
  const fileDir = dirname5(absPath);
5096
5105
  const mediaPath = pinWithinProject(projectDir, relative4(projectDir, join13(fileDir, source.src)));
@@ -5102,17 +5111,18 @@ async function extractStill(projectDir, absPath, source, playhead, tools) {
5102
5111
  if (!imagePath || dirname5(imagePath) !== freezeDir) {
5103
5112
  return { error: `forbidden freeze path: ${fileName}`, status: 403 };
5104
5113
  }
5105
- if (existsSync8(imagePath))
5114
+ if (!claimStillNameBeforeExtracting(imagePath))
5106
5115
  return { error: `freeze still already exists: ${fileName}`, status: 409 };
5107
5116
  const extracted = await tools.extract(freezeExtractArgs(mediaPath, source.mediaTime, imagePath));
5108
- if (!extracted.ok) {
5109
- return {
5110
- error: `Could not extract the frame: ${extracted.error ?? "ffmpeg failed"}`,
5111
- status: 500
5112
- };
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 };
5113
5122
  }
5114
5123
  const depth = relative4(projectDir, fileDir).split(sep2).filter(Boolean).length;
5115
- return { imageSrc: `${"../".repeat(depth)}${FREEZE_DIR.join("/")}/${fileName}` };
5124
+ const stillPath = `${FREEZE_DIR.join("/")}/${fileName}`;
5125
+ return { imageSrc: `${"../".repeat(depth)}${stillPath}`, stillPath, imagePath };
5116
5126
  }
5117
5127
  function writeFolded(projectDir, absPath, path, before, after, options) {
5118
5128
  if (readFileSync11(absPath, "utf-8") !== before) {
@@ -5146,17 +5156,21 @@ function registerFreezeFrameRoutes(api, adapter, extract = ffmpegExtractor, stil
5146
5156
  stillToken
5147
5157
  });
5148
5158
  if ("error" in still) return c.json({ error: still.error }, still.status);
5149
- const { imageSrc } = still;
5159
+ const { imageSrc, stillPath, imagePath } = still;
5150
5160
  const folded = applyFreezeFrameToHtml(before, {
5151
5161
  target: body.target,
5152
5162
  playhead: body.playhead,
5153
5163
  imageSrc
5154
5164
  });
5155
- 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);
5156
5170
  const written = writeFolded(project.dir, absPath, body.path, before, folded.html, {
5157
5171
  token: body.transactionToken ?? c.req.header("X-Hyperframes-Write-Token")
5158
5172
  });
5159
- if ("error" in written) return c.json({ error: written.error }, written.status);
5173
+ if ("error" in written) return failAndRemoveStill(written.error, written.status);
5160
5174
  const { version, writeToken, backupPath } = written;
5161
5175
  return c.json({
5162
5176
  ok: true,
@@ -5167,13 +5181,14 @@ function registerFreezeFrameRoutes(api, adapter, extract = ffmpegExtractor, stil
5167
5181
  writeToken,
5168
5182
  backupPath,
5169
5183
  freezeId: folded.freezeId,
5170
- imageSrc
5184
+ imageSrc,
5185
+ stillPath
5171
5186
  });
5172
5187
  });
5173
5188
  }
5174
5189
 
5175
5190
  // src/routes/loudness.ts
5176
- import { existsSync as existsSync9 } from "fs";
5191
+ import { existsSync as existsSync8 } from "fs";
5177
5192
  var defaultMeasure = (file, window) => measureAudio(requiredFfmpeg(), file, window);
5178
5193
  var InvalidField = class extends Error {
5179
5194
  };
@@ -5224,7 +5239,7 @@ function registerLoudnessRoutes(api, adapter, measure = defaultMeasure) {
5224
5239
  } catch (error) {
5225
5240
  return c.json({ error: error instanceof Error ? error.message : String(error) }, 400);
5226
5241
  }
5227
- if (!existsSync9(file)) return c.json({ error: "file not found" }, 404);
5242
+ if (!existsSync8(file)) return c.json({ error: "file not found" }, 404);
5228
5243
  try {
5229
5244
  const measurement = await measure(file, request.window);
5230
5245
  const plan = absoluteLoudnessPlan(
@@ -5239,7 +5254,7 @@ function registerLoudnessRoutes(api, adapter, measure = defaultMeasure) {
5239
5254
  }
5240
5255
 
5241
5256
  // src/routes/peaks.ts
5242
- 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";
5243
5258
  import { join as join14, resolve as resolve6 } from "path";
5244
5259
  import { isWithinProjectRoot as isWithinProjectRoot3 } from "@hyperframes/parsers/asset-resolution";
5245
5260
 
@@ -5357,7 +5372,7 @@ function decodeOnce(cachePath, mediaPath, decode) {
5357
5372
  }
5358
5373
  function readCachedBins(cacheDir, cachePath) {
5359
5374
  try {
5360
- if (!isWaveformCacheDirectory(cacheDir) || !existsSync10(cachePath)) return null;
5375
+ if (!isWaveformCacheDirectory(cacheDir) || !existsSync9(cachePath)) return null;
5361
5376
  const parsed = JSON.parse(readFileSync12(cachePath, "utf-8"));
5362
5377
  return Array.isArray(parsed) && parsed.every((n) => typeof n === "number") ? parsed : null;
5363
5378
  } catch {
@@ -5393,7 +5408,7 @@ function registerPeakRoutes(api, adapter, decode = decodePeakMap) {
5393
5408
  }
5394
5409
 
5395
5410
  // src/routes/fonts.ts
5396
- 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";
5397
5412
  import {
5398
5413
  collectFontFileEntries,
5399
5414
  fontDirectories,
@@ -5500,7 +5515,7 @@ function registerFontRoutes(api) {
5500
5515
  if (!located) return c.json({ error: "font not found" }, 404);
5501
5516
  let fd;
5502
5517
  try {
5503
- fd = openSync3(located.path, constants.O_RDONLY | constants.O_NOFOLLOW);
5518
+ fd = openSync4(located.path, constants.O_RDONLY | constants.O_NOFOLLOW);
5504
5519
  } catch {
5505
5520
  return c.json({ error: "font file not accessible" }, 404);
5506
5521
  }
@@ -5522,7 +5537,7 @@ function registerFontRoutes(api) {
5522
5537
  } catch {
5523
5538
  return c.json({ error: "failed to read font file" }, 500);
5524
5539
  } finally {
5525
- closeSync3(fd);
5540
+ closeSync4(fd);
5526
5541
  }
5527
5542
  });
5528
5543
  }
@@ -5658,7 +5673,7 @@ function registerSelectionRoutes(api, adapter) {
5658
5673
 
5659
5674
  // src/routes/media.ts
5660
5675
  import { streamSSE as streamSSE2 } from "hono/streaming";
5661
- import { existsSync as existsSync11 } from "fs";
5676
+ import { existsSync as existsSync10 } from "fs";
5662
5677
  import { basename as basename2, dirname as dirname6, extname, join as join15, posix } from "path";
5663
5678
  var VIDEO_EXTENSIONS = /* @__PURE__ */ new Set([
5664
5679
  ".mp4",
@@ -5696,7 +5711,7 @@ function uniqueAssetPath(projectDir, assetPath, taken) {
5696
5711
  const ext = extname(assetPath);
5697
5712
  const withoutExt = assetPath.slice(0, -ext.length);
5698
5713
  let candidate = assetPath;
5699
- 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++) {
5700
5715
  candidate = `${withoutExt}-${index}${ext}`;
5701
5716
  }
5702
5717
  return candidate;
@@ -5757,7 +5772,7 @@ function registerMediaRoutes(api, adapter, options = {}) {
5757
5772
  }
5758
5773
  const filePath = resolveWithinProject(project.dir, assetPath);
5759
5774
  if (!filePath) return c.json({ error: "forbidden" }, 403);
5760
- if (!existsSync11(filePath)) return c.json({ error: "media not found" }, 404);
5775
+ if (!existsSync10(filePath)) return c.json({ error: "media not found" }, 404);
5761
5776
  return c.json({ path: assetPath, metadata: await readMediaMetadata(filePath) });
5762
5777
  });
5763
5778
  api.post(
@@ -5779,7 +5794,7 @@ function registerMediaRoutes(api, adapter, options = {}) {
5779
5794
  }
5780
5795
  const inputPath = resolveWithinProject(project.dir, inputAssetPath);
5781
5796
  if (!inputPath) return c.json({ error: "forbidden" }, 403);
5782
- 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);
5783
5798
  const inputIsVideo = isVideoPath(inputAssetPath);
5784
5799
  const inputIsImage = isImagePath(inputAssetPath);
5785
5800
  if (!inputIsVideo && !inputIsImage) {
@@ -5873,12 +5888,12 @@ function registerMediaRoutes(api, adapter, options = {}) {
5873
5888
  }
5874
5889
 
5875
5890
  // src/routes/globalAssets.ts
5876
- import { existsSync as existsSync12, readFileSync as readFileSync13 } from "fs";
5891
+ import { existsSync as existsSync11, readFileSync as readFileSync13 } from "fs";
5877
5892
  import { homedir } from "os";
5878
5893
  import { join as join16 } from "path";
5879
5894
  function readGlobalAssets(home = homedir()) {
5880
5895
  const manifestPath = join16(home, ".media", "manifest.jsonl");
5881
- if (!existsSync12(manifestPath)) return [];
5896
+ if (!existsSync11(manifestPath)) return [];
5882
5897
  const out = [];
5883
5898
  for (const line of readFileSync13(manifestPath, "utf8").split("\n")) {
5884
5899
  if (!line.trim()) continue;
@@ -6132,7 +6147,7 @@ function numericAttribute(element, name) {
6132
6147
 
6133
6148
  // src/helpers/previewDocumentStore.ts
6134
6149
  import { createHash as createHash6 } from "crypto";
6135
- 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";
6136
6151
  import { join as join17 } from "path";
6137
6152
  import { replaceFileAtomically as replaceFileAtomically4 } from "@hyperframes/core/atomic-file";
6138
6153
  function createPreviewDocumentStore(projectDir, salt) {
@@ -6153,7 +6168,7 @@ ${key}`).digest("hex").slice(0, 32)}.html`);
6153
6168
  const file = fileFor(key);
6154
6169
  replaceFileAtomically4(file, html, 420);
6155
6170
  for (const name of readdirSync5(dir)) {
6156
- if (join17(dir, name) !== file) rmSync5(join17(dir, name), { force: true });
6171
+ if (join17(dir, name) !== file) rmSync6(join17(dir, name), { force: true });
6157
6172
  }
6158
6173
  } catch {
6159
6174
  }