diff --git a/AGENT.md b/AGENT.md index c8f1e590e..fbf59544a 100644 --- a/AGENT.md +++ b/AGENT.md @@ -1,53 +1,57 @@ # AGENT.md - iPhotron Development Principles -This file is the working guide for coding agents and contributors. It reflects -the current vNext state: the production runtime has converged on -`RuntimeContext -> LibrarySession -> application ports/services`, and the -legacy compatibility application tree has been removed. +This file is the authoritative working guide for coding agents and contributors. +It reflects the current production state: runtime composition converges on +`RuntimeContext -> LibrarySession -> application ports/services`, the desktop +coordinator graph is owned by `DesktopCoordinatorRuntime`, and the removed +legacy application tree is not a production extension point. ## 1. Current Architecture Status -- The vNext cleanup is complete for production source code. - Production runtime code must not import `iPhoto.legacy` or `iPhoto.models.*`. - The removed `src/iPhoto/legacy/` application tree must not be restored. - Historical behavior that remains a product requirement must be tested - through current application, session, domain, or infrastructure surfaces. -- GUI, CLI, file watchers, Qt workers, and future automation entry points must - enter library behavior through `RuntimeContext`, `LibrarySession`, and - application-level surfaces. +- GUI, CLI, file watchers, Qt workers, and future automation entry points enter + library behavior through `RuntimeContext`, `LibrarySession`, and application + surfaces. - New business logic belongs in application use cases/services, session surfaces, domain values/pure services, or infrastructure adapters. GUI code - is presentation and Qt transport only. + is presentation and Qt transport. +- Desktop GUI composition is owned by + `iPhoto.gui.coordinators.desktop_coordinator_runtime.DesktopCoordinatorRuntime`. + `gui/coordinators/main_coordinator.py` is a compatibility import only; do not + document or introduce a separate production `MainCoordinator` contract. +- Recognition is feature-driven. Application startup may warm cached People/Pets + dashboard data, but it must not start model inference. `RecognitionCoordinator` + requests recognition scans only after the People surface has been shown and + its first viewport is ready; activation is deliberately delayed so first + content delivery is not competing with model work. - Gallery-to-Detail still rendering uses one GPU-first production path: a - render transaction owns generation and terminal state, viewport-aware - neutral surfaces feed bounded disk/CPU/GPU caches, and Detail/Edit share one - render session. The removed Detail v2 frame cache and still Edit CPU-preview - path are not extension points. + render transaction owns generation and terminal state, viewport-aware neutral + surfaces feed bounded disk/CPU/GPU caches, and Detail/Edit share one render + session. -The authoritative current architecture is tracked in `docs/architecture.md`. -Completed vNext migration records are archived under -`docs/finished/refactor/vnext-2026-06/`. +The current architecture entry point is `docs/architecture.md`. Completed vNext +migration records live under `docs/finished/refactor/vnext-2026-06/`. ## 2. Product Invariants - **Folder-native library.** A folder is an album. Users can browse folders without an import step. -- **Local-first.** Core library, browsing, editing, Live Photo, People, and Maps - behavior is local. Optional runtimes must degrade gracefully when unavailable. +- **Local-first.** Core library, browsing, editing, Live Photo, People, Pets, + and Maps behavior is local. Optional runtimes degrade gracefully when absent. - **Non-destructive editing.** Visual edits are stored in `.ipo` sidecars. Original media is not overwritten by normal editing. -- **Explicit metadata write-back only.** Assign Location is the explicit - exception: it persists the location locally first, then best-effort writes GPS - metadata to the original file through ExifTool and reports warnings on - failure. +- **Explicit metadata write-back only.** Assign Location persists local state + first, then best-effort writes GPS metadata through ExifTool and reports + warnings on failure. - **Rebuildable facts vs durable choices.** Scan facts, thumbnails, Live Photo - materialization, and People runtime snapshots can be rebuilt. Favorites, - hidden/trash state, pinned items, album order, manual metadata, People names, - covers, groups, group order, hidden flags, and manual faces must survive - rescans and rebuilds. + materialization, People runtime snapshots, and Pets runtime snapshots can be + rebuilt. Favorites, hidden/trash state, pinned items, album order, manual + metadata, People names/groups/manual faces/covers, and Pets names/covers/ + hidden/rejected decisions must survive rescans and rebuilds. - **Cross-platform desktop first.** macOS, Windows, and Linux remain supported. - Platform-specific rendering, maps, ExifTool, FFmpeg, and AI behavior must be - isolated behind adapters or runtime discovery. + Platform-specific rendering, maps, ExifTool, FFmpeg, and AI behavior stay + behind adapters or runtime discovery. ## 3. Runtime And Layering Rules @@ -64,24 +68,27 @@ Forbidden directions: ```text domain -> application/gui/infrastructure application -> gui/concrete cache/concrete infrastructure -infrastructure/cache/core/io/library/people -> gui +infrastructure/cache/core/io/library/people/pets -> gui production runtime -> iPhoto.legacy production runtime -> iPhoto.models.* ``` Key runtime objects: -- `RuntimeContext`: process composition root, current settings/theme/recent - libraries, active `LibrarySession` lifecycle. +- `RuntimeContext`: process composition root, settings/theme/recent libraries, + active `LibrarySession` lifecycle. - `LibrarySession`: library-scoped adapters and surfaces for assets, state, - scanning, album metadata, People, Maps, thumbnails, edit sidecars, location, - asset lifecycle, and file operations. -- `LibraryRuntimeController`: GUI/runtime controller bound to the active - session; it should not re-create standalone compatibility services. - -The removed compatibility tree is not a production extension point. Do not -restore `src/iPhoto/legacy/`; migrate any still-required behavior to current -application/session surfaces instead. + scanning, album metadata, People, Pets, Maps, thumbnails, edit sidecars, + location, asset lifecycle, and file operations. +- `LibraryRuntimeController`: GUI/runtime controller bound to the active session; + it does not recreate standalone compatibility services. +- `DesktopCoordinatorRuntime`: desktop presentation composition root. It owns the + coordinator graph and late feature promotion/binding, not durable business + state. +- `RecognitionCoordinator`: lazy recognition presentation/service coordinator. + It may warm persisted dashboard snapshots without loading inference models, + and activates scans only after People is actually used and its first viewport + has rendered. ## 4. Files And State @@ -96,156 +103,194 @@ Library workspace: ```text //.iPhoto/ - global_index.db # SQLite index and current asset/state repository store + global_index.db # asset index; includes independent face_status/pet_status links.json # Live Photo compatibility materialization - cache/thumbs/ # Rebuildable thumbnail cache - cache/detail-surfaces/v3/ # SQLite-indexed rebuildable neutral RGBA8/sRGB surfaces + cache/thumbs/ # rebuildable thumbnail cache + cache/detail-surfaces/v3/ faces/ - face_index.db # Rebuildable People runtime snapshot - face_state.db # Durable People user decisions - thumbnails/ # Rebuildable cropped face thumbnails - manifest.bak/ # Manifest/links backup area - locks/ # File-level locks for JSON sidecars + face_index.db # rebuildable People runtime snapshot + face_state.db # durable People user decisions + thumbnails/ # rebuildable face crops + pets/ + pet_index.db # rebuildable Pets detections/identity snapshot + pet_state.db # durable Pets user decisions + thumbnails/ # rebuildable pet crops except referenced covers + manifest.bak/ + locks/ ``` State rules: - `global_index.db` is the current source of truth for asset scan rows, - pagination, Live Photo roles, trash/favorite/hidden flags, face scan status, - and the repository-backed user-state boundary. -- Large-library gallery reads are SQL-first and windowed through collection - query APIs. Normal visible rows must be thumbnail-ready and carry a - `thumb_cache_key`. + pagination, Live Photo roles, trash/favorite/hidden flags, independent + `face_status`/`pet_status`, and repository-backed user-state boundaries. +- Large-library Gallery reads are SQL-first and windowed. Normal visible rows + must be thumbnail-ready and carry a `thumb_cache_key`. - Gallery paint/model access is memory-only. Sparse rows load asynchronously; - viewport generations reject stale window and thumbnail results, and visible, - guard, and far-speculative thumbnail lanes keep separate capacity. -- `links.json` is derived compatibility materialization for Live Photo payloads; - target runtime behavior should read roles through repository/session surfaces. -- `cache/thumbs/` and People thumbnails are disposable. -- `faces/face_index.db` is rebuildable; `faces/face_state.db` is durable. + generation checks reject stale window and thumbnail results. +- `links.json` is derived compatibility materialization; runtime behavior reads + Live Photo roles through repository/session surfaces. +- `faces/face_index.db` and `pets/pet_index.db` are rebuildable. +- `faces/face_state.db` and `pets/pet_state.db` contain durable choices and must + not be replaced by a runtime rebuild. - `.ipo` sidecars are the durable source of non-destructive edit parameters. -- Scan merge must be idempotent and must not implicitly clear durable user - state. +- Scan merge is idempotent and must not implicitly clear durable user state. ## 5. Module Responsibilities -- `bootstrap/`: `RuntimeContext`, `LibrarySession`, and session-bound services - that wire application behavior to the current library root. -- `application/ports/`: public application boundary protocols, including - `AssetRepositoryPort`, `LibraryStateRepositoryPort`, `MediaScannerPort`, - `PeopleIndexPort`, `MapRuntimePort`, `EditSidecarPort`, +- `bootstrap/`: `RuntimeContext`, `LibrarySession`, and session-bound services. +- `application/ports/`: public boundaries such as `AssetRepositoryPort`, + `LibraryStateRepositoryPort`, `MediaScannerPort`, `PeopleIndexPort`, + `PetIndexPort`, `MapRuntimePort`, `EditSidecarPort`, `LocationAssetServicePort`, and `MapInteractionServicePort`. -- `application/use_cases/`: owning use cases for workflows such as scanning. -- `application/services/`: application-level services for album manifests, - pinned state, location queries, map interaction, and explicit location - assignment. -- `domain/`: dataclasses, value objects, query models, and pure domain services. - Domain code must not perform IO or import Qt/SQLite/runtime adapters. -- `infrastructure/`: concrete adapters for SQLite-backed state, manifests, - `.ipo` sidecars, ExifTool, FFmpeg, maps runtime discovery, thumbnail caches, - filesystem scanning, and runtime services. -- `cache/index_store/`: current SQLite global index implementation used behind - repository/session surfaces. GUI and application code must not bypass the - session boundary to call it directly. -- `gui/`: PySide6 views, widgets, controllers, viewmodels, coordinators, menus, - Qt task/signal adapters, and the Detail transaction/scheduler/cache/session - boundary. It owns presentation and GPU residency state, not durable workflow - rules. -- `library/`: runtime controller, tree/watch/scan coordination, trash and album - filesystem shell code bound to session services. -- `people/`: optional People runtime, scan coordination, repositories, manual - faces, stable People state, groups, covers, hidden flags, and service API. -- `maps/`: optional offline Maps runtime, tile parsing, OBF/native widget/helper - integration, search, and map rendering internals. -- `core/`: pure or rendering-oriented algorithms for Live Photo pairing, - adjustment math, geometry, export transforms, filters, and raw loading. +- `application/use_cases/`: owning workflow use cases such as scanning. +- `application/services/`: album manifests, pinned state, location queries, map + interaction, explicit location assignment, and other application workflows. +- `domain/`: dataclasses, value objects, query models, and pure domain services; + no Qt, SQLite, runtime singletons, or IO ownership. +- `infrastructure/`: concrete SQLite/manifest/sidecar/ExifTool/FFmpeg/maps/ + thumbnail/scanner adapters. +- `cache/index_store/`: SQLite global index implementation behind session and + repository surfaces; GUI/application code must not bypass the boundary. +- `gui/`: PySide6 views, viewmodels, controllers, coordinators, menus, Qt + tasks/signals, and rendering presentation state. +- `library/`: runtime controller and tree/watch/scan/trash/album shell code bound + to session services. +- `people/`: optional face detection/clustering runtime, repositories, durable + People state, manual faces, groups, covers, and People service API. +- `pets/`: optional YOLOX/DINOv2 pet detection and identity clustering runtime, + rebuildable Pets index, durable Pets state, and Pet service API. +- `maps/`: optional offline Maps runtime and OBF/native/helper integration. +- `core/`: Live Photo pairing, adjustment math, geometry, export transforms, + filters, raw loading, and other pure/rendering-oriented algorithms. - `io/`: metadata extraction, scanner adapters, and sidecar parsing helpers. -- `legacy/`: removed. Do not recreate it or add compatibility imports. - -## 6. Coding Rules - -- Prefer existing session/application patterns over adding new facades. -- Use application ports before introducing cross-layer behavior. -- Keep GUI workers thin: they adapt Qt threading/progress and call session or - application services. -- Use `Path` and shared path normalizers for filesystem paths. Never string-build - paths. -- Use schema validation for album/link JSON payloads where a schema exists. -- Use atomic writes for manifest, links, settings, sidecars, and user state - files. -- Use SQLite transactions for multi-row writes and scan merges. -- Use ExifTool/FFmpeg wrappers from `utils/`; never shell-concatenate user - paths. -- Return warnings for recoverable external-tool failures without corrupting - local state. -- Keep comments focused on non-obvious intent, boundaries, or failure modes. +- `legacy/`: removed. Do not recreate it. + +## 6. Startup And Recognition Rules + +- The installed GUI script is `iphoto-gui = iPhoto.entrypoint:main`. +- `iPhoto.entrypoint` stays lightweight and handles helper dispatch before the + full Qt GUI import. +- The main window shell and first-paint path must remain isolated from optional + heavy feature imports and model inference. +- Platform-required GPU detail construction may happen before `show()` where Qt + native-window behavior requires it; optional feature promotion remains owned + by the desktop startup/coordinator path. +- `DesktopCoordinatorRuntime`, not a conceptual `MainCoordinator`, is the + production desktop composition boundary. +- Recognition dashboard snapshot warmup may read existing local People/Pets + state. Warmup is not scan activation and must not initialize AI models. +- Recognition scan activation requires both `people_view_shown` and + `firstViewportReady`; `RecognitionCoordinator` then schedules the library + activation after a short quiet window. +- Do not reintroduce “start People/Pets after metadata scan” as an application + startup rule. ## 7. Bounded Context Rules ### People -- InsightFace/ONNXRuntime are optional. Missing AI runtime must not break - browsing, editing, Live Photo, Maps, or library state. -- Scan commits may rebuild `face_index.db`, but must preserve and repair +- InsightFace/ONNXRuntime are optional; missing AI runtime must not break normal + browsing, editing, Live Photo, Pets state, Maps, or library state. +- Runtime rebuilds may replace `face_index.db` but preserve/repair `face_state.db`. -- Names, covers, hidden flags, person order, groups, group order, pinned state, - group covers, manual faces, and group caches are durable user state. -- Do not merge people with incompatible hidden state. -- UI mutations must route through the session-bound People service or explicit - test doubles. +- Names, covers, hidden flags, order, groups, pinned state, group covers, manual + faces, and group caches are durable user state. +- UI mutations route through the session-bound People service. + +### Pets + +- `pets-ai` is optional. Missing dependencies or models leave eligible scan rows + resumable and must not block normal application use. +- `pet_index.db` is rebuildable; `pet_state.db` is durable. +- The production clustering contract is `species-bounded-single-link-v3`. + Cats and dogs never share an identity cluster. Candidate joins obey cannot-link + constraints and a bounded cluster diameter so single-link chains cannot grow + without limit. +- People/Pets conflict filtering uses geometry but is not an unconditional + “People always wins” rule. Strong face overlap normally suppresses a pet box, + while a substantially larger plausible pet-body detection containing a + smaller face can be preserved subject to the runtime image-coverage rule. +- Pet names, covers, hidden state, rejected detection keys, redirects, and other + explicit user choices survive runtime rebuilds. +- Cross-kind People/Pets composition belongs to recognition coordination/state; + each bounded context keeps ownership of its own runtime records. +- Production inference consumes prebuilt model artifacts. Torch Hub may be used + by release conversion/provenance tooling, but production runtime must not + execute arbitrary Torch Hub Python. +- The manifest is authoritative for integrity metadata. A `null` + `torchscript_url` means the DINOv2 artifact must already be packaged/staged; + documentation must not promise an automatic DINO download in that state. ### Maps - Maps are optional. Missing native OBF/helper/widget runtime must show graceful fallback. - Runtime availability belongs behind `MapRuntimePort`. -- Location asset aggregation and marker-click semantics belong behind session +- Location aggregation and marker-click semantics belong behind session location/map interaction surfaces. -- Qt overlay painting, pointer hit testing, drag cursors, and widget event - filters remain GUI transport details. +- Qt overlay/event behavior remains GUI transport. ### Thumbnails -- Thumbnail generation and cache lookup must not block the UI thread. -- Memory/disk cache hits must avoid re-running generators. -- Gallery-visible rows must not treat missing full thumbnail cache keys as - ready media. Old no-key rows belong on stale/backfill paths. -- Thumbnail rendering may apply `.ipo` edit state, but durable edit persistence - belongs behind edit sidecar/session services. +- Thumbnail generation/cache lookup must not block the UI thread. +- Cache hits avoid re-running generators. +- Gallery-visible rows do not treat missing full-thumbnail keys as ready media. +- Thumbnail rendering may apply `.ipo` state; durable edit persistence stays + behind edit sidecar/session services. ### Edit -- All normal edits are non-destructive and stored in `.ipo` sidecars. +- Normal edits are non-destructive and stored in `.ipo` sidecars. - Editing math belongs in `core/`; persistence belongs behind `EditSidecarPort` or session edit services. -- Static Detail and Edit exchange `PhotoRenderSessionHandle`; entering Edit, - Done/Cancel, compare, fullscreen, and adjustment updates must not restore a - second source decoder, CPU full-image preview session, or duplicate texture - upload path. -- Sidecar changes replace immutable `EditRenderState` only. They must not enter - `DetailDecodeKey` or invalidate source-identical neutral surfaces/textures. -- QRhi/Metal/OpenGL backend choice must not leak into product workflow rules. - -## 8. Rendering And Maps Platform Rules - -- macOS media preview defaults to QRhi/Metal and may decode non-RAW stills with - ImageIO; OpenGL and Qt decode are compatibility fallbacks. -- Windows uses QRhi/OpenGL and prefers WIC for non-RAW stills; WIC/COM - declarations must use fixed-width Windows ABI types. Linux uses QRhi/OpenGL - with the Qt still decoder. RAW remains routed through rawpy. -- Platform decoders may fall back to Qt only inside the existing worker lane. - They preserve cancellation and return detached RGBA8888/sRGB surfaces. -- Initial still presentation is viewport-LOD based and non-mipmapped. GPU - residency retains current/previous/next within both the three-texture and - 192MB limits; a higher LOD replaces the current layer only after a real draw. -- Legacy OpenGL maps use the `QOpenGLWindow + createWindowContainer()` surface - where required to avoid transparent-window composition issues. -- Native OsmAnd widget/helper selection belongs to maps runtime adapters and +- Static Detail/Edit share `PhotoRenderSessionHandle`; do not restore a second + source decoder, CPU full-image preview session, or duplicate texture path. +- Sidecar changes replace immutable `EditRenderState`; they do not enter source + decode keys or invalidate source-identical neutral surfaces/textures. + +## 8. Packaging And Model Assets + +- `src/extension/models/...` is a packaging/staging convention, not guaranteed + tracked content of a fresh clone. Build docs must say when the directory must + be provided before packaging. +- People/Pets capable offline packages must include the required optional Python + runtime and model artifacts explicitly. +- The Pets detector has a fixed HTTPS artifact contract. DINOv2 runtime loading + uses the prebuilt TorchScript artifact and its manifest hash/size contract; + the current manifest has no runtime DINO download URL. +- AppImage and Debian build contracts live in `docs/misc/BUILD_APPIMAGE.md` and + `docs/misc/BUILD_DEB.md`. +- A published Flatpak file is not the same as a reproducible in-repo Flatpak + build. Follow `docs/misc/BUILD_FLATPAK.md`. + +## 9. Coding Rules + +- Prefer session/application patterns over new facades. +- Use application ports for cross-layer behavior. +- Keep GUI workers thin and presentation-oriented. +- Use `Path` and shared normalizers; do not string-build filesystem paths. +- Use atomic writes for manifests, links, settings, sidecars, and durable user + state; use SQLite transactions for multi-row writes. +- Use the ExifTool/FFmpeg wrappers rather than shell-concatenating paths. +- Recoverable external-tool failures should report warnings without corrupting + local state. +- Keep comments focused on non-obvious intent, boundaries, or failure modes. + +## 10. Rendering And Platform Rules + +- macOS media preview defaults to QRhi/Metal; OpenGL/Qt decode are compatibility + fallbacks. Windows uses QRhi/OpenGL and prefers WIC for non-RAW stills. Linux + uses QRhi/OpenGL with the Qt still decoder. RAW remains routed through rawpy. +- Platform decoder fallback stays inside the worker lane and preserves + cancellation/detached RGBA8888/sRGB output. +- GPU residency/LOD policy is a rendering concern, not application workflow. +- Native OsmAnd helper/widget selection belongs to Maps runtime adapters and widget factories. -- Packaged builds must include required QSB shaders and maps extension runtime - assets when those features are enabled. +- Packaged builds include required QSB shaders and optional extension assets + only when those features are claimed by the package. -## 9. Testing And Verification +## 11. Testing And Verification Run architecture checks after boundary changes: @@ -254,52 +299,35 @@ python3 tools/check_architecture.py .venv/bin/python -m pytest tests/architecture -q ``` -Use targeted regression tests for changed behavior: - -```bash -.venv/bin/python -m pytest tests/application/test_runtime_context.py tests/application/test_library_session.py tests/application/test_scan_library_use_case.py -q -.venv/bin/python -m pytest tests/application/test_temp_library_end_to_end.py tests/application/test_library_asset_lifecycle_service.py tests/services/test_asset_move_service.py tests/services/test_restoration_service.py -q -.venv/bin/python -m pytest tests/performance -q -``` - -For Detail rendering, decoder, cache, Edit session, or viewer changes, run: - -```bash -.venv/bin/python -m pytest -q tests/gui/test_detail_pipeline.py tests/gui/test_detail_render_coordinator.py tests/gui/test_detail_decode_backend.py tests/gui/test_detail_request_scheduler.py tests/gui/test_detail_surface_cache.py tests/gui/test_detail_render_session.py tests/ui/controllers/test_player_view_controller_adjustments.py tests/ui/widgets/test_still_texture_residency.py tests/test_detail_benchmark.py -.venv/bin/python tools/check_architecture.py -.venv/bin/python -m compileall -q src tools -``` - -Platform backend changes also require packaged manual validation on the target -OS. Source/offscreen tests do not prove WIC, ImageIO, QRhi/Metal, or native -OpenGL behavior. - -Before touching scan visible publishing, collection query performance, trash -state, or move/restore optimistic UI behavior, read the matching guardrail under -`docs/misc/`. +Use focused tests for the behavior changed. For recognition work include People, +Pets, dashboard, playback/detail annotation, and relevant repository/service +coverage. For Detail rendering changes run the Detail transaction/scheduler/ +decoder/cache/session tests and packaged validation on the affected platform. Required guardrail expectations: - `application/` has no GUI or concrete persistence imports. - `infrastructure/` has no GUI imports. - production source has no `iPhoto.legacy` or `iPhoto.models.*` imports. -- GUI runtime has no compatibility service factory fallback. -- Architecture checks are part of CI. - -## 10. Release And Documentation Rules - -- Keep `README.md` product-facing and concise. -- Keep `docs/architecture.md` as the current architecture entry point. -- Keep completed refactor records under `docs/finished/refactor/`. -- Do not treat archived refactor documents under `docs/finished/` as current - implementation instructions. -- Keep `docs/requirements/DETAIL_OPEN_BENCHMARK_RUNBOOK.md` aligned with the - production Detail profiler/harness whenever transaction stages, cache tiers, - decoder names, or SLO validation fields change. -- Release validation may include manual Qt GUI smoke testing and opening an - existing library, but these are product acceptance checks rather than - architecture guardrail replacements. - -This guide is authoritative for new production work. When it conflicts with old -examples, follow the vNext runtime/session boundary and update the stale -example as part of the change. +- GUI runtime has no compatibility service-factory fallback. +- People/Pets inference is not an automatic application-startup task. +- Architecture and documentation checks are part of CI. + +## 12. Release And Documentation Rules + +- Keep `README.md`, `docs/readme/README_zh-CN.md`, and + `docs/readme/README_de.md` semantically aligned. +- README files distinguish development-branch capabilities from the latest + published binary release. +- `docs/architecture.md` is the current architecture entry point. +- `docs/misc/PETS_RECOGNITION_RUNTIME.md` is the canonical Pets runtime note. +- `docs/requirements/` contains active work or explicitly labelled historical/ + residual-debt material; completed normative work belongs under + `docs/finished/`. +- Historical requirement text must not silently override current architecture, + runtime guardrails, or code. +- Run the docs link/parity checks when changing maintained documentation. + +This guide is authoritative for new production work. When it conflicts with an +older example or historical requirement, follow the current runtime/session +boundary and update or explicitly supersede the stale documentation. diff --git a/README.md b/README.md index 2e631c9f9..0a3a3848a 100644 --- a/README.md +++ b/README.md @@ -1,316 +1,106 @@ # 📸 iPhotron -> A macOS *Photos*-inspired, folder-native photo manager for Windows, macOS, and Linux with Live Photo, maps, and smart albums. -![Platform](https://img.shields.io/badge/platform-Windows%20%7C%20macOS%20%7C%20Linux-lightgrey) -![Language](https://img.shields.io/badge/language-Python%203.12%2B-blue) -![Framework](https://img.shields.io/badge/framework-PySide6%20(Qt6)-orange) -![License](https://img.shields.io/badge/license-MIT-green) -[![GitHub Repo](https://img.shields.io/badge/github-iPhotron-181717?logo=github)](https://github.com/OliverZhaohaibin/iPhotron-LocalPhotoAlbumManager) +> A macOS Photos-inspired, folder-native photo manager for Windows, macOS, and Linux. -**Languages / 语言 / Sprachen:** -[![English](https://img.shields.io/badge/English-Click-blue?style=flat)](README.md) | [![中文简体](https://img.shields.io/badge/中文简体-点击-red?style=flat)](docs/readme/README_zh-CN.md) | [![Deutsch](https://img.shields.io/badge/Deutsch-Klick-yellow?style=flat)](docs/readme/README_de.md) +**Languages:** [English](README.md) · [简体中文](docs/readme/README_zh-CN.md) · [Deutsch](docs/readme/README_de.md) ---- +## Release and development status -## ☕ Support +**The downloads below are the published v6.6.8 binaries.** The feature overview +also describes the current `edit-base` development branch and may include +Unreleased work. A development feature is not automatically present in v6.6.8. +See [`docs/CHANGELOG.md`](docs/CHANGELOG.md) for current branch changes. -[![Buy Me a Coffee](https://img.shields.io/badge/Buy%20Me%20a%20Coffee-Support%20Development-yellow?style=for-the-badge&logo=buy-me-a-coffee&logoColor=white)](https://buymeacoffee.com/oliverzhao) -[![PayPal](https://img.shields.io/badge/PayPal-Donate-blue?style=for-the-badge&logo=paypal&logoColor=white)](https://www.paypal.com/donate/?hosted_button_id=AJKMJMQA8YHPN) +## v6.6.8 downloads +| Platform | Artifact | +| --- | --- | +| Windows | [`v6.68-x86-setup.exe`](https://github.com/OliverZhaohaibin/iPhotron-LocalPhotoAlbumManager/releases/download/v6.6.8/v6.68-x86-setup.exe) | +| Debian | [`iphotron_6.6.8_amd64.deb`](https://github.com/OliverZhaohaibin/iPhotron-LocalPhotoAlbumManager/releases/download/v6.6.8/iphotron_6.6.8_amd64.deb) | +| AppImage | [`iPhotron-6.6.8-x86_64.AppImage`](https://github.com/OliverZhaohaibin/iPhotron-LocalPhotoAlbumManager/releases/download/v6.6.8/iPhotron-6.6.8-x86_64.AppImage) | +| Flatpak | [`com.github.OliverZhaohaibin.iPhotron-6.6.8-x86_64.flatpak`](https://github.com/OliverZhaohaibin/iPhotron-LocalPhotoAlbumManager/releases/download/v6.6.8/com.github.OliverZhaohaibin.iPhotron-6.6.8-x86_64.flatpak) | -## 📥 Download & Install - -[![Download for Windows](https://img.shields.io/badge/⬇️%20Download-Windows%20(.exe)-blue?style=for-the-badge&logo=windows)](https://github.com/OliverZhaohaibin/iPhotron-LocalPhotoAlbumManager/releases/download/v6.6.8/v6.68-x86-setup.exe) -[![Download for Linux (.deb)](https://img.shields.io/badge/⬇️%20Download-Linux%20(.deb)-orange?style=for-the-badge&logo=linux&logoColor=white)](https://github.com/OliverZhaohaibin/iPhotron-LocalPhotoAlbumManager/releases/download/v6.6.8/iphotron_6.6.8_amd64.deb) -[![Download for Linux (.AppImage)](https://img.shields.io/badge/⬇️%20Download-Linux%20(.AppImage)-brightgreen?style=for-the-badge&logo=linux&logoColor=white)](https://github.com/OliverZhaohaibin/iPhotron-LocalPhotoAlbumManager/releases/download/v6.6.8/iPhotron-6.6.8-x86_64.AppImage) -[![Download for Linux (.flatpak)](https://img.shields.io/badge/⬇️%20Download-Linux%20(.flatpak)-purple?style=for-the-badge&logo=flatpak&logoColor=white)](https://github.com/OliverZhaohaibin/iPhotron-LocalPhotoAlbumManager/releases/download/v6.6.8/com.github.OliverZhaohaibin.iPhotron-6.6.8-x86_64.flatpak) - -**💡 Quick Install:** Click the buttons above to download the latest installer directly. - -- **Windows:** Run the `.exe` installer directly. -- **Linux (.deb):** Install with the following command: - -```bash -sudo apt install ./iphotron_6.6.8_amd64.deb -``` - -- **Linux (.AppImage):** Make the file executable and run it: +`v6.68-x86-setup.exe` is the actual published Windows asset name. -```bash -chmod +x iPhotron-6.6.8-x86_64.AppImage -./iPhotron-6.6.8-x86_64.AppImage -``` +The v6.6.8 Flatpak file is a published release artifact, but this development +branch currently has no maintained in-repository Flatpak build recipe. See +[`BUILD_FLATPAK.md`](docs/misc/BUILD_FLATPAK.md). Current reproducible Linux +packaging guides are [Debian](docs/misc/BUILD_DEB.md) and +[AppImage](docs/misc/BUILD_APPIMAGE.md). -- **Linux (.flatpak):** Install the bundle with Flatpak: - -```bash -flatpak install --user ./com.github.OliverZhaohaibin.iPhotron-6.6.8-x86_64.flatpak -``` - -**For developers** — install from source: +## Run from source ```bash +python -m venv .venv +source .venv/bin/activate pip install -e . -``` - ---- - -## 🚀 Quick Start - -```bash iphoto-gui ``` -Or open a specific album directly: - -```bash -iphoto-gui /photos/LondonTrip -``` - ---- - -## 🌟 Star History - - - - - - Star History Chart - - +The installed GUI entry point is `iPhoto.entrypoint:main`. -## 🚀 Product Hunt -

- - iPhotron - A macOS Photos–style photo manager for Windows | Product Hunt - -

-

- Please Upvote • - Follow • - Discuss on the Forum -

