@hyperframes/engine 0.9.26 → 0.9.27
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.
|
@@ -3,11 +3,12 @@
|
|
|
3
3
|
* so long audio is encoded as frame-aligned segments in parallel and their frames joined.
|
|
4
4
|
*/
|
|
5
5
|
import { execFile } from "node:child_process";
|
|
6
|
-
import {
|
|
6
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
7
7
|
import { removeScratch } from "@hyperframes/core/remove-scratch";
|
|
8
8
|
import { dirname, join } from "node:path";
|
|
9
9
|
import { getFfmpegBinary } from "../utils/ffmpegBinaries.js";
|
|
10
10
|
import { runFfmpeg } from "../utils/runFfmpeg.js";
|
|
11
|
+
import { createStagingDir } from "@hyperframes/core/atomic-file";
|
|
11
12
|
const SAMPLE_RATE = 48_000;
|
|
12
13
|
const FRAME_SAMPLES = 1024;
|
|
13
14
|
const MIN_SEGMENT_SECONDS = 60;
|
|
@@ -78,7 +79,7 @@ export async function encodeAac(input, outputPath, totalDuration, { signal, time
|
|
|
78
79
|
if (!(await supportsSettsDuration())) {
|
|
79
80
|
return encodeInOnePass(input, outputPath, { signal, timeout, filter });
|
|
80
81
|
}
|
|
81
|
-
const dir =
|
|
82
|
+
const dir = createStagingDir(dirname(outputPath), ".aac-segments-");
|
|
82
83
|
try {
|
|
83
84
|
const segments = planSegments(totalDuration, count).map((segment, k) => ({
|
|
84
85
|
...segment,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// fallow-ignore-file complexity code-duplication
|
|
2
2
|
import { isSelfOrAncestorHidden, memberGroupKey, isMemberGroupHidden } from "./mediaHidden.js";
|
|
3
|
-
import { closeSync, existsSync, mkdirSync,
|
|
3
|
+
import { closeSync, existsSync, mkdirSync, openSync, rmSync, writeFileSync } from "fs";
|
|
4
4
|
import { renameWithRetry } from "@hyperframes/core/rename-retry";
|
|
5
5
|
import { removeScratch } from "@hyperframes/core/remove-scratch";
|
|
6
6
|
import { join, dirname, isAbsolute, relative } from "path";
|
|
@@ -28,6 +28,7 @@ import { chainTailSeconds } from "@hyperframes/core/audio-fx-tail";
|
|
|
28
28
|
import { isAudibleVideoElement, MEDIA_RENDER_ID_ATTR, normalizePlaybackRate, normalizeRateSpec, parseStrictFiniteTimingNumber, readElementRateSpec, readMediaStart, sourceTimeAt, timeAtSourceTime, } from "@hyperframes/core";
|
|
29
29
|
import { readMasterFxChain, resolveAudioGroups } from "@hyperframes/core/audio-groups";
|
|
30
30
|
import { applyAudioFxChain, AudioFxRenderError } from "./audioFxRender.js";
|
|
31
|
+
import { createStagingDir } from "@hyperframes/core/atomic-file";
|
|
31
32
|
/** An MP4-family container records AAC priming as an edit list; a raw .aac plays ~21 ms late.
|
|
32
33
|
* The video mux, the distributed plan and the PNG-sequence sidecar all read this one name. */
|
|
33
34
|
export const MIXED_AUDIO_FILENAME = "audio.m4a";
|
|
@@ -539,7 +540,7 @@ async function extractAudioSegment(srcPath, outputPath, stage, mediaStart, durat
|
|
|
539
540
|
});
|
|
540
541
|
const loopDuration = (metadata?.durationSeconds ?? 0) - mediaStart;
|
|
541
542
|
if (options?.loop && loopDuration > 0 && sourceDuration > loopDuration) {
|
|
542
|
-
const cycleDir =
|
|
543
|
+
const cycleDir = createStagingDir(outputDir, ".audio-loop-");
|
|
543
544
|
const cyclePath = join(cycleDir, "cycle.wav");
|
|
544
545
|
try {
|
|
545
546
|
const cycle = await extractAudioSegment(srcPath, cyclePath, stage, mediaStart, loopDuration, 1, signal, config, audioSidecarUrl, { padToDuration: true });
|
|
@@ -746,7 +747,7 @@ async function mixAudioTracksFlat(tracks, outputPath, totalDuration, signal, con
|
|
|
746
747
|
const runMix = async (ignoreAutomation, useNormalize) => {
|
|
747
748
|
const inputs = [];
|
|
748
749
|
tracks.forEach((track) => inputs.push("-i", track.srcPath));
|
|
749
|
-
const scriptDir =
|
|
750
|
+
const scriptDir = createStagingDir(outputDir, ".filter-complex-");
|
|
750
751
|
const scriptPath = join(scriptDir, "graph.txt");
|
|
751
752
|
const fd = openSync(scriptPath, "wx", 0o600);
|
|
752
753
|
try {
|
|
@@ -898,7 +899,7 @@ async function mixGroupMembersFlat(memberTracks, outputPath, totalDuration, sign
|
|
|
898
899
|
// global track count.
|
|
899
900
|
`${mixInputs}amix=inputs=${memberTracks.length}:duration=longest:dropout_transition=0[mixed];[mixed]volume=${formatFilterNumber(memberTracks.length)}[out]`;
|
|
900
901
|
const filterComplex = [...inputFilters, mixFilter].join(";");
|
|
901
|
-
const scriptDir =
|
|
902
|
+
const scriptDir = createStagingDir(outputDir, ".group-filter-complex-");
|
|
902
903
|
const scriptPath = join(scriptDir, "graph.txt");
|
|
903
904
|
const fd = openSync(scriptPath, "wx", 0o600);
|
|
904
905
|
try {
|
|
@@ -1013,7 +1014,7 @@ async function mixGroupMembers(memberTracks, outputPath, totalDuration, signal,
|
|
|
1013
1014
|
return mixGroupMembersFlat(memberTracks, outputPath, totalDuration, signal, config);
|
|
1014
1015
|
}
|
|
1015
1016
|
mkdirSync(dirname(outputPath), { recursive: true });
|
|
1016
|
-
const scratchDir =
|
|
1017
|
+
const scratchDir = createStagingDir(dirname(outputPath), ".mix-batches-");
|
|
1017
1018
|
try {
|
|
1018
1019
|
const summed = await sumInSlices(memberTracks, scratchDir, totalDuration, signal, config);
|
|
1019
1020
|
if (!summed.success)
|
|
@@ -1030,7 +1031,7 @@ async function mixAudioTracks(tracks, outputPath, totalDuration, signal, config)
|
|
|
1030
1031
|
return mixAudioTracksFlat(tracks, outputPath, totalDuration, signal, config);
|
|
1031
1032
|
}
|
|
1032
1033
|
mkdirSync(dirname(outputPath), { recursive: true });
|
|
1033
|
-
const scratchDir =
|
|
1034
|
+
const scratchDir = createStagingDir(dirname(outputPath), ".mix-batches-");
|
|
1034
1035
|
try {
|
|
1035
1036
|
const summed = await sumInSlices(tracks, scratchDir, totalDuration, signal, config);
|
|
1036
1037
|
if (!summed.success) {
|
|
@@ -14,11 +14,12 @@
|
|
|
14
14
|
* `extractAudioSegment`). Anything else is rejected so
|
|
15
15
|
* the caller can fall back to the expression path rather than corrupting audio.
|
|
16
16
|
*/
|
|
17
|
-
import {
|
|
17
|
+
import { readFileSync, rmSync, writeFileSync } from "fs";
|
|
18
18
|
import { renameWithRetry } from "@hyperframes/core/rename-retry";
|
|
19
19
|
import { dirname, join } from "path";
|
|
20
20
|
import { normaliseEnvelope } from "@hyperframes/core/media-volume-envelope";
|
|
21
21
|
import { riffChunks, wavFormatTag } from "./wavChunks.js";
|
|
22
|
+
import { createStagingDir } from "@hyperframes/core/atomic-file";
|
|
22
23
|
const PCM_FORMAT = 1; // WAVE_FORMAT_PCM
|
|
23
24
|
const FLOAT_FORMAT = 3; // WAVE_FORMAT_IEEE_FLOAT
|
|
24
25
|
/** The `fmt ` chunk, or null for a format this cannot safely edit in place. */
|
|
@@ -129,7 +130,7 @@ export function applyVolumeEnvelopeToWav(wavPath, keyframes, trackStart, baseVol
|
|
|
129
130
|
scaleSamples(buffer, layout, gainAt);
|
|
130
131
|
// A private sibling directory owns the staging file; the same-filesystem
|
|
131
132
|
// rename keeps a failed write from truncating the original WAV.
|
|
132
|
-
stagingDir =
|
|
133
|
+
stagingDir = createStagingDir(dirname(wavPath), ".hf-volume-");
|
|
133
134
|
const tempPath = join(stagingDir, "audio.wav");
|
|
134
135
|
writeFileSync(tempPath, buffer, { flag: "wx" });
|
|
135
136
|
renameWithRetry(tempPath, wavPath);
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* Supports CPU (libx264) and GPU encoding.
|
|
7
7
|
*/
|
|
8
8
|
import { frameSequencePath } from "./extractedFrameIndex.js";
|
|
9
|
-
import { closeSync, copyFileSync, existsSync, ftruncateSync, mkdirSync,
|
|
9
|
+
import { closeSync, copyFileSync, existsSync, ftruncateSync, mkdirSync, openSync, readFileSync, readdirSync, statSync, writeFileSync, writeSync, } from "fs";
|
|
10
10
|
import { join, dirname, extname, resolve } from "path";
|
|
11
11
|
import { DEFAULT_CONFIG } from "../config.js";
|
|
12
12
|
import { getCachedGpuEncoder, getCachedNvencPresets, getGpuEncoderName, buildVideoToolboxRateControlArgs, mapPresetForGpuEncoder, } from "../utils/gpuEncoder.js";
|
|
@@ -19,6 +19,7 @@ import { fpsToFfmpegArg, fpsToNumber } from "@hyperframes/core";
|
|
|
19
19
|
import { appendVp9CpuUsedArg } from "./vp9Options.js";
|
|
20
20
|
import { appendSoftwareEncoderThreadsArg } from "./softwareEncoderThreads.js";
|
|
21
21
|
import { appendRenderProvenanceArgs } from "../utils/renderProvenance.js";
|
|
22
|
+
import { createStagingDir } from "@hyperframes/core/atomic-file";
|
|
22
23
|
export const ENCODER_PRESETS = {
|
|
23
24
|
draft: { preset: "ultrafast", quality: 28, codec: "h264" },
|
|
24
25
|
standard: { preset: "medium", quality: 18, codec: "h264" },
|
|
@@ -553,7 +554,7 @@ export async function encodeFramesChunkedConcat(framesDir, framePattern, outputP
|
|
|
553
554
|
mkdirSync(dirname(outputPath), { recursive: true });
|
|
554
555
|
// Keep intermediates under the caller-owned output directory for its existing
|
|
555
556
|
// cleanup/debug policy, but never reuse another invocation's chunk files.
|
|
556
|
-
const chunkDir =
|
|
557
|
+
const chunkDir = createStagingDir(dirname(outputPath), "chunk-encode-");
|
|
557
558
|
const chunkPaths = [];
|
|
558
559
|
for (let i = 0; i < chunkCount; i++) {
|
|
559
560
|
if (signal?.aborted) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { closeSync, createReadStream, createWriteStream, existsSync, fsyncSync, linkSync,
|
|
1
|
+
import { closeSync, createReadStream, createWriteStream, existsSync, fsyncSync, linkSync, mkdirSync, lstatSync, openSync, readdirSync, rmdirSync, rmSync, statSync, unlinkSync, } from "fs";
|
|
2
2
|
import { createHash, randomUUID } from "crypto";
|
|
3
3
|
import dns from "node:dns";
|
|
4
4
|
import { fetchGoesThroughEnvProxy } from "@hyperframes/core/env-proxy";
|
|
@@ -6,6 +6,7 @@ import { isBlockedNetworkHost } from "./networkPolicy.js";
|
|
|
6
6
|
import { dirname, extname, join } from "path";
|
|
7
7
|
import { Readable, Transform } from "stream";
|
|
8
8
|
import { pipeline } from "stream/promises";
|
|
9
|
+
import { createStagingDir } from "@hyperframes/core/atomic-file";
|
|
9
10
|
const inFlightDownloads = new Map();
|
|
10
11
|
const signalScopes = new WeakMap();
|
|
11
12
|
let nextSignalScope = 1;
|
|
@@ -763,7 +764,7 @@ function emitDownloadTelemetry(options, event) {
|
|
|
763
764
|
async function runDownloadAttempt(url, localPath, timeoutMs, attempt, options, signal) {
|
|
764
765
|
// A private, unguessable directory prevents symlink planting and keeps the
|
|
765
766
|
// partial on the destination filesystem so the final rename stays atomic.
|
|
766
|
-
const attemptDir =
|
|
767
|
+
const attemptDir = createStagingDir(dirname(localPath), ".hf-download-");
|
|
767
768
|
const partialPath = join(attemptDir, "payload");
|
|
768
769
|
const controller = new AbortController();
|
|
769
770
|
let timedOut = false;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperframes/engine",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.27",
|
|
4
4
|
"description": "Seekable web page to video rendering engine (Puppeteer + FFmpeg)",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -61,8 +61,8 @@
|
|
|
61
61
|
"puppeteer": "~25.11.0",
|
|
62
62
|
"puppeteer-core": "^25.11.0",
|
|
63
63
|
"undici": "7.25.0",
|
|
64
|
-
"@hyperframes/
|
|
65
|
-
"@hyperframes/
|
|
64
|
+
"@hyperframes/core": "^0.9.27",
|
|
65
|
+
"@hyperframes/parsers": "^0.9.27"
|
|
66
66
|
},
|
|
67
67
|
"devDependencies": {
|
|
68
68
|
"@types/node": "^25.0.10",
|