From 89ca89528a228a14e65bd353cf22595613817093 Mon Sep 17 00:00:00 2001 From: Jeffrey Anthony Date: Thu, 24 Jul 2025 02:36:06 -0400 Subject: [PATCH 1/2] Add simple PV graphical interface --- README.md | 1 + pv_gui.py | 62 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 pv_gui.py diff --git a/README.md b/README.md index 19e683c..9ed10db 100644 --- a/README.md +++ b/README.md @@ -13,5 +13,6 @@ PPVviaVLC enables anyone to broadcast pay-per-view (PPV) live streams from the c 3. Launch your broadcast using VLC. 4. Share your tokens so viewers can watch the live PPV stream. 5. Windows users can run ppv_vlc_setup.py to locate or install VLC. +6. Launch the graphical interface with `python pv_gui.py` to start a stream and share tokens. Remember to only stream content you have rights to share and comply with all local laws regarding pay-per-view broadcasts. diff --git a/pv_gui.py b/pv_gui.py new file mode 100644 index 0000000..eb8d6f1 --- /dev/null +++ b/pv_gui.py @@ -0,0 +1,62 @@ +import os +import subprocess +import uuid +import tkinter as tk +from tkinter import filedialog, messagebox + +DEFAULT_VLC_COMMAND = ["vlc"] + +class PVGUI(tk.Tk): + def __init__(self): + super().__init__() + self.title("PV Broadcaster") + self.geometry("400x280") + self.stream_path = tk.StringVar() + self.token = tk.StringVar(value=self._generate_token()) + self.vlc_proc = None + self._create_widgets() + + def _create_widgets(self): + tk.Label(self, text="PV Streaming UI", font=("Helvetica", 14)).pack(pady=10) + tk.Button(self, text="Select video file", command=self._select_file).pack(pady=5) + tk.Label(self, textvariable=self.stream_path).pack(pady=5) + tk.Button(self, text="Generate new token", command=self._refresh_token).pack(pady=5) + tk.Entry(self, textvariable=self.token, width=30).pack(pady=5) + tk.Button(self, text="Start stream", command=self._start_stream).pack(pady=5) + tk.Button(self, text="Stop stream", command=self._stop_stream).pack(pady=5) + + def _select_file(self): + path = filedialog.askopenfilename() + if path: + self.stream_path.set(path) + + def _refresh_token(self): + self.token.set(self._generate_token()) + + def _generate_token(self): + return uuid.uuid4().hex[:8] + + def _start_stream(self): + if not self.stream_path.get(): + messagebox.showwarning("Missing file", "Please select a video file to stream.") + return + cmd = DEFAULT_VLC_COMMAND + [ + self.stream_path.get(), + "--sout", + "#duplicate{dst=display,dst=std{access=http,mux=ts,dst=:8080}}", + ] + try: + self.vlc_proc = subprocess.Popen(cmd) + messagebox.showinfo("Streaming", f"Stream started on http://localhost:8080\nToken: {self.token.get()}") + except Exception as e: + messagebox.showerror("Error", str(e)) + + def _stop_stream(self): + if self.vlc_proc: + self.vlc_proc.terminate() + self.vlc_proc = None + messagebox.showinfo("Stopped", "Streaming stopped") + +if __name__ == "__main__": + app = PVGUI() + app.mainloop() From 86dcef33f331b9590998329f96952b9121ef27ec Mon Sep 17 00:00:00 2001 From: Jeffrey Anthony Date: Thu, 24 Jul 2025 02:45:37 -0400 Subject: [PATCH 2/2] Improve PV GUI --- README.md | 7 ++++--- pv_gui.py | 56 +++++++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 48 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 9ed10db..d6ebfc7 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# PPVviaVLC +# PV (formerly PPVviaVLC) -PPVviaVLC enables anyone to broadcast pay-per-view (PPV) live streams from the comfort of their own device using VLC. It tokenizes access to your encrypted VLC stream so viewers must purchase a token to watch. +PV enables anyone to broadcast pay-per-view (PPV) live streams from the comfort of their own device using VLC. It tokenizes access to your encrypted VLC stream so viewers must purchase a token to watch. ## Features - Uses standard VLC tools to stream directly from your machine. @@ -13,6 +13,7 @@ PPVviaVLC enables anyone to broadcast pay-per-view (PPV) live streams from the c 3. Launch your broadcast using VLC. 4. Share your tokens so viewers can watch the live PPV stream. 5. Windows users can run ppv_vlc_setup.py to locate or install VLC. -6. Launch the graphical interface with `python pv_gui.py` to start a stream and share tokens. +6. Launch the graphical interface with `python pv_gui.py` to select a video, + generate tokens, and manage your VLC stream. Remember to only stream content you have rights to share and comply with all local laws regarding pay-per-view broadcasts. diff --git a/pv_gui.py b/pv_gui.py index eb8d6f1..49c7644 100644 --- a/pv_gui.py +++ b/pv_gui.py @@ -4,35 +4,66 @@ import tkinter as tk from tkinter import filedialog, messagebox -DEFAULT_VLC_COMMAND = ["vlc"] +DEFAULT_VLC_COMMAND = "vlc" class PVGUI(tk.Tk): def __init__(self): super().__init__() self.title("PV Broadcaster") - self.geometry("400x280") + self.geometry("420x320") self.stream_path = tk.StringVar() self.token = tk.StringVar(value=self._generate_token()) + self.port = tk.StringVar(value="8080") + self.vlc_path = tk.StringVar(value=DEFAULT_VLC_COMMAND) self.vlc_proc = None self._create_widgets() + self.protocol("WM_DELETE_WINDOW", self._on_close) def _create_widgets(self): tk.Label(self, text="PV Streaming UI", font=("Helvetica", 14)).pack(pady=10) + tk.Button(self, text="Select video file", command=self._select_file).pack(pady=5) tk.Label(self, textvariable=self.stream_path).pack(pady=5) - tk.Button(self, text="Generate new token", command=self._refresh_token).pack(pady=5) - tk.Entry(self, textvariable=self.token, width=30).pack(pady=5) - tk.Button(self, text="Start stream", command=self._start_stream).pack(pady=5) - tk.Button(self, text="Stop stream", command=self._stop_stream).pack(pady=5) + + path_frame = tk.Frame(self) + path_frame.pack(pady=5) + tk.Label(path_frame, text="VLC path:").pack(side=tk.LEFT) + tk.Entry(path_frame, textvariable=self.vlc_path, width=30).pack(side=tk.LEFT) + tk.Button(path_frame, text="Browse", command=self._select_vlc).pack(side=tk.LEFT, padx=2) + + port_frame = tk.Frame(self) + port_frame.pack(pady=5) + tk.Label(port_frame, text="Port:").pack(side=tk.LEFT) + tk.Entry(port_frame, textvariable=self.port, width=6).pack(side=tk.LEFT) + + token_frame = tk.Frame(self) + token_frame.pack(pady=5) + tk.Button(token_frame, text="New token", command=self._refresh_token).pack(side=tk.LEFT) + tk.Entry(token_frame, textvariable=self.token, width=20, state="readonly").pack(side=tk.LEFT, padx=2) + tk.Button(token_frame, text="Copy", command=self._copy_token).pack(side=tk.LEFT) + + control_frame = tk.Frame(self) + control_frame.pack(pady=10) + tk.Button(control_frame, text="Start stream", command=self._start_stream).pack(side=tk.LEFT, padx=5) + tk.Button(control_frame, text="Stop stream", command=self._stop_stream).pack(side=tk.LEFT, padx=5) def _select_file(self): path = filedialog.askopenfilename() if path: self.stream_path.set(path) + def _select_vlc(self): + path = filedialog.askopenfilename(title="Select VLC executable") + if path: + self.vlc_path.set(path) + def _refresh_token(self): self.token.set(self._generate_token()) + def _copy_token(self): + self.clipboard_clear() + self.clipboard_append(self.token.get()) + def _generate_token(self): return uuid.uuid4().hex[:8] @@ -40,14 +71,11 @@ def _start_stream(self): if not self.stream_path.get(): messagebox.showwarning("Missing file", "Please select a video file to stream.") return - cmd = DEFAULT_VLC_COMMAND + [ - self.stream_path.get(), - "--sout", - "#duplicate{dst=display,dst=std{access=http,mux=ts,dst=:8080}}", - ] + cmd = [self.vlc_path.get(), self.stream_path.get(), "--sout", + f"#duplicate{{dst=display,dst=std{{access=http,mux=ts,dst=:{self.port.get()}}}}}"] try: self.vlc_proc = subprocess.Popen(cmd) - messagebox.showinfo("Streaming", f"Stream started on http://localhost:8080\nToken: {self.token.get()}") + messagebox.showinfo("Streaming", f"Stream started on http://localhost:{self.port.get()}\nToken: {self.token.get()}") except Exception as e: messagebox.showerror("Error", str(e)) @@ -57,6 +85,10 @@ def _stop_stream(self): self.vlc_proc = None messagebox.showinfo("Stopped", "Streaming stopped") + def _on_close(self): + self._stop_stream() + self.destroy() + if __name__ == "__main__": app = PVGUI() app.mainloop()