77 Commits

Author SHA1 Message Date
daa8c6ada1 adds Events::Client.on method
allows registering blocks to be called back later

examples, readme updated

minor bump
2023-08-17 23:09:32 +01:00
662f14282f move identified before start_driver 2023-08-17 16:28:43 +01:00
2e0e584d3a refactor 2023-08-17 16:18:50 +01:00
a8425cf7cd refactor 2023-08-17 16:13:57 +01:00
7e580dc91a check identified state in Event::Client
add passwordless test for event client

patch bump
2023-08-11 22:12:28 +01:00
92174219a7 patch bump 2023-08-11 22:05:28 +01:00
0c71eb2398 added no password error test 2023-08-11 22:05:14 +01:00
3ea21cd371 Identified class added. tracks identified state 2023-08-11 22:05:01 +01:00
70b60b3cac reword 2023-08-11 18:23:42 +01:00
c97d14abe2 move logger back into rescue block 2023-08-11 17:10:17 +01:00
123b9c55ca patch bump 2023-08-11 17:05:47 +01:00
299351cac0 refactor tests 2023-08-11 17:05:33 +01:00
3ef4396885 request ids are now UUID's. 2023-08-11 17:04:38 +01:00
c6bb8d07ff patch bump 2023-08-11 16:15:42 +01:00
48b94a2682 remove running reader method 2023-08-11 16:15:27 +01:00
210d13ba1e update main example to print mute state 2023-08-11 16:15:17 +01:00
59bcf2a338 assign timeout_sec directly 2023-08-11 16:14:53 +01:00
dc8ac155ec patch bump 2023-08-11 14:45:02 +01:00
23d64ef9d8 test files renamed with test_ prefix
error tests added

Rakefile updated with new test file names

event tasks moved into :e namespace
2023-08-11 14:44:51 +01:00
9be9dc80a2 adds connect_timeout kwarg for base class 2023-08-11 14:41:35 +01:00
a40ab77be9 cleanup error messages 2023-08-11 14:41:09 +01:00
b440ace20c fix req_name
patch bump
2023-08-11 13:18:28 +01:00
f5a817ab4e fix req_name
patch bump
2023-08-11 13:16:27 +01:00
13f57f79f6 rename name to req_name in OBSWSRequestError
patch bump
2023-08-11 13:10:44 +01:00
976c8f19a8 new error classes
OBSWSConnectionError, OBSWSRequestError added

they subclass OBSWSError

readme updated with new error classes
2023-08-11 02:22:14 +01:00
515fa565d4 use conditional assignment
patch bump
2023-08-06 11:33:15 +01:00
46bfb53db8 add docstring 2023-08-03 14:39:44 +01:00
f669498c69 remove_observers now accepts array of callbacks
callbacks aliases observers

patch bump
2023-08-03 14:15:46 +01:00
aeec0635ca add and control flow operator 2023-07-30 00:06:39 +01:00
86b84aeef9 minor syntax changes 2023-07-28 19:05:28 +01:00
e4f4961c56 upd Gemfile.lock 2023-07-27 15:08:33 +01:00
4fdebc8178 Callbacks module extended
Now supports callback methods as well observer classes

levels example now uses callback methods

register,deregister now alias add/remove observer methods

minor version bump
2023-07-27 14:55:00 +01:00
155cbe019a upd Gemfile.lock 2023-07-26 19:53:04 +01:00
6293ae7b8c patch bump 2023-07-26 18:55:57 +01:00
57fca646b5 remove the monkey patching 2023-07-26 18:55:35 +01:00
d12a1a5954 refactor Callbacks 2023-07-26 18:55:19 +01:00
438f3b1659 upd Gemfile.lock 2023-07-26 17:27:41 +01:00
d15418a660 mixin only methods for directing the driver
patch bump
2023-07-26 16:55:59 +01:00
2883fd42cc Socket class and driver methods
moved into Driver module

patch bump
2023-07-26 16:38:36 +01:00
88b2eabc0c typo fix 2023-07-26 16:15:43 +01:00
e15e17cc9f update readme title 2023-07-26 16:12:38 +01:00
72e09d5278 minor version bump 2023-07-26 14:38:12 +01:00
11d991b039 examples updated 2023-07-26 14:38:01 +01:00
3d3d8f3020 log level may now be set with environment variable 2023-07-26 14:37:49 +01:00
82c6ced760 logger module added 2023-07-26 14:37:35 +01:00
72ee539b96 upd gemfile.lock 2023-07-26 10:54:38 +01:00
bbfaf486c3 observer dependency removed
patchbump
2023-07-26 10:52:23 +01:00
8534c59fa2 close now aliases stop_driver 2023-07-26 10:52:07 +01:00
9940fbbf9f assign client updater methods as base lambdas 2023-07-26 10:51:38 +01:00
18d291c6eb rename info to infostring, now returns a string 2023-07-26 10:18:32 +01:00
6dc21314e8 break instead of exit 2023-07-21 07:55:50 +01:00
15585c90e9 fix error in rakefile 2023-07-21 06:39:53 +01:00
15c4baf5d7 raekfile updated
rework examples
2023-07-21 06:37:14 +01:00
15dcaeedda remove unnecessary assignment. 2023-07-21 06:04:09 +01:00
856b7b5a5b add attr_reader r_client for OBSWSTest 2023-07-19 15:24:15 +01:00
af85d8b9ab minor version bump 2023-07-19 15:14:46 +01:00
5f08b97759 example in readme and main.rb refactored
code style badge changed to standard
2023-07-19 15:14:28 +01:00
73ae24eb4b examples refactored 2023-07-19 15:13:26 +01:00
153f35e742 now using symbols when making api calls
in run, yield now passes a reference ot the client as an argument
2023-07-19 15:12:25 +01:00
17dcaa18d8 now using standard 2023-07-19 15:11:15 +01:00
955b00571a whitespace fix 2023-07-19 15:11:05 +01:00
d9b054b108 whitespace fix
load obs.yml instead
2023-07-19 15:10:38 +01:00
799ae52b02 add prettier settings 2023-05-26 21:17:17 +01:00
bc93654297 move identify payload out of auth block
patch bump
2022-11-25 18:05:10 +00:00
aafcd185d0 patch bump
add full request payload to logger.

raise OBSWSError if auth enabled but no password provided

add example tasks to rake file

add logging section to README
2022-11-25 17:57:56 +00:00
43ecfb37f5 upd gemfile.lock 2022-11-18 15:00:34 +00:00
2e70c63ae7 fix patch ver 2022-11-18 14:54:29 +00:00
c67ce47026 pass payload for call_vendor_request
patch bump
2022-11-18 14:52:28 +00:00
norm
0bac7eaf3a added missing response classes
patch bump
2022-11-03 04:11:59 +00:00
norm
5b0ce79e46 fix bug in get_group_list
patch bump
2022-11-03 03:36:56 +00:00
norm
a0f5d8e57b add levels example 2022-10-27 06:45:52 +01:00
norm
f1a1c970e0 add low, high, all constants to SUBS 2022-10-27 06:45:21 +01:00
onyx-and-iris
449684c405 upd gemfile.lock files 2022-10-25 00:33:24 +01:00
onyx-and-iris
da5ef76c81 minor bump 2022-10-25 00:06:10 +01:00
onyx-and-iris
8752132012 scene rotate example added 2022-10-25 00:05:12 +01:00
onyx-and-iris
fb162ca195 logger level set to info in events example 2022-10-25 00:04:28 +01:00
onyx-and-iris
b33fe94cee moved logger auth success to req,event classes
override to_s for req,event classes

rename authenticate to identify in base class
2022-10-25 00:03:43 +01:00
26 changed files with 773 additions and 583 deletions

7
.gitignore vendored
View File

@@ -46,7 +46,8 @@ build-iPhoneSimulator/
# for a library or gem, you might want to ignore these files since the code is # for a library or gem, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in: # intended to run in multiple environments; otherwise, check them in:
# Gemfile.lock # Gemfile.lock
# .ruby-version .ruby-version
.ruby_version
# .ruby-gemset # .ruby-gemset
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this: # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
@@ -56,6 +57,8 @@ build-iPhoneSimulator/
# .rubocop-https?--* # .rubocop-https?--*
# config # config
obs.toml obs.yml
# quick test # quick test
quick.rb quick.rb
.vscode/

View File

@@ -1,20 +1,58 @@
PATH PATH
remote: . remote: .
specs: specs:
obsws (0.0.3) obsws (0.5.8)
observer (~> 0.1.1)
waitutil (~> 0.2.1) waitutil (~> 0.2.1)
websocket-driver (~> 0.7.5) websocket-driver (~> 0.7.5)
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
ast (2.4.2)
json (2.6.3)
language_server-protocol (3.17.0.3)
lint_roller (1.1.0)
minitest (5.16.3) minitest (5.16.3)
observer (0.1.1) parallel (1.23.0)
perfect_toml (0.9.0) parser (3.2.2.3)
ast (~> 2.4.1)
racc
racc (1.7.1)
rainbow (3.1.1)
rake (11.3.0) rake (11.3.0)
regexp_parser (2.8.1)
rexml (3.2.5)
rubocop (1.52.1)
json (~> 2.3)
parallel (~> 1.10)
parser (>= 3.2.2.3)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.8, < 3.0)
rexml (>= 3.2.5, < 4.0)
rubocop-ast (>= 1.28.0, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 3.0)
rubocop-ast (1.29.0)
parser (>= 3.2.1.0)
rubocop-performance (1.18.0)
rubocop (>= 1.7.0, < 2.0)
rubocop-ast (>= 0.4.0)
ruby-progressbar (1.13.0)
standard (1.30.1)
language_server-protocol (~> 3.17.0.2)
lint_roller (~> 1.0)
rubocop (~> 1.52.0)
standard-custom (~> 1.0.0)
standard-performance (~> 1.1.0)
standard-custom (1.0.2)
lint_roller (~> 1.0)
rubocop (~> 1.50)
standard-performance (1.1.2)
lint_roller (~> 1.1)
rubocop-performance (~> 1.18.0)
unicode-display_width (2.4.2)
waitutil (0.2.1) waitutil (0.2.1)
websocket-driver (0.7.5) websocket-driver (0.7.6)
websocket-extensions (>= 0.1.0) websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5) websocket-extensions (0.1.5)
@@ -24,8 +62,8 @@ PLATFORMS
DEPENDENCIES DEPENDENCIES
minitest (~> 5.16, >= 5.16.3) minitest (~> 5.16, >= 5.16.3)
obsws! obsws!
perfect_toml (~> 0.9.0)
rake (~> 11.2, >= 11.2.2) rake (~> 11.2, >= 11.2.2)
standard (~> 1.30)
BUNDLED WITH BUNDLED WITH
2.3.22 2.3.22

