mirror of
https://github.com/9001/copyparty.git
synced 2025-08-17 09:02:15 -06:00
add RAW thumbnailer via rawpy
This commit is contained in:
parent
afac62f8f6
commit
762a829a3a
|
@ -266,6 +266,7 @@ also see [comparison to similar software](./docs/versus.md)
|
||||||
* ☑ realtime streaming of growing files (logfiles and such)
|
* ☑ realtime streaming of growing files (logfiles and such)
|
||||||
* ☑ [thumbnails](#thumbnails)
|
* ☑ [thumbnails](#thumbnails)
|
||||||
* ☑ ...of images using Pillow, pyvips, or FFmpeg
|
* ☑ ...of images using Pillow, pyvips, or FFmpeg
|
||||||
|
* ☑ ...of RAW files using rawpy
|
||||||
* ☑ ...of videos using FFmpeg
|
* ☑ ...of videos using FFmpeg
|
||||||
* ☑ ...of audio (spectrograms) using FFmpeg
|
* ☑ ...of audio (spectrograms) using FFmpeg
|
||||||
* ☑ cache eviction (max-age; maybe max-size eventually)
|
* ☑ cache eviction (max-age; maybe max-size eventually)
|
||||||
|
@ -2795,6 +2796,7 @@ enable [thumbnails](#thumbnails) of...
|
||||||
* **HEIF pictures:** `pyvips` or `ffmpeg` or `pyheif-pillow-opener` (requires Linux or a C compiler)
|
* **HEIF pictures:** `pyvips` or `ffmpeg` or `pyheif-pillow-opener` (requires Linux or a C compiler)
|
||||||
* **AVIF pictures:** `pyvips` or `ffmpeg` or `pillow-avif-plugin` or pillow v11.3+
|
* **AVIF pictures:** `pyvips` or `ffmpeg` or `pillow-avif-plugin` or pillow v11.3+
|
||||||
* **JPEG XL pictures:** `pyvips` or `ffmpeg`
|
* **JPEG XL pictures:** `pyvips` or `ffmpeg`
|
||||||
|
* **RAW images:** `rawpy`, plus one of `pyvips` or `Pillow` (for some formats)
|
||||||
|
|
||||||
enable sending [zeromq messages](#zeromq) from event-hooks: `pyzmq`
|
enable sending [zeromq messages](#zeromq) from event-hooks: `pyzmq`
|
||||||
|
|
||||||
|
@ -2828,6 +2830,7 @@ set any of the following environment variables to disable its associated optiona
|
||||||
| `PRTY_NO_PIL_HEIF` | disable 3rd-party Pillow plugin for [HEIF support](https://pypi.org/project/pyheif-pillow-opener/) |
|
| `PRTY_NO_PIL_HEIF` | disable 3rd-party Pillow plugin for [HEIF support](https://pypi.org/project/pyheif-pillow-opener/) |
|
||||||
| `PRTY_NO_PIL_WEBP` | disable use of native webp support in Pillow |
|
| `PRTY_NO_PIL_WEBP` | disable use of native webp support in Pillow |
|
||||||
| `PRTY_NO_PSUTIL` | do not use [psutil](https://pypi.org/project/psutil/) for reaping stuck hooks and plugins on Windows |
|
| `PRTY_NO_PSUTIL` | do not use [psutil](https://pypi.org/project/psutil/) for reaping stuck hooks and plugins on Windows |
|
||||||
|
| `PRTY_NO_RAW` | disable all [rawpy](https://pypi.org/project/rawpy/)-based thumbnail support for RAW images |
|
||||||
| `PRTY_NO_VIPS` | disable all [libvips](https://pypi.org/project/pyvips/)-based thumbnail support; will fallback to Pillow or ffmpeg |
|
| `PRTY_NO_VIPS` | disable all [libvips](https://pypi.org/project/pyvips/)-based thumbnail support; will fallback to Pillow or ffmpeg |
|
||||||
|
|
||||||
example: `PRTY_NO_PIL=1 python3 copyparty-sfx.py`
|
example: `PRTY_NO_PIL=1 python3 copyparty-sfx.py`
|
||||||
|
|
|
@ -1431,7 +1431,7 @@ def add_thumbnail(ap):
|
||||||
ap2.add_argument("--th-ram-max", metavar="GB", type=float, default=th_ram, help="max memory usage (GiB) permitted by thumbnailer; not very accurate")
|
ap2.add_argument("--th-ram-max", metavar="GB", type=float, default=th_ram, help="max memory usage (GiB) permitted by thumbnailer; not very accurate")
|
||||||
ap2.add_argument("--th-crop", metavar="TXT", type=u, default="y", help="crop thumbnails to 4:3 or keep dynamic height; client can override in UI unless force. [\033[32my\033[0m]=crop, [\033[32mn\033[0m]=nocrop, [\033[32mfy\033[0m]=force-y, [\033[32mfn\033[0m]=force-n (volflag=crop)")
|
ap2.add_argument("--th-crop", metavar="TXT", type=u, default="y", help="crop thumbnails to 4:3 or keep dynamic height; client can override in UI unless force. [\033[32my\033[0m]=crop, [\033[32mn\033[0m]=nocrop, [\033[32mfy\033[0m]=force-y, [\033[32mfn\033[0m]=force-n (volflag=crop)")
|
||||||
ap2.add_argument("--th-x3", metavar="TXT", type=u, default="n", help="show thumbs at 3x resolution; client can override in UI unless force. [\033[32my\033[0m]=yes, [\033[32mn\033[0m]=no, [\033[32mfy\033[0m]=force-yes, [\033[32mfn\033[0m]=force-no (volflag=th3x)")
|
ap2.add_argument("--th-x3", metavar="TXT", type=u, default="n", help="show thumbs at 3x resolution; client can override in UI unless force. [\033[32my\033[0m]=yes, [\033[32mn\033[0m]=no, [\033[32mfy\033[0m]=force-yes, [\033[32mfn\033[0m]=force-no (volflag=th3x)")
|
||||||
ap2.add_argument("--th-dec", metavar="LIBS", default="vips,pil,ff", help="image decoders, in order of preference")
|
ap2.add_argument("--th-dec", metavar="LIBS", default="vips,pil,raw,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-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 output for video thumbs (avoids issues on some FFmpeg builds)")
|
ap2.add_argument("--th-ff-jpg", action="store_true", help="force jpg output for video thumbs (avoids issues on some FFmpeg builds)")
|
||||||
|
@ -1442,9 +1442,11 @@ def add_thumbnail(ap):
|
||||||
ap2.add_argument("--th-covers", metavar="N,N", type=u, default="folder.png,folder.jpg,cover.png,cover.jpg", help="folder thumbnails to stat/look for; enabling \033[33m-e2d\033[0m will make these case-insensitive, and try them as dotfiles (.folder.jpg), and also automatically select thumbnails for all folders that contain pics, even if none match this pattern")
|
ap2.add_argument("--th-covers", metavar="N,N", type=u, default="folder.png,folder.jpg,cover.png,cover.jpg", help="folder thumbnails to stat/look for; enabling \033[33m-e2d\033[0m will make these case-insensitive, and try them as dotfiles (.folder.jpg), and also automatically select thumbnails for all folders that contain pics, even if none match this pattern")
|
||||||
# https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html
|
# https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html
|
||||||
# https://github.com/libvips/libvips
|
# https://github.com/libvips/libvips
|
||||||
|
# https://stackoverflow.com/a/47612661
|
||||||
# ffmpeg -hide_banner -demuxers | awk '/^ D /{print$2}' | while IFS= read -r x; do ffmpeg -hide_banner -h demuxer=$x; done | grep -E '^Demuxer |extensions:'
|
# ffmpeg -hide_banner -demuxers | awk '/^ D /{print$2}' | while IFS= read -r x; do ffmpeg -hide_banner -h demuxer=$x; done | grep -E '^Demuxer |extensions:'
|
||||||
ap2.add_argument("--th-r-pil", metavar="T,T", type=u, default="avif,avifs,blp,bmp,cbz,dcx,dds,dib,emf,eps,epub,fits,flc,fli,fpx,gif,heic,heics,heif,heifs,icns,ico,im,j2p,j2k,jp2,jpeg,jpg,jpx,pbm,pcx,pgm,png,pnm,ppm,psd,qoi,sgi,spi,tga,tif,tiff,webp,wmf,xbm,xpm", help="image formats to decode using pillow")
|
ap2.add_argument("--th-r-pil", metavar="T,T", type=u, default="avif,avifs,blp,bmp,cbz,dcx,dds,dib,emf,eps,epub,fits,flc,fli,fpx,gif,heic,heics,heif,heifs,icns,ico,im,j2p,j2k,jp2,jpeg,jpg,jpx,pbm,pcx,pgm,png,pnm,ppm,psd,qoi,sgi,spi,tga,tif,tiff,webp,wmf,xbm,xpm", help="image formats to decode using pillow")
|
||||||
ap2.add_argument("--th-r-vips", metavar="T,T", type=u, default="avif,exr,fit,fits,fts,gif,hdr,heic,jp2,jpeg,jpg,jpx,jxl,nii,pfm,pgm,png,ppm,svg,tif,tiff,webp", help="image formats to decode using pyvips")
|
ap2.add_argument("--th-r-vips", metavar="T,T", type=u, default="avif,exr,fit,fits,fts,gif,hdr,heic,jp2,jpeg,jpg,jpx,jxl,nii,pfm,pgm,png,ppm,svg,tif,tiff,webp", help="image formats to decode using pyvips")
|
||||||
|
ap2.add_argument("--th-r-raw", metavar="T,T", type=u, default="arw,cr2,crw,dcr,dng,erf,k25,kdc,mrw,nef,orf,pef,raf,raw,sr2,srf,x3f", help="image formats to decode using rawpy")
|
||||||
ap2.add_argument("--th-r-ffi", metavar="T,T", type=u, default="apng,avif,avifs,bmp,cbz,dds,dib,epub,fit,fits,fts,gif,hdr,heic,heics,heif,heifs,icns,ico,jp2,jpeg,jpg,jpx,jxl,pbm,pcx,pfm,pgm,png,pnm,ppm,psd,qoi,sgi,tga,tif,tiff,webp,xbm,xpm", help="image formats to decode using ffmpeg")
|
ap2.add_argument("--th-r-ffi", metavar="T,T", type=u, default="apng,avif,avifs,bmp,cbz,dds,dib,epub,fit,fits,fts,gif,hdr,heic,heics,heif,heifs,icns,ico,jp2,jpeg,jpg,jpx,jxl,pbm,pcx,pfm,pgm,png,pnm,ppm,psd,qoi,sgi,tga,tif,tiff,webp,xbm,xpm", help="image formats to decode using ffmpeg")
|
||||||
ap2.add_argument("--th-r-ffv", metavar="T,T", type=u, default="3gp,asf,av1,avc,avi,flv,h264,h265,hevc,m4v,mjpeg,mjpg,mkv,mov,mp4,mpeg,mpeg2,mpegts,mpg,mpg2,mts,nut,ogm,ogv,rm,ts,vob,webm,wmv", help="video formats to decode using ffmpeg")
|
ap2.add_argument("--th-r-ffv", metavar="T,T", type=u, default="3gp,asf,av1,avc,avi,flv,h264,h265,hevc,m4v,mjpeg,mjpg,mkv,mov,mp4,mpeg,mpeg2,mpegts,mpg,mpg2,mts,nut,ogm,ogv,rm,ts,vob,webm,wmv", help="video formats to decode using ffmpeg")
|
||||||
ap2.add_argument("--th-r-ffa", metavar="T,T", type=u, default="aac,ac3,aif,aiff,alac,alaw,amr,apac,ape,au,bonk,dfpwm,dts,flac,gsm,ilbc,it,itgz,itxz,itz,m4a,mdgz,mdxz,mdz,mo3,mod,mp2,mp3,mpc,mptm,mt2,mulaw,oga,ogg,okt,opus,ra,s3m,s3gz,s3xz,s3z,tak,tta,ulaw,wav,wma,wv,xm,xmgz,xmxz,xmz,xpk", help="audio formats to decode using ffmpeg")
|
ap2.add_argument("--th-r-ffa", metavar="T,T", type=u, default="aac,ac3,aif,aiff,alac,alaw,amr,apac,ape,au,bonk,dfpwm,dts,flac,gsm,ilbc,it,itgz,itxz,itz,m4a,mdgz,mdxz,mdz,mo3,mod,mp2,mp3,mpc,mptm,mt2,mulaw,oga,ogg,okt,opus,ra,s3m,s3gz,s3xz,s3z,tak,tta,ulaw,wav,wma,wv,xm,xmgz,xmxz,xmz,xpk", help="audio formats to decode using ffmpeg")
|
||||||
|
|
|
@ -40,6 +40,7 @@ from .th_srv import (
|
||||||
HAVE_PIL,
|
HAVE_PIL,
|
||||||
HAVE_VIPS,
|
HAVE_VIPS,
|
||||||
HAVE_WEBP,
|
HAVE_WEBP,
|
||||||
|
HAVE_RAW,
|
||||||
ThumbSrv,
|
ThumbSrv,
|
||||||
)
|
)
|
||||||
from .up2k import Up2k
|
from .up2k import Up2k
|
||||||
|
@ -316,11 +317,14 @@ class SvcHub(object):
|
||||||
|
|
||||||
self._feature_test()
|
self._feature_test()
|
||||||
|
|
||||||
|
self.log("initial th_dec", self.args.th_dec)
|
||||||
decs = {k: 1 for k in self.args.th_dec.split(",")}
|
decs = {k: 1 for k in self.args.th_dec.split(",")}
|
||||||
if not HAVE_VIPS:
|
if not HAVE_VIPS:
|
||||||
decs.pop("vips", None)
|
decs.pop("vips", None)
|
||||||
if not HAVE_PIL:
|
if not HAVE_PIL:
|
||||||
decs.pop("pil", None)
|
decs.pop("pil", None)
|
||||||
|
if not HAVE_RAW:
|
||||||
|
decs.pop("raw", None)
|
||||||
if not HAVE_FFMPEG or not HAVE_FFPROBE:
|
if not HAVE_FFMPEG or not HAVE_FFPROBE:
|
||||||
decs.pop("ff", None)
|
decs.pop("ff", None)
|
||||||
|
|
||||||
|
@ -343,7 +347,7 @@ class SvcHub(object):
|
||||||
self.thumbsrv = ThumbSrv(self)
|
self.thumbsrv = ThumbSrv(self)
|
||||||
else:
|
else:
|
||||||
want_ff = True
|
want_ff = True
|
||||||
msg = "need either Pillow, pyvips, or FFmpeg to create thumbnails; for example:\n{0}{1} -m pip install --user Pillow\n{0}{1} -m pip install --user pyvips\n{0}apt install ffmpeg"
|
msg = "need either Pillow, pyvips, rawpy, or FFmpeg to create thumbnails; for example:\n{0}{1} -m pip install --user Pillow\n{0}{1} -m pip install --user pyvips\n{0}apt install ffmpeg"
|
||||||
msg = msg.format(" " * 37, os.path.basename(pybin))
|
msg = msg.format(" " * 37, os.path.basename(pybin))
|
||||||
if EXE:
|
if EXE:
|
||||||
msg = "copyparty.exe cannot use Pillow or pyvips; need ffprobe.exe and ffmpeg.exe to create thumbnails"
|
msg = "copyparty.exe cannot use Pillow or pyvips; need ffprobe.exe and ffmpeg.exe to create thumbnails"
|
||||||
|
@ -811,6 +815,7 @@ class SvcHub(object):
|
||||||
(HAVE_ZMQ, "pyzmq", "send zeromq messages from event-hooks"),
|
(HAVE_ZMQ, "pyzmq", "send zeromq messages from event-hooks"),
|
||||||
(HAVE_HEIF, "pillow-heif", "read .heif images with pillow (rarely useful)"),
|
(HAVE_HEIF, "pillow-heif", "read .heif images with pillow (rarely useful)"),
|
||||||
(HAVE_AVIF, "pillow-avif", "read .avif images with pillow (rarely useful)"),
|
(HAVE_AVIF, "pillow-avif", "read .avif images with pillow (rarely useful)"),
|
||||||
|
(HAVE_RAW, "rawpy", "read RAW images"),
|
||||||
]
|
]
|
||||||
if ANYWIN:
|
if ANYWIN:
|
||||||
to_check += [
|
to_check += [
|
||||||
|
|
|
@ -36,11 +36,15 @@ class ThumbCli(object):
|
||||||
if not c:
|
if not c:
|
||||||
raise Exception()
|
raise Exception()
|
||||||
except:
|
except:
|
||||||
c = {k: set() for k in ["thumbable", "pil", "vips", "ffi", "ffv", "ffa"]}
|
c = {
|
||||||
|
k: set()
|
||||||
|
for k in ["thumbable", "pil", "vips", "raw", "ffi", "ffv", "ffa"]
|
||||||
|
}
|
||||||
|
|
||||||
self.thumbable = c["thumbable"]
|
self.thumbable = c["thumbable"]
|
||||||
self.fmt_pil = c["pil"]
|
self.fmt_pil = c["pil"]
|
||||||
self.fmt_vips = c["vips"]
|
self.fmt_vips = c["vips"]
|
||||||
|
self.fmt_raw = c["raw"]
|
||||||
self.fmt_ffi = c["ffi"]
|
self.fmt_ffi = c["ffi"]
|
||||||
self.fmt_ffv = c["ffv"]
|
self.fmt_ffv = c["ffv"]
|
||||||
self.fmt_ffa = c["ffa"]
|
self.fmt_ffa = c["ffa"]
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
from __future__ import print_function, unicode_literals
|
from __future__ import print_function, unicode_literals
|
||||||
|
|
||||||
import hashlib
|
import hashlib
|
||||||
|
import io
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
@ -121,6 +122,17 @@ try:
|
||||||
except:
|
except:
|
||||||
HAVE_VIPS = False
|
HAVE_VIPS = False
|
||||||
|
|
||||||
|
try:
|
||||||
|
if os.environ.get("PRTY_NO_RAW"):
|
||||||
|
raise Exception()
|
||||||
|
|
||||||
|
HAVE_RAW = True
|
||||||
|
import rawpy
|
||||||
|
|
||||||
|
logging.getLogger("rawpy").setLevel(logging.WARNING)
|
||||||
|
except:
|
||||||
|
HAVE_RAW = False
|
||||||
|
|
||||||
|
|
||||||
th_dir_cache = {}
|
th_dir_cache = {}
|
||||||
|
|
||||||
|
@ -205,11 +217,19 @@ class ThumbSrv(object):
|
||||||
if self.args.th_clean:
|
if self.args.th_clean:
|
||||||
Daemon(self.cleaner, "thumb.cln")
|
Daemon(self.cleaner, "thumb.cln")
|
||||||
|
|
||||||
self.fmt_pil, self.fmt_vips, self.fmt_ffi, self.fmt_ffv, self.fmt_ffa = [
|
(
|
||||||
|
self.fmt_pil,
|
||||||
|
self.fmt_vips,
|
||||||
|
self.fmt_raw,
|
||||||
|
self.fmt_ffi,
|
||||||
|
self.fmt_ffv,
|
||||||
|
self.fmt_ffa,
|
||||||
|
) = [
|
||||||
set(y.split(","))
|
set(y.split(","))
|
||||||
for y in [
|
for y in [
|
||||||
self.args.th_r_pil,
|
self.args.th_r_pil,
|
||||||
self.args.th_r_vips,
|
self.args.th_r_vips,
|
||||||
|
self.args.th_r_raw,
|
||||||
self.args.th_r_ffi,
|
self.args.th_r_ffi,
|
||||||
self.args.th_r_ffv,
|
self.args.th_r_ffv,
|
||||||
self.args.th_r_ffa,
|
self.args.th_r_ffa,
|
||||||
|
@ -232,6 +252,9 @@ class ThumbSrv(object):
|
||||||
if "vips" in self.args.th_dec:
|
if "vips" in self.args.th_dec:
|
||||||
self.thumbable |= self.fmt_vips
|
self.thumbable |= self.fmt_vips
|
||||||
|
|
||||||
|
if "raw" in self.args.th_dec:
|
||||||
|
self.thumbable |= self.fmt_raw
|
||||||
|
|
||||||
if "ff" in self.args.th_dec:
|
if "ff" in self.args.th_dec:
|
||||||
for zss in [self.fmt_ffi, self.fmt_ffv, self.fmt_ffa]:
|
for zss in [self.fmt_ffi, self.fmt_ffv, self.fmt_ffa]:
|
||||||
self.thumbable |= zss
|
self.thumbable |= zss
|
||||||
|
@ -313,6 +336,7 @@ class ThumbSrv(object):
|
||||||
"thumbable": self.thumbable,
|
"thumbable": self.thumbable,
|
||||||
"pil": self.fmt_pil,
|
"pil": self.fmt_pil,
|
||||||
"vips": self.fmt_vips,
|
"vips": self.fmt_vips,
|
||||||
|
"raw": self.fmt_raw,
|
||||||
"ffi": self.fmt_ffi,
|
"ffi": self.fmt_ffi,
|
||||||
"ffv": self.fmt_ffv,
|
"ffv": self.fmt_ffv,
|
||||||
"ffa": self.fmt_ffa,
|
"ffa": self.fmt_ffa,
|
||||||
|
@ -368,6 +392,8 @@ class ThumbSrv(object):
|
||||||
funs.append(self.conv_pil)
|
funs.append(self.conv_pil)
|
||||||
elif lib == "vips" and ext in self.fmt_vips:
|
elif lib == "vips" and ext in self.fmt_vips:
|
||||||
funs.append(self.conv_vips)
|
funs.append(self.conv_vips)
|
||||||
|
elif lib == "raw" and ext in self.fmt_raw:
|
||||||
|
funs.append(self.conv_raw)
|
||||||
elif can_au and (want_png or want_au):
|
elif can_au and (want_png or want_au):
|
||||||
if want_opus:
|
if want_opus:
|
||||||
funs.append(self.conv_opus)
|
funs.append(self.conv_opus)
|
||||||
|
@ -480,9 +506,7 @@ class ThumbSrv(object):
|
||||||
|
|
||||||
return im
|
return im
|
||||||
|
|
||||||
def conv_pil(self, abspath: str, tpath: str, fmt: str, vn: VFS) -> None:
|
def conv_image_pil(self, im: "Image.Image", tpath: str, fmt: str, vn: VFS) -> None:
|
||||||
self.wait4ram(0.2, tpath)
|
|
||||||
with Image.open(fsenc(abspath)) as im:
|
|
||||||
try:
|
try:
|
||||||
im = self.fancy_pillow(im, fmt, vn)
|
im = self.fancy_pillow(im, fmt, vn)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
|
@ -510,6 +534,11 @@ class ThumbSrv(object):
|
||||||
|
|
||||||
im.save(tpath, **args)
|
im.save(tpath, **args)
|
||||||
|
|
||||||
|
def conv_pil(self, abspath: str, tpath: str, fmt: str, vn: VFS) -> None:
|
||||||
|
self.wait4ram(0.2, tpath)
|
||||||
|
with Image.open(fsenc(abspath)) as im:
|
||||||
|
self.conv_image_pil(im, tpath, fmt, vn)
|
||||||
|
|
||||||
def conv_vips(self, abspath: str, tpath: str, fmt: str, vn: VFS) -> None:
|
def conv_vips(self, abspath: str, tpath: str, fmt: str, vn: VFS) -> None:
|
||||||
self.wait4ram(0.2, tpath)
|
self.wait4ram(0.2, tpath)
|
||||||
crops = ["centre", "none"]
|
crops = ["centre", "none"]
|
||||||
|
@ -531,6 +560,50 @@ class ThumbSrv(object):
|
||||||
assert img # type: ignore # !rm
|
assert img # type: ignore # !rm
|
||||||
img.write_to_file(tpath, Q=40)
|
img.write_to_file(tpath, Q=40)
|
||||||
|
|
||||||
|
def conv_raw(self, abspath: str, tpath: str, fmt: str, vn: VFS) -> None:
|
||||||
|
self.wait4ram(0.2, tpath)
|
||||||
|
with rawpy.imread(abspath) as raw:
|
||||||
|
thumb = raw.extract_thumb()
|
||||||
|
if thumb.format == rawpy.ThumbFormat.JPEG and tpath.endswith(".jpg"):
|
||||||
|
# if we have a jpg thumbnail and no webp output is available,
|
||||||
|
# just write the jpg directly (it'll be the wrong size, but it's fast)
|
||||||
|
with open(tpath, "wb") as f:
|
||||||
|
f.write(thumb.data)
|
||||||
|
if HAVE_VIPS:
|
||||||
|
crops = ["centre", "none"]
|
||||||
|
if "f" in fmt:
|
||||||
|
crops = ["none"]
|
||||||
|
w, h = self.getres(vn, fmt)
|
||||||
|
kw = {"height": h, "size": "down", "intent": "relative"}
|
||||||
|
|
||||||
|
for c in crops:
|
||||||
|
try:
|
||||||
|
kw["crop"] = c
|
||||||
|
if thumb.format == rawpy.ThumbFormat.BITMAP:
|
||||||
|
img = pyvips.Image.new_from_array(
|
||||||
|
thumb.data, interpretation="rgb"
|
||||||
|
)
|
||||||
|
img = img.thumbnail_image(w, **kw)
|
||||||
|
else:
|
||||||
|
img = pyvips.Image.thumbnail_buffer(thumb.data, w, **kw)
|
||||||
|
break
|
||||||
|
except:
|
||||||
|
if c == crops[-1]:
|
||||||
|
raise
|
||||||
|
|
||||||
|
assert img # type: ignore # !rm
|
||||||
|
img.write_to_file(tpath, Q=40)
|
||||||
|
elif HAVE_PIL:
|
||||||
|
if thumb.format == rawpy.ThumbFormat.BITMAP:
|
||||||
|
im = Image.fromarray(thumb.data, "RGB")
|
||||||
|
else:
|
||||||
|
im = Image.open(io.BytesIO(thumb.data))
|
||||||
|
self.conv_image_pil(im, tpath, fmt, vn)
|
||||||
|
else:
|
||||||
|
raise Exception(
|
||||||
|
"either pil or vips is needed to process embedded bitmap thumbnails in raw files"
|
||||||
|
)
|
||||||
|
|
||||||
def conv_ffmpeg(self, abspath: str, tpath: str, fmt: str, vn: VFS) -> None:
|
def conv_ffmpeg(self, abspath: str, tpath: str, fmt: str, vn: VFS) -> None:
|
||||||
self.wait4ram(0.2, tpath)
|
self.wait4ram(0.2, tpath)
|
||||||
ret, _ = ffprobe(abspath, int(vn.flags["convt"] / 2))
|
ret, _ = ffprobe(abspath, int(vn.flags["convt"] / 2))
|
||||||
|
|
|
@ -1083,7 +1083,7 @@
|
||||||
th-x3: n # default
|
th-x3: n # default
|
||||||
|
|
||||||
# image decoders, in order of preference
|
# image decoders, in order of preference
|
||||||
th-dec: vips,pil,ff # default
|
th-dec: vips,pil,raw,ff # default
|
||||||
|
|
||||||
# disable jpg output
|
# disable jpg output
|
||||||
th-no-jpg
|
th-no-jpg
|
||||||
|
@ -1115,6 +1115,9 @@
|
||||||
# image formats to decode using pyvips
|
# image formats to decode using pyvips
|
||||||
th-r-vips: a,very,long,list,of,file,extensions # hint
|
th-r-vips: a,very,long,list,of,file,extensions # hint
|
||||||
|
|
||||||
|
# image formats to decode using rawpy
|
||||||
|
th-r-raw: a,very,long,list,of,file,extensions # hint
|
||||||
|
|
||||||
# image formats to decode using ffmpeg
|
# image formats to decode using ffmpeg
|
||||||
th-r-ffi: a,very,long,list,of,file,extensions # hint
|
th-r-ffi: a,very,long,list,of,file,extensions # hint
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue