const {clipboard, shell, ipcRenderer} = require("electron"); const {default: YTDlpWrap} = require("yt-dlp-wrap-plus"); const path = require("path"); const os = require("os"); const fs = require("fs"); const {execSync} = require("child_process"); const { constants } = require("fs/promises"); let url; const ytDlp = localStorage.getItem("ytdlp"); const ytdlp = new YTDlpWrap(`"${ytDlp}"`); const downloadsDir = path.join(os.homedir(), "Downloads"); let downloadDir = localStorage.getItem("downloadPath") || downloadsDir; try { console.log("Trying to access", downloadDir) fs.accessSync(downloadDir, constants.W_OK); downloadDir = downloadDir; } catch (err) { console.log("Unable to write to download directory. Switching to default one.") console.log("Err:", err) downloadDir = downloadsDir; localStorage.setItem("downloadPath", downloadsDir); } getId("path").textContent = downloadDir; const i18n = new (require("../translations/i18n"))(); let cookieArg = ""; let browser = ""; const formats = { 144: 160, 240: 133, 360: 134, 480: 135, 720: 136, 1080: 299, 1440: 400, 2160: 401, 4320: 571, }; let originalCount = 0; let ffmpeg; let ffmpegPath; if (os.platform() === "win32") { ffmpeg = `"${__dirname}\\..\\ffmpeg.exe"`; ffmpegPath = `${__dirname}\\..\\ffmpeg.exe`; } else { ffmpeg = `"${__dirname}/../ffmpeg"`; ffmpegPath = `${__dirname}/../ffmpeg`; } if (!fs.existsSync(ffmpegPath)) { try { if (os.platform() === "win32") { ffmpeg = execSync("where ffmpeg.exe", {encoding: "utf8"}); console.log({ffmpeg}) ffmpeg = `"${ffmpeg.trimEnd()}"`; } else { ffmpeg = execSync("which ffmpeg", {encoding: "utf8"}); ffmpeg = `"${ffmpeg.trimEnd()}"`; } } catch (error) { ffmpeg = `""` console.log(error); } } console.log("ffmpeg:", ffmpeg); if (localStorage.getItem("preferredVideoQuality")) { const preferredVideoQuality = localStorage.getItem("preferredVideoQuality"); getId('select').value = preferredVideoQuality } if (localStorage.getItem("preferredAudioQuality")) { const preferredAudioQuality = localStorage.getItem("preferredAudioQuality"); getId("audioSelect").value = preferredAudioQuality; } let foldernameFormat = "%(playlist_title)s"; let filenameFormat = "%(playlist_index)s.%(title)s.%(ext)s"; let playlistIndex = 1; let playlistEnd = ""; let proxy = "" /** * * @param {string} id * @returns {any} */ function getId(id) { return document.getElementById(id); } function pasteLink() { url = clipboard.readText(); getId("link").textContent = " " + url; getId("options").style.display = "block"; getId("incorrectMsgPlaylist").textContent = ""; getId("errorBtn").style.display = "none"; } getId("pasteLink").addEventListener("click", () => { pasteLink(); }); document.addEventListener("keydown", (event) => { if (event.ctrlKey && event.key == "v") { pasteLink(); } }); // Patterns const playlistTxt = "Downloading playlist: "; const videoIndex = "Downloading item "; const oldVideoIndex = "Downloading video "; /** * @param {string} type */ function download(type) { // Config file let configArg = ""; let configTxt = ""; if (localStorage.getItem("configPath")) { configArg = "--config-location"; configTxt = `"${localStorage.getItem("configPath")}"`; } proxy = localStorage.getItem("proxy") || ""; console.log("Proxy:", proxy) nameFormatting(); originalCount = 0; // Playlist download range managePlaylistRange(); // Whether to use browser cookies or not if (localStorage.getItem("browser")) { browser = localStorage.getItem("browser"); } if (browser) { cookieArg = "--cookies-from-browser"; } else { cookieArg = ""; } let count = 0; let subs, subLangs; let playlistName; // If subtitles are checked if (getId("subChecked").checked) { subs = "--write-subs"; subLangs = "--sub-langs all"; console.log("Downloading with subtitles") } else { subs = ""; subLangs = ""; } hideOptions(); let quality, format, downloadProcess, videoType, audioQuality; if (type === "video") { quality = getId("select").value; videoType = getId("videoTypeSelect").value; const formatId = formats[quality]; if (quality === "best") { format = "-f bv*+ba/best"; } else if (quality === "worst") { format = "-f wv+wa/worst"; } else if (quality === "useConfig") { format = ""; } else { if (videoType === "mp4") { format = `-f "bestvideo[height<=1080]+bestaudio[ext=m4a]/best[height<=1080]/best" --merge-output-format "mp4" --recode-video "mp4"`; } else if (videoType === "webm") { format = `-f "bestvideo[height<=1080]+bestaudio[ext=webm]/best[height<=1080]/best" --merge-output-format "webm" --recode-video "webm"`; } else { format = `-f "bv*[height=${quality}]+ba/best[height=${quality}]/best[height<=${quality}]"`; } } } else { format = getId("audioSelect").value; audioQuality = getId("audioQualitySelect").value; console.log("Quality:", audioQuality); } console.log("Format:", format); const controller = new AbortController(); if (type === "video") { const args = [ format, "--yes-playlist", "-o", `"${path.join(downloadDir, foldernameFormat, filenameFormat)}"`, "-I", `"${playlistIndex}:${playlistEnd}"`, "--ffmpeg-location", ffmpeg, cookieArg, browser, configArg, configTxt, "--embed-metadata", subs, subLangs, videoType == "mp4" && (url.includes("youtube.com/") || url.includes("youtu.be/")) && os.platform() !== "darwin" ? "--embed-thumbnail" : "", proxy ? "--no-check-certificate" : "", proxy ? "--proxy" : "", proxy, "--compat-options", "no-youtube-unavailable-videos", `"${url}"`, ].filter(item => item); downloadProcess = ytdlp.exec( args, {shell: true, detached: false}, controller.signal ); } else { // Youtube provides m4a as audio, so no need to convert if ( (url.includes("youtube.com/") || url.includes("youtu.be/")) && format === "m4a" && audioQuality === "auto" ) { console.log("Downloading m4a without extracting") const args = [ "--yes-playlist", "--no-warnings", "-f", `ba[ext=${format}]/ba`, "-o", `"${path.join( downloadDir, foldernameFormat, filenameFormat )}"`, "-I", `"${playlistIndex}:${playlistEnd}"`, "--ffmpeg-location", ffmpeg, cookieArg, browser, configArg, configTxt, "--embed-metadata", subs, subLangs, os.platform() !== "darwin" ? "--embed-thumbnail" : "", proxy ? "--no-check-certificate" : "", proxy ? "--proxy" : "", proxy, "--compat-options", "no-youtube-unavailable-videos", `"${url}"`, ].filter(item => item); downloadProcess = ytdlp.exec( args, {shell: true, detached: false}, controller.signal ); } else { console.log("Extracting audio") const args = [ "--yes-playlist", "--no-warnings", "-x", "--audio-format", format, "--audio-quality", audioQuality, "-o", `"${path.join( downloadDir, foldernameFormat, filenameFormat )}"`, "-I", `"${playlistIndex}:${playlistEnd}"`, "--ffmpeg-location", ffmpeg, cookieArg, browser, configArg, configTxt, "--embed-metadata", subs, subLangs, format === "mp3" || format === "m4a" && (url.includes("youtube.com/") || url.includes("youtu.be/")) && os.platform() !== "darwin" ? "--embed-thumbnail" : "", proxy ? "--no-check-certificate" : "", proxy ? "--proxy" : "", proxy, "--compat-options", "no-youtube-unavailable-videos", `"${url}"`, ].filter(item => item); downloadProcess = ytdlp.exec( args, {shell: true, detached: false}, controller.signal ); } } // getId("finishBtn").addEventListener("click", () => { // controller.abort("user_finished") // try { // process.kill(downloadProcess.ytDlpProcess.pid, 'SIGINT') // } catch (_error) {} // }) downloadProcess.on("ytDlpEvent", (_eventType, eventData) => { // console.log(eventData); if (eventData.includes(playlistTxt)) { playlistName = eventData.split("playlist:")[1].slice(1); getId("playlistName").textContent = i18n.__("Downloading playlist:") + " " + playlistName; console.log(playlistName); } if ( (eventData.includes(videoIndex) || eventData.includes(oldVideoIndex)) && !eventData.includes("thumbnail") ) { count += 1; originalCount += 1; let itemTitle; if (type === "video") { itemTitle = i18n.__("Video") + " " + originalCount; } else { itemTitle = i18n.__("Audio") + " " + originalCount; } if (count > 1) { getId(`p${count - 1}`).textContent = i18n.__("File saved."); } const item = `

