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,QSlider ) from PyQt5.QtCore import Qt, QTimer, QEvent, QTime import vlc class VideoFrame(QFrame): def __init__(self, parent=None): super().__init__(parent) self.setStyleSheet("background-color: black;") class OverlayWindow(QWidget): def __init__(self, main_window): super().__init__(None) self.main_window = main_window self.setWindowFlags(Qt.FramelessWindowHint | Qt.Tool | Qt.WindowStaysOnTopHint) self.setAttribute(Qt.WA_TranslucentBackground) self.setStyleSheet("background-color: rgba(0, 0, 0, 0);") self.setFocusPolicy(Qt.NoFocus) self.setMouseTracking(True) self.channel_label = QLabel("Channel Name", self) self.resolution_label = QLabel("Resolution", self) self.clock_label = QLabel("Clock", self) for label in [self.channel_label, self.resolution_label, self.clock_label]: label.setStyleSheet("color: white; background-color: rgba(0,0,0,160); padding: 4px; border-radius: 6px;") label.setFixedHeight(25) self.channel_label.move(10, 10) self.resolution_label.move(10, 45) self.clock_label.move(10, 80) self.timer = QTimer(self) self.timer.timeout.connect(self.update_clock) self.timer.start(1000) self.update_clock() def update_clock(self): current_time = QTime.currentTime().toString("HH:mm:ss") self.clock_label.setText(current_time) def update_info(self, name, resolution): self.channel_label.setText(name) self.resolution_label.setText(resolution) def mouseDoubleClickEvent(self, event): self.main_window.toggle_fullscreen() class VLCPlayer: def __init__(self, video_frame): self.instance = vlc.Instance() self.media_player = self.instance.media_player_new() if sys.platform == "win32": self.media_player.set_hwnd(int(video_frame.winId())) else: self.media_player.set_xwindow(int(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.audio_set_volume(70) # Set default volume to 70% 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(100, 100, 1280, 720) self.php_dir = php_dir self._is_fullscreen = False self._normal_geometry = None self._normal_flags = None self.init_ui() self.installEventFilter(self) self.download_playlist() def init_ui(self): self.splitter = QSplitter(Qt.Horizontal) self.channel_list = QListWidget() self.channel_list.itemClicked.connect(self.on_channel_clicked) self.splitter.addWidget(self.channel_list) right_panel = QWidget() layout = QVBoxLayout(right_panel) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) self.video_container = QWidget() self.video_container.setStyleSheet("background-color: black;") self.video_container.setMinimumHeight(400) video_container_layout = QVBoxLayout(self.video_container) video_container_layout.setContentsMargins(0, 0, 0, 0) self.video_frame = VideoFrame(self.video_container) video_container_layout.addWidget(self.video_frame) layout.addWidget(self.video_container, stretch=3) self.player = VLCPlayer(self.video_frame) self.overlay_window = OverlayWindow(self) self.overlay_window.show() self.overlay_sync_timer = QTimer() self.overlay_sync_timer.timeout.connect(self.update_overlay_position) self.overlay_sync_timer.start(500) self.epg_display = QLabel("EPG info will appear here") self.epg_display.setStyleSheet("background-color: #222; color: white; padding: 10px;") layout.addWidget(self.epg_display, stretch=1) self.controls = QWidget() ctl_layout = QHBoxLayout(self.controls) for text, slot in [("⏮️", self.play_previous_channel), ("⏯️", self.toggle_play_pause), ("⏭️", self.play_next_channel), ("🖵", self.toggle_fullscreen)]: btn = QPushButton(text) btn.clicked.connect(slot) ctl_layout.addWidget(btn) layout.addWidget(self.controls) self.volume_slider = QSlider(Qt.Horizontal) self.volume_slider.setRange(0, 100) self.volume_slider.setValue(70) self.volume_slider.setToolTip("Volume") self.volume_slider.valueChanged.connect(self.set_volume) ctl_layout.addWidget(self.volume_slider) self.splitter.addWidget(right_panel) self.splitter.setSizes([300, 980]) self.setCentralWidget(self.splitter) def set_volume(self, value): self.player.media_player.audio_set_volume(value) def update_overlay_position(self): if not self.overlay_window or not self.video_frame: return vf_geom = self.video_frame.geometry() global_pos = self.video_frame.mapToGlobal(vf_geom.topLeft()) self.overlay_window.setGeometry(global_pos.x(), global_pos.y(), vf_geom.width(), vf_geom.height()) self.overlay_window.raise_() def resizeEvent(self, event): super().resizeEvent(event) self.update_overlay_position() def eventFilter(self, source, event): if event.type() == QEvent.MouseButtonDblClick: self.toggle_fullscreen() return True return super().eventFilter(source, event) def on_channel_clicked(self, item: QListWidgetItem): url = item.data(Qt.UserRole) self.player.play_stream(url) def update(attempt=0): w = self.player.media_player.video_get_width() h = self.player.media_player.video_get_height() fps = self.player.media_player.get_fps() if w > 0 and h > 0: label = "SD" if w >= 3840: label = "4K" elif w >= 1920: label = "FHD" elif w >= 1280: label = "HD" info = f"{label}, {fps:.2f}FPS" base = item.text().split('(')[0].strip() item.setText(f"{base} ({info})") self.overlay_window.update_info(base, info) QTimer.singleShot(100, self.update_overlay_position) elif attempt < 10: QTimer.singleShot(500, lambda: update(attempt+1)) QTimer.singleShot(1000, lambda: update(0)) def toggle_play_pause(self): mp = self.player.media_player if mp.is_playing(): mp.pause() else: mp.play() def play_next_channel(self): idx = self.channel_list.currentRow() if idx < self.channel_list.count() - 1: self.channel_list.setCurrentRow(idx+1) self.on_channel_clicked(self.channel_list.currentItem()) def play_previous_channel(self): idx = self.channel_list.currentRow() if idx > 0: self.channel_list.setCurrentRow(idx-1) self.on_channel_clicked(self.channel_list.currentItem()) def toggle_fullscreen(self): if not self._is_fullscreen: self._normal_geometry = self.geometry() self._normal_flags = self.windowFlags() self.channel_list.hide() self.epg_display.hide() self.controls.hide() self.setWindowFlags(self._normal_flags | Qt.FramelessWindowHint) self.showFullScreen() else: self.setWindowFlags(self._normal_flags) self.showNormal() if self._normal_geometry: self.setGeometry(self._normal_geometry) self.channel_list.show() self.epg_display.show() self.controls.show() self._is_fullscreen = not self._is_fullscreen self.update_overlay_position() 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" content = 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(content) self.load_playlist(path) except Exception as e: print(f"Failed to download playlist: {e}") def load_playlist(self, path): try: lines = open(path, encoding="utf-8").read().splitlines() self.channel_list.clear() first = None name = None for l in lines: 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 first is None: first = it name = None if first: self.channel_list.setCurrentItem(first) QTimer.singleShot(1000, lambda: self.on_channel_clicked(first)) except Exception as e: print(f"Failed to load playlist: {e}") def start_php_server(php_dir, port=8888): try: os.chdir(php_dir) 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__": php_dir = os.path.join(os.path.dirname(__file__), "php_files") os.makedirs(php_dir, exist_ok=True) php_proc = start_php_server(php_dir) app = QApplication(sys.argv) window = IPTVWindow(php_dir) window.show() exit_code = app.exec_() window.player.stop() window.player.release() if php_proc: php_proc.terminate(); php_proc.wait(2) sys.exit(exit_code)