
    ib                     b   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	Z	ddl
Z
ddlZddlZddlmZ ddlZ ej"                  ej$                  j&                          ej(                  d      ZdadZdZdZd	Zd
ZdZdddddddddddddddZdZ	  ed      5 Z e D ]7  Z!e!jE                  d      se!jG                         jI                  dd      d   Z9 	 ddd       dZ&de'fd Z(defd!Z)dRd"e'de'fd#Z*dRd"e'de'fd$Z+dRd"e'de'fd%Z,dSd&e'd'e-d"e'de.dz  fd(Z/dSd&e'd)e.d'e-d"e'de0f
d*Z1dRd+e'd"e'de0fd,Z2dRd-e'd.e'd"e'de'dz  fd/Z3dRd"e'de4dz  fd0Z5dRd1e4d"e'de0fd2Z6dRd3e0d"e'de0fd4Z7dRd"e'de'fd5Z8dRd"e'de.fd6Z9dTd'e-de'dz  fd7Z:dRd"e'fd8Z;dUd9e-d"e'fd:Z<dRd"e'de0fd;Z=dVd<e'd"e'de'dz  fd=Z>dRd"e'de?fd>Z@dRd?e'd"e'de0fd@ZAdRdAe'd&e'd)e.de.dz  fdBZBdCeCdeCfdDZDdEe'de0fdFZEdEe'dGe-fdHZFdRdAe'd&e'd)e.de.dz  fdIZGdWdJe'dKe4de0fdLZHdXdMe'dNe-de0fdOZIg dPZJde0fdQZKy# 1 sw Y   AxY w# e%$ r Y Lw xY w)Yu3  Shared TV control module — JointSpace, UPnP, ADB, Home Assistant, throttle.

Consolidates all TV/ADB/HA control functions used by sandman.py, sandman_bot.py,
and devices_server.py.  Import and call directly:

    import tvmod as tv_control
    tv_control.js_key("Pause")
    vol = tv_control.get_volume()
    N)HTTPDigestAuth
tv_controlz192.168.1.50a1b2c3d4e5f6@8f075a1826341135dcd3a9dd2ed30a49c327d7e002399fbaefae28d8e1f9936fi  z/urn:schemas-upnp-org:service:RenderingControl:1z<?xml version="1.0"?><s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><s:Body>{body}</s:Body></s:Envelope>  YouTubeNetflixzPrime VideozLive TVzMedia Browserz	Apple TV+Homez	Google TVSettingszDisney+mewatchSpotify)com.google.android.youtube.tvcom.netflix.ninja!com.amazon.amazonvideo.livingroomzorg.droidtv.channelsorg.droidtv.playtvzorg.droidtv.contentexplorer com.apple.atve.androidtv.appletvz$com.google.android.apps.tv.launcherxzcom.google.android.tvlauncherzcom.google.android.katnisszorg.droidtv.settingscom.disney.disneyplusz
tv.mewatchzcom.spotify.tv.android z/data/.ssh/environmentzSUPERVISOR_TOKEN==   z/share/.ha_cache/.ha_wd_tv_ipreturnc                      	 t        t              j                         j                         } | r| at        S # t
        $ r Y t        S w xY w)zOReturn the current TV IP, checking cache file if module-level TV_IP is default.)openTV_IP_CACHEreadstripTV_IP	Exception)cacheds    /share/.ha_cache/tvmod.py
_get_tv_ipr!   Q   sK    k"'')//1E L  Ls   /7 	AAc                  *    t        t        t              S N)r   JS_DEVICE_IDJS_AUTH_KEY     r    _js_authr(   `   s    ,44r'   ipc                 *    | xs
 t               } d|  dS )Nhttps://z:1926/6)r!   r)   s    r    _js_baser-   d   s    		z|BbT!!r'   c                 4    | xs
 t               } |  dt         S )N:)r!   ADB_TARGET_PORTr,   s    r    _adb_targetr1   i   s     		z|BT?#$$r'   c                 8    | xs
 t               } d|  dt         dS )Nzhttp://r/   z/upnp/control/RenderingControl1)r!   	UPNP_PORTr,   s    r    	_upnp_urlr4   n   s$    		z|BRD)$CDDr'   pathtimeoutc                 :   |xs
 t               }	 t        |       d| j                  d       }t        j                  |t               d|      }|j                          |j                         S # t        $ r!}t        j                  d| |       Y d}~yd}~ww xY w)z8GET a JointSpace endpoint.  Returns parsed JSON or None./F)authverifyr6   zjs_get /%s failed: %sN)r!   r-   lstriprequestsgetr(   raise_for_statusjsonr   logdebug)r5   r6   r)   urlres         r    js_getrE   u   s    		z|B"aC 012LL8:eWM	vvx 		)43s   AA0 0	B9BBdatac                    |xs
 t               }	 t        |       d| j                  d       }t        j                  ||t               d|      }|j                  dk  S # t        $ r!}t        j                  d| |       Y d}~yd}~ww xY w)z8POST to a JointSpace endpoint.  Returns True on success.r8   F)r?   r9   r:   r6   i  zjs_post /%s failed: %sN)
r!   r-   r;   r<   postr(   status_coder   r@   rA   )r5   rF   r6   r)   rB   rC   rD   s          r    js_postrJ      s}    		z|B"aC 012MM#Dxz%QXY}}s"" 		*D!4s   AA   	B
)BB
keyc                 "    t        dd| i|      S )z Send a key press via JointSpace.z	input/keyrK   r,   )rJ   )rK   r)   s     r    js_keyrM      s    ;44r'   action
body_innerc                    |xs
 t               }t        |      }t        j                  |      }ddt         d|  dd}	 t
        j                  j                  ||j                         |d      }t
        j                  j                  |d	      5 }|j                         j                         cd
d
d
       S # 1 sw Y   y
xY w# t        $ r!}t        j                  d| |       Y d
}~y
d
}~ww xY w)zMSend a UPnP SOAP request to RenderingControl.  Returns response body or None.)bodyztext/xml; charset="utf-8""#)Content-Type
SOAPActionPOSTrF   headersmethod   r6   NzUPnP %s failed: %s)r!   r4   UPNP_ENVELOPEformatUPNP_SVCurllibrequestRequestencodeurlopenr   decoder   r@   rA   )	rN   rO   r)   rB   rQ   rX   reqresprD   s	            r    _upnp_requestrg      s    		z|B
B-CZ0D3(1VHA.Gnn$$St{{}gV\$]^^##C#3t99;%%' 433 		&2s7   AC B;1	C ;C C C 	C1C,,C1c                     dt          d}t        d||       }|yt        j                  d|      }|rt	        |j                  d            S dS )z2Get current volume via UPnP.  Returns int or None.z<u:GetVolume xmlns:u="zC"><InstanceID>0</InstanceID><Channel>Master</Channel></u:GetVolume>	GetVolumer,   Nz$<CurrentVolume>(\d+)</CurrentVolume>r   )r^   rg   researchintgroup)r)   rQ   rf   ms       r    
get_volumero      sQ    #H:-pqDdr2D|
		94@A3qwwqz?)T)r'   volc                 h    t        dt        d|             } dt         d|  d}t        d||      duS )	zSet volume via UPnP (0-100).r   d   z<u:SetVolume xmlns:u="zD"><InstanceID>0</InstanceID><Channel>Master</Channel><DesiredVolume>z</DesiredVolume></u:SetVolume>	SetVolumer,   N)maxminr^   rg   )rp   r)   rQ   s      r    
set_volumerv      sF    
aS#
C$XJ /!U"@BD dr2$>>r'   mutedc                 H    | rdnd}dt          d| d}t        d||      duS )	zSet mute state via UPnP.10z<u:SetMute xmlns:u="zB"><InstanceID>0</InstanceID><Channel>Master</Channel><DesiredMute>z</DesiredMute></u:SetMute>SetMuter,   N)r^   rg   )rw   r)   valrQ   s       r    set_muter}      s<    #CC"8* -5 :<D DR0<<r'   c                 b    t        d|       }|r |j                  dd      j                         S y)z%Return 'On', 'Standby', or 'Unknown'.
powerstater,   Unknown)rE   r=   
capitalizer)   rF   s     r    get_power_stater      s.    ,2&Dxxi0;;==r'   c                 t    t        d|       }|r%|j                  dd      |j                  dd      dS dddS )z&Return {'volume': int, 'muted': bool}.zaudio/volumer,   currentr   rw   F)volumerw   )rE   r=   r   s     r    get_audio_stater      s=    .R(D((9a0488GU;STT%((r'   c                 0   	 dt        |        d}t        j                  t        j                  t        j                  t        j                        }|j                  t        j                  t        j                  d       |j                  t        | d             |j                  |j                         d       	 	 |j                  d      \  }}|j                  d      }d	|v sd
|v sd|j                         v r?|j                          t         j#                  d|d          |d   	 |j                          S # t$        $ r Y S w xY w# t        j&                  $ r Y nw xY w	 	 |j                          n4# t$        $ r Y n)w xY w# 	 |j                          w # t$        $ r Y w w xY wxY wn# t$        $ r Y nw xY wd }t)        dd      D cg c]  }d| 	 nc c}w }}t*        j,                  j/                  d      5 }	|D 
ci c]  }
|	j1                  ||
      |
 nc c}