${itemTitle}

${i18n.__("Downloading...")}

`; getId("list").innerHTML += item; window.scrollTo(0, document.body.scrollHeight); } }); downloadProcess.on("progress", (progress) => { if (progress.percent == 100) { if (getId(`p${count}`)) { getId(`p${count}`).textContent = i18n.__("Processing") + "..."; } } else { if (getId(`p${count}`)) { getId(`p${count}`).textContent = `${i18n.__("Progress")} ${ progress.percent }% | ${i18n.__("Speed")} ${progress.currentSpeed || 0}`; } } }); downloadProcess.on("error", (error) => { showErrorTxt(error); }); downloadProcess.on("close", () => { afterClose(count); }); } function managePlaylistRange() { if (getId("playlistIndex").value) { playlistIndex = Number(getId("playlistIndex").value); } if (getId("playlistEnd").value) { playlistEnd = getId("playlistEnd").value; } console.log(`Range: ${playlistIndex}:${playlistEnd}`); if (playlistIndex > 0) { originalCount = playlistIndex - 1; console.log(`Starting download from ${originalCount + 1}`); } } function afterClose(count) { getId(`p${count}`).textContent = i18n.__("File saved."); getId("pasteLink").style.display = "inline-block"; const notify = new Notification("ytDownloader", { body: i18n.__("Playlist downloaded"), icon: "../assets/images/icon.png", }); notify.onclick = () => { openFolder(downloadDir); }; } // Error handling function showErrorTxt(error) { console.log("Error " + error); getId("pasteLink").style.display = "inline-block"; getId("openDownloads").style.display = "none"; getId("options").style.display = "block"; getId("playlistName").textContent = ""; getId("incorrectMsgPlaylist").textContent = i18n.__( "Some error has occurred. Check your network and use correct URL" ); getId("incorrectMsgPlaylist").style.display = "block" getId("incorrectMsgPlaylist").title = error; getId("errorBtn").style.display = "inline-block"; getId("errorDetails").innerHTML = ` URL: ${url}

