alot of fixes
All checks were successful
ci / Image build (push) Successful in 2m41s
ci / Deployment (push) Successful in 27s

This commit is contained in:
Joris Bertomeu
2025-08-27 16:21:58 +02:00
parent b856269591
commit 08dd0a38a6
2 changed files with 485 additions and 77 deletions

547
index.js
View File

@@ -1,6 +1,6 @@
const express = require('express'); const express = require('express');
const Queue = require('bull'); const Queue = require('bull');
const { spawn } = require('child_process'); const { spawn, exec } = require('child_process');
const fs = require('fs'); const fs = require('fs');
const path = require('path'); const path = require('path');
const cors = require('cors'); const cors = require('cors');
@@ -10,6 +10,10 @@ const mpdParser = require('mpd-parser');
const softwareService = require('./services/softwares'); const softwareService = require('./services/softwares');
const ffprobe = require('ffprobe'); const ffprobe = require('ffprobe');
const ffprobeStatic = require('ffprobe-static'); const ffprobeStatic = require('ffprobe-static');
const { stringify } = require('querystring');
const xml2js = require('xml2js');
const { promisify } = require('util');
const execAsync = promisify(exec);
const BASE_PATH = process.env.DATA_PATH || `./data`; const BASE_PATH = process.env.DATA_PATH || `./data`;
const OUTPUT_PATH = process.env.OUTPUT_PATH || `${BASE_PATH}/output`; const OUTPUT_PATH = process.env.OUTPUT_PATH || `${BASE_PATH}/output`;
@@ -380,65 +384,457 @@ const formatDuration = (seconds) => {
} }
const parseMPDStream = async (mpdUrl) => { const parseMPDStream = async (mpdUrl) => {
const mpdResponse = await axios({ // Télécharger le manifest d'abord
url: mpdUrl, await downloadMPDManifest(mpdUrl, `./data/tmp/manifest.mpd`);
method: 'GET',
responseType: 'text'
});
const eventHandler = ({ type, message }) => console.log(`${type}: ${message}`);
const parsedManifest = mpdParser.parse(mpdResponse.data , { mpdUrl, eventHandler }); // 1. Parser le XML pour récupérer les KID
const mpdContent = fs.readFileSync('./data/tmp/manifest.mpd', 'utf8');
const parser = new xml2js.Parser({ explicitArray: false, mergeAttrs: true });
const parsedXML = await parser.parseStringPromise(mpdContent);
// Extraire les KID du XML par contentType et bandwidth
const kidMapping = {};
if (parsedXML.MPD?.Period) {
const periods = Array.isArray(parsedXML.MPD.Period) ? parsedXML.MPD.Period : [parsedXML.MPD.Period];
periods.forEach(period => {
if (period.AdaptationSet) {
const adaptationSets = Array.isArray(period.AdaptationSet) ? period.AdaptationSet : [period.AdaptationSet];
adaptationSets.forEach(adaptSet => {
let defaultKID = null;
// Chercher ContentProtection
if (adaptSet.ContentProtection) {
if (Array.isArray(adaptSet.ContentProtection)) {
defaultKID = adaptSet.ContentProtection[0]['cenc:default_KID'];
} else {
defaultKID = adaptSet.ContentProtection['cenc:default_KID'];
}
}
if (defaultKID && adaptSet.Representation) {
const representations = Array.isArray(adaptSet.Representation) ? adaptSet.Representation : [adaptSet.Representation];
representations.forEach(rep => {
const key = `${adaptSet.contentType}_${rep.bandwidth}`;
kidMapping[key] = defaultKID.replace(/-/g, '');
});
}
});
}
});
}
// 2. Parser avec N_m3u8DL-RE pour avoir les bonnes données
const baseUrl = mpdUrl.substring(0, mpdUrl.lastIndexOf('/') + 1);
const downloaderPath = softwareService.getLocalBinFileInfo('downloader').path;
const parseCommand = `${downloaderPath} "./data/tmp/manifest.mpd" --base-url "${baseUrl}" --skip-download -H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:142.0) Gecko/20100101 Firefox/142.0"`;
let stdout;
try {
const result = await execAsync(parseCommand);
stdout = result.stdout;
} catch (error) {
if (error.stdout) {
stdout = error.stdout;
} else {
throw error;
}
}
// 3. Parser N_m3u8DL-RE et associer les KID
const isValidDuration = (durationStr) => {
const timeMatch = durationStr.match(/~(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s)?/);
if (!timeMatch) return false;
const hours = parseInt(timeMatch[1]) || 0;
const minutes = parseInt(timeMatch[2]) || 0;
const totalMinutes = hours * 60 + minutes;
return totalMinutes >= 10 && totalMinutes <= 240;
};
const obj = { const obj = {
audioTracks: [], audioTracks: [],
videoTracks: [], videoTracks: [],
subtitles: [] subtitles: []
}; };
const toParse = [{
rootProp: 'AUDIO',
subProp: 'audio',
targetProp: 'audioTracks'
}, {
rootProp: 'SUBTITLES',
subProp: 'subs',
targetProp: 'subtitles'
}];
toParse.forEach(({ rootProp, subProp, targetProp }) => { const lines = stdout.split('\n');
try { lines.forEach(line => {
for (const [key, value] of Object.entries(parsedManifest?.mediaGroups?.[rootProp]?.[subProp])) { if (line.includes('INFO : Vid')) {
for (let i = 0; i < value.playlists.length; i++) { if (!/\d+x\d+/.test(line)) {
obj[targetProp].push({ console.log('Skipping non-video line:', line);
name: key, return;
language: value.language,
attributes: value.playlists[i].attributes
});
} }
const parts = line.split(' | ');
if (parts.length >= 7) {
const encrypted = parts[0].includes('*CENC');
const resolution = parts[0].match(/(\d+x\d+)/)[1];
const [width, height] = resolution.split('x').map(Number);
const kbps = parseInt(parts[1].match(/(\d+) Kbps/)[1]);
const videoId = parts[2].trim();
const fps = parseFloat(parts[3]);
const codec = parts[4].trim();
const duration = parts[7].trim();
if (!isValidDuration(duration)) {
console.log(`Skipping video track with suspicious duration (${duration}):`, line);
return;
} }
} catch(e) {
console.log(`No ${targetProp} found in manifest`); const bandwidth = kbps * 1000;
} const mappingKey1 = `video_${bandwidth}`;
}); const mappingKey2 = videoId.startsWith('video=') ? videoId : `video=${bandwidth}`;
for (let i = 0; i < parsedManifest.playlists.length; i++) { const defaultKID = kidMapping[mappingKey1] || kidMapping[mappingKey2] || null;
obj.videoTracks.push({ obj.videoTracks.push({
id: parsedManifest.playlists?.[i]?.attributes?.NAME, id: videoId,
name: `${parsedManifest.playlists?.[i]?.attributes?.RESOLUTION?.width || 'N/C'}x${parsedManifest.playlists?.[i]?.attributes?.RESOLUTION?.height || 'N/C'}`, name: resolution,
codec: parsedManifest.playlists?.[i]?.attributes?.CODECS, resolution: { width, height },
bandwidth: parsedManifest.playlists?.[i]?.attributes?.BANDWIDTH, bandwidth,
defaultKID: parsedManifest.playlists?.[i]?.contentProtection?.mp4protection?.attributes['cenc:default_KID']?.replaceAll('-', '') || null, fps,
fps: parsedManifest.playlists?.[i]?.attributes?.['FRAME-RATE'], codec,
resolution: { encrypted,
width: parsedManifest.playlists?.[i]?.attributes?.RESOLUTION?.width, formatedDuration: duration,
height: parsedManifest.playlists?.[i]?.attributes?.RESOLUTION?.height defaultKID
},
duration: parsedManifest.playlists?.[i]?.targetDuration,
formatedDuration: formatDuration(parsedManifest.playlists?.[i]?.targetDuration || 0)
}); });
} }
obj.videoTracks = obj.videoTracks.sort((a, b) => b.resolution.width - a.resolution.width); } else if (line.includes('INFO : Aud')) {
const audioMatch = line.match(/Aud (\*CENC )?(.+?) \| (\d+) Kbps \| ([^|]+) \| ([^|]+) \| ([^|]+) \| \d+ Segments? \| [^|]+ \| (~.+)$/);
if (audioMatch) {
const [, encrypted, audioId, kbps, codec, lang, channels, duration] = audioMatch;
if (!isValidDuration(duration)) return;
const bandwidth = parseInt(kbps) * 1000;
// Chercher le KID correspondant
const kidKey = `audio_${bandwidth}`;
const defaultKID = kidMapping[kidKey] || null;
obj.audioTracks.push({
id: audioId.trim(),
name: audioId.trim(),
language: lang.trim(),
bandwidth,
codec: codec.trim(),
channels: channels.trim(),
encrypted: !!encrypted,
formatedDuration: duration.trim(),
defaultKID
});
}
} else if (line.includes('INFO : Sub')) {
const parts = line.split(' | ');
if (parts.length >= 6) {
const subId = parts[0].replace(/.*INFO : Sub /, '').trim();
const lang = parts[1].trim();
const codec = parts[2].trim();
const duration = parts[5].trim();
if (!isValidDuration(duration)) return;
const defaultKID = kidMapping[subId] || kidMapping[`text_${subId}`] || null;
obj.subtitles.push({
id: subId,
name: subId,
language: lang,
codec,
encrypted: false,
formatedDuration: duration,
defaultKID
});
}
}
});
// Déduplication et tri
obj.videoTracks = obj.videoTracks
.filter((track, index, self) => index === self.findIndex(t => t.id === track.id))
.sort((a, b) => b.resolution.width - a.resolution.width);
obj.audioTracks = obj.audioTracks
.filter((track, index, self) => index === self.findIndex(t => t.id === track.id))
.sort((a, b) => b.bandwidth - a.bandwidth);
console.log(`Parsed ${obj.videoTracks.length} video tracks, ${obj.audioTracks.length} audio tracks`);
return obj; return obj;
}; };
// const parseMPDStream = async (mpdUrl) => {
// const mpdResponse = await axios({
// url: mpdUrl,
// method: 'GET',
// responseType: 'text'
// });
// const parser = new xml2js.Parser({ explicitArray: false, mergeAttrs: true });
// const parsedXML = await parser.parseStringPromise(mpdResponse.data);
// const obj = {
// audioTracks: [],
// videoTracks: [],
// subtitles: []
// };
// // Fonction pour parser la durée PT format vers secondes
// const parseDuration = (duration) => {
// if (!duration) return 0;
// // PT34M11.34S -> 34*60 + 11.34 = 2051.34 secondes
// const match = duration.match(/PT(?:(\d+)H)?(?:(\d+)M)?(?:([\d.]+)S)?/);
// if (!match) return 0;
// const hours = parseInt(match[1]) || 0;
// const minutes = parseInt(match[2]) || 0;
// const seconds = parseFloat(match[3]) || 0;
// return hours * 3600 + minutes * 60 + seconds;
// };
// // Fonction pour filtrer les periods avec contenu principal (> 10 minutes)
// const isMainContent = (duration) => {
// const durationInSeconds = parseDuration(duration);
// return durationInSeconds > 600; // Plus de 10 minutes
// };
// if (parsedXML.MPD && parsedXML.MPD.Period) {
// const periods = Array.isArray(parsedXML.MPD.Period) ? parsedXML.MPD.Period : [parsedXML.MPD.Period];
// // Filtrer seulement les periods de contenu principal
// const mainPeriods = periods.filter(period => isMainContent(period.duration));
// console.log(`Found ${periods.length} total periods, ${mainPeriods.length} main content periods`);
// mainPeriods.forEach((period, periodIndex) => {
// console.log(`Processing period: ${period.duration} (${parseDuration(period.duration)}s)`);
// if (period.AdaptationSet) {
// const adaptationSets = Array.isArray(period.AdaptationSet) ? period.AdaptationSet : [period.AdaptationSet];
// adaptationSets.forEach(adaptSet => {
// const contentType = adaptSet.contentType;
// let defaultKID = null;
// if (adaptSet.ContentProtection) {
// if (Array.isArray(adaptSet.ContentProtection)) {
// defaultKID = adaptSet.ContentProtection[0]['cenc:default_KID'];
// } else {
// defaultKID = adaptSet.ContentProtection['cenc:default_KID'];
// }
// if (defaultKID) {
// defaultKID = defaultKID.replace(/-/g, '');
// }
// }
// if (adaptSet.Representation) {
// const representations = Array.isArray(adaptSet.Representation) ? adaptSet.Representation : [adaptSet.Representation];
// representations.forEach(rep => {
// const baseTrack = {
// id: rep.id,
// codec: rep.codecs,
// bandwidth: parseInt(rep.bandwidth),
// defaultKID: defaultKID ? defaultKID.replace(/-/g, '') : null,
// duration: parseDuration(period.duration),
// formatedDuration: formatDuration(parseDuration(period.duration))
// };
// if (contentType === 'video') {
// obj.videoTracks.push({
// ...baseTrack,
// name: `${rep.width}x${rep.height}`,
// fps: parseFloat(rep.frameRate),
// resolution: {
// width: parseInt(rep.width),
// height: parseInt(rep.height)
// }
// });
// } else if (contentType === 'audio') {
// obj.audioTracks.push({
// ...baseTrack,
// name: rep.id || `${adaptSet.lang || 'unknown'}_${rep.bandwidth}`,
// language: adaptSet.lang || 'unknown',
// channels: rep.AudioChannelConfiguration ? rep.AudioChannelConfiguration.value + 'CH' : '2CH',
// audioSamplingRate: parseInt(rep.audioSamplingRate)
// });
// }
// });
// }
// });
// }
// });
// }
// obj.videoTracks = obj.videoTracks.sort((a, b) => b.resolution.width - a.resolution.width);
// obj.audioTracks = obj.audioTracks.sort((a, b) => b.bandwidth - a.bandwidth);
// obj.videoTracks = obj.videoTracks.filter((track, index, self) =>
// index === self.findIndex(t => t.id === track.id && t.bandwidth === track.bandwidth)
// );
// obj.audioTracks = obj.audioTracks.filter((track, index, self) =>
// index === self.findIndex(t => t.id === track.id && t.bandwidth === track.bandwidth)
// );
// return obj;
// };
// const parseMPDStream = async (mpdUrl) => {
// const mpdResponse = await axios({
// url: mpdUrl,
// method: 'GET',
// responseType: 'text'
// });
// const eventHandler = ({ type, message }) => console.log(`${type}: ${message}`);
// const parsedManifest = mpdParser.parse(mpdResponse.data , { mpdUrl, eventHandler });
// const obj = {
// audioTracks: [],
// videoTracks: [],
// subtitles: []
// };
// // Ajoutez ça temporairement dans votre fonction parseMPDStream après le parsing
// console.log('=== DEBUG STRUCTURE ===');
// // Debug premier élément vidéo
// if (parsedManifest.playlists && parsedManifest.playlists.length > 0) {
// console.log('\n🎥 FIRST VIDEO TRACK:');
// console.log(JSON.stringify(parsedManifest.playlists[0], null, 2));
// }
// // // Debug premier élément audio
// // try {
// // const audioGroups = parsedManifest?.mediaGroups?.AUDIO?.audio;
// // if (audioGroups) {
// // const firstAudioKey = Object.keys(audioGroups)[0];
// // console.log('\n🔊 FIRST AUDIO TRACK:');
// // console.log(`Key: "${firstAudioKey}"`);
// // console.log('Group:', JSON.stringify(audioGroups[firstAudioKey], null, 2));
// // if (audioGroups[firstAudioKey].playlists[0]) {
// // console.log('First playlist:', JSON.stringify(audioGroups[firstAudioKey].playlists[0], null, 2));
// // }
// // }
// // } catch(e) {
// // console.log('No audio debug possible:', e.message);
// // }
// // // Debug premier élément subtitle
// // try {
// // const subsGroups = parsedManifest?.mediaGroups?.SUBTITLES?.subs;
// // if (subsGroups) {
// // const firstSubKey = Object.keys(subsGroups)[0];
// // console.log('\n📝 FIRST SUBTITLE TRACK:');
// // console.log(`Key: "${firstSubKey}"`);
// // console.log('Group:', JSON.stringify(subsGroups[firstSubKey], null, 2));
// // if (subsGroups[firstSubKey].playlists[0]) {
// // console.log('First playlist:', JSON.stringify(subsGroups[firstSubKey].playlists[0], null, 2));
// // }
// // }
// // } catch(e) {
// // console.log('No subtitle debug possible:', e.message);
// // }
// console.log('=== END DEBUG ===\n');
// // Fonction pour filtrer les durées anormales (< 10min ou > 4h)
// const isValidDuration = (duration) => {
// if (!duration) return true; // Garder si pas de durée spécifiée
// const durationInSeconds = duration; // Conversion en secondes si nécessaire
// return durationInSeconds >= 600 && durationInSeconds <= 14400; // Entre 10min et 4h
// };
// // Traitement des groupes de média (AUDIO et SUBTITLES)
// const toParse = [{
// rootProp: 'AUDIO',
// subProp: 'audio',
// targetProp: 'audioTracks'
// }, {
// rootProp: 'SUBTITLES',
// subProp: 'subs',
// targetProp: 'subtitles'
// }];
// toParse.forEach(({ rootProp, subProp, targetProp }) => {
// try {
// for (const [key, value] of Object.entries(parsedManifest?.mediaGroups?.[rootProp]?.[subProp])) {
// for (let i = 0; i < value.playlists.length; i++) {
// const playlist = value.playlists[i];
// // Filtrer les tracks avec des durées anormales
// if (!isValidDuration(playlist.targetDuration)) {
// console.log(`🚫 Filtered out ${targetProp} "${key}" with suspicious duration: ${playlist.targetDuration}min`);
// continue;
// }
// if (targetProp === 'audioTracks') {
// obj.audioTracks.push({
// id: playlist?.attributes?.NAME || key,
// name: key,
// language: value.language || 'unknown',
// codec: playlist?.attributes?.CODECS,
// bandwidth: playlist?.attributes?.BANDWIDTH,
// channels: playlist?.attributes?.CHANNELS || '2CH',
// defaultKID: playlist?.contentProtection?.mp4protection?.attributes['cenc:default_KID']?.replaceAll('-', '') || null,
// duration: playlist?.targetDuration,
// formatedDuration: formatDuration(playlist?.targetDuration || 0),
// attributes: playlist.attributes
// });
// } else if (targetProp === 'subtitles') {
// obj.subtitles.push({
// id: playlist?.attributes?.NAME || key,
// name: key,
// language: value.language || 'unknown',
// codec: playlist?.attributes?.CODECS,
// format: playlist?.attributes?.FORMAT || 'UNKNOWN',
// defaultKID: playlist?.contentProtection?.mp4protection?.attributes['cenc:default_KID']?.replaceAll('-', '') || null,
// duration: playlist?.targetDuration,
// formatedDuration: formatDuration(playlist?.targetDuration || 0),
// attributes: playlist.attributes
// });
// }
// }
// }
// } catch(e) {
// console.log(`No ${targetProp} found in manifest`);
// }
// });
// // Traitement des tracks vidéo
// for (let i = 0; i < parsedManifest.playlists.length; i++) {
// const playlist = parsedManifest.playlists[i];
// // Filtrer les tracks vidéo avec des durées anormales
// if (!isValidDuration(playlist.targetDuration)) {
// console.log(`🚫 Filtered out video track with suspicious duration: ${playlist.targetDuration}min`);
// continue;
// }
// obj.videoTracks.push({
// id: playlist?.attributes?.NAME,
// name: `${playlist?.attributes?.RESOLUTION?.width || 'N/C'}x${playlist?.attributes?.RESOLUTION?.height || 'N/C'}`,
// codec: playlist?.attributes?.CODECS,
// bandwidth: playlist?.attributes?.BANDWIDTH,
// defaultKID: playlist?.contentProtection?.mp4protection?.attributes['cenc:default_KID']?.replaceAll('-', '') || null,
// fps: playlist?.attributes?.['FRAME-RATE'],
// resolution: {
// width: playlist?.attributes?.RESOLUTION?.width,
// height: playlist?.attributes?.RESOLUTION?.height
// },
// duration: playlist?.targetDuration,
// formatedDuration: formatDuration(playlist?.targetDuration || 0),
// attributes: playlist.attributes
// });
// }
// // Tri des tracks par qualité/priorité
// obj.videoTracks = obj.videoTracks.sort((a, b) => b.resolution.width - a.resolution.width);
// obj.audioTracks = obj.audioTracks.sort((a, b) => (b.bandwidth || 0) - (a.bandwidth || 0));
// return obj;
// };
app.post('/processMPD', async (req, res, next) => { app.post('/processMPD', async (req, res, next) => {
try { try {
res.json(await parseMPDStream(req.body.mpdUrl)); res.json(await parseMPDStream(req.body.mpdUrl));
@@ -889,8 +1285,8 @@ const remuxToMKV = async (options) => {
function extractAudioTrackInfo(audioTrack) { function extractAudioTrackInfo(audioTrack) {
const language = audioTrack.language || 'und'; const language = audioTrack.language || 'und';
const codec = audioTrack.attributes?.CODECS?.split('.')[0] || 'unknown'; const codec = audioTrack.codec?.split('.')[0] || 'unknown';
const bitrate = Math.round((audioTrack.attributes?.BANDWIDTH || 0) / 1000); const bitrate = Math.round((audioTrack.bandwidth || 0) / 1000);
return { language, codec, bitrate }; return { language, codec, bitrate };
} }
@@ -1158,6 +1554,29 @@ async function prepareDirectories(config) {
} }
} }
const downloadMPDManifest = async (url, filepath) => {
try {
const response = await axios({
method: 'GET',
url,
headers: {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:142.0) Gecko/20100101 Firefox/142.0',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'en-US,en;q=0.5',
'Accept-Encoding': 'gzip, deflate, br, zstd'
},
decompress: true
});
fs.writeFileSync(filepath, response.data);
return response.data;
} catch (error) {
console.error('Erreur lors du téléchargement:', error.message);
throw error;
}
};
async function downloadEncryptedFiles(job, config, { mpdUrl, wantedResolution, wantedAudioTracks, wantedSubtitles, mp4Filename }) { async function downloadEncryptedFiles(job, config, { mpdUrl, wantedResolution, wantedAudioTracks, wantedSubtitles, mp4Filename }) {
const { workdir, downloaderPath } = config; const { workdir, downloaderPath } = config;
const filesExist = await checkFilesExistance('encrypted', workdir); const filesExist = await checkFilesExistance('encrypted', workdir);
@@ -1166,56 +1585,44 @@ async function downloadEncryptedFiles(job, config, { mpdUrl, wantedResolution, w
console.log('📁 Encrypted files already exist, bypassing download...'); console.log('📁 Encrypted files already exist, bypassing download...');
return; return;
} }
console.log('⬇️ Encrypted files not found, downloading...'); console.log('⬇️ Encrypted files not found, downloading...');
// Construction des paramètres de téléchargement
const downloadParams = buildDownloadParameters(wantedAudioTracks, wantedSubtitles); const downloadParams = buildDownloadParameters(wantedAudioTracks, wantedSubtitles);
const objectsToDownload = 1 + wantedAudioTracks.length + wantedSubtitles.length; const objectsToDownload = 1 + wantedAudioTracks.length + wantedSubtitles.length;
job.progress(10); job.progress(10);
await downloadMPDManifest(mpdUrl, `${workdir}/manifest.mpd`);
const command = `${downloaderPath} "${mpdUrl}" --save-dir ${workdir} --save-name ${mp4Filename}_encrypted --select-video id="${wantedResolution.id}" ${downloadParams.audioPart} ${downloadParams.subPart}`; const baseUrl = mpdUrl.substring(0, mpdUrl.lastIndexOf('/') + 1);
const command = `${downloaderPath} "${workdir}/manifest.mpd" --base-url "${baseUrl}" --save-dir "${workdir}" --save-name "${mp4Filename}_encrypted" --select-video id="${wantedResolution.id}" ${downloadParams.audioPart} ${downloadParams.subPart} -H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:142.0) Gecko/20100101 Firefox/142.0"`;
const { executeCommand, emitter } = runProgressCommand(command, true); const { executeCommand, emitter } = runProgressCommand(command, true);
// Gestion du progress avec meilleure lisibilité // Gestion du progress avec meilleure lisibilité
let objectsDownloaded = -1; let objectsDownloaded = -1;
let previousPercentage = -1; let previousPercentage = -1;
emitter.on('percentage', (percentage) => { emitter.on('percentage', (percentage) => {
if (percentage < previousPercentage) { if (percentage < previousPercentage) {
objectsDownloaded++; objectsDownloaded++;
} }
previousPercentage = percentage; previousPercentage = percentage;
const subPercMax = 50 / objectsToDownload; const subPercMax = 50 / objectsToDownload;
const newProgress = Math.round((10 + (objectsDownloaded * subPercMax)) + (percentage * subPercMax / 100)); const newProgress = Math.round((10 + (objectsDownloaded * subPercMax)) + (percentage * subPercMax / 100));
job.progress(newProgress); job.progress(newProgress);
}); });
await executeCommand; await executeCommand;
} }
function buildDownloadParameters(wantedAudioTracks, wantedSubtitles) { function buildDownloadParameters(wantedAudioTracks, wantedSubtitles) {
// Construire les paramètres bandwidth pour audio // Construire la sélection par ID 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('|')}"`
: '';
// 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 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}` ? `--select-audio id="${wantedAudioTracks.map(elem => elem.id).join('|')}":for=all`
: '--drop-audio lang=".*"'; : '--drop-audio id=".*"';
// Construire la sélection par ID pour sous-titres
const subPart = wantedSubtitles.length > 0
? `--select-subtitle id="${wantedSubtitles.map(elem => elem.id).join('|')}":for=all`
: '--drop-subtitle id=".*"';
return { audioPart, subPart }; return { audioPart, subPart };
} }

View File

@@ -20,6 +20,7 @@
"mpd-parser": "^1.3.1", "mpd-parser": "^1.3.1",
"path": "^0.12.7", "path": "^0.12.7",
"tar": "^7.4.3", "tar": "^7.4.3",
"unzipper": "^0.12.3" "unzipper": "^0.12.3",
"xml2js": "^0.6.2"
} }
} }