Najeebsk

M3U-PLAYER3.0.pyw

Dec 29th, 2025 (edited)
278
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 16.91 KB | None | 0 0
  1. import tkinter as tk
  2. from tkinter import ttk, filedialog, simpledialog, messagebox
  3. import vlc
  4. import os, sys, json
  5. from urllib.parse import unquote
  6. from datetime import datetime
  7. import requests
  8. #================ADD-IMAGE-ICON=================
  9. import sys
  10.  
  11. def resource_path(relative_path):
  12.     """ Get the absolute path to the resource, works for PyInstaller. """
  13.     if getattr(sys, '_MEIPASS', False):
  14.         return os.path.join(sys._MEIPASS, relative_path)
  15.     return os.path.join(os.path.abspath("."), relative_path)
  16.  
  17. # Use this function to load files:
  18. splash_image = resource_path("THEME/splash-7.png")
  19. icon_path = resource_path("THEME/P.ico")
  20. #================ADD-IMAGE-ICON=================
  21. CONFIG_FILE = "config.json"
  22. VLC_EXE = r"C:\Program Files\VideoLAN\VLC\vlc.exe"
  23. #================ADD-SPLASH-IMAGE=================
  24. import tkinter as tk
  25. from PIL import Image, ImageTk
  26. import time
  27. def show_splash(image_path):
  28.     # Create splash screen window
  29.     splash = tk.Tk()
  30.     splash.overrideredirect(True)  # Remove window border
  31.    
  32.     # Load the image
  33.     image = Image.open(image_path)
  34.     img = ImageTk.PhotoImage(image)
  35.  
  36.     # Get image dimensions
  37.     img_width, img_height = image.size
  38.  
  39.     # Calculate position to center the splash screen
  40.     screen_width = splash.winfo_screenwidth()
  41.     screen_height = splash.winfo_screenheight()
  42.     x = (screen_width - img_width) // 2
  43.     y = (screen_height - img_height) // 2
  44.     splash.geometry(f"{img_width}x{img_height}+{x}+{y}")
  45.  
  46.     # Set transparent background
  47.     splash.config(bg="white")
  48.     splash.attributes("-transparentcolor", "white")  # Make white color transparent
  49.  
  50.     # Display the image
  51.     label = tk.Label(splash, image=img, bg="white")
  52.     label.pack()
  53.  
  54.     # Display the splash screen for 5 seconds
  55.     splash.after(5000, splash.destroy)
  56.     splash.mainloop()
  57.  
  58.  
  59. # Call splash screen function
  60. show_splash("THEME/splash-7.png")  # Replace with your image file path
  61. # ==================================================
  62.  
  63. class IPTVPlayerV3:
  64.     def __init__(self, root):
  65.         self.root = root
  66.         self.root.title("Najeeb Pro-Level IPTV M3U Player v3 ")
  67.         self.root.geometry("1200x720")
  68.         self.root.minsize(1000, 600)
  69.         self.root.iconbitmap(icon_path)
  70.         root.tk.call("source", r"THEME\azure.tcl")
  71.         root.tk.call("set_theme", "dark")
  72.  
  73.         # ================= DATA =================
  74.         self.playlists = [[]]
  75.         self.titles = [[]]
  76.         self.filtered_indices = []
  77.         self.is_playing = False
  78.         self.in_fullscreen = False
  79.  
  80.         # ================= CONFIG =================
  81.         self.config = self.load_config()
  82.         self.dark_mode = self.config.get("dark_mode", False)
  83.         self.volume = self.config.get("volume", 80)
  84.         self.last_file = self.config.get("last_file", "")
  85.  
  86.         # ================= UI =================
  87.         self.create_widgets()
  88.         self.apply_theme()
  89.         self.search_var.trace_add("write", self.filter_playlist)
  90.  
  91.         # ================= VLC =================
  92.         self.instance = vlc.Instance("--quiet")
  93.         self.player = self.instance.media_player_new()
  94.         self.root.after(500, self.embed_vlc)
  95.  
  96.         if self.last_file and os.path.isfile(self.last_file):
  97.             self.load_m3u_file(self.last_file)
  98.  
  99.         self.setup_shortcuts()
  100.         self.update_seek()
  101.  
  102.     # =====================================================
  103.     # VLC EMBED
  104.     # =====================================================
  105.     def embed_vlc(self):
  106.         self.video_frame.update_idletasks()
  107.         hwnd = self.video_frame.winfo_id()
  108.         if sys.platform.startswith("win"):
  109.             self.player.set_hwnd(hwnd)
  110.         elif sys.platform == "darwin":
  111.             self.player.set_nsobject(hwnd)
  112.         else:
  113.             self.player.set_xwindow(hwnd)
  114.  
  115.     # =====================================================
  116.         # ================= BUSY OVERLAY =================
  117.     def show_busy(self, text="Loading..."):
  118.         self.busy_frame = tk.Frame(self.video_frame, bg="black")
  119.         self.busy_frame.place(relx=0, rely=0, relwidth=1, relheight=1)
  120.         tk.Label(
  121.             self.busy_frame,
  122.             text=text,
  123.             fg="white",
  124.             bg="black",
  125.             font=("Segoe UI", 16, "bold")
  126.         ).pack(expand=True)
  127.         self.root.update()
  128.  
  129.     def hide_busy(self):
  130.         if hasattr(self, "busy_frame"):
  131.             self.busy_frame.destroy()
  132.     # UI
  133.     # =====================================================
  134.     def create_widgets(self):
  135.         # LEFT
  136.         self.left_frame = tk.Frame(self.root, width=300)
  137.         self.left_frame.pack(side=tk.LEFT, fill=tk.Y)
  138.         self.left_frame.pack_propagate(False)
  139.  
  140.         self.search_var = tk.StringVar()
  141.         self.search_entry = tk.Entry(self.left_frame, textvariable=self.search_var)
  142.         self.search_entry.pack(fill=tk.X, padx=5, pady=5)
  143.         self.search_entry.insert(0, "🔍 Search...")
  144.         self.search_entry.bind("<FocusIn>", self.on_search_focus_in)
  145.         self.search_entry.bind("<FocusOut>", self.on_search_focus_out)
  146.  
  147.         self.tab_control = ttk.Notebook(self.left_frame)
  148.         self.tab_control.pack(fill=tk.BOTH, expand=True, padx=5)
  149.  
  150.         self.playlist_boxes = []
  151.         self.add_playlist_tab("Playlist 1")
  152.  
  153.         btns = tk.Frame(self.left_frame)
  154.         btns.pack(fill=tk.X, pady=5)
  155.  
  156.         for text, cmd in [
  157.             ("Open M3U", self.load_m3u),
  158.             ("➕ URL", self.add_item),
  159.             ("➕ Tab", self.new_tab),
  160.             ("Dark", self.toggle_theme),
  161.             ("Save", self.save_m3u),
  162.         ]:
  163.             tk.Button(btns, text=text, command=cmd).pack(
  164.                 side=tk.LEFT, expand=True, fill=tk.X, padx=1
  165.             )
  166.  
  167.         # RIGHT
  168.         self.right_frame = tk.Frame(self.root)
  169.         self.right_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True)
  170.  
  171.         self.video_container = tk.Frame(self.right_frame, bg="black")
  172.         self.video_container.pack(fill=tk.BOTH, expand=True)
  173.  
  174.         self.video_frame = tk.Frame(self.video_container, bg="black")
  175.         self.video_frame.pack(fill=tk.BOTH, expand=True)
  176.  
  177.         controls = tk.Frame(self.right_frame)
  178.         controls.pack(fill=tk.X)
  179.  
  180.         tk.Button(controls, text="▶ Play", command=self.play).pack(side=tk.LEFT)
  181.         tk.Button(controls, text="⏸ Pause", command=self.pause).pack(side=tk.LEFT)
  182.         tk.Button(controls, text="⏹ Stop", command=self.stop).pack(side=tk.LEFT)
  183.  
  184.         self.fs_btn = tk.Button(
  185.             controls, text="➕ Fullscreen", command=self.toggle_fullscreen
  186.         )
  187.         self.fs_btn.pack(side=tk.RIGHT)
  188.  
  189.         tk.Label(controls, text="Vol").pack(side=tk.RIGHT)
  190.         self.vol_slider = tk.Scale(
  191.             controls, from_=0, to=100, orient=tk.HORIZONTAL,
  192.             command=self.set_volume, showvalue=0, length=120
  193.         )
  194.         self.vol_slider.set(self.volume)
  195.         self.vol_slider.pack(side=tk.RIGHT)
  196.  
  197.         self.seek_slider = tk.Scale(
  198.             self.right_frame, from_=0, to=100,
  199.             orient=tk.HORIZONTAL, showvalue=0, command=self.seek
  200.         )
  201.         self.seek_slider.pack(fill=tk.X)
  202.  
  203.         self.vlc_menu = tk.Menu(self.root, tearoff=0)
  204.         self.vlc_menu.add_command(label="📸 Snapshot", command=self.snapshot)
  205.         self.vlc_menu.add_command(label="📺 Open in VLC", command=self.open_in_vlc)
  206.         self.video_frame.bind("<Button-3>", self.show_vlc_menu)
  207.  
  208.     # =====================================================
  209.     # PLAYLIST TAB + SCROLLBAR (FIXED)
  210.     # =====================================================
  211.     def add_playlist_tab(self, name):
  212.         frame = tk.Frame(self.tab_control)
  213.         self.tab_control.add(frame, text=name)
  214.  
  215.         container = tk.Frame(frame)
  216.         container.pack(fill=tk.BOTH, expand=True)
  217.  
  218.         sb = ttk.Scrollbar(container, orient="vertical")
  219.         sb.pack(side=tk.RIGHT, fill=tk.Y)
  220.  
  221.         lb = tk.Listbox(container, yscrollcommand=sb.set, exportselection=False)
  222.         lb.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
  223.  
  224.         sb.config(command=lb.yview)
  225.         lb.bind("<Double-1>", lambda e: self.on_double_click())
  226.  
  227.         self.playlist_boxes.append(lb)
  228.  
  229.     def new_tab(self):
  230.         self.playlists.append([])
  231.         self.titles.append([])
  232.         self.add_playlist_tab(f"Playlist {len(self.playlists)}")
  233.  
  234.     def current_listbox(self):
  235.         return self.playlist_boxes[self.tab_control.index("current")]
  236.  
  237.     def current_playlist_index(self):
  238.         return self.tab_control.index("current")
  239.  
  240.     # =====================================================
  241.     # PLAYBACK
  242.     # =====================================================
  243.     def on_double_click(self):
  244.         sel = self.current_listbox().curselection()
  245.         if sel:
  246.             self.play_selected(sel[0])
  247.     def play_selected(self, idx):
  248.         tab = self.current_playlist_index()
  249.         real = self.filtered_indices[idx] if self.filtered_indices else idx
  250.         url = self.playlists[tab][real]
  251.  
  252.         self.show_busy("Buffering Stream Najeeb...")
  253.         media = self.instance.media_new(url)
  254.         self.player.set_media(media)
  255.         self.player.play()
  256.         self.is_playing = True
  257.         self.root.after(6000, self.hide_busy)
  258.     #def play_selected(self, idx):
  259.         #tab = self.current_playlist_index()
  260.         #real = self.filtered_indices[idx] if self.filtered_indices else idx
  261.         #media = self.instance.media_new(self.playlists[tab][real])
  262.         #self.player.set_media(media)
  263.         #self.player.play()
  264.         #self.is_playing = True
  265.  
  266.     def play(self): self.player.play(); self.is_playing = True
  267.     def pause(self): self.player.pause()
  268.     def stop(self): self.player.stop(); self.is_playing = False
  269.  
  270.     def set_volume(self, v):
  271.         self.player.audio_set_volume(int(v))
  272.         self.config["volume"] = int(v)
  273.         self.save_config()
  274.  
  275.     def seek(self, v):
  276.         if self.player.get_length() > 0:
  277.             self.player.set_time(int(self.player.get_length() * float(v) / 100))
  278.  
  279.     def update_seek(self):
  280.         if self.is_playing and self.player.get_length() > 0:
  281.             self.seek_slider.set(
  282.                 (self.player.get_time() / self.player.get_length()) * 100
  283.             )
  284.         self.root.after(500, self.update_seek)
  285.  
  286.     # =====================================================
  287.     # M3U
  288.     # =====================================================
  289.     def load_m3u(self):
  290.         path = filedialog.askopenfilename(filetypes=[("M3U", "*.m3u *.m3u8")])
  291.         if path:
  292.             self.load_m3u_file(path)
  293.  
  294.     def load_m3u_file(self, path):
  295.         idx = self.current_playlist_index()
  296.         lb = self.current_listbox()
  297.         lb.delete(0, tk.END)
  298.         self.playlists[idx].clear()
  299.         self.titles[idx].clear()
  300.  
  301.         with open(path, "r", encoding="utf-8", errors="ignore") as f:
  302.             lines = f.read().splitlines()
  303.  
  304.         i = 0
  305.         while i < len(lines):
  306.             if lines[i].startswith("#EXTINF"):
  307.                 title = lines[i].split(",", 1)[-1]
  308.                 i += 1
  309.                 self.playlists[idx].append(lines[i])
  310.                 self.titles[idx].append(title)
  311.                 lb.insert(tk.END, title)
  312.             i += 1
  313.  
  314.         self.config["last_file"] = path
  315.         self.save_config()
  316.  
  317.     def add_item(self):
  318.         url = simpledialog.askstring("M3U URL", "Enter M3U URL")
  319.         if not url:
  320.             return
  321.         r = requests.get(url, timeout=15)
  322.         lines = r.text.splitlines()
  323.  
  324.         idx = self.current_playlist_index()
  325.         lb = self.current_listbox()
  326.         lb.delete(0, tk.END)
  327.         self.playlists[idx].clear()
  328.         self.titles[idx].clear()
  329.  
  330.         i = 0
  331.         while i < len(lines):
  332.             if lines[i].startswith("#EXTINF"):
  333.                 title = lines[i].split(",", 1)[-1]
  334.                 i += 1
  335.                 self.playlists[idx].append(lines[i])
  336.                 self.titles[idx].append(title)
  337.                 lb.insert(tk.END, title)
  338.             i += 1
  339.  
  340.     def save_m3u(self):
  341.         idx = self.current_playlist_index()
  342.         path = filedialog.asksaveasfilename(defaultextension=".m3u")
  343.         if not path:
  344.             return
  345.         with open(path, "w", encoding="utf-8") as f:
  346.             f.write("#EXTM3U\n")
  347.             for t, u in zip(self.titles[idx], self.playlists[idx]):
  348.                 f.write(f"#EXTINF:-1,{t}\n{u}\n")
  349.  
  350.     # =====================================================
  351.     # SEARCH
  352.     # =====================================================
  353.     def filter_playlist(self, *a):
  354.         q = self.search_var.get().lower()
  355.         lb = self.current_listbox()
  356.         lb.delete(0, tk.END)
  357.         self.filtered_indices.clear()
  358.         idx = self.current_playlist_index()
  359.         for i, t in enumerate(self.titles[idx]):
  360.             if q in t.lower():
  361.                 lb.insert(tk.END, t)
  362.                 self.filtered_indices.append(i)
  363.  
  364.     def on_search_focus_in(self, e):
  365.         if self.search_entry.get().startswith("🔍"):
  366.             self.search_entry.delete(0, tk.END)
  367.  
  368.     def on_search_focus_out(self, e):
  369.         if not self.search_var.get():
  370.             self.search_entry.insert(0, "🔍 Search...")
  371.  
  372.     # =====================================================
  373.     # THEME (FIXED)
  374.     # =====================================================
  375.     def toggle_theme(self):
  376.         self.dark_mode = not self.dark_mode
  377.         self.config["dark_mode"] = self.dark_mode
  378.         self.apply_theme()
  379.         self.save_config()
  380.  
  381.     def apply_theme(self):
  382.         bg = "#1e1e1e" if self.dark_mode else "#f5f5f5"
  383.         fg = "#ffffff" if self.dark_mode else "#000000"
  384.  
  385.         self.root.configure(bg=bg)
  386.         self.left_frame.configure(bg=bg)
  387.         self.right_frame.configure(bg=bg)
  388.         self.search_entry.configure(bg=bg, fg=fg, insertbackground=fg)
  389.  
  390.         for lb in self.playlist_boxes:
  391.             lb.configure(
  392.                 bg="#252526" if self.dark_mode else "#ffffff",
  393.                 fg=fg,
  394.                 selectbackground="#3e3e42" if self.dark_mode else "#0078d7",
  395.                 selectforeground="white"
  396.             )
  397.  
  398.     # =====================================================
  399.     # MENU / SHORTCUTS
  400.     # =====================================================
  401.     def show_vlc_menu(self, e):
  402.         self.vlc_menu.tk_popup(e.x_root, e.y_root)
  403.  
  404.     def snapshot(self):
  405.         name = f"snapshot_{datetime.now().strftime('%Y%m%d_%H%M%S')}.png"
  406.         self.player.video_take_snapshot(0, name, 0, 0)
  407.         messagebox.showinfo("Saved", name)
  408.  
  409.     def open_in_vlc(self):
  410.         media = self.player.get_media()
  411.         if media and os.path.exists(VLC_EXE):
  412.             os.spawnl(os.P_NOWAIT, VLC_EXE, VLC_EXE, media.get_mrl())
  413.            
  414.     # === SHORTCUTS ===
  415.     # =================
  416.     def setup_shortcuts(self):
  417.         r = self.root
  418.         r.bind("<space>", lambda e: self.pause() if self.is_playing else self.play())
  419.         r.bind("<Return>", lambda e: self.on_double_click())
  420.         r.bind("<F11>", lambda e: self.toggle_fullscreen())
  421.         r.bind("<Escape>", lambda e: self.exit_fullscreen())
  422.         r.bind('<Delete>', lambda e: self.remove_selected())
  423.         r.bind('<Control-o>', lambda e: self.load_m3u())
  424.         r.bind('<Control-n>', lambda e: self.new_tab())
  425.         #r.bind('<Up>', self.navigate_list)
  426.         #r.bind('<Down>', self.navigate_list)
  427.         # New shortcuts for snapshot and open in VLC
  428.         r.bind('<Control-s>', lambda e: self.snapshot())
  429.         r.bind('<Control-v>', lambda e: self.open_in_vlc())
  430.  
  431.  
  432.     # =====================================================
  433.     # FULLSCREEN
  434.     # =====================================================
  435.     def toggle_fullscreen(self):
  436.         self.in_fullscreen = not self.in_fullscreen
  437.         if self.in_fullscreen:
  438.             self.left_frame.pack_forget()
  439.             self.fs_btn.config(text="➖ Exit Fullscreen")
  440.         else:
  441.             self.left_frame.pack(side=tk.LEFT, fill=tk.Y)
  442.             self.fs_btn.config(text="➕ Fullscreen")
  443.  
  444.     def exit_fullscreen(self):
  445.         if self.in_fullscreen:
  446.             self.toggle_fullscreen()
  447.  
  448.     # =====================================================
  449.     # CONFIG
  450.     # =====================================================
  451.     def load_config(self):
  452.         if os.path.exists(CONFIG_FILE):
  453.             try:
  454.                 with open(CONFIG_FILE, "r", encoding="utf-8") as f:
  455.                     return json.load(f)
  456.             except:
  457.                 pass
  458.         return {}
  459.  
  460.     def save_config(self):
  461.         with open(CONFIG_FILE, "w", encoding="utf-8") as f:
  462.             json.dump(self.config, f, indent=2)
  463.  
  464.  
  465. # =====================================================
  466. # RUN
  467. # =====================================================
  468. if __name__ == "__main__":
  469.     root = tk.Tk()
  470.     IPTVPlayerV3(root)
  471.     root.mainloop()
  472.  
Add Comment
Please, Sign In to add comment