123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791 |
- #!/usr/bin/env python3
- import urwid
- import os
- import pygame.mixer
- import mutagen
- import sys
- from datetime import datetime, timedelta
- import subprocess
- # Палитра (без изменений)
- palette = [
- ('header', 'light blue', 'default'),
- ('path_label', 'light blue', 'default'),
- ('path_value', 'dark gray', 'default'),
- ('directory', 'dark blue,bold', 'default'),
- ('audio_file', 'light cyan', 'default'),
- ('normal', 'white', 'default'),
- ('selected', 'black', 'light green'),
- ('perm_denied', 'light red', 'default'),
- ('error', 'light red', 'default'),
- ('playing', 'light green', 'default'),
- ('pink_frame', 'light magenta', 'default'),
- ('percent', 'white,bold', 'default'),
- ]
- class PlaybackMode(urwid.ListBox):
- def __init__(self, main_loop, root_dir, input_path=None):
- pygame.mixer.init()
- self.main_loop = main_loop
- self.root_dir = root_dir
- self.current_dir = os.getcwd()
- self.dir_history = []
- self.file_list = urwid.SimpleFocusListWalker([])
- self.playlist = [] # Список для плейлиста
- self.playlist_index = 0 # Текущий индекс в плейлисте
-
- # Левая рамка: Box00 (без изменений)
- self.progress_bar = urwid.Text([('path_value', " 0"), ('percent', '%'), (None, " | " + " " * 83)], align='left')
- self.file_frame = urwid.LineBox(self.progress_bar, title="Playback progress", title_align='center',
- tlcorner='┌', tline='─', trcorner='┐',
- lline='│', rline='│',
- blcorner='└', bline='─', brcorner='┘')
- self.file_frame = urwid.AttrMap(self.file_frame, 'pink_frame')
-
- # Правая рамка: Volume Level (без изменений)
- # self.volume_bar = urwid.Text(f" 50% {'░' * 17 + ' ' * 17}")
- self.volume_bar = urwid.Text(f" 50% | {'░' * 22 + ' ' * 22}")
- self.metadata_frame = urwid.LineBox(self.volume_bar, title='Pygame.mixer volume Level', title_align='center',
- tlcorner='┌', tline='─', trcorner='┐',
- lline='│', rline='│',
- blcorner='└', bline='─', brcorner='┘')
- self.metadata_frame = urwid.AttrMap(self.metadata_frame, 'pink_frame')
-
- # Рамка пути (без изменений)
- self.path_text_inner = urwid.Text([('path_value', self.current_dir)], align='left')
- self.path_text = urwid.Padding(self.path_text_inner, left=1)
- self.path_filler = urwid.Filler(self.path_text, valign='top')
- self.path_frame = urwid.LineBox(self.path_filler, title="Path", title_align='center',
- tlcorner='┌', tline='─', trcorner='┐',
- lline='│', rline='│',
- blcorner='└', bline='─', brcorner='┘')
- self.path_widget = urwid.AttrMap(self.path_frame, 'pink_frame')
-
- # Status (без изменений)
- self.status_output = urwid.Text("", align='left')
- self.status_filler = urwid.Filler(self.status_output, valign='top')
-
- self.playing = False
- self.paused = False
- self.volume = 0.5
- self.current_audio_duration = 0
-
- # Инициализация системной громкости (без изменений)
- try:
- result = subprocess.check_output("amixer get Master | grep -o '[0-9]*%' | uniq", shell=True, text=True).strip()
- percent = int(result.rstrip('%'))
- filled = min(34, percent // 3)
- initial_volume_text = f" {percent}% {'░' * filled + ' ' * (34 - filled)}"
- except subprocess.CalledProcessError:
- initial_volume_text = " --% " + " " * 34
- self.system_volume_bar = urwid.Text(initial_volume_text)
-
- # Инициализация прогресс-бара для левого наушника (без изменений)
- try:
- result = subprocess.check_output("amixer sget 'Headphone' | grep 'Front Left' | grep -o '[0-9]\+%' | head -1", shell=True, text=True).strip()
- percent = int(result.rstrip('%'))
- filled = min(29, percent // 3)
- initial_headphone_left_text = f" {percent}% | {'░' * filled + ' ' * (29 - filled)}"
- except (subprocess.CalledProcessError, ValueError):
- initial_headphone_left_text = " --% | " + " " * 29
- self.headphone_left_bar = urwid.Text(initial_headphone_left_text, align='left')
-
- # Инициализация прогресс-бара для правого наушника (без изменений)
- try:
- result = subprocess.check_output("amixer sget 'Headphone' | grep 'Front Right' | grep -o '[0-9]\+%' | head -1", shell=True, text=True).strip()
- percent = int(result.rstrip('%'))
- filled = min(29, percent // 3)
- initial_headphone_right_text = f" {percent}% | {'░' * filled + ' ' * (29 - filled)}"
- except (subprocess.CalledProcessError, ValueError):
- initial_headphone_right_text = " --% | " + " " * 29
- self.headphone_right_bar = urwid.Text(initial_headphone_right_text, align='left')
-
- # Alisa (часы) (без изменений)
- self.alisa_text = urwid.Text("", align='center')
-
- super().__init__(self.file_list)
-
- self.input_path = input_path
- if not input_path:
- self.refresh_list()
-
- self.widget = None
- self.initialize_widget()
- def start(self):
- if self.input_path:
- if os.path.isdir(self.input_path):
- self.load_and_play_directory(self.input_path)
- elif os.path.isfile(self.input_path):
- self.load_and_play_audio(self.input_path)
- def update_clock(self, loop=None, data=None):
- SEASONS = {
- 1: "Winter", 2: "Winter", 3: "Spring",
- 4: "Spring", 5: "Spring", 6: "Summer",
- 7: "Summer", 8: "Summer", 9: "Autumn",
- 10: "Autumn", 11: "Autumn", 12: "Winter"
- }
- MONTHS = {
- 1: "January", 2: "February", 3: "March",
- 4: "April", 5: "May", 6: "June",
- 7: "July", 8: "August", 9: "September",
- 10: "October", 11: "November", 12: "December"
- }
- WEEKDAYS = {
- 0: "Monday", 1: "Tuesday", 2: "Wednesday",
- 3: "Thursday", 4: "Friday", 5: "Saturday", 6: "Sunday"
- }
-
- now = datetime.now()
- year = "2025"
- season = SEASONS[now.month]
- month = MONTHS[now.month]
- weekday = WEEKDAYS[now.weekday()]
- time_str = now.strftime('%H:%M:%S')
-
- clock_text = f"{year}\n{season} {month} {weekday}\n{time_str}"
- self.alisa_text.set_text(clock_text)
-
- if self.main_loop:
- self.main_loop.set_alarm_in(1 / 30, self.update_clock)
- def format_time(self, seconds):
- return str(timedelta(seconds=int(seconds))).zfill(8)
- def update_progress_bar(self, loop=None, data=None):
- if self.playing and not self.paused and pygame.mixer.music.get_busy():
- elapsed = pygame.mixer.music.get_pos() / 1000
- duration = self.current_audio_duration
- if duration > 0:
- progress_percent = min(100, int((elapsed / duration) * 100))
- filled = min(83, int(progress_percent / 1.2048))
- unfilled = 83 - filled
- progress_str = [('path_value', f"{progress_percent:3d}"), ('percent', '%'), (None, f" | {'░' * filled}{' ' * unfilled}")]
- self.progress_bar.set_text(progress_str)
- elapsed_str = self.format_time(elapsed)
- duration_str = self.format_time(duration)
- self.grannik_text.set_text(f" {elapsed_str} / {duration_str}")
- else:
- self.progress_bar.set_text([('path_value', " 0"), ('percent', '%'), (None, " | " + " " * 83)])
- self.grannik_text.set_text(" 00:00:00 / 00:00:00")
- if self.main_loop:
- self.main_loop.set_alarm_in(1, self.update_progress_bar)
- def initialize_widget(self):
- self.widget = self.wrap_in_three_frames()
- def wrap_in_three_frames(self):
- term_size = os.get_terminal_size()
- term_width = term_size.columns
- term_height = term_size.lines
- total_border_chars = 6
-
- available_width = term_width - total_border_chars
- num_upper_boxes = 3
- base_status_width = (available_width // 3 + available_width // 4) // 2
- status_width = max(15, base_status_width + 3)
- remaining_width = available_width - status_width - 7
- files_width = remaining_width // 2
- metadata_width = available_width - files_width - status_width + 2
-
- total_width_so_far = files_width + status_width + metadata_width + 8
- if total_width_so_far < available_width:
- metadata_width += available_width - total_width_so_far
-
- total_width = term_width - 1
- left_width = int(total_width * 0.62)
-
- combined_widget = urwid.Columns([
- (left_width, self.file_frame),
- ('weight', 1, self.metadata_frame),
- ], dividechars=1, box_columns=[0, 1])
- height_limited_widget = urwid.Filler(combined_widget, height=3, valign='top')
-
- box02_clone = urwid.LineBox(self.system_volume_bar, title='Amixer master volume Level', title_align='center',
- tlcorner='┌', tline='─', trcorner='┐',
- lline='│', rline='│',
- blcorner='└', bline='─', brcorner='┘')
- box02_clone = urwid.AttrMap(box02_clone, 'pink_frame')
-
- box02_clone2 = urwid.LineBox(self.headphone_left_bar, title='Amixer headphone left', title_align='center',
- tlcorner='┌', tline='─', trcorner='┐',
- lline='│', rline='│',
- blcorner='└', bline='─', brcorner='┘')
- box02_clone2 = urwid.AttrMap(box02_clone2, 'pink_frame')
-
- box02_clone3 = urwid.LineBox(self.headphone_right_bar, title='Amixer headphone right', title_align='center',
- tlcorner='┌', tline='─', trcorner='┐',
- lline='│', rline='│',
- blcorner='└', bline='─', brcorner='┘')
- box02_clone3 = urwid.AttrMap(box02_clone3, 'pink_frame')
-
- header_height = 3
- frame_border_height = 2
- available_height = term_height - header_height - frame_border_height
-
- upper_boxes_height = 3
- min_footer_height = 8
-
- columns_height = max(21, available_height - upper_boxes_height - min_footer_height - 1)
-
- new_left_frame = urwid.LineBox(self, title="Files and directories of the Linux OS", title_align='center',
- tlcorner='┌', tline='─', trcorner='┐',
- lline='│', rline='│',
- blcorner='└', bline='─', brcorner='┘')
- new_left_frame = urwid.AttrMap(new_left_frame, 'pink_frame')
- new_left_frame_filler = urwid.Filler(new_left_frame, height=columns_height, valign='top')
-
- self.metadata_output = urwid.Text("", align='left')
- self.metadata_filler = urwid.Filler(self.metadata_output, valign='top')
- new_right_frame = urwid.LineBox(self.metadata_filler, title="Info", title_align='center',
- tlcorner='┌', tline='─', trcorner='┐',
- lline='│', rline='│',
- blcorner='└', bline='─', brcorner='┘')
- new_right_frame = urwid.AttrMap(new_right_frame, 'pink_frame')
- new_right_frame_filler = urwid.Filler(new_right_frame, height=columns_height, valign='top')
-
- new_frames_widget = urwid.Columns([
- (left_width, new_left_frame_filler),
- ('weight', 1, new_right_frame_filler)
- ], dividechars=1)
-
- footer_width = left_width
- footer_height = available_height - columns_height - upper_boxes_height
- box_height = max(min_footer_height + 1, footer_height)
-
- divider_width = 2
- available_footer_width = footer_width - divider_width
- box1_width = available_footer_width // 3
- box2_width = available_footer_width // 3
- box4_width = available_footer_width // 3
-
- self.grannik_text = urwid.Text(" 00:00:00 / 00:00:00", align='left')
- box1 = urwid.LineBox(self.grannik_text, title="Playback time", title_align='center',
- tlcorner='┌', tline='─', trcorner='┐',
- lline='│', rline='│',
- blcorner='└', bline='─', brcorner='┘')
- box1 = urwid.AttrMap(box1, 'pink_frame')
- box1_filler = urwid.Filler(box1, height=box_height, valign='middle')
-
- box2 = urwid.LineBox(self.alisa_text, title="Current time", title_align='center',
- tlcorner='┌', tline='─', trcorner='┐',
- lline='│', rline='│',
- blcorner='└', bline='─', brcorner='┘')
- box2 = urwid.AttrMap(box2, 'pink_frame')
- box2_filler = urwid.Filler(box2, height=box_height, valign='middle')
-
- box4 = urwid.LineBox(self.status_filler, title="Status", title_align='center',
- tlcorner='┌', tline='─', trcorner='┐',
- lline='│', rline='│',
- blcorner='└', bline='─', brcorner='┘')
- box4 = urwid.AttrMap(box4, 'pink_frame')
- box4_filler = urwid.Filler(box4, height=box_height, valign='middle')
-
- footer_columns = urwid.Columns([
- (box1_width, box1_filler),
- (box2_width, box2_filler),
- (box4_width, box4_filler),
- ], dividechars=1, box_columns=[0, 1, 2])
-
- clone_width = metadata_width
- clones_pile = urwid.Pile([
- (3, box02_clone),
- (3, box02_clone2),
- (3, box02_clone3),
- ])
- clones_filler = urwid.Filler(clones_pile, height=9, valign='middle')
-
- footer_with_clones = urwid.Columns([
- (footer_width, footer_columns),
- (clone_width, clones_filler),
- ], dividechars=1, box_columns=[0, 1])
- footer_widget = urwid.Filler(footer_with_clones, height=box_height, valign='middle')
-
- body_widget = urwid.Pile([
- (columns_height, new_frames_widget),
- (upper_boxes_height, height_limited_widget),
- (box_height, footer_widget),
- ])
-
- frame_with_path = urwid.Frame(
- body=body_widget,
- header=self.path_widget,
- )
- return frame_with_path
- def load_and_play_audio(self, audio_file):
- full_path = os.path.abspath(audio_file)
- self.current_dir = os.path.dirname(full_path)
- file_name = os.path.basename(full_path)
-
- self.file_list.clear()
- padded_text = urwid.Padding(urwid.Text(file_name), left=1, right=1)
- self.file_list.append(urwid.AttrMap(padded_text, 'normal', 'selected'))
- self.set_focus(0)
-
- self.path_text_inner.set_text([('path_value', self.current_dir)])
- self.play_media(full_path)
- def load_and_play_directory(self, directory):
- full_path = os.path.abspath(directory)
- self.current_dir = full_path
- self.path_text_inner.set_text([('path_value', self.current_dir)])
-
- AUDIO_EXTENSIONS = {'mp3', 'wav', 'ogg', 'flac', 'aac', 'm4a', 'wma', 'opus'}
- try:
- all_files = sorted(os.listdir(self.current_dir))
- audio_files = [f for f in all_files
- if not f.startswith('.') and
- f.lower().split('.')[-1] in AUDIO_EXTENSIONS]
- if not audio_files:
- self.file_list.clear()
- self.file_list.append(urwid.AttrMap(urwid.Padding(urwid.Text("(empty)"), left=1, right=1), 'normal', 'selected'))
- return
-
- self.file_list.clear()
- self.playlist = [os.path.join(self.current_dir, f) for f in audio_files]
- self.playlist_index = 0
-
- for file in audio_files:
- padded_text = urwid.Padding(urwid.Text(file), left=1, right=1)
- self.file_list.append(urwid.AttrMap(padded_text, 'audio_file', 'selected'))
-
- self.set_focus(0)
- self.play_media(self.playlist[self.playlist_index])
- except PermissionError:
- self.file_list.clear()
- self.file_list.append(urwid.AttrMap(urwid.Padding(urwid.Text("(access denied)"), left=1, right=1), 'perm_denied', 'selected'))
- def check_playback_end(self):
- if self.main_loop is not None and self.playing and not pygame.mixer.music.get_busy() and not self.paused:
- self.next_track()
- if self.main_loop is not None:
- self.main_loop.set_alarm_in(0.1, lambda loop, data: self.check_playback_end())
- def next_track(self):
- if self.playlist and self.playlist_index < len(self.playlist) - 1:
- self.playlist_index += 1
- self.set_focus(self.playlist_index)
- self.play_media(self.playlist[self.playlist_index])
- else:
- pygame.mixer.music.stop()
- self.playing = False
- self.status_output.set_text(" Playlist ended")
- self.metadata_output.set_text("")
- def update_file_list(self):
- AUDIO_EXTENSIONS = {'mp3', 'wav', 'ogg', 'flac', 'aac', 'm4a', 'wma', 'opus'}
- try:
- all_files = sorted(os.listdir(self.current_dir))
- files = [f for f in all_files
- if not f.startswith('.') and
- (os.path.isdir(os.path.join(self.current_dir, f)) or
- f.lower().split('.')[-1] in AUDIO_EXTENSIONS)]
- if not files:
- files = ["(empty)"]
- except PermissionError:
- files = ["(access denied)"]
-
- file_items = []
- for file in files:
- full_path = os.path.join(self.current_dir, file)
- if os.path.isdir(full_path):
- attr = 'directory'
- display_name = file + "/"
- elif os.path.isfile(full_path):
- attr = 'audio_file'
- display_name = file
- else:
- attr = 'normal'
- display_name = file
- padded_text = urwid.Padding(urwid.Text(display_name), left=1, right=1)
- file_items.append(urwid.AttrMap(padded_text, attr, 'selected'))
-
- return file_items
- def refresh_list(self):
- old_focus = self.focus_position if self.file_list else 0
- self.file_list[:] = self.update_file_list()
- self.path_text_inner.set_text([('path_value', self.current_dir)])
- if self.file_list:
- self.set_focus(min(old_focus, len(self.file_list) - 1))
- def get_widget(self):
- return self.widget
- def cleanup(self):
- if self.playing:
- pygame.mixer.music.stop()
- self.status_output.set_text("")
- self.metadata_output.set_text("")
- self.playing = False
- self.paused = False
- def show_message(self, message, duration=1):
- if "Permission denied" in message:
- self.status_output.set_text([('perm_denied', f" {message}")])
- duration = 0
- elif "not found" in message or "Error" in message:
- self.status_output.set_text([('error', f" {message}")])
- duration = 2
- else:
- self.status_output.set_text([('normal', f" {message}")])
- self.main_loop.draw_screen()
- if duration > 0:
- def clear_message(loop, data):
- if self.status_output.text in ([('perm_denied', f" {message}"), ('error', f" {message}"), ('normal', f" {message}")]):
- self.status_output.set_text("")
- self.main_loop.draw_screen()
- self.main_loop.set_alarm_in(duration, clear_message)
- def clear_message(self):
- self.status_output.set_text("")
- self.main_loop.draw_screen()
- def get_metadata(self, filepath):
- try:
- audio = mutagen.File(filepath)
- if audio is None:
- return " No metadata available"
- metadata = []
- if hasattr(audio, 'info'):
- metadata.append(f" Duration: {audio.info.length:.2f} sec")
- metadata.append(f" Bitrate: {audio.info.bitrate // 1000} kbps")
- metadata.append(f" Channels: {audio.info.channels}")
- metadata.append(f" Sample Rate: {audio.info.sample_rate} Hz")
- if audio.tags:
- for key, value in audio.tags.items():
- value_str = str(value)[:50] + "..." if len(str(value)) > 50 else str(value)
- metadata.append(f" {key}: {value_str}")
-
- max_lines = 10
- if len(metadata) > max_lines:
- metadata = metadata[:max_lines - 1] + [" ... (truncated)"]
- return "\n".join(metadata) if metadata else " No metadata available"
- except Exception as e:
- return f" Error reading metadata: {e}"
- def play_media(self, filepath):
- if not os.path.exists(filepath):
- self.show_message(f"File not found: {filepath}")
- return
- if not os.access(filepath, os.R_OK):
- self.show_message("Permission denied!")
- return
- if self.playing:
- pygame.mixer.music.stop()
- try:
- pygame.mixer.music.load(filepath)
- pygame.mixer.music.set_volume(self.volume)
- pygame.mixer.music.play()
- self.playing = True
- self.paused = False
- self.status_output.set_text([("playing", f" Playing: {os.path.basename(filepath)}")])
- self.metadata_output.set_text(self.get_metadata(filepath))
- # filled = int(self.volume * 40)
- # self.volume_bar.set_text(f" {int(self.volume * 100)}% {'░' * filled + ' ' * (40 - filled)}")
- filled = int(self.volume * 44)
- self.volume_bar.set_text(f" {int(self.volume * 100)}% | {'░' * filled + ' ' * (44 - filled)}")
- audio = mutagen.File(filepath)
- if audio and hasattr(audio, 'info'):
- self.current_audio_duration = audio.info.length
- else:
- sound = pygame.mixer.Sound(filepath)
- self.current_audio_duration = sound.get_length()
- except Exception as e:
- self.show_message(f"Error playing media: {str(e)}")
- def keypress(self, size, key):
- current_message = self.status_output.text
- is_perm_denied = isinstance(current_message, list) and len(current_message) > 0 and "Permission denied" in current_message[0][1]
-
- help_text = [
- ('normal,bold', ' left'), ('path_value', ' - Go to parent directory.\n'),
- ('normal,bold', ' right'), ('path_value', ' - Go back in directory history.\n'),
- ('normal,bold', ' up'), ('path_value', ' - Move focus up in file list.\n'),
- ('normal,bold', ' down'), ('path_value', ' - Move focus down in file list.\n'),
- ('normal,bold', ' enter'), ('path_value', ' - Open folder or play file.\n'),
- ('normal,bold', ' space'), ('path_value', ' - Play directory as playlist.\n'),
- ('normal,bold', ' + -'), ('path_value', ' - Increase/Decrease volume (pygame).\n'),
- ('normal,bold', ' a'), ('path_value', ' - Увеличить громкость правого наушника\n'),
- ('normal,bold', ' b'), ('path_value', ' - Уменьшить громкость правого наушника\n'),
- ('normal,bold', ' c'), ('path_value', ' - Увеличить громкость левого наушника\n'),
- ('normal,bold', ' d'), ('path_value', ' - Decrease system volume.\n'),
- ('normal,bold', ' e'), ('path_value', ' - Увеличить громкость обоих наушников\n'),
- ('normal,bold', ' f'), ('path_value', ' - Уменьшить громкость обоих наушников\n'),
- ('normal,bold', ' g'), ('path_value', ' - Уменьшить громкость левого наушника\n'),
- ('normal,bold', ' p'), ('path_value', ' - Pause or resume playback.\n'),
- ('normal,bold', ' s'), ('path_value', ' - Stop playback.\n'),
- ('normal,bold', ' r'), ('path_value', ' - Restart current track.\n'),
- ('normal,bold', ' i'), ('path_value', ' - Increase system volume.\n'),
- ('normal,bold', ' n'), ('path_value', ' - Next track.\n'),
- ('normal,bold', ' q or Q'), ('path_value', ' - Quit program.\n'),
- ('normal,bold', ' h'), ('path_value', ' - Show help.')
- ]
-
- if key == 'h' and not self.playing and not self.paused:
- self.metadata_output.set_text(help_text)
- self.main_loop.draw_screen()
- elif key != 'h':
- if self.metadata_output.text == help_text:
- self.metadata_output.set_text("")
- self.main_loop.draw_screen()
-
- if key == 'left':
- if self.current_dir != "/":
- try:
- self.dir_history.append(self.current_dir)
- os.chdir("..")
- self.current_dir = os.getcwd()
- self.refresh_list()
- self.clear_message()
- self.main_loop.draw_screen()
- except PermissionError:
- self.show_message("Permission denied!")
- elif key == 'right':
- if self.dir_history:
- try:
- os.chdir(self.dir_history.pop())
- self.current_dir = os.getcwd()
- self.refresh_list()
- self.clear_message()
- self.main_loop.draw_screen()
- except PermissionError:
- self.show_message("Permission denied!")
- elif key == 'up' and self.focus_position > 0:
- self.set_focus(self.focus_position - 1)
- if not is_perm_denied:
- self.clear_message()
- elif key == 'down' and self.focus_position < len(self.file_list) - 1:
- self.set_focus(self.focus_position + 1)
- if not is_perm_denied:
- self.clear_message()
- elif key == 'enter':
- if not self.file_list or self.focus.original_widget.original_widget.text.strip() in ["(empty)", "(access denied)"]:
- return
- selected = self.focus.original_widget.original_widget.text.rstrip('/')
- full_path = os.path.join(self.current_dir, selected)
- try:
- if os.path.isdir(full_path):
- self.dir_history.append(self.current_dir)
- os.chdir(full_path)
- self.current_dir = os.getcwd()
- self.refresh_list()
- self.clear_message()
- self.main_loop.draw_screen()
- elif os.path.isfile(full_path):
- self.play_media(full_path)
- self.playlist = [full_path]
- self.playlist_index = 0
- except Exception as e:
- self.show_message(f"Error: {str(e)}")
- elif key == ' ':
- if self.playing or self.paused:
- pygame.mixer.music.stop()
- self.playing = False
- self.paused = False
- self.file_list.clear()
- self.load_and_play_directory(self.current_dir)
- self.check_playback_end()
- self.main_loop.draw_screen()
- elif key == 'p':
- if self.playing:
- if self.paused:
- pygame.mixer.music.unpause()
- self.paused = False
- filepath = os.path.join(self.current_dir, self.focus.original_widget.original_widget.text.rstrip('/'))
- self.status_output.set_text([("playing", f" Resumed: {os.path.basename(filepath)}")])
- else:
- pygame.mixer.music.pause()
- self.paused = True
- self.status_output.set_text(" Paused")
- elif key == 's':
- if self.playing:
- pygame.mixer.music.stop()
- self.playing = False
- self.paused = False
- self.status_output.set_text(" Stopped")
- self.metadata_output.set_text("")
- elif key == 'r':
- if self.playing or self.paused:
- filepath = os.path.join(self.current_dir, self.focus.original_widget.original_widget.text.rstrip('/'))
- pygame.mixer.music.stop()
- pygame.mixer.music.load(filepath)
- pygame.mixer.music.set_volume(self.volume)
- pygame.mixer.music.play()
- self.playing = True
- self.paused = False
- self.status_output.set_text([("playing", f" Replaying: {os.path.basename(filepath)}")])
- self.metadata_output.set_text(self.get_metadata(filepath))
- elif key == '+':
- self.volume = min(1.0, self.volume + 0.03)
- if self.playing:
- pygame.mixer.music.set_volume(self.volume)
- # filled = int(self.volume * 34)
- filled = int(self.volume * 44)
- # self.volume_bar.set_text(f" {int(self.volume * 100)}% {'░' * filled + ' ' * (34 - filled)}")
- self.volume_bar.set_text(f" {int(self.volume * 100)}% | {'░' * filled + ' ' * (44 - filled)}")
- self.main_loop.draw_screen()
- elif key == '-':
- self.volume = max(0.0, self.volume - 0.03)
- if self.playing:
- pygame.mixer.music.set_volume(self.volume)
- # filled = int(self.volume * 34)
- filled = int(self.volume * 44)
- # self.volume_bar.set_text(f" {int(self.volume * 100)}% {'░' * filled + ' ' * (34 - filled)}")
- self.volume_bar.set_text(f" {int(self.volume * 100)}% | {'░' * filled + ' ' * (44 - filled)}")
- self.main_loop.draw_screen()
- elif key == 'i':
- try:
- result = subprocess.check_output("amixer set Master 3%+ | grep -o '[0-9]\+%' | head -1", shell=True, text=True).strip()
- percent = int(result.rstrip('%'))
- filled = min(34, percent // 3)
- self.system_volume_bar.set_text(f" {percent}% {'░' * filled + ' ' * (34 - filled)}")
- self.main_loop.draw_screen()
- except subprocess.CalledProcessError as e:
- self.show_message(f"Error adjusting system volume: {e}")
- elif key == 'd':
- try:
- result = subprocess.check_output("amixer set Master 3%- | grep -o '[0-9]\+%' | head -1", shell=True, text=True).strip()
- percent = int(result.rstrip('%'))
- filled = min(34, percent // 3)
- self.system_volume_bar.set_text(f" {percent}% {'░' * filled + ' ' * (34 - filled)}")
- self.main_loop.draw_screen()
- except subprocess.CalledProcessError as e:
- self.show_message(f"Error adjusting system volume: {e}")
- elif key == 'a':
- try:
- result = subprocess.check_output("amixer sset 'Headphone' frontright 5%+ -q && amixer sget 'Headphone' | grep 'Front Right' | grep -o '[0-9]\+%' | head -1", shell=True, text=True).strip()
- percent = int(result.rstrip('%'))
- filled = min(29, percent // 3)
- self.headphone_right_bar.set_text(f" {percent}% | {'░' * filled + ' ' * (29 - filled)}")
- # self.status_output.set_text(f"Headphone Front Right: {result}")
- self.main_loop.draw_screen()
- except subprocess.CalledProcessError as e:
- pass # Пустой оператор, чтобы блок не был пустым
- # self.show_message(f"Error adjusting headphone volume: {e}")
- elif key == 'b':
- try:
- result = subprocess.check_output("amixer sset 'Headphone' frontright 5%- -q && amixer sget 'Headphone' | grep 'Front Right' | grep -o '[0-9]\+%' | head -1", shell=True, text=True).strip()
- percent = int(result.rstrip('%'))
- filled = min(29, percent // 3)
- self.headphone_right_bar.set_text(f" {percent}% | {'░' * filled + ' ' * (29 - filled)}")
- # self.status_output.set_text(f"Headphone Front Right: {result}")
- self.main_loop.draw_screen()
- except subprocess.CalledProcessError as e:
- self.show_message(f"Error adjusting headphone volume: {e}")
- elif key == 'c':
- try:
- result = subprocess.check_output("amixer sset 'Headphone' frontleft 5%+ -q && amixer sget 'Headphone' | grep 'Front Left' | grep -o '[0-9]\+%' | head -1", shell=True, text=True).strip()
- percent = int(result.rstrip('%'))
- filled = min(29, percent // 3)
- self.headphone_left_bar.set_text(f" {percent}% | {'░' * filled + ' ' * (29 - filled)}")
- self.status_output.set_text(f"Headphone Front Left: {result}")
- self.main_loop.draw_screen()
- except subprocess.CalledProcessError as e:
- self.show_message(f"Error adjusting headphone volume: {e}")
- elif key == 'g':
- try:
- result = subprocess.check_output("amixer sset 'Headphone' frontleft 5%- -q && amixer sget 'Headphone' | grep 'Front Left' | grep -o '[0-9]\+%' | head -1", shell=True, text=True).strip()
- percent = int(result.rstrip('%'))
- filled = min(29, percent // 3)
- self.headphone_left_bar.set_text(f" {percent}% | {'░' * filled + ' ' * (29 - filled)}")
- self.status_output.set_text(f"Headphone Front Left: {result}")
- self.main_loop.draw_screen()
- except subprocess.CalledProcessError as e:
- self.show_message(f"Error adjusting headphone volume: {e}")
- elif key == 'e':
- try:
- # Увеличиваем громкость обоих наушников
- subprocess.check_output("amixer sset 'Headphone' 5%+ -q", shell=True, text=True)
- # Получаем значение для левого наушника
- left_result = subprocess.check_output("amixer sget 'Headphone' | grep 'Front Left' | grep -o '[0-9]\+%' | head -1", shell=True, text=True).strip()
- left_percent = int(left_result.rstrip('%'))
- left_filled = min(29, left_percent // 3)
- self.headphone_left_bar.set_text(f" {left_percent}% | {'░' * left_filled + ' ' * (29 - left_filled)}")
- # Получаем значение для правого наушника
- right_result = subprocess.check_output("amixer sget 'Headphone' | grep 'Front Right' | grep -o '[0-9]\+%' | head -1", shell=True, text=True).strip()
- right_percent = int(right_result.rstrip('%'))
- right_filled = min(29, right_percent // 3)
- self.headphone_right_bar.set_text(f" {right_percent}% | {'░' * right_filled + ' ' * (29 - right_filled)}")
- self.status_output.set_text(f"Headphone Left Right: {left_result} {right_result}")
- self.main_loop.draw_screen()
- except subprocess.CalledProcessError as e:
- self.show_message(f"Error adjusting headphone volume: {e}")
- elif key == 'f':
- try:
- # Уменьшаем громкость обоих наушников
- subprocess.check_output("amixer sset 'Headphone' 5%- -q", shell=True, text=True)
- # Получаем значение для левого наушника
- left_result = subprocess.check_output("amixer sget 'Headphone' | grep 'Front Left' | grep -o '[0-9]\+%' | head -1", shell=True, text=True).strip()
- left_percent = int(left_result.rstrip('%'))
- left_filled = min(29, left_percent // 3)
- self.headphone_left_bar.set_text(f" {left_percent}% | {'░' * left_filled + ' ' * (29 - left_filled)}")
- # Получаем значение для правого наушника
- right_result = subprocess.check_output("amixer sget 'Headphone' | grep 'Front Right' | grep -o '[0-9]\+%' | head -1", shell=True, text=True).strip()
- right_percent = int(right_result.rstrip('%'))
- right_filled = min(29, right_percent // 3)
- self.headphone_right_bar.set_text(f" {right_percent}% | {'░' * right_filled + ' ' * (29 - right_filled)}")
- self.status_output.set_text(f"Headphone Left Right: {left_result} {right_result}")
- self.main_loop.draw_screen()
- except subprocess.CalledProcessError as e:
- self.show_message(f"Error adjusting headphone volume: {e}")
- elif key == 'n':
- self.next_track()
- elif key in ('q', 'Q'):
- self.cleanup()
- return 'q'
- else:
- super().keypress(size, key)
- return key
- def handle_input(self, key):
- if isinstance(key, str):
- return key
- return None
- class FileManager:
- def __init__(self, input_path=None):
- self.main_loop = None
- self.root_dir = os.path.dirname(os.path.abspath(__file__))
- self.mode = PlaybackMode(None, self.root_dir, input_path)
- initial_widget = self.wrap_mode_widget(self.mode.get_widget())
- self.frame = urwid.Frame(body=initial_widget)
- def wrap_mode_widget(self, widget):
- framed_widget = urwid.LineBox(
- widget, title="grnManagerTerm", title_align='center',
- tlcorner='╔', tline='═', trcorner='╗',
- lline='║', rline='║',
- blcorner='╚', bline='═', brcorner='╝'
- )
- return urwid.AttrMap(framed_widget, 'header')
- def unhandled_input(self, key):
- mode_key = self.mode.handle_input(key)
- if mode_key == 'q':
- self.mode.cleanup()
- raise urwid.ExitMainLoop()
- def run(self):
- os.system('clear')
- self.main_loop = urwid.MainLoop(self.frame, palette=palette, unhandled_input=self.unhandled_input)
- self.mode.main_loop = self.main_loop
- self.mode.start()
- self.mode.check_playback_end()
- self.main_loop.set_alarm_in(0.1, self.mode.update_clock)
- self.main_loop.set_alarm_in(0.1, self.mode.update_progress_bar)
- try:
- self.main_loop.run()
- finally:
- os.system('stty sane')
- os.system('clear')
- if __name__ == "__main__":
- input_path = None
- if len(sys.argv) > 1:
- input_path = sys.argv[1]
- fm = FileManager(input_path)
- fm.run()
|