@hyperframes/studio-server 0.8.118 → 0.8.119

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
@@ -4891,10 +4891,14 @@ function registerWaveformRoutes(api, adapter) {
4891
4891
 
4892
4892
  // src/routes/freezeFrame.ts
4893
4893
  import { execFile } from "child_process";
4894
- import { closeSync as closeSync3, openSync as openSync3, readFileSync as readFileSync11, rmSync as rmSync5, statSync as statSync8 } from "fs";
4895
- import { dirname as dirname5, join as join13, relative as relative4, sep as sep2 } from "path";
4894
+ import { readFileSync as readFileSync11, rmSync as rmSync5, statSync as statSync8 } from "fs";
4895
+ import { basename as basename2, dirname as dirname5, join as join13, relative as relative4, sep as sep2 } from "path";
4896
4896
  import { findFfBinary as findFfBinary3 } from "@hyperframes/parsers/ff-binaries";
4897
- import { replaceFileAtomically as replaceFileAtomically3 } from "@hyperframes/core/atomic-file";
4897
+ import {
4898
+ atomicTempPath,
4899
+ createFileAtomically as createFileAtomically2,
4900
+ replaceFileAtomically as replaceFileAtomically3
4901
+ } from "@hyperframes/core/atomic-file";
4898
4902
 
4899
4903
  // src/helpers/freezeFrame.ts
4900
4904
  import { createHash as createHash5, randomBytes } from "crypto";
@@ -4919,7 +4923,22 @@ function freezeFrameMediaTime(input) {
4919
4923
  return input.mediaStart + sourceTimeAt(rate, Math.max(0, input.playhead - input.clipStart));
4920
4924
  }
4921
4925
  function freezeExtractArgs(src, mediaTime, output) {
4922
- return ["-y", "-ss", String(round3(mediaTime)), "-i", src, "-frames:v", "1", output];
4926
+ return [
4927
+ "-y",
4928
+ "-ss",
4929
+ String(round3(mediaTime)),
4930
+ "-i",
4931
+ src,
4932
+ "-frames:v",
4933
+ "1",
4934
+ "-c:v",
4935
+ "png",
4936
+ "-f",
4937
+ "image2",
4938
+ "-update",
4939
+ "1",
4940
+ output
4941
+ ];
4923
4942
  }
4924
4943
  var randomStillToken = () => randomBytes(4).toString("hex");
4925
4944
  function freezeStillFileName(clipId, playhead, token = randomStillToken()) {
@@ -5092,15 +5111,38 @@ function readExpected(absPath, expectedVersion) {
5092
5111
  }
5093
5112
  return fileContentVersion(content) === expectedVersion ? { content } : { error: "file conflict", status: 409 };
5094
5113
  }
5095
- function claimStillNameBeforeExtracting(path) {
5114
+ function readFrame(path) {
5115
+ try {
5116
+ return readFileSync11(path);
5117
+ } catch (error) {
5118
+ if (error.code === "ENOENT") return null;
5119
+ throw error;
5120
+ }
5121
+ }
5122
+ function publishStill(path, frame) {
5096
5123
  try {
5097
- closeSync3(openSync3(path, "wx"));
5124
+ createFileAtomically2(path, frame);
5098
5125
  return true;
5099
5126
  } catch (error) {
5100
5127
  if (error.code === "EEXIST") return false;
5101
5128
  throw error;
5102
5129
  }
5103
5130
  }
5131
+ async function extractThenPublishWhole(extract, mediaPath, mediaTime, imagePath) {
5132
+ const unseenByHistory = atomicTempPath(imagePath);
5133
+ try {
5134
+ const extracted = await extract(freezeExtractArgs(mediaPath, mediaTime, unseenByHistory));
5135
+ const frame = extracted.ok ? readFrame(unseenByHistory) : null;
5136
+ if (!frame?.length) {
5137
+ const reason = extracted.ok ? "no frame at this time" : extracted.error ?? "ffmpeg failed";
5138
+ return { error: `Could not extract the frame: ${reason}`, status: 500 };
5139
+ }
5140
+ if (publishStill(imagePath, frame)) return null;
5141
+ return { error: `freeze still already exists: ${basename2(imagePath)}`, status: 409 };
5142
+ } finally {
5143
+ rmSync5(unseenByHistory, { force: true });
5144
+ }
5145
+ }
5104
5146
  async function extractStill(projectDir, absPath, source, playhead, tools) {
5105
5147
  const fileDir = dirname5(absPath);
5106
5148
  const mediaPath = pinWithinProject(projectDir, relative4(projectDir, join13(fileDir, source.src)));
@@ -5112,15 +5154,13 @@ async function extractStill(projectDir, absPath, source, playhead, tools) {
5112
5154
  if (!imagePath || dirname5(imagePath) !== freezeDir) {
5113
5155
  return { error: `forbidden freeze path: ${fileName}`, status: 403 };
5114
5156
  }
5115
- if (!claimStillNameBeforeExtracting(imagePath))
5116
- return { error: `freeze still already exists: ${fileName}`, status: 409 };
5117
- const extracted = await tools.extract(freezeExtractArgs(mediaPath, source.mediaTime, imagePath));
5118
- const partialOrNoFrame = !extracted.ok || statSync8(imagePath).size === 0;
5119
- if (partialOrNoFrame) {
5120
- rmSync5(imagePath, { force: true });
5121
- const reason = extracted.ok ? "no frame at this time" : extracted.error ?? "ffmpeg failed";
5122
- return { error: `Could not extract the frame: ${reason}`, status: 500 };
5123
- }
5157
+ const failed = await extractThenPublishWhole(
5158
+ tools.extract,
5159
+ mediaPath,
5160
+ source.mediaTime,
5161
+ imagePath
5162
+ );
5163
+ if (failed) return failed;
5124
5164
  const depth = relative4(projectDir, fileDir).split(sep2).filter(Boolean).length;
5125
5165
  const stillPath = `${FREEZE_DIR.join("/")}/${fileName}`;
5126
5166
  return { imageSrc: `${"../".repeat(depth)}${stillPath}`, stillPath, imagePath };
@@ -5409,7 +5449,7 @@ function registerPeakRoutes(api, adapter, decode = decodePeakMap) {
5409
5449
  }
5410
5450
 
5411
5451
  // src/routes/fonts.ts
5412
- import { closeSync as closeSync4, constants, fstatSync as fstatSync3, openSync as openSync4, readSync } from "fs";
5452
+ import { closeSync as closeSync3, constants, fstatSync as fstatSync3, openSync as openSync3, readSync } from "fs";
5413
5453
  import {
5414
5454
  collectFontFileEntries,
5415
5455
  fontDirectories,
@@ -5516,7 +5556,7 @@ function registerFontRoutes(api) {
5516
5556
  if (!located) return c.json({ error: "font not found" }, 404);
5517
5557
  let fd;
5518
5558
  try {
5519
- fd = openSync4(located.path, constants.O_RDONLY | constants.O_NOFOLLOW);
5559
+ fd = openSync3(located.path, constants.O_RDONLY | constants.O_NOFOLLOW);
5520
5560
  } catch {
5521
5561
  return c.json({ error: "font file not accessible" }, 404);
5522
5562
  }
@@ -5538,7 +5578,7 @@ function registerFontRoutes(api) {
5538
5578
  } catch {
5539
5579
  return c.json({ error: "failed to read font file" }, 500);
5540
5580
  } finally {
5541
- closeSync4(fd);
5581
+ closeSync3(fd);
5542
5582
  }
5543
5583
  });
5544
5584
  }
@@ -5675,7 +5715,7 @@ function registerSelectionRoutes(api, adapter) {
5675
5715
  // src/routes/media.ts
5676
5716
  import { streamSSE as streamSSE2 } from "hono/streaming";
5677
5717
  import { existsSync as existsSync10 } from "fs";
5678
- import { basename as basename2, dirname as dirname6, extname, join as join15, posix } from "path";
5718
+ import { basename as basename3, dirname as dirname6, extname, join as join15, posix } from "path";
5679
5719
  var VIDEO_EXTENSIONS = /* @__PURE__ */ new Set([
5680
5720
  ".mp4",
5681
5721
  ".mov",
@@ -5705,7 +5745,7 @@ function containsNullByte(path) {
5705
5745
  return path.includes("\0");
5706
5746
  }
5707
5747
  function slugFileBase(path) {
5708
- const name = basename2(path, extname(path)).replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
5748
+ const name = basename3(path, extname(path)).replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
5709
5749
  return name || "media";
5710
5750
  }
5711
5751
  function uniqueAssetPath(projectDir, assetPath, taken) {