A deterministic, privacy-first generator for turning a folder of photos, videos, and music into a polished MP4 slideshow.
Slideshow analyzes media locally, orders it chronologically, creates collages, protects faces from aggressive cropping, selects short video excerpts, detects the soundtrack beat grid, builds an auditable timeline, renders it with Remotion, and finishes the result with FFmpeg.
Your media stays on your machine. The project does not upload photos, videos, manifests, or audio.
- Chronological ordering using manual dates, EXIF, video metadata, filenames, and filesystem dates.
- Photos and videos mixed in the same timeline.
- Face-safe framing: the sharp foreground is shown completely over a blurred full-frame background.
- Deterministic layouts, movement, timing, and transitions controlled by a seed.
- Six flicker-safe transition families: wipe, slide, geometric mask, shared motion, photo stack, and zoom-through.
- Optional beat analysis with selected scene changes and accents aligned to the soundtrack.
- Automatic collages, chapters, date labels, captions, safe areas, and readable text overlays.
- Exact duration budgeting down to the frame.
- Full HD, 4K, portrait, square, or custom output dimensions.
- Generated manifest and reports for auditing every creative decision.
- Synthetic demo media, so the project can be evaluated without personal photos.
project.json + local media
│
▼
metadata, dates, normalization, beat analysis
│
▼
output/project-manifest.json
│
▼
deterministic timeline + React/Remotion frames
│
▼
FFmpeg audio mix, fades, loudness, and MP4 output
Creative decisions are made during analyze, recorded in the manifest, and reused during
render. Original media is never modified.
- Node.js 22 or newer
- pnpm 11 or newer
- FFmpeg and FFprobe available in
PATH - macOS or Linux
Windows should work when the native Sharp and Remotion dependencies are available, but it is not currently part of CI.
git clone https://github.com/feliperun/slideshow.git
cd slideshow
pnpm install
cp project.example.json project.json
mkdir mediaAdd photos and videos to media/, add a soundtrack as media/soundtrack.mp3, then run:
pnpm validate --project ./project.json
pnpm analyze --project ./project.json
pnpm editor --project ./project.json
pnpm preview --project ./project.json
pnpm render --project ./project.jsonThe final file is written to the path configured in output.file.
project.json, *.edits.json, media/, photos/, output/, and .slideshow-cache/ are ignored
by Git to reduce the risk of publishing private material.
The demo creates abstract placeholder images and a synthetic audio track locally:
pnpm demo:generate
pnpm analyze --project ./examples/family/project.json
pnpm preview --project ./examples/family/project.json
pnpm render --project ./examples/family/project.jsonNo personal media or network download is involved.
| Command | Description |
|---|---|
pnpm validate --project FILE |
Validate configuration and referenced files |
pnpm analyze --project FILE |
Analyze media and create the manifest and reports |
pnpm editor --project FILE |
Open the local manual framing editor |
pnpm preview --project FILE |
Open the composition in Remotion Studio |
pnpm render --project FILE |
Render the existing manifest to MP4 |
pnpm render --project FILE --rebuild-manifest |
Analyze again, then render |
pnpm render:thumbnail --project FILE |
Render a JPEG thumbnail |
pnpm render:scene --project FILE --scene scene-0012 |
Render one scene |
pnpm clean-cache --project FILE |
Remove normalized media and render chunks |
pnpm demo:generate |
Generate local synthetic demo assets |
pnpm check |
Run types, tests, lint, and the editor build |
Start with project.example.json:
{
"id": "family-memories",
"title": "Our Story",
"subtitle": "A collection of favorite memories",
"closingMessage": "Here is to many more memories!",
"photosDirectory": "media",
"targetDurationSeconds": 90,
"fps": 30,
"width": 1920,
"height": 1080,
"theme": "warm-memory",
"seed": "family-memories-2026",
"music": {
"file": "media/soundtrack.mp3",
"volume": 0.7,
"fadeInSeconds": 2,
"fadeOutSeconds": 4,
"normalizeLoudness": true
},
"output": {
"file": "output/slideshow.mp4",
"quality": "high"
},
"photos": {}
}Recommended dimensions:
| Format | Dimensions |
|---|---|
| Full HD | 1920 × 1080 |
| 4K | 3840 × 2160 |
| Portrait / Stories | 1080 × 1920 |
| Square | 1080 × 1080 |
| Fast preview | 960 × 540 |
Use the original filename as the key:
{
"photos": {
"2024-05-12-birthday.jpg": {
"date": "2024-05-12T14:30:00-03:00",
"caption": "A special afternoon",
"location": "Florianópolis",
"priority": 1.8,
"layout": "hero",
"focus": { "x": 0.52, "y": 0.38 },
"rotation": -2,
"hero": true,
"allowCollage": false
},
"2024-06-20-playground.mov": {
"videoStartSeconds": 2.5,
"videoEndSeconds": 7,
"caption": "At the playground"
},
"duplicate.jpg": {
"include": false
}
}
}Available overrides include:
- manual date and ordering;
- caption, location, people, priority, and chapter;
- layout, fit, focus point, rotation, scale, and movement;
- fixed scene duration and transition;
- inclusion, hero status, and collage permission;
- video excerpt start and end times.
The automatic timeline remains responsible for dates, transitions, collages, duration, and beat synchronization. The local Framing Editor adds a human-controlled layer for the decisions that benefit most from visual sensitivity:
pnpm analyze --project ./project.json
pnpm editor --project ./project.jsonThe editor opens in the browser and uses the same Remotion composition as the final MP4. Select a scene and a photo, then:
- drag the photo to move its focus;
- use the zoom slider to emphasize the important subject;
- switch between showing the complete photo and filling its frame;
- hold
Shiftwhile dragging to move the entire frame; - drag a corner handle to resize the frame;
- adjust rotation or restore the automatic framing.
Changes are saved automatically beside the project file. For example, project.json writes
project.edits.json, while project.local.json writes project.local.edits.json. These sidecar
files are ignored by Git by default and are applied automatically by preview, render,
render:thumbnail, and render:scene.
Re-running analyze does not erase manual work. Edits are attached to the scene assets and can be
reapplied when scene numbering changes but the same group of media remains together.
Date resolution uses this priority:
- manual override;
- EXIF
DateTimeOriginal; - EXIF
CreateDate; - reliable video creation metadata;
- date embedded in the filename;
- file creation time;
- file modification time;
- alphabetical fallback.
Recognized filename forms include:
2024-05-20-photo.jpgIMG_20240520_143500.jpg20240520_143500.jpg20-05-2024.jpg2024_05_20_event.jpg
Suspicious video import dates are reported instead of silently replacing an older filesystem date.
Photo frames use two layers:
- a blurred, enlarged background that fills the frame;
- a sharp foreground rendered with
contain, preserving the complete image.
Automatic pan and zoom are applied only to the decorative background layer. The foreground remains intact, so faces near an edge are not lost. The manual framing editor can deliberately zoom or crop the foreground when a human chooses a more expressive composition.
The analyzer decodes the beginning of the soundtrack locally, builds an onset envelope, estimates a tempo and phase, and records a deterministic beat grid in the manifest.
Only selected nearby transitions are snapped to beats. This keeps the timeline musical without making every cut feel mechanical. Beat-aligned scenes may receive a short decorative accent.
The analysis is intentionally lightweight and dependency-free. It works best with music that has a clear rhythmic pulse.
Built-in themes:
warm-memoryclean-cinematicplayful-celebrationelegant-eventtravel-journal
Built-in layouts include portrait, landscape, square editorial, polaroid, photo stack, split screen, collage, hero, timeline strip, album page, and video editorial.
Transition variants avoid full-frame opacity animation because that can produce tiled compositor artifacts in some Chromium/Remotion combinations. New transitions should be tested as encoded video, not only as still frames.
Run the internal transition validation after changing composition behavior:
pnpm exec tsx scripts/validate-transitions.tsThe target is round(seconds × fps) frames. Since adjacent scenes overlap during transitions:
total = fixed frames + scene frames - transition frames
The allocator starts at every scene minimum, distributes remaining frames by weight, respects maximums, and applies integer-frame correction. Intro, outro, manual durations, and video excerpts are fixed. Analysis fails with a clear error if the requested duration is impossible.
.slideshow-cache/ stores:
- normalized and auto-oriented images;
- thumbnails and blurred backgrounds;
- transcoded video excerpts;
- reusable render chunks.
output/ stores:
project-manifest.json— source of truth for rendering;composition-report.json— machine-readable audit;composition-report.md— human-readable timeline report;- final MP4 and thumbnail files.
Both directories are ignored by Git.
src/
analysis/ metadata, dates, hashes, beat detection, image and video processing
cli/ command-line interface
config/ project loading and validation
editor/ local framing editor, API, and manual-edit persistence
manifest/ manifest and report generation
remotion/ compositions, scenes, media frames, and decorations
rendering/ Remotion bundle, chunk rendering, and FFmpeg finalization
schemas/ Zod schemas and TypeScript types
themes/ visual design tokens
timeline/ grouping, transitions, beat alignment, and duration allocation
utils/ deterministic random, files, frames, and logging
scripts/ demo and visual validation helpers
tests/ unit tests
Before publishing a fork or sending a pull request:
git status --ignored
git grep -n -i "private-name-or-path"Never force-add ignored media. If personal material was accidentally committed, removing the file in a later commit is not sufficient; rewrite the Git history before publishing.
See SECURITY.md for private vulnerability reporting.
Contributions are welcome. Read CONTRIBUTING.md, run pnpm check, and include
encoded before/after samples for visual changes.