<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Freqblog</title>
    <description>The latest articles on DEV Community by Freqblog (@birrings).</description>
    <link>https://dev.to/birrings</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3873922%2F4ee0520d-f25c-4231-b050-9aaf049e8ae7.png</url>
      <title>DEV Community: Freqblog</title>
      <link>https://dev.to/birrings</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/birrings"/>
    <language>en</language>
    <item>
      <title>Migrating from Spotify Audio Features: a Field-by-Field Threshold Guide</title>
      <dc:creator>Freqblog</dc:creator>
      <pubDate>Mon, 11 May 2026 22:14:39 +0000</pubDate>
      <link>https://dev.to/birrings/migrating-from-spotify-audio-features-a-field-by-field-threshold-guide-2e9b</link>
      <guid>https://dev.to/birrings/migrating-from-spotify-audio-features-a-field-by-field-threshold-guide-2e9b</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Spotify killed &lt;code&gt;/audio-features&lt;/code&gt; on 2024-11-27. FreqBlog Music API offers a drop-in at &lt;code&gt;api.freqblog.com/v1/audio-features/{id}&lt;/code&gt; — same path, same response shape. The &lt;em&gt;numbers&lt;/em&gt; are ours (signal analysis via librosa + Essentia, not Spotifys ML classifiers), so a few field distributions shift. This is the field-by-field guide to re-tuning, with real distribution data from our ~57,000-track catalog.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;This is a syndicated mirror — the canonical version with full formatting is at &lt;a href="https://freqblog.com/blog/spotify-audio-features-migration-thresholds/" rel="noopener noreferrer"&gt;https://freqblog.com/blog/spotify-audio-features-migration-thresholds/&lt;/a&gt; (where the table renders properly).&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The migration shape
&lt;/h2&gt;

&lt;p&gt;You swapped the host from &lt;code&gt;api.spotify.com&lt;/code&gt; to &lt;code&gt;api.freqblog.com&lt;/code&gt;. Your existing &lt;code&gt;GET /v1/audio-features/{id}&lt;/code&gt; handler still parses the response unchanged, the unit tests pass. Then your downstream &lt;code&gt;if features.danceability &amp;gt; 0.7&lt;/code&gt; recommender starts surfacing weird tracks. Your &lt;code&gt;acousticness &amp;gt; 0.5&lt;/code&gt; rule fires on basically &lt;em&gt;everything&lt;/em&gt;. Your &lt;code&gt;liveness &amp;gt; 0.8&lt;/code&gt; filter rejects the whole catalog.&lt;/p&gt;

&lt;p&gt;The migration is byte-compatible at the &lt;strong&gt;shape&lt;/strong&gt; level. Its &lt;strong&gt;directional&lt;/strong&gt; at the value level. Spotify built their audio features by training ML classifiers on a labelled corpus; we compute ours from signal analysis. Same names, different beasts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Threshold cheat sheet (catalog medians over ~8k tracks)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;bpm&lt;/code&gt; / &lt;code&gt;tempo&lt;/code&gt; — median &lt;strong&gt;118.7&lt;/strong&gt; — drop-in. Plus &lt;code&gt;bpm_alt&lt;/code&gt; corrects Spotifys known half-time bug.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;key&lt;/code&gt; (0–11 / -1) — drop-in. Returned as &lt;code&gt;key_int&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;mode&lt;/code&gt; (0/1) — drop-in.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;energy&lt;/code&gt; — median &lt;strong&gt;0.73&lt;/strong&gt; — shifted up slightly; bump thresholds ~0.05–0.1.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;valence&lt;/code&gt; — median &lt;strong&gt;0.48&lt;/strong&gt; — closest to Spotifys distribution. Minimal re-tune.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;danceability&lt;/code&gt; — median &lt;strong&gt;0.74&lt;/strong&gt; — shifted up. Old 0.65 ≈ ours 0.80.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;acousticness&lt;/code&gt;&lt;/strong&gt; — median &lt;strong&gt;0.98&lt;/strong&gt; — &lt;em&gt;do not&lt;/em&gt; use as is-acoustic. Discriminates near 0.99.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;instrumentalness&lt;/code&gt;&lt;/strong&gt; — median &lt;strong&gt;0.23&lt;/strong&gt; — signal-shape proxy, not a vocal classifier.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;liveness&lt;/code&gt;&lt;/strong&gt; — median &lt;strong&gt;1.00 (saturated)&lt;/strong&gt; — currently not diagnostic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;speechiness&lt;/code&gt;&lt;/strong&gt; — median &lt;strong&gt;0.45&lt;/strong&gt; — shifted up ~5–10×. Old 0.66 ≈ ours &amp;gt;0.85.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;loudness&lt;/code&gt; — median &lt;strong&gt;-14 dB&lt;/strong&gt; — 3–6 dB lower than Spotify (30s window vs full-track BS.1770); use relatively, not absolutely.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;time_signature&lt;/code&gt; — always &lt;strong&gt;4&lt;/strong&gt; in practice. Treat as informational, not as a filter.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why &lt;code&gt;acousticness&lt;/code&gt; is 0.98 for everything
&lt;/h2&gt;

&lt;p&gt;Our formula is &lt;code&gt;1 - clip(spectral_flatness / 0.15, 0, 1)&lt;/code&gt;. Low spectral flatness means the signal has strong tonal/harmonic peaks — which &lt;em&gt;most music&lt;/em&gt; has, acoustic or electric. ~99% of our catalog scores &amp;gt;0.9. If your Spotify code said &lt;code&gt;acousticness &amp;gt; 0.7 = acoustic track&lt;/code&gt; and triggered on ~20% of your library, the same threshold under FreqBlog will trigger on ~99% of it. The field is not a useless signal — it discriminates between tonal music and noise-floor content (white-noise tracks, pure ambient, recordings with heavy distortion) — but its useful threshold is up around 0.99.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why &lt;code&gt;liveness&lt;/code&gt; saturates
&lt;/h2&gt;

