@hyperframes/studio-server 0.9.6 → 0.9.8

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,8 @@
1
1
  import {
2
+ PROXY_STILL_EXTENSION,
2
3
  PROXY_VARIANT_CONFIG,
3
4
  ffmpegHasHdrFilters
4
- } from "./chunk-FUTDRYQK.js";
5
+ } from "./chunk-NOIHVBIQ.js";
5
6
  import {
6
7
  probeFirstFrameColour,
7
8
  probeMediaMetadata
@@ -36,9 +37,10 @@ import { extname, join } from "path";
36
37
  var DEFAULT_MAX_BYTES = 10 * 1024 * 1024 * 1024;
37
38
  var DEFAULT_STALE_TEMP_MS = 60 * 60 * 1e3;
38
39
  var DEFAULT_MIN_SWEEP_INTERVAL_MS = 5 * 60 * 1e3;
39
- var PROXY_EXTENSIONS = new Set(
40
- Object.values(PROXY_VARIANT_CONFIG).map(({ extension }) => extension)
41
- );
40
+ var PROXY_EXTENSIONS = /* @__PURE__ */ new Set([
41
+ ...Object.values(PROXY_VARIANT_CONFIG).map(({ extension }) => extension),
42
+ PROXY_STILL_EXTENSION
43
+ ]);
42
44
  var TRANSCODER_PROXY_NAME = /^[0-9a-f]{64}\.\w+$/;
43
45
  var TRANSCODER_TEMP_NAME = /^\.tmp-[0-9a-f-]{36}-(?:\.part-[0-9a-f]{64}-\d+|[0-9a-f]{64})\.\w+$/;
44
46
  var TRANSCODER_PIECE_NAME = /^\.part-([0-9a-f]{64})-\d+(\.\w+)$/;
@@ -1082,6 +1084,43 @@ async function resolveProxies(projectDir, sources, onSettled = () => {
1082
1084
  await Promise.all(Array.from({ length: workers }, worker));
1083
1085
  return results;
1084
1086
  }
1087
+ var stillsInFlight = /* @__PURE__ */ new Map();
1088
+ function resolveStill(projectDir, absoluteSourcePath, atSeconds, box) {
1089
+ const source = canonicalizeProxySource(projectDir, absoluteSourcePath);
1090
+ const at = Math.max(0, Math.round(atSeconds * 10) / 10);
1091
+ const key = fastCopyKey(source.sourcePath, PROXY_PARAMS_VERSION, `still@${at}`, box);
1092
+ const cachePath = join4(source.projectDir, CACHE_DIR_NAME, `${key}${PROXY_STILL_EXTENSION}`);
1093
+ if (existsSync4(cachePath)) return Promise.resolve(cachePath);
1094
+ const shared = stillsInFlight.get(cachePath);
1095
+ if (shared) return shared;
1096
+ const made = makeStill(source, at, box, cachePath).finally(
1097
+ () => stillsInFlight.delete(cachePath)
1098
+ );
1099
+ stillsInFlight.set(cachePath, made);
1100
+ return made;
1101
+ }
1102
+ async function makeStill(source, at, box, cachePath) {
1103
+ const ffmpegPath = findFfBinary("ffmpeg", { configuredMustExist: true });
1104
+ if (!ffmpegPath) throw new FfmpegUnavailableError();
1105
+ const filter = await buildVideoFilter(
1106
+ ffmpegPath,
1107
+ source.sourcePath,
1108
+ await probeMediaMetadata(source.sourcePath),
1109
+ false,
1110
+ box
1111
+ );
1112
+ mkdirWithinProject(source.projectDir, dirname3(cachePath));
1113
+ const tempPath = join4(dirname3(cachePath), `.tmp-${randomUUID()}-${basename3(cachePath)}`);
1114
+ try {
1115
+ const args = ["-y", "-ss", String(at), "-i", source.sourcePath, "-map", "0:V:0"];
1116
+ args.push("-frames:v", "1", "-vf", filter, "-pix_fmt", "yuvj420p", "-q:v", "3", tempPath);
1117
+ await spawnFfmpeg(ffmpegPath, args, TRANSCODE_STALL_MS, new AbortController().signal);
1118
+ renameSync(tempPath, cachePath);
1119
+ } finally {
1120
+ rmSync2(tempPath, { force: true });
1121
+ }
1122
+ return cachePath;
1123
+ }
1085
1124
  function joinJob(promise, job, signal) {
1086
1125
  job.callers++;
1087
1126
  if (!signal) return promise;
@@ -1117,5 +1156,6 @@ export {
1117
1156
  retryProxyCopy,
1118
1157
  proxyActivityMark,
1119
1158
  resolveProxy,
1120
- resolveProxies
1159
+ resolveProxies,
1160
+ resolveStill
1121
1161
  };
@@ -1,14 +1,14 @@
1
1
  import {
2
2
  PROXY_PARAMS_VERSION,
3
3
  resolveProxy
4
- } from "./chunk-CEQWOCYI.js";
4
+ } from "./chunk-BAJ6HKBU.js";
5
5
  import {
6
6
  createMediaCodecProbeCache,
7
7
  proxyVariantFor,
8
8
  recordProxyPrewarm,
9
9
  scanProjectMedia,
10
10
  shouldPrewarmProxy
11
- } from "./chunk-FUTDRYQK.js";
11
+ } from "./chunk-NOIHVBIQ.js";
12
12
 
13
13
  // src/helpers/mediaProxyPreview.ts
14
14
  import { resolve } from "path";
@@ -98,6 +98,7 @@ var PROXY_VARIANT_CONFIG = {
98
98
  h264: { extension: ".mp4", contentType: "video/mp4" },
99
99
  vp8: { extension: ".webm", contentType: "video/webm" }
100
100
  };
101
+ var PROXY_STILL_EXTENSION = ".jpg";
101
102
  function isProxyVariant(value) {
102
103
  return Object.hasOwn(PROXY_VARIANT_CONFIG, value);
103
104
  }
@@ -252,6 +253,7 @@ export {
252
253
  recordProxyPrewarm,
253
254
  recordProxyRequest,
254
255
  PROXY_VARIANT_CONFIG,
256
+ PROXY_STILL_EXTENSION,
255
257
  isProxyVariant,
256
258
  isProxyVariantRequest,
257
259
  proxyVariantFor,
@@ -89,6 +89,7 @@ declare const PROXY_VARIANT_CONFIG: Record<ProxyVariant, {
89
89
  extension: ".mp4" | ".webm";
90
90
  contentType: "video/mp4" | "video/webm";
91
91
  }>;
92
+ declare const PROXY_STILL_EXTENSION = ".jpg";
92
93
  declare function isProxyVariant(value: string): value is ProxyVariant;
93
94
  declare function isProxyVariantRequest(value: string): value is ProxyVariantRequest;
94
95
  declare function proxyVariantFor(facts: AssetCodecFacts): ProxyVariant;
@@ -144,4 +145,4 @@ declare function scanProjectMedia(projectDir: string, htmlSources: HtmlSourceLik
144
145
  /** The injection map alone: root-relative URL pathname -> codec facts of the hostile videos. */
145
146
  declare function scanProjectMediaCodecMap(projectDir: string, htmlSources: HtmlSourceLike[], options?: ScanProjectMediaCodecMapOptions): Promise<MediaCodecMap>;
146
147
 
147
- export { type AssetCodecFacts, BROWSER_HOSTILE_CODECS, type BrowserHostileCodec, type HtmlSourceLike, type MediaCodecMap, type MediaCodecProbeCache, type MediaProxyEligibility, type MediaProxyIneligibilityReason, PROXY_VARIANT_CONFIG, type ProjectMediaScan, type ProxyVariant, type ProxyVariantRequest, type ScanProjectMediaCodecMapOptions, createMediaCodecProbeCache, decideMediaProxyEligibility, isProxyVariant, isProxyVariantRequest, mediaProxyDemand, probeAssetCodec, proxyVariantFor, recordProxyPrewarm, recordProxyRequest, resolveProxyVariantRequest, scanProjectMedia, scanProjectMediaCodecMap, shouldPrewarmProxy };
148
+ export { type AssetCodecFacts, BROWSER_HOSTILE_CODECS, type BrowserHostileCodec, type HtmlSourceLike, type MediaCodecMap, type MediaCodecProbeCache, type MediaProxyEligibility, type MediaProxyIneligibilityReason, PROXY_STILL_EXTENSION, PROXY_VARIANT_CONFIG, type ProjectMediaScan, type ProxyVariant, type ProxyVariantRequest, type ScanProjectMediaCodecMapOptions, createMediaCodecProbeCache, decideMediaProxyEligibility, isProxyVariant, isProxyVariantRequest, mediaProxyDemand, probeAssetCodec, proxyVariantFor, recordProxyPrewarm, recordProxyRequest, resolveProxyVariantRequest, scanProjectMedia, scanProjectMediaCodecMap, shouldPrewarmProxy };
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  BROWSER_HOSTILE_CODECS,
3
+ PROXY_STILL_EXTENSION,
3
4
  PROXY_VARIANT_CONFIG,
4
5
  createMediaCodecProbeCache,
5
6
  decideMediaProxyEligibility,
@@ -14,13 +15,14 @@ import {
14
15
  scanProjectMedia,
15
16
  scanProjectMediaCodecMap,
16
17
  shouldPrewarmProxy
17
- } from "../chunk-FUTDRYQK.js";
18
+ } from "../chunk-NOIHVBIQ.js";
18
19
  import "../chunk-X73QTST2.js";
19
20
  import "../chunk-QF7MFJJU.js";
20
21
  import "../chunk-SVQFE44C.js";
21
22
  import "../chunk-L3HJVTQE.js";
22
23
  export {
23
24
  BROWSER_HOSTILE_CODECS,
25
+ PROXY_STILL_EXTENSION,
24
26
  PROXY_VARIANT_CONFIG,
25
27
  createMediaCodecProbeCache,
26
28
  decideMediaProxyEligibility,
@@ -4,9 +4,9 @@ import {
4
4
  isAutoProxyEnabled,
5
5
  proxyEtagSalt,
6
6
  resolvePreviewMediaCodecProbeCache
7
- } from "../chunk-PN2VGFAA.js";
8
- import "../chunk-CEQWOCYI.js";
9
- import "../chunk-FUTDRYQK.js";
7
+ } from "../chunk-J4SRRKQU.js";
8
+ import "../chunk-BAJ6HKBU.js";
9
+ import "../chunk-NOIHVBIQ.js";
10
10
  import "../chunk-X73QTST2.js";
11
11
  import "../chunk-QF7MFJJU.js";
12
12
  import "../chunk-SVQFE44C.js";
@@ -87,6 +87,8 @@ declare function resolveProxies(projectDir: string, sources: ReadonlyArray<{
87
87
  variant: ProxyVariant;
88
88
  box?: PreviewProxyBox;
89
89
  }>, onSettled?: (index: number) => void, onProgress?: (index: number, progress: ProxyProgress) => void, signal?: AbortSignal): Promise<PromiseSettledResult<string>[]>;
90
+ /** One JPEG of the source at a time (to a tenth of a second), sized like the copy's box. */
91
+ declare function resolveStill(projectDir: string, absoluteSourcePath: string, atSeconds: number, box?: PreviewProxyBox): Promise<string>;
90
92
  interface ResolveProxyOptions {
91
93
  priority?: boolean;
92
94
  background?: boolean;
@@ -95,4 +97,4 @@ interface ResolveProxyOptions {
95
97
  onProgress?: (progress: ProxyProgress) => void;
96
98
  }
97
99
 
98
- export { CACHE_DIR_NAME, DEFAULT_PROXY_WAIT_TIMEOUT_MS, FfmpegMissingFilterError, FfmpegUnavailableError, PROXY_CHUNK_SECONDS, PROXY_PARAMS_VERSION, PROXY_PENDING_RETRY_AFTER_SECONDS, ProxyCapacityError, type ProxyProgress, ProxySourceOutsideProjectError, ProxyTranscodeError, ProxyWaitTimeoutError, type ResolveProxyOptions, type SourceFolderKind, TRANSCODE_STALL_MS, clearFailedTranscodesForTest, getProxyCachePath, proxyActivityMark, proxyCopyProgress, resolveProxies, resolveProxy, retryProxyCopy, sourceFolderKind, waitForProxy };
100
+ export { CACHE_DIR_NAME, DEFAULT_PROXY_WAIT_TIMEOUT_MS, FfmpegMissingFilterError, FfmpegUnavailableError, PROXY_CHUNK_SECONDS, PROXY_PARAMS_VERSION, PROXY_PENDING_RETRY_AFTER_SECONDS, ProxyCapacityError, type ProxyProgress, ProxySourceOutsideProjectError, ProxyTranscodeError, ProxyWaitTimeoutError, type ResolveProxyOptions, type SourceFolderKind, TRANSCODE_STALL_MS, clearFailedTranscodesForTest, getProxyCachePath, proxyActivityMark, proxyCopyProgress, resolveProxies, resolveProxy, resolveStill, retryProxyCopy, sourceFolderKind, waitForProxy };
@@ -17,11 +17,12 @@ import {
17
17
  proxyCopyProgress,
18
18
  resolveProxies,
19
19
  resolveProxy,
20
+ resolveStill,
20
21
  retryProxyCopy,
21
22
  sourceFolderKind,
22
23
  waitForProxy
23
- } from "../chunk-CEQWOCYI.js";
24
- import "../chunk-FUTDRYQK.js";
24
+ } from "../chunk-BAJ6HKBU.js";
25
+ import "../chunk-NOIHVBIQ.js";
25
26
  import "../chunk-X73QTST2.js";
26
27
  import "../chunk-QF7MFJJU.js";
27
28
  import "../chunk-SVQFE44C.js";
@@ -45,6 +46,7 @@ export {
45
46
  proxyCopyProgress,
46
47
  resolveProxies,
47
48
  resolveProxy,
49
+ resolveStill,
48
50
  retryProxyCopy,
49
51
  sourceFolderKind,
50
52
  waitForProxy
package/dist/index.js CHANGED
@@ -91,7 +91,7 @@ import {
91
91
  isAutoProxyEnabled,
92
92
  proxyEtagSalt,
93
93
  resolvePreviewMediaCodecProbeCache
94
- } from "./chunk-PN2VGFAA.js";
94
+ } from "./chunk-J4SRRKQU.js";
95
95
  import {
96
96
  CACHE_DIR_NAME,
97
97
  PROXY_PENDING_RETRY_AFTER_SECONDS,
@@ -102,9 +102,10 @@ import {
102
102
  proxyActivityMark,
103
103
  proxyCopyProgress,
104
104
  resolveProxy,
105
+ resolveStill,
105
106
  retryProxyCopy,
106
107
  waitForProxy
107
- } from "./chunk-CEQWOCYI.js";
108
+ } from "./chunk-BAJ6HKBU.js";
108
109
  import {
109
110
  PROXY_VARIANT_CONFIG,
110
111
  decideMediaProxyEligibility,
@@ -112,7 +113,7 @@ import {
112
113
  probeAssetCodec,
113
114
  recordProxyRequest,
114
115
  resolveProxyVariantRequest
115
- } from "./chunk-FUTDRYQK.js";
116
+ } from "./chunk-NOIHVBIQ.js";
116
117
  import {
117
118
  probeAudioTrack,
118
119
  probeMediaMetadata
@@ -3932,6 +3933,7 @@ import {
3932
3933
  import {
3933
3934
  STUDIO_PREVIEW_CAPTURE_MODE,
3934
3935
  STUDIO_PREVIEW_MARK_META,
3936
+ STUDIO_PREVIEW_ERROR_KINDS,
3935
3937
  STUDIO_PREVIEW_ERRORS
3936
3938
  } from "@hyperframes/core/studio-preview-mark";
3937
3939
  import {
@@ -3940,6 +3942,7 @@ import {
3940
3942
  PREVIEW_PROXY_PRIORITY_HEADER,
3941
3943
  PREVIEW_PROXY_PROGRESS_HEADER,
3942
3944
  PREVIEW_PROXY_RETRY_PARAM,
3945
+ PREVIEW_PROXY_STILL_PARAM,
3943
3946
  parsePreviewProxyBox
3944
3947
  } from "@hyperframes/core";
3945
3948
  import {
@@ -4278,7 +4281,7 @@ var GSAP_FALLBACK_ATTR = "data-hf-gsap-fallback";
4278
4281
  function gsapCdnFallbackScript(baseUrl) {
4279
4282
  return `<script ${GSAP_FALLBACK_ATTR}>
4280
4283
  (function(){
4281
- var cdnBase=${JSON.stringify(gsapCdnDist2(void 0, baseUrl)).replace(/</g, "\\u003c")};
4284
+ var cdnBase=${scriptJson(gsapCdnDist2(void 0, baseUrl))};
4282
4285
  var loaded={};
4283
4286
  function loadFallback(file){
4284
4287
  if(loaded[file])return loaded[file];
@@ -4328,15 +4331,21 @@ function previewVariablesFromRequest(rawVariables) {
4328
4331
  return { raw: rawVariables, values: parse2.values };
4329
4332
  }
4330
4333
  var PREVIEW_CAPTURE_PARAM = "hf-capture";
4331
- var EARLY_ERRORS_SCRIPT = `<script>(function(){var seen=window.${STUDIO_PREVIEW_ERRORS}=[];
4332
- addEventListener("error",function(e){seen.push(e.message||String(e))});})();</script>`;
4334
+ function scriptJson(value) {
4335
+ return JSON.stringify(value).replace(/</g, "\\u003c").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
4336
+ }
4337
+ var earlyErrorsScript = (runtimeUrl) => `<script>(function(){var seen=window.${STUDIO_PREVIEW_ERRORS}=[],kinds=window.${STUDIO_PREVIEW_ERROR_KINDS}=[],known={SyntaxError:1,TypeError:1,ReferenceError:1,RangeError:1},runtime=${scriptJson(new URL(runtimeUrl, "http://x").pathname)};
4338
+ addEventListener("error",function(e){seen.push(e.message||String(e));
4339
+ var name=e.error&&e.error.constructor&&e.error.constructor.name,path="";
4340
+ try{var url=e.filename&&new URL(e.filename,window.location.href);if(url&&url.origin===window.location.origin)path=url.pathname}catch(_){}
4341
+ kinds.push({name:known[name]===1?name:"other",origin:!path?"unknown":path===runtime?"runtime":path.indexOf("/api/projects/")===0?"project":"studio"})});})();</script>`;
4333
4342
  function injectStudioPreviewAugmentations(html, adapter, projectDir, activeCompositionPath, options) {
4334
4343
  const marked = options.capture ? injectTagsAtHeadStart2(
4335
4344
  html,
4336
4345
  `<meta name="${STUDIO_PREVIEW_MARK_META}" content="${STUDIO_PREVIEW_CAPTURE_MODE}">`
4337
4346
  ) : injectTagsAtHeadStart2(
4338
4347
  lazyPreviewImages(html),
4339
- `<meta name="${STUDIO_PREVIEW_MARK_META}">${EARLY_ERRORS_SCRIPT}`
4348
+ `<meta name="${STUDIO_PREVIEW_MARK_META}">${earlyErrorsScript(adapter.runtimeUrl)}`
4340
4349
  );
4341
4350
  const withFallback = injectGsapCdnFallback(
4342
4351
  injectProjectSignature(
@@ -4624,6 +4633,22 @@ ${etag}`;
4624
4633
  return c.text("media proxy variant does not match asset", 422);
4625
4634
  }
4626
4635
  }
4636
+ const stillParam = c.req.query(PREVIEW_PROXY_STILL_PARAM);
4637
+ if (stillParam !== void 0) {
4638
+ const at = Number(stillParam);
4639
+ if (stillParam === "" || !Number.isFinite(at) || at < 0 || !proxyVariant) {
4640
+ return c.text("not found", 404);
4641
+ }
4642
+ try {
4643
+ const still = await resolveStill(project.dir, file, at, proxyBox);
4644
+ return new Response(readFileSync6(still), {
4645
+ headers: { "Content-Type": "image/jpeg", "Cache-Control": "private, max-age=3600" }
4646
+ });
4647
+ } catch (err) {
4648
+ if (isProjectRootMissing(err)) throw err;
4649
+ return c.text("media still failed", 502);
4650
+ }
4651
+ }
4627
4652
  const tag = settledFileTag(stat2);
4628
4653
  const etag = tag && `"${tag}${proxyEtagSalt(proxyVariant, proxyBox)}"`;
4629
4654
  const cacheHeaders = isText ? { "Cache-Control": "no-store" } : { "Cache-Control": "private, no-cache", ...etag && { ETag: etag } };
@@ -4806,6 +4831,13 @@ function contentDispositionHeader(disposition, filename) {
4806
4831
  }
4807
4832
  function registerRenderRoutes(api, adapter) {
4808
4833
  const renderJobs = /* @__PURE__ */ new Map();
4834
+ const nextFreeRenderJobId = (baseId, ext, rendersDir) => {
4835
+ let jobId = baseId;
4836
+ for (let n = 2; renderJobs.has(jobId) || existsSync6(join10(rendersDir, `${jobId}${ext}`)); n++) {
4837
+ jobId = `${baseId}-${n}`;
4838
+ }
4839
+ return jobId;
4840
+ };
4809
4841
  const TTL_MS = 3e5;
4810
4842
  const CLEANUP_INTERVAL_MS = 6e4;
4811
4843
  let cleanupTimer = null;
@@ -4869,11 +4901,14 @@ function registerRenderRoutes(api, adapter) {
4869
4901
  return c.json({ error: "pageMotionBlur must be off when supplied" }, 400);
4870
4902
  }
4871
4903
  const pageMotionBlur = requestedPageMotionBlur === void 0 && body.previewMotionBlur === false ? "off" : requestedPageMotionBlur;
4872
- const now = /* @__PURE__ */ new Date();
4873
- const jobId = `${project.id}_${formatRenderOutputTimestamp(now)}`;
4874
4904
  const rendersDir = adapter.rendersDir(project);
4875
4905
  mkdirWithinProject(project.dir, rendersDir);
4876
4906
  const ext = FORMAT_EXT[format] ?? ".mp4";
4907
+ const jobId = nextFreeRenderJobId(
4908
+ `${project.id}_${formatRenderOutputTimestamp(/* @__PURE__ */ new Date())}`,
4909
+ ext,
4910
+ rendersDir
4911
+ );
4877
4912
  const outputPath = join10(rendersDir, `${jobId}${ext}`);
4878
4913
  const jobState = adapter.startRender({
4879
4914
  project,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperframes/studio-server",
3
- "version": "0.9.6",
3
+ "version": "0.9.8",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/heygen-com/hyperframes",
@@ -88,8 +88,8 @@
88
88
  "linkedom": "^0.18.12",
89
89
  "postcss": "^8.5.8",
90
90
  "postcss-selector-parser": "^7.1.2",
91
- "@hyperframes/parsers": "0.9.6",
92
- "@hyperframes/core": "0.9.6"
91
+ "@hyperframes/core": "0.9.8",
92
+ "@hyperframes/parsers": "0.9.8"
93
93
  },
94
94
  "devDependencies": {
95
95
  "@types/node": "^25.0.10",