videotomp3transcriptor/src/services/conversion.js
2025-12-04 20:57:51 +08:00

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'],
};
}