Files
Curator/docs/modules/core.movie.md
T

5.1 KiB

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

Classes

MediaFile

class · line 102

class MediaFile:

One file belonging to a movie (today always a playable video).

MediaFile.format_3d

property · line 110

format_3d -> Optional[str]

The 3D format this version's label denotes, if any.

MediaFile.to_record

method · line 114

to_record(pool_dir: Path) -> dict

Serialise for the pool index (path stored pool-relative, POSIX).

MediaFile.from_record

class method · line 123

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

class Movie:

A film in the pool: its identity, its metadata, and its media files.

Movie.videos

property · line 156

videos -> list[MediaFile]

Playable versions, in a stable order (labelled ones by label).

Movie.is_grouped

property · line 163

is_grouped -> bool

True when the movie needs its own folder (more than one version).

Movie.stem

property · line 168

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

primary -> Optional[MediaFile]

The version that represents the movie (first by videos).

Movie.desired_layout

method · line 187

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

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

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

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

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

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.