Skip to content

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

1 Commit

Folders and files

Repository files navigation

MTG Art Screensaver

A screensaver that shows Magic: The Gathering illustration art with slow Ken Burns pans and crossfades, on Ubuntu and Windows.

Art comes from Scryfall; images are upscaled once with Real-ESRGAN and cached, so the render loop stays smooth.

Quick start (Ubuntu)

python3 -m venv .venv && . .venv/bin/activate
pip install -e .

# one-time: fetch the Real-ESRGAN build that bundles models (~45 MB)
mkdir -p vendor/realesrgan && cd vendor/realesrgan
curl -LO https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.5.0/realesrgan-ncnn-vulkan-20220424-ubuntu.zip
unzip -o realesrgan-ncnn-vulkan-20220424-ubuntu.zip && rm realesrgan-ncnn-vulkan-20220424-ubuntu.zip
chmod +x realesrgan-ncnn-vulkan && cd ../..

mtgss doctor                  # verify GPU, upscaler, displays
mtgss index                   # build the artwork index (~10s)
mtgss prefetch --count 100    # warm the cache (~16s per image)
mtgss run --display 1         # go

Note the upscaler binary must come from the xinntao/Real-ESRGAN releases, not Real-ESRGAN-ncnn-vulkan — only the former bundles the model files.

Commands

Command Purpose
mtgss doctor Check Vulkan, upscaler, index, cache, displays
mtgss index Build the index from Scryfall bulk data
mtgss index --rescore-only Re-apply curation weights after editing config
mtgss prefetch --count N Pre-produce N images
mtgss run Run the screensaver

mtgss run takes --display N, --windowed, --slide SECONDS, --fade SECONDS, --stats, and --no-upscale.

Keys

Key Action
f Favourite the current artwork (oversampled later, never evicted)
x Block it (never shown again, removed from cache)
space Pause
→ Skip to the next artwork
i Toggle the caption
anything else Exit

How it works

 index (sqlite)          producer (background)         renderer
 ─────────────           ─────────────────────         ────────
 unique_artwork.jsonl    pick candidate (weighted)     play queue from ready/
 art_tags.jsonl     ──►  download art_crop      ──►    SDL2 GPU textures
 → artworks table        realesrgan-ncnn-vulkan x4     Ken Burns + crossfade
   + tag weights         write ready/ atomically       caption overlay

The renderer only consumes images already in ready/. If the cache runs dry it falls back to a Lanczos upscale rather than stalling on the neural pass, and JPEG decoding happens on a worker thread so it never lands mid-crossfade.

Why Real-ESRGAN, and why the slow model

Scryfall's art crops top out around 626×457 — that is the hard ceiling, and the newer WebP variants are the same pixels in a smaller file. Filling a 4K display therefore means a ~6× upscale.

art_crop images are also scans of printed cards, so they carry halftone rosette artifacts. Measured on an Intel RPL-P iGPU, 626×457 → 2504×1828:

Model Time Result
Lanczos (no NN) instant soft, and amplifies the halftone
realesr-animevideov3 0.9s sharpens the halftone into crunchy dots
realesrgan-x4plus-anime 5.6s clean but flattens brushwork, posterised
realesrgan-x4plus 16.2s removes print screen, keeps the paint

Only x4plus both denoises the scan and preserves painterly texture. The cost is paid once per image, in the background, and cached.

Cache integrity — why mtgss verify exists

realesrgan-ncnn-vulkan exits 0 even when inference has silently failed. Under GPU contention — notably running mtgss prefetch while the screensaver is drawing on the same Intel iGPU — tiles come back empty and the result is a mostly-black image. Nothing downstream notices: the file is a valid JPEG of the right dimensions, so it is cached and then redisplayed on every rotation, the artwork appearing as a narrow strip against black.

Every upscale is therefore verified against its source before being accepted. Both images are reduced to 32×32 and compared; a real upscale changes detail, not overall composition. Measured separation is wide:

low-frequency difference
Good upscales 1.4 – 3.6
Corrupt upscales 61 – 127
Reject threshold 12

A rejected result is retried once (the corruption is transient), then falls back to Lanczos. Nothing that fails validation is ever written to the cache.

To audit images cached before this check existed, or after an interrupted run:

mtgss verify            # report
mtgss verify --repair   # rebuild the bad ones

mtgss doctor reports the corrupt count too.

Avoid running prefetch while the screensaver is displaying — they compete for the same GPU. Validation makes contention safe rather than silently destructive, but it still wastes a 16s upscale per retry.

Curation

Artwork selection is weighted-random over a precomputed score combining:

  • Quality filters — high-res scans only, no digital-only cards, no joke sets
  • Aspect preference — wider crops need less cropping on a 16:9 screen; 626×457 loses ~23% of its height, 571×460 old frames lose ~30%
  • Art tags from Scryfall's Tagger project (482k taggings over 52,784 illustrations). no-one — art with no figures — is the single best predictor of something that reads well fullscreen. Close-ups and headshots are demoted.
  • Favourites and blocks you set with f and x

Tag weights live in the config file, not in code, because tuning them is the main lever for dialling in what counts as good art:

~/.config/mtg-screensaver/config.toml

Copy config.example.toml there to start. Re-run mtgss index --rescore-only after editing.

Artwork narrower than max_crop_fraction allows is letterboxed onto a matte of its own dominant colour rather than cropped harder. Full-art cards, whose crops come back portrait, always take this path.

Known limitation: which monitor it opens on

--display N is a request. Under GNOME/mutter every SDL placement route tested — creation-time position, post-map SDL_SetWindowPosition, and SDL_WINDOWPOS_CENTERED_DISPLAY — was overridden, and the window consistently opened on whichever monitor had focus. The app asks for genuine fullscreen (which mutter does honour) and then adapts to whatever surface it is given, so it always renders correctly; it just may not be the monitor you named.

The renderer also re-reads its drawable size every frame and re-plans the layout if it changes, so a resize or a window manager surprise cannot leave the geometry stale.

Ubuntu: launching it

GNOME dropped screensaver support, so there is no system hook to register with. Bind mtgss run to a keyboard shortcut:

Settings → Keyboard → View and Customise Shortcuts → Custom Shortcuts → +

Command: /path/to/mtg-screensaver/.venv/bin/mtgss run --display 1

To start it automatically on idle instead, misc/mtg-screensaver.desktop can be launched from a small idle watcher (xidlehook, xss-lock); that is not wired up yet.

Windows

Fetch the Windows Real-ESRGAN build into vendor/realesrgan/, then:

python build_windows.py

This produces dist/mtg-screensaver.scr. Right-click → Install, or copy it to C:\Windows\System32. It handles /s (run), /c (config) and /p (preview).

Attribution

Card images and artwork are property of Wizards of the Coast. This is unofficial Fan Content permitted under the Fan Content Policy. Not approved or endorsed by Wizards. Portions of the materials used are property of Wizards of the Coast. ©Wizards of the Coast LLC.

Scryfall's image guidelines require that the artist and source stay identifiable wherever an art crop is shown, which is why the caption is on by default. i hides it for a session; it is not a setting.

Tests

.venv/bin/python -m pytest tests/ -q

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages