34
README.md
34
README.md
@ -35,6 +35,7 @@ The config file is located at `~/.config/miniplayer/config`. The example configu
|
|||||||
* ***volume_step*:** The ammount (in percents) the volume will be adjusted on pressing the volume up and volume down keys.
|
* ***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`).
|
* ***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`).
|
* ***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.
|
||||||
|
|
||||||
|
|
||||||
#### mpd
|
#### mpd
|
||||||
@ -44,7 +45,7 @@ The config file is located at `~/.config/miniplayer/config`. The example configu
|
|||||||
|
|
||||||
|
|
||||||
#### keybindings
|
#### keybindings
|
||||||
This section allows you to change the keybinds for the player. The format for a keybind is `key = action` (for example `p = play_pause`). Available actions are
|
This section allows you to change the keybinds for the player. The format for a keybind is `key = action` (for example `p = play_pause` or `left = last_track`). Available actions are
|
||||||
* `play_pause`
|
* `play_pause`
|
||||||
* `next_track`
|
* `next_track`
|
||||||
* `last_track`
|
* `last_track`
|
||||||
@ -53,20 +54,26 @@ This section allows you to change the keybinds for the player. The format for a
|
|||||||
* `toggle_info`
|
* `toggle_info`
|
||||||
* `help`
|
* `help`
|
||||||
* `quit`
|
* `quit`
|
||||||
|
* `select_down`
|
||||||
|
* `select_up`
|
||||||
|
* `select`
|
||||||
|
|
||||||
|
|
||||||
## Default keybinds
|
## Default keybinds
|
||||||
|
|
||||||
| Key | function |
|
| Key | function |
|
||||||
|-----|---------------|
|
|-------|---------------------|
|
||||||
| h | Show keybinds |
|
| h | Show keybinds |
|
||||||
| p | Play/pause |
|
| p | Play/pause |
|
||||||
| > | Next track |
|
| > | Next track |
|
||||||
| < | Last track |
|
| < | Last track |
|
||||||
| q | Quit |
|
| q | Quit |
|
||||||
| + | Volume up |
|
| + | Volume up |
|
||||||
| - | Volume down |
|
| - | Volume down |
|
||||||
| i | Toggle info |
|
| i | Toggle info |
|
||||||
|
| Up | Selection up |
|
||||||
|
| Down | Selection down |
|
||||||
|
| Enter | Play selected song |
|
||||||
|
|
||||||
These keybinds can be changed by editing the config file. See the [`config.example`](config.example) file for the format.
|
These keybinds can be changed by editing the config file. See the [`config.example`](config.example) file for the format.
|
||||||
|
|
||||||
@ -78,3 +85,8 @@ If this does not work, try changing `image_method` from `pixcat` to `ueberzug` o
|
|||||||
|
|
||||||
2. **Q:** Album art is too big/too small.
|
2. **Q:** Album art is too big/too small.
|
||||||
**A:** You need to configure `font_height` and `font_width`. Their values should be the actual pixel height and width of a character in your terminal.
|
**A:** You need to configure `font_height` and `font_width`. Their values should be the actual pixel height and width of a character in your terminal.
|
||||||
|
|
||||||
|
|
||||||
|
## More screenshots!
|
||||||
|
|
||||||
|

