90 lines
2.5 KiB
JavaScript
90 lines
2.5 KiB
JavaScript
/**
|
|
* Video Processing Service
|
|
*
|
|
* Provides video manipulation operations using FFmpeg.
|
|
* Used for generating reversed videos for back navigation transitions.
|
|
*/
|
|
|
|
const ffmpeg = require('fluent-ffmpeg');
|
|
const fs = require('fs').promises;
|
|
const path = require('path');
|
|
const os = require('os');
|
|
const { logger } = require('../utils/logger');
|
|
|
|
/**
|
|
* Reverse a video using FFmpeg
|
|
* @param {Buffer} inputBuffer - Input video buffer
|
|
* @param {string} filename - Original filename (for extension)
|
|
* @returns {Promise<Buffer>} Reversed video buffer
|
|
*/
|
|
async function reverseVideo(inputBuffer, filename) {
|
|
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'video-reverse-'));
|
|
const ext = path.extname(filename) || '.mp4';
|
|
const inputPath = path.join(tempDir, `input${ext}`);
|
|
const outputPath = path.join(tempDir, `reversed${ext}`);
|
|
|
|
try {
|
|
// Write input buffer to temp file
|
|
await fs.writeFile(inputPath, inputBuffer);
|
|
|
|
logger.info({ inputPath, outputPath }, 'Starting video reversal');
|
|
|
|
// Reverse video with FFmpeg
|
|
await new Promise((resolve, reject) => {
|
|
ffmpeg(inputPath)
|
|
.outputOptions([
|
|
'-vf', 'reverse',
|
|
'-af', 'areverse',
|
|
'-c:v', 'libx264',
|
|
'-preset', 'fast',
|
|
'-crf', '23',
|
|
'-c:a', 'aac',
|
|
'-movflags', '+faststart',
|
|
])
|
|
.output(outputPath)
|
|
.on('start', (cmd) => logger.debug({ cmd }, 'FFmpeg command'))
|
|
.on('progress', (progress) => {
|
|
if (progress.percent) {
|
|
logger.debug({ percent: progress.percent }, 'FFmpeg progress');
|
|
}
|
|
})
|
|
.on('end', () => {
|
|
logger.info({ outputPath }, 'Video reversal complete');
|
|
resolve();
|
|
})
|
|
.on('error', (err, stdout, stderr) => {
|
|
logger.error({ err, stdout, stderr }, 'FFmpeg error');
|
|
reject(err);
|
|
})
|
|
.run();
|
|
});
|
|
|
|
// Read output as buffer
|
|
return await fs.readFile(outputPath);
|
|
} finally {
|
|
// Cleanup temp files
|
|
try {
|
|
await fs.rm(tempDir, { recursive: true, force: true });
|
|
} catch (cleanupErr) {
|
|
logger.warn({ err: cleanupErr, tempDir }, 'Failed to cleanup temp dir');
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if FFmpeg is available
|
|
* @returns {Promise<boolean>}
|
|
*/
|
|
async function isFFmpegAvailable() {
|
|
return new Promise((resolve) => {
|
|
ffmpeg.getAvailableFormats((err) => {
|
|
resolve(!err);
|
|
});
|
|
});
|
|
}
|
|
|
|
module.exports = {
|
|
reverseVideo,
|
|
isFFmpegAvailable,
|
|
};
|