Ë
    Ô0æj'  ã                  ó\   — d Z ddlmZ ddlZddlmZ d\  ZZZ	Z
 G d„ d«      Zddd	„Zdd
„Zy)aò  Visual layer: on flagged windows only, check whether a visible face is actually talking.

MediaPipe FaceMesh (CPU) -> per-face mouth-opening ratio (inner-lip gap / face height). A talking face
shows high variance in that ratio. Used to raise/lower severity of timing issues:
  - no face / tiny face  -> off-screen speaker, timing error is less visible
  - large face, talking  -> lip-sync error is very visible
Also verifies the two videos are frame-identical (the dub must not alter the picture).
é    )ÚannotationsNé   )Úsample_frames)é   é   é
   é˜   c                  ó    — e Zd Zddd„Zddd„Zy)ÚMouthAnalyzerc                ój   — dd l }|j                  j                  j                  d|ddd¬«      | _        y )Nr   Fg      à?)Ústatic_image_modeÚmax_num_facesÚrefine_landmarksÚmin_detection_confidenceÚmin_tracking_confidence)Ú	mediapipeÚ	solutionsÚ	face_meshÚFaceMeshÚmesh)ÚselfÚ	max_facesÚmps      ú2/root/repos/pocs/video-evals/src/dubeval/visual.pyÚ__init__zMouthAnalyzer.__init__   s5   € Ûà—L‘L×*Ñ*×3Ñ3ÀeÐ[dØEJÐehØLOð 4ó Qˆ�	ó    c                óv  — t        |||||«      }i }i }d}	|D �]±  }
| j                  j                  |
«      }|j                  sŒ,|	dz  }	t	        |j                  d„ ¬«      }t        |«      D �]Z  \  }}|j                  }t        j                  |D �cg c]  }|j                  ‘Œ c}«      }t        j                  |D �cg c]  }|j                  ‘Œ c}«      }t        |t           j                  |t           j                  z
  «      dz   }|j                  |g «      j                  t        |t            j                  |t"           j                  z
  «      |z  «       t%        |j'                  |d«      t)        |j%                  «       |j+                  «       z
  |j%                  «       |j+                  «       z
  z  «      «      ||<   �Œ] �Œ´ |j-                  «       D ��ci c]3  \  }}t/        |«      dk\  sŒ|t)        t        j0                  |«      «      “Œ5 }}}|rt%        ||j&                  ¬«      nd }|�|j'                  |d«      nd}|rt%        |j3                  «       «      nd}|d uxr |dk\  }t5        t/        |«      «      |	t7        |d	«      t7        |d	«      |r|d
k\  dœS d dœS c c}w c c}w c c}}w )Nr   r   c                ó4   — | j                   d   j                  S )Nr   )ÚlandmarkÚx)Úms    r   ú<lambda>z'MouthAnalyzer.analyze.<locals>.<lambda>%   s   € À1Ç:Á:ÈaÁ=Ç?Á?€ r   )Úkeyg�íµ ÷Æ°>g        é   gü©ñÒMbp?é   ç{®Gáz”?)Úframes_analyzedÚface_framesÚmax_face_areaÚmouth_activityÚspeaking_likely)r   r   ÚprocessÚmulti_face_landmarksÚsortedÚ	enumerater   ÚnpÚarrayr    ÚyÚabsÚCHINÚFOREHEADÚ
setdefaultÚappendÚ	LOWER_LIPÚ	UPPER_LIPÚmaxÚgetÚfloatÚminÚitemsÚlenÚstdÚvaluesÚintÚround)r   ÚvideoÚstartÚendÚfpsÚwidthÚframesÚtracksÚareasr(   ÚfÚresÚfacesÚslotÚlmÚpÚqÚxsÚysÚface_hÚsÚvÚactivityÚbestÚmouthÚmax_areaÚusables                              r   ÚanalyzezMouthAnalyzer.analyze   s^  € Ü˜u e¨S°#°uÓ=ˆØ)+ˆØ"$ˆØˆØó 	nˆAØ—)‘)×#Ñ# AÓ&ˆCØ×+Ò+ØØ˜1ÑˆKä˜3×3Ñ3Ñ9RÔSˆEÜ% eÓ,ó n‘��bØ—K‘K�Ü—X‘X¨AÖ. q˜qŸs›sÒ.Ó/�Ü—X‘X¨AÖ. q˜qŸs›sÒ.Ó/�Ü˜Qœt™WŸY™Y¨¬8©¯©Ñ6Ó7¸$Ñ>�Ø×!Ñ! $¨Ó+×2Ñ2´3°q¼±|·~±~ÈÌ)ÉÏÉÑ7VÓ3WÐZ`Ñ3`ÔaÜ! %§)¡)¨D°#Ó"6¼¸r¿v¹v»xÈ"Ï&É&Ë(Ñ?RÐWY×W]ÑW]ÓW_Ðbd×bhÑbhÓbjÑWjÑ>kÓ8lÓm��d“ònð	nð 5;·L±L³N×R©D¨A¨qÄcÈ!ÃfÐPQÃk�A”uœRŸV™V A›YÓ'Ñ'ÐRˆÑRÙ2:Œs�8 §¡Õ.ÀˆØ+/Ð+;�—‘˜T 3Ô'ÀˆÙ*/”3�u—|‘|“~Ô&°SˆØ˜TÐ!Ò7 h°%Ñ&7ˆä"¤3 v£;Ó/Ø&Ü" 8¨QÓ/Ü# E¨1›oÙ28 ¨¡ñ
ð 	
ð
 ?Cñ
ð 	
ùò /ùÚ.ùó Ss   ÂJ+Â?J0Ç J5Ç7!J5N)r%   )r   rB   )g       @i€  )
rE   r<   rF   r<   rG   r<   rH   rB   ÚreturnÚdict)Ú__name__Ú
__module__Ú__qualname__r   r]   © r   r   r   r      s   „ ôQõ
r   r   c                óŠ  — g }t        j                  dt        d|dz
  «      |«      D �]  }t        | t	        |«      t	        |«      dz   d|«      }t        |t	        |«      t	        |«      dz   d|«      }t        |«      rt        |«      sŒct	        t        j                  |d   j                  t        «      |d   j                  t        «      z
  «      j                  «       «      }	|	dkD  sŒÄ|j                  dddd	t        t	        |«      d
