Codebase architecture
How the code is organized and where to make changes.
Runtime boundaries
The web UI and terminal use the same CLI workflow: choose a cut → render it → optionally upload it. A render consumes the chosen cut; it does not choose footage again. The store keeps reusable facts, settings and history; per-attempt records explain the decisions in one cut.
Data and service boundaries
Explore the deployment and processing phase below. Select a service for its data and runtime responsibilities. These are service boundaries, not a promise about GPU placement.
Your Immich
Immich Memories
Your browser
Metadata + previewsPreparation starts with your library; originals remain in Immich.
Browser → appUI requests and media go through the app, behind the same login.
The default film needs no model server or GPU. The app keeps the store and outputs; Immich keeps your originals. Select a service for details.
The text reader can be an app-owned local process or an API server. Captioning is a separate role; explicitly enabling LLM captions sends pictures to that configured model. A render worker additionally receives the chosen cut and Immich key, then fetches originals. Keep these services inside the network boundaries described in Privacy.
Composition over inheritance
The four main orchestrators compose smaller service objects through constructor injection. The
lifecycle a run reports is the OperationalPhase enum in operations/phases.py (discovery →
download → analysis → selection → render → music → delivery → complete), and it spans two entry
points. Selection runs first: generate (the web UI's Cut runs the same command) →
build_smart_pipeline(editorial_context) in analysis/editorial_runtime.py →
SmartPipeline.run_editorial_source() → RuntimeEditorialPlanner.plan_source(), which runs
preparation, the two readings, the structure and story planners, and certifies the timing.
generate_memory() in generate.py then does extract → assemble → music → upload. Hand it no
clips and it raises rather than going to find some.
| Orchestrator | Services | What it does |
|---|---|---|
| VideoAssembler | FFmpegProber, ClipEncoder, AssemblyEngine, AudioMixerService, TitleInserter | Assembles clips into final video |
| SmartPipeline | RuntimeEditorialPlanner (from build_smart_pipeline) | Runs the story-first selection and projects its plan into a PipelineResult |
| ImmichClient | SearchService, AllAssetsService, AssetService, PersonService, AlbumService | Talks to the Immich API |
| TitleScreenGenerator | RenderingService, EndingService, TripService | Creates title/ending screens |
The editorial route has Protocol-typed ports rather than services: the providers and the people
loader, the structure planner, the judges it calls out to, and EditorialAttempt in operations/
for the durable attempt tree and its OS lease. On disk each attempt is
<cache>/editorial-runs/<key>/attempts/<id>/, and the banked facts and
answers live in the store (the db/ package, tables in db/tables/annotations.py and
db/tables/model_answers.py).
ARCHITECTURE.md
names every port and the file it lives in, with the full module map.
Verification
CI and quality gates explain which checks run for each kind of diff. Testing covers local suites.
How to add a feature
A new processing capability
- Create a service class in the relevant package (for example
processing/my_service.py) - Inject it into the orchestrator's
__init__invideo_assembler.py - Add tests in
tests/test_my_service.py
A new API endpoint
- Add the method to the relevant service in
api/(for examplesearch_service.py) - Add a delegating method on
ImmichClientinapi/immich.py - Add the model to
api/models.pyif needed, and test against a mock HTTP client
A new memory type
- Add the value to the
MemoryTypeenum inmemory_types/registry.py - Write a factory function in
memory_types/factory.pyand decorate it with@register_preset: the decorator is the registration, there is no second list to edit there - Add date builder logic if the type needs its own, in
memory_types/date_builders.py - Add it to
OFFERED_MEMORY_TYPESinmemory_types/registry.pyfor--memory-type. Add its fields toFIELDSinweb/src/routes/create/+page.sveltetoo: the Memory page has its own map and does not discover presets from the Python tuple - Document it in
docs-site/docs/make/memory-types.mdx
A new CLI command
- Create a new file in
cli/(for examplecli/my_cmd.py) with aregister_my_commands(main)function, likecli/hardware_cmd.py - Import it and call it with the others at the bottom of
cli/__init__.py - Run
make docs-clito regenerate the CLI reference:make docs-cli-checkfails CI until you do - Add the docs page under
docs-site/docs/make/cli/, add its ID todocs-site/sidebars.ts, and runmake docs-build
File naming conventions
_prefixed.py: private helpers for their own package. Keep imports inside the owning package; public package entry points expose shared behavior*_service.py: composed service classes*_models.py: data models (Pydantic or dataclass)*_helpers.py: standalone helper functions*.py(no prefix): public modules and standalone classes. Re-export shims belong in__init__.pyand nowhere else