Add per-movie attributes and per-category filename templates
This commit is contained in:
@@ -60,6 +60,49 @@ class FileManager:
|
||||
self.global_config["copyasis_folders"] = cleaned
|
||||
save_global_config(self.global_config)
|
||||
|
||||
@property
|
||||
def tag_schema(self) -> list[dict]:
|
||||
"""Tag categories + ČSFD/Filmotéka rules (see config.DEFAULT_TAG_SCHEMA)."""
|
||||
from src.core.config import DEFAULT_TAG_SCHEMA
|
||||
return self.global_config.get("tag_schema", DEFAULT_TAG_SCHEMA)
|
||||
|
||||
def set_tag_schema(self, schema: list[dict]) -> None:
|
||||
"""Set the tag schema and persist it."""
|
||||
self.global_config["tag_schema"] = schema
|
||||
save_global_config(self.global_config)
|
||||
|
||||
def filmoteka_category_roots(self) -> dict[str, str]:
|
||||
"""Category → output root-folder map derived from the tag schema.
|
||||
|
||||
Categories with ``filmoteka_root`` set to None are filterable but get no
|
||||
folders in the generated tree.
|
||||
"""
|
||||
return {
|
||||
e["category"]: e["filmoteka_root"]
|
||||
for e in self.tag_schema
|
||||
if e.get("filmoteka_root") is not None
|
||||
}
|
||||
|
||||
def filmoteka_category_transforms(self) -> dict[str, str]:
|
||||
"""Category → folder transform-name map (for grouping, e.g. rating bands).
|
||||
|
||||
Tags keep their exact value; this transform only shapes the folder name
|
||||
in the generated tree. Categories without a transform are omitted.
|
||||
"""
|
||||
return {
|
||||
e["category"]: e["transform"]
|
||||
for e in self.tag_schema
|
||||
if e.get("filmoteka_root") is not None and e.get("transform")
|
||||
}
|
||||
|
||||
def filmoteka_category_filename_templates(self) -> dict[str, str]:
|
||||
"""Category → hardlink-name template map (applied inside that category)."""
|
||||
return {
|
||||
e["category"]: e["filename_template"]
|
||||
for e in self.tag_schema
|
||||
if e.get("filmoteka_root") is not None and e.get("filename_template")
|
||||
}
|
||||
|
||||
@property
|
||||
def filmoteka_dir(self) -> Path | None:
|
||||
value = self.global_config.get("filmoteka_dir")
|
||||
@@ -93,13 +136,35 @@ class FileManager:
|
||||
file_obj = File(each, self.tagmanager, index=self.index)
|
||||
self.filelist.append(file_obj)
|
||||
|
||||
def pooled_with_stem(self, title: str) -> list[File]:
|
||||
"""Pooled movies whose filename stem matches ``title`` (case-insensitive)."""
|
||||
stem = title.strip().lower()
|
||||
return [f for f in self.filelist if Path(f.filename).stem.lower() == stem]
|
||||
|
||||
def _evict(self, file_obj: File) -> None:
|
||||
"""Delete a pooled movie: its metadata, the file, and the list entry."""
|
||||
file_obj.delete_metadata()
|
||||
if file_obj.file_path.exists():
|
||||
file_obj.file_path.unlink()
|
||||
if file_obj in self.filelist:
|
||||
self.filelist.remove(file_obj)
|
||||
|
||||
def import_movie(
|
||||
self, source: Path, title: str, csfd_link: str | None = None, move: bool = False
|
||||
) -> File:
|
||||
self, source: Path, title: str, csfd_link: str | None = None,
|
||||
move: bool = False, on_conflict: str = "suffix",
|
||||
) -> File | None:
|
||||
"""Bring a video file into pool/Filmy as 'Title.ext' and index its metadata.
|
||||
|
||||
By default the original is **copied** (non-destructive). With ``move=True``
|
||||
the source file is moved into the pool instead, leaving nothing behind.
|
||||
|
||||
``on_conflict`` decides what happens when a pooled movie of the same name
|
||||
already exists:
|
||||
|
||||
- ``"suffix"`` (default): keep both, the new file gets a ``_N`` suffix.
|
||||
- ``"replace"``: evict the existing same-named movie(s) (file + metadata)
|
||||
and import the new one under the plain name.
|
||||
- ``"skip"``: do not import; return ``None``.
|
||||
"""
|
||||
movies = self.movies_dir
|
||||
pool = self.pool_dir
|
||||
@@ -114,11 +179,22 @@ class FileManager:
|
||||
safe_title = title.strip() or source.stem
|
||||
target = movies / f"{safe_title}{source.suffix}"
|
||||
|
||||
# Avoid clobbering an existing movie of the same name
|
||||
counter = 1
|
||||
while target.exists():
|
||||
target = movies / f"{safe_title}_{counter}{source.suffix}"
|
||||
counter += 1
|
||||
existing = self.pooled_with_stem(safe_title)
|
||||
conflict = bool(existing) or target.exists()
|
||||
|
||||
if conflict and on_conflict == "skip":
|
||||
return None
|
||||
if conflict and on_conflict == "replace":
|
||||
for f in existing:
|
||||
self._evict(f)
|
||||
if target.exists():
|
||||
target.unlink()
|
||||
else:
|
||||
# "suffix": never clobber an existing exact filename
|
||||
counter = 1
|
||||
while target.exists():
|
||||
target = movies / f"{safe_title}_{counter}{source.suffix}"
|
||||
counter += 1
|
||||
|
||||
if move:
|
||||
shutil.move(str(source), str(target))
|
||||
|
||||
Reference in New Issue
Block a user