{"id":49319368,"url":"https://github.com/dmitriy-dorofeev/photo-sorter","last_synced_at":"2026-05-15T20:03:41.632Z","repository":{"id":353988047,"uuid":"1214585399","full_name":"dmitriy-dorofeev/photo-sorter","owner":"dmitriy-dorofeev","description":null,"archived":false,"fork":false,"pushed_at":"2026-05-12T19:50:42.000Z","size":2952,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-12T20:13:05.314Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dmitriy-dorofeev.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":"roadmap.md","authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-04-18T19:21:12.000Z","updated_at":"2026-05-12T19:50:46.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/dmitriy-dorofeev/photo-sorter","commit_stats":null,"previous_names":["dmitriy-dorofeev/photo-sorter"],"tags_count":19,"template":false,"template_full_name":null,"purl":"pkg:github/dmitriy-dorofeev/photo-sorter","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dmitriy-dorofeev%2Fphoto-sorter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dmitriy-dorofeev%2Fphoto-sorter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dmitriy-dorofeev%2Fphoto-sorter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dmitriy-dorofeev%2Fphoto-sorter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dmitriy-dorofeev","download_url":"https://codeload.github.com/dmitriy-dorofeev/photo-sorter/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dmitriy-dorofeev%2Fphoto-sorter/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33077961,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-15T11:35:32.926Z","status":"ssl_error","status_checked_at":"2026-05-15T11:35:31.362Z","response_time":103,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-04-26T17:01:14.036Z","updated_at":"2026-05-15T20:03:41.625Z","avatar_url":"https://github.com/dmitriy-dorofeev.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# photo-sorter\n\nКонсольное TUI-приложение на Go для организации фотографий и видео с разных устройств.\n\n## Что делает\n\n- **Сканирует** исходные папки (iPhone, Android, компьютер и т.д.)\n- **Инкрементальные запуски** — при повторном запуске обрабатывает только новые и изменённые файлы, пропуская уже отсортированные\n- **Находит и пропускает дубликаты** — двухуровневая проверка (размер → xxhash), включая межзапусковую дедупликацию\n- **Определяет дату съёмки** по приоритету:\n  1. **Фото:** EXIF (`DateTimeOriginal`)\n  2. **Видео:** метаданные через `exiftool` (`DateTimeOriginal` → `CreateDate` → `MediaCreateDate`)\n  3. **Парсинг имени файла** — 8 паттернов (iPhone, Android, Screenshot, Signal, WhatsApp, Pixel и др.)\n  4. **`mtime`** (опционально, включается флагом `--use-mtime`)\n  5. **Fallback:** файл попадает в папку `unsorted/`\n- **Копирует** файлы в целевую папку по структуре `YYYY/MM/DD/`\n- **Переименовывает файлы по шаблону** — дата, оригинальное имя, устройство-источник, порядковый номер\n- **Поддерживает Live Photos** — `.HEIC` + `.MOV` с одним basename группируются рядом\n- **Обратная синхронизация метаданных** — если дата определена только по имени или `mtime`, можно записать её в EXIF (`DateTimeOriginal`) через `exiftool`\n- **Безопасно работает** — только копирование, никогда не перемещает\n\n## Требования\n\n- **Go 1.25+**\n- **`exiftool`** (опционально, но рекомендуется для видео)\n\n## Поддерживаемые платформы\n\n- **macOS** — основная целевая платформа (Apple Silicon и Intel)\n- **Linux** — полная поддержка\n- **BSD** — совместимость через `unix.Statfs`\n- **Windows** — требуется адаптация модуля проверки свободного места на диске (используется `golang.org/x/sys/unix`)\n\n### Установка Go\n\n```bash\n# macOS через Homebrew\nbrew install go\n\n# Проверь версию\ngo version\n```\n\n### Установка exiftool\n\n```bash\n# macOS через Homebrew\nbrew install exiftool\n\n# Проверь версию\nexiftool -ver\n```\n\n\u003e Если `exiftool` не установлен, видео всё равно будут обработаны через парсинг имени файла или `mtime`.\n\n## Сборка\n\n### Быстрая сборка (Makefile)\n\n```bash\ngit clone \u003crepo\u003e\ncd photo-sorter\nmake build\n```\n\nРезультат появится в `bin/photo-sorter`. Версия автоматически подставится из текущего git-тега или хеша коммита.\n\n### Вручную через go build\n\n```bash\ngo build -ldflags \"-X main.version=$(git describe --tags --always --dirty)\" -o photo-sorter ./cmd\n```\n\n\u003e Флаг `-ldflags \"-X main.version=...\"` встраивает версию в бинарник. Без него приложение сообщит версию `dev`.\n\n## macOS .app bundle\n\nДля удобства на macOS можно собрать приложение как `.app` bundle с иконкой в Finder.\n\n### Сборка .app\n\n```bash\nmake build-mac-app\n```\n\nРезультат:\n- `bin/Photo Sorter.app/` — готовый bundle\n- `bin/Photo Sorter.app.zip` — zip-архив для распространения\n\n### Иконка\n\nЧтобы у bundle была иконка, положите PNG размером **1024×1024** в `build/macos/icon.png` перед сборкой. Скрипт автоматически сконвертирует её в `.icns`.\n\n### Как запускать\n\n1. Распакуйте `Photo Sorter.app.zip` (двойной клик в Finder).\n2. (Опционально) Перетащите `Photo Sorter.app` в папку `Applications`.\n3. Двойной клик по иконке — откроется **Terminal** и в нём запустится TUI.\n\n\u003e Поскольку photo-sorter — консольное TUI-приложение, `.app` bundle не запускает графический интерфейс напрямую, а открывает Terminal. Это ожидаемое поведение.\n\n### Обновление внутри .app bundle\n\nКоманда `update` работает и из `.app`, но с ограничениями:\n- Обновляется только бинарник внутри `Photo Sorter.app/Contents/MacOS/photo-sorter`.\n- Wrapper, `Info.plist` и иконка **останутся от первоначальной версии** ( updater скачивает сырой бинарник, а не целый `.app` bundle).\n- Если приложение установлено в `/Applications/`, для обновления могут потребоваться права администратора (`sudo`).\n\nПри мажорных обновлениях рекомендуется скачивать новый `.app.zip` вручную.\n\n## Версионирование и релизы\n\nПроект следует [Semantic Versioning](https://semver.org/lang/ru/): `vMAJOR.MINOR.PATCH`.\n\n### Как посмотреть версию\n\n```bash\n./photo-sorter --version\n```\n\n### Автоматическое обновление\n\nПроверить, есть ли новая версия:\n\n```bash\n./photo-sorter --check-update\n```\n\nУстановить последнюю версию прямо из GitHub Releases:\n\n```bash\n./photo-sorter update\n```\n\n\u003e **Примечание:** при обновлении приложение скачивает архив под текущую платформу, распаковывает его и заменяет текущий бинарник. Старая версия сохраняется с суффиксом `.bak` на случай отката. Для сборок из исходников (`dev`) автообновление недоступно.\n\u003e\n\u003e Если приложение запущено из `.app` bundle на macOS, обновляется только бинарник внутри bundle. Wrapper, иконка и `Info.plist` остаются от версии, с которой был скачан `.app.zip`.\n\n### Как выпустить новый релиз\n\nРелизы создаются автоматически при пуше git-тега, начинающегося на `v`:\n\n```bash\n# 1. Обновите CHANGELOG или ROADMAP (по желанию)\n# 2. Создайте и запушьте тег\ngit tag -a v1.0.0 -m \"Release v1.0.0\"\ngit push origin v1.0.0\n```\n\nGitHub Actions запустит [GoReleaser](https://goreleaser.com/), который:\n- соберёт бинарники для **macOS** (Intel + Apple Silicon) и **Linux** (x86_64 + ARM64);\n- упакует их в архивы `.tar.gz`;\n- для **macOS** дополнительно создаст `.app.zip` с иконкой и bundle;\n- сгенерирует файл контрольных сумм `checksums.txt`;\n- создаст страницу Release на GitHub с changelog.\n\n### Где скачать готовые артефакты\n\nГотовые бинарники доступны на странице **Releases** репозитория:\n```\nhttps://github.com/dmitriy-dorofeev/photo-sorter/releases\n```\n\nВыберите последний релиз, скачайте архив под свою платформу и распакуйте:\n\n```bash\n# Пример для macOS Apple Silicon (сырой бинарник)\ncurl -LO https://github.com/dmitriy-dorofeev/photo-sorter/releases/download/v1.0.0/photo-sorter_v1.0.0_Darwin_arm64.tar.gz\ntar -xzf photo-sorter_v1.0.0_Darwin_arm64.tar.gz\n./photo-sorter --version\n```\n\nДля macOS также доступен `.app` bundle с иконкой:\n```bash\n# Скачайте .app.zip и распакуйте в Applications\ncurl -LO https://github.com/dmitriy-dorofeev/photo-sorter/releases/download/v1.0.0/photo-sorter_v1.0.0_macOS_arm64.app.zip\nunzip photo-sorter_v1.0.0_macOS_arm64.app.zip -d /Applications/\n```\n\n\u003e **Windows не поддерживается** в готовых сборках из-за использования системных вызовов `unix.Statfs` (можно собрать вручную с адаптацией).\n\n### Локальная сборка snapshot (без публикации)\n\nЕсли хотите проверить, как будет выглядеть релиз, не публикуя его:\n\n```bash\nmake snapshot\n```\n\nТребуется установленный [GoReleaser](https://goreleaser.com/install/). Артефакты появятся в папке `dist/`.\n\n## Тестирование\n\n```bash\n# Запуск всех тестов\ngo test ./...\n\n# Запуск с подробным выводом\ngo test -v ./...\n```\n\n## Запуск\n\nПриложение работает в двух режимах: интерактивном **TUI** и консольном **CLI**.\n\n### TUI-режим (интерактивный)\n\nПросто запустите бинарник без аргументов:\n\n```bash\n./photo-sorter\n```\n\nОткроется интерфейс с 6 экранами:\n1. **Выбор источника** — браузер папок. Выберите папку с фотографиями (`Пробел`), затем `→`.\n2. **Выбор цели** — выберите папку, куда скопировать отсортированные файлы.\n3. **Настройки** — шаблон папок (`YYYY/MM/DD` и др.), шаблон имён файлов, опции Live Photos, видео, mtime, уведомления.\n4. **Сканирование** — прогресс обработки файлов.\n5. **Предпросмотр** — дерево целевой структуры, список дублей и файлов без даты.\n6. **Копирование** — прогресс копирования файлов.\n\n**Навигация:** `↑/↓` — курсор, `Enter` — открыть/подтвердить, `Backspace` — вверх по папкам, `←/→` — назад/вперёд по экранам, `Esc` — выход.\n\n### CLI-режим (консольный)\n\nДля запуска из терминала без интерактивного интерфейса укажите `--source` и `--target`:\n\n```bash\n# Пробный прогон\n./photo-sorter --source ~/Photos/iPhone --target /Volumes/ExternalDisk --dry-run\n\n# Несколько источников\n./photo-sorter --source ~/Photos/iPhone --source ~/Photos/Android --target /Volumes/ExternalDisk\n\n# Реальное копирование\n./photo-sorter --source ~/Photos --target ~/Sorted --dry-run=false\n\n# Записать дату в EXIF (если дата взята из имени/mtime)\n./photo-sorter --source ~/Photos --target ~/Sorted --write-exif --dry-run=false\n\n# JSON-отчёт вместо текстового\n./photo-sorter --source ~/Photos --target ~/Sorted --format=json\n\n# Свой шаблон имён файлов\n./photo-sorter --source ~/Photos --target ~/Sorted --name-template \"{YYYY}-{MM}-{DD}_{original}{ext}\"\n\n# Посмотреть все флаги и примеры\n./photo-sorter --help\n```\n\n**Основные флаги:**\n- `--source` — исходная папка (можно указать несколько раз)\n- `--target` — целевая папка\n- `--template` — шаблон папок (default: `2006-01-02`)\n- `--name-template` — шаблон имён файлов (default: `{original}{ext}`)\n- `--live-photos` — группировать Live Photos (default: `true`)\n- `--include-video` — обрабатывать видео (default: `true`)\n- `--dry-run` — пробный прогон (default: `true`)\n- `--use-mtime` — fallback на дату изменения (default: `true`)\n- `--write-exif` — записывать дату в EXIF, если она взята из имени/mtime (default: `false`)\n- `--notify` — показать системное уведомление по завершении (default: `true`)\n- `--full-check` — игнорировать state, пересортировать все файлы (default: `false`)\n- `--reset-state` — удалить файл состояния перед запуском (default: `false`)\n- `--format` — формат отчёта: `text` или `json` (default: `text`)\n- `--version` — показать версию приложения и выйти\n- `--check-update` — проверить наличие новой версии на GitHub\n\n### Первый запуск на реальных данных\n\n1. **Всегда начинайте с пробного прогона:**\n   ```bash\n   ./photo-sorter --source ~/Photos --target /Volumes/ExternalDisk --dry-run\n   ```\n2. **Проверьте отчёт:** убедитесь, что даты определены правильно, дубли корректны.\n3. **Запустите реальное копирование:**\n   ```bash\n   ./photo-sorter --source ~/Photos --target /Volumes/ExternalDisk --dry-run=false\n   ```\n4. **Проверьте результат** — файлы скопированы, оригиналы на месте.\n\n### Инкрементальные запуски\n\nПо умолчанию `photo-sorter` запоминает, какие файлы уже были обработаны, и при повторном запуске на те же `source → target` пропускает неизменившиеся файлы. Это ускоряет повторные прогоны на больших коллекциях.\n\n- Состояние хранится в скрытой папке `\u003ctarget\u003e/.photo-sorter/state.bolt` (BoltDB).\n- Повторный запуск обрабатывает только новые и изменённые файлы (проверка по размеру и времени модификации).\n- Если добавленный файл является дубликатом уже скопированного — он будет распознан и пропущен.\n\n**Управление:**\n\n```bash\n# Обычный инкрементальный запуск (только новые/изменённые файлы)\n./photo-sorter --source ~/Photos --target ~/Sorted --dry-run=false\n\n# Полный пересорт всех файлов (игнорировать state, но обновить его после)\n./photo-sorter --source ~/Photos --target ~/Sorted --full-check --dry-run=false\n\n# Полный сброс — удалить state и пересортировать всё с нуля\n./photo-sorter --source ~/Photos --target ~/Sorted --reset-state --dry-run=false\n```\n\n\u003e **Примечание:** `--dry-run` не изменяет state-файл.\n\n### Шаблоны имён файлов\n\nПо умолчанию файлы сохраняют оригинальные имена (`{original}{ext}`). Вы можете задать свой шаблон через `--name-template` или в TUI (шаг 3).\n\n**Доступные плейсхолдеры:**\n\n| Плейсхолдер | Описание | Пример |\n|-------------|----------|--------|\n| `{YYYY}` | Год (4 цифры) | `2024` |\n| `{YY}` | Год (2 цифры) | `24` |\n| `{MM}` | Месяц | `03` |\n| `{DD}` | День | `15` |\n| `{HH}` | Часы | `14` |\n| `{mm}` | Минуты | `30` |\n| `{SS}` | Секунды | `22` |\n| `{original}` | Имя файла без расширения | `IMG_1234` |\n| `{ext}` | Расширение с точкой (сохраняет регистр) | `.jpg` |\n| `{device}` | Устройство-источник (iPhone, Pixel, Screenshot и др.) | `iPhone` |\n| `{seq}` | Порядковый номер в папке | `1` |\n| `{seq:03}` | Порядковый номер с ведущими нулями | `001` |\n\n**Примеры шаблонов:**\n\n```bash\n# Оригинальное имя (по умолчанию)\n--name-template \"{original}{ext}\"\n\n# Дата + оригинальное имя\n--name-template \"{YYYY}-{MM}-{DD}_{original}{ext}\"\n\n# Дата-время + устройство\n--name-template \"{YYYY}-{MM}-{DD}_{HH}-{mm}-{SS}_{device}{ext}\"\n\n# Компактная дата-время\n--name-template \"{YYYY}{MM}{DD}_{HH}{mm}{SS}{ext}\"\n\n# Хронологический счётчик по папкам\n--name-template \"{YYYY}{MM}{DD}_{seq:03}{ext}\"\n```\n\n\u003e Файлы без даты (`unsorted/`) получают `0000-00-00_00-00-00` вместо плейсхолдеров даты.\n\n## Безопасность\n\n- Приложение **только копирует**, никогда не перемещает.\n- В TUI сначала посмотрите предпросмотр (шаг 5), затем запускайте копирование.\n- В CLI используйте `--dry-run` перед реальным запуском.\n- Файлы без распознаваемой даты попадают в папку `unsorted/`.\n- При потере диска во время копирования приложение прерывает работу и сохраняет лог прогресса.\n\n## Поддерживаемые форматы\n\n- **Фото:** `.jpg`, `.jpeg`, `.png`, `.heic`\n- **Видео:** `.mov`, `.mp4`\n- **Другие:** любые файлы, если включён `--include-video` или файл попадает под фильтр расширений\n\n## Структура проекта\n\n```\nphoto-sorter/\n├── cmd/\n│   └── main.go              # точка входа (TUI по умолчанию, CLI через флаги)\n├── internal/\n│   ├── scanner/             # рекурсивный обход папок с фильтрацией\n│   ├── dateresolver/        # движок дат: EXIF, видео-метаданные, парсинг имён, mtime\n│   ├── deduper/             # поиск дубликатов (размер → xxhash)\n│   ├── sorter/              # построение целевого дерева папок\n│   ├── copier/              # безопасное копирование с проверкой диска\n│   ├── logger/              # запись логов каждого запуска\n│   ├── notify/              # системные уведомления (macOS / Linux)\n│   ├── runner/              # единый pipeline для TUI и CLI\n│   └── updater/             # проверка и установка обновлений\n├── tui/                     # интерфейс на bubbletea (6 экранов)\n└── testdata/                # тестовые файлы\n```\n\n## Лицензия\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdmitriy-dorofeev%2Fphoto-sorter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdmitriy-dorofeev%2Fphoto-sorter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdmitriy-dorofeev%2Fphoto-sorter/lists"}