vdj 1.2.0 → 1.4.1
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/dist/default_implementations/youtube/song.d.ts +1 -1
- package/dist/default_implementations/youtube/song.js +9 -14
- package/dist/default_implementations/youtubedl/service.js +2 -2
- package/dist/default_implementations/youtubedl/song.js +8 -3
- package/dist/index.d.ts +3 -1
- package/dist/index.js +9 -5
- package/package.json +3 -2
- package/src/default_implementations/youtube/service.ts +99 -0
- package/src/default_implementations/youtube/song.ts +79 -0
- package/src/default_implementations/youtubedl/service.ts +6 -6
- package/src/default_implementations/youtubedl/song.ts +13 -3
- package/src/index.ts +5 -0
- package/test/test.js +141 -41
|
@@ -17,5 +17,5 @@ export default class YouTubeSong extends Song {
|
|
|
17
17
|
constructor(service: YouTubeService, video: Video, playlistID?: string, logger?: Logger, seek?: number);
|
|
18
18
|
getSongInfo(): Promise<SongInfo>;
|
|
19
19
|
static extractSeek(url: string): number;
|
|
20
|
-
stream(): ReadableStream;
|
|
20
|
+
stream(): ReadableStream | null;
|
|
21
21
|
}
|
|
@@ -9,7 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
9
9
|
});
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
const
|
|
12
|
+
const youtube_dl_exec_1 = require("youtube-dl-exec");
|
|
13
13
|
const core_1 = require("../../core");
|
|
14
14
|
class YouTubeSong extends core_1.Song {
|
|
15
15
|
constructor(service, video, playlistID, logger, seek = 0) {
|
|
@@ -60,19 +60,14 @@ class YouTubeSong extends core_1.Song {
|
|
|
60
60
|
return 0;
|
|
61
61
|
}
|
|
62
62
|
stream() {
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
if (typeof (this.logger) == 'function')
|
|
74
|
-
this.logger(`[${this.type}] Fetching stream url for ${this.URL}.`);
|
|
75
|
-
return ytdl(this.trackID, options);
|
|
63
|
+
const stream = youtube_dl_exec_1.exec(this.streamURL, {
|
|
64
|
+
output: '-',
|
|
65
|
+
format: 'bestaudio/best',
|
|
66
|
+
limitRate: '1M',
|
|
67
|
+
rmCacheDir: true,
|
|
68
|
+
verbose: true,
|
|
69
|
+
}, { stdio: ['ignore', 'pipe', 'ignore'] });
|
|
70
|
+
return stream.stdout;
|
|
76
71
|
}
|
|
77
72
|
}
|
|
78
73
|
exports.default = YouTubeSong;
|
|
@@ -31,13 +31,13 @@ class YoutubedlService {
|
|
|
31
31
|
}
|
|
32
32
|
canFetch(target, logger) {
|
|
33
33
|
return __awaiter(this, void 0, void 0, function* () {
|
|
34
|
-
var res = yield youtube_dl_exec_1.default(target, {
|
|
34
|
+
var res = yield youtube_dl_exec_1.default(target, { dumpSingleJson: true }, {});
|
|
35
35
|
return !!res;
|
|
36
36
|
});
|
|
37
37
|
}
|
|
38
38
|
getSongInfo(url, logger) {
|
|
39
39
|
return __awaiter(this, void 0, void 0, function* () {
|
|
40
|
-
var res = yield youtube_dl_exec_1.default(url, {
|
|
40
|
+
var res = yield youtube_dl_exec_1.default(url, { dumpSingleJson: true, noCheckCertificate: true, addMetadata: true }, {});
|
|
41
41
|
return {
|
|
42
42
|
full: true,
|
|
43
43
|
metadataType: "youtubedl",
|
|
@@ -33,9 +33,14 @@ class YoutubedlSong extends core_1.Song {
|
|
|
33
33
|
});
|
|
34
34
|
}
|
|
35
35
|
stream() {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
const stream = youtube_dl_exec_1.exec(this.streamURL, {
|
|
37
|
+
output: '-',
|
|
38
|
+
format: 'bestaudio/best',
|
|
39
|
+
limitRate: '1M',
|
|
40
|
+
rmCacheDir: true,
|
|
41
|
+
verbose: true,
|
|
42
|
+
}, { stdio: ['ignore', 'pipe', 'ignore'] });
|
|
43
|
+
return stream.stdout;
|
|
39
44
|
}
|
|
40
45
|
}
|
|
41
46
|
exports.default = YoutubedlSong;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { Playlist, PlaylistSettings, IService, Song } from './core';
|
|
2
|
+
import YouTubeService from './default_implementations/youtube/service';
|
|
3
|
+
import YouTubeSong from './default_implementations/youtube/song';
|
|
2
4
|
import YoutubedlService from './default_implementations/youtubedl/service';
|
|
3
5
|
import YoutubedlSong from './default_implementations/youtubedl/song';
|
|
4
|
-
export { Playlist, PlaylistSettings, IService, Song, YoutubedlService, YoutubedlSong, };
|
|
6
|
+
export { Playlist, PlaylistSettings, IService, Song, YouTubeService, YouTubeSong, YoutubedlService, YoutubedlSong, };
|
package/dist/index.js
CHANGED
|
@@ -3,11 +3,15 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.YoutubedlSong = exports.YoutubedlService = exports.Song = exports.Playlist = void 0;
|
|
6
|
+
exports.YoutubedlSong = exports.YoutubedlService = exports.YouTubeSong = exports.YouTubeService = exports.Song = exports.Playlist = void 0;
|
|
7
7
|
const core_1 = require("./core");
|
|
8
8
|
Object.defineProperty(exports, "Playlist", { enumerable: true, get: function () { return core_1.Playlist; } });
|
|
9
9
|
Object.defineProperty(exports, "Song", { enumerable: true, get: function () { return core_1.Song; } });
|
|
10
|
-
const service_1 = __importDefault(require("./default_implementations/
|
|
11
|
-
exports.
|
|
12
|
-
const song_1 = __importDefault(require("./default_implementations/
|
|
13
|
-
exports.
|
|
10
|
+
const service_1 = __importDefault(require("./default_implementations/youtube/service"));
|
|
11
|
+
exports.YouTubeService = service_1.default;
|
|
12
|
+
const song_1 = __importDefault(require("./default_implementations/youtube/song"));
|
|
13
|
+
exports.YouTubeSong = song_1.default;
|
|
14
|
+
const service_2 = __importDefault(require("./default_implementations/youtubedl/service"));
|
|
15
|
+
exports.YoutubedlService = service_2.default;
|
|
16
|
+
const song_2 = __importDefault(require("./default_implementations/youtubedl/song"));
|
|
17
|
+
exports.YoutubedlSong = song_2.default;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vdj",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.1",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
"@types/node": "^12.19.2",
|
|
20
20
|
"got": "^11.8.0",
|
|
21
21
|
"simple-youtube-api": "^5.2.1",
|
|
22
|
-
"youtube-dl-exec": "^1.
|
|
22
|
+
"youtube-dl-exec": "^2.1.5",
|
|
23
|
+
"ytdl-core": "^4.11.2"
|
|
23
24
|
}
|
|
24
25
|
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import API = require('simple-youtube-api');
|
|
2
|
+
import ytdl = require('ytdl-core');
|
|
3
|
+
|
|
4
|
+
import { IService, SearchType, SongInfo, Logger } from '../../core/';
|
|
5
|
+
import YouTubeSong from './song';
|
|
6
|
+
|
|
7
|
+
export default class YouTubeService implements IService {
|
|
8
|
+
public readonly api: API;
|
|
9
|
+
public canSearch: boolean = true;
|
|
10
|
+
public type: string = 'youtube';
|
|
11
|
+
|
|
12
|
+
constructor(key: string) {
|
|
13
|
+
this.api = new API(key);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
public async fetch(targets: string[], logger?: Logger): Promise<YouTubeSong[]> {
|
|
17
|
+
const fetched: YouTubeSong[] = [];
|
|
18
|
+
|
|
19
|
+
for (const target of targets) {
|
|
20
|
+
var parsed = API.util.parseURL(target);
|
|
21
|
+
if (parsed.video) {
|
|
22
|
+
typeof (logger) == 'function' && logger(`[${this.type}] Fetching video from target ${target}.`);
|
|
23
|
+
|
|
24
|
+
const video = await this.api.getVideoByID(parsed.video);
|
|
25
|
+
const seek = YouTubeSong.extractSeek(target);
|
|
26
|
+
if (video) fetched.push(new YouTubeSong(this, video, undefined, logger, seek));
|
|
27
|
+
} else if (parsed.playlist) {
|
|
28
|
+
typeof (logger) == 'function' && logger(`[${this.type}] Fetching playlist from target ${target}.`);
|
|
29
|
+
|
|
30
|
+
const playlist = await this.api.getPlaylistByID(parsed.playlist);
|
|
31
|
+
if (!playlist) continue;
|
|
32
|
+
await playlist.getVideos();
|
|
33
|
+
fetched.push(...playlist.videos.map((v) => new YouTubeSong(this, v, parsed.playlist, logger)));
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return fetched;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
public async search(queries: string[], searchType: SearchType, logger?: Logger): Promise<YouTubeSong[]> {
|
|
41
|
+
const fetched: YouTubeSong[] = [];
|
|
42
|
+
|
|
43
|
+
if (!this.canSearch) return fetched;
|
|
44
|
+
for (const query of queries) {
|
|
45
|
+
if (searchType === 'playlist') {
|
|
46
|
+
typeof (logger) == 'function' && logger(`[${this.type}] Searching for a playlist with the input ${query}.`);
|
|
47
|
+
|
|
48
|
+
const results = await this.api.searchPlaylists(query, 1);
|
|
49
|
+
if (results.length) {
|
|
50
|
+
const list = results[0];
|
|
51
|
+
const videos = await list.getVideos();
|
|
52
|
+
fetched.push(...videos.map((v) => new YouTubeSong(this, v, list.id, logger)));
|
|
53
|
+
}
|
|
54
|
+
} else {
|
|
55
|
+
typeof (logger) == 'function' && logger(`[${this.type}] Searching for a song with the input ${query}.`);
|
|
56
|
+
|
|
57
|
+
const results = await this.api.searchVideos(query, 1);
|
|
58
|
+
if (results.length) fetched.push(new YouTubeSong(this, results[0], undefined, logger));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return fetched;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
public async canFetch(target: string, logger?: Logger): Promise<boolean> {
|
|
66
|
+
const parsed = API.util.parseURL(target);
|
|
67
|
+
return parsed.video != undefined || parsed.playlist != undefined;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
public async getSongInfo(url: string, logger?: Logger): Promise<SongInfo> {
|
|
71
|
+
try {
|
|
72
|
+
const result: ytdl.videoInfo = await ytdl.getInfo(url);
|
|
73
|
+
return {
|
|
74
|
+
full: true,
|
|
75
|
+
metadataType: "youtube",
|
|
76
|
+
imgURL: ((result.player_response.videoDetails as any).thumbnail as ytdl.VideoDetails).thumbnails[0].url,
|
|
77
|
+
title: result.videoDetails.title,
|
|
78
|
+
duration: Number(result.videoDetails.lengthSeconds),
|
|
79
|
+
url: result.videoDetails.video_url,
|
|
80
|
+
artist: [result.videoDetails.author.name],
|
|
81
|
+
date: new Date(result.videoDetails.publishDate),
|
|
82
|
+
custom: result
|
|
83
|
+
};
|
|
84
|
+
} catch (e) {
|
|
85
|
+
typeof (logger) == 'function' && logger(`Failed to get song info for url '${url}', error ${e}`);
|
|
86
|
+
return {
|
|
87
|
+
full: true,
|
|
88
|
+
metadataType: "youtube",
|
|
89
|
+
imgURL: "",
|
|
90
|
+
title: url,
|
|
91
|
+
duration: 0,
|
|
92
|
+
url: url,
|
|
93
|
+
artist: [],
|
|
94
|
+
date: new Date(),
|
|
95
|
+
custom: {}
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { Readable as ReadableStream } from 'stream';
|
|
2
|
+
import { Video } from 'simple-youtube-api';
|
|
3
|
+
import { exec as ytdlexec } from 'youtube-dl-exec';
|
|
4
|
+
|
|
5
|
+
import { Song, SongInfo, Logger } from '../../core';
|
|
6
|
+
import YouTubeService from './service';
|
|
7
|
+
|
|
8
|
+
export default class YouTubeSong extends Song {
|
|
9
|
+
public readonly type: string = 'youtube';
|
|
10
|
+
public readonly title: string;
|
|
11
|
+
public readonly playlistID?: string;
|
|
12
|
+
public readonly trackID: string;
|
|
13
|
+
public readonly streamURL: string;
|
|
14
|
+
public readonly URL: string;
|
|
15
|
+
public readonly live: boolean;
|
|
16
|
+
public info: SongInfo;
|
|
17
|
+
public seek: number;
|
|
18
|
+
public logger?: Logger;
|
|
19
|
+
|
|
20
|
+
constructor(service: YouTubeService, video: Video, playlistID?: string, logger?: Logger, seek: number = 0) {
|
|
21
|
+
super(service);
|
|
22
|
+
this.title = video.title;
|
|
23
|
+
this.trackID = video.id;
|
|
24
|
+
this.streamURL = video.url;
|
|
25
|
+
this.URL = video.url;
|
|
26
|
+
this.playlistID = playlistID;
|
|
27
|
+
this.live = video.raw.snippet.liveBroadcastContent == "live";
|
|
28
|
+
this.seek = seek;
|
|
29
|
+
this.info = {
|
|
30
|
+
full: false,
|
|
31
|
+
metadataType: this.type,
|
|
32
|
+
title: this.title,
|
|
33
|
+
url: this.URL,
|
|
34
|
+
duration: video.durationSeconds
|
|
35
|
+
}
|
|
36
|
+
this.logger = logger;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
public async getSongInfo(): Promise<SongInfo> {
|
|
40
|
+
if (this.info.full) return this.info;
|
|
41
|
+
this.info = await this.service.getSongInfo(this.URL);
|
|
42
|
+
return this.info;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
public static extractSeek(url: string): number {
|
|
46
|
+
var lookFor = ["&t=", "?t="];
|
|
47
|
+
for (var i in lookFor) {
|
|
48
|
+
var key = lookFor[i];
|
|
49
|
+
var start = url.indexOf(key);
|
|
50
|
+
if (start === -1) continue;
|
|
51
|
+
start += key.length;
|
|
52
|
+
var time = "";
|
|
53
|
+
var char = "";
|
|
54
|
+
for (var j = start; j < url.length; j++) {
|
|
55
|
+
char = url[j];
|
|
56
|
+
if (isNaN(Number(char)))
|
|
57
|
+
break;
|
|
58
|
+
time += char;
|
|
59
|
+
}
|
|
60
|
+
return Number(time);
|
|
61
|
+
}
|
|
62
|
+
return 0;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
public stream(): ReadableStream | null {
|
|
66
|
+
const stream = ytdlexec(
|
|
67
|
+
this.streamURL,
|
|
68
|
+
{
|
|
69
|
+
output: '-',
|
|
70
|
+
format: 'bestaudio/best',
|
|
71
|
+
limitRate: '1M',
|
|
72
|
+
rmCacheDir: true,
|
|
73
|
+
verbose: true,
|
|
74
|
+
},
|
|
75
|
+
{ stdio: ['ignore', 'pipe', 'ignore'] }
|
|
76
|
+
);
|
|
77
|
+
return stream.stdout;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -15,12 +15,12 @@ export default class YoutubedlService implements IService {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
public async canFetch(target: string, logger?: Logger): Promise<boolean> {
|
|
18
|
-
var res = await youtubedl(target, {
|
|
18
|
+
var res = await youtubedl(target, { dumpSingleJson: true }, {});
|
|
19
19
|
return !!res;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
public async getSongInfo(url: string, logger?: Logger): Promise<SongInfo> {
|
|
23
|
-
var res = await youtubedl(url, {
|
|
23
|
+
var res = await youtubedl(url, { dumpSingleJson: true, noCheckCertificate: true, addMetadata: true }, {});
|
|
24
24
|
return {
|
|
25
25
|
full: true,
|
|
26
26
|
metadataType: "youtubedl",
|
|
@@ -38,10 +38,10 @@ export default class YoutubedlService implements IService {
|
|
|
38
38
|
if (!dateString) {
|
|
39
39
|
return undefined;
|
|
40
40
|
}
|
|
41
|
-
const year = dateString.slice(0,4);
|
|
42
|
-
const month = dateString.slice(4,6);
|
|
43
|
-
const day = dateString.slice(6,8);
|
|
44
|
-
|
|
41
|
+
const year = dateString.slice(0, 4);
|
|
42
|
+
const month = dateString.slice(4, 6);
|
|
43
|
+
const day = dateString.slice(6, 8);
|
|
44
|
+
|
|
45
45
|
return new Date(`${year} ${month} ${day}`)
|
|
46
46
|
}
|
|
47
47
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Readable as ReadableStream } from 'stream';
|
|
2
2
|
import { Song, SongInfo, Logger } from '../../core';
|
|
3
|
-
import {
|
|
3
|
+
import { exec as ytdlexec } from 'youtube-dl-exec';
|
|
4
4
|
import YoutubedlService from './service';
|
|
5
5
|
|
|
6
6
|
export default class YoutubedlSong extends Song {
|
|
@@ -30,7 +30,17 @@ export default class YoutubedlSong extends Song {
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
public stream(): ReadableStream | null {
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
const stream = ytdlexec(
|
|
34
|
+
this.streamURL,
|
|
35
|
+
{
|
|
36
|
+
output: '-',
|
|
37
|
+
format: 'bestaudio/best',
|
|
38
|
+
limitRate: '1M',
|
|
39
|
+
rmCacheDir: true,
|
|
40
|
+
verbose: true,
|
|
41
|
+
},
|
|
42
|
+
{ stdio: ['ignore', 'pipe', 'ignore'] }
|
|
43
|
+
);
|
|
44
|
+
return stream.stdout;
|
|
35
45
|
}
|
|
36
46
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { Playlist, PlaylistSettings, IService, Song } from './core';
|
|
2
|
+
import YouTubeService from './default_implementations/youtube/service'
|
|
3
|
+
import YouTubeSong from './default_implementations/youtube/song'
|
|
2
4
|
import YoutubedlService from './default_implementations/youtubedl/service';
|
|
3
5
|
import YoutubedlSong from './default_implementations/youtubedl/song';
|
|
4
6
|
|
|
@@ -9,6 +11,9 @@ export {
|
|
|
9
11
|
IService,
|
|
10
12
|
Song,
|
|
11
13
|
|
|
14
|
+
YouTubeService,
|
|
15
|
+
YouTubeSong,
|
|
16
|
+
|
|
12
17
|
YoutubedlService,
|
|
13
18
|
YoutubedlSong,
|
|
14
19
|
};
|
package/test/test.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
require(
|
|
2
|
-
const assert = require(
|
|
3
|
-
const vdj = require(
|
|
1
|
+
require("dotenv").config({ path: "./test/.env" });
|
|
2
|
+
const assert = require("chai").assert;
|
|
3
|
+
const vdj = require("../dist");
|
|
4
4
|
|
|
5
5
|
var playlist,
|
|
6
|
+
youtubeService = new vdj.YouTubeService(process.env.GOOGLE_API_KEY),
|
|
6
7
|
youtubedlService = new vdj.YoutubedlService();
|
|
7
8
|
|
|
8
9
|
function sortOfDeepEqual(actual, expected) {
|
|
@@ -14,63 +15,162 @@ function sortOfDeepEqual(actual, expected) {
|
|
|
14
15
|
return assert.deepEqual(strippedActual, expected);
|
|
15
16
|
}
|
|
16
17
|
|
|
17
|
-
describe(
|
|
18
|
+
describe("Playlist", function () {
|
|
18
19
|
before(function () {
|
|
19
20
|
playlist = new vdj.Playlist({
|
|
20
|
-
services: []
|
|
21
|
+
services: [],
|
|
21
22
|
});
|
|
22
23
|
});
|
|
23
24
|
|
|
24
|
-
describe(
|
|
25
|
-
it(
|
|
26
|
-
const input = [
|
|
25
|
+
describe("#add", function () {
|
|
26
|
+
it("should return the not found targets", async function () {
|
|
27
|
+
const input = ["first", "second"];
|
|
27
28
|
const result = await playlist.add(input);
|
|
28
29
|
assert.deepEqual(result.notFound, input);
|
|
29
30
|
});
|
|
30
31
|
});
|
|
31
32
|
|
|
32
|
-
describe(
|
|
33
|
+
describe("Youtube", function () {
|
|
33
34
|
before(function () {
|
|
34
35
|
playlist = new vdj.Playlist({
|
|
35
|
-
services: [
|
|
36
|
+
services: [youtubeService],
|
|
36
37
|
});
|
|
37
38
|
});
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
it(
|
|
41
|
-
const input = [
|
|
39
|
+
|
|
40
|
+
describe("#add", function () {
|
|
41
|
+
it("should return a YouTubeSong from working youtube video link", async function () {
|
|
42
|
+
const input = ["https://www.youtube.com/watch?v=FtsefkeIE7k"];
|
|
42
43
|
const expected = {
|
|
43
|
-
|
|
44
|
+
added: [
|
|
44
45
|
{
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
"
|
|
46
|
+
URL: input[0],
|
|
47
|
+
info: {
|
|
48
|
+
duration: 282,
|
|
49
|
+
full: false,
|
|
50
|
+
metadataType: "youtube",
|
|
51
|
+
title: "楽園ベイベー - RIP SLYME(Cover) / KMNZ",
|
|
52
|
+
url: input[0],
|
|
50
53
|
},
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
live: false,
|
|
55
|
+
logger: undefined,
|
|
56
|
+
loop: false,
|
|
57
|
+
playlistID: undefined,
|
|
58
|
+
seek: 0,
|
|
59
|
+
service: playlist.services[0],
|
|
60
|
+
streamURL: input[0],
|
|
61
|
+
title: "楽園ベイベー - RIP SLYME(Cover) / KMNZ",
|
|
62
|
+
trackID: "FtsefkeIE7k",
|
|
63
|
+
type: "youtube",
|
|
64
|
+
},
|
|
57
65
|
],
|
|
58
|
-
|
|
66
|
+
notFound: [],
|
|
59
67
|
};
|
|
60
68
|
const result = await playlist.add(input);
|
|
61
69
|
assert.deepEqual(result, expected);
|
|
62
70
|
});
|
|
71
|
+
it("should return multiple YouTubeSongs from multiple working youtube video links", async function () {
|
|
72
|
+
const input = [
|
|
73
|
+
"https://www.youtube.com/watch?v=fmrA-gxJxgQ",
|
|
74
|
+
"https://www.youtube.com/watch?v=DDjcqu9uvUE",
|
|
75
|
+
"https://www.youtube.com/watch?v=lBnAY4VH9T4",
|
|
76
|
+
];
|
|
77
|
+
const expected = input.length;
|
|
78
|
+
const result = (await playlist.add(input)).added.length;
|
|
79
|
+
assert.equal(result, expected);
|
|
80
|
+
});
|
|
81
|
+
it("should return a YouTubeSong from working shortened youtube video link", async function () {
|
|
82
|
+
const input = ["https://youtu.be/LoK17z6xDwI"];
|
|
83
|
+
const expected = "https://www.youtube.com/watch?v=LoK17z6xDwI";
|
|
84
|
+
const result = (await playlist.add(input)).added[0].URL;
|
|
85
|
+
assert.deepEqual(result, expected);
|
|
86
|
+
});
|
|
87
|
+
it("should return a YouTubeSong from live youtube video link", async function () {
|
|
88
|
+
const input = ["https://www.youtube.com/watch?v=jfKfPfyJRdk"];
|
|
89
|
+
const result = (await playlist.add(input)).added[0].live;
|
|
90
|
+
assert.isTrue(result);
|
|
91
|
+
});
|
|
92
|
+
it("should return a YouTubeSong with seek from working youtube video link with timestamp parameter", async function () {
|
|
93
|
+
const input = ["https://www.youtube.com/watch?v=tMKrECxEpq8&t=64"];
|
|
94
|
+
const expected = 64;
|
|
95
|
+
const result = (await playlist.add(input)).added[0].seek;
|
|
96
|
+
assert.equal(result, expected);
|
|
97
|
+
});
|
|
98
|
+
it("should return multiple YouTubeSongs from youtube playlist link", async function () {
|
|
99
|
+
const input = ["https://www.youtube.com/playlist?list=PLD80CC3FEBE323161"];
|
|
100
|
+
const expected = 21;
|
|
101
|
+
const result = (await playlist.add(input)).added.length;
|
|
102
|
+
assert.equal(result, expected);
|
|
103
|
+
});
|
|
104
|
+
it("should return one YouTubeSong from youtube video link with playlist parameter", async function () {
|
|
105
|
+
const input = ["https://www.youtube.com/watch?v=wSCEC0lYTzk&list=PLFJU3fqbXA2e1_wAqOn7IoRB02nCfsAVh&index=67"];
|
|
106
|
+
const expected = 1;
|
|
107
|
+
const result = (await playlist.add(input)).added.length;
|
|
108
|
+
assert.equal(result, expected);
|
|
109
|
+
});
|
|
110
|
+
it("should return YouTubeSongs from search queries", async function () {
|
|
111
|
+
const input = ["globglogabgalab", "yooooooooo"];
|
|
112
|
+
const expected = 2;
|
|
113
|
+
const result = (
|
|
114
|
+
await playlist.add(input, {
|
|
115
|
+
playlistAddType: "searches",
|
|
116
|
+
})
|
|
117
|
+
).added.length;
|
|
118
|
+
assert.equal(result, expected);
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
describe("#getSongInfo", function () {
|
|
122
|
+
it("should return a lot more information from working youtube video link", async function () {
|
|
123
|
+
const input = "https://www.youtube.com/watch?v=FtsefkeIE7k";
|
|
124
|
+
const expected = {
|
|
125
|
+
full: true,
|
|
126
|
+
metadataType: "youtube",
|
|
127
|
+
url: input,
|
|
128
|
+
title: "楽園ベイベー - RIP SLYME(Cover) / KMNZ",
|
|
129
|
+
duration: 282,
|
|
130
|
+
artist: ["KMNZ LITA"],
|
|
131
|
+
date: new Date(1566950400000),
|
|
132
|
+
};
|
|
133
|
+
const result = await youtubeService.getSongInfo(input, console.log);
|
|
134
|
+
sortOfDeepEqual(result, expected);
|
|
135
|
+
});
|
|
63
136
|
});
|
|
64
137
|
});
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
138
|
+
describe("Youtubedl", function () {
|
|
139
|
+
before(function () {
|
|
140
|
+
playlist = new vdj.Playlist({
|
|
141
|
+
services: [youtubedlService],
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
describe("#add", function () {
|
|
71
145
|
this.timeout(5000);
|
|
72
|
-
it(
|
|
73
|
-
const input =
|
|
146
|
+
it("should return a YoutubedlSong from working ogg link", async function () {
|
|
147
|
+
const input = ["https://upload.wikimedia.org/wikipedia/commons/d/de/Lorem_ipsum.ogg"];
|
|
148
|
+
const expected = {
|
|
149
|
+
added: [
|
|
150
|
+
{
|
|
151
|
+
URL: input[0],
|
|
152
|
+
info: {
|
|
153
|
+
full: false,
|
|
154
|
+
metadataType: "youtubedl",
|
|
155
|
+
url: input[0],
|
|
156
|
+
},
|
|
157
|
+
logger: undefined,
|
|
158
|
+
loop: false,
|
|
159
|
+
service: playlist.services[0],
|
|
160
|
+
streamURL: input[0],
|
|
161
|
+
type: "youtubedl",
|
|
162
|
+
},
|
|
163
|
+
],
|
|
164
|
+
notFound: [],
|
|
165
|
+
};
|
|
166
|
+
const result = await playlist.add(input);
|
|
167
|
+
assert.deepEqual(result, expected);
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
describe("#getSongInfo", function () {
|
|
171
|
+
this.timeout(5000);
|
|
172
|
+
it("should return a lot more information from working youtube link", async function () {
|
|
173
|
+
const input = "https://www.youtube.com/watch?v=232fF5ECnGo";
|
|
74
174
|
const expected = {
|
|
75
175
|
full: true,
|
|
76
176
|
metadataType: "youtubedl",
|
|
@@ -78,16 +178,16 @@ describe('Service', function () {
|
|
|
78
178
|
title: "Rap God in the style of @Linkin Park (Feat. @Jonathan Young)",
|
|
79
179
|
duration: 371,
|
|
80
180
|
artist: [],
|
|
81
|
-
date: new Date(1660773600000)
|
|
181
|
+
date: new Date(1660773600000),
|
|
82
182
|
};
|
|
83
183
|
const result = await youtubedlService.getSongInfo(input, console.log);
|
|
84
184
|
sortOfDeepEqual(result, expected);
|
|
85
185
|
});
|
|
86
186
|
});
|
|
87
|
-
describe(
|
|
187
|
+
describe("#getSongInfo", function () {
|
|
88
188
|
this.timeout(5000);
|
|
89
|
-
it(
|
|
90
|
-
const input =
|
|
189
|
+
it("should return a lot more information from working soundcloud link", async function () {
|
|
190
|
+
const input = "https://soundcloud.com/bionicelcor/wakusei-abnormal";
|
|
91
191
|
const expected = {
|
|
92
192
|
full: true,
|
|
93
193
|
metadataType: "youtubedl",
|
|
@@ -95,7 +195,7 @@ describe('Service', function () {
|
|
|
95
195
|
title: "ふこうぶつ -- Wakusei Abnormal",
|
|
96
196
|
duration: 164.638,
|
|
97
197
|
artist: [],
|
|
98
|
-
date: new Date(1512946800000)
|
|
198
|
+
date: new Date(1512946800000),
|
|
99
199
|
};
|
|
100
200
|
const result = await youtubedlService.getSongInfo(input, console.log);
|
|
101
201
|
sortOfDeepEqual(result, expected);
|
|
@@ -121,4 +221,4 @@ describe('Service', function () {
|
|
|
121
221
|
});
|
|
122
222
|
*/
|
|
123
223
|
});
|
|
124
|
-
});
|
|
224
|
+
});
|