mirror of
https://github.com/9001/copyparty.git
synced 2025-08-18 01:22:13 -06:00
create webp thumbnails by default
This commit is contained in:
parent
6b065d507d
commit
5d63949e98
|
@ -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-thumb", action="store_true", help="disable all thumbnails")
|
||||||
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-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-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-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")
|
ap2.add_argument("--th-maxage", metavar="SEC", type=int, default=604800, help="max folder age")
|
||||||
|
|
|
@ -1385,10 +1385,11 @@ class HttpCli(object):
|
||||||
if rem.startswith(".hist/up2k."):
|
if rem.startswith(".hist/up2k."):
|
||||||
raise Pebkac(403)
|
raise Pebkac(403)
|
||||||
|
|
||||||
if "th" in self.uparam:
|
th_fmt = self.uparam.get("th")
|
||||||
|
if th_fmt is not None:
|
||||||
thp = None
|
thp = None
|
||||||
if self.thumbcli:
|
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:
|
if thp:
|
||||||
return self.tx_file(thp)
|
return self.tx_file(thp)
|
||||||
|
|
|
@ -22,7 +22,7 @@ class Ico(object):
|
||||||
c = "".join(["{:02x}".format(x) for x in c])
|
c = "".join(["{:02x}".format(x) for x in c])
|
||||||
|
|
||||||
h = 30
|
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")
|
w, h = self.args.th_size.split("x")
|
||||||
h = int(100 / (float(w) / float(h)))
|
h = int(100 / (float(w) / float(h)))
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ class ThumbCli(object):
|
||||||
# cache on both sides for less broker spam
|
# cache on both sides for less broker spam
|
||||||
self.cooldown = Cooldown(self.args.th_poke)
|
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()
|
ext = rem.rsplit(".")[-1].lower()
|
||||||
if ext not in THUMBABLE:
|
if ext not in THUMBABLE:
|
||||||
return None
|
return None
|
||||||
|
@ -21,7 +21,13 @@ class ThumbCli(object):
|
||||||
if self.args.no_vthumb and ext in FMT_FF:
|
if self.args.no_vthumb and ext in FMT_FF:
|
||||||
return None
|
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
|
ret = None
|
||||||
try:
|
try:
|
||||||
st = os.stat(tpath)
|
st = os.stat(tpath)
|
||||||
|
@ -39,5 +45,5 @@ class ThumbCli(object):
|
||||||
|
|
||||||
return ret
|
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()
|
return x.get()
|
||||||
|
|
|
@ -51,7 +51,7 @@ if HAVE_FFMPEG and HAVE_FFPROBE:
|
||||||
THUMBABLE.update(FMT_FF)
|
THUMBABLE.update(FMT_FF)
|
||||||
|
|
||||||
|
|
||||||
def thumb_path(ptop, rem, mtime):
|
def thumb_path(ptop, rem, mtime, fmt):
|
||||||
# base16 = 16 = 256
|
# base16 = 16 = 256
|
||||||
# b64-lc = 38 = 1444
|
# b64-lc = 38 = 1444
|
||||||
# base64 = 64 = 4096
|
# base64 = 64 = 4096
|
||||||
|
@ -72,7 +72,9 @@ def thumb_path(ptop, rem, mtime):
|
||||||
h = hashlib.sha512(fsenc(fn)).digest()[:24]
|
h = hashlib.sha512(fsenc(fn)).digest()[:24]
|
||||||
fn = base64.urlsafe_b64encode(h).decode("ascii")[: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):
|
class ThumbSrv(object):
|
||||||
|
@ -129,8 +131,8 @@ class ThumbSrv(object):
|
||||||
with self.mutex:
|
with self.mutex:
|
||||||
return not self.nthr
|
return not self.nthr
|
||||||
|
|
||||||
def get(self, ptop, rem, mtime):
|
def get(self, ptop, rem, mtime, fmt):
|
||||||
tpath = thumb_path(ptop, rem, mtime)
|
tpath = thumb_path(ptop, rem, mtime, fmt)
|
||||||
abspath = os.path.join(ptop, rem)
|
abspath = os.path.join(ptop, rem)
|
||||||
cond = threading.Condition()
|
cond = threading.Condition()
|
||||||
with self.mutex:
|
with self.mutex:
|
||||||
|
@ -207,7 +209,7 @@ class ThumbSrv(object):
|
||||||
|
|
||||||
def conv_pil(self, abspath, tpath):
|
def conv_pil(self, abspath, tpath):
|
||||||
with Image.open(abspath) as im:
|
with Image.open(abspath) as im:
|
||||||
crop = not self.args.th_nocrop
|
crop = not self.args.th_no_crop
|
||||||
res2 = self.res
|
res2 = self.res
|
||||||
if crop:
|
if crop:
|
||||||
res2 = (res2[0] * 2, res2[1] * 2)
|
res2 = (res2[0] * 2, res2[1] * 2)
|
||||||
|
@ -222,7 +224,15 @@ class ThumbSrv(object):
|
||||||
if im.mode not in ("RGB", "L"):
|
if im.mode not in ("RGB", "L"):
|
||||||
im = im.convert("RGB")
|
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):
|
def conv_ffmpeg(self, abspath, tpath):
|
||||||
ret, _ = ffprobe(abspath)
|
ret, _ = ffprobe(abspath)
|
||||||
|
@ -231,7 +241,7 @@ class ThumbSrv(object):
|
||||||
seek = "{:.0f}".format(dur / 3)
|
seek = "{:.0f}".format(dur / 3)
|
||||||
|
|
||||||
scale = "scale={0}:{1}:force_original_aspect_ratio="
|
scale = "scale={0}:{1}:force_original_aspect_ratio="
|
||||||
if self.args.th_nocrop:
|
if self.args.th_no_crop:
|
||||||
scale += "decrease,setsar=1:1"
|
scale += "decrease,setsar=1:1"
|
||||||
else:
|
else:
|
||||||
scale += "increase,crop={0}:{1},setsar=1:1"
|
scale += "increase,crop={0}:{1},setsar=1:1"
|
||||||
|
@ -249,10 +259,23 @@ class ThumbSrv(object):
|
||||||
scale,
|
scale,
|
||||||
b"-vframes",
|
b"-vframes",
|
||||||
b"1",
|
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 = sp.Popen(cmd, stdout=sp.PIPE, stderr=sp.PIPE)
|
||||||
p.communicate()
|
p.communicate()
|
||||||
|
|
||||||
|
|
|
@ -944,6 +944,9 @@ def guess_mime(url, fallback="application/octet-stream"):
|
||||||
if url.endswith(".md"):
|
if url.endswith(".md"):
|
||||||
return ["text/plain; charset=UTF-8"]
|
return ["text/plain; charset=UTF-8"]
|
||||||
|
|
||||||
|
if url.endswith(".webp"):
|
||||||
|
return ["image/webp"]
|
||||||
|
|
||||||
return mimetypes.guess_type(url) or fallback
|
return mimetypes.guess_type(url) or fallback
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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
|
// extract songs + add play column
|
||||||
function MPlayer() {
|
function MPlayer() {
|
||||||
this.id = Date.now();
|
this.id = Date.now();
|
||||||
|
@ -816,6 +829,9 @@ var thegrid = (function () {
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadgrid() {
|
function loadgrid() {
|
||||||
|
if (have_webp === null)
|
||||||
|
return setTimeout(loadgrid, 50);
|
||||||
|
|
||||||
if (!r.dirty)
|
if (!r.dirty)
|
||||||
return r.loadsel();
|
return r.loadsel();
|
||||||
|
|
||||||
|
@ -832,7 +848,7 @@ var thegrid = (function () {
|
||||||
ihref = '/.cpr/ico/folder'
|
ihref = '/.cpr/ico/folder'
|
||||||
}
|
}
|
||||||
else if (r.thumbs) {
|
else if (r.thumbs) {
|
||||||
ihref += ihref.indexOf('?') === -1 ? '?th' : '&th';
|
ihref += (ihref.indexOf('?') === -1 ? '?' : '&') + 'th=' + (have_webp ? 'w' : 'j');
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
var ar = href.split('?')[0].split('.');
|
var ar = href.split('?')[0].split('.');
|
||||||
|
|
Loading…
Reference in a new issue