${error} `; getId("errorDetails").title = i18n.__("Click to copy"); } // File and folder name patterns function nameFormatting() { // Handling folder and file names if (localStorage.getItem("foldernameFormat")) { foldernameFormat = localStorage.getItem("foldernameFormat"); } if (localStorage.getItem("filenameFormat")) { filenameFormat = localStorage.getItem("filenameFormat"); } } function hideOptions(justHide = false) { getId("options").style.display = "none"; getId("list").innerHTML = ""; getId("errorBtn").style.display = "none"; getId("errorDetails").style.display = "none"; getId("errorDetails").textContent = ""; getId("incorrectMsgPlaylist").style.display = "none" if (!justHide){ getId("playlistName").textContent = i18n.__("Processing") + "..."; getId("pasteLink").style.display = "none"; getId("openDownloads").style.display = "inline-block"; } } function downloadThumbnails() { let count = 0; let playlistName; hideOptions(); nameFormatting(); managePlaylistRange(); const args = [ "--yes-playlist", "--no-warnings", "-o", `"${path.join(downloadDir, foldernameFormat, filenameFormat)}"`, cookieArg, browser, "--write-thumbnail", "--convert-thumbnails png", "--skip-download", "-I", `"${playlistIndex}:${playlistEnd}"`, "--ffmpeg-location", ffmpeg, proxy ? "--no-check-certificate" : "", proxy ? "--proxy" : "", proxy, "--compat-options", "no-youtube-unavailable-videos", `"${url}"`, ].filter(item => item); const downloadProcess = ytdlp.exec( args, {shell: true, detached: false} ); // console.log(downloadProcess.ytDlpProcess.spawnargs[2]) downloadProcess.on("ytDlpEvent", (eventType, eventData) => { // console.log(eventData); if (eventData.includes(playlistTxt)) { playlistName = eventData.split("playlist:")[1].slice(1); getId("playlistName").textContent = i18n.__("Downloading playlist:") + " " + playlistName; console.log(playlistName); } if ( (eventData.includes(videoIndex) || eventData.includes(oldVideoIndex)) && !eventData.includes("thumbnail") ) { count += 1; originalCount++; let itemTitle; itemTitle = i18n.__("Thumbnail") + " " + originalCount; if (count > 1) { getId(`p${count - 1}`).textContent = i18n.__("File saved."); } const item = `

