Spaces:
Sleeping
Sleeping
| """Views for the browser-camera architecture. | |
| The browser captures the webcam and POSTs frames to ``/process``; the server | |
| runs MediaPipe + the DTW recognizer (via the ``FrameSession`` singleton) and | |
| returns landmarks + recognised glosses, which the browser draws. This is what | |
| lets the same Django app run locally *and* on Hugging Face (where the server | |
| has no camera). | |
| """ | |
| from __future__ import annotations | |
| from django.conf import settings | |
| from django.http import JsonResponse | |
| from django.shortcuts import render | |
| from django.views.decorators.csrf import csrf_exempt | |
| from django.views.decorators.http import require_GET, require_POST | |
| from lsf.lexicon import Lexicon | |
| from lsf.session import FrameSession | |
| _LEXICON: Lexicon | None = None | |
| MAX_FRAME_BYTES = 4 * 1024 * 1024 # 4 MB guard for a posted frame | |
| def _session() -> FrameSession: | |
| return FrameSession.instance( | |
| templates_path=settings.TEMPLATES_PATH, | |
| model_path=settings.MODEL_PATH, | |
| model_complexity=settings.MODEL_COMPLEXITY, | |
| refine_face_landmarks=settings.REFINE_FACE_LANDMARKS, | |
| ) | |
| def _lexicon() -> Lexicon: | |
| global _LEXICON | |
| if _LEXICON is None: | |
| _LEXICON = Lexicon(settings.LEXICON_PATH) | |
| return _LEXICON | |
| def index(request): | |
| return render(request, "interpreter/index.html") | |
| def process(request): | |
| """Receive one JPEG frame (raw body), return landmarks + any recognition.""" | |
| body = request.body | |
| if not body: | |
| return JsonResponse({"ok": False, "error": "Empty frame."}, status=400) | |
| if len(body) > MAX_FRAME_BYTES: | |
| return JsonResponse({"ok": False, "error": "Frame too large."}, status=413) | |
| return JsonResponse(_session().process_jpeg(body)) | |
| def depth(request): | |
| """Browser reports the device's depth/dual-pixel capability.""" | |
| dp = (request.POST.get("dual_pixel") or "0") in ("1", "true", "True") | |
| _session().set_depth_capability( | |
| dual_pixel=dp, | |
| reason=(request.POST.get("reason") or "").strip(), | |
| label=(request.POST.get("label") or "").strip(), | |
| ) | |
| return JsonResponse({"ok": True, "status": _session().status}) | |
| def status(request): | |
| return JsonResponse(_session().status) | |
| # -- recording (learn-by-example) ------------------------------------------- | |
| def record_start(request): | |
| label = (request.POST.get("label") or "").strip() | |
| if not label: | |
| return JsonResponse({"ok": False, "error": "Missing 'label'."}, status=400) | |
| _session().start_recording(label) | |
| return JsonResponse({"ok": True, "recording": label}) | |
| def record_stop(request): | |
| saved = _session().stop_recording() | |
| return JsonResponse({"ok": saved is not None, "saved": saved}) | |
| def record_cancel(request): | |
| _session().cancel_recording() | |
| return JsonResponse({"ok": True}) | |
| def clear(request): | |
| _session().clear_transcript() | |
| return JsonResponse({"ok": True}) | |
| # -- vocabulary ------------------------------------------------------------- | |
| def lexicon(request): | |
| lex = _lexicon() | |
| counts = _session().template_counts() | |
| return JsonResponse({"categories": lex.categories(), "words": lex.as_entries(counts)}) | |
| def vocab_add(request): | |
| fr = (request.POST.get("fr") or "").strip() | |
| if not fr: | |
| return JsonResponse({"ok": False, "error": "Missing 'fr'."}, status=400) | |
| entry = _lexicon().add_word( | |
| fr, | |
| en=(request.POST.get("en") or "").strip(), | |
| category=(request.POST.get("category") or "Personnalisés").strip(), | |
| tip=(request.POST.get("tip") or "").strip(), | |
| ) | |
| return JsonResponse({"ok": True, "word": entry}) | |
| def vocab_delete(request): | |
| gloss = (request.POST.get("gloss") or "").strip() | |
| if not gloss: | |
| return JsonResponse({"ok": False, "error": "Missing 'gloss'."}, status=400) | |
| return JsonResponse({"ok": _session().delete_gloss(gloss), "gloss": gloss}) | |