
    i,                        d Z ddlmZ ddlZddlZddlZddlZddlZddlZddl	Z
ddlZ
ddlmZ  ej                  e      ZdZdZdZdZd	Z G d
 d      Zy)a  Frame capture utility for the Edge Proxy.

Grabs a single JPEG frame from the robot's camera.  The primary method is an
HTTP snapshot endpoint exposed by MediaMTX.  If that endpoint is unavailable,
a **persistent** ffmpeg subprocess maintains an RTSP connection and continuously
decodes frames into JPEG.  Each ``capture()`` call returns the most recently
decoded frame without re-negotiating the RTSP session.
    )annotationsN)Optionalz$http://192.168.168.105:8889/cam/jpegzrtsp://192.168.168.105:8554/cam   s   s   c                  `    e Zd ZdZeef	 	 	 	 	 ddZddZddZddZ	ddZ
ddZddZdd	Zy
)FrameCapturea  Capture a single JPEG frame from the robot camera.

    Attempts an HTTP snapshot first (fast, ~100 ms).  Falls back to a
    **persistent** ffmpeg subprocess that holds the RTSP connection open and
    continuously pipes JPEG frames via stdout.  The latest frame is always
    available in ``capture()`` with near-zero latency.

    Args:
        snapshot_url: HTTP URL that returns a raw JPEG.  MediaMTX exposes this
            at ``/cam/jpeg`` when the API is enabled.
        rtsp_url: RTSP URL used for the persistent ffmpeg stream.
    c                   || _         || _        d | _        d | _        t	        j
                         | _        d | _        d| _        d| _	        d| _
        d | _        d| _        d| _        | j                         d u| _        | j                  rt        j!                  d       y t        j!                  d       | j#                          y )Ng        Fnoneu9   FrameCapture: HTTP snapshot available — using fast pathuK   FrameCapture: HTTP snapshot unavailable — starting persistent RTSP stream)_snapshot_url	_rtsp_url_proc_reader_thread	threadingLock_lock_latest_frame_frame_time_frame_wall_time_running_http_ok_last_capture_ms_last_capture_source_capture_httploggerinfo_ensure_rtsp_stream)selfsnapshot_urlrtsp_urls      F/home/nelsen/Projects/kognitive/edge-proxy/edge_proxy/frame_capture.py__init__zFrameCapture.__init__1   s    
 *! 26
:>^^%
.2"%'*(,'*)/! **,D8==KKSTKK] $$&    c                   t        j                         }| j                  r^| j                         }|L| j	                  dt        j                         |z
  dz         t
        j                  dt        |             |S | j                          | j                  5  | j                  }| j                  dkD  r$t        j                         | j                  z
  dz  nd}ddd       | j	                  dt        j                         |z
  dz         E"t
        j                  dt        |      |       |S t
        j                  d	t        |             |S t
        j                  d
       |S # 1 sw Y   xY w)zqGrab a single JPEG frame.

        Returns:
            Raw JPEG bytes, or ``None`` if all methods fail.
        Nhttp     @@)source
capture_msz0FrameCapture: HTTP snapshot succeeded (%d bytes)r   rtspz6FrameCapture: RTSP frame ready (%d bytes, age=%.1f ms)z)FrameCapture: RTSP frame ready (%d bytes)z2FrameCapture: RTSP stream not yet producing frames)time	monotonicr   r   _update_capture_statsr   debuglenr   r   r   r   )r   t0frameframe_age_mss       r   capturezFrameCapture.captureS   sg    ^^ ==&&(E **! $ 02 5? +  OQTUZQ[\ 	  "ZZ 	&&E ##a' !D$4$44> 	 	""(2-7 	# 	
 'LJ   H#e*U  LLMN-	 	s   AE..E7c                   | j                   5  | j                  dkD  r$t        j                         | j                  z
  dz  nd}| j                  }| j
                  }| j                  }ddd       t        | j                  xr, | j                  duxr | j                  j                         du       | j                  dS # 1 sw Y   ]xY w)z9Return current frame/capture stats for latency debugging.r   r$   N)r/   latest_frame_timestamplast_capture_mslast_capture_sourcertsp_runninghttp_snapshot_ok)r   r   r(   r)   r   r   r   boolr   r   pollr   )r   r/   latest_frame_tsr3   r4   s        r   	get_statszFrameCapture.get_stats   s    ZZ 	< ##a' !D$4$44> 
 #33O"33O"&";";	< )&5.#6 V$**D"8VTZZ__=NRV=V !%	
 		
	< 	<s   ACCc                   d| _         | j                  >	 | j                  j                          | j                  j                  d       d| _        | j                  #| j                  j                  d       d| _        t        j                  d       y# t        $ r | j                  j                          Y qw xY w)z+Shut down the persistent ffmpeg subprocess.FN   timeoutz+FrameCapture: persistent RTSP stream closed)
r   r   	terminatewait	Exceptionkillr   joinr   r   r   s    r   closezFrameCapture.close   s    ::!"

$$&

* DJ*$$Q$/"&DAB  "

!"s   6B #B=<B=c                   	 t         j                  j                  | j                  t              5 }|j
                  dk7  r*t        j                  d|j
                         	 ddd       y|j                         }|st        j                  d       	 ddd       y|cddd       S # 1 sw Y   yxY w# t         j                  j                  $ r }t        j                  d|       Y d}~yd}~wt        $ r }t        j                  d|       Y d}~yd}~wt        $ r }t        j                  d|       Y d}~yd}~ww xY w)	z7Try HTTP snapshot endpoint.  Returns raw bytes or None.r=      z.FrameCapture: HTTP snapshot returned status %dNz/FrameCapture: HTTP snapshot returned empty bodyz)FrameCapture: HTTP snapshot URL error: %sz(FrameCapture: HTTP snapshot OS error: %sz0FrameCapture: HTTP snapshot unexpected error: %s)urllibrequesturlopenr
   _HTTP_TIMEOUT_SECstatusr   r+   readerrorURLErrorOSErrorrA   )r   respdataexcs       r   r   zFrameCapture._capture_http   s    	''(:(:DU'V 
Z^;;#%LLH$++  
 
 yy{LL!RS
 
 
 
 
 ||$$ 	LLDcJ 	LLCSI 	LLKSQ	sd   /B3 1B'"B3 +(B'B3 B'	B3 'B0,B3 0B3 3D>C++D>7DD>D99D>c                   | j                   r'| j                  | j                  j                         y| j                  "	 | j                  j                          d| _        t
        j                  d| j                         	 t        j                  ddddddd	d
dddddd| j                  dddddddddgt        j                  t        j                  d      | _        d| _         t        j                  | j                   dd      | _        | j"                  j%                          y# t        $ r Y w xY w# t        $ r t
        j                  d       Y yw xY w) z;Start the persistent ffmpeg process if not already running.Nz5FrameCapture: starting persistent RTSP stream from %sffmpegz-fflagsnobufferz-flags	low_delayz
-avioflagsdirectz
-probesize32z-analyzeduration0z-rtsp_transporttcpz-iz-vfzfps=10z-f
image2pipez-vcodecmjpegz-q:v3-r   )stdoutstderrbufsizez&FrameCapture: ffmpeg not found in PATHTzframe-capture-reader)targetdaemonname)r   r   r8   rB   rA   r   r   r   
subprocessPopenPIPEDEVNULLFileNotFoundErrorwarningr   Thread_read_framesr   startrD   s    r   r   z FrameCapture._ensure_rtsp_stream   sE   ==TZZ3

8I8Q ::!

! DJKT^^\	#))zk ( $&%u$..8,wC "!))%DJ0 '..$$T8N
 	!!#E  4 ! 	NNCD	s%   D( AD7 (	D43D47EEc                p   	 | j                   | j                   j                  J t               }| j                   j                  }d}| j                  r	 |j	                  d      }|sn|j                  |       	 |j                  t              }|dk(  r|j                          n|dkD  r|d|= |j                  t        d      }|dk(  rn|dz   }t        |d|       }	|d|= |dz  }| j                  5  |	| _        t!        j"                         | _        t!        j                          | _        ddd       |dk(  rt        j)                  d	t+        |	             | j                  rd| _        t        j)                  ddt/               v r       yd       y# t
        $ r }t        j                  d|       Y d}~Ud}~ww xY w# 1 sw Y   xY w# t
        $ r"}t        j-                  d
|d       Y d}~d}~ww xY w# d| _        t        j)                  ddt/               v r       w d       w xY w)zBackground thread: read continuous JPEG stream from ffmpeg stdout.

        ffmpeg with ``-f image2pipe -vcodec mjpeg`` outputs concatenated JPEG
        images.  We split on SOI/EOI markers to extract individual frames.
        Nr   i   zFrameCapture: read error: %sT      z2FrameCapture: first RTSP frame received (%d bytes)z'FrameCapture: reader thread crashed: %s)exc_infoFz9FrameCapture: RTSP reader thread exited (frames read: %d)frame_count)r   r`   	bytearrayr   rM   rA   r   rk   extendfind_SOIclear_EOIbytesr   r   r(   r)   r   r   r   r,   rN   dir)
r   bufstreamrt   chunkrS   soieoi	frame_endr.   s
             r   rm   zFrameCapture._read_frames   s   5	F::)djj.?.?.KKK+CZZ&&FK--"KK.E 

5! ((4.Cby		 QwI((4+Cby !$aI!#jy/2EJYJ1$K <-2*+/>>+;(04		-<
 #a'PRUV[R\5  --Z "DMKKS'4'=FCDFW ! NN#A3G:< <  	XLLBCRVLWW	X "DMKKS'4'=FCDFsm   AG F 'BG 7:G	19G 	G&G<G GG 	GG 	H G;6H ;H  H 2H5c                b    | j                   5  || _        || _        d d d        y # 1 sw Y   y xY w)N)r   r   r   )r   r%   r&   s      r   r*   z"FrameCapture._update_capture_stats0  s.    ZZ 	/(.D%$.D!	/ 	/ 	/s   %.N)r   strr   r   returnNone)r   zOptional[bytes])r   dict)r   r   )r%   r   r&   floatr   r   )__name__
__module____qualname____doc___DEFAULT_SNAPSHOT_URL_DEFAULT_RTSP_URLr    r0   r:   rE   r   r   rm   r*    r!   r   r   r   #   sY     2)'' ' 
	'D,\
,C&8+$Z;Fz/r!   r   )r   
__future__r   iologgingosrf   r   r(   urllib.errorrH   urllib.requesttypingr   	getLoggerr   r   r   r   rK   rx   rz   r   r   r!   r   <module>r      sh    # 	  	      			8	$ ? 5   P/ P/r!   