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.(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, soHTTPClientis synchronous (Net::HTTPunder the hood, with the same exponential-backoff retry on 429/500/502/503/504 as the Pythonaiohttp_retry.ExponentialRetry, and redirect-following, whichNet::HTTPdoesn't do by default butaiohttpdoes). Wrap calls in threads, or use theasync/async-httpgems, 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 deprecatedrecognize_song), translated line-for-line intoShazamio::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::FFTis 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. afftw3gem) orNumo::NArrayinstead is a drop-in swap: onlyFFT.rfftneeds to change. - Audio decoding via
ffmpegdirectly, instead ofpydub(which itself just wrapsffmpeg). Same external dependency, one less layer.recognizetreats aStringargument 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::Serializeexposes the common fields as plain RubyStructs — but doesn't reproduce the full model tree.result.raw(or just using the returnedHashdirectly) always gives you everything Shazam sent back.