Last active
March 30, 2025 21:54
-
-
Save marcan/f470d94f889cb467d055c85e64ef3b83 to your computer and use it in GitHub Desktop.
Add Spotify volume normalization to Google Cast devices
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/python3 | |
# | |
# turnitdown.py - normalize Spotify playback volume on Google Cast devices | |
# | |
# Dependencies: pychromecast, spotipy | |
# | |
# Usage: register a Spotify app and put the credentials in the | |
# SPOTIPY_CLIENT_ID and SPOTIPY_CLIENT_SECRET environment variables. | |
# Then just launch the script. It will autodetect all cast devices | |
# on the network. | |
import logging | |
import time | |
import pychromecast | |
import spotipy | |
from spotipy.oauth2 import SpotifyClientCredentials | |
VOLUME_RANGE = 34.0 | |
MAX_VOL = 1.0 | |
MIN_VOL = 0.001 | |
WIGGLE = 0.01 | |
FUDGE = 0.001 | |
class VolumeFixer(pychromecast.controllers.media.MediaStatusListener): | |
def __init__(self, sp, cc): | |
self.sp = sp | |
self.cc = cc | |
self.last_track = None | |
self.last_loudness = None | |
self.ref_vol = None | |
def new_media_status(self, status: pychromecast.controllers.media.MediaStatus): | |
if status.content_type != "application/x-spotify.track": | |
return | |
track = status.content_id | |
if track == self.last_track: | |
return | |
try: | |
af = self.sp.audio_features([track]) | |
loudness = af[0]["loudness"] | |
except: | |
return | |
if self.last_loudness is None: | |
self.last_loudness = loudness | |
cur_vol = self.cc.status.volume_level | |
cur_ref = self.last_loudness + VOLUME_RANGE * (cur_vol - 1) | |
if (self.ref_vol is None or | |
(cur_vol > (MIN_VOL + FUDGE) and cur_ref > (self.ref_vol + WIGGLE)) or | |
(cur_vol < (MAX_VOL - FUDGE) and cur_ref < (self.ref_vol - WIGGLE))): | |
self.ref_vol = cur_ref | |
new_vol = min(MAX_VOL, max(MIN_VOL, (self.ref_vol - loudness) / VOLUME_RANGE + 1)) | |
print(f"[{self.cc.device.friendly_name}] Playing {status.title} ({track}) loudness {loudness} volume {cur_vol} -> {new_vol} (ref: {self.ref_vol})") | |
self.cc.set_volume(new_vol) | |
self.last_loudness = loudness | |
self.last_track = track | |
sp = spotipy.Spotify(auth_manager=SpotifyClientCredentials()) | |
print("Locating chromecasts...") | |
chromecasts, browser = pychromecast.get_chromecasts() | |
print("Found chromecasts:") | |
for cc in chromecasts: | |
print(f" - {cc.device.friendly_name} ({cc.device.cast_type})") | |
cc.wait() | |
cc.media_controller.register_status_listener(VolumeFixer(sp, cc)) | |
print("Waiting for events...") | |
while True: | |
time.sleep(1) |
Hi, does this still work? I can't get it to work.
No longer works because Spotify closed the API endpoint "audio-features"
https://developer.spotify.com/documentation/web-api/reference/get-audio-features
No longer works because Spotify closed the API endpoint "audio-features" https://developer.spotify.com/documentation/web-api/reference/get-audio-features
Alright. Thanks for the reply.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hi, does this still work?
I can't get it to work.