Jump to content
View in the app

A better way to browse. Learn more.

PreShow Experience

A full-screen app on your home screen with push notifications, badges and more.

To install this app on iOS and iPadOS
  1. Tap the Share icon in Safari
  2. Scroll the menu and tap Add to Home Screen.
  3. Tap Add in the top-right corner.
To install this app on Android
  1. Tap the 3-dot menu (⋮) in the top-right corner of the browser.
  2. Tap Add to Home screen or Install app.
  3. Confirm by tapping Install.

Shaolin23

New Member
  • Joined

  • Last visited

Everything posted by Shaolin23

  1. I've been working on a system that generates movie-specific content on the fly every time you launch a PreShow sequence. Instead of static slides, PSE now dynamically creates a "Tonight's Feature" title card and (optionally) 30 custom trivia slides tailored to whatever film you're about to watch. I wanted to share the approach in case others want to build on it. Here's what it looks like in practice: you press Start PreShow on "Fargo," and a few seconds later your sequence opens with a custom title card reading "FARGO" on a theater marquee background, followed by 30 slides of trivia, behind-the-scenes facts, and Q&A specific to that film — all generated fresh every time. The ProblemPSE's slideshow module is great for static trivia packs, but I wanted content that adapts to whatever movie I'm watching. The challenge is that PSE caches slideshow content when it builds the sequence, so you can't just generate images asynchronously and hope they show up in time. I tried a lot of approaches that didn't work before finding one that does: Action module with Python script — PSE runs Python actions inside Kodi's embedded Python, which doesn't have libraries like Pillow installed. Also runs asynchronously — PSE moves to the next module before the script finishes. Generating images and hoping the slideshow picks them up — PSE caches the slide directory contents at sequence build time. If your images aren't there yet, they won't appear. Overwriting existing placeholder images — PSE caches the actual image data, not the file reference. Overwriting the file after caching has no effect. Skin button interception — Tried adding a RunScript call before PSE launches from the skin's context menu. Unreliable timing. The Solution: Hooking into experience.pyThe key insight is that PSE resolves the feature film's metadata (title, genre, year, cast, etc.) before it builds and caches the sequence. There's a window between "PSE knows what movie you picked" and "PSE scans the slideshow directories" where we can inject code. The hook goes into PSE's experience.py, specifically in the method that resolves the feature film from Kodi's library. After PSE appends the feature to its internal list, we write the metadata to temp files and call external Python scripts that generate our content: # In experience.py, after self.features.append(feature) # and before "if not self.features: return False" if self.features and self.features[-1].title: import subprocess import json as _json _feat = self.features[-1] # Write title for the compositing script with open("/tmp/pse_feature_title.txt", "w") as _f: _f.write(_feat.title) # Write full metadata as JSON for other scripts _meta = { "title": _feat.title, "year": getattr(_feat, "year", 0), "genres": getattr(_feat, "genres", []), "directors": getattr(_feat, "directors", []), "cast": getattr(_feat, "cast", [])[:10], "rating": str(getattr(_feat, "rating", "")), "runtime": getattr(_feat, "runtime", 0), } with open("/tmp/pse_feature_metadata.json", "w") as _f: _json.dump(_meta, _f) # Run content generation scripts (synchronous — PSE waits) subprocess.call(["/usr/bin/python3", "/path/to/your/script.py"]) The critical detail is subprocess.call — this blocks PSE until the script finishes. PSE doesn't proceed to sequence building until our content is generated and sitting in the slideshow directory. Important: We call /usr/bin/python3 (system Python) rather than running inside Kodi's Python. This gives us access to any pip-installed library — Pillow for image compositing, requests for API calls, whatever you need. Kodi's embedded Python is limited to its bundled packages. Finding the Right SpotTo find where to inject, look for this pattern in experience.py: feature = self.featureFromId(movieid, episodeid) if feature: self.features.append(feature) # >>> YOUR HOOK GOES HERE <<< if not self.features: return False return True Search for self.features.append(feature) near featureFromId — there's one instance in the method that handles the "Start PreShow" button press. On my install it's around line 1060. After editing, you must delete the bytecode cache or Kodi will keep running the old version: rm ~/.kodi/addons/script.preshowexperience/resources/lib/__pycache__/experience.cpython-*.pyc Then restart Kodi. What You Can Build With ThisOnce you have the hook in place, the feature metadata is available to any external script. Here's what's in the self.features[-1] object: title — Movie title year — Release year genres — List of genres (Action, Horror, Drama, etc.) directors — Director names cast — Cast list with names and roles rating — MPAA rating runtime — Runtime in seconds studios — Studio names tags — Kodi library tags With this data you could: Generate a custom "Tonight's Feature" title card with the movie name Create genre-themed trivia (horror trivia for horror films, sci-fi for sci-fi) Select decade-appropriate trailers by dynamically populating a trailer directory Generate MPAA-style rating cards Pick studio-specific intro bumpers Call an AI API to generate movie-specific trivia and Q&A slides Create "on this day in cinema history" slides based on the current date and film era Example 1: Dynamic Title CardMy first use case was a "Tonight's Feature" title card — a theater marquee background with the movie title composited in a vintage font. The script reads the title from the temp file, uses Pillow to render text onto a template image, and saves it to a slideshow directory. The core of the compositing script: from PIL import Image, ImageDraw, ImageFont def generate_title_card(title): # Load background template img = Image.open("/tmp/template.png") draw = ImageDraw.Draw(img) font = ImageFont.truetype("/tmp/myfont.ttf", 80) # Center the title bbox = draw.textbbox((0, 0), title.upper(), font=font) text_w = bbox[2] - bbox[0] x = (1920 - text_w) // 2 draw.text((x, 450), title.upper(), font=font, fill=(0, 0, 0)) img.save("/path/to/pse/Slideshow/Tonight/tonight.png") The real script has auto-sizing logic (shrinks the font for long titles), word wrapping, and handles the CIFS font loading issue (Pillow can't load fonts from network shares — copy to /tmp/ first). But the concept is this simple. In the PSE sequence, a Slideshow module points at the Tonight directory and displays whatever image is in there. Runtime: About 1.4 seconds on a Vero 5. Barely noticeable in the PSE launch flow. Example 2: AI-Generated Trivia SlidesThe more ambitious extension calls the Claude API with the movie's metadata and asks it to generate trivia facts and Q&A pairs. The response is parsed and rendered into 30 slides — 10 trivia facts plus 10 question/answer pairs (20 slides). The flow: import json import urllib.request def generate_trivia(metadata): api_key = open("/home/user/.config/pse/api.key").read().strip() prompt = f"""Generate trivia about "{metadata['title']}" ({metadata['year']}). Director: {', '.join(metadata['directors'])} Cast: {', '.join(metadata['cast'][:5])} Return JSON with: - 10 trivia facts (behind the scenes, connections, legacy, etc.) - 10 Q&A pairs (movie trivia questions with answers) """ payload = json.dumps({ "model": "claude-sonnet-4-20250514", "max_tokens": 6000, "messages": [{"role": "user", "content": prompt}] }).encode() req = urllib.request.Request("https://api.anthropic.com/v1/messages", data=payload) req.add_header("Content-Type", "application/json") req.add_header("x-api-key", api_key) req.add_header("anthropic-version", "2023-06-01") resp = urllib.request.urlopen(req, timeout=30) data = json.loads(resp.read()) trivia = json.loads(data['content'][0]['text']) # Render each item as a slide image with Pillow for i, item in enumerate(trivia): render_slide(item, i, metadata['title']) Each slide gets rendered with a vintage cinema aesthetic — dark background, golden category headers, cream body text, decorative borders. Question slides use blue headers, answer slides use green. The trivia generation is opt-in — I have a toggle script that creates/removes a flag file (/tmp/pse_trivia_enabled). The generation script checks for this file and skips if it's not there. A Kodi notification confirms the toggle state. Runtime: About 8-10 seconds for the API call plus slide rendering. A progress notification shows during generation so you know it's working. Tips for ImplementationCIFS/Network Share Gotcha: If your PSE content lives on a network share, Pillow's FreeType library cannot load fonts from CIFS mounts. Always copy fonts and templates to /tmp/ before opening them with Pillow. Bytecode Cache: Kodi caches compiled Python as .pyc files. Every time you edit experience.py, delete the cache and restart Kodi or your changes won't take effect. PSE Updates: When PSE updates, experience.py gets overwritten. Keep a backup of your modified version and re-apply the hook after updates. The hook is small enough that re-applying takes 30 seconds. Testing: You can test your generation scripts independently from the command line: echo "Alien" > /tmp/pse_feature_title.txt echo '{"title":"Alien","year":1979,"genres":["Horror","Sci-Fi"],"directors":["Ridley Scott"]}' > /tmp/pse_feature_metadata.json python3 /path/to/your/script.py Then check the output directory for your generated slides. Keep Scripts Fast: Since subprocess.call blocks PSE initialization, your scripts need to finish in a reasonable time. The title card takes ~1.4 seconds, the AI trivia takes ~8-10 seconds. Much longer than that and the delay before the sequence starts becomes noticeable. Sequence SetupMy PSE sequence order: Slideshow — Tonight directory (dynamic title card, 30-60 second duration) Slideshow — Trivia directory (AI trivia slides, 10 second per slide) Trailer modules Audio Format Bumper Action — Enable refresh rate switching Feature The two slideshow modules point at directories that get populated fresh every time by the hook scripts. Everything else in the sequence is standard PSE configuration. What's NextI'm exploring a few more ideas with this system: dynamically selecting trailer folders based on the feature's genre/decade, generating "cinema history" slides that tie the film to its cultural moment, and creating custom intermission cards for double features. The hook pattern is flexible enough that any script with access to the movie metadata can feed content into the sequence. If anyone builds on this or has questions about the implementation, happy to help. The hardest part was figuring out the right injection point — once that's solved, the rest is just Python scripting.

Account

Navigation

Search

Configure browser push notifications

Chrome (Android)
  1. Tap the lock icon next to the address bar.
  2. Tap Permissions → Notifications.
  3. Adjust your preference.
Chrome (Desktop)
  1. Click the padlock icon in the address bar.
  2. Select Site settings.
  3. Find Notifications and adjust your preference.