Skip to content

Development

Bertrand Boutillier edited this page Oct 7, 2026 · 4 revisions

Architecture and development

Français | English

This page describes the concepts needed to contribute to Photo Album v0.3.0. The application workflow is in the User guide; extension contracts are in Creating template packs.

System boundaries

Area Responsibility
Models and database Photo identities, candidates and effective values; SQLite storage and repositories.
Sources, scanning and geocoding Local and Synology adapters, import, metadata, image materialization and location resolution.
Album Persisted settings, validation, chronological planning, pagination, composition and diagnostics.
Template engine Discovery, activation, public API, translations and preview contracts.
Packs Template-specific settings, layouts and painting.
Rendering and export Shared painting target, image caches, physical geometry and PDF.
Project service and GUI Orchestration, open session, controls and background work.

Provider-specific networking stays in photoalbum.sources. Providers implement PhotoSource for collections, assets, thumbnails and originals without depending on the GUI or rendering. Original images are never rewritten.

Storage and identity

A .photoalbum file is a SQLite database using schema 5. It contains ordered sources, photos, metadata candidates, manual and editorial corrections, the geocoding cache, album settings, project name and PDF preferences. Source configuration JSON uses schema 2; album settings JSON uses schema 3. These schemas are independent.

Photo.identity combines source_id and asset_id. Use this key for persistent selections, editorial directives and deduplication. Two sources may expose the same path without identifying the same photo. Path-based convenience operations reject ambiguous matches.

This stability does not imply automatic tracking of moved files: the local adapter uses the absolute path as asset_id. For remote sources, a local path is only the materialization of the asset.

The adjacent remote cache .<project-filename>.cache/assets/ derives paths from identity. A thumbnail initially occupies the canonical path; an original replaces it at the same path and receives a marker. Provider revisions allow that marker to be invalidated. Provider sessions and secrets remain in memory in ProjectService until the project closes.

Values and photo pools

The model separately retains EXIF, filename and provider candidates; policy-selected effective values; prioritized manual corrections; then editorial location text, caption and usage. Synchronization updates candidates without overwriting these decisions.

Filtering is centralized in the service:

Access Contents
list_photos() Present photos from active sources, including OFF.
list_album_photos() The same pool without OFF: BODY and TEMPLATE_ONLY.
list_body_photos() BODY photos only.

The chronological plan then requires an effective date. The template pool may therefore contain photos absent from the body, including undated photos. AlbumBuildResult.template_photos carries this canonical pool to selectors, preview, PDF and original preparation. Do not reconstruct it from photo pages alone.

Remote import creates an explicit snapshot. Local scanning and metadata refresh are separate from that import; policies and the corresponding commands are documented in Photo sources.

Building and paginating

AlbumStructureSettings is the persisted configuration. AlbumBuilder validates instances and dimensions, filters incompatible optional pages, builds the semantic plan and runs pagination and print diagnostics. Composition through PageComposer and caption diagnostics then complete the result for the views.

The semantic plan describes chronological content; pagination.pages describes the effective physical sequence, including sides and blanks. The four covers are handled separately. An inserted page can be present in pagination without appearing in plan.items: preview prewarming must traverse effective pagination.

PhotoPageOverride(photo_identity, page) and BodyPageInsertion(anchor, page, enabled) store content boundaries, never physical page numbers. Application, disabling and compatibility rules are in Advanced usage. Storage preserves instance IDs, options and dormant directives.

End-of-month spare capacity comes from the last photo page of the actual month and eligible trailing technical blanks. Intentional cuts and inline special pages do not artificially become month ends.

Rendering, preview and caches

The host resolves paper settings into millimetres before validation, editors and rendering. Packs receive that geometry; they do not infer orientation from a paper name.

Plan, Preview and PDF consume the same built sequence. PageRenderer.paint_template() shares painting dispatch, but a preview is not a raster of the final PDF: screen size, thumbnails, raster caching and original availability differ. PDF provides a QPainter target and renders synchronously without a preview service.

PreviewRenderService shares expensive calculations between editors and preview. PreviewRenderKey includes template ID, instance ID, settings and photo signatures, raster size and physical dimensions. The canonical raster preserves the physical aspect ratio with a 594-pixel long edge.

The backend defines its effective photos, deduplicated by identity and sorted, and the state affecting its result. The photo signature includes identity, file modification and photo_signature(); the latter defaults to filename, date, dimensions and orientation. A backend depending on GPS must add it. render_settings_signature() must include shared settings that affect the calculation.

A lightweight layer can be painted over the cached background. For example, the MSB scatter excludes title typography from its background signature, but not seed or geometry. Workers produce bytes; conversion to QPixmap takes place on the GUI thread.

GUI and concurrency

MainWindow coordinates tabs and the album result. PhotoSourcesWidget owns controls and the table; ScanController orchestrates scans; PhotoEditor handles corrections and geocoding; PdfExportWidget drives export. Widgets receive dependencies and emit signals without accessing their Qt parent's private fields.

Owners retain workers until their threads finish. Database work uses thread-appropriate connections. Place and caption corrections are persisted immediately, with rebuilding deferred until leaving the tab or exporting. Album settings trigger a rebuild after a committed change.

Loading settings emits no change. A format action updates compatible choices in one UI transaction and emits a single consolidated change.

Environment and tests

Prepare the Python environment described in Installation, then install contributor dependencies:

python -m pip install -e '.[dev]'
python -m pytest -q

On headless Linux:

QT_QPA_PLATFORM=offscreen python -m pytest -q

Code lives under src/photoalbum/, tests under tests/. Tests cover storage, sources, policies, pagination, composition, GUI, rendering, translations and distribution data inclusion. Provider tests with simulated responses do not guarantee connectivity to every real NAS.

Test a change at the boundary owning its behavior, then the affected consumers. For a pack, check composition, preview and PDF together. General translations are in photoalbum/i18n/translator.py; pack catalogs and help stay in their own directories. Maintain French and English together.

Clone this wiki locally