39948-vm/backend/src/services/videoProcessing.js

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,
};