init commit

This commit is contained in:
Carlos
2026-02-21 10:33:18 +01:00
parent c863a943ed
commit 9d955bf338
9512 changed files with 2015317 additions and 1305 deletions

View File

@@ -0,0 +1,22 @@
import type { CompositorCommand } from './payloads';
export type Compositor = {
shutDownOrKill: () => Promise<void>;
finishCommands: () => Promise<void>;
executeCommand: <T extends keyof CompositorCommand>(type: T, payload: CompositorCommand[T]) => Promise<Uint8Array>;
waitForDone: () => Promise<void>;
pid: number | null;
};
export declare const startLongRunningCompositor: ({ maximumFrameCacheItemsInBytes, logLevel, indent, binariesDirectory, extraThreads, }: {
maximumFrameCacheItemsInBytes: number | null;
logLevel: "error" | "info" | "trace" | "verbose" | "warn";
indent: boolean;
binariesDirectory: string | null;
extraThreads: number;
}) => Compositor;
export declare const startCompositor: <T extends keyof CompositorCommand>({ type, payload, logLevel, indent, binariesDirectory, }: {
type: T;
payload: CompositorCommand[T];
logLevel: "error" | "info" | "trace" | "verbose" | "warn";
indent: boolean;
binariesDirectory: string | null;
}) => Compositor;

View File

