copyparty 1.16.9__py3-none-any.whl → 1.16.11__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/__main__.py CHANGED
@@ -767,11 +767,22 @@ def get_sects():
767
767
  values for --urlform:
768
768
  \033[36mstash\033[35m dumps the data to file and returns length + checksum
769
769
  \033[36msave,get\033[35m dumps to file and returns the page like a GET
770
- \033[36mprint,get\033[35m prints the data in the log and returns GET
771
- (leave out the ",get" to return an error instead)\033[0m
770
+ \033[36mprint \033[35m prints the data to log and returns an error
771
+ \033[36mprint,xm \033[35m prints the data to log and returns --xm output
772
+ \033[36mprint,get\033[35m prints the data to log and returns GET\033[0m
772
773
 
773
- note that the \033[35m--xm\033[0m hook will only run if \033[35m--urlform\033[0m
774
- is either \033[36mprint\033[0m or the default \033[36mprint,get\033[0m
774
+ note that the \033[35m--xm\033[0m hook will only run if \033[35m--urlform\033[0m is
775
+ either \033[36mprint\033[0m or \033[36mprint,get\033[0m or the default \033[36mprint,xm\033[0m
776
+
777
+ if an \033[35m--xm\033[0m hook returns text, then
778
+ the response code will be HTTP 202;
779
+ http/get responses will be HTTP 200
780
+
781
+ if there are multiple \033[35m--xm\033[0m hooks defined, then
782
+ the first hook that produced output is returned
783
+
784
+ if there are no \033[35m--xm\033[0m hooks defined, then the default
785
+ \033[36mprint,xm\033[0m behaves like \033[36mprint,get\033[0m (returning html)
775
786
  """
776
787
  ),
777
788
  ],
@@ -952,7 +963,7 @@ def add_general(ap, nc, srvname):
952
963
  ap2.add_argument("-v", metavar="VOL", type=u, action="append", help="add volume, \033[33mSRC\033[0m:\033[33mDST\033[0m:\033[33mFLAG\033[0m; examples [\033[32m.::r\033[0m], [\033[32m/mnt/nas/music:/music:r:aed\033[0m], see --help-accounts")
953
964
  ap2.add_argument("--grp", metavar="G:N,N", type=u, action="append", help="add group, \033[33mNAME\033[0m:\033[33mUSER1\033[0m,\033[33mUSER2\033[0m,\033[33m...\033[0m; example [\033[32madmins:ed,foo,bar\033[0m]")
954
965
  ap2.add_argument("-ed", action="store_true", help="enable the ?dots url parameter / client option which allows clients to see dotfiles / hidden files (volflag=dots)")
955
- ap2.add_argument("--urlform", metavar="MODE", type=u, default="print,get", help="how to handle url-form POSTs; see \033[33m--help-urlform\033[0m")
966
+ ap2.add_argument("--urlform", metavar="MODE", type=u, default="print,xm", help="how to handle url-form POSTs; see \033[33m--help-urlform\033[0m")
956
967
  ap2.add_argument("--wintitle", metavar="TXT", type=u, default="cpp @ $pub", help="server terminal title, for example [\033[32m$ip-10.1.2.\033[0m] or [\033[32m$ip-]")
957
968
  ap2.add_argument("--name", metavar="TXT", type=u, default=srvname, help="server name (displayed topleft in browser and in mDNS)")
958
969
  ap2.add_argument("--mime", metavar="EXT=MIME", type=u, action="append", help="map file \033[33mEXT\033[0mension to \033[33mMIME\033[0mtype, for example [\033[32mjpg=image/jpeg\033[0m]")
@@ -1325,6 +1336,7 @@ def add_admin(ap):
1325
1336
  ap2.add_argument("--no-ups-page", action="store_true", help="disable ?ru (list of recent uploads)")
1326
1337
  ap2.add_argument("--no-up-list", action="store_true", help="don't show list of incoming files in controlpanel")
1327
1338
  ap2.add_argument("--dl-list", metavar="LVL", type=int, default=2, help="who can see active downloads in the controlpanel? [\033[32m0\033[0m]=nobody, [\033[32m1\033[0m]=admins, [\033[32m2\033[0m]=everyone")
1339
+ ap2.add_argument("--ups-who", metavar="LVL", type=int, default=2, help="who can see recent uploads on the ?ru page? [\033[32m0\033[0m]=nobody, [\033[32m1\033[0m]=admins, [\033[32m2\033[0m]=everyone (volflag=ups_who)")
1328
1340
  ap2.add_argument("--ups-when", action="store_true", help="let everyone see upload timestamps on the ?ru page, not just admins")
1329
1341
 
1330
1342
 
@@ -1365,6 +1377,8 @@ def add_transcoding(ap):
1365
1377
  ap2 = ap.add_argument_group('transcoding options')
1366
1378
  ap2.add_argument("--q-opus", metavar="KBPS", type=int, default=128, help="target bitrate for transcoding to opus; set 0 to disable")
1367
1379
  ap2.add_argument("--q-mp3", metavar="QUALITY", type=u, default="q2", help="target quality for transcoding to mp3, for example [\033[32m192k\033[0m] (CBR) or [\033[32mq0\033[0m] (CQ/CRF, q0=maxquality, q9=smallest); set 0 to disable")
1380
+ ap2.add_argument("--no-caf", action="store_true", help="disable transcoding to caf-opus (affects iOS v12~v17), will use mp3 instead")
1381
+ ap2.add_argument("--no-owa", action="store_true", help="disable transcoding to webm-opus (iOS v18 and later), will use mp3 instead")
1368
1382
  ap2.add_argument("--no-acode", action="store_true", help="disable audio transcoding")
1369
1383
  ap2.add_argument("--no-bacode", action="store_true", help="disable batch audio transcoding by folder download (zip/tar)")
1370
1384
  ap2.add_argument("--ac-maxage", metavar="SEC", type=int, default=86400, help="delete cached transcode output after \033[33mSEC\033[0m seconds")
copyparty/__version__.py CHANGED
@@ -1,8 +1,8 @@
1
1
  # coding: utf-8
2
2
 
3
- VERSION = (1, 16, 9)
3
+ VERSION = (1, 16, 11)
4
4
  CODENAME = "COPYparty"
5
- BUILD_DT = (2025, 1, 22)
5
+ BUILD_DT = (2025, 1, 27)
6
6
 
7
7
  S_VERSION = ".".join(map(str, VERSION))
8
8
  S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)
copyparty/cfg.py CHANGED
@@ -93,6 +93,7 @@ def vf_vmap() :
93
93
  "unlist",
94
94
  "u2abort",
95
95
  "u2ts",
96
+ "ups_who",
96
97
  ):
97
98
  ret[k] = k
98
99
  return ret
copyparty/httpcli.py CHANGED
@@ -1891,6 +1891,9 @@ class HttpCli(object):
1891
1891
  if "stash" in opt:
1892
1892
  return self.handle_stash(False)
1893
1893
 
1894
+ xm = []
1895
+ xm_rsp = {}
1896
+
1894
1897
  if "save" in opt:
1895
1898
  post_sz, _, _, _, _, path, _ = self.dump_to_file(False)
1896
1899
  self.log("urlform: %d bytes, %r" % (post_sz, path))
@@ -1913,7 +1916,7 @@ class HttpCli(object):
1913
1916
  plain = plain[4:]
1914
1917
  xm = self.vn.flags.get("xm")
1915
1918
  if xm:
1916
- runhook(
1919
+ xm_rsp = runhook(
1917
1920
  self.log,
1918
1921
  self.conn.hsrv.broker,
1919
1922
  None,
@@ -1937,6 +1940,13 @@ class HttpCli(object):
1937
1940
  except Exception as ex:
1938
1941
  self.log(repr(ex))
1939
1942
 
1943
+ if "xm" in opt:
1944
+ if xm:
1945
+ self.loud_reply(xm_rsp.get("stdout") or "", status=202)
1946
+ return True
1947
+ else:
1948
+ return self.handle_get()
1949
+
1940
1950
  if "get" in opt:
1941
1951
  return self.handle_get()
1942
1952
 
@@ -5120,6 +5130,12 @@ class HttpCli(object):
5120
5130
  adm = "*" in vol.axs.uadmin or self.uname in vol.axs.uadmin
5121
5131
  dots = "*" in vol.axs.udot or self.uname in vol.axs.udot
5122
5132
 
5133
+ lvl = int(vol.flags["ups_who"])
5134
+ if not lvl:
5135
+ continue
5136
+ elif lvl == 1 and not adm:
5137
+ continue
5138
+
5123
5139
  n = 1000
5124
5140
  q = "select sz, rd, fn, ip, at from up where at>0 order by at desc"
5125
5141
  for sz, rd, fn, ip, at in cur.execute(q):
copyparty/th_cli.py CHANGED
@@ -6,7 +6,7 @@ import os
6
6
  from .__init__ import TYPE_CHECKING
7
7
  from .authsrv import VFS
8
8
  from .bos import bos
9
- from .th_srv import HAVE_WEBP, thumb_path
9
+ from .th_srv import EXTS_AC, HAVE_WEBP, thumb_path
10
10
  from .util import Cooldown
11
11
 
12
12
  if TYPE_CHECKING:
@@ -54,13 +54,17 @@ class ThumbCli(object):
54
54
  if is_vid and "dvthumb" in dbv.flags:
55
55
  return None
56
56
 
57
- want_opus = fmt in ("opus", "caf", "mp3")
57
+ want_opus = fmt in EXTS_AC
58
58
  is_au = ext in self.fmt_ffa
59
59
  is_vau = want_opus and ext in self.fmt_ffv
60
60
  if is_au or is_vau:
61
61
  if want_opus:
62
62
  if self.args.no_acode:
63
63
  return None
64
+ elif fmt == "caf" and self.args.no_caf:
65
+ fmt = "mp3"
66
+ elif fmt == "owa" and self.args.no_owa:
67
+ fmt = "mp3"
64
68
  else:
65
69
  if "dathumb" in dbv.flags:
66
70
  return None
copyparty/th_srv.py CHANGED
@@ -43,6 +43,9 @@ HAVE_HEIF = False
43
43
  HAVE_AVIF = False
44
44
  HAVE_WEBP = False
45
45
 
46
+ EXTS_TH = set(["jpg", "webp", "png"])
47
+ EXTS_AC = set(["opus", "owa", "caf", "mp3"])
48
+
46
49
  try:
47
50
  if os.environ.get("PRTY_NO_PIL"):
48
51
  raise Exception()
@@ -136,7 +139,7 @@ def thumb_path(histpath , rem , mtime , fmt , ffa ) :
136
139
  h = hashlib.sha512(afsenc(fn)).digest()
137
140
  fn = ub64enc(h).decode("ascii")[:24]
138
141
 
139
- if fmt in ("opus", "caf", "mp3"):
142
+ if fmt in EXTS_AC:
140
143
  cat = "ac"
141
144
  else:
142
145
  fc = fmt[:1]
@@ -331,9 +334,10 @@ class ThumbSrv(object):
331
334
  ap_unpk = abspath
332
335
 
333
336
  if not bos.path.exists(tpath):
334
- want_mp3 = tpath.endswith(".mp3")
335
- want_opus = tpath.endswith(".opus") or tpath.endswith(".caf")
336
- want_png = tpath.endswith(".png")
337
+ tex = tpath.rsplit(".", 1)[-1]
338
+ want_mp3 = tex == "mp3"
339
+ want_opus = tex in ("opus", "owa", "caf")
340
+ want_png = tex == "png"
337
341
  want_au = want_mp3 or want_opus
338
342
  for lib in self.args.th_dec:
339
343
  can_au = lib == "ff" and (
@@ -750,47 +754,102 @@ class ThumbSrv(object):
750
754
  if "ac" not in tags:
751
755
  raise Exception("not audio")
752
756
 
757
+ sq = "%dk" % (self.args.q_opus,)
758
+ bq = sq.encode("ascii")
759
+ if tags["ac"][1] == "opus":
760
+ enc = "-c:a copy"
761
+ else:
762
+ enc = "-c:a libopus -b:a " + sq
763
+
764
+ fun = self._conv_caf if fmt == "caf" else self._conv_owa
765
+
766
+ fun(abspath, tpath, tags, rawtags, enc, bq, vn)
767
+
768
+ def _conv_owa(
769
+ self,
770
+ abspath ,
771
+ tpath ,
772
+ tags ,
773
+ rawtags ,
774
+ enc ,
775
+ bq ,
776
+ vn ,
777
+ ) :
778
+ if tpath.endswith(".owa"):
779
+ container = b"webm"
780
+ tagset = [b"-map_metadata", b"-1"]
781
+ else:
782
+ container = b"opus"
783
+ tagset = self.big_tags(rawtags)
784
+
785
+ self.log("conv2 %s [%s]" % (container, enc), 6)
786
+ benc = enc.encode("ascii").split(b" ")
787
+
788
+ # fmt: off
789
+ cmd = [
790
+ b"ffmpeg",
791
+ b"-nostdin",
792
+ b"-v", b"error",
793
+ b"-hide_banner",
794
+ b"-i", fsenc(abspath),
795
+ ] + tagset + [
796
+ b"-map", b"0:a:0",
797
+ ] + benc + [
798
+ b"-f", container,
799
+ fsenc(tpath)
800
+ ]
801
+ # fmt: on
802
+ self._run_ff(cmd, vn, oom=300)
803
+
804
+ def _conv_caf(
805
+ self,
806
+ abspath ,
807
+ tpath ,
808
+ tags ,
809
+ rawtags ,
810
+ enc ,
811
+ bq ,
812
+ vn ,
813
+ ) :
814
+ tmp_opus = tpath + ".opus"
815
+ try:
816
+ wunlink(self.log, tmp_opus, vn.flags)
817
+ except:
818
+ pass
819
+
753
820
  try:
754
821
  dur = tags[".dur"][1]
755
822
  except:
756
823
  dur = 0
757
824
 
758
- src_opus = abspath.lower().endswith(".opus") or tags["ac"][1] == "opus"
759
- want_caf = tpath.endswith(".caf")
760
- tmp_opus = tpath
761
- if want_caf:
762
- tmp_opus = tpath + ".opus"
763
- try:
764
- wunlink(self.log, tmp_opus, vn.flags)
765
- except:
766
- pass
767
-
768
- caf_src = abspath if src_opus else tmp_opus
769
- bq = ("%dk" % (self.args.q_opus,)).encode("ascii")
825
+ self.log("conv2 caf-tmp [%s]" % (enc,), 6)
826
+ benc = enc.encode("ascii").split(b" ")
770
827
 
771
- if not want_caf or not src_opus:
772
- # fmt: off
773
- cmd = [
774
- b"ffmpeg",
775
- b"-nostdin",
776
- b"-v", b"error",
777
- b"-hide_banner",
778
- b"-i", fsenc(abspath),
779
- ] + self.big_tags(rawtags) + [
780
- b"-map", b"0:a:0",
781
- b"-c:a", b"libopus",
782
- b"-b:a", bq,
783
- fsenc(tmp_opus)
784
- ]
785
- # fmt: on
786
- self._run_ff(cmd, vn, oom=300)
828
+ # fmt: off
829
+ cmd = [
830
+ b"ffmpeg",
831
+ b"-nostdin",
832
+ b"-v", b"error",
833
+ b"-hide_banner",
834
+ b"-i", fsenc(abspath),
835
+ b"-map_metadata", b"-1",
836
+ b"-map", b"0:a:0",
837
+ ] + benc + [
838
+ b"-f", b"opus",
839
+ fsenc(tmp_opus)
840
+ ]
841
+ # fmt: on
842
+ self._run_ff(cmd, vn, oom=300)
787
843
 
788
844
  # iOS fails to play some "insufficiently complex" files
789
845
  # (average file shorter than 8 seconds), so of course we
790
846
  # fix that by mixing in some inaudible pink noise :^)
791
847
  # 6.3 sec seems like the cutoff so lets do 7, and
792
848
  # 7 sec of psyqui-musou.opus @ 3:50 is 174 KiB
793
- if want_caf and (dur < 20 or bos.path.getsize(caf_src) < 256 * 1024):
849
+ sz = bos.path.getsize(tmp_opus)
850
+ if dur < 20 or sz < 256 * 1024:
851
+ zs = bq.decode("ascii")
852
+ self.log("conv2 caf-transcode; dur=%d sz=%d q=%s" % (dur, sz, zs), 6)
794
853
  # fmt: off
795
854
  cmd = [
796
855
  b"ffmpeg",
@@ -809,15 +868,16 @@ class ThumbSrv(object):
809
868
  # fmt: on
810
869
  self._run_ff(cmd, vn, oom=300)
811
870
 
812
- elif want_caf:
871
+ else:
813
872
  # simple remux should be safe
873
+ self.log("conv2 caf-remux; dur=%d sz=%d" % (dur, sz), 6)
814
874
  # fmt: off
815
875
  cmd = [
816
876
  b"ffmpeg",
817
877
  b"-nostdin",
818
878
  b"-v", b"error",
819
879
  b"-hide_banner",
820
- b"-i", fsenc(abspath if src_opus else tmp_opus),
880
+ b"-i", fsenc(tmp_opus),
821
881
  b"-map_metadata", b"-1",
822
882
  b"-map", b"0:a:0",
823
883
  b"-c:a", b"copy",
@@ -827,11 +887,10 @@ class ThumbSrv(object):
827
887
  # fmt: on
828
888
  self._run_ff(cmd, vn, oom=300)
829
889
 
830
- if tmp_opus != tpath:
831
- try:
832
- wunlink(self.log, tmp_opus, vn.flags)
833
- except:
834
- pass
890
+ try:
891
+ wunlink(self.log, tmp_opus, vn.flags)
892
+ except:
893
+ pass
835
894
 
836
895
  def big_tags(self, raw_tags ) :
837
896
  ret = []
@@ -887,7 +946,7 @@ class ThumbSrv(object):
887
946
 
888
947
  def _clean(self, cat , thumbpath ) :
889
948
  # self.log("cln {}".format(thumbpath))
890
- exts = ["jpg", "webp", "png"] if cat == "th" else ["opus", "caf", "mp3"]
949
+ exts = EXTS_TH if cat == "th" else EXTS_AC
891
950
  maxage = getattr(self.args, cat + "_maxage")
892
951
  now = time.time()
893
952
  prev_b64 = None
copyparty/util.py CHANGED
@@ -222,6 +222,7 @@ URL_BUG = URL_PRJ + "/issues/new?labels=bug&template=bug_report.md"
222
222
  HTTPCODE = {
223
223
  200: "OK",
224
224
  201: "Created",
225
+ 202: "Accepted",
225
226
  204: "No Content",
226
227
  206: "Partial Content",
227
228
  207: "Multi-Status",
@@ -309,6 +310,7 @@ DAV_ALLPROPS = set(DAV_ALLPROP_L)
309
310
 
310
311
  MIMES = {
311
312
  "opus": "audio/ogg; codecs=opus",
313
+ "owa": "audio/webm; codecs=opus",
312
314
  }
313
315
 
314
316
 
@@ -3391,7 +3393,7 @@ def _zmq_hook(
3391
3393
  msg ,
3392
3394
  wait ,
3393
3395
  sp_ka ,
3394
- ) :
3396
+ ) :
3395
3397
  import zmq
3396
3398
 
3397
3399
  try:
@@ -3402,6 +3404,7 @@ def _zmq_hook(
3402
3404
  mtx = ZMQ["mtx"]
3403
3405
 
3404
3406
  ret = ""
3407
+ nret = 0
3405
3408
  t0 = time.time()
3406
3409
  if verbose and log:
3407
3410
  log("hook(%s) %r entering zmq-main-lock" % (src, cmd), 6)
@@ -3428,18 +3431,21 @@ def _zmq_hook(
3428
3431
 
3429
3432
  if mode == "pub":
3430
3433
  sck = ctx.socket(zmq.PUB)
3434
+ sck.setsockopt(zmq.LINGER, 0)
3431
3435
  sck.bind(uri)
3432
3436
  time.sleep(1) # give clients time to connect; avoids losing first msg
3433
3437
  elif mode == "push":
3434
3438
  sck = ctx.socket(zmq.PUSH)
3435
- sck.bind(uri)
3436
3439
  if timeout:
3437
3440
  sck.SNDTIMEO = int(timeout * 1000)
3441
+ sck.setsockopt(zmq.LINGER, 0)
3442
+ sck.bind(uri)
3438
3443
  elif mode == "req":
3439
3444
  sck = ctx.socket(zmq.REQ)
3440
- sck.connect(uri)
3441
3445
  if timeout:
3442
3446
  sck.RCVTIMEO = int(timeout * 1000)
3447
+ sck.setsockopt(zmq.LINGER, 0)
3448
+ sck.connect(uri)
3443
3449
  else:
3444
3450
  raise Exception()
3445
3451
 
@@ -3460,6 +3466,10 @@ def _zmq_hook(
3460
3466
  log("hook(%s) %r awaiting ack from req" % (src, cmd), 6)
3461
3467
  try:
3462
3468
  ret = sck.recv().decode("utf-8", "replace")
3469
+ if ret.startswith("return "):
3470
+ m = re.search("^return ([0-9]+)", ret[:12])
3471
+ if m:
3472
+ nret = int(m.group(1))
3463
3473
  except:
3464
3474
  sck.close()
3465
3475
  del ZMQ[cmd] # bad state; must reset
@@ -3473,7 +3483,7 @@ def _zmq_hook(
3473
3483
  if wait > 0:
3474
3484
  time.sleep(wait)
3475
3485
 
3476
- return ret
3486
+ return nret, ret
3477
3487
 
3478
3488
 
3479
3489
  def _runhook(
@@ -3520,12 +3530,9 @@ def _runhook(
3520
3530
  arg = txt or ap
3521
3531
 
3522
3532
  if acmd[0].startswith("zmq:"):
3523
- zs = "zmq-error"
3524
- try:
3525
- zs = _zmq_hook(log, verbose, src, acmd[0][4:].lower(), arg, wait, sp_ka)
3526
- except Exception as ex:
3527
- if log:
3528
- log("zeromq failed: %r" % (ex,))
3533
+ zi, zs = _zmq_hook(log, verbose, src, acmd[0][4:].lower(), arg, wait, sp_ka)
3534
+ if zi:
3535
+ raise Exception("zmq says %d" % (zi,))
3529
3536
  return {"rc": 0, "stdout": zs}
3530
3537
 
3531
3538
  acmd += [arg]
@@ -3606,6 +3613,8 @@ def runhook(
3606
3613
  elif k in ret:
3607
3614
  if k == "rc" and v:
3608
3615
  ret[k] = v
3616
+ elif k == "stdout" and v and not ret[k]:
3617
+ ret[k] = v
3609
3618
  else:
3610
3619
  ret[k] = v
3611
3620
  except Exception as ex:
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 = "2.8"
5
- S_BUILD_DT = "2025-01-21"
4
+ S_VERSION = "2.9"
5
+ S_BUILD_DT = "2025-01-27"
6
6
 
7
7
  """
8
8
  u2c.py: upload to copyparty
@@ -234,6 +234,10 @@ CLEN = "Content-Length"
234
234
 
235
235
  web = None # type: HCli
236
236
 
237
+ links = [] # type: list[str]
238
+ linkmtx = threading.Lock()
239
+ linkfile = None
240
+
237
241
 
238
242
  class File(object):
239
243
  """an up2k upload task; represents a single file"""
@@ -759,6 +763,29 @@ def get_hashlist(file, pcb, mth):
759
763
  file.kchunks[k] = [v1, v2]
760
764
 
761
765
 
766
+ def printlink(ar, purl, name, fk):
767
+ if not name:
768
+ url = purl # srch
769
+ else:
770
+ name = quotep(name.encode("utf-8", WTF8)).decode("utf-8")
771
+ if fk:
772
+ url = "%s%s?k=%s" % (purl, name, fk)
773
+ else:
774
+ url = "%s%s" % (purl, name)
775
+
776
+ url = "%s/%s" % (ar.burl, url.lstrip("/"))
777
+
778
+ with linkmtx:
779
+ if ar.u:
780
+ links.append(url)
781
+ if ar.ud:
782
+ print(url)
783
+ if linkfile:
784
+ zs = "%s\n" % (url,)
785
+ zb = zs.encode("utf-8", "replace")
786
+ linkfile.write(zb)
787
+
788
+
762
789
  def handshake(ar, file, search):
763
790
  # type: (argparse.Namespace, File, bool) -> tuple[list[str], bool]
764
791
  """
@@ -830,12 +857,17 @@ def handshake(ar, file, search):
830
857
  raise Exception(txt)
831
858
 
832
859
  if search:
860
+ if ar.uon and r["hits"]:
861
+ printlink(ar, r["hits"][0]["rp"], "", "")
833
862
  return r["hits"], False
834
863
 
835
864
  file.url = quotep(r["purl"].encode("utf-8", WTF8)).decode("utf-8")
836
865
  file.name = r["name"]
837
866
  file.wark = r["wark"]
838
867
 
868
+ if ar.uon and not r["hash"]:
869
+ printlink(ar, file.url, r["name"], r.get("fk"))
870
+
839
871
  return r["hash"], r["sprs"]
840
872
 
841
873
 
@@ -1470,7 +1502,7 @@ class APF(argparse.ArgumentDefaultsHelpFormatter, argparse.RawDescriptionHelpFor
1470
1502
 
1471
1503
 
1472
1504
  def main():
1473
- global web
1505
+ global web, linkfile
1474
1506
 
1475
1507
  time.strptime("19970815", "%Y%m%d") # python#7980
1476
1508
  "".encode("idna") # python#29288
@@ -1507,6 +1539,11 @@ source file/folder selection uses rsync syntax, meaning that:
1507
1539
  ap.add_argument("--spd", action="store_true", help="print speeds for each file")
1508
1540
  ap.add_argument("--version", action="store_true", help="show version and exit")
1509
1541
 
1542
+ ap = app.add_argument_group("print links")
1543
+ ap.add_argument("-u", action="store_true", help="print list of download-links after all uploads finished")
1544
+ ap.add_argument("-ud", action="store_true", help="print download-link after each upload finishes")
1545
+ ap.add_argument("-uf", type=unicode, metavar="PATH", help="print list of download-links to file")
1546
+
1510
1547
  ap = app.add_argument_group("compatibility")
1511
1548
  ap.add_argument("--cls", action="store_true", help="clear screen before start")
1512
1549
  ap.add_argument("--rh", type=int, metavar="TRIES", default=0, help="resolve server hostname before upload (good for buggy networks, but TLS certs will break)")
@@ -1592,6 +1629,10 @@ source file/folder selection uses rsync syntax, meaning that:
1592
1629
  ar.x = "|".join(ar.x or [])
1593
1630
 
1594
1631
  setattr(ar, "wlist", ar.url == "-")
1632
+ setattr(ar, "uon", ar.u or ar.ud or ar.uf)
1633
+
1634
+ if ar.uf:
1635
+ linkfile = open(ar.uf, "wb")
1595
1636
 
1596
1637
  for k in "dl dr drd wlist".split():
1597
1638
  errs = []
@@ -1654,6 +1695,12 @@ source file/folder selection uses rsync syntax, meaning that:
1654
1695
  ar.z = True
1655
1696
  ctl = Ctl(ar, ctl.stats)
1656
1697
 
1698
+ if links:
1699
+ print()
1700
+ print("\n".join(links))
1701
+ if linkfile:
1702
+ linkfile.close()
1703
+
1657
1704
  if ctl.errs:
1658
1705
  print("WARNING: %d errors" % (ctl.errs))
1659
1706
 
Binary file
Binary file
copyparty/web/md.js.gz CHANGED
Binary file
copyparty/web/md2.js.gz CHANGED
Binary file
copyparty/web/svcs.js.gz CHANGED
Binary file
copyparty/web/up2k.js.gz CHANGED
Binary file
copyparty/web/util.js.gz CHANGED
Binary file
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: copyparty
3
- Version: 1.16.9
3
+ Version: 1.16.11
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
@@ -410,10 +410,19 @@ same order here too
410
410
  * iPhones: the volume control doesn't work because [apple doesn't want it to](https://developer.apple.com/library/archive/documentation/AudioVideo/Conceptual/Using_HTML5_Audio_Video/Device-SpecificConsiderations/Device-SpecificConsiderations.html#//apple_ref/doc/uid/TP40009523-CH5-SW11)
411
411
  * `AudioContext` will probably never be a viable workaround as apple introduces new issues faster than they fix current ones
412
412
 
413
+ * iPhones: music volume goes on a rollercoaster during song changes
414
+ * nothing I can do about it because `AudioContext` is still broken in safari
415
+
413
416
  * iPhones: the preload feature (in the media-player-options tab) can cause a tiny audio glitch 20sec before the end of each song, but disabling it may cause worse iOS bugs to appear instead
414
417
  * just a hunch, but disabling preloading may cause playback to stop entirely, or possibly mess with bluetooth speakers
415
418
  * tried to add a tooltip regarding this but looks like apple broke my tooltips
416
419
 
420
+ * iPhones: preloaded awo files make safari log MEDIA_ERR_NETWORK errors as playback starts, but the song plays just fine so eh whatever
421
+ * awo, opus-weba, is apple's new take on opus support, replacing opus-caf which was technically limited to cbr opus
422
+
423
+ * iPhones: preloading another awo file may cause playback to stop
424
+ * can be somewhat mitigated with `mp.au.play()` in `mp.onpreload` but that can hit a race condition in safari that starts playing the same audio object twice in parallel...
425
+
417
426
  * Windows: folders cannot be accessed if the name ends with `.`
418
427
  * python or windows bug
419
428
 
@@ -945,6 +954,8 @@ will show uploader IP and upload-time if the visitor has the admin permission
945
954
 
946
955
  * global-option `--ups-when` makes upload-time visible to all users, and not just admins
947
956
 
957
+ * global-option `--ups-who` (volflag `ups_who`) specifies who gets access (0=nobody, 1=admins, 2=everyone), default=2
958
+
948
959
  note that the [🧯 unpost](#unpost) feature is better suited for viewing *your own* recent uploads, as it includes the option to undo/delete them
949
960
 
950
961
 
@@ -984,6 +995,11 @@ open the `[🎺]` media-player-settings tab to configure it,
984
995
  * `[aac]` converts `aac` and `m4a` files into opus (if supported by browser) or mp3
985
996
  * `[oth]` converts all other known formats into opus (if supported by browser) or mp3
986
997
  * `aac|ac3|aif|aiff|alac|alaw|amr|ape|au|dfpwm|dts|flac|gsm|it|m4a|mo3|mod|mp2|mp3|mpc|mptm|mt2|mulaw|ogg|okt|opus|ra|s3m|tak|tta|ulaw|wav|wma|wv|xm|xpk`
998
+ * "transcode to":
999
+ * `[opus]` produces an `opus` whenever transcoding is necessary (the best choice on Android and PCs)
1000
+ * `[awo]` is `opus` in a `weba` file, good for iPhones (iOS 17.5 and newer) but Apple is still fixing some state-confusion bugs as of iOS 18.2.1
1001
+ * `[caf]` is `opus` in a `caf` file, good for iPhones (iOS 11 through 17), technically unsupported by Apple but works for the mos tpart
1002
+ * `[mp3]` -- the myth, the legend, the undying master of mediocre sound quality that definitely works everywhere
987
1003
  * "tint" reduces the contrast of the playback bar
988
1004
 
989
1005
 
@@ -1,17 +1,17 @@
1
1
  copyparty/__init__.py,sha256=VR6ZZhB9IxaK5TDXDTBM_OIP5ydkrdbaEnstktLM__s,2649
2
- copyparty/__main__.py,sha256=OvcLYTmA_IbuoA7PzqfozlfMUoWVhgK8OZ89htVQeHQ,114236
3
- copyparty/__version__.py,sha256=rMDKjaOJgDxkWi5DqhkiIl0LpXkT0uLtMhbMTaIUfuM,251
2
+ copyparty/__main__.py,sha256=5igJxZ_H08z51qaF_YrWsRp1SNzV810RHvVq38lHrtI,115336
3
+ copyparty/__version__.py,sha256=3EfC4asnk6XDUfAe5nBClbNInAuJvogNSfslz0AxznQ,252
4
4
  copyparty/authsrv.py,sha256=uADnPkHiTH0UcBUr-w5hdUare6p-74O7cJQaL9lkpws,104513
5
5
  copyparty/broker_mp.py,sha256=QdOXXvV2Xn6J0CysEqyY3GZbqxQMyWnTpnba-a5lMc0,4987
6
6
  copyparty/broker_mpw.py,sha256=PpSS4SK3pItlpfD8OwVr3QmJEPKlUgaf2nuMOozixgU,3347
7
7
  copyparty/broker_thr.py,sha256=fjoYtpSscUA7-nMl4r1n2R7UK3J9lrvLS3rUZ-iJzKQ,1721
8
8
  copyparty/broker_util.py,sha256=76mfnFOpX1gUUvtjm8UQI7jpTIaVINX10QonM-B7ggc,1680
9
9
  copyparty/cert.py,sha256=0ZAPeXeMR164vWn9GQU3JDKooYXEq_NOQkDeg543ivg,8009
10
- copyparty/cfg.py,sha256=WY4gMZ42T-mBitZivxh5wcN1JIN4i2m8Gg8kGumnDPw,10619
10
+ copyparty/cfg.py,sha256=5QJPyuCUoQn2svd-4zzFmC0As13b08qenNJhPF5YkOU,10638
11
11
  copyparty/dxml.py,sha256=vu5uZQtwvwoqnFHbULs2Zh_y2DETu0T-ENpMZ1i2CV4,2505
12
12
  copyparty/fsutil.py,sha256=IVOFG8zBQPMQDDv7RIStSJHwHiAnVNROZS37O5k465A,4524
13
13
  copyparty/ftpd.py,sha256=T97SFS7JFtvRLbJX8C4fJSYwe13vhN3-E6emtlVmqLA,17608
14
- copyparty/httpcli.py,sha256=lTMSK9xLXhRsPo1XPeV_RRTJZ6jQgx17wt_BFdQ26M4,215837
14
+ copyparty/httpcli.py,sha256=yXpUvHcU3AKwQLEyvC-p-8mWdcuDJSueWg7J2gHzMCs,216276
15
15
  copyparty/httpconn.py,sha256=mQSgljh0Q-jyWjF4tQLrHbRKRe9WKl19kGqsGMsJpWo,6880
16
16
  copyparty/httpsrv.py,sha256=pxH_Eh8ElBLvOEDejgpP9Bvk65HNEou-03aYIcgXhrs,18090
17
17
  copyparty/ico.py,sha256=eWSxEae4wOCfheHl-m-wchYvFRAR_97kJDb4NGaB-Z8,3561
@@ -28,11 +28,11 @@ copyparty/svchub.py,sha256=mgZ2UndrMqWuUywecsCgVz488WoS0EFHR94VvWZjeT0,41454
28
28
  copyparty/szip.py,sha256=HFtnwOiBgx0HMLUf-h_T84zSlRijPxmhRo5PM613kRA,8602
29
29
  copyparty/tcpsrv.py,sha256=2q18dGR8jnezA4SMfUXa-wrGRGX3nHIwkxkWvkTzF2A,19889
30
30
  copyparty/tftpd.py,sha256=PXgG4rTmiaU_TavSyZWD5cFphdfChs9YvNY21qfExt8,13611
31
- copyparty/th_cli.py,sha256=1B8el4NNs5cNyJyjOPiAdvLOX2HQXaebsHn6VLdZ_gU,4630
32
- copyparty/th_srv.py,sha256=uAcz-wZJQEG5KavcZDkwToONZujyoOeC4oCxxKTD5us,29575
31
+ copyparty/th_cli.py,sha256=PxDAmUvO_8Vm5edXiWtsCft0Fw69QL9rCHf9zLmUNeA,4800
32
+ copyparty/th_srv.py,sha256=tHbh_Ve3v8tYclWH2thLs5oFufeXgJi1duUMveKIx9k,30725
33
33
  copyparty/u2idx.py,sha256=G6MDbD4I_sJSOwaNFZ6XLTQhnEDrB12pVKuKhzQ_leE,13676
34
34
  copyparty/up2k.py,sha256=alObSL1EVyPedi_24s7DBFlBzA0ZMkr9_jOtzdRV3vk,175427
35
- copyparty/util.py,sha256=Msbj0esxQNmEEOheL6ffubMeyPLEqyRnMJ1g5t2pI68,99012
35
+ copyparty/util.py,sha256=Y_znSn3hBNYaaduwcCB7mmBYsi6vv9CYC1zJ9rq9yeQ,99435
36
36
  copyparty/bos/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
37
37
  copyparty/bos/bos.py,sha256=Wb7eWsXJgR5AFlBR9ZOyKrLTwy-Kct9RrGiOu4Jo37Y,1622
38
38
  copyparty/bos/path.py,sha256=yEjCq2ki9CvxA5sCT8pS0keEXwugs0ZeUyUhdBziOCI,777
@@ -55,17 +55,17 @@ copyparty/stolen/ifaddr/_posix.py,sha256=-67NdfGrCktfQPakT2fLbjl2U00QMvyBGkSvrUu
55
55
  copyparty/stolen/ifaddr/_shared.py,sha256=uNC4SdEIgdSLKvuUzsf1aM-H1Xrc_9mpLoOT43YukGs,6206
56
56
  copyparty/stolen/ifaddr/_win32.py,sha256=EE-QyoBgeB7lYQ6z62VjXNaRozaYfCkaJBHGNA8QtZM,4026
57
57
  copyparty/web/baguettebox.js.gz,sha256=_amC3ipOrXKEFz8DsVP-JEl49VjMQYiKyF78eWfG-uk,7965
58
- copyparty/web/browser.css.gz,sha256=kurx_iA-KxLYx8PqJsn0bJVjkAxP-0YTOHSV9l_oouo,11645
58
+ copyparty/web/browser.css.gz,sha256=fs6dyfIin4XNWlw3d6dOInZaW0U33-hZKMwRCB3ge-Y,11645
59
59
  copyparty/web/browser.html,sha256=dekrZQ6w8ciB-QPlp-mjcuzUVKlsCYcvvi6efmXRfQE,4822
60
- copyparty/web/browser.js.gz,sha256=V9_Xz8uZsJ01MNoc480L0SMduAOINGrcQ2296FNRoeA,89860
60
+ copyparty/web/browser.js.gz,sha256=CKdT13IN5XDOExgTnCFYMgxP2Fr9mdjRFIQv1OZQkEM,91199
61
61
  copyparty/web/browser2.html,sha256=NRUZ08GH-e2YcGXcoz0UjYg6JIVF42u4IMX4HHwWTmg,1587
62
62
  copyparty/web/cf.html,sha256=lJThtNFNAQT1ClCHHlivAkDGE0LutedwopXD62Z8Nys,589
63
63
  copyparty/web/dbg-audio.js.gz,sha256=Ma-KZtK8LnmiwNvNKFKXMPYl_Nn_3U7GsJ6-DRWC2HE,688
64
64
  copyparty/web/md.css.gz,sha256=UZpN0J7ubVM05CZkbZYkQRJeGgJt_GNDEzKTGSQd8h4,2032
65
65
  copyparty/web/md.html,sha256=hz-xJVfKtaeTUQn3tGh7ebIMvLbOjVKkrMhsCTr3lGM,4200
66
- copyparty/web/md.js.gz,sha256=tw9vS9yZzyVW4FNOTv0MMHBdBEedfahZRZ8WhVJDu4A,4180
66
+ copyparty/web/md.js.gz,sha256=m5dpIskZZ8FD9X-L0EWpoZP-yLOo-PCWJ7j4AFtHVRI,4163
67
67
  copyparty/web/md2.css.gz,sha256=uIVHKScThdbcfhXNSHgKZnALYpxbnXC-WuEzOJ20Lpc,699
68
- copyparty/web/md2.js.gz,sha256=w0Ve06BUcyZfcWR8DAPpCj-kI-uL_wTL2aX_0PsNc-I,8363
68
+ copyparty/web/md2.js.gz,sha256=z3JASmIT5PVEWgTpHpc4uBkZsbqgtSF4Nt0w2b_8Mek,8355
69
69
  copyparty/web/mde.css.gz,sha256=2SkAEDKIRPqywNJ8t_heQaeBQ_R73Rf-pQI_bDoKF6o,942
70
70
  copyparty/web/mde.html,sha256=fRGUlnNhK6ra8P4jskQLUw6Aaef6g6Heh4EEgYhJkxU,1770
71
71
  copyparty/web/mde.js.gz,sha256=kN2eUSvr4mFuksfK4-4LimJmWdwsao39Sea2lWtu8L0,2224
@@ -81,14 +81,14 @@ copyparty/web/splash.css.gz,sha256=S8_A7JJl71xACRBYGzafeaD82OacW6Fa7oKPiNyrhAs,1
81
81
  copyparty/web/splash.html,sha256=QEnTH9QZXFmAuyVtgqOuuHKBtIdi7uclpRqe0ZMewj4,6249
82
82
  copyparty/web/splash.js.gz,sha256=4VqNznN10-bT33IJm3VWzBEJ1s08XZyxFB1TYPUkuAo,2739
83
83
  copyparty/web/svcs.html,sha256=dnE1fG15zOpq7u0GYt8ij6BUv_LTwsiipFeneVYlMsM,14140
84
- copyparty/web/svcs.js.gz,sha256=rcc75HMmoc3KA7Ld2j8X9AKX_elZgwUD6Vnm2F-yj_U,805
84
+ copyparty/web/svcs.js.gz,sha256=lMXEP9W-VlXyANlva4q0ASSxvvHYlE2CrmxGgZXZop0,713
85
85
  copyparty/web/ui.css.gz,sha256=0sHIwGsL3_xH8Uu6N0Ag3bKBTjf-e_yfFbKynEZXAnk,2800
86
- copyparty/web/up2k.js.gz,sha256=f00wmaThk1CoFYzFBG_6KLG0acq60cAn3p12tngqprk,23843
87
- copyparty/web/util.js.gz,sha256=uCCnKWT_FeG-ShKPoj0MtXnBwtdAbyDSM9AP9KiBDjw,15098
86
+ copyparty/web/up2k.js.gz,sha256=N4idIqOefXurVh4ZhN6lv9nFPX_azLwE41vhXT2qBkI,23833
87
+ copyparty/web/util.js.gz,sha256=amPEbzFzNKmkM40jeGYB3h7wpgpHNSxHX19vcNAH8MI,15087
88
88
  copyparty/web/w.hash.js.gz,sha256=l3GpSJD6mcU-1CRWkIj7PybgbjlfSr8oeO3vortIrQk,1105
89
89
  copyparty/web/a/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
90
90
  copyparty/web/a/partyfuse.py,sha256=9p5Hpg_IBiSimv7j9kmPhCGpy-FLXSRUOYnLjJ5JifU,28049
91
- copyparty/web/a/u2c.py,sha256=N9NZHF6CbooNxIYkZMVz7wWTMh88nXCXcd4bDKzaanw,51603
91
+ copyparty/web/a/u2c.py,sha256=8F-uVfu0ceXRehbNLjjRfcBKStP5Lm_vJ_JAhEM6zgk,52994
92
92
  copyparty/web/a/webdav-cfg.bat,sha256=Y4NoGZlksAIg4cBMb7KdJrpKC6Nx97onaTl6yMjaimk,1449
93
93
  copyparty/web/dd/2.png,sha256=gJ14XFPzaw95L6z92fSq9eMPikSQyu-03P1lgiGe0_I,258
94
94
  copyparty/web/dd/3.png,sha256=4lho8Koz5tV7jJ4ODo6GMTScZfkqsT05yp48EDFIlyg,252
@@ -109,9 +109,9 @@ copyparty/web/deps/prismd.css.gz,sha256=ObUlksQVr-OuYlTz-I4B23TeBg2QDVVGRnWBz8cV
109
109
  copyparty/web/deps/scp.woff2,sha256=w99BDU5i8MukkMEL-iW0YO9H4vFFZSPWxbkH70ytaAg,8612
110
110
  copyparty/web/deps/sha512.ac.js.gz,sha256=lFZaCLumgWxrvEuDr4bqdKHsqjX82AbVAb7_F45Yk88,7033
111
111
  copyparty/web/deps/sha512.hw.js.gz,sha256=UAed2_ocklZCnIzcSYz2h4P1ycztlCLj-ewsRTud2lU,7939
112
- copyparty-1.16.9.dist-info/LICENSE,sha256=gOr4h33pCsBEg9uIy9AYmb7qlocL4V9t2uPJS5wllr0,1072
113
- copyparty-1.16.9.dist-info/METADATA,sha256=-gjl5i-kYVmjMeHdJ3ZXxeAu0FFWH2zcD0LTu2rb-Cw,145641
114
- copyparty-1.16.9.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
115
- copyparty-1.16.9.dist-info/entry_points.txt,sha256=4zw6a3rqASywQomiYLObjjlxybaI65LYYOTJwgKz7b0,128
116
- copyparty-1.16.9.dist-info/top_level.txt,sha256=LnYUPsDyk-8kFgM6YJLG4h820DQekn81cObKSu9g-sI,10
117
- copyparty-1.16.9.dist-info/RECORD,,
112
+ copyparty-1.16.11.dist-info/LICENSE,sha256=gOr4h33pCsBEg9uIy9AYmb7qlocL4V9t2uPJS5wllr0,1072
113
+ copyparty-1.16.11.dist-info/METADATA,sha256=Rm11Nqji9bckgX12wuhgCBYYbYRzX_RKei8H1GIW17A,146927
114
+ copyparty-1.16.11.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
115
+ copyparty-1.16.11.dist-info/entry_points.txt,sha256=4zw6a3rqASywQomiYLObjjlxybaI65LYYOTJwgKz7b0,128
116
+ copyparty-1.16.11.dist-info/top_level.txt,sha256=LnYUPsDyk-8kFgM6YJLG4h820DQekn81cObKSu9g-sI,10
117
+ copyparty-1.16.11.dist-info/RECORD,,