This site was built using Squarespace’s Om template, part of the Montauk family. The font is Kepler. (This page was inspired by Uses This.)
Social Media
You can find me across a range of platforms, although I don’t post actively on any of them.
BOINC
I donate idle compute time to scientific research via BOINC (Berkeley Open Infrastructure for Network Computing).
BOINC credits are a unit of computing work completed, roughly proportional to CPU/GPU time donated. Recent average credit measures current activity as an exponential average over the past week. My contributions range across 14 projects, over 20 years.
Media Log
My 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, which also largely composed the description in the rest of this section.
Three data sources feed into it:
- Films & TV sync from Trakt, enriched with metadata (cast, director, writer, 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; it is all chained together and run automatically by launchd jobs on macOS. Book titles link to Open Library.
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: a full sync (all three sources) at 2 a.m., and Overcast-only syncs at 2 p.m. and 8 p.m. for faster podcast updates.
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, writer, runtime, genre, poster and rating. You’ll need:
- A Trakt API application (client ID, client secret, access token)
- A TMDB API key (free)
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 is 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:
| Sheet | Key Columns |
|---|---|
| Watch Log | Watch Date, Title, Type, Season, Episode, Episode Title, Year, Director, Writer, Cast, Runtime, Genre, TMDB Rating, Poster Path |
| Books | Date Read, Title, Author, Year, Pages, My Rating, Avg Rating, Cover URL, ISBN |
| Podcasts | Watch Date, Duration, Show, Episode Title, Overcast URL, Artwork URL |
The spreadsheet is the source of truth. The Trakt sync only appends new entries, while the Overcast sync may also update the date of a recently added episode if its finish time changes. Hand-edits to older rows are safe.
4. The HTML Generator
A single Python script (media_log_to_html.py) reads all three sheets and produces one self-contained HTML file. The process involves no build tools, framework or 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 has month-level navigation
- Sortable columns on every table
- Inline poster/cover thumbnails pulled from TMDB for Films & TV, and Open Library for Books
- Summary line per tab (e.g. “This year, I’ve watched 12 films and 40 episodes of 8 television shows” for Films & TV; “This month, I’ve listened to 82 episodes across 32 podcasts, for 54 hours in total. I’ve listened to Odd Lots the most, at 12 hours” for Podcasts)
- Dates shown in British style (e.g. “27 March”) without the year, since the year nav already provides that context
- Responsive design: columns hide on mobile, tap targets enlarge, tabs stick to top
- Clickable rows: clicking anywhere in a row opens the relevant link (TMDB for Films & TV, Overcast for Podcasts, Open Library for Books)
- Keyboard navigation: left/right arrow keys change year on the active tab
- Deep linking: URL hash updates as you navigate (e.g.
#podcasts/2025), so any view can be bookmarked or shared - Sort indicators: ▲/▼ on the active sort column
Design details
- Font: Kepler Std, falling back to Georgia
- Color palette: black, white, and #ff0004 red for accents and links
- Poster/cover thumbnails load from TMDB and Open Library CDNs
5. Deployment
The HTML file is deployed to Cloudflare Pages (free tier). First-time setup:
- Create a Cloudflare account
- Install Wrangler:
npm install -g wrangler - Authenticate:
npx wrangler login - 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: a full sync (all three sources) runs at 2 a.m., and Overcast-only syncs run at 2 p.m. and 8 p.m. for faster podcast updates. You could also use cron on Linux or a GitHub Action on a schedule.
7. Requirements
- Python 3.10+
pip install openpyxl requests- Node.js +
npx wrangler(for Cloudflare deploy) - A TMDB API key (free)
- A Trakt API application
- A Goodreads account (for your user ID)
- A Cloudflare account (free tier)
- Overcast (if tracking podcasts)
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, writer, 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 and month navigation for Films & TV and Podcasts tabs
- Be sortable and fully responsive
- Show poster/cover grids that update with navigation
- Include a collapsible chart section per tab
- 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.