first commit

This commit is contained in:
Joris Bertomeu
2024-09-26 11:39:12 +02:00
commit 9aa7ba809d
3 changed files with 326 additions and 0 deletions

54
.gitignore vendored Normal file
View File

@@ -0,0 +1,54 @@
# Use yarn.lock instead
package-lock.json
*.mp4
*.mkv
*.m4a
*.srt
# Environment variables
#.env
# Logs
logs
*.log
npm-debug.log*
# Documentation
docs
# Runtime data
pids
*.pid
*.seed
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules
jspm_packages
# Optional npm cache directory
.npm
# Optional REPL history
.node_repl_history
[Uu]ploads/
dist/

254
index.js Normal file
View File

@@ -0,0 +1,254 @@
const express = require('express');
const Queue = require('bull');
const { exec, spawn } = require('child_process');
const fs = require('fs').promises;
const path = require('path');
const cors = require('cors');
const EventEmitter = require('events');
const app = express();
const port = 3000;
app.use(cors());
app.use(express.json());
// Configuration de la file d'attente Bull
const videoQueue = new Queue('crawl', 'redis://192.168.1.222:32768');
videoQueue.on('error', (e) => {
console.log('An error occured', e);
});
const runCommand = (command) => {
console.log('⚙️ Will execute: ' + command);
return new Promise((resolve, reject) => {
const cmd = spawn(command, { shell: true });
let lastLog = '';
cmd.stdout.on('data', (data) => {
lastLog = data.toString();
process.stdout.write(data.toString());
});
cmd.stderr.on('data', (data) => {
lastLog = data.toString();
process.stderr.write(`stderr: ${data.toString()}`);
});
cmd.on('close', (code) => {
if (code === 0) {
resolve('Command executed successfully.');
} else {
reject(lastLog || 'Command failed with status code ' + code);
}
});
});
};
const runProgressCommand = (command) => {
console.log('⚙️ Will execute: ' + command);
const emitter = new EventEmitter();
const executeCommand = new Promise((resolve, reject) => {
const cmd = spawn(command, { shell: true });
let lastLog = '';
cmd.stdout.on('data', (data) => {
lastLog = data.toString();
const perc = extractPercentage(data.toString());
if (!perc) {
process.stdout.write(data.toString());
} else {
//console.log('🐳 Percentage ====> ' + perc);
emitter.emit('percentage', perc);
}
});
cmd.stderr.on('data', (data) => {
lastLog = data.toString();
process.stderr.write(`stderr: ${data.toString()}`);
});
cmd.on('close', (code) => {
if (code === 0) {
resolve('Command executed successfully.');
} else {
reject(lastLog || 'Command failed with status code ' + code);
}
});
});
return { executeCommand, emitter };
};
// Route pour démarrer le processus
app.post('/start-process', async (req, res, next) => {
try {
const { mp4Filename, mpdUrl, keys, wantedResolution } = req.body;
console.log(req.body);
const job = await videoQueue.add({
mp4Filename,
mpdUrl,
keys,
wantedResolution
});
res.json({ jobId: job.id });
} catch(e) {
console.log(e);
next();
}
});
// Route pour vérifier le statut du job
app.get('/job-status/:jobId', async (req, res) => {
const job = await videoQueue.getJob(req.params.jobId);
if (job === null) {
res.status(404).json({ error: 'Job not found' });
} else {
const state = await job.getState();
const progress = job._progress;
res.json({ jobId: job.id, state, progress });
}
});
app.get('/jobs-status', async (req, res) => {
const jobs = await videoQueue.getJobs();
res.json(await Promise.all(jobs.splice(0, 25).map(async job => ({
id: job.id,
state: await job.getState(),
progress: job._progress,
addedOn: job.timestamp,
processedOn: job.processedOn,
finishedOn: job.finishedOn,
returnValue: job.returnvalue,
failedReason: job.failedReason,
data: job.data
}))));
});
app.get('/download/:filename', async (req, res) => {
const { filename } = req.params;
const file = path.join(process.cwd(), filename);
res.download(file);
});
const checkFilesExistance = (pattern) => {
return new Promise(async (resolve, reject) => {
try {
const files = await fs.readdir(process.cwd());
resolve(files.filter(file => file.includes(pattern)));
} catch(e) {
reject(e);
}
});
};
const extractPercentage = (line) => {
const s = line.split('%');
const percentages = [];
for (let i = 0; i < s.length; i++) {
if (s[i].length === 0) continue;
const ss = s[i].split(' ');
if (ss.length > 0 && !isNaN(ss[ss.length - 1]))
percentages.push(parseFloat(ss[ss.length - 1]));
}
if (percentages.length === 0) return null;
return Math.max(...percentages.filter((p) => p < 100));
}
// Processus de la file d'attente
videoQueue.process((job) => {
return new Promise(async (resolve, reject) => {
const { mp4Filename, mpdUrl, keys, wantedResolution } = job.data;
const downloaderPath = path.join(process.env.HOME, 'Downloads/N_m3u8DL-RE_Beta_osx-arm64/N_m3u8DL-RE');
const mp4decryptPath = path.join(process.env.HOME, 'Downloads/Bento4-SDK-1-6-0-641.universal-apple-macosx/bin/mp4decrypt');
try {
const filesExist = await checkFilesExistance('encrypted');
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-name ${mp4Filename}_encrypted --select-video \"(?=.*${resPattern[0]})(?=.*${resPattern[1]})\" --select-audio lang=\"fr|en\":for=best2 --select-subtitle all`, true);
emitter.on('percentage', (percentage) => {
console.log(`Download Progression : ${percentage}%`);
job.progress(Math.round(10 + (percentage / 5)));
});
await executeCommand;
} else {
console.log('Encrypted files already exist, bypassing download...')
}
job.progress(30);
// Décryptage vidéo
await runCommand(`${mp4decryptPath} ${keys.map(k => `--key ${k.key}:${k.value}`).join(' ')} "${mp4Filename}_encrypted.mp4" "${mp4Filename}_decrypted.mp4"`);
job.progress(50);
// Décryptage audio
const audioFiles = await fs.readdir('.');
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(' ')} "${file}" "${baseName}_decrypted.m4a"`);
finalAudio.push(`${baseName}_decrypted.m4a`);
}
}
job.progress(70);
// Combinaison avec ffmpeg
let ffmpegCommand = `ffmpeg -y -i ${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 += `${mapCommand} -c copy ${mp4Filename}.mp4`;
await runCommand(ffmpegCommand);
job.progress(90);
// Renommage des fichiers SRT
let counter = 1;
for (const file of audioFiles) {
if (file.startsWith(`${mp4Filename}_encrypted`) && file.endsWith('.srt')) {
await fs.rename(file, `${mp4Filename}_${counter}.srt`);
counter++;
}
}
// Nettoyage (commenté pour correspondre au script original)
await runCommand(`rm ${mp4Filename}_encrypted* && rm ${mp4Filename}_decrypted*`);
job.progress(100);
resolve({ message: `File fetched and decrypted with success: ${mp4Filename}.mp4`, filePath: `${mp4Filename}.mp4`, fileName: `${mp4Filename}.mp4` });
} catch (error) {
console.log('Error while processing task', error)
reject(new Error(`${error.toString() || error}`));
}
});
});
app.listen(port, () => {
console.log(`Server running at http://localhost:${port}`);
});

18
package.json Normal file
View File

@@ -0,0 +1,18 @@
{
"name": "crawlflix-api",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"bull": "^4.16.3",
"cors": "^2.8.5",
"express": "^4.21.0",
"fs": "^0.0.1-security",
"path": "^0.12.7"
}
}