diff --git a/.vscode/launch.json b/.vscode/launch.json index c4f10de0..0d6b7584 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -43,5 +43,13 @@ "${file}" ] }, + { + "name": "Python: Current File", + "type": "python", + "request": "launch", + "program": "${file}", + "console": "integratedTerminal", + "justMyCode": false + }, ] } \ No newline at end of file diff --git a/README.md b/README.md index df2fc7c0..8eb6e44a 100644 --- a/README.md +++ b/README.md @@ -122,6 +122,7 @@ summary: all planned features work! now please enjoy the bloatening * Windows: python 3.7 and older cannot read tags with ffprobe, so use mutagen or upgrade * Windows: python 2.7 cannot index non-ascii filenames with `-e2d` * Windows: python 2.7 cannot handle filenames with mojibake +* MacOS: `--th-ff-jpg` may fix thumbnails using macports-FFmpeg ## general bugs diff --git a/copyparty/__main__.py b/copyparty/__main__.py index 816b7c55..1aa0304d 100644 --- a/copyparty/__main__.py +++ b/copyparty/__main__.py @@ -277,6 +277,7 @@ def run_argparse(argv, formatter): ap2.add_argument("--th-poke", metavar="SEC", type=int, default=300, help="activity labeling cooldown") ap2.add_argument("--th-clean", metavar="SEC", type=int, default=43200, help="cleanup interval") ap2.add_argument("--th-maxage", metavar="SEC", type=int, default=604800, help="max folder age") + ap2.add_argument("--th-ff-jpg", action="store_true", help="force jpg for video thumbs") ap2 = ap.add_argument_group('database options') ap2.add_argument("-e2d", action="store_true", help="enable up2k database") diff --git a/copyparty/th_cli.py b/copyparty/th_cli.py index 18b04b31..49d7f29d 100644 --- a/copyparty/th_cli.py +++ b/copyparty/th_cli.py @@ -21,14 +21,16 @@ class ThumbCli(object): if ext not in THUMBABLE: return None - if self.args.no_vthumb and ext in FMT_FF: + is_vid = ext in FMT_FF + if is_vid and self.args.no_vthumb: return None if fmt == "j" and self.args.th_no_jpg: fmt = "w" - if fmt == "w" and self.args.th_no_webp: - fmt = "j" + if fmt == "w": + if self.args.th_no_webp or (is_vid and self.args.th_ff_jpg): + fmt = "j" hist = self.hist[ptop] tpath = thumb_path(hist, rem, mtime, fmt) diff --git a/copyparty/th_srv.py b/copyparty/th_srv.py index 4013058c..1a7f5e49 100644 --- a/copyparty/th_srv.py +++ b/copyparty/th_srv.py @@ -11,7 +11,7 @@ import threading import subprocess as sp from .__init__ import PY2 -from .util import fsenc, mchkcmd, Queue, Cooldown, BytesIO +from .util import fsenc, runcmd, Queue, Cooldown, BytesIO, min_ex from .mtag import HAVE_FFMPEG, HAVE_FFPROBE, ffprobe @@ -213,9 +213,9 @@ class ThumbSrv(object): if fun: try: fun(abspath, tpath) - except Exception as ex: - msg = "{} failed on {}\n {!r}" - self.log(msg.format(fun.__name__, abspath, ex), 3) + except: + msg = "{} failed on {}\n{}" + self.log(msg.format(fun.__name__, abspath, min_ex()), 3) with open(tpath, "wb") as _: pass @@ -263,8 +263,13 @@ class ThumbSrv(object): def conv_ffmpeg(self, abspath, tpath): ret, _ = ffprobe(abspath) - dur = ret[".dur"][1] if ".dur" in ret else 4 - seek = "{:.0f}".format(dur / 3) + ext = abspath.rsplit(".")[-1] + if ext in ["h264", "h265"]: + seek = [] + else: + dur = ret[".dur"][1] if ".dur" in ret else 4 + seek = "{:.0f}".format(dur / 3) + seek = [b"-ss", seek.encode("utf-8")] scale = "scale={0}:{1}:force_original_aspect_ratio=" if self.args.th_no_crop: @@ -273,19 +278,20 @@ class ThumbSrv(object): scale += "increase,crop={0}:{1},setsar=1:1" scale = scale.format(*list(self.res)).encode("utf-8") + # fmt: off cmd = [ b"ffmpeg", b"-nostdin", - b"-hide_banner", - b"-ss", - seek, - b"-i", - fsenc(abspath), - b"-vf", - scale, - b"-vframes", - b"1", + b"-v", b"error", + b"-hide_banner" ] + cmd += seek + cmd += [ + b"-i", fsenc(abspath), + b"-vf", scale, + b"-vframes", b"1", + ] + # fmt: on if tpath.endswith(".jpg"): cmd += [ @@ -302,7 +308,11 @@ class ThumbSrv(object): cmd += [fsenc(tpath)] - mchkcmd(cmd) + ret, sout, serr = runcmd(*cmd) + if ret != 0: + msg = ["ff: {}".format(x) for x in serr.split("\n")] + self.log("FFmpeg failed:\n" + "\n".join(msg), c="1;30") + raise sp.CalledProcessError(ret, (cmd[0], b"...", cmd[-1])) def poke(self, tdir): if not self.poke_cd.poke(tdir): diff --git a/copyparty/util.py b/copyparty/util.py index 5de00de5..fb71bdc9 100644 --- a/copyparty/util.py +++ b/copyparty/util.py @@ -998,8 +998,8 @@ def guess_mime(url, fallback="application/octet-stream"): def runcmd(*argv): p = sp.Popen(argv, stdout=sp.PIPE, stderr=sp.PIPE) stdout, stderr = p.communicate() - stdout = stdout.decode("utf-8") - stderr = stderr.decode("utf-8") + stdout = stdout.decode("utf-8", "replace") + stderr = stderr.decode("utf-8", "replace") return [p.returncode, stdout, stderr] diff --git a/scripts/test/smoketest.py b/scripts/test/smoketest.py index 43d7a803..8bcb6bc0 100644 --- a/scripts/test/smoketest.py +++ b/scripts/test/smoketest.py @@ -46,20 +46,22 @@ def main(): with open(vidp, "rb") as f: ovid = f.read() - args = ["-p", "4321"] + args = ["-p", "4321", "-e2dsa", "-e2tsr"] pdirs = [] for d1 in ["r", "w", "a"]: pdirs.append("{}/{}".format(td, d1)) + pdirs.append("{}/{}/j".format(td, d1)) for d2 in ["r", "w", "a"]: d = os.path.join(td, d1, "j", d2) pdirs.append(d.replace("\\", "/")) os.makedirs(d) udirs = [x.split("/", 2)[2] for x in pdirs] - for pd, ud in zip(pdirs, udirs): + perms = [x.rstrip("j/")[-1] for x in pdirs] + for pd, ud, p in zip(pdirs, udirs, perms): # args += ["-v", "{}:{}:{}".format(d.split("/", 1)[1], d, d[-1])] - args += ["-v", "{}:{}:{}".format(pd, ud, ud[-1])] + args += ["-v", "{}:{}:{}".format(pd, ud, p)] cpp = Cpp(args) @@ -74,14 +76,36 @@ def main(): pass assert up - # for d in dirs: - # rd, fn = d.rsplit("/", 1) - # requests.post(ub + rd, files={"act": "mkdir", "name": fn}) for d in udirs: vid = ovid + "\n{}".format(d).encode("utf-8") requests.post(ub + d, data={"act": "bput"}, files={"f": ("a.h264", vid)}) - time.sleep(3) + + for d, p in zip(udirs, perms): + u = "{}{}/a.h264".format(ub, d) + r = requests.get(u) + ok = bool(r) + if ok != (p in ["a"]): + raise Exception("get {} with perm {} at {}".format(ok, p, u)) + + for d, p in zip(pdirs, perms): + u = "{}/a.h264".format(d) + ok = os.path.exists(u) + if ok != (p in ["a", "w"]): + raise Exception("stat {} with perm {} at {}".format(ok, p, u)) + + for d, p in zip(udirs, perms): + u = "{}{}/a.h264?th=j".format(ub, d) + r = requests.get(u) + ok = False + if r: + r.raw.decode_content = True + buf = r.raw.read(256) + if buf[:3] == b"\xff\xd8\xff": + ok = True + if ok != (p in ["a"]): + raise Exception("thumb {} with perm {} at {}".format(ok, p, u)) + cpp.stop(True)