ffi-hdhomerun 0.3.100b91730e74 → 0.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.
- data/README.md +87 -0
- data/Rakefile +4 -0
- data/ffi-hdhomerun.gemspec +1 -2
- data/lib/ffi-hdhomerun.rb +169 -47
- data/lib/ffi/hdhomerun.rb +46 -0
- data/spec/hdhomerun_spec.rb +104 -0
- metadata +17 -10
- data/README.txt +0 -62
data/README.md
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
ffi-hdhomerun
|
2
|
+
=============
|
3
|
+
http://bitbucket.org/dmlary/ffi-hdhomerun
|
4
|
+
|
5
|
+
Description
|
6
|
+
-----------
|
7
|
+
Ruby-FFI bindings and wrappers for libhdhomerun.
|
8
|
+
|
9
|
+
Features
|
10
|
+
--------
|
11
|
+
Allows the user to capture video from an HDHomeRun device within Ruby.
|
12
|
+
|
13
|
+
What's missing
|
14
|
+
--------------
|
15
|
+
* Channel scan
|
16
|
+
* Documentation
|
17
|
+
|
18
|
+
Usage
|
19
|
+
-----
|
20
|
+
require 'ffi-hdhomerun'
|
21
|
+
|
22
|
+
# Dump the list of tuners on the network
|
23
|
+
HDHomeRun.discover.each do |hash|
|
24
|
+
puts "hdhomerun device %s found at %s" % [ hash[:id], hash[:ip_addr] ]
|
25
|
+
end
|
26
|
+
|
27
|
+
# Allocate a tuner
|
28
|
+
tuner = HDHomeRun::Tuner.new(:id => 'FFFFFFFF', :tuner => 0)
|
29
|
+
|
30
|
+
# Set the channel and optionally the program
|
31
|
+
tuner.channel = 20
|
32
|
+
tuner.program = 3
|
33
|
+
|
34
|
+
# Capture some data
|
35
|
+
File.open("capture.ts", "w") do |file|
|
36
|
+
puts "Capturing (^C to stop):"
|
37
|
+
tuner.capture do |buf|
|
38
|
+
file.write(buf)
|
39
|
+
STDOUT.write(buf.size > 0 ? "." : "?")
|
40
|
+
STDOUT.flush
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
Requirements
|
45
|
+
------------
|
46
|
+
* libhdhomerun >= 1.0
|
47
|
+
* ffi >= 1.0
|
48
|
+
|
49
|
+
FFI_HDHOMERUN_OLD_DEVICE_STRUCT
|
50
|
+
-------------------------------
|
51
|
+
libhdhomerun made a change to the device structure used for discovery
|
52
|
+
sometime between version 20100121 and 20110323. They added a new field
|
53
|
+
caller tuner_count, but the filename of the hdhomerun library was not
|
54
|
+
incremented in ubuntu. As a result, on older releases such as Lucid, users
|
55
|
+
of this gem will have to set FFI_HDHOMERUN_OLD_DEVICE_STRUCT in their
|
56
|
+
environment to get proper results from the HDHomeRun.discover() method.
|
57
|
+
This environment variable only needs to be set if you're planning on
|
58
|
+
using the discovery method.
|
59
|
+
|
60
|
+
Copyright
|
61
|
+
---------
|
62
|
+
Copyright (c) 2011,2012 David M. Lary
|
63
|
+
All rights reserved.
|
64
|
+
|
65
|
+
License
|
66
|
+
-------
|
67
|
+
Redistribution and use in source and binary forms, with or without
|
68
|
+
modification, are permitted provided that the following conditions are met:
|
69
|
+
|
70
|
+
* Redistributions of source code must retain the above copyright
|
71
|
+
notice, this list of conditions and the following disclaimer.
|
72
|
+
* Redistributions in binary form must reproduce the above copyright
|
73
|
+
notice, this list of conditions and the following disclaimer in the
|
74
|
+
documentation and/or other materials provided with the distribution.
|
75
|
+
* The name of the author may not be used to endorse or promote products
|
76
|
+
derived from this software without specific prior written permission.
|
77
|
+
|
78
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
79
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
80
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
81
|
+
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
|
82
|
+
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
83
|
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
84
|
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
85
|
+
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
86
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
87
|
+
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/Rakefile
CHANGED
data/ffi-hdhomerun.gemspec
CHANGED
@@ -4,8 +4,7 @@ require "ffi-hdhomerun"
|
|
4
4
|
|
5
5
|
Gem::Specification.new do |s|
|
6
6
|
s.name = "ffi-hdhomerun"
|
7
|
-
s.version = "%s
|
8
|
-
`hg id -i`.chomp.sub("+", "m") ]
|
7
|
+
s.version = "%s" % [ HDHomeRun::VERSION ]
|
9
8
|
s.authors = ["David M. Lary"]
|
10
9
|
s.email = ["dmlary@gmail.com"]
|
11
10
|
s.homepage = "http://bitbucket.org/dmlary/ffi-hdhomerun"
|
data/lib/ffi-hdhomerun.rb
CHANGED
@@ -1,10 +1,166 @@
|
|
1
1
|
require 'ffi/hdhomerun'
|
2
|
+
require 'pry'
|
2
3
|
|
3
4
|
#
|
4
5
|
# Documentation can be found under HDHomeRun::Tuner
|
5
6
|
#
|
6
7
|
module HDHomeRun
|
7
|
-
VERSION = "0.
|
8
|
+
VERSION = "0.4"
|
9
|
+
ERROR_STRING_UNKNOWN_VARIABLE = "ERROR: unknown getset variable"
|
10
|
+
MAX_TUNERS = 8
|
11
|
+
|
12
|
+
class ConnectionError < Exception; end
|
13
|
+
class UnknownVariableError < Exception; end
|
14
|
+
|
15
|
+
#
|
16
|
+
# Discover tuners on the network.
|
17
|
+
#
|
18
|
+
# Warning: This function will return invalid data
|
19
|
+
#
|
20
|
+
# Returns an array of hashes with the following fields:
|
21
|
+
# :id -- device id (string)
|
22
|
+
# :type -- device type (int)
|
23
|
+
# :ip_addr -- ip address (IPAddr)
|
24
|
+
# :tuner_count -- number of tuners on the device (int)
|
25
|
+
#
|
26
|
+
def self.discover
|
27
|
+
results_ptr = FFI::MemoryPointer.new(FFI::HDHomeRun::Device, 64)
|
28
|
+
count = FFI::HDHomeRun::discover_find_devices_custom(
|
29
|
+
FFI::HDHomeRun::DEVICE_ID_WILDCARD,
|
30
|
+
FFI::HDHomeRun::DEVICE_TYPE_TUNER,
|
31
|
+
FFI::HDHomeRun::DEVICE_ID_WILDCARD,
|
32
|
+
results_ptr,
|
33
|
+
64)
|
34
|
+
raise "error sending discover request" if count < 0
|
35
|
+
|
36
|
+
# Loop through the results and construct an array of hashes
|
37
|
+
# as our output.
|
38
|
+
(0..count-1).map do |i|
|
39
|
+
|
40
|
+
# construct our hash
|
41
|
+
p = FFI::HDHomeRun::Device.new(results_ptr[i])
|
42
|
+
out = { :id => "%08X" % p.device_id,
|
43
|
+
:type => p.device_type,
|
44
|
+
:ip_addr => p.ip_addr }
|
45
|
+
|
46
|
+
# Some versions of the library don't support tuner_count
|
47
|
+
out.merge!({ :tuner_count => p.tuner_count }) if
|
48
|
+
p.respond_to? :tuner_count
|
49
|
+
|
50
|
+
out
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# HDHomeRun device wrapper.
|
55
|
+
class Device
|
56
|
+
include FFI::HDHomeRun
|
57
|
+
|
58
|
+
attr_reader :id
|
59
|
+
|
60
|
+
# Initialize new hdhomerun device
|
61
|
+
#
|
62
|
+
# Arguments:
|
63
|
+
# [:id] HDHomeRun id (default "FFFFFFFF")
|
64
|
+
#
|
65
|
+
# Create a device instance for any HDHomeRun device on the network:
|
66
|
+
# device = HDHomeRun::Device.new
|
67
|
+
#
|
68
|
+
# Create a device instance for a specific HDHomeRun device on the network:
|
69
|
+
# device = HDHomeRun::Device.new "FEEDFACE"
|
70
|
+
#
|
71
|
+
def initialize(p={})
|
72
|
+
p = {:id => p} unless p.is_a? Hash
|
73
|
+
p[:id] ||= "FFFFFFFF"
|
74
|
+
p[:id] = "%08X" % p[:id] if p[:id].is_a? Fixnum
|
75
|
+
|
76
|
+
@hd = create_from_str(p[:id].to_s, nil)
|
77
|
+
raise ArgumentError, "Invalid device id: %s" % p[:id] if @hd.null?
|
78
|
+
@id = get_device_id_requested(@hd)
|
79
|
+
raise ArgumentError, "Invalid device id: %08X" % id \
|
80
|
+
unless validate_device_id(@id)
|
81
|
+
raise ConnectionError, "Unable to connect to device" \
|
82
|
+
unless get_model_str(@hd)
|
83
|
+
end
|
84
|
+
|
85
|
+
#
|
86
|
+
# Get a device variable; equivalent to:
|
87
|
+
# hdhomerun_config <id> get <key>
|
88
|
+
#
|
89
|
+
def get(key)
|
90
|
+
value = FFI::MemoryPointer.new(:pointer, 1)
|
91
|
+
error = FFI::MemoryPointer.new(:pointer, 1)
|
92
|
+
|
93
|
+
if get_var(@hd, key, value, error) < 0
|
94
|
+
raise ConnectionError,
|
95
|
+
"communication error sending request to hdhomerun device"
|
96
|
+
end
|
97
|
+
value = value.read_pointer
|
98
|
+
error = error.read_pointer
|
99
|
+
|
100
|
+
# Return the value if we didn't get an error.
|
101
|
+
return value.read_string if error.null?
|
102
|
+
|
103
|
+
# Throw a useful exception if this get was for an unknown variable.
|
104
|
+
error = error.read_string
|
105
|
+
raise UnknownVariableError, "unknown variable '%s'" % key if
|
106
|
+
error == ERROR_STRING_UNKNOWN_VARIABLE
|
107
|
+
|
108
|
+
# otherwise, it's a generic runtime error.
|
109
|
+
raise RuntimeError, error
|
110
|
+
end
|
111
|
+
|
112
|
+
#
|
113
|
+
# Set a device variable; equivalent to:
|
114
|
+
# hdhomerun_config <id> set <key> <value>
|
115
|
+
#
|
116
|
+
def set(key, value)
|
117
|
+
error = FFI::MemoryPointer.new(:pointer, 1)
|
118
|
+
|
119
|
+
if set_var(@hd, key, value.to_s, nil, error) < 0
|
120
|
+
raise ConnectionError,
|
121
|
+
"communication error sending request to hdhomerun device"
|
122
|
+
end
|
123
|
+
error = error.read_pointer
|
124
|
+
|
125
|
+
return true if error.null?
|
126
|
+
|
127
|
+
# Throw a useful exception if this get was for an unknown variable.
|
128
|
+
error = error.read_string
|
129
|
+
raise UnknownVariableError, "unknown variable '%s'" % key if
|
130
|
+
error == ERROR_STRING_UNKNOWN_VARIABLE
|
131
|
+
|
132
|
+
# otherwise, it's a generic runtime error.
|
133
|
+
raise RuntimeError, error
|
134
|
+
end
|
135
|
+
|
136
|
+
# Get the number of tuners on this device.
|
137
|
+
def tuner_count
|
138
|
+
@tuner_count ||= begin
|
139
|
+
MAX_TUNERS.times do |tuner|
|
140
|
+
begin
|
141
|
+
get "/tuner%d/debug" % tuner
|
142
|
+
rescue UnknownVariableError
|
143
|
+
break tuner
|
144
|
+
end
|
145
|
+
end or raise RuntimeError, "Unable to detect tuner count"
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
# Get an array of tuners for this device.
|
150
|
+
def tuners
|
151
|
+
@tuners ||= tuner_count.times.map do |tuner|
|
152
|
+
Tuner.new(:id => @id, :tuner => tuner)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def id
|
157
|
+
"%08X" % @id
|
158
|
+
end
|
159
|
+
|
160
|
+
def to_s
|
161
|
+
"<%s:0x%08x @id=%08X>" % [ self.class, object_id, @id ]
|
162
|
+
end
|
163
|
+
end
|
8
164
|
|
9
165
|
# HDHomeRun tuner wrapper.
|
10
166
|
#
|
@@ -33,10 +189,11 @@ module HDHomeRun
|
|
33
189
|
# end
|
34
190
|
# end
|
35
191
|
#
|
36
|
-
class Tuner
|
192
|
+
class Tuner < Device
|
193
|
+
extend Forwardable
|
37
194
|
include FFI::HDHomeRun
|
38
195
|
|
39
|
-
attr_reader :
|
196
|
+
attr_reader :tuner
|
40
197
|
|
41
198
|
# Initialize new hdhomerun tuner
|
42
199
|
#
|
@@ -46,32 +203,20 @@ module HDHomeRun
|
|
46
203
|
#
|
47
204
|
# Create a tuner instance for the first tuner on any HDHomeRun box
|
48
205
|
# on the network:
|
49
|
-
# tuner => HDHomeRun::Tuner.new
|
206
|
+
# tuner => HDHomeRun::Tuner.new
|
50
207
|
#
|
51
208
|
# Create a tuner instance for the second tuner on the HDHomeRun box
|
52
209
|
# with id FE92EBD0
|
53
210
|
# tuner = HDHomeRun::Tuner.new(:id => "FE92EBD0", :tuner => 1)
|
54
211
|
#
|
55
212
|
def initialize(p={})
|
56
|
-
p
|
57
|
-
p[:
|
58
|
-
|
59
|
-
p[:tuner] = "/tuner%d" % p[:tuner] if p[:tuner].is_a? Fixnum
|
60
|
-
|
61
|
-
@hd = create_from_str(p[:id].to_s, nil)
|
62
|
-
raise ArgumentError, "Invalid device id: #{id}" if @hd.null?
|
63
|
-
@id = get_device_id_requested(@hd)
|
213
|
+
super(p)
|
214
|
+
@tuner = p[:tuner] || 0
|
215
|
+
@tuner = "/tuner%d" % @tuner if @tuner.is_a? Fixnum
|
64
216
|
|
65
|
-
@tuner = p[:tuner]
|
66
|
-
@tuner = "/tuner%d" % @tuner unless @tuner.is_a? String
|
67
217
|
if set_tuner_from_str(@hd, @tuner) <= 0
|
68
218
|
raise RuntimeError, "invalid tuner: %s" % @tuner
|
69
219
|
end
|
70
|
-
|
71
|
-
raise ArgumentError, "Invalid device id: %08X" % id \
|
72
|
-
unless validate_device_id(@id)
|
73
|
-
raise RuntimeError, "Unable to connect to device" \
|
74
|
-
unless get_model_str(@hd)
|
75
220
|
end
|
76
221
|
|
77
222
|
# Dynamically define the following getter and setter methods
|
@@ -86,7 +231,7 @@ module HDHomeRun
|
|
86
231
|
end
|
87
232
|
|
88
233
|
# Dynamically define these as simple getter methods
|
89
|
-
%w{status streaminfo}.each do |name|
|
234
|
+
%w{status streaminfo debug}.each do |name|
|
90
235
|
define_method name do
|
91
236
|
get_tuner(name)
|
92
237
|
end
|
@@ -147,35 +292,12 @@ module HDHomeRun
|
|
147
292
|
end
|
148
293
|
end
|
149
294
|
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
value = FFI::MemoryPointer.new(:pointer, 1)
|
154
|
-
error = FFI::MemoryPointer.new(:pointer, 1)
|
155
|
-
|
156
|
-
if get_var(@hd, key, value, error) < 0
|
157
|
-
raise RuntimeError,
|
158
|
-
"communication error sending request to hdhomerun device"
|
159
|
-
end
|
160
|
-
value = value.read_pointer
|
161
|
-
error = error.read_pointer
|
162
|
-
|
163
|
-
raise RuntimeError, error.read_string unless error.null?
|
164
|
-
value.read_string
|
295
|
+
def to_s
|
296
|
+
"<%s:0x%08x @id=%08X, @tuner=%s>" %
|
297
|
+
[ self.class, object_id, @id, @tuner ]
|
165
298
|
end
|
166
299
|
|
167
|
-
|
168
|
-
error = FFI::MemoryPointer.new(:pointer, 1)
|
169
|
-
|
170
|
-
if set_var(@hd, key, value.to_s, nil, error) < 0
|
171
|
-
raise RuntimeError,
|
172
|
-
"communication error sending request to hdhomerun device"
|
173
|
-
end
|
174
|
-
error = error.read_pointer
|
175
|
-
|
176
|
-
raise RuntimeError, error.read_string unless error.null?
|
177
|
-
true
|
178
|
-
end
|
300
|
+
private
|
179
301
|
|
180
302
|
def get_tuner(key)
|
181
303
|
get("%s/%s" % [@tuner, key.to_s])
|
data/lib/ffi/hdhomerun.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'ffi'
|
2
|
+
require 'ipaddr'
|
2
3
|
|
3
4
|
module FFI # :nodoc:
|
4
5
|
module HDHomeRun
|
@@ -6,6 +7,8 @@ module FFI # :nodoc:
|
|
6
7
|
ffi_lib "libhdhomerun.so.1"
|
7
8
|
|
8
9
|
VIDEO_DATA_BUFFER_SIZE_1S = (20000000 / 8)
|
10
|
+
DEVICE_TYPE_TUNER = 0x00000001
|
11
|
+
DEVICE_ID_WILDCARD = 0xFFFFFFFF
|
9
12
|
|
10
13
|
attach_function :create_from_str,
|
11
14
|
:hdhomerun_device_create_from_str,
|
@@ -40,6 +43,10 @@ module FFI # :nodoc:
|
|
40
43
|
attach_function :get_video_stats,
|
41
44
|
:hdhomerun_device_get_video_stats,
|
42
45
|
[:pointer, :pointer], :void
|
46
|
+
attach_function :discover_find_devices_custom,
|
47
|
+
:hdhomerun_discover_find_devices_custom,
|
48
|
+
[ :uint32, :uint32, :uint32, :pointer, :int ],
|
49
|
+
:int
|
43
50
|
|
44
51
|
# HDHomeRun capture statistics for a given tuner
|
45
52
|
#
|
@@ -63,5 +70,44 @@ module FFI # :nodoc:
|
|
63
70
|
end
|
64
71
|
end
|
65
72
|
end
|
73
|
+
|
74
|
+
# Discover device structure
|
75
|
+
#
|
76
|
+
# Silicon dust changed this structure at some point between
|
77
|
+
# revision 20100121 and 20110323, but the version number in the
|
78
|
+
# library filename was not changed by package maintainers for
|
79
|
+
# ubuntu. As a result, there's no simple way to determine which
|
80
|
+
# structure we should use. To work around this, the gem will
|
81
|
+
# assume the library has the newer structure unless the
|
82
|
+
# FFI_HDHOMERUN_OLD_DEVICE_STRUCT environment variable has been
|
83
|
+
# set.
|
84
|
+
#
|
85
|
+
class Device < FFI::Struct
|
86
|
+
if ENV['FFI_HDHOMERUN_OLD_DEVICE_STRUCT']
|
87
|
+
layout :ip_addr, :uint32,
|
88
|
+
:device_type, :uint32,
|
89
|
+
:device_id, :uint32
|
90
|
+
else
|
91
|
+
# Newer layout has support for :tuner_count
|
92
|
+
layout :ip_addr, :uint32,
|
93
|
+
:device_type, :uint32,
|
94
|
+
:device_id, :uint32,
|
95
|
+
:tuner_count, :uint8
|
96
|
+
|
97
|
+
define_method :tuner_count do
|
98
|
+
send(:[], :tuner_count)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
%w{device_type device_id}.each do |name|
|
103
|
+
define_method name do
|
104
|
+
send(:[], name.to_sym)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def ip_addr
|
109
|
+
IPAddr.new(self[:ip_addr], Socket::AF_INET)
|
110
|
+
end
|
111
|
+
end
|
66
112
|
end
|
67
113
|
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
require 'ffi-hdhomerun'
|
2
|
+
require 'timeout'
|
3
|
+
|
4
|
+
describe HDHomeRun do
|
5
|
+
it "can find hdhomerun devices on the network" do
|
6
|
+
devices = HDHomeRun.discover
|
7
|
+
devices.empty?.should eq(false)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
describe HDHomeRun::Device do
|
12
|
+
before :each do
|
13
|
+
@device = HDHomeRun::Device.new
|
14
|
+
end
|
15
|
+
|
16
|
+
it "can connect to any device" do
|
17
|
+
HDHomeRun::Device.new
|
18
|
+
end
|
19
|
+
|
20
|
+
it "throws exception on invalid name" do
|
21
|
+
lambda { HDHomeRun::Device.new("WOOF WOOF, I'm a puppy!") } \
|
22
|
+
.should raise_error(ArgumentError)
|
23
|
+
end
|
24
|
+
|
25
|
+
it "throws exception on unreachable device" do
|
26
|
+
lambda { HDHomeRun::Device.new("FEEDFACE") } \
|
27
|
+
.should raise_error(HDHomeRun::ConnectionError)
|
28
|
+
end
|
29
|
+
|
30
|
+
it "can get device variables" do
|
31
|
+
str = @device.get("/sys/debug")
|
32
|
+
str.nil?.should eq(false)
|
33
|
+
end
|
34
|
+
|
35
|
+
it "can determine the correct number of tuners" do
|
36
|
+
count1 = @device.tuner_count
|
37
|
+
count2 = @device.get("/sys/debug").split(/\n/) \
|
38
|
+
.count { |l| l =~ /^t\d+: / }
|
39
|
+
count1.should eq(count2)
|
40
|
+
end
|
41
|
+
|
42
|
+
it "can return a list of tuners" do
|
43
|
+
tuners = @device.tuners
|
44
|
+
tuners.empty?.should eq(false)
|
45
|
+
tuners.each do |t|
|
46
|
+
t.channel.should eq(@device.get("%s/channel" % t.tuner))
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe HDHomeRun::Tuner do
|
52
|
+
before :all do
|
53
|
+
@tuner = HDHomeRun.discover.map do |device|
|
54
|
+
HDHomeRun::Device.new(device[:id]).tuners
|
55
|
+
end.flatten.find do |tuner|
|
56
|
+
tuner.target == "none"
|
57
|
+
end
|
58
|
+
@tuner.nil?.should eq(false)
|
59
|
+
|
60
|
+
@device = HDHomeRun::Device.new(@tuner.id)
|
61
|
+
|
62
|
+
# Pull the test channel from the environment
|
63
|
+
@channel = ENV['FFI_HDHOMERUN_TEST_CHANNEL']
|
64
|
+
|
65
|
+
# Failing that, do a channel scan to find a channel with strong signal.
|
66
|
+
@channel ||= (2..69).to_a.find do |channel|
|
67
|
+
@tuner.channel = channel
|
68
|
+
sleep 0.75
|
69
|
+
@tuner.status !~ / lock=none /
|
70
|
+
end
|
71
|
+
|
72
|
+
fail "Unable to find good channel for tests. " +
|
73
|
+
"Please set the environment variable FFI_HDHOMERUN_TEST_CHANNEL " +
|
74
|
+
"to a channel that comes in clearly on your tuner" unless @channel
|
75
|
+
end
|
76
|
+
|
77
|
+
it "can set and get the channel" do
|
78
|
+
@tuner.channel = "none"
|
79
|
+
@tuner.channel.should eq("none")
|
80
|
+
@device.get("%s/channel" % @tuner.tuner).should eq("none")
|
81
|
+
|
82
|
+
channel = "auto:%d" % @channel
|
83
|
+
@tuner.channel = channel
|
84
|
+
@tuner.channel.should eq(channel)
|
85
|
+
@device.get("%s/channel" % @tuner.tuner).should eq(channel)
|
86
|
+
end
|
87
|
+
|
88
|
+
it "can capture from the tuner" do
|
89
|
+
@tuner.channel = "auto:%d" % @channel
|
90
|
+
|
91
|
+
bytes = 0
|
92
|
+
calls = 25
|
93
|
+
Timeout::timeout(2) do
|
94
|
+
@tuner.capture do |buf|
|
95
|
+
bytes += buf.size
|
96
|
+
calls -= 1
|
97
|
+
break if calls == 0
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
bytes.size.should be > 0
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
metadata
CHANGED
@@ -1,19 +1,19 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ffi-hdhomerun
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
5
|
-
prerelease:
|
4
|
+
version: '0.4'
|
5
|
+
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- David M. Lary
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2012-07-07 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: ffi
|
16
|
-
requirement:
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,7 +21,12 @@ dependencies:
|
|
21
21
|
version: '0'
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements:
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
25
30
|
description: Ruby FFI bindings for libhdhomerun
|
26
31
|
email:
|
27
32
|
- dmlary@gmail.com
|
@@ -31,11 +36,12 @@ extra_rdoc_files: []
|
|
31
36
|
files:
|
32
37
|
- .hgignore
|
33
38
|
- Gemfile
|
34
|
-
- README.
|
39
|
+
- README.md
|
35
40
|
- Rakefile
|
36
41
|
- ffi-hdhomerun.gemspec
|
37
42
|
- lib/ffi-hdhomerun.rb
|
38
43
|
- lib/ffi/hdhomerun.rb
|
44
|
+
- spec/hdhomerun_spec.rb
|
39
45
|
homepage: http://bitbucket.org/dmlary/ffi-hdhomerun
|
40
46
|
licenses: []
|
41
47
|
post_install_message:
|
@@ -51,13 +57,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
51
57
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
52
58
|
none: false
|
53
59
|
requirements:
|
54
|
-
- - ! '
|
60
|
+
- - ! '>='
|
55
61
|
- !ruby/object:Gem::Version
|
56
|
-
version:
|
62
|
+
version: '0'
|
57
63
|
requirements: []
|
58
64
|
rubyforge_project: ffi-hdhomerun
|
59
|
-
rubygems_version: 1.8.
|
65
|
+
rubygems_version: 1.8.24
|
60
66
|
signing_key:
|
61
67
|
specification_version: 3
|
62
68
|
summary: Ruby FFI bindings for libhdhomerun
|
63
|
-
test_files:
|
69
|
+
test_files:
|
70
|
+
- spec/hdhomerun_spec.rb
|
data/README.txt
DELETED
@@ -1,62 +0,0 @@
|
|
1
|
-
= ffi-hdhomerun
|
2
|
-
http://bitbucket.org/dmlary/ffi-hdhomerun
|
3
|
-
|
4
|
-
== Description
|
5
|
-
Ruby-FFI bindings and wrappers for libhdhomerun.
|
6
|
-
|
7
|
-
== Features
|
8
|
-
Allows the user to capture video from an HDHomeRun device within Ruby.
|
9
|
-
|
10
|
-
== What's missing
|
11
|
-
* Discovery
|
12
|
-
* Documentation
|
13
|
-
|
14
|
-
== Usage
|
15
|
-
require 'ffi-hdhomerun'
|
16
|
-
|
17
|
-
# Allocate a tuner
|
18
|
-
tuner = HDHomeRun::Tuner.new(:id => 'FFFFFFFF', :tuner => 0)
|
19
|
-
|
20
|
-
# Set the channel and optionally the program
|
21
|
-
tuner.channel = 20
|
22
|
-
tuner.program = 3
|
23
|
-
|
24
|
-
# Capture some data
|
25
|
-
File.open("capture.ts", "w") do |file|
|
26
|
-
puts "Capturing (^C to stop):"
|
27
|
-
tuner.capture do |buf|
|
28
|
-
file.write(buf)
|
29
|
-
STDOUT.write(buf.size > 0 ? "." : "?")
|
30
|
-
STDOUT.flush
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
== Requirements
|
35
|
-
- libhdhomerun >= 1.0
|
36
|
-
- ffi >= 1.0
|
37
|
-
|
38
|
-
== Copyright
|
39
|
-
Copyright (c) 2011, David M. Lary
|
40
|
-
All rights reserved.
|
41
|
-
|
42
|
-
== License
|
43
|
-
Redistribution and use in source and binary forms, with or without
|
44
|
-
modification, are permitted provided that the following conditions are met:
|
45
|
-
* Redistributions of source code must retain the above copyright
|
46
|
-
notice, this list of conditions and the following disclaimer.
|
47
|
-
* Redistributions in binary form must reproduce the above copyright
|
48
|
-
notice, this list of conditions and the following disclaimer in the
|
49
|
-
documentation and/or other materials provided with the distribution.
|
50
|
-
* The name of the author may not be used to endorse or promote products
|
51
|
-
derived from this software without specific prior written permission.
|
52
|
-
|
53
|
-
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
54
|
-
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
55
|
-
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
56
|
-
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
|
57
|
-
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
58
|
-
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
59
|
-
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
60
|
-
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
61
|
-
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
62
|
-
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|