import os import sys import time import requests import subprocess from PyQt5.QtWidgets import ( QApplication, QMainWindow, QVBoxLayout, QListWidget, QWidget, QHBoxLayout, QListWidgetItem, QLabel, QSplitter, QFrame, QPushButton ) from PyQt5.QtCore import Qt, QTimer import vlc class VideoFrame(QFrame): def __init__(self, parent=None): super().__init__(parent) self.setStyleSheet("background-color: black;") def mouseDoubleClickEvent(self, event): self.parent().toggle_fullscreen() class VLCPlayer: def __init__(self, video_frame): self.instance = vlc.Instance() self.media_player = self.instance.media_player_new() self.video_frame = video_frame if sys.platform == "win32": self.media_player.set_hwnd(int(self.video_frame.winId())) else: self.media_player.set_xwindow(int(self.video_frame.winId())) def play_stream(self, url): if self.media_player.is_playing(): self.media_player.stop() time.sleep(0.5) media = self.instance.media_new(url) media.add_option(":network-caching=3000") self.media_player.set_media(media) self.media_player.play() def stop(self): self.media_player.stop() def release(self): self.media_player.release() self.instance.release() class IPTVWindow(QMainWindow): def __init__(self, php_dir): super().__init__() self.setWindowTitle("IPTV Player with EPG") self.setGeometry(0, 0, 1280, 720) self.php_dir = php_dir self._is_fullscreen = False self._normal_geometry = None self._normal_flags = None self.init_ui() self.download_playlist() def init_ui(self): # Main splitter self.splitter = QSplitter(Qt.Horizontal) # Left: Channel list self.channel_list = QListWidget() self.channel_list.itemClicked.connect(self.on_channel_clicked) self.splitter.addWidget(self.channel_list) # Right: Video + EPG + controls self.right_panel = QWidget() right_layout = QVBoxLayout(self.right_panel) right_layout.setContentsMargins(0, 0, 0, 0) right_layout.setSpacing(0) # Video frame self.video_frame = VideoFrame(self) right_layout.addWidget(self.video_frame, stretch=3) # EPG display self.epg_display = QLabel("EPG info will appear here") self.epg_display.setStyleSheet("background-color: #222; color: white; padding: 10px;") right_layout.addWidget(self.epg_display, stretch=1) # Controls self.controls = QWidget() control_layout = QHBoxLayout(self.controls) for name, func in [("⏮️", self.play_previous_channel), ("⏯️", self.toggle_play_pause), ("⏭️", self.play_next_channel), ("🖵", self.toggle_fullscreen)]: btn = QPushButton(name) btn.clicked.connect(func) control_layout.addWidget(btn) right_layout.addWidget(self.controls) self.splitter.addWidget(self.right_panel) self.splitter.setSizes([300, 1000]) self.setCentralWidget(self.splitter) # VLC player self.player = VLCPlayer(self.video_frame) def on_channel_clicked(self, item): url = item.data(Qt.UserRole) self.player.play_stream(url) def update_info(attempt=0): width = self.player.media_player.video_get_width() height = self.player.media_player.video_get_height() fps = self.player.media_player.get_fps() if width > 0 and height > 0: res = "SD" if width >= 3840: res = "4K" elif width >= 1920: res = "FHD" elif width >= 1280: res = "HD" text = f"{res}, {fps:.2f}FPS" base = item.text().split('(')[0].strip() item.setText(f"{base} ({text})") elif attempt < 10: QTimer.singleShot(500, lambda: update_info(attempt+1)) QTimer.singleShot(800, lambda: update_info(0)) def toggle_play_pause(self): if self.player.media_player.is_playing(): self.player.media_player.pause() else: self.player.media_player.play() def play_next_channel(self): i = self.channel_list.currentRow() if i < self.channel_list.count()-1: self.channel_list.setCurrentRow(i+1) self.on_channel_clicked(self.channel_list.currentItem()) def play_previous_channel(self): i = self.channel_list.currentRow() if i > 0: self.channel_list.setCurrentRow(i-1) self.on_channel_clicked(self.channel_list.currentItem()) def toggle_fullscreen(self): if not self._is_fullscreen: # Save state self._normal_geometry = self.geometry() self._normal_flags = self.windowFlags() # Hide UI panels self.channel_list.hide() self.controls.hide() self.epg_display.hide() # Frameless and full screen via state self.setWindowFlags(Qt.FramelessWindowHint) self.show() self.setWindowState(self.windowState() | Qt.WindowFullScreen) else: # Restore state self.setWindowState(Qt.WindowNoState) self.setWindowFlags(self._normal_flags) self.show() if self._normal_geometry: self.setGeometry(self._normal_geometry) # Show UI self.channel_list.show() self.controls.show() self.epg_display.show() self._is_fullscreen = not self._is_fullscreen def keyPressEvent(self, event): if event.key() == Qt.Key_Escape and self._is_fullscreen: self.toggle_fullscreen() else: super().keyPressEvent(event) def download_playlist(self): try: os.makedirs(self.php_dir, exist_ok=True) url = "https://iptv.nywebforum.com/playlist.m3u" txt = requests.get(url, timeout=10).text.replace( "https://iptv.nywebforum.com", "http://localhost:8888" ) path = os.path.join(self.php_dir, "playlist.m3u") with open(path, "w", encoding="utf-8") as f: f.write(txt) self.load_playlist(path) except Exception as e: print(f"Failed to download playlist: {e}") def load_playlist(self, path): try: with open(path, "r", encoding="utf-8") as f: lines = f.readlines() self.channel_list.clear() first = None name = None for l in lines: l = l.strip() if l.startswith("#EXTINF:"): name = l.split(',')[-1].strip() elif l and not l.startswith("#") and name: it = QListWidgetItem(f"{name} (Loading...)") it.setData(Qt.UserRole, l) self.channel_list.addItem(it) if not first: first = it name = None if first: self.channel_list.setCurrentItem(first) QTimer.singleShot(800, lambda: self.on_channel_clicked(first)) except Exception as e: print(f"Failed to load playlist: {e}") def start_php_server(d, port=8888): try: os.chdir(d) return subprocess.Popen(["php", "-S", f"localhost:{port}"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, creationflags=subprocess.CREATE_NO_WINDOW if sys.platform=="win32" else 0) except Exception: return None if __name__ == "__main__": pd = os.path.join(os.path.dirname(__file__), "php_files") os.makedirs(pd, exist_ok=True) proc = start_php_server(pd) app = QApplication(sys.argv) w = IPTVWindow(pd) w.show() rc = app.exec_() w.player.stop() w.player.release() if proc: proc.terminate() try: proc.wait(2) except: proc.kill() sys.exit(rc)