copyparty 1.13.0__py3-none-any.whl → 1.13.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
copyparty/tcpsrv.py CHANGED
@@ -460,6 +460,12 @@ class TcpSrv(object):
460
460
  sys.stderr.flush()
461
461
 
462
462
  def _qr(self, t1 , t2 ) :
463
+ t2c = {zs: zli for zs, zli in t2.items() if zs in ("127.0.0.1", "::1")}
464
+ t2b = {zs: zli for zs, zli in t2.items() if ":" in zs and zs not in t2c}
465
+ t2 = {zs: zli for zs, zli in t2.items() if zs not in t2b and zs not in t2c}
466
+ t2.update(t2b) # first ipv4, then ipv6...
467
+ t2.update(t2c) # ...and finally localhost
468
+
463
469
  ip = None
464
470
  ips = list(t1) + list(t2)
465
471
  qri = self.args.qri
copyparty/th_cli.py CHANGED
@@ -103,6 +103,11 @@ class ThumbCli(object):
103
103
  sfmt += "3" if "3" in fmt else ""
104
104
 
105
105
  fmt = sfmt
106
+
107
+ elif fmt[:1] == "p" and not is_au:
108
+ t = "cannot thumbnail [%s]: png only allowed for waveforms"
109
+ self.log(t % (rem), 6)
110
+ return None
106
111
 
107
112
  histpath = self.asrv.vfs.histtab.get(ptop)
108
113
  if not histpath:
copyparty/th_srv.py CHANGED
@@ -15,7 +15,7 @@ from queue import Queue
15
15
  from .__init__ import ANYWIN, TYPE_CHECKING
16
16
  from .authsrv import VFS
17
17
  from .bos import bos
18
- from .mtag import HAVE_FFMPEG, HAVE_FFPROBE, ffprobe
18
+ from .mtag import HAVE_FFMPEG, HAVE_FFPROBE, au_unpk, ffprobe
19
19
  from .util import BytesIO # type: ignore
