123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077 |
- import urwid, time
- import os
- import pygame.mixer
- import mutagen
- import sys
- from datetime import datetime, timedelta
- import subprocess
- import signal
- 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', 'light green,bold', 'default'),
- ('perm_denied', 'light red', 'default'),
- ('error', 'light red', 'default'),
- ('playing', 'light green', 'default'),
- ('pink_frame', 'light magenta', 'default'),
- ('percent', 'white,bold', 'default'),
- ('time_separator', 'dark gray', 'default'),
- ('time_separator,bold', 'dark gray,bold', 'default'),
- ]
- font = {
- '0': ["┌─┐", "│ │", "└─┘"],
- '1': [" ┌┐", " │", " ┘"],
- '2': ["┌─┐", "┌─┘", "└─┘"],
- '3': ["┌─┐", " ─┤", "└─┘"],
- '4': ["┌ ┐", "└─┤", " ┘"],
- '5': ["┌─┐", "└─┐", "└─┘"],
- '6': ["┌─┐", "├─┐", "└─┘"],
- '7': ["┌─┐", " ┤", " ┘"],
- '8': ["┌─┐", "├─┤", "└─┘"],
- '9': ["┌─┐", "└─┤", "└─┘"],
- ':': [" ┌┐ ", " ├┤ ", " └┘ "]
- }
- empty_char = [" ", " ", " "]
- def get_pseudographic_char(c):
- return font.get(c, empty_char)
- def print_pseudographic_time(hours, mins, secs):
- if not (0 <= hours <= 23 and 0 <= mins <= 59 and 0 <= secs <= 59):
- return [('error', f"Invalid time: {hours:02d}:{mins:02d}:{secs:02d}")]
- time_str = f"{hours:02d}:{mins:02d}:{secs:02d}"
- chars = [get_pseudographic_char(c) for c in time_str]
- result = []
- for row in range(3):
- line = []
- for i, char in enumerate(chars):
- style = 'time_separator' if i in [2, 5] else 'normal'
- line.append((style, char[row].rstrip().ljust(4)))
- result.append(line)
- return result
- def get_month_name(month):
- months = ["January", "February", "March", "April", "May", "June",
- "July", "August", "September", "October", "November", "December"]
- return months[month - 1] if 1 <= month <= 12 else "Unknown"
- def get_weekday_name(weekday):
- days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
- return days[weekday] if 0 <= weekday <= 6 else "Unknown"
- def get_date_string():
- t = time.localtime()
- month_name = get_month_name(t.tm_mon)
- weekday_name = get_weekday_name(t.tm_wday)
- return " {}/{}|{}/{} ".format(
- t.tm_year,
- month_name,
- t.tm_mday,
- weekday_name
- )
- 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
- self.progress_bar = urwid.Text([('normal', " 0"), ('time_separator', '%'), (None, " | " + " " * 83)], align='left')
- term_size = os.get_terminal_size()
- term_width = term_size.columns
- total_width = term_width - 1
- left_width = int(total_width * 0.62)
- title = "PLAYBACK PROGRESS"
- title_with_symbols = f"┤ {title} ├"
- title_len = len(title_with_symbols)
- adjusted_width = left_width - 2
- side_len = max(0, (adjusted_width - title_len) // 2)
- top_line = f'┌{"─" * side_len}{title_with_symbols}{"─" * (adjusted_width - title_len - side_len)}┐'
- if len(top_line) > left_width:
- top_line = top_line[:left_width - 1] + '┐'
- elif len(top_line) < left_width:
- top_line = top_line[:-1] + '─' * (left_width - len(top_line)) + '┐'
- top_text = urwid.Filler(urwid.Text(('pink_frame', top_line), align='left'), valign='top')
- side_borders = urwid.LineBox(self.progress_bar, lline='│', rline='│',
- tline='', bline='',
- tlcorner='', trcorner='',
- blcorner='', brcorner='')
- footer_line = f'└{"─" * (left_width - 2)}┘'
- footer_text = urwid.Filler(urwid.Text(('pink_frame', footer_line), align='left'), valign='top')
- framed_widget = urwid.Pile([(1, top_text), ('weight', 1, side_borders), (1, footer_text)])
- self.file_frame = urwid.AttrMap(framed_widget, 'pink_frame')
- self.volume_bar = urwid.Text([('normal', " 50"), ('time_separator', '%'), (None, " | " + '░' * 25 + ' ' * 25)])
- term_size = os.get_terminal_size()
- term_width = term_size.columns
- 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
- title = "PYGAME.MIXER VOLUME LEVEL"
- title_with_symbols = f"┤ {title} ├"
- title_len = len(title_with_symbols)
- adjusted_width = metadata_width - 2
- side_len = max(0, (adjusted_width - title_len) // 2)
- top_line = f'┌{"─" * side_len}{title_with_symbols}{"─" * (adjusted_width - title_len - side_len)}┐'
- if len(top_line) > metadata_width:
- top_line = top_line[:metadata_width - 1] + '┐'
- elif len(top_line) < metadata_width:
- top_line = top_line[:-1] + '─' * (metadata_width - len(top_line)) + '┐'
- top_text = urwid.Filler(urwid.Text(('pink_frame', top_line), align='left'), valign='top')
- side_borders = urwid.LineBox(self.volume_bar, lline='│', rline='│', tline='', bline='─', tlcorner='', trcorner='', blcorner='└', brcorner='┘')
- new_right_frame = urwid.Pile([(1, top_text), ('weight', 1, side_borders)])
- self.metadata_frame = urwid.AttrMap(new_right_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')
- term_width = os.get_terminal_size().columns
- title = "Path"
- title_len = len("┤ Path ├")
- adjusted_width = term_width - 4
- side_len = max(0, (adjusted_width - title_len) // 2)
- top_line = f'┌{"─" * side_len}┤ PATH ├{"─" * side_len}'
- if len(top_line) < term_width - 2:
- top_line += "─" * (term_width - len(top_line) - 3) + "┐"
- elif len(top_line) >= term_width - 1:
- top_line = top_line[:term_width - 3] + "┐"
- else:
- top_line += "┐"
- top_text = urwid.Filler(urwid.Text(('pink_frame', top_line), align='left'), valign='top')
- side_borders = urwid.LineBox(self.path_filler, lline='│', rline='│', tline='', bline='', tlcorner='', trcorner='', blcorner='', brcorner='')
- footer_line = f'└{"─" * (len(top_line) - 2)}┘'
- if len(footer_line) > term_width - 1:
- footer_line = footer_line[:term_width - 2]
- footer_text = urwid.Filler(urwid.Text(('pink_frame', footer_line), align='left'), valign='top')
- framed_widget = urwid.Pile([(1, top_text), ('weight', 1, side_borders), (1, footer_text)])
- self.path_widget = urwid.AttrMap(framed_widget, 'pink_frame')
- 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(50, int(percent / 2))
- initial_volume_text = [('normal', f" {percent}"), ('time_separator', '%'), (None, f" | {'░' * filled + ' ' * (50 - filled)}")]
- except subprocess.CalledProcessError:
- initial_volume_text = " --% " + " " * 50
- 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(50, int(percent / 2))
- initial_headphone_left_text = [('normal', f" {percent}"), ('time_separator', '%'), (None, f" | {'░' * filled + ' ' * (50 - filled)}")]
- except (subprocess.CalledProcessError, ValueError):
- initial_headphone_left_text = " --% | " + " " * 50
- 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(50, int(percent / 2))
- initial_headphone_right_text = [('normal', f" {percent}"), ('time_separator', '%'), (None, f" | {'░' * filled + ' ' * (50 - filled)}")]
- except (subprocess.CalledProcessError, ValueError):
- initial_headphone_right_text = " --% | " + " " * 50
- self.headphone_right_bar = urwid.Text(initial_headphone_right_text, align='left')
- 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 format_time(self, seconds):
- return str(timedelta(seconds=int(seconds))).zfill(8)
- def format_time(self, seconds):
- return str(timedelta(seconds=int(seconds))).zfill(8)
- def format_active_time(self, elapsed_str, duration_str):
- result = [('normal', " ")]
- for i, char in enumerate(elapsed_str):
- if char.isdigit():
- result.append(('normal', char))
- else:
- result.append(('time_separator,bold', char))
- result.append(('time_separator,bold', " / "))
- for i, char in enumerate(duration_str):
- if char.isdigit():
- result.append(('normal', char))
- else:
- result.append(('time_separator,bold', char))
- return result
- 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 = [('normal', f"{progress_percent:3d}"), ('time_separator', '%'), (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(self.format_active_time(elapsed_str, duration_str))
- else:
- self.progress_bar.set_text([('normal', " 0"), ('time_separator', '%'), (None, " | " + " " * 83)])
- self.grannik_text.set_text([('pink_frame', " 00:00:00 / 00:00:00")])
- if self.main_loop:
- self.main_loop.set_alarm_in(1, self.update_progress_bar)
- def update_clock(self, loop=None, data=None):
- current_time = time.localtime()
- self.clock_text.set_text(print_pseudographic_time(current_time.tm_hour, current_time.tm_min, current_time.tm_sec))
- if self.main_loop:
- self.main_loop.set_alarm_in(1, self.update_clock)
- 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)
- if term_height < 10:
- return urwid.Filler(self, height=term_height, valign='top')
- elif term_height < 15:
- combined_widget = urwid.Columns([
- (left_width, self.file_frame),
- ], dividechars=1)
- return urwid.Filler(combined_widget, height=term_height, valign='top')
- else:
- 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=max(1, min(3, term_height - 10)), valign='top')
- term_size = os.get_terminal_size()
- term_width = term_size.columns
- 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
- title = "AMIXER MASTER VOLUME LEVEL"
- title_with_symbols = f"┤ {title} ├"
- title_len = len(title_with_symbols)
- adjusted_width = metadata_width - 2
- side_len = max(0, (adjusted_width - title_len) // 2)
- top_line = f'┌{"─" * side_len}{title_with_symbols}{"─" * (adjusted_width - title_len - side_len)}┐'
- if len(top_line) > metadata_width:
- top_line = top_line[:metadata_width - 1] + '┐'
- elif len(top_line) < metadata_width:
- top_line = top_line[:-1] + '─' * (metadata_width - len(top_line)) + '┐'
- top_text = urwid.Filler(urwid.Text(('pink_frame', top_line), align='left'), valign='top')
- side_borders = urwid.LineBox(self.system_volume_bar, lline='│', rline='│', tline='', bline='─', tlcorner='', trcorner='', blcorner='└', brcorner='┘')
- new_right_frame = urwid.Pile([(1, top_text), ('weight', 1, side_borders)])
- box02_clone = urwid.AttrMap(new_right_frame, 'pink_frame')
- term_size = os.get_terminal_size()
- term_width = term_size.columns
- 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
- title = "AMIXER HEADPHONE LEFT VOLUME LEVEL"
- title_with_symbols = f"┤ {title} ├"
- title_len = len(title_with_symbols)
- adjusted_width = metadata_width - 2
- side_len = max(0, (adjusted_width - title_len) // 2)
- top_line = f'┌{"─" * side_len}{title_with_symbols}{"─" * (adjusted_width - title_len - side_len)}┐'
- if len(top_line) > metadata_width:
- top_line = top_line[:metadata_width - 1] + '┐'
- elif len(top_line) < metadata_width:
- top_line = top_line[:-1] + '─' * (metadata_width - len(top_line)) + '┐'
- top_text = urwid.Filler(urwid.Text(('pink_frame', top_line), align='left'), valign='top')
- side_borders = urwid.LineBox(self.headphone_left_bar, lline='│', rline='│', tline='', bline='─', tlcorner='', trcorner='', blcorner='└', brcorner='┘')
- new_right_frame = urwid.Pile([(1, top_text), ('weight', 1, side_borders)])
- box02_clone2 = urwid.AttrMap(new_right_frame, 'pink_frame')
- term_size = os.get_terminal_size()
- term_width = term_size.columns
- 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
- title = "AMIXER HEADPHONE RIGHT VOLUME LEVEL"
- title_with_symbols = f"┤ {title} ├"
- title_len = len(title_with_symbols)
- adjusted_width = metadata_width - 2
- side_len = max(0, (adjusted_width - title_len) // 2)
- top_line = f'┌{"─" * side_len}{title_with_symbols}{"─" * (adjusted_width - title_len - side_len)}┐'
- if len(top_line) > metadata_width:
- top_line = top_line[:metadata_width - 1] + '┐'
- elif len(top_line) < metadata_width:
- top_line = top_line[:-1] + '─' * (metadata_width - len(top_line)) + '┐'
- top_text = urwid.Filler(urwid.Text(('pink_frame', top_line), align='left'), valign='top')
- side_borders = urwid.LineBox(self.headphone_right_bar, lline='│', rline='│', tline='', bline='─', tlcorner='', trcorner='', blcorner='└', brcorner='┘')
- new_right_frame = urwid.Pile([(1, top_text), ('weight', 1, side_borders)])
- box02_clone3 = urwid.AttrMap(new_right_frame, 'pink_frame')
- header_height = max(1, min(3, term_height - 10))
- frame_border_height = max(1, min(2, term_height - 10))
- available_height = term_height - header_height - frame_border_height
- if available_height < 10:
- raise ValueError("Not enough height for main content: need at least 10 lines, got %d" % available_height)
- upper_boxes_height = 3
- min_footer_height = 8
- columns_height = max(21, available_height - upper_boxes_height - min_footer_height - 1)
- title = "FILES AND DIRECTORIES OF THE LINUX OS"
- title_with_symbols = f"┤ {title} ├"
- title_len = len(title_with_symbols)
- adjusted_width = left_width - 2
- side_len = max(0, (adjusted_width - title_len) // 2)
- top_line = f'┌{"─" * side_len}{title_with_symbols}{"─" * (adjusted_width - title_len - side_len)}┐'
- if len(top_line) > left_width:
- top_line = top_line[:left_width - 1] + '┐'
- elif len(top_line) < left_width:
- top_line = top_line[:-1] + '─' * (left_width - len(top_line)) + '┐'
- top_text = urwid.Filler(urwid.Text(('pink_frame', top_line), align='left'), valign='top')
- side_borders = urwid.LineBox(self, lline='│', rline='│', tline='', bline='─', tlcorner='', trcorner='', blcorner='└', brcorner='┘')
- new_left_frame = urwid.Pile([(1, top_text), ('weight', 1, side_borders)])
- 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')
- self.metadata_output = urwid.Text("", align='left')
- self.metadata_filler = urwid.Filler(self.metadata_output, valign='top')
- title = "INFO"
- title_with_symbols = f"┤ {title} ├"
- title_len = len(title_with_symbols)
- adjusted_width = metadata_width - 2
- side_len = max(0, (adjusted_width - title_len) // 2)
- top_line = f'┌{"─" * side_len}┤ {title} ├{"─" * (adjusted_width - title_len - side_len)}┐'
- if len(top_line) > metadata_width:
- top_line = top_line[:metadata_width - 1] + '┐'
- elif len(top_line) < metadata_width:
- top_line = top_line[:-1] + '─' * (metadata_width - len(top_line)) + '┐'
- top_text = urwid.Filler(urwid.Text(('pink_frame', top_line), align='left'), valign='top')
- side_borders = urwid.LineBox(self.metadata_filler, lline='│', rline='│', tline='', bline='─', tlcorner='', trcorner='', blcorner='└', brcorner='┘')
- new_right_frame = urwid.Pile([(1, top_text), ('weight', 1, side_borders)])
- 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)
- 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(4, footer_height)
- divider_width = 2
- available_footer_width = footer_width - divider_width
- box1_width = 23
- box2_width = 33
- box4_width = available_footer_width - box1_width - box2_width - divider_width + 2
- self.grannik_text = urwid.Text(" 00:00:00 / 00:00:00", align='left')
- title = "PLAYBACK TIME"
- title_with_symbols = f"┤ {title} ├"
- title_len = len(title_with_symbols)
- adjusted_width = box1_width - 2
- side_len = max(0, (adjusted_width - title_len) // 2)
- top_line = f'┌{"─" * side_len}{title_with_symbols}{"─" * (adjusted_width - title_len - side_len)}┐'
- top_text = urwid.Filler(urwid.Text(('pink_frame', top_line), align='left'), valign='top')
- side_borders = urwid.LineBox(self.grannik_text, lline='│', rline='│',
- tline='', bline='',
- tlcorner='', trcorner='',
- blcorner='', brcorner='')
- footer_line = f'└{"─" * (len(top_line) - 2)}┘'
- footer_text = urwid.Filler(urwid.Text(('pink_frame', footer_line), align='left'), valign='top')
- framed_widget = urwid.Pile([(1, top_text), ('weight', 1, side_borders), (1, footer_text)])
- box1 = urwid.AttrMap(framed_widget, 'pink_frame')
- box1_filler = urwid.Filler(box1, height=box_height, valign='middle')
- title = "CURRENT DATE"
- title_with_symbols = f"┤ {title} ├"
- title_len = len(title_with_symbols)
- adjusted_width = box2_width - 2
- side_len = max(0, (adjusted_width - title_len) // 2)
- top_line = f'┌{"─" * side_len}{title_with_symbols}{"─" * (adjusted_width - title_len - side_len)}┐'
- top_text = urwid.Filler(urwid.Text(('pink_frame', top_line), align='left'), valign='top')
- side_borders = urwid.LineBox(urwid.Text([
- ('normal', get_date_string().split('|')[0].split('/')[0]),
- ('path_value', '/'),
- ('normal', get_date_string().split('|')[0].split('/')[1]),
- ('path_value', '|'),
- ('normal', get_date_string().split('|')[1].split('/')[0]),
- ('path_value', '/'),
- ('normal', get_date_string().split('|')[1].split('/')[1]),
- ], align='center'), lline='│', rline='│', tline='', bline='', tlcorner='', trcorner='', blcorner='', brcorner='')
- footer_line = f'└{"─" * (len(top_line) - 2)}┘'
- footer_text = urwid.Filler(urwid.Text(('pink_frame', footer_line), align='left'), valign='top')
- framed_widget = urwid.Pile([(1, top_text), ('weight', 1, side_borders), (1, footer_text)])
- box2 = urwid.AttrMap(framed_widget, 'pink_frame')
- box2_filler = urwid.Filler(box2, height=3, valign='middle')
- current_time = time.localtime()
- test_text = urwid.Text(print_pseudographic_time(current_time.tm_hour, current_time.tm_min, current_time.tm_sec), align='center')
- self.clock_text = test_text
- title = "CURRENT TIME"
- title_with_symbols = f"┤ {title} ├"
- title_len = len(title_with_symbols)
- adjusted_width = box2_width - 2
- side_len = max(0, (adjusted_width - title_len) // 2)
- top_line = f'┌{"─" * side_len}{title_with_symbols}{"─" * (adjusted_width - title_len - side_len)}┐'
- top_text = urwid.Filler(urwid.Text(('pink_frame', top_line), align='left'), valign='top')
- side_borders = urwid.LineBox(test_text, lline='│', rline='│',
- tline='', bline='',
- tlcorner='', trcorner='',
- blcorner='', brcorner='')
- footer_line = f'└{"─" * (len(top_line) - 2)}┘'
- footer_text = urwid.Filler(urwid.Text(('pink_frame', footer_line), align='left'), valign='top')
- framed_widget = urwid.Pile([(1, top_text), ('weight', 1, side_borders), (1, footer_text)])
- test_box_attr = urwid.AttrMap(framed_widget, 'pink_frame')
- test_box_filler = urwid.Filler(test_box_attr, height=6, valign='top')
- box2_with_test = urwid.Filler(
- urwid.Pile([
- (3, box2_filler),
- (6, test_box_filler)
- ]),
- height=box_height, valign='middle'
- )
- title = "STATUS"
- title_with_symbols = f"┤ {title} ├"
- title_len = len(title_with_symbols)
- adjusted_width = box4_width - 2
- side_len = max(0, (adjusted_width - title_len) // 2)
- top_line = f'┌{"─" * side_len}{title_with_symbols}{"─" * (adjusted_width - title_len - side_len)}┐'
- top_text = urwid.Filler(urwid.Text(('pink_frame', top_line), align='left'), valign='top')
- side_borders = urwid.LineBox(self.status_filler, lline='│', rline='│',
- tline='', bline='',
- tlcorner='', trcorner='',
- blcorner='', brcorner='')
- footer_line = f'└{"─" * (len(top_line) - 2)}┘'
- footer_text = urwid.Filler(urwid.Text(('pink_frame', footer_line), align='left'), valign='top')
- framed_widget = urwid.Pile([(1, top_text), ('weight', 1, side_borders), (1, footer_text)])
- box4 = urwid.AttrMap(framed_widget, 'pink_frame')
- box4_filler = urwid.Filler(box4, height=box_height, valign='middle')
- footer_columns = urwid.Filler(
- urwid.Columns([
- (box1_width, box1_filler),
- (box2_width, box2_with_test),
- (box4_width, box4_filler),
- ], dividechars=1, box_columns=[0, 1, 2]),
- height=box_height, valign='middle'
- )
- 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) if term_height >= 15 else (0, urwid.Filler(urwid.Text(""))),
- ])
- 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([('time_separator,bold', " Playlist ended")])
- self.metadata_output.set_text([('path_value', ' No metadata available')])
- 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([('path_value', ' No status available')])
- self.metadata_output.set_text([('path_value', ' No metadata available')])
- 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([('path_value', ' Duration: '), ('normal', f'{audio.info.length:.2f} sec')])
- metadata.append([('path_value', ' Bitrate: '), ('normal', f'{audio.info.bitrate // 1000} kbps')])
- metadata.append([('path_value', ' Channels: '), ('normal', f'{audio.info.channels}')])
- metadata.append([('path_value', ' Sample Rate: '), ('normal', f'{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([('path_value', f' {key}: '), ('normal', value_str)])
- max_lines = 10
- if len(metadata) > max_lines:
- metadata = metadata[:max_lines - 1] + [[('path_value', ' ... (truncated)')]]
- result = []
- for i, line in enumerate(metadata):
- result.extend(line)
- if i < len(metadata) - 1:
- result.append(('normal', '\n'))
- return result if result else [('path_value', ' No metadata available')]
- except Exception as e:
- return [('path_value', ' Error reading metadata: '), ('normal', str(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([('time_separator,bold', " Playing:\n "), ('normal', f"{os.path.basename(filepath)}")])
- self.metadata_output.set_text(self.get_metadata(filepath))
- filled = min(50, int(self.volume * 50))
- self.volume_bar.set_text([('normal', f" {int(self.volume * 100)}"), ('time_separator', '%'), (None, f" | {'░' * filled + ' ' * (50 - 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]
- 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', ' - Increase right headphone volume\n'),
- ('normal,bold', ' b'), ('path_value', ' - Decrease right headphone volume\n'),
- ('normal,bold', ' c'), ('path_value', ' - Increase left headphone volume\n'),
- ('normal,bold', ' d'), ('path_value', ' - Decrease system volume.\n'),
- ('normal,bold', ' e'), ('path_value', ' - Increase both headphones volume\n'),
- ('normal,bold', ' f'), ('path_value', ' - Decrease both headphones volume\n'),
- ('normal,bold', ' g'), ('path_value', ' - Decrease left headphone volume\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([('path_value', ' No metadata available')])
- self.main_loop.draw_screen()
- 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([('path_value', ' No metadata available')])
- 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([('time_separator,bold', " Resumed: "), ('normal', f"{os.path.basename(filepath)}")])
- else:
- pygame.mixer.music.pause()
- self.paused = True
- self.status_output.set_text([('time_separator,bold', " Paused")])
- elif key == 's':
- if self.playing:
- pygame.mixer.music.stop()
- self.playing = False
- self.paused = False
- self.status_output.set_text([('time_separator,bold', " Stopped")])
- self.metadata_output.set_text([('path_value', ' No metadata available')])
- 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([('time_separator,bold', " Replaying: "), ('normal', f"{os.path.basename(filepath)}")])
- self.metadata_output.set_text(self.get_metadata(filepath))
- elif key == '+':
- self.volume = min(1.0, self.volume + 0.02)
- if self.playing:
- pygame.mixer.music.set_volume(self.volume)
- filled = int(self.volume * 50)
- self.volume_bar.set_text([('normal', f" {int(self.volume * 100)}"), ('time_separator', '%'), (None, f" | {'░' * filled + ' ' * (50 - filled)}")])
- self.main_loop.draw_screen()
- elif key == '-':
- self.volume = max(0.0, self.volume - 0.02)
- if self.playing:
- pygame.mixer.music.set_volume(self.volume)
- filled = int(self.volume * 44)
- self.volume_bar.set_text([('normal', f" {int(self.volume * 100)}"), ('time_separator', '%'), (None, f" | {'░' * filled + ' ' * (44 - filled)}")])
- self.main_loop.draw_screen()
- elif key == 'i':
- try:
- result = subprocess.check_output("amixer set Master 2%+ | grep -o '[0-9]\+%' | head -1", shell=True, text=True).strip()
- percent = int(result.rstrip('%'))
- filled = min(50, int(percent / 2))
- self.system_volume_bar.set_text([('normal', f" {percent}"), ('time_separator', '%'), (None, f" | {'░' * filled + ' ' * (50 - 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 2%- | grep -o '[0-9]\+%' | head -1", shell=True, text=True).strip()
- percent = int(result.rstrip('%'))
- filled = min(50, int(percent / 2))
- self.system_volume_bar.set_text([('normal', f" {percent}"), ('time_separator', '%'), (None, f" | {'░' * filled + ' ' * (50 - 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 2%+ -q && amixer sget 'Headphone' | grep 'Front Right' | grep -o '[0-9]\+%' | head -1", shell=True, text=True).strip()
- percent = int(result.rstrip('%'))
- filled = min(50, int(percent / 2))
- self.headphone_right_bar.set_text([('normal', f" {percent}"), ('time_separator', '%'), (None, f" | {'░' * filled + ' ' * (50 - filled)}")])
- self.main_loop.draw_screen()
- except subprocess.CalledProcessError as e:
- pass
- elif key == 'b':
- try:
- result = subprocess.check_output("amixer sset 'Headphone' frontright 2%- -q && amixer sget 'Headphone' | grep 'Front Right' | grep -o '[0-9]\+%' | head -1", shell=True, text=True).strip()
- percent = int(result.rstrip('%'))
- filled = min(50, int(percent / 2))
- self.headphone_right_bar.set_text([('normal', f" {percent}"), ('time_separator', '%'), (None, f" | {'░' * filled + ' ' * (50 - filled)}")])
- 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 2%+ -q && amixer sget 'Headphone' | grep 'Front Left' | grep -o '[0-9]\+%' | head -1", shell=True, text=True).strip()
- percent = int(result.rstrip('%'))
- filled = min(50, int(percent / 2))
- self.headphone_left_bar.set_text([('normal', f" {percent}"), ('time_separator', '%'), (None, f" | {'░' * filled + ' ' * (50 - filled)}")])
- 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 2%- -q && amixer sget 'Headphone' | grep 'Front Left' | grep -o '[0-9]\+%' | head -1", shell=True, text=True).strip()
- percent = int(result.rstrip('%'))
- filled = min(50, int(percent / 2))
- self.headphone_left_bar.set_text([('normal', f" {percent}"), ('time_separator', '%'), (None, f" | {'░' * filled + ' ' * (50 - filled)}")])
- 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' 2%+ -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(50, left_percent // 2)
- self.headphone_left_bar.set_text([('normal', f" {left_percent}"), ('time_separator', '%'), (None, f" | {'░' * left_filled + ' ' * (50 - 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(50, right_percent // 2)
- self.headphone_right_bar.set_text([('normal', f" {right_percent}"), ('time_separator', '%'), (None, f" | {'░' * right_filled + ' ' * (50 - right_filled)}")])
- 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' 2%- -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(50, left_percent // 2)
- self.headphone_left_bar.set_text([('normal', f" {left_percent}"), ('time_separator', '%'), (None, f" | {'░' * left_filled + ' ' * (50 - 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(50, right_percent // 2)
- self.headphone_right_bar.set_text([('normal', f" {right_percent}"), ('time_separator', '%'), (None, f" | {'░' * right_filled + ' ' * (50 - right_filled)}")])
- 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):
- title = "╡ AUDIO PLAYER TERM PY ╞"
- term_width = os.get_terminal_size().columns
- title_len = len(title)
- side_len = max(1, (term_width - title_len - 2) // 2)
- top_line = f'╔{"═" * side_len}{title}{"═" * (side_len + term_width % 2)}╗'
- top_text = urwid.Text(('header', top_line), align='center')
- side_borders = urwid.LineBox(widget, lline='║', rline='║', tline='', bline='', tlcorner='', trcorner='', blcorner='', brcorner='')
- side_borders = urwid.AttrMap(side_borders, 'header')
- framed_widget = urwid.Frame(
- body=side_borders,
- header=top_text,
- focus_part='body',
- footer=urwid.Text(('header', '╚' + '═' * (len(top_line) - 2) + '╝'))
- )
- 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')
- try:
- 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.mode.update_clock()
- self.main_loop.set_alarm_in(0.1, self.mode.update_progress_bar)
- self.main_loop.run()
- except Exception as e:
- with open("error_log.txt", "w") as f:
- f.write(f"Error in run: {str(e)}\n")
- import traceback
- traceback.print_exc(file=f)
- print(f"Error occurred! Check error_log.txt for details.")
- import time
- time.sleep(3)
- 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()
|