sensible-cinema 0.6.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/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
|