MovieMapper/utils/fileUtils.js

135 lines
4.2 KiB
JavaScript

const fs = require('fs');
const path = require('path');
const ffmpeg = require('fluent-ffmpeg');
// Supported media file extensions
const MEDIA_EXTENSIONS = ['.mp4', '.mkv', '.avi', '.mov', '.flv', '.webm'];
/**
* Check if a file has a media extension
* @param {string} filePath - Path to the file
* @returns {boolean} - True if file is a media file
*/
function isMediaFile(filePath) {
const ext = path.extname(filePath).toLowerCase();
return MEDIA_EXTENSIONS.includes(ext);
}
/**
* Scan a directory for media files
* @param {string} directoryPath - Path to the directory to scan
* @returns {Promise<Array>} - Array of media file paths
*/
async function scanDirectory(directoryPath) {
try {
const files = fs.readdirSync(directoryPath);
const mediaFiles = [];
for (const file of files) {
const filePath = path.join(directoryPath, file);
try {
const stat = fs.statSync(filePath);
if (stat.isDirectory()) {
// Recursively scan subdirectories
const subDirFiles = await scanDirectory(filePath);
mediaFiles.push(...subDirFiles);
} else if (isMediaFile(filePath)) {
// Extract duration for media files
let fileData = {
path: filePath,
name: file,
size: stat.size,
modified: stat.mtime,
duration: '00:00',
isProblematic: false
};
try {
const durationResult = await extractFileDuration(filePath);
fileData.duration = durationResult.duration;
fileData.isProblematic = durationResult.isProblematic;
} catch (error) {
console.warn(`Failed to extract duration for ${filePath}:`, error.message);
}
mediaFiles.push(fileData);
}
} catch (fileError) {
// Skip files/directories that cause permission errors
console.warn(`Skipping file/directory due to permission error: ${filePath}`);
continue;
}
}
return mediaFiles;
} catch (error) {
throw new Error(`Failed to scan directory: ${error.message}`);
}
}
/**
* Extract file duration using ffmpeg
* @param {string} filePath - Path to the media file
* @returns {Promise<string>} - Duration in mm:ss format
*/
async function extractFileDuration(filePath) {
return new Promise((resolve, reject) => {
ffmpeg.ffprobe(filePath, (err, metadata) => {
if (err) {
console.warn(`ffprobe error for ${filePath}:`, err.message);
// Try alternative approach - attempt to get duration from file stats or use fallback
try {
const fs = require('fs');
const stats = fs.statSync(filePath);
console.log(`File size for ${filePath}: ${stats.size} bytes`);
// For now, return 00:00 as fallback for problematic files
resolve({ duration: '00:00', isProblematic: true });
} catch (statsError) {
console.warn(`Could not get file stats for ${filePath}:`, statsError.message);
resolve({ duration: '00:00', isProblematic: true });
}
return;
}
const duration = metadata.format.duration;
if (!duration) {
resolve({ duration: '00:00', isProblematic: false });
return;
}
// Convert seconds to mm:ss format
const minutes = Math.floor(duration / 60);
const seconds = Math.floor(duration % 60);
const formattedDuration = `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
resolve({ duration: formattedDuration, isProblematic: false });
});
});
}
/**
* Extract file metadata (duration and quality)
* @param {string} filePath - Path to the media file
* @returns {Promise<Object>} - File metadata
*/
async function extractFileMetadata(filePath) {
// Extract duration using ffmpeg
let duration = '00:00';
try {
duration = await extractFileDuration(filePath);
} catch (error) {
console.warn(`Failed to extract duration for ${filePath}:`, error.message);
}
return {
duration: duration,
quality: 'unknown'
};
}
module.exports = {
scanDirectory,
extractFileMetadata,
isMediaFile,
extractFileDuration
};