copyparty 1.18.9__tar.gz → 1.18.10__tar.gz

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.
Files changed (127) hide show
  1. {copyparty-1.18.9 → copyparty-1.18.10}/PKG-INFO +29 -2
  2. {copyparty-1.18.9 → copyparty-1.18.10}/README.md +21 -1
  3. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/__init__.py +0 -4
  4. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/__main__.py +12 -5
  5. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/__version__.py +2 -2
  6. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/authsrv.py +8 -2
  7. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/httpcli.py +22 -15
  8. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/httpsrv.py +2 -1
  9. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/mtag.py +5 -1
  10. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/tcpsrv.py +2 -3
  11. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/th_cli.py +2 -2
  12. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/th_srv.py +68 -2
  13. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/up2k.py +18 -16
  14. copyparty-1.18.10/copyparty/web/browser.css.gz +0 -0
  15. copyparty-1.18.10/copyparty/web/browser.js.gz +0 -0
  16. copyparty-1.18.10/copyparty/web/splash.js.gz +0 -0
  17. copyparty-1.18.10/copyparty/web/util.js.gz +0 -0
  18. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty.egg-info/PKG-INFO +29 -2
  19. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty.egg-info/SOURCES.txt +0 -5
  20. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty.egg-info/requires.txt +8 -0
  21. {copyparty-1.18.9 → copyparty-1.18.10}/pyproject.toml +8 -1
  22. copyparty-1.18.9/copyparty/web/browser.css.gz +0 -0
  23. copyparty-1.18.9/copyparty/web/browser.js.gz +0 -0
  24. copyparty-1.18.9/copyparty/web/dd/2.png +0 -0
  25. copyparty-1.18.9/copyparty/web/dd/3.png +0 -0
  26. copyparty-1.18.9/copyparty/web/dd/4.png +0 -0
  27. copyparty-1.18.9/copyparty/web/dd/5.png +0 -0
  28. copyparty-1.18.9/copyparty/web/deps/__init__.py +0 -0
  29. copyparty-1.18.9/copyparty/web/splash.js.gz +0 -0
  30. copyparty-1.18.9/copyparty/web/util.js.gz +0 -0
  31. {copyparty-1.18.9 → copyparty-1.18.10}/LICENSE +0 -0
  32. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/bos/__init__.py +0 -0
  33. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/bos/bos.py +0 -0
  34. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/bos/path.py +0 -0
  35. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/broker_mp.py +0 -0
  36. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/broker_mpw.py +0 -0
  37. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/broker_thr.py +0 -0
  38. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/broker_util.py +0 -0
  39. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/cert.py +0 -0
  40. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/cfg.py +0 -0
  41. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/dxml.py +0 -0
  42. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/fsutil.py +0 -0
  43. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/ftpd.py +0 -0
  44. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/httpconn.py +0 -0
  45. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/ico.py +0 -0
  46. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/mdns.py +0 -0
  47. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/metrics.py +0 -0
  48. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/multicast.py +0 -0
  49. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/pwhash.py +0 -0
  50. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/res/COPYING.txt +0 -0
  51. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/res/__init__.py +0 -0
  52. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/res/insecure.pem +0 -0
  53. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/smbd.py +0 -0
  54. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/ssdp.py +0 -0
  55. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/star.py +0 -0
  56. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/stolen/__init__.py +0 -0
  57. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/stolen/dnslib/__init__.py +0 -0
  58. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/stolen/dnslib/bimap.py +0 -0
  59. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/stolen/dnslib/bit.py +0 -0
  60. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/stolen/dnslib/buffer.py +0 -0
  61. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/stolen/dnslib/dns.py +0 -0
  62. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/stolen/dnslib/label.py +0 -0
  63. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/stolen/dnslib/lex.py +0 -0
  64. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/stolen/dnslib/ranges.py +0 -0
  65. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/stolen/ifaddr/__init__.py +0 -0
  66. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/stolen/ifaddr/_posix.py +0 -0
  67. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/stolen/ifaddr/_shared.py +0 -0
  68. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/stolen/ifaddr/_win32.py +0 -0
  69. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/stolen/qrcodegen.py +0 -0
  70. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/stolen/surrogateescape.py +0 -0
  71. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/sutil.py +0 -0
  72. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/svchub.py +0 -0
  73. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/szip.py +0 -0
  74. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/tftpd.py +0 -0
  75. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/u2idx.py +0 -0
  76. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/util.py +0 -0
  77. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/web/a/__init__.py +0 -0
  78. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/web/a/partyfuse.py +0 -0
  79. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/web/a/u2c.py +0 -0
  80. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/web/a/webdav-cfg.bat +0 -0
  81. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/web/baguettebox.js.gz +0 -0
  82. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/web/browser.html +0 -0
  83. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/web/browser2.html +0 -0
  84. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/web/cf.html +0 -0
  85. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/web/dbg-audio.js.gz +0 -0
  86. {copyparty-1.18.9/copyparty/web/dd → copyparty-1.18.10/copyparty/web/deps}/__init__.py +0 -0
  87. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/web/deps/busy.mp3.gz +0 -0
  88. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/web/deps/easymde.css.gz +0 -0
  89. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/web/deps/easymde.js.gz +0 -0
  90. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/web/deps/fuse.py +0 -0
  91. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/web/deps/marked.js.gz +0 -0
  92. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/web/deps/mini-fa.css.gz +0 -0
  93. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/web/deps/mini-fa.woff +0 -0
  94. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/web/deps/prism.css.gz +0 -0
  95. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/web/deps/prism.js.gz +0 -0
  96. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/web/deps/prismd.css.gz +0 -0
  97. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/web/deps/scp.woff2 +0 -0
  98. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/web/deps/sha512.ac.js.gz +0 -0
  99. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/web/deps/sha512.hw.js.gz +0 -0
  100. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/web/idp.html +0 -0
  101. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/web/md.css.gz +0 -0
  102. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/web/md.html +0 -0
  103. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/web/md.js.gz +0 -0
  104. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/web/md2.css.gz +0 -0
  105. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/web/md2.js.gz +0 -0
  106. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/web/mde.css.gz +0 -0
  107. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/web/mde.html +0 -0
  108. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/web/mde.js.gz +0 -0
  109. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/web/msg.css.gz +0 -0
  110. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/web/msg.html +0 -0
  111. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/web/rups.css.gz +0 -0
  112. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/web/rups.html +0 -0
  113. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/web/rups.js.gz +0 -0
  114. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/web/shares.css.gz +0 -0
  115. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/web/shares.html +0 -0
  116. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/web/shares.js.gz +0 -0
  117. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/web/splash.css.gz +0 -0
  118. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/web/splash.html +0 -0
  119. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/web/svcs.html +0 -0
  120. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/web/svcs.js.gz +0 -0
  121. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/web/ui.css.gz +0 -0
  122. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/web/up2k.js.gz +0 -0
  123. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty/web/w.hash.js.gz +0 -0
  124. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty.egg-info/dependency_links.txt +0 -0
  125. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty.egg-info/entry_points.txt +0 -0
  126. {copyparty-1.18.9 → copyparty-1.18.10}/copyparty.egg-info/top_level.txt +0 -0
  127. {copyparty-1.18.9 → copyparty-1.18.10}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: copyparty
