- Rust 90.8%
- CSS 8.5%
- Just 0.4%
- Dockerfile 0.3%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| src | ||
| .dockerignore | ||
| .gitignore | ||
| AGENTS.md | ||
| build.rs | ||
| Cargo.lock | ||
| Cargo.toml | ||
| data.db | ||
| Dockerfile | ||
| justfile | ||
| README.md | ||
manga-mover
Organizes single-file manga archives (.cbz, .zip, etc.) from a source directory tree into a sorted destination directory. Filenames are matched against hardcoded regex patterns to extract metadata (chapter, episode, volume, title). State is tracked in SQLite. Uses axum + maud + datastar for a server-rendered web UI.
Setup
Requires Rust 1.93+.
just build
Configuration
Create a TOML config file:
source = "/path/to/unsorted"
dest = "/path/to/sorted"
db = "/path/to/manga-mover.db"
# Optional: background scan interval in seconds (default 300, 0 to disable)
scan_interval_secs = 300
# Optional: auto-move files during background scans (default false = dry-run only)
auto_move = false
Set MANGA_MOVER_CONFIG to the config path, or place it at /config.toml.
export MANGA_MOVER_CONFIG=./config.toml
cargo run
Usage
Open http://127.0.0.1:3000 in a browser.
Workflow
- Import a manga from
/ui/untracked— pick a detected source subdirectory, give it a name and destination subdirectory. - Edit manga metadata on the detail page (
/ui/mangas/:id) — adjust the name, source/dest paths, or toggle volume numbering. - Sort at
/ui/mangas/:id/sort(per-manga) or/ui/sort(global) — preview what would be moved, then confirm. - Use Refresh on
/uito recompute stats without waiting for the background scanner.
Filename patterns
Patterns are hardcoded in src/logic/source_pattern.rs. They use regex to extract metadata from filenames. Examples of supported filename formats:
Episode 42.cbz(S2) Ep. 69 - Servant of the Aesir (ch. 151).cbzChapter 1.cbzWebToon_Chapter 3_ Using My Clone (1).cbzVol. 3 Ch. 25.cbzManga_v1_ch01.cbz
If a file doesn't match any pattern, it is flagged as unsortable in the UI.
Dest naming convention
Destination filenames follow a fixed convention based on the manga's with_volumes setting:
- Without volumes (default):
{name} {chapter}.cbz— e.g.Berserk 314.cbz - With volumes:
{name} v{volume} {chapter}.cbz— e.g.Berserk v3 25.cbz
If the pattern extracts a title, it is appended: {name} {chapter} {title}.cbz.
Season tracking
Manga entries track a current_season (default 1). Season markers in source filenames can trigger season changes:
- set — sets the season to a specific value
- increment — increments the season by 1
Files are processed in alphabetical order, so season markers should sort before the chapters they affect.
Anomaly categories
Each file in a manga's source or dest directory is classified:
| Category | Meaning |
|---|---|
| sorted | Already moved to dest and matches expected naming |
| unsorted | In source, matched a pattern, pending move |
| unsortable | In source, didn't match any pattern |
| dest-anomaly | In dest, doesn't match expected naming convention |
| duplicate | Multiple files in source match to the same dest path |
Development
just run # build CSS and run with local test config
just test # run tests
just clippy # run clippy lints
just check # build CSS + clippy + tests (pre-commit gate)
just build # release build
just css # rebuild min.css from src/ui/css/*.css