Skip to content

Read and write the iTunes tmpo atom for M4A BPM#74

Merged
pixelate merged 1 commit into
mainfrom
write-m4a-bpm-tmpo
Jun 20, 2026
Merged

Read and write the iTunes tmpo atom for M4A BPM#74
pixelate merged 1 commit into
mainfrom
write-m4a-bpm-tmpo

Conversation

@pixelate

Copy link
Copy Markdown
Owner

Why

wavesync did all tag I/O through ffmpeg/ffprobe, but ffmpeg can neither read nor write the iTunes tmpo atom:

  • Writing -metadata BPM=… (with or without +use_metadata_tags, under any muxer/key) never emits a tmpo atom. The old +use_metadata_tags path instead wrote a non-standard mdta/BPM key.
  • ffprobe doesn't surface an existing tmpo atom as any tag.

tmpo is the de-facto standard for BPM in M4A (Apple Music, iTunes, Mp3tag, Picard), and it's what AVFoundation — and therefore audioplayer — reads. The mdta/BPM key AVFoundation exposes only as an opaque index, so it couldn't be mapped to BPM.

What

New Wavesync::Mp4Tmpo — a hand-rolled MP4 atom reader/writer in the style of AcidChunk/CueChunk:

  • Walks moov → udta → meta → ilst → tmpo → data, reading/writing BPM as a 16-bit big-endian integer.
  • Creates any missing udta/meta (with an mdir handler)/ilst boxes, and replaces an existing tmpo rather than duplicating it.
  • Handles the meta FullBox quirk (vs. plain QuickTime meta).
  • When moov precedes mdat, patches every stco/co64 chunk offset by the size delta so audio data stays valid.

Audio#bpm_from_m4a and #write_bpm_to_m4a now use Mp4Tmpo, and the mdta-producing movflags +use_metadata_tags line is dropped.

Testing

  • New Mp4TmpoTest (round-trip, no-duplicate-atom, still-decodable, preserves existing string tags) + existing audio_test m4a cases. Full suite: 417 runs, 0 failures. Rubocop and Steep clean.
  • Verified end-to-end on a clean fixture, an ffmpeg-mdta file, and an iTunes moov-before-mdat file: round-trips, stays decodable, and AVFoundation reads itsk/tmpo.

Note

The reader now reads tmpo only. Files previously tagged by wavesync with the old mdta/BPM key will read as no-BPM until re-tagged (re-running write_bpm migrates them).

Companion change in audioplayer reads the tmpo atom for display.

🤖 Generated with Claude Code

ffmpeg/ffprobe can neither read nor write the iTunes `tmpo` atom, so the
previous `movflags +use_metadata_tags` path wrote BPM as a non-standard
`mdta/BPM` key that AVFoundation (and audioplayer) can't read.

Add `Mp4Tmpo`, a hand-rolled MP4 atom reader/writer in the style of
`AcidChunk`/`CueChunk`. It walks moov → udta → meta → ilst → tmpo → data,
reading/writing BPM as a 16-bit big-endian integer — the form Apple Music
writes and AVFoundation reads. It creates any missing udta/meta(mdir)/ilst
boxes, replaces an existing tmpo rather than duplicating it, handles the
meta FullBox quirk, and patches stco/co64 chunk offsets when moov precedes
mdat so audio stays valid.

Wire `Audio#bpm_from_m4a`/`#write_bpm_to_m4a` to `Mp4Tmpo` and drop the
`mdta`-producing movflags line.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@pixelate
pixelate merged commit 93ca9e9 into main Jun 20, 2026
3 checks passed
@pixelate
pixelate deleted the write-m4a-bpm-tmpo branch June 20, 2026 12:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant