Add test utilities and Jellyfin audio conventions doc
- test_ean_scan.py: interactive EAN barcode scanning test via scanner server - test_backcover_scan.py: comprehensive backcover metadata extraction test - docs/Grundsaetzliche_Audio_Koventionen_und_Methoden_Jellyfin.md: notes on audio conventions and naming methods for Jellyfin Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
5de6caba3a
commit
41378e8a10
3 changed files with 1342 additions and 0 deletions
107
test_ean_scan.py
Normal file
107
test_ean_scan.py
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Interaktiver EAN-Scan-Test: Foto hochladen → Vision-LLM → EAN ausgeben.
|
||||
|
||||
Starten: python3 test_ean_scan.py
|
||||
Beenden: Strg+C
|
||||
"""
|
||||
|
||||
import base64
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import httpx
|
||||
|
||||
from musiksammlung.scanner_server import ScannerServer, print_qr
|
||||
from musiksammlung.vision_llm import EAN_PROMPT
|
||||
|
||||
UPLOAD_DIR = Path("/tmp/ean_scan_test")
|
||||
PORT = 8765
|
||||
MODEL = "qwen3-vl:235b-cloud"
|
||||
BASE_URL = "http://localhost:11434"
|
||||
TIMEOUT_PHOTO = 300.0 # Sekunden warten auf Foto-Upload
|
||||
TIMEOUT_LLM = 60.0 # Sekunden warten auf LLM-Antwort
|
||||
|
||||
|
||||
def query_llm(image_path: Path) -> str:
|
||||
"""Schickt das Bild ans Vision-LLM und gibt die Rohausgabe zurück."""
|
||||
b64 = base64.b64encode(image_path.read_bytes()).decode()
|
||||
response = httpx.post(
|
||||
f"{BASE_URL}/api/chat",
|
||||
json={
|
||||
"model": MODEL,
|
||||
"messages": [{"role": "user", "content": EAN_PROMPT, "images": [b64]}],
|
||||
"stream": False,
|
||||
},
|
||||
timeout=TIMEOUT_LLM,
|
||||
)
|
||||
response.raise_for_status()
|
||||
return response.json()["message"]["content"]
|
||||
|
||||
|
||||
def extract_ean(raw: str) -> str | None:
|
||||
"""Bereinigt Rohausgabe und extrahiert Ziffernfolge."""
|
||||
cleaned = re.sub(r"<think>.*?</think>", "", raw, flags=re.DOTALL).strip()
|
||||
digits = re.sub(r"\D", "", cleaned)
|
||||
return digits if digits else None
|
||||
|
||||
|
||||
def run() -> None:
|
||||
UPLOAD_DIR.mkdir(parents=True, exist_ok=True)
|
||||
server = ScannerServer(port=PORT, upload_dir=UPLOAD_DIR)
|
||||
server.start()
|
||||
|
||||
print(f"\nModel: {MODEL}")
|
||||
print(f"Prompt: {EAN_PROMPT!r}\n")
|
||||
print_qr(server.url())
|
||||
print(f"Upload-URL: {server.url()}\n")
|
||||
|
||||
runde = 0
|
||||
try:
|
||||
while True:
|
||||
runde += 1
|
||||
print(f"\n{'─'*50}")
|
||||
print(f"Runde {runde}: Bitte Foto hochladen — URL: {server.url()}")
|
||||
print(f"(Timeout {TIMEOUT_PHOTO:.0f}s — Formular auf dem Handy lädt sich nach 3s selbst zurück)")
|
||||
photo = server.get_photo(timeout=TIMEOUT_PHOTO)
|
||||
|
||||
if photo is None:
|
||||
print("Timeout — kein Foto empfangen. Nochmal? (j/n) ", end="", flush=True)
|
||||
if input().strip().lower() not in ("j", "ja", "y", "yes"):
|
||||
break
|
||||
continue
|
||||
|
||||
print(f"Foto empfangen: {photo} ({photo.stat().st_size} Bytes)")
|
||||
print(f"LLM-Anfrage läuft ({MODEL}) ...")
|
||||
|
||||
try:
|
||||
raw = query_llm(photo)
|
||||
except Exception as exc:
|
||||
print(f"LLM-Fehler: {exc}")
|
||||
continue
|
||||
|
||||
print(f"\nRohantwort:\n {raw!r}")
|
||||
|
||||
cleaned = re.sub(r"<think>.*?</think>", "", raw, flags=re.DOTALL).strip()
|
||||
if cleaned != raw.strip():
|
||||
print(f"Nach Think-Tag-Entfernung:\n {cleaned!r}")
|
||||
|
||||
ean = extract_ean(raw)
|
||||
if ean:
|
||||
print(f"\n✓ EAN erkannt: {ean} ({len(ean)} Stellen)")
|
||||
else:
|
||||
print("\n✗ Kein Barcode erkannt")
|
||||
|
||||
print("\nNochmal? (j/n) ", end="", flush=True)
|
||||
if input().strip().lower() not in ("j", "ja", "y", "yes"):
|
||||
break
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\nAbgebrochen.")
|
||||
finally:
|
||||
server.stop()
|
||||
print("Server gestoppt.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
run()
|
||||
Loading…
Add table
Add a link
Reference in a new issue