Metadata-Version: 2.5
Name: immich-memories
Version: 0.75.5
Summary: Turn your Immich photo library into video memory compilations with music and scene-aware cuts
Project-URL: Homepage, https://github.com/sam-dumont/immich-video-memory-generator
Project-URL: Documentation, https://sam-dumont.github.io/immich-video-memory-generator/
Project-URL: Repository, https://github.com/sam-dumont/immich-video-memory-generator
Project-URL: Issues, https://github.com/sam-dumont/immich-video-memory-generator/issues
Project-URL: Changelog, https://github.com/sam-dumont/immich-video-memory-generator/releases
Author: Immich Memories Contributors
License-Expression: MIT
License-File: LICENSE
Keywords: compilation,immich,memories,photo-management,video
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Environment :: Web Environment
Classifier: Intended Audience :: End Users/Desktop
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Multimedia :: Video
Classifier: Topic :: Multimedia :: Video :: Conversion
Requires-Python: >=3.11
Requires-Dist: apprise>=1.9
Requires-Dist: click>=8.3.3
Requires-Dist: croniter>=1.3
Requires-Dist: geopy>=2.4
Requires-Dist: httpx>=0.27
Requires-Dist: nicegui>=3.16.0
Requires-Dist: numpy>=1.24
Requires-Dist: opencv-python<5,>=4.9
Requires-Dist: pillow-heif>=1.0
Requires-Dist: pillow>=12.3.0
Requires-Dist: pydantic-settings>=2.14.2
Requires-Dist: pydantic>=2.0
Requires-Dist: pyyaml>=6.0
Requires-Dist: rich>=13.0
Requires-Dist: scenedetect<0.8,>=0.7
Requires-Dist: staticmap>=0.5
Requires-Dist: watchdog>=3.0
Provides-Extra: all
Requires-Dist: authlib>=1.6.12; extra == 'all'
Requires-Dist: demucs>=4.0; extra == 'all'
Requires-Dist: freetype-py>=2.4; extra == 'all'
Requires-Dist: immich-memories-music>=0.1.0; extra == 'all'
Requires-Dist: kaldi-native-fbank>=1.20; extra == 'all'
Requires-Dist: mutagen>=1.47; extra == 'all'
Requires-Dist: onnxruntime>=1.28; extra == 'all'
Requires-Dist: panns-inference>=0.1; extra == 'all'
Requires-Dist: pywhispercpp>=1.5; extra == 'all'
Requires-Dist: taichi>=1.7.0; (sys_platform != 'linux' or (platform_machine != 'aarch64' and platform_machine != 'arm64')) and extra == 'all'
Requires-Dist: torch>=2.0; extra == 'all'
Provides-Extra: all-mac
Requires-Dist: demucs>=4.0; extra == 'all-mac'
Requires-Dist: freetype-py>=2.4; extra == 'all-mac'
Requires-Dist: immich-memories-music>=0.1.0; extra == 'all-mac'
Requires-Dist: kaldi-native-fbank>=1.20; extra == 'all-mac'
Requires-Dist: mutagen>=1.47; extra == 'all-mac'
Requires-Dist: onnxruntime>=1.28; extra == 'all-mac'
Requires-Dist: panns-inference>=0.1; extra == 'all-mac'
Requires-Dist: pyobjc-core>=9.0; (sys_platform == 'darwin') and extra == 'all-mac'
Requires-Dist: pyobjc-framework-metal>=9.0; (sys_platform == 'darwin') and extra == 'all-mac'
Requires-Dist: pyobjc-framework-quartz>=9.0; (sys_platform == 'darwin') and extra == 'all-mac'
Requires-Dist: pyobjc-framework-vision>=9.0; (sys_platform == 'darwin') and extra == 'all-mac'
Requires-Dist: pywhispercpp>=1.5; extra == 'all-mac'
Requires-Dist: taichi>=1.7.0; (sys_platform != 'linux' or (platform_machine != 'aarch64' and platform_machine != 'arm64')) and extra == 'all-mac'
Requires-Dist: torch>=2.0; extra == 'all-mac'
Provides-Extra: audio
Requires-Dist: mutagen>=1.47; extra == 'audio'
Provides-Extra: audio-ml
Requires-Dist: panns-inference>=0.1; extra == 'audio-ml'
Requires-Dist: torch>=2.0; extra == 'audio-ml'
Provides-Extra: auth
Requires-Dist: authlib>=1.6.12; extra == 'auth'
Provides-Extra: demucs
Requires-Dist: demucs>=4.0; extra == 'demucs'
Provides-Extra: dev
Requires-Dist: commitizen>=3.0; extra == 'dev'
Requires-Dist: deptry>=0.20; extra == 'dev'
Requires-Dist: diff-cover>=9.0; extra == 'dev'
Requires-Dist: grimp>=3.0; extra == 'dev'
Requires-Dist: import-linter>=2.0; extra == 'dev'
Requires-Dist: mypy>=1.0; extra == 'dev'
Requires-Dist: oidc-provider-mock>=0.3; extra == 'dev'
Requires-Dist: pre-commit>=3.0; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.21; extra == 'dev'
Requires-Dist: pytest-benchmark>=5.2.3; extra == 'dev'
Requires-Dist: pytest-cov>=4.0; extra == 'dev'
Requires-Dist: pytest-playwright>=0.5; extra == 'dev'
Requires-Dist: pytest-xdist>=3.5; extra == 'dev'
Requires-Dist: pytest>=9.0.3; extra == 'dev'
Requires-Dist: python-semantic-release>=9.0; extra == 'dev'
Requires-Dist: refurb>=2.0; extra == 'dev'
Requires-Dist: ruff>=0.1; extra == 'dev'
Requires-Dist: soundfile>=0.12; extra == 'dev'
Requires-Dist: types-croniter>=6.0; extra == 'dev'
Requires-Dist: types-pyyaml>=6.0; extra == 'dev'
Provides-Extra: gpu
Requires-Dist: freetype-py>=2.4; extra == 'gpu'
Requires-Dist: taichi>=1.7.0; (sys_platform != 'linux' or (platform_machine != 'aarch64' and platform_machine != 'arm64')) and extra == 'gpu'
Provides-Extra: mac
Requires-Dist: pyobjc-core>=9.0; (sys_platform == 'darwin') and extra == 'mac'
Requires-Dist: pyobjc-framework-metal>=9.0; (sys_platform == 'darwin') and extra == 'mac'
Requires-Dist: pyobjc-framework-quartz>=9.0; (sys_platform == 'darwin') and extra == 'mac'
Requires-Dist: pyobjc-framework-vision>=9.0; (sys_platform == 'darwin') and extra == 'mac'
Provides-Extra: music
Requires-Dist: immich-memories-music>=0.1.0; extra == 'music'
Provides-Extra: speech
Requires-Dist: kaldi-native-fbank>=1.20; extra == 'speech'
Requires-Dist: onnxruntime>=1.28; extra == 'speech'
Provides-Extra: transcribe
Requires-Dist: pywhispercpp>=1.5; extra == 'transcribe'
Description-Content-Type: text/markdown

