class Adri::Photo

Attributes

location_cache[RW]
dry_run[R]
path_format[R]
prefix[R]
source_path[R]
verbose[R]

Public Class Methods

new(path, options) click to toggle source
# File bin/adri, line 47
def initialize(path, options)
  @source_path = File.absolute_path(path)
  @path_format = options[:path_format].gsub('/', File::SEPARATOR)
  @verbose = !options[:quiet]
  @dry_run = !options[:run]

  @prefix =
    if options[:prefix].to_s.strip.empty?
      File.dirname(@source_path)
    else
      File.absolute_path(options[:prefix])
    end
end

Public Instance Methods

date_time() click to toggle source
# File bin/adri, line 61
def date_time
  return @date_time if @date_time

  if exif&.date_time && exif.date_time != '0000:00:00 00:00:00'
    @date_time =
      Time.strptime(exif.date_time.sub(' 24:', ' 00:'), '%Y:%m:%d %H:%M:%S')
  end
end
destination_path() click to toggle source
# File bin/adri, line 100
def destination_path
  return @destination_path if @destination_path

  path = date_time.strftime(path_format)

  if location_in_path_format?
    path = sprintf(path, location: location)
  end

  @destination_path ||= File.join(
    prefix,
    path,
    File.basename(source_path)
  )
end
latitude() click to toggle source
# File bin/adri, line 70
def latitude
  return @latitude if @latitude

  if exif&.gps_latitude
    @latitude = geo_float(exif.gps_latitude)
  end
end
location() click to toggle source
# File bin/adri, line 86
def location
  return @location if defined?(@location)

  @location = read_location_from_cache

  return @location if @location != false

  @location = location_from_latlng

  write_location_to_cache

  @location
end
longitude() click to toggle source
# File bin/adri, line 78
def longitude
  return @longitude if @longitude

  if exif&.gps_longitude
    @longitude = geo_float(exif.gps_longitude)
  end
end
move() click to toggle source
# File bin/adri, line 116
def move
  return if skip_move?

  if verbose
    puts sprintf(
      '%s -> %s%s',
      source_path,
      destination_path,
      dry_run ? ' (DRY RUN)' : ''
    )
  end

  return if dry_run

  FileUtils.mkdir_p(File.dirname(destination_path))
  FileUtils.mv(source_path, destination_path)
end

Private Instance Methods

exif() click to toggle source
# File bin/adri, line 134
        def exif
  begin
    @exif ||= Exif::Data.new(File.open(source_path))
  rescue Exif::NotReadable
  end
end
geo_float(value) click to toggle source
# File bin/adri, line 239
        def geo_float(value)
  degrees, minutes, seconds = value
  degrees + minutes / 60.0 + seconds / 3600.0
end
latlng() click to toggle source
# File bin/adri, line 145
        def latlng
  @latlng ||= [latitude, longitude].compact
end
location_cache_key() click to toggle source
# File bin/adri, line 224
        def location_cache_key
  @location_cache_key ||= [
    latitude.truncate(LOCATION_CACHE_SCALE).to_s,
    longitude.truncate(LOCATION_CACHE_SCALE).to_s
  ]
end
location_from_geocode_result(result) click to toggle source
# File bin/adri, line 212
        def location_from_geocode_result(result)
  LOCATION_ADDRESS_TYPES.each do |type|
    entity = result.address_components_of_type(type).first

    if entity
      return entity['long_name']
    end
  end

  nil
end
location_from_latlng() click to toggle source
# File bin/adri, line 180
        def location_from_latlng
  current_delay = 0.1 # 100 ms

  begin
    geocode_results = Geocoder.search(latlng)
  rescue Geocoder::OverQueryLimitError,
         Geocoder::LookupTimeout => e
    puts "Got #{e.class.name}" if verbose

    if current_delay > GEOCODE_MAX_DELAY
      puts "Exceeded max delay of #{GEOCODE_MAX_DELAY} seconds" if verbose
      return
    end

    puts "Waiting #{current_delay} seconds before retrying..." if verbose
    sleep(current_delay)
    current_delay *= 2 # Exponential backoff

    retry
  end

  geocode_results.each do |result|
    location = location_from_geocode_result(result)

    if location
      return location
    end
  end

  nil
end
location_in_path_format?() click to toggle source
# File bin/adri, line 141
        def location_in_path_format?
  path_format['%{location}']
end
read_location_from_cache() click to toggle source
# File bin/adri, line 235
        def read_location_from_cache
  self.class.location_cache[location_cache_key]
end
skip_move?() click to toggle source
# File bin/adri, line 149
        def skip_move?
  if !File.exist?(source_path)
    puts "Missing file #{source_path}" if verbose
    return true
  end

  if date_time.nil?
    puts "No datetime data #{source_path}" if verbose
    return true
  end

  if location_in_path_format?
    if latlng.empty?
      puts "No location data #{source_path}" if verbose
      return true
    end

    if location.nil? # Geocoding failed
      puts "Unknown location #{source_path}" if verbose
      return true
    end
  end

  if File.exist?(destination_path)
    puts "Existing file #{destination_path}" if verbose
    return true
  end

  false
end
write_location_to_cache() click to toggle source
# File bin/adri, line 231
        def write_location_to_cache
  self.class.location_cache[location_cache_key] = location
end