+## Current development highlights ---- +- Folder-native albums with no import step. +- SQLite-backed large-library browsing with sparse asynchronous Gallery windows. +- Demand-driven thumbnail loading and generation-safe scrolling. +- Live Photo pairing and playback. +- Optional offline Maps/OsmAnd runtime. +- Optional People recognition with names, covers, groups, hidden state, and + manual faces. +- Optional Pets recognition for cats/dogs with durable names/covers/state. +- GPU-first Detail rendering with shared Detail/Edit render sessions. +- Non-destructive `.ipo` sidecar editing. +- Assign Location with local persistence and best-effort GPS metadata write-back. -## 🌟 Overview - -**iPhotron** is a **folder-native photo manager** inspired by macOS *Photos*. -It keeps your folders as the album structure, combines folder-local manifests -with a library-scoped `.iPhoto/global_index.db`, and separates rebuildable cache -facts from durable user choices while keeping destructive edits out of your -original media files. - -Key highlights: -- 🗂 Folder-native design — every folder *is* an album, no import needed. -- ⚙️ Folder-local manifests record album metadata such as cover, featured items, and order. -- ⚡ **SQLite-powered global database** for lightning-fast session-backed queries on massive libraries. -- 🖼 Demand-driven Gallery scrolling with sparse asynchronous windows, - micro-thumbnail fallback, and memory-aware full-thumbnail prefetching. -- 🧠 Smart incremental scanning with persistent SQLite index. -- 🎥 Full **Live Photo** pairing and playback support. -- 🗺 Optional map view that visualizes GPS metadata across all photos & videos and falls back gracefully when the maps extension is unavailable. -- 👥 Optional People scanning with face clusters, names, covers, hidden people, - and multi-person groups. -- 🐾 Optional Pets scanning with cat/dog detection, identity clusters, names, - covers, hidden pets, pins, and People & Pets groups. ![Main interface](docs/picture/mainview.png) -![Preview interface](docs/picture/preview.png) ---- - -## 🗺 Maps Extension - -iPhotron's offline OBF map runtime ships as a self-contained **maps extension** -rooted at `src/maps/tiles/extension/`. That directory is the contract consumed -by local development, packaged builds, and platform-specific installers. -The app remains usable without this extension; map-specific views and panels use -the runtime availability surface to show graceful fallback behavior. - -The extension currently contains: -- `World_basemap_2.obf` offline map data -- OsmAnd resources under `misc/`, `poi/`, `rendering_styles/`, `routing/`, - and related runtime resource directories -- offline search data under `search/geonames.sqlite3` -- platform-specific native binaries under `bin/` - - Windows: `osmand_render_helper.exe`, `osmand_native_widget.dll`, - `OsmAndCore_shared.dll`, `OsmAndCoreTools_shared.dll`, and the required Qt DLLs - - Linux: `osmand_render_helper`, `osmand_native_widget.so`, - `libOsmAndCore_shared.so`, and `libOsmAndCoreTools_shared.so` - - macOS: `osmand_render_helper`, `osmand_native_widget.dylib`, and copied - non-system Mach-O dependencies - -Platform maps notes: -- iPhotron can use both the helper-backed OBF renderer and the native OsmAnd widget when the platform runtime is available. -- If a sibling `PySide6-OsmAnd-SDK/` checkout exists, Linux and macOS development can prefer its `tools/osmand_render_helper_native/dist-*` widget builds. -- The native Linux widget currently expects Qt's XCB desktop OpenGL path. When that backend is selected, iPhotron auto-sets `QT_QPA_PLATFORM=xcb`, `QT_OPENGL=desktop`, and `QT_XCB_GL_INTEGRATION=xcb_glx`. -- On macOS, the legacy OpenGL map uses `QOpenGLWindow + createWindowContainer()` to avoid transparent-window `QOpenGLWidget` composition issues; media previews default to the Metal-capable QRhi path unless `IPHOTO_RHI_BACKEND=opengl` is set. -| Without maps extension | With maps extension | -| --- | --- | -| ![Without maps extension](docs/picture/without_extension.png) | ![With maps extension](docs/picture/maps_extension.png) | - -The extension is built upstream from the standalone -[PySide6-OsmAnd-SDK](https://github.com/OliverZhaohaibin/PySide6-OsmAnd-SDK) -sub-project. That repository carries the vendored OsmAnd sources, build scripts -for Windows, Linux, and macOS, the native Qt widget bridge, and the preview app -used to produce the runtime consumed here. - -See [Development](docs/development.md) for the full "build the maps extension -from the side project" workflow, and -[Executable Build](docs/misc/BUILD_EXE.md) for how the extension is synchronized -into Nuitka and platform-specific release builds. +## People & Pets -## ✨ Features +People and Pets are separate optional bounded contexts. They keep independent +runtime indexes and durable state while the UI can compose them into cards, +groups, Gallery queries, and Detail annotations. -### 🗺 Location View -Displays your photo footprints on an interactive map, clustering nearby photos by GPS metadata. -![Location interface](docs/picture/map1.png) -![Location interface](docs/picture/map2.png) -### 🎞 Live Photo Support -Seamlessly pairs HEIC/JPG and MOV files using Apple's `ContentIdentifier`. -A "LIVE" badge appears on still photos — click to play the motion video inline. -![Live interface](docs/picture/live.png) -### 🧩 Smart Albums -The sidebar provides an auto-generated **Basic Library**, grouping photos into: -`All Photos`, `Videos`, `Live Photos`, `Favorites`, and `Recently Deleted`. +The current Pets identity pipeline is `species-bounded-single-link-v3`: +species stay separate, cannot-link constraints are respected, and cluster +diameter is bounded to prevent uncontrolled chaining. -### 👥🐾 People, Pets, Face Clusters & Groups -The optional People pipeline detects faces, builds face clusters, and presents -them as People cards. You can name people, merge duplicate clusters, hide or -show hidden people, and keep chosen covers persistent across rescans. +People/Pets conflict filtering is not an unconditional “People always wins” +rule. Strong face overlap normally suppresses a pet candidate, while a much +larger plausible pet-body detection containing a smaller face can be preserved +by the runtime size/image-coverage exception. -The independent Pets pipeline detects cats and dogs with YOLOX, creates DINOv2 -embeddings, and presents pet identity cards in the same dashboard. Pets can be -named, merged, hidden, pinned, assigned covers, and opened as gallery queries. -People and Pets may participate in the same identity groups while their runtime -indexes and durable state remain separate. +Recognition inference is feature-driven: scanning is activated only after the +People surface has been opened and its first viewport is ready. Normal app +startup does not independently start People/Pets inference. -When a pet detection and a People face detection refer to the same image -region, People takes priority. The conflicting pet detection is removed from -the rebuildable Pets snapshot, so it does not create a duplicate dashboard -card, gallery result, detail annotation, or overlay. This cross-type filter -compares geometry only; People and Pets records, models, indexes, and durable -state remain independently owned. +DINOv2 production loading uses a prebuilt TorchScript artifact verified by the +model manifest. The current manifest has `torchscript_url: null`, so the DINOv2 +artifact must currently be packaged or explicitly staged. `src/extension/models` +is a packaging/staging convention, not guaranteed fresh-clone content. -Drag people into groups to collect shared photos for multiple people. Group -cards can use a selected cover, be reordered, and be disbanded when they are not -pinned. Face scanning uses the optional `ai-demo` dependencies; the core photo -manager remains usable without installing the AI runtime, and People state is -kept behind the library session so names, covers, hidden flags, groups, and -manual faces survive rescans. Pet scanning uses the optional `pets-ai` extra; -missing pet dependencies or models leave eligible rows pending without blocking -the rest of the application. -![People and groups interface](docs/picture/People%20%26%20Group.png) +See [`PETS_RECOGNITION_RUNTIME.md`](docs/misc/PETS_RECOGNITION_RUNTIME.md). -### ⚡ Large-Library Gallery -Gallery browsing uses sparse asynchronous SQL windows instead of materializing -an entire collection. Scroll intent drives separate visible, nearby guard, and -speculative thumbnail lanes; fast bursts keep input responsive and use warmed -micro thumbnails while full thumbnails are decoded away from the GUI thread. -Memory and worker budgets adapt to the host platform and physical RAM, with -generation-aware cancellation preventing old viewport work from repainting the -current view. +## Architecture and packaging -### 🖼 Immersive Detail View -An elegant viewer with a filmstrip navigator, floating playback bar for videos, -and a platform-selected GPU path: QRhi/Metal on macOS, OpenGL-backed QRhi on -Windows and Linux. +`DesktopCoordinatorRuntime` is the production desktop composition root; +`main_coordinator.py` is a compatibility import only. -### 🎨 Non-Destructive Photo Editing -A comprehensive editing suite with **Adjust** and **Crop** modes: +Current documentation: -#### Adjust Mode -- **Light Adjustments:** Brilliance, Exposure, Highlights, Shadows, Brightness, Contrast, Black Point -- **Color Adjustments:** Saturation, Vibrance, Cast (white balance correction) -- **Black & White:** Intensity, Neutrals, Tone, Grain with artistic film presets -- **Color Curves:** RGB and per-channel (R/G/B) curve editor with draggable control points for precise tonal adjustments -- **Selective Color:** Target six hue ranges (Red/Yellow/Green/Cyan/Blue/Magenta) with independent Hue/Saturation/Luminance controls -- **Levels:** 5-handle input-output tone mapping with histogram backdrop and per-channel control -- **Master Sliders:** Each section features an intelligent master slider that distributes values across multiple fine-tuning controls -- **Live Thumbnails:** Real-time preview strips showing the effect range for each adjustment -![edit interface](docs/picture/editview.png) -![edit interface](docs/picture/professionaltools.png) -#### Crop Mode -- **Perspective Correction:** Vertical and horizontal keystoning adjustments -- **Straighten Tool:** ±45° rotation with sub-degree precision -- **Flip (Horizontal):** Horizontal flip support -- **Interactive Crop Box:** Drag handles, edge snapping, and aspect ratio constraints -- **Black Border Prevention:** Automatic validation ensures no black edges appear after perspective transforms - -![crop interface](docs/picture/cropview.png) -All edits are stored in `.ipo` sidecar files through the edit session surface, -preserving original photos untouched. +- [`AGENT.md`](AGENT.md) +- [`docs/architecture.md`](docs/architecture.md) +- [`docs/development.md`](docs/development.md) +- [`docs/security.md`](docs/security.md) +- [`docs/requirements/README.md`](docs/requirements/README.md) -### ℹ️ Floating Info Panel -Toggle a floating metadata panel with EXIF, camera/lens details, exposure, -aperture, focal length, dimensions, file size, and capture time. For assets -with recognition data, the panel and image overlay show face and pet -annotations. Face and pet detections can be removed or moved to another/new -identity through their owning service. - -Location tools are built in as well: geotagged assets can show an inline map, -and assets without a location can use the "Assign a Location" search flow to -pick and confirm a place. The assignment is always saved in the local library -database; if ExifTool is available, iPhotron also best-effort writes GPS -metadata back to the original file and warns when that write-back fails. If the -maps extension is missing, the panel offers the download path instead of failing -silently. - -| Info panel with map | Floating info panel in detail view | +| Package target | Current branch status | | --- | --- | -| ![Info panel with map](docs/picture/info.png) | ![Floating info panel in detail view](docs/picture/info2.png) | - -### 💬 Rich Interactions -- Drag & drop files from Explorer/Finder directly into albums. -- Multi-selection & context menus for Copy, Show in Folder, Move, Delete, Restore. -- Smooth thumbnail transitions and macOS-like album navigation. - ---- - -## 📚 Documentation - -For deeper technical details, see the following docs: - -[![Architecture](https://img.shields.io/badge/📐_Architecture-blue?style=for-the-badge)](docs/architecture.md) -[![Development](https://img.shields.io/badge/🧰_Development-green?style=for-the-badge)](docs/development.md) -[![Executable Build](https://img.shields.io/badge/🧱_Executable_Build-purple?style=for-the-badge)](docs/misc/BUILD_EXE.md) -[![Security](https://img.shields.io/badge/🔒_Security-red?style=for-the-badge)](docs/security.md) -[![Changelog](https://img.shields.io/badge/📋_Changelog-orange?style=for-the-badge)](docs/CHANGELOG.md) - -| Document | Description | -|----------|-------------| -| [Architecture](docs/architecture.md) | Current vNext library-scoped modular monolith architecture, module boundaries, legacy removal policy, data flow, and key design decisions | -| [Development](docs/development.md) | Dev environment, dependencies, debugging, and the side-project-based maps extension workflow for Windows, Linux, and macOS | -| [Pets Runtime](docs/misc/PETS_RECOGNITION_RUNTIME.md) | Current Pets models, scan scheduling, persistence, mutation safety, and People & Pets composition contract | -| [Executable Build](docs/misc/BUILD_EXE.md) | Nuitka packaging, AOT filters, QRhi shader assets, maps extension sync, and platform runtime notes | -| [Security](docs/security.md) | Permissions, encryption, data storage locations, threat model | -| [Changelog](docs/CHANGELOG.md) | All version release notes and changes | - ---- - -## 🧩 External Tools - -| Tool | Purpose | -|------|----------| -| **ExifTool** | Reads EXIF, GPS, QuickTime, and Live Photo metadata; writes GPS metadata for explicit Assign Location actions. | -| **FFmpeg / FFprobe** | Generates video thumbnails & parses video info. | -| **InsightFace / ONNXRuntime + `buffalo_s` models** | Optional People face scanning: face detection (`det_500m.onnx`) and face embeddings (`w600k_mbf.onnx`) from `src/extension/models/buffalo_s/`. | -| **YOLOX / ONNXRuntime + DINOv2 / Torch** | Optional Pets scanning: cat/dog detection and pet identity embeddings from `src/extension/models/pets/`. | - -> Ensure FFmpeg/FFprobe are available in your system `PATH`; install ExifTool if -> you want assigned GPS coordinates written back into original media files. -> Recognition runtimes are optional; install them with -> `pip install -e ".[ai-demo,pets-ai]"` for source builds, and keep -> `extension/models` bundled for offline packaged builds. - -Python dependencies (e.g., `Pillow`, `reverse-geocoder`) are auto-installed via `pyproject.toml`. - ---- - -## 📄 License +| Windows / Nuitka | documented | +| Debian | reproducible in-repo guide | +| AppImage | reproducible in-repo guide | +| Flatpak | v6.6.8 download exists; current in-repo recipe is absent | -**MIT License © 2025** -Created by **Haibin Zhao (OliverZhaohaibin)** +## License -> *iPhotron — A folder-native, human-readable, and fully rebuildable photo system.* -> *No forced imports. No proprietary lock-in. Just your photos, organized elegantly.* +MIT — see [`LICENSE`](LICENSE). diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 22bd2e3aa..8791a2101 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,652 +1,111 @@ -# 📋 Changelog +# Changelog -All notable changes to **iPhotron** are documented in this file. +This changelog keeps the current development-branch contract concise. Older +release-by-release detail remains available in Git history and GitHub Releases. ---- +## Unreleased — Startup & Architecture Hardening -## Unreleased — Gallery Detail GPU-first Rendering +### Desktop composition -### Rendering pipeline +- The installed GUI entry point is `iPhoto.entrypoint:main`; helper dispatch can + occur before importing the full Qt desktop runtime. +- `DesktopCoordinatorRuntime` is the production desktop coordinator composition + root in `gui/coordinators/desktop_coordinator_runtime.py`. +- `gui/coordinators/main_coordinator.py` is now a compatibility import only. New + architecture and developer documentation must not describe a separate + production `MainCoordinator` contract. +- Optional feature promotion remains separated from the first-frame path. + Platform-required GPU Detail construction may still occur before `show()` + where Qt native-window behavior requires it. -- Unified still and video Detail presentation under one immutable render - transaction and terminal-state coordinator; stale generations cannot publish - a final frame after a newer open. -- Replaced sensor-resolution-first still loading with viewport/DPR/crop/ - rotation/perspective/zoom-aware LOD decode and atomic replacement after the - new texture is actually drawn. -- Added versioned neutral surface caching, byte-budgeted memory residency, and - current/previous/next GPU texture residency. Source identity is independent - from `.ipo` edit revision, and initial still textures do not generate mipmaps. -- Added macOS ImageIO and Windows WIC non-RAW decoders with in-worker Qt - fallback; RAW uses the embedded-preview/half/full rawpy path. Windows WIC COM - declarations now use a fixed signed 32-bit `HRESULT` ABI across supported - CPython builds. +### Recognition activation -### Detail/Edit session +- People/Pets model inference is no longer an automatic application-startup or + post-metadata-scan task. +- `RecognitionCoordinator` can bind services and warm persisted dashboard data + without initializing inference models. +- Recognition scans are requested only after the People surface has actually + been shown **and** its first viewport reports ready. +- Scan activation is then delayed briefly so first content/cover delivery is not + immediately competing with AI work. -- Added `PhotoRenderSessionHandle` and immutable `EditRenderState` so static - Detail and Edit share source texture, LODs, color statistics, live shader - state, and persisted baseline. -- Removed the still Edit full-image loader, CPU realtime preview chain, repeated - color-stat calculation, and static Done/Cancel media replay. Export and video - editing retain their independent quality/lifecycle paths. -- Removed the legacy Detail v2/frame-cache flags, old full-frame cache coupling, - diagnostic duplicate scheduler path, and synchronous video compatibility - entry point. GPU-first is the sole production Detail path. - -### Verification and operations - -- Added generation-safe background JSONL profiling and a packaged benchmark - harness covering cold/disk/memory/GPU cache groups, sidecar-only changes, - Detail/Edit, fullscreen, LOD, memory pressure, and rapid switching. -- Added macOS/Windows/Linux CI contract coverage for transactions, schedulers, - decoders, surface caches, render sessions, residency, and benchmark - validation. Windows and Linux packaged/manual acceptance was completed for - the rollout in addition to the macOS development path. -- Stabilized full-process Qt tests by retaining one strongly owned - `QApplication`; this prevents cached QIcon/QPixmap resources from surviving a - destroyed application and crashing later QtSvg widget construction. +This supersedes older Unreleased wording that described Face/Pet workers as +starting after startup or metadata scan completion. ## Unreleased — People & Pets Recognition -🐾 *Adds a library-scoped Pets bounded context and composes pet identities with -the existing People dashboard, gallery, groups, pins, and media annotations.* - -### Key Updates - -#### 🐕 Pet Detection And Clustering -- Added optional YOLOX/ONNXRuntime pet detection, DINOv2 embeddings, and - species-separated identity clustering through the `pets-ai` extra. -- Added tiled fallback detection for small cats/dogs, stable `pet_key` - canonicalization, detector/clustering pipeline versioning, and graceful - pending-state recovery when optional dependencies or models are unavailable. -- Pinned the DINOv2 Torch Hub source to an immutable revision and kept the - shared model contract under `src/extension/models/pets/`. - -#### 💾 Library-Scoped Pets State -- Added independent `pet_status` bookkeeping to `global_index.db`, rebuildable - `.iPhoto/pets/pet_index.db`, and durable `.iPhoto/pets/pet_state.db` state. -- Added durable names, covers, hidden flags, rejected detections, merges, moves, - identity redirects, and reference-safe cleanup of replaced pet thumbnails. -- Added `LibrarySession.pets`, Pets application ports, bootstrap composition, - snapshot coordination, and background `PetScanWorker` scheduling. - -#### 👥 People & Pets UI -- Extended the People dashboard with pet cards, naming, merge/move, hide, - cover, delete-detection, pin, and gallery navigation actions. -- Added mixed person/pet identity groups and cross-kind redirects while keeping - People and Pets runtime records in separate bounded contexts. -- Added pet bounding-box/name annotations to image playback, detail overlays, - and information surfaces through the shared recognition annotation transport. - -#### 🚀 Startup And Worker Lifecycle -- Deferred saved-library metadata scanning until the first gallery window is - warmed, with a bounded fallback timer. -- Deferred startup Face/Pet AI workers until metadata scanning completes; - interactive rescans continue to feed both workers from committed scan rows. -- Hardened cancellation and shutdown waits for scanner, face, pet, thumbnail, - map, playback, and event-bus resources. - -#### 📚 Documentation -- Updated the production architecture, security, development, packaging, and - localized README surfaces for Pets. -- Added `docs/misc/PETS_RECOGNITION_RUNTIME.md` as the current maintenance - contract and marked the original Pets requirements as historical inputs. - ---- - -## 🚀 v6.6.8 — Gallery Scroll Performance, Async Windows & Thumbnail Demand - -🖼️ *A Gallery performance release focused on low-latency scrolling, sparse -viewport windows, micro-thumbnail warm-up, predictive full-thumbnail reads, and -memory-aware thumbnail publishing.* - -### Key Updates - -#### 🪟 Desktop Startup & On-Demand Features -- Added a real `MainWindow.firstPainted` boundary. Hidden feature widgets and - the main coordinator are now created over later event-loop turns instead of - blocking the initial window paint. -- Split the main UI into on-demand detail, preview, Map, People, and Albums - feature bundles, with `featureCreated` wiring for components that appear - after the window shell. -- Kept the GPU-backed detail page in the Windows pre-show phase to avoid native - window recreation and a visible false first window; Linux follows the same - pre-show rule, while macOS defers it for the faster first-frame path. -- Reused the settings object loaded during early startup and avoided rewriting - an unchanged settings file on every launch. - -#### 🧩 Lazy Imports & Runtime Work -- Replaced eager package exports and startup imports across GUI services, - widgets, models, scan workers, geocoding, People, and library runtime code - with compatibility-preserving lazy imports. -- Deferred pending OsmAnd extension installation until the Map feature is first - created, and moved Windows map and face assets to versioned per-user extension - roots under `%LOCALAPPDATA%`. -- Added `IPHOTO_STARTUP_PROFILE` checkpoints written as JSON Lines to the - platform log directory; diagnostics are disabled and perform no file I/O by - default. -- Added subprocess import-boundary tests that prevent NumPy, Qt Multimedia, - People AI, Maps rendering, and the coordinator graph from returning to the - initial GUI import path. - -#### 📦 Windows Startup Packaging -- Changed the Windows Nuitka script to build a smaller base package by default; - map data/native binaries and face models are included only with - `-IncludeOptionalAssets` for offline deployments. -- Added a Nuitka compilation report for auditing frozen imports while retaining - explicit package inclusion required by lazy package exports. - -#### ⚡ Gallery Scroll Pipeline -- Added `GalleryScrollController` for wheel-aware scroll handling, viewport - generation tracking, scroll intent classification, and display thumbnail - bucket selection. -- Replaced buffered visible-row polling with `GalleryViewportDemand`, publishing - visible, full-prefetch, and micro-warm ranges from the grid every event-loop - turn. -- Added demand policy constants for slow, medium, fast, directional-dwell, and - continuous-burst scrolling so the gallery can trade prefetch depth for input - responsiveness. -- Removed the extra off-screen paint pass in `GalleryGridView`; scrolling now - relies on warmed model rows and thumbnail demand instead of manual adjacent-row - painting. - -#### 🧩 Sparse Gallery Model Windows -- Added asynchronous `GalleryWindowLoader` and request/result types for - generation-aware background loading of gallery windows. -- Reworked `GalleryCollectionStore` into a sparse, OrderedDict-backed cache that - merges visible and warm chunks without replacing the whole model window. -- Added row-load signaling for deep or detail-view requests, plus retained - explicit row loads across newer viewport generations. -- Preserved optimistic move overlays, pinned rows, revision checks, and stale - result filtering across asynchronous window results. - -#### 🖼️ Tile Snapshots & Model Updates -- Added `GalleryTileRecord` and `GalleryTileSnapshot` so delegates can paint - gallery tiles from one compact role instead of repeatedly querying many roles. -- Added the `TILE_SNAPSHOT` role and updated the asset delegate to draw full - thumbnails, micro thumbnails, badges, favorite state, video duration, and - current-row state from the snapshot when available. -- Batched thumbnail-ready model updates so bursts of completed thumbnails emit - coalesced `dataChanged` ranges. - -#### 🧠 Thumbnail Runtime & Prefetching -- Rebuilt `ThumbnailCacheService` around explicit visible, predictive, and - far-speculative request lanes with separate concurrency controls. -- Added `ThumbnailRuntimePolicy` to size memory limits, worker counts, staging - depth, publish budget, miss TTLs, and speculative backoff from platform and - physical memory. -- Added L1 byte accounting with pinned-visible retention and eviction preference - for old demand or far speculative entries before visible thumbnails. -- Added staged publish queues that convert a bounded number of `QImage` results - to `QPixmap` on the GUI thread, keeping expensive conversion work under a - small frame budget. -- Added L2-only predictive reads for prefetched thumbnails, active prefetch - promotion when an item becomes visible, and cancellation/backoff metrics for - speculative work. - -#### 🗂️ Index Hints & Thumbnail Backfill -- Added lightweight gallery collection windows in the index repository that omit - wide metadata columns while preserving fields needed for tile rendering. -- Added thumbnail hint windows that return paths and existing 512px cache keys - without doing collection counts, enabling predictive L2 reads for nearby rows. -- Moved thumbnail backfill candidate discovery off the gallery load path and - allowed old ready rows with missing micro thumbnails to derive a micro layer - from the existing full-size cache. -- Hardened micro-thumbnail decoding so corrupt index blobs are rejected before - reaching Qt image plugins. - -#### 🧪 Tests & Benchmarks -- Added coverage for viewport demand construction, scroll-controller behavior, - async gallery window merging, stale generation handling, explicit row loads, - tile snapshot updates, thumbnail hint loading, and runtime policy detection. -- Added thumbnail cache tests for visible/predictive/speculative scheduling, - L2-only reads, staged publishing, memory pressure eviction, promotion, and - backoff behavior. -- Added a Qt gallery scroll performance benchmark covering warmed rows, next - screen thumbnails, burst behavior, and display-bucket residency. -- Documented the production sparse-window/thumbnail-demand architecture and a - focused Gallery scroll regression checklist under `docs/misc/`. - ---- - -## 🚀 v6.6.6 — i18n, macOS Rendering, Map Runtime & Location Resilience - -🌐 *A UI internationalization and platform-compatibility pass focused on -runtime language switching, German and Simplified Chinese resources, macOS -Metal/QRhi previews, transparent-window map rendering, safer location -assignment, and packaged runtime coverage.* - -### Key Updates - -#### 🌐 GUI Internationalization -- Added `TranslationManager` as a runtime service on `RuntimeContext`, backed by - the `ui.language` setting and runtime `languageChanged` refresh signals. -- Added bundled Qt translation resources for German and Simplified Chinese, - plus `languages.json` metadata and package-data coverage for `.ts` / `.qm` - resources. -- Added a language menu under Settings with `English`, `Deutsch`, and - `简体中文` choices; English remains the fallback language. -- Migrated major GUI surfaces to translated text, including main menus, - status-bar feedback, info panel, People dashboard, album navigation, gallery - context menus, detail/player controls, share/export feedback, face overlays, - edit sidebar controls, and the standalone map preview entry point. -- Added locale-aware GUI formatters for dates, integers, decimals, and file - sizes so translated surfaces do not depend on the host system locale. - -#### 🧰 i18n Tooling & Guardrails -- Added Python-aware i18n extraction through `tools/extract_i18n_strings.py` - and `scripts/i18n_extract.sh`, covering `src/iPhoto/gui` and `src/maps` - without depending on Qt's unavailable Python `lupdate` path. -- Added `scripts/i18n_compile.sh` for rebuilding `.qm` resources with - `pyside6-lrelease`. -- Added `tools/check_i18n_strings.py` and architecture tests that block direct - English literals in high-risk GUI APIs such as `setText`, `setToolTip`, - `QAction`, dialogs, and status messages. -- Added Apple Photos-aligned edit terminology notes under - `docs/requirements/i18n/` and a long-term i18n UI text guardrail under - `docs/misc/`. - -#### 🐾 Requirements Planning -- Added pet recognition and clustering requirements plus a development guide - under `docs/requirements/pets-cluster/`; these documents describe planned - work and do not yet represent shipped runtime behavior. - -#### 🍎 macOS Media Rendering -- Added platform QRhi backend selection via `IPHOTO_RHI_BACKEND`; macOS now - prefers Metal when Qt exposes it, while Windows and Linux keep the OpenGL path. -- Added a QRhi-backed image/video adjustment renderer with QSB shader assets for - image preview, crop overlay, LUTs, and adjusted-video frames. -- Routed macOS long-press video previews through the RHI popup path so adjusted, - rotate-only, and plain previews share the stable GPU surface. -- Improved high-DPI crop and pan math by converting logical viewport - coordinates through the actual QRhi render-target scale. - -#### 🗺️ Maps Runtime & macOS GL Stability -- Added macOS OsmAnd runtime discovery for `dist-macosx` helper/widget builds - and a `scripts/sync_macos_map_extension.py` workflow that copies resources, - search data, `.dylib` binaries, dependencies, rpaths, and ad-hoc signatures. -- Switched the macOS legacy GL map to `QOpenGLWindow + createWindowContainer()` - to avoid transparent `QOpenGLWidget` FBO composition in the frameless main - window. -- Hardened map surfaces with opaque backing colors, full-update repaint - behavior, optional `IPHOTO_MAP_GL_DEBUG` diagnostics, and GL marker rendering - inside supported map passes. -- Extended standalone map preview backend selection with explicit - `auto/native/python/legacy` modes and runtime diagnostics. - -#### 📍 Assign Location Resilience -- Assign Location now persists the selected place to `global_index.db` even when - ExifTool is missing or the original file metadata write fails. -- Added user-facing warnings for missing ExifTool or failed GPS write-back while - keeping the local database assignment intact. -- Sanitized metadata updates before JSON storage so non-serializable third-party - values cannot corrupt asset rows. - -#### 📦 Packaging & Tests -- Updated the Windows Nuitka script to bundle QRhi image/overlay/video shader - assets and the `maps` package alongside the maps extension. -- Added regression coverage for render backend selection, macOS map GL surface - formats, native map widget event targets, RHI overlay rendering, map runtime - sources, macOS extension sync, location assignment fallback, preview windows, - and worker-side image scaling. - ---- - -## 🚀 v6.0.0 — People, Face Clusters, Groups & Linux Maps Runtime - -👥 *A major People release with automatic face clustering, persistent People -state, multi-person groups, richer location metadata, and broader Windows/Linux -runtime packaging.* - -### Key Updates - -#### 👥 People Face Clusters -- Added the optional **People face-scanning pipeline** powered by InsightFace - and ONNXRuntime through the `ai-demo` extra. -- Detects faces in image assets, writes cropped face thumbnails, builds face - embeddings, and clusters them into persistent People cards. -- Added background face-scan scheduling alongside the normal asset scan, with - `pending`, `done`, `skipped`, `retry`, and `failed` status tracking in the - global asset index. -- Rebuilt the People persistence model around a rebuildable runtime snapshot - plus stable People state so names, covers, ordering, hidden flags, and group - decisions survive rescans and reclustering. - -#### 👨‍👩‍👧 People Groups & Dashboard Workflow -- Added **People groups** for collecting photos where multiple selected people - appear together. -- Group cards support shared-photo queries, cover selection, drag ordering, - pinned state, and safe disbanding without deleting the underlying people or - photos. -- Added People card actions for naming, merging, hiding/unhiding, cover - management, and dashboard filtering for hidden people. -- Hardened merge safety so hidden and visible people cannot be merged by - accident. -- Split the People dashboard into focused board, card, dialog, shared, and - widget modules for easier testing and future iteration. - -#### 🗺️ Location & Info Panel Improvements -- Added an embedded location map to the floating info panel so geotagged assets - can show map context directly in metadata view. -- Added location assignment plumbing and background tasks for updating selected - asset coordinates. -- Improved map source handling, OsmAnd search support, and map widget runtime - behavior. -- Extended Linux maps support with helper-backed OBF rendering and the native - OsmAnd widget runtime when the required shared libraries are present. - -#### 🧩 Albums, Menus & Pinned Items -- Added persistent pinned-item services for albums, people, and groups. -- Expanded sidebar and gallery context-menu plumbing with shared menu styling - and consistent action handling. -- Improved album dashboard/sidebar behavior, cover actions, rename/delete - workflows, and album tree model coverage. -- Added project popup guardrails so routine warnings and confirmations use the - app-themed popup system instead of native `QMessageBox` surfaces. - -#### ⚙️ Scanning, Indexing & Packaging -- Improved the scan pipeline with chunked persistence, scan-merge behavior, and - global repository tests for move/delete and status preservation scenarios. -- Added People cover caching and thumbnail cache services for faster dashboard - rendering. -- Updated Nuitka and Debian packaging guidance for bundled `insightface`, - `onnxruntime`, `extension/models`, Linux maps runtime files, and People-page - release smoke tests. -- Added troubleshooting guidance for packaged face-scan failures, runtime typing - compatibility issues, model-cache problems, and Linux XCB/GLX map startup. - -#### 🧪 Tests & Reliability -- Added focused coverage for People pipeline clustering, People repositories, - People service behavior, People dashboard widgets, group workflows, hidden - state, merge guards, and cover persistence. -- Added tests for info panel maps, map extension download tasks, gallery and - playback coordinators, album sidebar/model behavior, scan/index sync, and - Linux map source handling. -- Improved packaged-runtime diagnostics so asset-level face failures are logged - and retried without deadlocking the full People scan. - ---- - -## 🚀 v5.0.0 — Video Editing, Trim System & Platform Stability - -🎬 *Full non-destructive video editing, a visual trim timeline, centralized keyboard shortcuts, and a sweeping round of Linux/GL stability fixes.* - -### Key Updates - -#### ✂️ Video Editing Workflow -- Introduced a **complete non-destructive video editing suite** powered by a smooth OpenGL-accelerated preview pipeline. -- Added a **visual trim timeline** with thumbnail strip, draggable in/out handles, and real-time playhead clamping inside the trim range. -- Trim in/out points are saved to the sidecar file and **persist across app restarts**; the gallery duration badge now reflects the trimmed length. -- **Playback progress bar** is remapped to the active trim range so the scrubber always tracks the visible portion of the clip. -- Fixed stale progress bar after returning from edit mode — duration is force-synced when the video is reloaded. -- Added **video transport keyboard shortcuts** (seek, play/pause, frame-step) inside the video edit panel. -- Simplified the video edit sidebar section layout for a cleaner editing experience. - -#### ⌨️ Centralized Keyboard Shortcuts -- Introduced **`AppShortcutManager`** — a single class that owns all application-level keyboard bindings, eliminating duplicated shortcut setup across components. -- **Space bar** play/pause now works reliably in both gallery view and detail view. -- Added full **gallery-mode video shortcuts**: volume up/down, mute toggle (M key), and playback controls. -- Fixed M-key mute shortcut registration; renamed shortcut volume constant for clarity. - -#### 🔍 Zoom Handle in Gallery / Detail View -- Added a **resize/zoom handle** in the video header bar so users can adjust the preview size directly from the gallery and detail view without entering edit mode. -- Zoom state is emitted via `zoomChanged` and properly synced when switching renderer surfaces or clearing a frame. - -#### 🐧 Linux GL Playback Stability -- Fixed a **critical black-screen bug** in adjusted (edited) video playback on Linux caused by Qt's QRhi context not owning the OpenGL state needed by the custom GL renderer. -- Aligned the Linux QRhi GL viewer with the Qt GL context; used Qt GL functions for VAO creation with an automatic fallback when QRhi rejects VAO binds. -- Hardened GL matrix uniform uploads and stabilized video frame dispatch on the GUI thread to eliminate flicker and rotation artifacts on Linux. -- Snapshots non-packed video frames before uploading to prevent intermittent corruption. -- Fixed Linux edit-preview viewport sizing and queuing of frames to avoid dropped frames during playback. - -#### 🖼️ Crop Overlay & Edit Transition Fixes -- Fixed **crop overlay disappearing** when the overlay VAO was unavailable — the renderer now falls back to default vertex-array state so the orange crop frame always renders. -- Cleared stale pre-existing GL errors before binding the overlay VAO; those errors were silently disabling the overlay on Linux. -- Restored correct **crop-frame fade behavior**: the orange border always remains visible; only handles and guides are suppressed in faded state. -- Deferred restoration of the detail chrome and filmstrip until the edit-exit animation fully completes, preventing layout jumps. -- Respected the user's filmstrip visibility preference when leaving edit mode. -- Fixed video canvas proportions after exiting edit mode by using `crop_center_zoom_strength=1.0` in detail/non-edit mode. - -#### 🎞️ Video Metadata & Info Panel -- Implemented **multi-level cross-brand lens extraction** for both video and image assets — the info panel now resolves lens model, focal length, and aperture from multiple ExifTool fields across all major camera brands. -- ExifTool is invoked during video playback enrichment to populate lens/focal-length fields that are absent from the media container. -- Fixed normalization of raw `LensInfo` tuples (e.g. `"23 23 2 2"`) into human-readable strings (`"23mm f/2"`). -- Eliminated duplicated focal-length/aperture suffixes when the lens string already contains mm notation. -- Fixed the `ƒ` aperture format in the info panel display. - -#### 🐛 Fixes of major bugs -- Fixed video rotation not refreshing immediately when changed in playback mode. -- Fixed `ExternalToolError` not being caught explicitly during playback metadata enrichment, preventing silent failures. -- Fixed `_restore_detail_video_preview` to correctly use `video_requires_adjusted_preview` and pass raw adjustments on the native render path. -- Fixed out-point reset when re-entering edit mode — `PlaybackCoordinator` now guards trim remapping in edit mode. -- Fixed SHA-1 usage in temp file naming replaced with **SHA-256** for stronger stability guarantees. -- Fixed GL `glBindTexture` redundancy before `glGenerateMipmap` calls. -- Used `ctypes c_uint` GL id buffers to avoid numpy dtype warnings on Windows. -- Fixed `QShortcut` parent widget to use the top-level window instead of a nested widget, preventing shortcuts from silently failing. -- Fixed several other minor bugs and improved overall stability. - ---- - -## 🚀 v4.6.0 — Windows Maps Extension & Offline OsmAnd Runtime - -🗺️ *A new Windows-only maps extension brings the offline OsmAnd/OBF runtime into iPhotron, with clearer packaging, installer integration, and a documented upstream build workflow.* - -### Key Updates - -#### 🗺️ Windows Maps Extension -- Added a self-contained **maps extension** rooted at `src/maps/tiles/extension/` for the offline OBF map runtime. -- The bundled extension now carries `World_basemap_2.obf`, OsmAnd resources, and native runtime binaries in one predictable layout. -- Windows builds can use the native OsmAnd widget runtime for a fuller offline map experience while keeping the repository self-contained. - -#### ⚙️ Runtime Selection & Fallback Behavior -- Improved map backend startup so iPhotron can prefer the native Windows widget when the runtime is healthy. -- Preserved the Python/helper-backed OBF renderer as a practical fallback path. -- Linux and macOS continue using the existing Python / legacy map path while the native maps extension remains Windows only. - -#### 📦 Packaging & Installer Integration -- Aligned local development, Nuitka packaging, and the Windows installer around the same extension directory contract. -- Documented how the extension is synchronized into packaged builds and optional installer assets. -- Made Windows release work more reproducible by standardizing which runtime artifacts ship with the application. - -#### 🧰 Upstream Build Workflow -- Split the OsmAnd runtime build pipeline into the dedicated - [PySide6-OsmAnd-SDK](https://github.com/OliverZhaohaibin/PySide6-OsmAnd-SDK) side project. -- Added clearer developer documentation for building, syncing, and validating the maps extension from the upstream workspace. -- Improved the handoff between runtime experimentation in the side project and release packaging in the main iPhotron repository. - ---- - -## 🚀 v4.5.0 — Color Grading Expansion & Video Compatibility Improvements - -🎨 *A richer color grading workflow, new creative tools, stronger video compatibility, and more native desktop window behavior.* - -### Key Updates - -#### 🎨 Expanded Color Grading Workflow -- Further refined the color grading experience for smoother, more precise adjustment workflows. -- Added new editing tools including **Definition**, **Noise Reduction**, **Sharpen**, and **Vignette**. -- **Sharpen** includes dedicated `Intensity`, `Edges`, and `Falloff` controls, while **Vignette** adds `Strength`, `Radius`, and `Softness` adjustments. -- Improved the overall editing flow to make advanced adjustments feel more consistent and intuitive. - -#### 🎬 Video Preview & Playback Fixes -- **Fixed preview black borders:** Videos now render correctly in preview mode without unwanted letterboxing artifacts. -- **Fixed HEVC and HDR display issues:** Improved compatibility for modern video formats to ensure more reliable playback and preview rendering. -- Better overall media presentation consistency across different codecs and dynamic-range formats. - -#### 🐧 Linux Video Thumbnail Reliability -- **Fixed incorrect thumbnail orientation on Linux:** Resolved an intermittent issue that could generate video thumbnails with the wrong rotation. -- Improved thumbnail generation stability for rotated and metadata-sensitive video sources on Linux systems. - -#### 🪟 Native Window Snapping -- Added support for native window snapping behavior to better match each platform's built-in desktop experience. -- Window management now feels more natural and integrated across supported operating systems. - ---- - -## 🚀 v4.3.0 — Linux Alpha, RAW Support & Crop Refinements - -📸 *Linux enters Alpha testing, RAW workflows arrive, and cropping becomes more precise and familiar.* - -### Key Updates - -#### 🐧 Linux Version Enters Alpha Testing -- The **Linux version is now officially in Alpha testing**, bringing the iPhotron experience to a whole new platform. -- Early Linux builds extend photo management workflows beyond Windows and macOS while broader compatibility work continues. - -#### 📷 Native RAW Image Support -- Added support for **RAW format images**. -- You can now seamlessly import, view, and manage uncompressed, high-quality RAW photos directly inside your library. - -#### ✂️ Aspect Ratio Constraints for Cropping -- Added aspect ratio constraint options to the crop tool. -- The cropping workflow now feels closer to the native macOS Photos experience, making edits more intuitive, precise, and familiar. - -#### 🐛 Fullscreen and General Bug Fixes -- Fixed a bug affecting fullscreen mode to ensure a more seamless and reliable viewing experience. -- Resolved a range of smaller issues under the hood to improve overall stability. - ---- - -## 🚀 v4.1.0 — MVVM Refinement & Major Scrolling Performance Boost - -📸 *A more complete MVVM foundation with dramatically smoother scrolling and more stable large-library browsing.* - -### Key Updates - -#### 🏗️ MVVM Architecture — More Complete, More Stable State-Driven UI -- **Stronger MVVM boundaries:** Clearer responsibilities across View / ViewModel / Model reduce cross-layer coupling and implicit dependencies. -- **Upgraded state management:** Standardized UI State (`Loading / Content / Empty / Error`) helps prevent edge-case rendering divergence. -- **More consistent unidirectional data flow:** The View only subscribes to ViewModel outputs, while all mutations enter through the ViewModel. -- **Better testability:** Critical logic moved into ViewModel plus UseCase/Service layers for finer unit testing and safer regression coverage. -- **Lifecycle & resource governance:** Subscriptions and async tasks are properly scoped and disposed with lifecycle events to reduce leaks and background overhead. - -#### ⚡ Scrolling Performance Boost — Dramatically Smoother Browsing -- **Lighter rendering pipeline:** Reduced unnecessary re-renders and layout recalculations for steadier high FPS while scrolling. -- **Enhanced virtualization for lists and grids:** Improved visible-range computation and reuse strategy to lower UI workload on large datasets. -- **Smarter thumbnail loading:** Prefetching and prioritization now focus on on-screen items, with progressive loading and better decode scheduling. -- **Cache improvements:** Multi-level caching (`memory + disk`) with smarter eviction stabilizes hit rate and reduces redundant decoding. -- **Async task coordination:** Better debouncing and coalescing for rapid scroll events helps avoid main-thread contention and request storms. -- **Lower memory churn:** Fewer transient allocations during fast scrolling reduce GC/ARC pressure and micro-stutters. - ---- - -## 🚀 v4.00 — MVVM Architecture & Advanced Editing - -📸 *MVVM architecture for smooth performance, color curves support, and cluster-based map browsing.* - -### Key Updates - -#### 🏗️ MVVM Architecture — Dramatically Improved Performance -- Complete architectural refactoring to **Model-View-ViewModel (MVVM)** design pattern. -- Clear separation between UI presentation, business logic, and data management layers. -- Reactive UI updates — ViewModel efficiently manages state changes and automatically updates the View. -- Significantly lower UI freezing and lag during photo browsing, editing, and library management. -- Improved memory usage and CPU efficiency through proper data binding and lifecycle management. - -#### 🎨 Advanced Color Grading Tools - -- **White Balance:** Dedicated panel with Neutral Gray / Skin Tone / Temp & Tint modes; eyedropper sampler for automatic reference white point estimation; Warmth slider with gradient track. -- **Color Curves:** RGB Master curve + individual R/G/B channel curves; interactive editor with draggable control points; Bezier interpolation; histogram overlay. -- **Selective Color:** Six hue-range targets (Red/Yellow/Green/Cyan/Blue/Magenta); independent Hue/Saturation/Luminance controls; feathered hue-distance masking. -- **Levels:** 5-handle input-output tone mapping; per-channel control (RGB/R/G/B); histogram backdrop; smooth interpolation. - -#### 🗺️ Cluster-Based Map Browsing -- Smart clustering: automatically groups nearby photos based on GPS coordinates. -- Dynamic cluster sizing adapts to zoom level and photo density. -- Efficient rendering of thousands of GPS-tagged photos. - ---- - -## 🚀 v3.00 — Performance Overhaul - -⚡ *Migration to SQLite with global database architecture, optimized for TB-level libraries.* - -### Key Updates - -#### ⚡ Backend Migration to SQLite with Global Database Architecture -- Complete backend rewrite from JSON-based indexing to **SQLite-powered global database**. -- Single database design — all metadata in one high-performance SQLite database at library root. -- Massive scalability for TB-level photo libraries with hundreds of thousands of files. -- Smart indexing on `parent_album_path`, `ts`, `media_type`, and `is_favorite`. - -#### 🏗️ Modular Architecture Refactoring -- 1100+ line monolithic index store split into 5 focused modules: `engine.py`, `migrations.py`, `recovery.py`, `queries.py`, `repository.py`. -- 100% backward compatible. - -#### 🛡️ Enhanced Robustness & Efficiency -- Reduced RAM and CPU footprint. -- Automatic recovery with graded repair strategies (REINDEX → Salvage → Reset). -- WAL mode for better concurrency and crash recovery. - -#### 💾 Unified Global Cache System -- Single global database replaces scattered `.iPhoto/index.jsonl` files. -- Centralized management for easier backup and sync. - ---- - -## 🌓 v2.3.0 — Dark Mode - -📸 *Seamlessly switch between Light and Dark themes.* - -### Key Updates - -#### 🌓 Comprehensive Dark Mode Support -- Three theme options: System Default, Light Mode, Dark Mode. -- Intelligent theme application across the entire UI. -- Edit mode automatically switches to dark theme for optimal color grading. -- Instant theme switching — no restart required. -- Theme-aware components: sidebar, asset grid, detail viewer, info panel, edit panels, context menus. - -#### Additional Improvements -- Enhanced edit mode experience with consistent dark theme. -- Refined color palette with improved accessibility contrast ratios. -- Performance optimizations for faster theme switching. -- Native detection of macOS and Windows system theme preferences. - ---- - -## 🐛 v2.1.1 — Bug Fixes and UI Improvements - -### Key Updates - -#### 🐛 Bug Fixes -- **Fixed thumbnail synchronization:** After editing photos, thumbnails in aggregated albums now sync properly. -- **Fixed gallery grid auto-sizing:** Grid view dynamically responds to window resizing. - -#### 🎨 UI Improvements -- Refined album interface to more closely replicate the macOS Photos experience. -- Improved visual consistency, layout spacing, transitions, and animations. - ---- - -## 🚀 v2.00 — Non-Destructive Photo Editing - -📸 *Comprehensive non-destructive editing suite with Adjust and Crop modes.* - -### Key Updates - -#### 🎨 Non-Destructive Photo Editing -- **Adjust Mode:** Light adjustments (Brilliance, Exposure, Highlights, Shadows, Brightness, Contrast, Black Point), Color adjustments (Saturation, Vibrance, Cast), Black & White mode (Intensity, Neutrals, Tone, Grain). -- **Crop Mode:** Perspective correction, Straighten tool (±45°), horizontal flip, interactive crop box with edge snapping. -- All edits stored in `.ipo` sidecar files — originals remain untouched. -- GPU-accelerated preview with real-time OpenGL 3.3 rendering. - -#### 💾 Export System -- Export selected photos or all edited photos. -- Configurable export destination (Basic Library or Ask Every Time). - ---- - -## 🚀 v1.00 — First Stable Release - -📸 *A modern, folder-native photo manager for Windows and macOS.* - -### Key Features -- **🎥 Live Photo Support:** Auto-pairs HEIC/JPG + MOV files by content-ID or timestamp. -- **🗺 Interactive Map View:** GPS metadata visualization on an interactive map. -- **🗂 Folder = Album:** Each folder becomes an album via `.iphoto.album.json`. -- **🧠 Smart Albums:** Library, All Photos, Videos, Favorites, Recently Deleted. -- **🖼 Immersive Detail Viewer:** Filmstrip navigation and floating playback controls. -- **ℹ️ Floating Metadata Panel:** EXIF, camera/lens info, exposure, aperture, file size. -- **⚙️ Rich Interactions:** Drag-and-drop, context menus, incremental scanning, async thumbnail loading. +### Pets runtime + +- Pets remains an optional bounded context with rebuildable `pet_index.db` and + durable `pet_state.db`. +- The production clustering pipeline is `species-bounded-single-link-v3`. +- Identity clustering is species-separated, applies cannot-link constraints, + and bounds cluster diameter to prevent uncontrolled single-link chaining. +- People/Pets conflict filtering is geometry-based but is not an unconditional + “People always wins” rule. Strong face overlap normally suppresses a pet + candidate; a substantially larger plausible pet-body detection containing a + smaller face may be preserved by the runtime size/image-coverage exception. +- People and Pets keep independent runtime/durable ownership while the combined + UI can compose them into cards, groups, gallery queries, and annotations. + +### Model trust and delivery + +- Production Pets inference does not execute arbitrary Torch Hub Python. +- The pinned DINOv2 upstream revision is a release conversion/provenance input; + production runtime trust is the prebuilt TorchScript artifact plus manifest + SHA-256 and exact-size validation. +- The current Pets manifest has `torchscript_url: null`; DINOv2 therefore must + be packaged or explicitly staged rather than being promised as a first-use + runtime download. +- `src/extension/models/...` is a build/staging convention, not guaranteed + tracked content of a fresh clone. + +## Unreleased — Gallery / Detail GPU-first Rendering + +- Gallery browsing uses sparse asynchronous SQL-backed windows rather than + materializing entire collections. +- Viewport demand separates visible, guard, speculative, and micro-thumbnail + work and rejects stale generations. +- Normal Gallery-visible rows require ready thumbnail state and a non-empty + thumbnail key; repair/backfill rows do not masquerade as ready media. +- Detail still/video opens share a generation-safe render transaction. +- Static Detail and Edit share `PhotoRenderSessionHandle`, source surfaces, GPU + residency, and immutable edit state instead of restoring a parallel CPU + full-image preview path. +- Platform decoding/rendering remains adapter-specific: QRhi/Metal is preferred + on macOS, QRhi/OpenGL on Windows/Linux, with platform decoder fallbacks inside + the existing worker lane. + +## Unreleased — Documentation & Packaging Contracts + +- `docs/architecture.md`, `AGENT.md`, and `docs/development.md` now use the + current desktop/recognition/session contracts. +- Pets historical clustering requirements are explicitly marked as historical; + `docs/misc/PETS_RECOGNITION_RUNTIME.md` is the canonical runtime note. +- `docs/requirements/README.md` defines Active, residual-debt, + Historical/Superseded, and Finished requirement states. +- Debian packaging derives the version from `pyproject.toml` and wraps the + current `dist/entrypoint.dist/` standalone bundle. +- AppImage has an in-repository build guide matching `scripts/build_appimage.sh`. +- Flatpak documentation distinguishes the existing v6.6.8 release bundle from + current source-build support: this branch does not yet contain a maintained + Flatpak manifest/build driver. +- Maintained English, Simplified Chinese, and German README files distinguish + published v6.6.8 binaries from development-branch / Unreleased capabilities. +- Documentation CI validates local Markdown links and README release-artifact + parity to reduce future localization drift. + +## v6.6.8 — Published Release Baseline + +The download section in the maintained README files targets the published +v6.6.8 artifacts. Those binaries are a release baseline and must not be assumed +to include every feature described under the Unreleased sections above. + +Current development documentation intentionally describes the `edit-base` +branch. When a feature is promoted into a release, move its user-facing release +status from Unreleased into the corresponding versioned release notes and keep +all maintained README languages aligned. + +For older detailed release history, use the repository's Git history and GitHub +Releases. The pre-sync changelog remains available in commits prior to this +documentation-contract cleanup. diff --git a/docs/architecture.md b/docs/architecture.md index b7c896b0a..d67a07496 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -1,121 +1,67 @@ # Architecture vNext -This document describes the current production architecture of iPhotron after -the vNext cleanup. The codebase is a library-scoped modular desktop monolith: -one process-level `RuntimeContext` owns one active `LibrarySession`, and GUI, -CLI, watchers, and workers enter behavior through application surfaces rather +This document describes the current production architecture of iPhotron. The +application is a library-scoped modular desktop monolith: one process-level +`RuntimeContext` owns at most one active `LibrarySession`, and GUI, CLI, +watchers, and workers enter behavior through application/session surfaces rather than legacy facades. -For completed migration records and verification history, see +Completed migration records and verification history live under `docs/finished/refactor/vnext-2026-06/`. ## Status -The vNext architecture cleanup is complete for production source code. - -- Production runtime code no longer imports `iPhoto.legacy` or `iPhoto.models.*`. -- The compatibility application tree and old domain-repository implementation - under `src/iPhoto/legacy/` have been removed. +- Production runtime code does not import `iPhoto.legacy` or + `iPhoto.models.*`. +- The removed compatibility application tree is not a production extension + point. - `RuntimeContext -> LibrarySession` is the active library entry path. -- Application ports and services define the boundary used by GUI, CLI, workers, - People, Maps, Edit, thumbnails, and lifecycle operations. -- Gallery-to-Detail rendering has converged on one GPU-first production path. - Still and video opens share a generation-safe render transaction; static +- Application ports/services define the boundary used by GUI, CLI, workers, + People, Pets, Maps, Edit, thumbnails, scanning, and lifecycle operations. +- Desktop presentation composition is owned by `DesktopCoordinatorRuntime`. + `gui/coordinators/main_coordinator.py` is a compatibility import only. +- Gallery-to-Detail rendering uses one GPU-first production path. Static Detail/Edit share source surfaces, GPU residency, and immutable edit state. +- People/Pets inference is feature-driven, not an application-startup task. - Architecture guardrails are enforced by `tools/check_architecture.py` and `tests/architecture`. -The remaining pre-release checks are product validation tasks, such as manual Qt -GUI smoke testing and opening an existing library. They do not change the -architecture convergence status. - ## Product Principles -- **Folder-native library.** The filesystem remains the user's album structure. - A folder is an album, and browsing must not require import. +- **Folder-native library.** The filesystem remains the album structure; no + import step is required. - **Local-first runtime.** Library state lives under `/.iPhoto/`. - Core workflows do not depend on cloud services. -- **Non-destructive editing.** Visual edits are persisted as `.ipo` sidecars. - Original media is preserved. -- **Explicit metadata write-back.** Assign Location persists local state first, - then best-effort writes GPS metadata to original media through ExifTool and - reports warnings on failure. -- **Rebuildable facts vs durable choices.** Scan rows, thumbnails, Live Photo - materialization, and People/Pets runtime snapshots can be rebuilt. Favorites, - hidden/trash state, pinned items, covers, ordering, manual metadata, People - names/groups/manual faces, and Pets names/covers/hidden/rejected decisions are - durable user state. +- **Non-destructive editing.** Visual edits persist as `.ipo` sidecars. +- **Explicit metadata write-back.** Assign Location saves local state first, + then best-effort writes GPS metadata through ExifTool. +- **Rebuildable facts vs durable choices.** Scan rows, caches, thumbnails, + People runtime snapshots, Pets runtime snapshots, and Live Photo + materialization can be rebuilt. Favorites, hidden/trash state, pinned state, + album order, manual metadata, People names/groups/manual faces/covers, and + Pets names/covers/hidden/rejected decisions are durable. - **Optional bounded contexts.** People AI, Pets AI, and Maps native/runtime - extensions are optional and must degrade gracefully when missing. -- **Cross-platform desktop first.** macOS, Windows, and Linux are supported - through runtime adapters and platform-specific rendering choices. + extensions degrade gracefully when absent. +- **Cross-platform desktop first.** macOS, Windows, and Linux are supported via + runtime adapters and platform-specific rendering choices. ## Architecture Shape ```mermaid graph TB - subgraph Runtime["Runtime / Bootstrap"] - RuntimeContext["RuntimeContext"] - LibrarySession["LibrarySession"] - SessionSurfaces["Session Services"] - end - - subgraph GUI["GUI / PySide6 Presentation"] - Views["Views / Widgets"] - ViewModels["ViewModels"] - Coordinators["Coordinators"] - QtAdapters["Qt Workers / Signals"] - end - - subgraph Application["Application"] - Ports["application/ports"] - UseCases["Use Cases"] - AppServices["Application Services"] - DTOs["DTOs / Queries / Events"] - end - - subgraph Domain["Domain"] - Models["Models / Value Objects"] - PureServices["Pure Domain Services"] - end - - subgraph Infrastructure["Infrastructure"] - SQLite["SQLite / Index Store Adapters"] - Manifest["Manifest / Sidecar Adapters"] - Metadata["ExifTool / FFmpeg Adapters"] - Scanner["Filesystem Scanner Adapter"] - Thumbnails["Thumbnail Cache / Renderer"] - PeopleInfra["People Runtime / State"] - PetsInfra["Pets Runtime / State"] - MapsInfra["Maps Runtime Adapter"] - end - - RuntimeContext --> LibrarySession - LibrarySession --> SessionSurfaces - SessionSurfaces --> Ports - SessionSurfaces --> UseCases - SessionSurfaces --> AppServices - - Views --> ViewModels - Coordinators --> ViewModels - ViewModels --> SessionSurfaces - QtAdapters --> SessionSurfaces - - UseCases --> Ports - AppServices --> Ports - UseCases --> Domain - AppServices --> Domain - Domain --> Models - Domain --> PureServices - - SQLite -.implements.-> Ports - Manifest -.implements.-> Ports - Metadata -.implements.-> Ports - Scanner -.implements.-> Ports - Thumbnails -.implements.-> Ports - PeopleInfra -.implements.-> Ports - PetsInfra -.implements.-> Ports - MapsInfra -.implements.-> Ports + RuntimeContext[RuntimeContext] --> LibrarySession[LibrarySession] + LibrarySession --> SessionSurfaces[Session/Application Surfaces] + + GUI[PySide6 GUI] --> SessionSurfaces + CLI[CLI / headless callers] --> SessionSurfaces + Workers[Workers / watchers] --> SessionSurfaces + + SessionSurfaces --> Application[Application ports/services/use cases] + Application --> Domain[Domain values/pure services] + + Infrastructure[Infrastructure adapters] -.implements.-> Application + People[People bounded context] --> Application + Pets[Pets bounded context] --> Application + Maps[Maps bounded context] --> Application ``` Allowed dependency direction: @@ -138,578 +84,268 @@ production runtime -> iPhoto.models.* ## Runtime Contract -`RuntimeContext` is the process composition root. - -```text -RuntimeContext - settings - translation - theme - library - facade - event_bus - asset_runtime - recent_albums - defer_startup_tasks - container - library_session: LibrarySession | None - open_library(root) - close_library() - resume_startup_tasks(defer_scan=False) - start_deferred_startup_scan() - remember_album(root) -``` +`RuntimeContext` is the process composition root. It owns process-level +settings, translation/theme, recent-library state, the current library runtime, +and the active `LibrarySession` lifecycle. `LibrarySession` owns library-scoped adapters and exposes application-facing -surfaces. +surfaces, including: ```text -LibrarySession - library_root - state_repository - assets - state - asset_queries - asset_state - album_metadata - scans - asset_lifecycle - asset_operations - thumbnails - people - pets - maps - map_interactions - edit - locations - shutdown() -``` - -CLI and other non-GUI callers create the same session boundary through -`create_headless_library_session(root)`. - -Production GUI and CLI do not create independently bound fallback services. -Widgets may receive an unbound `PeopleService()` or `PetService()` as a -presentation-safe empty object before a library session exists, but it must not -open repositories or become a second source of library truth. Session-bound -services fail explicitly or no-op safely according to their responsibility. - -`TranslationManager` is created after settings and before theme initialization. -It reads `ui.language`, installs the active Qt translator, exposes -`languageChanged`, and falls back to English when the requested or system -language has no bundled resource. The active stored language values are -`system`, `de`, and `zh-CN`; the language menu presents `system` as `English`. - -Desktop construction deliberately has two boundaries. The process first loads -settings, configures graphics caches, creates `QApplication`, `RuntimeContext`, -and the lightweight main-window shell, then calls `show()`. Only after the shell -emits `firstPainted` may startup create hidden feature bundles, import and start -`MainCoordinator`, resume library startup tasks, or select the initial -collection. This is an architecture constraint rather than a timer-based -optimization: optional feature imports must not migrate back above the first -paint boundary. - -`Ui_MainWindow.ensure_feature()` owns the on-demand lifetime of the detail, -preview, Map, People, and Albums bundles. It caches each bundle and emits -`featureCreated` so the window manager and coordinator can attach behavior to -late-created widgets. Navigation may also call `ensure_feature()` if Map or the -Albums dashboard was not constructed during the post-paint warm-up. On Windows -and Linux, the QRhi-backed detail bundle is constructed before `show()` because -inserting it into a visible top-level window can recreate the native window; -preview and People remain post-paint. macOS keeps all three in the post-paint -warm-up path. - -## Layer Boundaries - -### Domain - -`domain/` owns dataclasses, value objects, query models, and pure domain -services. It must not import Qt, SQLite, filesystem-writing adapters, ExifTool, -FFmpeg, GUI helpers, or runtime singletons. - -### Application - -`application/` owns workflow use cases, application services, DTOs, queries, -events, and port protocols. Application code depends on ports and domain values. -It must not import GUI modules, concrete persistence modules, Qt workers, -widgets, or process-wide repository singletons. - -Current public boundary names include: - -| Port | Responsibility | -| --- | --- | -| `AlbumRepositoryPort` | Folder-local album manifest read/write without exposing legacy shims upstream. | -| `AssetRepositoryPort` | Asset query, count, scan merge, state update, and transaction semantics for the current library store. | -| `AssetFavoriteQueryPort` | Favorite-state reads through a session-owned query surface. | -| `AssetStateServicePort` | Durable asset-state commands such as favorite toggles. | -| `EditServicePort` | Session-scoped edit sidecar and render-state surface. | -| `EditSidecarPort` | `.ipo` read/write and edit state persistence. | -| `LibraryStateRepositoryPort` | Durable library user-state boundary for favorites, hidden/trash, pinned/order, and related state. | -| `LocationAssetServicePort` | Session-bound geotagged asset queries. | -| `LocationMetadataPort` | Explicit location assignment metadata write/read-back boundary. | -| `MediaScannerPort` | Media discovery and normalized scan candidates without persistence ownership. | -| `MetadataReaderPort` | Image/video metadata reads. | -| `MetadataWriterPort` | Explicit best-effort metadata writes such as Assign Location GPS write-back. | -| `MapInteractionServicePort` | Marker-click and map interaction semantics. | -| `MapRuntimePort` | Maps extension availability and runtime adapter selection. | -| `PeopleAssetRepositoryPort` | Asset-index reads and face-status updates used by the People bounded context. | -| `PeopleIndexPort` | People scan candidate enqueue, snapshot commit, and People/group queries. | -| `PetAssetRepositoryPort` | Asset-index reads plus `pet_status` updates/counts used by the Pets bounded context. | -| `PetIndexPort` | Pets candidate enqueue and rebuildable snapshot commit boundary. | -| `PinnedStateRepositoryPort` | Pinned sidebar state persistence across libraries. | -| `TaskSchedulerPort` | Background task submission and cancellation boundary. | -| `ThumbnailRendererPort` | Thumbnail/preview generation without GUI ownership. | - -### Large Library Query And Scan Contracts - -Large-library browsing is part of the current production architecture, not a -future side design. The active query path is SQL-first and windowed: - -- `CollectionQuery`, `PageCursor`, `PageResult`, and `WindowResult` describe - collection intent, keyset pages, and bounded viewport windows. -- `LibraryAssetQueryService` converts supported `AssetQuery` reads to - repository-backed collection SQL for All Photos, albums, favorites, videos, - maps/GPS, media type, and date filters. -- `GalleryCollectionStore` treats `asset_at()` as cache-only and loads bounded - windows asynchronously through `GalleryWindowLoader` and - `read_gallery_asset_window()`; direct row lookup uses `find_row_by_path()` - rather than scanning the model. Loaded chunks are merged into a bounded - sparse cache, and stale generation/revision results are discarded. -- Gallery SQL has two narrow projections in addition to the general collection - window: `read_gallery_collection_window()` returns tile-rendering fields, and - `read_thumbnail_hint_window()` returns only paths and existing full-thumbnail - keys without repeating the collection count. -- `GalleryViewportDemand` is the contract between scrolling, sparse row loading, - and thumbnail scheduling. It separates visible rows, the full-thumbnail guard, - far speculative full-thumbnail work, and the wider micro-thumbnail warm range. -- Delegates consume one `GalleryTileSnapshot` through `TILE_SNAPSHOT`. A paint - miss must remain memory-only; it schedules bounded background work and paints - an available micro thumbnail or placeholder instead of reading SQLite or L2. -- Normal gallery collections default to `thumbnail_state='ready'` and require a - non-empty `thumb_cache_key`; stale, pending, failed, or old no-key rows are - repair/backfill candidates, not visible media grid rows. -- Scan publishing uses `ScanBatchCommitted`, split into small ready-row batches - after DB commit. Production code must not restore the old `scanChunkReady` - transport for real-time UI updates. -- `scan_jobs` and `scan_events` persist job state, stage changes, batch commit - metadata, and stage timing for scan observability. - -### Infrastructure - -`infrastructure/` owns concrete adapters: SQLite-backed state, manifest and -sidecar persistence, ExifTool/FFmpeg wrappers, filesystem scanners, thumbnail -caches/renderers, maps runtime discovery, and supporting runtime services. It -implements application ports and may depend on domain values. It must not import -GUI modules or own product workflow decisions. - -### GUI - -`gui/` owns PySide6 presentation: views, widgets, controllers, viewmodels, -coordinators, menus, shortcuts, Qt workers, and signal adapters. GUI code calls -session/application surfaces and does not directly write durable state or call -concrete repository singletons. - -The static media pipeline is a GUI/rendering boundary rather than an application -workflow service. `DetailRenderCoordinator` owns the active still/video -transaction and its single terminal state. `DetailStillRequestScheduler` -deduplicates one source revision and decode level, while platform decoders -produce detached neutral RGBA8888/sRGB surfaces. `PhotoRenderSessionHandle` -shares the current source texture, available LODs, `ColorStats`, and immutable -`EditRenderState` between Detail and Edit. Sidecar persistence still enters -through the session edit surface; it does not become part of the neutral source -cache key. - -User-visible GUI text goes through the Qt translation boundary. New strings -should use `iPhoto.gui.i18n.tr(context, source_text)` or -`QCoreApplication.translate(...)` with a stable context. Long-lived widgets -refresh translated labels through `retranslate_ui()`; the main window wires -`TranslationManager.languageChanged` to `retranslate_ui_tree()` so runtime -language switches update menus, tooltips, pages, and status text without -rebuilding the active library session. Business logic must use stable command -ids, node types, callbacks, or `QAction.data()` rather than translated labels. - -Bundled i18n resources live in `src/iPhoto/resources/i18n/`: - -| Resource | Responsibility | -| --- | --- | -| `languages.json` | Advertises supported UI language choices and Qt locales. | -| `iPhoto_de.ts` / `iPhoto_zh_CN.ts` | Qt Linguist source translations. | -| `iPhoto_de.qm` / `iPhoto_zh_CN.qm` | Compiled translators loaded at runtime. | - -Package data includes `.ts` and `.qm` files so editable installs and packaged -builds can load the same resources. - -### Library Runtime - -`library/` contains the production runtime controller, album tree/watch shells, -scan coordination, and trash/filesystem orchestration bound to session services. -It is not a legacy manager facade. - -### Bounded Contexts - -- `people/`: optional face detection/clustering runtime, People repositories, - stable People state, manual faces, groups, covers, and People service API. -- `pets/`: optional YOLOX/DINOv2 detection and identity clustering runtime, - rebuildable pet repository, durable pet state, and session-bound Pet service. - The People & Pets dashboard composes both services without merging their - runtime tables. See [`docs/misc/PETS_RECOGNITION_RUNTIME.md`](misc/PETS_RECOGNITION_RUNTIME.md). -- `src/maps`: optional offline map runtime, tile parsing, OBF/native - widget/helper integration, search, and map rendering internals. GUI map - views construct concrete map widgets through `map_widget_factory`. -- `core/`: editing math, filters, geometry, export transforms, raw loading, and - Live Photo pairing rules. -- `cache/index_store/`: current global SQLite index implementation used behind - repository/session surfaces, not a public GUI/application shortcut. - -## Persistence Model - -Each library root owns a `.iPhoto/` workspace. - -| Path | Ownership | -| --- | --- | -| `.iPhoto/global_index.db` | Current SQLite asset index and repository-backed state store for scan rows, pagination, Live Photo roles, trash/favorite/hidden state, independent `face_status`/`pet_status`, and related library state. | -| `.iPhoto/links.json` | Derived Live Photo compatibility materialization; repository/session Live Photo role state remains authoritative for runtime behavior. | -| `.iPhoto/cache/thumbs/` | Rebuildable thumbnail cache. | -| `.iPhoto/cache/detail-surfaces/v3/` | SQLite-indexed, rebuildable neutral RGBA8/sRGB Detail surfaces keyed by source identity, decoder contract, orientation, and LOD; trusted hits validate indexed header/stat metadata without scanning the full payload, and sidecar revision is excluded. | -| `.iPhoto/faces/face_index.db` | Rebuildable People runtime snapshot. | -| `.iPhoto/faces/face_state.db` | Durable People user state: names, covers, hidden flags, order, groups, pinned state, group covers, and manual faces. | -| `.iPhoto/faces/thumbnails/` | Rebuildable cropped face thumbnails. | -| `.iPhoto/pets/pet_index.db` | Rebuildable Pets detections and clustered pet records. | -| `.iPhoto/pets/pet_state.db` | Durable Pets profiles, names, covers, hidden flags, rejected keys, and merge redirects. | -| `.iPhoto/pets/thumbnails/` | Rebuildable cropped pet thumbnails; replaced detections are reference-pruned after a successful snapshot commit. | -| `.ipo` sidecars | Durable non-destructive edit instructions next to source media. | -| `.iphoto.album.json` / `.iPhoto/manifest.json` | Folder-local album metadata formats. | -| `.iphoto.album` | Legacy album marker compatibility file. | - -Implementation stages may continue storing scan facts and some user state in the -same SQLite file, but repository APIs and merge behavior must maintain the -logical boundary: scans may rebuild facts and must not implicitly delete durable -choices. - -## Core Flows - -### Library Startup - -```mermaid -sequenceDiagram - participant UI as GUI Shell - participant Paint as First Paint - participant Runtime as RuntimeContext - participant Feature as Feature Bundles - participant Coordinator as MainCoordinator - participant Session as LibrarySession - participant Infra as Infrastructure - - UI->>Runtime: create(defer_startup=True) - UI->>UI: show lightweight window shell - UI-->>Paint: firstPainted - Paint->>Feature: create deferred hidden features over event-loop turns - Feature->>Coordinator: import, wire, and start - Coordinator->>Runtime: resume_startup_tasks(defer_scan=True) - Runtime->>Runtime: open saved library root - Runtime->>Session: create library-scoped session - Session->>Infra: bind SQLite/cache/people/pets/maps/edit adapters - Runtime-->>Coordinator: session surfaces ready - Coordinator->>Coordinator: warm first gallery window - Coordinator->>Runtime: start_deferred_startup_scan() +assets / asset_queries / asset_state +album_metadata / scans / asset_lifecycle / asset_operations +thumbnails +people / pets +maps / map_interactions +edit +locations +shutdown() ``` -Headless callers do not use the paint boundary; they create the same -library-scoped session directly. Scan workers, geocoding, People/Pets AI, Qt -Multimedia, and Maps rendering remain demand-loaded by their owning workflow. -The GUI delays the saved-library metadata scan until the first gallery warm-up -signals readiness, with a bounded timer fallback so an empty or failed gallery -cannot suppress scanning indefinitely. - -### Open Collection - -```mermaid -sequenceDiagram - participant Grid as Gallery Grid - participant Demand as Demand Coordinator - participant Store as Sparse Collection Store - participant Loader as Window/Hint Loaders - participant Query as Asset Query Surface - participant Repo as AssetRepositoryPort - - Grid->>Demand: viewport + intent + generation - Demand->>Store: visible and micro-warm ranges - Store->>Loader: bounded async requests - Loader->>Query: gallery window / thumbnail hints - Query->>Repo: narrow SQL projections - Repo-->>Loader: rows + revision - Loader-->>Store: generation-tagged results - Store-->>Grid: merged snapshots + local row updates -``` +Headless callers create the same library boundary rather than a parallel data +model. GUI widgets may receive presentation-safe empty People/Pets services +before a library exists, but those objects must not open repositories or become +second sources of truth. -GUI viewmodels may cache window/selection state, but repository/session surfaces -remain the source of truth for persisted asset state. Explicit detail-view row -loads are retained independently from newer viewport generations so navigation -does not lose an in-flight target. +## Desktop Composition And Startup -### Gallery To Detail/Edit Rendering +The installed GUI entry is `iPhoto.entrypoint:main`. `iPhoto.entrypoint` stays +lightweight so helper dispatch can occur before importing the full Qt desktop +runtime. -```mermaid -sequenceDiagram - participant Gallery as Gallery - participant Tx as DetailRenderCoordinator - participant Scheduler as Still Request Scheduler - participant Cache as Surface Cache - participant Decoder as Platform Decoder - participant GPU as Texture Residency - participant Edit as Detail/Edit Session - - Gallery->>Tx: begin immutable transaction - Tx->>Scheduler: viewport/DPR/geometry request - Scheduler->>Scheduler: deduplicate or promote same key - Scheduler->>Cache: memory then disk lookup - alt cache miss - Cache->>Decoder: viewport-aware decode - Decoder-->>Cache: detached neutral surface - Cache->>Cache: async versioned write - end - Cache-->>GPU: decoded or cached surface - GPU->>GPU: reuse key or upload without initial mipmaps - GPU-->>Tx: actual draw presented - Tx-->>Edit: shared PhotoRenderSessionHandle - Edit->>GPU: immutable shader-state updates -``` +The desktop has distinct startup boundaries: -`DetailDecodeKey` contains asset/source revision, orientation, and decode level; -it intentionally excludes `.ipo` revision. Initial quality is selected from -physical viewport demand rather than full sensor dimensions. Zoom, crop, -rotation, or perspective may request a higher LOD, but the prior texture stays -visible until the replacement is drawn. Current/previous/next GPU residency is -bounded by both three textures and 192MB. Source changes invalidate neutral -surfaces and textures; sidecar changes replace render state only. +1. configure startup/runtime environment; +2. create `QApplication`, `RuntimeContext`, and the lightweight main-window + shell; +3. construct any platform-required pre-show GPU surface; +4. call `show()` and reach first paint; +5. promote/bind deferred feature bundles and create the desktop coordinator + graph without moving optional heavy work back onto the first-frame path. -Non-RAW platform selection is ImageIO on macOS, WIC on Windows, and Qt on Linux, -with Qt fallback inside the same worker lane. RAW uses rawpy and its embedded, -half-size, then full fallback sequence. All stale generations are rejected at -thread/render boundaries. Static Edit no longer creates a second full-image -loader or CPU preview session; Done/Cancel and fullscreen retain the same render -session. Export remains an independent full-resolution path. +`DesktopCoordinatorRuntime` in +`iPhoto.gui.coordinators.desktop_coordinator_runtime` is the production desktop +composition root. It owns the coordinator graph, feature promotion/binding, and +presentation coordination. A separate production `MainCoordinator` is not part +of the architecture; `main_coordinator.py` only preserves compatibility imports. -### Scan And Index +`Ui_MainWindow.ensure_feature()` owns on-demand feature bundle lifetime. A +feature can be created before or after first paint depending on platform/Qt +surface constraints, but optional feature construction must not silently +reintroduce model initialization or unrelated blocking work into first paint. -```mermaid -sequenceDiagram - participant Trigger as GUI/CLI/Watcher - participant Session as LibrarySession - participant Scan as ScanLibraryUseCase - participant Scanner as MediaScannerPort - participant Repo as AssetRepositoryPort - participant Faces as FaceScanWorker - participant Pets as PetScanWorker - participant Pairing as Live Photo Pairing - - Trigger->>Session: scan(scope, filters) - Session->>Scan: execute - Scan->>Scanner: discover media - Scanner-->>Scan: scan chunks - Scan->>Repo: merge scan rows - Scan->>Repo: append scan job/event records - Scan->>Faces: enqueue face-eligible committed rows - Scan->>Pets: enqueue pet-eligible committed rows - Scan->>Pairing: refresh roles/materialization - Scan-->>Trigger: progress/result + ScanBatchCommitted -``` +### Recognition startup contract -Scanning has one application use case. Qt workers adapt threading/progress, and -CLI uses the same session surface without Qt. UI scan batches are ready-only and -carry full thumbnail cache keys so visible media rows are immediately drawable. -For an interactive rescan, Face and Pet workers run alongside metadata scanning -and receive committed rows. When saved-library startup requires a metadata -scan, AI workers are deferred until it finishes, then drain `pending`/`retry` -rows from the global index. A scan-complete startup does not create AI workers. -The workers and their databases remain independent; failure or a missing -optional runtime in one must not block the other. +`RecognitionCoordinator` is deliberately lazy. -### Assign Location +- It can bind People/Pets services and warm an existing dashboard snapshot + without initializing inference models. +- Opening a library or completing metadata scan does **not** by itself start + People/Pets model inference. +- Recognition scans are requested only after both conditions are true: + 1. the People surface has actually been shown; + 2. that surface reports its first viewport ready. +- The coordinator then schedules scan activation after a short quiet window so + cover/first-content delivery is not immediately competing with AI work. -```mermaid -sequenceDiagram - participant UI as Info Panel - participant Coordinator as PlaybackCoordinator - participant Repository as LocationAssignmentRepositoryPort - participant Queue as LocationFileWriteQueue - participant Writer as MetadataWriterPort - - UI->>Coordinator: confirm(asset, lat, lon, name) - Coordinator->>Repository: assign_location(...) - Repository-->>Coordinator: local state + durable write job - Coordinator->>Queue: enqueue(job) - Queue->>Writer: write GPS - alt write fails - Writer-->>Queue: recoverable warning - Queue-->>Coordinator: warning event - end - Coordinator-->>UI: refresh local location state -``` +This feature-driven rule supersedes older documentation that described People +and Pets workers as automatic post-startup or post-metadata-scan tasks. -The local assignment is authoritative. ExifTool failures are warnings and do not -roll back local state; pending durable write-back jobs are recovered on the next -session. - -### Thumbnail Rendering +## Layer Boundaries -```mermaid -sequenceDiagram - participant Paint as Delegate Paint - participant Demand as Gallery Demand - participant Thumb as Thumbnail Cache Service - participant Worker as Visible/Guard/Far Workers - participant L2 as Disk Cache - participant GUI as GUI Publish Queue - - Paint->>Thumb: peek_full_thumbnail() - Thumb-->>Paint: memory pixmap or miss - Demand->>Thumb: request_many(generation, priority) - Thumb->>Worker: deduplicate/promote/schedule - Worker->>L2: decode existing thumbnail to QImage - Worker-->>GUI: bounded staging result - GUI->>GUI: QImage to QPixmap within frame budget - GUI-->>Paint: coalesced exact-row update - alt stale or superseded demand - Thumb->>Worker: cancel, back off, or discard result - end -``` +### Domain -Visible recovery, near guard, and far speculation use separate scheduling lanes; -far work must not consume workers reserved for urgent visible/guard requests. -`ThumbnailRuntimePolicy` derives worker, staging, publish, and byte-budget limits -from platform and physical memory. L1 eviction accounts for actual image bytes, -pins active visible demand, and prefers old/far speculative entries. Disk access -and image decoding stay off the GUI thread; only bounded `QPixmap` publication -runs there. Thumbnail infrastructure may apply edit state, but edit persistence -remains behind session/edit sidecar services. +`domain/` owns dataclasses, value objects, query models, and pure services. It +must not own Qt, SQLite, filesystem writes, ExifTool, FFmpeg, or process runtime +singletons. -## Removed Legacy Application Tree +### Application -The former `src/iPhoto/legacy/` compatibility tree, including app/appctx -wrappers, bootstrap shims, domain-repository use cases, repository adapters, -and model shims, has been removed. +`application/` owns workflow use cases, services, DTOs, queries, events, and +port protocols. It depends on ports/domain values, not GUI widgets or concrete +persistence implementations. -Rules: +Representative ports include: -- Production runtime must not import `iPhoto.legacy`. -- Production runtime must not import `iPhoto.models.*`. -- Do not restore compatibility modules or tests that target removed interfaces. -- Historical behavior that remains a product requirement must be covered - through current application, session, domain, or infrastructure surfaces. +| Port | Responsibility | +| --- | --- | +| `AssetRepositoryPort` | asset query/count/scan merge/state persistence semantics | +| `LibraryStateRepositoryPort` | durable library user-state boundary | +| `MediaScannerPort` | media discovery and normalized scan candidates | +| `PeopleIndexPort` | People candidate/snapshot/query boundary | +| `PetIndexPort` | Pets candidate/snapshot boundary | +| `MapRuntimePort` | optional Maps runtime availability/adapter selection | +| `MapInteractionServicePort` | marker/map interaction semantics | +| `EditSidecarPort` | `.ipo` read/write boundary | +| `ThumbnailRendererPort` | thumbnail generation without GUI ownership | -## Architecture Guardrails +### Infrastructure -Run: +`infrastructure/` owns concrete SQLite, manifest, sidecar, ExifTool/FFmpeg, +scanner, thumbnail, and Maps runtime adapters. It implements application ports +and must not import GUI modules or own product workflow decisions. -```bash -python3 tools/check_architecture.py -python tools/check_i18n_strings.py src/iPhoto/gui src/maps -.venv/bin/python -m pytest tests/architecture -q -``` +### Library runtime -The guardrails enforce: +`library/` contains the production runtime controller and tree/watch/scan/trash +shell code bound to the active session. It does not recreate old manager/facade +architectures. -- runtime `AppContext` imports are not reintroduced; -- coordinators do not import collection-store implementation types directly; -- vNext layer boundaries are respected; -- `application/` does not import GUI or concrete persistence; -- `infrastructure/` does not import GUI; -- production runtime does not import quarantined legacy paths or old model - shims. -- high-risk GUI APIs do not receive direct English literals that bypass the - translation boundary. +### GUI -The GitHub Actions workflow also runs `python tools/check_architecture.py` -before the broader test suite. +`gui/` owns PySide6 presentation: views, widgets, viewmodels, controllers, +coordinators, menus, Qt task/signal adapters, and rendering presentation state. +Durable workflow decisions remain behind session/application surfaces. -## Decision Log +The desktop coordinator graph is decomposed by responsibility. In particular, +recognition coordination is kept separate from Gallery, navigation, Detail, +Edit, and other feature coordinators. Compatibility filenames must not be used +to infer ownership when the exported production type has moved. -### ADR-1: Folder-Native Albums +## Large-Library Query And Scan Contracts -Folders remain albums. Manifests store folder-local metadata, while global -browsing/indexing state lives in the library database and session surfaces. +Large-library browsing is SQL-first and windowed. -### ADR-2: Library-Scoped Runtime +- Collection/query models describe collection intent and bounded pages/windows. +- Gallery models use bounded sparse asynchronous windows rather than + materializing an entire library. +- Direct row lookup uses repository/query surfaces rather than scanning the + in-memory model. +- Gallery tile/delegate access remains memory-only on paint paths. +- Viewport demand separates visible, guard, speculative, and micro-thumbnail + work; stale generations are discarded. +- Normal Gallery-visible rows must be thumbnail-ready and carry a non-empty + `thumb_cache_key`; stale/pending/failed/no-key rows belong to repair/backfill + paths. +- Scan publishing occurs after database commit through `ScanBatchCommitted`. + Do not restore historical `scanChunkReady` transport as the production UI + update path. +- Scan merge remains idempotent and preserves durable user state. -One active library root owns one runtime session, one asset index, one thumbnail -cache root, separate People and Pets state roots, and one Maps runtime context. +Historical scan-performance call graphs are not architecture references. New +performance work must profile the current scanner/application/index-store path. -### ADR-3: Application Ports Over Concrete Singletons +## Rendering And Edit Boundary -Use cases and application services depend on ports. Concrete SQLite, ExifTool, -FFmpeg, thumbnail, People, Pets, edit, and Maps implementations are bound through -runtime/session composition. +`DetailRenderCoordinator` owns the active still/video render transaction and its +terminal state. Still scheduling deduplicates source revision/decode level and +platform decoders return detached neutral RGBA8888/sRGB surfaces. -### ADR-4: Single Asset Repository Boundary +`PhotoRenderSessionHandle` shares the current source texture/LOD state, +`ColorStats`, and immutable `EditRenderState` between Detail and Edit. Sidecar +persistence enters through the session edit surface and is not part of the +neutral source cache key. -Asset persistence is exposed through one public application port. The current -SQLite global index implementation is used behind that boundary; GUI and -application code must not bypass it. +Platform rendering choices remain adapter/presentation concerns: -### ADR-5: Single Scan Use Case +- macOS: QRhi/Metal preferred for media preview; ImageIO may decode non-RAW + stills; OpenGL/Qt are compatibility paths. +- Windows: QRhi/OpenGL with WIC preference for non-RAW still decode. +- Linux: QRhi/OpenGL with Qt still decode. +- RAW: rawpy path. -Scanning is an application workflow. GUI workers, CLI commands, watchers, and -runtime refreshes adapt the same scan/session surface so progress, cache checks, -metadata fallback, People/Pets enqueueing, and Live Photo pairing stay consistent. +## Bounded Contexts -### ADR-6: Durable Recognition State Split +### People -People runtime scan output is rebuildable. Human-authored People state is -durable and survives rescans, reclustering, app restarts, and model changes. -Pets follows the same split through separate `pet_index.db` and `pet_state.db`; -the contexts share orchestration patterns but never share detection tables or -identity record types. +`people/` owns optional face detection/clustering, People repositories, +rebuildable runtime snapshot, durable People state, manual faces, groups, +covers, hidden state, and service API. -### ADR-7: Platform Rendering Behind Adapters +### Pets -OpenGL, QRhi/Metal, native OsmAnd widgets, helper-backed map renderers, and CPU -fallbacks are runtime-selected adapters. Product workflows must not depend on a -specific rendering backend. Detail still decode follows the same rule through -ImageIO, WIC, Qt, and rawpy adapters; native failure may fall back to Qt without -creating a second scheduler or presentation path. +`pets/` owns optional YOLOX/DINOv2 detection and pet identity clustering, +rebuildable Pets runtime state, durable Pets user state, and the Pet service. +People and Pets can be composed in the dashboard/groups while their runtime +records and durable stores remain independently owned. -### ADR-8: GPU-First Detail With Shared Edit Sessions +The current clustering pipeline is `species-bounded-single-link-v3`: -The current viewport, not full sensor dimensions, defines initial still-image -quality. Neutral source surfaces, edit state, and GPU residency have separate -identities and invalidation rules. Detail and static Edit share one GUI-owned -render session; shader updates do not reload source pixels. The legacy Detail -v2 full-frame cache and still Edit CPU-preview chain must not be restored. +- clustering is species-separated; +- cannot-link constraints prevent known-incompatible detections from joining; +- single-link candidate growth is bounded by cluster-diameter constraints to + prevent uncontrolled chaining; +- stable identity/canonicalization logic preserves durable user choices across + rebuilds where contracts are compatible. -### ADR-9: Composed People And Pets Identities +People/Pets overlap arbitration is geometry-based but not an unconditional +“People always wins” rule. Strong face overlap normally suppresses a pet +candidate. A substantially larger plausible pet-body box that contains a +smaller face may be preserved under the runtime size/image-coverage exception. +See `misc/PETS_RECOGNITION_RUNTIME.md` for the detailed thresholds and lifecycle. -The dashboard, pinned sidebar, annotations, and identity groups may compose -person and pet summaries. Canonical records remain owned by their bounded -contexts, while cross-kind redirects and mixed identity-group membership are -durable coordination state in the People state repository. +Production Pets inference does not execute arbitrary Torch Hub Python. Release +conversion/provenance tooling may use a pinned source revision to produce a +TorchScript artifact. Runtime trust is the prebuilt artifact plus manifest +hash/size validation. If the manifest has `torchscript_url: null`, runtime +documentation must treat DINOv2 as package/prestage-provided rather than promise +a download that cannot occur. -## Acceptance Criteria +### Maps -The current production source satisfies the vNext architecture criteria when: +`src/maps` owns optional offline map runtime, OBF/native helper/widget +integration, search, and map rendering internals. Maps availability is a runtime +capability and must not decide whether the rest of the desktop can start. -- GUI, CLI, watchers, and workers enter through `RuntimeContext`, - `LibrarySession`, and application/session surfaces. -- Asset persistence is exposed through `AssetRepositoryPort` and state-specific - application ports. -- Scanning is owned by `ScanLibraryUseCase`, with Qt and non-Qt adapters around - it. -- `application/` has no direct concrete persistence or GUI imports. -- `infrastructure/` has no GUI imports. -- production runtime has no `iPhoto.legacy` or `iPhoto.models.*` imports. -- architecture checks are in CI. -- Detail still opens use the generation-safe scheduler/cache/session path; - static Edit does not re-decode or re-upload the current source. -- key product behavior remains covered: folder browsing, global indexing, Live - Photos, People, Pets, Maps fallback, editing, location assignment, trash, - import/move/delete/restore, and export. +## Persistence Model -Recommended verification after architecture-sensitive changes: +Each library root owns `.iPhoto/` state: -```bash -python3 tools/check_architecture.py -.venv/bin/python -m pytest tests/architecture -q -.venv/bin/python -m pytest tests/application/test_runtime_context.py tests/application/test_library_session.py tests/application/test_scan_library_use_case.py -q -.venv/bin/python -m pytest tests/application/test_temp_library_end_to_end.py tests/application/test_library_asset_lifecycle_service.py tests/services/test_asset_move_service.py tests/services/test_restoration_service.py -q -.venv/bin/python -m pytest tests/performance -q -``` +| Path | Ownership | +| --- | --- | +| `.iPhoto/global_index.db` | asset index/state including independent `face_status` and `pet_status` | +| `.iPhoto/links.json` | derived Live Photo compatibility materialization | +| `.iPhoto/cache/thumbs/` | rebuildable thumbnail cache | +| `.iPhoto/cache/detail-surfaces/v3/` | rebuildable neutral Detail surfaces | +| `.iPhoto/faces/face_index.db` | rebuildable People runtime snapshot | +| `.iPhoto/faces/face_state.db` | durable People user decisions | +| `.iPhoto/faces/thumbnails/` | rebuildable face crops | +| `.iPhoto/pets/pet_index.db` | rebuildable Pets detection/identity snapshot | +| `.iPhoto/pets/pet_state.db` | durable Pets names/covers/hidden/rejected/redirect state | +| `.iPhoto/pets/thumbnails/` | rebuildable pet crops subject to cover references | +| `.ipo` sidecars beside media | durable non-destructive edit parameters | + +Runtime snapshot replacement must not erase the durable state stores. + +## Model And Packaging Boundary + +Optional model assets are packaging inputs, not architecture-owned source data. +`src/extension/models/...` is a staging convention used by build scripts and is +not guaranteed tracked content in a fresh clone. + +- People-capable packages explicitly include the People runtime/models they + claim to support. +- Pets-capable packages explicitly include `pets-ai` dependencies and the + required model artifacts for offline behavior. +- The YOLOX detector has a fixed artifact download/integrity contract. +- DINOv2 uses the manifest-declared TorchScript artifact. With the current null + runtime URL, offline/package staging is the canonical delivery path. +- AppImage and Debian reproducible build contracts are documented under + `docs/misc/BUILD_APPIMAGE.md` and `docs/misc/BUILD_DEB.md`. +- A published Flatpak artifact does not imply an in-repository reproducible + Flatpak build; see `docs/misc/BUILD_FLATPAK.md`. + +## Internationalization + +User-visible GUI text goes through Qt translation helpers with stable contexts. +Long-lived widgets provide `retranslate_ui()` behavior, and runtime language +changes refresh the UI without rebuilding the active library session. Business +logic uses stable ids/callbacks/data rather than translated labels. + +Bundled language resources live under `src/iPhoto/resources/i18n/`. The current +long-term guardrail is `misc/I18N_UI_TEXT_GUARDRAILS.md`. + +## Documentation Authority + +For current production behavior, prefer sources in this order: + +1. runtime code and tests; +2. this architecture document and `AGENT.md`; +3. active guardrails under `docs/misc/`; +4. active requirements/runbooks under `docs/requirements/`; +5. historical/superseded requirement documents; +6. archived material under `docs/finished/`. + +`docs/requirements/README.md` defines lifecycle/status rules. Historical design +text must be explicitly labelled and must not silently override current runtime +contracts. diff --git a/docs/development.md b/docs/development.md index ef17c2332..07081f2de 100644 --- a/docs/development.md +++ b/docs/development.md @@ -1,225 +1,119 @@ # 🧰 Development Guide -> Development environment, dependencies, build/package, debugging, code style, and commit conventions for **iPhotron**. - ---- - -## Prerequisites - -| Requirement | Version | -|-------------|---------| -| Python | ≥ 3.12 | -| ExifTool | Latest (in `PATH`) | -| FFmpeg / FFprobe | Latest (in `PATH`) | -| Git | Latest | - ---- +This guide documents the current development contracts for iPhotron. For +architecture-sensitive work, read `../AGENT.md`, `architecture.md`, and the +relevant guardrail under `misc/` before historical requirements. ## Setup -### 1. Clone the Repository +Requirements: Python 3.12+, Git, ExifTool, and FFmpeg/FFprobe. ```bash git clone https://github.com/OliverZhaohaibin/iPhotron-LocalPhotoAlbumManager.git cd iPhotron-LocalPhotoAlbumManager -``` - -### 2. Create a Virtual Environment - -```bash python -m venv .venv -source .venv/bin/activate # macOS / Linux -.venv\Scripts\activate # Windows -``` - -### 3. Install Dependencies - -```bash -# Core + development dependencies +source .venv/bin/activate # macOS/Linux +# .venv\Scripts\activate # Windows pip install -e ".[dev]" ``` -This installs all runtime dependencies plus dev tools (`pytest`, `ruff`, `black`, `mypy`). - ---- - -## Architecture Guardrails +The installed desktop entry point is: -Current production development follows the vNext runtime boundary: - -- `RuntimeContext` owns the active `LibrarySession`. -- GUI, CLI, file watchers, and Qt workers must use session/application - surfaces instead of legacy compatibility facades. -- Production source must not import `iPhoto.legacy` or `iPhoto.models.*`. -- New business behavior belongs in application use cases/services, session - services, domain values/pure services, or infrastructure adapters. -- GUI code should remain presentation and Qt transport. +```text +iphoto-gui = iPhoto.entrypoint:main +``` -Read these before architecture-sensitive work: +`iPhoto.entrypoint` is intentionally lightweight: helper dispatch happens before +the full Qt GUI import. `iPhoto.gui.main:main` is the internal GUI dispatch +target, not the console-script contract. -- [AGENT.md](../AGENT.md) -- [Architecture](architecture.md) -- [Completed vNext refactor archive](finished/refactor/vnext-2026-06/README.md) +## Architecture And Startup -Run the architecture guard before or with focused tests: +Production development follows: -```bash -python3 tools/check_architecture.py -.venv/bin/python -m pytest tests/architecture -q +```text +RuntimeContext -> LibrarySession -> application ports/services -> domain ``` -The GitHub Actions test workflow runs `python tools/check_architecture.py` -before the Python test suite. - ---- +`DesktopCoordinatorRuntime` in +`iPhoto.gui.coordinators.desktop_coordinator_runtime` owns the desktop +coordinator graph. `gui/coordinators/main_coordinator.py` is a compatibility +import only; do not introduce or document a separate production +`MainCoordinator` ownership model. -## Regression Guardrails +Keep optional heavy imports and AI model initialization off the first-frame +path. A platform-required GPU Detail surface may be created before `show()` when +Qt native-window behavior requires it, but optional feature promotion must not +move unrelated blocking work back into first paint. -Small behavior contracts that are easy to break during feature work live in -`docs/misc/`. Read the relevant note before touching these areas: +### Recognition activation -| Area | Guardrail | -|------|-----------| -| Scan UI publishing | [SCAN_VISIBLE_PUBLISH_GUARDRAILS.md](misc/SCAN_VISIBLE_PUBLISH_GUARDRAILS.md) | -| Large library collection queries | [LARGE_LIBRARY_QUERY_GUARDRAILS.md](misc/LARGE_LIBRARY_QUERY_GUARDRAILS.md) | -| Gallery scrolling, sparse windows, and thumbnail demand | [GALLERY_SCROLL_PIPELINE_GUARDRAILS.md](misc/GALLERY_SCROLL_PIPELINE_GUARDRAILS.md) | -| Gallery → Detail GPU-first transactions, caches, sessions, and packaged validation | [DETAIL_OPEN_BENCHMARK_RUNBOOK.md](requirements/DETAIL_OPEN_BENCHMARK_RUNBOOK.md) | -| Trash and restore state | [TRASH_RESTORE_STATE_GUARDRAILS.md](misc/TRASH_RESTORE_STATE_GUARDRAILS.md) | -| Move/restore optimistic UI | [MOVE_RESTORE_OPTIMISTIC_UI_GUARDRAILS.md](misc/MOVE_RESTORE_OPTIMISTIC_UI_GUARDRAILS.md) | -| Project popups and People & Pets UI regressions | [PROJECT_POPUP_GUARDRAILS.md](misc/PROJECT_POPUP_GUARDRAILS.md) | -| Pets recognition runtime, persistence, and scan scheduling | [PETS_RECOGNITION_RUNTIME.md](misc/PETS_RECOGNITION_RUNTIME.md) | -| macOS map GL transparency | [MACOS_MAP_GL_TRANSPARENCY_NOTES.md](misc/MACOS_MAP_GL_TRANSPARENCY_NOTES.md) | -| GUI internationalization | [I18N_UI_TEXT_GUARDRAILS.md](misc/I18N_UI_TEXT_GUARDRAILS.md) | +People/Pets inference is feature-driven. `RecognitionCoordinator` requests scan +activation only after both conditions are true: -Useful focused checks when touching scan, query, trash, move, or restore: +1. the People surface has actually been shown; and +2. its first viewport is ready. -```bash -.venv/bin/python -m pytest tests/application/test_scan_library_use_case.py tests/application/test_library_scan_service.py tests/library/test_scanner_worker.py -q -.venv/bin/python -m pytest tests/application/test_library_asset_query_service.py tests/cache/test_index_store_features.py tests/performance/test_refactor_performance_baseline.py -q -.venv/bin/python -m pytest tests/application/test_temp_library_end_to_end.py tests/application/test_library_asset_lifecycle_service.py tests/services/test_asset_move_service.py tests/services/test_restoration_service.py -q -.venv/bin/python -m pytest tests/gui/viewmodels/test_gallery_collection_store.py tests/gui/viewmodels/test_gallery_list_model_adapter.py tests/gui/coordinators/test_main_coordinator_pending_moves.py -q -.venv/bin/python -m pytest tests/test_gallery_demand.py tests/test_asset_grid_scroll.py tests/gui/viewmodels/test_gallery_demand_coordinator.py tests/gui/viewmodels/test_gallery_thumbnail_hint_loader.py tests/test_thumbnail_cache_service.py tests/test_thumbnail_runtime_policy.py -q -``` +Activation then runs after a short quiet delay. Opening the app, opening a +library, or finishing metadata scan does not independently start People/Pets +model inference. Dashboard warmup may read existing local recognition state +without initializing inference models. -Gallery performance work has an additional opt-in real Qt event-loop benchmark: +## Architecture Checks ```bash -IPHOTO_RUN_GALLERY_SCROLL_BENCHMARK=1 .venv/bin/python -m pytest tests/performance/test_gallery_scroll_qt_benchmark.py -q +python3 tools/check_architecture.py +.venv/bin/python -m pytest tests/architecture -q ``` -Run it on the affected target platform with a populated L2 thumbnail cache. -Timing results from macOS do not replace Windows or Linux validation. - ---- +Production source must not restore `iPhoto.legacy` / `iPhoto.models.*`, and +application/infrastructure code must not bypass the current layer boundaries. -## Internationalization Development Workflow +## Current Guardrails -The desktop GUI has runtime language support through -`RuntimeContext.translation` and the `ui.language` setting. Current supported -stored choices are `system`, `de`, and `zh-CN`; the menu shows the `system` -choice as `English`, and English is the fallback when no translator is -installed. +- Scan publishing: `misc/SCAN_VISIBLE_PUBLISH_GUARDRAILS.md` +- Large-library queries: `misc/LARGE_LIBRARY_QUERY_GUARDRAILS.md` +- Gallery scrolling: `misc/GALLERY_SCROLL_PIPELINE_GUARDRAILS.md` +- Detail acceptance: `requirements/DETAIL_OPEN_BENCHMARK_RUNBOOK.md` +- People/Pets: `misc/PETS_RECOGNITION_RUNTIME.md` +- GUI i18n: `misc/I18N_UI_TEXT_GUARDRAILS.md` +- macOS Maps GL: `misc/MACOS_MAP_GL_TRANSPARENCY_NOTES.md` -When adding or changing UI text: +The old `docs/requirements/i18n/` reference is obsolete. The current long-term +i18n contract is `docs/misc/I18N_UI_TEXT_GUARDRAILS.md`. -- Wrap user-visible strings with `iPhoto.gui.i18n.tr(context, source_text)` or - `QCoreApplication.translate(...)`. -- Keep source strings in English and use a stable context name. -- Use named placeholders for dynamic values. File names, paths, people names, - place results, camera/lens/codec values, backend names, environment - variables, and exception details should stay as original data. -- Use `iPhoto.gui.i18n.formatters` for UI-facing dates, counts, decimals, and - file sizes. -- Add `retranslate_ui()` on long-lived widgets that own labels, tooltips, - placeholders, menus, or status text. -- Drive menu and control behavior from stable ids, callbacks, node types, or - `QAction.data()`, not translated labels. +## Internationalization -Update and compile translation resources after text changes: +Use `iPhoto.gui.i18n.tr(...)` or `QCoreApplication.translate(...)` with stable +contexts for user-visible text. Business logic must use stable ids/callbacks, +not translated labels. Long-lived widgets should support `retranslate_ui()`. ```bash bash scripts/i18n_extract.sh bash scripts/i18n_compile.sh -``` - -Run focused i18n checks before merging: - -```bash python tools/check_i18n_strings.py src/iPhoto/gui src/maps -.venv/bin/python -m pytest tests/test_i18n_extract_tool.py tests/test_i18n_translation_manager.py tests/architecture/test_i18n_string_gate.py -q ``` -Process notes and terminology references live under `docs/requirements/i18n/`. -Those files document the completed i18n rollout and Apple Photos-aligned edit -terminology; the long-term regression contract is -[`docs/misc/I18N_UI_TEXT_GUARDRAILS.md`](misc/I18N_UI_TEXT_GUARDRAILS.md). - -The pet recognition and clustering materials under -`docs/requirements/pets-cluster/` are historical requirements and development -planning inputs. Production behavior is now implemented; use -[`docs/misc/PETS_RECOGNITION_RUNTIME.md`](misc/PETS_RECOGNITION_RUNTIME.md) and -[`docs/architecture.md`](architecture.md) as the current runtime contracts. - ---- - -## Dependencies - -### Runtime Dependencies - -Managed in `pyproject.toml`: - -| Package | Purpose | -|---------|---------| -| `jsonschema` | JSON Schema validation | -| `PySide6` | Qt6 GUI framework | -| `Pillow` / `pillow-heif` | Image loading (HEIC support) | -| `imagehash` / `xxhash` | Perceptual & fast hashing | -| `opencv-python-headless` | Image processing | -| `reverse-geocoder` | GPS → location name | -| `pyexiftool` | ExifTool wrapper | -| `numpy` / `numba` | Numeric computation & JIT | -| `mapbox-vector-tile` | Map tile parsing | -| `av` | Video decoding | -| `PyOpenGL` / `PyOpenGL_accelerate` | OpenGL rendering | - -### Optional Face Recognition Dependencies - -The People face-scanning pipeline is installed through the optional `ai-demo` -extra: +## Optional People Runtime ```bash pip install -e ".[ai-demo]" ``` -That extra intentionally stays small: +People uses InsightFace and ONNX Runtime. Missing AI dependencies must not block +normal browsing, editing, Live Photo, Pets state, Maps, or library state. +`face_index.db` is rebuildable; `face_state.db` is durable. -| Package | Purpose | -|---------|---------| -| `insightface>=0.7.3,<1.0` | Face detection and face embeddings | -| `onnxruntime>=1.18,<2` | ONNX model execution backend | - -Do not add InsightFace's unused mask-rendering dependency chain to the runtime -unless the product starts using it directly. The app does not need -`albumentations` or `pydantic` for People clustering. - -The editable source install remains valid without this extra. In that mode the -desktop app should still open libraries and use albums, maps, Live Photos, and -editing; only the background People face scan is unavailable until `ai-demo` is -installed. - -### Optional Pets AI Runtime - -Pet recognition and clustering is installed through the optional `pets-ai` -extra: +## Optional Pets Runtime ```bash pip install -e ".[pets-ai]" ``` -The runtime uses the same model-cache posture as People: local files are used -first, and a missing cache can be downloaded on first scan. The default shared -cache is `src/extension/models/pets/`, or `IPHOTO_PET_MODEL_DIR` when that -environment variable is set: +The extra supplies `certifi`, `onnxruntime`, `torch`, `torchvision`, and +`usearch`. + +Current model layout expected by the Pets contract is: ```text pets/ @@ -227,1051 +121,90 @@ pets/ embedding/dinov2_vits14/dinov2_vits14.pt ``` -When the YOLOX detector is missing, iPhotron downloads it from the configured -HTTPS model URL into the shared cache. The default URL points to the upstream -YOLOX release asset and can be overridden with: - -```bash -export IPHOTO_PET_DETECTOR_MODEL_URL="https://example.invalid/yolox_nano.onnx" -``` - -Production never executes Torch Hub. It loads a packaged DINOv2 TorchScript model, -or downloads the fixed HTTPS artifact declared by SHA-256 and exact byte size in -`src/iPhoto/pets/model_manifest.json`. Release engineering may regenerate the -artifact from the pinned source revision with -`tools/convert_dinov2_torchscript.py`; that tool also checks eager/TorchScript -numeric equivalence before publishing. - -For offline or packaged validation, disable first-use downloads with: - -```bash -export IPHOTO_PET_MODEL_AUTO_DOWNLOAD=0 -``` - -When `pets-ai` is missing, or model download/initialization fails, normal -browsing, People, editing, and maps continue to work; pet scan candidates remain -pending so scanning can resume after the runtime or model cache is installed. -For worker scheduling, status transitions, snapshot/state ownership, model -version upgrades, mixed People/Pets identity behavior, and focused tests, see -[`PETS_RECOGNITION_RUNTIME.md`](misc/PETS_RECOGNITION_RUNTIME.md). - -### Dev Dependencies - -```bash -pip install -e ".[dev]" -``` - -Includes: `pytest`, `pytest-mock`, `pytest-qt`, `ruff`, `black`, `mypy`, `types-Pillow`, `types-python-dateutil`. - ---- - -## Album Naming Rules - -### Filesystem case stability - -`v5.0.0` already used `.iPhoto` as the runtime work directory on Windows and -Linux (`WORK_DIR_NAME = ".iPhoto"`). That spelling is the canonical storage -contract for new libraries. - -Linux filesystems are case-sensitive, so a directory rename that only changes -letter case is a real path migration, not a harmless spelling cleanup. Treat -lowercase `.iphoto` as a legacy-compatible alias only: code may read and exclude -it when it already exists, but new libraries must create `.iPhoto`. - -Do not change managed library names such as `.iPhoto`, `.iphoto`, -`.iphoto.album.json`, or `.iPhoto/manifest.json` by case alone unless the -change includes an explicit migration plan, compatibility reads for existing -libraries, and focused tests on a case-sensitive filesystem. - -### Reserved album directories - -Album creation and rename flows must reject directory names reserved for -internal library infrastructure. Today the reserved names are: - -- `.iPhoto` and legacy `.iphoto` case variants -- `.Trash` -- `exported` - -These names are intentionally hidden by the library scan layer and therefore -must never be accepted as user album names. If a create/rename flow allows one -of them, the album can appear to "disappear" because the directory still exists -on disk but is filtered out of the visible album tree/dashboard. - -Implementation rules: - -- Keep the validation in the library layer so every entry point stays aligned - (`album dashboard`, sidebar menus, and any future CLI/API path). -- Keep the reserved-name list in a single shared source of truth used by both - name validation and album discovery. -- Raise a normal `LibraryError` path such as `AlbumOperationError` with a clear - user-facing message; UI surfaces should only display the warning and should - not duplicate the rule locally. -- Add regression coverage when touching album naming logic: - library tests should verify reserved names are rejected and existing albums - remain listed, while UI tests should verify reserved-name rename attempts show - a warning instead of removing the album from the dashboard. - -### Index-store visibility flags - -Collection queries use denormalized columns such as `is_deleted`, `has_gps`, -`thumbnail_state`, and `live_role` so large libraries can stay on covering -indexes instead of scanning path/text payloads. When adding or tightening one -of these flags, update both row writes and schema migration: - -- New scan rows should derive the flag at write time in the index-store mapper. -- Existing databases need a migration backfill before hot queries depend on the - new flag. -- Do not remove compatibility path predicates from a query unless the migration - proves equivalent legacy rows are backfilled. `.Trash` is especially - sensitive: normal collections must exclude it, but the Recently Deleted album - still needs to show it. -- Add one focused upgrade regression test for legacy rows and one query-level - test for the visible collection behavior. - ---- - -## Maps Extension Development Workflow - -### What the maps extension is - -iPhotron's offline OsmAnd/OBF runtime is expected to live in a self-contained -directory rooted at `src/maps/tiles/extension/`. At runtime, -`MapSourceSpec.osmand_default()` resolves that directory and expects the -following layout: - -| Path | Purpose | -|------|---------| -| `src/maps/tiles/extension/World_basemap_2.obf` | Default offline OBF map dataset | -| `src/maps/tiles/extension/misc/` | OsmAnd miscellaneous resources | -| `src/maps/tiles/extension/poi/` | OsmAnd POI resources | -| `src/maps/tiles/extension/rendering_styles/` | OsmAnd style XML files; the default is `snowmobile.render.xml` | -| `src/maps/tiles/extension/routing/` | OsmAnd routing resources | -| `src/maps/tiles/extension/search/geonames.sqlite3` | Offline place search database used by Assign Location | -| `src/maps/tiles/extension/bin/` | Platform-specific helper/native widget binaries and dependent libraries (`.exe`/`.dll` on Windows, ELF binaries/`.so` on Linux, Mach-O binaries/`.dylib`/frameworks on macOS) | - -This directory is the contract used by: - -- local source checkouts -- `iphoto-gui` map startup and `PhotoMapView` -- `scripts/build_nuitka_windows.ps1` -- `scripts/build_nuitka_fast.sh` and Linux standalone packaging -- the Windows installer's optional map-extension package - -### Platform runtime notes - -On Linux, iPhotron can use both the helper-backed OBF renderer and the native -OsmAnd widget. The native widget currently expects Qt's XCB desktop OpenGL path, -so when that backend is selected iPhotron auto-sets: - -- `QT_QPA_PLATFORM=xcb` -- `QT_OPENGL=desktop` -- `QT_XCB_GL_INTEGRATION=xcb_glx` - -That means native maps on Linux currently run best on X11 or XWayland. If a -`PySide6-OsmAnd-SDK/` checkout exists either inside this repository root or as a -sibling directory next to it, iPhotron prefers its -`tools/osmand_render_helper_native/dist-linux/` widget build during development. - -On macOS, the legacy Python/OpenGL map path deliberately uses -`QOpenGLWindow + QWidget.createWindowContainer()` instead of `QOpenGLWidget`. -That keeps map tiles opaque inside the app's transparent, frameless main -window. The native OsmAnd widget can also be discovered from the extension -`bin/` directory or from a sibling SDK checkout when a `dist-macosx` runtime is -available. - -Media preview widgets use QRhi backend selection rather than a fixed raw-GL -path. `IPHOTO_RHI_BACKEND=auto` selects Metal on macOS when Qt exposes it, and -OpenGL elsewhere. Use `IPHOTO_RHI_BACKEND=opengl` to force the legacy OpenGL -path for diagnostics. - -### Upstream sub-project: `PySide6-OsmAnd-SDK` - -The source of truth for building the map extension is the standalone upstream -repository: - -- `https://github.com/OliverZhaohaibin/PySide6-OsmAnd-SDK` - -That repository exists specifically to build and validate the OsmAnd runtime -outside of the main iPhotron application. It contains: - -- vendored `OsmAnd-core`, `OsmAnd-core-legacy`, and `OsmAnd-resources` -- Windows, Linux, and macOS build scripts/output directories for helper and - native widget runtimes -- the PySide6/OsmAnd preview app used to validate the runtime independently -- a stable place to iterate on Qt6/PySide6 integration without touching the - entire iPhotron application - -In practice: - -- `PySide6-OsmAnd-SDK` builds the runtime -- `iPhotron` vendors the produced runtime into `src/maps/tiles/extension/` -- packaged builds then consume the vendored extension from this repository - -### Recommended build strategy - -For Windows, Linux, and macOS packaging, the recommended path is: - -1. build the runtime in `PySide6-OsmAnd-SDK` -2. copy the resulting map data, OsmAnd resources, and native binaries into - `iPhotron/src/maps/tiles/extension/` -3. verify the runtime from the iPhotron checkout -4. package with Nuitka from the iPhotron checkout - -This keeps the OsmAnd-specific toolchain work in the dedicated side project, -while keeping iPhotron releases self-contained. - -### Step 1: Clone and prepare the side project - -```powershell -git clone https://github.com/OliverZhaohaibin/PySide6-OsmAnd-SDK -cd PySide6-OsmAnd-SDK -python -m venv .venv -.venv\Scripts\activate -python -m pip install -e . -``` - -If you want to work with the same Python environment as iPhotron, that is also -fine as long as `PySide6`, `cmake`, and the required Windows toolchains are -available. - -### Step 2: Build the native runtime in the side project - -For the full iPhotron maps extension on Windows, prefer the MSVC build because -it produces the complete native widget runtime mirrored under -`tools\osmand_render_helper_native\dist-msvc`: - -```powershell -powershell -ExecutionPolicy Bypass -File tools\osmand_render_helper_native\build_native_widget_msvc.ps1 -BuildType Release -``` - -For Linux, build the native helper/widget runtime into `dist-linux`: - -```bash -bash tools/osmand_render_helper_native/build_linux.sh -``` - -For macOS, build the helper/widget runtime into `dist-macosx` from the SDK -checkout: - -```bash -QT_ROOT=/opt/homebrew/opt/qt bash tools/osmand_render_helper_native/build_macos.sh -``` - -Useful alternatives inside `PySide6-OsmAnd-SDK`: - -- `build_helper.ps1` - Shortest path if you only need the helper EXE and optional MinGW widget build. -- `build_helper_official.ps1` - Runs the official OsmAnd MinGW-oriented chain in a staged workspace. -- `build_native_widget_msvc.ps1` - Recommended for iPhotron release work because it produces the native widget - DLL and the `dist-msvc` runtime consumed most directly by the packaging flow. -- `build_linux.sh` - Produces the Linux helper and `.so` widget runtime under `dist-linux`. -- `build_macos.sh` - Produces the macOS helper and `.dylib` widget runtime under `dist-macosx`. - -The main outputs you need are: - -| Side-project output | Why it matters in iPhotron | -|---------------------|----------------------------| -| `tools\osmand_render_helper_native\dist-msvc\osmand_render_helper.exe` | Helper-backed Python OBF rendering | -| `tools\osmand_render_helper_native\dist-msvc\osmand_native_widget.dll` | Native Qt/OpenGL OsmAnd widget | -| `tools\osmand_render_helper_native\dist-msvc\OsmAndCore_shared.dll` | Native OsmAnd core runtime | -| `tools\osmand_render_helper_native\dist-msvc\OsmAndCoreTools_shared.dll` | Native OsmAnd tools runtime | -| `tools\osmand_render_helper_native\dist-msvc\Qt6*.dll` | Required Qt runtime dependencies for the native/helper binaries | -| `tools/osmand_render_helper_native/dist-linux/osmand_render_helper` | Linux helper-backed Python OBF rendering | -| `tools/osmand_render_helper_native/dist-linux/osmand_native_widget.so` | Linux native Qt/OpenGL OsmAnd widget | -| `tools/osmand_render_helper_native/dist-linux/libOsmAndCore_shared.so` | Linux native OsmAnd core runtime | -| `tools/osmand_render_helper_native/dist-linux/libOsmAndCoreTools_shared.so` | Linux native OsmAnd tools runtime | -| `tools/osmand_render_helper_native/dist-macosx/osmand_render_helper` | macOS helper-backed Python OBF rendering | -| `tools/osmand_render_helper_native/dist-macosx/osmand_native_widget.dylib` | macOS native Qt/OpenGL OsmAnd widget | -| `plugin/data/geonames.sqlite3` | Offline search database for Assign Location | -| `vendor\osmand\resources\...` | Rendering styles and supporting OsmAnd resources | -| `src\maps\tiles\World_basemap_2.obf` | Default demo OBF dataset used by the extension | - -### Step 3: Sync the side-project outputs into `iPhotron` - -The safest approach is to copy the side-project outputs into -`src/maps/tiles/extension/` so the iPhotron checkout stays self-contained. - -Example PowerShell sync: - -```powershell -$sdkRoot = "D:\python_code\iPhoto\PySide6-OsmAnd-SDK" -$repoRoot = "D:\python_code\iPhoto\iPhotron-LocalPhotoAlbumManager" -$extensionRoot = Join-Path $repoRoot "src\maps\tiles\extension" -$binRoot = Join-Path $extensionRoot "bin" -$searchRoot = Join-Path $extensionRoot "search" - -New-Item -ItemType Directory -Force -Path $extensionRoot, $binRoot, $searchRoot | Out-Null - -Copy-Item -LiteralPath (Join-Path $sdkRoot "src\maps\tiles\World_basemap_2.obf") ` - -Destination $extensionRoot -Force -Copy-Item -LiteralPath (Join-Path $sdkRoot "plugin\data\geonames.sqlite3") ` - -Destination $searchRoot -Force - -foreach ($resourceDir in "misc", "poi", "rendering_styles", "routing") { - Copy-Item -LiteralPath (Join-Path $sdkRoot "vendor\osmand\resources\$resourceDir") ` - -Destination $extensionRoot -Recurse -Force -} - -Copy-Item -LiteralPath (Join-Path $sdkRoot "tools\osmand_render_helper_native\dist-msvc\*") ` - -Destination $binRoot -Recurse -Force -``` - -Equivalent Linux sync: - -```bash -sdk_root="$HOME/python-code/PySide6-OsmAnd-SDK" -repo_root="$HOME/python-code/iPhotron-LocalPhotoAlbumManager" -extension_root="$repo_root/src/maps/tiles/extension" -bin_root="$extension_root/bin" -search_root="$extension_root/search" - -mkdir -p "$extension_root" "$bin_root" "$search_root" -cp -f "$sdk_root/src/maps/tiles/World_basemap_2.obf" "$extension_root/" -cp -f "$sdk_root/plugin/data/geonames.sqlite3" "$search_root/" -for resource_dir in misc poi rendering_styles routing; do - rm -rf "$extension_root/$resource_dir" - cp -a "$sdk_root/vendor/osmand/resources/$resource_dir" "$extension_root/" -done -cp -a "$sdk_root/tools/osmand_render_helper_native/dist-linux/." "$bin_root/" -``` - -Recommended macOS sync: - -```bash -python scripts/sync_macos_map_extension.py \ - --sdk-root "$HOME/python-code/PySide6-OsmAnd-SDK" -``` - -The macOS sync script copies `World_basemap_2.obf`, `search/geonames.sqlite3`, -the OsmAnd resource directories, `osmand_render_helper`, -`osmand_native_widget.dylib`, recursively resolved non-system Mach-O -dependencies, then patches `install_name`/rpaths and ad-hoc signs copied -binaries. - -If you are intentionally using the MinGW path instead of MSVC, replace -`dist-msvc` with `dist`. The helper-backed Python renderer only requires the -helper executable plus its dependent DLLs, but the native widget path also -requires a usable widget DLL in the same `bin/` directory. - -On Linux, native widget discovery prefers the sibling `PySide6-OsmAnd-SDK` -build when it exists. On macOS, the local extension is checked first and the -SDK `dist-macosx` output is also searched for development convenience. Keep the -checkout in sync with the runtime you actually want to exercise. - -### Step 4: Verify the runtime from the iPhotron checkout - -After syncing the extension, return to the iPhotron repository and verify the -runtime before packaging: - -```powershell -cd D:\python_code\iPhoto\iPhotron-LocalPhotoAlbumManager -python -m pip install -e ".[dev]" -python src\maps\main.py --backend auto -python src\maps\main.py --backend python -python src\maps\main.py --backend native -python src\maps\main.py --backend legacy -``` - -Recommended additional checks: - -```powershell -python -m pytest tests\test_maps_main.py tests\test_photo_map_view.py -q -iphoto-gui -``` - -What to look for: - -- `--backend auto` chooses the native widget when it is healthy -- `--backend python` succeeds with the helper-backed OBF renderer -- `--backend native` loads the native widget library without missing runtime errors -- `--backend legacy` still renders the bundled legacy vector tiles -- the GUI Location view starts without falling back unexpectedly -- on Linux, the native path starts under X11/XWayland rather than failing with missing GLX/XCB support -- on macOS, the legacy GL map reports a `MapGLWindowWidget`/`MapGLWindow` - diagnostic and does not show transparent tile areas - -### Development-time overrides - -For experimentation you can override the managed extension root or individual -runtime binaries: - -| Environment variable | Purpose | -|----------------------|---------| -| `IPHOTO_OSMAND_EXTENSION_ROOT` | Override the managed extension root. The directory must already use the `tiles/extension` layout described above | -| `IPHOTO_OSMAND_RENDER_HELPER` | Override the helper executable/command | -| `IPHOTO_OSMAND_NATIVE_WIDGET_LIBRARY` | Override the native widget library path | -| `IPHOTO_PREFER_OSMAND_NATIVE_WIDGET` | Set to `0` to force the Python OBF path in auto mode | -| `IPHOTO_DISABLE_OPENGL` | Set to `1` to force CPU/fallback rendering where supported | -| `IPHOTO_MAP_GL_DEBUG` | Set to `1` to print one-shot map GL surface diagnostics | -| `IPHOTO_OSMAND_GL_PARTIAL_UPDATE` | Set to `1` to allow partial updates on platforms that default to full GL repaint | -| `IPHOTO_RHI_BACKEND` | `auto`, `metal`, or `opengl` for media preview QRhi backend selection | -| `IPHOTO_ALLOW_PACKAGED_LINUX_WAYLAND` | Set to `1` only when deliberately testing packaged Linux maps outside the default XCB/GLX path | - -Example: - -```powershell -$env:IPHOTO_OSMAND_EXTENSION_ROOT = "D:\tmp\iphoto-extension\extension" -$env:IPHOTO_OSMAND_RENDER_HELPER = "D:\python_code\iPhoto\PySide6-OsmAnd-SDK\tools\osmand_render_helper_native\dist-msvc\osmand_render_helper.exe" -$env:IPHOTO_OSMAND_NATIVE_WIDGET_LIBRARY = "D:\python_code\iPhoto\PySide6-OsmAnd-SDK\tools\osmand_render_helper_native\dist-msvc\osmand_native_widget.dll" -iphoto-gui -``` - -Linux example: +`IPHOTO_PET_MODEL_DIR` can select the model root. Build scripts also use +`src/extension/models/...` as a packaging/staging convention; that directory is +not guaranteed tracked content in a fresh clone. -```bash -export IPHOTO_OSMAND_EXTENSION_ROOT="$HOME/tmp/iphoto-extension/extension" -export IPHOTO_OSMAND_RENDER_HELPER="$HOME/python-code/PySide6-OsmAnd-SDK/tools/osmand_render_helper_native/dist-linux/osmand_render_helper" -export IPHOTO_OSMAND_NATIVE_WIDGET_LIBRARY="$HOME/python-code/PySide6-OsmAnd-SDK/tools/osmand_render_helper_native/dist-linux/osmand_native_widget.so" -iphoto-gui -``` +The detector manifest has a fixed HTTPS source plus integrity metadata. The +DINOv2 manifest describes a prebuilt TorchScript artifact, pinned source +provenance, SHA-256, and exact size. Its current `torchscript_url` is `null`. +Therefore production runtime must not promise a DINOv2 first-use download: +DINOv2 must be packaged or explicitly staged while that URL remains null. -macOS example: +Production inference does not execute arbitrary Torch Hub Python. Torch Hub / a +pinned upstream revision belongs to release conversion and provenance tooling +(`tools/convert_dinov2_torchscript.py`); runtime trust is the prebuilt +TorchScript artifact plus manifest hash/size validation. -```bash -export IPHOTO_OSMAND_EXTENSION_ROOT="$HOME/tmp/iphoto-extension/extension" -export IPHOTO_OSMAND_RENDER_HELPER="$HOME/python-code/PySide6-OsmAnd-SDK/tools/osmand_render_helper_native/dist-macosx/osmand_render_helper" -export IPHOTO_OSMAND_NATIVE_WIDGET_LIBRARY="$HOME/python-code/PySide6-OsmAnd-SDK/tools/osmand_render_helper_native/dist-macosx/osmand_native_widget.dylib" -iphoto-gui -``` +The current clustering pipeline is `species-bounded-single-link-v3`: clustering +is species-separated, obeys cannot-link constraints, and bounds cluster diameter +to prevent uncontrolled chaining. Strong People-face overlap normally suppresses +a pet candidate, but a substantially larger plausible pet-body box containing a +smaller face may be preserved by the runtime size/image-coverage exception. +Do not summarize the rule as unconditional “People always wins”. -This is convenient for debugging, but release builds should still copy the -runtime into `src/maps/tiles/extension/` so the repository and packaged app stay -self-contained. +See `misc/PETS_RECOGNITION_RUNTIME.md` for thresholds, status transitions, +persistence, and reconciliation behavior. -### Linux packaged native-widget guardrails +## Maps Development -The Linux source checkout and the Linux Nuitka bundle must be treated as two -separate runtime targets. A map preview that works from `python src/maps/main.py` -does **not** prove that the packaged GUI will survive opening the map section. -The failure mode that triggered this guidance was: +The optional OsmAnd runtime is staged under: ```text -ERROR: Failed to initialize GLEW: GLX 1.2 and up are not supported -``` - -That error appeared only after entering the map section in a packaged build, -even though the unfrozen source checkout already had Linux X11 forcing in the -main entry point. - -When touching Linux map packaging, keep these rules in place: - -- Treat packaged/frozen Linux runs as a dedicated code path. Before - `QApplication` is created, force `QT_QPA_PLATFORM=xcb` for packaged builds - unless `IPHOTO_ALLOW_PACKAGED_LINUX_WAYLAND=1` is set explicitly for - debugging. -- When `QT_QPA_PLATFORM=xcb`, keep `QT_OPENGL=desktop` and - `QT_XCB_GL_INTEGRATION=xcb_glx` aligned so the native OsmAnd widget gets the - GLX-backed desktop OpenGL context expected by GLEW. -- Do not use the generic OpenGL probe as the only gate for the native widget. - Backend selection must still run `probe_native_widget_runtime(...)`; if the - native library loads cleanly, prefer it even when the generic Qt OpenGL probe - failed, and if the runtime probe fails, log the reason and fall back to the - Python OBF path. -- Keep the Linux/Nuitka packaging inputs explicit. The current fast build - script needs `--enable-plugin=pyside6`, - `--include-qt-plugins=qml,multimedia`, `--include-package=OpenGL`, and - `--include-package=OpenGL_accelerate` so the packaged runtime matches the - editable environment more closely. -- After a Linux Nuitka build, verify the packaged OsmAnd binaries can still - resolve Qt at runtime. If the packaged `maps/tiles/extension/bin` runtime can - not find the bundled PySide6 Qt libraries, repair its RUNPATH before treating - the build as releasable. -- Regressions must be tested from the packaged executable, not only from the - source checkout. The minimum smoke test is: start the packaged app on Linux, - switch into the map section, confirm no GLEW/GLX error is emitted, and verify - the view uses the native widget only when `probe_native_widget_runtime(...)` - succeeds. - -For future work, do not remove the packaged-Linux override just because -development mode works under Wayland/XWayland. If you want to relax that rule, -first prove the packaged map section is stable from a fresh Nuitka bundle and -keep the opt-out behind a documented environment variable. - ---- - -## Face Recognition Development Workflow - -### Runtime contract - -The People feature scans assets in the background, detects faces with -InsightFace, stores face embeddings, and clusters those embeddings into people. -The runtime model cache is shared at: - -| Path | Purpose | -|------|---------| -| `src/extension/models/buffalo_s/` | Checked-in InsightFace model cache | -| `src/extension/models/buffalo_s/det_500m.onnx` | Face detector | -| `src/extension/models/buffalo_s/w600k_mbf.onnx` | Face recognition embedding model | - -The default packaged path is resolved from the installed package as -`extension/models`. For local debugging, override it with: - -```powershell -$env:IPHOTO_FACE_MODEL_DIR = "D:\python_code\iPhoto\iPhotos\src\extension\models" +src/maps/tiles/extension/ ``` -The model directory may be absent in a packaged build. In that case -InsightFace can download the model pack on first use, but release builds should -still bundle the model cache when an offline-ready distribution is required. - -### InsightFace import rules +Its upstream build project is +`OliverZhaohaibin/PySide6-OsmAnd-SDK`. Build the platform runtime there, stage +map resources/search/native helper or widget binaries into the extension tree, +then validate and package from iPhotron. Missing Maps runtime must degrade +gracefully rather than block desktop startup. -Always import the concrete FaceAnalysis module through the People pipeline's -compatibility path: +## Large-Library / Scan Work -```python -from insightface.app.face_analysis import FaceAnalysis -``` +Gallery reads are SQL-first and windowed. Paint/model access stays memory-only; +sparse windows and thumbnail demand load asynchronously and reject stale +generations. Visible scan updates use post-commit `ScanBatchCommitted` events. +Do not restore historical `scanChunkReady` production transport. -Do not switch back to: - -```python -from insightface.app import FaceAnalysis -``` +`requirements/scan_c_hotspot_optimization.md` contains historical profiling and +an obsolete scanner call graph. New native/compiled optimization decisions must +first profile the current scanner/application/index-store path. See +`requirements/README.md` for document lifecycle rules. -The package-level import pulls in InsightFace's mask-rendering path, which can -drag in `albumentations` and `pydantic`. In Nuitka builds this has produced -runtime annotation failures such as `name 'Literal' is not defined` and -`name 'NDArray' is not defined`. +## Packaging -The pipeline intentionally installs two compatibility shims before importing -InsightFace: +- Windows/general Nuitka: `misc/BUILD_EXE.md` +- Linux standalone: `scripts/build_nuitka_fast.sh` -> `dist/entrypoint.dist/` +- Debian: `misc/BUILD_DEB.md` +- AppImage: `misc/BUILD_APPIMAGE.md` +- Flatpak status: `misc/BUILD_FLATPAK.md` -- runtime typing names in `builtins`, for third-party annotations evaluated at - runtime inside packaged apps -- a lightweight `albumentations` stub, because iPhotron does not use - InsightFace mask rendering +The Debian/AppImage paths are current in-repository build contracts. The v6.6.8 +Flatpak bundle is a published release artifact, but this branch currently has no +maintained in-repository Flatpak manifest/build driver; do not conflate the two. -Keep these shims in place unless the packaging strategy changes and the -replacement has been verified in a Nuitka build. - -### Required InsightFace modules - -People clustering only needs bounding boxes and embeddings. Keep -`FaceAnalysis` constrained to: - -```python -allowed_modules=["detection", "recognition"] -``` - -Do not load `landmark_2d_106`, `landmark_3d_68`, or `genderage` for the People -scan. Those models are not used for clustering and have caused packaged -asset-level failures such as: - -```text -'NoneType' object has no attribute 'shape' -``` - -If a future feature needs landmarks or gender/age attributes, add that feature -behind a separate tested path and validate it in a Nuitka package before -enabling it for background scanning. - -### Scan status and retry rules - -Face scan state is stored per asset. The intended behavior is: - -- `pending`: asset has not been scanned yet -- `done`: scan completed, with or without detected faces -- `skipped`: asset is not eligible for face scanning -- `retry`: asset failed once and should be attempted again -- `failed`: asset failed after a retry and should not block the whole queue - -Important rules: - -- A batch-level exception should pause scanning and surface the real exception - text in the People page. -- An asset-level exception should be logged, then marked `retry` on the first - failure. -- If the same asset fails again while already in `retry`, mark it `failed` so - the queue can continue. -- A full library rescan must reset `retry` and `failed` face statuses back to - the initial status. It should preserve only stable completed states such as - `done` and `skipped` when the asset identity is unchanged. - -This prevents a broken image or transient packaged-runtime issue from -deadlocking People scanning until the user deletes the database manually. - -### Stable People state - -Keep the People runtime snapshot and user decisions separate: - -- `.iPhoto/faces/face_index.db` is the rebuildable runtime snapshot containing - detected/manual faces and clustered person records. -- `.iPhoto/faces/face_state.db` stores human decisions: names, canonical - identities, selected covers, hidden flags, person order, groups, group order, - pinned state, group covers, and group asset caches. -- A scan commit may recluster all faces and rewrite the runtime snapshot, but it - must preserve the stable state and repair it through repository/coordinator - APIs instead of dropping it. -- Group asset caches must be refreshed when scan commits, merges, manual face - edits, person deletion, or group membership changes can affect common-photo - results. -- People in different hidden states must not be merged. Keep this enforced in - both UI and repository/service layers. - -### Debugging packaged face scan failures - -The app writes rotating logs to: - -```powershell -%LOCALAPPDATA%\iPhoto\iPhoto.log -``` - -For custom locations: - -```powershell -$env:IPHOTO_LOG_DIR = "D:\tmp\iphoto-logs" -``` - -Useful log messages: - -- `Face scanning paused: ...` - Batch-level failure; the message should include the actual exception. -- `Face detection failed for ...` - Full traceback for an asset-level detection failure. -- `Face scan failed for asset ...` - The worker marked a specific asset for retry or failure. - -When a packaged build says `Some assets could not be face scanned and will be -retried after a rescan`, inspect the log before changing model paths. That -message means model initialization succeeded far enough to process assets, but -at least one asset failed during detection/embedding. - -### Verification checklist - -After changing People scanning, run the focused tests: - -```powershell -python -m pytest tests\test_people_pipeline.py tests\test_people_service.py tests\cache\test_global_repository.py tests\test_face_cluster_pipeline.py -``` - -When changing People UI, groups, covers, hidden-state filtering, merges, or -popup/menu behavior, also run: - -```powershell -python -m pytest tests\gui\widgets\test_people_dashboard_widget.py tests\test_people_repository.py tests\test_people_service.py tests\test_information_popup.py tests\ui\controllers\test_context_menu_cover.py -``` - -For a local smoke test against a real image: - -```powershell -python -c "from pathlib import Path; from iPhoto.people.pipeline import FaceClusterPipeline; p=FaceClusterPipeline(model_root=Path('src/extension/models')); out=p.detect_faces_for_rows([{'id':'test','rel':'DSCF5586.JPG'}], library_root=Path(r'C:\Users\Olive\Downloads\face'), thumbnail_dir=Path(r'C:\Users\Olive\Downloads\face\.iPhoto\faces')); print([(x.asset_rel, x.error, len(x.faces)) for x in out]); print(sorted(p._ensure_face_analysis().models.keys()))" -``` - -The loaded InsightFace models should be only: - -```text -['detection', 'recognition'] -``` - -For release verification, rebuild with Nuitka and test the People page from the -packaged executable, not from the editable source checkout. - ---- - -## Gallery Detail GPU-first Development - -Static Gallery → Detail presentation has one production pipeline: - -```text -DetailRenderTransaction - -> DetailStillRequestScheduler - -> memory/disk neutral surface cache or platform decoder - -> bounded GPU texture residency - -> PhotoRenderSessionHandle shared by Detail/Edit - -> actual-draw presented terminal event -``` - -The first still surface is selected from viewport physical pixels, DPR, crop, -rotation, perspective, and zoom demand. It is detached RGBA8888/sRGB and does -not generate initial mipmaps. The source cache key excludes `.ipo`; adjustment -changes replace immutable shader state without re-decoding or re-uploading the -same source. Export remains the separate full-resolution path. - -Decoder selection is RAW → rawpy, macOS non-RAW → ImageIO, Windows non-RAW → -WIC, and Linux/general non-RAW → Qt. ImageIO/WIC failures fall back to Qt in the -same worker lane and must be visible as `decode_fallback` profiler events. Do -not add a second controller-owned decoder, path-keyed texture load, or CPU -full-image Edit preview. - -Run the focused contracts after changing this path: - -```bash -QT_QPA_PLATFORM=offscreen .venv/bin/python -m pytest -q \ - tests/gui/test_detail_pipeline.py \ - tests/gui/test_detail_render_coordinator.py \ - tests/gui/test_detail_decode_backend.py \ - tests/gui/test_detail_request_scheduler.py \ - tests/gui/test_detail_surface_cache.py \ - tests/gui/test_detail_render_session.py \ - tests/ui/controllers/test_player_view_controller_adjustments.py \ - tests/ui/widgets/test_still_texture_residency.py \ - tests/test_detail_benchmark.py -``` - -Windows must additionally run `tests\gui\test_detail_decode_backend.py` on a -real Windows Python so the WIC/COM test is not skipped. Package-level validation -uses `tools/run_detail_packaged_benchmark.py`; commands, manifest semantics, -privacy rules, and output validation are documented in the runbook linked -above. Manual acceptance has been completed on Windows and Linux for this -rollout; future platform decoder/render changes require new target-OS checks. - ---- - -## Build & Package - -### Running the Application - -```bash -# Launch the GUI -iphoto-gui -``` - -### Building the Executable - -For distribution, iPhotron uses **Nuitka** with an AOT compilation step for Numba filters. - -#### Step 1: AOT Compilation - -```bash -python src/iPhoto/core/filters/build_jit.py -``` - -This generates a compiled C-extension (`.so` / `.pyd`) in `src/iPhoto/core/filters/`. - -#### Step 2: Build with Nuitka - -```bash -bash scripts/build_nuitka_fast.sh -``` - -The script uses a startup-optimized Nuitka profile (`--standalone`, `--python-flag=no_site`, `--lto=yes`, `--clang`) and excludes heavy dev/runtime-only packages from the final bundle. -It also includes `src/maps/tiles`, so Linux standalone builds keep the bundled -OBF/resources layout intact as long as `src/maps/tiles/extension/` is staged -correctly before packaging. - -Any manual Nuitka profile must include the QRhi shader assets next to the media -widgets. The current Windows script includes `image_viewer_rhi.*`, -`image_viewer_overlay.*`, and `video_renderer.*` source/QSB files explicitly so -macOS/Metal and OpenGL QRhi previews share the same packaged shader set. - -For Windows release work that includes the native maps extension, prefer: - -```powershell -powershell -ExecutionPolicy Bypass -File scripts\build_nuitka_windows.ps1 -OutputDir build -IncludeOptionalAssets -``` - -With `-IncludeOptionalAssets`, the script stages -`src/maps/tiles/extension/bin` from the native runtime before invoking Nuitka; -without that switch it builds the smaller base package. It is the recommended -packaging entry point whenever the OsmAnd helper/native widget runtime is part -of the build. It uses -`docs/picture/logo_new.ico` by default and discovers Python from the repository -`.venv`, the parent `.venv`, `py.exe -3.12`, or a real `python.exe` on `PATH`. -Use `-PythonExe ` to override discovery; Microsoft Store execution aliases -are rejected during preflight. - -For macOS packaging, run the SDK build and sync script first: - -```bash -QT_ROOT=/opt/homebrew/opt/qt bash ../PySide6-OsmAnd-SDK/tools/osmand_render_helper_native/build_macos.sh -python scripts/sync_macos_map_extension.py --sdk-root ../PySide6-OsmAnd-SDK -``` - -Then use the same AOT/Nuitka discipline: bundle `src/maps/tiles`, include the -QRhi `.qsb` files, and verify the packaged app opens both media previews and -the Location view from the frozen runtime. `scripts/build_nuitka_macos.sh` -passes the same default ICO to Nuitka, which requires `imageio` to convert it -to the app bundle's ICNS resource. Linux remains a standalone build and gets -its desktop icon only at the AppImage packaging stage. - -See [docs/misc/BUILD_EXE.md](misc/BUILD_EXE.md) for detailed troubleshooting and manual flags. - ---- - -## Desktop Startup Performance - -The GUI startup contract is “paint the window shell, then warm optional -features.” Do not use a zero-delay timer as a substitute for the boundary: -startup work must be connected to `MainWindow.firstPainted`. Widget creation -still belongs on the GUI thread and should be split across event-loop turns. - -Keep imports above that boundary narrow. In particular, importing -`iPhoto.gui.main` or `MainWindow` must not load NumPy, Qt Multimedia, the -People/Pets AI pipelines, map rendering, asset-import services, edit-session models, or -`MainCoordinator`. Package-level convenience imports in startup-reachable -packages should use `__getattr__` lazy exports, while imports needed only by a -scan or optional feature should live at the call site. Preserve public names so -callers and test patch targets continue to work. - -Enable checkpoint logging for a local diagnostic run with: - -```bash -IPHOTO_STARTUP_PROFILE=1 iphoto-gui -``` - -On Windows PowerShell: - -```powershell -$env:IPHOTO_STARTUP_PROFILE = "1" -iphoto-gui -``` - -The profiler appends JSON Lines records containing `stage`, `elapsed_ms`, -`pid`, and wall time. Logs are written to: - -- Windows: `%LOCALAPPDATA%\iPhoto\logs\startup.jsonl` -- macOS: `~/Library/Logs/iPhoto/logs/startup.jsonl` -- Linux: `${XDG_STATE_HOME:-~/.local/state}/iPhoto/logs/startup.jsonl` - -Unset the variable for normal launches; disabled profiling does not create a -file. Compare at least `main_window.show_called`, `main_window.first_paint`, -feature creation, and `main_coordinator.started` when investigating a -regression. - -Run the focused startup guardrails with: - -```bash -python -m pytest tests/gui/test_startup_import_boundary.py tests/gui/test_main.py -``` - -On Windows, also verify that startup shows one stable top-level window. The -detail feature intentionally remains pre-show there because adding its QRhi -widgets after the window is visible can recreate the native window. - ---- - -## Running Tests - -```bash -# Architecture guardrail -python3 tools/check_architecture.py - -# Run all tests -python -m pytest - -# Run with verbose output -python -m pytest -v - -# Run a specific test file -python -m pytest tests/application/test_library_session.py - -# Run tests matching a pattern -python -m pytest -k "test_scan" - -# Run architecture tests explicitly -python -m pytest tests/architecture -q -``` - -Test configuration is in `pyproject.toml` under `[tool.pytest.ini_options]`: - -- Test paths: `tests/` -- The `pytest-qt` plugin is disabled; GUI tests remain part of normal discovery - and provide their own Qt fixtures/offscreen setup where required. - -Use the project virtual environment explicitly when the shell does not have -`pytest` on `PATH`: - -```bash -.venv/bin/python -m pytest tests/architecture -q -``` - ---- - -## Debugging - -### GUI Debugging - -```bash -# Enable Qt debug output -export QT_DEBUG_PLUGINS=1 -iphoto-gui -``` - -### Common Issues - -| Issue | Solution | -|-------|----------| -| `ExifTool not found` | Ensure `exiftool` is in your `PATH` | -| `FFmpeg not found` | Ensure `ffmpeg` and `ffprobe` are in your `PATH` | -| OpenGL errors | Update GPU drivers; ensure OpenGL 3.3+ support | -| Windows Detail logs `wic_to_qt` for every image | Run `pytest -q tests\gui\test_detail_decode_backend.py`; verify the WIC test passes and that COM declarations use fixed-width `HRESULT` rather than `ctypes.wintypes.HRESULT` | -| `_jit_compiled` module not found | Run AOT compilation step (see Build section) | -| macOS map tile area is transparent | Verify the active backend is `MapGLWindowWidget`/`MapGLWindow`, keep `IPHOTO_MAP_GL_DEBUG=1` diagnostics, and avoid forcing the legacy `QOpenGLWidget` map path | -| Packaged media preview cannot load QRhi shaders | Ensure `image_viewer_rhi.*`, `image_viewer_overlay.*`, and `video_renderer.*` `.qsb` files are included in the Nuitka data files | - ---- - -## Popup Guardrails - -The application has shared popup plumbing for information/warning surfaces. -When popup code is refactored, prefer the project's own popup implementation -instead of dropping back to native/system-styled `QMessageBox` windows. - -- Route routine in-app warning/info popups through the shared themed helpers. -- Make popup theme resolution follow the active app/window theme before the OS - color scheme. -- Keep popup positioning centered on the hosting top-level window. -- See - [`docs/misc/PROJECT_POPUP_GUARDRAILS.md`](misc/PROJECT_POPUP_GUARDRAILS.md) - for the project-wide rule plus the People dashboard regression checklist. - -### Context Menu Guardrails - -Qt context menus must use the project menu styling instead of bare `QMenu` -instances. The main window uses translucent rounded chrome, and unstyled menus -can inherit that translucency and render with a transparent background. - -- For sidebar and album-related menus, call - `_apply_main_window_menu_style(menu, parent)` from - `iPhoto.gui.ui.menus.album_sidebar_menu` before adding or executing actions. -- If a menu uses a local stylesheet instead, set - `menu.setAttribute(Qt.WidgetAttribute.WA_StyledBackground, True)` and apply - an explicit opaque `QMenu { background-color: ... }` rule. -- Do not create and execute a naked `QMenu(self)` from widgets such as - dashboards, cards, sidebars, or popups. -- When adding a new right-click surface, add or update a focused GUI test that - verifies the menu is styled and that important actions are present. - -#### Unified Right-Click Menu Rules - -The app now treats sidebar, dashboard, and gallery context menus as one shared -interaction system. When you add or change a right-click entry, keep these -rules aligned across surfaces: - -- Use `MenuContext` + `populate_menu()` for declarative menus whenever the - surface already participates in the shared menu system. -- Right-clicking an asset in gallery must first sync selection to the clicked - row before computing menu visibility, so selection-scoped actions operate on - the intended asset. -- Album-cover actions must resolve paths relative to the active album root - before calling `facade.set_cover(...)`. Do not assume `AssetDTO.rel_path` - already matches the current album root. -- `Rename…` is the canonical label for rename actions. Use the same ellipsis - style and the same empty-name validation across sidebar and pinned-item menus. -- Pinned-item rename is a sidebar-local alias. Persist it through - `PinnedItemsService` instead of mutating the underlying album/person/group - entity name. -- `Pin`/`Unpin` and `Rename…` should stay adjacent on sidebar-driven menus so - users can manage the same entity without hunting across different surfaces. -- Any regression around menu visibility or per-surface action parity needs a - targeted test in the menu/controller/widget layer that owns that surface. - -### People UI Conventions - -#### Reusable person-picker popup - -The canonical picker for choosing one or more People cards is -`GroupPeopleDialog` in -`src/iPhoto/gui/ui/widgets/people_dashboard_dialogs.py`. - -Use this dialog for all People-selection flows instead of creating ad-hoc -`QInputDialog` or combo-box popups. Current uses include: - -- `New Group` from the People dashboard -- `Merge Into...` from a People card context menu -- `Choose Someone Else...` from the Info panel face actions - -When reusing it: - -- pass `dark_mode=` from the hosting window/theme context explicitly when the - caller is not the People dashboard itself -- use `min_selection=1` and `max_selection=1` for single-target pickers -- customize `title_text`, `prompt_text`, and `confirm_text` per workflow -- keep multi-select behavior only for true grouping flows - - ---- - -## Code Style - -### Linters & Formatters - -| Tool | Purpose | Config | -|------|---------|--------| -| `ruff` | Linting & import sorting | `pyproject.toml` `[tool.ruff]` | -| `black` | Code formatting | `pyproject.toml` `[tool.black]` | -| `mypy` | Static type checking | — | - -### Style Rules - -- **Line length:** ≤ 100 characters -- **Type hints:** Use full annotations (e.g., `Optional[str]`, `list[Path]`, `dict[str, Any]`) -- **Imports:** Sorted by `ruff` (isort-compatible) -- **Docstrings:** Use triple-double-quote style - -### Running Linters - -```bash -# Lint check -ruff check src/ - -# Auto-fix lint issues -ruff check --fix src/ - -# Format code -black src/ - -# Type check -mypy src/ -``` - ---- - -## Commit Conventions - -Follow the [Conventional Commits](https://www.conventionalcommits.org/) specification: - -``` -(): - - - - -``` - -### Types - -| Type | Description | -|------|-------------| -| `feat` | New feature | -| `fix` | Bug fix | -| `docs` | Documentation only | -| `style` | Formatting (no code change) | -| `refactor` | Code refactoring (no feature/fix) | -| `perf` | Performance improvement | -| `test` | Adding or updating tests | -| `build` | Build system or dependencies | -| `ci` | CI/CD configuration | -| `chore` | Maintenance tasks | - -### Examples - -``` -feat(edit): add selective color adjustment panel -fix(cache): resolve SQLite WAL checkpoint deadlock -docs: update architecture diagram with MVVM layer -refactor(gui): extract coordinator from main window -test(core): add unit tests for curve resolver -``` +Offline People/Pets capable packages must explicitly stage their optional Python +runtime and model files. Build documentation must not describe +`src/extension/models` as guaranteed checked-in content. ---- +## Entry Points -## Project Entry Points +| Surface | Current boundary | +| --- | --- | +| Installed GUI | `iPhoto.entrypoint:main` | +| Internal Qt GUI | `iPhoto.gui.main:main` | +| CLI | `iPhoto.cli:app` | +| Desktop coordinator graph | `DesktopCoordinatorRuntime` | +| Library composition | `RuntimeContext` / `LibrarySession` | +| Recognition presentation | `RecognitionCoordinator` | -| Command | Entry Point | Description | -|---------|-------------|-------------| -| `iphoto-gui` | `iPhoto.gui.main:main` | GUI application | -| `iphoto` | `iPhoto.cli:app` | Typer CLI, using headless `LibrarySession` surfaces | +## Documentation Lifecycle -Important runtime entry classes: +`requirements/README.md` defines Active, residual-debt, Historical/Superseded, +and Finished states. Historical requirement documents are design evidence, not a +higher-authority production contract than current code, architecture, AGENT, or +active guardrails. -| Class / Function | Module | Description | -|------------------|--------|-------------| -| `RuntimeContext` | `iPhoto.bootstrap.runtime_context` | Process composition root and active library lifecycle | -| `LibrarySession` | `iPhoto.bootstrap.library_session` | Library-scoped assets, state, scans, People, Pets, Maps, edit, thumbnails, and location surfaces | -| `create_headless_library_session()` | `iPhoto.bootstrap.library_session` | CLI/non-GUI session construction | +When maintained documentation changes, run the docs link and README parity CI +checks in addition to subsystem-specific tests. diff --git a/docs/misc/BUILD_APPIMAGE.md b/docs/misc/BUILD_APPIMAGE.md new file mode 100644 index 000000000..53441edce --- /dev/null +++ b/docs/misc/BUILD_APPIMAGE.md @@ -0,0 +1,137 @@ +# Building an AppImage for Linux + +This guide documents the current AppImage wrapper in +`scripts/build_appimage.sh`. The AppImage stage consumes an existing Nuitka +standalone directory; it does not compile iPhotron itself. + +## Prerequisites + +- Linux +- a successful standalone Nuitka build +- `appimagetool` in `PATH`, or an explicit `--appimagetool PATH` +- a PNG application icon + +Build the standalone application first: + +```bash +bash scripts/build_nuitka_fast.sh +``` + +The default standalone directory is: + +```text +dist/entrypoint.dist/ +``` + +## Required Standalone Payload + +Before creating the AppImage, `scripts/build_appimage.sh` validates that the +standalone bundle contains: + +- an executable named `entrypoint.bin`, `entrypoint`, `main.bin`, or `main`; +- at least one compiled Qt shader (`*.qsb`); +- `maps/tiles/`; +- a native OsmAnd render helper under `maps/tiles/extension/bin/`; +- Qt's XCB platform plugin (`platforms/libqxcb.so`). + +The script intentionally fails instead of producing an AppImage that advertises +an incomplete Linux runtime. + +If the release advertises offline People/Pets recognition, populate and verify +the model staging input before the Nuitka step. `src/extension/models/` is a +build-staging location and is not guaranteed to exist in a fresh source clone. + +## Build Command + +Resolve the project version from `pyproject.toml`: + +```bash +VERSION="$(python - <<'PY' +import tomllib +from pathlib import Path +print(tomllib.loads(Path('pyproject.toml').read_text())['project']['version']) +PY +)" +``` + +Then run: + +```bash +scripts/build_appimage.sh \ + --standalone-dir dist/entrypoint.dist \ + --icon path/to/iphoto.png \ + --output "dist/iPhotron-${VERSION}-x86_64.AppImage" +``` + +Use `--appimagetool /absolute/path/to/appimagetool` if it is not in `PATH`. +The environment variable `APPIMAGETOOL` is also supported. + +For a non-default Python executable used by the build-manifest step: + +```bash +PYTHON_BIN=/path/to/python3 scripts/build_appimage.sh ... +``` + +## AppDir Layout + +The wrapper creates a temporary sibling directory named `iPhotron.AppDir`: + +```text +iPhotron.AppDir/ +├── AppRun +├── iphoto.desktop +├── iphoto.png +└── usr/ + └── bin/ + └── ... complete Nuitka standalone bundle ... +``` + +`AppRun` and the desktop file come from `packaging/appimage/`. + +The script refuses to overwrite an existing `iPhotron.AppDir`. Remove or archive +an old staging directory before rebuilding. + +## Output And Build Manifest + +The requested AppImage is written exactly to `--output`. A companion build +manifest is generated next to it: + +```text +.build-manifest.json +``` + +The manifest records the AppImage artifact, build driver, architecture, native +Maps runtime, map assets, and i18n resources. + +## Verification + +Make the artifact executable if required and launch it on a clean Linux target: + +```bash +chmod +x "dist/iPhotron-${VERSION}-x86_64.AppImage" +"dist/iPhotron-${VERSION}-x86_64.AppImage" +``` + +At minimum verify: + +1. first window and Gallery become usable without startup crashes; +2. Gallery -> Detail opens stills and videos; +3. Qt XCB startup works on the supported X11/XWayland environment; +4. offline Maps loads when advertised; +5. People & Pets recognition does not initialize merely because the app starts; +6. opening the recognition feature activates the enabled recognition runtime; +7. an offline-recognition release can initialize with model auto-downloads + disabled; +8. translations and QSB-backed rendering assets load from the packaged bundle. + +## Troubleshooting + +| Symptom | Cause | Fix | +| --- | --- | --- | +| `appimagetool not found` | tool is not installed/in `PATH` | install it or pass `--appimagetool PATH` | +| entrypoint validation fails | wrong `--standalone-dir` or failed Nuitka build | point to `dist/entrypoint.dist` from a successful build | +| QSB validation fails | shader data was omitted by Nuitka | rebuild with the current `scripts/build_nuitka_fast.sh` | +| Maps validation fails | standalone Maps payload incomplete | restore `src/maps/tiles` staging and rebuild Nuitka | +| XCB validation fails | Qt platform plugins were stripped | rebuild with the current PySide6/Nuitka plugin options | +| script refuses `iPhotron.AppDir` | previous staging directory exists | remove/archive it before rerunning | +| People/Pets unavailable offline | optional AI runtime/models were not staged before Nuitka | rebuild with the required optional dependencies and verified models | diff --git a/docs/misc/BUILD_DEB.md b/docs/misc/BUILD_DEB.md index 28b094744..2b5b35dfe 100644 --- a/docs/misc/BUILD_DEB.md +++ b/docs/misc/BUILD_DEB.md @@ -1,197 +1,206 @@ # Building a Debian Package (.deb) for Linux -This document describes how to build a Debian package (`.deb`) for iPhotron on Linux. +This document describes how to wrap the current Nuitka standalone Linux build +of iPhotron in a Debian package. Do not hard-code the application version in +this guide: the package version must come from `pyproject.toml`. ## Overview -A `.deb` package allows easy installation and removal on Debian-based -distributions (Ubuntu, Mint, etc.) using standard package management tools -such as `apt` and `dpkg`. +The `.deb` stage is a packaging wrapper around the standalone bundle produced by +`scripts/build_nuitka_fast.sh`. It must preserve the bundle contents rather than +reconstructing Python dependencies independently. -For iPhotron, Linux packaging should preserve the standalone application bundle -and the offline maps extension together. The Location view's native Linux maps -runtime depends on the helper binary plus the shared libraries under -`maps/tiles/extension/bin/`. +The current Linux standalone build is generated from `src/entrypoint.py` and is +written below `dist/entrypoint.dist/`. The executable is normally one of: -Builds that ship People/Pets recognition must also preserve the selected AI -runtimes from the standalone bundle. People needs `insightface` and -`onnxruntime`; Pets needs `onnxruntime`, `torch`, `torchvision`, `usearch`, and -`certifi`. Offline builds also retain the shared `extension/models` cache. -These are added at the Nuitka stage described in -[`BUILD_EXE.md`](BUILD_EXE.md); the `.deb` stage must not strip them from -`/opt/iPhotron/`. +```text +dist/entrypoint.dist/entrypoint.bin +dist/entrypoint.dist/entrypoint +``` + +The build script also writes `dist/build-manifest.json`. + +Linux packages that claim offline Maps or People/Pets support must preserve the +corresponding staged runtime data from the standalone bundle, including +`maps/tiles/extension/` and `extension/models/`. ## Prerequisites -- A Debian-based Linux distribution (Ubuntu, Debian, Mint, …) -- `dpkg-deb` (usually pre-installed on Debian/Ubuntu systems) -- A working standalone build of iPhotron (see [`BUILD_EXE.md`](BUILD_EXE.md)) +- Debian/Ubuntu/Mint or another environment with `dpkg-deb` +- the project development/build dependencies required by + `scripts/build_nuitka_fast.sh` +- a successful standalone build -## Directory Structure +Build the standalone application first: -Create a staging directory that keeps the standalone bundle intact and exposes -an `iPhotron` launcher on `PATH`: +```bash +bash scripts/build_nuitka_fast.sh +``` +## Resolve Version And Executable + +Read the release version from `pyproject.toml` instead of duplicating it: + +```bash +VERSION="$(python - <<'PY' +import tomllib +from pathlib import Path +print(tomllib.loads(Path('pyproject.toml').read_text())['project']['version']) +PY +)" + +APP_DIST="dist/entrypoint.dist" +if [[ -x "$APP_DIST/entrypoint.bin" ]]; then + APP_EXECUTABLE="entrypoint.bin" +elif [[ -x "$APP_DIST/entrypoint" ]]; then + APP_EXECUTABLE="entrypoint" +else + echo "Nuitka entrypoint not found; run scripts/build_nuitka_fast.sh first" >&2 + exit 2 +fi + +PKG_ROOT="iPhotron_${VERSION}_amd64" +APP_ROOT="$PKG_ROOT/opt/iPhotron" +BIN_ROOT="$PKG_ROOT/usr/local/bin" ``` + +For another architecture, change the Debian architecture value and package +suffix together; do not publish an `amd64` package built for another CPU. + +## Staging Layout + +```text iPhotron_VERSION_amd64/ ├── DEBIAN/ │ └── control ├── opt/ -│ └── iPhotron/ ← standalone app bundle copied here -│ ├── iPhotron ← main executable (name may vary by build) -│ └── maps/ -│ └── tiles/ -│ └── extension/ -│ ├── World_basemap_2.obf -│ ├── misc/ -│ ├── poi/ -│ ├── rendering_styles/ -│ ├── routing/ -│ ├── search/ -│ │ └── geonames.sqlite3 -│ └── bin/ +│ └── iPhotron/ +│ └── ... complete contents of dist/entrypoint.dist/ ... └── usr/ └── local/ └── bin/ - └── iPhotron ← launcher script + └── iPhotron ``` -## The `control` File - -The `DEBIAN/control` file contains the package metadata. Create it with content -like the following: +Stage the standalone bundle without stripping optional feature assets: +```bash +mkdir -p "$PKG_ROOT/DEBIAN" "$APP_ROOT" "$BIN_ROOT" +cp -a "$APP_DIST/." "$APP_ROOT/" +printf '#!/bin/sh\nexec /opt/iPhotron/%s "$@"\n' "$APP_EXECUTABLE" > "$BIN_ROOT/iPhotron" +chmod 755 "$BIN_ROOT/iPhotron" "$PKG_ROOT/DEBIAN" ``` -Package: iPhotron -Version: 5.00 + +## Debian Control Metadata + +Create `"$PKG_ROOT/DEBIAN/control"` with the resolved version: + +```bash +cat > "$PKG_ROOT/DEBIAN/control" < **Fields explained** -> -> | Field | Value | Notes | -> |-------|-------|-------| -> | `Package` | `iPhotron` | Binary package name | -> | `Version` | `5.00` | Upstream version; update to match your release | -> | `Architecture` | `amd64` | Target CPU architecture (x86-64) | -> | `Maintainer` | `OliverZhao` | Name (and optionally email) of the package maintainer | -> | `Description` | short + long | First line is the synopsis; indented lines form the long description | - -## Build Steps +Keep Debian's package identifier stable (`iphotron`) even if the product name is +rendered as `iPhotron` in UI/documentation. -1. **Prepare the staging tree** — copy your compiled iPhotron binary into the correct location inside the staging directory: +## Validate Optional Runtime Payloads - ```bash - PKG_ROOT=iPhotron_5.00_amd64 - APP_ROOT="$PKG_ROOT/opt/iPhotron" - BIN_ROOT="$PKG_ROOT/usr/local/bin" - APP_DIST=dist/YOUR_STANDALONE_DIR - APP_EXECUTABLE=YOUR_EXECUTABLE_NAME +The Nuitka build already stages application data. The `.deb` step must not +remove it. - mkdir -p "$PKG_ROOT/DEBIAN" "$APP_ROOT" "$BIN_ROOT" - cp -a "$APP_DIST/." "$APP_ROOT/" - printf '#!/bin/sh\nexec /opt/iPhotron/%s "$@"\n' "$APP_EXECUTABLE" > "$BIN_ROOT/iPhotron" - chmod 755 "$BIN_ROOT/iPhotron" - ``` +For Maps-enabled builds: - Replace `YOUR_STANDALONE_DIR` and `YOUR_EXECUTABLE_NAME` with the actual - Nuitka output names produced by your Linux build. - - Before continuing, verify that the maps extension is still present inside - the staged app bundle: - - ```bash - find "$APP_ROOT/maps/tiles/extension" -maxdepth 2 -type f | sort - ``` - - At minimum, Linux native maps should retain: - - - `maps/tiles/extension/World_basemap_2.obf` - - `maps/tiles/extension/bin/osmand_render_helper` - - `maps/tiles/extension/bin/osmand_native_widget.so` - - `maps/tiles/extension/bin/libOsmAndCore_shared.so` - - `maps/tiles/extension/bin/libOsmAndCoreTools_shared.so` - - `maps/tiles/extension/search/geonames.sqlite3` - - If this release includes offline-ready People/Pets scanning, also verify the - recognition runtime payload from the Nuitka bundle: +```bash +find "$APP_ROOT/maps/tiles/extension" -maxdepth 2 -type f | sort +``` - ```bash - find "$APP_ROOT" -path '*insightface*' -o -path '*onnxruntime*' - find "$APP_ROOT/extension/models" -name 'det_500m.onnx' -o -name 'w600k_mbf.onnx' - find "$APP_ROOT" -path '*torch*' -o -path '*torchvision*' -o -path '*usearch*' - find "$APP_ROOT/extension/models/pets" -name 'yolox_nano_coco.onnx' -o -name 'dinov2_vits14.pt' - ``` +Typical Linux map payloads include: -2. **Create the `control` file** — save the content from the section above to `"$PKG_ROOT/DEBIAN/control"` and ensure it is not world-writable: +- `maps/tiles/extension/World_basemap_2.obf` +- `maps/tiles/extension/bin/osmand_render_helper` +- `maps/tiles/extension/bin/osmand_native_widget.so` +- `maps/tiles/extension/bin/libOsmAndCore_shared.so` +- `maps/tiles/extension/bin/libOsmAndCoreTools_shared.so` +- `maps/tiles/extension/search/geonames.sqlite3` - ```bash - chmod 644 "$PKG_ROOT/DEBIAN/control" - ``` +For offline-ready People/Pets builds: -3. **Build the package**: +```bash +find "$APP_ROOT" -path '*insightface*' -o -path '*onnxruntime*' +find "$APP_ROOT/extension/models" -type f | sort +find "$APP_ROOT" -path '*torch*' -o -path '*torchvision*' -o -path '*usearch*' +``` - ```bash - dpkg-deb --build "$PKG_ROOT" - ``` +`src/extension/models/` is a build-staging input, not guaranteed fresh-clone +content. A release that advertises offline recognition must populate and verify +that staging area before running Nuitka. - This produces `"${PKG_ROOT}.deb"` in the current directory. +## Build And Inspect The Package -4. **Verify the package**: +```bash +dpkg-deb --build "$PKG_ROOT" +dpkg-deb --info "${PKG_ROOT}.deb" +dpkg-deb --contents "${PKG_ROOT}.deb" +``` - ```bash - dpkg-deb --info "${PKG_ROOT}.deb" - dpkg-deb --contents "${PKG_ROOT}.deb" - dpkg-deb --contents "${PKG_ROOT}.deb" | grep 'maps/tiles/extension' - # If this build ships offline-ready People/Pets scanning: - dpkg-deb --contents "${PKG_ROOT}.deb" | grep 'extension/models' - ``` +Useful payload checks: - After installing on a clean test machine, open a small image library and - verify that the People & Pets page can create each enabled cluster type. For - a fuller smoke test, name an identity, set a cover, create a group, restart - iPhotron, and confirm those user decisions persist. +```bash +dpkg-deb --contents "${PKG_ROOT}.deb" | grep 'maps/tiles/extension' || true +dpkg-deb --contents "${PKG_ROOT}.deb" | grep 'extension/models' || true +``` ## Installation -Install the generated package with: - ```bash sudo apt install ./"${PKG_ROOT}.deb" ``` -If you open a new shell before installing, replace `${PKG_ROOT}` with the real -package directory name you used in Step 1. - -Or using `dpkg` directly (and then resolving any missing dependencies): +Or: ```bash sudo dpkg -i "${PKG_ROOT}.deb" -sudo apt-get install -f # fix missing dependencies if any +sudo apt-get install -f ``` ## Removal ```bash -sudo apt remove iPhotron +sudo apt remove iphotron ``` +## Release Smoke Test + +Validate the package on a clean target machine, not only from the source tree: + +1. launch `iPhotron` from `PATH`; +2. open an existing and a small fresh library; +3. verify Gallery -> Detail still/video opening; +4. verify Maps if the package advertises offline Maps support; +5. open People & Pets and confirm enabled recognition runtimes activate on + feature use rather than during application startup; +6. if offline recognition is advertised, confirm People/Pets can initialize + with downloads disabled; +7. create durable recognition state (for example a name/cover), restart, and + verify that it persists. + ## Troubleshooting | Symptom | Likely cause | Fix | -|---------|--------------|-----| -| `dpkg-deb: error: control directory has bad permissions` | `DEBIAN/` directory not mode 755 | `chmod 755 "$PKG_ROOT/DEBIAN"` | -| `dpkg: dependency problems` after install | Missing runtime libraries | Add `Depends:` line to `control` listing required packages | -| Binary not found after install | Wrong install path in staging tree, or launcher points to the wrong standalone executable | Ensure the launcher under `usr/local/bin/` points to the executable copied into `/opt/iPhotron/` | -| Location view falls back unexpectedly after install | `maps/tiles/extension/` was not included in the package | Re-stage the standalone bundle and verify the `.deb` contents include `World_basemap_2.obf`, resources, and Linux map binaries | -| Native maps fail with GLX/XCB startup errors | The runtime was installed correctly, but the desktop session lacks XWayland/XCB GL integration | Install/enable XWayland and rerun, or set `IPHOTO_PREFER_OSMAND_NATIVE_WIDGET=0` to force the helper-backed Python OBF path | -| People scan is unavailable in the installed app | The standalone build was produced without the optional face runtime | Rebuild the standalone app with `insightface`, `onnxruntime`, and `src/extension/models` included before staging the `.deb` | -| People scan starts but never creates clusters | The model cache or an InsightFace submodel/dependency is missing from `/opt/iPhotron/` | Verify `extension/models`, exclude unused `albumentations`/`pydantic` packages at the Nuitka stage, and keep InsightFace limited to detection and recognition | -| Pets scan is unavailable in the installed app | The standalone build omitted `pets-ai` packages or `extension/models/pets` | Rebuild the standalone app with `onnxruntime`, `torch`, `torchvision`, `usearch`, `certifi`, and both Pets model files before staging the `.deb` | +| --- | --- | --- | +| `dpkg-deb: error: control directory has bad permissions` | `DEBIAN/` mode is wrong | `chmod 755 "$PKG_ROOT/DEBIAN"` | +| launcher exists but app does not start | launcher points at the wrong Nuitka executable | Re-resolve `entrypoint.bin` / `entrypoint` from `dist/entrypoint.dist/` | +| Maps falls back after install | map extension stripped from standalone bundle | Verify `maps/tiles/extension/` inside `/opt/iPhotron/` | +| native Maps fails with XCB/GLX errors | desktop lacks the expected X11/XWayland GL integration | Install/enable XWayland/XCB GL support or use the helper-backed map path | +| People/Pets unavailable offline | optional runtime or model staging was omitted before Nuitka | Rebuild the standalone bundle with the required runtime and verified models | +| package version disagrees with the application | version was duplicated manually | Regenerate `VERSION` from `pyproject.toml` | diff --git a/docs/misc/BUILD_FLATPAK.md b/docs/misc/BUILD_FLATPAK.md new file mode 100644 index 000000000..c000aa64f --- /dev/null +++ b/docs/misc/BUILD_FLATPAK.md @@ -0,0 +1,51 @@ +# Flatpak Build Contract + +This note separates the Flatpak **release artifact** from the current +**development build contract**. + +## Current status + +The v6.6.8 release contains a downloadable Flatpak bundle. The current +`edit-base` tree, however, does not contain a maintained Flatpak manifest, +Flatpak build driver, or Flatpak CI job. `packaging/` currently defines the +AppImage path, while the reproducible Debian path wraps the Linux Nuitka +standalone bundle. + +Therefore: + +- README files may keep a link to an existing published Flatpak bundle. +- Developer documentation must not imply that this branch can reproduce that + bundle from source. +- AppImage or Debian instructions are not substitutes for a Flatpak manifest. + +## Requirements for first-class Flatpak support + +Flatpak becomes a current reproducible target only when the repository contains: + +1. a checked-in Flatpak manifest and application id; +2. an explicit Flatpak runtime/SDK version; +3. a documented command that builds from a clean checkout; +4. Qt/QML/QSB resource packaging; +5. an explicit Maps-extension packaging/fallback policy; +6. an explicit People/Pets optional-runtime and model-artifact policy; +7. smoke tests for launch, Gallery, Detail, and enabled optional features; +8. CI or release validation for the manifest and generated bundle. + +A recommended future layout is: + +```text +packaging/flatpak/ +scripts/build_flatpak.sh +``` + +The exact names are not normative; having a reviewable in-repository build +contract is. + +## README parity rule + +English, Simplified Chinese, and German README files must all communicate the +same distinction: a published Flatpak bundle can be downloaded, while the +current branch does not yet provide a reproducible in-repo Flatpak build path. + +For Linux packaging paths that are currently reproducible from this repository, +see `BUILD_APPIMAGE.md` and `BUILD_DEB.md`. diff --git a/docs/misc/PETS_RECOGNITION_RUNTIME.md b/docs/misc/PETS_RECOGNITION_RUNTIME.md index 8fc24f750..8e18965ea 100644 --- a/docs/misc/PETS_RECOGNITION_RUNTIME.md +++ b/docs/misc/PETS_RECOGNITION_RUNTIME.md @@ -11,8 +11,8 @@ Pets is an independent bounded context under `src/iPhoto/pets/`. | Component | Responsibility | | --- | --- | -| `PetClusterPipeline` | YOLOX detection, DINOv2 embeddings, species-aware clustering, and stable identity canonicalization. | -| `PetScanWorker` | Low-pressure background batches, status transitions, model-version upgrades, and cancellation. | +| `PetClusterPipeline` | YOLOX detection, DINOv2 embeddings, species-aware bounded single-link clustering, and stable identity canonicalization. | +| `PetScanWorker` | Low-pressure feature-activated background batches, status transitions, model-version upgrades, and cancellation. | | `PetIndexCoordinator` | Serializes snapshot mutations, updates asset bookkeeping, and publishes committed revisions. | | `PetRepository` | Rebuildable detections and clustered pet records in `pet_index.db`. | | `PetStateRepository` | Durable profiles, names, covers, hidden flags, rejected keys, and redirects in `pet_state.db`. | @@ -39,8 +39,7 @@ pip install -e ".[pets-ai]" ``` The extra provides `onnxruntime`, `torch`, `torchvision`, `usearch`, and -`certifi`. Bundled models are read-only fallbacks; downloads are written to the -platform user cache: +`certifi`. Build-time model staging uses: ```text src/extension/models/pets/ @@ -48,14 +47,17 @@ src/extension/models/pets/ └── embedding/dinov2_vits14/dinov2_vits14.pt ``` -`IPHOTO_PET_MODEL_DIR` overrides that root. Missing models may be populated on -first use unless `IPHOTO_PET_MODEL_AUTO_DOWNLOAD=0`. The detector URL defaults -to the upstream YOLOX release and can be overridden with -`IPHOTO_PET_DETECTOR_MODEL_URL`. Production does not execute Torch Hub. DINOv2 -must be supplied as the hash- and size-verified TorchScript artifact declared -in `iPhoto/pets/model_manifest.json`; Torch Hub is restricted to the release -conversion tool. `IPHOTO_PET_SCAN_DISABLED=1` disables the worker without -disabling the rest of the application. +That tree is a packaging/staging location, not a guarantee that a fresh source +checkout contains the model binaries. Runtime model resolution may also use the +platform user cache. `IPHOTO_PET_MODEL_DIR` overrides the model root. + +The detector may be populated on first use unless +`IPHOTO_PET_MODEL_AUTO_DOWNLOAD=0`; its URL defaults to the upstream YOLOX +release and can be overridden with `IPHOTO_PET_DETECTOR_MODEL_URL`. Production +does not execute Torch Hub. DINOv2 must be supplied as the hash- and size-verified +TorchScript artifact declared in `iPhoto/pets/model_manifest.json`; Torch Hub is +only model provenance/conversion tooling. `IPHOTO_PET_SCAN_DISABLED=1` disables +Pets scanning without disabling the rest of the application. Packaged/offline builds that promise Pets support must include the Python AI runtime and the two model files under `extension/models/pets`. A build that @@ -71,12 +73,18 @@ and minimum-size thresholds. After species filtering and pet-to-pet deduplication, accepted pet boxes are compared with automatic and manual People face boxes for the same asset. People -is authoritative when either intersection-over-union is at least `0.50`, or the -intersection covers at least `90%` of the smaller box. Conflicting pet boxes are -discarded before crop embedding and thumbnail generation. Confidence does not -override People priority. The suppressed result is absent from dashboard cards, -pet gallery queries, detail annotations, and overlays rather than being hidden -only in the dashboard. +normally remains authoritative for strong conflicts, but face overlap is not an +unconditional pet-suppression rule. A plausible larger pet-body detection is +preserved when it substantially contains a much smaller face box instead of +behaving like a duplicate person detection. The current guard treats a pet box +as materially larger at a pet-to-face area ratio of `1.5` or greater and avoids +using this exception for image-filling/mural-like detections whose box covers at +least `0.60` of the image area. + +The result of the People/Pets conflict filter is part of the committed Pets +snapshot. Suppressed detections are absent from dashboard cards, pet gallery +queries, detail annotations, and overlays rather than being hidden only in the +dashboard. Each accepted crop receives: @@ -86,19 +94,20 @@ Each accepted crop receives: - a cropped PNG under `.iPhoto/pets/thumbnails/`; - model, image-size, confidence, and species metadata. -Clustering is species-separated and uses the current complete-link pipeline -(`species-complete-link-v1`) with a default cosine distance threshold of -`0.42`. Incremental identity matching uses a progressively expanded ANN -shortlist followed by exact complete-link verification against every persisted -member of each shortlisted candidate. Cats and dogs must never enter the same -identity cluster. Stable state uses `pet_key` mappings and profile distance to -preserve canonical `pet_id` values across rebuilds. +Clustering is species-separated and uses the current +`species-bounded-single-link-v3` pipeline. Candidate edges are formed from +embedding similarity, but ordinary single-link chaining is constrained by +cluster-diameter and cannot-link/anti-chain-merge checks so a sequence of +locally similar detections cannot bridge identities that are globally too far +apart. Cats and dogs must never enter the same identity cluster. Stable state +uses `pet_key` mappings and profile distance to preserve canonical `pet_id` +values across rebuilds. The detector and clustering pipeline versions are stored separately. A detector version change resets eligible `done` assets to `pending`; a clustering-only version change reclusters stored embeddings without resetting asset scan state. -The People-priority filter is part of the detector pipeline version so existing -libraries are re-evaluated after upgrading. +The People/Pets conflict filter is part of the detector pipeline version so +existing libraries are re-evaluated after relevant upgrades. When an exact `pet_key` carries a canonical identity into a new embedding contract, compatible detections from the same batch may join that staged anchor. @@ -125,16 +134,14 @@ starts before the previous drain finishes. | `done` | Detection completed, including valid images with no pets. | | `skipped` | Video, non-primary Live Photo component, or another ineligible asset. | -Interactive scans start Face and Pet workers alongside metadata scanning and -enqueue rows only after their asset batches commit. When a saved library needs -a startup metadata scan, startup first warms the gallery, runs that scan, then -starts both AI workers with closed input so they drain persisted -`pending`/`retry` rows. This avoids model initialization and competing AI work -on the first-frame path. If the metadata scan scope is already complete, startup -still starts the Pet backfill worker whenever persisted `pending` or `retry` rows -need draining. With no metadata scan and no queued AI work, startup does not -launch scan workers; an explicit rescan is only needed to reset or rediscover -otherwise completed/failed assets. +Application startup and metadata-scan completion do not by themselves start the +People or Pets recognition workers. Startup may discover/persist pending work, +but model initialization and recognition scanning remain outside the first-frame +path. The recognition runtime is activated on first use of the recognition +feature after its initial viewport is ready; the coordinator then starts only +the required worker(s) and drains eligible persisted `pending`/`retry` work. +Interactive rescans may enqueue newly committed assets for a recognition runtime +that is already active, but they do not restore unconditional startup scanning. The Pet worker uses small batches and queue top-up from the asset repository. Missing dependencies/models are runtime-availability failures: pending rows are diff --git a/docs/readme/README_de.md b/docs/readme/README_de.md index 05ae50bfd..f907782c6 100644 --- a/docs/readme/README_de.md +++ b/docs/readme/README_de.md @@ -1,319 +1,82 @@ # 📸 iPhotron -> Ein von macOS *Fotos* inspirierter, ordnerbasierter Foto-Manager für Windows, macOS und Linux mit Live Photo, Karten und intelligenten Alben. -![Platform](https://img.shields.io/badge/platform-Windows%20%7C%20macOS%20%7C%20Linux-lightgrey) -![Language](https://img.shields.io/badge/language-Python%203.12%2B-blue) -![Framework](https://img.shields.io/badge/framework-PySide6%20(Qt6)-orange) -![License](https://img.shields.io/badge/license-MIT-green) -[![GitHub Repo](https://img.shields.io/badge/github-iPhotron-181717?logo=github)](https://github.com/OliverZhaohaibin/iPhotron-LocalPhotoAlbumManager) +> Ordnernativer Foto-Manager für Windows, macOS und Linux, inspiriert von macOS Fotos. -**Sprachen / Languages:** -[![English](https://img.shields.io/badge/English-Click-blue?style=flat)](../../README.md) | [![中文简体](https://img.shields.io/badge/中文简体-点击-red?style=flat)](README_zh-CN.md) | [![Deutsch](https://img.shields.io/badge/Deutsch-Klick-yellow?style=flat)](README_de.md) +**Sprachen:** [English](../../README.md) · [简体中文](README_zh-CN.md) · [Deutsch](README_de.md) ---- +## Release und Entwicklung -## ☕ Unterstützung +**Die Downloads sind die veröffentlichten Dateien von v6.6.8.** Die +Funktionsübersicht beschreibt zusätzlich den aktuellen `edit-base`-Zweig und +kann Unreleased-Funktionen enthalten. Entwicklungsfunktionen sind deshalb nicht +automatisch Bestandteil von v6.6.8. Siehe [`CHANGELOG.md`](../CHANGELOG.md). -[![Buy Me a Coffee](https://img.shields.io/badge/Buy%20Me%20a%20Coffee-Entwicklung%20unterstützen-yellow?style=for-the-badge&logo=buy-me-a-coffee&logoColor=white)](https://buymeacoffee.com/oliverzhao) -[![PayPal](https://img.shields.io/badge/PayPal-Entwicklung%20unterstützen-blue?style=for-the-badge&logo=paypal&logoColor=white)](https://www.paypal.com/donate/?hosted_button_id=AJKMJMQA8YHPN) +## v6.6.8 Downloads -## 📥 Download & Installation +- Windows: [`v6.68-x86-setup.exe`](https://github.com/OliverZhaohaibin/iPhotron-LocalPhotoAlbumManager/releases/download/v6.6.8/v6.68-x86-setup.exe) +- Debian: [`iphotron_6.6.8_amd64.deb`](https://github.com/OliverZhaohaibin/iPhotron-LocalPhotoAlbumManager/releases/download/v6.6.8/iphotron_6.6.8_amd64.deb) +- AppImage: [`iPhotron-6.6.8-x86_64.AppImage`](https://github.com/OliverZhaohaibin/iPhotron-LocalPhotoAlbumManager/releases/download/v6.6.8/iPhotron-6.6.8-x86_64.AppImage) +- Flatpak: [`com.github.OliverZhaohaibin.iPhotron-6.6.8-x86_64.flatpak`](https://github.com/OliverZhaohaibin/iPhotron-LocalPhotoAlbumManager/releases/download/v6.6.8/com.github.OliverZhaohaibin.iPhotron-6.6.8-x86_64.flatpak) -[![Für Windows herunterladen](https://img.shields.io/badge/⬇️%20Download-Windows%20(.exe)-blue?style=for-the-badge&logo=windows)](https://github.com/OliverZhaohaibin/iPhotron-LocalPhotoAlbumManager/releases/download/v6.6.8/v6.68-x86-setup.exe) -[![Für Linux herunterladen (.deb)](https://img.shields.io/badge/⬇️%20Download-Linux%20(.deb)-orange?style=for-the-badge&logo=linux&logoColor=white)](https://github.com/OliverZhaohaibin/iPhotron-LocalPhotoAlbumManager/releases/download/v6.6.8/iphotron_6.6.8_amd64.deb) -[![Für Linux herunterladen (.AppImage)](https://img.shields.io/badge/⬇️%20Download-Linux%20(.AppImage)-brightgreen?style=for-the-badge&logo=linux&logoColor=white)](https://github.com/OliverZhaohaibin/iPhotron-LocalPhotoAlbumManager/releases/download/v6.6.8/iPhotron-6.6.8-x86_64.AppImage) -[![Für Linux herunterladen (.flatpak)](https://img.shields.io/badge/⬇️%20Download-Linux%20(.flatpak)-purple?style=for-the-badge&logo=flatpak&logoColor=white)](https://github.com/OliverZhaohaibin/iPhotron-LocalPhotoAlbumManager/releases/download/v6.6.8/com.github.OliverZhaohaibin.iPhotron-6.6.8-x86_64.flatpak) +`v6.68-x86-setup.exe` ist der tatsächliche Windows-Dateiname im Release. +Für v6.6.8 gibt es ein Flatpak-Release-Artefakt. Der aktuelle Branch enthält +jedoch kein gepflegtes Flatpak-Build-Rezept im Repository. Siehe +[`BUILD_FLATPAK.md`](../misc/BUILD_FLATPAK.md). Debian und AppImage besitzen +aktuelle In-Repo-Anleitungen. -**💡 Schnellinstallation:** Klicken Sie auf die Schaltflächen oben, um das neueste Installationsprogramm direkt herunterzuladen. - -- **Windows:** Führen Sie das `.exe`-Installationsprogramm direkt aus. -- **Linux:** Installationsbefehl: - -```bash -sudo apt install ./iphotron_6.6.8_amd64.deb -``` - -- **Linux (AppImage):** Datei ausführbar machen und direkt starten: - -```bash -chmod +x iPhotron-6.6.8-x86_64.AppImage -./iPhotron-6.6.8-x86_64.AppImage -``` - -- **Linux (Flatpak):** Bundle mit Flatpak installieren: - -```bash -flatpak install --user ./com.github.OliverZhaohaibin.iPhotron-6.6.8-x86_64.flatpak -``` - -**Für Entwickler:** +## Aus dem Quellcode ```bash +python -m venv .venv +source .venv/bin/activate pip install -e . -``` - ---- - -## 🚀 Schnellstart - -```bash iphoto-gui ``` -Oder direkt ein bestimmtes Album öffnen: - -```bash -iphoto-gui /fotos/LondonReise -``` - ---- - -## 🌟 Star-Verlauf - - - - - - Star History Chart - - - -## 🚀 Product Hunt -

