{ description = "Thumbnailer for Affinity files (.afpub, .afdesign, .afphoto, .af, .afpackage) on KDE Plasma / freedesktop"; inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; }; outputs = { self, nixpkgs }: let systems = [ "x86_64-linux" "aarch64-linux" ]; forAllSystems = f: nixpkgs.lib.genAttrs systems (system: f nixpkgs.legacyPackages.${system}); in { packages = forAllSystems (pkgs: { default = self.packages.${pkgs.stdenv.hostPlatform.system}.affinity-thumbnailer; affinity-thumbnailer = pkgs.stdenv.mkDerivation { pname = "affinity-thumbnailer"; version = "0.1.0"; # Don't pull source from GitHub — we build everything from inline content src = pkgs.runCommand "affinity-thumbnailer-src" { } '' mkdir -p $out ''; nativeBuildInputs = [ pkgs.makeWrapper ]; # Runtime dependencies buildInputs = [ pkgs.python3 ]; dontConfigure = true; dontBuild = true; installPhase = '' runHook preInstall # --- 1. Install the Python thumbnailer script --- mkdir -p $out/bin cat > $out/bin/affinity-thumbnailer << 'PYEOF' #!/usr/bin/env python3 """Extract embedded PNG thumbnails from Affinity files (.afpub, .afdesign, .afphoto, .af, .afpackage).""" import argparse import os import sys PNG_SIGNATURE = b"\x89PNG" PNG_IEND = b"IEND" SUPPORTED_EXTENSIONS = {".afpub", ".afdesign", ".afphoto", ".af", ".afpackage"} def find_png(data: bytes) -> bytes | None: """Find and extract the first complete PNG from binary data.""" sig_offset = data.find(PNG_SIGNATURE) if sig_offset == -1: return None iend_offset = data.find(PNG_IEND, sig_offset) if iend_offset == -1: return None png_end = iend_offset + len(PNG_IEND) + 4 return data[sig_offset:png_end] def extract_thumbnail(filepath: str) -> bytes | None: """Read a file and extract its embedded PNG thumbnail.""" ext = os.path.splitext(filepath)[1].lower() if ext not in SUPPORTED_EXTENSIONS: print( f"Error: unsupported extension '{ext}'. " f"Supported: {', '.join(sorted(SUPPORTED_EXTENSIONS))}", file=sys.stderr, ) return None if not os.path.isfile(filepath): print(f"Error: file not found: {filepath}", file=sys.stderr) return None with open(filepath, "rb") as f: data = f.read() return find_png(data) def main() -> None: parser = argparse.ArgumentParser( description="Extract embedded PNG thumbnail from Affinity files." ) parser.add_argument("input", help="Input Affinity file (.afpub, .afdesign, .afphoto, .af, .afpackage)") parser.add_argument( "output", nargs="?", default=None, help="Output PNG file (default: stdout)", ) args = parser.parse_args() png_data = extract_thumbnail(args.input) if png_data is None: print("Error: no embedded PNG thumbnail found.", file=sys.stderr) sys.exit(1) if args.output: with open(args.output, "wb") as f: f.write(png_data) print(f"Thumbnail saved: {args.output} ({len(png_data)} bytes)") else: sys.stdout.buffer.write(png_data) if __name__ == "__main__": main() PYEOF chmod +x $out/bin/affinity-thumbnailer # Wrap so it finds python3 in NixOS wrapProgram $out/bin/affinity-thumbnailer \ --prefix PATH : ${pkgs.lib.makeBinPath [ pkgs.python3 ]} # --- 2. Install the .thumbnailer freedesktop entry --- mkdir -p $out/share/thumbnailers cat > $out/share/thumbnailers/affinity.thumbnailer << EOF [Thumbnailer Entry] TryExec=$out/bin/affinity-thumbnailer Exec=$out/bin/affinity-thumbnailer %i %o MimeType=application/affinity-publisher;application/affinity-designer;application/affinity-photo;application/affinity-v3;application/affinity-package; EOF # --- 3. Install MIME type definitions --- mkdir -p $out/share/mime/packages cat > $out/share/mime/packages/application-affinity.xml << 'MIMEXML' Affinity File Affinity File Affinity File Affinity File Affinity File MIMEXML runHook postInstall ''; meta = with pkgs.lib; { description = "Thumbnailer for Affinity files on KDE Plasma / freedesktop"; homepage = "https://github.com/Nczer/Affinity-thumbnailer-KDE-plasma"; license = licenses.mit; # No license specified in the repo platforms = platforms.linux; }; }; }); # --- NixOS module for easy integration --- nixosModules.default = { pkgs, config, lib, ... }: { options.services.affinity-thumbnailer = { enable = lib.mkEnableOption "Affinity file thumbnailer for KDE Plasma / freedesktop"; }; config = lib.mkIf config.services.affinity-thumbnailer.enable { environment.systemPackages = [ self.packages.${pkgs.stdenv.hostPlatform.system}.default ]; # Make sure thumbnailer and MIME directories are linked environment.pathsToLink = [ "/share/thumbnailers" "/share/mime" ]; }; }; }; }