View File

@@ -1,8 +1,8 @@
[![Gem Version](https://badge.fury.io/rb/obsws.svg)](https://badge.fury.io/rb/obsws) [![Gem Version](https://badge.fury.io/rb/obsws.svg)](https://badge.fury.io/rb/obsws)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/onyx-and-iris/obsws-ruby/blob/dev/LICENSE) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/onyx-and-iris/obsws-ruby/blob/dev/LICENSE)
[![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square)](https://github.com/prettier/plugin-ruby) [![Ruby Code Style](https://img.shields.io/badge/code_style-standard-violet.svg)](https://github.com/standardrb/standard)
# A Ruby wrapper around OBS Studio WebSocket v5.0 # Ruby Clients for OBS Studio WebSocket v5.0
## Requirements ## Requirements
@@ -16,80 +16,73 @@
### Bundler ### Bundler
``` ```
bundle add 'obsws' bundle add obsws
bundle install bundle install
``` ```
### Gem
`gem install 'obsws'`
## `Use` ## `Use`
#### Example `main.rb` #### Example `main.rb`
pass `host`, `port` and `password` as keyword arguments. Pass `host`, `port` and `password` as keyword arguments.
```ruby ```ruby
require "obsws" require "obsws"
def main class Main
r_client = INPUT = "Mic/Aux"
OBSWS::Requests::Client.new(
host: "localhost",
port: 4455,
password: "strongpassword"
)
r_client.run do def run
# Toggle the mute state of your Mic input OBSWS::Requests::Client
r_client.toggle_input_mute("Mic/Aux") .new(host: "localhost", port: 4455, password: "strongpassword")
.run do |client|
# Toggle the mute state of your Mic input and print its new mute state
client.toggle_input_mute(INPUT)
resp = client.get_input_mute(INPUT)
puts "Input '#{INPUT}' was set to #{resp.input_muted}"
end
end end
end end
main if $0 == __FILE__ Main.new.run if $PROGRAM_NAME == __FILE__
``` ```
Passing OBSWS::Requests::Client.run a block closes the socket once the block returns.
### Requests ### Requests
Method names for requests match the API calls but snake cased. `run` accepts a block that closes the socket once you are done. Method names for requests match the API calls but snake cased.
example: example:
```ruby ```ruby
r_client.run do
# GetVersion # GetVersion
resp = r_client.get_version resp = r_client.get_version
# SetCurrentProgramScene # SetCurrentProgramScene
r_client.set_current_program_scene("BRB") r_client.set_current_program_scene("BRB")
end
``` ```
For a full list of requests refer to [Requests](https://github.com/obsproject/obs-websocket/blob/master/docs/generated/protocol.md#requests) For a full list of requests refer to [Requests](https://github.com/obsproject/obs-websocket/blob/master/docs/generated/protocol.md#requests)
### Events ### Events
Register an observer class and define `on_` methods for events. Method names should match the api event but snake cased. Register blocks with the Event client using the `on` method. The event data will be passed to the block.
example: example:
```ruby ```ruby
class Observer class Observer
def initialize def initialize
@e_client = OBSWS::Events::Client.new(**kwargs) @e_client = OBSWS::Events::Client.new(host: "localhost", port: 4455, password: "strongpassword")
# register class with the event client # register blocks on event types.
@e_client.add_observer(self) @e_client.on(:current_program_scene_changed) do |data|
end
# define "on_" event methods.
def on_current_program_scene_changed
... ...
end end
def on_input_mute_state_changed @e_client.on(:input_mute_state_changed) do |data|
... ...
end end
... end
end end
``` ```
@@ -111,10 +104,28 @@ def on_scene_created(data):
### Errors ### Errors
If a request fails an `OBSWSError` will be raised with a status code. If a general error occurs an `OBSWSError` will be raised.
If a connection attempt fails or times out an `OBSWSConnectionError` will be raised.
If a request fails an `OBSWSRequestError` will be raised with a status code.
- The request name and code are retrievable through the following attributes:
- `req_name`
- `code`
For a full list of status codes refer to [Codes](https://github.com/obsproject/obs-websocket/blob/master/docs/generated/protocol.md#requeststatus) For a full list of status codes refer to [Codes](https://github.com/obsproject/obs-websocket/blob/master/docs/generated/protocol.md#requeststatus)
### Logging
To enable logs set an environmental variable `OBSWS_LOG_LEVEL` to the appropriate level.
example in powershell:
```powershell
$env:OBSWS_LOG_LEVEL="DEBUG"
```
### Tests ### Tests
To run all tests: To run all tests:

View File

@@ -1,9 +1,29 @@
require "minitest/test_task" require "minitest/test_task"
HERE = __dir__
Minitest::TestTask.create(:test) do |t| Minitest::TestTask.create(:test) do |t|
t.libs << "test" t.libs << "test"
t.warning = false t.warning = false
t.test_globs = ["test/**/*_test.rb"] t.test_globs = ["test/**/test_*.rb"]
end end
task default: :test task default: :test
namespace :e do
desc "Runs the events example"
task :events do
filepath = File.join(HERE, "examples", "events", "main.rb")
ruby filepath
end
desc "Runs the levels example"
task :levels do
filepath = File.join(HERE, "examples", "levels", "main.rb")
ruby filepath
end
desc "Runs the scene_rotate example"
task :scene_rotate do
filepath = File.join(HERE, "examples", "scene_rotate", "main.rb")
ruby filepath
end
end

View File

@@ -1,9 +0,0 @@
# frozen_string_literal: true
source "https://rubygems.org"
# gem "rails"
gem "perfect_toml", "~> 0.9.0"
gem "obsws", path: "../.."

View File

@@ -1,27 +0,0 @@
PATH
remote: ../..
specs:
obsws (0.0.2)
observer (~> 0.1.1)
waitutil (~> 0.2.1)
websocket-driver (~> 0.7.5)
GEM
remote: https://rubygems.org/
specs:
observer (0.1.1)
perfect_toml (0.9.0)
waitutil (0.2.1)
websocket-driver (0.7.5)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5)
PLATFORMS
x64-mingw-ucrt
DEPENDENCIES
obsws!
perfect_toml (~> 0.9.0)
BUNDLED WITH
2.3.24

View File

@@ -1,58 +1,46 @@
require "obsws" require_relative "../../lib/obsws"
require "perfect_toml" require "yaml"
OBSWS::LOGGER.debug!
class Observer
attr_reader :running
class Main
def initialize(**kwargs) def initialize(**kwargs)
@r_client = OBSWS::Requests::Client.new(**kwargs) @r_client = OBSWS::Requests::Client.new(**kwargs)
@e_client = OBSWS::Events::Client.new(**kwargs) @e_client = OBSWS::Events::Client.new(**kwargs)
@e_client.add_observer(self)
puts info.join("\n") @e_client.on(:current_program_scene_changed) do |data|
@running = true
end
def info
resp = @r_client.get_version
[
"Using obs version:",
resp.obs_version,
"With websocket version:",
resp.obs_web_socket_version
]
end
def on_current_program_scene_changed(data)
puts "Switched to scene #{data.scene_name}" puts "Switched to scene #{data.scene_name}"
end end
@e_client.on(:scene_created) do |data|
def on_scene_created(data)
puts "scene #{data.scene_name} has been created" puts "scene #{data.scene_name} has been created"
end end
@e_client.on(:input_mute_state_changed) do |data|
def on_input_mute_state_changed(data)
puts "#{data.input_name} mute toggled" puts "#{data.input_name} mute toggled"
end end
@e_client.on(:exit_started) do
def on_exit_started
puts "OBS closing!" puts "OBS closing!"
@r_client.close @r_client.close
@e_client.close @e_client.close
@running = false @running = false
end end
puts infostring
end end
def conn_from_toml def infostring
PerfectTOML.load_file("obs.toml", symbolize_names: true)[:connection] resp = @r_client.get_version
[
"Using obs version: #{resp.obs_version}.",
"With websocket version: #{resp.obs_web_socket_version}"
].join(" ")
end end
def main def run
o = Observer.new(**conn_from_toml) @running = true
sleep(0.1) while @running
sleep(0.1) while o.running end
end end
main if $0 == __FILE__ def conn_from_yaml
YAML.load_file("obs.yml", symbolize_names: true)[:connection]
end
Main.new(**conn_from_yaml).run if $PROGRAM_NAME == __FILE__

45
examples/levels/main.rb Normal file
View File

@@ -0,0 +1,45 @@
require_relative "../../lib/obsws"
require "yaml"
module LevelTypes
VU = 0
POSTFADER = 1
PREFADER = 2
end
class Main
DEVICE = "Desktop Audio"
def initialize(**kwargs)
subs = OBSWS::Events::SUBS::LOW_VOLUME | OBSWS::Events::SUBS::INPUTVOLUMEMETERS
@e_client = OBSWS::Events::Client.new(subs:, **kwargs)
@e_client.on(:input_mute_state_changed) do |data|
if data.input_name == DEVICE
puts "#{DEVICE} mute toggled"
end
end
@e_client.on(:input_volume_meters) do |data|
fget = ->(x) { (x > 0) ? (20 * Math.log(x, 10)).round(1) : -200.0 }
data.inputs.each do |d|
name = d[:inputName]
if name == DEVICE && !d[:inputLevelsMul].empty?
left, right = d[:inputLevelsMul]
puts "#{name} [L: #{fget.call(left[LevelTypes::POSTFADER])}, R: #{fget.call(right[LevelTypes::POSTFADER])}]"
end
end
end
end
def run
puts "press <Enter> to quit"
loop { break if gets.chomp.empty? }
end
end
def conn_from_yaml
YAML.load_file("obs.yml", symbolize_names: true)[:connection]
end
Main.new(**conn_from_yaml).run if $PROGRAM_NAME == __FILE__

View File

@@ -0,0 +1,23 @@
require_relative "../../lib/obsws"
require "yaml"
class Main
def conn_from_yaml
YAML.load_file("obs.yml", symbolize_names: true)[:connection]
end
def run
OBSWS::Requests::Client.new(**conn_from_yaml).run do |client|
resp = client.get_scene_list
resp.scenes.reverse_each do |scene|
puts "Switching to scene #{scene[:sceneName]}"
client.set_current_program_scene(scene[:sceneName])
sleep(0.5)
end
end
end
end
Main.new.run if $PROGRAM_NAME == __FILE__

View File

@@ -1,11 +1,36 @@
require "digest/sha2"
require "json"
require "logger" require "logger"
require "securerandom"
require "socket"
require "waitutil"
require "websocket/driver"
require_relative "obsws/logger"
require_relative "obsws/driver"
require_relative "obsws/util"
require_relative "obsws/mixin"
require_relative "obsws/base"
require_relative "obsws/req" require_relative "obsws/req"
require_relative "obsws/event" require_relative "obsws/event"
module OBSWS require_relative "obsws/version"
include Logger::Severity
LOGGER = Logger.new(STDOUT) module OBSWS
LOGGER.level = WARN class OBSWSError < StandardError; end
class OBSWSConnectionError < OBSWSError; end
class OBSWSRequestError < OBSWSError
attr_reader :req_name, :code
def initialize(req_name, code, comment)
@req_name = req_name
@code = code
message = "Request #{@req_name} returned code #{@code}."
message << " With message: #{comment}" if comment
super(message)
end
end
end end

View File

@@ -1,72 +1,44 @@
require "socket"
require "websocket/driver"
require "digest/sha2"
require "json"
require "observer"
require "waitutil"
require_relative "mixin"
require_relative "error"
module OBSWS module OBSWS
class Socket class Identified
attr_reader :url attr_accessor :state
def initialize(url, socket) def initialize
@url = url @state = :pending
@socket = socket
end end
def write(s) def error_message
@socket.write(s) case @state
when :passwordless
"auth enabled but no password provided"
else
"failed to identify client with the websocket server"
end
end end
end end
class Base class Base
include Observable include Logging
include Driver::Director
include Mixin::OPCodes include Mixin::OPCodes
attr_reader :id, :driver, :closed attr_reader :closed, :identified
attr_writer :updater
def initialize(**kwargs) def initialize(**kwargs)
host = kwargs[:host] || "localhost" host = kwargs[:host] || "localhost"
port = kwargs[:port] || 4455 port = kwargs[:port] || 4455
@password = kwargs[:password] || "" @password = kwargs[:password] || ""
@subs = kwargs[:subs] || 0 @subs = kwargs[:subs] || 0
@identified = Identified.new
@socket = TCPSocket.new(host, port) setup_driver(host, port) and start_driver
@driver =
WebSocket::Driver.client(Socket.new("ws://#{host}:#{port}", @socket))
@driver.on :open do |msg|
LOGGER.debug("driver socket open")
end
@driver.on :close do |msg|
LOGGER.debug("driver socket closed")
@closed = true
end
@driver.on :message do |msg|
LOGGER.debug("received [#{msg}] passing to handler")
msg_handler(JSON.parse(msg.data, symbolize_names: true))
end
start_driver
WaitUtil.wait_for_condition( WaitUtil.wait_for_condition(
"waiting successful identification", "successful identification",
delay_sec: 0.01, delay_sec: 0.01,
timeout_sec: 3 timeout_sec: kwargs[:connect_timeout] || 3
) { @identified } ) { @identified.state != :pending }
end end
def start_driver private
Thread.new do
@driver.start
loop do
@driver.parse(@socket.readpartial(4096))
rescue EOFError
break
end
end
end
def auth_token(salt:, challenge:) def auth_token(salt:, challenge:)
Digest::SHA256.base64digest( Digest::SHA256.base64digest(
@@ -74,7 +46,7 @@ module OBSWS
) )
end end
def authenticate(auth = nil) def identify(auth)
payload = { payload = {
op: Mixin::OPCodes::IDENTIFY, op: Mixin::OPCodes::IDENTIFY,
d: { d: {
@@ -82,29 +54,29 @@ module OBSWS
eventSubscriptions: @subs eventSubscriptions: @subs
} }
} }
payload[:d][:authentication] = auth_token(**auth) if auth if auth
if @password.empty?
@identified.state = :passwordless
return
end
logger.info("initiating authentication")
payload[:d][:authentication] = auth_token(**auth)
end
@driver.text(JSON.generate(payload)) @driver.text(JSON.generate(payload))
end end
def msg_handler(data) def msg_handler(data)
case data[:op] case data[:op]
when Mixin::OPCodes::HELLO when Mixin::OPCodes::HELLO
if data[:d].key? :authentication identify(data[:d][:authentication])
LOGGER.debug("initiating authentication")
else
LOGGER.debug("authentication disabled... skipping.")
end
authenticate(data[:d][:authentication])
when Mixin::OPCodes::IDENTIFIED when Mixin::OPCodes::IDENTIFIED
LOGGER.debug("client succesfully identified with server") @identified.state = :identified
@identified = true
when Mixin::OPCodes::EVENT, Mixin::OPCodes::REQUESTRESPONSE when Mixin::OPCodes::EVENT, Mixin::OPCodes::REQUESTRESPONSE
changed @updater.call(data[:op], data[:d])
notify_observers(data[:op], data[:d])
end end
end end
def req(id, type_, data = nil) public def req(id, type_, data = nil)
payload = { payload = {
op: Mixin::OPCodes::REQUEST, op: Mixin::OPCodes::REQUEST,
d: { d: {
@@ -113,8 +85,8 @@ module OBSWS
} }
} }
payload[:d][:requestData] = data if data payload[:d][:requestData] = data if data
queued = @driver.text(JSON.generate(payload)) logger.debug("sending request: #{payload}")
LOGGER.debug("request with id #{id} queued? #{queued}") @driver.text(JSON.generate(payload))
end end
end end
end end

50
lib/obsws/driver.rb Normal file
View File

@@ -0,0 +1,50 @@
module OBSWS
module Driver
class Socket
attr_reader :url
def initialize(url, socket)
@url = url
@socket = socket
end
def write(s)
@socket.write(s)
end
end
module Director
def setup_driver(host, port)
@socket = TCPSocket.new(host, port)
@driver =
WebSocket::Driver.client(Socket.new("ws://#{host}:#{port}", @socket))
@driver.on :open do |msg|
logger.debug("driver socket open")
end
@driver.on :close do |msg|
logger.debug("driver socket closed")
@closed = true
end
@driver.on :message do |msg|
msg_handler(JSON.parse(msg.data, symbolize_names: true))
end
end
private def start_driver
Thread.new do
@driver.start
loop do
@driver.parse(@socket.readpartial(4096))
rescue EOFError
break
end
end
end
public def stop_driver
@driver.close
end
end
end
end

View File

@@ -1,6 +0,0 @@
module OBSWS
module Error
class OBSWSError < StandardError
end
end
end

View File

@@ -1,91 +1,91 @@
require "json"
require_relative "util"
require_relative "mixin"
module OBSWS module OBSWS
module Events module Events
module SUBS module SUBS
NONE = 0 NONE = 0
GENERAL = (1 << 0) GENERAL = 1 << 0
CONFIG = (1 << 1) CONFIG = 1 << 1
SCENES = (1 << 2) SCENES = 1 << 2
INPUTS = (1 << 3) INPUTS = 1 << 3
TRANSITIONS = (1 << 4) TRANSITIONS = 1 << 4
FILTERS = (1 << 5) FILTERS = 1 << 5
OUTPUTS = (1 << 6) OUTPUTS = 1 << 6
SCENEITEMS = (1 << 7) SCENEITEMS = 1 << 7
MEDIAINPUTS = (1 << 8) MEDIAINPUTS = 1 << 8
VENDORS = (1 << 9) VENDORS = 1 << 9
UI = (1 << 10) UI = 1 << 10
def low_volume LOW_VOLUME = GENERAL | CONFIG | SCENES | INPUTS | TRANSITIONS | FILTERS | OUTPUTS |
GENERAL | CONFIG | SCENES | INPUTS | TRANSITIONS | FILTERS | OUTPUTS |
SCENEITEMS | MEDIAINPUTS | VENDORS | UI SCENEITEMS | MEDIAINPUTS | VENDORS | UI
end
INPUTVOLUMEMETERS = (1 << 16) INPUTVOLUMEMETERS = 1 << 16
INPUTACTIVESTATECHANGED = (1 << 17) INPUTACTIVESTATECHANGED = 1 << 17
INPUTSHOWSTATECHANGED = (1 << 18) INPUTSHOWSTATECHANGED = 1 << 18
SCENEITEMTRANSFORMCHANGED = (1 << 19) SCENEITEMTRANSFORMCHANGED = 1 << 19
def high_volume HIGH_VOLUME = INPUTVOLUMEMETERS | INPUTACTIVESTATECHANGED | INPUTSHOWSTATECHANGED |
INPUTVOLUMEMETERS | INPUTACTIVESTATECHANGED | INPUTSHOWSTATECHANGED |
SCENEITEMTRANSFORMCHANGED SCENEITEMTRANSFORMCHANGED
ALL = LOW_VOLUME | HIGH_VOLUME
end end
def all module EventDirector
low_volume | high_volume include Util::String
def observers
@observers ||= {}
end end
module_function :low_volume, :high_volume, :all def on(event, method = nil, &block)
(observers[event] ||= []) << (block || method)
end end
module Callbacks def register(cbs)
include Util cbs = [cbs] unless cbs.respond_to? :each
cbs.each { |cb| on(cb.name[3..].to_sym, cb) }
def add_observer(observer)
@observers = [] unless defined?(@observers)
observer = [observer] if !observer.respond_to? :each
observer.each { |o| @observers.append(o) }
end end
def remove_observer(observer) def deregister(cbs)
@observers.delete(observer) cbs = [cbs] unless cbs.respond_to? :each
cbs.each { |cb| observers[cb.name[3..].to_sym]&.reject! { |o| cbs.include? o } }
end end
def notify_observers(event, data) def fire(event, data)
if defined?(@observers) observers[snakecase(event).to_sym]&.each { |block| data.empty? ? block.call : block.call(data) }
@observers.each do |o|
if o.respond_to? "on_#{event.to_snake}"
if data.empty?
o.send("on_#{event.to_snake}")
else
o.send("on_#{event.to_snake}", data)
end
end
end
end
end end
end end
class Client class Client
include Callbacks include Logging
include EventDirector
include Mixin::TearDown include Mixin::TearDown
include Mixin::OPCodes include Mixin::OPCodes
def initialize(**kwargs) def initialize(**kwargs)
kwargs[:subs] = SUBS.low_volume kwargs[:subs] ||= SUBS::LOW_VOLUME
@base_client = Base.new(**kwargs) @base_client = Base.new(**kwargs)
@base_client.add_observer(self) unless @base_client.identified.state == :identified
err_msg = @base_client.identified.error_message
logger.error(err_msg)
raise OBSWSConnectionError.new(err_msg)
end
logger.info("#{self} successfully identified with server")
rescue Errno::ECONNREFUSED, WaitUtil::TimeoutError => e
msg = "#{e.class.name}: #{e.message}"
logger.error(msg)
raise OBSWSConnectionError.new(msg)
else
@base_client.updater = ->(op_code, data) {
if op_code == Mixin::OPCodes::EVENT
logger.debug("received: #{data}")
event = data[:eventType]
data = data.fetch(:eventData, {})
fire(event, Mixin::Data.new(data, data.keys))
end
}
end end
def update(op_code, data) def to_s
if op_code == Mixin::OPCodes::EVENT self.class.name.split("::").last(2).join("::")
event = data[:eventType]
data = data.key?(:eventData) ? data[:eventData] : {}
notify_observers(event, Mixin::Data.new(data, data.keys))
end
end end
end end
end end

9
lib/obsws/logger.rb Normal file
View File

@@ -0,0 +1,9 @@
module OBSWS
module Logging
def logger
@logger ||= Logger.new($stdout, level: ENV.fetch("OBSWS_LOG_LEVEL", "WARN"))
@logger.progname = instance_of?(::Module) ? name : self.class.name
@logger
end
end
end

View File

@@ -1,13 +1,11 @@
require_relative "util"
module OBSWS module OBSWS
module Mixin module Mixin
module Meta module Meta
include Util include Util::String
def make_field_methods(*params) def make_field_methods(*params)
params.each do |param| params.each do |param|
define_singleton_method(param.to_s.to_snake) { @resp[param] } define_singleton_method(snakecase(param.to_s)) { @resp[param] }
end end
end end
end end
@@ -18,24 +16,26 @@ module OBSWS
def initialize(resp, fields) def initialize(resp, fields)
@resp = resp @resp = resp
@fields = fields @fields = fields
self.make_field_methods *fields make_field_methods(*fields)
end end
def empty? = @fields.empty? def empty? = @fields.empty?
def attrs = @fields.map { |f| f.to_s.to_snake } def attrs = @fields.map { |f| snakecase(f.to_s) }
end end
class Response < MetaObject class Response < MetaObject; end
end # Represents a request response object
class Data < MetaObject class Data < MetaObject; end
end # Represents an event data object
module TearDown module TearDown
def close def stop_driver
@base_client.driver.close @base_client.stop_driver
end end
alias_method :close, :stop_driver
end end
module OPCodes module OPCodes

View File

@@ -1,88 +1,93 @@
require "waitutil"
require_relative "base"
require_relative "error"
require_relative "util"
require_relative "mixin"
module OBSWS module OBSWS
module Requests module Requests
class Client class Client
include Error include Logging
include Mixin::TearDown include Mixin::TearDown
include Mixin::OPCodes include Mixin::OPCodes
def initialize(**kwargs) def initialize(**kwargs)
@base_client = Base.new(**kwargs) @base_client = Base.new(**kwargs)
@base_client.add_observer(self) unless @base_client.identified.state == :identified
err_msg = @base_client.identified.error_message
logger.error(err_msg)
raise OBSWSConnectionError.new(err_msg)
end
logger.info("#{self} successfully identified with server")
rescue Errno::ECONNREFUSED, WaitUtil::TimeoutError => e
logger.error("#{e.class.name}: #{e.message}")
raise OBSWSConnectionError.new(e.message)
else
@base_client.updater = ->(op_code, data) {
logger.debug("response received: #{data}")
@response = data if op_code == Mixin::OPCodes::REQUESTRESPONSE
}
@response = {requestId: 0} @response = {requestId: 0}
end end
def to_s
self.class.name.split("::").last(2).join("::")
end
def run def run
yield yield(self)
ensure ensure
close stop_driver
WaitUtil.wait_for_condition( WaitUtil.wait_for_condition(
"driver has closed", "driver to close",
delay_sec: 0.01, delay_sec: 0.01,
timeout_sec: 1 timeout_sec: 1
) { @base_client.closed } ) { @base_client.closed }
end end
def update(op_code, data)
@response = data if op_code == Mixin::OPCodes::REQUESTRESPONSE
end
def call(req, data = nil) def call(req, data = nil)
id = rand(1..1000) uuid = SecureRandom.uuid
@base_client.req(id, req, data) @base_client.req(uuid, req, data)
WaitUtil.wait_for_condition( WaitUtil.wait_for_condition(
"reponse id matches request id", "reponse id to match request id",
delay_sec: 0.001, delay_sec: 0.001,
timeout_sec: 3 timeout_sec: 3
) { @response[:requestId] == id } ) { @response[:requestId] == uuid }
if !@response[:requestStatus][:result] unless @response[:requestStatus][:result]
error = [ raise OBSWSRequestError.new(@response[:requestType], @response[:requestStatus][:code], @response[:requestStatus][:comment])
"Request #{@response[:requestType]} returned code #{@response[:requestStatus][:code]}"
]
if @response[:requestStatus].key?(:comment)
error += ["With message: #{@response[:requestStatus][:comment]}"]
end
raise OBSWSError.new(error.join("\n"))
end end
@response[:responseData] @response[:responseData]
rescue WaitUtil::TimeoutError rescue OBSWSRequestError => e
msg = "no response with matching id received" logger.error(["#{e.class.name}: #{e.message}", *e.backtrace].join("\n"))
LOGGER.error(msg) raise
raise OBSWSError.new(msg) rescue WaitUtil::TimeoutError => e
logger.error(["#{e.class.name}: #{e.message}", *e.backtrace].join("\n"))
raise OBSWSError.new([e.message, *e.backtrace].join("\n"))
end end
def get_version def get_version
resp = call("GetVersion") resp = call(:GetVersion)
Mixin::Response.new(resp, resp.keys) Mixin::Response.new(resp, resp.keys)
end end
def get_stats def get_stats
resp = call("GetStats") resp = call(:GetStats)
Mixin::Response.new(resp, resp.keys) Mixin::Response.new(resp, resp.keys)
end end
def broadcast_custom_event(data) def broadcast_custom_event(data)
call("BroadcastCustomEvent", data) call(:BroadcastCustomEvent, data)
end end
def call_vendor_request(name, type_, data = nil) def call_vendor_request(vendor_name, request_type, data = nil)
call(requestType, requestData) payload = {vendorName: vendor_name, requestType: request_type}
payload[:requestData] = data if data
resp = call(:CallVendorRequest, payload)
Mixin::Response.new(resp, resp.keys)
end end
def get_hotkey_list def get_hotkey_list
resp = call("GetHotkeyList") resp = call(:GetHotkeyList)
Mixin::Response.new(resp, resp.keys) Mixin::Response.new(resp, resp.keys)
end end
def trigger_hotkey_by_name(name) def trigger_hotkey_by_name(name)
payload = {hotkeyName: name} payload = {hotkeyName: name}
call("TriggerHotkeyByName", payload) call(:TriggerHotkeyByName, payload)
end end
def trigger_hotkey_by_key_sequence( def trigger_hotkey_by_key_sequence(
@@ -101,63 +106,63 @@ module OBSWS
cmd: press_cmd cmd: press_cmd
} }
} }
call("TriggerHotkeyByKeySequence", payload) call(:TriggerHotkeyByKeySequence, payload)
end end
def sleep(sleep_millis = nil, sleep_frames = nil) def sleep(sleep_millis = nil, sleep_frames = nil)
payload = {sleepMillis: sleep_millis, sleepFrames: sleep_frames} payload = {sleepMillis: sleep_millis, sleepFrames: sleep_frames}
call("Sleep", payload) call(:Sleep, payload)
end end
def get_persistent_data(realm, slot_name) def get_persistent_data(realm, slot_name)
payload = {realm: realm, slotName: slot_name} payload = {realm: realm, slotName: slot_name}
resp = call("GetPersistentData", payload) resp = call(:GetPersistentData, payload)
Mixin::Response.new(resp, resp.keys) Mixin::Response.new(resp, resp.keys)
end end
def set_persistent_data(realm, slot_name, slot_value) def set_persistent_data(realm, slot_name, slot_value)
payload = {realm: realm, slotName: slot_name, slotValue: slot_value} payload = {realm: realm, slotName: slot_name, slotValue: slot_value}
call("SetPersistentData", payload) call(:SetPersistentData, payload)
end end
def get_scene_collection_list def get_scene_collection_list
resp = call("GetSceneCollectionList") resp = call(:GetSceneCollectionList)
Mixin::Response.new(resp, resp.keys) Mixin::Response.new(resp, resp.keys)
end end
def set_current_scene_collection(name) def set_current_scene_collection(name)
payload = {sceneCollectionName: name} payload = {sceneCollectionName: name}
call("SetCurrentSceneCollection", payload) call(:SetCurrentSceneCollection, payload)
end end
def create_scene_collection(name) def create_scene_collection(name)
payload = {sceneCollectionName: name} payload = {sceneCollectionName: name}
call("CreateSceneCollection", payload) call(:CreateSceneCollection, payload)
end end
def get_profile_list def get_profile_list
resp = call("GetProfileList") resp = call(:GetProfileList)
Mixin::Response.new(resp, resp.keys) Mixin::Response.new(resp, resp.keys)
end end
def set_current_profile(name) def set_current_profile(name)
payload = {profileName: name} payload = {profileName: name}
call("SetCurrentProfile", payload) call(:SetCurrentProfile, payload)
end end
def create_profile(name) def create_profile(name)
payload = {profileName: name} payload = {profileName: name}
call("CreateProfile", payload) call(:CreateProfile, payload)
end end
def remove_profile(name) def remove_profile(name)
payload = {profileName: name} payload = {profileName: name}
call("RemoveProfile", payload) call(:RemoveProfile, payload)
end end
def get_profile_parameter(category, name) def get_profile_parameter(category, name)
payload = {parameterCategory: category, parameterName: name} payload = {parameterCategory: category, parameterName: name}
resp = call("GetProfileParameter", payload) resp = call(:GetProfileParameter, payload)
Mixin::Response.new(resp, resp.keys) Mixin::Response.new(resp, resp.keys)
end end
@@ -167,11 +172,11 @@ module OBSWS
parameterName: name, parameterName: name,
parameterValue: value parameterValue: value
} }
call("SetProfileParameter", payload) call(:SetProfileParameter, payload)
end end
def get_video_settings def get_video_settings
resp = call("GetVideoSettings") resp = call(:GetVideoSettings)
Mixin::Response.new(resp, resp.keys) Mixin::Response.new(resp, resp.keys)
end end
@@ -191,11 +196,11 @@ module OBSWS
outputWidth: out_width, outputWidth: out_width,
outputHeight: out_height outputHeight: out_height
} }
call("SetVideoSettings", payload) call(:SetVideoSettings, payload)
end end
def get_stream_service_settings def get_stream_service_settings
resp = call("GetStreamServiceSettings") resp = call(:GetStreamServiceSettings)
Mixin::Response.new(resp, resp.keys) Mixin::Response.new(resp, resp.keys)
end end
@@ -204,17 +209,17 @@ module OBSWS
streamServiceType: ss_type, streamServiceType: ss_type,
streamServiceSettings: ss_settings streamServiceSettings: ss_settings
} }
call("SetStreamServiceSettings", payload) call(:SetStreamServiceSettings, payload)
end end
def get_record_directory def get_record_directory
resp = call("GetRecordDirectory") resp = call(:GetRecordDirectory)
Mixin::Response.new(resp, resp.keys) Mixin::Response.new(resp, resp.keys)
end end
def get_source_active(name) def get_source_active(name)
payload = {sourceName: name} payload = {sourceName: name}
resp = call("GetSourceActive", payload) resp = call(:GetSourceActive, payload)
Mixin::Response.new(resp, resp.keys) Mixin::Response.new(resp, resp.keys)
end end
@@ -226,7 +231,7 @@ module OBSWS
imageHeight: height, imageHeight: height,
imageCompressionQuality: quality imageCompressionQuality: quality
} }
resp = call("GetSourceScreenshot", payload) resp = call(:GetSourceScreenshot, payload)
Mixin::Response.new(resp, resp.keys) Mixin::Response.new(resp, resp.keys)
end end
@@ -246,58 +251,58 @@ module OBSWS
imageHeight: height, imageHeight: height,
imageCompressionQuality: quality imageCompressionQuality: quality
} }
resp = call("SaveSourceScreenshot", payload) resp = call(:SaveSourceScreenshot, payload)
Mixin::Response.new(resp, resp.keys) Mixin::Response.new(resp, resp.keys)
end end
def get_scene_list def get_scene_list
resp = call("GetSceneList") resp = call(:GetSceneList)
Mixin::Response.new(resp, resp.keys) Mixin::Response.new(resp, resp.keys)
end end
def get_group_list def get_group_list
resp = call("GetSceneList") resp = call(:GetGroupList)
Mixin::Response.new(resp, resp.keys) Mixin::Response.new(resp, resp.keys)
end end
def get_current_program_scene def get_current_program_scene
resp = call("GetCurrentProgramScene") resp = call(:GetCurrentProgramScene)
Mixin::Response.new(resp, resp.keys) Mixin::Response.new(resp, resp.keys)
end end
def set_current_program_scene(name) def set_current_program_scene(name)
payload = {sceneName: name} payload = {sceneName: name}
call("SetCurrentProgramScene", payload) call(:SetCurrentProgramScene, payload)
end end
def get_current_preview_scene def get_current_preview_scene
resp = call("GetCurrentPreviewScene") resp = call(:GetCurrentPreviewScene)
Mixin::Response.new(resp, resp.keys) Mixin::Response.new(resp, resp.keys)
end end
def set_current_preview_scene(name) def set_current_preview_scene(name)
payload = {sceneName: name} payload = {sceneName: name}
call("SetCurrentPreviewScene", payload) call(:SetCurrentPreviewScene, payload)
end end
def create_scene(name) def create_scene(name)
payload = {sceneName: name} payload = {sceneName: name}
call("CreateScene", payload) call(:CreateScene, payload)
end end
def remove_scene(name) def remove_scene(name)
payload = {sceneName: name} payload = {sceneName: name}
call("RemoveScene", payload) call(:RemoveScene, payload)
end end
def set_scene_name(old_name, new_name) def set_scene_name(old_name, new_name)
payload = {sceneName: old_name, newSceneName: new_name} payload = {sceneName: old_name, newSceneName: new_name}
call("SetSceneName", payload) call(:SetSceneName, payload)
end end
def get_scene_scene_transition_override(name) def get_scene_scene_transition_override(name)
payload = {sceneName: name} payload = {sceneName: name}
resp = call("GetSceneSceneTransitionOverride", payload) resp = call(:GetSceneSceneTransitionOverride, payload)
Mixin::Response.new(resp, resp.keys) Mixin::Response.new(resp, resp.keys)
end end
@@ -307,23 +312,24 @@ module OBSWS
transitionName: tr_name, transitionName: tr_name,
transitionDuration: tr_duration transitionDuration: tr_duration
} }
call("SetSceneSceneTransitionOverride", payload) call(:SetSceneSceneTransitionOverride, payload)
end end
def get_input_list(kind = nil) def get_input_list(kind = nil)
payload = {inputKind: kind} payload = {inputKind: kind}
resp = call("GetInputList", payload) resp = call(:GetInputList, payload)
Mixin::Response.new(resp, resp.keys) Mixin::Response.new(resp, resp.keys)
end end
def get_input_kind_list(unversioned) def get_input_kind_list(unversioned)
payload = {unversioned: unversioned} payload = {unversioned: unversioned}
resp = call("GetInputKindList", payload) resp = call(:GetInputKindList, payload)
Mixin::Response.new(resp, resp.keys) Mixin::Response.new(resp, resp.keys)
end end
def get_special_inputs def get_special_inputs
resp = call("GetSpecialInputs") resp = call(:GetSpecialInputs)
Mixin::Response.new(resp, resp.keys)
end end
def create_input( def create_input(
@@ -340,56 +346,57 @@ module OBSWS
inputSettings: input_settings, inputSettings: input_settings,
sceneItemEnabled: scene_item_enabled sceneItemEnabled: scene_item_enabled
} }
resp = call("CreateInput", payload) resp = call(:CreateInput, payload)
Mixin::Response.new(resp, resp.keys)
end end
def remove_input(name) def remove_input(name)
payload = {inputName: name} payload = {inputName: name}
call("RemoveInput", payload) call(:RemoveInput, payload)
end end
def set_input_name(old_name, new_name) def set_input_name(old_name, new_name)
payload = {inputName: old_name, newInputName: new_name} payload = {inputName: old_name, newInputName: new_name}
call("SetInputName", payload) call(:SetInputName, payload)
end end
def get_input_default_settings(kind) def get_input_default_settings(kind)
payload = {inputKind: kind} payload = {inputKind: kind}
resp = call("GetInputDefaultSettings", payload) resp = call(:GetInputDefaultSettings, payload)
Mixin::Response.new(resp, resp.keys) Mixin::Response.new(resp, resp.keys)
end end
def get_input_settings(name) def get_input_settings(name)
payload = {inputName: name} payload = {inputName: name}
resp = call("GetInputSettings", payload) resp = call(:GetInputSettings, payload)
Mixin::Response.new(resp, resp.keys) Mixin::Response.new(resp, resp.keys)
end end
def set_input_settings(name, settings, overlay) def set_input_settings(name, settings, overlay)
payload = {inputName: name, inputSettings: settings, overlay: overlay} payload = {inputName: name, inputSettings: settings, overlay: overlay}
call("SetInputSettings", payload) call(:SetInputSettings, payload)
end end
def get_input_mute(name) def get_input_mute(name)
payload = {inputName: name} payload = {inputName: name}
resp = call("GetInputMute", payload) resp = call(:GetInputMute, payload)
Mixin::Response.new(resp, resp.keys) Mixin::Response.new(resp, resp.keys)
end end
def set_input_mute(name, muted) def set_input_mute(name, muted)
payload = {inputName: name, inputMuted: muted} payload = {inputName: name, inputMuted: muted}
call("SetInputMute", payload) call(:SetInputMute, payload)
end end
def toggle_input_mute(name) def toggle_input_mute(name)
payload = {inputName: name} payload = {inputName: name}
resp = call("ToggleInputMute", payload) resp = call(:ToggleInputMute, payload)
Mixin::Response.new(resp, resp.keys) Mixin::Response.new(resp, resp.keys)
end end
def get_input_volume(name) def get_input_volume(name)
payload = {inputName: name} payload = {inputName: name}
resp = call("GetInputVolume", payload) resp = call(:GetInputVolume, payload)
Mixin::Response.new(resp, resp.keys) Mixin::Response.new(resp, resp.keys)
end end
@@ -399,117 +406,117 @@ module OBSWS
inputVolumeMul: vol_mul, inputVolumeMul: vol_mul,
inputVolumeDb: vol_db inputVolumeDb: vol_db
} }
call("SetInputVolume", payload) call(:SetInputVolume, payload)
end end
def get_input_audio_balance(name) def get_input_audio_balance(name)
payload = {inputName: name} payload = {inputName: name}
resp = call("GetInputAudioBalance", payload) resp = call(:GetInputAudioBalance, payload)
Mixin::Response.new(resp, resp.keys) Mixin::Response.new(resp, resp.keys)
end end
def set_input_audio_balance(name, balance) def set_input_audio_balance(name, balance)
payload = {inputName: name, inputAudioBalance: balance} payload = {inputName: name, inputAudioBalance: balance}
call("SetInputAudioBalance", payload) call(:SetInputAudioBalance, payload)
end end
def get_input_audio_sync_offset(name) def get_input_audio_sync_offset(name)
payload = {inputName: name} payload = {inputName: name}
resp = call("GetInputAudioSyncOffset", payload) resp = call(:GetInputAudioSyncOffset, payload)
Mixin::Response.new(resp, resp.keys) Mixin::Response.new(resp, resp.keys)
end end
def set_input_audio_sync_offset(name, offset) def set_input_audio_sync_offset(name, offset)
payload = {inputName: name, inputAudioSyncOffset: offset} payload = {inputName: name, inputAudioSyncOffset: offset}
call("SetInputAudioSyncOffset", payload) call(:SetInputAudioSyncOffset, payload)
end end
def get_input_audio_monitor_type(name) def get_input_audio_monitor_type(name)
payload = {inputName: name} payload = {inputName: name}
resp = call("GetInputAudioMonitorType", payload) resp = call(:GetInputAudioMonitorType, payload)
Mixin::Response.new(resp, resp.keys) Mixin::Response.new(resp, resp.keys)
end end
def set_input_audio_monitor_type(name, mon_type) def set_input_audio_monitor_type(name, mon_type)
payload = {inputName: name, monitorType: mon_type} payload = {inputName: name, monitorType: mon_type}
call("SetInputAudioMonitorType", payload) call(:SetInputAudioMonitorType, payload)
end end
def get_input_audio_tracks(name) def get_input_audio_tracks(name)
payload = {inputName: name} payload = {inputName: name}
resp = call("GetInputAudioTracks", payload) resp = call(:GetInputAudioTracks, payload)
Mixin::Response.new(resp, resp.keys) Mixin::Response.new(resp, resp.keys)
end end
def set_input_audio_tracks(name, track) def set_input_audio_tracks(name, track)
payload = {inputName: name, inputAudioTracks: track} payload = {inputName: name, inputAudioTracks: track}
call("SetInputAudioTracks", payload) call(:SetInputAudioTracks, payload)
end end
def get_input_properties_list_property_items(input_name, prop_name) def get_input_properties_list_property_items(input_name, prop_name)
payload = {inputName: input_name, propertyName: prop_name} payload = {inputName: input_name, propertyName: prop_name}
resp = call("GetInputPropertiesListPropertyItems", payload) resp = call(:GetInputPropertiesListPropertyItems, payload)
Mixin::Response.new(resp, resp.keys) Mixin::Response.new(resp, resp.keys)
end end
def press_input_properties_button(input_name, prop_name) def press_input_properties_button(input_name, prop_name)
payload = {inputName: input_name, propertyName: prop_name} payload = {inputName: input_name, propertyName: prop_name}
call("PressInputPropertiesButton", payload) call(:PressInputPropertiesButton, payload)
end end
def get_transition_kind_list def get_transition_kind_list
resp = call("GetTransitionKindList") resp = call(:GetTransitionKindList)
Mixin::Response.new(resp, resp.keys) Mixin::Response.new(resp, resp.keys)
end end
def get_scene_transition_list def get_scene_transition_list
resp = call("GetSceneTransitionList") resp = call(:GetSceneTransitionList)
Mixin::Response.new(resp, resp.keys) Mixin::Response.new(resp, resp.keys)
end end
def get_current_scene_transition def get_current_scene_transition
resp = call("GetCurrentSceneTransition") resp = call(:GetCurrentSceneTransition)
Mixin::Response.new(resp, resp.keys) Mixin::Response.new(resp, resp.keys)
end end
def set_current_scene_transition(name) def set_current_scene_transition(name)
payload = {transitionName: name} payload = {transitionName: name}
call("SetCurrentSceneTransition", payload) call(:SetCurrentSceneTransition, payload)
end end
def set_current_scene_transition_duration(duration) def set_current_scene_transition_duration(duration)
payload = {transitionDuration: duration} payload = {transitionDuration: duration}
call("SetCurrentSceneTransitionDuration", payload) call(:SetCurrentSceneTransitionDuration, payload)
end end
def set_current_scene_transition_settings(settings, overlay = nil) def set_current_scene_transition_settings(settings, overlay = nil)
payload = {transitionSettings: settings, overlay: overlay} payload = {transitionSettings: settings, overlay: overlay}
call("SetCurrentSceneTransitionSettings", payload) call(:SetCurrentSceneTransitionSettings, payload)
end end
def get_current_scene_transition_cursor def get_current_scene_transition_cursor
resp = call("GetCurrentSceneTransitionCursor") resp = call(:GetCurrentSceneTransitionCursor)
Mixin::Response.new(resp, resp.keys) Mixin::Response.new(resp, resp.keys)
end end
def trigger_studio_mode_transition def trigger_studio_mode_transition
call("TriggerStudioModeTransition") call(:TriggerStudioModeTransition)
end end
def set_t_bar_position(pos, release = nil) def set_t_bar_position(pos, release = nil)
payload = {position: pos, release: release} payload = {position: pos, release: release}
call("SetTBarPosition", payload) call(:SetTBarPosition, payload)
end end
def get_source_filter_list(name) def get_source_filter_list(name)
payload = {sourceName: name} payload = {sourceName: name}
resp = call("GetSourceFilterList", payload) resp = call(:GetSourceFilterList, payload)
Mixin::Response.new(resp, resp.keys) Mixin::Response.new(resp, resp.keys)
end end
def get_source_filter_default_settings(kind) def get_source_filter_default_settings(kind)
payload = {filterKind: kind} payload = {filterKind: kind}
resp = call("GetSourceFilterDefaultSettings", payload) resp = call(:GetSourceFilterDefaultSettings, payload)
Mixin::Response.new(resp, resp.keys) Mixin::Response.new(resp, resp.keys)
end end
@@ -525,12 +532,12 @@ module OBSWS
filterKind: filter_kind, filterKind: filter_kind,
filterSettings: filter_settings filterSettings: filter_settings
} }
call("CreateSourceFilter", payload) call(:CreateSourceFilter, payload)
end end
def remove_source_filter(source_name, filter_name) def remove_source_filter(source_name, filter_name)
payload = {sourceName: source_name, filterName: filter_name} payload = {sourceName: source_name, filterName: filter_name}
call("RemoveSourceFilter", payload) call(:RemoveSourceFilter, payload)
end end
def set_source_filter_name(source_name, old_filter_name, new_filter_name) def set_source_filter_name(source_name, old_filter_name, new_filter_name)
@@ -539,12 +546,12 @@ module OBSWS
filterName: old_filter_name, filterName: old_filter_name,
newFilterName: new_filter_name newFilterName: new_filter_name
} }
call("SetSourceFilterName", payload) call(:SetSourceFilterName, payload)
end end
def get_source_filter(source_name, filter_name) def get_source_filter(source_name, filter_name)
payload = {sourceName: source_name, filterName: filter_name} payload = {sourceName: source_name, filterName: filter_name}
resp = call("GetSourceFilter", payload) resp = call(:GetSourceFilter, payload)
Mixin::Response.new(resp, resp.keys) Mixin::Response.new(resp, resp.keys)
end end
@@ -554,7 +561,7 @@ module OBSWS
filterName: filter_name, filterName: filter_name,
filterIndex: filter_index filterIndex: filter_index
} }
call("SetSourceFilterIndex", payload) call(:SetSourceFilterIndex, payload)
end end
def set_source_filter_settings( def set_source_filter_settings(
@@ -569,7 +576,7 @@ module OBSWS
filterSettings: settings, filterSettings: settings,
overlay: overlay overlay: overlay
} }
call("SetSourceFilterSettings", payload) call(:SetSourceFilterSettings, payload)
end end
def set_source_filter_enabled(source_name, filter_name, enabled) def set_source_filter_enabled(source_name, filter_name, enabled)
@@ -578,18 +585,18 @@ module OBSWS
filterName: filter_name, filterName: filter_name,
filterEnabled: enabled filterEnabled: enabled
} }
call("SetSourceFilterEnabled", payload) call(:SetSourceFilterEnabled, payload)
end end
def get_scene_item_list(name) def get_scene_item_list(name)
payload = {sceneName: name} payload = {sceneName: name}
resp = call("GetSceneItemList", payload) resp = call(:GetSceneItemList, payload)
Mixin::Response.new(resp, resp.keys) Mixin::Response.new(resp, resp.keys)
end end
def get_group_scene_item_list(name) def get_group_scene_item_list(name)
payload = {sceneName: name} payload = {sceneName: name}
resp = call("GetGroupSceneItemList", payload) resp = call(:GetGroupSceneItemList, payload)
Mixin::Response.new(resp, resp.keys) Mixin::Response.new(resp, resp.keys)
end end
@@ -599,7 +606,7 @@ module OBSWS
sourceName: source_name, sourceName: source_name,
searchOffset: offset searchOffset: offset
} }
resp = call("GetSceneItemId", payload) resp = call(:GetSceneItemId, payload)
Mixin::Response.new(resp, resp.keys) Mixin::Response.new(resp, resp.keys)
end end
@@ -609,13 +616,13 @@ module OBSWS
sourceName: source_name, sourceName: source_name,
sceneItemEnabled: enabled sceneItemEnabled: enabled
} }
resp = call("CreateSceneItem", payload) resp = call(:CreateSceneItem, payload)
Mixin::Response.new(resp, resp.keys) Mixin::Response.new(resp, resp.keys)
end end
def remove_scene_item(scene_name, item_id) def remove_scene_item(scene_name, item_id)
payload = {sceneName: scene_name, sceneItemId: item_id} payload = {sceneName: scene_name, sceneItemId: item_id}
call("RemoveSceneItem", payload) call(:RemoveSceneItem, payload)
end end
def duplicate_scene_item(scene_name, item_id, dest_scene_name = nil) def duplicate_scene_item(scene_name, item_id, dest_scene_name = nil)
@@ -624,13 +631,13 @@ module OBSWS
sceneItemId: item_id, sceneItemId: item_id,
destinationSceneName: dest_scene_name destinationSceneName: dest_scene_name
} }
resp = call("DuplicateSceneItem", payload) resp = call(:DuplicateSceneItem, payload)
Mixin::Response.new(resp, resp.keys) Mixin::Response.new(resp, resp.keys)
end end
def get_scene_item_transform(scene_name, item_id) def get_scene_item_transform(scene_name, item_id)
payload = {sceneName: scene_name, sceneItemId: item_id} payload = {sceneName: scene_name, sceneItemId: item_id}
resp = call("GetSceneItemTransform", payload) resp = call(:GetSceneItemTransform, payload)
Mixin::Response.new(resp, resp.keys) Mixin::Response.new(resp, resp.keys)
end end
@@ -640,12 +647,12 @@ module OBSWS
sceneItemId: item_id, sceneItemId: item_id,
sceneItemTransform: transform sceneItemTransform: transform
} }
call("SetSceneItemTransform", payload) call(:SetSceneItemTransform, payload)
end end
def get_scene_item_enabled(scene_name, item_id) def get_scene_item_enabled(scene_name, item_id)
payload = {sceneName: scene_name, sceneItemId: item_id} payload = {sceneName: scene_name, sceneItemId: item_id}
resp = call("GetSceneItemEnabled", payload) resp = call(:GetSceneItemEnabled, payload)
Mixin::Response.new(resp, resp.keys) Mixin::Response.new(resp, resp.keys)
end end
@@ -655,12 +662,12 @@ module OBSWS
sceneItemId: item_id, sceneItemId: item_id,
sceneItemEnabled: enabled sceneItemEnabled: enabled
} }
call("SetSceneItemEnabled", payload) call(:SetSceneItemEnabled, payload)
end end
def get_scene_item_locked(scene_name, item_id) def get_scene_item_locked(scene_name, item_id)
payload = {sceneName: scene_name, sceneItemId: item_id} payload = {sceneName: scene_name, sceneItemId: item_id}
resp = call("GetSceneItemLocked", payload) resp = call(:GetSceneItemLocked, payload)
Mixin::Response.new(resp, resp.keys) Mixin::Response.new(resp, resp.keys)
end end
@@ -670,12 +677,12 @@ module OBSWS
sceneItemId: item_id, sceneItemId: item_id,
sceneItemLocked: locked sceneItemLocked: locked
} }
call("SetSceneItemLocked", payload) call(:SetSceneItemLocked, payload)
end end
def get_scene_item_index(scene_name, item_id) def get_scene_item_index(scene_name, item_id)
payload = {sceneName: scene_name, sceneItemId: item_id} payload = {sceneName: scene_name, sceneItemId: item_id}
resp = call("GetSceneItemIndex", payload) resp = call(:GetSceneItemIndex, payload)
Mixin::Response.new(resp, resp.keys) Mixin::Response.new(resp, resp.keys)
end end
@@ -683,14 +690,14 @@ module OBSWS
payload = { payload = {
sceneName: scene_name, sceneName: scene_name,
sceneItemId: item_id, sceneItemId: item_id,
sceneItemLocked: item_index sceneItemIndex: item_index
} }
call("SetSceneItemIndex", payload) call(:SetSceneItemIndex, payload)
end end
def get_scene_item_blend_mode(scene_name, item_id) def get_scene_item_blend_mode(scene_name, item_id)
payload = {sceneName: scene_name, sceneItemId: item_id} payload = {sceneName: scene_name, sceneItemId: item_id}
resp = call("GetSceneItemBlendMode", payload) resp = call(:GetSceneItemBlendMode, payload)
Mixin::Response.new(resp, resp.keys) Mixin::Response.new(resp, resp.keys)
end end
@@ -700,191 +707,192 @@ module OBSWS
sceneItemId: item_id, sceneItemId: item_id,
sceneItemBlendMode: blend sceneItemBlendMode: blend
} }
call("SetSceneItemBlendMode", payload) call(:SetSceneItemBlendMode, payload)
end end
def get_virtual_cam_status def get_virtual_cam_status
resp = call("GetVirtualCamStatus") resp = call(:GetVirtualCamStatus)
Mixin::Response.new(resp, resp.keys) Mixin::Response.new(resp, resp.keys)
end end
def toggle_virtual_cam def toggle_virtual_cam
resp = call("ToggleVirtualCam") resp = call(:ToggleVirtualCam)
Mixin::Response.new(resp, resp.keys) Mixin::Response.new(resp, resp.keys)
end end
def start_virtual_cam def start_virtual_cam
call("StartVirtualCam") call(:StartVirtualCam)
end end
def stop_virtual_cam def stop_virtual_cam
call("StopVirtualCam") call(:StopVirtualCam)
end end
def get_replay_buffer_status def get_replay_buffer_status
resp = call("GetReplayBufferStatus") resp = call(:GetReplayBufferStatus)
Mixin::Response.new(resp, resp.keys) Mixin::Response.new(resp, resp.keys)
end end
def toggle_replay_buffer def toggle_replay_buffer
resp = call("ToggleReplayBuffer") resp = call(:ToggleReplayBuffer)
Mixin::Response.new(resp, resp.keys) Mixin::Response.new(resp, resp.keys)
end end
def start_replay_buffer def start_replay_buffer
call("StartReplayBuffer") call(:StartReplayBuffer)
end end
def stop_replay_buffer def stop_replay_buffer
call("StopReplayBuffer") call(:StopReplayBuffer)
end end
def save_replay_buffer def save_replay_buffer
call("SaveReplayBuffer") call(:SaveReplayBuffer)
end end
def get_last_replay_buffer_replay def get_last_replay_buffer_replay
resp = call("GetLastReplayBufferReplay") resp = call(:GetLastReplayBufferReplay)
Mixin::Response.new(resp, resp.keys) Mixin::Response.new(resp, resp.keys)
end end
def get_output_list def get_output_list
resp = call("GetOutputList") resp = call(:GetOutputList)
Mixin::Response.new(resp, resp.keys) Mixin::Response.new(resp, resp.keys)
end end
def get_output_status(name) def get_output_status(name)
payload = {outputName: name} payload = {outputName: name}
resp = call("GetOutputStatus", payload) resp = call(:GetOutputStatus, payload)
Mixin::Response.new(resp, resp.keys) Mixin::Response.new(resp, resp.keys)
end end
def toggle_output(name) def toggle_output(name)
payload = {outputName: name} payload = {outputName: name}
resp = call("ToggleOutput", payload) resp = call(:ToggleOutput, payload)
Mixin::Response.new(resp, resp.keys) Mixin::Response.new(resp, resp.keys)
end end
def start_output(name) def start_output(name)
payload = {outputName: name} payload = {outputName: name}
call("StartOutput", payload) call(:StartOutput, payload)
end end
def stop_output(name) def stop_output(name)
payload = {outputName: name} payload = {outputName: name}
call("StopOutput", payload) call(:StopOutput, payload)
end end
def get_output_settings(name) def get_output_settings(name)
payload = {outputName: name} payload = {outputName: name}
resp = call("GetOutputSettings", payload) resp = call(:GetOutputSettings, payload)
Mixin::Response.new(resp, resp.keys) Mixin::Response.new(resp, resp.keys)
end end
def set_output_settings(name, settings) def set_output_settings(name, settings)
payload = {outputName: name, outputSettings: settings} payload = {outputName: name, outputSettings: settings}
call("SetOutputSettings", payload) call(:SetOutputSettings, payload)
end end
def get_stream_status def get_stream_status
resp = call("GetStreamStatus") resp = call(:GetStreamStatus)
Mixin::Response.new(resp, resp.keys) Mixin::Response.new(resp, resp.keys)
end end
def toggle_stream def toggle_stream
resp = call("ToggleStream") resp = call(:ToggleStream)
Mixin::Response.new(resp, resp.keys) Mixin::Response.new(resp, resp.keys)
end end
def start_stream def start_stream
call("StartStream") call(:StartStream)
end end
def stop_stream def stop_stream
call("StopStream") call(:StopStream)
end end
def send_stream_caption(caption) def send_stream_caption(caption)
call("SendStreamCaption") call(:SendStreamCaption)
end end
def get_record_status def get_record_status
resp = call("GetRecordStatus") resp = call(:GetRecordStatus)
Mixin::Response.new(resp, resp.keys) Mixin::Response.new(resp, resp.keys)
end end
def toggle_record def toggle_record
call("ToggleRecord") call(:ToggleRecord)
end end
def start_record def start_record
call("StartRecord") call(:StartRecord)
end end
def stop_record def stop_record
resp = call("StopRecord") resp = call(:StopRecord)
Mixin::Response.new(resp, resp.keys)
end end
def toggle_record_pause def toggle_record_pause
call("ToggleRecordPause") call(:ToggleRecordPause)
end end
def pause_record def pause_record
call("PauseRecord") call(:PauseRecord)
end end
def resume_record def resume_record
call("ResumeRecord") call(:ResumeRecord)
end end
def get_media_input_status(name) def get_media_input_status(name)
payload = {inputName: name} payload = {inputName: name}
resp = call("GetMediaInputStatus", payload) resp = call(:GetMediaInputStatus, payload)
Mixin::Response.new(resp, resp.keys) Mixin::Response.new(resp, resp.keys)
end end
def set_media_input_cursor(name, cursor) def set_media_input_cursor(name, cursor)
payload = {inputName: name, mediaCursor: cursor} payload = {inputName: name, mediaCursor: cursor}
call("SetMediaInputCursor", payload) call(:SetMediaInputCursor, payload)
end end
def offset_media_input_cursor(name, offset) def offset_media_input_cursor(name, offset)
payload = {inputName: name, mediaCursorOffset: offset} payload = {inputName: name, mediaCursorOffset: offset}
call("OffsetMediaInputCursor", payload) call(:OffsetMediaInputCursor, payload)
end end
def trigger_media_input_action(name, action) def trigger_media_input_action(name, action)
payload = {inputName: name, mediaAction: action} payload = {inputName: name, mediaAction: action}
call("TriggerMediaInputAction", payload) call(:TriggerMediaInputAction, payload)
end end
def get_studio_mode_enabled def get_studio_mode_enabled
resp = call("GetStudioModeEnabled") resp = call(:GetStudioModeEnabled)
Mixin::Response.new(resp, resp.keys) Mixin::Response.new(resp, resp.keys)
end end
def set_studio_mode_enabled(enabled) def set_studio_mode_enabled(enabled)
payload = {studioModeEnabled: enabled} payload = {studioModeEnabled: enabled}
call("SetStudioModeEnabled", payload) call(:SetStudioModeEnabled, payload)
end end
def open_input_properties_dialog(name) def open_input_properties_dialog(name)
payload = {inputName: name} payload = {inputName: name}
call("OpenInputPropertiesDialog", payload) call(:OpenInputPropertiesDialog, payload)
end end
def open_input_filters_dialog(name) def open_input_filters_dialog(name)
payload = {inputName: name} payload = {inputName: name}
call("OpenInputFiltersDialog", payload) call(:OpenInputFiltersDialog, payload)
end end
def open_input_interact_dialog(name) def open_input_interact_dialog(name)
payload = {inputName: name} payload = {inputName: name}
call("OpenInputInteractDialog", payload) call(:OpenInputInteractDialog, payload)
end end
def get_monitor_list def get_monitor_list
resp = call("GetMonitorList") resp = call(:GetMonitorList)
Mixin::Response.new(resp, resp.keys) Mixin::Response.new(resp, resp.keys)
end end
end end