- - iPhotron - A macOS Photos–style photo manager for Windows | Product Hunt - -

- -

- Bitte unterstütze uns mit einem Upvote • - Folgen • - Im Forum diskutieren -

- ---- - -## 🌟 Überblick - -**iPhotron** ist ein **ordnerbasierter Foto-Manager**, inspiriert von macOS *Fotos*. -Es behält Ihre Ordner als Albumstruktur bei, kombiniert ordnerlokale Manifeste -mit einer bibliotheksweiten `.iPhoto/global_index.db` und trennt wiederaufbaubare -Cache-Fakten von dauerhaften Benutzerentscheidungen, während Bearbeitungen von -den Original-Mediendateien getrennt bleiben. - -Wichtige Highlights: -- 🗂 Ordnerbasiertes Design — jeder Ordner *ist* ein Album, kein Import erforderlich. -- ⚙️ Ordnerlokale Manifeste speichern Album-Metadaten wie Cover, Favoriten und Reihenfolge. -- ⚡ **SQLite-gestützte globale Datenbank** für schnelle session-gestützte Abfragen auf massiven Bibliotheken. -- 🖼 Bedarfsgesteuertes Gallery-Scrolling mit dünn besetzten asynchronen Fenstern, Micro-Thumbnail-Fallback und speicherbewusstem Full-Thumbnail-Prefetching. -- 🧠 Intelligentes inkrementelles Scannen mit persistentem SQLite-Index. -- 🎥 Vollständige **Live Photo**-Paarungs- und Wiedergabeunterstützung. -- 🗺 Optionale Kartenansicht, die GPS-Metadaten über alle Fotos und Videos visualisiert und ohne Maps Extension sauber zurückfällt. -- 👥 Optionales People-Scanning mit Face Clusters, Namen, Covern, versteckten Personen und Mehrpersonen-Gruppen. -- 🐾 Optionales Pets-Scanning mit Katzen-/Hundeerkennung, Tier-Identitätsclustern, Namen, Covern, versteckten und angepinnten Tieren. -![Main interface](../picture/mainview.png) -![Preview interface](../picture/preview.png) ---- - -## 🗺 Maps Extension - -Die Offline-OBF-Kartenlaufzeit von iPhotron wird als selbstenthaltene -**maps extension** unter `src/maps/tiles/extension/` bereitgestellt. Genau -dieses Verzeichnislayout wird von der lokalen Entwicklung, von Paket-Builds -und von plattformspezifischen Installationsartefakten verwendet. -Die App bleibt auch ohne diese Extension nutzbar; kartenspezifische Ansichten -und Panels verwenden die Runtime-Verfügbarkeitsgrenze, um sauberes -Fallback-Verhalten anzuzeigen. - -Die Extension enthält derzeit: -- Offline-Kartendaten in `World_basemap_2.obf` -- OsmAnd-Ressourcen unter `misc/`, `poi/`, `rendering_styles/`, `routing/` - und weiteren Laufzeit-Ressourcenverzeichnissen -- Offline-Suchdaten unter `search/geonames.sqlite3` -- plattformspezifische native Binärdateien unter `bin/` - - Windows: `osmand_render_helper.exe`, `osmand_native_widget.dll`, - `OsmAndCore_shared.dll`, `OsmAndCoreTools_shared.dll` und die benötigten Qt-DLLs - - Linux: `osmand_render_helper`, `osmand_native_widget.so`, - `libOsmAndCore_shared.so` und `libOsmAndCoreTools_shared.so` - - macOS: `osmand_render_helper`, `osmand_native_widget.dylib` und kopierte - nicht-systemeigene Mach-O-Abhängigkeiten - -Hinweise zur Kartenlaufzeit: -- iPhotron kann den helper-basierten OBF-Renderer und das native OsmAnd-Widget verwenden, sobald die jeweilige Plattformlaufzeit vorhanden ist. -- Wenn neben diesem Repository ein `PySide6-OsmAnd-SDK/`-Checkout existiert, können Linux und macOS dessen Widget-Builds aus `tools/osmand_render_helper_native/dist-*` bevorzugen. -- Das native Linux-Widget erwartet derzeit den XCB- + Desktop-OpenGL-Pfad von Qt. Bei Auswahl dieses Backends setzt iPhotron automatisch `QT_QPA_PLATFORM=xcb`, `QT_OPENGL=desktop` und `QT_XCB_GL_INTEGRATION=xcb_glx`. -- Unter macOS verwendet die Legacy-OpenGL-Karte `QOpenGLWindow + createWindowContainer()`, um Kompositionsprobleme von `QOpenGLWidget` in transparenten Hauptfenstern zu vermeiden. Medienvorschauen nutzen standardmäßig den Metal-fähigen QRhi-Pfad, außer `IPHOTO_RHI_BACKEND=opengl` ist gesetzt. - -| Ohne Maps Extension | Mit Maps Extension | -| --- | --- | -| ![Ohne Maps Extension](../picture/without_extension.png) | ![Mit Maps Extension](../picture/maps_extension.png) | - -Die Extension wird im Upstream-Teilprojekt -[PySide6-OsmAnd-SDK](https://github.com/OliverZhaohaibin/PySide6-OsmAnd-SDK) -gebaut. Dieses Repository enthält die vendorten OsmAnd-Quellen, Buildskripte -für Windows, Linux und macOS, die native Qt-Widget-Bridge und die Preview-App, -aus denen die hier verwendete Laufzeit erzeugt wird. - -Den vollständigen Workflow "maps extension aus dem Side-Project in dieses -Repository übernehmen" findest du in [Development](../development.md). Hinweise -zu Nuitka, Runtime-Synchronisierung und Windows-Installer stehen in -[Executable Build](../misc/BUILD_EXE.md). - -## ✨ Funktionen - -### 🗺 Standortansicht -Zeigt Ihre Foto-Fußabdrücke auf einer interaktiven Karte und gruppiert nahe gelegene Fotos nach GPS-Metadaten. -![Location interface](../picture/map1.png) -![Location interface](../picture/map2.png) - -### 🎞 Live Photo-Unterstützung -Paart nahtlos HEIC/JPG- und MOV-Dateien mithilfe von Apples `ContentIdentifier`. -Ein "LIVE"-Badge erscheint auf Standbildern — klicken Sie, um das Bewegungsvideo inline abzuspielen. -![Live interface](../picture/live.png) - -### 🧩 Intelligente Alben -Die Seitenleiste bietet eine automatisch generierte **Grundbibliothek**, die Fotos in Gruppen einteilt: -`Alle Fotos`, `Videos`, `Live Photos`, `Favoriten` und `Kürzlich gelöscht`. - -### 👥🐾 People, Pets, Face Clusters & Gruppen -Die optionale People-Pipeline erkennt Gesichter, erstellt Face Clusters und -zeigt sie als People-Karten an. Personen können benannt, doppelte Cluster -zusammengeführt, versteckt oder wieder eingeblendet werden; ausgewählte Cover -bleiben über erneute Scans hinweg erhalten. - -Die unabhängige Pets-Pipeline erkennt Katzen und Hunde mit YOLOX, erzeugt -DINOv2-Embeddings und zeigt Tier-Identitätskarten im selben Dashboard. Tiere -können benannt, zusammengeführt, versteckt, angepinnt und mit einem Cover -versehen werden. Personen und Tiere können in gemeinsamen Identity-Gruppen -erscheinen, während Laufzeitindex und dauerhafter Zustand getrennt bleiben. - -Mehrere Personen lassen sich zu Gruppen zusammenfassen, um gemeinsame Fotos -anzuzeigen. Gruppenkarten unterstützen ein ausgewähltes Cover, Drag-and-drop- -Sortierung und können aufgelöst werden, solange sie nicht angepinnt sind. Das -Face Scanning nutzt die optionalen `ai-demo`-Abhängigkeiten; die zentrale -Fotoverwaltung bleibt auch ohne AI-Laufzeit nutzbar. People-Zustand bleibt -hinter der Library-Session-Grenze dauerhaft erhalten, damit Namen, Cover, -versteckte Personen, Gruppen und manuelle Gesichtsanmerkungen erneute Scans -überstehen. Das Pets-Scanning verwendet das optionale `pets-ai`-Extra; fehlende -Abhängigkeiten oder Modelle blockieren die übrige Anwendung nicht. -![People and groups interface](<../picture/People & Group.png>) - -### ⚡ Gallery für große Bibliotheken -Die Gallery verwendet dünn besetzte asynchrone SQL-Fenster, statt beim Öffnen -einer Sammlung die gesamte Bibliothek zu materialisieren. Die Scroll-Absicht -steuert getrennte Warteschlangen für sichtbare, nahe Guard- und spekulative -Thumbnails. Schnelle Scroll-Bursts halten die Eingabe reaktionsfähig und nutzen -vorgewärmte Micro-Thumbnails, während Full-Thumbnails außerhalb des GUI-Threads -dekodiert werden. Speicher- und Worker-Budgets passen sich Plattform und RAM an; -generation-basierte Stornierung verhindert Updates aus veralteten Viewports. - -### 🖼 Immersive Detailansicht -Ein eleganter Foto-/Videobetrachter mit Filmstreifen-Navigator, schwebendem -Wiedergabebalken und plattformgewähltem GPU-Pfad: QRhi/Metal unter macOS, -OpenGL-backed QRhi unter Windows und Linux. - -### 🎨 Nicht-destruktive Fotobearbeitung -Eine umfassende Bearbeitungssuite mit **Anpassen**- und **Zuschneiden**-Modi: - -#### Anpassen-Modus -- **Lichtanpassungen:** Brillanz, Belichtung, Lichter, Schatten, Helligkeit, Kontrast, Schwarzpunkt -- **Farbanpassungen:** Sättigung, Lebendigkeit, Farbstich (Weißabgleichkorrektur) -- **Schwarzweiß:** Intensität, Neutraltöne, Ton, Körnung mit künstlerischen Film-Voreinstellungen -- **Farbkurven:** RGB- und kanalbasierter (R/G/B) Kurven-Editor mit ziehbaren Kontrollpunkten für präzise Tonanpassungen -- **Selektive Farbe:** Zielt auf sechs Farbbereiche (Rot/Gelb/Grün/Cyan/Blau/Magenta) mit unabhängigen Farbton-/Sättigungs-/Helligkeitskontrollen -- **Tonwerte:** 5-Punkt-Eingangs-Ausgangs-Tonzuordnung mit Histogramm-Hintergrund und kanalbasierter Steuerung -- **Master-Schieberegler:** Jeder Abschnitt verfügt über einen intelligenten Master-Schieberegler, der Werte auf mehrere Feinabstimmungssteuerungen verteilt -- **Live-Miniaturansichten:** Echtzeit-Vorschaustreifen, die den Effektbereich für jede Anpassung zeigen - -![edit interface](../picture/editview.png) -![edit interface](../picture/professionaltools.png) - -#### Zuschneiden-Modus -- **Perspektivkorrektur:** Vertikale und horizontale Trapezverzerrungsanpassungen -- **Ausrichten-Werkzeug:** ±45° Drehung mit Sub-Grad-Präzision -- **Spiegeln (Horizontal):** Horizontale Spiegelungsunterstützung -- **Interaktives Zuschneiderechteck:** Ziehbare Griffe, Kantenfang und Seitenverhältnisbeschränkungen -- **Schwarzrand-Prävention:** Automatische Validierung stellt sicher, dass nach Perspektivtransformationen keine schwarzen Kanten erscheinen - -![crop interface](../picture/cropview.png) -Alle Bearbeitungen werden über die Edit-Session-Oberfläche in -`.ipo`-Sidecar-Dateien gespeichert und bewahren die Originalfotos unberührt. - -### ℹ️ Schwebendes Info-Panel -Schalten Sie ein schwebendes Metadaten-Panel um, das EXIF, -Kamera-/Objektivinformationen, Belichtung, Blende, Brennweite, Abmessungen, -Dateigröße und Aufnahmezeit anzeigt. Für Assets mit Erkennungsdaten zeigen das -Panel und das Bild-Overlay Gesichts- und Tierannotationen; Erkennungen können -über den jeweils zuständigen Dienst entfernt oder einer anderen/neuen Identität -zugewiesen werden. - -Auch Standortwerkzeuge sind integriert: Assets mit GPS-Daten können eine -eingebettete Karte anzeigen, und Assets ohne Standort können über den -"Assign a Location"-Suchfluss einen Ort auswählen und bestätigen. Die Auswahl -wird immer in der lokalen Bibliotheksdatenbank gespeichert. Wenn ExifTool -verfügbar ist, schreibt iPhotron die GPS-Daten zusätzlich best-effort in die -Originaldatei zurück und warnt, wenn dieser Schreibvorgang fehlschlägt. Wenn -die maps extension fehlt, bietet das Panel den Downloadpfad an, statt still zu -scheitern. - -| Info-Panel mit Karte | Schwebendes Info-Panel in der Detailansicht | -| --- | --- | -| ![Info-Panel mit Karte](../picture/info.png) | ![Schwebendes Info-Panel in der Detailansicht](../picture/info2.png) | - -### 💬 Umfangreiche Interaktionen -- Ziehen und Ablegen von Dateien direkt aus dem Explorer/Finder in Alben. -- Mehrfachauswahl und Kontextmenüs für Kopieren, In Ordner anzeigen, Verschieben, Löschen, Wiederherstellen. -- Sanfte Miniaturansichts-Übergänge und macOS-ähnliche Album-Navigation. - ---- +Der installierte GUI-Einstieg ist `iPhoto.entrypoint:main`. -## 📚 Dokumentation +## Aktuelle Entwicklung -Detaillierte technische Dokumentation (auf Englisch): +- ordnernative Alben ohne Import; +- SQLite-basierte große Bibliotheken und asynchrone Gallery-Fenster; +- Live Photo; +- optionale Offline-Maps; +- optionale People- und Pets-Erkennung; +- GPU-first Detail und gemeinsames Detail/Edit-Rendering; +- nicht-destruktive `.ipo`-Bearbeitung. -[![Architecture](https://img.shields.io/badge/📐_Architecture-blue?style=for-the-badge)](../architecture.md) -[![Development](https://img.shields.io/badge/🧰_Development-green?style=for-the-badge)](../development.md) -[![Executable Build](https://img.shields.io/badge/🧱_Executable_Build-purple?style=for-the-badge)](../misc/BUILD_EXE.md) -[![Security](https://img.shields.io/badge/🔒_Security-red?style=for-the-badge)](../security.md) -[![Changelog](https://img.shields.io/badge/📋_Changelog-orange?style=for-the-badge)](../CHANGELOG.md) +## People & Pets -| Dokument | Beschreibung | -|----------|-------------| -| [Architecture](../architecture.md) | Aktuelle vNext library-scoped modular monolith Architektur, Modulgrenzen, Legacy-Quarantäne, Datenfluss und wichtige Designentscheidungen | -| [Development](../development.md) | Entwicklungsumgebung, Abhängigkeiten, Debugging und der maps-extension-Workflow für Windows, Linux und macOS | -| [Pets Runtime](../misc/PETS_RECOGNITION_RUNTIME.md) | Aktueller Vertrag für Tiermodelle, Scan-Planung, Persistenz, Mutationssicherheit und People-&-Pets-Komposition | -| [Executable Build](../misc/BUILD_EXE.md) | Nuitka-Paketierung, AOT, QRhi-Shader-Assets, maps-extension-Synchronisierung und Plattformlaufzeit-Hinweise | -| [Security](../security.md) | Berechtigungen, Verschlüsselung, Datenspeicherorte, Bedrohungsmodell | -| [Changelog](../CHANGELOG.md) | Alle Versionshinweise und Änderungen | +People und Pets besitzen getrennte Runtime-Indizes und dauerhaften State. Die +aktuelle Pets-Pipeline ist `species-bounded-single-link-v3` mit Arttrennung, +Cannot-Link-Regeln und begrenztem Cluster-Durchmesser. ---- +Die Konfliktregel bedeutet nicht „People gewinnt immer“. Starke +Gesichtsüberlappung unterdrückt normalerweise einen Pet-Kandidaten; ein deutlich +größerer plausibler Tierkörper-Rahmen kann durch die Größen-/Bildabdeckungsregel +erhalten bleiben. -## 🧩 Externe Werkzeuge +Erkennungs-Inferenz startet erst, nachdem die People-Oberfläche geöffnet wurde +und ihr erster Viewport bereit ist; normaler App-Start startet sie nicht. -| Werkzeug | Zweck | -|----------|-------| -| **ExifTool** | Liest EXIF-, GPS-, QuickTime- und Live-Photo-Metadaten und schreibt GPS-Daten bei expliziten Assign-Location-Aktionen. | -| **FFmpeg / FFprobe** | Erzeugt Video-Miniaturansichten und analysiert Videoinformationen. | -| **InsightFace / ONNXRuntime + `buffalo_s`-Modelle** | Optionales People Face Scanning: Gesichtserkennung (`det_500m.onnx`) und Face Embeddings (`w600k_mbf.onnx`) aus `src/extension/models/buffalo_s/`. | -| **YOLOX / ONNXRuntime + DINOv2 / Torch** | Optionales Pets-Scanning: Katzen-/Hundeerkennung und Tier-Identitäts-Embeddings aus `src/extension/models/pets/`. | +DINOv2 wird als vorbereitete TorchScript-Datei über das Manifest geprüft. Mit +`torchscript_url: null` muss das Artefakt derzeit paketiert oder explizit gestaged +werden. `src/extension/models` ist nur eine Packaging-/Staging-Konvention. -> FFmpeg/FFprobe müssen im System-`PATH` verfügbar sein. Installieren Sie -> ExifTool zusätzlich, wenn zugewiesene GPS-Koordinaten in Originaldateien -> zurückgeschrieben werden sollen. -> Die AI-Erkennungslaufzeiten sind optional; für Source-Builds können sie mit -> `pip install -e ".[ai-demo,pets-ai]"` installiert werden. Offline-Pakete sollten -> `extension/models` mitliefern. +Siehe [`PETS_RECOGNITION_RUNTIME.md`](../misc/PETS_RECOGNITION_RUNTIME.md). -Python-Abhängigkeiten wie `Pillow` und `reverse-geocoder` werden über -`pyproject.toml` automatisch installiert. +## Architektur ---- +`DesktopCoordinatorRuntime` ist der Desktop Composition Root; +`main_coordinator.py` ist nur ein Compatibility Import. -## 📄 Lizenz +- [`AGENT.md`](../../AGENT.md) +- [`architecture.md`](../architecture.md) +- [`development.md`](../development.md) +- [`security.md`](../security.md) +- [`requirements/README.md`](../requirements/README.md) -**MIT-Lizenz © 2025** -Erstellt von **Haibin Zhao (OliverZhaohaibin)** +## Lizenz -> *iPhotron — Ein ordnerbasiertes, menschenlesbares und vollständig wiederaufbaubares Fotosystem.* -> *Keine erzwungenen Importe. Kein proprietärer Lock-in. Nur Ihre Fotos, elegant organisiert.* +MIT — siehe [`LICENSE`](../../LICENSE). diff --git a/docs/readme/README_zh-CN.md b/docs/readme/README_zh-CN.md index f03ce3c1c..7986b1f11 100644 --- a/docs/readme/README_zh-CN.md +++ b/docs/readme/README_zh-CN.md @@ -1,291 +1,99 @@ # 📸 iPhotron -> 受 macOS *照片* 启发的文件夹原生照片管理器,支持 Windows、macOS 与 Linux,提供实况照片、地图和智能相册。 -![Platform](https://img.shields.io/badge/platform-Windows%20%7C%20macOS%20%7C%20Linux-lightgrey) -![Language](https://img.shields.io/badge/language-Python%203.12%2B-blue) -![Framework](https://img.shields.io/badge/framework-PySide6%20(Qt6)-orange) -![License](https://img.shields.io/badge/license-MIT-green) -[![GitHub Repo](https://img.shields.io/badge/github-iPhotron-181717?logo=github)](https://github.com/OliverZhaohaibin/iPhotron-LocalPhotoAlbumManager) +> 一款受 macOS「照片」启发、以文件夹为原生相册结构的 Windows / macOS / Linux 本地照片管理器。 -**语言 / Languages:** -[![English](https://img.shields.io/badge/English-Click-blue?style=flat)](../../README.md) | [![中文简体](https://img.shields.io/badge/中文简体-点击-red?style=flat)](README_zh-CN.md) | [![Deutsch](https://img.shields.io/badge/Deutsch-Klick-yellow?style=flat)](README_de.md) +**语言:** [English](../../README.md) · [简体中文](README_zh-CN.md) · [Deutsch](README_de.md) ---- +## 发布版与开发分支说明 -## ☕ 支持 +**下面的下载链接对应已经发布的 v6.6.8 二进制文件。** 本 README 的功能概览同时描述当前 +`edit-base` 开发分支,因此可能包含尚未发布(Unreleased)的功能。开发分支中已经实现的功能, +并不代表一定存在于 v6.6.8 二进制文件中。当前分支变更见 +[`docs/CHANGELOG.md`](../CHANGELOG.md)。 -[![请我喝杯咖啡](https://img.shields.io/badge/Buy%20Me%20a%20Coffee-支持开发-yellow?style=for-the-badge&logo=buy-me-a-coffee&logoColor=white)](https://buymeacoffee.com/oliverzhao) -[![PayPal](https://img.shields.io/badge/PayPal-支持开发-blue?style=for-the-badge&logo=paypal&logoColor=white)](https://www.paypal.com/donate/?hosted_button_id=AJKMJMQA8YHPN) +## v6.6.8 下载 -## 📥 下载与安装 - -[![下载 Windows 版本](https://img.shields.io/badge/⬇️%20下载-Windows%20(.exe)-blue?style=for-the-badge&logo=windows)](https://github.com/OliverZhaohaibin/iPhotron-LocalPhotoAlbumManager/releases/download/v6.6.8/v6.68-x86-setup.exe) -[![下载 Linux 版本(.deb)](https://img.shields.io/badge/⬇️%20下载-Linux%20(.deb)-orange?style=for-the-badge&logo=linux&logoColor=white)](https://github.com/OliverZhaohaibin/iPhotron-LocalPhotoAlbumManager/releases/download/v6.6.8/iphotron_6.6.8_amd64.deb) -[![下载 Linux 版本(.AppImage)](https://img.shields.io/badge/⬇️%20下载-Linux%20(.AppImage)-brightgreen?style=for-the-badge&logo=linux&logoColor=white)](https://github.com/OliverZhaohaibin/iPhotron-LocalPhotoAlbumManager/releases/download/v6.6.8/iPhotron-6.6.8-x86_64.AppImage) -[![下载 Linux 版本(.flatpak)](https://img.shields.io/badge/⬇️%20下载-Linux%20(.flatpak)-purple?style=for-the-badge&logo=flatpak&logoColor=white)](https://github.com/OliverZhaohaibin/iPhotron-LocalPhotoAlbumManager/releases/download/v6.6.8/com.github.OliverZhaohaibin.iPhotron-6.6.8-x86_64.flatpak) - -**💡 快速安装:** 点击上方按钮直接下载最新安装程序。 - -- **Windows:** 直接运行 `.exe` 安装程序。 -- **Linux:** 安装命令为: - -```bash -sudo apt install ./iphotron_6.6.8_amd64.deb -``` - -- **Linux(AppImage):** 赋予执行权限后直接运行: - -```bash -chmod +x iPhotron-6.6.8-x86_64.AppImage -./iPhotron-6.6.8-x86_64.AppImage -``` +| 平台 | 发布文件 | +| --- | --- | +| Windows | [`v6.68-x86-setup.exe`](https://github.com/OliverZhaohaibin/iPhotron-LocalPhotoAlbumManager/releases/download/v6.6.8/v6.68-x86-setup.exe) | +| Debian | [`iphotron_6.6.8_amd64.deb`](https://github.com/OliverZhaohaibin/iPhotron-LocalPhotoAlbumManager/releases/download/v6.6.8/iphotron_6.6.8_amd64.deb) | +| AppImage | [`iPhotron-6.6.8-x86_64.AppImage`](https://github.com/OliverZhaohaibin/iPhotron-LocalPhotoAlbumManager/releases/download/v6.6.8/iPhotron-6.6.8-x86_64.AppImage) | +| Flatpak | [`com.github.OliverZhaohaibin.iPhotron-6.6.8-x86_64.flatpak`](https://github.com/OliverZhaohaibin/iPhotron-LocalPhotoAlbumManager/releases/download/v6.6.8/com.github.OliverZhaohaibin.iPhotron-6.6.8-x86_64.flatpak) | -- **Linux(Flatpak):** 使用 Flatpak 安装 bundle: +`v6.68-x86-setup.exe` 就是 v6.6.8 Release 中实际发布的 Windows 文件名,并非 README 笔误。 -```bash -flatpak install --user ./com.github.OliverZhaohaibin.iPhotron-6.6.8-x86_64.flatpak -``` +v6.6.8 已经存在 Flatpak 发布文件,但当前开发分支**没有**维护中的仓库内 Flatpak manifest / 构建流程。 +“已有可下载的 Release artifact”和“当前源码可以复现构建”是两件不同的事。详见 +[`BUILD_FLATPAK.md`](../misc/BUILD_FLATPAK.md)。当前仓库中可复现的 Linux 打包说明包括 +[Debian](../misc/BUILD_DEB.md) 和 [AppImage](../misc/BUILD_APPIMAGE.md)。 -**开发者安装:** +## 从源码运行 ```bash +python -m venv .venv +source .venv/bin/activate pip install -e . -``` - ---- - -## 🚀 快速开始 - -```bash iphoto-gui ``` -或直接打开特定相册: - -```bash -iphoto-gui /photos/LondonTrip -``` - ---- - -## 🌟 Star 历史 - - - - - - Star History Chart - - - -## 🚀 Product Hunt -

