Browse Source

08/22/2025

master
melancholytron 4 months ago
parent
commit
67fd5788ec
  1. 247
      terminalTv.py

247
terminalTv.py

@ -96,6 +96,154 @@ class FocusAwareListBox(urwid.ListBox):
self.on_focus_change(current_focus)
self._last_focus = current_focus
class SettingsMenu:
"""Settings configuration menu"""
def __init__(self, config, on_save_callback, on_cancel_callback):
self.config = config.copy()
self.on_save_callback = on_save_callback
self.on_cancel_callback = on_cancel_callback
self.setup_ui()
def setup_ui(self):
# Create styled form fields with consistent colors
self.m3u_edit = urwid.Edit(("title", "M3U URL: "), self.config.get('m3u_url', ''))
self.xmltv_edit = urwid.Edit(("title", "XMLTV URL: "), self.config.get('xmltv_url', ''))
self.user_agent_edit = urwid.Edit(("title", "User Agent: "), self.config.get('user_agent', ''))
self.update_interval_edit = urwid.IntEdit(("title", "Update Interval (seconds): "), self.config.get('update_interval', 900))
# Monitor selection - simplified as a numeric selection
monitor_value = self.config.get('monitor', None)
# Create a radio button group for monitor selection
self.monitor_group = []
rb_none = urwid.RadioButton(self.monitor_group, "Default (No specific monitor)", state=(monitor_value is None))
rb_1 = urwid.RadioButton(self.monitor_group, "Monitor 1", state=(monitor_value == 1))
rb_2 = urwid.RadioButton(self.monitor_group, "Monitor 2", state=(monitor_value == 2))
rb_3 = urwid.RadioButton(self.monitor_group, "Monitor 3", state=(monitor_value == 3))
# Create styled form sections
form_items = [
# Header section
urwid.AttrMap(urwid.Text("Settings Configuration", align='center'), 'header'),
urwid.AttrMap(urwid.Divider(""), 'divider'),
urwid.Divider(),
# Connection settings section
urwid.AttrMap(urwid.Text("CONNECTION SETTINGS", align='left'), 'program_now'),
urwid.Divider(),
urwid.AttrMap(self.m3u_edit, None, 'channel_focus'),
urwid.Divider(),
urwid.AttrMap(self.xmltv_edit, None, 'channel_focus'),
urwid.Divider(),
urwid.AttrMap(self.user_agent_edit, None, 'channel_focus'),
urwid.Divider(),
# Application settings section
urwid.AttrMap(urwid.Text("APPLICATION SETTINGS", align='left'), 'program_now'),
urwid.Divider(),
urwid.AttrMap(self.update_interval_edit, None, 'channel_focus'),
urwid.Divider(),
# Display settings section
urwid.AttrMap(urwid.Text("DISPLAY SETTINGS", align='left'), 'program_now'),
urwid.Divider(),
urwid.AttrMap(urwid.Text("Fullscreen Monitor Selection:", align='left'), 'program_desc'),
urwid.AttrMap(rb_none, None, 'channel_focus'),
urwid.AttrMap(rb_1, None, 'channel_focus'),
urwid.AttrMap(rb_2, None, 'channel_focus'),
urwid.AttrMap(rb_3, None, 'channel_focus'),
urwid.Divider(),
# Action buttons
urwid.AttrMap(urwid.Divider(""), 'divider'),
urwid.Divider(),
urwid.Columns([
('weight', 1, urwid.AttrMap(urwid.Button("Save Settings", on_press=self.save_settings), 'program_now', 'program_desc')),
('weight', 1, urwid.AttrMap(urwid.Button("Cancel", on_press=self.cancel_settings), 'error', 'program_desc'))
], dividechars=2)
]
listwalker = urwid.SimpleFocusListWalker(form_items)
self.listbox = urwid.ListBox(listwalker)
# Create the main widget with styled frame
self.widget = urwid.Frame(
urwid.AttrMap(urwid.LineBox(self.listbox, title=" Settings ", title_align='center'), 'channel'),
footer=urwid.AttrMap(
urwid.Text("Tab/Shift+Tab: Navigate | Enter: Select | Esc: Cancel", align='center'),
'footer'
)
)
def save_settings(self, button=None):
"""Validate and save settings"""
try:
# Validate and collect settings
new_config = {
'm3u_url': self.m3u_edit.edit_text.strip(),
'xmltv_url': self.xmltv_edit.edit_text.strip(),
'user_agent': self.user_agent_edit.edit_text.strip(),
'update_interval': self.update_interval_edit.value()
}
# Handle monitor setting from radio buttons
# Find which radio button is selected
monitor_value = None
for i, rb in enumerate(self.monitor_group):
if rb.state:
if i == 0: # "Default" option
monitor_value = None
else:
monitor_value = i # Monitor number (1, 2, 3)
break
new_config['monitor'] = monitor_value
# Set default MPV options without exposing them in the UI
standard_mpv_options = [
"--really-quiet",
"--no-terminal",
"--force-window=immediate"
]
# Add fullscreen options if monitor is selected
if monitor_value is not None:
fs_options = [
"--fs",
f"--fs-screen={monitor_value}"
]
new_config['mpv_options'] = fs_options + standard_mpv_options
else:
new_config['mpv_options'] = standard_mpv_options
# Validate required fields
if not new_config['m3u_url']:
self.show_error("M3U URL is required")
return
if not new_config['xmltv_url']:
self.show_error("XMLTV URL is required")
return
if not new_config['user_agent']:
self.show_error("User Agent is required")
return
if new_config['update_interval'] <= 0:
self.show_error("Update interval must be positive")
return
self.on_save_callback(new_config)
except Exception as e:
self.show_error(f"Error saving settings: {str(e)}")
def cancel_settings(self, button=None):
"""Cancel settings changes"""
self.on_cancel_callback()
def show_error(self, message):
"""Show error message in footer"""
error_text = urwid.AttrMap(urwid.Text(f"Error: {message}", align='center'), 'error')
self.widget.footer = error_text
class IPTVPlayer:
def __init__(self):
self.channels = []
@ -106,6 +254,8 @@ class IPTVPlayer:
self.last_update = datetime.now()
self.update_thread = None
self.update_running = True
self.settings_menu = None
self.main_widget = None
self.load_data()
self.setup_ui()
@ -263,7 +413,7 @@ class IPTVPlayer:
# Add update time to footer
monitor_info = f" | Monitor: {MONITOR}" if MONITOR is not None else ""
self.footer_text = urwid.Text(f"Q: Quit | ↑↓: Navigate | Enter: Play Channel | L: Reload | Last update: Loading...{monitor_info}", align='center')
self.footer_text = urwid.Text(f"Q: Quit | ↑↓: Navigate | Enter: Play | L: Reload | S: Settings | Last update: Loading...{monitor_info}", align='center')
self.footer = urwid.AttrMap(self.footer_text, 'footer')
program_frame = urwid.Frame(
@ -288,9 +438,12 @@ class IPTVPlayer:
columns
])
# Store the main layout
self.main_widget = urwid.LineBox(layout, title="")
# Overlay for centering
self.top = urwid.Overlay(
urwid.LineBox(layout, title=""),
self.main_widget,
urwid.SolidFill(' '),
align='center',
width=('relative', 85),
@ -305,7 +458,87 @@ class IPTVPlayer:
"""Update footer with last update time"""
time_str = self.last_update.strftime("%H:%M:%S")
monitor_info = f" | Monitor: {MONITOR}" if MONITOR is not None else ""
self.footer_text.set_text(f"Q: Quit | ↑↓: Navigate | Enter: Play Channel | L: Reload | Last update: {time_str}{monitor_info}")
self.footer_text.set_text(f"Q: Quit | ↑↓: Navigate | Enter: Play | L: Reload | S: Settings | Last update: {time_str}{monitor_info}")
def show_settings_menu(self):
"""Show settings configuration menu"""
# Get current config
current_config = {
'm3u_url': M3U_URL,
'xmltv_url': XMLTV_URL,
'user_agent': USER_AGENT,
'update_interval': UPDATE_INTERVAL,
'monitor': MONITOR,
'mpv_options': MPV_BASE[1:] if MPV_BASE[0] == 'mpv' else MPV_BASE # Remove 'mpv' command
}
# Create settings menu
self.settings_menu = SettingsMenu(
current_config,
self.on_settings_save,
self.on_settings_cancel
)
# Create settings overlay properly
self.top = urwid.Overlay(
self.settings_menu.widget,
self.top,
align='center',
width=('relative', 80),
valign='middle',
height=('relative', 90)
)
# Update the MainLoop's widget
if self.loop:
self.loop.widget = self.top
def on_settings_save(self, new_config):
"""Handle settings save"""
try:
# Save to config.json
with open('config.json', 'w') as config_file:
json.dump(new_config, config_file, indent=4)
# Update global variables
global M3U_URL, XMLTV_URL, USER_AGENT, UPDATE_INTERVAL, MONITOR, MPV_BASE
M3U_URL = new_config['m3u_url']
XMLTV_URL = new_config['xmltv_url']
USER_AGENT = new_config['user_agent']
UPDATE_INTERVAL = new_config['update_interval']
MONITOR = new_config['monitor']
# Update MPV base command directly from new_config
MPV_BASE = ["mpv"]
MPV_BASE.extend(new_config['mpv_options'])
# Close settings menu
self.on_settings_cancel()
# Reload data with new settings
self.reload_data()
except Exception as e:
# Show error in settings menu
if self.settings_menu:
self.settings_menu.show_error(f"Failed to save config: {str(e)}")
def on_settings_cancel(self):
"""Close settings menu"""
self.settings_menu = None
# Restore the original top widget structure
self.top = urwid.Overlay(
self.main_widget,
urwid.SolidFill(' '),
align='center',
width=('relative', 85),
valign='middle',
height=('relative', 85)
)
# Update the MainLoop's widget
if self.loop:
self.loop.widget = self.top
def start_update_thread(self):
"""Start background thread for periodic updates"""
@ -557,10 +790,18 @@ class IPTVPlayer:
self.update_thread.join(timeout=1.0)
def handle_input(self, key):
# Only handle main UI input if settings menu is not open
if self.settings_menu:
if key == 'esc':
self.on_settings_cancel()
return
if key in ('q', 'Q'):
self.quit_player()
elif key in ('l', 'L'):
self.reload_data()
elif key in ('s', 'S'):
self.show_settings_menu()
elif key == 'enter':
if self.current_channel and self.current_channel.get('url'):
self.play_channel(self.current_channel['url'])

Loading…
Cancel
Save