@hyperframes/studio-server 0.8.201 → 0.8.202

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.
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  PROXY_PARAMS_VERSION,
3
3
  resolveProxy
4
- } from "./chunk-5UEDEOD2.js";
4
+ } from "./chunk-RX6VALW3.js";
5
5
  import {
6
6
  createMediaCodecProbeCache,
7
7
  proxyVariantFor,
@@ -11,23 +11,19 @@ import {
11
11
 
12
12
  // src/helpers/proxyTranscoder.ts
13
13
  import { spawn } from "child_process";
14
- import { createHash, randomUUID } from "crypto";
14
+ import { randomUUID } from "crypto";
15
15
  import {
16
- existsSync as existsSync2,
16
+ existsSync as existsSync4,
17
17
  mkdtempSync,
18
18
  renameSync,
19
- rmSync,
20
- statSync as statSync2,
19
+ rmSync as rmSync2,
21
20
  unlinkSync as unlinkSync2,
22
21
  utimesSync,
23
- writeFileSync
22
+ writeFileSync as writeFileSync2
24
23
  } from "fs";
25
24
  import { availableParallelism, tmpdir, totalmem } from "os";
26
- import { basename, dirname, extname as extname2, isAbsolute, join as join2, relative, resolve, sep } from "path";
27
- import {
28
- formatPreviewProxyBox,
29
- hdrToSdrToneMapFilter
30
- } from "@hyperframes/core";
25
+ import { basename as basename3, dirname as dirname3, extname as extname3, isAbsolute, join as join4, relative, resolve, sep } from "path";
26
+ import { hdrToSdrToneMapFilter } from "@hyperframes/core";
31
27
  import { findFfBinary } from "@hyperframes/parsers/ff-binaries";
32
28
  import { alphaDecoderFor } from "@hyperframes/parsers/media-alpha";
33
29
 
@@ -143,6 +139,136 @@ function cleanupProxyCache(cacheDir, options = {}) {
143
139
  };
144
140
  }
145
141
 
142
+ // src/helpers/proxyCacheKey.ts
143
+ import { createHash } from "crypto";
144
+ import {
145
+ closeSync,
146
+ createReadStream,
147
+ existsSync as existsSync2,
148
+ fstatSync,
149
+ openSync,
150
+ readFileSync,
151
+ statSync as statSync2,
152
+ writeFileSync
153
+ } from "fs";
154
+ import { basename, dirname, extname as extname2, join as join2 } from "path";
155
+ import { formatPreviewProxyBox } from "@hyperframes/core";
156
+ var SMALL_FILE_BYTES = 16 * 1024 * 1024;
157
+ var COPY_NAME = /^[0-9a-f]{64}\.(?:mp4|webm)$/;
158
+ function digest(...parts) {
159
+ return createHash("sha256").update(parts.join("\0")).digest("hex");
160
+ }
161
+ function boxPart(box) {
162
+ return box ? formatPreviewProxyBox(box) : "";
163
+ }
164
+ function fastCopyKey(sourcePath, version, variant, box) {
165
+ const stat = statSync2(sourcePath);
166
+ return digest(
167
+ sourcePath,
168
+ String(stat.mtimeMs),
169
+ String(stat.size),
170
+ version,
171
+ variant,
172
+ boxPart(box)
173
+ );
174
+ }
175
+ async function contentCopyKey(sourcePath, version, variant, box, signal) {
176
+ const hash = createHash("sha256");
177
+ const fd = openSync(sourcePath, "r");
178
+ try {
179
+ if (fstatSync(fd).size <= SMALL_FILE_BYTES) hash.update(readFileSync(fd));
180
+ else
181
+ for await (const chunk of createReadStream(sourcePath, { fd, autoClose: false, signal }))
182
+ hash.update(chunk);
183
+ } finally {
184
+ closeSync(fd);
185
+ }
186
+ return digest(hash.digest("hex"), version, variant, boxPart(box));
187
+ }
188
+ function fastIndexPath(cachePath) {
189
+ return join2(dirname(cachePath), `.fast-${basename(cachePath, extname2(cachePath))}`);
190
+ }
191
+ function contentIndexPath(cacheDir, contentKey) {
192
+ return join2(cacheDir, `.content-${contentKey}`);
193
+ }
194
+ function readCopyIndex(indexPath) {
195
+ try {
196
+ const name = readFileSync(indexPath, "utf8").trim();
197
+ const target = join2(dirname(indexPath), name);
198
+ return COPY_NAME.test(name) && existsSync2(target) ? target : null;
199
+ } catch {
200
+ return null;
201
+ }
202
+ }
203
+ function writeCopyIndex(indexPath, copyPath) {
204
+ writeFileSync(indexPath, basename(copyPath));
205
+ }
206
+
207
+ // src/helpers/proxyLock.ts
208
+ import {
209
+ closeSync as closeSync2,
210
+ existsSync as existsSync3,
211
+ openSync as openSync2,
212
+ readFileSync as readFileSync2,
213
+ rmSync,
214
+ statSync as statSync3,
215
+ writeSync
216
+ } from "fs";
217
+ import { basename as basename2, dirname as dirname2, join as join3 } from "path";
218
+ var POLL_MS = 500;
219
+ var UNREADABLE_LOCK_MS = 1e4;
220
+ function lockPathFor(cachePath) {
221
+ return join3(dirname2(cachePath), `.lock-${basename2(cachePath)}`);
222
+ }
223
+ function ownerIsAlive(lockPath) {
224
+ try {
225
+ const pid = Number(readFileSync2(lockPath, "utf8"));
226
+ if (!Number.isInteger(pid) || pid <= 0) {
227
+ return Date.now() - statSync3(lockPath).mtimeMs < UNREADABLE_LOCK_MS;
228
+ }
229
+ try {
230
+ process.kill(pid, 0);
231
+ return true;
232
+ } catch (err) {
233
+ return err.code === "EPERM";
234
+ }
235
+ } catch {
236
+ return false;
237
+ }
238
+ }
239
+ function pause(ms, signal) {
240
+ return new Promise((resolve2, reject) => {
241
+ const timer = setTimeout(() => {
242
+ signal.removeEventListener("abort", onAbort);
243
+ resolve2();
244
+ }, ms);
245
+ const onAbort = () => {
246
+ clearTimeout(timer);
247
+ reject(signal.reason);
248
+ };
249
+ signal.addEventListener("abort", onAbort, { once: true });
250
+ });
251
+ }
252
+ async function acquireCopyLock(cachePath, signal) {
253
+ const lockPath = lockPathFor(cachePath);
254
+ for (; ; ) {
255
+ signal.throwIfAborted();
256
+ if (existsSync3(cachePath)) return null;
257
+ try {
258
+ const fd = openSync2(lockPath, "wx");
259
+ writeSync(fd, String(process.pid));
260
+ closeSync2(fd);
261
+ if (!existsSync3(cachePath)) return () => rmSync(lockPath, { force: true });
262
+ rmSync(lockPath, { force: true });
263
+ return null;
264
+ } catch (err) {
265
+ if (err.code !== "EEXIST") throw err;
266
+ }
267
+ if (ownerIsAlive(lockPath)) await pause(POLL_MS, signal);
268
+ else rmSync(lockPath, { force: true });
269
+ }
270
+ }
271
+
146
272
  // src/helpers/proxyTranscoder.ts
147
273
  var PROXY_PARAMS_VERSION = "v7";
148
274
  var CACHE_DIR_NAME = ".transcode-cache";
@@ -235,33 +361,16 @@ function canonicalizeProxySource(projectDir, absoluteSourcePath) {
235
361
  if (requestedRelativePath === ".." || requestedRelativePath.startsWith(`..${sep}`) || isAbsolute(requestedRelativePath)) {
236
362
  throw new ProxySourceOutsideProjectError();
237
363
  }
238
- const canonicalProjectDir = realProjectRoot(projectDir);
239
- const canonicalSourcePath = realpath(absoluteSourcePath);
240
- const canonicalRelativePath = relative(canonicalProjectDir, canonicalSourcePath);
241
- const sourceIsInsideCanonicalProject = canonicalRelativePath !== ".." && !canonicalRelativePath.startsWith(`..${sep}`) && !isAbsolute(canonicalRelativePath);
242
- return {
243
- projectDir: canonicalProjectDir,
244
- sourcePath: canonicalSourcePath,
245
- relativePath: requestedRelativePath.normalize("NFC"),
246
- // An external target needs a stable identity in addition to its project-local
247
- // symlink path, otherwise retargeting the link can reuse an unrelated proxy.
248
- cacheIdentity: (sourceIsInsideCanonicalProject ? canonicalRelativePath : canonicalSourcePath).normalize("NFC")
249
- };
250
- }
251
- function buildProxyCacheKey(source, variant, box) {
252
- const stat = statSync2(source.sourcePath);
253
- const size = box ? `\0${formatPreviewProxyBox(box)}` : "";
254
- return createHash("sha256").update(
255
- `${source.relativePath}\0${source.cacheIdentity}\0${stat.mtimeMs}\0${stat.size}\0${PROXY_PARAMS_VERSION}\0${variant}${size}`
256
- ).digest("hex");
364
+ return { projectDir: realProjectRoot(projectDir), sourcePath: realpath(absoluteSourcePath) };
257
365
  }
258
366
  function getCanonicalProxyCachePath(source, variant, box) {
259
- const key = buildProxyCacheKey(source, variant, box);
260
- return join2(
367
+ const key = fastCopyKey(source.sourcePath, PROXY_PARAMS_VERSION, variant, box);
368
+ const made = join4(
261
369
  source.projectDir,
262
370
  CACHE_DIR_NAME,
263
371
  `${key}${PROXY_VARIANT_CONFIG[variant].extension}`
264
372
  );
373
+ return existsSync4(made) ? made : readCopyIndex(fastIndexPath(made)) ?? made;
265
374
  }
266
375
  function getProxyCachePath(projectDir, absoluteSourcePath, variant = "h264", box) {
267
376
  return getCanonicalProxyCachePath(
@@ -456,9 +565,9 @@ function createPieceGate(size) {
456
565
  }
457
566
  async function encodeInChunks(ranges, chunkPrefix, tempPath, encode, concat, signal, gate, onDone = () => {
458
567
  }) {
459
- const extension = extname2(tempPath);
568
+ const extension = extname3(tempPath);
460
569
  const pieces = ranges.map((_, index) => `${chunkPrefix}${index}${extension}`);
461
- const pending = ranges.flatMap((_, index) => existsSync2(pieces[index]) ? [] : [index]);
570
+ const pending = ranges.flatMap((_, index) => existsSync4(pieces[index]) ? [] : [index]);
462
571
  let doneSeconds = 0;
463
572
  for (const [index, range] of ranges.entries()) {
464
573
  if (!pending.includes(index)) doneSeconds += range.lengthSeconds;
@@ -477,7 +586,7 @@ async function encodeInChunks(ranges, chunkPrefix, tempPath, encode, concat, sig
477
586
  const index = pending[cursor++];
478
587
  const range = ranges[index];
479
588
  const piece = pieces[index];
480
- const pieceTemp = join2(dirname(piece), `.tmp-${randomUUID()}-${basename(piece)}`);
589
+ const pieceTemp = join4(dirname3(piece), `.tmp-${randomUUID()}-${basename3(piece)}`);
481
590
  try {
482
591
  await encode(pieceTemp, range, pieceSignal, (encodedSeconds) => {
483
592
  inFlight2.set(index, encodedSeconds);
@@ -488,7 +597,7 @@ async function encodeInChunks(ranges, chunkPrefix, tempPath, encode, concat, sig
488
597
  doneSeconds += range.lengthSeconds;
489
598
  onDone(doneSeconds);
490
599
  } finally {
491
- if (existsSync2(pieceTemp)) unlinkSync2(pieceTemp);
600
+ if (existsSync4(pieceTemp)) unlinkSync2(pieceTemp);
492
601
  }
493
602
  } finally {
494
603
  gate.release();
@@ -505,19 +614,19 @@ async function encodeInChunks(ranges, chunkPrefix, tempPath, encode, concat, sig
505
614
  )
506
615
  );
507
616
  if (failure !== void 0) throw failure;
508
- const listDir = mkdtempSync(join2(tmpdir(), "hf-proxy-join-"));
617
+ const listDir = mkdtempSync(join4(tmpdir(), "hf-proxy-join-"));
509
618
  try {
510
- const listPath = join2(listDir, "pieces.txt");
511
- writeFileSync(
619
+ const listPath = join4(listDir, "pieces.txt");
620
+ writeFileSync2(
512
621
  listPath,
513
622
  pieces.map((piece) => `file '${piece.replace(/\\/g, "/").replace(/'/g, "'\\''")}'
514
623
  `).join("")
515
624
  );
516
625
  await concat(listPath, tempPath);
517
626
  } finally {
518
- rmSync(listDir, { recursive: true, force: true });
627
+ rmSync2(listDir, { recursive: true, force: true });
519
628
  }
520
- for (const piece of pieces) if (existsSync2(piece)) unlinkSync2(piece);
629
+ for (const piece of pieces) if (existsSync4(piece)) unlinkSync2(piece);
521
630
  }
522
631
  var KEYFRAME_EVERY_SECOND_ARGS = ["-force_key_frames", "expr:gte(t,n_forced*1)"];
523
632
  var H264_ARGS = [
@@ -737,14 +846,31 @@ function spawnFfmpeg(ffmpegPath, args, stallMs, signal, onProgress) {
737
846
  async function transcodeToCache(job, projectDir, absoluteSourcePath, cachePath, variant, box) {
738
847
  await acquireSlot(job);
739
848
  try {
740
- if (existsSync2(cachePath)) return cachePath;
741
- const cacheDir = dirname(cachePath);
849
+ if (existsSync4(cachePath)) return cachePath;
850
+ const cacheDir = dirname3(cachePath);
742
851
  mkdirWithinProject(projectDir, cacheDir);
743
- const tempPath = join2(cacheDir, `.tmp-${randomUUID()}-${basename(cachePath)}`);
852
+ const contentIndex = contentIndexPath(
853
+ cacheDir,
854
+ await contentCopyKey(
855
+ absoluteSourcePath,
856
+ PROXY_PARAMS_VERSION,
857
+ variant,
858
+ box,
859
+ job.abandoned.signal
860
+ )
861
+ );
862
+ const sameContent = readCopyIndex(contentIndex);
863
+ if (sameContent) {
864
+ writeCopyIndex(fastIndexPath(cachePath), sameContent);
865
+ return sameContent;
866
+ }
867
+ const release = await acquireCopyLock(cachePath, job.abandoned.signal);
868
+ if (!release) return cachePath;
869
+ const tempPath = join4(cacheDir, `.tmp-${randomUUID()}-${basename3(cachePath)}`);
744
870
  try {
745
- const chunkPrefix = join2(
871
+ const chunkPrefix = join4(
746
872
  cacheDir,
747
- `${CHUNK_PREFIX}${basename(cachePath, extname2(cachePath))}-`
873
+ `${CHUNK_PREFIX}${basename3(cachePath, extname3(cachePath))}-`
748
874
  );
749
875
  await runFfmpeg(
750
876
  absoluteSourcePath,
@@ -756,10 +882,12 @@ async function transcodeToCache(job, projectDir, absoluteSourcePath, cachePath,
756
882
  (progress) => reportProgress(job, progress)
757
883
  );
758
884
  renameSync(tempPath, cachePath);
885
+ writeCopyIndex(contentIndex, cachePath);
759
886
  maintainProxyCache(cacheDir);
760
887
  return cachePath;
761
888
  } finally {
762
- if (existsSync2(tempPath)) unlinkSync2(tempPath);
889
+ if (existsSync4(tempPath)) unlinkSync2(tempPath);
890
+ release();
763
891
  }
764
892
  } finally {
765
893
  releaseSlot();
@@ -767,8 +895,8 @@ async function transcodeToCache(job, projectDir, absoluteSourcePath, cachePath,
767
895
  }
768
896
  var proxyActivity = 0;
769
897
  function proxyActivityMark(projectDir) {
770
- if (!existsSync2(projectDir)) return String(proxyActivity);
771
- const cacheDir = join2(realpath(projectDir), CACHE_DIR_NAME) + sep;
898
+ if (!existsSync4(projectDir)) return String(proxyActivity);
899
+ const cacheDir = join4(realpath(projectDir), CACHE_DIR_NAME) + sep;
772
900
  for (const cachePath of inFlight.keys()) if (cachePath.startsWith(cacheDir)) return null;
773
901
  return String(proxyActivity);
774
902
  }
@@ -776,9 +904,9 @@ async function resolveProxy(projectDir, absoluteSourcePath, variant = "h264", bo
776
904
  options.signal?.throwIfAborted();
777
905
  const source = canonicalizeProxySource(projectDir, absoluteSourcePath);
778
906
  const cachePath = getCanonicalProxyCachePath(source, variant, box);
779
- if (existsSync2(cachePath)) {
907
+ if (existsSync4(cachePath)) {
780
908
  markCacheEntryUsed(cachePath);
781
- maintainProxyCache(dirname(cachePath));
909
+ maintainProxyCache(dirname3(cachePath));
782
910
  return cachePath;
783
911
  }
784
912
  const rememberedFailure = failedTranscodes.get(cachePath);
@@ -835,7 +963,7 @@ async function resolveProxies(projectDir, sources, onSettled = () => {
835
963
  const first = sources[0];
836
964
  if (first) {
837
965
  maintainProxyCache(
838
- dirname(getProxyCachePath(projectDir, first.sourcePath, first.variant)),
966
+ dirname3(getProxyCachePath(projectDir, first.sourcePath, first.variant)),
839
967
  Math.min(
840
968
  Number(process.env.HYPERFRAMES_PROXY_CACHE_STALE_TEMP_MS) || UNTOUCHED_TEMP_IS_DEAD_MS,
841
969
  UNTOUCHED_TEMP_IS_DEAD_MS
@@ -4,8 +4,8 @@ import {
4
4
  isAutoProxyEnabled,
5
5
  proxyEtagSalt,
6
6
  resolvePreviewMediaCodecProbeCache
7
- } from "../chunk-HZCA2O76.js";
8
- import "../chunk-5UEDEOD2.js";
7
+ } from "../chunk-2MJKVMZU.js";
8
+ import "../chunk-RX6VALW3.js";
9
9
  import "../chunk-C2RFW2FX.js";
10
10
  import "../chunk-QF7MFJJU.js";
11
11
  import "../chunk-SVQFE44C.js";
@@ -17,7 +17,7 @@ import {
17
17
  resolveProxies,
18
18
  resolveProxy,
19
19
  waitForProxy
20
- } from "../chunk-5UEDEOD2.js";
20
+ } from "../chunk-RX6VALW3.js";
21
21
  import "../chunk-C2RFW2FX.js";
22
22
  import "../chunk-QF7MFJJU.js";
23
23
  import "../chunk-SVQFE44C.js";
package/dist/index.js CHANGED
@@ -82,7 +82,7 @@ import {
82
82
  isAutoProxyEnabled,
83
83
  proxyEtagSalt,
84
84
  resolvePreviewMediaCodecProbeCache
85
- } from "./chunk-HZCA2O76.js";
85
+ } from "./chunk-2MJKVMZU.js";
86
86
  import {
87
87
  CACHE_DIR_NAME,
88
88
  PROXY_PENDING_RETRY_AFTER_SECONDS,
@@ -93,7 +93,7 @@ import {
93
93
  proxyActivityMark,
94
94
  resolveProxy,
95
95
  waitForProxy
96
- } from "./chunk-5UEDEOD2.js";
96
+ } from "./chunk-RX6VALW3.js";
97
97
  import {
98
98
  PROXY_VARIANT_CONFIG,
99
99
  decideMediaProxyEligibility,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperframes/studio-server",
3
- "version": "0.8.201",
3
+ "version": "0.8.202",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/heygen-com/hyperframes",
@@ -84,8 +84,8 @@
84
84
  "linkedom": "^0.18.12",
85
85
  "postcss": "^8.5.8",
86
86
  "postcss-selector-parser": "^7.1.2",
87
- "@hyperframes/core": "0.8.201",
88
- "@hyperframes/parsers": "0.8.201"
87
+ "@hyperframes/core": "0.8.202",
88
+ "@hyperframes/parsers": "0.8.202"
89
89
  },
90
90
  "devDependencies": {
91
91
  "@types/node": "^25.0.10",