Files
obsidian-vault/steam-grid-artwork.py
T

142 lines
4.5 KiB
Python

#!/usr/bin/env python3
"""Universal SteamGridDB artwork fetcher for non-Steam shortcuts on Bazzite HTPC.
Usage:
python3 steam-grid-artwork.py --appid APPID --name "Game Name"
Will download grid (p.png), hero (_hero.png), and logo (_logo.png)
into ~/.steam/steam/userdata/147839491/config/grid/
Requires: SGDB_API_KEY env var or ~/Documents/steamgriddb_api_key.txt
"""
import json
import os
import subprocess
import sys
import tempfile
from pathlib import Path
# ── config ────────────────────────────────────────────
GRID_DIR = os.path.expanduser(
"~/.steam/steam/userdata/147839491/config/grid"
)
KEY_FILE = os.path.expanduser("~/Documents/steamgriddb_api_key.txt")
SGDB_KEY = os.environ.get("SGDB_API_KEY")
if not SGDB_KEY:
try:
SGDB_KEY = Path(KEY_FILE).read_text().strip()
except Exception:
print("ERROR: SGDB_API_KEY not set and ~/Documents/steamgriddb_api_key.txt not found")
sys.exit(1)
API = "https://www.steamgriddb.com/api/v2"
UA = "steam-grid-artwork/1.0"
def api_get(path):
"""curl wrapper returning parsed JSON."""
url = f"{API}/{path}"
r = subprocess.run(
["curl", "-s", "-H", f"Authorization: Bearer {SGDB_KEY}", "-A", UA, url],
capture_output=True, text=True, timeout=20
)
if r.returncode != 0:
print(f" curl error: {r.stderr}")
return None
try:
return json.loads(r.stdout)
except json.JSONDecodeError:
print(f" invalid JSON response: {r.stdout[:200]}")
return None
def download(url, dest):
"""Download a file via curl."""
print(f" → {dest.name} ({dest.parent})")
subprocess.run(
["curl", "-sL", "-H", "User-Agent: Mozilla/5.0", url, "-o", str(dest)],
capture_output=True, timeout=60, check=True
)
size = dest.stat().st_size
print(f" {size:,} bytes")
def pick_first(items, prefer_style=None, prefer_dim=None):
"""Pick first item, optionally preferring a style or dimension."""
if prefer_style:
for item in items:
if item.get("style") == prefer_style:
return item
if prefer_dim:
w, h = prefer_dim
for item in items:
if item.get("width") == w and item.get("height") == h:
return item
return items[0] if items else None
def fetch_artwork(appid, game_name):
"""Fetch grid, hero, logo for a given appid and game name."""
# ── search ──
safe_name = game_name.replace(" ", "%20")
result = api_get(f"search/autocomplete/{safe_name}")
if not result or not result.get("data"):
# try trimmed name (before colon, dash etc.)
short = game_name.split(":")[0].split("-")[0].strip().replace(" ", "%20")
if short != safe_name:
result = api_get(f"search/autocomplete/{short}")
if not result or not result.get("data"):
print(f" ✗ game '{game_name}' not found on SteamGridDB")
return False
game = result["data"][0]
game_id = game["id"]
sg_name = game["name"]
print(f" ✓ SGDB id={game_id}\"{sg_name}\"")
# ── grid (p.png) ──
dest = Path(GRID_DIR) / f"{appid}p.png"
print(" Grid:")
result = api_get(f"grids/game/{game_id}")
if result and result.get("data"):
item = pick_first(result["data"], prefer_style="alternate")
if item:
download(item["url"], dest)
else:
print(" no grids found")
# ── hero (_hero.png) ──
dest = Path(GRID_DIR) / f"{appid}_hero.png"
print(" Hero:")
result = api_get(f"heroes/game/{game_id}")
if result and result.get("data"):
item = pick_first(result["data"])
if item:
download(item["url"], dest)
else:
print(" no heroes found")
# ── logo (_logo.png) ──
dest = Path(GRID_DIR) / f"{appid}_logo.png"
print(" Logo:")
result = api_get(f"logos/game/{game_id}")
if result and result.get("data"):
item = pick_first(result["data"])
if item:
download(item["url"], dest)
else:
print(" no logos found")
return True
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(description="Fetch SteamGridDB artwork")
parser.add_argument("--appid", required=True, help="Steam app ID (unsigned)")
parser.add_argument("--name", required=True, help="Game name for search")
args = parser.parse_args()
fetch_artwork(args.appid, args.name)