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
7 months ago
by
nextime
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Internal communication for OBS events
parent
9b7a4469
master
No related merge requests found
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
...
...
This diff is collapsed.
Click to expand it.
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
...
...
This diff is collapsed.
Click to expand it.
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
)
This diff is collapsed.
Click to expand it.
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'
]):
...
...
This diff is collapsed.
Click to expand it.
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
()
...
...
This diff is collapsed.
Click to expand it.
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
)
...
...
This diff is collapsed.
Click to expand it.
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
)
This diff is collapsed.
Click to expand it.
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