/** * 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} 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} */ async function isFFmpegAvailable() { return new Promise((resolve) => { ffmpeg.getAvailableFormats((err) => { resolve(!err); }); }); } module.exports = { reverseVideo, isFFmpegAvailable, };