hyperframes 0.6.115 → 0.6.117
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/cli.js
CHANGED
|
@@ -50,7 +50,7 @@ var VERSION;
|
|
|
50
50
|
var init_version = __esm({
|
|
51
51
|
"src/version.ts"() {
|
|
52
52
|
"use strict";
|
|
53
|
-
VERSION = true ? "0.6.
|
|
53
|
+
VERSION = true ? "0.6.117" : "0.0.0-dev";
|
|
54
54
|
}
|
|
55
55
|
});
|
|
56
56
|
|
|
@@ -32382,6 +32382,25 @@ var init_systemMemory = __esm({
|
|
|
32382
32382
|
}
|
|
32383
32383
|
});
|
|
32384
32384
|
|
|
32385
|
+
// ../engine/src/services/vp9Options.ts
|
|
32386
|
+
function normalizeVp9CpuUsed(value) {
|
|
32387
|
+
if (value === void 0 || !Number.isFinite(value)) return DEFAULT_VP9_CPU_USED;
|
|
32388
|
+
const integer = Math.trunc(value);
|
|
32389
|
+
return Math.max(MIN_VP9_CPU_USED, Math.min(MAX_VP9_CPU_USED, integer));
|
|
32390
|
+
}
|
|
32391
|
+
function appendVp9CpuUsedArg(args, value) {
|
|
32392
|
+
args.push("-cpu-used", String(normalizeVp9CpuUsed(value)));
|
|
32393
|
+
}
|
|
32394
|
+
var DEFAULT_VP9_CPU_USED, MIN_VP9_CPU_USED, MAX_VP9_CPU_USED;
|
|
32395
|
+
var init_vp9Options = __esm({
|
|
32396
|
+
"../engine/src/services/vp9Options.ts"() {
|
|
32397
|
+
"use strict";
|
|
32398
|
+
DEFAULT_VP9_CPU_USED = 4;
|
|
32399
|
+
MIN_VP9_CPU_USED = -8;
|
|
32400
|
+
MAX_VP9_CPU_USED = 8;
|
|
32401
|
+
}
|
|
32402
|
+
});
|
|
32403
|
+
|
|
32385
32404
|
// ../engine/src/config.ts
|
|
32386
32405
|
function memoryAdaptiveCacheLimit() {
|
|
32387
32406
|
const total = getSystemTotalMb();
|
|
@@ -32408,6 +32427,11 @@ function resolveConfig(overrides) {
|
|
|
32408
32427
|
if (raw === void 0) return fallback;
|
|
32409
32428
|
return raw === "true";
|
|
32410
32429
|
};
|
|
32430
|
+
const envVp9CpuUsed = () => {
|
|
32431
|
+
const raw = env("PRODUCER_VP9_CPU_USED");
|
|
32432
|
+
if (raw === void 0 || raw === "") return DEFAULT_CONFIG2.vp9CpuUsed;
|
|
32433
|
+
return normalizeVp9CpuUsed(Number(raw));
|
|
32434
|
+
};
|
|
32411
32435
|
const envBrowserGpuMode = () => {
|
|
32412
32436
|
const raw = env("PRODUCER_BROWSER_GPU_MODE");
|
|
32413
32437
|
if (raw === "hardware" || raw === "software" || raw === "auto") return raw;
|
|
@@ -32448,6 +32472,7 @@ function resolveConfig(overrides) {
|
|
|
32448
32472
|
"HF_PAGE_SIDE_COMPOSITING",
|
|
32449
32473
|
DEFAULT_CONFIG2.enablePageSideCompositing
|
|
32450
32474
|
),
|
|
32475
|
+
vp9CpuUsed: envVp9CpuUsed(),
|
|
32451
32476
|
enableChunkedEncode: envBool(
|
|
32452
32477
|
"PRODUCER_ENABLE_CHUNKED_ENCODE",
|
|
32453
32478
|
DEFAULT_CONFIG2.enableChunkedEncode
|
|
@@ -32505,17 +32530,22 @@ function resolveConfig(overrides) {
|
|
|
32505
32530
|
extractCacheDir: env("HYPERFRAMES_EXTRACT_CACHE_DIR")
|
|
32506
32531
|
};
|
|
32507
32532
|
const cleanEnv = Object.fromEntries(Object.entries(fromEnv).filter(([, v2]) => v2 !== void 0));
|
|
32508
|
-
|
|
32533
|
+
const merged = {
|
|
32509
32534
|
...DEFAULT_CONFIG2,
|
|
32510
32535
|
...cleanEnv,
|
|
32511
32536
|
...overrides
|
|
32512
32537
|
};
|
|
32538
|
+
return {
|
|
32539
|
+
...merged,
|
|
32540
|
+
vp9CpuUsed: normalizeVp9CpuUsed(merged.vp9CpuUsed)
|
|
32541
|
+
};
|
|
32513
32542
|
}
|
|
32514
32543
|
var DEFAULT_CONFIG2;
|
|
32515
32544
|
var init_config2 = __esm({
|
|
32516
32545
|
"../engine/src/config.ts"() {
|
|
32517
32546
|
"use strict";
|
|
32518
32547
|
init_systemMemory();
|
|
32548
|
+
init_vp9Options();
|
|
32519
32549
|
DEFAULT_CONFIG2 = {
|
|
32520
32550
|
fps: 30,
|
|
32521
32551
|
quality: "standard",
|
|
@@ -32536,6 +32566,7 @@ var init_config2 = __esm({
|
|
|
32536
32566
|
// DEFAULT_CONFIG (used directly by tests and worker-sizing fallbacks).
|
|
32537
32567
|
lowMemoryMode: false,
|
|
32538
32568
|
enablePageSideCompositing: true,
|
|
32569
|
+
vp9CpuUsed: DEFAULT_VP9_CPU_USED,
|
|
32539
32570
|
enableChunkedEncode: false,
|
|
32540
32571
|
chunkSizeFrames: 360,
|
|
32541
32572
|
enableStreamingEncode: true,
|
|
@@ -34837,15 +34868,322 @@ var init_runFfmpeg = __esm({
|
|
|
34837
34868
|
}
|
|
34838
34869
|
});
|
|
34839
34870
|
|
|
34840
|
-
// ../engine/src/
|
|
34871
|
+
// ../engine/src/utils/ffprobe.ts
|
|
34841
34872
|
import { spawn as spawn3 } from "child_process";
|
|
34873
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
34874
|
+
import { extname } from "path";
|
|
34875
|
+
function runFfprobe(args) {
|
|
34876
|
+
return new Promise((resolve58, reject) => {
|
|
34877
|
+
const command2 = getFfprobeBinary();
|
|
34878
|
+
const proc = spawn3(command2, args);
|
|
34879
|
+
let stdout2 = "";
|
|
34880
|
+
let stderr = "";
|
|
34881
|
+
proc.stdout.on("data", (data2) => {
|
|
34882
|
+
stdout2 += data2.toString();
|
|
34883
|
+
});
|
|
34884
|
+
proc.stderr.on("data", (data2) => {
|
|
34885
|
+
stderr += data2.toString();
|
|
34886
|
+
});
|
|
34887
|
+
proc.on("close", (code) => {
|
|
34888
|
+
if (code !== 0) {
|
|
34889
|
+
reject(new Error(`[FFmpeg] ffprobe exited with code ${code}: ${stderr}`));
|
|
34890
|
+
} else {
|
|
34891
|
+
resolve58(stdout2);
|
|
34892
|
+
}
|
|
34893
|
+
});
|
|
34894
|
+
proc.on("error", (err) => {
|
|
34895
|
+
if (err.code === "ENOENT") {
|
|
34896
|
+
const configured = process.env[FFPROBE_PATH_ENV]?.trim();
|
|
34897
|
+
reject(
|
|
34898
|
+
new Error(
|
|
34899
|
+
configured ? `[FFmpeg] ffprobe not found at ${FFPROBE_PATH_ENV}="${configured}". Please install FFmpeg.` : "[FFmpeg] ffprobe not found. Please install FFmpeg."
|
|
34900
|
+
)
|
|
34901
|
+
);
|
|
34902
|
+
} else {
|
|
34903
|
+
reject(err);
|
|
34904
|
+
}
|
|
34905
|
+
});
|
|
34906
|
+
});
|
|
34907
|
+
}
|
|
34908
|
+
function parseProbeJson(stdout2) {
|
|
34909
|
+
try {
|
|
34910
|
+
return JSON.parse(stdout2);
|
|
34911
|
+
} catch (e3) {
|
|
34912
|
+
throw new Error(
|
|
34913
|
+
`[FFmpeg] Failed to parse ffprobe output: ${e3 instanceof Error ? e3.message : e3}`
|
|
34914
|
+
);
|
|
34915
|
+
}
|
|
34916
|
+
}
|
|
34917
|
+
function crc32(buf) {
|
|
34918
|
+
let crc = 4294967295;
|
|
34919
|
+
for (let i2 = 0; i2 < buf.length; i2++) {
|
|
34920
|
+
crc ^= buf[i2] ?? 0;
|
|
34921
|
+
for (let bit = 0; bit < 8; bit++) {
|
|
34922
|
+
const mask = -(crc & 1);
|
|
34923
|
+
crc = crc >>> 1 ^ 3988292384 & mask;
|
|
34924
|
+
}
|
|
34925
|
+
}
|
|
34926
|
+
return (crc ^ 4294967295) >>> 0;
|
|
34927
|
+
}
|
|
34928
|
+
function extractPngMetadataFromBuffer(buf) {
|
|
34929
|
+
if (buf.length < 8 || buf[0] !== 137 || buf[1] !== 80 || buf[2] !== 78 || buf[3] !== 71 || buf[4] !== 13 || buf[5] !== 10 || buf[6] !== 26 || buf[7] !== 10) {
|
|
34930
|
+
return null;
|
|
34931
|
+
}
|
|
34932
|
+
let width = 0;
|
|
34933
|
+
let height = 0;
|
|
34934
|
+
let seenIdat = false;
|
|
34935
|
+
let pos = 8;
|
|
34936
|
+
while (pos + 12 <= buf.length) {
|
|
34937
|
+
const chunkLen = buf.readUInt32BE(pos);
|
|
34938
|
+
const chunkType = buf.toString("ascii", pos + 4, pos + 8);
|
|
34939
|
+
if (pos + 12 + chunkLen > buf.length) return null;
|
|
34940
|
+
const chunkData = buf.subarray(pos + 8, pos + 8 + chunkLen);
|
|
34941
|
+
const chunkCrc = buf.readUInt32BE(pos + 8 + chunkLen);
|
|
34942
|
+
const chunkBytes = Buffer.concat([Buffer.from(chunkType, "ascii"), chunkData]);
|
|
34943
|
+
if (crc32(chunkBytes) !== chunkCrc) return null;
|
|
34944
|
+
if (chunkType === "IHDR" && chunkLen >= 8) {
|
|
34945
|
+
width = buf.readUInt32BE(pos + 8);
|
|
34946
|
+
height = buf.readUInt32BE(pos + 12);
|
|
34947
|
+
}
|
|
34948
|
+
if (chunkType === "IDAT") {
|
|
34949
|
+
seenIdat = true;
|
|
34950
|
+
}
|
|
34951
|
+
if (chunkType === "cICP" && chunkLen === 4 && !seenIdat) {
|
|
34952
|
+
const primariesCode = chunkData[0] ?? 0;
|
|
34953
|
+
const transferCode = chunkData[1] ?? 0;
|
|
34954
|
+
const matrixCode = chunkData[2] ?? 0;
|
|
34955
|
+
return {
|
|
34956
|
+
width,
|
|
34957
|
+
height,
|
|
34958
|
+
colorSpace: {
|
|
34959
|
+
colorPrimaries: primariesCode === 9 ? "bt2020" : primariesCode === 1 ? "bt709" : `unknown-${primariesCode}`,
|
|
34960
|
+
colorTransfer: transferCode === 16 ? "smpte2084" : transferCode === 18 ? "arib-std-b67" : transferCode === 1 ? "bt709" : `unknown-${transferCode}`,
|
|
34961
|
+
colorSpace: matrixCode === 9 ? "bt2020nc" : matrixCode === 0 ? "gbr" : `unknown-${matrixCode}`
|
|
34962
|
+
}
|
|
34963
|
+
};
|
|
34964
|
+
}
|
|
34965
|
+
if (chunkType === "IEND") break;
|
|
34966
|
+
pos += 12 + chunkLen;
|
|
34967
|
+
}
|
|
34968
|
+
return width > 0 && height > 0 ? { width, height, colorSpace: null } : null;
|
|
34969
|
+
}
|
|
34970
|
+
function extractStillImageMetadata(filePath) {
|
|
34971
|
+
if (extname(filePath).toLowerCase() !== ".png") return null;
|
|
34972
|
+
try {
|
|
34973
|
+
return extractPngMetadataFromBuffer(readFileSync3(filePath));
|
|
34974
|
+
} catch {
|
|
34975
|
+
return null;
|
|
34976
|
+
}
|
|
34977
|
+
}
|
|
34978
|
+
function readTagCI(tags, name) {
|
|
34979
|
+
if (!tags) return "";
|
|
34980
|
+
const target = name.toLowerCase();
|
|
34981
|
+
for (const [key2, value] of Object.entries(tags)) {
|
|
34982
|
+
if (key2.toLowerCase() === target && typeof value === "string") return value;
|
|
34983
|
+
}
|
|
34984
|
+
return "";
|
|
34985
|
+
}
|
|
34986
|
+
function parseFrameRate(frameRateStr) {
|
|
34987
|
+
if (!frameRateStr) return 0;
|
|
34988
|
+
const parts = frameRateStr.split("/");
|
|
34989
|
+
if (parts.length === 2) {
|
|
34990
|
+
const num = parseFloat(parts[0] ?? "");
|
|
34991
|
+
const den = parseFloat(parts[1] ?? "");
|
|
34992
|
+
if (den !== 0) return Math.round(num / den * 100) / 100;
|
|
34993
|
+
}
|
|
34994
|
+
return parseFloat(frameRateStr) || 0;
|
|
34995
|
+
}
|
|
34996
|
+
async function extractMediaMetadata(filePath) {
|
|
34997
|
+
const cached2 = videoMetadataCache.get(filePath);
|
|
34998
|
+
if (cached2) return cached2;
|
|
34999
|
+
const probePromise = (async () => {
|
|
35000
|
+
const stillImageMeta = extractStillImageMetadata(filePath);
|
|
35001
|
+
let output = null;
|
|
35002
|
+
try {
|
|
35003
|
+
const stdout2 = await runFfprobe([
|
|
35004
|
+
"-v",
|
|
35005
|
+
"quiet",
|
|
35006
|
+
"-print_format",
|
|
35007
|
+
"json",
|
|
35008
|
+
"-show_format",
|
|
35009
|
+
"-show_streams",
|
|
35010
|
+
filePath
|
|
35011
|
+
]);
|
|
35012
|
+
output = parseProbeJson(stdout2);
|
|
35013
|
+
} catch (error) {
|
|
35014
|
+
if (!stillImageMeta) throw error;
|
|
35015
|
+
}
|
|
35016
|
+
const videoStream = output?.streams.find((s2) => s2.codec_type === "video");
|
|
35017
|
+
if (!videoStream) {
|
|
35018
|
+
if (stillImageMeta) {
|
|
35019
|
+
return {
|
|
35020
|
+
durationSeconds: 0,
|
|
35021
|
+
videoStreamDurationSeconds: 0,
|
|
35022
|
+
width: stillImageMeta.width,
|
|
35023
|
+
height: stillImageMeta.height,
|
|
35024
|
+
fps: 0,
|
|
35025
|
+
videoCodec: "png",
|
|
35026
|
+
hasAudio: false,
|
|
35027
|
+
isVFR: false,
|
|
35028
|
+
hasAlpha: false,
|
|
35029
|
+
colorSpace: stillImageMeta.colorSpace
|
|
35030
|
+
};
|
|
35031
|
+
}
|
|
35032
|
+
throw new Error("[FFmpeg] No video stream found");
|
|
35033
|
+
}
|
|
35034
|
+
const rFps = parseFrameRate(videoStream.r_frame_rate);
|
|
35035
|
+
const avgFps = parseFrameRate(videoStream.avg_frame_rate);
|
|
35036
|
+
const fps = avgFps || rFps;
|
|
35037
|
+
const isVFR = rFps > 0 && avgFps > 0 && Math.abs(rFps - avgFps) / Math.max(rFps, avgFps) > 0.1;
|
|
35038
|
+
const colorTransfer = videoStream.color_transfer || "";
|
|
35039
|
+
const colorPrimaries = videoStream.color_primaries || "";
|
|
35040
|
+
const colorSpaceVal = videoStream.color_space || "";
|
|
35041
|
+
const ffprobeColorSpace = colorTransfer || colorPrimaries || colorSpaceVal ? { colorTransfer, colorPrimaries, colorSpace: colorSpaceVal } : null;
|
|
35042
|
+
const colorSpace = ffprobeColorSpace ?? stillImageMeta?.colorSpace ?? null;
|
|
35043
|
+
const pixelFormat = videoStream.pix_fmt || "";
|
|
35044
|
+
const alphaMode = readTagCI(videoStream.tags, "alpha_mode");
|
|
35045
|
+
const hasAlpha = /(^|[^a-z])yuva|rgba|argb|bgra|gbrap|gray[a-z0-9]*a/i.test(pixelFormat) || alphaMode === "1";
|
|
35046
|
+
const containerDuration = output?.format.duration ? parseFloat(output.format.duration) : 0;
|
|
35047
|
+
const streamDuration = videoStream.duration ? parseFloat(videoStream.duration) : 0;
|
|
35048
|
+
return {
|
|
35049
|
+
durationSeconds: containerDuration,
|
|
35050
|
+
videoStreamDurationSeconds: streamDuration > 0 ? streamDuration : containerDuration,
|
|
35051
|
+
width: videoStream.width || stillImageMeta?.width || 0,
|
|
35052
|
+
height: videoStream.height || stillImageMeta?.height || 0,
|
|
35053
|
+
fps,
|
|
35054
|
+
videoCodec: videoStream.codec_name || "unknown",
|
|
35055
|
+
hasAudio: output?.streams.some((s2) => s2.codec_type === "audio") ?? false,
|
|
35056
|
+
isVFR,
|
|
35057
|
+
hasAlpha,
|
|
35058
|
+
colorSpace
|
|
35059
|
+
};
|
|
35060
|
+
})();
|
|
35061
|
+
videoMetadataCache.set(filePath, probePromise);
|
|
35062
|
+
probePromise.catch(() => {
|
|
35063
|
+
if (videoMetadataCache.get(filePath) === probePromise) {
|
|
35064
|
+
videoMetadataCache.delete(filePath);
|
|
35065
|
+
}
|
|
35066
|
+
});
|
|
35067
|
+
return probePromise;
|
|
35068
|
+
}
|
|
35069
|
+
async function extractAudioMetadata(filePath) {
|
|
35070
|
+
const cached2 = audioMetadataCache.get(filePath);
|
|
35071
|
+
if (cached2) return cached2;
|
|
35072
|
+
const probePromise = (async () => {
|
|
35073
|
+
const stdout2 = await runFfprobe([
|
|
35074
|
+
"-v",
|
|
35075
|
+
"quiet",
|
|
35076
|
+
"-print_format",
|
|
35077
|
+
"json",
|
|
35078
|
+
"-show_format",
|
|
35079
|
+
"-show_streams",
|
|
35080
|
+
filePath
|
|
35081
|
+
]);
|
|
35082
|
+
const output = parseProbeJson(stdout2);
|
|
35083
|
+
const audioStream = output.streams.find((s2) => s2.codec_type === "audio");
|
|
35084
|
+
if (!audioStream) throw new Error("[FFmpeg] No audio stream found");
|
|
35085
|
+
const durationSeconds = output.format.duration ? parseFloat(output.format.duration) : 0;
|
|
35086
|
+
return {
|
|
35087
|
+
durationSeconds,
|
|
35088
|
+
sampleRate: audioStream.sample_rate ? parseInt(audioStream.sample_rate) : 44100,
|
|
35089
|
+
channels: audioStream.channels || 2,
|
|
35090
|
+
audioCodec: audioStream.codec_name || "unknown",
|
|
35091
|
+
bitrate: output.format.bit_rate ? parseInt(output.format.bit_rate) : void 0
|
|
35092
|
+
};
|
|
35093
|
+
})();
|
|
35094
|
+
audioMetadataCache.set(filePath, probePromise);
|
|
35095
|
+
probePromise.catch(() => {
|
|
35096
|
+
if (audioMetadataCache.get(filePath) === probePromise) {
|
|
35097
|
+
audioMetadataCache.delete(filePath);
|
|
35098
|
+
}
|
|
35099
|
+
});
|
|
35100
|
+
return probePromise;
|
|
35101
|
+
}
|
|
35102
|
+
async function analyzeKeyframeIntervals(filePath) {
|
|
35103
|
+
const cached2 = keyframeCache.get(filePath);
|
|
35104
|
+
if (cached2) return cached2;
|
|
35105
|
+
const promise = analyzeKeyframeIntervalsUncached(filePath);
|
|
35106
|
+
keyframeCache.set(filePath, promise);
|
|
35107
|
+
promise.catch(() => {
|
|
35108
|
+
if (keyframeCache.get(filePath) === promise) {
|
|
35109
|
+
keyframeCache.delete(filePath);
|
|
35110
|
+
}
|
|
35111
|
+
});
|
|
35112
|
+
return promise;
|
|
35113
|
+
}
|
|
35114
|
+
async function analyzeKeyframeIntervalsUncached(filePath) {
|
|
35115
|
+
const stdout2 = await runFfprobe([
|
|
35116
|
+
"-v",
|
|
35117
|
+
"quiet",
|
|
35118
|
+
"-select_streams",
|
|
35119
|
+
"v:0",
|
|
35120
|
+
"-skip_frame",
|
|
35121
|
+
"nokey",
|
|
35122
|
+
"-show_entries",
|
|
35123
|
+
"frame=pts_time",
|
|
35124
|
+
"-of",
|
|
35125
|
+
"csv=p=0",
|
|
35126
|
+
filePath
|
|
35127
|
+
]);
|
|
35128
|
+
const timestamps = stdout2.split("\n").map((line) => parseFloat(line.trim())).filter((t2) => Number.isFinite(t2));
|
|
35129
|
+
if (timestamps.length < 2) {
|
|
35130
|
+
return {
|
|
35131
|
+
avgIntervalSeconds: 0,
|
|
35132
|
+
maxIntervalSeconds: 0,
|
|
35133
|
+
keyframeCount: timestamps.length,
|
|
35134
|
+
isProblematic: false
|
|
35135
|
+
};
|
|
35136
|
+
}
|
|
35137
|
+
let maxInterval = 0;
|
|
35138
|
+
let totalInterval = 0;
|
|
35139
|
+
for (let i2 = 1; i2 < timestamps.length; i2++) {
|
|
35140
|
+
const interval = (timestamps[i2] ?? 0) - (timestamps[i2 - 1] ?? 0);
|
|
35141
|
+
totalInterval += interval;
|
|
35142
|
+
if (interval > maxInterval) maxInterval = interval;
|
|
35143
|
+
}
|
|
35144
|
+
const avgInterval = totalInterval / (timestamps.length - 1);
|
|
35145
|
+
return {
|
|
35146
|
+
avgIntervalSeconds: Math.round(avgInterval * 100) / 100,
|
|
35147
|
+
maxIntervalSeconds: Math.round(maxInterval * 100) / 100,
|
|
35148
|
+
keyframeCount: timestamps.length,
|
|
35149
|
+
isProblematic: maxInterval > 2
|
|
35150
|
+
};
|
|
35151
|
+
}
|
|
35152
|
+
var videoMetadataCache, audioMetadataCache, extractVideoMetadata, keyframeCache;
|
|
35153
|
+
var init_ffprobe = __esm({
|
|
35154
|
+
"../engine/src/utils/ffprobe.ts"() {
|
|
35155
|
+
"use strict";
|
|
35156
|
+
init_ffmpegBinaries();
|
|
35157
|
+
videoMetadataCache = /* @__PURE__ */ new Map();
|
|
35158
|
+
audioMetadataCache = /* @__PURE__ */ new Map();
|
|
35159
|
+
extractVideoMetadata = extractMediaMetadata;
|
|
35160
|
+
keyframeCache = /* @__PURE__ */ new Map();
|
|
35161
|
+
}
|
|
35162
|
+
});
|
|
35163
|
+
|
|
35164
|
+
// ../engine/src/services/chunkEncoder.ts
|
|
35165
|
+
import { spawn as spawn4 } from "child_process";
|
|
34842
35166
|
import { copyFileSync, existsSync as existsSync6, mkdirSync as mkdirSync3, readdirSync as readdirSync2, statSync, writeFileSync as writeFileSync3 } from "fs";
|
|
34843
|
-
import { join as join6, dirname as dirname4 } from "path";
|
|
35167
|
+
import { join as join6, dirname as dirname4, extname as extname2 } from "path";
|
|
34844
35168
|
function appendEncodeTimeoutMessage(error, timedOut, timeoutMs) {
|
|
34845
35169
|
if (!timedOut) return error;
|
|
34846
35170
|
return `${error}
|
|
34847
35171
|
FFmpeg killed after exceeding ffmpegEncodeTimeout (${timeoutMs} ms)`;
|
|
34848
35172
|
}
|
|
35173
|
+
function isAacSidecar(audioPath) {
|
|
35174
|
+
return extname2(audioPath).toLowerCase() === ".aac";
|
|
35175
|
+
}
|
|
35176
|
+
async function shouldCopyAacSidecar(audioPath, options) {
|
|
35177
|
+
if (options?.audioCodec === "aac" || isAacSidecar(audioPath)) return true;
|
|
35178
|
+
const audioExtension = extname2(audioPath).toLowerCase();
|
|
35179
|
+
if (KNOWN_NON_AAC_AUDIO_EXTENSIONS.has(audioExtension)) return false;
|
|
35180
|
+
try {
|
|
35181
|
+
const metadata = await extractAudioMetadata(audioPath);
|
|
35182
|
+
return metadata.audioCodec === "aac";
|
|
35183
|
+
} catch {
|
|
35184
|
+
return false;
|
|
35185
|
+
}
|
|
35186
|
+
}
|
|
34849
35187
|
function getEncoderPreset(quality, format = "mp4", hdr) {
|
|
34850
35188
|
const base2 = ENCODER_PRESETS[quality];
|
|
34851
35189
|
if (format === "webm") {
|
|
@@ -34883,6 +35221,7 @@ function buildEncoderArgs(options, inputArgs, outputPath, gpuEncoder = null) {
|
|
|
34883
35221
|
quality = 23,
|
|
34884
35222
|
bitrate,
|
|
34885
35223
|
pixelFormat = "yuv420p",
|
|
35224
|
+
vp9CpuUsed,
|
|
34886
35225
|
useGpu = false
|
|
34887
35226
|
} = options;
|
|
34888
35227
|
if (options.hdr && codec === "h264") {
|
|
@@ -34985,6 +35324,7 @@ function buildEncoderArgs(options, inputArgs, outputPath, gpuEncoder = null) {
|
|
|
34985
35324
|
args.push("-c:v", "libvpx-vp9", "-b:v", bitrate || "0", "-crf", String(quality));
|
|
34986
35325
|
args.push("-deadline", preset === "ultrafast" ? "realtime" : "good");
|
|
34987
35326
|
args.push("-row-mt", "1");
|
|
35327
|
+
appendVp9CpuUsedArg(args, vp9CpuUsed);
|
|
34988
35328
|
const lockGopVp9 = options.lockGopForChunkConcat === true;
|
|
34989
35329
|
if (lockGopVp9) {
|
|
34990
35330
|
if (typeof options.gopSize !== "number" || !Number.isFinite(options.gopSize) || options.gopSize <= 0) {
|
|
@@ -34993,16 +35333,7 @@ function buildEncoderArgs(options, inputArgs, outputPath, gpuEncoder = null) {
|
|
|
34993
35333
|
);
|
|
34994
35334
|
}
|
|
34995
35335
|
const gop = Math.floor(options.gopSize);
|
|
34996
|
-
args.push(
|
|
34997
|
-
"-g",
|
|
34998
|
-
String(gop),
|
|
34999
|
-
"-keyint_min",
|
|
35000
|
-
String(gop),
|
|
35001
|
-
"-auto-alt-ref",
|
|
35002
|
-
"0",
|
|
35003
|
-
"-cpu-used",
|
|
35004
|
-
"2"
|
|
35005
|
-
);
|
|
35336
|
+
args.push("-g", String(gop), "-keyint_min", String(gop), "-auto-alt-ref", "0");
|
|
35006
35337
|
}
|
|
35007
35338
|
if (pixelFormat === "yuva420p") {
|
|
35008
35339
|
if (!lockGopVp9) {
|
|
@@ -35081,7 +35412,7 @@ async function encodeFramesFromDir(framesDir, framePattern, outputPath, options,
|
|
|
35081
35412
|
const inputArgs = ["-framerate", fpsToFfmpegArg(options.fps), "-i", inputPath];
|
|
35082
35413
|
const args = buildEncoderArgs(options, inputArgs, outputPath, gpuEncoder);
|
|
35083
35414
|
return new Promise((resolve58) => {
|
|
35084
|
-
const ffmpeg =
|
|
35415
|
+
const ffmpeg = spawn4(getFfmpegBinary(), args);
|
|
35085
35416
|
trackChildProcess(ffmpeg);
|
|
35086
35417
|
let stderr = "";
|
|
35087
35418
|
const onAbort = () => {
|
|
@@ -35198,7 +35529,7 @@ async function encodeFramesChunkedConcat(framesDir, framePattern, outputPath, op
|
|
|
35198
35529
|
if (options.useGpu) gpuEncoder = await getCachedGpuEncoder();
|
|
35199
35530
|
const args = buildEncoderArgs(options, inputArgs, chunkPath, gpuEncoder);
|
|
35200
35531
|
const chunkResult = await new Promise((resolve58) => {
|
|
35201
|
-
const ffmpeg =
|
|
35532
|
+
const ffmpeg = spawn4(getFfmpegBinary(), args);
|
|
35202
35533
|
trackChildProcess(ffmpeg);
|
|
35203
35534
|
let stderr = "";
|
|
35204
35535
|
const encodeTimeout = config?.ffmpegEncodeTimeout ?? DEFAULT_CONFIG2.ffmpegEncodeTimeout;
|
|
@@ -35264,7 +35595,7 @@ async function encodeFramesChunkedConcat(framesDir, framePattern, outputPath, op
|
|
|
35264
35595
|
outputPath
|
|
35265
35596
|
];
|
|
35266
35597
|
const concatResult = await new Promise((resolve58) => {
|
|
35267
|
-
const ffmpeg =
|
|
35598
|
+
const ffmpeg = spawn4(getFfmpegBinary(), concatArgs);
|
|
35268
35599
|
trackChildProcess(ffmpeg);
|
|
35269
35600
|
let stderr = "";
|
|
35270
35601
|
const encodeTimeout = config?.ffmpegEncodeTimeout ?? DEFAULT_CONFIG2.ffmpegEncodeTimeout;
|
|
@@ -35326,13 +35657,22 @@ async function muxVideoWithAudio(videoPath, audioPath, outputPath, signal, confi
|
|
|
35326
35657
|
if (!existsSync6(outputDir)) mkdirSync3(outputDir, { recursive: true });
|
|
35327
35658
|
const isWebm = outputPath.endsWith(".webm");
|
|
35328
35659
|
const isMov = outputPath.endsWith(".mov");
|
|
35660
|
+
const shouldCopyAudio = isWebm ? false : await shouldCopyAacSidecar(audioPath, config);
|
|
35329
35661
|
const args = ["-i", videoPath, "-i", audioPath, "-c:v", "copy"];
|
|
35330
35662
|
if (isWebm) {
|
|
35331
35663
|
args.push("-c:a", "libopus", "-b:a", "128k");
|
|
35332
35664
|
} else if (isMov) {
|
|
35333
|
-
|
|
35665
|
+
if (shouldCopyAudio) {
|
|
35666
|
+
args.push("-c:a", "copy");
|
|
35667
|
+
} else {
|
|
35668
|
+
args.push("-c:a", "aac", "-b:a", "192k");
|
|
35669
|
+
}
|
|
35334
35670
|
} else {
|
|
35335
|
-
|
|
35671
|
+
if (shouldCopyAudio) {
|
|
35672
|
+
args.push("-c:a", "copy", "-movflags", "+faststart");
|
|
35673
|
+
} else {
|
|
35674
|
+
args.push("-c:a", "aac", "-b:a", "192k", "-movflags", "+faststart");
|
|
35675
|
+
}
|
|
35336
35676
|
}
|
|
35337
35677
|
args.push("-avoid_negative_ts", "make_zero");
|
|
35338
35678
|
if (fps !== void 0) {
|
|
@@ -35383,7 +35723,7 @@ async function applyFaststart(inputPath, outputPath, signal, config, fps) {
|
|
|
35383
35723
|
error: !result.success ? formatFfmpegError(result.exitCode, result.stderr) : void 0
|
|
35384
35724
|
};
|
|
35385
35725
|
}
|
|
35386
|
-
var ENCODER_PRESETS;
|
|
35726
|
+
var ENCODER_PRESETS, KNOWN_NON_AAC_AUDIO_EXTENSIONS;
|
|
35387
35727
|
var init_chunkEncoder = __esm({
|
|
35388
35728
|
"../engine/src/services/chunkEncoder.ts"() {
|
|
35389
35729
|
"use strict";
|
|
@@ -35393,18 +35733,29 @@ var init_chunkEncoder = __esm({
|
|
|
35393
35733
|
init_hdr();
|
|
35394
35734
|
init_runFfmpeg();
|
|
35395
35735
|
init_ffmpegBinaries();
|
|
35736
|
+
init_ffprobe();
|
|
35396
35737
|
init_src();
|
|
35738
|
+
init_vp9Options();
|
|
35397
35739
|
init_gpuEncoder();
|
|
35398
35740
|
ENCODER_PRESETS = {
|
|
35399
35741
|
draft: { preset: "ultrafast", quality: 28, codec: "h264" },
|
|
35400
35742
|
standard: { preset: "medium", quality: 18, codec: "h264" },
|
|
35401
35743
|
high: { preset: "slow", quality: 15, codec: "h264" }
|
|
35402
35744
|
};
|
|
35745
|
+
KNOWN_NON_AAC_AUDIO_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
35746
|
+
".flac",
|
|
35747
|
+
".mp3",
|
|
35748
|
+
".oga",
|
|
35749
|
+
".ogg",
|
|
35750
|
+
".opus",
|
|
35751
|
+
".wav",
|
|
35752
|
+
".webm"
|
|
35753
|
+
]);
|
|
35403
35754
|
}
|
|
35404
35755
|
});
|
|
35405
35756
|
|
|
35406
35757
|
// ../engine/src/services/streamingEncoder.ts
|
|
35407
|
-
import { spawn as
|
|
35758
|
+
import { spawn as spawn5 } from "child_process";
|
|
35408
35759
|
import { once } from "events";
|
|
35409
35760
|
import { existsSync as existsSync7, mkdirSync as mkdirSync4, statSync as statSync2 } from "fs";
|
|
35410
35761
|
import { dirname as dirname5 } from "path";
|
|
@@ -35453,6 +35804,7 @@ function buildStreamingArgs(options, outputPath, gpuEncoder = null) {
|
|
|
35453
35804
|
quality = 23,
|
|
35454
35805
|
bitrate,
|
|
35455
35806
|
pixelFormat = "yuv420p",
|
|
35807
|
+
vp9CpuUsed,
|
|
35456
35808
|
useGpu = false,
|
|
35457
35809
|
imageFormat = "jpeg"
|
|
35458
35810
|
} = options;
|
|
@@ -35559,6 +35911,7 @@ function buildStreamingArgs(options, outputPath, gpuEncoder = null) {
|
|
|
35559
35911
|
args.push("-c:v", "libvpx-vp9", "-b:v", bitrate || "0", "-crf", String(quality));
|
|
35560
35912
|
args.push("-deadline", preset === "ultrafast" ? "realtime" : "good");
|
|
35561
35913
|
args.push("-row-mt", "1");
|
|
35914
|
+
appendVp9CpuUsedArg(args, vp9CpuUsed);
|
|
35562
35915
|
if (pixelFormat === "yuva420p") {
|
|
35563
35916
|
args.push("-auto-alt-ref", "0");
|
|
35564
35917
|
args.push("-metadata:s:v:0", "alpha_mode=1");
|
|
@@ -35619,7 +35972,7 @@ async function spawnStreamingEncoder(outputPath, options, signal, config) {
|
|
|
35619
35972
|
}
|
|
35620
35973
|
const args = buildStreamingArgs(options, outputPath, gpuEncoder);
|
|
35621
35974
|
const startTime = Date.now();
|
|
35622
|
-
const ffmpeg =
|
|
35975
|
+
const ffmpeg = spawn5(getFfmpegBinary(), args, {
|
|
35623
35976
|
stdio: ["pipe", "pipe", "pipe"]
|
|
35624
35977
|
});
|
|
35625
35978
|
trackChildProcess(ffmpeg);
|
|
@@ -35758,306 +36111,14 @@ var init_streamingEncoder = __esm({
|
|
|
35758
36111
|
init_hdr();
|
|
35759
36112
|
init_config2();
|
|
35760
36113
|
init_src();
|
|
35761
|
-
|
|
35762
|
-
});
|
|
35763
|
-
|
|
35764
|
-
// ../engine/src/utils/ffprobe.ts
|
|
35765
|
-
import { spawn as spawn5 } from "child_process";
|
|
35766
|
-
import { readFileSync as readFileSync3 } from "fs";
|
|
35767
|
-
import { extname } from "path";
|
|
35768
|
-
function runFfprobe(args) {
|
|
35769
|
-
return new Promise((resolve58, reject) => {
|
|
35770
|
-
const command2 = getFfprobeBinary();
|
|
35771
|
-
const proc = spawn5(command2, args);
|
|
35772
|
-
let stdout2 = "";
|
|
35773
|
-
let stderr = "";
|
|
35774
|
-
proc.stdout.on("data", (data2) => {
|
|
35775
|
-
stdout2 += data2.toString();
|
|
35776
|
-
});
|
|
35777
|
-
proc.stderr.on("data", (data2) => {
|
|
35778
|
-
stderr += data2.toString();
|
|
35779
|
-
});
|
|
35780
|
-
proc.on("close", (code) => {
|
|
35781
|
-
if (code !== 0) {
|
|
35782
|
-
reject(new Error(`[FFmpeg] ffprobe exited with code ${code}: ${stderr}`));
|
|
35783
|
-
} else {
|
|
35784
|
-
resolve58(stdout2);
|
|
35785
|
-
}
|
|
35786
|
-
});
|
|
35787
|
-
proc.on("error", (err) => {
|
|
35788
|
-
if (err.code === "ENOENT") {
|
|
35789
|
-
const configured = process.env[FFPROBE_PATH_ENV]?.trim();
|
|
35790
|
-
reject(
|
|
35791
|
-
new Error(
|
|
35792
|
-
configured ? `[FFmpeg] ffprobe not found at ${FFPROBE_PATH_ENV}="${configured}". Please install FFmpeg.` : "[FFmpeg] ffprobe not found. Please install FFmpeg."
|
|
35793
|
-
)
|
|
35794
|
-
);
|
|
35795
|
-
} else {
|
|
35796
|
-
reject(err);
|
|
35797
|
-
}
|
|
35798
|
-
});
|
|
35799
|
-
});
|
|
35800
|
-
}
|
|
35801
|
-
function parseProbeJson(stdout2) {
|
|
35802
|
-
try {
|
|
35803
|
-
return JSON.parse(stdout2);
|
|
35804
|
-
} catch (e3) {
|
|
35805
|
-
throw new Error(
|
|
35806
|
-
`[FFmpeg] Failed to parse ffprobe output: ${e3 instanceof Error ? e3.message : e3}`
|
|
35807
|
-
);
|
|
35808
|
-
}
|
|
35809
|
-
}
|
|
35810
|
-
function crc32(buf) {
|
|
35811
|
-
let crc = 4294967295;
|
|
35812
|
-
for (let i2 = 0; i2 < buf.length; i2++) {
|
|
35813
|
-
crc ^= buf[i2] ?? 0;
|
|
35814
|
-
for (let bit = 0; bit < 8; bit++) {
|
|
35815
|
-
const mask = -(crc & 1);
|
|
35816
|
-
crc = crc >>> 1 ^ 3988292384 & mask;
|
|
35817
|
-
}
|
|
35818
|
-
}
|
|
35819
|
-
return (crc ^ 4294967295) >>> 0;
|
|
35820
|
-
}
|
|
35821
|
-
function extractPngMetadataFromBuffer(buf) {
|
|
35822
|
-
if (buf.length < 8 || buf[0] !== 137 || buf[1] !== 80 || buf[2] !== 78 || buf[3] !== 71 || buf[4] !== 13 || buf[5] !== 10 || buf[6] !== 26 || buf[7] !== 10) {
|
|
35823
|
-
return null;
|
|
35824
|
-
}
|
|
35825
|
-
let width = 0;
|
|
35826
|
-
let height = 0;
|
|
35827
|
-
let seenIdat = false;
|
|
35828
|
-
let pos = 8;
|
|
35829
|
-
while (pos + 12 <= buf.length) {
|
|
35830
|
-
const chunkLen = buf.readUInt32BE(pos);
|
|
35831
|
-
const chunkType = buf.toString("ascii", pos + 4, pos + 8);
|
|
35832
|
-
if (pos + 12 + chunkLen > buf.length) return null;
|
|
35833
|
-
const chunkData = buf.subarray(pos + 8, pos + 8 + chunkLen);
|
|
35834
|
-
const chunkCrc = buf.readUInt32BE(pos + 8 + chunkLen);
|
|
35835
|
-
const chunkBytes = Buffer.concat([Buffer.from(chunkType, "ascii"), chunkData]);
|
|
35836
|
-
if (crc32(chunkBytes) !== chunkCrc) return null;
|
|
35837
|
-
if (chunkType === "IHDR" && chunkLen >= 8) {
|
|
35838
|
-
width = buf.readUInt32BE(pos + 8);
|
|
35839
|
-
height = buf.readUInt32BE(pos + 12);
|
|
35840
|
-
}
|
|
35841
|
-
if (chunkType === "IDAT") {
|
|
35842
|
-
seenIdat = true;
|
|
35843
|
-
}
|
|
35844
|
-
if (chunkType === "cICP" && chunkLen === 4 && !seenIdat) {
|
|
35845
|
-
const primariesCode = chunkData[0] ?? 0;
|
|
35846
|
-
const transferCode = chunkData[1] ?? 0;
|
|
35847
|
-
const matrixCode = chunkData[2] ?? 0;
|
|
35848
|
-
return {
|
|
35849
|
-
width,
|
|
35850
|
-
height,
|
|
35851
|
-
colorSpace: {
|
|
35852
|
-
colorPrimaries: primariesCode === 9 ? "bt2020" : primariesCode === 1 ? "bt709" : `unknown-${primariesCode}`,
|
|
35853
|
-
colorTransfer: transferCode === 16 ? "smpte2084" : transferCode === 18 ? "arib-std-b67" : transferCode === 1 ? "bt709" : `unknown-${transferCode}`,
|
|
35854
|
-
colorSpace: matrixCode === 9 ? "bt2020nc" : matrixCode === 0 ? "gbr" : `unknown-${matrixCode}`
|
|
35855
|
-
}
|
|
35856
|
-
};
|
|
35857
|
-
}
|
|
35858
|
-
if (chunkType === "IEND") break;
|
|
35859
|
-
pos += 12 + chunkLen;
|
|
35860
|
-
}
|
|
35861
|
-
return width > 0 && height > 0 ? { width, height, colorSpace: null } : null;
|
|
35862
|
-
}
|
|
35863
|
-
function extractStillImageMetadata(filePath) {
|
|
35864
|
-
if (extname(filePath).toLowerCase() !== ".png") return null;
|
|
35865
|
-
try {
|
|
35866
|
-
return extractPngMetadataFromBuffer(readFileSync3(filePath));
|
|
35867
|
-
} catch {
|
|
35868
|
-
return null;
|
|
35869
|
-
}
|
|
35870
|
-
}
|
|
35871
|
-
function readTagCI(tags, name) {
|
|
35872
|
-
if (!tags) return "";
|
|
35873
|
-
const target = name.toLowerCase();
|
|
35874
|
-
for (const [key2, value] of Object.entries(tags)) {
|
|
35875
|
-
if (key2.toLowerCase() === target && typeof value === "string") return value;
|
|
35876
|
-
}
|
|
35877
|
-
return "";
|
|
35878
|
-
}
|
|
35879
|
-
function parseFrameRate(frameRateStr) {
|
|
35880
|
-
if (!frameRateStr) return 0;
|
|
35881
|
-
const parts = frameRateStr.split("/");
|
|
35882
|
-
if (parts.length === 2) {
|
|
35883
|
-
const num = parseFloat(parts[0] ?? "");
|
|
35884
|
-
const den = parseFloat(parts[1] ?? "");
|
|
35885
|
-
if (den !== 0) return Math.round(num / den * 100) / 100;
|
|
35886
|
-
}
|
|
35887
|
-
return parseFloat(frameRateStr) || 0;
|
|
35888
|
-
}
|
|
35889
|
-
async function extractMediaMetadata(filePath) {
|
|
35890
|
-
const cached2 = videoMetadataCache.get(filePath);
|
|
35891
|
-
if (cached2) return cached2;
|
|
35892
|
-
const probePromise = (async () => {
|
|
35893
|
-
const stillImageMeta = extractStillImageMetadata(filePath);
|
|
35894
|
-
let output = null;
|
|
35895
|
-
try {
|
|
35896
|
-
const stdout2 = await runFfprobe([
|
|
35897
|
-
"-v",
|
|
35898
|
-
"quiet",
|
|
35899
|
-
"-print_format",
|
|
35900
|
-
"json",
|
|
35901
|
-
"-show_format",
|
|
35902
|
-
"-show_streams",
|
|
35903
|
-
filePath
|
|
35904
|
-
]);
|
|
35905
|
-
output = parseProbeJson(stdout2);
|
|
35906
|
-
} catch (error) {
|
|
35907
|
-
if (!stillImageMeta) throw error;
|
|
35908
|
-
}
|
|
35909
|
-
const videoStream = output?.streams.find((s2) => s2.codec_type === "video");
|
|
35910
|
-
if (!videoStream) {
|
|
35911
|
-
if (stillImageMeta) {
|
|
35912
|
-
return {
|
|
35913
|
-
durationSeconds: 0,
|
|
35914
|
-
videoStreamDurationSeconds: 0,
|
|
35915
|
-
width: stillImageMeta.width,
|
|
35916
|
-
height: stillImageMeta.height,
|
|
35917
|
-
fps: 0,
|
|
35918
|
-
videoCodec: "png",
|
|
35919
|
-
hasAudio: false,
|
|
35920
|
-
isVFR: false,
|
|
35921
|
-
hasAlpha: false,
|
|
35922
|
-
colorSpace: stillImageMeta.colorSpace
|
|
35923
|
-
};
|
|
35924
|
-
}
|
|
35925
|
-
throw new Error("[FFmpeg] No video stream found");
|
|
35926
|
-
}
|
|
35927
|
-
const rFps = parseFrameRate(videoStream.r_frame_rate);
|
|
35928
|
-
const avgFps = parseFrameRate(videoStream.avg_frame_rate);
|
|
35929
|
-
const fps = avgFps || rFps;
|
|
35930
|
-
const isVFR = rFps > 0 && avgFps > 0 && Math.abs(rFps - avgFps) / Math.max(rFps, avgFps) > 0.1;
|
|
35931
|
-
const colorTransfer = videoStream.color_transfer || "";
|
|
35932
|
-
const colorPrimaries = videoStream.color_primaries || "";
|
|
35933
|
-
const colorSpaceVal = videoStream.color_space || "";
|
|
35934
|
-
const ffprobeColorSpace = colorTransfer || colorPrimaries || colorSpaceVal ? { colorTransfer, colorPrimaries, colorSpace: colorSpaceVal } : null;
|
|
35935
|
-
const colorSpace = ffprobeColorSpace ?? stillImageMeta?.colorSpace ?? null;
|
|
35936
|
-
const pixelFormat = videoStream.pix_fmt || "";
|
|
35937
|
-
const alphaMode = readTagCI(videoStream.tags, "alpha_mode");
|
|
35938
|
-
const hasAlpha = /(^|[^a-z])yuva|rgba|argb|bgra|gbrap|gray[a-z0-9]*a/i.test(pixelFormat) || alphaMode === "1";
|
|
35939
|
-
const containerDuration = output?.format.duration ? parseFloat(output.format.duration) : 0;
|
|
35940
|
-
const streamDuration = videoStream.duration ? parseFloat(videoStream.duration) : 0;
|
|
35941
|
-
return {
|
|
35942
|
-
durationSeconds: containerDuration,
|
|
35943
|
-
videoStreamDurationSeconds: streamDuration > 0 ? streamDuration : containerDuration,
|
|
35944
|
-
width: videoStream.width || stillImageMeta?.width || 0,
|
|
35945
|
-
height: videoStream.height || stillImageMeta?.height || 0,
|
|
35946
|
-
fps,
|
|
35947
|
-
videoCodec: videoStream.codec_name || "unknown",
|
|
35948
|
-
hasAudio: output?.streams.some((s2) => s2.codec_type === "audio") ?? false,
|
|
35949
|
-
isVFR,
|
|
35950
|
-
hasAlpha,
|
|
35951
|
-
colorSpace
|
|
35952
|
-
};
|
|
35953
|
-
})();
|
|
35954
|
-
videoMetadataCache.set(filePath, probePromise);
|
|
35955
|
-
probePromise.catch(() => {
|
|
35956
|
-
if (videoMetadataCache.get(filePath) === probePromise) {
|
|
35957
|
-
videoMetadataCache.delete(filePath);
|
|
35958
|
-
}
|
|
35959
|
-
});
|
|
35960
|
-
return probePromise;
|
|
35961
|
-
}
|
|
35962
|
-
async function extractAudioMetadata(filePath) {
|
|
35963
|
-
const cached2 = audioMetadataCache.get(filePath);
|
|
35964
|
-
if (cached2) return cached2;
|
|
35965
|
-
const probePromise = (async () => {
|
|
35966
|
-
const stdout2 = await runFfprobe([
|
|
35967
|
-
"-v",
|
|
35968
|
-
"quiet",
|
|
35969
|
-
"-print_format",
|
|
35970
|
-
"json",
|
|
35971
|
-
"-show_format",
|
|
35972
|
-
"-show_streams",
|
|
35973
|
-
filePath
|
|
35974
|
-
]);
|
|
35975
|
-
const output = parseProbeJson(stdout2);
|
|
35976
|
-
const audioStream = output.streams.find((s2) => s2.codec_type === "audio");
|
|
35977
|
-
if (!audioStream) throw new Error("[FFmpeg] No audio stream found");
|
|
35978
|
-
const durationSeconds = output.format.duration ? parseFloat(output.format.duration) : 0;
|
|
35979
|
-
return {
|
|
35980
|
-
durationSeconds,
|
|
35981
|
-
sampleRate: audioStream.sample_rate ? parseInt(audioStream.sample_rate) : 44100,
|
|
35982
|
-
channels: audioStream.channels || 2,
|
|
35983
|
-
audioCodec: audioStream.codec_name || "unknown",
|
|
35984
|
-
bitrate: output.format.bit_rate ? parseInt(output.format.bit_rate) : void 0
|
|
35985
|
-
};
|
|
35986
|
-
})();
|
|
35987
|
-
audioMetadataCache.set(filePath, probePromise);
|
|
35988
|
-
probePromise.catch(() => {
|
|
35989
|
-
if (audioMetadataCache.get(filePath) === probePromise) {
|
|
35990
|
-
audioMetadataCache.delete(filePath);
|
|
35991
|
-
}
|
|
35992
|
-
});
|
|
35993
|
-
return probePromise;
|
|
35994
|
-
}
|
|
35995
|
-
async function analyzeKeyframeIntervals(filePath) {
|
|
35996
|
-
const cached2 = keyframeCache.get(filePath);
|
|
35997
|
-
if (cached2) return cached2;
|
|
35998
|
-
const promise = analyzeKeyframeIntervalsUncached(filePath);
|
|
35999
|
-
keyframeCache.set(filePath, promise);
|
|
36000
|
-
promise.catch(() => {
|
|
36001
|
-
if (keyframeCache.get(filePath) === promise) {
|
|
36002
|
-
keyframeCache.delete(filePath);
|
|
36003
|
-
}
|
|
36004
|
-
});
|
|
36005
|
-
return promise;
|
|
36006
|
-
}
|
|
36007
|
-
async function analyzeKeyframeIntervalsUncached(filePath) {
|
|
36008
|
-
const stdout2 = await runFfprobe([
|
|
36009
|
-
"-v",
|
|
36010
|
-
"quiet",
|
|
36011
|
-
"-select_streams",
|
|
36012
|
-
"v:0",
|
|
36013
|
-
"-skip_frame",
|
|
36014
|
-
"nokey",
|
|
36015
|
-
"-show_entries",
|
|
36016
|
-
"frame=pts_time",
|
|
36017
|
-
"-of",
|
|
36018
|
-
"csv=p=0",
|
|
36019
|
-
filePath
|
|
36020
|
-
]);
|
|
36021
|
-
const timestamps = stdout2.split("\n").map((line) => parseFloat(line.trim())).filter((t2) => Number.isFinite(t2));
|
|
36022
|
-
if (timestamps.length < 2) {
|
|
36023
|
-
return {
|
|
36024
|
-
avgIntervalSeconds: 0,
|
|
36025
|
-
maxIntervalSeconds: 0,
|
|
36026
|
-
keyframeCount: timestamps.length,
|
|
36027
|
-
isProblematic: false
|
|
36028
|
-
};
|
|
36029
|
-
}
|
|
36030
|
-
let maxInterval = 0;
|
|
36031
|
-
let totalInterval = 0;
|
|
36032
|
-
for (let i2 = 1; i2 < timestamps.length; i2++) {
|
|
36033
|
-
const interval = (timestamps[i2] ?? 0) - (timestamps[i2 - 1] ?? 0);
|
|
36034
|
-
totalInterval += interval;
|
|
36035
|
-
if (interval > maxInterval) maxInterval = interval;
|
|
36036
|
-
}
|
|
36037
|
-
const avgInterval = totalInterval / (timestamps.length - 1);
|
|
36038
|
-
return {
|
|
36039
|
-
avgIntervalSeconds: Math.round(avgInterval * 100) / 100,
|
|
36040
|
-
maxIntervalSeconds: Math.round(maxInterval * 100) / 100,
|
|
36041
|
-
keyframeCount: timestamps.length,
|
|
36042
|
-
isProblematic: maxInterval > 2
|
|
36043
|
-
};
|
|
36044
|
-
}
|
|
36045
|
-
var videoMetadataCache, audioMetadataCache, extractVideoMetadata, keyframeCache;
|
|
36046
|
-
var init_ffprobe = __esm({
|
|
36047
|
-
"../engine/src/utils/ffprobe.ts"() {
|
|
36048
|
-
"use strict";
|
|
36049
|
-
init_ffmpegBinaries();
|
|
36050
|
-
videoMetadataCache = /* @__PURE__ */ new Map();
|
|
36051
|
-
audioMetadataCache = /* @__PURE__ */ new Map();
|
|
36052
|
-
extractVideoMetadata = extractMediaMetadata;
|
|
36053
|
-
keyframeCache = /* @__PURE__ */ new Map();
|
|
36114
|
+
init_vp9Options();
|
|
36054
36115
|
}
|
|
36055
36116
|
});
|
|
36056
36117
|
|
|
36057
36118
|
// ../engine/src/utils/urlDownloader.ts
|
|
36058
36119
|
import { createWriteStream, existsSync as existsSync8, mkdirSync as mkdirSync5 } from "fs";
|
|
36059
36120
|
import { createHash } from "crypto";
|
|
36060
|
-
import { join as join7, extname as
|
|
36121
|
+
import { join as join7, extname as extname3 } from "path";
|
|
36061
36122
|
import { Readable } from "stream";
|
|
36062
36123
|
import { finished } from "stream/promises";
|
|
36063
36124
|
function isBlockedHost(hostname) {
|
|
@@ -36092,7 +36153,7 @@ function assertPublicHttpsUrl(url) {
|
|
|
36092
36153
|
function getFilenameFromUrl(url) {
|
|
36093
36154
|
const hash2 = createHash("md5").update(url).digest("hex").slice(0, 12);
|
|
36094
36155
|
const urlObj = new URL(url);
|
|
36095
|
-
const ext =
|
|
36156
|
+
const ext = extname3(urlObj.pathname) || ".mp4";
|
|
36096
36157
|
return `download_${hash2}${ext}`;
|
|
36097
36158
|
}
|
|
36098
36159
|
async function downloadToTemp(url, destDir, timeoutMs = 3e5) {
|
|
@@ -40023,7 +40084,7 @@ var init_parallelCoordinator = __esm({
|
|
|
40023
40084
|
import { Hono } from "hono";
|
|
40024
40085
|
import { serve } from "@hono/node-server";
|
|
40025
40086
|
import { readFileSync as readFileSync6, existsSync as existsSync14, statSync as statSync4 } from "fs";
|
|
40026
|
-
import { join as join13, extname as
|
|
40087
|
+
import { join as join13, extname as extname4 } from "path";
|
|
40027
40088
|
function createFileServer(options) {
|
|
40028
40089
|
const { projectDir, compiledDir, port = 0, stripEmbeddedRuntime = true } = options;
|
|
40029
40090
|
const headScripts = options.headScripts ?? [];
|
|
@@ -40041,7 +40102,7 @@ function createFileServer(options) {
|
|
|
40041
40102
|
if (!existsSync14(filePath) || !statSync4(filePath).isFile()) {
|
|
40042
40103
|
return c3.text("Not found", 404);
|
|
40043
40104
|
}
|
|
40044
|
-
const ext =
|
|
40105
|
+
const ext = extname4(filePath).toLowerCase();
|
|
40045
40106
|
const contentType = MIME_TYPES[ext] || "application/octet-stream";
|
|
40046
40107
|
if (ext === ".html") {
|
|
40047
40108
|
const rawHtml = readFileSync6(filePath, "utf-8");
|
|
@@ -41569,6 +41630,7 @@ __export(src_exports2, {
|
|
|
41569
41630
|
BROWSER_GPU_NOT_SOFTWARE: () => BROWSER_GPU_NOT_SOFTWARE,
|
|
41570
41631
|
DEFAULT_CONFIG: () => DEFAULT_CONFIG2,
|
|
41571
41632
|
DEFAULT_HDR10_MASTERING: () => DEFAULT_HDR10_MASTERING,
|
|
41633
|
+
DEFAULT_VP9_CPU_USED: () => DEFAULT_VP9_CPU_USED,
|
|
41572
41634
|
DOM_LAYER_MASK_STYLE_ID: () => DOM_LAYER_MASK_STYLE_ID,
|
|
41573
41635
|
ENABLE_BROWSER_POOL: () => ENABLE_BROWSER_POOL,
|
|
41574
41636
|
ENCODER_PRESETS: () => ENCODER_PRESETS,
|
|
@@ -41576,7 +41638,9 @@ __export(src_exports2, {
|
|
|
41576
41638
|
FFPROBE_PATH_ENV: () => FFPROBE_PATH_ENV,
|
|
41577
41639
|
FrameLookupTable: () => FrameLookupTable,
|
|
41578
41640
|
LOW_MEMORY_TOTAL_MB_THRESHOLD: () => LOW_MEMORY_TOTAL_MB_THRESHOLD,
|
|
41641
|
+
MAX_VP9_CPU_USED: () => MAX_VP9_CPU_USED,
|
|
41579
41642
|
MEDIA_VISUAL_STYLE_PROPERTIES: () => MEDIA_VISUAL_STYLE_PROPERTIES,
|
|
41643
|
+
MIN_VP9_CPU_USED: () => MIN_VP9_CPU_USED,
|
|
41580
41644
|
SwiftShaderAssertionError: () => SwiftShaderAssertionError,
|
|
41581
41645
|
TRANSITIONS: () => TRANSITIONS,
|
|
41582
41646
|
VIDEO_FRAME_FORMATS: () => VIDEO_FRAME_FORMATS,
|
|
@@ -41654,6 +41718,7 @@ __export(src_exports2, {
|
|
|
41654
41718
|
mergeWorkerFrames: () => mergeWorkerFrames,
|
|
41655
41719
|
muxVideoWithAudio: () => muxVideoWithAudio,
|
|
41656
41720
|
normalizeObjectFit: () => normalizeObjectFit,
|
|
41721
|
+
normalizeVp9CpuUsed: () => normalizeVp9CpuUsed,
|
|
41657
41722
|
pageScreenshotCapture: () => pageScreenshotCapture,
|
|
41658
41723
|
parseAudioElements: () => parseAudioElements,
|
|
41659
41724
|
parseImageElements: () => parseImageElements,
|
|
@@ -41686,6 +41751,7 @@ var init_src2 = __esm({
|
|
|
41686
41751
|
"../engine/src/index.ts"() {
|
|
41687
41752
|
"use strict";
|
|
41688
41753
|
init_config2();
|
|
41754
|
+
init_vp9Options();
|
|
41689
41755
|
init_systemMemory();
|
|
41690
41756
|
init_browserManager();
|
|
41691
41757
|
init_frameCapture();
|
|
@@ -45223,9 +45289,9 @@ __export(normalize_exports, {
|
|
|
45223
45289
|
stripBeforeOnset: () => stripBeforeOnset
|
|
45224
45290
|
});
|
|
45225
45291
|
import { readFileSync as readFileSync12, readdirSync as readdirSync7, writeFileSync as writeFileSync8 } from "fs";
|
|
45226
|
-
import { extname as
|
|
45292
|
+
import { extname as extname5, join as join18 } from "path";
|
|
45227
45293
|
function detectFormat(filePath) {
|
|
45228
|
-
const ext =
|
|
45294
|
+
const ext = extname5(filePath).toLowerCase();
|
|
45229
45295
|
if (ext === ".srt") return "srt";
|
|
45230
45296
|
if (ext === ".vtt") return "vtt";
|
|
45231
45297
|
if (ext === ".json") return detectJsonFormat(JSON.parse(readFileSync12(filePath, "utf-8")));
|
|
@@ -45377,7 +45443,7 @@ function round3(n2) {
|
|
|
45377
45443
|
return Math.round(n2 * 1e3) / 1e3;
|
|
45378
45444
|
}
|
|
45379
45445
|
function loadTranscript(filePath) {
|
|
45380
|
-
const ext =
|
|
45446
|
+
const ext = extname5(filePath).toLowerCase();
|
|
45381
45447
|
const content = readFileSync12(filePath, "utf-8");
|
|
45382
45448
|
if (ext === ".srt") {
|
|
45383
45449
|
const words2 = parseSrt(content).map((w3, i2) => ({ ...w3, id: w3.id ?? `w${i2}` }));
|
|
@@ -45502,7 +45568,7 @@ __export(transcribe_exports, {
|
|
|
45502
45568
|
});
|
|
45503
45569
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
45504
45570
|
import { existsSync as existsSync21, readFileSync as readFileSync14, mkdirSync as mkdirSync12, unlinkSync as unlinkSync2 } from "fs";
|
|
45505
|
-
import { join as join20, extname as
|
|
45571
|
+
import { join as join20, extname as extname6 } from "path";
|
|
45506
45572
|
import { tmpdir } from "os";
|
|
45507
45573
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
45508
45574
|
function detectLanguage(whisperPath, modelPath2, wavPath) {
|
|
@@ -45576,10 +45642,10 @@ function detectSpeechOnset(wavPath) {
|
|
|
45576
45642
|
return null;
|
|
45577
45643
|
}
|
|
45578
45644
|
function isAudioFile(filePath) {
|
|
45579
|
-
return AUDIO_EXTENSIONS.has(
|
|
45645
|
+
return AUDIO_EXTENSIONS.has(extname6(filePath).toLowerCase());
|
|
45580
45646
|
}
|
|
45581
45647
|
function isVideoFile(filePath) {
|
|
45582
|
-
return VIDEO_EXTENSIONS.has(
|
|
45648
|
+
return VIDEO_EXTENSIONS.has(extname6(filePath).toLowerCase());
|
|
45583
45649
|
}
|
|
45584
45650
|
function tempWavPath() {
|
|
45585
45651
|
return join20(tmpdir(), `hyperframes-audio-${process.pid}-${randomUUID2()}.wav`);
|
|
@@ -45616,7 +45682,7 @@ function isWav16kMono(filePath) {
|
|
|
45616
45682
|
}
|
|
45617
45683
|
}
|
|
45618
45684
|
function prepareAudio(audioPath) {
|
|
45619
|
-
if (
|
|
45685
|
+
if (extname6(audioPath).toLowerCase() === ".wav" && isWav16kMono(audioPath)) {
|
|
45620
45686
|
return audioPath;
|
|
45621
45687
|
}
|
|
45622
45688
|
const ffmpegPath = findFFmpeg();
|
|
@@ -45640,7 +45706,7 @@ async function transcribe(inputPath, outputDir, options) {
|
|
|
45640
45706
|
onProgress: options?.onProgress
|
|
45641
45707
|
});
|
|
45642
45708
|
let wavPath;
|
|
45643
|
-
const ext =
|
|
45709
|
+
const ext = extname6(inputPath).toLowerCase();
|
|
45644
45710
|
if (isAudioFile(inputPath)) {
|
|
45645
45711
|
options?.onProgress?.("Preparing audio...");
|
|
45646
45712
|
wavPath = prepareAudio(inputPath);
|
|
@@ -45867,7 +45933,7 @@ var init_lint = __esm({
|
|
|
45867
45933
|
|
|
45868
45934
|
// src/utils/lintProject.ts
|
|
45869
45935
|
import { existsSync as existsSync22, readFileSync as readFileSync15, readdirSync as readdirSync8 } from "fs";
|
|
45870
|
-
import { dirname as dirname9, extname as
|
|
45936
|
+
import { dirname as dirname9, extname as extname7, isAbsolute as isAbsolute6, join as join21, posix as posix3, relative as relative4, resolve as resolve12 } from "path";
|
|
45871
45937
|
function readHtmlAttr(tag, name) {
|
|
45872
45938
|
const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
45873
45939
|
const match = tag.match(new RegExp(`\\b${escaped}\\s*=\\s*(?:"([^"]*)"|'([^']*)')`, "i"));
|
|
@@ -46040,7 +46106,7 @@ function lintProjectAudioFiles(projectDir, htmlSources) {
|
|
|
46040
46106
|
let audioFiles;
|
|
46041
46107
|
try {
|
|
46042
46108
|
audioFiles = readdirSync8(projectDir).filter(
|
|
46043
|
-
(f3) => AUDIO_EXTENSIONS2.has(
|
|
46109
|
+
(f3) => AUDIO_EXTENSIONS2.has(extname7(f3).toLowerCase())
|
|
46044
46110
|
);
|
|
46045
46111
|
} catch {
|
|
46046
46112
|
return findings;
|
|
@@ -86064,13 +86130,13 @@ var init_subComposition = __esm({
|
|
|
86064
86130
|
// ../core/src/studio-api/helpers/projectSignature.ts
|
|
86065
86131
|
import { createHash as createHash4 } from "crypto";
|
|
86066
86132
|
import { lstatSync, readFileSync as readFileSync21, readdirSync as readdirSync12 } from "fs";
|
|
86067
|
-
import { extname as
|
|
86133
|
+
import { extname as extname8, isAbsolute as isAbsolute7, relative as relative6, resolve as resolve18 } from "path";
|
|
86068
86134
|
function isPathWithin(parentDir, childPath) {
|
|
86069
86135
|
const childRelativePath = relative6(parentDir, childPath);
|
|
86070
86136
|
return childRelativePath === "" || !childRelativePath.startsWith("..") && !isAbsolute7(childRelativePath);
|
|
86071
86137
|
}
|
|
86072
86138
|
function isTextContentEligible(file, size) {
|
|
86073
|
-
return SIGNATURE_TEXT_EXTENSIONS.has(
|
|
86139
|
+
return SIGNATURE_TEXT_EXTENSIONS.has(extname8(file).toLowerCase()) && size <= MAX_SIGNATURE_TEXT_BYTES;
|
|
86074
86140
|
}
|
|
86075
86141
|
function collectProjectSignatureFiles(projectDir, dir, files) {
|
|
86076
86142
|
let entries2;
|
|
@@ -92412,6 +92478,8 @@ function buildAnimatedGifTranscodeArgs(input2) {
|
|
|
92412
92478
|
"0",
|
|
92413
92479
|
"-deadline",
|
|
92414
92480
|
"good",
|
|
92481
|
+
"-cpu-used",
|
|
92482
|
+
String(DEFAULT_VP9_CPU_USED),
|
|
92415
92483
|
"-crf",
|
|
92416
92484
|
"18",
|
|
92417
92485
|
"-b:v",
|
|
@@ -92704,7 +92772,7 @@ var init_hf_early_stub_inline = __esm({
|
|
|
92704
92772
|
import { Hono as Hono3 } from "hono";
|
|
92705
92773
|
import { serve as serve2 } from "@hono/node-server";
|
|
92706
92774
|
import { readFileSync as readFileSync31, existsSync as existsSync38, realpathSync as realpathSync3, statSync as statSync12 } from "fs";
|
|
92707
|
-
import { join as join38, extname as
|
|
92775
|
+
import { join as join38, extname as extname9, resolve as resolve22, sep as sep4 } from "path";
|
|
92708
92776
|
function isPathInside2(child, parent, options = {}) {
|
|
92709
92777
|
const { resolveSymlinks = false, pathModule } = options;
|
|
92710
92778
|
const resolveFn = pathModule?.resolve ?? resolve22;
|
|
@@ -92904,7 +92972,7 @@ function createFileServer2(options) {
|
|
|
92904
92972
|
}
|
|
92905
92973
|
return c3.text("Not found", 404);
|
|
92906
92974
|
}
|
|
92907
|
-
const ext =
|
|
92975
|
+
const ext = extname9(filePath).toLowerCase();
|
|
92908
92976
|
const contentType = MIME_TYPES3[ext] || "application/octet-stream";
|
|
92909
92977
|
if (ext === ".html") {
|
|
92910
92978
|
const rawHtml = readFileSync31(filePath, "utf-8");
|
|
@@ -98465,6 +98533,7 @@ async function runEncodeStage(input2) {
|
|
|
98465
98533
|
quality: effectiveQuality,
|
|
98466
98534
|
bitrate: effectiveBitrate,
|
|
98467
98535
|
pixelFormat: preset.pixelFormat,
|
|
98536
|
+
vp9CpuUsed: engineCfg.vp9CpuUsed,
|
|
98468
98537
|
useGpu: job.config.useGpu,
|
|
98469
98538
|
hdr: preset.hdr,
|
|
98470
98539
|
// Distributed chunk renders pass these so the encoder writes closed-GOP
|
|
@@ -98525,7 +98594,7 @@ async function runAssembleStage(input2) {
|
|
|
98525
98594
|
audioOutputPath,
|
|
98526
98595
|
outputPath,
|
|
98527
98596
|
abortSignal,
|
|
98528
|
-
|
|
98597
|
+
{ audioCodec: "aac" },
|
|
98529
98598
|
job.config.fps
|
|
98530
98599
|
);
|
|
98531
98600
|
assertNotAborted();
|
|
@@ -99390,6 +99459,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
99390
99459
|
quality: effectiveQuality,
|
|
99391
99460
|
bitrate: effectiveBitrate,
|
|
99392
99461
|
pixelFormat: preset.pixelFormat,
|
|
99462
|
+
vp9CpuUsed: cfg.vp9CpuUsed,
|
|
99393
99463
|
useGpu: job.config.useGpu,
|
|
99394
99464
|
imageFormat: captureOptions.format || "jpeg",
|
|
99395
99465
|
hdr: preset.hdr
|
|
@@ -100848,6 +100918,7 @@ async function readFontSnapshotSha() {
|
|
|
100848
100918
|
function buildLockedRenderConfig(input2) {
|
|
100849
100919
|
const { config, forceScreenshot, deviceScaleFactor, ffmpegVersion } = input2;
|
|
100850
100920
|
const { encoder, pixelFormat, preset } = resolveEncoderTriple(config);
|
|
100921
|
+
const locksVp9CpuUsed = encoder === "libvpx-vp9-software" ? { vp9CpuUsed: normalizeVp9CpuUsed(input2.engineConfig.vp9CpuUsed) } : {};
|
|
100851
100922
|
return {
|
|
100852
100923
|
captureMode: forceScreenshot ? "screenshot" : "beginframe",
|
|
100853
100924
|
forceScreenshot,
|
|
@@ -100864,6 +100935,7 @@ function buildLockedRenderConfig(input2) {
|
|
|
100864
100935
|
preset,
|
|
100865
100936
|
crf: config.crf,
|
|
100866
100937
|
bitrate: config.bitrate,
|
|
100938
|
+
...locksVp9CpuUsed,
|
|
100867
100939
|
// GOP === chunkSize so every chunk's first frame is an IDR keyframe and
|
|
100868
100940
|
// ffmpeg concat-copy round-trips losslessly.
|
|
100869
100941
|
gopSize: input2.effectiveChunkSize,
|
|
@@ -101091,6 +101163,7 @@ async function plan(projectDir, config, planDir) {
|
|
|
101091
101163
|
forceScreenshot,
|
|
101092
101164
|
deviceScaleFactor,
|
|
101093
101165
|
ffmpegVersion,
|
|
101166
|
+
engineConfig: cfg,
|
|
101094
101167
|
effectiveChunkSize,
|
|
101095
101168
|
chunkCount,
|
|
101096
101169
|
runtimeEnv
|
|
@@ -101213,7 +101286,7 @@ var init_plan = __esm({
|
|
|
101213
101286
|
// ../producer/src/services/distributed/renderChunk.ts
|
|
101214
101287
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
101215
101288
|
import { existsSync as existsSync51, mkdirSync as mkdirSync30, readFileSync as readFileSync38, readdirSync as readdirSync20, rmSync as rmSync14, writeFileSync as writeFileSync23 } from "fs";
|
|
101216
|
-
import { extname as
|
|
101289
|
+
import { extname as extname10, join as join61 } from "path";
|
|
101217
101290
|
function rebuildExtractedFramesFromPlanDir(planDir, videos) {
|
|
101218
101291
|
const result = [];
|
|
101219
101292
|
for (const v2 of videos) {
|
|
@@ -101223,7 +101296,7 @@ function rebuildExtractedFramesFromPlanDir(planDir, videos) {
|
|
|
101223
101296
|
`[renderChunk] planDir missing extracted video frames for ${JSON.stringify(v2.videoId)}: ${outputDir} not present. plan() should have written frames here; the planDir is malformed.`
|
|
101224
101297
|
);
|
|
101225
101298
|
}
|
|
101226
|
-
const ext = (
|
|
101299
|
+
const ext = (extname10(v2.framePattern) || ".jpg").toLowerCase();
|
|
101227
101300
|
const frames = readdirSync20(outputDir).filter((name) => name.toLowerCase().endsWith(ext)).sort();
|
|
101228
101301
|
const framePaths = /* @__PURE__ */ new Map();
|
|
101229
101302
|
for (let i2 = 0; i2 < frames.length; i2++) {
|
|
@@ -101264,6 +101337,10 @@ function resolvePresetForLockedEncoder(basePreset, lockedEncoder) {
|
|
|
101264
101337
|
}
|
|
101265
101338
|
return basePreset;
|
|
101266
101339
|
}
|
|
101340
|
+
function resolveLockedVp9CpuUsed(lockedEncoder) {
|
|
101341
|
+
if (lockedEncoder.encoder !== "libvpx-vp9-software") return void 0;
|
|
101342
|
+
return lockedEncoder.vp9CpuUsed ?? LEGACY_DISTRIBUTED_VP9_CPU_USED;
|
|
101343
|
+
}
|
|
101267
101344
|
async function renderChunk(planDir, chunkIndex, outputChunkPath) {
|
|
101268
101345
|
const start = Date.now();
|
|
101269
101346
|
const log2 = defaultLogger;
|
|
@@ -101473,6 +101550,10 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath) {
|
|
|
101473
101550
|
preset,
|
|
101474
101551
|
effectiveQuality,
|
|
101475
101552
|
effectiveBitrate,
|
|
101553
|
+
engineConfig: {
|
|
101554
|
+
ffmpegEncodeTimeout: cfg.ffmpegEncodeTimeout,
|
|
101555
|
+
vp9CpuUsed: resolveLockedVp9CpuUsed(encoder) ?? cfg.vp9CpuUsed
|
|
101556
|
+
},
|
|
101476
101557
|
// Distributed chunks emit a single ffmpeg call per chunk; the
|
|
101477
101558
|
// in-process per-chunk-within-chunk path would re-split our
|
|
101478
101559
|
// already-chunked work.
|
|
@@ -101547,7 +101628,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath) {
|
|
|
101547
101628
|
envRestore.restore();
|
|
101548
101629
|
}
|
|
101549
101630
|
}
|
|
101550
|
-
var FFMPEG_VERSION_MISMATCH, PLAN_HASH_MISMATCH, MISSING_PLAN_ARTIFACT, CHUNK_INDEX_OUT_OF_RANGE, MISSING_RUNTIME_ENV_SNAPSHOT, RenderChunkValidationError;
|
|
101631
|
+
var FFMPEG_VERSION_MISMATCH, PLAN_HASH_MISMATCH, MISSING_PLAN_ARTIFACT, CHUNK_INDEX_OUT_OF_RANGE, MISSING_RUNTIME_ENV_SNAPSHOT, LEGACY_DISTRIBUTED_VP9_CPU_USED, RenderChunkValidationError;
|
|
101551
101632
|
var init_renderChunk = __esm({
|
|
101552
101633
|
"../producer/src/services/distributed/renderChunk.ts"() {
|
|
101553
101634
|
"use strict";
|
|
@@ -101567,6 +101648,7 @@ var init_renderChunk = __esm({
|
|
|
101567
101648
|
MISSING_PLAN_ARTIFACT = "MISSING_PLAN_ARTIFACT";
|
|
101568
101649
|
CHUNK_INDEX_OUT_OF_RANGE = "CHUNK_INDEX_OUT_OF_RANGE";
|
|
101569
101650
|
MISSING_RUNTIME_ENV_SNAPSHOT = "MISSING_RUNTIME_ENV_SNAPSHOT";
|
|
101651
|
+
LEGACY_DISTRIBUTED_VP9_CPU_USED = 2;
|
|
101570
101652
|
RenderChunkValidationError = class extends Error {
|
|
101571
101653
|
code;
|
|
101572
101654
|
constructor(code, message) {
|
|
@@ -101580,41 +101662,89 @@ var init_renderChunk = __esm({
|
|
|
101580
101662
|
|
|
101581
101663
|
// ../producer/src/services/render/audioPadTrim.ts
|
|
101582
101664
|
import { spawn as spawn11 } from "child_process";
|
|
101583
|
-
|
|
101665
|
+
import { rmSync as rmSync15 } from "fs";
|
|
101666
|
+
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
101667
|
+
function buildPadTrimAudioPlan(audioPath, outputPath, sourceDurationSeconds, targetDurationSeconds, audioInfo = {}) {
|
|
101584
101668
|
const delta = targetDurationSeconds - sourceDurationSeconds;
|
|
101585
101669
|
const targetSec = formatSeconds(targetDurationSeconds);
|
|
101586
101670
|
if (Math.abs(delta) < AUDIO_DURATION_TOLERANCE_SECONDS) {
|
|
101587
101671
|
return {
|
|
101588
101672
|
operation: "copy",
|
|
101589
|
-
args: ["-i", audioPath, "-c:a", "copy", "-y", outputPath]
|
|
101673
|
+
steps: [{ kind: "copy", args: ["-i", audioPath, "-c:a", "copy", "-y", outputPath] }],
|
|
101674
|
+
cleanupPaths: []
|
|
101590
101675
|
};
|
|
101591
101676
|
}
|
|
101592
101677
|
if (delta > 0) {
|
|
101593
101678
|
const padDur = formatSeconds(delta);
|
|
101679
|
+
const silencePath = `${outputPath}.pad-silence.aac`;
|
|
101594
101680
|
return {
|
|
101595
101681
|
operation: "pad",
|
|
101596
|
-
|
|
101597
|
-
|
|
101598
|
-
|
|
101599
|
-
|
|
101600
|
-
|
|
101601
|
-
|
|
101602
|
-
|
|
101603
|
-
|
|
101604
|
-
|
|
101605
|
-
|
|
101606
|
-
|
|
101607
|
-
|
|
101682
|
+
steps: [
|
|
101683
|
+
{
|
|
101684
|
+
kind: "pad-silence",
|
|
101685
|
+
args: [
|
|
101686
|
+
"-f",
|
|
101687
|
+
"lavfi",
|
|
101688
|
+
"-i",
|
|
101689
|
+
`anullsrc=channel_layout=${channelLayoutForChannels(audioInfo.channels)}:sample_rate=${sampleRateForFilter(audioInfo.sampleRate)}`,
|
|
101690
|
+
"-t",
|
|
101691
|
+
padDur,
|
|
101692
|
+
"-c:a",
|
|
101693
|
+
"aac",
|
|
101694
|
+
"-b:a",
|
|
101695
|
+
"192k",
|
|
101696
|
+
"-y",
|
|
101697
|
+
silencePath
|
|
101698
|
+
]
|
|
101699
|
+
},
|
|
101700
|
+
{
|
|
101701
|
+
kind: "pad-concat",
|
|
101702
|
+
args: [
|
|
101703
|
+
"-f",
|
|
101704
|
+
"concat",
|
|
101705
|
+
"-safe",
|
|
101706
|
+
"0",
|
|
101707
|
+
"-protocol_whitelist",
|
|
101708
|
+
"file,pipe,crypto,data",
|
|
101709
|
+
"-i",
|
|
101710
|
+
"pipe:0",
|
|
101711
|
+
"-c:a",
|
|
101712
|
+
"copy",
|
|
101713
|
+
"-y",
|
|
101714
|
+
outputPath
|
|
101715
|
+
],
|
|
101716
|
+
stdin: `${concatFileLine(audioPath)}
|
|
101717
|
+
${concatFileLine(silencePath)}
|
|
101718
|
+
`
|
|
101719
|
+
}
|
|
101720
|
+
],
|
|
101721
|
+
cleanupPaths: [silencePath]
|
|
101608
101722
|
};
|
|
101609
101723
|
}
|
|
101610
101724
|
return {
|
|
101611
101725
|
operation: "trim",
|
|
101612
|
-
|
|
101726
|
+
steps: [
|
|
101727
|
+
{ kind: "trim", args: ["-i", audioPath, "-t", targetSec, "-c:a", "copy", "-y", outputPath] }
|
|
101728
|
+
],
|
|
101729
|
+
cleanupPaths: []
|
|
101613
101730
|
};
|
|
101614
101731
|
}
|
|
101615
101732
|
function formatSeconds(sec) {
|
|
101616
101733
|
return sec.toFixed(6);
|
|
101617
101734
|
}
|
|
101735
|
+
function sampleRateForFilter(sampleRate) {
|
|
101736
|
+
return sampleRate !== void 0 && Number.isFinite(sampleRate) && sampleRate > 0 ? Math.round(sampleRate) : 48e3;
|
|
101737
|
+
}
|
|
101738
|
+
function channelLayoutForChannels(channels) {
|
|
101739
|
+
if (channels === 1) return "mono";
|
|
101740
|
+
if (channels === 6) return "5.1";
|
|
101741
|
+
if (channels === 8) return "7.1";
|
|
101742
|
+
return "stereo";
|
|
101743
|
+
}
|
|
101744
|
+
function concatFileLine(path2) {
|
|
101745
|
+
const normalized = pathToFileURL2(path2).href;
|
|
101746
|
+
return `file '${normalized.replace(/'/g, "'\\''")}'`;
|
|
101747
|
+
}
|
|
101618
101748
|
async function padOrTrimAudioToVideoFrameCount(input2) {
|
|
101619
101749
|
const probeVideo2 = input2.probeVideoFrameInfo ?? defaultProbeVideoFrameInfo;
|
|
101620
101750
|
const probeAudio = input2.probeAudioInfo ?? defaultProbeAudioInfo;
|
|
@@ -101650,29 +101780,45 @@ async function padOrTrimAudioToVideoFrameCount(input2) {
|
|
|
101650
101780
|
);
|
|
101651
101781
|
}
|
|
101652
101782
|
const targetDurationSeconds = videoInfo.frameCount * videoInfo.fpsDen / videoInfo.fpsNum;
|
|
101653
|
-
const
|
|
101783
|
+
const plan2 = buildPadTrimAudioPlan(
|
|
101654
101784
|
input2.audioPath,
|
|
101655
101785
|
input2.outputPath,
|
|
101656
101786
|
audioInfo.durationSeconds,
|
|
101657
|
-
targetDurationSeconds
|
|
101787
|
+
targetDurationSeconds,
|
|
101788
|
+
audioInfo
|
|
101658
101789
|
);
|
|
101659
|
-
|
|
101660
|
-
|
|
101790
|
+
try {
|
|
101791
|
+
for (const step of plan2.steps) {
|
|
101792
|
+
const ffmpegResult = await runner(step.args, { stdin: step.stdin });
|
|
101793
|
+
if (!ffmpegResult.success) {
|
|
101794
|
+
return {
|
|
101795
|
+
success: false,
|
|
101796
|
+
outputPath: input2.outputPath,
|
|
101797
|
+
targetDurationSeconds,
|
|
101798
|
+
sourceDurationSeconds: audioInfo.durationSeconds,
|
|
101799
|
+
operation: plan2.operation,
|
|
101800
|
+
error: ffmpegResult.error
|
|
101801
|
+
};
|
|
101802
|
+
}
|
|
101803
|
+
}
|
|
101804
|
+
} catch (err) {
|
|
101661
101805
|
return {
|
|
101662
101806
|
success: false,
|
|
101663
101807
|
outputPath: input2.outputPath,
|
|
101664
101808
|
targetDurationSeconds,
|
|
101665
101809
|
sourceDurationSeconds: audioInfo.durationSeconds,
|
|
101666
|
-
operation,
|
|
101667
|
-
error:
|
|
101810
|
+
operation: plan2.operation,
|
|
101811
|
+
error: `audioPadTrim: failed to materialize ${plan2.operation}: ${err instanceof Error ? err.message : String(err)}`
|
|
101668
101812
|
};
|
|
101813
|
+
} finally {
|
|
101814
|
+
for (const path2 of plan2.cleanupPaths) rmSync15(path2, { force: true });
|
|
101669
101815
|
}
|
|
101670
101816
|
return {
|
|
101671
101817
|
success: true,
|
|
101672
101818
|
outputPath: input2.outputPath,
|
|
101673
101819
|
targetDurationSeconds,
|
|
101674
101820
|
sourceDurationSeconds: audioInfo.durationSeconds,
|
|
101675
|
-
operation
|
|
101821
|
+
operation: plan2.operation
|
|
101676
101822
|
};
|
|
101677
101823
|
}
|
|
101678
101824
|
function failResult(outputPath, target, source, error) {
|
|
@@ -101733,9 +101879,15 @@ function parseFrameRate2(rate) {
|
|
|
101733
101879
|
}
|
|
101734
101880
|
async function defaultProbeAudioInfo(audioPath) {
|
|
101735
101881
|
const metadata = await extractAudioMetadata(audioPath);
|
|
101736
|
-
return {
|
|
101882
|
+
return {
|
|
101883
|
+
durationSeconds: metadata.durationSeconds,
|
|
101884
|
+
sampleRate: metadata.sampleRate,
|
|
101885
|
+
channels: metadata.channels,
|
|
101886
|
+
audioCodec: metadata.audioCodec
|
|
101887
|
+
};
|
|
101737
101888
|
}
|
|
101738
|
-
async function defaultRunFfmpeg(args) {
|
|
101889
|
+
async function defaultRunFfmpeg(args, options) {
|
|
101890
|
+
if (options?.stdin !== void 0) return runFfmpegWithStdin(args, options.stdin);
|
|
101739
101891
|
const result = await runFfmpeg(args);
|
|
101740
101892
|
if (result.success) return { success: true };
|
|
101741
101893
|
return {
|
|
@@ -101743,6 +101895,32 @@ async function defaultRunFfmpeg(args) {
|
|
|
101743
101895
|
error: `[audioPadTrim] ${formatFfmpegError(result.exitCode, result.stderr)}`
|
|
101744
101896
|
};
|
|
101745
101897
|
}
|
|
101898
|
+
async function runFfmpegWithStdin(args, stdin) {
|
|
101899
|
+
return new Promise((resolve58) => {
|
|
101900
|
+
const proc = spawn11(getFfmpegBinary(), args);
|
|
101901
|
+
let stderr = "";
|
|
101902
|
+
proc.stderr.on("data", (data2) => {
|
|
101903
|
+
stderr += data2.toString();
|
|
101904
|
+
});
|
|
101905
|
+
proc.on("error", (err) => {
|
|
101906
|
+
resolve58({
|
|
101907
|
+
success: false,
|
|
101908
|
+
error: `[audioPadTrim] ${err instanceof Error ? err.message : String(err)}`
|
|
101909
|
+
});
|
|
101910
|
+
});
|
|
101911
|
+
proc.on("close", (code) => {
|
|
101912
|
+
if (code === 0) {
|
|
101913
|
+
resolve58({ success: true });
|
|
101914
|
+
return;
|
|
101915
|
+
}
|
|
101916
|
+
resolve58({
|
|
101917
|
+
success: false,
|
|
101918
|
+
error: `[audioPadTrim] ${formatFfmpegError(code, stderr)}`
|
|
101919
|
+
});
|
|
101920
|
+
});
|
|
101921
|
+
proc.stdin.end(stdin);
|
|
101922
|
+
});
|
|
101923
|
+
}
|
|
101746
101924
|
function runFfprobeJson(args) {
|
|
101747
101925
|
return new Promise((resolve58, reject) => {
|
|
101748
101926
|
const proc = spawn11(getFfprobeBinary(), args);
|
|
@@ -101790,7 +101968,7 @@ import {
|
|
|
101790
101968
|
mkdirSync as mkdirSync31,
|
|
101791
101969
|
readFileSync as readFileSync39,
|
|
101792
101970
|
readdirSync as readdirSync21,
|
|
101793
|
-
rmSync as
|
|
101971
|
+
rmSync as rmSync16,
|
|
101794
101972
|
statSync as statSync18,
|
|
101795
101973
|
writeFileSync as writeFileSync24
|
|
101796
101974
|
} from "fs";
|
|
@@ -101827,7 +102005,7 @@ async function assemble(planDir, chunkPaths, audioPath, outputPath, options) {
|
|
|
101827
102005
|
mkdirSync31(dirname21(outputPath), { recursive: true });
|
|
101828
102006
|
}
|
|
101829
102007
|
const workDir = `${outputPath}.assemble-work`;
|
|
101830
|
-
if (existsSync52(workDir))
|
|
102008
|
+
if (existsSync52(workDir)) rmSync16(workDir, { recursive: true, force: true });
|
|
101831
102009
|
mkdirSync31(workDir, { recursive: true });
|
|
101832
102010
|
try {
|
|
101833
102011
|
const concatOutputPath = join62(workDir, `concat.${plan2.dimensions.format}`);
|
|
@@ -101943,7 +102121,7 @@ async function assemble(planDir, chunkPaths, audioPath, outputPath, options) {
|
|
|
101943
102121
|
audioForMux,
|
|
101944
102122
|
muxOutputPath,
|
|
101945
102123
|
abortSignal,
|
|
101946
|
-
|
|
102124
|
+
{ audioCodec: "aac" },
|
|
101947
102125
|
{ num: plan2.dimensions.fpsNum, den: plan2.dimensions.fpsDen }
|
|
101948
102126
|
);
|
|
101949
102127
|
if (!muxResult.success) {
|
|
@@ -101965,7 +102143,7 @@ async function assemble(planDir, chunkPaths, audioPath, outputPath, options) {
|
|
|
101965
102143
|
}
|
|
101966
102144
|
} finally {
|
|
101967
102145
|
try {
|
|
101968
|
-
|
|
102146
|
+
rmSync16(workDir, { recursive: true, force: true });
|
|
101969
102147
|
} catch (err) {
|
|
101970
102148
|
log2.warn("[assemble] failed to remove work dir", {
|
|
101971
102149
|
workDir,
|
|
@@ -101982,7 +102160,7 @@ async function assemble(planDir, chunkPaths, audioPath, outputPath, options) {
|
|
|
101982
102160
|
};
|
|
101983
102161
|
}
|
|
101984
102162
|
function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, startTimeMs) {
|
|
101985
|
-
if (existsSync52(outputPath))
|
|
102163
|
+
if (existsSync52(outputPath)) rmSync16(outputPath, { recursive: true, force: true });
|
|
101986
102164
|
mkdirSync31(outputPath, { recursive: true });
|
|
101987
102165
|
let globalIdx = 0;
|
|
101988
102166
|
for (const chunkDir of chunkPaths) {
|
|
@@ -105915,6 +106093,7 @@ function buildDockerRunArgs(input2) {
|
|
|
105915
106093
|
...options.gifLoop != null ? ["--gif-loop", String(options.gifLoop)] : [],
|
|
105916
106094
|
...options.workers != null ? ["--workers", String(options.workers)] : [],
|
|
105917
106095
|
...options.crf != null ? ["--crf", String(options.crf)] : [],
|
|
106096
|
+
...options.vp9CpuUsed != null ? ["--vp9-cpu-used", String(options.vp9CpuUsed)] : [],
|
|
105918
106097
|
...options.videoBitrate ? ["--video-bitrate", options.videoBitrate] : [],
|
|
105919
106098
|
...options.videoFrameFormat && options.videoFrameFormat !== "auto" ? ["--video-frame-format", options.videoFrameFormat] : [],
|
|
105920
106099
|
...options.quiet ? ["--quiet"] : [],
|
|
@@ -106503,7 +106682,7 @@ __export(render_exports, {
|
|
|
106503
106682
|
renderLocal: () => renderLocal,
|
|
106504
106683
|
resolveBrowserGpuForCli: () => resolveBrowserGpuForCli
|
|
106505
106684
|
});
|
|
106506
|
-
import { mkdirSync as mkdirSync35, readdirSync as readdirSync25, readFileSync as readFileSync48, statSync as statSync21, writeFileSync as writeFileSync28, rmSync as
|
|
106685
|
+
import { mkdirSync as mkdirSync35, readdirSync as readdirSync25, readFileSync as readFileSync48, statSync as statSync21, writeFileSync as writeFileSync28, rmSync as rmSync17 } from "fs";
|
|
106507
106686
|
import { cpus as cpus4, freemem as freemem5, tmpdir as tmpdir5 } from "os";
|
|
106508
106687
|
import { resolve as resolve42, dirname as dirname27, join as join70, basename as basename13 } from "path";
|
|
106509
106688
|
import { execFileSync as execFileSync8, spawn as spawn14 } from "child_process";
|
|
@@ -106595,7 +106774,7 @@ function ensureDockerImage(version2, platform10, quiet) {
|
|
|
106595
106774
|
const message = error instanceof Error ? error.message : String(error);
|
|
106596
106775
|
throw new Error(`Failed to build Docker image: ${message}`);
|
|
106597
106776
|
} finally {
|
|
106598
|
-
|
|
106777
|
+
rmSync17(tmpDir, { recursive: true, force: true });
|
|
106599
106778
|
}
|
|
106600
106779
|
if (!quiet) console.log(c.dim(` Docker image: ${tag} (built)`));
|
|
106601
106780
|
return tag;
|
|
@@ -106657,6 +106836,7 @@ async function renderDocker(projectDir, outputPath, options) {
|
|
|
106657
106836
|
browserGpu: options.browserGpuMode === "hardware",
|
|
106658
106837
|
hdrMode: options.hdrMode,
|
|
106659
106838
|
crf: options.crf,
|
|
106839
|
+
vp9CpuUsed: options.vp9CpuUsed,
|
|
106660
106840
|
videoBitrate: options.videoBitrate,
|
|
106661
106841
|
videoFrameFormat: options.videoFrameFormat,
|
|
106662
106842
|
quiet: options.quiet,
|
|
@@ -106745,7 +106925,8 @@ async function renderLocal(projectDir, outputPath, options) {
|
|
|
106745
106925
|
browserGpuMode: options.browserGpuMode ?? "software",
|
|
106746
106926
|
...options.pageNavigationTimeoutMs != null ? { pageNavigationTimeout: options.pageNavigationTimeoutMs } : {},
|
|
106747
106927
|
...options.protocolTimeout != null && { protocolTimeout: options.protocolTimeout },
|
|
106748
|
-
...options.playerReadyTimeout != null && { playerReadyTimeout: options.playerReadyTimeout }
|
|
106928
|
+
...options.playerReadyTimeout != null && { playerReadyTimeout: options.playerReadyTimeout },
|
|
106929
|
+
...options.vp9CpuUsed != null ? { vp9CpuUsed: options.vp9CpuUsed } : {}
|
|
106749
106930
|
}),
|
|
106750
106931
|
hdrMode: options.hdrMode,
|
|
106751
106932
|
crf: options.crf,
|
|
@@ -107116,6 +107297,10 @@ var init_render2 = __esm({
|
|
|
107116
107297
|
type: "string",
|
|
107117
107298
|
description: "Target video bitrate such as 10M. Mutually exclusive with --crf."
|
|
107118
107299
|
},
|
|
107300
|
+
"vp9-cpu-used": {
|
|
107301
|
+
type: "string",
|
|
107302
|
+
description: "libvpx-vp9 -cpu-used value for WebM encodes (-8 to 8). Higher is faster with a larger quality/size tradeoff. Env: PRODUCER_VP9_CPU_USED."
|
|
107303
|
+
},
|
|
107119
107304
|
gpu: { type: "boolean", description: "Use GPU encoding", default: false },
|
|
107120
107305
|
"browser-gpu": {
|
|
107121
107306
|
type: "boolean",
|
|
@@ -107380,6 +107565,19 @@ var init_render2 = __esm({
|
|
|
107380
107565
|
}
|
|
107381
107566
|
crf = parsed;
|
|
107382
107567
|
}
|
|
107568
|
+
let vp9CpuUsed;
|
|
107569
|
+
if (args["vp9-cpu-used"] != null) {
|
|
107570
|
+
const raw = args["vp9-cpu-used"];
|
|
107571
|
+
const parsed = Number(raw);
|
|
107572
|
+
if (!Number.isInteger(parsed) || parsed < MIN_VP9_CPU_USED || parsed > MAX_VP9_CPU_USED) {
|
|
107573
|
+
errorBox(
|
|
107574
|
+
"Invalid vp9-cpu-used",
|
|
107575
|
+
`Got "${raw}". Must be an integer between ${MIN_VP9_CPU_USED} and ${MAX_VP9_CPU_USED}.`
|
|
107576
|
+
);
|
|
107577
|
+
process.exit(1);
|
|
107578
|
+
}
|
|
107579
|
+
vp9CpuUsed = parsed;
|
|
107580
|
+
}
|
|
107383
107581
|
if (args["video-bitrate"] != null && !videoBitrate) {
|
|
107384
107582
|
errorBox(
|
|
107385
107583
|
"Invalid video-bitrate",
|
|
@@ -107502,6 +107700,7 @@ var init_render2 = __esm({
|
|
|
107502
107700
|
browserGpuMode,
|
|
107503
107701
|
hdrMode,
|
|
107504
107702
|
crf,
|
|
107703
|
+
vp9CpuUsed,
|
|
107505
107704
|
videoBitrate,
|
|
107506
107705
|
quiet: batchQuiet,
|
|
107507
107706
|
browserPath,
|
|
@@ -107549,6 +107748,7 @@ var init_render2 = __esm({
|
|
|
107549
107748
|
browserGpuMode,
|
|
107550
107749
|
hdrMode: args.sdr ? "force-sdr" : args.hdr ? "force-hdr" : "auto",
|
|
107551
107750
|
crf,
|
|
107751
|
+
vp9CpuUsed,
|
|
107552
107752
|
videoBitrate,
|
|
107553
107753
|
videoFrameFormat,
|
|
107554
107754
|
quiet,
|
|
@@ -107572,6 +107772,7 @@ var init_render2 = __esm({
|
|
|
107572
107772
|
browserGpuMode,
|
|
107573
107773
|
hdrMode: args.sdr ? "force-sdr" : args.hdr ? "force-hdr" : "auto",
|
|
107574
107774
|
crf,
|
|
107775
|
+
vp9CpuUsed,
|
|
107575
107776
|
videoBitrate,
|
|
107576
107777
|
videoFrameFormat,
|
|
107577
107778
|
quiet,
|
|
@@ -109991,9 +110192,9 @@ __export(pipeline_exports, {
|
|
|
109991
110192
|
waitForExit: () => waitForExit
|
|
109992
110193
|
});
|
|
109993
110194
|
import { spawn as spawn15 } from "child_process";
|
|
109994
|
-
import { extname as
|
|
110195
|
+
import { extname as extname11 } from "path";
|
|
109995
110196
|
function inferOutputFormat(outputPath) {
|
|
109996
|
-
const ext =
|
|
110197
|
+
const ext = extname11(outputPath).toLowerCase();
|
|
109997
110198
|
if (ext === ".webm") return "webm";
|
|
109998
110199
|
if (ext === ".mov") return "mov";
|
|
109999
110200
|
if (ext === ".png") return "png";
|
|
@@ -110002,7 +110203,7 @@ function inferOutputFormat(outputPath) {
|
|
|
110002
110203
|
);
|
|
110003
110204
|
}
|
|
110004
110205
|
function inferInputKind(inputPath) {
|
|
110005
|
-
const ext =
|
|
110206
|
+
const ext = extname11(inputPath).toLowerCase();
|
|
110006
110207
|
if (VIDEO_EXTENSIONS2.has(ext)) return "video";
|
|
110007
110208
|
if (IMAGE_EXTENSIONS.has(ext)) return "image";
|
|
110008
110209
|
throw new Error(
|
|
@@ -110047,6 +110248,8 @@ function buildEncoderArgs2(format, width, height, fps, outputPath, quality = DEF
|
|
|
110047
110248
|
"good",
|
|
110048
110249
|
"-row-mt",
|
|
110049
110250
|
"1",
|
|
110251
|
+
"-cpu-used",
|
|
110252
|
+
String(DEFAULT_VP9_CPU_USED),
|
|
110050
110253
|
"-auto-alt-ref",
|
|
110051
110254
|
"0",
|
|
110052
110255
|
"-pix_fmt",
|
|
@@ -110102,7 +110305,7 @@ function resolveRenderTargets(inputPath, outputPath, backgroundOutputPath) {
|
|
|
110102
110305
|
const inputKind = inferInputKind(inputPath);
|
|
110103
110306
|
if (inputKind === "image" && format !== "png") {
|
|
110104
110307
|
throw new Error(
|
|
110105
|
-
`Image input requires a .png output (got ${
|
|
110308
|
+
`Image input requires a .png output (got ${extname11(outputPath)}). Use a video input for .webm/.mov.`
|
|
110106
110309
|
);
|
|
110107
110310
|
}
|
|
110108
110311
|
if (inputKind === "video" && format === "png") {
|
|
@@ -110285,6 +110488,7 @@ var init_pipeline = __esm({
|
|
|
110285
110488
|
"use strict";
|
|
110286
110489
|
init_ffmpeg();
|
|
110287
110490
|
init_inference();
|
|
110491
|
+
init_src2();
|
|
110288
110492
|
QUALITY_CRF = {
|
|
110289
110493
|
fast: 30,
|
|
110290
110494
|
balanced: 18,
|
|
@@ -110521,7 +110725,7 @@ __export(transcribe_exports2, {
|
|
|
110521
110725
|
examples: () => examples18
|
|
110522
110726
|
});
|
|
110523
110727
|
import { existsSync as existsSync72, writeFileSync as writeFileSync30 } from "fs";
|
|
110524
|
-
import { resolve as resolve48, join as join78, extname as
|
|
110728
|
+
import { resolve as resolve48, join as join78, extname as extname12, dirname as dirname32 } from "path";
|
|
110525
110729
|
async function importTranscript(inputPath, dir, json) {
|
|
110526
110730
|
const { loadTranscript: loadTranscript2, patchCaptionHtml: patchCaptionHtml2 } = await Promise.resolve().then(() => (init_normalize(), normalize_exports));
|
|
110527
110731
|
const { words, format } = loadTranscript2(inputPath);
|
|
@@ -110657,7 +110861,7 @@ var init_transcribe2 = __esm({
|
|
|
110657
110861
|
process.exit(1);
|
|
110658
110862
|
}
|
|
110659
110863
|
const dir = resolve48(args.dir ?? dirname32(inputPath));
|
|
110660
|
-
const ext =
|
|
110864
|
+
const ext = extname12(inputPath).toLowerCase();
|
|
110661
110865
|
const isImport = ext === ".json" || ext === ".srt" || ext === ".vtt";
|
|
110662
110866
|
if (isImport) {
|
|
110663
110867
|
return importTranscript(inputPath, dir, args.json);
|
|
@@ -110946,7 +111150,7 @@ __export(tts_exports, {
|
|
|
110946
111150
|
examples: () => examples19
|
|
110947
111151
|
});
|
|
110948
111152
|
import { existsSync as existsSync75, readFileSync as readFileSync56 } from "fs";
|
|
110949
|
-
import { resolve as resolve49, extname as
|
|
111153
|
+
import { resolve as resolve49, extname as extname13 } from "path";
|
|
110950
111154
|
function listVoices(json) {
|
|
110951
111155
|
const rows = BUNDLED_VOICES.map((v2) => ({ ...v2, defaultLang: inferLangFromVoiceId(v2.id) }));
|
|
110952
111156
|
if (json) {
|
|
@@ -111055,7 +111259,7 @@ var init_tts = __esm({
|
|
|
111055
111259
|
}
|
|
111056
111260
|
let text;
|
|
111057
111261
|
const maybeFile = resolve49(args.input);
|
|
111058
|
-
if (existsSync75(maybeFile) &&
|
|
111262
|
+
if (existsSync75(maybeFile) && extname13(maybeFile).toLowerCase() === ".txt") {
|
|
111059
111263
|
text = readFileSync56(maybeFile, "utf-8").trim();
|
|
111060
111264
|
if (!text) {
|
|
111061
111265
|
console.error(c.error("File is empty."));
|
|
@@ -112056,7 +112260,7 @@ __export(contactSheet_exports, {
|
|
|
112056
112260
|
});
|
|
112057
112261
|
import sharp from "sharp";
|
|
112058
112262
|
import { readdirSync as readdirSync29, readFileSync as readFileSync59, writeFileSync as writeFileSync32, unlinkSync as unlinkSync8, existsSync as existsSync78 } from "fs";
|
|
112059
|
-
import { join as join83, extname as
|
|
112263
|
+
import { join as join83, extname as extname14, basename as basename16, dirname as dirname36 } from "path";
|
|
112060
112264
|
async function createContactSheet(imagePaths, outputPath, opts = {}) {
|
|
112061
112265
|
const {
|
|
112062
112266
|
cols = 3,
|
|
@@ -112089,7 +112293,7 @@ async function createContactSheet(imagePaths, outputPath, opts = {}) {
|
|
|
112089
112293
|
overlays.push({ input: resized, left: x3, top: y + labelH });
|
|
112090
112294
|
let labelText = `${i2 + 1}`;
|
|
112091
112295
|
if (labelMode === "filename") {
|
|
112092
|
-
labelText = `${i2 + 1}. ${basename16(files[i2]).replace(
|
|
112296
|
+
labelText = `${i2 + 1}. ${basename16(files[i2]).replace(extname14(files[i2]), "")}`;
|
|
112093
112297
|
} else if (labelMode === "custom" && labels?.[i2]) {
|
|
112094
112298
|
labelText = `${i2 + 1}. ${labels[i2]}`;
|
|
112095
112299
|
}
|
|
@@ -112171,7 +112375,7 @@ async function createSnapshotContactSheet(snapshotsDir, outputPath) {
|
|
|
112171
112375
|
async function createAssetContactSheet(assetsDir, outputPath) {
|
|
112172
112376
|
if (!existsSync78(assetsDir)) return [];
|
|
112173
112377
|
const imageExts = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".webp"]);
|
|
112174
|
-
const assetFiles = readdirSync29(assetsDir).filter((f3) => imageExts.has(
|
|
112378
|
+
const assetFiles = readdirSync29(assetsDir).filter((f3) => imageExts.has(extname14(f3).toLowerCase()) && !f3.includes("contact-sheet")).sort();
|
|
112175
112379
|
if (assetFiles.length === 0) return [];
|
|
112176
112380
|
const paths = assetFiles.map((f3) => join83(assetsDir, f3));
|
|
112177
112381
|
return createContactSheetPages(paths, outputPath, {
|
|
@@ -151984,7 +152188,7 @@ __export(snapshot_exports, {
|
|
|
151984
152188
|
examples: () => examples25
|
|
151985
152189
|
});
|
|
151986
152190
|
import { spawn as spawn16 } from "child_process";
|
|
151987
|
-
import { existsSync as existsSync79, mkdtempSync as mkdtempSync5, readFileSync as readFileSync60, mkdirSync as mkdirSync40, rmSync as
|
|
152191
|
+
import { existsSync as existsSync79, mkdtempSync as mkdtempSync5, readFileSync as readFileSync60, mkdirSync as mkdirSync40, rmSync as rmSync18, writeFileSync as writeFileSync33 } from "fs";
|
|
151988
152192
|
import { tmpdir as tmpdir6 } from "os";
|
|
151989
152193
|
import { resolve as resolve51, join as join84, relative as relative14, isAbsolute as isAbsolute12 } from "path";
|
|
151990
152194
|
async function extractVideoFrameToBuffer(videoPath, timeSeconds, useVp9AlphaDecoder = false) {
|
|
@@ -152035,7 +152239,7 @@ async function extractVideoFrameToBuffer(videoPath, timeSeconds, useVp9AlphaDeco
|
|
|
152035
152239
|
return readFileSync60(outPath);
|
|
152036
152240
|
} finally {
|
|
152037
152241
|
try {
|
|
152038
|
-
|
|
152242
|
+
rmSync18(tmp, { recursive: true, force: true });
|
|
152039
152243
|
} catch {
|
|
152040
152244
|
}
|
|
152041
152245
|
}
|
|
@@ -152136,7 +152340,7 @@ async function captureSnapshots(projectDir, opts) {
|
|
|
152136
152340
|
const { readdirSync: readdirSync35 } = await import("fs");
|
|
152137
152341
|
for (const file of readdirSync35(snapshotDir)) {
|
|
152138
152342
|
if (/\.(png|jpg|jpeg)$/i.test(file)) {
|
|
152139
|
-
|
|
152343
|
+
rmSync18(join84(snapshotDir, file), { force: true });
|
|
152140
152344
|
}
|
|
152141
152345
|
}
|
|
152142
152346
|
} catch {
|
|
@@ -152428,7 +152632,7 @@ ${c.error("\u2717")} Snapshot failed: ${msg}`);
|
|
|
152428
152632
|
|
|
152429
152633
|
// src/capture/assetDownloader.ts
|
|
152430
152634
|
import { writeFileSync as writeFileSync34, mkdirSync as mkdirSync41 } from "fs";
|
|
152431
|
-
import { join as join85, extname as
|
|
152635
|
+
import { join as join85, extname as extname15 } from "path";
|
|
152432
152636
|
import { createHash as createHash12 } from "crypto";
|
|
152433
152637
|
function svgContentHashSlug(svgSource, isLogo) {
|
|
152434
152638
|
const hash2 = createHash12("sha1").update(svgSource).digest("hex").slice(0, 8);
|
|
@@ -152463,7 +152667,7 @@ async function downloadAssets(tokens, outputDir, catalogedAssets, faviconLinks)
|
|
|
152463
152667
|
for (const icon of faviconLinks || []) {
|
|
152464
152668
|
if (!icon.href) continue;
|
|
152465
152669
|
try {
|
|
152466
|
-
const ext =
|
|
152670
|
+
const ext = extname15(new URL(icon.href).pathname) || ".ico";
|
|
152467
152671
|
const name = `favicon${ext}`;
|
|
152468
152672
|
const localPath = `assets/${name}`;
|
|
152469
152673
|
const buffer = await fetchBuffer(icon.href);
|
|
@@ -152507,7 +152711,7 @@ async function downloadAssets(tokens, outputDir, catalogedAssets, faviconLinks)
|
|
|
152507
152711
|
const results = await Promise.allSettled(
|
|
152508
152712
|
batch.map(async ({ url, isPoster, catalog }) => {
|
|
152509
152713
|
const parsedUrl = new URL(url);
|
|
152510
|
-
const pathExt =
|
|
152714
|
+
const pathExt = extname15(parsedUrl.pathname);
|
|
152511
152715
|
const ext = pathExt && pathExt.length <= 5 ? pathExt : ".jpg";
|
|
152512
152716
|
const buffer = await fetchBuffer(url);
|
|
152513
152717
|
if (!buffer) return null;
|
|
@@ -152542,7 +152746,7 @@ async function downloadAssets(tokens, outputDir, catalogedAssets, faviconLinks)
|
|
|
152542
152746
|
}
|
|
152543
152747
|
if (tokens.ogImage && !downloadedUrls.has(normalizeUrl(tokens.ogImage))) {
|
|
152544
152748
|
try {
|
|
152545
|
-
const ext =
|
|
152749
|
+
const ext = extname15(new URL(tokens.ogImage).pathname) || ".jpg";
|
|
152546
152750
|
const localPath = `assets/og-image${ext}`;
|
|
152547
152751
|
const buffer = await fetchBuffer(tokens.ogImage);
|
|
152548
152752
|
if (buffer && buffer.length > 5e3) {
|
|
@@ -154276,7 +154480,7 @@ var init_animationCataloger = __esm({
|
|
|
154276
154480
|
|
|
154277
154481
|
// src/capture/mediaCapture.ts
|
|
154278
154482
|
import { mkdirSync as mkdirSync43, writeFileSync as writeFileSync36, readdirSync as readdirSync31, readFileSync as readFileSync63, statSync as statSync25 } from "fs";
|
|
154279
|
-
import { join as join88, extname as
|
|
154483
|
+
import { join as join88, extname as extname16 } from "path";
|
|
154280
154484
|
async function saveLottieAnimations(discoveredLotties, lottieDir) {
|
|
154281
154485
|
let savedCount = 0;
|
|
154282
154486
|
const savedHashes = /* @__PURE__ */ new Set();
|
|
@@ -154415,7 +154619,7 @@ async function downloadVideoBody(srcUrl, filename, videosDir) {
|
|
|
154415
154619
|
if (isPrivateUrl(srcUrl)) return null;
|
|
154416
154620
|
let ext = "";
|
|
154417
154621
|
try {
|
|
154418
|
-
ext =
|
|
154622
|
+
ext = extname16(new URL(srcUrl).pathname).toLowerCase();
|
|
154419
154623
|
} catch {
|
|
154420
154624
|
return null;
|
|
154421
154625
|
}
|
|
@@ -156359,7 +156563,7 @@ __export(state_exports, {
|
|
|
156359
156563
|
stateFilePath: () => stateFilePath,
|
|
156360
156564
|
writeStackOutputs: () => writeStackOutputs
|
|
156361
156565
|
});
|
|
156362
|
-
import { existsSync as existsSync86, mkdirSync as mkdirSync46, readdirSync as readdirSync34, readFileSync as readFileSync66, rmSync as
|
|
156566
|
+
import { existsSync as existsSync86, mkdirSync as mkdirSync46, readdirSync as readdirSync34, readFileSync as readFileSync66, rmSync as rmSync19, writeFileSync as writeFileSync41 } from "fs";
|
|
156363
156567
|
import { dirname as dirname37, join as join94 } from "path";
|
|
156364
156568
|
function stateFilePath(stackName = DEFAULT_STACK_NAME, cwd = process.cwd()) {
|
|
156365
156569
|
return join94(cwd, STATE_DIR_NAME, `${STATE_FILE_PREFIX}${stackName}.json`);
|
|
@@ -156381,7 +156585,7 @@ function readStackOutputs(stackName = DEFAULT_STACK_NAME, cwd = process.cwd()) {
|
|
|
156381
156585
|
}
|
|
156382
156586
|
function deleteStackOutputs(stackName = DEFAULT_STACK_NAME, cwd = process.cwd()) {
|
|
156383
156587
|
const path2 = stateFilePath(stackName, cwd);
|
|
156384
|
-
if (existsSync86(path2))
|
|
156588
|
+
if (existsSync86(path2)) rmSync19(path2);
|
|
156385
156589
|
}
|
|
156386
156590
|
function listStackNames(cwd = process.cwd()) {
|
|
156387
156591
|
const dir = join94(cwd, STATE_DIR_NAME);
|