Починаючи з липня 2021 року, Google Фото більше не пропонуватиме необмежене зберігання фотографій і відео. Власне, я ніколи не покладався цілком на цю службу і керував своєю колекцією медіа файлів у GNOME Shotwell. Чому б не просунутися далі і не дозволити мережевий доступ з портативних пристроїв? Це могло б покращити доступність колекції світлин і майже повністю замінити Google Фото.

Необхідні такі складники:

  • Rapberry Pi 4 в якості веб-сервера
  • Сховище даних через вихід USB з колекцією медіа файлів
  • Tinc VPN, налаштований на сервері та кожному пристрої з доступом до галереї
  • PiGallery2 чи будь-яка інша веб програма-оглядач галереї
  • Спосіб виділення корисної інформації з бази даних Shotwell, щоб представити галерею в зручному для користувача вигляді
  • Може також стати в нагоді доступ до файлів через NFC, щоб можна було керувати всією колекцією з допомогою Shotwell на віддалі.

Shotwell зберігає свої метадані у базі даних Sqlite. Нам потрібно згрупувати світлини і відео по подіях, так як вони впорядковані у програмі. То чому б не відобразити ці зв’язки у вигляді ієрархії директорій у файловій системі, скориставшись символічними посиланнями до фізичних файлів?

#!/usr/bin/env python

"""Створити ієрархію світлин, використовуючи інформацію з Shotwell.

Буде створено символічні посилання до фізичних файлів у директоріях, що
відповідають описам подій. Таку колекцію фото і відео буде зручно
відкривати і переглядати з допомогою PiGallery2
(https://github.com/bpatrik/PiGallery2).
"""

import sqlite3
from datetime import datetime
import pathlib
import os
import shutil


QUERY = """
SELECT f.filename, f.exposure_time, e.id, e.name FROM
(SELECT p.filename, p.exposure_time, p.event_id FROM PhotoTable p
UNION
SELECT v.filename, v.exposure_time, v.event_id FROM VideoTable v) AS f
JOIN EventTable e ON f.event_id = e.id
ORDER BY f.exposure_time;
"""

PHOTO_DB = "/home/sakhnik/Pictures/shotwell/data/photo.db"
ROOT = "/home/sakhnik/Pictures2"

# Видалити старе представлення
shutil.rmtree(ROOT)

events = {}  # eid -> path

with sqlite3.connect(PHOTO_DB) as conn:
    c = conn.cursor()
    for (fname, timestamp, eid, ename) in c.execute(QUERY):
        # print(fname, timestamp, eid, ename)
        dir_path = events.get(eid)
        if not dir_path:
            dt = datetime.fromtimestamp(timestamp)
            if ename:
                dir_path = f"{ROOT}/{dt.year}/{dt.year}-{dt.month:02d}-{dt.day:02d} {ename}"
            else:
                dir_path = f"{ROOT}/{dt.year}/{dt.year}-{dt.month:02d}-{dt.day:02d}"
            events[eid] = dir_path
            pathlib.Path(dir_path).mkdir(parents=True, exist_ok=True)
        try:
            os.symlink(fname, f"{dir_path}/{timestamp}_{os.path.basename(fname)}")
        except Exception as e:
            print(e)

Ось зразок заповненої таким чином директорії:

/home/sakhnik/Pictures2/
├── 1995
│   ├── 1995-05-25
│   │   ├── 801385896_scan0012.jpg -> /home/sakhnik/Pictures/1990/1995_05_25/scan0012.jpg
│   │   └── 801385912_scan0013.jpg -> /home/sakhnik/Pictures/1990/1995_05_25/scan0013.jpg
│   └── 1995-09-01 УФМЛ КНУ 9-Б клас
│       ├── 809941402_Untitled.jpg -> /home/sakhnik/Pictures/1990/1995_09_01/Untitled.jpg
│       └── 809982959_1995-9B.jpg -> /home/sakhnik/Pictures/1990/1995_09_04/1995-9B.jpg
...
├── 2009
│   ├── 2009-01-04 Лижний біг
│   │   ├── 1231057698_imgp3745.jpg -> /home/sakhnik/Pictures/2009/2009_01_04-Winter/imgp3745.jpg
│   │   ├── 1231057703_imgp3746.jpg -> /home/sakhnik/Pictures/2009/2009_01_04-Winter/imgp3746.jpg
│   │   ├── 1231060514_imgp3749.jpg -> /home/sakhnik/Pictures/2009/2009_01_04-Winter/imgp3749.jpg
│   │   ├── 1231061012_imgp3751.jpg -> /home/sakhnik/Pictures/2009/2009_01_04-Winter/imgp3751.jpg

А ось результат на екрані мобільного телефону:

PiGallery2 on the mobile screen