rairtame 1.0.0

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.
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