From 8793e28fe06627deb988eebc0d7f6944f461f5dc Mon Sep 17 00:00:00 2001 From: Bart Kleijngeld Date: Mon, 9 Aug 2021 15:40:54 +0200 Subject: [PATCH 1/8] Added possibility to fetch album art from HTTP server --- bin/miniplayer | 97 +++++++++++++++++++++++++++++++++++--------------- config.example | 6 ++-- setup.cfg | 1 + 3 files changed, 74 insertions(+), 30 deletions(-) diff --git a/bin/miniplayer b/bin/miniplayer index 14b0d46..204047f 100755 --- a/bin/miniplayer +++ b/bin/miniplayer @@ -1,6 +1,8 @@ #!/bin/python import curses import os +import posixpath +import requests from mpd import MPDClient import ffmpeg import pixcat @@ -14,16 +16,19 @@ config = configparser.ConfigParser() config.read(os.path.expanduser("~/.config/miniplayer/config")) if "player" not in config.sections(): - config["player"] = {"music_directory": "~/Music", - "font_width": 11, + config["player"] = {"font_width": 11, "font_height": 24, - "image_method": "pixcat", "album_art_only": False, "volume_step": 5, "auto_close": False, "show_playlist": True, } +if "art" not in config.sections(): + config["art"] = {"music_directory": "~/Music", + "image_method": "pixcat", + } + if "mpd" not in config.sections(): config["mpd"] = {"host": "localhost", "port": "6600", @@ -59,6 +64,7 @@ for key, action in default_bindings.items(): keybindings[key] = action player_config = config["player"] +art_config = config["art"] mpd_config = config["mpd"] @@ -71,17 +77,13 @@ IMAGERATIO = (player_config.getint("font_width", 11), player_config.getint("font_height", 24) ) -# Music directory -MUSICDIR = player_config.get("music_directory", "~/Music") -MUSICDIR = os.path.expanduser(MUSICDIR) - # MPD config MPDHOST = mpd_config.get("host", "localhost") MPDPORT = mpd_config.getint("port", 6600) MPDPASS = mpd_config.get("pass", False) # What to use to draw images -IMAGEMETHOD = player_config.get("image_method", "pixcat") +IMAGEMETHOD = art_config.get("image_method", "pixcat") # Volume step VOLUMESTEP = player_config.getint("volume_step", 5) @@ -314,14 +316,51 @@ class Player: self.screen_height, self.screen_width = window_height, window_width - def getAlbumArt(self, song_file): + """ + A function that fetches the album art and saves + it to self.album_art_loc + """ + http_base_url = art_config.get("http_base_url") + + if http_base_url: + self._getAlbumArtFromHttpServer(http_base_url, song_file) + else: + self._getAlbumArtFromFile(song_file) + + + + def _getAlbumArtFromHttpServer(self, base_url, song_file): + """ + A function that fetches the album art from the configured + HTTP server, and saves it to self.album_art_loc + """ + + album = os.path.dirname(song_file) + album_art_url = posixpath.join(base_url, album, "cover.jpg") + + try: + album_art_resp = requests.get(album_art_url) + except requests.RequestException: + # If any exception occurs, simply give up and show default art. + self.drawDefaultAlbumArt() + + if album_art_resp.ok: + with open(self.album_art_loc, "wb") as f: + f.write(album_art_resp.content) + else: + self.drawDefaultAlbumArt() + + + def _getAlbumArtFromFile(self, song_file): """ A function that extracts the album art from song_file and saves it to self.album_art_loc """ + music_dir = os.path.expanduser( + art_config.get("music_directory", "~/Music")) - song_file_abs = os.path.join(MUSICDIR, song_file) + song_file_abs = os.path.join(music_dir, song_file) process = ( ffmpeg @@ -332,30 +371,34 @@ class Player: try: process.run(quiet=True, overwrite_output=True) except ffmpeg._run.Error: - foregroundCol = "#D8DEE9" - backgroundCol = "#262A33" + self.drawDefaultAlbumArt() - size = 512*4 - art = Image.new("RGB", (size, size), color=backgroundCol) - d = ImageDraw.Draw(art) + def drawDefaultAlbumArt(self): + foregroundCol = "#D8DEE9" + backgroundCol = "#262A33" - for i in range(4): - offset = (i - 2) * 70 + size = 512*4 - external = size/3 + art = Image.new("RGB", (size, size), color=backgroundCol) + d = ImageDraw.Draw(art) - x0 = round(external) - offset - y0 = round(external) + offset - x1 = round(external*2) - offset - y1 = round(external*2) + offset + for i in range(4): + offset = (i - 2) * 70 - externalyx = [(x0, y0), (x1, y1)] + external = size/3 - d.rectangle(externalyx, outline=foregroundCol, width=40) + x0 = round(external) - offset + y0 = round(external) + offset + x1 = round(external*2) - offset + y1 = round(external*2) + offset - art.resize((512, 512)) - art.save(self.album_art_loc, "PNG") + externalyx = [(x0, y0), (x1, y1)] + + d.rectangle(externalyx, outline=foregroundCol, width=40) + + art.resize((512, 512)) + art.save(self.album_art_loc, "PNG") def getSongInfo(self, song): @@ -413,8 +456,6 @@ class Player: self.selected_song = int(song["pos"]) self.album, self.artist, self.title = self.getSongInfo(song) - self.last_song = song - self.getAlbumArt(song["file"]) self.last_song = song diff --git a/config.example b/config.example index 54c19a2..ea72a9b 100644 --- a/config.example +++ b/config.example @@ -1,13 +1,15 @@ [player] -music_directory = ~/Music font_width = 11 font_height = 24 -image_method = pixcat volume_step = 5 auto_close = false album_art_only = false show_playlist = true +[art] +image_method = pixcat +music_directory = ~/Music +# http_base_url = http://localhost:6667/cover-art [mpd] host = localhost diff --git a/setup.cfg b/setup.cfg index e49db48..47c4de2 100644 --- a/setup.cfg +++ b/setup.cfg @@ -22,5 +22,6 @@ install_requires = ffmpeg-python pixcat pillow + requests ueberzug From e9e377f2eef8f01ba55634b9289a2087f6d52bb3 Mon Sep 17 00:00:00 2001 From: Bart Kleijngeld Date: Mon, 9 Aug 2021 15:47:16 +0200 Subject: [PATCH 2/8] Updated README and project files --- README.md | 10 +++++++--- setup.cfg | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 04d5410..ba89143 100644 --- a/README.md +++ b/README.md @@ -24,19 +24,23 @@ pip install miniplayer The config file is located at `~/.config/miniplayer/config`. The example configuration file, [`config.example`](config.example), has all the default values. You will need to create the file yourself. #### player -* ***music_directory*:** The path to your music directory for extracting album art. * ***font_width*:** The width of your font in pixels in the actual terminal. * ***font_height*:** The height of your font in pixels in the actual terminal. ![font-example](https://github.com/GuardKenzie/miniplayer/blob/main/img/font.png?raw=true) -* ***image_method*:** The method to use for drawing album art. Available values are `pixcat` and `ueberzug` - If you are not using Kitty, try `ueberzug`. * ***volume_step*:** The ammount (in percents) the volume will be adjusted on pressing the volume up and volume down keys. * ***album_art_only*:** Whether or not to only draw the album art and no other track info (`true/false`). * ***auto_close*:** Whether or not to automatically close the player once the mpd playlist has concluded (`true/false`). * ***show_playlist*:** Whether or not to show the playlist view. +### art +* ***music_directory*:** The path to your music directory for extracting album art from the files. +* ***http_base_url*:** Base URL of webserver which serves the album art for your albums. Useful for users of Android MPD clients _MAFA_ or _MPDroid_. + +For more information see [the MPDroid wiki](https://github.com/abarisain/dmix/wiki/Album-Art-on-your-LAN). +* ***image_method*:** The method to use for drawing album art. Available values are `pixcat` and `ueberzug` + If you are not using Kitty, try `ueberzug`. #### mpd * ***host*:** The mpd host diff --git a/setup.cfg b/setup.cfg index 47c4de2..910f309 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = miniplayer -version = 1.3.2 +version = 1.4.0 description = An mpd client with album art and basic functionality. long_description = file: README.md long_description_content_type = text/markdown From e2b777489f2d37870942388eb839e52f5cf4f48c Mon Sep 17 00:00:00 2001 From: Bart Kleijngeld Date: Mon, 9 Aug 2021 15:55:50 +0200 Subject: [PATCH 3/8] Updated README --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index ba89143..ee7d5f5 100644 --- a/README.md +++ b/README.md @@ -36,9 +36,7 @@ The config file is located at `~/.config/miniplayer/config`. The example configu ### art * ***music_directory*:** The path to your music directory for extracting album art from the files. -* ***http_base_url*:** Base URL of webserver which serves the album art for your albums. Useful for users of Android MPD clients _MAFA_ or _MPDroid_. - -For more information see [the MPDroid wiki](https://github.com/abarisain/dmix/wiki/Album-Art-on-your-LAN). +* ***http_base_url*:** Base URL of webserver which serves the album art for your albums (takes precedence over `music_directory`). Useful for users of Android MPD clients _MAFA_ or _MPDroid_. For more information see [the MPDroid wiki](https://github.com/abarisain/dmix/wiki/Album-Art-on-your-LAN). * ***image_method*:** The method to use for drawing album art. Available values are `pixcat` and `ueberzug` If you are not using Kitty, try `ueberzug`. From f9db89ec978986157c90a7df177c61943255eb2d Mon Sep 17 00:00:00 2001 From: Bart Kleijngeld Date: Mon, 9 Aug 2021 15:56:39 +0200 Subject: [PATCH 4/8] Updated README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ee7d5f5..27b53b4 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ The config file is located at `~/.config/miniplayer/config`. The example configu * ***auto_close*:** Whether or not to automatically close the player once the mpd playlist has concluded (`true/false`). * ***show_playlist*:** Whether or not to show the playlist view. -### art +#### art * ***music_directory*:** The path to your music directory for extracting album art from the files. * ***http_base_url*:** Base URL of webserver which serves the album art for your albums (takes precedence over `music_directory`). Useful for users of Android MPD clients _MAFA_ or _MPDroid_. For more information see [the MPDroid wiki](https://github.com/abarisain/dmix/wiki/Album-Art-on-your-LAN). * ***image_method*:** The method to use for drawing album art. Available values are `pixcat` and `ueberzug` From 2de0e879e58fe6c0a4411356d1bb996500b9356f Mon Sep 17 00:00:00 2001 From: Bart Kleijngeld Date: Mon, 9 Aug 2021 15:57:54 +0200 Subject: [PATCH 5/8] Updated README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 27b53b4..3302693 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,7 @@ These keybinds can be changed by editing the config file. See the [`config.examp ## F.A.Q. - **Q:** Album art is not showing up. - **A:** Make sure your `music_directory` is not quoted i.e. if your music directory is `~/My Music` then your config should look like `music_directory = ~/My Music`. + **A:** If you're using `music_directory` for fetching your album art, make sure your it is not quoted i.e. if your music directory is `~/My Music` then your config should look like `music_directory = ~/My Music`. If this does not work, try changing `image_method` from `pixcat` to `ueberzug` or vice versa. - **Q:** Album art is too big/too small. From 8deec2b4992cc0cd883ef690119a1a6ad52ca9c9 Mon Sep 17 00:00:00 2001 From: Bart Kleijngeld Date: Mon, 9 Aug 2021 16:21:55 +0200 Subject: [PATCH 6/8] Added possibility to configure what art filenames to try with webserver call --- README.md | 1 + bin/miniplayer | 24 +++++++++++++++--------- config.example | 1 + 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 3302693..d133dc1 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,7 @@ The config file is located at `~/.config/miniplayer/config`. The example configu #### art * ***music_directory*:** The path to your music directory for extracting album art from the files. * ***http_base_url*:** Base URL of webserver which serves the album art for your albums (takes precedence over `music_directory`). Useful for users of Android MPD clients _MAFA_ or _MPDroid_. For more information see [the MPDroid wiki](https://github.com/abarisain/dmix/wiki/Album-Art-on-your-LAN). +* ***http_cover_filenames*:** Space separated list of filenames to use in the call to the webserver to fetch the album art. * ***image_method*:** The method to use for drawing album art. Available values are `pixcat` and `ueberzug` If you are not using Kitty, try `ueberzug`. diff --git a/bin/miniplayer b/bin/miniplayer index 204047f..bf0d6cb 100755 --- a/bin/miniplayer +++ b/bin/miniplayer @@ -337,17 +337,23 @@ class Player: """ album = os.path.dirname(song_file) - album_art_url = posixpath.join(base_url, album, "cover.jpg") - try: - album_art_resp = requests.get(album_art_url) - except requests.RequestException: - # If any exception occurs, simply give up and show default art. - self.drawDefaultAlbumArt() + for cover_filename in art_config.get("http_cover_filenames").split(): + album_art_url = posixpath.join(base_url, album, cover_filename) - if album_art_resp.ok: - with open(self.album_art_loc, "wb") as f: - f.write(album_art_resp.content) + try: + album_art_resp = requests.get(album_art_url) + except requests.RequestException: + # If any exception occurs, simply give up and show default art. + self.drawDefaultAlbumArt() + break + + if album_art_resp.ok: + with open(self.album_art_loc, "wb") as f: + f.write(album_art_resp.content) + break + elif album_art_resp.status_code == 404: + continue else: self.drawDefaultAlbumArt() diff --git a/config.example b/config.example index ea72a9b..51fbc27 100644 --- a/config.example +++ b/config.example @@ -10,6 +10,7 @@ show_playlist = true image_method = pixcat music_directory = ~/Music # http_base_url = http://localhost:6667/cover-art +# http_cover_filenames = cover.jpg cover.png folder.jpg folder.png art.jpg art.png artwork.jpg artwork.png [mpd] host = localhost From c398b53f4d13c9b3dbcad51f007dd76a60e1edb7 Mon Sep 17 00:00:00 2001 From: Bart Kleijngeld Date: Mon, 9 Aug 2021 16:34:04 +0200 Subject: [PATCH 7/8] Using requests HTTP session for better performance --- bin/miniplayer | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/bin/miniplayer b/bin/miniplayer index bf0d6cb..d55ef09 100755 --- a/bin/miniplayer +++ b/bin/miniplayer @@ -143,6 +143,11 @@ class Player: self.last_song = None + # Album art HTTP server + + if art_config.get("http_base_url"): + self.art_http_session = requests.Session() + # Album art only flag self.album_art_only = player_config.getboolean("album_art_only", False) @@ -338,11 +343,11 @@ class Player: album = os.path.dirname(song_file) - for cover_filename in art_config.get("http_cover_filenames").split(): + for cover_filename in art_config.get("http_cover_filenames", "cover.jpg").split(): album_art_url = posixpath.join(base_url, album, cover_filename) try: - album_art_resp = requests.get(album_art_url) + album_art_resp = self.art_http_session.get(album_art_url) except requests.RequestException: # If any exception occurs, simply give up and show default art. self.drawDefaultAlbumArt() From 2b113f73581d8c7eafb5a757d477045f3c2778ff Mon Sep 17 00:00:00 2001 From: Bart Kleijngeld Date: Thu, 9 Sep 2021 14:01:50 +0200 Subject: [PATCH 8/8] Added backwards compatibility with older configs --- bin/miniplayer | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/bin/miniplayer b/bin/miniplayer index d55ef09..1d7e496 100755 --- a/bin/miniplayer +++ b/bin/miniplayer @@ -11,6 +11,7 @@ import configparser import ueberzug.lib.v0 as ueberzug from PIL import Image, ImageDraw + # Get config config = configparser.ConfigParser() config.read(os.path.expanduser("~/.config/miniplayer/config")) @@ -51,6 +52,23 @@ default_bindings = {">": "next_track", if "keybindings" not in config.sections(): config["keybindings"] = default_bindings +# Ensure compatibility with older configs +deprecated_field_notice = ("The config option `{field}` under the `player` " + "section is deprecated and will be removed in the " + "future. Use the config option `{field}` under the " + "`art` section instead.") +deprecated_fields_present = False +for field in ["music_directory", "image_method"]: + if config.has_option("player", field): + deprecated_fields_present = True + print(deprecated_field_notice.format(field=field)) + + if not config.has_option("art", field): + config["art"][field] = config["player"][field] + +if deprecated_fields_present: + input("(Press to continue) ...") + # Load configured keybindings keybindings = config["keybindings"]