
* Added Chromium caching of identity provider cookies * Moved token expiry check in standalone method * Created refreshSession function * Session is now refreshed if the token expires * Linting fixes * Removed debug console.log() * Added CC support * Created function to prompt user for download parameters (interactive mode) * Fix data folder for puppeteer * Fixed multiple session error * Fix token expire time * Moved session refreshing to a more sensible place * Changed Metadata name to Video (to better reflect the data structure) * Complete CLI refactoring * Removed useless sleep function * Added outDir check from CLI * Complete input parsing refactoring (both inline and file) * Fixed and improved tests to work with the new input parsing * Moved and improved output path generation to videoUtils * Main code refactoring, added outpath to video type * Minor changes in spacing and type definition style * Updated readme after code refactoring * Fix if inputFile doesn't start with url on line 1 * Minor naming change * Use module 'winston' for logging * Created logge, changed all console.log and similar to use the logger * Added verbose logging, changed posterUrl property name on Video type * Moved GUID extraction to input parsing * Added support for group links * Fixed test after last input parsing update * Removed debug proces.exit() * Changed from desc to asc order for group videos * Updated test to reflect GUIDs output after parsing * Added couple of comments and restyled some imports * More readable verbose GUIDs logging * Removed unused errors * Temporary fix for timeout not working in ApiClient * Explicit class member accessibility * Defined array naming schema to be Array<T> * Defined type/interface schema to be type only * A LOT of type definitions
173 lines
5 KiB
TypeScript
173 lines
5 KiB
TypeScript
import { CLI_ERROR, ERROR_CODE } from './Errors';
|
|
import { checkOutDir } from './Utils';
|
|
import { logger } from './Logger';
|
|
|
|
import fs from 'fs';
|
|
import readlineSync from 'readline-sync';
|
|
import yargs from 'yargs';
|
|
|
|
|
|
export const argv: any = yargs.options({
|
|
username: {
|
|
alias: 'u',
|
|
type: 'string',
|
|
describe: 'The username used to log into Microsoft Stream (enabling this will fill in the email field for you)',
|
|
demandOption: false
|
|
},
|
|
videoUrls: {
|
|
alias: 'i',
|
|
describe: 'List of video urls',
|
|
type: 'array',
|
|
demandOption: false
|
|
},
|
|
inputFile: {
|
|
alias: 'f',
|
|
describe: 'Path to text file containing URLs and optionally outDirs. See the README for more on outDirs.',
|
|
type: 'string',
|
|
demandOption: false
|
|
},
|
|
outputDirectory: {
|
|
alias: 'o',
|
|
describe: 'The directory where destreamer will save your downloads',
|
|
type: 'string',
|
|
default: 'videos',
|
|
demandOption: false
|
|
},
|
|
keepLoginCookies: {
|
|
alias: 'k',
|
|
describe: 'Let Chromium cache identity provider cookies so you can use "Remember me" during login',
|
|
type: 'boolean',
|
|
default: false,
|
|
demandOption: false
|
|
},
|
|
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,
|
|
demandOption: false
|
|
},
|
|
closedCaptions: {
|
|
alias: 'cc',
|
|
describe: 'Check if closed captions are aviable and let the user choose which one to download (will not ask if only one aviable)',
|
|
type: 'boolean',
|
|
default: false,
|
|
demandOption: false
|
|
},
|
|
noCleanup: {
|
|
alias: 'nc',
|
|
describe: 'Do not delete the downloaded video file when an FFmpeg error occurs',
|
|
type: 'boolean',
|
|
default: false,
|
|
demandOption: false
|
|
},
|
|
vcodec: {
|
|
describe: 'Re-encode video track. Specify FFmpeg codec (e.g. libx265) or set to "none" to disable video.',
|
|
type: 'string',
|
|
default: 'copy',
|
|
demandOption: false
|
|
},
|
|
acodec: {
|
|
describe: 'Re-encode audio track. Specify FFmpeg codec (e.g. libopus) or set to "none" to disable audio.',
|
|
type: 'string',
|
|
default: 'copy',
|
|
demandOption: false
|
|
},
|
|
format: {
|
|
describe: 'Output container format (mkv, mp4, mov, anything that FFmpeg supports)',
|
|
type: 'string',
|
|
default: 'mkv',
|
|
demandOption: false
|
|
},
|
|
skip: {
|
|
describe: 'Skip download if file already exists',
|
|
type: 'boolean',
|
|
default: false,
|
|
demandOption: false
|
|
}
|
|
})
|
|
.wrap(120)
|
|
.check(() => noArguments())
|
|
.check((argv: any) => inputConflicts(argv.videoUrls, argv.inputFile))
|
|
.check((argv: any) => {
|
|
if (checkOutDir(argv.outputDirectory)) {
|
|
return true;
|
|
}
|
|
else {
|
|
logger.error(CLI_ERROR.INVALID_OUTDIR);
|
|
|
|
throw new Error(' ');
|
|
}
|
|
})
|
|
.argv;
|
|
|
|
|
|
function noArguments(): boolean {
|
|
// if only 2 args no other args (0: node path, 1: js script path)
|
|
if (process.argv.length === 2) {
|
|
logger.error(CLI_ERROR.MISSING_INPUT_ARG, {fatal: true});
|
|
|
|
// so that the output stays clear
|
|
throw new Error(' ');
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
function inputConflicts(videoUrls: Array<string | number> | undefined,
|
|
inputFile: string | undefined): boolean {
|
|
// check if both inputs are declared
|
|
if ((videoUrls !== undefined) && (inputFile !== undefined)) {
|
|
logger.error(CLI_ERROR.INPUT_ARG_CONFLICT);
|
|
|
|
throw new Error(' ');
|
|
}
|
|
// check if no input is declared or if they are declared but empty
|
|
else if (!(videoUrls || inputFile) || (videoUrls?.length === 0) || (inputFile?.length === 0)) {
|
|
logger.error(CLI_ERROR.MISSING_INPUT_ARG);
|
|
|
|
throw new Error(' ');
|
|
}
|
|
else if (inputFile) {
|
|
// check if inputFile doesn't end in '.txt'
|
|
if (inputFile.substring(inputFile.length - 4) !== '.txt') {
|
|
logger.error(CLI_ERROR.INPUTFILE_WRONG_EXTENSION);
|
|
|
|
throw new Error(' ');
|
|
}
|
|
// check if the inputFile exists
|
|
else if (!fs.existsSync(inputFile)) {
|
|
logger.error(CLI_ERROR.INPUTFILE_NOT_FOUND);
|
|
|
|
throw new Error(' ');
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
export function promptUser(choices: Array<string>): number {
|
|
let index: number = readlineSync.keyInSelect(choices, 'Which resolution/format do you prefer?');
|
|
|
|
if (index === -1) {
|
|
process.exit(ERROR_CODE.CANCELLED_USER_INPUT);
|
|
}
|
|
|
|
return index;
|
|
}
|