From 13b5c85112892d7b545176444f8895e95450d363 Mon Sep 17 00:00:00 2001 From: Jelle Glebbeek Date: Mon, 2 Aug 2021 03:22:22 +0200 Subject: [PATCH 01/14] feat: add bitrate selection to audio only quality selector --- modules/download/DownloadQuery.js | 9 +++++++-- renderer/renderer.html | 7 +++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/modules/download/DownloadQuery.js b/modules/download/DownloadQuery.js index 98d3a8e7..4df03522 100644 --- a/modules/download/DownloadQuery.js +++ b/modules/download/DownloadQuery.js @@ -21,10 +21,15 @@ class DownloadQuery extends Query { let args = []; let output = path.join(this.environment.settings.downloadPath, Utils.resolvePlaylistPlaceholders(this.environment.settings.nameFormat, this.playlistMeta)); if(this.video.audioOnly) { - let numeralAudioQuality = (this.video.audioQuality === "best") ? "0" : "9"; + let audioQuality = this.video.audioQuality; + if(audioQuality === "best") { + audioQuality = "0"; + } else if(audioQuality === "worst") { + audioQuality = "9"; + } const audioOutputFormat = this.environment.settings.audioOutputFormat; args = [ - '--extract-audio', '--audio-quality', numeralAudioQuality, + '--extract-audio', '--audio-quality', audioQuality, '--ffmpeg-location', this.environment.paths.ffmpeg, '--no-mtime', '-o', output, diff --git a/renderer/renderer.html b/renderer/renderer.html index 11969533..c03d4ce1 100644 --- a/renderer/renderer.html +++ b/renderer/renderer.html @@ -108,6 +108,13 @@ - - +
+ + +
+
+ + +
diff --git a/renderer/renderer.js b/renderer/renderer.js index 31147dd7..0925e527 100644 --- a/renderer/renderer.js +++ b/renderer/renderer.js @@ -164,6 +164,7 @@ async function init() { updateBinary: $('#updateBinary').prop('checked'), updateApplication: $('#updateApplication').prop('checked'), autoFillClipboard: $('#autoFillClipboard').prop('checked'), + noPlaylist: $('#noPlaylist').prop('checked'), globalShortcut: $('#globalShortcut').prop('checked'), outputFormat: $('#outputFormat').val(), audioOutputFormat: $('#audioOutputFormat').val(), @@ -212,6 +213,7 @@ async function init() { $('#enableEncoding').prop('checked', settings.enableEncoding); $('#taskList').prop('checked', settings.taskList); $('#autoFillClipboard').prop('checked', settings.autoFillClipboard); + $('#noPlaylist').prop('checked', settings.noPlaylist); $('#globalShortcut').prop('checked', settings.globalShortcut); $('#ratelimitSetting').val(settings.rateLimit); $('#proxySetting').val(settings.proxy); From 7301730c6f0b72b00e367d5aabdb3fcc49341999 Mon Sep 17 00:00:00 2001 From: Jelle Glebbeek Date: Mon, 2 Aug 2021 17:05:01 +0200 Subject: [PATCH 06/14] test: update tests to reflect new setting --- tests/Settings.test.js | 4 ++-- tests/test-settings.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/Settings.test.js b/tests/Settings.test.js index aed4b0b7..1fcd9b9d 100644 --- a/tests/Settings.test.js +++ b/tests/Settings.test.js @@ -2,8 +2,8 @@ const fs = require('fs').promises; const os = require("os"); const Settings = require('../modules/persistence/Settings'); const env = {version: "2.0.0-test1", app: {getPath: jest.fn().mockReturnValue("test/path")}}; -const defaultSettingsInstance = new Settings({settings: "tests/test-settings.json"}, env, "none", "none", "test/path", "", "", true, true, true, false, false, true, "%(title).200s-(%(height)sp%(fps).0d).%(ext)s", "%(title).200s-(%(height)sp%(fps).0d).%(ext)s", "click", "49", 8, true, true, "C:\\Users\\user\\cookies.txt", false, true, false, false, true, "dark"); -const defaultSettings = "{\"outputFormat\":\"none\",\"audioOutputFormat\":\"none\",\"downloadPath\":\"test/path\",\"proxy\":\"\",\"rateLimit\":\"\",\"autoFillClipboard\":true,\"globalShortcut\":true,\"spoofUserAgent\":true,\"validateCertificate\":false,\"enableEncoding\":false,\"taskList\":true,\"nameFormat\":\"%(title).200s-(%(height)sp%(fps).0d).%(ext)s\",\"nameFormatMode\":\"%(title).200s-(%(height)sp%(fps).0d).%(ext)s\",\"sizeMode\":\"click\",\"splitMode\":\"49\",\"maxConcurrent\":8,\"defaultConcurrent\":8,\"updateBinary\":true,\"updateApplication\":true,\"statSend\":false,\"downloadMetadata\":true,\"downloadThumbnail\":false,\"keepUnmerged\":false,\"calculateTotalSize\":true,\"theme\":\"dark\",\"version\":\"2.0.0-test1\"}" +const defaultSettingsInstance = new Settings({settings: "tests/test-settings.json"}, env, "none", "none", "test/path", "", "", true, false, true, true, false, false, true, "%(title).200s-(%(height)sp%(fps).0d).%(ext)s", "%(title).200s-(%(height)sp%(fps).0d).%(ext)s", "click", "49", 8, true, true, "C:\\Users\\user\\cookies.txt", false, true, false, false, true, "dark"); +const defaultSettings = "{\"outputFormat\":\"none\",\"audioOutputFormat\":\"none\",\"downloadPath\":\"test/path\",\"proxy\":\"\",\"rateLimit\":\"\",\"autoFillClipboard\":true,\"noPlaylist\":false,\"globalShortcut\":true,\"spoofUserAgent\":true,\"validateCertificate\":false,\"enableEncoding\":false,\"taskList\":true,\"nameFormat\":\"%(title).200s-(%(height)sp%(fps).0d).%(ext)s\",\"nameFormatMode\":\"%(title).200s-(%(height)sp%(fps).0d).%(ext)s\",\"sizeMode\":\"click\",\"splitMode\":\"49\",\"maxConcurrent\":8,\"defaultConcurrent\":8,\"updateBinary\":true,\"updateApplication\":true,\"statSend\":false,\"downloadMetadata\":true,\"downloadThumbnail\":false,\"keepUnmerged\":false,\"calculateTotalSize\":true,\"theme\":\"dark\",\"version\":\"2.0.0-test1\"}" describe('Load settings from file', () => { beforeEach(() => { diff --git a/tests/test-settings.json b/tests/test-settings.json index a9fdfc91..314ca03c 100644 --- a/tests/test-settings.json +++ b/tests/test-settings.json @@ -1 +1 @@ -{"outputFormat":"none","audioOutputFormat":"none","downloadPath": "test/path","proxy": "","rateLimit": "","autoFillClipboard":true,"globalShortcut":true,"spoofUserAgent":true,"validateCertificate": false,"enableEncoding": false,"taskList":true,"nameFormat":"%(title).200s-(%(height)sp%(fps).0d).%(ext)s","nameFormatMode":"%(title).200s-(%(height)sp%(fps).0d).%(ext)s","sizeMode":"click","splitMode":"49","maxConcurrent":8,"defaultConcurrent":8,"updateBinary":true,"updateApplication":true,"cookiePath":"C:\\Users\\user\\cookies.txt","statSend":false,"downloadMetadata":true,"downloadThumbnail":false,"keepUnmerged":false,"calculateTotalSize":true,"theme": "dark","version":"2.0.0-test1"} +{"outputFormat":"none","audioOutputFormat":"none","downloadPath": "test/path","proxy": "","rateLimit": "","autoFillClipboard":true,"noPlaylist": false,"globalShortcut":true,"spoofUserAgent":true,"validateCertificate": false,"enableEncoding": false,"taskList":true,"nameFormat":"%(title).200s-(%(height)sp%(fps).0d).%(ext)s","nameFormatMode":"%(title).200s-(%(height)sp%(fps).0d).%(ext)s","sizeMode":"click","splitMode":"49","maxConcurrent":8,"defaultConcurrent":8,"updateBinary":true,"updateApplication":true,"cookiePath":"C:\\Users\\user\\cookies.txt","statSend":false,"downloadMetadata":true,"downloadThumbnail":false,"keepUnmerged":false,"calculateTotalSize":true,"theme": "dark","version":"2.0.0-test1"} From ab82ec5ecdf028278ed7370387458c91b9e3eaa2 Mon Sep 17 00:00:00 2001 From: Jelle Glebbeek Date: Mon, 2 Aug 2021 17:38:24 +0200 Subject: [PATCH 07/14] feat: save global download type selection (#141) --- main.js | 3 ++ modules/persistence/Settings.js | 6 ++- renderer/renderer.js | 73 +++++++++++++++++++-------------- tests/Settings.test.js | 4 +- tests/test-settings.json | 2 +- 5 files changed, 54 insertions(+), 34 deletions(-) diff --git a/main.js b/main.js index e0415862..95d62bd2 100644 --- a/main.js +++ b/main.js @@ -61,6 +61,9 @@ function startCriticalHandlers(env) { taskList.load(); } + //Send the saved download type to the renderer + win.webContents.send("videoAction", {action: "setDownloadType", type: env.settings.downloadType}); + env.errorHandler = new ErrorHandler(win, queryManager, env); if(appStarting) { diff --git a/modules/persistence/Settings.js b/modules/persistence/Settings.js index 77d79d8b..16877501 100644 --- a/modules/persistence/Settings.js +++ b/modules/persistence/Settings.js @@ -7,7 +7,7 @@ class Settings { paths, env, outputFormat, audioOutputFormat, downloadPath, proxy, rateLimit, autoFillClipboard, noPlaylist, globalShortcut, spoofUserAgent, validateCertificate, enableEncoding, taskList, nameFormat, nameFormatMode, - sizeMode, splitMode, maxConcurrent, updateBinary, updateApplication, cookiePath, + sizeMode, splitMode, maxConcurrent, updateBinary, downloadType, updateApplication, cookiePath, statSend, downloadMetadata, downloadThumbnail, keepUnmerged, calculateTotalSize, theme ) { this.paths = paths; @@ -34,6 +34,7 @@ class Settings { this.splitMode = splitMode == null? "49" : splitMode; this.maxConcurrent = (maxConcurrent == null || maxConcurrent <= 0) ? Math.round(os.cpus().length / 2) : maxConcurrent; //Max concurrent is standard half of the system's available cores this.updateBinary = updateBinary == null ? true : updateBinary; + this.downloadType = downloadType == null ? "video" : downloadType; this.updateApplication = updateApplication == null ? true : updateApplication; this.cookiePath = cookiePath; this.statSend = statSend == null ? false : statSend; @@ -66,6 +67,7 @@ class Settings { data.splitMode, data.maxConcurrent, data.updateBinary, + data.downloadType, data.updateApplication, data.cookiePath, data.statSend, @@ -109,6 +111,7 @@ class Settings { this.env.changeMaxConcurrent(settings.maxConcurrent); } this.updateBinary = settings.updateBinary; + this.downloadType = settings.downloadType; this.updateApplication = settings.updateApplication; this.theme = settings.theme; this.save(); @@ -139,6 +142,7 @@ class Settings { maxConcurrent: this.maxConcurrent, defaultConcurrent: Math.round(os.cpus().length / 2), updateBinary: this.updateBinary, + downloadType: this.downloadType, updateApplication: this.updateApplication, cookiePath: this.cookiePath, statSend: this.statSend, diff --git a/renderer/renderer.js b/renderer/renderer.js index 0925e527..2c8ef14d 100644 --- a/renderer/renderer.js +++ b/renderer/renderer.js @@ -124,7 +124,12 @@ async function init() { updateSize($(card).prop('id'), false); }); - $('#download-quality, #download-type').on('change', () => updateAllVideoSettings()); + $('#download-quality').on('change', () => updateAllVideoSettings()); + + $('#download-type').on('change', () => { + updateAllVideoSettings(); + sendSettings(); + }); $('#infoModal .img-overlay, #infoModal .info-img').on('click', () => { window.main.invoke("videoAction", {action: "downloadThumb", url: $('#infoModal .info-img').attr("src")}); @@ -160,35 +165,7 @@ async function init() { $('#settingsModal .apply').on('click', () => { $('#settingsModal').modal("hide"); - let settings = { - updateBinary: $('#updateBinary').prop('checked'), - updateApplication: $('#updateApplication').prop('checked'), - autoFillClipboard: $('#autoFillClipboard').prop('checked'), - noPlaylist: $('#noPlaylist').prop('checked'), - globalShortcut: $('#globalShortcut').prop('checked'), - outputFormat: $('#outputFormat').val(), - audioOutputFormat: $('#audioOutputFormat').val(), - proxy: $('#proxySetting').val(), - spoofUserAgent: $('#spoofUserAgent').prop('checked'), - validateCertificate: $('#validateCertificate').prop('checked'), - enableEncoding: $('#enableEncoding').prop('checked'), - taskList: $('#taskList').prop('checked'), - nameFormatMode: $('#nameFormat').val(), - nameFormat: $('#nameFormatCustom').val(), - downloadMetadata: $('#downloadMetadata').prop('checked'), - downloadThumbnail: $('#downloadThumbnail').prop('checked'), - keepUnmerged: $('#keepUnmerged').prop('checked'), - calculateTotalSize: $('#calculateTotalSize').prop('checked'), - sizeMode: $('#sizeSetting').val(), - splitMode: $('#splitMode').val(), - rateLimit: $('#ratelimitSetting').val(), - maxConcurrent: parseInt($('#maxConcurrent').val()), - theme: $('#theme').val() - } - window.settings = settings; - window.main.invoke("settingsAction", {action: "save", settings}); - updateEncodingDropdown(settings.enableEncoding); - toggleWhiteMode(settings.theme); + sendSettings(); }); $('#maxConcurrent').on('input', () => { @@ -410,6 +387,9 @@ async function init() { case "setUnified": setUnifiedPlaylist(arg); break; + case "setDownloadType": + $('#download-type').val(arg.type).change(); + break; } }); @@ -431,6 +411,39 @@ async function init() { }); } +function sendSettings() { + let settings = { + updateBinary: $('#updateBinary').prop('checked'), + updateApplication: $('#updateApplication').prop('checked'), + autoFillClipboard: $('#autoFillClipboard').prop('checked'), + noPlaylist: $('#noPlaylist').prop('checked'), + globalShortcut: $('#globalShortcut').prop('checked'), + outputFormat: $('#outputFormat').val(), + audioOutputFormat: $('#audioOutputFormat').val(), + proxy: $('#proxySetting').val(), + spoofUserAgent: $('#spoofUserAgent').prop('checked'), + validateCertificate: $('#validateCertificate').prop('checked'), + enableEncoding: $('#enableEncoding').prop('checked'), + taskList: $('#taskList').prop('checked'), + nameFormatMode: $('#nameFormat').val(), + nameFormat: $('#nameFormatCustom').val(), + downloadMetadata: $('#downloadMetadata').prop('checked'), + downloadThumbnail: $('#downloadThumbnail').prop('checked'), + keepUnmerged: $('#keepUnmerged').prop('checked'), + calculateTotalSize: $('#calculateTotalSize').prop('checked'), + sizeMode: $('#sizeSetting').val(), + splitMode: $('#splitMode').val(), + rateLimit: $('#ratelimitSetting').val(), + maxConcurrent: parseInt($('#maxConcurrent').val()), + downloadType: $('#download-type').val(), + theme: $('#theme').val() + } + window.settings = settings; + window.main.invoke("settingsAction", {action: "save", settings}); + updateEncodingDropdown(settings.enableEncoding); + toggleWhiteMode(settings.theme); +} + function verifyURL(value) { if (linkCopied && (value == null || value.length === 0)) { parseURL($('#add-url').prop('placeholder')); diff --git a/tests/Settings.test.js b/tests/Settings.test.js index 1fcd9b9d..07514a92 100644 --- a/tests/Settings.test.js +++ b/tests/Settings.test.js @@ -2,8 +2,8 @@ const fs = require('fs').promises; const os = require("os"); const Settings = require('../modules/persistence/Settings'); const env = {version: "2.0.0-test1", app: {getPath: jest.fn().mockReturnValue("test/path")}}; -const defaultSettingsInstance = new Settings({settings: "tests/test-settings.json"}, env, "none", "none", "test/path", "", "", true, false, true, true, false, false, true, "%(title).200s-(%(height)sp%(fps).0d).%(ext)s", "%(title).200s-(%(height)sp%(fps).0d).%(ext)s", "click", "49", 8, true, true, "C:\\Users\\user\\cookies.txt", false, true, false, false, true, "dark"); -const defaultSettings = "{\"outputFormat\":\"none\",\"audioOutputFormat\":\"none\",\"downloadPath\":\"test/path\",\"proxy\":\"\",\"rateLimit\":\"\",\"autoFillClipboard\":true,\"noPlaylist\":false,\"globalShortcut\":true,\"spoofUserAgent\":true,\"validateCertificate\":false,\"enableEncoding\":false,\"taskList\":true,\"nameFormat\":\"%(title).200s-(%(height)sp%(fps).0d).%(ext)s\",\"nameFormatMode\":\"%(title).200s-(%(height)sp%(fps).0d).%(ext)s\",\"sizeMode\":\"click\",\"splitMode\":\"49\",\"maxConcurrent\":8,\"defaultConcurrent\":8,\"updateBinary\":true,\"updateApplication\":true,\"statSend\":false,\"downloadMetadata\":true,\"downloadThumbnail\":false,\"keepUnmerged\":false,\"calculateTotalSize\":true,\"theme\":\"dark\",\"version\":\"2.0.0-test1\"}" +const defaultSettingsInstance = new Settings({settings: "tests/test-settings.json"}, env, "none", "none", "test/path", "", "", true, false, true, true, false, false, true, "%(title).200s-(%(height)sp%(fps).0d).%(ext)s", "%(title).200s-(%(height)sp%(fps).0d).%(ext)s", "click", "49", 8, true, "video", true, "C:\\Users\\user\\cookies.txt", false, true, false, false, true, "dark"); +const defaultSettings = "{\"outputFormat\":\"none\",\"audioOutputFormat\":\"none\",\"downloadPath\":\"test/path\",\"proxy\":\"\",\"rateLimit\":\"\",\"autoFillClipboard\":true,\"noPlaylist\":false,\"globalShortcut\":true,\"spoofUserAgent\":true,\"validateCertificate\":false,\"enableEncoding\":false,\"taskList\":true,\"nameFormat\":\"%(title).200s-(%(height)sp%(fps).0d).%(ext)s\",\"nameFormatMode\":\"%(title).200s-(%(height)sp%(fps).0d).%(ext)s\",\"sizeMode\":\"click\",\"splitMode\":\"49\",\"maxConcurrent\":8,\"defaultConcurrent\":8,\"updateBinary\":true,\"downloadType\":\"video\",\"updateApplication\":true,\"statSend\":false,\"downloadMetadata\":true,\"downloadThumbnail\":false,\"keepUnmerged\":false,\"calculateTotalSize\":true,\"theme\":\"dark\",\"version\":\"2.0.0-test1\"}" describe('Load settings from file', () => { beforeEach(() => { diff --git a/tests/test-settings.json b/tests/test-settings.json index 314ca03c..09c39c33 100644 --- a/tests/test-settings.json +++ b/tests/test-settings.json @@ -1 +1 @@ -{"outputFormat":"none","audioOutputFormat":"none","downloadPath": "test/path","proxy": "","rateLimit": "","autoFillClipboard":true,"noPlaylist": false,"globalShortcut":true,"spoofUserAgent":true,"validateCertificate": false,"enableEncoding": false,"taskList":true,"nameFormat":"%(title).200s-(%(height)sp%(fps).0d).%(ext)s","nameFormatMode":"%(title).200s-(%(height)sp%(fps).0d).%(ext)s","sizeMode":"click","splitMode":"49","maxConcurrent":8,"defaultConcurrent":8,"updateBinary":true,"updateApplication":true,"cookiePath":"C:\\Users\\user\\cookies.txt","statSend":false,"downloadMetadata":true,"downloadThumbnail":false,"keepUnmerged":false,"calculateTotalSize":true,"theme": "dark","version":"2.0.0-test1"} +{"outputFormat":"none","audioOutputFormat":"none","downloadPath": "test/path","proxy": "","rateLimit": "","autoFillClipboard":true,"noPlaylist": false,"globalShortcut":true,"spoofUserAgent":true,"validateCertificate": false,"enableEncoding": false,"taskList":true,"nameFormat":"%(title).200s-(%(height)sp%(fps).0d).%(ext)s","nameFormatMode":"%(title).200s-(%(height)sp%(fps).0d).%(ext)s","sizeMode":"click","splitMode":"49","maxConcurrent":8,"defaultConcurrent":8,"updateBinary":true,"downloadType":"video","updateApplication":true,"cookiePath":"C:\\Users\\user\\cookies.txt","statSend":false,"downloadMetadata":true,"downloadThumbnail":false,"keepUnmerged":false,"calculateTotalSize":true,"theme": "dark","version":"2.0.0-test1"} From e883cda7b3048ba4b85f77f3f44eb3e4883e8a2b Mon Sep 17 00:00:00 2001 From: Jelle Glebbeek Date: Mon, 2 Aug 2021 17:52:24 +0200 Subject: [PATCH 08/14] fix: all settings being set to null or false on startup --- renderer/renderer.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/renderer/renderer.js b/renderer/renderer.js index 2c8ef14d..14625c48 100644 --- a/renderer/renderer.js +++ b/renderer/renderer.js @@ -388,7 +388,7 @@ async function init() { setUnifiedPlaylist(arg); break; case "setDownloadType": - $('#download-type').val(arg.type).change(); + $('#download-type').val(arg.type); break; } }); @@ -411,7 +411,8 @@ async function init() { }); } -function sendSettings() { +async function sendSettings() { + await settingExists(); let settings = { updateBinary: $('#updateBinary').prop('checked'), updateApplication: $('#updateApplication').prop('checked'), From 76048d70fdcba3bdd0da2fd5da7fcbd5b52a80ee Mon Sep 17 00:00:00 2001 From: Jelle Glebbeek Date: Tue, 3 Aug 2021 13:23:32 +0200 Subject: [PATCH 09/14] chore: add url not found error definition --- modules/exceptions/ErrorHandler.js | 2 +- modules/exceptions/errorDefinitions.json | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/modules/exceptions/ErrorHandler.js b/modules/exceptions/ErrorHandler.js index 826eadd3..7ffef608 100644 --- a/modules/exceptions/ErrorHandler.js +++ b/modules/exceptions/ErrorHandler.js @@ -19,7 +19,7 @@ class ErrorHandler { console.error("An error has occurred but no error message was given.") return false; } - if(stderr.includes("WARNING")) { + if(stderr.trim().startsWith("WARNING:")) { console.warn(stderr); return; } diff --git a/modules/exceptions/errorDefinitions.json b/modules/exceptions/errorDefinitions.json index 79973b22..c81584a0 100644 --- a/modules/exceptions/errorDefinitions.json +++ b/modules/exceptions/errorDefinitions.json @@ -45,6 +45,11 @@ "Unsupported URL" ] }, + { + "code": "URL not found", + "description": "This is an incorrect or non-existing URL.", + "trigger": "[Errno 11001]" + }, { "code": "Private or non-existent playlist", "description": "This playlist does not exist or is private.", @@ -195,7 +200,7 @@ }, { "code": "Binaries missing/corrupted", - "description": "Please restart the app, or disable antivirus.", + "description": "Please restart the app and disable antivirus.", "trigger": [ "Command failed with ENOENT: resources\\app.asar.unpacked\\binaries\\youtube-dl.exe", "Command failed with ENOENT: binaries/youtube-dl.exe", From fee3ab3a98894f6fa5190ec90334a11acfaee9b7 Mon Sep 17 00:00:00 2001 From: Jelle Glebbeek Date: Tue, 3 Aug 2021 13:25:59 +0200 Subject: [PATCH 10/14] feat: add log viewer (#132) --- main.js | 10 +++++ modules/Environment.js | 2 + modules/QueryManager.js | 1 + modules/download/DownloadQuery.js | 10 +++++ modules/info/InfoQueryList.js | 3 +- modules/persistence/Logger.js | 63 +++++++++++++++++++++++++++ modules/types/Query.js | 1 + preload.js | 4 +- renderer/renderer.css | 21 +++++++++ renderer/renderer.html | 21 +++++++++ renderer/renderer.js | 71 ++++++++++++++++++++++++++++--- 11 files changed, 199 insertions(+), 8 deletions(-) create mode 100644 modules/persistence/Logger.js diff --git a/main.js b/main.js index 95d62bd2..8b27dc08 100644 --- a/main.js +++ b/main.js @@ -74,6 +74,16 @@ function startCriticalHandlers(env) { taskList.restore() }); + //Send the log for a specific download to renderer + ipcMain.handle("getLog", (event, identifier) => { + return env.logger.get(identifier); + }); + + //Save the log when renderer asks main + ipcMain.handle("saveLog", (event, identifier) => { + return env.logger.save(identifier); + }) + //Catch all console.log calls, print them to stdout and send them to the renderer devtools. console.log = (arg) => { process.stdout.write(arg + "\n"); diff --git a/modules/Environment.js b/modules/Environment.js index a51dfd80..1d36fc96 100644 --- a/modules/Environment.js +++ b/modules/Environment.js @@ -2,6 +2,7 @@ const Bottleneck = require("bottleneck"); const Filepaths = require("./Filepaths"); const Settings = require("./persistence/Settings"); const DetectPython = require("./DetectPython"); +const Logger = require("./persistence/Logger"); const fs = require("fs").promises; class Environment { @@ -15,6 +16,7 @@ class Environment { this.mainAudioQuality = "best"; this.mainDownloadSubs = false; this.doneAction = "Do nothing"; + this.logger = new Logger(this); this.paths = new Filepaths(app, this); this.downloadLimiter = new Bottleneck({ trackDoneStatus: true, diff --git a/modules/QueryManager.js b/modules/QueryManager.js index 46ce662e..9130b434 100644 --- a/modules/QueryManager.js +++ b/modules/QueryManager.js @@ -321,6 +321,7 @@ class QueryManager { this.managedVideos = this.managedVideos.filter(item => item.identifier !== video.identifier); this.playlistMetadata = this.playlistMetadata.filter(item => item.video_url !== video.url && item.playlist_url !== video.url); this.window.webContents.send("videoAction", { action: "remove", identifier: video.identifier }) + this.environment.logger.clear(video.identifier); } onError(identifier) { diff --git a/modules/download/DownloadQuery.js b/modules/download/DownloadQuery.js index 4df03522..5ebc6fee 100644 --- a/modules/download/DownloadQuery.js +++ b/modules/download/DownloadQuery.js @@ -106,6 +106,16 @@ class DownloadQuery extends Query { const perLine = liveData.split("\n"); for(const line of perLine) { this.video.setFilename(line); + if(line.lastIndexOf("[download]") !== line.indexOf("[download]")) { + const splitLines = line.split("["); + for(const splitLine of splitLines) { + if(splitLine.trim() !== "") { + this.environment.logger.log(this.video.identifier, "[" + splitLine.trim()); + } + } + } else { + this.environment.logger.log(this.video.identifier, line); + } } if (!liveData.includes("[download]")) return; if (!initialReset) { diff --git a/modules/info/InfoQueryList.js b/modules/info/InfoQueryList.js index e8ed63cb..c3a48f4a 100644 --- a/modules/info/InfoQueryList.js +++ b/modules/info/InfoQueryList.js @@ -13,7 +13,7 @@ class InfoQueryList { } async start() { - let result = await new Promise(((resolve) => { + return await new Promise(((resolve) => { let totalMetadata = []; let playlistUrls = Utils.extractPlaylistUrls(this.query); for (const videoData of playlistUrls[1]) { @@ -43,7 +43,6 @@ class InfoQueryList { }); } })); - return result; } createVideo(data, url) { diff --git a/modules/persistence/Logger.js b/modules/persistence/Logger.js new file mode 100644 index 00000000..b34c5cd1 --- /dev/null +++ b/modules/persistence/Logger.js @@ -0,0 +1,63 @@ +const {dialog} = require("electron"); +const path = require("path"); +const fs = require("fs"); + +class Logger { + constructor(environment) { + this.environment = environment; + this.logs = {}; + } + + log(identifier, line) { + if(line == null || line === "") return; + let trimmedLine; + if(line === "done") { + trimmedLine = "Download finished"; + } else if(line === "killed") { + trimmedLine = "Download stopped"; + } else { + trimmedLine = line.replace(/[\n\r]/g, ""); + } + if(identifier in this.logs) { + this.logs[identifier].push(trimmedLine); + } else { + this.logs[identifier] = [trimmedLine]; + } + } + + get(identifier) { + return this.logs[identifier]; + } + + clear(identifier) { + delete this.logs[identifier]; + } + + async save(identifier) { + const logLines = this.logs[identifier]; + let log = ""; + for(const line of logLines) { + log += line + "\n"; + } + const date = new Date().toLocaleString() + .replace(", ", "-") + .replaceAll("/", "-") + .replaceAll(":", "-") + .replace(/:.. /," "); + let result = await dialog.showSaveDialog(this.environment.win, { + defaultPath: path.join(this.environment.settings.downloadPath, "ytdl-log-" + date), + buttonLabel: "Save metadata", + filters: [ + { name: "txt", extensions: ["txt"] }, + { name: "All Files", extensions: ["*"] }, + ], + properties: ["createDirectory"] + }); + if(!result.canceled) { + fs.promises.writeFile(result.filePath, log).then(() => console.log("Download log saved.")); + } + } + +} + +module.exports = Logger; diff --git a/modules/types/Query.js b/modules/types/Query.js index 7ebdf10b..8f7bf1d4 100644 --- a/modules/types/Query.js +++ b/modules/types/Query.js @@ -104,6 +104,7 @@ class Query { resolve("done"); }); this.process.stderr.on("data", (data) => { + cb(data.toString()); if(this.environment.errorHandler.checkError(data.toString(), this.identifier)) { cb("killed"); resolve("killed"); diff --git a/preload.js b/preload.js index 817974d4..13ce1ba9 100644 --- a/preload.js +++ b/preload.js @@ -40,7 +40,9 @@ contextBridge.exposeInMainWorld( "getDoneActions", "setDoneAction", "getSubtitles", - "getSelectedSubtitles" + "getSelectedSubtitles", + "getLog", + "saveLog" ]; if (validChannels.includes(channel)) { return await ipcRenderer.invoke(channel, data); diff --git a/renderer/renderer.css b/renderer/renderer.css index 13e253a7..6078437a 100644 --- a/renderer/renderer.css +++ b/renderer/renderer.css @@ -500,6 +500,27 @@ option.audio { } } +/* LOG MODAL */ +#logModal { + padding: 0 5rem; +} + +#logModal .modal-dialog { + max-width: 1200px; + margin: 1.75rem auto !important; +} + +#logModal .log { + overflow-y: auto; + max-height: 550px; + border-color: var(--tertiary-bg-color); + background-color: var(--secondary-bg-color); +} + +#logModal .log p { + margin-bottom: 0.5rem; +} + /* INFO MODAL */ #info-description { resize: none; diff --git a/renderer/renderer.html b/renderer/renderer.html index 807dad83..0877c655 100644 --- a/renderer/renderer.html +++ b/renderer/renderer.html @@ -222,6 +222,27 @@
+ +