Added ffmpeg progress bar via fluent-ffmpeg and progress libs (#57)

* Add fluent-ffmpeg back and cross-platform progress bar

* Repo clean up

Move ts files to src, build and output js files to build folder

* Do not print messages when exit code is 0

this is triggered by signal events

Co-authored-by: kylon <kylonux@gmail.com>
This commit is contained in:
kylon 2020-04-11 15:12:46 +02:00 committed by GitHub
parent d489b02d03
commit 9faa0c4846
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 118 additions and 201 deletions

216
package-lock.json generated
View file

@ -30,44 +30,6 @@
"js-tokens": "^4.0.0"
}
},
"@sidneys/cli-progress": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@sidneys/cli-progress/-/cli-progress-2.2.0.tgz",
"integrity": "sha512-JMIiVpZpDhH0HgHEpM4HH3SxE0OkwzOaCqpm3GxuHHG3bIsEju6pcvrA6FyANI6Tbt991hZnH/flJX+DNNmmwA==",
"requires": {
"colors": "^1.3.2",
"string-width": "^2.1.1"
},
"dependencies": {
"ansi-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
"integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg="
},
"is-fullwidth-code-point": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
"integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8="
},
"string-width": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
"integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
"requires": {
"is-fullwidth-code-point": "^2.0.0",
"strip-ansi": "^4.0.0"
}
},
"strip-ansi": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
"integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
"requires": {
"ansi-regex": "^3.0.0"
}
}
}
},
"@sindresorhus/jimp": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/@sindresorhus/jimp/-/jimp-0.3.0.tgz",
@ -108,6 +70,14 @@
}
}
},
"@types/cli-progress": {
"version": "3.4.2",
"resolved": "https://registry.npmjs.org/@types/cli-progress/-/cli-progress-3.4.2.tgz",
"integrity": "sha512-9Rlk664JggbgDLDMCM/8HziTh6ZU2IBLVS2/Kkh3T/TNVlpWlwgLrFl7kDyQBOlX1pofPM05ZKG/GyuULJ0FfA==",
"requires": {
"@types/node": "*"
}
},
"@types/color-name": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz",
@ -340,11 +310,6 @@
"execa": "^1.0.0"
}
},
"app-root-path": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-2.2.1.tgz",
"integrity": "sha512-91IFKeKk7FjfmezPKkwtaRvSpnUc4gDwPAjA1YZ9Gn0q0PPeW+vbeUsZuyDwjI7+QTHhcLen2v25fi/AmhvbJA=="
},
"argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
@ -360,6 +325,11 @@
"integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==",
"dev": true
},
"async": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/async/-/async-3.2.0.tgz",
"integrity": "sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw=="
},
"async-limiter": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz",
@ -518,6 +488,15 @@
"restore-cursor": "^3.1.0"
}
},
"cli-progress": {
"version": "3.7.0",
"resolved": "https://registry.npmjs.org/cli-progress/-/cli-progress-3.7.0.tgz",
"integrity": "sha512-xo2HeQ3vNyAO2oYF5xfrk5YM6jzaDNEbeJRLAQir6QlH54g4f6AXW+fLyJ/f12gcTaCbJznsOdQcr/yusp/Kjg==",
"requires": {
"colors": "^1.1.2",
"string-width": "^4.2.0"
}
},
"cli-width": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz",
@ -613,14 +592,6 @@
"object-keys": "^1.0.12"
}
},
"define-property": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
"integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
"requires": {
"is-descriptor": "^1.0.0"
}
},
"depd": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
@ -646,11 +617,6 @@
"resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz",
"integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w=="
},
"ellipsize": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/ellipsize/-/ellipsize-0.1.0.tgz",
"integrity": "sha1-nUNoLUS5GtFuvYQmisEDFwplU/g="
},
"emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
@ -965,52 +931,6 @@
"pend": "~1.2.0"
}
},
"ffmpeg-progressbar-cli": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/ffmpeg-progressbar-cli/-/ffmpeg-progressbar-cli-1.5.0.tgz",
"integrity": "sha512-0oEomm2If0xN8Cop4eVL+6OSPAhSs4V+89xdRBztGj+tqrBGp6fBhBK+OzN5Epi+1NuSgTxDQzFno1SioUp4ZQ==",
"requires": {
"@sidneys/cli-progress": "^2.2.0",
"app-root-path": "^2.1.0",
"chalk": "^2.4.1",
"ellipsize": "^0.1.0",
"ini": "^1.3.5",
"moment": "^2.22.2",
"moment-duration-format": "^2.2.2",
"string-width": "^2.1.1",
"which": "^1.3.1",
"window-size": "^1.1.1"
},
"dependencies": {
"ansi-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
"integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg="
},
"is-fullwidth-code-point": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
"integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8="
},
"string-width": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
"integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
"requires": {
"is-fullwidth-code-point": "^2.0.0",
"strip-ansi": "^4.0.0"
}
},
"strip-ansi": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
"integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
"requires": {
"ansi-regex": "^3.0.0"
}
}
}
},
"figures": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz",
@ -1097,6 +1017,15 @@
"integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==",
"dev": true
},
"fluent-ffmpeg": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/fluent-ffmpeg/-/fluent-ffmpeg-2.1.2.tgz",
"integrity": "sha1-yVLeIkD4EuvaCqgAbXd27irPfXQ=",
"requires": {
"async": ">=0.2.9",
"which": "^1.1.1"
}
},
"follow-redirects": {
"version": "1.5.10",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz",
@ -1294,11 +1223,6 @@
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"ini": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz",
"integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw=="
},
"inquirer": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.1.0.tgz",
@ -1347,14 +1271,6 @@
}
}
},
"is-accessor-descriptor": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
"integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
"requires": {
"kind-of": "^6.0.0"
}
},
"is-admin": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-admin/-/is-admin-3.0.0.tgz",
@ -1372,41 +1288,18 @@
"binary-extensions": "^2.0.0"
}
},
"is-buffer": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
"integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w=="
},
"is-callable": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz",
"integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==",
"dev": true
},
"is-data-descriptor": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
"integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
"requires": {
"kind-of": "^6.0.0"
}
},
"is-date-object": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz",
"integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==",
"dev": true
},
"is-descriptor": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
"integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
"requires": {
"is-accessor-descriptor": "^1.0.0",
"is-data-descriptor": "^1.0.0",
"kind-of": "^6.0.2"
}
},
"is-elevated": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-elevated/-/is-elevated-3.0.0.tgz",
@ -1491,6 +1384,11 @@
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA="
},
"iso8601-duration": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/iso8601-duration/-/iso8601-duration-1.2.0.tgz",
"integrity": "sha512-ErTBd++b17E8nmWII1K1uZtBgD1E8RjyvwmxlCjPHNqHMD7gmcMHOw0E8Ro/6+QT4PhHRSnnMo7bxa1vFPkwhg=="
},
"iterm2-version": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/iterm2-version/-/iterm2-version-4.2.0.tgz",
@ -1542,11 +1440,6 @@
"resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-2.2.0.tgz",
"integrity": "sha1-fYa9VmefWM5qhHBKZX3TkruoGnk="
},
"kind-of": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
"integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw=="
},
"levn": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz",
@ -1888,16 +1781,6 @@
}
}
},
"moment": {
"version": "2.24.0",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz",
"integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg=="
},
"moment-duration-format": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/moment-duration-format/-/moment-duration-format-2.3.2.tgz",
"integrity": "sha512-cBMXjSW+fjOb4tyaVHuaVE/A5TqkukDWiOfxxAjY+PEqmmBQlLwn+8OzwPiG3brouXKY5Un4pBjAeB6UToXHaQ=="
},
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
@ -2755,33 +2638,6 @@
}
}
},
"window-size": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/window-size/-/window-size-1.1.1.tgz",
"integrity": "sha512-5D/9vujkmVQ7pSmc0SCBmHXbkv6eaHwXEx65MywhmUMsI8sGqJ972APq1lotfcwMKPFLuCFfL8xGHLIp7jaBmA==",
"requires": {
"define-property": "^1.0.0",
"is-number": "^3.0.0"
},
"dependencies": {
"is-number": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
"integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=",
"requires": {
"kind-of": "^3.0.2"
}
},
"kind-of": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
"integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
"requires": {
"is-buffer": "^1.1.5"
}
}
}
},
"word-wrap": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",

