Dockerized media library scanner for NAS drives. Identifies movies and series from filenames, fetches posters via the public IMDB suggestion API (no key required), and exposes a dark-themed web UI with multilingual search support. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
109 lines
3.3 KiB
Python
109 lines
3.3 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
generate-compose.py
|
|
Reads .env and generates docker-compose.override.yml with NAS volumes.
|
|
No external dependencies required.
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import re
|
|
from pathlib import Path
|
|
|
|
ENV_FILE = Path(".env")
|
|
OUTPUT_FILE = Path("docker-compose.override.yml")
|
|
|
|
OVERRIDE_HEADER = """\
|
|
# Auto-generated by generate-compose.py — do not edit manually.
|
|
# Re-run the script after changing NAS_* entries in .env.
|
|
|
|
services:
|
|
backend:
|
|
volumes:
|
|
"""
|
|
|
|
|
|
def load_env(path: Path) -> dict:
|
|
env = {}
|
|
if not path.exists():
|
|
print(f"[ERROR] {path} not found. Copy .env.example to .env first.")
|
|
sys.exit(1)
|
|
with path.open(encoding="utf-8") as fh:
|
|
for line in fh:
|
|
line = line.strip()
|
|
if not line or line.startswith("#"):
|
|
continue
|
|
if "=" not in line:
|
|
continue
|
|
key, _, value = line.partition("=")
|
|
key = key.strip()
|
|
# Strip inline comments
|
|
value = value.split("#")[0].strip()
|
|
env[key] = value
|
|
return env
|
|
|
|
|
|
def parse_nas_entries(env: dict) -> list[tuple[str, str, str]]:
|
|
"""Return list of (key, name, host_path) sorted by key."""
|
|
nas_pattern = re.compile(r"^NAS_(\d+)$")
|
|
entries = []
|
|
for key, value in env.items():
|
|
match = nas_pattern.match(key)
|
|
if not match:
|
|
continue
|
|
if "|" not in value:
|
|
print(f"[WARN] {key}={value!r} — expected format 'Name|/host/path', skipping.")
|
|
continue
|
|
name, _, host_path = value.partition("|")
|
|
entries.append((key, name.strip(), host_path.strip()))
|
|
entries.sort(key=lambda t: int(t[0].split("_")[1]))
|
|
return entries
|
|
|
|
|
|
def validate_paths(entries: list[tuple[str, str, str]]) -> None:
|
|
for key, name, host_path in entries:
|
|
if not Path(host_path).exists():
|
|
print(f"[WARN] {key} — host path does not exist: {host_path!r}")
|
|
else:
|
|
print(f"[OK] {key} — {name!r} → {host_path}")
|
|
|
|
|
|
def generate_override(entries: list[tuple[str, str, str]]) -> str:
|
|
lines = [OVERRIDE_HEADER]
|
|
for idx, (key, name, host_path) in enumerate(entries, start=1):
|
|
container_path = f"/nas/nas{idx}"
|
|
lines.append(f" - {host_path}:{container_path}:ro # {name}")
|
|
# Pass NAS_X env vars into the container so config.py can read the names
|
|
lines.append(" environment:")
|
|
for idx, (key, name, host_path) in enumerate(entries, start=1):
|
|
lines.append(f" - {key}={name}|/nas/nas{idx}")
|
|
lines.append("")
|
|
return "\n".join(lines)
|
|
|
|
|
|
def main() -> None:
|
|
print("=" * 55)
|
|
print(" MediaTracker — generate-compose.py")
|
|
print("=" * 55)
|
|
|
|
env = load_env(ENV_FILE)
|
|
entries = parse_nas_entries(env)
|
|
|
|
if not entries:
|
|
print("[ERROR] No NAS_* entries found in .env. Add at least one NAS.")
|
|
sys.exit(1)
|
|
|
|
print(f"\nFound {len(entries)} NAS source(s):\n")
|
|
validate_paths(entries)
|
|
|
|
content = generate_override(entries)
|
|
OUTPUT_FILE.write_text(content, encoding="utf-8")
|
|
|
|
print(f"\n[DONE] {OUTPUT_FILE} generated with {len(entries)} volume(s).")
|
|
print("\nNext step:")
|
|
print(" docker compose -f docker-compose.yml -f docker-compose.override.yml up -d --build\n")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|