Make it possible to pin specific yt-dlp version
This commit is contained in:
parent
d4e837a805
commit
96149f5e2d
8 changed files with 101 additions and 32 deletions
|
|
@ -41,7 +41,7 @@ ENV PYTHONDONTWRITEBYTECODE=1
|
||||||
ENV PYTHONFAULTHANDLER=1
|
ENV PYTHONFAULTHANDLER=1
|
||||||
|
|
||||||
RUN mkdir /config /downloads && ln -snf /usr/share/zoneinfo/${TZ} /etc/localtime && echo ${TZ} > /etc/timezone && \
|
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 && \
|
useradd -u ${USER_ID:-1000} -U -d /app -s /bin/bash app && \
|
||||||
rm -rf /var/cache/apk/*
|
rm -rf /var/cache/apk/*
|
||||||
|
|
||||||
|
|
|
||||||
10
FAQ.md
10
FAQ.md
|
|
@ -39,3 +39,13 @@ whenever the link includes a playlist id.
|
||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
> You can also do the same via advanced options `Command options for yt-dlp` field, but presets are more convenient.
|
> 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.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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_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_TEMP_KEEP | Whether to keep the Individual video temp directory or remove it | `false` |
|
||||||
| YTP_KEEP_ARCHIVE | Keep history of downloaded videos | `true` |
|
| 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_HOST | Which IP address to bind to | `0.0.0.0` |
|
||||||
| YTP_PORT | Which port to bind to | `8081` |
|
| YTP_PORT | Which port to bind to | `8081` |
|
||||||
| YTP_LOG_LEVEL | Log level | `info` |
|
| 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_ENABLED | Whether to enable the file browser | `false` |
|
||||||
| YTP_BROWSER_CONTROL_ENABLED | Whether to enable the file browser actions | `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_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_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_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 * * *` |
|
| YTP_TASKS_HANDLER_TIMER | The cron expression for the tasks handler timer | `15 */1 * * *` |
|
||||||
|
|
|
||||||
|
|
@ -111,7 +111,7 @@ class Download:
|
||||||
self.info = info
|
self.info = info
|
||||||
self.id = info._id
|
self.id = info._id
|
||||||
self.debug = bool(config.debug)
|
self.debug = bool(config.debug)
|
||||||
self.debug_ytdl = bool(config.ytdl_debug)
|
self.debug_ytdl = bool(config.ytdlp_debug)
|
||||||
self.cancelled = False
|
self.cancelled = False
|
||||||
self.tmpfilename = None
|
self.tmpfilename = None
|
||||||
self.status_queue = None
|
self.status_queue = None
|
||||||
|
|
|
||||||
|
|
@ -676,7 +676,7 @@ class DownloadQueue(metaclass=Singleton):
|
||||||
extract_info,
|
extract_info,
|
||||||
config=yt_conf,
|
config=yt_conf,
|
||||||
url=item.url,
|
url=item.url,
|
||||||
debug=bool(self.config.ytdl_debug),
|
debug=bool(self.config.ytdlp_debug),
|
||||||
no_archive=False,
|
no_archive=False,
|
||||||
follow_redirect=True,
|
follow_redirect=True,
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -49,10 +49,18 @@ class PackageInstaller:
|
||||||
sys.path.insert(0, str(self.user_site))
|
sys.path.insert(0, str(self.user_site))
|
||||||
|
|
||||||
def action(self, pkg: str, upgrade: bool = False):
|
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:
|
current_version: str | None = self._get_installed_version(pkg)
|
||||||
latest_version = self._get_latest_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):
|
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.")
|
LOG.info(f"'{pkg}' is already the latest version ({current_version}). Skipping upgrade.")
|
||||||
return
|
return
|
||||||
|
|
@ -62,7 +70,7 @@ class PackageInstaller:
|
||||||
else:
|
else:
|
||||||
LOG.info(f"'{pkg}' is not installed. Installing...")
|
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:
|
def _get_installed_version(self, pkg: str) -> str | None:
|
||||||
try:
|
try:
|
||||||
|
|
@ -82,23 +90,37 @@ class PackageInstaller:
|
||||||
LOG.warning(f"Error while querying PyPI for '{pkg}': {e}")
|
LOG.warning(f"Error while querying PyPI for '{pkg}': {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _install_pkg(self, pkg: str):
|
def _install_pkg(self, pkg: str, version: str | None = None):
|
||||||
subprocess.run(
|
cmd: list[str] = [
|
||||||
[
|
sys.executable,
|
||||||
sys.executable,
|
"-m",
|
||||||
"-m",
|
"pip",
|
||||||
"pip",
|
"install",
|
||||||
"install",
|
"--no-warn-script-location",
|
||||||
"--upgrade",
|
"--upgrade",
|
||||||
"--disable-pip-version-check",
|
"--target",
|
||||||
"--target",
|
str(self.user_site),
|
||||||
str(self.user_site),
|
]
|
||||||
"--no-warn-script-location",
|
|
||||||
pkg,
|
if version:
|
||||||
],
|
if "nightly" == version and pkg == "yt_dlp":
|
||||||
check=True,
|
cmd.extend(["--pre", "yt-dlp[default]"])
|
||||||
creationflags=subprocess.CREATE_NO_WINDOW if os.name == "nt" else 0,
|
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):
|
def check(self, pkgs: Packages):
|
||||||
"""
|
"""
|
||||||
|
|
@ -118,3 +140,28 @@ class PackageInstaller:
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.error(f"Failed to install or upgrade package '{package}'. Error message: {e!s}")
|
LOG.error(f"Failed to install or upgrade package '{package}'. Error message: {e!s}")
|
||||||
LOG.exception(e)
|
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
|
||||||
|
|
|
||||||
|
|
@ -46,9 +46,6 @@ class Config:
|
||||||
keep_archive: bool = True
|
keep_archive: bool = True
|
||||||
"""Keep the download archive file."""
|
"""Keep the download archive file."""
|
||||||
|
|
||||||
ytdl_debug: bool = False
|
|
||||||
"""Enable yt-dlp debugging."""
|
|
||||||
|
|
||||||
host: str = "0.0.0.0"
|
host: str = "0.0.0.0"
|
||||||
"""The host to bind the server to."""
|
"""The host to bind the server to."""
|
||||||
|
|
||||||
|
|
@ -178,6 +175,12 @@ class Config:
|
||||||
_ytdlp_cli_mutable: str = ""
|
_ytdlp_cli_mutable: str = ""
|
||||||
"""The command line options to use for yt-dlp."""
|
"""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_native: bool = False
|
||||||
"Is the application running in webview."
|
"Is the application running in webview."
|
||||||
|
|
||||||
|
|
@ -228,7 +231,7 @@ class Config:
|
||||||
|
|
||||||
_boolean_vars: tuple = (
|
_boolean_vars: tuple = (
|
||||||
"keep_archive",
|
"keep_archive",
|
||||||
"ytdl_debug",
|
"ytdlp_debug",
|
||||||
"debug",
|
"debug",
|
||||||
"temp_keep",
|
"temp_keep",
|
||||||
"access_log",
|
"access_log",
|
||||||
|
|
@ -531,11 +534,11 @@ class Config:
|
||||||
if not data.get("keep_archive", False) and ytdlp_args.get("download_archive", None):
|
if not data.get("keep_archive", False) and ytdlp_args.get("download_archive", None):
|
||||||
data["keep_archive"] = True
|
data["keep_archive"] = True
|
||||||
|
|
||||||
data["ytdlp_version"] = Config.ytdlp_version()
|
data["ytdlp_version"] = Config._ytdlp_version()
|
||||||
return data
|
return data
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def ytdlp_version():
|
def _ytdlp_version():
|
||||||
try:
|
try:
|
||||||
from yt_dlp.version import __version__ as YTDLP_VERSION
|
from yt_dlp.version import __version__ as YTDLP_VERSION
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,8 @@ class Upgrader:
|
||||||
config_path: Path = Path(__file__).parent.parent / "var" / "config"
|
config_path: Path = Path(__file__).parent.parent / "var" / "config"
|
||||||
if env_path := os.environ.get("YTP_CONFIG_PATH", None):
|
if env_path := os.environ.get("YTP_CONFIG_PATH", None):
|
||||||
config_path = Path(env_path)
|
config_path = Path(env_path)
|
||||||
|
else:
|
||||||
|
os.environ.update({"YTP_CONFIG_PATH": str(config_path)})
|
||||||
|
|
||||||
if config_path.exists():
|
if config_path.exists():
|
||||||
envFile: Path = config_path / ".env"
|
envFile: Path = config_path / ".env"
|
||||||
|
|
@ -33,11 +35,17 @@ class Upgrader:
|
||||||
pkg_installer = PackageInstaller()
|
pkg_installer = PackageInstaller()
|
||||||
|
|
||||||
ytdlp_auto_update: bool = os.environ.get("YTP_YTDLP_AUTO_UPDATE", "true").strip().lower() == "true"
|
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:
|
if ytdlp_auto_update:
|
||||||
try:
|
try:
|
||||||
LOG.info("Checking for newer versions of 'yt-dlp' package.")
|
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
|
from yt_dlp.version import __version__ as YTDLP_VERSION
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue