@hyperframes/producer 0.8.190 → 0.8.192
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.
- package/dist/distributed.d.ts +1 -0
- package/dist/distributed.js +86 -5
- package/dist/hyperframe.manifest.json +1 -1
- package/dist/hyperframe.runtime.iife.js +64 -64
- package/dist/index.js +59 -13
- package/dist/public-server.js +46 -12
- package/dist/services/distributed/audioSidecar.d.ts +10 -0
- package/dist/services/distributed/plan.d.ts +2 -0
- package/dist/services/render/artifactTransaction.d.ts +14 -11
- package/dist/services/render/stages/audioStage.d.ts +1 -0
- package/dist/services/render/stages/extractVideosStage.d.ts +17 -2
- package/package.json +7 -7
package/dist/distributed.d.ts
CHANGED
|
@@ -39,6 +39,7 @@ export { muxChunkWithAudio } from "./services/distributed/chunkAudio.js";
|
|
|
39
39
|
export { buildStitchedHeader, readMp4Layout, type Mp4Layout, type ReadRange, } from "./services/distributed/mp4Stitch.js";
|
|
40
40
|
export { InvalidConfigError, type SerializableDistributedRenderConfig, validateDistributedRenderConfig, validateVariablesPayload, } from "./services/distributed/renderConfigValidation.js";
|
|
41
41
|
export { hashProjectDir, listSiteFiles } from "./services/distributed/projectHash.js";
|
|
42
|
+
export { AUDIO_SIDECAR_MIN_BYTES, buildAudioSidecar, wantsAudioSidecar, type AudioSidecarOutcome, } from "./services/distributed/audioSidecar.js";
|
|
42
43
|
export { CURRENT_PLAN_PROTOCOL, DISTRIBUTED_RENDER_CAPABILITIES, getDistributedRenderCapabilities, PLAN_ARTIFACT_LAYOUT, PLAN_HASH_SCHEMA, PLAN_PROTOCOL_V1, PLAN_PROTOCOL_V2, PLAN_PROTOCOL_UNSUPPORTED, PLAN_SCHEMA_VERSION, PLAN_V2_ARTIFACT_LAYOUT, PLAN_V2_HASH_SCHEMA, PLAN_V2_SCHEMA_VERSION, PlanProtocolUnsupportedError, readPlanProtocol, readPlanProtocolV1, type DistributedRenderCapabilities, type PlanProtocolConsumerCapabilities, type PlanProtocolDescriptor, type PlanProtocolV1Descriptor, type PlanProtocolV2Descriptor, type SupportedPlanProtocolDescriptor, } from "./services/distributed/planProtocol.js";
|
|
43
44
|
export { PlanVideosMetadataError, type DistributedFormat } from "./services/distributed/shared.js";
|
|
44
45
|
export { isPlanAudioArtifactPath, PLAN_AUDIO_LEGACY_RELATIVE_PATH, PLAN_AUDIO_RELATIVE_PATH, resolvePlanAudioPath, } from "./services/distributed/shared.js";
|
package/dist/distributed.js
CHANGED
|
@@ -18,7 +18,8 @@ import {
|
|
|
18
18
|
import {
|
|
19
19
|
getEncoderPreset as getEncoderPreset4,
|
|
20
20
|
normalizeVp9CpuUsed,
|
|
21
|
-
resolveConfig as resolveConfig3
|
|
21
|
+
resolveConfig as resolveConfig3,
|
|
22
|
+
declareContentAddressedUrl
|
|
22
23
|
} from "@hyperframes/engine";
|
|
23
24
|
|
|
24
25
|
// src/logger.ts
|
|
@@ -3395,7 +3396,7 @@ async function compileForRender(projectDir, htmlPath, downloadDir, options = {})
|
|
|
3395
3396
|
);
|
|
3396
3397
|
if (analysis.isProblematic) {
|
|
3397
3398
|
defaultLogger.warn(
|
|
3398
|
-
`[Compiler] WARNING: Video "${video.id}" has sparse keyframes (max interval: ${analysis.maxIntervalSeconds}s).
|
|
3399
|
+
`[Compiler] WARNING: Video "${video.id}" has sparse keyframes (max interval: ${analysis.maxIntervalSeconds}s). Long GOPs can make preview seeking slower; rendering is unaffected. If preview seeking is slow, re-encode with: ${reencode}`
|
|
3399
3400
|
);
|
|
3400
3401
|
}
|
|
3401
3402
|
if (metadata.isVFR) {
|
|
@@ -4281,6 +4282,10 @@ async function runProbeStage(input) {
|
|
|
4281
4282
|
if (el.loop && !existing.loop) {
|
|
4282
4283
|
existing.loop = true;
|
|
4283
4284
|
}
|
|
4285
|
+
const audio = composition.audios.find(
|
|
4286
|
+
(audio2) => audio2.type === "video" && audio2.id === `${el.id}-audio`
|
|
4287
|
+
);
|
|
4288
|
+
if (audio) audio.loop = existing.loop;
|
|
4284
4289
|
}
|
|
4285
4290
|
} else if (!compiled.droppedMediaIds.has(el.id)) {
|
|
4286
4291
|
composition.videos.push({
|
|
@@ -4318,6 +4323,7 @@ async function runProbeStage(input) {
|
|
|
4318
4323
|
el,
|
|
4319
4324
|
authoredStarts.get(el.id) ?? existing.mediaStart
|
|
4320
4325
|
);
|
|
4326
|
+
existing.loop = el.loop;
|
|
4321
4327
|
if (el.volume > 0 && Math.abs((existing.volume ?? 1) - el.volume) > BROWSER_MEDIA_EPSILON) {
|
|
4322
4328
|
existing.volume = el.volume;
|
|
4323
4329
|
}
|
|
@@ -4329,6 +4335,7 @@ async function runProbeStage(input) {
|
|
|
4329
4335
|
start: el.start,
|
|
4330
4336
|
end: resolveBrowserMediaEnd(el.start, el.end, el.duration),
|
|
4331
4337
|
mediaStart: el.mediaStart,
|
|
4338
|
+
loop: el.loop,
|
|
4332
4339
|
layer: 0,
|
|
4333
4340
|
volume: el.volume,
|
|
4334
4341
|
type: "audio"
|
|
@@ -4583,6 +4590,10 @@ import {
|
|
|
4583
4590
|
extractMediaMetadata as extractMediaMetadata3,
|
|
4584
4591
|
isVideoPastTimelineEnd,
|
|
4585
4592
|
isHdrColorSpace as isHdrColorSpace2,
|
|
4593
|
+
rebaseVideoToWindow,
|
|
4594
|
+
resolveFinalFrameExtractionWindow as resolveFinalFrameExtractionWindow2,
|
|
4595
|
+
resolveVideoExtractionWindow as resolveVideoExtractionWindow2,
|
|
4596
|
+
isHttpUrl as isHttpUrl2,
|
|
4586
4597
|
resolveProjectRelativeSrc as resolveProjectRelativeSrc3,
|
|
4587
4598
|
runVideoExtractionWithRetry,
|
|
4588
4599
|
safeVideoExtractionSourceIdentity
|
|
@@ -4948,6 +4959,8 @@ function appendAutoDetectedVideoAudio(composition, extracted) {
|
|
|
4948
4959
|
start: video.start,
|
|
4949
4960
|
end: video.end,
|
|
4950
4961
|
mediaStart: video.mediaStart,
|
|
4962
|
+
playbackRate: video.playbackRate,
|
|
4963
|
+
loop: video.loop,
|
|
4951
4964
|
layer: 0,
|
|
4952
4965
|
volume: 1,
|
|
4953
4966
|
type: "video"
|
|
@@ -4955,6 +4968,33 @@ function appendAutoDetectedVideoAudio(composition, extracted) {
|
|
|
4955
4968
|
existingAudioSrcs.add(video.src);
|
|
4956
4969
|
}
|
|
4957
4970
|
}
|
|
4971
|
+
async function appendVideoAudioFromProbe(input) {
|
|
4972
|
+
const { projectDir, compiledDir, composition, urlByAbsentLocalPath } = input;
|
|
4973
|
+
const videos = composition.videos.filter(
|
|
4974
|
+
(video) => !isVideoPastTimelineEnd(video, composition.duration)
|
|
4975
|
+
);
|
|
4976
|
+
const probed = await Promise.all(
|
|
4977
|
+
videos.map(async (video) => {
|
|
4978
|
+
const path = isHttpUrl2(video.src) ? video.src : resolveProjectRelativeSrc3(video.src, projectDir, compiledDir);
|
|
4979
|
+
const readFrom = urlByAbsentLocalPath?.get(path) ?? path;
|
|
4980
|
+
if (!isHttpUrl2(readFrom) && !existsSync7(readFrom)) return null;
|
|
4981
|
+
const metadata = await extractMediaMetadata3(readFrom);
|
|
4982
|
+
const window2 = await resolveFinalFrameExtractionWindow2(
|
|
4983
|
+
readFrom,
|
|
4984
|
+
video,
|
|
4985
|
+
metadata,
|
|
4986
|
+
resolveVideoExtractionWindow2(video, metadata, composition.duration)
|
|
4987
|
+
);
|
|
4988
|
+
if (window2.durationSeconds <= 0) return null;
|
|
4989
|
+
rebaseVideoToWindow(video, window2);
|
|
4990
|
+
return { videoId: video.id, metadata };
|
|
4991
|
+
})
|
|
4992
|
+
);
|
|
4993
|
+
appendAutoDetectedVideoAudio(
|
|
4994
|
+
composition,
|
|
4995
|
+
probed.filter((entry) => entry !== null)
|
|
4996
|
+
);
|
|
4997
|
+
}
|
|
4958
4998
|
|
|
4959
4999
|
// src/services/render/stages/audioStage.ts
|
|
4960
5000
|
import { join as join10 } from "node:path";
|
|
@@ -5446,6 +5486,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
|
|
|
5446
5486
|
audios,
|
|
5447
5487
|
masterFxChain,
|
|
5448
5488
|
urlByAbsentLocalPath,
|
|
5489
|
+
audioSidecarUrlByLocalPath,
|
|
5449
5490
|
abortSignal,
|
|
5450
5491
|
assertNotAborted,
|
|
5451
5492
|
log
|
|
@@ -5469,6 +5510,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
|
|
|
5469
5510
|
audioGain,
|
|
5470
5511
|
masterFxChain,
|
|
5471
5512
|
urlByAbsentLocalPath,
|
|
5513
|
+
audioSidecarUrlByLocalPath,
|
|
5472
5514
|
keepEncoderInputAt: pcmSourcePath
|
|
5473
5515
|
},
|
|
5474
5516
|
compiledDir
|
|
@@ -8577,12 +8619,18 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
|
|
|
8577
8619
|
const needsAlpha = outputNeedsAlpha(config.format);
|
|
8578
8620
|
const remoteFiles = Object.entries(options.remoteFiles ?? {});
|
|
8579
8621
|
const remoteLocalPath = (rel) => join21(projectDir, ...rel.split("/"));
|
|
8622
|
+
for (const [, file] of remoteFiles) declareContentAddressedUrl(file.url, file.sha256);
|
|
8580
8623
|
const urlByAbsentLocalPath = new Map(
|
|
8581
8624
|
remoteFiles.map(([rel, file]) => [remoteLocalPath(rel), file.url])
|
|
8582
8625
|
);
|
|
8583
8626
|
const localPathByUrl = new Map(
|
|
8584
8627
|
remoteFiles.map(([rel, file]) => [file.url, remoteLocalPath(rel)])
|
|
8585
8628
|
);
|
|
8629
|
+
const audioSidecarUrlByLocalPath = new Map(
|
|
8630
|
+
remoteFiles.flatMap(
|
|
8631
|
+
([rel, file]) => file.audioSidecarUrl ? [[remoteLocalPath(rel), file.audioSidecarUrl]] : []
|
|
8632
|
+
)
|
|
8633
|
+
);
|
|
8586
8634
|
stageStarted = Date.now();
|
|
8587
8635
|
const compileResult = await runCompileStage({
|
|
8588
8636
|
urlByAbsentLocalPath,
|
|
@@ -8667,6 +8715,7 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
|
|
|
8667
8715
|
finalCompiledDir,
|
|
8668
8716
|
remoteFiles,
|
|
8669
8717
|
urlByAbsentLocalPath,
|
|
8718
|
+
audioSidecarUrlByLocalPath,
|
|
8670
8719
|
localPathByUrl,
|
|
8671
8720
|
compileResult,
|
|
8672
8721
|
compiled,
|
|
@@ -8684,6 +8733,7 @@ function mixPlanAudio(c, compiledDir) {
|
|
|
8684
8733
|
workDir: c.workDir,
|
|
8685
8734
|
compiledDir,
|
|
8686
8735
|
urlByAbsentLocalPath: c.urlByAbsentLocalPath,
|
|
8736
|
+
audioSidecarUrlByLocalPath: c.audioSidecarUrlByLocalPath,
|
|
8687
8737
|
totalFrames: c.totalFrames,
|
|
8688
8738
|
fps: c.job.config.fps,
|
|
8689
8739
|
ffmpegProcessTimeout: c.cfg.ffmpegProcessTimeout,
|
|
@@ -8968,7 +9018,7 @@ async function buildPlanAudio(projectDir, config, workRoot, outputPath, options
|
|
|
8968
9018
|
});
|
|
8969
9019
|
try {
|
|
8970
9020
|
let stageStarted = Date.now();
|
|
8971
|
-
await
|
|
9021
|
+
await appendVideoAudioFromProbe(c);
|
|
8972
9022
|
logPlanStage(c.log, "extract", stageStarted);
|
|
8973
9023
|
stageStarted = Date.now();
|
|
8974
9024
|
const mixed = await mixPlanAudio(c, c.compiledDir);
|
|
@@ -10036,7 +10086,7 @@ function cfrEncodeArgs(inputArgs, encoder, fpsArg) {
|
|
|
10036
10086
|
"-r",
|
|
10037
10087
|
fpsArg
|
|
10038
10088
|
];
|
|
10039
|
-
appendSoftwareEncoderThreadsArg(args);
|
|
10089
|
+
appendSoftwareEncoderThreadsArg(args, "libx264");
|
|
10040
10090
|
return args;
|
|
10041
10091
|
}
|
|
10042
10092
|
async function reencodeCfr(ctx, chunkPaths, encoder) {
|
|
@@ -11164,7 +11214,36 @@ function hashProjectDir(projectDir, files = listSiteFiles(projectDir)) {
|
|
|
11164
11214
|
}
|
|
11165
11215
|
return hash.digest("hex").slice(0, 16);
|
|
11166
11216
|
}
|
|
11217
|
+
|
|
11218
|
+
// src/services/distributed/audioSidecar.ts
|
|
11219
|
+
import { extractAudioMetadata as extractAudioMetadata4, runFfmpeg as runFfmpeg8 } from "@hyperframes/engine";
|
|
11220
|
+
import { extname as extname5 } from "node:path";
|
|
11221
|
+
var AUDIO_SIDECAR_MIN_BYTES = 32 * 1024 * 1024;
|
|
11222
|
+
var AUDIO_SIDECAR_SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".mp4", ".mov", ".m4v"]);
|
|
11223
|
+
function wantsAudioSidecar(path, sizeBytes) {
|
|
11224
|
+
return sizeBytes >= AUDIO_SIDECAR_MIN_BYTES && AUDIO_SIDECAR_SOURCE_EXTENSIONS.has(extname5(path).toLowerCase());
|
|
11225
|
+
}
|
|
11226
|
+
async function buildAudioSidecar(inputPath, outputPath, signal) {
|
|
11227
|
+
const audio = await extractAudioMetadata4(inputPath, { signal }).catch(() => null);
|
|
11228
|
+
if (!audio) return await ffmpegIsMissing(signal) ? "unavailable" : "none";
|
|
11229
|
+
if (audio.audioStreamCount !== 1 || (audio.audioStreamLeadSeconds ?? 0) > 0) return "none";
|
|
11230
|
+
const result = await runFfmpeg8(
|
|
11231
|
+
["-v", "error", "-y", "-i", inputPath, "-map", "0:a:0", "-vn", "-c:a", "copy"].concat([
|
|
11232
|
+
"-movflags",
|
|
11233
|
+
"+faststart",
|
|
11234
|
+
"-f",
|
|
11235
|
+
"mp4",
|
|
11236
|
+
outputPath
|
|
11237
|
+
]),
|
|
11238
|
+
{ signal }
|
|
11239
|
+
);
|
|
11240
|
+
if (result.success) return "built";
|
|
11241
|
+
return isMissing(result) ? "unavailable" : "none";
|
|
11242
|
+
}
|
|
11243
|
+
var isMissing = (result) => result.error?.code === "ENOENT";
|
|
11244
|
+
var ffmpegIsMissing = async (signal) => isMissing(await runFfmpeg8(["-version"], { signal }));
|
|
11167
11245
|
export {
|
|
11246
|
+
AUDIO_SIDECAR_MIN_BYTES,
|
|
11168
11247
|
CHUNK_EXCEEDS_WORKER_TIMEOUT,
|
|
11169
11248
|
CHUNK_FRAME_RANGE_INVALID,
|
|
11170
11249
|
CURRENT_PLAN_PROTOCOL,
|
|
@@ -11207,6 +11286,7 @@ export {
|
|
|
11207
11286
|
applyRuntimeEnvSnapshot,
|
|
11208
11287
|
assemble,
|
|
11209
11288
|
assembleV2,
|
|
11289
|
+
buildAudioSidecar,
|
|
11210
11290
|
buildChunkSlices,
|
|
11211
11291
|
buildPlanAudio,
|
|
11212
11292
|
buildStitchedHeader,
|
|
@@ -11242,5 +11322,6 @@ export {
|
|
|
11242
11322
|
stitchesSeparateAudio,
|
|
11243
11323
|
validateDistributedRenderConfig,
|
|
11244
11324
|
validatePlanV2MaterializedTarget,
|
|
11245
|
-
validateVariablesPayload
|
|
11325
|
+
validateVariablesPayload,
|
|
11326
|
+
wantsAudioSidecar
|
|
11246
11327
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": "0.1.0",
|
|
3
3
|
"buildId": "dev",
|
|
4
|
-
"sha256": "
|
|
4
|
+
"sha256": "a44a8127a863733eafcb4dfa80f2517a3a74266a32bbaf072be4b7401ea46c42",
|
|
5
5
|
"artifacts": {
|
|
6
6
|
"iife": "hyperframe.runtime.iife.js",
|
|
7
7
|
"esm": "hyperframe.runtime.mjs"
|