Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Contribute to GitLab
Sign in
Toggle navigation
S
SHMCamStudio
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
1
Merge Requests
1
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
SexHackMe
SHMCamStudio
Commits
5cf2ed06
Commit
5cf2ed06
authored
Nov 09, 2024
by
Robocoders
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Initial commit for SHMCamStudio
parents
Changes
3
Show whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
670 additions
and
0 deletions
+670
-0
shmcamstudio
shmcamstudio
+291
-0
index.html
templates/index.html
+280
-0
stream.html
templates/stream.html
+99
-0
No files found.
shmcamstudio
0 → 100644
View file @
5cf2ed06
#!/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
()
templates/index.html
0 → 100644
View file @
5cf2ed06
<!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
,
1
fr
);
grid-template-rows
:
repeat
(
3
,
1
fr
);
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>
templates/stream.html
0 → 100644
View file @
5cf2ed06
<!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>
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment