Add upscale feat
This commit is contained in:
865
index.js
865
index.js
@@ -8,6 +8,8 @@ const EventEmitter = require('events');
|
||||
const axios = require('axios');
|
||||
const mpdParser = require('mpd-parser');
|
||||
const softwareService = require('./services/softwares');
|
||||
const ffprobe = require('ffprobe');
|
||||
const ffprobeStatic = require('ffprobe-static');
|
||||
|
||||
const BASE_PATH = process.env.DATA_PATH || `./data`;
|
||||
const OUTPUT_PATH = process.env.OUTPUT_PATH || `${BASE_PATH}/output`;
|
||||
@@ -94,7 +96,7 @@ app.use((req, res, next) => {
|
||||
|
||||
app.post('/start-process', async (req, res, next) => {
|
||||
try {
|
||||
const { mp4Filename, mpdUrl, keys, wantedResolution, wantedAudioTracks, wantedSubtitles, wantedRemux } = req.body;
|
||||
const { mp4Filename, mpdUrl, keys, wantedResolution, wantedAudioTracks, wantedSubtitles, wantedRemux, wantToUpscale } = req.body;
|
||||
console.log(JSON.stringify(req.body, null, 2));
|
||||
const job = await videoQueue.add({
|
||||
mp4Filename,
|
||||
@@ -103,7 +105,8 @@ app.post('/start-process', async (req, res, next) => {
|
||||
wantedResolution,
|
||||
wantedAudioTracks,
|
||||
wantedSubtitles,
|
||||
wantedRemux
|
||||
wantedRemux,
|
||||
wantToUpscale
|
||||
});
|
||||
res.json({ jobId: job.id });
|
||||
} catch(e) {
|
||||
@@ -477,52 +480,374 @@ const safeMove = async (source, destination) => {
|
||||
}
|
||||
};
|
||||
|
||||
const remuxToMKV = async (options) => {
|
||||
const { mp4FilePath, outputPath, filename, wantedAudioTracks, wantedSubtitles, videoInfo } = options;
|
||||
// const remuxToMKV = async (options) => {
|
||||
// const { mp4FilePath, outputPath, filename, wantedAudioTracks, wantedSubtitles, videoInfo } = options;
|
||||
|
||||
// try {
|
||||
// console.log('🎬 Starting MKV remux...');
|
||||
|
||||
// const mkvFilePath = `${outputPath}/${filename}.mkv`;
|
||||
|
||||
// let mkvCommand = `mkvmerge -o "${mkvFilePath}" --verbose`;
|
||||
|
||||
// mkvCommand += ` --title "${filename}"`;
|
||||
|
||||
// const resolution = videoInfo?.resolution || { width: 'Unknown', height: 'Unknown' };
|
||||
// mkvCommand += ` --language 0:und`;
|
||||
// mkvCommand += ` --track-name "0:Video ${resolution.width}x${resolution.height}"`;
|
||||
// mkvCommand += ` "${mp4FilePath}"`;
|
||||
|
||||
// for (let i = 0; i < wantedAudioTracks.length; i++) {
|
||||
// const audioTrack = wantedAudioTracks[i];
|
||||
// const codec = audioTrack.attributes.CODECS?.split('.')[0] || 'Unknown';
|
||||
// const bitrate = Math.round(audioTrack.attributes.BANDWIDTH / 1000);
|
||||
|
||||
// const trackIndex = i + 1;
|
||||
|
||||
// mkvCommand += ` --language ${trackIndex}:${audioTrack.language}`;
|
||||
// mkvCommand += ` --track-name "${trackIndex}:${audioTrack.language.toUpperCase()} ${codec} ${bitrate}kbps"`;
|
||||
|
||||
// mkvCommand += ` --default-track ${trackIndex}:${i === 0 ? 'yes' : 'no'}`;
|
||||
// }
|
||||
|
||||
// const srtFiles = fs.readdirSync(outputPath).filter(file =>
|
||||
// file.startsWith(filename) && file.endsWith('.srt')
|
||||
// );
|
||||
|
||||
// for (let i = 0; i < srtFiles.length; i++) {
|
||||
// const srtFile = srtFiles[i];
|
||||
// const srtPath = `${outputPath}/${srtFile}`;
|
||||
|
||||
// const srtIndex = i;
|
||||
// const correspondingSubtitle = wantedSubtitles[srtIndex];
|
||||
|
||||
// if (correspondingSubtitle) {
|
||||
// const language = correspondingSubtitle.language;
|
||||
|
||||
// const isForced = srtFile.toLowerCase().includes('forced') || srtFile.toLowerCase().includes('sdh') === false;
|
||||
// const trackName = isForced ? `${language.toUpperCase()} Forced` : `${language.toUpperCase()}`;
|
||||
|
||||
// mkvCommand += ` --language 0:${language}`;
|
||||
// mkvCommand += ` --track-name "0:${trackName}"`;
|
||||
|
||||
// if (isForced) {
|
||||
// mkvCommand += ` --forced-track 0:yes`;
|
||||
// }
|
||||
|
||||
// const defaultSub = (i === 0 && wantedAudioTracks.length > 0 && language === wantedAudioTracks[0].language) ? 'yes' : 'no';
|
||||
// mkvCommand += ` --default-track 0:${defaultSub}`;
|
||||
|
||||
// mkvCommand += ` "${srtPath}"`;
|
||||
// }
|
||||
// }
|
||||
|
||||
// console.log('🔧 MKV Command:', mkvCommand);
|
||||
|
||||
// await runCommand(mkvCommand);
|
||||
|
||||
// console.log(`✅ MKV remux completed: ${mkvFilePath}`);
|
||||
|
||||
// //fs.unlinkSync(mp4FilePath);
|
||||
// //srtFiles.forEach(srt => fs.unlinkSync(`${outputPath}/${srt}`));
|
||||
|
||||
// return mkvFilePath;
|
||||
|
||||
// } catch (error) {
|
||||
// console.error('❌ MKV remux failed:', error);
|
||||
// throw new Error(`MKV remux failed: ${error.message}`);
|
||||
// }
|
||||
// };
|
||||
|
||||
const upscaleVideo = async (inputFilePath, options = {}) => {
|
||||
const {
|
||||
scale = 2, // Facteur d'upscale (2x, 4x)
|
||||
method = 'realesrgan', // 'realesrgan', 'waifu2x', 'lanczos'
|
||||
model = 'realesr-animevideov3', // Modèle Real-ESRGAN
|
||||
outputSuffix = '_upscaled',
|
||||
deleteOriginal = false,
|
||||
fps = null // FPS de sortie (null = garder l'original)
|
||||
} = options;
|
||||
|
||||
try {
|
||||
console.log('🎬 Starting MKV remux...');
|
||||
console.log(`🔍 Starting ${scale}x upscale with ${method}...`);
|
||||
|
||||
const mkvFilePath = `${outputPath}/${filename}.mkv`;
|
||||
const inputDir = path.dirname(inputFilePath);
|
||||
const inputName = path.basename(inputFilePath, '.mp4');
|
||||
const outputFilePath = path.join(inputDir, `${inputName}${outputSuffix}.mp4`);
|
||||
|
||||
let mkvCommand = `mkvmerge -o "${mkvFilePath}" --verbose`;
|
||||
|
||||
mkvCommand += ` --title "${filename}"`;
|
||||
|
||||
const resolution = videoInfo?.resolution || { width: 'Unknown', height: 'Unknown' };
|
||||
mkvCommand += ` --language 0:und`;
|
||||
mkvCommand += ` --track-name "0:Video ${resolution.width}x${resolution.height}"`;
|
||||
mkvCommand += ` "${mp4FilePath}"`;
|
||||
|
||||
for (let i = 0; i < wantedAudioTracks.length; i++) {
|
||||
const audioTrack = wantedAudioTracks[i];
|
||||
const codec = audioTrack.attributes.CODECS?.split('.')[0] || 'Unknown';
|
||||
const bitrate = Math.round(audioTrack.attributes.BANDWIDTH / 1000);
|
||||
|
||||
const trackIndex = i + 1;
|
||||
|
||||
mkvCommand += ` --language ${trackIndex}:${audioTrack.language}`;
|
||||
mkvCommand += ` --track-name "${trackIndex}:${audioTrack.language.toUpperCase()} ${codec} ${bitrate}kbps"`;
|
||||
|
||||
mkvCommand += ` --default-track ${trackIndex}:${i === 0 ? 'yes' : 'no'}`;
|
||||
// Vérifier si le fichier upscalé existe déjà
|
||||
if (fs.existsSync(outputFilePath)) {
|
||||
console.log('📁 Upscaled file already exists, skipping...');
|
||||
return outputFilePath;
|
||||
}
|
||||
|
||||
const srtFiles = fs.readdirSync(outputPath).filter(file =>
|
||||
file.startsWith(filename) && file.endsWith('.srt')
|
||||
);
|
||||
// Sélection de la méthode d'upscale
|
||||
switch (method.toLowerCase()) {
|
||||
case 'realesrgan':
|
||||
await upscaleWithRealESRGAN(inputFilePath, outputFilePath, { scale, model });
|
||||
break;
|
||||
case 'lanczos':
|
||||
await upscaleWithLanczos(inputFilePath, outputFilePath, { scale, fps });
|
||||
break;
|
||||
case 'bicubic':
|
||||
await upscaleWithBicubic(inputFilePath, outputFilePath, { scale, fps });
|
||||
break;
|
||||
case 'super-resolution':
|
||||
await upscaleWithSuperResolution(inputFilePath, outputFilePath, { scale, fps });
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unknown upscale method: ${method}`);
|
||||
}
|
||||
|
||||
for (let i = 0; i < srtFiles.length; i++) {
|
||||
const srtFile = srtFiles[i];
|
||||
const srtPath = `${outputPath}/${srtFile}`;
|
||||
// Vérifier que le fichier de sortie existe
|
||||
if (!fs.existsSync(outputFilePath)) {
|
||||
throw new Error('Upscale failed: output file not created');
|
||||
}
|
||||
|
||||
const srtIndex = i;
|
||||
const correspondingSubtitle = wantedSubtitles[srtIndex];
|
||||
// Supprimer l'original si demandé
|
||||
if (deleteOriginal) {
|
||||
fs.unlinkSync(inputFilePath);
|
||||
fs.renameSync(outputFilePath, inputFilePath);
|
||||
console.log('🗑️ Original file deleted');
|
||||
}
|
||||
|
||||
if (correspondingSubtitle) {
|
||||
const language = correspondingSubtitle.language;
|
||||
console.log(`✅ Video upscaled successfully: ${outputFilePath}`);
|
||||
return outputFilePath;
|
||||
|
||||
const isForced = srtFile.toLowerCase().includes('forced') || srtFile.toLowerCase().includes('sdh') === false;
|
||||
const trackName = isForced ? `${language.toUpperCase()} Forced` : `${language.toUpperCase()}`;
|
||||
} catch (error) {
|
||||
console.error('❌ Video upscale failed:', error);
|
||||
throw new Error(`Upscale failed: ${error.message}`);
|
||||
}
|
||||
};
|
||||
|
||||
// ========== MÉTHODES D'UPSCALE ==========
|
||||
|
||||
async function upscaleWithRealESRGAN(inputPath, outputPath, { scale, model }) {
|
||||
console.log(`🤖 Using Real-ESRGAN with model: ${model}`);
|
||||
|
||||
const realEsrganPath = softwareService.getLocalBinFileInfo('realesrgan').path;
|
||||
|
||||
// Real-ESRGAN pour vidéo (meilleure qualité)
|
||||
const command = `${realEsrganPath} -i "${inputPath}" -o "${outputPath}" -n ${model} -s ${scale} -v`;
|
||||
|
||||
console.log('🔧 Real-ESRGAN Command:', command);
|
||||
await runCommand(command);
|
||||
}
|
||||
|
||||
async function upscaleWithBicubic(inputPath, outputPath, { scale, fps }) {
|
||||
console.log('🔄 Using FFmpeg Bicubic');
|
||||
|
||||
const videoInfo = await getVideoInfo(inputPath);
|
||||
const newWidth = Math.round(videoInfo.width * scale);
|
||||
const newHeight = Math.round(videoInfo.height * scale);
|
||||
|
||||
const fpsFilter = fps ? ` -r ${fps}` : '';
|
||||
const command = `ffmpeg -i "${inputPath}" -vf "scale=${newWidth}:${newHeight}:flags=bicubic" -c:a copy${fpsFilter} "${outputPath}"`;
|
||||
|
||||
console.log('🔧 FFmpeg Bicubic Command:', command);
|
||||
await runCommand(command);
|
||||
}
|
||||
|
||||
async function upscaleWithSuperResolution(inputPath, outputPath, { scale, fps }) {
|
||||
console.log('🧠 Using FFmpeg Super Resolution filter');
|
||||
|
||||
const videoInfo = await getVideoInfo(inputPath);
|
||||
const newWidth = Math.round(videoInfo.width * scale);
|
||||
const newHeight = Math.round(videoInfo.height * scale);
|
||||
|
||||
// Utilise le filtre super-resolution de FFmpeg (si disponible)
|
||||
const fpsFilter = fps ? ` -r ${fps}` : '';
|
||||
const command = `ffmpeg -i "${inputPath}" -vf "scale=${newWidth}:${newHeight}:flags=lanczos+accurate_rnd+full_chroma_int" -c:a copy${fpsFilter} "${outputPath}"`;
|
||||
|
||||
console.log('🔧 FFmpeg Super Resolution Command:', command);
|
||||
await runCommand(command);
|
||||
}
|
||||
|
||||
async function upscaleWithLanczos(inputPath, outputPath, { scale }) {
|
||||
console.log('⚡ Using FFmpeg Lanczos (fast fallback)');
|
||||
|
||||
// Déterminer la résolution cible
|
||||
const videoInfo = await getVideoInfo(inputPath);
|
||||
const newWidth = Math.round(videoInfo.width * scale);
|
||||
const newHeight = Math.round(videoInfo.height * scale);
|
||||
|
||||
// FFmpeg avec filtre Lanczos (rapide mais qualité moindre)
|
||||
const command = `ffmpeg -i "${inputPath}" -vf "scale=${newWidth}:${newHeight}:flags=lanczos" -c:a copy "${outputPath}"`;
|
||||
|
||||
console.log('🔧 FFmpeg Lanczos Command:', command);
|
||||
await runCommand(command);
|
||||
}
|
||||
|
||||
// ========== FONCTIONS UTILITAIRES ==========
|
||||
|
||||
async function getVideoInfo(filePath) {
|
||||
try {
|
||||
// const command = `ffprobe -v quiet -print_format json -show_format -show_streams "${filePath}"`;
|
||||
// const output = await runCommand(command, { returnOutput: true });
|
||||
const info = await ffprobe(filePath, { path: ffprobeStatic.path });
|
||||
|
||||
console.log(info);
|
||||
|
||||
const videoStream = info.streams.find(stream => stream.codec_type === 'video');
|
||||
|
||||
return {
|
||||
width: parseInt(videoStream.width),
|
||||
height: parseInt(videoStream.height),
|
||||
//duration: parseFloat(info.format.duration),
|
||||
codec: videoStream.codec_name
|
||||
};
|
||||
} catch (error) {
|
||||
console.warn('⚠️ Could not get video info, using defaults', error);
|
||||
return { width: 1920, height: 1080, duration: 0, codec: 'unknown' };
|
||||
}
|
||||
}
|
||||
|
||||
// ========== FONCTION D'AIDE POUR CHOISIR LA MEILLEURE MÉTHODE ==========
|
||||
|
||||
function getRecommendedUpscaleMethod(videoInfo, contentType = 'mixed') {
|
||||
const { width, height } = videoInfo;
|
||||
const totalPixels = width * height;
|
||||
|
||||
// Recommandations basées sur le contenu et la résolution
|
||||
if (contentType === 'anime' || contentType === 'cartoon') {
|
||||
return {
|
||||
method: 'waifu2x',
|
||||
model: null,
|
||||
reason: 'Optimized for animated content'
|
||||
};
|
||||
}
|
||||
|
||||
if (totalPixels < 1000000) { // < 1MP (très basse résolution)
|
||||
return {
|
||||
method: 'realesrgan',
|
||||
model: 'realesr-animevideov3',
|
||||
reason: 'Best quality for low resolution content'
|
||||
};
|
||||
}
|
||||
|
||||
if (totalPixels < 2000000) { // < 2MP (résolution moyenne)
|
||||
return {
|
||||
method: 'realesrgan',
|
||||
model: 'realesrgan-x4plus',
|
||||
reason: 'Good balance of quality and speed'
|
||||
};
|
||||
}
|
||||
|
||||
// Haute résolution - utiliser Lanczos pour la vitesse
|
||||
return {
|
||||
method: 'lanczos',
|
||||
model: null,
|
||||
reason: 'Fast processing for high resolution content'
|
||||
};
|
||||
}
|
||||
|
||||
// ========== EXEMPLE D'UTILISATION ==========
|
||||
|
||||
/*
|
||||
// Utilisation basique
|
||||
const upscaledPath = await upscaleVideo('/path/to/video.mp4');
|
||||
|
||||
// Utilisation avancée
|
||||
const upscaledPath = await upscaleVideo('/path/to/video.mp4', {
|
||||
scale: 4,
|
||||
method: 'realesrgan',
|
||||
model: 'realesr-animevideov3',
|
||||
outputSuffix: '_4x_upscaled',
|
||||
deleteOriginal: false
|
||||
});
|
||||
|
||||
// Auto-détection de la meilleure méthode
|
||||
const videoInfo = await getVideoInfo(inputPath);
|
||||
const recommendation = getRecommendedUpscaleMethod(videoInfo, 'mixed');
|
||||
const upscaledPath = await upscaleVideo(inputPath, {
|
||||
method: recommendation.method,
|
||||
model: recommendation.model,
|
||||
scale: 2
|
||||
});
|
||||
*/
|
||||
|
||||
const remuxToMKV = async (options) => {
|
||||
const {
|
||||
mp4FilePath, // Mode classique (MP4 existant)
|
||||
videoFilePath, // Mode direct (fichier vidéo décrypté)
|
||||
audioFiles, // Mode direct (array de fichiers audio)
|
||||
subtitleFiles, // Mode direct (array de fichiers SRT)
|
||||
outputPath,
|
||||
filename,
|
||||
wantedAudioTracks,
|
||||
wantedSubtitles,
|
||||
videoInfo
|
||||
} = options;
|
||||
|
||||
try {
|
||||
console.log('🎬 Starting MKV creation...');
|
||||
|
||||
const mkvFilePath = `${outputPath}/${filename}.mkv`;
|
||||
const isDirect = videoFilePath && audioFiles; // Mode direct si ces params sont fournis
|
||||
|
||||
console.log(`📝 Mode: ${isDirect ? 'Direct from decrypted files' : 'Remux from MP4'}`);
|
||||
|
||||
// Construction de la commande de base
|
||||
let mkvCommand = `mkvmerge -o "${mkvFilePath}" --verbose --title "${filename}"`;
|
||||
|
||||
// === PARTIE VIDÉO ===
|
||||
const resolution = videoInfo?.resolution || { width: 'Unknown', height: 'Unknown' };
|
||||
const videoFile = isDirect ? videoFilePath : mp4FilePath;
|
||||
|
||||
mkvCommand += ` --language 0:und --track-name "0:Video ${resolution.width}x${resolution.height}" "${videoFile}"`;
|
||||
|
||||
// === PARTIE AUDIO ===
|
||||
if (isDirect) {
|
||||
wantedAudioTracks.forEach((audioTrack, index) => {
|
||||
const { language, codec, bitrate } = extractAudioTrackInfo(audioTrack);
|
||||
|
||||
const matchingAudioFile = audioFiles.find(audioFile => {
|
||||
const filename = path.basename(audioFile);
|
||||
return filename.includes(`.${language.toLowerCase()}_`) || filename.includes(`_${language.toLowerCase()}.`) || filename.includes(`_${language.toLowerCase()}_`);
|
||||
});
|
||||
|
||||
if (matchingAudioFile) {
|
||||
console.log(`🔗 Matching ${language} track with: ${path.basename(matchingAudioFile)}`);
|
||||
|
||||
mkvCommand += ` --language 0:${language}`;
|
||||
mkvCommand += ` --track-name "0:${language.toUpperCase()} ${codec} ${bitrate}kbps"`;
|
||||
mkvCommand += ` --default-track 0:${index === 0 ? 'yes' : 'no'}`;
|
||||
mkvCommand += ` "${matchingAudioFile}"`;
|
||||
} else {
|
||||
console.warn(`⚠️ No audio file found for language: ${language}`);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
wantedAudioTracks.forEach((audioTrack, index) => {
|
||||
const { language, codec, bitrate } = extractAudioTrackInfo(audioTrack);
|
||||
const trackIndex = index + 1; // +1 car 0 = vidéo
|
||||
|
||||
mkvCommand += ` --language ${trackIndex}:${language}`;
|
||||
mkvCommand += ` --track-name "${trackIndex}:${language.toUpperCase()}"`;
|
||||
mkvCommand += ` --default-track ${trackIndex}:${index === 0 ? 'yes' : 'no'}`;
|
||||
});
|
||||
}
|
||||
|
||||
// === PARTIE SOUS-TITRES ===
|
||||
const srtFilesToProcess = isDirect ? subtitleFiles : findSrtFiles(outputPath, filename);
|
||||
|
||||
// Matcher les sous-titres par langue comme pour l'audio
|
||||
wantedSubtitles.forEach((subtitleInfo, index) => {
|
||||
const language = subtitleInfo.language || 'und';
|
||||
|
||||
// Trouver le fichier SRT correspondant à cette langue
|
||||
const matchingSrtFile = srtFilesToProcess.find(srtPath => {
|
||||
const filename = path.basename(srtPath).toLowerCase();
|
||||
return filename.includes(`.${language.toLowerCase()}_`) ||
|
||||
filename.includes(`_${language.toLowerCase()}.`) ||
|
||||
filename.includes(`_${language.toLowerCase()}_`) ||
|
||||
filename.includes(`.${language.toLowerCase()}.`);
|
||||
});
|
||||
|
||||
if (matchingSrtFile) {
|
||||
console.log(`🔗 Matching ${language} subtitle with: ${path.basename(matchingSrtFile)}`);
|
||||
|
||||
const { isForced, trackName } = extractSubtitleInfo(subtitleInfo, matchingSrtFile);
|
||||
|
||||
mkvCommand += ` --language 0:${language}`;
|
||||
mkvCommand += ` --track-name "0:${trackName}"`;
|
||||
@@ -531,59 +856,332 @@ const remuxToMKV = async (options) => {
|
||||
mkvCommand += ` --forced-track 0:yes`;
|
||||
}
|
||||
|
||||
const defaultSub = (i === 0 && wantedAudioTracks.length > 0 && language === wantedAudioTracks[0].language) ? 'yes' : 'no';
|
||||
mkvCommand += ` --default-track 0:${defaultSub}`;
|
||||
const defaultAudioLang = wantedAudioTracks[0]?.language;
|
||||
const isDefault = (index === 0 && language === defaultAudioLang) ? 'yes' : 'no';
|
||||
mkvCommand += ` --default-track 0:${isDefault}`;
|
||||
|
||||
mkvCommand += ` "${srtPath}"`;
|
||||
}
|
||||
mkvCommand += ` "${matchingSrtFile}"`;
|
||||
} else {
|
||||
console.warn(`⚠️ No subtitle file found for language: ${language}`);
|
||||
}
|
||||
});
|
||||
|
||||
console.log('🔧 MKV Command:', mkvCommand);
|
||||
console.log('⚙️ Will execute:', mkvCommand);
|
||||
|
||||
await runCommand(mkvCommand);
|
||||
|
||||
console.log(`✅ MKV remux completed: ${mkvFilePath}`);
|
||||
console.log(`✅ MKV creation completed: ${mkvFilePath}`);
|
||||
|
||||
//fs.unlinkSync(mp4FilePath);
|
||||
//srtFiles.forEach(srt => fs.unlinkSync(`${outputPath}/${srt}`));
|
||||
// Nettoyage optionnel (commenté pour sécurité)
|
||||
// if (!isDirect && mp4FilePath) fs.unlinkSync(mp4FilePath);
|
||||
// srtFilesToProcess.forEach(srt => fs.unlinkSync(srt));
|
||||
|
||||
return mkvFilePath;
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ MKV remux failed:', error);
|
||||
throw new Error(`MKV remux failed: ${error.message}`);
|
||||
console.error('❌ MKV creation failed:', error);
|
||||
throw new Error(`MKV creation failed: ${error.message}`);
|
||||
}
|
||||
};
|
||||
|
||||
// Processus de la file d'attente
|
||||
videoQueue.process((job) => {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
// ========== FONCTIONS UTILITAIRES ==========
|
||||
|
||||
function extractAudioTrackInfo(audioTrack) {
|
||||
const language = audioTrack.language || 'und';
|
||||
const codec = audioTrack.attributes?.CODECS?.split('.')[0] || 'unknown';
|
||||
const bitrate = Math.round((audioTrack.attributes?.BANDWIDTH || 0) / 1000);
|
||||
|
||||
return { language, codec, bitrate };
|
||||
}
|
||||
|
||||
function extractSubtitleInfo(subtitleInfo, srtPath) {
|
||||
const language = subtitleInfo.language || 'und';
|
||||
|
||||
// Détecter si c'est forcé via le nom du fichier ou les métadonnées
|
||||
const filename = path.basename(srtPath).toLowerCase();
|
||||
const isForced = false;//filename.includes('forced') ||
|
||||
//filename.includes('sdh') === false ||
|
||||
//subtitleInfo.forced === true;
|
||||
|
||||
const trackName = isForced ? `${language.toUpperCase()} Forced` : language.toUpperCase();
|
||||
|
||||
return { language, isForced, trackName };
|
||||
}
|
||||
|
||||
function findSrtFiles(outputPath, filename) {
|
||||
try {
|
||||
console.log('Will launch job')
|
||||
const { mp4Filename, mpdUrl, keys, wantedResolution, wantedAudioTracks, wantedSubtitles, wantedRemux } = job.data;
|
||||
const downloaderPath = softwareService.getLocalBinFileInfo('downloader').path;
|
||||
const mp4decryptPath = softwareService.getLocalBinFileInfo('mp4decrypt').path;
|
||||
return fs.readdirSync(outputPath)
|
||||
.filter(file => file.startsWith(filename) && file.endsWith('.srt'))
|
||||
.map(file => `${outputPath}/${file}`)
|
||||
.sort(); // Tri pour ordre prévisible
|
||||
} catch (error) {
|
||||
console.warn('⚠️ Could not read SRT files:', error.message);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
const workdir = path.join(TMP_PATH, mp4Filename);
|
||||
if (!fs.existsSync(workdir))
|
||||
fs.mkdirSync(workdir);
|
||||
const mp4FilenameWithExt= `${mp4Filename}.mp4`;
|
||||
const finalPath = path.join(OUTPUT_PATH, mp4Filename);
|
||||
// Processus de la file d'attente
|
||||
// videoQueue.process((job) => {
|
||||
// return new Promise(async (resolve, reject) => {
|
||||
// try {
|
||||
// console.log('Will launch job')
|
||||
// const { mp4Filename, mpdUrl, keys, wantedResolution, wantedAudioTracks, wantedSubtitles, wantedRemux } = job.data;
|
||||
// const downloaderPath = softwareService.getLocalBinFileInfo('downloader').path;
|
||||
// const mp4decryptPath = softwareService.getLocalBinFileInfo('mp4decrypt').path;
|
||||
|
||||
// console.log('wantedSubtitles', wantedSubtitles);
|
||||
// const workdir = path.join(TMP_PATH, mp4Filename);
|
||||
// if (!fs.existsSync(workdir))
|
||||
// fs.mkdirSync(workdir);
|
||||
// const mp4FilenameWithExt= `${mp4Filename}.mp4`;
|
||||
// const finalPath = path.join(OUTPUT_PATH, mp4Filename);
|
||||
|
||||
// const filesExist = await checkFilesExistance('encrypted', workdir);
|
||||
// if (filesExist.length === 0) {
|
||||
// console.log('Encrypted files not found, downloading...');
|
||||
// let objectsDownloaded = -1, previousPercentage = -1;
|
||||
// const objectsToDownload = 1 + wantedAudioTracks.length + wantedSubtitles.length;
|
||||
// job.progress(10); // Début à 10%
|
||||
|
||||
// const bwAudio = wantedAudioTracks.length === 1 ? `:bwMin=\"${wantedAudioTracks.map(elem => Math.floor(elem.attributes.BANDWIDTH / 1000 -1)).join('|')}\":bwMax=\"${wantedAudioTracks.map(elem => Math.round(elem.attributes.BANDWIDTH / 1000 + 1)).join('|')}\"` : '';
|
||||
// const bwSubs = wantedSubtitles.length === 1 ? `:bwMin=\"${wantedSubtitles.map(elem => Math.floor(elem.attributes.BANDWIDTH / 1000 -1)).join('|')}\":bwMax=\"${wantedSubtitles.map(elem => Math.round(elem.attributes.BANDWIDTH / 1000 + 1)).join('|')}\"` : '';
|
||||
|
||||
// const subPart = wantedSubtitles.length > 0 ? `--select-subtitle lang=\"${wantedSubtitles.map(elem => elem.language).join('|')}\"${bwSubs}` : '--drop-subtitle lang=\".*\"';
|
||||
// const audioPart = wantedAudioTracks.length > 0 ? `--select-audio lang=\"${wantedAudioTracks.map(elem => elem.language).join('|')}\":codecs=\"${[...new Set(wantedAudioTracks.map(elem => elem.attributes.CODECS))].join('|')}\":for=all${bwAudio}` : '--drop-audio lang=\".*\"';
|
||||
|
||||
// const { executeCommand, emitter } = runProgressCommand(`${downloaderPath} \"${mpdUrl}\" --save-dir ${workdir} --save-name ${mp4Filename}_encrypted --select-video id=\"${wantedResolution.id}\" ${audioPart} ${subPart}`, true);
|
||||
|
||||
// emitter.on('percentage', (percentage) => {
|
||||
// if (percentage < previousPercentage) {
|
||||
// objectsDownloaded++;
|
||||
// }
|
||||
// previousPercentage = percentage;
|
||||
|
||||
// const subPercMax = 50 / objectsToDownload;
|
||||
// job.progress(Math.round((10 + (objectsDownloaded * subPercMax)) + (percentage * subPercMax / 100)));
|
||||
// });
|
||||
|
||||
// await executeCommand;
|
||||
// } else {
|
||||
// console.log('Encrypted files already exist, bypassing download...')
|
||||
// }
|
||||
// job.progress(60);
|
||||
|
||||
// // Decrypt video stream
|
||||
// await runCommand(`${mp4decryptPath} ${keys.map(k => `--key ${k.key}:${k.value}`).join(' ')} "${workdir}/${mp4Filename}_encrypted.mp4" "${workdir}/${mp4Filename}_decrypted.mp4"`);
|
||||
|
||||
// job.progress(70);
|
||||
|
||||
// // Decrypt audio streams
|
||||
// const audioFiles = fs.readdirSync(workdir);
|
||||
// const finalAudio = [];
|
||||
// for (const file of audioFiles) {
|
||||
// if (file.startsWith(`${mp4Filename}_encrypted`) && file.endsWith('.m4a')) {
|
||||
// const baseName = path.basename(file, '.m4a');
|
||||
// await runCommand(`${mp4decryptPath} ${keys.map(k => `--key ${k.key}:${k.value}`).join(' ')} "${workdir}/${file}" "${workdir}/${baseName}_decrypted.m4a"`);
|
||||
// finalAudio.push(`${workdir}/${baseName}_decrypted.m4a`);
|
||||
// }
|
||||
// }
|
||||
|
||||
// job.progress(80);
|
||||
|
||||
// // Combinaison avec ffmpeg
|
||||
// let ffmpegCommand = `ffmpeg -y -i ${workdir}/${mp4Filename}_decrypted.mp4`;
|
||||
// let mapCommand = ' -map 0:v';
|
||||
// let inputIndex = 1;
|
||||
|
||||
// for (const file of finalAudio) {
|
||||
// ffmpegCommand += ` -i ${file}`;
|
||||
// mapCommand += ` -map ${inputIndex}:a`;
|
||||
// inputIndex++;
|
||||
// }
|
||||
|
||||
// if (!fs.existsSync(finalPath))
|
||||
// fs.mkdirSync(finalPath);
|
||||
|
||||
// ffmpegCommand += `${mapCommand} -c copy ${finalPath}/${mp4FilenameWithExt}`;
|
||||
// await runCommand(ffmpegCommand);
|
||||
|
||||
// job.progress(90);
|
||||
|
||||
// if (wantedRemux) {
|
||||
// console.log('🎬 Starting optional MKV remux...');
|
||||
|
||||
// try {
|
||||
// await remuxToMKV({
|
||||
// mp4FilePath: `${finalPath}/${mp4FilenameWithExt}`,
|
||||
// outputPath: finalPath,
|
||||
// filename: mp4Filename,
|
||||
// wantedAudioTracks,
|
||||
// wantedSubtitles,
|
||||
// videoInfo: wantedResolution
|
||||
// });
|
||||
|
||||
// job.progress(95);
|
||||
// console.log('✅ MKV remux completed successfully');
|
||||
|
||||
// } catch (remuxError) {
|
||||
// console.error('⚠️ MKV remux failed, keeping MP4:', remuxError);
|
||||
// }
|
||||
// }
|
||||
|
||||
// // Renommage des fichiers SRT
|
||||
// const subFiles = fs.readdirSync(workdir);
|
||||
// let counter = 1;
|
||||
// for (const file of subFiles) {
|
||||
// if (file.startsWith(`${mp4Filename}_encrypted`) && file.endsWith('.srt')) {
|
||||
// const sourcePath = `${workdir}/${file}`;
|
||||
// const destPath = `${finalPath}/${mp4Filename}_${counter}.srt`;
|
||||
|
||||
// await safeMove(sourcePath, destPath);
|
||||
// counter++;
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
// // Nettoyage (commenté pour correspondre au script original)
|
||||
// await runCommand(`rm -fr ${workdir}`);
|
||||
|
||||
// job.progress(100);
|
||||
// resolve({ message: `File fetched and decrypted with success: ${mp4Filename}.mp4`, filePath: `${OUTPUT_PATH}/${mp4Filename}.mp4`, fileName: `${mp4Filename}.mp4` });
|
||||
// } catch (error) {
|
||||
// console.log('Error while processing task', error)
|
||||
// reject(new Error(`${error.toString() || error}`));
|
||||
// }
|
||||
// });
|
||||
// });
|
||||
|
||||
videoQueue.process(async (job) => {
|
||||
try {
|
||||
console.log('🚀 Starting video processing job');
|
||||
const { mp4Filename, mpdUrl, keys, wantedResolution, wantedAudioTracks, wantedSubtitles, wantedRemux, wantToUpscale } = job.data;
|
||||
|
||||
const config = {
|
||||
downloaderPath: softwareService.getLocalBinFileInfo('downloader').path,
|
||||
mp4decryptPath: softwareService.getLocalBinFileInfo('mp4decrypt').path,
|
||||
workdir: path.join(TMP_PATH, mp4Filename),
|
||||
mp4FilenameWithExt: `${mp4Filename}.mp4`,
|
||||
finalPath: path.join(OUTPUT_PATH, mp4Filename)
|
||||
};
|
||||
|
||||
await prepareDirectories(config);
|
||||
|
||||
await downloadEncryptedFiles(job, config, { mpdUrl, wantedResolution, wantedAudioTracks, wantedSubtitles, mp4Filename });
|
||||
job.progress(60);
|
||||
|
||||
await decryptVideoFile(config, keys, mp4Filename);
|
||||
job.progress(70);
|
||||
|
||||
const decryptedAudioFiles = await decryptAudioFiles(config, keys, mp4Filename);
|
||||
job.progress(80);
|
||||
|
||||
if (wantToUpscale) {
|
||||
await upscaleVideo(`${config.workdir}/${mp4Filename}_decrypted.mp4`, {
|
||||
method: 'lanczos',
|
||||
deleteOriginal: true,
|
||||
});
|
||||
}
|
||||
|
||||
if (wantedRemux) {
|
||||
console.log('🎬 Direct MKV creation with mkvtoolnix (bypassing FFmpeg)...');
|
||||
await createMKVDirectly(config, {
|
||||
decryptedAudioFiles,
|
||||
wantedAudioTracks,
|
||||
wantedSubtitles,
|
||||
videoInfo: wantedResolution,
|
||||
mp4Filename
|
||||
});
|
||||
job.progress(95);
|
||||
} else {
|
||||
await combineMediaFiles(config, decryptedAudioFiles, mp4Filename);
|
||||
job.progress(90);
|
||||
}
|
||||
|
||||
await handleSubtitlesAndCleanup(config, mp4Filename);
|
||||
job.progress(100);
|
||||
|
||||
const finalExtension = wantedRemux ? '.mkv' : '.mp4';
|
||||
const finalFileName = `${mp4Filename}${finalExtension}`;
|
||||
|
||||
return {
|
||||
message: `File fetched and decrypted with success: ${finalFileName}`,
|
||||
filePath: `${OUTPUT_PATH}/${mp4Filename}/${finalFileName}`,
|
||||
fileName: finalFileName
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Error while processing task:', error);
|
||||
throw new Error(`Processing failed: ${error.message || error}`);
|
||||
}
|
||||
});
|
||||
|
||||
async function createMKVDirectly(config, { decryptedAudioFiles, wantedAudioTracks, wantedSubtitles, videoInfo, mp4Filename }) {
|
||||
console.log('🎬 Creating MKV directly from decrypted files...');
|
||||
const { workdir, finalPath } = config;
|
||||
|
||||
const subFiles = fs.readdirSync(workdir);
|
||||
const srtFiles = subFiles
|
||||
.filter(file => file.startsWith(`${mp4Filename}_encrypted`) && file.endsWith('.srt'))
|
||||
.map(file => `${workdir}/${file}`);
|
||||
|
||||
try {
|
||||
await remuxToMKV({
|
||||
videoFilePath: `${workdir}/${mp4Filename}_decrypted.mp4`,
|
||||
audioFiles: decryptedAudioFiles,
|
||||
subtitleFiles: srtFiles,
|
||||
outputPath: finalPath,
|
||||
filename: mp4Filename,
|
||||
wantedAudioTracks,
|
||||
wantedSubtitles,
|
||||
videoInfo
|
||||
});
|
||||
|
||||
console.log('✅ Direct MKV creation completed successfully');
|
||||
} catch (remuxError) {
|
||||
console.error('❌ Direct MKV creation failed:', remuxError);
|
||||
// Fallback vers FFmpeg si échec
|
||||
console.log('🔄 Falling back to FFmpeg + remux...');
|
||||
await combineMediaFiles(config, decryptedAudioFiles, mp4Filename);
|
||||
await performMKVRemux(config, { wantedAudioTracks, wantedSubtitles, videoInfo: videoInfo, mp4Filename });
|
||||
}
|
||||
}
|
||||
|
||||
async function prepareDirectories(config) {
|
||||
const { workdir, finalPath } = config;
|
||||
|
||||
if (!fs.existsSync(workdir)) {
|
||||
fs.mkdirSync(workdir, { recursive: true });
|
||||
}
|
||||
if (!fs.existsSync(finalPath)) {
|
||||
fs.mkdirSync(finalPath, { recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
async function downloadEncryptedFiles(job, config, { mpdUrl, wantedResolution, wantedAudioTracks, wantedSubtitles, mp4Filename }) {
|
||||
const { workdir, downloaderPath } = config;
|
||||
const filesExist = await checkFilesExistance('encrypted', workdir);
|
||||
if (filesExist.length === 0) {
|
||||
console.log('Encrypted files not found, downloading...');
|
||||
let objectsDownloaded = -1, previousPercentage = -1;
|
||||
|
||||
if (filesExist.length > 0) {
|
||||
console.log('📁 Encrypted files already exist, bypassing download...');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('⬇️ Encrypted files not found, downloading...');
|
||||
|
||||
// Construction des paramètres de téléchargement
|
||||
const downloadParams = buildDownloadParameters(wantedAudioTracks, wantedSubtitles);
|
||||
const objectsToDownload = 1 + wantedAudioTracks.length + wantedSubtitles.length;
|
||||
job.progress(10); // Début à 10%
|
||||
|
||||
const bwAudio = wantedAudioTracks.length === 1 ? `:bwMin=\"${wantedAudioTracks.map(elem => Math.floor(elem.attributes.BANDWIDTH / 1000 -1)).join('|')}\":bwMax=\"${wantedAudioTracks.map(elem => Math.round(elem.attributes.BANDWIDTH / 1000 + 1)).join('|')}\"` : '';
|
||||
const bwSubs = wantedSubtitles.length === 1 ? `:bwMin=\"${wantedSubtitles.map(elem => Math.floor(elem.attributes.BANDWIDTH / 1000 -1)).join('|')}\":bwMax=\"${wantedSubtitles.map(elem => Math.round(elem.attributes.BANDWIDTH / 1000 + 1)).join('|')}\"` : '';
|
||||
job.progress(10);
|
||||
|
||||
const subPart = wantedSubtitles.length > 0 ? `--select-subtitle lang=\"${wantedSubtitles.map(elem => elem.language).join('|')}\"${bwSubs}` : '--drop-subtitle lang=\".*\"';
|
||||
const audioPart = wantedAudioTracks.length > 0 ? `--select-audio lang=\"${wantedAudioTracks.map(elem => elem.language).join('|')}\":codecs=\"${[...new Set(wantedAudioTracks.map(elem => elem.attributes.CODECS))].join('|')}\":for=all${bwAudio}` : '--drop-audio lang=\".*\"';
|
||||
const command = `${downloaderPath} "${mpdUrl}" --save-dir ${workdir} --save-name ${mp4Filename}_encrypted --select-video id="${wantedResolution.id}" ${downloadParams.audioPart} ${downloadParams.subPart}`;
|
||||
|
||||
const { executeCommand, emitter } = runProgressCommand(`${downloaderPath} \"${mpdUrl}\" --save-dir ${workdir} --save-name ${mp4Filename}_encrypted --select-video id=\"${wantedResolution.id}\" ${audioPart} ${subPart}`, true);
|
||||
const { executeCommand, emitter } = runProgressCommand(command, true);
|
||||
|
||||
// Gestion du progress avec meilleure lisibilité
|
||||
let objectsDownloaded = -1;
|
||||
let previousPercentage = -1;
|
||||
|
||||
emitter.on('percentage', (percentage) => {
|
||||
if (percentage < previousPercentage) {
|
||||
@@ -592,98 +1190,133 @@ videoQueue.process((job) => {
|
||||
previousPercentage = percentage;
|
||||
|
||||
const subPercMax = 50 / objectsToDownload;
|
||||
job.progress(Math.round((10 + (objectsDownloaded * subPercMax)) + (percentage * subPercMax / 100)));
|
||||
const newProgress = Math.round((10 + (objectsDownloaded * subPercMax)) + (percentage * subPercMax / 100));
|
||||
job.progress(newProgress);
|
||||
});
|
||||
|
||||
await executeCommand;
|
||||
} else {
|
||||
console.log('Encrypted files already exist, bypassing download...')
|
||||
}
|
||||
job.progress(60);
|
||||
|
||||
// Decrypt video stream
|
||||
await runCommand(`${mp4decryptPath} ${keys.map(k => `--key ${k.key}:${k.value}`).join(' ')} "${workdir}/${mp4Filename}_encrypted.mp4" "${workdir}/${mp4Filename}_decrypted.mp4"`);
|
||||
function buildDownloadParameters(wantedAudioTracks, wantedSubtitles) {
|
||||
// Construire les paramètres bandwidth pour audio
|
||||
const bwAudio = wantedAudioTracks.length === 1
|
||||
? `:bwMin="${wantedAudioTracks.map(elem => Math.floor(elem.attributes.BANDWIDTH / 1000 - 1)).join('|')}":bwMax="${wantedAudioTracks.map(elem => Math.round(elem.attributes.BANDWIDTH / 1000 + 1)).join('|')}"`
|
||||
: '';
|
||||
|
||||
job.progress(70);
|
||||
// Construire les paramètres bandwidth pour sous-titres
|
||||
const bwSubs = wantedSubtitles.length === 1
|
||||
? `:bwMin="${wantedSubtitles.map(elem => Math.floor(elem.attributes.BANDWIDTH / 1000 - 1)).join('|')}":bwMax="${wantedSubtitles.map(elem => Math.round(elem.attributes.BANDWIDTH / 1000 + 1)).join('|')}"`
|
||||
: '';
|
||||
|
||||
// Construire les parties de la commande
|
||||
const subPart = wantedSubtitles.length > 0
|
||||
? `--select-subtitle lang="${wantedSubtitles.map(elem => elem.language).join('|')}"${bwSubs}`
|
||||
: '--drop-subtitle lang=".*"';
|
||||
|
||||
const audioPart = wantedAudioTracks.length > 0
|
||||
? `--select-audio lang="${wantedAudioTracks.map(elem => elem.language).join('|')}":codecs="${[...new Set(wantedAudioTracks.map(elem => elem.attributes.CODECS))].join('|')}":for=all${bwAudio}`
|
||||
: '--drop-audio lang=".*"';
|
||||
|
||||
return { audioPart, subPart };
|
||||
}
|
||||
|
||||
async function decryptVideoFile(config, keys, mp4Filename) {
|
||||
console.log('🔓 Decrypting video stream...');
|
||||
const { workdir, mp4decryptPath } = config;
|
||||
|
||||
const keyParams = keys.map(k => `--key ${k.key}:${k.value}`).join(' ');
|
||||
const command = `${mp4decryptPath} ${keyParams} "${workdir}/${mp4Filename}_encrypted.mp4" "${workdir}/${mp4Filename}_decrypted.mp4"`;
|
||||
|
||||
await runCommand(command);
|
||||
}
|
||||
|
||||
async function decryptAudioFiles(config, keys, mp4Filename) {
|
||||
console.log('🔓 Decrypting audio streams...');
|
||||
const { workdir, mp4decryptPath } = config;
|
||||
|
||||
// Decrypt audio streams
|
||||
const audioFiles = fs.readdirSync(workdir);
|
||||
const finalAudio = [];
|
||||
for (const file of audioFiles) {
|
||||
if (file.startsWith(`${mp4Filename}_encrypted`) && file.endsWith('.m4a')) {
|
||||
const encryptedAudioFiles = audioFiles.filter(file =>
|
||||
file.startsWith(`${mp4Filename}_encrypted`) && file.endsWith('.m4a')
|
||||
);
|
||||
|
||||
const decryptedAudioFiles = [];
|
||||
const keyParams = keys.map(k => `--key ${k.key}:${k.value}`).join(' ');
|
||||
|
||||
// Déchiffrer tous les fichiers audio
|
||||
for (const file of encryptedAudioFiles) {
|
||||
const baseName = path.basename(file, '.m4a');
|
||||
await runCommand(`${mp4decryptPath} ${keys.map(k => `--key ${k.key}:${k.value}`).join(' ')} "${workdir}/${file}" "${workdir}/${baseName}_decrypted.m4a"`);
|
||||
finalAudio.push(`${workdir}/${baseName}_decrypted.m4a`);
|
||||
}
|
||||
const decryptedPath = `${workdir}/${baseName}_decrypted.m4a`;
|
||||
|
||||
const command = `${mp4decryptPath} ${keyParams} "${workdir}/${file}" "${decryptedPath}"`;
|
||||
await runCommand(command);
|
||||
|
||||
decryptedAudioFiles.push(decryptedPath);
|
||||
}
|
||||
|
||||
job.progress(80);
|
||||
return decryptedAudioFiles;
|
||||
}
|
||||
|
||||
// Combinaison avec ffmpeg
|
||||
async function combineMediaFiles(config, decryptedAudioFiles, mp4Filename) {
|
||||
console.log('🎬 Combining media files with FFmpeg...');
|
||||
const { workdir, finalPath, mp4FilenameWithExt } = config;
|
||||
|
||||
// Construction de la commande FFmpeg
|
||||
let ffmpegCommand = `ffmpeg -y -i ${workdir}/${mp4Filename}_decrypted.mp4`;
|
||||
let mapCommand = ' -map 0:v';
|
||||
let inputIndex = 1;
|
||||
|
||||
for (const file of finalAudio) {
|
||||
// Ajouter les inputs audio et leurs mappings
|
||||
decryptedAudioFiles.forEach((file, index) => {
|
||||
const inputIndex = index + 1;
|
||||
ffmpegCommand += ` -i ${file}`;
|
||||
mapCommand += ` -map ${inputIndex}:a`;
|
||||
inputIndex++;
|
||||
}
|
||||
|
||||
if (!fs.existsSync(finalPath))
|
||||
fs.mkdirSync(finalPath);
|
||||
});
|
||||
|
||||
ffmpegCommand += `${mapCommand} -c copy ${finalPath}/${mp4FilenameWithExt}`;
|
||||
await runCommand(ffmpegCommand);
|
||||
}
|
||||
|
||||
job.progress(90);
|
||||
|
||||
if (wantedRemux) {
|
||||
async function performMKVRemux(config, { wantedAudioTracks, wantedSubtitles, videoInfo, mp4Filename }) {
|
||||
console.log('🎬 Starting optional MKV remux...');
|
||||
|
||||
try {
|
||||
await remuxToMKV({
|
||||
mp4FilePath: `${finalPath}/${mp4FilenameWithExt}`,
|
||||
outputPath: finalPath,
|
||||
mp4FilePath: `${config.finalPath}/${config.mp4FilenameWithExt}`,
|
||||
outputPath: config.finalPath,
|
||||
filename: mp4Filename,
|
||||
wantedAudioTracks,
|
||||
wantedSubtitles,
|
||||
videoInfo: wantedResolution
|
||||
videoInfo
|
||||
});
|
||||
|
||||
job.progress(95);
|
||||
console.log('✅ MKV remux completed successfully');
|
||||
|
||||
} catch (remuxError) {
|
||||
console.error('⚠️ MKV remux failed, keeping MP4:', remuxError);
|
||||
// On continue sans faire échouer le job
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSubtitlesAndCleanup(config, mp4Filename) {
|
||||
console.log('📝 Handling subtitles and cleanup...');
|
||||
const { workdir, finalPath } = config;
|
||||
|
||||
// Renommage des fichiers SRT
|
||||
const subFiles = fs.readdirSync(workdir);
|
||||
let counter = 1;
|
||||
for (const file of subFiles) {
|
||||
if (file.startsWith(`${mp4Filename}_encrypted`) && file.endsWith('.srt')) {
|
||||
const srtFiles = subFiles.filter(file =>
|
||||
file.startsWith(`${mp4Filename}_encrypted`) && file.endsWith('.srt')
|
||||
);
|
||||
|
||||
for (let i = 0; i < srtFiles.length; i++) {
|
||||
const file = srtFiles[i];
|
||||
const sourcePath = `${workdir}/${file}`;
|
||||
const destPath = `${finalPath}/${mp4Filename}_${counter}.srt`;
|
||||
const destPath = `${finalPath}/${mp4Filename}_${i + 1}.srt`;
|
||||
|
||||
await safeMove(sourcePath, destPath);
|
||||
counter++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Nettoyage (commenté pour correspondre au script original)
|
||||
await runCommand(`rm -fr ${workdir}`);
|
||||
|
||||
job.progress(100);
|
||||
resolve({ message: `File fetched and decrypted with success: ${mp4Filename}.mp4`, filePath: `${OUTPUT_PATH}/${mp4Filename}.mp4`, fileName: `${mp4Filename}.mp4` });
|
||||
} catch (error) {
|
||||
console.log('Error while processing task', error)
|
||||
reject(new Error(`${error.toString() || error}`));
|
||||
// Nettoyage du répertoire de travail
|
||||
console.log('🧹 Cleaning up temporary files...');
|
||||
await runCommand(`rm -rf ${workdir}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
app.listen(port, () => {
|
||||
console.log(`Server running at http://localhost:${port}`);
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
"cheerio": "^1.1.2",
|
||||
"cors": "^2.8.5",
|
||||
"express": "^5.1.0",
|
||||
"ffprobe": "^1.1.2",
|
||||
"ffprobe-static": "^3.1.0",
|
||||
"fs": "^0.0.1-security",
|
||||
"mpd-parser": "^1.3.1",
|
||||
"path": "^0.12.7",
|
||||
|
||||
@@ -126,6 +126,10 @@ const getLocalBinFileInfo = (binType) => {
|
||||
stat: fs.existsSync(path) ? fs.statSync(path) : null,
|
||||
version: getBinVersion(`${BIN_PATH}/.${binType}.version`)
|
||||
}
|
||||
} else if (binType === 'realesrgan') {
|
||||
return {
|
||||
path: `${BIN_PATH}/realesrgan_macos/realesrgan-ncnn-vulkan`
|
||||
};
|
||||
} else {
|
||||
throw new Error(`Bad binType "${binType}" provided`);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user