View File

@@ -1,12 +1,12 @@
module OBSWS module OBSWS
module Util module Util
class ::String module String
def to_camel def camelcase(s)
self.split(/_/).map(&:capitalize).join s.split("_").map(&:capitalize).join
end end
def to_snake def snakecase(s)
self s
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2') .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
.gsub(/([a-z\d])([A-Z])/, '\1_\2') .gsub(/([a-z\d])([A-Z])/, '\1_\2')
.downcase .downcase

View File

@@ -1,5 +1,5 @@
module OBSWS module OBSWS
module Version module VERSION
module_function module_function
def major def major
@@ -7,11 +7,11 @@ module OBSWS
end end
def minor def minor
0 6
end end
def patch def patch
3 0
end end
def to_a def to_a

25
main.rb
View File

@@ -1,17 +1,18 @@
require_relative "lib/obsws" require "obsws"
def main class Main
r_client = INPUT = "Mic/Aux"
OBSWS::Requests::Client.new(
host: "localhost",
port: 4455,
password: "strongpassword"
)
r_client.run do def run
# Toggle the mute state of your Mic input OBSWS::Requests::Client
r_client.toggle_input_mute("Mic/Aux") .new(host: "localhost", port: 4455, password: "strongpassword")
.run do |client|
# Toggle the mute state of your Mic input and print its new mute state
client.toggle_input_mute(INPUT)
resp = client.get_input_mute(INPUT)
puts "Input '#{INPUT}' was set to #{resp.input_muted}"
end
end end
end end
main if $0 == __FILE__ Main.new.run if $PROGRAM_NAME == __FILE__

