twitch-video-downloader-plus 1.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +9 -0
- package/README.md +284 -0
- package/build/chat-downloader.d.ts +13 -0
- package/build/chat-downloader.js +46 -0
- package/build/chat-downloader.js.map +1 -0
- package/build/downloader.d.ts +12 -0
- package/build/downloader.js +92 -0
- package/build/downloader.js.map +1 -0
- package/build/enums/errors.d.ts +8 -0
- package/build/enums/errors.js +13 -0
- package/build/enums/errors.js.map +1 -0
- package/build/enums/twitch-api.d.ts +3 -0
- package/build/enums/twitch-api.js +8 -0
- package/build/enums/twitch-api.js.map +1 -0
- package/build/index.d.ts +5 -0
- package/build/index.js +14 -0
- package/build/index.js.map +1 -0
- package/build/interfaces/access-token.d.ts +32 -0
- package/build/interfaces/access-token.js +3 -0
- package/build/interfaces/access-token.js.map +1 -0
- package/build/interfaces/comment.d.ts +18 -0
- package/build/interfaces/comment.js +3 -0
- package/build/interfaces/comment.js.map +1 -0
- package/build/interfaces/fragments-response.d.ts +4 -0
- package/build/interfaces/fragments-response.js +3 -0
- package/build/interfaces/fragments-response.js.map +1 -0
- package/build/interfaces/hls-video.d.ts +5 -0
- package/build/interfaces/hls-video.js +3 -0
- package/build/interfaces/hls-video.js.map +1 -0
- package/build/interfaces/manifiest-response.d.ts +6 -0
- package/build/interfaces/manifiest-response.js +3 -0
- package/build/interfaces/manifiest-response.js.map +1 -0
- package/build/interfaces/mkv-video.d.ts +5 -0
- package/build/interfaces/mkv-video.js +3 -0
- package/build/interfaces/mkv-video.js.map +1 -0
- package/build/interfaces/transcode-options.d.ts +4 -0
- package/build/interfaces/transcode-options.js +3 -0
- package/build/interfaces/transcode-options.js.map +1 -0
- package/build/interfaces/video-download-information.d.ts +5 -0
- package/build/interfaces/video-download-information.js +3 -0
- package/build/interfaces/video-download-information.js.map +1 -0
- package/build/twitch-oauth.d.ts +9 -0
- package/build/twitch-oauth.js +37 -0
- package/build/twitch-oauth.js.map +1 -0
- package/build/utils/filesystem.d.ts +1 -0
- package/build/utils/filesystem.js +11 -0
- package/build/utils/filesystem.js.map +1 -0
- package/build/utils/is-json.d.ts +1 -0
- package/build/utils/is-json.js +14 -0
- package/build/utils/is-json.js.map +1 -0
- package/build/utils/logger.d.ts +1 -0
- package/build/utils/logger.js +39 -0
- package/build/utils/logger.js.map +1 -0
- package/build/utils/parse-url.d.ts +1 -0
- package/build/utils/parse-url.js +14 -0
- package/build/utils/parse-url.js.map +1 -0
- package/build/video-downloader.d.ts +32 -0
- package/build/video-downloader.js +158 -0
- package/build/video-downloader.js.map +1 -0
- package/build/video-fragments.d.ts +7 -0
- package/build/video-fragments.js +33 -0
- package/build/video-fragments.js.map +1 -0
- package/build/video-manifiest.d.ts +12 -0
- package/build/video-manifiest.js +83 -0
- package/build/video-manifiest.js.map +1 -0
- package/package.json +52 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifiest-response.js","sourceRoot":"","sources":["../../src/interfaces/manifiest-response.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mkv-video.js","sourceRoot":"","sources":["../../src/interfaces/mkv-video.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transcode-options.js","sourceRoot":"","sources":["../../src/interfaces/transcode-options.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"video-download-information.js","sourceRoot":"","sources":["../../src/interfaces/video-download-information.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface LoginOptions {
|
|
2
|
+
authy_token?: string;
|
|
3
|
+
client_id?: string;
|
|
4
|
+
remember_me?: boolean;
|
|
5
|
+
}
|
|
6
|
+
export declare class TwitchOAuth {
|
|
7
|
+
private readonly PASSPORT_ENDPOINT;
|
|
8
|
+
login(username: string, password: string, options?: LoginOptions): Promise<any>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.TwitchOAuth = void 0;
|
|
7
|
+
const cross_fetch_1 = __importDefault(require("cross-fetch"));
|
|
8
|
+
class TwitchOAuth {
|
|
9
|
+
constructor() {
|
|
10
|
+
this.PASSPORT_ENDPOINT = "https://passport.twitch.tv/login";
|
|
11
|
+
}
|
|
12
|
+
async login(username, password, options) {
|
|
13
|
+
const payload = {
|
|
14
|
+
username: username,
|
|
15
|
+
password: password,
|
|
16
|
+
authy_token: options === null || options === void 0 ? void 0 : options.authy_token,
|
|
17
|
+
client_id: (options === null || options === void 0 ? void 0 : options.client_id) || "kimne78kx3ncx6brgo4mv6wki5h1ko",
|
|
18
|
+
remember_me: (options === null || options === void 0 ? void 0 : options.remember_me) || true,
|
|
19
|
+
undelete_user: false
|
|
20
|
+
};
|
|
21
|
+
const passportFetchOptions = {
|
|
22
|
+
method: "POST",
|
|
23
|
+
body: JSON.stringify(payload)
|
|
24
|
+
};
|
|
25
|
+
return (0, cross_fetch_1.default)(this.PASSPORT_ENDPOINT, passportFetchOptions)
|
|
26
|
+
.then((response) => response.json())
|
|
27
|
+
.then((response) => {
|
|
28
|
+
if (response && response.error_code)
|
|
29
|
+
throw new Error(`[TwitchOAuth] Twitch server say: ${response.error}`);
|
|
30
|
+
if (!response.access_token)
|
|
31
|
+
throw new Error(`[TwitchOAuth] Unknown error.`);
|
|
32
|
+
return response.access_token;
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
exports.TwitchOAuth = TwitchOAuth;
|
|
37
|
+
//# sourceMappingURL=twitch-oauth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"twitch-oauth.js","sourceRoot":"","sources":["../src/twitch-oauth.ts"],"names":[],"mappings":";;;;;;AAAA,8DAAgC;AAqBhC,MAAa,WAAW;IAAxB;QACqB,sBAAiB,GAAW,kCAAkC,CAAC;IA2BpF,CAAC;IAzBU,KAAK,CAAC,KAAK,CAAC,QAAgB,EAAE,QAAgB,EAAE,OAAsB;QACzE,MAAM,OAAO,GAAiB;YAC1B,QAAQ,EAAE,QAAQ;YAClB,QAAQ,EAAE,QAAQ;YAClB,WAAW,EAAE,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,WAAW;YACjC,SAAS,EAAE,CAAA,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,SAAS,KAAI,gCAAgC;YACjE,WAAW,EAAE,CAAA,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,WAAW,KAAI,IAAI;YACzC,aAAa,EAAE,KAAK;SACvB,CAAC;QAEF,MAAM,oBAAoB,GAAgB;YACtC,MAAM,EAAE,MAAM;YACd,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC;SAChC,CAAC;QAEF,OAAO,IAAA,qBAAK,EAAC,IAAI,CAAC,iBAAiB,EAAE,oBAAoB,CAAC;aACrD,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;aACnC,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE;YACf,IAAI,QAAQ,IAAI,QAAQ,CAAC,UAAU;gBAAE,MAAM,IAAI,KAAK,CAAC,oCAAoC,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC;YAE3G,IAAI,CAAC,QAAQ,CAAC,YAAY;gBAAE,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC;YAE5E,OAAO,QAAQ,CAAC,YAAY,CAAC;QACjC,CAAC,CAAC,CAAC;IACX,CAAC;CACJ;AA5BD,kCA4BC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function ensureDirectoryExists(path: string): void;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ensureDirectoryExists = void 0;
|
|
4
|
+
const fs_1 = require("fs");
|
|
5
|
+
function ensureDirectoryExists(path) {
|
|
6
|
+
if (!(0, fs_1.existsSync)(path)) {
|
|
7
|
+
(0, fs_1.mkdirSync)(path, { recursive: true });
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
exports.ensureDirectoryExists = ensureDirectoryExists;
|
|
11
|
+
//# sourceMappingURL=filesystem.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filesystem.js","sourceRoot":"","sources":["../../src/utils/filesystem.ts"],"names":[],"mappings":";;;AAAA,2BAA2C;AAE3C,SAAgB,qBAAqB,CAAC,IAAY;IAC9C,IAAI,CAAC,IAAA,eAAU,EAAC,IAAI,CAAC,EAAE;QACnB,IAAA,cAAS,EAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;KACxC;AACL,CAAC;AAJD,sDAIC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function parseIfIsJSON<T>(text: string): T | null;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseIfIsJSON = void 0;
|
|
4
|
+
function parseIfIsJSON(text) {
|
|
5
|
+
try {
|
|
6
|
+
const data = JSON.parse(text);
|
|
7
|
+
return data;
|
|
8
|
+
}
|
|
9
|
+
catch (error) {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
exports.parseIfIsJSON = parseIfIsJSON;
|
|
14
|
+
//# sourceMappingURL=is-json.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"is-json.js","sourceRoot":"","sources":["../../src/utils/is-json.ts"],"names":[],"mappings":";;;AAAA,SAAgB,aAAa,CAAI,IAAY;IACzC,IAAI;QACA,MAAM,IAAI,GAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAEjC,OAAO,IAAI,CAAC;KACf;IAAC,OAAO,KAAK,EAAE;QACZ,OAAO,IAAI,CAAC;KACf;AACL,CAAC;AARD,sCAQC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const log: (message: string) => void;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
|
|
5
|
+
}) : (function(o, m, k, k2) {
|
|
6
|
+
if (k2 === undefined) k2 = k;
|
|
7
|
+
o[k2] = m[k];
|
|
8
|
+
}));
|
|
9
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
10
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
11
|
+
}) : function(o, v) {
|
|
12
|
+
o["default"] = v;
|
|
13
|
+
});
|
|
14
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
15
|
+
if (mod && mod.__esModule) return mod;
|
|
16
|
+
var result = {};
|
|
17
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
18
|
+
__setModuleDefault(result, mod);
|
|
19
|
+
return result;
|
|
20
|
+
};
|
|
21
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
+
exports.log = void 0;
|
|
23
|
+
const winston_1 = __importStar(require("winston"));
|
|
24
|
+
const longFormat = winston_1.default.format.combine(winston_1.format.colorize(), winston_1.format.timestamp(), winston_1.format.align(), winston_1.format.printf((info) => `${info.timestamp} ${info.level} ${info.message}`));
|
|
25
|
+
const logger = (0, winston_1.createLogger)({
|
|
26
|
+
format: longFormat,
|
|
27
|
+
transports: [new winston_1.transports.Console({ level: "debug" })]
|
|
28
|
+
});
|
|
29
|
+
const log = (message) => {
|
|
30
|
+
const isDebug = process.env.TWITCH_VIDEO_DOWNLOADER_DEBUG === "true";
|
|
31
|
+
if (isDebug) {
|
|
32
|
+
if (logger.transports.length < 2) {
|
|
33
|
+
logger.add(new winston_1.transports.File({ filename: "debug.log", level: "debug" }));
|
|
34
|
+
}
|
|
35
|
+
logger.log({ level: "debug", message });
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
exports.log = log;
|
|
39
|
+
//# sourceMappingURL=logger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAAA,mDAAoE;AAEpE,MAAM,UAAU,GAAG,iBAAO,CAAC,MAAM,CAAC,OAAO,CACrC,gBAAM,CAAC,QAAQ,EAAE,EACjB,gBAAM,CAAC,SAAS,EAAE,EAClB,gBAAM,CAAC,KAAK,EAAE,EACd,gBAAM,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC,CAC7E,CAAC;AAEF,MAAM,MAAM,GAAG,IAAA,sBAAY,EAAC;IACxB,MAAM,EAAE,UAAU;IAClB,UAAU,EAAE,CAAC,IAAI,oBAAU,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC;CAC3D,CAAC,CAAC;AAEI,MAAM,GAAG,GAAG,CAAC,OAAe,EAAE,EAAE;IACnC,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,6BAA6B,KAAK,MAAM,CAAC;IAErE,IAAI,OAAO,EAAE;QACT,IAAI,MAAM,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE;YAC9B,MAAM,CAAC,GAAG,CAAC,IAAI,oBAAU,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,WAAW,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC;SAC9E;QAED,MAAM,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;KAC3C;AACL,CAAC,CAAC;AAVW,QAAA,GAAG,OAUd"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function parseURL(url: string): string;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseURL = void 0;
|
|
4
|
+
function parseURL(url) {
|
|
5
|
+
const REGEX = /(http(s)?:\/\/)?(www.)?twitch.tv\/videos\/(\d+)/;
|
|
6
|
+
const search = url.match(REGEX);
|
|
7
|
+
let result = "";
|
|
8
|
+
if (search) {
|
|
9
|
+
result = search[4];
|
|
10
|
+
}
|
|
11
|
+
return result;
|
|
12
|
+
}
|
|
13
|
+
exports.parseURL = parseURL;
|
|
14
|
+
//# sourceMappingURL=parse-url.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse-url.js","sourceRoot":"","sources":["../../src/utils/parse-url.ts"],"names":[],"mappings":";;;AAAA,SAAgB,QAAQ,CAAC,GAAW;IAChC,MAAM,KAAK,GAAG,iDAAiD,CAAC;IAChE,MAAM,MAAM,GAAG,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAEhC,IAAI,MAAM,GAAG,EAAE,CAAC;IAEhB,IAAI,MAAM,EAAE;QACR,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;KACtB;IAED,OAAO,MAAM,CAAC;AAClB,CAAC;AAXD,4BAWC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import { EventEmitter } from "events";
|
|
3
|
+
import { MkvVideo } from "./interfaces/mkv-video";
|
|
4
|
+
import { HslVideo } from "./interfaces/hls-video";
|
|
5
|
+
import { VideoDownloadInformation } from "./interfaces/video-download-information";
|
|
6
|
+
import { TranscodeOptions } from "./interfaces/transcode-options";
|
|
7
|
+
interface options {
|
|
8
|
+
poolLimit?: number;
|
|
9
|
+
downloadFolder?: string;
|
|
10
|
+
clientID?: string;
|
|
11
|
+
oAuthToken?: string;
|
|
12
|
+
debug?: boolean;
|
|
13
|
+
}
|
|
14
|
+
export declare class VideoDownloader extends EventEmitter {
|
|
15
|
+
private _clientID;
|
|
16
|
+
private _oAuthToken;
|
|
17
|
+
private _pathFolder;
|
|
18
|
+
private _poolLimit;
|
|
19
|
+
private _quality;
|
|
20
|
+
private _rootProjectPath;
|
|
21
|
+
private _totalVideoFragments;
|
|
22
|
+
private _videoFragmentsDownloaded;
|
|
23
|
+
private _videoURL;
|
|
24
|
+
private _vodID;
|
|
25
|
+
constructor(videoURL: string, options?: options);
|
|
26
|
+
getVideoResolutionsAvailable(): Promise<VideoDownloadInformation[]>;
|
|
27
|
+
download(video: VideoDownloadInformation): Promise<HslVideo>;
|
|
28
|
+
private _downloadFragments;
|
|
29
|
+
private _downloadFragment;
|
|
30
|
+
transcode(videoSaved: HslVideo, options?: TranscodeOptions): Promise<MkvVideo>;
|
|
31
|
+
}
|
|
32
|
+
export {};
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.VideoDownloader = void 0;
|
|
7
|
+
const events_1 = require("events");
|
|
8
|
+
const fs_1 = require("fs");
|
|
9
|
+
const path_1 = require("path");
|
|
10
|
+
const tiny_async_pool_1 = __importDefault(require("tiny-async-pool"));
|
|
11
|
+
const fluent_ffmpeg_1 = __importDefault(require("fluent-ffmpeg"));
|
|
12
|
+
const downloader_1 = require("./downloader");
|
|
13
|
+
const video_fragments_1 = require("./video-fragments");
|
|
14
|
+
const video_manifiest_1 = require("./video-manifiest");
|
|
15
|
+
const filesystem_1 = require("./utils/filesystem");
|
|
16
|
+
const logger_1 = require("./utils/logger");
|
|
17
|
+
const parse_url_1 = require("./utils/parse-url");
|
|
18
|
+
const errors_1 = require("./enums/errors");
|
|
19
|
+
class VideoDownloader extends events_1.EventEmitter {
|
|
20
|
+
constructor(videoURL, options) {
|
|
21
|
+
super();
|
|
22
|
+
this._clientID = "kimne78kx3ncx6brgo4mv6wki5h1ko";
|
|
23
|
+
this._oAuthToken = "";
|
|
24
|
+
this._pathFolder = "";
|
|
25
|
+
this._poolLimit = 20;
|
|
26
|
+
this._quality = "";
|
|
27
|
+
this._rootProjectPath = process.cwd();
|
|
28
|
+
this._totalVideoFragments = 0;
|
|
29
|
+
this._videoFragmentsDownloaded = [];
|
|
30
|
+
if (options) {
|
|
31
|
+
this._poolLimit = options.poolLimit ? options.poolLimit : this._poolLimit;
|
|
32
|
+
this._rootProjectPath = options.downloadFolder ? options.downloadFolder : this._rootProjectPath;
|
|
33
|
+
this._clientID = options.clientID ? options.clientID : this._clientID;
|
|
34
|
+
this._oAuthToken = options.oAuthToken ? options.oAuthToken : this._oAuthToken;
|
|
35
|
+
if (process.env.TWITCH_VIDEO_DOWNLOADER_DEBUG == null) {
|
|
36
|
+
process.env.TWITCH_VIDEO_DOWNLOADER_DEBUG = options.debug ? "true" : "false";
|
|
37
|
+
}
|
|
38
|
+
(0, logger_1.log)(`[VideoDownloader] Options:
|
|
39
|
+
${JSON.stringify(options, undefined, 2)}`);
|
|
40
|
+
}
|
|
41
|
+
this._videoURL = videoURL;
|
|
42
|
+
this._vodID = (0, parse_url_1.parseURL)(this._videoURL);
|
|
43
|
+
(0, logger_1.log)(`[VideoDownloader] VodID is ${this._vodID}.`);
|
|
44
|
+
}
|
|
45
|
+
async getVideoResolutionsAvailable() {
|
|
46
|
+
const manifiests = new video_manifiest_1.VideoManifiest(this._vodID, this._clientID, this._oAuthToken);
|
|
47
|
+
const resolutions = await manifiests.getVideoResolutions();
|
|
48
|
+
return resolutions;
|
|
49
|
+
}
|
|
50
|
+
async download(video) {
|
|
51
|
+
if (!video || !video.quality || !video.resolution || !video.url) {
|
|
52
|
+
(0, logger_1.log)(`[VideoDownloader] Video download request failed, invalid video information, object must have quality, resolution, and url fields`);
|
|
53
|
+
(0, logger_1.log)(JSON.stringify(video, undefined, 2));
|
|
54
|
+
throw new Error(errors_1.ERRORS.INVALID_VIDEO_METADATA);
|
|
55
|
+
}
|
|
56
|
+
(0, logger_1.log)(`[VideoDownloader] Video download request, with information`);
|
|
57
|
+
(0, logger_1.log)(JSON.stringify(video, undefined, 2));
|
|
58
|
+
const videoFragmentsFetcher = new video_fragments_1.VideoFragmentsFetcher();
|
|
59
|
+
const { rawContent, fragments } = await videoFragmentsFetcher.getFragments(video);
|
|
60
|
+
this._quality = video.quality;
|
|
61
|
+
this._pathFolder = (0, path_1.join)(this._rootProjectPath, `downloads/videos/${this._vodID}/hls/${video.quality}`);
|
|
62
|
+
(0, filesystem_1.ensureDirectoryExists)(this._pathFolder);
|
|
63
|
+
(0, logger_1.log)(`[VideoDownloader] Path of downloading ${this._pathFolder}`);
|
|
64
|
+
(0, fs_1.writeFileSync)(`${this._pathFolder}/index.m3u8`, rawContent);
|
|
65
|
+
if (fragments.length > 0) {
|
|
66
|
+
this._videoFragmentsDownloaded = [];
|
|
67
|
+
this._totalVideoFragments = fragments.length;
|
|
68
|
+
(0, logger_1.log)(`[VideoDownloader] Total fragments of video is ${this._totalVideoFragments}.`);
|
|
69
|
+
this.emit("start-download", {
|
|
70
|
+
vodID: this._vodID,
|
|
71
|
+
quality: this._quality,
|
|
72
|
+
folderPath: this._pathFolder
|
|
73
|
+
});
|
|
74
|
+
await this._downloadFragments(fragments);
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
(0, logger_1.log)(`[VideoDownloader] Not found fragments of VodID ${this._vodID}.`);
|
|
78
|
+
throw new Error(errors_1.ERRORS.NOT_FOUND_FRAGMENTS);
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
vodID: this._vodID,
|
|
82
|
+
quality: this._quality,
|
|
83
|
+
folderPath: this._pathFolder
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
async _downloadFragments(fragments) {
|
|
87
|
+
await (0, tiny_async_pool_1.default)(this._poolLimit, fragments, this._downloadFragment.bind(this));
|
|
88
|
+
}
|
|
89
|
+
async _downloadFragment(fragmentData) {
|
|
90
|
+
const [name, url] = fragmentData;
|
|
91
|
+
const path = `${this._pathFolder}/${name}`;
|
|
92
|
+
const downloader = new downloader_1.Downloader(url, path);
|
|
93
|
+
downloader.on("fragment-downloaded", (fragment) => {
|
|
94
|
+
this.emit("fragment-downloaded", fragment);
|
|
95
|
+
this._videoFragmentsDownloaded.push(fragment);
|
|
96
|
+
const percentage = (this._videoFragmentsDownloaded.length / this._totalVideoFragments) * 100;
|
|
97
|
+
if (percentage)
|
|
98
|
+
this.emit("progress-download", percentage);
|
|
99
|
+
});
|
|
100
|
+
await downloader.download();
|
|
101
|
+
return name;
|
|
102
|
+
}
|
|
103
|
+
transcode(videoSaved, options) {
|
|
104
|
+
(0, logger_1.log)("[VideoDownloader] Video transcode, with options");
|
|
105
|
+
if (options)
|
|
106
|
+
(0, logger_1.log)(JSON.stringify(options, undefined, 2));
|
|
107
|
+
const m3u8FilePath = (0, path_1.join)(videoSaved.folderPath, "index.m3u8");
|
|
108
|
+
if (!(0, fs_1.existsSync)(m3u8FilePath)) {
|
|
109
|
+
throw new Error(errors_1.ERRORS.M3U8_DOES_NOT_EXISTS);
|
|
110
|
+
}
|
|
111
|
+
const outputPathFolder = (0, path_1.join)(this._rootProjectPath, `downloads/videos/${videoSaved.vodID}/mkv`);
|
|
112
|
+
const outputPath = (options === null || options === void 0 ? void 0 : options.outputPath)
|
|
113
|
+
? (0, path_1.join)(options.outputPath, `${videoSaved.vodID}-${videoSaved.quality}.mkv`)
|
|
114
|
+
: (0, path_1.join)(outputPathFolder, `${videoSaved.quality}.mkv`);
|
|
115
|
+
(0, logger_1.log)(`[VideoDownloader] Transcode output path: ${outputPath}`);
|
|
116
|
+
(0, filesystem_1.ensureDirectoryExists)(outputPathFolder);
|
|
117
|
+
return new Promise((resolve, reject) => {
|
|
118
|
+
const transcoder = (0, fluent_ffmpeg_1.default)();
|
|
119
|
+
transcoder
|
|
120
|
+
.addInput(m3u8FilePath)
|
|
121
|
+
.outputOptions(["-codec copy", "-preset ultrafast"])
|
|
122
|
+
.output(outputPath)
|
|
123
|
+
.on("start", (commands) => {
|
|
124
|
+
(0, logger_1.log)(`[VideoDownloader] Transcode start, commands: ${commands}`);
|
|
125
|
+
this.emit("start-transcode", commands);
|
|
126
|
+
})
|
|
127
|
+
.on("end", (err, stdout, stderr) => {
|
|
128
|
+
if (err) {
|
|
129
|
+
(0, logger_1.log)(`[VideoDownloader] Transcode error: ${err}`);
|
|
130
|
+
return reject(err);
|
|
131
|
+
}
|
|
132
|
+
if (options === null || options === void 0 ? void 0 : options.deleteHslFiles) {
|
|
133
|
+
(0, logger_1.log)(`[VideoDownloader] Delete HLS files`);
|
|
134
|
+
(0, fs_1.rmSync)(videoSaved.folderPath, { recursive: true, force: true });
|
|
135
|
+
}
|
|
136
|
+
(0, logger_1.log)(`[VideoDownloader] Transcode end`);
|
|
137
|
+
return resolve({
|
|
138
|
+
vodID: videoSaved.vodID,
|
|
139
|
+
quality: videoSaved.quality,
|
|
140
|
+
filePath: outputPath
|
|
141
|
+
});
|
|
142
|
+
})
|
|
143
|
+
.on("progress", (progress) => {
|
|
144
|
+
if (progress.percent) {
|
|
145
|
+
(0, logger_1.log)(`[VideoDownloader] Transcode progress: ${progress.percent}%`);
|
|
146
|
+
this.emit("progress-transcode", progress.percent);
|
|
147
|
+
}
|
|
148
|
+
})
|
|
149
|
+
.on("error", (error) => {
|
|
150
|
+
(0, logger_1.log)(`[VideoDownloader] Transcode error: ${error}`);
|
|
151
|
+
reject(error);
|
|
152
|
+
})
|
|
153
|
+
.run();
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
exports.VideoDownloader = VideoDownloader;
|
|
158
|
+
//# sourceMappingURL=video-downloader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"video-downloader.js","sourceRoot":"","sources":["../src/video-downloader.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { GetFragmentsResponse } from "./interfaces/fragments-response";
|
|
2
|
+
import { VideoDownloadInformation } from "./interfaces/video-download-information";
|
|
3
|
+
export declare class VideoFragmentsFetcher {
|
|
4
|
+
private readonly MANIFIEST_URL_BASE;
|
|
5
|
+
private _getManifiest;
|
|
6
|
+
getFragments(manifiest: VideoDownloadInformation): Promise<GetFragmentsResponse>;
|
|
7
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.VideoFragmentsFetcher = void 0;
|
|
7
|
+
const cross_fetch_1 = __importDefault(require("cross-fetch"));
|
|
8
|
+
const logger_1 = require("./utils/logger");
|
|
9
|
+
const errors_1 = require("./enums/errors");
|
|
10
|
+
class VideoFragmentsFetcher {
|
|
11
|
+
constructor() {
|
|
12
|
+
this.MANIFIEST_URL_BASE = /^https?:\/\/.*\.net\/.*\//;
|
|
13
|
+
}
|
|
14
|
+
async _getManifiest(url) {
|
|
15
|
+
(0, logger_1.log)(`[VideoFragmentsFetcher] Downloading specific manifiest, url: \n ${url}`);
|
|
16
|
+
return (0, cross_fetch_1.default)(url).then((response) => response.text());
|
|
17
|
+
}
|
|
18
|
+
async getFragments(manifiest) {
|
|
19
|
+
const manifiestUrlBase = (manifiest.url.match(this.MANIFIEST_URL_BASE) || [])[0];
|
|
20
|
+
if (!manifiestUrlBase)
|
|
21
|
+
throw new Error(errors_1.ERRORS.URL_MANIFIEST_REQUIRED);
|
|
22
|
+
const streamManifiest = await this._getManifiest(manifiest.url);
|
|
23
|
+
let fragmentsMatched = streamManifiest.matchAll(/#EXTINF.*?\n(.*?\.ts)/g) || [];
|
|
24
|
+
let fragments = Array.from(fragmentsMatched).map((fragment) => [fragment[1], `${manifiestUrlBase}${fragment[1]}`]);
|
|
25
|
+
(0, logger_1.log)(`[VideoFragmentsFetcher] All fragments found: \n ${JSON.stringify(fragments, undefined, 2)}`);
|
|
26
|
+
return {
|
|
27
|
+
rawContent: streamManifiest,
|
|
28
|
+
fragments: fragments
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
exports.VideoFragmentsFetcher = VideoFragmentsFetcher;
|
|
33
|
+
//# sourceMappingURL=video-fragments.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"video-fragments.js","sourceRoot":"","sources":["../src/video-fragments.ts"],"names":[],"mappings":";;;;;;AAAA,8DAAgC;AAEhC,2CAAqC;AAKrC,2CAAwC;AAExC,MAAa,qBAAqB;IAAlC;QACqB,uBAAkB,GAAG,2BAA2B,CAAC;IAyBtE,CAAC;IAvBW,KAAK,CAAC,aAAa,CAAC,GAAW;QACnC,IAAA,YAAG,EAAC,mEAAmE,GAAG,EAAE,CAAC,CAAC;QAC9E,OAAO,IAAA,qBAAK,EAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;IAC1D,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,SAAmC;QAClD,MAAM,gBAAgB,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,kBAAkB,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QAEjF,IAAI,CAAC,gBAAgB;YAAE,MAAM,IAAI,KAAK,CAAC,eAAM,CAAC,sBAAsB,CAAC,CAAC;QAEtE,MAAM,eAAe,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;QAEhE,IAAI,gBAAgB,GAAG,eAAe,CAAC,QAAQ,CAAC,wBAAwB,CAAC,IAAI,EAAE,CAAC;QAEhF,IAAI,SAAS,GAAG,KAAK,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,GAAG,gBAAgB,GAAG,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAuB,CAAC;QAEzI,IAAA,YAAG,EAAC,mDAAmD,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,SAAS,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;QAElG,OAAO;YACH,UAAU,EAAE,eAAe;YAC3B,SAAS,EAAE,SAAS;SACvB,CAAC;IACN,CAAC;CACJ;AA1BD,sDA0BC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { VideoDownloadInformation } from "./interfaces/video-download-information";
|
|
2
|
+
export declare class VideoManifiest {
|
|
3
|
+
private readonly _vodID;
|
|
4
|
+
private readonly _clientID;
|
|
5
|
+
private readonly _oAuthToken;
|
|
6
|
+
private readonly GRAPHQL_ENDPOINT;
|
|
7
|
+
constructor(vodID: string | number, clientID: string | number, oAuthToken?: string | number);
|
|
8
|
+
private _getPlaybackAccessToken;
|
|
9
|
+
private _getVideoManifiest;
|
|
10
|
+
private _parseVideoManifiest;
|
|
11
|
+
getVideoResolutions(): Promise<VideoDownloadInformation[]>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.VideoManifiest = void 0;
|
|
7
|
+
const cross_fetch_1 = __importDefault(require("cross-fetch"));
|
|
8
|
+
const logger_1 = require("./utils/logger");
|
|
9
|
+
const is_json_1 = require("./utils/is-json");
|
|
10
|
+
const errors_1 = require("./enums/errors");
|
|
11
|
+
const twitch_api_1 = require("./enums/twitch-api");
|
|
12
|
+
class VideoManifiest {
|
|
13
|
+
constructor(vodID, clientID, oAuthToken = "") {
|
|
14
|
+
this.GRAPHQL_ENDPOINT = "https://gql.twitch.tv/gql";
|
|
15
|
+
this._vodID = String(vodID);
|
|
16
|
+
this._clientID = String(clientID);
|
|
17
|
+
this._oAuthToken = String(oAuthToken);
|
|
18
|
+
}
|
|
19
|
+
_getPlaybackAccessToken() {
|
|
20
|
+
const graphqlQuery = {
|
|
21
|
+
operationName: "PlaybackAccessToken_Template",
|
|
22
|
+
query: 'query PlaybackAccessToken_Template($login: String!, $isLive: Boolean!, $vodID: ID!, $isVod: Boolean!, $playerType: String!) { streamPlaybackAccessToken(channelName: $login, params: {platform: "web", playerBackend: "mediaplayer", playerType: $playerType}) @include(if: $isLive) { value signature __typename } videoPlaybackAccessToken(id: $vodID, params: {platform: "web", playerBackend: "mediaplayer", playerType: $playerType}) @include(if: $isVod) { value signature __typename }}',
|
|
23
|
+
variables: {
|
|
24
|
+
isLive: false,
|
|
25
|
+
login: "",
|
|
26
|
+
isVod: true,
|
|
27
|
+
vodID: this._vodID,
|
|
28
|
+
playerType: "site"
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
const graphqlFentchOptions = {
|
|
32
|
+
method: "POST",
|
|
33
|
+
headers: {
|
|
34
|
+
"Content-Type": "application/json",
|
|
35
|
+
Authorization: "OAuth " + this._oAuthToken,
|
|
36
|
+
"Client-ID": this._clientID
|
|
37
|
+
},
|
|
38
|
+
body: JSON.stringify(graphqlQuery)
|
|
39
|
+
};
|
|
40
|
+
(0, logger_1.log)(`[VideoManifiest] Generating access token.`);
|
|
41
|
+
return (0, cross_fetch_1.default)(this.GRAPHQL_ENDPOINT, graphqlFentchOptions).then((response) => response.json());
|
|
42
|
+
}
|
|
43
|
+
_getVideoManifiest(signature, accessToken) {
|
|
44
|
+
(0, logger_1.log)(`[VideoManifiest] Fetching video manifiest, with parameters: \n Signature: ${signature}, \n Access Token: ${JSON.stringify(accessToken, undefined, 4)}.`);
|
|
45
|
+
const MANIFIEST_ENPOINT = `https://usher.ttvnw.net/vod/${this._vodID}.m3u8?allow_source=true&player_backend=mediaplayer&playlist_include_framerate=true&reassignments_supported=true&sig=${signature}&supported_codecs=avc1&token=${accessToken}&cdm=wv&player_version=1.7.0`;
|
|
46
|
+
return (0, cross_fetch_1.default)(MANIFIEST_ENPOINT).then((response) => response.text());
|
|
47
|
+
}
|
|
48
|
+
_parseVideoManifiest(manifiest) {
|
|
49
|
+
const isJsonResponse = (0, is_json_1.parseIfIsJSON)(manifiest);
|
|
50
|
+
if ((isJsonResponse === null || isJsonResponse === void 0 ? void 0 : isJsonResponse.length) && isJsonResponse[0].error_code == twitch_api_1.TWITCH_API_ERROR.VOD_MANIFIEST_RESTRICTED) {
|
|
51
|
+
(0, logger_1.log)(`[VideoManifiest] Can´t get video maifiest, you probably don't have permission to view the video. Pass your OAuth token from twitch to VideoDownloader options to try get the video.`);
|
|
52
|
+
throw new Error(errors_1.ERRORS.MANIFIEST_IS_RESTRICRED);
|
|
53
|
+
}
|
|
54
|
+
const parsedPlaylist = [];
|
|
55
|
+
const lines = manifiest.split("\n");
|
|
56
|
+
for (let i = 4; i < lines.length; i += 3) {
|
|
57
|
+
parsedPlaylist.push({
|
|
58
|
+
quality: lines[i - 2].split('NAME="')[1].split('"')[0],
|
|
59
|
+
resolution: lines[i - 1].indexOf("RESOLUTION") != -1 ? lines[i - 1].split("RESOLUTION=")[1].split(",")[0] : null,
|
|
60
|
+
url: lines[i]
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
(0, logger_1.log)(`[VideoManifiest] Resolutions found`);
|
|
64
|
+
(0, logger_1.log)(JSON.stringify(parsedPlaylist, undefined, 2));
|
|
65
|
+
return parsedPlaylist;
|
|
66
|
+
}
|
|
67
|
+
getVideoResolutions() {
|
|
68
|
+
return this._getPlaybackAccessToken()
|
|
69
|
+
.then((response) => {
|
|
70
|
+
var _a, _b, _c, _d;
|
|
71
|
+
const signature = ((_b = (_a = response === null || response === void 0 ? void 0 : response.data) === null || _a === void 0 ? void 0 : _a.videoPlaybackAccessToken) === null || _b === void 0 ? void 0 : _b.signature) || null;
|
|
72
|
+
const token = ((_d = (_c = response === null || response === void 0 ? void 0 : response.data) === null || _c === void 0 ? void 0 : _c.videoPlaybackAccessToken) === null || _d === void 0 ? void 0 : _d.value) || null;
|
|
73
|
+
if (!signature || !token) {
|
|
74
|
+
(0, logger_1.log)(`[VideoManifiest] Can´t generante access token, with clientId ${this._clientID}.`);
|
|
75
|
+
throw new Error(errors_1.ERRORS.CANT_GENERATE_ACCESS_TOKEN);
|
|
76
|
+
}
|
|
77
|
+
return this._getVideoManifiest(signature, token);
|
|
78
|
+
})
|
|
79
|
+
.then(this._parseVideoManifiest.bind(this));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
exports.VideoManifiest = VideoManifiest;
|
|
83
|
+
//# sourceMappingURL=video-manifiest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"video-manifiest.js","sourceRoot":"","sources":["../src/video-manifiest.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "twitch-video-downloader-plus",
|
|
3
|
+
"version": "1.6.3",
|
|
4
|
+
"description": "A library to download twitch videos",
|
|
5
|
+
"main": "./build/index.js",
|
|
6
|
+
"types": "./build/index.d.ts",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/Alejandro095/twitch-video-downloader.git"
|
|
10
|
+
},
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "tsc -p ./tsconfig.json",
|
|
13
|
+
"dev:watch": "cross-env TWITCH_VIDEO_DOWNLOADER_DEBUG=true nodemon",
|
|
14
|
+
"format": "prettier --write .",
|
|
15
|
+
"prepare": "npm run build",
|
|
16
|
+
"start:example": "ts-node example/index.ts",
|
|
17
|
+
"start:hls-server": "node ./hls-server.js",
|
|
18
|
+
"start:watch": "nodemon"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"Twitch",
|
|
22
|
+
"video",
|
|
23
|
+
"downloader"
|
|
24
|
+
],
|
|
25
|
+
"author": "Alejandro Tovar",
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@types/fluent-ffmpeg": "^2.1.20",
|
|
29
|
+
"@types/tiny-async-pool": "^1.0.0",
|
|
30
|
+
"cross-env": "^7.0.3",
|
|
31
|
+
"hls-server": "^1.5.0",
|
|
32
|
+
"nodemon": "^2.0.15",
|
|
33
|
+
"prettier": "^2.5.1",
|
|
34
|
+
"ts-node": "^10.4.0",
|
|
35
|
+
"typescript": "^4.5.4"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@lifeomic/attempt": "^3.0.1",
|
|
39
|
+
"cross-fetch": "^3.1.4",
|
|
40
|
+
"fluent-ffmpeg": "^2.1.2",
|
|
41
|
+
"http-attach": "^1.0.0",
|
|
42
|
+
"tiny-async-pool": "^1.2.0",
|
|
43
|
+
"winston": "^3.3.3"
|
|
44
|
+
},
|
|
45
|
+
"bugs": {
|
|
46
|
+
"url": "https://github.com/Alejandro095/twitch-video-downloader/issues"
|
|
47
|
+
},
|
|
48
|
+
"homepage": "https://github.com/Alejandro095/twitch-video-downloader#readme",
|
|
49
|
+
"directories": {
|
|
50
|
+
"example": "example"
|
|
51
|
+
}
|
|
52
|
+
}
|