* feat(metadata): register builtin NFO provider and broaden parsing Phases A and B of the #216 local-NFO work, implemented test-first. Registration & hint-first identity (Phase A): - Migration seeds a reserved kind='builtin' silo.builtin installation and an 'nfo' metadata capability (default_enabled=false, priority 1 for movie/series) with a partial unique index and documented Down. - In-process builtin provider registry (internal/metadata/builtin.go); buildProviders returns the registered provider for builtin rows. - Guard rails keep the reserved row out of every plugin surface (user plugin-settings, installations list, image resolvers, preload, auto-update, store Delete, mutation handlers -> 409); silo.builtin is a reserved manifest id. - Startup sync materializes legacy content_level='' chains per level, then appends builtin capabilities disabled via AppendProviderToAllChains (idempotent); resolveEnabledProvidersBy priority now respects default_enabled=false. - NFO uniqueids seed the trusted-hint machinery via IdentityHintProvider with per-mode conflict policy (stored IDs win on scheduled refresh, NFO wins on manual refresh, Identify skips NFO); ID-less candidates are excluded from provider-priority tie-breaks and nfo never counts as corroboration. - Web chain-editor empty-state gate is now server-derived so builtin providers are reachable on plugin-less servers. Parser breadth & sidecar hardening (Phase B): - Parser covers the practical Kodi/Jellyfin field set for <movie> and <tvshow>: original title, tagline, runtime, dates, content rating, genres/studios/countries/tags, multi-source ratings with scale normalization, cast with roles/order, director/credits. Empty collections stay nil so merge early-returns apply. - findNFO parses candidates and falls through on read/parse failure or root-type mismatch, so a stray movie.nfo cannot shadow tvshow.nfo; GetMetadata gains the same ContentType guard Search has. - New FieldReleaseDates lock gates Year/ReleaseDate/First+LastAirDate in merge (Go) and the edit-metadata dialog (web), closing the gap where a manual refresh re-applied NFO dates over admin corrections. - Merge-contract tests pin NFO fill semantics, genres whole-list first-provider-wins, and NFO edits propagating on manual refresh only. - Docs: new admin wiki page (supported fields, merge semantics, naming-supplies-structure contract), index bullet, sidecar wording revision, v1-scope feature-detection note. Zero behavior change while the provider is disabled (default); pinned by CI-mode and DB-gated test suites. Part of #216 AI-use disclosure: implemented with Claude Code (Fable 5) via spec-driven TDD and agent-assisted implementation. * feat(metadata): ingest local sidecar artwork and read series-depth NFO Phases C and D of the #216 local-NFO work, implemented test-first, plus the mixed-library use-case pins. Together these deliver the headline case: a series absent from every remote database (e.g. a fitness library) scans into a fully presented show -> named seasons -> titled episodes tree from NFO files and sidecar art alone. Local sidecar artwork through the S3 image cache (Phase C): - The NFO provider implements ImageProvider: poster/backdrop/logo sidecar discovery with a fixed precedence map, symlink/non-regular rejection, an 8 MiB cap, and file:// source URLs at rating 0. Generic filenames apply only via the sidecar search paths, so a shared folder.jpg in a flat multi-movie directory applies to none. - file:// becomes a live local source scheme: routed into *_source_path (never *_path), accepted by every image enqueue gate, attributed as provider "local", excluded from cached-path detection. - The image-cache processor caches local files with lexical-on-logical confinement to the library roots, open-handle reads with re-checks, the same variant widths as remote art, and stable (7-day) failure classification. Keys land under local/{contentType}/{contentID}/{hash8}/{imageType}; superseded prefixes are cleaned on re-cache and item deletion. - applyIfBetter gains a local exemption so rating-0 local art can fill matched items without being stickily displaced; ImageRequest carries additive sidecar path context. Series depth (Phase D): - SeasonsRequest/EpisodesRequest carry additive local path context (series roots, per-season directories, per-episode file paths), derived from naming at match time and reconstructed on refresh. - season.nfo supplies season name/plot; NFO season numbers are advisory (directory-derived number wins with a Warn - naming owns structure). <episodedetails> gains aired/runtime/ratings; <basename>.nfo titles episodes and <basename>-thumb.ext supplies thumbs; filename SxxEyy wins over NFO numbers. - Episode NFOs work without a season.nfo (provider seasons unioned with on-disk seasons); SynthesizeFallbackEpisodes always runs after persist so NFO-less episodes keep synthesized rows. Season/episode file:// art rides the Phase C pipeline unchanged. - Migration adds season:1/episode:1 to the builtin NFO capability's default_priority (still default_enabled=false). Mixed sports-library use case (tests only, no product change): - Pins the classification contract for one library holding movie-shaped and show-shaped content (WWE PPV events as movies next to a "WWE SmackDown" show, NASCAR/F1/FIFA with partial TVDB/TMDB data): naming decides movie-vs-series per file before any provider runs; the NFO supplies metadata/identity but never flips type (ContentType guard); the per-root Type override is the correction path. - NFO-driven type classification at scan time is recorded as an explicit deferred open question. Part of #216 AI-use disclosure: implemented with Claude Code (Fable 5) via spec-driven TDD and agent-assisted implementation. * docs(metadata): document local NFO metadata architecture Add a single as-built architecture page (docs/architecture/local-nfo-metadata.md) for the #216 local-NFO feature: the builtin registration model, hint-first identity semantics, the file:// -> S3 artwork pipeline and its deployment constraint, series depth, the mixed-library classification contract, and known limitations. This replaces the working implementation plan, the per-phase specs, and the narrow sidecar-artwork note, which were planning drafts and are left untracked; admin-facing behavior remains in the wiki. Part of #216 AI-use disclosure: planned, drafted, and consolidated with Claude Code (Fable 5) using multi-agent exploration and adversarial review. * fix(metadata): address PR review findings on NFO builtin provider Fold in the valid, low-risk fixes surfaced by automated review on #390: - imagecache: extract validateCacheRequest so CacheBytes (the local sidecar season/episode path) enforces the same episode-requires-season guard as Cache, preventing distinct episodes' art from colliding under one S3 key. - image_cache_processor: close the sidecar symlink-swap window by rejecting the opened handle unless os.SameFile matches the Lstat'd file, so a leaf swapped to a symlink can't pull an out-of-root target into the public cache. - plugins: guard the reserved builtin installation row in the store's Update, matching Delete, so its version/enabled/capabilities can never be rewritten even if a mutation slips past the HTTP layer. - cmd/silo: bound SyncBuiltinProviderChains with a 30s timeout so a stuck DB round-trip fails fast at startup instead of hanging. - metadata: panic instead of silently no-op'ing on an invalid RegisterBuiltinProvider call (init-time programmer error). - docs: correct the media-folder-and-naming NFO paragraph to state season/episode NFOs and sidecar artwork are actively read. --------- Co-authored-by: Quick104 <31828688+Quick104@users.noreply.github.com>
9.2 KiB
title, description, summary, tags, audience, last_reviewed, related
| title | description | summary | tags | audience | last_reviewed | related | |||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Supported Media Folder Structures and Naming | Accurate reference for the folder layouts and filename patterns Silo supports today. | Supported movie and series organization rules, naming conventions, and known ambiguous cases. |
|
|
2026-04-11 |
|
Supported Media Folder Structures and Naming
This page documents the folder layouts and filename conventions that Silo supports today.
It is based on the current scanner, naming, and metadata-matching code paths, plus a validation pass
against the dev anime library on 2026-04-11.
All examples in this page use generic placeholder names rather than real media titles.
Core Rules
- Movies and series do not have the same requirements.
- Series are expected to be contained within a single parent show folder.
- Movies can be stored either in a dedicated movie folder or as loose files.
- Mixed libraries are supported, but they rely on heuristics and are more likely to become ambiguous when names and folders are inconsistent.
- Provider ID tags such as
{tvdb-12345},{tmdb-12345}, or{imdb-tt1234567}are strongly recommended because they improve matching and reduce ambiguity.
Supported Series Layouts
Recommended series layout
/television/Show Name (2024) {tvdb-12345}/Season 01/Show Name - S01E01 - Pilot.mkv
This is the clearest and most reliable structure for series.
Also supported
Series content is supported in these layouts:
- Show folder with
Season XXdirectories. - Show folder with
Season XX - extra textdirectories, such as arc names. - Show folder with numeric season directories like
01when the surrounding context still looks like series content. - Show folder with
Specialsdirectories. - Show folder with
Extrasdirectories. Silo maps these to season0. - Show folder with episode files directly under the show folder when the filenames contain a
supported episodic token such as
S01E03.
Examples:
/tv/Series Name (2008)/Season 01/Series.Name.S01E01.mkv
/tv/Show Name/Specials/Show.Name.S00E01.mkv
/tv/Show Name/Extras/Show.Name.S00E01.mkv
/mixed/Show Name/01/Show Name S01E03.mkv
/mixed/Show Name/Show Name S01E03.mkv
One narrow edge case is worth calling out separately:
/television/anime/Series Name/Season 01 - Arc 01 - Arc Name/Series.Name.E01.1080p.mkv
That kind of path can still help Silo recognize the show root because the folder layout is
strong, but bare E01 is not a generally supported episode parsing format.
Supported Episode Filename Patterns
Silo currently treats SxxExx as the supported episode token.
Supported examples:
Show Name - S01E01 - Pilot.mkv
Show Name.S01E03.mkv
Show Name - S00E01 - Special.mkv
Show Name - S01E01.001 - Pilot [Bluray-1080p][x265].mkv
Important details:
- The parser only needs the
SxxExxpart. Extra text after it is tolerated. - Decimal suffixes such as
S01E01.001are supported because the parser still recognizes the leadingS01E01. - Release metadata and noisy suffixes after the episode token are tolerated as long as the file
still contains a valid
SxxExxtoken. SpecialsandExtrasuse season0, soS00E01is supported.
Representative validated pattern from the dev server:
Series Name (2017) - S01E01.001 - Episode Title [Bluray-1080p][10bit][x265][AC3 5.1][EN+JA]-GROUP][.mkv
Series Patterns That Are Not Reliably Supported
These patterns should be treated as unsupported or ambiguous:
- Unrelated episodes from different series thrown into the same folder.
- Episode files that use only absolute numbering, such as
Stone Ocean 24.mkv, withoutSxxExx. - Bare
E01filenames as a general naming convention. Silo has a narrow test case whereE01still helps identify a season-folder layout, but it is not a generally supported episode parsing format. - Series files with no clear show folder, no supported episode token, and no provider-tagged parent folder.
If a series library contains a flat dump of unrelated episodes, Silo can collapse them into the same inferred root because the current model assumes one parent show folder per series.
When Silo cannot confidently classify or reconcile a root, it can surface the content as an explicitly ambiguous item instead of auto-matching it. In practice, unsupported layouts often show up as ambiguous or pending items rather than as clean matches.
Supported Movie Layouts
Movies are more flexible than series.
Recommended movie layout
/movies/Movie Name (2016)/Movie.Name.2016.1080p.BluRay.mkv
Also supported
- Loose movie files in a movie library.
- Dedicated movie folders with provider tags.
- Release-style filenames inside a trusted movie folder.
Examples:
/movies/Movie Name (2016)/Movie.Name.2016.1080p.BluRay.mkv
/movies/Movie Name [imdbid-tt1234567]/Movie Name.mp4
/movies/Loose Movie.2024.2160p.WEB-DL.mkv
/movies/Movie Name {imdb-tt1234567} {tmdb-12345}/Movie.Name (2023) [Remux-1080p].mkv
Silo can infer a synthetic root for a truly loose movie file when there is no trusted movie folder around it.
Supported Mixed-Library Behavior
Mixed libraries are supported, but they are heuristic-driven.
Silo resolves mixed-library content roughly like this:
- If the path has season structure, it is treated as series.
- If the enclosing folder looks like a movie folder, it is treated as a movie.
- If the filename contains
SxxExx, it is treated as series. - Otherwise it falls back to movie.
This means mixed libraries can work well, but they are less deterministic than separate movie and series libraries.
Provider Tags
Provider tags are supported in folder names and are strongly recommended.
Supported formats include:
Series Name (2024) {tvdb-12345}
Movie Name (2024) {tmdb-12345}
Movie Name [imdbid-tt1234567]
Benefits of provider tags:
- More reliable initial skeleton creation.
- Better deduplication.
- Less ambiguity in mixed libraries.
- Better resilience when release filenames diverge from the human title.
Sidecar Files and Supplemental Files
Silo often sees sidecar files that mirror the media basename:
.nfo.bif-thumb.jpgtvshow.nfoseason.nfo- posters, banners, logos, and fanart
These are common and expected. Episode naming guidance in this page refers to the media files themselves, but sidecars may legitimately reuse the same stem.
Some NFO sidecars are actively read: movie.nfo, tvshow.nfo, <media basename>.nfo,
season.nfo, <episode basename>.nfo, and sidecar artwork (season posters, episode
thumbnails) feed the built-in NFO metadata provider — including <uniqueid> provider IDs,
which act as trusted matching anchors just like {tmdb-...} folder tags. NFO files never
change how files are grouped; naming and layout still decide series/season/episode
structure. See Local NFO Metadata for the supported fields and
merge behavior.
Supplemental directories next to a movie (and directly under a series root) are scanned as extras attached to that item, following the Jellyfin/Plex folder convention:
Trailers,TeasersFeaturettesBehind the ScenesDeleted ScenesClips,Shorts,Interviews,ScenesExtras,Other
Filename suffixes on files sitting next to the movie are also recognized:
Movie (2020)-trailer.mkv, -teaser, -featurette, -clip,
-behindthescenes, -deleted, -interview, -short, -other.
Extras never appear as versions of the main title; they show in the item's
Extras section and play like any other file. Extras are bound to the item
owning the surrounding folder, so an extras directory at the library root is
ignored. For series libraries, Extras/ files carrying a valid SxxExx
token keep their documented season-0 mapping and are not treated as extras.
Noise content is still intentionally skipped:
Sample/Samplesdirectories andSample.mkv-style filesSubs/Subtitlesdirectories (handled by subtitle detection)
What The Dev Anime Library Validated
A large anime series library on the dev server was audited to validate that this page reflects real-world usage and not just unit tests.
Observed on 2026-04-11:
4038top-level show directories.4029show directories with at least oneSeason XXdirectory.1184show directories with aSpecialsdirectory.4036show directories with a provider tag in the folder name.474304files matchingSxxExx.xxx.87289.mkvfiles matchingSxxExx.xxx.
That makes SxxExx.xxx an important supported pattern, not a corner case.
Recommended Practices
- Put every series inside its own parent show folder.
- Use
Season XXandSpecialswhen possible. - Include the year in the top-level folder when it is known.
- Add provider tags when you can.
- Keep movies and series in separate libraries unless you specifically want mixed-library heuristics.
- Do not rely on flat folders of unrelated episodes.
Source References
internal/naming/filename.gointernal/naming/root_inference.gointernal/scanner/scanner.gointernal/metadata/service.gointernal/naming/filename_test.gointernal/scanner/root_observation_test.gointernal/metadata/service_test.go