CouchTimes has no import feature. This converts a TV Time
GDPR data export into a CouchTimes backup file (.couchtimes) that you restore in the app.
Validated against a real library of ~400 shows + ~700 movies: watched episodes/counts, movie
watch dates, follows→active, archived→abandoned, and completed shows correctly reading as
"watched" (via lastEpisodeWatchedDate) all restore cleanly. A handful of items may not resolve
— titles with no TMDB entry (upcoming/unreleased), or shows TMDB reclassified as movies — these
are reported at the end of the run; watched items are essentially always resolvable.
- Input: a TV Time GDPR export — request it at
gdpr.tvtime.com/gdpr/self-service. It's generated
in the portal (a few minutes, depending on how much you've tracked) and downloaded there as a
.zipof ~50 CSVs. Reset your password first if you've forgotten it. All account data is deleted after 15 July 2026 — export before then. - Output:
couchtimes-import-tvtime.couchtimes - Script:
tvtime_to_couchtimes.py— stdlib only, no dependencies
export TMDB_TOKEN="<TMDB v4 Read Access Token>" # free: themoviedb.org/settings/api
python3 tvtime_to_couchtimes.py --export tv-time-export.zip --test-tvdb 71814 # one show first
python3 tvtime_to_couchtimes.py --export tv-time-export.zip # full run--export accepts the raw GDPR zip (CSVs are read straight out of it, nested folders and all)
or an unpacked directory. It defaults to a tvtime-gdpr-data/ dir or a lone *.zip in the cwd.
The output uses a built-in backup envelope (CouchTimes 2.1.0.366) so you don't need to export a
backup first. If a future app version rejects it, export any backup from CouchTimes and pass
--backup <your.couchtimes> (or set $COUCHTIMES_BACKUP) to reuse its envelope instead.
Then in CouchTimes: restore couchtimes-import-tvtime.couchtimes.
TMDB responses are cached in .tmdb-cache/ forever, so re-runs are seconds. Delete the
folder to force a refetch.
Reverse-engineered — the full field-by-field schema (container, envelope, show/season/episode,
movie) and the date rule that makes it work live in FORMAT.md. In short:
raw-DEFLATE'd (wbits=-15) UTF-8 JSON, { appVersion, exportDate, schemaVersion, shows[], movies[] },
shows keyed by TMDB show id, movies by TMDB movie id.
| TV Time source | → CouchTimes |
|---|---|
watched episodes (tracking-prod-records-v2.csv) |
episode watchedStatus + watchCount |
movie watches (tracking-prod-records.csv, type watch/rewatch) |
movie watchedStatus + watchedDate (from created_at) |
follows (followed_tv_show.csv) |
isActive |
| archived shows | isAbandoned |
| latest watch timestamp per show | lastEpisodeWatchedDate (required — see gotchas) |
ID mapping: TV Time stores TheTVDB ids; CouchTimes needs TMDB. Shows resolve
exactly via /find?external_source=tvdb_id. Movies have no id in the export, so they resolve
by title + release year search, then — if that comes up empty — via IMDB: IMDB's
suggestion API indexes the alternate/translated titles TMDB search misses (e.g. Sonnenallee
exported as "Sun Alley"), giving an tt id that TMDB's /find?external_source=imdb_id maps to a
TMDB id. This resolves the translated-title case automatically, no manual work.
If both TMDB and IMDB come up empty — usually unreleased titles with no database entry yet — the
run prints them at the end. To pin one by hand, copy movie_aliases.example.json to
movie_aliases.json and map the title (exactly as TV Time exported it) to its TMDB movie id
(the number in themoviedb.org/movie/<id>):
{ "Some Untitled Sequel": 123456 }Re-run; the cache makes it instant. The script auto-loads movie_aliases.json from the current
directory (or pass --aliases <path>). Watched movies almost always resolve; leftover misses are
typically watchlist/upcoming titles, so nothing you've actually seen is lost.
Not imported (the format can't hold it): episode-level watch dates, custom lists, favorites
(TV Time exports is_favorited=0 for everything), ratings. See FORMAT.md.
Six problems each caused a failed/incorrect import before being fixed — empty dates rejecting the
whole file, lastEpisodeWatchedDate (not episode flags) driving "watched" status, unwatched
specials blocking completion, and more. All are documented with the schema in FORMAT.md.
Restore is a full replace. To sanity-check quickly, run --test-tvdb <id> to carve a
single-show couchtimes-TEST.couchtimes, restore that first and confirm it's accepted with the
right watched status, then restore the full file.