<commit_before>module FrancisCms
  class Photo < ActiveRecord::Base
    include FrancisCms::Concerns::Models::Publishable
    include FrancisCms::Concerns::Models::Redcarpeted
    include FrancisCms::Concerns::Models::Syndicatable
    include FrancisCms::Concerns::Models::Taggable
    include FrancisCms::Concerns::Models::Webmentionable

    validates :photo, presence: true

    mount_uploader :photo, PhotoUploader

    before_save :extract_exif_data, :reverse_geocode

    redcarpet :body

    self.per_page = 20

    def geolocated?
      street_address? && city? && state? && country?
    end

    def title
      @title ||= body? ? body.lines.first.chomp : 'Untitled'
    end

    private

    def convert_coords_to_decimal(str, ref)
      if str && ref
        parts = str.split(', ')

        out = to_fraction(parts[0]) + (to_fraction(parts[1]) / 60) + (to_fraction(parts[2]) / 3600)

        if (ref == 'S' || ref == 'W')
          out = out * -1
        end

        out
      end
    end

    def extract_exif_data
      img = MiniMagick::Image.open(photo.path)

      self.latitude = convert_coords_to_decimal(img.exif['GPSLatitude'], img.exif['GPSLatitudeRef'])
      self.longitude = convert_coords_to_decimal(img.exif['GPSLongitude'], img.exif['GPSLongitudeRef'])

      if taken_at = img.exif['DateTimeOriginal']
        self.taken_at = DateTime.strptime(taken_at, '%Y:%m:%d %H:%M:%S')
      end
    end

    def reverse_geocode
      if geo = Geocoder.search(%{#{self.latitude},#{self.longitude}}).first
        self.street_address = geo.street_address
        self.city = geo.city
        self.state = geo.state
        self.state_code = geo.state_code
        self.postal_code = geo.postal_code
        self.country = geo.country
        self.country_code = geo.country_code
      end
    end

    def to_fraction(str)
      numerator, denominator = str.split('/').map(&:to_f)
      denominator ||= 1

      numerator / denominator
    end
  end
end<commit_msg>Shore up EXIF-extracting methods.<commit_after>module FrancisCms
  class Photo < ActiveRecord::Base
    include FrancisCms::Concerns::Models::Publishable
    include FrancisCms::Concerns::Models::Redcarpeted
    include FrancisCms::Concerns::Models::Syndicatable
    include FrancisCms::Concerns::Models::Taggable
    include FrancisCms::Concerns::Models::Webmentionable

    validates :photo, presence: true

    mount_uploader :photo, PhotoUploader

    before_save :extract_exif_data, :reverse_geocode

    redcarpet :body

    self.per_page = 20

    def geolocated?
      latitude? && longitude?
    end

    def title
      @title ||= body? ? body.lines.first.chomp : 'Untitled'
    end

    private

    def convert_coords_to_decimal(str, ref)
      if str && ref
        parts = str.split(', ')

        out = to_fraction(parts[0]) + (to_fraction(parts[1]) / 60) + (to_fraction(parts[2]) / 3600)

        if (ref == 'S' || ref == 'W')
          out = out * -1
        end

        out
      end
    end

    def extract_exif_data
      img = MiniMagick::Image.open(photo.path)

      self.latitude = extract_latitude(img.exif)
      self.longitude = extract_longitude(img.exif)

      self.taken_at = extract_taken_at(img.exif)
    end

    def extract_latitude(exif)
      convert_coords_to_decimal(exif['GPSLatitude'], exif['GPSLatitudeRef'])
    end

    def extract_longitude(exif)
      convert_coords_to_decimal(exif['GPSLongitude'], exif['GPSLongitudeRef'])
    end

    def extract_taken_at(exif)
      gps_date_stamp = exif['GPSDateStamp']
      gps_time_stamp = exif['GPSTimeStamp']
      date_time_original = exif['DateTimeOriginal']

      if gps_date_stamp && gps_time_stamp
        matches = gps_time_stamp.match(%r{^(?<hours>\d+)\/\d+, (?<minutes>\d+)\/\d+, (?<seconds>\d+)\/\d+$})

        %{#{gps_date_stamp.gsub(':', '-')} #{matches[:hours]}:#{matches[:minutes]}:#{matches[:seconds]}}.to_datetime
      elsif date_time_original
        DateTime.strptime(date_time_original, '%Y:%m:%d %H:%M:%S')
      end
    end

    def reverse_geocode
      if geo = Geocoder.search(%{#{self.latitude},#{self.longitude}}).first
        self.street_address = geo.street_address
        self.city = geo.city
        self.state = geo.state
        self.state_code = geo.state_code
        self.postal_code = geo.postal_code
        self.country = geo.country
        self.country_code = geo.country_code
      end
    end

    def to_fraction(str)
      numerator, denominator = str.split('/').map(&:to_f)
      denominator ||= 1

      numerator / denominator
    end
  end
end
{'fraction_non_alphanumeric': 0.124945295404814, 'fraction_numerical': 0.0056892778993435445, 'mean_word_length': 2.954152249134948, 'pattern_counts': {'":': 0, '<': 8, '<?xml version=': 0, '>': 6, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5605525', 'n_tokens_mistral': 1648, 'n_tokens_neox': 1538, 'n_words': 270}