Commit Graph

111 Commits

Author SHA1 Message Date
Homer 2420091a4e Check for SageTube updates, not YTSage's
The update check asked PyPI for the `ytsage` package's version, asked GitHub
for oop7/YTSage's release notes, and pointed the download button at upstream's
releases. None of that describes this program. Being reminded to install
YTSage was the visible symptom; the cause was that the check had never been
repointed when the fork was made.

It now reads SageTube's own releases from git.houmeres.sk. Gitea's release API
is shaped like GitHub's, so the dialog and the caller are unchanged -- the
class keeps its name and signal signature, and only its body moved out to
core/ytsage_app_update.py, which is fork-owned and will not conflict on the
next merge from upstream.

Upstream's inherited tags end in `b` (v5.3.0b and earlier). packaging reads
that as a beta marker, so they sort below v5.4.0 and a stable instance cannot
be handed one. Tag parsing is defensive anyway: one unparseable tag must not
take the whole check down with it.

Also: the check is rate-limited to once a day rather than every start, the
dialog gained a "Skip this version" that survives a restart, the thread is now
joined on close, and the About dialog says SageTube. The three binary updaters
-- yt-dlp, Deno, ffmpeg -- legitimately track their own upstreams and are
deliberately untouched; ytsage_app_update's docstring says so, because "update"
is an overloaded word in this codebase.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09 00:36:14 +02:00
Homer 089b144626 Correct the version and make the config merge deeply
The package version had been left at the scaffolded 0.1.0 while the released
tag was v5.4.0. Nothing in the code chose 0.1.0 -- it was npm-init's equivalent,
never updated -- and it made every version comparison meaningless: the window
title, the About dialog and the update check all read it.

ConfigManager merged a stored config over the defaults with dict.update(),
which is shallow. A config written by an older build carries partial "player"
and "feed" objects, and a shallow update replaces the whole nested default with
the partial one, so keys added since came back missing. The `or 15` and
`or "auto"` fallbacks at the call sites were load-bearing because of it. The
merge is now recursive, and keys present only in the stored file are kept so a
downgrade cannot destroy settings.

Configs now carry a config_version. A file written before 5.4.0 -- including
one inherited from an upstream YTSage install -- has its stored
check_app_updates cleared once, because that setting used to point at PyPI's
`ytsage` package and oop7/YTSage's releases, neither of which is this program.

The settings tab read a missing check_app_updates as enabled and persisted that
reading on OK, so merely opening Custom Options turned the checker back on.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09 00:31:56 +02:00
Jaroslav Beneš 8a05d5ff8f Rebrand user-visible surfaces to SageTube
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>
2026-07-25 02:45:58 +02:00
Jaroslav Beneš a8ebe89cc1 Wire watch history, resume positions and persistent queue
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>
2026-07-25 02:42:38 +02:00
Jaroslav Beneš 3afe96a8cd Add local subscriptions and the Feed tab
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>
2026-07-25 01:56:12 +02:00
Jaroslav Beneš 8e935c6116 Implement channel and playlist browsing
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>
2026-07-25 01:53:44 +02:00
Jaroslav Beneš 16be4b4afa Restructure main window into a watch-first tab shell
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>
2026-07-25 01:48:39 +02:00
Jaroslav Beneš f886125857 Add embedded mpv player (render API + QOpenGLWidget)
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>
2026-07-25 01:44:25 +02:00
Jaroslav Beneš 8dd8a3c1ad Add YtdlpClient: shared JSON invocation layer for yt-dlp
Every metadata call site previously built its own command list. The new
ytsage/core/ytsage_client.py centralizes binary resolution (managed,
verified binary only), cookie/proxy args from ConfigManager (with
session-state overrides), utf-8 output handling, process-group-safe
timeouts, and a short-TTL cache for flat/search results.

