Commit b9d880fa authored by nextime's avatar nextime

Split program in modules and share config by using builtins

parent 30905fdb
...@@ -14,26 +14,30 @@ ...@@ -14,26 +14,30 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>. # along with this program. If not, see <https://www.gnu.org/licenses/>.
import builtins
import logging
from logging.handlers import RotatingFileHandler
import configparser
import sys
import argparse import argparse
import os import os
import sys
import signal import signal
import threading import threading
import webbrowser
import logging import logging
from logging.handlers import RotatingFileHandler
import socket
import configparser
from flask import Flask, render_template, request
import subprocess import subprocess
import tkinter as tk
from tkinter import font as tkFont
import vlc
# Read configuration # Read configuration
config = configparser.ConfigParser() config = configparser.ConfigParser()
config.read('config.ini') config.read('shmcamstudio.conf')
# Command Mapping
COMMANDS = dict(config['Commands'])
builtins.config = config
builtins.COMMANDS = COMMANDS
# Setup logging # Setup logging
log_file = config.get('General', 'log_file', fallback='/tmp/streaming_control.log') log_file = config.get('General', 'log_file', fallback='/tmp/streaming_control.log')
...@@ -47,246 +51,23 @@ logging.basicConfig( ...@@ -47,246 +51,23 @@ logging.basicConfig(
logging.StreamHandler(sys.stdout) logging.StreamHandler(sys.stdout)
] ]
) )
logger = logging.getLogger(__name__) logger = logging.getLogger('SHMCamStudio')
# Flask App Setup
flask_app = Flask(__name__)
# Command Mapping
COMMANDS = dict(config['Commands'])
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 = config.get('Web', 'stream_url', fallback="https://192.168.42.1/HLS/record/Live.m3u8")
return render_template('stream.html', stream_url=stream_url)
def create_daemon():
if os.name == 'posix': # Unix-like systems
try:
# First fork
pid = os.fork()
if pid > 0:
# Exit first parent
sys.exit(0)
except OSError as err:
logger.error(f'Fork #1 failed: {err}')
sys.exit(1)
# Decouple from parent environment
os.chdir('/')
os.setsid()
os.umask(0)
# Second fork
try:
pid = os.fork()
if pid > 0:
# Exit from second parent
sys.exit(0)
except OSError as err:
logger.error(f'Fork #2 failed: {err}')
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())
elif os.name == 'nt': # Windows
try:
# Hide the console window
si = subprocess.STARTUPINFO()
si.dwFlags |= subprocess.STARTF_USESHOWWINDOW
# Start the script as a new process
subprocess.Popen([sys.executable, __file__],
startupinfo=si,
creationflags=subprocess.CREATE_NEW_PROCESS_GROUP)
# Exit the current process
sys.exit(0)
except Exception as err:
logger.error(f'Failed to create background process: {err}')
sys.exit(1)
else:
logger.error(f'Unsupported operating system: {os.name}')
sys.exit(1)
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(config.get("Tkinter", "window_title", fallback="SHM CamStudio"))
helv36 = tkFont.Font(family='Helvetica', size=config.get("Tkinter", "font_size", fallback=12), weight='bold')
# Frame for the left side
fleft = tk.Frame(window)
fleft.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
# URL of your RTMP stream
video_url = config.get("General", "rtmp_url", fallback="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 builtins.logger = logger
for i in range(3):
frame2.grid_columnconfigure(i, weight=1)
# Add a button to open web interface builtins.TEMPLATE_DIR=os.path.join(os.path.dirname(__file__), 'templates')
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 from shmcs import run_command, check_port_available, create_daemon
from shmcs.webpanel import run_flask_app
from shmcs.panel import create_panel_gui
def main(): def main():
# Setup argument parser # Setup argument parser
parser = argparse.ArgumentParser(description='Streaming Control Panel') parser = argparse.ArgumentParser(description='Streaming Control Panel')
parser.add_argument('--web-only', action='store_true', parser.add_argument('--nogui', action='store_true',
help='Start only the web interface') help='Do not start the graphical interface')
parser.add_argument('--daemon', action='store_true', parser.add_argument('--daemon', action='store_true',
help='Run in daemon mode (Unix-like systems only)') help='Run in daemon mode (Unix-like systems only, include --nogui)')
parser.add_argument('--port', type=int, default=5000, parser.add_argument('--port', type=int, default=5000,
help='Port for the web interface (default: 5000)') help='Port for the web interface (default: 5000)')
...@@ -295,7 +76,7 @@ def main(): ...@@ -295,7 +76,7 @@ def main():
try: try:
# Daemon mode for web interface # Daemon mode for web interface
if args.web_only or args.daemon: if args.nogui or args.daemon:
run_flask_app(port=args.port, daemon_mode=args.daemon) run_flask_app(port=args.port, daemon_mode=args.daemon)
else: else:
# Start web interface in a background thread # Start web interface in a background thread
...@@ -307,7 +88,7 @@ def main(): ...@@ -307,7 +88,7 @@ def main():
web_thread.start() web_thread.start()
# Launch Tkinter GUI # Launch Tkinter GUI
window = create_tkinter_gui() window = create_panel_gui()
window.mainloop() window.mainloop()
except Exception as e: except Exception as e:
......
...@@ -25,3 +25,10 @@ window_title = SHM Cam Studio ...@@ -25,3 +25,10 @@ window_title = SHM Cam Studio
button_width = 20 button_width = 20
button_height = 2 button_height = 2
font_size = 12 font_size = 12
[OBS:mdma]
host = 192.168.42.115
port = 4455
password = motorol4
import sys, os
sys.path+=[os.path.dirname(__file__)]
from utils import *
# Copyright (C) 2023 Stefy Lanza <stefy@nexlab.net> and SexHack.me
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
import tkinter as tk
from tkinter import font as tkFont
import vlc
import webbrowser
import sys
import os
from utils import run_command
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_panel_gui():
# Create the main window
window = tk.Tk()
window.title(config.get("Tkinter", "window_title", fallback="SHM CamStudio"))
helv36 = tkFont.Font(family='Helvetica', size=config.get("Tkinter", "font_size", fallback=12), weight='bold')
# Frame for the left side
fleft = tk.Frame(window)
fleft.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
# URL of your RTMP stream
video_url = config.get("General", "rtmp_url", fallback="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
# Copyright (C) 2023 Stefy Lanza <stefy@nexlab.net> and SexHack.me
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
import subprocess
import socket
import os
import sys
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}"
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 create_daemon():
if os.name == 'posix': # Unix-like systems
try:
# First fork
pid = os.fork()
if pid > 0:
# Exit first parent
sys.exit(0)
except OSError as err:
logger.error(f'Fork #1 failed: {err}')
sys.exit(1)
# Decouple from parent environment
os.chdir('/')
os.setsid()
os.umask(0)
# Second fork
try:
pid = os.fork()
if pid > 0:
# Exit from second parent
sys.exit(0)
except OSError as err:
logger.error(f'Fork #2 failed: {err}')
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())
elif os.name == 'nt': # Windows
try:
# Hide the console window
si = subprocess.STARTUPINFO()
si.dwFlags |= subprocess.STARTF_USESHOWWINDOW
# Start the script as a new process
subprocess.Popen([sys.executable, __file__],
startupinfo=si,
creationflags=subprocess.CREATE_NEW_PROCESS_GROUP)
# Exit the current process
sys.exit(0)
except Exception as err:
logger.error(f'Failed to create background process: {err}')
sys.exit(1)
else:
logger.error(f'Unsupported operating system: {os.name}')
sys.exit(1)
# Copyright (C) 2023 Stefy Lanza <stefy@nexlab.net> and SexHack.me
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from flask import Flask, render_template, request
from utils import check_port_available, run_command, create_daemon
import sys
import os
# Flask App Setup
flask_app = Flask('SHMCamStudio', template_folder=TEMPLATE_DIR)
@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 = config.get('Web', 'stream_url', fallback="https://192.168.42.1/HLS/record/Live.m3u8")
return render_template('stream.html', stream_url=stream_url)
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)
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