sensible-cinema 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- data/FAQ +12 -0
- data/LICENSE +5 -0
- data/README +7 -0
- data/Rakefile +30 -0
- data/TODO +106 -0
- data/VERSION +1 -0
- data/bin/bad_digit10.bmp +0 -0
- data/bin/bad_digit11.bmp +0 -0
- data/bin/bad_digit12.bmp +0 -0
- data/bin/bad_digit13.bmp +0 -0
- data/bin/bad_digit14.bmp +0 -0
- data/bin/bad_digit15.bmp +0 -0
- data/bin/bad_digit16.bmp +0 -0
- data/bin/bad_digit17.bmp +0 -0
- data/bin/bad_digit18.bmp +0 -0
- data/bin/bad_digit19.bmp +0 -0
- data/bin/bad_digit2.bmp +0 -0
- data/bin/bad_digit20.bmp +0 -0
- data/bin/bad_digit21.bmp +0 -0
- data/bin/bad_digit22.bmp +0 -0
- data/bin/bad_digit3.bmp +0 -0
- data/bin/bad_digit4.bmp +0 -0
- data/bin/bad_digit5.bmp +0 -0
- data/bin/bad_digit6.bmp +0 -0
- data/bin/bad_digit7.bmp +0 -0
- data/bin/bad_digit8.bmp +0 -0
- data/bin/bad_digit9.bmp +0 -0
- data/bin/hours.bmp +0 -0
- data/bin/minute_ones.bmp +0 -0
- data/bin/minute_tens.bmp +0 -0
- data/bin/scene-skipper +58 -0
- data/bin/second_ones.bmp +0 -0
- data/bin/second_tens.bmp +0 -0
- data/ext/mkrf_conf.rb +5 -0
- data/lib/blanker.rb +54 -0
- data/lib/keyboard_input.rb +45 -0
- data/lib/mouse.rb +60 -0
- data/lib/muter.rb +48 -0
- data/lib/ocr.rb +57 -0
- data/lib/overlayer.rb +341 -0
- data/lib/screen_tracker.rb +160 -0
- data/spec/common.rb +43 -0
- data/spec/convert_image.rb +8 -0
- data/spec/images/4.bmp +0 -0
- data/spec/images/black.bmp +0 -0
- data/spec/images/colon.bmp +0 -0
- data/spec/images/hulu_0.bmp +0 -0
- data/spec/images/hulu_2.bmp +0 -0
- data/spec/images/hulu_2_4.bmp +0 -0
- data/spec/images/hulu_3.bmp +0 -0
- data/spec/images/hulu_3_4.bmp +0 -0
- data/spec/images/hulu_4.bmp +0 -0
- data/spec/images/hulu_4_4.bmp +0 -0
- data/spec/images/hulu_5.bmp +0 -0
- data/spec/images/hulu_7.bmp +0 -0
- data/spec/images/hulu_slash.bmp +0 -0
- data/spec/images/vlc_0.bmp +0 -0
- data/spec/images/vlc_2_6.bmp +0 -0
- data/spec/images/vlc_4.bmp +0 -0
- data/spec/images/vlc_5.bmp +0 -0
- data/spec/images/vlc_6.bmp +0 -0
- data/spec/images/vlc_9.bmp +0 -0
- data/spec/images/vlc_colon.bmp +0 -0
- data/spec/mouse_forever.rb +2 -0
- data/spec/open.bat +1 -0
- data/spec/silence.wav +0 -0
- data/spec/spec.blanker.rb +35 -0
- data/spec/spec.keyboard_input.rb +56 -0
- data/spec/spec.mouse.rb +11 -0
- data/spec/spec.muter.rb +33 -0
- data/spec/spec.ocr.rb +33 -0
- data/spec/spec.overlayer.rb +331 -0
- data/spec/spec.screen_tracker.rb +211 -0
- data/spec/test_yaml.yml +4 -0
- data/vendor/gocr048.exe +0 -0
- data/zamples/players/captures/hulu full screen non hour.jpg +0 -0
- data/zamples/players/captures/hulu full screen over hour.jpg +0 -0
- data/zamples/players/captures/silence.bmp +0 -0
- data/zamples/players/captures/vlc grab over one hour file over one hour play.bmp +0 -0
- data/zamples/players/captures/vlc2.bmp +0 -0
- data/zamples/players/captures/vlc_full_screen_slider under one hour.bmp +0 -0
- data/zamples/players/captures/youtube full screen big screen.jpg +0 -0
- data/zamples/players/how_to +21 -0
- data/zamples/players/hulu_full_screened_over_an_hour.yml +23 -0
- data/zamples/players/vlc_full_screened.yml +6 -0
- data/zamples/players/vlc_full_screened_over_hour.yml +23 -0
- data/zamples/players/vlc_non_full_screened.yml +19 -0
- data/zamples/players/vlc_non_full_screened_under_an_hour.yml +19 -0
- data/zamples/scene_lists/disney_cars.yml +13 -0
- data/zamples/scene_lists/happy_feet.yml +7 -0
- data/zamples/scene_lists/mute_list.yml +15 -0
- metadata +286 -0
data/lib/overlayer.rb
ADDED
@@ -0,0 +1,341 @@
|
|
1
|
+
require 'sane'
|
2
|
+
require 'thread'
|
3
|
+
require 'timeout'
|
4
|
+
require 'yaml'
|
5
|
+
require_relative 'muter'
|
6
|
+
require_relative 'blanker'
|
7
|
+
require 'pp' # pretty_inspect
|
8
|
+
|
9
|
+
class Time
|
10
|
+
def self.now_f
|
11
|
+
now.to_f
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class OverLayer
|
16
|
+
|
17
|
+
def muted?
|
18
|
+
@am_muted
|
19
|
+
end
|
20
|
+
|
21
|
+
def blank?
|
22
|
+
@am_blanked
|
23
|
+
end
|
24
|
+
|
25
|
+
def mute!
|
26
|
+
@am_muted = true
|
27
|
+
puts 'muting!' if $VERBOSE
|
28
|
+
Muter.mute! unless $DEBUG
|
29
|
+
end
|
30
|
+
|
31
|
+
def unmute!
|
32
|
+
@am_muted = false
|
33
|
+
puts 'unmuting!' if $VERBOSE
|
34
|
+
Muter.unmute! unless $DEBUG
|
35
|
+
end
|
36
|
+
|
37
|
+
def blank!
|
38
|
+
@am_blanked = true
|
39
|
+
Blanker.blank_full_screen! unless $DEBUG
|
40
|
+
end
|
41
|
+
|
42
|
+
def unblank!
|
43
|
+
@am_blanked = false
|
44
|
+
Blanker.unblank_full_screen! unless $DEBUG
|
45
|
+
end
|
46
|
+
|
47
|
+
def reload_yaml!
|
48
|
+
if @file_mtime != (new_time = File.stat(@filename).mtime)
|
49
|
+
@all_sequences = OverLayer.translate_yaml(File.read(@filename))
|
50
|
+
# LTODO... @all_sequences = @all_sequences.map{|k, v| v.sort!} etc.
|
51
|
+
puts '(re) loaded mute sequences as', pretty_sequences.pretty_inspect, ""
|
52
|
+
pps 'because old time', @file_mtime.to_f, '!= new time', new_time.to_f if $VERBOSE
|
53
|
+
@file_mtime = new_time # save 0.0002!
|
54
|
+
else
|
55
|
+
p 'matching time:', new_time if $VERBOSE
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def pretty_sequences
|
60
|
+
new_sequences = {}
|
61
|
+
@all_sequences.each{|type, values|
|
62
|
+
if values.is_a? Array
|
63
|
+
new_sequences[type] = values.map{|s, f|
|
64
|
+
[translate_time_to_human_readable(s), translate_time_to_human_readable(f)]
|
65
|
+
}
|
66
|
+
else
|
67
|
+
new_sequences[type] = values
|
68
|
+
end
|
69
|
+
}
|
70
|
+
new_sequences
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.new_raw ruby_hash
|
74
|
+
File.write 'temp.yml', YAML.dump(ruby_hash)
|
75
|
+
OverLayer.new('temp.yml')
|
76
|
+
end
|
77
|
+
|
78
|
+
def initialize filename, minutes = nil
|
79
|
+
@filename = filename
|
80
|
+
@am_muted = false
|
81
|
+
@am_blanked = false
|
82
|
+
@mutex = Mutex.new
|
83
|
+
@cv = ConditionVariable.new
|
84
|
+
@file_mtime = nil
|
85
|
+
reload_yaml!
|
86
|
+
@start_time = Time.now_f # assume they want to start immediately...
|
87
|
+
if minutes
|
88
|
+
self.set_seconds self.class.translate_string_to_seconds(minutes)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def self.translate_yaml raw_yaml
|
93
|
+
all = YAML.load(raw_yaml)
|
94
|
+
# now it's like {:mutes => {"1:02.0" => "1:3.0"}}
|
95
|
+
# translate to floats like 62.0 => 63.0
|
96
|
+
for type in [:mutes, :blank_outs]
|
97
|
+
maps = all[type] || all[type.to_s] || {}
|
98
|
+
new = {}
|
99
|
+
maps.each{|s,e|
|
100
|
+
# both are like 1:02.0
|
101
|
+
new[translate_string_to_seconds(s)] = translate_string_to_seconds(e)
|
102
|
+
}
|
103
|
+
all.delete(type.to_s)
|
104
|
+
all[type] = new.sort
|
105
|
+
end
|
106
|
+
all
|
107
|
+
end
|
108
|
+
|
109
|
+
def translate_time_to_human_readable seconds
|
110
|
+
# 3600 => "1:00:00"
|
111
|
+
out = ''
|
112
|
+
hours = seconds.to_i / 3600
|
113
|
+
out << "%d" % hours
|
114
|
+
out << ":"
|
115
|
+
seconds = seconds - hours*3600
|
116
|
+
minutes = seconds.to_i / 60
|
117
|
+
out << "%02d" % minutes
|
118
|
+
seconds = seconds - minutes * 60
|
119
|
+
out << ":"
|
120
|
+
out << "%04.1f" % seconds
|
121
|
+
end
|
122
|
+
|
123
|
+
# make it optional...for now muhaha [lodo take out if never useful]
|
124
|
+
def timestamp_changed to_this_exact_string, delta
|
125
|
+
if to_this_exact_string
|
126
|
+
set_seconds OverLayer.translate_string_to_seconds(to_this_exact_string) + delta
|
127
|
+
else
|
128
|
+
round_current_time_to_nearest_second
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def round_current_time_to_nearest_second
|
133
|
+
current_time = cur_time
|
134
|
+
better_time = current_time.round
|
135
|
+
set_seconds better_time
|
136
|
+
puts 'screen snapshot diff with time we thought it was was:' + (current_time - better_time).to_s if $VERBOSE
|
137
|
+
end
|
138
|
+
|
139
|
+
def self.translate_string_to_seconds s
|
140
|
+
# might actually already be a float...
|
141
|
+
if s.is_a? Float
|
142
|
+
return s
|
143
|
+
end
|
144
|
+
|
145
|
+
# s is like 1:01:02.0
|
146
|
+
total = 0.0
|
147
|
+
seconds = s.split(":")[-1]
|
148
|
+
total += seconds.to_f
|
149
|
+
minutes = s.split(":")[-2] || "0"
|
150
|
+
total += 60 * minutes.to_i
|
151
|
+
hours = s.split(":")[-3] || "0"
|
152
|
+
total += 60* 60 * hours.to_i
|
153
|
+
total
|
154
|
+
end
|
155
|
+
|
156
|
+
# returns seconds it's at currently...
|
157
|
+
def cur_time
|
158
|
+
return Time.now_f - @start_time
|
159
|
+
end
|
160
|
+
|
161
|
+
def status
|
162
|
+
time = "Current time: " + translate_time_to_human_readable(cur_time)
|
163
|
+
begin
|
164
|
+
mute, blank, next_sig = get_current_state
|
165
|
+
if next_sig == :done
|
166
|
+
state = " no more actions after this point..."
|
167
|
+
else
|
168
|
+
state = " next action at #{translate_time_to_human_readable next_sig}s (#{mute ? "muted" : '' } #{blank ? "blanked" : '' })"
|
169
|
+
end
|
170
|
+
end
|
171
|
+
time + state + " (HhMmSsTtdvq): "
|
172
|
+
end
|
173
|
+
|
174
|
+
def keyboard_input char
|
175
|
+
delta = case char
|
176
|
+
when 'h' then 60*60
|
177
|
+
when 'H' then -60*60
|
178
|
+
when 'm' then 60
|
179
|
+
when 'M' then -60
|
180
|
+
when 's' then 1
|
181
|
+
when 'S' then -1
|
182
|
+
when 't' then 0.1
|
183
|
+
when 'T' then -0.1
|
184
|
+
when 'v' then
|
185
|
+
$VERBOSE = !$VERBOSE
|
186
|
+
p 'set verbose to ', $VERBOSE
|
187
|
+
return
|
188
|
+
when 'd'
|
189
|
+
$DEBUG = !$DEBUG
|
190
|
+
p 'set debug to', $DEBUG
|
191
|
+
return
|
192
|
+
when ' ' then
|
193
|
+
puts cur_time
|
194
|
+
return
|
195
|
+
else nil
|
196
|
+
end
|
197
|
+
if delta
|
198
|
+
reload_yaml!
|
199
|
+
set_seconds(cur_time + delta)
|
200
|
+
else
|
201
|
+
puts 'invalid char: [' + char + ']'
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
# sets it to a new set of seconds...
|
206
|
+
def set_seconds seconds
|
207
|
+
seconds = [seconds, 0].max
|
208
|
+
@mutex.synchronize {
|
209
|
+
@start_time = Time.now_f - seconds
|
210
|
+
# tell the driver thread to continue onward. Cheery-o. We're not super thread friendly but good enough for having two contact each other...
|
211
|
+
@cv.signal
|
212
|
+
}
|
213
|
+
end
|
214
|
+
|
215
|
+
# we have a single scheduler thread, that is notified when the time may have changed
|
216
|
+
# like
|
217
|
+
# def restart new_time
|
218
|
+
# @current_time = xxx
|
219
|
+
# broadcast # things have changed
|
220
|
+
# end
|
221
|
+
|
222
|
+
def start_thread continue_forever = false
|
223
|
+
Thread.new { continue_until_past_all continue_forever }
|
224
|
+
end
|
225
|
+
|
226
|
+
# returns [start, end, active|:done]
|
227
|
+
def discover_state type, cur_time
|
228
|
+
for start, endy in @all_sequences[type]
|
229
|
+
if cur_time < endy
|
230
|
+
# first one that we haven't passed the *end* of yet
|
231
|
+
if(cur_time >= start)
|
232
|
+
return [start, endy, true]
|
233
|
+
else
|
234
|
+
return [start, endy, false]
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
end
|
239
|
+
return [nil, nil, :done]
|
240
|
+
end
|
241
|
+
|
242
|
+
# returns [true, false, next_moment_of_importance|:done]
|
243
|
+
def get_current_state
|
244
|
+
all = []
|
245
|
+
time = cur_time
|
246
|
+
for type in [:mutes, :blank_outs] do
|
247
|
+
all << discover_state(type, time)
|
248
|
+
end
|
249
|
+
output = []
|
250
|
+
# all is [[start, end, active]...] or [:done, :done]
|
251
|
+
# so create [true, false, next_moment]
|
252
|
+
earliest_moment = 1_000_000
|
253
|
+
all.each{|start, endy, active|
|
254
|
+
if active == :done
|
255
|
+
output << false
|
256
|
+
next
|
257
|
+
else
|
258
|
+
output << active
|
259
|
+
end
|
260
|
+
if active
|
261
|
+
earliest_moment = [earliest_moment, endy].min
|
262
|
+
else
|
263
|
+
earliest_moment = [earliest_moment, start].min
|
264
|
+
end
|
265
|
+
}
|
266
|
+
if earliest_moment == 1_000_000
|
267
|
+
output << :done
|
268
|
+
else
|
269
|
+
output << earliest_moment
|
270
|
+
end
|
271
|
+
output
|
272
|
+
end
|
273
|
+
|
274
|
+
def continue_until_past_all continue_forever
|
275
|
+
if RUBY_VERSION < '1.9.2'
|
276
|
+
raise 'need 1.9.2+ for MRI' unless RUBY_PLATFORM =~ /java/
|
277
|
+
end
|
278
|
+
|
279
|
+
@mutex.synchronize {
|
280
|
+
loop {
|
281
|
+
muted, blanked, next_point = get_current_state
|
282
|
+
if next_point == :done
|
283
|
+
unless continue_forever
|
284
|
+
return # done!
|
285
|
+
else
|
286
|
+
time_till_next_mute_starts = 1_000_000
|
287
|
+
end
|
288
|
+
else
|
289
|
+
time_till_next_mute_starts = next_point - cur_time
|
290
|
+
end
|
291
|
+
|
292
|
+
pps 'sleeping until next action (%s) begins in %fs (%f) %f' % [next_point, time_till_next_mute_starts, Time.now_f, cur_time] if $VERBOSE
|
293
|
+
|
294
|
+
@cv.wait(@mutex, time_till_next_mute_starts) if time_till_next_mute_starts > 0
|
295
|
+
pps 'just woke up from pre-mute wait at', Time.now_f if $VERBOSE
|
296
|
+
something_has_possibly_changed
|
297
|
+
}
|
298
|
+
}
|
299
|
+
end
|
300
|
+
|
301
|
+
def set_states!
|
302
|
+
should_be_muted, should_be_blank, next_point = get_current_state
|
303
|
+
|
304
|
+
if should_be_muted && !muted?
|
305
|
+
mute!
|
306
|
+
end
|
307
|
+
|
308
|
+
if !should_be_muted && muted?
|
309
|
+
unmute!
|
310
|
+
end
|
311
|
+
|
312
|
+
if should_be_blank && !blank?
|
313
|
+
blank!
|
314
|
+
end
|
315
|
+
|
316
|
+
if !should_be_blank && blank?
|
317
|
+
unblank!
|
318
|
+
end
|
319
|
+
|
320
|
+
end
|
321
|
+
|
322
|
+
def something_has_possibly_changed
|
323
|
+
current = cur_time
|
324
|
+
muted, blanked, next_point = get_current_state
|
325
|
+
@muted = muted
|
326
|
+
@blanked = blanked
|
327
|
+
@endy = next_point
|
328
|
+
return if next_point == :done
|
329
|
+
if(current < next_point)
|
330
|
+
set_states!
|
331
|
+
duration_left = @endy - current
|
332
|
+
pps 'just muted it at', Time.now_f, current, 'for interval:', 'which is', duration_left, 'more s' if $VERBOSE
|
333
|
+
if duration_left > 0
|
334
|
+
@cv.wait(@mutex, duration_left) if duration_left > 0
|
335
|
+
end
|
336
|
+
pps 'done sleeping', duration_left, 'was muted unmuting now', Time.now_f if $VERBOSE
|
337
|
+
set_states!
|
338
|
+
end
|
339
|
+
end
|
340
|
+
|
341
|
+
end
|
@@ -0,0 +1,160 @@
|
|
1
|
+
require 'win32/screenshot'
|
2
|
+
require 'sane'
|
3
|
+
require 'yaml'
|
4
|
+
require_relative 'ocr'
|
5
|
+
|
6
|
+
class ScreenTracker
|
7
|
+
|
8
|
+
def self.new_from_yaml yaml, callback
|
9
|
+
settings = YAML.load yaml
|
10
|
+
# heigth is shared...
|
11
|
+
height = settings["height"]
|
12
|
+
digits = settings["digits"]
|
13
|
+
return new(settings["name"], settings["x"], settings["y"], settings["width"], settings["height"], digits, callback)
|
14
|
+
end
|
15
|
+
|
16
|
+
# digits like {:hours => [100,5], :minute_tens, :minute_ones, :second_tens, :second_ones}
|
17
|
+
# digits share the height...
|
18
|
+
def initialize name_or_regex,x,y,width,height,digits=nil,callback=nil
|
19
|
+
# cache to save us 0.00445136 per time LOL
|
20
|
+
if name_or_regex.to_s.downcase == 'desktop'
|
21
|
+
# full screen option
|
22
|
+
@hwnd = hwnd = Win32::Screenshot::BitmapMaker.desktop_window
|
23
|
+
else
|
24
|
+
@hwnd = Win32::Screenshot::BitmapMaker.hwnd(name_or_regex)
|
25
|
+
end
|
26
|
+
unless @hwnd
|
27
|
+
print 'perhaps not running yet? [%s] START IT QUICKLY' % name_or_regex
|
28
|
+
until @hwnd
|
29
|
+
sleep 2
|
30
|
+
print ' trying again .'
|
31
|
+
STDOUT.flush
|
32
|
+
@hwnd = Win32::Screenshot::BitmapMaker.hwnd(name_or_regex)
|
33
|
+
end
|
34
|
+
puts 'found window'
|
35
|
+
end
|
36
|
+
pps 'height', height, 'width', width if $VERBOSE
|
37
|
+
raise 'poor dimentia' if width <= 0 || height <= 0
|
38
|
+
always_zero, always_zero, max_x, max_y = Win32::Screenshot::BitmapMaker.dimensions_for(@hwnd)
|
39
|
+
if(x < 0 || y < 0)
|
40
|
+
if x < 0
|
41
|
+
x = max_x + x
|
42
|
+
end
|
43
|
+
if y < 0
|
44
|
+
y = max_y + y
|
45
|
+
end
|
46
|
+
end
|
47
|
+
@height = height
|
48
|
+
@x = x; @y = y; @x2 = x+width; @y2 = y+height; @callback = callback
|
49
|
+
@max_x = max_x
|
50
|
+
raise 'poor width or wrong window' if @x2 > max_x || @x2 == x
|
51
|
+
raise 'poor height or wrong window' if @y2 > max_y || @y2 == y
|
52
|
+
@digits = digits
|
53
|
+
pps 'using x',@x, 'from x', x, 'y', @y, 'from y', y,'x2',@x2,'y2',@y2,'digits', @digits if $VERBOSE
|
54
|
+
end
|
55
|
+
|
56
|
+
def get_bmp
|
57
|
+
# Note: we no longer bring the window to the front tho...which it needs to be in both XP and Vista to work...sigh.
|
58
|
+
Win32::Screenshot::BitmapMaker.capture_area(@hwnd,@x,@y,@x2,@y2) {|h,w,bmp| return bmp}
|
59
|
+
end
|
60
|
+
|
61
|
+
def get_full_bmp
|
62
|
+
Win32::Screenshot.hwnd(@hwnd, 0) {|h,w,bmp| return bmp}
|
63
|
+
end
|
64
|
+
|
65
|
+
def dump_bmp filename = 'dump.bmp'
|
66
|
+
File.binwrite filename, get_bmp
|
67
|
+
File.binwrite 'all.' + filename, get_full_bmp
|
68
|
+
dump_digits
|
69
|
+
end
|
70
|
+
|
71
|
+
def dump_digits
|
72
|
+
if @digits
|
73
|
+
for type, bitmap in get_digits_as_bitmaps
|
74
|
+
File.binwrite type.to_s + '.bmp', bitmap
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
DIGIT_TYPES = [:hours, :minute_tens, :minute_ones, :second_tens, :second_ones]
|
80
|
+
# returns like {:hours => nil, :minutes_tens => raw_bmp, ...
|
81
|
+
def get_digits_as_bitmaps
|
82
|
+
# @digits are like {:hours => [100,5], :minute_tens => [x, width], :minute_ones, :second_tens, :second_ones}
|
83
|
+
out = {}
|
84
|
+
for type in DIGIT_TYPES
|
85
|
+
assert @digits.key?(type)
|
86
|
+
if @digits[type]
|
87
|
+
x,w = @digits[type]
|
88
|
+
if(x < 0)
|
89
|
+
x = @max_x + x
|
90
|
+
end
|
91
|
+
out[type] = Win32::Screenshot::BitmapMaker.capture_area(@hwnd, x, @y, x+w, @y2) {|h,w,bmp| bmp}
|
92
|
+
end
|
93
|
+
end
|
94
|
+
out
|
95
|
+
end
|
96
|
+
|
97
|
+
def get_relative_coords
|
98
|
+
[@x,@y,@x2,@y2]
|
99
|
+
end
|
100
|
+
|
101
|
+
# split out for unit testing purposes
|
102
|
+
def identify_digit bitmap
|
103
|
+
require 'ruby-debug'
|
104
|
+
# debugger
|
105
|
+
OCR.identify_digit(bitmap, @digits)
|
106
|
+
end
|
107
|
+
|
108
|
+
def wait_till_next_change
|
109
|
+
original = get_bmp
|
110
|
+
loop {
|
111
|
+
current = get_bmp
|
112
|
+
if current != original
|
113
|
+
if @digits
|
114
|
+
out = {}
|
115
|
+
dump_digits if $DEBUG
|
116
|
+
digits = get_digits_as_bitmaps # 0.08s [!] not too accurate...
|
117
|
+
start = Time.now
|
118
|
+
DIGIT_TYPES.each{|type|
|
119
|
+
if digits[type]
|
120
|
+
digit = identify_digit(digits[type])
|
121
|
+
unless digit
|
122
|
+
if $DEBUG || $VERBOSE
|
123
|
+
@a ||= 1
|
124
|
+
@a += 1
|
125
|
+
p 'unable to identify digit!' + type.to_s + @a.to_s
|
126
|
+
File.binwrite("bad_digit#{@a}.bmp", digits[type])
|
127
|
+
end
|
128
|
+
# early return
|
129
|
+
return
|
130
|
+
end
|
131
|
+
out[type] = digit
|
132
|
+
else
|
133
|
+
# there isn't one on screen, so probably zero...
|
134
|
+
out[type] = 0
|
135
|
+
end
|
136
|
+
}
|
137
|
+
out = "%d:%d%d:%d%d" % DIGIT_TYPES.map{|type| out[type]}
|
138
|
+
p 'got new screen time ' + out + " delta:" + (Time.now - start).to_s if $VERBOSE
|
139
|
+
# if the window was in the background it will be all zeroes, so nil it out
|
140
|
+
out = nil unless out =~ /[1-9]/
|
141
|
+
return out, Time.now - start
|
142
|
+
else
|
143
|
+
puts 'screen time change only detected...' if $VERBOSE
|
144
|
+
end
|
145
|
+
return
|
146
|
+
end
|
147
|
+
sleep 0.02
|
148
|
+
}
|
149
|
+
end
|
150
|
+
|
151
|
+
def process_forever_in_thread
|
152
|
+
Thread.new {
|
153
|
+
loop {
|
154
|
+
out_time, delta = wait_till_next_change
|
155
|
+
@callback.timestamp_changed out_time, delta
|
156
|
+
}
|
157
|
+
}
|
158
|
+
end
|
159
|
+
|
160
|
+
end
|
data/spec/common.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
begin
|
3
|
+
require 'rspec' # rspec2
|
4
|
+
rescue LoadError
|
5
|
+
require 'spec' # rspec1
|
6
|
+
require 'spec/autorun'
|
7
|
+
end
|
8
|
+
|
9
|
+
# some useful utilities...
|
10
|
+
|
11
|
+
require 'sane'
|
12
|
+
require 'benchmark'
|
13
|
+
Thread.abort_on_exception = true
|
14
|
+
require 'timeout'
|
15
|
+
require 'fileutils'
|
16
|
+
|
17
|
+
begin
|
18
|
+
require 'hitimes'
|
19
|
+
Benchmark.module_eval {
|
20
|
+
def self.realtime
|
21
|
+
Hitimes::Interval.measure { yield }
|
22
|
+
end
|
23
|
+
}
|
24
|
+
rescue LoadError
|
25
|
+
if RUBY_PLATFORM =~ /java/
|
26
|
+
require 'java'
|
27
|
+
Benchmark.module_eval {
|
28
|
+
def self.realtime
|
29
|
+
beginy = java.lang.System.nano_time
|
30
|
+
yield
|
31
|
+
(java.lang.System.nano_time - beginy)/1000000000.0
|
32
|
+
end
|
33
|
+
}
|
34
|
+
else
|
35
|
+
puts 'no hitimes available...'
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
#for file in Dir[File.dirname(__FILE__) + "/../lib/*"] do
|
41
|
+
# don't load them here in case one or other fails...
|
42
|
+
# require file
|
43
|
+
#end
|
data/spec/images/4.bmp
ADDED
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
data/spec/open.bat
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
"/program files/VideoLan/VLC/vlc.exe" silence.wav
|
data/spec/silence.wav
ADDED
Binary file
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/common'
|
2
|
+
require_relative '../lib/blanker.rb'
|
3
|
+
|
4
|
+
describe Blanker do
|
5
|
+
|
6
|
+
it "should be able to blank then unblank" do
|
7
|
+
Blanker.blank_full_screen!
|
8
|
+
sleep 3
|
9
|
+
Blanker.unblank_full_screen!
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should be able to blank several times" do
|
13
|
+
3.times {
|
14
|
+
Blanker.blank_full_screen!
|
15
|
+
Blanker.unblank_full_screen!
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should be able to unblank several times I suppose" do
|
20
|
+
3.times {
|
21
|
+
Blanker.unblank_full_screen!
|
22
|
+
}
|
23
|
+
end
|
24
|
+
|
25
|
+
describe "future work", :pending => true do
|
26
|
+
|
27
|
+
it "should be able to blank certain coords"
|
28
|
+
|
29
|
+
it "should have a color optionally"
|
30
|
+
|
31
|
+
it "should have a picture optionally"
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|