Created
January 16, 2025 19:37
-
-
Save bonelifer/dec7e625e3411fc548b291909a800813 to your computer and use it in GitHub Desktop.
MPD Tray application(similar to RadioTray-NG) to manage and play categorized radio station URLs.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/python3 | |
""" | |
MPD Tray application(similar to RadioTray-NG) to manage and play categorized radio station URLs. | |
Provides Stop, Stations (organized by categories), Refresh, and Exit options in the tray menu. | |
Uses `mpc load URL` equivalent functionality, ensuring reconnection to MPD. | |
Pressing Stop clears the queue. | |
""" | |
# Editable variables | |
menu_icon = False # Set to True to use system icon (XFce add-folder-to-archive icon), False for custom icon | |
STATIONS_FILE = "stations.txt" # Path to the stations file | |
MPD_HOST = "localhost" # MPD server host | |
MPD_PORT = 6600 # MPD server port | |
SYSTEM_ICON_PATH = "/usr/share/icons/hicolor/16x16/actions/add-folder-to-archive.png" # Path to system icon | |
import os | |
from collections import defaultdict | |
from mpd import MPDClient | |
from pystray import Icon, Menu, MenuItem | |
from PIL import Image, ImageDraw, ImageOps | |
import psutil | |
def connect_mpd(): | |
"""Connect to the MPD server and return a client instance.""" | |
client = MPDClient() | |
try: | |
client.connect(MPD_HOST, MPD_PORT) | |
except Exception as e: | |
print(f"Error connecting to MPD: {e}") | |
return None | |
return client | |
def load_stations(): | |
"""Load stations from the stations.txt file, organized by categories.""" | |
stations_by_category = defaultdict(list) | |
if not os.path.exists(STATIONS_FILE): | |
print(f"{STATIONS_FILE} not found.") | |
return stations_by_category | |
with open(STATIONS_FILE, "r") as f: | |
for line in f: | |
line = line.strip() | |
# Skip comments or empty lines | |
if not line or line.startswith("#"): | |
continue | |
if "|" in line: | |
category, name, url = line.split("|", 2) | |
stations_by_category[category].append((name, url)) | |
return stations_by_category | |
def load_station(url): | |
"""Simulate `mpc load URL` functionality.""" | |
client = connect_mpd() | |
if not client: | |
return | |
try: | |
client.clear() # Clear the current playlist | |
client.load(url) # Load the URL directly into MPD | |
client.play() # Start playback | |
print(f"Loaded and playing: {url}") | |
except Exception as e: | |
print(f"Error loading URL: {e}") | |
finally: | |
client.close() | |
client.disconnect() | |
def play_station_wrapper(url): | |
"""Create a wrapper function to load the station URL.""" | |
def wrapper(icon, item): | |
load_station(url) | |
return wrapper | |
def stop_playback(icon, item): | |
"""Stop MPD playback and clear the queue.""" | |
client = connect_mpd() | |
if not client: | |
return | |
try: | |
client.stop() # Stop playback | |
client.clear() # Clear the playlist/queue | |
print("Playback stopped and queue cleared.") | |
except Exception as e: | |
print(f"Error stopping playback: {e}") | |
finally: | |
client.close() | |
client.disconnect() | |
def refresh(icon, item): | |
"""Refresh the station list in the tray menu.""" | |
icon.menu = create_menu(icon) | |
def create_icon_image(): | |
"""Create a tray icon image with a minimalistic design and a zigzag line.""" | |
# Create a 64x64 pixel image with transparent background | |
image = Image.new("RGBA", (64, 64), (0, 0, 0, 0)) # Transparent background | |
draw = ImageDraw.Draw(image) | |
# Define the icon's fill and outline colors based on dark mode compatibility | |
fill_color = (255, 255, 255) # White for a light icon | |
outline_color = (0, 0, 0) # Black outline | |
# Draw a circle icon with a simple line across the middle | |
draw.ellipse((10, 10, 54, 54), fill=fill_color, outline=outline_color) | |
# Define the points for the zigzag line inside the circle | |
zigzag_points = [ | |
(18, 32), # Starting point | |
(22, 28), # Zigzag down | |
(26, 32), # Zigzag up | |
(30, 28), # Zigzag down | |
(34, 32), # Zigzag up | |
(38, 28), # Zigzag down | |
(42, 32), # Zigzag up | |
(46, 28) # End point | |
] | |
# Draw the zigzag line (connecting the points) | |
draw.line(zigzag_points, fill=outline_color, width=2) | |
return image | |
def get_system_icon(icon_path): | |
"""Load a system icon from the given path.""" | |
try: | |
icon = Image.open(icon_path) | |
icon = icon.convert("RGBA") # Ensure the icon is in a format pystray can use | |
return icon | |
except Exception as e: | |
print(f"Error loading system icon: {e}") | |
return None | |
def create_menu(icon): | |
"""Create a dynamic tray menu with categories as individual items and a separator.""" | |
stations_by_category = load_stations() | |
# Create the main menu items | |
menu_items = [] | |
# Add Stop item | |
menu_items.append(MenuItem("Stop", stop_playback)) | |
# Add categories as menu items with subitems for each station | |
for category, stations in stations_by_category.items(): | |
category_item = MenuItem( | |
category, | |
Menu( | |
*[ | |
MenuItem(name, play_station_wrapper(url)) | |
for name, url in stations | |
] | |
) | |
) | |
menu_items.append(category_item) | |
# Add Refresh and Exit items | |
menu_items.append(MenuItem("Refresh", refresh)) | |
menu_items.append(MenuItem("Exit", lambda icon, item: icon.stop())) | |
# Create the main menu with the items | |
return Menu(*menu_items) | |
def main(): | |
"""Main function to run the tray application.""" | |
# Determine which icon to use | |
if menu_icon: | |
icon_image = get_system_icon(SYSTEM_ICON_PATH) | |
if icon_image is None: | |
print("System icon loading failed. Falling back to custom icon.") | |
icon_image = create_icon_image() | |
else: | |
icon_image = create_icon_image() # Use custom icon | |
icon = Icon("MPD", icon_image, menu=create_menu(None)) | |
icon.run() | |
if __name__ == "__main__": | |
main() | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#Category|Station Name|URL | |
Country|181.FM Classic Hits|http://www.181.fm/winamp.pls?station=181-greatoldies&file=181-greatoldies.pls | |
Covers|Covers|http://somafm.com/covers.pls | |
Country|Highway 181|http://www.181.fm/winamp.pls?station=181-highway&file=181-highway.pls | |
Radio NOIR|AM600 Conyers OTR|http://www.conyersradio.net/listen.m3u |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment