@hyperframes/studio-server 0.8.181 → 0.8.184

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.
@@ -2,7 +2,7 @@ import {
2
2
  PROXY_VARIANT_CONFIG,
3
3
  probeFirstFrameColour,
4
4
  probeMediaMetadata
5
- } from "./chunk-E3VGIAMJ.js";
5
+ } from "./chunk-OVOXGASQ.js";
6
6
  import {
7
7
  mkdirWithinProject,
8
8
  realProjectRoot,
@@ -19,6 +19,7 @@ import {
19
19
  hdrToSdrToneMapFilter
20
20
  } from "@hyperframes/core";
21
21
  import { findFfBinary } from "@hyperframes/parsers/ff-binaries";
22
+ import { alphaDecoderFor } from "@hyperframes/parsers/media-alpha";
22
23
 
23
24
  // src/helpers/proxyCache.ts
24
25
  import { existsSync, readdirSync, statSync, unlinkSync } from "fs";
@@ -390,7 +391,9 @@ async function runFfmpeg(sourcePath, outputPath, variant, box, signal) {
390
391
  `format=${pixelFormat}`
391
392
  ].join(",") : [evenScale, `format=${pixelFormat}`].join(",");
392
393
  return new Promise((resolvePromise, reject) => {
393
- const commonArgs = ["-y", "-i", sourcePath, "-vf", videoFilter];
394
+ const alphaDecoder = keepsAlpha ? alphaDecoderFor(metadata.color.codecName) : null;
395
+ const decoderArgs = alphaDecoder ? ["-c:v", alphaDecoder] : [];
396
+ const commonArgs = ["-y", ...decoderArgs, "-i", sourcePath, "-vf", videoFilter];
394
397
  const h264Args = [
395
398
  "-c:v",
396
399
  "libx264",
@@ -555,7 +558,8 @@ async function resolveProxy(projectDir, absoluteSourcePath, variant = "h264", bo
555
558
  if (options.priority) prioritize(entry.job);
556
559
  return joinJob(entry.promise, entry.job, options.signal);
557
560
  }
558
- async function resolveProxies(projectDir, sources) {
561
+ async function resolveProxies(projectDir, sources, onSettled = () => {
562
+ }) {
559
563
  const results = [];
560
564
  let next = 0;
561
565
  const worker = async () => {
@@ -568,6 +572,7 @@ async function resolveProxies(projectDir, sources) {
568
572
  (value) => ({ status: "fulfilled", value }),
569
573
  (reason) => ({ status: "rejected", reason })
570
574
  );
575
+ onSettled();
571
576
  }
572
577
  };
573
578
  const workers = Math.min(MAX_CONCURRENT_TRANSCODES, sources.length);
@@ -22,6 +22,7 @@ import {
22
22
  parseFirstFrameColour
23
23
  } from "@hyperframes/core";
24
24
  import { findFfBinary } from "@hyperframes/parsers/ff-binaries";
25
+ import { videoStreamHasAlpha } from "@hyperframes/parsers/media-alpha";
25
26
  var execFileRunner = (command, args, options) => new Promise((resolvePromise) => {
26
27
  execFile(
27
28
  command,
@@ -76,10 +77,6 @@ function colorLabel(input) {
76
77
  }
77
78
  return "SDR/unknown";
78
79
  }
79
- var ALPHA_PIX_FMT_RE = /^(?:yuva|rgba|argb|bgra|abgr|gbrap|ya)/;
80
- function pixelFormatHasAlpha(pixFmt) {
81
- return pixFmt !== void 0 && ALPHA_PIX_FMT_RE.test(pixFmt.toLowerCase());
82
- }
83
80
  function classifyMediaColor(stream) {
84
81
  const colorPrimaries = lower(stream?.color_primaries);
85
82
  const colorSpace = lower(stream?.color_space);
@@ -124,7 +121,7 @@ async function probeMediaMetadata(filePath, runner = execFileRunner) {
124
121
  "-v",
125
122
  "error",
126
123
  "-show_entries",
127
- "stream=codec_type,codec_name,profile,pix_fmt,color_space,color_transfer,color_primaries,bits_per_raw_sample:stream_disposition=attached_pic",
124
+ "stream=codec_type,codec_name,profile,pix_fmt,color_space,color_transfer,color_primaries,bits_per_raw_sample:stream_disposition=attached_pic:stream_tags=alpha_mode",
128
125
  "-of",
129
126
  "json",
130
127
  "--",
@@ -147,6 +144,7 @@ async function probeMediaMetadata(filePath, runner = execFileRunner) {
147
144
  const metadata = { kind, color: classifyMediaColor(stream) };
148
145
  if (kind === "video") {
149
146
  metadata.hasAudio = (parsed.streams ?? []).some((item) => item.codec_type === "audio");
147
+ metadata.hasAlpha = stream !== void 0 && videoStreamHasAlpha(stream);
150
148
  }
151
149
  return metadata;
152
150
  } catch {
@@ -239,7 +237,7 @@ async function probeCodecFacts(filePath, runner) {
239
237
  if (metadata.kind !== "video") return null;
240
238
  const codecName = metadata.color.codecName;
241
239
  if (!codecName) return null;
242
- return codecFactsFor(codecName, pixelFormatHasAlpha(metadata.color.pixelFormat));
240
+ return codecFactsFor(codecName, metadata.hasAlpha === true);
243
241
  }
244
242
  function createMediaCodecProbeCache() {
245
243
  return /* @__PURE__ */ new Map();
@@ -1,14 +1,14 @@
1
1
  import {
2
2
  PROXY_PARAMS_VERSION,
3
3
  resolveProxy
4
- } from "./chunk-QFJR3NIL.js";
4
+ } from "./chunk-25IT6FMR.js";
5
5
  import {
6
6
  createMediaCodecProbeCache,
7
7
  proxyVariantFor,
8
8
  recordProxyPrewarm,
9
9
  scanProjectMediaCodecMap,
10
10
  shouldPrewarmProxy
11
- } from "./chunk-E3VGIAMJ.js";
11
+ } from "./chunk-OVOXGASQ.js";
12
12
 
13
13
  // src/helpers/mediaProxyPreview.ts
14
14
  import { resolve } from "path";
@@ -119,6 +119,10 @@ var HistoryBusyError = class extends Error {
119
119
  pid;
120
120
  url;
121
121
  };
122
+ var WINDOWS_BUSY = /* @__PURE__ */ new Set(["EPERM", "EBUSY", "EACCES"]);
123
+ function busyOnWindows(error) {
124
+ return process.platform === "win32" && WINDOWS_BUSY.has(error.code ?? "");
125
+ }
122
126
  function alive(pid) {
123
127
  if (!Number.isInteger(pid) || pid <= 0) return false;
124
128
  try {
@@ -183,27 +187,36 @@ function servedBy(file, urlFile, pid) {
183
187
  return void 0;
184
188
  }
185
189
  }
190
+ function attemptClaim(file, urlFile, deadline) {
191
+ if (claim(file)) return "claimed";
192
+ const pid = ownerOf(file);
193
+ if (pid === null) return "retry";
194
+ if (!alive(pid) && evictDeadOwner(file)) return "retry";
195
+ if (Date.now() >= deadline) throw new HistoryBusyError(pid, servedBy(file, urlFile, pid));
196
+ return "wait";
197
+ }
186
198
  async function takeHistoryOwnership(home, waitMs, url) {
187
199
  const file = join(home, "owner.pid");
188
200
  const urlFile = join(home, "owner-url.json");
189
201
  const deadline = Date.now() + waitMs;
190
202
  mkdirSync(home, { recursive: true });
191
203
  for (; ; ) {
192
- if (claim(file)) {
193
- advertise(urlFile, url);
194
- let held = true;
195
- return () => {
196
- if (held && servedBy(file, urlFile, process.pid)) rmSync(urlFile, { force: true });
197
- if (held) releaseOwn(file);
198
- held = false;
199
- };
204
+ let next = "wait";
205
+ try {
206
+ next = attemptClaim(file, urlFile, deadline);
207
+ } catch (error) {
208
+ if (!busyOnWindows(error) || Date.now() >= deadline) throw error;
200
209
  }
201
- const pid = ownerOf(file);
202
- if (pid === null) continue;
203
- if (!alive(pid) && evictDeadOwner(file)) continue;
204
- if (Date.now() >= deadline) throw new HistoryBusyError(pid, servedBy(file, urlFile, pid));
205
- await new Promise((settle) => setTimeout(settle, 50));
206
- }
210
+ if (next === "claimed") break;
211
+ if (next === "wait") await new Promise((settle) => setTimeout(settle, 50));
212
+ }
213
+ advertise(urlFile, url);
214
+ let held = true;
215
+ return () => {
216
+ if (held && servedBy(file, urlFile, process.pid)) rmSync(urlFile, { force: true });
217
+ if (held) releaseOwn(file);
218
+ held = false;
219
+ };
207
220
  }
208
221
 
209
222
  // src/helpers/renderOutputs.ts
@@ -28,7 +28,7 @@ interface AssetCodecFacts {
28
28
  * when no representative mime exists (ProRes: browsers never decode it, so
29
29
  * the runtime always proxies rather than probing `canPlayType`). */
30
30
  representativeMime: string | null;
31
- /** Source carries an alpha channel (ffprobe pix_fmt). Alpha sources use a
31
+ /** Source carries an alpha channel (`MediaMetadata.hasAlpha`). Alpha sources use a
32
32
  * VP8/WebM proxy so their transparency is preserved across Chromium builds. */
33
33
  hasAlpha: boolean;
34
34
  }
@@ -13,7 +13,7 @@ import {
13
13
  resolveProxyVariantRequest,
14
14
  scanProjectMediaCodecMap,
15
15
  shouldPrewarmProxy
16
- } from "../chunk-E3VGIAMJ.js";
16
+ } from "../chunk-OVOXGASQ.js";
17
17
  import "../chunk-QF7MFJJU.js";
18
18
  import "../chunk-SVQFE44C.js";
19
19
  import "../chunk-L3HJVTQE.js";
@@ -1,6 +1,7 @@
1
1
  import '@hyperframes/core';
2
- export { a as PreviewApiAdapter, P as PreviewDocumentStore, i as injectMediaCodecMap, f as injectMediaCodecMapIntoHtml, g as isAutoProxyEnabled, p as proxyEtagSalt, r as resolvePreviewMediaCodecProbeCache } from '../mediaProxyPreview-DIAid9-k.js';
2
+ export { a as PreviewApiAdapter, P as PreviewDocumentStore, i as injectMediaCodecMap, f as injectMediaCodecMapIntoHtml, g as isAutoProxyEnabled, p as proxyEtagSalt, r as resolvePreviewMediaCodecProbeCache } from '../mediaProxyPreview-GMF-_zBh.js';
3
3
  import './mediaCodecMap.js';
4
+ import 'sharp';
4
5
  import '@hyperframes/parsers';
5
6
  import '@hyperframes/core/compiler';
6
7
  import '../projectHistory-ByC9sa3X.js';
@@ -4,9 +4,9 @@ import {
4
4
  isAutoProxyEnabled,
5
5
  proxyEtagSalt,
6
6
  resolvePreviewMediaCodecProbeCache
7
- } from "../chunk-7L3DUCM3.js";
8
- import "../chunk-QFJR3NIL.js";
9
- import "../chunk-E3VGIAMJ.js";
7
+ } from "../chunk-T5CGF7VZ.js";
8
+ import "../chunk-25IT6FMR.js";
9
+ import "../chunk-OVOXGASQ.js";
10
10
  import "../chunk-QF7MFJJU.js";
11
11
  import "../chunk-SVQFE44C.js";
12
12
  import "../chunk-L3HJVTQE.js";
@@ -71,7 +71,7 @@ declare function resolveProxy(projectDir: string, absoluteSourcePath: string, va
71
71
  declare function resolveProxies(projectDir: string, sources: ReadonlyArray<{
72
72
  sourcePath: string;
73
73
  variant: ProxyVariant;
74
- }>): Promise<PromiseSettledResult<string>[]>;
74
+ }>, onSettled?: () => void): Promise<PromiseSettledResult<string>[]>;
75
75
  interface ResolveProxyOptions {
76
76
  priority?: boolean;
77
77
  signal?: AbortSignal;
@@ -16,8 +16,8 @@ import {
16
16
  resolveProxies,
17
17
  resolveProxy,
18
18
  waitForProxy
19
- } from "../chunk-QFJR3NIL.js";
20
- import "../chunk-E3VGIAMJ.js";
19
+ } from "../chunk-25IT6FMR.js";
20
+ import "../chunk-OVOXGASQ.js";
21
21
  import "../chunk-QF7MFJJU.js";
22
22
  import "../chunk-SVQFE44C.js";
23
23
  import "../chunk-L3HJVTQE.js";
@@ -20,7 +20,7 @@ import {
20
20
  recordRenderSuccess,
21
21
  registerRenderOutput,
22
22
  retainSnapshotOutput
23
- } from "../chunk-6PE5OD32.js";
23
+ } from "../chunk-XM2TJIF5.js";
24
24
  import "../chunk-QF7MFJJU.js";
25
25
  import "../chunk-SVQFE44C.js";
26
26
  import "../chunk-L3HJVTQE.js";
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Hono } from 'hono';
2
- import { S as StudioApiAdapter, P as PreviewDocumentStore, M as MediaProcessingJobState } from './mediaProxyPreview-DIAid9-k.js';
3
- export { L as LintResult, a as PreviewApiAdapter, R as RenderJobState, b as ResolvedProject, c as StudioSelectionResponse, d as StudioSelectionSnapshot, e as StudioSelectionTextField } from './mediaProxyPreview-DIAid9-k.js';
2
+ import { S as StudioApiAdapter, P as PreviewDocumentStore, M as MediaProcessingJobState } from './mediaProxyPreview-GMF-_zBh.js';
3
+ export { L as LintResult, a as PreviewApiAdapter, R as RenderJobState, b as ResolvedProject, c as StudioSelectionResponse, d as StudioSelectionSnapshot, e as StudioSelectionTextField } from './mediaProxyPreview-GMF-_zBh.js';
4
4
  export { C as ClosedWindow, D as DEFAULT_HISTORY_ROOT, S as HISTORY_START, H as HistoryClosedError, a as HistoryEntry, b as HistoryEntrySide, c as HistoryFileChange, d as HistoryListItem, e as HistoryResult, f as HistoryTarget, g as HistoryWho, h as HistoryWindow, M as MAX_WINDOW_IDLE_MS, P as ProjectHistory, i as ProjectHistoryOptions, j as ProjectHistoryView, U as UNDO_MODES, k as UndoMode, o as openProjectHistory, r as readProjectHistory } from './projectHistory-ByC9sa3X.js';
5
5
  export { ArtifactCapture, ArtifactInput, ArtifactRecord, HistoryBusyError, HistoryIdError, ProjectHistoryRecord, PrunedHistory, RenderRecord, VideoState, artifactDirectory, beginArtifactCapture, finishSnapshotRun, forgetRecordedArtifacts, historyCache, listProjectHistories, pruneGoneProjectHistories, recordArtifact, recordArtifacts, recordRenderSuccess, registerRenderOutput, retainSnapshotOutput } from './history/index.js';
6
6
  export { CACHE_DIR_NAME as PROXY_CACHE_DIR_NAME } from './helpers/proxyTranscoder.js';
@@ -14,6 +14,7 @@ export { STUDIO_MOTION_PATH, StudioMotionRenderScriptOptions, createStudioMotion
14
14
  export { decodeWellFormedEscapes } from '@hyperframes/parsers/asset-paths';
15
15
  export { isSafePath } from '@hyperframes/core/safe-path';
16
16
  import '@hyperframes/core';
17
+ import 'sharp';
17
18
  import '@hyperframes/parsers';
18
19
  import '@hyperframes/core/compiler';
19
20
  import './helpers/mediaCodecMap.js';
package/dist/index.js CHANGED
@@ -60,7 +60,7 @@ import {
60
60
  resolveProjectSignature,
61
61
  retainSnapshotOutput,
62
62
  settledFileTag
63
- } from "./chunk-6PE5OD32.js";
63
+ } from "./chunk-XM2TJIF5.js";
64
64
  import {
65
65
  clearElementScreenshotIsolation,
66
66
  getElementScreenshotClip
@@ -80,7 +80,7 @@ import {
80
80
  isAutoProxyEnabled,
81
81
  proxyEtagSalt,
82
82
  resolvePreviewMediaCodecProbeCache
83
- } from "./chunk-7L3DUCM3.js";
83
+ } from "./chunk-T5CGF7VZ.js";
84
84
  import {
85
85
  CACHE_DIR_NAME,
86
86
  PROXY_PENDING_RETRY_AFTER_SECONDS,
@@ -91,7 +91,7 @@ import {
91
91
  proxyActivityMark,
92
92
  resolveProxy,
93
93
  waitForProxy
94
- } from "./chunk-QFJR3NIL.js";
94
+ } from "./chunk-25IT6FMR.js";
95
95
  import {
96
96
  PROXY_VARIANT_CONFIG,
97
97
  decideMediaProxyEligibility,
@@ -100,7 +100,7 @@ import {
100
100
  probeMediaMetadata,
101
101
  recordProxyRequest,
102
102
  resolveProxyVariantRequest
103
- } from "./chunk-E3VGIAMJ.js";
103
+ } from "./chunk-OVOXGASQ.js";
104
104
  import {
105
105
  folderGone,
106
106
  isInHiddenOrVendorDir,
@@ -200,11 +200,11 @@ import { resolve as resolve2, dirname as dirname3, join as join6, sep as sep2 }
200
200
 
201
201
  // src/helpers/mime.ts
202
202
  var MIME_TYPES = {
203
- ".html": "text/html",
204
- ".css": "text/css",
205
- ".js": "text/javascript",
206
- ".mjs": "text/javascript",
207
- ".json": "application/json",
203
+ ".html": "text/html; charset=utf-8",
204
+ ".css": "text/css; charset=utf-8",
205
+ ".js": "text/javascript; charset=utf-8",
206
+ ".mjs": "text/javascript; charset=utf-8",
207
+ ".json": "application/json; charset=utf-8",
208
208
  ".svg": "image/svg+xml",
209
209
  ".png": "image/png",
210
210
  ".jpg": "image/jpeg",
@@ -4948,18 +4948,18 @@ var thumbnailGenerationCoordinator = new ThumbnailGenerationCoordinator(1);
4948
4948
  // src/routes/imageThumbnail.ts
4949
4949
  var MAX_SOURCE_BYTES = 64 * 1024 * 1024;
4950
4950
  var MAX_CACHE_BYTES = 8 * 1024 * 1024;
4951
- var sharpLoad;
4952
- function loadSharp() {
4953
- sharpLoad ??= import("sharp").then(
4954
- (module) => module.default,
4955
- (error) => {
4956
- console.warn("[Studio] JPEG thumbnails are off: sharp could not load:", error);
4957
- throw error;
4958
- }
4959
- );
4960
- return sharpLoad;
4951
+ function retryingSharpLoader(adapter) {
4952
+ let loaded;
4953
+ let warned = false;
4954
+ const load = () => adapter.loadSharp?.() ?? import("sharp").then((module) => module.default);
4955
+ return () => loaded ??= load().catch((error) => {
4956
+ loaded = void 0;
4957
+ if (!warned) console.warn("[Studio] JPEG thumbnails are off: sharp could not load:", error);
4958
+ warned = true;
4959
+ throw error;
4960
+ });
4961
4961
  }
4962
- async function generateThumbnail(path, signal) {
4962
+ async function generateThumbnail(loadSharp, path, signal) {
4963
4963
  const sharp = await loadSharp();
4964
4964
  const file = await open(path, "r");
4965
4965
  try {
@@ -5000,6 +5000,7 @@ async function resolveSource(adapter, id, url) {
5000
5000
  }
5001
5001
  function registerImageThumbnailRoutes(api, adapter) {
5002
5002
  const coordinator = new ThumbnailGenerationCoordinator(2);
5003
+ const loadSharp = retryingSharpLoader(adapter);
5003
5004
  const cache = /* @__PURE__ */ new Map();
5004
5005
  let cacheBytes = 0;
5005
5006
  const remember = (key, image) => {
@@ -5028,7 +5029,7 @@ function registerImageThumbnailRoutes(api, adapter) {
5028
5029
  else {
5029
5030
  if (coordinator.protectedKeys().size >= 32) return c.text("thumbnail queue full", 429);
5030
5031
  result = await coordinator.acquire(key, c.req.raw.signal, async (signal) => {
5031
- const image = await generateThumbnail(path, signal);
5032
+ const image = await generateThumbnail(loadSharp, path, signal);
5032
5033
  remember(key, image);
5033
5034
  return image;
5034
5035
  }) ?? void 0;
@@ -1,5 +1,6 @@
1
1
  import * as _hyperframes_core from '@hyperframes/core';
2
2
  import { RegistryItem, PreviewProxyBox } from '@hyperframes/core';
3
+ import * as sharp from 'sharp';
3
4
  import { CanvasResolution } from '@hyperframes/parsers';
4
5
  import { BundleOptions } from '@hyperframes/core/compiler';
5
6
  import { P as ProjectHistory } from './projectHistory-ByC9sa3X.js';
@@ -119,6 +120,7 @@ interface StudioApiAdapter {
119
120
  /** Optional: a cached `createProjectSignature(dir)`; preview caching checks builds against it. */
120
121
  getProjectSignature?: (projectDir: string) => string;
121
122
  invalidateProjectSignature?: (projectDir: string) => void;
123
+ loadSharp?: () => Promise<typeof sharp.default>;
122
124
  /** Lint a single HTML string. */
123
125
  lint(html: string, opts?: {
124
126
  filePath?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperframes/studio-server",
3
- "version": "0.8.181",
3
+ "version": "0.8.184",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/heygen-com/hyperframes",
@@ -84,9 +84,8 @@
84
84
  "linkedom": "^0.18.12",
85
85
  "postcss": "^8.5.8",
86
86
  "postcss-selector-parser": "^7.1.2",
87
- "sharp": "^0.35.0",
88
- "@hyperframes/core": "0.8.181",
89
- "@hyperframes/parsers": "0.8.181"
87
+ "@hyperframes/core": "0.8.184",
88
+ "@hyperframes/parsers": "0.8.184"
90
89
  },
91
90
  "devDependencies": {
92
91
  "@types/node": "^25.0.10",
@@ -95,6 +94,9 @@
95
94
  "typescript": "^5.0.0",
96
95
  "vitest": "^4.1.11"
97
96
  },
97
+ "optionalDependencies": {
98
+ "sharp": "^0.35.0"
99
+ },
98
100
  "scripts": {
99
101
  "build": "tsup",
100
102
  "test": "vitest run",