The internal ytsage package name is kept deliberately - renaming it
would touch every file and destroy the ability to merge upstream
YTSage changes.
- pyproject: distribution name sagetube v0.1.0, sagetube entrypoint
(ytsage alias retained), URLs point at the Gitea repo with an
Upstream link to YTSage.
- Data dirs move to SageTube/ (fresh fork, fresh state) and the config
file becomes sagetube_config.json; QApplication name and window
title read SageTube.
- About dialog credits Houmeres and links "Based on YTSage by oop7".
- App self-update check against the upstream PyPI package is disabled
by default; yt-dlp/deno/ffmpeg update flows are unaffected.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Format table: a missing acodec was treated as "has audio", skipping
the +bestaudio merge and producing silent videos for extractors that
omit the field.
- Progress bar: separate video/audio stream downloads each reported
0-100%, making the bar jump backwards; per-phase scaling now maps the
two streams onto 0-50/50-100.
- Custom commands: parse with shlex (quoted arguments with spaces were
shredded by str.split), keep POSIX mode off on Windows so backslash
paths survive, hide the console window like every other call site,
close the stdout pipe, and support cancellation of a running command.
- Settings dialog: _("settings", "error_saving", ...) passed two
positional args to the i18n helper, raising TypeError inside the
except handler instead of showing the intended error dialog.
- ffmpeg on Windows: Path(os.getenv("LOCALAPPDATA")) crashed with
TypeError when the variable is unset; fall back to the standard
AppData/Local location.
- Version cache: cached path (str) was compared against a Path, so the
cache never hit and every version query spawned a subprocess.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The yt-dlp stable/nightly channel switcher mutated QLabel/QRadioButton
state directly from a raw threading.Thread, which is undefined behavior
in Qt. The worker now only runs the subprocess and emits a signal; the
connected slot applies all widget updates on the GUI thread via Qt's
queued delivery.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Set the default generic_mode to True in the config manager and update GUI initialization to distinguish between a missing config and an explicit False. Replace usages of `ConfigManager.get(... ) or False` with a None check so that an explicit False value is respected. Changes made in ytsage/utils/ytsage_config_manager.py and GUI initializers in ytsage/gui/ytsage_gui_main.py and ytsage/gui/ytsage_gui_dialogs/ytsage_dialogs_settings.py.
Introduce a "Remember Cookie Settings" QCheckBox to the cookies UI (label uses gettext key "cookies.remember_settings"). The control defaults to True when no prior setting exists and applies custom styling for the indicator and text. Its toggled signal is wired to ConfigManager.set("cookie_remember", checked) so the preference is saved immediately (avoiding dependence on an Apply button), and the value is also saved when the dialog persists other cookie settings. Uses the "cookie_remember" config key for storage.
Replace entry.get('file_size', 0) with entry.get('file_size') or 0 in HistoryDelegate (ytsage_dialogs_history.py) to treat None as 0. This prevents TypeError when comparing None > 0 and ensures file size formatting/display only occurs for positive sizes.
Introduce a "Default Selection Settings" group in the Download Settings dialog to let users specify a default video resolution (height) and default subtitle language(s). Adds labeled QLineEdit inputs with placeholders and explanatory help text, uses localized strings for labels, and persists values to ConfigManager under keys "default_video_quality" and "default_subtitle_language" when saving.
Add video id to the default output filename template across the downloader and settings UI. The template was changed from "%(title)s_%(resolution)s.%(ext)s" to "%(title)s_%(resolution)s_[%(id)s].%(ext)s" in ytsage_downloader.py and ytsage_dialogs_settings.py (including placeholder and reset behavior). This helps avoid filename collisions and makes downloaded files easier to identify.
Add a "Concurrent Connections" section to DownloadSettingsDialog with a QComboBox (1–20) and help text; initialize from ConfigManager("concurrent_fragments"). Introduce get_concurrent_fragments() to return the chosen value (with a safe fallback to 1) and persist the setting on dialog accept. Uses localized labels and minor styling for the help label.
Reorganize the Download Settings dialog into a SmoothTabWidget with three tabs (General, Format, File) and move existing group sections into corresponding tab layouts. Import QWidget and SmoothTabWidget, increase dialog minimum width from 450 to 550, and add stretches for better layout spacing. Overhaul the dialog stylesheet to restyle tabs, frames, group boxes, checkboxes, radio buttons, combo boxes and item views (borders, paddings, colors, radii, hover/selected states). Also style OK/Cancel buttons to match the Custom Options dialog. These changes improve the dialog's visual consistency and UX while preserving existing settings controls and behavior.
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.
Update AboutDialog link markup to remove explicit font-size and font-weight styles and to unify link color. The author and repo links no longer set font-size, and the sponsor link color was changed from #ea4aaa to #c90000 (and its font-size/weight removed) for consistent appearance across links.
Add a styled '❤️ Sponsor' GitHub Sponsors link to the About dialog (ytsage_dialogs_base.py). The change creates a QLabel with an external href to the sponsor page, enables openExternalLinks, and appends it to the info layout so the About dialog displays a sponsor call-to-action.
Normalize PyPI's version string (e.g. "2026.2.21") to the zero-padded GitHub tag format (e.g. "2026.02.21") before building the update target. The code splits a three-part version and pads the month/day components with two digits so stable@<tag> matches yt-dlp GitHub tagging.
Strip the 🔄 emoji from the localized loading string to avoid rendering issues and use the cleaned text for the QLabel. Tweak the label styling by changing the color from #cccccc to #888888 and removing the italic font-style for a more consistent appearance.
Add an 'Open Logs' button to the About dialog that opens the app log folder via QDesktopServices/QUrl, ensures the log directory exists, and logs warnings/errors. Include minimal styling and a handler (open_logs_folder) that uses APP_LOG_DIR and the logger. Also update settings dialog imports to include QUrl/QDesktopServices and APP_LOG_DIR. Simplify the bug report template to instruct users to open logs from the About dialog and attach ytsage.log / ytsage_error.log.
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.
Bump help text font-size from 10px to 11px in DownloadSettingsDialog for improved readability. Updated style for force format, audio format, and filename format help labels in ytsage/gui/ytsage_gui_dialogs/ytsage_dialogs_settings.py.
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.
Introduce check_ytdlp_deno_integration() in ytsage_yt_dlp.py to detect Deno integration by running `yt-dlp --verbose` and scanning debug output for JS runtimes containing "deno" (with timeout and logging/fallback).
Add SystemInfoThread (QThread) in ytsage_dialogs_base.py to collect yt-dlp, ffmpeg and deno presence, versions, paths, cache timestamps and integration status in the background, emitting the gathered info via info_ready. AboutDialog now starts the thread in update_system_info() and populates the UI asynchronously via _populate_system_info(), showing a small "+ yt-dlp" integration indicator next to the Deno status when detected. Refactors usage of version/cache lookups to use the thread-provided info dictionary.
Add streaming progress support to Deno upgrades and hook it into the GUI. upgrade_deno() now accepts an optional progress_callback and uses subprocess.Popen to read stdout line-by-line (line-buffered, utf-8, errors replaced), collecting output and invoking the callback as lines arrive. Added DenoUpdateThread (QThread) with finished/progress/error signals and replaced the previous background thread usage in the updater UI with this QThread. GUI slots strip ANSI escapes, truncate long messages, and update status text; buttons are re-enabled on finish/error. Improved logging and error handling for upgrade failures.
Introduce SmoothTabWidget and a FadingStackedWidget to provide cross-fade transitions between tabs. Added ytsage/gui/ytsage_smooth_tab_widget.py which implements a QTabBar + FadingStackedWidget (default 300ms fade) and overlay-based fade logic. Updated ytsage/gui/ytsage_gui_dialogs/ytsage_dialogs_custom.py to use SmoothTabWidget instead of QTabWidget and adjusted the stylesheet selector to target the new content frame (#tabContent) so existing styles continue to apply.
Moved all files from src/gui/ to ytsage/gui/ and updated import statements to use relative imports within the new package structure. This improves modularity and prepares the codebase for distribution as a proper Python package.