Improve UI of video/audio format selection

improved_format_select
aandrew-me 4 weeks ago
parent a25fb1f59c
commit eee9d83a00

@ -363,7 +363,7 @@ select {
font-size: large;
outline: none;
position: relative;
width: 160px;
width: 220px;
}
#browserInfo {
cursor: pointer;

@ -149,7 +149,7 @@
--blueBtn: #268bd2;
--blueBtn-bottom: #2074b1;
}
:root[theme = "gruvbox"]{
:root[theme="gruvbox"] {
--background: #242424;
--text: #fffefd;
--box-main: #32302f;
@ -158,13 +158,12 @@
--box-toggleOn: #458588;
--item-bg: #eebd35;
--select: #98971a;
--greenBtn:#8ec07c;
--greenBtn-bottom:#689D6A;
--greenBtn: #8ec07c;
--greenBtn-bottom: #689d6a;
--redBtn: #fb4934;
--redBtn-bottom: #cc241d;
--blueBtn: #7fa2ac;
--blueBtn-bottom: #458588;
}
body {
@ -291,7 +290,9 @@ img {
border-radius: 8px;
padding: 8px;
font-family: "Inter", sans-serif;
transition: border 0.2s, background 0.2s;
transition:
border 0.2s,
background 0.2s;
width: 100%;
cursor: pointer;
}
@ -382,23 +383,22 @@ img {
}
.custom-progress {
width: 90%;
height: 8px;
background: #e0e0e0;
border-radius: 6px;
overflow: hidden;
display:inline-block;
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;
width: 0%;
height: 100%;
background: var(--blueBtn);
transition: width 0.3s ease;
border-radius: 6px 0 0 6px;
}
.itemSpeed {
padding: 10px 5px 5px 5px;
}
@ -534,16 +534,8 @@ img {
padding: 10px;
}
#videoFormatSelect,
#audioFormatSelect,
#audioForVideoFormatSelect {
font-family: JetBrains, monospace;
font-size: medium;
width: 400px;
}
.formatSelect {
margin-right: 4px;
margin: 2px 4px 2px 0;
}
label {
@ -1217,3 +1209,170 @@ input[type="range"]::-webkit-slider-thumb:hover {
#updateProgress {
font-size: 14px;
}
#videoFormatSelectContainer,
#audioFormatSelectContainer,
#audioFormatSelectContainer {
display: inline-block;
width: min(400px, 100%);
padding: 5px 0;
}
/* SlimeSelect styles */
.ss-main {
background-color: var(--select);
color: var(--text);
border: none;
border-radius: 12px;
height: 50px;
}
.ss-arrow path {
stroke: var(--text) !important;
opacity: 0.6;
}
.ss-search {
padding: 8px 12px !important;
border-bottom: 1px solid var(--select) !important;
}
.ss-search input {
background-color: var(--box-separation) !important;
color: var(--text) !important;
border-radius: 6px !important;
padding: 8px 12px !important;
font-family: inherit !important;
font-size: 13px !important;
transition: border-color 0.2s !important;
}
.ss-search input::placeholder {
color: var(--text) !important;
opacity: 0.6;
}
.ss-content {
background-color: var(--select) !important;
border: transparent !important;
border-radius: 8px !important;
}
.ss-list {
padding: 4px !important;
}
.ss-list::-webkit-scrollbar {
width: 10px;
}
.ss-list::-webkit-scrollbar-track {
box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
}
.ss-list::-webkit-scrollbar-thumb {
background-color: rgb(79, 78, 78);
border-radius: 5px;
}
.ss-option {
padding: 8px 12px !important;
border-radius: 6px !important;
color: var(--text);
}
.ss-option:hover,
.ss-selected,
.ss-highlighted {
background-color: var(--box-toggleOn) !important;
color: var(--text) !important;
}
/* Row Grid structures */
.modern-option-row {
display: grid;
align-items: center;
width: 100%;
justify-items: center;
}
.video-grid-extended {
grid-template-columns: 85px 65px 60px 1fr auto;
}
.video-grid-compact {
grid-template-columns: 85px 65px 1fr auto;
}
.audio-grid {
grid-template-columns: 200px 60px 1fr;
justify-items: center;
}
.modern-option-row .main-text {
font-weight: 600;
font-size: 14px;
text-overflow: ellipsis;
overflow: hidden;
opacity: 0.9;
white-space: normal;
}
.modern-option-row .codec-text {
font-size: 12px;
font-family: JetBrains;
opacity: 0.6;
}
.modern-option-row .size-text {
font-size: 13px;
font-weight: 500;
justify-self: end;
margin-right: 12px;
opacity: 0.8;
}
.badge {
font-size: 11px;
font-weight: 600;
padding: 3px 6px;
border-radius: 4px;
text-transform: uppercase;
width: max-content;
text-align: center;
}
.badge-format {
background-color: var(--box-main);
color: var(--text);
}
.audio-indicator {
display: inline-flex;
align-items: center;
gap: 4px;
font-size: 11px;
padding: 2px 6px;
border-radius: 4px;
background-color: transparent;
color: #10b981;
font-weight: 500;
}
.audio-indicator svg {
width: 20px;
height: 20px;
fill: currentColor;
}
.audio-placeholder {
width: 53px;
}
/* Resolution */
.ss-option .main-text {
color: var(--text) !important;
opacity: 0.8;
}
/* Video codec */
.ss-option .codec-text {
color: var(--text) !important;
opacity: 0.6;
}
/* Video/audio size */
.ss-option .size-text {
color: var(--text) !important;
opacity: 0.8;
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -7,6 +7,10 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>YtDownloader</title>
<script src="../assets/slimselect.js"></script>
<link href="../assets/slimselect.css" rel="stylesheet">
<link rel="stylesheet" href="../assets/css/index.css">
<script src="../translations/i18n-init.js" defer></script>
@ -70,8 +74,9 @@
<option id="matrixTxt" value="matrix" data-translate="themeMatrix">Matrix</option>
<option id="githubTxt" value="github">Github</option>
<option id="latteTxt" value="latte">Latte</option>
<option id="solarizedDarkTxt" value="solarized-dark" data-translate="themeSolarizedDark">Solarized Dark</option>
<option id ="guvboxTxt" value="gruvbox">Gruvbox</option>
<option id="solarizedDarkTxt" value="solarized-dark" data-translate="themeSolarizedDark">Solarized Dark
</option>
<option id="guvboxTxt" value="gruvbox">Gruvbox</option>
</select>
</div>
@ -119,8 +124,11 @@
<div class="separationBox">
<h2 id="videoHeader" data-translate="video">Video</h2>
<label class="formatSelect" data-translate="selectFormat">Select Format </label>
<select id="videoFormatSelect" class="select">
</select>
<div id="videoFormatSelectContainer">
<select id="videoFormatSelect">
</select>
</div>
<br>
<input type="hidden" name="url" class="url" id="url">
@ -128,8 +136,13 @@
<div id="audioForVideo">
<h2 id="audioHeader" data-translate="audio">Audio</h2>
<label class="formatSelect" data-translate="selectAudioFormat">Select Audio Format </label>
<select id="audioForVideoFormatSelect" class="select">
</select>
<div id="audioFormatSelectContainer">
<select id="audioForVideoFormatSelect">
</select>
</div>
<br>
<input type="hidden" name="url" class="url">
</div>
@ -146,9 +159,13 @@
<div id="audioList">
<div id="audioPresent">
<label class="formatSelect" data-translate="selectFormat">Select Format </label>
<select id="audioFormatSelect" class="select">
</select>
<br>
<div id="audioFormatSelectContainer">
<select id="audioFormatSelect">
</select>
</div>
<br><br>
<input type="hidden" name="url" class="url">
<button class="submitBtn" id="audioDownload" data-translate="download">Download</button>
<button id="advancedAudioToggle" class="blueBtn" onClick="advancedToggle()"
@ -259,6 +276,11 @@
<span id="updateProgress">0.0%</span>
</div>
<script>
new SlimSelect({ select: '#videoFormatSelect' });
</script>
</body>
</html>

@ -201,7 +201,7 @@ class YtDownloaderApp {
}
const savedPath = localStorage.getItem(
CONSTANTS.LOCAL_STORAGE_KEYS.DOWNLOAD_PATH
CONSTANTS.LOCAL_STORAGE_KEYS.DOWNLOAD_PATH,
);
if (savedPath) {
try {
@ -209,12 +209,12 @@ class YtDownloaderApp {
this.state.downloadDir = savedPath;
} catch {
console.warn(
`Cannot write to saved path "${savedPath}". Falling back to default.`
`Cannot write to saved path "${savedPath}". Falling back to default.`,
);
this.state.downloadDir = defaultDownloadDir;
localStorage.setItem(
CONSTANTS.LOCAL_STORAGE_KEYS.DOWNLOAD_PATH,
defaultDownloadDir
defaultDownloadDir,
);
}
} else {
@ -272,7 +272,7 @@ class YtDownloaderApp {
window.i18n.translatePage();
resolve();
},
{once: true}
{once: true},
);
});
}
@ -296,7 +296,7 @@ class YtDownloaderApp {
executablePath = process.env.YTDOWNLOADER_YTDLP_PATH;
} else {
throw new Error(
"YTDOWNLOADER_YTDLP_PATH is set, but no file exists there."
"YTDOWNLOADER_YTDLP_PATH is set, but no file exists there.",
);
}
}
@ -325,7 +325,7 @@ class YtDownloaderApp {
executablePath = execSync("which yt-dlp").toString().trim();
} catch {
throw new Error(
"No yt-dlp found in PATH on FreeBSD. Please install it."
"No yt-dlp found in PATH on FreeBSD. Please install it.",
);
}
}
@ -336,7 +336,7 @@ class YtDownloaderApp {
// app-managed binary so YouTube fixes arrive fastest.
const source =
localStorage.getItem(
CONSTANTS.LOCAL_STORAGE_KEYS.YT_DLP_SOURCE
CONSTANTS.LOCAL_STORAGE_KEYS.YT_DLP_SOURCE,
) || CONSTANTS.YT_DLP_SOURCE.NIGHTLY;
// "system": use yt-dlp from PATH (apt/pip/etc.). Falls back to the
@ -366,7 +366,7 @@ class YtDownloaderApp {
// binary. A stored path is reused; otherwise it is downloaded.
if (!executablePath) {
const storedPath = localStorage.getItem(
CONSTANTS.LOCAL_STORAGE_KEYS.YT_DLP_PATH
CONSTANTS.LOCAL_STORAGE_KEYS.YT_DLP_PATH,
);
if (
@ -378,16 +378,15 @@ class YtDownloaderApp {
}
// Download if missing
else {
executablePath = await this.ensureYtDlpBinary(
defaultYtDlpPath
);
executablePath =
await this.ensureYtDlpBinary(defaultYtDlpPath);
}
}
}
localStorage.setItem(
CONSTANTS.LOCAL_STORAGE_KEYS.YT_DLP_PATH,
executablePath
executablePath,
);
// Auto update
@ -411,10 +410,10 @@ class YtDownloaderApp {
const brewUpdate = spawn(brewExec, ["upgrade", "yt-dlp"]);
brewUpdate.on("error", (err) =>
console.error("Failed to run 'brew upgrade yt-dlp':", err)
console.error("Failed to run 'brew upgrade yt-dlp':", err),
);
brewUpdate.stdout.on("data", (data) =>
console.log("yt-dlp brew update:", data.toString())
console.log("yt-dlp brew update:", data.toString()),
);
} else {
// Only self-update binaries we manage in ~/.ytDownloader.
@ -423,7 +422,7 @@ class YtDownloaderApp {
const hiddenDir = join(homedir(), ".ytDownloader");
if (!executablePath.startsWith(hiddenDir)) {
console.log(
"Using system yt-dlp; skipping self-update (-U)."
"Using system yt-dlp; skipping self-update (-U).",
);
return;
}
@ -438,8 +437,8 @@ class YtDownloaderApp {
updateProc.on("error", (err) =>
console.error(
"Failed to run background yt-dlp update:",
err
)
err,
),
);
updateProc.stdout.on("data", (data) => {
@ -479,7 +478,7 @@ class YtDownloaderApp {
$(CONSTANTS.DOM_IDS.POPUP_BOX).style.display = "block";
$(CONSTANTS.DOM_IDS.POPUP_SVG).style.display = "inline";
document.querySelector("#popupBox p").textContent = i18n.__(
"downloadingNecessaryFilesWait"
"downloadingNecessaryFilesWait",
);
try {
@ -489,18 +488,18 @@ class YtDownloaderApp {
undefined,
(progress, _d, _t) => {
$(
CONSTANTS.DOM_IDS.YTDLP_DOWNLOAD_PROGRESS
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
defaultYtDlpPath,
);
return defaultYtDlpPath;
@ -510,7 +509,7 @@ class YtDownloaderApp {
console.error("Failed to download yt-dlp:", downloadError);
document.querySelector("#popupBox p").textContent = i18n.__(
"errorFailedFileDownload"
"errorFailedFileDownload",
);
$(CONSTANTS.DOM_IDS.POPUP_SVG).style.display = "none";
@ -539,7 +538,7 @@ class YtDownloaderApp {
return process.env.YTDOWNLOADER_FFMPEG_PATH;
}
throw new Error(
"YTDOWNLOADER_FFMPEG_PATH is set, but no file exists there."
"YTDOWNLOADER_FFMPEG_PATH is set, but no file exists there.",
);
}
@ -549,7 +548,7 @@ class YtDownloaderApp {
return execSync("which ffmpeg").toString().trim();
} catch {
throw new Error(
"No ffmpeg found in PATH on FreeBSD. App may not work correctly."
"No ffmpeg found in PATH on FreeBSD. App may not work correctly.",
);
}
}
@ -647,35 +646,37 @@ class YtDownloaderApp {
prefs.videoQuality =
Number(
localStorage.getItem(
CONSTANTS.LOCAL_STORAGE_KEYS.PREFERRED_VIDEO_QUALITY
)
CONSTANTS.LOCAL_STORAGE_KEYS.PREFERRED_VIDEO_QUALITY,
),
) || 1080;
prefs.audioQuality =
localStorage.getItem(
CONSTANTS.LOCAL_STORAGE_KEYS.PREFERRED_AUDIO_QUALITY
CONSTANTS.LOCAL_STORAGE_KEYS.PREFERRED_AUDIO_QUALITY,
) || "";
prefs.videoCodec =
localStorage.getItem(
CONSTANTS.LOCAL_STORAGE_KEYS.PREFERRED_VIDEO_CODEC
CONSTANTS.LOCAL_STORAGE_KEYS.PREFERRED_VIDEO_CODEC,
) || "avc1";
prefs.showMoreFormats =
localStorage.getItem(
CONSTANTS.LOCAL_STORAGE_KEYS.SHOW_MORE_FORMATS
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
CONSTANTS.LOCAL_STORAGE_KEYS.BROWSER_COOKIES,
) || "";
prefs.customYtDlpArgs =
localStorage.getItem(
CONSTANTS.LOCAL_STORAGE_KEYS.YT_DLP_CUSTOM_ARGS
CONSTANTS.LOCAL_STORAGE_KEYS.YT_DLP_CUSTOM_ARGS,
) || "";
prefs.configPath = localStorage.getItem(CONSTANTS.LOCAL_STORAGE_KEYS.CONFIG_PATH) || "";
prefs.configPath =
localStorage.getItem(CONSTANTS.LOCAL_STORAGE_KEYS.CONFIG_PATH) ||
"";
const maxDownloads = Number(
localStorage.getItem(CONSTANTS.LOCAL_STORAGE_KEYS.MAX_DOWNLOADS)
localStorage.getItem(CONSTANTS.LOCAL_STORAGE_KEYS.MAX_DOWNLOADS),
);
this.state.maxActiveDownloads = maxDownloads >= 1 ? maxDownloads : 5;
@ -683,7 +684,7 @@ class YtDownloaderApp {
$(CONSTANTS.DOM_IDS.CUSTOM_ARGS_INPUT).value = prefs.customYtDlpArgs;
const downloadDir = localStorage.getItem(
CONSTANTS.LOCAL_STORAGE_KEYS.DOWNLOAD_PATH
CONSTANTS.LOCAL_STORAGE_KEYS.DOWNLOAD_PATH,
);
if (downloadDir) {
@ -697,7 +698,7 @@ class YtDownloaderApp {
*/
_addEventListeners() {
$(CONSTANTS.DOM_IDS.PASTE_URL_BTN).addEventListener("click", () =>
this.pasteAndGetInfo()
this.pasteAndGetInfo(),
);
document.addEventListener("keydown", (event) => {
if (
@ -712,7 +713,7 @@ class YtDownloaderApp {
setTimeout(() => {
$(CONSTANTS.DOM_IDS.PASTE_URL_BTN).classList.remove(
"active"
"active",
);
}, 150);
@ -722,24 +723,24 @@ class YtDownloaderApp {
// Download buttons
$(CONSTANTS.DOM_IDS.VIDEO_DOWNLOAD_BTN).addEventListener("click", () =>
this.handleDownloadRequest("video")
this.handleDownloadRequest("video"),
);
$(CONSTANTS.DOM_IDS.AUDIO_DOWNLOAD_BTN).addEventListener("click", () =>
this.handleDownloadRequest("audio")
this.handleDownloadRequest("audio"),
);
$(CONSTANTS.DOM_IDS.EXTRACT_BTN).addEventListener("click", () =>
this.handleDownloadRequest("extract")
this.handleDownloadRequest("extract"),
);
// UI controls
$(CONSTANTS.DOM_IDS.CLOSE_HIDDEN_BTN).addEventListener("click", () =>
this._hideInfoPanel()
this._hideInfoPanel(),
);
$(CONSTANTS.DOM_IDS.SELECT_LOCATION_BTN).addEventListener("click", () =>
ipcRenderer.send("select-location-main", "")
ipcRenderer.send("select-location-main", ""),
);
$(CONSTANTS.DOM_IDS.CLEAR_BTN).addEventListener("click", () =>
this._clearAllDownloaded()
this._clearAllDownloaded(),
);
// Error details
@ -813,19 +814,19 @@ class YtDownloaderApp {
const maxSlider = $(CONSTANTS.DOM_IDS.MAX_SLIDER);
minSlider.addEventListener("input", () =>
this._updateSliderUI(minSlider)
this._updateSliderUI(minSlider),
);
maxSlider.addEventListener("input", () =>
this._updateSliderUI(maxSlider)
this._updateSliderUI(maxSlider),
);
$(CONSTANTS.DOM_IDS.START_TIME).addEventListener(
"change",
this._handleTimeInputChange
this._handleTimeInputChange,
);
$(CONSTANTS.DOM_IDS.END_TIME).addEventListener(
"change",
this._handleTimeInputChange
this._handleTimeInputChange,
);
this._updateSliderUI(null);
@ -899,7 +900,7 @@ class YtDownloaderApp {
uiSnapshot: {
videoFormat: $(CONSTANTS.DOM_IDS.VIDEO_FORMAT_SELECT).value,
audioForVideoFormat: $(
CONSTANTS.DOM_IDS.AUDIO_FOR_VIDEO_FORMAT_SELECT
CONSTANTS.DOM_IDS.AUDIO_FOR_VIDEO_FORMAT_SELECT,
).value,
audioFormat: $(CONSTANTS.DOM_IDS.AUDIO_FORMAT_SELECT).value,
extractFormat: $(CONSTANTS.DOM_IDS.EXTRACT_SELECTION).value,
@ -944,9 +945,9 @@ class YtDownloaderApp {
const process = this.state.ytDlp.exec(args, {shell: true});
console.log(
"Spawned yt-dlp with args:",
process.ytDlpProcess.spawnargs.join(" ")
);
"Spawned yt-dlp with args:",
process.ytDlpProcess.spawnargs.join(" "),
);
let stdout = "";
let stderr = "";
@ -964,15 +965,15 @@ class YtDownloaderApp {
reject(
new Error(
"Failed to parse yt-dlp JSON output: " +
(stderr || e.message)
)
(stderr || e.message),
),
);
}
} else {
reject(
new Error(
stderr || `yt-dlp exited with a non-zero code.`
)
stderr || `yt-dlp exited with a non-zero code.`,
),
);
}
});
@ -1005,7 +1006,7 @@ class YtDownloaderApp {
console.log(
"Spawned yt-dlp with args:",
downloadProcess.ytDlpProcess.spawnargs.join(" ")
downloadProcess.ytDlpProcess.spawnargs.join(" "),
);
// Attach event listeners
@ -1026,7 +1027,7 @@ class YtDownloaderApp {
randomId,
finalFilename,
finalExt,
job.thumbnail
job.thumbnail,
);
})
.once("error", (error) => {
@ -1051,7 +1052,7 @@ class YtDownloaderApp {
job.thumbnail || "../assets/images/thumb.png"
}" alt="thumbnail" class="itemIcon" crossorigin="anonymous">
<span class="itemType">${i18n.__(
job.type === "video" ? "video" : "audio"
job.type === "video" ? "video" : "audio",
)}</span>
</div>
<div class="itemBody">
@ -1061,7 +1062,7 @@ class YtDownloaderApp {
</div>`;
$(CONSTANTS.DOM_IDS.DOWNLOAD_LIST).insertAdjacentHTML(
"beforeend",
itemHTML
itemHTML,
);
}
@ -1146,7 +1147,7 @@ class YtDownloaderApp {
const outputPath = `"${join(
this.state.downloadDir,
`${finalFilename}.${ext}`
`${finalFilename}.${ext}`,
)}"`;
const baseArgs = [
@ -1198,7 +1199,7 @@ class YtDownloaderApp {
if (rangeOption) downloadArgs.push(rangeOption, rangeCmd);
const customArgsString = $(
CONSTANTS.DOM_IDS.CUSTOM_ARGS_INPUT
CONSTANTS.DOM_IDS.CUSTOM_ARGS_INPUT,
).value.trim();
if (customArgsString) {
const customArgs = customArgsString.split(/\s+/);
@ -1225,7 +1226,7 @@ class YtDownloaderApp {
// 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
randomId,
);
}
@ -1247,7 +1248,7 @@ class YtDownloaderApp {
console.log(`Download ${randomId} was aborted.`);
this.state.currentDownloads = Math.max(
0,
this.state.currentDownloads - 1
this.state.currentDownloads - 1,
);
this.state.downloadControllers.delete(randomId);
this._processQueue();
@ -1309,9 +1310,8 @@ class YtDownloaderApp {
$(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 = `<option value="none|none">${noAudioTxt}</option>`;
$(CONSTANTS.DOM_IDS.AUDIO_FOR_VIDEO_FORMAT_SELECT).innerHTML =
`<option value="none|none">${noAudioTxt}</option>`;
}
/**
@ -1319,48 +1319,18 @@ class YtDownloaderApp {
* @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 videoSelectId = CONSTANTS.DOM_IDS.VIDEO_FORMAT_SELECT;
const audioSelectId = CONSTANTS.DOM_IDS.AUDIO_FORMAT_SELECT;
const audioForVideoSelectId =
CONSTANTS.DOM_IDS.AUDIO_FOR_VIDEO_FORMAT_SELECT;
const {videoQuality, videoCodec, showMoreFormats} =
this.state.preferences;
let bestMatchHeight = 0;
const speakerIconSvg = `<svg viewBox="0 0 24 24"><path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02z"/></svg>`;
// Find the ideal match height boundary
formats.forEach((f) => {
if (
f.height &&
@ -1373,19 +1343,24 @@ class YtDownloaderApp {
});
if (bestMatchHeight === 0 && formats.length > 0) {
bestMatchHeight = Math.max(
...formats.filter((f) => f.height).map((f) => f.height)
...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])
.map((f) => f.vcodec.split(".")[0]),
);
const finalCodec = availableCodecs.has(videoCodec)
? videoCodec
: [...availableCodecs].pop();
let isAVideoSelected = false;
const videoOptions = [];
const audioOptions = [];
formats.forEach((format) => {
let sizeInMB = null;
let isApprox = false;
@ -1411,7 +1386,9 @@ class YtDownloaderApp {
) {
return;
}
let isSelected = false;
if (
!isAVideoSelected &&
format.height === bestMatchHeight &&
@ -1421,31 +1398,44 @@ class YtDownloaderApp {
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 = `<option value="${format.format_id}|${
format.ext
}|${format.height}|${format.vcodec}" ${
isSelected ? "selected" : ""
}>${optionText}</option>`;
const quality = `${format.height || "???"}p${format.fps === 60 ? "60" : ""}`;
const vcodecText = format.vcodec?.split(".")[0] || "";
const audioMarkup =
format.acodec !== "none"
? `<div class="audio-indicator">${speakerIconSvg}</div>`
: `<div class="audio-placeholder"></div>`;
const codecHtml = showMoreFormats
? `<span class="codec-text">${vcodecText}</span>`
: "";
const gridClass = showMoreFormats
? "video-grid-extended"
: "video-grid-compact";
const optionTextFallback = showMoreFormats
? `${quality} ${format.ext} ${vcodecText} ${displaySize}`
: `${quality} ${format.ext} ${displaySize}`;
const htmlContent = `
<div class="modern-option-row ${gridClass}">
<span class="main-text">${quality}</span>
<span class="badge badge-format">${format.ext}</span>
${codecHtml}
<span class="size-text">${displaySize}</span>
${audioMarkup}
</div>
`;
videoOptions.push({
text: optionTextFallback,
value: `${format.format_id}|${format.ext}|${format.height}|${format.vcodec}`,
selected: isSelected,
html: htmlContent,
});
videoSelect.innerHTML += option;
// PROCESS AUDIO ONLY CHANNELS
} else if (
format.acodec !== "none" &&
format.video_ext === "none"
@ -1456,31 +1446,82 @@ class YtDownloaderApp {
const formatNote =
i18n.__(format.format_note) || i18n.__("unknownQuality");
const audioExtPadded = audioExt.padEnd(extPadding, NBSP);
// HTML for Audio Grid
const htmlContent = `
<div class="modern-option-row audio-grid">
<span class="main-text">${formatNote}</span>
<span class="badge badge-format">${audioExt}</span>
<span class="size-text">${displaySize}</span>
</div>
`;
audioOptions.push({
text: `${formatNote} ${audioExt} ${displaySize}`,
value: `${format.format_id}|${audioExt}`,
html: htmlContent,
});
}
});
const hasAudioTrack = formats.some(
(f) => f.acodec !== "none" && f.acodec !== undefined && f.vcodec === "none",
);
const audioSection = $(CONSTANTS.DOM_IDS.AUDIO_PRESENT_SECTION);
const audioQualityPadded = formatNote.padEnd(
audioQualityPadding,
NBSP
);
const audioSizePadded = displaySize.padEnd(
filesizePadding,
NBSP
);
if (audioSection) {
audioSection.style.display = hasAudioTrack ? "block" : "none";
}
const videoSelectEl = $(CONSTANTS.DOM_IDS.VIDEO_FORMAT_SELECT);
const audioSelectEl = $(CONSTANTS.DOM_IDS.AUDIO_FORMAT_SELECT);
const audioForVideoSelectEl = $(
CONSTANTS.DOM_IDS.AUDIO_FOR_VIDEO_FORMAT_SELECT,
);
const option_audio = `<option value="${format.format_id}|${audioExt}">${audioQualityPadded} | ${audioExtPadded} | ${audioSizePadded}</option>`;
const mountSlimSelect = (domElement, optionsData) => {
if (!domElement) return;
audioSelect.innerHTML += option_audio;
audioForVideoSelect.innerHTML += option_audio;
if (domElement.slim) {
domElement.slim.destroy();
}
});
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";
domElement.slim = new SlimSelect({
select: domElement,
data: optionsData,
settings: {
showSearch: true,
contentLocation: document.body,
},
});
};
if (videoOptions.length > 0) {
mountSlimSelect(videoSelectEl, videoOptions);
}
if (audioOptions.length > 0) {
mountSlimSelect(audioSelectEl, audioOptions);
mountSlimSelect(
audioForVideoSelectEl,
JSON.parse(JSON.stringify(audioOptions)),
);
}
const audioForVideoOptions = JSON.parse(JSON.stringify(audioOptions));
const noAudioTxt = i18n.__("noAudio") || "No Audio";
audioForVideoOptions.push({
text: noAudioTxt,
value: "none|none",
html: `
<div class="modern-option-row audio-grid">
<span class="main-text">${noAudioTxt}</span>
</div>
`,
});
mountSlimSelect(audioForVideoSelectEl, audioForVideoOptions);
}
/**
@ -1501,7 +1542,7 @@ class YtDownloaderApp {
type: "text",
value: `${info.title} [${info.id}]`,
onchange: (e) => (this.state.videoInfo.title = e.target.value),
})
}),
);
document
@ -1526,7 +1567,7 @@ class YtDownloaderApp {
job.thumbnail || "../assets/images/thumb.png"
}" alt="thumbnail" class="itemIcon" crossorigin="anonymous">
<span class="itemType">${i18n.__(
job.type === "video" ? "video" : "audio"
job.type === "video" ? "video" : "audio",
)}</span>
</div>
<img src="../assets/images/close.png" class="itemClose" id="${randomId}_close">
@ -1534,17 +1575,17 @@ class YtDownloaderApp {
<div class="itemTitle">${job.title}</div>
<strong class="itemSpeed" id="${randomId}_speed"></strong>
<div id="${randomId}_prog" class="itemProgress">${i18n.__(
"preparing"
)}</div>
"preparing",
)}</div>
</div>
</div>`;
$(CONSTANTS.DOM_IDS.DOWNLOAD_LIST).insertAdjacentHTML(
"beforeend",
itemHTML
itemHTML,
);
$(`${randomId}_close`).addEventListener("click", () =>
this._cancelDownload(randomId)
this._cancelDownload(randomId),
);
}
@ -1633,7 +1674,7 @@ class YtDownloaderApp {
duration: this.state.videoInfo.duration,
})
.catch((err) =>
console.error("Error adding to history:", err)
console.error("Error adding to history:", err),
);
})
.catch((error) => console.error("Error saving to history:", error));
@ -1710,7 +1751,7 @@ class YtDownloaderApp {
$(CONSTANTS.DOM_IDS.MENU).style.opacity = "0";
setTimeout(
() => ($(CONSTANTS.DOM_IDS.MENU).style.display = "none"),
500
500,
);
}
@ -1725,7 +1766,7 @@ class YtDownloaderApp {
}
// If it's in the queue
this.state.downloadQueue = this.state.downloadQueue.filter(
(job) => job.queueId !== id
(job) => job.queueId !== id,
);
// If it has been downloaded, remove from the set
@ -1888,7 +1929,7 @@ class YtDownloaderApp {
if (isNaN(newSeconds)) {
input.value = this._formatTime(
input.id === "min-time" ? minSlider.value : maxSlider.value
input.id === "min-time" ? minSlider.value : maxSlider.value,
);
return;
}
@ -1901,7 +1942,7 @@ class YtDownloaderApp {
if (newSeconds >= parseInt(maxSlider.value)) {
newSeconds = Math.max(
minSliderVal,
parseInt(maxSlider.value) - 1
parseInt(maxSlider.value) - 1,
);
}
minSlider.value = newSeconds;
@ -1909,7 +1950,7 @@ class YtDownloaderApp {
if (newSeconds <= parseInt(minSlider.value)) {
newSeconds = Math.min(
maxSliderVal,
parseInt(minSlider.value) + 1
parseInt(minSlider.value) + 1,
);
}
maxSlider.value = newSeconds;
@ -1928,7 +1969,7 @@ class YtDownloaderApp {
if (typeof duration !== "number" || duration < 1) {
console.error(
"Invalid duration provided to setVideoLength. Must be a number greater than 0."
"Invalid duration provided to setVideoLength. Must be a number greater than 0.",
);
minSlider.max = 0;

Loading…
Cancel
Save