Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Remove Ads from Spotify Linux client by mutting the audio system
# -*- coding: utf-8 -*-
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# Author: Manuel Kaufmann <humitos@gmail.com>
# Date: 29 October 2016
# Release: 0.5
# DESCRIPTION:
#
# This program allows you to do not listen to Spotify Ads withuot having a paid
# account. It's not too much intelligent right now, so you have to add the
# Artists you usually listen to or the name of the Spotify Ads you want to mute.
#
# How it works? It just mute the sound system when it detects an Ads is being
# played. How? By reading the window's title and check against all the known
# names and Ads you have defined and using some dumb pre-defined rules that fail
# in some cases.
from __future__ import division, print_function, unicode_literals
import commands
import os
import sys
import time
HOSTNAME = commands.getoutput('hostname')
TERMINAL_ENCODING = commands.getoutput('locale charmap')
WMCTRL_WINDOW_LIST = "wmctrl -l -x | grep 'spotify.Spotify' | grep -v 'grep'"
MUTE_COMMAND = "amixer -q -D pulse sset Master mute"
UNMUTE_COMMAND = "amixer -q -D pulse sset Master unmute"
SPOTIFY_OPEN_COMMAND = "ps -ef | grep Spotify | grep -v grep | wc -l"
MUTED = False
SPOTIFY_OPENED = False
SONGS_PLAYED = []
# Time in seconds
SLEEP_BEFORE_UNMUTE = 1.5
SLEEP_BETWEEN_EACH_CHECK = 0.1
SLEEP_WHEN_SPOTIFY_CLOSED = 5
# Define the names of the Artists, Songs, etc you usually listen to here
KNOWN_TITLES = (
# Titles that I found with problems matching dumb rules
['Emir Kusturica'],
['Jarabe De Palo'],
['Bob Marley'],
['Queen'],
['Soda Stereo'],
# Titles I want to be sure that are valid
map(
lambda x: '- {}'.format(x),
['Remastered', 'Extended Version', 'En Vivo', 'MTV Unplugged', 'Acoustic', 'Versión Acústica', 'Live',
'Directo', 'directo', 'unplugged', 'en directo', 'Unplugged Version', 'Live/Unplugged', 'Instrumental',
'Remasterizado', 'BBC'],
),
# ...
)
# Define the names of the Ads you usually listen to and want to avoid here
ADS_TITLES = (
# ...
['REYKON'],
)
def mute():
global MUTED
MUTED = True
os.system(MUTE_COMMAND)
def unmute():
global MUTED
MUTED = False
os.system(UNMUTE_COMMAND)
def is_spotify_opened():
global SPOTIFY_OPENED
output = commands.getoutput(SPOTIFY_OPEN_COMMAND)
SPOTIFY_OPENED = bool(int(output)) # 0 is closed
return SPOTIFY_OPENED
def is_known_title(spotify_title):
if spotify_title.isupper():
# most of the ads are upper case
return False
if len(spotify_title.split()) == 1:
# some of the ads are just one word
return False
# check for known Ads
for title in ADS_TITLES:
for t in title:
if t in spotify_title:
return False
if len(spotify_title.split(' - ')) == 2:
# most of the songs are in the form "Artist - Title"
return True
# check for known Artists or Songs
for title in KNOWN_TITLES:
for t in title:
if t in spotify_title:
return True
return False
def check_spotify_ads():
output = commands.getoutput(WMCTRL_WINDOW_LIST)
try:
output = output.decode(TERMINAL_ENCODING)
_, spotify_title = output.split(HOSTNAME)
except (ValueError, UnicodeDecodeError) as e:
print('*** ERROR: {} ***'.format(e))
print('*** Command Output: {} ***'.format(output))
return
spotify_title = spotify_title.strip()
known_title = is_known_title(spotify_title)
if (spotify_title not in SONGS_PLAYED and known_title) or (not MUTED and not known_title):
print('Playing:', spotify_title)
if spotify_title not in SONGS_PLAYED and known_title:
SONGS_PLAYED.append(spotify_title)
if known_title:
if MUTED:
time.sleep(SLEEP_BEFORE_UNMUTE) # the window title changes before the song starts
unmute()
else:
if not MUTED:
print('Sound MUTED.')
mute()
if __name__ == '__main__':
try:
while True:
if not is_spotify_opened():
print('Spotify not running. Sleeping...')
time.sleep(SLEEP_WHEN_SPOTIFY_CLOSED)
continue
check_spotify_ads()
time.sleep(SLEEP_BETWEEN_EACH_CHECK)
except KeyboardInterrupt:
if MUTED:
print('\nUnmuting...', end=' ')
unmute()
print('\nBye.')
sys.exit(0)
@nitely

This comment has been minimized.

Copy link

commented Sep 18, 2016

@humitos thank you very much for sharing this!

I found many of the ads are in "words - words" format (ie: SIA - Staylist), those will always fail. IMHO, you should check for KNOWN_TITLES first (or at least before the upper-score rule), since that's the most bullet proof rule. Then try the ADS_TITLES rule, and then the others. Also, there are many legit songs with the "artist - song - acoustic/cover/etc" format so it may be better to check if there are two or more upper-score instead of just two.

EDIT. Actually you would have to remove the upper-score rule for the above to work, or make a setting to disable it.

@humitos

This comment has been minimized.

Copy link
Owner Author

commented Oct 30, 2016

I found many of the ads are in "words - words" format (ie: SIA - Staylist), those will always fail.

I've never seen one of those. It depends on the place from where you are connected to internet.

IMHO, you should check for KNOWN_TITLES first (or at least before the upper-score rule), since that's the most bullet proof rule. Then try the ADS_TITLES rule, and then the others.

The thing is that I want to check as less as possible things to make it work fast. The two first rules works like a champ for me and are really dumb but super fast:

  • everything in uppercase
  • ads with just one word

Also, there are many legit songs with the "artist - song - acoustic/cover/etc" format so it may be better to check if there are two or more upper-score instead of just two.

For this, I added a map inside the KNOWN_TITLES in the 0.5 version:

    map(
        lambda x: '- {}'.format(x),
        ['Remastered', 'Extended Version', 'En Vivo', 'MTV Unplugged', 'Acoustic', 'Versión Acústica', 'Live',
         'Directo', 'directo', 'unplugged', 'en directo', 'Unplugged Version', 'Live/Unplugged', 'Instrumental',
         'Remasterizado', 'BBC'],
    ),

Sorry for the late response, I don't know why, but I see this comment today.

@humitos

This comment has been minimized.

Copy link
Owner Author

commented Oct 30, 2016

@juancarlospaco have you ever tried one of those? do you use them regularly?

@juancarlospaco

This comment has been minimized.

Copy link

commented Oct 30, 2016

@humitos Everyday, they are near top on Arch repo.

@Franr

This comment has been minimized.

Copy link

commented Dec 7, 2016

Piola! Me voy a armar un "mostrador" de letras basándome en el concepto inverso de esto 😸

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.