11import asyncio
22from typing import Optional
33from fastapi import Header , UploadFile
4- from fastapi .responses import JSONResponse
4+ from fastapi .responses import JSONResponse , FileResponse
55from fastapi import APIRouter , FastAPI , File , HTTPException , status
66from dto .transcription_dto import TranscriptionRequest
77from dto .summarizer_dto import SummaryRequest
88from dto .video_analytics_dto import VideoAnalyticsRequest
9+ from dto .video_metadata_dto import VideoDurationRequest
910from pipeline import Pipeline
1011import json , os
1112import subprocess , re
2223from components .va .va_pipeline_service import VideoAnalyticsPipelineService , PipelineOptions
2324from utils .session_manager import generate_session_id
2425from dto .search_dto import SearchRequest
26+ from utils .session_state_manager import SessionState
2527import logging
2628logger = logging .getLogger (__name__ )
2729
@@ -523,6 +525,126 @@ async def stream_statistics():
523525
524526 return StreamingResponse (stream_statistics (), media_type = "application/json" )
525527
528+ @router .post ("/mark-video-usage" )
529+ def mark_video_usage (
530+ session_id : str = Header (None , alias = "X-Session-ID" )
531+ ):
532+ """
533+ Mark that a video is being used in the current session.
534+
535+ """
536+ if not session_id :
537+ raise HTTPException (
538+ status_code = 400 ,
539+ detail = "X-Session-ID header is required"
540+ )
541+
542+ try :
543+ with SessionState ._lock :
544+ if session_id not in SessionState ._sessions :
545+ SessionState ._sessions [session_id ] = {}
546+ SessionState ._sessions [session_id ]['has_video' ] = True
547+
548+ logger .info (f"Session { session_id } : Video usage marked" )
549+ return JSONResponse (
550+ status_code = 200 ,
551+ content = {"status" : "success" , "message" : "Video usage marked for session" }
552+ )
553+
554+ except Exception as e :
555+ logger .error (f"Session { session_id } : Error marking video usage: { e } " )
556+ raise HTTPException (
557+ status_code = 500 ,
558+ detail = f"Error marking video usage: { e } "
559+ )
560+
561+ @router .post ("/store-video-duration" )
562+ def store_video_duration (
563+ request : VideoDurationRequest ,
564+ session_id : str = Header (None , alias = "X-Session-ID" )
565+ ):
566+ """
567+ Store video duration
568+
569+ """
570+ if not session_id :
571+ raise HTTPException (
572+ status_code = 400 ,
573+ detail = "X-Session-ID header is required"
574+ )
575+
576+ try :
577+ duration = request .duration
578+
579+ if not duration or duration <= 0 :
580+ raise HTTPException (
581+ status_code = 400 ,
582+ detail = "Invalid duration: duration must be greater than 0"
583+ )
584+
585+ # Store the video duration in session state
586+ SessionState .set_video_duration (session_id , duration )
587+
588+ return JSONResponse (
589+ status_code = 200 ,
590+ content = {"status" : "success" , "message" : f"Video duration stored: { duration :.2f} s" }
591+ )
592+
593+ except HTTPException as http_exc :
594+ raise http_exc
595+ except Exception as e :
596+ logger .error (f"Session { session_id } : Error storing video duration: { e } " )
597+ raise HTTPException (
598+ status_code = 500 ,
599+ detail = f"Error storing video duration: { e } "
600+ )
601+
602+ @router .post ("/store-audio-duration" )
603+ def store_audio_duration (
604+ request : VideoDurationRequest ,
605+ session_id : str = Header (None , alias = "X-Session-ID" )
606+ ):
607+ """
608+ Store audio duration
609+
610+ """
611+
612+ if not session_id :
613+ raise HTTPException (
614+ status_code = 400 ,
615+ detail = "X-Session-ID header is required"
616+ )
617+
618+ try :
619+ duration = request .duration
620+
621+ if not duration or duration <= 0 :
622+ raise HTTPException (
623+ status_code = 400 ,
624+ detail = "Invalid duration: duration must be greater than 0"
625+ )
626+
627+ SessionState .set_audio_duration (session_id , duration )
628+
629+ with SessionState ._lock :
630+ if session_id not in SessionState ._sessions :
631+ SessionState ._sessions [session_id ] = {}
632+ SessionState ._sessions [session_id ]['has_audio' ] = True
633+
634+ return JSONResponse (
635+ status_code = 200 ,
636+ content = {"status" : "success" , "message" : f"Audio duration stored: { duration :.2f} s" }
637+ )
638+
639+ except HTTPException as http_exc :
640+ raise http_exc
641+ except Exception as e :
642+ logger .error (f"Session { session_id } : Error storing audio duration: { e } " )
643+ raise HTTPException (
644+ status_code = 500 ,
645+ detail = f"Error storing audio duration: { e } "
646+ )
647+
526648@router .post ("/content-segmentation" )
527649def content_segmentation (request : SummaryRequest ):
528650 """
@@ -534,17 +656,22 @@ def content_segmentation(request: SummaryRequest):
534656 raise HTTPException (status_code = 429 , detail = "Session Active, Try Later" )
535657
536658 pipeline = Pipeline (request .session_id )
659+
660+ # Log session state before validation
661+ session_state = SessionState .get_session_state (request .session_id )
662+ logger .info (f"📋 Content-segmentation request for session: { request .session_id } " )
663+ logger .info (f" Session state: { session_state } " )
537664
538665 try :
539666 contents_json = pipeline .run_content_segmentation ()
540- logger .info ("content segmentation generated successfully." )
541- JSONResponse (content = {"session_id" : request .session_id })
667+ logger .info ("✅ content segmentation generated successfully." )
668+ return JSONResponse (content = {"session_id" : request .session_id })
542669
543670 except HTTPException as http_exc :
544671 raise http_exc
545672
546673 except Exception as e :
547- logger .exception (f"Error during content segmentation: { e } " )
674+ logger .exception (f"❌ Error during content segmentation: { e } " )
548675 raise HTTPException (
549676 status_code = 500 ,
550677 detail = f"content segmentation failed: { e } "
@@ -576,5 +703,136 @@ def search_content(request: SearchRequest):
576703 detail = f"Search failed: { e } "
577704 )
578705
706+ @router .get ("/check-recorded-videos" )
707+ def check_recorded_videos (x_session_id : Optional [str ] = Header (None )):
708+ """
709+ Check which video files were saved for a session after RTSP recording.
710+ Returns the priority-ordered available video (back > board > front).
711+
712+ """
713+ if not x_session_id :
714+ raise HTTPException (
715+ status_code = 400 , detail = "Missing required header: x-session-id"
716+ )
717+
718+ try :
719+ project_config = RuntimeConfig .get_section ("Project" )
720+ base_path = os .path .join (
721+ project_config .get ("location" ),
722+ project_config .get ("name" ),
723+ x_session_id
724+ )
725+
726+ if not os .path .exists (base_path ):
727+ logger .warn (f"Session path does not exist: { base_path } " )
728+ return JSONResponse (
729+ content = {
730+ "session_id" : x_session_id ,
731+ "back" : None ,
732+ "board" : None ,
733+ "front" : None ,
734+ "selected_video" : None ,
735+ "message" : "No session path found"
736+ },
737+ status_code = 200
738+ )
739+
740+ # Check which videos exist
741+ videos = {
742+ "back" : None ,
743+ "board" : None ,
744+ "front" : None ,
745+ }
746+
747+ back_path = os .path .join (base_path , "back.mp4" )
748+ content_path = os .path .join (base_path , "content.mp4" )
749+ front_path = os .path .join (base_path , "front.mp4" )
750+
751+ if os .path .exists (back_path ):
752+ videos ["back" ] = back_path
753+ if os .path .exists (content_path ):
754+ videos ["board" ] = content_path
755+ if os .path .exists (front_path ):
756+ videos ["front" ] = front_path
757+
758+ # Select highest priority video (back > board > front)
759+ selected_video = None
760+ if videos ["back" ]:
761+ selected_video = "back"
762+ elif videos ["board" ]:
763+ selected_video = "board"
764+ elif videos ["front" ]:
765+ selected_video = "front"
766+
767+ return JSONResponse (
768+ content = {
769+ "session_id" : x_session_id ,
770+ "back" : videos ["back" ],
771+ "board" : videos ["board" ],
772+ "front" : videos ["front" ],
773+ "selected_video" : selected_video ,
774+ "selected_path" : videos [selected_video ] if selected_video else None
775+ },
776+ status_code = 200
777+ )
778+
779+ except Exception as e :
780+ logger .error (f"Error checking recorded videos: { e } " )
781+ raise HTTPException (status_code = 500 , detail = str (e ))
782+
783+ @router .get ("/recorded-video/{videoType}" )
784+ def get_recorded_video (videoType : str , x_session_id : Optional [str ] = Header (None ), session_id : Optional [str ] = None ):
785+ """
786+ Stream a recorded video file (back.mp4, board.mp4, or front.mp4).
787+
788+ """
789+ # Accept session ID from either header or query parameter
790+ actual_session_id = x_session_id or session_id
791+ if not actual_session_id :
792+ raise HTTPException (
793+ status_code = 400 , detail = "Missing required session ID: x-session-id header or ?session_id query parameter"
794+ )
795+
796+ if videoType not in ['back' , 'board' , 'front' ]:
797+ raise HTTPException (
798+ status_code = 400 , detail = f"Invalid videoType: { videoType } . Must be 'back', 'board', or 'front'"
799+ )
800+
801+ try :
802+ backend_video_type = "content" if videoType == "board" else videoType
803+
804+ project_config = RuntimeConfig .get_section ("Project" )
805+ video_path = os .path .join (
806+ project_config .get ("location" ),
807+ project_config .get ("name" ),
808+ actual_session_id ,
809+ f"{ backend_video_type } .mp4"
810+ )
811+
812+ if not os .path .exists (video_path ):
813+ raise HTTPException (
814+ status_code = 404 ,
815+ detail = f"Video file not found: { videoType } .mp4"
816+ )
817+
818+ logger .info (f"Serving video file: { video_path } for session { actual_session_id } " )
819+
820+ file_response = FileResponse (
821+ path = video_path ,
822+ media_type = "video/mp4" ,
823+ filename = f"{ videoType } .mp4"
824+ )
825+ file_response .headers ["Accept-Ranges" ] = "bytes"
826+ file_response .headers ["Access-Control-Allow-Origin" ] = "*"
827+ file_response .headers ["Cache-Control" ] = "no-cache"
828+
829+ return file_response
830+
831+ except HTTPException :
832+ raise
833+ except Exception as e :
834+ logger .error (f"Error serving recorded video: { e } " )
835+ raise HTTPException (status_code = 500 , detail = str (e ))
836+
579837def register_routes (app : FastAPI ):
580838 app .include_router (router )
0 commit comments