&lt;p&gt;Formula: &lt;code&gt;clip(noise_floor_ratio * 6.0, 0, 1)&lt;/code&gt; where &lt;code&gt;noise_floor_ratio&lt;/code&gt; is the 5th-percentile RMS divided by the 95th-percentile RMS. The intuition is that live recordings have an elevated noise floor. The problem: modern mastered music has heavy dynamic-range compression, which compresses the RMS percentiles together — and the ×6 multiplier saturates at 1.0 almost immediately. Until we ship a better detector, drop the &lt;code&gt;liveness&lt;/code&gt; filter.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we get right
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;BPM&lt;/strong&gt; + &lt;strong&gt;key/mode/key_int&lt;/strong&gt; — Essentia is mature DSP; comparable to Spotifys ML for the common cases. Plus &lt;code&gt;bpm_alt&lt;/code&gt; is a &lt;em&gt;documented improvement&lt;/em&gt; over Spotifys known half-time detection bug (Blinding Lights: Spotify said 85; perceived is 171; we ship 85 in &lt;code&gt;bpm&lt;/code&gt; and 171 in &lt;code&gt;bpm_alt&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Camelot + Open Key&lt;/strong&gt; — first-class fields, ready for harmonic-mixing tooling.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Identifier flexibility&lt;/strong&gt; — &lt;code&gt;/v1/audio-features/{id}&lt;/code&gt; accepts Spotify track IDs, ISRCs (with or without hyphens), &lt;code&gt;spotify:track:…&lt;/code&gt; URIs, or &lt;code&gt;open.spotify.com/track/…&lt;/code&gt; URLs. Pass whichever your data layer gives you.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Long-tail backfill&lt;/strong&gt; — if a track isnt in the catalog, our backfill ingests it in 30s–2min and emails you when its ready. Spotifys API never had this.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The full doc (with worked examples + the formula per field) is here:
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://freqblog.com/blog/spotify-audio-features-migration-thresholds/" rel="noopener noreferrer"&gt;freqblog.com/blog/spotify-audio-features-migration-thresholds/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Free tier is 1,000 requests/month, no card. Comments / use-case feedback welcome — the caveated fields (acousticness/liveness etc.) are where concrete customer demand drives the next round of improvements.&lt;/p&gt;

</description>
      <category>spotify</category>
      <category>api</category>
      <category>music</category>
      <category>audio</category>
    </item>
    <item>
      <title>AcousticBrainz Alternative in 2026: The Honest Insider's Guide</title>
      <dc:creator>Freqblog</dc:creator>
      <pubDate>Fri, 08 May 2026 17:29:38 +0000</pubDate>
      <link>https://dev.to/birrings/acousticbrainz-alternative-in-2026-the-honest-insiders-guide-28kn</link>
      <guid>https://dev.to/birrings/acousticbrainz-alternative-in-2026-the-honest-insiders-guide-28kn</guid>
      <description>&lt;p&gt;If you were one of the developers, researchers, or hobbyists who built on AcousticBrainz, you already know the story. The Music Technology Group at Universitat Pompeu Fabra announced the shutdown on &lt;strong&gt;16 February 2022&lt;/strong&gt;, took the live API offline, and published the entire dataset as a one-time public dump. Four years later, there's still nothing exactly like it.&lt;/p&gt;

&lt;p&gt;This post is the honest insider's view of the post-AB landscape — written by a team that uses the frozen AcousticBrainz dump in production right now, as one of four fallback layers in our music-features API. We know what works in the dump, what doesn't, and what the realistic alternatives look like in 2026.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you actually lost
&lt;/h2&gt;

&lt;p&gt;AcousticBrainz had three things going for it that nothing has fully replaced:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Free, public, scriptable.&lt;/strong&gt; No API key, no rate limit, no sign-up. &lt;code&gt;GET /api/v1/&amp;lt;mbid&amp;gt;/low-level&lt;/code&gt; returned ~120 fields per recording. Researchers could pull millions of rows for a paper without negotiating commercial terms.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MBID-keyed.&lt;/strong&gt; Every track was identified by its MusicBrainz ID, the open community-maintained identifier. That meant data from AcousticBrainz could be joined cleanly to MusicBrainz, Discogs, ListenBrainz, lyrics databases — the whole open-data ecosystem.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Crowd-contributed.&lt;/strong&gt; Anyone could run the AcousticBrainz client on their own audio collection and submit features back. The dataset grew from real personal libraries, not a label-licensed catalog.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;None of the commercial replacements has all three. Most have none.&lt;/p&gt;

&lt;h2&gt;
  
  
  The frozen dump — still extremely useful
&lt;/h2&gt;

&lt;p&gt;This is the under-appreciated fact: &lt;strong&gt;the entire AcousticBrainz dataset is still freely downloadable&lt;/strong&gt;. The &lt;a href="https://acousticbrainz.org/download" rel="noopener noreferrer"&gt;official dump page&lt;/a&gt; hosts both the high-level (mood, genre, instrument-detection) and low-level (BPM, key, MFCCs, ~120 descriptors) tarballs as of June 2022, plus per-month deltas up to the shutdown.&lt;/p&gt;

&lt;p&gt;What you get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;~7.5M unique recordings&lt;/strong&gt; by MBID, with one or more contributed analyses each&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;~120 low-level descriptors per track&lt;/strong&gt; — spectral centroid, MFCCs, rhythm features, tonal features, dynamic complexity, all the numbers Essentia's &lt;code&gt;MusicExtractor&lt;/code&gt; outputs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;11 high-level classifier outputs&lt;/strong&gt; per track — genre, mood (happy/sad/aggressive/relaxed/party), instrumentalness, danceability, etc.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The AcousticBrainz JSON schema&lt;/strong&gt; — same one the live API used, so existing client code works against the dump with zero changes if you stand up your own static endpoint&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Practical setup: serve the dump as a local API
&lt;/h3&gt;

&lt;p&gt;The dump is a giant tarball of one-JSON-per-MBID. The cleanest pattern is to extract it into SQLite and serve via a thin wrapper:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Pseudocode for the loader
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sqlite3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tarfile&lt;/span&gt;

&lt;span class="n"&gt;conn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sqlite3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ab_features.db&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    CREATE TABLE ab_features (
        mbid TEXT PRIMARY KEY,
        bpm REAL,
        key TEXT,
        scale TEXT,
        danceability REAL,
        average_loudness REAL,
        dynamic_complexity REAL,
        onset_rate REAL,
        tuning_frequency REAL,
        mood_happy REAL,
        mood_sad REAL,
        mood_aggressive REAL,
        mood_relaxed REAL,
        mood_party REAL,
        genre TEXT,
        instrumentalness REAL,
        full_json TEXT
    )
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;tarfile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;acousticbrainz-lowlevel-features-20220623.tar.zst&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;tar&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;member&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;tar&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;endswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tar&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extractfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="n"&gt;mbid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;INSERT OR IGNORE INTO ab_features ...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mbid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Expect the resulting SQLite to land around 2.2 GB once you've extracted the columns you actually use. Full-JSON-per-row blows it up to ~50 GB; only do that if you need every descriptor.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The dump is frozen at June 2022.&lt;/strong&gt; Nothing released after that has values. For 2024-2026 releases, you'll need a live source. Use the dump as the historical layer of a tiered system — check it first, fall back to live analysis on miss.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  The MBID problem
&lt;/h3&gt;

&lt;p&gt;The dump is keyed by MBID, but most of your queries will arrive with &lt;em&gt;artist + title&lt;/em&gt; strings, not MBIDs. Resolution is a separate problem:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Hit the live MusicBrainz API: &lt;code&gt;GET /ws/2/recording/?query=artist:"&amp;lt;artist&amp;gt;" AND recording:"&amp;lt;title&amp;gt;"&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Score the candidates — usually the first hit is right but featured-artist strings ("Mark Ronson featuring Bruno Mars") and remixes/covers cause noise&lt;/li&gt;
&lt;li&gt;If you find a match, take the MBID and look it up in your AB dump table&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Realistic hit rate from name-only resolution to AB-dump features: &lt;strong&gt;~50%&lt;/strong&gt;. Half your tracks will resolve cleanly, the other half will be misses for one of: no MBID assigned, MBID exists but track not in AB, featured-artist confusing the resolver, or a release after June 2022.&lt;/p&gt;

&lt;h2&gt;
  
  
  The live alternatives
&lt;/h2&gt;

&lt;p&gt;Five categories, ranked from "closest to AB's spirit" to "closest in functionality":&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Build your own with Essentia
&lt;/h3&gt;

&lt;p&gt;AcousticBrainz &lt;em&gt;was&lt;/em&gt; Essentia + crowd contributions. The same toolkit is open-source, actively maintained, and runs on a VPS. &lt;code&gt;MusicExtractor&lt;/code&gt; takes a 30-second audio clip and returns the same ~120 fields AB stored. &lt;strong&gt;Caveat:&lt;/strong&gt; you need the audio. The dump worked because users contributed local libraries; your replacement needs an audio source — iTunes 30-second previews work but rate-limit hard, full-track licenses cost money.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Self-host with the dump as primary, Essentia as fallback
&lt;/h3&gt;

&lt;p&gt;This is what we do at FreqBlog. &lt;code&gt;ab_features.db&lt;/code&gt; covers ~50% of inbound name-based queries via the MBID resolution path; for misses we run Essentia on iTunes preview clips and cache the result. The two layers complement each other — the dump catches anything pre-2022 that has an MBID; Essentia catches everything else with a commercial preview. Coverage hits ~85% in practice. The remaining 15% is bootlegs, demos, and obscurities with no preview anywhere.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Hosted music-feature APIs
&lt;/h3&gt;

&lt;p&gt;The post-AB market split into two camps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Spotify-shim APIs&lt;/strong&gt; — Musicae and similar, designed to feel like the deprecated Spotify &lt;code&gt;audio_features&lt;/code&gt; endpoint. Field names match Spotify's vocabulary. Useful if you're a Spotify-deprecation refugee, but they don't expose AB's ~120 low-level descriptors — just the ~11 Spotify-style high-level fields.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Catalog-style APIs&lt;/strong&gt; — including &lt;a href="https://freqblog.com/" rel="noopener noreferrer"&gt;FreqBlog&lt;/a&gt; (full disclosure: ours). Pass artist + title, get back BPM, key, energy, mood-vector, the four AB low-level descriptors we backfilled (onset_rate, dynamic_complexity, tuning_frequency, average_loudness), and standard cross-link IDs. Different ergonomics from AB but covers most practical use cases.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Apple Music API
&lt;/h3&gt;

&lt;p&gt;Apple's catalog API exposes &lt;code&gt;tempo&lt;/code&gt;, &lt;code&gt;key&lt;/code&gt;, &lt;code&gt;timeSignature&lt;/code&gt;, and a few mood/genre tags. Free for developer accounts ($99/year to ship). Doesn't expose danceability, energy, valence, or anything below the high-level surface — closer to AB's high-level layer than its low-level descriptors.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Re-run the analysis on labelled academic datasets
&lt;/h3&gt;

&lt;p&gt;For research/non-commercial work where licensing matters, the &lt;a href="https://github.com/mdeff/fma" rel="noopener noreferrer"&gt;FMA&lt;/a&gt;, &lt;a href="http://millionsongdataset.com/" rel="noopener noreferrer"&gt;Million Song Dataset&lt;/a&gt;, GTZAN, and similar academic corpora ship with audio that you can run Essentia on yourself. None match AB's coverage but all are legally clean for paper-publishing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Field-mapping table
&lt;/h2&gt;

&lt;p&gt;For migrating code that previously called the AcousticBrainz live API, here's roughly how the field names translate:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;AcousticBrainz field&lt;/th&gt;
&lt;th&gt;Frozen dump&lt;/th&gt;
&lt;th&gt;FreqBlog&lt;/th&gt;
&lt;th&gt;Essentia (DIY)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;rhythm.bpm&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bpm&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;RhythmExtractor2013.bpm&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tonal.key_key&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;&lt;code&gt;key&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;KeyExtractor.key&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tonal.key_scale&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;&lt;code&gt;mode&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;KeyExtractor.scale&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;highlevel.danceability&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;&lt;code&gt;danceability&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;SVM model, deprecated&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;highlevel.mood_happy&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;&lt;code&gt;mood_vector.happy&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;SVM model, deprecated&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;lowlevel.average_loudness&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;&lt;code&gt;average_loudness&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Loudness&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;lowlevel.dynamic_complexity&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;&lt;code&gt;dynamic_complexity&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;DynamicComplexity&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;rhythm.onset_rate&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;&lt;code&gt;onset_rate&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;OnsetRate&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tonal.tuning_frequency&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;&lt;code&gt;tuning_frequency&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;TuningFrequency&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;highlevel.genre_*&lt;/code&gt; (multiple)&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;genre&lt;/code&gt; (single)&lt;/td&gt;
&lt;td&gt;SVM models, deprecated&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;lowlevel.mfcc.mean[0..12]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;not exposed&lt;/td&gt;
&lt;td&gt;&lt;code&gt;MFCC&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;lowlevel.spectral_*&lt;/code&gt; (~30 fields)&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;not exposed&lt;/td&gt;
&lt;td&gt;various spectral algorithms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The dump is the only option if you need the deep low-level vector (~120 fields). Hosted APIs typically expose the ~10-15 fields that map cleanly to product use cases.&lt;/p&gt;

&lt;h2&gt;
  
  
  What no replacement gives you back
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The crowd contribution loop.&lt;/strong&gt; AB grew because users ran the client on their personal libraries and submitted back. No commercial replacement has that bottom-up data flow — it's all top-down catalog ingestion.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The high-level mood/genre classifiers.&lt;/strong&gt; MTG's own shutdown post acknowledged the high-level model quality wasn't reliable enough — that's part of why they killed AB. Essentia's documentation now flags those SVM models as deprecated. &lt;strong&gt;Reproducing them locally reproduces a known-broken system.&lt;/strong&gt; If you need genre/mood at production-grade quality, the modern path is the Essentia Labs &lt;a href="https://essentia.upf.edu/models.html" rel="noopener noreferrer"&gt;MusiCNN models&lt;/a&gt; (TensorFlow-based, much better trained), not the deprecated SVMs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The MBID-keyed open-data ecosystem.&lt;/strong&gt; AB joined cleanly to MusicBrainz/Discogs/ListenBrainz because everyone shared the MBID identifier. Commercial APIs key on their own internal IDs (or Spotify track IDs, which are now deprecated for new apps). Cross-linking is harder.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How to choose
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Academic / research / one-shot dataset analysis&lt;/strong&gt; → download the dump. It's free, complete through June 2022, and citable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Building a product that needs MIR data on current releases&lt;/strong&gt; → tier the dump under a live source (Essentia self-hosted, or a hosted API). Use the dump as the cheap-and-deep layer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Building a quick app that just needs BPM/key/energy&lt;/strong&gt; → a hosted catalog API (FreqBlog, Musicae, or Apple Music) is faster to integrate than running Essentia yourself.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Need the deep low-level vector (MFCCs, spectral centroid, etc.) on every track&lt;/strong&gt; → only the dump or your own Essentia worker will give you those. No hosted API exposes them at retail prices.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Originally published at &lt;a href="https://freqblog.com/blog/acousticbrainz-alternative/" rel="noopener noreferrer"&gt;freqblog.com&lt;/a&gt;. If you've got a specific MIR migration question (resolver issues, dataset licensing, etc.), drop it in the comments.&lt;/p&gt;

</description>
      <category>music</category>
      <category>api</category>
      <category>mir</category>
      <category>python</category>
    </item>
    <item>
      <title>BPM API: Get BPM and Key From a Track Name (No Audio File Required)</title>
      <dc:creator>Freqblog</dc:creator>
      <pubDate>Fri, 08 May 2026 17:29:37 +0000</pubDate>
      <link>https://dev.to/birrings/bpm-api-get-bpm-and-key-from-a-track-name-no-audio-file-required-4ibi</link>
      <guid>https://dev.to/birrings/bpm-api-get-bpm-and-key-from-a-track-name-no-audio-file-required-4ibi</guid>
      <description>&lt;p&gt;Most "music analysis" APIs want a 30-second audio clip and a multipart upload. That's fine if you're building a DJ app where the user is dragging files in. It's a wall if you're building a fitness app trying to match treadmill cadence to a Spotify playlist, a music recommender that takes a track URL and returns "more like this," or honestly, any app where the user types &lt;em&gt;Blinding Lights&lt;/em&gt; and expects the BPM back.&lt;/p&gt;

&lt;p&gt;This is the architectural split nobody talks about: &lt;strong&gt;upload-based analysis APIs&lt;/strong&gt; vs &lt;strong&gt;catalog-based lookup APIs&lt;/strong&gt;. They look superficially similar (both return BPM, key, energy, etc.), but the failure modes are completely different and the right one for your app depends on what your user actually has in their hand.&lt;/p&gt;

&lt;h2&gt;
  
  
  The two architectures, in one paragraph each
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Upload-based
&lt;/h3&gt;

&lt;p&gt;You POST audio bytes; the API runs &lt;a href="https://essentia.upf.edu/" rel="noopener noreferrer"&gt;Essentia&lt;/a&gt; or &lt;a href="https://librosa.org/" rel="noopener noreferrer"&gt;librosa&lt;/a&gt; on them server-side; you get features back. Examples: AudD, Cyanite, AI Mastering. Hit rate is 100% (any audio works) but the user must have the file. No file means no answer. Pricing is usually per-call and on the higher end (compute-bound).&lt;/p&gt;

&lt;h3&gt;
  
  
  Catalog-based
&lt;/h3&gt;

&lt;p&gt;You GET with &lt;code&gt;?track=Blinding+Lights&amp;amp;artist=The+Weeknd&lt;/code&gt;; the API looks the track up in a pre-analyzed library and returns the cached features. Hit rate is &amp;lt;100% (only works for tracks already in the catalog) but no audio file required, the response is fast (typically &amp;lt;100 ms), and pricing is usually per-lookup and lower (no per-call compute). When the catalog misses, the better APIs queue an on-demand analysis and email the user when it lands.&lt;/p&gt;

&lt;p&gt;Spotify's &lt;code&gt;audio_features&lt;/code&gt; endpoint was a catalog-based API: you passed a Spotify track ID, you got the features. &lt;a href="https://freqblog.com/blog/spotify-audio-features-replacement-2026/" rel="noopener noreferrer"&gt;It died in November 2024&lt;/a&gt;, which is why this post exists at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "name-based lookup" actually does
&lt;/h2&gt;

&lt;p&gt;The pattern, in code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.freqblog.com/lookup&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;track&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Blinding Lights&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;artist&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;The Weeknd&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;X-Api-Key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c1"&gt;# {
#   "track_name": "Blinding Lights",
#   "artist_name": "The Weeknd",
#   "audio_features": {
#     "bpm": 171.0,
#     "bpm_alt": 85.5,           # half-time variant
#     "key": "C#-Minor",
#     "camelot": "12A",
#     "open_key": "5m",
#     "energy": 0.91,
#     "danceability": 0.85,
#     ...
#   }
# }
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What's actually happening behind the request:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Catalog hit&lt;/strong&gt; — the lookup matches by name and returns within ~10 ms. Most popular tracks are pre-analyzed and live here.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Catalog miss but reachable&lt;/strong&gt; — the API can't find the track, but it can find the iTunes preview for it. It returns HTTP 202 ("queued"), kicks off a background analysis (download preview → run Essentia → cache result), and emails the user when the data is ready (typically 30 seconds to 2 minutes later). Subsequent calls hit the cache.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Genuinely unfindable&lt;/strong&gt; — demos, bootlegs, alternate-takes that have no commercial release. The API returns 404 or an empty object. Plan for this in your UX.&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The 202 path is the differentiator.&lt;/strong&gt; Most catalog APIs return 404 on miss and leave you to figure it out. The "queue and email" pattern means a track that's missing today is in the catalog tomorrow — the catalog grows toward the long tail of what your users actually search for, instead of being frozen to whatever was popular when the dataset was built.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What you can ask for by name
&lt;/h2&gt;

&lt;p&gt;The full set of fields a good name-based BPM API should return:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;th&gt;Why it matters&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;bpm&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;171.0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Tempo for sync/cadence/sorting&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;bpm_alt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;85.5&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Half-time variant when the detector reports the wrong octave&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;key&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;"C#-Minor"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Musical key (12 pitches × major/minor)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;camelot&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;"12A"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;DJ-friendly key notation for harmonic mixing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;open_key&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;"5m"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Alternative DJ notation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;energy&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0.91&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0-1 normalised intensity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;danceability&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0.85&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0-1 rhythmic groove score&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;valence&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0.50&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0-1 musical positivity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;loudness&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;-5.2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;dB FS reference&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;time_signature&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;4&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Beats per bar&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;genre&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;"pop"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Coarse Last.fm-style tag&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;isrc&lt;/code&gt; + &lt;code&gt;mbid&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;"USUM72003867"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Cross-link IDs to other catalogs&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The hit rate is the metric that matters
&lt;/h2&gt;

&lt;p&gt;The single most important question to ask any catalog-based API: &lt;strong&gt;what fraction of the tracks my users will search for are actually in your catalog?&lt;/strong&gt; Vendors will quote you a top-line track count ("10M tracks!") which is meaningless unless you know the distribution.&lt;/p&gt;

&lt;p&gt;Three honest signals to look for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Charts coverage&lt;/strong&gt; — if the catalog has the current Apple Music / Spotify global top-200, you'll hit on most pop tracks people search for.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decade depth&lt;/strong&gt; — if 80% of the catalog is 2020+, anything pre-2010 will miss. Music apps that surface throwback tracks need 60s/70s/80s coverage too.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;On-demand backfill&lt;/strong&gt; — "we'll fetch and analyze if it's missing" turns a hard miss into a slow hit. Bigger long-term win than headline catalog size.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  When the catalog misses, what do you do?
&lt;/h2&gt;

&lt;p&gt;Three patterns work:&lt;/p&gt;

&lt;h3&gt;
  
  
  Pattern 1: Queue + notify
&lt;/h3&gt;

&lt;p&gt;The 202-pattern. Show "Analyzing… we'll email you" in your UX. Comes back via webhook or the user re-querying. Best when users care about that specific track and will wait.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.freqblog.com/lookup&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                 &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;track&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;track&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;artist&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;artist&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
                 &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;X-Api-Key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;202&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Queued. Show a "checking..." UI. Re-poll in 30s, or wire a webhook.
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;queued&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unavailable&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Pattern 2: Bulk pre-warm
&lt;/h3&gt;

&lt;p&gt;If you know the user's library upfront (e.g. they uploaded a CSV of their playlist), POST the whole list to &lt;code&gt;/bulk&lt;/code&gt;. The API returns immediately with a mix of hits + queued items, and the queued ones land via email/webhook over the next few minutes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pattern 3: Graceful degradation
&lt;/h3&gt;

&lt;p&gt;If your UX doesn't need every field, the API returns what it has and nulls the rest. Render BPM and key when present; hide the energy/valence chips when null. Users don't know what they're missing if you don't show empty boxes.&lt;/p&gt;

&lt;h2&gt;
  
  
  BPM accuracy: a footnote that matters
&lt;/h2&gt;

&lt;p&gt;BPM detectors get confused on roughly 20% of dance tracks. The Weeknd's &lt;em&gt;Blinding Lights&lt;/em&gt; is the textbook example: the actual tempo is 171 BPM, but Spotify (and most DSP-based detectors) returns ~85.5 because the algorithm latches onto the half-time pulse. The fix: expose &lt;em&gt;both&lt;/em&gt; the detected BPM and the half/double-time alternate, and let your app decide which to display based on the genre context.&lt;/p&gt;

&lt;p&gt;Any BPM API you evaluate should either fix the half-time issue automatically or expose enough context (the alternate value, a confidence score, or both) for you to fix it client-side. Spotify's old &lt;code&gt;audio_features&lt;/code&gt; didn't, which created a long tail of "this app says my song is 85 BPM" UX problems.&lt;/p&gt;

&lt;h2&gt;
  
  
  What no name-based API will give you
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Per-bar/per-beat timing.&lt;/strong&gt; Catalog APIs return whole-track features. If you need beat-level timing for visuals or sync, you need an upload-based API or you'll have to run a beat tracker on the audio yourself.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tracks below a popularity floor.&lt;/strong&gt; Bootlegs, demos, edit-only releases, and most non-commercial music isn't in any catalog. If your app routes around obscure music, you'll need either upload-based fallback or to live with the gap.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real-time analysis of &lt;em&gt;this&lt;/em&gt; microphone input.&lt;/strong&gt; That's a DSP problem, not an API one. Use librosa or Essentia client-side.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How to choose
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Users have audio files in hand?&lt;/strong&gt; Upload-based API. Pay per call, accept the higher latency, get 100% coverage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Users type a track name or paste a URL?&lt;/strong&gt; Catalog-based. Fast, cheap, plan for ~80-95% hit rate plus a graceful "queued" path for the misses.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Both, sometimes?&lt;/strong&gt; Most apps end up here. Use a catalog API as the fast path and an upload API (or your own Essentia worker) as the fallback for misses + obscure content.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Originally published at &lt;a href="https://freqblog.com/blog/bpm-key-from-track-name/" rel="noopener noreferrer"&gt;freqblog.com&lt;/a&gt;. The &lt;a href="https://freqblog.com/" rel="noopener noreferrer"&gt;FreqBlog Music API&lt;/a&gt; has a free tier — no card required.&lt;/p&gt;

&lt;p&gt;Got a specific catalog-vs-upload architecture question for your app? Drop it in the comments.&lt;/p&gt;

</description>
      <category>api</category>
      <category>music</category>
      <category>python</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Spotify's audio_features API died in 2024. Here's what to use in 2026.</title>
      <dc:creator>Freqblog</dc:creator>
      <pubDate>Sat, 11 Apr 2026 18:06:40 +0000</pubDate>
      <link>https://dev.to/birrings/spotifys-audiofeatures-api-died-in-2024-heres-what-i-built-to-replace-it-3dn3</link>
      <guid>https://dev.to/birrings/spotifys-audiofeatures-api-died-in-2024-heres-what-i-built-to-replace-it-3dn3</guid>
      <description>&lt;p&gt;If you maintain anything that ever called &lt;code&gt;GET /v1/audio-features/{id}&lt;/code&gt;, you already know how this story starts. On &lt;strong&gt;November 27, 2024&lt;/strong&gt;, Spotify quietly killed &lt;code&gt;audio_features&lt;/code&gt;, &lt;code&gt;audio_analysis&lt;/code&gt;, recommendations, related artists, and featured playlists in a single developer-blog post. New apps got &lt;code&gt;403&lt;/code&gt; the same day. Eighteen months later there's still no official replacement, and the &lt;a href="https://developer.spotify.com/documentation/web-api/tutorials/february-2026-migration-guide" rel="noopener noreferrer"&gt;February 2026 changes&lt;/a&gt; made the rest of the Web API harder to use, not easier.&lt;/p&gt;

&lt;p&gt;This post is the honest version of "what now?" — the real options, where they fall short, and a Python migration example.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually died
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Endpoint&lt;/th&gt;
&lt;th&gt;What it gave you&lt;/th&gt;
&lt;th&gt;Status (May 2026)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/audio-features/{id}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;BPM, key, mode, danceability, energy, valence, acousticness, instrumentalness, liveness, loudness, speechiness, time_signature&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;403&lt;/code&gt; for new apps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/audio-analysis/{id}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Per-bar / per-beat / per-segment timing&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;403&lt;/code&gt; for new apps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/recommendations&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Genre-seed and feature-seed recommendations&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;403&lt;/code&gt; for new apps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/artists/{id}/related-artists&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Up to 20 related artists&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;403&lt;/code&gt; for new apps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/browse/featured-playlists&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Editorial curation lists&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;403&lt;/code&gt; for new apps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;`/me/top/{tracks\&lt;/td&gt;
&lt;td&gt;artists}`&lt;/td&gt;
&lt;td&gt;User listening history&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Apps that already had a quota extension in flight on Nov 27 2024 are still live. Everyone else gets 403. There's no waitlist, no path forward, and no public statement that this will change.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Spotify isn't bringing it back
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;audio_features&lt;/code&gt; endpoint was a thin wrapper around analysis Spotify acquired when they bought The Echo Nest in 2014, plus features derived from the AcousticBrainz dataset (which itself shut down in 2022 for similar "we don't want to host this anymore" reasons). Returning eleven floats per track in a 100M-track catalog costs Spotify infrastructure for zero strategic value.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;recommendations&lt;/code&gt; endpoint was worse for them — letting any third-party build a Spotify-quality recommender by spamming &lt;code&gt;seed_genres=house&amp;amp;target_energy=0.8&lt;/code&gt; undermined the whole "premium algorithm" pitch.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Stop asking "will Spotify bring this back?" The right question is "what's the smallest dependency I can rebuild this on so I'm never one PR-merge away from being broken again?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The realistic options
&lt;/h2&gt;

&lt;p&gt;Five categories, ranked by how quickly you can ship the migration:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Apple Music API
&lt;/h3&gt;

&lt;p&gt;Apple exposes &lt;code&gt;tempo&lt;/code&gt;, &lt;code&gt;key&lt;/code&gt;, &lt;code&gt;timeSignature&lt;/code&gt;, &lt;code&gt;contentRating&lt;/code&gt;, plus mood/genre tags. Free for dev accounts, $99/year to ship. &lt;strong&gt;Missing:&lt;/strong&gt; danceability, energy, valence, acousticness, instrumentalness, liveness — six of the eleven Spotify fields gone. Best if you only need BPM + key from a major catalog and don't mind paying Apple.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Build your own with Essentia
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://essentia.upf.edu/" rel="noopener noreferrer"&gt;Essentia&lt;/a&gt; is the open-source MIR toolkit Spotify &lt;em&gt;itself&lt;/em&gt; used to derive most of the deprecated values. &lt;code&gt;MusicExtractor&lt;/code&gt; takes a 30-second audio clip and returns BPM, key, danceability, average loudness, dynamic complexity, tuning frequency, onset rate. Compute cost only — but &lt;strong&gt;you need the audio&lt;/strong&gt;. Spotify never let you download it; iTunes 30-second previews work but rate-limit hard (~25 RPM). Plan for a multi-week backfill on any catalog over 10k tracks.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. AcousticBrainz public dump (free, frozen)
&lt;/h3&gt;

&lt;p&gt;The MusicBrainz folks did &lt;em&gt;exactly&lt;/em&gt; what Spotify won't: published the entire AcousticBrainz dataset (7.5M tracks, 11 high-level features and ~120 low-level descriptors per track) as a one-time public dump in July 2022 before shutting the live service down. &lt;strong&gt;Catch:&lt;/strong&gt; frozen in time — nothing released after July 2022 has values. Coverage on tracks with a MusicBrainz ID is ~60% of recent commercial releases; without an MBID, nothing. Useful as a baseline layer.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Musicae API
&lt;/h3&gt;

&lt;p&gt;Built specifically as a Spotify shim — same field names, same value ranges, similar ergonomics. The closest drop-in if minimising migration diff matters more than anything else.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. &lt;a href="https://freqblog.com/" rel="noopener noreferrer"&gt;FreqBlog Music API&lt;/a&gt; (full disclosure: ours)
&lt;/h3&gt;

&lt;p&gt;Different design choice: a &lt;em&gt;catalog&lt;/em&gt; first, not a wrap-an-id service. Pass a track-name + artist string, get back BPM, key, Camelot, energy, loudness, danceability, valence, mood, time signature, ISRC, MBID, genre. From £0.17 per 1,000 requests, free tier with no card. We backfill missing tracks via a queue (returns &lt;code&gt;202 Accepted&lt;/code&gt;; the next call has the data). Doesn't cover per-segment analysis or &lt;code&gt;speechiness&lt;/code&gt;/&lt;code&gt;instrumentalness&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Migration walkthrough: Spotify → FreqBlog
&lt;/h2&gt;

&lt;p&gt;Most apps that depended on &lt;code&gt;audio_features&lt;/code&gt; were doing one of two things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Look up known tracks&lt;/strong&gt; — user pastes a Spotify URL, app shows BPM/key/energy&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Filter by feature ranges&lt;/strong&gt; — "give me upbeat tracks above 120 BPM in a major key"&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Pattern 1: Single-track lookup
&lt;/h3&gt;

&lt;p&gt;Before:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_features&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;spotify_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.spotify.com/v1/audio-features/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;spotify_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Authorization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bearer &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# &amp;gt;&amp;gt;&amp;gt; get_features("0VjIjW4GlUZAMYd2vXMi3b", token)
# {"danceability": 0.514, "energy": 0.730, "key": 1, "tempo": 171.005, ...}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_features&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;track_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;artist&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.freqblog.com/lookup&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;track&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;track_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;artist&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;artist&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;X-Api-Key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# &amp;gt;&amp;gt;&amp;gt; get_features("Blinding Lights", "The Weeknd", api_key)
# {"audio_features": {"bpm": 171.0, "key": "C#-Minor", "camelot": "12A",
#                     "energy": 0.91, "danceability": 0.85, ...}}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The shape of the request changes — name+artist instead of a Spotify ID, because we're a catalog not a Spotify-wrapper. The response shape is similar enough that a 5-line adapter handles it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Field mapping
&lt;/h3&gt;

&lt;p&gt;For the 11 Spotify fields, here's how they map to the replacement landscape:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Spotify field&lt;/th&gt;
&lt;th&gt;Apple Music&lt;/th&gt;
&lt;th&gt;Essentia (build-your-own)&lt;/th&gt;
&lt;th&gt;FreqBlog&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tempo&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;tempo&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bpm&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bpm&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;key&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;key&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;key.key&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;key&lt;/code&gt; + &lt;code&gt;camelot&lt;/code&gt; + &lt;code&gt;open_key&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mode&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;— (in &lt;code&gt;key&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;key.scale&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;mode&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;time_signature&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;timeSignature&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;code&gt;time_signature&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;danceability&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;code&gt;danceability&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;danceability&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;energy&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;derived from RMS&lt;/td&gt;
&lt;td&gt;&lt;code&gt;energy&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;valence&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;via SVM model&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;valence&lt;/code&gt; (where available)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;loudness&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;code&gt;average_loudness&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;loudness&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;acousticness&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;via SVM model&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;instrumentalness&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;via SVM model&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;liveness&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;via SVM model&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;speechiness&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;via SVM model&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Some fields don't exist anywhere except Spotify's frozen-in-time models (&lt;code&gt;speechiness&lt;/code&gt;, &lt;code&gt;instrumentalness&lt;/code&gt;). Be honest with users about which you actually need vs which were nice-to-haves.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pattern 2: Feature-range filtering
&lt;/h3&gt;

&lt;p&gt;If you were doing the &lt;code&gt;recommendations&lt;/code&gt;-with-target-features dance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;# Spotify (deprecated)
GET /v1/recommendations?seed_genres=house&amp;amp;target_energy=0.8&amp;amp;min_tempo=120

# FreqBlog
GET /charts?genre=house&amp;amp;min_energy=0.8&amp;amp;min_bpm=120&amp;amp;n=20
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same shape, different host. We also expose a similarity-based recommender (&lt;code&gt;/similar?seed_track_id=...&lt;/code&gt;) that doesn't require seed-genre tuning — pass a track you like, get back the 10 acoustically nearest neighbours by cosine similarity over an 18-feature embedding.&lt;/p&gt;

&lt;h2&gt;
  
  
  What no replacement gives you
&lt;/h2&gt;

&lt;p&gt;Be realistic about the gaps. None of the alternatives — including ours — replicate Spotify's old offering one-for-one:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Per-segment analysis with timestamps.&lt;/strong&gt; Replicating &lt;code&gt;/audio-analysis&lt;/code&gt; requires you to run a beat-tracker on the audio yourself. &lt;code&gt;librosa.beat.beat_track&lt;/code&gt; or Essentia's &lt;code&gt;RhythmExtractor2013&lt;/code&gt; get you most of the way.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Speechiness, instrumentalness, liveness.&lt;/strong&gt; Spotify trained these on internal labelled data nobody else has. Open-source Essentia models exist but Essentia's own docs flag them as deprecated due to data-quality concerns — reproducing them locally reproduces a known-noisy system.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Coverage of obscure tracks.&lt;/strong&gt; Spotify had every track in their catalog. Every alternative has a coverage gap on long-tail releases. Plan for a "no data yet, queue for analysis" path in your UX.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How to choose
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Already on Apple Music?&lt;/strong&gt; Use their API, accept the field reduction.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Need a 1:1 Spotify shim?&lt;/strong&gt; Musicae is closest by design.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Have audio files already?&lt;/strong&gt; Build with Essentia, pay only compute.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Need a queryable catalog by name+artist with BPM, key, Camelot, similarity, charts?&lt;/strong&gt; &lt;a href="https://freqblog.com/" rel="noopener noreferrer"&gt;Try FreqBlog&lt;/a&gt; — free tier, no card.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Building research/non-commercial work?&lt;/strong&gt; AcousticBrainz dump is free and large enough.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Originally published at &lt;a href="https://freqblog.com/blog/spotify-audio-features-replacement-2026/" rel="noopener noreferrer"&gt;freqblog.com&lt;/a&gt;. If you found this useful, the FreqBlog Music API has a free tier — no card required.&lt;/p&gt;

&lt;p&gt;Got questions about a specific migration scenario? Drop them in the comments and I'll do my best.&lt;/p&gt;

</description>
      <category>spotify</category>
      <category>api</category>
      <category>music</category>
      <category>python</category>
    </item>
  </channel>
</rss>
