copyparty 1.16.8__py3-none-any.whl → 1.16.10__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/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/up2k.py CHANGED
@@ -790,7 +790,7 @@ class Up2k(object):
790
790
  continue
791
791
 
792
792
  self.log("xiu: %d# %r" % (len(wrfs), cmd))
793
- runihook(self.log, cmd, vol, ups)
793
+ runihook(self.log, self.args.hook_v, cmd, vol, ups)
794
794
 
795
795
  def _vis_job_progress(self, job ) :
796
796
  perc = 100 - (len(job["need"]) * 100.0 / (len(job["hash"]) or 1))
@@ -4881,7 +4881,8 @@ class Up2k(object):
4881
4881
  except:
4882
4882
  pass
4883
4883
 
4884
- xbu = self.flags[job["ptop"]].get("xbu")
4884
+ vf = self.flags[job["ptop"]]
4885
+ xbu = vf.get("xbu")
4885
4886
  ap_chk = djoin(pdir, job["name"])
4886
4887
  vp_chk = djoin(job["vtop"], job["prel"], job["name"])
4887
4888
  if xbu:
@@ -4911,7 +4912,7 @@ class Up2k(object):
4911
4912
  if x:
4912
4913
  zvfs = vfs
4913
4914
  pdir, _, job["name"], (vfs, rem) = x
4914
- job["vcfg"] = vfs.flags
4915
+ job["vcfg"] = vf = vfs.flags
4915
4916
  job["ptop"] = vfs.realpath
4916
4917
  job["vtop"] = vfs.vpath
4917
4918
  job["prel"] = rem
@@ -4961,8 +4962,13 @@ class Up2k(object):
4961
4962
  fs = self.fstab.get(pdir)
4962
4963
  if fs == "ok":
4963
4964
  pass
4964
- elif "sparse" in self.flags[job["ptop"]]:
4965
- t = "volflag 'sparse' is forcing use of sparse files for uploads to [%s]"
4965
+ elif "nosparse" in vf:
4966
+ t = "volflag 'nosparse' is preventing creation of sparse files for uploads to [%s]"
4967
+ self.log(t % (job["ptop"],))
4968
+ relabel = True
4969
+ sprs = False
4970
+ elif "sparse" in vf:
4971
+ t = "volflag 'sparse' is forcing creation of sparse files for uploads to [%s]"
4966
4972
  self.log(t % (job["ptop"],))
4967
4973
  relabel = True
4968
4974
  else:
copyparty/util.py CHANGED
@@ -120,6 +120,13 @@ try:
120
120
  except:
121
121
  HAVE_SQLITE3 = False
122
122
 
123
+ try:
124
+ import importlib.util
125
+
126
+ HAVE_ZMQ = bool(importlib.util.find_spec("zmq"))
127
+ except:
128
+ HAVE_ZMQ = False
129
+
123
130
  try:
124
131
  if os.environ.get("PRTY_NO_PSUTIL"):
125
132
  raise Exception()
@@ -208,9 +215,14 @@ META_NOBOTS = '<meta name="robots" content="noindex, nofollow">\n'
208
215
 
209
216
  FFMPEG_URL = "https://www.gyan.dev/ffmpeg/builds/ffmpeg-git-full.7z"
210
217
 
218
+ URL_PRJ = "https://github.com/9001/copyparty"
219
+
220
+ URL_BUG = URL_PRJ + "/issues/new?labels=bug&template=bug_report.md"
221
+
211
222
  HTTPCODE = {
212
223
  200: "OK",
213
224
  201: "Created",
225
+ 202: "Accepted",
214
226
  204: "No Content",
215
227
  206: "Partial Content",
216
228
  207: "Multi-Status",
@@ -298,6 +310,7 @@ DAV_ALLPROPS = set(DAV_ALLPROP_L)
298
310
 
299
311
  MIMES = {
300
312
  "opus": "audio/ogg; codecs=opus",
313
+ "owa": "audio/webm; codecs=opus",
301
314
  }
302
315
 
303
316
 
@@ -470,6 +483,15 @@ def py_desc() :
470
483
  )
471
484
 
472
485
 
486
+ def expat_ver() :
487
+ try:
488
+ import pyexpat
489
+
490
+ return ".".join([str(x) for x in pyexpat.version_info])
491
+ except:
492
+ return "?"
493
+
494
+
473
495
  def _sqlite_ver() :
474
496
  try:
475
497
  co = sqlite3.connect(":memory:")
