mirror of
https://github.com/9001/copyparty.git
synced 2025-08-17 17:12:13 -06:00
add audio spectrogram thumbnails
This commit is contained in:
parent
280fe8e36b
commit
c2469935cb
|
@ -402,6 +402,7 @@ def run_argparse(argv, formatter):
|
||||||
|
|
||||||
ap2 = ap.add_argument_group('thumbnail options')
|
ap2 = ap.add_argument_group('thumbnail options')
|
||||||
ap2.add_argument("--no-thumb", action="store_true", help="disable all thumbnails")
|
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("--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-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")
|
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-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-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-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-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-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")
|
ap2.add_argument("--th-maxage", metavar="SEC", type=int, default=604800, help="max folder age")
|
||||||
|
|
|
@ -4,7 +4,7 @@ from __future__ import print_function, unicode_literals
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from .util import Cooldown
|
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
|
from .bos import bos
|
||||||
|
|
||||||
|
|
||||||
|
@ -22,10 +22,14 @@ class ThumbCli(object):
|
||||||
if ext not in THUMBABLE:
|
if ext not in THUMBABLE:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
is_vid = ext in FMT_FF
|
is_vid = ext in FMT_FFV
|
||||||
if is_vid and self.args.no_vthumb:
|
if is_vid and self.args.no_vthumb:
|
||||||
return None
|
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"]:
|
if rem.startswith(".hist/th/") and rem.split(".")[-1] in ["webp", "jpg"]:
|
||||||
return os.path.join(ptop, rem)
|
return os.path.join(ptop, rem)
|
||||||
|
|
||||||
|
@ -33,7 +37,7 @@ class ThumbCli(object):
|
||||||
fmt = "w"
|
fmt = "w"
|
||||||
|
|
||||||
if 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"
|
fmt = "j"
|
||||||
|
|
||||||
histpath = self.asrv.vfs.histtab[ptop]
|
histpath = self.asrv.vfs.histtab[ptop]
|
||||||
|
|
|
@ -50,7 +50,8 @@ except:
|
||||||
# https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html
|
# https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html
|
||||||
# ffmpeg -formats
|
# 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_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:
|
if HAVE_HEIF:
|
||||||
FMT_PIL += " heif heifs heic heics"
|
FMT_PIL += " heif heifs heic heics"
|
||||||
|
@ -58,7 +59,9 @@ if HAVE_HEIF:
|
||||||
if HAVE_AVIF:
|
if HAVE_AVIF:
|
||||||
FMT_PIL += " avif avifs"
|
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 = {}
|
THUMBABLE = {}
|
||||||
|
@ -67,7 +70,8 @@ if HAVE_PIL:
|
||||||
THUMBABLE.update(FMT_PIL)
|
THUMBABLE.update(FMT_PIL)
|
||||||
|
|
||||||
if HAVE_FFMPEG and HAVE_FFPROBE:
|
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):
|
def thumb_path(histpath, rem, mtime, fmt):
|
||||||
|
@ -115,7 +119,8 @@ class ThumbSrv(object):
|
||||||
t.daemon = True
|
t.daemon = True
|
||||||
t.start()
|
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 = []
|
missing = []
|
||||||
if not HAVE_FFMPEG:
|
if not HAVE_FFMPEG:
|
||||||
missing.append("FFmpeg")
|
missing.append("FFmpeg")
|
||||||
|
@ -123,7 +128,7 @@ class ThumbSrv(object):
|
||||||
if not HAVE_FFPROBE:
|
if not HAVE_FFPROBE:
|
||||||
missing.append("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)
|
msg += ", ".join(missing)
|
||||||
self.log(msg, c=3)
|
self.log(msg, c=3)
|
||||||
|
|
||||||
|
@ -199,8 +204,10 @@ class ThumbSrv(object):
|
||||||
if not bos.path.exists(tpath):
|
if not bos.path.exists(tpath):
|
||||||
if ext in FMT_PIL:
|
if ext in FMT_PIL:
|
||||||
fun = self.conv_pil
|
fun = self.conv_pil
|
||||||
elif ext in FMT_FF:
|
elif ext in FMT_FFV:
|
||||||
fun = self.conv_ffmpeg
|
fun = self.conv_ffmpeg
|
||||||
|
elif ext in FMT_FFA:
|
||||||
|
fun = self.conv_spec
|
||||||
|
|
||||||
if fun:
|
if fun:
|
||||||
try:
|
try:
|
||||||
|
@ -326,8 +333,10 @@ class ThumbSrv(object):
|
||||||
]
|
]
|
||||||
|
|
||||||
cmd += [fsenc(tpath)]
|
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)
|
ret, sout, serr = runcmd(cmd)
|
||||||
if ret != 0:
|
if ret != 0:
|
||||||
m = "FFmpeg failed (probably a corrupt video file):\n"
|
m = "FFmpeg failed (probably a corrupt video file):\n"
|
||||||
|
@ -335,6 +344,43 @@ class ThumbSrv(object):
|
||||||
self.log(m, c="1;30")
|
self.log(m, c="1;30")
|
||||||
raise sp.CalledProcessError(ret, (cmd[0], b"...", cmd[-1]))
|
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):
|
def poke(self, tdir):
|
||||||
if not self.poke_cd.poke(tdir):
|
if not self.poke_cd.poke(tdir):
|
||||||
return
|
return
|
||||||
|
|
Loading…
Reference in a new issue