WatchPage now records every played video in watch_history, saves the
playback position every 5 seconds and on stop/end (completed at >=95%),
and seeks back on replay when player.resume is "auto" - resume kicks in
between 30 seconds and 95% of the duration. The play queue persists
across restarts and reorders/removals write through immediately; the
main window's closeEvent flushes position and queue and releases libmpv.
PlayerPanel gains an automatic stall-retry: YouTube's CDN intermittently
serves stalled streams to non-browser clients (reproduced ~1/3 of
attempts headless with identical code), and a reload re-resolves onto a
healthy node - two retries after 25s of no playback, then a user-facing
error.
ytsage_constants now prepends the managed-binaries dir to PATH so
yt-dlp subprocesses (including mpv's ytdl_hook) can find the managed
Deno runtime - previously nothing exported APP_BIN_DIR, so the deno
integration silently never worked.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
New ytsage/utils/ytsage_library_manager.py: sagetube_library.db
(separate from the upstream download-history DB) with WAL from day one,
holding subscriptions, cached feed_items, watch_history with resume
positions, and the persisted play_queue. Watch history and queue tables
are wired up by the next commit.
FeedPage:
- Local mode: FeedRefreshWorker refreshes each subscribed channel
sequentially (feed.per_channel_items, default 15) and the grid fills
incrementally per channel; results are cached so the feed is
populated instantly on startup.
- Account mode: fetches youtube.com/feed/subscriptions with the user's
browser cookies - the real logged-in feed; the option is enabled only
while cookies are active and errors surface as a status banner.
- Sidebar lists subscriptions (double-click opens the channel in
Browse; context menu unsubscribes). Browse's Subscribe button now
toggles subscription state through the Feed page.
Verified live: subscribe -> refresh -> 15 videos cached in SQLite and
rendered as cards.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
BrowsePage accepts pasted or routed channel/playlist URLs:
- Channels get Videos / Shorts / Live sub-tabs mapped to the channel's
/videos, /shorts and /streams listings, lazily fetched 24 at a time
with -I range pagination; channel title and id resolve from a cheap
-I 1:1 metadata fetch. Subscribe emits subscribeRequested for the
Feed page to wire up.
- Playlists get a single grid with Play all (bulk-enqueues into the
Watch queue) and a Download button that deep-links the playlist into
the Downloads tab.
Workers are parented to their pages and fetch errors surface as status
text instead of crashing (verified against a channel with no videos
tab). fetch_flat_info gains an items="1:1" limiter so metadata probes
no longer enumerate whole channels.
Verified live: kurzgesagt channel (24 cards, title+id resolved) and a
17-video playlist with Play-all queueing.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The single-page downloader layout becomes the Downloads tab of a
SmoothTabWidget with Watch / Search / Feed / Browse / Downloads pages.
The init_ui edit is deliberately small (the old central widget is now
self.download_page); all new behavior lives in new modules:
- ytsage_gui_router.py: AppRouter signal hub (playVideo, queueVideo,
downloadVideo, openChannel, openPlaylist). A card's Download button
deep-links into the Downloads tab with the URL prefilled and analysis
started automatically.
- ytsage_gui_cards.py: VideoCard (thumbnail with disk cache under
APP_THUMBNAILS_DIR, title/channel/duration, Play/Queue/Download
actions, double-click to play) and VideoCardGrid (responsive grid,
Load more pagination).
- ytsage_gui_watch.py: WatchPage hosting the mpv PlayerPanel and a
drag-reorderable play queue with auto-advance on end of file.
- ytsage_gui_search.py: SearchPage running YtdlpClient.search off the
GUI thread with Load more pagination.
- Browse and Feed pages are placeholders, implemented next.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
New ytsage/gui/ytsage_gui_player.py:
- MpvRenderWidget hosts libmpv through MpvRenderContext in a
QOpenGLWidget - works on native Wayland where wid-embedding cannot -
with all mpv-thread callbacks marshalled to the GUI thread via queued
signals only.
- PlayerPanel adds transport controls: play/pause, seek slider, time
display, quality selector (caps ytdl-format height and reloads in
place), speed, volume (persisted), subtitle toggle, fullscreen
(reparent to top-level window), and Space/F/Escape keys.
- Playback resolves watch URLs through mpv's ytdl_hook pointed at the
app-managed SHA256-verified yt-dlp binary (script-opts
ytdl_hook-ytdl_path), inheriting cookies and proxy settings via
ytdl-raw-options - stream freshness, DASH muxing and nsig handling
stay in yt-dlp's hands.
New ytsage/core/ytsage_mpv.py probes libmpv availability; without it
the Watch UI shows a per-OS install hint and everything else works.
python-mpv added to dependencies (libmpv itself is a system package).
Config gains player.* and feed.* defaults.
Verified on Wayland: real YouTube video streams with position/duration
signals flowing and no thread-safety errors.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add two new localization keys across multiple language files: "remember_settings" (cookie dialog) and "private_video" (download/error message) to inform users about remembering cookie settings and private videos. Also fix minor formatting in en.json (split combined line for open_folder/save_playlist/reset). Updated locale files: ar, de, en, es, fr, hi, id, it, ja, pl, pt, ru, tr, zh.
Update English locale with new UI strings for playlist saving and default selections. Adds 'save_playlist' and related save messages (save_as, save_success_title, saved_successfully, save_error_title, no_videos_to_save, save_error_msg), adds default selection settings keys (defaults_settings, default_video_quality, default_subtitle_language, defaults_help), and documents the %(playlist_index)s variable in filename_format_help. Also minor formatting cleanup in the file.
Add new i18n keys "concurrent_fragments" and "concurrent_fragments_help" to multiple language JSON files (ar, de, en, es, fr, hi, id, it, ja, pl, pt, ru, tr, zh). These expose a UI option for the number of concurrent connections per download with a help string (default: 1, warns about throttling/temporary blocks). Strings were added in English across locales and may need translation.
Add "tab_general", "tab_format", and "tab_file" entries to multiple ytsage/languages/*.json files to provide localized labels for the UI tabs (General, Format, File). Also include a minor formatting/newline fix in en.json.
Introduce an "audio_normalization" option across the app and implement normalization for audio-only downloads. Updates include: add config default (ConfigManager), new language strings, settings UI checkbox + help text and logic to auto-enable force-audio-format when normalization is enabled, and persist the setting. Pass the setting from main app to DownloadThread, and in the downloader force re-encoding to mp3 when necessary and append --postprocessor-args ExtractAudio:-af loudnorm=... to apply EBU R128 normalization. This ensures normalization works reliably (avoids ffmpeg stream-copy errors) and keeps the behavior scoped to ExtractAudio postprocessor.
Introduce a new "check_app_updates" configuration (default: true) to allow users to enable/disable automatic YTSage update checks. Adds a styled checkbox to the Updater tab with getter, loads the setting (defaults to enabled for older configs), and saves it from the custom options dialog. The main update check now respects this setting and will skip checking when disabled. Also include English localization entries for the new UI strings.
Introduce a "generic_mode" option to allow validating/downloading from non-YouTube sites and wire it through the UI, config, and validation logic. Key changes:
- Add generic_mode default to ConfigManager and persist setting from DownloadSettingsDialog (checkbox + help text).
- Extend validate_video_url to accept a generic_mode flag and allow any http/https URL with a domain when enabled; pass this flag from Analysis and Download flows.
- Update YTSageApp to load/save generic_mode, update URL placeholder and settings tooltip behavior, and refresh tooltip when settings change.
- Improve robustness in FormatTableMixin: handle None/incorrect types for format_note, abr, resolution, ext, and codec values to avoid type errors and ensure consistent display.
- Add localization entries for generic mode, placeholder, and related help text across multiple language files and update the in-app default localization strings.
These changes enable broader site support via yt-dlp while hardening UI format handling and keeping user settings persistent.
Add open_logs and logs_tooltip localization entries across multiple language JSON files (ar, de, en, es, fr, hi, id, it, ja, pl, pt, ru, tr, zh) so the About/System Info UI can expose an action to open the application logs folder. Also add an "about" section to ytsage/utils/ytsage_localization.py with default entries (open_logs, logs_tooltip, refresh). This wires the logs-folder UI text into the localization system.
Introduce an opt-in beta updates feature: add a styled "Receive Beta Updates" checkbox in the Updater UI with i18n strings, load/save logic in the custom options dialog, and a default config key (check_beta_updates=false).
Add _fetch_github_beta_version to the update thread to query GitHub releases (selecting the highest tag) and emit update_available for newer beta builds. When beta checking is enabled, the thread will perform the GitHub beta check and return early to avoid conflicting with the PyPI check. This enables users to opt into preview releases safely.
Add a Reset button next to the filename format input so users can restore the default pattern. The filename input and reset button are grouped in a QHBoxLayout; the button is fixed width (70) and sets the field back to the default %(title)s_%(resolution)s.%(ext)s. Also add the corresponding i18n key (buttons.reset -> "Reset") to en.json.
Introduce a new filename_format setting and UI to control yt-dlp output templates. ConfigManager now includes a default filename_format (%(title)s_%(resolution)s.%(ext)s). The DownloadSettingsDialog exposes a text input and help text for the format and saves the value to ConfigManager. DownloadThread now accepts a filename_format argument and uses it when building output templates for single videos and playlists. YTSageApp reads the config and passes the filename format into the download thread. Added corresponding English language strings.
Update English UI strings in ytsage/languages/en.json to remove progress percentages and the repeated "Analyzing" prefix, replacing them with concise action messages (e.g., "Extracting basic info...", "Loading thumbnail..."). This makes status messages shorter and more consistent across regular and ytdlp-related entries without changing functionality.