@@ -3297,6 +3319,7 @@ def _parsehook(
3297
3319
 
3298
3320
  def runihook(
3299
3321
  log ,
3322
+ verbose ,
3300
3323
  cmd ,
3301
3324
  vol ,
3302
3325
  ups ,
@@ -3326,6 +3349,17 @@ def runihook(
3326
3349
  else:
3327
3350
  sp_ka["sin"] = b"\n".join(fsenc(x) for x in aps)
3328
3351
 
3352
+ if acmd[0].startswith("zmq:"):
3353
+ try:
3354
+ msg = sp_ka["sin"].decode("utf-8", "replace")
3355
+ _zmq_hook(log, verbose, "xiu", acmd[0][4:].lower(), msg, wait, sp_ka)
3356
+ if verbose and log:
3357
+ log("hook(xiu) %r OK" % (cmd,), 6)
3358
+ except Exception as ex:
3359
+ if log:
3360
+ log("zeromq failed: %r" % (ex,))
3361
+ return True
3362
+
3329
3363
  t0 = time.time()
3330
3364
  if fork:
3331
3365
  Daemon(runcmd, cmd, bcmd, ka=sp_ka)
@@ -3335,15 +3369,126 @@ def runihook(
3335
3369
  retchk(rc, bcmd, err, log, 5)
3336
3370
  return False
3337
3371
 
3338
- wait -= time.time() - t0
3339
- if wait > 0:
3340
- time.sleep(wait)
3372
+ if wait:
3373
+ wait -= time.time() - t0
3374
+ if wait > 0:
3375
+ time.sleep(wait)
3341
3376
 
3342
3377
  return True
3343
3378
 
3344
3379
 
3380
+ ZMQ = {}
3381
+ ZMQ_DESC = {
3382
+ "pub": "fire-and-forget to all/any connected SUB-clients",
3383
+ "push": "fire-and-forget to one of the connected PULL-clients",
3384
+ "req": "send messages to a REP-server and blocking-wait for ack",
3385
+ }
3386
+
3387
+
3388
+ def _zmq_hook(
3389
+ log ,
3390
+ verbose ,
3391
+ src ,
3392
+ cmd ,
3393
+ msg ,
3394
+ wait ,
3395
+ sp_ka ,
3396
+ ) :
3397
+ import zmq
3398
+
3399
+ try:
3400
+ mtx = ZMQ["mtx"]
3401
+ except:
3402
+ ZMQ["mtx"] = threading.Lock()
3403
+ time.sleep(0.1)
3404
+ mtx = ZMQ["mtx"]
3405
+
3406
+ ret = ""
3407
+ nret = 0
3408
+ t0 = time.time()
3409
+ if verbose and log:
3410
+ log("hook(%s) %r entering zmq-main-lock" % (src, cmd), 6)
3411
+
3412
+ with mtx:
3413
+ try:
3414
+ mode, sck, mtx = ZMQ[cmd]
3415
+ except:
3416
+ mode, uri = cmd.split(":", 1)
3417
+ try:
3418
+ desc = ZMQ_DESC[mode]
3419
+ if log:
3420
+ t = "libzmq(%s) pyzmq(%s) init(%s); %s"
3421
+ log(t % (zmq.zmq_version(), zmq.__version__, cmd, desc))
3422
+ except:
3423
+ raise Exception("the only supported ZMQ modes are REQ PUB PUSH")
3424
+
3425
+ try:
3426
+ ctx = ZMQ["ctx"]
3427
+ except:
3428
+ ctx = ZMQ["ctx"] = zmq.Context()
3429
+
3430
+ timeout = sp_ka["timeout"]
3431
+
3432
+ if mode == "pub":
3433
+ sck = ctx.socket(zmq.PUB)
3434
+ sck.setsockopt(zmq.LINGER, 0)
3435
+ sck.bind(uri)
3436
+ time.sleep(1) # give clients time to connect; avoids losing first msg
3437
+ elif mode == "push":
3438
+ sck = ctx.socket(zmq.PUSH)
3439
+ if timeout:
3440
+ sck.SNDTIMEO = int(timeout * 1000)
3441
+ sck.setsockopt(zmq.LINGER, 0)
3442
+ sck.bind(uri)
3443
+ elif mode == "req":
3444
+ sck = ctx.socket(zmq.REQ)
3445
+ if timeout:
3446
+ sck.RCVTIMEO = int(timeout * 1000)
3447
+ sck.setsockopt(zmq.LINGER, 0)
3448
+ sck.connect(uri)
3449
+ else:
3450
+ raise Exception()
3451
+
3452
+ mtx = threading.Lock()
3453
+ ZMQ[cmd] = (mode, sck, mtx)
3454
+
3455
+ if verbose and log:
3456
+ log("hook(%s) %r entering socket-lock" % (src, cmd), 6)
3457
+
3458
+ with mtx:
3459
+ if verbose and log:
3460
+ log("hook(%s) %r sending |%d|" % (src, cmd, len(msg)), 6)
3461
+
3462
+ sck.send_string(msg) # PUSH can safely timeout here
3463
+
3464
+ if mode == "req":
3465
+ if verbose and log:
3466
+ log("hook(%s) %r awaiting ack from req" % (src, cmd), 6)
3467
+ try:
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))
3473
+ except:
3474
+ sck.close()
3475
+ del ZMQ[cmd] # bad state; must reset
3476
+ raise Exception("ack timeout; zmq socket killed")
3477
+
3478
+ if ret and log:
3479
+ log("hook(%s) %r ACK: %r" % (src, cmd, ret), 6)
3480
+
3481
+ if wait:
3482
+ wait -= time.time() - t0
3483
+ if wait > 0:
3484
+ time.sleep(wait)
3485
+
3486
+ return nret, ret
3487
+
3488
+
3345
3489
  def _runhook(
3346
3490
  log ,
3491
+ verbose ,
3347
3492
  src ,
3348
3493
  cmd ,
3349
3494
  ap ,
@@ -3384,6 +3529,12 @@ def _runhook(
3384
3529
  else:
3385
3530
  arg = txt or ap
3386
3531
 
3532
+ if acmd[0].startswith("zmq:"):
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,))
3536
+ return {"rc": 0, "stdout": zs}
3537
+
3387
3538
  acmd += [arg]
3388
3539
  if acmd[0].endswith(".py"):
3389
3540
  acmd = [pybin] + acmd
@@ -3412,9 +3563,10 @@ def _runhook(
3412
3563
  except:
3413
3564
  ret = {"rc": rc, "stdout": v}
3414
3565
 
3415
- wait -= time.time() - t0
3416
- if wait > 0:
3417
- time.sleep(wait)
3566
+ if wait:
3567
+ wait -= time.time() - t0
3568
+ if wait > 0:
3569
+ time.sleep(wait)
3418
3570
 
3419
3571
  return ret
3420
3572
 
@@ -3437,14 +3589,15 @@ def runhook(
3437
3589
  txt ,
3438
3590
  ) :
3439
3591
  args = (broker or up2k).args
3592
+ verbose = args.hook_v
3440
3593
  vp = vp.replace("\\", "/")
3441
3594
  ret = {"rc": 0}
3442
3595
  for cmd in cmds:
3443
3596
  try:
3444
3597
  hr = _runhook(
3445
- log, src, cmd, ap, vp, host, uname, perms, mt, sz, ip, at, txt
3598
+ log, verbose, src, cmd, ap, vp, host, uname, perms, mt, sz, ip, at, txt
3446
3599
  )
3447
- if log and args.hook_v:
3600
+ if verbose and log:
3448
3601
  log("hook(%s) %r => \033[32m%s" % (src, cmd, hr), 6)
3449
3602
  if not hr:
3450
3603
  return {}
@@ -3460,6 +3613,8 @@ def runhook(
3460
3613
  elif k in ret:
3461
3614
  if k == "rc" and v:
3462
3615
  ret[k] = v
3616
+ elif k == "stdout" and v and not ret[k]:
3617
+ ret[k] = v
3463
3618
  else:
3464
3619
  ret[k] = v
3465
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.7"
5
- S_BUILD_DT = "2024-12-06"
4
+ S_VERSION = "2.8"
5
+ S_BUILD_DT = "2025-01-21"
6
6
 
7
7
  """
8
8
  u2c.py: upload to copyparty
@@ -1247,7 +1247,7 @@ class Ctl(object):
1247
1247
  for n, zsii in enumerate(file.cids)
1248
1248
  ]
1249
1249
  print("chs: %s\n%s" % (vp, "\n".join(zsl)))
1250
- zsl = [self.ar.wsalt, str(file.size)] + [x[0] for x in file.kchunks]
1250
+ zsl = [self.ar.wsalt, str(file.size)] + [x[0] for x in file.cids]
1251
1251
  zb = hashlib.sha512("\n".join(zsl).encode("utf-8")).digest()[:33]
1252
1252
  wark = ub64enc(zb).decode("utf-8")
1253
1253
  if self.ar.jw:
Binary file
Binary file
copyparty/web/md.js.gz CHANGED
Binary file
copyparty/web/md2.js.gz CHANGED
Binary file
copyparty/web/svcs.html CHANGED
@@ -239,7 +239,7 @@
239
239
  <div class="os win">
240
240
  <h1>ShareX</h1>
241
241
 
242
- <p>to upload screenshots using ShareX <a href="https://github.com/ShareX/ShareX/releases/tag/v12.4.1">v12</a> or <a href="https://getsharex.com/">v15+</a>, save this as <code>copyparty.sxcu</code> and run it:</p>
242
+ <p>to upload screenshots using ShareX <a href="https://github.com/ShareX/ShareX/releases/tag/v12.1.1">v12</a> or <a href="https://getsharex.com/">v15+</a>, save this as <code>copyparty.sxcu</code> and run it:</p>
243
243
 
244
244
  <pre class="dl" name="copyparty.sxcu">
245
245
  { "Name": "copyparty",
copyparty/web/svcs.js.gz CHANGED
Binary file
copyparty/web/up2k.js.gz CHANGED
Binary file
copyparty/web/util.js.gz CHANGED
Binary file