@@ -0,0 +1,201 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.startCompositor = exports.startLongRunningCompositor = void 0;
const streaming_1 = require("@remotion/streaming");
const node_child_process_1 = require("node:child_process");
const node_path_1 = __importDefault(require("node:path"));
const log_level_1 = require("../log-level");
const logger_1 = require("../logger");
const get_executable_path_1 = require("./get-executable-path");
const make_file_executable_1 = require("./make-file-executable");
const make_nonce_1 = require("./make-nonce");
const serialize_command_1 = require("./serialize-command");
const startLongRunningCompositor = ({ maximumFrameCacheItemsInBytes, logLevel, indent, binariesDirectory, extraThreads, }) => {
return (0, exports.startCompositor)({
type: 'StartLongRunningProcess',
payload: {
concurrency: extraThreads,
maximum_frame_cache_size_in_bytes: maximumFrameCacheItemsInBytes,
verbose: (0, log_level_1.isEqualOrBelowLogLevel)(logLevel, 'verbose'),
},
logLevel,
indent,
binariesDirectory,
});
};
exports.startLongRunningCompositor = startLongRunningCompositor;
const startCompositor = ({ type, payload, logLevel, indent, binariesDirectory = null, }) => {
var _a;
const bin = (0, get_executable_path_1.getExecutablePath)({
type: 'compositor',
indent,
logLevel,
binariesDirectory,
});
(0, make_file_executable_1.makeFileExecutableIfItIsNot)(bin);
const fullCommand = (0, serialize_command_1.serializeCommand)(type, payload);
const cwd = node_path_1.default.dirname(bin);
const child = (0, node_child_process_1.spawn)(bin, [JSON.stringify(fullCommand)], {
cwd,
env: process.platform === 'darwin'
? {
// Should work out of the box, but sometimes it doesn't
// https://github.com/remotion-dev/remotion/issues/3862
DYLD_LIBRARY_PATH: cwd,
}
: undefined,
});
let stderrChunks = [];
const waiters = new Map();
const onMessage = (statusType, nonce, data) => {
// Nonce '0' just means that the message should be logged
if (nonce === '0') {
logger_1.Log.verbose({ indent, logLevel, tag: 'compositor' }, new TextDecoder('utf8').decode(data));
}
else if (waiters.has(nonce)) {
if (statusType === 'error') {
try {
const parsed = JSON.parse(new TextDecoder('utf8').decode(data));
waiters.get(nonce).reject(new Error(`Compositor error: ${parsed.error}\n${parsed.backtrace}`));
}
catch (_a) {
waiters.get(nonce).reject(new Error(new TextDecoder('utf8').decode(data)));
}
}
else {
waiters.get(nonce).resolve(data);
}
waiters.delete(nonce);
}
};
const { onData, getOutputBuffer, clear } = (0, streaming_1.makeStreamer)(onMessage);
let runningStatus = { type: 'running' };
child.stdout.on('data', onData);
child.stderr.on('data', (data) => {
stderrChunks.push(data);
});
let resolve = null;
let reject = null;
child.on('close', (code, signal) => {
const waitersToKill = Array.from(waiters.values());
if (code === 0) {
runningStatus = { type: 'quit-without-error', signal };
resolve === null || resolve === void 0 ? void 0 : resolve();
for (const waiter of waitersToKill) {
waiter.reject(new Error(`Compositor quit${signal ? ` with signal ${signal}` : ''}`));
}
waiters.clear();
}
else {
const errorMessage = Buffer.concat(stderrChunks).toString('utf-8') +
new TextDecoder('utf-8').decode(getOutputBuffer());
runningStatus = { type: 'quit-with-error', error: errorMessage, signal };
logger_1.Log.verbose({ indent, logLevel }, `Compositor exited with code ${code} and signal ${signal}`);
const error = code === null
? new Error(`Compositor exited with signal ${signal}`)
: new Error(`Compositor exited with code ${code}: ${errorMessage}`);
for (const waiter of waitersToKill) {
waiter.reject(error);
}
waiters.clear();
reject === null || reject === void 0 ? void 0 : reject(error);
}
// Need to manually free up memory
clear();
stderrChunks = [];
});
const waitForDone = () => {
return new Promise((res, rej) => {
if (runningStatus.type === 'quit-without-error') {
rej(new Error(`Compositor quit${runningStatus.signal ? ` with signal ${runningStatus.signal}` : ''}`));
return;
}
if (runningStatus.type === 'quit-with-error') {
rej(new Error(`Compositor quit${runningStatus.signal ? ` with signal ${runningStatus.signal}` : ''}: ${runningStatus.error}`));
return;
}
resolve = res;
reject = rej;
});
};
const finishCommands = async () => {
// Prevent this function from throwing an error if instead a rejected promise should be returned
await Promise.resolve();
if (runningStatus.type === 'quit-with-error') {
return Promise.reject(new Error(`Compositor quit${runningStatus.signal ? ` with signal ${runningStatus.signal}` : ''}: ${runningStatus.error}`));
}
if (runningStatus.type === 'quit-without-error') {
return Promise.reject(new Error(`Compositor quit${runningStatus.signal ? ` with signal ${runningStatus.signal}` : ''}`));
}
return new Promise((res, rej) => {
child.stdin.write('EOF\n', (e) => {
if (e) {
rej(e);
return;
}
res();
});
});
};
const shutDownOrKill = () => {
const shutDownCase = async () => {
await finishCommands();
await waitForDone();
};
let timeout = null;
const killCase = async () => {
await new Promise((res) => {
timeout = setTimeout(res, 5000);
});
child.kill('SIGKILL');
};
return Promise.race([shutDownCase(), killCase()]).finally(() => {
if (timeout !== null) {
clearTimeout(timeout);
}
});
};
return {
shutDownOrKill,
waitForDone,
finishCommands,
executeCommand: (command, params) => {
return new Promise((_resolve, _reject) => {
if (runningStatus.type === 'quit-without-error') {
_reject(new Error(`Compositor quit${runningStatus.signal
? ` with signal ${runningStatus.signal}`
: ''}`));
return;
}
if (runningStatus.type === 'quit-with-error') {
_reject(new Error(`Compositor quit${runningStatus.signal
? ` with signal ${runningStatus.signal}`
: ''}: ${runningStatus.error.trim()}`));
return;
}
const nonce = (0, make_nonce_1.makeNonce)();
const composed = {
nonce,
payload: {
type: command,
params,
},
};
child.stdin.write(JSON.stringify(composed) + '\n', (e) => {
if (e) {
_reject(e);
}
});
waiters.set(nonce, {
resolve: _resolve,
reject: _reject,
});
});
},
pid: (_a = child.pid) !== null && _a !== void 0 ? _a : null,
};
};
exports.startCompositor = startCompositor;

View File

