feat: add volume dial (#4)
* feat: add volume dial * Set input dial action as untested for keys --------- Co-authored-by: Core447 <100139110+Core447@users.noreply.github.com>
This commit is contained in:
parent
a9dcc5b28e
commit
a8b296a26f
|
@ -0,0 +1,221 @@
|
|||
from plugins.com_core447_OBSPlugin.OBSActionBase import OBSActionBase
|
||||
from src.backend.DeckManagement.DeckController import DeckController
|
||||
from src.backend.DeckManagement.InputIdentifier import Input, InputEvent
|
||||
from src.backend.PageManagement.Page import Page
|
||||
from src.backend.PluginManager.PluginBase import PluginBase
|
||||
from GtkHelper.GtkHelper import ComboRow
|
||||
|
||||
import os
|
||||
import threading
|
||||
import math
|
||||
|
||||
# Import gtk modules
|
||||
import gi
|
||||
gi.require_version("Gtk", "4.0")
|
||||
gi.require_version("Adw", "1")
|
||||
from gi.repository import Gtk, Adw
|
||||
|
||||
class InputDial(OBSActionBase):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.muted = None
|
||||
self.volume = None
|
||||
self.last_muted = None
|
||||
self.last_volume = None
|
||||
|
||||
def on_ready(self):
|
||||
# Connect ot obs if not connected
|
||||
if self.plugin_base.backend is not None:
|
||||
if not self.plugin_base.get_connected():
|
||||
self.reconnect_obs()
|
||||
|
||||
# Show current input volume
|
||||
self.muted = None
|
||||
self.volume = None
|
||||
threading.Thread(target=self.show_current_input_volume, daemon=True, name="show_current_input_volume").start()
|
||||
|
||||
def show_current_input_volume(self):
|
||||
if self.plugin_base.backend is None:
|
||||
self.current_state = None
|
||||
self.show_error()
|
||||
self.set_media(media_path=os.path.join(self.plugin_base.PATH, "assets", "error.png"))
|
||||
return
|
||||
if not self.plugin_base.backend.get_connected():
|
||||
self.current_state = None
|
||||
self.show_error()
|
||||
self.set_media(media_path=os.path.join(self.plugin_base.PATH, "assets", "error.png"))
|
||||
return
|
||||
if not self.get_settings().get("input"):
|
||||
self.current_state = None
|
||||
self.show_error()
|
||||
self.set_media(media_path=os.path.join(self.plugin_base.PATH, "assets", "error.png"))
|
||||
return
|
||||
|
||||
# update muted
|
||||
status = self.plugin_base.backend.get_input_muted(self.get_settings().get("input"))
|
||||
if status is None:
|
||||
self.current_state = -1
|
||||
self.show_error()
|
||||
self.set_media(media_path=os.path.join(self.plugin_base.PATH, "assets", "error.png"))
|
||||
return
|
||||
self.muted = status["muted"]
|
||||
|
||||
# update volume
|
||||
status = self.plugin_base.backend.get_input_volume(self.get_settings().get("input"))
|
||||
if status is None:
|
||||
self.current_state = -1
|
||||
self.show_error()
|
||||
self.set_media(media_path=os.path.join(self.plugin_base.PATH, "assets", "error.png"))
|
||||
return
|
||||
self.volume = self.db_to_volume(status["volume"])
|
||||
|
||||
# Now render the button
|
||||
image = "input_muted.png" if self.muted else "input_unmuted.png"
|
||||
label = f"{self.volume}%"
|
||||
|
||||
if self.last_muted != self.muted:
|
||||
self.last_muted = self.muted
|
||||
self.set_media(media_path=os.path.join(self.plugin_base.PATH, "assets", image), size=0.9)
|
||||
if self.last_volume != self.volume:
|
||||
self.last_volume = self.volume
|
||||
self.set_label(label)
|
||||
|
||||
def get_config_rows(self) -> list:
|
||||
super_rows = super().get_config_rows()
|
||||
|
||||
self.input_model = Gtk.StringList()
|
||||
self.input_row = Adw.ComboRow(model=self.input_model, title=self.plugin_base.lm.get("actions.input-dial-row.label"))
|
||||
|
||||
self.connect_signals()
|
||||
|
||||
self.load_input_model()
|
||||
self.load_configs()
|
||||
|
||||
super_rows.append(self.input_row)
|
||||
return super_rows
|
||||
|
||||
def connect_signals(self):
|
||||
self.input_row.connect("notify::selected", self.on_input_change)
|
||||
|
||||
def disconnect_signals(self):
|
||||
try:
|
||||
self.input_row.disconnect_by_func(self.on_input_change)
|
||||
except TypeError as e:
|
||||
pass
|
||||
|
||||
def load_input_model(self):
|
||||
self.disconnect_signals()
|
||||
# Clear model
|
||||
while self.input_model.get_n_items() > 0:
|
||||
self.input_model.remove(0)
|
||||
|
||||
# Load model
|
||||
if self.plugin_base.backend.get_connected():
|
||||
inputs = self.plugin_base.backend.get_inputs()
|
||||
if inputs is None:
|
||||
self.set_media(media_path=os.path.join(self.plugin_base.PATH, "assets", "error.png"))
|
||||
return
|
||||
for input in inputs:
|
||||
self.input_model.append(input)
|
||||
|
||||
self.connect_signals()
|
||||
|
||||
def load_configs(self):
|
||||
self.load_selected_device()
|
||||
|
||||
def load_selected_device(self):
|
||||
self.disconnect_signals()
|
||||
settings = self.get_settings()
|
||||
for i, input_name in enumerate(self.input_model):
|
||||
if input_name.get_string() == settings.get("input"):
|
||||
self.input_row.set_selected(i)
|
||||
self.connect_signals()
|
||||
return
|
||||
|
||||
self.input_row.set_selected(Gtk.INVALID_LIST_POSITION)
|
||||
self.connect_signals()
|
||||
|
||||
def on_input_change(self, *args):
|
||||
settings = self.get_settings()
|
||||
selected_index = self.input_row.get_selected()
|
||||
settings["input"] = self.input_model[selected_index].get_string()
|
||||
self.set_settings(settings)
|
||||
|
||||
def event_callback(self, event: InputEvent, data: dict = None):
|
||||
if event == Input.Key.Events.DOWN or event == Input.Dial.Events.DOWN:
|
||||
self.mute_toggle()
|
||||
if str(event) == str(Input.Dial.Events.TURN_CW):
|
||||
self.volume_change(+5)
|
||||
if str(event) == str(Input.Dial.Events.TURN_CCW):
|
||||
self.volume_change(-5)
|
||||
|
||||
def mute_toggle(self):
|
||||
if self.plugin_base.backend is None:
|
||||
self.current_state = None
|
||||
self.show_error()
|
||||
self.set_media(media_path=os.path.join(self.plugin_base.PATH, "assets", "error.png"))
|
||||
return
|
||||
if not self.plugin_base.backend.get_connected():
|
||||
self.current_state = None
|
||||
self.show_error()
|
||||
self.set_media(media_path=os.path.join(self.plugin_base.PATH, "assets", "error.png"))
|
||||
return
|
||||
|
||||
input_name = self.get_settings().get("input")
|
||||
if input_name in [None, ""]:
|
||||
self.set_media(media_path=os.path.join(self.plugin_base.PATH, "assets", "error.png"))
|
||||
return
|
||||
|
||||
self.muted = not self.muted
|
||||
self.plugin_base.backend.set_input_muted(input_name, self.muted)
|
||||
self.on_tick()
|
||||
|
||||
def volume_change(self, diff):
|
||||
if self.plugin_base.backend is None:
|
||||
self.current_state = None
|
||||
self.show_error()
|
||||
self.set_media(media_path=os.path.join(self.plugin_base.PATH, "assets", "error.png"))
|
||||
return
|
||||
if not self.plugin_base.backend.get_connected():
|
||||
self.current_state = None
|
||||
self.show_error()
|
||||
self.set_media(media_path=os.path.join(self.plugin_base.PATH, "assets", "error.png"))
|
||||
return
|
||||
|
||||
input_name = self.get_settings().get("input")
|
||||
if input_name in [None, ""]:
|
||||
self.set_media(media_path=os.path.join(self.plugin_base.PATH, "assets", "error.png"))
|
||||
return
|
||||
|
||||
self.volume += diff
|
||||
if self.volume < 0:
|
||||
self.volume = 0
|
||||
if self.volume > 100:
|
||||
self.volume = 100
|
||||
self.plugin_base.backend.set_input_volume(input_name, self.volume_to_db(self.volume))
|
||||
self.on_tick()
|
||||
|
||||
def on_tick(self):
|
||||
self.show_current_input_volume()
|
||||
|
||||
def reconnect_obs(self):
|
||||
super().reconnect_obs()
|
||||
if hasattr(self, "input_model"):
|
||||
self.load_input_model()
|
||||
self.load_configs()
|
||||
self.muted = None
|
||||
self.volume = None
|
||||
|
||||
def volume_to_db(self, vol):
|
||||
if vol == 0:
|
||||
return -100
|
||||
if vol > 100:
|
||||
return 0
|
||||
return math.log(vol/100)*10/math.log(1.5)
|
||||
|
||||
def db_to_volume(self, db):
|
||||
if db < -100:
|
||||
return 0
|
||||
if db > 0:
|
||||
return 100
|
||||
return math.floor(1.5**(db/10) * 100)
|
|
@ -230,7 +230,7 @@ class OBSController(obsws):
|
|||
log.error(e)
|
||||
|
||||
|
||||
## Input Muting
|
||||
## Input mixer
|
||||
def get_inputs(self) -> list:
|
||||
try:
|
||||
inputs = self.call(requests.GetInputList()).getInputs()
|
||||
|
@ -259,6 +259,24 @@ class OBSController(obsws):
|
|||
except (obswebsocket.exceptions.MessageTimeout, websocket._exceptions.WebSocketConnectionClosedException, KeyError) as e:
|
||||
log.error(e)
|
||||
|
||||
def get_input_volume(self, input: str):
|
||||
try:
|
||||
request = self.call(requests.GetInputVolume(inputName=input))
|
||||
|
||||
if not request.datain:
|
||||
log.warning("Cannot find the input!")
|
||||
return
|
||||
else:
|
||||
return request
|
||||
except (obswebsocket.exceptions.MessageTimeout, websocket._exceptions.WebSocketConnectionClosedException, KeyError) as e:
|
||||
log.error(e)
|
||||
|
||||
def set_input_volume(self, input: str, volume: int) -> None:
|
||||
try:
|
||||
self.call(requests.SetInputVolume(inputName=input, inputVolumeDb=volume))
|
||||
except (obswebsocket.exceptions.MessageTimeout, websocket._exceptions.WebSocketConnectionClosedException, KeyError) as e:
|
||||
log.error(e)
|
||||
|
||||
|
||||
## Scene Items
|
||||
def get_scene_items(self, sceneName: str) -> list:
|
||||
|
|
|
@ -121,7 +121,7 @@ class Backend(BackendBase):
|
|||
def trigger_transition(self):
|
||||
self.OBSController.trigger_transition()
|
||||
|
||||
# Input Muting
|
||||
# Input Mixing
|
||||
def get_inputs(self) -> list[str]:
|
||||
return self.OBSController.get_inputs()
|
||||
|
||||
|
@ -136,6 +136,17 @@ class Backend(BackendBase):
|
|||
def set_input_muted(self, input: str, muted: bool):
|
||||
self.OBSController.set_input_muted(input, muted)
|
||||
|
||||
def get_input_volume(self, input: str):
|
||||
status = self.OBSController.get_input_volume(input)
|
||||
if status is None:
|
||||
return
|
||||
return {
|
||||
"volume": status.datain["inputVolumeDb"]
|
||||
}
|
||||
|
||||
def set_input_volume(self, input: str, volume: int):
|
||||
self.OBSController.set_input_volume(input, volume)
|
||||
|
||||
# Scenes
|
||||
def get_scene_names(self) -> list[str]:
|
||||
return self.OBSController.get_scenes()
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
|
||||
"actions.toggle-input-mute-row.label": "Input:",
|
||||
"actions.toggle-input-mute.name": "Toggle Input Mute",
|
||||
"actions.input-dial-row.label": "Input:",
|
||||
"actions.input-dial.name": "Input Dial",
|
||||
|
||||
"actions.switch.scene-row.label": "Scene:",
|
||||
"actions.switch-scene.name": "Switch Scene",
|
||||
|
|
16
main.py
16
main.py
|
@ -35,6 +35,7 @@ from actions.ToggleStudioMode.ToggleStudioMode import ToggleStudioMode
|
|||
from actions.TriggerTransition.TriggerTransition import TriggerTransition
|
||||
|
||||
from actions.ToggleInputMute.ToggleInputMute import ToggleInputMute
|
||||
from actions.InputDial.InputDial import InputDial
|
||||
|
||||
from actions.SwitchScene.SwitchScene import SwitchScene
|
||||
from actions.ToggleSceneItemEnabled.ToggleSceneItemEnabled import ToggleSceneItemEnabled
|
||||
|
@ -169,7 +170,7 @@ class OBS(PluginBase):
|
|||
)
|
||||
self.add_action_holder(trigger_transition_action_holder)
|
||||
|
||||
# Input Muting
|
||||
# Input mixing
|
||||
toggle_input_mute_action_holder = ActionHolder(
|
||||
plugin_base=self,
|
||||
action_base=ToggleInputMute,
|
||||
|
@ -183,6 +184,19 @@ class OBS(PluginBase):
|
|||
)
|
||||
self.add_action_holder(toggle_input_mute_action_holder)
|
||||
|
||||
input_dial_holder = ActionHolder(
|
||||
plugin_base=self,
|
||||
action_base=InputDial,
|
||||
action_id_suffix="InputDial",
|
||||
action_name=self.lm.get("actions.input-dial.name"),
|
||||
action_support={
|
||||
Input.Key: ActionInputSupport.UNTESTED,
|
||||
Input.Dial: ActionInputSupport.SUPPORTED,
|
||||
Input.Touchscreen: ActionInputSupport.UNSUPPORTED,
|
||||
}
|
||||
)
|
||||
self.add_action_holder(input_dial_holder)
|
||||
|
||||
# Scenes
|
||||
switch_scene_action_holder = ActionHolder(
|
||||
plugin_base=self,
|
||||
|
|
Loading…
Reference in New Issue