From 3d778246ca869a4defee2b0992471df93a95c200 Mon Sep 17 00:00:00 2001 From: xpltd Date: Thu, 19 Mar 2026 05:29:41 -0500 Subject: [PATCH] Best quality format, password UX, mobile columns MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Best quality format: - Synthetic 'bestvideo+bestaudio/best' entry added at top of format list when the best separate video stream exceeds the best pre-muxed format. Shows as 'Best quality (1920x1080)' in Video+Audio group. - YouTube typically only has 360p pre-muxed but 1080p+ as separate streams — users can now select full quality with auto-merge. - Only appears when there's actually a quality advantage vs pre-muxed. Password change UX: - Enter key on confirm password field submits the change - Auto-logout 1.5s after successful password change - User sees '✓ Password changed' before being redirected home Mobile table: - Status column hidden on mobile (<640px) alongside Progress - Only Name + Actions columns shown — clean two-column layout - Removed mobile-specific status badge font tweaks (column gone) --- backend/app/services/download.py | 41 +++++++++++++++++++++++ frontend/src/components/AdminPanel.vue | 11 +++--- frontend/src/components/DownloadTable.vue | 9 ++--- 3 files changed, 50 insertions(+), 11 deletions(-) diff --git a/backend/app/services/download.py b/backend/app/services/download.py index 4828b3b..e4aba92 100644 --- a/backend/app/services/download.py +++ b/backend/app/services/download.py @@ -229,6 +229,47 @@ class DownloadService: key=lambda fi: _parse_resolution_height(fi.resolution), reverse=True, ) + + # Add synthetic "best quality" entries at the top. + # yt-dlp can merge separate video+audio streams for best quality, + # but those don't appear as pre-muxed formats in the format list. + best_video = None + best_audio = None + for f in formats_raw: + vcodec = f.get("vcodec", "none") + acodec = f.get("acodec", "none") + height = f.get("height") or 0 + if vcodec and vcodec != "none" and height > 0: + if best_video is None or height > (best_video.get("height") or 0): + best_video = f + if acodec and acodec != "none" and (vcodec == "none" or not vcodec): + if best_audio is None: + best_audio = f + + if best_video: + bv_height = best_video.get("height", 0) + bv_res = f"{best_video.get('width', '?')}x{bv_height}" + # Only add if the best separate video exceeds the best pre-muxed + best_premuxed_height = 0 + for f in formats_raw: + vc = f.get("vcodec", "none") + ac = f.get("acodec", "none") + if vc and vc != "none" and ac and ac != "none": + h = f.get("height") or 0 + if h > best_premuxed_height: + best_premuxed_height = h + + if bv_height > best_premuxed_height: + result.insert(0, FormatInfo( + format_id="bestvideo+bestaudio/best", + ext=best_video.get("ext", "webm"), + resolution=bv_res, + codec=best_video.get("vcodec"), + format_note=f"Best quality ({bv_res})", + vcodec=best_video.get("vcodec"), + acodec="merged", + )) + return result async def cancel(self, job_id: str) -> None: diff --git a/frontend/src/components/AdminPanel.vue b/frontend/src/components/AdminPanel.vue index c50d1f9..2358726 100644 --- a/frontend/src/components/AdminPanel.vue +++ b/frontend/src/components/AdminPanel.vue @@ -95,13 +95,15 @@ async function changePassword() { }) if (res.ok) { - // Update stored credentials to use new password - store.password = newPassword.value + passwordChanged.value = true currentPassword.value = '' newPassword.value = '' confirmPassword.value = '' - passwordChanged.value = true - setTimeout(() => { passwordChanged.value = false }, 3000) + // Auto-logout after 1.5s so user sees the success message + setTimeout(() => { + store.logout() + router.push('/') + }, 1500) } else { const data = await res.json() passwordError.value = data.detail || 'Failed to change password' @@ -335,6 +337,7 @@ function formatFilesize(bytes: number | null): string { placeholder="Confirm new password" autocomplete="new-password" class="settings-input" + @keydown.enter="changePassword" />
diff --git a/frontend/src/components/DownloadTable.vue b/frontend/src/components/DownloadTable.vue index 0c541d5..cf7ed2c 100644 --- a/frontend/src/components/DownloadTable.vue +++ b/frontend/src/components/DownloadTable.vue @@ -515,7 +515,7 @@ async function clearJob(jobId: string): Promise { opacity: 0; } -/* Mobile: hide speed, ETA, and progress columns */ +/* Mobile: hide speed, ETA, progress, and status columns */ @media (max-width: 639px) { .hide-mobile { display: none; @@ -527,7 +527,7 @@ async function clearJob(jobId: string): Promise { } .col-status { - width: 75px; + display: none; } .col-progress { @@ -553,11 +553,6 @@ async function clearJob(jobId: string): Promise { .action-group { gap: 2px; } - - .status-badge { - font-size: 0.625rem; - padding: 1px 4px; - } } /* Toast notification */