@@ -0,0 +1,12 @@
import type { LogLevel } from '../log-level';
export declare function isMusl({ indent, logLevel }: {
indent: boolean;
logLevel: LogLevel;
}): boolean;
export declare const getExecutablePath: ({ indent, logLevel, type, binariesDirectory, }: {
type: "compositor" | "ffmpeg" | "ffprobe";
indent: boolean;
logLevel: "error" | "info" | "trace" | "verbose" | "warn";
binariesDirectory: string | null;
}) => string;
export declare const getExecutableDir: (indent: boolean, logLevel: "error" | "info" | "trace" | "verbose" | "warn") => string;

View File

@@ -0,0 +1,95 @@
"use strict";
// Adapted from @swc/core package
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getExecutableDir = exports.getExecutablePath = void 0;
exports.isMusl = isMusl;
const path_1 = __importDefault(require("path"));
const logger_1 = require("../logger");
let warned = false;
function isMusl({ indent, logLevel, }) {
var _a;
if (!process.report && typeof Bun !== 'undefined') {
if (!warned) {
logger_1.Log.warn({ indent, logLevel }, 'Bun limitation: Could not determine if your Linux is using musl or glibc. Assuming glibc.');
}
warned = true;
return false;
}
const report = (_a = process.report) === null || _a === void 0 ? void 0 : _a.getReport();
if (report && typeof report === 'string') {
if (!warned) {
logger_1.Log.warn({ indent, logLevel }, 'Bun limitation: Could not determine if your Windows is using musl or glibc. Assuming glibc.');
}
warned = true;
return false;
}
// @ts-expect-error no types
const { glibcVersionRuntime } = report.header;
return !glibcVersionRuntime;
}
const getExecutablePath = ({ indent, logLevel, type, binariesDirectory, }) => {
const base = binariesDirectory !== null && binariesDirectory !== void 0 ? binariesDirectory : (0, exports.getExecutableDir)(indent, logLevel);
switch (type) {
case 'compositor':
if (process.platform === 'win32') {
return path_1.default.resolve(base, 'remotion.exe');
}
return path_1.default.resolve(base, 'remotion');
case 'ffmpeg':
if (process.platform === 'win32') {
return path_1.default.join(base, 'ffmpeg.exe');
}
return path_1.default.join(base, 'ffmpeg');
case 'ffprobe':
if (process.platform === 'win32') {
return path_1.default.join(base, 'ffprobe.exe');
}
return path_1.default.join(base, 'ffprobe');
default:
throw new Error(`Unknown executable type: ${type}`);
}
};
exports.getExecutablePath = getExecutablePath;
const getExecutableDir = (indent, logLevel) => {
switch (process.platform) {
case 'win32':
switch (process.arch) {
case 'x64':
return require('@remotion/compositor-win32-x64-msvc').dir;
default:
throw new Error(`Unsupported architecture on Windows: ${process.arch}`);
}
case 'darwin':
switch (process.arch) {
case 'x64':
return require('@remotion/compositor-darwin-x64').dir;
case 'arm64':
return require('@remotion/compositor-darwin-arm64').dir;
default:
throw new Error(`Unsupported architecture on macOS: ${process.arch}`);
}
case 'linux': {
const musl = isMusl({ indent, logLevel });
switch (process.arch) {
case 'x64':
if (musl) {
return require('@remotion/compositor-linux-x64-musl').dir;
}
return require('@remotion/compositor-linux-x64-gnu').dir;
case 'arm64':
if (musl) {
return require('@remotion/compositor-linux-arm64-musl').dir;
}
return require('@remotion/compositor-linux-arm64-gnu').dir;
default:
throw new Error(`Unsupported architecture on Linux: ${process.arch}`);
}
}
default:
throw new Error(`Unsupported OS: ${process.platform}, architecture: ${process.arch}`);
}
};
exports.getExecutableDir = getExecutableDir;

View File

@@ -0,0 +1,3 @@
export declare const getExplicitEnv: (cwd: string) => {
DYLD_LIBRARY_PATH: string;
} | undefined;

View File

@@ -0,0 +1,13 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getExplicitEnv = void 0;
const getExplicitEnv = (cwd) => {
return process.platform === 'darwin'
? {
// Should work out of the box, but sometimes it doesn't
// https://github.com/remotion-dev/remotion/issues/3862
DYLD_LIBRARY_PATH: cwd,
}
: undefined;
};
exports.getExplicitEnv = getExplicitEnv;

View File

