Files
UHD_Media_Tool/packages/domain/test/differential/gen-naming-mp.py
T
rootandClaude Opus 5.5 f7c75ad781 feat(domain): naming template engine (Jinja safe subset, MP variables, path layout)
- naming/engine: lexer/parser/AST/render for {{ }} / if-elif-else / whitelisted filters
  (default pad pad2 upper lower trim replace truncate date join); rejects for/set/attribute/
  subscript/call/~/% with line:col errors and 'did you mean' hints
- naming/variables: variable table aligned with MoviePilot names plus ours (cut, airDate,
  issue, slotName, libraryRoot ...); buildNamingVars from parsed release + entity + units
- naming/layout: split by literal '/', segment roles (category/root/season/sub/file),
  value escaping, reserved names, 255-byte segments, validateNamingTemplate
- naming/defaults: MP default movie/tv + our variety template, v1 syntax upgrade,
  entity > root > global override resolution, builtin preview samples
- path/binding: resolveTargetDirectory keeps bound dirs, only unbound segments render;
  renderMediaFileName = last segment of the full template; DEFAULT_TEMPLATES = MP v2
- differential: 34 cases vs MP (21 identical, 13 documented deviations)

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-28 15:24:32 +08:00

81 lines
3.0 KiB
Python

"""命名模板差分:用 MP 上游(MetaInfo + TemplateContextBuilder + jinja2.Template)生成 naming-mp.json。
用法(MP 仓库根目录,用其 .venv):
cd /opt/moviepilot-plus/upstream/MoviePilot && PYTHONPATH=. \
.venv/bin/python /opt/uhd_media_tool/packages/domain/test/differential/gen-naming-mp.py
只记录行为输出,不复制 MP 源码。默认模板取 settings.RENAME_FORMAT(与 MP 规范化一致);
MP 没有综艺类型,综艺样例用剧集类型渲染我们的模板(预期 MP 报错,属我们特有)。
"""
import json
import pathlib
import subprocess
from pathlib import PurePosixPath
from jinja2 import Template
from app.application.messaging.message import TemplateHelper
from app.core.config import settings
from app.core.context import MediaInfo
from app.core.metainfo import MetaInfo
from app.schemas.types import MediaType
HERE = pathlib.Path(__file__).parent
cases = json.loads((HERE / "naming-cases.json").read_text(encoding="utf-8"))["cases"]
def normalize_template(s: str) -> str:
# 同 settings.RENAME_FORMAT:\ → /,合并 /,去首尾 /
import re
s = s.replace("\\", "/")
s = re.sub(r"/+", "/", s)
return s.strip("/")
def run(case: dict) -> dict:
kind = case["kind"]
mtype = MediaType.MOVIE if kind == "movie" else MediaType.TV
meta = MetaInfo(case["fileName"])
if mtype == MediaType.TV and meta.begin_episode is not None:
# transhandler.plan_transfer:单文件 3 集及以上截成首集
meta.end_season = None
if meta.total_season:
meta.total_season = 1
if meta.total_episode > 2:
meta.total_episode = 1
meta.end_episode = None
e = case["entity"]
mi = MediaInfo()
mi.type = mtype
mi.title = e["title"]
mi.year = str(e["year"]) if e.get("year") else None
mi.en_title = e.get("enTitle")
mi.original_title = e.get("originalTitle")
mi.category = e.get("category", "")
if e.get("tmdbId") is not None:
mi.tmdb_id = e["tmdbId"]
ext = "." + case["fileName"].rsplit(".", 1)[1]
ctx = TemplateHelper().builder.build(meta=meta, mediainfo=mi, file_extension=ext, include_raw_objects=False)
ctx.pop("media_source", None)
ctx.pop("media_id", None)
ctx.update(case.get("vars") or {})
tpl = case.get("template") or settings.RENAME_FORMAT(mtype)
try:
out = Template(normalize_template(tpl)).render(ctx)
except Exception as ex: # noqa: BLE001 - 记录 MP 的报错
return {"error": f"{type(ex).__name__}: {ex}"}
return {"path": str(PurePosixPath(out))}
def main() -> None:
commit = subprocess.run(["git", "rev-parse", "--short", "HEAD"], capture_output=True, text=True).stdout.strip()
result = {"generatedFrom": {"repo": "MoviePilot", "commit": commit}, "mp": {c["id"]: run(c) for c in cases}}
(HERE / "naming-mp.json").write_text(json.dumps(result, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
print(f"wrote {len(cases)} cases")
if __name__ == "__main__":
main()