From 3db117d85fcd83890b65acee36973c88e4756a4b Mon Sep 17 00:00:00 2001 From: ed Date: Mon, 12 Aug 2024 22:48:53 +0000 Subject: [PATCH] list status of optional dependencies --- README.md | 1 + copyparty/__main__.py | 1 + copyparty/mtag.py | 18 +++++++---- copyparty/pwhash.py | 10 +++++++ copyparty/svchub.py | 70 +++++++++++++++++++++++++++++++++++++++++-- 5 files changed, 92 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index ec24ff97..1f460b0a 100644 --- a/README.md +++ b/README.md @@ -2076,6 +2076,7 @@ set any of the following environment variables to disable its associated optiona | env-var | what it does | | -------------------- | ------------ | +| `PRTY_NO_ARGON2` | disable argon2-cffi password hashing | | `PRTY_NO_CFSSL` | never attempt to generate self-signed certificates using [cfssl](https://github.com/cloudflare/cfssl) | | `PRTY_NO_FFMPEG` | **audio transcoding** goes byebye, **thumbnailing** must be handled by Pillow/libvips | | `PRTY_NO_FFPROBE` | **audio transcoding** goes byebye, **thumbnailing** must be handled by Pillow/libvips, **metadata-scanning** must be handled by mutagen | diff --git a/copyparty/__main__.py b/copyparty/__main__.py index cdbf87f6..a609fce1 100644 --- a/copyparty/__main__.py +++ b/copyparty/__main__.py @@ -1388,6 +1388,7 @@ def add_debug(ap): ap2 = ap.add_argument_group('debug options') ap2.add_argument("--vc", action="store_true", help="verbose config file parser (explain config)") ap2.add_argument("--cgen", action="store_true", help="generate config file from current config (best-effort; probably buggy)") + ap2.add_argument("--deps", action="store_true", help="list information about detected optional dependencies") if hasattr(select, "poll"): ap2.add_argument("--no-poll", action="store_true", help="kernel-bug workaround: disable poll; use select instead (limits max num clients to ~700)") ap2.add_argument("--no-sendfile", action="store_true", help="kernel-bug workaround: disable sendfile; do a safe and slow read-send-loop instead") diff --git a/copyparty/mtag.py b/copyparty/mtag.py index bf2719ad..59a5ab16 100644 --- a/copyparty/mtag.py +++ b/copyparty/mtag.py @@ -32,6 +32,17 @@ if True: # pylint: disable=using-constant-test from .util import NamedLogger, RootLogger +try: + if os.environ.get("PRTY_NO_MUTAGEN"): + raise Exception() + + from mutagen import version # noqa: F401 + + HAVE_MUTAGEN = True +except: + HAVE_MUTAGEN = False + + def have_ff(scmd: str) -> bool: if ANYWIN: scmd += ".exe" @@ -336,12 +347,7 @@ class MTag(object): if self.backend == "mutagen": self._get = self.get_mutagen - try: - if os.environ.get("PRTY_NO_MUTAGEN"): - raise Exception() - - from mutagen import version # noqa: F401 - except: + if not HAVE_MUTAGEN: self.log("could not load Mutagen, trying FFprobe instead", c=3) self.backend = "ffprobe" diff --git a/copyparty/pwhash.py b/copyparty/pwhash.py index f8e5c9fa..c613c910 100644 --- a/copyparty/pwhash.py +++ b/copyparty/pwhash.py @@ -4,11 +4,21 @@ from __future__ import print_function, unicode_literals import argparse import base64 import hashlib +import os import sys import threading from .__init__ import unicode +try: + if os.environ.get("PRTY_NO_ARGON2"): + raise Exception() + + HAVE_ARGON2 = True + from argon2 import __version__ as argon2ver +except: + HAVE_ARGON2 = False + class PWHash(object): def __init__(self, args: argparse.Namespace): diff --git a/copyparty/svchub.py b/copyparty/svchub.py index e0082d9a..ff74779f 100644 --- a/copyparty/svchub.py +++ b/copyparty/svchub.py @@ -31,15 +31,27 @@ if True: # pylint: disable=using-constant-test from .__init__ import ANYWIN, EXE, MACOS, PY2, TYPE_CHECKING, E, EnvParams, unicode from .authsrv import BAD_CFG, AuthSrv from .cert import ensure_cert -from .mtag import HAVE_FFMPEG, HAVE_FFPROBE +from .mtag import HAVE_FFMPEG, HAVE_FFPROBE, HAVE_MUTAGEN +from .pwhash import HAVE_ARGON2 from .tcpsrv import TcpSrv -from .th_srv import HAVE_PIL, HAVE_VIPS, HAVE_WEBP, ThumbSrv +from .th_srv import ( + HAVE_AVIF, + HAVE_FFMPEG, + HAVE_FFPROBE, + HAVE_HEIF, + HAVE_PIL, + HAVE_VIPS, + HAVE_WEBP, + ThumbSrv, +) from .up2k import Up2k from .util import ( DEF_EXP, DEF_MTE, DEF_MTH, FFMPEG_URL, + HAVE_PSUTIL, + HAVE_SQLITE3, UTC, VERSIONS, Daemon, @@ -235,6 +247,8 @@ class SvcHub(object): self.up2k = Up2k(self) + self._feature_test() + decs = {k: 1 for k in self.args.th_dec.split(",")} if not HAVE_VIPS: decs.pop("vips", None) @@ -423,6 +437,58 @@ class SvcHub(object): Daemon(self.sd_notify, "sd-notify") + def _feature_test(self) -> None: + fok = [] + fng = [] + t_ff = "transcode audio, create spectrograms, video thumbnails" + to_check = [ + (HAVE_SQLITE3, "sqlite", "file and media indexing"), + (HAVE_PIL, "pillow", "image thumbnails (plenty fast)"), + (HAVE_VIPS, "vips", "image thumbnails (faster, eats more ram)"), + (HAVE_WEBP, "pillow-webp", "create thumbnails as webp files"), + (HAVE_FFMPEG, "ffmpeg", t_ff + ", good-but-slow image thumbnails"), + (HAVE_FFPROBE, "ffprobe", t_ff + ", read audio/media tags"), + (HAVE_MUTAGEN, "mutagen", "read audio tags (ffprobe is better but slower)"), + (HAVE_ARGON2, "argon2", "secure password hashing (advanced users only)"), + (HAVE_HEIF, "pillow-heif", "read .heif images with pillow (rarely useful)"), + (HAVE_AVIF, "pillow-avif", "read .avif images with pillow (rarely useful)"), + ] + if ANYWIN: + to_check += [ + (HAVE_PSUTIL, "psutil", "improved plugin cleanup (rarely useful)") + ] + + verbose = self.args.deps + if verbose: + self.log("dependencies", "") + + for have, feat, what in to_check: + lst = fok if have else fng + lst.append((feat, what)) + if verbose: + zi = 2 if have else 5 + sgot = "found" if have else "missing" + t = "%7s: %s \033[36m(%s)" + self.log("dependencies", t % (sgot, feat, what), zi) + + if verbose: + self.log("dependencies", "") + return + + sok = ", ".join(x[0] for x in fok) + sng = ", ".join(x[0] for x in fng) + + t = "" + if sok: + t += "OK: \033[32m" + sok + if sng: + if t: + t += ", " + t += "\033[0mNG: \033[35m" + sng + + t += "\033[0m, see --deps" + self.log("dependencies", t, 6) + def _check_env(self) -> None: try: files = os.listdir(E.cfg)