mirror of
https://github.com/snobu/destreamer.git
synced 2026-03-11 06:28:25 +00:00
done decryption/merging of video/audio/sub traks
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import { ApiClient } from './ApiClient';
|
import { ApiClient } from './ApiClient';
|
||||||
import { argv, promptUser } from './CommandLineParser';
|
import { argv, promptUser } from './CommandLineParser';
|
||||||
import { getDecrypter } from './Descrypter';
|
import { getDecrypter } from './Decrypter';
|
||||||
import { DownloadManager } from './DownloadManager';
|
import { DownloadManager } from './DownloadManager';
|
||||||
import { ERROR_CODE } from './Errors';
|
import { ERROR_CODE } from './Errors';
|
||||||
import { setProcessEvents } from './Events';
|
import { setProcessEvents } from './Events';
|
||||||
@@ -10,7 +10,7 @@ import { getPuppeteerChromiumPath } from './PuppeteerHelper';
|
|||||||
import { TokenCache/* , refreshSession */} from './TokenCache';
|
import { TokenCache/* , refreshSession */} from './TokenCache';
|
||||||
import { Video, Session } from './Types';
|
import { Video, Session } from './Types';
|
||||||
import { checkRequirements, /* ffmpegTimemarkToChunk, */parseInputFile, parseCLIinput, getUrlsFromPlaylist} from './Utils';
|
import { checkRequirements, /* ffmpegTimemarkToChunk, */parseInputFile, parseCLIinput, getUrlsFromPlaylist} from './Utils';
|
||||||
import { getVideoInfo, createUniquePath } from './VideoUtils';
|
import { getVideosInfo, createUniquePaths } from './VideoUtils';
|
||||||
|
|
||||||
import { exec, execSync } from 'child_process';
|
import { exec, execSync } from 'child_process';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
@@ -131,9 +131,9 @@ async function downloadVideo(videoGUIDs: Array<string>,
|
|||||||
|
|
||||||
logger.info('Downloading video info, this might take a while...');
|
logger.info('Downloading video info, this might take a while...');
|
||||||
|
|
||||||
const videos: Array<Video> = createUniquePath (
|
const videos: Array<Video> = createUniquePaths (
|
||||||
await getVideoInfo(videoGUIDs, session, argv.closedCaptions),
|
await getVideosInfo(videoGUIDs, session, argv.closedCaptions),
|
||||||
outputDirectories, argv.format, argv.skip
|
outputDirectories, argv.outputTemplate ,argv.format, argv.skip
|
||||||
);
|
);
|
||||||
|
|
||||||
if (argv.simulate) {
|
if (argv.simulate) {
|
||||||
@@ -152,10 +152,14 @@ async function downloadVideo(videoGUIDs: Array<string>,
|
|||||||
|
|
||||||
// Launch aria2c
|
// Launch aria2c
|
||||||
logger.info('Trying to launch and connect to aria2c...\n');
|
logger.info('Trying to launch and connect to aria2c...\n');
|
||||||
const aria2cExec = exec('aria2c --enable-rpc --pause=true --rpc-listen-port=6789', (err, stdout, stderr) => {
|
|
||||||
logger.error(err?.message ?? (stderr || stdout));
|
const aria2cExec = exec(
|
||||||
process.exit(ERROR_CODE.ARIA2C_CRASH);
|
'aria2c --enable-rpc --pause=true --rpc-listen-port=6789',(err, stdout, stderr) => {
|
||||||
});
|
logger.error(err?.message ?? (stderr || stdout));
|
||||||
|
process.exit(ERROR_CODE.ARIA2C_CRASH);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
// Try to connect to aria2c webSocket
|
// Try to connect to aria2c webSocket
|
||||||
const downloadManager = new DownloadManager(6789);
|
const downloadManager = new DownloadManager(6789);
|
||||||
try {
|
try {
|
||||||
@@ -171,7 +175,6 @@ async function downloadVideo(videoGUIDs: Array<string>,
|
|||||||
|
|
||||||
console.info(`\nDownloading video no.${videos.indexOf(video) + 1} \n`);
|
console.info(`\nDownloading video no.${videos.indexOf(video) + 1} \n`);
|
||||||
|
|
||||||
// TODO: check issue
|
|
||||||
if (argv.skip && fs.existsSync(video.outPath)) {
|
if (argv.skip && fs.existsSync(video.outPath)) {
|
||||||
logger.info(`File already exists, skipping: ${video.outPath} \n`);
|
logger.info(`File already exists, skipping: ${video.outPath} \n`);
|
||||||
continue;
|
continue;
|
||||||
@@ -186,33 +189,40 @@ async function downloadVideo(videoGUIDs: Array<string>,
|
|||||||
.filter(playlist =>
|
.filter(playlist =>
|
||||||
Object.prototype.hasOwnProperty.call(playlist.attributes, 'RESOLUTION'));
|
Object.prototype.hasOwnProperty.call(playlist.attributes, 'RESOLUTION'));
|
||||||
|
|
||||||
if (videoPlaylists.length === 1 || argv.bestQuality) {
|
if (videoPlaylists.length === 1 || argv.selectQuality === 5) {
|
||||||
videoPlaylistUrl = videoPlaylists.pop().uri;
|
videoPlaylistUrl = videoPlaylists.pop().uri;
|
||||||
}
|
}
|
||||||
else {
|
else if (argv.selectQuality === 0) {
|
||||||
let resolutions = videoPlaylists.map(playlist =>
|
const resolutions = videoPlaylists.map(playlist =>
|
||||||
playlist.attributes.RESOLUTION.width + 'x' +
|
playlist.attributes.RESOLUTION.width + 'x' +
|
||||||
playlist.attributes.RESOLUTION.height);
|
playlist.attributes.RESOLUTION.height
|
||||||
|
);
|
||||||
|
|
||||||
videoPlaylistUrl = videoPlaylists[promptUser(resolutions)].uri;
|
videoPlaylistUrl = videoPlaylists[promptUser(resolutions)].uri;
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
const choiche = Math.round((argv.selectQuality * videoPlaylists.length) / 10);
|
||||||
|
videoPlaylistUrl = videoPlaylists[choiche].uri;
|
||||||
|
}
|
||||||
|
|
||||||
// audio playlist url
|
// audio playlist url
|
||||||
// TODO: better audio playlists parsing..?
|
// TODO: better audio playlists parsing? With language maybe?
|
||||||
let audioPlaylistUrl: string;
|
let audioPlaylistUrl: string;
|
||||||
let audioPlaylists: Array<string> = Object.keys(masterParser.manifest.mediaGroups.AUDIO.audio);
|
let audioPlaylists: Array<string> = Object.keys(masterParser.manifest.mediaGroups.AUDIO.audio);
|
||||||
|
audioPlaylistUrl = masterParser.manifest.mediaGroups.AUDIO.audio[audioPlaylists[0]].uri;
|
||||||
if (audioPlaylists.length === 1 || argv.bestQuality){
|
// if (audioPlaylists.length === 1){
|
||||||
audioPlaylistUrl = masterParser.manifest.mediaGroups.AUDIO
|
// audioPlaylistUrl = masterParser.manifest.mediaGroups.AUDIO
|
||||||
.audio[audioPlaylists[0]].uri;
|
// .audio[audioPlaylists[0]].uri;
|
||||||
}
|
// }
|
||||||
else {
|
// else {
|
||||||
audioPlaylistUrl = masterParser.manifest.mediaGroups.AUDIO
|
// audioPlaylistUrl = masterParser.manifest.mediaGroups.AUDIO
|
||||||
.audio[audioPlaylists[promptUser(audioPlaylists)]].uri;
|
// .audio[audioPlaylists[promptUser(audioPlaylists)]].uri;
|
||||||
}
|
// }
|
||||||
|
|
||||||
const videoUrls = await getUrlsFromPlaylist(videoPlaylistUrl, session);
|
const videoUrls = await getUrlsFromPlaylist(videoPlaylistUrl, session);
|
||||||
const audioUrls = await getUrlsFromPlaylist(audioPlaylistUrl, session);
|
const audioUrls = await getUrlsFromPlaylist(audioPlaylistUrl, session);
|
||||||
const decrypter = await getDecrypter(videoPlaylistUrl, session);
|
const videoDecrypter = await getDecrypter(videoPlaylistUrl, session);
|
||||||
|
const audioDecrypter = await getDecrypter(videoPlaylistUrl, session);
|
||||||
|
|
||||||
// video download
|
// video download
|
||||||
const videoSegmentsDir = tmp.dirSync({
|
const videoSegmentsDir = tmp.dirSync({
|
||||||
@@ -223,8 +233,7 @@ async function downloadVideo(videoGUIDs: Array<string>,
|
|||||||
|
|
||||||
logger.info('\nDownloading and merging video segments \n');
|
logger.info('\nDownloading and merging video segments \n');
|
||||||
await downloadManager.downloadUrls(videoUrls, videoSegmentsDir.name);
|
await downloadManager.downloadUrls(videoUrls, videoSegmentsDir.name);
|
||||||
execSync('copy /b *.encr ..\\video.encr', {cwd: videoSegmentsDir.name});
|
execSync(`copy /b *.encr "${video.filename}.video.encr"`, {cwd: videoSegmentsDir.name});
|
||||||
videoSegmentsDir.removeCallback();
|
|
||||||
|
|
||||||
|
|
||||||
// audio download
|
// audio download
|
||||||
@@ -236,31 +245,71 @@ async function downloadVideo(videoGUIDs: Array<string>,
|
|||||||
|
|
||||||
logger.info('\nDownloading and merging audio segments \n');
|
logger.info('\nDownloading and merging audio segments \n');
|
||||||
await downloadManager.downloadUrls(audioUrls, audioSegmentsDir.name);
|
await downloadManager.downloadUrls(audioUrls, audioSegmentsDir.name);
|
||||||
execSync('copy /b *.encr ..\\audio.encr', {cwd: audioSegmentsDir.name});
|
execSync(`copy /b *.encr "${video.filename}.audio.encr"`, {cwd: audioSegmentsDir.name});
|
||||||
audioSegmentsDir.removeCallback();
|
|
||||||
|
|
||||||
// subs download
|
// subs download
|
||||||
if (argv.closedCaptions && video.captionsUrl) {
|
if (argv.closedCaptions && video.captionsUrl) {
|
||||||
await apiClient.callUrl(video.captionsUrl, 'get', null, 'text')
|
await apiClient.callUrl(video.captionsUrl, 'get', null, 'text')
|
||||||
.then(res => fs.writeFileSync('subs.vtt', res?.data));
|
.then(res => fs.writeFileSync(
|
||||||
|
path.join(videoSegmentsDir.name, 'CC.vtt'), res?.data));
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.warn('START DECRYPT');
|
|
||||||
|
|
||||||
const input = fs.createReadStream( path.join(path.dirname(video.outPath), 'video.encr'));
|
logger.verbose('starting decrypt');
|
||||||
const output = fs.createWriteStream(video.outPath);
|
|
||||||
|
|
||||||
input.pipe(decrypter).pipe(output);
|
const videoDecryptInput = fs.createReadStream(
|
||||||
|
path.join(videoSegmentsDir.name, video.filename + '.video.encr'));
|
||||||
|
const videoDecryptOutput = fs.createWriteStream(
|
||||||
|
path.join(videoSegmentsDir.name, video.filename + '.video'));
|
||||||
|
|
||||||
logger.warn('DONE DECRYPT');
|
const decryptVideoPromise = new Promise(resolve => {
|
||||||
|
videoDecryptOutput.on('finish', resolve);
|
||||||
|
videoDecryptInput.pipe(videoDecrypter).pipe(videoDecryptOutput);
|
||||||
|
});
|
||||||
|
|
||||||
|
const audioDecryptInput = fs.createReadStream(
|
||||||
|
path.join(audioSegmentsDir.name, video.filename + '.audio.encr'));
|
||||||
|
const audioDecryptOutput = fs.createWriteStream(
|
||||||
|
path.join(audioSegmentsDir.name, video.filename + '.audio'));
|
||||||
|
|
||||||
|
const decryptAudioPromise = new Promise(resolve => {
|
||||||
|
audioDecryptOutput.on('finish', resolve);
|
||||||
|
audioDecryptInput.pipe(audioDecrypter).pipe(audioDecryptOutput);
|
||||||
|
});
|
||||||
|
|
||||||
|
await Promise.all([decryptVideoPromise, decryptAudioPromise]);
|
||||||
|
|
||||||
|
logger.verbose('done decrypt');
|
||||||
|
|
||||||
|
logger.info('\nMerging vdeo and audio together');
|
||||||
|
const mergeCommand = (
|
||||||
|
// add video input
|
||||||
|
`ffmpeg -i "${path.join(videoSegmentsDir.name, video.filename + '.video')}" ` +
|
||||||
|
// add audio input
|
||||||
|
`-i "${path.join(audioSegmentsDir.name, video.filename + '.audio')}" ` +
|
||||||
|
// add subtitles input if present and wanted
|
||||||
|
((argv.closedCaptions && video.captionsUrl) ?
|
||||||
|
`-i "${path.join(videoSegmentsDir.name, 'CC.vtt')}" ` : '') +
|
||||||
|
// copy codec and output path
|
||||||
|
`-c copy "${video.outPath}"`
|
||||||
|
);
|
||||||
|
|
||||||
|
logger.debug('[destreamer] ' + mergeCommand);
|
||||||
|
|
||||||
|
execSync(mergeCommand, { stdio: 'ignore' });
|
||||||
|
|
||||||
|
videoSegmentsDir.removeCallback();
|
||||||
|
audioSegmentsDir.removeCallback();
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.debug('closing');
|
logger.debug('[destreamer] closing downloader socket');
|
||||||
await downloadManager.close();
|
await downloadManager.close();
|
||||||
logger.debug('closed websocket');
|
logger.debug('[destreamer] closed downloader. Stopping aria2c deamon');
|
||||||
aria2cExec.kill('SIGINT');
|
aria2cExec.kill('SIGINT');
|
||||||
logger.debug('closed aria2c');
|
logger.debug('[destreamer] stopped aria2c');
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -444,6 +493,10 @@ async function main(): Promise<void> {
|
|||||||
'process._getActiveRequests();' in the debug console to see lingering
|
'process._getActiveRequests();' in the debug console to see lingering
|
||||||
Handles (where you can find the sockets) or Requests */
|
Handles (where you can find the sockets) or Requests */
|
||||||
await downloadVideo(videoGUIDs, outDirs, session);
|
await downloadVideo(videoGUIDs, outDirs, session);
|
||||||
|
|
||||||
|
// workaround for issue above
|
||||||
|
process.exit(0);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user