@hyperframes/studio-server 0.9.5 → 0.9.7

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
@@ -3940,6 +3941,7 @@ import {
3940
3941
  PREVIEW_PROXY_PRIORITY_HEADER,
3941
3942
  PREVIEW_PROXY_PROGRESS_HEADER,
3942
3943
  PREVIEW_PROXY_RETRY_PARAM,
3944
+ PREVIEW_PROXY_STILL_PARAM,
3943
3945
  parsePreviewProxyBox
3944
3946
  } from "@hyperframes/core";
3945
3947
  import {
@@ -4624,6 +4626,22 @@ ${etag}`;
4624
4626
  return c.text("media proxy variant does not match asset", 422);
4625
4627
  }
4626
4628
  }
4629
+ const stillParam = c.req.query(PREVIEW_PROXY_STILL_PARAM);
4630
+ if (stillParam !== void 0) {
4631
+ const at = Number(stillParam);
4632
+ if (stillParam === "" || !Number.isFinite(at) || at < 0 || !proxyVariant) {
4633
+ return c.text("not found", 404);
4634
+ }
4635
+ try {
4636
+ const still = await resolveStill(project.dir, file, at, proxyBox);
4637
+ return new Response(readFileSync6(still), {
4638
+ headers: { "Content-Type": "image/jpeg", "Cache-Control": "private, max-age=3600" }
4639
+ });
4640
+ } catch (err) {
4641
+ if (isProjectRootMissing(err)) throw err;
4642
+ return c.text("media still failed", 502);
4643
+ }
4644
+ }
4627
4645
  const tag = settledFileTag(stat2);
4628
4646
  const etag = tag && `"${tag}${proxyEtagSalt(proxyVariant, proxyBox)}"`;
4629
4647
  const cacheHeaders = isText ? { "Cache-Control": "no-store" } : { "Cache-Control": "private, no-cache", ...etag && { ETag: etag } };
@@ -4806,6 +4824,13 @@ function contentDispositionHeader(disposition, filename) {
4806
4824
  }
4807
4825
  function registerRenderRoutes(api, adapter) {
4808
4826
  const renderJobs = /* @__PURE__ */ new Map();
4827
+ const nextFreeRenderJobId = (baseId, ext, rendersDir) => {
4828
+ let jobId = baseId;
4829
+ for (let n = 2; renderJobs.has(jobId) || existsSync6(join10(rendersDir, `${jobId}${ext}`)); n++) {
4830
+ jobId = `${baseId}-${n}`;
4831
+ }
4832
+ return jobId;
4833
+ };
4809
4834
  const TTL_MS = 3e5;
4810
4835
  const CLEANUP_INTERVAL_MS = 6e4;
4811
4836
  let cleanupTimer = null;
@@ -4869,11 +4894,14 @@ function registerRenderRoutes(api, adapter) {
4869
4894
  return c.json({ error: "pageMotionBlur must be off when supplied" }, 400);
4870
4895
  }
4871
4896
  const pageMotionBlur = requestedPageMotionBlur === void 0 && body.previewMotionBlur === false ? "off" : requestedPageMotionBlur;
4872
- const now = /* @__PURE__ */ new Date();
4873
- const jobId = `${project.id}_${formatRenderOutputTimestamp(now)}`;
4874
4897
  const rendersDir = adapter.rendersDir(project);
4875
4898
  mkdirWithinProject(project.dir, rendersDir);
4876
4899
  const ext = FORMAT_EXT[format] ?? ".mp4";
4900
+ const jobId = nextFreeRenderJobId(
4901
+ `${project.id}_${formatRenderOutputTimestamp(/* @__PURE__ */ new Date())}`,
4902
+ ext,
4903
+ rendersDir
4904
+ );
4877
4905
  const outputPath = join10(rendersDir, `${jobId}${ext}`);
4878
4906
  const jobState = adapter.startRender({
4879
4907
  project,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperframes/studio-server",
3
- "version": "0.9.5",
3
+ "version": "0.9.7",
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/core": "0.9.5",
92
- "@hyperframes/parsers": "0.9.5"
91
+ "@hyperframes/parsers": "0.9.7",
92
+ "@hyperframes/core": "0.9.7"
93
93
  },
94
94
  "devDependencies": {
95
95
  "@types/node": "^25.0.10",