create webp thumbnails by default

This commit is contained in:
ed 2021-05-28 02:44:13 +02:00
parent 6b065d507d
commit 5d63949e98
7 changed files with 69 additions and 18 deletions

View file

@ -253,7 +253,9 @@ def run_argparse(argv, formatter):
ap2.add_argument("--no-thumb", action="store_true", help="disable all 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-nocrop", action="store_true", help="dynamic height (no crop)")
ap2.add_argument("--th-no-crop", action="store_true", help="dynamic height; show full image")
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-poke", metavar="SEC", type=int, default=300, help="activity labeling cooldown")
ap2.add_argument("--th-clean", metavar="SEC", type=int, default=1800, help="cleanup interval")
ap2.add_argument("--th-maxage", metavar="SEC", type=int, default=604800, help="max folder age")

View file

@ -1385,10 +1385,11 @@ class HttpCli(object):
if rem.startswith(".hist/up2k."):
raise Pebkac(403)
if "th" in self.uparam:
th_fmt = self.uparam.get("th")
if th_fmt is not None:
thp = None
if self.thumbcli:
thp = self.thumbcli.get(vn.realpath, rem, int(st.st_mtime))
thp = self.thumbcli.get(vn.realpath, rem, int(st.st_mtime), th_fmt)
if thp:
return self.tx_file(thp)

View file

@ -22,7 +22,7 @@ class Ico(object):
c = "".join(["{:02x}".format(x) for x in c])
h = 30
if not self.args.th_nocrop and as_thumb:
if not self.args.th_no_crop and as_thumb:
w, h = self.args.th_size.split("x")
h = int(100 / (float(w) / float(h)))

View file

@ -13,7 +13,7 @@ class ThumbCli(object):
# cache on both sides for less broker spam
self.cooldown = Cooldown(self.args.th_poke)
def get(self, ptop, rem, mtime):
def get(self, ptop, rem, mtime, fmt):
ext = rem.rsplit(".")[-1].lower()
if ext not in THUMBABLE:
return None
@ -21,7 +21,13 @@ class ThumbCli(object):
if self.args.no_vthumb and ext in FMT_FF:
return None
tpath = thumb_path(ptop, rem, mtime)
if fmt == "w" and self.args.th_no_webp:
fmt = "j"
if fmt == "j" and self.args.th_no_jpg:
fmt = "w"
tpath = thumb_path(ptop, rem, mtime, fmt)
ret = None
try:
st = os.stat(tpath)
@ -39,5 +45,5 @@ class ThumbCli(object):
return ret
x = self.broker.put(True, "thumbsrv.get", ptop, rem, mtime)
x = self.broker.put(True, "thumbsrv.get", ptop, rem, mtime, fmt)
return x.get()

View file

@ -51,7 +51,7 @@ if HAVE_FFMPEG and HAVE_FFPROBE:
THUMBABLE.update(FMT_FF)
def thumb_path(ptop, rem, mtime):
def thumb_path(ptop, rem, mtime, fmt):
# base16 = 16 = 256
# b64-lc = 38 = 1444
# base64 = 64 = 4096
@ -72,7 +72,9 @@ def thumb_path(ptop, rem, mtime):
h = hashlib.sha512(fsenc(fn)).digest()[:24]
fn = base64.urlsafe_b64encode(h).decode("ascii")[:24]
return "{}/.hist/th/{}/{}.{:x}.jpg".format(ptop, rd, fn, int(mtime))
return "{}/.hist/th/{}/{}.{:x}.{}".format(
ptop, rd, fn, int(mtime), "webp" if fmt == "w" else "jpg"
)
class ThumbSrv(object):
@ -129,8 +131,8 @@ class ThumbSrv(object):
with self.mutex:
return not self.nthr
def get(self, ptop, rem, mtime):
tpath = thumb_path(ptop, rem, mtime)
def get(self, ptop, rem, mtime, fmt):
tpath = thumb_path(ptop, rem, mtime, fmt)
abspath = os.path.join(ptop, rem)
cond = threading.Condition()
with self.mutex:
@ -207,7 +209,7 @@ class ThumbSrv(object):
def conv_pil(self, abspath, tpath):
with Image.open(abspath) as im:
crop = not self.args.th_nocrop
crop = not self.args.th_no_crop
res2 = self.res
if crop:
res2 = (res2[0] * 2, res2[1] * 2)
@ -222,7 +224,15 @@ class ThumbSrv(object):
if im.mode not in ("RGB", "L"):
im = im.convert("RGB")
im.save(tpath, quality=50)
if tpath.endswith(".webp"):
# quality 80 = pillow-default
# quality 75 = ffmpeg-default
# method 0 = pillow-default, fast
# method 4 = ffmpeg-default
# method 6 = max, slow
im.save(tpath, quality=40, method=6)
else:
im.save(tpath, quality=40) # default=75
def conv_ffmpeg(self, abspath, tpath):
ret, _ = ffprobe(abspath)
@ -231,7 +241,7 @@ class ThumbSrv(object):
seek = "{:.0f}".format(dur / 3)
scale = "scale={0}:{1}:force_original_aspect_ratio="
if self.args.th_nocrop:
if self.args.th_no_crop:
scale += "decrease,setsar=1:1"
else:
scale += "increase,crop={0}:{1},setsar=1:1"
@ -249,10 +259,23 @@ class ThumbSrv(object):
scale,
b"-vframes",
b"1",
b"-q:v",
b"6",
fsenc(tpath),
]
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)]
p = sp.Popen(cmd, stdout=sp.PIPE, stderr=sp.PIPE)
p.communicate()

View file

@ -944,6 +944,9 @@ def guess_mime(url, fallback="application/octet-stream"):
if url.endswith(".md"):
return ["text/plain; charset=UTF-8"]
if url.endswith(".webp"):
return ["image/webp"]
return mimetypes.guess_type(url) or fallback

View file

@ -29,6 +29,19 @@ ebi('widget').innerHTML = (
);
var have_webp = null;
(function () {
var img = new Image();
img.onload = function () {
have_webp = img.width > 0 && img.height > 0;
};
img.onerror = function () {
have_webp = false;
};
img.src = "data:image/webp;base64,UklGRiIAAABXRUJQVlA4IBYAAAAwAQCdASoBAAEADsD+JaQAA3AAAAAA";
})();
// extract songs + add play column
function MPlayer() {
this.id = Date.now();
@ -816,6 +829,9 @@ var thegrid = (function () {
}
function loadgrid() {
if (have_webp === null)
return setTimeout(loadgrid, 50);
if (!r.dirty)
return r.loadsel();
@ -832,7 +848,7 @@ var thegrid = (function () {
ihref = '/.cpr/ico/folder'
}
else if (r.thumbs) {
ihref += ihref.indexOf('?') === -1 ? '?th' : '&th';
ihref += (ihref.indexOf('?') === -1 ? '?' : '&') + 'th=' + (have_webp ? 'w' : 'j');
}
else {
var ar = href.split('?')[0].split('.');