«      t        t	        |«      dz   d
«      d|	d›d�ddt        |	d«      idœ	«       �Œ |S )z[Compare sparse frames; any large pixel difference means the picture was changed or shifted.g      ð?g{®Gáz¤?é   r   é   ÚVIDEO_MISMATCHrD   Úmajorgffffffæ?é   z;Video frame differs from the original (mean abs pixel diff z.1fz).zYDub must reuse the original video stream untouched (stream copy); check re-encode/offset.Úmean_abs_diffr   )	ÚcategoryÚsourceÚseverityÚ
confidencerE   rF   ÚdescriptionÚfix_hintÚevidence)r0   Úlinspacer:   r   r<   r?   r3   ÚastyperB   Úmeanr7   rC   )
ÚorigÚdubÚdurationÚnrH   ÚissuesÚtÚaÚbÚdiffs
             r   Úvideo_identityr~   ;   s   € à€FÜ�[‰[˜œc # x°#¡~Ó6¸Ó:ó ˆÜ˜$¤ a£¬%°«(°T©/¸2¸uÓEˆÜ˜#œu Q›x¬¨q«°D©¸"¸eÓDˆÜ�1ŒvœS œVØÜ”R—V‘V˜A˜a™DŸK™K¬Ó,¨q°©t¯{©{¼3Ó/?Ñ?Ó@×EÑEÓGÓHˆØ�"‹9Ø�M‰MØ,¸ÈWÐdgÜœu Q›x¨Ó+´E¼%À»(ÀT¹/È1Ó4MØ!\Ð]aÐbeÐ\fÐfhÐiØwØ,¬e°D¸!«nÐ=ñö ðð €Mr   c                óz  — g d¢}|j                  | d   «      }| d   }|d   }|€!|dv r‹t        d|dz
  «      }| d	xx   d
z  cc<   nn|rH|dv rD|d   dk\  rt        d|dz   «      }| d	xx   dz  cc<   t        t        d| d   dz   «      d«      | d<   n$|du r |dv rt        d|dz   «      }| d	xx   dz  cc<   ||   | d<   y)z9Re-weight a timing issue with visual evidence (in place).)Úminorrh   Úcriticalrm   rk   r+   N)ÚEARLY_CUTOFFÚMID_LINE_GAPÚ
LATE_STARTÚOVERRUNÚEARLY_STARTr   r   ro   zF No clear face on screen (likely off-screen speaker), so less visible.)r‚   rƒ   r„   ÚMISSING_DUBr)   r&   ri   zH A visible face is moving its mouth during this window (lip-sync break).g\�Âõ(\ï?rn   gš™™™™™¹?F)r…   ÚUNMATCHED_SPEECHz8 On-screen face is not talking while dubbed voice plays.)Úindexr:   r=   rC   )ÚissueÚvisÚorderÚsevÚcatÚtalkings         r   Úadjustr�   O   sõ   € â*€EØ
�+‰+�e˜JÑ'Ó
(€CØ
�
Ñ
€CØÐ#Ñ$€GØ€ØÐZÑZÜ�a˜˜q™“/ˆCØ�-Ó Ð$lÑlÔ Ù	�SÐYÑYØˆÑ 4Ò'Ü�a˜˜q™“/ˆCØˆmÓÐ jÑjÓÜ#¤C¨¨e°LÑ.AÀCÑ.GÓ$HÈ!ÓLˆˆlÒØ	�EÑ	˜cÐ%DÑDÜ�!�S˜1‘W‹oˆØˆmÓÐ ZÑZÓØ˜c™
€Eˆ*Òr   )rf   é    )rw   r<   rx   rB   rH   rB   r^   z
list[dict])rŠ   r_   r‹   r_   r^   ÚNone)Ú__doc__Ú
__future__r   Únumpyr0   Úmediar   r9   r8   r5   r4   r   r~   r�   rc   r   r   ú<module>r—      s8   ðñõ #ã å  à'6Ñ $€	ˆ9�h ÷&
ñ &
ôRô(#r   