@@ -0,0 +1 @@
export declare const makeFileExecutableIfItIsNot: (path: string) => void;

View File

@@ -0,0 +1,44 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.makeFileExecutableIfItIsNot = void 0;
/* eslint-disable no-bitwise */
const node_fs_1 = require("node:fs");
const hasPermissions = (p) => {
// We observe that with Bun, the problem also happens in macOS
if (process.platform !== 'linux' && process.platform !== 'darwin') {
try {
(0, node_fs_1.accessSync)(p, node_fs_1.constants.X_OK);
return true;
}
catch (_a) {
return false;
}
}
// On Linux, checking file permissions, because Node.js
// seems buggy: https://github.com/remotion-dev/remotion/issues/3587
const stats = (0, node_fs_1.statSync)(p);
const { mode } = stats;
const othersHaveExecutePermission = Boolean(mode & 0o001);
if (othersHaveExecutePermission) {
return true;
}
if (!process.getuid || !process.getgid) {
throw new Error('Cannot check permissions on Linux without process.getuid and process.getgid');
}
const uid = process.getuid();
const gid = process.getgid();
const isOwner = uid === stats.uid;
const isGroup = gid === stats.gid;
const ownerHasExecutePermission = Boolean(mode & 0o100);
const groupHasExecutePermission = Boolean(mode & 0o010);
const canExecute = (isOwner && ownerHasExecutePermission) ||
(isGroup && groupHasExecutePermission);
return canExecute;
};
const makeFileExecutableIfItIsNot = (path) => {
const hasPermissionsResult = hasPermissions(path);
if (!hasPermissionsResult) {
(0, node_fs_1.chmodSync)(path, 0o755);
}
};
exports.makeFileExecutableIfItIsNot = makeFileExecutableIfItIsNot;

View File

@@ -0,0 +1 @@
export declare const makeNonce: () => string;

View File

@@ -0,0 +1,8 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.makeNonce = void 0;
const makeNonce = () => {
return (Math.random().toString(36).substring(2, 15) +
Math.random().toString(36).substring(2, 15));
};
exports.makeNonce = makeNonce;

View File

