Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b4ff618d41 | |||
| 8a05d5ff8f |
@@ -1,12 +1,45 @@
|
|||||||
# SageTube
|
# SageTube
|
||||||
|
|
||||||
A watch-first YouTube client for the desktop — search, browse channels and playlists, follow subscriptions, and stream videos in an embedded mpv player, with full yt-dlp download capability inherited from [YTSage](https://github.com/oop7/YTSage).
|
A watch-first YouTube client for the desktop. Search, browse channels and
|
||||||
|
playlists, follow subscriptions, and stream videos in an embedded mpv player —
|
||||||
|
with the full yt-dlp download feature set inherited from
|
||||||
|
[YTSage](https://github.com/oop7/YTSage).
|
||||||
|
|
||||||
SageTube is a fork of [YTSage](https://github.com/oop7/YTSage) by [oop7](https://github.com/oop7) (MIT). The original YTSage documentation is preserved at [docs/UPSTREAM_README.md](docs/UPSTREAM_README.md).
|
## Features
|
||||||
|
|
||||||
## Status
|
**Watching**
|
||||||
|
- 📺 Embedded mpv player (Wayland/X11/Windows/macOS) with quality selector,
|
||||||
|
speed, volume, subtitles, fullscreen
|
||||||
|
- 🔍 In-app YouTube search with thumbnail card grid
|
||||||
|
- 📂 Channel browsing (Videos / Shorts / Live) and playlist views
|
||||||
|
- 🔔 Local subscriptions — no Google account needed — with an aggregated feed;
|
||||||
|
optional real account feed via browser cookies
|
||||||
|
- ⏯️ Watch history with resume positions and a persistent play queue
|
||||||
|
- ⬇️ One click from any video card into the downloader
|
||||||
|
|
||||||
Under active development. See the upstream README for the downloader feature set, which remains fully functional.
|
**Downloading** (from YTSage)
|
||||||
|
- Format table, audio extraction, subtitles, SponsorBlock, chapters,
|
||||||
|
playlists, trimming, speed limits, proxies, cookies, custom yt-dlp commands,
|
||||||
|
download history — see the [upstream README](docs/UPSTREAM_README.md)
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install .
|
||||||
|
sagetube
|
||||||
|
```
|
||||||
|
|
||||||
|
Requires **libmpv** for playback (Arch: `pacman -S mpv`) — see
|
||||||
|
[docs/BUILDING.md](docs/BUILDING.md). Without it the app works as a downloader.
|
||||||
|
|
||||||
|
## Fork notes
|
||||||
|
|
||||||
|
SageTube is a fork of [YTSage](https://github.com/oop7/YTSage) by
|
||||||
|
[oop7](https://github.com/oop7) (MIT). The internal package keeps the
|
||||||
|
`ytsage` name so upstream fixes remain mergeable — see
|
||||||
|
[docs/UPSTREAM.md](docs/UPSTREAM.md). The fork also fixes a number of
|
||||||
|
upstream bugs (unsafe partial-file cleanup, unverified binary updates,
|
||||||
|
thread-safety issues, PATH corruption on Windows — see the git log).
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
# Building & running SageTube
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- Python >= 3.10
|
||||||
|
- **libmpv** (system library; `python-mpv` binds to it at runtime)
|
||||||
|
- Arch: `sudo pacman -S mpv`
|
||||||
|
- Debian/Ubuntu: `sudo apt install libmpv2`
|
||||||
|
- Fedora: `sudo dnf install mpv-libs`
|
||||||
|
- macOS: `brew install mpv`
|
||||||
|
- Windows: place `libmpv-2.dll` next to the executable (builds from
|
||||||
|
https://github.com/shinchiro/mpv-winbuild-cmake/releases), or install mpv
|
||||||
|
and add it to PATH
|
||||||
|
- ffmpeg (for download merging; the app can auto-install it)
|
||||||
|
|
||||||
|
Without libmpv the app still runs — the Watch tab shows an install hint and
|
||||||
|
search/browse/download work normally.
|
||||||
|
|
||||||
|
## Run from source
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python -m venv .venv && source .venv/bin/activate
|
||||||
|
pip install -e .
|
||||||
|
sagetube
|
||||||
|
```
|
||||||
|
|
||||||
|
On first start the app downloads its own SHA256-verified `yt-dlp` binary and
|
||||||
|
the Deno runtime (yt-dlp's JS challenge solver) into the SageTube data dir:
|
||||||
|
|
||||||
|
- Linux: `~/.local/share/SageTube/bin/`
|
||||||
|
- macOS: `~/Library/Application Support/SageTube/bin/`
|
||||||
|
- Windows: `%LOCALAPPDATA%\SageTube\bin\`
|
||||||
|
|
||||||
|
## Notes on YouTube playback reliability
|
||||||
|
|
||||||
|
Playback resolves streams through mpv's `ytdl_hook` using the managed yt-dlp.
|
||||||
|
YouTube's CDN intermittently serves stalled streams to non-browser clients;
|
||||||
|
the player retries automatically (twice, after 25 s of no playback). Keeping
|
||||||
|
yt-dlp updated (built-in updater, stable or nightly channel) is the long-term
|
||||||
|
fix channel for extractor breakage.
|
||||||
|
|
||||||
|
## Release workflows
|
||||||
|
|
||||||
|
The `.github/workflows/` files are inherited from upstream YTSage and target
|
||||||
|
GitHub runners; they do not run on Gitea. If you set up Gitea Actions, note
|
||||||
|
that the upstream Linux/Windows workflows download appimagetool and a bundled
|
||||||
|
ffmpeg **without checksum verification** — add hash checks before reusing
|
||||||
|
them for release artifacts.
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
# Tracking upstream YTSage
|
||||||
|
|
||||||
|
SageTube keeps the full git history of [YTSage](https://github.com/oop7/YTSage)
|
||||||
|
and the internal `ytsage` package name so upstream improvements can be merged.
|
||||||
|
|
||||||
|
## Syncing
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git fetch upstream # upstream = https://github.com/oop7/YTSage.git
|
||||||
|
git merge upstream/main
|
||||||
|
```
|
||||||
|
|
||||||
|
## Expected conflict zones
|
||||||
|
|
||||||
|
Most SageTube features live in **new files** and merge cleanly. Conflicts are
|
||||||
|
likely only in:
|
||||||
|
|
||||||
|
| File | Reason |
|
||||||
|
|---|---|
|
||||||
|
| `ytsage/gui/ytsage_gui_main.py` | tab-shell edit in `init_ui` (`_setup_main_tabs`), closeEvent hook |
|
||||||
|
| `ytsage/gui/ytsage_gui_analysis.py` | delegates subprocess execution to `YtdlpClient` |
|
||||||
|
| `ytsage/utils/ytsage_constants.py` | SageTube data dirs, PATH prepend for managed binaries |
|
||||||
|
| `ytsage/utils/ytsage_config_manager.py` | atomic save, defaults merge, player/feed/advanced keys |
|
||||||
|
| `pyproject.toml` | name, version, deps (python-mpv), URLs |
|
||||||
|
| `ytsage/languages/en.json` | added sections: player, cards, main_tabs, search, watch, browse, feed |
|
||||||
|
|
||||||
|
Bug fixes made in this fork (see git log between the merge commit and the
|
||||||
|
first feature commit) may also conflict if upstream fixes them differently —
|
||||||
|
prefer whichever version is stricter about verification and thread safety.
|
||||||
|
|
||||||
|
## SageTube-only modules (never conflict)
|
||||||
|
|
||||||
|
- `ytsage/core/ytsage_client.py` — shared yt-dlp JSON invocation layer
|
||||||
|
- `ytsage/core/ytsage_mpv.py` — libmpv probe
|
||||||
|
- `ytsage/gui/ytsage_gui_player.py` — embedded mpv player
|
||||||
|
- `ytsage/gui/ytsage_gui_watch.py` / `_search.py` / `_browse.py` / `_feed.py` — tabs
|
||||||
|
- `ytsage/gui/ytsage_gui_router.py` / `_cards.py` — navigation + video cards
|
||||||
|
- `ytsage/utils/ytsage_library_manager.py` — subscriptions/history/queue DB
|
||||||
+9
-7
@@ -3,11 +3,12 @@ requires = ["setuptools"]
|
|||||||
build-backend = "setuptools.build_meta"
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "ytsage"
|
name = "sagetube"
|
||||||
version = "5.2.0"
|
version = "0.1.0"
|
||||||
description = "Modern YouTube downloader with a clean PySide6 interface."
|
description = "Watch-first YouTube client with embedded mpv playback and yt-dlp downloading. Fork of YTSage."
|
||||||
authors = [
|
authors = [
|
||||||
{ name = "oop7", email = "oop7_support@proton.me" },
|
{ name = "Houmeres", email = "admin@ecoposta.sk" },
|
||||||
|
{ name = "oop7 (YTSage upstream)", email = "oop7_support@proton.me" },
|
||||||
]
|
]
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"PySide6>=6.10.1",
|
"PySide6>=6.10.1",
|
||||||
@@ -48,12 +49,13 @@ keywords = ["youtube", "downloader", "video", "audio", "PySide6", "yt-dlp", "GUI
|
|||||||
|
|
||||||
|
|
||||||
[project.scripts]
|
[project.scripts]
|
||||||
|
sagetube = "ytsage.main:main"
|
||||||
ytsage = "ytsage.main:main"
|
ytsage = "ytsage.main:main"
|
||||||
|
|
||||||
[project.urls]
|
[project.urls]
|
||||||
Homepage = "https://github.com/oop7/YTSage"
|
Homepage = "https://git.houmeres.sk/Houmeres/SageTube"
|
||||||
Bug-Tracker = "https://github.com/oop7/YTSage/issues"
|
Bug-Tracker = "https://git.houmeres.sk/Houmeres/SageTube/issues"
|
||||||
Reddit = "https://www.reddit.com/r/NO-N_A_M_E/"
|
Upstream = "https://github.com/oop7/YTSage"
|
||||||
|
|
||||||
[tool.setuptools]
|
[tool.setuptools]
|
||||||
include-package-data = true
|
include-package-data = true
|
||||||
|
|||||||
+1
-1
@@ -4,5 +4,5 @@ YTSage - YouTube Video Downloader
|
|||||||
A modern, user-friendly YouTube video downloader built with PySide6.
|
A modern, user-friendly YouTube video downloader built with PySide6.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__version__ = "5.2.0"
|
__version__ = "0.1.0"
|
||||||
__author__ = "oop7"
|
__author__ = "oop7"
|
||||||
|
|||||||
@@ -226,24 +226,27 @@ class AboutDialog(QDialog):
|
|||||||
info_layout = QHBoxLayout()
|
info_layout = QHBoxLayout()
|
||||||
info_layout.setSpacing(15)
|
info_layout.setSpacing(15)
|
||||||
|
|
||||||
author_link = '<a href="https://github.com/oop7/" style="color: #c90000; text-decoration: none;">oop7</a>'
|
author_link = '<a href="https://git.houmeres.sk/Houmeres" style="color: #c90000; text-decoration: none;">Houmeres</a>'
|
||||||
author_label = QLabel(
|
author_label = QLabel(
|
||||||
f"{_('about.author', author=author_link)}"
|
f"{_('about.author', author=author_link)}"
|
||||||
)
|
)
|
||||||
author_label.setOpenExternalLinks(True)
|
author_label.setOpenExternalLinks(True)
|
||||||
info_layout.addWidget(author_label)
|
info_layout.addWidget(author_label)
|
||||||
|
|
||||||
repo_link = '<a href="https://github.com/oop7/YTSage/" style="color: #c90000; text-decoration: none;">YTSage</a>'
|
repo_link = '<a href="https://git.houmeres.sk/Houmeres/SageTube" style="color: #c90000; text-decoration: none;">SageTube</a>'
|
||||||
repo_label = QLabel(
|
repo_label = QLabel(
|
||||||
f"{_('about.github', repo=repo_link)}"
|
f"{_('about.github', repo=repo_link)}"
|
||||||
)
|
)
|
||||||
repo_label.setOpenExternalLinks(True)
|
repo_label.setOpenExternalLinks(True)
|
||||||
info_layout.addWidget(repo_label)
|
info_layout.addWidget(repo_label)
|
||||||
|
|
||||||
sponsor_link = '<a href="https://github.com/sponsors/oop7" style="color: #c90000; text-decoration: none;">❤️ Sponsor</a>'
|
upstream_link = (
|
||||||
sponsor_label = QLabel(sponsor_link)
|
'<a href="https://github.com/oop7/YTSage" style="color: #c90000; text-decoration: none;">'
|
||||||
sponsor_label.setOpenExternalLinks(True)
|
"Based on YTSage by oop7</a>"
|
||||||
info_layout.addWidget(sponsor_label)
|
)
|
||||||
|
upstream_label = QLabel(upstream_link)
|
||||||
|
upstream_label.setOpenExternalLinks(True)
|
||||||
|
info_layout.addWidget(upstream_label)
|
||||||
|
|
||||||
# Center the info layout
|
# Center the info layout
|
||||||
info_container = QHBoxLayout()
|
info_container = QHBoxLayout()
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
"restart_notice": "Language change will take effect after restarting the application."
|
"restart_notice": "Language change will take effect after restarting the application."
|
||||||
},
|
},
|
||||||
"app": {
|
"app": {
|
||||||
"title": "YTSage",
|
"title": "SageTube",
|
||||||
"version": "v{version}",
|
"version": "v{version}",
|
||||||
"ready": "Ready"
|
"ready": "Ready"
|
||||||
},
|
},
|
||||||
@@ -240,9 +240,9 @@
|
|||||||
"update_in_progress_message": "yt-dlp is currently updating. Please wait a moment."
|
"update_in_progress_message": "yt-dlp is currently updating. Please wait a moment."
|
||||||
},
|
},
|
||||||
"about": {
|
"about": {
|
||||||
"title": "About YTSage",
|
"title": "About SageTube",
|
||||||
"version": "Version {version}",
|
"version": "Version {version}",
|
||||||
"description": "Modern YouTube downloader with clean PySide6 interface.",
|
"description": "Watch-first YouTube client with embedded mpv playback and yt-dlp downloading. Fork of YTSage by oop7 (MIT).",
|
||||||
"author": "By: {author}",
|
"author": "By: {author}",
|
||||||
"github": "GitHub: {repo}",
|
"github": "GitHub: {repo}",
|
||||||
"system_info": "System Information",
|
"system_info": "System Information",
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ def main():
|
|||||||
try:
|
try:
|
||||||
logger.info("Starting YTSage application")
|
logger.info("Starting YTSage application")
|
||||||
app = QApplication(sys.argv)
|
app = QApplication(sys.argv)
|
||||||
|
app.setApplicationName("SageTube")
|
||||||
|
app.setDesktopFileName("sagetube")
|
||||||
|
|
||||||
window = YTSageApp() # Instantiate the main application class
|
window = YTSageApp() # Instantiate the main application class
|
||||||
window.show()
|
window.show()
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ class ConfigManager:
|
|||||||
"geo_proxy_url": None,
|
"geo_proxy_url": None,
|
||||||
"auto_update_ytdlp": True,
|
"auto_update_ytdlp": True,
|
||||||
"auto_update_frequency": "daily",
|
"auto_update_frequency": "daily",
|
||||||
"check_app_updates": True,
|
"check_app_updates": False, # fork updates come from Gitea, not the upstream PyPI package
|
||||||
"check_beta_updates": False,
|
"check_beta_updates": False,
|
||||||
"last_update_check": 0,
|
"last_update_check": 0,
|
||||||
"concurrent_fragments": 1,
|
"concurrent_fragments": 1,
|
||||||
|
|||||||
@@ -91,11 +91,11 @@ if OS_NAME == "Windows":
|
|||||||
|
|
||||||
# Always use user data directory for app data, logs, config, and binaries
|
# Always use user data directory for app data, logs, config, and binaries
|
||||||
# Even when frozen, we don't want to create these folders next to the executable
|
# Even when frozen, we don't want to create these folders next to the executable
|
||||||
APP_DIR: Path = Path(os.environ.get("LOCALAPPDATA", USER_HOME_DIR / "AppData" / "Local")) / "YTSage"
|
APP_DIR: Path = Path(os.environ.get("LOCALAPPDATA", USER_HOME_DIR / "AppData" / "Local")) / "SageTube"
|
||||||
APP_BIN_DIR: Path = APP_DIR / "bin"
|
APP_BIN_DIR: Path = APP_DIR / "bin"
|
||||||
APP_DATA_DIR: Path = APP_DIR / "data"
|
APP_DATA_DIR: Path = APP_DIR / "data"
|
||||||
APP_LOG_DIR: Path = APP_DIR / "logs"
|
APP_LOG_DIR: Path = APP_DIR / "logs"
|
||||||
APP_CONFIG_FILE: Path = APP_DATA_DIR / "ytsage_config.json"
|
APP_CONFIG_FILE: Path = APP_DATA_DIR / "sagetube_config.json"
|
||||||
APP_HISTORY_FILE: Path = APP_DATA_DIR / "ytsage_history.json"
|
APP_HISTORY_FILE: Path = APP_DATA_DIR / "ytsage_history.json"
|
||||||
APP_THUMBNAILS_DIR: Path = APP_DATA_DIR / "thumbnails"
|
APP_THUMBNAILS_DIR: Path = APP_DATA_DIR / "thumbnails"
|
||||||
|
|
||||||
@@ -110,11 +110,11 @@ elif OS_NAME == "Darwin": # macOS
|
|||||||
|
|
||||||
# Always use user data directory for app data, logs, config, and binaries
|
# Always use user data directory for app data, logs, config, and binaries
|
||||||
# Even when frozen, we don't want to create these folders next to the executable
|
# Even when frozen, we don't want to create these folders next to the executable
|
||||||
APP_DIR: Path = USER_HOME_DIR / "Library" / "Application Support" / "YTSage"
|
APP_DIR: Path = USER_HOME_DIR / "Library" / "Application Support" / "SageTube"
|
||||||
APP_BIN_DIR: Path = APP_DIR / "bin"
|
APP_BIN_DIR: Path = APP_DIR / "bin"
|
||||||
APP_DATA_DIR: Path = APP_DIR / "data"
|
APP_DATA_DIR: Path = APP_DIR / "data"
|
||||||
APP_LOG_DIR: Path = APP_DIR / "logs"
|
APP_LOG_DIR: Path = APP_DIR / "logs"
|
||||||
APP_CONFIG_FILE: Path = APP_DATA_DIR / "ytsage_config.json"
|
APP_CONFIG_FILE: Path = APP_DATA_DIR / "sagetube_config.json"
|
||||||
APP_HISTORY_FILE: Path = APP_DATA_DIR / "ytsage_history.json"
|
APP_HISTORY_FILE: Path = APP_DATA_DIR / "ytsage_history.json"
|
||||||
APP_THUMBNAILS_DIR: Path = APP_DATA_DIR / "thumbnails"
|
APP_THUMBNAILS_DIR: Path = APP_DATA_DIR / "thumbnails"
|
||||||
|
|
||||||
@@ -129,11 +129,11 @@ else: # Linux and other UNIX-like
|
|||||||
|
|
||||||
# Always use user data directory for app data, logs, config, and binaries
|
# Always use user data directory for app data, logs, config, and binaries
|
||||||
# Even when frozen, we don't want to create these folders next to the executable
|
# Even when frozen, we don't want to create these folders next to the executable
|
||||||
APP_DIR: Path = USER_HOME_DIR / ".local" / "share" / "YTSage"
|
APP_DIR: Path = USER_HOME_DIR / ".local" / "share" / "SageTube"
|
||||||
APP_BIN_DIR: Path = APP_DIR / "bin"
|
APP_BIN_DIR: Path = APP_DIR / "bin"
|
||||||
APP_DATA_DIR: Path = APP_DIR / "data"
|
APP_DATA_DIR: Path = APP_DIR / "data"
|
||||||
APP_LOG_DIR: Path = APP_DIR / "logs"
|
APP_LOG_DIR: Path = APP_DIR / "logs"
|
||||||
APP_CONFIG_FILE: Path = APP_DATA_DIR / "ytsage_config.json"
|
APP_CONFIG_FILE: Path = APP_DATA_DIR / "sagetube_config.json"
|
||||||
APP_HISTORY_FILE: Path = APP_DATA_DIR / "ytsage_history.json"
|
APP_HISTORY_FILE: Path = APP_DATA_DIR / "ytsage_history.json"
|
||||||
APP_THUMBNAILS_DIR: Path = APP_DATA_DIR / "thumbnails"
|
APP_THUMBNAILS_DIR: Path = APP_DATA_DIR / "thumbnails"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user