Skip to content

daybologic/twitch-tag-media

Repository files navigation

twitch-tag-media

When you record or archive Twitch streams using yt-dlp, you end up with MP3 files whose names encode everything worth knowing — the streamer, the date, the time, the stream ID — but whose ID3 tags are completely blank. Blank tags mean your music player, media server, or NAS cannot sort, browse, or display the recordings correctly.

twitch-tag-media fixes that. It reads the structured filenames that yt-dlp produces and writes proper ID3v1/v2 tags directly into the MP3 files:

Tag Value
Artist The streamer's display name (normalised from their Twitch handle)
Album <Artist> on Twitch
Track The stream timestamp, used as a unique title
Year The year the stream took place
Comment A provenance note pointing back to this tool

Filename formats understood

ArtistHandle (type) YYYY-MM-DD HH_MM-StreamID.mp3
YYYY-MM-DD-HH-MM-SS-artisthandle.mp3

Artist handles are normalised automatically: underscores become spaces, noise words like Official, Music, and dj are stripped, and a growing table of known Twitch handles maps to the DJ or artist's real display name.

Files are processed concurrently — one child process per file, up to the limit set by --jobs — so large collections tag quickly. Synology NAS index directories (@eaDir) are skipped automatically when recursing.

Any feedback on this is welcome. The author is happy to make reasonable adjustments.

Usage

twitch-tag-media [--atime <S>] [--ctime <S>] [--force] [--help] [--jobs <N>] [--json] [--mtime <S>] [--noop] [--random] [--recursive] [--verbose] [--version] PATH [PATH...]
twitch-tag-media [-f] [-h] [-j <N>] [-J] [-n] [-R] [-r] [-v] [-V] PATH [PATH...]

Add MP3/MP4 tags to media files downloaded from Twitch using yt-dlp. Each PATH may be a file or a directory; directories are walked one level deep unless --recursive is also given.

Option Short Description
--delay <S> -d <S> Pause for S seconds between files (fractional, e.g. 0.001 for 1 ms)
--force -f Rewrite tags even when already up to date
--help -h Display this usage information and exit
--jobs <N> -j <N> Allow parallel I/O (default 1)
--json -J Emit newline-delimited JSON events instead of human-readable text (see below)
--atime <S> Skip files whose atime is too recent (same threshold semantics as --mtime). Not overridden by --force
--ctime <S> Skip files whose ctime is too recent (same threshold semantics as --mtime). Not overridden by --force
--mtime <S> Skip files whose mtime is too recent: values ≤ 604800 are a maximum file age in seconds; larger values are an absolute Unix timestamp cutoff. Not overridden by --force
--noop -n Preview the tags which would be written without modifying any files
--recursive -r Descend into subdirectories
--verbose -v See verbose progress (with elapsed time and ETA), tag information, and a run summary
--version -V Print the version number and exit

JSON output mode

--json is intended for front-end applications and scripts that want to consume tagging progress programmatically — a progress bar UI, a web dashboard, a log aggregator, or any wrapper that needs structured data rather than human-readable text.

With --json and --verbose active together, twitch-tag-media writes one JSON object per line to stdout (newline-delimited JSON / JSON Lines format). Each object has a process envelope and a type-specific payload.

Event: progress

Emitted by the parent process once per file, just before the child is forked. Reports the current percentage, elapsed wall-clock time, and (from the second file onwards) an estimated time to completion based on the file-dispatch rate.

{
  "process":   { "type": "progress", "pct": 42 },
  "file":      "/media/streams/artist (live) 2024-06-01 20_30-123456789.mp3",
  "elapsed_s": 5.2,
  "eta_s":     7.1
}

eta_s is absent on the first file because no rate data is available yet.

Event: tag

Emitted when processing begins on a file. Reports the resolved tag fields before any write occurs.

{
  "process": { "type": "tag", "pct": 42, "pid": 12345 },
  "fields": {
    "artist":  "DJ Example",
    "album":   "DJ Example on Twitch",
    "track":   "2024-06-01 20_30-123456789",
    "year":    "2024",
    "comment": "Generated by github.com/daybologic/twitch-tag-media"
  }
}

Event: changelog

Emitted after comparing the file's existing tags against the values that will be written. Contains a changes array listing every field that differs. If no fields differ but --force is set, a message key is added instead.

{
  "process": { "type": "changelog", "pct": 42, "pid": 12345 },
  "changes": [
    { "field": "artist", "old": "djexample", "new": "DJ Example" },
    { "field": "year",   "old": "",           "new": "2024"       }
  ]
}

pct is the progress percentage (0–100) across the whole run; pid identifies which child process emitted the event, which is useful when --jobs is greater than 1 and events from multiple workers are interleaved on stdout.

Event: stats

Emitted once at the end of a run (requires --verbose). Summarises the entire run.

{
  "process": { "type": "stats" },
  "stats": {
    "total_files":          42,
    "modified_files":       38,
    "skipped_files":         4,
    "total_bytes":    1234567890,
    "modified_bytes": 1100000000,
    "tags_altered":        380,
    "elapsed_s":          45.3,
    "avg_time_per_file_s": 1.079,
    "avg_time_per_mib_s":  0.038
  }
}

skipped_files counts files whose tags were already correct (no write needed). tags_altered is the total number of individual tag fields that differed from the existing values across all modified files. Under --noop, modified_files is always 0 but tags_altered still reflects what would have changed.

Experimental features

EXPERIMENTAL_PROGRESS=1 twitch-tag-media -d <DIR> ...

Setting the EXPERIMENTAL_PROGRESS environment variable enables size-weighted progress percentages. Instead of advancing by an equal step per file, each file contributes weight proportional to its size on disk, so large files move the percentage more than small ones. Feedback welcome.

Dependencies

  • id3v2 — command-line ID3 tagger
  • opustags — command-line Opus tagger

Contributing

Branch naming scheme

When contributing to the project, please fork from the GitHub repository and make all contributions based on the master branch, unless you are specifically patching a bug within an historical release, in which case, branch from the relevant rel/ branch.

Please name your branch using this scheme:

branch description FF allowed rebase allowed
bugfix/<ticket>-<description> A user bug report, with the ticket number NO NO
docs/<description> Documentation changes only NO NO
feature/<description> New functionality NO NO
f/YYYYMM-<description> Legacy features, please don't create new ones NO NO
hotfix/<description> Emergency fixes only NO YES
maint Maintainer branches (features for developers) NO NO
master Mainline merge point for all features NO NO
platform/<uname>/base Specific changes which can't be merged to master NO NO
private/<user-defined> Undocumented hierarchy, maintainer-use only YES YES
rel/X.Y released 1.0, 2.0, 2.1 etc, which contain specific tags vX.Y.Z NO NO
refactor/<description> Not features, design changes NO NO
tests/<description> Unit tests, functional tests, sanity improvements NO NO
translation/<identifier> Translation work NO NO
<user>/<hierarchy> Your GitHub username, followed by recognized hierarchies above NO YES

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors