§
    áhÇji&  ã                   ór  — d 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	e
¦  «                             ¦   «         j        Zej        Zedz  Zedz  Z ee¦  «         ee¦  «         ee¦  «        fD ]&Zeej        vrej                             de¦  «         Œ'ej        dk    rC	 ej                             d¬¦  «         ej                             d¬¦  «         n# e$ r Y nw xY wdd	lmZmZmZmZ dd
lm Z  ddl!m"Z"m#Z# ddl$m%Z%  G d„ dej&        ¦  «        Z'e(dk    r ej)        ¦   «          dS dS )aÑ  
Tests for Reverse Motion & Multi-Character Entrance Pipeline
Verifies:
1. reverse_motion metadata in muse_ai_video_prompts.json and episodes/ep01/prompts/muse_prompts.json
2. reverse_video_file in production_orchestrator.py
3. reverse_video in pipeline_helper.py
4. run_shot.py CLI arguments and signature
5. production_orchestrator.py CLI arguments
6. web_review API /api/matrix/ep01 reverse_motion serialization
7. GenerateRequest model accepting reverse_motion
é    N)ÚPathÚ05_Production_PipelineÚ
web_reviewÚwin32úutf-8©Úencoding)Úreverse_video_fileÚ
get_ffmpegÚload_muse_promptsÚget_all_shots)Úreverse_video)ÚappÚGenerateRequest)Ú
TestClientc                   óz   — e Zd ZdZed„ ¦   «         Zed„ ¦   «         Zd„ Zd„ Zd„ Z	d„ Z
d„ Zd	„ Zd
„ Zd„ Zd„ Zd„ ZdS )ÚTestReverseMotionPipelinez5Test suite for Reverse Motion Video & Audio Pipeline.c                 óÊ  — t          t          ¦  «        | _        t          t	          j        d¬¦  «        ¦  «        | _        t          ¦   «         | _        | j        dz  | _	        | j        ddddddddd	d
dddddddddt          | j	        ¦  «        g}t          j        |dd¬¦  «        }|j        dk    s| j	                             ¦   «         st          d|j        › �¦  «        ‚d S )NÚkieu_test_reverse_)Úprefixzsample_input.mp4z-yz-fÚlavfiz-iz!color=c=navy:s=320x240:d=1.0:r=24zsine=f=440:d=1.0z-c:vÚlibx264z-pix_fmtÚyuv420pz-c:aÚaacz-b:aÚ128kz-arÚ48000T)Úcapture_outputÚtextr   z)Failed to generate synthetic test video: )r   r   Úclientr   ÚtempfileÚmkdtempÚtemp_dirr   Ú
ffmpeg_exeÚsample_videoÚstrÚ
subprocessÚrunÚ
returncodeÚexistsÚRuntimeErrorÚstderr)ÚclsÚcmdÚress      ú;C:\Projects\KieuStory\tests\test_reverse_motion_pipeline.pyÚ
setUpClassz$TestReverseMotionPipeline.setUpClass7   sê   € å¥‘_”_ˆŒ
Ý�HÔ,Ð4HÐIÑIÔIÑJÔJˆŒÝ#™œˆŒð œ<Ð*<Ñ<ˆÔàŒN˜DØ�'˜4Ð!DØ�'˜4Ð!3Ø�I˜z¨9Ø�E˜6 6¨5°'Ý�Ô Ñ!Ô!ð
ˆõ Œn˜S°¸DÐAÑAÔAˆØŒ>˜QÒÐ cÔ&6×&=Ò&=Ñ&?Ô&?ÐÝÐWÈ3Ì:ÐWÐWÑXÔXÐXð Ðó    c                 ó<   — t          j        | j        d¬¦  «         d S )NT)Úignore_errors)ÚshutilÚrmtreer"   )r,   s    r/   ÚtearDownClassz'TestReverseMotionPipeline.tearDownClassK   s   € åŒ�c”l°$Ð7Ñ7Ô7Ð7Ð7Ð7r1   c                 ó8  — t           dz  dz  }|                      |                     ¦   «         d¦  «         t          j        |                     d¬¦  «        ¦  «        }|                     di ¦  «        }g d¢}|D ]Ÿ}|                      ||d|› d	�¦  «         ||         }|                      |                     d
d¦  «        d|› d�¦  «         |                     dd¦  «        }|                      d|d|› d�¦  «         |                      d|d|› d�¦  «         Œ dS )zdVerify 02_AI_Prompts/muse_ai_video_prompts.json has reverse_motion on all shots and True on targets.Ú02_AI_Promptszmuse_ai_video_prompts.jsonzMaster prompts file must existr   r   Úmotion_prompts©Úep01_scene02_shot08Úep01_scene06_shot07Úep01_scene08_shot04Úep01_scene09_shot02Úep01_scene12_shot06zTarget entrance shot z" must be defined in master promptsÚreverse_motionFúShot z% must have reverse_motion set to TrueÚmotion_promptÚ zREVERSE MOTIONz- prompt must contain REVERSE MOTION directivez-vf reverse -af areversez* prompt must mention FFmpeg reverse filterN©ÚBASE_DIRÚ
assertTruer)   ÚjsonÚloadsÚ	read_textÚgetÚassertIn)ÚselfÚprompts_fileÚdataÚmpÚtargetsÚtÚ	shot_infoÚprompt_texts           r/   Ú+test_01_reverse_motion_metadata_master_filezETestReverseMotionPipeline.test_01_reverse_motion_metadata_master_fileO   sd  € å /Ñ1Ð4PÑPˆØ�Š˜×+Ò+Ñ-Ô-Ð/OÑPÔPÐPÝŒz˜,×0Ò0¸'Ð0ÑBÔBÑCÔCˆØ�XŠXÐ&¨Ñ+Ô+ˆð
ð 
ð 
ˆð ð 	ð 	ˆAØ�MŠM˜!˜RÐ!^¸Ð!^Ð!^Ð!^Ñ_Ô_Ð_Ø˜1œˆIØ�OŠOØ—’Ð.°Ñ6Ô6Ø@˜Ð@Ð@Ð@ñô ð ð $Ÿ-š-¨¸Ñ<Ô<ˆKØ�MŠMØ ØØH˜ÐHÐHÐHñô ð ð
 �MŠMØ*ØØE˜ÐEÐEÐEñô ð ð ð	ð 	r1   c                 ó¨  — t           dz  dz  dz  dz  }|                      |                     ¦   «         d¦  «         t          j        |                     d¬¦  «        ¦  «        }|                     di ¦  «        }g d	¢}|D ]Q}|                      ||d
|› d�¦  «         |                      ||                              dd¦  «        d
|› d�¦  «         ŒRdS )zWVerify episodes/ep01/prompts/muse_prompts.json has reverse_motion True on target shots.ÚepisodesÚep01Úpromptszmuse_prompts.jsonzep01 prompts file must existr   r   r9   r:   rA   z must exist in ep01 promptsr@   Fz' in ep01 must have reverse_motion: TrueNrD   )rL   Ú	ep01_filerN   rO   rP   rQ   s         r/   Ú)test_02_reverse_motion_metadata_ep01_filezCTestReverseMotionPipeline.test_02_reverse_motion_metadata_ep01_fileq   sþ   € å˜zÑ)¨FÑ2°YÑ>ÐATÑTˆ	Ø�Š˜	×(Ò(Ñ*Ô*Ð,JÑKÔKÐKÝŒz˜)×-Ò-°wÐ-Ñ?Ô?Ñ@Ô@ˆØ�XŠXÐ&¨Ñ+Ô+ˆð
ð 
ð 
ˆð ð 	ð 	ˆAØ�MŠM˜!˜RÐ!G¨Ð!GÐ!GÐ!GÑHÔHÐHØ�OŠOØ�1”—	’	Ð*¨EÑ2Ô2ØB˜ÐBÐBÐBñô ð ð ð	ð 	r1   c                 óP  — | j         dz  }t          t          | j        ¦  «        t          |¦  «        ¦  «        }|                      |d¦  «         |                      |                     ¦   «         d¦  «         |                      |                     ¦   «         j        dd¦  «         dS )zYVerify reverse_video_file creates an inverted video with audio to a separate output path.zreversed_output.mp4z0reverse_video_file should return True on successzReversed output file must existéè  z%Output file size must be > 1000 bytesN)	r"   r
   r%   r$   rF   r)   ÚassertGreaterÚstatÚst_size©rL   Ú	out_videoÚsuccesss      r/   Ú-test_03_reverse_video_file_to_separate_outputzGTestReverseMotionPipeline.test_03_reverse_video_file_to_separate_output‡   s‘   € à”MÐ$9Ñ9ˆ	Ý$¥S¨Ô):Ñ%;Ô%;½SÀ¹^¼^ÑLÔLˆØ�Š˜Ð!SÑTÔTÐTØ�Š˜	×(Ò(Ñ*Ô*Ð,MÑNÔNÐNØ×Ò˜9Ÿ>š>Ñ+Ô+Ô3°TÐ;bÑcÔcÐcÐcÐcr1   c                 ó�  — | j         dz  }t          j        | j        |¦  «         |                     ¦   «         j        }t          t          |¦  «        ¦  «        }|                      |d¦  «         |                      | 	                    ¦   «         d¦  «         |  
                    |                     ¦   «         j        dd¦  «         dS )zLVerify reverse_video_file safely reverses in-place when output_path is None.ztest_inplace.mp4z.In-place reverse_video_file should return TruezIn-place file must still existr\   z!In-place file must be valid videoN)r"   r4   Úcopy2r$   r^   r_   r
   r%   rF   r)   r]   )rL   Útest_inplaceÚoriginal_sizerb   s       r/   Ú#test_04_reverse_video_file_in_placez=TestReverseMotionPipeline.test_04_reverse_video_file_in_place�   s±   € à”}Ð'9Ñ9ˆÝŒ�TÔ&¨Ñ5Ô5Ð5Ø$×)Ò)Ñ+Ô+Ô3ˆå$¥S¨Ñ%6Ô%6Ñ7Ô7ˆØ�Š˜Ð!QÑRÔRÐRØ�Š˜×+Ò+Ñ-Ô-Ð/OÑPÔPÐPØ×Ò˜<×,Ò,Ñ.Ô.Ô6¸Ð>aÑbÔbÐbÐbÐbr1   c                 ó~   — | j         dz  }t          t          |¦  «        ¦  «        }|                      |d¦  «         dS )z?Verify reverse_video_file handles nonexistent input gracefully.znon_existent_file_xyz.mp4z:reverse_video_file must return False for nonexistent inputN)r"   r
   r%   ÚassertFalse)rL   Ú
bogus_filerb   s      r/   Ú&test_05_reverse_video_nonexistent_filez@TestReverseMotionPipeline.test_05_reverse_video_nonexistent_fileš   s?   € à”]Ð%@Ñ@ˆ
Ý$¥S¨¡_¤_Ñ5Ô5ˆØ×Ò˜Ð"^Ñ_Ô_Ð_Ð_Ð_r1   c                 óP  — | j         dz  }t          t          | j        ¦  «        t          |¦  «        ¦  «        }|                      |d¦  «         |                      |                     ¦   «         d¦  «         |                      |                     ¦   «         j        dd¦  «         dS )z8Verify reverse_video in pipeline_helper works correctly.zhelper_reversed.mp4z0pipeline_helper.reverse_video should return Truez Helper reversed video must existr\   z!Helper reversed video size > 1000N)	r"   r   r%   r$   rF   r)   r]   r^   r_   r`   s      r/   Ú%test_06_pipeline_helper_reverse_videoz?TestReverseMotionPipeline.test_06_pipeline_helper_reverse_video    s‘   € à”MÐ$9Ñ9ˆ	Ý¥ DÔ$5Ñ 6Ô 6½¸I¹¼ÑGÔGˆØ�Š˜Ð!SÑTÔTÐTØ�Š˜	×(Ò(Ñ*Ô*Ð,NÑOÔOÐOØ×Ò˜9Ÿ>š>Ñ+Ô+Ô3°TÐ;^Ñ_Ô_Ð_Ð_Ð_r1   c                 ó4  — ddl }|j                             ¦   «         }|                     ddddd¬¦  «         |                     dg¦  «        }|                      |j        ¦  «         |                     dg¦  «        }|                      |j        ¦  «         dS )zJVerify run_shot.py argument parser accepts --reverse and --reverse-motion.r   Nú	--reverseú--reverse-motionr@   Ú
store_true©ÚdestÚactionÚdefault)Úrun_shotÚargparseÚArgumentParserÚadd_argumentÚ
parse_argsrF   r@   )rL   rw   ÚparserÚargs1Úargs2s        r/   Útest_07_run_shot_cli_parserz5TestReverseMotionPipeline.test_07_run_shot_cli_parser¨   sž   € àˆˆˆØÔ"×1Ò1Ñ3Ô3ˆà×Ò˜KÐ);ÐBRÐ[gÐquÐÑvÔvÐvØ×!Ò! ; -Ñ0Ô0ˆØ�Š˜Ô,Ñ-Ô-Ð-Ø×!Ò!Ð#5Ð"6Ñ7Ô7ˆØ�Š˜Ô,Ñ-Ô-Ð-Ð-Ð-r1   c                 óF  — ddl }|j                             ¦   «         }|                     dt          d¬¦  «         |                     ddddd¬	¦  «         |                     g d
¢¦  «        }|                      |j        d¦  «         |                      |j	        ¦  «         dS )zWVerify production_orchestrator.py main() parser includes --reverse-video and --reverse.r   Nú--reverse-video)Útyperv   rp   rq   r@   rr   rs   )r�   útest.mp4rp   rƒ   )
Úproduction_orchestratorrx   ry   rz   r%   r{   ÚassertEqualr   rF   r@   )rL   r„   r|   Úargss       r/   Ú*test_08_production_orchestrator_cli_parserzDTestReverseMotionPipeline.test_08_production_orchestrator_cli_parser³   s¯   € à&Ð&Ð&Ð&à(Ô1×@Ò@ÑBÔBˆØ×ÒÐ-µCÀÐÑFÔFÐFØ×Ò˜KÐ);ÐBRÐ[gÐquÐÑvÔvÐvØ× Ò Ð!MÐ!MÐ!MÑNÔNˆØ×Ò˜Ô+¨ZÑ8Ô8Ð8Ø�Š˜Ô+Ñ,Ô,Ð,Ð,Ð,r1   c           	      ó^  — | j                              d¦  «        }|                      |j        d¦  «         |                     ¦   «         }|                     dg ¦  «        }|                      t          |¦  «        dd¦  «         g }|D ]€}|                     dg ¦  «        D ]g}|                      d|d|d	         › d
�¦  «         |                      |d         t          ¦  «         |d         r| 	                    |d	         ¦  «         ŒhŒ�g d¢}|D ]}|                      ||d|› d�¦  «         ŒdS )zUVerify /api/matrix/ep01 response includes reverse_motion boolean field for all shots.z/api/matrix/ep01éÈ   Úscenesé   zEP01 matrix must have 15 scenesÚshotsr@   rA   Úshot_idz must have reverse_motion keyr:   zTarget shot z) must be flagged as reverse_motion in APIN)
r   rJ   r…   Ústatus_coderG   ÚlenrK   ÚassertIsInstanceÚboolÚappend)	rL   ÚresprN   rŠ   Úreverse_shot_idsÚscÚshotÚexpected_targetsrQ   s	            r/   Ú.test_09_api_matrix_ep01_returns_reverse_motionzHTestReverseMotionPipeline.test_09_api_matrix_ep01_returns_reverse_motion¾   sf  € àŒ{�ŠÐ1Ñ2Ô2ˆØ×Ò˜Ô)¨3Ñ/Ô/Ð/Ø�yŠy‰{Œ{ˆØ—’˜( BÑ'Ô'ˆØ×Ò�˜V™œ bÐ*KÑLÔLÐLàÐØð 	=ð 	=ˆBØŸš˜w¨Ñ+Ô+ð =ð =�Ø—’Ð.°Ð6l¸dÀ9¼oÐ6lÐ6lÐ6lÑmÔmÐmØ×%Ò% dÐ+;Ô&<½dÑCÔCÐCØÐ(Ô)ð =Ø$×+Ò+¨D°¬OÑ<Ô<Ð<øð	=ð
ð 
ð 
Ðð "ð 	lð 	lˆAØ�MŠM˜!Ð-Ð/j¸aÐ/jÐ/jÐ/jÑkÔkÐkÐkð	lð 	lr1   c                 ó  — t          dd¬¦  «        }|                      |j        ¦  «         t          dd¬¦  «        }|                      |j        ¦  «         t          d¬¦  «        }|                      |j        ¦  «         dS )z=Verify GenerateRequest pydantic model accepts reverse_motion.r>   T)r�   r@   F)r�   N)r   rF   r@   rj   ÚassertIsNone)rL   Úreq1Úreq2Úreq3s       r/   Ú/test_10_generate_request_accepts_reverse_motionzITestReverseMotionPipeline.test_10_generate_request_accepts_reverse_motionØ   s‚   € åÐ'<ÈTÐRÑRÔRˆØ�Š˜Ô+Ñ,Ô,Ð,ÝÐ'<ÈUÐSÑSÔSˆØ×Ò˜Ô,Ñ-Ô-Ð-ÝÐ'<Ð=Ñ=Ô=ˆØ×Ò˜$Ô-Ñ.Ô.Ð.Ð.Ð.r1   N)Ú__name__Ú
__module__Ú__qualname__Ú__doc__Úclassmethodr0   r6   rT   rZ   rc   rh   rl   rn   r   r‡   r˜   rž   © r1   r/   r   r   4   s÷   € € € € € Ø?Ð?àðYð Yñ „[ðYð& ð8ð 8ñ „[ð8ð ð  ð  ðDð ð ð,dð dð dð	cð 	cð 	cð`ð `ð `ð`ð `ð `ð	.ð 	.ð 	.ð	-ð 	-ð 	-ðlð lð lð4/ð /ð /ð /ð /r1   r   Ú__main__)*r¢   ÚosÚsysrG   r4   r    r&   ÚunittestÚpathlibr   Ú__file__ÚresolveÚparentÚ	TESTS_DIRrE   ÚPIPELINE_DIRÚWEB_REVIEW_DIRr%   ÚpÚpathÚinsertÚplatformÚstdoutÚreconfigurer+   Ú	Exceptionr„   r
   r   r   r   Úpipeline_helperr   Úweb_review.serverr   r   Ústarlette.testclientr   ÚTestCaser   rŸ   Úmainr¤   r1   r/   ú<module>r¼      s5  ðð
ð 
ð 
€	€	€	Ø 
€
€
€
Ø €€€Ø €€€Ø €€€Ø Ð Ð Ð Ø €€€Ø Ð Ð Ð Ð Ð ð ˆD�‰NŒN×"Ò"Ñ$Ô$Ô+€	ØÔ€ØÐ2Ñ2€Ø˜LÑ(€à
ˆ#ˆh‰-Œ-˜˜˜\Ñ*Ô*¨C¨C°Ñ,?Ô,?Ð	@ð ð €AØ�”ÐÐØŒ�Š˜˜1ÑÔÐøð „<�7ÒÐðØŒ
×Ò¨ÐÑ0Ô0Ð0ØŒ
×Ò¨ÐÑ0Ô0Ð0Ð0øØð ð ð Øˆðøøøðð ð ð ð ð ð ð ð ð ð ð ð *Ð )Ð )Ð )Ð )Ð )Ø 2Ð 2Ð 2Ð 2Ð 2Ð 2Ð 2Ð 2Ø +Ð +Ð +Ð +Ð +Ð +ðk/ð k/ð k/ð k/ð k/ Ô 1ñ k/ô k/ð k/ð\ ˆzÒÐØ€H„M�O„O€O€O€Oð Ðs   Â+6C" Ã"C*Ã)C*