151 lines
4.8 KiB
JavaScript

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.extractVideoMetadata = extractVideoMetadata;
exports.validateVideoFile = validateVideoFile;
const child_process_1 = require("child_process");
const promises_1 = require("fs/promises");
const logger_1 = require("../../../utils/logger");
/**
* Run a command with a timeout
*/
function runCommand(command, args, timeoutMs = 30000) {
return new Promise((resolve, reject) => {
const process = (0, child_process_1.spawn)(command, args);
let stdout = '';
let stderr = '';
const timeout = setTimeout(() => {
process.kill();
reject(new Error(`Command timed out after ${timeoutMs}ms`));
}, timeoutMs);
process.stdout.on('data', (data) => {
stdout += data.toString();
});
process.stderr.on('data', (data) => {
stderr += data.toString();
});
process.on('close', (code) => {
clearTimeout(timeout);
if (code === 0) {
resolve(stdout);
}
else {
reject(new Error(`Command failed with code ${code}: ${stderr}`));
}
});
process.on('error', (err) => {
clearTimeout(timeout);
reject(err);
});
});
}
/**
* Extract video metadata using ffprobe
*/
async function extractVideoMetadata(filePath) {
try {
// Run ffprobe to get video metadata
const output = await runCommand('ffprobe', [
'-v', 'quiet',
'-print_format', 'json',
'-show_streams',
'-show_format',
filePath,
]);
const data = JSON.parse(output);
// Find video stream
const videoStream = data.streams.find((s) => s.codec_type === 'video');
if (!videoStream) {
throw new Error('No video stream found in file');
}
// Find audio stream
const hasAudio = data.streams.some((s) => s.codec_type === 'audio');
// Extract dimensions
const width = videoStream.width || 0;
const height = videoStream.height || 0;
if (width === 0 || height === 0) {
throw new Error('Could not determine video dimensions');
}
// Calculate orientation
const orientation = width >= height ? 'H' : 'V';
// Calculate quality based on height
let quality;
if (height >= 2160) {
quality = '4K';
}
else if (height >= 1440) {
quality = '1440p';
}
else if (height >= 1080) {
quality = '1080p';
}
else if (height >= 720) {
quality = '720p';
}
else if (height >= 480) {
quality = '480p';
}
else {
quality = 'SD';
}
// Extract duration (prefer stream duration, fallback to format duration)
let durationSeconds = 0;
const streamDuration = videoStream.duration;
const formatDuration = data.format.duration;
if (streamDuration) {
durationSeconds = parseFloat(streamDuration);
}
else if (formatDuration) {
durationSeconds = parseFloat(formatDuration);
}
if (isNaN(durationSeconds) || durationSeconds <= 0) {
throw new Error('Could not determine video duration');
}
// Get file size
const stats = await (0, promises_1.stat)(filePath);
const fileSize = stats.size;
logger_1.logger.info(`Extracted metadata for ${filePath}:`, {
width,
height,
orientation,
quality,
duration: durationSeconds,
hasAudio,
fileSize,
});
return {
durationSeconds,
width,
height,
orientation,
quality,
hasAudio,
fileSize,
};
}
catch (error) {
logger_1.logger.error(`Failed to extract metadata from ${filePath}:`, error);
throw new Error(`Metadata extraction failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
/**
* Validate video file by attempting to decode frames
*/
async function validateVideoFile(filePath) {
try {
// Try to decode 5 frames
await runCommand('ffmpeg', [
'-v', 'error',
'-i', filePath,
'-vframes', '5',
'-f', 'null',
'-',
], 60000); // 60s timeout for validation
logger_1.logger.info(`Video validation successful: ${filePath}`);
return true;
}
catch (error) {
logger_1.logger.warn(`Video validation failed for ${filePath}:`, error);
return false;
}
}
//# sourceMappingURL=ffprobe.service.js.map