From 536360f48d8a2ca73b404c0226a1cb3ac91f6d6f Mon Sep 17 00:00:00 2001 From: Max Falk Date: Fri, 8 Apr 2016 00:52:09 +0200 Subject: [PATCH] update to 3.5.0 with more robust ad detection, closes #89 #95 --- CHANGELOG.md | 1 + README.md | 61 ++++++++++++++++++++++------------ blockify/cli.py | 40 +++++++++++++++++++++- blockify/data/blockify.desktop | 7 ++++ blockify/dbusclient.py | 47 ++++++++++++++++---------- blockify/util.py | 2 +- 6 files changed, 117 insertions(+), 41 deletions(-) create mode 100644 blockify/data/blockify.desktop diff --git a/CHANGELOG.md b/CHANGELOG.md index aab875a..c561084 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Changelog +- v3.5.0 (2016-04-08): Reintroduce wmctrl to catch video ads ([issue #89](https://github.com/mikar/blockify/issues/89)) and block some audio ads more reliably. Fix encoding issues ([issue #95](https://github.com/mikar/blockify/issues/95)). - v3.4.0 (2016-03-25): Fix play/pause toggle button, right click on tray [issue #83](https://github.com/mikar/blockify/issues/83) and add start_minimized option [issue #93](https://github.com/mikar/blockify/issues/93). - v3.3.1 (2016-01-03): Fix interlude player crashes ([issue #84](https://github.com/mikar/blockify/issues/84)). - v3.3.0 (2016-01-03): Enable profiling, improve GUI performance, fix playback button & title status functionality and add tray icon toolip. diff --git a/README.md b/README.md index ef75343..57dff75 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,18 @@ Blockify is a linux only application that allows you to automatically mute songs ## Installation ### Basic Requirements -- Python3 -- Pulseaudio -- Gstreamer1.0 (including the plugins you need for the audio formats you want to be able to play as interlude music) -- Spotify > 1.0.12 (to get the latest version follow the instructions given here [Spotify-Client-1-x-beta-] (https://community.spotify.com/t5/Spotify-Community-Blog/Spotify-Client-1-x-beta-for-Linux-has-been-released/ba-p/1147084)) + +Mandatory: + - Python3 + - Spotify > 1.0.12 (to get the latest version follow the instructions given here [Spotify-Client-1-x-beta-] (https://community.spotify.com/t5/Spotify-Community-Blog/Spotify-Client-1-x-beta-for-Linux-has-been-released/ba-p/1147084)) + - wmtrcl (provides information about the Spotify window) + +Optional but highly recommended: + - Pulseaudio (allows muting Spotify instead of all system sound) + - Gstreamer1.0 (used to play music of your choice during muted ads. Requires pulseaudio.) + +Optional: + - docopt (provides a command-line interface for blockify and blockify-ui) ### Prepackaged @@ -30,7 +38,7 @@ Available in the [openSUSE build service](https://build.opensuse.org/package/sho If there is no blockify package available on your distribution, you'll have to install it directly via one of pythons many installation tools. Before installing blockify, please make sure you have the appropriate dependencies installed: -`pacman -S python pulseaudio gst-python alsa-utils pygtk python-dbus python-setuptools python-gobject python-docopt` +`pacman -S git python-pip gst-python pulseaudio alsa-utils pygtk python-dbus python-gobject python-docopt wmctrl` Package names are for ArchLinux and will probably differ slightly between distributions. @@ -41,7 +49,7 @@ echo deb http://repository.spotify.com testing non-free | sudo tee /etc/apt/sour sudo apt-get update sudo apt-get install spotify-client # Install blockify dependencies -sudo apt-get install git python3-requests python3-docopt python3-pip python3-gst-1.0 python-configparser +sudo apt-get install git python3-pip python3-gst-1.0 python3-requests python3-docopt wmctrl ``` Install routine: @@ -85,29 +93,40 @@ Blockify accepts several signals: To easily use these signals add the following function to your .bashrc: ```bash -bl() { +bb() { local signal local cmd + [[ "$#" -lt 1 ]] && echo "Usage: bb ( b[lock] | u[nblock] | p[revious] | n[ext] | t[oggle] | t[oggle]b[lock] |...)" && return 0 case "$1" in - "") blockify-dbus get && return 0;; - ex) signal='TERM';; # Exit - b) signal='USR1';; # Block - u) signal='USR2';; # Unblock - p) signal='RTMIN';; # Previous song - n) signal='RTMIN+1';; # Next song - t) signal='RTMIN+2';; # Toggle play song - tb) signal='RTMIN+3';; # Toggle block song - pi) signal='RTMIN+10';; # Previous interlude song - ni) signal='RTMIN+11';; # Next interlude song - ti) signal='RTMIN+12';; # Toggle play interlude song - tir) signal='RTMIN+13';; # Toggle interlude resume + '') blockify-dbus get 2>/dev/null && return 0;; + ex|exit) + signal='TERM';; # Exit + b|block) + signal='USR1';; # Block + u|unblock) + signal='USR2';; # Unblock + p|previous) + signal='RTMIN';; # Previous song + n|next) + signal='RTMIN+1';; # Next song + t|toggle) + signal='RTMIN+2';; # Toggle play song + tb|toggleblock) + signal='RTMIN+3';; # Toggle block song + ip|iprevious) + signal='RTMIN+10';; # Previous interlude song + in|inext) + signal='RTMIN+11';; # Next interlude song + it|itoggle) + signal='RTMIN+12';; # Toggle play interlude song + itr|itoggleresume) + signal='RTMIN+13';; # Toggle interlude resume *) echo "Bad option" && return 0;; esac pkill --signal "$signal" -f 'python.*blockify' } ``` - -Then use it via e.g. `bl` to get current song info or `bl t` to toggle playback. +Then use it via e.g. `bb` to get current song info or `bb t` to toggle playback. #### CLI Blockify has a CLI/daemon that you can start with `blockify`. diff --git a/blockify/cli.py b/blockify/cli.py index 5bcf0a1..675f8da 100644 --- a/blockify/cli.py +++ b/blockify/cli.py @@ -16,6 +16,7 @@ import re import signal import subprocess +import sys import time from gi import require_version @@ -50,6 +51,7 @@ def __init__(self, blocklist): self.pulse_unmuted_value = "" self.song_delimiter = " - " # u" \u2013 " self.found = False + self.current_song_from_window_title = "" self.current_song = "" self.current_song_artist = "" self.current_song_title = "" @@ -256,6 +258,22 @@ def update(self): # Always return True to keep looping this method. return True + def find_spotify_window(self): + spotify_window = [] + try: + pipe = subprocess.Popen(['wmctrl', '-lx'], stdout=subprocess.PIPE).stdout + window_list = pipe.read().decode("utf-8").split("\n") + for window in window_list: + if window.find("spotify.Spotify") >= 0: + # current_song = " ".join(window.split()[5:]) + spotify_window.append(window) + break + except OSError: + log.error("Please install wmctrl first! Exiting.") + self.stop() + + return spotify_window + def find_ad(self): """Main loop. Checks for ads and mutes accordingly.""" self.previous_song = self.current_song @@ -302,13 +320,33 @@ def unmute_with_delay(self): self.toggle_mute() return False + # Audio ads typically have no artist information (via DBus) and/or "/ad/" in their spotify url. + # Video ads have no DBus information whatsoever so they are determined via window title (wmctrl). def current_song_is_ad(self): - return self.current_song_title and not self.current_song_artist + + no_artist = self.current_song_title and not self.current_song_artist + ad_url = "/ad/" in self.dbus.get_spotify_url() + title_mismatch = self.spotify_is_playing() and self.current_song != self.current_song_from_window_title + + return no_artist or ad_url or title_mismatch def update_current_song_info(self): self.current_song_artist = self.dbus.get_song_artist() self.current_song_title = self.dbus.get_song_title() self.current_song = self.current_song_artist + self.song_delimiter + self.current_song_title + self.current_song_from_window_title = self.get_current_song_from_window_title() + + def get_current_song_from_window_title(self): + """Checks if a Spotify window exists and returns the current songname.""" + song = "" + spotify_window = self.find_spotify_window() + if spotify_window: + try: + song = " ".join(map(str, spotify_window[0].split()[4:])) + except Exception as e: + log.debug("Could not match spotify pid to sink pid: %s".format(e), exc_info=1) + + return song def block_current(self): if self.current_song: diff --git a/blockify/data/blockify.desktop b/blockify/data/blockify.desktop new file mode 100644 index 0000000..088d337 --- /dev/null +++ b/blockify/data/blockify.desktop @@ -0,0 +1,7 @@ +[Desktop Entry] +Name=Blockify +Comment=Blocks Spotify commercials +Exec=blockify-ui +Icon=data/icon-red-512.png +Type=Application +Categories=AudioVideo diff --git a/blockify/dbusclient.py b/blockify/dbusclient.py index ced0e7d..7f750d4 100644 --- a/blockify/dbusclient.py +++ b/blockify/dbusclient.py @@ -138,38 +138,49 @@ def seek(self, seconds): except Exception as e: log.warn("Cannot Seek: {}".format(e)) + def get_song_length(self): + """Gets the length of current song from metadata (in seconds).""" + length = 0 + try: + metadata = self.get_property("Metadata") + length = int(metadata["mpris:length"] / 1000000) + except Exception as e: + log.warn("Cannot get song length: {}".format(e)) + + return length + def get_art_url(self): """Get album cover""" - url = "" + art_url = "" try: metadata = self.get_property("Metadata") - url = str(metadata["mpris:artUrl"], "utf-8") + art_url = str(metadata["mpris:artUrl"]) except Exception as e: log.error("Cannot fetch album cover url: {}".format(e)) - return url + return art_url + + def get_spotify_url(self): + """Get spotify url for the track.""" + spotify_url = "" + try: + metadata = self.get_property("Metadata") + spotify_url = str(metadata["xesam:url"]) + except Exception as e: + log.error("Cannot fetch spotify url: {}".format(e)) + + return spotify_url def get_song_status(self): """Get current PlaybackStatus (Paused/Playing...).""" status = "" try: - status = str(self.get_property("PlaybackStatus"), "utf-8") + status = str(self.get_property("PlaybackStatus")) except Exception as e: log.warn("Cannot get PlaybackStatus: {}".format(e)) return status - def get_song_length(self): - """Gets the length of current song from metadata (in seconds).""" - length = 0 - try: - metadata = self.get_property("Metadata") - length = int(metadata["mpris:length"] / 1000000) - except Exception as e: - log.warn("Cannot get song length: {}".format(e)) - - return length - def get_song(self): artist = self.get_song_artist() title = self.get_song_title() @@ -182,7 +193,7 @@ def get_song_title(self): title = "" try: metadata = self.get_property("Metadata") - title = str(metadata["xesam:title"], "utf-8") + title = str(metadata["xesam:title"]) except Exception as e: log.warn("Cannot get song title: {}".format(e)) @@ -193,7 +204,7 @@ def get_song_album(self): album = "" try: metadata = self.get_property("Metadata") - album = str(metadata["xesam:album"], "utf-8") + album = str(metadata["xesam:album"]) except Exception as e: log.warn("Cannot get song album: {}".format(e)) @@ -204,7 +215,7 @@ def get_song_artist(self): artist = "" try: metadata = self.get_property("Metadata") - artist = str(metadata["xesam:artist"][0], "utf-8") + artist = str(metadata["xesam:artist"][0]) except Exception as e: log.warn("Cannot get song artist: {}".format(e)) diff --git a/blockify/util.py b/blockify/util.py index c066428..98faa05 100755 --- a/blockify/util.py +++ b/blockify/util.py @@ -11,7 +11,7 @@ except ImportError: log.error("ImportError: Please install docopt to use the CLI.") -VERSION = "3.4.0" +VERSION = "3.5.0" CONFIG = None CONFIG_DIR = os.path.expanduser("~/.config/blockify") CONFIG_FILE = os.path.join(CONFIG_DIR, "blockify.ini")