151 lines
4.8 KiB
JavaScript
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
|