Class: ScreenTracker

Inherits:
Object
  • Object
show all
Extended by:
FFI::Library
Defined in:
lib/screen_tracker.rb

Constant Summary collapse

DIGIT_TYPES =
[:hours, :minute_tens, :minute_ones, :second_tens, :second_ones]

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name_or_regex, x, y, width, height, use_class_name = nil, digits = nil, callback = nil) ⇒ ScreenTracker

digits are like => [100,5], :minute_tens, :minute_ones, :second_tens, :second_ones digits share the height start point, have their own x and width…



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/screen_tracker.rb', line 42

def initialize name_or_regex, x, y, width, height, use_class_name=nil, digits=nil, callback=nil
  # cache to save us 0.00445136 per time LOL
  @name_or_regex = name_or_regex
  @use_class_name = use_class_name
  pps 'height', height, 'width', width if $VERBOSE
  raise 'poor dimentia' if width <= 0 || height <= 0
  get_hwnd_loop_forever
  max_x, max_y = Win32::Screenshot::Util.dimensions_for(@hwnd)
  if(x < 0 || y < 0)
    if x < 0
      x = max_x + x
    end
    if y < 0
      y = max_y + y
    end
  end
  @x = x; @y = y; @x2 = x+width; @y2 = y+height; @callback = callback    
  @max_x = max_x
  raise "poor width or wrong window #{@x2} #{max_x} #{x}" if @x2 > max_x  || @x2 == x
  if @y2 > max_y || @y2 == y || @y2 <= 0
    raise "poor height or wrong window selected #{@y2} > #{max_y} || #{@y2} == #{y} || #{@y2} <= 0" 
  end
  if max_x == 0 || max_y == 0
    # I don't think we can ever get here, because of the checks above
    raise 'window invisible?'
  end
  @digits = digits
  @previously_displayed_warning = false
  @dump_digit_count = 1
  pps 'using x',@x, 'from x', x, 'y', @y, 'from y', y,'x2',@x2,'y2',@y2,'digits', @digits.inspect if $VERBOSE
end

Instance Attribute Details

#hwndObject

Returns the value of attribute hwnd.



38
39
40
# File 'lib/screen_tracker.rb', line 38

def hwnd
  @hwnd
end

Class Method Details

.new_from_yaml(yaml, callback) ⇒ Object

callback can be nil, is used for timestamp changed stuff



32
33
34
35
36
# File 'lib/screen_tracker.rb', line 32

def self.new_from_yaml yaml, callback # callback can be nil, is used for timestamp changed stuff
  settings = YAML.load yaml
  return new(settings["name"], settings["x"], settings["y"], settings["width"], 
      settings["height"], settings["use_class_name"], settings["digits"], callback)
end

Instance Method Details

#attempt_to_get_time_from_screen(start_time) ⇒ Object



209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
# File 'lib/screen_tracker.rb', line 209

def attempt_to_get_time_from_screen start_time
  out = {}
  # force it to have two matching snapshots in a row, to avoid race conditions grabbing the digits...
  # allow youtube to update (sigh) lodo just for utube
  previous = nil 
  sleep 0.05
  current = get_digits_as_bitmaps
  while previous != current
    previous = current
    sleep 0.05
    current = get_digits_as_bitmaps
    # lodo it should probably poll *before* calling this, not here...maybe?
  end
  assert previous == current
  digits = current = previous    
  DIGIT_TYPES.each{|type|
    if digits[type]
      digit = identify_digit(digits[type])
      unless digit
        bitmap = digits[type]
        # unable to identify a digit?
        if $DEBUG || $VERBOSE && (type != :hours)
          @a ||= 1
          @a += 1
          @already_wrote ||= {}
          unless @already_wrote[bitmap]
            p 'unable to identify capture!' + type.to_s + @a.to_s + ' dump:' + @dump_digit_count.to_s
            File.binwrite("bad_digit#{@a}#{type}.bmp", bitmap)
            @already_wrote[bitmap] = true
          end
        end
        if type == :hours
          digit = 0 # this one can fail and that's ok in VLC bottom right
        else
          # early (failure) return
          return nil
        end
      else
        p " got digit #{type} OCR as #{digit} which was captured to dump #{@dump_digit_count - 1} #{Time.now_f}" if $DEBUG
      end
      out[type] = digit
    else
      # there isn't one specified as being on screen, so assume it is always zero (like youtube hour)...
      out[type] = 0
    end
  }
  out = "%d:%d%d:%d%d" % DIGIT_TYPES.map{ |type| out[type] }
  puts '', 'got new screen time ' + out + " (+ tracking delta:" + (Time.now - start_time).to_s  + ")" if $VERBOSE
  return out, Time.now-start_time
end

#dump_bmps(filename = 'dump.bmp') ⇒ Object

writes out all screen tracking info to various files in the current pwd



114
115
116
117
118
# File 'lib/screen_tracker.rb', line 114

def dump_bmps filename = 'dump.bmp'
  File.binwrite filename, get_bmp
  File.binwrite 'all.' + filename, get_full_bmp
  dump_digits(get_digits_as_bitmaps, 'dump_bmp') if @digits
end

#dump_digits(digits, message) ⇒ Object



120
121
122
123
124
125
126
127
# File 'lib/screen_tracker.rb', line 120

