@lumen5/beamcoder
Version:
Node.js native bindings to FFmpeg.
286 lines (247 loc) • 9.15 kB
JavaScript
/*
Aerostat Beam Coder - Node.js native bindings to FFmpeg.
Copyright (C) 2019 Streampunk Media Ltd.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
https://www.streampunk.media/ mailto:furnace@streampunk.media
14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K.
*/
const os = require('os');
const fs = require('fs');
const util = require('util');
const https = require('https');
const cp = require('child_process');
const path = require('path');
const [ mkdir, access, rename, execFile, exec ] = // eslint-disable-line
[ fs.mkdir, fs.access, fs.rename, cp.execFile, cp.exec ].map(util.promisify);
// GitHub repository with pre-built static libraries
const GITHUB_REPO = 'Lumen5/ffmpeg-static';
const GITHUB_BRANCH = 'main';
// Get platform string for static library directory
// Maps to the directory structure in ffmpeg-static: output/linux/{amd64,arm64}, output/darwin/arm64
function getPlatform() {
const platform = os.platform();
const arch = os.arch();
// Map Node.js arch to platform format
// Note: macOS arm64 uses linux/arm64 binaries
if (arch === 'x64') {
if (platform !== 'linux') {
throw new Error(`Platform ${platform} with x64 architecture is not supported for static linking. Only Linux x64 is supported.`);
}
return 'linux/amd64';
} else if (arch === 'arm64') {
if (platform === 'linux') {
return 'linux/arm64';
} else if (platform === 'darwin') {
return 'darwin/arm64';
} else {
throw new Error(`Platform ${platform} with arm64 architecture is not supported for static linking.`);
}
} else {
throw new Error(`Architecture ${arch} is not supported. Only x64 (amd64) and arm64 are supported.`);
}
}
// Get the download URL for the repository archive
function getDownloadUrl() {
return `https://github.com/${GITHUB_REPO}/archive/refs/heads/${GITHUB_BRANCH}.tar.gz`;
}
async function download(url, destPath, name) {
return new Promise((resolve, reject) => {
console.log(`Downloading ${name} from ${url}...`);
const file = fs.createWriteStream(destPath);
let received = 0;
let totalLength = 0;
https.get(url, res => {
if (res.statusCode === 301 || res.statusCode === 302) {
file.close();
fs.unlinkSync(destPath);
// Follow redirect
return download(res.headers.location, destPath, name).then(resolve).catch(reject);
}
totalLength = +res.headers['content-length'] || 0;
res.pipe(file);
res.on('data', chunk => {
received += chunk.length;
if (totalLength > 0) {
process.stdout.write(`Downloaded ${Math.floor(received * 100 / totalLength)}% of '${name}'.\r`);
}
});
file.on('finish', () => {
file.close();
console.log(`\nDownloaded 100% of '${name}'. Total length ${received} bytes.`);
resolve();
});
}).on('error', err => {
fs.unlinkSync(destPath);
reject(err);
});
file.on('error', err => {
fs.unlinkSync(destPath);
reject(err);
});
});
}
async function extractTarGz(tarPath, destDir) {
console.log(`Extracting ${tarPath} to ${destDir}...`);
await exec(`tar -xzf ${tarPath} -C ${destDir}`);
console.log('Extraction complete.');
}
async function downloadFFmpegStatic() {
const ffmpegDir = path.join(__dirname, 'ffmpeg');
const buildDir = path.join(ffmpegDir, 'ffmpeg-static-build');
const tarPath = path.join(ffmpegDir, 'ffmpeg-static.tar.gz');
const extractDir = path.join(ffmpegDir, 'ffmpeg-static-main');
// Clean up previous installation
if (fs.existsSync(ffmpegDir)) {
console.log('Removing previous installation...');
await exec(`rm -rf ${ffmpegDir}`);
}
// Create ffmpeg directory
await mkdir(ffmpegDir).catch(e => {
if (e.code === 'EEXIST') return;
else throw e;
});
// Check if already downloaded - verify all required libraries exist
// Note: Based on https://github.com/Lumen5/ffmpeg-static repo structure
const requiredLibs = [
'libavcodec.a',
'libavdevice.a',
'libavfilter.a',
'libavformat.a',
'libavutil.a',
'libpostproc.a',
'libswresample.a',
'libswscale.a'
];
// No optional libraries
const optionalLibs = [];
try {
// Check all libraries exist
for (const lib of requiredLibs) {
await access(path.join(buildDir, 'lib', lib), fs.constants.R_OK);
}
console.log('FFmpeg static libraries already present.');
return buildDir;
} catch (err) {
console.log('Downloading FFmpeg static libraries from GitHub...');
// Clean up incomplete build directory if it exists
if (fs.existsSync(buildDir)) {
console.log('Removing incomplete installation...');
await exec(`rm -rf ${buildDir}`);
}
}
// Get download URL
const downloadUrl = getDownloadUrl();
console.log(`Downloading from: https://github.com/${GITHUB_REPO}`);
// Download pre-built binaries from GitHub
try {
await download(downloadUrl, tarPath, 'ffmpeg-static repository');
} catch (err) {
console.error(`Failed to download FFmpeg static binaries from ${downloadUrl}`);
console.error(`Please ensure https://github.com/${GITHUB_REPO} is accessible.`);
throw err;
}
// Extract the archive
console.log('Extracting FFmpeg static libraries...');
await extractTarGz(tarPath, ffmpegDir);
// Get the platform-specific directory
const platformDir = getPlatform();
console.log(`Using platform: ${platformDir}`);
// Copy output directory contents to ffmpeg-static-build
// The repo has structure: ffmpeg-static-main/output/{linux/{amd64,arm64},darwin/arm64}/{lib,include}
// We need: ffmpeg-static-build/{lib,include}
console.log('Setting up library directories...');
const outputDir = path.join(extractDir, 'output', platformDir);
if (!fs.existsSync(outputDir)) {
throw new Error(`Expected directory not found: ${outputDir}. Platform ${platformDir} may not be available in the repository.`);
}
// Create build directory
await mkdir(buildDir).catch(e => {
if (e.code === 'EEXIST') return;
else throw e;
});
// Copy contents of platform-specific output directory (lib and include) to ffmpeg-static-build
await exec(`cp -r "${outputDir}"/* "${buildDir}"/`);
// Verify all required libraries were copied
try {
for (const lib of requiredLibs) {
await access(path.join(buildDir, 'lib', lib), fs.constants.R_OK);
}
console.log('FFmpeg static libraries installed successfully.');
// Check for optional libraries
for (const lib of optionalLibs) {
try {
await access(path.join(buildDir, 'lib', lib), fs.constants.R_OK);
console.log(` Optional library found: ${lib}`);
} catch (err) {
console.log(` Optional library not found: ${lib} (skipping)`);
}
}
} catch (err) {
console.error('Installation failed: Not all required libraries were found.');
console.error(`Expected libraries in: ${buildDir}/lib/`);
throw err;
}
// Clean up temporary files
console.log('Cleaning up...');
if (fs.existsSync(tarPath)) {
await exec(`rm ${tarPath}`);
}
if (fs.existsSync(extractDir)) {
await exec(`rm -rf ${extractDir}`);
}
return buildDir;
}
async function win32() {
console.log('Building static FFmpeg libraries on Windows is complex.');
console.log('For Windows, consider using pre-built static libraries or use WSL/MSYS2.');
console.log('This script focuses on Linux and macOS support.');
process.exit(1);
}
async function linux() {
const platformDir = getPlatform();
console.log(`Installing FFmpeg static libraries for Linux (${platformDir}).`);
await downloadFFmpegStatic();
}
async function darwin() {
const platformDir = getPlatform();
console.log(`Installing FFmpeg static libraries for macOS (${platformDir}).`);
await downloadFFmpegStatic();
}
switch (os.platform()) {
case 'win32':
if (os.arch() != 'x64') {
console.error('Only 64-bit platforms are supported.');
process.exit(1);
} else {
win32().catch(console.error);
}
break;
case 'linux':
if (os.arch() != 'x64' && os.arch() != 'arm64') {
console.error('Only 64-bit platforms are supported.');
process.exit(1);
} else {
linux().catch(console.error);
}
break;
case 'darwin':
if (os.arch() != 'x64' && os.arch() != 'arm64') {
console.error('Only 64-bit platforms are supported.');
process.exit(1);
} else {
darwin().catch(console.error);
}
break;
default:
console.error(`Platform ${os.platform()} is not supported.`);
break;
}