import os import sys import requests import time import xml.etree.ElementTree as ET from datetime import datetime, timedelta from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLabel, QListWidget, QSlider, QMessageBox, QTextEdit, QListWidgetItem, QScrollArea, QFrame) from PyQt5.QtCore import Qt, QTimer, QSize from PyQt5.QtGui import QColor import vlc import subprocess class EPGDisplay(QWidget): def __init__(self, parent=None): super().__init__(parent) self.setLayout(QVBoxLayout()) self.scroll = QScrollArea() self.scroll.setWidgetResizable(True) self.layout().addWidget(self.scroll) self.content = QWidget() self.content.setLayout(QHBoxLayout()) self.content.layout().setContentsMargins(0, 0, 0, 0) self.content.layout().setSpacing(0) self.scroll.setWidget(self.content) self.setMinimumHeight(150) self.setMaximumHeight(200) def update_epg(self, programs): # Clear existing widgets for i in reversed(range(self.content.layout().count())): self.content.layout().itemAt(i).widget().deleteLater() # Add program blocks for program in programs: frame = QFrame() frame.setFrameShape(QFrame.Box) frame.setLineWidth(1) frame.setStyleSheet("background-color: #f0f0f0;") layout = QVBoxLayout() layout.setContentsMargins(5, 5, 5, 5) time_label = QLabel(program['start_time'].strftime("%H:%M")) time_label.setStyleSheet("font-weight: bold;") title_label = QLabel(program['title']) title_label.setWordWrap(True) layout.addWidget(time_label) layout.addWidget(title_label) frame.setLayout(layout) # Calculate width based on duration (1 hour = 200px) duration_hours = (program['end_time'] - program['start_time']).total_seconds() / 3600 frame.setFixedWidth(int(duration_hours * 200)) self.content.layout().addWidget(frame) class IPTVPlayer(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("IPTV Player - Playlist") self.php_port = 8888 self.php_dir = os.path.join(os.path.dirname(__file__), "php_files") os.makedirs(self.php_dir, exist_ok=True) self.php_process = None self.epg_data = {} self.current_epg_url = "" # Initialize UI self.init_ui() # Create separate video window self.video_window = VideoWindow() self.video_window.show() # Position windows self.position_windows() # Initialize VLC in video window self.video_window.init_vlc() # Start PHP server self.start_php_server() # Download and process files self.download_and_process_files() def init_ui(self): """Initialize the playlist window UI""" main_widget = QWidget(self) self.setCentralWidget(main_widget) layout = QVBoxLayout() main_widget.setLayout(layout) # Channel list self.channel_list = QListWidget() self.channel_list.setStyleSheet(""" QListWidget { font-size: 12px; } QListWidget::item { padding: 5px; } QListWidget::item:selected { background-color: #0078d7; color: white; } """) self.channel_list.itemDoubleClicked.connect(self.play_selected) self.channel_list.currentItemChanged.connect(self.update_epg_display) layout.addWidget(self.channel_list, 3) # EPG display self.epg_display = EPGDisplay() layout.addWidget(self.epg_display, 1) # Debug console self.debug_console = QTextEdit() self.debug_console.setReadOnly(True) self.debug_console.setMaximumHeight(100) layout.addWidget(self.debug_console) # Controls control_layout = QHBoxLayout() self.play_button = QPushButton("Play") self.play_button.clicked.connect(self.play_selected) self.stop_button = QPushButton("Stop") self.stop_button.clicked.connect(self.stop) self.reload_button = QPushButton("Update") self.reload_button.clicked.connect(self.download_and_process_files) control_layout.addWidget(self.play_button) control_layout.addWidget(self.stop_button) control_layout.addWidget(self.reload_button) layout.addLayout(control_layout) def position_windows(self): """Position the video and playlist windows""" screen = QApplication.primaryScreen().availableGeometry() # Playlist window (left half of screen) playlist_width = int(screen.width() * 0.5) self.setGeometry( screen.left(), screen.top(), playlist_width, screen.height() ) # Video window (upper right quarter) video_width = int(screen.width() * 0.25) video_height = int(screen.height() * 0.25) self.video_window.setGeometry( screen.left() + screen.width() - video_width, screen.top(), video_width, video_height ) def start_php_server(self): """Start the built-in PHP server""" try: os.chdir(self.php_dir) self.php_process = subprocess.Popen( ["php", "-S", f"localhost:{self.php_port}"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, creationflags=subprocess.CREATE_NO_WINDOW if sys.platform == "win32" else 0 ) self.log_message(f"PHP server started on port {self.php_port}") except Exception as e: self.log_message(f"Failed to start PHP server: {str(e)}", error=True) QMessageBox.critical(self, "PHP Server Error", "Could not start PHP server.\n" "Please check if PHP is properly installed.") def log_message(self, message, error=False): """Add message to debug console""" timestamp = time.strftime("%H:%M:%S", time.localtime()) if error: self.debug_console.setTextColor(Qt.red) self.debug_console.append(f"[ERROR {timestamp}] {message}") else: self.debug_console.setTextColor(Qt.black) self.debug_console.append(f"[INFO {timestamp}] {message}") self.debug_console.setTextColor(Qt.black) self.debug_console.ensureCursorVisible() def download_and_process_files(self): """Download and process all required files""" files = { "myportal.php": "https://iptv.nywebforum.com/myportal.php?download=true", "mac_list.php": "https://iptv.nywebforum.com/mac_list.php?download=true", "playlist.m3u": "https://iptv.nywebforum.com/playlist.m3u" } for filename, url in files.items(): try: temp_path = os.path.join(self.php_dir, f"temp_{filename}") response = requests.get(url, timeout=10) with open(temp_path, 'wb') as f: f.write(response.content) if filename == "playlist.m3u": with open(temp_path, 'r', encoding='utf-8') as f: content = f.read() if "x-tvg-url=" in content: self.current_epg_url = content.split('x-tvg-url="')[1].split('"')[0] final_path = os.path.join(self.php_dir, filename) if os.path.exists(final_path): os.remove(final_path) os.rename(temp_path, final_path) self.log_message(f"Downloaded {filename}") except Exception as e: self.log_message(f"Error processing {filename}: {str(e)}", error=True) if os.path.exists(temp_path): os.remove(temp_path) self.process_url_replacements() self.load_playlist() if self.current_epg_url: self.load_epg() def process_url_replacements(self): """Replace all server URLs with localhost URLs""" try: playlist_path = os.path.join(self.php_dir, "playlist.m3u") if os.path.exists(playlist_path): with open(playlist_path, 'r', encoding='utf-8') as f: content = f.read() content = content.replace( "https://iptv.nywebforum.com", f"http://localhost:{self.php_port}" ) with open(playlist_path, 'w', encoding='utf-8') as f: f.write(content) self.log_message("URL replacements completed") except Exception as e: self.log_message(f"Error during URL replacement: {str(e)}", error=True) def load_epg(self): """Load EPG data from XMLTV file""" try: response = requests.get(self.current_epg_url, timeout=10) root = ET.fromstring(response.content) self.epg_data = {} for programme in root.findall('programme'): channel = programme.get('channel') start_time = datetime.strptime(programme.get('start'), '%Y%m%d%H%M%S %z') end_time = datetime.strptime(programme.get('stop'), '%Y%m%d%H%M%S %z') title = programme.find('title').text if programme.find('title') is not None else "No Title" if channel not in self.epg_data: self.epg_data[channel] = [] self.epg_data[channel].append({ 'start_time': start_time, 'end_time': end_time, 'title': title }) self.log_message(f"Loaded EPG data for {len(self.epg_data)} channels") except Exception as e: self.log_message(f"Error loading EPG: {str(e)}", error=True) def update_epg_display(self, current, previous): """Update EPG display when channel selection changes""" if current is None: return channel_id = current.data(Qt.UserRole + 1) # Channel ID for EPG lookup now = datetime.now().astimezone() end_time = now + timedelta(hours=2) programs = [] if channel_id in self.epg_data: for program in self.epg_data[channel_id]: if program['end_time'] > now and program['start_time'] < end_time: programs.append(program) self.epg_display.update_epg(programs) def load_playlist(self): """Load the playlist with channel names and icons""" playlist_path = os.path.join(self.php_dir, "playlist.m3u") if os.path.exists(playlist_path): try: with open(playlist_path, 'r', encoding='utf-8') as f: lines = f.readlines() self.channel_list.clear() current_channel = None current_id = None for line in lines: line = line.strip() if line.startswith("#EXTINF:"): parts = line.split(',') if len(parts) > 1: current_channel = parts[1].strip() if 'tvg-id="' in line: current_id = line.split('tvg-id="')[1].split('"')[0] elif line and not line.startswith("#"): if current_channel: item = QListWidgetItem(current_channel) item.setData(Qt.UserRole, line) # Store URL item.setData(Qt.UserRole + 1, current_id) # Store channel ID self.channel_list.addItem(item) current_channel = None current_id = None if self.channel_list.count() > 0: self.channel_list.setCurrentRow(0) QTimer.singleShot(1000, self.play_selected) self.log_message(f"Playlist loaded with {self.channel_list.count()} channels") except Exception as e: self.log_message(f"Error loading playlist: {str(e)}", error=True) def play_selected(self): """Play the selected channel""" selected = self.channel_list.currentItem() if not selected: return url = selected.data(Qt.UserRole) self.video_window.play_stream(url, selected.text()) def stop(self): """Stop playback""" self.video_window.stop() def closeEvent(self, event): """Clean up when closing""" if self.php_process: self.php_process.terminate() try: self.php_process.wait(timeout=1) except subprocess.TimeoutExpired: self.php_process.kill() self.video_window.close() super().closeEvent(event) class VideoWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("IPTV Player - Video") self.setWindowFlags(Qt.WindowStaysOnTopHint) self.video_frame = QFrame() self.video_frame.setFrameShape(QFrame.Box) self.video_frame.setStyleSheet("background-color: black;") # Volume control self.volume_slider = QSlider(Qt.Horizontal) self.volume_slider.setRange(0, 100) self.volume_slider.setValue(50) self.volume_slider.valueChanged.connect(self.set_volume) layout = QVBoxLayout() layout.addWidget(self.video_frame, 1) layout.addWidget(self.volume_slider) container = QWidget() container.setLayout(layout) self.setCentralWidget(container) def init_vlc(self): """Initialize VLC player""" try: self.instance = vlc.Instance() self.media_player = self.instance.media_player_new() if sys.platform == "win32": self.media_player.set_hwnd(self.video_frame.winId()) else: self.media_player.set_xwindow(self.video_frame.winId()) except Exception as e: print(f"VLC initialization failed: {str(e)}") def play_stream(self, url, title): """Play a stream URL""" self.setWindowTitle(f"IPTV Player - {title}") if hasattr(self, 'media_player'): self.media_player.stop() media = self.instance.media_new(url) media.add_option(':network-caching=3000') media.add_option(':file-caching=3000') media.add_option(':live-caching=3000') self.media_player.set_media(media) self.media_player.play() def set_volume(self, value): """Set player volume""" if hasattr(self, 'media_player'): self.media_player.audio_set_volume(value) def stop(self): """Stop playback""" if hasattr(self, 'media_player'): self.media_player.stop() if __name__ == "__main__": app = QApplication(sys.argv) # Check dependencies try: import requests import vlc except ImportError as e: QMessageBox.critical(None, "Missing Dependencies", f"Required packages missing:\n{str(e)}\n" "Please install with: pip install python-vlc requests") sys.exit(1) # Check if PHP is available try: subprocess.run(["php", "-v"], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) except: QMessageBox.critical(None, "PHP Not Found", "PHP is required but not found in system PATH.\n" "Please install PHP or place PHP binaries in the application directory.") sys.exit(1) player = IPTVPlayer() player.show() sys.exit(app.exec_())