def dump_digits digits, message
  p "#{message} dumping digits to dump no: #{@dump_digit_count} #{Time.now.to_f}"
  for type, bitmap in digits
    File.binwrite type.to_s + '.' + @dump_digit_count.to_s + '.bmp', bitmap    
  end
  File.binwrite @dump_digit_count.to_s + '.mrsh', Marshal.dump(digits)
  @dump_digit_count += 1
end

#get_bmpObject

gets the snapshot of “all the digits together”



103
104
105
106
# File 'lib/screen_tracker.rb', line 103

def get_bmp
  # Note: we no longer bring the window to the front tho...which it needs to be in both XP and Vista to work...sigh.
  Win32::Screenshot::BitmapMaker.capture_area(@hwnd,@x,@y,@x2,@y2) {|h,w,bmp| return bmp}
end

#get_coords_of_window_on_displayObject

yea



158
159
160
161
162
# File 'lib/screen_tracker.rb', line 158

def get_coords_of_window_on_display # yea
  out = FFI::MemoryPointer.new(:long, 4)
  ScreenTracker.GetWindowRect @hwnd, out
  out.read_array_of_long(4)
end

#get_digits_as_bitmapsObject

returns like {:hours => nil, :minutes_tens => raw_bmp, …



132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/screen_tracker.rb', line 132

def get_digits_as_bitmaps
  # @digits are like {:hours => [100,5], :minute_tens => [x, width], :minute_ones, :second_tens, :second_ones}
  out = {}
  for type in DIGIT_TYPES
    assert @digits.key?(type)
    if @digits[type]
      x,w = @digits[type]
      if(x < 0)
        x = @max_x + x
      end
      x2 = x + w
      raise 'a digit width can never be negative #{w}' if w <= 0
      y2 = @y2
      width = x2 - x
      height = y2 - @y
      # lodo calculate these only once...
      out[type] = Win32::Screenshot::BitmapMaker.capture_area(@hwnd, x, @y, x2, y2) {|h,w,bmp| bmp}
    end
  end
  out
end

#get_full_bmpObject

gets snapshot of the full window



109
110
111
# File 'lib/screen_tracker.rb', line 109

def get_full_bmp
   Win32::Screenshot::BitmapMaker.capture_all(@hwnd) {|h,w,bmp| return bmp}
end

#get_hwnd_loop_foreverObject



74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/screen_tracker.rb', line 74

def get_hwnd_loop_forever
  if @name_or_regex.to_s.downcase == 'desktop'
    # full screen option
    assert !@use_class_name # not an option
    @hwnd = hwnd = Win32::Screenshot::BitmapMaker.desktop_window
    return
  else
    raise if OS.mac?
    @hwnd = Win32::Screenshot::BitmapMaker.hwnd(@name_or_regex, @use_class_name)
  end

  # display the 'found it message' only if it was previously lost...
  unless @hwnd
    until @hwnd
      print 'unable to find the player window currently [%s] (maybe need to start program or move mouse over it)' % @name_or_regex.inspect
      sleep 1
      STDOUT.flush

      hwnd = Win32::Screenshot::BitmapMaker.hwnd(@name_or_regex, @use_class_name)
      width, height = Win32::Screenshot::Util.dimensions_for(hwnd)
      p width, height
      @hwnd = hwnd
    end
    puts 're-established contact with window'
  end
  true
end

#get_relative_coords_of_timestamp_windowObject



154
155
156
# File 'lib/screen_tracker.rb', line 154

def get_relative_coords_of_timestamp_window
  [@x,@y,@x2,@y2]
end

#identify_digit(bitmap) ⇒ Object



164
165
166
# File 'lib/screen_tracker.rb', line 164

def identify_digit bitmap
  OCR.identify_digit(bitmap, @digits)
end

#process_forever_in_threadObject



260
261
262
263
264
265
266
267
# File 'lib/screen_tracker.rb', line 260

def process_forever_in_thread
  Thread.new {
    loop {
      out_time, delta = wait_till_next_change
      @callback.timestamp_changed out_time, delta
    }
  }
end

#wait_till_next_changeObject

we have to wait until the next change, because when we start, it might be half-way through the current second…



170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
# File 'lib/screen_tracker.rb', line 170

def wait_till_next_change
  original = get_bmp
  time_since_last_screen_change = Time.now
  loop {
    # save away the current time to try and be most accurate...
    time_before_current_scan = Time.now
    current = get_bmp
    if current != original
      if @digits
        got = attempt_to_get_time_from_screen time_before_current_scan
        if @previously_displayed_warning && got
          # reassure user :)
          p 'tracking it successfully again' 
          @previously_displayed_warning = false
        end
        return got
      else
        puts 'screen time change only detected... [unexpected]' # unit tests do this still <sigh>
        return
      end
    else
      if(Time.now - time_since_last_screen_change > 2.0)
        got_implies_able_to_still_ocr = attempt_to_get_time_from_screen time_before_current_scan
        if got_implies_able_to_still_ocr
          return got_implies_able_to_still_ocr
        else
          p 'warning--unable to track screen time for some reason [perhaps screen obscured or it\'s not playing yet?] ' + @hwnd.to_s
          @previously_displayed_warning = true
          # also reget window hwnd, just in case that's the problem...(can be with VLC moving from title to title)
          get_hwnd_loop_forever
          # LODO loop through all available player descriptions to find the right one, or a changed different new one, et al
        end
        time_since_last_screen_change = Time.now
      end
    end
    sleep 0.02
  }
end