${itemTitle}

${i18n.__("Downloading...")}

`; getId("list").innerHTML += item; window.scrollTo(0, document.body.scrollHeight); } }); downloadProcess.on("error", (error) => { showErrorTxt(error); }); downloadProcess.on("close", () => { afterClose(count); }); } // Download video links function saveLinks() { let count = 0; let playlistName; hideOptions(); nameFormatting(); managePlaylistRange(); const args = [ "--yes-playlist", "--no-warnings", cookieArg, browser, "--skip-download", "--print-to-file", "webpage_url", `"${path.join(downloadDir, foldernameFormat, "links.txt")}"`, "--skip-download", "-I", `"${playlistIndex}:${playlistEnd}"`, proxy ? "--no-check-certificate" : "", proxy ? "--proxy" : "", proxy, "--compat-options", "no-youtube-unavailable-videos", `"${url}"`, ].filter(item => item); const downloadProcess = ytdlp.exec( args, {shell: true, detached: false} ); downloadProcess.on("ytDlpEvent", (eventType, eventData) => { // console.log(eventData); if (eventData.includes(playlistTxt)) { playlistName = eventData.split("playlist:")[1].slice(1); getId("playlistName").textContent = i18n.__("Downloading playlist:") + " " + playlistName; console.log(playlistName); } if ( (eventData.includes(videoIndex) || eventData.includes(oldVideoIndex)) && !eventData.includes("thumbnail") ) { count += 1; let itemTitle; itemTitle = i18n.__("Link") + " " + count; if (count > 1) { getId(`p${count - 1}`).textContent = i18n.__("Link added"); } const item = `

${itemTitle}

${i18n.__("Downloading...")}

