Image Geotagger | автоматизация гео-меток


Ладожское озеро, Карелия, Россия. Открыть в новом окне.

Долгое время я использовал GPSPhotoLinker для записи гео-меток в фотографии. С недавних пор мне надоело, что эта программа зависит от старых библиотек Perl, которые больше не включены по умолчанию в состав OS X. Обходное решение, которое было предложено на форуме производителя софта, решало проблему в течение следующих двух версий OS X. Тем не менее, GPSPhotoLinker стал регулярно падать с RAF-файлами Fujifilm X100T, а также с большими файлами Canon CR2.

По сути, GPSPhotoLinker использует Exiftool для записи геометок. Поэтому я решил написать свой скрипт, который будет подготавливать командную строку для запуска Exiftool с выбранными файлами.

Geotagger в контекстном меню

Приложение реализовано как сервис OS X запускается из контекстного меню в Finder. Таким образом, внедрить гео-метки в новые или давно забытые старые снимки легко, как никогда – не надо даже запоминать название приложения :)
В принципе, при запуске всё понятно, что делать, тем не менее я прокомментирую каждый этап работы скрипта.

Geotagger: варианты работы

После запуска Geotagger предлагает два варианта: синхронизацию с треками, записанными телефоном или отдельным приёмником GPS, либо ручной ввод координат.

Geotagger: выбор файлов трека

Первый вариант очень прост: всего-то нужно выбрать один или несколько файлов треков (допустим, вы обрабатываете несколько съёмочных дней).

Geotagger: указание разницы во времени

Затем предлагается указать разницу во времени между приёмником GPS (трек) и фотокамерой. Однако лучше выставить правильное время на камере ещё до съёмки.

Geotagger: ручной ввод координат

Второй вариант позволяет ввести координаты вручную, например, для фотографий, сделанных при отключенном приёмнике GPS, либо когда он терял сигнал (пещера или каньон). При первом запуске скрипт подхватит содержимое буфера обмена, а дальше будет предлагать значение, введённое при предыдущем запуске.

Geotagger: выбор страны Geotagger: выбор региона или провинции Geotagger: выбор города Geotagger: выбор сублокации

В обоих случаях после выбора файлов трека или ввода координат вручную, вам будет предложено указать текстовые данные о локации, которые записываются в XMP: страну, регион/штат/провинцию, город и сублокацию. Вы можете проигнорировать любое из полей, и тогда соответствующие данные не будут вписаны в файлы изображений. Это удобно, например, когда снимки сделаны в одной стране, но в разных городах. Опять же, эти данные сохраняются до следующего запуска утилиты.

Для вашего удобства ранее введённые данные сохраняются стандартным для OS X способом – в файле plist здесь:
~/Library/Preferences/com.pozhvanov.geotagger.plist

Запросив необходимые данные у пользователя, Geotagger запускает Exiftool с выбранными файлами, а в конце показывает уведомление со статистикой. В дополнение к этому, скрипт проверяет каждый файл на предмет полноты заполнения географических координат и текстовых метаданных в XMP. Если присутствует и то, и то, файлу проставляется зелёная метка. Если файл изображения содержит только лишь координаты или только текстовые данные о локации, то он маркируется жёлтой или серой меткой соответственно. Таким образом можно легко найти файлы, которые требуют работы в индивидуальном порядке.

Скрипт Image Geotagger можно бесплатно скачать в виде ZIP-архива:

Geotag_images.workflow.zip
642 KB.

Распакуйте архив и сохраните здесь:
~/Library/Services/

Включить и отключить контекстное меню Finder можно здесь:  > System Preferences > Keyboard > Shortcuts > Services. Удачного геотегинга!

Исходный код

Для вашей уверенности и спокойствия, полный код службы Image Geotagger на AppleScript выложен ниже:
property preferences_file : "com.pozhvanov.geotagger.plist"
property preferences_path : "~/Library/Preferences/"
property exiftool_path : "/usr/local/bin/exiftool"
to SelectInputGPX(title, folder_location)
  set InputGPX to choose file with prompt title default location folder_location with multiple selections allowed
  return InputGPX as list
end SelectInputGPX
on replace_chars(this_text, search_string, replacement_string)
  set AppleScript's text