View file

@ -11,7 +11,7 @@
"build": "echo Transpiling TypeScript to JavaScript... & node node_modules/typescript/bin/tsc --listEmittedFiles",
"run": "node ./destreamer.js",
"start": "npm run -s build & npm run -s run",
"test": "mocha"
"test": "mocha build/test"
},
"keywords": [],
"author": "snobu",
@ -28,12 +28,15 @@
"tmp": "^0.1.0"
},
"dependencies": {
"@types/cli-progress": "^3.4.2",
"@types/fluent-ffmpeg": "^2.1.14",
"@types/jwt-decode": "^2.2.1",
"axios": "^0.19.2",
"cli-progress": "^3.7.0",
"colors": "^1.4.0",
"ffmpeg-progressbar-cli": "^1.5.0",
"fluent-ffmpeg": "^2.1.2",
"is-elevated": "^3.0.0",
"iso8601-duration": "^1.2.0",
"jwt-decode": "^2.2.0",
"puppeteer": "^2.1.1",
"sanitize-filename": "^1.6.3",

View file

@ -1,5 +1,6 @@
import { Metadata, Session } from './Types';
import { parse } from 'iso8601-duration';
import axios from 'axios';
function publishedDateToString(date: string) {
@ -10,10 +11,21 @@ function publishedDateToString(date: string) {
return day+'-'+month+'-'+dateJs.getFullYear();
}
function durationToTotalChuncks(duration: string) {
const durationObj = parse(duration);
const hrs = durationObj['hours'] ?? 0;
const mins = durationObj['minutes'] ?? 0;
const secs = Math.ceil(durationObj['seconds'] ?? 0);
return hrs * 1000 + mins * 100 + secs;
}
export async function getVideoMetadata(videoGuids: string[], session: Session, verbose: boolean): Promise<Metadata[]> {
let metadata: Metadata[] = [];
let title: string;
let date: string;
let duration: number;
let playbackUrl: string;
let posterImage: string;
@ -38,9 +50,11 @@ export async function getVideoMetadata(videoGuids: string[], session: Session, v
posterImage = response.data['posterImage']['medium']['url'];
date = publishedDateToString(response.data['publishedDate']);
duration = durationToTotalChuncks(response.data.media['duration']);
metadata.push({
date: date,
duration: duration,
title: title,
playbackUrl: playbackUrl,
posterImage: posterImage
@ -48,4 +62,4 @@ export async function getVideoMetadata(videoGuids: string[], session: Session, v
}));
return metadata;
}
}

View file

@ -7,6 +7,7 @@ export type Session = {
export type Metadata = {
date: string;
duration: number;
title: string;
playbackUrl: string;
posterImage: string;

View file

@ -1,4 +1,4 @@
import { sleep, parseVideoUrls, checkRequirements, makeUniqueTitle } from './utils';
import { sleep, parseVideoUrls, checkRequirements, makeUniqueTitle, ffmpegTimemarkToChunk } from './utils';
import { TokenCache } from './TokenCache';
import { getVideoMetadata } from './Metadata';
import { Metadata, Session, Errors } from './Types';
@ -6,13 +6,13 @@ import { drawThumbnail } from './Thumbnail';
import isElevated from 'is-elevated';
import puppeteer from 'puppeteer';
import { execSync } from 'child_process';
import colors from 'colors';
import fs from 'fs';
import path from 'path';
import yargs from 'yargs';
import sanitize from 'sanitize-filename';
import ffmpeg from 'fluent-ffmpeg';
import cliProgress from 'cli-progress';
let tokenCache = new TokenCache();
@ -65,9 +65,7 @@ async function init() {
});
process.on('exit', (code) => {
if (code === 0)
console.log(colors.bgGreen('\n\nDestreamer finished successfully! \n'))
else if (code in Errors)
if (code in Errors)
console.error(colors.bgRed(`\n\nError: ${Errors[code]} \n`))
else
console.error(colors.bgRed(`\n\nUnknown exit code ${code} \n`))
@ -182,6 +180,14 @@ function extractVideoGuid(videoUrls: string[]): string[] {
async function downloadVideo(videoUrls: string[], outputDirectory: string, session: Session) {
const videoGuids = extractVideoGuid(videoUrls);
const pbar = new cliProgress.SingleBar({
barCompleteChar: '\u2588',
barIncompleteChar: '\u2591',
format: 'progress [{bar}] {percentage}% {speed}Kbps {eta_formatted}',
barsize: Math.floor(process.stdout.columns / 3),
stopOnComplete: true,
etaBuffer: 20
});
console.log('Fetching metadata...');
@ -204,27 +210,52 @@ async function downloadVideo(videoUrls: string[], outputDirectory: string, sessi
video.title = makeUniqueTitle(sanitize(video.title) + ' - ' + video.date, argv.outputDirectory);
const outputPath = outputDirectory + path.sep + video.title + '.mp4';
// Very experimental inline thumbnail rendering
if (!argv.noThumbnails)
await drawThumbnail(video.posterImage, session.AccessToken);
console.info('Spawning ffmpeg with access token and HLS URL. This may take a few seconds...\n');
const outputPath = outputDirectory + path.sep + video.title + '.mp4';
ffmpeg()
.input(video.playbackUrl)
.inputOption([
// Never remove those "useless" escapes or ffmpeg will not
// pick up the header correctly
// eslint-disable-next-line no-useless-escape
'-headers', `Authorization:\ Bearer\ ${session.AccessToken}`,
])
.format('mp4')
.saveToFile(outputPath)
.on('codecData', data => {
console.log(`Input is ${data.video} with ${data.audio} audio.\n`);
// We probably need a way to be deterministic about
// how we locate that ffmpeg-bar wrapper, npx maybe?
// Do not remove those "useless" escapes or ffmpeg will
// not pick up the header correctly.
// eslint-disable-next-line no-useless-escape
let cmd = `node_modules/.bin/ffmpeg-bar -headers "Authorization:\ Bearer\ ${session.AccessToken}" -i "${video.playbackUrl}" -y "${outputPath}"`;
execSync(cmd, {stdio: 'inherit'});
console.info(`Download finished: ${outputPath}`);
pbar.start(video.duration, 0, {
speed: '0'
});
process.on('SIGINT', () => {
pbar.stop();
});
})
.on('progress', progress => {
const currentChuncks = ffmpegTimemarkToChunk(progress.timemark);
pbar.update(currentChuncks, {
speed: progress.currentKbps
});
})
.on('error', err => {
pbar.stop();
console.log(`ffmpeg returned an error: ${err.message}`);
})
.on('end', () => {
console.log(colors.green(`\nDownload finished: ${outputPath}`));
});
}));
}
async function main() {
checkRequirements() ?? process.exit(22);
await init();

View file

@ -71,3 +71,13 @@ export function makeUniqueTitle(title: string, outDir: string) {
return ntitle;
}
export function ffmpegTimemarkToChunk(timemark: string) {
const timeVals: string[] = timemark.split(':');
const hrs = parseInt(timeVals[0]);
const mins = parseInt(timeVals[1]);
const secs = parseInt(timeVals[2]);
return hrs * 1000 + mins * 100 + secs;
}

View file

@ -1,4 +1,4 @@
import { parseVideoUrls } from '../utils';
import { parseVideoUrls } from '../src/utils';
import puppeteer from 'puppeteer';
import assert from 'assert';
import tmp from 'tmp';

View file

@ -1,5 +1,7 @@
{
"compilerOptions": {
"rootDirs": ["./src", "./test"],
"outDir": "./build",
"target": "ES2019", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
"strict": true, /* Enable all strict type-checking options. */