20
20
  from .util import (
21
21
  FFMPEG_URL,
@@ -294,6 +294,12 @@ class ThumbSrv(object):
294
294
  ext = abspath.split(".")[-1].lower()
295
295
  png_ok = False
296
296
  funs = []
297
+
298
+ if ext in self.args.au_unpk:
299
+ ap_unpk = au_unpk(self.log, self.args.au_unpk, abspath, vn)
300
+ else:
301
+ ap_unpk = abspath
302
+
297
303
  if not bos.path.exists(tpath):
298
304
  for lib in self.args.th_dec:
299
305
  if lib == "pil" and ext in self.fmt_pil:
@@ -313,9 +319,6 @@ class ThumbSrv(object):
313
319
  else:
314
320
  funs.append(self.conv_spec)
315
321
 
316
- if not png_ok and tpath.endswith(".png"):
317
- raise Pebkac(400, "png only allowed for waveforms")
318
-
319
322
  tdir, tfn = os.path.split(tpath)
320
323
  ttpath = os.path.join(tdir, "w", tfn)
321
324
  try:
@@ -325,7 +328,10 @@ class ThumbSrv(object):
325
328
 
326
329
  for fun in funs:
327
330
  try:
328
- fun(abspath, ttpath, fmt, vn)
331
+ if not png_ok and tpath.endswith(".png"):
332
+ raise Exception("png only allowed for waveforms")
333
+
334
+ fun(ap_unpk, ttpath, fmt, vn)
329
335
  break
330
336
  except Exception as ex:
331
337
  msg = "{} could not create thumbnail of {}\n{}"
@@ -343,6 +349,9 @@ class ThumbSrv(object):
343
349
  except:
344
350
  pass
345
351
 
352
+ if abspath != ap_unpk:
353
+ wunlink(self.log, ap_unpk, vn.flags)
354
+
346
355
  try:
347
356
  wrename(self.log, ttpath, tpath, vn.flags)
348
357
  except:
@@ -581,6 +590,24 @@ class ThumbSrv(object):
581
590
  cmd += [fsenc(tpath)]
582
591
  self._run_ff(cmd, vn)
583
592
 
593
+ if "pngquant" in vn.flags:
594
+ wtpath = tpath + ".png"
595
+ cmd = [
596
+ b"pngquant",
597
+ b"--strip",
598
+ b"--nofs",
599
+ b"--output", fsenc(wtpath),
600
+ fsenc(tpath)
601
+ ]
602
+ ret = runcmd(cmd, timeout=vn.flags["convt"], nice=True, oom=400)[0]
603
+ if ret:
604
+ try:
605
+ wunlink(self.log, wtpath, vn.flags)
606
+ except:
607
+ pass
608
+ else:
609
+ wrename(self.log, wtpath, tpath, vn.flags)
610
+
584
611
  def conv_spec(self, abspath , tpath , fmt , vn ) :
585
612
  ret, _ = ffprobe(abspath, int(vn.flags["convt"] / 2))
586
613
  if "ac" not in ret:
copyparty/u2idx.py CHANGED
@@ -59,6 +59,17 @@ class U2idx(object):
59
59
  def log(self, msg , c = 0) :
60
60
  self.log_func("u2idx", msg, c)
61
61
 
62
+ def shutdown(self) :
63
+ for cur in self.cur.values():
64
+ db = cur.connection
65
+ try:
66
+ db.interrupt()
67
+ except:
68
+ pass
69
+
70
+ cur.close()
71
+ db.close()
72
+
62
73
  def fsearch(
63
74
  self, uname , vols , body
64
75
  ) :
@@ -78,14 +89,18 @@ class U2idx(object):
78
89
  except:
79
90
  raise Pebkac(500, min_ex())
80
91
 
81
- def get_cur(self, ptop ) :
92
+ def get_cur(self, vn ) :
82
93
  if not HAVE_SQLITE3:
83
94
  return None
84
95
 
85
- cur = self.cur.get(ptop)
96
+ cur = self.cur.get(vn.realpath)
86
97
  if cur:
87
98
  return cur
88
99
 
100
+ if "e2d" not in vn.flags:
101
+ return None
102
+
103
+ ptop = vn.realpath
89
104
  histpath = self.asrv.vfs.histtab.get(ptop)
90
105
  if not histpath:
91
106
  self.log("no histpath for [{}]".format(ptop))
@@ -314,7 +329,7 @@ class U2idx(object):
314
329
  ptop = vol.realpath
315
330
  flags = vol.flags
316
331
 
317
- cur = self.get_cur(ptop)
332
+ cur = self.get_cur(vol)
318
333
  if not cur:
319
334
  continue
320
335
 
copyparty/up2k.py CHANGED
@@ -10,7 +10,6 @@ import math
10
10
  import os
11
11
  import re
12
12
  import shutil
13
- import signal
14
13
  import stat
15
14
  import subprocess as sp
16
15
  import tempfile
@@ -29,6 +28,7 @@ from .fsutil import Fstab
29
28
  from .mtag import MParser, MTag
30
29
  from .util import (
31
30
  HAVE_SQLITE3,
31
+ VF_CAREFUL,
32
32
  SYMTIME,
33
33
  Daemon,
34
34
  MTHash,
@@ -88,9 +88,6 @@ CV_EXTS = set(zsg.split(","))
88
88
  HINT_HISTPATH = "you could try moving the database to another location (preferably an SSD or NVME drive) using either the --hist argument (global option for all volumes), or the hist volflag (just for this volume)"
89
89
 
90
90
 
91
- VF_CAREFUL = {"mv_re_t": 5, "rm_re_t": 5, "mv_re_r": 0.1, "rm_re_r": 0.1}
92
-
93
-
94
91
  class Dbw(object):
95
92
  def __init__(self, c , n , t ) :
96
93
  self.c = c
@@ -183,7 +180,7 @@ class Up2k(object):
183
180
  t = "could not initialize sqlite3, will use in-memory registry only"
184
181
  self.log(t, 3)
185
182
 
186
- self.fstab = Fstab(self.log_func)
183
+ self.fstab = Fstab(self.log_func, self.args)
187
184
  self.gen_fk = self._gen_fk if self.args.log_fk else gen_filekey
188
185
 
189
186
  if self.args.hash_mt < 2:
@@ -458,7 +455,13 @@ class Up2k(object):
458
455
  # important; not deferred by db_act
459
456
  timeout = self._check_lifetimes()
460
457
 
461
- timeout = min(timeout, now + self._check_xiu())
458
+ try:
459
+ timeout = min(timeout, now + self._check_xiu())
460
+ except Exception as ex:
461
+ if "closed cursor" in str(ex):
462
+ self.log("sched_rescan: lost db")
463
+ return
464
+ raise
462
465
 
463
466
  with self.mutex:
464
467
  for vp, vol in sorted(self.asrv.vfs.all_vols.items()):
@@ -1036,8 +1039,11 @@ class Up2k(object):
1036
1039
  return None
1037
1040
 
1038
1041
  def _verify_db_cache(self, cur , vpath ) :
1039
- # check if volume config changed since last use; drop caches if so
1040
- zsl = [vpath] + list(sorted(self.asrv.vfs.all_vols.keys()))
1042
+ # check if list of intersecting volumes changed since last use; drop caches if so
1043
+ prefix = (vpath + "/").lstrip("/")
1044
+ zsl = [x for x in self.asrv.vfs.all_vols if x.startswith(prefix)]
1045
+ zsl = [x[len(prefix) :] for x in zsl]
1046
+ zsl.sort()
1041
1047
  zb = hashlib.sha1("\n".join(zsl).encode("utf-8", "replace")).digest()
1042
1048
  vcfg = base64.urlsafe_b64encode(zb[:18]).decode("ascii")
1043
1049
 
@@ -1647,7 +1653,7 @@ class Up2k(object):
1647
1653
 
1648
1654
  if e2vp and rewark:
1649
1655
  self.hub.retcode = 1
1650
- os.kill(os.getpid(), signal.SIGTERM)
1656
+ Daemon(self.hub.sigterm)
1651
1657
  raise Exception("{} files have incorrect hashes".format(len(rewark)))
1652
1658
 
1653
1659
  if not e2vu or not rewark:
@@ -4376,6 +4382,18 @@ class Up2k(object):
4376
4382
  for x in list(self.spools):
4377
4383
  self._unspool(x)
4378
4384
 
4385
+ for cur in self.cur.values():
4386
+ db = cur.connection
4387
+ try:
4388
+ db.interrupt()
4389
+ except:
4390
+ pass
4391
+
4392
+ cur.close()
4393
+ db.close()
4394
+
4395
+ self.registry = {}
4396
+
4379
4397
 
4380
4398
  def up2k_chunksize(filesize ) :
4381
4399
  chunksize = 1024 * 1024
copyparty/util.py CHANGED
@@ -35,6 +35,9 @@ from .__init__ import ANYWIN, EXE, MACOS, PY2, TYPE_CHECKING, VT100, WINDOWS
35
35
  from .__version__ import S_BUILD_DT, S_VERSION
36
36
  from .stolen import surrogateescape
37
37
 
38
+ ub64dec = base64.urlsafe_b64decode
39
+ ub64enc = base64.urlsafe_b64encode
40
+
38
41
  try:
39
42
  from datetime import datetime, timezone
40
43
 
@@ -334,6 +337,9 @@ APPLESAN_TXT = r"/(__MACOS|Icon\r\r)|/\.(_|DS_Store|AppleDouble|LSOverride|Docum
334
337
  APPLESAN_RE = re.compile(APPLESAN_TXT)
335
338
 
336
339
 
340
+ VF_CAREFUL = {"mv_re_t": 5, "rm_re_t": 5, "mv_re_r": 0.1, "rm_re_r": 0.1}
341
+
342
+
337
343
  pybin = sys.executable or ""
338
344
  if EXE:
339
345
  pybin = ""
@@ -439,13 +445,22 @@ class Daemon(threading.Thread):
439
445
  r = True,
440
446
  ka = None,
441
447
  ) :
442
- threading.Thread.__init__(
443
- self, target=target, name=name, args=a or (), kwargs=ka
444
- )
448
+ threading.Thread.__init__(self, name=name)
449
+ self.a = a or ()
450
+ self.ka = ka or {}
451
+ self.fun = target
445
452
  self.daemon = True
446
453
  if r:
447
454
  self.start()
448
455
 
456
+ def run(self):
457
+ if not ANYWIN and not PY2:
458
+ signal.pthread_sigmask(
459
+ signal.SIG_BLOCK, [signal.SIGINT, signal.SIGTERM, signal.SIGUSR1]
460
+ )
461
+
462
+ self.fun(*self.a, **self.ka)
463
+
449
464
 
450
465
  class Netdev(object):
451
466
  def __init__(self, ip , idx , name , desc ):
@@ -840,6 +855,7 @@ class ProgressPrinter(threading.Thread):
840
855
  self.start()
841
856
 
842
857
  def run(self) :
858
+ sigblock()
843
859
  tp = 0
844
860
  msg = None
845
861
  no_stdout = self.args.q
@@ -1284,6 +1300,15 @@ def log_thrs(log , ival , name ) :
1284
1300
  log(name, "\033[0m \033[33m".join(tv), 3)
1285
1301
 
1286
1302
 
1303
+ def sigblock():
1304
+ if ANYWIN or PY2:
1305
+ return
1306
+
1307
+ signal.pthread_sigmask(
1308
+ signal.SIG_BLOCK, [signal.SIGINT, signal.SIGTERM, signal.SIGUSR1]
1309
+ )
1310
+
1311
+
1287
1312
  def vol_san(vols , txt ) :
1288
1313
  txt0 = txt
1289
1314
  for vol in vols:
@@ -1305,10 +1330,11 @@ def vol_san(vols , txt ) :
1305
1330
 
1306
1331
  def min_ex(max_lines = 8, reverse = False) :
1307
1332
  et, ev, tb = sys.exc_info()
1308
- stb = traceback.extract_tb(tb)
1333
+ stb = traceback.extract_tb(tb) if tb else traceback.extract_stack()[:-1]
1309
1334
  fmt = "%s @ %d <%s>: %s"
1310
1335
  ex = [fmt % (fp.split(os.sep)[-1], ln, fun, txt) for fp, ln, fun, txt in stb]
1311
- ex.append("[%s] %s" % (et.__name__ if et else "(anonymous)", ev))
1336
+ if et or ev or tb:
1337
+ ex.append("[%s] %s" % (et.__name__ if et else "(anonymous)", ev))
1312
1338
  return "\n".join(ex[-max_lines:][:: -1 if reverse else 1])
1313
1339
 
1314
1340
 
@@ -2010,6 +2036,7 @@ def vsplit(vpath ) :
2010
2036
  return vpath.rsplit("/", 1) # type: ignore
2011
2037
 
2012
2038
 
2039
+ # vpath-join
2013
2040
  def vjoin(rd , fn ) :
2014
2041
  if rd and fn:
2015
2042
  return rd + "/" + fn
@@ -2017,6 +2044,14 @@ def vjoin(rd , fn ) :
2017
2044
  return rd or fn
2018
2045
 
2019
2046
 
2047
+ # url-join
2048
+ def ujoin(rd , fn ) :
2049
+ if rd and fn:
2050
+ return rd.rstrip("/") + "/" + fn.lstrip("/")
2051
+ else:
2052
+ return rd or fn
2053
+
2054
+
2020
2055
  def _w8dec2(txt ) :
2021
2056
  """decodes filesystem-bytes to wtf8"""
2022
2057
  return surrogateescape.decodefilename(txt)
@@ -2648,7 +2683,7 @@ def unescape_cookie(orig ) :
2648
2683
 
2649
2684
  def guess_mime(url , fallback = "application/octet-stream") :
2650
2685
  try:
2651
- _, ext = url.rsplit(".", 1)
2686
+ ext = url.rsplit(".", 1)[1].lower()
2652
2687
  except:
2653
2688
  return fallback
2654
2689
 
copyparty/web/a/u2c.py CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env python3
2
2
  from __future__ import print_function, unicode_literals
3
3
 
4
- S_VERSION = "1.16"
5
- S_BUILD_DT = "2024-04-20"
4
+ S_VERSION = "1.17"
5
+ S_BUILD_DT = "2024-05-09"
6
6
 
7
7
  """
8
8
  u2c.py: upload to copyparty
@@ -79,12 +79,21 @@ req_ses = requests.Session()
79
79
 
80
80
 
81
81
  class Daemon(threading.Thread):
82
- def __init__(self, target, name=None, a=None):
83
- # type: (Any, Any, Any) -> None
84
- threading.Thread.__init__(self, target=target, args=a or (), name=name)
82
+ def __init__(self, target, name = None, a = None):
83
+ threading.Thread.__init__(self, name=name)
84
+ self.a = a or ()
85
+ self.fun = target
85
86
  self.daemon = True
86
87
  self.start()
87
88
 
89
+ def run(self):
90
+ try:
91
+ signal.pthread_sigmask(signal.SIG_BLOCK, [signal.SIGINT, signal.SIGTERM])
92
+ except:
93
+ pass
94
+
95
+ self.fun(*self.a)
96
+
88
97
 
89
98
  class File(object):
90
99
  """an up2k upload task; represents a single file"""
Binary file
Binary file
@@ -6,7 +6,7 @@
6
6
  <title>{{ title }}</title>
7
7
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
8
8
  <meta name="viewport" content="width=device-width, initial-scale=0.8, minimum-scale=0.6">
9
- <meta name="theme-color" content="#333">
9
+ <meta name="theme-color" content="#{{ tcolor }}">
10
10
  <link rel="stylesheet" media="screen" href="{{ r }}/.cpr/ui.css?_={{ ts }}">
11
11
  <link rel="stylesheet" media="screen" href="{{ r }}/.cpr/browser.css?_={{ ts }}">
12
12
  {{ html_head }}
Binary file
Binary file
copyparty/web/md.html CHANGED
@@ -3,7 +3,7 @@
3
3
  <title>📝 {{ title }}</title>
4
4
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=0.7">
6
- <meta name="theme-color" content="#333">
6
+ <meta name="theme-color" content="#{{ tcolor }}">
7
7
  <link rel="stylesheet" href="{{ r }}/.cpr/ui.css?_={{ ts }}">
8
8
  <link rel="stylesheet" href="{{ r }}/.cpr/md.css?_={{ ts }}">
9
9
  {%- if edit %}
copyparty/web/mde.html CHANGED
@@ -3,7 +3,7 @@
3
3
  <title>📝 {{ title }}</title>
4
4
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=0.7">
6
- <meta name="theme-color" content="#333">
6
+ <meta name="theme-color" content="#{{ tcolor }}">
7
7
  <link rel="stylesheet" href="{{ r }}/.cpr/ui.css?_={{ ts }}">
8
8
  <link rel="stylesheet" href="{{ r }}/.cpr/mde.css?_={{ ts }}">
9
9
  <link rel="stylesheet" href="{{ r }}/.cpr/deps/mini-fa.css?_={{ ts }}">
copyparty/web/msg.html CHANGED
@@ -6,7 +6,7 @@
6
6
  <title>{{ s_doctitle }}</title>
7
7
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
8
8
  <meta name="viewport" content="width=device-width, initial-scale=0.8">
9
- <meta name="theme-color" content="#333">
9
+ <meta name="theme-color" content="#{{ tcolor }}">
10
10
  <link rel="stylesheet" media="screen" href="{{ r }}/.cpr/msg.css?_={{ ts }}">
11
11
  {{ html_head }}
12
12
  </head>
copyparty/web/splash.html CHANGED
@@ -6,7 +6,7 @@
6
6
  <title>{{ s_doctitle }}</title>
7
7
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
8
8
  <meta name="viewport" content="width=device-width, initial-scale=0.8">
9
- <meta name="theme-color" content="#333">
9
+ <meta name="theme-color" content="#{{ tcolor }}">
10
10
  <link rel="stylesheet" media="screen" href="{{ r }}/.cpr/splash.css?_={{ ts }}">
11
11
  <link rel="stylesheet" media="screen" href="{{ r }}/.cpr/ui.css?_={{ ts }}">
12
12
  {{ html_head }}
@@ -95,6 +95,7 @@
95
95
  <form method="post" enctype="multipart/form-data" action="{{ r }}/{{ qvpath }}">
96
96
  <input type="hidden" name="act" value="login" />
97
97
  <input type="password" name="cppwd" placeholder=" password" />
98
+ <input type="hidden" name="uhash" id="uhash" value="x" />
98
99
  <input type="submit" value="Login" />
99
100
  {% if ahttps %}
100
101
  <a id="w" href="{{ ahttps }}">switch to https</a>
Binary file
copyparty/web/svcs.html CHANGED
@@ -6,7 +6,7 @@
6
6
  <title>{{ s_doctitle }}</title>
7
7
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
8
8
  <meta name="viewport" content="width=device-width, initial-scale=0.8">
9
- <meta name="theme-color" content="#333">
9
+ <meta name="theme-color" content="#{{ tcolor }}">
10
10
  <link rel="stylesheet" media="screen" href="{{ r }}/.cpr/splash.css?_={{ ts }}">
11
11
  <link rel="stylesheet" media="screen" href="{{ r }}/.cpr/ui.css?_={{ ts }}">
12
12
  <style>ul{padding-left:1.3em}li{margin:.4em 0}</style>
copyparty/web/util.js.gz CHANGED
Binary file
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: copyparty
3
- Version: 1.13.0
3
+ Version: 1.13.2
4
4
  Summary: Portable file server with accelerated resumable uploads, deduplication, WebDAV, FTP, zeroconf, media indexer, video thumbnails, audio transcoding, and write-only folders
5
5
  Author-email: ed <copyparty@ocv.me>
6
6
  License: MIT
@@ -101,6 +101,7 @@ turn almost any device into a file server with resumable uploads/downloads using
101
101
  * [audio equalizer](#audio-equalizer) - and [dynamic range compressor](https://en.wikipedia.org/wiki/Dynamic_range_compression)
102
102
  * [fix unreliable playback on android](#fix-unreliable-playback-on-android) - due to phone / app settings
103
103
  * [markdown viewer](#markdown-viewer) - and there are *two* editors
104
+ * [markdown vars](#markdown-vars) - dynamic docs with serverside variable expansion
104
105
  * [other tricks](#other-tricks)
105
106
  * [searching](#searching) - search by size, date, path/name, mp3-tags, ...
106
107
  * [server config](#server-config) - using arguments or config files, or a mix of both
@@ -114,6 +115,7 @@ turn almost any device into a file server with resumable uploads/downloads using
114
115
  * [tftp server](#tftp-server) - a TFTP server (read/write) can be started using `--tftp 3969`
115
116
  * [smb server](#smb-server) - unsafe, slow, not recommended for wan
116
117
  * [browser ux](#browser-ux) - tweaking the ui
118
+ * [opengraph](#opengraph) - discord and social-media embeds
117
119
  * [file indexing](#file-indexing) - enables dedup and music search ++
118
120
  * [exclude-patterns](#exclude-patterns) - to save some time
119
121
  * [filesystem guards](#filesystem-guards) - avoid traversing into other filesystems
@@ -162,8 +164,9 @@ turn almost any device into a file server with resumable uploads/downloads using
162
164
  * [dependencies](#dependencies) - mandatory deps
163
165
  * [optional dependencies](#optional-dependencies) - install these to enable bonus features
164
166
  * [optional gpl stuff](#optional-gpl-stuff)
165
- * [sfx](#sfx) - the self-contained "binary"
167
+ * [sfx](#sfx) - the self-contained "binary" (recommended!)
166
168
  * [copyparty.exe](#copypartyexe) - download [copyparty.exe](https://github.com/9001/copyparty/releases/latest/download/copyparty.exe) (win8+) or [copyparty32.exe](https://github.com/9001/copyparty/releases/latest/download/copyparty32.exe) (win7+)
169
+ * [zipapp](#zipapp) - another emergency alternative, [copyparty.pyz](https://github.com/9001/copyparty/releases/latest/download/copyparty.pyz)
167
170
  * [install on android](#install-on-android)
168
171
  * [reporting bugs](#reporting-bugs) - ideas for context to include, and where to submit them
169
172
  * [devnotes](#devnotes) - for build instructions etc, see [./docs/devnotes.md](./docs/devnotes.md)
@@ -177,6 +180,7 @@ just run **[copyparty-sfx.py](https://github.com/9001/copyparty/releases/latest/
177
180
  * or if you cannot install python, you can use [copyparty.exe](#copypartyexe) instead
178
181
  * or install [on arch](#arch-package) ╱ [on NixOS](#nixos-module) ╱ [through nix](#nix-package)
179
182
  * or if you are on android, [install copyparty in termux](#install-on-android)
183
+ * or if your computer is messed up and nothing else works, [try the pyz](#zipapp)
180
184
  * or if you prefer to [use docker](./scripts/docker/) 🐋 you can do that too
181
185
  * docker has all deps built-in, so skip this step:
182
186
 
@@ -280,6 +284,7 @@ also see [comparison to similar software](./docs/versus.md)
280
284
  * ☑ ...of videos using FFmpeg
281
285
  * ☑ ...of audio (spectrograms) using FFmpeg
282
286
  * ☑ cache eviction (max-age; maybe max-size eventually)
287
+ * ☑ multilingual UI (english, norwegian, [add your own](./docs/rice/#translations)))
283
288
  * ☑ SPA (browse while uploading)
284
289
  * server indexing
285
290
  * ☑ [locate files by contents](#file-search)
@@ -288,9 +293,11 @@ also see [comparison to similar software](./docs/versus.md)
288
293
  * client support
289
294
  * ☑ [folder sync](#folder-sync)
290
295
  * ☑ [curl-friendly](https://user-images.githubusercontent.com/241032/215322619-ea5fd606-3654-40ad-94ee-2bc058647bb2.png)
296
+ * ☑ [opengraph](#opengraph) (discord embeds)
291
297
  * markdown
292
298
  * ☑ [viewer](#markdown-viewer)
293
299
  * ☑ editor (sure why not)
300
+ * ☑ [variables](#markdown-vars)
294
301
 
295
302
  PS: something missing? post any crazy ideas you've got as a [feature request](https://github.com/9001/copyparty/issues/new?assignees=9001&labels=enhancement&template=feature_request.md) or [discussion](https://github.com/9001/copyparty/discussions/new?category=ideas) 🤙
296
303
 
@@ -460,7 +467,7 @@ configuring accounts/volumes with arguments:
460
467
  `-v .::r,usr1,usr2:rw,usr3,usr4` = usr1/2 read-only, 3/4 read-write
461
468
 
462
469
  permissions:
463
- * `r` (read): browse folder contents, download files, download as zip/tar
470
+ * `r` (read): browse folder contents, download files, download as zip/tar, see filekeys/dirkeys
464
471
  * `w` (write): upload files, move files *into* this folder
465
472
  * `m` (move): move files/folders *from* this folder
466
473
  * `d` (delete): delete files/folders
@@ -662,15 +669,21 @@ you can also zip a selection of files or folders by clicking them in the browser
662
669
 
663
670
  cool trick: download a folder by appending url-params `?tar&opus` or `?tar&mp3` to transcode all audio files (except aac|m4a|mp3|ogg|opus|wma) to opus/mp3 before they're added to the archive
664
671
  * super useful if you're 5 minutes away from takeoff and realize you don't have any music on your phone but your server only has flac files and downloading those will burn through all your data + there wouldn't be enough time anyways
665
- * and url-params `&j` / `&w` produce jpeg/webm thumbnails/spectrograms instead of the original audio/video/images
672
+ * and url-params `&j` / `&w` produce jpeg/webm thumbnails/spectrograms instead of the original audio/video/images (`&p` for audio waveforms)
666
673
  * can also be used to pregenerate thumbnails; combine with `--th-maxage=9999999` or `--th-clean=0`
667
674
 
668
675
 
669
676
  ## uploading
670
677
 
671
- drag files/folders into the web-browser to upload (or use the [command-line uploader](https://github.com/9001/copyparty/tree/hovudstraum/bin#u2cpy))
678
+ drag files/folders into the web-browser to upload
672
679
 
673
- this initiates an upload using `up2k`; there are two uploaders available:
680
+ dragdrop is the recommended way, but you may also:
681
+
682
+ * select some files (not folders) in your file explorer and press CTRL-V inside the browser window
683
+ * use the [command-line uploader](https://github.com/9001/copyparty/tree/hovudstraum/bin#u2cpy)
684
+ * upload using [curl or sharex](#client-examples)
685
+
686
+ when uploading files through dragdrop or CTRL-V, this initiates an upload using `up2k`; there are two browser-based uploaders available:
674
687
  * `[🎈] bup`, the basic uploader, supports almost every browser since netscape 4.0
675
688
  * `[🚀] up2k`, the good / fancy one
676
689
 
@@ -897,6 +910,13 @@ other notes,
897
910
  * the document preview has a max-width which is the same as an A4 paper when printed
898
911
 
899
912
 
913
+ ### markdown vars
914
+
915
+ dynamic docs with serverside variable expansion to replace stuff like `{{self.ip}}` with the client's IP, or `{{srv.htime}}` with the current time on the server
916
+
917
+ see [./srv/expand/](./srv/expand/) for usage and examples
918
+
919
+
900
920
  ## other tricks
901
921
 
902
922
  * you can link a particular timestamp in an audio file by adding it to the URL, such as `&20` / `&20s` / `&1m20` / `&t=1:20` after the `.../#af-c8960dab`
@@ -945,6 +965,8 @@ using arguments or config files, or a mix of both:
945
965
 
946
966
  **NB:** as humongous as this readme is, there is also a lot of undocumented features. Run copyparty with `--help` to see all available global options; all of those can be used in the `[global]` section of config files, and everything listed in `--help-flags` can be used in volumes as volflags.
947
967
  * if running in docker/podman, try this: `docker run --rm -it copyparty/ac --help`
968
+ * or see this (probably outdated): https://ocv.me/copyparty/helptext.html
969
+ * or if you prefer plaintext, https://ocv.me/copyparty/helptext.txt
948
970
 
949
971
 
950
972
  ## zeroconf
@@ -1121,7 +1143,22 @@ tweaking the ui
1121
1143
  * to sort in music order (album, track, artist, title) with filename as fallback, you could `--sort tags/Cirle,tags/.tn,tags/Artist,tags/Title,href`
1122
1144
  * to sort by upload date, first enable showing the upload date in the listing with `-e2d -mte +.up_at` and then `--sort tags/.up_at`
1123
1145
 
1124
- see [./docs/rice](./docs/rice) for more
1146
+ see [./docs/rice](./docs/rice) for more, including how to add stuff (css/`<meta>`/...) to the html `<head>` tag, or to add your own translation
1147
+
1148
+
1149
+ ## opengraph
1150
+
1151
+ discord and social-media embeds
1152
+
1153
+ can be enabled globally with `--og` or per-volume with volflag `og`
1154
+
1155
+ note that this disables hotlinking because the opengraph spec demands it; to sneak past this intentional limitation, you can enable opengraph selectively by user-agent, for example `--og-ua '(Discord|Twitter|Slack)bot'` (or volflag `og_ua`)
1156
+
1157
+ you can also hotlink files regardless by appending `?raw` to the url
1158
+
1159
+ NOTE: because discord (and maybe others) strip query args such as `?raw` in opengraph tags, any links which require a filekey or dirkey will not work
1160
+
1161
+ if you want to entirely replace the copyparty response with your own jinja2 template, give the template filepath to `--og-tpl` or volflag `og_tpl` (all members of `HttpCli` are available through the `this` object)
1125
1162
 
1126
1163
 
1127
1164
  ## file indexing
@@ -2035,7 +2072,7 @@ these are standalone programs and will never be imported / evaluated by copypart
2035
2072
 
2036
2073
  # sfx
2037
2074
 
2038
- the self-contained "binary" [copyparty-sfx.py](https://github.com/9001/copyparty/releases/latest/download/copyparty-sfx.py) will unpack itself and run copyparty, assuming you have python installed of course
2075
+ the self-contained "binary" (recommended!) [copyparty-sfx.py](https://github.com/9001/copyparty/releases/latest/download/copyparty-sfx.py) will unpack itself and run copyparty, assuming you have python installed of course
2039
2076
 
2040
2077
  you can reduce the sfx size by repacking it; see [./docs/devnotes.md#sfx-repack](./docs/devnotes.md#sfx-repack)
2041
2078
 
@@ -2062,6 +2099,16 @@ meanwhile [copyparty-sfx.py](https://github.com/9001/copyparty/releases/latest/d
2062
2099
  then again, if you are already into downloading shady binaries from the internet, you may also want my [minimal builds](./scripts/pyinstaller#ffmpeg) of [ffmpeg](https://ocv.me/stuff/bin/ffmpeg.exe) and [ffprobe](https://ocv.me/stuff/bin/ffprobe.exe) which enables copyparty to extract multimedia-info, do audio-transcoding, and thumbnails/spectrograms/waveforms, however it's much better to instead grab a [recent official build](https://www.gyan.dev/ffmpeg/builds/ffmpeg-git-full.7z) every once ina while if you can afford the size
2063
2100
 
2064
2101
 
2102
+ ## zipapp
2103
+
2104
+ another emergency alternative, [copyparty.pyz](https://github.com/9001/copyparty/releases/latest/download/copyparty.pyz) has less features, requires python 3.7 or newer, worse compression, and more importantly is unable to benefit from more recent versions of jinja2 and such (which makes it less secure)... lots of drawbacks with this one really -- but it *may* just work if the regular sfx fails to start because the computer is messed up in certain funky ways, so it's worth a shot if all else fails
2105
+
2106
+ run it by doubleclicking it, or try typing `python copyparty.pyz` in your terminal/console/commandline/telex if that fails
2107
+
2108
+ it is a python [zipapp](https://docs.python.org/3/library/zipapp.html) meaning it doesn't have to unpack its own python code anywhere to run, so if the filesystem is busted it has a better chance of getting somewhere
2109
+ * but note that it currently still needs to extract the web-resources somewhere (they'll land in the default TEMP-folder of your OS)
2110
+
2111
+
2065
2112
  # install on android
2066
2113
 
2067
2114
  install [Termux](https://termux.com/) + its companion app `Termux:API` (see [ocv.me/termux](https://ocv.me/termux/)) and then copy-paste this into Termux (long-tap) all at once: