Add MKV Remux + fix download process
This commit is contained in:
148
index.js
148
index.js
@@ -258,9 +258,50 @@ app.get('/jobs-status', async (req, res) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
app.get('/download/:filename', async (req, res) => {
|
app.get('/download/:filename', async (req, res) => {
|
||||||
const { filename } = req.params;
|
try {
|
||||||
const file = path.join(process.cwd(), filename);
|
const { filename } = req.params;
|
||||||
res.download(file);
|
|
||||||
|
if (filename.includes('..') || filename.includes('/') || filename.includes('\\')) {
|
||||||
|
return res.status(400).json({ error: 'Invalid filename' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const filePath = path.join(OUTPUT_PATH, filename);
|
||||||
|
|
||||||
|
if (!fs.existsSync(filePath)) {
|
||||||
|
return res.status(404).json({ error: 'File not found' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const stat = fs.statSync(filePath);
|
||||||
|
if (!stat.isFile()) {
|
||||||
|
return res.status(400).json({ error: 'Not a file' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const allowedExtensions = ['.mp4', '.mkv', '.srt', '.m4a'];
|
||||||
|
const fileExt = path.extname(filename).toLowerCase();
|
||||||
|
|
||||||
|
if (!allowedExtensions.includes(fileExt)) {
|
||||||
|
return res.status(403).json({ error: 'File type not allowed' });
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`📥 Download requested: ${filename} (${stat.size} bytes)`);
|
||||||
|
|
||||||
|
res.download(filePath, filename, (err) => {
|
||||||
|
if (err) {
|
||||||
|
console.error('Download error:', err);
|
||||||
|
if (!res.headersSent) {
|
||||||
|
res.status(500).json({ error: 'Download failed' });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log(`✅ Download completed: ${filename}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Download route error:', error);
|
||||||
|
if (!res.headersSent) {
|
||||||
|
res.status(500).json({ error: 'Internal server error' });
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get('/hello', async (req, res, next) => {
|
app.get('/hello', async (req, res, next) => {
|
||||||
@@ -409,12 +450,90 @@ const safeMove = async (source, destination) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Processus de la file d'attente
|
// Processus de la file d'attente
|
||||||
videoQueue.process((job) => {
|
videoQueue.process((job) => {
|
||||||
return new Promise(async (resolve, reject) => {
|
return new Promise(async (resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
console.log('Will launch job')
|
console.log('Will launch job')
|
||||||
const { mp4Filename, mpdUrl, keys, wantedResolution, wantedAudioTracks, wantedSubtitles } = job.data;
|
const { mp4Filename, mpdUrl, keys, wantedResolution, wantedAudioTracks, wantedSubtitles, wantedRemux } = job.data;
|
||||||
const downloaderPath = softwareService.getLocalBinFileInfo('downloader').path;
|
const downloaderPath = softwareService.getLocalBinFileInfo('downloader').path;
|
||||||
const mp4decryptPath = softwareService.getLocalBinFileInfo('mp4decrypt').path;
|
const mp4decryptPath = softwareService.getLocalBinFileInfo('mp4decrypt').path;
|
||||||
|
|
||||||
@@ -492,6 +611,27 @@ videoQueue.process((job) => {
|
|||||||
|
|
||||||
job.progress(90);
|
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
|
// Renommage des fichiers SRT
|
||||||
const subFiles = fs.readdirSync(workdir);
|
const subFiles = fs.readdirSync(workdir);
|
||||||
let counter = 1;
|
let counter = 1;
|
||||||
|
|||||||
Reference in New Issue
Block a user