From 0d09fb681869932b16015e5cba0b068ed4a27508 Mon Sep 17 00:00:00 2001 From: ed Date: Sun, 3 Aug 2025 21:23:41 +0000 Subject: [PATCH] audio transcoding tweaks --- README.md | 3 +++ copyparty/__main__.py | 4 ++-- copyparty/authsrv.py | 2 ++ copyparty/mtag.py | 4 +++- copyparty/th_srv.py | 8 ++++---- copyparty/web/browser.js | 37 ++++++++++++++++++++++++++----------- copyparty/web/splash.js | 1 + scripts/tl.js | 5 +++-- 8 files changed, 44 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 1a500c5b..93b6a464 100644 --- a/README.md +++ b/README.md @@ -1084,6 +1084,9 @@ open the `[🎺]` media-player-settings tab to configure it, * `[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 most part * `[mp3]` -- the myth, the legend, the undying master of mediocre sound quality that definitely works everywhere + * `[flac]` -- lossless but compressed, for LAN and/or fiber playback on electrostatic headphones + * `[wav]` -- lossless and uncompressed, for LAN and/or fiber playback on electrostatic headphones connected to very old equipment + * `flac` and `wav` must be enabled with `--allow-flac` / `--allow-wav` to allow spending the disk space * "tint" reduces the contrast of the playback bar diff --git a/copyparty/__main__.py b/copyparty/__main__.py index 183fb916..21bda508 100644 --- a/copyparty/__main__.py +++ b/copyparty/__main__.py @@ -1447,10 +1447,10 @@ def add_transcoding(ap): ap2 = ap.add_argument_group('transcoding options') ap2.add_argument("--q-opus", metavar="KBPS", type=int, default=128, help="target bitrate for transcoding to opus; set 0 to disable") 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") + ap2.add_argument("--allow-wav", action="store_true", help="allow transcoding to wav (lossless, uncompressed)") + ap2.add_argument("--allow-flac", action="store_true", help="allow transcoding to flac (lossless, compressed)") ap2.add_argument("--no-caf", action="store_true", help="disable transcoding to caf-opus (affects iOS v12~v17), will use mp3 instead") ap2.add_argument("--no-owa", action="store_true", help="disable transcoding to webm-opus (iOS v18 and later), will use mp3 instead") - ap2.add_argument("--no-wav", action="store_true", help="disable transcoding to wav (lossless, uncompressed)") - ap2.add_argument("--no-flac", action="store_true", help="disable transcoding to flac (lossless, compressed)") ap2.add_argument("--no-acode", action="store_true", help="disable audio transcoding") ap2.add_argument("--no-bacode", action="store_true", help="disable batch audio transcoding by folder download (zip/tar)") ap2.add_argument("--ac-maxage", metavar="SEC", type=int, default=86400, help="delete cached transcode output after \033[33mSEC\033[0m seconds") diff --git a/copyparty/authsrv.py b/copyparty/authsrv.py index 01fad895..76a09ecd 100644 --- a/copyparty/authsrv.py +++ b/copyparty/authsrv.py @@ -2751,6 +2751,8 @@ class AuthSrv(object): "s_name": self.args.bname, "have_up2k_idx": "e2d" in vf, "have_acode": not self.args.no_acode, + "have_c2flac": self.args.allow_flac, + "have_c2wav": self.args.allow_wav, "have_shr": self.args.shr, "have_zip": not self.args.no_zip, "have_mv": not self.args.no_mv, diff --git a/copyparty/mtag.py b/copyparty/mtag.py index 240b5a83..e9428b79 100644 --- a/copyparty/mtag.py +++ b/copyparty/mtag.py @@ -67,6 +67,8 @@ HAVE_FFPROBE = not os.environ.get("PRTY_NO_FFPROBE") and have_ff("ffprobe") CBZ_PICS = set("png jpg jpeg gif bmp tga tif tiff webp avif".split()) CBZ_01 = re.compile(r"(^|[^0-9v])0+[01]\b") +FMT_AU = set("mp3 ogg flac wav".split()) + class MParser(object): def __init__(self, cmdline: str) -> None: @@ -242,7 +244,7 @@ def parse_ffprobe(txt: str) -> tuple[dict[str, tuple[int, Any]], dict[str, list[ ret: dict[str, Any] = {} # processed md: dict[str, list[Any]] = {} # raw tags - is_audio = fmt.get("format_name") in ["mp3", "ogg", "flac", "wav"] + is_audio = fmt.get("format_name") in FMT_AU if fmt.get("filename", "").split(".")[-1].lower() in ["m4a", "aac"]: is_audio = True diff --git a/copyparty/th_srv.py b/copyparty/th_srv.py index e8094b5a..f40d0a43 100644 --- a/copyparty/th_srv.py +++ b/copyparty/th_srv.py @@ -814,8 +814,8 @@ class ThumbSrv(object): self._run_ff(cmd, vn, oom=300) def conv_flac(self, abspath: str, tpath: str, fmt: str, vn: VFS) -> None: - if self.args.no_acode or self.args.no_flac: - raise Exception("disabled in server config") + if self.args.no_acode or not self.args.allow_flac: + raise Exception("flac not permitted in server config") self.wait4ram(0.2, tpath) tags, rawtags = ffprobe(abspath, int(vn.flags["convt"] / 2)) @@ -839,8 +839,8 @@ class ThumbSrv(object): self._run_ff(cmd, vn, oom=300) def conv_wav(self, abspath: str, tpath: str, fmt: str, vn: VFS) -> None: - if self.args.no_acode or self.args.no_wav: - raise Exception("disabled in server config") + if self.args.no_acode or not self.args.allow_wav: + raise Exception("wav not permitted in server config") self.wait4ram(0.2, tpath) tags, rawtags = ffprobe(abspath, int(vn.flags["convt"] / 2)) diff --git a/copyparty/web/browser.js b/copyparty/web/browser.js index 201b3588..3dca9476 100644 --- a/copyparty/web/browser.js +++ b/copyparty/web/browser.js @@ -4,6 +4,7 @@ var XHR = XMLHttpRequest, img_re = /\.(a?png|avif|bmp|gif|heif|jpe?g|jfif|svg|webp|webm|mkv|mp4|m4v|mov)(\?|$)/i; // please add translations in alphabetic order, but keep "eng" and "nor" first +// (lines ending with //m are machine translations) var Ls = { "eng": { "tt": "English", @@ -306,8 +307,8 @@ var Ls = { "mt_c2owa": "opus-weba, for iOS 17.5 and newer\">owa", "mt_c2caf": "opus-caf, for iOS 11 through 17\">caf", "mt_c2mp3": "use this on very old devices\">mp3", - "mt_c2flac": "best sound quality\">flac", - "mt_c2wav": "uncompressed playback\">wav", + "mt_c2flac": "best sound quality, but huge downloads\">flac", + "mt_c2wav": "uncompressed playback (even bigger)\">wav", "mt_c2ok": "nice, good choice", "mt_c2nd": "that's not the recommended output format for your device, but that's fine", "mt_c2ng": "your device does not seem to support this output format, but let's try anyways", @@ -936,6 +937,8 @@ var Ls = { "mt_c2owa": "opus-weba, for iOS 17.5 og nyere\">owa", "mt_c2caf": "opus-caf, for iOS 11 tilogmed 17\">caf", "mt_c2mp3": "bra valg for steinalder-utstyr (slår aldri feil)\">mp3", + "mt_c2flac": "gir best lydkvalitet, men eter nettet ditt\">flac", + "mt_c2wav": "helt rå lydstrøm (bruker enda mere data enn flac)\">wav", "mt_c2ok": "bra valg!", "mt_c2nd": "ikke det foretrukne valget for din enhet, men funker sikkert greit", "mt_c2ng": "ser virkelig ikke ut som enheten din takler dette formatet... men ok, vi prøver", @@ -1563,6 +1566,8 @@ var Ls = { "mt_c2owa": "opus-weba(适用于 iOS 17.5 及更新版本)\">owa", //m "mt_c2caf": "opus-caf(适用于 iOS 11 到 iOS 17)\">caf", //m "mt_c2mp3": "适用于非常旧的设备\">mp3", //m + "mt_c2flac": "最佳音质,但下载量很大\">flac", //m + "mt_c2wav": "无压缩播放(更占空间)\">wav", //m "mt_c2ok": "不错的选择!", //m "mt_c2nd": "这不是您的设备推荐的输出格式,但应该没问题。", //m "mt_c2ng": "您的设备似乎不支持此输出格式,不过我们还是试试看吧。", //m @@ -2190,6 +2195,8 @@ var Ls = { "mt_c2owa": "opus-weba, für iOS 17.5 und neuer\">owa", "mt_c2caf": "opus-caf, für iOS 11 bis 17\">caf", "mt_c2mp3": "benutze dieses Format für ältere Geräte\">mp3", + "mt_c2flac": "beste Klangqualität, aber große Downloads\">flac", //m + "mt_c2wav": "unkomprimierte Wiedergabe (noch größer)\">wav", //m "mt_c2ok": "Gute Wahl, Chef!", "mt_c2nd": "Das ist nicht das empfohlene Ausgabeformat für dein Gerät, aber passt schon", "mt_c2ng": "Dein Gerät scheint dieses Ausgabeformat nicht zu unterstützen, aber lass trotzdem mal probieren", @@ -2817,6 +2824,8 @@ var Ls = { "mt_c2owa": "opus-weba, iOS 17.5:lle ja uudemmille\">owa", "mt_c2caf": "opus-caf, iOS 11:lle - 17:lle\">caf", "mt_c2mp3": "käytä tätä erittäin vanhoissa laitteissa\">mp3", + "mt_c2flac": "paras äänenlaatu, mutta isot lataukset\">flac", //m + "mt_c2wav": "pakkaamaton toisto (vielä suurempi tiedosto)\">wav", //m "mt_c2ok": "hienoa, hyvä valinta", "mt_c2nd": "tuo ei ole suositeltu formaatti laitteellesi, mutta tee miten lystäät", "mt_c2ng": "laitteesi ei näytä tukevan tätä formaattia, mutta yritetään nyt silti", @@ -3444,6 +3453,8 @@ var Ls = { "mt_c2owa": "opus-weba, per iOS 17.5 e più recenti\">owa", "mt_c2caf": "opus-caf, per iOS 11 fino a 17\">caf", "mt_c2mp3": "usa questo su dispositivi molto vecchi\">mp3", + "mt_c2flac": "qualità audio migliore, ma download pesanti\">flac", //m + "mt_c2wav": "riproduzione non compressa (ancora più grande)\">wav", //m "mt_c2ok": "bene, buona scelta", "mt_c2nd": "quello non è il formato di output raccomandato per il tuo dispositivo, ma va bene", "mt_c2ng": "il tuo dispositivo non sembra supportare questo formato di output, ma proviamo comunque", @@ -4071,6 +4082,8 @@ var Ls = { "mt_c2owa": "opus-weba, voor iOS 17.5 en nieuwer\">owa", "mt_c2caf": "opus-caf, voor iOS 11 tot en met iOS 17\">caf", "mt_c2mp3": "Gebruik dit hele oude toestellen\">mp3", + "mt_c2flac": "Beste geluidskwaliteit, maar grote downloads\">flac", //m + "mt_c2wav": "Ongemprimeerde weergave (nog groter)\">wav", //m "mt_c2ok": "Mooi, goede keuze", "mt_c2nd": "Dat is niet het aanbevolen uitvoerformaat voor uw apparaat, maar dat is prima", "mt_c2ng": "Uw apparaat lijkt dit uitvoerformaat niet te ondersteunen, maar we gaan het toch proberen", @@ -4698,6 +4711,8 @@ var Ls = { "mt_c2owa": "opus-weba, для iOS 17.5 и выше\">owa", "mt_c2caf": "opus-caf, для iOS 11-17\">caf", "mt_c2mp3": "для очень старых устройств\">mp3", + "mt_c2flac": "лучшее качество звука, но большие файлы\">flac", //m + "mt_c2wav": "не сжатое воспроизведение (ещё больше)\">wav", //m "mt_c2ok": "хороший выбор", "mt_c2nd": "это не рекомендованный вариант формата для вашего устройства, но сойдёт", "mt_c2ng": "не похоже, что ваше устройство поддерживает этот формат, но давайте попробуем и узнаем наверняка", @@ -5324,6 +5339,8 @@ var Ls = { "mt_c2owa": 'opus-weba, para iOS 17.5 y superior">owa', "mt_c2caf": 'opus-caf, para iOS 11 a 17">caf', "mt_c2mp3": 'usar en dispositivos muy antiguos">mp3', + "mt_c2flac": "la mejor calidad de sonido,$Npero descargas muy grandes\">flac", //m + "mt_c2wav": "reproducción sin comprimir (aún más grande)\">wav", //m "mt_c2ok": "bien, buena elección", "mt_c2nd": "ese no es el formato de salida recomendado para tu dispositivo, pero está bien", "mt_c2ng": "tu dispositivo no parece soportar este formato de salida, pero intentémoslo de todas formas", @@ -5951,6 +5968,8 @@ var Ls = { "mt_c2owa": "opus-weba, для iOS 17.5 і новіших\">owa", "mt_c2caf": "opus-caf, для iOS 11 до 17\">caf", "mt_c2mp3": "використовуйте це на дуже старих пристроях\">mp3", + "mt_c2flac": "найкраща якість звуку, але великі завантаження\">flac", //m + "mt_c2wav": "відтворення без стиснення (ще більше)\">wav", //m "mt_c2ok": "гарно, хороший вибір", "mt_c2nd": "це не рекомендований вихідний формат для вашого пристрою, але це нормально", "mt_c2ng": "ваш пристрій, здається, не підтримує цей вихідний формат, але давайте все одно спробуємо", @@ -6921,10 +6940,8 @@ var mpl = (function () { if (!have_acode) c = false; - else if (/\.flac$/i.exec(cs)) + else if (/\.(wav|flac)$/i.exec(cs)) c = r.ac_flac; - else if (/\.wav$/i.exec(cs)) - c = r.ac_wav; else if (/\.(aac|m4a)$/i.exec(cs)) c = r.ac_aac; else if (/\.(oga|ogg|opus)$/i.exec(cs) && (!can_ogg || mpl.ac2 == 'mp3')) @@ -6932,6 +6949,7 @@ var mpl = (function () { else if (re_au_native.exec(cs)) c = false; + // allow flac->flac (bitstream fixup) if (!c) return url; @@ -6949,9 +6967,7 @@ var mpl = (function () { } var dv = can_ogg ? 'opus' : - can_caf ? 'caf' : - can_mp3 ? 'mp3' : - can_flac ? 'flac' : 'wav', + can_caf ? 'caf' : 'mp3', fmts = ['opus', 'owa', 'caf', 'mp3', 'flac', 'wav'], btns = []; @@ -6963,7 +6979,6 @@ var mpl = (function () { if ((v == 'opus' && !can_ogg) || (v == 'caf' && !can_caf) || (v == 'owa' && !can_owa) || - (v == 'mp3' && !can_mp3) || (v == 'flac' && !can_flac)) toast.warn(15, L.mt_c2ng); @@ -6979,6 +6994,8 @@ var mpl = (function () { } if (!IPHONE) btns[1].style.display = btns[2].style.display = 'none'; + btns[4].style.display = have_c2flac ? '' : 'none'; + btns[5].style.display = have_c2wav ? '' : 'none'; if (v) swrite('acode2', v); @@ -7102,14 +7119,12 @@ var mpl = (function () { var za, can_ogg = true, can_owa = false, - can_mp3 = false, can_flac = false, can_caf = APPLE && !/ OS ([1-9]|1[01])_/.test(UA); try { za = new Audio(); can_ogg = za.canPlayType('audio/ogg; codecs=opus') === 'probably'; can_owa = za.canPlayType('audio/webm; codecs=opus') === 'probably'; - can_mp3 = za.canPlayType('audio/mpeg') === 'probably'; can_flac = za.canPlayType('audio/flac') === 'probably'; can_caf = za.canPlayType('audio/x-caf') && can_caf; //'maybe' } diff --git a/copyparty/web/splash.js b/copyparty/web/splash.js index 44f315ff..bad3615f 100644 --- a/copyparty/web/splash.js +++ b/copyparty/web/splash.js @@ -1,4 +1,5 @@ // please add translations in alphabetic order, but keep "nor" and "eng" first +// (lines ending with //m are machine translations) var Ls = { "nor": { "a1": "oppdater", diff --git a/scripts/tl.js b/scripts/tl.js index e24d9451..97bf061d 100644 --- a/scripts/tl.js +++ b/scripts/tl.js @@ -315,6 +315,7 @@ var tl_browser = { "ct_qdel": 'when deleting files, only ask for confirmation once">qdel', "ct_dir1st": 'sort folders before files">📁 first', "ct_nsort": 'natural sort (for filenames with leading digits)">nsort', + "ct_utc": 'show all datetimes in UTC">UTC', "ct_readme": 'show README.md in folder listings">📜 readme', "ct_idxh": 'show index.html instead of folder listing">htm', "ct_sbars": 'show scrollbars">⟊', @@ -390,8 +391,8 @@ var tl_browser = { "mt_c2owa": "opus-weba, for iOS 17.5 and newer\">owa", "mt_c2caf": "opus-caf, for iOS 11 through 17\">caf", "mt_c2mp3": "use this on very old devices\">mp3", - "mt_c2flac": "best sound quality\">flac", - "mt_c2wav": "uncompressed playback\">wav", + "mt_c2flac": "best sound quality, but huge downloads\">flac", + "mt_c2wav": "uncompressed playback (even bigger)\">wav", "mt_c2ok": "nice, good choice", "mt_c2nd": "that's not the recommended output format for your device, but that's fine", "mt_c2ng": "your device does not seem to support this output format, but let's try anyways",