added some media keys
This commit is contained in:
1 parent
14ac9b197d
commit
6d37093b26
2 files changed
+82
-10
No files matched your search
@@ -1,3 +1,4 @@
|
|||||||
|
import multiprocessing
|
||||||
import configparser
|
import configparser
|
||||||
import logging
|
import logging
|
||||||
import asyncio
|
import asyncio
|
||||||
@@ -11,10 +12,6 @@ LOG_FILE = 'textual.log'
|
|||||||
LOGGING_LEVEL = logging.DEBUG
|
LOGGING_LEVEL = logging.DEBUG
|
||||||
LOG_FORMAT = '[%(asctime)s] %(levelname)s in %(module)s: %(message)s'
|
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
|
# Read config file
|
||||||
config = configparser.ConfigParser()
|
config = configparser.ConfigParser()
|
||||||
config.read('config.ini')
|
config.read('config.ini')
|
||||||
@@ -25,16 +22,11 @@ BITRATE = re.findall(r'[0-9]+', BITRATE)[0]
|
|||||||
BITRATE = int(BITRATE) if BITRATE else BITRATE
|
BITRATE = int(BITRATE) if BITRATE else BITRATE
|
||||||
IMAGE_MODE = config.getboolean('GENERAL', 'GUI')
|
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
|
import SubSonic
|
||||||
|
|
||||||
os.environ['PYGAME_HIDE_SUPPORT_PROMPT'] = "hide"
|
os.environ['PYGAME_HIDE_SUPPORT_PROMPT'] = "hide"
|
||||||
import pygame
|
import pygame
|
||||||
|
import pynput
|
||||||
|
|
||||||
from rich.text import Text
|
from rich.text import Text
|
||||||
|
|
||||||
@@ -967,6 +959,45 @@ class MainView(Screen):
|
|||||||
queue_table.update_queue()
|
queue_table.update_queue()
|
||||||
app.update_now_playing()
|
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):
|
class TextualSubpyPlayer(App):
|
||||||
BINDINGS = [
|
BINDINGS = [
|
||||||
("r", "rewind()", "⏪"),
|
("r", "rewind()", "⏪"),
|
||||||
@@ -998,12 +1029,17 @@ class TextualSubpyPlayer(App):
|
|||||||
is_paused: bool = False
|
is_paused: bool = False
|
||||||
place: int = 0
|
place: int = 0
|
||||||
|
|
||||||
|
music_command_queue: multiprocessing.Queue
|
||||||
|
|
||||||
check_playing_timer: Timer | None = None
|
check_playing_timer: Timer | None = None
|
||||||
|
check_music_commands: Timer
|
||||||
|
|
||||||
def on_mount(self) -> None:
|
def on_mount(self) -> None:
|
||||||
self.title = "SubPy"
|
self.title = "SubPy"
|
||||||
self.switch_mode('main')
|
self.switch_mode('main')
|
||||||
|
|
||||||
|
self.check_music_commands = self.set_interval(2/3, self._music_player_loop, pause=False)
|
||||||
|
|
||||||
@work(exclusive=True)
|
@work(exclusive=True)
|
||||||
async def play_song(self, song: SubSonic.Song) -> None:
|
async def play_song(self, song: SubSonic.Song) -> None:
|
||||||
self.currently_playing = song
|
self.currently_playing = song
|
||||||
@@ -1051,6 +1087,24 @@ class TextualSubpyPlayer(App):
|
|||||||
# Set callback loop for progress bar
|
# Set callback loop for progress bar
|
||||||
# self.progress_timer = self.set_interval(1 / 10, self.update_progress, pause=False)
|
# 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:
|
def _cache_audio(self, path: str, content: bytes) -> None:
|
||||||
"""Helper method to write audio to cache."""
|
"""Helper method to write audio to cache."""
|
||||||
with open(path, "wb") as file:
|
with open(path, "wb") as file:
|
||||||
@@ -1168,5 +1222,22 @@ class TextualSubpyPlayer(App):
|
|||||||
logging.debug(event.key)
|
logging.debug(event.key)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
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 = TextualSubpyPlayer()
|
||||||
|
app.music_command_queue = q
|
||||||
|
|
||||||
app.run()
|
app.run()
|
||||||
|
p.terminate()
|
||||||
@@ -3,3 +3,4 @@ textual
|
|||||||
pygame
|
pygame
|
||||||
Pillow
|
Pillow
|
||||||
rich-pixels
|
rich-pixels
|
||||||
|
pynput
|
||||||
Reference in new issue
Block a user