From 6d37093b265f50756d3ea2fc6ed25bb2c145853e Mon Sep 17 00:00:00 2001 From: JISAUAY Date: Thu, 2 Oct 2025 14:54:35 -0500 Subject: [PATCH] added some media keys --- app.py | 91 ++++++++++++++++++++++++++++++++++++++++++------ requirements.txt | 1 + 2 files changed, 82 insertions(+), 10 deletions(-) diff --git a/app.py b/app.py index c261d02..fcfc654 100644 --- a/app.py +++ b/app.py @@ -1,3 +1,4 @@ +import multiprocessing import configparser import logging import asyncio @@ -11,10 +12,6 @@ LOG_FILE = 'textual.log' LOGGING_LEVEL = logging.DEBUG LOG_FORMAT = '[%(asctime)s] %(levelname)s in %(module)s: %(message)s' -# Clear Log File -if os.path.exists(LOG_FILE): - os.remove(LOG_FILE) - # Read config file config = configparser.ConfigParser() config.read('config.ini') @@ -25,16 +22,11 @@ BITRATE = re.findall(r'[0-9]+', BITRATE)[0] BITRATE = int(BITRATE) if BITRATE else BITRATE IMAGE_MODE = config.getboolean('GENERAL', 'GUI') -os.makedirs(".cache/art/", exist_ok=True) -os.makedirs(".cache/audio/", exist_ok=True) -os.makedirs(".cache/art/artists/", exist_ok=True) - -logging.basicConfig(filename=LOG_FILE, level=LOGGING_LEVEL, format=LOG_FORMAT) - import SubSonic os.environ['PYGAME_HIDE_SUPPORT_PROMPT'] = "hide" import pygame +import pynput from rich.text import Text @@ -967,6 +959,45 @@ class MainView(Screen): queue_table.update_queue() app.update_now_playing() +# Startup keyboard listener for media keys +def listen_media_keys(queue: multiprocessing.Queue): + + media_log_file = 'media_keys.log' + logger = logging.getLogger('media_keys') + logger.setLevel(LOGGING_LEVEL) + formatter = logging.Formatter('[%(asctime)s] %(levelname)s in %(module)s: %(message)s') + handler = logging.FileHandler(media_log_file) + handler.setFormatter(formatter) + logger.addHandler(handler) + + logger.info('media_key logger initialized') + + try: + def on_press(key: pynput.keyboard.Key | pynput.keyboard.KeyCode | None) -> None: + logger.debug(f'Process Key: {key}') + match key: + case pynput.keyboard.Key.media_play_pause: + logger.info('Media play/pause key pressed') + queue.put('play_pause') + case pynput.keyboard.Key.media_next: + logger.info('Media next key pressed') + queue.put('next') + case pynput.keyboard.Key.media_previous: + logger.info('Media rewind pressed') + queue.put('prev') + case pynput.keyboard.Key.media_volume_down: + logger.info('Media volume down') + queue.put('vdown') + case pynput.keyboard.Key.media_volume_up: + logger.info('Media volume up') + queue.put('udown') + with pynput.keyboard.Listener(on_press=on_press) as listener: + logger.info('Media key listener started') + listener.join() + except Exception as e: + pass + logger.error(f'Media key listener failed: {e}') + class TextualSubpyPlayer(App): BINDINGS = [ ("r", "rewind()", "⏪"), @@ -998,12 +1029,17 @@ class TextualSubpyPlayer(App): is_paused: bool = False place: int = 0 + music_command_queue: multiprocessing.Queue + check_playing_timer: Timer | None = None + check_music_commands: Timer def on_mount(self) -> None: self.title = "SubPy" self.switch_mode('main') + self.check_music_commands = self.set_interval(2/3, self._music_player_loop, pause=False) + @work(exclusive=True) async def play_song(self, song: SubSonic.Song) -> None: self.currently_playing = song @@ -1051,6 +1087,24 @@ class TextualSubpyPlayer(App): # Set callback loop for progress bar # self.progress_timer = self.set_interval(1 / 10, self.update_progress, pause=False) + def _music_player_loop(self) -> None: + if self.music_command_queue.empty(): + return + + command = self.music_command_queue.get_nowait() + + match command: + case "play_pause": + self.action_play_pause_song() + case "next": + self.action_skip() + case "prev": + self.action_rewind() + case "vdown": + self.action_volume_down() + case "udown": + self.action_volume_up() + def _cache_audio(self, path: str, content: bytes) -> None: """Helper method to write audio to cache.""" with open(path, "wb") as file: @@ -1168,5 +1222,22 @@ class TextualSubpyPlayer(App): logging.debug(event.key) if __name__ == '__main__': + os.makedirs(".cache/art/", exist_ok=True) + os.makedirs(".cache/audio/", exist_ok=True) + os.makedirs(".cache/art/artists/", exist_ok=True) + + # Clear Log File + if os.path.exists(LOG_FILE): + os.remove(LOG_FILE) + + logging.basicConfig(filename=LOG_FILE, level=LOGGING_LEVEL, format=LOG_FORMAT) + + q = multiprocessing.Queue(-1) + p = multiprocessing.Process(target=listen_media_keys, args=(q,)) + p.start() + app = TextualSubpyPlayer() + app.music_command_queue = q + app.run() + p.terminate() diff --git a/requirements.txt b/requirements.txt index 8b890f2..365ff1a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,3 +3,4 @@ textual pygame Pillow rich-pixels +pynput