obsws 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 339b5706222511d0cf272eff7040f45dc5bc1cffdfdc6b6d0cc59ea299c325e1
4
+ data.tar.gz: 2070c51a27e764297d2555bd958411fd8ee06c49f98bd09fa6144f7fba5790c8
5
+ SHA512:
6
+ metadata.gz: c80761585030c73bd304de37ead5ea8f5260116f7e2acb6a15549d920695be579c0fc0ffa9f0485569ffd56f4c11b6ca027711eeadb2ecb81c8b9b3016e68396
7
+ data.tar.gz: 13fc659eb70544b51555cb9f68608927f64671377f550ae78c3d9a83b60de2f42050d17403ae72aeb701525fc15e48a5086d37f05c780b4055a24cfe252bd0a1
data/CHANGELOG.md ADDED
File without changes
data/README.md ADDED
@@ -0,0 +1,116 @@
1
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/onyx-and-iris/voicemeeter-api-ruby/blob/dev/LICENSE)
2
+ [![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square)](https://github.com/prettier/plugin-ruby)
3
+
4
+ # A Ruby wrapper around OBS Studio WebSocket v5.0
5
+
6
+ ## Requirements
7
+
8
+ - [OBS Studio](https://obsproject.com/)
9
+ - [OBS Websocket v5 Plugin](https://github.com/obsproject/obs-websocket/releases/tag/5.0.0)
10
+ - With the release of OBS Studio version 28, Websocket plugin is included by default. But it should be manually installed for earlier versions of OBS.
11
+ - Ruby 3.0 or greater
12
+
13
+ ## `Use`
14
+
15
+ #### Example `main.rb`
16
+
17
+ pass `host`, `port` and `password` as keyword arguments.
18
+
19
+ ```ruby
20
+ require_relative "lib/obsws"
21
+
22
+ def main
23
+ r_client =
24
+ OBSWS::Requests::Client.new(
25
+ host: "localhost",
26
+ port: 4455,
27
+ password: "strongpassword"
28
+ )
29
+
30
+ r_client.run do
31
+ # Toggle the mute state of your Mic input
32
+ r_client.toggle_input_mute("Mic/Aux")
33
+ end
34
+ end
35
+
36
+ main if $0 == __FILE__
37
+ ```
38
+
39
+ ### Requests
40
+
41
+ Method names for requests match the API calls but snake cased. `run` accepts a block that closes the socket once you are done.
42
+
43
+ example:
44
+
45
+ ```ruby
46
+ r_client.run do
47
+ # GetVersion
48
+ resp = r_client.get_version
49
+
50
+ # SetCurrentProgramScene
51
+ r_client.set_current_program_scene("BRB")
52
+ end
53
+ ```
54
+
55
+ For a full list of requests refer to [Requests](https://github.com/obsproject/obs-websocket/blob/master/docs/generated/protocol.md#requests)
56
+
57
+ ### Events
58
+
59
+ Register an observer class and define `on_` methods for events. Method names should match the api event but snake cased.
60
+
61
+ example:
62
+
63
+ ```ruby
64
+ class Observer
65
+ def initialize
66
+ @e_client = OBSWS::Events::Client.new(**kwargs)
67
+ # register class with the event client
68
+ @e_client.add_observer(self)
69
+ end
70
+
71
+ # define "on_" event methods.
72
+ def on_current_program_scene_changed
73
+ ...
74
+ end
75
+ def on_input_mute_state_changed
76
+ ...
77
+ end
78
+ ...
79
+ end
80
+ ```
81
+
82
+ For a full list of events refer to [Events](https://github.com/obsproject/obs-websocket/blob/master/docs/generated/protocol.md#events)
83
+
84
+ ### Attributes
85
+
86
+ For both request responses and event data you may inspect the available attributes using `attrs`.
87
+
88
+ example:
89
+
90
+ ```ruby
91
+ resp = cl.get_version
92
+ p resp.attrs
93
+
94
+ def on_scene_created(data):
95
+ p data.attrs
96
+ ```
97
+
98
+ ### Errors
99
+
100
+ If a request fails an `OBSWSError` will be raised with a status code.
101
+
102
+ For a full list of status codes refer to [Codes](https://github.com/obsproject/obs-websocket/blob/master/docs/generated/protocol.md#requeststatus)
103
+
104
+ ### Tests
105
+
106
+ To run all tests:
107
+
108
+ ```
109
+ bundle exec rake -v
110
+ ```
111
+
112
+ ### Official Documentation
113
+
114
+ For the full documentation:
115
+
116
+ - [OBS Websocket SDK](https://github.com/obsproject/obs-websocket/blob/master/docs/generated/protocol.md#obs-websocket-501-protocol)
data/lib/obsws/base.rb ADDED
@@ -0,0 +1,117 @@
1
+ require "socket"
2
+ require "websocket/driver"
3
+ require "digest/sha2"
4
+ require "json"
5
+ require "observer"
6
+ require "waitutil"
7
+
8
+ require_relative "mixin"
9
+ require_relative "error"
10
+
11
+ module OBSWS
12
+ class Socket
13
+ attr_reader :url
14
+
15
+ def initialize(url, socket)
16
+ @url = url
17
+ @socket = socket
18
+ end
19
+
20
+ def write(s)
21
+ @socket.write(s)
22
+ end
23
+ end
24
+
25
+ class Base
26
+ include Observable
27
+ include Mixin::OPCodes
28
+
29
+ attr_reader :id, :driver, :closed
30
+
31
+ def initialize(**kwargs)
32
+ host = kwargs[:host] || "localhost"
33
+ port = kwargs[:port] || 4455
34
+ @password = kwargs[:password] || ""
35
+ @subs = kwargs[:subs] || 0
36
+
37
+ @socket = TCPSocket.new(host, port)
38
+ @driver =
39
+ WebSocket::Driver.client(Socket.new("ws://#{host}:#{port}", @socket))
40
+ @ready = false
41
+ @closed = false
42
+ @driver.on :open do |msg|
43
+ LOGGER.debug("driver socket open")
44
+ @ready = true
45
+ end
46
+ @driver.on :close do |msg|
47
+ LOGGER.debug("driver socket closed")
48
+ @closed = true
49
+ end
50
+ @driver.on :message do |msg|
51
+ LOGGER.debug("received [#{msg}] passing to handler")
52
+ msg_handler(JSON.parse(msg.data, symbolize_names: true))
53
+ end
54
+ Thread.new { start_driver }
55
+ WaitUtil.wait_for_condition(
56
+ "driver socket ready",
57
+ delay_sec: 0.01,
58
+ timeout_sec: 0.5
59
+ ) { @ready }
60
+ end
61
+
62
+ def start_driver
63
+ @driver.start
64
+
65
+ loop do
66
+ @driver.parse(@socket.readpartial(4096))
67
+ rescue EOFError
68
+ break
69
+ end
70
+ end
71
+
72
+ def auth_token(salt:, challenge:)
73
+ Digest::SHA256.base64digest(
74
+ Digest::SHA256.base64digest(@password + salt) + challenge
75
+ )
76
+ end
77
+
78
+ def authenticate(auth)
79
+ token = auth_token(**auth)
80
+ payload = {
81
+ op: Mixin::OPCodes::IDENTIFY,
82
+ d: {
83
+ rpcVersion: 1,
84
+ authentication: token,
85
+ eventSubscriptions: @subs
86
+ }
87
+ }
88
+ @driver.text(JSON.generate(payload))
89
+ end
90
+
91
+ def msg_handler(data)
92
+ op_code = data[:op]
93
+ case op_code
94
+ when Mixin::OPCodes::HELLO
95
+ authenticate(data[:d][:authentication])
96
+ when Mixin::OPCodes::IDENTIFIED
97
+ LOGGER.debug("Authentication successful")
98
+ when Mixin::OPCodes::EVENT, Mixin::OPCodes::REQUESTRESPONSE
99
+ changed
100
+ notify_observers(op_code, data[:d])
101
+ end
102
+ end
103
+
104
+ def req(id, type_, data = nil)
105
+ payload = {
106
+ op: Mixin::OPCodes::REQUEST,
107
+ d: {
108
+ requestType: type_,
109
+ requestId: id
110
+ }
111
+ }
112
+ payload[:d][:requestData] = data if data
113
+ queued = @driver.text(JSON.generate(payload))
114
+ LOGGER.debug("request with id #{id} queued? #{queued}")
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,6 @@
1
+ module OBSWS
2
+ module Error
3
+ class OBSWSError < StandardError
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,92 @@
1
+ require "json"
2
+
3
+ require_relative "util"
4
+ require_relative "mixin"
5
+
6
+ module OBSWS
7
+ module Events
8
+ module SUBS
9
+ NONE = 0
10
+ GENERAL = (1 << 0)
11
+ CONFIG = (1 << 1)
12
+ SCENES = (1 << 2)
13
+ INPUTS = (1 << 3)
14
+ TRANSITIONS = (1 << 4)
15
+ FILTERS = (1 << 5)
16
+ OUTPUTS = (1 << 6)
17
+ SCENEITEMS = (1 << 7)
18
+ MEDIAINPUTS = (1 << 8)
19
+ VENDORS = (1 << 9)
20
+ UI = (1 << 10)
21
+
22
+ def low_volume
23
+ GENERAL | CONFIG | SCENES | INPUTS | TRANSITIONS | FILTERS | OUTPUTS |
24
+ SCENEITEMS | MEDIAINPUTS | VENDORS | UI
25
+ end
26
+
27
+ INPUTVOLUMEMETERS = (1 << 16)
28
+ INPUTACTIVESTATECHANGED = (1 << 17)
29
+ INPUTSHOWSTATECHANGED = (1 << 18)
30
+ SCENEITEMTRANSFORMCHANGED = (1 << 19)
31
+
32
+ def high_volume
33
+ INPUTVOLUMEMETERS | INPUTACTIVESTATECHANGED | INPUTSHOWSTATECHANGED |
34
+ SCENEITEMTRANSFORMCHANGED
35
+ end
36
+
37
+ def all
38
+ low_volume | high_volume
39
+ end
40
+
41
+ module_function :low_volume, :high_volume, :all
42
+ end
43
+
44
+ module Callbacks
45
+ include Util
46
+
47
+ def add_observer(observer)
48
+ @observers = [] unless defined?(@observers)
49
+ observer = [observer] if !observer.respond_to? :each
50
+ observer.each { |o| @observers.append(o) }
51
+ end
52
+
53
+ def remove_observer(observer)
54
+ @observers.delete(observer)
55
+ end
56
+
57
+ def notify_observers(event, data)
58
+ if defined?(@observers)
59
+ @observers.each do |o|
60
+ if o.respond_to? "on_#{event.to_snake}"
61
+ if data.empty?
62
+ o.send("on_#{event.to_snake}")
63
+ else
64
+ o.send("on_#{event.to_snake}", data)
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+
72
+ class Client
73
+ include Callbacks
74
+ include Mixin::TearDown
75
+ include Mixin::OPCodes
76
+
77
+ def initialize(**kwargs)
78
+ kwargs[:subs] = SUBS.low_volume
79
+ @base_client = Base.new(**kwargs)
80
+ @base_client.add_observer(self)
81
+ end
82
+
83
+ def update(op_code, data)
84
+ if op_code == Mixin::OPCodes::EVENT
85
+ event = data[:eventType]
86
+ data = data.key?(:eventData) ? data[:eventData] : {}
87
+ notify_observers(event, Mixin::Data.new(data, data.keys))
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,53 @@
1
+ require_relative "util"
2
+
3
+ module OBSWS
4
+ module Mixin
5
+ module Meta
6
+ include Util
7
+
8
+ def make_response_methods(*params)
9
+ params.each do |param|
10
+ define_singleton_method(param.to_s.to_snake) { @resp[param] }
11
+ end
12
+ end
13
+ end
14
+
15
+ class MetaObject
16
+ include Mixin::Meta
17
+
18
+ def initialize(resp, fields)
19
+ @resp = resp
20
+ @fields = fields
21
+ self.make_response_methods *fields
22
+ end
23
+
24
+ def empty? = @fields.empty?
25
+
26
+ def attrs = @fields.map { |f| f.to_s.to_snake }
27
+ end
28
+
29
+ class Response < MetaObject
30
+ end
31
+
32
+ class Data < MetaObject
33
+ end
34
+
35
+ module TearDown
36
+ def close
37
+ @base_client.driver.close
38
+ end
39
+ end
40
+
41
+ module OPCodes
42
+ HELLO = 0
43
+ IDENTIFY = 1
44
+ IDENTIFIED = 2
45
+ REIDENTIFY = 3
46
+ EVENT = 5
47
+ REQUEST = 6
48
+ REQUESTRESPONSE = 7
49
+ REQUESTBATCH = 8
50
+ REQUESTBATCHRESPONSE = 9
51
+ end
52
+ end
53
+ end