Skip to content

Instantly share code, notes, and snippets.

@mbafford
Created March 11, 2015 14:17
Show Gist options
  • Save mbafford/3ce0141b680177fd6170 to your computer and use it in GitHub Desktop.
Save mbafford/3ce0141b680177fd6170 to your computer and use it in GitHub Desktop.
You can't skip Pandora ads on Sonos (not even with UPNP commands), but you can mute them. This just watches for a Pandora ad playing and mutes the audio until the ad has finished. A much better approach is to just pay for Pandora.
#!/usr/bin/env python
import soco
import time
import re
# pandora song:
# {u'album': 'Everything All The Time', u'artist': 'Band Of Horses', u'title': 'The Funeral', u'uri': 'pndrradio-http://audio-ch1-t1-2-v4v6.pandora.com/access/?version=4&lid=3729520&token=slyAWqqgjJ8Bl3TgMB%2F03C4tamOHsLj%2BOQLq1T8xbiwSzkcoc253HeYbNFTcOP7K6Oy9GOq6tcaManOhyKDpECIiCENj7hjBsH%2FJVhJnDjLzyPRWjg8q0LtX1mn80yFiurvB1r0G0Be76YBZJUswLdWtNZtfYSO2y2WdYHSge8Ul2hML1MWvkv23AQjw9%2BAKPmIHXX8nUzfcfFfLfmyKk%2Flxwh2cf%2BvDrJIbnnVZfvlfirWhshENZDm7BBofIlUqR0qsPXWbYN5m7WD4bS3XCK4dedE0QtHIlBaGvNIwNrorp3j5EoZM%2BybBEigsDXg%2B9To1w30%2BRbV8V3o6hyM5lpviYM4icmRBYVVjmA4hlWOE7aOFqcNAPA%3D%3D&a=http%3a%2f%2fcont-sv5-3.pandora.com%2fimages%2fpublic%2famz%2f0%2f2%2f0%2f9%2f098787069020_500W_500H.jpg&m=94f084d4cda66036692600d16036898d258a387c7a546916b33f7d907698ddf0e59d86721b571e2bfd793b4d25efc3cc856420f223031811&f=1&r=0&g=-5.93&x=000020aa', u'playlist_position': '4', u'duration': '0:05:22', u'position': '0:03:09', u'album_art': 'http://cont-sv5-3.pandora.com/images/public/amz/0/2/0/9/098787069020_500W_500H.jpg', u'metadata': '<DIDL-Lite xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:upnp="urn:schemas-upnp-org:metadata-1-0/upnp/" xmlns:r="urn:schemas-rinconnetworks-com:metadata-1-0/" xmlns="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/"><item id="-1" parentID="-1" restricted="true"><res protocolInfo="pandora.com-pndrradio-http:*:audio/mpeg:*" duration="0:05:22">pndrradio-http://audio-ch1-t1-2-v4v6.pandora.com/access/?version=4&amp;lid=3729520&amp;token=slyAWqqgjJ8Bl3TgMB%2F03C4tamOHsLj%2BOQLq1T8xbiwSzkcoc253HeYbNFTcOP7K6Oy9GOq6tcaManOhyKDpECIiCENj7hjBsH%2FJVhJnDjLzyPRWjg8q0LtX1mn80yFiurvB1r0G0Be76YBZJUswLdWtNZtfYSO2y2WdYHSge8Ul2hML1MWvkv23AQjw9%2BAKPmIHXX8nUzfcfFfLfmyKk%2Flxwh2cf%2BvDrJIbnnVZfvlfirWhshENZDm7BBofIlUqR0qsPXWbYN5m7WD4bS3XCK4dedE0QtHIlBaGvNIwNrorp3j5EoZM%2BybBEigsDXg%2B9To1w30%2BRbV8V3o6hyM5lpviYM4icmRBYVVjmA4hlWOE7aOFqcNAPA%3D%3D&amp;a=http%3a%2f%2fcont-sv5-3.pandora.com%2fimages%2fpublic%2famz%2f0%2f2%2f0%2f9%2f098787069020_500W_500H.jpg&amp;m=94f084d4cda66036692600d16036898d258a387c7a546916b33f7d907698ddf0e59d86721b571e2bfd793b4d25efc3cc856420f223031811&amp;f=1&amp;r=0&amp;g=-5.93&amp;x=000020aa</res><r:streamContent></r:streamContent><upnp:albumArtURI>http://cont-sv5-3.pandora.com/images/public/amz/0/2/0/9/098787069020_500W_500H.jpg</upnp:albumArtURI><dc:title>The Funeral</dc:title><upnp:class>object.item.audioItem.musicTrack</upnp:class><dc:creator>Band Of Horses</dc:creator><upnp:album>Everything All The Time</upnp:album></item></DIDL-Lite>'}
# pandora ad:
# {u'album': u'', u'artist': u'', u'title': ' ', u'uri': 'pndrradioad://http://t2-1.p-cdn.com/access/?version=4&lid=3729520&token=JWKN59qxNu2lJGzFLFmI4%2BbPJWonBt%2FM5B9I%2Bliu%2FUNPq7DkOKDU%2BjXEDimK4q3M5WlxBSaxUtp2Sa4tUbALw7FutSpYL6ndhgqeCSu3KOHm1gIHjg48lwngIcwbh1K9QBGUBAroEq2kep%2Fou977EuksnPUF1BbxRBLKziwUyfsDHgLvRADYtLiBDt2lwJb25X1kMCdrD%2BK4gEq7BUxLglha0VlsOB1MrhZmjcv8l8Tn0kFqAJiW6pY54e5f2MGueBFfCRL8bjKoYN99cZjsjPjfvBem5%2BfrGzsu%2FiN1VhNL3AzHo64Ytap1heZEER6wmcneMSj3PsvSKKRmlZYrmh3nCY%2F7s5VYZdqvpMKfLNs%3D&a=http%3a%2f%2fcont-1.p-cdn.com%2fimages%2fpublic%2fdevicead%2fs%2fm%2f2%2fr%2fdaarv2190082q3r2ms_500W_500H.jpg&m=ADU-3729520-6-15520-none-0-1423412705129%3aADU-3729520-6-15520-none-1-1423412705130%3aADU-3729520-6-15520-none-2-1423412705130&f=0&r=0&g=0.000&x=000020aa', u'playlist_position': '3', u'duration': '0:00:16', u'position': '0:00:03', u'album_art': 'http://cont-1.p-cdn.com/images/public/devicead/s/m/2/r/daarv2190082q3r2ms_500W_500H.jpg', u'metadata': '<DIDL-Lite xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:upnp="urn:schemas-upnp-org:metadata-1-0/upnp/" xmlns:r="urn:schemas-rinconnetworks-com:metadata-1-0/" xmlns="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/"><item id="-1" parentID="-1" restricted="true"><res protocolInfo="pandora.com-pndrradioad:*:audio/mpeg:*" duration="0:00:16">pndrradioad://http://t2-1.p-cdn.com/access/?version=4&amp;lid=3729520&amp;token=JWKN59qxNu2lJGzFLFmI4%2BbPJWonBt%2FM5B9I%2Bliu%2FUNPq7DkOKDU%2BjXEDimK4q3M5WlxBSaxUtp2Sa4tUbALw7FutSpYL6ndhgqeCSu3KOHm1gIHjg48lwngIcwbh1K9QBGUBAroEq2kep%2Fou977EuksnPUF1BbxRBLKziwUyfsDHgLvRADYtLiBDt2lwJb25X1kMCdrD%2BK4gEq7BUxLglha0VlsOB1MrhZmjcv8l8Tn0kFqAJiW6pY54e5f2MGueBFfCRL8bjKoYN99cZjsjPjfvBem5%2BfrGzsu%2FiN1VhNL3AzHo64Ytap1heZEER6wmcneMSj3PsvSKKRmlZYrmh3nCY%2F7s5VYZdqvpMKfLNs%3D&amp;a=http%3a%2f%2fcont-1.p-cdn.com%2fimages%2fpublic%2fdevicead%2fs%2fm%2f2%2fr%2fdaarv2190082q3r2ms_500W_500H.jpg&amp;m=ADU-3729520-6-15520-none-0-1423412705129%3aADU-3729520-6-15520-none-1-1423412705130%3aADU-3729520-6-15520-none-2-1423412705130&amp;f=0&amp;r=0&amp;g=0.000&amp;x=000020aa</res><r:streamContent></r:streamContent><upnp:albumArtURI>http://cont-1.p-cdn.com/images/public/devicead/s/m/2/r/daarv2190082q3r2ms_500W_500H.jpg</upnp:albumArtURI><dc:title> </dc:title><upnp:class>object.item.audioItem.musicTrack</upnp:class></item></DIDL-Lite>'}
def isPandora (uri): return uri.startswith("pndrradio")
def isPandoraAd(uri): return uri.startswith("pndrradioad")
# take the hours:minutes:seconds (e.g. 0:03:12) format from the soco/pandora
# library for duration or position values and convert into raw seconds
def hmsToSecs(hms):
m = re.match(r'([0-9]+):([0-9]+):([0-9]+)', hms)
if m:
return int(m.group(1))*60*60 + int(m.group(2))*60 + int(m.group(3))
else:
return 0
if __name__ == '__main__':
# store the old volume for the zones that are currently being paused
# because o0
# zone was (no longer) being paused by us
pausedZones = {}
debug = True
while True:
try:
if debug: print("Checking zones.")
# for each playing zone find the one with the smallest amount of seconds left until the current track ends
minTrackSecsLeft = 240
for zone in soco.discover():
playing = zone.get_current_transport_info()['current_transport_state'] == 'PLAYING'
if debug: print("Checking: %s (%s)" % ( zone.player_name, zone ))
#if debug: print(" %s" % ( zone.get_current_transport_info() ))
if not zone.is_coordinator: continue
if not playing: continue
current = zone.get_current_track_info()
uri = current['uri']
#if debug: print(" URI: %s" % ( uri ) )
print(" Current Track: %s (%s) / %s" % ( current['title'], current['album'], current['artist'] ) )
if not isPandora(uri):
if debug: print(" Zone is not playing a Pandora radio station. Skipping.")
continue
dur = hmsToSecs( current['duration'] )
pos = hmsToSecs( current['position'] )
minTrackSecsLeft = min(minTrackSecsLeft, dur-pos)
if debug: print(" Current position: %s / %s ( %d s )" % ( current['position'], current['duration'], dur-pos ))
# no longer an ad and we'd previously muted this zone
if not isPandoraAd(uri) and zone.player_name in pausedZones and zone.volume == 1:
if debug: print(" Zone is muted and ad has finished. Unmuting.")
zone.volume = pausedZones[zone.player_name]
del(pausedZones[zone.player_name])
# ad is playing, volume isn't muted - take the current volume and store it then mute
if isPandoraAd(uri) and zone.volume > 1:
if debug: print(" Zone was not muted and ad is playing. Muting!")
pausedZones[zone.player_name] = zone.volume
zone.volume = 1
except Exception as ex:
if debug: print(ex)
pass
sleepTime = min( 30, max( minTrackSecsLeft-2, 1 ))
if debug: print("Zone with the next ending track has %d seconds left. Sleeping for %d seconds." % ( minTrackSecsLeft, sleepTime ) )
time.sleep(sleepTime)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment