Migrate from Poetry and pyenv to uv
This commit is contained in:
@@ -0,0 +1,207 @@
|
||||
# `src/core/movie.py`
|
||||
|
||||
The movie object — metadata first, files second.
|
||||
|
||||
A movie is identified by `(title, year, tmdb_id)` and owns **its files**, not
|
||||
the other way round. That inversion is the point: the pool index used to key a
|
||||
record by a file path, so metadata was a passenger on one particular file — and
|
||||
everything about multi-version movies got awkward (which file "holds" the tags,
|
||||
why renaming a grouped movie had to be blocked, why an import could silently
|
||||
create a duplicate instead of a version).
|
||||
|
||||
Here the record is the movie; each playable file is a `MediaFile` under
|
||||
it, told apart by a free-text `label` (`3D HSBS`, `4K`, `Director's
|
||||
Cut`, …). Where those files *live* is then a pure function of the record —
|
||||
`Movie.desired_layout` — so promoting to a folder, demoting back, or
|
||||
renaming the whole thing are all the same operation: recompute and move what
|
||||
doesn't match.
|
||||
|
||||
Layout (Jellyfin's own conventions — https://jellyfin.org/docs/general/server/media/movies/)::
|
||||
|
||||
one video <Library>/Title (rok) [tmdbid-N].ext
|
||||
two or more <Library>/Title (rok) [tmdbid-N]/
|
||||
Title (rok) [tmdbid-N] - Výchozí.ext
|
||||
Title (rok) [tmdbid-N].3D.HSBS.ext
|
||||
|
||||
Every file in a movie folder must start with the folder's exact name; both
|
||||
`" - label"` and `".3D.FMT"` are valid separators, so a 3D file keeps the
|
||||
documented `.3D.<FORMAT>` spelling whether it sits flat or grouped.
|
||||
Subtitles are not media entries — they ride along with their video by filename
|
||||
stem (see `src.core.subtitles`).
|
||||
|
||||
Source: [`src/core/movie.py`](../../src/core/movie.py)
|
||||
|
||||
## Classes
|
||||
|
||||
### `MediaFile`
|
||||
|
||||
*class · line 102*
|
||||
|
||||
```python
|
||||
class MediaFile:
|
||||
```
|
||||
|
||||
One file belonging to a movie (today always a playable video).
|
||||
|
||||
#### `MediaFile.format_3d`
|
||||
|
||||
*property · line 110*
|
||||
|
||||
```python
|
||||
format_3d -> Optional[str]
|
||||
```
|
||||
|
||||
The 3D format this version's label denotes, if any.
|
||||
|
||||
#### `MediaFile.to_record`
|
||||
|
||||
*method · line 114*
|
||||
|
||||
```python
|
||||
to_record(pool_dir: Path) -> dict
|
||||
```
|
||||
|
||||
Serialise for the pool index (path stored pool-relative, POSIX).
|
||||
|
||||
#### `MediaFile.from_record`
|
||||
|
||||
*class method · line 123*
|
||||
|
||||
```python
|
||||
from_record(data: dict, pool_dir: Path) -> 'MediaFile'
|
||||
```
|
||||
|
||||
Build a `MediaFile` from one entry of a movie record's `media` list, resolving its relative path against the pool directory.
|
||||
|
||||
### `Movie`
|
||||
|
||||
*class · line 132*
|
||||
|
||||
```python
|
||||
class Movie:
|
||||
```
|
||||
|
||||
A film in the pool: its identity, its metadata, and its media files.
|
||||
|
||||
#### `Movie.videos`
|
||||
|
||||
*property · line 156*
|
||||
|
||||
```python
|
||||
videos -> list[MediaFile]
|
||||
```
|
||||
|
||||
Playable versions, in a stable order (labelled ones by label).
|
||||
|
||||
#### `Movie.is_grouped`
|
||||
|
||||
*property · line 163*
|
||||
|
||||
```python
|
||||
is_grouped -> bool
|
||||
```
|
||||
|
||||
True when the movie needs its own folder (more than one version).
|
||||
|
||||
#### `Movie.stem`
|
||||
|
||||
*property · line 168*
|
||||
|
||||
```python
|
||||
stem -> str
|
||||
```
|
||||
|
||||
Canonical `Title (rok) [tmdbid-N]` — the folder / filename base.
|
||||
|
||||
Falls back to the bare title when no four-digit year is known (a movie
|
||||
can be imported without one).
|
||||
|
||||
#### `Movie.primary`
|
||||
|
||||
*property · line 182*
|
||||
|
||||
```python
|
||||
primary -> Optional[MediaFile]
|
||||
```
|
||||
|
||||
The version that represents the movie (first by `videos`).
|
||||
|
||||
#### `Movie.desired_layout`
|
||||
|
||||
*method · line 187*
|
||||
|
||||
```python
|
||||
desired_layout(pool_dir: Path) -> list[tuple[MediaFile, Path]]
|
||||
```
|
||||
|
||||
`(media, where it belongs)` for every file, from the record alone.
|
||||
|
||||
The whole flat-vs-folder rule lives here: one video sits directly in
|
||||
the library, two or more move into `<Library>/<stem>/`. Callers move
|
||||
whatever doesn't already match — which makes promotion, demotion and
|
||||
renaming the same operation.
|
||||
|
||||
#### `Movie.to_record`
|
||||
|
||||
*method · line 208*
|
||||
|
||||
```python
|
||||
to_record(pool_dir: Path) -> dict
|
||||
```
|
||||
|
||||
Serialise the movie into the dictionary stored in the pool index, with media paths relative to the pool directory.
|
||||
|
||||
#### `Movie.from_record`
|
||||
|
||||
*class method · line 227*
|
||||
|
||||
```python
|
||||
from_record(key: str, data: dict, pool_dir: Path, tagmanager=None) -> 'Movie'
|
||||
```
|
||||
|
||||
Build a `Movie` from an index record stored under `key`. Tags are resolved through `tagmanager` when one is given.
|
||||
|
||||
## Functions
|
||||
|
||||
### `normalize_3d_label`
|
||||
|
||||
*function · line 53*
|
||||
|
||||
```python
|
||||
normalize_3d_label(label: object) -> Optional[str]
|
||||
```
|
||||
|
||||
The 3D format a version label denotes (upper-case), or `None`.
|
||||
|
||||
Lets a plain label double as the 3D marker — `"3D HSBS"` typed in the
|
||||
version dialog and `"hsbs"` picked from a dropdown mean the same thing.
|
||||
|
||||
### `media_filename`
|
||||
|
||||
*function · line 63*
|
||||
|
||||
```python
|
||||
media_filename(stem: str, label: str, ext: str, *, grouped: bool) -> str
|
||||
```
|
||||
|
||||
Filename for one media file of a movie whose canonical stem is `stem`.
|
||||
|
||||
- no label → `stem.ext` (a lone, plain video)
|
||||
- 3D label → `stem.3D.<FORMAT>.ext` (Jellyfin's documented 3D form)
|
||||
- anything else → `stem - label.ext`
|
||||
|
||||
`grouped` only decides whether an empty label is allowed: inside a movie
|
||||
folder every file gets one, so it falls back to `DEFAULT_VERSION_LABEL`.
|
||||
|
||||
### `identity_key`
|
||||
|
||||
*function · line 84*
|
||||
|
||||
```python
|
||||
identity_key(tmdb_id: object=None, title: str='', year: object=None) -> str
|
||||
```
|
||||
|
||||
The pool-index key for a movie — `tmdb-<id>`, else a title/year slug.
|
||||
|
||||
Keying by identity (rather than by file path) is what lets a second file of
|
||||
the same film find its movie instead of becoming a separate entry.
|
||||
Reference in New Issue
Block a user