Add a button to import Photoprism geodata

This commit is contained in:
Eugene Burmakin 2024-12-03 15:40:21 +01:00
parent 42b74ecd52
commit e17b671c9c
7 changed files with 116 additions and 7 deletions

View file

@ -7,6 +7,8 @@ class EnqueueBackgroundJob < ApplicationJob
case job_name
when 'start_immich_import'
Import::ImmichGeodataJob.perform_later(user_id)
when 'start_photoprism_import'
Import::PhotoprismGeodataJob.perform_later(user_id)
when 'start_reverse_geocoding', 'continue_reverse_geocoding'
Jobs::Create.new(job_name, user_id).call
else

View file

@ -0,0 +1,12 @@
# frozen_string_literal: true
class Import::PhotoprismGeodataJob < ApplicationJob
queue_as :imports
sidekiq_options retry: false
def perform(user_id)
user = User.find(user_id)
Photoprism::ImportGeodata.new(user).call
end
end

View file

@ -10,7 +10,7 @@ class Import < ApplicationRecord
enum :source, {
google_semantic_history: 0, owntracks: 1, google_records: 2,
google_phone_takeout: 3, gpx: 4, immich_api: 5, geojson: 6
google_phone_takeout: 3, gpx: 4, immich_api: 5, geojson: 6, photoprism_api: 7
}
def process!

View file

@ -57,7 +57,7 @@ class Immich::ImportGeodata
end
def log_no_data
Rails.logger.info 'No data found'
Rails.logger.info 'No geodata found for Immich'
end
def create_import_failed_notification(import_name)

View file

@ -0,0 +1,80 @@
# frozen_string_literal: true
class Photoprism::ImportGeodata
attr_reader :user, :start_date, :end_date
def initialize(user, start_date: '1970-01-01', end_date: nil)
@user = user
@start_date = start_date
@end_date = end_date
end
def call
photoprism_data = retrieve_photoprism_data
log_no_data and return if photoprism_data.empty?
photoprism_data_json = parse_photoprism_data(photoprism_data)
file_name = file_name(photoprism_data_json)
import = user.imports.find_or_initialize_by(name: file_name, source: :photoprism_api)
create_import_failed_notification(import.name) and return unless import.new_record?
import.raw_data = photoprism_data_json
import.save!
ImportJob.perform_later(user.id, import.id)
end
private
def retrieve_photoprism_data
Photoprism::RequestPhotos.new(user, start_date:, end_date:).call
end
def parse_photoprism_data(photoprism_data)
geodata = photoprism_data.map do |asset|
next unless valid?(asset)
extract_geodata(asset)
end
geodata.compact.sort_by { |data| data[:timestamp] }
end
def valid?(asset)
asset['Lat'] &&
asset['Lat'] != 0 &&
asset['Lng'] &&
asset['Lng'] != 0 &&
asset['TakenAt']
end
def extract_geodata(asset)
{
latitude: asset.dig('exifInfo', 'latitude'),
longitude: asset.dig('exifInfo', 'longitude'),
timestamp: Time.zone.parse(asset.dig('exifInfo', 'dateTimeOriginal')).to_i
}
end
def log_no_data
Rails.logger.info 'No geodata found for Photoprism'
end
def create_import_failed_notification(import_name)
Notifications::Create.new(
user:,
kind: :info,
title: 'Import was not created',
content: "Import with the same name (#{import_name}) already exists. If you want to proceed, delete the existing import and try again."
).call
end
def file_name(photoprism_data_json)
from = Time.zone.at(photoprism_data_json.first[:timestamp]).to_date
to = Time.zone.at(photoprism_data_json.last[:timestamp]).to_date
"photoprism-geodata-#{user.email}-from-#{from}-to-#{to}.json"
end
end

View file

@ -10,6 +10,11 @@
<% else %>
<a href='' class="rounded-lg py-3 px-5 bg-blue-900 text-gray block font-medium tooltip cursor-not-allowed" data-tip="You need to provide your Immich instance data in the Settings">Import Immich data</a>
<% end %>
<% if current_user.settings['photoprism_url'] && current_user.settings['photoprism_api_key'] %>
<%= link_to 'Import Photoprism data', settings_background_jobs_path(job_name: 'start_photoprism_import'), method: :post, data: { confirm: 'Are you sure?', turbo_confirm: 'Are you sure?', turbo_method: :post }, class: 'rounded-lg py-3 px-5 bg-blue-600 text-white block font-medium' %>
<% else %>
<a href='' class="rounded-lg py-3 px-5 bg-blue-900 text-gray block font-medium tooltip cursor-not-allowed" data-tip="You need to provide your Photoprism instance data in the Settings">Import Photoprism data</a>
<% end %>
</div>
<div id="imports" class="min-w-full">

View file

@ -9,25 +9,35 @@ RSpec.describe 'Api::V1::Photos', type: :request do
let(:photo_data) do
[
{
'id' => '123',
'id' => 1,
'latitude' => 35.6762,
'longitude' => 139.6503,
'localDateTime' => '2024-01-01T00:00:00.000Z',
'type' => 'photo'
'originalFileName' => 'photo1.jpg',
'city' => 'Tokyo',
'state' => 'Tokyo',
'country' => 'Japan',
'type' => 'photo',
'source' => 'photoprism'
},
{
'id' => '456',
'id' => 2,
'latitude' => 40.7128,
'longitude' => -74.0060,
'localDateTime' => '2024-01-02T00:00:00.000Z',
'type' => 'photo'
'originalFileName' => 'photo2.jpg',
'city' => 'New York',
'state' => 'New York',
'country' => 'USA',
'type' => 'photo',
'source' => 'immich'
}
]
end
context 'when the request is successful' do
before do
allow_any_instance_of(Immich::RequestPhotos).to receive(:call).and_return(photo_data)
allow_any_instance_of(Photos::Request).to receive(:call).and_return(photo_data)
get '/api/v1/photos', params: { api_key: user.api_key }
end