Declare a library
A Library is one root directory on one volume, holding media of one
kind. This guide declares one, reads what it reports, and describes
what its namespace determines.
The declaration
A Library names a claim in its namespace, a directory inside it,
and the kind that directory holds:
apiVersion: library.liken.sh/v1alpha1
kind: Library
metadata:
name: movies
namespace: media
spec:
storage:
claim: movies-pvc
root: /media/movies
kind: movies
movies: {}
The block named by kind must be present, and the other kinds'
blocks must not. An empty block is a complete one. The kinds are
movies, series, and franchises. A series library holds one
folder per series with a season folder inside. A franchises library
is the one kind whose files are written by people, and
Franchises
covers it.
root defaults to / and must be absolute. One volume can therefore
hold several libraries at different roots. The root holds title
folders and grouping folders, never a video file of its own: the walk
catalogs no video that sits directly in the root, so put each film in
a folder of its own. Scanning
gives the layout of each kind. The kind and the storage
are immutable. A different volume, root, or kind is a different
Library.
Library describes every field. The ones you are likely to set:
spec.sourcesnames theMetadataProvidersto ask, in order. Enrichment covers them.spec.ignorelists path components the scanner skips, such as a recycle bin or a staging directory.spec.scan.scheduleis the cron expression the full walk runs on, in the cluster’s time zone. It defaults to once an hour, on the hour.spec.trickplay.enabledbuilds the thumbnail sheets for the scrub bar. It is off by default, because the first pass reads every video end to end. A trickplay workerJobof its own decodes the videos after eachJobof theLibraryends.spec.trickplay.gpuResourceClaimTemplatenames aResourceClaimTemplateyou write, so the decode runs on the GPU it claims instead of on the CPU.spec.appearances.enabledrecords which credited actor is on screen at each second of each feature, from the faces in the video and the actors’ headshots. It is off by default, because the first pass decodes every frame of every feature. An appearances workerJobruns after eachJobof theLibraryends, andspec.appearances.gpuResourceClaimTemplatenames theResourceClaimTemplateof a GPU to decode and run the face models on. The enrichment guide describes the fact.spec.refreshreopens one fact at a time, so it asks its provider again and rewrites its own files in place.
What it reports
The listing shows the counts and the phase:
$ kubectl -n media get libraries
NAME KIND TITLES ITEMS FILES WAITING SOURCES STATUS READY AGE
movies movies 128 128 560 1 2/2 Idle True 12d
Titles is what the last walk cataloged. Items counts movies, or
series and episodes together. Files counts the video files with their
.nfo files, art, subtitles, and trickplay directories. Waiting counts
the titles a provider returned candidates for, which a person resolves
by naming the right uniqueid in the .nfo. Sources counts the
ready providers against the names in spec.sources, and it is empty
for a library that names none. With -o wide the listing
adds the claim and the Unidentified count. Those are the folders the
walk could not name, and they are still browsable under their folder
names.
Status is the phase. Pending means the storage, the catalog pod, or
the schedule is not ready yet. Scanning means a walk runs, and
Enriching means the phases of a Job run after its walk or in a
Job that fills gaps. Idle is the state between Jobs. Blocked
means the pod of a Job has stayed Pending for five minutes, and no
other Job of the Library starts until that Job ends. Failed
means the last walk failed and wrote no rows, or a Job failed and no
later Job succeeded. A Job that does not start or that
fails
says what to do for both. Offline means the
namespace’s reporter has left the bus. Departing means a deleted
Library is still removing its rows from the catalog.
Four conditions report why the phase is what it is:
Boundreports the storage. The reasons forFalseareClaimNotFound,ClaimUnbound, andVolumeNotFound.Readyreports the scanning path: the catalog pod runs,spec.scan.scheduleparses, the library’sJobsstart and succeed, and the reporter has reported this library. The reasons forFalse, in the order they are checked, areNotBound,NoCatalog,ManyCatalogs,CatalogPending,ScheduleInvalid,JobNotStarted,JobFailed,Offline, andNoReport. The message ofJobNotStartedand ofJobFailednames theJoband the reason Kubernetes gives.Sourcesreportsspec.sources. It is absent on a library that names none.Departingreports the teardown of a deleted library, for as long as its finalizer keeps the object from being removed.
A fifth condition, GPUClaimTemplates, does not change the phase. It
reports the ResourceClaimTemplates that the enabled trickplay and
appearances workers name. The reason for False is
ClaimTemplateNotFound, and the operator starts no worker whose
template is missing. It is absent on a library whose enabled workers
name none.
A worker on a GPU
describes the templates.
status.runs holds the last run of each worker, with its Job, its
times, and its failure if it had one. scan is a full walk, rescan
is a walk of the folders webhooks named, enrich is the phases of a
Job and its hand-off, and cleanup is the sweep of a deleted
Library. A phase that failed names its failure in the enrich run,
and the Job still succeeds, so the next Job tries that phase’s
titles again. status.webhook is the address that rescans one folder;
Webhooks
gives it to Radarr, Sonarr, and
Jellyfin.
The Events it posts
A condition holds only its last verdict. The operator posts a
Kubernetes Event on the Library for each change, so
kubectl -n media describe library movies shows what happened in the
last hour:
- Each new condition, and each change of a condition’s status or
reason, posts one
Eventwith the condition’s reason and message. A new message with the same reason posts none. The reasons that ask a person to act post aWarning:ClaimNotFound,VolumeNotFound,ManyCatalogs,ScheduleInvalid,JobNotStarted,JobFailed,ProviderNotFound,ProviderNotReady,FactNotServed,ClaimTemplateNotFound, and theDepartingreasonBlocked. Every other reason posts aNormalEvent, so aReadythat returns toTruepostsReady. JobCreated(Normal) names eachJobthe operator creates for the library, what it runs, and why.ScanCompleted(Normal) gives the titles and files of a walk that finished, andScanFailed(Warning) gives the failure of a walk that did not. The operator posts each one when the reporter publishes the run.
The API server deletes an Event an hour after its last write, so the
conditions and the operator’s log keep the facts after that.
The namespace is a boundary
Every Library in a namespace writes into that namespace’s one
catalog, and every screen in the namespace shows that catalog. So a
Library waits with the reason NoCatalog until the namespace holds
a Catalog, and two Catalogs block both. Every catalog row is keyed
by its library. So two libraries in one namespace never touch each
other’s rows, even on the same relative path or the same provider id.
Put libraries that should show together in one namespace. Put libraries that should never meet on a screen in different namespaces.
How a title is played
When a person plays a title from the browser, the operator creates a
Play for media-operator. The media reference in it names the
namespace and the claim, never the volume behind it:
claim://media/movies-pvc//media/movies/Some Film (1999)/Some Film (1999).mkv
The claim is the one the screen already mounts read-only, so
media-operator plays from the same claim, and no second claim is
created. A path outside the library’s root is refused.