This Site

This Squarespace site was built using the Om template, part of the Montauk family. The font is Kepler.

Media Log

The Media Log is a self-contained HTML page generated by a Python script from an Excel spreadsheet, deployed to Cloudflare Pages and embedded here via iframe. The entire thing was built using Claude Code.

Three data sources feed into it:

  • Films & TV sync from Trakt, enriched with metadata (cast, director, posters, ratings) from TMDB
  • Books sync from Goodreads via RSS, with page counts backfilled from Open Library
  • Podcasts sync from Overcast via OPML export

A set of Python scripts pull from each source, write to a shared spreadsheet, generate the HTML, and deploy it to Cloudflare—all chained together and run automatically by launchd jobs on macOS—hourly for podcasts, nightly for everything else. Book titles link to Bookshop.org.

More on how it works

1. Architecture

The pipeline has three stages:

Data Sources          Spreadsheet           HTML              Hosting
┌───────────┐
│ Trakt      │──┐
└───────────┘  │   ┌──────────────┐   ┌─────────────┐   ┌────────────┐
┌───────────┐  ├──┤ media_log.xlsx ├──┤ Python script ├──┤ Cloudflare  │
│ Goodreads  │──┤  │  (3 sheets)     │   │ (to_html.py)  │   │ Pages       │
└───────────┘  │   └──────────────┘   └─────────────┘   └────────────┘
┌───────────┐  │
│ Overcast   │──┘
└───────────┘

On macOS, launchd plists run the pipeline automatically—hourly for podcasts, nightly for everything else.

2. Data Sources

Films & TV: Trakt + TMDB

I use Trakt to track what I watch. A Python sync script pulls my watch history via the Trakt API, then enriches each entry with metadata from TMDB—cast, director, runtime, genre, poster, and rating. You’ll need:

Books: Goodreads RSS

Goodreads exposes your “read” shelf via a public RSS feed. The sync script pulls titles, authors, ratings, dates, and cover art. Page counts are backfilled from Open Library’s API. No API key required — just your Goodreads user ID.

Podcasts: Overcast

Overcast (iOS podcast app) provides an OPML export of your listening history. The sync script parses episode titles, show names, artwork URLs, durations, and dates.

3. The Spreadsheet

All data lives in a single Excel file (media_log.xlsx) with three sheets:

SheetKey Columns
Watch LogWatch Date, Title, Type, Season, Episode, Episode Title, Year, Director, Cast, Runtime, Genre, TMDB Rating, Poster Path
BooksDate Read, Title, Author, Year, Pages, My Rating, Avg Rating, Cover URL, ISBN
PodcastsWatch Date, Duration, Show, Episode Title, Overcast URL, Artwork URL

The spreadsheet is the source of truth. Sync scripts only append new entries and never modify existing rows, so you can safely hand-edit data.

4. The HTML Generator

A single Python script (media_log_to_html.py) reads all three sheets and produces one self-contained HTML file. No build tools, no framework, no external CSS — everything is inlined.

python3 media_log_to_html.py media_log.xlsx --title "Watch Log"

The generated page includes:

  • Three tabs: Films & TV, Books, Podcasts
  • Year navigation with left/right arrows and an “All” button (podcasts also have month-level navigation)
  • Search that filters across all fields
  • Sortable columns on every table
  • Collapsible stats section per tab with poster/cover grids, charts, and top lists
  • Summary line per tab (e.g. “I’ve watched 200 films and 500 episodes of 80 TV shows”)
  • Responsive design — columns hide on mobile, tap targets enlarge, tabs stick to top
  • Book titles link to Bookshop.org (by ISBN when available, by title search otherwise)
  • TMDB ratings link to the TMDB page for each film or show

Design details

  • Font: Kepler Std, falling back to Georgia
  • Color palette: black, white, and #cc0000 red for accents and numbers
  • Poster/cover grids load images from TMDB and Goodreads CDNs
  • Charts use Chart.js (loaded from CDN)
  • Stats sections are lazily built on first open to keep initial load fast

5. Deployment

The HTML file is deployed to Cloudflare Pages (free tier). First-time setup:

  1. Create a Cloudflare account
  2. Install Wrangler: npm install -g wrangler
  3. Authenticate: npx wrangler login
  4. Deploy:
    mkdir deploy && cp media_log.html deploy/index.html
    npx wrangler pages deploy deploy/ --project-name your-project-name

The first deploy creates the project automatically. Your page will be live at https://your-project-name.pages.dev.

6. Automation

A shell script chains all the sync scripts, regenerates the HTML, and deploys:

#!/bin/bash
PYTHON=python3
SCRIPTS=~/Scripts

# Sync all sources
$PYTHON "$SCRIPTS/media_log_overcast_sync.py" "$SCRIPTS/media_log.xlsx"
$PYTHON "$SCRIPTS/media_log_trakt_sync.py"
$PYTHON "$SCRIPTS/media_log_goodreads_sync.py" "$SCRIPTS/media_log.xlsx"

# Regenerate and deploy
$PYTHON "$SCRIPTS/media_log_to_html.py" "$SCRIPTS/media_log.xlsx" --title "Watch Log"
mkdir -p /tmp/deploy && cp "$SCRIPTS/media_log.html" /tmp/deploy/index.html
npx wrangler pages deploy /tmp/deploy/ --project-name your-project-name

On macOS, two launchd plists automate the pipeline: one runs the full sync (all three sources) nightly at midnight, and a second runs Overcast-only syncs hourly for faster podcast updates. You could also use cron on Linux or a GitHub Action on a schedule.

7. Requirements

8. Optional: Embed in an Existing Site

If you want to embed the page inside another site (e.g. Squarespace), use an iframe. The generated HTML includes a ResizeObserver that sends its height to the parent window via postMessage, so the iframe can resize dynamically:

<script>
window.addEventListener("message", function(e) {
  if (e.data && e.data.iframeHeight) {
    var iframe = document.querySelector('iframe[src*="your-project"]');
    if (iframe) iframe.style.height = e.data.iframeHeight + "px";
  }
});
</script>

<iframe
  src="https://your-project.pages.dev/"
  width="100%"
  height="1200px"
  frameborder="0"
  scrolling="no"
  style="border:none;display:block;">
</iframe>

9. Build Your Own

The entire Media Log was built using Claude Code. If you want to build something similar, here’s a prompt you could use as a starting point:

Build me a personal media log: a single, self-contained HTML page
that tracks films, TV, books, and podcasts I consume.

Data pipeline:
- Pull film/TV watch history from Trakt, enrich with TMDB metadata
  (cast, director, poster, rating, runtime, genre)
- Pull books from Goodreads RSS, backfill page counts from Open Library
- Pull podcasts from an Overcast OPML export
- Store everything in an Excel spreadsheet (one sheet per media type)
- Write a Python script that reads the spreadsheet and generates the HTML

The generated HTML page should:
- Have three tabs: Films & TV, Books, Podcasts
- Include year-by-year navigation (and month-level navigation for podcasts)
- Be sortable, searchable, and fully responsive
- Include a collapsible stats section per tab with charts and grids
- Be completely self-contained (inline CSS, no build tools)
- Deploy to Cloudflare Pages via Wrangler

Write a shell script that chains the sync scripts, regenerates the
HTML, and deploys—suitable for running as a nightly cron job
or launchd plist.

Swap in your own data sources, tracking apps, or media types. The key architectural idea—API → spreadsheet → static HTML → free hosting—works for almost any personal data you want to publish.

This Page

Inspired by Uses This and the long tradition of the colophon in book publishing.