mirror of
https://github.com/9001/copyparty.git
synced 2025-08-17 09:02:15 -06:00
client decides if thumbnails should be cropped or not
this carries some intentional side-effects; each thumbnail format will now be stored in its own subfolder under .hist/th/ making cleanup more effective (jpeg and webm are dropped separately)
This commit is contained in:
parent
9b77c9ce7d
commit
2df76eb6e1
|
@ -1069,7 +1069,7 @@ def add_thumbnail(ap):
|
|||
ap2.add_argument("--th-size", metavar="WxH", default="320x256", help="thumbnail res (volflag=thsize)")
|
||||
ap2.add_argument("--th-mt", metavar="CORES", type=int, default=CORES, help="num cpu cores to use for generating thumbnails")
|
||||
ap2.add_argument("--th-convt", metavar="SEC", type=float, default=60, help="conversion timeout in seconds (volflag=convt)")
|
||||
ap2.add_argument("--th-no-crop", action="store_true", help="dynamic height; show full image (volflag=nocrop)")
|
||||
ap2.add_argument("--th-no-crop", action="store_true", help="dynamic height; show full image by default (volflag=nocrop)")
|
||||
ap2.add_argument("--th-dec", metavar="LIBS", default="vips,pil,ff", help="image decoders, in order of preference")
|
||||
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")
|
||||
|
|
|
@ -129,7 +129,7 @@ flagcats = {
|
|||
"dathumb": "disables audio thumbnails (spectrograms)",
|
||||
"dithumb": "disables image thumbnails",
|
||||
"thsize": "thumbnail res; WxH",
|
||||
"nocrop": "disable center-cropping",
|
||||
"nocrop": "disable center-cropping by default",
|
||||
"convt": "conversion timeout in seconds",
|
||||
},
|
||||
"handlers\n(better explained in --help-handlers)": {
|
||||
|
|
|
@ -3834,6 +3834,7 @@ class HttpCli(object):
|
|||
"idx": e2d,
|
||||
"itag": e2t,
|
||||
"dsort": vf["sort"],
|
||||
"dfull": "nocrop" in vf,
|
||||
"u2ts": vf["u2ts"],
|
||||
"lifetime": vn.flags.get("lifetime") or 0,
|
||||
"frand": bool(vn.flags.get("rand")),
|
||||
|
@ -3860,6 +3861,7 @@ class HttpCli(object):
|
|||
"sb_md": "" if "no_sb_md" in vf else (vf.get("md_sbf") or "y"),
|
||||
"readme": readme,
|
||||
"dgrid": "grid" in vf,
|
||||
"dfull": "nocrop" in vf,
|
||||
"dsort": vf["sort"],
|
||||
"themes": self.args.themes,
|
||||
"turbolvl": self.args.turbo,
|
||||
|
|
|
@ -84,12 +84,13 @@ def thumb_path(histpath: str, rem: str, mtime: float, fmt: str) -> str:
|
|||
# b64-lc = 38 = 1444
|
||||
# base64 = 64 = 4096
|
||||
rd, fn = vsplit(rem)
|
||||
if rd:
|
||||
h = hashlib.sha512(afsenc(rd)).digest()
|
||||
b64 = base64.urlsafe_b64encode(h).decode("ascii")[:24]
|
||||
rd = "{}/{}/".format(b64[:2], b64[2:4]).lower() + b64
|
||||
else:
|
||||
rd = "top"
|
||||
if not rd:
|
||||
rd = "\ntop"
|
||||
|
||||
rd += "\n" + fmt
|
||||
h = hashlib.sha512(afsenc(rd)).digest()
|
||||
b64 = base64.urlsafe_b64encode(h).decode("ascii")[:24]
|
||||
rd = "{}/{}/".format(b64[:2], b64[2:4]).lower() + b64
|
||||
|
||||
# could keep original filenames but this is safer re pathlen
|
||||
h = hashlib.sha512(afsenc(fn)).digest()
|
||||
|
@ -98,7 +99,8 @@ def thumb_path(histpath: str, rem: str, mtime: float, fmt: str) -> str:
|
|||
if fmt in ("opus", "caf"):
|
||||
cat = "ac"
|
||||
else:
|
||||
fmt = "webp" if fmt == "w" else "png" if fmt == "p" else "jpg"
|
||||
fc = fmt[:1]
|
||||
fmt = "webp" if fc == "w" else "png" if fc == "p" else "jpg"
|
||||
cat = "th"
|
||||
|
||||
return "{}/{}/{}/{}.{:x}.{}".format(histpath, cat, rd, fn, int(mtime), fmt)
|
||||
|
@ -118,7 +120,7 @@ class ThumbSrv(object):
|
|||
self.stopping = False
|
||||
self.nthr = max(1, self.args.th_mt)
|
||||
|
||||
self.q: Queue[Optional[tuple[str, str, VFS]]] = Queue(self.nthr * 4)
|
||||
self.q: Queue[Optional[tuple[str, str, str, VFS]]] = Queue(self.nthr * 4)
|
||||
for n in range(self.nthr):
|
||||
Daemon(self.worker, "thumb-{}-{}".format(n, self.nthr))
|
||||
|
||||
|
@ -220,8 +222,8 @@ class ThumbSrv(object):
|
|||
self.log("ptop [{}] not in {}".format(ptop, allvols), 3)
|
||||
vn = self.asrv.vfs.all_aps[0][1]
|
||||
|
||||
self.q.put((abspath, tpath, vn))
|
||||
self.log("conv {} \033[0m{}".format(tpath, abspath), c=6)
|
||||
self.q.put((abspath, tpath, fmt, vn))
|
||||
self.log("conv {} :{} \033[0m{}".format(tpath, fmt, abspath), c=6)
|
||||
|
||||
while not self.stopping:
|
||||
with self.mutex:
|
||||
|
@ -257,7 +259,7 @@ class ThumbSrv(object):
|
|||
if not task:
|
||||
break
|
||||
|
||||
abspath, tpath, vn = task
|
||||
abspath, tpath, fmt, vn = task
|
||||
ext = abspath.split(".")[-1].lower()
|
||||
png_ok = False
|
||||
funs = []
|
||||
|
@ -290,7 +292,7 @@ class ThumbSrv(object):
|
|||
|
||||
for fun in funs:
|
||||
try:
|
||||
fun(abspath, ttpath, vn)
|
||||
fun(abspath, ttpath, fmt, vn)
|
||||
break
|
||||
except Exception as ex:
|
||||
msg = "{} could not create thumbnail of {}\n{}"
|
||||
|
@ -324,7 +326,7 @@ class ThumbSrv(object):
|
|||
with self.mutex:
|
||||
self.nthr -= 1
|
||||
|
||||
def fancy_pillow(self, im: "Image.Image", vn: VFS) -> "Image.Image":
|
||||
def fancy_pillow(self, im: "Image.Image", fmt: str, vn: VFS) -> "Image.Image":
|
||||
# exif_transpose is expensive (loads full image + unconditional copy)
|
||||
res = self.getres(vn)
|
||||
r = max(*res) * 2
|
||||
|
@ -341,7 +343,7 @@ class ThumbSrv(object):
|
|||
if rot in rots:
|
||||
im = im.transpose(rots[rot])
|
||||
|
||||
if "nocrop" in vn.flags:
|
||||
if fmt.endswith("f"):
|
||||
im.thumbnail(res, resample=Image.LANCZOS)
|
||||
else:
|
||||
iw, ih = im.size
|
||||
|
@ -351,10 +353,10 @@ class ThumbSrv(object):
|
|||
|
||||
return im
|
||||
|
||||
def conv_pil(self, abspath: str, tpath: str, vn: VFS) -> None:
|
||||
def conv_pil(self, abspath: str, tpath: str, fmt: str, vn: VFS) -> None:
|
||||
with Image.open(fsenc(abspath)) as im:
|
||||
try:
|
||||
im = self.fancy_pillow(im, vn)
|
||||
im = self.fancy_pillow(im, fmt, vn)
|
||||
except Exception as ex:
|
||||
self.log("fancy_pillow {}".format(ex), "90")
|
||||
im.thumbnail(self.getres(vn))
|
||||
|
@ -380,9 +382,9 @@ class ThumbSrv(object):
|
|||
|
||||
im.save(tpath, **args)
|
||||
|
||||
def conv_vips(self, abspath: str, tpath: str, vn: VFS) -> None:
|
||||
def conv_vips(self, abspath: str, tpath: str, fmt: str, vn: VFS) -> None:
|
||||
crops = ["centre", "none"]
|
||||
if "nocrop" in vn.flags:
|
||||
if fmt.endswith("f"):
|
||||
crops = ["none"]
|
||||
|
||||
w, h = self.getres(vn)
|
||||
|
@ -399,7 +401,7 @@ class ThumbSrv(object):
|
|||
|
||||
img.write_to_file(tpath, Q=40)
|
||||
|
||||
def conv_ffmpeg(self, abspath: str, tpath: str, vn: VFS) -> None:
|
||||
def conv_ffmpeg(self, abspath: str, tpath: str, fmt: str, vn: VFS) -> None:
|
||||
ret, _ = ffprobe(abspath, int(vn.flags["convt"] / 2))
|
||||
if not ret:
|
||||
return
|
||||
|
@ -412,7 +414,7 @@ class ThumbSrv(object):
|
|||
seek = [b"-ss", "{:.0f}".format(dur / 3).encode("utf-8")]
|
||||
|
||||
scale = "scale={0}:{1}:force_original_aspect_ratio="
|
||||
if "nocrop" in vn.flags:
|
||||
if fmt.endswith("f"):
|
||||
scale += "decrease,setsar=1:1"
|
||||
else:
|
||||
scale += "increase,crop={0}:{1},setsar=1:1"
|
||||
|
@ -497,7 +499,7 @@ class ThumbSrv(object):
|
|||
self.log(t + txt, c=c)
|
||||
raise sp.CalledProcessError(ret, (cmd[0], b"...", cmd[-1]))
|
||||
|
||||
def conv_waves(self, abspath: str, tpath: str, vn: VFS) -> None:
|
||||
def conv_waves(self, abspath: str, tpath: str, fmt: str, vn: VFS) -> None:
|
||||
ret, _ = ffprobe(abspath, int(vn.flags["convt"] / 2))
|
||||
if "ac" not in ret:
|
||||
raise Exception("not audio")
|
||||
|
@ -525,7 +527,7 @@ class ThumbSrv(object):
|
|||
cmd += [fsenc(tpath)]
|
||||
self._run_ff(cmd, vn)
|
||||
|
||||
def conv_spec(self, abspath: str, tpath: str, vn: VFS) -> None:
|
||||
def conv_spec(self, abspath: str, tpath: str, fmt: str, vn: VFS) -> None:
|
||||
ret, _ = ffprobe(abspath, int(vn.flags["convt"] / 2))
|
||||
if "ac" not in ret:
|
||||
raise Exception("not audio")
|
||||
|
@ -568,7 +570,7 @@ class ThumbSrv(object):
|
|||
cmd += [fsenc(tpath)]
|
||||
self._run_ff(cmd, vn)
|
||||
|
||||
def conv_opus(self, abspath: str, tpath: str, vn: VFS) -> None:
|
||||
def conv_opus(self, abspath: str, tpath: str, fmt: str, vn: VFS) -> None:
|
||||
if self.args.no_acode:
|
||||
raise Exception("disabled in server config")
|
||||
|
||||
|
|
|
@ -333,6 +333,7 @@ var Ls = {
|
|||
"tvt_edit": "open file in text editor$NHotkey: E\">✏️ edit",
|
||||
|
||||
"gt_msel": "enable file selection; ctrl-click a file to override$N$N<em>when active: doubleclick a file / folder to open it</em>$N$NHotkey: S\">multiselect",
|
||||
"gt_full": "show uncropped thumbnails\">full",
|
||||
"gt_zoom": "zoom",
|
||||
"gt_chop": "chop",
|
||||
"gt_sort": "sort by",
|
||||
|
@ -804,6 +805,7 @@ var Ls = {
|
|||
"tvt_edit": "redigér filen$NSnarvei: E\">✏️ endre",
|
||||
|
||||
"gt_msel": "markér filer istedenfor å åpne dem; ctrl-klikk filer for å overstyre$N$N<em>når aktiv: dobbelklikk en fil / mappe for å åpne</em>$N$NSnarvei: S\">markering",
|
||||
"gt_full": "ikke beskjær bildene\">full",
|
||||
"gt_zoom": "zoom",
|
||||
"gt_chop": "trim",
|
||||
"gt_sort": "sorter",
|
||||
|
@ -4265,7 +4267,8 @@ var thegrid = (function () {
|
|||
gfiles.style.display = 'none';
|
||||
gfiles.innerHTML = (
|
||||
'<div id="ghead" class="ghead">' +
|
||||
'<a href="#" class="tgl btn" id="gridsel" tt="' + L.gt_msel + '</a> <span>' + L.gt_zoom + ': ' +
|
||||
'<a href="#" class="tgl btn" id="gridsel" tt="' + L.gt_msel + '</a> ' +
|
||||
'<a href="#" class="tgl btn" id="gridfull" tt="' + L.gt_full + '</a> <span>' + L.gt_zoom + ': ' +
|
||||
'<a href="#" class="btn" z="-1.2" tt="Hotkey: shift-A">–</a> ' +
|
||||
'<a href="#" class="btn" z="1.2" tt="Hotkey: shift-D">+</a></span> <span>' + L.gt_chop + ': ' +
|
||||
'<a href="#" class="btn" l="-1" tt="' + L.gt_c1 + '">–</a> ' +
|
||||
|
@ -4515,6 +4518,9 @@ var thegrid = (function () {
|
|||
if (!r.dirty)
|
||||
return r.loadsel();
|
||||
|
||||
if (dfull != r.full && !sread('gridfull'))
|
||||
bcfg_upd_ui('gridfull', r.full = dfull);
|
||||
|
||||
var html = [],
|
||||
svgs = new Set(),
|
||||
max_svgs = CHROME ? 500 : 5000,
|
||||
|
@ -4532,6 +4538,8 @@ var thegrid = (function () {
|
|||
|
||||
if (r.thumbs) {
|
||||
ihref += '?th=' + (have_webp ? 'w' : 'j');
|
||||
if (r.full)
|
||||
ihref += 'f'
|
||||
if (href == "#")
|
||||
ihref = SR + '/.cpr/ico/⏏️';
|
||||
}
|
||||
|
@ -4618,6 +4626,7 @@ var thegrid = (function () {
|
|||
};
|
||||
|
||||
bcfg_bind(r, 'thumbs', 'thumbs', true, r.setdirty);
|
||||
bcfg_bind(r, 'full', 'gridfull', false, r.setdirty);
|
||||
bcfg_bind(r, 'sel', 'gridsel', false, r.loadsel);
|
||||
bcfg_bind(r, 'en', 'griden', dgrid, function (v) {
|
||||
v ? loadgrid() : r.setvis(true);
|
||||
|
@ -5823,6 +5832,7 @@ var treectl = (function () {
|
|||
res.files[a].tags = {};
|
||||
|
||||
read_dsort(res.dsort);
|
||||
dfull = res.dfull;
|
||||
|
||||
srvinf = res.srvinf;
|
||||
try {
|
||||
|
|
Loading…
Reference in a new issue