From 74fb4b0cb8c341739cedb233b23e3e9933bff9fc Mon Sep 17 00:00:00 2001 From: ed Date: Thu, 24 Apr 2025 20:51:45 +0000 Subject: [PATCH 01/12] fix `--u2j` helptext: * mention potential hdd-bottleneck from big values * most browsers enforce a max-value of 6 (c354a38b) * chunk-stitching (132a8350) made this less important; still beneficial, but only to a point --- copyparty/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/copyparty/__main__.py b/copyparty/__main__.py index b6ea33fe..6bf2b0c6 100644 --- a/copyparty/__main__.py +++ b/copyparty/__main__.py @@ -1025,7 +1025,7 @@ def add_upload(ap): ap2.add_argument("--df", metavar="GiB", type=u, default="0", help="ensure \033[33mGiB\033[0m free disk space by rejecting upload requests; assumes gigabytes unless a unit suffix is given: [\033[32m256m\033[0m], [\033[32m4\033[0m], [\033[32m2T\033[0m] (volflag=df)") ap2.add_argument("--sparse", metavar="MiB", type=int, default=4, help="windows-only: minimum size of incoming uploads through up2k before they are made into sparse files") ap2.add_argument("--turbo", metavar="LVL", type=int, default=0, help="configure turbo-mode in up2k client; [\033[32m-1\033[0m] = forbidden/always-off, [\033[32m0\033[0m] = default-off and warn if enabled, [\033[32m1\033[0m] = default-off, [\033[32m2\033[0m] = on, [\033[32m3\033[0m] = on and disable datecheck") - ap2.add_argument("--u2j", metavar="JOBS", type=int, default=2, help="web-client: number of file chunks to upload in parallel; 1 or 2 is good for low-latency (same-country) connections, 4-8 for android clients, 16 for cross-atlantic (max=64)") + ap2.add_argument("--u2j", metavar="JOBS", type=int, default=2, help="web-client: number of file chunks to upload in parallel; 1 or 2 is good when latency is low (same-country), 2~4 for android-clients, 2~6 for cross-atlantic. Max is 6 in most browsers. Big values increase network-speed but may reduce HDD-speed") ap2.add_argument("--u2sz", metavar="N,N,N", type=u, default="1,64,96", help="web-client: default upload chunksize (MiB); sets \033[33mmin,default,max\033[0m in the settings gui. Each HTTP POST will aim for \033[33mdefault\033[0m, and never exceed \033[33mmax\033[0m. Cloudflare max is 96. Big values are good for cross-atlantic but may increase HDD fragmentation on some FS. Disable this optimization with [\033[32m1,1,1\033[0m]") ap2.add_argument("--u2ow", metavar="NUM", type=int, default=0, help="web-client: default setting for when to replace/overwrite existing files; [\033[32m0\033[0m]=never, [\033[32m1\033[0m]=if-client-newer, [\033[32m2\033[0m]=always (volflag=u2ow)") ap2.add_argument("--u2sort", metavar="TXT", type=u, default="s", help="upload order; [\033[32ms\033[0m]=smallest-first, [\033[32mn\033[0m]=alphabetical, [\033[32mfs\033[0m]=force-s, [\033[32mfn\033[0m]=force-n -- alphabetical is a bit slower on fiber/LAN but makes it easier to eyeball if everything went fine") From dbfc899d79cfa5e086dc91485c126c5d9a9a53d2 Mon Sep 17 00:00:00 2001 From: ed Date: Fri, 25 Apr 2025 18:12:35 +0000 Subject: [PATCH 02/12] pw-hash tweaks (#159): * do not take lock on shares-db / sessions-db when running with `--ah-gen` or `--ah-cli` (allows a 2nd instance for that purpose) * add options to print effective salt for ah/fk/dk; useful for nixos and other usecases where config is derived or otherwise opaque --- copyparty/__main__.py | 3 +++ copyparty/svchub.py | 8 ++++++++ 2 files changed, 11 insertions(+) diff --git a/copyparty/__main__.py b/copyparty/__main__.py index 6bf2b0c6..0dbab9f2 100644 --- a/copyparty/__main__.py +++ b/copyparty/__main__.py @@ -1308,6 +1308,9 @@ def add_salt(ap, fk_salt, dk_salt, ah_salt): ap2.add_argument("--fk-salt", metavar="SALT", type=u, default=fk_salt, help="per-file accesskey salt; used to generate unpredictable URLs for hidden files") ap2.add_argument("--dk-salt", metavar="SALT", type=u, default=dk_salt, help="per-directory accesskey salt; used to generate unpredictable URLs to share folders with users who only have the 'get' permission") ap2.add_argument("--warksalt", metavar="SALT", type=u, default="hunter2", help="up2k file-hash salt; serves no purpose, no reason to change this (but delete all databases if you do)") + ap2.add_argument("--show-ah-salt", action="store_true", help="on startup, print the effective value of \033[33m--ah-salt\033[0m (the autogenerated value in $XDG_CONFIG_HOME unless otherwise specified)") + ap2.add_argument("--show-fk-salt", action="store_true", help="on startup, print the effective value of \033[33m--fk-salt\033[0m (the autogenerated value in $XDG_CONFIG_HOME unless otherwise specified)") + ap2.add_argument("--show-dk-salt", action="store_true", help="on startup, print the effective value of \033[33m--dk-salt\033[0m (the autogenerated value in $XDG_CONFIG_HOME unless otherwise specified)") def add_shutdown(ap): diff --git a/copyparty/svchub.py b/copyparty/svchub.py index 6dcc9fd3..e4f1110a 100644 --- a/copyparty/svchub.py +++ b/copyparty/svchub.py @@ -253,6 +253,14 @@ class SvcHub(object): setattr(args, "ipu_iu", iu) setattr(args, "ipu_nm", nm) + for zs in "ah_salt fk_salt dk_salt".split(): + if getattr(args, "show_%s" % (zs,)): + self.log("root", "effective %s is %s" % (zs, getattr(args, zs))) + + if args.ah_cli or args.ah_gen: + args.no_ses = True + args.shr = "" + if not self.args.no_ses: self.setup_session_db() From efbe34f29df46041a7b0ecd62a5a9665d424b020 Mon Sep 17 00:00:00 2001 From: ed Date: Fri, 25 Apr 2025 18:57:12 +0000 Subject: [PATCH 03/12] readme: mention basic-auth behavior --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 6bf26ffd..1742cdb0 100644 --- a/README.md +++ b/README.md @@ -2388,6 +2388,8 @@ copyparty returns a truncated sha512sum of your PUT/POST as base64; you can gene you can provide passwords using header `PW: hunter2`, cookie `cppwd=hunter2`, url-param `?pw=hunter2`, or with basic-authentication (either as the username or password) +> for basic-authentication, all of the following are accepted: `password` / `whatever:password` / `password:whatever` (the username is ignored) + NOTE: curl will not send the original filename if you use `-T` combined with url-params! Also, make sure to always leave a trailing slash in URLs unless you want to override the filename From 897f9d328d7265b54a7189ec7403dcf41f0c1514 Mon Sep 17 00:00:00 2001 From: ed Date: Fri, 25 Apr 2025 22:33:00 +0000 Subject: [PATCH 04/12] audioplayer: load and play m3u8 playlists --- copyparty/web/browser.js | 130 +++++++++++++++++++++++++++++++++++---- 1 file changed, 118 insertions(+), 12 deletions(-) diff --git a/copyparty/web/browser.js b/copyparty/web/browser.js index 98883e23..9655ea27 100644 --- a/copyparty/web/browser.js +++ b/copyparty/web/browser.js @@ -305,6 +305,7 @@ var Ls = { "mb_play": "play", "mm_hashplay": "play this audio file?", + "mm_m3u": "press Enter/OK to Play\npress ESC/Cancel to Edit", "mp_breq": "need firefox 82+ or chrome 73+ or iOS 15+", "mm_bload": "now loading...", "mm_bconv": "converting to {0}, please wait...", @@ -911,6 +912,7 @@ var Ls = { "mb_play": "lytt", "mm_hashplay": "spill denne sangen?", + "mm_m3u": "trykk Enter/OK for Γ₯ spille\ntrykk ESC/Avbryt for Γ₯ redigere", "mp_breq": "krever firefox 82+, chrome 73+, eller iOS 15+", "mm_bload": "laster inn...", "mm_bconv": "konverterer til {0}, vent litt...", @@ -2630,6 +2632,7 @@ if (can_owa && APPLE && / OS ([1-9]|1[0-7])_/.test(UA)) mpl.init_ac2(); +var re_m3u = /\.(m3u8?)$/i; var re_au_native = (can_ogg || have_acode) ? /\.(aac|flac|m4a|mp3|ogg|opus|wav)$/i : /\.(aac|flac|m4a|mp3|wav)$/i, re_au_all = /\.(aac|ac3|aif|aiff|alac|alaw|amr|ape|au|dfpwm|dts|flac|gsm|it|itgz|itxz|itz|m4a|mdgz|mdxz|mdz|mo3|mod|mp2|mp3|mpc|mptm|mt2|mulaw|ogg|okt|opus|ra|s3m|s3gz|s3xz|s3z|tak|tta|ulaw|wav|wma|wv|xm|xmgz|xmxz|xmz|xpk|3gp|asf|avi|flv|m4v|mkv|mov|mp4|mpeg|mpeg2|mpegts|mpg|mpg2|nut|ogm|ogv|rm|ts|vob|webm|wmv)$/i; @@ -2656,9 +2659,9 @@ function MPlayer() { link = link[link.length - 1]; var url = link.getAttribute('href'), - m = re_audio.exec(url.split('?')[0]); + fn = url.split('?')[0]; - if (m) { + if (re_audio.exec(fn)) { var tid = link.getAttribute('id'); r.order.push(tid); r.tracks[tid] = url; @@ -2666,6 +2669,11 @@ function MPlayer() { ebi('a' + tid).onclick = ev_play; clmod(trs[a], 'au', 1); } + else if (re_m3u.exec(fn)) { + var tid = link.getAttribute('id'); + tds[0].innerHTML = '' + L.mb_play + ''; + ebi('a' + tid).onclick = ev_load_m3u; + } } r.vol = clamp(fcfg_get('vol', IPHONE ? 1 : dvol / 100), 0, 1); @@ -4395,6 +4403,11 @@ function eval_hash() { goto(v.slice(3)); return; } + + if (v.startsWith("#m3u=")) { + load_m3u(v.slice(5)); + return; + } } @@ -6900,7 +6913,7 @@ var ahotkeys = function (e) { // search -(function () { +var search_ui = (function () { var sconf = [ [ L.s_sz, @@ -6935,7 +6948,8 @@ var ahotkeys = function (e) { ] ]; - var trs = [], + var r = {}, + trs = [], orig_url = null, orig_html = null, cap = 125; @@ -7142,13 +7156,19 @@ var ahotkeys = function (e) { search_in_progress = 0; srch_msg(false, ''); - var res = JSON.parse(this.responseText), - tagord = res.tag_order; + var res = JSON.parse(this.responseText); + r.render(res, this, true); + } - sortfiles(res.hits); + r.render = function (res, xhr, sort) { + var tagord = res.tag_order; + + srch_msg(false, ''); + if (sort) + sortfiles(res.hits); var ofiles = ebi('files'); - if (ofiles.getAttribute('ts') > this.ts) + if (xhr && ofiles.getAttribute('ts') > xhr.ts) return; treectl.hide(); @@ -7200,19 +7220,21 @@ var ahotkeys = function (e) { } ofiles = set_files_html(html.join('\n')); - ofiles.setAttribute("ts", this.ts); - ofiles.setAttribute("q_raw", this.q_raw); + ofiles.setAttribute("ts", xhr ? xhr.ts : 1); + ofiles.setAttribute("q_raw", xhr ? xhr.q_raw : 'playlist'); set_vq(); mukey.render(); reload_browser(); filecols.set_style(['File Name']); - sethash('q=' + uricom_enc(this.q_raw)); + if (xhr) + sethash('q=' + uricom_enc(xhr.q_raw)); + ebi('unsearch').onclick = unsearch; var m = ebi('moar'); if (m) m.onclick = moar; - } + }; function unsearch(e) { ev(e); @@ -7229,9 +7251,93 @@ var ahotkeys = function (e) { cap *= 2; do_search(); } + + return r; })(); +function ev_load_m3u(e) { + ev(e); + var id = this.getAttribute('id').slice(1), + url = ebi(id).getAttribute('href').split('?')[0]; + + modal.confirm(L.mm_m3u, + function () { load_m3u(url); }, + function () { window.location = url + '?edit'} + ); + return false; +} +function load_m3u(url) { + var xhr = new XHR(); + xhr.open('GET', url, true); + xhr.onload = render_m3u; + xhr.url = url; + xhr.send(); + return false; +} +function render_m3u() { + if (!xhrchk(this, L.tv_xe1, L.tv_xe2)) + return; + + var evp = get_evpath(), + m3u = this.responseText, + xtd = m3u.slice(0, 12).indexOf('#EXTM3U') + 1, + lines = m3u.replace(/\r/g, '\n').split('\n'), + dur = 1, + artist = '', + title = '', + ret = {'hits': [], 'tag_order': ['artist', 'title', '.dur'], 'trunc': false}; + + for (var a = 0; a < lines.length; a++) { + var ln = lines[a].trim(); + if (xtd && ln.startsWith('#')) { + var m = /^#EXTINF:([0-9]+)[, ](.*)/.exec(ln); + if (m) { + dur = m[1]; + title = m[2]; + var ofs = title.indexOf(' - '); + if (ofs > 0) { + artist = title.slice(0, ofs); + title = title.slice(ofs + 3); + } + } + continue; + } + if (ln.indexOf('.') < 0) + continue; + + var n = ret.hits.length + 1, + url = ln; + + if (url.indexOf(':\\')) // C:\ + url = url.split(/\\/g).pop(); + + url = url.replace(/\\/g, '/'); + url = uricom_enc(url).replace(/%2f/gi, '/') + + if (!url.startsWith('/')) + url = vjoin(evp, url); + + ret.hits.push({ + "ts": 946684800 + n, + "sz": 100000 + n, + "rp": url, + "tags": {".dur": dur, "artist": artist, "title": title} + }); + dur = 1; + artist = title = ''; + } + + search_ui.render(ret, null, false); + sethash('m3u=' + this.url.split('?')[0].split('/').pop()); + goto(); + + var el = QS('#files>tbody>tr.au>td>a.play'); + if (el) + el.click(); +} + + function aligngriditems() { if (!treectl) return; From ad200f2b97d561f6a240d1bfca0d6cd2ed12ef1a Mon Sep 17 00:00:00 2001 From: ed Date: Sat, 26 Apr 2025 00:19:41 +0000 Subject: [PATCH 05/12] add ui for creating playlists --- README.md | 1 + copyparty/web/browser.css | 15 +++++++-- copyparty/web/browser.js | 69 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 83 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1742cdb0..48668b68 100644 --- a/README.md +++ b/README.md @@ -1044,6 +1044,7 @@ open the `[🎺]` media-player-settings tab to configure it, * `[full]` does a full preload by downloading the entire next file; good for unreliable connections, bad for slow connections * `[~s]` toggles the seekbar waveform display * `[/np]` enables buttons to copy the now-playing info as an irc message + * `[πŸ“»]` enables buttons to create an m3u playlist with the selected songs * `[os-ctl]` makes it possible to control audio playback from the lockscreen of your device (enables [mediasession](https://developer.mozilla.org/en-US/docs/Web/API/MediaSession)) * `[seek]` allows seeking with lockscreen controls (buggy on some devices) * `[art]` shows album art on the lockscreen diff --git a/copyparty/web/browser.css b/copyparty/web/browser.css index 14770e65..71fa8c17 100644 --- a/copyparty/web/browser.css +++ b/copyparty/web/browser.css @@ -1151,10 +1151,10 @@ html.y #widget.open { background: #fff; background: var(--bg-u3); } -#wfs, #wfm, #wzip, #wnp { +#wfs, #wfm, #wzip, #wnp, #wm3u { display: none; } -#wfs, #wzip, #wnp { +#wfs, #wzip, #wnp, #wm3u { margin-right: .2em; padding-right: .2em; border: 1px solid var(--bg-u5); @@ -1175,6 +1175,7 @@ html.y #widget.open { line-height: 1em; } #wtoggle.sel #wzip, +#wtoggle.m3u #wm3u, #wtoggle.np #wnp { display: inline-block; } @@ -1183,6 +1184,7 @@ html.y #widget.open { } #wfm a, #wnp a, +#wm3u a, #wzip a { font-size: .5em; padding: 0 .3em; @@ -1190,6 +1192,10 @@ html.y #widget.open { position: relative; display: inline-block; } +#wm3u a { + margin: -.2em .1em; + font-size: .45em; +} #wfs { font-size: .36em; text-align: right; @@ -1198,6 +1204,7 @@ html.y #widget.open { border-width: 0 .25em 0 0; } #wfm span, +#wm3u span, #wnp span { font-size: .6em; display: block; @@ -1205,6 +1212,10 @@ html.y #widget.open { #wnp span { font-size: .7em; } +#wm3u span { + font-size: .77em; + padding-top: .2em; +} #wfm a:not(.en) { opacity: .3; color: var(--fm-off); diff --git a/copyparty/web/browser.js b/copyparty/web/browser.js index 9655ea27..af007753 100644 --- a/copyparty/web/browser.js +++ b/copyparty/web/browser.js @@ -144,6 +144,8 @@ var Ls = { "wt_seldl": "download selection as separate files$NHotkey: Y", "wt_npirc": "copy irc-formatted track info", "wt_nptxt": "copy plaintext track info", + "wt_m3ua": "add to m3u playlist (click πŸ“»copy later)", + "wt_m3uc": "copy m3u playlist to clipboard", "wt_grid": "toggle grid / list view$NHotkey: G", "wt_prev": "previous track$NHotkey: J", "wt_play": "play / pause$NHotkey: P", @@ -280,6 +282,7 @@ var Ls = { "mt_fau": "on phones, prevent music from stopping if the next song doesn't preload fast enough (can make tags display glitchy)\">β˜•οΈ", "mt_waves": "waveform seekbar:$Nshow audio amplitude in the scrubber\">~s", "mt_npclip": "show buttons for clipboarding the currently playing song\">/np", + "mt_m3u_c": "show buttons for clipboarding the$Nselected songs as m3u8 playlist entries\">πŸ“»", "mt_octl": "os integration (media hotkeys / osd)\">os-ctl", "mt_oseek": "allow seeking through os integration$N$Nnote: on some devices (iPhones),$Nthis replaces the next-song button\">seek", "mt_oscv": "show album cover in osd\">art", @@ -436,6 +439,10 @@ var Ls = { "tvt_sel": "select file   ( for cut / copy / delete / ... )$NHotkey: S\">sel", "tvt_edit": "open file in text editor$NHotkey: E\">✏️ edit", + "m3u_add1": "song added to m3u playlist", + "m3u_addn": "{0} songs added to m3u playlist", + "m3u_clip": "m3u playlist now copied to clipboard\n\nyou should create a new textfile named something.m3u and paste the playlist in that document; this will make it playable", + "gt_vau": "don't show videos, just play the audio\">🎧", "gt_msel": "enable file selection; ctrl-click a file to override$N$N<em>when active: doubleclick a file / folder to open it</em>$N$NHotkey: S\">multiselect", "gt_crop": "center-crop thumbnails\">crop", @@ -751,6 +758,8 @@ var Ls = { "wt_seldl": "last ned de valgte filene$NSnarvei: Y", "wt_npirc": "kopiΓ©r sang-info (irc-formatert)", "wt_nptxt": "kopiΓ©r sang-info", + "wt_m3ua": "legg til sang i m3u-spilleliste$N(husk Γ₯ klikke pΓ₯ πŸ“»copy senere)", + "wt_m3uc": "kopiΓ©r m3u-spillelisten til utklippstavlen", "wt_grid": "bytt mellom ikoner og listevisning$NSnarvei: G", "wt_prev": "forrige sang$NSnarvei: J", "wt_play": "play / pause$NSnarvei: P", @@ -887,6 +896,7 @@ var Ls = { "mt_fau": "for telefoner: forhindre at avspilling stopper hvis nettet er for tregt til Γ₯ laste neste sang i tide. Hvis pΓ₯skrudd, kan forΓ₯rsake at sang-info ikke vises korrekt i OS'et\">β˜•οΈ", "mt_waves": "waveform seekbar:$Nvis volumkurve i avspillingsfeltet\">~s", "mt_npclip": "vis knapper for Γ₯ kopiere info om sangen du hΓΈrer pΓ₯\">/np", + "mt_m3u_c": "vis knapper for Γ₯ kopiere de valgte$Nsangene som innslag i en m3u8 spilleliste\">πŸ“»", "mt_octl": "integrering med operativsystemet (fjernkontroll, info-skjerm)\">os-ctl", "mt_oseek": "tillat spoling med fjernkontroll$N$Nmerk: pΓ₯ noen enheter (iPhones) sΓ₯ vil$Ndette erstatte knappen for neste sang\">spoling", "mt_oscv": "vis album-cover pΓ₯ infoskjermen\">bilde", @@ -1043,6 +1053,10 @@ var Ls = { "tvt_sel": "markΓ©r filen   ( for utklipp / sletting / ... )$NSnarvei: S\">merk", "tvt_edit": "redigΓ©r filen$NSnarvei: E\">✏️ endre", + "m3u_add1": "sangen ble lagt til i m3u-spillelisten", + "m3u_addn": "{0} sanger ble lagt til i m3u-spillelisten", + "m3u_clip": "m3u-spillelisten ble kopiert til utklippstavlen\n\nneste steg er Γ₯ opprette et tekstdokument med filnavn som slutter pΓ₯ .m3u og lime inn spillelisten der", + "gt_vau": "ikke vis videofiler, bare spill lyden\">🎧", "gt_msel": "markΓ©r filer istedenfor Γ₯ Γ₯pne dem; ctrl-klikk filer for Γ₯ overstyre$N$N<em>nΓ₯r aktiv: dobbelklikk en fil / mappe for Γ₯ Γ₯pne</em>$N$NSnarvei: S\">markering", "gt_crop": "beskjΓ¦r ikonene sΓ₯ de passer bedre\">βœ‚", @@ -1893,6 +1907,9 @@ ebi('widget').innerHTML = ( 'πŸ“‹ircπŸ“‹txt' + + 'πŸ“»addπŸ“»copy' + 'η”°β™«' + @@ -2308,6 +2325,7 @@ var mpl = (function () { ' 0) { + dur = dur.split(':'); + dur = 60 * parseInt(dur[0]) + parseInt(dur[1]); + } + else dur = parseInt(dur); + + mpl.m3ut += '#EXTINF:' + dur + ',' + tag + '\n' + uricom_dec(get_evpath()) + md.file + '\n'; + } + toast.ok(2, files.length == 1 ? L.m3u_add1 : L.m3u_addn.format(files.length), null, 'top'); + }; + m3uc.onclick = function (e) { + ev(e); + cliptxt(mpl.m3ut, function () { + toast.ok(15, L.m3u_clip, null, 'top'); + }); + }; r.set(sread('au_open') == 1); setTimeout(function () { clmod(widget, 'anim', 1); @@ -4129,6 +4196,7 @@ function play(tid, is_ev, seek) { clmod(ebi(oid), 'act', 1); clmod(ebi(oid).closest('tr'), 'play', 1); clmod(ebi('wtoggle'), 'np', mpl.clip); + clmod(ebi('wtoggle'), 'm3u', mpl.m3uen); if (thegrid) thegrid.loadsel(); @@ -4749,6 +4817,7 @@ var fileman = (function () { clmod(bshr, 'hide', hshr); clmod(ebi('wfm'), 'act', QS('#wfm a.en:not(.hide)')); + clmod(ebi('wtoggle'), 'm3u', mpl.m3uen && (nsel || (mp && mp.au))); var wfs = ebi('wfs'), h = ''; try { From dc3b7a2720761d69fe62e8f04022ff18ae4b9e34 Mon Sep 17 00:00:00 2001 From: ed Date: Sat, 26 Apr 2025 19:06:32 +0000 Subject: [PATCH 06/12] reduce `--th-ram-max` floor; helps avoid oom in a vm with 512 MiB ram --- copyparty/__main__.py | 2 +- copyparty/util.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/copyparty/__main__.py b/copyparty/__main__.py index 0dbab9f2..20400540 100644 --- a/copyparty/__main__.py +++ b/copyparty/__main__.py @@ -1352,7 +1352,7 @@ def add_admin(ap): def add_thumbnail(ap): th_ram = (RAM_AVAIL or RAM_TOTAL or 9) * 0.6 - th_ram = int(max(min(th_ram, 6), 1) * 10) / 10 + th_ram = int(max(min(th_ram, 6), 0.3) * 10) / 10 ap2 = ap.add_argument_group('thumbnail options') ap2.add_argument("--no-thumb", action="store_true", help="disable all thumbnails (volflag=dthumb)") ap2.add_argument("--no-vthumb", action="store_true", help="disable video thumbnails (volflag=dvthumb)") diff --git a/copyparty/util.py b/copyparty/util.py index 7849f103..2e204ba1 100644 --- a/copyparty/util.py +++ b/copyparty/util.py @@ -472,6 +472,8 @@ FN_EMB = set([".prologue.html", ".epilogue.html", "readme.md", "preadme.md"]) def read_ram() -> tuple[float, float]: + # NOTE: apparently no need to consider /sys/fs/cgroup/memory.max + # (cgroups2) since the limit is synced to /proc/meminfo a = b = 0 try: with open("/proc/meminfo", "rb", 0x10000) as f: From 4195762d2a82c8189917c3652fb9a84ff3bb73d2 Mon Sep 17 00:00:00 2001 From: ed Date: Sat, 26 Apr 2025 19:28:12 +0000 Subject: [PATCH 07/12] playlist: when lacking perms, s/edit/view/ --- copyparty/web/browser.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/copyparty/web/browser.js b/copyparty/web/browser.js index af007753..5fdb4659 100644 --- a/copyparty/web/browser.js +++ b/copyparty/web/browser.js @@ -7332,7 +7332,12 @@ function ev_load_m3u(e) { modal.confirm(L.mm_m3u, function () { load_m3u(url); }, - function () { window.location = url + '?edit'} + function () { + if (has(perms, 'write') && has(perms, 'delete')) + window.location = url + '?edit'; + else + showfile.show(url); + } ); return false; } From 3090c7483233cd69c29860c13819cb9ca43ecb33 Mon Sep 17 00:00:00 2001 From: ed Date: Sat, 26 Apr 2025 19:57:59 +0000 Subject: [PATCH 08/12] ie11: fix debounce-untint; css 'unset' appeared in chr41, ff27 dom.closest appeared in chr41, ff35 --- copyparty/web/browser.js | 6 +++--- copyparty/web/util.js | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/copyparty/web/browser.js b/copyparty/web/browser.js index 5fdb4659..3e589ac5 100644 --- a/copyparty/web/browser.js +++ b/copyparty/web/browser.js @@ -8313,7 +8313,7 @@ var treectl = (function () { document.documentElement.scrollLeft = 0; setTimeout(function () { r.gentab(get_evpath(), r.lsc); - ebi('wrap').style.opacity = 'unset'; + ebi('wrap').style.opacity = CLOSEST ? 'unset' : 1; }, 1); }; @@ -8466,7 +8466,7 @@ var wfp_debounce = (function () { if (--r.n <= 0) { r.n = 0; clearTimeout(r.t); - ebi('wfp').style.opacity = 'unset'; + ebi('wfp').style.opacity = CLOSEST ? 'unset' : 1; } }; r.reset = function () { @@ -9704,7 +9704,7 @@ window.addEventListener("message", function (e) { el.parentNode.removeChild(el.previousSibling); el.style.height = (parseInt(t[2]) + SBH) + 'px'; - el.style.visibility = 'unset'; + el.style.visibility = CLOSEST ? 'unset' : 'block'; wfp_debounce.show(); } else if (t[0] == 'iscroll') { diff --git a/copyparty/web/util.js b/copyparty/web/util.js index 17fb1943..e2c37ec2 100644 --- a/copyparty/web/util.js +++ b/copyparty/web/util.js @@ -364,7 +364,8 @@ if (!Element.prototype.matches) Element.prototype.mozMatchesSelector || Element.prototype.webkitMatchesSelector; -if (!Element.prototype.closest) +var CLOSEST = !!Element.prototype.closest; +if (!CLOSEST) Element.prototype.closest = function (s) { var el = this; do { From 95157d02c9b8977b33409b658470ab27d6ea1ed1 Mon Sep 17 00:00:00 2001 From: ed Date: Sat, 26 Apr 2025 20:14:23 +0000 Subject: [PATCH 09/12] ie11 can't sandbox; add minimal fallback --- copyparty/web/browser.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/copyparty/web/browser.js b/copyparty/web/browser.js index 3e589ac5..ec3bbd66 100644 --- a/copyparty/web/browser.js +++ b/copyparty/web/browser.js @@ -9637,6 +9637,11 @@ function sandbox(tgt, rules, allow, cls, html) { clmod(tgt, 'sb'); return false; } + if (!CLOSEST) { + tgt.textContent = html; + clmod(tgt, 'sb'); + return false; + } clmod(tgt, 'sb', 1); var tid = tgt.getAttribute('id'), From fff45552daa3d7ca18f26672f0f2698fd0f5d678 Mon Sep 17 00:00:00 2001 From: ed Date: Sat, 26 Apr 2025 21:49:09 +0000 Subject: [PATCH 10/12] v1.17.0 --- README.md | 35 +++++++++++++++++++++++++++++++++-- copyparty/__version__.py | 6 +++--- docs/changelog.md | 23 +++++++++++++++++++++++ 3 files changed, 59 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 48668b68..5eba8c7b 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,8 @@ turn almost any device into a file server with resumable uploads/downloads using * [rss feeds](#rss-feeds) - monitor a folder with your RSS reader * [recent uploads](#recent-uploads) - list all recent uploads * [media player](#media-player) - plays almost every audio format there is + * [playlists](#playlists) - create and play [m3u8](https://en.wikipedia.org/wiki/M3U) playlists + * [creating a playlist](#creating-a-playlist) - with a standalone mediaplayer or copyparty * [audio equalizer](#audio-equalizer) - and [dynamic range compressor](https://en.wikipedia.org/wiki/Dynamic_range_compression) * [fix unreliable playback on android](#fix-unreliable-playback-on-android) - due to phone / app settings * [markdown viewer](#markdown-viewer) - and there are *two* editors @@ -251,6 +253,7 @@ also see [comparison to similar software](./docs/versus.md) * β˜‘ file manager (cut/paste, delete, [batch-rename](#batch-rename)) * β˜‘ audio player (with [OS media controls](https://user-images.githubusercontent.com/241032/215347492-b4250797-6c90-4e09-9a4c-721edf2fb15c.png) and opus/mp3 transcoding) * β˜‘ play video files as audio (converted on server) + * β˜‘ create and play [m3u8 playlists](#playlists) * β˜‘ image gallery with webm player * β˜‘ textfile browser with syntax hilighting * β˜‘ [thumbnails](#thumbnails) @@ -1044,7 +1047,7 @@ open the `[🎺]` media-player-settings tab to configure it, * `[full]` does a full preload by downloading the entire next file; good for unreliable connections, bad for slow connections * `[~s]` toggles the seekbar waveform display * `[/np]` enables buttons to copy the now-playing info as an irc message - * `[πŸ“»]` enables buttons to create an m3u playlist with the selected songs + * `[πŸ“»]` enables buttons to create an [m3u playlist](#playlists) with the selected songs * `[os-ctl]` makes it possible to control audio playback from the lockscreen of your device (enables [mediasession](https://developer.mozilla.org/en-US/docs/Web/API/MediaSession)) * `[seek]` allows seeking with lockscreen controls (buggy on some devices) * `[art]` shows album art on the lockscreen @@ -1063,11 +1066,39 @@ open the `[🎺]` media-player-settings tab to configure it, * "transcode to": * `[opus]` produces an `opus` whenever transcoding is necessary (the best choice on Android and PCs) * `[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 - * `[caf]` is `opus` in a `caf` file, good for iPhones (iOS 11 through 17), technically unsupported by Apple but works for the mos tpart + * `[caf]` is `opus` in a `caf` file, good for iPhones (iOS 11 through 17), technically unsupported by Apple but works for the most part * `[mp3]` -- the myth, the legend, the undying master of mediocre sound quality that definitely works everywhere * "tint" reduces the contrast of the playback bar +### playlists + +create and play [m3u8](https://en.wikipedia.org/wiki/M3U) playlists -- see example [text](https://a.ocv.me/pub/demo/music/?doc=example-playlist.m3u) and [player](https://a.ocv.me/pub/demo/music/#m3u=example-playlist.m3u) + +click a file with the extension `m3u` or `m3u8` (for example `mixtape.m3u` or `touhou.m3u8` ) and you get two choices: Play / Edit + +playlists can include songs across folders anywhere on the server, but filekeys/dirkeys are NOT supported, so the listener must have read-access or get-access to the files + + +### creating a playlist + +with a standalone mediaplayer or copyparty + +you can use foobar2000, deadbeef, just about any standalone player should work -- but you might need to edit the filepaths in the playlist so they fit with the server-URLs + +alternatively, you can create the playlist using copyparty itself: + +* open the `[🎺]` media-player-settings tab and enable the `[πŸ“»]` create-playlist feature -- this adds two new buttons in the bottom-right tray, `[πŸ“»add]` and `[πŸ“»copy]` which appear when you listen to music, or when you select a few audiofiles + +* click the `πŸ“»add` button while a song is playing (or when you've selected some songs) and they'll be added to "the list" (you can't see it yet) + +* at any time, click `πŸ“»copy` to send the playlist to your clipboard + * you can then continue adding more songs if you'd like + * if you want to wipe the playlist and start from scratch, just refresh the page + +* create a new textfile, name it `something.m3u` and paste the playlist there + + ### audio equalizer and [dynamic range compressor](https://en.wikipedia.org/wiki/Dynamic_range_compression) diff --git a/copyparty/__version__.py b/copyparty/__version__.py index d20e66c4..e97d864a 100644 --- a/copyparty/__version__.py +++ b/copyparty/__version__.py @@ -1,8 +1,8 @@ # coding: utf-8 -VERSION = (1, 16, 21) -CODENAME = "COPYparty" -BUILD_DT = (2025, 4, 20) +VERSION = (1, 17, 0) +CODENAME = "mixtape.m3u" +BUILD_DT = (2025, 4, 26) S_VERSION = ".".join(map(str, VERSION)) S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT) diff --git a/docs/changelog.md b/docs/changelog.md index b0d3438e..ea823f28 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,3 +1,26 @@ +β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€ +# 2025-0420-1836 `v1.16.21` unzip-compat + +a couple guys have been asking if I accept donations -- thanks a lot!! added a few options on [my github page](https://github.com/9001/) :> + +## πŸ§ͺ new features + +* #156 add button to loop/repeat music 71c55659 + +## 🩹 bugfixes + +* #155 download-as-zip: increase compatibility with the unix `unzip` command db33d68d + * this unfortunately reduces support for huge zipfiles on old software (WinXP and such) + * and makes it less safe to stream zips into unzippers, so use tar.gz instead + * and is perhaps not even a copyparty bug; see commit-message for the full story + +## πŸ”§ other changes + +* show warning on Ctrl-A in lazy-loaded folders 5b3a5fe7 +* docker: hide keepalive pings from logs d5a9bd80 + + + β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€β–€ # 2025-0413-2151 `v1.16.20` all sorted From 3cbb7243ab12b9e300a71313c14cd06c60e5cd61 Mon Sep 17 00:00:00 2001 From: ed Date: Sat, 26 Apr 2025 22:50:45 +0000 Subject: [PATCH 11/12] update pkgs to 1.17.0 --- contrib/package/arch/PKGBUILD | 4 ++-- contrib/package/nix/copyparty/pin.json | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/contrib/package/arch/PKGBUILD b/contrib/package/arch/PKGBUILD index d541adc2..45acdc07 100644 --- a/contrib/package/arch/PKGBUILD +++ b/contrib/package/arch/PKGBUILD @@ -1,6 +1,6 @@ # Maintainer: icxes pkgname=copyparty -pkgver="1.16.21" +pkgver="1.17.0" pkgrel=1 pkgdesc="File server with accelerated resumable uploads, dedup, WebDAV, FTP, TFTP, zeroconf, media indexer, thumbnails++" arch=("any") @@ -22,7 +22,7 @@ optdepends=("ffmpeg: thumbnails for videos, images (slower) and audio, music tag ) source=("https://github.com/9001/${pkgname}/releases/download/v${pkgver}/${pkgname}-${pkgver}.tar.gz") backup=("etc/${pkgname}.d/init" ) -sha256sums=("2e416e18dc854c65643b8aaedca56e0a5c5a03b0c3d45b7ff3f68daa38d8e9c6") +sha256sums=("d8a49b3398f4cdb0754dd8b9e3ab32544e44e2fee94c88903f65ffc003b6eeec") build() { cd "${srcdir}/${pkgname}-${pkgver}" diff --git a/contrib/package/nix/copyparty/pin.json b/contrib/package/nix/copyparty/pin.json index 3491347d..9138bb87 100644 --- a/contrib/package/nix/copyparty/pin.json +++ b/contrib/package/nix/copyparty/pin.json @@ -1,5 +1,5 @@ { - "url": "https://github.com/9001/copyparty/releases/download/v1.16.21/copyparty-sfx.py", - "version": "1.16.21", - "hash": "sha256-+/f4g8J2Mv0l6ChXzbNJ84G8LeB+mP1UfkWzQxizd/g=" + "url": "https://github.com/9001/copyparty/releases/download/v1.17.0/copyparty-sfx.py", + "version": "1.17.0", + "hash": "sha256-iRqaXQvwX4DceOhZmI6g9KXeR+rFAWnNHK/GTHkoQ7Q=" } \ No newline at end of file From 4fb87ebe324bbcb211f818864d7f6c8abf412060 Mon Sep 17 00:00:00 2001 From: ed Date: Sun, 27 Apr 2025 09:25:01 +0000 Subject: [PATCH 12/12] flatcase best case --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 5eba8c7b..d3defd5e 100644 --- a/README.md +++ b/README.md @@ -417,6 +417,9 @@ upgrade notes "frequently" asked questions +* CopyParty? + * nope! the name is either copyparty (all-lowercase) or Copyparty -- it's [one word](https://en.wiktionary.org/wiki/copyparty) after all :> + * can I change the 🌲 spinning pine-tree loading animation? * [yeah...](https://github.com/9001/copyparty/tree/hovudstraum/docs/rice#boring-loader-spinner) :-(