View File

@@ -1,10 +1,10 @@
# frozen_string_literal: true # frozen_string_literal: true
require File.expand_path("lib/obsws/version", __dir__) require File.expand_path("lib/obsws/version", __dir__)
lib = File.expand_path("./lib")
Gem::Specification.new do |spec| Gem::Specification.new do |spec|
spec.name = "obsws" spec.name = "obsws"
spec.version = OBSWS::Version spec.version = OBSWS::VERSION
spec.summary = "OBS Websocket v5 wrapper" spec.summary = "OBS Websocket v5 wrapper"
spec.description = "A Ruby wrapper around OBS Websocket v5" spec.description = "A Ruby wrapper around OBS Websocket v5"
spec.authors = ["onyx_online"] spec.authors = ["onyx_online"]
@@ -13,14 +13,13 @@ Gem::Specification.new do |spec|
spec.extra_rdoc_files = Dir["README.md", "CHANGELOG.md", "LICENSE"] spec.extra_rdoc_files = Dir["README.md", "CHANGELOG.md", "LICENSE"]
spec.homepage = "https://rubygems.org/gems/obsws" spec.homepage = "https://rubygems.org/gems/obsws"
spec.license = "MIT" spec.license = "MIT"
spec.add_runtime_dependency "observer", "~> 0.1.1"
spec.add_runtime_dependency "websocket-driver", "~> 0.7.5" spec.add_runtime_dependency "websocket-driver", "~> 0.7.5"
spec.add_runtime_dependency "waitutil", "~> 0.2.1" spec.add_runtime_dependency "waitutil", "~> 0.2.1"
spec.add_development_dependency "perfect_toml", "~> 0.9.0" spec.add_development_dependency "standard", "~> 1.30"
spec.add_development_dependency "minitest", "~> 5.16", ">= 5.16.3" spec.add_development_dependency "minitest", "~> 5.16", ">= 5.16.3"
spec.add_development_dependency "rake", "~> 11.2", ">= 11.2.2" spec.add_development_dependency "rake", "~> 11.2", ">= 11.2.2"
spec.required_ruby_version = ">= 3.0" spec.required_ruby_version = ">= 3.0"
spec.metadata = { spec.metadata = {
"source_code_uri" => "https://github.com/onyx-and-iris/obsws.git" "source_code_uri" => "https://github.com/onyx-and-iris/obsws-ruby"
} }
end end

