Browse Source

10/02/2025

master
melancholytron 3 weeks ago
parent
commit
b63e1abd80
  1. 501
      terminalTv.py

501
terminalTv.py

@ -11,6 +11,7 @@ import urwid
from urllib.error import URLError
import threading
import time
import urllib.parse
# Load configuration from JSON file
try:
@ -21,6 +22,7 @@ try:
USER_AGENT = config.get('user_agent', "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36")
UPDATE_INTERVAL = config.get('update_interval', 900)
MONITOR = config.get('monitor', None)
DEBUG_MODE = config.get('debug_mode', False)
# Base MPV command with monitor options if specified
MPV_BASE = ["mpv"]
@ -38,6 +40,7 @@ except FileNotFoundError:
USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
UPDATE_INTERVAL = 900
MONITOR = None
DEBUG_MODE = False
MPV_BASE = [
"mpv",
"--really-quiet",
@ -45,8 +48,9 @@ except FileNotFoundError:
"--force-window=immediate"
]
# Color Palette remains the same
# Enhanced color palette with animation colors
PALETTE = [
# Original colors
('header', 'white', 'dark blue'),
('footer', 'white', 'dark blue'),
('channel', 'black', 'light cyan'),
@ -59,9 +63,29 @@ PALETTE = [
('time', 'yellow', ''),
('title', 'bold', ''),
('update', 'light blue', ''),
# Animation colors
('splash_1', 'light red', ''),
('splash_2', 'yellow', ''),
('splash_3', 'light green', ''),
('splash_4', 'light cyan', ''),
('splash_5', 'light blue', ''),
('splash_6', 'light magenta', ''),
('highlight', 'white,bold', 'dark blue'),
('loading', 'yellow', ''),
('pulse_1', 'dark cyan', ''),
('pulse_2', 'light cyan', ''),
('pulse_3', 'white', ''),
('flash', 'white,bold', 'dark green'),
('fade', 'dark gray', ''),
]
# ASCII Art
# Function for debug prints that only outputs when debug mode is enabled
def debug_print(*args, **kwargs):
if DEBUG_MODE:
print(*args, **kwargs)
# ASCII Art and Animation Frames
BANNER = r"""
__________._____. ___. .__ __ _______________ ____
\______ \__\_ |__\_ |__ |__|/ |_ \__ ___/\ \ / /
@ -71,6 +95,79 @@ __________._____. ___. .__ __ _______________ ____
\/ \/ \/
"""
# Animation frames for the splash screen
SPLASH_FRAMES = [
# Frame 1 - Just B
r"""
_________
|\\ |
| \\ |
| \\_____|
| |_____|
| | |
|___|_____|
""",
# Frame 2 - BI
r"""
_________ ___
|\\ | | |
| \\ | | |
| \\_____| | |
| |_____| | |
| | | |
|___|_____| |___|
""",
# Frame 3 - BIB
r"""
_________ ___ _________
|\\ | | | |\\ |
| \\ | | | | \\ |
| \\_____| | | | \\_____|
| |_____| | | | |_____|
| | | | | | |
|___|_____| |___| |___|_____|
""",
# Frame 4 - BIBB
r"""
_________ ___ _________ _________
|\\ | | | |\\ | |\\ |
| \\ | | | | \\ | | \\ |
| \\_____| | | | \\_____| | \\_____|
| |_____| | | | |_____| | |_____|
| | | | | | | | | |
|___|_____| |___| |___|_____| |___|_____|
""",
# Frame 5 - BIBBI
r"""
_________ ___ _________ _________ ___
|\\ | | | |\\ | |\\ | | |
| \\ | | | | \\ | | \\ | | |
| \\_____| | | | \\_____| | \\_____| | |
| |_____| | | | |_____| | |_____| | |
| | | | | | | | | | |
|___|_____| |___| |___|_____| |___|_____| |___|
""",
# Frame 6 - BIBBIT
r"""
_________ ___ _________ _________ ___ _______
|\\ | | | |\\ | |\\ | | | | |
| \\ | | | | \\ | | \\ | | | |_ _|
| \\_____| | | | \\_____| | \\_____| | | | |
| |_____| | | | |_____| | |_____| | | | |
| | | | | | | | | | | | |
|___|_____| |___| |___|_____| |___|_____| |___| |_______|
""",
]
# Loading animation characters
LOADING_CHARS = ['', '', '', '', '', '', '', '']
class ChannelButton(urwid.Button):
def __init__(self, channel, *args, **kwargs):
self.channel = channel
@ -121,6 +218,9 @@ class SettingsMenu:
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))
# Debug mode checkbox
self.debug_checkbox = urwid.CheckBox("Enable Debug Mode", state=self.config.get('debug_mode', False))
# Create styled form sections
form_items = [
# Header section
@ -154,6 +254,12 @@ class SettingsMenu:
urwid.AttrMap(rb_3, None, 'channel_focus'),
urwid.Divider(),
# Debug settings section
urwid.AttrMap(urwid.Text("DEBUG SETTINGS", align='left'), 'program_now'),
urwid.Divider(),
urwid.AttrMap(self.debug_checkbox, None, 'channel_focus'),
urwid.Divider(),
# Action buttons
urwid.AttrMap(urwid.Divider(""), 'divider'),
urwid.Divider(),
@ -183,7 +289,8 @@ class SettingsMenu:
'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()
'update_interval': self.update_interval_edit.value(),
'debug_mode': self.debug_checkbox.state
}
# Handle monitor setting from radio buttons
@ -256,18 +363,25 @@ class IPTVPlayer:
self.update_running = True
self.settings_menu = None
self.main_widget = None
self.animate_startup = False # Disable animations for better performance
self.splash_screen = None
self.load_data()
self.setup_ui()
def display_splash_screen(self):
"""Disabled splash screen for better performance"""
# Skip splash screen completely for better performance
pass
def load_data(self):
print("\n[DEBUG] Loading IPTV data...")
debug_print("\n[DEBUG] Loading IPTV data...")
# Load M3U playlist
try:
req = Request(M3U_URL, headers={'User-Agent': USER_AGENT})
with urlopen(req, timeout=10) as response:
m3u_data = response.read().decode('utf-8')
print(f"[DEBUG] Received M3U data (length: {len(m3u_data)} bytes)")
debug_print(f"[DEBUG] Received M3U data (length: {len(m3u_data)} bytes)")
# Parse M3U
lines = m3u_data.split('\n')
@ -293,7 +407,10 @@ class IPTVPlayer:
'current_show': None,
'next_show': None
}
elif line.startswith('http://'):
elif (line.startswith('http://') or line.startswith('https://') or
line.startswith('rtmp://') or line.startswith('udp://') or
line.startswith('rtp://') or line.startswith('mms://') or
line.startswith('rtsp://') or line.startswith('file://')):
if current_channel:
current_channel['url'] = line
self.channels.append(current_channel)
@ -303,7 +420,7 @@ class IPTVPlayer:
print("[WARNING] No channels found in M3U file!")
self.add_error_channel("No channels found in playlist")
else:
print(f"[DEBUG] Successfully loaded {len(self.channels)} channels")
debug_print(f"[DEBUG] Successfully loaded {len(self.channels)} channels")
except Exception as e:
print(f"[ERROR] Failed to load M3U: {str(e)}")
@ -315,7 +432,7 @@ class IPTVPlayer:
req = Request(XMLTV_URL, headers={'User-Agent': USER_AGENT})
with urlopen(req, timeout=10) as response:
xml_data = response.read().decode('utf-8')
print(f"[DEBUG] Received XMLTV data (length: {len(xml_data)} bytes)")
debug_print(f"[DEBUG] Received XMLTV data (length: {len(xml_data)} bytes)")
root = ET.fromstring(xml_data)
for programme in root.findall('programme'):
@ -325,20 +442,170 @@ class IPTVPlayer:
title_elem = programme.find('title')
desc_elem = programme.find('desc')
# Extract additional episode information
episode_num_elem = programme.find('episode-num')
episode_num = ""
season = None
episode = None
# Parse episode numbering systems
if episode_num_elem is not None and episode_num_elem.text:
episode_num = episode_num_elem.text
debug_print(f"[DEBUG] Found episode number: {episode_num}, system: {episode_num_elem.get('system')}")
# Extract season and episode from xmltv_ns format (common format: S.E.P/TOTAL where S=season, E=episode, P=part)
if episode_num_elem.get('system') == 'xmltv_ns':
# Format example: "2.9." means Season 3, Episode 10 (zero-based)
parts = episode_num.split('.')
debug_print(f"[DEBUG] Split episode_num into parts: {parts}")
if len(parts) >= 2:
try:
# XMLTV uses zero-based numbering, so add 1
if parts[0].strip():
season = int(parts[0]) + 1
debug_print(f"[DEBUG] Parsed season: {season}")
if parts[1].strip():
episode = int(parts[1]) + 1
debug_print(f"[DEBUG] Parsed episode: {episode}")
except ValueError as e:
debug_print(f"[DEBUG] Error parsing season/episode: {e}")
# Also try to find other common formats
elif episode_num.lower().startswith('s') and 'e' in episode_num.lower():
# Format like "S01E05"
try:
s_part = episode_num.lower().split('e')[0].strip('s')
e_part = episode_num.lower().split('e')[1].strip()
if s_part.isdigit():
season = int(s_part)
debug_print(f"[DEBUG] Parsed season from SxxExx format: {season}")
if e_part.isdigit():
episode = int(e_part)
debug_print(f"[DEBUG] Parsed episode from SxxExx format: {episode}")
except Exception as e:
debug_print(f"[DEBUG] Error parsing SxxExx format: {e}")
# Try digit format (like "105" for S01E05)
elif episode_num.isdigit() and len(episode_num) >= 3:
try:
if len(episode_num) == 3:
season = int(episode_num[0])
episode = int(episode_num[1:])
elif len(episode_num) == 4:
season = int(episode_num[:2])
episode = int(episode_num[2:])
debug_print(f"[DEBUG] Parsed from numeric format: S{season}E{episode}")
except Exception as e:
debug_print(f"[DEBUG] Error parsing numeric format: {e}")
# For debugging: also check direct episode and season tags
season_elem = programme.find('season-num')
if season_elem is not None and season_elem.text:
try:
season = int(season_elem.text)
debug_print(f"[DEBUG] Found direct season tag: {season}")
except ValueError:
pass
episode_elem = programme.find('episode-num')
if episode_elem is not None and episode_elem.text and episode_elem.get('system') == 'onscreen':
debug_print(f"[DEBUG] Found onscreen episode format: {episode_elem.text}")
# Try to extract numbers from onscreen format (like "Episode 5")
try:
num_match = re.search(r'(\d+)', episode_elem.text)
if num_match and episode is None:
episode = int(num_match.group(1))
debug_print(f"[DEBUG] Extracted episode number from onscreen format: {episode}")
except Exception as e:
debug_print(f"[DEBUG] Error parsing onscreen format: {e}")
# Also check if there's a direct 'episode' tag
direct_episode_elem = programme.find('episode')
if direct_episode_elem is not None and direct_episode_elem.text:
try:
episode = int(direct_episode_elem.text)
debug_print(f"[DEBUG] Found direct episode tag: {episode}")
except ValueError:
pass
# Detect if we have the season/episode directly in the title
title_text = title_elem.text if title_elem is not None else ""
if title_text and (season is None or episode is None):
# Look for patterns like "S01E05" in the title
se_match = re.search(r'S(\d+)E(\d+)', title_text, re.IGNORECASE)
if se_match:
season = int(se_match.group(1))
episode = int(se_match.group(2))
debug_print(f"[DEBUG] Extracted from title: S{season}E{episode} from '{title_text}'")
# Check format like "1x05"
elif 'x' in title_text.lower():
x_match = re.search(r'(\d+)x(\d+)', title_text, re.IGNORECASE)
if x_match:
season = int(x_match.group(1))
episode = int(x_match.group(2))
debug_print(f"[DEBUG] Extracted from title 'x' format: S{season}E{episode} from '{title_text}'")
# Check for "Season X Episode Y" text
elif 'season' in title_text.lower() and 'episode' in title_text.lower():
debug_print(f"[DEBUG] Title contains 'season' and 'episode': '{title_text}'")
# Try to extract season and episode numbers
season_match = re.search(r'season\s+(\d+)', title_text, re.IGNORECASE)
episode_match = re.search(r'episode\s+(\d+)', title_text, re.IGNORECASE)
if season_match:
season = int(season_match.group(1))
debug_print(f"[DEBUG] Extracted season from text: {season}")
if episode_match:
episode = int(episode_match.group(1))
debug_print(f"[DEBUG] Extracted episode from text: {episode}")
# Check for Episode number in brackets like "Show Name (5)"
elif re.search(r'\(\s*\d+\s*\)', title_text):
num_match = re.search(r'\(\s*(\d+)\s*\)', title_text)
if num_match:
# Assume this is an episode number if we don't have one yet
if episode is None:
episode = int(num_match.group(1))
debug_print(f"[DEBUG] Extracted episode from brackets: {episode}")
# Check for common patterns like "E05" without season
elif re.search(r'E\d+', title_text, re.IGNORECASE) and episode is None:
e_match = re.search(r'E(\d+)', title_text, re.IGNORECASE)
if e_match:
episode = int(e_match.group(1))
debug_print(f"[DEBUG] Extracted episode from E-format: {episode}")
if season is not None or episode is not None:
debug_print(f"[DEBUG] Final season/episode for '{title_text}': S{season}E{episode}")
# Get original air date if available
date_elem = programme.find('date')
air_date = date_elem.text if date_elem is not None else None
if channel_id not in self.programs:
self.programs[channel_id] = []
# Create formatted show title with season/episode if available
formatted_title = title_elem.text if title_elem is not None else "No Title"
self.programs[channel_id].append({
'start': start,
'stop': stop,
'title': title_elem.text if title_elem is not None else "No Title",
'desc': desc_elem.text if desc_elem is not None else ""
'title': formatted_title,
'desc': desc_elem.text if desc_elem is not None else "",
'season': season,
'episode': episode,
'episode_num': episode_num,
'air_date': air_date
})
print(f"[DEBUG] Loaded EPG data for {len(self.programs)} channels")
debug_print(f"[DEBUG] Loaded EPG data for {len(self.programs)} channels")
# Pre-load current and next shows for each channel
self.update_current_shows()
# Show a sample of program data with season/episode info for debugging
for channel_id, programs in self.programs.items():
if programs and len(programs) > 0:
sample = programs[0]
if sample.get('season') is not None or sample.get('episode') is not None:
debug_print(f"[DEBUG] Sample program: Title={sample.get('title')}, Season={sample.get('season')}, Episode={sample.get('episode')}, Episode_num={sample.get('episode_num')}")
break
except Exception as e:
print(f"[WARNING] Failed to load EPG: {str(e)}")
@ -363,6 +630,8 @@ class IPTVPlayer:
break
def add_error_channel(self, message):
# Clear existing channels before adding error channel
self.channels = []
self.channels.append({
'id': "error_channel",
'name': f"Error: {message}",
@ -379,6 +648,33 @@ class IPTVPlayer:
return datetime.strptime(time_str[:14], "%Y%m%d%H%M%S")
except:
return datetime.now()
def generate_imdb_url(self, show):
"""Generate IMDb search URL for a show or specific episode"""
# Base title for search
title = show.get('title', '')
if not title:
return None
# Check if we have season and episode information
season = show.get('season')
episode = show.get('episode')
# Advanced search for TV episodes is more accurate for specific episodes
if season is not None and episode is not None:
# For episodes, use IMDb's advanced title search which gives better results
# Format properly for the advanced search with title, season and episode
show_title = urllib.parse.quote_plus(title)
# Use IMDb's advanced search for TV episodes
# This directly targets the episode search with better filtering
return f"https://www.imdb.com/search/title/?title={show_title}&title_type=tv_episode&season={season}&episode={episode}"
else:
# For shows without episode info, use regular search
encoded_query = urllib.parse.quote_plus(title)
return f"https://www.imdb.com/find?q={encoded_query}&s=tt&ttype=tv"
# Removed clickable link function since it doesn't work in Windows CMD
def setup_ui(self):
# Create channel list with current show info
@ -501,12 +797,13 @@ class IPTVPlayer:
json.dump(new_config, config_file, indent=4)
# Update global variables
global M3U_URL, XMLTV_URL, USER_AGENT, UPDATE_INTERVAL, MONITOR, MPV_BASE
global M3U_URL, XMLTV_URL, USER_AGENT, UPDATE_INTERVAL, MONITOR, MPV_BASE, DEBUG_MODE
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']
DEBUG_MODE = new_config['debug_mode']
# Update MPV base command directly from new_config
MPV_BASE = ["mpv"]
@ -555,11 +852,11 @@ class IPTVPlayer:
self.loop.set_alarm_in(0, self.refresh_schedule)
def refresh_schedule(self, loop=None, user_data=None):
"""Update schedule information"""
"""Update schedule information without animation for better performance"""
try:
print("[DEBUG] Refreshing schedule data...")
debug_print("[DEBUG] Refreshing schedule data...")
# Just update current shows without reloading everything
# Update current shows without animations
self.update_current_shows()
self.last_update = datetime.now()
@ -567,16 +864,13 @@ class IPTVPlayer:
self.refresh_ui()
self.update_footer()
# Add notification to program view
# Add simple update notification
time_str = self.last_update.strftime("%H:%M:%S")
notification = [
urwid.Text([("update", f"Schedule updated at {time_str}")]),
urwid.Text("") # Empty line
]
# Prepend notification to existing content
self.program_walker[:] = notification + self.program_walker[:]
update_text = urwid.Text([("update", f"Schedule updated at {time_str}")])
empty_line = urwid.Text("")
self.program_walker[:] = [update_text, empty_line] + self.program_walker[:]
print("[DEBUG] Schedule refreshed successfully")
debug_print("[DEBUG] Schedule refreshed successfully")
except Exception as e:
print(f"[ERROR] Failed to refresh schedule: {str(e)}")
@ -606,15 +900,17 @@ class IPTVPlayer:
self.on_channel_hover(button.channel)
def on_channel_hover(self, channel):
"""Update program info"""
"""Update program info without animation for better performance"""
self.current_channel = channel
program_info = self.get_program_info(channel)
# Convert each line to a Text widget
# Direct update without animation for better performance
self.program_walker[:] = [urwid.Text(line) for line in program_info]
def on_channel_select(self, channel):
"""Play channel when selected"""
"""Play channel when selected (no animation for better performance)"""
if channel.get('url'):
# Play the channel directly without animation
self.play_channel(channel['url'])
def get_program_info(self, channel):
@ -626,6 +922,14 @@ class IPTVPlayer:
if channel.get('group'):
info.append([("title", f"Group: {channel['group']}")])
# Debug program information
if channel.get('current_show'):
current_show = channel['current_show']
debug_print(f"[DEBUG] Program info - Title: {current_show.get('title')}")
debug_print(f"[DEBUG] Program info - Season: {current_show.get('season')}")
debug_print(f"[DEBUG] Program info - Episode: {current_show.get('episode')}")
debug_print(f"[DEBUG] Program info - Air date: {current_show.get('air_date')}")
if channel.get('current_show'):
now = datetime.now()
remaining = (channel['current_show']['stop'] - now).seconds // 60
@ -635,12 +939,57 @@ class IPTVPlayer:
# Current show section with colorful formatting
info.append([]) # Empty line
info.append([("program_now", "--NOW PLAYING")])
info.append([("title", f"Title: {channel['current_show']['title']}")])
# Display title with season and episode prominently if available
current_show = channel['current_show']
# Extract and display season/episode information
season = current_show.get('season')
episode = current_show.get('episode')
# If season/episode not available in metadata, try to extract from title
if season is None or episode is None:
# Try SxxExx format (S01E05)
se_match = re.search(r'S(\d+)E(\d+)', current_show['title'], re.IGNORECASE)
if se_match:
season = int(se_match.group(1))
episode = int(se_match.group(2))
debug_print(f"[DEBUG] Extracted S{season}E{episode} from title format")
# Try season x episode format (1x05)
elif 'x' in current_show['title'].lower():
x_match = re.search(r'(\d+)x(\d+)', current_show['title'], re.IGNORECASE)
if x_match:
season = int(x_match.group(1))
episode = int(x_match.group(2))
debug_print(f"[DEBUG] Extracted S{season}E{episode} from Nx format")
# Display title with or without season/episode info
if season is not None and episode is not None:
# Format title with season/episode info
formatted_title = f"Title: {current_show['title']} (S{season:02d}E{episode:02d})"
info.append([("title", formatted_title)])
# Also add a detailed version on a separate line
info.append([("program_desc", f"Season {season}, Episode {episode}")])
# Update the current_show object with the extracted info if it wasn't there already
current_show['season'] = season
current_show['episode'] = episode
else:
info.append([("title", f"Title: {current_show['title']}")])
# Add air date if available
if current_show.get('air_date'):
info.append([("time", f"Original Air Date: {current_show['air_date']}")])
info.append([
("time", f"Time: {start_time} - {end_time} "),
("", f"({remaining} minutes remaining)")
])
# Add IMDb link if we can generate one
imdb_url = self.generate_imdb_url(current_show)
# Skip IMDb URL display as it doesn't work well in Windows CMD
# if imdb_url:
# info.append([("update", f"IMDb: {imdb_url}")])
if channel['current_show']['desc']:
info.append([]) # Empty line
info.append([("program_desc", "--Description:")])
@ -658,9 +1007,53 @@ class IPTVPlayer:
info.append([("divider", "" * 50)])
info.append([]) # Empty line
info.append([("program_next", "--UP NEXT")])
info.append([("title", f"Title: {channel['next_show']['title']}")])
# Display title with season and episode information
next_show = channel['next_show']
# Extract season/episode information for next show
season = next_show.get('season')
episode = next_show.get('episode')
# If season/episode not available in metadata, try to extract from title
if season is None or episode is None:
# Try SxxExx format (S01E05)
se_match = re.search(r'S(\d+)E(\d+)', next_show['title'], re.IGNORECASE)
if se_match:
season = int(se_match.group(1))
episode = int(se_match.group(2))
debug_print(f"[DEBUG] Next show: Extracted S{season}E{episode} from title format")
# Try season x episode format (1x05)
elif 'x' in next_show['title'].lower():
x_match = re.search(r'(\d+)x(\d+)', next_show['title'], re.IGNORECASE)
if x_match:
season = int(x_match.group(1))
episode = int(x_match.group(2))
debug_print(f"[DEBUG] Next show: Extracted S{season}E{episode} from Nx format")
# Display title with or without season/episode info
if season is not None and episode is not None:
# Format title with season/episode info
formatted_title = f"Title: {next_show['title']} (S{season:02d}E{episode:02d})"
info.append([("title", formatted_title)])
# Also add a detailed version on a separate line
info.append([("program_desc", f"Season {season}, Episode {episode}")])
# Update the next_show object with the extracted info if it wasn't there already
next_show['season'] = season
next_show['episode'] = episode
else:
info.append([("title", f"Title: {next_show['title']}")])
# Add air date if available for next show
if next_show.get('air_date'):
info.append([("time", f"Original Air Date: {next_show['air_date']}")])
info.append([("time", f"Time: {next_start} - {next_end}")])
# Add IMDb link for next show if we can generate one
imdb_url = self.generate_imdb_url(next_show)
# Skip IMDb URL display as it doesn't work well in Windows CMD
if channel['next_show']['desc']:
info.append([]) # Empty line
info.append([("program_desc", "--Description:")])
@ -682,9 +1075,53 @@ class IPTVPlayer:
info.append([]) # Empty line
info.append([("program_next", "--UP NEXT")])
info.append([("title", f"Title: {channel['next_show']['title']}")])
# Display title with season and episode information
next_show = channel['next_show']
# Extract season/episode information for next show
season = next_show.get('season')
episode = next_show.get('episode')
# If season/episode not available in metadata, try to extract from title
if season is None or episode is None:
# Try SxxExx format (S01E05)
se_match = re.search(r'S(\d+)E(\d+)', next_show['title'], re.IGNORECASE)
if se_match:
season = int(se_match.group(1))
episode = int(se_match.group(2))
debug_print(f"[DEBUG] Next show: Extracted S{season}E{episode} from title format")
# Try season x episode format (1x05)
elif 'x' in next_show['title'].lower():
x_match = re.search(r'(\d+)x(\d+)', next_show['title'], re.IGNORECASE)
if x_match:
season = int(x_match.group(1))
episode = int(x_match.group(2))
debug_print(f"[DEBUG] Next show: Extracted S{season}E{episode} from Nx format")
# Display title with or without season/episode info
if season is not None and episode is not None:
# Format title with season/episode info
formatted_title = f"Title: {next_show['title']} (S{season:02d}E{episode:02d})"
info.append([("title", formatted_title)])
# Also add a detailed version on a separate line
info.append([("program_desc", f"Season {season}, Episode {episode}")])
# Update the next_show object with the extracted info if it wasn't there already
next_show['season'] = season
next_show['episode'] = episode
else:
info.append([("title", f"Title: {next_show['title']}")])
# Add air date if available for next show
if next_show.get('air_date'):
info.append([("time", f"Original Air Date: {next_show['air_date']}")])
info.append([("time", f"Time: {next_start} - {next_end}")])
# Add IMDb link for next show if we can generate one
imdb_url = self.generate_imdb_url(next_show)
# Skip IMDb URL display as it doesn't work well in Windows CMD
if channel['next_show']['desc']:
info.append([]) # Empty line
info.append([("program_desc", "--Description:")])
@ -715,7 +1152,7 @@ class IPTVPlayer:
self.program_walker[:] = [urwid.Text([("error", f"Failed to play stream: {str(e)}")])]
def reload_data(self):
"""Reload all data from sources without rebuilding UI"""
"""Reload all data from sources without animation for better performance"""
# Save current focus position
current_focus_pos = self.channel_list.focus_position if self.channel_list.body else None
@ -782,6 +1219,10 @@ class IPTVPlayer:
self.start_update_thread()
try:
# Show splash screen animation before running the main loop
self.display_splash_screen()
# Start the main event loop
self.loop.run()
finally:
# Clean up when exiting

Loading…
Cancel
Save