shazamio-rb

A Ruby port of ShazamIO: a client for the reverse engineered Shazam API. Recognize songs from an audio file, search tracks/artists, and fetch track metadata.

Install

Copy this directory into your project, or build the gem locally:

gem build shazamio.gemspec
gem install ./shazamio-rb-0.1.0.gem

shazamio-rb is stdlib-only (net/http, json, zlib, base64, ...) — no gems are required at runtime. recognize additionally shells out to the ffmpeg binary to decode audio files, exactly like the Python project does under the hood via pydub.

Usage

require "shazamio"

shazam = Shazamio::Shazam.new

# Recognize a song from a file (mp3, wav, ogg, ... anything ffmpeg reads).
# Confirmed working end-to-end: recognized a real track against the live API.
result = shazam.recognize("song.mp3")
puts result.dig("track", "title")

# Track info
shazam.track_about(552_406_075)

# Search
shazam.search_track("Alan Walker", limit: 5)
shazam.search_artist("Alan Walker", limit: 5)

# Related tracks / listening counters
shazam.related_tracks(559_284_007, limit: 10)
shazam.listening_counter(559_284_007)
shazam.listening_counter_many([559_284_007, 552_406_075])

Every method returns the raw Shazam JSON response as a Ruby Hash. For the handful of fields most people actually want off a track payload, there's a small helper:

track_hash = shazam.track_about(552_406_075)
info = Shazamio::Serialize.track(track_hash["track"] || track_hash)
info.title        #=> "Ale Jazz"
info.spotify_url
info.apple_music_url
info.raw           # the untouched Hash, for anything not mapped above

More runnable examples live in examples/.

Not included (broken upstream, not a porting bug)

The Python project also has artist_about, artist_albums, search_album, and every "top tracks" chart method (top_world_tracks, top_country_tracks, top_city_tracks, top_world_genre_tracks, top_country_genre_tracks). All of these hit URLs under https://www.shazam.com/services/amapi/v1/catalog/....

That whole path is currently returning 405 Method Not Allowed for GET requests on Shazam's own servers — confirmed by a report of the exact same 405 on the exact same path, hit by Shazam's own web app in a browser (not by any third-party client): see this Apple Community thread.

Porting notes

A few things a straight Python→Ruby translation can't carry over 1:1:

  • No async runtime. The Python client is built on asyncio/aiohttp. Ruby's standard library has no equivalent, so HTTPClient is synchronous (Net::HTTP under the hood, with the same exponential-backoff retry on 429/500/502/503/504 as the Python aiohttp_retry.ExponentialRetry, and redirect-following, which Net::HTTP doesn't do by default but aiohttp does). Wrap calls in threads, or use the async/async-http gems, if you need concurrency.
  • No Rust extension for recognition. Python's recognize() calls into a compiled Rust library (shazamio_core) for speed; there's no Ruby equivalent available offline. This port always uses the algorithm from the Python project's pure-Python fallback (algorithm.py, what backs the deprecated recognize_song), translated line-for-line into Shazamio::SignatureGenerator. It produces the same signatures, just slower — expect a handful of seconds of Ruby-side CPU work per ~10s audio clip, since there's no NumPy/FFTW here either (see next point). It has been confirmed to work end-to-end against the live API.
  • Pure-Ruby FFT. Shazamio::FFT is a small iterative Cooley-Tukey FFT (power-of-two sizes only, which is all the algorithm ever needs). It's plain Ruby, not vectorized/SIMD like NumPy. If you process a lot of audio, dropping in a binding to FFTW (e.g. a fftw3 gem) or Numo::NArray instead is a drop-in swap: only FFT.rfft needs to change.
  • Audio decoding via ffmpeg directly, instead of pydub (which itself just wraps ffmpeg). Same external dependency, one less layer. recognize treats a String argument as a file path; to pass raw audio bytes instead of a path, wrap them (e.g. StringIO.new(bytes)) so there's no ambiguity between "a path" and "some bytes that happen to be a String".
  • Schemas simplified. The Python project maps every response onto a deep tree of Pydantic models (shazamio/schemas/**, thousands of lines). This port keeps the ergonomics people actually use — Shazamio::Serialize exposes the common fields as plain Ruby Structs — but doesn't reproduce the full model tree. result.raw (or just using the returned Hash directly) always gives you everything Shazam sent back.