View File

@@ -1,17 +1,21 @@
require "minitest" require "minitest"
require "minitest/autorun" require "minitest/autorun"
require "perfect_toml" require "yaml"
require_relative "../lib/obsws" require_relative "../lib/obsws"
class OBSWSTest < Minitest::Test class OBSWSTest < Minitest::Test
def self.before_run class << self
conn = PerfectTOML.load_file("obs.toml", symbolize_names: true)[:connection] attr_reader :r_client
@@r_client = OBSWS::Requests::Client.new(**conn) end
@@r_client.create_scene("START_TEST") def self.before_run
@@r_client.create_scene("BRB_TEST") conn = YAML.load_file("obs.yml", symbolize_names: true)[:connection]
@@r_client.create_scene("END_TEST") @r_client = OBSWS::Requests::Client.new(**conn)
r_client.create_scene("START_TEST")
r_client.create_scene("BRB_TEST")
r_client.create_scene("END_TEST")
end end
before_run before_run
@@ -23,9 +27,9 @@ class OBSWSTest < Minitest::Test
end end
Minitest.after_run do Minitest.after_run do
@@r_client.remove_scene("START_TEST") r_client.remove_scene("START_TEST")
@@r_client.remove_scene("BRB_TEST") r_client.remove_scene("BRB_TEST")
@@r_client.remove_scene("END_TEST") r_client.remove_scene("END_TEST")
@@r_client.close r_client.close
end end
end end