w }}
t*        j,                  j3                  |      D ]7  }|j5                         }|st         j#                  d|       |c cddd       S  	 ddd       y# 1 sw Y   yxY w)zIFind the Philips TV via SSDP multicast, falling back to JointSpace probe.zKM-SEARCH * HTTP/1.1
HOST: 239.255.255.250:1900
MAN: "ssdp:discover"
MX: z
ST: ssdp:all

      )z239.255.255.250il  i   replace)errorsPhilipsPhilipsIntelSDKandroidzSSDP discovered TV at %sr   c                     	 t        j                         }t        j                  j	                  t        j                  j                  d|  d      d|       | S # t        $ r Y y w xY w)Nr+   z:1926/6/systemr   )r6   context)ssl_create_unverified_contextr_   r`   rc   ra   r   )check_ipctxs     r    probezdiscover_tv.<locals>.probe   se    	002CNN""&&(>'JK3 #  O 		s   AA 	A'&A'   z
192.168.1.   )max_workerszProbe discovered TV at %sN)rl   socketAF_INET
SOCK_DGRAMIPPROTO_UDP
setsockopt
IPPROTO_IPIP_MULTICAST_TTL
settimeoutru   sendtorb   recvfromrd   lowercloser@   infor   r6   range
concurrentfuturesThreadPoolExecutorsubmitas_completedresult)r6   ssdp_msgsockrF   addrtextr   i
candidatespoolr)   futsfr   s                 r    discover_tvr      sQ    w<. !## 	 }}V^^V->->@R@RS))6+B+BAFGQ(HOO%'@A	!]]40
d{{){4$(9T(AYRVR\R\R^E^JJLHH7aA7

    ~~ 		

 

  	 -2!RL9LqJqc"L9J9				.	.2	.	>$5?@ZrE2&*Z@@##006AXXZF4f= 
?	>6 
?  
? s   B:F. =A.E ,D>=E >	E
F. 	E

F. E# F "E##F (E9 8F. 9	FF. FF. F*
FF*	F'$F*&F''F**F. .	F:9F:GJH"!<JJJJc                    | xs
 t               } 	 t        j                         }t               |_        d|_        t        |        d}|j                  |ddid       t        j                  d       t        d      D ]  }|j                  |dd	id        t        j                  d
       |j                  |ddid       |j                  |ddid       y# t        $ r }t        j                  d|       Y d}~yd}~ww xY w)zSToggle ambilight via menu navigation (AmbilightOnOff -> up 15x -> Confirm -> Back).Fz
/input/keyrK   AmbilightOnOffr   )r?   r6   g333333?r   CursorUpg?ConfirmBackzAmbilight toggle failed: %sN)r!   r<   Sessionr(   r9   r:   r-   rH   timesleepr   r   r@   warning)r)   srB   _rD   s        r    ambilight_toggler     s    		z|B6"j)	s%!12A>

4rAFF3eZ0!F< 

4	s%+Q7	s%!4 611556s   C
C 	D$C??Doff_durationc                 ^    t        |       t        j                  |        t        |       y)z+Toggle ambilight off, wait, toggle back on.r,   N)r   r   r   )r   r)   s     r    ambilight_glitchr   $  s    JJ|r'   c                    t        |       }	 t        j                  dd|gddd      }d|j                  j	                         v xs d|j                  j	                         v S # t
        $ r }t        j                  d|       Y d	}~y
d	}~ww xY w)z0Connect to TV via ADB.  Returns True on success.adbconnectT
   capture_outputr   r6   	connectedalreadyzadb_connect failed: %sNF)r1   