3
- Version: 1.18.9
3
+ Version: 1.18.10
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
@@ -37,6 +37,13 @@ Requires-Python: >=3.3
37
37
  Description-Content-Type: text/markdown
38
38
  License-File: LICENSE
39
39
  Requires-Dist: Jinja2
40
+ Provides-Extra: all
41
+ Requires-Dist: argon2-cffi; extra == "all"
42
+ Requires-Dist: partftpy>=0.4.0; extra == "all"
43
+ Requires-Dist: Pillow; extra == "all"
44
+ Requires-Dist: pyftpdlib; extra == "all"
45
+ Requires-Dist: pyopenssl; extra == "all"
46
+ Requires-Dist: pyzmq; extra == "all"
40
47
  Provides-Extra: thumbnails
41
48
  Requires-Dist: Pillow; extra == "thumbnails"
42
49
  Provides-Extra: thumbnails2
@@ -205,6 +212,8 @@ made in Norway 🇳🇴
205
212
 
206
213
  just run **[copyparty-sfx.py](https://github.com/9001/copyparty/releases/latest/download/copyparty-sfx.py)** -- that's it! 🎉
207
214
 
215
+ > ℹ️ the sfx is a [self-extractor](https://github.com/9001/copyparty/issues/270) which unpacks an embedded `tar.gz` into `$TEMP` -- if this looks too scary, you can use the [zipapp](#zipapp) which has slightly worse performance
216
+
208
217
  * or install through [pypi](https://pypi.org/project/copyparty/): `python3 -m pip install --user -U copyparty`
209
218
  * or if you cannot install python, you can use [copyparty.exe](#copypartyexe) instead
210
219
  * or install [on arch](#arch-package) ╱ [on NixOS](#nixos-module) ╱ [through nix](#nix-package)
@@ -571,12 +580,17 @@ anyone trying to bruteforce a password gets banned according to `--ban-pw`; defa
571
580
 
572
581
  and if you want to use config files instead of commandline args (good!) then here's the same examples as a configfile; save it as `foobar.conf` and use it like this: `python copyparty-sfx.py -c foobar.conf`
573
582
 
583
+ * you can also `PRTY_CONFIG=foobar.conf python copyparty-sfx.py` (convenient in docker etc)
584
+
574
585
  ```yaml
575
586
  [accounts]
576
587
  u1: p1 # create account "u1" with password "p1"
577
588
  u2: p2 # (note that comments must have
578
589
  u3: p3 # two spaces before the # sign)
579
590
 
591
+ [groups]
592
+ g1: u1, u2 # create a group
593
+
580
594
  [/] # this URL will be mapped to...
581
595
  /srv # ...this folder on the server filesystem
582
596
  accs:
@@ -586,6 +600,7 @@ and if you want to use config files instead of commandline args (good!) then her
586
600
  /mnt/music # which is mapped to this folder
587
601
  accs:
588
602
  r: u1, u2 # only these accounts can read,
603
+ r: @g1 # (exactly the same, just with a group instead)
589
604
  rw: u3 # and only u3 can read-write
590
605
 
591
606
  [/inc]
@@ -1142,6 +1157,9 @@ open the `[🎺]` media-player-settings tab to configure it,
1142
1157
  * `[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
1143
1158
  * `[caf]` is `opus` in a `caf` file, good for iPhones (iOS 11 through 17), technically unsupported by Apple but works for the most part
1144
1159
  * `[mp3]` -- the myth, the legend, the undying master of mediocre sound quality that definitely works everywhere
1160
+ * `[flac]` -- lossless but compressed, for LAN and/or fiber playback on electrostatic headphones
1161
+ * `[wav]` -- lossless and uncompressed, for LAN and/or fiber playback on electrostatic headphones connected to very old equipment
1162
+ * `flac` and `wav` must be enabled with `--allow-flac` / `--allow-wav` to allow spending the disk space
1145
1163
  * "tint" reduces the contrast of the playback bar
1146
1164
 
1147
1165
 
@@ -1483,6 +1501,8 @@ note that this disables hotlinking because the opengraph spec demands it; to sne
1483
1501
 
1484
1502
  you can also hotlink files regardless by appending `?raw` to the url
1485
1503
 
1504
+ > WARNING: if you plan to use WebDAV, then `--og-ua` / `og_ua` must be configured
1505
+
1486
1506
  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)
1487
1507
 
1488
1508
 
@@ -1939,6 +1959,8 @@ you can disable the built-in password-based login system, and instead replace it
1939
1959
 
1940
1960
  * the regular config-defined users will be used as a fallback for requests which don't include a valid (trusted) IdP username header
1941
1961
 
1962
+ * if your IdP-server is slow, consider `--idp-cookie` and let requests with the cookie `cppws` bypass the IdP; experimental sessions-based feature added for a party
1963
+
1942
1964
  some popular identity providers are [Authelia](https://www.authelia.com/) (config-file based) and [authentik](https://goauthentik.io/) (GUI-based, more complex)
1943
1965
 
1944
1966
  there is a [docker-compose example](./docs/examples/docker/idp-authelia-traefik) which is hopefully a good starting point (alternatively see [./docs/idp.md](./docs/idp.md) if you're the DIY type)
@@ -2487,6 +2509,7 @@ quick summary of more eccentric web-browsers trying to view a directory index:
2487
2509
  | **SerenityOS** (7e98457) | hits a page fault, works with `?b=u`, file upload not-impl |
2488
2510
  | **sony psp** 5.50 | can browse, upload/mkdir/msg (thx dwarf) [screenshot](https://github.com/user-attachments/assets/9d21f020-1110-4652-abeb-6fc09c533d4f) |
2489
2511
  | **nintendo 3ds** | can browse, upload, view thumbnails (thx bnjmn) |
2512
+ | **Nintendo Wii (Opera 9.0 "Internet Channel")** | can browse, can't upload or download (no local storage), can view images - works best with `?b=u`, default view broken |
2490
2513
 
2491
2514
  <p align="center"><img src="https://github.com/user-attachments/assets/88deab3d-6cad-4017-8841-2f041472b853" /></p>
2492
2515
 
@@ -2757,6 +2780,10 @@ optionally also specify `--ah-cli` to enter an interactive mode where it will ha
2757
2780
 
2758
2781
  the default configs take about 0.4 sec and 256 MiB RAM to process a new password on a decent laptop
2759
2782
 
2783
+ when generating hashes using `--ah-cli` for docker or systemd services, make sure it is using the same `--ah-salt` by:
2784
+ * inspecting the generated salt using `--show-ah-salt` in copyparty service configuration
2785
+ * setting the same `--ah-salt` in both environments
2786
+
2760
2787
 
2761
2788
  ## https
2762
2789
 
@@ -2901,7 +2928,7 @@ then again, if you are already into downloading shady binaries from the internet
2901
2928
 
2902
2929
  ## zipapp
2903
2930
 
2904
- another emergency alternative, [copyparty.pyz](https://github.com/9001/copyparty/releases/latest/download/copyparty.pyz) has less features, is slow, 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 does not unpack any temporary files to disk, so 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
2931
+ another emergency alternative, [copyparty.pyz](https://github.com/9001/copyparty/releases/latest/download/copyparty.pyz) has less features, is slow, 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, unlike the sfx, it is a completely normal zipfile which does not unpack any temporary files to disk, so 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
2905
2932
 
2906
2933
  run it by doubleclicking it, or try typing `python copyparty.pyz` in your terminal/console/commandline/telex if that fails
2907
2934
 
@@ -147,6 +147,8 @@ made in Norway 🇳🇴
147
147
 
148
148
  just run **[copyparty-sfx.py](https://github.com/9001/copyparty/releases/latest/download/copyparty-sfx.py)** -- that's it! 🎉
149
149
 
150
+ > ℹ️ the sfx is a [self-extractor](https://github.com/9001/copyparty/issues/270) which unpacks an embedded `tar.gz` into `$TEMP` -- if this looks too scary, you can use the [zipapp](#zipapp) which has slightly worse performance
151
+
150
152
  * or install through [pypi](https://pypi.org/project/copyparty/): `python3 -m pip install --user -U copyparty`
151
153
  * or if you cannot install python, you can use [copyparty.exe](#copypartyexe) instead
152
154
  * or install [on arch](#arch-package) ╱ [on NixOS](#nixos-module) ╱ [through nix](#nix-package)
@@ -513,12 +515,17 @@ anyone trying to bruteforce a password gets banned according to `--ban-pw`; defa
513
515
 
514
516
  and if you want to use config files instead of commandline args (good!) then here's the same examples as a configfile; save it as `foobar.conf` and use it like this: `python copyparty-sfx.py -c foobar.conf`
515
517
 
518
+ * you can also `PRTY_CONFIG=foobar.conf python copyparty-sfx.py` (convenient in docker etc)
519
+
516
520
  ```yaml
517
521
  [accounts]
518
522
  u1: p1 # create account "u1" with password "p1"
519
523
  u2: p2 # (note that comments must have
520
524
  u3: p3 # two spaces before the # sign)
521
525
 
526
+ [groups]
527
+ g1: u1, u2 # create a group
528
+
522
529
  [/] # this URL will be mapped to...
523
530
  /srv # ...this folder on the server filesystem
524
531
  accs:
@@ -528,6 +535,7 @@ and if you want to use config files instead of commandline args (good!) then her
528
535
  /mnt/music # which is mapped to this folder
529
536
  accs:
530
537
  r: u1, u2 # only these accounts can read,
538
+ r: @g1 # (exactly the same, just with a group instead)
531
539
  rw: u3 # and only u3 can read-write
532
540
 
533
541
  [/inc]
@@ -1084,6 +1092,9 @@ open the `[🎺]` media-player-settings tab to configure it,
1084
1092
  * `[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
1085
1093
  * `[caf]` is `opus` in a `caf` file, good for iPhones (iOS 11 through 17), technically unsupported by Apple but works for the most part
1086
1094
  * `[mp3]` -- the myth, the legend, the undying master of mediocre sound quality that definitely works everywhere
1095
+ * `[flac]` -- lossless but compressed, for LAN and/or fiber playback on electrostatic headphones
1096
+ * `[wav]` -- lossless and uncompressed, for LAN and/or fiber playback on electrostatic headphones connected to very old equipment
1097
+ * `flac` and `wav` must be enabled with `--allow-flac` / `--allow-wav` to allow spending the disk space
1087
1098
  * "tint" reduces the contrast of the playback bar
1088
1099
 
1089
1100
 
@@ -1425,6 +1436,8 @@ note that this disables hotlinking because the opengraph spec demands it; to sne
1425
1436
 
1426
1437
  you can also hotlink files regardless by appending `?raw` to the url
1427
1438
 
1439
+ > WARNING: if you plan to use WebDAV, then `--og-ua` / `og_ua` must be configured
1440
+
1428
1441
  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)
1429
1442
 
1430
1443
 
@@ -1881,6 +1894,8 @@ you can disable the built-in password-based login system, and instead replace it
1881
1894
 
1882
1895
  * the regular config-defined users will be used as a fallback for requests which don't include a valid (trusted) IdP username header
1883
1896
 
1897
+ * if your IdP-server is slow, consider `--idp-cookie` and let requests with the cookie `cppws` bypass the IdP; experimental sessions-based feature added for a party
1898
+
1884
1899
  some popular identity providers are [Authelia](https://www.authelia.com/) (config-file based) and [authentik](https://goauthentik.io/) (GUI-based, more complex)
1885
1900
 
1886
1901
  there is a [docker-compose example](./docs/examples/docker/idp-authelia-traefik) which is hopefully a good starting point (alternatively see [./docs/idp.md](./docs/idp.md) if you're the DIY type)
@@ -2429,6 +2444,7 @@ quick summary of more eccentric web-browsers trying to view a directory index:
2429
2444
  | **SerenityOS** (7e98457) | hits a page fault, works with `?b=u`, file upload not-impl |
2430
2445
  | **sony psp** 5.50 | can browse, upload/mkdir/msg (thx dwarf) [screenshot](https://github.com/user-attachments/assets/9d21f020-1110-4652-abeb-6fc09c533d4f) |
2431
2446
  | **nintendo 3ds** | can browse, upload, view thumbnails (thx bnjmn) |
2447
+ | **Nintendo Wii (Opera 9.0 "Internet Channel")** | can browse, can't upload or download (no local storage), can view images - works best with `?b=u`, default view broken |
2432
2448
 
2433
2449
  <p align="center"><img src="https://github.com/user-attachments/assets/88deab3d-6cad-4017-8841-2f041472b853" /></p>
2434
2450
 
@@ -2699,6 +2715,10 @@ optionally also specify `--ah-cli` to enter an interactive mode where it will ha
2699
2715
 
2700
2716
  the default configs take about 0.4 sec and 256 MiB RAM to process a new password on a decent laptop
2701
2717
 
2718
+ when generating hashes using `--ah-cli` for docker or systemd services, make sure it is using the same `--ah-salt` by:
2719
+ * inspecting the generated salt using `--show-ah-salt` in copyparty service configuration
2720
+ * setting the same `--ah-salt` in both environments
2721
+
2702
2722
 
2703
2723
  ## https
2704
2724
 
@@ -2843,7 +2863,7 @@ then again, if you are already into downloading shady binaries from the internet
2843
2863
 
2844
2864
  ## zipapp
2845
2865
 
2846
- another emergency alternative, [copyparty.pyz](https://github.com/9001/copyparty/releases/latest/download/copyparty.pyz) has less features, is slow, 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 does not unpack any temporary files to disk, so 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
2866
+ another emergency alternative, [copyparty.pyz](https://github.com/9001/copyparty/releases/latest/download/copyparty.pyz) has less features, is slow, 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, unlike the sfx, it is a completely normal zipfile which does not unpack any temporary files to disk, so 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
2847
2867
 
2848
2868
  run it by doubleclicking it, or try typing `python copyparty.pyz` in your terminal/console/commandline/telex if that fails
2849
2869
 
@@ -60,10 +60,6 @@ web/browser.js
60
60
  web/browser2.html
61
61
  web/cf.html
62
62
  web/copyparty.gif
63
- web/dd/2.png
64
- web/dd/3.png
65
- web/dd/4.png
66
- web/dd/5.png
67
63
  web/deps/busy.mp3
68
64
  web/deps/easymde.css
69
65
  web/deps/easymde.js
@@ -87,6 +87,10 @@ u = unicode
87
87
  printed = []
88
88
  zsid = uuid.uuid4().urn[4:]
89
89
 
90
+ CFG_DEF = [os.environ.get("PRTY_CONFIG", "")]
91
+ if not CFG_DEF[0]:
92
+ CFG_DEF.pop()
93
+
90
94
 
91
95
  class RiceFormatter(argparse.HelpFormatter):
92
96
  def __init__(self, *args , **kwargs ) :
@@ -566,7 +570,7 @@ def get_sects():
566
570
 
567
571
  --grp takes groupname:username1,username2,...
568
572
  and groupnames can be used instead of usernames in -v
569
- by prefixing the groupname with %
573
+ by prefixing the groupname with @
570
574
 
571
575
  list of permissions:
572
576
  "r" (read): list folder contents, download files
@@ -982,7 +986,7 @@ def build_flags_desc():
982
986
 
983
987
  def add_general(ap, nc, srvname):
984
988
  ap2 = ap.add_argument_group('general options')
985
- ap2.add_argument("-c", metavar="PATH", type=u, action="append", help="add config file")
989
+ ap2.add_argument("-c", metavar="PATH", type=u, default=CFG_DEF, action="append", help="add config file")
986
990
  ap2.add_argument("-nc", metavar="NUM", type=int, default=nc, help="max num clients")
987
991
  ap2.add_argument("-j", metavar="CORES", type=int, default=1, help="max num cpu cores, 0=all")
988
992
  ap2.add_argument("-a", metavar="ACCT", type=u, action="append", help="add account, \033[33mUSER\033[0m:\033[33mPASS\033[0m; example [\033[32med:wark\033[0m]")
@@ -1101,7 +1105,7 @@ def add_tls(ap, cert_path):
1101
1105
  ap2 = ap.add_argument_group('SSL/TLS options')
1102
1106
  ap2.add_argument("--http-only", action="store_true", help="disable ssl/tls -- force plaintext")
1103
1107
  ap2.add_argument("--https-only", action="store_true", help="disable plaintext -- force tls")
1104
- ap2.add_argument("--cert", metavar="PATH", type=u, default=cert_path, help="path to TLS certificate")
1108
+ ap2.add_argument("--cert", metavar="PATH", type=u, default=cert_path, help="path to file containing a concatenation of TLS key and certificate chain")
1105
1109
  ap2.add_argument("--ssl-ver", metavar="LIST", type=u, default="", help="set allowed ssl/tls versions; [\033[32mhelp\033[0m] shows available versions; default is what your python version considers safe")
1106
1110
  ap2.add_argument("--ciphers", metavar="LIST", type=u, default="", help="set allowed ssl/tls ciphers; [\033[32mhelp\033[0m] shows available ciphers")
1107
1111
  ap2.add_argument("--ssl-dbg", action="store_true", help="dump some tls info")
@@ -1138,6 +1142,7 @@ def add_auth(ap):
1138
1142
  ap2.add_argument("--idp-db", metavar="PATH", type=u, default=idp_db, help="where to store the known IdP users/groups (if you run multiple copyparty instances, make sure they use different DBs)")
1139
1143
  ap2.add_argument("--idp-store", metavar="N", type=int, default=1, help="how to use \033[33m--idp-db\033[0m; [\033[32m0\033[0m] = entirely disable, [\033[32m1\033[0m] = write-only (effectively disabled), [\033[32m2\033[0m] = remember users, [\033[32m3\033[0m] = remember users and groups.\nNOTE: Will remember and restore the IdP-volumes of all users for all eternity if set to 2 or 3, even when user is deleted from your IdP")
1140
1144
  ap2.add_argument("--idp-adm", metavar="U,U", type=u, default="", help="comma-separated list of users allowed to use /?idp (the cache management UI)")
1145
+ ap2.add_argument("--idp-cookie", metavar="S", type=int, default=0, help="generate a session-token for IdP users which is written to cookie \033[33mcppws\033[0m (or \033[33mcppwd\033[0m if plaintext), to reduce the load on the IdP server, lifetime \033[33mS\033[0m seconds.\n └─note: The expiration time is a client hint only; the actual lifetime of the session-token is infinite (until next restart with \033[33m--ses-db\033[0m wiped)")
1141
1146
  ap2.add_argument("--no-bauth", action="store_true", help="disable basic-authentication support; do not accept passwords from the 'Authenticate' header at all. NOTE: This breaks support for the android app")
1142
1147
  ap2.add_argument("--bauth-last", action="store_true", help="keeps basic-authentication enabled, but only as a last-resort; if a cookie is also provided then the cookie wins")
1143
1148
  ap2.add_argument("--ses-db", metavar="PATH", type=u, default=ses_db, help="where to store the sessions database (if you run multiple copyparty instances, make sure they use different DBs)")
@@ -1435,6 +1440,8 @@ def add_transcoding(ap):
1435
1440
  ap2 = ap.add_argument_group('transcoding options')
1436
1441
  ap2.add_argument("--q-opus", metavar="KBPS", type=int, default=128, help="target bitrate for transcoding to opus; set 0 to disable")
1437
1442
  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")
1443
+ ap2.add_argument("--allow-wav", action="store_true", help="allow transcoding to wav (lossless, uncompressed)")
1444
+ ap2.add_argument("--allow-flac", action="store_true", help="allow transcoding to flac (lossless, compressed)")
1438
1445
  ap2.add_argument("--no-caf", action="store_true", help="disable transcoding to caf-opus (affects iOS v12~v17), will use mp3 instead")
1439
1446
  ap2.add_argument("--no-owa", action="store_true", help="disable transcoding to webm-opus (iOS v18 and later), will use mp3 instead")
1440
1447
  ap2.add_argument("--no-acode", action="store_true", help="disable audio transcoding")
@@ -1553,7 +1560,7 @@ def add_ui(ap, retry):
1553
1560
  ap2.add_argument("--unlist", metavar="REGEX", type=u, default="", help="don't show files/folders matching \033[33mREGEX\033[0m in file list. WARNING: Purely cosmetic! Does not affect API calls, just the browser. Example: [\033[32m\\.(js|css)$\033[0m] (volflag=unlist)")
1554
1561
  ap2.add_argument("--favico", metavar="TXT", type=u, default="c 000 none" if retry else "🎉 000 none", help="\033[33mfavicon-text\033[0m [ \033[33mforeground\033[0m [ \033[33mbackground\033[0m ] ], set blank to disable")
1555
1562
  ap2.add_argument("--ext-th", metavar="E=VP", type=u, action="append", help="use thumbnail-image \033[33mVP\033[0m for file-extension \033[33mE\033[0m, example: [\033[32mexe=/.res/exe.png\033[0m] (volflag=ext_th)")
1556
- ap2.add_argument("--mpmc", metavar="URL", type=u, default="", help="change the mediaplayer-toggle mouse cursor; URL to a folder with {2..5}.png inside (or disable with [\033[32m.\033[0m])")
1563
+ ap2.add_argument("--mpmc", type=u, default="", help=argparse.SUPPRESS)
1557
1564
  ap2.add_argument("--spinner", metavar="TXT", type=u, default="🌲", help="\033[33memoji\033[0m or \033[33memoji,css\033[0m Example: [\033[32m🥖,padding:0\033[0m]")
1558
1565
  ap2.add_argument("--css-browser", metavar="L", type=u, default="", help="URL to additional CSS to include in the filebrowser html")
1559
1566
  ap2.add_argument("--js-browser", metavar="L", type=u, default="", help="URL to additional JS to include in the filebrowser html")
@@ -1778,7 +1785,7 @@ def main(argv = None) :
1778
1785
  argv[idx] = nk + ov
1779
1786
  time.sleep(2)
1780
1787
 
1781
- da = len(argv) == 1
1788
+ da = len(argv) == 1 and not CFG_DEF
1782
1789
  try:
1783
1790
  if da:
1784
1791
  argv.extend(["--qr"])
@@ -1,8 +1,8 @@
1
1
  # coding: utf-8
2
2
 
3
- VERSION = (1, 18, 9)
3
+ VERSION = (1, 18, 10)
4
4
  CODENAME = "logtail"
5
- BUILD_DT = (2025, 8, 1)
5
+ BUILD_DT = (2025, 8, 4)
6
6
 
7
7
  S_VERSION = ".".join(map(str, VERSION))
8
8
  S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)
@@ -1705,7 +1705,8 @@ class AuthSrv(object):
1705
1705
  t = "Read-access has been disabled due to failsafe: No volumes were defined by the config-file. This failsafe is to prevent unintended access if this is due to accidental loss of config. You can override this safeguard and allow read/write to the working-directory by adding the following arguments: -v .::rw"
1706
1706
  self.log(t, 1)
1707
1707
  axs = AXS()
1708
- vfs = VFS(self.log_func, absreal("."), "", "", axs, self.vf0())
1708
+ zvf = {"tcolor": self.args.tcolor}
1709
+ vfs = VFS(self.log_func, absreal("."), "", "", axs, zvf)
1709
1710
  if not axs.uread:
1710
1711
  self.badcfg1 = True
1711
1712
  elif "" not in mount:
@@ -2742,6 +2743,8 @@ class AuthSrv(object):
2742
2743
  "s_name": self.args.bname,
2743
2744
  "have_up2k_idx": "e2d" in vf,
2744
2745
  "have_acode": not self.args.no_acode,
2746
+ "have_c2flac": self.args.allow_flac,
2747
+ "have_c2wav": self.args.allow_wav,
2745
2748
  "have_shr": self.args.shr,
2746
2749
  "have_zip": not self.args.no_zip,
2747
2750
  "have_mv": not self.args.no_mv,
@@ -2854,7 +2857,10 @@ class AuthSrv(object):
2854
2857
 
2855
2858
  n = []
2856
2859
  q = "insert into us values (?,?,?)"
2857
- for uname in self.acct:
2860
+ accs = list(self.acct)
2861
+ if self.args.idp_h_usr and self.args.idp_cookie:
2862
+ accs.extend(self.idp_accs.keys())
2863
+ for uname in accs:
2858
2864
  if uname not in ases:
2859
2865
  sid = ub64enc(os.urandom(blen)).decode("ascii")
2860
2866
  cur.execute(q, (uname, sid, int(time.time())))
@@ -653,6 +653,9 @@ class HttpCli(object):
653
653
  self.pw = ""
654
654
  self.uname = idp_usr
655
655
  self.html_head += "<script>var is_idp=1</script>\n"
656
+ zs = self.asrv.ases.get(idp_usr)
657
+ if zs:
658
+ self.set_idp_cookie(zs)
656
659
  else:
657
660
  self.log("unknown username: %r" % (idp_usr,), 1)
658
661
 
@@ -1191,15 +1194,6 @@ class HttpCli(object):
1191
1194
  self.reply(b"ssdp is disabled in server config", 404)
1192
1195
  return False
1193
1196
 
1194
- if self.vpath.startswith(".cpr/dd/") and self.args.mpmc:
1195
- if self.args.mpmc == ".":
1196
- raise Pebkac(404)
1197
-
1198
- loc = self.args.mpmc.rstrip("/") + self.vpath[self.vpath.rfind("/") :]
1199
- h = {"Location": loc, "Cache-Control": "max-age=39"}
1200
- self.reply(b"", 301, headers=h)
1201
- return True
1202
-
1203
1197
  if self.vpath == ".cpr/metrics":
1204
1198
  return self.conn.hsrv.metrics.tx(self)
1205
1199
 
@@ -2075,16 +2069,16 @@ class HttpCli(object):
2075
2069
  rnd, lifetime, xbu, xau = self.upload_flags(vfs)
2076
2070
  lim = vfs.get_dbv(rem)[0].lim
2077
2071
  fdir = vfs.canonical(rem)
2078
- if lim:
2079
- fdir, rem = lim.all(
2080
- self.ip, rem, remains, vfs.realpath, fdir, self.conn.hsrv.broker
2081
- )
2082
-
2083
2072
  fn = None
2084
2073
  if rem and not self.trailing_slash and not bos.path.isdir(fdir):
2085
2074
  fdir, fn = os.path.split(fdir)
2086
2075
  rem, _ = vsplit(rem)
2087
2076
 
2077
+ if lim:
2078
+ fdir, rem = lim.all(
2079
+ self.ip, rem, remains, vfs.realpath, fdir, self.conn.hsrv.broker
2080
+ )
2081
+
2088
2082
  bos.makedirs(fdir, vf=vfs.flags)
2089
2083
 
2090
2084
  open_ka = {"fun": open}
@@ -3024,6 +3018,19 @@ class HttpCli(object):
3024
3018
 
3025
3019
  return dur > 0, msg
3026
3020
 
3021
+ def set_idp_cookie(self, ases) :
3022
+ k = "cppws" if self.is_https else "cppwd"
3023
+ ck = gencookie(
3024
+ k,
3025
+ ases,
3026
+ self.args.R,
3027
+ self.args.cookie_lax,
3028
+ self.is_https,
3029
+ self.args.idp_cookie,
3030
+ "; HttpOnly",
3031
+ )
3032
+ self.out_headers["Set-Cookie"] = ck
3033
+
3027
3034
  def handle_mkdir(self) :
3028
3035
  new_dir = self.parser.require("name", 512)
3029
3036
  self.parser.drop()
@@ -4662,7 +4669,7 @@ class HttpCli(object):
4662
4669
  # for f in fgen: print(repr({k: f[k] for k in ["vp", "ap"]}))
4663
4670
  cfmt = ""
4664
4671
  if self.thumbcli and not self.args.no_bacode:
4665
- for zs in ("opus", "mp3", "w", "j", "p"):
4672
+ for zs in ("opus", "mp3", "flac", "wav", "w", "j", "p"):
4666
4673
  if zs in self.ouparam or uarg == zs:
4667
4674
  cfmt = zs
4668
4675
 
@@ -319,7 +319,8 @@ class HttpSrv(object):
319
319
  spins = 0
320
320
  while self.ncli >= self.nclimax:
321
321
  if not spins:
322
- self.log(self.name, "at connection limit; waiting", 3)
322
+ t = "at connection limit (global-option 'nc'); waiting"
323
+ self.log(self.name, t, 3)
323
324
 
324
325
  spins += 1
325
326
  time.sleep(0.1)
@@ -61,6 +61,8 @@ HAVE_FFPROBE = not os.environ.get("PRTY_NO_FFPROBE") and have_ff("ffprobe")
61
61
  CBZ_PICS = set("png jpg jpeg gif bmp tga tif tiff webp avif".split())
62
62
  CBZ_01 = re.compile(r"(^|[^0-9v])0+[01]\b")
63
63
 
64
+ FMT_AU = set("mp3 ogg flac wav".split())
65
+
64
66
 
65
67
  class MParser(object):
66
68
  def __init__(self, cmdline ) :
@@ -236,7 +238,7 @@ def parse_ffprobe(txt ) :
236
238
  ret = {} # processed
237
239
  md = {} # raw tags
238
240
 
239
- is_audio = fmt.get("format_name") in ["mp3", "ogg", "flac", "wav"]
241
+ is_audio = fmt.get("format_name") in FMT_AU
240
242
  if fmt.get("filename", "").split(".")[-1].lower() in ["m4a", "aac"]:
241
243
  is_audio = True
242
244
 
@@ -264,6 +266,8 @@ def parse_ffprobe(txt ) :
264
266
  ["channel_layout", "chs"],
265
267
  ["sample_rate", ".hz"],
266
268
  ["bit_rate", ".aq"],
269
+ ["bits_per_sample", ".bps"],
270
+ ["bits_per_raw_sample", ".bprs"],
267
271
  ["duration", ".dur"],
268
272
  ]
269
273
 
@@ -580,8 +580,7 @@ class TcpSrv(object):
580
580
  if not ip:
581
581
  return ""
582
582
 
583
- if ":" in ip:
584
- ip = "[{}]".format(ip)
583
+ hip = "[%s]" % (ip,) if ":" in ip else ip
585
584
 
586
585
  if self.args.http_only:
587
586
  https = ""
@@ -593,7 +592,7 @@ class TcpSrv(object):
593
592
  ports = t1.get(ip, t2.get(ip, []))
594
593
  dport = 443 if https else 80
595
594
  port = "" if dport in ports or not ports else ":{}".format(ports[0])
596
- txt = "http{}://{}{}/{}".format(https, ip, port, self.args.qrl)
595
+ txt = "http{}://{}{}/{}".format(https, hip, port, self.args.qrl)
597
596
 
598
597
  btxt = txt.encode("utf-8")
599
598
  if PY2:
@@ -85,7 +85,7 @@ class ThumbCli(object):
85
85
  if rem.startswith(".hist/th/") and rem.split(".")[-1] in ["webp", "jpg", "png"]:
86
86
  return os.path.join(ptop, rem)
87
87
 
88
- if fmt[:1] in "jw":
88
+ if fmt[:1] in "jw" and fmt != "wav":
89
89
  sfmt = fmt[:1]
90
90
 
91
91
  if sfmt == "j" and self.args.th_no_jpg:
@@ -126,7 +126,7 @@ class ThumbCli(object):
126
126
 
127
127
  tpath = thumb_path(histpath, rem, mtime, fmt, self.fmt_ffa)
128
128
  tpaths = [tpath]
129
- if fmt[:1] == "w":
129
+ if fmt[:1] == "w" and fmt != "wav":
130
130
  # also check for jpg (maybe webp is unavailable)
131
131
  tpaths.append(tpath.rsplit(".", 1)[0] + ".jpg")
132
132
 
@@ -47,7 +47,7 @@ HAVE_AVIF = False
47
47
  HAVE_WEBP = False
48
48
 
49
49
  EXTS_TH = set(["jpg", "webp", "png"])
50
- EXTS_AC = set(["opus", "owa", "caf", "mp3"])
50
+ EXTS_AC = set(["opus", "owa", "caf", "mp3", "flac", "wav"])
51
51
  EXTS_SPEC_SAFE = set("aif aiff flac mp3 opus wav".split())
52
52
 
53
53
  PTN_TS = re.compile("^-?[0-9a-f]{8,10}$")
@@ -352,8 +352,10 @@ class ThumbSrv(object):
352
352
  tex = tpath.rsplit(".", 1)[-1]
353
353
  want_mp3 = tex == "mp3"
354
354
  want_opus = tex in ("opus", "owa", "caf")
355
+ want_flac = tex == "flac"
356
+ want_wav = tex == "wav"
355
357
  want_png = tex == "png"
356
- want_au = want_mp3 or want_opus
358
+ want_au = want_mp3 or want_opus or want_flac or want_wav
357
359
  for lib in self.args.th_dec:
358
360
  can_au = lib == "ff" and (
359
361
  ext in self.fmt_ffa or ext in self.fmt_ffv
@@ -368,6 +370,10 @@ class ThumbSrv(object):
368
370
  funs.append(self.conv_opus)
369
371
  elif want_mp3:
370
372
  funs.append(self.conv_mp3)
373
+ elif want_flac:
374
+ funs.append(self.conv_flac)
375
+ elif want_wav:
376
+ funs.append(self.conv_wav)
371
377
  elif want_png:
372
378
  funs.append(self.conv_waves)
373
379
  png_ok = True
@@ -803,6 +809,66 @@ class ThumbSrv(object):
803
809
  # fmt: on
804
810
  self._run_ff(cmd, vn, oom=300)
805
811
 
812
+ def conv_flac(self, abspath , tpath , fmt , vn ) :
813
+ if self.args.no_acode or not self.args.allow_flac:
814
+ raise Exception("flac not permitted in server config")
815
+
816
+ self.wait4ram(0.2, tpath)
817
+ tags, rawtags = ffprobe(abspath, int(vn.flags["convt"] / 2))
818
+ if "ac" not in tags:
819
+ raise Exception("not audio")
820
+
821
+ self.log("conv2 flac", 6)
822
+
823
+ # fmt: off
824
+ cmd = [
825
+ b"ffmpeg",
826
+ b"-nostdin",
827
+ b"-v", b"error",
828
+ b"-hide_banner",
829
+ b"-i", fsenc(abspath),
830
+ b"-map", b"0:a:0",
831
+ b"-c:a", b"flac",
832
+ fsenc(tpath)
833
+ ]
834
+ # fmt: on
835
+ self._run_ff(cmd, vn, oom=300)
836
+
837
+ def conv_wav(self, abspath , tpath , fmt , vn ) :
838
+ if self.args.no_acode or not self.args.allow_wav:
839
+ raise Exception("wav not permitted in server config")
840
+
841
+ self.wait4ram(0.2, tpath)
842
+ tags, rawtags = ffprobe(abspath, int(vn.flags["convt"] / 2))
843
+ if "ac" not in tags:
844
+ raise Exception("not audio")
845
+
846
+ bits = tags[".bps"][1]
847
+ if bits == 0.0:
848
+ bits = tags[".bprs"][1]
849
+
850
+ codec = b"pcm_s32le"
851
+ if bits <= 16.0:
852
+ codec = b"pcm_s16le"
853
+ elif bits <= 24.0:
854
+ codec = b"pcm_s24le"
855
+
856
+ self.log("conv2 wav", 6)
857
+
858
+ # fmt: off
859
+ cmd = [
860
+ b"ffmpeg",
861
+ b"-nostdin",
862
+ b"-v", b"error",
863
+ b"-hide_banner",
864
+ b"-i", fsenc(abspath),
865
+ b"-map", b"0:a:0",
866
+ b"-c:a", codec,
867
+ fsenc(tpath)
868
+ ]
869
+ # fmt: on
870
+ self._run_ff(cmd, vn, oom=300)
871
+
806
872
  def conv_opus(self, abspath , tpath , fmt , vn ) :
807
873
  if self.args.no_acode or not self.args.q_opus:
808
874
  raise Exception("disabled in server config")
@@ -83,7 +83,10 @@ if TYPE_CHECKING:
83
83
  from .svchub import SvcHub
84
84
 
85
85
  zsg = "avif,avifs,bmp,gif,heic,heics,heif,heifs,ico,j2p,j2k,jp2,jpeg,jpg,jpx,png,tga,tif,tiff,webp"
86
- CV_EXTS = set(zsg.split(","))
86
+ ICV_EXTS = set(zsg.split(","))
87
+
88
+ zsg = "3gp,asf,av1,avc,avi,flv,m4v,mjpeg,mjpg,mkv,mov,mp4,mpeg,mpeg2,mpegts,mpg,mpg2,mts,nut,ogm,ogv,rm,vob,webm,wmv"
89
+ VCV_EXTS = set(zsg.split(","))
87
90
 
88
91
  zsg = "nohash noidx xdev xvol"
89
92
  VF_AFFECTS_INDEXING = set(zsg.split(" "))
@@ -1470,7 +1473,7 @@ class Up2k(object):
1470
1473
  unreg = []
1471
1474
  files = []
1472
1475
  fat32 = True
1473
- cv = ""
1476
+ cv = vcv = ""
1474
1477
 
1475
1478
  th_cvd = self.args.th_coversd
1476
1479
  th_cvds = self.args.th_coversd_set
@@ -1564,25 +1567,24 @@ class Up2k(object):
1564
1567
 
1565
1568
  rsz += sz
1566
1569
  files.append((sz, lmod, iname))
1567
- liname = iname.lower()
1568
- if (
1569
- sz
1570
- and (
1570
+ if sz:
1571
+ liname = iname.lower()
1572
+ ext = liname.rsplit(".", 1)[-1]
1573
+ if (
1571
1574
  liname in th_cvds
1572
- or (
1573
- not cv
1574
- and liname.rsplit(".", 1)[-1] in CV_EXTS
1575
- and not iname.startswith(".")
1576
- )
1577
- )
1578
- and (
1575
+ or (not cv and ext in ICV_EXTS and not iname.startswith("."))
1576
+ ) and (
1579
1577
  not cv
1580
1578
  or liname not in th_cvds
1581
1579
  or cv.lower() not in th_cvds
1582
1580
  or th_cvd.index(liname) < th_cvd.index(cv.lower())
1583
- )
1584
- ):
1585
- cv = iname
1581
+ ):
1582
+ cv = iname
1583
+ elif not vcv and ext in VCV_EXTS and not iname.startswith("."):
1584
+ vcv = iname
1585
+
1586
+ if not cv:
1587
+ cv = vcv
1586
1588
 
1587
1589
  if not self.args.no_dirsz:
1588
1590
  tnf += len(files)