mumble-ruby2 1.1.4

Sign up to get free protection for your applications and to get access to all the features.
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