|
|
|
@ -3,6 +3,7 @@ import os |
|
|
|
import re |
|
|
|
import subprocess |
|
|
|
import sys |
|
|
|
import json |
|
|
|
import xml.etree.ElementTree as ET |
|
|
|
from datetime import datetime, timedelta |
|
|
|
from urllib.request import urlopen, Request |
|
|
|
@ -11,27 +12,53 @@ from urllib.error import URLError |
|
|
|
import threading |
|
|
|
import time |
|
|
|
|
|
|
|
# Configuration |
|
|
|
# Load configuration from JSON file |
|
|
|
try: |
|
|
|
with open('config.json') as config_file: |
|
|
|
config = json.load(config_file) |
|
|
|
M3U_URL = config.get('m3u_url', "http://10.0.0.17:8409/iptv/channels.m3u") |
|
|
|
XMLTV_URL = config.get('xmltv_url', "http://10.0.0.17:8409/iptv/xmltv.xml") |
|
|
|
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) |
|
|
|
|
|
|
|
# Base MPV command with monitor options if specified |
|
|
|
MPV_BASE = ["mpv"] |
|
|
|
if MONITOR is not None: |
|
|
|
MPV_BASE.extend(["--fs", f"--fs-screen={MONITOR}"]) |
|
|
|
MPV_BASE.extend(config.get('mpv_options', [ |
|
|
|
"--really-quiet", |
|
|
|
"--no-terminal", |
|
|
|
"--force-window=immediate" |
|
|
|
])) |
|
|
|
except FileNotFoundError: |
|
|
|
print("Configuration file not found, using default settings") |
|
|
|
M3U_URL = "http://10.0.0.17:8409/iptv/channels.m3u" |
|
|
|
XMLTV_URL = "http://10.0.0.17:8409/iptv/xmltv.xml" |
|
|
|
MPV_COMMAND = ["mpv", "--really-quiet", "--no-terminal", "--force-window=immediate"] |
|
|
|
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 # 15 minutes in seconds |
|
|
|
UPDATE_INTERVAL = 900 |
|
|
|
MONITOR = None |
|
|
|
MPV_BASE = [ |
|
|
|
"mpv", |
|
|
|
"--really-quiet", |
|
|
|
"--no-terminal", |
|
|
|
"--force-window=immediate" |
|
|
|
] |
|
|
|
|
|
|
|
# Color Palette |
|
|
|
# Color Palette remains the same |
|
|
|
PALETTE = [ |
|
|
|
('header', 'white', 'dark blue'), |
|
|
|
('footer', 'white', 'dark blue'), |
|
|
|
('channel', 'black', 'light gray'), |
|
|
|
('channel', 'black', 'light cyan'), |
|
|
|
('channel_focus', 'white', 'dark blue'), |
|
|
|
('program_now', 'white', 'dark green'), |
|
|
|
('program_next', 'black', 'light gray'), |
|
|
|
('error', 'white', 'dark red'), |
|
|
|
('divider', 'light gray', ''), |
|
|
|
('program_desc', 'light cyan', ''), # New color for descriptions |
|
|
|
('time', 'yellow', ''), # New color for time displays |
|
|
|
('title', 'bold', ''), # New color for titles |
|
|
|
('update', 'light green', ''), # Color for update notifications |
|
|
|
('divider', 'dark green', ''), |
|
|
|
('program_desc', 'light cyan', ''), |
|
|
|
('time', 'yellow', ''), |
|
|
|
('title', 'bold', ''), |
|
|
|
('update', 'light blue', ''), |
|
|
|
] |
|
|
|
|
|
|
|
# ASCII Art |
|
|
|
@ -235,7 +262,8 @@ class IPTVPlayer: |
|
|
|
self.program_listbox = urwid.ListBox(self.program_walker) |
|
|
|
|
|
|
|
# Add update time to footer |
|
|
|
self.footer_text = urwid.Text("Q: Quit | ↑↓: Navigate | Enter: Play Channel | L: Reload | Last update: Loading...", align='center') |
|
|
|
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 = urwid.AttrMap(self.footer_text, 'footer') |
|
|
|
|
|
|
|
program_frame = urwid.Frame( |
|
|
|
@ -276,7 +304,8 @@ class IPTVPlayer: |
|
|
|
def update_footer(self): |
|
|
|
"""Update footer with last update time""" |
|
|
|
time_str = self.last_update.strftime("%H:%M:%S") |
|
|
|
self.footer_text.set_text(f"Q: Quit | ↑↓: Navigate | Enter: Play Channel | L: Reload | Last update: {time_str}") |
|
|
|
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}") |
|
|
|
|
|
|
|
def start_update_thread(self): |
|
|
|
"""Start background thread for periodic updates""" |
|
|
|
@ -359,7 +388,7 @@ class IPTVPlayer: |
|
|
|
info = [] |
|
|
|
max_line_length = 60 # Define this at the top so it's available everywhere |
|
|
|
|
|
|
|
info.append([("header", f"📺 Channel: {channel['name']}")]) |
|
|
|
info.append([("header", f"--Channel: {channel['name']}")]) |
|
|
|
|
|
|
|
if channel.get('group'): |
|
|
|
info.append([("title", f"Group: {channel['group']}")]) |
|
|
|
@ -372,7 +401,7 @@ class IPTVPlayer: |
|
|
|
|
|
|
|
# Current show section with colorful formatting |
|
|
|
info.append([]) # Empty line |
|
|
|
info.append([("program_now", "⏺ NOW PLAYING")]) |
|
|
|
info.append([("program_now", "--NOW PLAYING")]) |
|
|
|
info.append([("title", f"Title: {channel['current_show']['title']}")]) |
|
|
|
info.append([ |
|
|
|
("time", f"Time: {start_time} - {end_time} "), |
|
|
|
@ -381,7 +410,7 @@ class IPTVPlayer: |
|
|
|
|
|
|
|
if channel['current_show']['desc']: |
|
|
|
info.append([]) # Empty line |
|
|
|
info.append([("program_desc", "📝 Description:")]) |
|
|
|
info.append([("program_desc", "--Description:")]) |
|
|
|
# Split long description into multiple lines |
|
|
|
desc = channel['current_show']['desc'] |
|
|
|
for i in range(0, len(desc), max_line_length): |
|
|
|
@ -395,13 +424,13 @@ class IPTVPlayer: |
|
|
|
info.append([]) # Empty line |
|
|
|
info.append([("divider", "─" * 50)]) |
|
|
|
info.append([]) # Empty line |
|
|
|
info.append([("program_next", "⏭ UP NEXT")]) |
|
|
|
info.append([("program_next", "--UP NEXT")]) |
|
|
|
info.append([("title", f"Title: {channel['next_show']['title']}")]) |
|
|
|
info.append([("time", f"Time: {next_start} - {next_end}")]) |
|
|
|
|
|
|
|
if channel['next_show']['desc']: |
|
|
|
info.append([]) # Empty line |
|
|
|
info.append([("program_desc", "📝 Description:")]) |
|
|
|
info.append([("program_desc", "--Description:")]) |
|
|
|
desc = channel['next_show']['desc'] |
|
|
|
# Limit description to 5 lines to prevent overflow |
|
|
|
max_lines = 5 |
|
|
|
@ -419,13 +448,13 @@ class IPTVPlayer: |
|
|
|
next_end = channel['next_show']['stop'].strftime("%H:%M") |
|
|
|
|
|
|
|
info.append([]) # Empty line |
|
|
|
info.append([("program_next", "⏭ UP NEXT")]) |
|
|
|
info.append([("program_next", "--UP NEXT")]) |
|
|
|
info.append([("title", f"Title: {channel['next_show']['title']}")]) |
|
|
|
info.append([("time", f"Time: {next_start} - {next_end}")]) |
|
|
|
|
|
|
|
if channel['next_show']['desc']: |
|
|
|
info.append([]) # Empty line |
|
|
|
info.append([("program_desc", "📝 Description:")]) |
|
|
|
info.append([("program_desc", "--Description:")]) |
|
|
|
desc = channel['next_show']['desc'] |
|
|
|
# Limit description to 5 lines to prevent overflow |
|
|
|
max_lines = 5 |
|
|
|
@ -447,7 +476,7 @@ class IPTVPlayer: |
|
|
|
self.mpv_process.wait() |
|
|
|
|
|
|
|
try: |
|
|
|
self.mpv_process = subprocess.Popen(MPV_COMMAND + [url]) |
|
|
|
self.mpv_process = subprocess.Popen(MPV_BASE + [url]) |
|
|
|
except Exception as e: |
|
|
|
# Create proper widget for error message |
|
|
|
self.program_walker[:] = [urwid.Text([("error", f"Failed to play stream: {str(e)}")])] |
|
|
|
|