.
================================================
FILE: README.md
================================================
# ytDownloader
[](https://flathub.org/apps/details/me.aandrew.ytdownloader)
[](https://github.com/aandrew-me/ytDownloader/releases)
[](https://github.com/aandrew-me/ytDownloader/releases/latest)
[](https://flathub.org/apps/io.github.aandrew_me.ytdn)
[](https://snapcraft.io/ytdownloader)


A modern GUI video and audio downloader supporting [hundreds of sites](https://github.com/yt-dlp/yt-dlp/blob/master/supportedsites.md)
[](https://snapcraft.io/ytdownloader)
[](https://github.com/aandrew-me/ytDownloader/releases/latest/download/YTDownloader_Linux.AppImage)
## Features 🚀
✅ Supports hundreds of sites including Youtube, Facebook, Instagram, Tiktok, Twitter and so on.
✅ Multiple themes
✅ Video Compressor with Hardware Acceleration
✅ Advanced options like Range Selection, Subtitles
✅ Download playlists
✅ Available on Linux, Windows & macOS
✅ Fast download speeds
✅ And of-course no trackers or ads
## Screenshots



# Installation
## Windows 🪟
- **Traditional way**
Download and install the exe or msi file. Exe file lets you choose custom download location, msi file doesn't ask for location. Windows defender may show a popup saying **Windows Protected Your PC**. Just click on **More info** and click on **Run Anyway**
- **Chocolatey**
App can be installed from [Chocolatey](https://community.chocolatey.org/packages/ytdownloader) using the following command
```
choco install ytdownloader
```
- **Scoop**
App can be installed with [Scoop](https://scoop.sh) using the following command
```
scoop install https://raw.githubusercontent.com/aandrew-me/ytDownloader/main/ytdownloader.json
```
- **Winget**
App can be installed with [Winget](https://github.com/microsoft/winget-cli) using the following command
```
winget install aandrew-me.ytDownloader
```
## Linux 🐧
Linux has several options available - Flatpak, AppImage and Snap.
Flatpak is recommended. For arm processors, download from flathub.
- ### AppImage
**AppImage** format is supported on most Linux distros and has Auto-Update support.
It just needs to be executed after downloading. See more about [AppImages here](https://appimage.org/).
[AppImageLauncher](https://github.com/TheAssassin/AppImageLauncher) is recommended for integrating AppImages.
- ### Flatpak
```
flatpak install flathub io.github.aandrew_me.ytdn
```
- ### Snapcraft
```
sudo snap install ytdownloader
```
## macOS 🍎
Since the app is not signed, when you will try to open the app, macOS will not allow you to open it.
You need to open terminal and execute:
```
sudo xattr -r -d com.apple.quarantine /Applications/YTDownloader.app
```
You will also need to install `yt-dlp` with [homebrew](https://brew.sh/)
```
brew install yt-dlp
```
## Internationalization (Localization) 🌍
Translations into other languages would be highly appreciated. If you want to help translating the app to other languages, you can join from [here](https://crwd.in/ytdownloader). Open a new issue and that language will be added to Crowdin. Please don't make pull requests with json files, instead use Crowdin.
[](https://crowdin.com/project/ytdownloader)
### ✅ Available languages
| Name | Status |
| ------------------- | ------ |
| Arabic | ✔️ |
| Bengali | ✔️ |
| English | ✔️ |
| Chinese Simplified | ✔️ |
| Chinese Traditional | ✔️ |
| Finnish | ✔️ |
| Hindi | ✔️ |
| French | ✔️ |
| Finnish | ✔️ |
| German | ✔️ |
| Greek | ✔️ |
| Hungarian | ✔️ |
| Italian | ✔️ |
| Japanese | ✔️ |
| Persian | ✔️ |
| Polish | ✔️ |
| Portuguese (Brazil) | ✔️ |
| Russian | ✔️ |
| Spanish | ✔️ |
| Turkish | ✔️ |
| Ukrainian | ✔️ |
| Vietnamese | ✔️ |
Thanks to [nxjosephofficial](https://github.com/nxjosephofficial), [LINUX-SAUNA](https://t.me/linuxsauna), [Proxycon](https://github.com/proxycon), [albanobattistella](https://github.com/albanobattistella), [TheBlueQuasar](https://github.com/TheBlueQuasar), [MrQuerter](https://github.com/MrQuerter), [KotoWhiskas](https://github.com/KotoWhiskas), [André](https://github.com/andre1828), [haggen88](https://github.com/haggen88), [XfedeX](https://github.com/XfedeX), [Jok3r](https://github.com/th3knv), [TitouanReal](https://github.com/TitouanReal), [soredake](https://github.com/soredake), [yoi](https://github.com/thiennguyenqn), [HowlingWerewolf](https://github.com/HowlingWerewolf), [Kum](https://github.com/kum4423), [Mohammed Bakry](https://crowdin.com/profile/m7md_b4kry), [Huang Bingfeng](https://github.com/jackiotyu), [Abhinav](https://github.com/abhixdd), [CodWiz](https://github.com/C0dwiz) and others for helping.
## Used technologies
- [yt-dlp](https://github.com/yt-dlp/yt-dlp)
- [Electron](https://www.electronjs.org/)
- [ffmpeg](https://ffmpeg.org/)
- [nodeJS](https://nodejs.org/en/)
- [flaticon](https://www.flaticon.com/)
## For building or running from source code
[Nodejs](https://nodejs.org/) (along with npm) needs to be installed.
Required commands to get started.
```
git clone https://github.com/aandrew-me/ytDownloader.git
cd ytDownloader
npm i
```
To run with [Electron](https://www.electronjs.org/) :
```
npm start
```
You need to download ffmpeg and put it in the root directory of the project. If you don't need to build for arm processor, you can download ffmpeg by executing any of the files - linux.sh / mac.sh / windows.sh depending on the platform. Otherwise you need to download ffmpeg from [here](https://github.com/yt-dlp/FFmpeg-Builds/releases) for windows/linux and from [here](http://www.osxexperts.net/) for mac (not tested)
To build for Linux (It will create packages as specified in package.json). The builds are stored in **release** folder.
```
npm run linux
```
To build for Windows
```
npm run windows
```
To build for macOS
```
npm run mac
```
If you only want to build for one format, you can do
```
npx electron-builder -l appimage
```
It will just create a linux appimage build.
================================================
FILE: assets/css/extra.css
================================================
@font-face {
font-family: "JetBrains";
src: url("../fonts/JetBrainsMono-Regular.ttf") format("truetype");
}
:root[theme="light"] {
--background: #f9fafb;
--text: #1f2937;
--box-main: #f3f4f6;
--box-toggle: rgb(215 238 233);
--box-separation: #e5e7eb;
--box-toggleOn: rgb(127, 250, 172);
--item-bg: #dddddd;
--box-shadow: none;
--select: #a7f3d0;
--greenBtn: #22c55e;
--greenBtn-bottom: #16a34a;
--redBtn: #d64d4f;
--redBtn-bottom: #854243;
--blueBtn: #3b82f6;
--blueBtn-bottom: rgb(44, 78, 180);
}
:root[theme="dark"] {
--background: #121212;
--text: rgb(229, 229, 229);
--box-main: #1d1d1d;
--box-toggle: #191919;
--box-separation: #2e2e2e;
--box-toggleOn: #2e2e2e;
--item-bg: #2c2e31;
--box-shadow: none;
--select: #252426;
--greenBtn: #05aa76;
--greenBtn-bottom: #047652;
--redBtn: #c82b2d;
--redBtn-bottom: #803334;
--blueBtn: rgb(80, 140, 230);
--blueBtn-bottom: rgb(44, 78, 180);
--border: none;
}
:root[theme="frappe"] {
--background: #232634;
--text: #e2e8ff;
--box-main: #303446;
--box-toggle: #414559;
--box-separation: #414559;
--box-toggleOn: #607dc1;
--item-bg: #414559;
--select: #3b3e4a;
--greenBtn: #78c346;
--greenBtn-bottom: #597844;
--redBtn: #d64d4f;
--redBtn-bottom: #854243;
--blueBtn: rgb(80, 128, 230);
--blueBtn-bottom: rgb(44, 78, 180);
}
:root[theme="onedark"] {
--background: #282c34;
--text: #d2d6df;
--box-main: #3a3d46;
--box-toggle: #2f333d;
--box-separation: #2f333d;
--box-toggleOn: #13a3b7;
--item-bg: #4d515d;
--select: #262c33;
--greenBtn: #85cf50;
--greenBtn-bottom: #406923;
--redBtn: #be2d39;
--redBtn-bottom: #791a22;
--blueBtn: rgb(80, 128, 230);
--blueBtn-bottom: rgb(44, 78, 180);
}
:root[theme="matrix"] {
--background: #0d0208;
--text: #00ff41;
--box-main: #0c2216;
--box-toggle: #214338;
--box-separation: #214338;
--box-toggleOn: #24782e;
--item-bg: #214338;
--select: #08180f;
--greenBtn: #19b42b;
--greenBtn-bottom: #10701c;
--redBtn: #19b42b;
--redBtn-bottom: #10701c;
--blueBtn: #19b42b;
--blueBtn-bottom: #10701c;
}
:root[theme="github"] {
--background: #f6f8fa;
--text: #292d31;
--box-main: #ffffff;
--box-toggle: #f3f3f3;
--box-separation: #f3f3f3;
--box-toggleOn: #cce5ff;
--item-bg: #3a66d150;
--select: #cce5ff;
--greenBtn: #0a9431;
--greenBtn-bottom: #0c6826;
--redBtn: #d73a49;
--redBtn-bottom: #9b2733;
--blueBtn: #005cc5;
--blueBtn-bottom: #00428e;
}
:root[theme="latte"] {
--background: #dce0e8;
--text: #4c4f69;
--box-main: #eff1f5;
--box-toggle: #e6e9ef;
--box-separation: #e6e9ef;
--box-toggleOn: #cce5ff;
--item-bg: #bcc0cc;
--select: #cce5ff;
--greenBtn: #40a02b;
--greenBtn-bottom: #2e711f;
--redBtn: #d20f39;
--redBtn-bottom: #9c0c2b;
--blueBtn: #1e66f5;
--blueBtn-bottom: rgb(3, 49, 101);
}
:root[theme="solarized-dark"] {
--background: #002b36;
--text: #a4b1b3;
--box-main: #003745;
--box-toggle: #2e4c52;
--box-separation: #2e4c52;
--box-toggleOn: #005a6f;
--item-bg: #003745;
--select: rgb(9, 57, 53);
--greenBtn: #859900;
--greenBtn-bottom: rgb(73, 84, 1);
--redBtn: #dc322f;
--redBtn-bottom: #af2523;
--blueBtn: #268bd2;
--blueBtn-bottom: #2074b1;
}
:root[theme = "gruvbox"]{
--background: #242424;
--text: #fffefd;
--box-main: #32302f;
--box-toggle: #282828;
--box-separation: #504945;
--box-toggleOn: #458588;
--item-bg: #4d515d;
--select: #98971a;
--greenBtn:#8ec07c;
--greenBtn-bottom:#689D6A;
--redBtn: #fb4934;
--redBtn-bottom: #cc241d;
--blueBtn: #7fa2ac;
--blueBtn-bottom: #458588;
}
body {
background-color: var(--background);
color: var(--text);
padding: 20px;
font-size: x-large;
text-align: left;
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue",
sans-serif;
}
h1 {
margin-top: 0;
display: inline-block;
}
#version {
margin: 5px;
}
input[type="text"],
.input {
border-radius: 5px;
padding: 5px;
outline: none;
border: none;
width: 50%;
height: 35px;
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue",
sans-serif;
}
.input {
width: 70px;
font-size: larger;
padding: 5px;
}
.prefBox,
#pathConfig {
display: flex;
justify-content: space-between;
align-items: center;
margin: 15px 15px;
padding: 20px 15px;
border-radius: 15px;
background-color: var(--box-main);
}
#ytDlpArgBox {
margin: 15px 15px;
padding: 20px 15px;
border-radius: 15px;
background-color: var(--box-main);
}
#path {
font-family: "JetBrains";
font-size: medium;
}
#flatpakTxt {
margin: 0 15px;
padding: 20px 15px;
cursor: pointer;
text-decoration: underline;
color: var(--blueBtn);
display: none;
}
.prefBox {
flex-direction: row;
}
#pathConfig {
flex-direction: column;
}
.configBox {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
width: 100%;
}
#configBtn {
display: inline-block;
}
#configOpts {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
#save {
padding: 10px;
border-radius: 8px;
border: none;
color: rgb(255, 255, 255);
background-color: rgb(49, 215, 49);
cursor: pointer;
}
#save:active {
background-color: rgb(41, 155, 41);
}
#top {
position: absolute;
top: 10px;
right: 10px;
display: flex;
}
#back,
#restart {
text-decoration: none;
padding: 8px;
border-radius: 10px;
margin: 3px;
font-size: large;
cursor: pointer;
}
#back {
background-color: var(--blueBtn);
color: white;
}
#restart {
background-color: var(--redBtn);
color: white;
}
.redBtn {
background-color: var(--redBtn);
color: white;
text-decoration: none;
border: none;
margin: 5px;
border-radius: 10px;
cursor: pointer;
font-size: medium;
padding: 5px;
}
a {
color: rgb(29, 140, 209);
cursor: pointer;
}
input[type="checkbox"] {
width: 25px;
height: 25px;
}
.greenBtn {
padding: 10px;
margin: 0 20px;
border: none;
border-radius: 10px;
font-size: large;
color: white;
background-color: var(--greenBtn);
cursor: pointer;
position: relative;
outline: none;
}
#selectLocation:active {
border-bottom: none;
top: 4px;
margin-bottom: 4px;
}
select {
padding: 15px;
background-color: var(--select);
color: var(--text);
border: none;
border-radius: 8px;
cursor: pointer;
font-size: large;
outline: none;
position: relative;
width: 160px;
}
#browserInfo {
cursor: pointer;
}
#configOpts {
display: none;
}
body::-webkit-scrollbar {
width: 10px;
}
body::-webkit-scrollbar-thumb {
background: linear-gradient(rgb(110, 110, 110), rgb(77, 77, 77));
border-radius: 8px;
}
#proxyTxt:valid {
border: 2px solid var(--greenBtn);
}
#proxyTxt:invalid {
border: 2px solid var(--redBtn);
}
#customArgsInput {
padding: 8px;
width: 94%;
margin: auto;
outline: none;
font-family: "JetBrains";
border-radius: 8px;
resize: none;
overflow: hidden;
}
.popup-container {
position: fixed;
bottom: 30px;
left: 30px;
z-index: 9999;
display: flex;
flex-direction: column;
gap: 12px;
}
.popup-item {
display: inline-block;
color: #fff;
padding: 12px 24px;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
font-weight: 600;
font-size: 15px;
opacity: 1;
transition: opacity 0.4s;
}
================================================
FILE: assets/css/index.css
================================================
@font-face {
font-family: "JetBrains";
src: url("../fonts/JetBrainsMono-Regular.ttf") format("truetype");
}
@font-face {
font-family: "Ubuntu";
src: url("../fonts/Ubuntu-Regular.ttf") format("truetype");
}
@font-face {
font-family: "Inter";
src: url("../fonts/Inter.ttf") format("truetype");
}
:root[theme="light"] {
--background: #f9fafb;
--text: #1f2937;
--box-main: #f3f4f6;
--box-toggle: rgb(215 238 233);
--box-separation: #e5e7eb;
--box-toggleOn: rgb(127, 250, 172);
--item-bg: #dddddd;
--box-shadow: none;
--select: #a7f3d0;
--greenBtn: #22c55e;
--greenBtn-bottom: #16a34a;
--redBtn: #d64d4f;
--redBtn-bottom: #854243;
--blueBtn: #3b82f6;
--blueBtn-bottom: rgb(44, 78, 180);
}
:root[theme="dark"] {
--background: #121212;
--text: rgb(229, 229, 229);
--box-main: #1d1d1d;
--box-toggle: #191919;
--box-separation: #2e2e2e;
--box-toggleOn: #2e2e2e;
--item-bg: #2c2e31;
--box-shadow: none;
--select: #252426;
--greenBtn: #05aa76;
--greenBtn-bottom: #047652;
--redBtn: #c82b2d;
--redBtn-bottom: #803334;
--blueBtn: rgb(80, 140, 230);
--blueBtn-bottom: rgb(44, 78, 180);
--border: none;
}
:root[theme="frappe"] {
--background: #232634;
--text: #e2e8ff;
--box-main: #303446;
--box-toggle: #414559;
--box-separation: #414559;
--box-toggleOn: #607dc1;
--item-bg: #414559;
--select: #3b3e4a;
--greenBtn: #78c346;
--greenBtn-bottom: #597844;
--redBtn: #d64d4f;
--redBtn-bottom: #854243;
--blueBtn: rgb(80, 128, 230);
--blueBtn-bottom: rgb(44, 78, 180);
}
:root[theme="onedark"] {
--background: #282c34;
--text: #d2d6df;
--box-main: #3a3d46;
--box-toggle: #2f333d;
--box-separation: #2f333d;
--box-toggleOn: #13a3b7;
--item-bg: #4d515d;
--select: #262c33;
--greenBtn: #85cf50;
--greenBtn-bottom: #406923;
--redBtn: #be2d39;
--redBtn-bottom: #791a22;
--blueBtn: rgb(80, 128, 230);
--blueBtn-bottom: rgb(44, 78, 180);
}
:root[theme="matrix"] {
--background: #0d0208;
--text: #00ff41;
--box-main: #0c2216;
--box-toggle: #214338;
--box-separation: #214338;
--box-toggleOn: #24782e;
--item-bg: #214338;
--select: #08180f;
--greenBtn: #19b42b;
--greenBtn-bottom: #10701c;
--redBtn: #19b42b;
--redBtn-bottom: #10701c;
--blueBtn: #19b42b;
--blueBtn-bottom: #10701c;
}
:root[theme="github"] {
--background: #f6f8fa;
--text: #292d31;
--box-main: #ffffff;
--box-toggle: #f3f3f3;
--box-separation: #f3f3f3;
--box-toggleOn: #cce5ff;
--item-bg: #3a66d150;
--select: #cce5ff;
--greenBtn: #0a9431;
--greenBtn-bottom: #0c6826;
--redBtn: #d73a49;
--redBtn-bottom: #9b2733;
--blueBtn: #005cc5;
--blueBtn-bottom: #00428e;
}
:root[theme="latte"] {
--background: #dce0e8;
--text: #4c4f69;
--box-main: #eff1f5;
--box-toggle: #e6e9ef;
--box-separation: #e6e9ef;
--box-toggleOn: #cce5ff;
--item-bg: #bcc0cc;
--select: #cce5ff;
--greenBtn: #40a02b;
--greenBtn-bottom: #2e711f;
--redBtn: #d20f39;
--redBtn-bottom: #9c0c2b;
--blueBtn: #1e66f5;
--blueBtn-bottom: rgb(3, 49, 101);
}
:root[theme="solarized-dark"] {
--background: #002b36;
--text: #a4b1b3;
--box-main: #003745;
--box-toggle: #2e4c52;
--box-separation: #2e4c52;
--box-toggleOn: #005a6f;
--item-bg: #003745;
--select: rgb(9, 57, 53);
--greenBtn: #859900;
--greenBtn-bottom: rgb(73, 84, 1);
--redBtn: #dc322f;
--redBtn-bottom: #af2523;
--blueBtn: #268bd2;
--blueBtn-bottom: #2074b1;
}
:root[theme = "gruvbox"]{
--background: #242424;
--text: #fffefd;
--box-main: #32302f;
--box-toggle: #282828;
--box-separation: #504945;
--box-toggleOn: #458588;
--item-bg: #eebd35;
--select: #98971a;
--greenBtn:#8ec07c;
--greenBtn-bottom:#689D6A;
--redBtn: #fb4934;
--redBtn-bottom: #cc241d;
--blueBtn: #7fa2ac;
--blueBtn-bottom: #458588;
}
body {
font-family: "Ubuntu";
text-align: center;
padding: 10px;
background-color: var(--background);
color: var(--text);
transition: 0.4s;
font-size: large;
user-select: none;
}
img {
-webkit-user-drag: none;
}
#popupBox,
#popupBoxMac {
width: 100vw;
height: 100vh;
margin: 0;
background-color: rgba(17, 25, 40, 0.75);
position: absolute;
top: 0;
left: 0;
z-index: 2;
display: none;
}
#popup,
#popupMac {
position: absolute;
padding: 20px;
top: 50%;
left: 50%;
width: 300px;
border-radius: 10px;
transform: translate(-50%, -50%);
background-color: rgb(91, 91, 91);
color: white;
}
#tryBtn {
background-color: rgb(137, 226, 255);
color: rgb(35, 35, 35);
border: none;
padding: 10px;
border-radius: 10px;
cursor: pointer;
position: relative;
}
#tryBtn:active {
border: none;
}
#menuIcon {
position: absolute;
top: 10px;
right: 10px;
width: 40px;
height: 40px;
cursor: pointer;
transition: 0.3s;
}
#menu {
display: none;
flex-direction: column;
backdrop-filter: blur(18px) saturate(180%);
-webkit-backdrop-filter: blur(18px) saturate(180%);
background: rgba(15, 23, 42, 0.85);
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 16px;
position: absolute;
top: 45px;
right: 50px;
padding: 12px;
width: 220px;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
text-align: left;
font-family: "Inter", sans-serif;
font-size: medium;
color: #e2e8f0;
z-index: 2;
animation: fadeIn 0.25s ease-out;
}
.menuItem {
cursor: pointer;
padding: 10px 12px;
border-radius: 8px;
margin-bottom: 6px;
text-decoration: none;
color: #d6dadf;
transition: all 0.15s ease-in-out;
border-bottom: none;
background: rgba(25, 37, 66, 0.223);
}
.menuItem:hover {
background: rgba(255, 255, 255, 0.08);
color: #38bdf8;
transform: translateX(2px);
}
.menuDivider {
height: 1px;
background: rgba(255, 255, 255, 0.08);
margin: 4px 0;
}
.menuLabel {
margin-top: 5px;
font-size: 0.9rem;
color: #94a3b8;
padding: 4px 0;
}
.themeSelect {
margin-top: 4px;
background: rgba(30, 41, 59, 0.9);
color: #f8fafc;
border: 1px solid rgba(255, 255, 255, 0.15);
border-radius: 8px;
padding: 8px;
font-family: "Inter", sans-serif;
transition: border 0.2s, background 0.2s;
width: 100%;
cursor: pointer;
}
.themeSelect:hover,
.themeSelect:focus {
border-color: #38bdf8;
background: rgba(51, 65, 85, 0.9);
outline: none;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(-6px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.item {
display: flex;
position: relative;
width: 86%;
background-color: var(--item-bg);
color: var(--text);
margin: 10px auto;
border-radius: 10px;
padding: 10px;
align-items: center;
justify-content: space-between;
}
.playlistItem {
display: flex;
position: relative;
width: 94%;
height: 25px;
background-color: var(--item-bg);
color: var(--text);
padding: 16px 25px;
border-radius: 15px;
align-items: center;
justify-content: space-between;
margin: 10px auto;
}
.itemIconBox {
display: flex;
flex-direction: column;
}
.itemIcon {
max-width: 160px;
max-height: 90px;
border-radius: 10px;
margin: 0 10px 0 0;
}
.title {
padding: 12px 10px;
border-radius: 8px;
margin-left: 4px;
border: none;
outline: none;
width: 50%;
text-align: center;
background-color: var(--box-separation);
color: var(--text);
font-size: large;
font-family: "Ubuntu";
}
.itemTitle {
padding: 5px;
}
.itemBody {
height: 90%;
width: 100%;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.itemProgress {
font-weight: bold;
cursor: pointer;
padding: 10px 0;
}
.custom-progress {
width: 90%;
height: 8px;
background: #e0e0e0;
border-radius: 6px;
overflow: hidden;
display:inline-block;
}
.custom-progress-fill {
width: 0%;
height: 100%;
background: var(--blueBtn);
transition: width 0.3s ease;
border-radius: 6px 0 0 6px;
}
.itemSpeed {
padding: 10px 5px 5px 5px;
}
.itemClose {
position: absolute;
top: 8px;
right: 8px;
cursor: pointer;
cursor: pointer;
width: 20px;
height: 20px;
}
.itemType {
font-style: italic;
margin-top: 5px;
margin: top 8px;
}
#closeHidden {
bottom: 7px;
position: absolute;
right: 10px;
cursor: pointer;
cursor: pointer;
width: 20px;
height: 20px;
}
#hidden {
display: none;
position: absolute;
z-index: 1;
left: 0;
right: 0;
margin: auto;
top: 20%;
background-color: var(--box-main);
border-radius: 15px;
width: 80%;
padding: 10px 10px 25px 10px;
color: var(--text);
border: var(--border);
}
#videoBox,
#audioBox {
background-color: var(--box-toggle);
padding: 10px;
border-radius: 10px;
}
#audioExtract,
.separationBox {
margin: 10px;
padding: 10px;
background-color: var(--box-separation);
border-radius: 10px;
}
#options {
display: none;
position: absolute;
overflow: hidden;
z-index: 1;
left: 0;
right: 0;
margin: auto;
top: 15%;
background-color: var(--box-main);
width: 80%;
border-radius: 10px;
padding: 10px;
color: var(--text);
}
#btnContainer {
display: flex;
flex-direction: row;
justify-content: center;
padding-top: 10px;
}
.toggleBtn {
width: 50%;
font-size: x-large;
border: var(--border);
background-color: var(--box-toggle);
border-radius: 10px;
cursor: pointer;
padding: 8px;
color: var(--text);
}
#videoToggle {
background-color: var(--box-toggleOn);
}
.select {
padding: 12px 15px;
background-color: var(--select);
color: var(--text);
border: none;
border-radius: 12px;
cursor: pointer;
font-size: large;
margin-bottom: 8px;
margin-left: 8px;
outline: none;
max-width: min(400px, 100%);
font-family: "JetBrains";
}
.audioSelect {
width: 180px;
}
#videoTypeSelect {
width: 160px;
}
#audioQualitySelect {
width: 145px;
}
#link {
padding: 10px;
}
#videoFormatSelect,
#audioFormatSelect,
#audioForVideoFormatSelect {
font-family: JetBrains, monospace;
font-size: medium;
width: 400px;
}
.formatSelect {
margin-right: 4px;
}
label {
position: relative;
top: 3px;
}
#videoList,
#audioList {
display: none;
}
.cb {
width: 20px;
height: 20px;
position: relative;
left: 5px;
top: 4px;
}
input[type="number"]::-webkit-inner-spin-button,
input[type="number"]::-webkit-outer-spin-button {
appearance: none;
}
.submitBtn {
padding: 15px;
border-radius: 10px;
background-color: var(--greenBtn);
color: white;
border: none;
font-size: large;
cursor: pointer;
display: inline-block;
outline: none;
position: relative;
}
.submitBtn:active {
background-color: var(--greenBtn-bottom);
border: none;
}
#pasteUrl,
#pasteLink {
--greenTop: #34d399;
--greenBottom: #059669;
--greenHover: #10b981;
--greenActive: #047857;
background: linear-gradient(
to bottom right,
var(--greenTop),
var(--greenBottom)
);
color: #fff;
font-size: 1.1rem;
padding: 16px 28px;
border: none;
border-radius: 16px;
cursor: pointer;
outline: none;
position: relative;
transition: all 0.25s ease;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);
margin-top: 15px;
font-weight: bold;
}
#pasteUrl:hover,
#pasteLink:hover {
background: linear-gradient(
to bottom right,
var(--greenHover),
var(--greenBottom)
);
transform: translateY(-2px);
}
#pasteUrl:active,
#pasteUrl.active,
#pasteLink:active {
background: linear-gradient(
to bottom right,
var(--greenActive),
var(--greenBottom)
);
transform: translateY(0);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3) inset;
}
.paste-keys {
margin-left: 4px;
}
.paste-keys .key {
background: rgba(255, 255, 255, 0.128);
border-radius: 8px;
padding: 6px 10px;
font-size: 15px;
font-family: "Inter", sans-serif;
color: #e5e5e5;
margin: 0 3px;
border-bottom: 3px solid rgba(2, 107, 72, 0.6);
}
.resumeBtn {
padding: 10px;
border-radius: 8px;
background-color: rgb(64, 227, 64);
color: white;
border: none;
cursor: pointer;
display: inline-block;
outline: none;
position: absolute;
right: 10px;
bottom: 10px;
width: 100px;
}
.input {
padding: 8px;
border: none;
outline: none;
border-radius: 5px;
width: 70px;
font-size: large;
margin: 5px;
}
#incorrectMsg,
#incorrectMsgPlaylist {
color: var(--redBtn);
}
#incorrectMsgPlaylist {
display: none;
}
#errorBtn {
display: none;
}
#errorDetails {
cursor: pointer;
font-family: monospace;
padding: 15px;
margin: 10px 0;
border: 2px solid rgb(189, 0, 0);
border-radius: 8px;
display: none;
transition: 0.5s all;
}
#loadingWrapper {
/* Default is flex */
display: none;
flex-direction: row;
justify-content: center;
align-items: center;
}
#preparingBox {
display: none;
flex-direction: row;
justify-content: center;
align-items: center;
}
svg {
width: 100px;
height: 100px;
display: inline-block;
margin-left: 20px;
}
.savedMsg {
color: rgb(52, 170, 234);
cursor: pointer;
}
#savedMsg {
cursor: pointer;
}
button {
font-family: "Ubuntu";
font-weight: bold;
outline: none;
}
#extractBtn,
.blueBtn {
color: white;
background-color: var(--blueBtn);
border: none;
position: relative;
padding: 15px;
border-radius: 10px;
cursor: pointer;
margin: 8px;
font-size: large;
text-decoration: none;
}
#extractBtn {
width: 150px;
}
#download,
#audioDownload {
width: 140px;
}
#clearBtn {
display: none;
}
#extractBtn:active,
.blueBtn:active {
background-color: var(--blueBtn-bottom);
border: none;
}
.advancedToggle {
color: white;
background-color: var(--redBtn);
border: none;
position: relative;
padding: 15px;
border-radius: 10px;
cursor: pointer;
margin: 8px;
font-size: large;
}
.advancedToggle:active {
background-color: var(--blueBtn-bottom);
border: none;
}
#advanced {
display: none;
}
.advancedItem {
border-radius: 15px;
padding: 20px;
margin: 10px;
background-color: var(--item-bg);
}
#path {
font-family: "JetBrains";
font-size: medium;
}
#subHeader {
font-weight: bold;
margin-top: 0;
}
h2 {
margin: 12px;
font-size: 24px;
}
/* Scrollbar */
body::-webkit-scrollbar {
width: 5px;
}
body::-webkit-scrollbar-track {
box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
}
body::-webkit-scrollbar-thumb {
background-color: rgb(79, 78, 78);
border-radius: 5px;
}
#goToTop {
display: none;
position: fixed;
bottom: 10px;
right: 10px;
z-index: 2;
border: none;
outline: none;
background-image: url(../images/up-arrow.png);
background-size: contain;
width: 40px;
height: 40px;
cursor: pointer;
color: white;
cursor: pointer;
}
.popup-container {
position: fixed;
bottom: 30px;
left: 30px;
z-index: 9999;
display: flex;
flex-direction: column;
gap: 12px;
}
.popup-item {
display: inline-block;
color: #fff;
padding: 12px 24px;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
font-weight: 600;
font-size: 15px;
opacity: 1;
transition: opacity 0.4s;
}
.scale {
animation-name: scale;
animation-duration: 0.4s;
animation-fill-mode: forwards;
}
.scaleUp {
animation-name: scaleUp;
animation-duration: 0.4s;
animation-fill-mode: forwards;
}
body::-webkit-scrollbar {
width: 10px;
}
body::-webkit-scrollbar-thumb {
background: linear-gradient(rgb(110, 110, 110), rgb(77, 77, 77));
border-radius: 8px;
}
@keyframes scale {
0% {
scale: 1;
}
100% {
scale: 0;
}
}
@keyframes scaleUp {
0% {
scale: 0;
}
100% {
scale: 1;
}
}
@media screen and (max-width: 650px) {
.item {
width: 90%;
}
.title {
width: 80%;
}
}
/* Compressor styles */
#compressor-header {
text-align: center;
margin-top: 0;
}
.container {
max-width: 800px;
margin: 30px auto 10px auto;
background: var(--box-main);
padding: 30px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.drop-zone {
border: 2px dashed #ccc;
border-radius: 8px;
padding: 2rem;
text-align: center;
margin-bottom: 2rem;
transition: border-color 0.3s ease;
background: var(--box-toggle);
}
.drop-zone:hover {
border-color: #2196f3;
cursor: pointer;
}
.compress-select {
font-family: "Ubuntu";
padding: 10px;
}
.drop-zone.dragover {
border-color: #2196f3;
background-color: var(--box-toggleOn);
}
#settings-group-container {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
}
.output-folder-conf {
display: flex;
justify-content: space-between;
align-items: center;
}
#output-folder-box {
background-color: var(--box-toggle);
margin-bottom: 12px;
padding: 15px;
border-radius: 8px;
text-align: left;
}
#output-folder-input {
width: 20px;
height: 20px;
margin-left: 10px;
}
.folder-input-checkbox {
display: flex;
align-items: center;
}
#output-suffix {
cursor: text;
}
.settings-group {
margin-bottom: 10px;
display: flex;
flex-direction: column;
width: 46%;
}
.compress-label {
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
display: block;
margin-bottom: 10px;
font-weight: 500;
text-align: left;
}
#compression-status {
max-width: 800px;
margin: 20px auto 0 auto;
border-top: 1px solid var(--item-bg);
padding-top: 20px;
}
.status-item {
padding: 16px;
margin: 6px auto;
border-radius: 4px;
display: flex;
justify-content: space-between;
flex-direction: row;
background-color: var(--item-bg);
max-width: 800px;
}
.status-item.success {
color: var(--greenBtn);
}
.status-item.error {
color: var(--redBtn);
}
.status {
min-width: 80px;
text-transform: uppercase;
font-size: 0.9em;
font-weight: bold;
}
.fileinput-btn {
background: var(--blueBtn);
color: white;
padding: 10px;
display: block;
border-radius: 5px;
margin: 5px 0 10px 0;
cursor: pointer;
border: none;
}
.progressBarCompress {
width: 200px;
}
.nvidia_opt,
.amf_opt,
.qsv_opt,
.vaapi_opt,
.videotoolbox_opt {
display: none;
}
#custom-folder-select {
padding: 10px;
margin-left: 0;
display: none;
}
#custom-folder-path {
font-family: "JetBrains";
background-color: var(--box-main);
padding: 8px;
border-radius: 4px;
display: none;
}
#selected-files {
padding-top: 10px;
opacity: 0.8;
}
::view-transition-old(root),
::view-transition-new(root) {
animation: none;
mix-blend-mode: normal;
}
@media (min-width: 640px) {
.container {
padding: 2rem;
}
}
.slider-wrapper {
display: flex;
align-items: center;
gap: 1rem;
}
.time-display {
flex-shrink: 0;
width: 5.5rem;
height: 3rem;
background-color: var(--box-main);
color: var(--text);
border-radius: 0.5rem;
font-weight: 500;
border: none;
outline: none;
text-align: center;
font-family: "Inter", sans-serif;
font-size: 1rem;
padding: 0;
}
.slider-container {
position: relative;
width: 100%;
height: 2rem;
display: flex;
align-items: center;
}
.track-background {
position: absolute;
width: 100%;
height: 0.25rem;
background-color: #4b5563;
border-radius: 9999px;
}
#range-highlight {
position: absolute;
height: 0.25rem;
background-color: var(--blueBtn);
border-radius: 9999px;
z-index: 1;
}
input[type="range"] {
-webkit-appearance: none;
appearance: none;
width: 100%;
height: 4px;
background: transparent;
outline: none;
position: absolute;
margin: 0;
padding: 0;
pointer-events: none;
}
#min-slider {
z-index: 2;
}
#max-slider {
z-index: 3;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 12px;
height: 32px;
background-color: var(--blueBtn);
border-radius: 9999px;
cursor: pointer;
pointer-events: auto;
margin-top: -14px;
transition: transform 0.1s ease-in-out;
position: relative;
}
input[type="range"]::-webkit-slider-thumb:hover {
transform: scale(1.1);
}
#customArgsInput {
padding: 8px;
width: 94%;
margin: auto;
outline: none;
font-family: "JetBrains";
border-radius: 8px;
resize: vertical;
}
#updatePopup {
position: fixed;
bottom: 30px;
right: 30px;
z-index: 9999;
background-color: var(--box-separation);
padding: 15px 20px;
border-radius: 10px;
display: none;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 8px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3);
}
.progress-track {
width: 200px;
height: 8px;
background-color: rgba(209, 209, 209, 0.446);
border-radius: 4px;
overflow: hidden;
margin: 10px 2px;
}
#progressBarFill {
height: 100%;
width: 0%;
background-color: var(--greenBtn);
transition: width 0.2s ease;
}
.update-label,
#updateProgress {
font-size: 14px;
}
================================================
FILE: crowdin.yml
================================================
files:
- source: /translations/en.json
translation: /translations/%locale%.json
================================================
FILE: flatpak/io.github.aandrew_me.ytdn.desktop
================================================
[Desktop Entry]
Name=ytDownloader
Comment=Download videos and audios from YouTube and other sites
Exec=run.sh
Type=Application
Icon=io.github.aandrew_me.ytdn
Categories=Utility;
================================================
FILE: flatpak/io.github.aandrew_me.ytdn.metainfo.xml
================================================
io.github.aandrew_me.ytdn
ytDownloader
io.github.aandrew_me.ytdn.desktop
Andrew
https://github.com/aandrew-me/ytDownloader/issues
https://github.com/aandrew-me/ytDownloader?tab=readme-ov-file#internationalization-localization-
Download videos and audios from hundreds of sites
ytDownloader (Formerly know as Youtube Downloader Plus) lets you download videos and audios
of different qualities from hundreds of
sites including the popular ones but not limited to Youtube, Facebook, Instagram, Tiktok,
Twitter, Twitch and so on.
✔️ Supports high quality video resolutions
✔️ Video Compressor with Hardware Acceleration
✔️ Supports audio extraction in multiple formats
✔️ Supports playlist downloads
✔️ Supports downloading particular ranges
✔️ Supports downloading subtitles
✔️ Supports multiple themes
✔️ Completely free and open source
✔️ Fast download speeds
GPL-3.0
CC0-1.0
https://github.com/user-attachments/assets/12410bca-31c3-48a0-bbd3-1d74bcc752b6
ytDownloader homepage
https://github.com/user-attachments/assets/060557bc-d209-4bd0-bda4-debe42ca83a0
ytDownloader Settings
https://github.com/user-attachments/assets/52da7e50-46bb-4749-8152-5e79324a6cc3
ytDownloader video compressor
https://github.com/aandrew-me/ytDownloader
Bring back cookie related info
Added download history
Added JS runtime support as per new yt-dlp requirements. The app will now ship with a
nodejs binary. Youtube downloads should be more stable.
Added slider for range selection.
Added progress for yt-dlp download and update
Translation updates, added new languages.
Added custom built ffmpeg, ffprobe for Windows and Linux builds.
Added support to add custom yt-dlp arguments
Fixed incorrect resolution downloads for playlists.
Added showing approximate file sizes when possible.
Improved user interface.
Minor bug fixes.
Major code refactoring.
Improved app performance
UI improvements
Fixed minor bugs
Added support for chapters
UI improvements
Added Video Compressor with Hardware Acceleration (Beta)
Bug fixes
Bug fixes
Bug fixes
Fixes and enhancements
Design enhancements
Added removal of old windows updates
Ignore unavailable videos for playlists
Fixed issues with config file on resize
Added proxy support
Added button to clear downloads
Fixed download errors
Added compatibility for X(Twitter)
Minor enhancements
Added Chinese language
Fixed video trim info to filename
Fixed formats not being shown
Added Arabic language
Fixed conflicts with same filenames
Fixed formats not being selected properly
Fixed config file not being used when getting info
Changelog
Added option to choose audio for video
Webm options will be hidden by default
Enhancements
================================================
FILE: html/about.html
================================================
About
ytDownloader
ytDownloader lets you download videos and audios from hundreds of sites
like Youtube, Facebook,
Instagram, Tiktok, Twitter and so on
It's a Free and Open Source app built on top of Node.js and
Electron. yt-dlp has been used for
downloading
Source Code is available here
================================================
FILE: html/compressor.html
================================================
Video Compressor
Video format
Unchanged
mp4
mkv
Video Encoder
x264
x265
x264 (Intel QSV Hardware Acceleration)
x265 (Intel QSV Hardware Acceleration)
x264 (AMD Hardware Acceleration)
x265 (AMD Hardware Acceleration)
x264 (NVIDIA Hardware Acceleration)
x265 (NVIDIA Hardware Acceleration)
x264 (VA-API Hardware Acceleration)
x265 (VA-API Hardware Acceleration)
x264 (VideoToolbox Hardware Acceleration)
x265 (VideoToolbox Hardware Acceleration)
Copy video
Compression
Speed
Fast
Medium
Slow
Video Quality
18 (Best quality)
19
20
21
22
23 (Medium size, good quality)
24
25
26
27
28 (Smaller file size, decent quality)
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51 (Worst quality)
Audio Format
Unchanged
aac
mp3
Start Compression
Cancel
================================================
FILE: html/history.html
================================================
Download History - YtDownloader
All Formats
MP4
MP3
M4A
WEBM
Opus
WAV
FLAC
Export as
JSON
Export as CSV
Clear All History
================================================
FILE: html/index.html
================================================
YtDownloader
Click to paste video link from clipboard
Ctrl V
Error
Details ▼
Video
Audio
Title
Select Format
Select Audio Format
Download
More options
Select Format
Download
More options
Clear Downloads
================================================
FILE: html/playlist.html
================================================
Playlist download
Click to paste playlist link from
clipboard
Ctrl V
Video
Audio
Link:
Select Video Quality
Best
Worst
Use config file
144p
240p
360p
480p
720p (HD)
1080p (FHD)
1440p
2160p (4k)
Select video quality
Auto
Mp4
WebM
Download
Select Audio format
Mp3
M4a
Opus
Wav
Alac
Flac
Select
Quality
Auto
Normal
Best
Good
Bad
Worst
Download
More options
Error
Details ▼
Open download folder
================================================
FILE: html/playlist_new.html
================================================
Playlist download
Click to paste playlist link from clipboard [Ctrl + V]
Error Details ▼
================================================
FILE: html/preferences.html
================================================
Preferences
Preferences
Download location
Current download location -
Select Download
Location
Select Language (Requires reload)
English
Deutsch
Español
فارسی
Ελληνικά
Français
Italiano
Japanese
Magyar
Polski
Português
Русский
Finnish
Українська
Türkçe
Vietnamese
اَلْعَرَبِيَّةُ
简体中文
繁體中文
বাংলা
हिन्दी
नेपाली
Preferred video quality
144p
240p
360p
480p
720p (HD)
1080p (FHD)
1440p
2160p (4k)
Preferred video codec
AVC1
AV1
VP9
MP4V
Preferred audio format
Mp3
Aac
M4a
Opus
Wav
Alac
Flac
Select browser to use cookies from
ⓘ
None
Chrome
Firefox
Brave
Opera Mini
Edge
Chromium
Safari
Vivaldi
Proxy
Show more format options
Filename format for playlists
Reset to default
Folder name format for playlists
Reset to default
Maximum number of active downloads
Close to system tray
Disable auto updates
================================================
FILE: linux.sh
================================================
#!/bin/bash
# >> Check if curl is installed or nor
if ! command -V curl > /dev/null 2>&1; then
echo "curl not installed, please install it and try again"
exit
fi
wget "https://github.com/aandrew-me/ffmpeg-builds/releases/download/v8/ffmpeg_linux_amd64.tar.xz"
wget "https://github.com/aandrew-me/ffmpeg-builds/releases/download/v8/node_linux_amd64" -O node
chmod +x node
tar -xf ffmpeg_linux_amd64.tar.xz
mv ffmpeg_linux_amd64 ffmpeg
chmod +x ffmpeg/bin/ffmpeg
chmod +x ffmpeg/bin/ffprobe
chmod +x ffmpeg/bin/ffplay
================================================
FILE: mac.sh
================================================
#!/bin/bash
# >> Check if curl is installed or nor
if ! command -v curl > /dev/null 2>&1; then
echo "curl not installed, please install it and try again"
exit 1
fi
ARCH=$(uname -m)
mkdir -p ffmpeg/bin
if [ "$ARCH" = "arm64" ]; then
FF_URL="https://github.com/aandrew-me/ffmpeg-builds/releases/download/v8/ffmpeg_macos_arm64"
FP_URL="https://github.com/aandrew-me/ffmpeg-builds/releases/download/v8/ffprobe_macos_arm64"
elif [ "$ARCH" = "x86_64" ]; then
FF_URL="https://github.com/aandrew-me/ffmpeg-builds/releases/download/v8/ffmpeg_macos_amd64"
FP_URL="https://github.com/aandrew-me/ffmpeg-builds/releases/download/v8/ffprobe_macos_amd64"
else
echo "Unsupported architecture: $ARCH"
exit 1
fi
curl -L "$FF_URL" -o ffmpeg/bin/ffmpeg
curl -L "$FP_URL" -o ffmpeg/bin/ffprobe
chmod +x ffmpeg/bin/ffmpeg
chmod +x ffmpeg/bin/ffprobe
================================================
FILE: main.js
================================================
const {
app,
BrowserWindow,
dialog,
ipcMain,
shell,
Tray,
Menu,
clipboard,
} = require("electron");
const {autoUpdater} = require("electron-updater");
const fs = require("fs").promises;
const {existsSync, readFileSync} = require("fs");
const path = require("path");
const DownloadHistory = require("./src/history");
process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = "true";
autoUpdater.autoDownload = false;
const USER_DATA_PATH = app.getPath("userData");
const CONFIG_FILE_PATH = path.join(USER_DATA_PATH, "ytdownloader.json");
const appState = {
/** @type {BrowserWindow | null} */
mainWindow: null,
/** @type {BrowserWindow | null} */
secondaryWindow: null,
/** @type {Tray | null} */
tray: null,
isQuitting: false,
indexPageIsOpen: true,
trayEnabled: false,
loadedLanguage: {},
config: {},
downloadHistory: new DownloadHistory(),
autoUpdateEnabled: false,
};
const gotTheLock = app.requestSingleInstanceLock();
if (!gotTheLock) {
app.quit();
} else {
app.on("second-instance", () => {
if (appState.mainWindow) {
if (appState.mainWindow.isMinimized())
appState.mainWindow.restore();
appState.mainWindow.show();
appState.mainWindow.focus();
}
});
}
app.whenReady().then(async () => {
await initialize();
app.on("activate", () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
});
app.on("before-quit", async () => {
appState.isQuitting = true;
try {
// Save the final config state before exiting.
await saveConfiguration();
} catch (error) {
console.error("Failed to save configuration during quit:", error);
}
});
app.on("window-all-closed", () => {
if (process.platform !== "darwin") {
app.quit();
}
});
/**
* Initializes the application by loading config, translations,
* and setting up handlers.
*/
async function initialize() {
await loadConfiguration();
await loadTranslations();
registerIpcHandlers();
registerAutoUpdaterEvents();
createWindow();
if (process.platform === "win32") {
app.setAppUserModelId(app.name);
}
}
function createWindow() {
const bounds = appState.config.bounds || {};
appState.mainWindow = new BrowserWindow({
...bounds,
minWidth: 800,
minHeight: 600,
autoHideMenuBar: true,
show: false,
icon: path.join(__dirname, "/assets/images/icon.png"),
webPreferences: {
nodeIntegration: true,
contextIsolation: false,
spellcheck: false,
},
});
appState.mainWindow.loadFile("html/index.html");
appState.mainWindow.once("ready-to-show", () => {
if (appState.config.isMaximized) {
appState.mainWindow.maximize();
}
appState.mainWindow.show();
});
const saveBounds = () => {
if (appState.mainWindow && !appState.mainWindow.isMaximized()) {
appState.config.bounds = appState.mainWindow.getBounds();
}
};
appState.mainWindow.on("resize", saveBounds);
appState.mainWindow.on("move", saveBounds);
appState.mainWindow.on("maximize", () => {
appState.config.isMaximized = true;
});
appState.mainWindow.on("unmaximize", () => {
appState.config.isMaximized = false;
});
appState.mainWindow.on("close", (event) => {
if (!appState.isQuitting && appState.trayEnabled) {
event.preventDefault();
appState.mainWindow.hide();
if (app.dock) app.dock.hide();
}
});
}
/**
* @param {string} file The HTML file to load.
*/
function createSecondaryWindow(file) {
if (appState.secondaryWindow) {
appState.secondaryWindow.focus();
return;
}
appState.secondaryWindow = new BrowserWindow({
parent: appState.mainWindow,
modal: true,
show: false,
webPreferences: {
nodeIntegration: true,
contextIsolation: false,
},
width: 1000,
height: 800,
});
// appState.secondaryWindow.webContents.openDevTools();
appState.secondaryWindow.loadFile(file);
appState.secondaryWindow.setMenu(null);
appState.secondaryWindow.once("ready-to-show", () => {
appState.secondaryWindow.show();
});
appState.secondaryWindow.on("closed", () => {
appState.secondaryWindow = null;
});
}
/**
* Creates the system tray icon
*/
function createTray() {
if (appState.tray) return;
let iconPath;
if (process.platform === "win32") {
iconPath = path.join(__dirname, "resources/icon.ico");
} else if (process.platform === "darwin") {
iconPath = path.join(__dirname, "resources/icons/16x16.png");
} else {
iconPath = path.join(__dirname, "resources/icons/256x256.png");
}
appState.tray = new Tray(iconPath);
const contextMenu = Menu.buildFromTemplate([
{
label: i18n("openApp"),
click: () => {
appState.mainWindow?.show();
if (app.dock) app.dock.show();
},
},
{
label: i18n("pasteVideoLink"),
click: async () => {
const text = clipboard.readText();
appState.mainWindow?.show();
if (app.dock) app.dock.show();
if (appState.indexPageIsOpen) {
appState.mainWindow.webContents.send("link", text);
} else {
await appState.mainWindow.loadFile("html/index.html");
appState.indexPageIsOpen = true;
appState.mainWindow.webContents.once(
"did-finish-load",
() => {
appState.mainWindow.webContents.send("link", text);
}
);
}
},
},
{
label: i18n("downloadPlaylistButton"),
click: () => {
appState.indexPageIsOpen = false;
appState.mainWindow?.loadFile("html/playlist.html");
appState.mainWindow?.show();
if (app.dock) app.dock.show();
},
},
{
label: i18n("quit"),
click: () => {
app.quit();
},
},
]);
appState.tray.setToolTip("ytDownloader");
appState.tray.setContextMenu(contextMenu);
appState.tray.on("click", () => {
appState.mainWindow?.show();
if (app.dock) app.dock.show();
});
}
function registerIpcHandlers() {
ipcMain.on("autoUpdate", (_event, status) => {
appState.autoUpdateEnabled = status;
if (status) {
autoUpdater.checkForUpdates();
}
});
ipcMain.on("reload", () => {
appState.mainWindow?.reload();
appState.secondaryWindow?.reload();
});
ipcMain.on("get-version", (event) => {
event.sender.send("version", app.getVersion());
});
ipcMain.on("show-file", async (_event, fullPath) => {
try {
await fs.stat(fullPath);
shell.showItemInFolder(fullPath);
} catch (error) {}
});
ipcMain.handle("show-file", async (_event, fullPath) => {
try {
await fs.stat(fullPath);
shell.showItemInFolder(fullPath);
return {success: true};
} catch (error) {
return {success: false, error: error.message};
}
});
ipcMain.handle("open-folder", async (_event, folderPath) => {
try {
await fs.stat(folderPath);
const result = await shell.openPath(folderPath);
if (result) {
return {success: false, error: result};
} else {
return {success: true};
}
} catch (error) {
return {success: false, error: error.message};
}
});
ipcMain.on("load-win", (_event, file) => {
appState.indexPageIsOpen = file.includes("index.html");
appState.mainWindow?.loadFile(file);
});
ipcMain.on("load-page", (_event, file) => {
createSecondaryWindow(file);
});
ipcMain.on("close-secondary", () => {
appState.secondaryWindow?.close();
});
ipcMain.on("quit", () => {
app.quit();
});
ipcMain.on("select-location-main", async () => {
if (!appState.mainWindow) return;
const {canceled, filePaths} = await dialog.showOpenDialog(
appState.mainWindow,
{properties: ["openDirectory"]}
);
if (!canceled && filePaths.length > 0) {
appState.mainWindow.webContents.send("downloadPath", filePaths);
}
});
ipcMain.on("select-location-secondary", async () => {
if (!appState.secondaryWindow) return;
const {canceled, filePaths} = await dialog.showOpenDialog(
appState.secondaryWindow,
{properties: ["openDirectory"]}
);
if (!canceled && filePaths.length > 0) {
appState.secondaryWindow.webContents.send(
"downloadPath",
filePaths
);
}
});
ipcMain.on("get-directory", async () => {
if (!appState.mainWindow) return;
const {canceled, filePaths} = await dialog.showOpenDialog(
appState.mainWindow,
{properties: ["openDirectory"]}
);
if (!canceled && filePaths.length > 0) {
appState.mainWindow.webContents.send("directory-path", filePaths);
}
});
ipcMain.on("select-config", async () => {
if (!appState.secondaryWindow) return;
const {canceled, filePaths} = await dialog.showOpenDialog(
appState.secondaryWindow,
{properties: ["openFile"]}
);
if (!canceled && filePaths.length > 0) {
appState.secondaryWindow.webContents.send("configPath", filePaths);
}
});
ipcMain.on("useTray", (_event, enabled) => {
appState.trayEnabled = enabled;
if (enabled) createTray();
else {
appState.tray?.destroy();
appState.tray = null;
}
});
ipcMain.on("progress", (_event, percentage) => {
if (appState.mainWindow) appState.mainWindow.setProgressBar(percentage);
});
ipcMain.on("error_dialog", async (_event, message) => {
const {response} = await dialog.showMessageBox(appState.mainWindow, {
type: "error",
title: "Error",
message: message,
buttons: ["Ok", i18n("clickToCopy")],
});
if (response === 1) clipboard.writeText(message);
});
ipcMain.handle("get-system-locale", async (_event) => {
return app.getSystemLocale();
});
ipcMain.handle("get-translation", (_event, locale) => {
const fallbackFile = path.join(__dirname, "translations", "en.json");
const localeFile = path.join(
__dirname,
"translations",
`${locale}.json`
);
const fallbackData = JSON.parse(readFileSync(fallbackFile, "utf8"));
let localeData = {};
if (locale !== "en" && existsSync(localeFile)) {
try {
localeData = JSON.parse(readFileSync(localeFile, "utf8"));
} catch (e) {
console.error(`Could not parse ${localeFile}`, e);
}
}
const mergedTranslations = {...fallbackData, ...localeData};
return mergedTranslations;
});
ipcMain.handle("get-download-history", () =>
appState.downloadHistory.getHistory()
);
ipcMain.handle("add-to-history", (_, info) =>
appState.downloadHistory.addDownload(info)
);
ipcMain.handle("get-download-stats", () =>
appState.downloadHistory.getStats()
);
ipcMain.handle("delete-history-item", (_, id) =>
appState.downloadHistory.removeHistoryItem(id)
);
ipcMain.handle("clear-all-history", async () => {
await appState.downloadHistory.clearHistory();
return true;
});
ipcMain.handle("export-history-json", () =>
appState.downloadHistory.exportAsJSON()
);
ipcMain.handle("export-history-csv", () =>
appState.downloadHistory.exportAsCSV()
);
}
function registerAutoUpdaterEvents() {
autoUpdater.on("update-available", async (info) => {
const dialogOpts = {
type: "info",
buttons: [i18n("update"), i18n("no")],
title: "Update Available",
message: i18n("updateAvailablePrompt"),
detail:
info.releaseNotes?.toString().replace(/<[^>]*>?/gm, "") ||
"No details available.",
};
const {response} = await dialog.showMessageBox(
appState.mainWindow,
dialogOpts
);
if (response === 0) {
autoUpdater.downloadUpdate();
}
});
autoUpdater.on("update-downloaded", async () => {
appState.mainWindow.webContents.send("update-downloaded", "");
const dialogOpts = {
type: "info",
buttons: [i18n("restart"), i18n("later")],
title: "Update Ready",
message: i18n("installAndRestartPrompt"),
};
const {response} = await dialog.showMessageBox(
appState.mainWindow,
dialogOpts
);
if (response === 0) {
autoUpdater.quitAndInstall();
}
});
autoUpdater.on("download-progress", async (info) => {
appState.mainWindow.webContents.send("download-progress", info.percent);
});
autoUpdater.on("error", (error) => {
console.error("Auto-update error:", error);
dialog.showErrorBox("Update Error", i18n("updateError"));
});
}
/**
* @param {string} phrase The key to translate.
* @returns {string} The translated string or the key itself.
*/
function i18n(phrase) {
return appState.loadedLanguage[phrase] || phrase;
}
/**
* Loads the configuration from the config file.
*/
async function loadConfiguration() {
try {
const fileContent = await fs.readFile(CONFIG_FILE_PATH, "utf8");
appState.config = JSON.parse(fileContent);
} catch (error) {
console.log(
"Could not load config file, using defaults.",
error.message
);
appState.config = {
bounds: {width: 1024, height: 768},
isMaximized: false,
};
}
}
async function saveConfiguration() {
try {
await fs.writeFile(CONFIG_FILE_PATH, JSON.stringify(appState.config));
} catch (error) {
console.error("Failed to save configuration:", error);
}
}
async function loadTranslations() {
const locale = app.getSystemLocale();
console.log({locale});
const defaultLangPath = path.join(__dirname, "translations", "en.json");
let langPath = path.join(__dirname, "translations", `${locale}.json`);
try {
await fs.access(langPath);
} catch {
langPath = defaultLangPath;
}
try {
const fileContent = await fs.readFile(langPath, "utf8");
appState.loadedLanguage = JSON.parse(fileContent);
} catch (error) {
console.error("Failed to load translation file:", error);
appState.loadedLanguage = {};
}
}
================================================
FILE: package.json
================================================
{
"dependencies": {
"electron-updater": "^6.6.2",
"systeminformation": "^5.25.11",
"yt-dlp-wrap-plus": "^2.4.3"
},
"name": "ytdownloader",
"version": "3.20.2",
"main": "main.js",
"scripts": {
"start": "electron .",
"dist": "electron-builder",
"debug": "electron --inspect=5858 .",
"windows": "electron-builder -w",
"linux": "electron-builder -l",
"mac": "electron-builder -m",
"gh-windows": "electron-builder -w --publish=always",
"gh-linux": "electron-builder -l --publish=always",
"gh-mac": "electron-builder -m --publish=always"
},
"author": {
"name": "Andrew",
"email": "aandrew.me@pm.me"
},
"publish": {
"provider": "github",
"owner": "aandrew-me",
"repo": "ytDownloader",
"private": false
},
"license": "GPL-3.0",
"description": "Download videos and audios from YouTube and many other sites",
"devDependencies": {
"electron": "^30.0.0",
"electron-builder": "^26.0.12",
"typescript": "^5.3.3"
},
"build": {
"productName": "YTDownloader",
"appId": "io.github.aandrew_me.ytdn",
"artifactName": "${productName}.${ext}",
"files": [
"./resources/**/*",
"main.js",
"./html/**/*",
"./resources/**/*",
"./public/**/*",
"package.json",
"./assets/**/*",
"./src/*.js",
"./ffmpeg/**/*",
"!ffmpeg.patch",
"translations",
"node*"
],
"electronLanguages": [
"en-US"
],
"mac": {
"category": "Utility",
"target": [
"zip",
"dmg"
],
"artifactName": "${productName}_Mac_${arch}.${ext}"
},
"dmg": {
"contents": [
{
"x": 130,
"y": 220
},
{
"x": 410,
"y": 220,
"type": "link",
"path": "/Applications"
}
],
"sign": false
},
"asar": false,
"directories": {
"buildResources": "resources",
"output": "release"
},
"linux": {
"target": [
"Appimage",
"snap",
"rpm",
"zip",
"deb"
],
"category": "Utility",
"artifactName": "${productName}_Linux.${ext}"
},
"snap": {
"grade": "stable",
"base": "core22"
},
"win": {
"target": [
"nsis",
"msi",
"zip"
],
"artifactName": "${productName}_Win.${ext}"
},
"nsis": {
"allowToChangeInstallationDirectory": true,
"oneClick": false,
"deleteAppDataOnUninstall": true
},
"msi": {
"oneClick": true,
"createDesktopShortcut": true,
"createStartMenuShortcut": true,
"shortcutName": "YTDownloader"
}
}
}
================================================
FILE: src/common.js
================================================
const electron = require("electron");
/**
*
* @param {string} id
* @returns {any}
*/
function getId(id) {
return document.getElementById(id);
}
getId("menuIcon").addEventListener("click", () => {
const menuDisplay = getId("menu").style.display;
if (menuDisplay != "none" && menuDisplay != "") {
getId("menuIcon").style.transform = "rotate(0deg)";
let count = 0;
let opacity = 1;
const fade = setInterval(() => {
if (count >= 10) {
getId("menu").style.display = "none";
clearInterval(fade);
} else {
opacity -= 0.1;
getId("menu").style.opacity = opacity.toFixed(3).toString();
count++;
}
}, 50);
} else {
getId("menuIcon").style.transform = "rotate(90deg)";
setTimeout(() => {
getId("menu").style.display = "flex";
getId("menu").style.opacity = "1";
}, 150);
}
});
getId("themeToggle").addEventListener("change", () => {
localStorage.setItem("theme", getId("themeToggle").value);
const x = window.innerWidth;
const y = 0;
const maxRadius = Math.hypot(window.innerWidth, window.innerHeight);
const transition = document.startViewTransition(() => {
document.documentElement.setAttribute("theme", getId("themeToggle").value);
});
transition.ready.then(() => {
document.documentElement.animate(
{
clipPath: [
`circle(0px at ${x}px ${y}px)`,
`circle(${maxRadius}px at ${x}px ${y}px)`
]
},
{
duration: 1100,
easing: 'cubic-bezier(0.4, 0, 0.2, 1)',
pseudoElement: '::view-transition-new(root)'
}
);
});
});
const storageTheme = localStorage.getItem("theme");
if (storageTheme) {
document.documentElement.setAttribute("theme", storageTheme);
getId("themeToggle").value = storageTheme;
} else {
getId("themeToggle").value = "frappe";
document.documentElement.setAttribute("theme", "frappe");
}
////
let advancedHidden = true;
function advancedToggle() {
if (advancedHidden) {
getId("advanced").style.display = "block";
advancedHidden = false;
} else {
getId("advanced").style.display = "none";
advancedHidden = true;
}
}
// Check scroll go to top
window.onscroll = function () {
scrollFunction();
};
function scrollFunction() {
if (
document.body.scrollTop > 50 ||
document.documentElement.scrollTop > 50
) {
getId("goToTop").style.display = "block";
} else {
getId("goToTop").style.display = "none";
}
}
// Function to scroll go to top
getId("goToTop").addEventListener("click", () => {
window.scrollTo({top: 0, behavior: "smooth"});
});
// Showing and hiding error details
function toggleErrorDetails() {
const status = getId("errorDetails").style.display;
if (status === "none") {
getId("errorDetails").style.display = "block";
// @ts-ignore
getId("errorBtn").textContent = i18n.__("errorDetails") + " ▲";
} else {
getId("errorDetails").style.display = "none";
// @ts-ignore
getId("errorBtn").textContent = i18n.__("errorDetails") + " ▼";
}
}
================================================
FILE: src/compressor.js
================================================
const {exec, execSync} = require("child_process");
const path = require("path");
const {ipcRenderer, shell} = require("electron");
const os = require("os");
const si = require("systeminformation");
const {existsSync} = require("fs");
document.addEventListener("translations-loaded", () => {
window.i18n.translatePage();
});
let menuIsOpen = false;
getId("menuIcon").addEventListener("click", () => {
if (menuIsOpen) {
getId("menuIcon").style.transform = "rotate(0deg)";
menuIsOpen = false;
let count = 0;
let opacity = 1;
const fade = setInterval(() => {
if (count >= 10) {
getId("menu").style.display = "none";
clearInterval(fade);
} else {
opacity -= 0.1;
getId("menu").style.opacity = opacity.toFixed(3).toString();
count++;
}
}, 50);
} else {
getId("menuIcon").style.transform = "rotate(90deg)";
menuIsOpen = true;
setTimeout(() => {
getId("menu").style.display = "flex";
getId("menu").style.opacity = "1";
}, 150);
}
});
const ffmpeg = `"${getFfmpegPath()}"`;
console.log(ffmpeg);
const vaapi_device = "/dev/dri/renderD128";
// Checking GPU
si.graphics().then((info) => {
console.log({gpuInfo: info});
const gpuDevices = info.controllers;
gpuDevices.forEach((gpu) => {
// NVIDIA
const gpuName = gpu.vendor.toLowerCase();
const gpuModel = gpu.model.toLowerCase();
if (gpuName.includes("nvidia") || gpuModel.includes("nvidia")) {
document.querySelectorAll(".nvidia_opt").forEach((opt) => {
opt.style.display = "block";
});
} else if (
gpuName.includes("advanced micro devices") ||
gpuModel.includes("amd")
) {
if (os.platform() == "win32") {
document.querySelectorAll(".amf_opt").forEach((opt) => {
opt.style.display = "block";
});
} else {
document.querySelectorAll(".vaapi_opt").forEach((opt) => {
opt.style.display = "block";
});
}
} else if (gpuName.includes("intel")) {
if (os.platform() == "win32") {
document.querySelectorAll(".qsv_opt").forEach((opt) => {
opt.style.display = "block";
});
} else if (os.platform() != "darwin") {
document.querySelectorAll(".vaapi_opt").forEach((opt) => {
opt.style.display = "block";
});
}
} else {
if (os.platform() == "darwin") {
document
.querySelectorAll(".videotoolbox_opt")
.forEach((opt) => {
opt.style.display = "block";
});
}
}
});
});
/** @type {File[]} */
let files = [];
let activeProcesses = new Set();
let currentItemId = "";
let isCancelled = false;
/**
* @param {string} id
*/
function getId(id) {
return document.getElementById(id);
}
// File Handling
const dropZone = document.querySelector(".drop-zone");
const fileInput = getId("fileInput");
const selectedFilesDiv = getId("selected-files");
dropZone.addEventListener("dragover", (e) => {
e.preventDefault();
dropZone.classList.add("dragover");
});
dropZone.addEventListener("dragleave", () => {
dropZone.classList.remove("dragover");
});
dropZone.addEventListener("drop", (e) => {
e.preventDefault();
dropZone.classList.remove("dragover");
// @ts-ignore
console.log(e.dataTransfer);
files = Array.from(e.dataTransfer.files);
updateSelectedFiles();
});
fileInput.addEventListener("change", (e) => {
// @ts-ignore
files = Array.from(e.target.files);
updateSelectedFiles();
});
getId("custom-folder-select").addEventListener("click", (e) => {
ipcRenderer.send("get-directory", "");
});
function updateSelectedFiles() {
const fileList = files
.map((f) => `${f.name} (${formatBytes(f.size)}) `)
.join("\n");
selectedFilesDiv.innerHTML = fileList || "No files selected";
}
// Compression Logic
getId("compress-btn").addEventListener("click", startCompression);
getId("cancel-btn").addEventListener("click", cancelCompression);
async function startCompression() {
if (files.length === 0) return alert("Please select files first!");
const settings = getEncoderSettings();
for (const file of files) {
const itemId =
"f" + Math.random().toFixed(10).toString().slice(2).toString();
currentItemId = itemId;
const outputPath = generateOutputPath(file, settings);
try {
await compressVideo(file, settings, itemId, outputPath);
if (isCancelled) {
isCancelled = false;
} else {
updateProgress("success", "", itemId);
const fileSavedElement = document.createElement("b");
fileSavedElement.textContent = i18n.__("fileSavedClickToOpen");
fileSavedElement.onclick = () => {
ipcRenderer.send("show-file", outputPath);
};
getId(itemId + "_prog").appendChild(fileSavedElement);
currentItemId = "";
}
} catch (error) {
const errorElement = document.createElement("div");
errorElement.onclick = () => {
ipcRenderer.send("error_dialog", error.message);
};
errorElement.textContent = i18n.__("errorClickForDetails");
updateProgress("error", "", itemId);
getId(itemId + "_prog").appendChild(errorElement);
currentItemId = "";
}
}
}
function cancelCompression() {
activeProcesses.forEach((child) => {
child.stdin.write("q");
isCancelled = true;
});
activeProcesses.clear();
updateProgress("error", "Cancelled", currentItemId);
}
/**
* @param {File} file
*/
function generateOutputPath(file, settings) {
console.log({settings});
const output_extension = settings.extension;
const parsed_file = path.parse(file.path);
let outputDir = settings.outputPath || parsed_file.dir;
if (output_extension == "unchanged") {
return path.join(
outputDir,
`${parsed_file.name}${settings.outputSuffix}${parsed_file.ext}`
);
}
return path.join(
outputDir,
`${parsed_file.name}_compressed.${output_extension}`
);
}
/**
* @param {File} file
* @param {{ encoder: any; speed: any; videoQuality: any; audioQuality?: any; audioFormat: string, extension: string }} settings
* @param {string} itemId
* @param {string} outputPath
*/
async function compressVideo(file, settings, itemId, outputPath) {
const command = buildFFmpegCommand(file, settings, outputPath);
console.log("Command: " + command);
return new Promise((resolve, reject) => {
const child = exec(command, (error) => {
if (error) reject(error);
else resolve();
});
activeProcesses.add(child);
child.on("exit", (_code) => {
activeProcesses.delete(child);
});
let video_info = {
duration: "",
bitrate: "",
};
createProgressItem(
path.basename(file.path),
"progress",
`Starting...`,
itemId
);
child.stderr.on("data", (data) => {
// console.log(data)
const duration_match = data.match(/Duration:\s*([\d:.]+)/);
if (duration_match) {
video_info.duration = duration_match[1];
}
// const bitrate_match = data.match(/bitrate:\s*([\d:.]+)/);
// if (bitrate_match) {
// // Bitrate in kb/s
// video_info.bitrate = bitrate_match[1];
// }
const progressTime = data.match(/time=(\d+:\d+:\d+\.\d+)/);
const totalSeconds = timeToSeconds(video_info.duration);
const currentSeconds =
progressTime && progressTime.length > 1
? timeToSeconds(progressTime[1])
: null;
if (currentSeconds && !isCancelled) {
const progress = Math.round(
(currentSeconds / totalSeconds) * 100
);
getId(
itemId + "_prog"
).innerHTML = ``;
}
});
});
}
/**
* @param {File} file
* @param {{ encoder: string; speed: string; videoQuality: string; audioQuality: string; audioFormat: string }} settings
* @param {string} outputPath
*/
function buildFFmpegCommand(file, settings, outputPath) {
const inputPath = file.path;
console.log("Output path: " + outputPath);
const args = ["-hide_banner", "-y", "-stats", "-i", `"${inputPath}"`];
switch (settings.encoder) {
case "copy":
args.push("-c:v", "copy");
break;
case "x264":
args.push(
"-c:v",
"libx264",
"-preset",
settings.speed,
"-vf",
"format=yuv420p",
"-crf",
parseInt(settings.videoQuality).toString()
);
break;
case "x265":
args.push(
"-c:v",
"libx265",
"-vf",
"format=yuv420p",
"-preset",
settings.speed,
"-crf",
parseInt(settings.videoQuality).toString()
);
break;
// Intel windows
case "qsv":
args.push(
"-c:v",
"h264_qsv",
"-vf",
"format=yuv420p",
"-preset",
settings.speed,
"-global_quality",
parseInt(settings.videoQuality).toString()
);
break;
// Linux amd and intel
case "vaapi":
args.push(
"-vaapi_device",
vaapi_device,
"-vf",
"format=nv12,hwupload",
"-c:v",
"h264_vaapi",
"-qp",
parseInt(settings.videoQuality).toString()
);
break;
case "hevc_vaapi":
args.push(
"-vaapi_device",
vaapi_device,
"-vf",
"format=nv12,hwupload",
"-c:v",
"hevc_vaapi",
"-qp",
parseInt(settings.videoQuality).toString()
);
break;
// Nvidia windows and linux
case "nvenc":
args.push(
"-c:v",
"h264_nvenc",
"-vf",
"format=yuv420p",
"-preset",
getNvencPreset(settings.speed),
"-rc",
"vbr",
"-cq",
parseInt(settings.videoQuality).toString()
);
break;
// Amd windows
case "hevc_amf":
let amf_hevc_quality = "balanced";
if (settings.speed == "slow") {
amf_hevc_quality = "quality";
} else if (settings.speed == "fast") {
amf_hevc_quality = "speed";
}
args.push(
"-c:v",
"hevc_amf",
"-vf",
"format=yuv420p",
"-quality",
amf_hevc_quality,
"-rc",
"cqp",
"-qp_i",
parseInt(settings.videoQuality).toString(),
"-qp_p",
parseInt(settings.videoQuality).toString()
);
break;
case "amf":
let amf_quality = "balanced";
if (settings.speed == "slow") {
amf_quality = "quality";
} else if (settings.speed == "fast") {
amf_quality = "speed";
}
args.push(
"-c:v",
"h264_amf",
"-vf",
"format=yuv420p",
"-quality",
amf_quality,
"-rc",
"cqp",
"-qp_i",
parseInt(settings.videoQuality).toString(),
"-qp_p",
parseInt(settings.videoQuality).toString(),
"-qp_b",
parseInt(settings.videoQuality).toString()
);
break;
case "videotoolbox":
args.push(
"-c:v",
"-vf",
"format=yuv420p",
"h264_videotoolbox",
"-q:v",
parseInt(settings.videoQuality).toString()
);
break;
}
// args.push("-vf", "scale=trunc(iw*1/2)*2:trunc(ih*1/2)*2,format=yuv420p");
args.push("-c:a", settings.audioFormat, `"${outputPath}"`);
return `${ffmpeg} ${args.join(" ")}`;
}
/**
*
* @returns {{ encoder: string; speed: string; videoQuality: string; audioQuality?: string; audioFormat: string, extension: string, outputPath:string }} settings
*/
function getEncoderSettings() {
return {
// @ts-ignore
encoder: getId("encoder").value,
// @ts-ignore
speed: getId("compression-speed").value,
// @ts-ignore
videoQuality: getId("video-quality").value,
// @ts-ignore
audioFormat: getId("audio-format").value,
// @ts-ignore
extension: getId("file_extension").value,
outputPath: getId("custom-folder-path").textContent,
// @ts-ignore
outputSuffix: getId("output-suffix").value,
};
}
/**
* @param {string | number} speed
*/
function getNvencPreset(speed) {
const presets = {fast: "p3", medium: "p4", slow: "p5"};
return presets[speed] || "p4";
}
/**
* @param {string} status
* @param {string} data
* @param {string} itemId
*/
function updateProgress(status, data, itemId) {
if (status == "success" || status == "error") {
const item = getId("itemId");
if (item) {
getId(itemId).classList.remove("progress");
getId(itemId).classList.add(status);
}
}
if (itemId) {
getId(itemId + "_prog").textContent = data;
}
}
/**
* @param {string} filename
* @param {string} status
* @param {string} data
* @param {string} itemId
*/
function createProgressItem(filename, status, data, itemId) {
const statusElement = getId("compression-status");
const newStatus = document.createElement("div");
newStatus.id = itemId;
newStatus.className = `status-item ${status}`;
const visibleFilename = filename.substring(0, 45);
newStatus.innerHTML = `
${visibleFilename}
${data}
`;
statusElement.append(newStatus);
}
/**
* @param {any} bytes
*/
function formatBytes(bytes) {
const units = ["B", "KB", "MB", "GB"];
let size = bytes;
let unitIndex = 0;
while (size >= 1024 && unitIndex < units.length - 1) {
size /= 1024;
unitIndex++;
}
return `${size.toFixed(2)} ${units[unitIndex]}`;
}
/**
* @param {string} timeStr
*/
function timeToSeconds(timeStr) {
if (!timeStr) {
return 0;
}
const [hh, mm, ss] = timeStr.split(":").map(parseFloat);
return hh * 3600 + mm * 60 + ss;
}
function getFfmpegPath() {
if (
process.env.YTDOWNLOADER_FFMPEG_PATH &&
existsSync(process.env.YTDOWNLOADER_FFMPEG_PATH)
) {
console.log("Using FFMPEG from YTDOWNLOADER_FFMPEG_PATH");
return process.env.YTDOWNLOADER_FFMPEG_PATH;
}
switch (os.platform()) {
case "win32":
return path.join(__dirname, "..", "ffmpeg", "bin", "ffmpeg.exe");
case "freebsd":
try {
return execSync("which ffmpeg").toString("utf8").trim();
} catch (error) {
console.error("ffmpeg not found on FreeBSD:", error);
return "";
}
default:
return path.join(__dirname, "..", "ffmpeg", "bin", "ffmpeg");
}
}
getId("themeToggle").addEventListener("change", () => {
document.documentElement.setAttribute("theme", getId("themeToggle").value);
localStorage.setItem("theme", getId("themeToggle").value);
});
getId("output-folder-input").addEventListener("change", (e) => {
const checked = e.target.checked;
if (!checked) {
getId("custom-folder-select").style.display = "block";
} else {
getId("custom-folder-select").style.display = "none";
getId("custom-folder-path").textContent = "";
getId("custom-folder-path").style.display = "none";
}
});
const storageTheme = localStorage.getItem("theme");
if (storageTheme) {
document.documentElement.setAttribute("theme", storageTheme);
getId("themeToggle").value = storageTheme;
} else {
document.documentElement.setAttribute("theme", "frappe");
getId("themeToggle").value = "frappe";
}
ipcRenderer.on("directory-path", (_event, msg) => {
let customFolderPathItem = getId("custom-folder-path");
customFolderPathItem.textContent = msg;
customFolderPathItem.style.display = "inline";
});
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);
}
// Menu
getId("preferenceWin").addEventListener("click", () => {
closeMenu();
menuIsOpen = false;
ipcRenderer.send("load-page", __dirname + "/preferences.html");
});
getId("playlistWin").addEventListener("click", () => {
closeMenu();
menuIsOpen = false;
ipcRenderer.send("load-win", __dirname + "/playlist.html");
});
getId("aboutWin").addEventListener("click", () => {
closeMenu();
menuIsOpen = false;
ipcRenderer.send("load-page", __dirname + "/about.html");
});
getId("historyWin").addEventListener("click", () => {
closeMenu();
menuIsOpen = false;
ipcRenderer.send("load-page", __dirname + "/history.html");
});
getId("homeWin").addEventListener("click", () => {
closeMenu();
menuIsOpen = false;
ipcRenderer.send("load-win", __dirname + "/index.html");
});
================================================
FILE: src/history.js
================================================
/**
* Download History Manager
*/
const fs = require("fs");
const path = require("path");
const crypto = require("crypto");
const {app} = require("electron");
class DownloadHistory {
constructor() {
this.historyFile = path.join(
app.getPath("userData"),
"download_history.json"
);
this.maxHistoryItems = 800;
this.history = [];
this.initialized = this._loadHistory().then((history) => {
this.history = history;
});
}
_generateUniqueId() {
return crypto.randomUUID();
}
async _loadHistory() {
try {
if (fs.existsSync(this.historyFile)) {
const data = await fs.promises.readFile(
this.historyFile,
"utf8"
);
return JSON.parse(data) || [];
}
} catch (error) {
console.error("Error loading history:", error);
}
return [];
}
async _saveHistory() {
try {
await fs.promises.writeFile(
this.historyFile,
JSON.stringify(this.history, null, 2)
);
} catch (error) {
console.error("Error saving history:", error);
}
}
async addDownload(downloadInfo) {
await this.initialized;
const historyItem = {
id: this._generateUniqueId(),
title: downloadInfo.title || "Unknown",
url: downloadInfo.url || "",
filename: downloadInfo.filename || "",
filePath: downloadInfo.filePath || "",
fileSize: downloadInfo.fileSize || 0,
format: downloadInfo.format || "unknown",
thumbnail: downloadInfo.thumbnail || "",
duration: downloadInfo.duration || 0,
downloadDate: new Date().toISOString(),
timestamp: Date.now(),
};
// Add to beginning for most recent first
this.history.unshift(historyItem);
// Keep only recent items
if (this.history.length > this.maxHistoryItems) {
this.history = this.history.slice(0, this.maxHistoryItems);
}
await this._saveHistory();
return historyItem;
}
async getHistory() {
await this.initialized;
return this.history;
}
async getFilteredHistory(options = {}) {
await this.initialized;
let filtered = [...this.history];
if (options.format) {
filtered = filtered.filter(
(item) =>
item.format.toLowerCase() === options.format.toLowerCase()
);
}
if (options.searchTerm) {
const term = options.searchTerm.toLowerCase();
filtered = filtered.filter(
(item) =>
item.title.toLowerCase().includes(term) ||
item.url.toLowerCase().includes(term)
);
}
if (options.limit) {
filtered = filtered.slice(0, options.limit);
}
return filtered;
}
async getHistoryItem(id) {
await this.initialized;
return this.history.find((item) => item.id === id) || null;
}
async removeHistoryItem(id) {
await this.initialized;
const index = this.history.findIndex((item) => item.id === id);
if (index !== -1) {
this.history.splice(index, 1);
await this._saveHistory();
return true;
}
return false;
}
async clearHistory() {
await this.initialized;
this.history = [];
await this._saveHistory();
}
async getStats() {
await this.initialized;
const stats = {
totalDownloads: this.history.length,
totalSize: 0,
byFormat: {},
oldestDownload: null,
newestDownload: null,
};
this.history.forEach((item) => {
stats.totalSize += item.fileSize || 0;
const fmt = item.format.toLowerCase();
stats.byFormat[fmt] = (stats.byFormat[fmt] || 0) + 1;
});
if (this.history.length > 0) {
stats.newestDownload = this.history[0];
stats.oldestDownload = this.history[this.history.length - 1];
}
return stats;
}
async exportAsJSON() {
await this.initialized;
return JSON.stringify(this.history, null, 2);
}
_sanitizeCSVField(value) {
if (value == null) {
value = "";
}
const stringValue = String(value);
let sanitized = stringValue.replace(/"/g, '""');
const dangerousChars = ["=", "+", "-", "@"];
if (sanitized.length > 0 && dangerousChars.includes(sanitized[0])) {
sanitized = "'" + sanitized;
}
return `"${sanitized}"`;
}
async exportAsCSV() {
await this.initialized;
if (this.history.length === 0) return "No history to export\n";
const headers = [
"Title",
"URL",
"Filename",
"Format",
"File Size (bytes)",
"Download Date",
];
const rows = this.history.map((item) => [
this._sanitizeCSVField(item.title),
this._sanitizeCSVField(item.url),
this._sanitizeCSVField(item.filename),
this._sanitizeCSVField(item.format),
this._sanitizeCSVField(item.fileSize),
this._sanitizeCSVField(item.downloadDate),
]);
return (
headers.join(",") +
"\n" +
rows.map((row) => row.join(",")).join("\n")
);
}
}
module.exports = DownloadHistory;
================================================
FILE: src/index.js
================================================
const videoToggle = getId("videoToggle");
const audioToggle = getId("audioToggle");
const incorrectMsg = getId("incorrectMsg");
const loadingMsg = getId("loadingWrapper");
function getId(id) {
return document.getElementById(id);
}
// Video and audio toggle
videoToggle.addEventListener("click", (event) => {
selectVideo()
});
audioToggle.addEventListener("click", (event) => {
selectAudio()
});
/////////////
function selectVideo(){
localStorage.setItem("defaultWindow", "video")
videoToggle.style.backgroundColor = "var(--box-toggleOn)";
audioToggle.style.backgroundColor = "var(--box-toggle)";
getId("audioList").style.display = "none";
getId("audioExtract").style.display = "none";
getId("videoList").style.display = "block";
}
function selectAudio(){
localStorage.setItem("defaultWindow", "audio")
audioToggle.style.backgroundColor = "var(--box-toggleOn)";
videoToggle.style.backgroundColor = "var(--box-toggle)";
getId("videoList").style.display = "none";
getId("audioList").style.display = "block";
getId("audioExtract").style.display = "block";
}
================================================
FILE: src/playlist.js
================================================
const {clipboard, 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");
const playlistDownloader = {
// State and config
state: {
url: null,
downloadDir: null,
ytDlpPath: null,
ytDlpWrap: null,
ffmpegPath: null,
jsRuntimePath: null,
playlistName: "",
originalCount: 0,
currentDownloadProcess: null,
},
config: {
foldernameFormat: "%(playlist_title)s",
filenameFormat: "%(playlist_index)s.%(title)s.%(ext)s",
proxy: "",
cookie: {
browser: "",
arg: "",
},
configFile: {
arg: "",
path: "",
},
playlistRange: {
start: 1,
end: "",
},
},
// DOM elements
ui: {
pasteLinkBtn: document.getElementById("pasteLink"),
linkDisplay: document.getElementById("link"),
optionsContainer: document.getElementById("options"),
downloadList: document.getElementById("list"),
downloadVideoBtn: document.getElementById("download"),
downloadAudioBtn: document.getElementById("audioDownload"),
downloadThumbnailsBtn: document.getElementById("downloadThumbnails"),
saveLinksBtn: document.getElementById("saveLinks"),
selectLocationBtn: document.getElementById("selectLocation"),
pathDisplay: document.getElementById("path"),
openDownloadsBtn: document.getElementById("openDownloads"),
videoToggle: document.getElementById("videoToggle"),
audioToggle: document.getElementById("audioToggle"),
advancedToggle: document.getElementById("advancedToggle"),
videoBox: document.getElementById("videoBox"),
audioBox: document.getElementById("audioBox"),
videoQualitySelect: document.getElementById("select"),
videoTypeSelect: document.getElementById("videoTypeSelect"),
typeSelectBox: document.getElementById("typeSelectBox"),
audioTypeSelect: document.getElementById("audioSelect"),
audioQualitySelect: document.getElementById("audioQualitySelect"),
advancedMenu: document.getElementById("advancedMenu"),
playlistIndexInput: document.getElementById("playlistIndex"),
playlistEndInput: document.getElementById("playlistEnd"),
subtitlesCheckbox: document.getElementById("subChecked"),
closeHiddenBtn: document.getElementById("closeHidden"),
playlistNameDisplay: document.getElementById("playlistName"),
errorMsgDisplay: document.getElementById("incorrectMsgPlaylist"),
errorBtn: document.getElementById("errorBtn"),
errorDetails: document.getElementById("errorDetails"),
menuIcon: document.getElementById("menuIcon"),
menu: document.getElementById("menu"),
preferenceWinBtn: document.getElementById("preferenceWin"),
aboutWinBtn: document.getElementById("aboutWin"),
historyWinBtn: document.getElementById("historyWin"),
homeWinBtn: document.getElementById("homeWin"),
compressorWinBtn: document.getElementById("compressorWin"),
},
init() {
this.loadInitialConfig();
this.initEventListeners();
// Set initial UI state
this.ui.pathDisplay.textContent = this.state.downloadDir;
this.ui.videoToggle.style.backgroundColor = "var(--box-toggleOn)";
this.updateVideoTypeVisibility();
// Load translations when ready
document.addEventListener("translations-loaded", () => {
window.i18n.translatePage();
});
console.log(`yt-dlp path: ${this.state.ytDlpPath}`);
console.log(`ffmpeg path: ${this.state.ffmpegPath}`);
},
loadInitialConfig() {
// yt-dlp path
this.state.ytDlpPath = localStorage.getItem("ytdlp");
this.state.ytDlpWrap = new YTDlpWrap(`"${this.state.ytDlpPath}"`);
const defaultDownloadsDir = path.join(os.homedir(), "Downloads");
let preferredDir =
localStorage.getItem("downloadPath") || defaultDownloadsDir;
try {
fs.accessSync(preferredDir, constants.W_OK);
this.state.downloadDir = preferredDir;
} catch (err) {
console.error(
"Unable to write to preferred download directory. Reverting to default.",
err
);
this.state.downloadDir = defaultDownloadsDir;
localStorage.setItem("downloadPath", defaultDownloadsDir);
}
// ffmpeg and js runtime path setup
this.state.ffmpegPath = this.getFfmpegPath();
this.state.jsRuntimePath = this.getJsRuntimePath();
if (localStorage.getItem("preferredVideoQuality")) {
this.ui.videoQualitySelect.value = localStorage.getItem(
"preferredVideoQuality"
);
}
if (localStorage.getItem("preferredAudioQuality")) {
this.ui.audioQualitySelect.value = localStorage.getItem(
"preferredAudioQuality"
);
}
},
initEventListeners() {
this.ui.pasteLinkBtn.addEventListener("click", () => this.pasteLink());
document.addEventListener("keydown", (event) => {
if (
(event.ctrlKey && event.key === "v") ||
(event.metaKey &&
event.key === "v" &&
os.platform() === "darwin" &&
document.activeElement.tagName !== "INPUT" &&
document.activeElement.tagName !== "TEXTAREA")
) {
this.pasteLink();
}
});
this.ui.downloadVideoBtn.addEventListener("click", () =>
this.startDownload("video")
);
this.ui.downloadAudioBtn.addEventListener("click", () =>
this.startDownload("audio")
);
this.ui.downloadThumbnailsBtn.addEventListener("click", () =>
this.startDownload("thumbnails")
);
this.ui.saveLinksBtn.addEventListener("click", () =>
this.startDownload("links")
);
this.ui.videoToggle.addEventListener("click", () =>
this.toggleDownloadType("video")
);
this.ui.audioToggle.addEventListener("click", () =>
this.toggleDownloadType("audio")
);
this.ui.advancedToggle.addEventListener("click", () =>
this.toggleAdvancedMenu()
);
this.ui.videoQualitySelect.addEventListener("change", () =>
this.updateVideoTypeVisibility()
);
this.ui.selectLocationBtn.addEventListener("click", () =>
ipcRenderer.send("select-location-main", "")
);
this.ui.openDownloadsBtn.addEventListener("click", () =>
this.openDownloadsFolder()
);
this.ui.closeHiddenBtn.addEventListener("click", () =>
this.hideOptions(true)
);
this.ui.preferenceWinBtn.addEventListener("click", () =>
this.navigate("page", "/preferences.html")
);
this.ui.aboutWinBtn.addEventListener("click", () =>
this.navigate("page", "/about.html")
);
this.ui.historyWinBtn.addEventListener("click", () =>
this.navigate("page", "/history.html")
);
this.ui.homeWinBtn.addEventListener("click", () =>
this.navigate("win", "/index.html")
);
this.ui.compressorWinBtn.addEventListener("click", () =>
this.navigate("win", "/compressor.html")
);
ipcRenderer.on("downloadPath", (_event, downloadPath) => {
if (downloadPath && downloadPath[0]) {
this.ui.pathDisplay.textContent = downloadPath[0];
this.state.downloadDir = downloadPath[0];
}
});
},
startDownload(type) {
if (!this.state.url) {
this.showError("URL is missing. Please paste a link first.");
return;
}
this.updateDynamicConfig();
this.hideOptions();
const controller = new AbortController();
const baseArgs = this.buildBaseArgs();
let specificArgs = [];
switch (type) {
case "video":
specificArgs = this.getVideoArgs();
break;
case "audio":
specificArgs = this.getAudioArgs();
break;
case "thumbnails":
specificArgs = this.getThumbnailArgs();
break;
case "links":
specificArgs = this.getLinkArgs();
break;
}
const allArgs = [
...baseArgs,
...specificArgs,
`"${this.state.url}"`,
].filter(Boolean);
console.log(`Command: ${this.state.ytDlpPath}`, allArgs.join(" "));
this.state.currentDownloadProcess = this.state.ytDlpWrap.exec(
allArgs,
{shell: true, detached: false},
controller.signal
);
this.handleDownloadEvents(this.state.currentDownloadProcess, type);
},
buildBaseArgs() {
const {start, end} = this.config.playlistRange;
const outputPath = `"${path.join(
this.state.downloadDir,
this.config.foldernameFormat,
this.config.filenameFormat
)}"`;
return [
"--yes-playlist",
"-o",
outputPath,
"-I",
`"${start}:${end}"`,
"--ffmpeg-location",
`"${this.state.ffmpegPath}"`,
...(this.state.jsRuntimePath
? ["--no-js-runtimes", "--js-runtime", this.state.jsRuntimePath]
: []),
this.config.cookie.arg,
this.config.cookie.browser,
this.config.configFile.arg,
this.config.configFile.path,
...(this.config.proxy
? ["--no-check-certificate", "--proxy", this.config.proxy]
: []),
"--compat-options",
"no-youtube-unavailable-videos",
].filter(Boolean);
},
getVideoArgs() {
const quality = this.ui.videoQualitySelect.value;
const videoType = this.ui.videoTypeSelect.value;
let formatArgs = [];
if (quality === "best") {
formatArgs = ["-f", "bv*+ba/best"];
} else if (quality === "worst") {
formatArgs = ["-f", "wv+wa/worst"];
} else if (quality === "useConfig") {
formatArgs = [];
} else {
if (videoType === "mp4") {
formatArgs = [
"-f",
`"bestvideo[height<=${quality}]+bestaudio[ext=m4a]/best[height<=${quality}]/best"`,
"--merge-output-format",
"mp4",
"--recode-video",
"mp4",
];
} else if (videoType === "webm") {
formatArgs = [
"-f",
`"bestvideo[height<=${quality}]+bestaudio[ext=webm]/best[height<=${quality}]/best"`,
"--merge-output-format",
"webm",
"--recode-video",
"webm",
];
} else {
formatArgs = [
"-f",
`"bv*[height=${quality}]+ba/best[height=${quality}]/best[height<=${quality}]"`,
];
}
}
const isYouTube =
this.state.url.includes("youtube.com/") ||
this.state.url.includes("youtu.be/");
const canEmbedThumb = os.platform() !== "darwin";
return [
...formatArgs,
"--embed-metadata",
this.ui.subtitlesCheckbox.checked ? "--write-subs" : "",
this.ui.subtitlesCheckbox.checked ? "--sub-langs" : "",
this.ui.subtitlesCheckbox.checked ? "all" : "",
videoType === "mp4" && isYouTube && canEmbedThumb
? "--embed-thumbnail"
: "",
].filter(Boolean);
},
getAudioArgs() {
const format = this.ui.audioTypeSelect.value;
const quality = this.ui.audioQualitySelect.value;
const isYouTube =
this.state.url.includes("youtube.com/") ||
this.state.url.includes("youtu.be/");
const canEmbedThumb = os.platform() !== "darwin";
if (isYouTube && format === "m4a" && quality === "auto") {
return [
"-f",
`ba[ext=${format}]/ba`,
"--embed-metadata",
canEmbedThumb ? "--embed-thumbnail" : "",
];
}
return [
"-x",
"--audio-format",
format,
"--audio-quality",
quality,
"--embed-metadata",
(format === "mp3" || (format === "m4a" && isYouTube)) &&
canEmbedThumb
? "--embed-thumbnail"
: "",
];
},
getThumbnailArgs() {
return [
"--write-thumbnail",
"--convert-thumbnails",
"png",
"--skip-download",
];
},
getLinkArgs() {
const linksFilePath = `"${path.join(
this.state.downloadDir,
this.config.foldernameFormat,
"links.txt"
)}"`;
return [
"--skip-download",
"--print-to-file",
"webpage_url",
linksFilePath,
];
},
// yt-dlp event handling
handleDownloadEvents(process, type) {
let count = 0;
process.on("ytDlpEvent", (_eventType, eventData) => {
const playlistTxt = "Downloading playlist: ";
if (eventData.includes(playlistTxt)) {
this.state.playlistName = eventData
.split(playlistTxt)[1]
.trim();
this.state.playlistName = this.state.playlistName
.replaceAll("|", "|")
.replaceAll(`"`, `"`)
.replaceAll("*", "*")
.replaceAll("/", "⧸")
.replaceAll("\\", "⧹")
.replaceAll(":", ":")
.replaceAll("?", "?");
if (
os.platform() === "win32" &&
this.state.playlistName.endsWith(".")
) {
this.state.playlistName =
this.state.playlistName.slice(0, -1) + "#";
}
this.ui.playlistNameDisplay.textContent = `${window.i18n.__(
"downloadingPlaylist"
)} ${this.state.playlistName}`;
}
const videoIndexTxt = "Downloading item ";
const oldVideoIndexTxt = "Downloading video ";
if (
(eventData.includes(videoIndexTxt) ||
eventData.includes(oldVideoIndexTxt)) &&
!eventData.includes("thumbnail")
) {
count++;
this.state.originalCount++;
this.updatePlaylistUI(count, type);
}
});
process.on("progress", (progress) => {
const progressElement = document.getElementById(`p${count}`);
if (!progressElement) return;
if (progress.percent === 100) {
progressElement.textContent = `${window.i18n.__(
"processing"
)}...`;
} else {
progressElement.textContent = `${window.i18n.__("progress")} ${
progress.percent
}% | ${window.i18n.__("speed")} ${
progress.currentSpeed || "N/A"
}`;
}
});
process.on("error", (error) => this.showError(error));
process.on("close", () => this.finishDownload(count));
},
pasteLink() {
this.state.url = clipboard.readText();
this.ui.linkDisplay.textContent = ` ${this.state.url}`;
this.ui.optionsContainer.style.display = "block";
this.ui.errorMsgDisplay.textContent = "";
this.ui.errorBtn.style.display = "none";
},
updatePlaylistUI(count, type) {
let itemTitle = "";
switch (type) {
case "thumbnails":
itemTitle = `${window.i18n.__("thumbnail")} ${
this.state.originalCount
}`;
break;
case "links":
itemTitle = `${window.i18n.__("link")} ${
this.state.originalCount
}`;
break;
default:
itemTitle = `${window.i18n.__(type)} ${
this.state.originalCount
}`;
}
if (count > 1) {
const prevProgress = document.getElementById(`p${count - 1}`);
if (prevProgress)
prevProgress.textContent = window.i18n.__("fileSaved");
}
const itemHTML = `
${itemTitle}
${window.i18n.__(
"downloading"
)}
`;
this.ui.downloadList.innerHTML += itemHTML;
window.scrollTo(0, document.body.scrollHeight);
},
updateDynamicConfig() {
// Naming formats from localStorage
this.config.foldernameFormat =
localStorage.getItem("foldernameFormat") || "%(playlist_title)s";
this.config.filenameFormat =
localStorage.getItem("filenameFormat") ||
"%(playlist_index)s.%(title)s.%(ext)s";
// Proxy, cookies, config file
this.config.proxy = localStorage.getItem("proxy") || "";
this.config.cookie.browser = localStorage.getItem("browser") || "";
this.config.cookie.arg = this.config.cookie.browser
? "--cookies-from-browser"
: "";
const configPath = localStorage.getItem("configPath");
this.config.configFile.path = configPath ? `"${configPath}"` : "";
this.config.configFile.arg = configPath ? "--config-location" : "";
// Playlist range from UI inputs
this.config.playlistRange.start =
Number(this.ui.playlistIndexInput.value) || 1;
this.config.playlistRange.end = this.ui.playlistEndInput.value || "";
this.state.originalCount =
this.config.playlistRange.start > 1
? this.config.playlistRange.start - 1
: 0;
// Reset playlist name for new download
this.state.playlistName = "";
},
hideOptions(justHide = false) {
this.ui.optionsContainer.style.display = "none";
this.ui.downloadList.innerHTML = "";
this.ui.errorBtn.style.display = "none";
this.ui.errorDetails.style.display = "none";
this.ui.errorDetails.textContent = "";
this.ui.errorMsgDisplay.style.display = "none";
if (!justHide) {
this.ui.playlistNameDisplay.textContent = `${window.i18n.__(
"processing"
)}...`;
this.ui.pasteLinkBtn.style.display = "none";
this.ui.openDownloadsBtn.style.display = "inline-block";
}
},
finishDownload(count) {
const lastProgress = document.getElementById(`p${count}`);
if (lastProgress)
lastProgress.textContent = window.i18n.__("fileSaved");
this.ui.pasteLinkBtn.style.display = "inline-block";
this.ui.openDownloadsBtn.style.display = "inline-block";
const notify = new Notification("ytDownloader", {
body: window.i18n.__("playlistDownloaded"),
icon: "../assets/images/icon.png",
});
notify.onclick = () => this.openDownloadsFolder();
},
showError(error) {
console.error("Download Error:", error.toString());
this.ui.pasteLinkBtn.style.display = "inline-block";
this.ui.openDownloadsBtn.style.display = "none";
this.ui.optionsContainer.style.display = "block";
this.ui.playlistNameDisplay.textContent = "";
this.ui.errorMsgDisplay.textContent =
window.i18n.__("errorNetworkOrUrl");
this.ui.errorMsgDisplay.style.display = "block";
this.ui.errorMsgDisplay.title = error.toString();
this.ui.errorBtn.style.display = "inline-block";
this.ui.errorDetails.innerHTML = `URL: ${
this.state.url
} ${error.toString()}`;
// this.ui.errorDetails.title = window.i18n.__("clickToCopy");
},
openDownloadsFolder() {
const openPath =
this.state.playlistName &&
fs.existsSync(
path.join(this.state.downloadDir, this.state.playlistName)
)
? path.join(this.state.downloadDir, this.state.playlistName)
: this.state.downloadDir;
ipcRenderer.invoke("open-folder", openPath).then((result) => {
if (!result.success) {
ipcRenderer.invoke("open-folder", this.state.downloadDir);
}
});
},
toggleDownloadType(type) {
const isVideo = type === "video";
this.ui.videoToggle.style.backgroundColor = isVideo
? "var(--box-toggleOn)"
: "var(--box-toggle)";
this.ui.audioToggle.style.backgroundColor = isVideo
? "var(--box-toggle)"
: "var(--box-toggleOn)";
this.ui.videoBox.style.display = isVideo ? "block" : "none";
this.ui.audioBox.style.display = isVideo ? "none" : "block";
},
updateVideoTypeVisibility() {
const value = this.ui.videoQualitySelect.value;
const show = !["best", "worst", "useConfig"].includes(value);
this.ui.typeSelectBox.style.display = show ? "block" : "none";
},
toggleAdvancedMenu() {
const isHidden =
this.ui.advancedMenu.style.display === "none" ||
this.ui.advancedMenu.style.display === "";
this.ui.advancedMenu.style.display = isHidden ? "block" : "none";
},
closeMenu() {
this.ui.menuIcon.style.transform = "rotate(0deg)";
this.ui.menu.style.opacity = "0";
setTimeout(() => {
this.ui.menu.style.display = "none";
}, 300);
},
navigate(type, page) {
this.closeMenu();
const event = type === "page" ? "load-page" : "load-win";
ipcRenderer.send(event, path.join(__dirname, page));
},
getFfmpegPath() {
if (
process.env.YTDOWNLOADER_FFMPEG_PATH &&
fs.existsSync(process.env.YTDOWNLOADER_FFMPEG_PATH)
) {
console.log("Using FFMPEG from YTDOWNLOADER_FFMPEG_PATH");
return process.env.YTDOWNLOADER_FFMPEG_PATH;
}
switch (os.platform()) {
case "win32":
return path.join(__dirname, "..", "ffmpeg", "bin");
case "freebsd":
try {
return execSync("which ffmpeg").toString("utf8").trim();
} catch (error) {
console.error("ffmpeg not found on FreeBSD:", error);
return "";
}
default:
return path.join(__dirname, "..", "ffmpeg", "bin");
}
},
getJsRuntimePath() {
{
const exeName = "node";
if (process.env.YTDOWNLOADER_NODE_PATH) {
if (fs.existsSync(process.env.YTDOWNLOADER_NODE_PATH)) {
return `$node:"${process.env.YTDOWNLOADER_NODE_PATH}"`;
}
return "";
}
if (process.env.YTDOWNLOADER_DENO_PATH) {
if (fs.existsSync(process.env.YTDOWNLOADER_DENO_PATH)) {
return `$deno:"${process.env.YTDOWNLOADER_DENO_PATH}"`;
}
return "";
}
if (os.platform() === "darwin") {
return "";
}
let jsRuntimePath = path.join(__dirname, "..", exeName);
if (os.platform() === "win32") {
jsRuntimePath = path.join(__dirname, "..", `${exeName}.exe`);
}
if (fs.existsSync(jsRuntimePath)) {
return `${exeName}:"${jsRuntimePath}"`;
} else {
return "";
}
}
},
};
playlistDownloader.init();
================================================
FILE: src/playlist_new.js
================================================
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, exec, spawnSync } = require("child_process");
let url;
const ytDlp = localStorage.getItem("ytdlp");
const ytdlp = new YTDlpWrap(ytDlp);
const downloadDir = localStorage.getItem("downloadPath");
const i18n = new (require("../translations/i18n"))();
let cookieArg = "";
let browser = "";
const formats = {
144: 160,
240: 133,
360: 134,
480: 135,
720: 136,
1080: 137,
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 {
ffmpeg = execSync("which ffmpeg", { encoding: "utf8" });
ffmpeg = `"${ffmpeg.trimEnd()}"`;
} catch (error) {
console.log(error);
}
}
console.log("ffmpeg:", ffmpeg);
let foldernameFormat = "%(playlist_title)s";
let filenameFormat = "%(playlist_index)s.%(title)s.%(ext)s";
let playlistIndex = 1;
let playlistEnd = "";
function getId(id) {
return document.getElementById(id);
}
function pasteLink() {
const clipboardText = clipboard.readText();
getId("loadingWrapper").style.display = "flex";
getId("incorrectMsg").textContent = "";
getId("errorBtn").style.display = "none";
getId("errorDetails").style.display = "none";
getId("errorDetails").textContent = "";
exec(
`${ytDlp} --yes-playlist --no-warnings -J --flat-playlist "${clipboardText}"`,
(error, stdout, stderr) => {
if (error) {
getId("loadingWrapper").style.display = "none";
getId("incorrectMsg").textContent = i18n.__(
"Some error has occurred. Check your network and use correct URL"
);
getId("errorDetails").innerHTML = `
URL: ${clipboardText}
${error}
`;
getId("errorDetails").title = i18n.__("Click to copy");
getId("errorBtn").style.display = "inline-block";
} else {
const parsed = JSON.parse(stdout);
console.log(parsed);
let items = "";
// If correct playlist url is used
if (parsed.entries) {
parsed.entries.forEach((entry) => {
console.log(entry)
const randId = Math.random()
.toFixed(10)
.toString()
.slice(2);
if (entry.channel) {
items += `
`;
}
});
getId("data").innerHTML = items;
getId("loadingWrapper").style.display = "none";
}
// If correct playlist url is not used
else {
getId("loadingWrapper").style.display = "none";
getId("incorrectMsg").textContent = i18n.__(
"Incompatible URL. Please provide a playlist URL"
);
getId("errorDetails").innerHTML = `
URL: ${clipboardText}
${error}
`;
getId("errorDetails").title = i18n.__("Click to copy");
getId("errorBtn").style.display = "inline-block";
}
}
}
);
}
getId("pasteLink").addEventListener("click", (e) => {
pasteLink();
});
document.addEventListener("keydown", (event) => {
if (event.ctrlKey && event.key == "v") {
pasteLink();
}
});
function formatTime(seconds) {
let hours = Math.floor(seconds / 3600);
let minutes = Math.floor((seconds - hours * 3600) / 60);
seconds = seconds - hours * 3600 - minutes * 60;
let formattedTime = "";
if (hours > 0) {
formattedTime += hours + ":";
}
if (minutes < 10 && hours > 0) {
formattedTime += "0";
}
formattedTime += minutes + ":";
if (seconds < 10) {
formattedTime += "0";
}
formattedTime += seconds;
return formattedTime;
}
function closeMenu() {
getId("menuIcon").style.transform = "rotate(0deg)";
menuIsOpen = false;
let count = 0;
let opacity = 1;
const fade = setInterval(() => {
if (count >= 10) {
clearInterval(fade);
} else {
opacity -= 0.1;
getId("menu").style.opacity = opacity;
count++;
}
}, 50);
}
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("historyWin").addEventListener("click", () => {
closeMenu();
ipcRenderer.send("load-page", __dirname + "/history.html");
});
getId("homeWin").addEventListener("click", () => {
closeMenu();
ipcRenderer.send("load-win", __dirname + "/index.html");
});
================================================
FILE: src/preferences.js
================================================
const {ipcRenderer, shell} = require("electron");
const {accessSync, constants} = require("original-fs");
const {join} = require("path");
const {homedir} = require("os");
const storageTheme = localStorage.getItem("theme");
if (storageTheme) {
document.documentElement.setAttribute("theme", storageTheme);
} else {
document.documentElement.setAttribute("theme", "frappe");
}
let rightToLeft = "false";
if (localStorage.getItem("rightToLeft")) {
rightToLeft = localStorage.getItem("rightToLeft");
}
if (rightToLeft == "true") {
document
.querySelectorAll(".prefBox")
.forEach((/** @type {HTMLElement} */ item) => {
item.style.flexDirection = "row-reverse";
});
} else {
console.log("Change to left to right");
document
.querySelectorAll(".prefBox")
.forEach((/** @type {HTMLElement} */ item) => {
item.style.flexDirection = "row";
});
}
// Download path
let downloadPath = localStorage.getItem("downloadPath");
if (!downloadPath) {
downloadPath = join(homedir(), "Downloads");
}
getId("path").textContent = downloadPath;
/**
*
* @param {string} id
* @returns {any}
*/
function getId(id) {
return document.getElementById(id);
}
document.addEventListener("translations-loaded", () => {
window.i18n.translatePage();
document.title = window.i18n.__("preferences");
if (process.env.FLATPAK_ID) {
getId("flatpakTxt").addEventListener("click", () => {
shell.openExternal(
"https://flathub.org/apps/com.github.tchx84.Flatseal"
);
});
getId("flatpakTxt").style.display = "block";
}
});
getId("back").addEventListener("click", () => {
ipcRenderer.send("close-secondary");
});
// Selecting download directory
getId("selectLocation").addEventListener("click", () => {
ipcRenderer.send("select-location-secondary", "");
});
ipcRenderer.on("downloadPath", (_event, downloadPath) => {
try {
accessSync(downloadPath[0], constants.W_OK);
console.log(downloadPath[0]);
localStorage.setItem("downloadPath", downloadPath[0]);
getId("path").textContent = downloadPath[0];
} catch (error) {
showPopup(i18n.__("unableToAccessDir"), true);
}
});
// Selecting config directory
getId("configBtn").addEventListener("click", () => {
ipcRenderer.send("select-config", "");
});
ipcRenderer.on("configPath", (event, configPath) => {
console.log(configPath);
localStorage.setItem("configPath", configPath);
getId("configPath").textContent = configPath;
});
const configCheck = getId("configCheck");
configCheck.addEventListener("change", (event) => {
if (configCheck.checked) {
getId("configOpts").style.display = "flex";
} else {
getId("configOpts").style.display = "none";
localStorage.setItem("configPath", "");
}
});
const configPath = localStorage.getItem("configPath");
if (configPath) {
getId("configPath").textContent = configPath;
configCheck.checked = true;
getId("configOpts").style.display = "flex";
}
// Language settings
const language = localStorage.getItem("locale");
if (language) {
if (language.startsWith("en")) {
getId("select").value = "en";
} else {
getId("select").value = language;
}
}
function changeLanguage() {
const language = getId("select").value;
localStorage.setItem("locale", language);
if (language === "fa" || language === "ar") {
rightToLeft = "true";
localStorage.setItem("rightToLeft", "true");
} else {
rightToLeft = "false";
localStorage.setItem("rightToLeft", "false");
}
}
// Browser preferences
let browser = localStorage.getItem("browser");
if (browser) {
getId("browser").value = browser;
}
getId("browser").addEventListener("change", () => {
browser = getId("browser").value;
localStorage.setItem("browser", browser);
});
// Handling preferred video quality
let preferredVideoQuality = localStorage.getItem("preferredVideoQuality");
if (preferredVideoQuality) {
getId("preferredVideoQuality").value = preferredVideoQuality;
}
getId("preferredVideoQuality").addEventListener("change", () => {
preferredVideoQuality = getId("preferredVideoQuality").value;
localStorage.setItem("preferredVideoQuality", preferredVideoQuality);
});
// Handling preferred audio quality
let preferredAudioQuality = localStorage.getItem("preferredAudioQuality");
if (preferredAudioQuality) {
getId("preferredAudioQuality").value = preferredAudioQuality;
}
getId("preferredAudioQuality").addEventListener("change", () => {
preferredAudioQuality = getId("preferredAudioQuality").value;
localStorage.setItem("preferredAudioQuality", preferredAudioQuality);
});
// Handling preferred video codec
let preferredVideoCodec = localStorage.getItem("preferredVideoCodec");
if (preferredVideoCodec) {
getId("preferredVideoCodec").value = preferredVideoCodec;
}
getId("preferredVideoCodec").addEventListener("change", () => {
preferredVideoCodec = getId("preferredVideoCodec").value;
localStorage.setItem("preferredVideoCodec", preferredVideoCodec);
});
// Proxy
let proxy = localStorage.getItem("proxy");
if (proxy) {
getId("proxyTxt").value = proxy;
}
getId("proxyTxt").addEventListener("change", () => {
proxy = getId("proxyTxt").value;
localStorage.setItem("proxy", proxy);
});
// Custom yt-dlp args
const ytDlpArgsInput = getId("customArgsInput");
let customYtDlpArgs = localStorage.getItem("customYtDlpArgs");
if (customYtDlpArgs) {
ytDlpArgsInput.value = customYtDlpArgs;
ytDlpArgsInput.style.height = ytDlpArgsInput.scrollHeight + "px";
}
ytDlpArgsInput.addEventListener("input", () => {
customYtDlpArgs = getId("customArgsInput").value;
localStorage.setItem("customYtDlpArgs", customYtDlpArgs.trim());
ytDlpArgsInput.style.height = "auto";
ytDlpArgsInput.style.height = ytDlpArgsInput.scrollHeight + "px";
});
getId("learnMoreLink").addEventListener("click", () => {
shell.openExternal(
"https://github.com/aandrew-me/ytDownloader/wiki/Custom-yt%E2%80%90dlp-options"
);
});
// Reload
function reload() {
ipcRenderer.send("reload");
}
getId("restart").addEventListener("click", () => {
reload();
});
// Handling filename formats
getId("filenameFormat").addEventListener("input", () => {
const text = getId("filenameFormat").value;
localStorage.setItem("filenameFormat", text);
});
if (localStorage.getItem("filenameFormat")) {
getId("filenameFormat").value = localStorage.getItem("filenameFormat");
}
getId("resetFilenameFormat").addEventListener("click", () => {
getId("filenameFormat").value = "%(playlist_index)s.%(title)s.%(ext)s";
localStorage.setItem(
"filenameFormat",
"%(playlist_index)s.%(title)s.%(ext)s"
);
});
// Handling folder name formats
getId("foldernameFormat").addEventListener("input", () => {
const text = getId("foldernameFormat").value;
localStorage.setItem("foldernameFormat", text);
});
if (localStorage.getItem("foldernameFormat")) {
getId("foldernameFormat").value = localStorage.getItem("foldernameFormat");
}
getId("resetFoldernameFormat").addEventListener("click", () => {
getId("foldernameFormat").value = "%(playlist_title)s";
localStorage.setItem("foldernameFormat", "%(playlist_title)s");
});
// Max active downloads
getId("maxDownloads").addEventListener("input", () => {
const number = Number(getId("maxDownloads").value);
if (number < 1) {
localStorage.setItem("maxActiveDownloads", "1");
} else {
localStorage.setItem("maxActiveDownloads", String(number));
}
});
if (localStorage.getItem("maxActiveDownloads")) {
getId("maxDownloads").value = localStorage.getItem("maxActiveDownloads");
}
// Closing app to system tray
const closeToTray = getId("closeToTray");
closeToTray.addEventListener("change", (event) => {
if (closeToTray.checked) {
localStorage.setItem("closeToTray", "true");
ipcRenderer.send("useTray", true);
} else {
localStorage.setItem("closeToTray", "false");
ipcRenderer.send("useTray", false);
}
});
const trayEnabled = localStorage.getItem("closeToTray");
if (trayEnabled == "true") {
closeToTray.checked = true;
ipcRenderer.send("useTray", true);
}
// Auto updates
const autoUpdateDisabled = getId("autoUpdateDisabled");
autoUpdateDisabled.addEventListener("change", (event) => {
if (autoUpdateDisabled.checked) {
localStorage.setItem("autoUpdate", "false");
} else {
localStorage.setItem("autoUpdate", "true");
}
});
const autoUpdate = localStorage.getItem("autoUpdate");
if (autoUpdate == "false") {
autoUpdateDisabled.checked = true;
}
// Show more format options
const showMoreFormats = getId("showMoreFormats");
showMoreFormats.addEventListener("change", (event) => {
if (showMoreFormats.checked) {
localStorage.setItem("showMoreFormats", "true");
} else {
localStorage.setItem("showMoreFormats", "false");
}
});
const showMoreFormatOpts = localStorage.getItem("showMoreFormats");
if (showMoreFormatOpts == "true") {
showMoreFormats.checked = true;
}
function showPopup(text, isError = false) {
let popupContainer = document.getElementById("popupContainer");
if (!popupContainer) {
popupContainer = document.createElement("div");
popupContainer.id = "popupContainer";
popupContainer.className = "popup-container";
document.body.appendChild(popupContainer);
}
const popup = document.createElement("span");
popup.textContent = text;
popup.classList.add("popup-item");
popup.style.background = isError ? "#ff6b6b" : "#54abde";
if (isError) {
popup.classList.add("popup-error");
}
popupContainer.appendChild(popup);
setTimeout(() => {
popup.style.opacity = "0";
setTimeout(() => {
popup.remove();
if (popupContainer.childElementCount === 0) {
popupContainer.remove();
}
}, 1000);
}, 2200);
}
================================================
FILE: src/renderer.js
================================================
const {shell, ipcRenderer, clipboard} = require("electron");
const {default: YTDlpWrap} = require("yt-dlp-wrap-plus");
const {constants} = require("fs/promises");
const {homedir, platform} = require("os");
const {join} = require("path");
const {mkdirSync, accessSync, promises, existsSync} = require("fs");
const {execSync, spawn} = require("child_process");
const CONSTANTS = {
DOM_IDS: {
// Main UI
PASTE_URL_BTN: "pasteUrl",
LOADING_WRAPPER: "loadingWrapper",
INCORRECT_MSG: "incorrectMsg",
ERROR_BTN: "errorBtn",
ERROR_DETAILS: "errorDetails",
PATH_DISPLAY: "path",
SELECT_LOCATION_BTN: "selectLocation",
DOWNLOAD_LIST: "list",
CLEAR_BTN: "clearBtn",
// Hidden Info Panel
HIDDEN_PANEL: "hidden",
CLOSE_HIDDEN_BTN: "closeHidden",
TITLE_CONTAINER: "title",
TITLE_INPUT: "titleName",
URL_INPUTS: ".url",
AUDIO_PRESENT_SECTION: "audioPresent",
QUIT_APP_BTN: "quitAppBtn",
// Format Selectors
VIDEO_FORMAT_SELECT: "videoFormatSelect",
AUDIO_FORMAT_SELECT: "audioFormatSelect",
AUDIO_FOR_VIDEO_FORMAT_SELECT: "audioForVideoFormatSelect",
// Download Buttons
VIDEO_DOWNLOAD_BTN: "videoDownload",
AUDIO_DOWNLOAD_BTN: "audioDownload",
EXTRACT_BTN: "extractBtn",
// Audio Extraction
EXTRACT_SELECTION: "extractSelection",
EXTRACT_QUALITY_SELECT: "extractQualitySelect",
// Advanced Options
CUSTOM_ARGS_INPUT: "customArgsInput", // Add this line
START_TIME: "min-time",
END_TIME: "max-time",
MIN_SLIDER: "min-slider",
MAX_SLIDER: "max-slider",
SLIDER_RANGE_HIGHLIGHT: "range-highlight",
SUB_CHECKED: "subChecked",
QUIT_CHECKED: "quitChecked",
// Popups
POPUP_BOX: "popupBox",
POPUP_BOX_MAC: "popupBoxMac",
POPUP_TEXT: "popupText",
POPUP_SVG: "popupSvg",
YTDLP_DOWNLOAD_PROGRESS: "ytDlpDownloadProgress",
UPDATE_POPUP: "updatePopup",
UPDATE_POPUP_PROGRESS: "updateProgress",
UPDATE_POPUP_BAR: "progressBarFill",
// Menu
MENU_ICON: "menuIcon",
MENU: "menu",
PREFERENCE_WIN: "preferenceWin",
ABOUT_WIN: "aboutWin",
PLAYLIST_WIN: "playlistWin",
HISTORY_WIN: "historyWin",
COMPRESSOR_WIN: "compressorWin",
},
LOCAL_STORAGE_KEYS: {
DOWNLOAD_PATH: "downloadPath",
YT_DLP_PATH: "ytdlp",
MAX_DOWNLOADS: "maxActiveDownloads",
PREFERRED_VIDEO_QUALITY: "preferredVideoQuality",
PREFERRED_AUDIO_QUALITY: "preferredAudioQuality",
PREFERRED_VIDEO_CODEC: "preferredVideoCodec",
SHOW_MORE_FORMATS: "showMoreFormats",
BROWSER_COOKIES: "browser",
PROXY: "proxy",
CONFIG_PATH: "configPath",
AUTO_UPDATE: "autoUpdate",
CLOSE_TO_TRAY: "closeToTray",
YT_DLP_CUSTOM_ARGS: "customYtDlpArgs",
},
};
/**
* Shorthand for document.getElementById.
* @param {string} id The ID of the DOM element.
* @returns {HTMLElement | null}
*/
const $ = (id) => document.getElementById(id);
class YtDownloaderApp {
constructor() {
this.state = {
ytDlp: null,
ytDlpPath: "",
ffmpegPath: "",
jsRuntimePath: "",
downloadDir: "",
maxActiveDownloads: 5,
currentDownloads: 0,
// Video metadata
videoInfo: {
title: "",
thumbnail: "",
duration: 0,
extractor_key: "",
url: "",
},
// Download options
downloadOptions: {
rangeCmd: "",
rangeOption: "",
subs: "",
subLangs: "",
},
// Preferences
preferences: {
videoQuality: 1080,
audioQuality: "",
videoCodec: "avc1",
showMoreFormats: false,
proxy: "",
browserForCookies: "",
customYtDlpArgs: "",
},
downloadControllers: new Map(),
downloadedItems: new Set(),
downloadQueue: [],
};
}
/**
* Initializes the application, setting up directories, finding executables,
* and attaching event listeners.
*/
async initialize() {
await this._initializeTranslations();
this._setupDirectories();
this._configureTray();
this._configureAutoUpdate();
try {
this.state.ytDlpPath = await this._findOrDownloadYtDlp();
this.state.ytDlp = new YTDlpWrap(`"${this.state.ytDlpPath}"`);
this.state.ffmpegPath = await this._findFfmpeg();
this.state.jsRuntimePath = await this._getJsRuntimePath();
console.log("yt-dlp path:", this.state.ytDlpPath);
console.log("ffmpeg path:", this.state.ffmpegPath);
console.log("JS runtime path:", this.state.jsRuntimePath);
this._loadSettings();
this._addEventListeners();
// Signal to the main process that the renderer is ready for links
ipcRenderer.send("ready-for-links");
} catch (error) {
console.error("Initialization failed:", error);
$(CONSTANTS.DOM_IDS.INCORRECT_MSG).textContent = error.message;
$(CONSTANTS.DOM_IDS.PASTE_URL_BTN).style.display = "none";
}
}
/**
* Sets up the application's hidden directory and the default download directory.
*/
_setupDirectories() {
const userHomeDir = homedir();
const hiddenDir = join(userHomeDir, ".ytDownloader");
if (!existsSync(hiddenDir)) {
try {
mkdirSync(hiddenDir, {recursive: true});
} catch (error) {
console.log(error);
}
}
let defaultDownloadDir = join(userHomeDir, "Downloads");
if (platform() === "linux") {
try {
const xdgDownloadDir = execSync("xdg-user-dir DOWNLOAD")
.toString()
.trim();
if (xdgDownloadDir) {
defaultDownloadDir = xdgDownloadDir;
}
} catch (err) {
console.warn("Could not execute xdg-user-dir:", err.message);
}
}
const savedPath = localStorage.getItem(
CONSTANTS.LOCAL_STORAGE_KEYS.DOWNLOAD_PATH
);
if (savedPath) {
try {
accessSync(savedPath, constants.W_OK);
this.state.downloadDir = savedPath;
} catch {
console.warn(
`Cannot write to saved path "${savedPath}". Falling back to default.`
);
this.state.downloadDir = defaultDownloadDir;
localStorage.setItem(
CONSTANTS.LOCAL_STORAGE_KEYS.DOWNLOAD_PATH,
defaultDownloadDir
);
}
} else {
this.state.downloadDir = defaultDownloadDir;
}
$(CONSTANTS.DOM_IDS.PATH_DISPLAY).textContent = this.state.downloadDir;
if (!existsSync(this.state.downloadDir)) {
mkdirSync(this.state.downloadDir, {recursive: true});
}
}
/**
* Checks localStorage to determine if the tray icon should be used.
*/
_configureTray() {
if (
localStorage.getItem(CONSTANTS.LOCAL_STORAGE_KEYS.CLOSE_TO_TRAY) ===
"true"
) {
console.log("Tray is enabled.");
ipcRenderer.send("useTray", true);
}
}
/**
* Checks settings to determine if auto-updates should be enabled.
*/
_configureAutoUpdate() {
let autoUpdate = true;
if (
localStorage.getItem(CONSTANTS.LOCAL_STORAGE_KEYS.AUTO_UPDATE) ===
"false"
) {
autoUpdate = false;
}
if (
process.windowsStore ||
process.env.YTDOWNLOADER_AUTO_UPDATES === "0"
) {
autoUpdate = false;
}
ipcRenderer.send("autoUpdate", autoUpdate);
}
/**
* Waits for the i18n module to load and then translates the static page content.
*/
async _initializeTranslations() {
return new Promise((resolve) => {
document.addEventListener(
"translations-loaded",
() => {
window.i18n.translatePage();
resolve();
},
{once: true}
);
});
}
/**
* Locates the yt-dlp executable path from various sources or downloads it.
* @returns {Promise} A promise that resolves with the path to yt-dlp.
*/
async _findOrDownloadYtDlp() {
const hiddenDir = join(homedir(), ".ytDownloader");
const defaultYtDlpName = platform() === "win32" ? "ytdlp.exe" : "ytdlp";
const defaultYtDlpPath = join(hiddenDir, defaultYtDlpName);
const isMacOS = platform() === "darwin";
const isFreeBSD = platform() === "freebsd";
let executablePath = null;
// PRIORITY 1: Environment Variable
if (process.env.YTDOWNLOADER_YTDLP_PATH) {
if (existsSync(process.env.YTDOWNLOADER_YTDLP_PATH)) {
executablePath = process.env.YTDOWNLOADER_YTDLP_PATH;
} else {
throw new Error(
"YTDOWNLOADER_YTDLP_PATH is set, but no file exists there."
);
}
}
// PRIORITY 2: macOS homebrew
else if (isMacOS) {
const possiblePaths = [
"/opt/homebrew/bin/yt-dlp", // Apple Silicon
"/usr/local/bin/yt-dlp", // Intel
];
executablePath = possiblePaths.find((p) => existsSync(p));
// If Homebrew check fails, show popup and abort
if (!executablePath) {
$(CONSTANTS.DOM_IDS.POPUP_BOX_MAC).style.display = "block";
console.warn("Homebrew yt-dlp not found. Prompting user.");
return "";
}
}
// PRIORITY 3: FreeBSD
else if (isFreeBSD) {
try {
executablePath = execSync("which yt-dlp").toString().trim();
} catch {
throw new Error(
"No yt-dlp found in PATH on FreeBSD. Please install it."
);
}
}
// PRIORITY 4: LocalStorage or Download (Windows/Linux)
else {
const storedPath = localStorage.getItem(
CONSTANTS.LOCAL_STORAGE_KEYS.YT_DLP_PATH
);
if (storedPath && existsSync(storedPath)) {
executablePath = storedPath;
}
// Download if missing
else {
executablePath = await this.ensureYtDlpBinary(defaultYtDlpPath);
}
}
localStorage.setItem(
CONSTANTS.LOCAL_STORAGE_KEYS.YT_DLP_PATH,
executablePath
);
// Auto update
this._runBackgroundUpdate(executablePath, isMacOS);
return executablePath;
}
/**
* yt-dlp background update
*/
_runBackgroundUpdate(executablePath, isMacOS) {
try {
if (isMacOS) {
const brewPaths = [
"/opt/homebrew/bin/brew",
"/usr/local/bin/brew",
];
const brewExec = brewPaths.find((p) => existsSync(p)) || "brew";
const brewUpdate = spawn(brewExec, ["upgrade", "yt-dlp"]);
brewUpdate.on("error", (err) =>
console.error("Failed to run 'brew upgrade yt-dlp':", err)
);
brewUpdate.stdout.on("data", (data) =>
console.log("yt-dlp brew update:", data.toString())
);
} else {
const updateProc = spawn(executablePath, ["-U"]);
updateProc.on("error", (err) =>
console.error(
"Failed to run background yt-dlp update:",
err
)
);
updateProc.stdout.on("data", (data) => {
const output = data.toString();
console.log("yt-dlp update check:", output);
if (output.toLowerCase().includes("updating to")) {
this._showPopup(i18n.__("updatingYtdlp"));
} else if (
output.toLowerCase().includes("updated yt-dlp to")
) {
this._showPopup(i18n.__("updatedYtdlp"));
}
});
}
} catch (err) {
console.warn("Error initiating background update:", err);
}
}
/**
* Checks for the presence of the yt-dlp binary at the default path.
* If not found, it attempts to download it from GitHub.
*
* @param {string} defaultYtDlpPath The expected path to the yt-dlp binary.
* @returns {Promise} A promise that resolves with the path to the yt-dlp binary.
* @throws {Error} Throws an error if the download fails.
*/
async ensureYtDlpBinary(defaultYtDlpPath) {
try {
await promises.access(defaultYtDlpPath);
return defaultYtDlpPath;
} catch {
console.log("yt-dlp not found, downloading...");
$(CONSTANTS.DOM_IDS.POPUP_BOX).style.display = "block";
$(CONSTANTS.DOM_IDS.POPUP_SVG).style.display = "inline";
document.querySelector("#popupBox p").textContent = i18n.__(
"downloadingNecessaryFilesWait"
);
try {
await YTDlpWrap.downloadFromGithub(
defaultYtDlpPath,
undefined,
undefined,
(progress, _d, _t) => {
$(
CONSTANTS.DOM_IDS.YTDLP_DOWNLOAD_PROGRESS
).textContent =
i18n.__("progress") +
`: ${(progress * 100).toFixed(2)}%`;
}
);
$(CONSTANTS.DOM_IDS.POPUP_BOX).style.display = "none";
localStorage.setItem(
CONSTANTS.LOCAL_STORAGE_KEYS.YT_DLP_PATH,
defaultYtDlpPath
);
return defaultYtDlpPath;
} catch (downloadError) {
$(CONSTANTS.DOM_IDS.YTDLP_DOWNLOAD_PROGRESS).textContent = "";
console.error("Failed to download yt-dlp:", downloadError);
document.querySelector("#popupBox p").textContent = i18n.__(
"errorFailedFileDownload"
);
$(CONSTANTS.DOM_IDS.POPUP_SVG).style.display = "none";
const tryAgainBtn = document.createElement("button");
tryAgainBtn.id = "tryBtn";
tryAgainBtn.textContent = i18n.__("tryAgain");
tryAgainBtn.addEventListener("click", () => {
// TODO: Improve it
ipcRenderer.send("reload");
});
document.getElementById("popup").appendChild(tryAgainBtn);
throw new Error("Failed to download yt-dlp.");
}
}
}
/**
* Locates the ffmpeg executable path.
* @returns {Promise} A promise that resolves with the path to ffmpeg.
*/
async _findFfmpeg() {
// Priority 1: Environment Variable
if (process.env.YTDOWNLOADER_FFMPEG_PATH) {
if (existsSync(process.env.YTDOWNLOADER_FFMPEG_PATH)) {
return process.env.YTDOWNLOADER_FFMPEG_PATH;
}
throw new Error(
"YTDOWNLOADER_FFMPEG_PATH is set, but no file exists there."
);
}
// Priority 2: System-installed (FreeBSD)
if (platform() === "freebsd") {
try {
return execSync("which ffmpeg").toString().trim();
} catch {
throw new Error(
"No ffmpeg found in PATH on FreeBSD. App may not work correctly."
);
}
}
// Priority 3: Bundled ffmpeg
return join(__dirname, "..", "ffmpeg", "bin");
}
/**
* Determines the JavaScript runtime path for yt-dlp.
* @returns {Promise} A promise that resolves with the JS runtime path.
*/
async _getJsRuntimePath() {
const exeName = "node";
if (process.env.YTDOWNLOADER_NODE_PATH) {
if (existsSync(process.env.YTDOWNLOADER_NODE_PATH)) {
return `$node:"${process.env.YTDOWNLOADER_NODE_PATH}"`;
}
return "";
}
if (process.env.YTDOWNLOADER_DENO_PATH) {
if (existsSync(process.env.YTDOWNLOADER_DENO_PATH)) {
return `$deno:"${process.env.YTDOWNLOADER_DENO_PATH}"`;
}
return "";
}
if (platform() === "darwin") {
const possiblePaths = [
"/opt/homebrew/bin/deno",
"/usr/local/bin/deno",
];
for (const p of possiblePaths) {
if (existsSync(p)) {
return `deno:"${p}"`;
}
}
console.log("No Deno installation found");
return "";
}
let jsRuntimePath = join(__dirname, "..", exeName);
if (platform() === "win32") {
jsRuntimePath = join(__dirname, "..", `${exeName}.exe`);
}
if (existsSync(jsRuntimePath)) {
return `${exeName}:"${jsRuntimePath}"`;
} else {
return "";
}
}
/**
* Loads various settings from localStorage into the application state.
*/
_loadSettings() {
const prefs = this.state.preferences;
prefs.videoQuality =
Number(
localStorage.getItem(
CONSTANTS.LOCAL_STORAGE_KEYS.PREFERRED_VIDEO_QUALITY
)
) || 1080;
prefs.audioQuality =
localStorage.getItem(
CONSTANTS.LOCAL_STORAGE_KEYS.PREFERRED_AUDIO_QUALITY
) || "";
prefs.videoCodec =
localStorage.getItem(
CONSTANTS.LOCAL_STORAGE_KEYS.PREFERRED_VIDEO_CODEC
) || "avc1";
prefs.showMoreFormats =
localStorage.getItem(
CONSTANTS.LOCAL_STORAGE_KEYS.SHOW_MORE_FORMATS
) === "true";
prefs.proxy =
localStorage.getItem(CONSTANTS.LOCAL_STORAGE_KEYS.PROXY) || "";
prefs.browserForCookies =
localStorage.getItem(
CONSTANTS.LOCAL_STORAGE_KEYS.BROWSER_COOKIES
) || "";
prefs.customYtDlpArgs =
localStorage.getItem(
CONSTANTS.LOCAL_STORAGE_KEYS.YT_DLP_CUSTOM_ARGS
) || "";
prefs.configPath = localStorage.getItem(CONSTANTS.LOCAL_STORAGE_KEYS.CONFIG_PATH) || "";
const maxDownloads = Number(
localStorage.getItem(CONSTANTS.LOCAL_STORAGE_KEYS.MAX_DOWNLOADS)
);
this.state.maxActiveDownloads = maxDownloads >= 1 ? maxDownloads : 5;
// Update UI with loaded settings
$(CONSTANTS.DOM_IDS.CUSTOM_ARGS_INPUT).value = prefs.customYtDlpArgs;
const downloadDir = localStorage.getItem(
CONSTANTS.LOCAL_STORAGE_KEYS.DOWNLOAD_PATH
);
if (downloadDir) {
this.state.downloadDir = downloadDir;
$(CONSTANTS.DOM_IDS.PATH_DISPLAY).textContent = downloadDir;
}
}
/**
* Attaches all necessary event listeners for the UI.
*/
_addEventListeners() {
$(CONSTANTS.DOM_IDS.PASTE_URL_BTN).addEventListener("click", () =>
this.pasteAndGetInfo()
);
document.addEventListener("keydown", (event) => {
if (
((event.ctrlKey && event.key === "v") ||
(event.metaKey &&
event.key === "v" &&
platform() === "darwin")) &&
document.activeElement.tagName !== "INPUT" &&
document.activeElement.tagName !== "TEXTAREA"
) {
$(CONSTANTS.DOM_IDS.PASTE_URL_BTN).classList.add("active");
setTimeout(() => {
$(CONSTANTS.DOM_IDS.PASTE_URL_BTN).classList.remove(
"active"
);
}, 150);
this.pasteAndGetInfo();
}
});
// Download buttons
$(CONSTANTS.DOM_IDS.VIDEO_DOWNLOAD_BTN).addEventListener("click", () =>
this.handleDownloadRequest("video")
);
$(CONSTANTS.DOM_IDS.AUDIO_DOWNLOAD_BTN).addEventListener("click", () =>
this.handleDownloadRequest("audio")
);
$(CONSTANTS.DOM_IDS.EXTRACT_BTN).addEventListener("click", () =>
this.handleDownloadRequest("extract")
);
// UI controls
$(CONSTANTS.DOM_IDS.CLOSE_HIDDEN_BTN).addEventListener("click", () =>
this._hideInfoPanel()
);
$(CONSTANTS.DOM_IDS.SELECT_LOCATION_BTN).addEventListener("click", () =>
ipcRenderer.send("select-location-main", "")
);
$(CONSTANTS.DOM_IDS.CLEAR_BTN).addEventListener("click", () =>
this._clearAllDownloaded()
);
// Error details
$(CONSTANTS.DOM_IDS.ERROR_DETAILS).addEventListener("click", (e) => {
// @ts-ignore
clipboard.writeText(e.target.innerText);
this._showPopup(i18n.__("copiedText"), false);
});
$(CONSTANTS.DOM_IDS.QUIT_APP_BTN).addEventListener("click", () => {
ipcRenderer.send("quit", "quit");
});
// IPC listeners
ipcRenderer.on("link", (event, text) => this.getInfo(text));
ipcRenderer.on("downloadPath", (event, downloadPath) => {
try {
accessSync(downloadPath[0], constants.W_OK);
const newPath = downloadPath[0];
$(CONSTANTS.DOM_IDS.PATH_DISPLAY).textContent = newPath;
this.state.downloadDir = newPath;
} catch (error) {
console.log(error);
this._showPopup(i18n.__("unableToAccessDir"), true);
}
});
ipcRenderer.on("download-progress", (_event, percent) => {
if (percent) {
const popup = $(CONSTANTS.DOM_IDS.UPDATE_POPUP);
const textEl = $(CONSTANTS.DOM_IDS.UPDATE_POPUP_PROGRESS);
const barEl = $(CONSTANTS.DOM_IDS.UPDATE_POPUP_BAR);
popup.style.display = "flex";
textEl.textContent = `${percent.toFixed(1)}%`;
barEl.style.width = `${percent}%`;
}
});
ipcRenderer.on("update-downloaded", (_event, _) => {
$(CONSTANTS.DOM_IDS.UPDATE_POPUP).style.display = "none";
});
// Menu Listeners
const menuMapping = {
[CONSTANTS.DOM_IDS.PREFERENCE_WIN]: "/preferences.html",
[CONSTANTS.DOM_IDS.ABOUT_WIN]: "/about.html",
[CONSTANTS.DOM_IDS.HISTORY_WIN]: "/history.html",
};
const windowMapping = {
[CONSTANTS.DOM_IDS.PLAYLIST_WIN]: "/playlist.html",
[CONSTANTS.DOM_IDS.COMPRESSOR_WIN]: "/compressor.html",
};
Object.entries(menuMapping).forEach(([id, page]) => {
$(id)?.addEventListener("click", () => {
this._closeMenu();
ipcRenderer.send("load-page", join(__dirname, page));
});
});
Object.entries(windowMapping).forEach(([id, page]) => {
$(id)?.addEventListener("click", () => {
this._closeMenu();
ipcRenderer.send("load-win", join(__dirname, page));
});
});
const minSlider = $(CONSTANTS.DOM_IDS.MIN_SLIDER);
const maxSlider = $(CONSTANTS.DOM_IDS.MAX_SLIDER);
minSlider.addEventListener("input", () =>
this._updateSliderUI(minSlider)
);
maxSlider.addEventListener("input", () =>
this._updateSliderUI(maxSlider)
);
$(CONSTANTS.DOM_IDS.START_TIME).addEventListener(
"change",
this._handleTimeInputChange
);
$(CONSTANTS.DOM_IDS.END_TIME).addEventListener(
"change",
this._handleTimeInputChange
);
this._updateSliderUI(null);
}
// --- Public Methods ---
/**
* Pastes URL from clipboard and initiates fetching video info.
*/
pasteAndGetInfo() {
this.getInfo(clipboard.readText());
}
/**
* Fetches video metadata from a given URL.
* @param {string} url The video URL.
*/
async getInfo(url) {
this._loadSettings();
this._defaultVideoToggle();
this._resetUIForNewLink();
this.state.videoInfo.url = url;
try {
const metadata = await this._fetchVideoMetadata(url);
console.log(metadata);
const durationInt =
metadata.duration == null ? null : Math.ceil(metadata.duration);
this.state.videoInfo = {
...this.state.videoInfo,
id: metadata.id,
title: metadata.title,
thumbnail: metadata.thumbnail,
duration: durationInt,
extractor_key: metadata.extractor_key,
};
this.setVideoLength(durationInt);
this._populateFormatSelectors(metadata.formats || []);
this._displayInfoPanel();
} catch (error) {
if (
error.message.includes("js-runtimes") &&
error.message.includes("no such option")
) {
this._showError(i18n.__("ytDlpUpdateRequired"), url);
} else {
this._showError(error.message, url);
}
} finally {
$(CONSTANTS.DOM_IDS.LOADING_WRAPPER).style.display = "none";
}
}
/**
* Handles a download request, either starting it immediately or queuing it.
* @param {'video' | 'audio' | 'extract'} type The type of download.
*/
handleDownloadRequest(type) {
this._updateDownloadOptionsFromUI();
const downloadJob = {
type,
url: this.state.videoInfo.url,
title: this.state.videoInfo.title,
thumbnail: this.state.videoInfo.thumbnail,
options: {...this.state.downloadOptions},
// Capture UI values at the moment of click
uiSnapshot: {
videoFormat: $(CONSTANTS.DOM_IDS.VIDEO_FORMAT_SELECT).value,
audioForVideoFormat: $(
CONSTANTS.DOM_IDS.AUDIO_FOR_VIDEO_FORMAT_SELECT
).value,
audioFormat: $(CONSTANTS.DOM_IDS.AUDIO_FORMAT_SELECT).value,
extractFormat: $(CONSTANTS.DOM_IDS.EXTRACT_SELECTION).value,
extractQuality: $(CONSTANTS.DOM_IDS.EXTRACT_QUALITY_SELECT)
.value,
},
};
if (this.state.currentDownloads < this.state.maxActiveDownloads) {
this._startDownload(downloadJob);
} else {
this._queueDownload(downloadJob);
}
this._hideInfoPanel();
}
/**
* Executes yt-dlp to get video metadata in JSON format.
* @param {string} url The video URL.
* @returns {Promise} A promise that resolves with the parsed JSON metadata.
*/
_fetchVideoMetadata(url) {
return new Promise((resolve, reject) => {
const {proxy, browserForCookies, configPath} =
this.state.preferences;
const args = [
"-j",
"--no-playlist",
"--no-warnings",
proxy ? "--proxy" : "",
proxy,
browserForCookies ? "--cookies-from-browser" : "",
browserForCookies,
this.state.jsRuntimePath
? `--no-js-runtimes --js-runtime ${this.state.jsRuntimePath}`
: "",
configPath ? "--config-location" : "",
configPath ? `"${configPath}"` : "",
`"${url}"`,
].filter(Boolean);
const process = this.state.ytDlp.exec(args, {shell: true});
console.log(
"Spawned yt-dlp with args:",
process.ytDlpProcess.spawnargs.join(" ")
);
let stdout = "";
let stderr = "";
process.ytDlpProcess.stdout.on("data", (data) => {
stdout += data;
});
process.ytDlpProcess.stderr.on("data", (data) => (stderr += data));
process.on("close", () => {
if (stdout) {
try {
resolve(JSON.parse(stdout));
} catch (e) {
reject(
new Error(
"Failed to parse yt-dlp JSON output: " +
(stderr || e.message)
)
);
}
} else {
reject(
new Error(
stderr || `yt-dlp exited with a non-zero code.`
)
);
}
});
process.on("error", (err) => reject(err));
});
}
/**
* Starts the download process for a given job.
* @param {object} job The download job object.
*/
_startDownload(job) {
this.state.currentDownloads++;
const randomId = "item_" + Math.random().toString(36).substring(2, 12);
const {downloadArgs, finalFilename, finalExt} =
this._prepareDownloadArgs(job);
this._createDownloadUI(randomId, job);
const controller = new AbortController();
this.state.downloadControllers.set(randomId, controller);
const downloadProcess = this.state.ytDlp.exec(downloadArgs, {
shell: true,
detached: false,
signal: controller.signal,
});
console.log(
"Spawned yt-dlp with args:",
downloadProcess.ytDlpProcess.spawnargs.join(" ")
);
// Attach event listeners
downloadProcess
.on("progress", (progress) => {
this._updateProgressUI(randomId, progress);
})
.once("ytDlpEvent", () => {
const el = $(`${randomId}_prog`);
if (el) el.textContent = i18n.__("downloading");
})
// .on("ytDlpEvent", (eventType, eventData) => {
// console.log(eventData)
// })
.once("close", (code) => {
this._handleDownloadCompletion(
code,
randomId,
finalFilename,
finalExt,
job.thumbnail
);
})
.once("error", (error) => {
this.state.downloadedItems.add(randomId);
this._updateClearAllButton();
this._handleDownloadError(error, randomId);
});
}
/**
* Queues a download job if the maximum number of active downloads is reached.
* @param {object} job The download job object.
*/
_queueDownload(job) {
const randomId = "queue_" + Math.random().toString(36).substring(2, 12);
this.state.downloadQueue.push({...job, queueId: randomId});
const itemHTML = `
${i18n.__(
job.type === "video" ? "video" : "audio"
)}
${job.title}
${i18n.__("preparing")}
`;
$(CONSTANTS.DOM_IDS.DOWNLOAD_LIST).insertAdjacentHTML(
"beforeend",
itemHTML
);
}
/**
* Checks the queue and starts the next download if a slot is available.
*/
_processQueue() {
if (
this.state.downloadQueue.length > 0 &&
this.state.currentDownloads < this.state.maxActiveDownloads
) {
const nextJob = this.state.downloadQueue.shift();
// Remove the pending UI element
$(nextJob.queueId)?.remove();
this._startDownload(nextJob);
}
}
/**
* Prepares the command-line arguments for yt-dlp based on the download job.
* @param {object} job The download job object.
* @returns {{downloadArgs: string[], finalFilename: string, finalExt: string}}
*/
_prepareDownloadArgs(job) {
const {type, url, title, options, uiSnapshot} = job;
const {rangeOption, rangeCmd, subs, subLangs} = options;
const {proxy, browserForCookies, configPath} = this.state.preferences;
let format_id, ext, audioForVideoFormat_id, audioFormat;
if (type === "video") {
const [videoFid, videoExt, _, videoCodec] =
uiSnapshot.videoFormat.split("|");
const [audioFid, audioExt] =
uiSnapshot.audioForVideoFormat.split("|");
format_id = videoFid;
audioForVideoFormat_id = audioFid;
const finalAudioExt = audioExt === "webm" ? "opus" : audioExt;
ext = videoExt;
if (videoExt === "mp4" && finalAudioExt === "opus") {
if (videoCodec.includes("avc")) ext = "mkv";
else if (videoCodec.includes("av01")) ext = "webm";
} else if (
videoExt === "webm" &&
["m4a", "mp4"].includes(finalAudioExt)
) {
ext = "mkv";
}
audioFormat =
audioForVideoFormat_id === "none"
? ""
: `+${audioForVideoFormat_id}`;
} else if (type === "audio") {
[format_id, ext] = uiSnapshot.audioFormat.split("|");
ext = ext === "webm" ? "opus" : ext;
} else {
// type === 'extract'
ext =
{alac: "m4a"}[uiSnapshot.extractFormat] ||
uiSnapshot.extractFormat;
}
const invalidChars =
platform() === "win32" ? /[<>:"/\\|?*[\]`#]/g : /["/`#]/g;
let finalFilename = title
.replace(invalidChars, "")
.trim()
.slice(0, 100);
if (finalFilename.startsWith(".")) {
finalFilename = finalFilename.substring(1);
}
if (rangeCmd) {
let rangeTxt = rangeCmd.replace("*", "");
if (platform() === "win32") rangeTxt = rangeTxt.replace(/:/g, "_");
finalFilename += ` [${rangeTxt}]`;
}
const outputPath = `"${join(
this.state.downloadDir,
`${finalFilename}.${ext}`
)}"`;
const baseArgs = [
"--no-playlist",
"--no-mtime",
browserForCookies ? "--cookies-from-browser" : "",
browserForCookies,
proxy ? "--proxy" : "",
proxy,
configPath ? "--config-location" : "",
configPath ? `"${configPath}"` : "",
"--ffmpeg-location",
`"${this.state.ffmpegPath}"`,
this.state.jsRuntimePath
? `--no-js-runtimes --js-runtime ${this.state.jsRuntimePath}`
: "",
].filter(Boolean);
if (type === "audio") {
if (ext === "m4a" || ext === "mp3" || ext === "mp4") {
baseArgs.unshift("--embed-thumbnail");
}
} else if (type === "extract") {
if (ext === "mp3" || ext === "m4a") {
baseArgs.unshift("--embed-thumbnail");
}
}
let downloadArgs;
if (type === "extract") {
downloadArgs = [
"-x",
"--audio-format",
uiSnapshot.extractFormat,
"--audio-quality",
uiSnapshot.extractQuality,
"-o",
outputPath,
...baseArgs,
];
} else {
const formatString =
type === "video" ? `${format_id}${audioFormat}` : format_id;
downloadArgs = ["-f", formatString, "-o", outputPath, ...baseArgs];
}
if (subs) downloadArgs.push(subs);
if (subLangs) downloadArgs.push(subLangs);
if (rangeOption) downloadArgs.push(rangeOption, rangeCmd);
const customArgsString = $(
CONSTANTS.DOM_IDS.CUSTOM_ARGS_INPUT
).value.trim();
if (customArgsString) {
const customArgs = customArgsString.split(/\s+/);
downloadArgs.push(...customArgs);
}
downloadArgs.push(`"${url}"`);
return {downloadArgs, finalFilename, finalExt: ext};
}
/**
* Handles the completion of a download process.
*/
_handleDownloadCompletion(code, randomId, filename, ext, thumbnail) {
this.state.currentDownloads--;
this.state.downloadControllers.delete(randomId);
if (code === 0) {
this._showDownloadSuccessUI(randomId, filename, ext, thumbnail);
this.state.downloadedItems.add(randomId);
this._updateClearAllButton();
} else if (code !== null) {
// code is null if aborted, so only show error if it's a real exit code
this._handleDownloadError(
new Error(`Download process exited with code ${code}.`),
randomId
);
}
this._processQueue();
if ($(CONSTANTS.DOM_IDS.QUIT_CHECKED).checked) {
ipcRenderer.send("quit", "quit");
}
}
/**
* Handles an error during the download process.
*/
_handleDownloadError(error, randomId) {
if (
error.name === "AbortError" ||
error.message.includes("AbortError")
) {
console.log(`Download ${randomId} was aborted.`);
this.state.currentDownloads = Math.max(
0,
this.state.currentDownloads - 1
);
this.state.downloadControllers.delete(randomId);
this._processQueue();
return; // Don't treat user cancellation as an error
}
this.state.currentDownloads--;
this.state.downloadControllers.delete(randomId);
console.error("Download Error:", error);
const progressEl = $(`${randomId}_prog`);
if (progressEl) {
progressEl.textContent = i18n.__("errorHoverForDetails");
progressEl.title = error.message;
}
this._processQueue();
}
/**
* Updates the download options state from the UI elements.
*/
_updateDownloadOptionsFromUI() {
const startTime = $(CONSTANTS.DOM_IDS.START_TIME).value;
const endTime = $(CONSTANTS.DOM_IDS.END_TIME).value;
const duration = this.state.videoInfo.duration;
const startSeconds = this.parseTime(startTime);
const endSeconds = this.parseTime(endTime);
if (
startSeconds === 0 &&
(endSeconds === duration || endSeconds === 0)
) {
this.state.downloadOptions.rangeCmd = "";
this.state.downloadOptions.rangeOption = "";
} else {
const start = startTime || "0";
const end = endTime || "inf";
this.state.downloadOptions.rangeCmd = `*${start}-${end}`;
this.state.downloadOptions.rangeOption = "--download-sections";
}
if ($(CONSTANTS.DOM_IDS.SUB_CHECKED).checked) {
this.state.downloadOptions.subs = "--write-subs";
this.state.downloadOptions.subLangs = "--sub-langs all";
} else {
this.state.downloadOptions.subs = "";
this.state.downloadOptions.subLangs = "";
}
}
/**
* Resets the UI state for a new link.
*/
_resetUIForNewLink() {
this._hideInfoPanel();
$(CONSTANTS.DOM_IDS.LOADING_WRAPPER).style.display = "flex";
$(CONSTANTS.DOM_IDS.INCORRECT_MSG).textContent = "";
$(CONSTANTS.DOM_IDS.ERROR_BTN).style.display = "none";
$(CONSTANTS.DOM_IDS.ERROR_DETAILS).style.display = "none";
$(CONSTANTS.DOM_IDS.VIDEO_FORMAT_SELECT).innerHTML = "";
$(CONSTANTS.DOM_IDS.AUDIO_FORMAT_SELECT).innerHTML = "";
const noAudioTxt = i18n.__("noAudio");
$(
CONSTANTS.DOM_IDS.AUDIO_FOR_VIDEO_FORMAT_SELECT
).innerHTML = `${noAudioTxt} `;
}
/**
* Populates the video and audio format elements.
* @param {Array} formats The formats array from yt-dlp metadata.
*/
_populateFormatSelectors(formats) {
const videoSelect = $(CONSTANTS.DOM_IDS.VIDEO_FORMAT_SELECT);
const audioSelect = $(CONSTANTS.DOM_IDS.AUDIO_FORMAT_SELECT);
const audioForVideoSelect = $(
CONSTANTS.DOM_IDS.AUDIO_FOR_VIDEO_FORMAT_SELECT
);
const NBSP = " ";
let maxVideoQualityLen = 0;
let maxAudioQualityLen = 0;
formats.forEach((format) => {
if (format.video_ext !== "none" && format.vcodec !== "none") {
const quality = `${format.height || "???"}p${
format.fps === 60 ? "60" : ""
}`;
if (quality.length > maxVideoQualityLen) {
maxVideoQualityLen = quality.length;
}
} else if (
format.acodec !== "none" &&
format.video_ext === "none"
) {
const formatNote =
i18n.__(format.format_note) || i18n.__("unknownQuality");
if (formatNote.length > maxAudioQualityLen) {
maxAudioQualityLen = formatNote.length;
}
}
});
const videoQualityPadding = maxVideoQualityLen;
const audioQualityPadding = maxAudioQualityLen;
const extPadding = 5; // "mp4", "webm"
const vcodecPadding = 5; // "avc1", "vp9"
const filesizePadding = 10; // "12.48 MB"
const {videoQuality, videoCodec, showMoreFormats} =
this.state.preferences;
let bestMatchHeight = 0;
formats.forEach((f) => {
if (
f.height &&
f.height <= videoQuality &&
f.height > bestMatchHeight &&
f.video_ext !== "none"
) {
bestMatchHeight = f.height;
}
});
if (bestMatchHeight === 0 && formats.length > 0) {
bestMatchHeight = Math.max(
...formats.filter((f) => f.height).map((f) => f.height)
);
}
const availableCodecs = new Set(
formats
.filter((f) => f.height === bestMatchHeight && f.vcodec)
.map((f) => f.vcodec.split(".")[0])
);
const finalCodec = availableCodecs.has(videoCodec)
? videoCodec
: [...availableCodecs].pop();
let isAVideoSelected = false;
formats.forEach((format) => {
let sizeInMB = null;
let isApprox = false;
if (format.filesize) {
sizeInMB = format.filesize / 1000000;
} else if (format.filesize_approx) {
sizeInMB = format.filesize_approx / 1000000;
isApprox = true;
} else if (this.state.videoInfo.duration && format.tbr) {
sizeInMB = (this.state.videoInfo.duration * format.tbr) / 8192;
isApprox = true;
}
const displaySize = sizeInMB
? `${isApprox ? "~" : ""}${sizeInMB.toFixed(2)} MB`
: i18n.__("unknownSize");
if (format.video_ext !== "none" && format.vcodec !== "none") {
if (
!showMoreFormats &&
(format.ext === "webm" || format.vcodec?.startsWith("vp"))
) {
return;
}
let isSelected = false;
if (
!isAVideoSelected &&
format.height === bestMatchHeight &&
format.vcodec?.startsWith(finalCodec)
) {
isSelected = true;
isAVideoSelected = true;
}
const quality = `${format.height || "???"}p${
format.fps === 60 ? "60" : ""
}`;
const hasAudio = format.acodec !== "none" ? " 🔊" : "";
const col1 = quality.padEnd(videoQualityPadding + 1, NBSP);
const col2 = format.ext.padEnd(extPadding, NBSP);
const col4 = displaySize.padEnd(filesizePadding, NBSP);
let optionText;
if (showMoreFormats) {
const vcodec = format.vcodec?.split(".")[0] || "";
const col3 = vcodec.padEnd(vcodecPadding, NBSP);
optionText = `${col1} | ${col2} | ${col3} | ${col4}${hasAudio}`;
} else {
optionText = `${col1} | ${col2} | ${col4}${hasAudio}`;
}
const option = `${optionText} `;
videoSelect.innerHTML += option;
} else if (
format.acodec !== "none" &&
format.video_ext === "none"
) {
if (!showMoreFormats && format.ext === "webm") return;
const audioExt = format.ext === "webm" ? "opus" : format.ext;
const formatNote =
i18n.__(format.format_note) || i18n.__("unknownQuality");
const audioExtPadded = audioExt.padEnd(extPadding, NBSP);
const audioQualityPadded = formatNote.padEnd(
audioQualityPadding,
NBSP
);
const audioSizePadded = displaySize.padEnd(
filesizePadding,
NBSP
);
const option_audio = `${audioQualityPadded} | ${audioExtPadded} | ${audioSizePadded} `;
audioSelect.innerHTML += option_audio;
audioForVideoSelect.innerHTML += option_audio;
}
});
if (
formats.every((f) => f.acodec === "none" || f.acodec === undefined)
) {
$(CONSTANTS.DOM_IDS.AUDIO_PRESENT_SECTION).style.display = "none";
} else {
$(CONSTANTS.DOM_IDS.AUDIO_PRESENT_SECTION).style.display = "block";
}
}
/**
* Shows the hidden panel with video information.
*/
_displayInfoPanel() {
const info = this.state.videoInfo;
const titleContainer = $(CONSTANTS.DOM_IDS.TITLE_CONTAINER);
titleContainer.innerHTML = ""; // Clear previous content
titleContainer.append(
Object.assign(document.createElement("b"), {
textContent: i18n.__("title") + ": ",
}),
Object.assign(document.createElement("input"), {
className: "title",
id: CONSTANTS.DOM_IDS.TITLE_INPUT,
type: "text",
value: `${info.title} [${info.id}]`,
onchange: (e) => (this.state.videoInfo.title = e.target.value),
})
);
document
.querySelectorAll(CONSTANTS.DOM_IDS.URL_INPUTS)
.forEach((el) => {
el.value = info.url;
});
const hiddenPanel = $(CONSTANTS.DOM_IDS.HIDDEN_PANEL);
hiddenPanel.style.display = "inline-block";
hiddenPanel.classList.add("scaleUp");
}
/**
* Creates the initial UI element for a new download.
*/
_createDownloadUI(randomId, job) {
const itemHTML = `
${i18n.__(
job.type === "video" ? "video" : "audio"
)}
${job.title}
${i18n.__(
"preparing"
)}
`;
$(CONSTANTS.DOM_IDS.DOWNLOAD_LIST).insertAdjacentHTML(
"beforeend",
itemHTML
);
$(`${randomId}_close`).addEventListener("click", () =>
this._cancelDownload(randomId)
);
}
/**
* Updates the progress bar and speed for a download item.
*/
_updateProgressUI(randomId, progress) {
const speedEl = $(`${randomId}_speed`);
const progEl = $(`${randomId}_prog`);
if (!speedEl || !progEl) return;
let fillEl = progEl.querySelector(".custom-progress-fill");
if (!fillEl) {
progEl.innerHTML = "";
const bar = document.createElement("div");
bar.className = "custom-progress";
fillEl = document.createElement("div");
fillEl.className = "custom-progress-fill";
bar.appendChild(fillEl);
progEl.appendChild(bar);
}
if (progress.percent === 100) {
fillEl.style.width = progress.percent + "%";
speedEl.textContent = "";
progEl.textContent = i18n.__("processing");
ipcRenderer.send("progress", 0);
return;
}
speedEl.textContent = `${i18n.__("speed")}: ${
progress.currentSpeed || "0 B/s"
}`;
fillEl.style.width = progress.percent + "%";
ipcRenderer.send("progress", progress.percent / 100);
}
/**
* Updates a download item's UI to show it has completed successfully.
*/
_showDownloadSuccessUI(randomId, filename, ext, thumbnail) {
const progressEl = $(`${randomId}_prog`);
if (!progressEl) return;
const fullFilename = `${filename}.${ext}`;
const fullPath = join(this.state.downloadDir, fullFilename);
progressEl.innerHTML = ""; // Clear progress bar
const link = document.createElement("b");
link.textContent = i18n.__("fileSavedClickToOpen");
link.style.cursor = "pointer";
link.onclick = () => {
ipcRenderer.send("show-file", fullPath);
};
progressEl.appendChild(link);
$(`${randomId}_speed`).textContent = "";
// Send desktop notification
new Notification("ytDownloader", {
body: fullFilename,
icon: thumbnail,
}).onclick = () => {
shell.showItemInFolder(fullPath);
};
// Add to download history
promises
.stat(fullPath)
.then((stat) => {
const fileSize = stat.size || 0;
ipcRenderer
.invoke("add-to-history", {
title: this.state.videoInfo.title,
url: this.state.videoInfo.url,
filename: filename,
filePath: fullPath,
fileSize: fileSize,
format: ext,
thumbnail: thumbnail,
duration: this.state.videoInfo.duration,
})
.catch((err) =>
console.error("Error adding to history:", err)
);
})
.catch((error) => console.error("Error saving to history:", error));
}
/**
* Shows an error message in the main UI.
*/
_showError(errorMessage, url) {
$(CONSTANTS.DOM_IDS.INCORRECT_MSG).textContent =
i18n.__("errorNetworkOrUrl");
$(CONSTANTS.DOM_IDS.ERROR_BTN).style.display = "inline-block";
const errorDetails = $(CONSTANTS.DOM_IDS.ERROR_DETAILS);
errorDetails.innerHTML = `URL: ${url} ${errorMessage}`;
errorDetails.title = i18n.__("clickToCopy");
}
/**
* Hides the info panel with an animation.
*/
_hideInfoPanel() {
const panel = $(CONSTANTS.DOM_IDS.HIDDEN_PANEL);
if (panel.style.display !== "none") {
panel.classList.remove("scaleUp");
panel.classList.add("scale");
setTimeout(() => {
panel.style.display = "none";
panel.classList.remove("scale");
}, 400);
}
}
/**
* Displays a temporary popup message.
*/
_showPopup(text, isError = false) {
let popupContainer = document.getElementById("popupContainer");
if (!popupContainer) {
popupContainer = document.createElement("div");
popupContainer.id = "popupContainer";
popupContainer.className = "popup-container";
document.body.appendChild(popupContainer);
}
const popup = document.createElement("span");
popup.textContent = text;
popup.classList.add("popup-item");
popup.style.background = isError ? "#ff6b6b" : "#54abde";
if (isError) {
popup.classList.add("popup-error");
}
popupContainer.appendChild(popup);
setTimeout(() => {
popup.style.opacity = "0";
setTimeout(() => {
popup.remove();
if (popupContainer.childElementCount === 0) {
popupContainer.remove();
}
}, 1000);
}, 2200);
}
/**
* Hides the main menu.
*/
_closeMenu() {
$(CONSTANTS.DOM_IDS.MENU_ICON).style.transform = "rotate(0deg)";
$(CONSTANTS.DOM_IDS.MENU).style.opacity = "0";
setTimeout(
() => ($(CONSTANTS.DOM_IDS.MENU).style.display = "none"),
500
);
}
/**
* Cancels a download in progress or removes it from the queue.
* @param {string} id The ID of the download item.
*/
_cancelDownload(id) {
// If it's an active download
if (this.state.downloadControllers.has(id)) {
this.state.downloadControllers.get(id).abort();
}
// If it's in the queue
this.state.downloadQueue = this.state.downloadQueue.filter(
(job) => job.queueId !== id
);
// If it has been downloaded, remove from the set
this.state.downloadedItems.delete(id);
this._fadeAndRemoveItem(id);
this._updateClearAllButton();
}
/**
* Fades and removes a DOM element.
*/
_fadeAndRemoveItem(id) {
const item = $(id);
if (item) {
item.classList.add("scale");
setTimeout(() => item.remove(), 500);
}
}
/**
* Removes all completed download items from the UI.
*/
_clearAllDownloaded() {
this.state.downloadedItems.forEach((id) => this._fadeAndRemoveItem(id));
this.state.downloadedItems.clear();
this._updateClearAllButton();
}
/**
* Shows or hides the "Clear All" button based on the number of completed items.
*/
_updateClearAllButton() {
const btn = $(CONSTANTS.DOM_IDS.CLEAR_BTN);
btn.style.display =
this.state.downloadedItems.size > 1 ? "inline-block" : "none";
}
/**
* Toggles between audio and video tabs
*/
_defaultVideoToggle() {
let defaultWindow = "video";
if (localStorage.getItem("defaultWindow")) {
defaultWindow = localStorage.getItem("defaultWindow");
}
if (defaultWindow == "video") {
selectVideo();
} else {
selectAudio();
}
}
/**
* @param {string} timeString
*/
parseTime(timeString) {
const parts = timeString.split(":").map((p) => parseInt(p.trim(), 10));
let totalSeconds = 0;
if (parts.length === 3) {
// H:MM:SS format
const [hrs, mins, secs] = parts;
if (
isNaN(hrs) ||
isNaN(mins) ||
isNaN(secs) ||
mins < 0 ||
mins > 59 ||
secs < 0 ||
secs > 59
)
return NaN;
totalSeconds = hrs * 3600 + mins * 60 + secs;
} else if (parts.length === 2) {
// MM:SS format
const [mins, secs] = parts;
if (isNaN(mins) || isNaN(secs) || secs < 0 || secs > 59) return NaN;
totalSeconds = mins * 60 + secs;
} else if (parts.length === 1) {
const [secs] = parts;
if (isNaN(secs)) return NaN;
totalSeconds = secs;
} else {
return NaN;
}
return totalSeconds;
}
_formatTime(duration) {
if (duration === null) {
return "";
}
const hrs = Math.floor(duration / 3600);
const mins = Math.floor((duration % 3600) / 60);
const secs = Math.floor(duration % 60);
const paddedMins = String(mins).padStart(2, "0");
const paddedSecs = String(secs).padStart(2, "0");
if (hrs > 0) {
// H:MM:SS format
return `${hrs}:${paddedMins}:${paddedSecs}`;
} else {
// MM:SS format
return `${paddedMins}:${paddedSecs}`;
}
}
/**
* @param {HTMLElement} movedSlider
*/
_updateSliderUI(movedSlider) {
const minSlider = $(CONSTANTS.DOM_IDS.MIN_SLIDER);
const maxSlider = $(CONSTANTS.DOM_IDS.MAX_SLIDER);
const minTimeDisplay = $(CONSTANTS.DOM_IDS.START_TIME);
const maxTimeDisplay = $(CONSTANTS.DOM_IDS.END_TIME);
const rangeHighlight = $(CONSTANTS.DOM_IDS.SLIDER_RANGE_HIGHLIGHT);
let minValue = parseInt(minSlider.value);
let maxValue = parseInt(maxSlider.value);
const minSliderVal = parseInt(minSlider.min);
const maxSliderVal = parseInt(minSlider.max);
const sliderRange = maxSliderVal - minSliderVal;
// Prevent sliders from crossing each other
if (minValue >= maxValue) {
if (movedSlider && movedSlider.id === "min-slider") {
// Min must be at least 1 second less than Max
minValue = Math.max(minSliderVal, maxValue - 1);
minSlider.value = minValue;
} else {
// Max must be at least 1 second more than Min
maxValue = Math.min(maxSliderVal, minValue + 1);
maxSlider.value = maxValue;
}
}
minTimeDisplay.value = this._formatTime(minValue);
maxTimeDisplay.value = this._formatTime(maxValue);
const minPercent = ((minValue - minSliderVal) / sliderRange) * 100;
const maxPercent = ((maxValue - minSliderVal) / sliderRange) * 100;
rangeHighlight.style.left = `${minPercent}%`;
rangeHighlight.style.width = `${maxPercent - minPercent}%`;
}
/**
* @param {Event} e
*/
_handleTimeInputChange = (e) => {
const input = e.target;
let newSeconds = this.parseTime(input.value);
const minSlider = $("min-slider");
const maxSlider = $("max-slider");
if (isNaN(newSeconds)) {
input.value = this._formatTime(
input.id === "min-time" ? minSlider.value : maxSlider.value
);
return;
}
const minSliderVal = parseInt(minSlider.min);
const maxSliderVal = parseInt(minSlider.max);
newSeconds = Math.max(minSliderVal, Math.min(maxSliderVal, newSeconds));
if (input.id === "min-time") {
if (newSeconds >= parseInt(maxSlider.value)) {
newSeconds = Math.max(
minSliderVal,
parseInt(maxSlider.value) - 1
);
}
minSlider.value = newSeconds;
} else {
if (newSeconds <= parseInt(minSlider.value)) {
newSeconds = Math.min(
maxSliderVal,
parseInt(minSlider.value) + 1
);
}
maxSlider.value = newSeconds;
}
this._updateSliderUI(null);
};
/**
* Sets the maximum duration for the video and updates the slider's max range.
* @param {number} duration - The total length of the video in seconds (must be an integer >= 1).
*/
setVideoLength(duration) {
const minSlider = $(CONSTANTS.DOM_IDS.MIN_SLIDER);
const maxSlider = $(CONSTANTS.DOM_IDS.MAX_SLIDER);
if (typeof duration !== "number" || duration < 1) {
console.error(
"Invalid duration provided to setVideoLength. Must be a number greater than 0."
);
minSlider.max = 0;
maxSlider.max = 0;
minSlider.value = 0;
maxSlider.value = 0;
return;
}
minSlider.max = duration;
maxSlider.max = duration;
const defaultMin = 0;
const defaultMax = duration;
minSlider.value = defaultMin;
maxSlider.value = defaultMax;
this._updateSliderUI(null);
}
}
// --- Application Entry Point ---
document.addEventListener("DOMContentLoaded", () => {
const app = new YtDownloaderApp();
app.initialize();
});
================================================
FILE: src/types.d.ts
================================================
type format = {
vcodec?: string,
acodec?: string,
ext: string,
filesize?: number,
format_id: string,
format_note: string,
height: number,
resolution: string,
video_ext: string,
audio_ext: string,
filesize_approx?: number,
tbr: number,
fps: number,
}
type info = {
title: string,
id: string,
thumbnail: string,
duration: number,
formats: format[],
extractor_key: string,
}
export {
format,
info
}
================================================
FILE: translations/ar-SA.json
================================================
{
"preferences": "الإعدادات",
"about": "عن التطبيق",
"downloadLocation": "مسار التنزيل",
"currentDownloadLocation": "مسار التنزيل الحالي ",
"enableTransparentDarkMode": "تشغيل الوضع الداكن الشفاف (لينكس فقط، يحتاج إعادة تشغيل للبرنامج)",
"downloadingNecessaryFilesWait": "بالرجاء الانتظار، يتم تنزيل الملفات الضرورية",
"video": "فيديو",
"audio": "صوت",
"title": "العنوان ",
"selectFormat": "اختر الصيغة ",
"download": "تحميل",
"selectDownloadLocation": "اختر مسار التنزيل",
"moreOptions": "المزيد من الخيارات",
"start": "ابدأ",
"selectLanguageRelaunch": "اختر اللغة (يحتاج إعادة تشغيل للبرنامج)",
"downloadTimeRange": "تنزيل نطاق زمني محدد",
"end": "إنهاء",
"timeRangeStartEmptyHint": "إذا تُركت فارغة، ستبدأ من البداية",
"timeRangeEndEmptyHint": "إذا تُركت فارغة، سيتم التنزيل حتى النهاية",
"homepage": "الصَّفحة الرئيسة",
"aboutAppDescription": "إنه تطبيق مجاني ومفتوح المصدر مبني على Node.js و Electron. تم استخدام yt-dlp للتنزيل",
"sourceCodeAvailable": "كود المصدر متاح ",
"here": "هنا",
"processing": "جارٍ المعالجة",
"errorNetworkOrUrl": "حدثت بعض الأخطاء. تفحص الإنترنت الخاص بك واستخدم الرابط الصحيح",
"errorFailedFileDownload": "فشل تنزيل الملفات الضرورية. بالرجاء تفحص الإنترنت الخاص بك والمحاولة مرة أُخرى",
"tryAgain": "حاول مجددًا",
"unknownSize": "حجم غير معلوم",
"megabyte": "ميجابايت",
"unknownQuality": "جودة غير معروفة",
"downloading": "جاري التحميل...",
"errorHoverForDetails": "حدثت بعض المشاكل. ضع المؤشر هُنا لترى التفاصيل",
"fileSavedSuccessfully": "تم حفظ المِلَف بنجاح",
"fileSavedClickToOpen": "تم حفظ المِلَف. انقر للفتح",
"preparing": "قيد التحضير...",
"progress": "مستوى التقدُّم",
"speed": "السرعة",
"quality": "الجودة",
"restartApp": "أعد تشغيل التطبيق",
"subtitles": "الترجمة",
"downloadSubtitlesAvailable": "حمّل الترجَمة إذا وجدت",
"downloadSubtitlesAuto": "حمّل التّرجَمة التي تم إنشاؤها تِلقائيا",
"extractAudioFromVideo": "استخرج الصوت من الفيديو",
"extract": "استخرج",
"downloadingNecessaryFiles": "جاري تحميل الملفات الضرورية",
"qualityLow": "منخفضة",
"qualityMedium": "متوسطة",
"appDescription": "يتيح لك YtDownloader تنزيل مقاطع الفيديو والصوت من مئات المواقع مثل يوتيوب، وفيسبوك، وإنستاغرام، وتيكتوك، وتويتر وما إلى ذلك",
"pasteText": "انقر للصق رابط الفيديو من الحافظة",
"pastePlaylistLinkTooltip": "انقر للصق رابط قائمة التشغيل من الحافظة",
"link": "الرابط:",
"downloadingPlaylist": "جاري تنزيل قائمة التشغيل:",
"downloadPlaylistButton": "حمِل قائمة تشغيل",
"playlistDownloaded": "تم تنزيل قائمة التشغيل",
"cookiesWarning": "يتيح هذا الخِيار تنزيل المحتوى المقيد. ستجد أخطاء إذا لم توجد ملفات تعريف الارتباط",
"selectBrowserForCookies": "اختر المتصفح لاستخدام ملفات تعريف الارتباط منه",
"none": "لا شَيْء",
"updateAvailableDownloadPrompt": "يتوفر إصدار جديد من البرنامج ، هل تريد تنزيله؟",
"updateAvailablePrompt": "يتوفر إصدار جديد من البرنامج ، هل تريد تنزيله؟",
"update": "حدّث",
"no": "لا",
"installAndRestartPrompt": "هل تريد التثبيت وإعادة التشغيل الآن؟",
"restart": "إعادة تشغيل البرنامَج",
"later": "في وقت لاحق",
"extractAudio": "استخرج الصوت",
"selectVideoFormat": "اختر صيغة الفيديو ",
"selectAudioFormat": "اختر صيغة الصوت ",
"maxActiveDownloads": "أقصى عدد للتنزيلات الجارية",
"preferredVideoQuality": "جودة الفيديو المفضلة",
"preferredAudioFormat": "صيغة الصوت المفضلة",
"best": "الأفضل",
"fileSaved": "تم حفظ الملف",
"openDownloadFolder": "فتح مجلد التنزيل",
"path": "المسار:",
"selectConfigFile": "اختر مِلَف الإعداد",
"useConfigFile": "استخدام مِلَف الإعداد",
"playlistFilenameFormat": "صيغة تسميه الملفات لقوائم التشغيل",
"playlistFolderNameFormat": "صيغة تسميه المجلدات لقوائم التشغيل",
"resetToDefault": "إعادة الإعدادات الافتراضية",
"playlistRange": "نطاق قائمة التشغيل",
"thumbnail": "الصورة المصغرة",
"linkAdded": "تم إضافة الرابط",
"downloadThumbnails": "تنزيل الصور المصغرة",
"saveVideoLinksToFile": "حفظ روابط الفيديو إلى مِلَف",
"closeAppToTray": "إغلاق التطبيق إلى غطاء النظام",
"useConfigFileCheckbox": "استخدم مِلَف الإعداد",
"openApp": "فتح تطبيق",
"pasteVideoLink": "لصق رابط الفيديو",
"quit": "خروج",
"errorDetails": "تفاصيل الخطأ",
"clickToCopy": "انقر للنسخ",
"copiedText": "نص منسوخ",
"qualityNormal": "عادي",
"qualityGood": "جيد",
"qualityBad": "سىء",
"qualityWorst": "أسوأ",
"selectQuality": "حدد الجودة",
"disableAutoUpdates": "تعطيل التحديثات التلقائية",
"qualityUltraLow": "منخفضه جداً",
"closeAppOnFinish": "إغلاق التطبيق عند انتهاء التنزيل",
"auto": "تلقائي",
"theme": "السمة",
"themeLight": "فاتح",
"themeDark": "داكن",
"themeFrappe": "Frappé",
"themeOneDark": "One Dark",
"themeMatrix": "مصفوفة",
"themeSolarizedDark": "داكن مُشمس",
"preferredVideoCodec": "ترميز الفيديو المفضل",
"showMoreFormatOptions": "إظهار المزيد من خيارات التنسيق",
"flatsealPermissionWarning": "تحتاج إلى إعطاء الإذن للتطبيق للوصول إلى المجلد الرئيس لاستخدام هذا. يمكنك ذلك باستخدام Flatseal عن طريق تمكين الإذن ذو النص 'filesystem=home'",
"noAudio": "دون صوت",
"proxy": "الوكيل",
"clearDownloads": "مسح سجل التنزيلات",
"compressor": "ضاغط",
"dragAndDropFiles": "اسحب وأفلت الملف(الملفات)",
"chooseFiles": "اختر ملف(ملفات)",
"noFilesSelected": "لم يتم اختيار أي ملفات",
"videoFormat": "صيغة الفيديو",
"videoEncoder": "مُرمز الفيديو",
"compressionSpeed": "سرعة الضغط",
"videoQuality": "جودة الفيديو",
"audioFormat": "صيغة الصوت",
"outputSuffix": "لاحقة المخرجات",
"outputInSameFolder": "إخراج في نفس المجلد",
"selectCustomFolder": "اختر مجلدًا مخصصًا",
"startCompression": "بدء الضغط",
"cancel": "إلغاء",
"errorClickForDetails": "خطأ! انقر للتفاصيل",
"homebrewYtDlpWarning": "يجب عليك تنزيل yt-dlp من homebrew أولاً",
"openHomebrew": "افتح Homebrew",
"downloadHistory": "سجل التنزيلات",
"close": "إغلاق",
"searchByTitleOrUrl": "ابحث بالعنوان أو الرابط...",
"allFormats": "جميع الصيغ",
"exportAsJson": "تصدير كـ JSON",
"exportAsCsv": "تصدير كـ CSV",
"clearAllHistory": "مسح كل السجل",
"noDownloadsYet": "لا توجد تنزيلات بعد",
"downloadHistoryPlaceholder": "سيظهر سجل التنزيلات الخاص بك هنا",
"format": "الصيغة",
"size": "الحجم",
"date": "التاريخ",
"duration": "المدة",
"copyUrl": "نسخ الرابط",
"open": "فتح",
"delete": "حذف",
"totalDownloads": "إجمالي التنزيلات",
"totalSize": "إجمالي الحجم",
"mostCommonFormat": "الصيغة الأكثر شيوعًا",
"urlCopiedToClipboard": "تم نسخ الرابط إلى الحافظة!",
"confirmDeleteHistoryItem": "هل أنت متأكد من أنك تريد حذف هذا العنصر من السجل؟",
"confirmClearAllHistory": "هل أنت متأكد من أنك تريد مسح كل سجل التنزيلات؟ لا يمكن التراجع عن هذا!",
"fileDoesNotExist": "File does not exist anymore",
"updatingYtdlp": "Updating yt-dlp",
"updatedYtdlp": "Updated yt-dlp",
"ytDlpUpdateRequired": "If yt-dlp is updating, wait for the update to finish. If you have installed yt-dlp by yourself, please update it.",
"failedToDeleteHistoryItem": "Failed to delete history item",
"customArgsTxt": "Set custom yt-dlp options.",
"learnMore": "Learn more",
"updateError": "An error occurred during the update process",
"unableToAccessDir": "The program cannot access that folder",
"downloadingUpdate": "Downloading update"
}
================================================
FILE: translations/bn-BD.json
================================================
{
"preferences": "পছন্দসমূহ",
"about": "তথ্য",
"downloadLocation": "ডাউনলোডগুলির স্থান",
"currentDownloadLocation": "এই মুহূর্তে ডাউনলোড রত ফাইল এর অবস্থান ",
"enableTransparentDarkMode": "স্বচ্ছল ডার্ক মোড চালু করুন (শুধুমাত্র লিনাক্স এ কার্যকর, অ্যাপ বন্ধ করে চালু করতে হবে)",
"downloadingNecessaryFilesWait": "অপেক্ষা করুন, দরকারি ফাইল সমূহ ডাউনলোড হচ্ছে",
"video": "ভিডিও",
"audio": "অডিও",
"title": "নাম ",
"selectFormat": "ফরমেট বেছে নিন ",
"download": "ডাউনলোড",
"selectDownloadLocation": "ফাইল ডাউনলোড এর স্থান বেছে নিন",
"moreOptions": "আরও অপশন দেখান",
"start": "শুরু",
"selectLanguageRelaunch": "ভাষা বেছে নিন (বন্ধ করে চালু করতে হবে)",
"downloadTimeRange": "নির্ধারিত সময় সীমা এর মাঝের টুকু ডাউনলোড করো",
"end": "শেষ",
"timeRangeStartEmptyHint": "খালি রাখলে প্রথম থেকে শুরু হবে",
"timeRangeEndEmptyHint": "খালি রাখলে শেষ সীমা শেষ এ হবে|",
"homepage": "হোমপেজ",
"aboutAppDescription": "এটি একটি মুক্ত/উন্মুক্ত উৎস সফটওয়্যার যা Node.js এবং Electron বেবহার করে | yt-dlp ডাউনলোড এর জন্য ব্যবহৃত হয় |",
"sourceCodeAvailable": "উৎস কোড টি প্রদান করা হয়েছে ",
"here": "এখানে",
"processing": "প্রক্রিয়াকরণ চলমান",
"errorNetworkOrUrl": "কোনো ত্রুটি দেখা দিয়েছে| আপনার নেটওয়ার্ক চেক করুন এবং সঠিক ইউআরএল ব্যবহার করুন",
"errorFailedFileDownload": "দরকারি ফাইল সমূহ ডাউনলোড করা যায়নি| নেটওয়ার্ক চেক করে পুনরায় চেষ্টা করুন",
"tryAgain": "আবার চেষ্টা করুন",
"unknownSize": "ফাইল সাইজ জানা নেই",
"megabyte": "এমবি",
"unknownQuality": "ভিডিও কোয়ালিটি অজানা",
"downloading": "ডাউনলোড হচ্ছে...",
"errorHoverForDetails": "কোনো ত্রুটি দেখা দিয়েছে| এর উপরে মাউস রেখে আরও তথ্য দেখুন",
"fileSavedSuccessfully": "ডাউনলোড প্রক্রিয়া সফল হয়েছে",
"fileSavedClickToOpen": "ফাইল সেভ হয়েছে| খুলতে ক্লিক করুন",
"preparing": "প্রস্তুত হচ্ছে...",
"progress": "অবস্থান",
"speed": "স্পিড",
"quality": "কোয়ালিটি",
"restartApp": "অ্যাপ পুনরায় চালু করুন",
"subtitles": "সাবটাইটেল",
"downloadSubtitlesAvailable": "সাবটাইটেল থাকলে ডাউনলোড করুন",
"downloadSubtitlesAuto": "অটো জেনারেটেড সাবটাইটেল ডাউনলোড করুন",
"extractAudioFromVideo": "ভিডিও থেকে অডিও আলাদা করুন",
"extract": "আলাদা করুন",
"downloadingNecessaryFiles": "দরকারি ফাইল সমূহ ডাউনলোড করুন",
"qualityLow": "নিম্ন",
"qualityMedium": "মদ্ধ",
"appDescription": "ytDownloader এর মাধ্যমে আপনি নানান ওয়েবসাইট থেকে ভিডিও এবং অডিও ডাউনলোড করতে পারবেন| যেমন ইউটুবে, ফেইসবুক, ইনস্টাগ্রাম, টুইটার ইত্যাদি",
"pasteText": "ক্লিপবোর্ড থেকে ভিডিও লিংক পেস্ট করতে ক্লিক করুন",
"pastePlaylistLinkTooltip": "ক্লিপবোর্ড থেকে প্লেলিস্ট লিংক পেস্ট করতে ক্লিক করুন",
"link": "লিংক:",
"downloadingPlaylist": "প্লেলিস্ট ডাউনলোড হচ্ছে:",
"downloadPlaylistButton": "প্লেলিস্ট ডাউনলোড করো",
"playlistDownloaded": "প্লেলিস্ট ডাউনলোড শেষ",
"cookiesWarning": "এর মাধ্যমে আপনি রেস্ট্রিক্টেড কনটেন্ট ডাউনলোড করতে পারবেন| \"কুকিজ\" না থাকলে সমস্যা দেখা দিবে",
"selectBrowserForCookies": "কুকিজ সিলেক্ট করতে ব্রাউসার বেছে নিন",
"none": "কোনটাই না",
"updateAvailableDownloadPrompt": "নতুন ভার্সন পাওয়া গেছে, আপনি কি সেটা ডাউনলোড করতে চান?",
"updateAvailablePrompt": "নতুন ভার্সন পাওয়া গেছে, আপনি কি আপডেট করতে চান?",
"update": "আপডেট",
"no": "না",
"installAndRestartPrompt": "ইনস্টল করে রিস্টার্ট করবো এখনই?",
"restart": "রিস্টার্ট",
"later": "পরে",
"extractAudio": "অডিও আলাদা করুন",
"selectVideoFormat": "ভিডিও ফরমেট বেছে নিন ",
"selectAudioFormat": "অডিও ফরমেট বেছে নিন ",
"maxActiveDownloads": "সর্বোচ্চ কত গুলো ডাউনলোড একই মুহূর্তে চলতে পারবে",
"preferredVideoQuality": "পছন্দের ভিডিও কোয়ালিটি",
"preferredAudioFormat": "পছন্দের অডিও ফরমেট",
"best": "সেরা",
"fileSaved": "ফাইল সেভ হয়েছে",
"openDownloadFolder": "ডাউনলোড ফোল্ডার খুলুন",
"path": "পথ:",
"selectConfigFile": "কনফিগ ফাইল বেছে নিন",
"useConfigFile": "কনফিগ ফাইল ব্যবহার করুন",
"playlistFilenameFormat": "ফাইল এর নাম এর ফরমেট",
"playlistFolderNameFormat": "প্লেলিস্ট এর ফোল্ডার গুলির নাম এর ফরমেট",
"resetToDefault": "পুনরায় ডিফল্টে চলে যান",
"playlistRange": "প্লেলিস্ট এর রেঞ্জ",
"thumbnail": "থাম্বনেইল",
"linkAdded": "লিংক যুক্ত হয়েছে",
"downloadThumbnails": "থাম্বনেইল ডাউনলোড করুন",
"saveVideoLinksToFile": "ভিডিও লিংক গুলো একটি ফাইল এ সেভ করুন",
"closeAppToTray": "অ্যাপ মিনি মাইজ করুন",
"useConfigFileCheckbox": "কনফিগ ফাইল ব্যবহার করুন",
"openApp": "অ্যাপ খুলুন",
"pasteVideoLink": "ভিডিও লিংক পেস্ট করুন",
"quit": "বন্ধ",
"errorDetails": "সমস্যার বিবরণ",
"clickToCopy": "কপি করতে ক্লিক করুন",
"copiedText": "লেখা কপি হয়েছে",
"qualityNormal": "সাধারণ",
"qualityGood": "ভালো",
"qualityBad": "খারাপ",
"qualityWorst": "সবচেয়ে খারাপ",
"selectQuality": "কোয়ালিটি বেছে নিন",
"disableAutoUpdates": "নিজ থেকে আপডেট হওয়া বন্ধ করুন",
"qualityUltraLow": "সর্বনিম্ন",
"closeAppOnFinish": "ডাউনলোড শেষ হলে অ্যাপ নিজেথেকে বন্ধ হয়ে যাক",
"auto": "অটো",
"theme": "থিম",
"themeLight": "লাইট",
"themeDark": "ডার্ক",
"themeFrappe": "ফ্রাপে",
"themeOneDark": "ওয়ান ডার্ক",
"themeMatrix": "মেট্রিক্স",
"themeSolarizedDark": "সোলাররাইসড ডার্ক",
"preferredVideoCodec": "পছন্দের ভিডিও কোডেক",
"showMoreFormatOptions": "আরও ফরমেট অপসন দেখান",
"flatsealPermissionWarning": "আপনাকে এই অ্যাপ তা কে যথাযথ অনুমতি দিতে হবে. এইটা করতে পারেন \"ফ্ল্যাটসিল\" নামক অ্যাপ এর মাদ্ধমে 'filesystem=home' হিসেবে চিহ্নিত পারমিশন তা কে চালু করে দিয়ে|",
"noAudio": "অডিও বিহীন",
"proxy": "প্রক্সী",
"clearDownloads": "ডাউনলোড লিস্ট মুছে দিন",
"compressor": "কম্প্রেসার",
"dragAndDropFiles": "ফাইল(গুলি) টেনে এনে রাখুন",
"chooseFiles": "ফাইল(গুলি) বেছে নিন",
"noFilesSelected": "কোনো ফাইল বেছে নেওয়া হয়নি",
"videoFormat": "ভিডিও ফরম্যাট",
"videoEncoder": "ভিডিও এনকোডার",
"compressionSpeed": "কম্প্রেশন স্পিড",
"videoQuality": "ভিডিও কোয়ালিটি",
"audioFormat": "অডিও ফরম্যাট",
"outputSuffix": "আউটপুট সাফিক্স",
"outputInSameFolder": "একই ফোল্ডারে আউটপুট",
"selectCustomFolder": "কাস্টম ফোল্ডার বেছে নিন",
"startCompression": "কম্প্রেশন শুরু করুন",
"cancel": "বাতিল",
"errorClickForDetails": "ত্রুটি! বিস্তারিত জানতে ক্লিক করুন",
"homebrewYtDlpWarning": "আপনাকে প্রথমে হোমব্রু থেকে yt-dlp ডাউনলোড করতে হবে",
"openHomebrew": "হোমব্রু খুলুন",
"downloadHistory": "ডাউনলোড ইতিহাস",
"close": "বন্ধ করুন",
"searchByTitleOrUrl": "শিরোনাম বা ইউআরএল দিয়ে খুঁজুন...",
"allFormats": "সব ফরম্যাট",
"exportAsJson": "JSON হিসাবে রপ্তানি করুন",
"exportAsCsv": "CSV হিসাবে রপ্তানি করুন",
"clearAllHistory": "সমস্ত ইতিহাস মুছে ফেলুন",
"noDownloadsYet": "এখনো কোনো ডাউনলোড নেই",
"downloadHistoryPlaceholder": "আপনার ডাউনলোড ইতিহাস এখানে দেখা যাবে",
"format": "ফরম্যাট",
"size": "সাইজ",
"date": "তারিখ",
"duration": "সময়কাল",
"copyUrl": "ইউআরএল কপি করুন",
"open": "খুলুন",
"delete": "মুছে ফেলুন",
"totalDownloads": "মোট ডাউনলোড",
"totalSize": "মোট সাইজ",
"mostCommonFormat": "সবচেয়ে সাধারণ ফরম্যাট",
"urlCopiedToClipboard": "ইউআরএল ক্লিপবোর্ডে কপি করা হয়েছে!",
"confirmDeleteHistoryItem": "আপনি কি নিশ্চিত যে আপনি ইতিহাস থেকে এই আইটেমটি মুছে ফেলতে চান?",
"confirmClearAllHistory": "আপনি কি নিশ্চিত যে আপনি সমস্ত ডাউনলোড ইতিহাস মুছে ফেলতে চান? এটি পূর্বাবস্থায় ফেরানো যাবে না!",
"fileDoesNotExist": "File does not exist anymore",
"updatingYtdlp": "Updating yt-dlp",
"updatedYtdlp": "Updated yt-dlp",
"ytDlpUpdateRequired": "If yt-dlp is updating, wait for the update to finish. If you have installed yt-dlp by yourself, please update it.",
"failedToDeleteHistoryItem": "Failed to delete history item",
"customArgsTxt": "Set custom yt-dlp options.",
"learnMore": "Learn more",
"updateError": "An error occurred during the update process",
"unableToAccessDir": "The program cannot access that folder",
"downloadingUpdate": "Downloading update"
}
================================================
FILE: translations/de-DE.json
================================================
{
"preferences": "Einstellungen",
"about": "Über",
"downloadLocation": "Download-Verzeichnis",
"currentDownloadLocation": "Aktuelles Download-Verzeichnis - ",
"enableTransparentDarkMode": "Aktiviere transparenten Dunkelmodus (nur Linux, Neustart erforderlich)",
"downloadingNecessaryFilesWait": "Bitte warten, notwendige Dateien werden heruntergeladen",
"video": "Video",
"audio": "Audio",
"title": "Titel ",
"selectFormat": "Wähle Format ",
"download": "Download",
"selectDownloadLocation": "Wähle Download-Verzeichnis",
"moreOptions": "Weitere Optionen",
"start": "Start",
"selectLanguageRelaunch": "Sprache wählen (Neustart erforderlich)",
"downloadTimeRange": "Bestimmten Zeitraum herunterladen",
"end": "Ende",
"timeRangeStartEmptyHint": "Wenn leer gelassen, wird es am Anfang beginnen",
"timeRangeEndEmptyHint": "Wenn leer gelassen, wird bis zum Ende heruntergeladen",
"homepage": "Homepage",
"aboutAppDescription": "Eine kostenlose Open-Source-App, die auf Node.js und Electron aufgebaut ist. yt-dlp wurde zum Herunterladen verwendet",
"sourceCodeAvailable": "Quellcode verfügbar ",
"here": "hier",
"processing": "Verarbeiten",
"errorNetworkOrUrl": "Ein Fehler ist aufgetreten. Überprüfen Sie Ihr Netzwerk und verwenden Sie eine korrekte URL",
"errorFailedFileDownload": "Fehler beim Herunterladen der benötigten Dateien. Bitte überprüfen Sie Ihr Netzwerk und versuchen Sie es erneut",
"tryAgain": "Erneut versuchen",
"unknownSize": "Unbekannte Größe",
"megabyte": "MB",
"unknownQuality": "Unbekannte Qualität",
"downloading": "Herunterladen...",
"errorHoverForDetails": "Ein Fehler ist aufgetreten. Hovern um Details zu sehen",
"fileSavedSuccessfully": "Datei erfolgreich gespeichert",
"fileSavedClickToOpen": "Datei gespeichert. Klicken zum Öffnen",
"preparing": "Vorbereiten...",
"progress": "Fortschritt",
"speed": "Geschwindigkeit",
"quality": "Qualität",
"restartApp": "App neu starten",
"subtitles": "Untertitel",
"downloadSubtitlesAvailable": "Untertitel herunterladen, falls verfügbar",
"downloadSubtitlesAuto": "Automatisch generierte Untertitel herunterladen",
"extractAudioFromVideo": "Audio aus Video extrahieren",
"extract": "Extrahieren",
"downloadingNecessaryFiles": "Benötigte Dateien herunterladen",
"qualityLow": "niedrig",
"qualityMedium": "mittel",
"appDescription": "ytDownloader ermöglicht es, Videos und Audios von hunderten Websites wie Youtube, Facebook, Instagram, Tiktok, Twitter und vielen mehr herunterzuladen",
"pasteText": "Klicken, um Video-Link aus der Zwischenablage einzufügen",
"pastePlaylistLinkTooltip": "Klicken, um Playlist-Link aus der Zwischenablage einzufügen",
"link": "Link:",
"downloadingPlaylist": "Wiedergabeliste wird heruntergeladen:",
"downloadPlaylistButton": "Playlist herunterladen",
"playlistDownloaded": "Wiedergabeliste heruntergeladen",
"cookiesWarning": "Mit dieser Option können Sie eingeschränkte Inhalte herunterladen. Sie werden Fehler erhalten, wenn Cookies nicht vorhanden sind",
"selectBrowserForCookies": "Browser zum Verwenden von Cookies auswählen",
"none": "Keine",
"updateAvailableDownloadPrompt": "Eine neue Version ist verfügbar, möchten Sie sie herunterladen?",
"updateAvailablePrompt": "Eine neue Version ist verfügbar, möchten Sie aktualisieren?",
"update": "Aktualisierung",
"no": "Nein",
"installAndRestartPrompt": "Jetzt installieren und neu starten?",
"restart": "Neustart",
"later": "Später",
"extractAudio": "Audio extrahieren",
"selectVideoFormat": "Videoformat auswählen ",
"selectAudioFormat": "Audioformat auswählen ",
"maxActiveDownloads": "Maximale Anzahl aktiver Downloads",
"preferredVideoQuality": "Bevorzugte Videoqualität",
"preferredAudioFormat": "Bevorzugtes Audioformat",
"best": "Beste",
"fileSaved": "Datei gespeichert",
"openDownloadFolder": "Downloadordner öffnen",
"path": "Pfad:",
"selectConfigFile": "Konfiguration auswählen",
"useConfigFile": "Konfigurationsdatei verwenden",
"playlistFilenameFormat": "Dateinamenformat für Wiedergabelisten",
"playlistFolderNameFormat": "Ordnername für Wiedergabelisten",
"resetToDefault": "Auf Standard zurücksetzen",
"playlistRange": "Playlist-Bereich",
"thumbnail": "Miniaturansicht",
"linkAdded": "Verlinkung hinzugefügt",
"downloadThumbnails": "Thumbnails herunterladen",
"saveVideoLinksToFile": "Videolinks in einer Datei speichern",
"closeAppToTray": "Schließen Sie die App in der Taskleiste",
"useConfigFileCheckbox": "Konfigurationsdatei verwenden",
"openApp": "App öffnen",
"pasteVideoLink": "Video-Link einfügen",
"quit": "Beenden",
"errorDetails": "Fehler-Details",
"clickToCopy": "Zum Kopieren klicken",
"copiedText": "Text kopiert",
"qualityNormal": "Normal",
"qualityGood": "Gut",
"qualityBad": "Schlecht",
"qualityWorst": "Schlechteste",
"selectQuality": "Qualität auswählen",
"disableAutoUpdates": "Automatische Updates deaktivieren",
"qualityUltraLow": "Sehr niedrig",
"closeAppOnFinish": "App schließen, wenn der Download beendet ist",
"auto": "Auto",
"theme": "Thema",
"themeLight": "Licht",
"themeDark": "Dunkel",
"themeFrappe": "Frappé",
"themeOneDark": "Ein Dunkler",
"themeMatrix": "Matrizen",
"themeSolarizedDark": "Solarized dunkel",
"preferredVideoCodec": "Bevorzugter Video-Codec",
"showMoreFormatOptions": "Weitere Formatoptionen anzeigen",
"flatsealPermissionWarning": "Sie müssen der App die Berechtigung zum Zugriff auf das Home-Verzeichnis erteilen, um dies zu verwenden. Sie können dies mit Flatseal tun, indem Sie die Berechtigung mit dem Text 'filesystem = home' aktivieren",
"noAudio": "Kein Audio",
"proxy": "Proxy",
"clearDownloads": "Downloads löschen",
"compressor": "Kompressor",
"dragAndDropFiles": "Datei(en) per Drag & Drop verschieben",
"chooseFiles": "Datei(en) auswählen",
"noFilesSelected": "Keine Dateien ausgewählt",
"videoFormat": "Videoformat",
"videoEncoder": "Video Encoder",
"compressionSpeed": "Komprimierungsgeschwindigkeit",
"videoQuality": "Videoqualität",
"audioFormat": "Audioformat",
"outputSuffix": "Ausgabe-Suffix",
"outputInSameFolder": "Ausgabe im gleichen Ordner",
"selectCustomFolder": "Benutzerdefinierten Ordner auswählen",
"startCompression": "Komprimierung starten",
"cancel": "Abbrechen",
"errorClickForDetails": "Fehler! Für Details klicken",
"homebrewYtDlpWarning": "Du musst zuerst yt-dlp von Homebrew herunterladen",
"openHomebrew": "Homebrew öffnen",
"downloadHistory": "Verlauf herunterladen",
"close": "Schließen",
"searchByTitleOrUrl": "Suche nach Titel oder URL...",
"allFormats": "Alle Formate",
"exportAsJson": "Als JSON exportieren",
"exportAsCsv": "Als CSV exportieren",
"clearAllHistory": "Lösche die ganze History",
"noDownloadsYet": "Noch nicht heruntergeladen",
"downloadHistoryPlaceholder": "Ihr Downloadverlauf wird hier erscheinen",
"format": "Format",
"size": "Größe",
"date": "Datum",
"duration": "Dauer",
"copyUrl": "URL kopieren",
"open": "Öffnen",
"delete": "Löschen",
"totalDownloads": "Downloads gesamt",
"totalSize": "Gesamtgröße",
"mostCommonFormat": "Häufigstes Format",
"urlCopiedToClipboard": "URL in Zwischenablage kopiert",
"confirmDeleteHistoryItem": "Sind Sie sicher, dass Sie dieses Element aus dem Verlauf löschen möchten?",
"confirmClearAllHistory": "Sind Sie sicher, dass Sie den gesamten Downloadverlauf löschen möchten? Dies kann nicht rückgängig gemacht werden!",
"fileDoesNotExist": "Datei existiert nicht mehr",
"updatingYtdlp": "Aktualisiere yt-dlp",
"updatedYtdlp": "Aktualisierte yt-dlp",
"ytDlpUpdateRequired": "Wenn yt-dlp aktualisiert wird, warten Sie, bis die Aktualisierung abgeschlossen ist. Wenn Sie yt-dlp selbst installiert haben, aktualisieren Sie es bitte.",
"failedToDeleteHistoryItem": "Fehler beim Löschen des Verlaufs",
"customArgsTxt": "Setze eigene yt-dlp Optionen.",
"learnMore": "Erfahre mehr",
"updateError": "Beim Update-Prozess ist ein Fehler aufgetreten",
"unableToAccessDir": "Das Programm kann nicht auf diesen Ordner zugreifen",
"downloadingUpdate": "Update wird heruntergeladen"
}
================================================
FILE: translations/el-GR.json
================================================
{
"preferences": "Προτιμήσεις",
"about": "Σχετικά με",
"downloadLocation": "Τοποθεσία λήψεων",
"currentDownloadLocation": "Τρέχουσα τοποθεσία λήψεων - ",
"enableTransparentDarkMode": "Ενεργοποίηση σκοτεινής λειτουργίας(μόνο για Linux, χρειάζεται επανεκκίνηση)",
"downloadingNecessaryFilesWait": "Παρακαλώ περιμένετε, τα απαραίτητα αρχεία κατεβαίνουν",
"video": "Βίντεο",
"audio": "Ήχος",
"title": "Τίτλος ",
"selectFormat": "Επιλέξτε μορφή ",
"download": "Λήψη",
"selectDownloadLocation": "Επιλέξτε τοποθεσία λήψεων",
"moreOptions": "Περισσότερες επιλογές",
"start": "Έναρξη",
"selectLanguageRelaunch": "Επιλέξτε Γλώσσα (Απαιτεί επανεκκίνηση)",
"downloadTimeRange": "Κατεβάστε συγκεκριμένα χρονικά διαστήματα του βίντεο",
"end": "Τέλος",
"timeRangeStartEmptyHint": "Αν παραμείνει κενό, θα ξεκινήσει από την αρχή",
"timeRangeEndEmptyHint": "Αν παραμείνει κενό, θα γίνει λήψη στο τέλος",
"homepage": "Αρχική σελίδα",
"aboutAppDescription": "Είναι μια εφαρμογή Δωρεάν και Ανοιχτού Κώδικα που βασίζεται στην κορυφή των Node.js και Electron. Το yt-dlp έχει χρησιμοποιηθεί για τη λήψη",
"sourceCodeAvailable": "Ο κώδικας του προγράμματος είναι διαθέσιμος ",
"here": "εδώ",
"processing": "Σε επεξεργασία",
"errorNetworkOrUrl": "Παρουσιάστηκε σφάλμα. Ελέγξτε το δίκτυό σας και χρησιμοποιήστε σωστό URL",
"errorFailedFileDownload": "Αποτυχία λήψης των απαραίτητων αρχείων. Παρακαλώ ελέγξτε το δίκτυό σας και προσπαθήστε ξανά",
"tryAgain": "Προσπαθήστε ξανά",
"unknownSize": "Άγνωστο μέγεθος",
"megabyte": "MB",
"unknownQuality": "Άγνωστη ποιότητα",
"downloading": "Γίνεται Λήψη...",
"errorHoverForDetails": "Παρουσιάστηκε κάποιο σφάλμα. Πατήστε για να δείτε τις λεπτομέρειες",
"fileSavedSuccessfully": "Το αρχείο αποθηκεύτηκε επιτυχώς",
"fileSavedClickToOpen": "Το αρχείο αποθηκεύτηκε. Κάντε κλικ για άνοιγμα",
"preparing": "Προετοιμασία...",
"progress": "Πρόοδος",
"speed": "Ταχύτητα",
"quality": "Ποιότητα",
"restartApp": "Επανεκκίνηση της εφαρμογής",
"subtitles": "Υπότιτλοι",
"downloadSubtitlesAvailable": "Λήψη υποτίτλων εάν είναι διαθέσιμοι",
"downloadSubtitlesAuto": "Λήψη αυτόματης δημιουργίας υπότιτλων",
"extractAudioFromVideo": "Εξαγωγή ήχου από το βίντεο",
"extract": "Εξαγωγή",
"downloadingNecessaryFiles": "Λήψη απαραίτητων αρχείων",
"qualityLow": "χαμηλή",
"qualityMedium": "μέτρια",
"appDescription": "Το ytDownloader σας επιτρέπει να κατεβάσετε βίντεο και ήχους από εκατοντάδες ιστοσελίδες όπως το Youtube, Facebook, Instagram, Tiktok, Twitter και πολλά άλλα",
"pasteText": "Κάντε κλικ για επικόλληση συνδέσμου βίντεο από το πρόχειρο",
"pastePlaylistLinkTooltip": "Κάντε κλικ για επικόλληση συνδέσμου λίστας αναπαραγωγής από το πρόχειρο",
"link": "Σύνδεσμος:",
"downloadingPlaylist": "Γίνεται λήψη λίστας αναπαραγωγής:",
"downloadPlaylistButton": "Λήψη λίστας αναπαραγωγής",
"playlistDownloaded": "Η λίστα αναπαραγωγής λήφθηκε",
"cookiesWarning": "Αυτή η επιλογή σας επιτρέπει να κατεβάσετε περιορισμένο περιεχόμενο. Θα λάβετε σφάλματα εάν τα cookies δεν υπάρχουν",
"selectBrowserForCookies": "Επιλέξτε πρόγραμμα περιήγησης για χρήση των cookies",
"none": "Τίποτα",
"updateAvailableDownloadPrompt": "Νέα έκδοση είναι διαθέσιμη, θέλετε να την κατεβάσετε;",
"updateAvailablePrompt": "Νέα έκδοση είναι διαθέσιμη, θέλετε να την ενημερώσετε;",
"update": "Ενημέρωση",
"no": "Όχι",
"installAndRestartPrompt": "Εγκατάσταση και επανεκκίνηση τώρα;",
"restart": "Επανεκκίνηση",
"later": "Αργότερα",
"extractAudio": "Εξαγωγή ήχου",
"selectVideoFormat": "Επιλογή Μορφής Βιντέου ",
"selectAudioFormat": "Επιλέξτε Μορφή Ήχου ",
"maxActiveDownloads": "Μέγιστος αριθμός ενεργών λήψεων",
"preferredVideoQuality": "Προτεινόμενη ποιότητα βιντέου",
"preferredAudioFormat": "Προτιμώμενη μορφή ήχου",
"best": "Καλύτερο",
"fileSaved": "Το αρχείο αποθηκεύτηκε.",
"openDownloadFolder": "Ανοίξτε τον φάκελο λήψεων",
"path": "Μονοπάτι:",
"selectConfigFile": "Επιλέξτε αρχείο ρυθμίσεων",
"useConfigFile": "Χρησιμοποιήστε το αρχείο ρυθμίσεων",
"playlistFilenameFormat": "Μορφή ονόματος αρχείου για λίστες αναπαραγωγής",
"playlistFolderNameFormat": "Μορφή ονόματος φακέλου για λίστες αναπαραγωγής",
"resetToDefault": "Επαναφορά στις αρχικές ρυθμίσεις",
"playlistRange": "Εύρος λίστας αναπαραγωγής",
"thumbnail": "Εικονίδιο",
"linkAdded": "Ο σύνδεσμος προστέθηκε",
"downloadThumbnails": "Λήψη εικονιδίου",
"saveVideoLinksToFile": "Αποθήκευση συνδέσμων βιντέου σε αρχείο",
"closeAppToTray": "Κλείσιμο εφαρμογής στην περιοχή ειδοποιήσεων",
"useConfigFileCheckbox": "Χρήση αρχείου ρυθμίσεων",
"openApp": "Άνοιγμα εφαρμογής",
"pasteVideoLink": "Επικόλληση σύνδεσμο βιντέου",
"quit": "Έξοδος",
"errorDetails": "Λεπτομέρειες σφάλματος",
"clickToCopy": "Κάνε κλικ για αντιγραφή",
"copiedText": "Αντιγραμμένο κείμενο",
"qualityNormal": "Φυσιολογικό",
"qualityGood": "Καλός",
"qualityBad": "Κακή",
"qualityWorst": "Χειρότερο",
"selectQuality": "Επιλογή Ποιότητας",
"disableAutoUpdates": "Απενεργοποίηση αυτόματων ενημερώσεων",
"qualityUltraLow": "υπερβολικά χαμηλό",
"closeAppOnFinish": "Κλείσιμο εφαρμογής όταν ολοκληρωθεί η λήψη",
"auto": "Αυτόματο",
"theme": "Θέμα",
"themeLight": "Φως",
"themeDark": "Σκοτεινό",
"themeFrappe": "Frappé",
"themeOneDark": "Ένα Σκοτεινό",
"themeMatrix": "Matrix",
"themeSolarizedDark": "Solarized Dark",
"preferredVideoCodec": "Προτιμώμενος κωδικοποιητής βίντεο",
"showMoreFormatOptions": "Εμφάνιση περισσότερων επιλογών μορφής",
"flatsealPermissionWarning": "Πρέπει να δώσετε στην εφαρμογή άδεια πρόσβασης στον αρχικό κατάλογο για να το χρησιμοποιήσετε. Μπορείτε να το κάνετε με το Flatseal ενεργοποιώντας την άδεια με το κείμενο 'filesystem=home'",
"noAudio": "Χωρίς Ήχο",
"proxy": "Διακομιστής Μεσολαβητής (Proxy)",
"clearDownloads": "Εκκαθάριση Λήψεων",
"compressor": "Συμπιεστής",
"dragAndDropFiles": "Σύρετε και αφήστε αρχείο(α)",
"chooseFiles": "Επιλέξτε Αρχείο(α)",
"noFilesSelected": "Δεν έχουν επιλεγεί αρχεία",
"videoFormat": "Μορφή Βίντεο",
"videoEncoder": "Κωδικοποιητής Βίντεο",
"compressionSpeed": "Ταχύτητα Συμπίεσης",
"videoQuality": "Ποιότητα Βίντεο",
"audioFormat": "Μορφή Ήχου",
"outputSuffix": "Κατάληξη Εξόδου",
"outputInSameFolder": "Έξοδος στον ίδιο φάκελο",
"selectCustomFolder": "Επιλέξτε προσαρμοσμένο φάκελο",
"startCompression": "Έναρξη Συμπίεσης",
"cancel": "Ακύρωση",
"errorClickForDetails": "Σφάλμα! Κάντε κλικ για λεπτομέρειες",
"homebrewYtDlpWarning": "Πρέπει πρώτα να κατεβάσετε το yt-dlp από το homebrew",
"openHomebrew": "Άνοιγμα Homebrew",
"downloadHistory": "Ιστορικό Λήψεων",
"close": "Κλείσιμο",
"searchByTitleOrUrl": "Αναζήτηση με τίτλο ή URL...",
"allFormats": "Όλες οι Μορφές",
"exportAsJson": "Εξαγωγή ως JSON",
"exportAsCsv": "Εξαγωγή ως CSV",
"clearAllHistory": "Εκκαθάριση Όλου του Ιστορικού",
"noDownloadsYet": "Δεν υπάρχουν Λήψεις ακόμα",
"downloadHistoryPlaceholder": "Το ιστορικό λήψεών σας θα εμφανιστεί εδώ",
"format": "Μορφή",
"size": "Μέγεθος",
"date": "Ημερομηνία",
"duration": "Διάρκεια",
"copyUrl": "Αντιγραφή URL",
"open": "Άνοιγμα",
"delete": "Διαγραφή",
"totalDownloads": "Σύνολο Λήψεων",
"totalSize": "Συνολικό Μέγεθος",
"mostCommonFormat": "Πιο Συχνή Μορφή",
"urlCopiedToClipboard": "Το URL αντιγράφηκε στο πρόχειρο!",
"confirmDeleteHistoryItem": "Είστε σίγουροι ότι θέλετε να διαγράψετε αυτό το στοιχείο από το ιστορικό;",
"confirmClearAllHistory": "Είστε σίγουροι ότι θέλετε να διαγράψετε όλο το ιστορικό λήψεων; Αυτό δεν αναιρείται!",
"fileDoesNotExist": "Το αρχείο δεν υπάρχει πια",
"updatingYtdlp": "Ενημέρωση yt-dlp",
"updatedYtdlp": "Ενημερώθηκε το yt-dlp",
"ytDlpUpdateRequired": "Αν το yt-dlp ενημερώνεται, περιμένετε να ολοκληρωθεί η ενημέρωση. Αν έχετε εγκαταστήσει το yt-dlp από τον εαυτό σας, ενημερώστε το.",
"failedToDeleteHistoryItem": "Αποτυχία διαγραφής στοιχείου ιστορικού",
"customArgsTxt": "Ορίστε προσαρμοσμένες επιλογές yt-dlp.",
"learnMore": "Μάθε περισσότερα",
"updateError": "Παρουσιάστηκε σφάλμα κατά τη διαδικασία ενημέρωσης",
"unableToAccessDir": "Το πρόγραμμα δεν μπορεί να έχει πρόσβαση σε αυτόν τον φάκελο",
"downloadingUpdate": "Λήψη ενημέρωσης"
}
================================================
FILE: translations/en.json
================================================
{
"preferences": "Preferences",
"about": "About",
"downloadLocation": "Download location",
"currentDownloadLocation": "Current download location - ",
"enableTransparentDarkMode": "Enable transparent dark mode(only Linux, needs relaunch)",
"downloadingNecessaryFilesWait": "Please wait, necessary files are being downloaded",
"video": "Video",
"audio": "Audio",
"title": "Title ",
"selectFormat": "Select Format ",
"download": "Download",
"selectDownloadLocation": "Select Download Location",
"moreOptions": "More options",
"start": "Start",
"selectLanguageRelaunch": "Select Language (Requires relaunch)",
"downloadTimeRange": "Download particular time-range",
"end": "End",
"timeRangeStartEmptyHint": "If kept empty, it will start from the beginning",
"timeRangeEndEmptyHint": "If kept empty, it will be downloaded to the end",
"homepage": "Homepage",
"aboutAppDescription": "It's a Free and Open Source app built on top of Node.js and Electron. yt-dlp has been used for downloading",
"sourceCodeAvailable": "Source Code is available ",
"here": "here",
"processing": "Processing",
"errorNetworkOrUrl": "Some error has occurred. Check your network and use correct URL",
"errorFailedFileDownload": "Failed to download necessary files. Please check your network and try again",
"tryAgain": "Try again",
"unknownSize": "Unknown size",
"megabyte": "MB",
"unknownQuality": "Unknown quality",
"downloading": "Downloading...",
"errorHoverForDetails": "Some error has occurred. Hover to see details",
"fileSavedSuccessfully": "File saved successfully",
"fileSavedClickToOpen": "File saved. Click to Open",
"preparing": "Preparing...",
"progress": "Progress",
"speed": "Speed",
"quality": "Quality",
"restartApp": "Restart app",
"subtitles": "Subtitles",
"downloadSubtitlesAvailable": "Download subtitles if available",
"downloadSubtitlesAuto": "Download auto generated subtitles",
"extractAudioFromVideo": "Extract Audio from Video",
"extract": "Extract",
"downloadingNecessaryFiles": "Downloading necessary files",
"qualityLow": "low",
"qualityMedium": "medium",
"appDescription": "ytDownloader lets you download videos and audios from hundreds of sites like Youtube, Facebook, Instagram, Tiktok, Twitter and so on",
"pasteText": "Click to paste video link from clipboard",
"pastePlaylistLinkTooltip": "Click to paste playlist link from clipboard",
"link": "Link:",
"downloadingPlaylist": "Downloading playlist:",
"downloadPlaylistButton": "Download playlist",
"playlistDownloaded": "Playlist downloaded",
"cookiesWarning": "This option lets you download restricted content. You will get errors if cookies are not there",
"selectBrowserForCookies": "Select browser to use cookies from",
"none": "None",
"updateAvailableDownloadPrompt": "A new version is available, do you want to download it?",
"updateAvailablePrompt": "A new version is available, do you want to update?",
"update": "Update",
"no": "No",
"installAndRestartPrompt": "Install and restart now?",
"restart": "Restart",
"later": "Later",
"extractAudio": "Extract Audio",
"selectVideoFormat": "Select Video Format ",
"selectAudioFormat": "Select Audio Format ",
"maxActiveDownloads": "Maximum number of active downloads",
"preferredVideoQuality": "Preferred video quality",
"preferredAudioFormat": "Preferred audio format",
"best": "Best",
"fileSaved": "File saved.",
"openDownloadFolder": "Open download folder",
"path": "Path:",
"selectConfigFile": "Select config file",
"useConfigFile": "Use configuration file",
"playlistFilenameFormat": "Filename format for playlists",
"playlistFolderNameFormat": "Folder name format for playlists",
"resetToDefault": "Reset to default",
"playlistRange": "Playlist range",
"thumbnail": "Thumbnail",
"linkAdded": "Link added",
"downloadThumbnails": "Download thumbnails",
"saveVideoLinksToFile": "Save video links to a file",
"closeAppToTray": "Close app to system tray",
"useConfigFileCheckbox": "Use config file",
"openApp": "Open app",
"pasteVideoLink": "Paste video link",
"quit": "Quit",
"errorDetails": "Error Details",
"clickToCopy": "Click to copy",
"copiedText": "Copied text",
"qualityNormal": "Normal",
"qualityGood": "Good",
"qualityBad": "Bad",
"qualityWorst": "Worst",
"selectQuality": "Select Quality",
"disableAutoUpdates": "Disable auto updates",
"qualityUltraLow": "ultralow",
"closeAppOnFinish": "Close app when download finishes",
"auto": "Auto",
"theme": "Theme",
"themeLight": "Light",
"themeDark": "Dark",
"themeFrappe": "Frappé",
"themeOneDark": "One Dark",
"themeMatrix": "Matrix",
"themeSolarizedDark": "Solarized Dark",
"preferredVideoCodec": "Preferred video codec",
"showMoreFormatOptions": "Show more format options",
"flatsealPermissionWarning": "You need to give the app permission to access home directory to use this. You can do it with Flatseal by enabling the permission with text 'filesystem=home'",
"noAudio": "No Audio",
"proxy": "Proxy",
"clearDownloads": "Clear Downloads",
"compressor": "Compressor",
"dragAndDropFiles": "Drag and drop file(s)",
"chooseFiles": "Choose File(s)",
"noFilesSelected": "No files selected",
"videoFormat": "Video format",
"videoEncoder": "Video Encoder",
"compressionSpeed": "Compression Speed",
"videoQuality": "Video Quality",
"audioFormat": "Audio Format",
"outputSuffix": "Output suffix",
"outputInSameFolder": "Output in same folder",
"selectCustomFolder": "Select custom folder",
"startCompression": "Start Compression",
"cancel": "Cancel",
"errorClickForDetails": "Error! Click for details",
"homebrewYtDlpWarning": "You need to download yt-dlp from homebrew first",
"openHomebrew": "Open Homebrew",
"downloadHistory": "Download History",
"close": "Close",
"searchByTitleOrUrl": "Search by title or URL...",
"allFormats": "All Formats",
"exportAsJson": "Export as JSON",
"exportAsCsv": "Export as CSV",
"clearAllHistory": "Clear All History",
"noDownloadsYet": "No Downloads Yet",
"downloadHistoryPlaceholder": "Your download history will appear here",
"format": "Format",
"size": "Size",
"date": "Date",
"duration": "Duration",
"copyUrl": "Copy URL",
"open": "Open",
"delete": "Delete",
"totalDownloads": "Total Downloads",
"totalSize": "Total Size",
"mostCommonFormat": "Most Common Format",
"urlCopiedToClipboard": "URL copied to clipboard!",
"confirmDeleteHistoryItem": "Are you sure you want to delete this item from history?",
"confirmClearAllHistory": "Are you sure you want to clear all download history? This cannot be undone!",
"fileDoesNotExist": "File does not exist anymore",
"updatingYtdlp": "Updating yt-dlp",
"updatedYtdlp": "Updated yt-dlp",
"ytDlpUpdateRequired": "If yt-dlp is updating, wait for the update to finish. If you have installed yt-dlp by yourself, please update it.",
"failedToDeleteHistoryItem": "Failed to delete history item",
"customArgsTxt": "Set custom yt-dlp options.",
"learnMore": "Learn more",
"updateError": "An error occurred during the update process",
"unableToAccessDir": "The program cannot access that folder",
"downloadingUpdate": "Downloading update"
}
================================================
FILE: translations/es-ES.json
================================================
{
"preferences": "Preferencias",
"about": "Acerca de",
"downloadLocation": "Ubicación de la descarga",
"currentDownloadLocation": "Ubicación de la descarga actual - ",
"enableTransparentDarkMode": "Habilitar el modo oscuro transparente (solo en Linux, necesita relanzar)",
"downloadingNecessaryFilesWait": "Espere, se están descargando los archivos necesarios",
"video": "Video",
"audio": "Audio",
"title": "Título ",
"selectFormat": "Seleccionar formato ",
"download": "Descargar",
"selectDownloadLocation": "Seleccione la ubicación de la descarga",
"moreOptions": "Mas opciones",
"start": "Inicio",
"selectLanguageRelaunch": "Seleccionar idioma (Requiere relanzar)",
"downloadTimeRange": "Descargar un rango de tiempo particular",
"end": "Término",
"timeRangeStartEmptyHint": "Si se mantiene vacío, comenzará desde el principio",
"timeRangeEndEmptyHint": "Si se mantiene vacío, se descargará hasta el final",
"homepage": "Página web",
"aboutAppDescription": "Es una aplicación gratuita y de código abierto construida sobre Node.js y Electron. yt-dlp se ha utilizado para descargar",
"sourceCodeAvailable": "El código fuente está disponible ",
"here": "aquí",
"processing": "Procesando",
"errorNetworkOrUrl": "Ha ocurrido algún error. Verifique su red y use la URL correcta",
"errorFailedFileDownload": "No se han podido descargar los archivos necesarios. Por favor, compruebe su red e inténtelo de nuevo",
"tryAgain": "Inténtelo de nuevo",
"unknownSize": "Tamaño desconocido",
"megabyte": "MB",
"unknownQuality": "Calidad desconocida",
"downloading": "Descargando...",
"errorHoverForDetails": "Ha ocurrido algún error. Pase el cursor para ver los detalles",
"fileSavedSuccessfully": "Archivo guardado con éxito",
"fileSavedClickToOpen": "Archivo guardado. Haga clic para abrir",
"preparing": "Preparando...",
"progress": "Progreso",
"speed": "Velocidad",
"quality": "Calidad",
"restartApp": "Reiniciar la aplicación",
"subtitles": "Subtítulos",
"downloadSubtitlesAvailable": "Descargar los subtítulos si están disponibles",
"downloadSubtitlesAuto": "Descargar subtítulos generados automáticamente",
"extractAudioFromVideo": "Extraer el audio del vídeo",
"extract": "Extraer",
"downloadingNecessaryFiles": "Descarga de los archivos necesarios",
"qualityLow": "baja",
"qualityMedium": "media",
"appDescription": "ytDownloader te permite descargar vídeos y audios de cientos de sitios como Youtube, Facebook, Instagram, Tiktok, Twitter, etc.",
"pasteText": "Haga clic para pegar enlace de vídeo del portapapeles",
"pastePlaylistLinkTooltip": "Haga clic para pegar el enlace de la lista de reproducción del portapapeles",
"link": "Enlace:",
"downloadingPlaylist": "Descargando lista de reproducción:",
"downloadPlaylistButton": "Descargar lista de reproducción",
"playlistDownloaded": "Lista de reproducción descargada",
"cookiesWarning": "Esta opción le permite descargar contenido restringido. Obtendrá errores si no hay cookies",
"selectBrowserForCookies": "Seleccionar navegador para usar cookies de",
"none": "Ninguna",
"updateAvailableDownloadPrompt": "Hay una nueva versión disponible, ¿quieres descargarla?",
"updateAvailablePrompt": "Hay una nueva versión disponible, ¿quieres actualizar?",
"update": "Actualización",
"no": "No",
"installAndRestartPrompt": "¿Instalar y reiniciar ahora?",
"restart": "Reiniciar",
"later": "Luego",
"extractAudio": "Extraer Audio",
"selectVideoFormat": "Seleccionar formato de vídeo ",
"selectAudioFormat": "Seleccionar formato de audio ",
"maxActiveDownloads": "Número máximo de descargas activas",
"preferredVideoQuality": "Calidad de vídeo preferida",
"preferredAudioFormat": "Formato de audio preferido",
"best": "Mejor",
"fileSaved": "Archivo guardado",
"openDownloadFolder": "Abrir carpeta de descargas",
"path": "Ruta:",
"selectConfigFile": "Seleccionar archivo de configuración",
"useConfigFile": "Usar archivo de configuración",
"playlistFilenameFormat": "Formato de archivo para listas",
"playlistFolderNameFormat": "Formato de nombre de carpeta para listas",
"resetToDefault": "Restablecer por defecto",
"playlistRange": "Rango de lista",
"thumbnail": "Miniatura",
"linkAdded": "Enlace añadido",
"downloadThumbnails": "Descargar miniaturas",
"saveVideoLinksToFile": "Guardar enlaces de vídeo en un archivo",
"closeAppToTray": "Cerrar aplicación a la bandeja del sistema",
"useConfigFileCheckbox": "Usar archivo de configuración",
"openApp": "Abrir app",
"pasteVideoLink": "Pegar enlace de vídeo",
"quit": "Salir",
"errorDetails": "Detalles del error",
"clickToCopy": "Clic para copiar",
"copiedText": "Texto copiado",
"qualityNormal": "Estándar",
"qualityGood": "Bueno",
"qualityBad": "Mal",
"qualityWorst": "Peor",
"selectQuality": "Seleccionar calidad",
"disableAutoUpdates": "Desactivar actualizaciones automáticas",
"qualityUltraLow": "ultra bajo",
"closeAppOnFinish": "Cerrar aplicación cuando finalice la descarga",
"auto": "Auto",
"theme": "Tema",
"themeLight": "Ligero",
"themeDark": "Oscuro",
"themeFrappe": "Frappé",
"themeOneDark": "One Dark",
"themeMatrix": "Matriz",
"themeSolarizedDark": "Oscuro Solar",
"preferredVideoCodec": "Código de vídeo preferido",
"showMoreFormatOptions": "Mostrar más opciones de formato",
"flatsealPermissionWarning": "Debe otorgar permiso a la aplicación para acceder al directorio de inicio para usar esto. Puede hacerlo con Flatseal habilitando el permiso con el texto 'filesystem=home'",
"noAudio": "Sin audio",
"proxy": "Proxy",
"clearDownloads": "Borrar Descargas",
"compressor": "Compresor",
"dragAndDropFiles": "Arrastrar y soltar archivo(s)",
"chooseFiles": "Elegir archivo(s)",
"noFilesSelected": "No hay archivos seleccionados",
"videoFormat": "Formato de vídeo",
"videoEncoder": "Codificador de Vídeo",
"compressionSpeed": "Velocidad de compresión",
"videoQuality": "Calidad del vídeo",
"audioFormat": "Formato de audio",
"outputSuffix": "Sufijo de salida",
"outputInSameFolder": "Salida en la misma carpeta",
"selectCustomFolder": "Seleccionar carpeta personalizada",
"startCompression": "Iniciar compresión",
"cancel": "Cancelar",
"errorClickForDetails": "Detalles",
"homebrewYtDlpWarning": "Necesitas descargar yt-dlp desde homebrew primero",
"openHomebrew": "Abrir Homebrew",
"downloadHistory": "Historial de Descargas",
"close": "Cerrar",
"searchByTitleOrUrl": "Buscar por título o URL...",
"allFormats": "Todos los formatos",
"exportAsJson": "Exportar como JSON",
"exportAsCsv": "Exportar a CSV",
"clearAllHistory": "Borrar todo el historial",
"noDownloadsYet": "Aún no hay descargas",
"downloadHistoryPlaceholder": "Tu historial de descargas aparecerá aquí",
"format": "Formato",
"size": "Tamaño",
"date": "Fecha",
"duration": "Duración",
"copyUrl": "Copiar URL",
"open": "Abrir",
"delete": "Eliminar",
"totalDownloads": "Descargas Totales",
"totalSize": "Tamaño Total",
"mostCommonFormat": "Formato Más Común",
"urlCopiedToClipboard": "¡URL copiada al portapapeles!",
"confirmDeleteHistoryItem": "¿Estás seguro de que quieres eliminar este elemento del historial?",
"confirmClearAllHistory": "¿Estás seguro de que quieres borrar todo el historial de descargas? ¡Esta acción no se puede deshacer!",
"fileDoesNotExist": "El archivo ya no existe",
"updatingYtdlp": "Actualizando yt-dlp",
"updatedYtdlp": "Actualizado yt-dlp",
"ytDlpUpdateRequired": "Si yt-dlp está actualizando, espere a que la actualización termine. Si usted mismo ha instalado yt-dlp, por favor actualícela.",
"failedToDeleteHistoryItem": "Error al eliminar el elemento del historial",
"customArgsTxt": "Establecer opciones personalizadas yt-dlp.",
"learnMore": "Saber más",
"updateError": "Se produjo un error durante el proceso de actualización",
"unableToAccessDir": "El programa no puede acceder a esa carpeta",
"downloadingUpdate": "Descargando actualización"
}
================================================
FILE: translations/fa-IR.json
================================================
{
"preferences": "تنظیمات",
"about": "درباره",
"downloadLocation": "مکان بارگیری",
"currentDownloadLocation": "مکان بارگیری فعلی ",
"enableTransparentDarkMode": "فعالسازی حالت تاریک شفاف(فقط در لینوکس، نیازمند راهاندازیمجدد)",
"downloadingNecessaryFilesWait": "لطفا صبر کنید، پروندههای ضروری در حال بارگیری هستند",
"video": "ویدیو",
"audio": "صدا",
"title": "عنوان ",
"selectFormat": "انتخاب فرمت ",
"download": "بارگیری",
"selectDownloadLocation": "انتخاب مکان بارگیری",
"moreOptions": "گزینههای بیشتر",
"start": "ابتدا",
"selectLanguageRelaunch": "انتخاب زبان (نیازمند راهاندازیمجدد)",
"downloadTimeRange": "بارگیری بازهی زمانی خاص",
"end": "انتها",
"timeRangeStartEmptyHint": "اگر خالی گذاشتهشود، از ابتدا شروع خواهد کرد",
"timeRangeEndEmptyHint": "اگر خالی گذاشتهشود، تا انتها بارگیری خواهد کرد",
"homepage": "صفحهی نخست",
"aboutAppDescription": "نرمافزاری رایگان و منبع-باز است که برپایهی Node.js و Electron ساخته شدهاست. yt-dlp برای بارگیری به کار رفتهاست",
"sourceCodeAvailable": "کد منبع دردسترس است ",
"here": "اینجا",
"processing": "درحال پردازش",
"errorNetworkOrUrl": "خطایی رخ دادهاست. اتصال شبکهی خود را بررسی کرده و از نشانی اینترنتی صحیح استفاده کنید",
"errorFailedFileDownload": "بارگیری پروندههای ضروری ناموفق بود. لطفا اتصال شبکهی خود را بررسی کرده و دوباره تلاش کنید",
"tryAgain": "دوباره تلاش کن",
"unknownSize": "اندازهی نامشخص",
"megabyte": "مگابایت",
"unknownQuality": "کیفیت نامشخص",
"downloading": "درحال بارگیری...",
"errorHoverForDetails": "خطایی رخ دادهاست. برای دیدن جزئیات نشانگر موس را نگهدارید",
"fileSavedSuccessfully": "پرونده با موفقیت ذخیره شد",
"fileSavedClickToOpen": "پرونده ذخیرهشد. برای بازکردن کلیک کنید",
"preparing": "درحال آمادهسازی...",
"progress": "پیشرفت",
"speed": "سرعت",
"quality": "کیفیت",
"restartApp": "راهاندازی مجدد برنامه",
"subtitles": "زیرنویسها",
"downloadSubtitlesAvailable": "در صورت وجود زیرنویسها بارگیری شود",
"downloadSubtitlesAuto": "زیرنویسهای بهطورخودکار تولیدشده بارگیری شود",
"extractAudioFromVideo": "صدا از ویدیو استخراج شود",
"extract": "استخراج",
"downloadingNecessaryFiles": "درحال بارگیری پروندههای ضروری",
"qualityLow": "پایین",
"qualityMedium": "متوسط",
"appDescription": "ytDownloader به شما امکان بارگیری ویدیو و صدا را از صدها سایت مانند یوتیوب، فیسبوک، اینستاگرام، تیکتاک، توییتر و غیره میدهد",
"pasteText": "برای چسباندن پیوند ویدیو از کلیپبورد کلیک کنید",
"pastePlaylistLinkTooltip": "برای چسباندن پیوند لیست پخش از کلیپبورد کلیک کنید",
"link": "پیوند:",
"downloadingPlaylist": "درحال بارگیری لیست پخش:",
"downloadPlaylistButton": "بارگیری لیست پخش",
"playlistDownloaded": "لیست پخش بارگیری شد",
"cookiesWarning": "این گزینه به شما اجازه میدهد محتوای محدودشده را بارگیری کنید. اگر کوکیها موجود نباشند اخطار دریافت خواهیدکرد",
"selectBrowserForCookies": "انتخاب مرورگر برای استفادهی کوکیها از آن",
"none": "هیچکدام",
"updateAvailableDownloadPrompt": "نسخهی جدیدی دردسترس است، میخواهید آن را بارگیری کنید؟",
"updateAvailablePrompt": "نسخهی جدیدی دردسترس است، میخواهید آن را بروزرسانی کنید؟",
"update": "بروزرسانی",
"no": "نه",
"installAndRestartPrompt": "اکنون نصب و راهاندازی مجدد انجام شود؟",
"restart": "راهاندازی مجدد",
"later": "بعداً",
"extractAudio": "استخراج صدا",
"selectVideoFormat": "انتخاب فرمت ویدیو ",
"selectAudioFormat": "انتخاب فرمت صدا ",
"maxActiveDownloads": "حداکثر تعداد دانلودهای فعال",
"preferredVideoQuality": "کیفیت ویدیوی ترجیحی",
"preferredAudioFormat": "فرمت صوتی ترجیحی",
"best": "بهترین",
"fileSaved": "پرونده ذخیره شد.",
"openDownloadFolder": "بازکردن پوشهٔ بارگیری ها",
"path": "مسیر:",
"selectConfigFile": "فایل پیکربندی را انتخاب کنید",
"useConfigFile": "از فایل تنظیمات استفاده کنید",
"playlistFilenameFormat": "فرمت نام فایل برای لیست های پخش",
"playlistFolderNameFormat": "فرمت نام پوشه برای لیست های پخش",
"resetToDefault": "بازگردانی به حالت پیش فرض",
"playlistRange": "محدوده لیست پخش",
"thumbnail": "تصویر بند انگشتی",
"linkAdded": "لینک اضافه شد",
"downloadThumbnails": "تصاویر کوچک را دانلود کنید",
"saveVideoLinksToFile": "لینک های ویدیو را در یک فایل ذخیره کنید",
"closeAppToTray": "برنامه را به سینی سیستم ببندید",
"useConfigFileCheckbox": "از فایل کانفیگ استفاده کنید",
"openApp": "اپلیکیشن را باز کن",
"pasteVideoLink": "پیوند ویدیو را بچسبانید",
"quit": "ترک کنید",
"errorDetails": "جزئیات خطا",
"clickToCopy": "برای کپی کلیک کنید",
"copiedText": "پیوند کپی شده",
"qualityNormal": "عادی",
"qualityGood": "درست",
"qualityBad": "بد",
"qualityWorst": "بدترین",
"selectQuality": "کیفیت را انتخاب کنید",
"disableAutoUpdates": "به روز رسانی خودکار را غیرفعال کنید",
"qualityUltraLow": "بسیار کم",
"closeAppOnFinish": "پس از پایان دانلود برنامه را ببندید",
"auto": "خودکار",
"theme": "تِم",
"themeLight": "روشن",
"themeDark": "تیره",
"themeFrappe": "Frappé",
"themeOneDark": "One Dark",
"themeMatrix": "ماتریکس",
"themeSolarizedDark": "تاریک خورشیدی",
"preferredVideoCodec": "کدک ویدیویی ترجیح داده شده",
"showMoreFormatOptions": "نمایش گزینه های فرمت بیشتر",
"flatsealPermissionWarning": "برای استفاده از این باید به برنامه اجازه دسترسی به دایرکتوری اصلی را بدهید. شما می توانید آن را با Flatseal با فعال کردن مجوز با متن 'filesystem=home' انجام دهید",
"noAudio": "بدون صدا",
"proxy": "پروکسی",
"clearDownloads": "پاک کردن دانلودها",
"compressor": "فشردهساز",
"dragAndDropFiles": "فایل(ها) را بکشید و رها کنید",
"chooseFiles": "انتخاب فایل(ها)",
"noFilesSelected": "هیچ فایلی انتخاب نشده است",
"videoFormat": "فرمت ویدیو",
"videoEncoder": "کدگذار ویدیو",
"compressionSpeed": "سرعت فشرده سازی",
"videoQuality": "کیفیت ویدیو",
"audioFormat": "فرمت صوتی",
"outputSuffix": "پسوند خروجی",
"outputInSameFolder": "خروجی در همان پوشه",
"selectCustomFolder": "انتخاب پوشه سفارشی",
"startCompression": "شروع فشردهسازی",
"cancel": "لغو",
"errorClickForDetails": "خطا! برای جزئیات کلیک کنید",
"homebrewYtDlpWarning": "ابتدا باید yt-dlp را از Homebrew دانلود کنید",
"openHomebrew": "باز کردن Homebrew",
"downloadHistory": "تاریخچه دانلود",
"close": "بستن",
"searchByTitleOrUrl": "جستجو بر اساس عنوان یا نشانی...",
"allFormats": "همه فرمتها",
"exportAsJson": "خروجی به صورت JSON",
"exportAsCsv": "خروجی به صورت CSV",
"clearAllHistory": "پاک کردن تمام تاریخچه",
"noDownloadsYet": "هنوز دانلودی انجام نشده است",
"downloadHistoryPlaceholder": "تاریخچه دانلود شما در اینجا ظاهر خواهد شد",
"format": "فرمت",
"size": "اندازه",
"date": "تاریخ",
"duration": "مدت زمان",
"copyUrl": "کپی نشانی",
"open": "باز کردن",
"delete": "حذف",
"totalDownloads": "کل دانلودها",
"totalSize": "اندازه کل",
"mostCommonFormat": "پرکاربردترین فرمت",
"urlCopiedToClipboard": "نشانی در کلیپبورد کپی شد!",
"confirmDeleteHistoryItem": "آیا مطمئن هستید که میخواهید این مورد را از تاریخچه حذف کنید؟",
"confirmClearAllHistory": "آیا مطمئن هستید که میخواهید کل تاریخچه دانلود را پاک کنید؟ این عمل قابل برگشت نیست!",
"fileDoesNotExist": "File does not exist anymore",
"updatingYtdlp": "Updating yt-dlp",
"updatedYtdlp": "Updated yt-dlp",
"ytDlpUpdateRequired": "If yt-dlp is updating, wait for the update to finish. If you have installed yt-dlp by yourself, please update it.",
"failedToDeleteHistoryItem": "Failed to delete history item",
"customArgsTxt": "Set custom yt-dlp options.",
"learnMore": "Learn more",
"updateError": "An error occurred during the update process",
"unableToAccessDir": "The program cannot access that folder",
"downloadingUpdate": "Downloading update"
}
================================================
FILE: translations/fi-FI.json
================================================
{
"preferences": "Asetukset",
"about": "Tietoja",
"downloadLocation": "Latausten kohdekansio",
"currentDownloadLocation": "Nykyinen kohde latauksille - ",
"enableTransparentDarkMode": "Kytke päälle tumma läpinäkyvä tila (vain Linux, ohjelman uudelleenkäynnistys tarvitaan)",
"downloadingNecessaryFilesWait": "Ole hyvä ja odota, tarvittavia tiedostoja ladataan juuri",
"video": "Video",
"audio": "Ääni",
"title": "Nimike ",
"selectFormat": "Valitse muoto ",
"download": "Lataa",
"selectDownloadLocation": "Valitse kohdekansio lataukselle",
"moreOptions": "Lisävaihtoehdot",
"start": "Aloita",
"selectLanguageRelaunch": "Valitse kieli (uudelleenkäynnistys tarvitaan)",
"downloadTimeRange": "Lataa tietty aikaväli",
"end": "Loppu",
"timeRangeStartEmptyHint": "Mikäli tyhjänä, tämä alkaa alusta",
"timeRangeEndEmptyHint": "Mikäli tyhjänä, se ladataan loppuun",
"homepage": "Kotisivusto",
"aboutAppDescription": "Vapaa ja ohjelmakoodiltaan avoin sovellus rakennettuna Node.js:n ja Electron:in päälle. yt-dlp:tä on käytetty toteuttamaan lataukset",
"sourceCodeAvailable": "Lähdekoodi on saatavilla ",
"here": "täällä",
"processing": "Käsitellään",
"errorNetworkOrUrl": "Ilmeni jokin virhe. Tarkista verkkoyhteytesi ja käytä kelvollista URL-osoitetta",
"errorFailedFileDownload": "Tarvittavia tiedostoja ei saatu ladattua. Tarkista verkkoyhteytesi ja koeta uudelleen",
"tryAgain": "Yritä uudelleen",
"unknownSize": "Tuntematon koko",
"megabyte": "Mt",
"unknownQuality": "Tuntematon laatu",
"downloading": "Ladataan...",
"errorHoverForDetails": "Ilmeni jokin virhe. Ohjaa hiiri ylle nähdäksesi virheen yksityiskohdat",
"fileSavedSuccessfully": "Tiedosto tallennettu onnistuneesti",
"fileSavedClickToOpen": "Tiedosto tallennettu. Napsauta tästä avataksesi kohdekansion",
"preparing": "Valmistellaan...",
"progress": "Edistyminen",
"speed": "Nopeus",
"quality": "Laatu",
"restartApp": "Uudelleenkäynnistä sovellus",
"subtitles": "Tekstitykset",
"downloadSubtitlesAvailable": "Lataa tekstitykset mikäli tarjolla",
"downloadSubtitlesAuto": "Lataa automaattisesti luodut tekstitykset",
"extractAudioFromVideo": "Eriytä ääniraita videosta",
"extract": "Vedä ääniraita",
"downloadingNecessaryFiles": "Ladataan tarvittavia tiedostoja",
"qualityLow": "matala",
"qualityMedium": "keskitaso",
"appDescription": "ytDownloader mahdollistaa videoiden ja äänen lataamisen talteen sadoilta sivustoilta kuten YouTube, Facebook, Instagram, Tiktok, Twitter ja niin edelleen",
"pasteText": "Napsauta liittääksesi videolinkin leikepöydältä",
"pastePlaylistLinkTooltip": "Napsauta liittääksesi soittolistan linkin leikepöydältä",
"link": "Linkki:",
"downloadingPlaylist": "Ladataan soittolista:",
"downloadPlaylistButton": "Lataa soittolista",
"playlistDownloaded": "Soittolista ladattu",
"cookiesWarning": "Tämän vaihtoehdon avulla voit ladata rajoitetun sisällön. Saat virheitä, jos evästeitä ei ole",
"selectBrowserForCookies": "Valitse selain käyttääksesi evästeitä",
"none": "Ei ole",
"updateAvailableDownloadPrompt": "Uusi julkaisu on saatavilla, haluatko ladata sen?",
"updateAvailablePrompt": "Uusi julkaisu on saatavilla, haluatko päivittää?",
"update": "Päivitä",
"no": "Ei",
"installAndRestartPrompt": "Asenna ja uudelleenkäynnistä nyt?",
"restart": "Uudelleenkäynnistys",
"later": "Myöhemmin",
"extractAudio": "Pura Ääni",
"selectVideoFormat": "Valitse Videon Muoto ",
"selectAudioFormat": "Valitse Äänen Muoto ",
"maxActiveDownloads": "Aktiivisten latausten enimmäismäärä",
"preferredVideoQuality": "Ensisijainen videon laatu",
"preferredAudioFormat": "Haluttu ääniformaatti",
"best": "Paras",
"fileSaved": "Tiedosto tallennettu",
"openDownloadFolder": "Avaa latauskansio",
"path": "Polku:",
"selectConfigFile": "Valitse konfiguraatiotiedosto",
"useConfigFile": "Käytä asetustiedostoa",
"playlistFilenameFormat": "Soittolistojen tiedostonimen muoto",
"playlistFolderNameFormat": "Soittolistojen kansion nimen muoto",
"resetToDefault": "Palauta oletusasetukset",
"playlistRange": "Soittolistan alue",
"thumbnail": "Pikkukuva",
"linkAdded": "Linkki lisätty",
"downloadThumbnails": "Lataa pikkukuvat",
"saveVideoLinksToFile": "Tallenna videolinkit tiedostoon",
"closeAppToTray": "Sulje sovellus ilmoitusalueelle",
"useConfigFileCheckbox": "Käytä asetustiedostoa",
"openApp": "Avaa sovellus",
"pasteVideoLink": "Liitä video linkki",
"quit": "Lopeta",
"errorDetails": "Virheen tiedot",
"clickToCopy": "Klikkaa kopioidaksesi",
"copiedText": "Teksti kopioitu",
"qualityNormal": "Normaali",
"qualityGood": "Hyvä",
"qualityBad": "Huono",
"qualityWorst": "Huonoin",
"selectQuality": "Valitse Laatu",
"disableAutoUpdates": "Poista automaattiset päivitykset käytöstä",
"qualityUltraLow": "erittäin matala",
"closeAppOnFinish": "Sulje sovellus latauksen valmistuttua",
"auto": "Automaattinen",
"theme": "Teema",
"themeLight": "Valoisa",
"themeDark": "Tumma",
"themeFrappe": "Frappé",
"themeOneDark": "One Dark",
"themeMatrix": "Matriisi",
"themeSolarizedDark": "Solarized, tumma",
"preferredVideoCodec": "Ensisijainen videon koodekki",
"showMoreFormatOptions": "Näytä lisää muotoiluvaihtoehtoja",
"flatsealPermissionWarning": "Sinun on annettava sovellukselle lupa käyttää kotihakemistoa tämän käyttämiseksi. Voit tehdä sen Flatsealilla ottamalla käyttöön oikeudet tekstillä 'filesystem=home'",
"noAudio": "Ei Ääntä",
"proxy": "Välityspalvelin",
"clearDownloads": "Tyhjennä Lataukset",
"compressor": "Pakkaaja",
"dragAndDropFiles": "Vedä ja pudota tiedosto(t)",
"chooseFiles": "Valitse tiedosto(t)",
"noFilesSelected": "Ei tiedostoja valittuna",
"videoFormat": "Videon muoto",
"videoEncoder": "Videon kooderi",
"compressionSpeed": "Pakkausnopeus",
"videoQuality": "Videon laatu",
"audioFormat": "Äänen muoto",
"outputSuffix": "Ulostulon pääte",
"outputInSameFolder": "Ulostulo samaan kansioon",
"selectCustomFolder": "Valitse mukautettu kansio",
"startCompression": "Aloita pakkaus",
"cancel": "Peruuta",
"errorClickForDetails": "Virhe! Napsauta nähdäksesi yksityiskohdat",
"homebrewYtDlpWarning": "Sinun on ladattava yt-dlp Homebrew'n kautta ensin",
"openHomebrew": "Avaa Homebrew",
"downloadHistory": "Lataushistoria",
"close": "Sulje",
"searchByTitleOrUrl": "Hae nimikkeen tai URL:n perusteella...",
"allFormats": "Kaikki muodot",
"exportAsJson": "Vie JSON-muodossa",
"exportAsCsv": "Vie CSV-muodossa",
"clearAllHistory": "Tyhjennä koko historia",
"noDownloadsYet": "Ei latauksia vielä",
"downloadHistoryPlaceholder": "Lataushistoriasi näkyy tässä",
"format": "Muoto",
"size": "Koko",
"date": "Päivämäärä",
"duration": "Kesto",
"copyUrl": "Kopioi URL",
"open": "Avaa",
"delete": "Poista",
"totalDownloads": "Latauksia yhteensä",
"totalSize": "Koko yhteensä",
"mostCommonFormat": "Yleisin muoto",
"urlCopiedToClipboard": "URL kopioitu leikepöydälle!",
"confirmDeleteHistoryItem": "Oletko varma, että haluat poistaa tämän kohteen historiasta?",
"confirmClearAllHistory": "Oletko varma, että haluat tyhjentää koko lataushistorian? Tätä ei voi perua!",
"fileDoesNotExist": "Tiedostoa ei ole enää olemassa",
"updatingYtdlp": "Päivitetään yt-dlp:tä",
"updatedYtdlp": "yt-dlp on päivitetty",
"ytDlpUpdateRequired": "Jos yt-dlp on päivittymässä, odota päivityksen valmistumista. Jos olet asentanut yt-dlp:n itse, ole hyvä ja päivitä se.",
"failedToDeleteHistoryItem": "Historiaelementin poistaminen epäonnistui",
"customArgsTxt": "Aseta mukautetut yt-dlp-asetukset.",
"learnMore": "Lue lisää",
"updateError": "Päivitysprosessin aikana tapahtui virhe",
"unableToAccessDir": "Ohjelma ei pääse käsiksi kyseiseen kansioon",
"downloadingUpdate": "Ladataan päivitystä"
}
================================================
FILE: translations/fr-FR.json
================================================
{
"preferences": "Préférences",
"about": "À propos",
"downloadLocation": "Emplacement des téléchargements",
"currentDownloadLocation": "Emplacement de téléchargement actuel - ",
"enableTransparentDarkMode": "Activer le mode sombre transparent (seulement sur Linux, nécessite un redémarrage)",
"downloadingNecessaryFilesWait": "Veuillez patienter, les fichiers nécessaires sont en cours de téléchargement",
"video": "Vidéo",
"audio": "Audio",
"title": "Titre ",
"selectFormat": "Format ",
"download": "Téléchargement",
"selectDownloadLocation": "Choisir l'emplacement de téléchargement",
"moreOptions": "Plus d'options",
"start": "Début",
"selectLanguageRelaunch": "Choix de la langue (nécessite un redémarrage)",
"downloadTimeRange": "Télécharger un extrait",
"end": "Fin",
"timeRangeStartEmptyHint": "Si laissé vide, commencera depuis le début",
"timeRangeEndEmptyHint": "Si laissé vide, terminera à la fin",
"homepage": "Page d'accueil",
"aboutAppDescription": "Ce logiciel est libre et open-source, construit avec Node.js et Electron. yt-dlp est utilisé pour le téléchargement",
"sourceCodeAvailable": "Le code source est disponible ",
"here": "ici",
"processing": "En cours",
"errorNetworkOrUrl": "Une erreur est survenue. Vérifiez votre connexion internet ainsi que l'adresse URL",
"errorFailedFileDownload": "Impossible de télécharger les fichiers. Vérifiez votre connexion internet et réessayez",
"tryAgain": "Réessayez",
"unknownSize": "Taille inconnue",
"megabyte": "Mo",
"unknownQuality": "Qualité inconnue",
"downloading": "Téléchargement...",
"errorHoverForDetails": "Une erreur est survenue. Voir détails",
"fileSavedSuccessfully": "Fichier enregistré avec succès",
"fileSavedClickToOpen": "Fichier enregistré. Cliquez pour ouvrir",
"preparing": "Préparation...",
"progress": "Avancement",
"speed": "Vitesse",
"quality": "Qualité",
"restartApp": "Redémarrer l'application",
"subtitles": "Sous-titres",
"downloadSubtitlesAvailable": "Télécharger les sous-titres si disponibles",
"downloadSubtitlesAuto": "Télécharger les sous-titres automatiquement générés",
"extractAudioFromVideo": "Extraire l'audio depuis la vidéo",
"extract": "Extraire",
"downloadingNecessaryFiles": "Téléchargement des fichiers",
"qualityLow": "faible",
"qualityMedium": "moyenne",
"appDescription": "ytDownloader vous permet de télécharger des vidéos et audios depuis des centaines de sites comme Youtube, Facebook, Instagram, TikTok, Twitter et plus encore",
"pasteText": "Cliquez pour coller le lien vidéo depuis le presse-papiers",
"pastePlaylistLinkTooltip": "Cliquez pour coller le lien de la playlist depuis le presse-papiers",
"link": "Lien:",
"downloadingPlaylist": "Téléchargement de la playlist:",
"downloadPlaylistButton": "Télécharger une playlist",
"playlistDownloaded": "Playlist téléchargée",
"cookiesWarning": "Cette option vous permet de télécharger du contenu restreint. Vous rencontrerez des erreurs s'il n'y a pas de cookies",
"selectBrowserForCookies": "Choisir le navigateur dont utiliser les cookies",
"none": "Aucun",
"updateAvailableDownloadPrompt": "Une nouvelle version est disponible, souhaitez-vous la télécharger ?",
"updateAvailablePrompt": "Une nouvelle version est disponible, souhaitez-vous mettre à jour ?",
"update": "Mise à jour",
"no": "Non",
"installAndRestartPrompt": "Installer et redémarrer maintenant ?",
"restart": "Redémarrer",
"later": "Plus tard",
"extractAudio": "Extraire l'audio",
"selectVideoFormat": "Choisir le format vidéo ",
"selectAudioFormat": "Choisir le format audio ",
"maxActiveDownloads": "Nombre maximal de téléchargements simultanés",
"preferredVideoQuality": "Qualité vidéo par défaut",
"preferredAudioFormat": "Qualité audio par défaut",
"best": "Meilleur",
"fileSaved": "Fichier enregistré.",
"openDownloadFolder": "Ouvrir dossier de téléchargements",
"path": "Chemin :",
"selectConfigFile": "Choisir un fichier de configuration",
"useConfigFile": "Utiliser un fichier de configuration",
"playlistFilenameFormat": "Format des noms de fichier pour les playlists",
"playlistFolderNameFormat": "Format des noms de dossier pour les playlists",
"resetToDefault": "Remettre la valeur par défaut",
"playlistRange": "Partie de la playlist",
"thumbnail": "Miniature",
"linkAdded": "Lien ajouté",
"downloadThumbnails": "Télécharger les miniatures",
"saveVideoLinksToFile": "Sauvegarder les liens des vidéos dans un fichier",
"closeAppToTray": "Fermer dans la barre d'état",
"useConfigFileCheckbox": "Utiliser un fichier de configuration",
"openApp": "Ouvrir l'application",
"pasteVideoLink": "Coller le lien de la vidéo",
"quit": "Quitter",
"errorDetails": "Détails de l'erreur",
"clickToCopy": "Cliquez pour copier",
"copiedText": "Texte copié",
"qualityNormal": "Normale",
"qualityGood": "Bon",
"qualityBad": "Mauvais",
"qualityWorst": "Pire",
"selectQuality": "Sélectionner la qualité",
"disableAutoUpdates": "Désactiver les mises à jour automatiques",
"qualityUltraLow": "extrêmement bas",
"closeAppOnFinish": "Fermer l'application à la fin du téléchargement",
"auto": "Automatique",
"theme": "Thème",
"themeLight": "Clair",
"themeDark": "Sombre",
"themeFrappe": "Frappé",
"themeOneDark": "One Dark",
"themeMatrix": "Matrice",
"themeSolarizedDark": "Solarisé sombre",
"preferredVideoCodec": "Codec vidéo préféré",
"showMoreFormatOptions": "Afficher plus d'options de format",
"flatsealPermissionWarning": "Vous devez donner à l'application la permission d'accéder au répertoire personnel pour utiliser ceci. Vous pouvez le faire avec Flatseal en activant la permission avec le texte 'filesystem=home'",
"noAudio": "Pas d'Audio",
"proxy": "Proxy",
"clearDownloads": "Effacer les téléchargements",
"compressor": "Compresseur",
"dragAndDropFiles": "Glisser-déposer le(s) fichier(s)",
"chooseFiles": "Choisir le(s) fichier(s)",
"noFilesSelected": "Aucun fichier sélectionné",
"videoFormat": "Format vidéo",
"videoEncoder": "Encodeur vidéo",
"compressionSpeed": "Vitesse de compression",
"videoQuality": "Qualité vidéo",
"audioFormat": "Format audio",
"outputSuffix": "Suffixe de sortie",
"outputInSameFolder": "Sortie dans le même dossier",
"selectCustomFolder": "Sélectionner un dossier personnalisé",
"startCompression": "Démarrer la compression",
"cancel": "Annuler",
"errorClickForDetails": "Erreur ! Cliquez pour les détails",
"homebrewYtDlpWarning": "Vous devez d'abord télécharger yt-dlp via Homebrew",
"openHomebrew": "Ouvrir Homebrew",
"downloadHistory": "Historique des téléchargements",
"close": "Fermer",
"searchByTitleOrUrl": "Rechercher par titre ou URL...",
"allFormats": "Tous les formats",
"exportAsJson": "Exporter en JSON",
"exportAsCsv": "Exporter en CSV",
"clearAllHistory": "Effacer tout l'historique",
"noDownloadsYet": "Aucun téléchargement pour l'instant",
"downloadHistoryPlaceholder": "Votre historique de téléchargement apparaîtra ici",
"format": "Format",
"size": "Taille",
"date": "Date",
"duration": "Durée",
"copyUrl": "Copier l'URL",
"open": "Ouvrir",
"delete": "Supprimer",
"totalDownloads": "Total des téléchargements",
"totalSize": "Taille totale",
"mostCommonFormat": "Format le plus courant",
"urlCopiedToClipboard": "URL copiée dans le presse-papiers !",
"confirmDeleteHistoryItem": "Êtes-vous sûr de vouloir supprimer cet élément de l'historique ?",
"confirmClearAllHistory": "Êtes-vous sûr de vouloir effacer tout l'historique de téléchargement ? Cette action est irréversible !",
"fileDoesNotExist": "Le fichier n'existe plus",
"updatingYtdlp": "Mise à jour de yt-dlp",
"updatedYtdlp": "yt-dlp mis à jour",
"ytDlpUpdateRequired": "Si yt-dlp est en cours de mise à jour, attendez que la mise à jour se termine. Si vous avez installé yt-dlp vous-même, veuillez le mettre à jour.",
"failedToDeleteHistoryItem": "Échec de la suppression de l'élément de l'historique",
"customArgsTxt": "Définir des options yt-dlp personnalisées.",
"learnMore": "En savoir plus",
"updateError": "Une erreur s'est produite lors du processus de mise à jour",
"unableToAccessDir": "Le programme ne peut pas accéder à ce dossier",
"downloadingUpdate": "Chargement des données"
}
================================================
FILE: translations/hi-IN.json
================================================
{
"preferences": "प्राथमिकताएं",
"about": "के बारे में",
"downloadLocation": "डाउनलोड स्थान",
"currentDownloadLocation": "वर्तमान डाउनलोड स्थान - ",
"enableTransparentDarkMode": "पारदर्शी डार्क मोड सक्षम करें (केवल Linux, पुनः आरंभ करना होगा)",
"downloadingNecessaryFilesWait": "कृपया प्रतीक्षा करें, आवश्यक फ़ाइलें डाउनलोड हो रही हैं",
"video": "वीडियो",
"audio": "ऑडियो",
"title": "शीर्षक ",
"selectFormat": "स्वरूप चुनें ",
"download": "डाउनलोड करें",
"selectDownloadLocation": "डाउनलोड स्थान चुनें",
"moreOptions": "अधिक विकल्प",
"start": "शुरू",
"selectLanguageRelaunch": "भाषा चुनें (पुनः आरंभ करना होगा)",
"downloadTimeRange": "एक विशेष समय-सीमा डाउनलोड करें",
"end": "समाप्त",
"timeRangeStartEmptyHint": "यदि खाली रखा जाता है, तो यह शुरुआत से शुरू होगा",
"timeRangeEndEmptyHint": "यदि खाली रखा जाता है, तो यह अंत तक डाउनलोड होगा",
"homepage": "मुखपृष्ठ",
"aboutAppDescription": "यह एक मुफ़्त और ओपन सोर्स ऐप है जो Node.js और Electron पर बनाया गया है। डाउनलोड के लिए yt-dlp का उपयोग किया गया है",
"sourceCodeAvailable": "स्रोत कोड उपलब्ध है ",
"here": "यहाँ",
"processing": "प्रसंस्करण हो रहा है",
"errorNetworkOrUrl": "कुछ त्रुटि हुई है। अपना नेटवर्क जांचें और सही URL का उपयोग करें",
"errorFailedFileDownload": "आवश्यक फ़ाइलें डाउनलोड करने में विफल। कृपया अपना नेटवर्क जांचें और पुनः प्रयास करें",
"tryAgain": "पुनः प्रयास करें",
"unknownSize": "अज्ञात आकार",
"megabyte": "MB",
"unknownQuality": "अज्ञात गुणवत्ता",
"downloading": "डाउनलोड हो रहा है...",
"errorHoverForDetails": "कुछ त्रुटि हुई है। विवरण देखने के लिए होवर करें",
"fileSavedSuccessfully": "फ़ाइल सफलतापूर्वक सहेजी गई",
"fileSavedClickToOpen": "फ़ाइल सहेजी गई। खोलने के लिए क्लिक करें",
"preparing": "तैयारी हो रही है...",
"progress": "प्रगति",
"speed": "गति",
"quality": "गुणवत्ता",
"restartApp": "ऐप पुनः आरंभ करें",
"subtitles": "उपशीर्षक",
"downloadSubtitlesAvailable": "उपलब्ध होने पर उपशीर्षक डाउनलोड करें",
"downloadSubtitlesAuto": "स्वचालित रूप से उत्पन्न उपशीर्षक डाउनलोड करें",
"extractAudioFromVideo": "वीडियो से ऑडियो निकालें",
"extract": "निकालें",
"downloadingNecessaryFiles": "आवश्यक फ़ाइलें डाउनलोड हो रही हैं",
"qualityLow": "कम",
"qualityMedium": "मध्यम",
"appDescription": "ytDownloader आपको YouTube, Facebook, Instagram, Tiktok, Twitter और कई अन्य सैकड़ों साइटों से वीडियो और ऑडियो डाउनलोड करने देता है",
"pasteText": "क्लिपबोर्ड से वीडियो लिंक पेस्ट करने के लिए क्लिक करें",
"pastePlaylistLinkTooltip": "क्लिपबोर्ड से प्लेलिस्ट लिंक पेस्ट करने के लिए क्लिक करें",
"link": "लिंक:",
"downloadingPlaylist": "प्लेलिस्ट डाउनलोड हो रही है:",
"downloadPlaylistButton": "प्लेलिस्ट डाउनलोड करें",
"playlistDownloaded": "प्लेलिस्ट डाउनलोड हो गई",
"cookiesWarning": "यह विकल्प आपको प्रतिबंधित सामग्री डाउनलोड करने देता है। यदि कुकीज़ मौजूद नहीं हैं तो आपको त्रुटियां मिलेंगी",
"selectBrowserForCookies": "कुकीज़ का उपयोग करने के लिए ब्राउज़र चुनें",
"none": "कोई नहीं",
"updateAvailableDownloadPrompt": "एक नया संस्करण उपलब्ध है, क्या आप इसे डाउनलोड करना चाहते हैं?",
"updateAvailablePrompt": "एक नया संस्करण उपलब्ध है, क्या आप अपडेट करना चाहते हैं?",
"update": "अपडेट करें",
"no": "नहीं",
"installAndRestartPrompt": "अभी इंस्टॉल करें और पुनः आरंभ करें?",
"restart": "पुनः आरंभ करें",
"later": "बाद में",
"extractAudio": "ऑडियो निकालें",
"selectVideoFormat": "वीडियो स्वरूप चुनें ",
"selectAudioFormat": "ऑडियो स्वरूप चुनें ",
"maxActiveDownloads": "सक्रिय डाउनलोड की अधिकतम संख्या",
"preferredVideoQuality": "पसंदीदा वीडियो गुणवत्ता",
"preferredAudioFormat": "पसंदीदा ऑडियो स्वरूप",
"best": "सर्वोत्तम",
"fileSaved": "फ़ाइल सहेजी गई।",
"openDownloadFolder": "डाउनलोड फ़ोल्डर खोलें",
"path": "पथ:",
"selectConfigFile": "कॉन्फ़िगरेशन फ़ाइल चुनें",
"useConfigFile": "कॉन्फ़िगरेशन फ़ाइल का उपयोग करें",
"playlistFilenameFormat": "प्लेलिस्ट के लिए फ़ाइल नाम स्वरूप",
"playlistFolderNameFormat": "प्लेलिस्ट के लिए फ़ोल्डर नाम स्वरूप",
"resetToDefault": "डिफ़ॉल्ट पर रीसेट करें",
"playlistRange": "प्लेलिस्ट रेंज",
"thumbnail": "थंबनेल",
"linkAdded": "लिंक जोड़ा गया",
"downloadThumbnails": "थंबनेल डाउनलोड करें",
"saveVideoLinksToFile": "वीडियो लिंक को एक फ़ाइल में सहेजें",
"closeAppToTray": "सिस्टम ट्रे में ऐप बंद करें",
"useConfigFileCheckbox": "कॉन्फ़िगरेशन फ़ाइल का उपयोग करें",
"openApp": "ऐप खोलें",
"pasteVideoLink": "वीडियो लिंक पेस्ट करें",
"quit": "छोड़ें",
"errorDetails": "त्रुटि विवरण",
"clickToCopy": "कॉपी करने के लिए क्लिक करें",
"copiedText": "पाठ कॉपी किया गया",
"qualityNormal": "सामान्य",
"qualityGood": "अच्छा",
"qualityBad": "बुरा",
"qualityWorst": "सबसे खराब",
"selectQuality": "गुणवत्ता चुनें",
"disableAutoUpdates": "ऑटो अपडेट अक्षम करें",
"qualityUltraLow": "अति निम्न",
"closeAppOnFinish": "डाउनलोड समाप्त होने पर ऐप बंद करें",
"auto": "ऑटो",
"theme": "थीम",
"themeLight": "हल्का",
"themeDark": "गहरा",
"themeFrappe": "फ्रैप्पे",
"themeOneDark": "वन डार्क",
"themeMatrix": "मैट्रिक्स",
"themeSolarizedDark": "सोलराइज़्ड डार्क",
"preferredVideoCodec": "पसंदीदा वीडियो कोडेक",
"showMoreFormatOptions": "अधिक स्वरूप विकल्प दिखाएँ",
"flatsealPermissionWarning": "इसका उपयोग करने के लिए आपको ऐप को होम डायरेक्टरी तक पहुंचने की अनुमति देनी होगी। आप इसे Flatseal के साथ 'filesystem=home' टेक्स्ट के साथ अनुमति सक्षम करके कर सकते हैं",
"noAudio": "कोई ऑडियो नहीं",
"proxy": "प्रॉक्सी",
"clearDownloads": "डाउनलोड साफ़ करें",
"compressor": "कंप्रेसर",
"dragAndDropFiles": "फ़ाइल (फ़ाइलें) खींचें और छोड़ें",
"chooseFiles": "फ़ाइल (फ़ाइलें) चुनें",
"noFilesSelected": "कोई फ़ाइल नहीं चुनी गई",
"videoFormat": "वीडियो स्वरूप",
"videoEncoder": "वीडियो एन्कोडर",
"compressionSpeed": "संपीड़न गति",
"videoQuality": "वीडियो गुणवत्ता",
"audioFormat": "ऑडियो स्वरूप",
"outputSuffix": "आउटपुट प्रत्यय",
"outputInSameFolder": "एक ही फ़ोल्डर में आउटपुट",
"selectCustomFolder": "कस्टम फ़ोल्डर चुनें",
"startCompression": "संपीड़न शुरू करें",
"cancel": "रद्द करें",
"errorClickForDetails": "त्रुटि! विवरण के लिए क्लिक करें",
"homebrewYtDlpWarning": "आपको पहले Homebrew से yt-dlp डाउनलोड करना होगा",
"openHomebrew": "होमब्रू खोलें",
"downloadHistory": "डाउनलोड इतिहास",
"close": "बंद करें",
"searchByTitleOrUrl": "शीर्षक या URL द्वारा खोजें...",
"allFormats": "सभी स्वरूप",
"exportAsJson": "JSON के रूप में निर्यात करें",
"exportAsCsv": "CSV के रूप में निर्यात करें",
"clearAllHistory": "सभी इतिहास साफ़ करें",
"noDownloadsYet": "अभी तक कोई डाउनलोड नहीं",
"downloadHistoryPlaceholder": "आपका डाउनलोड इतिहास यहाँ दिखाई देगा",
"format": "स्वरूप",
"size": "आकार",
"date": "तारीख",
"duration": "अवधि",
"copyUrl": "URL कॉपी करें",
"open": "खोलें",
"delete": "हटाएँ",
"totalDownloads": "कुल डाउनलोड",
"totalSize": "कुल आकार",
"mostCommonFormat": "सबसे सामान्य स्वरूप",
"urlCopiedToClipboard": "URL क्लिपबोर्ड पर कॉपी हो गया!",
"confirmDeleteHistoryItem": "क्या आप वाकई इस आइटम को इतिहास से हटाना चाहते हैं?",
"confirmClearAllHistory": "क्या आप वाकई सभी डाउनलोड इतिहास साफ़ करना चाहते हैं? यह पूर्ववत नहीं किया जा सकता!",
"fileDoesNotExist": "File does not exist anymore",
"updatingYtdlp": "Updating yt-dlp",
"updatedYtdlp": "Updated yt-dlp",
"ytDlpUpdateRequired": "If yt-dlp is updating, wait for the update to finish. If you have installed yt-dlp by yourself, please update it.",
"failedToDeleteHistoryItem": "Failed to delete history item",
"customArgsTxt": "Set custom yt-dlp options.",
"learnMore": "और जानें",
"updateError": "An error occurred during the update process",
"unableToAccessDir": "The program cannot access that folder",
"downloadingUpdate": "Downloading update"
}
================================================
FILE: translations/hu-HU.json
================================================
{
"preferences": "Beállítások",
"about": "Névjegy",
"downloadLocation": "Letöltés helye",
"currentDownloadLocation": "Jelenlegi letöltés helye - ",
"enableTransparentDarkMode": "Áttetszó sötét mód bekapcsolása (csak Linuxon, újraindítás szükséges)",
"downloadingNecessaryFilesWait": "Kérem várjon, a szükséges fájlok letöltés alatt",
"video": "Videó",
"audio": "Hang",
"title": "Cím ",
"selectFormat": "Válasszon formátumot ",
"download": "Letöltés",
"selectDownloadLocation": "Letöltési hely kiválasztása",
"moreOptions": "További beállítások",
"start": "Indít",
"selectLanguageRelaunch": "Nyelv kiválasztása (újraindítás szükséges)",
"downloadTimeRange": "Idősáv letöltése",
"end": "Vége",
"timeRangeStartEmptyHint": "Elejéről kezdi, ha üresen hagyja",
"timeRangeEndEmptyHint": "Végéig letölti, ha üresen hagyja",
"homepage": "Kezdőlap",
"aboutAppDescription": "Ez egy ingyenes és nyílt forráskódú alkalmazás, amely a Node.js-re és az Electronra épül. yt-dlp-t letöltéshez használható",
"sourceCodeAvailable": "Forráskód elérhető ",
"here": "itt",
"processing": "Feldolgozás alatt",
"errorNetworkOrUrl": "Valami hiba történt. Ellenőrizze a hálózatot, és használja a megfelelő URL-t",
"errorFailedFileDownload": "Szükséges fájlok letöltése nem sikerült. Kérjük, ellenőrizze a hálózatot, és próbálja újra",
"tryAgain": "Próbálja meg újra",
"unknownSize": "Ismeretlen méret",
"megabyte": "MB",
"unknownQuality": "Ismeretlen minőség",
"downloading": "Letöltés...",
"errorHoverForDetails": "Hiba történt. Vigye az egérmutatót a hiba fölé a részletekért",
"fileSavedSuccessfully": "Fájl sikeresen elmentve",
"fileSavedClickToOpen": "Fájl elmentve. Kattintson a megnyitáshoz",
"preparing": "Előkészítés...",
"progress": "Készültség",
"speed": "Sebesség",
"quality": "Minőség",
"restartApp": "Alkalmazás újraindítása",
"subtitles": "Feliratok",
"downloadSubtitlesAvailable": "Felirat letöltése, ha elérhető",
"downloadSubtitlesAuto": "Automatikusan generált feliratok letöltése",
"extractAudioFromVideo": "Hang kinyerése videóból",
"extract": "Kinyerés",
"downloadingNecessaryFiles": "Szükséges fájlok letöltése",
"qualityLow": "alacsony",
"qualityMedium": "közepes",
"appDescription": "a ytDownloader segítségével videókat és hanganyagokat tölthet le több száz webhelyről, például Youtube, Facebook, Instagram, Tiktok, Twitter és így tovább",
"pasteText": "Kattintson ide a videólink vágólapról történő beillesztéséhez",
"pastePlaylistLinkTooltip": "Kattintson ide a lejátszási lista linkjének vágólapról történő beillesztéséhez",
"link": "Link:",
"downloadingPlaylist": "Lejátszási lista letöltése:",
"downloadPlaylistButton": "Lejátszási lista letöltése",
"playlistDownloaded": "Lejátszási lista letöltve",
"cookiesWarning": "Ezzel az opcióval korlátozott tartalom is letölthető. Hibákat fog kapni sütik hiányában",
"selectBrowserForCookies": "Válaszzon böngészőt, ahonnan a sütiket használjuk",
"none": "Egyik sem",
"updateAvailableDownloadPrompt": "Új verzió érhető el, szeretné letölteni?",
"updateAvailablePrompt": "Új verzió érhető el, szeretné frissíteni?",
"update": "Frissítés",
"no": "Nem",
"installAndRestartPrompt": "Telepíti és újraindítja most?",
"restart": "Újraindítás",
"later": "Később",
"extractAudio": "Hang kinyerése",
"selectVideoFormat": "Válasszon videó formátumot ",
"selectAudioFormat": "Válasszon hang formátumot ",
"maxActiveDownloads": "Aktív letöltések maximum száma",
"preferredVideoQuality": "Előnyben részesített videó formátum",
"preferredAudioFormat": "Előnyben részesített hang formátum",
"best": "Legjobb",
"fileSaved": "Fájl elmentve",
"openDownloadFolder": "Letöltési mappa megnyitása",
"path": "Elérési út:",
"selectConfigFile": "Konfigurációs fájl kiválasztása",
"useConfigFile": "Konfigurációs fájl alkalmazása",
"playlistFilenameFormat": "Fájlnév formátum lejátszási listákhoz",
"playlistFolderNameFormat": "Mappanév formátum lejátszási listákhoz",
"resetToDefault": "Visszaállítás alapértelmezettre",
"playlistRange": "Lejátszási lista hossza",
"thumbnail": "Előnézet",
"linkAdded": "Link hozzáadva",
"downloadThumbnails": "Előnézet letöltése",
"saveVideoLinksToFile": "Videó linkek mentése fájlba",
"closeAppToTray": "Bezárás az értesítési területre",
"useConfigFileCheckbox": "Konfigurációs fájl használata",
"openApp": "Alkalmazás megnyitása",
"pasteVideoLink": "Videó link beillesztése",
"quit": "Kilépés",
"errorDetails": "Hiba részletei",
"clickToCopy": "Kattintson a másoláshoz",
"copiedText": "Kimásolt szöveg",
"qualityNormal": "Átlagos",
"qualityGood": "Jó",
"qualityBad": "Rossz",
"qualityWorst": "Legrosszabb",
"selectQuality": "Minőség kiválasztása",
"disableAutoUpdates": "Automatikus frissítések kikapcsolása",
"qualityUltraLow": "legeslegrosszabb",
"closeAppOnFinish": "Alkalmazás bezárása amint a letöltés befejeződött",
"auto": "Automatikus",
"theme": "Téma",
"themeLight": "Világos",
"themeDark": "Sötét",
"themeFrappe": "Frappé",
"themeOneDark": "Sötét",
"themeMatrix": "Mátrix",
"themeSolarizedDark": "Szolarizált sötét",
"preferredVideoCodec": "Előnyben részesített videokodek",
"showMoreFormatOptions": "További formátumbeállítások megjelenítése",
"flatsealPermissionWarning": "Ennek használatához engedélyt kell adnia az alkalmazásnak a home könyvtár eléréséhez. Meg tudod csinálni a Flatseal segítségével, Ha engedélyezed az engedélyt a 'filesystem=home'szöveggel",
"noAudio": "Nincs hang",
"proxy": "Proxy",
"clearDownloads": "Letöltések törlése",
"compressor": "Tömörítő",
"dragAndDropFiles": "Húzzon át fájl(oka)t",
"chooseFiles": "Fájl(ok) kiválasztása",
"noFilesSelected": "Nincs kiválasztott fájl",
"videoFormat": "Videó formátum",
"videoEncoder": "Videó kódoló",
"compressionSpeed": "Tömörítési sebesség",
"videoQuality": "Videó minőség",
"audioFormat": "Hang formátum",
"outputSuffix": "Kimeneti utótag",
"outputInSameFolder": "Kimenet ugyanabba a mappába",
"selectCustomFolder": "Egyedi mappa kiválasztása",
"startCompression": "Tömörítés indítása",
"cancel": "Mégse",
"errorClickForDetails": "Hiba! Kattintson a részletekért",
"homebrewYtDlpWarning": "Először le kell töltenie a yt-dlp-t a Homebrew-ból",
"openHomebrew": "Homebrew megnyitása",
"downloadHistory": "Letöltési előzmények",
"close": "Bezárás",
"searchByTitleOrUrl": "Keresés cím vagy URL alapján...",
"allFormats": "Minden formátum",
"exportAsJson": "Exportálás JSON-ként",
"exportAsCsv": "Exportálás CSV-ként",
"clearAllHistory": "Összes előzmény törlése",
"noDownloadsYet": "Még nincsenek letöltések",
"downloadHistoryPlaceholder": "A letöltési előzmények itt fognak megjelenni",
"format": "Formátum",
"size": "Méret",
"date": "Dátum",
"duration": "Időtartam",
"copyUrl": "URL másolása",
"open": "Megnyitás",
"delete": "Törlés",
"totalDownloads": "Összes letöltés",
"totalSize": "Teljes méret",
"mostCommonFormat": "Leggyakoribb formátum",
"urlCopiedToClipboard": "URL vágólapra másolva!",
"confirmDeleteHistoryItem": "Biztosan törölni szeretné ezt az elemet az előzményekből?",
"confirmClearAllHistory": "Biztosan törölni szeretné az összes letöltési előzményt? Ezt nem lehet visszavonni!",
"fileDoesNotExist": "File does not exist anymore",
"updatingYtdlp": "Updating yt-dlp",
"updatedYtdlp": "Updated yt-dlp",
"ytDlpUpdateRequired": "If yt-dlp is updating, wait for the update to finish. If you have installed yt-dlp by yourself, please update it.",
"failedToDeleteHistoryItem": "Failed to delete history item",
"customArgsTxt": "Set custom yt-dlp options.",
"learnMore": "További információ",
"updateError": "Hiba történt a frissítési folyamat során",
"unableToAccessDir": "The program cannot access that folder",
"downloadingUpdate": "Frissítés letöltése"
}
================================================
FILE: translations/i18n-init.js
================================================
const I18n = require("../translations/i18n");
const i18n = new I18n();
(async () => {
await i18n.init();
document.dispatchEvent(new Event("translations-loaded"));
})();
window.i18n = i18n;
================================================
FILE: translations/i18n.js
================================================
const {ipcRenderer} = require("electron");
function normalizeLocale(locale) {
if (!locale) return "en";
const parts = locale.split("-");
const lang = parts[0].toLowerCase();
const region = parts[1] ? parts[1].toUpperCase() : null;
const defaultRegions = {
zh: "CN",
en: "US",
ru: "RU",
pt: "BR",
fi: "FI",
fr: "FR",
es: "ES",
de: "DE",
it: "IT",
ja: "JP",
ar: "SA",
};
if (!region && defaultRegions[lang]) {
return `${lang}-${defaultRegions[lang]}`;
}
return region ? `${lang}-${region}` : lang;
}
async function getLocale() {
try {
const saved = localStorage.getItem("locale");
if (saved) return saved;
} catch (e) {}
let locale = null;
try {
locale = await ipcRenderer.invoke("get-system-locale");
} catch (e) {
console.log(e)
}
if (!locale && typeof navigator !== "undefined") {
locale =
navigator.language ||
(navigator.languages && navigator.languages[0]);
}
const normalized = normalizeLocale(locale || "en");
try {
localStorage.setItem("locale", normalized);
} catch (e) {}
return normalized;
}
function I18n() {
this.loadedLanguage = {};
this.locale = "en";
this.init = async () => {
try {
this.locale = await getLocale();
this.loadedLanguage = await ipcRenderer.invoke(
"get-translation",
this.locale
);
} catch (error) {
console.error("Error loading translations:", error);
this.loadedLanguage = {};
}
};
this.__ = function (phrase) {
return this.loadedLanguage[phrase] !== undefined
? this.loadedLanguage[phrase]
: phrase;
};
this.translatePage = () => {
document.querySelectorAll("[data-translate]").forEach((element) => {
const key = element.getAttribute("data-translate");
element.textContent = this.__(key);
});
document
.querySelectorAll("[data-translate-placeholder]")
.forEach((element) => {
const key = element.getAttribute("data-translate-placeholder");
element.placeholder = this.__(key);
});
document
.querySelectorAll("[data-translate-title]")
.forEach((element) => {
const key = element.getAttribute("data-translate-title");
element.title = this.__(key);
});
};
this.setLocale = async function (newLocale) {
const normalized = normalizeLocale(newLocale);
localStorage.setItem("locale", normalized);
this.loadedLanguage = await ipcRenderer.invoke(
"get-translation",
normalized
);
this.locale = normalized;
this.translatePage();
};
}
module.exports = I18n;
================================================
FILE: translations/it-IT.json
================================================
{
"preferences": "Preferenze",
"about": "Informazioni",
"downloadLocation": "Posizione di download",
"currentDownloadLocation": "Posizione di download corrente - ",
"enableTransparentDarkMode": "Abilita la modalità scura trasparente (solo Linux, necessita di riavvio)",
"downloadingNecessaryFilesWait": "Attendere, è in corso lo scaricamento dei file necessari",
"video": "Video",
"audio": "Audio",
"title": "Titolo ",
"selectFormat": "Selezione formato ",
"download": "Scarica",
"selectDownloadLocation": "Seleziona posizione di download",
"moreOptions": "Più opzioni",
"start": "Inizia",
"selectLanguageRelaunch": "Seleziona la lingua (richiede il riavvio)",
"downloadTimeRange": "Scarica particolare intervallo di tempo",
"end": "Fine",
"timeRangeStartEmptyHint": "Se lasciato vuoto, ricomincerà dall'inizio",
"timeRangeEndEmptyHint": "Se lasciato vuoto, verrà scaricato fino alla fine",
"homepage": "Homepage",
"aboutAppDescription": "È un'app gratuita e open source basata su Node.js ed Electron. yt-dlp è stato utilizzato per il download",
"sourceCodeAvailable": "Il codice sorgente è disponibile ",
"here": "qui",
"processing": "In elaborazione",
"errorNetworkOrUrl": "Si è verificato un errore. Controlla la tua rete e usa l'URL corretto",
"errorFailedFileDownload": "Impossibile scaricare i file necessari. Controlla la tua rete e riprova",
"tryAgain": "Riprova",
"unknownSize": "Dimensione sconosciuta",
"megabyte": "MB",
"unknownQuality": "Qualità sconosciuta",
"downloading": "Scaricamento in corso...",
"errorHoverForDetails": "Si è verificato un errore. Passa il mouse per vedere i dettagli",
"fileSavedSuccessfully": "File salvato con successo",
"fileSavedClickToOpen": "File salvato. Fare clic per aprire",
"preparing": "Preparazione in corso...",
"progress": "Avanzamento in corso",
"speed": "Velocità",
"quality": "Qualità",
"restartApp": "Riavvia l'app",
"subtitles": "Sottotitoli",
"downloadSubtitlesAvailable": "Scarica i sottotitoli se disponibili",
"downloadSubtitlesAuto": "Scarica i sottotitoli generati automaticamente",
"extractAudioFromVideo": "Estrai l'audio dal video",
"extract": "Estrai",
"downloadingNecessaryFiles": "Scaricamento dei file necessari",
"qualityLow": "bassa",
"qualityMedium": "media",
"appDescription": "ytDownloader ti consente di scaricare video e audio da centinaia di siti come Youtube, Facebook, Instagram, Tiktok, Twitter e così via",
"pasteText": "Clicca per incollare il link del video dagli appunti",
"pastePlaylistLinkTooltip": "Clicca per incollare il link della playlist dagli appunti",
"link": "Collegamento:",
"downloadingPlaylist": "Scaricamento playlist:",
"downloadPlaylistButton": "Scarica playlist",
"playlistDownloaded": "Playlist scaricata",
"cookiesWarning": "Questa opzione consente di scaricare contenuti limitati. Si otterranno errori se i cookie non ci sono",
"selectBrowserForCookies": "Seleziona il browser da cui utilizzare i cookie",
"none": "Nessuna",
"updateAvailableDownloadPrompt": "È disponibile una nuova versione, vuoi scaricarla?",
"updateAvailablePrompt": "È disponibile una nuova versione, vuoi aggiornare?",
"update": "Aggiorna",
"no": "No",
"installAndRestartPrompt": "Installare e riavviare ora?",
"restart": "Riavvia",
"later": "Dopo",
"extractAudio": "Estrai Audio",
"selectVideoFormat": "Seleziona Formato Video ",
"selectAudioFormat": "Seleziona Formato Audio ",
"maxActiveDownloads": "Numero massimo di download attivi",
"preferredVideoQuality": "Qualità video preferita",
"preferredAudioFormat": "Formato audio preferito",
"best": "Migliore",
"fileSaved": "File salvato.",
"openDownloadFolder": "Apri cartella di download",
"path": "Percorso:",
"selectConfigFile": "File di configurazione",
"useConfigFile": "Usa il file di configurazione",
"playlistFilenameFormat": "Formato nome file per scalette",
"playlistFolderNameFormat": "Formato nome cartella per le scalette",
"resetToDefault": "Ripristina predefinito",
"playlistRange": "Intervallo di playlist",
"thumbnail": "Miniatura",
"linkAdded": "Link aggiunto",
"downloadThumbnails": "Scarica miniature",
"saveVideoLinksToFile": "Salva link video su un file",
"closeAppToTray": "Chiudi app nel vassoio di sistema",
"useConfigFileCheckbox": "Usa file di configurazione",
"openApp": "Apri app",
"pasteVideoLink": "Incolla link video",
"quit": "Esci",
"errorDetails": "Dettagli errore",
"clickToCopy": "Clicca per copiare",
"copiedText": "Testo copiato",
"qualityNormal": "Normale",
"qualityGood": "Buona",
"qualityBad": "Cattiva",
"qualityWorst": "Bassa",
"selectQuality": "Seleziona qualità",
"disableAutoUpdates": "Disabilita aggiornamenti automatici",
"qualityUltraLow": "ultra bassa",
"closeAppOnFinish": "Chiudi app al termine del download",
"auto": "Automatico",
"theme": "Tema",
"themeLight": "Chiaro",
"themeDark": "Scuro",
"themeFrappe": "Frappé",
"themeOneDark": "Uno Oscuro",
"themeMatrix": "Matrice",
"themeSolarizedDark": "Solarizzato scuro",
"preferredVideoCodec": "Codifica video preferita",
"showMoreFormatOptions": "Mostra più opzioni di formato",
"flatsealPermissionWarning": "Devi dare all'app il permesso di accedere alla home directory per usarlo. Puoi farlo con Flatseal abilitando l'autorizzazione con il testo 'filesystem=home'",
"noAudio": "Nessun Audio",
"proxy": "Proxy",
"clearDownloads": "Cancella Download",
"compressor": "Compressore",
"dragAndDropFiles": "Trascina e rilascia file",
"chooseFiles": "Scegli File",
"noFilesSelected": "Nessun file selezionato",
"videoFormat": "Formato Video",
"videoEncoder": "Codificatore Video",
"compressionSpeed": "Velocità di Compressione",
"videoQuality": "Qualità Video",
"audioFormat": "Formato Audio",
"outputSuffix": "Suffisso di Output",
"outputInSameFolder": "Output nella stessa cartella",
"selectCustomFolder": "Seleziona cartella personalizzata",
"startCompression": "Avvia Compressione",
"cancel": "Annulla",
"errorClickForDetails": "Errore! Clicca per i dettagli",
"homebrewYtDlpWarning": "Devi prima scaricare yt-dlp da Homebrew",
"openHomebrew": "Apri Homebrew",
"downloadHistory": "Cronologia Download",
"close": "Chiudi",
"searchByTitleOrUrl": "Cerca per titolo o URL...",
"allFormats": "Tutti i Formati",
"exportAsJson": "Esporta come JSON",
"exportAsCsv": "Esporta come CSV",
"clearAllHistory": "Cancella Tutta la Cronologia",
"noDownloadsYet": "Ancora Nessun Download",
"downloadHistoryPlaceholder": "La tua cronologia download apparirà qui",
"format": "Formato",
"size": "Dimensione",
"date": "Data",
"duration": "Durata",
"copyUrl": "Copia URL",
"open": "Apri",
"delete": "Elimina",
"totalDownloads": "Download Totali",
"totalSize": "Dimensione Totale",
"mostCommonFormat": "Formato più Comune",
"urlCopiedToClipboard": "URL copiato negli appunti!",
"confirmDeleteHistoryItem": "Sei sicuro di voler eliminare questo elemento dalla cronologia?",
"confirmClearAllHistory": "Sei sicuro di voler cancellare tutta la cronologia dei download? Questa azione è irreversibile!",
"fileDoesNotExist": "File does not exist anymore",
"updatingYtdlp": "Updating yt-dlp",
"updatedYtdlp": "Updated yt-dlp",
"ytDlpUpdateRequired": "If yt-dlp is updating, wait for the update to finish. If you have installed yt-dlp by yourself, please update it.",
"failedToDeleteHistoryItem": "Failed to delete history item",
"customArgsTxt": "Set custom yt-dlp options.",
"learnMore": "Learn more",
"updateError": "An error occurred during the update process",
"unableToAccessDir": "The program cannot access that folder",
"downloadingUpdate": "Downloading update"
}
================================================
FILE: translations/ja-JP.json
================================================
{
"preferences": "環境設定",
"about": "概要",
"downloadLocation": "ダウンロード場所",
"currentDownloadLocation": "現在のダウンロード場所 - ",
"enableTransparentDarkMode": "透明なダークモードを有効にする(Linuxのみ、再起動が必要)",
"downloadingNecessaryFilesWait": "お待ちください、必要なファイルがダウンロードされています",
"video": "ビデオ",
"audio": "オーディオ",
"title": "タイトル ",
"selectFormat": "フォーマットを選択 ",
"download": "ダウンロード",
"selectDownloadLocation": "ダウンロード場所を選択",
"moreOptions": "その他のオプション",
"start": "開始",
"selectLanguageRelaunch": "言語を選択(再起動が必要)",
"downloadTimeRange": "特定の時間範囲をダウンロード",
"end": "終了",
"timeRangeStartEmptyHint": "空白の場合は、最初から始めます",
"timeRangeEndEmptyHint": "空白の場合、最後までダウンロードされます",
"homepage": "ホーム",
"aboutAppDescription": "これは、Node.jsとElectronの上に構築されたフリーでオープンソースアプリです。yt-dlpはダウンロードに使用されています。",
"sourceCodeAvailable": "ソースコードはここで公開されています ",
"here": "こちら",
"processing": "処理中",
"errorNetworkOrUrl": "いくつかのエラーが発生しました。ネットワークを確認し、正しいURLを使用してください。",
"errorFailedFileDownload": "必要なファイルのダウンロードに失敗しました。ネットワークを確認し、もう一度試してください。",
"tryAgain": "もう一度試してください",
"unknownSize": "不明なサイズ",
"megabyte": "MB",
"unknownQuality": "不明な品質",
"downloading": "ダウンロード中…",
"errorHoverForDetails": "いくつかのエラーが発生しました。詳細を確認してください",
"fileSavedSuccessfully": "ファイルは正常に保存されました",
"fileSavedClickToOpen": "ファイルを保存しました。クリックして開く",
"preparing": "準備中...",
"progress": "進行状況",
"speed": "速度",
"quality": "品質",
"restartApp": "アプリを再起動",
"subtitles": "字幕",
"downloadSubtitlesAvailable": "利用可能な場合は字幕をダウンロード",
"downloadSubtitlesAuto": "自動生成された字幕をダウンロード",
"extractAudioFromVideo": "ビデオからオーディオを抽出",
"extract": "抽出",
"downloadingNecessaryFiles": "必要なファイルをダウンロード中",
"qualityLow": "低",
"qualityMedium": "中",
"appDescription": "ytDownloaderを使えば、Youtube・Facebook・Instagram・Tiktok・Twitterなど何百ものサイトからビデオやオーディオをダウンロードすることができます",
"pasteText": "クリップボードからビデオリンクを貼り付けるにはクリックしてください",
"pastePlaylistLinkTooltip": "クリップボードからプレイリストのリンクを貼り付けるにはクリックしてください",
"link": "リンク:",
"downloadingPlaylist": "プレイリストをダウンロード中:",
"downloadPlaylistButton": "プレイリストをダウンロード",
"playlistDownloaded": "プレイリストをダウンロードしました",
"cookiesWarning": "このオプションを使用すると、制限されたコンテンツをダウンロードできます。クッキーがない場合はエラーが発生します。",
"selectBrowserForCookies": "Cookieを使用するブラウザを選択",
"none": "無し",
"updateAvailableDownloadPrompt": "新しいバージョンが利用可能です。ダウンロードしますか?",
"updateAvailablePrompt": "新しいバージョンが利用可能です。アップデートしますか?",
"update": "アップデート",
"no": "いいえ",
"installAndRestartPrompt": "インストールして再起動しますか?",
"restart": "再起動",
"later": "後で",
"extractAudio": "オーディオを抽出",
"selectVideoFormat": "ビデオフォーマットを選択 ",
"selectAudioFormat": "オーディオフォーマットを選択 ",
"maxActiveDownloads": "アクティブなダウンロードの最大数",
"preferredVideoQuality": "優先するビデオ品質",
"preferredAudioFormat": "優先する音声フォーマット",
"best": "最高",
"fileSaved": "ファイルを保存しました。",
"openDownloadFolder": "ダウンロードフォルダを開く",
"path": "パス:",
"selectConfigFile": "設定ファイルを選択",
"useConfigFile": "設定ファイルを使用",
"playlistFilenameFormat": "プレイリストのファイル名フォーマット",
"playlistFolderNameFormat": "プレイリストのフォルダ名フォーマット",
"resetToDefault": "初期設定に戻す",
"playlistRange": "プレイリストの範囲",
"thumbnail": "サムネイル",
"linkAdded": "リンクを追加しました",
"downloadThumbnails": "サムネイルをダウンロード",
"saveVideoLinksToFile": "ビデオリンクをファイルに保存",
"closeAppToTray": "システムトレイにアプリを閉じる",
"useConfigFileCheckbox": "設定ファイルを使用",
"openApp": "アプリを開く",
"pasteVideoLink": "ビデオリンクを貼り付け",
"quit": "終了",
"errorDetails": "エラーの詳細",
"clickToCopy": "クリックしてコピー",
"copiedText": "コピーしたテキスト",
"qualityNormal": "標準",
"qualityGood": "良",
"qualityBad": "悪",
"qualityWorst": "最悪",
"selectQuality": "品質を選択",
"disableAutoUpdates": "自動アップデートを無効",
"qualityUltraLow": "超低",
"closeAppOnFinish": "ダウンロード終了時にアプリを閉じる",
"auto": "自動",
"theme": "外観",
"themeLight": "ライト",
"themeDark": "ダーク",
"themeFrappe": "フラッペ",
"themeOneDark": "One Dark",
"themeMatrix": "マトリックス",
"themeSolarizedDark": "ソーラライズド・ダーク",
"preferredVideoCodec": "優先するビデオコーデック",
"showMoreFormatOptions": "他のフォーマットオプションを表示",
"flatsealPermissionWarning": "これを使用するには、ホームディレクトリにアクセスする権限をアプリに与える必要があります。 テキスト'filesystem=home'で権限を有効にすることで、Flatsealでそれを行うことができます",
"noAudio": "オーディオなし",
"proxy": "プロキシ",
"clearDownloads": "ダウンロードをクリア",
"compressor": "コンプレッサー",
"dragAndDropFiles": "ファイル(群)をドラッグ&ドロップ",
"chooseFiles": "ファイルを選択",
"noFilesSelected": "ファイルは選択されていません",
"videoFormat": "ビデオフォーマット",
"videoEncoder": "ビデオエンコーダー",
"compressionSpeed": "圧縮速度",
"videoQuality": "ビデオ品質",
"audioFormat": "オーディオフォーマット",
"outputSuffix": "出力サフィックス",
"outputInSameFolder": "同じフォルダに出力",
"selectCustomFolder": "カスタムフォルダを選択",
"startCompression": "圧縮を開始",
"cancel": "キャンセル",
"errorClickForDetails": "エラー!クリックで詳細を確認",
"homebrewYtDlpWarning": "まずHomebrewからyt-dlpをダウンロードする必要があります",
"openHomebrew": "Homebrewを開く",
"downloadHistory": "ダウンロード履歴",
"close": "閉じる",
"searchByTitleOrUrl": "タイトルまたはURLで検索...",
"allFormats": "全てのフォーマット",
"exportAsJson": "JSONとしてエクスポート",
"exportAsCsv": "CSVとしてエクスポート",
"clearAllHistory": "全ての履歴をクリア",
"noDownloadsYet": "まだダウンロードはありません",
"downloadHistoryPlaceholder": "ここにダウンロード履歴が表示されます",
"format": "フォーマット",
"size": "サイズ",
"date": "日付",
"duration": "期間",
"copyUrl": "URLをコピー",
"open": "開く",
"delete": "削除",
"totalDownloads": "合計ダウンロード数",
"totalSize": "合計サイズ",
"mostCommonFormat": "最も一般的なフォーマット",
"urlCopiedToClipboard": "URLがクリップボードにコピーされました!",
"confirmDeleteHistoryItem": "本当にこのアイテムを履歴から削除してもよろしいですか?",
"confirmClearAllHistory": "本当に全てのダウンロード履歴をクリアしてもよろしいですか?この操作は元に戻せません!",
"fileDoesNotExist": "ファイルはもう存在しません",
"updatingYtdlp": "yt-dlpの更新",
"updatedYtdlp": "更新日 yt-dlp",
"ytDlpUpdateRequired": "Yt-dlpが更新されている場合は、更新が完了するのを待ちます。yt-dlpを自分でインストールした場合は更新してください。",
"failedToDeleteHistoryItem": "履歴アイテムの削除に失敗しました",
"customArgsTxt": "カスタム yt-dlp オプションを設定します。",
"learnMore": "詳細",
"updateError": "更新処理中にエラー発生.",
"unableToAccessDir": "プログラムはそのフォルダにアクセスできません",
"downloadingUpdate": "更新プログラムをダウンロード中"
}
================================================
FILE: translations/ne-NP.json
================================================
{
"preferences": "सेटिङहरू",
"about": "बारेमा",
"downloadLocation": "डाउनलोड स्थान",
"currentDownloadLocation": "हालको डाउनलोड स्थान - ",
"enableTransparentDarkMode": "पारदर्शी डार्क मोड सक्षम गर्नुहोस् (लिनक्स मात्र, पुनः सुरु गर्नु पर्छ)",
"downloadingNecessaryFilesWait": "कृपया पर्खनुहोस्, आवश्यक फाइलहरू डाउनलोड हुँदैछन्",
"video": "भिडियो",
"audio": "अडियो",
"title": "शीर्षक ",
"selectFormat": "ढाँचा चयन गर्नुहोस् ",
"download": "डाउनलोड गर्नुहोस्",
"selectDownloadLocation": "डाउनलोड स्थान चयन गर्नुहोस्",
"moreOptions": "थप विकल्पहरू",
"start": "सुरु",
"selectLanguageRelaunch": "भाषा चयन गर्नुहोस् (पुनः सुरु गर्नु पर्छ)",
"downloadTimeRange": "समय दायरा डाउनलोड",
"end": "समाप्ति",
"timeRangeStartEmptyHint": "यदि खाली राखियो भने, यो सुरु देखि हुनेछ",
"timeRangeEndEmptyHint": "यदि खाली राखियो भने, यो सुरु अन्त्यदेखि हुनेछ",
"homepage": "गृहपृष्ठ",
"aboutAppDescription": "यो Node.js र Electron मा निर्मित एक नि: शुल्क र खुला स्रोत एप हो। yt-dlp डाउनलोड गर्न प्रयोग गरिएको छ",
"sourceCodeAvailable": "स्रोत कोड उपलब्ध छ ",
"here": "यहाँ",
"processing": "प्रशोधन भइरहेको",
"errorNetworkOrUrl": "केही त्रुटि भएको छ। आफ्नो नेटवर्क जाँच गर्नुहोस् र सही URL प्रयोग गर्नुहोस्",
"errorFailedFileDownload": "आवश्यक फाइलहरू डाउनलोड गर्न असफल भयो। कृपया आफ्नो नेटवर्क जाँच गर्नुहोस् र फेरि प्रयास गर्नुहोस्",
"tryAgain": "फेरि प्रयास गर्नुहोस्",
"unknownSize": "अज्ञात आकार",
"megabyte": "MB",
"unknownQuality": "अज्ञात गुणस्तर",
"downloading": "डाउनलोड हुँदैछ...",
"errorHoverForDetails": "केही त्रुटि भएको छ। विवरण हेर्न होभर गर्नुहोस्",
"fileSavedSuccessfully": "फाइल सफलतापूर्वक सुरक्षित भयो",
"fileSavedClickToOpen": "फाइल सुरक्षित भयो। खोल्न क्लिक गर्नुहोस्",
"preparing": "तयारी हुँदैछ...",
"progress": "प्रगति",
"speed": "गति",
"quality": "गुणस्तर",
"restartApp": "एप पुनः सुरु गर्नुहोस्",
"subtitles": "उपशीर्षकहरू",
"downloadSubtitlesAvailable": "उपलब्ध भएमा उपशीर्षकहरू डाउनलोड गर्नुहोस्",
"downloadSubtitlesAuto": "स्वचालित रूपमा उत्पन्न उपशीर्षकहरू डाउनलोड गर्नुहोस्",
"extractAudioFromVideo": "भिडियोबाट अडियो निकाल्नुहोस्",
"extract": "निकाल्नुहोस्",
"downloadingNecessaryFiles": "आवश्यक फाइलहरू डाउनलोड हुँदैछन्",
"qualityLow": "कम",
"qualityMedium": "मध्यम",
"appDescription": "ytDownloader ले तपाईंलाई Youtube, Facebook, Instagram, Tiktok, Twitter, लगायत सयौं साइटहरूबाट भिडियो र अडियो डाउनलोड गर्न दिन्छ",
"pasteText": "क्लिपबोर्डबाट भिडियो लिङ्क टाँस्न क्लिक गर्नुहोस्",
"pastePlaylistLinkTooltip": "क्लिपबोर्डबाट प्लेलिस्ट लिङ्क टाँस्न क्लिक गर्नुहोस्",
"link": "लिङ्क:",
"downloadingPlaylist": "प्लेलिस्ट डाउनलोड हुँदैछ:",
"downloadPlaylistButton": "प्लेलिस्ट डाउनलोड गर्नुहोस्",
"playlistDownloaded": "प्लेलिस्ट डाउनलोड भयो",
"cookiesWarning": "यो विकल्पले तपाईंलाई प्रतिबन्धित सामग्री डाउनलोड गर्न दिन्छ। तपाईले असफल पाउनुहुनेछ यदि cookies छैन भने",
"selectBrowserForCookies": "Cookies प्रयोग गर्न ब्राउजर चयन गर्नुहोस्",
"none": "कुनै पनि छैन",
"updateAvailableDownloadPrompt": "नयाँ संस्करण उपलब्ध छ, के तपाईं यसलाई डाउनलोड गर्न चाहनुहुन्छ?",
"updateAvailablePrompt": "नयाँ संस्करण उपलब्ध छ, के तपाईं अद्यावधिक गर्न चाहनुहुन्छ?",
"update": "अद्यावधिक गर्नुहोस्",
"no": "छैन",
"installAndRestartPrompt": "अहिले स्थापना गरेर पुनः सुरु गर्ने?",
"restart": "पुनः सुरु गर्नुहोस्",
"later": "पछि",
"extractAudio": "अडियो निकाल्नुहोस्",
"selectVideoFormat": "भिडियो ढाँचा चयन गर्नुहोस् ",
"selectAudioFormat": "अडियो ढाँचा चयन गर्नुहोस् ",
"maxActiveDownloads": "सक्रिय डाउनलोडहरूको अधिकतम संख्या",
"preferredVideoQuality": "मनपर्ने भिडियो गुणस्तर",
"preferredAudioFormat": "मनपर्ने अडियो ढाँचा",
"best": "उत्कृष्ट",
"fileSaved": "फाइल सुरक्षित भयो।",
"openDownloadFolder": "डाउनलोड फोल्डर खोल्नुहोस्",
"path": "मार्ग:",
"selectConfigFile": "सेटिङ फाइल चयन गर्नुहोस्",
"useConfigFile": "सेटिङ फाइल प्रयोग गर्नुहोस्",
"playlistFilenameFormat": "प्लेलिस्टको लागि फाइलनाम ढाँचा",
"playlistFolderNameFormat": "प्लेलिस्टको लागि फोल्डर नाम ढाँचा",
"resetToDefault": "पूर्वनिर्धारितमा रिसेट गर्नुहोस्",
"playlistRange": "प्लेलिस्ट दायरा",
"thumbnail": "थम्बनेल",
"linkAdded": "लिङ्क थपियो",
"downloadThumbnails": "थम्बनेलहरू डाउनलोड गर्नुहोस्",
"saveVideoLinksToFile": "भिडियो लिङ्कहरू फाइलमा सुरक्षित गर्नुहोस्",
"closeAppToTray": "सिस्टम ट्रेमा एप बन्द गर्नुहोस्",
"useConfigFileCheckbox": "सेटिङ फाइल प्रयोग गर्नुहोस्",
"openApp": "एप खोल्नुहोस्",
"pasteVideoLink": "भिडियो लिङ्क टाँस्नुहोस्",
"quit": "छोड्नुहोस्",
"errorDetails": "त्रुटि विवरणहरू",
"clickToCopy": "प्रतिलिपि गर्न क्लिक गर्नुहोस्",
"copiedText": "प्रतिलिपि गरिएको पाठ",
"qualityNormal": "सामान्य",
"qualityGood": "राम्रो",
"qualityBad": "खराब",
"qualityWorst": "सबैभन्दा खराब",
"selectQuality": "गुणस्तर चयन गर्नुहोस्",
"disableAutoUpdates": "स्वतः अद्यावधिकहरू असक्षम गर्नुहोस्",
"qualityUltraLow": "अति कम",
"closeAppOnFinish": "डाउनलोड समाप्त भएपछि एप बन्द गर्नुहोस्",
"auto": "स्वत:",
"theme": "थीम",
"themeLight": "हल्का",
"themeDark": "गाढा",
"themeFrappe": "फ्रेप्पे",
"themeOneDark": "एक गाढा",
"themeMatrix": "म्याट्रिक्स",
"themeSolarizedDark": "सोलराइज्ड गाढा",
"preferredVideoCodec": "मनपर्ने भिडियो कोडेक",
"showMoreFormatOptions": "थप ढाँचा विकल्पहरू देखाउनुहोस्",
"flatsealPermissionWarning": "यो प्रयोग गर्नको लागि तपाईंले एपलाई होम डाइरेक्टरी पहुँच गर्न अनुमति दिनुपर्छ। तपाईंले Flatseal मा 'filesystem=home' पाठ सहितको अनुमति सक्षम गरेर यो गर्न सक्नुहुन्छ",
"noAudio": "अडियो छैन",
"proxy": "प्रोक्सी",
"clearDownloads": "डाउनलोडहरू खाली गर्नुहोस्",
"compressor": "कम्प्रेसर",
"dragAndDropFiles": "फाइल(हरू) तान्नुहोस् र छोड्नुहोस्",
"chooseFiles": "फाइल(हरू) छान्नुहोस्",
"noFilesSelected": "कुनै फाइल चयन गरिएको छैन",
"videoFormat": "भिडियो ढाँचा",
"videoEncoder": "भिडियो इन्कोडर",
"compressionSpeed": "कम्प्रेसन गति",
"videoQuality": "भिडियो गुणस्तर",
"audioFormat": "अडियो ढाँचा",
"outputSuffix": "आउटपुट प्रत्यय",
"outputInSameFolder": "एउटै फोल्डरमा आउटपुट",
"selectCustomFolder": "अनुकूल फोल्डर चयन गर्नुहोस्",
"startCompression": "कम्प्रेसन सुरु गर्नुहोस्",
"cancel": "रद्द गर्नुहोस्",
"errorClickForDetails": "त्रुटि! विवरणहरूको लागि क्लिक गर्नुहोस्",
"homebrewYtDlpWarning": "तपाईंले पहिले Homebrew बाट yt-dlp डाउनलोड गर्नुपर्छ",
"openHomebrew": "Homebrew खोल्नुहोस्",
"downloadHistory": "डाउनलोड ईतिहास",
"close": "बन्द गर्नुहोस्",
"searchByTitleOrUrl": "शीर्षक वा URL द्वारा खोज्नुहोस्...",
"allFormats": "सबै ढाँचाहरू",
"exportAsJson": "JSON को रूपमा निर्यात गर्नुहोस्",
"exportAsCsv": "CSV को रूपमा निर्यात गर्नुहोस्",
"clearAllHistory": "सबै ईतिहास खाली गर्नुहोस्",
"noDownloadsYet": "अहिलेसम्म कुनै डाउनलोड छैन",
"downloadHistoryPlaceholder": "तपाईंको डाउनलोड ईतिहास यहाँ देखा पर्नेछ",
"format": "ढाँचा",
"size": "आकार",
"date": "मिति",
"duration": "अवधि",
"copyUrl": "URL प्रतिलिपि गर्नुहोस्",
"open": "खोल्नुहोस्",
"delete": "मेटाउनुहोस्",
"totalDownloads": "कुल डाउनलोडहरू",
"totalSize": "कुल आकार",
"mostCommonFormat": "सबैभन्दा सामान्य ढाँचा",
"urlCopiedToClipboard": "URL क्लिपबोर्डमा प्रतिलिपि भयो!",
"confirmDeleteHistoryItem": "के तपाईं निश्चित हुनुहुन्छ कि तपाईं यो वस्तु ईतिहासबाट मेटाउन चाहनुहुन्छ?",
"confirmClearAllHistory": "के तपाईं निश्चित हुनुहुन्छ कि तपाईं सबै डाउनलोड ईतिहास खाली गर्न चाहनुहुन्छ? यो पूर्ववत गर्न सकिँदैन!",
"fileDoesNotExist": "File does not exist anymore",
"updatingYtdlp": "Updating yt-dlp",
"updatedYtdlp": "Updated yt-dlp",
"ytDlpUpdateRequired": "If yt-dlp is updating, wait for the update to finish. If you have installed yt-dlp by yourself, please update it.",
"failedToDeleteHistoryItem": "Failed to delete history item",
"customArgsTxt": "Set custom yt-dlp options.",
"learnMore": "Learn more",
"updateError": "An error occurred during the update process",
"unableToAccessDir": "The program cannot access that folder",
"downloadingUpdate": "Downloading update"
}
================================================
FILE: translations/pl-PL.json
================================================
{
"preferences": "Ustawienia",
"about": "O programie",
"downloadLocation": "Lokalizacja pobierania",
"currentDownloadLocation": "Obecna lokalizacja pobierania - ",
"enableTransparentDarkMode": "Włącz przezroczysty tryb okna (tylko Linux, wymaga ponownego uruchomienia)",
"downloadingNecessaryFilesWait": "Proszę czekać, pobierane są wymagane pliki",
"video": "Wideo",
"audio": "Audio",
"title": "Tytuł ",
"selectFormat": "Wybierz format ",
"download": "Pobierz",
"selectDownloadLocation": "Wybierz lokalizację pobierania",
"moreOptions": "Więcej opcji",
"start": "Początek",
"selectLanguageRelaunch": "Wybierz język (Wymaga ponownego uruchomienia)",
"downloadTimeRange": "Pobierz określony czasowo wycinek",
"end": "Koniec",
"timeRangeStartEmptyHint": "Jeśli pole jest puste, zacznie od samego początku",
"timeRangeEndEmptyHint": "Jeśli pole jest puste, pobierze do samego końca",
"homepage": "Strona główna",
"aboutAppDescription": "Jest to darmowa aplikacja z otwartym kodem źródłowym, zbudowana przy użyciu Node.js i Electron. yt-dlp jest używany do pobierania",
"sourceCodeAvailable": "Kod źródłowy jest dostępny ",
"here": "tutaj",
"processing": "Przetwarzanie",
"errorNetworkOrUrl": "Wystąpił błąd. Sprawdź swoje połączenie internetowe i skopiowany adres URL",
"errorFailedFileDownload": "Pobieranie wymaganych plików nie powiodło się. Sprawdź swoje połączenie internetowe i spróbuj ponownie",
"tryAgain": "Spróbuj ponownie",
"unknownSize": "Nieznany rozmiar",
"megabyte": "MB",
"unknownQuality": "Nieznana jakość",
"downloading": "Pobieranie...",
"errorHoverForDetails": "Wystąpił błąd. Najedź kursorem by zobaczyć więcej informacji",
"fileSavedSuccessfully": "Plik zapisany pomyślnie",
"fileSavedClickToOpen": "Plik zapisany. Naciśnij żeby otworzyć",
"preparing": "Przygotowywanie...",
"progress": "Postęp",
"speed": "Prędkość",
"quality": "Jakość",
"restartApp": "Zrestartuj aplikację",
"subtitles": "Napisy",
"downloadSubtitlesAvailable": "Pobierz napisy jeśli są dostępne",
"downloadSubtitlesAuto": "Pobierz automatycznie wygenerowane napisy",
"extractAudioFromVideo": "Wydobądź Audio z Wideo",
"extract": "Wydobądź",
"downloadingNecessaryFiles": "Pobieranie wymaganych plików",
"qualityLow": "niska",
"qualityMedium": "średnia",
"appDescription": "ytDownloader pomaga pobierać wideo i audio z setek różnych stron takich jak YouTube, Instagram, TikTok, Twitter i tym podobnych",
"pasteText": "Kliknij, aby wkleić link wideo ze schowka",
"pastePlaylistLinkTooltip": "Kliknij, aby wkleić link playlisty ze schowka",
"link": "Link:",
"downloadingPlaylist": "Pobieranie playlisty:",
"downloadPlaylistButton": "Pobierz playlistę",
"playlistDownloaded": "Playlista pobrana",
"cookiesWarning": "Ta opcja pozwala pobierać treści z ograniczonym dostępem. Otrzymasz błędy jeśli nie będzie tu plików cookies",
"selectBrowserForCookies": "Wybierz przeglądarkę do używania cookies",
"none": "Żadna",
"updateAvailableDownloadPrompt": "Nowa wersja jest dostępna. Czy chcesz ją pobrać?",
"updateAvailablePrompt": "Nowa wersja jest dostępna. Czy chcesz zaktualizować program?",
"update": "Aktualizuj",
"no": "Nie",
"installAndRestartPrompt": "Zainstalować i zrestartować teraz?",
"restart": "Zrestartuj",
"later": "Później",
"extractAudio": "Wydobądź Audio",
"selectVideoFormat": "Wybierz format Wideo ",
"selectAudioFormat": "Wybierz format Audio ",
"maxActiveDownloads": "Maksymalna liczba aktywnych pobrań",
"preferredVideoQuality": "Preferowana jakość wideo",
"preferredAudioFormat": "Preferowany format audio",
"best": "Najlepsza",
"fileSaved": "Plik zapisany.",
"openDownloadFolder": "Otwórz folder pobierania",
"path": "Ścieżka:",
"selectConfigFile": "Wybierz plik konfiguracyjny",
"useConfigFile": "Użyj pliku konfiguracji",
"playlistFilenameFormat": "Format nazwy pliku dla playlist",
"playlistFolderNameFormat": "Format nazwy folderu dla list odtwarzania",
"resetToDefault": "Przywróć domyślne",
"playlistRange": "Zakres listy odtwarzania",
"thumbnail": "Miniaturka",
"linkAdded": "Link dodany",
"downloadThumbnails": "Pobierz miniaturki",
"saveVideoLinksToFile": "Zapisz odnośniki wideo do pliku",
"closeAppToTray": "Zamknij aplikację do zasobnika systemowego",
"useConfigFileCheckbox": "Użyj pliku konfiguracyjnego",
"openApp": "Otwórz aplikację",
"pasteVideoLink": "Wklej link wideo",
"quit": "Wyjdź",
"errorDetails": "Szczegóły błędu",
"clickToCopy": "Kliknij aby skopiować",
"copiedText": "Skopiowany tekst",
"qualityNormal": "Normalny",
"qualityGood": "Dobra",
"qualityBad": "Zła",
"qualityWorst": "Najgorsza",
"selectQuality": "Wybierz jakość",
"disableAutoUpdates": "Wyłącz automatyczne aktualizacje",
"qualityUltraLow": "bardzo niska",
"closeAppOnFinish": "Zamknij aplikację po zakończeniu pobierania",
"auto": "Automatycznie",
"theme": "Motyw",
"themeLight": "Jasny",
"themeDark": "Ciemny",
"themeFrappe": "Frappé",
"themeOneDark": "One Dark",
"themeMatrix": "Matryca",
"themeSolarizedDark": "Solarized ciemny",
"preferredVideoCodec": "Preferowany kodek wideo",
"showMoreFormatOptions": "Pokaż więcej opcji formatu",
"flatsealPermissionWarning": "Aby z tego skorzystać, Musisz zezwolić aplikacji na dostęp do katalogu domowego. Możesz to zrobić za pomocą Flatseal, włączając uprawnienie z tekstem \"filesystem=home\"",
"noAudio": "Bez dźwięku",
"proxy": "Proxy",
"clearDownloads": "Wyczyść pobrania",
"compressor": "Kompresor",
"dragAndDropFiles": "Przeciągnij i upuść plik(i)",
"chooseFiles": "Wybierz plik(i)",
"noFilesSelected": "Nie wybrano plików",
"videoFormat": "Format wideo",
"videoEncoder": "Koder wideo",
"compressionSpeed": "Prędkość kompresji",
"videoQuality": "Jakość wideo",
"audioFormat": "Format audio",
"outputSuffix": "Sufiks wyjściowy",
"outputInSameFolder": "Wyjście w tym samym folderze",
"selectCustomFolder": "Wybierz niestandardowy folder",
"startCompression": "Rozpocznij kompresję",
"cancel": "Anuluj",
"errorClickForDetails": "Błąd! Kliknij, aby zobaczyć szczegóły",
"homebrewYtDlpWarning": "Najpierw musisz pobrać yt-dlp z Homebrew",
"openHomebrew": "Otwórz Homebrew",
"downloadHistory": "Historia pobierania",
"close": "Zamknij",
"searchByTitleOrUrl": "Szukaj według tytułu lub URL...",
"allFormats": "Wszystkie formaty",
"exportAsJson": "Eksportuj jako JSON",
"exportAsCsv": "Eksportuj jako CSV",
"clearAllHistory": "Wyczyść całą historię",
"noDownloadsYet": "Brak pobrań",
"downloadHistoryPlaceholder": "Twoja historia pobierania pojawi się tutaj",
"format": "Format",
"size": "Rozmiar",
"date": "Data",
"duration": "Czas trwania",
"copyUrl": "Kopiuj URL",
"open": "Otwórz",
"delete": "Usuń",
"totalDownloads": "Wszystkie pobrania",
"totalSize": "Całkowity rozmiar",
"mostCommonFormat": "Najczęstszy format",
"urlCopiedToClipboard": "URL skopiowany do schowka!",
"confirmDeleteHistoryItem": "Czy na pewno chcesz usunąć ten element z historii?",
"confirmClearAllHistory": "Czy na pewno chcesz wyczyścić całą historię pobierania? Tej operacji nie można cofnąć!",
"fileDoesNotExist": "Plik już nie istnieje",
"updatingYtdlp": "Aktualizacja yt-dlp",
"updatedYtdlp": "Zaktualizowano yt-dlp",
"ytDlpUpdateRequired": "Jeśli yt-dlp aktualizuje, poczekaj na zakończenie aktualizacji. Jeśli zainstalowałeś yt-dlp samodzielnie, zaktualizuj go.",
"failedToDeleteHistoryItem": "Nie udało się usunąć elementu historii",
"customArgsTxt": "Ustaw niestandardowe opcje yt-dlp.",
"learnMore": "Dowiedz się więcej",
"updateError": "Wystąpił błąd podczas procesu aktualizacji",
"unableToAccessDir": "Program nie może uzyskać dostępu do tego folderu",
"downloadingUpdate": "Pobieranie aktualizacji"
}
================================================
FILE: translations/pt-BR.json
================================================
{
"preferences": "Preferências",
"about": "Sobre",
"downloadLocation": "Local para salvar",
"currentDownloadLocation": "Local para salvar atual - ",
"enableTransparentDarkMode": "Habilitar modo escuro transparente (somente Linux, requer reinicialização)",
"downloadingNecessaryFilesWait": "Por favor, aguarde, transferindo arquivos necessários",
"video": "Vídeo",
"audio": "Áudio",
"title": "Título ",
"selectFormat": "Selecionar um formato ",
"download": "Baixar",
"selectDownloadLocation": "Selecionar local para salvar",
"moreOptions": "Mais opções",
"start": "Início",
"selectLanguageRelaunch": "Selecionar Idioma (Requer reinicialização)",
"downloadTimeRange": "Baixar intervalo de tempo específico",
"end": "Fim",
"timeRangeStartEmptyHint": "Se não informado, começará do início",
"timeRangeEndEmptyHint": "Se não informado, será baixado até o fim",
"homepage": "Página inicial",
"aboutAppDescription": "É um aplicativo gratuito e de código aberto construído usando Node.js e Electron. yt-dlp é usado para baixar os vídeos",
"sourceCodeAvailable": "Código-fonte disponível ",
"here": "aqui",
"processing": "Processando",
"errorNetworkOrUrl": "Ocorreu um erro. Verifique sua conexão e use a URL correta",
"errorFailedFileDownload": "Erro ao baixar os arquivos necessários. Verifique sua conexão e tente novamente",
"tryAgain": "Tentar novamente",
"unknownSize": "Tamanho desconhecido",
"megabyte": "MB",
"unknownQuality": "Qualidade desconhecida",
"downloading": "Baixando...",
"errorHoverForDetails": "Ocorreu um erro. Passe o mouse para ver detalhes",
"fileSavedSuccessfully": "Arquivo salvo com sucesso",
"fileSavedClickToOpen": "Arquivo salvo. Clique para abrir",
"preparing": "Preparando...",
"progress": "Progresso",
"speed": "Velocidade",
"quality": "Qualidade",
"restartApp": "Reiniciar aplicativo",
"subtitles": "Legendas",
"downloadSubtitlesAvailable": "Baixar legendas se disponíveis",
"downloadSubtitlesAuto": "Baixar legendas geradas automaticamente",
"extractAudioFromVideo": "Extrair áudio do vídeo",
"extract": "Extrair",
"downloadingNecessaryFiles": "Baixando arquivos necessários",
"qualityLow": "baixa",
"qualityMedium": "média",
"appDescription": "ytDownloader permite baixar vídeos e áudios de centenas de sites como Youtube, Facebook, Instagram, Tiktok, Twitter e mais",
"pasteText": "Clique para colar o link do vídeo da área de transferência",
"pastePlaylistLinkTooltip": "Clique para colar o link da playlist da área de transferência",
"link": "Link:",
"downloadingPlaylist": "Baixando playlist:",
"downloadPlaylistButton": "Baixar playlist",
"playlistDownloaded": "Playlist baixada",
"cookiesWarning": "Esta opção permite baixar conteúdo restrito. Ocorrerão erros se os cookies não estiverem disponíveis",
"selectBrowserForCookies": "Selecionar navegador de onde usar os cookies",
"none": "Nenhum",
"updateAvailableDownloadPrompt": "Há uma nova versão disponível. Gostaria de baixá-la?",
"updateAvailablePrompt": "Há uma nova versão disponível. Gostaria de atualizar?",
"update": "Atualizar",
"no": "Não",
"installAndRestartPrompt": "Instalar e reiniciar agora?",
"restart": "Reiniciar",
"later": "Depois",
"extractAudio": "Extrair Áudio",
"selectVideoFormat": "Selecionar Formato de Vídeo ",
"selectAudioFormat": "Selecionar formato de áudio ",
"maxActiveDownloads": "Número máximo de downloads ativos",
"preferredVideoQuality": "Qualidade preferencial de vídeo",
"preferredAudioFormat": "Formato de áudio predileto",
"best": "Melhor",
"fileSaved": "Arquivo salvo",
"openDownloadFolder": "Abrir pasta de download",
"path": "Local:",
"selectConfigFile": "Selecione arquivo de configuração",
"useConfigFile": "Usar arquivo de configuração",
"playlistFilenameFormat": "Formato do arquivo para playlists",
"playlistFolderNameFormat": "Formato de pasta para playlists",
"resetToDefault": "Restaurar para Padrão",
"playlistRange": "Intervalo da playlist",
"thumbnail": "Miniatura",
"linkAdded": "Link adicionado",
"downloadThumbnails": "Baixar miniaturas",
"saveVideoLinksToFile": "Salvar links para um arquivo",
"closeAppToTray": "Fechar aplicativo na bandeja do sistema",
"useConfigFileCheckbox": "Utilizar arquivo de configuração",
"openApp": "Abrir app",
"pasteVideoLink": "Colar link do vídeo",
"quit": "Sair",
"errorDetails": "Detalhes do erro",
"clickToCopy": "Clique para copiar",
"copiedText": "Texto copiado",
"qualityNormal": "Standard",
"qualityGood": "Boa",
"qualityBad": "Ruim",
"qualityWorst": "Pior",
"selectQuality": "Escolha a qualidade",
"disableAutoUpdates": "Desativar atualizações automáticas",
"qualityUltraLow": "ultra Baixo",
"closeAppOnFinish": "Fechar aplicativo quando o download terminar",
"auto": "Automático",
"theme": "Tema",
"themeLight": "Luz",
"themeDark": "Escuro",
"themeFrappe": "Frappé",
"themeOneDark": "Um Escuro",
"themeMatrix": "Matriz",
"themeSolarizedDark": "Escuro Solarizado",
"preferredVideoCodec": "Codec de vídeo preferido",
"showMoreFormatOptions": "Mostrar mais opções de formato",
"flatsealPermissionWarning": "Você precisa dar permissão ao aplicativo para acessar o diretório inicial para usar isso. Você pode fazer isso com Flatseal ativando a permissão com o texto 'filesystem=home'",
"noAudio": "Sem áudio",
"proxy": "Proxy",
"clearDownloads": "Limpar transferências",
"compressor": "Compressor",
"dragAndDropFiles": "Arraste e solte o(s) arquivo(s)",
"chooseFiles": "Escolher arquivo(s)",
"noFilesSelected": "Nenhum arquivo selecionado",
"videoFormat": "Formato de vídeo",
"videoEncoder": "Codificador de vídeo",
"compressionSpeed": "Velocidade de compressão",
"videoQuality": "Qualidade de vídeo",
"audioFormat": "Formato de áudio",
"outputSuffix": "Sufixo de saída",
"outputInSameFolder": "Salvar na mesma pasta",
"selectCustomFolder": "Selecionar pasta personalizada",
"startCompression": "Iniciar compressão",
"cancel": "Cancelar",
"errorClickForDetails": "Erro! Clique para detalhes",
"homebrewYtDlpWarning": "Você precisa baixar o yt-dlp pelo Homebrew primeiro",
"openHomebrew": "Abrir Homebrew",
"downloadHistory": "Histórico de Downloads",
"close": "Fechar",
"searchByTitleOrUrl": "Buscar por título ou URL...",
"allFormats": "Todos os formatos",
"exportAsJson": "Exportar como JSON",
"exportAsCsv": "Exportar como CSV",
"clearAllHistory": "Limpar todo o histórico",
"noDownloadsYet": "Nenhum download ainda",
"downloadHistoryPlaceholder": "Seu histórico de downloads aparecerá aqui",
"format": "Formato",
"size": "Tamanho",
"date": "Data",
"duration": "Duração",
"copyUrl": "Copiar URL",
"open": "Abrir",
"delete": "Excluir",
"totalDownloads": "Total de Downloads",
"totalSize": "Tamanho Total",
"mostCommonFormat": "Formato Mais Comum",
"urlCopiedToClipboard": "URL copiada para a área de transferência!",
"confirmDeleteHistoryItem": "Tem certeza de que deseja excluir este item do histórico?",
"confirmClearAllHistory": "Tem certeza de que deseja limpar todo o histórico de downloads? Esta ação não pode ser desfeita!",
"fileDoesNotExist": "Arquivo não existe mais",
"updatingYtdlp": "Atualizando yt-dlp",
"updatedYtdlp": "yt-dlp atualizado",
"ytDlpUpdateRequired": "Se o yt-dlp estiver atualizando, espere que a atualização termine. Se você tiver instalado o yt-dlp, por favor, atualize-o.",
"failedToDeleteHistoryItem": "Falha ao excluir item do histórico",
"customArgsTxt": "Defina opções yt-dlp personalizadas.",
"learnMore": "Saiba mais",
"updateError": "Ocorreu um erro durante o processo de atualização",
"unableToAccessDir": "O programa não pode acessar essa pasta",
"downloadingUpdate": "Baixando atualização"
}
================================================
FILE: translations/ru-RU.json
================================================
{
"preferences": "Настройки",
"about": "О программе",
"downloadLocation": "Папка загрузки",
"currentDownloadLocation": "Текущая папка загрузки: ",
"enableTransparentDarkMode": "Включить прозрачный тёмный режим (только Linux, требуется перезапуск)",
"downloadingNecessaryFilesWait": "Пожалуйста, подождите, загружаются необходимые файлы",
"video": "Видео",
"audio": "Аудио",
"title": "Название",
"selectFormat": "Выберите формат",
"download": "Скачать",
"selectDownloadLocation": "Выберите папку для загрузки",
"moreOptions": "Дополнительные опции",
"start": "Начало",
"selectLanguageRelaunch": "Выберите язык (требуется перезапуск)",
"downloadTimeRange": "Скачать определённый фрагмент",
"end": "Конец",
"timeRangeStartEmptyHint": "Если оставить пустым, начнётся с начала",
"timeRangeEndEmptyHint": "Если оставить пустым, скачается до конца",
"homepage": "Главная страница",
"aboutAppDescription": "Это бесплатное приложение с открытым исходным кодом, созданное на основе Node.js и Electron. Для загрузки используется yt-dlp",
"sourceCodeAvailable": "Исходный код доступен",
"here": "здесь",
"processing": "Обработка",
"errorNetworkOrUrl": "Произошла ошибка. Проверьте подключение к сети и убедитесь, что URL указан верно",
"errorFailedFileDownload": "Не удалось загрузить необходимые файлы. Проверьте подключение к сети и повторите попытку",
"tryAgain": "Повторить попытку",
"unknownSize": "Размер неизвестен",
"megabyte": "МБ",
"unknownQuality": "Неизвестное качество",
"downloading": "Скачивание...",
"errorHoverForDetails": "Произошла ошибка. Для просмотра подробностей наведите курсор",
"fileSavedSuccessfully": "Файл успешно сохранён",
"fileSavedClickToOpen": "Файл сохранён. Нажмите, чтобы открыть",
"preparing": "Подготовка...",
"progress": "Прогресс",
"speed": "Скорость",
"quality": "Качество",
"restartApp": "Перезапустить приложение",
"subtitles": "Субтитры",
"downloadSubtitlesAvailable": "Скачать субтитры, если доступны",
"downloadSubtitlesAuto": "Скачать автоматически созданные субтитры",
"extractAudioFromVideo": "Извлечь аудио из видео",
"extract": "Извлечь",
"downloadingNecessaryFiles": "Загрузка необходимых файлов",
"qualityLow": "Низкое",
"qualityMedium": "Среднее",
"appDescription": "ytDownloader позволяет скачивать видео и аудио с сотен сайтов, таких как YouTube, Facebook, Instagram, TikTok, Twitter и других",
"pasteText": "Нажмите, чтобы вставить ссылку на видео из буфера обмена",
"pastePlaylistLinkTooltip": "Нажмите, чтобы вставить ссылку на плейлист из буфера обмена",
"link": "Ссылка:",
"downloadingPlaylist": "Скачивание плейлиста:",
"downloadPlaylistButton": "Скачать плейлист",
"playlistDownloaded": "Плейлист загружен",
"cookiesWarning": "Эта опция позволяет загружать ограниченный контент. Если файлы cookie отсутствуют, могут возникать ошибки",
"selectBrowserForCookies": "Выберите браузер для использования файлов cookie",
"none": "Нет",
"updateAvailableDownloadPrompt": "Доступна новая версия. Скачать сейчас?",
"updateAvailablePrompt": "Доступна новая версия. Хотите обновиться?",
"update": "Обновить",
"no": "Нет",
"installAndRestartPrompt": "Установить и перезапустить сейчас?",
"restart": "Перезапустить",
"later": "Позже",
"extractAudio": "Извлечь аудио",
"selectVideoFormat": "Выберите формат видео",
"selectAudioFormat": "Выберите формат аудио",
"maxActiveDownloads": "Максимальное количество одновременных загрузок",
"preferredVideoQuality": "Предпочитаемое качество видео",
"preferredAudioFormat": "Предпочитаемый аудиоформат",
"best": "Лучшее",
"fileSaved": "Файл сохранён",
"openDownloadFolder": "Открыть папку загрузок",
"path": "Путь:",
"selectConfigFile": "Выбрать файл конфигурации",
"useConfigFile": "Использовать файл конфигурации",
"playlistFilenameFormat": "Формат имён файлов для плейлистов",
"playlistFolderNameFormat": "Формат имён папок для плейлистов",
"resetToDefault": "Сбросить настройки по умолчанию",
"playlistRange": "Диапазон плейлиста",
"thumbnail": "Миниатюра",
"linkAdded": "Ссылка добавлена",
"downloadThumbnails": "Скачать миниатюры",
"saveVideoLinksToFile": "Сохранить ссылки на видео в файл",
"closeAppToTray": "Свернуть приложение в системный трей",
"useConfigFileCheckbox": "Использовать файл конфигурации",
"openApp": "Открыть приложение",
"pasteVideoLink": "Вставить ссылку на видео",
"quit": "Закрыть",
"errorDetails": "Подробности ошибки",
"clickToCopy": "Нажмите, чтобы скопировать",
"copiedText": "Текст скопирован",
"qualityNormal": "Обычное",
"qualityGood": "Хорошее",
"qualityBad": "Плохое",
"qualityWorst": "Худшее",
"selectQuality": "Выберите качество",
"disableAutoUpdates": "Отключить автоматические обновления",
"qualityUltraLow": "Сверхнизкое",
"closeAppOnFinish": "Закрыть приложение после завершения загрузки",
"auto": "Авто",
"theme": "Тема",
"themeLight": "Светлая",
"themeDark": "Тёмная",
"themeFrappe": "Фраппе",
"themeOneDark": "One Dark",
"themeMatrix": "Матрица",
"themeSolarizedDark": "Solarized Dark",
"preferredVideoCodec": "Предпочтительный видеокодек",
"showMoreFormatOptions": "Показать больше опций формата",
"flatsealPermissionWarning": "Чтобы использовать эту функцию, нужно предоставить приложению доступ к домашнему каталогу. Это можно сделать в Flatseal, включив разрешение с параметром 'filesystem=home'",
"noAudio": "Без аудио",
"proxy": "Прокси",
"clearDownloads": "Очистить загрузки",
"compressor": "Компрессор",
"dragAndDropFiles": "Перетащите файл(ы)",
"chooseFiles": "Выбрать файл(ы)",
"noFilesSelected": "Файлы не выбраны",
"videoFormat": "Формат видео",
"videoEncoder": "Видеокодек",
"compressionSpeed": "Скорость сжатия",
"videoQuality": "Качество видео",
"audioFormat": "Формат аудиофайла",
"outputSuffix": "Суффикс выходного файла",
"outputInSameFolder": "Сохранять в ту же папку",
"selectCustomFolder": "Выбрать папку",
"startCompression": "Начать сжатие",
"cancel": "Отмена",
"errorClickForDetails": "Ошибка! Нажмите для подробностей",
"homebrewYtDlpWarning": "Сначала необходимо установить yt-dlp через Homebrew",
"openHomebrew": "Открыть Homebrew",
"downloadHistory": "История загрузок",
"close": "Закрыть",
"searchByTitleOrUrl": "Поиск по названию или URL...",
"allFormats": "Все форматы",
"exportAsJson": "Экспортировать в JSON",
"exportAsCsv": "Экспортировать в CSV",
"clearAllHistory": "Очистить всю историю",
"noDownloadsYet": "Загрузок пока нет",
"downloadHistoryPlaceholder": "Здесь появится история загрузок",
"format": "Формат",
"size": "Размер",
"date": "Дата",
"duration": "Длительность",
"copyUrl": "Копировать ссылку",
"open": "Открыть",
"delete": "Удалить",
"totalDownloads": "Всего загрузок",
"totalSize": "Общий размер",
"mostCommonFormat": "Наиболее частый формат",
"urlCopiedToClipboard": "URL скопирован в буфер обмена!",
"confirmDeleteHistoryItem": "Вы уверены, что хотите удалить этот элемент из истории?",
"confirmClearAllHistory": "Вы уверены, что хотите удалить всю историю загрузок? Это действие необратимо!",
"fileDoesNotExist": "Файл больше не существует",
"updatingYtdlp": "Обновление yt-dlp",
"updatedYtdlp": "yt-dlp обновлён",
"ytDlpUpdateRequired": "Если yt-dlp обновляется, дождитесь завершения. Если вы установили yt-dlp самостоятельно, обновите его.",
"failedToDeleteHistoryItem": "Не удалось удалить элемент истории",
"customArgsTxt": "Задать параметры yt-dlp.",
"learnMore": "Подробнее",
"updateError": "Во время обновления произошла ошибка",
"unableToAccessDir": "Нет доступа к папке",
"downloadingUpdate": "Загрузка обновления"
}
================================================
FILE: translations/tr-TR.json
================================================
{
"preferences": "Ayarlar",
"about": "Hakkında",
"downloadLocation": "İndirme dizini",
"currentDownloadLocation": "Mevcut indirme dizini - ",
"enableTransparentDarkMode": "Koyu tema için saydamlığı aktif edin (sadece Linux için, yeniden başlatma gerekir)",
"downloadingNecessaryFilesWait": "Lütfen bekleyin, dosyalar indiriliyor",
"video": "Video",
"audio": "Ses",
"title": "Başlık ",
"selectFormat": "Format seçin ",
"download": "İndir",
"selectDownloadLocation": "İndirme dizinini seç",
"moreOptions": "Daha fazla ayar",
"start": "Başlangıç",
"selectLanguageRelaunch": "Dil seçin (Yeniden başlatma gerekir)",
"downloadTimeRange": "Belirli bir zaman aralığını indir",
"end": "Bitiş",
"timeRangeStartEmptyHint": "Eğer boş bırakılırsa, başlangıçtan başlayacaktır",
"timeRangeEndEmptyHint": "Eğer boş bırakılırsa, sonuna kadar inecektir",
"homepage": "Ana sayfa",
"aboutAppDescription": "Node.js ve Electron ile yapılmış ücretsiz ve açık kaynak bir uygulamadır. yt-dlp indirme işlemi için kullanılır",
"sourceCodeAvailable": "Kaynak kodu mevcuttur ",
"here": "burada",
"processing": "İşleniyor",
"errorNetworkOrUrl": "Hata oluştu. İnternetinizi kontrol edin ve doğru bir bağlantı kullanın",
"errorFailedFileDownload": "Dosyalar indirilemedi. Lütfen internetinizi kontrol edin ve tekrar deneyin",
"tryAgain": "Tekrar deneyin",
"unknownSize": "Bilinmeyen boyut",
"megabyte": "MB",
"unknownQuality": "Bilinmeyen kalite",
"downloading": "İndiriliyor...",
"errorHoverForDetails": "Hata oluştu. Detayları görmek için üzerine gelin",
"fileSavedSuccessfully": "Dosya başarıyla kaydedildi",
"fileSavedClickToOpen": "Dosya kaydedildi. Açmak için tıklayın",
"preparing": "Hazırlanıyor...",
"progress": "Süreç",
"speed": "Hız",
"quality": "Kalite",
"restartApp": "Uygulamayı yeniden başlat",
"subtitles": "Altyazılar",
"downloadSubtitlesAvailable": "Altyazılar mevcut ise indir",
"downloadSubtitlesAuto": "Otomatik oluşturulan altyazıyı indir",
"extractAudioFromVideo": "Videodan sesi çıkart",
"extract": "Çıkart",
"downloadingNecessaryFiles": "Dosyalar indiriliyor",
"qualityLow": "düşük",
"qualityMedium": "orta",
"appDescription": "ytDownloader ile Youtube, Facebook, Instagram, Tiktok ve Twitter gibi yüzlerce siteden videolar ve sesler indirebilirsiniz",
"pasteText": "Klip tahtasından video bağlantısını yapıştırmak için tıklayın",
"pastePlaylistLinkTooltip": "Klip tahtasından oynatma listesi bağlantısını yapıştırmak için tıklayın",
"link": "Link:",
"downloadingPlaylist": "İndirilen oynatma listesi:",
"downloadPlaylistButton": "Oynatma listesini indir",
"playlistDownloaded": "Oynatma listesi indirildi",
"cookiesWarning": "Bu seçenek kısıtlı içeriği indirmenize olanak sağlar. Çerezler bulunmuyorsa hata alırsınız",
"selectBrowserForCookies": "Çerezlerin kullanılacağı tarayıcıyı seçin",
"none": "Hiçbiri",
"updateAvailableDownloadPrompt": "Yeni bir versiyon mevcut, indirmek ister misiniz?",
"updateAvailablePrompt": "Yeni bir versiyon mevcut, güncellemek ister misiniz?",
"update": "Güncelleme",
"no": "Hayır",
"installAndRestartPrompt": "Yüklendikten sonra yeniden başlatılsın mı?",
"restart": "Yeniden başlat",
"later": "Daha sonra",
"extractAudio": "Sesi çıkart",
"selectVideoFormat": "Video Formatını Seçin ",
"selectAudioFormat": "Ses Formatını Seçin ",
"maxActiveDownloads": "Maksimum aktif indirme sayısı",
"preferredVideoQuality": "Tercih edilen video kalitesi",
"preferredAudioFormat": "Tercih edilen ses formatı",
"best": "En iyi",
"fileSaved": "Dosya kaydedildi",
"openDownloadFolder": "İndirme klasörünü açın",
"path": "Dizin:",
"selectConfigFile": "Konfigürasyon dosyasını seç",
"useConfigFile": "Konfigürasyon dosyasını kullan",
"playlistFilenameFormat": "Oynatma listesi için dosya adı",
"playlistFolderNameFormat": "Oynatma listesi için klasör ismi",
"resetToDefault": "Varsayılana sıfırla",
"playlistRange": "Oynatma listesi aralığı",
"thumbnail": "Küçük resim",
"linkAdded": "Bağlantı eklendi",
"downloadThumbnails": "Küçük Resimleri İndir",
"saveVideoLinksToFile": "Video bağlantılarını bir dosyaya kaydet",
"closeAppToTray": "Sistem tepsisine kapat",
"useConfigFileCheckbox": "Konfigürasyon dosyası kullan",
"openApp": "Uygulamayı aç",
"pasteVideoLink": "Video bağlantısını yapıştırın",
"quit": "Çıkış",
"errorDetails": "Hata Ayrıntıları",
"clickToCopy": "Kopyalamak için tıkla",
"copiedText": "Kopyalanan metin",
"qualityNormal": "Normal",
"qualityGood": "İyi",
"qualityBad": "Kötü",
"qualityWorst": "En kötü",
"selectQuality": "Kalite Seç",
"disableAutoUpdates": "Otomatik güncellemeleri devre dışı bırak",
"qualityUltraLow": "Çok düşük",
"closeAppOnFinish": "İndirme bittiğinde uygulamayı kapat",
"auto": "Otomatik",
"theme": "Tema",
"themeLight": "Açık",
"themeDark": "Karanlık",
"themeFrappe": "Frappé",
"themeOneDark": "One Dark",
"themeMatrix": "Matris",
"themeSolarizedDark": "Solarized koyu",
"preferredVideoCodec": "Tercih edilen video kodeği",
"showMoreFormatOptions": "Daha fazla format ayarı göster",
"flatsealPermissionWarning": "Bunu kullanmak için uygulamaya ev dizininize erişme izni vermeniz gerekiyor. Flatseal kullanarak bunu yapabilirsiniz: 'filesystem=home' parametresini kullanın",
"noAudio": "Ses Yok",
"proxy": "Proxy",
"clearDownloads": "İndirilenleri temizle",
"compressor": "Sıkıştırıcı",
"dragAndDropFiles": "Dosya(ları) sürükleyip bırak",
"chooseFiles": "Dosya(ları) Seç",
"noFilesSelected": "Hiçbir dosya seçilmedi",
"videoFormat": "Video formatı",
"videoEncoder": "Video Kodlayıcı",
"compressionSpeed": "Sıkıştırma Hızı",
"videoQuality": "Video Kalitesi",
"audioFormat": "Ses Formatı",
"outputSuffix": "Çıktı soneki",
"outputInSameFolder": "Aynı klasörde çıktı",
"selectCustomFolder": "Özel klasör seç",
"startCompression": "Sıkıştırmayı Başlat",
"cancel": "İptal",
"errorClickForDetails": "Hata! Ayrıntılar için tıklayın",
"homebrewYtDlpWarning": "Öncelikle Homebrew üzerinden yt-dlp uygulamasını indirmeniz gerekiyor",
"openHomebrew": "Homebrew'u Aç",
"downloadHistory": "İndirme Geçmişi",
"close": "Kapat",
"searchByTitleOrUrl": "Başlığa veya URL'ye göre ara...",
"allFormats": "Tüm Formatlar",
"exportAsJson": "JSON olarak dışa aktar",
"exportAsCsv": "CSV olarak dışa aktar",
"clearAllHistory": "Tüm Geçmişi Temizle",
"noDownloadsYet": "Henüz İndirme Yok",
"downloadHistoryPlaceholder": "İndirme geçmişiniz burada görünecek",
"format": "Format",
"size": "Boyut",
"date": "Tarih",
"duration": "Süre",
"copyUrl": "URL'yi Kopyala",
"open": "Aç",
"delete": "Sil",
"totalDownloads": "Toplam İndirme",
"totalSize": "Toplam Boyut",
"mostCommonFormat": "En Yaygın Format",
"urlCopiedToClipboard": "URL panoya kopyalandı!",
"confirmDeleteHistoryItem": "Bu öğeyi geçmişten silmek istediğinizden emin misiniz?",
"confirmClearAllHistory": "Tüm indirme geçmişini temizlemek istediğinizden emin misiniz? Bu işlem geri alınamaz!",
"fileDoesNotExist": "File does not exist anymore",
"updatingYtdlp": "Updating yt-dlp",
"updatedYtdlp": "Updated yt-dlp",
"ytDlpUpdateRequired": "If yt-dlp is updating, wait for the update to finish. If you have installed yt-dlp by yourself, please update it.",
"failedToDeleteHistoryItem": "Failed to delete history item",
"customArgsTxt": "Set custom yt-dlp options.",
"learnMore": "Learn more",
"updateError": "An error occurred during the update process",
"unableToAccessDir": "The program cannot access that folder",
"downloadingUpdate": "Downloading update"
}
================================================
FILE: translations/uk-UA.json
================================================
{
"preferences": "Налаштування",
"about": "Про застосунок",
"downloadLocation": "Шлях завантаження",
"currentDownloadLocation": "Поточне місце завантаження - ",
"enableTransparentDarkMode": "Увімкнути прозорий темний режим (доступно тільки на Linux, потребує перезапуску)",
"downloadingNecessaryFilesWait": "Зачекайте, будь ласка, поки необхідні файли завантажуються",
"video": "Відео",
"audio": "Аудіо",
"title": "Заголовок ",
"selectFormat": "Оберіть формат ",
"download": "Завантажити",
"selectDownloadLocation": "Оберіть шлях для завантаження",
"moreOptions": "Більше опцій",
"start": "Початок",
"selectLanguageRelaunch": "Оберіть мову (потребує перезапуску)",
"downloadTimeRange": "Завантажити певний проміжок часу",
"end": "Кінець",
"timeRangeStartEmptyHint": "Якщо залишити порожнім, почнеться з початку",
"timeRangeEndEmptyHint": "Якщо залишити порожнім, буде завантажено до кінця",
"homepage": "На головну",
"aboutAppDescription": "Це безкоштовний застосунок з відкритим кодом, створений на Node.js та Electron. Для завантаження використовується yt-dlp",
"sourceCodeAvailable": "Вихідний код доступний ",
"here": "тут",
"processing": "Обробка",
"errorNetworkOrUrl": "Сталася помилка. Перевірте мережу та використовуйте правильну URL-адресу",
"errorFailedFileDownload": "Не вдалося завантажити необхідні файли. Перевірте мережу та спробуйте ще раз",
"tryAgain": "Спробувати ще",
"unknownSize": "Невідомий розмір",
"megabyte": "МБ",
"unknownQuality": "Невідома якість",
"downloading": "Завантаження...",
"errorHoverForDetails": "Сталася помилка. Наведіть курсор, щоб побачити деталі",
"fileSavedSuccessfully": "Файл успішно збережено",
"fileSavedClickToOpen": "Файл збережено. Натисніть, щоб відкрити",
"preparing": "Підготовка...",
"progress": "Прогрес",
"speed": "Швидкість",
"quality": "Якість",
"restartApp": "Перезапустити застосунок",
"subtitles": "Субтитри",
"downloadSubtitlesAvailable": "Завантажити субтитри, якщо вони є",
"downloadSubtitlesAuto": "Завантажити автоматично створені субтитри",
"extractAudioFromVideo": "Витягти аудіо з відео",
"extract": "Витягти",
"downloadingNecessaryFiles": "Завантаження необхідних файлів",
"qualityLow": "низька",
"qualityMedium": "середня",
"appDescription": "ytDownloader дозволяє завантажувати відео та аудіо з сотень сайтів, таких як Youtube, Facebook, Instagram, Tiktok, Twitter тощо",
"pasteText": "Натисніть, щоб вставити посилання на відео з буфера обміну",
"pastePlaylistLinkTooltip": "Натисніть, щоб вставити посилання на плейлист з буфера обміну",
"link": "Посилання:",
"downloadingPlaylist": "Завантаження плейлиста:",
"downloadPlaylistButton": "Завантажити плейлист",
"playlistDownloaded": "Плейлист завантажено",
"cookiesWarning": "Ця опція дозволяє завантажувати обмежений контент. Ви отримаєте помилки, якщо cookies відсутні",
"selectBrowserForCookies": "Виберіть браузер для використання cookies",
"none": "Немає",
"updateAvailableDownloadPrompt": "Доступна нова версія, бажаєте завантажити її?",
"updateAvailablePrompt": "Доступна нова версія, бажаєте оновити?",
"update": "Оновити",
"no": "Ні",
"installAndRestartPrompt": "Встановити та перезапустити зараз?",
"restart": "Перезапустити",
"later": "Пізніше",
"extractAudio": "Витягти аудіо",
"selectVideoFormat": "Вибрати формат відео ",
"selectAudioFormat": "Вибрати формат аудіо ",
"maxActiveDownloads": "Максимальна кількість активних завантажень",
"preferredVideoQuality": "Бажана якість відео",
"preferredAudioFormat": "Бажаний формат аудіо",
"best": "Найкраща",
"fileSaved": "Файл збережено.",
"openDownloadFolder": "Відкрити папку завантажень",
"path": "Шлях:",
"selectConfigFile": "Вибрати файл конфігурації",
"useConfigFile": "Використовувати файл конфігурації",
"playlistFilenameFormat": "Формат назв файлів для плейлистів",
"playlistFolderNameFormat": "Формат назв папок для плейлистів",
"resetToDefault": "Скинути до стандартних",
"playlistRange": "Діапазон плейлиста",
"thumbnail": "Мініатюра",
"linkAdded": "Посилання додано",
"downloadThumbnails": "Завантажити мініатюри",
"saveVideoLinksToFile": "Зберегти посилання на відео у файл",
"closeAppToTray": "Закрити програму в системний трей",
"useConfigFileCheckbox": "Використовувати конфігурацію",
"openApp": "Відкрити застосунок",
"pasteVideoLink": "Вставити посилання на відео",
"quit": "Вийти",
"errorDetails": "Деталі помилки",
"clickToCopy": "Натисніть, щоб скопіювати",
"copiedText": "Текст скопійовано",
"qualityNormal": "Звичайна",
"qualityGood": "Добра",
"qualityBad": "Погана",
"qualityWorst": "Найгірша",
"selectQuality": "Виберіть якість",
"disableAutoUpdates": "Вимкнути автооновлення",
"qualityUltraLow": "дуже низька",
"closeAppOnFinish": "Закрити застосунок після завершення завантаження",
"auto": "Авто",
"theme": "Тема",
"themeLight": "Світла",
"themeDark": "Темна",
"themeFrappe": "Frappé",
"themeOneDark": "One Dark",
"themeMatrix": "Matrix",
"themeSolarizedDark": "Solarized Dark",
"preferredVideoCodec": "Бажаний відеокодек",
"showMoreFormatOptions": "Показати більше опцій формату",
"flatsealPermissionWarning": "Потрібно надати програмі дозвіл на доступ до домашнього каталогу. Це можна зробити за допомогою Flatseal, увімкнувши дозвіл з текстом 'filesystem=home'",
"noAudio": "Без аудіо",
"proxy": "Проксі",
"clearDownloads": "Очистити завантаження",
"compressor": "Стиснення",
"dragAndDropFiles": "Перетягніть файли",
"chooseFiles": "Вибрати файли",
"noFilesSelected": "Файли не вибрано",
"videoFormat": "Формат відео",
"videoEncoder": "Відеокодер",
"compressionSpeed": "Швидкість стиснення",
"videoQuality": "Якість відео",
"audioFormat": "Формат аудіо",
"outputSuffix": "Суфікс на виході",
"outputInSameFolder": "Вивести в ту саму папку",
"selectCustomFolder": "Вибрати іншу папку",
"startCompression": "Почати стиснення",
"cancel": "Скасувати",
"errorClickForDetails": "Помилка! Натисніть для деталей",
"homebrewYtDlpWarning": "Спочатку потрібно завантажити yt-dlp через Homebrew",
"openHomebrew": "Відкрити Homebrew",
"downloadHistory": "Історія завантажень",
"close": "Закрити",
"searchByTitleOrUrl": "Пошук за назвою або URL...",
"allFormats": "Всі формати",
"exportAsJson": "Експортувати як JSON",
"exportAsCsv": "Експортувати як CSV",
"clearAllHistory": "Очистити всю історію",
"noDownloadsYet": "Завантажень ще немає",
"downloadHistoryPlaceholder": "Ваша історія завантажень з'явиться тут",
"format": "Формат",
"size": "Розмір",
"date": "Дата",
"duration": "Тривалість",
"copyUrl": "Копіювати URL",
"open": "Відкрити",
"delete": "Видалити",
"totalDownloads": "Усього завантажень",
"totalSize": "Загальний розмір",
"mostCommonFormat": "Найпоширеніший формат",
"urlCopiedToClipboard": "URL скопійовано в буфер обміну!",
"confirmDeleteHistoryItem": "Ви впевнені, що хочете видалити цей елемент з історії?",
"confirmClearAllHistory": "Ви впевнені, що хочете очистити всю історію завантажень? Цю дію не можна скасувати!",
"fileDoesNotExist": "Файл більше не існує",
"updatingYtdlp": "Оновлення yt-dlp",
"updatedYtdlp": "Оновлено yt-dlp",
"ytDlpUpdateRequired": "Якщо yt-dlp оновлюється, зачекайте поки оновлення завершиться. Якщо встановлено yt-dlp самостійно, будь ласка, оновіть його.",
"failedToDeleteHistoryItem": "Не вдалося видалити елемент історії",
"customArgsTxt": "Встановити користувацькі параметри yt-dlp.",
"learnMore": "Докладніше",
"updateError": "Сталася помилка під час процесу оновлення",
"unableToAccessDir": "Програма не має доступу до папки",
"downloadingUpdate": "Завантаження оновлення"
}
================================================
FILE: translations/vi-VN.json
================================================
{
"preferences": "Tùy chỉnh",
"about": "Giới thiệu",
"downloadLocation": "Vị trí tải xuống",
"currentDownloadLocation": "Vị trí tải về hiện tại",
"enableTransparentDarkMode": "Bật chế độ tối trong suốt (chỉ Linux, cần khởi động lại)",
"downloadingNecessaryFilesWait": "Vui lòng chờ, các tập tin cần thiết đang được tải xuống",
"video": "Video",
"audio": "Âm thanh",
"title": "Tiêu đề ",
"selectFormat": "Định dạng ",
"download": "Tải xuống",
"selectDownloadLocation": "Chọn nơi tải xuống",
"moreOptions": "Các tuỳ chọn khác",
"start": "Bắt đầu",
"selectLanguageRelaunch": "Lựa chọn ngôn ngữ (Cần khởi động lại)",
"downloadTimeRange": "Tải xuống với khoảng thời gian tùy chỉnh",
"end": "Đến",
"timeRangeStartEmptyHint": "Để trống sẽ bắt đầu từ đầu",
"timeRangeEndEmptyHint": "Để trống sẽ tải đến hết",
"homepage": "Trang chủ",
"aboutAppDescription": "Đây là ứng dụng mã nguồn mở và miễn phí, được xây dựng dựa trên Node.js và Electron. yt-dlp được sử dụng để tải xuống các file(s)",
"sourceCodeAvailable": "Mã nguồn có sẵn ",
"here": "tại đây",
"processing": "Đang xử lý",
"errorNetworkOrUrl": "Một số lỗi đã xảy ra. Vui lòng kiểm tra lại kết nối Internet của bạn và URL",
"errorFailedFileDownload": "Không thể tải xuống các tệp cần thiết. Vui lòng kiểm tra lại kết nối Internet của bạn và thử lại",
"tryAgain": "Thử lại",
"unknownSize": "Kích thước không xác định",
"megabyte": "MB",
"unknownQuality": "Chất lượng không rõ",
"downloading": "Đang tải...",
"errorHoverForDetails": "Một số lỗi đã xảy ra. Di chuột để xem chi tiết",
"fileSavedSuccessfully": "Tải xuống thành công",
"fileSavedClickToOpen": "Đã lưu. Nhấn vào đây để mở",
"preparing": "Đang chuẩn bị...",
"progress": "Tiến trình",
"speed": "Tốc độ",
"quality": "Chất lượng tải xuống",
"restartApp": "Khởi động lại",
"subtitles": "Phụ đề",
"downloadSubtitlesAvailable": "Tải xuống phụ đề nếu có",
"downloadSubtitlesAuto": "Tải xuống phụ đề được tạo tự động",
"extractAudioFromVideo": "Tách âm thanh từ video",
"extract": "Tách",
"downloadingNecessaryFiles": "Đang tải các tập tin cần thiết",
"qualityLow": "Thấp",
"qualityMedium": "Trung bình",
"appDescription": "ytDownloader hỗ trợ tải video và âm thanh từ hàng trăm website như Youtube, Facebook, Instagram, Tiktok, Twitter và hơn thế nữa",
"pasteText": "Nhấn để dán liên kết video từ clipboard",
"pastePlaylistLinkTooltip": "Nhấn để dán liên kết danh sách phát từ clipboard",
"link": "URL video/playlist cần tải:",
"downloadingPlaylist": "Đang tải xuống danh sách phát:",
"downloadPlaylistButton": "Tải xuống danh sách phát",
"playlistDownloaded": "Đã tải xuống danh sách phát",
"cookiesWarning": "Tùy chọn này cho phép bạn tải xuống nội dung bị hạn chế (Yêu cầu có cookies)",
"selectBrowserForCookies": "Chọn trình duyệt để sử dụng cookie",
"none": "Trống",
"updateAvailableDownloadPrompt": "Đã có phiên bản mới, bạn có muốn tải về không?",
"updateAvailablePrompt": "Đã có phiên bản mới, bạn có muốn cập nhật không?",
"update": "Cập nhật",
"no": "Không",
"installAndRestartPrompt": "Cài đặt và khởi động lại ngay?",
"restart": "Khởi động lại",
"later": "Để sau",
"extractAudio": "Tách âm thanh",
"selectVideoFormat": "Chọn định dạng video",
"selectAudioFormat": "Chọn định dạng âm thanh",
"maxActiveDownloads": "Số lượt tải xuống tối đa",
"preferredVideoQuality": "Chất lượng video ưu tiên",
"preferredAudioFormat": "Định dạng âm thanh ưu tiên",
"best": "Tốt nhất",
"fileSaved": "Đã lưu tập tin.",
"openDownloadFolder": "Mở thư mục tải xuống",
"path": "Đường dẫn:",
"selectConfigFile": "Chọn tập tin cấu hình",
"useConfigFile": "Sử dụng tập tin cấu hình",
"playlistFilenameFormat": "Định dạng tên tập tin cho danh sách phát",
"playlistFolderNameFormat": "Định dạng tên thư mục cho danh sách phát",
"resetToDefault": "Đặt lại về mặc định",
"playlistRange": "Vùng danh sách phát",
"thumbnail": "Ảnh thu nhỏ",
"linkAdded": "Liên kết đã thêm",
"downloadThumbnails": "Tải ảnh thu nhỏ",
"saveVideoLinksToFile": "Lưu đường dẫn video vào tệp tin",
"closeAppToTray": "Thu nhỏ ứng dụng xuống khay hệ thống",
"useConfigFileCheckbox": "Sử dụng tệp tin cấu hình",
"openApp": "Mở ứng dụng",
"pasteVideoLink": "Dán link video",
"quit": "Thoát",
"errorDetails": "Chi tiết lỗi",
"clickToCopy": "Nhấn để sao chép",
"copiedText": "Đã sao chép văn bản",
"qualityNormal": "Bình thường",
"qualityGood": "Tốt",
"qualityBad": "Tệ",
"qualityWorst": "Tệ nhất",
"selectQuality": "Chọn Chất lượng",
"disableAutoUpdates": "Tắt tự động cập nhật",
"qualityUltraLow": "rất thấp",
"closeAppOnFinish": "Đóng ứng dụng khi quá trình tải xuống kết thúc",
"auto": "Tự động",
"theme": "Giao diện",
"themeLight": "Sáng",
"themeDark": "Tối",
"themeFrappe": "Frappé",
"themeOneDark": "One Dark",
"themeMatrix": "Matrix",
"themeSolarizedDark": "Solarized Dark",
"preferredVideoCodec": "Codec video ưa thích",
"showMoreFormatOptions": "Hiển thị nhiều tùy chọn định dạng hơn",
"flatsealPermissionWarning": "Bạn cần cấp quyền truy cập thư mục chính cho ứng dụng để sử dụng tính năng này. Bạn có thể làm điều đó với Flatseal bằng cách bật quyền với văn bản 'filesystem=home'",
"noAudio": "Không có Âm thanh",
"proxy": "Proxy",
"clearDownloads": "Xóa Tải xuống",
"compressor": "Bộ nén",
"dragAndDropFiles": "Kéo và thả tệp",
"chooseFiles": "Chọn Tệp",
"noFilesSelected": "Chưa có tệp nào được chọn",
"videoFormat": "Định dạng Video",
"videoEncoder": "Bộ mã hóa Video",
"compressionSpeed": "Tốc độ nén",
"videoQuality": "Chất lượng Video",
"audioFormat": "Định dạng Âm thanh",
"outputSuffix": "Hậu tố đầu ra",
"outputInSameFolder": "Xuất trong cùng thư mục",
"selectCustomFolder": "Chọn thư mục tùy chỉnh",
"startCompression": "Bắt đầu nén",
"cancel": "Hủy",
"errorClickForDetails": "Lỗi! Nhấn để xem chi tiết",
"homebrewYtDlpWarning": "Bạn cần tải yt-dlp từ homebrew trước",
"openHomebrew": "Mở Homebrew",
"downloadHistory": "Lịch sử Tải xuống",
"close": "Đóng",
"searchByTitleOrUrl": "Tìm kiếm theo tiêu đề hoặc URL...",
"allFormats": "Tất cả Định dạng",
"exportAsJson": "Xuất dưới dạng JSON",
"exportAsCsv": "Xuất dưới dạng CSV",
"clearAllHistory": "Xóa Toàn bộ Lịch sử",
"noDownloadsYet": "Chưa có Tải xuống nào",
"downloadHistoryPlaceholder": "Lịch sử tải xuống của bạn sẽ xuất hiện ở đây",
"format": "Định dạng",
"size": "Kích thước",
"date": "Ngày",
"duration": "Thời lượng",
"copyUrl": "Sao chép URL",
"open": "Mở",
"delete": "Xóa",
"totalDownloads": "Tổng số Tải xuống",
"totalSize": "Tổng Kích thước",
"mostCommonFormat": "Định dạng Phổ biến nhất",
"urlCopiedToClipboard": "URL đã được sao chép vào clipboard!",
"confirmDeleteHistoryItem": "Bạn có chắc chắn muốn xóa mục này khỏi lịch sử không?",
"confirmClearAllHistory": "Bạn có chắc chắn muốn xóa toàn bộ lịch sử tải xuống không? Thao tác này không thể hoàn tác!",
"fileDoesNotExist": "File does not exist anymore",
"updatingYtdlp": "Updating yt-dlp",
"updatedYtdlp": "Updated yt-dlp",
"ytDlpUpdateRequired": "If yt-dlp is updating, wait for the update to finish. If you have installed yt-dlp by yourself, please update it.",
"failedToDeleteHistoryItem": "Failed to delete history item",
"customArgsTxt": "Set custom yt-dlp options.",
"learnMore": "Learn more",
"updateError": "An error occurred during the update process",
"unableToAccessDir": "The program cannot access that folder",
"downloadingUpdate": "Downloading update"
}
================================================
FILE: translations/zh-CN.json
================================================
{
"preferences": "设置",
"about": "關於",
"downloadLocation": "下載位置",
"currentDownloadLocation": "目前下載位置 - ",
"enableTransparentDarkMode": "启用透明深色模式 (仅限 Linux,需重新启动)",
"downloadingNecessaryFilesWait": "请稍候,正在下载必要文件",
"video": "影片",
"audio": "音訊",
"title": "標題 ",
"selectFormat": "選擇格式 ",
"download": "下載",
"selectDownloadLocation": "選取下載位置",
"moreOptions": "更多選項",
"start": "開始",
"selectLanguageRelaunch": "选择语言 (需重新启动)",
"downloadTimeRange": "下載特定時間範圍",
"end": "結束",
"timeRangeStartEmptyHint": "若保留空白,将从头开始",
"timeRangeEndEmptyHint": "若保留空白,将下载至末尾",
"homepage": "首页",
"aboutAppDescription": "这是一款基于 Node.js 和 Electron 构建的免费开源应用程序。使用 yt-dlp 进行下载",
"sourceCodeAvailable": "源代码可于",
"here": "此处获取",
"processing": "處理中",
"errorNetworkOrUrl": "发生错误。请检查您的网络并使用正确的网址",
"errorFailedFileDownload": "下载必要文件失败。请检查您的网络并重试",
"tryAgain": "再试一次",
"unknownSize": "未知大小",
"megabyte": "MB",
"unknownQuality": "未知品质",
"downloading": "下載中...",
"errorHoverForDetails": "发生错误。悬停以查看详细信息",
"fileSavedSuccessfully": "檔案儲存成功",
"fileSavedClickToOpen": "文件已保存。点击以打开",
"preparing": "準備中...",
"progress": "進度",
"speed": "速度",
"quality": "品质",
"restartApp": "重新啟動應用程式",
"subtitles": "字幕",
"downloadSubtitlesAvailable": "若有字幕则下载",
"downloadSubtitlesAuto": "下載自動產生的字幕",
"extractAudioFromVideo": "从视频提取音频",
"extract": "擷取",
"downloadingNecessaryFiles": "正在下载必要文件",
"qualityLow": "低",
"qualityMedium": "中",
"appDescription": "ytDownloader 可让您从 YouTube、Facebook、Instagram、Tiktok、Twitter 等数百个网站下载视频和音频",
"pasteText": "点击以从剪贴板粘贴视频链接",
"pastePlaylistLinkTooltip": "点击以从剪贴板粘贴播放列表链接",
"link": "链接:",
"downloadingPlaylist": "正在下载播放列表:",
"downloadPlaylistButton": "下載播放清單",
"playlistDownloaded": "播放清單已下載",
"cookiesWarning": "此选项可让您下载受限制的内容。若没有 Cookies,将会发生错误",
"selectBrowserForCookies": "选择要使用 Cookies 的浏览器",
"none": "無",
"updateAvailableDownloadPrompt": "有可用的新版本,您要下载吗?",
"updateAvailablePrompt": "有可用的新版本,您要更新吗?",
"update": "更新",
"no": "否",
"installAndRestartPrompt": "立即安装并重新启动?",
"restart": "重新啟動",
"later": "稍後",
"extractAudio": "擷取音訊",
"selectVideoFormat": "選擇影片格式 ",
"selectAudioFormat": "選擇音訊格式 ",
"maxActiveDownloads": "最大同时下载数",
"preferredVideoQuality": "偏好视频画质",
"preferredAudioFormat": "偏好音频格式",
"best": "最佳",
"fileSaved": "文件已保存。",
"openDownloadFolder": "開啟下載資料夾",
"path": "路径:",
"selectConfigFile": "選擇設定檔",
"useConfigFile": "使用設定檔",
"playlistFilenameFormat": "播放清單的檔案名稱格式",
"playlistFolderNameFormat": "播放列表的文件夹名称格式",
"resetToDefault": "重设为默认值",
"playlistRange": "播放清單範圍",
"thumbnail": "縮圖",
"linkAdded": "已添加链接",
"downloadThumbnails": "下載縮圖",
"saveVideoLinksToFile": "将视频链接保存至文件",
"closeAppToTray": "关闭应用程序到系统托盘",
"useConfigFileCheckbox": "使用設定檔",
"openApp": "打开应用程序",
"pasteVideoLink": "貼上影片連結",
"quit": "離開",
"errorDetails": "错误详细信息",
"clickToCopy": "点击以复制",
"copiedText": "已複製文字",
"qualityNormal": "一般",
"qualityGood": "良好",
"qualityBad": "差",
"qualityWorst": "最差",
"selectQuality": "选择品质",
"disableAutoUpdates": "停用自動更新",
"qualityUltraLow": "超低",
"closeAppOnFinish": "下載完成後關閉應用程式",
"auto": "自動",
"theme": "主題",
"themeLight": "亮色",
"themeDark": "暗色",
"themeFrappe": "Frappé",
"themeOneDark": "One Dark",
"themeMatrix": "Matrix",
"themeSolarizedDark": "Solarized Dark",
"preferredVideoCodec": "偏好视频编码器",
"showMoreFormatOptions": "顯示更多格式選項",
"flatsealPermissionWarning": "您需要授予应用程序访问家目录的权限才能使用此功能。您可以使用 Flatseal 并通过 'filesystem=home' 文本来启用权限",
"noAudio": "無音訊",
"proxy": "代理",
"clearDownloads": "清除下载项目",
"compressor": "壓縮器",
"dragAndDropFiles": "拖放文件",
"chooseFiles": "选择文件",
"noFilesSelected": "未选择任何文件",
"videoFormat": "影片格式",
"videoEncoder": "視訊編碼器",
"compressionSpeed": "壓縮速度",
"videoQuality": "影片畫質",
"audioFormat": "音訊格式",
"outputSuffix": "輸出後綴",
"outputInSameFolder": "输出至相同文件夹",
"selectCustomFolder": "選擇自訂資料夾",
"startCompression": "開始壓縮",
"cancel": "取消",
"errorClickForDetails": "错误!点击查看详细信息",
"homebrewYtDlpWarning": "您需要先通过 Homebrew 下载 yt-dlp",
"openHomebrew": "打开 Homebrew",
"downloadHistory": "下载记录",
"close": "关闭",
"searchByTitleOrUrl": "依标题或网址搜索...",
"allFormats": "所有格式",
"exportAsJson": "导出为 JSON",
"exportAsCsv": "导出为 CSV",
"clearAllHistory": "清除所有记录",
"noDownloadsYet": "尚无下载",
"downloadHistoryPlaceholder": "您的下载记录将显示在此处",
"format": "格式",
"size": "大小",
"date": "日期",
"duration": "时长",
"copyUrl": "复制网址",
"open": "打开",
"delete": "删除",
"totalDownloads": "总下载数",
"totalSize": "总大小",
"mostCommonFormat": "最常用格式",
"urlCopiedToClipboard": "网址已复制到剪贴板!",
"confirmDeleteHistoryItem": "您确定要从记录中删除此项目吗?",
"confirmClearAllHistory": "您确定要清除所有下载记录吗?此操作无法撤销!",
"fileDoesNotExist": "该文件已不存在",
"updatingYtdlp": "更新 yt-dlp",
"updatedYtdlp": "更新的yt-dlp",
"ytDlpUpdateRequired": "如果 yt-dlp 正在更新,请等待更新完成。如果您是自行安装的 yt-dlp,请手动更新它。",
"failedToDeleteHistoryItem": "删除历史记录项失败",
"customArgsTxt": "设置自定义yt-dlp选项。",
"learnMore": "了解更多",
"updateError": "更新过程中发生错误",
"unableToAccessDir": "该程序无法访问该文件夹",
"downloadingUpdate": "下载更新"
}
================================================
FILE: translations/zh-TW.json
================================================
{
"preferences": "設定",
"about": "關於",
"downloadLocation": "下載位置",
"currentDownloadLocation": "目前下載位置 - ",
"enableTransparentDarkMode": "啟用透明暗黑模式 (僅限 Linux,需重新啟動)",
"downloadingNecessaryFilesWait": "請稍候,正在下載必要檔案",
"video": "影片",
"audio": "音訊",
"title": "標題 ",
"selectFormat": "選擇格式 ",
"download": "下載",
"selectDownloadLocation": "選取下載位置",
"moreOptions": "更多選項",
"start": "開始",
"selectLanguageRelaunch": "選擇語言 (需重新啟動)",
"downloadTimeRange": "下載特定時間範圍",
"end": "結束",
"timeRangeStartEmptyHint": "若保留空白,將從頭開始",
"timeRangeEndEmptyHint": "若保留空白,將下載至結尾",
"homepage": "首頁",
"aboutAppDescription": "這是一款基於 Node.js 和 Electron 建立的自由及開放原始碼應用程式。使用 yt-dlp 進行下載",
"sourceCodeAvailable": "原始碼可於",
"here": "此處取得",
"processing": "處理中",
"errorNetworkOrUrl": "發生錯誤。請檢查您的網路並使用正確的網址",
"errorFailedFileDownload": "下載必要檔案失敗。請檢查您的網路並重試",
"tryAgain": "再試一次",
"unknownSize": "未知大小",
"megabyte": "MB",
"unknownQuality": "未知品質",
"downloading": "下載中...",
"errorHoverForDetails": "發生錯誤。懸停以查看詳細資訊",
"fileSavedSuccessfully": "檔案儲存成功",
"fileSavedClickToOpen": "檔案已儲存。點擊以開啟",
"preparing": "準備中...",
"progress": "進度",
"speed": "速度",
"quality": "品質",
"restartApp": "重新啟動應用程式",
"subtitles": "字幕",
"downloadSubtitlesAvailable": "若有字幕則下載",
"downloadSubtitlesAuto": "下載自動產生的字幕",
"extractAudioFromVideo": "從影片擷取音訊",
"extract": "擷取",
"downloadingNecessaryFiles": "正在下載必要檔案",
"qualityLow": "低",
"qualityMedium": "中",
"appDescription": "ytDownloader 讓您能從 YouTube、Facebook、Instagram、Tiktok、Twitter 等數百個網站下載影片和音訊",
"pasteText": "點擊以從剪貼簿貼上影片連結",
"pastePlaylistLinkTooltip": "點擊以從剪貼簿貼上播放清單連結",
"link": "連結:",
"downloadingPlaylist": "正在下載播放清單:",
"downloadPlaylistButton": "下載播放清單",
"playlistDownloaded": "播放清單已下載",
"cookiesWarning": "此選項可讓您下載受限制的內容。若沒有 Cookies,將會發生錯誤",
"selectBrowserForCookies": "選擇要從哪個瀏覽器使用 Cookies",
"none": "無",
"updateAvailableDownloadPrompt": "有可用的新版本,您要下載嗎?",
"updateAvailablePrompt": "有可用的新版本,您要更新嗎?",
"update": "更新",
"no": "否",
"installAndRestartPrompt": "立即安裝並重新啟動?",
"restart": "重新啟動",
"later": "稍後",
"extractAudio": "擷取音訊",
"selectVideoFormat": "選擇影片格式 ",
"selectAudioFormat": "選擇音訊格式 ",
"maxActiveDownloads": "最大同時下載數",
"preferredVideoQuality": "偏好影片畫質",
"preferredAudioFormat": "偏好音訊格式",
"best": "最佳",
"fileSaved": "檔案已儲存。",
"openDownloadFolder": "開啟下載資料夾",
"path": "路徑:",
"selectConfigFile": "選擇設定檔",
"useConfigFile": "使用設定檔",
"playlistFilenameFormat": "播放清單的檔案名稱格式",
"playlistFolderNameFormat": "播放清單的資料夾名稱格式",
"resetToDefault": "重設為預設值",
"playlistRange": "播放清單範圍",
"thumbnail": "縮圖",
"linkAdded": "已新增連結",
"downloadThumbnails": "下載縮圖",
"saveVideoLinksToFile": "將影片連結儲存至檔案",
"closeAppToTray": "關閉應用程式至系統匣",
"useConfigFileCheckbox": "使用設定檔",
"openApp": "開啟應用程式",
"pasteVideoLink": "貼上影片連結",
"quit": "離開",
"errorDetails": "錯誤詳細資訊",
"clickToCopy": "點擊以複製",
"copiedText": "已複製文字",
"qualityNormal": "一般",
"qualityGood": "良好",
"qualityBad": "差",
"qualityWorst": "最差",
"selectQuality": "選擇品質",
"disableAutoUpdates": "停用自動更新",
"qualityUltraLow": "超低",
"closeAppOnFinish": "下載完成後關閉應用程式",
"auto": "自動",
"theme": "主題",
"themeLight": "亮色",
"themeDark": "暗色",
"themeFrappe": "Frappé",
"themeOneDark": "One Dark",
"themeMatrix": "Matrix",
"themeSolarizedDark": "暗青色",
"preferredVideoCodec": "偏好視訊編碼器",
"showMoreFormatOptions": "顯示更多格式選項",
"flatsealPermissionWarning": "您需要授予應用程式存取家目錄的權限才能使用此功能。您可以使用 Flatseal 並透過 'filesystem=home' 文字來啟用權限",
"noAudio": "無音訊",
"proxy": "代理",
"clearDownloads": "清除下載項目",
"compressor": "壓縮器",
"dragAndDropFiles": "拖放檔案",
"chooseFiles": "選擇檔案",
"noFilesSelected": "未選擇任何檔案",
"videoFormat": "影片格式",
"videoEncoder": "視訊編碼器",
"compressionSpeed": "壓縮速度",
"videoQuality": "影片畫質",
"audioFormat": "音訊格式",
"outputSuffix": "輸出後綴",
"outputInSameFolder": "輸出至相同資料夾",
"selectCustomFolder": "選擇自訂資料夾",
"startCompression": "開始壓縮",
"cancel": "取消",
"errorClickForDetails": "錯誤!點擊查看詳細資訊",
"homebrewYtDlpWarning": "您需要先透過 Homebrew 下載 yt-dlp",
"openHomebrew": "開啟 Homebrew",
"downloadHistory": "下載紀錄",
"close": "關閉",
"searchByTitleOrUrl": "依標題或網址搜尋...",
"allFormats": "所有格式",
"exportAsJson": "匯出為 JSON",
"exportAsCsv": "匯出為 CSV",
"clearAllHistory": "清除所有紀錄",
"noDownloadsYet": "尚無下載",
"downloadHistoryPlaceholder": "您的下載紀錄將會顯示於此",
"format": "格式",
"size": "大小",
"date": "日期",
"duration": "時長",
"copyUrl": "複製網址",
"open": "開啟",
"delete": "刪除",
"totalDownloads": "總下載數",
"totalSize": "總大小",
"mostCommonFormat": "最常用格式",
"urlCopiedToClipboard": "網址已複製到剪貼簿!",
"confirmDeleteHistoryItem": "您確定要從紀錄中刪除此項目嗎?",
"confirmClearAllHistory": "您確定要清除所有下載紀錄嗎?此操作無法復原!",
"fileDoesNotExist": "檔案已不存在",
"updatingYtdlp": "正在更新 yt-dlp",
"updatedYtdlp": "已更新 yt-dlp",
"ytDlpUpdateRequired": "如果 yt-dlp 正在更新,請等待更新完成。如果您是自行安裝 yt-dlp,請手動更新。",
"failedToDeleteHistoryItem": "無法刪除歷史記錄項目",
"customArgsTxt": "設定自訂 yt-dlp 選項。",
"learnMore": "了解詳情",
"updateError": "更新過程中發生錯誤",
"unableToAccessDir": "程式無法存取該資料夾",
"downloadingUpdate": "正在下載更新"
}
================================================
FILE: tsconfig.json
================================================
{
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"lib": [
"ES2021.String",
"DOM",
]
}
}
================================================
FILE: windows.ps1
================================================
Invoke-WebRequest -Uri "https://github.com/aandrew-me/ffmpeg-builds/releases/download/v8/node.exe" -OutFile node.exe
Invoke-WebRequest -Uri "https://github.com/aandrew-me/ffmpeg-builds/releases/download/v8/ffmpeg_win64.zip" -OutFile ffmpeg_win64.zip
Expand-Archive -Path ffmpeg_win64.zip -DestinationPath .
Remove-Item -Path ffmpeg_win64.zip
Move-Item -Path .\ffmpeg_win64 -Destination .\ffmpeg
================================================
FILE: ytdownloader.json
================================================
{
"homepage": "https://ytdn.netlify.app",
"version": "3.20.2",
"description": "A modern GUI App for downloading Videos and Audios from hundreds of sites",
"license": "GPL-3",
"url": "https://github.com/aandrew-me/ytDownloader/releases/download/v3.20.2/YTDownloader_Win.zip",
"checkver": {
"github": "https://github.com/aandrew-me/ytDownloader"
},
"bin": ["YTDownloader.exe", ["YTDownloader.exe", "ytdownloader"]],
"shortcuts": [["YTDownloader.exe", "YTDownloader"]],
"architecture": ["x64"],
"autoupdate": {
"url": "https://github.com/aandrew-me/ytDownloader/releases/download/v$version/YTDownloader_Win.zip",
"hash": {
"url": "$url.sha256"
}
}
}