Make the codebase mypy-clean, drop dead media_utils and Pillow, rewrite README
This commit is contained in:
@@ -1,38 +1,63 @@
|
||||
# Curator
|
||||
|
||||
Curator is a desktop **movie-library manager (Filmotéka)**. You import video
|
||||
files into a managed **pool**, tag them, and Curator generates a browsable
|
||||
**Filmotéka** — a directory tree of hardlinks into the pool — without ever
|
||||
duplicating the actual video data on disk.
|
||||
Curator is a PySide6 desktop app for managing a **Jellyfin-first movie and series
|
||||
library**. You import video files into a managed **pool**; Curator identifies them
|
||||
through **TMDb**, names and lays them out the way Jellyfin expects, and can
|
||||
generate a genre-split **miniDLNA / Filmotéka** tree of hardlinks for dumb DLNA
|
||||
players — without ever duplicating the video data on disk.
|
||||
|
||||
Curator is a fork of the former *Tagger* project; the tagging, filtering and
|
||||
hardlink-tree machinery is inherited and extended into a full library workflow.
|
||||
Metadata (posters, plots, `.nfo` files) is Jellyfin's job; Curator owns the files,
|
||||
their names and the library structure.
|
||||
|
||||
## Concepts
|
||||
|
||||
- **Pool** — the managed repository of files and the **single source of truth**.
|
||||
Curator owns it: it imports and removes files itself, so nothing moves behind
|
||||
its back. The pool has two top-level folders:
|
||||
- `Filmy` — movies, organized by tags into the generated tree.
|
||||
- `Seriály` — series (and any other **copy-as-is** folder), mirrored verbatim.
|
||||
- **Filmotéka (output)** — a generated tree of **hardlinks** into the pool. It is
|
||||
fully disposable: delete it and nothing is lost, because it is regenerated from
|
||||
the pool on demand.
|
||||
- **Pool index** — all pool metadata lives in a single `.Curator.!index` JSON
|
||||
file at the pool root (title, ČSFD link, tags, date per file).
|
||||
- **Pool** — the managed folder and the **single source of truth**. Curator
|
||||
imports, renames and removes files itself, so nothing moves behind its back.
|
||||
Each top-level folder is a **library** with a kind:
|
||||
|
||||
## Workflow
|
||||
| Kind | Holds | Layout |
|
||||
|------|-------|--------|
|
||||
| `movie` | movies | `Title (rok) [tmdbid].ext`, or a folder once a film has several versions |
|
||||
| `series` | series | `Series (rok) [tmdbid]/Season NN/SxxEyy - Series.ext` |
|
||||
| `mixed` | movies and series together | as above, side by side |
|
||||
| `homevideo` | files without a TMDb id | mirrored as-is |
|
||||
|
||||
1. Configure the **pool** and the **Filmotéka output** folder.
|
||||
2. **Import a movie**: pick a video, enter its **Title** and a **ČSFD link**. The
|
||||
file is copied (non-destructively) into `pool/Filmy` as `Title.ext` and
|
||||
recorded in the index. If a ČSFD link is given, Curator fetches the movie from
|
||||
[ČSFD.cz](https://www.csfd.cz) and assigns **Žánr / Rok / Země původu** tags
|
||||
automatically (use "Načíst tagy z ČSFD" to (re)fetch later).
|
||||
3. **Tag** movies (Rok, Žánr, Hodnocení, …) and filter them in the UI.
|
||||
4. **Generate the Filmotéka**: movies become a `Category/Tag/film` hardlink tree
|
||||
(from the `Rok` / `Žánr` / `Hodnocení` categories); copy-as-is folders such as
|
||||
`Seriály` are mirrored 1:1 as hardlinks.
|
||||
- **Pool index** — Curator's own metadata (TMDb id, ČSFD link, versions, tags,
|
||||
library settings) lives in one `.Curator.!index` JSON file at the pool root.
|
||||
- **miniDLNA output** — a generated tree of **hardlinks** into the pool, built
|
||||
from the genres/years/ratings in Jellyfin's NFO files. It is disposable: delete
|
||||
it and regenerate it at any time.
|
||||
|
||||
## Features
|
||||
|
||||
- Import movies and episodes with a mandatory **TMDb id**; Czech titles come from
|
||||
TMDb, with **ČSFD** as a fallback and as a bridge for finding the right TMDb id.
|
||||
- **Multiple versions** of one film (2D/3D, 4K, director's cut) and **multi-part**
|
||||
films and episodes, named so Jellyfin groups them.
|
||||
- Subtitle sidecars follow their video through renames.
|
||||
- Duplicate check, video integrity check (FFmpeg), library statistics.
|
||||
- Jellyfin integration: trigger a library scan, read what Jellyfin has.
|
||||
- miniDLNA tree scoped per library, with "Tipy dne" and "Nově přidané" folders.
|
||||
|
||||
## Requirements
|
||||
|
||||
- [uv](https://docs.astral.sh/uv/) — manages Python 3.14 and all dependencies
|
||||
- `ffmpeg` / `ffprobe` on `PATH` — optional, for the integrity check
|
||||
|
||||
## Configuration
|
||||
|
||||
Secrets and settings are read from a `.env` file in the project root (never
|
||||
committed):
|
||||
|
||||
| Variable | Purpose |
|
||||
|----------|---------|
|
||||
| `TMDB_API_KEY` | TMDb API key — required for identifying movies and series |
|
||||
| `TMDB_LANGUAGE` | TMDb language, default `cs-CZ` |
|
||||
| `JELLYFIN_URL` | Jellyfin server URL — optional, for the Jellyfin menu |
|
||||
| `JELLYFIN_API_KEY` | Jellyfin API key — optional |
|
||||
| `ENV_DEBUG` | `true` enables debug mode |
|
||||
|
||||
The pool and output folders are chosen in the app (*Pool* menu).
|
||||
|
||||
## Running
|
||||
|
||||
@@ -41,20 +66,23 @@ uv sync
|
||||
uv run python Curator.py
|
||||
```
|
||||
|
||||
The GUI is built with **PySide6**.
|
||||
|
||||
## Development
|
||||
|
||||
```bash
|
||||
uv run pytest # tests
|
||||
uv run ruff check # lint / format
|
||||
uv run ruff check # lint
|
||||
uv run ruff format # format
|
||||
uv run mypy # type checking
|
||||
```
|
||||
|
||||
Detailed documentation — architecture and a per-module reference — is in
|
||||
[`docs/`](docs/README.md).
|
||||
|
||||
## Building a standalone executable
|
||||
|
||||
```bash
|
||||
uv run pyinstaller Curator.spec
|
||||
uv run python prebuild.py
|
||||
uv run pyinstaller Curator.spec --noconfirm
|
||||
```
|
||||
|
||||
The resulting `dist/Curator` is the distribution artifact.
|
||||
The result is a single self-contained `dist/Curator` executable.
|
||||
|
||||
Reference in New Issue
Block a user