#!/usr/bin/env python3 import json import re import os import sys import subprocess as sp try: from copyparty.util import fsenc except: def fsenc(p): return p.encode("utf-8") _ = r""" inspects video files for errors and such plus stores a bunch of metadata to filename.ff.json usage: -mtp vidchk=t600,ay,p,bin/mtag/vidchk.py explained: t600: timeout 10min ay: only process files which contain audio (including video with audio) p: set priority 1 (lowest priority after initial ffprobe/mutagen for base tags), makes copyparty feed base tags into this script as json if you wanna use this script standalone / separately from copyparty, provide the video resolution on stdin as json: {"res":"1920x1080"} """ FAST = True # parse entire file at container level # FAST = False # fully decode audio and video streams # warnings to ignore harmless = re.compile( r"Unsupported codec with id |Could not find codec parameters.*Attachment:|analyzeduration" + r"|timescale not set" ) def wfilter(lines): return [x for x in lines if x.strip() and not harmless.search(x)] def errchk(so, se, rc, dbg): if dbg: with open(dbg, "wb") as f: f.write(b"so:\n" + so + b"\nse:\n" + se + b"\n") if rc: err = (so + se).decode("utf-8", "replace").split("\n", 1) err = wfilter(err) or err return f"ERROR {rc}: {err[0]}" if se: err = se.decode("utf-8", "replace").split("\n", 1) err = wfilter(err) if err: return f"Warning: {err[0]}" return None def main(): fp = sys.argv[1] zb = sys.stdin.buffer.read() zs = zb.decode("utf-8", "replace") md = json.loads(zs) fdir = os.path.dirname(os.path.realpath(fp)) flag = os.path.join(fdir, ".processed") if os.path.exists(flag): return "already processed" try: w, h = [int(x) for x in md["res"].split("x")] if not w + h: raise Exception() except: return "could not determine resolution" # grab streams/format metadata + 2 seconds of frames at the start and end zs = "ffprobe -hide_banner -v warning -of json -show_streams -show_format -show_packets -show_data_hash crc32 -read_intervals %+2,999999%+2" cmd = zs.encode("ascii").split(b" ") + [fsenc(fp)] p = sp.Popen(cmd, stdout=sp.PIPE, stderr=sp.PIPE) so, se = p.communicate() # spaces to tabs, drops filesize from 69k to 48k so = b"\n".join( [ b"\t" * int((len(x) - len(x.lstrip())) / 4) + x.lstrip() for x in (so or b"").split(b"\n") ] ) with open(fsenc(f"{fp}.ff.json"), "wb") as f: f.write(so) err = errchk(so, se, p.returncode, f"{fp}.vidchk") if err: return err if max(w, h) < 1280 and min(w, h) < 720: return "resolution too small" zs = ( "ffmpeg -y -hide_banner -nostdin -v warning" + " -err_detect +crccheck+bitstream+buffer+careful+compliant+aggressive+explode" + " -xerror -i" ) cmd = zs.encode("ascii").split(b" ") + [fsenc(fp)] if FAST: zs = "-c copy -f null -" else: zs = "-vcodec rawvideo -acodec pcm_s16le -f null -" cmd += zs.encode("ascii").split(b" ") p = sp.Popen(cmd, stdout=sp.PIPE, stderr=sp.PIPE) so, se = p.communicate() return errchk(so, se, p.returncode, f"{fp}.vidchk") if __name__ == "__main__": print(main() or "ok")