@hyperframes/producer 0.8.177 → 0.8.179

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.
@@ -1407,9 +1407,9 @@ import {
1407
1407
  } from "node:fs";
1408
1408
  import { hostname, uptime } from "node:os";
1409
1409
  import { dirname as dirname3, join as join4 } from "node:path";
1410
+ import { RENDER_TEMP_DIR } from "@hyperframes/core/project-folders";
1410
1411
  var OWNER_FILE = "owner.json";
1411
1412
  var TRANSACTION_BACKUP = "backup";
1412
- var RENDER_TEMP_DIR = /^(work-[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}|hf-render|\..+\.hf-transaction)-[A-Za-z0-9]{6}$/;
1413
1413
  function pidScope() {
1414
1414
  const attempt = (read) => {
1415
1415
  try {
@@ -6086,7 +6086,7 @@ function detectShaderTransitionUsage(html) {
6086
6086
  }
6087
6087
  return false;
6088
6088
  }
6089
- async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, downloadDir, tagName, elementIdentity, log) {
6089
+ async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, downloadDir, tagName, elementIdentity, log, urlByAbsentLocalPath) {
6090
6090
  let filePath = src;
6091
6091
  if (isHttpUrl(src)) {
6092
6092
  if (!existsSync10(downloadDir)) mkdirSync6(downloadDir, { recursive: true });
@@ -6100,8 +6100,9 @@ async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, down
6100
6100
  } else {
6101
6101
  filePath = decodedUrlPath(src);
6102
6102
  if (!filePath.startsWith("/")) filePath = join13(baseDir, filePath);
6103
+ filePath = urlByAbsentLocalPath?.get(filePath) ?? filePath;
6103
6104
  }
6104
- if (!existsSync10(filePath)) {
6105
+ if (!isHttpUrl(filePath) && !existsSync10(filePath)) {
6105
6106
  return { duration: null, resolvedPath: filePath };
6106
6107
  }
6107
6108
  const withSrcContext = (error) => {
@@ -6164,7 +6165,7 @@ function markInferredVariableMediaDurations(html, unresolvedMedia) {
6164
6165
  }
6165
6166
  return changed ? document2.toString() : html;
6166
6167
  }
6167
- async function compileHtmlFile(html, baseDir, downloadDir, log) {
6168
+ async function compileHtmlFile(html, baseDir, downloadDir, log, urlByAbsentLocalPath) {
6168
6169
  const { html: staticCompiled, unresolved } = compileTimingAttrs(html);
6169
6170
  const mediaUnresolved = unresolved.filter(
6170
6171
  (el) => (el.tagName === "video" || el.tagName === "audio") && el.src
@@ -6180,7 +6181,8 @@ async function compileHtmlFile(html, baseDir, downloadDir, log) {
6180
6181
  downloadDir,
6181
6182
  el.tagName,
6182
6183
  el.id,
6183
- log
6184
+ log,
6185
+ urlByAbsentLocalPath
6184
6186
  ).then(({ duration }) => ({ id: el.id, duration }))
6185
6187
  )
6186
6188
  );
@@ -6222,7 +6224,7 @@ async function compileHtmlFile(html, baseDir, downloadDir, log) {
6222
6224
  compiledHtml = compiledHtml.replace(/(<audio\b[^>]*)\s+crossorigin(?:=["'][^"']*["'])?/gi, "$1");
6223
6225
  return { html: compiledHtml, unresolvedCompositions };
6224
6226
  }
6225
- async function parseSubCompositions(html, projectDir, downloadDir, visited = /* @__PURE__ */ new Set()) {
6227
+ async function parseSubCompositions(html, projectDir, downloadDir, visited = /* @__PURE__ */ new Set(), urlByAbsentLocalPath) {
6226
6228
  const subCompositions = /* @__PURE__ */ new Map();
6227
6229
  const { document: document2 } = parseHTML4(html);
6228
6230
  const compEls = document2.querySelectorAll("[data-composition-src]");
@@ -6248,13 +6250,16 @@ async function parseSubCompositions(html, projectDir, downloadDir, visited = /*
6248
6250
  const { html: compiledSub } = await compileHtmlFile(
6249
6251
  item.rawSubHtml,
6250
6252
  dirname6(item.filePath),
6251
- downloadDir
6253
+ downloadDir,
6254
+ void 0,
6255
+ urlByAbsentLocalPath
6252
6256
  );
6253
6257
  const nested = await parseSubCompositions(
6254
6258
  compiledSub,
6255
6259
  projectDir,
6256
6260
  downloadDir,
6257
- item.nestedVisited
6261
+ item.nestedVisited,
6262
+ urlByAbsentLocalPath
6258
6263
  );
6259
6264
  return {
6260
6265
  srcPath: item.srcPath,
@@ -7012,9 +7017,16 @@ async function compileForRender(projectDir, htmlPath, downloadDir, options = {})
7012
7017
  rawHtml,
7013
7018
  projectDir,
7014
7019
  downloadDir,
7015
- options.log
7020
+ options.log,
7021
+ options.urlByAbsentLocalPath
7022
+ );
7023
+ const { subCompositions } = await parseSubCompositions(
7024
+ compiledHtml,
7025
+ projectDir,
7026
+ downloadDir,
7027
+ void 0,
7028
+ options.urlByAbsentLocalPath
7016
7029
  );
7017
- const { subCompositions } = await parseSubCompositions(compiledHtml, projectDir, downloadDir);
7018
7030
  const fullHtml = ensureFullDocument(compiledHtml);
7019
7031
  const inlinedHtml = inlineSubCompositions(
7020
7032
  fullHtml,
@@ -7510,6 +7522,7 @@ async function runCompileStage(input) {
7510
7522
  allowSystemFontCapture,
7511
7523
  abortSignal,
7512
7524
  variables: input.variables,
7525
+ urlByAbsentLocalPath: input.urlByAbsentLocalPath,
7513
7526
  animatedGifCacheDir: cfg.extractCacheDir ? join14(cfg.extractCacheDir, "animated-gif") : void 0,
7514
7527
  ffmpegProcessTimeout: cfg.ffmpegProcessTimeout
7515
7528
  });
@@ -8472,7 +8485,8 @@ async function runExtractVideosStage(input) {
8472
8485
  abortSignal,
8473
8486
  assertNotAborted,
8474
8487
  materializeSymlinks,
8475
- deferRangeExtraction
8488
+ deferRangeExtraction,
8489
+ urlByAbsentLocalPath
8476
8490
  } = input;
8477
8491
  const stage2Start = Date.now();
8478
8492
  const extractionPolicy = resolveVideoExtractionPolicy();
@@ -8566,7 +8580,8 @@ async function runExtractVideosStage(input) {
8566
8580
  maxTransientRetries: extractionPolicy.maxTransientRetries,
8567
8581
  collectProbeFailures: extractionPolicy.failureMode === "enforce",
8568
8582
  deferRangeExtraction,
8569
- contentKeyedDirs: [join16(compiledDir, REMOTE_MEDIA_SUBDIR)]
8583
+ contentKeyedDirs: [join16(compiledDir, REMOTE_MEDIA_SUBDIR)],
8584
+ urlByAbsentLocalPath
8570
8585
  },
8571
8586
  abortSignal,
8572
8587
  cfg,
@@ -8649,6 +8664,7 @@ async function runAudioStage(input) {
8649
8664
  audioGain,
8650
8665
  audios,
8651
8666
  masterFxChain,
8667
+ urlByAbsentLocalPath,
8652
8668
  abortSignal,
8653
8669
  assertNotAborted,
8654
8670
  log
@@ -8668,7 +8684,7 @@ async function runAudioStage(input) {
8668
8684
  audioOutputPath,
8669
8685
  duration,
8670
8686
  abortSignal,
8671
- { ffmpegProcessTimeout, audioGain, masterFxChain },
8687
+ { ffmpegProcessTimeout, audioGain, masterFxChain, urlByAbsentLocalPath },
8672
8688
  compiledDir
8673
8689
  );
8674
8690
  } catch (err) {
@@ -13835,6 +13851,17 @@ var DISTRIBUTED_RENDER_CAPABILITIES = Object.freeze({
13835
13851
  // src/services/render/stages/planHash.ts
13836
13852
  var FIELD_DELIMITER = Buffer.from([0]);
13837
13853
 
13854
+ // src/services/distributed/chunkAudio.ts
13855
+ import { appendRenderProvenanceArgs, describeFfmpegFailure as describeFfmpegFailure2, runFfmpeg as runFfmpeg5 } from "@hyperframes/engine";
13856
+
13857
+ // src/services/distributed/mp4Stitch.ts
13858
+ var SCAN_BYTES = 64 * 1024;
13859
+
13860
+ // src/services/distributed/chunkAudio.ts
13861
+ var SLICE_DIR = "audio-chunks";
13862
+ var SLICE_FRAME = `${SLICE_DIR}/frame.m4a`;
13863
+ var SLICE_LAYOUT = `${SLICE_DIR}/layout.json`;
13864
+
13838
13865
  // src/services/distributed/planSize.ts
13839
13866
  var PLAN_ROOT_CATEGORY = {
13840
13867
  compiled: "compiled",
@@ -15,6 +15,17 @@ interface AudioCut {
15
15
  packetCount: number;
16
16
  }
17
17
  export declare function audioCutForChunk(grid: AacPacketGrid, chunks: readonly ChunkSliceJson[], index: number, fps: Fps): AudioCut | null;
18
+ export declare function chunkAudioSlicePath(index: number): string;
19
+ /** Which chunks need a sliced-audio file: `"all"`, one index, or `null` if it is not one. */
20
+ export declare function chunkAudioSliceTarget(path: string): "all" | number | null;
21
+ export declare function writeChunkAudioSlices(input: {
22
+ audioPath: string;
23
+ planDir: string;
24
+ chunks: readonly ChunkSliceJson[];
25
+ fps: Fps;
26
+ }): Promise<void>;
27
+ /** The plan audio for a chunk: the whole file, or one rebuilt at `scratchPath` from its slice. */
28
+ export declare function chunkAudioSource(planDir: string, chunkIndex: number, scratchPath: string): string | null;
18
29
  interface MuxInput {
19
30
  videoPath: string;
20
31
  audioPath: string | null;
@@ -1,5 +1,6 @@
1
1
  import { type BeforeCaptureHook, BROWSER_GPU_NOT_SOFTWARE, type CaptureOptions, type EngineConfig, type ExtractedFrames, type FrameLookupTable } from "@hyperframes/engine";
2
2
  import type { LockedRenderConfig } from "../render/stages/freezePlan.js";
3
+ import type { PlanV2RemoteSources } from "./planV2.js";
3
4
  import { type DistributedFormat, INVALID_VIDEO_METADATA, type PlanVideosJson } from "./shared.js";
4
5
  /** Distinct codes so adapters route retries per cause: a missing artifact may heal, a hash mismatch never. */
5
6
  export declare const FFMPEG_VERSION_MISMATCH = "FFMPEG_VERSION_MISMATCH";
@@ -33,6 +34,8 @@ export declare function buildChunkCaptureOptions(input: {
33
34
  encoder: Pick<LockedRenderConfig, "quality" | "deviceScaleFactor" | "variables">;
34
35
  videoCount: number;
35
36
  html: string;
37
+ skipReadinessVideoIds?: string[];
38
+ videoMetadataHints?: CaptureOptions["videoMetadataHints"];
36
39
  }): CaptureOptions;
37
40
  /** Extracts the given frames of deferred videos under the names a full extraction gives them. */
38
41
  export declare function extractDeferredVideoFramesForChunk(input: {
@@ -45,4 +48,5 @@ export declare function extractDeferredVideoFramesForChunk(input: {
45
48
  };
46
49
  outputRoot: string;
47
50
  cfg: Pick<EngineConfig, "ffmpegProcessTimeout">;
51
+ remoteSources?: PlanV2RemoteSources;
48
52
  }): Promise<ExtractedFrames[]>;
@@ -32,6 +32,7 @@ export interface TrackTable {
32
32
  /** Each track's sample tables, expanded to one entry per sample (and per chunk). */
33
33
  export declare function readTracks(moov: Buffer): TrackTable[];
34
34
  export declare function buildStitchedHeader(layouts: readonly Mp4Layout[]): Buffer;
35
+ export declare function fileRange(path: string): ReadRange;
35
36
  /** Local join: the stitched header, then each chunk's mdat payload in order. Like the ffmpeg
36
37
  * join, it writes in a work folder beside `outputPath` and renames only a finished file there. */
37
38
  export declare function writeStitchedMp4(chunkPaths: readonly string[], outputPath: string): Promise<void>;
@@ -201,15 +201,21 @@ export interface BuildLocalExecutionPlanOptions {
201
201
  * chunks write those frames into their own work directory.
202
202
  */
203
203
  readonly deferVideoExtraction?: boolean;
204
+ /** Project files absent locally: probe their URL and publish their existing digest. */
205
+ readonly remoteFiles?: Readonly<Record<string, RemoteProjectFile>>;
204
206
  /** Shared with the publisher so each plan file is hashed once. */
205
207
  readonly digests?: PlanFileDigests;
206
208
  /** Runs while the audio mixes, once `compiled/` and the video trees are final. */
207
209
  readonly whileAudioMixes?: () => Promise<void>;
208
- /**
209
- * mp4 chunks carry their own slice of the plan audio (renderChunk muxes it). Ignored for other
210
- * formats and with `cfr`, which re-encodes.
211
- */
210
+ /** mp4 chunks carry their plan-audio slice unless cfr re-encodes. */
212
211
  readonly chunksCarryAudio?: boolean;
212
+ /** With `chunksCarryAudio`, also write each chunk's audio packets to their own file. Plan v2 only. */
213
+ readonly sliceChunkAudio?: boolean;
214
+ }
215
+ export interface RemoteProjectFile {
216
+ readonly url: string;
217
+ readonly sha256: string;
218
+ readonly sizeBytes: number;
213
219
  }
214
220
  export declare function logPlanStage(log: ProducerLogger, stage: string, startedAt: number): void;
215
221
  /**
@@ -11,7 +11,7 @@
11
11
  * is absent, v2 explicitly falls back to the full-source pack.
12
12
  */
13
13
  import { type DistributedFps } from "@hyperframes/core";
14
- import { type DistributedRenderConfig } from "./plan.js";
14
+ import { type BuildLocalExecutionPlanOptions, type DistributedRenderConfig } from "./plan.js";
15
15
  import { type PlanProtocolV2Descriptor } from "./planProtocol.js";
16
16
  import { type PlanV2ArtifactPublisher } from "./planV2Publisher.js";
17
17
  import { PLAN_V2_INTEGRITY_UNRECOVERABLE, PlanV2IntegrityError } from "./planV2Errors.js";
@@ -88,6 +88,8 @@ export interface PlanV2WithPublisherOptions {
88
88
  * can avoid a second set of data blocks.
89
89
  */
90
90
  readonly stagingParentDir?: string;
91
+ /** See {@link BuildLocalExecutionPlanOptions.remoteFiles}; needs a publisher with `referenceStoredBlob`. */
92
+ readonly remoteFiles?: BuildLocalExecutionPlanOptions["remoteFiles"];
91
93
  readonly chunksCarryAudio?: boolean;
92
94
  }
93
95
  export interface PlanV2MaterializationResult {
@@ -105,6 +107,13 @@ export interface PlanV2MaterializationResult {
105
107
  readonly sizeBytes: number;
106
108
  readonly audioPath: string | null;
107
109
  }
110
+ export type PlanV2RemoteSources = Readonly<Record<string, string>>;
111
+ export interface PlanV2MaterializeOptions {
112
+ /** Deferred video sources the chunk reads by URL; the caller vouches for their bytes. */
113
+ readonly remoteSources?: PlanV2RemoteSources;
114
+ }
115
+ /** A chunk-scoped compiled/ file is a deferred video source (see `artifactTargets`). */
116
+ export declare function isPlanV2DeferredSourceArtifact(artifact: Readonly<PlanV2Artifact>): boolean;
108
117
  /** Publish a frozen local execution directory into an immutable local v2 transport. */
109
118
  export declare function createPlanV2FromExecutionPlan(executionPlanDir: string, planV2Dir: string): PlanV2Result;
110
119
  /**
@@ -147,6 +156,10 @@ export declare function listPlanV2ArtifactsForTarget(manifest: Readonly<PlanV2Ma
147
156
  * execution directory. The marker lets execution functions revalidate the
148
157
  * selected subset without requiring assembler-only audio in chunk workers.
149
158
  */
150
- export declare function materializePlanV2Target(planV2Dir: string, target: PlanV2MaterializationTarget, destinationDir: string): PlanV2MaterializationResult;
159
+ export declare function materializePlanV2Target(planV2Dir: string, target: PlanV2MaterializationTarget, destinationDir: string, options?: Readonly<PlanV2MaterializeOptions>): PlanV2MaterializationResult;
151
160
  /** Revalidate a materialized subset immediately before execution. */
152
161
  export declare function validatePlanV2MaterializedTarget(planDir: string, expectedTarget: PlanV2MaterializationTarget): Readonly<PlanV2Manifest> | null;
162
+ export declare function readValidatedPlanV2Materialization(planDir: string, expectedTarget: PlanV2MaterializationTarget): {
163
+ manifest: Readonly<PlanV2Manifest>;
164
+ remoteSources: PlanV2RemoteSources;
165
+ } | null;
@@ -11,6 +11,7 @@ export interface PlanV2ArtifactPublisher {
11
11
  * adapters must finish reading/uploading it before the promise resolves.
12
12
  */
13
13
  putBlob(blob: Readonly<PlanV2PublishBlob>): Promise<void>;
14
+ referenceStoredBlob?(blob: Readonly<Omit<PlanV2PublishBlob, "sourcePath">>): Promise<void>;
14
15
  /** Commit the manifest only after every referenced blob is durable. */
15
16
  commitManifest(manifestBytes: string): Promise<void>;
16
17
  /** Idempotent best-effort cleanup for an unpublished or partial plan. */
@@ -2,24 +2,29 @@
2
2
  * Content-addressing for a project directory, shared by the distributed-render
3
3
  * adapters' `deploySite` verbs.
4
4
  *
5
- * Each adapter uploads a project tarball to `…/sites/<siteId>/project.tar.gz`
6
- * and short-circuits the upload when the object already exists. `siteId` is a
5
+ * Each adapter uploads a project under `…/sites/<siteId>/` (AWS: a file list,
6
+ * Cloud Run: a tarball) and skips content already uploaded. `siteId` is a
7
7
  * SHA-256 over the project's files so identical content always maps to the
8
8
  * same key — that contract has to be byte-identical across adapters, which is
9
9
  * exactly why it lives here rather than being copy-pasted per adapter.
10
10
  */
11
11
  /**
12
- * SHA-256 over every regular file under `projectDir` (sorted by relative
13
- * path) → 16-character hex prefix. The prefix is the `siteId`.
12
+ * The files a site upload ships, as `/`-separated paths under `projectDir`. Symlinks are followed
13
+ * (root included), top-level {@link PLAN_PROJECT_DIR_SKIP_SEGMENTS} are left out as the plan stage
14
+ * does, and a dangling symlink throws.
15
+ */
16
+ export declare function listSiteFiles(projectDir: string): string[];
17
+ /**
18
+ * SHA-256 over a scheme tag plus each of {@link listSiteFiles}'s paths and
19
+ * bytes → 16-character hex prefix. The prefix is the `siteId`.
14
20
  *
15
21
  * The hash includes the relative path plus every byte of each file, so a
16
22
  * same-bytes rename still yields a fresh id. We trim to 16 chars because the
17
- * full 64 isn't useful in an object key for legibility. Top-level segments in
18
- * {@link PLAN_PROJECT_DIR_SKIP_SEGMENTS} (e.g. `node_modules`) are skipped to
19
- * match what the plan stage copies.
23
+ * full 64 isn't useful in an object key for legibility. Pass `files` when the
24
+ * caller already listed them, so the hashed set is the shipped set.
20
25
  *
21
26
  * Reads are synchronous: project trees are typically tens of MB at most
22
27
  * (HTML/CSS/JS plus a few composition assets), so the simpler shape wins over
23
28
  * a streaming pipeline.
24
29
  */
25
- export declare function hashProjectDir(projectDir: string): string;
30
+ export declare function hashProjectDir(projectDir: string, files?: readonly string[]): string;
@@ -157,6 +157,8 @@ export interface CompileForRenderOptions {
157
157
  * mismatch warning). Optional so non-render callers can omit it.
158
158
  */
159
159
  log?: ProducerLogger;
160
+ /** Absolute project paths not on disk, readable at these URLs instead. */
161
+ urlByAbsentLocalPath?: ReadonlyMap<string, string>;
160
162
  /**
161
163
  * Threaded through to {@link injectDeterministicFontFaces}. When `true`,
162
164
  * deterministic font resolution and exhausted transient fetch failures
@@ -33,6 +33,7 @@ export interface AudioStageInput {
33
33
  /** Read-only view of `composition.audios`. */
34
34
  audios: CompositionMetadata["audios"];
35
35
  masterFxChain?: string;
36
+ urlByAbsentLocalPath?: ReadonlyMap<string, string>;
36
37
  abortSignal: AbortSignal | undefined;
37
38
  assertNotAborted: () => void;
38
39
  /** Where a per-track failure's detail goes. Optional so tests need not pass one. */
@@ -78,6 +78,8 @@ export interface CompileStageInput {
78
78
  allowSystemFontCapture?: boolean;
79
79
  /** Render-time variable overrides (`--variables`); see CompileForRenderOptions.variables. */
80
80
  variables?: Record<string, unknown>;
81
+ /** Absolute project paths not on disk, readable at these URLs instead. */
82
+ urlByAbsentLocalPath?: ReadonlyMap<string, string>;
81
83
  }
82
84
  export interface CompileStageResult {
83
85
  compiled: CompiledComposition;
@@ -51,6 +51,7 @@ export interface ExtractVideosStageInput {
51
51
  */
52
52
  materializeSymlinks?: boolean;
53
53
  deferRangeExtraction?: boolean;
54
+ urlByAbsentLocalPath?: ReadonlyMap<string, string>;
54
55
  }
55
56
  export interface ExtractVideosStageResult {
56
57
  /** Result of `extractAllVideoFrames`, or `null` if the composition has no videos. */
@@ -121,6 +121,14 @@ export type PlanFileDigests = Map<string, string>;
121
121
  export declare function planFileSha256(absolutePath: string, digests?: PlanFileDigests): string;
122
122
  /** Hash every file under `dir` into `digests` without blocking the event loop. */
123
123
  export declare function prehashPlanFiles(dir: string, digests: PlanFileDigests): Promise<void>;
124
+ /** Plan file left in remote storage: hashed and published by digest, never copied into the plan. */
125
+ export interface PlanRemoteFile {
126
+ readonly path: string;
127
+ readonly sha256: string;
128
+ readonly sizeBytes: number;
129
+ }
130
+ export declare function writePlanRemoteFiles(planDir: string, files: readonly PlanRemoteFile[]): void;
131
+ export declare function readPlanRemoteFiles(planDir: string): PlanRemoteFile[];
124
132
  /**
125
133
  * Read a frozen plan directory back from disk and recompute its
126
134
  * content-addressed `planHash` over the actual on-disk bytes — including
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperframes/producer",
3
- "version": "0.8.177",
3
+ "version": "0.8.179",
4
4
  "description": "HTML-to-video rendering engine using Chrome's BeginFrame API",
5
5
  "repository": {
6
6
  "type": "git",
@@ -35,11 +35,11 @@
35
35
  "postcss": "^8.4.0",
36
36
  "puppeteer": "^25.2.1",
37
37
  "puppeteer-core": "^25.2.1",
38
- "@hyperframes/engine": "0.8.177",
39
- "@hyperframes/lint": "0.8.177",
40
- "@hyperframes/parsers": "0.8.177",
41
- "@hyperframes/studio-server": "0.8.177",
42
- "@hyperframes/core": "0.8.177"
38
+ "@hyperframes/core": "0.8.179",
39
+ "@hyperframes/engine": "0.8.179",
40
+ "@hyperframes/lint": "0.8.179",
41
+ "@hyperframes/studio-server": "0.8.179",
42
+ "@hyperframes/parsers": "0.8.179"
43
43
  },
44
44
  "devDependencies": {
45
45
  "@types/node": "^25.0.10",
@@ -48,7 +48,7 @@
48
48
  "tsx": "^4.21.0",
49
49
  "typescript": "^5.7.2",
50
50
  "vitest": "^4.1.11",
51
- "@hyperframes/aws-lambda": "0.8.177"
51
+ "@hyperframes/aws-lambda": "0.8.179"
52
52
  },
53
53
  "engines": {
54
54
  "node": ">=22"
@@ -1,38 +0,0 @@
1
- /**
2
- * Audio Extractor Service
3
- *
4
- * Extracts audio from media elements in the composition HTML,
5
- * applies timeline positioning, and mixes into a single audio track.
6
- */
7
- export interface AudioElement {
8
- id: string;
9
- src: string;
10
- start: number;
11
- duration: number;
12
- mediaStart: number;
13
- volume: number;
14
- tagName: "audio" | "video";
15
- }
16
- export interface AudioTrack {
17
- id: string;
18
- srcPath: string;
19
- start: number;
20
- duration: number;
21
- mediaStart: number;
22
- volume: number;
23
- }
24
- /**
25
- * Parse audio/video elements from HTML to find media with audio.
26
- */
27
- export declare function parseAudioElements(html: string): AudioElement[];
28
- /**
29
- * Process all audio for a composition.
30
- *
31
- * @param htmlPath - Path to the composition HTML (for parsing media elements)
32
- * @param projectDir - Base directory for resolving relative media paths
33
- * @param workDir - Temporary working directory for intermediate files
34
- * @param outputPath - Final mixed audio output path
35
- * @param totalDuration - Total composition duration in seconds
36
- * @returns true if audio was produced, false if no audio elements found
37
- */
38
- export declare function processAudio(htmlPath: string, projectDir: string, workDir: string, outputPath: string, totalDuration: number): Promise<boolean>;