← Back to the gamebug or idea?Terrapin Trail//Code BibleIIhow the game works, archive to van
Reel 13 · 7 min read

The Live Music Archive pipeline

Bands, shows, nights and tours from archive.org; the legendary night; offline geocoding; streaming songs with taper credits.

Everything playable comes from the Internet Archive's Live Music Archive: about 7,800 band collections and hundreds of thousands of fan-made recordings. archive.py turns that into bands, years, tours and songs, and caches every raw record it fetches.

Bands

The band list is one advanced-search query for every collection inside etree (the Live Music Archive), sorted by downloads, cached for a day:

●archive.py : 411–440trace ↗
411def list_bands(data_dir: Path) -> list[dict]:
412    """Every band collection on the Live Music Archive, most downloaded first.
413
414    Cached under ``DATA_DIR/bandlist.json`` for a day: ``[{id, name, items}]``.
415    """
416    cache = data_dir / "bandlist.json"
417    if cache.exists() and time.time() - cache.stat().st_mtime < MAX_BAND_LIST_AGE:
418        try:
419            return json.loads(cache.read_text())["bands"]
420        except (ValueError, KeyError):
421            pass
422    params = [
423        ("q", "collection:etree AND mediatype:collection"),
424        ("fl[]", "identifier"),
425        ("fl[]", "title"),
426        ("fl[]", "item_count"),
427        ("sort[]", "downloads desc"),
428        ("rows", "20000"),
429        ("output", "json"),
430    ]
431    data = _get_json("https://archive.org/advancedsearch.php?" + urllib.parse.urlencode(params))
432    bands = [
433        {"id": d["identifier"], "name": d.get("title") or d["identifier"], "items": int(d.get("item_count") or 0)}
434        for d in data.get("response", {}).get("docs", [])
435        if d.get("identifier")
436    ]
437    bands = [b for b in bands if b["items"] > 0]
438    cache.parent.mkdir(parents=True, exist_ok=True)
439    cache.write_text(json.dumps({"fetched": time.time(), "bands": bands}))
440    return bands
list_bands: one query, cached as bandlist.json. The browser sorts it A to Z (sort_name drops 'The' and punctuation) or keeps the most-played order.

Shows, nights, tours

load_shows pulls every recording in a band's collection through the archive's paginated scrape API and caches the raw list for 30 days. Many recordings can exist for one night, so concert_nights collapses them to one canonical night each and drops studio sessions and compilations. A year's nights then split into tours wherever there is a gap of more than eleven days:

●archive.py : 293–307trace ↗
293def _split_into_tours(nights: list[dict]) -> list[list[dict]]:
294    """Split date-sorted nights into runs, breaking where a long gap appears."""
295    runs: list[list[dict]] = []
296    current: list[dict] = []
297    prev: date_cls | None = None
298    for n in nights:
299        d = date_cls.fromisoformat(n["ymd"])
300        if prev is not None and (d - prev).days > _TOUR_GAP_DAYS:
301            runs.append(current)
302            current = []
303        current.append(n)
304        prev = d
305    if current:
306        runs.append(current)
307    return runs
Tours are runs of nights with no gap longer than _TOUR_GAP_DAYS.

build_tour turns one run into the game's tour shape: it keeps the first night, the last, and the legendary night (the one with the most recordings, which is a good proxy for how celebrated a show is), samples the rest down to twelve stops, and geocodes the miles between them.

Placing towns on the map

City text such as "Alta, WY" is resolved offline against a bundled city index (towns of about 15,000 people and up). A state was named but the town isn't in the index? Rather than accept a foreign namesake, the resolver falls back to the middle of that state:

●geocode.py : 166–177trace ↗
166def resolve(coverage: str | None) -> Place | None:
167    """Resolve a coverage string to a Place, falling back to the state's centre for
168    a US town too small for the city index."""
169    place = _resolve_city(coverage)
170    if place is not None or not coverage:
171        return place
172    parts = [p.strip() for p in coverage.split(",")]
173    st = _region_to_us_state(parts[1]) if len(parts) > 1 and parts[1].strip() else None
174    if st and st in _STATE_CENTERS:
175        lat, lon = _STATE_CENTERS[st]
176        return Place(parts[0], lat, lon, st, "US", 0)
177    return None
resolve: the city index first; for a small US town, the state's centre, which is close enough to price the drive.

The songs

show_tracks reads one recording's file list and keeps the MP3 derivatives the archive makes for every upload, in track order, with the taper and transfer credits from its metadata:

●archive.py : 554–591trace ↗
554def show_tracks(identifier: str, data_dir: Path) -> dict:
555    """A recording's playable songs plus who taped it, for the in-game player."""
556    meta = fetch_metadata(identifier, data_dir)
557    md = meta.get("metadata", {})
558    files = meta.get("files", [])
559    mp3 = [f for f in files if f.get("format") in ("VBR MP3", "MP3", "64Kbps MP3", "128Kbps MP3")]
560    # Prefer the VBR derivatives (one per song); fall back to whatever MP3s exist.
561    vbr = [f for f in mp3 if f.get("format") == "VBR MP3"]
562    picked = vbr or mp3
563    tracks = []
564    for f in picked:
565        tracks.append(
566            {
567                "title": _first(f.get("title")) or f["name"].rsplit(".", 1)[0],
568                "length": _clock(_first(f.get("length"))),
569                "track": _first(f.get("track")),
570                "url": "https://archive.org/download/" + urllib.parse.quote(identifier) + "/" + urllib.parse.quote(f["name"]),
571            }
572        )
573
574    def track_key(t: dict):
575        m = re.match(r"\d+", t["track"] or "")
576        return (int(m.group()) if m else 9999, t["url"])
577
578    if all(t["track"] for t in tracks):
579        tracks.sort(key=track_key)
580    return {
581        "identifier": identifier,
582        "title": _first(md.get("title")),
583        "date": _first(md.get("date"))[:10],
584        "venue": _first(md.get("venue")),
585        "city": _first(md.get("coverage")),
586        "taper": _first(md.get("taper")),
587        "transferer": _first(md.get("transferer")),
588        "source": _first(md.get("source"))[:200],
589        "details": "https://archive.org/details/" + urllib.parse.quote(identifier),
590        "tracks": tracks,
591    }
show_tracks: one entry per song with a direct archive.org stream URL, plus who taped and transferred it.

Those URLs are played straight from archive.org: by the browser's Listen panel, and by the van stereo inside the game (chapter 17).

Key takeaways
  • Bands, shows and songs all come from archive.org and are cached raw under the data directory.
  • A tour is a run of nights with no long gap; its legendary night is the most-recorded one.
  • Small US towns land at their state's centre instead of a same-named city abroad.
  • Songs stream directly from archive.org, credited to the taper who recorded them.