rairtame 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md ADDED
@@ -0,0 +1,50 @@
1
+ # Rairtame
2
+
3
+ Rairtame is a library and CLI utility to interact with the `airtame-streamer` JSON-RPC API.
4
+
5
+ It allows to easily control the `airtame-streamer` daemon, which is in charge of capturing and streaming video to an [AIRTAME dongle](http://airtame.com).
6
+
7
+ **Make sure the `airtame-streamer` is running when using `rairtame`**.
8
+
9
+ ## Installation
10
+
11
+ Rairtame is distributed as a Ruby gem. It can be installed by running:
12
+
13
+ $ gem install rairtame
14
+
15
+ ## CLI Usage
16
+
17
+ The Rairtame Command-Line interface is accessed through the
18
+
19
+ $ rairtame command [arguments]
20
+
21
+ command.
22
+
23
+ Remember to run `rairtame init` to init the streamer before connecting anywhere.
24
+
25
+ You can see all the options running `rairtame -h`.
26
+
27
+ ## Ruby library usage
28
+
29
+ You can also easily integrate your own code against this library. For example:
30
+
31
+ ```ruby
32
+ require 'rairtame'
33
+ client = Rairtame::Client.new()
34
+ client.init_streamer()
35
+ client.connect("myairtame")
36
+ client.quality = 5
37
+ client.buffer = 5000
38
+ client.disconnect()
39
+ client.close_streamer()
40
+ ```
41
+
42
+ ## Limitations
43
+
44
+ Rairtame does not yet implement the SSDP protocol (which allows autodiscovery of the streamer) nor performs any registration with the `airtame-streamer` (for example to receive notifications).
45
+
46
+ Particularly, I don't see many uses for these features at this point, and the official [Airtame CLI](https://github.com/airtame/airtame-cli) supports them.
47
+
48
+ ## Contributing
49
+
50
+ Please do!
data/Rakefile ADDED
@@ -0,0 +1,18 @@
1
+ # coding: utf-8
2
+ # This file is part of Rairtame.
3
+
4
+ # Rairtame is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+
9
+ # Rairtame is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with Rairtame. If not, see <http://www.gnu.org/licenses/>
16
+
17
+ require "bundler/gem_tasks"
18
+
data/bin/rairtame ADDED
@@ -0,0 +1,177 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # This file is part of Rairtame.
4
+
5
+ # Rairtame is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU General Public License as published by
7
+ # the Free Software Foundation, either version 3 of the License, or
8
+ # (at your option) any later version.
9
+
10
+ # Rairtame is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU General Public License for more details.
14
+
15
+ # You should have received a copy of the GNU General Public License
16
+ # along with Rairtame. If not, see <http://www.gnu.org/licenses/>
17
+
18
+ lib = File.expand_path('../../lib', __FILE__)
19
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
20
+
21
+ require "bundler/setup"
22
+ require 'gli'
23
+ require 'colorize'
24
+ require 'rairtame'
25
+
26
+ module Rairtame
27
+ module RairtameCLI
28
+ SUBCOMMANDS = {
29
+ :init => {
30
+ :short => 'Init the streamer',
31
+ :long => 'Initializes the streamer',
32
+ :arg => nil,
33
+ :method => :init_streamer
34
+ },
35
+ :close => {
36
+ :short => 'Close the streamer',
37
+ :long => 'Closes the streamer',
38
+ :arg => nil,
39
+ :method => :close_streamer
40
+ },
41
+ :connect => {
42
+ :short => 'Connect to Airtame dongle',
43
+ :long => 'Connects and starts streaming to the Airtame dongle',
44
+ :arg => 'hostname/ip',
45
+ :method => :connect
46
+ },
47
+ :disconnect => {
48
+ :short => 'Disconnect from the Airtame dongle',
49
+ :long => 'Disconnects and stops streaming to the Airtame dongle',
50
+ :arg => 'hostname/ip',
51
+ :method => :disconnect
52
+ },
53
+ :mode => {
54
+ :short => 'Set streaming mode',
55
+ :long => 'Sets streaming mode to one of the options',
56
+ :arg => 'manual|video|work|present',
57
+ :allowed => ['manual', 'video', 'work', 'present'],
58
+ :method => :mode=
59
+ },
60
+ :quality => {
61
+ :short => 'Set streaming quality [1-5]',
62
+ :long => 'Sets the streaming quality from 0-worst to 5-best,
63
+ when using manual mode',
64
+ :arg => 'quality',
65
+ :allowed => (0..5).map(&:to_s),
66
+ :method => :quality=
67
+ },
68
+ :fps => {
69
+ :short => 'Set streaming fps [1-60]',
70
+ :long => 'Sets the fps [1-60] for streaming, when using manual mode',
71
+ :arg => 'fps',
72
+ :allowed => (1..60).map(&:to_s),
73
+ :method => :framerate=
74
+ },
75
+ :audio => {
76
+ :short => 'Enable or disable audio',
77
+ :long => 'Enables or disables audio streaming to the Airtame dongle',
78
+ :arg => 'on/off',
79
+ :allowed => ['on', 'off'],
80
+ :method => :audio=
81
+ },
82
+ :video => {
83
+ :short => 'Enable or disable video',
84
+ :long => 'Enables or disables video streaming to the Airtame dongle',
85
+ :arg => 'on/off',
86
+ :allowed => ['on', 'off'],
87
+ :method => :video=
88
+ },
89
+ :buffer => {
90
+ :short => 'Set streaming buffer length in ms',
91
+ :long => 'Sets the length of the streaming buffer in ms',
92
+ :arg => 'ms',
93
+ :method => :buffer=
94
+ },
95
+ :fluent => {
96
+ :short => 'Enable or disable fluent video',
97
+ :long => 'Enables or disables the fluent video streaming feature',
98
+ :arg => 'on/off',
99
+ :allowed => ['on', 'off'],
100
+ :method => :video_jitterbuffer=
101
+ },
102
+ :state => {
103
+ :short => 'Show streamer\'s state',
104
+ :long => 'Prints streamer\'s state in a pretty way',
105
+ :arg => nil,
106
+ :method => :pretty_state
107
+ }
108
+ }
109
+
110
+ class << self
111
+ include GLI::App
112
+
113
+ def setup
114
+ program_desc <<EOF
115
+ A Ruby interface to the airtame-streamer JSON-RPC API, which allows
116
+ to stream to an AIRTAME dongle.
117
+ EOF
118
+
119
+ switch [:v, :verbose]
120
+ switch [:c, :color], :default_value => true
121
+ flag [:streamer_host], :default_value => 'localhost'
122
+ flag [:config_file]
123
+
124
+ pre do |global_options, command, options, args|
125
+ @client = Rairtame::Client.new(global_options)
126
+ String.disable_colorization = !global_options[:color]
127
+ true
128
+ end
129
+
130
+ SUBCOMMANDS.each do |cmd_name, cmd|
131
+ block = Proc.new do |c|
132
+ c.action do |global_options, options, args|
133
+ argument = args.first
134
+ client_method = cmd[:method]
135
+ if cmd[:arg] && argument.nil?
136
+ help_now!("Provide an argument for the command")
137
+ end
138
+ if cmd[:allowed] &&
139
+ !cmd[:allowed].include?(argument)
140
+ help_now!("Invalid argument: #{argument}")
141
+ end
142
+
143
+ begin
144
+
145
+ if argument
146
+ puts "> Setting #{cmd_name}:#{argument}".
147
+ colorize(:light_yellow).bold()
148
+ result = @client.send(client_method, argument)
149
+ else
150
+ puts "> Getting #{client_method}".
151
+ colorize(:light_yellow).bold()
152
+ result = @client.send(client_method)
153
+ end
154
+ puts "< OK".colorize(:light_green).bold()
155
+ rescue ClientException
156
+ puts "< ERROR: #{$!.message}".
157
+ colorize(:light_red).bold
158
+ rescue Jsonrpctcp::RPCException, Jsonrpctcp::RPCError
159
+ puts "< ERROR from streamer: #{$!.message}".
160
+ colorize(:light_red).bold
161
+ end
162
+
163
+ end
164
+ end
165
+
166
+ desc(cmd[:short])
167
+ long_desc(cmd[:long])
168
+ arg_name("<#{cmd[:arg]}>") if cmd[:arg]
169
+ command(cmd_name, &block)
170
+ end
171
+ end
172
+ end
173
+ end
174
+ end
175
+
176
+ Rairtame::RairtameCLI::setup()
177
+ Rairtame::RairtameCLI::run(ARGV)
@@ -0,0 +1,177 @@
1
+ # coding: utf-8
2
+ # This file is part of Rairtame.
3
+
4
+ # Rairtame is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+
9
+ # Rairtame is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with Rairtame. If not, see <http://www.gnu.org/licenses/>
16
+
17
+ require 'ipaddr'
18
+ require 'jsonrpctcp'
19
+ require 'socket'
20
+ require 'pp'
21
+
22
+ require 'rairtame/config'
23
+
24
+ module Rairtame
25
+
26
+ class ClientException < Exception
27
+ attr_reader :code, :message
28
+ def initialize(message, code=nil)
29
+ @code = code
30
+ @message = message
31
+ end
32
+ end
33
+
34
+ class Client
35
+ STREAMER_CMDS_PORT = "8004"
36
+ RECEIVER_PORT = "8002"
37
+
38
+ def initialize(opts={})
39
+ @config = Rairtame::Config.new(opts)
40
+ @verbose = opts[:verbose]
41
+ @streamer_host = opts[:streamer_host] || 'localhost'
42
+ @streamer_uri = "http://#{@streamer_host}:#{STREAMER_CMDS_PORT}/"
43
+ @json_rpc_client = Jsonrpctcp::Client.new(@streamer_host,
44
+ STREAMER_CMDS_PORT)
45
+ end
46
+
47
+ def rpc_call(method, *params)
48
+ begin
49
+ log_command(method, params)
50
+ r = @json_rpc_client[method.to_s, *params]
51
+ log_response(r)
52
+ return r
53
+ rescue Jsonrpctcp::RPCException
54
+ raise $!
55
+ rescue Jsonrpctcp::RPCError
56
+ log_response($!.source_object)
57
+ raise $!
58
+ rescue StandardError
59
+ msg = "Cannot connect to streamer: is it running?: #{$!.message}"
60
+ raise ClientException.new(msg)
61
+ rescue Exception
62
+ msg = "An error occurred while talking to the streamer: #{$!.message}"
63
+ raise ClientException.new(msg)
64
+ end
65
+ end
66
+
67
+ def init_streamer
68
+ rpc_call(:initStreamer)
69
+ end
70
+
71
+ def connect(host)
72
+ ip = resolve(host)
73
+ rpc_call(:connect, ip, RECEIVER_PORT)
74
+ end
75
+
76
+ def disconnect(host)
77
+ ip = resolve(host)
78
+ rpc_call(:disconnect, ip, RECEIVER_PORT)
79
+ end
80
+
81
+ def close_streamer
82
+ rpc_call(:closeStreamer)
83
+ end
84
+
85
+ def state
86
+ rpc_call(:getState)
87
+ end
88
+
89
+ def pretty_state
90
+ rpc_call(:getState)
91
+ end
92
+
93
+ def framerate=(v)
94
+ rpc_call(:setStreamerSettings, 'framerate', v.to_s)
95
+ end
96
+
97
+ def quality=(v)
98
+ rpc_call(:setStreamerSettings, 'quality', v.to_s)
99
+ end
100
+
101
+ def buffer=(v)
102
+ rpc_call(:setStreamerSettings, 'buffer', v.to_s)
103
+ end
104
+
105
+ def mode=(v)
106
+ rpc_call(:setStreamerSettings, 'streaming_mode', v)
107
+ end
108
+
109
+ def audio=(v)
110
+ # TODO: Read av flags first and keep the video flag
111
+ value = case v
112
+ when "on" then "3"
113
+ when "off" then "1"
114
+ end
115
+ rpc_call(:setStreamerSettings, 'av_flags', value)
116
+
117
+ end
118
+
119
+ def video=(v)
120
+ # TODO: Read av flags first and keep the audio
121
+ value = case v
122
+ when "on" then "1"
123
+ when "off" then "0"
124
+ end
125
+ rpc_call(:setStreamerSettings, 'av_flags', value)
126
+ end
127
+
128
+ # fluent video
129
+ def video_jitterbuffer=(v)
130
+ value = case v
131
+ when "on" then "1"
132
+ when "off" then "0"
133
+ end
134
+ rpc_call(:setStreamerSettings, 'video_jb_flags', value)
135
+ end
136
+
137
+ # unused
138
+ def audio_jitterbuffer=(v)
139
+ warn "Not implemented"
140
+ nil
141
+ end
142
+
143
+ # unknown
144
+ def jitterbuffer_delay=(v)
145
+ rpc_call(:setStreamerSettings, 'jb_delay', v.to_s)
146
+ end
147
+
148
+ def reliable_transport=(v)
149
+ warn "Not implemented"
150
+ nil
151
+ end
152
+
153
+ private
154
+
155
+ def log_command(method, params)
156
+ return unless @verbose
157
+ puts "Sending command: [#{method} | #{params}]"
158
+ end
159
+
160
+ def log_response(r)
161
+ # log anyway
162
+ return unless @verbose
163
+ is_error = JsonRpcClient.is_error?(r)
164
+ if is_error then warn "Received error:"
165
+ else puts "Received response:" end
166
+ pp r
167
+ end
168
+
169
+ def resolve(host)
170
+ begin
171
+ IPAddr.new(host).to_s
172
+ rescue IPAddr::InvalidAddressError
173
+ Socket.getaddrinfo(host, nil)[0][3]
174
+ end
175
+ end
176
+ end
177
+ end
@@ -0,0 +1,91 @@
1
+ # coding: utf-8
2
+ # This file is part of Rairtame.
3
+
4
+ # Rairtame is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+
9
+ # Rairtame is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with Rairtame. If not, see <http://www.gnu.org/licenses/>
16
+
17
+ require 'fileutils'
18
+ require 'uuidtools'
19
+ require 'json'
20
+
21
+
22
+ module Rairtame
23
+ class Config
24
+ def initialize(opts={})
25
+ set_paths(opts[:config_file])
26
+ @config = read_config()
27
+ end
28
+
29
+ def [](key)
30
+ @config[key]
31
+ end
32
+
33
+ def []=(key, value)
34
+ @config[key] = value
35
+ end
36
+
37
+ private
38
+
39
+ def set_paths(config_file)
40
+ if config_file.nil?
41
+ home = Dir.respond_to?(:home) ? Dir.home : File.expand_path('~')
42
+ @cfg_folder = File.join(home, '.config', 'rairtame')
43
+ @cfg_file = File.join(@cfg_folder, 'config')
44
+ else
45
+ @cfg_file = config_file
46
+ @cfg_folder = File.dirname(@cfg_file)
47
+ end
48
+ end
49
+
50
+ def read_config
51
+ begin
52
+ @config = JSON.load(File.read(@cfg_file))
53
+ @uuid = @config['uuid']
54
+ # We really need an uuid
55
+ if @uuid.nil?
56
+ create_uuid
57
+ end
58
+ rescue
59
+ initialize_config()
60
+ retry
61
+ end
62
+ end
63
+
64
+ def save_config
65
+ begin
66
+ File.write(@cfg_file, @config.to_json)
67
+ rescue
68
+ warn 'Cannot save configuration!!'
69
+ raise $!
70
+ end
71
+ end
72
+
73
+ def initialize_config
74
+ begin
75
+ puts "Initialize configuration at #{@cfg_file}"
76
+ FileUtils.mkdir_p(@cfg_folder) if !File.exists?(@cfg_folder)
77
+ FileUtils.touch(@cfg_file) if !File.exists?(@cfg_file)
78
+ File.write(@cfg_file, '{}')
79
+ rescue
80
+ warn 'Cannot initialize configuration!!'
81
+ raise $!
82
+ end
83
+ end
84
+
85
+ def create_uuid
86
+ @uuid = UUIDTools::UUID.random_create.to_s
87
+ @config['uuid'] = @uuid
88
+ save_config
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,19 @@
1
+ # coding: utf-8
2
+ # This file is part of Rairtame.
3
+
4
+ # Rairtame is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+
9
+ # Rairtame is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with Rairtame. If not, see <http://www.gnu.org/licenses/>
16
+
17
+ module Rairtame
18
+ VERSION = "1.0.0"
19
+ end
data/lib/rairtame.rb ADDED
@@ -0,0 +1,22 @@
1
+ # coding: utf-8
2
+ # This file is part of Rairtame.
3
+
4
+ # Rairtame is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+
9
+ # Rairtame is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with Rairtame. If not, see <http://www.gnu.org/licenses/>
16
+
17
+ require "rairtame/version"
18
+ require "rairtame/client"
19
+
20
+ module Rairtame
21
+ # Your code goes here...
22
+ end
data/rairtame.gemspec ADDED
@@ -0,0 +1,48 @@
1
+ # coding: utf-8
2
+ # Copyright (C) 2015 Hector Sanjuan
3
+
4
+ # This file is part of Rairtame.
5
+
6
+ # Rairtame is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU General Public License as published by
8
+ # the Free Software Foundation, either version 3 of the License, or
9
+ # (at your option) any later version.
10
+
11
+ # Rairtame is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU General Public License for more details.
15
+
16
+ # You should have received a copy of the GNU General Public License
17
+ # along with Rairtame. If not, see <http://www.gnu.org/licenses/>
18
+
19
+ lib = File.expand_path('../lib', __FILE__)
20
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
21
+ require 'rairtame/version'
22
+
23
+ Gem::Specification.new do |spec|
24
+ spec.name = "rairtame"
25
+ spec.version = Rairtame::VERSION
26
+ spec.authors = ["Hector Sanjuan"]
27
+ spec.email = ["hector@convivencial.org"]
28
+ spec.summary = "CLI and Ruby wrapper around the 'airtame-streamer' JSON-RPC API."
29
+ spec.description = <<EOF
30
+ Rairtame is a Command-Line Interface and Ruby wrapper around the 'airtame-streamer' JSON-RPC API.
31
+
32
+ It allows to easily control the `airtame-streamer` daemon, which is in charge of capturing and streaming video to an AIRTAME dongle.
33
+ EOF
34
+ spec.homepage = "https://github.com/hsanjuan/rairtame"
35
+ spec.license = "GPLv3+"
36
+
37
+ spec.files = `git ls-files -z`.split("\x0")
38
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
39
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
40
+ spec.require_paths = ["lib"]
41
+
42
+ spec.add_development_dependency "bundler", "~> 1.7"
43
+ spec.add_development_dependency "rake", "~> 10.0"
44
+ spec.add_dependency "gli", "~> 2.13"
45
+ spec.add_dependency "uuidtools", "~> 2.1"
46
+ spec.add_dependency "colorize", "~> 0.7"
47
+ spec.add_dependency "jsonrpctcp", '~> 1.0', '>= 1.0.1'
48
+ end