- - iPhotron - A macOS Photos–style photo manager for Windows | Product Hunt - -

+安装后的 GUI 入口为 `iPhoto.entrypoint:main`。 -

- 请为我们点赞支持 • - 关注我们 • - 在论坛参与讨论 -

+## 当前开发分支主要能力 ---- +- 文件夹即相册,无需额外导入。 +- 基于 SQLite 的大型图库查询与稀疏异步 Gallery 窗口。 +- 按视口需求调度缩略图,并通过 generation 防止快速滚动时旧结果回写。 +- Live Photo 配对与播放。 +- 可选的离线 OsmAnd 地图运行时。 +- 可选 People 人脸识别:姓名、封面、群组、隐藏状态、手工人脸等。 +- 可选 Pets 猫/狗识别:身份聚类以及持久化姓名、封面、隐藏等用户状态。 +- GPU-first Detail 渲染,以及 Detail/Edit 共享渲染会话。 +- 基于 `.ipo` sidecar 的非破坏性编辑。 +- Assign Location:先保存本地状态,再尽力写回原文件 GPS 元数据。 -## 🌟 概述 +![主界面](../picture/mainview.png) -**iPhotron** 是一款受 macOS *照片* 启发的**文件夹原生照片管理器**。 -它保留文件夹作为相册结构,将文件夹本地清单与 library 级 -`.iPhoto/global_index.db` 结合使用,并区分可重建缓存事实与不可丢失的用户选择, -同时让编辑操作保持非破坏性,不覆盖原始媒体内容。 +## People & Pets -核心亮点: -- 🗂 文件夹原生设计 —— 每个文件夹*就是*一个相册,无需导入。 -- ⚙️ 文件夹本地清单记录封面、精选、排序等相册元数据。 -- ⚡ **SQLite 驱动的全局数据库**,为海量图库提供基于 session 的高速查询。 -- 🖼 按需驱动的 Gallery 滚动,支持稀疏异步窗口、微型缩略图兜底和内存感知的全尺寸缩略图预取。 -- 🧠 智能增量扫描,使用持久化 SQLite 索引。 -- 🎥 完整的**实况照片**配对和播放支持。 -- 🗺 可选地图视图,可视化所有照片和视频的 GPS 元数据;缺少 maps extension 时会优雅降级。 -- 👥 可选的 People 人脸扫描,支持 face cluster、人物命名、封面、隐藏人物与多人 group。 -- 🐾 可选的 Pets 扫描,支持猫狗检测、宠物个体 cluster、命名、封面、隐藏、置顶与 People & Pets group。 -![Main interface](../picture/mainview.png) -![Preview interface](../picture/preview.png) ---- +People 与 Pets 是两个独立的可选 bounded context。它们各自拥有独立的运行时索引和持久化状态, +UI 可以在卡片、群组、Gallery 查询和 Detail 标注层进行组合。 -## 🗺 Maps Extension +当前 Pets 身份聚类版本为 `species-bounded-single-link-v3`:猫狗分开聚类,遵守 cannot-link +约束,并限制 cluster diameter,避免单链式聚类无限扩张。 -iPhotron 的离线 OBF 地图运行时以自包含的 **maps extension** 形式提供, -根目录位于 `src/maps/tiles/extension/`。本项目源码运行、Nuitka 打包产物、 -以及各平台安装产物都以这套目录结构作为运行时约定。 -即使缺少这套 extension,应用的图库浏览、编辑、People & Pets 和 Live Photo 功能仍可使用; -地图相关视图和面板会通过运行时可用性边界显示优雅降级状态。 +People/Pets 冲突规则**不是**无条件的“People 永远优先”。当宠物框与人脸框强烈重叠时,通常会 +抑制宠物候选;但如果检测框明显更大、仍像完整宠物身体、只是包含了较小的人脸框,则满足当前 +尺寸/图像覆盖率例外时可以保留该宠物检测。 -当前 extension 主要包含: -- `World_basemap_2.obf` 离线地图数据 -- `misc/`、`poi/`、`rendering_styles/`、`routing/` 以及相关运行时资源目录 -- `search/geonames.sqlite3` 离线地点搜索数据 -- `bin/` 下的平台原生二进制 - - Windows:`osmand_render_helper.exe`、`osmand_native_widget.dll`、 - `OsmAndCore_shared.dll`、`OsmAndCoreTools_shared.dll` 以及所需 Qt DLL - - Linux:`osmand_render_helper`、`osmand_native_widget.so`、 - `libOsmAndCore_shared.so`、`libOsmAndCoreTools_shared.so` - - macOS:`osmand_render_helper`、`osmand_native_widget.dylib` 以及复制到 - `bin/` 下的非系统 Mach-O 依赖 +识别推理也是按功能首次使用激活:只有用户真正打开 People 页面,并且第一个 viewport 已准备好后, +才会启动 People/Pets 扫描;普通应用启动本身不会自动触发识别推理。 -平台地图运行时说明: -- 当平台运行时可用时,iPhotron 既可使用 helper-backed OBF renderer,也可使用原生 OsmAnd widget。 -- 如果仓库旁边存在 `PySide6-OsmAnd-SDK/` 工作区,Linux 和 macOS 开发环境可优先使用其中 `tools/osmand_render_helper_native/dist-*` 的 widget 构建产物。 -- 原生 Linux widget 目前依赖 Qt 的 XCB + desktop OpenGL 路径;选择该后端时,iPhotron 会自动设置 `QT_QPA_PLATFORM=xcb`、`QT_OPENGL=desktop` 与 `QT_XCB_GL_INTEGRATION=xcb_glx`。 -- macOS 上 legacy OpenGL 地图使用 `QOpenGLWindow + createWindowContainer()`,以避开透明主窗口下 `QOpenGLWidget` 的合成问题;媒体预览默认走支持 Metal 的 QRhi 路径,可通过 `IPHOTO_RHI_BACKEND=opengl` 强制 OpenGL。 +DINOv2 在生产运行时加载预先生成的 TorchScript artifact,并通过 model manifest 校验。 +当前 manifest 的 `torchscript_url: null`,因此 DINOv2 目前必须由打包流程包含或显式 staging; +`src/extension/models` 只是打包/staging 约定,并不保证 fresh clone 中存在全部模型。 -| 未启用 maps extension | 启用 maps extension | -| --- | --- | -| ![未启用 maps extension](../picture/without_extension.png) | ![启用 maps extension](../picture/maps_extension.png) | - -这套 extension 的上游构建工作区是独立子项目 -[PySide6-OsmAnd-SDK](https://github.com/OliverZhaohaibin/PySide6-OsmAnd-SDK)。 -该仓库维护 vendored OsmAnd 源码、Windows、Linux 与 macOS 构建脚本、原生 Qt Widget bridge -以及预览程序,本仓库消费的运行时产物正是由它生成。 - -完整的“如何基于 side project 构建本仓库 maps extension”流程请参阅 -[Development](../development.md); -Nuitka 打包、runtime 同步与安装器说明请参阅 -[Executable Build](../misc/BUILD_EXE.md)。 - -## ✨ 功能特性 - -### 🗺 位置视图 -在交互式地图上显示您的照片足迹,根据 GPS 元数据聚类附近的照片。 -![Location interface](../picture/map1.png) -![Location interface](../picture/map2.png) - -### 🎞 实况照片支持 -使用 Apple 的 `ContentIdentifier` 无缝配对 HEIC/JPG 和 MOV 文件。 -静态照片上会显示"实况"徽章 —— 点击即可内联播放动态视频。 -![Live interface](../picture/live.png) - -### 🧩 智能相册 -侧边栏提供自动生成的**基础图库**,将照片分组为: -`所有照片`、`视频`、`实况照片`、`收藏`和`最近删除`。 +详细运行时规则见 [`PETS_RECOGNITION_RUNTIME.md`](../misc/PETS_RECOGNITION_RUNTIME.md)。 -### 👥🐾 People、Pets、Face Cluster 与 Group -可选的 People 管线会检测照片中的人脸,生成 face cluster,并在 People 页面以人物卡片展示。 -您可以为人物命名、合并重复 cluster、隐藏或重新显示隐藏人物,并让选定封面在重新扫描后继续保留。 +## 架构与打包状态 -独立的 Pets 管线使用 YOLOX 检测猫狗、使用 DINOv2 生成 embedding,并在同一页面展示宠物个体卡片。 -宠物可以命名、合并、隐藏、置顶、设置封面并打开对应图库。人物与宠物可以加入同一个 identity group, -但当同一图片区域同时被识别为 People 人脸和 pet 时,People 结果优先;冲突的 pet 检测会从可重建的 -Pets 快照中移除,因此不会继续生成重复卡片、宠物图库结果、详情标注或 overlay。该规则只组合比较 -检测框,People 与 Pets 的模型、记录、索引和持久状态仍保持独立。 +`DesktopCoordinatorRuntime` 是当前桌面端 coordinator graph 的 production composition root; +`main_coordinator.py` 仅保留 compatibility import。 -将多个人物组成 group 后,可以查看这些人物共同出现的照片。Group 卡片支持设置封面、拖拽排序, -未置顶的 group 可以解散。人脸扫描依赖可选的 `ai-demo` 依赖;即使不安装 AI 运行时, -核心照片管理功能仍可使用。People 状态通过 library session 边界持久化,人物命名、封面、 -隐藏状态、group 和手动人脸标注都会在重新扫描后保留。宠物扫描依赖可选的 `pets-ai`;缺少依赖或模型时, -候选资源会保持 pending,且不会阻塞应用的其他功能。 -![People and groups interface](<../picture/People & Group.png>) +当前维护文档: -### ⚡ 大型图库 Gallery -Gallery 使用稀疏异步 SQL 窗口,不会在打开集合时物化整个图库。 -滚动意图会分别驱动可见区、近端保护区与远端推测缩略图队列; -高速连续滚动时优先保证输入响应,并使用已预热的微型缩略图。 -完整缩略图在 GUI 线程之外解码,内存与 worker 预算会根据平台和物理内存调整, -过期 viewport 任务则通过 generation 校验取消或丢弃。 +- [`AGENT.md`](../../AGENT.md) +- [`docs/architecture.md`](../architecture.md) +- [`docs/development.md`](../development.md) +- [`docs/security.md`](../security.md) +- [`docs/requirements/README.md`](../requirements/README.md) -### 🖼 沉浸式详细视图 -优雅的照片/视频查看器,带有胶片条导航器和浮动播放栏;GPU 渲染路径会按平台选择: -macOS 默认 QRhi/Metal,Windows 与 Linux 使用 OpenGL-backed QRhi。 - -### 🎨 非破坏性照片编辑 -全面的编辑套件,包含**调整**和**裁剪**模式: - -#### 调整模式 -- **光线调整:** 亮度、曝光、高光、阴影、明度、对比度、黑场 -- **颜色调整:** 饱和度、自然饱和度、色偏(白平衡校正) -- **黑白:** 强度、中性、色调、颗粒,带有艺术胶片预设 -- **色彩曲线:** RGB 和单通道(R/G/B)曲线编辑器,可拖动控制点进行精确色调调整 -- **可选颜色:** 针对六个色相范围(红/黄/绿/青/蓝/品红)进行独立的色相/饱和度/亮度控制 -- **色阶:** 5 点输入-输出色调映射,带有直方图背景和单通道控制 -- **主滑块:** 每个部分都有一个智能主滑块,可在多个微调控件之间分配值 -- **实时缩略图:** 实时预览条显示每个调整的效果范围 -![edit interface](../picture/editview.png) -![edit interface](../picture/professionaltools.png) -#### 裁剪模式 -- **透视校正:** 垂直和水平梯形失真调整 -- **拉直工具:** ±45° 旋转,亚度精度 -- **翻转(水平):** 水平翻转支持 -- **交互式裁剪框:** 拖动手柄、边缘吸附和宽高比约束 -- **黑边防止:** 自动验证确保透视变换后不出现黑边 - -![crop interface](../picture/cropview.png) -所有编辑都通过编辑 session surface 存储在 `.ipo` 附属文件中,保持原始照片不被触动。 - -### ℹ️ 浮动信息面板 -切换浮动元数据面板,查看 EXIF、相机/镜头信息、曝光、光圈、焦距、尺寸、文件大小和拍摄时间等。 -如果当前资源已有识别数据,面板与图片 overlay 会同时显示人脸和宠物标注,并通过各自服务支持删除、 -移动到其他身份或创建新身份。 - -位置工具也集成在面板中:带 GPS 的资源可以显示内嵌地图;没有位置的资源可以通过 -“Assign a Location” 搜索、选择并确认地点。地点会始终保存到本机图库数据库;如果 -ExifTool 可用,iPhotron 还会尽力把 GPS 写回原始媒体文件,写回失败时会给出提示。 -如果 maps extension 缺失,面板会提供下载入口,而不是静默失效。 - -| 带地图的信息面板 | 详情页浮动信息面板 | +| 打包目标 | 当前分支状态 | | --- | --- | -| ![带地图的信息面板](../picture/info.png) | ![详情页浮动信息面板](../picture/info2.png) | - -### 💬 丰富的交互 -- 从资源管理器/访达直接拖放文件到相册。 -- 多选和上下文菜单,用于复制、在文件夹中显示、移动、删除、恢复。 -- 流畅的缩略图过渡和 macOS 风格的相册导航。 - ---- - -## 📚 文档 - -详细技术文档请参阅(英文版): - -[![Architecture](https://img.shields.io/badge/📐_Architecture-blue?style=for-the-badge)](../architecture.md) -[![Development](https://img.shields.io/badge/🧰_Development-green?style=for-the-badge)](../development.md) -[![Executable Build](https://img.shields.io/badge/🧱_Executable_Build-purple?style=for-the-badge)](../misc/BUILD_EXE.md) -[![Security](https://img.shields.io/badge/🔒_Security-red?style=for-the-badge)](../security.md) -[![Changelog](https://img.shields.io/badge/📋_Changelog-orange?style=for-the-badge)](../CHANGELOG.md) - -| 文档 | 说明 | -|------|------| -| [Architecture](../architecture.md) | 当前 vNext library-scoped modular monolith 架构、模块边界、legacy 隔离策略、数据流和关键设计决策 | -| [Development](../development.md) | 开发环境、依赖、调试,以及面向 Windows / Linux / macOS 的 maps extension 构建流程 | -| [Pets Runtime](../misc/PETS_RECOGNITION_RUNTIME.md) | 当前宠物模型、扫描调度、持久化、变更安全和 People & Pets 组合契约 | -| [Executable Build](../misc/BUILD_EXE.md) | Nuitka 打包、AOT、QRhi shader 资源、maps extension 同步与平台运行时说明 | -| [Security](../security.md) | 权限、加密、数据存储位置、威胁模型 | -| [Changelog](../CHANGELOG.md) | 所有版本更新记录 | - ---- - -## 🧩 外部工具 - -| 工具 | 用途 | -|------|------| -| **ExifTool** | 读取 EXIF、GPS、QuickTime 和实况照片元数据;在用户执行 Assign Location 时写入 GPS 元数据。 | -| **FFmpeg / FFprobe** | 生成视频缩略图并解析视频信息。 | -| **InsightFace / ONNXRuntime + `buffalo_s` 模型** | 可选的 People 人脸扫描:使用 `src/extension/models/buffalo_s/` 中的 `det_500m.onnx` 进行人脸检测,使用 `w600k_mbf.onnx` 生成人脸 embedding。 | -| **YOLOX / ONNXRuntime + DINOv2 / Torch** | 可选的 Pets 扫描:使用 `src/extension/models/pets/` 中的模型进行猫狗检测和宠物个体 embedding。 | - -> 请确保 FFmpeg/FFprobe 已加入系统 `PATH`;如果需要把指定地点的 GPS 写回原始媒体文件, -> 也请安装 ExifTool。 -> AI 识别运行时是可选功能;源码安装可使用 `pip install -e ".[ai-demo,pets-ai]"`, -> 离线打包版本需要保留 `extension/models`。 - -Python 依赖(例如 `Pillow`、`reverse-geocoder`)会通过 `pyproject.toml` 自动安装。 - ---- - -## 📄 许可证 +| Windows / Nuitka | 有维护中的构建说明 | +| Debian | 有仓库内可复现说明 | +| AppImage | 有仓库内可复现说明 | +| Flatpak | v6.6.8 有下载文件;当前仓库内构建流程缺失 | -**MIT 许可证 © 2025** -由 **Haibin Zhao (OliverZhaohaibin)** 创建 +## License -> *iPhotron —— 一个文件夹原生、人类可读且完全可重建的照片系统。* -> *无需强制导入。没有专有锁定。只有您的照片,优雅地组织。* +MIT,见 [`LICENSE`](../../LICENSE)。 diff --git a/docs/requirements/README.md b/docs/requirements/README.md new file mode 100644 index 000000000..d0e23444d --- /dev/null +++ b/docs/requirements/README.md @@ -0,0 +1,40 @@ +# Requirements Document Lifecycle + +`docs/requirements/` is for active requirements, acceptance runbooks, and work +that still carries unresolved implementation or validation obligations. + +Completed work belongs under `docs/finished/`. Historical design inputs that +must stay near an active requirement must be explicitly labelled Historical or +Superseded and link to the current canonical contract. + +## Status rules + +- **Active**: implementation or acceptance work remains. +- **Closed with residual debt**: the completed phase is documented, but the + directory remains here because named follow-up obligations are still active. +- **Historical / Superseded**: retained for design history only; it is not a + production contract. +- **Finished**: no active requirement remains; archive under `docs/finished/`. + +A document under `docs/requirements/` must not use unqualified wording such as +"current implementation" when it is only describing the design state at the +time it was written. + +## Current classifications + +- `DETAIL_OPEN_BENCHMARK_RUNBOOK.md`: Active acceptance/runbook. +- `GALLERY_SCROLL_PERFORMANCE_REARCHITECTURE.md`: Active validation context; + implementation is substantially complete but platform validation remains. +- `startup-chain-optimization/`: Closed v1 with residual debt. Keep here while + the explicitly documented startup diagnostics/deferred work remains active. +- `pets-cluster/`: Historical design snapshots. Current runtime contract is + `../misc/PETS_RECOGNITION_RUNTIME.md`. +- `gallery-detail-gpu-first/`: Completed implementation material. New changes + should use the current architecture/runbook rather than treating phase design + notes as normative. +- `scan_c_hotspot_optimization.md`: Re-baselining requirement; historical call + graphs/profiling numbers are not current evidence. + +When a remaining obligation is closed, move its completed planning/design +material to `docs/finished/requirements/` rather than leaving a silently stale +"current" document in this directory. diff --git a/docs/requirements/pets-cluster/README.md b/docs/requirements/pets-cluster/README.md new file mode 100644 index 000000000..049c8424d --- /dev/null +++ b/docs/requirements/pets-cluster/README.md @@ -0,0 +1,21 @@ +# Pets Clustering Requirements — Historical Design Archive + +The files in this directory are retained as design and implementation history. +They are **not** the current production contract. + +Any use of words such as "current", "now", or "production" inside the archived +files must be read relative to the design snapshot in which that file was +written. In particular, the older complete-link clustering description is +superseded. + +The canonical runtime contract is: + +- `../../misc/PETS_RECOGNITION_RUNTIME.md` +- `../../architecture.md` + +Current production behavior uses `species-bounded-single-link-v3`, including +species separation, cannot-link constraints, bounded cluster diameter to prevent +chaining, and the current People/Pets overlap arbitration rules. + +Do not implement new Pets behavior from the archived requirements without first +reconciling it against the runtime code and the canonical documents above. diff --git a/docs/requirements/scan_c_hotspot_optimization.md b/docs/requirements/scan_c_hotspot_optimization.md index d51b52a1e..8d36c48aa 100644 --- a/docs/requirements/scan_c_hotspot_optimization.md +++ b/docs/requirements/scan_c_hotspot_optimization.md @@ -1,754 +1,32 @@ -# 扫描阶段 C 语言热点代码优化分析 - -> **目的**:识别扫描(Scan)阶段中可以用 C 语言重写以大幅提升性能的热点代码,并给出优先级排序、预期收益与实施建议。 - ---- - -## 1. 扫描流程概览 - -iPhotos 的扫描流程分为以下几个阶段: - -``` -文件发现 - └─ FileDiscoveryThread.run() [scan_album.py] - └─ ParallelScanner._discover_files() [parallel_scanner.py] - ↓ -批量元数据提取 - └─ get_metadata_batch() [utils/exiftool.py] - └─ normalize_metadata() [infrastructure/services/metadata_provider.py] - ↓ -文件内容哈希(去重) - └─ compute_file_id() [utils/hashutils.py] - ↓ -缩略图生成(微缩图) - └─ generate_micro_thumbnail() [utils/image_loader.py] - ↓ -Live Photo 配对 - └─ pair_live() / _match_by_time() [core/pairing.py] - ↓ -数据库持久化 - └─ append_rows() / _insert_rows() [cache/index_store/repository.py] -``` - -扫描 5,000 个文件的各阶段耗时估算: - -| 阶段 | 估算耗时 | 占比 | 主要瓶颈 | -|------|---------|------|---------| -| 文件发现 | 2–5 s | ~2% | `os.walk` I/O | -| 元数据提取 | 10–50 s | ~15% | ExifTool 子进程启动 | -| 文件哈希 | 5–30 s | ~10% | 磁盘 I/O + Python 循环 | -| 微缩图生成 | 5–30 s | ~10% | 图像解码 | -| **Live Photo 配对** | **50–300 s** | **~50%** | **datetime 解析在内层循环** | -| 数据库写入 | 5–25 s | ~5% | 事务批量过小(chunk=10) | - ---- - -## 2. 热点代码详细分析 - -### 🔴 热点 1:ISO 8601 日期时间解析(**最高优先级**) - -**文件:** `src/iPhoto/core/pairing.py` - -**函数:** - -```python -def _parse_dt(value: str | None) -> datetime | None: - if not value: - return None - try: - return parser.isoparse(value) # ← 热点:每次调用约 1–5 ms - except (ValueError, TypeError): - return None - -def _match_by_time(photo, candidates, used_videos): - photo_dt = _parse_dt(photo.get("dt")) # ← 调用 1 次 - for candidate in candidates: - video_dt = _parse_dt(candidate.get("dt")) # ← 每个候选视频调用 1 次 - if not photo_dt or not video_dt: - continue - delta = abs((photo_dt - video_dt).total_seconds()) - ... -``` - -**为何是热点:** - -- `dateutil.parser.isoparse()` 是纯 Python 实现,每次调用约 1–5 ms。 -- `_match_by_time` 被 `pair_live()` 中的两个循环(茎名匹配、目录匹配)反复调用,内层再对每个候选视频调用 `_parse_dt`。 -- 对于 100,000 张照片的库(1,000 个视频,每张照片平均 5 个候选): - - 解析次数:~5,000 次 - - 总耗时:~5,000 × 3 ms = **15 秒**仅在匹配循环内,加上外层调用可达 50–300 s。 - -**C 实现方案:** - -```c -/* scan_utils.c */ - -#define _GNU_SOURCE /* timegm() — Linux/glibc; BSDs 无需此宏 */ -#include -#include -#include -#include - -/* 辅助:从字符串 s 读取恰好 n 位十进制整数(不带符号) */ -static int read_n_digits(const char *s, int n) { - int v = 0; - for (int i = 0; i < n; i++) { - if (s[i] < '0' || s[i] > '9') return -1; /* 错误:非数字 */ - v = v * 10 + (s[i] - '0'); - } - return v; -} - -/* 辅助:解析亚秒小数部分,归一化为微秒;*endp 指向解析结束后的位置 */ -static int parse_subsec_us(const char *s, const char **endp) { - int digits = 0, us = 0; - while (digits < 6 && s[digits] >= '0' && s[digits] <= '9') { - us = us * 10 + (s[digits] - '0'); - digits++; - } - /* 补零至 6 位精度 */ - for (int i = digits; i < 6; i++) us *= 10; - *endp = s + digits; - /* 跳过超出 6 位的多余数字 */ - while (**endp >= '0' && **endp <= '9') (*endp)++; - return us; -} - -/** - * parse_iso8601_to_unix_us - * - * 将 ISO 8601 字符串(如 "2024-03-15T10:30:00Z" 或 "2024-03-15T10:30:00+08:00") - * 解析为 Unix 微秒时间戳,失败返回 INT64_MIN。 - * - * 格式支持: - * YYYY-MM-DDTHH:MM:SS[.ffffff][Z|±HH:MM] - * - * 移植说明:timegm() 是 GNU/BSD 扩展,非 POSIX 标准。 - * 若需要 POSIX 可移植性,可改为:将 struct tm 的 tm_isdst=-1, - * 用 mktime() 获得本地时间后加上 timezone 偏移量。 - */ -int64_t parse_iso8601_to_unix_us(const char *s) { - if (!s || strlen(s) < 19) return INT64_MIN; - - struct tm t = {0}; - t.tm_year = read_n_digits(s, 4) - 1900; /* YYYY */ - t.tm_mon = read_n_digits(s+5, 2) - 1; /* MM */ - t.tm_mday = read_n_digits(s+8, 2); /* DD */ - t.tm_hour = read_n_digits(s+11, 2); /* HH */ - t.tm_min = read_n_digits(s+14, 2); /* MM */ - t.tm_sec = read_n_digits(s+17, 2); /* SS */ - - /* 基本范围验证 */ - if (t.tm_year < 0 || t.tm_mon < 0 || t.tm_mday < 1 || - t.tm_hour < 0 || t.tm_min < 0 || t.tm_sec < 0) - return INT64_MIN; - - const char *p = s + 19; - int us = 0; - int tz_offset_sec = 0; - - /* 可选亚秒部分 */ - if (*p == '.') { p++; us = parse_subsec_us(p, &p); } - - /* 时区 */ - if (*p == 'Z' || *p == 'z') { - tz_offset_sec = 0; - } else if (*p == '+' || *p == '-') { - int sign = (*p++ == '+') ? 1 : -1; - int hh = read_n_digits(p, 2); - int mm = (strlen(p) >= 5) ? read_n_digits(p + 3, 2) : 0; - if (hh < 0 || mm < 0) return INT64_MIN; - tz_offset_sec = sign * (hh * 3600 + mm * 60); - } - - /* timegm() 将 struct tm(UTC)转为 time_t,不受本地时区影响 */ - time_t epoch = timegm(&t); - if (epoch == (time_t)-1) return INT64_MIN; - - return (int64_t)(epoch - tz_offset_sec) * 1000000LL + us; -} -``` - -**Python ctypes 调用示例:** - -```python -# src/iPhoto/_native/scan_utils.py -import ctypes, os, pathlib - -_lib = ctypes.CDLL(str(pathlib.Path(__file__).parent / "_scan_utils.so")) -_lib.parse_iso8601_to_unix_us.restype = ctypes.c_int64 -_lib.parse_iso8601_to_unix_us.argtypes = [ctypes.c_char_p] -_INT64_MIN = -2**63 - -def parse_dt_fast(value: str | None) -> int | None: - """返回 Unix 微秒时间戳,解析失败返回 None。""" - if not value: - return None - result = _lib.parse_iso8601_to_unix_us(value.encode()) - return None if result == _INT64_MIN else result -``` - -**预期加速:10–50×**(每次调用 0.05–0.1 ms vs. 原来 1–5 ms) -**对总扫描时间的影响:Live Photo 配对阶段可减少 80–95%** - ---- - -### 🔴 热点 2:文件内容哈希(去重标识) - -**文件:** `src/iPhoto/utils/hashutils.py` - -**函数:** - -```python -def compute_file_id(path: Path) -> str: - threshold = 2 * 1024 * 1024 # 2 MB - - with path.open("rb") as f: - file_size = os.fstat(f.fileno()).st_size - - if file_size <= threshold: - # 小文件:整体哈希,Python 循环读取 1MB 块 - hasher = xxhash.xxh3_128() - chunk_size = 1024 * 1024 - while True: - chunk = f.read(chunk_size) # ← Python bytes 对象分配 - if not chunk: - break - hasher.update(chunk) - return hasher.hexdigest() - - # 大文件:采样哈希(头 + 中 + 尾 各 256KB) - hasher = xxhash.xxh3_128() - hasher.update(file_size.to_bytes(8, "little")) - hasher.update(f.read(256 * 1024)) # Head - f.seek(file_size // 2 - 131072) - hasher.update(f.read(256 * 1024)) # Middle - f.seek(max(0, file_size - 256 * 1024)) - hasher.update(f.read(256 * 1024)) # Tail - return hasher.hexdigest() -``` - -**为何是热点:** - -- 扫描中**每个文件**都要调用(100% 覆盖)。 -- 小文件 Python 循环:每次 `f.read()` 创建 Python `bytes` 对象,有 GIL 保持 + 引用计数开销。 -- 大文件每次 `f.seek()` + `f.read()` 均经过 Python 文件对象层。 -- `xxhash.xxh3_128()` 本身已是 C 扩展,但 Python 调用链开销仍不可忽略。 - -**C 实现方案(`mmap` + 直接哈希):** - -```c -/* scan_utils.c */ - -#include -#include -#include -#include -#include -#include -#include "xxhash.h" /* xxHash 头文件(vendored 或系统库) */ - -#define THRESHOLD (2 * 1024 * 1024) -#define CHUNK_256K (256 * 1024) - -/* 辅助:循环读取直到满足 count 字节或 EOF;返回实际读取字节数 */ -static ssize_t pread_full(int fd, void *buf, size_t count, off_t offset) { - ssize_t total = 0; - while ((size_t)total < count) { - ssize_t n = pread(fd, (char *)buf + total, count - (size_t)total, - offset + total); - if (n <= 0) break; /* EOF 或错误 */ - total += n; - } - return total; -} - -/** - * compute_file_id_c - * - * 计算文件的 128-bit XXH3 哈希(小文件完整、大文件采样)。 - * 输出写入 out_hex(至少 33 字节),成功返回 0,失败返回 -1。 - * - * 注意:out_hex 格式为 "low64_hexhigh64_hex"(小端 64 位拼接), - * 若需与 Python xxhash.xxh3_128().hexdigest() 对齐,请验证字节序后调整。 - */ -int compute_file_id_c(const char *path, char *out_hex) { - int fd = open(path, O_RDONLY); - if (fd < 0) return -1; - - struct stat st; - if (fstat(fd, &st) < 0) { close(fd); return -1; } - off_t size = st.st_size; - - XXH3_state_t *state = XXH3_createState(); - if (!state) { close(fd); return -1; } - XXH3_128bits_reset(state); - - int ok = 0; - - if (size > 0 && size <= THRESHOLD) { - /* 小文件:mmap 后一次性哈希,无 Python 循环开销 */ - void *data = mmap(NULL, (size_t)size, PROT_READ, MAP_PRIVATE, fd, 0); - if (data == MAP_FAILED) { ok = -1; goto cleanup; } - madvise(data, (size_t)size, MADV_SEQUENTIAL); - XXH3_128bits_update(state, data, (size_t)size); - munmap(data, (size_t)size); - } else if (size > THRESHOLD) { - /* 大文件:采样头 + 中 + 尾,混入文件大小 */ - uint8_t buf[CHUNK_256K]; - uint64_t size_le = (uint64_t)size; - XXH3_128bits_update(state, &size_le, 8); - - ssize_t n; - - /* Head */ - n = pread_full(fd, buf, CHUNK_256K, 0); - if (n < 0) { ok = -1; goto cleanup; } - XXH3_128bits_update(state, buf, (size_t)n); - - /* Middle */ - off_t mid = (size / 2) - (CHUNK_256K / 2); - if (mid < 0) mid = 0; - n = pread_full(fd, buf, CHUNK_256K, mid); - if (n < 0) { ok = -1; goto cleanup; } - XXH3_128bits_update(state, buf, (size_t)n); - - /* Tail */ - off_t tail_off = (size > (off_t)CHUNK_256K) ? (size - CHUNK_256K) : 0; - n = pread_full(fd, buf, CHUNK_256K, tail_off); - if (n < 0) { ok = -1; goto cleanup; } - XXH3_128bits_update(state, buf, (size_t)n); - } - /* size == 0:哈希空内容,结果为全零摘要 */ - - { - XXH128_hash_t result = XXH3_128bits_digest(state); - /* - * xxhash Python 库的 hexdigest() 输出为大端序(高 64 位在前): - * high64_hex || low64_hex - * 此处与 Python 实现保持一致。 - */ - snprintf(out_hex, 33, "%016llx%016llx", - (unsigned long long)result.high64, - (unsigned long long)result.low64); - } - -cleanup: - XXH3_freeState(state); - close(fd); - return ok; -} -``` - -**关键优化点:** - -| 优化 | 说明 | -|------|------| -| `mmap` 替代 `f.read()` 循环 | 避免 Python `bytes` 对象分配;操作系统页面缓存直接传给哈希函数 | -| `madvise(MADV_SEQUENTIAL)` | 小文件触发预读,减少缺页中断 | -| `pread()` 替代 `lseek+read` | 原子操作,无需额外 seek 调用 | -| 无 GIL 持有 | 整个 C 函数可在 `Py_BEGIN_ALLOW_THREADS` 块中运行 | - -**预期加速:3–8×**(消除 Python 循环和 bytes 对象分配) -**对总扫描时间的影响:哈希阶段减少 60–75%** - ---- - -### 🟡 热点 3:Glob 路径过滤(文件发现) - -**文件:** `src/iPhoto/utils/pathutils.py` - -**函数:** - -```python -def should_include(path: Path, include_globs, exclude_globs, *, root: Path) -> bool: - if is_excluded(path, exclude_globs, root=root): - return False - rel = path.relative_to(root).as_posix() - for pattern in include_globs: - for expanded in _expand_cached(pattern): - if fnmatch.fnmatch(rel, expanded): # ← 热点 - return True - if expanded.startswith("**/") and fnmatch.fnmatch(rel, expanded[3:]): - return True - return False -``` - -**为何是热点:** - -- 在 `FileDiscoveryThread.run()` 中,**目录树中每个文件**都调用一次(通过 `os.walk`)。 -- `fnmatch.fnmatch()` 在 CPython 中有 C 加速,但仍需要进入 Python 帧,加上 `_expand_cached` 的 tuple 迭代。 -- `path.relative_to(root).as_posix()` 每次调用构造一个 `Path` 对象和一个字符串,有内存分配开销。 -- 对于 100,000 文件的库,此函数调用次数高达 100,000+。 - -**C 实现方案:** - -```c -/* scan_utils.c */ - -#include -#include -#include - -/** - * should_include_c - * - * 检查相对路径 rel_path 是否匹配 include_globs 中的任意模式, - * 且不匹配 exclude_globs 中的任意模式。 - * - * globs 为 NULL 结尾的字符串数组。 - * 返回 1 表示应包含,0 表示排除。 - */ -int should_include_c( - const char *rel_path, - const char **include_globs, - const char **exclude_globs -) { - /* 排除检查 */ - for (int i = 0; exclude_globs[i] != NULL; i++) { - const char *pat = exclude_globs[i]; - if (fnmatch(pat, rel_path, FNM_PATHNAME) == 0) - return 0; - /* 支持 **/ 前缀 */ - if (strncmp(pat, "**/", 3) == 0) { - if (fnmatch(pat + 3, rel_path, FNM_PATHNAME) == 0) - return 0; - } - } - /* 包含检查 */ - for (int i = 0; include_globs[i] != NULL; i++) { - const char *pat = include_globs[i]; - if (fnmatch(pat, rel_path, FNM_PATHNAME) == 0) - return 1; - if (strncmp(pat, "**/", 3) == 0) { - if (fnmatch(pat + 3, rel_path, FNM_PATHNAME) == 0) - return 1; - } - } - return 0; -} -``` - -**注意:** `{a,b}` 花括号扩展逻辑(`_expand()`)也需要在 C 中实现,或在 Python 端预展开后传入展平的 glob 列表。 - -**预期加速:2–5×** -**对总扫描时间的影响:文件发现阶段减少 40–60%** - ---- - -### 🟡 热点 4:目录遍历扩展支持(文件类型过滤) - -**文件:** `src/iPhoto/application/services/parallel_scanner.py` - -**函数:** - -```python -def _discover_files(self, path: Path) -> Generator[Path, None, None]: - for entry in os.scandir(path): - if self._cancelled.is_set(): - return - if entry.is_file(follow_symlinks=False) and self._is_supported(entry.name): - yield Path(entry.path) - elif entry.is_dir(follow_symlinks=False) and not entry.name.startswith("."): - yield from self._discover_files(Path(entry.path)) - -@staticmethod -def _is_supported(filename: str) -> bool: - _, _, ext = filename.rpartition(".") - return f".{ext.lower()}" in _SUPPORTED_EXTENSIONS if ext else False -``` - -**为何是热点:** - -- Python 生成器递归调用(`yield from self._discover_files(...)`)有函数栈帧开销。 -- `entry.name.rpartition(".")` + 字符串小写化 + frozenset 查找:每个文件约 1–2 μs。 -- 100,000 文件 × 2 μs = 0.2 s(独立看不多,但与其他阶段叠加会影响总时延)。 - -**C 实现方案(POSIX `nftw`):** - -```c -/* scan_utils.c */ - -#define _GNU_SOURCE -#include -#include -#include /* strcasecmp */ - -static const char *SUPPORTED_EXT[] = { - ".jpg", ".jpeg", ".png", ".heic", ".heif", - ".mov", ".mp4", ".m4v", ".qt", ".avi", ".mkv", - ".dng", ".cr2", ".cr3", ".nef", ".arw", ".raf", - NULL -}; - -/* 回调函数指针(供 Python 层注册) */ -typedef void (*FileFoundCallback)(const char *path, void *userdata); -static FileFoundCallback g_callback = NULL; -static void *g_userdata = NULL; - -static int _nftw_cb(const char *fpath, const struct stat *sb, - int typeflag, struct FTW *ftwbuf) { - const char *base = fpath + ftwbuf->base; - - /* 跳过以 '.' 开头的隐藏目录(含其所有子树) */ - if ((typeflag == FTW_D || typeflag == FTW_DNR) && base[0] == '.') - return FTW_SKIP_SUBTREE; /* 需要 FTW_ACTIONRETVAL 标志 */ - - /* 只处理普通文件 */ - if (typeflag != FTW_F) return FTW_CONTINUE; - - /* 提取扩展名并与支持列表比对 */ - const char *dot = strrchr(base, '.'); - if (!dot) return FTW_CONTINUE; - - for (int i = 0; SUPPORTED_EXT[i]; i++) { - if (strcasecmp(dot, SUPPORTED_EXT[i]) == 0) { - if (g_callback) g_callback(fpath, g_userdata); - return FTW_CONTINUE; - } - } - return FTW_CONTINUE; -} - -/** - * discover_files_c - * - * 递归扫描 root_dir,对每个支持的媒体文件调用 callback(path, userdata)。 - * 使用 nftw + FTW_ACTIONRETVAL 以支持 FTW_SKIP_SUBTREE 跳过隐藏目录。 - */ -void discover_files_c(const char *root_dir, - FileFoundCallback callback, - void *userdata) { - g_callback = callback; - g_userdata = userdata; - nftw(root_dir, _nftw_cb, 64, - FTW_PHYS /* 不跟随符号链接 */ - | FTW_ACTIONRETVAL /* 启用 FTW_SKIP_SUBTREE / FTW_CONTINUE 返回值 */ - ); -} -``` - -**预期加速:2–4×(文件发现阶段)** - ---- - -### 🟡 热点 5:元数据归一化中的时间戳解析 - -**文件:** `src/iPhoto/infrastructure/services/metadata_provider.py` - -**函数片段:** - -```python -def normalize_metadata(self, root, file_path, raw_metadata): - stat = file_path.stat() - row = { - ... - "dt": datetime.fromtimestamp(stat.st_mtime, tz=timezone.utc) - .isoformat().replace("+00:00", "Z"), - "ts": int(stat.st_mtime * 1_000_000), - ... - } - # 修正 dt/ts - if "dt" in processed_meta: - dt_str = processed_meta["dt"].replace("Z", "+00:00") - dt_obj = datetime.fromisoformat(dt_str) # ← 字符串解析 - row["ts"] = int(dt_obj.timestamp() * 1_000_000) - - # 计算 year/month - if "dt" in row: - dt_str = row["dt"].replace("Z", "+00:00") - dt_obj = datetime.fromisoformat(dt_str) # ← 再次解析同一字符串 - row["year"] = dt_obj.year - row["month"] = dt_obj.month -``` - -**为何是热点:** - -- `datetime.fromisoformat()` **对同一字符串解析两次**(ts 修正 + year/month 计算)。 -- 每个文件调用一次 `normalize_metadata`,100,000 文件 × 2 次解析 = 200,000 次解析。 -- 可直接复用热点 1 的 `parse_iso8601_to_unix_us` C 函数,同时返回年、月、微秒时间戳。 - -**C 实现扩展:** - -```c -/* 扩展版本,同时输出 year / month */ -typedef struct { - int64_t unix_us; /* Unix 微秒时间戳 */ - int year; - int month; -} DateTimeResult; - -DateTimeResult parse_iso8601_full(const char *s); -``` - -**预期加速:** 将 normalize_metadata 中的日期处理开销减少 70–80%, -**对总扫描时间的影响:** 元数据归一化阶段减少 30–50% - ---- - -### 🟢 热点 6:Live Photo 内容 ID 归一化 - -**文件:** `src/iPhoto/core/pairing.py` - -**函数:** - -```python -def _normalise_content_id(value: object) -> str | None: - if not isinstance(value, str): - return None - trimmed = value.strip() - if not trimmed: - return None - return trimmed.casefold() -``` - -**为何是热点:** - -- 在 `pair_live()` 中对所有照片和视频各调用一次,然后在 `defaultdict` 构建循环中再次调用。 -- Python 的 `.strip()` 和 `.casefold()` 都创建新的字符串对象(堆分配)。 -- 对于 100,000 资产库:200,000 次字符串操作。 - -**C 实现方案:** - -```c -/** - * normalise_content_id_inplace - * - * 原地修改字符串:去除首尾空白,转为小写。 - * 若结果为空字符串,将第一个字节设为 '\0'。 - * 返回修改后的长度(0 表示空)。 - */ -int normalise_content_id_inplace(char *s) { - if (!s) return 0; - /* 去尾空白 */ - int len = strlen(s); - while (len > 0 && (s[len-1] == ' ' || s[len-1] == '\t' || - s[len-1] == '\n' || s[len-1] == '\r')) - s[--len] = '\0'; - /* 去首空白 */ - int start = 0; - while (start < len && (s[start] == ' ' || s[start] == '\t')) - start++; - if (start > 0) { - memmove(s, s + start, len - start + 1); - len -= start; - } - /* 小写化(仅 ASCII;UUID 内容 ID 均为 ASCII) */ - for (int i = 0; i < len; i++) - if (s[i] >= 'A' && s[i] <= 'Z') s[i] += 32; - return len; -} -``` - -**预期加速:3–5×(该函数单体)** -**对总扫描时间的影响:Live Photo 配对阶段额外减少 5–10%** - ---- - -## 3. 优先级汇总与实施建议 - -| 优先级 | 热点 | 涉及文件 | 实施难度 | 预期加速 | 总时间收益 | -|-------|------|---------|---------|---------|-----------| -| 🔴 P1 | ISO 8601 datetime 解析 | `core/pairing.py` | 中 | 10–50× | **最大**(-80%配对耗时) | -| 🔴 P2 | 文件内容哈希 | `utils/hashutils.py` | 中 | 3–8× | 大(-60%哈希耗时) | -| 🟡 P3 | Glob 路径过滤 | `utils/pathutils.py` | 中 | 2–5× | 中(-40%发现耗时) | -| 🟡 P4 | 目录遍历 + 扩展名过滤 | `application/services/parallel_scanner.py` | 中高 | 2–4× | 中 | -| 🟡 P5 | 元数据时间戳解析(重复) | `infrastructure/services/metadata_provider.py` | 低 | 2–3× | 中(-30%归一化耗时) | -| 🟢 P6 | Content ID 归一化 | `core/pairing.py` | 低 | 3–5× | 小 | - -### 实施路径建议 - -**第一阶段(2–3 天,收益最大)** - -1. 创建 `src/iPhoto/_native/` 目录,参考 `demo/video/_native/` 的 JIT 编译模式(gcc ctypes)。 -2. 实现 `parse_iso8601_to_unix_us()` C 函数,替换 `pairing.py` 中的 `_parse_dt()`。 -3. 实现 `compute_file_id_c()` C 函数,替换 `hashutils.py` 中的 `compute_file_id()`(使用 `mmap`)。 -4. 在两处都保留 Python 纯实现作为 fallback(`gcc` 不可用或编译失败时)。 - -**第二阶段(3–5 天)** - -5. 实现 `should_include_c()` C 函数,替换 `pathutils.py` 中的 `should_include()`。 -6. 实现 `parse_iso8601_full()` 扩展版本,消除 `metadata_provider.py` 中的重复解析。 - -**第三阶段(5–7 天,可选)** - -7. 实现 `discover_files_c()` 使用 POSIX `nftw`,替换 `parallel_scanner.py` 中的 Python 递归生成器。 -8. 实现 `normalise_content_id_inplace()` 降低字符串分配压力。 - ---- - -## 4. C 扩展集成模式 - -参考项目中已有的 `demo/video/_native/` 实现方式(JIT 编译 + ctypes + Python fallback): - -``` -src/iPhoto/_native/ -├── __init__.py # JIT 编译 + ctypes 绑定 + fallback 切换 -└── scan_utils.c # 所有扫描热点 C 实现 -``` - -**`__init__.py` 关键逻辑:** - -```python -"""iPhoto 扫描阶段 C 原生加速模块。 - -在首次导入时 JIT 编译 scan_utils.c(需要 gcc)。 -若编译失败,所有函数自动回退到纯 Python 实现。 -""" - -import ctypes -import os -import shutil -import subprocess -import pathlib -import threading - -_lock = threading.Lock() -_lib = None -_C_AVAILABLE = False - -_SRC = pathlib.Path(__file__).parent / "scan_utils.c" -_OUT = pathlib.Path(__file__).parent / "_scan_utils.so" - -def _compile(): - global _lib, _C_AVAILABLE - gcc = shutil.which("gcc") or shutil.which("cc") - if not gcc: - return - try: - flags = ["-O3", "-march=native", "-shared", "-fPIC", - "-o", str(_OUT), str(_SRC)] - subprocess.run([gcc] + flags, check=True, - capture_output=True, timeout=30) - except Exception: - try: # 降级:不使用 -march=native - flags = ["-O2", "-shared", "-fPIC", - "-o", str(_OUT), str(_SRC)] - subprocess.run([gcc] + flags, check=True, - capture_output=True, timeout=30) - except Exception: - return - try: - _lib = ctypes.CDLL(str(_OUT)) - _lib.parse_iso8601_to_unix_us.restype = ctypes.c_int64 - _lib.parse_iso8601_to_unix_us.argtypes = [ctypes.c_char_p] - # ... 其他函数签名 ... - _C_AVAILABLE = True - except Exception: - pass - -with _lock: - if not _C_AVAILABLE and _SRC.exists(): - _compile() -``` - ---- - -## 5. 参考资料 - -- [xxHash 官方文档](https://xxhash.com/) -- [POSIX `nftw(3)`](https://man7.org/linux/man-pages/man3/nftw.3.html) -- [POSIX `mmap(2)` + `madvise(2)`](https://man7.org/linux/man-pages/man2/mmap.2.html) -- [ISO 8601 规范](https://www.iso.org/iso-8601-date-and-time-format.html) -- `demo/video/_native/fast_thumb.c` — 项目内已有的 C JIT 扩展示例 -- `demo/video/_native/__init__.py` — JIT 编译 + ctypes 绑定示例 +# Scan Hotspot Optimization Rebaseline + +> Status: **Active profiling requirement.** Historical scanner call graphs and +> timing estimates are not current evidence. + +Before selecting C/C++ or another compiled rewrite target, profile the current +production scan path through `RuntimeContext`, `LibrarySession`, application +services/ports, scanner adapters, and the current index store. + +Measure filesystem discovery, metadata handling, hashing, Live Photo pairing, +thumbnail work, repository commit, and visible post-commit publishing +separately on representative small, medium, and large libraries. + +A native optimization is justified only when current measurements show material +CPU cost, the boundary is narrow/stable, conversion overhead does not erase the +gain, and reference-vs-optimized equivalence can be tested. I/O- or +subprocess-dominated stages should first be improved through batching, fewer +round trips, scheduling, or cache policy. + +Any optimization must preserve scan idempotence, durable user state, Live Photo +roles, independent `face_status`/`pet_status`, cancellation/generation safety, +and post-commit `ScanBatchCommitted` publishing. It must not bypass +application/session ownership or repository boundaries. + +For each candidate record the current call site, CPU share, wall-time share, +proposed boundary, before/after result, regression coverage, and packaging +impact. `src/iPhoto/core/pairing.py` is valid to profile, but it is not assumed +to be a hotspot merely because an older document identified date parsing there. + +Move this requirement to `docs/finished/requirements/` after the current scanner +has been profiled and every material compiled-code candidate has a measured +decision. diff --git a/docs/security.md b/docs/security.md index 05104d68e..ff90fa73e 100644 --- a/docs/security.md +++ b/docs/security.md @@ -1,198 +1,161 @@ # 🔒 Security -> Permissions, encryption, data storage locations, and threat model for **iPhotron**. +## Overview ---- +iPhotron is a local-first photo manager. Core library browsing, editing, Live +Photo handling, persisted People/Pets state, and offline Maps operation do not +require a cloud account. User libraries and iPhotron state remain on local +storage unless the user explicitly invokes a network-backed optional download. -## Overview +## Filesystem And External Tools + +The application reads user-selected media libraries and writes managed local +state such as `.iPhoto/`, folder manifests, thumbnails, recognition state, and +`.ipo` sidecars. Normal editing is non-destructive. + +Assign Location is an explicit metadata-write exception: iPhotron saves local +location state first and may then best-effort write GPS metadata back to the +selected original through ExifTool. External-tool errors must not corrupt the +local database/state transaction. + +ExifTool and FFmpeg/FFprobe are invoked through the repository wrappers rather +than shell-concatenated user paths. + +## Optional Network Access + +Normal library operation requires no network connection. Optional runtimes may +have separately controlled download paths, for example a published Maps +extension or model artifact with an explicit source/integrity contract. + +A package that claims offline People/Pets or Maps capability must stage the +required optional runtime/assets at build time rather than silently depend on a +network request after installation. + +## Model Asset Locations + +`src/extension/models/...` is a build/staging convention used by packaging +scripts. It is **not** a guarantee that a fresh source checkout contains every +AI model. + +At runtime, model roots may be selected through the feature's configured cache +or environment override. Packaging documentation must distinguish: + +- tracked application code/manifests; +- locally staged model assets; +- packaged assets; +- runtime-downloaded assets with a declared download contract. + +Do not treat a local staging directory as a trusted source merely because it is +inside the repository checkout. + +## Pets Model Trust Boundary + +Pets uses a YOLOX detector artifact and a DINOv2 TorchScript embedding artifact. +The model manifest under `src/iPhoto/pets/model_manifest.json` defines the +runtime integrity contract. + +### YOLOX detector + +The detector entry declares a fixed HTTPS artifact source and SHA-256/size +constraints. When first-use model downloads are enabled, the runtime may fetch +that declared artifact and must validate it before use. + +### DINOv2 embedder + +The DINOv2 entry separates **source provenance** from **production runtime +trust**: + +- `source_repository` and `source_revision` identify the upstream source used by + release conversion tooling; +- `torchscript_sha256` and `torchscript_size` identify the exact prebuilt + TorchScript artifact accepted by production runtime; +- the current `torchscript_url` is `null`. + +Therefore the current production contract is package/prestage-first for DINOv2. +A missing DINOv2 artifact cannot be described as automatically downloadable +until a fixed runtime URL is explicitly added to the manifest and validated by +the same integrity checks. -iPhotron is a **local-first photo manager**. It does not upload data to any cloud service, does not require an internet connection for core functionality, and does not collect user telemetry. All library data remains on the user's local filesystem. Network-facing flows are limited to optional extension/model downloads: a user-triggered maps-extension download on platforms where a published extension archive exists, and first-use People/Pets model downloads when local AI model caches are absent. +### Torch Hub boundary ---- - -## Permissions - -### Filesystem Access - -| Access | Scope | Purpose | -|--------|-------|---------| -| **Read** | User-selected library folders | Scan photos/videos, read metadata (EXIF, GPS) | -| **Write** | Library folders | Create `.iphoto.album.json` manifests, `.ipo` sidecar files | -| **Write** | `.iPhoto/` directory at library root | Global SQLite database, thumbnail caches, and durable People/Pets state | -| **Read/Write** | `extension/models/` or an explicit model override | Read bundled AI models and populate an allowed first-use model cache | -| **Write** | Selected original media file | Best-effort GPS metadata write-back after an explicit Assign Location action | -| **Read/Write** | Application settings directory | User preferences (theme, export destination) | +Production runtime must not execute arbitrary Torch Hub repository Python to +obtain DINOv2. A pinned Torch Hub/upstream revision may be used by controlled +release tooling such as `tools/convert_dinov2_torchscript.py` to reproduce or +validate a TorchScript artifact. That is a build/provenance operation, not a +runtime trust mechanism. -### External Tool Access - -| Tool | Access | Purpose | -|------|--------|---------| -| **ExifTool** | Read/write on media files when requested | Extract EXIF, GPS, QuickTime metadata; write GPS coordinates for explicit Assign Location actions | -| **FFmpeg / FFprobe** | Read-only on media files | Generate video thumbnails, parse video info | +The resulting prebuilt artifact is trusted only after the manifest's expected +hash/size checks succeed. A source revision alone is not sufficient runtime +integrity validation. -### Network Access +## People Model Boundary -iPhotron requires **no network access for normal library operation**. Map rendering, -reverse geocoding, and Assign Location search work offline when the maps -extension is installed. +People recognition uses the optional InsightFace/ONNX Runtime stack. Packaged +People support must explicitly include or provision the required local model +artifacts. Missing People AI dependencies/models must degrade the recognition +feature without blocking the rest of the library application. -| Feature | Access | Purpose | -|---------|--------|---------| -| **Map rendering** | Offline (bundled OBF/vector map assets) | Render map tiles for the location view | -| **Reverse geocoding** | Local database lookup | Convert GPS coordinates to place names (offline, via `reverse-geocoder` library) | -| **Map extension download** | Optional HTTPS download | Fetch a published extension archive only when the user chooses the download path | -| **People/Pets model download** | Optional HTTPS download | Populate missing local AI model caches before background recognition scans | +Rebuildable People runtime data and durable People user state remain separate: +`face_index.db` can be rebuilt, while `face_state.db` stores durable choices such +as names/covers/groups/manual faces. -> **Note:** No telemetry or cloud sync is performed. A network connection is only -> needed if the user chooses to download a missing map extension or lets a -> background People/Pets scan populate a missing model cache. +## Pets Persistence Boundary ---- +Pets follows the same rebuildable-versus-durable split: -## Encryption +- `.iPhoto/pets/pet_index.db`: rebuildable detections/identity snapshot; +- `.iPhoto/pets/pet_state.db`: durable names, covers, hidden/rejected decisions, + redirects, and other explicit user choices; +- `.iPhoto/pets/thumbnails/`: rebuildable crops subject to durable cover + references. -### At Rest - -iPhotron does **not** encrypt data at rest. The following files are stored in plaintext: +A runtime rebuild or model-version migration must not silently erase durable +user decisions. -| File | Format | Contents | -|------|--------|----------| -| `.iphoto.album.json` | JSON | Album metadata: cover image, featured photos, sort order | -| `*.ipo` | JSON | Edit parameters: light, color, B&W, crop, perspective adjustments | -| `global_index.db` | SQLite | Asset/index facts plus repository-backed user state such as favorites, hidden/trash flags, pinned/order data, and manual metadata | -| `.iPhoto/faces/face_index.db` | SQLite | Rebuildable People runtime snapshot | -| `.iPhoto/faces/face_state.db` | SQLite | Stable People decisions: names, covers, hidden flags, groups, ordering | -| `.iPhoto/pets/pet_index.db` | SQLite | Rebuildable Pets runtime snapshot | -| `.iPhoto/pets/pet_state.db` | SQLite | Stable Pets decisions: names, covers, hidden flags, rejected detections | -| `extension/models/` | Model files | Local AI model cache for People and Pets recognition | -| Thumbnail cache | Image files | Downscaled preview images | -| `settings.json` | JSON | Theme, language, recent library, export destination, and other application preferences | +## Recognition Activation And Resource Isolation -**Rationale:** The data managed by iPhotron (album organization, edit parameters, file metadata) is non-sensitive in most contexts. Users who require encryption should use full-disk encryption (e.g., BitLocker, FileVault, LUKS). - -### In Transit +Recognition inference is feature-driven. Application startup may warm persisted +recognition summaries, but People/Pets model inference is activated only after +the People surface is actually shown and its first viewport is ready. -- No media, metadata, telemetry, or library state is transmitted by normal app - operation. Optional downloads retrieve only extension/model files; media and - library databases are not uploaded. +This reduces unnecessary model execution during normal startup and keeps +optional AI initialization out of the first-frame path. Missing or invalid model +assets leave recognition unavailable/resumable according to its runtime status +contract rather than turning into a desktop-startup failure. ---- - -## Data Storage Locations +## Database / Sidecar Safety -``` -LibraryRoot/ # User-selected photo library folder -├── .iPhoto/ -│ ├── global_index.db # SQLite database (all asset metadata) -│ ├── cache/ -│ │ └── thumbs/ # Rebuildable thumbnail cache -│ ├── faces/ -│ │ ├── face_index.db # Rebuildable People runtime snapshot -│ │ ├── face_state.db # Stable People user decisions -│ │ └── thumbnails/ # Cropped face thumbnails -│ └── pets/ -│ ├── pet_index.db # Rebuildable Pets runtime snapshot -│ ├── pet_state.db # Stable Pets user decisions -│ └── thumbnails/ # Cropped pet thumbnails -├── Album1/ -│ ├── .iphoto.album.json # Album manifest -│ ├── photo.jpg # Original photo (edits are sidecar-only) -│ └── photo.jpg.ipo # Edit sidecar (if edited) -└── Album2/ - └── ... -``` +- Use SQLite transactions for multi-row state changes. +- Keep scan merges idempotent and preserve durable user choices. +- Use atomic writes for JSON manifests/settings and `.ipo` sidecars. +- Treat `.iPhoto/global_index.db`, People/Pets state databases, and sidecars as + user-controlled local data; validate inputs and do not execute content from + them as code. +- Keep rebuildable caches replaceable without making them authoritative durable + state. -### Settings Storage +## Maps Runtime -User settings, including theme, language, export destination, and recent library -state, are stored in a validated `settings.json` file: +Maps is optional. Native helper/widget binaries and offline map/search data are +packaging/runtime assets and must be discovered through the Maps runtime +boundary. Missing native binaries must produce graceful fallback rather than +cause core startup failure. -| Platform | Location | -|----------|----------| -| **Windows** | `%APPDATA%\iPhoto\settings.json` | -| **macOS** | `~/Library/Application Support/iPhoto/settings.json` | -| **Linux** | `$XDG_CONFIG_HOME/iPhoto/settings.json`, or `~/.config/iPhoto/settings.json` when `XDG_CONFIG_HOME` is unset | - ---- - -## Threat Model - -### Assets Protected - -| Asset | Sensitivity | Protection | -|-------|-------------|------------| -| Original photos/videos | Personal (potentially high) | Edits are non-destructive; explicit Assign Location may best-effort write GPS metadata | -| GPS coordinates in metadata | Location data (medium) | Stored in SQLite index and, when ExifTool write-back succeeds, in the original file metadata | -| Album organization | Low | Stored in JSON manifests alongside photos | -| Edit parameters | Low | Stored in `.ipo` sidecar files | -| Durable library, People, and Pets choices | Personal | Stored in `global_index.db`, `.iPhoto/faces/face_state.db`, and `.iPhoto/pets/pet_state.db`; include `.iPhoto/` in backups | - -### Threat Scenarios - -#### T1: Unauthorized Access to Photo Library - -| | | -|---|---| -| **Threat** | An attacker gains read access to the library folder | -| **Impact** | Access to original photos, GPS metadata, album organization | -| **Mitigation** | OS-level file permissions; full-disk encryption recommended for sensitive libraries | -| **iPhotron's role** | iPhotron does not add or remove filesystem protections | - -#### T2: SQLite Database Tampering - -| | | -|---|---| -| **Threat** | An attacker modifies `global_index.db` | -| **Impact** | Corrupted display/index facts and possible loss of repository-backed user choices if no backup exists | -| **Mitigation** | OS-level file permissions, SQLite recovery, and regular backup of the complete `.iPhoto/` workspace | -| **Recovery** | Re-scan rebuilds asset facts and thumbnails; restore `.iPhoto/` from backup to recover durable choices that cannot be inferred from media files | +## Packaging And Release Provenance -#### T3: Malicious Media Files - -| | | -|---|---| -| **Threat** | A crafted image/video exploits a vulnerability in a parsing library | -| **Impact** | Potential code execution via Pillow, FFmpeg, or ExifTool | -| **Mitigation** | Keep dependencies updated; use `pillow-heif` and `opencv-python-headless` (no GUI attack surface) | - -#### T4: Malicious Map Data - -| | | -|---|---| -| **Threat** | Crafted or corrupted bundled map data, resources, or search database | -| **Impact** | Incorrect map display; potential parsing vulnerability | -| **Mitigation** | Map assets are rendered through local native/helper renderers or Qt/OpenGL paths, not a web view; no script execution is performed; published extension archives should be validated before release | - -#### T5: Supply Chain Attack via Dependencies - -| | | -|---|---| -| **Threat** | A compromised PyPI package is installed | -| **Impact** | Arbitrary code execution | -| **Mitigation** | Constrain dependencies in `pyproject.toml`; review resolved dependency updates; use isolated virtual environments and reproducible release lock inputs | - -#### T6: AI Model Supply Chain - -| | | -|---|---| -| **Threat** | A model or model-loader source changes upstream or is replaced in transit/cache | -| **Impact** | Recognition failure, unsafe model parsing, or execution of changed loader code | -| **Mitigation** | HTTPS downloads, a pinned immutable DINOv2 Torch Hub revision, explicit model-cache overrides, and offline release builds with reviewed files under `extension/models/` | -| **Operator control** | Set `IPHOTO_PET_MODEL_AUTO_DOWNLOAD=0` to require pre-provisioned Pets models | - ---- - -## Security Best Practices for Users - -1. **Use full-disk encryption** (BitLocker / FileVault / LUKS) if your photo library contains sensitive content. -2. **Keep ExifTool and FFmpeg updated** to receive security patches. -3. **Install ExifTool only from a trusted source** if you plan to use GPS write-back through Assign Location. -4. **Run `pip install --upgrade`** periodically to update Python dependencies. -5. **Use OS-level file permissions** to restrict access to your library folder. -6. **Back up your library** regularly — iPhotron's `.iPhoto/` directory and `.ipo` files should be included in backups. +Build outputs should preserve an auditable relationship between source, +optional native/runtime assets, and the produced artifact. Current AppImage and +Linux standalone tooling emit build-manifest provenance; Debian packaging wraps +the standalone bundle rather than silently rebuilding different contents. ---- +A published release artifact does not prove a current source-build path exists. +In particular, the v6.6.8 Flatpak download is distinct from current +`edit-base` build support: this branch does not presently contain a maintained +Flatpak manifest/build driver. See `misc/BUILD_FLATPAK.md`. ## Reporting Security Issues -If you discover a security vulnerability, please report it via [GitHub Security Advisories](https://github.com/OliverZhaohaibin/iPhotron-LocalPhotoAlbumManager/security/advisories) or email the maintainers directly. Do not open a public issue for security vulnerabilities. +Do not publish sensitive exploit details, private library data, access tokens, or +other credentials in public issues. Use the repository's available private +security-reporting channel when the finding could materially compromise user +files, packaged binaries, model delivery, or update/download integrity. diff --git a/tools/check_docs_links.py b/tools/check_docs_links.py new file mode 100644 index 000000000..20330696b --- /dev/null +++ b/tools/check_docs_links.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python3 +"""Validate repository-local links in maintained Markdown documentation.""" + +from __future__ import annotations + +import re +import sys +from pathlib import Path +from urllib.parse import unquote + +ROOT = Path(__file__).resolve().parents[1] +LINK_RE = re.compile(r"!?\[[^\]]*\]\(([^)]+)\)") +SKIP_PREFIXES = ("http://", "https://", "mailto:", "tel:", "data:") + + +def markdown_files() -> list[Path]: + files = [ROOT / "README.md"] + files.extend(sorted((ROOT / "docs").rglob("*.md"))) + return [path for path in files if path.is_file()] + + +def normalize_target(raw: str) -> str | None: + target = raw.strip() + if target.startswith("<") and target.endswith(">"): + target = target[1:-1].strip() + if not target or target.startswith("#") or target.startswith(SKIP_PREFIXES): + return None + # Optional Markdown link title follows the path after whitespace. + if " " in target and not target.startswith("./"): + first = target.split()[0] + if first: + target = first + target = target.split("#", 1)[0].split("?", 1)[0] + return unquote(target) or None + + +def main() -> int: + failures: list[str] = [] + for source in markdown_files(): + text = source.read_text(encoding="utf-8") + for match in LINK_RE.finditer(text): + target = normalize_target(match.group(1)) + if target is None: + continue + resolved = (source.parent / target).resolve() + try: + resolved.relative_to(ROOT) + except ValueError: + failures.append(f"{source.relative_to(ROOT)}: link escapes repo: {target}") + continue + if not resolved.exists(): + failures.append(f"{source.relative_to(ROOT)}: missing local link: {target}") + + if failures: + print("Documentation link check failed:", file=sys.stderr) + for failure in failures: + print(f"- {failure}", file=sys.stderr) + return 1 + print(f"Documentation links OK ({len(markdown_files())} Markdown files checked).") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tools/check_readme_parity.py b/tools/check_readme_parity.py new file mode 100644 index 000000000..366abe30f --- /dev/null +++ b/tools/check_readme_parity.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 +"""Keep the maintained EN/ZH/DE README release/runtime contracts aligned.""" + +from __future__ import annotations + +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +READMES = ( + ROOT / "README.md", + ROOT / "docs/readme/README_zh-CN.md", + ROOT / "docs/readme/README_de.md", +) + +REQUIRED = ( + "v6.6.8", + "v6.68-x86-setup.exe", + "iphotron_6.6.8_amd64.deb", + "iPhotron-6.6.8-x86_64.AppImage", + "com.github.OliverZhaohaibin.iPhotron-6.6.8-x86_64.flatpak", + "iPhoto.entrypoint:main", + "DesktopCoordinatorRuntime", + "species-bounded-single-link-v3", + "torchscript_url", + "BUILD_FLATPAK.md", +) + + +def main() -> int: + failures: list[str] = [] + for path in READMES: + if not path.is_file(): + failures.append(f"missing maintained README: {path.relative_to(ROOT)}") + continue + text = path.read_text(encoding="utf-8") + for token in REQUIRED: + if token not in text: + failures.append(f"{path.relative_to(ROOT)}: missing parity token {token!r}") + + if failures: + print("README parity check failed:", file=sys.stderr) + for failure in failures: + print(f"- {failure}", file=sys.stderr) + return 1 + + print("README parity OK: EN/ZH/DE release and runtime contracts are aligned.") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main())