Convert Pixiv ugoira zips to animated GIFs, add filetype to info panel
- Detect .zip files (Pixiv ugoira) and convert frames to animated GIF - Cache the converted GIF so subsequent loads are instant - Add filetype field to the info panel - Add ZIP to valid media magic bytes
This commit is contained in:
parent
495eb4c64d
commit
526606c7c5
@ -3,9 +3,11 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
import httpx
|
||||
from PIL import Image
|
||||
|
||||
from .config import cache_dir, thumbnails_dir, USER_AGENT
|
||||
|
||||
@ -21,6 +23,7 @@ _IMAGE_MAGIC = {
|
||||
b'RIFF': True, # WebP
|
||||
b'\x00\x00\x00': True, # MP4/MOV
|
||||
b'\x1aE\xdf\xa3': True, # WebM/MKV
|
||||
b'PK\x03\x04': True, # ZIP (ugoira)
|
||||
}
|
||||
|
||||
|
||||
@ -48,6 +51,27 @@ def _ext_from_url(url: str) -> str:
|
||||
return ".jpg"
|
||||
|
||||
|
||||
def _convert_ugoira_to_gif(zip_path: Path) -> Path:
|
||||
"""Convert a Pixiv ugoira zip (numbered JPEG/PNG frames) to an animated GIF."""
|
||||
gif_path = zip_path.with_suffix(".gif")
|
||||
if gif_path.exists():
|
||||
return gif_path
|
||||
with zipfile.ZipFile(zip_path, "r") as zf:
|
||||
names = sorted(zf.namelist())
|
||||
frames = []
|
||||
for name in names:
|
||||
data = zf.read(name)
|
||||
frames.append(Image.open(__import__("io").BytesIO(data)).convert("RGBA"))
|
||||
if not frames:
|
||||
raise ValueError("Zip contains no image frames")
|
||||
frames[0].save(
|
||||
gif_path, save_all=True, append_images=frames[1:],
|
||||
duration=80, loop=0, disposal=2,
|
||||
)
|
||||
zip_path.unlink()
|
||||
return gif_path
|
||||
|
||||
|
||||
async def download_image(
|
||||
url: str,
|
||||
client: httpx.AsyncClient | None = None,
|
||||
@ -62,6 +86,12 @@ async def download_image(
|
||||
filename = _url_hash(url) + _ext_from_url(url)
|
||||
local = dest_dir / filename
|
||||
|
||||
# Check if a ugoira zip was already converted to gif
|
||||
if local.suffix.lower() == ".zip":
|
||||
gif_path = local.with_suffix(".gif")
|
||||
if gif_path.exists():
|
||||
return gif_path
|
||||
|
||||
# Validate cached file isn't corrupt (e.g. HTML error page saved as image)
|
||||
if local.exists():
|
||||
if _is_valid_media(local):
|
||||
@ -119,6 +149,10 @@ async def download_image(
|
||||
if not _is_valid_media(local):
|
||||
local.unlink()
|
||||
raise ValueError("Downloaded file is not valid media")
|
||||
|
||||
# Convert ugoira zip to animated GIF
|
||||
if local.suffix.lower() == ".zip" and zipfile.is_zipfile(local):
|
||||
local = _convert_ugoira_to_gif(local)
|
||||
finally:
|
||||
if own_client:
|
||||
await client.aclose()
|
||||
|
||||
@ -123,10 +123,12 @@ class InfoPanel(QWidget):
|
||||
|
||||
def set_post(self, post: Post) -> None:
|
||||
self._title.setText(f"Post #{post.id}")
|
||||
filetype = Path(post.file_url.split("?")[0]).suffix.lstrip(".").upper() if post.file_url else "unknown"
|
||||
self._details.setText(
|
||||
f"Size: {post.width}x{post.height}\n"
|
||||
f"Score: {post.score}\n"
|
||||
f"Rating: {post.rating or 'unknown'}\n"
|
||||
f"Filetype: {filetype}\n"
|
||||
f"Source: {post.source or 'none'}"
|
||||
)
|
||||
# Clear old tags
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user