diff --git a/index.js b/index.js index 8176a8d..eb213e8 100644 --- a/index.js +++ b/index.js @@ -90,7 +90,7 @@ const runProgressCommand = (command) => { app.post('/start-process', async (req, res, next) => { try { const { mp4Filename, mpdUrl, keys, wantedResolution, wantedAudioTracks, wantedSubtitles } = req.body; - console.log(req.body); + console.log(JSON.stringify(req.body, null, 2)); const job = await videoQueue.add({ mp4Filename, mpdUrl, @@ -202,24 +202,27 @@ const parseMPDStream = async (mpdUrl) => { videoTracks: [], subtitles: [] }; - for (const [key, value] of Object.entries(parsedManifest?.mediaGroups?.AUDIO?.audio)) { - for (let i = 0; i < value.playlists.length; i++) { - obj.audioTracks.push({ - name: key, - language: value.language, - attributes: value.playlists[i].attributes - }); + const toParse = [{ + rootProp: 'AUDIO', + subProp: 'audio', + targetProp: 'audioTracks' + }, { + rootProp: 'SUBTITLES', + subProp: 'subs', + targetProp: 'subtitles' + }] + + toParse.forEach(({ rootProp, subProp, targetProp }) => { + for (const [key, value] of Object.entries(parsedManifest?.mediaGroups?.[rootProp]?.[subProp])) { + for (let i = 0; i < value.playlists.length; i++) { + obj[targetProp].push({ + name: key, + language: value.language, + attributes: value.playlists[i].attributes + }); + } } - } - for (const [key, value] of Object.entries(parsedManifest?.mediaGroups?.SUBTITLES?.subs)) { - for (let i = 0; i < value.playlists.length; i++) { - obj.subtitles.push({ - name: key, - language: value.language, - attributes: value.playlists[i].attributes - }); - } - } + }); for (let i = 0; i < parsedManifest.playlists.length; i++) { obj.videoTracks.push({ name: `${parsedManifest.playlists?.[i]?.attributes?.RESOLUTION?.width || 'N/C'}x${parsedManifest.playlists?.[i]?.attributes?.RESOLUTION?.height || 'N/C'}`, @@ -253,85 +256,93 @@ videoQueue.process((job) => { const { mp4Filename, mpdUrl, keys, wantedResolution, wantedAudioTracks, wantedSubtitles } = job.data; const downloaderPath = softwareService.getLocalBinFileInfo('downloader').path; const mp4decryptPath = softwareService.getLocalBinFileInfo('mp4decrypt').path; - const mp4TmpFilepath = path.join(TMP_PATH, `${mp4Filename}.mp4`); - const mp4FinalFilepath = path.join(OUTPUT_PATH, `${mp4Filename}.mp4`); - console.log('1') - //await parseMPDStream(mpdUrl); - const filesExist = await checkFilesExistance('encrypted', TMP_PATH); - console.log('2') + + 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) { - // const resPattern = { - // '4k': [3840, 2160], - // '1080p': [1920, 1080], - // '720p': [1280, 720], - // '480p': [854, 480], - // '360p': [640, 360], - // '240p': [426, 240] - // }[wantedResolution] || [1920, 1080]; console.log('Encrypted files not found, downloading...'); - job.progress(10); - const { executeCommand, emitter } = runProgressCommand(`${downloaderPath} \"${mpdUrl}\" --save-dir ${TMP_PATH} --save-name ${mp4Filename}.mp4_encrypted --select-video res=\"${wantedResolution.resolution.width}*\" --select-audio lang=\"${wantedAudioTracks.map(elem => elem.language).join('|')}\":for=best2 --select-subtitle all`, true); + let objectsDownloaded = -1, previousPercentage = -1; + const objectsToDownload = 1 + wantedAudioTracks.length + wantedSubtitles.length; + job.progress(10); // Début à 10% + + const subPart = wantedSubtitles.length > 0 ? `--select-subtitle lang=\"${wantedSubtitles.map(elem => elem.language).join('|')}\":bwMin=\"${wantedSubtitles.map(elem => Math.floor(elem.attributes.BANDWIDTH / 1000 -1)).join('|')}\":bwMax=\"${wantedSubtitles.map(elem => Math.round(elem.attributes.BANDWIDTH / 1000 + 1)).join('|')}\"` : '--drop-subtitle lang=\".*\"'; + const audioPart = wantedAudioTracks.length > 0 ? `--select-audio lang=\"${wantedAudioTracks.map(elem => elem.language).join('|')}\":codecs=\"${wantedAudioTracks.map(elem => elem.attributes.CODECS).join('|')}\":bwMin=\"${wantedAudioTracks.map(elem => Math.floor(elem.attributes.BANDWIDTH / 1000 -1)).join('|')}\":bwMax=\"${wantedAudioTracks.map(elem => Math.round(elem.attributes.BANDWIDTH / 1000 + 1)).join('|')}\"` : '--drop-audio lang=\".*\"'; + + const { executeCommand, emitter } = runProgressCommand(`${downloaderPath} \"${mpdUrl}\" --save-dir ${workdir} --save-name ${mp4Filename}_encrypted --select-video res=\"${wantedResolution.resolution.width}*\" ${audioPart} ${subPart}`, true); + emitter.on('percentage', (percentage) => { - console.log(`Download Progression : ${percentage}%`); - job.progress(Math.round(10 + (percentage / 5))); + 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); - job.progress(30); - - // Décryptage vidéo - await runCommand(`${mp4decryptPath} ${keys.map(k => `--key ${k.key}:${k.value}`).join(' ')} "${mp4TmpFilepath}_encrypted.mp4" "${mp4TmpFilepath}_decrypted.mp4"`); - - job.progress(50); - - // Décryptage audio - const audioFiles = fs.readdirSync(mp4TmpFilepath); - const finalAudio = []; - for (const file of audioFiles) { - if (file.startsWith(`${mp4TmpFilepath}_encrypted`) && file.endsWith('.m4a')) { - const baseName = path.basename(file, '.m4a'); - await runCommand(`${mp4decryptPath} ${keys.map(k => `--key ${k.key}:${k.value}`).join(' ')} "${file}" "${baseName}_decrypted.m4a"`); - finalAudio.push(`${baseName}_decrypted.m4a`); - } - } + // 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); - // Combinaison avec ffmpeg - let ffmpegCommand = `ffmpeg -y -i ${mp4TmpFilepath}_decrypted.mp4`; + // 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) { - //if (file.startsWith(`${mp4Filename}_encrypted`) && file.endsWith('_decrypted.m4a')) { - ffmpegCommand += ` -i ${file}`; - mapCommand += ` -map ${inputIndex}:a`; - inputIndex++; - //} + ffmpegCommand += ` -i ${file}`; + mapCommand += ` -map ${inputIndex}:a`; + inputIndex++; } - ffmpegCommand += `${mapCommand} -c copy ${mp4FinalFilepath}.mp4`; + if (!fs.existsSync(finalPath)) + fs.mkdirSync(finalPath); + + ffmpegCommand += `${mapCommand} -c copy ${finalPath}/${mp4FilenameWithExt}`; await runCommand(ffmpegCommand); job.progress(90); - // Renommage des fichiers SRT + // Renommage des fichiers SRT + const subFiles = fs.readdirSync(workdir); let counter = 1; - for (const file of audioFiles) { - if (file.startsWith(`${mp4TmpFilepath}_encrypted`) && file.endsWith('.srt')) { - fs.renameSync(file, `${mp4FinalFilepath}_${counter}.srt`); + for (const file of subFiles) { + if (file.startsWith(`${mp4Filename}_encrypted`) && file.endsWith('.srt')) { + fs.renameSync(`${workdir}/${file}`, `${finalPath}/${mp4Filename}_${counter}.srt`); counter++; } } - // Nettoyage (commenté pour correspondre au script original) - await runCommand(`rm ${mp4TmpFilepath}_encrypted* && rm ${mp4TmpFilepath}_decrypted*`); + // 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: `${mp4FinalFilepath}.mp4`, fileName: `${mp4Filename}.mp4` }); + 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}`));