diff --git a/dev/screenshots/README.md b/dev/screenshots/README.md index 0866b90..ad165c3 100644 --- a/dev/screenshots/README.md +++ b/dev/screenshots/README.md @@ -18,7 +18,7 @@ DejaVu fonts; `oxipng` (optional) compresses the PNGs losslessly. It takes about |---|---| | `catalog.py` | The made-up library: movies (with file details) and series (with seasons). | | `posters.sh` | Draws a poster per title into `posters/` (gradient, title, year); run by `take.sh` when `posters/` is missing. | -| `server.py` | The fake services on 127.0.0.1: Radarr 19891, Sonarr 19892, SABnzbd 19893. Library, search, releases, queues, history, calendar, posters. | +| `server.py` | The fake services on 127.0.0.1: Radarr 19891, Sonarr 19892, SABnzbd 19893, Trakt 19894 (`take.sh` points arr-tui there with `ARR_TUI_TRAKT_URL`). Library, search, lists, releases, queues, history, calendar, posters. | | `xshot.sh` | Runs a command in Ghostty under Xvfb and follows a scene script. | | `scenes/*.txt` | What to press and when to take a picture: `key`, `type`, `sleep`, `shot `. | diff --git a/dev/screenshots/catalog.py b/dev/screenshots/catalog.py index 35d94b1..9898389 100644 --- a/dev/screenshots/catalog.py +++ b/dev/screenshots/catalog.py @@ -43,3 +43,6 @@ SERIES = [ (5,"Orbital",2025,"Vector","continuing",[(8,2)],"The crew of a commercial space station tries to stay profitable, and alive.",("#000814","#ffc300")), (6,"Ferryman",2020,"Coastline","ended",[(6,0)],"A ferry captain becomes the only link between an island and the mainland during a long quarantine.",("#2f3e46","#cad2c5")), ] + +# A sequel the Trakt "Anticipated" list has, not in the library: id, title, year, colors. +UPCOMING = (9001, "The Long Orbit: Return", 2027, ("#0b132b", "#e0aaff")) diff --git a/dev/screenshots/posters.sh b/dev/screenshots/posters.sh index 0de000a..8b5140b 100755 --- a/dev/screenshots/posters.sh +++ b/dev/screenshots/posters.sh @@ -11,6 +11,7 @@ python3 -c ' import catalog for m in catalog.MOVIES: print("m%d|%s|%d|%s|%s" % (m[0], m[1], m[2], m[10][0], m[10][1])) for s in catalog.SERIES: print("s%d|%s|%d|%s|%s" % (s[0], s[1], s[2], s[7][0], s[7][1])) +u = catalog.UPCOMING; print("m%d|%s|%d|%s|%s" % (u[0], u[1], u[2], u[3][0], u[3][1])) ' | while IFS='|' read -r id title year c1 c2; do magick -size 500x750 "gradient:$c1-$c2" \ \( -size 420x320 -background none -fill white -font "$font" -gravity center caption:"$title" \) \ diff --git a/dev/screenshots/scenes/discover.txt b/dev/screenshots/scenes/discover.txt new file mode 100644 index 0000000..4ec950e --- /dev/null +++ b/dev/screenshots/scenes/discover.txt @@ -0,0 +1,6 @@ +# The Trakt lists in the Movies view (empty search): Anticipated, with a title +# that is not in the library yet selected. +sleep 4 +key bracketright bracketright +sleep 4 +shot discover diff --git a/dev/screenshots/server.py b/dev/screenshots/server.py index b11553b..e560842 100644 --- a/dev/screenshots/server.py +++ b/dev/screenshots/server.py @@ -1,4 +1,4 @@ -"""Fake Radarr (19891), Sonarr (19892) and SABnzbd (19893) serving the made-up +"""Fake Radarr (19891), Sonarr (19892), SABnzbd (19893) and Trakt (19894) serving the made-up library in catalog.py, for the README screenshots. Only the API calls arr-tui makes are answered; posters come from posters/ (see posters.sh).""" import datetime, json, os, sys, threading, urllib.parse @@ -8,7 +8,7 @@ import catalog HERE = os.path.dirname(os.path.abspath(__file__)) TODAY = datetime.date.today() GB = 1 << 30 -RADARR, SONARR, SAB = 19891, 19892, 19893 +RADARR, SONARR, SAB, TRAKT = 19891, 19892, 19893, 19894 def day(n): return (TODAY + datetime.timedelta(days=n)).isoformat() @@ -98,10 +98,10 @@ def radarr(path, q): return next(x for x in movies() if x["id"] == int(path.rsplit("/", 1)[1])) if path.endswith("/movie/lookup"): term = q.get("term", [""])[0].lower() + if term.startswith("tmdb:"): + return [m for m in movies() + [UPCOMING] if m["tmdbId"] == int(term[5:])] res = [m for m in movies() if term and term in m["title"].lower()] - extra = [{"id": 0, "title": "The Long Orbit: Return", "year": 2027, "overview": "The sequel.", "tmdbId": 9001, "titleSlug": "the-long-orbit-return", - "images": [{"coverType": "poster", "remoteUrl": "http://127.0.0.1:%d/remote/m1.jpg" % RADARR}], "status": "announced", "ratings": {}}] - return res + (extra if "orbit" in term else []) + return res + ([UPCOMING] if "orbit" in term else []) if path.endswith("/qualityprofile"): return [{"id": 1, "name": "Any"}, {"id": 4, "name": "HD-1080p"}, {"id": 5, "name": "Ultra-HD"}] if path.endswith("/rootfolder"): return [{"id": 1, "path": "/movies", "freeSpace": 3 << 40, "accessible": True}] if path.endswith("/release"): @@ -125,6 +125,11 @@ def radarr(path, q): def sonarr(path, q): if path.endswith("/system/status"): return {"appName": "Sonarr", "version": "4.0.15"} + if path.endswith("/series/lookup"): + term = q.get("term", [""])[0].lower() + if term.startswith("tvdb:"): + return [x for x in series() if x["tvdbId"] == int(term[5:])] + return [x for x in series() if term in x["title"].lower()] if path.endswith("/series"): return series() if "/series/" in path and path.rsplit("/", 1)[1].isdigit(): return next(x for x in series() if x["id"] == int(path.rsplit("/", 1)[1])) @@ -148,6 +153,28 @@ def sonarr(path, q): return out return [] +# A title Trakt lists that is not in the library yet. +UPCOMING = {"id": 0, "title": catalog.UPCOMING[1], "year": catalog.UPCOMING[2], "overview": "Years after the first voyage, a second crew follows the freighter's last signal.", + "tmdbId": 9001, "titleSlug": "the-long-orbit-return", "genres": ["Science Fiction"], "studio": "Halcyon Pictures", "runtime": 0, + "images": [{"coverType": "poster", "remoteUrl": "http://127.0.0.1:%d/remote/m9001.jpg" % RADARR}], "status": "announced", "ratings": {}} + +def trakt(path): + def movie(id, title, year): return {"title": title, "year": year, "ids": {"trakt": id, "slug": "x", "tmdb": 1000 + id if id < 9000 else id}} + def show(id): s = next(x for x in catalog.SERIES if x[0] == id); return {"title": s[1], "year": s[2], "ids": {"trakt": id, "slug": "x", "tvdb": 2000 + id}} + by = {m[0]: m for m in catalog.MOVIES} + if path == "/movies/trending": + return [{"watchers": n, "movie": movie(i, by[i][1], by[i][2])} for i, n in [(13, 2841), (1, 2217), (11, 1604), (6, 1186), (3, 902), (8, 655), (16, 412), (12, 371)]] + if path == "/movies/anticipated": + return [{"list_count": 18342, "movie": movie(9001, "The Long Orbit: Return", 2027)}] + \ + [{"list_count": n, "movie": movie(i, by[i][1], by[i][2])} for i, n in [(15, 9120), (9, 6204), (4, 3311)]] + if path.startswith("/movies/"): + return [{"watcher_count": n * 10, "movie": movie(i, by[i][1], by[i][2])} for i, n in [(1, 5120), (13, 4870), (3, 3010), (2, 1200)]] + if path == "/shows/trending": + return [{"watchers": n, "show": show(i)} for i, n in [(1, 3022), (5, 2410), (3, 1170), (2, 640)]] + if path.startswith("/shows/"): + return [{"watcher_count": n, "show": show(i)} for i, n in [(3, 51200), (1, 40100), (4, 22000)]] + return [] + def sab(q): mode = q.get("mode", [""])[0] if mode == "version": return {"version": "4.5.1"} @@ -184,11 +211,14 @@ def handler(port): if not os.path.exists(p): self.send_response(404); self.end_headers(); return data = open(p, "rb").read() self.send_response(200); self.send_header("Content-Type", "image/jpeg"); self.send_header("Content-Length", str(len(data))); self.end_headers(); self.wfile.write(data); return - out = sab(q) if port == SAB else (radarr(path, q) if port == RADARR else sonarr(path, q)) + if port == TRAKT: + out = trakt(path) + else: + out = sab(q) if port == SAB else (radarr(path, q) if port == RADARR else sonarr(path, q)) b = json.dumps(out).encode() self.send_response(200); self.send_header("Content-Type", "application/json"); self.send_header("Content-Length", str(len(b))); self.end_headers(); self.wfile.write(b) return H -for port in (RADARR, SONARR): +for port in (RADARR, SONARR, TRAKT): threading.Thread(target=ThreadingHTTPServer(("127.0.0.1", port), handler(port)).serve_forever, daemon=True).start() ThreadingHTTPServer(("127.0.0.1", SAB), handler(SAB)).serve_forever() diff --git a/dev/screenshots/take.sh b/dev/screenshots/take.sh index 00086fe..e67832f 100755 --- a/dev/screenshots/take.sh +++ b/dev/screenshots/take.sh @@ -29,7 +29,8 @@ go build -C "$repo" -o "$work/arr-tui" ./cmd/arr-tui [[ -d $here/posters ]] || { echo "generating posters"; "$here/posters.sh"; } -echo "starting the fake Radarr, Sonarr and SABnzbd" +echo "starting the fake Radarr, Sonarr, SABnzbd and Trakt" +export ARR_TUI_TRAKT_URL=http://127.0.0.1:19894 python3 "$here/server.py" & server=$! sleep 1 @@ -42,6 +43,7 @@ for theme in dark light; do "radarr": {"url": "http://127.0.0.1:19891", "apikey": "0123456789abcdef0123456789abcdef", "default_quality_profile": "Ultra-HD", "default_root_folder": "/movies"}, "sonarr": {"url": "http://127.0.0.1:19892", "apikey": "0123456789abcdef0123456789abcdef", "default_quality_profile": "HD-1080p", "default_root_folder": "/tv"}, "sabnzbd": {"url": "http://127.0.0.1:19893", "apikey": "0123456789abcdef0123456789abcdef"}, + "trakt": {"client_id": "0123456789abcdef0123456789abcdef"}, "ui": {"theme": "$theme"} } JSON @@ -54,6 +56,7 @@ shoot() { # scene theme [ghostty args] XDG_CONFIG_HOME=$demo/$2/.config "$here/xshot.sh" "$here/scenes/$1.txt" "$out" "${3:-}" "$work/arr-tui" } shoot views dark +shoot discover dark shoot episodes dark shoot config dark shoot light light "--background=eeeeee --foreground=1c1c1c" diff --git a/docs/screenshots/calendar.png b/docs/screenshots/calendar.png index ea8839f..0c49553 100644 Binary files a/docs/screenshots/calendar.png and b/docs/screenshots/calendar.png differ diff --git a/docs/screenshots/config.png b/docs/screenshots/config.png index 10467ff..e1b4ff1 100644 Binary files a/docs/screenshots/config.png and b/docs/screenshots/config.png differ diff --git a/docs/screenshots/discover.png b/docs/screenshots/discover.png new file mode 100644 index 0000000..3bf3a2b Binary files /dev/null and b/docs/screenshots/discover.png differ diff --git a/docs/screenshots/episodes.png b/docs/screenshots/episodes.png index 7e7514a..e210290 100644 Binary files a/docs/screenshots/episodes.png and b/docs/screenshots/episodes.png differ diff --git a/docs/screenshots/search.png b/docs/screenshots/search.png index 4740729..8c03eae 100644 Binary files a/docs/screenshots/search.png and b/docs/screenshots/search.png differ