View File

@@ -1,17 +0,0 @@
require_relative "../minitest_helper"
class AttrsTest < OBSWSTest
def test_get_version_attrs
resp = @@r_client.get_version
assert resp.attrs ==
%w[
available_requests
obs_version
obs_web_socket_version
platform
platform_description
rpc_version
supported_image_formats
]
end
end

17
test/obsws/test_attrs.rb Normal file
View File

@@ -0,0 +1,17 @@
require_relative "../minitest_helper"
class AttrsTest < Minitest::Test
def test_get_version_attrs
resp = OBSWSTest.r_client.get_version
assert resp.attrs ==
%w[
available_requests
obs_version
obs_web_socket_version
platform
platform_description
rpc_version
supported_image_formats
]
end
end

36
test/obsws/test_error.rb Normal file
View File

@@ -0,0 +1,36 @@
require_relative "../minitest_helper"
class OBSWSConnectionErrorTest < Minitest::Test
def test_it_raises_an_obsws_connection_error_on_wrong_password
e = assert_raises(OBSWS::OBSWSConnectionError) do
OBSWS::Requests::Client
.new(host: "localhost", port: 4455, password: "wrongpassword", connect_timeout: 0.1)
end
assert_equal("Timed out waiting for successful identification (0.1 seconds elapsed)", e.message)
end
def test_it_raises_an_obsws_connection_error_on_auth_enabled_but_no_password_provided_for_request_client
e = assert_raises(OBSWS::OBSWSConnectionError) do
OBSWS::Requests::Client
.new(host: "localhost", port: 4455, password: "")
end
assert_equal("auth enabled but no password provided", e.message)
end
def test_it_raises_an_obsws_connection_error_on_auth_enabled_but_no_password_provided_for_event_client
e = assert_raises(OBSWS::OBSWSConnectionError) do
OBSWS::Events::Client
.new(host: "localhost", port: 4455, password: "")
end
assert_equal("auth enabled but no password provided", e.message)
end
end
class OBSWSRequestErrorTest < Minitest::Test
def test_it_raises_an_obsws_request_error_on_invalid_request
e = assert_raises(OBSWS::OBSWSRequestError) { OBSWSTest.r_client.toggle_input_mute("unknown") }
assert_equal("ToggleInputMute", e.req_name)
assert_equal(600, e.code)
assert_equal("Request ToggleInputMute returned code 600. With message: No source was found by the name of `unknown`.", e.message)
end
end

