From 9faa0c4846cdc48bae5af8a7332fca6b943a29a2 Mon Sep 17 00:00:00 2001 From: kylon Date: Sat, 11 Apr 2020 15:12:46 +0200 Subject: [PATCH] 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 --- package-lock.json | 216 +++++------------------------ package.json | 7 +- Metadata.ts => src/Metadata.ts | 16 ++- Thumbnail.ts => src/Thumbnail.ts | 0 TokenCache.ts => src/TokenCache.ts | 0 Types.ts => src/Types.ts | 1 + destreamer.ts => src/destreamer.ts | 65 ++++++--- utils.ts => src/utils.ts | 10 ++ test/test.ts | 2 +- tsconfig.json | 2 + 10 files changed, 118 insertions(+), 201 deletions(-) rename Metadata.ts => src/Metadata.ts (75%) rename Thumbnail.ts => src/Thumbnail.ts (100%) rename TokenCache.ts => src/TokenCache.ts (100%) rename Types.ts => src/Types.ts (94%) rename destreamer.ts => src/destreamer.ts (79%) rename utils.ts => src/utils.ts (86%) diff --git a/package-lock.json b/package-lock.json index c5beb35..1e0406f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index c4d8619..cb17a23 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/Metadata.ts b/src/Metadata.ts similarity index 75% rename from Metadata.ts rename to src/Metadata.ts index 658a1c7..87faf65 100644 --- a/Metadata.ts +++ b/src/Metadata.ts @@ -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 { 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; -} \ No newline at end of file +} diff --git a/Thumbnail.ts b/src/Thumbnail.ts similarity index 100% rename from Thumbnail.ts rename to src/Thumbnail.ts diff --git a/TokenCache.ts b/src/TokenCache.ts similarity index 100% rename from TokenCache.ts rename to src/TokenCache.ts diff --git a/Types.ts b/src/Types.ts similarity index 94% rename from Types.ts rename to src/Types.ts index 3c71868..7709449 100644 --- a/Types.ts +++ b/src/Types.ts @@ -7,6 +7,7 @@ export type Session = { export type Metadata = { date: string; + duration: number; title: string; playbackUrl: string; posterImage: string; diff --git a/destreamer.ts b/src/destreamer.ts similarity index 79% rename from destreamer.ts rename to src/destreamer.ts index f6eba2a..0647dbc 100644 --- a/destreamer.ts +++ b/src/destreamer.ts @@ -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(); diff --git a/utils.ts b/src/utils.ts similarity index 86% rename from utils.ts rename to src/utils.ts index f8095d0..19bd49c 100644 --- a/utils.ts +++ b/src/utils.ts @@ -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; +} diff --git a/test/test.ts b/test/test.ts index 003fd65..c4dda9c 100644 --- a/test/test.ts +++ b/test/test.ts @@ -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'; diff --git a/tsconfig.json b/tsconfig.json index 8340d60..c8a30ce 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -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. */