- clap-based subcommands over the existing service layer - map: Jellyfin SxxExx naming with auto episode numbering, --dry-run - tag/untag: persisted to .moviemapper.json in the media directory - move: relocates tagged files into Jellyfin extras folders, --dry-run - tvdb: search/show via TheTVDB v4 API - audit: pretty-prints the .audit JSON log - config: ~/.config/movemapper/config.toml (flag > env > file precedence) - fix: generate_jellyfin_filename missing dot before extension - 10 new unit tests; workspace tests green
MovieMapper
Desktop application for organizing and managing movie and TV show collections. Scan media directories, extract metadata with FFmpeg, tag files as extras or commentary, and move them into Jellyfin-compatible folder structures — with a full audit trail.
Features
- Directory scanning — non-recursive folder browsing with real-time progress
- Metadata extraction — duration, video quality, and FPS via
ffprobe - File tagging — mark files as
extraorcommentarywith visual state - Jellyfin-compatible moves — tagged files move into spec-compliant folders
(
extras/,commentary/,behind the scenes/, ...) - TheTVDB integration — search shows, browse seasons/episodes, map episodes
to
SxxExxnaming convention - Audit logging — every move/rename writes a
.auditentry in the target directory; nothing happens silently - Progress reporting — IPC-driven progress updates during scans
Architecture
Three implementations share the same core concepts (scanner → metadata → tagging → mapping → audit):
┌─────────────────────────────────────────────────────────────┐
│ MovieMapper │
├──────────────┬──────────────────────────┬───────────────────┤
│ Electron │ Rust core (crate) │ iOS (Swift) │
│ desktop app │ movie_mapper │ companion client │
│ ├──────────────────────────┤ │
│ main.js │ model/ Show, Season, │ SharedModels/ │
│ renderer.js │ Episode │ Swift mirrors │
│ index.html │ service/ │ │
│ utils/ │ FileScanner │ build.sh │
│ │ MetadataExtractor │ archive.sh │
│ Electron │ TVDBClient │ Xcode project │
│ IPC bridge │ FileMapper │ │
│ │ TagManager │ │
│ │ AuditLogger │ │
└──────────────┴──────────────────────────┴───────────────────┘
| Component | Stack | Role |
|---|---|---|
main.js, renderer.js |
Electron (Node 20) | Desktop UI, IPC handlers, filesystem ops |
utils/fileUtils.js |
Node + fluent-ffmpeg | Directory scanning, ffprobe metadata |
Rust/ (crate movie_mapper) |
Rust, tokio, reqwest | Reusable core: scanner, TVDB client, mapper, tags, audit |
backend/ (crate) |
Rust | Async backend integration layer |
ui/ (crate) |
Rust, Iced | Native Rust UI prototype |
SharedModels/ |
Swift | Data models for the iOS companion |
Data flow
directory scan ──▶ ffprobe metadata ──▶ tag (extra/commentary)
│
▼
Jellyfin folder mapping
│
▼
.audit log entry per operation
Project structure
├── main.js # Electron main process (IPC, file ops, TVDB)
├── renderer.js # Renderer process (UI state, events)
├── index.html # UI (dark theme)
├── preload.js # Context bridge
├── utils/fileUtils.js # Directory scan + ffprobe metadata
├── tests/ # vitest suite (118 tests)
│ └── legacy/ # Earlier node:test integration tests
├── Rust/ # Rust core crate (movie_mapper)
│ ├── src/service/ # Scanner, TVDB client, mapper, tags, audit
│ ├── src/model/ # Show, Season, Episode, MediaFile
│ └── tests/ # Unit, integration, e2e, benchmarks
├── backend/ # Rust backend integration crate
├── ui/ # Rust Iced UI prototype
├── SharedModels/ # Swift models (iOS companion)
└── docs/ # Design docs, phase summaries, plans
Getting started
Desktop app (Electron)
Prerequisites: Node.js 20+, npm, ffmpeg/ffprobe on PATH.
npm install
cp .env.example .env # add your TVDB_API_KEY
npm start
Rust core
cargo build --workspace
cargo test --workspace
Rust CLI
The movie_mapper binary exposes the core as a command-line tool for
scripting and headless use:
# List media files with metadata
movie-mapper scan /path/to/season [--json]
# Rename files to Jellyfin naming (Show S01E01 - Quality.ext)
movie-mapper map /path/to/season --show "The Show" --season 1 [--episode-start 1] [--dry-run]
# Tag / untag files (persisted to .moviemapper.json)
movie-mapper tag /path/to/media "behind the scenes"
movie-mapper untag /path/to/media "behind the scenes"
# Move tagged files into the Jellyfin folder for that tag
movie-mapper move /path/to/media "behind the scenes" [--dry-run]
# TheTVDB (requires TVDB_API_KEY)
movie-mapper tvdb search "Breaking Bad"
movie-mapper tvdb show 81189
# Show the audit log for a directory
movie-mapper audit /path/to/season
All destructive commands support --dry-run for a filesystem-safe preview.
Every rename and move writes an entry to the directory's .audit log.
Configuration is read from ~/.config/movemapper/config.toml:
tvdb_api_key = "your-api-key"
media_dir = "/path/to/media"
Precedence for the API key: --tvdb-key flag > TVDB_API_KEY env var > config file.
Tests
npm test # vitest suite (JS)
cargo test --workspace # Rust unit + integration
Configuration
| Variable | Required | Description |
|---|---|---|
TVDB_API_KEY |
for TVDB search | TheTVDB v4 API token |
Jellyfin folder compatibility
Moves target the Jellyfin extras folder specification:
behind the scenes, deleted scenes, interviews, scenes, samples,
shorts, featurettes, clips, other, extras, trailers,
theme-music, backdrops.