5528e0f652
- clients/omdb.py: OMDb als zweite Quelle (Fallback-Kette: TMDB exakt ->
OMDb -> bester TMDB-Vorschlag mit niedriger Confidence -> unknown)
- Pre-Scan-Reparatur: Titel kam nie an — makemkvcon existiert nur im
Worker, isosize war nirgends installiert (fiel still auf "DVD" zurueck).
Jetzt: ISO-9660-Volume-Label direkt vom Medium + Label-Normalisierung
(PULP_FICTION -> Pulp Fiction), Disc-Typ ueber zentrale detection.py
- POST /devices/{name}/eject (CDROMEJECT-ioctl) mit Job-Schutz (409 wenn
auf dem Laufwerk gerade gerippt wird)
- OMDB_API_KEY in compose/.env.example; .env.example komplett ehrlich
dokumentiert (JWT-Pflicht, MakeMKV-Beta-Key-Rhythmus)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
77 lines
2.1 KiB
Python
77 lines
2.1 KiB
Python
"""Laufwerks-Discovery über /sys und ioctls — ehrlich, ohne udev.
|
|
|
|
Warum kein udevadm mehr: Im Container läuft kein udevd, die udev-Datenbank ist
|
|
leer — `udevadm info` lieferte schlicht nichts (der alte Weg zeigte deshalb nie
|
|
ein Laufwerk an). /sys/class/block/<name>/device/{vendor,model} kommt dagegen
|
|
direkt vom Kernel und funktioniert überall.
|
|
"""
|
|
|
|
import glob
|
|
import os
|
|
from fcntl import ioctl
|
|
|
|
from detection import (
|
|
CDS_DISC_OK,
|
|
classify,
|
|
disc_size_bytes,
|
|
disc_status,
|
|
drive_status,
|
|
)
|
|
|
|
# include/uapi/linux/cdrom.h
|
|
CDROMEJECT = 0x5309
|
|
|
|
|
|
def eject(device_path: str) -> None:
|
|
"""Wirft die Disc aus (CDROMEJECT-ioctl). Wirft OSError bei Fehlern."""
|
|
fd = os.open(device_path, os.O_RDONLY | os.O_NONBLOCK)
|
|
try:
|
|
ioctl(fd, CDROMEJECT, 0)
|
|
finally:
|
|
os.close(fd)
|
|
|
|
|
|
def list_optical_devices() -> list:
|
|
"""Alle optischen Laufwerke, die der Container sieht (devices: in compose)."""
|
|
return sorted(glob.glob("/dev/sr[0-9]*"))
|
|
|
|
|
|
def read_sys_attr(device_name: str, attr: str) -> str:
|
|
pfad = f"/sys/class/block/{device_name}/device/{attr}"
|
|
try:
|
|
with open(pfad, encoding="ascii", errors="replace") as f:
|
|
return f.read().strip()
|
|
except OSError:
|
|
return ""
|
|
|
|
|
|
def device_info(device_path: str) -> dict:
|
|
"""Baut den Geräte-Eintrag fürs UI: Name aus /sys, Disc-Status per ioctl."""
|
|
name = os.path.basename(device_path)
|
|
vendor = read_sys_attr(name, "vendor")
|
|
model = read_sys_attr(name, "model")
|
|
|
|
try:
|
|
status_code = drive_status(device_path)
|
|
except OSError:
|
|
status_code = -1
|
|
|
|
disc_type = "unknown"
|
|
status = "empty"
|
|
if status_code == CDS_DISC_OK:
|
|
status = "ready"
|
|
try:
|
|
disc_type = classify(disc_status(device_path), disc_size_bytes(device_path))
|
|
except OSError:
|
|
disc_type = "unknown"
|
|
|
|
return {
|
|
"id": name,
|
|
"name": " ".join(teil for teil in (vendor, model) if teil) or f"Laufwerk {name}",
|
|
"type": disc_type,
|
|
"path": device_path,
|
|
"status": status,
|
|
"model": model,
|
|
"serial": read_sys_attr(name, "wwid"),
|
|
}
|