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:
pax 2026-04-04 19:40:49 -05:00
parent 495eb4c64d
commit 526606c7c5
2 changed files with 36 additions and 0 deletions

View File

@ -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()

View File

@ -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