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
9ca09ce5
Commit
9ca09ce5
authored
Dec 23, 2024
by
nextime
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Internal communication for OBS events
parent
9b7a4469
Changes
7
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
277 additions
and
36 deletions
+277
-36
shmcamstudio
shmcamstudio
+4
-1
shmcamstudio.conf
shmcamstudio.conf
+40
-15
obs.py
shmcs/obs.py
+141
-8
panel.py
shmcs/panel.py
+5
-2
studio.py
shmcs/studio.py
+31
-0
utils.py
shmcs/utils.py
+1
-2
webpanel.py
shmcs/webpanel.py
+55
-8
No files found.
shmcamstudio
View file @
9ca09ce5
...
...
@@ -34,9 +34,12 @@ config = configparser.ConfigParser()
config
.
read
(
'shmcamstudio.conf'
)
qcore
=
queue
.
Queue
()
# Inter thread communication
qcore
=
queue
.
Queue
()
# IN -> core application (studio.py)
qobs
=
queue
.
Queue
()
# IN -> OBS module
builtins
.
qcore
=
qcore
builtins
.
qobs
=
qobs
builtins
.
config
=
config
# Setup logging
...
...
shmcamstudio.conf
View file @
9ca09ce5
...
...
@@ -13,11 +13,22 @@ button_width = 20
button_height
=
2
font_size
=
12
[
OBS
:
mdma
]
host
=
192
.
168
.
42
.
115
#[OBS:mdma]
#host = 192.168.42.115
#port = 4455
#password = motorol4
[
OBS
:
slut
]
host
=
192
.
168
.
42
.
125
port
=
4455
password
=
motorol4
[
OBS
:
leeloo
]
host
=
192
.
168
.
42
.
111
port
=
4455
password
=
motorol4
# OUTPUT are shenes on OBS sending to a specific stream, can be a rtmp stream with
# OBS multistream plugin or a virtual cam, both the included in OBS or the ones
...
...
@@ -26,23 +37,25 @@ password = motorol4
[
OUTPUT
:
smleeloo
]
obs
=
leeloo
scene
=
LIVE
SFW
source_title
=
5
source_closed
=
4
source_tease
=
3
status
.
open
.
disable
=
5
,
4
,
3
status
.
close
.
disable
=
3
status
.
close
.
enable
=
5
,
4
status
.
tease
.
disable
=
4
status
.
tease
.
enable
=
5
,
3
[
OUTPUT
:
smstefy
]
obs
=
slut
scene
=
live
SFW
source
_
title
=
5
source
_
closed
=
4
source
_
tease
=
3
source
.
title
=
5
source
.
closed
=
4
source
.
tease
=
3
[
OUTPUT
:
shine
]
obs
=
slut
scene
=
SHINE
source
_
title
=
4
source
_
closed
=
2
source
_
tease
=
3
source
.
title
=
4
source
.
closed
=
2
source
.
tease
=
3
#[OUTPUT:livejasmin]
#obs = slut
...
...
@@ -83,17 +96,29 @@ color = green
[
BUTTON
:
2
:
shine_openclose
]
title
=
Open
/
Close
Shine
action
=
shine_openclose
color
=
orange
output
=
shine
color
.
close
=
red
color
.
open
=
green
color
.
tease
=
orange
color
=
grey
[
BUTTON
:
2
:
stefy_openclose
]
title
=
Open
/
Close
Stefy
action
=
stefy_openclose
color
=
orange
output
=
smstefy
color
.
close
=
red
color
.
open
=
green
color
.
tease
=
orange
color
=
grey
[
BUTTON
:
2
:
leelo_openclose
]
title
=
Open
/
Close
Leeloo
action
=
leelo_openclose
color
=
orange
output
=
smleeloo
color
.
close
=
red
color
.
open
=
green
color
.
tease
=
orange
color
=
grey
#[BUTTON:2:leelo_livejasmine]
#title = Open/Close JASM
...
...
@@ -142,7 +167,7 @@ execute = /usr/local/bin/smblur_private_leeloo
#execute = /usr/local/bin/smblur_private_jasmin
[
ACTION
:
leelo_openclose
]
execute
= /
usr
/
local
/
bin
/
smblur_leelo
execute
= /
usr
/
local
/
bin
/
smblur_leelo
o
[
ACTION
:
shine_openclose
]
execute
= /
usr
/
local
/
bin
/
smblur_shine
...
...
shmcs/obs.py
View file @
9ca09ce5
...
...
@@ -15,7 +15,92 @@
import
obsws_python
as
obs
import
time
from
contextlib
import
suppress
import
websocket
import
queue
import
logging
obws_logger
=
logging
.
getLogger
(
"obsws_python.baseclient"
)
obws_logger
.
setLevel
(
logging
.
CRITICAL
)
logger
=
logging
.
getLogger
(
__name__
)
class
OBSOutput
:
def
__init__
(
self
,
output
,
config_section
,
obss
):
self
.
config_section
=
config_section
self
.
output
=
output
self
.
status
=
False
self
.
obss
=
obss
self
.
config_options
=
config
.
options
(
config_section
)
self
.
config_section
=
config_section
self
.
statuses
=
{}
self
.
inputs
=
{}
for
c
in
self
.
config_options
:
if
'status.'
in
c
:
for
inp
in
config
.
get
(
config_section
,
c
)
.
split
(
','
):
if
inp
not
in
self
.
inputs
.
keys
():
self
.
inputs
[
inp
]
=
False
if
c
.
split
(
'.'
)[
1
]
not
in
self
.
statuses
.
keys
():
enabled
=
[]
disanled
=
[]
if
config
.
get
(
config_section
,
"status."
+
c
.
split
(
"."
)[
1
]
+
".disable"
,
fallback
=
False
):
disabled
=
config
.
get
(
config_section
,
"status."
+
c
.
split
(
"."
)[
1
]
+
".disable"
,
fallback
=
False
)
.
split
(
','
)
if
config
.
get
(
config_section
,
"status."
+
c
.
split
(
"."
)[
1
]
+
".enable"
,
fallback
=
False
):
enabled
=
config
.
get
(
config_section
,
"status."
+
c
.
split
(
"."
)[
1
]
+
".enable"
,
fallback
=
""
)
.
split
(
','
)
self
.
statuses
[
c
.
split
(
'.'
)[
1
]]
=
{
'disable'
:
disabled
,
'enable'
:
enabled
,
}
setattr
(
self
,
c
,
config
.
get
(
config_section
,
c
))
#print(getattr(self, 'source.closed'))
#print(dir(self))
def
disable
(
self
,
sources
):
pass
def
enable
(
self
,
sources
):
pass
def
setStatus
(
self
,
status
):
if
hasattr
(
self
,
'status.'
+
status
+
'.disable'
):
self
.
disable
(
getattr
(
self
,
'status.'
+
status
+
'.disable'
)
.
split
(
','
))
if
hasattr
(
self
,
'status.'
+
status
+
'.enable'
):
self
.
enable
(
getattr
(
self
,
'status.'
+
status
+
'.enable'
)
.
split
(
','
))
def
updateStatus
(
self
):
if
not
self
.
obss
.
online
:
self
.
status
=
False
logging
.
info
(
'FOUND False'
)
return
False
for
inp
in
self
.
inputs
.
keys
():
self
.
inputs
[
inp
]
=
self
.
obss
.
getInputStatus
(
self
.
scene
,
inp
)
found
=
False
for
status
in
self
.
statuses
.
keys
():
if
not
found
:
found
=
status
for
enabled
in
self
.
statuses
[
status
][
'enable'
]:
if
not
self
.
inputs
[
enabled
]:
found
=
False
if
found
:
for
disabled
in
self
.
statuses
[
status
][
'disable'
]:
if
self
.
inputs
[
disabled
]:
found
=
False
if
found
:
self
.
status
=
found
logging
.
info
(
"FOUND "
+
found
)
return
found
logging
.
info
(
'FOUND False'
)
self
.
status
=
found
return
found
def
getStatus
(
self
):
if
not
self
.
status
:
self
.
status
=
self
.
updateStatus
()
return
self
.
status
class
OBSControl
:
...
...
@@ -24,8 +109,11 @@ class OBSControl:
self
.
last_try
=
time
.
time
()
-
11
self
.
lastping
=
time
.
time
()
-
20
self
.
server
=
obs_server
self
.
queue
=
queue
.
Queue
()
self
.
config_section
=
config_section
self
.
config_options
=
config
.
options
(
config_section
)
self
.
cl
=
False
self
.
cr
=
False
for
c
in
self
.
config_options
:
setattr
(
self
,
c
,
config
.
get
(
config_section
,
c
))
...
...
@@ -38,18 +126,26 @@ class OBSControl:
self
.
cr
=
obs
.
ReqClient
(
host
=
self
.
host
,
port
=
self
.
port
,
password
=
self
.
password
)
self
.
cl
.
callback
.
register
(
self
.
on_scene_item_enable_state_changed
)
self
.
setonline
()
except
Exception
:
except
:
pass
def
getInputStatus
(
self
,
scene
,
inp
):
if
self
.
cr
:
return
self
.
cr
.
get_scene_item_enabled
(
scene
,
int
(
inp
))
.
scene_item_enabled
return
False
def
setonline
(
self
):
logging
.
info
(
'OBS '
+
self
.
server
+
' ONLINE'
)
self
.
online
=
True
self
.
queue
.
put
({
'event'
:
'SETONLINE'
,
'data'
:
{
'server'
:
self
.
server
}})
def
setoffline
(
self
):
self
.
online
=
False
self
.
queue
.
put
({
'event'
:
'SETOFFLINE'
,
'data'
:
{
'server'
:
self
.
server
}})
def
run_tasks
(
self
):
if
not
self
.
online
and
time
.
time
()
-
self
.
last_try
>
10
:
print
(
"OBS "
,
self
.
server
,
"
starting... "
)
logging
.
info
(
"OBS "
+
self
.
server
+
"
starting... "
)
self
.
start
()
if
self
.
online
:
...
...
@@ -57,22 +153,36 @@ class OBSControl:
try
:
self
.
cr
.
broadcast_custom_event
({
'eventData'
:
{
'eventType'
:
'Ping'
,
'time'
:
time
.
time
()}})
self
.
lastping
=
time
.
time
()
logging
.
info
(
"SENT Ping to "
+
self
.
server
)
except
:
self
.
setoffline
()
def
on_scene_item_enable_state_changed
(
self
,
data
):
print
(
"scscene_item_enable_state_changed"
)
print
(
data
.
attrs
())
print
(
data
.
scene_item_enabled
,
data
.
scene_item_id
,
data
.
scene_name
,
data
.
scene_uuid
)
logging
.
info
(
"scscene_item_enable_state_changed"
)
logging
.
info
(
data
.
attrs
())
logging
.
info
([
data
.
scene_item_enabled
,
data
.
scene_item_id
,
data
.
scene_name
,
data
.
scene_uuid
]
)
self
.
queue
.
put
({
'event'
:
'INPUTCHANGE'
,
'data'
:
{
'server'
:
self
.
server
,
'status'
:
data
.
scene_item_enabled
,
'scene'
:
data
.
scene_name
,
'source'
:
data
.
scene_item_id
}})
def
run_obs_controller
():
obs_servers
=
{}
for
k
in
[
x
for
x
in
config
.
sections
()
if
'OBS:'
in
x
]:
if
not
config
.
get
(
k
,
'active'
,
fallback
=
False
):
obs_servers
[
k
.
split
(
":"
,
1
)[
1
]]
=
OBSControl
(
k
.
split
(
":"
,
1
)[
1
],
k
)
#if not config.get(k, 'active', fallback=False):
obs_servers
[
k
.
split
(
":"
,
1
)[
1
]]
=
OBSControl
(
k
.
split
(
":"
,
1
)[
1
],
k
)
obs_outputs
=
{}
for
k
in
[
x
for
x
in
config
.
sections
()
if
'OUTPUT:'
in
x
]:
obss
=
False
if
config
.
get
(
k
,
'obs'
)
in
obs_servers
.
keys
():
obss
=
obs_servers
[
config
.
get
(
k
,
'obs'
)]
obs_outputs
[
k
.
split
(
":"
,
1
)[
1
]]
=
OBSOutput
(
k
.
split
(
":"
,
1
)[
1
],
k
,
obss
)
logging
.
info
(
'OUTPUT '
+
config
.
get
(
k
,
'obs'
)
+
" -> "
+
k
.
split
(
":"
,
1
)[
1
])
logging
.
info
(
obs_outputs
[
k
.
split
(
":"
,
1
)[
1
]]
.
getStatus
())
#cl = obs.EventClient(host='192.168.42.115', port=4455, password='motorol4')
#cr = obs.ReqClient(host='192.168.42.115', port=4455, password='motorol4')
...
...
@@ -85,10 +195,33 @@ def run_obs_controller():
now
=
time
.
time
()
-
30
while
True
:
if
not
qobs
.
empty
():
task
=
qobs
.
get
(
block
=
True
)
if
task
:
logging
.
info
(
'TASK INCOMING FOR OBS'
)
logging
.
info
(
task
)
if
(
time
.
time
()
-
now
)
>
30
:
now
=
time
.
time
()
#cr.broadcast_custom_event({'eventData': {'eventType': 'Ping', 'time': time.time()}})
for
o
in
obs_servers
.
keys
():
obs_servers
[
o
]
.
run_tasks
()
if
not
obs_servers
[
o
]
.
queue
.
empty
():
task
=
obs_servers
[
o
]
.
queue
.
get
(
block
=
True
)
if
task
:
logging
.
info
(
'EVENT COMING FROM OBS '
+
o
)
logging
.
info
(
task
)
event
=
task
[
'event'
]
data
=
task
[
'data'
]
if
event
==
'SETOFFLINE'
or
event
==
'SETONLINE'
:
for
output
in
obs_outputs
.
values
():
if
output
.
obss
.
server
==
data
[
'server'
]:
output
.
updateStatus
()
if
event
==
'INPUTCHANGE'
:
#'data': {'server': 'leeloo', 'status': False, 'scene': 'LIVE SFW', 'source': 5}}
for
output
in
obs_outputs
.
values
():
if
output
.
obss
.
server
==
data
[
'server'
]
and
output
.
scene
==
data
[
'scene'
]
and
str
(
data
[
'source'
])
in
output
.
inputs
.
keys
():
output
.
updateStatus
()
time
.
sleep
(
.01
)
shmcs/panel.py
View file @
9ca09ce5
...
...
@@ -22,6 +22,10 @@ import os
from
utils
import
run_command
,
run_action
from
guiutils
import
get_buttons
import
queue
import
logging
logging
.
getLogger
(
__name__
)
class
VideoPlayer
:
def
__init__
(
self
,
master
,
video_url
):
...
...
@@ -81,7 +85,6 @@ def create_panel_gui():
# Buttons configuration
buttons
,
numrows
=
get_buttons
()
print
(
numrows
,
buttons
)
bh
=
int
(
55
/
numrows
)
...
...
@@ -98,7 +101,7 @@ def create_panel_gui():
# add the buttons
col
=
0
print
(
buttons
[
row
])
logging
.
info
(
buttons
[
row
])
for
b
in
buttons
[
row
]
.
keys
():
command
=
None
if
config
.
has_section
(
'ACTION:'
+
buttons
[
row
][
b
][
'action'
]):
...
...
shmcs/studio.py
View file @
9ca09ce5
...
...
@@ -15,11 +15,42 @@
import
time
import
sys
from
utils
import
create_daemon
import
queue
import
logging
logging
.
getLogger
(
__name__
)
STATUSES
=
[
'manual'
,
'open'
,
'close'
]
class
TaskEngine
():
status
=
'init'
def
process_task
(
self
,
task
):
cmd
,
val
=
task
.
split
(
':'
,
1
)
if
cmd
==
'SETSTATUS'
and
val
in
STATUSES
:
logging
.
info
(
'SETSTATUS TO '
+
val
)
if
self
.
status
!=
val
:
self
.
status
=
val
def
camstudio
():
engine
=
TaskEngine
()
while
True
:
task
=
qcore
.
get
(
block
=
True
)
if
task
:
logging
.
info
(
task
)
engine
.
process_task
(
task
)
qcore
.
task_done
()
time
.
sleep
(
.001
)
def
run_camstudio
(
daemon
=
False
):
if
daemon
and
sys
.
platform
!=
'win32'
:
create_daemon
()
...
...
shmcs/utils.py
View file @
9ca09ce5
...
...
@@ -33,8 +33,7 @@ def run_command(command):
def
run_action
(
command
,
setstatus
=
None
):
if
setstatus
:
print
(
'SETSTATUS:'
+
str
(
setstatus
))
qcore
.
put
(
'SETSTATUS:'
+
str
(
setstatus
))
qcore
.
put
(
'SETSTATUS:'
+
str
(
setstatus
),
block
=
False
)
if
command
:
return
run_command
(
command
)
...
...
shmcs/webpanel.py
View file @
9ca09ce5
...
...
@@ -14,13 +14,54 @@
# 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
from
utils
import
check_port_available
,
run_command
,
create_daemon
,
run_action
import
sys
import
os
from
guiutils
import
get_buttons
import
flask_restful
as
restful
# Flask App Setup
flask_app
=
Flask
(
'SHMCamStudio'
,
template_folder
=
TEMPLATE_DIR
)
flask_api
=
restful
.
Api
(
flask_app
)
class
PollAPI
(
restful
.
Resource
):
def
_is_updated
(
self
,
request_time
):
"""
Returns if resource is updated or it's the first
time it has been requested.
args:
request_time: last request timestamp
"""
return
os
.
stat
(
'data.txt'
)
.
st_mtime
>
request_time
def
get
(
self
):
"""
Returns 'data.txt' content when the resource has
changed after the request time
"""
request_time
=
time
.
time
()
while
not
self
.
_is_updated
(
request_time
):
time
.
sleep
(
0.5
)
content
=
''
with
open
(
'data.txt'
)
as
data
:
content
=
data
.
read
()
return
{
'content'
:
content
,
'date'
:
datetime
.
now
()
.
strftime
(
'
%
Y/
%
m/
%
d
%
H:
%
M:
%
S'
)}
class
AppData
(
restful
.
Resource
):
def
get
(
self
):
"""
Returns the current data content
"""
content
=
''
with
open
(
'data.txt'
)
as
data
:
content
=
data
.
read
()
return
{
'content'
:
content
}
@
flask_app
.
route
(
'/'
)
def
index
():
...
...
@@ -38,10 +79,13 @@ def index():
for
b
in
buttons
[
row
]
.
keys
():
command
=
buttons
[
row
][
b
][
'action'
]
color
=
buttons
[
row
][
b
][
'color'
]
htmlbuttons
=
htmlbuttons
+
"""<button style="color:white;background-color:"""
+
color
+
""";" class="button private button_row"""
+
str
(
row
)
+
"""" onclick="executeCommand('"""
+
command
+
"""')">
pollclass
=
''
if
'output'
in
buttons
[
row
][
b
]
.
keys
():
pollclass
=
'output_'
+
buttons
[
row
][
b
][
'output'
]
htmlbuttons
=
htmlbuttons
+
"""<button style="color:white;background-color:"""
+
color
+
""";"
class="button private button_row"""
+
str
(
row
)
+
" "
+
pollclass
+
""" " onclick="executeCommand('"""
+
command
+
"""')">
"""
+
buttons
[
row
][
b
][
'title'
]
+
"""
</button>"""
</button>"""
htmlbuttons
=
htmlbuttons
+
"</div>"
row
=
row
+
1
...
...
@@ -55,11 +99,12 @@ def execute():
if
config
.
has_section
(
'ACTION:'
+
command_key
):
command
=
config
.
get
(
'ACTION:'
+
command_key
,
'execute'
,
fallback
=
None
)
if
command
:
result
=
run_command
(
command
)
setstatus
=
config
.
get
(
'ACTION:'
+
command_key
,
'setstatus'
,
fallback
=
None
)
if
command
or
setstatus
:
result
=
run_action
(
command
,
setstatus
)
else
:
return
"No command available"
return
result
return
result
or
'OK'
else
:
return
"Invalid command"
,
400
...
...
@@ -80,6 +125,8 @@ def run_flask_app(port=5000, daemon_mode=False):
if
daemon_mode
and
sys
.
platform
!=
'win32'
:
create_daemon
()
flask_api
.
add_resource
(
PollAPI
,
'/update'
)
flask_api
.
add_resource
(
AppData
,
'/data'
)
flask_app
.
run
(
host
=
'0.0.0.0'
,
port
=
port
,
debug
=
False
,
use_reloader
=
False
)
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