From 28c9de3f6a313ad7b0414488196b58c60988058f Mon Sep 17 00:00:00 2001 From: ed Date: Sat, 25 Jan 2025 10:15:44 +0000 Subject: [PATCH] add opus-weba transcoding (for iOS 18 and newer) support for "owa", audio-only webm, was introduced in iOS 17.5 owa is a more compliant alternative to opus-caf from iOS 11, which was technically limited to CBR opus, a limitation which we ignored since it worked mostly fine for regular opus too being the new officially-recommended way to do things, we'll default to owa for iOS 18 and later, even though iOS still has some bugs affecting our use specifically: if a weba file is preloaded into a 2nd audio object, safari will throw a spurious exception as playback is initiated, even as the file is playing just fine the `.ld` stuff is an attempt at catching and ignoring this spurious error without eating any actual network exceptions --- README.md | 9 ++++ copyparty/__main__.py | 3 +- copyparty/th_cli.py | 2 + copyparty/th_srv.py | 16 ++++-- copyparty/util.py | 1 + copyparty/web/browser.js | 112 ++++++++++++++++++++++++++++++++++++--- 6 files changed, 132 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 95de4f74..aa3d6273 100644 --- a/README.md +++ b/README.md @@ -357,6 +357,10 @@ same order here too * just a hunch, but disabling preloading may cause playback to stop entirely, or possibly mess with bluetooth speakers * tried to add a tooltip regarding this but looks like apple broke my tooltips +* iPhones: preloaded awo files make safari log MEDIA_ERR_NETWORK errors as playback starts, but the song plays just fine so eh whatever + + * awo, opus-weba, is apple's new take on opus support, replacing opus-caf which was technically limited to cbr opus + * Windows: folders cannot be accessed if the name ends with `.` * python or windows bug @@ -927,6 +931,11 @@ open the `[🎺]` media-player-settings tab to configure it, * `[aac]` converts `aac` and `m4a` files into opus (if supported by browser) or mp3 * `[oth]` converts all other known formats into opus (if supported by browser) or mp3 * `aac|ac3|aif|aiff|alac|alaw|amr|ape|au|dfpwm|dts|flac|gsm|it|m4a|mo3|mod|mp2|mp3|mpc|mptm|mt2|mulaw|ogg|okt|opus|ra|s3m|tak|tta|ulaw|wav|wma|wv|xm|xpk` +* "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 + * `[mp3]` -- the myth, the legend, the undying master of mediocre sound quality that definitely works everywhere * "tint" reduces the contrast of the playback bar diff --git a/copyparty/__main__.py b/copyparty/__main__.py index 8451ca7b..6c2d3fe3 100644 --- a/copyparty/__main__.py +++ b/copyparty/__main__.py @@ -1373,7 +1373,8 @@ 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("--no-caf", action="store_true", help="disable transcoding to caf-opus (iOS v12 and later), use mp3 instead") + 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-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/th_cli.py b/copyparty/th_cli.py index 7dafea5f..45017ee2 100644 --- a/copyparty/th_cli.py +++ b/copyparty/th_cli.py @@ -66,6 +66,8 @@ class ThumbCli(object): return None elif fmt == "caf" and self.args.no_caf: fmt = "mp3" + elif fmt == "owa" and self.args.no_owa: + fmt = "mp3" else: if "dathumb" in dbv.flags: return None diff --git a/copyparty/th_srv.py b/copyparty/th_srv.py index a3d9d660..04ac0d14 100644 --- a/copyparty/th_srv.py +++ b/copyparty/th_srv.py @@ -47,7 +47,7 @@ HAVE_AVIF = False HAVE_WEBP = False EXTS_TH = set(["jpg", "webp", "png"]) -EXTS_AC = set(["opus", "caf", "mp3"]) +EXTS_AC = set(["opus", "owa", "caf", "mp3"]) try: if os.environ.get("PRTY_NO_PIL"): @@ -339,7 +339,7 @@ class ThumbSrv(object): if not bos.path.exists(tpath): tex = tpath.rsplit(".", 1)[-1] want_mp3 = tex == "mp3" - want_opus = tex in ("opus", "caf") + want_opus = tex in ("opus", "owa", "caf") want_png = tex == "png" want_au = want_mp3 or want_opus for lib in self.args.th_dec: @@ -765,6 +765,8 @@ class ThumbSrv(object): src_opus = abspath.lower().endswith(".opus") or tags["ac"][1] == "opus" want_caf = tpath.endswith(".caf") + want_owa = tpath.endswith(".owa") + tmp_opus = tpath if want_caf: tmp_opus = tpath + ".opus" @@ -777,6 +779,13 @@ class ThumbSrv(object): bq = ("%dk" % (self.args.q_opus,)).encode("ascii") if not want_caf or not src_opus: + if want_owa: + container = b"webm" + tagset = [b"-map_metadata", b"-1"] + else: + container = b"opus" + tagset = self.big_tags(rawtags) + # fmt: off cmd = [ b"ffmpeg", @@ -784,10 +793,11 @@ class ThumbSrv(object): b"-v", b"error", b"-hide_banner", b"-i", fsenc(abspath), - ] + self.big_tags(rawtags) + [ + ] + tagset + [ b"-map", b"0:a:0", b"-c:a", b"libopus", b"-b:a", bq, + b"-f", container, fsenc(tmp_opus) ] # fmt: on diff --git a/copyparty/util.py b/copyparty/util.py index b416a8b3..a7d0fbbe 100644 --- a/copyparty/util.py +++ b/copyparty/util.py @@ -330,6 +330,7 @@ DAV_ALLPROPS = set(DAV_ALLPROP_L) MIMES = { "opus": "audio/ogg; codecs=opus", + "owa": "audio/webm; codecs=opus", } diff --git a/copyparty/web/browser.js b/copyparty/web/browser.js index 1486fc99..9f1bef1a 100644 --- a/copyparty/web/browser.js +++ b/copyparty/web/browser.js @@ -263,6 +263,7 @@ var Ls = { "ml_pmode": "at end of folder...", "ml_btns": "cmds", "ml_tcode": "transcode", + "ml_tcode2": "transcode to", "ml_tint": "tint", "ml_eq": "audio equalizer", "ml_drc": "dynamic range compressor", @@ -286,6 +287,13 @@ var Ls = { "mt_cflac": "convert flac / wav to opus\">flac", "mt_caac": "convert aac / m4a to opus\">aac", "mt_coth": "convert all others (not mp3) to opus\">oth", + "mt_c2opus": "best choice for desktops, laptops, android\">opus", + "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_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", "mt_tint": "background level (0-100) on the seekbar$Nto make buffering less distracting", "mt_eq": "enables the equalizer and gain control;$N$Nboost <code>0</code> = standard 100% volume (unmodified)$N$Nwidth <code>1  </code> = standard stereo (unmodified)$Nwidth <code>0.5</code> = 50% left-right crossfeed$Nwidth <code>0  </code> = mono$N$Nboost <code>-0.8</code> & width <code>10</code> = vocal removal :^)$N$Nenabling the equalizer makes gapless albums fully gapless, so leave it on with all the values at zero (except width = 1) if you care about that", "mt_drc": "enables the dynamic range compressor (volume flattener / brickwaller); will also enable EQ to balance the spaghetti, so set all EQ fields except for 'width' to 0 if you don't want it$N$Nlowers the volume of audio above THRESHOLD dB; for every RATIO dB past THRESHOLD there is 1 dB of output, so default values of tresh -24 and ratio 12 means it should never get louder than -22 dB and it is safe to increase the equalizer boost to 0.8, or even 1.8 with ATK 0 and a huge RLS like 90 (only works in firefox; RLS is max 1 in other browsers)$N$N(see wikipedia, they explain it much better)", @@ -852,6 +860,7 @@ var Ls = { "ml_pmode": "ved enden av mappen", "ml_btns": "knapper", "ml_tcode": "konvertering", + "ml_tcode2": "konverter til", "ml_tint": "tint", "ml_eq": "audio equalizer (tonejustering)", "ml_drc": "compressor (volum-utjevning)", @@ -875,6 +884,13 @@ var Ls = { "mt_cflac": "konverter flac / wav-filer til opus\">flac", "mt_caac": "konverter aac / m4a-filer til to opus\">aac", "mt_coth": "konverter alt annet (men ikke mp3) til opus\">andre", + "mt_c2opus": "det beste valget for alle PCer og Android\">opus", + "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_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", "mt_tint": "nivå av bakgrunnsfarge på søkestripa (0-100),$Ngjør oppdateringer mindre distraherende", "mt_eq": "aktiver tonekontroll og forsterker;$N$Nboost <code>0</code> = normal volumskala$N$Nwidth <code>1  </code> = normal stereo$Nwidth <code>0.5</code> = 50% blanding venstre-høyre$Nwidth <code>0  </code> = mono$N$Nboost <code>-0.8</code> & width <code>10</code> = instrumental :^)$N$Nreduserer også dødtid imellom sangfiler", "mt_drc": "aktiver volum-utjevning (dynamic range compressor); vil også aktivere tonejustering, så sett alle EQ-feltene bortsett fra 'width' til 0 hvis du ikke vil ha noe EQ$N$Nfilteret vil dempe volumet på alt som er høyere enn TRESH dB; for hver RATIO dB over grensen er det 1dB som treffer høyttalerne, så standardverdiene tresh -24 og ratio 12 skal bety at volumet ikke går høyere enn -22 dB, slik at man trygt kan øke boost-verdien i equalizer'n til rundt 0.8, eller 1.8 kombinert med ATK 0 og RLS 90 (bare mulig i firefox; andre nettlesere tar ikke høyere RLS enn 1)$N$Nwikipedia forklarer dette mye bedre forresten", @@ -1441,6 +1457,7 @@ var Ls = { "ml_pmode": "在文件夹末尾时...", "ml_btns": "命令", "ml_tcode": "转码", + "ml_tcode2": "转换为", //m "ml_tint": "透明度", "ml_eq": "音频均衡器", "ml_drc": "动态范围压缩器", @@ -1464,6 +1481,13 @@ var Ls = { "mt_cflac": "将 flac / wav 转换为 opus\">flac", "mt_caac": "将 aac / m4a 转换为 opus\">aac", "mt_coth": "将所有其他(不是 mp3)转换为 opus\">oth", + "mt_c2opus": "适合桌面电脑、笔记本电脑和安卓设备的最佳选择\">opus", //m + "mt_c2owa": "opus-weba(适用于 iOS 17.5 及更新版本)\">owa", //m + "mt_c2caf": "opus-caf(适用于 iOS 11 到 iOS 17)\">caf", //m + "mt_c2mp3": "适用于非常旧的设备\">mp3", //m + "mt_c2ok": "不错的选择!", //m + "mt_c2nd": "这不是您的设备推荐的输出格式,但应该没问题。", //m + "mt_c2ng": "您的设备似乎不支持此输出格式,不过我们还是试试看吧。", //m "mt_tint": "在进度条上设置背景级别(0-100)", "mt_eq": "启用均衡器和增益控制;$N$Nboost <code>0</code> = 标准 100% 音量(默认)$N$Nwidth <code>1  </code> = 标准立体声(默认)$Nwidth <code>0.5</code> = 50% 左右交叉反馈$Nwidth <code>0  </code> = 单声道$N$Nboost <code>-0.8</code> & width <code>10</code> = 人声移除 )$N$N启用均衡器使无缝专辑完全无缝,所以如果你在乎这一点,请保持启用,所有值设为零(除了宽度 = 1)", "mt_drc": "启用动态范围压缩器(音量平滑器 / 限幅器);还会启用均衡器以平衡音频,因此如果你不想要它,请将均衡器字段除了 '宽度' 外的所有字段设置为 0$N$N降低 THRESHOLD dB 以上的音频的音量;每超过 THRESHOLD dB 的 RATIO 会有 1 dB 输出,所以默认值 tresh -24 和 ratio 12 意味着它的音量不应超过 -22 dB,可以安全地将均衡器增益提高到 0.8,甚至在 ATK 0 和 RLS 如 90 的情况下提高到 1.8(仅在 Firefox 中有效;其他浏览器中 RLS 最大为 1)$N$N(见维基百科,他们解释得更好)", @@ -2269,6 +2293,12 @@ var mpl = (function () { ' 0.1) mp.au.currentTime = 0; } - else + else { + console.log('get ' + url.split('/').pop()); mp.au.src = mp.au.rsrc = url; + } mp.au.osrc = mp.tracks[tid]; afilt.apply(); @@ -4037,6 +4127,14 @@ function evau_error(e) { err = L.mm_eabrt; break; case eplaya.error.MEDIA_ERR_NETWORK: + if (IPHONE && eplaya.ld === 1 && mpl.ac2 == 'owa' && !eplaya.paused && !eplaya.currentTime) { + eplaya.ded = 0; + if (!mpl.owaw) { + mpl.owaw = 1; + console.log('ignored iOS bug; spurious error sent in parallel with preloaded songs starting to play just fine'); + } + return; + } err = L.mm_enet; break; case eplaya.error.MEDIA_ERR_DECODE: