import tkinter as tk
from tkinter import filedialog, ttk
import vlc
import os
import time
import subprocess
class VLCVideoPlayer:
# Main class for the video/audio player using VLC and Tkinter
def __init__(self, root):
self.root = root
self.root.title("E-Stim Player")
self.instance = vlc.Instance()
self.player = self.instance.media_player_new()
self.audio_instance = vlc.Instance()
self.audio_player = self.audio_instance.media_player_new()
self.audio_media = None
self.audio_device_map = {}
self.selected_audio_device_id = None
self.video_media = None
self.slider_dragging = False
self.was_playing_before_drag = False
self.is_paused = True
self.fullscreen = False
self.main_frame = tk.Frame(self.root)
self.main_frame.pack(fill=tk.BOTH, expand=1)
self.create_menu()
self.create_toolbar()
self.create_video_frame()
self.create_controls()
self.root.bind("<f>", lambda e: self.toggle_fullscreen())
self.update_slider()
self.root.bind("<Left>", lambda e: self.seek_relative(-5))
self.root.bind("<Right>", lambda e: self.seek_relative(5))
self.root.bind("<m>", lambda e: self.toggle_mute())
self.root.bind("<q>", lambda e: self.root.quit())
self.root.bind("<space>", lambda e: self.toggle_play_pause())
def create_menu(self):
self.menubar = tk.Menu(self.root)
file_menu = tk.Menu(self.menubar, tearoff=0)
file_menu.add_command(label="Open Video", command=self.open_video)
file_menu.add_command(label="Select Audio", command=self.select_audio_file)
file_menu.add_separator()
file_menu.add_command(label="Exit", command=self.root.quit)
self.menubar.add_cascade(label="File", menu=file_menu)
setup_menu = tk.Menu(self.menubar, tearoff=0)
setup_menu.add_command(label="System Audio Settings", command=self.open_audio_settings)
self.menubar.add_cascade(label="Setup", menu=setup_menu)
self.root.config(menu=self.menubar)
def create_toolbar(self):
self.toolbar = tk.Frame(self.main_frame, bd=1, relief=tk.RAISED)
tk.Button(self.toolbar, text="📂 Select Video", command=self.open_video).pack(side=tk.LEFT, padx=2, pady=2)
tk.Button(self.toolbar, text="🎵 Select Audio", command=self.select_audio_file).pack(side=tk.LEFT, padx=2, pady=2)
tk.Button(self.toolbar, text="⛶ Full Screen", command=self.toggle_fullscreen).pack(side=tk.LEFT, padx=2, pady=2)
tk.Label(self.toolbar, text="Audio Device:").pack(side=tk.LEFT, padx=5)
self.audio_combo = ttk.Combobox(self.toolbar, state="readonly", width=30)
self.audio_combo.pack(side=tk.LEFT, padx=2)
self.audio_combo.bind("<<ComboboxSelected>>", self.on_audio_device_selected)
self.volume_slider = tk.Scale(self.toolbar, from_=0, to=100, orient=tk.HORIZONTAL, length=200, showvalue=0, command=self.set_audio_volume)
self.volume_slider.set(100)
self.volume_slider.pack(side=tk.LEFT, padx=5)
self.mute_button = tk.Button(self.toolbar, text="🔇 Mute", command=self.toggle_mute)
self.mute_button.pack(side=tk.LEFT, padx=5)
self.toolbar.pack(side=tk.TOP, fill=tk.X)
self.populate_audio_devices()
def populate_audio_devices(self):
outputs = self.audio_player.audio_output_device_enum()
devices = []
while outputs:
name = outputs.contents.device
desc = outputs.contents.description
if name and desc:
name_str = name.decode()
desc_str = desc.decode()
self.audio_device_map[desc_str] = name_str
devices.append(desc_str)
outputs = outputs.contents.next
self.audio_combo['values'] = devices
if devices:
self.audio_combo.current(0)
self.selected_audio_device_id = self.audio_device_map[devices[0]]
def on_audio_device_selected(self, event):
selected = self.audio_combo.get()
self.selected_audio_device_id = self.audio_device_map.get(selected, None)
def set_audio_volume(self, val):
if not self.audio_player.audio_get_mute():
self.audio_player.audio_set_volume(int(val))
self.audio_player.audio_set_volume(int(val))
def create_video_frame(self):
self.video_panel = tk.Frame(self.main_frame, bg="black")
self.video_panel.pack(fill=tk.BOTH, expand=1)
self.set_video_output()
def set_video_output(self):
self.root.update()
if os.name == "nt":
self.player.set_hwnd(self.video_panel.winfo_id())
else:
self.player.set_xwindow(self.video_panel.winfo_id())
def create_controls(self):
self.control_frame = tk.Frame(self.main_frame)
self.slider = tk.Scale(self.control_frame, from_=0, to=1000, orient=tk.HORIZONTAL, showvalue=0, command=self.on_slider_move)
self.slider.pack(fill=tk.X, expand=1, padx=5)
self.slider.bind("<ButtonPress-1>", self.on_slider_click)
self.slider.bind("<ButtonRelease-1>", self.on_slider_release)
self.btns = tk.Frame(self.control_frame)
self.btn_play = tk.Button(self.btns, text="▶ Play", command=self.toggle_play_pause)
self.btn_play.pack(side=tk.LEFT, padx=5)
self.btn_stop = tk.Button(self.btns, text="■ Stop", command=self.stop)
self.btn_stop.pack(side=tk.LEFT, padx=5)
self.btns.pack(pady=5)
self.control_frame.pack(fill=tk.X)
def open_video(self):
self.stop()
path = filedialog.askopenfilename(filetypes=[("Video files", "*.mp4 *.avi *.mkv *.mov")])
if not path:
return
self.video_media = self.instance.media_new(path)
self.player.set_media(self.video_media)
self.btn_play.config(text="▶ Play")
self.is_paused = True
def select_audio_file(self):
self.stop()
path = filedialog.askopenfilename(filetypes=[("Audio files", "*.mp3 *.wav *.ogg *.aac")])
if not path:
return
self.audio_media = self.audio_instance.media_new(path)
self.audio_player.set_media(self.audio_media)
self.audio_player.play()
time.sleep(0.1)
self.audio_player.pause()
if self.selected_audio_device_id:
self.audio_player.audio_output_device_set(None, self.selected_audio_device_id)
self.audio_player.audio_set_volume(self.volume_slider.get())
def toggle_play_pause(self):
if not self.video_media and not self.audio_media:
return
if self.is_paused:
if self.video_media:
self.player.play()
if self.audio_media:
if self.audio_player.get_state() == vlc.State.Stopped:
self.audio_player.set_media(self.audio_media)
if self.selected_audio_device_id:
self.audio_player.audio_output_device_set(None, self.selected_audio_device_id)
self.audio_player.audio_set_volume(self.volume_slider.get())
self.audio_player.play()
self.btn_play.config(text="⏸ Pause")
self.is_paused = False
else:
if self.video_media:
self.player.pause()
if self.audio_media:
self.audio_player.pause()
self.btn_play.config(text="▶ Play")
self.is_paused = True
def stop(self):
if self.video_media:
self.player.stop()
if self.audio_media:
self.audio_player.pause()
self.audio_player.set_position(0)
self.slider.set(0)
self.btn_play.config(text="▶ Play")
self.is_paused = True
def on_slider_move(self, val):
if self.slider_dragging:
pos = float(val) / 1000
if self.video_media:
self.player.set_position(pos)
if self.audio_media:
self.audio_player.set_position(pos)
def on_slider_click(self, event):
self.slider_dragging = True
self.was_playing_before_drag = not self.is_paused
widget = event.widget
width = widget.winfo_width()
click_x = event.x
ratio = click_x / width
new_val = int(ratio * 1000)
self.slider.set(new_val)
pos = float(new_val) / 1000
if self.video_media:
self.player.set_position(pos)
if not self.is_paused:
self.player.pause()
if self.audio_media:
self.audio_player.set_position(pos)
if not self.is_paused:
self.audio_player.pause()
def on_slider_release(self, event):
self.slider_dragging = False
pos = float(self.slider.get()) / 1000
if self.video_media:
self.player.set_position(pos)
if self.audio_media:
self.audio_player.set_position(pos)
if self.was_playing_before_drag:
if self.video_media:
self.player.play()
if self.audio_media:
self.audio_player.play()
def seek_relative(self, seconds):
if self.player.get_length() > 0:
pos = self.player.get_time() + int(seconds * 1000)
pos = max(0, min(pos, self.player.get_length()))
self.player.set_time(pos)
if self.audio_media:
self.audio_player.set_time(pos)
def toggle_fullscreen(self):
self.fullscreen = not self.fullscreen
self.root.attributes("-fullscreen", self.fullscreen)
if self.fullscreen:
self.toolbar.pack_forget()
self.control_frame.pack_forget()
self.video_panel.pack_forget()
self.root.config(menu=tk.Menu())
self.video_panel.pack(fill=tk.BOTH, expand=1)
self.root.update()
self.set_video_output()
else:
self.video_panel.pack_forget()
self.toolbar.pack(side=tk.TOP, fill=tk.X)
self.video_panel.pack(fill=tk.BOTH, expand=1)
self.control_frame.pack(fill=tk.X)
self.root.config(menu=self.menubar)
self.root.update()
self.set_video_output()
def update_slider(self):
if self.player and self.player.get_length() > 0 and not self.slider_dragging:
pos = self.player.get_position()
if 0 <= pos <= 1:
self.slider.set(int(pos * 1000))
if pos >= 0.99:
self.stop()
self.root.after(500, self.update_slider)
def toggle_mute(self):
muted = self.audio_player.audio_get_mute()
self.audio_player.audio_toggle_mute()
self.mute_button.config(text="🔉 Unmute" if muted else "🔇 Mute")
def open_audio_settings(self):
if os.name == "nt":
subprocess.Popen(["control", "mmsys.cpl"])
if __name__ == "__main__":
root = tk.Tk()
root.geometry("1000x600")
app = VLCVideoPlayer(root)
root.mainloop()
Okay, it took me a while, but it works on Windows..
- have python 3 on your system
- select a directory and do the following:
- create a file with the code (
<file_name.py>
) - run
pip install python-vlc
- run
python -c "import vlc; print(vlc.libvlc_get_version())"
// tells your python version 32/64bit
- create a file with the code (
- download and install VLC with the same bitness
- add the directory to your environment variables
- now if in a terminal you run
vlc
it should open upvlc
- you might have to open a new terminal for it to register
- now if in a terminal you run
- now you should be able to run the script
- go back to your directory and run
python <file_name.py>
- go back to your directory and run
if you get import error for tk
/tkinter
you have to install python3-tk
as well
Your video audio will be on the default output and the selected audio will be on the selected audio device.
Have fun!
Edit:
I made some changes since sometimes the stim audio is on the video but you probably do not want to change your default output to be the one you use for stimming.
Now you have control over both audio
import tkinter as tk
from tkinter import filedialog, ttk
import vlc
import os
import subprocess
import time
HOVER_ZONE_PX = 50 # pixels from bottom where hover will show controls
APPLY_DELAY_MS = 200 # delay before applying device/volume after play()
class VLCVideoPlayer:
def __init__(self, root):
self.root = root
self.root.title("E-Stim Player")
# Separate VLC instances for video and audio
self.video_inst = vlc.Instance()
self.video_player = self.video_inst.media_player_new()
self.audio_inst = vlc.Instance()
self.audio_player = self.audio_inst.media_player_new()
# State
self.video_media = None
self.audio_media = None
self.video_device_map = {}
self.audio_device_map = {}
self.selected_video_device = None
self.selected_audio_device = None
# UI flags
self.is_paused = True
self.fullscreen = False
self.controls_visible = True
# Build UI
self.main_frame = tk.Frame(root)
self.main_frame.pack(fill=tk.BOTH, expand=True)
self._create_menu()
self._create_toolbar()
self._create_video_panel()
self._create_controls()
# Populate devices
self._populate_device_lists()
# Init outputs
if self.selected_video_device:
self.video_player.audio_output_device_set(None, self.selected_video_device)
if self.selected_audio_device:
self.audio_player.audio_output_device_set(None, self.selected_audio_device)
# Init volume UI
for p, slider, btn in (
(self.video_player, self.vid_vol, self.vid_mute),
(self.audio_player, self.aud_vol, self.aud_mute),
):
p.audio_set_volume(100)
p.audio_set_mute(False)
slider.set(100)
btn.config(text="🔉")
# Global hover & keys
root.bind("<Motion>", self._on_panel_hover)
root.bind("f", lambda e: self._toggle_fullscreen())
root.bind("m", lambda e: self._toggle_audio_mute())
root.bind("v", lambda e: self._toggle_video_mute())
root.bind("<Left>", lambda e: self._seek_relative(-5))
root.bind("<Right>", lambda e: self._seek_relative(5))
root.bind("<space>", lambda e: self._toggle_play_pause())
root.bind("q", lambda e: root.quit())
self._update_slider()
def _create_menu(self):
self.menubar = tk.Menu(self.root)
filem = tk.Menu(self.menubar, tearoff=0)
filem.add_command(label="Open Video", command=self._open_video)
filem.add_command(label="Select Audio", command=self._open_audio)
filem.add_separator()
filem.add_command(label="Exit", command=self.root.quit)
self.menubar.add_cascade(label="File", menu=filem)
setup = tk.Menu(self.menubar, tearoff=0)
setup.add_command(label="Audio Settings", command=self._open_audio_settings)
self.menubar.add_cascade(label="Setup", menu=setup)
self.root.config(menu=self.menubar)
def _create_toolbar(self):
self.toolbar = tk.Frame(self.main_frame, bd=1, relief=tk.RAISED)
# Video controls
tk.Button(self.toolbar, text="📂", command=self._open_video).pack(side=tk.LEFT, padx=2)
tk.Label(self.toolbar, text="Vid Dev:").pack(side=tk.LEFT, padx=(10,2))
self.video_combo = ttk.Combobox(self.toolbar, state="readonly", width=25)
self.video_combo.pack(side=tk.LEFT, padx=2)
self.video_combo.bind("<<ComboboxSelected>>", self._on_video_device)
tk.Label(self.toolbar, text="Vol:").pack(side=tk.LEFT, padx=(10,2))
self.vid_vol = tk.Scale(self.toolbar, from_=0, to=100,
orient=tk.HORIZONTAL, length=120, showvalue=0,
command=self._on_video_volume_change)
self.vid_vol.pack(side=tk.LEFT)
self.vid_mute = tk.Button(self.toolbar, text="🔇", command=self._toggle_video_mute)
self.vid_mute.pack(side=tk.LEFT, padx=5)
# Audio controls
tk.Button(self.toolbar, text="🎵", command=self._open_audio).pack(side=tk.LEFT, padx=10)
tk.Label(self.toolbar, text="Aud Dev:").pack(side=tk.LEFT, padx=(10,2))
self.audio_combo = ttk.Combobox(self.toolbar, state="readonly", width=25)
self.audio_combo.pack(side=tk.LEFT, padx=2)
self.audio_combo.bind("<<ComboboxSelected>>", self._on_audio_device)
tk.Label(self.toolbar, text="Vol:").pack(side=tk.LEFT, padx=(10,2))
self.aud_vol = tk.Scale(self.toolbar, from_=0, to=100,
orient=tk.HORIZONTAL, length=120, showvalue=0,
command=self._on_audio_volume_change)
self.aud_vol.pack(side=tk.LEFT)
self.aud_mute = tk.Button(self.toolbar, text="🔇", command=self._toggle_audio_mute)
self.aud_mute.pack(side=tk.LEFT, padx=5)
# Fullscreen toggle
tk.Button(self.toolbar, text="⛶", command=self._toggle_fullscreen).pack(side=tk.RIGHT, padx=5)
self.toolbar.pack(side=tk.TOP, fill=tk.X)
def _create_video_panel(self):
self.video_panel = tk.Frame(self.main_frame, bg="black")
self.video_panel.pack(fill=tk.BOTH, expand=True)
self._attach_video_output()
def _create_controls(self):
self.controls_frame = tk.Frame(self.main_frame)
self.slider = tk.Scale(self.controls_frame, from_=0, to=1000,
orient=tk.HORIZONTAL, showvalue=0)
self.slider.pack(fill=tk.X, expand=True, padx=5)
self.slider.bind("<Button-1>", self._on_slider_click)
self.slider.bind("<B1-Motion>", self._on_slider_click)
self.slider.bind("<ButtonRelease-1>", self._on_slider_end)
btnf = tk.Frame(self.controls_frame)
self.play_btn = tk.Button(btnf, text="▶ Play", command=self._toggle_play_pause)
self.play_btn.pack(side=tk.LEFT, padx=5)
self.stop_btn = tk.Button(btnf, text="■ Stop", command=self._stop)
self.stop_btn.pack(side=tk.LEFT)
btnf.pack(pady=5)
self.controls_frame.pack(side=tk.BOTTOM, fill=tk.X)
def _populate_device_lists(self):
# Video devices
vouts = self.video_player.audio_output_device_enum()
video_devs = []
while vouts:
desc = vouts.contents.description.decode()
video_devs.append(desc)
self.video_device_map[desc] = vouts.contents.device.decode()
vouts = vouts.contents.next
self.video_combo['values'] = video_devs
if video_devs:
self.video_combo.current(0)
self.selected_video_device = self.video_device_map[video_devs[0]]
# Audio devices
aouts = self.audio_player.audio_output_device_enum()
audio_devs = []
while aouts:
desc = aouts.contents.description.decode()
audio_devs.append(desc)
self.audio_device_map[desc] = aouts.contents.device.decode()
aouts = aouts.contents.next
self.audio_combo['values'] = audio_devs
if audio_devs:
self.audio_combo.current(0)
self.selected_audio_device = self.audio_device_map[audio_devs[0]]
def _open_video(self):
self._stop()
path = filedialog.askopenfilename(filetypes=[("Video","*.mp4 *.avi *.mkv *.mov")])
if not path: return
self.video_media = self.video_inst.media_new(path)
self.video_player.set_media(self.video_media)
self.video_player.play(); time.sleep(0.1); self.video_player.pause()
def _open_audio(self):
self._stop()
path = filedialog.askopenfilename(filetypes=[("Audio","*.mp3 *.wav *.ogg *.aac")])
if not path: return
self.audio_media = self.audio_inst.media_new(path)
self.audio_player.set_media(self.audio_media)
self.audio_player.play(); time.sleep(0.1); self.audio_player.pause()
def _on_slider_click(self, event):
w = event.widget
ratio = max(0.0, min(event.x / w.winfo_width(), 1.0))
if self.video_media:
self.video_player.set_position(ratio)
if self.audio_media:
self.audio_player.set_position(ratio)
self.slider.set(int(ratio * 1000))
def _on_slider_end(self, event):
if not self.is_paused:
if self.video_media:
self.video_player.play()
self.root.after(APPLY_DELAY_MS, self._apply_video_settings)
if self.audio_media:
self.audio_player.play()
self.root.after(APPLY_DELAY_MS, self._apply_audio_settings)
def _on_video_device(self, _):
new_dev = self.video_device_map[self.video_combo.get()]
self.selected_video_device = new_dev
cur = self.video_player.audio_output_device_get()
if cur != new_dev:
self.video_player.audio_output_device_set(None, new_dev)
def _on_audio_device(self, _):
new_dev = self.audio_device_map[self.audio_combo.get()]
self.selected_audio_device = new_dev
cur = self.audio_player.audio_output_device_get()
if cur != new_dev:
self.audio_player.audio_output_device_set(None, new_dev)
def _on_video_volume_change(self, v):
vol = int(v)
if self.selected_video_device:
cur = self.video_player.audio_output_device_get()
if cur != self.selected_video_device:
self.video_player.audio_output_device_set(None, self.selected_video_device)
if self.video_player.audio_get_volume() != vol:
self.video_player.audio_set_volume(vol)
def _on_audio_volume_change(self, v):
vol = int(v)
if self.selected_audio_device:
cur = self.audio_player.audio_output_device_get()
if cur != self.selected_audio_device:
self.audio_player.audio_output_device_set(None, self.selected_audio_device)
if self.audio_player.audio_get_volume() != vol:
self.audio_player.audio_set_volume(vol)
def _toggle_play_pause(self):
if not (self.video_media or self.audio_media): return
if self.is_paused:
if self.video_media:
self.video_player.play()
self.root.after(APPLY_DELAY_MS, self._apply_video_settings)
if self.audio_media:
self.audio_player.play()
self.root.after(APPLY_DELAY_MS, self._apply_audio_settings)
self.play_btn.config(text="⏸ Pause")
else:
if self.video_media: self.video_player.pause()
if self.audio_media: self.audio_player.pause()
self.play_btn.config(text="▶ Play")
self.is_paused = not self.is_paused
def _apply_video_settings(self):
if not self.is_paused and self.selected_video_device:
cur = self.video_player.audio_output_device_get()
if cur != self.selected_video_device:
self.video_player.audio_output_device_set(None, self.selected_video_device)
self.video_player.audio_set_volume(self.vid_vol.get())
def _apply_audio_settings(self):
if not self.is_paused and self.selected_audio_device:
cur = self.audio_player.audio_output_device_get()
if cur != self.selected_audio_device:
self.audio_player.audio_output_device_set(None, self.selected_audio_device)
self.audio_player.audio_set_volume(self.aud_vol.get())
def _stop(self):
if self.video_media: self.video_player.stop()
if self.audio_media: self.audio_player.stop()
self.slider.set(0)
self.play_btn.config(text="▶ Play")
self.is_paused = True
def _seek_relative(self, sec):
if self.video_media and self.video_player.get_length() > 0:
t = self.video_player.get_time() + int(sec * 1000)
t = max(0, min(t, self.video_player.get_length()))
self.video_player.set_time(t)
if self.audio_media:
self.audio_player.set_time(t)
def _on_panel_hover(self, event):
if not self.fullscreen:
return
# calculate y relative to window
y = event.y_root - self.root.winfo_rooty()
h = self.root.winfo_height()
# get total controls height
tb_h = self.toolbar.winfo_height()
ctrl_h = self.controls_frame.winfo_height()
total_h = tb_h + ctrl_h
# show if in hover zone
if y >= h - HOVER_ZONE_PX:
if not self.controls_visible:
self.toolbar.pack(side=tk.TOP, fill=tk.X)
self.controls_frame.pack(side=tk.BOTTOM, fill=tk.X)
self.controls_visible = True
# hide if cursor above controls area
elif y < h - total_h:
if self.controls_visible:
self.toolbar.pack_forget()
self.controls_frame.pack_forget()
self.controls_visible = False
def _toggle_video_mute(self):
m = self.video_player.audio_get_mute()
self.video_player.audio_toggle_mute()
self.vid_mute.config(text="🔉" if m else "🔇")
def _toggle_audio_mute(self):
m = self.audio_player.audio_get_mute()
self.audio_player.audio_toggle_mute()
self.aud_mute.config(text="🔉" if m else "🔇")
def _toggle_fullscreen(self):
self.fullscreen = not self.fullscreen
self.root.attributes("-fullscreen", self.fullscreen)
if self.fullscreen:
self.root.config(menu="")
self.toolbar.pack_forget()
self.controls_frame.pack_forget()
self.controls_visible = False
else:
self.root.config(menu=self.menubar)
self.toolbar.pack(side=tk.TOP, fill=tk.X)
self.video_panel.pack_forget()
self.video_panel.pack(fill=tk.BOTH, expand=True)
self.controls_frame.pack(side=tk.BOTTOM, fill=tk.X)
self.controls_visible = True
self._attach_video_output()
def _attach_video_output(self):
self.root.update()
wid = self.video_panel.winfo_id()
if os.name == "nt":
self.video_player.set_hwnd(wid)
else:
self.video_player.set_xwindow(wid)
def _update_slider(self):
if self.video_media and not self.dragging:
pos = self.video_player.get_position()
if 0 <= pos <= 1:
self.slider.set(int(pos * 1000))
if pos >= 0.99:
self._stop()
self.root.after(500, self._update_slider)
def _open_audio_settings(self):
if os.name == "nt":
subprocess.Popen(["control", "mmsys.cpl"])
if __name__ == "__main__":
root = tk.Tk()
root.geometry("1000x600")
VLCVideoPlayer(root)
root.mainloop()