item delimiters
to the search_string
  set the item_list to every text item of this_text
  set AppleScript's text item delimiters to the replacement_string
  set this_text to the item_list as string
  set AppleScript's text item delimiters to ""
  return this_text
end replace_chars
to checkPreferences()
  tell application "System Events"
    if exists file preferences_file of (path to preferences from user domain) then
      return 1
    else
      return 0
    end if
  end tell
end checkPreferences
on run {input, parameters}
  if user locale of (get system info) starts with "ru" then
    set lang to "ru"
    set msg_coord_src to "Выберите источник координат:"
    set msg_coord_src_gpx to "Файл GPX"
    set msg_coord_src_man to "Ввести вручную"
    set msg_select_gpx to "Выберите файл(ы) трека GPX/KML/XML/TCX:"
    set msg_enter_coords to "Введите координаты через пробел (широта долгота высота).
Для ввода координат в западном или южном полушарии используйте знак -.
Значение высоты можно опустить.
Десятичный разделитель – точка.
Чтобы только геокодировать файлы, оставьте поле пустым."
    set msg_enter_coords_title to "Ввод координат вручную"
    set msg_button_next to "Далее"
    set msg_button_cancel to "Отмена"
    set msg_gpx_offset to "Укажите сдвиг времени трека относительно съёмки, если необходимо (HH:MM:SS)"
    set msg_gpx_offset_title to "Сдвиг по времени"
    set msg_enter_country to "Введите название страны:"
    set msg_enter_country_title to "Страна"
    set msg_enter_state to "Введите название региона или штата:"
    set msg_enter_state_title to "Регион/штат/провинция"
    set msg_enter_city to "Введите название города:"
    set msg_enter_city_title to "Город"
    set msg_enter_sublocation to "Введите название локации:"
    set msg_enter_sublocation_title to "Локация"
    set msg_result_1 to "Запись геоданных завершена. Обработано файлов: "
    set msg_result_2 to " Сообщение exiftool: "
  else
    set lang to "en"
    set msg_coord_src to "Select coordinates source:"
    set msg_coord_src_gpx to "GPX file"
    set msg_coord_src_man to "Enter manually"
    set msg_select_gpx to "Select GPX/KML/XML/TCX track file(s):"
    set msg_enter_coords to "Enter coordinates delimited by space (latitude longitude
altitude).
Use - sign to enter coordinates for western or southern hemispheres.
Altitude value could be omitted.
Decimal delimiter is dot.
To only geocode files, leave this field empty."
    set msg_enter_coords_title to "Manual entry of coordinates"
    set msg_button_next to "Next"
    set msg_button_cancel to "Cancel"
    set msg_gpx_offset to "Specify time difference of track to photos, if necessary
(HH:MM:SS)"
    set msg_gpx_offset_title to "Time difference"
    set msg_enter_country to "Enter the Country name:"
    set msg_enter_country_title to "Country"
    set msg_enter_state to "Enter the name of State or Region:"
    set msg_enter_state_title to "State/Region/Province"
    set msg_enter_city to "Enter the City name:"
    set msg_enter_city_title to "City"
    set msg_enter_sublocation to "Enter the Sublocation name:"
    set msg_enter_sublocation_title to "Sublocation"
    set msg_result_1 to "Geodata recording is completed. "
    set msg_result_2 to "
