Commit 5cf2ed06 authored by Robocoders's avatar Robocoders

Initial commit for SHMCamStudio

parents
#!/usr/bin/env python3
import argparse
import os
import sys
import signal
import threading
import webbrowser
import logging
import socket
from flask import Flask, render_template, request
import subprocess
import tkinter as tk
from tkinter import font as tkFont
import vlc
# Setup logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('/tmp/streaming_control.log'),
logging.StreamHandler(sys.stdout)
]
)
logger = logging.getLogger(__name__)
# Flask App Setup
flask_app = Flask(__name__)
# Command Mapping
COMMANDS = {
# Private commands
'private_stefy': 'smblur_private_stefy',
'private_leeloo': 'smblur_private_leeloo',
'private_jasmin': 'smblur_private_jasmin',
'private_other': 'smblur_private',
# Open/Close commands
'toggle_stefy': 'smblur_stefy',
'toggle_leeloo': 'smblur_leeloo',
'toggle_jasmin': 'smblur_jasmin',
'toggle_others': 'smblur_shine',
# Special commands
'tease': 'smblur_tease',
'tease_all': 'smblur_teaseall',
'open': 'smblur_clean'
}
def run_command(command):
try:
result = subprocess.run(command, shell=True, check=True,
capture_output=True, text=True)
logger.info(f"Command executed: {command}")
return result.stdout
except subprocess.CalledProcessError as e:
logger.error(f"Error executing command {command}: {e}")
return f"Error: {e}"
@flask_app.route('/')
def index():
return render_template('index.html', commands=COMMANDS)
@flask_app.route('/execute', methods=['POST'])
def execute():
command_key = request.form.get('command')
if command_key in COMMANDS:
result = run_command(COMMANDS[command_key])
return result
else:
return "Invalid command", 400
@flask_app.route('/stream')
def stream():
stream_url = "https://192.168.42.1/HLS/record/Live.m3u8"
return render_template('stream.html', stream_url=stream_url)
def create_daemon():
"""Create a Unix-style daemon process"""
try:
# First fork
pid = os.fork()
if pid > 0:
# Exit first parent
sys.exit(0)
except OSError as err:
sys.stderr.write(f'Fork #1 failed: {err}\n')
sys.exit(1)
# Decouple from parent environment
os.chdir('/')
os.setsid()
os.umask(0)
# Second fork
try:
pid = os.fork()
if pid > 0:
# Exit second parent
sys.exit(0)
except OSError as err:
sys.stderr.write(f'Fork #2 failed: {err}\n')
sys.exit(1)
# Redirect standard file descriptors
sys.stdout.flush()
sys.stderr.flush()
si = open(os.devnull, 'r')
so = open(os.devnull, 'a+')
se = open(os.devnull, 'a+')
os.dup2(si.fileno(), sys.stdin.fileno())
os.dup2(so.fileno(), sys.stdout.fileno())
os.dup2(se.fileno(), sys.stderr.fileno())
def check_port_available(port):
"""Check if a port is available"""
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
return s.connect_ex(('localhost', port)) != 0
def run_flask_app(port=5000, daemon_mode=False):
"""Run Flask app with optional daemon mode"""
if not check_port_available(port):
logger.error(f"Port {port} is already in use")
sys.exit(1)
logger.info(f"Starting Flask app on port {port}")
if daemon_mode and sys.platform != 'win32':
create_daemon()
flask_app.run(host='0.0.0.0', port=port, debug=False, use_reloader=False)
class VideoPlayer:
def __init__(self, master, video_url):
self.master = master
# VLC player setup
args = []
_isLinux = sys.platform.startswith('linux')
if _isLinux:
args.append('--vout=mmal_vout')
# Create a VLC instance
self.Instance = vlc.Instance(args)
# Create a new MediaPlayer
self.player = self.Instance.media_player_new()
# Set the media
media = self.Instance.media_new(video_url)
self.player.set_media(media)
# Create a frame for the video
self.video_frame = self.master
self.video_frame.pack(fill=tk.BOTH, expand=True)
self.canvas = tk.Canvas(self.video_frame, width=800)
self.canvas.pack(fill=tk.BOTH, expand=True)
# Embed the VLC Video
win_id = self.canvas.winfo_id()
if _isLinux:
self.player.set_xwindow(win_id)
else:
self.player.set_hwnd(win_id)
# Play the video
self.player.play()
def create_tkinter_gui():
# Create the main window
window = tk.Tk()
window.title("Streaming Control Panel")
helv36 = tkFont.Font(family='Helvetica', size=13, weight='bold')
# Frame for the left side
fleft = tk.Frame(window)
fleft.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
# URL of your HLS stream
video_url = "rtmp://192.168.42.1/record/Live"
VideoPlayer(fleft, video_url)
# Frame for the right side
fright = tk.Frame(window)
fright.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True)
# Frame for the first two rows in the right frame
frame1 = tk.Frame(fright)
frame1.pack(fill=tk.BOTH, expand=True)
# Buttons configuration
buttons_row0 = [
('PRIVATE STEFY', 'smblur_private_stefy'),
('PRIVATE LEELOO', 'smblur_private_leeloo'),
('PRIVATE JASMIN', 'smblur_private_jasmin'),
('PRIVATE OTHER', 'smblur_private')
]
buttons_row1 = [
('OPEN/CLOSE STEFY', 'smblur_stefy'),
('OPEN/CLOSE LEELOO', 'smblur_leeloo'),
('OPEN/CLOSE JASMIN', 'smblur_jasmin'),
('OPEN/CLOSE OTHERS', 'smblur_shine')
]
# Create buttons for the first two rows
for j, (text, command) in enumerate(buttons_row0):
button = tk.Button(frame1, text=text, font=helv36, width=25, height=15, bg="green", fg="white",
command=lambda cmd=command: run_command(cmd))
button.grid(row=0, column=j, sticky='nsew')
for j, (text, command) in enumerate(buttons_row1):
button = tk.Button(frame1, text=text, font=helv36, width=25, height=15, bg="green", fg="white",
command=lambda cmd=command: run_command(cmd))
button.grid(row=1, column=j, sticky='nsew')
# Configure the columns in the first frame
for i in range(4):
frame1.grid_columnconfigure(i, weight=1)
# Frame for the third row in the right frame
frame2 = tk.Frame(fright)
frame2.pack(fill=tk.BOTH, expand=True)
# Row 2 with 3 buttons
buttons_row2 = [
('TEASE', 'smblur_tease'),
('TEASE ALL', 'smblur_teaseall'),
('OPEN', 'smblur_clean')
]
# Create buttons for the third row
for j, (text, command) in enumerate(buttons_row2):
button = tk.Button(frame2, text=text, font=helv36, width=30, height=25, bg="blue", fg="white",
command=lambda cmd=command: run_command(cmd))
button.grid(row=0, column=j, sticky='nsew')
# Configure the columns in the second frame
for i in range(3):
frame2.grid_columnconfigure(i, weight=1)
# Add a button to open web interface
web_button = tk.Button(frame2, text="Open Web Interface",
command=lambda: webbrowser.open('http://localhost:5000'),
bg="purple", fg="white", font=helv36)
web_button.grid(row=1, column=1, sticky='nsew')
return window
def main():
# Setup argument parser
parser = argparse.ArgumentParser(description='Streaming Control Panel')
parser.add_argument('--web-only', action='store_true',
help='Start only the web interface')
parser.add_argument('--daemon', action='store_true',
help='Run in daemon mode (Unix-like systems only)')
parser.add_argument('--port', type=int, default=5000,
help='Port for the web interface (default: 5000)')
# Parse arguments
args = parser.parse_args()
try:
# Daemon mode for web interface
if args.web_only or args.daemon:
run_flask_app(port=args.port, daemon_mode=args.daemon)
else:
# Start web interface in a background thread
web_thread = threading.Thread(
target=run_flask_app,
kwargs={'port': args.port},
daemon=True
)
web_thread.start()
# Launch Tkinter GUI
window = create_tkinter_gui()
window.mainloop()
except Exception as e:
logger.error(f"Unexpected error: {e}")
sys.exit(1)
if __name__ == '__main__':
main()
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Streaming Control Panel</title>
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body, html {
height: 100%;
font-family: Arial, sans-serif;
overflow: hidden;
}
.main-container {
display: flex;
height: 100vh;
width: 100vw;
}
.sidebar {
background-color: #333;
width: 50px;
transition: width 0.3s ease;
overflow: hidden;
display: flex;
flex-direction: column;
position: relative;
}
.sidebar.expanded {
width: 33.33vw;
}
.resize-handle {
position: absolute;
right: 0;
top: 0;
bottom: 0;
width: 10px;
cursor: col-resize;
background-color: #555;
}
.sidebar-content {
flex-grow: 1;
display: flex;
flex-direction: column;
opacity: 0;
transition: opacity 0.3s ease;
}
.sidebar.expanded .sidebar-content {
opacity: 1;
}
.sidebar-toggle {
background-color: #444;
color: white;
border: none;
padding: 10px;
cursor: pointer;
text-align: center;
}
.video-container {
flex-grow: 1;
display: none;
width: 100%;
height: 0;
background-color: black;
}
.sidebar.expanded .video-container {
display: block;
height: auto;
}
.buttons-container {
display: grid;
grid-template-columns: repeat(12, 1fr);
grid-template-rows: repeat(3, 1fr);
height: 100%;
width: 100%;
transition: width 0.3s ease;
}
.sidebar.expanded ~ .buttons-container {
width: 66.66vw;
}
/* Button layout */
.buttons-container > .button {
grid-column: span 3;
}
/* Third row layout */
.buttons-container > .button:nth-child(9),
.buttons-container > .button:nth-child(10),
.buttons-container > .button:nth-child(11) {
grid-column: span 4;
}
.button {
display: flex;
justify-content: center;
align-items: center;
font-size: 1.5vw;
font-weight: bold;
text-align: center;
border: 2px solid white;
cursor: pointer;
transition: all 0.3s ease;
text-transform: uppercase;
}
/* Color variations */
.private { background-color: #4CAF50; color: white; }
.toggle { background-color: #2196F3; color: white; }
.special { background-color: #FF9800; color: white; }
.button:hover {
opacity: 0.8;
transform: scale(1.05);
}
.button:active {
background-color: #45a049;
transform: scale(0.95);
}
/* Third row buttons */
.buttons-container > .button:nth-child(n+9) {
grid-column: span 2;
}
/* Responsive adjustments */
@media (max-width: 768px) {
.button {
font-size: 3vw;
}
}
</style>
</head>
<body>
<div class="main-container">
<!-- Sidebar -->
<div class="sidebar">
<button class="sidebar-toggle" onclick="toggleSidebar()">
</button>
<div class="resize-handle" id="resize-handle"></div>
<div class="sidebar-content">
<div class="video-container">
<iframe src="/stream" frameborder="0" allowfullscreen style="width:100%; height:100%;"></iframe>
</div>
</div>
</div>
<!-- Buttons Container -->
<div class="buttons-container">
<!-- Private Commands - Row 1 -->
<button class="button private" onclick="executeCommand('private_stefy')">
PRIVATE STEFY
</button>
<button class="button private" onclick="executeCommand('private_leeloo')">
PRIVATE LEELOO
</button>
<button class="button private" onclick="executeCommand('private_jasmin')">
PRIVATE JASMIN
</button>
<button class="button private" onclick="executeCommand('private_other')">
PRIVATE OTHER
</button>
<!-- Toggle Commands - Row 2 -->
<button class="button toggle" onclick="executeCommand('toggle_stefy')">
OPEN/CLOSE STEFY
</button>
<button class="button toggle" onclick="executeCommand('toggle_leeloo')">
OPEN/CLOSE LEELOO
</button>
<button class="button toggle" onclick="executeCommand('toggle_jasmin')">
OPEN/CLOSE JASMIN
</button>
<button class="button toggle" onclick="executeCommand('toggle_others')">
OPEN/CLOSE OTHERS
</button>
<!-- Special Commands - Row 3 -->
<button class="button special" onclick="executeCommand('tease')">
TEASE
</button>
<button class="button special" onclick="executeCommand('tease_all')">
TEASE ALL
</button>
<button class="button special" onclick="executeCommand('open')">
OPEN
</button>
<div></div>
</div>
</div>
<script>
let isExpanded = false;
function toggleSidebar() {
const sidebar = document.querySelector('.sidebar');
sidebar.classList.toggle('expanded');
isExpanded = !isExpanded;
}
function executeCommand(command) {
fetch('/execute', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: `command=${command}`
})
.then(response => response.text())
.then(result => {
console.log(result);
// Visual feedback
event.target.style.backgroundColor = '#45a049';
setTimeout(() => {
event.target.style.backgroundColor =
event.target.classList.contains('private') ? '#4CAF50' :
event.target.classList.contains('toggle') ? '#2196F3' :
'#FF9800';
}, 300);
})
.catch(error => {
console.error('Error:', error);
});
}
// Resize functionality
const resizeHandle = document.getElementById('resize-handle');
const sidebar = document.querySelector('.sidebar');
let isResizing = false;
resizeHandle.addEventListener('mousedown', initResize, false);
document.addEventListener('mousemove', resize, false);
document.addEventListener('mouseup', stopResize, false);
function initResize(e) {
isResizing = true;
document.body.style.cursor = 'col-resize';
}
function resize(e) {
if (!isResizing) return;
const containerWidth = window.innerWidth;
let newWidth = e.clientX;
// Limit sidebar width between 50px and 50% of screen
newWidth = Math.max(50, Math.min(newWidth, containerWidth / 2));
sidebar.style.width = `${newWidth}px`;
}
function stopResize() {
isResizing = false;
document.body.style.cursor = '';
}
// Prevent zoom on double-tap for mobile
document.addEventListener('touchmove', function(event) {
if (event.scale !== 1) { event.preventDefault(); }
}, { passive: false });
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>Stream Interface</title>
<!-- Video.js CSS -->
<link href="https://vjs.zencdn.net/7.20.3/video-js.css" rel="stylesheet" />
<style>
body {
margin: 0;
padding: 0;
background-color: #000;
overflow: hidden;
}
#videoContainer {
width: 100%;
height: 100vh;
background-color: #000;
}
/* Prevent text selection and touch callout */
body {
-webkit-user-select: none;
-webkit-touch-callout: none;
user-select: none;
}
</style>
</head>
<body>
<div id="videoContainer">
<video
id="videoElement"
class="video-js vjs-default-skin"
controls
playsinline
width="100%"
height="100%"
>
Your browser does not support the video tag.
</video>
</div>
<!-- Video.js library -->
<script src="https://vjs.zencdn.net/7.20.3/video.min.js"></script>
<script>
const videoElement = document.getElementById('videoElement');
window.onload = function() {
var player = videojs('videoElement', {
html5: {
vhs: {
overrideNative: !videojs.browser.IS_SAFARI
},
nativeAudioTracks: false,
nativeVideoTracks: false
}});
player.src({ src: '{{ stream_url }}', type: 'application/x-mpegURL'});
player.play();
};
// Fullscreen on click
videoElement.addEventListener('click', () => {
if (!document.fullscreenElement) {
if (videoElement.requestFullscreen) {
videoElement.requestFullscreen();
} else if (videoElement.mozRequestFullScreen) { // Firefox
videoElement.mozRequestFullScreen();
} else if (videoElement.webkitRequestFullscreen) { // Chrome, Safari and Opera
videoElement.webkitRequestFullscreen();
} else if (videoElement.msRequestFullscreen) { // IE/Edge
videoElement.msRequestFullscreen();
}
} else {
if (document.exitFullscreen) {
document.exitFullscreen();
} else if (document.mozCancelFullScreen) { // Firefox
document.mozCancelFullScreen();
} else if (document.webkitExitFullscreen) { // Chrome, Safari and Opera
document.webkitExitFullscreen();
} else if (document.msExitFullscreen) { // IE/Edge
document.msExitFullscreen();
}
}
});
// Prevent zoom
document.addEventListener('touchmove', function(event) {
if (event.scale !== 1) {
event.preventDefault();
}
}, { passive: false });
</script>
</body>
</html>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment