Screenshots: Trakt lists, Trakt in the Config view
The fake services gain a Trakt API (port 19894) and a sequel that is not in the library; a new scene shows the Anticipated list. The other pictures are retaken (Config has the Trakt section, the search status bar mentions [ ] lists, the calendar is two days on). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -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 <name>`. |
|
||||
|
||||
|
||||
@@ -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"))
|
||||
|
||||
@@ -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" \) \
|
||||
|
||||
@@ -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
|
||||
@@ -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()
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user