diff --git a/README.md b/README.md index 04d5410..d133dc1 100644 --- a/README.md +++ b/README.md @@ -24,19 +24,22 @@ 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.  -* ***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 (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`. #### mpd * ***host*:** The mpd host @@ -80,7 +83,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. diff --git a/bin/miniplayer b/bin/miniplayer index 14b0d46..1d7e496 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 @@ -9,21 +11,25 @@ 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")) 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", @@ -46,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 <Enter> to continue) ...") + # Load configured keybindings keybindings = config["keybindings"] @@ -59,6 +82,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 +95,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) @@ -141,6 +161,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) @@ -314,14 +339,57 @@ 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) + + 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 = self.art_http_session.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() + + + 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 +400,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 +485,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..51fbc27 100644 --- a/config.example +++ b/config.example @@ -1,13 +1,16 @@ [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 +# http_cover_filenames = cover.jpg cover.png folder.jpg folder.png art.jpg art.png artwork.jpg artwork.png [mpd] host = localhost diff --git a/setup.cfg b/setup.cfg index e49db48..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 @@ -22,5 +22,6 @@ install_requires = ffmpeg-python pixcat pillow + requests ueberzug