subprocessrunstdoutr   r   r@   rA   )r)   targetrC   rD   s       r    adb_connectr   -  s|    _FNNE9f5*.T2Gahhnn..O)qxx~~?O2OO 		*A.s   AA# #	B,BBoutput_pathc           	      
   t        |      }	 t        |       t        j                  dd|ddddgddd	       t        j                  dd|d
d| gddd	       | S # t        $ r }t
        j                  d|       Y d}~yd}~ww xY w)zKTake a screenshot via ADB.  Returns local path on success, None on failure.r   -sshell	screencapz-pz/sdcard/screen.pngTr   )r   r6   checkpullzadb_screenshot failed: %sN)r1   r   r   r   r   r@   r   )r   r)   r   rD   s       r    adb_screenshotr   9  s    _FBD&';>RS4	
 	D&&*>L4	
  /3s   AA 	B"A==Bc           	         t        |       }	 i }t        j                  dd|ddddgddd	      }d
}|j                  j	                         D ]B  }d|v st        j                  d|      }|r"|j                  d      j                         d   } n d
}t        j                  dd|dddgddd	      }d}	|j                  j	                         D ]  }d|v rd}	|	r!d|v rd|v rd}nd|v rd}|rt        j                  d|      }
t        j                  d|      }t        j                  d|      }|
r|rt        |
j                  d            }t        |j                  d            }|rt        |j                  d            nd}	 t        j                  dd|ddgddd	      }t        t        |j                  j                         d         dz        }||z
  |z  }||z   }|dkD  rt        |dz        |d<   |	s2d|v s8d|v s>t        j                  d |      }|r|j                  d      j                         j                  d!      j                  d"      }|j                  d"      D cg c]  }|j                          }}|r|d   |d#<   t        |      dkD  r|d   |d$<   d}	 |||fS # t        $ r |dkD  r|dz  |d<   Y w xY wc c}w # t        $ r }t        j!                  d%|       Y d
}~y&d
}~ww xY w)'zGet current foreground app, playback state, and extras via ADB.

    Returns (package_name, playback_state, extras_dict) where extras may
    contain 'title', 'artist', 'position_s'.
    Any element may be None on failure.
    r   r   r   dumpsysactivity
activitiesT   r   NtopResumedActivityz	(\S+)/\S+r   media_sessionFzactive=truezstate=PlaybackStatePLAYINGPAUSEDzposition=(\d+)zupdated=(\d+)zspeed=([\d.]+)g      ?zcat /proc/uptimer   r   i  
position_sz	metadata:zsize=zdescription=(.+?)(?:,\s*null)?$z, null,titleartistzadb_get_current_app failed: %s)NNN)r1   r   r   r   
splitlinesrj   rk   rm   splitrl   floatr   r   rstriplenr@   r   )r)   r   extrasrC   pkglinern   stater2found_activepos_mupd_mspd_mpos_msupdatedspeedup_r	uptime_ms
elapsed_msreal_pos_msdescppartsrD   s                           r    adb_get_current_appr  L  s    _F?  NNE4%z<A*.T1F HH'')D#t+IIlD1''!***,R0C * ^^UD&'&9+/dAG II((*D$# 5 =$%E%$EII&7>EII&6=EII&7>E!$U[[^!4"%ekk!n"59>ekk!n 5CF#->>!&fg?Q R/3$$KD ),E$++2C2C2Ea2H,ID,P(QI*3g*=)FJ*0:*=K*Q7:;$;N7O| 4 t 34II@$G771:++-44X>EEcJD04

3@1QWWYE@*/(w5zA~+08x($K +N E6!!  ) F%z7=~| 4F A   4a8 sc   AK D4K A3J6;K ?K K A(K 3K
+K 6KK KK 	L  K;;L packagec           	         t        |      }ddddddd}	 t        |       |j                  |       }|r"t        j                  dd	|d
d| dgdd       yt        j                  dd	|d
d|  dgdd      }|j
                  dk7  r!t        j                  dd	|d