# Immich Memories

[![CI](https://github.com/sam-dumont/immich-video-memory-generator/actions/workflows/ci.yml/badge.svg)](https://github.com/sam-dumont/immich-video-memory-generator/actions/workflows/ci.yml)
[![codecov](https://codecov.io/gh/sam-dumont/immich-video-memory-generator/graph/badge.svg)](https://codecov.io/gh/sam-dumont/immich-video-memory-generator)
[![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/sam-dumont/immich-video-memory-generator/badge)](https://scorecard.dev/viewer/?uri=github.com/sam-dumont/immich-video-memory-generator)
[![Release](https://github.com/sam-dumont/immich-video-memory-generator/actions/workflows/release.yml/badge.svg)](https://github.com/sam-dumont/immich-video-memory-generator/actions/workflows/release.yml)
[![Python](https://img.shields.io/pypi/pyversions/immich-memories)](https://pypi.org/project/immich-memories/)
[![License](https://img.shields.io/github/license/sam-dumont/immich-video-memory-generator)](LICENSE)
[![Docs](https://img.shields.io/badge/docs-Docusaurus-blue)](https://sam-dumont.github.io/immich-video-memory-generator/)

**Cuts your [Immich](https://immich.app/) library into edited memory videos: title screens, music, and only the good five seconds of each clip.**

It connects to your self-hosted Immich server and runs a real editor over your library: a vision
model looks at the material and describes what is happening, selection and review judge those
descriptions, and the keepers become a real edit — a year in review, a trip with its map, one
person across the years. Always chronological, favourites treated as law, and when it can't name
a day honestly it refuses rather than faking it. How it decides is documented in
[The Curator](https://sam-dumont.github.io/immich-video-memory-generator/docs/create/pipeline/the-curator).

> **Full documentation**: [sam-dumont.github.io/immich-video-memory-generator](https://sam-dumont.github.io/immich-video-memory-generator/)

<p align="center">
  <a href="https://sam-dumont.github.io/immich-video-memory-generator/docs/welcome/overview">
    <img src="https://sam-dumont.github.io/immich-video-memory-generator/img/demo-hero.gif" alt="Immich Memories demo: clip review, title screens and a finished memory video" width="800">
  </a>
  <br/>
  <sub><a href="https://sam-dumont.github.io/immich-video-memory-generator/docs/welcome/overview">▶ Watch the 60-second demo</a> · <a href="https://sam-dumont.github.io/immich-video-memory-generator/docs/create/first-memory">Make your first memory</a></sub>
</p>

**Why:** you left Google Photos for Immich and lost the year-in-review / trip / "your kid's year" videos. This brings them back: on your hardware, with clips you can veto and music that isn't canned. LLM titles and AI music are optional extras; the core pipeline runs on CPU.

---

## Docker (recommended for self-hosters)

```bash
curl -O https://raw.githubusercontent.com/sam-dumont/immich-video-memory-generator/main/docker-compose.yml
export IMMICH_URL="http://your-immich-server:2283"
export IMMICH_API_KEY="your-api-key"
docker compose up -d     # then open http://localhost:8080
```

> **The compose file publishes port 8080 on localhost only.** Authentication is disabled by
> default, and the app holds an Immich API key to your whole library — anyone who can reach the
> port can use it. To get to the UI from another machine, enable
> [authentication](https://sam-dumont.github.io/immich-video-memory-generator/docs/deploy/configuration/authentication)
> first, then change the mapping to `"8080:8080"`. The UI is single-user, single-replica; run one
> instance.

### Resource Requirements

Time depends mostly on whether analysis runs on a GPU/Apple Silicon or a CPU-only box. Results are
cached, so the first run of a library is the slow one.

| Phase | RAM | CPU | Apple Silicon / GPU | CPU-only (4-core NAS class) |
|-------|-----|-----|---------------------|-----------------------------|
| Idle (UI) | ~100MB | minimal | — | — |
| Analyzing clips (first run) | 2-4GB | 2+ cores | ~1 min per 10 clips | ~1-2 min per clip |
| Assembling 1080p | 4GB | 4 cores | ~2 min per 5 min of output | ~10-16 min for a 14-clip monthly (measured) |
| Assembling 4K | 6-8GB | 4+ cores | ~5 min per 5 min of output | not recommended |

Most of that assembly time is the title screens, not the encode: measured at 2 CPUs, title
rendering took ~263 s of a ~339 s assembly, so read
[CPU-Only Mode](https://sam-dumont.github.io/immich-video-memory-generator/docs/deploy/hardware/cpu-only)
before you buy a GPU for the encoder.

Measured once for calibration (2026-08-18): a 14-clip monthly at 1080p, cold cache, in the Docker
image with `--cpus=4 --memory=4g` and no GPU took **10 min with `preset: fast`** and 15.7 min with
the default profile (4 M5 Max cores; a Celeron-class NAS is 2-3× slower). `preset: fast` swaps in
1080p H.264, a fast encoder, static titles, no speech pass and favorites-first analysis; explicit
settings still win over it. The
[NAS-only guide](https://sam-dumont.github.io/immich-video-memory-generator/docs/deploy/common-setups/nas-only)
has the Celeron-class table. Field reports from Synology, Unraid, Proxmox and Raspberry Pi are
welcome: [open an issue](https://github.com/sam-dumont/immich-video-memory-generator/issues).

## Without Docker

```bash
uvx immich-memories --help          # no clone needed

mkdir -p ~/.immich-memories
cat > ~/.immich-memories/config.yaml << EOF
immich:
  url: "https://photos.example.com"
  api_key: "your-api-key-here"
EOF

immich-memories ui                  # web wizard on http://localhost:8080
immich-memories generate --year 2024 --person "John" --output ~/Videos/john_2024.mp4
```

### Supported Immich Versions

Immich Memories supports **Immich v2 and v3**, detected at runtime:

```yaml
immich:
  api_version: auto  # auto | v2 | v3
```

Leave this on `auto`. The app detects the server major version and uses the matching API contract;
you do not choose a version for each run. The explicit `v2` and `v3` values are manual
troubleshooting overrides: escape hatches for proxies or unusual deployments that hide or rewrite
the version endpoint. They force that contract, so don't use them as upgrade flags.

`immich-memories config test` reports the detected contract and checks your credentials without
generating or uploading anything.

### Optional: an LLM for clip analysis

Everything runs on your own hardware by default: analysis, encoding, titles, music. The LLM below
is the one piece you can point somewhere else, and it speaks any OpenAI-compatible endpoint. That
path exists for people who don't have the hardware or the patience to run a local model, not
because the tool needs a cloud.

Developed and tested against Qwen3.6-27B and Qwen3.6-35B-A3B (vision is built into the Qwen3.x
models — no `-VL` variant to find).

```yaml
# In ~/.immich-memories/config.yaml
advanced:
  llm:
    provider: "openai-compatible"
    base_url: "http://your-llm-server:8000/v1"
    model: "mlx-community/Qwen3.6-27B-8bit"
```

## What it does

- Scores every clip on faces (35% of the weight), motion, camera stability and audio, then keeps
  the best ~5 seconds of a 45-second recording instead of all 45. LLM scene understanding is an
  optional fifth signal.
- 11 memory types: year in review, monthly, person spotlight, multi-person, season, on this day,
  holiday, then-and-now, trip (GPS-detected, with an animated satellite map fly-over), album, and
  special day — a day the library itself flagged, found by `discover-days` rather than asked for.
  The wizard shows 12 cards: those eleven plus Custom.
- Photos share one selection pool with videos: Ken Burns, face-aware pan, blurred fill behind
  anything that doesn't fill the frame. Live Photos are scored like any other clip.
- Title screens with satellite map fly-overs, month dividers and particles, GPU-rendered through
  Taichi (static PIL titles without it). This is what makes the output look edited, not concatenated.
- Music: bring your own file, use the 28 bundled tracks (the `music` extra, already in the Docker
  image), or generate with ACE-Step or MusicGen. Ducking drops the music when someone talks.
- Runs as a 4-step web wizard (basic auth, OIDC/SSO, or a trusted header proxy) or a headless CLI,
  in Docker, Kubernetes or a plain venv. Privacy mode blurs and mutes everything for demos.

## Daily automation

Schedule one daily `immich-memories auto run`. It retries the oldest pending Immich upload if a
finished video still needs delivering, otherwise it generates a single eligible memory: never
several in one invocation. It ends `skipped`, `dry_run`, `completed` or `failed`, only `failed`
exits non-zero, and `--quiet` gives a scheduler stable JSON to read. In Docker skip cron entirely:
`IMMICH_MEMORIES_AUTOMATION__ENABLED=true` (plus `…__DAILY_AT=09:00`) and the UI process runs that
same decision once a day. The variety rules that stop it repeating itself are in the
[auto CLI docs](https://sam-dumont.github.io/immich-video-memory-generator/docs/create/cli/auto).

## Documentation

The [full documentation](https://sam-dumont.github.io/immich-video-memory-generator/) covers
installation (Docker, uv/pip, Kubernetes, Terraform), the web UI walkthrough, the
[CLI reference](https://sam-dumont.github.io/immich-video-memory-generator/docs/reference/cli-reference),
every [config key](https://sam-dumont.github.io/immich-video-memory-generator/docs/deploy/configuration/config-file),
hardware acceleration, [audio and music](https://sam-dumont.github.io/immich-video-memory-generator/docs/create/pipeline/audio-and-music)
and per-setup recipes.

## How the maintainer runs it

```mermaid
graph LR
    IM["Immich Memories<br/>Python + FFmpeg, Apple Silicon Mac"]
    LLM["omlx / mlx-vlm<br/>local vision LLM, same Mac"]
    ACE["ACE-Step 1.5<br/>in-process, or a GPU box / K8s"]
    MG["MusicGen API<br/>fallback"]
    Immich["Immich v2 or v3<br/>Synology NAS"]

    IM -->|"download clips"| Immich
    IM -->|"clip scoring"| LLM
    IM -->|"background music"| ACE
    ACE -.->|"fallback"| MG
    IM -->|"upload back (optional)"| Immich
```

*One example, not a requirement. Both the LLM ([omlx](https://github.com/nicepkg/omlx)) and the music
generator are optional: without them you get template titles and your own music, or silence.*

## Development

`make dev` installs everything, `make ci` runs the full pipeline, `make help` lists the rest.
Guidelines in [CONTRIBUTING.md](CONTRIBUTING.md).

## Built with AI

> This entire codebase was written with AI (Claude) as an experiment in building complex
> software cleanly with AI assistance. 5,600+ tests (5,000+ unit, 600+ integration/E2E),
> 20 static analysis gates in CI (15 quality, 5 security), 300+ source modules.
> See [DISCLAIMER.md](DISCLAIMER.md) for the full story.

## License

MIT License, see [LICENSE](LICENSE) for details.
