#!/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".*?", "", 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".*?", "", 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()