Make it possible to pin specific yt-dlp version

This commit is contained in:
arabcoders 2025-08-09 17:27:00 +03:00
parent d4e837a805
commit 96149f5e2d
8 changed files with 101 additions and 32 deletions

View file

@ -41,7 +41,7 @@ ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONFAULTHANDLER=1
RUN mkdir /config /downloads && ln -snf /usr/share/zoneinfo/${TZ} /etc/localtime && echo ${TZ} > /etc/timezone && \
apk add --update --no-cache bash mkvtoolnix patch aria2 coreutils curl shadow sqlite tzdata libmagic ffmpeg rtmpdump fribidi && \
apk add --update --no-cache bash mkvtoolnix patch aria2 coreutils curl shadow sqlite tzdata libmagic ffmpeg rtmpdump fribidi git && \
useradd -u ${USER_ID:-1000} -U -d /app -s /bin/bash app && \
rm -rf /var/cache/apk/*

10
FAQ.md
View file

@ -39,3 +39,13 @@ whenever the link includes a playlist id.
> [!NOTE]
> You can also do the same via advanced options `Command options for yt-dlp` field, but presets are more convenient.
# Install specific yt-dlp version?
You can force specific version of `yt-dlp` by setting the `YTP_YTDLP_VERSION` environment variable for example
```env
YTP_YTDLP_VERSION=2025.07.21 or master or nightly
Then restart the container to apply the changes.

View file

@ -291,7 +291,6 @@ Certain configuration values can be set via environment variables, using the `-e
| YTP_TEMP_PATH | Path to where tmp files are stored. | `/tmp` |
| YTP_TEMP_KEEP | Whether to keep the Individual video temp directory or remove it | `false` |
| YTP_KEEP_ARCHIVE | Keep history of downloaded videos | `true` |
| YTP_YTDL_DEBUG | Whether to turn debug logging for the internal `yt-dlp` package | `false` |
| YTP_HOST | Which IP address to bind to | `0.0.0.0` |
| YTP_PORT | Which port to bind to | `8081` |
| YTP_LOG_LEVEL | Log level | `info` |
@ -312,6 +311,8 @@ Certain configuration values can be set via environment variables, using the `-e
| YTP_BROWSER_ENABLED | Whether to enable the file browser | `false` |
| YTP_BROWSER_CONTROL_ENABLED | Whether to enable the file browser actions | `false` |
| YTP_YTDLP_AUTO_UPDATE | Whether to enable the auto update for yt-dlp | `true` |
| YTP_YTDLP_DEBUG | Whether to turn debug logging for the internal `yt-dlp` package | `false` |
| YTP_YTDLP_VERSION | The version of yt-dlp to use. Defaults to latest version | `empty string` |
| YTP_BASE_PATH | Set this if you are serving YTPTube from sub-folder | `/` |
| YTP_PREVENT_LIVE_PREMIERE | Prevents the initial youtube premiere stream from being downloaded | `false` |
| YTP_TASKS_HANDLER_TIMER | The cron expression for the tasks handler timer | `15 */1 * * *` |

View file

@ -111,7 +111,7 @@ class Download:
self.info = info
self.id = info._id
self.debug = bool(config.debug)
self.debug_ytdl = bool(config.ytdl_debug)
self.debug_ytdl = bool(config.ytdlp_debug)
self.cancelled = False
self.tmpfilename = None
self.status_queue = None

View file

@ -676,7 +676,7 @@ class DownloadQueue(metaclass=Singleton):
extract_info,
config=yt_conf,
url=item.url,
debug=bool(self.config.ytdl_debug),
debug=bool(self.config.ytdlp_debug),
no_archive=False,
follow_redirect=True,
),

View file

@ -49,10 +49,18 @@ class PackageInstaller:
sys.path.insert(0, str(self.user_site))
def action(self, pkg: str, upgrade: bool = False):
current_version = self._get_installed_version(pkg)
version: str | None = None
if "==" in pkg:
pkg, version = pkg.split("==", 1)
if upgrade and current_version:
latest_version = self._get_latest_version(pkg)
current_version: str | None = self._get_installed_version(pkg)
if current_version and version and self.compare_versions(current_version, version):
LOG.info(f"'{pkg}' is already installed with the specified version ({version}). Skipping installation.")
return
if upgrade and current_version and not version:
latest_version: str | None = self._get_latest_version(pkg)
if latest_version and parse_version(current_version) >= parse_version(latest_version):
LOG.info(f"'{pkg}' is already the latest version ({current_version}). Skipping upgrade.")
return
@ -62,7 +70,7 @@ class PackageInstaller:
else:
LOG.info(f"'{pkg}' is not installed. Installing...")
self._install_pkg(pkg)
self._install_pkg(pkg, version=version)
def _get_installed_version(self, pkg: str) -> str | None:
try:
@ -82,23 +90,37 @@ class PackageInstaller:
LOG.warning(f"Error while querying PyPI for '{pkg}': {e}")
return None
def _install_pkg(self, pkg: str):
subprocess.run(
[
sys.executable,
"-m",
"pip",
"install",
"--upgrade",
"--disable-pip-version-check",
"--target",
str(self.user_site),
"--no-warn-script-location",
pkg,
],
check=True,
creationflags=subprocess.CREATE_NO_WINDOW if os.name == "nt" else 0,
)
def _install_pkg(self, pkg: str, version: str | None = None):
cmd: list[str] = [
sys.executable,
"-m",
"pip",
"install",
"--no-warn-script-location",
"--upgrade",
"--target",
str(self.user_site),
]
if version:
if "nightly" == version and pkg == "yt_dlp":
cmd.extend(["--pre", "yt-dlp[default]"])
elif "master" == version and pkg == "yt_dlp":
cmd.append("git+https://github.com/yt-dlp/yt-dlp.git@master")
else:
cmd.append(version if str(version).startswith("git+") else f"{pkg}=={version}")
else:
cmd.extend(["--disable-pip-version-check", pkg])
try:
subprocess.run(
cmd,
check=True,
creationflags=subprocess.CREATE_NO_WINDOW if os.name == "nt" else 0,
)
except subprocess.CalledProcessError as e:
LOG.error(f"Failed to install package '{pkg}'. Error: {e}")
raise
def check(self, pkgs: Packages):
"""
@ -118,3 +140,28 @@ class PackageInstaller:
except Exception as e:
LOG.error(f"Failed to install or upgrade package '{package}'. Error message: {e!s}")
LOG.exception(e)
def compare_versions(self, current: str, target: str) -> bool:
"""
Compare versions, handling yt-dlp format where pip uses 2025.7.21 but actual is 2025.07.21
Returns True if versions match, False otherwise
"""
if current == target:
return True
# Handle yt-dlp version format differences
current_parts = current.split(".")
target_parts = target.split(".")
if len(current_parts) == len(target_parts):
# Normalize parts by zero-padding single digits
current_normalized: list[str] = [
part.zfill(2) if part.isdigit() and len(part) == 1 else part for part in current_parts
]
target_normalized: list[str] = [
part.zfill(2) if part.isdigit() and len(part) == 1 else part for part in target_parts
]
if current_normalized == target_normalized:
return True
return False

View file

@ -46,9 +46,6 @@ class Config:
keep_archive: bool = True
"""Keep the download archive file."""
ytdl_debug: bool = False
"""Enable yt-dlp debugging."""
host: str = "0.0.0.0"
"""The host to bind the server to."""
@ -178,6 +175,12 @@ class Config:
_ytdlp_cli_mutable: str = ""
"""The command line options to use for yt-dlp."""
ytdlp_version: str | None = None
"""The version of yt-dlp to use, if not set, the latest version will be used."""
ytdlp_debug: bool = False
"""Enable yt-dlp debugging."""
is_native: bool = False
"Is the application running in webview."
@ -228,7 +231,7 @@ class Config:
_boolean_vars: tuple = (
"keep_archive",
"ytdl_debug",
"ytdlp_debug",
"debug",
"temp_keep",
"access_log",
@ -531,11 +534,11 @@ class Config:
if not data.get("keep_archive", False) and ytdlp_args.get("download_archive", None):
data["keep_archive"] = True
data["ytdlp_version"] = Config.ytdlp_version()
data["ytdlp_version"] = Config._ytdlp_version()
return data
@staticmethod
def ytdlp_version():
def _ytdlp_version():
try:
from yt_dlp.version import __version__ as YTDLP_VERSION

View file

@ -21,6 +21,8 @@ class Upgrader:
config_path: Path = Path(__file__).parent.parent / "var" / "config"
if env_path := os.environ.get("YTP_CONFIG_PATH", None):
config_path = Path(env_path)
else:
os.environ.update({"YTP_CONFIG_PATH": str(config_path)})
if config_path.exists():
envFile: Path = config_path / ".env"
@ -33,11 +35,17 @@ class Upgrader:
pkg_installer = PackageInstaller()
ytdlp_auto_update: bool = os.environ.get("YTP_YTDLP_AUTO_UPDATE", "true").strip().lower() == "true"
ytdlp_version: str | None = os.environ.get("YTP_YTDLP_VERSION", "").strip() or None
if ytdlp_auto_update:
try:
LOG.info("Checking for newer versions of 'yt-dlp' package.")
pkg_installer.action(pkg="yt_dlp", upgrade=True)
pkg_name = "yt_dlp"
if ytdlp_version:
LOG.info(f"Using specified version '{ytdlp_version}' for '{pkg_name}'.")
pkg_name += f"=={ytdlp_version}"
pkg_installer.action(pkg=pkg_name, upgrade=True)
from yt_dlp.version import __version__ as YTDLP_VERSION