Provides fetch_video_info, fetch_flat_info, fetch_flat_entries (with
-I range pagination), search (ytsearchN:), and fetch_account_feed
(youtube.com/feed/subscriptions with cookies) plus a generic YtdlpWorker
QThread. AnalysisThread now delegates its subprocess execution to the
shared runner; its signal surface is unchanged.

Groundwork for the SageTube watch/search/browse/feed features.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 01:39:20 +02:00
Jaroslav Beneš 1c885e3fc5 Fix low-severity robustness issues
- show_error_dialog() aborted when QApplication construction itself was
  the failure; fall back to stderr so the real error is visible.
- Auto-update QThread cleanup dropped the last Python reference while
  run() could still be unwinding ("QThread: Destroyed while thread is
  still running"); defer destruction to deleteLater on finished.
- macOS ffmpeg install no longer curl|bash-es the Homebrew bootstrap
  script unattended; it now asks the user to install Homebrew
  themselves and fails cleanly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 01:36:29 +02:00
Jaroslav Beneš 4cc48ae98f Fix medium-severity defects across download, formats and tooling
- 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>
2026-07-25 01:35:23 +02:00
Jaroslav Beneš 288d30ad8b Make config persistence robust and history DB concurrency-safe
- Config saves are atomic (temp file + fsync + os.replace); a crash or
  power loss mid-write no longer truncates the file, which previously
  caused a silent reset to defaults on next launch.
- Stored config is merged over a deep copy of the defaults: keys added
  in newer versions resolve properly instead of returning None, and the
  nested cached_versions dict is no longer shared with (and mutated on)
  the class-level default dict.
- History SQLite connection enables WAL and a 5s busy timeout so the
  download thread can record entries while the history dialog reads
  without "database is locked" errors.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 01:31:49 +02:00
Jaroslav Beneš ebb9422591 Harden yt-dlp binary resolution and analysis subprocess handling
- get_yt_dlp_path() no longer implicitly executes a bare "yt-dlp" from
  PATH (on Windows that lookup includes the CWD, so a planted binary
  in a writable directory could be run). A system yt-dlp is used only
  behind the explicit advanced.allow_system_ytdlp config opt-in, and
  then always as a which()-resolved absolute path. Analysis and
  download refuse to exec the not-installed sentinel.
- Analysis subprocesses now run in their own session and the whole
  process group is killed on timeout, so deno grandchildren no longer
  leak; partial stderr is preserved and logged, and output decoding is
  pinned to utf-8 with replacement (Windows locale codecs crashed on
  non-UTF8 titles).
- Flat-playlist entries are filtered for None (private/deleted first
  video no longer breaks analysis).
- update_yt_dlp() normalizes the sentinel to Path, unbreaking the pip
  fallback path that crashed on str.exists().

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 01:30:08 +02:00
Jaroslav Beneš 852804ee5c Actually suspend the download process group on pause
Pause only stopped the stdout reader loop; yt-dlp kept transferring at
full rate until the pipe buffer filled, consuming bandwidth while the
UI claimed the download was paused. Send SIGSTOP/SIGCONT to the whole
process group (yt-dlp and its ffmpeg children) on Unix. Windows has no
equivalent signal; the limitation is documented in the helper.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 01:26:44 +02:00
Jaroslav Beneš 5064b38315 Marshal channel-switch UI updates back to the GUI thread
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>
2026-07-25 01:26:00 +02:00
Jaroslav Beneš dff14ec3e8 Enforce SHA256 verification on every binary install path
Three gaps allowed an unverified binary to reach a trusted location:

- The ffmpeg ZIP fallback logged a warning on checksum mismatch and
  installed anyway (the 7z path already aborted). Abort instead.
- The yt-dlp auto-update path downloaded and renamed the binary over
  the verified one with no checksum at all. Verify against the official
  SHA2-256SUMS like the first-install path, and use atomic os.replace.
- The yt-dlp first install streamed the download directly to the
  trusted path and only verified afterwards; a crash in between left an
  unverified executable to be run on next launch. Download to .part and
  os.replace only after verification.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 01:24:35 +02:00
Jaroslav Beneš b40deb0d5f Write ffmpeg dir to user PATH via registry instead of setx
setx truncates values at 1024 characters, and the old code fed it the
merged process PATH (system + user), permanently duplicating every
system entry into the user hive and silently dropping anything past the
limit. Read and rewrite only the HKCU Environment Path value with
winreg, preserving REG_EXPAND_SZ, and broadcast WM_SETTINGCHANGE.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 01:23:04 +02:00
Jaroslav Beneš 202dd9eaee Fix UnboundLocalError in manual yt-dlp binary selection
Unpacking getOpenFileName() into a variable named _ made _ local to
select_ytdlp_path(), so the i18n _() calls earlier in the function
raised UnboundLocalError before the file dialog could open, breaking
manual binary selection entirely.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 01:22:26 +02:00
Jaroslav Beneš a1d1f87b65 Scope cleanup routines to files created by the current download
cleanup_partial_files() deleted every *.part and *.fNNN.* file in the
whole download directory, and cleanup_subtitle_files() deleted any new
.vtt/.srt under it recursively - including files belonging to other
applications (e.g. a browser's own .part downloads in ~/Downloads).

Track every destination path yt-dlp reports for this download and
restrict both cleanup passes to those files and their .part/.ytdl
siblings.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 01:22:05 +02:00
oop7 4bc43174b6 Release YTSage 5.2.0
Bump the project and package version from 5.2.0b to 5.2.0.
2026-07-01 13:42:10 +03:00
oop7 bc3e7fd4fd Persist speed limit settings to config
Speed limit settings (value and unit) are now loaded from config on app initialization and saved to config when updated. This ensures the user's speed limit preferences persist across app restarts.
2026-07-01 12:33:12 +03:00
oop7 dac298a524 Show audio codec details in format table
Highlight AC3/EAC3 surround-sound formats in the GUI format table and include channel/bitrate details where available. Also relax the audio-only filter so formats without filesize can still be shown.
2026-07-01 11:46:30 +03:00
oop7 72f9098a70 Persist and restore main window state
Restore window geometry/state on startup and save them on exit. Adds YTSageApp._load_window_state() and calls it during UI init; saves Base64-encoded geometry/state via ConfigManager when closing. Adds default config keys "window_geometry" and "window_state". Includes error handling and debug logging to avoid failures if stored values are invalid.
2026-06-14 20:19:40 +03:00
oop7 8c9966000e Improve save_path handling and use ConfigManager
Normalize path input to string, ensure parent directories are created (mkdir with parents=True), and check writability using the normalized path. Replace manual JSON file write with ConfigManager.set("download_path", ...) and add import for ConfigManager. Adjust logging to use logger.error for failures and replace logger.exception where appropriate. These changes improve robustness when saving the download path and centralize config persistence.
2026-06-14 17:59:48 +03:00
oop7 ec18262bba Bump version to 5.2.0b
Update package version from 5.0.10b to 5.2.0b in pyproject.toml and ytsage/__init__.py to reflect the new beta release and keep metadata in sync.
2026-06-14 17:54:03 +03:00
oop7 45d3cdbad4 Default generic_mode to True; preserve explicit False
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.
2026-06-14 17:49:36 +03:00
oop7 9fd09cd0d0 Handle playlist items as completed on partial errors
For playlist downloads, treat an item as completed if a file was created even when the downloader returned a non-zero code. Emit 100% progress for such cases and update the status to "download.completed (with some errors)" when return_code != 0, otherwise keep the normal completed status. This avoids reporting false failures for playlist items that produced output despite errors.
2026-05-14 13:47:05 +03:00
oop7 99ba5d6d8a Ignore playlist errors; normalize version cache
When downloading playlists, add --ignore-errors and --no-abort-on-error so a single failure won't stop the entire playlist download. Also normalize values stored in the version cache: coerce version_info to a string (or empty string) and path to a string or None to ensure consistent types when reading/writing cached tool metadata.
2026-05-14 12:37:56 +03:00
oop7 ba07a3300c Bump version to 5.0.10b
Update project version from 5.0.9b to 5.0.10b in pyproject.toml and ytsage/__init__.py to prepare a new beta release.
2026-05-01 22:51:06 +03:00
oop7 0d5cbb2df1 Write auto-subs only if selected; show count
Downloader: add detection for "Auto-generated" subtitle selection and only append --write-auto-subs when an auto-generated subtitle is explicitly chosen (prevents always enabling the flag when any subtitles are requested). GUI: update subtitle label to display the number of selected subtitles when >0, falling back to the zero-selected message otherwise. Small UX and behavior fixes in DownloadThread and AnalysisMixin.
2026-05-01 16:49:58 +03:00
oop7 abef0d8a6d Strip playlist_index placeholder for single files
When building the output template for single-file downloads, remove any playlist-specific preamble (e.g. '%(playlist_index)s - ') from filename_part. Adds a regex-based substitution to clean up leftover playlist_index placeholders so single downloads don't get playlist-style prefixes in their filenames.
2026-05-01 16:29:28 +03:00
oop7 abec69fb84 Include index and formatted durations in exports
Enhance playlist export output for Text and CSV formats. Text files now include a numeric index, a title fallback ("Video N"), and a human-readable duration ([H:MM:SS] or [MM:SS]) when available before the URL. CSV export adds a "Playlist Index" column and writes a formatted title with index and duration. Duration parsing is protected against non-integer values. M3U and JSON export behavior unchanged.
2026-05-01 14:23:29 +03:00
oop7 d2f96baf63 Bump version to 5.0.9b
Update package version from 5.0.7b to 5.0.9b in pyproject.toml and ytsage/__init__.py to reflect the new release.
2026-04-28 13:37:31 +03:00
oop7 e78b634bee Add remember_settings and private_video keys
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.
2026-04-28 13:31:07 +03:00
oop7 8dc0991619 Persist cookie settings across sessions
Restore cookie configuration on startup when previously active and the user opted to remember them. If config indicates a file source and the path exists, set cookie_file_path; if browser source, set browser_cookies_option. Treat missing "cookie_remember" as true by default. If the user did not opt to remember cookies, reset cookie_active to false. Added logging for restored settings and initialization state.
2026-04-28 13:30:13 +03:00
oop7 4ae78b830d Add 'Remember Cookie Settings' checkbox
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.
2026-04-28 13:29:43 +03:00
oop7 5bbecf1e1d Detect private/sign-in yt-dlp errors
Update AnalysisThread error handling to detect yt-dlp stderr containing "Private video" or "Sign in". For those cases, emit a localized errors.private_video message and log a specific private-video error; otherwise preserve the existing generic ytdlp_failed behavior. Playlist visibility signals and early return remain unchanged.
2026-04-28 13:29:26 +03:00
oop7 fe0f8241ff Improve playlist format selection and resolution
When downloading playlists, avoid reusing a specific format_id from the first item (which can fail for subsequent videos). For audio-only playlists, use yt-dlp's bestaudio fallback; for video playlists, respect a provided resolution by extracting the height and applying -S res:<height>, otherwise fall back to bestvideo+bestaudio/best. Invalid resolution strings are caught and logged, and revert to the dynamic best-quality fallback.
2026-04-28 12:55:46 +03:00
oop7 bb461898e4 Update translations across multiple locales
Update and improve translations for multiple language files (ar, de, es, fr, hi, id, it, ja, pl, pt, ru, tr, zh). Key changes include localized strings for playlist saving ("Save Playlist As" -> localized), save/result/error messages, concurrent connections and help text, default selection/settings (video quality and subtitle language), and various wording/typo fixes. Also add/translate FFmpeg installation and yt-dlp setup messages in Turkish and Chinese. No functional code changes.
2026-04-25 17:42:36 +03:00
oop7 a2e11eec06 Update French translations
Translate several UI strings in ytsage/languages/fr.json from English to French: update `save_playlist`, `filter_playlist_placeholder`, and a group of settings strings (`concurrent_fragments`, `concurrent_fragments_help`, `defaults_settings`, `default_video_quality`, `default_subtitle_language`, `defaults_help`) to provide correct French localization and improve consistency.
2026-04-25 17:40:49 +03:00
oop7 9856dbf301 Update Spanish and Hindi translations
Improve localization by updating translations in ytsage/languages/es.json and ytsage/languages/hi.json. Spanish fixes provide full translations for interface help text, restart notice, playlist/save labels, dialog placeholders, and playlist save messages. Hindi adds translated strings for concurrent connections, related help text, and default selection settings for video quality and subtitles.
2026-04-25 17:40:24 +03:00
oop7 3d06c4dba6 Update German and Hindi translations
Translate several UI strings in ytsage/languages for German (de.json) and Hindi (hi.json). Changes include localized labels for saving playlists and filtering videos, dialog placeholders, and various settings text (e.g. concurrent connections and default selection help in German). Hindi updates add translated playlist save dialog messages and related UI strings.
2026-04-25 17:39:52 +03:00
oop7 d8c3ce8fa1 Add new i18n keys to language files
Add new localization keys across multiple ytsage language JSONs. Common addition: "filter_playlist_placeholder" added to most languages. Spanish, Turkish and Chinese receive several new strings for custom commands, proxy settings, FFmpeg installation messages, history loading, time/placeholders and validation messages. Also ensure files end with newline. Files modified: ar, de, es, fr, hi, id, it, ja, pl, pt, ru, tr, zh.
2026-04-25 10:58:43 +03:00
oop7 d19ac96ff7 Bump package version to 5.0.7b
Update project version in pyproject.toml and ytsage/__init__.py from 5.0.5b to 5.0.7b to mark the new release.
2026-04-25 10:54:08 +03:00
oop7 c9217fccb7 Handle None file_size in history delegate
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.
2026-04-25 10:40:38 +03:00
oop7 57f5450725 Add save_playlist key to language files
Add a new "save_playlist" entry (placeholder "Save Playlist As") to multiple locale JSON files (ar, de, es, fr, hi, id, it, ja, pl, pt, ru, tr, zh) to provide a translation key for saving playlists. Also adds the required trailing commas when inserting the new entries.
2026-04-25 10:39:00 +03:00
oop7 405ae07cac Add i18n entries for defaults and playlist save
Add new localization keys across multiple languages to support default selection settings and playlist saving UI. New keys include defaults_settings, default_video_quality, default_subtitle_language, defaults_help, and playlist save messages (save_as, save_success_title, saved_successfully, save_error_title, no_videos_to_save, save_error_msg). Files updated: ar, de, es, fr, hi, id, it, ja, pl, pt, ru, tr, zh.
2026-04-25 10:37:40 +03:00
oop7 3b6ac7ce81 Add playlist save and default selection strings
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.
2026-04-25 10:33:10 +03:00
oop7 cf4c4e37c8 Add 'Save Playlist' button and save dialog
Introduce a Save Playlist UI flow: import QFileDialog, group playlist action buttons into a horizontal layout, add a new "Save Playlist As" button with styling and visibility control, and connect its click to a new save_playlist_to_file method. The new method prompts for a save location and supports saving playlist_entries in TXT, M3U, CSV, or JSON formats, shows success/error message boxes, and logs exceptions. Also wire the existing playlist visibility signal to control the save button and adjust layout placement for the playlist buttons.
2026-04-25 10:32:59 +03:00
oop7 fb209351f2 Auto-select default video quality in formats
After populating the format table, read default_video_quality from ConfigManager and attempt to auto-select a matching video format (by resolution/height). If a matching video format is found it is checked and handle_checkbox_click is invoked to trigger selection side-effects; if not, the first (highest-quality) format is selected as a fallback. Existing visibility filtering via filter_formats is still applied afterward.
2026-04-25 10:32:49 +03:00