mumble-ruby2 1.1.4

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: b0b2f66cdde08f10db6f6af8e9fd08eedf13cf856b0674446c4ea726238c09f4
4
+ data.tar.gz: 2938bc5085b878c7fa0611074bd9ca3ed1d140210b0a6b95fb5a3dec23f1e272
5
+ SHA512:
6
+ metadata.gz: 78bd56173cb547f21bf784bd3c16dd8851c415141f100bf4c1f4954c60eda2132fbb43839e1ac15bc199bd838f8c1dd216ef467cbd1898e9a866fc33e977dfa8
7
+ data.tar.gz: 87df7a958f3b142d8418e0da1d53c3c4b20c865a5d95e35588508e158c38021427d4a14589a745c6f8e1339fa87b021217d84851af47498c636816273911f429
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.ruby-gemset ADDED
@@ -0,0 +1 @@
1
+ mumble_ruby_gem
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ ruby-3.0
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'http://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in mumble-ruby2.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Matthew Perry, Philip Mayer
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,119 @@
1
+ mumble-ruby
2
+ https://github.com/Shadowsith/mumble-ruby2
3
+
4
+ == DESCRIPTION:
5
+
6
+ Mumble-Ruby is a headless client for the Mumble VOIP application.
7
+ Mumble-Ruby provides the ability to write scripts and bots which interact with mumble servers through a simple DSL.
8
+ Mumble-Ruby also has the ability to stream raw audio from a fifo pipe (mpd) to the mumble server.
9
+ There is huge room for improvement in this library and I am willing to accept all sorts of pull requests so please do.
10
+
11
+ == INSTALL:
12
+
13
+ [sudo] gem install mumble-ruby2
14
+
15
+ == REQUIREMENTS:
16
+
17
+ * Ruby >= 2.1.0
18
+ * OPUS Audio Codec
19
+ * Murmur server > 1.2.4 -- NOTE: mumble-ruby2 will not be able to stream audio to servers that don't support OPUS anymore. I haven't looked into backwards-compatability with CELT.
20
+
21
+ == RECENT CHANGES:
22
+
23
+ * Merged changes for proper user/channel objects
24
+ * Merged changes for recording feature
25
+ * Added half-broken support for playing files (Something is wrong, wouldn't recommend using it)
26
+ * Bit of refactoring and renaming
27
+
28
+ * Added OPUS support
29
+ * Added more configuration options
30
+ * Added image text messages
31
+ * Added ssl cert auth
32
+ * Fixed several bugs
33
+
34
+ == BASIC USAGE:
35
+
36
+ # Configure all clients globally
37
+ Mumble.configure do |conf|
38
+ # sample rate of sound (48 khz recommended)
39
+ conf.sample_rate = 48000
40
+
41
+ # bitrate of sound (32 kbit/s recommended)
42
+ conf.bitrate = 32000
43
+
44
+ # directory to store user's ssl certs
45
+ conf.ssl_cert_opts[:cert_dir] = File.expand_path("./")
46
+ end
47
+
48
+ # Create client instance for your server
49
+ cli = Mumble::Client.new('localhost') do |conf|
50
+ conf.username = 'Mumble Bot'
51
+ conf.password = 'password123'
52
+
53
+ # Overwrite global config
54
+ conf.bitrate = 48000
55
+ end
56
+ # => #<Mumble::Client:0x00000003064fe8 @host="localhost", @port=64738, @username="Mumble Bot", @password="password123", @channels={}, @users={}, @callbacks={}>
57
+
58
+ # Set up some callbacks for when you recieve text messages
59
+ # There are callbacks for every Mumble Protocol Message that a client can recieve
60
+ # For a reference on those, see the linked PDF at the bottom of the README.
61
+ cli.on_text_message do |msg|
62
+ puts msg.message
63
+ end
64
+ # => [#<Proc:0x0000000346e5f8@(irb):2>]
65
+
66
+ # Initiate the connection to the client
67
+ cli.connect
68
+ # => #<Thread:0x000000033d7388 run>
69
+
70
+ # Mute and Deafen yourself after connecting
71
+ cli.on_connected do
72
+ cli.me.mute
73
+ cli.me.deafen
74
+ end
75
+
76
+ # Join the channel titled "Chillen" (this will return a channel object for that channel)
77
+ cli.join_channel('Chillen')
78
+
79
+ # Get a list of channels
80
+ cli.channels
81
+ # Returns a hash of channel_id: Channel objects
82
+
83
+ # Join Channel using ID
84
+ cli.join_channel(0)
85
+
86
+ # Join Channel using Channel object
87
+ cli.join_channel(cli.channels[0])
88
+ cli.channels[0].join
89
+
90
+ # Get a list of users
91
+ cli.users
92
+ # Returns a hash of session_id: UserState Messages
93
+
94
+ # Text user
95
+ cli.text_user('perrym5', "Hello there, I'm a robot!")
96
+
97
+ # Text an image to a channel
98
+ cli.text_channel_img('Chillen', '/path/to/image.jpg')
99
+
100
+ # Start streaming from a FIFO queue of raw PCM data
101
+ cli.player.stream_named_pipe('/tmp/mpd.fifo')
102
+
103
+ # EXPERIMENTAL: Recording feature
104
+ cli.recorder.start('/home/matt/record.wav')
105
+ sleep(2)
106
+ cli.recorder.stop
107
+
108
+ # EXPERIMENTAL: Play wav files
109
+ cli.player.play_file('/home/matt/record.wav')
110
+
111
+ # Safely disconnect
112
+ cli.disconnect
113
+ # => nil
114
+
115
+ == MUMBLE PROTOCOL:
116
+
117
+ The documentation for Mumble's control and voice protocol is a good reference for using this client as all of the callbacks
118
+ are based on the types of messages the Mumble uses to accomplish its tasks.
119
+ You can see it here[https://github.com/mumble-voip/mumble-protocol/blob/master/protocol_stack_tcp.rst].
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
@@ -0,0 +1,39 @@
1
+ require 'opus-ruby'
2
+ require 'active_support/inflector'
3
+ require 'mumble-ruby2/version'
4
+ require 'mumble-ruby2/thread_tools'
5
+ require 'mumble-ruby2/messages'
6
+ require 'mumble-ruby2/connection'
7
+ require 'mumble-ruby2/model'
8
+ require 'mumble-ruby2/user'
9
+ require 'mumble-ruby2/channel'
10
+ require 'mumble-ruby2/client'
11
+ require 'mumble-ruby2/audio_player'
12
+ require 'mumble-ruby2/packet_data_stream'
13
+ require 'mumble-ruby2/img_reader'
14
+ require 'mumble-ruby2/cert_manager'
15
+ require 'mumble-ruby2/audio_recorder'
16
+ require 'hashie'
17
+
18
+ module Mumble
19
+ DEFAULTS = {
20
+ sample_rate: 48000,
21
+ bitrate: 32000,
22
+ ssl_cert_opts: {
23
+ cert_dir: File.expand_path("./"),
24
+ country_code: "US",
25
+ organization: "github.com",
26
+ organization_unit: "Engineering"
27
+ }
28
+ }
29
+
30
+ def self.configuration
31
+ @configuration ||= Hashie::Mash.new(DEFAULTS)
32
+ end
33
+
34
+ def self.configure
35
+ yield(configuration) if block_given?
36
+ end
37
+
38
+ Thread.abort_on_exception = true
39
+ end
@@ -0,0 +1,107 @@
1
+ require 'wavefile'
2
+
3
+ module Mumble
4
+ class AudioPlayer
5
+ include ThreadTools
6
+ COMPRESSED_SIZE = 960
7
+
8
+ def initialize(type, connection, sample_rate, bitrate)
9
+ @packet_header = (type << 5).chr
10
+ @conn = connection
11
+ @pds = PacketDataStream.new
12
+ @queue = Queue.new
13
+ @wav_format = WaveFile::Format.new :mono, :pcm_16, sample_rate
14
+
15
+ create_encoder sample_rate, bitrate
16
+ end
17
+
18
+ def volume
19
+ @volume ||= 100
20
+ end
21
+
22
+ def volume=(volume)
23
+ @volume = volume
24
+ end
25
+
26
+ def playing?
27
+ @playing ||= false
28
+ end
29
+
30
+ def play_file(file)
31
+ unless playing?
32
+ @file = WaveFile::Reader.new(file, @wav_format)
33
+ Thread.new { bounded_produce }
34
+ @playing = true
35
+ end
36
+ end
37
+
38
+ def stream_named_pipe(pipe)
39
+ unless playing?
40
+ @file = File.open(pipe, 'rb')
41
+ spawn_threads :produce, :consume
42
+ @playing = true
43
+ end
44
+ end
45
+
46
+ def stop
47
+ if playing?
48
+ kill_threads
49
+ @encoder.reset
50
+ @file.close unless @file.closed?
51
+ @playing = false
52
+ end
53
+ end
54
+
55
+ private
56
+ def create_encoder(sample_rate, bitrate)
57
+ @encoder = Opus::Encoder.new sample_rate, sample_rate / 100, 1
58
+ @encoder.vbr_rate = 0 # CBR
59
+ @encoder.bitrate = bitrate
60
+ end
61
+
62
+ def change_volume(pcm_data)
63
+ pcm_data.unpack('s*').map { |s| s * (volume / 100.0) }.pack('s*')
64
+ end
65
+
66
+ def bounded_produce
67
+ frame_count = 0
68
+ start_time = Time.now.to_f
69
+ @file.each_buffer(@encoder.frame_size) do |buffer|
70
+ encode_sample buffer.samples.pack('s*')
71
+ consume
72
+ frame_count += 1
73
+ wait_time = start_time - Time.now.to_f + frame_count * 0.01
74
+ sleep(wait_time) if wait_time > 0
75
+ end
76
+
77
+ stop
78
+ end
79
+
80
+ def produce
81
+ encode_sample @file.read(@encoder.frame_size * 2)
82
+ end
83
+
84
+ def encode_sample(sample)
85
+ pcm_data = change_volume sample
86
+ @queue << @encoder.encode(pcm_data, COMPRESSED_SIZE)
87
+ end
88
+
89
+ def consume
90
+ @seq ||= 0
91
+ @seq %= 1000000 # Keep sequence number reasonable for long runs
92
+
93
+ @pds.rewind
94
+ @seq += 1
95
+ @pds.put_int @seq
96
+
97
+ frame = @queue.pop
98
+ @pds.put_int frame.size
99
+ @pds.append_block frame
100
+
101
+ size = @pds.size
102
+ @pds.rewind
103
+ data = [@packet_header, @pds.get_block(size)].flatten.join
104
+ @conn.send_udp_packet data
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,79 @@
1
+ require 'wavefile'
2
+ require 'thread'
3
+
4
+ module Mumble
5
+ class AudioRecorder
6
+ include ThreadTools
7
+
8
+ def initialize(client, sample_rate)
9
+ @client = client
10
+ @wav_format = WaveFile::Format.new(:mono, :pcm_16, sample_rate)
11
+ @pds = PacketDataStream.new
12
+ @pds_lock = Mutex.new
13
+
14
+ @decoders = Hash.new do |h, k|
15
+ h[k] = Opus::Decoder.new sample_rate, sample_rate / 100, 1
16
+ end
17
+
18
+ @queues = Hash.new do |h, k|
19
+ h[k] = Queue.new
20
+ end
21
+ end
22
+
23
+ def recording?
24
+ @recording ||= false
25
+ end
26
+
27
+ def start(file)
28
+ unless recording?
29
+ @file = WaveFile::Writer.new(file, @wav_format)
30
+ @callback = @client.on_udp_tunnel { |msg| process_udp_tunnel msg }
31
+ spawn_thread :write_audio
32
+ @recording = true
33
+ end
34
+ end
35
+
36
+ def stop
37
+ if recording?
38
+ @client.remove_callback :udp_tunnel, @callback
39
+ kill_threads
40
+ @decoders.values.each &:destroy
41
+ @decoders.clear
42
+ @queues.clear
43
+ @file.close
44
+ @recording = false
45
+ end
46
+ end
47
+
48
+ private
49
+ def process_udp_tunnel(message)
50
+ @pds_lock.synchronize do
51
+ @pds.rewind
52
+ @pds.append_block message.packet[1..-1]
53
+
54
+ @pds.rewind
55
+ source = @pds.get_int
56
+ seq = @pds.get_int
57
+ len = @pds.get_int
58
+ opus = @pds.get_block len
59
+
60
+ @queues[source] << @decoders[source].decode(opus.join)
61
+ end
62
+ end
63
+
64
+ # TODO: Better audio stream merge with normalization
65
+ def write_audio
66
+ pcms = @queues.values
67
+ .reject { |q| q.empty? } # Remove empty queues
68
+ .map { |q| q.pop.unpack 's*' } # Grab the top element of each queue and expand
69
+
70
+ head, *tail = pcms
71
+ if head
72
+ samples = head.zip(*tail)
73
+ .map { |pcms| pcms.reduce(:+) / pcms.size } # Average together all the columns of the matrix (merge audio streams)
74
+ .flatten # Flatten the resulting 1d matrix
75
+ @file.write WaveFile::Buffer.new(samples, @wav_format)
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,67 @@
1
+ require 'openssl'
2
+ require 'fileutils'
3
+
4
+ module Mumble
5
+ class CertManager
6
+ attr_reader :key, :cert
7
+
8
+ CERT_STRING = "/C=%s/O=%s/OU=%s/CN=%s"
9
+
10
+ def initialize(username, opts)
11
+ @cert_dir = File.join(opts[:cert_dir], "#{username.downcase}_cert")
12
+ @username = username
13
+ @opts = opts
14
+
15
+ FileUtils.mkdir_p @cert_dir
16
+ setup_key
17
+ setup_cert
18
+ end
19
+
20
+ [:private_key, :public_key, :cert].each do |sym|
21
+ define_method "#{sym}_path" do
22
+ File.join(@cert_dir, "#{sym}.pem")
23
+ end
24
+ end
25
+
26
+ private
27
+ def setup_key
28
+ if File.exists?(private_key_path)
29
+ @key ||= OpenSSL::PKey::RSA.new File.read(private_key_path)
30
+ else
31
+ @key ||= OpenSSL::PKey::RSA.new 2048
32
+ File.write private_key_path, key.to_pem
33
+ File.write public_key_path, key.public_key.to_pem
34
+ end
35
+ end
36
+
37
+ def setup_cert
38
+ if File.exists?(cert_path)
39
+ @cert ||= OpenSSL::X509::Certificate.new File.read(cert_path)
40
+ else
41
+ @cert ||= OpenSSL::X509::Certificate.new
42
+
43
+ subject = CERT_STRING % [@opts[:country_code], @opts[:organization], @opts[:organization_unit], @username]
44
+
45
+ cert.subject = cert.issuer = OpenSSL::X509::Name.parse(subject)
46
+ cert.not_before = Time.now
47
+ cert.not_after = Time.new + 365 * 24 * 60 * 60 * 5
48
+ cert.public_key = key.public_key
49
+ cert.serial = rand(65535) + 1
50
+ cert.version = 2
51
+
52
+ ef = OpenSSL::X509::ExtensionFactory.new
53
+ ef.subject_certificate = cert
54
+ ef.issuer_certificate = cert
55
+
56
+ cert.add_extension(ef.create_extension("basicConstraints", "CA:TRUE", true))
57
+ cert.add_extension(ef.create_extension("keyUsage", "keyCertSign, cRLSign", true))
58
+ cert.add_extension(ef.create_extension("subjectKeyIdentifier", "hash", false))
59
+ cert.add_extension(ef.create_extension("authorityKeyIdentifier", "keyid:always", false))
60
+
61
+ cert.sign key, OpenSSL::Digest::SHA256.new
62
+
63
+ File.write cert_path, cert.to_pem
64
+ end
65
+ end
66
+ end
67
+ end