💜 Build a Modern Python Video Player (Purple-Glass GUI)
This tutorial will guide you to create a feature-rich desktop video player in Python using CustomTkinter and python-vlc. You’ll build a stylish purple-glass themed player with playlist, mini-player, PiP, speed control, and more.
🌟 Step 1 — Installing Python Tools & Libraries You Will Need
Before coding, make sure you have:
- Python 3.10+
- VLC Media Player installed
Install Python packages:
pip install customtkinter pillow python-vlc
Optional (for drag-and-drop):
pip install tkdnd2
🏗 Step 2 — Understand the App Architecture
- GUI Layer: Window layout, playlist, buttons, sliders, hover effects
- Video Engine (VLC): Handles playback, speed, screenshots, PiP
- Background Thread: Updates seek bar and time, auto-next playback
- Handling User Actions: 🖱️Drag-Drop, Double-Clicks, and Keyboard Tricks
🎨 Step 3 — Purple-Glass UI Design
Main colors:
- Deep purple:
#2A1F3A - Glass overlay:
#261C33 - Accent neon:
#CFA8FF
Layout:
- Playlist panel (left)
- Video frame (center)
- Control bar (bottom) with buttons, sliders, info label
🎬 Step 4 — Core Features Explained
- Playlist: Add/remove/reorder videos, auto-next playback
- Drag & Drop: Drop files to play instantly
- Mini-Player: Compact floating window
- PiP Mode: Small topmost window playing same video
- Speed Control: 0.25x – 3x playback
- Screenshot Capture: Saves frames to
screenshots/ - Keyboard Shortcuts:
Key Function Space Play/Pause F Fullscreen Esc Exit fullscreen ← / → Seek ±5s ↑ / ↓ Volume up/down - Animated Theme: Subtle purple-glass color cycling
💻 Step 5 — How the Code Works
The app uses:
- CTkTextbox for playlist
- VLC engine to play videos inside the Tkinter frame
- Background thread for UI updates and auto-next
- Separate windows for PiP and mini-player
🏁 Step 6 — Run the Application
python video_player_all_features.py
You will see a purple-glass themed player with all features ready.
📦 Step 7 — Package as an EXE (Optional)
pip install pyinstaller
pyinstaller --noconsole --onefile video_player_all_features.py
EXE will be in dist/ folder.
🌈 Step 8 — Next Steps / Enhancements
- Subtitle support
- Playlist save/load
- Drag-and-drop reordering
- Custom themes and colors
- Audio visualizers or video filters
🌟Full Code for Python Video Player
Copy the following code into vide.py:
"""
video_player_all_features.py
Feature-rich Python GUI Video Player (Purple-Glass theme) using CustomTkinter + python-vlc.
"""
import os
import sys
import time
import threading
import platform
from pathlib import Path
from tkinter import filedialog, messagebox
import customtkinter as ctk
import vlc
from PIL import Image # pillow used for potential further image ops
# Attempt to import tkdnd for drag & drop. If not available, we'll still work.
try:
import tkdnd
TKDND_AVAILABLE = True
except Exception:
TKDND_AVAILABLE = False
# ---- Purple-Glass theme colors (single-file, no external JSON) ----
PURPLE = "#8C52FF"
PURPLE_LIGHT = "#A874FF"
DEEP_PURPLE = "#2A1F3A"
DARK_PURPLE = "#1E142B"
GLASS_BG = "#261C33"
TEXT_ON_DARK = "#ECECEC"
ACCENT = "#CFA8FF"
# ---- Appearance setup ----
ctk.set_appearance_mode("dark")
# Do NOT call set_default_color_theme with "purple" (not built-in).
# We'll style widgets explicitly to produce the purple-glass look.
APP_TITLE = "🎬 Purple-Glass Video Player"
# ---- Helper functions ----
def resource_path(rel: str) -> str:
# for frozen apps; otherwise returns rel
base = getattr(sys, "_MEIPASS", os.path.abspath("."))
return os.path.join(base, rel)
def human_time(ms: int) -> str:
if ms <= 0:
return "00:00"
s = ms // 1000
h, s = divmod(s, 3600)
m, s = divmod(s, 60)
if h:
return f"{h:02d}:{m:02d}:{s:02d}"
return f"{m:02d}:{s:02d}"
# ---- Main Application ----
class VideoPlayer(ctk.CTk):
def __init__(self):
super().__init__()
self.title(APP_TITLE)
self.geometry("1100x680")
self.minsize(860, 520)
# Apply window background color
self.configure(fg_color=DEEP_PURPLE)
# VLC player setup
# On Windows, try to add default VLC path so libvlc.dll is found (common location)
if platform.system() == "Windows":
default_vlc = r"C:\Program Files\VideoLAN\VLC"
if os.path.isdir(default_vlc):
try:
os.add_dll_directory(default_vlc)
except Exception:
pass
self.vlc_instance = vlc.Instance()
self.player = self.vlc_instance.media_player_new()
self.pip_player = None # separate VLC player for PiP, created on demand
# Playback state
self.current_index = None
self.playlist = [] # list of filepaths
self._is_fullscreen = False
self._pip_on = False
self._mini_mode = False
self._animated = True
# UI variables
self._playback_speed = ctk.DoubleVar(value=1.0)
self._volume = ctk.IntVar(value=60)
self._position = ctk.DoubleVar(value=0.0)
self._duration_ms = 0
# Build UI
self._build_ui()
# Start background update thread
self._running = True
threading.Thread(target=self._update_thread, daemon=True).start()
# Animated theme cycle (colors still purple-glass variants)
self._theme_index = 0
self._theme_colors = [GLASS_BG, "#2C1A3E", "#341944", "#3D1846"]
self._cycle_theme()
# keyboard shortcuts
self.bind("", lambda e: self.toggle_play())
self.bind("", lambda e: self.toggle_fullscreen())
self.bind("", lambda e: self.exit_fullscreen())
self.bind("", lambda e: self.seek_relative(-5000))
self.bind("", lambda e: self.seek_relative(5000))
self.protocol("WM_DELETE_WINDOW", self._on_close)
# Try enabling drag & drop to playlist area if tkdnd present
if TKDND_AVAILABLE:
try:
dnd = tkdnd.TkDND(self)
dnd.bindtarget(self.playlist_box, 'text/uri-list', '', self._on_drop)
except Exception:
pass
# ---- UI construction ----
def _build_ui(self):
# Main layout: top video area, bottom controls + left playlist
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(1, weight=1)
# Playlist frame (left)
left_frame = ctk.CTkFrame(self, corner_radius=12, fg_color=GLASS_BG)
left_frame.grid(row=0, column=0, sticky="nsw", padx=(12, 6), pady=12)
left_frame.grid_rowconfigure(1, weight=1)
ctk.CTkLabel(left_frame, text="Playlist", anchor="w", font=ctk.CTkFont(size=14, weight="bold"), text_color=TEXT_ON_DARK, fg_color=GLASS_BG).grid(row=0, column=0, sticky="ew", padx=8, pady=(6, 4))
self.playlist_box = ctk.CTkTextbox(left_frame, width=260, height=420, state="disabled", fg_color="#2b1530", text_color=TEXT_ON_DARK, corner_radius=8)
self.playlist_box.grid(row=1, column=0, padx=8, pady=6, sticky="nsew")
# Provide simple playlist controls
pl_controls = ctk.CTkFrame(left_frame, fg_color="transparent")
pl_controls.grid(row=2, column=0, padx=8, pady=6, sticky="ew")
add_btn = ctk.CTkButton(pl_controls, text="Add", command=self.add_files, width=1, fg_color=PURPLE, hover_color=PURPLE_LIGHT, text_color="white")
add_btn.pack(side="left", padx=6)
remove_btn = ctk.CTkButton(pl_controls, text="Remove", command=self.remove_selected, width=1, fg_color=PURPLE, hover_color=PURPLE_LIGHT, text_color="white")
remove_btn.pack(side="left", padx=6)
up_btn = ctk.CTkButton(pl_controls, text="Up", command=self.move_up, width=1, fg_color=PURPLE, hover_color=PURPLE_LIGHT, text_color="white")
up_btn.pack(side="left", padx=6)
down_btn = ctk.CTkButton(pl_controls, text="Down", command=self.move_down, width=1, fg_color=PURPLE, hover_color=PURPLE_LIGHT, text_color="white")
down_btn.pack(side="left", padx=6)
clear_btn = ctk.CTkButton(pl_controls, text="Clear", command=self.clear_playlist, width=1, fg_color=PURPLE, hover_color=PURPLE_LIGHT, text_color="white")
clear_btn.pack(side="left", padx=6)
# Video area (center)
self.video_frame = ctk.CTkFrame(self, corner_radius=12, fg_color=GLASS_BG)
self.video_frame.grid(row=0, column=1, sticky="nsew", padx=(6, 12), pady=12)
self.video_frame.grid_rowconfigure(0, weight=1)
self.video_frame.grid_columnconfigure(0, weight=1)
# embed area where VLC renders video
self.embed = ctk.CTkFrame(self.video_frame, height=420, corner_radius=10, fg_color="#201026")
self.embed.grid(row=0, column=0, sticky="nsew", padx=12, pady=12)
# Info overlay
self.info_label = ctk.CTkLabel(self.embed, text="Drop files here or click Add", anchor="center", text_color=ACCENT, fg_color="#201026")
self.info_label.place(relx=0.5, rely=0.5, anchor="center")
# Controls (below video)
controls = ctk.CTkFrame(self, corner_radius=12, fg_color=GLASS_BG)
controls.grid(row=1, column=0, columnspan=2, sticky="ew", padx=12, pady=(0,12))
controls.grid_columnconfigure(4, weight=1)
# Left control group
left_controls = ctk.CTkFrame(controls, fg_color="transparent")
left_controls.grid(row=0, column=0, sticky="w", padx=8, pady=8)
self.play_btn = ctk.CTkButton(left_controls, text="Play", command=self.toggle_play, width=80, fg_color=PURPLE, hover_color=PURPLE_LIGHT, text_color="white")
self._add_hover(self.play_btn)
self.play_btn.pack(side="left", padx=6)
ctk.CTkButton(left_controls, text="Stop", command=self.stop, width=80, fg_color=PURPLE, hover_color=PURPLE_LIGHT, text_color="white").pack(side="left", padx=6)
ctk.CTkButton(left_controls, text="Prev", command=self.prev_track, width=70, fg_color=PURPLE, hover_color=PURPLE_LIGHT, text_color="white").pack(side="left", padx=6)
ctk.CTkButton(left_controls, text="Next", command=self.next_track, width=70, fg_color=PURPLE, hover_color=PURPLE_LIGHT, text_color="white").pack(side="left", padx=6)
# Center controls: seek and time
center_ctrl = ctk.CTkFrame(controls, fg_color="transparent")
center_ctrl.grid(row=0, column=1, sticky="ew", padx=8, pady=8, columnspan=1)
self.time_label = ctk.CTkLabel(center_ctrl, text="00:00 / 00:00", text_color=TEXT_ON_DARK)
self.time_label.pack(side="left", padx=(6,10))
self.seek = ctk.CTkSlider(center_ctrl, from_=0, to=100, number_of_steps=100, variable=self._position, command=self._on_seek, progress_color=PURPLE, button_color=PURPLE_LIGHT)
self.seek.pack(side="left", fill="x", expand=True, padx=6)
# Right controls: volume, speed, PiP, screenshot, mini, theme
right_controls = ctk.CTkFrame(controls, fg_color="transparent")
right_controls.grid(row=0, column=2, sticky="e", padx=8, pady=8)
ctk.CTkLabel(right_controls, text="Vol", text_color=TEXT_ON_DARK).pack(side="left", padx=(6,2))
vol_slider = ctk.CTkSlider(right_controls, from_=0, to=100, variable=self._volume, command=self._on_volume, progress_color=PURPLE, button_color=PURPLE_LIGHT, width=120)
vol_slider.pack(side="left", padx=6)
ctk.CTkLabel(right_controls, text="Speed", text_color=TEXT_ON_DARK).pack(side="left", padx=(12,2))
speed = ctk.CTkSlider(right_controls, from_=0.25, to=3.0, number_of_steps=55, variable=self._playback_speed, command=self._on_speed, progress_color=PURPLE, button_color=PURPLE_LIGHT, width=140)
speed.pack(side="left", padx=6)
pip_btn = ctk.CTkButton(right_controls, text="PiP", command=self.toggle_pip, fg_color=PURPLE, hover_color=PURPLE_LIGHT, text_color="white")
pip_btn.pack(side="left", padx=6)
self._add_hover(pip_btn)
ctk.CTkButton(right_controls, text="Screenshot", command=self.screenshot, fg_color=PURPLE, hover_color=PURPLE_LIGHT, text_color="white").pack(side="left", padx=6)
mini_btn = ctk.CTkButton(right_controls, text="Mini", command=self.toggle_mini, fg_color=PURPLE, hover_color=PURPLE_LIGHT, text_color="white")
mini_btn.pack(side="left", padx=6)
theme_btn = ctk.CTkButton(right_controls, text="Theme", command=self.toggle_animated_theme, fg_color=PURPLE, hover_color=PURPLE_LIGHT, text_color="white")
theme_btn.pack(side="left", padx=6)
# Bottom small area: playlist management quick info
bottom_frame = ctk.CTkFrame(self, corner_radius=8, fg_color=GLASS_BG)
bottom_frame.grid(row=2, column=0, columnspan=2, sticky="ew", padx=12, pady=(0,12))
self.status_label = ctk.CTkLabel(bottom_frame, text="Ready", anchor="w", text_color=TEXT_ON_DARK, fg_color=GLASS_BG)
self.status_label.pack(fill="x", padx=10, pady=6)
# Right-click bindings to playlist area use textbox selection index -> we'll simulate simple selection by storing an index when clicking lines
self.playlist_box.bind("", self._playlist_click)
self.playlist_box.bind("", self._playlist_double_click)
# initialize volume
try:
self.player.audio_set_volume(self._volume.get())
except Exception:
pass
# ---- UI/UX helpers ----
def _add_hover(self, widget):
def on_enter(e):
try:
widget.configure(opacity=0.9)
except Exception:
pass
def on_leave(e):
try:
widget.configure(opacity=1.0)
except Exception:
pass
widget.bind("", on_enter)
widget.bind("", on_leave)
def _cycle_theme(self):
if self._animated:
color = self._theme_colors[self._theme_index % len(self._theme_colors)]
try:
self.video_frame.configure(fg_color=color)
except Exception:
pass
# cycle slowly
self._theme_index += 1
self.after(2000, self._cycle_theme)
def toggle_animated_theme(self):
self._animated = not self._animated
self.status("Animated theme: " + ("On" if self._animated else "Off"))
# ---- Playlist management ----
def add_files(self):
files = filedialog.askopenfilenames(title="Select videos", filetypes=[("Video files", "*.mp4 *.mkv *.avi *.mov *.flv *.webm"), ("All files","*.*")])
if not files:
return
for f in files:
f = os.path.abspath(f)
self.playlist.append(f)
self._refresh_playlist_view()
if self.current_index is None:
self.current_index = 0
self.load_index(0)
def remove_selected(self):
index = self._get_textbox_index_at_click()
if index is None:
self.status("No selection to remove")
return
try:
del self.playlist[index]
if self.current_index == index:
self.stop()
self.current_index = None
elif self.current_index and self.current_index > index:
self.current_index -= 1
self._refresh_playlist_view()
except Exception:
pass
def move_up(self):
idx = self._get_textbox_index_at_click()
if idx is None or idx == 0:
return
self.playlist[idx-1], self.playlist[idx] = self.playlist[idx], self.playlist[idx-1]
if self.current_index == idx:
self.current_index -= 1
elif self.current_index == idx-1:
self.current_index += 1
self._refresh_playlist_view()
def move_down(self):
idx = self._get_textbox_index_at_click()
if idx is None or idx >= len(self.playlist)-1:
return
self.playlist[idx+1], self.playlist[idx] = self.playlist[idx], self.playlist[idx+1]
if self.current_index == idx:
self.current_index += 1
elif self.current_index == idx+1:
self.current_index -= 1
self._refresh_playlist_view()
def clear_playlist(self):
self.playlist.clear()
self.current_index = None
self._refresh_playlist_view()
self.stop()
def _refresh_playlist_view(self):
# simple clear + write with indices
self.playlist_box.configure(state="normal")
self.playlist_box.delete("1.0", "end")
for i, p in enumerate(self.playlist):
short = os.path.basename(p)
mark = "▶ " if i == self.current_index else " "
self.playlist_box.insert("end", f"{mark}{i+1}. {short}\n")
self.playlist_box.configure(state="disabled")
# helper: find line index clicked in playlist textbox
def _playlist_click(self, event=None):
# store last click index for selection-based operations
index = self.playlist_box.index(f"@{event.x},{event.y}")
line_no = int(str(index).split(".")[0]) - 1
if 0 <= line_no < len(self.playlist):
self._last_clicked_index = line_no
else:
self._last_clicked_index = None
def _get_textbox_index_at_click(self):
return getattr(self, "_last_clicked_index", None)
def _playlist_double_click(self, event=None):
self._playlist_click(event)
idx = self._get_textbox_index_at_click()
if idx is not None:
self.load_index(idx)
self.play()
def _on_drop(self, event):
# tkdnd returns a string of URIs
data = getattr(event, 'data', None)
if not data:
return
uris = data.split()
for u in uris:
if u.startswith("file://"):
path = u[7:]
else:
path = u
path = path.strip()
if os.path.exists(path):
self.playlist.append(path)
self._refresh_playlist_view()
if self.current_index is None and len(self.playlist) > 0:
self.load_index(0)
# ---- Loading & playback ----
def load_index(self, idx: int):
if idx < 0 or idx >= len(self.playlist):
return
self.current_index = idx
path = self.playlist[idx]
media = self.vlc_instance.media_new(path)
self.player.set_media(media)
self._set_video_hwnd(self.player, self.embed)
self.status(f"Loaded: {os.path.basename(path)}")
self._refresh_playlist_view()
def play(self):
if self.player.get_media() is None:
if self.playlist:
self.load_index(0)
else:
self.status("Playlist empty. Add files.")
return
self.player.play()
# set speed and volume
try:
self.player.audio_set_volume(self._volume.get())
self.player.set_rate(self._playback_speed.get())
except Exception:
pass
self.play_btn.configure(text="Pause")
def pause(self):
self.player.pause()
self.play_btn.configure(text="Play")
def stop(self):
try:
self.player.stop()
except Exception:
pass
self.play_btn.configure(text="Play")
self._position.set(0.0)
self.time_label.configure(text="00:00 / 00:00")
def toggle_play(self):
if self.player.is_playing():
self.pause()
else:
self.play()
def prev_track(self):
if self.current_index is None: return
prev = max(0, self.current_index - 1)
self.load_index(prev)
self.play()
def next_track(self):
if self.current_index is None: return
nxt = min(len(self.playlist)-1, self.current_index + 1)
if nxt == self.current_index:
# try repeat
self.player.stop()
self.player.play()
else:
self.load_index(nxt)
self.play()
# ---- Video output handling ----
def _set_video_hwnd(self, player_obj, tk_frame):
# platform-specific: get window id from tk_frame.winfo_id()
wid = tk_frame.winfo_id()
if platform.system() == "Windows":
player_obj.set_hwnd(wid)
elif platform.system() == "Darwin":
# macOS requires special handling using ctypes - but python-vlc on mac often handles automatically
player_obj.set_nsobject(wid)
else:
player_obj.set_xwindow(wid)
# ---- Seek & volume callbacks ----
def _on_seek(self, value):
# seek value is 0-100 percent
if self.player.get_media() and self.player.get_length() > 0:
length = self.player.get_length()
new_ms = int(float(value) / 100.0 * length)
self.player.set_time(new_ms)
def seek_relative(self, ms_delta):
if self.player.get_media() and self.player.get_length() > 0:
cur = self.player.get_time()
self.player.set_time(max(0, cur + ms_delta))
def _on_volume(self, value):
try:
self.player.audio_set_volume(int(value))
except Exception:
pass
def _on_speed(self, value):
# VLC rate changes may not work on all codecs; attempt and store
try:
self.player.set_rate(float(value))
self.status(f"Speed: {value:.2f}x")
except Exception:
self.status("Speed change not supported for this file.")
# ---- Screenshot (uses VLC snapshot) ----
def screenshot(self):
if self.player.get_media() is None:
self.status("No media to screenshot.")
return
out_dir = Path.cwd() / "screenshots"
out_dir.mkdir(exist_ok=True)
ts = int(time.time())
filename = str(out_dir / f"snap_{ts}.png")
# video_take_snapshot(renderer, num, filepath, width, height)
try:
# width/height 0 to keep source size
self.player.video_take_snapshot(0, filename, 0, 0)
self.status(f"Screenshot saved: {filename}")
except Exception as e:
self.status(f"Screenshot failed: {e}")
# ---- PiP (Picture in Picture) ----
def toggle_pip(self):
if not self._pip_on:
# create a new window and a separate VLC player that attaches to it
self.pip_win = ctk.CTkToplevel(self, fg_color=GLASS_BG)
self.pip_win.title("PiP")
self.pip_win.geometry("480x270")
self.pip_win.attributes("-topmost", True)
self.pip_win.protocol("WM_DELETE_WINDOW", self.toggle_pip)
pip_frame = ctk.CTkFrame(self.pip_win, corner_radius=10, fg_color="#201026")
pip_frame.pack(fill="both", expand=True, padx=6, pady=6)
# create new player instance
self.pip_player = self.vlc_instance.media_player_new()
# set same media
if self.player.get_media():
self.pip_player.set_media(self.player.get_media())
self._set_video_hwnd(self.pip_player, pip_frame)
# sync: set same time and play
try:
self.pip_player.play()
time.sleep(0.05)
self.pip_player.set_time(self.player.get_time())
except Exception:
pass
self._pip_on = True
self.status("PiP on")
else:
try:
self.pip_player.stop()
self.pip_win.destroy()
except Exception:
pass
self.pip_player = None
self._pip_on = False
self.status("PiP off")
# ---- Mini player mode (compact) ----
def toggle_mini(self):
if not self._mini_mode:
self._prev_geometry = self.geometry()
self._prev_widgets_state = {} # keep if needed
# reduce to a small compact window
self.geometry("420x240")
self._mini_mode = True
self.status("Mini mode on")
else:
if hasattr(self, "_prev_geometry"):
self.geometry(self._prev_geometry)
self._mini_mode = False
self.status("Mini mode off")
# ---- Background update thread ----
def _update_thread(self):
while self._running:
try:
if self.player and self.player.get_media():
length = self.player.get_length() # ms
if length > 0:
self._duration_ms = length
cur = self.player.get_time()
pct = (cur / length) * 100 if length > 0 else 0
# schedule UI update on main thread
self.after(0, lambda cur=cur, length=length, pct=pct: self._update_ui(cur, length, pct))
time.sleep(0.3)
except Exception:
time.sleep(0.5)
def _update_ui(self, cur, length, pct):
try:
self._position.set(pct)
self.time_label.configure(text=f"{human_time(cur)} / {human_time(length)}")
# auto next when finished
if length > 0 and (length - cur) < 900 and not self.player.is_playing():
# little pause before next to avoid false positives
# if near end, advance to next track
if self.current_index is not None and self.current_index < len(self.playlist)-1:
self.load_index(self.current_index + 1)
self.play()
except Exception:
pass
# ---- Fullscreen ----
def toggle_fullscreen(self):
self._is_fullscreen = not self._is_fullscreen
self.attributes("-fullscreen", self._is_fullscreen)
def exit_fullscreen(self):
if self._is_fullscreen:
self._is_fullscreen = False
self.attributes("-fullscreen", False)
# ---- Status bar ----
def status(self, text: str):
self.status_label.configure(text=text)
# ---- Close/cleanup ----
def _on_close(self):
self._running = False
try:
self.player.stop()
except Exception:
pass
try:
if self.pip_player:
self.pip_player.stop()
except Exception:
pass
self.destroy()
# ---- Run ----
if __name__ == "__main__":
app = VideoPlayer()
app.mainloop()
Screenshot:
❓ FAQ — Purple-Glass Python Video Player
Q1: Do I need VLC installed to run this player?
A: Yes. The python-vlc library requires the VLC Media Player installed on your system to handle video playback.
Q2: Can I run this on Mac or Linux?
A: Absolutely! The code is cross-platform. Some window handle assignments differ by OS, but the app is functional on Windows, macOS, and Linux.
Q3: How do I add videos to the playlist?
A: Click the Add button or drag-and-drop files (if tkdnd is installed) into the playlist panel.
Q4: Can I save my playlist?
A: Currently, the app does not auto-save playlists, but you can enhance it by using Python's pickle or json to store file paths.
Q5: How do I take a screenshot of the video?
A: Click the Screenshot button. The frame will be saved in a screenshots/ folder in your current working directory.
Q6: What is Mini-Player mode?
A: Mini-Player mode shrinks the app to a small, floating window, perfect for multitasking while watching a video.
Q7: How do I enable PiP mode?
A: Click the PiP button. A small topmost window opens and plays the same video in sync.
Q8: Can I adjust playback speed?
A: Yes! Use the speed slider (0.25x – 3x) to slow down or speed up video playback.
Q9: Why aren’t drag-and-drop files working?
A: Drag-and-drop requires the tkdnd module. Install it using pip install tkdnd2.
Q10: Can I turn off the animated theme?
A: Yes, click the Theme button to toggle animated color cycling on/off.
💻 Python Video Player Developer Worksheet
Name: ____________________ Date: ____________________
Complete the tasks below using your Python Video Player project. Write your answers in the spaces provided.
Part 1: Understanding the Code
- List all the main components of the video player (GUI, VLC engine, Playlist, etc.).
Answer: _______________________________________________ - What variable stores the current video index in the playlist?
Answer: _______________________________________________ - Explain what
_update_threaddoes in one sentence.
Answer: _______________________________________________
Part 2: Playlist & Event System
- Add a video to your playlist using code. Show the snippet.
# Your code here
- How would you remove a video from the playlist programmatically?
Answer: _______________________________________________ - What happens when you double-click a playlist item?
Answer: _______________________________________________
Part 3: Mini Player & PiP
- Write a code snippet to toggle mini-player mode.
# Your code here
- How does the PiP window synchronize with the main video player?
Answer: _______________________________________________
Part 4: Playback & Controls
- How do you change volume programmatically? Provide an example.
# Your code here
- Write a code snippet to seek forward 10 seconds.
# Your code here
- How do you take a screenshot of the current video frame?
Answer: _______________________________________________
Part 5: Extra Challenges
- Add a new keyboard shortcut to toggle PiP using the
pkey.
# Your code here
- Save your playlist to a JSON file. Show the code snippet.
# Your code here
- Implement a speed control feature to play videos at 2x.
# Your code here
✅ Reflection Questions
- Which feature was the most challenging to implement?
Answer: _______________________________________________ - Which feature would you like to add next to your player?
Answer: _______________________________________________
📝 Answer Key: Python Video Player Developer Worksheet
Part 1: Understanding the Code
- Main components of the video player:
- GUI Layer: CustomTkinter
- Video Engine: python-vlc
- Playlist management
- Background update thread
- Event system: drag & drop, double-click, keyboard shortcuts
- Variable storing current video index:
current_index - Purpose of
_update_thread: Continuously updates video progress, slider, time label, and auto-plays the next video when the current one finishes.
Part 2: Playlist & Event System
- Add a video snippet:
from tkinter import filedialog files = filedialog.askopenfilenames(filetypes=[("Video files", "*.mp4 *.mkv")]) for f in files: self.playlist.append(f) self._refresh_playlist_view() - Remove a video programmatically:
index = 0 # example index del self.playlist[index] self._refresh_playlist_view() - Double-clicking a playlist item loads and plays the selected video immediately.
Part 3: Mini Player & PiP
- Toggle mini-player mode:
self.toggle_mini() - PiP synchronization: The PiP player shares the same media as the main player and is set to the same playback time before starting, so it remains in sync.
Part 4: Playback & Controls
- Change volume example:
self.player.audio_set_volume(80) # sets volume to 80% - Seek forward 10 seconds:
cur = self.player.get_time() self.player.set_time(cur + 10000) # 10000ms = 10s - Take a screenshot: Uses
self.player.video_take_snapshot(0, "filename.png", 0, 0)to save the current frame.
Part 5: Extra Challenges
- Keyboard shortcut for PiP (p key):
self.bind("p", lambda e: self.toggle_pip()) - Save playlist to JSON:
import json with open("playlist.json", "w") as f: json.dump(self.playlist, f) - Speed control to play at 2x:
self.player.set_rate(2.0)
Reflection Questions
- Most challenging feature: Answers will vary (e.g., PiP sync, drag & drop, animated theme)
- Feature to add next: Answers will vary (e.g., playlist save/load, subtitles, night mode)
✨ Final Thoughts
This project teaches practical Python GUI programming with multimedia integration. You now have a real-world, portfolio-ready application that’s visually appealing and functional!

.webp)
.webp)
.webp)
.webp)
.webp)
.webp)
.webp)