Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v2.3.0 - Open Video Downloader #146

Merged
merged 14 commits into from
Aug 3, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions main.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -71,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");
Expand Down Expand Up @@ -205,6 +218,7 @@ function createWindow(env) {
}

app.on('ready', async () => {
app.setAppUserModelId("com.jelleglebbeek.youtube-dl-gui");
env = new Environment(app, analytics);
await env.initialize();
createWindow(env);
Expand Down
2 changes: 2 additions & 0 deletions modules/Environment.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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,
Expand Down
77 changes: 57 additions & 20 deletions modules/Filepaths.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,10 @@ class Filepaths {
break;
}
case "win32portable":
this.persistentPath = path.join(this.app.getPath('appData'), "youtube-dl-gui-portable");
this.persistentPath = path.join(process.env.PORTABLE_EXECUTABLE_DIR , "open-video-downloader");
this.unpackedPrefix = path.join(path.dirname(this.appPath), "app.asar.unpacked");
this.packedPrefix = this.appPath;
await this.createAppDataFolder();
await this.createPortableFolder();
this.ffmpeg = path.join(this.persistentPath, "ffmpeg.exe");
this.ytdl = path.join(this.persistentPath, "youtube-dl.exe");
this.icon = path.join(this.packedPrefix, "renderer/img/icon.png");
Expand Down Expand Up @@ -97,7 +97,7 @@ class Filepaths {
}

detectPlatform() {
if(this.appPath.includes("\\AppData\\Local\\Temp\\")) return "win32portable";
if(process.env.PORTABLE_EXECUTABLE_DIR != null) return "win32portable";
else if(this.appPath.includes("WindowsApps")) return "win32app"
else return process.platform;
}
Expand All @@ -112,30 +112,67 @@ class Filepaths {
}

async createAppDataFolder() {
await new Promise((resolve) => {
mkdirp(this.persistentPath).then(made => {
if (made != null) {
fs.copyFileSync(path.join(this.unpackedPrefix, "binaries/youtube-dl.exe"), path.join(this.persistentPath, "youtube-dl.exe"));
fs.copyFileSync(path.join(this.unpackedPrefix, "binaries/ffmpeg.exe"), path.join(this.persistentPath, "ffmpeg.exe"));
fs.copyFileSync(path.join(this.unpackedPrefix, "binaries/AtomicParsley.exe"), path.join(this.persistentPath, "AtomicParsley.exe"));
fs.copyFileSync(path.join(this.unpackedPrefix, "binaries/ytdlVersion"), path.join(this.persistentPath, "ytdlVersion"));
}
resolve();
})
})
const from = path.join(this.unpackedPrefix + "binaries");
const toCopy = ["youtube-dl.exe", "ffmpeg.exe", "ytdlVersion", "AtomicParsley.exe"];
await this.copyFiles(from, this.persistentPath, toCopy);
}

async createPortableFolder() {
try {
await fs.promises.access(process.env.PORTABLE_EXECUTABLE_DIR, fs.constants.W_OK);
if(await this.migrateExistingAppDataFolder()) return;
const from = path.join(this.unpackedPrefix + "binaries");
const toCopy = ["youtube-dl.exe", "ffmpeg.exe", "ytdlVersion", "AtomicParsley.exe"];
await this.copyFiles(from, this.persistentPath, toCopy);
} catch (e) {
setTimeout(() => console.error(e), 5000);
this.persistentPath = path.join(this.app.getPath("appData"), "open-video-downloader");
await this.createAppDataFolder();
}
}

async migrateExistingAppDataFolder() {
const from = path.join(this.app.getPath("appData"), "youtube-dl-gui-portable");
try {
await fs.promises.access(from, fs.constants.W_OK);
const toCopy = ["youtube-dl.exe", "ffmpeg.exe", "ytdlVersion", "userSettings", "taskList"];
await this.copyFiles(from, this.persistentPath, toCopy);
try {
await fs.promises.rmdir(from, {recursive: true});
} catch (e) {
console.error(e);
}
return true;
} catch (e) {
return false;
}
}

async createHomeFolder() {
const from = path.join(this.unpackedPrefix + "binaries");
const toCopy = ["youtube-dl-unix", "ffmpeg-linux", "ytdlVersion"];
await this.copyFiles(from, this.persistentPath, toCopy);
}

async copyFiles(from, to, files) {
await new Promise((resolve) => {
mkdirp(this.persistentPath).then(made => {
mkdirp(to).then(made => {
if (made != null) {
fs.copyFileSync(path.join(this.unpackedPrefix, "binaries/youtube-dl-unix"), path.join(this.persistentPath, "youtube-dl-unix"));
fs.copyFileSync(path.join(this.unpackedPrefix, "binaries/ffmpeg-linux"), path.join(this.persistentPath, "ffmpeg"));
fs.copyFileSync(path.join(this.unpackedPrefix, "binaries/ytdlVersion"), path.join(this.persistentPath, "ytdlVersion"));
for (const file of files) {
this.copyFile(from, to, file);
}
}
resolve();
})
})
});
});
}

copyFile(from, to, filename) {
try {
fs.copyFileSync(path.join(from, filename), path.join(to, filename));
} catch (e) {
console.error("Could not copy " + filename + " to " + to + ".");
}
}
}

Expand Down
1 change: 1 addition & 0 deletions modules/QueryManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
19 changes: 17 additions & 2 deletions modules/download/DownloadQuery.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -101,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) {
Expand Down
2 changes: 1 addition & 1 deletion modules/exceptions/ErrorHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
7 changes: 6 additions & 1 deletion modules/exceptions/errorDefinitions.json
Original file line number Diff line number Diff line change
Expand Up @@ -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&nbsp;<span onclick=\"$('#authModal').modal('show');\" class=\"openAuth\">private</span>.",
Expand Down Expand Up @@ -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",
Expand Down
3 changes: 1 addition & 2 deletions modules/info/InfoQueryList.js
Original file line number Diff line number Diff line change
Expand Up @@ -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]) {
Expand Down Expand Up @@ -43,7 +43,6 @@ class InfoQueryList {
});
}
}));
return result;
}

createVideo(data, url) {
Expand Down
62 changes: 62 additions & 0 deletions modules/persistence/Logger.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
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(", ", "-")
.replace(/\//g, "-")
.replace(/:/g, "-")
let result = await dialog.showSaveDialog(this.environment.win, {
defaultPath: path.join(this.environment.settings.downloadPath, "ytdl-log-" + date.slice(0, date.length - 6)),
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;
Loading