ffmpeg bwrap sandbox

This commit is contained in:
ed 2026-06-13 21:26:09 +00:00
parent 90639de984
commit efa43f891b
8 changed files with 158 additions and 70 deletions

View file

@ -28,6 +28,7 @@ from .__init__ import (
MACOS, MACOS,
PY2, PY2,
PY36, PY36,
UNIX,
VT100, VT100,
WINDOWS, WINDOWS,
E, E,
@ -45,6 +46,7 @@ from .util import (
DEF_EXP, DEF_EXP,
DEF_MTE, DEF_MTE,
DEF_MTH, DEF_MTH,
HAVE_BWRAP,
HAVE_IPV6, HAVE_IPV6,
IMPLICATIONS, IMPLICATIONS,
JINJA_VER, JINJA_VER,
@ -1643,6 +1645,29 @@ def add_optouts(ap):
def add_safety(ap): def add_safety(ap):
th_bwrap = ""
if HAVE_BWRAP:
zsl = [
"bwrap",
"--proc /proc",
"--tmpfs /tmp",
"--tmpfs /var",
"--tmpfs /run",
"--dev-bind /dev/null /dev/null",
"--dev-bind /dev/random /dev/random",
"--dev-bind /dev/urandom /dev/urandom",
"--chdir /tmp",
"--clearenv",
"--unshare-all",
"--cap-drop ALL",
"--die-with-parent",
"--new-session",
]
for d in ("/lib", "/lib64", "/usr/lib", "/usr/lib64"):
if os.path.isdir(d):
zsl.append(" --ro-bind %s %s" % (d, d))
th_bwrap = " ".join(zsl)
ap2 = ap.add_argument_group("safety options") ap2 = ap.add_argument_group("safety options")
ap2.add_argument("-s", action="count", default=0, help="increase safety: Disable thumbnails / potentially dangerous software (ffmpeg/pillow/vips), hide partial uploads, avoid crawlers.\n └─Alias of\033[32m --dotpart --no-thumb --no-mtag-ff --no-robots --force-js") ap2.add_argument("-s", action="count", default=0, help="increase safety: Disable thumbnails / potentially dangerous software (ffmpeg/pillow/vips), hide partial uploads, avoid crawlers.\n └─Alias of\033[32m --dotpart --no-thumb --no-mtag-ff --no-robots --force-js")
ap2.add_argument("-ss", action="store_true", help="further increase safety: Prevent js-injection, accidental move/delete, broken symlinks, webdav requires login, 404 on 403, ban on excessive 404s.\n └─Alias of\033[32m -s --no-html --no-readme --no-logues --unpost=0 --no-del --no-mv --reflink --dav-auth --vague-403 -nih") ap2.add_argument("-ss", action="store_true", help="further increase safety: Prevent js-injection, accidental move/delete, broken symlinks, webdav requires login, 404 on 403, ban on excessive 404s.\n └─Alias of\033[32m -s --no-html --no-readme --no-logues --unpost=0 --no-del --no-mv --reflink --dav-auth --vague-403 -nih")
@ -1679,6 +1704,8 @@ def add_safety(ap):
ap2.add_argument("--loris", metavar="B", type=int, default=60, help="if a client maxes out the server connection limit without sending headers, ban it for \033[33mB\033[0m minutes; disable with [\033[32m0\033[0m]") ap2.add_argument("--loris", metavar="B", type=int, default=60, help="if a client maxes out the server connection limit without sending headers, ban it for \033[33mB\033[0m minutes; disable with [\033[32m0\033[0m]")
ap2.add_argument("--acao", metavar="V[,V]", type=u, default="*", help="Access-Control-Allow-Origin; list of origins (domains/IPs without port) to accept requests from; [\033[32mhttps://1.2.3.4\033[0m]. Default [\033[32m*\033[0m] allows requests from all sites but removes cookies and http-auth; only ?pw=hunter2 survives") ap2.add_argument("--acao", metavar="V[,V]", type=u, default="*", help="Access-Control-Allow-Origin; list of origins (domains/IPs without port) to accept requests from; [\033[32mhttps://1.2.3.4\033[0m]. Default [\033[32m*\033[0m] allows requests from all sites but removes cookies and http-auth; only ?pw=hunter2 survives")
ap2.add_argument("--acam", metavar="V[,V]", type=u, default="GET,HEAD", help="Access-Control-Allow-Methods; list of methods to accept from offsite ('*' behaves like \033[33m--acao\033[0m's description)") ap2.add_argument("--acam", metavar="V[,V]", type=u, default="GET,HEAD", help="Access-Control-Allow-Methods; list of methods to accept from offsite ('*' behaves like \033[33m--acao\033[0m's description)")
if not ANYWIN and not UNIX:
ap2.add_argument("--th-bwrap", metavar="CMD", type=u, default=th_bwrap, help="optional bwrap sandbox command for FFmpeg and dcraw (Linux-only)")
def add_salt(ap, fk_salt, dk_salt, ah_salt): def add_salt(ap, fk_salt, dk_salt, ah_salt):

View file

@ -74,6 +74,7 @@ def have_ff(name: str) -> bytes:
HAVE_FFMPEG = have_ff("ffmpeg") HAVE_FFMPEG = have_ff("ffmpeg")
HAVE_FFPROBE = have_ff("ffprobe") HAVE_FFPROBE = have_ff("ffprobe")
TH_BWRAP = []
CBZ_PICS = set("png jpg jpeg gif bmp tga tif tiff webp avif jxl".split()) CBZ_PICS = set("png jpg jpeg gif bmp tga tif tiff webp avif jxl".split())
CBZ_01 = re.compile(r"(^|[^0-9v])0+[01]\b") CBZ_01 = re.compile(r"(^|[^0-9v])0+[01]\b")
@ -224,17 +225,28 @@ def au_unpk(
return abspath return abspath
def bwrap(prog: bytes, ap_in: bytes, ap_out: bytes) -> list[bytes]:
if not TH_BWRAP:
return [prog]
ret = TH_BWRAP + [b"--ro-bind", prog, prog, b"--ro-bind", ap_in, ap_in]
if ap_out:
zs = ap_out.rsplit(b"/", 1)[0]
ret += [b"--bind", zs, zs]
ret.append(prog)
return ret
def ffprobe( def ffprobe(
abspath: str, timeout: int = 60 abspath: str, timeout: int = 60
) -> tuple[dict[str, tuple[int, Any]], dict[str, list[Any]], list[Any], dict[str, Any]]: ) -> tuple[dict[str, tuple[int, Any]], dict[str, list[Any]], list[Any], dict[str, Any]]:
# ffprobe -hide_banner -show_streams -show_format -- # ffprobe -hide_banner -show_streams -show_format --
cmd = [ bap = fsenc(abspath)
HAVE_FFPROBE, cmd = bwrap(HAVE_FFPROBE, bap, b"") + [
b"-hide_banner", b"-hide_banner",
b"-show_streams", b"-show_streams",
b"-show_format", b"-show_format",
b"--", b"--",
fsenc(abspath), bap,
] ]
rc, so, se = runcmd(cmd, timeout=timeout, nice=True, oom=200) rc, so, se = runcmd(cmd, timeout=timeout, nice=True, oom=200)
retchk(rc, cmd, se) retchk(rc, cmd, se)

View file

@ -27,13 +27,23 @@ if True: # pylint: disable=using-constant-test
import typing import typing
from typing import Any, Optional, Union from typing import Any, Optional, Union
from .__init__ import ANYWIN, EXE, MACOS, PY2, TYPE_CHECKING, E, EnvParams, unicode from .__init__ import (
ANYWIN,
EXE,
MACOS,
PY2,
TYPE_CHECKING,
UNIX,
E,
EnvParams,
unicode,
)
from .__version__ import S_VERSION, VERSION from .__version__ import S_VERSION, VERSION
from .authsrv import BAD_CFG, AuthSrv, derive_args, n_du_who, n_ver_who from .authsrv import BAD_CFG, AuthSrv, derive_args, n_du_who, n_ver_who
from .bos import bos from .bos import bos
from .cert import ensure_cert from .cert import ensure_cert
from .fsutil import ramdisk_chk from .fsutil import ramdisk_chk
from .mtag import HAVE_FFMPEG, HAVE_FFPROBE, HAVE_MUTAGEN from .mtag import HAVE_FFMPEG, HAVE_FFPROBE, HAVE_MUTAGEN, TH_BWRAP
from .pwhash import HAVE_ARGON2 from .pwhash import HAVE_ARGON2
from .sutil import close_pools as sutil_close_pools from .sutil import close_pools as sutil_close_pools
from .tcpsrv import TcpSrv from .tcpsrv import TcpSrv
@ -42,8 +52,6 @@ from .th_srv import (
H_PIL_HEIF, H_PIL_HEIF,
H_PIL_WEBP, H_PIL_WEBP,
HAVE_DCRAW, HAVE_DCRAW,
HAVE_FFMPEG,
HAVE_FFPROBE,
HAVE_PIL, HAVE_PIL,
HAVE_RAWPY, HAVE_RAWPY,
HAVE_VIPS, HAVE_VIPS,
@ -56,6 +64,7 @@ from .util import (
DEF_MTE, DEF_MTE,
DEF_MTH, DEF_MTH,
FFMPEG_URL, FFMPEG_URL,
HAVE_BWRAP,
HAVE_PSUTIL, HAVE_PSUTIL,
HAVE_SQLITE3, HAVE_SQLITE3,
HAVE_ZMQ, HAVE_ZMQ,
@ -1013,6 +1022,8 @@ class SvcHub(object):
fok = [] fok = []
fng = [] fng = []
t_ff = "transcode audio, create spectrograms, video thumbnails" t_ff = "transcode audio, create spectrograms, video thumbnails"
# fmt: off
to_check = [ to_check = [
(HAVE_SQLITE3, "sqlite", "sessions and file/media indexing"), (HAVE_SQLITE3, "sqlite", "sessions and file/media indexing"),
(HAVE_PIL, "pillow", "image thumbnails (plenty fast)"), (HAVE_PIL, "pillow", "image thumbnails (plenty fast)"),
@ -1020,6 +1031,7 @@ class SvcHub(object):
(H_PIL_WEBP, "pillow-webp", "create thumbnails as webp files"), (H_PIL_WEBP, "pillow-webp", "create thumbnails as webp files"),
(HAVE_FFMPEG, "ffmpeg", t_ff + ", good-but-slow image thumbnails"), (HAVE_FFMPEG, "ffmpeg", t_ff + ", good-but-slow image thumbnails"),
(HAVE_FFPROBE, "ffprobe", t_ff + ", read audio/media tags"), (HAVE_FFPROBE, "ffprobe", t_ff + ", read audio/media tags"),
(HAVE_BWRAP, "bwrap", "sandbox to make ffmpeg less dangerous", not ANYWIN and not UNIX),
(HAVE_MUTAGEN, "mutagen", "read audio tags (ffprobe is better but slower)"), (HAVE_MUTAGEN, "mutagen", "read audio tags (ffprobe is better but slower)"),
(HAVE_ARGON2, "argon2", "secure password hashing (advanced users only)"), (HAVE_ARGON2, "argon2", "secure password hashing (advanced users only)"),
(HAVE_ZMQ, "pyzmq", "send zeromq messages from event-hooks"), (HAVE_ZMQ, "pyzmq", "send zeromq messages from event-hooks"),
@ -1027,17 +1039,21 @@ class SvcHub(object):
(H_PIL_AVIF, "pillow-avif", "read .avif pics with pillow (rarely useful)"), (H_PIL_AVIF, "pillow-avif", "read .avif pics with pillow (rarely useful)"),
(HAVE_RAWPY, "rawpy", "read RAW images"), (HAVE_RAWPY, "rawpy", "read RAW images"),
(HAVE_DCRAW, "libraw", "read RAW images"), (HAVE_DCRAW, "libraw", "read RAW images"),
(HAVE_PSUTIL, "psutil", "improved plugin cleanup (rarely useful)", ANYWIN),
] ]
if ANYWIN: # fmt: on
to_check += [
(HAVE_PSUTIL, "psutil", "improved plugin cleanup (rarely useful)")
]
verbose = self.args.deps verbose = self.args.deps
if verbose: if verbose:
self.log("dependencies", "") self.log("dependencies", "")
for have, feat, what in to_check: for zc in to_check:
try:
have, feat, what = zc
except:
have, feat, what, zb = zc
if not zb:
continue
lst = fok if have else fng lst = fok if have else fng
lst.append((feat, what)) lst.append((feat, what))
if verbose: if verbose:
@ -1204,6 +1220,15 @@ class SvcHub(object):
vsa = [x.upper() for x in vsa if x] vsa = [x.upper() for x in vsa if x]
setattr(al, k + "_set", set(vsa)) setattr(al, k + "_set", set(vsa))
zs = "th_bwrap"
for k in zs.split(" "):
zsl = [x for x in str(getattr(al, k)).split(" ") if x]
zbl = [x.encode("ascii", "replace") for x in zsl]
setattr(al, k + "_s", zsl)
setattr(al, k + "_b", zbl)
TH_BWRAP[:] = al.th_bwrap_b
zs = "dav_ua1 lf_url sus_urls nonsus_urls ua_nodav ua_nodoc ua_nozip" zs = "dav_ua1 lf_url sus_urls nonsus_urls ua_nodav ua_nodoc ua_nozip"
for k in zs.split(" "): for k in zs.split(" "):
vs = getattr(al, k) vs = getattr(al, k)

View file

@ -18,7 +18,7 @@ from queue import Queue
from .__init__ import ANYWIN, PY2, TYPE_CHECKING, unicode from .__init__ import ANYWIN, PY2, TYPE_CHECKING, unicode
from .authsrv import VFS from .authsrv import VFS
from .bos import bos from .bos import bos
from .mtag import HAVE_FFMPEG, HAVE_FFPROBE, au_unpk, ffprobe, have_ff from .mtag import HAVE_FFMPEG, HAVE_FFPROBE, au_unpk, bwrap, ffprobe, have_ff
from .util import BytesIO # type: ignore from .util import BytesIO # type: ignore
from .util import ( from .util import (
FFMPEG_URL, FFMPEG_URL,
@ -763,14 +763,14 @@ class ThumbSrv(object):
def _conv_dcraw(self, abspath: str, tpath: str, fmt: str, vn: VFS) -> None: def _conv_dcraw(self, abspath: str, tpath: str, fmt: str, vn: VFS) -> None:
self.wait4ram(0.6, tpath) self.wait4ram(0.6, tpath)
bap = fsenc(abspath)
# fmt: off # fmt: off
cmd = [ cmd = bwrap(HAVE_DCRAW, bap, b"") + [
b"dcraw_emu",
b"-h", # halfsize b"-h", # halfsize
b"-o", b"1", # srgb b"-o", b"1", # srgb
b"-s", b"0", # first frame b"-s", b"0", # first frame
b"-Z", b"-", # to stdout b"-Z", b"-", # to stdout
fsenc(abspath), bap,
] ]
# fmt: on # fmt: on
p = sp.Popen(cmd, stdout=sp.PIPE) p = sp.Popen(cmd, stdout=sp.PIPE)
@ -858,16 +858,15 @@ class ThumbSrv(object):
res = self.getres(vn, fmt) res = self.getres(vn, fmt)
bscale = scale.format(*list(res)).encode("utf-8") bscale = scale.format(*list(res)).encode("utf-8")
bap_in = fsenc(abspath)
bap_out = fsenc(tpath)
# fmt: off # fmt: off
cmd = [ cmd = bwrap(HAVE_FFMPEG, bap_in, bap_out) + [
HAVE_FFMPEG,
b"-nostdin", b"-nostdin",
b"-v", b"error", b"-v", b"error",
b"-hide_banner" b"-hide_banner"
] ] + seek + [
cmd += seek b"-i", bap_in,
cmd += [
b"-i", fsenc(abspath),
b"-map", imap, b"-map", imap,
b"-vf", bscale, b"-vf", bscale,
b"-frames:v", b"1", b"-frames:v", b"1",
@ -875,15 +874,15 @@ class ThumbSrv(object):
] ]
# fmt: on # fmt: on
self._ffmpeg_im_o(tpath, vn, cmd) self._ffmpeg_im_o(bap_out, vn, cmd)
def _ffmpeg_im_o(self, tpath: str, vn: VFS, cmd: list[bytes]) -> None: def _ffmpeg_im_o(self, tpath: bytes, vn: VFS, cmd: list[bytes]) -> None:
if tpath.endswith(".jpg"): if tpath.endswith(b".jpg"):
cmd += [ cmd += [
b"-q:v", b"-q:v",
FF_JPG_Q[vn.flags["th_qv"] // 5], # default=?? FF_JPG_Q[vn.flags["th_qv"] // 5], # default=??
] ]
elif tpath.endswith(".jxl"): elif tpath.endswith(b".jxl"):
cmd += [ cmd += [
b"-q:v", b"-q:v",
unicode(vn.flags["th_qvx"]).encode("ascii"), # default=?? unicode(vn.flags["th_qvx"]).encode("ascii"), # default=??
@ -898,7 +897,7 @@ class ThumbSrv(object):
b"6", # default=4, 0=fast, 6=max b"6", # default=4, 0=fast, 6=max
] ]
cmd += [fsenc(tpath)] cmd.append(tpath)
self._run_ff(cmd, vn, "convt") self._run_ff(cmd, vn, "convt")
def _run_ff(self, cmd: list[bytes], vn: VFS, kto: str, oom: int = 400) -> None: def _run_ff(self, cmd: list[bytes], vn: VFS, kto: str, oom: int = 400) -> None:
@ -998,20 +997,21 @@ class ThumbSrv(object):
b",showwavespic=s=2048x64:colors=white" b",showwavespic=s=2048x64:colors=white"
b",convolution=1 1 1 1 1 1 1 1 1:1 1 1 1 1 1 1 1 1:1 1 1 1 1 1 1 1 1:1 -1 1 -1 5 -1 1 -1 1" # idk what im doing but it looks ok b",convolution=1 1 1 1 1 1 1 1 1:1 1 1 1 1 1 1 1 1:1 1 1 1 1 1 1 1 1:1 -1 1 -1 5 -1 1 -1 1" # idk what im doing but it looks ok
) )
bap_in = fsenc(abspath)
bap_out = fsenc(tpath)
# fmt: off # fmt: off
cmd = [ cmd = bwrap(HAVE_FFMPEG, bap_in, bap_out) + [
HAVE_FFMPEG,
b"-nostdin", b"-nostdin",
b"-v", b"error", b"-v", b"error",
b"-hide_banner", b"-hide_banner",
b"-i", fsenc(abspath), b"-i", bap_in,
b"-filter_complex", flt, b"-filter_complex", flt,
b"-frames:v", b"1", b"-frames:v", b"1",
] ]
# fmt: on # fmt: on
cmd += [fsenc(tpath)] cmd.append(bap_out)
self._run_ff(cmd, vn, "convt") self._run_ff(cmd, vn, "convt")
if "pngquant" in vn.flags: if "pngquant" in vn.flags:
@ -1078,19 +1078,21 @@ class ThumbSrv(object):
except: except:
self.untemp[tpath] = [infile] self.untemp[tpath] = [infile]
bap_in = fsenc(abspath)
bap_out = fsenc(infile)
# fmt: off # fmt: off
cmd = [ cmd = bwrap(HAVE_FFMPEG, bap_in, bap_out) + [
HAVE_FFMPEG,
b"-nostdin", b"-nostdin",
b"-v", b"error", b"-v", b"error",
b"-hide_banner", b"-hide_banner",
b"-i", fsenc(abspath), b"-i", bap_in,
b"-map", b"0:a:0", b"-map", b"0:a:0",
b"-ac", b"1", b"-ac", b"1",
b"-ar", b"48000", b"-ar", b"48000",
b"-sample_fmt", b"s16", b"-sample_fmt", b"s16",
b"-t", b"900", b"-t", b"900",
b"-y", fsenc(infile), b"-y", bap_out,
] ]
# fmt: on # fmt: on
self._run_ff(cmd, vn, "convt") self._run_ff(cmd, vn, "convt")
@ -1110,20 +1112,22 @@ class ThumbSrv(object):
fc = fc.format(fco) fc = fc.format(fco)
bap_in = fsenc(infile)
bap_out = fsenc(tpath)
# fmt: off # fmt: off
cmd = [ cmd = bwrap(HAVE_FFMPEG, bap_in, bap_out) + [
HAVE_FFMPEG,
b"-nostdin", b"-nostdin",
b"-v", b"error", b"-v", b"error",
b"-hide_banner", b"-hide_banner",
b"-i", fsenc(infile), b"-i", bap_in,
b"-filter_complex", fc.encode("utf-8"), b"-filter_complex", fc.encode("utf-8"),
b"-map", b"[o]", b"-map", b"[o]",
b"-frames:v", b"1", b"-frames:v", b"1",
] ]
# fmt: on # fmt: on
self._ffmpeg_im_o(tpath, vn, cmd) self._ffmpeg_im_o(bap_out, vn, cmd)
def conv_mp3(self, abspath: str, tpath: str, fmt: str, vn: VFS) -> None: def conv_mp3(self, abspath: str, tpath: str, fmt: str, vn: VFS) -> None:
quality = self.args.q_mp3.lower() quality = self.args.q_mp3.lower()
@ -1142,24 +1146,26 @@ class ThumbSrv(object):
qk = b"-q:a" qk = b"-q:a"
qv = quality[1:].encode("ascii") qv = quality[1:].encode("ascii")
bap_in = fsenc(abspath)
bap_out = fsenc(tpath)
# extremely conservative choices for output format # extremely conservative choices for output format
# (always 2ch 44k1) because if a device is old enough # (always 2ch 44k1) because if a device is old enough
# to not support opus then it's probably also super picky # to not support opus then it's probably also super picky
# fmt: off # fmt: off
cmd = [ cmd = bwrap(HAVE_FFMPEG, bap_in, bap_out) + [
HAVE_FFMPEG,
b"-nostdin", b"-nostdin",
b"-v", b"error", b"-v", b"error",
b"-hide_banner", b"-hide_banner",
b"-i", fsenc(abspath), b"-i", bap_in,
] + self.big_tags(rawtags) + [ ] + self.big_tags(rawtags) + [
b"-map", b"0:a:0", b"-map", b"0:a:0",
b"-ar", b"44100", b"-ar", b"44100",
b"-ac", b"2", b"-ac", b"2",
b"-c:a", b"libmp3lame", b"-c:a", b"libmp3lame",
qk, qv, qk, qv,
fsenc(tpath) bap_out,
] ]
# fmt: on # fmt: on
self._run_ff(cmd, vn, "aconvt", oom=300) self._run_ff(cmd, vn, "aconvt", oom=300)
@ -1175,16 +1181,18 @@ class ThumbSrv(object):
self.log("conv2 flac", 6) self.log("conv2 flac", 6)
bap_in = fsenc(abspath)
bap_out = fsenc(tpath)
# fmt: off # fmt: off
cmd = [ cmd = bwrap(HAVE_FFMPEG, bap_in, bap_out) + [
HAVE_FFMPEG,
b"-nostdin", b"-nostdin",
b"-v", b"error", b"-v", b"error",
b"-hide_banner", b"-hide_banner",
b"-i", fsenc(abspath), b"-i", bap_in,
b"-map", b"0:a:0", b"-map", b"0:a:0",
b"-c:a", b"flac", b"-c:a", b"flac",
fsenc(tpath) bap_out,
] ]
# fmt: on # fmt: on
self._run_ff(cmd, vn, "aconvt", oom=300) self._run_ff(cmd, vn, "aconvt", oom=300)
@ -1210,16 +1218,18 @@ class ThumbSrv(object):
self.log("conv2 wav", 6) self.log("conv2 wav", 6)
bap_in = fsenc(abspath)
bap_out = fsenc(tpath)
# fmt: off # fmt: off
cmd = [ cmd = bwrap(HAVE_FFMPEG, bap_in, bap_out) + [
HAVE_FFMPEG,
b"-nostdin", b"-nostdin",
b"-v", b"error", b"-v", b"error",
b"-hide_banner", b"-hide_banner",
b"-i", fsenc(abspath), b"-i", bap_in,
b"-map", b"0:a:0", b"-map", b"0:a:0",
b"-c:a", codec, b"-c:a", codec,
fsenc(tpath) bap_out,
] ]
# fmt: on # fmt: on
self._run_ff(cmd, vn, "aconvt", oom=300) self._run_ff(cmd, vn, "aconvt", oom=300)
@ -1271,19 +1281,21 @@ class ThumbSrv(object):
except: except:
pass pass
bap_in = fsenc(abspath)
bap_out = fsenc(tpath)
# fmt: off # fmt: off
cmd = [ cmd = bwrap(HAVE_FFMPEG, bap_in, bap_out) + [
HAVE_FFMPEG,
b"-nostdin", b"-nostdin",
b"-v", b"error", b"-v", b"error",
b"-hide_banner", b"-hide_banner",
b"-i", fsenc(abspath), b"-i", bap_in,
] + tagset + [ ] + tagset + [
b"-map", b"0:a:0", b"-map", b"0:a:0",
b"-ac", ac, b"-ac", ac,
] + benc + [ ] + benc + [
b"-f", container, b"-f", container,
fsenc(tpath) bap_out,
] ]
# fmt: on # fmt: on
self._run_ff(cmd, vn, "aconvt", oom=300) self._run_ff(cmd, vn, "aconvt", oom=300)
@ -1312,19 +1324,21 @@ class ThumbSrv(object):
self.log("conv2 caf-tmp [%s]" % (enc,), 6) self.log("conv2 caf-tmp [%s]" % (enc,), 6)
benc = enc.encode("ascii").split(b" ") benc = enc.encode("ascii").split(b" ")
bap_in = fsenc(abspath)
bap_out = fsenc(tmp_opus)
# fmt: off # fmt: off
cmd = [ cmd = bwrap(HAVE_FFMPEG, bap_in, bap_out) + [
HAVE_FFMPEG,
b"-nostdin", b"-nostdin",
b"-v", b"error", b"-v", b"error",
b"-hide_banner", b"-hide_banner",
b"-i", fsenc(abspath), b"-i", bap_in,
b"-map_metadata", b"-1", b"-map_metadata", b"-1",
b"-map", b"0:a:0", b"-map", b"0:a:0",
b"-ac", b"2", b"-ac", b"2",
] + benc + [ ] + benc + [
b"-f", b"opus", b"-f", b"opus",
fsenc(tmp_opus) bap_out,
] ]
# fmt: on # fmt: on
self._run_ff(cmd, vn, "aconvt", oom=300) self._run_ff(cmd, vn, "aconvt", oom=300)
@ -1338,20 +1352,21 @@ class ThumbSrv(object):
if dur < 20 or sz < 256 * 1024: if dur < 20 or sz < 256 * 1024:
zs = bq.decode("ascii") zs = bq.decode("ascii")
self.log("conv2 caf-transcode; dur=%d sz=%d q=%s" % (dur, sz, zs), 6) self.log("conv2 caf-transcode; dur=%d sz=%d q=%s" % (dur, sz, zs), 6)
bap_in = fsenc(abspath)
bap_out = fsenc(tpath)
# fmt: off # fmt: off
cmd = [ cmd = bwrap(HAVE_FFMPEG, bap_in, bap_out) + [
HAVE_FFMPEG,
b"-nostdin", b"-nostdin",
b"-v", b"error", b"-v", b"error",
b"-hide_banner", b"-hide_banner",
b"-i", fsenc(abspath), b"-i", bap_in,
b"-filter_complex", b"anoisesrc=a=0.001:d=7:c=pink,asplit[l][r]; [l][r]amerge[s]; [0:a:0][s]amix", b"-filter_complex", b"anoisesrc=a=0.001:d=7:c=pink,asplit[l][r]; [l][r]amerge[s]; [0:a:0][s]amix",
b"-map_metadata", b"-1", b"-map_metadata", b"-1",
b"-ac", b"2", b"-ac", b"2",
b"-c:a", b"libopus", b"-c:a", b"libopus",
b"-b:a", bq, b"-b:a", bq,
b"-f", b"caf", b"-f", b"caf",
fsenc(tpath) bap_out,
] ]
# fmt: on # fmt: on
self._run_ff(cmd, vn, "aconvt", oom=300) self._run_ff(cmd, vn, "aconvt", oom=300)
@ -1359,18 +1374,19 @@ class ThumbSrv(object):
else: else:
# simple remux should be safe # simple remux should be safe
self.log("conv2 caf-remux; dur=%d sz=%d" % (dur, sz), 6) self.log("conv2 caf-remux; dur=%d sz=%d" % (dur, sz), 6)
bap_in = fsenc(tmp_opus)
bap_out = fsenc(tpath)
# fmt: off # fmt: off
cmd = [ cmd = bwrap(HAVE_FFMPEG, bap_in, bap_out) + [
HAVE_FFMPEG,
b"-nostdin", b"-nostdin",
b"-v", b"error", b"-v", b"error",
b"-hide_banner", b"-hide_banner",
b"-i", fsenc(tmp_opus), b"-i", bap_in,
b"-map_metadata", b"-1", b"-map_metadata", b"-1",
b"-map", b"0:a:0", b"-map", b"0:a:0",
b"-c:a", b"copy", b"-c:a", b"copy",
b"-f", b"caf", b"-f", b"caf",
fsenc(tpath) bap_out,
] ]
# fmt: on # fmt: on
self._run_ff(cmd, vn, "aconvt", oom=300) self._run_ff(cmd, vn, "aconvt", oom=300)

View file

@ -657,6 +657,14 @@ if EXE:
pass pass
try:
if PY2 or ANYWIN:
raise Exception()
HAVE_BWRAP = shutil.which("bwrap")
except:
HAVE_BWRAP = ""
def py_desc() -> str: def py_desc() -> str:
interp = platform.python_implementation() interp = platform.python_implementation()
py_ver = ".".join([str(x) for x in sys.version_info]) py_ver = ".".join([str(x) for x in sys.version_info])

View file

@ -9,7 +9,7 @@ ENV XDG_CONFIG_HOME=/cfg
ARG ADD_PKG="" ARG ADD_PKG=""
RUN apk --no-cache add !pyc ${ADD_PKG} \ RUN apk --no-cache add !pyc ${ADD_PKG} \
tzdata wget mimalloc2 mimalloc2-insecure \ tzdata wget mimalloc2 mimalloc2-insecure bubblewrap \
py3-jinja2 py3-argon2-cffi py3-pyzmq \ py3-jinja2 py3-argon2-cffi py3-pyzmq \
py3-openssl py3-paramiko py3-pillow py3-openssl py3-paramiko py3-pillow

View file

@ -12,7 +12,7 @@ COPY i/bin/mtag/install-deps.sh ./
COPY i/bin/mtag/audio-bpm.py /mtag/ COPY i/bin/mtag/audio-bpm.py /mtag/
COPY i/bin/mtag/audio-key.py /mtag/ COPY i/bin/mtag/audio-key.py /mtag/
RUN apk add -U !pyc ${ADD_PKG} \ RUN apk add -U !pyc ${ADD_PKG} \
tzdata wget mimalloc2 mimalloc2-insecure \ tzdata wget mimalloc2 mimalloc2-insecure bubblewrap \
py3-jinja2 py3-argon2-cffi py3-pyzmq \ py3-jinja2 py3-argon2-cffi py3-pyzmq \
py3-openssl py3-paramiko py3-pillow \ py3-openssl py3-paramiko py3-pillow \
py3-pip \ py3-pip \

View file

@ -9,7 +9,7 @@ ENV XDG_CONFIG_HOME=/cfg
ARG ADD_PKG="" ARG ADD_PKG=""
RUN apk add -U !pyc ${ADD_PKG} \ RUN apk add -U !pyc ${ADD_PKG} \
tzdata wget mimalloc2 mimalloc2-insecure \ tzdata wget mimalloc2 mimalloc2-insecure bubblewrap \
py3-jinja2 py3-argon2-cffi py3-pyzmq \ py3-jinja2 py3-argon2-cffi py3-pyzmq \
py3-openssl py3-paramiko py3-pillow \ py3-openssl py3-paramiko py3-pillow \
py3-pip \ py3-pip \