# Copyright 2026 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from pathlib import Path from fastapi import APIRouter, HTTPException from fastapi.responses import FileResponse, HTMLResponse from artemis.config import IMAGES_DIR, WORKSPACE_ROOT try: from admin_console.database.repositories.session_repository import session_repo from admin_console.services.media_service import media_service except ImportError: from apps.admin_console.database.repositories.session_repository import session_repo from apps.admin_console.services.media_service import media_service router = APIRouter(tags=["media"]) @router.get("/admin", response_class=HTMLResponse) @router.get("/debug", response_class=HTMLResponse) async def get_admin_index(): admin_index = Path(__file__).resolve().parent.parent / "index.html" if admin_index.exists(): return admin_index.read_text(encoding="utf-8") admin_index_alt = WORKSPACE_ROOT / "apps" / "admin_console" / "index.html" if admin_index_alt.exists(): return admin_index_alt.read_text(encoding="utf-8") return "

Admin Console index.html not found

" @router.get("/images/{image_name}") @router.get("/api/images/{image_name}") async def get_image(image_name: str): if not image_name.endswith(".jpg"): image_name += ".jpg" image_path = IMAGES_DIR / image_name if not image_path.exists(): raise HTTPException(status_code=404, detail="Image not found") return FileResponse(image_path, media_type="image/jpeg") @router.get("/videos/{video_path:path}") async def get_video(video_path: str): path = Path(video_path) if not path.exists(): if not path.is_absolute(): candidate = WORKSPACE_ROOT / path if candidate.exists(): path = candidate else: candidate_root = Path("/" + video_path.lstrip("/")) if candidate_root.exists(): path = candidate_root else: path = candidate if not path.exists(): raise HTTPException(status_code=404, detail=f"Video not found at {path}") suffix = path.suffix.lower() media_type = "video/mp4" if suffix == ".webm": media_type = "video/webm" elif suffix == ".mkv": media_type = "video/x-matroska" return FileResponse(path, media_type=media_type) @router.get("/api/sessions/{session_id}/video") async def get_session_video(session_id: str): video_rec_map = session_repo.get_video_recordings_map() video_idx = media_service.build_video_index() row = session_repo.get_session_by_id(session_id) row_dict = dict(row) if row else {"session_id": session_id} recording = session_repo.get_video_recording_for_session(session_id) recording_status = str((recording or {}).get("status") or "") if recording_status in ("recording", "finalizing"): return { "session_id": session_id, "status": "processing", "has_video": False, "video_url": None, "video_segments": [], "retry_after_ms": 750, } if recording_status == "failed": v_url = media_service.resolve_video_url(row_dict, video_rec_map, video_idx) if not v_url: return { "session_id": session_id, "status": "failed", "has_video": False, "video_url": None, "video_segments": [], "message": recording.get("error") or "Recording finalization failed", } # If a video was recovered/found, update DB to ready and proceed to serve it session_repo.mark_recording_ready(session_id, v_url) v_url = media_service.resolve_video_url(row_dict, video_rec_map, video_idx) video_segments = media_service.resolve_video_segments(v_url) if v_url: version = int(float((recording or {}).get("end_time") or row_dict.get("end_time") or 0) * 1000) separator = "&" if "?" in v_url else "?" versioned_url = f"{v_url}{separator}v={version}" if version else v_url for segment in video_segments: segment_separator = "&" if "?" in segment["url"] else "?" segment["url"] = ( f"{segment['url']}{segment_separator}v={version}" if version else segment["url"] ) return { "session_id": session_id, "status": "ready", "has_video": True, "video_url": versioned_url, "video_segments": video_segments, } return { "session_id": session_id, "status": "unavailable", "has_video": False, "video_url": None, "video_segments": [], } @router.get("/local_file") async def get_local_file(path: str): p, media_type = media_service.get_safe_local_file(path) return FileResponse(p, media_type=media_type) @router.get("/api/sessions/{session_id}/plan") async def get_task_plan(session_id: str): return {"plan": media_service.get_task_plan_content(session_id)} @router.get("/api/sessions/{session_id}/notes") async def get_all_notes(session_id: str): return {"notes": media_service.get_session_notes_content(session_id)}