Files
audioPlayerTermPy/audioPlayerTermPy.py

1077 lines
56 KiB
Python

#!/usr/bin/env python3
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') #self.progress_bar = urwid.Text([('path_value', " 0"), ('percent', '%'), (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)]) #self.volume_bar = urwid.Text(f" 50% | {'░' * 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)}")] #initial_volume_text = f" {percent}% | {'░' * 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)}")] #initial_headphone_left_text = f" {percent}% | {'░' * 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)}")] #initial_headphone_right_text = f" {percent}% | {'░' * 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}")] #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(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) # 21 -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)}")]) #self.volume_bar.set_text(f" {int(self.volume * 100)}% | {'░' * 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.volume_bar.set_text(f" {int(self.volume * 100)}% | {'░' * 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.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 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.system_volume_bar.set_text(f" {percent}% | {'░' * 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.system_volume_bar.set_text(f" {percent}% | {'░' * 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.headphone_right_bar.set_text(f" {percent}% | {'░' * 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.headphone_right_bar.set_text(f" {percent}% | {'░' * 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.headphone_left_bar.set_text(f" {percent}% | {'░' * 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.headphone_left_bar.set_text(f" {percent}% | {'░' * 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)}")]) #self.headphone_left_bar.set_text(f" {left_percent}% | {'░' * 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.headphone_right_bar.set_text(f" {right_percent}% | {'░' * 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)}")]) #self.headphone_left_bar.set_text(f" {left_percent}% | {'░' * 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.headphone_right_bar.set_text(f" {right_percent}% | {'░' * 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) # Пауза 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()