d|  dgdd       y# t        $ r }t        j                  d|       Y d}~yd}~ww xY w)z:Launch an app on the TV via ADB.  Returns True on success.zcom.netflix.ninja/.MainActivityzWcom.google.android.youtube.tv/com.google.android.apps.youtube.tv.activity.ShellActivityz.com.apple.atve.androidtv.appletv/.MainActivityz/com.disney.disneyplus/.ui.splash.LaunchActivityzFcom.amazon.amazonvideo.livingroom/com.amazon.ignition.IgnitionActivityz"org.droidtv.playtv/.PlayTvActivity)r   r   r   r   r   r   r   r   r   z*am start -a android.intent.action.MAIN -n z --activity-clear-topTr   )r   r6   z
monkey -p z/ -c android.intent.category.LEANBACK_LAUNCHER 1r   z& -c android.intent.category.LAUNCHER 1zadb_launch_app failed: %sNF)	r1   r   r=   r   r   
returncoder   r@   r   )r  r)   r   launch_intents	componentrC   rD   s          r    adb_launch_appr    s
   _F> *C,\!R-uBNB"&&w/	NNE4G	{Rghj*.<  tVW",WI5d e g.2B@A ||q tVW",WI5[ \ ^.2B@  /3s   ?B( AB( (	C1CCrY   c                    	 d| }|r#t        j                  |      j                         nd}t        j                  j                  ||dt         dd|       }t        j                  j                  |d      }t        j                  |j                               S # t        $ r"}t        j                  d	| ||       Y d}~yd}~ww xY w)
z5Call HA Supervisor API.  Returns parsed JSON or None.http://supervisor/core/apiNBearer application/jsonAuthorizationrT   rW   r   r[   zha_api %s %s failed: %sr?   dumpsrb   r_   r`   ra   SUPERVISOR_TOKENrc   loadsr   r   r@   rA   rY   r5   rF   rB   rQ   re   rf   rD   s           r    ha_apir    s    *4&1,0tzz$&&(dnn$$d#*+;*<!= 2  % 
 ~~%%c2%6zz$))+&& 		+VT1=   BB 	C
(CC
device_listc                     t        dd      }|sg S g }|D ]E  }|d   }|| v s|j                  di       j                  d|      }|j                  |||d   f       G |S )zEReturn [(entity_id, friendly_name, state), ...] for given entity IDs.GETz/states	entity_id
attributesfriendly_namer   )r  r=   append)r  statesr   rD   eidnames         r    ha_get_device_statesr     sq    E9%F	Fn+55r*..DDMM3aj12	 
 Mr'   r  c                 
   	 t        dd|        }|sy|j                  dd      }| j                  d      d   }|dk(  rd	nd
}t        dd| d| d| i       y# t        $ r }t        j                  d|       Y d}~yd}~ww xY w)z3Toggle a HA switch/light.  Returns True on success.r  /states/Fr   off.r   onturn_offturn_onrV   
/services/r8   r  Tzha_switch_toggle failed: %sN)r  r=   r   r   r@   r   )r  
state_datar   domainservicerD   s         r    ha_switch_toggler,    s    EXi[#9:
..%0%a( '4*YvF81WI6i8PQ 115s   A AA 	B"A==Bdurationc                 z   	 t        dd|        }|sy|j                  dd      }| j                  d      d   }|dk(  rdnd	}|dk(  rd	nd}t        d
d| d| d| i       |dkD  rt	        j
                  |       t        d
d| d| d| i       y# t        $ r }t        j                  d|       Y d}~yd}~ww xY w)u[  Toggle a HA switch for `duration` seconds, then restore original state.

    The visible flicker is bounded by: toggle API latency + sleep + restore API
    latency.  To keep it short, we use a 2s timeout on the individual calls and
    skip the sleep when duration is very small — the API round-trip itself
    provides enough visible gap.
    r  r"  Nr   r#  r$  r   r'  r&  rV   r(  r8   r  zha_switch_flicker failed: %s)	r  r=   r   _ha_api_fastr   r   r   r@   r   )r  r-  r)  r   r*  
toggle_svcrestore_svcrD   s           r    ha_switch_flickerr2    s    7EXi[#9:
..%0%a(")U"2Y

$+u$4j)Vz&:,?+yAYZa<JJx Vz&;-@;PYBZ[ 72A667s   B A<B 	B:B55B:c                    	 d| }|r#t        j                  |      j                         nd}t        j                  j                  ||dt         dd|       }t        j                  j                  |d      }t        j                  |j                               S # t        $ r"}t        j                  d	| ||       Y d}~yd}~ww xY w)
zFLike ha_api but with a short 2s timeout for time-sensitive operations.r
  Nr  r  r  rW   r   r[   z_ha_api_fast %s %s failed: %sr  r  s           r    r/  r/    s    *4&1,0tzz$&&(dnn$$d#*+;*<!= 2  % 
 ~~%%c1%5zz$))+&& 		164Cr  tv_ipbandwidth_kbpsc                    | xs
 t               } d}	 t        j                  dddd|dgd       |d	k  ryt        j                  ddd
d|ddddddgdd       t        j                  ddd
d|dddddddgdd       t        j                  ddd
d|dddddd| dd| dgdd       t        j                  ddd
d|dddddddd dd!|  d"d dddd#d$dgdd       d%D ]B  }t        j                  ddd
d|dddddddd dd!|  d"d ddd&d#d dd't        |      d(d$dgdd       D t        j                  ddd
d|dddddd)dd dd!|  d"d$dgdd       y# t        $ r }t
        j                  d*|       Y d+}~y,d+}~ww xY w)-zDApply tc HTB throttle targeting the TV IP.  Returns True on success.end0tcqdiscdeldevrootTr   r   addhandlez1:htbdefault10)r   r   classparentclassidz1:10rate1000mbitz1:20kbitceilfilterprotocolr)   priory   u32matchdstz/320xffflowid)i  r   6dport0xffff2zthrottle_apply failed: %sNF)r!   r   r   strr   r@   r   )r4  r5  r;  portrD   s        r    throttle_applyrX    s   !Z\E
