From 4f615a37e478e570b3972d76b7f8f57100094916 Mon Sep 17 00:00:00 2001 From: Toby Kohlhagen Date: Sun, 3 Aug 2025 23:26:13 +1000 Subject: [PATCH] Add wav transcoding option --- copyparty/httpcli.py | 2 +- copyparty/mtag.py | 2 ++ copyparty/th_cli.py | 4 ++-- copyparty/th_srv.py | 45 +++++++++++++++++++++++++++++++++++++--- copyparty/web/browser.js | 15 ++++++++++---- 5 files changed, 58 insertions(+), 10 deletions(-) diff --git a/copyparty/httpcli.py b/copyparty/httpcli.py index bea4ce43..51620912 100644 --- a/copyparty/httpcli.py +++ b/copyparty/httpcli.py @@ -4684,7 +4684,7 @@ class HttpCli(object): # for f in fgen: print(repr({k: f[k] for k in ["vp", "ap"]})) cfmt = "" if self.thumbcli and not self.args.no_bacode: - for zs in ("opus", "mp3", "w", "j", "p"): + for zs in ("opus", "mp3", "wav", "w", "j", "p"): if zs in self.ouparam or uarg == zs: cfmt = zs diff --git a/copyparty/mtag.py b/copyparty/mtag.py index cacfda39..240b5a83 100644 --- a/copyparty/mtag.py +++ b/copyparty/mtag.py @@ -270,6 +270,8 @@ def parse_ffprobe(txt: str) -> tuple[dict[str, tuple[int, Any]], dict[str, list[ ["channel_layout", "chs"], ["sample_rate", ".hz"], ["bit_rate", ".aq"], + ["bits_per_sample", ".bps"], + ["bits_per_raw_sample", ".bprs"], ["duration", ".dur"], ] diff --git a/copyparty/th_cli.py b/copyparty/th_cli.py index cfb148c2..6c2632b5 100644 --- a/copyparty/th_cli.py +++ b/copyparty/th_cli.py @@ -88,7 +88,7 @@ class ThumbCli(object): if rem.startswith(".hist/th/") and rem.split(".")[-1] in ["webp", "jpg", "png"]: return os.path.join(ptop, rem) - if fmt[:1] in "jw": + if fmt[:1] in "jw" and fmt != "wav": sfmt = fmt[:1] if sfmt == "j" and self.args.th_no_jpg: @@ -129,7 +129,7 @@ class ThumbCli(object): tpath = thumb_path(histpath, rem, mtime, fmt, self.fmt_ffa) tpaths = [tpath] - if fmt[:1] == "w": + if fmt[:1] == "w" and fmt != "wav": # also check for jpg (maybe webp is unavailable) tpaths.append(tpath.rsplit(".", 1)[0] + ".jpg") diff --git a/copyparty/th_srv.py b/copyparty/th_srv.py index 67413c10..8eaf9287 100644 --- a/copyparty/th_srv.py +++ b/copyparty/th_srv.py @@ -50,7 +50,7 @@ HAVE_AVIF = False HAVE_WEBP = False EXTS_TH = set(["jpg", "webp", "png"]) -EXTS_AC = set(["opus", "owa", "caf", "mp3"]) +EXTS_AC = set(["opus", "owa", "caf", "mp3", "wav"]) EXTS_SPEC_SAFE = set("aif aiff flac mp3 opus wav".split()) PTN_TS = re.compile("^-?[0-9a-f]{8,10}$") @@ -355,8 +355,9 @@ class ThumbSrv(object): tex = tpath.rsplit(".", 1)[-1] want_mp3 = tex == "mp3" want_opus = tex in ("opus", "owa", "caf") + want_wav = tex == "wav" want_png = tex == "png" - want_au = want_mp3 or want_opus + want_au = want_mp3 or want_opus or want_wav for lib in self.args.th_dec: can_au = lib == "ff" and ( ext in self.fmt_ffa or ext in self.fmt_ffv @@ -371,6 +372,8 @@ class ThumbSrv(object): funs.append(self.conv_opus) elif want_mp3: funs.append(self.conv_mp3) + elif want_wav: + funs.append(self.conv_wav) elif want_png: funs.append(self.conv_waves) png_ok = True @@ -580,7 +583,7 @@ class ThumbSrv(object): self._run_ff(cmd, vn) def _run_ff(self, cmd: list[bytes], vn: VFS, oom: int = 400) -> None: - # self.log((b" ".join(cmd)).decode("utf-8")) + self.log((b" ".join(cmd)).decode("utf-8")) ret, _, serr = runcmd(cmd, timeout=vn.flags["convt"], nice=True, oom=oom) if not ret: return @@ -807,6 +810,42 @@ class ThumbSrv(object): # fmt: on 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 not self.args.q_wav: + raise Exception("disabled in server config") + + self.wait4ram(0.2, tpath) + tags, rawtags = ffprobe(abspath, int(vn.flags["convt"] / 2)) + if "ac" not in tags: + raise Exception("not audio") + + bits = tags[".bps"][1] + if bits == 0.0: + bits = tags[".bprs"][1] + + codec = b"pcm_s32le" + if bits == 16.0: + codec = b"pcm_s16le" + elif bits == 24.0: + codec = b"pcm_s24le" + + self.log("conv2 wav-tmp", 6) + + # fmt: off + cmd = [ + b"ffmpeg", + b"-nostdin", + b"-v", b"error", + b"-hide_banner", + b"-i", fsenc(abspath), + b"-map", b"0:a:0", + b"-c:a", codec, + fsenc(tpath) + ] + # fmt: on + print(cmd) + self._run_ff(cmd, vn, oom=300) + def conv_opus(self, abspath: str, tpath: str, fmt: str, vn: VFS) -> None: if self.args.no_acode or not self.args.q_opus: raise Exception("disabled in server config") diff --git a/copyparty/web/browser.js b/copyparty/web/browser.js index 5340d5bb..aaa00599 100644 --- a/copyparty/web/browser.js +++ b/copyparty/web/browser.js @@ -306,6 +306,7 @@ 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_c2wav": "use this for uncompressed playback\">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", @@ -5548,6 +5549,7 @@ var mpl = (function () { '