diff --git a/clip-thumbnailer.py b/clip-thumbnailer.py index 0261937..9c874da 100755 --- a/clip-thumbnailer.py +++ b/clip-thumbnailer.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +import io import os import sqlite3 import sys @@ -155,10 +156,31 @@ def _best_image(data: bytes) -> Optional[Tuple[int, int]]: def _parse_args(argv): # Accept: [size, input, output] or [input, output] if len(argv) == 3: - return argv[1], argv[2] + return None, argv[1], argv[2] if len(argv) == 4: - return argv[2], argv[3] - return None, None + return argv[1], argv[2], argv[3] + return None, None, None + + +def _write_preview(preview: bytes, out_path: str, size: Optional[int]) -> bool: + try: + from PIL import Image + except Exception: + Image = None + + if Image is None: + if preview.startswith(b"\x89PNG\r\n\x1a\n") and size is None: + with open(out_path, "wb") as f: + f.write(preview) + return True + return False + + with Image.open(io.BytesIO(preview)) as im: + im.load() + if size: + im.thumbnail((size, size), Image.Resampling.LANCZOS) + im.save(out_path, format="PNG") + return True def main() -> int: @@ -204,14 +226,17 @@ def main() -> int: exit_code = 1 continue try: - with open(out_path, "wb") as f: - f.write(preview) + if not _write_preview(preview, out_path, None): + sys.stderr.write( + f"failed to write preview for {in_path} (missing Pillow or unsupported format)\n" + ) + exit_code = 1 except OSError as exc: sys.stderr.write(f"failed to write {out_path}: {exc}\n") exit_code = 1 return exit_code - in_path, out_path = _parse_args(sys.argv) + size_str, in_path, out_path = _parse_args(sys.argv) if not in_path or not out_path: sys.stderr.write( "usage: clip-thumbnailer [size] INPUT OUTPUT | clip-extract-preview INPUT OUTPUT | " @@ -219,6 +244,13 @@ def main() -> int: ) return 2 + size = None + if size_str: + try: + size = max(1, int(size_str)) + except ValueError: + size = None + try: with open(in_path, "rb") as f: data = f.read() @@ -229,8 +261,9 @@ def main() -> int: preview = _extract_canvas_preview(data) if preview is not None: try: - with open(out_path, "wb") as f: - f.write(preview) + if not _write_preview(preview, out_path, size): + sys.stderr.write("failed to write preview (missing Pillow or unsupported format)\n") + return 1 except OSError as exc: sys.stderr.write(f"failed to write {out_path}: {exc}\n") return 1 @@ -243,8 +276,9 @@ def main() -> int: start, end = loc try: - with open(out_path, "wb") as f: - f.write(data[start:end]) + if not _write_preview(data[start:end], out_path, size): + sys.stderr.write("failed to write preview (missing Pillow or unsupported format)\n") + return 1 except OSError as exc: sys.stderr.write(f"failed to write {out_path}: {exc}\n") return 1 diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..bd8c7dc --- /dev/null +++ b/flake.lock @@ -0,0 +1,26 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1773371044, + "narHash": "sha256-UioNXlVNmX+3QQ0ZjdZkZFq7ctHyObpcvdHm9aZV0/c=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "65feaa4cdcddbc1659abb8796810f29c4531aad8", + "type": "github" + }, + "original": { + "owner": "NixOS", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix index c2c8903..4cdece9 100644 --- a/flake.nix +++ b/flake.nix @@ -12,6 +12,7 @@ packages = forAllSystems (system: let pkgs = import nixpkgs { inherit system; }; + pythonEnv = pkgs.python3.withPackages (ps: [ ps.pillow ]); in { clip-thumbnailer = pkgs.stdenvNoCC.mkDerivation { @@ -26,6 +27,8 @@ runHook preInstall install -Dm755 ${./clip-thumbnailer.py} $out/bin/clip-thumbnailer + substituteInPlace $out/bin/clip-thumbnailer --replace \ + "#!/usr/bin/env python" "#!${pythonEnv}/bin/python3" ln -s $out/bin/clip-thumbnailer $out/bin/clip-extract-preview install -Dm644 ${./clip.thumbnailer} $out/share/thumbnailers/clip.thumbnailer install -Dm644 ${./clip-studio.xml} $out/share/mime/packages/clip-studio.xml