- 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
178 lines
7.0 KiB
Markdown
178 lines
7.0 KiB
Markdown
# 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.
|
|
|
|
[](https://github.com/actions/workflows/test.yml)
|
|
[](LICENSE)
|
|
|
|
## 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 `extra` or `commentary` with 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 `SxxExx` naming convention
|
|
- **Audit logging** — every move/rename writes a `.audit` entry 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.
|
|
|
|
```bash
|
|
npm install
|
|
cp .env.example .env # add your TVDB_API_KEY
|
|
npm start
|
|
```
|
|
|
|
### Rust core
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
# 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`:
|
|
|
|
```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
|
|
|
|
```bash
|
|
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](https://docs.jellyfin.org/):
|
|
`behind the scenes`, `deleted scenes`, `interviews`, `scenes`, `samples`,
|
|
`shorts`, `featurettes`, `clips`, `other`, `extras`, `trailers`,
|
|
`theme-music`, `backdrops`.
|
|
|
|
## Documentation
|
|
|
|
- [Project overview](docs/PROJECT_OVERVIEW.md)
|
|
- [Feature matrix](docs/FEATURES.md)
|
|
- [Rust implementation notes](docs/RUST_IMPLEMENTATION.md)
|
|
- [Folder navigation design](docs/FOLDER_NAVIGATION_PLAN.md)
|
|
- [iOS plan](docs/iOS_PLAN.md)
|
|
|
|
## License
|
|
|
|
[MIT](LICENSE)
|