add audio spectrogram thumbnails

This commit is contained in:
ed 2021-11-02 00:26:51 +01:00
parent 280fe8e36b
commit c2469935cb
3 changed files with 62 additions and 10 deletions

View file

@ -402,6 +402,7 @@ def run_argparse(argv, formatter):
ap2 = ap.add_argument_group('thumbnail options')
ap2.add_argument("--no-thumb", action="store_true", help="disable all thumbnails")
ap2.add_argument("--no-athumb", action="store_true", help="disable audio thumbnails (spectrograms)")
ap2.add_argument("--no-vthumb", action="store_true", help="disable video thumbnails")
ap2.add_argument("--th-size", metavar="WxH", default="320x256", help="thumbnail res")
ap2.add_argument("--th-mt", metavar="CORES", type=int, default=cores, help="num cpu cores to use for generating thumbnails")
@ -409,6 +410,7 @@ def run_argparse(argv, formatter):
ap2.add_argument("--th-no-jpg", action="store_true", help="disable jpg output")
ap2.add_argument("--th-no-webp", action="store_true", help="disable webp output")
ap2.add_argument("--th-ff-jpg", action="store_true", help="force jpg for video thumbs")
ap2.add_argument("--th-ff-swr", action="store_true", help="use swresample instead of soxr for audio thumbs")
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; 0=disabled")
ap2.add_argument("--th-maxage", metavar="SEC", type=int, default=604800, help="max folder age")

View file

@ -4,7 +4,7 @@ from __future__ import print_function, unicode_literals
import os
from .util import Cooldown
from .th_srv import thumb_path, THUMBABLE, FMT_FF
from .th_srv import thumb_path, THUMBABLE, FMT_FFV, FMT_FFA
from .bos import bos
@ -22,10 +22,14 @@ class ThumbCli(object):
if ext not in THUMBABLE:
return None
is_vid = ext in FMT_FF
is_vid = ext in FMT_FFV
if is_vid and self.args.no_vthumb:
return None
is_au = ext in FMT_FFA
if is_au and self.args.no_athumb:
return None
if rem.startswith(".hist/th/") and rem.split(".")[-1] in ["webp", "jpg"]:
return os.path.join(ptop, rem)
@ -33,7 +37,7 @@ class ThumbCli(object):
fmt = "w"
if fmt == "w":
if self.args.th_no_webp or (is_vid and self.args.th_ff_jpg):
if self.args.th_no_webp or ((is_vid or is_au) and self.args.th_ff_jpg):
fmt = "j"
histpath = self.asrv.vfs.histtab[ptop]

View file

@ -50,7 +50,8 @@ except:
# https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html
# ffmpeg -formats
FMT_PIL = "bmp dib gif icns ico jpg jpeg jp2 jpx pcx png pbm pgm ppm pnm sgi tga tif tiff webp xbm dds xpm"
FMT_FF = "av1 asf avi flv m4v mkv mjpeg mjpg mpg mpeg mpg2 mpeg2 h264 avc mts h265 hevc mov 3gp mp4 ts mpegts nut ogv ogm rm vob webm wmv"
FMT_FFV = "av1 asf avi flv m4v mkv mjpeg mjpg mpg mpeg mpg2 mpeg2 h264 avc mts h265 hevc mov 3gp mp4 ts mpegts nut ogv ogm rm vob webm wmv"
FMT_FFA = "aac m4a ogg opus flac alac mp3 mp2 ac3 dts wma wav aif aiff au amr gsm ape tak tta wv"
if HAVE_HEIF:
FMT_PIL += " heif heifs heic heics"
@ -58,7 +59,9 @@ if HAVE_HEIF:
if HAVE_AVIF:
FMT_PIL += " avif avifs"
FMT_PIL, FMT_FF = [{x: True for x in y.split(" ") if x} for y in [FMT_PIL, FMT_FF]]
FMT_PIL, FMT_FFV, FMT_FFA = [
{x: True for x in y.split(" ") if x} for y in [FMT_PIL, FMT_FFV, FMT_FFA]
]
THUMBABLE = {}
@ -67,7 +70,8 @@ if HAVE_PIL:
THUMBABLE.update(FMT_PIL)
if HAVE_FFMPEG and HAVE_FFPROBE:
THUMBABLE.update(FMT_FF)
THUMBABLE.update(FMT_FFV)
THUMBABLE.update(FMT_FFA)
def thumb_path(histpath, rem, mtime, fmt):
@ -115,7 +119,8 @@ class ThumbSrv(object):
t.daemon = True
t.start()
if not self.args.no_vthumb and (not HAVE_FFMPEG or not HAVE_FFPROBE):
want_ff = not self.args.no_vthumb or not self.args.no_athumb
if want_ff and (not HAVE_FFMPEG or not HAVE_FFPROBE):
missing = []
if not HAVE_FFMPEG:
missing.append("FFmpeg")
@ -123,7 +128,7 @@ class ThumbSrv(object):
if not HAVE_FFPROBE:
missing.append("FFprobe")
msg = "cannot create video thumbnails because some of the required programs are not available: "
msg = "cannot create audio/video thumbnails because some of the required programs are not available: "
msg += ", ".join(missing)
self.log(msg, c=3)
@ -199,8 +204,10 @@ class ThumbSrv(object):
if not bos.path.exists(tpath):
if ext in FMT_PIL:
fun = self.conv_pil
elif ext in FMT_FF:
elif ext in FMT_FFV:
fun = self.conv_ffmpeg
elif ext in FMT_FFA:
fun = self.conv_spec
if fun:
try:
@ -326,8 +333,10 @@ class ThumbSrv(object):
]
cmd += [fsenc(tpath)]
# self.log((b" ".join(cmd)).decode("utf-8"))
self._run_ff(cmd)
def _run_ff(self, cmd):
# self.log((b" ".join(cmd)).decode("utf-8"))
ret, sout, serr = runcmd(cmd)
if ret != 0:
m = "FFmpeg failed (probably a corrupt video file):\n"
@ -335,6 +344,43 @@ class ThumbSrv(object):
self.log(m, c="1;30")
raise sp.CalledProcessError(ret, (cmd[0], b"...", cmd[-1]))
def conv_spec(self, abspath, tpath):
ret, _ = ffprobe(abspath)
if "ac" not in ret:
raise Exception("not audio")
fc = "[0:a:0]aresample=48000{},showspectrumpic=s=640x512,crop=780:544:70:50[o]"
fc = fc.format("" if self.args.th_ff_swr else ":resampler=soxr")
# fmt: off
cmd = [
b"ffmpeg",
b"-nostdin",
b"-v", b"error",
b"-hide_banner",
b"-i", fsenc(abspath),
b"-filter_complex", fc.encode("utf-8"),
b"-map", b"[o]"
]
# fmt: on
if tpath.endswith(".jpg"):
cmd += [
b"-q:v",
b"6", # default=??
]
else:
cmd += [
b"-q:v",
b"50", # default=75
b"-compression_level:v",
b"6", # default=4, 0=fast, 6=max
]
cmd += [fsenc(tpath)]
self._run_ff(cmd)
def poke(self, tdir):
if not self.poke_cd.poke(tdir):
return