Сортировка фотографий по данным из EXIF + PHP
Хочу поделиться своим опытом сортировки фотографий с помощью скрипта на 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.