C)gueS&A&*	,QgueS&(Dy$0&*$	8 	gueS(D)vz;&*$	8 	gueS(D)v.1A/F>"2$ 79 '+$	8 	huc8T:fc5usmz3 &	*
 '+$	8 !DNND(E5#xz &#u#T5UG3-#T:sF#T7CIx$f. +/d< ! 	huc8T:fc5'4 'x9 '+$	8  /3s   "E DE 	E8E33E8
sound_filer   c                     	 t        ddd|d       t        ddddd|  d       y	# t        $ r }t        j                  d
|       Y d}~yd}~ww xY w)zFPlay a sound file on the Xiaomi Sound Pro speaker via HA media_player.rV   z!/services/media_player/volume_setzmedia_player.sound_pro_1264)r  volume_levelz!/services/media_player/play_mediamusicz!http://192.168.1.187:8888/sounds/)r  media_content_typemedia_content_idTzspeaker_play_sound failed: %sNF)r  r   r@   r   )rY  r   rD   s      r    speaker_play_soundr_  J  sj    v:6"=
 	 	v:6")"CJ< P=
 	
  3Q7s   $' 	AAA)zknock_2.mp3zcough_single.mp3zthroat_final2.mp3ztongue_click.mp3zcustom_20to22.mp3zexhale_loud.mp3zexhale_soft.mp3c                      d} 	 t        j                  dddd| dgd       y# t        $ r }t        j	                  d	|       Y d
}~yd
}~ww xY w)z7Remove all tc throttle rules.  Returns True on success.r7  r8  r9  r:  r;  r<  Tr=  zthrottle_remove failed: %sNF)r   r   r   r@   r   )r;  rD   s     r    throttle_removera  g  sO    
CgueS&A&*	, 0!4s   " 	AAAr#   )rZ   N)g      @)g      @N)z/tmp/tv_screen.pngN)Ni  )g333333?)L__doc__concurrent.futuresr   r?   loggingosrj   r   r   r   r   urllib.requestr_   r<   requests.authr   urllib3disable_warnings
exceptionsInsecureRequestWarning	getLoggerr@   r   r$   r%   r3   r^   r\   r0   	APP_NAMESr  r   _f_line
startswithr   r   r   r   rV  r!   r(   r-   r1   r4   r   dictrE   boolrJ   rM   rg   rl   ro   rv   r}   r   r   r   r   r   r   r   tupler  r  r  listr   r,  r2  r/  rX  r_  SPEAKER_SOUNDSra  r&   r'   r    <module>rv     s      	 	  
     (    ++BB Cg% 	P	<+   &/")6%##2(3,2%+"-&&'	&  		&	'2E 34#(;;=#6#6sA#>q#A   
( .
C 
5. 5" " "
%C %3 %
E# E E
 
u 
c 
TD[ 
	# 	T 	E 	3 	$ 	5 5 5 5# 3 C 3: $*3 *#* *?C ?S ?D ?=D =c =T = s ) )t )5 5t 5t6 6&5 C 	C 	4 	  sUYz &G C G 5 G TC S D D3 c   (d t    7 7 74 C t td{ ,-# -c -T -`3   $	 	E 
(	'  		s0   H% H3%HH% H"H% %H.-H.