feat(config): per-title-type folder templates

Allow output_template.folder to be a dict with movies/series/songs keys so music libraries can use artist/album folder layouts while movies and series keep their own scheme. Legacy string form still applies to all title types.
This commit is contained in:
imSp4rky
2026-04-30 10:51:32 -06:00
parent 605b46f723
commit 21754ad37e
5 changed files with 46 additions and 10 deletions

View File

@@ -98,7 +98,13 @@ class Config:
self.language_tags: dict = kwargs.get("language_tags") or {}
self.output_template: dict = kwargs.get("output_template") or {}
self.folder_template: str = self.output_template.pop("folder", "") or ""
folder_cfg = self.output_template.pop("folder", "")
self.folder_template: str = ""
self.folder_templates: dict = {}
if isinstance(folder_cfg, dict):
self.folder_templates = {k: v for k, v in folder_cfg.items() if isinstance(v, str) and v}
elif isinstance(folder_cfg, str):
self.folder_template = folder_cfg or ""
if kwargs.get("scene_naming") is not None:
raise SystemExit(
@@ -158,6 +164,11 @@ class Config:
all_templates = dict(self.output_template)
if self.folder_template:
all_templates["folder"] = self.folder_template
for kind, tmpl in self.folder_templates.items():
if kind not in {"movies", "series", "songs"}:
warnings.warn(f"Unknown folder template kind '{kind}' (expected movies/series/songs)")
continue
all_templates[f"folder.{kind}"] = tmpl
for template_type, template_str in all_templates.items():
if not isinstance(template_str, str):
@@ -178,6 +189,18 @@ class Config:
if not template_str.strip():
warnings.warn(f"Template '{template_type}' is empty")
def get_folder_template(self, kind: str) -> str:
"""Resolve the folder template for the given title kind.
kind: one of "movies", "series", "songs".
Falls back to the legacy single-string folder template, then "".
"""
if self.folder_templates:
tmpl = self.folder_templates.get(kind)
if tmpl:
return tmpl
return self.folder_template or ""
def get_template_separator(self, template_type: str = "movies") -> str:
"""Get the filename separator for the given template type.

View File

@@ -100,14 +100,15 @@ class Episode(Title):
def get_filename(self, media_info: MediaInfo, folder: bool = False, show_service: bool = True) -> str:
if folder:
if config.folder_template:
formatter = TemplateFormatter(config.folder_template)
template = config.get_folder_template("series")
if template:
formatter = TemplateFormatter(template)
context = self._build_template_context(media_info, show_service)
context["season"] = f"S{self.season:02}"
folder_name = formatter.format(context)
separators = re.sub(r"\{[^}]*\}", "", config.folder_template)
separators = re.sub(r"\{[^}]*\}", "", template)
spacer = "." if "." in separators and " " not in separators else " "
return sanitize_filename(folder_name, spacer)

View File

@@ -60,12 +60,13 @@ class Movie(Title):
def get_filename(self, media_info: MediaInfo, folder: bool = False, show_service: bool = True) -> str:
if folder:
if config.folder_template:
formatter = TemplateFormatter(config.folder_template)
template = config.get_folder_template("movies")
if template:
formatter = TemplateFormatter(template)
context = self._build_template_context(media_info, show_service)
folder_name = formatter.format(context)
separators = re.sub(r"\{[^}]*\}", "", config.folder_template)
separators = re.sub(r"\{[^}]*\}", "", template)
spacer = "." if "." in separators and " " not in separators else " "
return sanitize_filename(folder_name, spacer)
name = f"{self.name}"

View File

@@ -95,12 +95,13 @@ class Song(Title):
def get_filename(self, media_info: MediaInfo, folder: bool = False, show_service: bool = True) -> str:
if folder:
if config.folder_template:
formatter = TemplateFormatter(config.folder_template)
template = config.get_folder_template("songs")
if template:
formatter = TemplateFormatter(template)
context = self._build_template_context(media_info, show_service)
folder_name = formatter.format(context)
separators = re.sub(r"\{[^}]*\}", "", config.folder_template)
separators = re.sub(r"\{[^}]*\}", "", template)
spacer = "." if "." in separators and " " not in separators else " "
return sanitize_filename(folder_name, spacer)
name = f"{self.artist} - {self.album}"

View File

@@ -63,6 +63,16 @@ output_template:
#
# Plex-friendly folder:
# folder: '{title} ({year?})'
#
# Per-title-type folder templates (optional). Override folder naming separately for
# movies, series, and songs. Useful when music libraries need artist/album-style folders
# while movies/series follow a different scheme. Any kind omitted falls back to the
# default for that title type.
#
# folder:
# movies: '{title} ({year})'
# series: '{title} ({year?})'
# songs: '{artist}/{album} ({year?})'
# Language-based tagging for output filenames
# Automatically adds language identifiers (e.g., DANiSH, NORDiC, DKsubs) based on