Problem Statement
I want to turn folders of personal photos and videos into polished slideshow videos, the way Apple Photos "Memories" does — but Apple's tool decides too much for me. I can't properly control which media is included, the order, the pacing, the music, or the transitions. Other editors (iMovie, CapCut) offer control but demand timeline-editing labor for what should be a "arrange and play" experience. I want hand-curated content with automatic, Apple-grade presentation: I pick and order the media, the app makes it beautiful, and everything the app decides is overridable.
Solution
A local-first web app (Chromium, installable as a PWA) where a Project is a folder on disk. The user points the app at a folder; all photos/videos inside become the media bin, and the app writes a slideshow.json (timeline + settings) into that same folder. The user arranges media in a storyboard grid, picks music via in-app Jamendo search (downloaded into the folder) or any audio file already in the folder, and the app produces a beat-synced, Ken Burns–animated slideshow rendered live by a Remotion composition in a preview pane. A settings cascade (global defaults → per-slide overrides) plus Theme presets makes every automatic decision customizable, down to a fully plain mode (no transitions, fixed duration per image, videos play as-is). Export to .mp4 is deliberately deferred; the Remotion architecture pre-solves it.
User Stories
- As a user, I want to point the app at a local folder, so that everything in it becomes my slideshow's media bin without uploading anything anywhere.
- As a user, I want the app to save my timeline and settings as
slideshow.json inside the project folder, so that the project is a self-contained, syncable, backup-able folder.
- As a user, I want the app to remember my recent project folders and re-open them with minimal permission friction, so that returning to a project tomorrow "just works".
- As a user, I want new files dropped into the project folder to appear in the media bin on refresh, so that adding media is as easy as using Finder/Explorer.
- As a user, I want to drag-and-drop additional files into the app window and have them copied into the project folder, so that I never have to think about where media lives.
- As a user, I want to see my media as a reorderable storyboard grid of thumbnails, so that I can arrange the sequence the way I think about it — a series of moments.
- As a user, I want to include or exclude any media file from the timeline without deleting it from the folder, so that curation is non-destructive.
- As a user, I want both photos and videos to be first-class slides in the same sequence, so that my slideshows mix media like Apple Memories does.
- As a user, I want to add title/text slides (e.g. a trip name to open the show), so that slideshows have narrative framing.
- As a user, I want global default settings (slide duration, transition type, Ken Burns on/off, fit mode), so that I configure the look once, not per slide.
- As a user, I want to override any global setting on an individual slide, so that one odd photo doesn't force me to change the whole show.
- As a user, I want Theme presets (Classic, Energetic, Plain) that bundle settings, so that I can get a coherent look in one click.
- As a user, I want a Plain mode — no transitions, no motion, each image shown X seconds, videos play untouched — so that I can produce a no-frills slideshow when that's the right tone.
- As a user, I want to search open-licensed music by mood/genre/keyword inside the app (Jamendo), so that I can find a soundtrack without leaving the editor.
- As a user, I want to preview streaming search results before choosing, so that I can audition tracks quickly.
- As a user, I want a chosen track downloaded into the project folder, so that the slideshow keeps working even if the source link rots.
- As a user, I want any audio file already present in the project folder to be offered as a soundtrack option, so that I can source music any way I like.
- As a user, I want slide transitions nudged to land on the music's beats, so that the slideshow feels edited to the soundtrack like Apple Memories.
- As a user, I want an Energy setting controlling how many beats each slide holds, so that I can make the same show feel calm or punchy.
- As a user, I want beat sync to use my target durations as the baseline (nudged, not hard-quantized), so that slides keep roughly even screen time.
- As a user, I want to manually correct tempo/offset (e.g. tap-along) when automatic detection gets it wrong, so that a bad analysis never blocks me.
- As a user, I want to turn beat sync off entirely, so that durations are exactly what I set.
- As a user, I want background music to duck to ~20% while a video clip's own audio plays, so that voices in clips remain the point of including them.
- As a user, I want per-slide audio overrides (mute video audio / mute music / mix levels), so that each clip can be handled appropriately.
- As a user, I want photos cropped to fill the frame by default, with Ken Burns motion making the crop feel intentional, so that portrait phone photos look cinematic in a 16:9 show.
- As a user, I want per-slide fit overrides (letterbox, blur-fill), so that an important photo is never cropped against my will.
- As a user, I want videos fitted (letterboxed), never cropped, so that the original framing of clips is preserved.
- As a user, I want Ken Burns direction to auto-alternate between consecutive slides, so that motion never feels repetitive.
- As a user, I want a 16:9 1080p canvas by default with aspect ratio selectable per project, so that I can later make 9:16 versions for phones.
- As a user, I want a live preview pane that plays the slideshow exactly as the final render would look, so that what I see is what I get.
- As a user, I want playback controls (play/pause, scrub, jump to slide), so that I can review specific moments quickly.
- As a user, I want clicking a slide in the grid to open its settings (duration, transition, motion, fit, audio), so that per-slide control is one click away.
- As a user, I want the app to detect missing/renamed media files on project open and tell me which slides are affected, so that breakage is visible and fixable rather than silent.
- As a user, I want my edits autosaved to
slideshow.json, so that closing the tab never loses work.
- As a user, I want to install the app as a PWA, so that folder permissions persist and it feels like a desktop app.
- As a user, I want full-screen playback, so that I can present the slideshow on a TV via AirPlay/casting of the browser.
Implementation Decisions
- Platform & stack: Vite + React + TypeScript SPA. No backend, no accounts. Chromium-only (File System Access API); installable as PWA. Rendering/preview via Remotion
@remotion/player. Audio analysis via Web Audio API. Music search via Jamendo API (free tier, client_id).
- Project = folder: the only persistence is the user's folder. Media is referenced by filename within the folder (never copied elsewhere);
slideshow.json stores the timeline, settings cascade, theme, soundtrack reference, and beat-grid corrections. Directory handles are persisted in IndexedDB for re-open; permission re-grant flow handled in Project Store.
- Module architecture (deep modules, pure cores):
- Timeline Core — pure domain model: slides (media + title types), ordering, theme presets, and settings-cascade resolution
(globalSettings, slideOverrides) → ResolvedSlideSettings. No browser APIs.
- Beat Grid — two layers: a thin Web Audio decode adapter, and pure analysis math (onset detection, BPM estimation, beat offsets) plus
nudge(targetDuration, beatGrid, energy) → snappedDuration. Stores manual tempo/offset corrections.
- Sequence Planner — the heart: pure function
(timeline, beatGrid, mediaMetadata) → RenderPlan producing absolute frame positions, per-slide durations, transition windows, alternating Ken Burns vectors, and the music ducking envelope around video-audio segments. All downstream layers execute the plan and make no decisions.
- Project Store — File System Access wrapper: open/enumerate folder, media/audio type detection, read/write
slideshow.json, autosave debounce, missing-file detection, permission lifecycle.
- Composition — thin Remotion layer: components for media slides, title cards, crossfade/dip-to-black/cut transitions, Ken Burns transform, and audio tracks driven by the RenderPlan's ducking envelope.
- Music Search — Jamendo client: query, stream-preview URLs, download-to-folder via Project Store.
- Editor Shell — storyboard grid (drag reorder), settings panels, preview host. Intentionally shallow glue.
- Settings cascade contract: every presentational behavior (duration, transition, motion, fit, audio policy) resolves global → per-slide; Themes are saved settings objects, nothing more. "Plain mode" is the Plain theme, not special-cased code.
- Beat sync policy: target-duration-nudged-to-nearest-beat (never hard quantize); Energy maps to beats-held-per-slide bands. Beat sync is a cascade setting and can be off.
- Audio policy: default duck-to-20% under video audio with attack/release ramps in the ducking envelope; per-slide overrides (mute video / mute music / custom mix).
- Fit policy: photos crop-to-fill default; letterbox and blur-fill as overrides. Videos always fit. Canvas default 1920×1080; aspect is a project setting and compositions are dimension-parametric.
- v1 motion vocabulary: crossfade, dip-to-black, hard cut; Ken Burns zoom in/out + pan with auto-alternation; title slides. Nothing else until three real slideshows have been made.
Testing Decisions
- A good test exercises external behavior through the module's public interface — given inputs, assert outputs/effects — never internal helpers or intermediate state. Pure modules make this cheap.
- Timeline Core: table-driven tests for cascade resolution (global only, override wins, theme application, plain mode), ordering operations, and serialization round-trip of
slideshow.json.
- Beat Grid: tests on the pure math with synthetic beat grids and synthesized click-track buffers (known BPM in → detected BPM/offset out within tolerance); nudge behavior across energy levels and edge cases (duration shorter than one beat, track shorter than show).
- Sequence Planner: the most heavily tested module. Golden-plan tests: fixed timeline + beat grid + metadata in → exact RenderPlan out (frame positions, transition windows, alternating Ken Burns, ducking envelope around video segments). Property tests: total duration consistency, no overlapping non-transition windows, ducking segments exactly cover video-audio spans.
- Project Store: tested against an in-memory fake of the File System Access handle interfaces — folder enumeration, json round-trip, autosave debounce, missing-file detection. No real-FS tests in CI.
- Music Search: tested against recorded/mocked Jamendo responses — query mapping, result parsing, error/empty states, download-to-folder handoff. No live network in tests.
- Composition & Editor Shell: no unit tests; verified by eye via preview. Their correctness is dominated by the RenderPlan, which is tested upstream.
- Prior art: none (greenfield). Establish Vitest as the runner; tests colocated per module.
Out of Scope
- Video export (.mp4) — architecture pre-solves it via Remotion, but no export UI/pipeline in v1.
- Automatic curation: face/scene detection, "best shot" selection, auto-drafting from a photo dump.
- Video trimming (in/out points) — user trims before import; candidate for v2 via per-slide scrubber.
- Cloud provider integrations (Google Photos picker, Dropbox chooser, Drive) — v2; users export to disk for v1.
- In-app paste-a-link audio fetching and any CORS proxy.
- Accounts, sync, sharing, collaboration, hosting of any kind.
- Multiple audio tracks, narration recording, audio crossfading between songs.
- Firefox/Safari support.
- Transition library beyond the v1 vocabulary (wipes, 3D, particles).
Further Notes
- Known risk: onset/BPM detection quality varies by genre (pop/electronic good; sparse acoustic weak). The manual tap-to-correct fallback is therefore required for v1, not a nice-to-have.
- Licensing: personal use keeps music licensing forgiving; Jamendo's terms suit personal/non-commercial use. Revisit before any public release.
- Future-proofing notes: export later means the Composition must remain deterministic — no wall-clock or
Math.random() in render paths; Ken Burns directions and any variation derive from slide index/seed in the RenderPlan. Aspect-ratio parametrization (9:16) should be respected in all Composition components from day one.
- Origin story: spec produced from a grilled design session resolving platform, persistence, rendering, audio sourcing, beat sync, editor model, fit policy, and motion vocabulary one branch at a time.
Problem Statement
I want to turn folders of personal photos and videos into polished slideshow videos, the way Apple Photos "Memories" does — but Apple's tool decides too much for me. I can't properly control which media is included, the order, the pacing, the music, or the transitions. Other editors (iMovie, CapCut) offer control but demand timeline-editing labor for what should be a "arrange and play" experience. I want hand-curated content with automatic, Apple-grade presentation: I pick and order the media, the app makes it beautiful, and everything the app decides is overridable.
Solution
A local-first web app (Chromium, installable as a PWA) where a Project is a folder on disk. The user points the app at a folder; all photos/videos inside become the media bin, and the app writes a
slideshow.json(timeline + settings) into that same folder. The user arranges media in a storyboard grid, picks music via in-app Jamendo search (downloaded into the folder) or any audio file already in the folder, and the app produces a beat-synced, Ken Burns–animated slideshow rendered live by a Remotion composition in a preview pane. A settings cascade (global defaults → per-slide overrides) plus Theme presets makes every automatic decision customizable, down to a fully plain mode (no transitions, fixed duration per image, videos play as-is). Export to .mp4 is deliberately deferred; the Remotion architecture pre-solves it.User Stories
slideshow.jsoninside the project folder, so that the project is a self-contained, syncable, backup-able folder.slideshow.json, so that closing the tab never loses work.Implementation Decisions
@remotion/player. Audio analysis via Web Audio API. Music search via Jamendo API (free tier, client_id).slideshow.jsonstores the timeline, settings cascade, theme, soundtrack reference, and beat-grid corrections. Directory handles are persisted in IndexedDB for re-open; permission re-grant flow handled in Project Store.(globalSettings, slideOverrides) → ResolvedSlideSettings. No browser APIs.nudge(targetDuration, beatGrid, energy) → snappedDuration. Stores manual tempo/offset corrections.(timeline, beatGrid, mediaMetadata) → RenderPlanproducing absolute frame positions, per-slide durations, transition windows, alternating Ken Burns vectors, and the music ducking envelope around video-audio segments. All downstream layers execute the plan and make no decisions.slideshow.json, autosave debounce, missing-file detection, permission lifecycle.Testing Decisions
slideshow.json.Out of Scope
Further Notes
Math.random()in render paths; Ken Burns directions and any variation derive from slide index/seed in the RenderPlan. Aspect-ratio parametrization (9:16) should be respected in all Composition components from day one.