Skip to content

Commit

Permalink
fix testing and begin refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
mp3butcher committed Nov 18, 2024
1 parent 99d3c65 commit ccb0c66
Show file tree
Hide file tree
Showing 12 changed files with 66 additions and 43 deletions.
3 changes: 1 addition & 2 deletions main.js
Original file line number Diff line number Diff line change
Expand Up @@ -360,8 +360,7 @@ function scan(msg) {
if (res[0] == "#") { //HLS?
console.log(res);
toscandeeply = true;
}
else if (res.startsWith('<MPD')) { //DASH?
} else if (res.startsWith('<MPD')) { //DASH?
console.log(res);
toscandeeply = true;
}
Expand Down
5 changes: 3 additions & 2 deletions modules/QueryManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,11 @@ class QueryManager {
async manage(url, headers) {
let metadataVideo = new Video(url, headers, "metadata", this.environment);
this.addVideo(metadataVideo);
const initialQuery = await new InfoQuery(url, headers,metadataVideo.identifier, this.environment).connect();
const initialQuery = await new InfoQuery(metadataVideo, headers,metadataVideo.identifier, this.environment).connect();
if(metadataVideo.error) return;
if(Utils.isYouTubeChannel(url)) {
const actualQuery = await new InfoQuery(initialQuery.entries[0].url, metadataVideo.headers, metadataVideo.identifier, this.environment).connect();
let metadataVideo2 = new Video(initialQuery.entries[0].url, metadataVideo.headers, metadataVideo.identifier, this.environment);
const actualQuery = await new InfoQuery(metadataVideo2, metadataVideo.headers, metadataVideo.identifier, this.environment).connect();
if(metadataVideo.error) return;
this.removeVideo(metadataVideo);
if(actualQuery.entries == null || actualQuery.entries.length === 0) this.managePlaylist(initialQuery, url);
Expand Down
3 changes: 2 additions & 1 deletion modules/Utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,14 +60,15 @@ class Utils {
}
for(const entry of infoQueryResult.entries) {
let url;
entry.headers=[];
if (entry.url == null) url = entry.webpage_url;
else url = entry.url;
if(entry.formats != null && entry.formats.length > 0) {
entry.url = url;
alreadyDone.push(entry);
continue;
}
urls.push({'url':url,'headers':entry.headers});
urls.push({'url':url,'headers':[]});
}
return [urls, alreadyDone]
}
Expand Down
4 changes: 2 additions & 2 deletions modules/download/DownloadQuery.js
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ class DownloadQuery extends Query {
let result = null;
const regexliverec = /size=\s*(\d+\w)iB\s*time=(\d+:\d+:\d+.\d+)\s*bitrate=\s*(\d+.\d+\w)bits\/s\s*speed=\d+.\d+x/;
try {
result = await this.environment.downloadLimiter.schedule(() => this.start(this.url, args, (liveData) => {
result = await this.environment.downloadLimiter.schedule(() => this.start(this.video, args, (liveData) => {
this.environment.logger.log(this.video.identifier, liveData);
this.video.setFilename(liveData);
if(this.video.is_live) {
Expand All @@ -183,7 +183,7 @@ class DownloadQuery extends Query {
try{
const livrec = liveData.match(regexliverec);
if (typeof (`${livrec[1]}`) == "undefined") return;
this.progressBar.updateDownload('livestream', `${livrec[2]}`, `${livrec[3]}bits\/s`, this.video.audioOnly || this.video.downloadingAudio);
this.progressBar.updateDownload('livestream', `${livrec[2]}`, `${livrec[3]}bits/s`, this.video.audioOnly || this.video.downloadingAudio);
} catch(e) {
return;
}
Expand Down
6 changes: 3 additions & 3 deletions modules/info/InfoQuery.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
const Query = require("../types/Query");

class InfoQuery extends Query {
constructor(url, headers, identifier, environment) {
constructor(video, headers, identifier, environment) {
super(environment, identifier);
this.url = url;
this.video = video;
this.headers = headers;
this.environment = environment;
this.identifier = identifier;
Expand All @@ -17,7 +17,7 @@ class InfoQuery extends Query {
args.push(this.environment.settings.fileAccessRetries);
}
this.headers.forEach((h) => args.push("--add-headers", h.k + ": " + h.v));
let data = await this.environment.metadataLimiter.schedule(() => this.start(this.url, args));
let data = await this.environment.metadataLimiter.schedule(() => this.start(this.video, args));
return JSON.parse(data);
} catch (e) {
this.environment.errorHandler.checkError(e.stderr, this.identifier)
Expand Down
8 changes: 5 additions & 3 deletions modules/info/InfoQueryList.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@ const Video = require('../types/Video');
const Utils = require("../Utils");

class InfoQueryList {
constructor(query, environment, progressBar) {
constructor(query, headers, environment, progressBar) {
this.query = query;
this.environment = environment;
this.progressBar = progressBar;
this.urls = null;
this.length = null;
this.done = 0;
this.headers = headers;
}

async start() {
Expand All @@ -29,10 +30,11 @@ class InfoQueryList {
resolve(null);
}
for (const url of this.urls) {
let task = new InfoQuery(url,this.progressBar.video.headers, this.progressBar.video.identifier, this.environment);
let metadataVideo = new Video(url.url, this.headers, "", this.environment);
let task = new InfoQuery(metadataVideo, this.headers, "", this.environment);
task.connect().then((data) => {
if (data.formats != null) {
let video = this.createVideo(data, url, data.headers);
let video = this.createVideo(data, url.url, data.headers);
totalMetadata.push(video);
}
this.done++;
Expand Down
2 changes: 1 addition & 1 deletion modules/size/SizeQuery.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class SizeQuery extends Query {
formatArgument = `bestvideo+${this.format}audio/bestvideo+bestaudio/best`;
}

let output = await this.environment.metadataLimiter.schedule(() => this.start(this.video.url, ["-J", "--flat-playlist", "-f", formatArgument]));
let output = await this.environment.metadataLimiter.schedule(() => this.start(this.video, ["-J", "--flat-playlist", "-f", formatArgument]));
let data = JSON.parse(output);
let totalSize = 0;
if(data.requested_formats != null) {
Expand Down
19 changes: 13 additions & 6 deletions modules/types/Query.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,20 @@ class Query {
this.identifier = identifier
this.process = null;
this.stopped = false;
this.video = null;
}

stop() {
this.stopped = true;
if(this.process != null) {
if (this.progressBar.video.is_live)
process.kill(this.process.pid, 'SIGINT');///Only way to stop ffmpeg lives
else this.process.kill();
if (this.video.is_live) process.kill(this.process.pid, 'SIGINT');///Only way to stop ffmpeg lives
else this.process.cancel();
}
}

async start(url, args, cb) {
async start(video, args, cb) {
this.video = video;
let url = video.url;
if(this.stopped) return "killed";
args.push("--no-cache-dir");
args.push("--ignore-config");
Expand Down Expand Up @@ -82,7 +84,9 @@ class Query {
//Return data while the query is running (live)
//Return "done" when the query has finished
return await new Promise((resolve) => {
this.process = child_process.spawn(command, args);
try {
if(video.is_live) this.process = child_process.spawn(command, args);
else this.process = execa(command, args);
this.process.stdout.setEncoding('utf8');
this.process.stdout.on('data', (data) => {
const lines = data
Expand All @@ -108,7 +112,10 @@ class Query {
resolve("killed");
}
console.error(data.toString())
})
});
} catch(e) {
console.log(e);
}
});
}
}
Expand Down
2 changes: 1 addition & 1 deletion renderer/renderer.css
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ select {

.bi-card-text-strike {
display: block;
background: url(img/card-text-strike.svg);
background: url(img/card-text-strike.svg) center no-repeat;
height: 1em;
width: 1em;
margin-top: 0.2em ;
Expand Down
5 changes: 4 additions & 1 deletion tests/InfoQuery.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const InfoQuery = require("../modules/info/InfoQuery");
const Video = require("../modules/types/Video");

describe('Connect the InfoQuery', () => {
beforeEach(() => {
Expand Down Expand Up @@ -44,6 +45,8 @@ function instanceBuilder() {
},
settings: {}
};
return [env, new InfoQuery("http://url.link", [], "test__id", env)];
let metadataVideo = new Video("http://url.link", [], "test__id", env);

return [env, new InfoQuery(metadataVideo, [], "test__id", env)];
}

46 changes: 28 additions & 18 deletions tests/Query.test.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
const UserAgent = require('user-agents');
const Query = require("../modules/types/Query");
const Video = require("../modules/types/Video");
const execa = require('execa');
const { PassThrough } = require('stream');

jest.mock('user-agents');
jest.mock('execa');

//jest.mock('child_process');
const env = {
metadataLimiter: {
schedule: jest.fn()
},
errorHandler: {
checkError: jest.fn()
},
settings: {}
};
beforeEach(() => {
jest.clearAllMocks();
jest.doMock('execa', () => {
Expand All @@ -26,7 +36,7 @@ describe('ytdl Query', () => {
UserAgent.prototype.toString = jest.fn().mockReturnValue("agent");
const errorHandlerMock = jest.fn();
const instance = instanceBuilder("spoof", null, errorHandlerMock, "python");
return instance.start("https://url.link", [], null).then(() => {
return instance.start(new Video("https://url.link", [], "test__id",env), [], null).then(() => {
expect(UserAgent.prototype.toString).toBeCalledTimes(1);
expect(execa.mock.calls[0][1]).toContain("--user-agent");
expect(execa.mock.calls[0][1]).toContain("agent");
Expand All @@ -36,7 +46,7 @@ describe('ytdl Query', () => {
UserAgent.prototype.toString = jest.fn().mockReturnValue("agent");
const errorHandlerMock = jest.fn();
const instance = instanceBuilder("empty", null, errorHandlerMock, "python");
return instance.start("https://url.link", [], null).then(() => {
return instance.start(new Video("https://url.link", [], "test__id",env), [], null).then(() => {
expect(UserAgent.prototype.toString).toBeCalledTimes(0);
expect(execa.mock.calls[0][1]).toContain("--user-agent");
expect(execa.mock.calls[0][1]).toContain("''");
Expand All @@ -45,45 +55,45 @@ describe('ytdl Query', () => {
it('adds the proxy when one is set', () => {
const errorHandlerMock = jest.fn();
const instance = instanceBuilder("default", "a/path/to/cookies.txt", errorHandlerMock, "python", "https://iama.proxy");
return instance.start("https://url.link", [], null).then(() => {
return instance.start(new Video("https://url.link", [], "test__id",env), [], null).then(() => {
expect(execa.mock.calls[0][1]).toContain("--proxy");
expect(execa.mock.calls[0][1]).toContain("https://iama.proxy");
});
})
it('does not add a proxy when none are set', () => {
const errorHandlerMock = jest.fn();
const instance = instanceBuilder("default", "a/path/to/cookies.txt", errorHandlerMock, "python", "");
return instance.start("https://url.link", [], null).then(() => {
return instance.start(new Video("https://url.link", [], "test__id",env), [], null).then(() => {
expect(execa.mock.calls[0][1]).not.toContain("--proxy");
});
})
it('adds the cookies argument when specified in settings', () => {
const errorHandlerMock = jest.fn();
const instance = instanceBuilder("default", "a/path/to/cookies.txt", errorHandlerMock, "python");
return instance.start("https://url.link", [], null).then(() => {
return instance.start(new Video("https://url.link", [], "test__id",env), [], null).then(() => {
expect(execa.mock.calls[0][1]).toContain("--cookies");
expect(execa.mock.calls[0][1]).toContain("a/path/to/cookies.txt");
});
});
it('uses the detected python command', () => {
const errorHandlerMock = jest.fn();
const instance = instanceBuilder("default", null, errorHandlerMock, "python3");
return instance.start("https://url.link", [], null).then(() => {
return instance.start(new Video("https://url.link", [], "test__id",env), [], null).then(() => {
expect(execa.mock.calls[0][0]).toEqual("python3");
expect(execa.mock.calls[0][1][0]).toEqual("a/path/to/ytdl");
});
});
it('adds the url as final argument', () => {
const errorHandlerMock = jest.fn();
const instance = instanceBuilder("default", null, errorHandlerMock, "python");
return instance.start("https://url.link", [], null).then(() => {
return instance.start(new Video("https://url.link", [], "test__id",env), [], null).then(() => {
expect(execa.mock.calls[0][1][execa.mock.calls[0][1].length - 1]).toContain("https://url.link");
});
})
it('adds the no-cache-dir as argument', () => {
const errorHandlerMock = jest.fn();
const instance = instanceBuilder("default", null, errorHandlerMock, "python");
return instance.start("https://url.link", [], null).then(() => {
return instance.start(new Video("https://url.link", [], "test__id",env), [], null).then(() => {
expect(execa.mock.calls[0][1]).toContain("--no-cache-dir");
});
});
Expand All @@ -96,10 +106,10 @@ describe('Query with live callback', () => {
const errorHandlerMock = jest.fn();
const callbackMock = jest.fn();
const instance = instanceBuilder("default", null, errorHandlerMock, "python");
const result = instance.start("https://url.link", [], callbackMock);
const result = instance.start(new Video("https://url.link", [], "",env), [], callbackMock);
setTimeout(() => {
instance.stop();
}, 100);
}, 10);
await expect(result).resolves.toEqual("killed");
expect(callbackMock).toBeCalledWith("killed");
});
Expand All @@ -110,7 +120,7 @@ describe('Query with live callback', () => {
const errorHandlerMock = jest.fn();
const callbackMock = jest.fn();
const instance = instanceBuilder("default", null, errorHandlerMock, "python");
const result = instance.start("https://url.link", [], callbackMock);
const result = instance.start(new Video("https://url.link", [], "test__id",env), [], callbackMock);
setTimeout(() => {
stderr.emit("data", "test-error");
}, 100);
Expand All @@ -125,7 +135,7 @@ describe('Query with live callback', () => {
execa.mockReturnValue(mock)
const callbackMock = jest.fn();
const instance = instanceBuilder("default", null, jest.fn(), "python");
const result = instance.start("https://url.link", [], callbackMock);
const result = instance.start(new Video("https://url.link", [], "test__id",env), [], callbackMock);
setTimeout(() => {
stdout.emit("close");
}, 100);
Expand All @@ -137,7 +147,7 @@ describe('Query with live callback', () => {
execa.mockReturnValue(mock);
const callbackMock = jest.fn();
const instance = instanceBuilder("default", null, jest.fn(), "python");
const result = instance.start("https://url.link", [], callbackMock);
const result = instance.start(new Video("https://url.link", [], "test__id",env), [], callbackMock);
setTimeout(() => {
stdout.emit("data", "test-data");
stdout.emit("data", "more-test-data");
Expand All @@ -154,21 +164,21 @@ describe('Query without callback', () => {
execa.mockResolvedValue({stdout: "fake-data"});
const errorHandlerMock = jest.fn();
const instance = instanceBuilder("default", null, errorHandlerMock, "python");
const result = instance.start("https://url.link", [], null)
const result = instance.start(new Video("https://url.link", [], "test__id",env), [], null)
await expect(result).resolves.toEqual("fake-data");
});
it('Returns a stringified empty object on error', async () => {
execa.mockResolvedValue(null);
const errorHandlerMock = jest.fn();
const instance = instanceBuilder("default", null, errorHandlerMock, "python");
const result = instance.start("https://url.link", [], null)
const result = instance.start(new Video("https://url.link", [], "test__id",env), [], null)
await expect(result).resolves.toEqual("{}");
});
it('Checks the error on error', () => {
execa.mockResolvedValue(null);
const errorHandlerMock = jest.fn();
const instance = instanceBuilder("default", null, errorHandlerMock, "python");
return instance.start("https://url.link", [], null).then(() => {
return instance.start(new Video("https://url.link", [], "test__id",env), [], null).then(() => {
expect(errorHandlerMock).toBeCalled();
});
});
Expand All @@ -183,4 +193,4 @@ function execaMockBuilder(killed) {

function instanceBuilder(userAgent, cookiePath, errorHandlerMock, pythonCommand, proxy) {
return new Query({pythonCommand: pythonCommand, errorHandler: {checkError: errorHandlerMock, raiseUnhandledError: errorHandlerMock}, paths: {ytdl: "a/path/to/ytdl"}, settings: {cookiePath: cookiePath, userAgent, proxy: proxy}}, "test__id");
}
}
6 changes: 3 additions & 3 deletions tests/Utils.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,13 +98,13 @@ describe('extractPlaylistUrls', () => {
expect(console.error).toBeCalledTimes(1);
});
it('returns playlist urls',() => {
expect(Utils.extractPlaylistUrls({entries: [{url: "1"}, {url: "2"}]})).toContainEqual([{headers: undefined, url: "1"}, {headers: undefined, url: "2"}])
expect(Utils.extractPlaylistUrls({entries: [{url: "1"}, {url: "2"}]})).toContainEqual([{headers: [], url: "1"}, {headers: [], url: "2"}])
});
it('returns already done (queried) urls', () => {
expect(Utils.extractPlaylistUrls({entries: [{url: "1", formats: ["format"]}, {url: "2"}]})).toContainEqual([{url: "1", headers: undefined, formats: ["format"]}]);
expect(Utils.extractPlaylistUrls({entries: [{url: "1", formats: ["format"]}, {url: "2"}]})).toContainEqual([{url: "1", headers: [], formats: ["format"]}]);
});
it('uses the webpage_url when url is null', () => {
expect(Utils.extractPlaylistUrls({entries: [{webpage_url: "url"}]})).toContainEqual([{url:"url", "headers": undefined}]);
expect(Utils.extractPlaylistUrls({entries: [{webpage_url: "url"}]})).toContainEqual([{url:"url", "headers": []}]);
});
});

Expand Down

0 comments on commit ccb0c66

Please sign in to comment.