files were processed.
Exiftool output: "
  end if
  set xml_gpx_path to POSIX path of (path to documents folder) as text
  set xml_latitude to ""
  set xml_longitude to ""
  set xml_altitude to ""
  set xml_timeDifference to ""
  set xml_country to ""
  set xml_state to ""
  set xml_city to ""
  set xml_sublocation to ""
  (*  Read preferences from Preferences File  *)
  if checkPreferences() is equal to 1 then
    set the geotag_plist to preferences_path & preferences_file
    tell application "System Events"
      tell property list file geotag_plist
        tell contents
          if exists property list item "gpx_path" then
            set xml_gpx_path to value of property list item "gpx_path"
          end if
          if exists property list item "latitude" then
            set xml_latitude to value of property list item "latitude"
          end if
          if exists property list item "longitude" then
            set xml_longitude to value of property list item "longitude"
          end if
          if exists property list item "altitude" then
            set xml_altitude to value of property list item "altitude"
          end if
          if exists property list item "timeDifference" then
            set xml_timeDifference to value of property list item "timeDifference"
          end if
          if exists property list item "country" then
            set xml_country to value of property list item "country"
          end if
          if exists property list item "state" then
            set xml_state to value of property list item "state"
          end if
          if exists property list item "city" then
            set xml_city to value of property list item "city"
          end if
          if exists property list item "sublocation" then
            set xml_sublocation to value of property list item "sublocation"
          end if
        end tell
      end tell
    end tell
  end if
  set coord_src to display dialog msg_coord_src buttons {msg_coord_src_gpx, msg_coord_src_man} default button msg_coord_src_gpx
  if (button returned of coord_src as string) is equal to msg_coord_src_gpx then
    set gpx_file to SelectInputGPX(msg_select_gpx, xml_gpx_path)
    (*
    if ((time to GMT) / hours > 0) then
      set gpx_offset to "-" & (round
((time to GMT) / hours)) & ":00:00"

    else
      set gpx_offset to "+" & (round
(get (characters 2 thru length of (round (time to GMT))) / hours) &
":00:00")

    end if
    *)
    tell application "Finder"
      set file1 to (item 1 of (gpx_file as list))
      set folder1 to (container of file1) as string
    end tell
    set xml_gpx_path to (POSIX path of folder1) as text
    --    display notification xml_gpx_path
    set gpx_offset to xml_timeDifference
    set gpx_offset to text returned of (display dialog msg_gpx_offset with title msg_gpx_offset_title default answer gpx_offset buttons {msg_button_next, msg_button_cancel} default button msg_button_next cancel button msg_button_cancel)
    set xml_timeDifference to gpx_offset as text
  else
    if (length of xml_latitude < 1 and length of xml_longitude < 1) then
      if (length of (get the clipboard as text)) > 30 then
        set msg_clipboard to ((get characters 1 thru 30 of (get the clipboard as string)) as text)
      else
        set msg_clipboard to get the clipboard as text
      end if
    else
      set msg_clipboard to xml_latitude & " " & xml_longitude & " " & xml_altitude
    end if
    set coord_manual to text returned of (display dialog msg_enter_coords with title msg_enter_coords_title default answer replace_chars(msg_clipboard, ",", "") buttons {msg_button_next, msg_button_cancel} default button msg_button_next cancel button msg_button_cancel)
  end if
  set select_Country to text returned of (display dialog msg_enter_country with title msg_enter_country_title default answer xml_country buttons {msg_button_next, msg_button_cancel} default button msg_button_next cancel button msg_button_cancel)
  set select_State to text returned of (display dialog msg_enter_state with title msg_enter_state_title default answer xml_state buttons {msg_button_next, msg_button_cancel} default button msg_button_next cancel button msg_button_cancel)
  set select_City to text returned of (display dialog msg_enter_city with title msg_enter_city_title default answer xml_city buttons {msg_button_next, msg_button_cancel} default button msg_button_next cancel button msg_button_cancel)
  set select_Sublocation to text returned of (display dialog msg_enter_sublocation with title msg_enter_sublocation_title default answer xml_sublocation buttons {msg_button_next, msg_button_cancel} default button msg_button_next cancel button msg_button_cancel)
  set xmp_Country to select_Country as text
  set xmp_State to select_State as text
  set xmp_City to select_City as text
  set xmp_Sublocation to select_Sublocation as text
  set xml_country to xmp_Country
  set xml_state to xmp_State
  set xml_city to xmp_City
  set xml_sublocation to xmp_Sublocation
  set CommandString to exiftool_path & " -overwrite_original "
  --set CommandString to "exiftool -overwrite_original "
  if (button returned of coord_src as string) is equal to msg_coord_src_gpx then
    if length of gpx_offset as text is greater than 0 then
      set CommandString to (CommandString & "-geosync=\"" & gpx_offset as text) & "\" "
    end if
    if (count of gpx_file) as list is greater than 1 then
      repeat with i in gpx_file as list
        set CommandString to CommandString & "-geotag \"" & POSIX path of (i as string) & "\" "
      end repeat
    else
      set CommandString to CommandString & "-geotag \"" & POSIX path of (gpx_file as string) & "\" "
    end if
  else
    set coord_manual to replace_chars(coord_manual as text, ", ", " ")
    if length of coord_manual > 0 then
      set AppleScript's text item delimiters to " "
      set the coord_list to every text item of coord_manual
      if ((count of coord_list) as list) is greater than 2 then
        set CommandString to (((CommandString & "-exif:GPSLatitude=\"" & (item 1 of coord_list) as text) & "\" -exif:GPSLongitude=\"" & (item 2 of coord_list) as text) & "\" -exif:GPSAltitude=\"" & (item 3 of coord_list) as text) & "\" "
        set xml_latitude to (item 1 of coord_list) as text
        set xml_longitude to (item 2 of coord_list) as text
        set xml_altitude to (item 3 of coord_list) as text
      else
        set CommandString to ((CommandString & "-exif:GPSLatitude=\"" & (item 1 of coord_list) as text) & "\" -exif:GPSLongitude=\"" & (item 2 of coord_list) as text) & "\" "
        set xml_latitude to (item 1 of coord_list) as text
        set xml_longitude to (item 2 of coord_list) as text
      end if
    end if
  end if
  if length of xmp_Country is greater than 0 then
    set CommandString to CommandString & "-xmp:Country=\"" & xmp_Country & "\" "
  end if
  if length of xmp_State is greater than 0 then
    set CommandString to CommandString & "-xmp:State=\"" & xmp_State & "\" "
  end if
  if length of xmp_City is greater than 0 then
    set CommandString to CommandString & "-xmp:City=\"" & xmp_City & "\" "
  end if
  if length of xmp_Sublocation is greater than 0 then
    set CommandString to CommandString & "-xmp:Sublocation=\"" & xmp_Sublocation & "\" "
  end if
  repeat with i in input as list
    set CommandString to CommandString & "\"" & POSIX path of (i as text) & "\" "
  end repeat
  set ScriptResult to do shell script CommandString
  (*  Color label files depending on their geotagging completeness  *)
  repeat with i in input as list
    set counter to 0
    set exifstatus to (do shell script exiftool_path & " -exif:GPSLatitude -exif:GPSLongitude -xmp:Country -xmp:State -xmp:City -xmp:Sublocation \"" & POSIX path of (i as text) & "\"") as text
    if (exifstatus contains "GPS Latitude") and (exifstatus contains "GPS Longitude") then
      set counter to counter + 2
    end if
    if (exifstatus contains "Country") or (exifstatus contains "State") or (exifstatus contains "City") then
      set counter to counter + 1
    end if
    if counter is equal to 3 then
      --green label
      tell application "Finder"
        set thefile to (i as alias)
        set label index of thefile to 6
      end tell
    else if counter is equal to 2 then
      --yellow label
      tell application "Finder"
        set thefile to (i as alias)
        set label index of thefile to 3
      end tell
    else if counter is equal to 1 then
      --grey label
      tell application "Finder"
        set thefile to (i as alias)
        set label index of thefile to 7
      end tell
    end if
  end repeat
  (*  Save preferences in Preferences File  *)
  tell application "System Events"
    set geotag_dictionary to make new property list item with properties {kind:record}
    set geotag_plist to preferences_path & preferences_file
    set this_plist to make new property list file with properties {contents:geotag_dictionary, name:geotag_plist}
    make new property list item at end of property list items of contents of this_plist with properties {kind:string, name:"gpx_path", value:xml_gpx_path}
    make new property list item at end of property list items of contents of this_plist with properties {kind:string, name:"latitude", value:xml_latitude}
    make new property list item at end of property list items of contents of this_plist with properties {kind:string, name:"longitude", value:xml_longitude}
    make new property list item at end of property list items of contents of this_plist with properties {kind:string, name:"altitude", value:xml_altitude}
    make new property list item at end of property list items of contents of this_plist with properties {kind:string, name:"timeDifference", value:xml_timeDifference}
    make new property list item at end of property list items of contents of this_plist with properties {kind:string, name:"country", value:xml_country}
    make new property list item at end of property list items of contents of this_plist with properties {kind:string, name:"state", value:xml_state}
    make new property list item at end of property list items of contents of this_plist with properties {kind:string, name:"city", value:xml_city}
    make new property list item at end of property list items of contents of this_plist with properties {kind:string, name:"sublocation", value:xml_sublocation}
  end tell
  display notification ((msg_result_1 & ((count of input) as list) as text) & msg_result_2 & ScriptResult as text) with title "Geotagger" sound name "Submarine"
  return ScriptResult
end run

Добавить комментарий