Сортировка фотографий по данным из EXIF + PHP

?v=1

Хочу поделиться своим опытом сортировки фотографий с помощью скрипта на PHP
Наступает тот момент, когда фотографий становится не много, а катастрофически много.

Предыстория

Решил я в один из дней отсортировать весь свой архив цифровых фото, накопленный за 20 лет, и понял, что всего за это время накопил 112 000 фотографий на 435 гигабайт.

Причем, некоторая часть из них, лежит более менее сортировано, например фотографии с зеркальной камеры, по папкам с названиями, и датами, а другая часть фотографий, которая была импортирована с iphone/android никак не названа и не отсортирована, часто это просто гигантская папка на 10 гигабайт, с парой тысяч файлов внутри, и удалить жалко и сортировать невозможно.

Начал искать инструменты автоматической сортировки и понял, что все хорошие сервисы, вроде Picasa уже купил и закрыл Google, можно конечно все выгрузить к ним на Google.Photos однако там есть проблемы поиск ищет далеко не все и вообще не хватает где-то половины функций от того чтобы было в Picasa, а если вы еще переживаете за то, что ваши фотографии будут распознавать и использовать, то выкладывать в веб это вообще не ваш путь.

В итоге было решено написать небольшой скрипт, который все отсортирует, сначала думал о shell скрипте, однако поняв, что нужен будет EXIF решил вернуться к старому доброму PHP.

Задача №1 — Разложить все файлы по датам

Сначала я пошел самым простым путем, возьмем все файлы, посмотрим дату создания и раскидаем по вложенным путям:

$file_list = $files->getDirContents($config['photos.unsorted']);

foreach ($file_list as $key => $value) {
	moveImageFile($value);
}

function moveImageFile($filename) {
    $dt= new DateTime();
    $dt->setTimestamp(filectime($filename));
    $start_path = $this->config['photos'];

    $year = $start_path."\Year".$dt->format('Y');
    if (!is_dir($year)) mkdir($year);

    $month = $year."\\".$dt->format('Y-m-F');
    if (!is_dir($month)) mkdir($month);

    $path = $month."\\".$dt->format('Y-m-d');
    if (!is_dir($path)) mkdir($path);
}
$full_path = getUniqueFilename($filename, $path, $dt, 0);
copy($filename, $full_path);

Нашлось несколько проблем:

  • Часть файлов имели неправильную дату создания
  • Если делать copy, новый файл создается текущей датой
  • Файлы могут иметь дубли, с одинаковым временем создания

Задача №2 — Получаем дату из Exif


Было решено брать дату из EXIF, для файлов делать rename и touch ставить дату из exif, а также проверять файлы на дубликаты с помощью md5.

В принципе PHP уже имеет в наборе библиотек расширение exif, поэтому ничего сверхъестественного не предвиделось

    $dt = DateTime::createFromFormat('Y:m:d H:i:s', $exif['DateTime']);	
    $start_path = $this->config['photos.exif'];
    $is_exif = true;

    if (md5_file($filename) == md5_file($full_path)) return false;

    rename($filename, $full_path);
    touch($full_path, $dt->getTimestamp());

Все бы хорошо, 500 гигабайт фотографий было отсортировано и очищено от дубликатов за пару часов, но тут я вспомнил о старых папках, которые содержали в себе название региона, где была проведена фотосессия, и подумал, почему бы не получать названия городов из геоданных?

Задача №3 — Страны, города и регионы из геоданных EXIF

Координаты легко найти в файлах, они лежат в Exif в GPSLongitude и GPSLatitude, но нельзя забывать, что хранятся они там в градусах, минутах и секундах, поэтому нужно использовать функции преобразования координат в десятичную систему счисления.

function getGps($exifCoord, $hemi) {
    $degrees = count($exifCoord) > 0 ? $this->gps2Num($exifCoord[0]) : 0;
    $minutes = count($exifCoord) > 1 ? $this->gps2Num($exifCoord[1]) : 0;
    $seconds = count($exifCoord) > 2 ? $this->gps2Num($exifCoord[2]) : 0;

    $flip = ($hemi == 'W' or $hemi == 'S') ? -1 : 1;
    return $flip * ($degrees + $minutes / 60 + $seconds / 3600);
}

Второй вопрос, а что делать с координатами, как получить название города?
На помощь приходит Geocoder от Yandex, но будьте внимательны с лимитами и условиями использования.

$url = "https://geocode-maps.yandex.ru/1.x/";
$apikey = require('../config/apikey.php');

$json = array(
    'geocode' => $lon.",".$lat,
    'kind' => 'locality',
    'apikey' => $apikey,
     'results' =>'1',
    'skip' => '0',
    'format' => 'json'
);

$response = file_get_contents($url."?".http_build_query($json));

Чтобы не убивать Yandex миллионами запросов, кэшируем данные в MySql, округляя координаты до 3х знаков после запятой, то есть 43.161 — 19.182 этого достаточно, чтобы определить город, и тем самым на 110 000 фотографий у меня получилось всего 1500 геометок.

Внешний вид папок примерно такой:

  • D:\photos\photos_exif\Year2019\2019–09-September\2019–09–23-Босния и Герцеговина, Республика Сербская, Фоча\
  • D:\photos\photos_exif\Year2019\2019–08-August\2019–08–25-Албания, область Дуррес, Круя\
  • D:\photos\photos_exif\Year2018\2018–10-October\2018–10–06-Россия, Московская область, Балашиха\

Вместо заключения


На самом деле если взяться за этот продукт, можно делать его месяцами, мне понадобился день, на то, чтобы написать скрипт, и оптимизировать хранение фотографий.

Из планов: добавление геотегов в существующие фотографии, пересортировка текущего архива фотографий, поиск дубликатов среди пережатых изображений.

Все файлы проекта доступны в GitHub: https://github.com/vseznaut/photo-exif-sort

Не бейте меня сильно это первый мой полностью open source проект, если что-то разместил или написал не так, скажите, и да сейчас все заточено под среду исполнения Windows с кодировкой 1251.

© Habrahabr.ru