From 856fada3c7cfa9168753934da3ac699de5d0a79b Mon Sep 17 00:00:00 2001 From: ed Date: Fri, 17 Jul 2026 22:18:17 +0000 Subject: [PATCH] th-covers volflag; `no` disables folderthumbs --- README.md | 1 + copyparty/__main__.py | 2 +- copyparty/authsrv.py | 9 ++++++++- copyparty/cfg.py | 2 ++ copyparty/httpcli.py | 7 ++++--- copyparty/svchub.py | 8 -------- copyparty/up2k.py | 38 +++++++++----------------------------- tests/util.py | 5 +---- 8 files changed, 26 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index a0e6e678..3aef6ef6 100644 --- a/README.md +++ b/README.md @@ -782,6 +782,7 @@ audio files are converted into spectrograms using FFmpeg unless you `--no-athumb images with the following names (see `--th-covers`) become the thumbnail of the folder they're in: `folder.png`, `folder.jpg`, `cover.png`, `cover.jpg` * the order is significant, so if both `cover.png` and `folder.jpg` exist in a folder, it will pick the first matching `--th-covers` entry (`folder.jpg`) * and, if you enable [file indexing](#file-indexing), it will also try those names as dotfiles (`.folder.jpg` and so), and then fallback on the first picture in the folder (if it has any pictures at all) +* disable folderthumbs with `--th-covers no` enabling `multiselect` lets you click files to select them, and then shift-click another file for range-select * `multiselect` is mostly intended for phones/tablets, but the `sel` option in the `[⚙️] settings` tab is better suited for desktop use, allowing selection by CTRL-clicking and range-selection with SHIFT-click, all without affecting regular clicking diff --git a/copyparty/__main__.py b/copyparty/__main__.py index b8cf9df5..de2ce706 100644 --- a/copyparty/__main__.py +++ b/copyparty/__main__.py @@ -1807,7 +1807,7 @@ def add_thumbnail(ap): ap2.add_argument("--th-maxage", metavar="SEC", type=int, default=604800, help="max folder age -- folders which haven't been poked for longer than \033[33m--th-poke\033[0m seconds will get deleted every \033[33m--th-clean\033[0m seconds") ap2.add_argument("--th-pregen", metavar="F,F", type=u, default="", help="pregenerate thumbnails on startup; \033[33mF,F\033[0m is comma-separated list of formats; example: [\033[32mj,jf,w,w3,wf,wf3,x,xf\033[0m] NOTE: remember to set \033[33m--th-maxage 123456789\033[0m (volflag=th_pregen)") ap2.add_argument("--th-pre-rl", metavar="SEC", type=int, default=30, help="while pregen is running, ratelimit the thumbnailer logger to one message every \033[33mSEC\033[0m seconds (only works with \033[33m-j1\033[0m); set 0 to disable ratelimit") - ap2.add_argument("--th-covers", metavar="N,N", type=u, default="folder.png,folder.jpg,cover.png,cover.jpg", help="folder thumbnails to stat/look for; enabling \033[33m-e2d\033[0m will make these case-insensitive, and try them as dotfiles (.folder.jpg), and also automatically select thumbnails for all folders that contain pics, even if none match this pattern") + ap2.add_argument("--th-covers", metavar="N,N", type=u, default="folder.png,folder.jpg,cover.png,cover.jpg", help="folder thumbnails to stat/look for; enabling \033[33m-e2d\033[0m will make these case-insensitive, and try them as dotfiles (.folder.jpg), and also automatically select thumbnails for all folders that contain pics, even if none match this pattern (volflag=th_covers)") ap2.add_argument("--th-spec-p", metavar="N", type=u, default=1, help="for music, do spectrograms or embedded coverart? [\033[32m0\033[0m]=only-art, [\033[32m1\033[0m]=prefer-art, [\033[32m2\033[0m]=only-spec") ap2.add_argument("--th-spec-fl", action="store_true", help="generate spectrograms with logarithmic frequency scale instead of linear") # https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html diff --git a/copyparty/authsrv.py b/copyparty/authsrv.py index fd0845c1..8d89b19d 100644 --- a/copyparty/authsrv.py +++ b/copyparty/authsrv.py @@ -2612,6 +2612,13 @@ class AuthSrv(object): t = "WARNING: volume [/%s]: invalid value specified for ext-th: %s" self.log(t % (vol.vpath, etv), 3) + zsl = [x.strip() for x in vol.flags["th_covers"].split(",")] + zsl = [x for x in zsl if x not in ("", "no")] + vol.flags["th_coversl"] = zsl + vol.flags["th_coversd"] = zsl + ["." + x for x in zsl] + vol.flags["th_covers_set"] = set(vol.flags["th_coversl"]) + vol.flags["th_coversd_set"] = set(vol.flags["th_coversd"]) + zsl = [x.strip() for x in vol.flags["rw_edit"].split(",")] zsl = [x for x in zsl if x] vol.flags["rw_edit"] = ",".join(zsl) @@ -3789,7 +3796,7 @@ class AuthSrv(object): "", ] - csv = set("i p th_covers zm_on zm_off zs_on zs_off".split()) + csv = set("i p zm_on zm_off zs_on zs_off".split()) zs = "c ihead ohead mtm mtp on403 on404 xac xad xar xau xiu xban xbc xbd xbr xbu xm" lst = set(zs.split()) askip = set("a v c vc cgen exp_lg exp_md theme".split()) diff --git a/copyparty/cfg.py b/copyparty/cfg.py index c128b6c1..91eca0ee 100644 --- a/copyparty/cfg.py +++ b/copyparty/cfg.py @@ -157,6 +157,7 @@ def vf_vmap() -> dict[str, str]: "tail_tmax", "tail_who", "tcolor", + "th_covers", "th_pregen", "th_qv", "th_qvx", @@ -321,6 +322,7 @@ flagcats = { "th3x": "3x resolution (y/n/fy/fn)", "th_qv=40": "webp/jpg thumbnail quality (10~90)", "th_qvx=40": "jxl thumbnail quality (10~90)", + "th_covers=a.jpg,a.png": "filenames to prefer as folder-thumbs", "convt": "convert-to-image timeout in seconds", "aconvt": "convert-to-audio timeout in seconds", "th_spec_p=1": "make spectrograms? 0=never 1=fallback 2=always", diff --git a/copyparty/httpcli.py b/copyparty/httpcli.py index 996e1643..fd586f1c 100644 --- a/copyparty/httpcli.py +++ b/copyparty/httpcli.py @@ -6856,7 +6856,8 @@ class HttpCli(object): nothumb = "dthumb" in dbv.flags if is_dir: vrem = vrem.rstrip("/") - if nothumb: + cvs = dbv.flags["th_coversl"] + if nothumb or not cvs: pass elif icur and vrem: q = "select fn from cv where rd=? and dn=?" @@ -6873,7 +6874,7 @@ class HttpCli(object): except: pass else: - for fn in self.args.th_covers: + for fn in cvs: fp = os.path.join(abspath, fn) try: st = bos.stat(fp) @@ -7636,7 +7637,7 @@ class HttpCli(object): self.conn.hsrv.j2[tpl] = j2env.get_template(tname) thumb = "" is_pic = is_vid = is_au = False - for fn in self.args.th_coversd: + for fn in vn.flags["th_coversd"]: if fn in lnames: thumb = lnames[fn] break diff --git a/copyparty/svchub.py b/copyparty/svchub.py index 933f2def..96954f2e 100644 --- a/copyparty/svchub.py +++ b/copyparty/svchub.py @@ -1183,14 +1183,6 @@ class SvcHub(object): if al.rsp_jtr: al.rsp_slp = 0.000001 - zsl = al.th_covers.split(",") - zsl = [x.strip() for x in zsl] - zsl = [x for x in zsl if x] - al.th_covers = zsl - al.th_coversd = zsl + ["." + x for x in zsl] - al.th_covers_set = set(al.th_covers) - al.th_coversd_set = set(al.th_coversd) - zs = al.use_bwrap.strip().lower() if zs == "a": if not HAVE_BWRAP: diff --git a/copyparty/up2k.py b/copyparty/up2k.py index d6c0fde9..ac1dc726 100644 --- a/copyparty/up2k.py +++ b/copyparty/up2k.py @@ -1159,7 +1159,7 @@ class Up2k(object): ft = "\033[0;32m{}{:.0}" ff = "\033[0;35m{}{:.0}" fv = "\033[0;36m{}:\033[90m{}" - zs = "bcasechk du_iwho emb_all emb_lgs emb_mds ext_th_d html_head html_head_d html_head_s ls_q_m put_name2 mv_re_r mv_re_t rm_re_r rm_re_t oh_f oh_g rw_edit_set srch_re_dots srch_re_nodot zipmax zipmaxn_v zipmaxs_v" + zs = "bcasechk du_iwho emb_all emb_lgs emb_mds ext_th_d html_head html_head_d html_head_s ls_q_m put_name2 mv_re_r mv_re_t rm_re_r rm_re_t oh_f oh_g rw_edit_set srch_re_dots srch_re_nodot th_coversd th_coversl th_covers_set th_coversd_set zipmax zipmaxn_v zipmaxs_v" fx = set(zs.split()) fd = vf_bmap() fd.update(vf_cmap()) @@ -1431,6 +1431,8 @@ class Up2k(object): rtop, rei, reh, + vol.flags["th_coversd"], + vol.flags["th_coversd_set"], n4g, ffat, [], @@ -1508,6 +1510,8 @@ class Up2k(object): rcdir: str, rei: Optional[Pattern[str]], reh: Optional[Pattern[str]], + th_cvd: list[str], + th_cvds: set[str], n4g: bool, ffat: bool, seen: list[str], @@ -1533,8 +1537,6 @@ class Up2k(object): cv = vcv = acv = "" e_d = {} - th_cvd = self.args.th_coversd - th_cvds = self.args.th_coversd_set scan_pr_s = self.args.scan_pr_s assert self.pp and self.mem_cur # !rm @@ -1601,6 +1603,8 @@ class Up2k(object): rap, rei, reh, + th_cvd, + th_cvds, n4g, fat32, seen, @@ -1631,7 +1635,7 @@ class Up2k(object): rsz += sz files.append((sz, lmod, iname)) - if sz: + if sz and th_cvd: liname = iname.lower() ext = liname.rsplit(".", 1)[-1] if ( @@ -4117,31 +4121,7 @@ class Up2k(object): with self.rescan_cond: self.rescan_cond.notify_all() - if rd and sz and fn.lower() in self.args.th_coversd_set: - # wasteful; db_add will re-index actual covers - # but that won't catch existing files - crd, cdn = rd.rsplit("/", 1) if "/" in rd else ("", rd) - try: - q = "select fn from cv where rd=? and dn=?" - db_cv = db.execute(q, (crd, cdn)).fetchone()[0] - db_lcv = db_cv.lower() - if db_lcv in self.args.th_coversd_set: - idx_db = self.args.th_coversd.index(db_lcv) - idx_fn = self.args.th_coversd.index(fn.lower()) - add_cv = idx_fn < idx_db - else: - add_cv = True - except: - add_cv = True - - if add_cv: - try: - db.execute("delete from cv where rd=? and dn=?", (crd, cdn)) - db.execute("insert into cv values (?,?,?)", (crd, cdn, fn)) - except: - pass - - if "nodirsz" not in vflags: + if vflags and "nodirsz" not in vflags: try: q = "update ds set nf=nf+1, sz=sz+? where rd=?" q2 = "insert into ds values(?,?,1)" diff --git a/tests/util.py b/tests/util.py index 746e08fb..6774f04d 100644 --- a/tests/util.py +++ b/tests/util.py @@ -223,10 +223,7 @@ class Cfg(Namespace): sort="href", srch_hits=99999, SRS="/", - th_covers=["folder.png"], - th_coversd=["folder.png"], - th_covers_set=set(["folder.png"]), - th_coversd_set=set(["folder.png"]), + th_covers="folder.png", th_crop="y", th_size="320x256", th_x3="n",