@@ -0,0 +1,73 @@
export type CompositorImageFormat = 'Png' | 'Jpeg';
export type VideoMetadata = {
fps: number;
width: number;
height: number;
durationInSeconds: number | null;
codec: 'h264' | 'h265' | 'vp8' | 'vp9' | 'av1' | 'prores' | 'unknown';
canPlayInVideoTag: boolean;
supportsSeeking: boolean;
colorSpace: 'rgb' | 'bt601' | 'bt709' | 'bt2020-ncl' | 'bt2020-cl' | 'fcc' | 'bt470bg' | 'smpte170m' | 'smpte240m' | 'ycgco' | 'smpte2085' | 'chroma-derived-ncl' | 'chroma-derived-cl' | 'ictcp' | 'unknown';
audioCodec: null | 'opus' | 'aac' | 'mp3' | 'pcm-f16le' | 'pcm-f24le' | 'pcm-f32be' | 'pcm-s16be' | 'pcm-s16le' | 'pcm-f32le' | 'pcm-s32be' | 'pcm-s32le' | 'pcm-s64be' | 'pcm-s64le' | 'pcm-u16be' | 'pcm-u16le' | 'pcm-u24be' | 'pcm-u24le' | 'pcm-u32be' | 'pcm-u32le' | 'pcm-u8' | 'pcm-f64be' | 'pcm-s24be' | 'pcm-s24le' | 'pcm-s8' | 'pcm-s16be-planar' | 'pcm-s8-planar' | 'pcm-s24le-planar' | 'pcm-s32le-planar' | 'unknown';
audioFileExtension: string | null;
pixelFormat: null | 'yuv420p' | 'yuyv422' | 'rgb24' | 'bgr24' | 'yuv422p' | 'yuv444p' | 'yuv410p' | 'yuv411p' | 'yuvj420p' | 'yuvj422p' | 'yuvj444p' | 'argb' | 'rgba' | 'abgr' | 'bgra' | 'yuv440p' | 'yuvj440p' | 'yuva420p' | 'yuv420p16le' | 'yuv420p16be' | 'yuv422p16le' | 'yuv422p16be' | 'yuv444p16le' | 'yuv444p16be' | 'yuv420p9be' | 'yuv420p9le' | 'yuv420p10be' | 'yuv420p10le' | 'yuv422p10be' | 'yuv422p10le' | 'yuv444p9be' | 'yuv444p9le' | 'yuv444p10be' | 'yuv444p10le' | 'yuv422p9be' | 'yuv422p9le' | 'yuva420p9be' | 'yuva420p9le' | 'yuva422p9be' | 'yuva422p9le' | 'yuva444p9be' | 'yuva444p9le' | 'yuva420p10be' | 'yuva420p10le' | 'yuva422p10be' | 'yuva422p10le' | 'yuva444p10be' | 'yuva444p10le' | 'yuva420p16be' | 'yuva420p16le' | 'yuva422p16be' | 'yuva422p16le' | 'yuva444p16be' | 'yuva444p16le' | 'yuva444p' | 'yuva422p' | 'yuv420p12be' | 'yuv420p12le' | 'yuv420p14be' | 'yuv420p14le' | 'yuv422p12be' | 'yuv422p12le' | 'yuv422p14be' | 'yuv422p14le' | 'yuv444p12be' | 'yuv444p12le' | 'yuv444p14be' | 'yuv444p14le' | 'yuvj411p' | 'yuv440p10le' | 'yuv440p10be' | 'yuv440p12le' | 'yuv440p12be' | 'yuv420p9' | 'yuv422p9' | 'yuv444p9' | 'yuv420p10' | 'yuv422p10' | 'yuv440p10' | 'yuv444p10' | 'yuv420p12' | 'yuv422p12' | 'yuv440p12' | 'yuv444p12' | 'yuv420p14' | 'yuv422p14' | 'yuv444p14' | 'yuv420p16' | 'yuv422p16' | 'yuv444p16' | 'yuva420p9' | 'yuva422p9' | 'yuva444p9' | 'yuva420p10' | 'yuva422p10' | 'yuva444p10' | 'yuva420p16' | 'yuva422p16' | 'yuva444p16' | 'yuva422p12be' | 'yuva422p12le' | 'yuva444p12be' | 'yuva444p12le' | 'unknown';
};
export type SilentPart = {
startInSeconds: number;
endInSeconds: number;
};
export type SilentParts = SilentPart[];
export type GetSilentPartsResponseRust = {
silentParts: SilentParts;
durationInSeconds: number;
};
export type GetSilentPartsResponse = GetSilentPartsResponseRust & {
audibleParts: SilentParts;
};
export type CompositorCommand = {
ExtractFrame: {
src: string;
original_src: string;
time: number;
transparent: boolean;
tone_mapped: boolean;
};
GetSilences: {
src: string;
noiseThresholdInDecibels: number;
minDurationInSeconds: number;
};
Echo: {
message: string;
};
StartLongRunningProcess: {
concurrency: number;
maximum_frame_cache_size_in_bytes: number | null;
verbose: boolean;
};
GetOpenVideoStats: {};
DeliberatePanic: {};
CloseAllVideos: {};
FreeUpMemory: {
remaining_bytes: number;
};
GetVideoMetadata: {
src: string;
};
ExtractAudio: {
input_path: string;
output_path: string;
};
VideoMetadata: VideoMetadata;
};
export type CompositorCommandSerialized<T extends keyof CompositorCommand> = {
nonce: string;
payload: {
type: T;
params: CompositorCommand[T];
};
};
export type ErrorPayload = {
error: string;
backtrace: string;
};

View File

@@ -0,0 +1,2 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });

View File

@@ -0,0 +1,2 @@
import type { CompositorCommand, CompositorCommandSerialized } from './payloads';
export declare const serializeCommand: <Type extends keyof CompositorCommand>(command: Type, params: CompositorCommand[Type]) => CompositorCommandSerialized<Type>;

View File

@@ -0,0 +1,14 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.serializeCommand = void 0;
const make_nonce_1 = require("./make-nonce");
const serializeCommand = (command, params) => {
return {
nonce: (0, make_nonce_1.makeNonce)(),
payload: {
type: command,
params,
},
};
};
exports.serializeCommand = serializeCommand;