from fastapi import APIRouter, File, UploadFile, Depends, HTTPException, Form from firebase_admin import firestore import hashlib import uuid from ...core.auth import require_role, get_user from typing import List, Optional, Dict from app.services.youtube_downloader import download_youtube_video, parse_urls from ...services.processor import process_video from typing import Dict from ...core.firebase import db from ...services.clip_assignment import ClipAssignmentService router = APIRouter() @router.get("/videos") async def get_videos(user_info=Depends(get_user)): try: # Récupération des vidéos globales videos_ref = db.collection('videos') videos = [] # Récupération du statut utilisateur user_status_ref = db.collection('user_video_status').document(user_info['uid']) user_status_doc = user_status_ref.get() user_statuses = user_status_doc.to_dict() if user_status_doc.exists else {"video_status": []} # Fusion des données for doc in videos_ref.stream(): video_data = doc.to_dict() video_data['id'] = doc.id # Ajout du statut spécifique à l'utilisateur user_status = next( (status for status in user_status_doc.get('video_status', []) if status['uuid'] == doc.id), None ) video_data['user_status'] = user_status['status'] if user_status else 'ready' videos.append(video_data) return {"videos": videos} except Exception as e: raise HTTPException(status_code=500, detail=str(e)) @router.post("/videos/upload") async def upload_video( sport_id: str = Form(...), file: Optional[UploadFile] = File(None), youtube_url: Optional[str] = Form(None), user_info=Depends(require_role(["admin", "user_intern"])) ): try: responses = [] urls_to_process = [] # Collecter toutes les URLs à traiter if youtube_url: # Cas URL YouTube directe - peut contenir plusieurs URLs séparées par des points-virgules urls_to_process.extend(parse_urls(youtube_url)) elif file and file.content_type in ['text/plain', 'text/csv']: # Cas fichier texte avec URLs text_content = (await file.read()).decode('utf-8') urls_to_process.extend(parse_urls(text_content)) elif file: # Cas upload direct de vidéo content = await file.read() return await process_single_video(content, sport_id, user_info, file.filename) if not urls_to_process and not file: raise HTTPException(status_code=400, detail="Aucune URL YouTube valide trouvée") # Traiter chaque URL for url in urls_to_process: try: print(f"[DEBUG] Processing YouTube URL: {url}") content = await download_youtube_video(url) result = await process_single_video(content, sport_id, user_info, f"YouTube: {url}") responses.append({ "url": url, "status": "success", "video_id": result["video_id"] }) except Exception as e: print(f"[ERROR] Failed to process URL {url}: {str(e)}") responses.append({ "url": url, "status": "error", "error": str(e) }) return { "message": f"Traitement terminé pour {len(responses)} vidéos", "results": responses } except Exception as e: print(f"[ERROR] Upload failed: {str(e)}") raise HTTPException(status_code=500, detail=str(e)) async def process_single_video(content: bytes, sport_id: str, user_info: dict, title: str): """Traite une seule vidéo et retourne son ID""" video_uuid = str(uuid.uuid4()) active_assignement = True # Calcul du hash MD5 md5_hash = hashlib.md5() md5_hash.update(content) file_hash = md5_hash.hexdigest() # Vérification des doublons existing_videos = db.collection('videos').where('md5_hash', '==', file_hash).get() if len(existing_videos) > 0: return {"message": "Cette vidéo existe déjà", "video_id": existing_videos[0].id} # Création de l'entrée Firestore video_data = { "uuid": video_uuid, "sport_id": sport_id, "upload_date": firestore.SERVER_TIMESTAMP, "uploaded_by": user_info['uid'], "title": title, "status": "downloading", "md5_hash": file_hash, "paths": { "raw": f"{sport_id}/raw/{video_uuid}_raw.mp4", "compressed": f"{sport_id}/compressed/{video_uuid}_compressed.mp4", "reduced_videos": [] }, "scenes": [], "reduced_scenes": [] } db.collection('videos').document(video_uuid).set(video_data) await process_video(video_uuid, content, user_info['uid'], sport_id) if user_info['role'] in ["admin", "user_intern"] and active_assignement: clip_service = ClipAssignmentService() await clip_service.assign_clips_to_user(user_info["uid"], user_info["role"]) return {"message": "Upload initié", "video_id": video_uuid} @router.put("/videos/{video_id}/status") async def update_video_status( video_id: str, status: str, user_info=Depends(get_user) ): try: # Vérifier si la vidéo existe video_ref = db.collection('videos').document(video_id) video_doc = video_ref.get() if not video_doc.exists: raise HTTPException(status_code=404, detail="Vidéo non trouvée") # Mettre à jour ou créer le statut utilisateur user_status_ref = db.collection('user_video_status').document(user_info['uid']) user_status_doc = user_status_ref.get() if user_status_doc.exists: statuses = user_status_doc.get('video_status', []) # Mettre à jour le statut existant ou ajouter un nouveau status_updated = False for s in statuses: if s['uuid'] == video_id: s['status'] = status status_updated = True break if not status_updated: statuses.append({ 'uuid': video_id, 'status': status }) user_status_ref.update({'video_status': statuses}) else: # Créer un nouveau document avec le statut user_status_ref.set({ 'video_status': [{ 'uuid': video_id, 'status': status }] }) return {"message": "Statut mis à jour avec succès"} except Exception as e: raise HTTPException(status_code=500, detail=str(e)) @router.post("/clips/assign") async def assign_clips(user_info=Depends(require_role(["admin", "user_intern"]))): try: clip_service = ClipAssignmentService() await clip_service.assign_clips_to_user(user_info["uid"], user_info["role"]) return {"message": "Clips assignés avec succès"} except Exception as e: raise HTTPException(status_code=500, detail=str(e)) @router.delete("/clips/{clip_id}/user") async def remove_clip(clip_id: str, user_info=Depends(require_role(["admin"]))): try: clip_service = ClipAssignmentService() await clip_service.remove_clip_from_user(user_info["uid"], clip_id) return {"message": "Clip retiré avec succès"} except Exception as e: raise HTTPException(status_code=500, detail=str(e)) @router.post("/clips/sync") async def sync_user_clips(user_info=Depends(require_role(["admin", "user_intern"]))): """Synchronise les clips disponibles pour l'utilisateur lors de sa connexion""" try: clip_service = ClipAssignmentService() await clip_service.assign_clips_to_user(user_info["uid"], user_info["role"]) return {"message": "Clips synchronisés avec succès"} except Exception as e: raise HTTPException(status_code=500, detail=str(e)) @router.get("/clips/debug/{user_id}") async def debug_user_clips(user_id: str, user_info=Depends(require_role(["admin"]))): """Endpoint de débogage pour vérifier les clips d'un utilisateur""" try: user_ref = db.collection('users').document(user_id) user_doc = user_ref.get() if not user_doc.exists: raise HTTPException(status_code=404, detail="Utilisateur non trouvé") user_data = user_doc.to_dict() return { "clips_count": len(user_data.get("clips", [])), "clips": user_data.get("clips", []) } except Exception as e: raise HTTPException(status_code=500, detail=str(e))