Scan an archive of audio files and write metadata back

Source: AudD•

Scan an archive of audio files and write metadata back

Walk a directory of audio files, identify each one with AudD, and write artist, title, album, and ISRC to a resumable CSV.

This recipe walks a directory of audio files, identifies each one with AudD, and writes the results — artist, title, album, ISRC, and more — to a CSV with one row per file. It’s for anyone sitting on an unlabeled archive: a folder of track01.mp3-style rips, field recordings, an old library with broken tags, or a set of files you need to audit against what they actually contain.

The script is built to survive a real run over thousands of files: it processes files concurrently with a bounded worker pool, is friendly to rate limits, skips files already recorded in the CSV so you can stop and restart, and records no-match and unreadable files explicitly instead of silently dropping them.

What you’ll build

A single Python script, scan_archive.py, that you point at a directory. It:

  • Walks the directory for audio files.
  • Loads any existing CSV and skips files already done (resumability).
  • Recognizes each remaining file through a bounded thread pool.
  • Writes one CSV row per file as results come in — matched, no_match, or error — so a crash never loses completed work.

For short clips, the script uses the standard endpoint (POST https://api.audd.io/), which returns one match in under 2 seconds. For long files — full-length tracks, mixes, podcasts — you switch to the enterprise endpoint, which chunks server-side and returns every track; the recipe shows both and explains when to flip.

Set limit on every enterprise call. The enterprise endpoint bills per 12 seconds of audio processed. Across a whole archive an unbounded call can ingest hours per file. Keep a limit and only raise it when you know the cost on your inputs.

Set limit on every enterprise call. The enterprise endpoint bills per 12 seconds of audio processed. Across a whole archive an unbounded call can ingest hours per file. Keep a limit and only raise it when you know the cost on your inputs.

A one-off scan needs no code. The AudD CLI writes a folder’s results to a CSV, resumes an interrupted run, and skips files it has already identified: audd recognize ./archive --max-files 500 --format csv > archive.csv. Build the script below when the scan is part of your own pipeline.

A one-off scan needs no code. The AudD CLI writes a folder’s results to a CSV, resumes an interrupted run, and skips files it has already identified: audd recognize ./archive --max-files 500 --format csv > archive.csv. Build the script below when the scan is part of your own pipeline.

Prerequisites

  • An API token from dashboard.audd.io. ISRC and UPC in responses require a Startup plan or higher; the standard endpoint returns the core tags on any plan.
  • Python 3.10+ with the official SDK: pip install audd
  • A directory of audio files. Supported audio formats: MP3, WAV, FLAC, M4A, OGG, AAC, WMA, AIFF.

Walkthrough

Step 1: Recognize one local file

Start with the smallest unit: identify a single file on disk and print the tags. The SDK accepts a path directly. (audd.tech/example.mp3 is a known track if you want to confirm the path before pointing at your own files.)

recognize returns a RecognitionResult on a match and None on a successful call that didn’t match — that distinction is the whole reason the CSV has both a no_match and an error status. A None is not a failure; it’s a clean “we don’t know this file.”

Step 2: Walk the directory

Collect the files to process. Match by extension so you don’t hand the SDK a cover-art JPEG or a .cue sheet.

rglob("*") recurses into subdirectories; drop to glob("*") if you only want the top level.

Step 3: Make it resumable

Before scanning anything, read the CSV you’re writing to and remember which files are already done. On a restart, those are skipped. The file’s absolute path is the key.

Because every file gets a row — including no_match and error — a resumed run won’t retry files that already failed to decode. If you want to retry errors on the next run, filter already_done to rows where status == "matched" or status == "no_match" only.

Step 4: Recognize one file into a row

Wrap a single file’s recognition so it always returns a CSV row, whatever happens — match, no match, or an unreadable/unreachable file. This is the unit the worker pool runs.

Passing the open file handle (rb) lets the SDK reopen it on retry. A file that can’t be decoded as audio raises AudDInvalidAudioError, which becomes an error row rather than aborting the run. OSError catches a file that vanished or has bad permissions.

Step 5: Run a bounded worker pool and stream rows to the CSV

Recognition is I/O-bound (you’re waiting on the network), so a thread pool gives real concurrency. Bound it. A small pool keeps you friendly to the API’s rate limits and keeps memory flat over a large archive. Write each row as its result arrives so an interrupted run keeps everything done so far.

Run it:

You’ll see one line per file as it completes — matched, no_match, or error — and a results.csv that grows row by row. Stop it with Ctrl-C and re-run the same command; it picks up where it left off because completed paths are already in the CSV.

Step 6: Handle long files with the enterprise endpoint

The standard endpoint is for short clips and has a 10 MB file-size cap. For full-length tracks, mixes, or podcasts — anything long or over 10 MB — use the enterprise endpoint. It chunks the file server-side and returns every matched track, so one input file can produce several rows. Swap scan_one’s recognition call:

When a file can produce many rows, write the list and adjust resumability to key on path (any row with that path means the file is done). For a long-file archive where you only need to confirm whether a file contains known music rather than the full tracklist, add every=5 to recognize every fifth chunk and a low limit to stop early — that cuts metered audio sharply.

What you get back

A CSV with one row per file (or per match, for enterprise long files):

no_match means AudD ran the file and recognized nothing — expected for voice memos, ambient recordings, or obscure material not in the 160-million-track database. It is not the same as error, which means the file couldn’t be read or the API call failed.

Handling errors

The script already routes every per-file failure into an error row. The errors that need attention at the run level — not per file — are:

  • Authentication errors (AudDAuthenticationError) — bad or missing token. This fails on every file, so catch it once at startup rather than recording thousands of identical error rows.
  • Quota / rate-limit errors (AudDQuotaError, AudDRateLimitError) — you hit a request or rate limit. Lower MAX_WORKERS, raise THROTTLE_SECONDS, and re-run; resumability means you only retry what didn’t finish.
  • Invalid-audio errors (AudDInvalidAudioError) — a single unreadable file. Recorded as an error row; the run continues.
  • Connection errors (AudDConnectionError) — transient network issues. The SDK retries pre-upload network failures automatically; a persistent one lands in the error column for that file.

To stop the whole run when the token is clearly wrong, hoist the authentication check above the pool:

Going further

  • Write tags back into the files. Once results.csv is correct, a second pass with mutagen can write artist / title / album into each file’s metadata. Keep recognition and tagging separate so you can review the CSV before mutating files.
  • Add streaming links. Pass return_metadata=["apple_music", "spotify"] to recognize to populate provider blocks and add columns for direct service URLs. Each provider adds latency, so request only what you’ll store.
  • Read fields outside the typed surface. Any server field the SDK doesn’t expose as a typed property is available on the result’s model_extra map (the Python SDK’s models are Pydantic) — add it as a CSV column if you need it.
  • Match against your own catalog. To identify files against tracks you own rather than the public database (auditing leaks, finding sample reuse), upload your tracks to a custom catalog first — contact [email protected] for access.
  • Build a copyright scanner for user-uploaded content
  • Build a Discord bot that identifies songs in voice channels
  • Python SDK docs
  • API reference

What this article says