146 lines
4.3 KiB
JavaScript
146 lines
4.3 KiB
JavaScript
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<object>} 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<object>} 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'],
|
|
};
|
|
}
|