mirror of
https://github.com/snobu/destreamer.git
synced 2026-01-29 19:32:16 +00:00
v2.0 RELEASE
This commit is contained in:
@@ -5,43 +5,51 @@ import colors from 'colors';
|
||||
import fs from 'fs';
|
||||
|
||||
export const argv = yargs.options({
|
||||
url: {
|
||||
videoUrls: {
|
||||
alias: 'i',
|
||||
describe: 'List of video urls',
|
||||
type: 'array',
|
||||
demandOption: false
|
||||
},
|
||||
'from-file': {
|
||||
videoUrlsFile: {
|
||||
alias: 'f',
|
||||
describe: 'Path to txt file containing the urls',
|
||||
type: 'string',
|
||||
demandOption: false
|
||||
},
|
||||
username: {
|
||||
alias: 'u',
|
||||
type: 'string',
|
||||
demandOption: false
|
||||
},
|
||||
outdir: {
|
||||
outputDirectory: {
|
||||
alias: 'o',
|
||||
describe: 'The directory where destreamer will save your downloads [default: videos]',
|
||||
type: 'string',
|
||||
demandOption: false
|
||||
},
|
||||
'out-dirs-from-file': {
|
||||
outputDirectories: {
|
||||
alias: 'O',
|
||||
describe: 'Path to a txt file containing one output directory per video',
|
||||
type: 'string',
|
||||
demandOption: false
|
||||
},
|
||||
'no-experiments': {
|
||||
describe: `Disable experimental features (do not display video thumbnails)`,
|
||||
noExperiments: {
|
||||
alias: 'x',
|
||||
describe: `Do not attempt to render video thumbnails in the console`,
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
demandOption: false
|
||||
},
|
||||
simulate: {
|
||||
alias: 's',
|
||||
describe: `Disable video download and print metadata information to the console`,
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
demandOption: false
|
||||
},
|
||||
verbose: {
|
||||
alias: 'v',
|
||||
describe: `Print additional information to the console (use this before opening an issue on GitHub)`,
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
|
||||
@@ -48,13 +48,13 @@ export const enum CLI_ERROR {
|
||||
GRACEFULLY_STOP = ' ', // gracefully stop execution, yargs way
|
||||
|
||||
MISSING_REQUIRED_ARG = 'You must specify a URLs source.\n' +
|
||||
'Valid options are --videoUrls or --videoUrlsFile.',
|
||||
'Valid options are -i for one or more URLs separated by sapce or -f for URLs from file.',
|
||||
|
||||
VIDEOURLS_ARG_CONFLICT = 'Too many URLs sources specified!\n' +
|
||||
'Please specify a single URLs source with either --videoUrls or --videoUrlsFile.',
|
||||
'Please specify a single source, either -i or -f (URLs from file)',
|
||||
|
||||
OUTPUTDIR_ARG_CONFLICT = 'Too many output arguments specified!\n' +
|
||||
'Please specify a single output argument with either --outputDirectory or --outputDirectories.',
|
||||
'Please specify a single output argument, either -o or --outputDirectories.',
|
||||
|
||||
FILE_INPUT_VIDEOURLS_ARG = 'Wrong input for option --videoUrls.\n' +
|
||||
'To read URLs from file, use --videoUrlsFile option.',
|
||||
|
||||
@@ -2,6 +2,8 @@ import * as fs from 'fs';
|
||||
import { Session } from './Types';
|
||||
import { bgGreen, bgYellow, green } from 'colors';
|
||||
import jwtDecode from 'jwt-decode';
|
||||
import axios from 'axios';
|
||||
import colors from 'colors';
|
||||
|
||||
export class TokenCache {
|
||||
private tokenCacheFile: string = '.token_cache';
|
||||
@@ -53,4 +55,27 @@ export class TokenCache {
|
||||
console.info(green('Fresh access token dropped into .token_cache'));
|
||||
});
|
||||
}
|
||||
|
||||
public async RefreshToken(session: Session): Promise<string | null> {
|
||||
let endpoint = `${session.ApiGatewayUri}refreshtoken?api-version=${session.ApiGatewayVersion}`;
|
||||
|
||||
let response = await axios.get(endpoint,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${session.AccessToken}`
|
||||
}
|
||||
});
|
||||
|
||||
let freshCookie: string | null = null;
|
||||
|
||||
try {
|
||||
let cookie: string = response.headers["set-cookie"].toString();
|
||||
freshCookie = cookie.split(',Authorization_Api=')[0];
|
||||
}
|
||||
catch (e) {
|
||||
console.error(colors.yellow("Error when calling /refreshtoken: Missing or unexpected set-cookie header."));
|
||||
}
|
||||
|
||||
return freshCookie;
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@ export type Session = {
|
||||
ApiGatewayVersion: string;
|
||||
}
|
||||
|
||||
|
||||
export type Metadata = {
|
||||
date: string;
|
||||
totalChunks: number; // Abstraction of FFmpeg timemark
|
||||
|
||||
@@ -136,6 +136,7 @@ export function ffmpegTimemarkToChunk(timemark: string) {
|
||||
const hrs = parseInt(timeVals[0]);
|
||||
const mins = parseInt(timeVals[1]);
|
||||
const secs = parseInt(timeVals[2]);
|
||||
const chunk = (hrs * 60) + mins + (secs / 60);
|
||||
|
||||
return (hrs * 60) + mins + (secs / 60);
|
||||
return chunk;
|
||||
}
|
||||
|
||||
@@ -163,17 +163,34 @@ async function downloadVideo(videoUrls: string[], outputDirectories: string[], s
|
||||
video.title = makeUniqueTitle(sanitize(video.title) + ' - ' + video.date, outputDirectories[j]);
|
||||
|
||||
// Very experimental inline thumbnail rendering
|
||||
if (!argv.noThumbnails)
|
||||
if (!argv.noExperiments)
|
||||
await drawThumbnail(video.posterImage, session.AccessToken);
|
||||
|
||||
console.info('Spawning ffmpeg with access token and HLS URL. This may take a few seconds...\n');
|
||||
console.info('Spawning ffmpeg with access token and HLS URL. This may take a few seconds...');
|
||||
|
||||
// Try to get a fresh cookie, else gracefully fall back
|
||||
// to our session access token (Bearer)
|
||||
let freshCookie = await tokenCache.RefreshToken(session);
|
||||
let headers = `Authorization:\ Bearer\ ${session.AccessToken}`;
|
||||
if (freshCookie) {
|
||||
console.info(colors.green('Using a fresh cookie.'));
|
||||
headers = `Cookie:\ ${freshCookie}`;
|
||||
}
|
||||
|
||||
const outputPath = outputDirectories[j] + path.sep + video.title + '.mp4';
|
||||
const ffmpegInpt = new FFmpegInput(video.playbackUrl, new Map([
|
||||
['headers', `Authorization:\ Bearer\ ${session.AccessToken}`]
|
||||
['headers', headers]
|
||||
]));
|
||||
const ffmpegOutput = new FFmpegOutput(outputPath);
|
||||
const ffmpegCmd = new FFmpegCommand();
|
||||
|
||||
const cleanupFn = function () {
|
||||
pbar.stop();
|
||||
|
||||
try {
|
||||
fs.unlinkSync(outputPath);
|
||||
} catch(e) {}
|
||||
}
|
||||
|
||||
pbar.start(video.totalChunks, 0, {
|
||||
speed: '0'
|
||||
@@ -203,13 +220,7 @@ async function downloadVideo(videoUrls: string[], outputDirectories: string[], s
|
||||
process.exit(ERROR_CODE.UNK_FFMPEG_ERROR);
|
||||
});
|
||||
|
||||
process.on('SIGINT', () => {
|
||||
pbar.stop();
|
||||
|
||||
try {
|
||||
fs.unlinkSync(outputPath);
|
||||
} catch (e) {}
|
||||
});
|
||||
process.on('SIGINT', cleanupFn);
|
||||
|
||||
// let the magic begin...
|
||||
await new Promise((resolve: any, reject: any) => {
|
||||
@@ -221,6 +232,8 @@ async function downloadVideo(videoUrls: string[], outputDirectories: string[], s
|
||||
|
||||
ffmpegCmd.spawn();
|
||||
});
|
||||
|
||||
process.off('SIGINT', cleanupFn);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user