Держи. Постановка готова: dHash и порог в процентах
Постановка. Цель: dupes --similar <ПАПКА> находит одинаковые фото разного размера и качества по перцептивному хешу. Строгость сравнения задаётся флагом --similarity <ПРОЦЕНТ> (по умолчанию 90). Отчёт показывает группы похожих фото и сколько места освободится, если в каждой группе оставить фото в лучшем качестве.
План. Добавить зависимость image (только JPEG, PNG и WebP), отдельный крейт хеша не подключать. Новые модули: перцептивный хеш dHash (картинка в оттенках серого сжимается до 9 на 8, бит равен 1, если пиксель темнее правого соседа, расстояние Хэмминга по 64 битам); чтение фото с диска (формат по содержимому, лимиты памяти image по умолчанию); поиск групп. Группировка идёт по связным компонентам: ребро между фото есть, если расстояние не больше допустимого для порога; одиночки отбрасываются. Лучшее фото в группе — с наибольшим разрешением, затем с большим размером файла, затем по пути. Группы идут по освобождаемому месту. В причины пропуска добавляются повреждённое изображение, неподдерживаемое изображение и слишком большое изображение, причём разбор ошибок image исчерпывающий. В разборе аргументов появляются флаги --similar и --similarity с русской справкой и русскими сообщениями об ошибках: неверное число, флаг без значения, --similarity без --similar (код 2). В отчёте старая функция переименовывается, добавляется отчёт по похожим фото: у группы лучшее фото идёт первым с пометкой «лучшее качество», у каждого указаны разрешение и размер; если групп нет, выводится «Похожих фото не найдено.». В главной функции выбор режима исчерпывающий. Решения записываются в журнал решений.
Принятые решения. Свой dHash вместо image_hasher: тот тянет 43 крейта против 22, чистая сборка 18 секунд против 11, а свой алгоритм не изменится от обновления чужой библиотеки. Форматы только JPEG, PNG и WebP. Фото отбираются по расширению, не-картинки пропускаются молча, а битый файл или слишком большая картинка дают предупреждение, код 0. Порог в процентах совпадающих битов, от 50 до 100, по умолчанию 90 (до 6 различающихся битов из 64); замеры: уменьшенная копия, WebP и JPEG низкого качества — расстояние 0–1, картинка с небольшой меткой — 4, другой рисунок — 39. Неверный порог и порог без --similar дают понятную ошибку с кодом 2. --similar — отдельный режим, точные копии картинок попадают в группы. Оставляемым считается фото лучшего качества; группа хранит лучшее и остальные отдельно, чтобы задача 3 взяла остальные, а какое правило применять при очистке похожих фото, решится в задаче 3.
Критерии готовности: make check зелёный и укладывается в лимит хука 180 секунд; исходная PNG, уменьшенная JPEG-копия, JPEG низкого качества и WebP-копия образуют одну группу из четырёх фото, непохожий рисунок в неё не входит; без похожих фото выводится ровно «Похожих фото не найдено.»; картинка с небольшой меткой группируется по умолчанию, но не при --similarity 100; неверные значения порога, флаг без значения и порог без --similar дают код 2 с пояснением диапазона; повреждённый файл с расширением картинки даёт предупреждение, код 0; два запуска дают одинаковый вывод; справка полностью по-русски, режим без --similar работает как раньше.
Тесты используют картинки, сгенерированные в тесте (градиенты, круг, прямоугольник) во временных каталогах: хеш и расстояния, порог, группировка и выбор лучшего, поддерживаемые расширения, чтение повреждённых файлов, сообщения разбора аргументов, тексты отчёта, интеграционные сценарии и справка.
Вне задачи: --trash, --json, README, версия 0.1.0; параллельное декодирование, прогресс, ускорение перебора пар, поворот по EXIF, HEIC, GIF, TIFF, RAW, прозрачность PNG; совместный вывод дубликатов и похожих фото; свои ограничения памяти. Вопросов нет.