`; getId("list").innerHTML += item; window.scrollTo(0, document.body.scrollHeight); } }); downloadProcess.on("close", () => { afterClose(count); }); downloadProcess.on("error", (error) => { showErrorTxt(error); }); } // Downloading video getId("download").addEventListener("click", () => { download("video"); }); // Downloading audio getId("audioDownload").addEventListener("click", () => { download("audio"); }); // Downloading thumbnails getId("downloadThumbnails").addEventListener("click", () => { downloadThumbnails(); }); // Saving video links to a text file getId("saveLinks").addEventListener("click", () => { saveLinks(); }); // Selecting download directory getId("selectLocation").addEventListener("click", () => { ipcRenderer.send("select-location-main", ""); }); ipcRenderer.on("downloadPath", (event, downloadPath) => { console.log(downloadPath); getId("path").textContent = downloadPath[0]; downloadDir = downloadPath[0]; }); function openFolder(location) { shell.openPath(location); } function closeMenu() { getId("menuIcon").style.transform = "rotate(0deg)"; let count = 0; let opacity = 1; const fade = setInterval(() => { if (count >= 10) { clearInterval(fade); } else { opacity -= 0.1; getId("menu").style.opacity = String(opacity); count++; } }, 50); } // Video and audio toggle const videoToggle = getId("videoToggle"); const audioToggle = getId("audioToggle"); videoToggle.style.backgroundColor = "var(--box-toggleOn)"; videoToggle.addEventListener("click", (event) => { videoToggle.style.backgroundColor = "var(--box-toggleOn)"; audioToggle.style.backgroundColor = "var(--box-toggle)"; getId("audioBox").style.display = "none"; getId("videoBox").style.display = "block"; }); audioToggle.addEventListener("click", (event) => { audioToggle.style.backgroundColor = "var(--box-toggleOn)"; videoToggle.style.backgroundColor = "var(--box-toggle)"; getId("videoBox").style.display = "none"; getId("audioBox").style.display = "block"; }); getId("select").addEventListener("change", () => { const value = getId("select").value; if (value == "best" || value == "worst" || value == "useConfig") { getId("typeSelectBox").style.display = "none"; } else { getId("typeSelectBox").style.display = "block"; } }); getId("closeHidden").addEventListener("click", () => { hideOptions(true); }); // More options let moreOptions = true; getId("advancedToggle").addEventListener("click", () => { if (moreOptions) { getId("advancedMenu").style.display = "block"; moreOptions = false; } else { getId("advancedMenu").style.display = "none"; moreOptions = true; } }); // Menu getId("openDownloads").addEventListener("click", () => { openFolder(downloadDir); }); getId("preferenceWin").addEventListener("click", () => { closeMenu(); ipcRenderer.send("load-page", __dirname + "/preferences.html"); }); getId("aboutWin").addEventListener("click", () => { closeMenu(); ipcRenderer.send("load-page", __dirname + "/about.html"); }); getId("homeWin").addEventListener("click", () => { closeMenu(); ipcRenderer.send("load-win", __dirname + "/index.html"); }); getId("compressorWin").addEventListener("click", () => { closeMenu(); ipcRenderer.send("load-win", __dirname + "/compressor.html"); }); // Translations getId("pasteLink").textContent = i18n.__( "Click to paste playlist link from clipboard [Ctrl + V]" ); getId("preferenceWin").textContent = i18n.__("Preferences"); getId("aboutWin").textContent = i18n.__("About"); getId("homeWin").textContent = i18n.__("Homepage"); getId("linkTitle").textContent = i18n.__("Link:"); getId("videoFormat").textContent = i18n.__("Select Quality"); getId("audioFormat").textContent = i18n.__("Select Audio Format "); getId("download").textContent = i18n.__("Download"); getId("audioDownload").textContent = i18n.__("Download"); getId("bestVideoOption").textContent = i18n.__("Best"); getId("worstVideoOption").textContent = i18n.__("Worst"); getId("openDownloads").textContent = i18n.__("Open download folder"); getId("videoToggle").textContent = i18n.__("Video"); getId("audioToggle").textContent = i18n.__("Audio"); getId("advancedToggle").textContent = i18n.__("More options"); getId("rangeTxt").textContent = i18n.__("Playlist range"); getId("playlistIndex").placeholder = i18n.__("Start"); getId("playlistEnd").placeholder = i18n.__("End"); getId("downloadThumbnails").textContent = i18n.__("Download thumbnails"); getId("saveLinks").textContent = i18n.__("Save video links to a file"); getId("useConfigTxt").textContent = i18n.__("Use config file"); getId("errorBtn").textContent = i18n.__("Error Details") + " ▼"; getId("clText").textContent = i18n.__("Current download location - "); getId("selectLocation").textContent = i18n.__("Select Download Location"); getId("themeTxt").textContent = i18n.__("Theme"); getId("autoTxt").textContent = i18n.__("Auto"); getId("videoQualityTxt").textContent = i18n.__("Select Video Format "); getId("lightTxt").textContent = i18n.__("Light"); getId("darkTxt").textContent = i18n.__("Dark"); getId("frappeTxt").textContent = i18n.__("Frappé"); getId("onedarkTxt").textContent = i18n.__("One Dark"); getId("matrixTxt").textContent = i18n.__("Matrix"); getId("githubTxt").textContent = i18n.__("Github"); getId("latteTxt").textContent = i18n.__("Latte"); getId("solarizedDarkTxt").textContent = i18n.__("Solarized Dark"); getId("audioQualitySelectTxt").textContent = i18n.__("Select Quality") getId("audioQualityAuto").textContent = i18n.__("Auto") getId("audioQualityNormal").textContent = i18n.__("Normal") getId("audioQualityBest").textContent = i18n.__("Best") getId("audioQualityGood").textContent = i18n.__("Good") getId("audioQualityBad").textContent = i18n.__("Bad") getId("audioQualityWorst").textContent = i18n.__("Worst") getId("subHeader").textContent = i18n.__("Subtitles"); getId("subTxt").textContent = i18n.__("Download subtitles if available");