|
||||||
|
331
bin/miniplayer
331
bin/miniplayer
@ -2,7 +2,6 @@
|
|||||||
import curses
|
import curses
|
||||||
import os
|
import os
|
||||||
from mpd import MPDClient
|
from mpd import MPDClient
|
||||||
import mpd
|
|
||||||
import ffmpeg
|
import ffmpeg
|
||||||
import pixcat
|
import pixcat
|
||||||
import time
|
import time
|
||||||
@ -21,7 +20,8 @@ if "player" not in config.sections():
|
|||||||
"image_method": "pixcat",
|
"image_method": "pixcat",
|
||||||
"album_art_only": False,
|
"album_art_only": False,
|
||||||
"volume_step": 5,
|
"volume_step": 5,
|
||||||
"auto_close": False
|
"auto_close": False,
|
||||||
|
"show_playlist": True,
|
||||||
}
|
}
|
||||||
|
|
||||||
if "mpd" not in config.sections():
|
if "mpd" not in config.sections():
|
||||||
@ -31,14 +31,17 @@ if "mpd" not in config.sections():
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Initialise keybindings
|
# Initialise keybindings
|
||||||
default_bindings = {">": "next_track",
|
default_bindings = {">": "next_track",
|
||||||
"<": "last_track",
|
"<": "last_track",
|
||||||
"+": "volume_up",
|
"+": "volume_up",
|
||||||
"-": "volume_down",
|
"-": "volume_down",
|
||||||
"p": "play_pause",
|
"p": "play_pause",
|
||||||
"q": "quit",
|
"q": "quit",
|
||||||
"h": "help",
|
"h": "help",
|
||||||
"i": "toggle_info"
|
"i": "toggle_info",
|
||||||
|
"down": "select_down",
|
||||||
|
"up": "select_up",
|
||||||
|
"enter": "select"
|
||||||
}
|
}
|
||||||
|
|
||||||
if "keybindings" not in config.sections():
|
if "keybindings" not in config.sections():
|
||||||
@ -87,6 +90,12 @@ VOLUMESTEP = player_config.getint("volume_step", 5)
|
|||||||
# Autoclose boolean
|
# Autoclose boolean
|
||||||
AUTOCLOSE = player_config.getboolean("auto_close", False)
|
AUTOCLOSE = player_config.getboolean("auto_close", False)
|
||||||
|
|
||||||
|
# Playlist padding
|
||||||
|
PLAYLISTMARGIN = 4
|
||||||
|
|
||||||
|
# Config option to display the playlist
|
||||||
|
DISABLEPLAYLIST = not player_config.getboolean("show_playlist", True)
|
||||||
|
|
||||||
|
|
||||||
def albumArtSize(album_space, window_width):
|
def albumArtSize(album_space, window_width):
|
||||||
"""
|
"""
|
||||||
@ -104,11 +113,15 @@ def albumArtSize(album_space, window_width):
|
|||||||
return image_width_px, image_width, image_height
|
return image_width_px, image_width, image_height
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Player:
|
class Player:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
# Curses initialisation
|
# Curses initialisation
|
||||||
self.stdscr = curses.initscr()
|
self.stdscr = curses.initscr()
|
||||||
self.stdscr.nodelay(True)
|
self.stdscr.nodelay(True)
|
||||||
|
self.stdscr.keypad(True)
|
||||||
|
|
||||||
# Curses config
|
# Curses config
|
||||||
curses.noecho()
|
curses.noecho()
|
||||||
@ -129,15 +142,39 @@ class Player:
|
|||||||
|
|
||||||
self.last_song = None
|
self.last_song = None
|
||||||
|
|
||||||
# Curses window
|
# Album art only flag
|
||||||
self.window_height, self.window_width = self.stdscr.getmaxyx()
|
self.album_art_only = player_config.getboolean("album_art_only", False)
|
||||||
self.win = curses.newwin(self.window_height, self.window_width, 0, 0)
|
|
||||||
|
|
||||||
self.text_start = int(self.window_height - 5)
|
# Screen size
|
||||||
|
maxyx = self.stdscr.getmaxyx()
|
||||||
|
self.screen_height, self.screen_width = maxyx
|
||||||
|
|
||||||
|
# Album art window
|
||||||
|
self.art_window_height, self.art_window_width = self.albumArtWinWidth(*maxyx)
|
||||||
|
self.art_win = curses.newwin(
|
||||||
|
self.art_window_height, self.art_window_width,
|
||||||
|
0, 0
|
||||||
|
)
|
||||||
|
|
||||||
|
# Playlist window
|
||||||
|
if self.playlistFits(*maxyx) and not self.album_art_only:
|
||||||
|
self.draw_playlist = True
|
||||||
|
self.playlist_window_width = maxyx[1] - self.art_window_width - PLAYLISTMARGIN
|
||||||
|
self.playlist_window_height = maxyx[0]
|
||||||
|
|
||||||
|
self.playlist_win = curses.newwin(
|
||||||
|
self.playlist_window_height, self.playlist_window_width,
|
||||||
|
0, self.art_window_width + PLAYLISTMARGIN
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.draw_playlist = False
|
||||||
|
self.playlist_win = None
|
||||||
|
|
||||||
|
self.text_start = int(self.art_window_height - 5)
|
||||||
self.album_space = self.text_start - 2
|
self.album_space = self.text_start - 2
|
||||||
|
|
||||||
# Calculate the size of the image
|
# Calculate the size of the image
|
||||||
self.image_width_px, self.image_width, self.image_height = albumArtSize(self.album_space, self.window_width)
|
self.image_width_px, self.image_width, self.image_height = albumArtSize(self.album_space, self.art_window_width)
|
||||||
self.image_y_pos = (self.album_space - self.image_height) // 2 + 1
|
self.image_y_pos = (self.album_space - self.image_height) // 2 + 1
|
||||||
|
|
||||||
# Album art location
|
# Album art location
|
||||||
@ -153,12 +190,34 @@ class Player:
|
|||||||
# Update needed flag
|
# Update needed flag
|
||||||
self.update_needed = False
|
self.update_needed = False
|
||||||
|
|
||||||
# Album art only flag
|
|
||||||
self.album_art_only = player_config.getboolean("album_art_only", False)
|
|
||||||
|
|
||||||
# Flag to check if any music has been played
|
# Flag to check if any music has been played
|
||||||
self.has_music_been_played = False
|
self.has_music_been_played = False
|
||||||
|
|
||||||
|
# A counter to check how long since playlist has moved
|
||||||
|
self.control_cycle = 0
|
||||||
|
|
||||||
|
# Selected song in playlist
|
||||||
|
self.selected_song = 0
|
||||||
|
|
||||||
|
|
||||||
|
def playlistFits(self, height, width):
|
||||||
|
"""
|
||||||
|
A function that checks if the playlist display should be drawn
|
||||||
|
based on the provided height and width
|
||||||
|
"""
|
||||||
|
return height / width < 1/3 and not DISABLEPLAYLIST
|
||||||
|
|
||||||
|
|
||||||
|
def albumArtWinWidth(self, height, width):
|
||||||
|
"""
|
||||||
|
A function that calculates the album art window height and
|
||||||
|
width based on the window height and width
|
||||||
|
"""
|
||||||
|
if self.playlistFits(height, width) and not self.album_art_only:
|
||||||
|
return height, round(width * 2/5)
|
||||||
|
else:
|
||||||
|
return height, width
|
||||||
|
|
||||||
|
|
||||||
def fitText(self):
|
def fitText(self):
|
||||||
"""
|
"""
|
||||||
@ -169,7 +228,7 @@ class Player:
|
|||||||
song = self.title
|
song = self.title
|
||||||
album = self.album
|
album = self.album
|
||||||
artist = self.artist
|
artist = self.artist
|
||||||
width = self.window_width
|
width = self.art_window_width
|
||||||
|
|
||||||
if len(song) > width:
|
if len(song) > width:
|
||||||
song = song[:width - len(song)]
|
song = song[:width - len(song)]
|
||||||
@ -199,30 +258,63 @@ class Player:
|
|||||||
"""
|
"""
|
||||||
A function to check if the window size changed
|
A function to check if the window size changed
|
||||||
"""
|
"""
|
||||||
new_height, new_width = self.stdscr.getmaxyx()
|
window_height, window_width = self.stdscr.getmaxyx()
|
||||||
|
|
||||||
if (new_height, new_width) != (self.window_height, self.window_width) or force_update:
|
if (window_height, window_width) != (self.screen_height, self.screen_width) or force_update:
|
||||||
self.win.clear()
|
|
||||||
|
|
||||||
# Curses window
|
self.draw_playlist = self.playlistFits(window_height, window_width) and not self.album_art_only
|
||||||
self.window_height, self.window_width = self.stdscr.getmaxyx()
|
|
||||||
|
# Album art window
|
||||||
|
self.art_window_height, self.art_window_width = self.albumArtWinWidth(window_height, window_width)
|
||||||
|
|
||||||
|
# Playlist window
|
||||||
|
if self.draw_playlist:
|
||||||
|
self.playlist_window_width = window_width - self.art_window_width - PLAYLISTMARGIN
|
||||||
|
self.playlist_window_height = window_height
|
||||||
|
|
||||||
|
# Close the playlist window if it exists
|
||||||
|
elif self.playlist_win is not None:
|
||||||
|
del self.playlist_win
|
||||||
|
self.playlist_win = None
|
||||||
|
|
||||||
# Check if we are drawing info
|
# Check if we are drawing info
|
||||||
if self.album_art_only:
|
if self.album_art_only:
|
||||||
self.text_start = int(self.window_height)
|
self.text_start = int(self.art_window_height)
|
||||||
self.album_space = self.text_start - 1
|
self.album_space = self.text_start - 1
|
||||||
else:
|
else:
|
||||||
self.text_start = int(self.window_height - 5)
|
self.text_start = int(self.art_window_height - 5)
|
||||||
self.album_space = self.text_start - 2
|
self.album_space = self.text_start - 2
|
||||||
|
|
||||||
# Calculate the size of the image
|
# Calculate the size of the image
|
||||||
self.image_width_px, self.image_width, self.image_height = albumArtSize(self.album_space, self.window_width)
|
self.image_width_px, self.image_width, self.image_height = albumArtSize(self.album_space, self.art_window_width)
|
||||||
self.image_y_pos = (self.album_space - self.image_height) // 2 + 1
|
self.image_y_pos = (self.album_space - self.image_height) // 2 + 1
|
||||||
|
|
||||||
# Resize the window
|
# Check if playlist window exists and if we are drawing it
|
||||||
self.win.resize(self.window_height, self.window_width)
|
if self.playlist_win is not None and self.draw_playlist:
|
||||||
|
self.playlist_win.clear()
|
||||||
|
self.playlist_win.refresh()
|
||||||
|
|
||||||
|
self.playlist_win.resize(
|
||||||
|
self.playlist_window_height,
|
||||||
|
self.playlist_window_width
|
||||||
|
)
|
||||||
|
|
||||||
|
self.playlist_win.mvwin(0, self.art_window_width + PLAYLISTMARGIN)
|
||||||
|
|
||||||
|
elif self.draw_playlist:
|
||||||
|
self.playlist_win = curses.newwin(
|
||||||
|
self.playlist_window_height, self.playlist_window_width,
|
||||||
|
0, self.art_window_width + PLAYLISTMARGIN
|
||||||
|
)
|
||||||
|
|
||||||
self.last_song = None
|
self.last_song = None
|
||||||
|
|
||||||
|
# Resize the window
|
||||||
|
self.art_win.clear()
|
||||||
|
self.art_win.resize(self.art_window_height, self.art_window_width)
|
||||||
|
|
||||||
|
self.screen_height, self.screen_width = window_height, window_width
|
||||||
|
|
||||||
|
|
||||||
def getAlbumArt(self, song_file):
|
def getAlbumArt(self, song_file):
|
||||||
"""
|
"""
|
||||||
@ -233,9 +325,9 @@ class Player:
|
|||||||
song_file_abs = os.path.join(MUSICDIR, song_file)
|
song_file_abs = os.path.join(MUSICDIR, song_file)
|
||||||
|
|
||||||
process = (
|
process = (
|
||||||
ffmpeg
|
ffmpeg
|
||||||
.input(song_file_abs)
|
.input(song_file_abs)
|
||||||
.output(self.album_art_loc)
|
.output(self.album_art_loc)
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -261,13 +353,12 @@ class Player:
|
|||||||
|
|
||||||
externalyx = [(x0, y0), (x1, y1)]
|
externalyx = [(x0, y0), (x1, y1)]
|
||||||
|
|
||||||
d.rectangle(externalyx, outline=foregroundCol, width=40)# fill=foregroundCol)
|
d.rectangle(externalyx, outline=foregroundCol, width=40)
|
||||||
# d.ellipse(internalxy, fill=backgroundCol)
|
|
||||||
art.resize((512, 512))
|
art.resize((512, 512))
|
||||||
art.save(self.album_art_loc, "PNG")
|
art.save(self.album_art_loc, "PNG")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def checkSongUpdate(self):
|
def checkSongUpdate(self):
|
||||||
"""
|
"""
|
||||||
Checks if there is a new song playing
|
Checks if there is a new song playing
|
||||||
@ -288,7 +379,11 @@ class Player:
|
|||||||
self.progress = self.elapsed/self.duration
|
self.progress = self.elapsed/self.duration
|
||||||
|
|
||||||
if self.last_song != song:
|
if self.last_song != song:
|
||||||
self.win.clear()
|
self.art_win.clear()
|
||||||
|
|
||||||
|
# Move selected_song to the currently playing one
|
||||||
|
if self.control_cycle == 0:
|
||||||
|
self.selected_song = int(song["pos"])
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.album = song["album"]
|
self.album = song["album"]
|
||||||
@ -324,11 +419,10 @@ class Player:
|
|||||||
"""
|
"""
|
||||||
A function that toggles the display of track info
|
A function that toggles the display of track info
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.album_art_only = not self.album_art_only
|
self.album_art_only = not self.album_art_only
|
||||||
self.win.clear()
|
|
||||||
self.updateWindowSize(force_update=True)
|
self.updateWindowSize(force_update=True)
|
||||||
self.win.refresh()
|
self.art_win.clear()
|
||||||
|
self.art_win.refresh()
|
||||||
|
|
||||||
|
|
||||||
def handleKeypress(self):
|
def handleKeypress(self):
|
||||||
@ -345,7 +439,15 @@ class Player:
|
|||||||
'h' -- Help
|
'h' -- Help
|
||||||
"""
|
"""
|
||||||
|
|
||||||
anytime_keys = ["quit", "help"]
|
anytime_keys = ["quit", "help", "select_up", "select_down", "select"]
|
||||||
|
|
||||||
|
special_key_map = {curses.KEY_UP: "up",
|
||||||
|
curses.KEY_DOWN: "down",
|
||||||
|
curses.KEY_LEFT: "left",
|
||||||
|
curses.KEY_RIGHT: "right",
|
||||||
|
curses.KEY_ENTER: "enter",
|
||||||
|
10: "enter"
|
||||||
|
}
|
||||||
|
|
||||||
if self.checkSongUpdate() == 1:
|
if self.checkSongUpdate() == 1:
|
||||||
stopped = True
|
stopped = True
|
||||||
@ -357,7 +459,10 @@ class Player:
|
|||||||
|
|
||||||
while key > 0:
|
while key > 0:
|
||||||
# Resolve every key in buffer
|
# Resolve every key in buffer
|
||||||
keyChar = chr(key).lower()
|
if key in special_key_map.keys():
|
||||||
|
keyChar = special_key_map[key]
|
||||||
|
else:
|
||||||
|
keyChar = chr(key).lower()
|
||||||
|
|
||||||
# Parse key
|
# Parse key
|
||||||
if keyChar not in keybindings.keys():
|
if keyChar not in keybindings.keys():
|
||||||
@ -400,6 +505,22 @@ class Player:
|
|||||||
self.toggleInfo()
|
self.toggleInfo()
|
||||||
self.update_needed = True
|
self.update_needed = True
|
||||||
|
|
||||||
|
elif action == "select_up":
|
||||||
|
self.control_cycle = 1
|
||||||
|
self.selected_song -= 1
|
||||||
|
self.update_needed = True
|
||||||
|
|
||||||
|
elif action == "select_down":
|
||||||
|
self.control_cycle = 1
|
||||||
|
self.selected_song += 1
|
||||||
|
self.update_needed = True
|
||||||
|
|
||||||
|
elif action == "select":
|
||||||
|
self.control_cycle = 1
|
||||||
|
self.client.play(self.selected_song % len(self.client.playlist()))
|
||||||
|
self.update_needed = True
|
||||||
|
|
||||||
|
|
||||||
key = self.stdscr.getch()
|
key = self.stdscr.getch()
|
||||||
|
|
||||||
def drawInfo(self):
|
def drawInfo(self):
|
||||||
@ -415,41 +536,101 @@ class Player:
|
|||||||
|
|
||||||
if state == 0:
|
if state == 0:
|
||||||
# Everything fits
|
# Everything fits
|
||||||
self.win.addstr(self.text_start, 0, f"{title}")
|
self.art_win.addstr(self.text_start, 0, f"{title}")
|
||||||
self.win.addstr(self.text_start + 1, 0, f"{artist}{seperator}{album}")
|
self.art_win.addstr(self.text_start + 1, 0, f"{artist}{seperator}{album}")
|
||||||
|
|
||||||
elif state == 1:
|
elif state == 1:
|
||||||
# Too wide
|
# Too wide
|
||||||
self.win.addstr(self.text_start - 1, 0, f"{title}")
|
self.art_win.addstr(self.text_start - 1, 0, f"{title}")
|
||||||
self.win.addstr(self.text_start, 0, f"{album}")
|
self.art_win.addstr(self.text_start, 0, f"{album}")
|
||||||
self.win.addstr(self.text_start + 1, 0, f"{artist}")
|
self.art_win.addstr(self.text_start + 1, 0, f"{artist}")
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# No album
|
# No album
|
||||||
self.win.addstr(self.text_start, 0, f"{title}")
|
self.art_win.addstr(self.text_start, 0, f"{title}")
|
||||||
self.win.addstr(self.text_start + 1, 0, f"{artist}")
|
self.art_win.addstr(self.text_start + 1, 0, f"{artist}")
|
||||||
|
|
||||||
|
|
||||||
# Progress bar
|
# Progress bar
|
||||||
song_duration = (int(self.duration / 60), round(self.duration % 60))
|
song_duration = (int(self.duration / 60), round(self.duration % 60))
|
||||||
song_elapsed = (int(self.elapsed / 60), round(self.elapsed % 60))
|
song_elapsed = (int(self.elapsed / 60), round(self.elapsed % 60))
|
||||||
|
|
||||||
self.win.addstr(
|
self.art_win.addstr(
|
||||||
self.text_start + 2, 0,
|
self.text_start + 2, 0,
|
||||||
"-"*(int((self.window_width - 1) * self.progress)) + ">",
|
"-"*(int((self.art_window_width - 1) * self.progress)) + ">",
|
||||||
curses.color_pair(1)
|
curses.color_pair(1)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Duration string
|
# Duration string
|
||||||
time_string = f"{song_elapsed[0]}:{song_elapsed[1]:02d}/{song_duration[0]}:{song_duration[1]:02d}"
|
time_string = f"{song_elapsed[0]}:{song_elapsed[1]:02d}/{song_duration[0]}:{song_duration[1]:02d}"
|
||||||
|
|
||||||
self.win.addstr(
|
self.art_win.addstr(
|
||||||
self.text_start + 3, 0,
|
self.text_start + 3, 0,
|
||||||
f"{time_string:>{self.window_width}}",
|
f"{time_string:>{self.art_window_width}}",
|
||||||
curses.color_pair(2)
|
curses.color_pair(2)
|
||||||
)
|
)
|
||||||
|
|
||||||
self.win.refresh()
|
self.art_win.refresh()
|
||||||
|
|
||||||
|
|
||||||
|
def drawPlaylist(self):
|
||||||
|
"""
|
||||||
|
A function that draws the playlist
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Draw playlist
|
||||||
|
if not self.draw_playlist:
|
||||||
|
return
|
||||||
|
|
||||||
|
playlist = self.client.playlistinfo()
|
||||||
|
current_song = self.client.currentsong()
|
||||||
|
|
||||||
|
# selected_pos = int(current_song["pos"])
|
||||||
|
selected_pos = self.selected_song % len(playlist)
|
||||||
|
|
||||||
|
# Determine where to start the playlist
|
||||||
|
if selected_pos > self.playlist_window_height // 2 and len(playlist) > self.playlist_window_height:
|
||||||
|
start = selected_pos - (self.playlist_window_height - 1) // 2
|
||||||
|
else:
|
||||||
|
start = 0
|
||||||
|
|
||||||
|
start = min(abs(len(playlist) - self.playlist_window_height), start)
|
||||||
|
|
||||||
|
line = 0
|
||||||
|
while line < self.playlist_window_height:
|
||||||
|
# Check if playlist is empty
|
||||||
|
if line + start < len(playlist):
|
||||||
|
playlist_item = playlist[start + line]
|
||||||
|
else:
|
||||||
|
playlist_item = None
|
||||||
|
|
||||||
|
# Decide color
|
||||||
|
pair = 0
|
||||||
|
|
||||||
|
if playlist_item == current_song:
|
||||||
|
pair = curses.color_pair(2)
|
||||||
|
|
||||||
|
if playlist_item == playlist[selected_pos]:
|
||||||
|
pair = curses.color_pair(2) | curses.A_REVERSE
|
||||||
|
|
||||||
|
# Move and write text
|
||||||
|
try:
|
||||||
|
self.playlist_win.move(line, 0)
|
||||||
|
|
||||||
|
if playlist_item is not None:
|
||||||
|
self.playlist_win.addstr(
|
||||||
|
f"{playlist_item['artist']} - {playlist_item['title']}"[:self.playlist_window_width - 1],
|
||||||
|
pair
|
||||||
|
)
|
||||||
|
|
||||||
|
self.playlist_win.clrtoeol()
|
||||||
|
|
||||||
|
except curses.error:
|
||||||
|
return
|
||||||
|
|
||||||
|
line += 1
|
||||||
|
|
||||||
|
self.playlist_win.refresh()
|
||||||
|
|
||||||
|
|
||||||
def hideAlbumArt(self):
|
def hideAlbumArt(self):
|
||||||
@ -457,7 +638,7 @@ class Player:
|
|||||||
A function that hides the album art
|
A function that hides the album art
|
||||||
"""
|
"""
|
||||||
if IMAGEMETHOD == "ueberzug":
|
if IMAGEMETHOD == "ueberzug":
|
||||||
self.art_placement.visibility = ueberzug.Visibility.INVISIBLE
|
self.art_placement.visibility = ueberzug.Visibility.INVISIBLE
|
||||||
|
|
||||||
|
|
||||||
def drawAlbumArt(self):
|
def drawAlbumArt(self):
|
||||||
@ -467,7 +648,7 @@ class Player:
|
|||||||
|
|
||||||
if IMAGEMETHOD == "ueberzug":
|
if IMAGEMETHOD == "ueberzug":
|
||||||
# Figure out new placement
|
# Figure out new placement
|
||||||
self.art_placement.x = (self.window_width - self.image_width)//2
|
self.art_placement.x = (self.art_window_width - self.image_width)//2
|
||||||
self.art_placement.y = self.image_y_pos
|
self.art_placement.y = self.image_y_pos
|
||||||
|
|
||||||
# Figure out height and width
|
# Figure out height and width
|
||||||
@ -483,8 +664,8 @@ class Player:
|
|||||||
elif IMAGEMETHOD == "pixcat":
|
elif IMAGEMETHOD == "pixcat":
|
||||||
(
|
(
|
||||||
pixcat.Image(self.album_art_loc)
|
pixcat.Image(self.album_art_loc)
|
||||||
.thumbnail(self.image_width_px )
|
.thumbnail(self.image_width_px)
|
||||||
.show(x=(self.window_width - self.image_width)//2, y=self.image_y_pos)
|
.show(x=(self.art_window_width - self.image_width)//2, y=self.image_y_pos)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -498,8 +679,8 @@ class Player:
|
|||||||
string -- The string to draw
|
string -- The string to draw
|
||||||
"""
|
"""
|
||||||
|
|
||||||
x_pos = self.window_width / 2 - len(string) / 2
|
x_pos = self.art_window_width / 2 - len(string) / 2
|
||||||
self.win.addstr(y, int(x_pos), string)
|
self.art_win.addstr(y, int(x_pos), string)
|
||||||
|
|
||||||
|
|
||||||
def drawHelp(self):
|
def drawHelp(self):
|
||||||
@ -512,18 +693,17 @@ class Player:
|
|||||||
|
|
||||||
# Left and right margin pct
|
# Left and right margin pct
|
||||||
lr_margin_pct = 0.1
|
lr_margin_pct = 0.1
|
||||||
lr_margin = round(self.window_width * lr_margin_pct)
|
lr_margin = round(self.art_window_width * lr_margin_pct)
|
||||||
|
|
||||||
# Actual space for text
|
# Actual space for text
|
||||||
x_space = self.window_width - 2 * (lr_margin)
|
x_space = self.art_window_width - 2 * (lr_margin)
|
||||||
|
|
||||||
# Check if window has been cleared
|
# Check if window has been cleared
|
||||||
if not self.cleared:
|
if not self.cleared:
|
||||||
self.win.clear()
|
self.art_win.clear()
|
||||||
self.cleared = True
|
self.cleared = True
|
||||||
|
|
||||||
# Figure out center, y_start and x_start
|
# Figure out center, y_start and x_start
|
||||||
center_y, center_x = (self.window_height // 2, self.window_width // 2)
|
|
||||||
y_start = top_vspace
|
y_start = top_vspace
|
||||||
x_start = int(lr_margin)
|
x_start = int(lr_margin)
|
||||||
|
|
||||||
@ -535,9 +715,9 @@ class Player:
|
|||||||
y_start += 1
|
y_start += 1
|
||||||
sep = "." * (x_space - len(key) - len(desc) - 2)
|
sep = "." * (x_space - len(key) - len(desc) - 2)
|
||||||
desc = desc.replace("_", " ").capitalize()
|
desc = desc.replace("_", " ").capitalize()
|
||||||
self.win.addstr(y_start, x_start, f"{key} {sep} {desc}")
|
self.art_win.addstr(y_start, x_start, f"{key} {sep} {desc}")
|
||||||
|
|
||||||
self.win.refresh()
|
self.art_win.refresh()
|
||||||
|
|
||||||
|
|
||||||
def draw(self):
|
def draw(self):
|
||||||
@ -545,12 +725,12 @@ class Player:
|
|||||||
The function that draws the now playing window
|
The function that draws the now playing window
|
||||||
"""
|
"""
|
||||||
if not self.cleared:
|
if not self.cleared:
|
||||||
self.win.clear()
|
self.art_win.clear()
|
||||||
self.cleared = True
|
self.cleared = True
|
||||||
|
|
||||||
# Force window nings
|
# Force window nings
|
||||||
self.win.redrawln(0, 1)
|
self.art_win.redrawln(0, 1)
|
||||||
self.win.addstr(0, 0, " ")
|
self.art_win.addstr(0, 0, " ")
|
||||||
|
|
||||||
# Get mpd state
|
# Get mpd state
|
||||||
state = self.checkSongUpdate()
|
state = self.checkSongUpdate()
|
||||||
@ -561,13 +741,14 @@ class Player:
|
|||||||
# Check if the playlist has concluded and if we should close
|
# Check if the playlist has concluded and if we should close
|
||||||
raise KeyboardInterrupt
|
raise KeyboardInterrupt
|
||||||
|
|
||||||
self.win.clear()
|
self.art_win.clear()
|
||||||
self.hideAlbumArt()
|
self.hideAlbumArt()
|
||||||
|
|
||||||
infomsg = "Put some beats on!"
|
infomsg = "Put some beats on!"
|
||||||
|
|
||||||
self.win.addstr(self.window_height // 2, (self.window_width - len(infomsg)) // 2, infomsg)
|
self.art_win.addstr(self.art_window_height // 2, (self.art_window_width - len(infomsg)) // 2, infomsg)
|
||||||
self.win.refresh()
|
self.art_win.refresh()
|
||||||
|
self.drawPlaylist()
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -576,6 +757,7 @@ class Player:
|
|||||||
# Draw the window
|
# Draw the window
|
||||||
if not self.album_art_only:
|
if not self.album_art_only:
|
||||||
self.drawInfo()
|
self.drawInfo()
|
||||||
|
self.drawPlaylist()
|
||||||
|
|
||||||
self.drawAlbumArt()
|
self.drawAlbumArt()
|
||||||
|
|
||||||
@ -617,6 +799,10 @@ class Player:
|
|||||||
|
|
||||||
self.update_needed = False
|
self.update_needed = False
|
||||||
|
|
||||||
|
# Update control_cycle once a second if it is not 0
|
||||||
|
if i == 0 and self.control_cycle != 0:
|
||||||
|
self.control_cycle = (self.control_cycle + 1) % 30
|
||||||
|
|
||||||
e = time.perf_counter()
|
e = time.perf_counter()
|
||||||
|
|
||||||
sleeptime = abs(1/FPS - (e-s))
|
sleeptime = abs(1/FPS - (e-s))
|
||||||
@ -644,6 +830,3 @@ except ConnectionRefusedError:
|
|||||||
curses.nocbreak()
|
curses.nocbreak()
|
||||||
curses.endwin()
|
curses.endwin()
|
||||||
print(f"Could not connect to mpd on {MPDHOST}:{MPDPORT}")
|
print(f"Could not connect to mpd on {MPDHOST}:{MPDPORT}")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,23 +1,28 @@
|
|||||||
[player]
|
[player]
|
||||||
music_directory = ~/Music
|
music_directory = ~/Music
|
||||||
font_width = 11
|
font_width = 11
|
||||||
font_height = 24
|
font_height = 24
|
||||||
image_method = pixcat
|
image_method = pixcat
|
||||||
volume_step = 5
|
volume_step = 5
|
||||||
auto_close = false
|
auto_close = false
|
||||||
album_art_only = false
|
album_art_only = false
|
||||||
|
show_playlist = true
|
||||||
|
|
||||||
|
|
||||||
[mpd]
|
[mpd]
|
||||||
host = localhost
|
host = localhost
|
||||||
port = 6600
|
port = 6600
|
||||||
# pass = example
|
# pass = example
|
||||||
|
|
||||||
# [keybindings]
|
# [keybindings]
|
||||||
# > = next_track
|
# > = next_track
|
||||||
# < = last_track
|
# < = last_track
|
||||||
# + = volume_up
|
# + = volume_up
|
||||||
# - = volume_down
|
# - = volume_down
|
||||||
# p = play_pause
|
# p = play_pause
|
||||||
# q = quit
|
# q = quit
|
||||||
# h = help
|
# h = help
|
||||||
# i = toggle_info
|
# i = toggle_info
|
||||||
|
# up = select_up
|
||||||
|
# down = select_down
|
||||||
|
# enter = select
|
||||||
|
BIN
img/playlist.png
Normal file
BIN
img/playlist.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 382 KiB |
@ -1,6 +1,6 @@
|
|||||||
[metadata]
|
[metadata]
|
||||||
name = miniplayer
|
name = miniplayer
|
||||||
version = 1.2.0
|
version = 1.3.0
|
||||||
description = An mpd client with album art and basic functionality.
|
description = An mpd client with album art and basic functionality.
|
||||||
long_description = file: README.md
|
long_description = file: README.md
|
||||||
long_description_content_type = text/markdown
|
long_description_content_type = text/markdown
|
||||||
|
Reference in New Issue
Block a user