View File

@@ -1,22 +1,22 @@
require_relative "../minitest_helper" require_relative "../minitest_helper"
class RequestTest < OBSWSTest class RequestTest < Minitest::Test
def test_it_checks_obs_major_version def test_it_checks_obs_major_version
resp = @@r_client.get_version resp = OBSWSTest.r_client.get_version
ver = resp.obs_version.split(".").map(&:to_i) ver = resp.obs_version.split(".").map(&:to_i)
assert ver[0] >= 28 assert ver[0] >= 28
end end
def test_it_checks_ws_major_version def test_it_checks_ws_major_version
resp = @@r_client.get_version resp = OBSWSTest.r_client.get_version
ver = resp.obs_web_socket_version.split(".").map(&:to_i) ver = resp.obs_web_socket_version.split(".").map(&:to_i)
assert ver[0] >= 5 assert ver[0] >= 5
end end
def test_it_sets_and_gets_current_program_scene def test_it_sets_and_gets_current_program_scene
%w[START_TEST BRB_TEST END_TEST].each do |s| %w[START_TEST BRB_TEST END_TEST].each do |s|
@@r_client.set_current_program_scene(s) OBSWSTest.r_client.set_current_program_scene(s)
resp = @@r_client.get_current_program_scene resp = OBSWSTest.r_client.get_current_program_scene
assert resp.current_program_scene_name == s assert resp.current_program_scene_name == s
end end
end end
@@ -26,8 +26,8 @@ class RequestTest < OBSWSTest
server: "rtmp://addressofrtmpserver", server: "rtmp://addressofrtmpserver",
key: "live_myvery_secretkey" key: "live_myvery_secretkey"
} }
@@r_client.set_stream_service_settings("rtmp_common", settings) OBSWSTest.r_client.set_stream_service_settings("rtmp_common", settings)
resp = @@r_client.get_stream_service_settings resp = OBSWSTest.r_client.get_stream_service_settings
assert resp.stream_service_type == "rtmp_common" assert resp.stream_service_type == "rtmp_common"
assert resp.stream_service_settings == assert resp.stream_service_settings ==
{ {