import { exec } from 'child_process'; import { promisify } from 'util'; import path from 'path'; import fs from 'fs'; const execPromise = promisify(exec); /** * Convert a video/audio file to MP3 using FFmpeg * @param {string} inputPath - Path to input file * @param {object} options - Conversion options * @param {string} options.outputDir - Output directory (default: same as input) * @param {string} options.bitrate - Audio bitrate (default: 192k) * @param {string} options.quality - Audio quality 0-9 (default: 2, where 0 is best) * @returns {Promise} Conversion result with output path */ export async function convertToMP3(inputPath, options = {}) { const { outputDir = path.dirname(inputPath), bitrate = '192k', quality = '2', } = options; // Ensure input file exists if (!fs.existsSync(inputPath)) { throw new Error(`Input file not found: ${inputPath}`); } // Generate output path const inputFilename = path.basename(inputPath, path.extname(inputPath)); const outputPath = path.join(outputDir, `${inputFilename}.mp3`); // Check if output already exists if (fs.existsSync(outputPath)) { // Add timestamp to make it unique const timestamp = Date.now(); const uniqueOutputPath = path.join(outputDir, `${inputFilename}_${timestamp}.mp3`); return convertToMP3Internal(inputPath, uniqueOutputPath, bitrate, quality); } return convertToMP3Internal(inputPath, outputPath, bitrate, quality); } /** * Internal conversion function */ async function convertToMP3Internal(inputPath, outputPath, bitrate, quality) { try { // FFmpeg command to convert to MP3 // -i: input file // -vn: no video (audio only) // -ar 44100: audio sample rate 44.1kHz // -ac 2: stereo // -b:a: audio bitrate // -q:a: audio quality (VBR) const command = `ffmpeg -i "${inputPath}" -vn -ar 44100 -ac 2 -b:a ${bitrate} -q:a ${quality} "${outputPath}"`; console.log(`Converting: ${path.basename(inputPath)} -> ${path.basename(outputPath)}`); const { stdout, stderr } = await execPromise(command); // Verify output file was created if (!fs.existsSync(outputPath)) { throw new Error('Conversion failed: output file not created'); } const stats = fs.statSync(outputPath); return { success: true, inputPath, outputPath, filename: path.basename(outputPath), size: stats.size, sizeHuman: formatBytes(stats.size), }; } catch (error) { console.error(`Conversion error: ${error.message}`); throw new Error(`FFmpeg conversion failed: ${error.message}`); } } /** * Convert multiple files to MP3 * @param {string[]} inputPaths - Array of input file paths * @param {object} options - Conversion options * @returns {Promise} Batch conversion results */ export async function convertMultipleToMP3(inputPaths, options = {}) { const results = []; let successCount = 0; let failCount = 0; for (let i = 0; i < inputPaths.length; i++) { const inputPath = inputPaths[i]; console.log(`[${i + 1}/${inputPaths.length}] Converting: ${path.basename(inputPath)}`); try { const result = await convertToMP3(inputPath, options); results.push({ ...result, index: i }); successCount++; } catch (error) { results.push({ success: false, inputPath, error: error.message, index: i, }); failCount++; console.error(`Failed to convert ${path.basename(inputPath)}: ${error.message}`); } } return { totalFiles: inputPaths.length, successCount, failCount, results, }; } /** * Format bytes to human readable format */ function formatBytes(bytes, decimals = 2) { if (bytes === 0) return '0 Bytes'; const k = 1024; const dm = decimals < 0 ? 0 : decimals; const sizes = ['Bytes', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]; } /** * Get supported input formats */ export function getSupportedFormats() { return { video: ['.mp4', '.avi', '.mkv', '.mov', '.wmv', '.flv', '.webm', '.m4v'], audio: ['.m4a', '.wav', '.flac', '.ogg', '.aac', '.wma', '.opus'], }; }