From ac54d22fd0b1f7fc4f4cdf4a0c8c936f33af1cac Mon Sep 17 00:00:00 2001 From: Uriziel Date: Fri, 12 Oct 2012 18:37:12 +0200 Subject: [PATCH] tequila init --- .gitignore | 9 + Makefile | 27 ++ README.md | 171 +++++++++++ buildPy2exe.py | 33 ++ icon.ico | Bin 0 -> 226501 bytes syncplay-server.desktop | 9 + syncplay.desktop | 11 + syncplay/__init__.py | 3 + syncplay/client.py | 451 ++++++++++++++++++++++++++++ syncplay/clientManager.py | 61 ++++ syncplay/players/__init__.py | 0 syncplay/players/basePlayer.py | 22 ++ syncplay/players/mpc.py | 439 +++++++++++++++++++++++++++ syncplay/players/mplayer.py | 153 ++++++++++ syncplay/protocols.py | 389 ++++++++++++++++++++++++ syncplay/server.py | 234 +++++++++++++++ syncplay/ui/ConfigurationGetter.py | 182 +++++++++++ syncplay/ui/GuiConfiguration.py | 109 +++++++ syncplay/ui/__init__.py | 11 + syncplay/ui/consoleUI.py | 97 ++++++ syncplay/ui/gui.py | 9 + syncplayClient.py | 5 + syncplayClientForceConfiguration.py | 9 + syncplayServer.py | 11 + 24 files changed, 2445 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 README.md create mode 100644 buildPy2exe.py create mode 100644 icon.ico create mode 100644 syncplay-server.desktop create mode 100644 syncplay.desktop create mode 100644 syncplay/__init__.py create mode 100644 syncplay/client.py create mode 100644 syncplay/clientManager.py create mode 100644 syncplay/players/__init__.py create mode 100644 syncplay/players/basePlayer.py create mode 100644 syncplay/players/mpc.py create mode 100644 syncplay/players/mplayer.py create mode 100644 syncplay/protocols.py create mode 100644 syncplay/server.py create mode 100644 syncplay/ui/ConfigurationGetter.py create mode 100644 syncplay/ui/GuiConfiguration.py create mode 100644 syncplay/ui/__init__.py create mode 100644 syncplay/ui/consoleUI.py create mode 100644 syncplay/ui/gui.py create mode 100644 syncplayClient.py create mode 100644 syncplayClientForceConfiguration.py create mode 100644 syncplayServer.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..255517d --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +*.py[co] +.*.sw[po] +venv + +/SyncPlay.egg-info +/build +/dist +dist.7z +.* \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..e79c4d9 --- /dev/null +++ b/Makefile @@ -0,0 +1,27 @@ +BIN_PATH = /usr/bin +LIB_PATH = /usr/lib +APP_SHORTCUT_PATH = /usr/share/applications +ICON_PATH = /usr/share/icons + +install: + touch $(BIN_PATH)/syncplay + echo '#!/bin/sh\npython $(LIB_PATH)/syncplay/syncplayClient.py "$$@"' > $(BIN_PATH)/syncplay + chmod a+x $(BIN_PATH)/syncplay + touch $(BIN_PATH)/syncplay-server + echo '#!/bin/sh\npython $(LIB_PATH)/syncplay/syncplayServer.py "$$@"' > $(BIN_PATH)/syncplay-server + chmod a+x $(BIN_PATH)/syncplay-server + mkdir $(LIB_PATH)/syncplay/ + cp syncplayClient.py $(LIB_PATH)/syncplay/ + cp syncplayServer.py $(LIB_PATH)/syncplay/ + cp -r syncplay $(LIB_PATH)/syncplay/ + cp syncplay.desktop $(APP_SHORTCUT_PATH)/ + cp syncplay-server.desktop $(APP_SHORTCUT_PATH)/ + cp icon.ico $(ICON_PATH)/ + +uninstall: + rm $(BIN_PATH)/syncplay + rm $(BIN_PATH)/syncplay-server + rm -rf $(LIB_PATH)/syncplay + rm $(APP_SHORTCUT_PATH)/syncplay.desktop + rm $(APP_SHORTCUT_PATH)/syncplay-server.desktop + rm $(ICON_PATH)/icon.ico diff --git a/README.md b/README.md new file mode 100644 index 0000000..6dd4a82 --- /dev/null +++ b/README.md @@ -0,0 +1,171 @@ +# Syncplay + +Solution to synchronize video playback across multiple instances of mplayer2 and/or Media Player Classic (MPC-HC) over the Internet. + +## Notice +No official builds of Syncplay have been released yet. If you want the current beta then compile it yourself from the official branch or check out the #syncplay IRC channel on irc.rizon.net. + +## What does it do + +Syncplay synchronises the position and play state of multiple media players so that the viewers can watch the same thing at the same time. +This means that when one person pauses/unpauses playback or seeks (jumps position) within their media player then this will be replicated across all media players connected to the same server and in the same 'room' (viewing session). +When a new person joins they will also be synchronised. + +## What it doesn't do + +Syncplay does not use video streaming or file sharing so each user must have their own copy of the media to be played. Syncplay does not synchronise player configuration, audio or subtitle track choice, playback rate, volume or filters. Furthermore, users must manually choose what file to play as Syncplay does not synchronise which file is open. Finally, Syncplay does not provide a voice or text-based chat platform to allow for discussion during playback as Syncplay is intended to be used in conjunction with third-party communication solutions such as IRC and Mumble. + +## Requirements +Frozen Windows executables are available on the download page - https://github.com/Uriziel/syncplay/downloads + +* On Windows: `Media Player Classic - Home Cinema (MPC-HC)` >= `1.6.4`. +* On Linux: `mplayer2`. `MPlayer` >= `1.1` should be compatible, but is not supported. + +### Python scripts (for those not using the frozen executable package) + +If you are not using the frozen executable package then you will need the following to run Python scripts: + +* `pygtk` >= `2.0.0` (unless started with `--no-gui`) +* `argparse` >= `1.1` +* `pywin32` >= `r217` (MPC-HC, Windows only) +* `twisted` >= `12.1.0` + +If you are using the frozen executable package available from the download page then you will not need to be able to run Python scripts. + +## Supported players +### mplayer2 on Linux + +On Linux `syncplay` acts as a front-end for mplayer2. +To use it select "Open with..." in context menu and choose `Syncplay` or from command line: "syncplay video_filename". If you wish to pass more arguments to mplayer2 prepend them with -- argument, it's treated as the last argument for wrapper. + +Default mplayer2 output is suppressed, but if mplayer2 quits with errors, those errors will be printed (at most 50 last lines). + +### Media Player Classic - Home Cinema (MPC-HC) on Windows + +On Windows simply running `syncplayClient.exe` opens a Syncplay command-line window for communication/control and a new instance of MPC-HC for synchronised video playback. This instance of MPC-HC is controlled by Syncplay through the associated command-line window, but other instances of MPC-HC will be unaffected. + +## Using Syncplay + +### Getting started with Syncplay on Windows + +1. Ensure that you have the latest version of `Media Player Classic - Home Cinema (MPC-HC)` installed. The latest stable build is `1.6.4`. + +2. Download Syncplay frozen executable package from https://github.com/Uriziel/syncplay/downloads and extract to a folder of your choosing. + +3. If you are running your own server then open `syncplayServer.exe` (see "How to use the server", below). + +4. Open `syncplayClient.exe` (or open the media file you wish to play with `syncplayClient.exe`, e.g. using "Open with"). + +5. Enter configuration settings (see "Configuration window", below). Ensure that you are on the same server and room as your fellow viewers. + +6. If you don't have the file you want to play open then open it from within the MPC-HC instance initiated by Syncplay. + +7. Playing, pausing and seeking from within the MPC-HC instance should now be synchronised with everyone else in the same 'room'. + +### Getting started with Syncplay on Linux + +1. Ensure that you have an up to date version of `mplayer2` installed. + +2. Download Syncplay tarball from https://github.com/Uriziel/syncplay/downloads and run `make install`. + +3. Open the media file you wish to play with `syncplay` (e.g. using "Open with..."). + +4. Enter configuration settings (see "Configuration window", below). Ensure that you are on the same server and room as your fellow viewers. + +5. Playing, pausing and seeking from within the mplayer2 instance should now be synchronised with everyone else in the same 'room'. + +### Opening a media file with Syncplay + +Opening a file with `syncplayClient` (`Syncplay` on Linux) will automatically run Syncplay and load the file through MPC-HC on Windows or mplayer2 on Linux. + +### Configuration window +The configuration window allows for various settings to be configured prior to Syncplay starting. + +The window will appear if you: + +1. Run `syncplayClient` without settings being configured, e.g. on first boot, +2. Run `syncplayClient` with the `--force-gui-prompt` or `-g` commandline switches, or +3. Run `syncplayClientForceConfiguration`. + +The settings to be configured are as follows: + +* `Host` - Address (hostname / IP) of server to connect to (optionally with port), e.g. `localhost:2734` or `192.168.0.1`. Default port is `8999`. +* `Username` - Name that the server and other viewers know you by. +* `Default room (optional)` - Room to join upon connection. You will only be synchronised with others in the same room on the same server. Default room is `default`. +* `Server password (optional)` - Password for server. Servers that are not password protected have a blank password. +* `Path to mpc-hc.exe [Windows only]` - Location of the MPC-HC executable (mpc-hc.exe or mpc-hc64.exe). If this is in a common location then it will be filled in by default. Users are advised to check that it points to their desired installation. + +Pressing "Save" will save the settings and continue Syncplay start-up. + +### Syncplay Commands + +Within the Syncplay command-line you can enter the following commands (and then press enter) to access features: + +* `help` - Displays list of commands and other information. +* `room [room]` - Leaves current room and joins specified room. You are only synchronised with others in the same room on the same server. If no room is specified then this command will use the filename of the currently open file, or alternatively will join the room `default`. +* `s [time]` - Seek (jump) to specified time. Can be `seconds`, `minutes:seconds` or `hours:minutes:seconds`. +* `s+ [time]` - Jumps [time] forward. Can be `seconds`, `minutes:seconds` or `hours:minutes:seconds`. +* `r` - Revert last seek. Seeks to where you were before the most recent seek. +* `p` - Toggle play/pause. + +### Command-line switches + +You can run `syncplayClient` with the following command-line switches to alter Syncplay settings or behaviour: + +* `--no-gui` - Do not display graphical user interface (GUI) +* `--host [address]` - Specify address of server to connect to (can be `address:port`) +* `--name [name]` / `-n [name]` - Specify username to use +* `--debug` / `-d` - Enable debug mode +* `--force-gui-prompt` / `-g` - Force the configuration window to appear when Syncplay starts +* `--no-store` - Disable the storing of configuration data (in .syncplay file) +* `--room [room]` / `-r [room]` - Specify default room to join upon connection. +* `--password [password]` / `-p [password]` - Specify server password +* `[file]` - File to play upon start +* `--` - used as a last argument for syncplayClient, used to prepend arguments that are meant to be passed to player + +### Notification messages + +* `Rewinded due to time difference with [user]` - This means that your media player ended up too far in front of the specified user and has jumped back to keep you in sync. This is usually because someone's computer isn't powerful enough to play the file smoothly. +* `File you're playing is different from [user]'s` - This means that the filename, length and/or duration of the file that the user is playing is different from the file that you are playing. This is for information only and is not an error. + +## How to use the server + +Run `syncplayServer` to host a Syncplay server. If you have a public IP then you can try to launch the server on your computer +and give your friends your IP number so that they can connect to it. The server software will listen on port `8999` by default, but you can specify a different port. You might need to specifically allow connections to `syncplayServer` in your firewall/router. If that is the case then please consult your firewall/router instructions or contact your network administrator. + +Pass the IP or hostname (and password / port if necessary) to people you want to watch with and you're ready to go. There are various online services that will tell you what your IP address is. + +### Server command-line switches + +* `--port [port]` - Use stated port instead of the default one. +* `--isolate-room` - If specified then 'room isolation' is enabled. This means that viewers will not be able to see information about users who are in rooms other than the one they are in. This feature is recommended for a public server, but not for a small private server. +* `--password [password]` - Restrict access to the Syncplay server to only those who use this password when they connect to the server. This feature is recommended for a private server but is not needed for a public server. By default the password is blank (i.e. there is no password restriction). + +## Syncplay behaviour +The following information is sent from the client to the server: +* Public IP address of client and other necessary routing information (as per TCP/IP standards). +* Media position, play state, and any seek/pause/unpause commands (associated with the instance of the media player initiated by Syncplay). +* Size, length, and optionally filename of currently open media (associated with the instance of the media player initiated by Syncplay). +* Syncplay version, username, server password and current 'room'. +* Ping responses to assess latency. + +Note: The current official build of the Syncplay server does not store any of this information. However, some of the information (not the IP address) is passed on to other users connected to the server (or just to those in the same room if 'isolation' mode is enabled). + +The server has the ability to control the following aspects of the instance of the media player initiated by Syncplay: +* Current position (seek commands). +* Current play state (pause and unpause commands). + +The client affects the following files: +* Modifying .syncplay file in %APPDATA% (or $HOME on Linux version) folder to store configuration information. + +Note: This behaviour can be disabled by using the `--no-store` command-line switch (see "Command-line switches", above) + +## How to report bugs + +You can report bugs through https://github.com/Uriziel/syncplay/issues but first please check to see if your problem has already been reported. + +You might also be able to discuss your problem through Internet Relay Chat (IRC). The #Syncplay channel is on the irc.rizon.net server. + +### Known issues +1. Changing your system time while Syncplay is running confuses the sync. PROTIP: Don't do it. +2. Syncplay cannot properly handle a seek that is within 8 seconds of the current position. PROTIP: Don't do it. diff --git a/buildPy2exe.py b/buildPy2exe.py new file mode 100644 index 0000000..d93c88b --- /dev/null +++ b/buildPy2exe.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python +#coding:utf8 + +from setuptools import setup +import py2exe #@UnusedImport +from setuptools import find_packages +import syncplay + +common_info = dict( + name = 'Syncplay', + version = syncplay.version, + author = 'Tomasz Kowalczyk, Uriziel', + author_email = 'code@fluxid.pl, urizieli@gmail.com', + description = 'Syncplay', + packages = find_packages(exclude=['venv']), + install_requires = ['Twisted>=11.1'], +) + +info = dict( + common_info, + console = [{"script":"syncplayClient.py","icon_resources":[(1,"icon.ico")]}, {"script":"syncplayClientForceConfiguration.py","icon_resources":[(1,"icon.ico")]}, 'syncplayServer.py'], + options = {'py2exe': { + 'includes': 'cairo, pango, pangocairo, atk, gobject', + 'optimize': 2, + 'compressed': 1 + } + + }, + +) + +setup(**info) + diff --git a/icon.ico b/icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..eb42cc0529693962a068bdc1eb322f5bd25450c2 GIT binary patch literal 226501 zcmXtf1ymbdv~}>{Ufi9wcyV|4K#N;(clSbYiWGN>1TPedYw-qmZIR%?U4Fjz-al(a z?%c`B+&g#nIeVWy000622>5S90MG)^v=9JMuiryM|F_KwL;xhb?lUv{-*!bB0Pyn@ zB0y5||N5l?0OJ!x02kN)^)mqgU$~F}w6y=%#sdJ}>mdP1N&mMEMh5`qtdRhCulxV| zKU?J2-@76KVl>qiurbIn0002ClA^5kf4%>`fT*t@M(!2XfY*bSWTkX{bB{Wek{y@a zn7mivoi4H9+>qS~2|WZ*1!5SaPqMfdeJ{+39yvumE8B5|F_oVu^`}+#>-)8nfgMAZ z>hg7SMoJ9kryK)&9f2h)p9n%gxY-je9)!rnddf0z8Pb5!GTlbkrd|oeZCo z-ee%;B>3^xtvj;H>*w?R`+J0wfbOA?Srv&*IDF^Ybo4&%R=ak7?%w>3d55~)qu;mC z4}YPOAM41tr`6-DA*B#T`gio|pie(PgWPP>>_LbIfP02t*Ggd?cwad(7-d!H=;`Rq zcZbLO#?M1P^}h41ijidKDZofU8kPvLI~+wYs_9lfbRQ_g6Q7`0z{+hLjuj9ZuSfC)&UKs9%YyVyv}(u5V#|qj%;zRb>()R}n6|ZkCp&rtLQ`12p$54+!(= zNWTn1KyQSGwpU@Fw(+a-FRrggar-x;%}?pQn1prLf6LFIXgO1=$V95^+R!#(IfI4p zw7wUo6Ih2jb6-SRIxn_vE;d*y49FrYpWQq(Ac~k2hJs_kI0Q_Cg5Y5x*+c~Y(pW+! zW5|Ir2wEfv`rW`z!~1tpD0gTNy4d=&>QNXX3o~97UyQkVKE>S`x}@s1F%}A~y2@zA z!MPr8)i@s+5$P}x+fBfBvp%Jhr{Tl;FtiA9Wl-j2-cRFVDHh`PLe9po2h{gN|72B| z@Ag1ZQ1wIk0kym{I3hICK;18rbh>7+qkk{rs+)a97%|~p@CK8*rlg3$F1XwoscfBH z{1~en8@e%44d3X$ zGbCy5I!~`V73ew>u313RtBO7nyO_ic2Fox>gSLyTV-hfa($8{_NyofFHkNFlEq20d zUTVdL84bx&@lHtIk`i)u;EE$_(9Le=A#sbq7DZr2K;mdk^M;RRK0YMlSoD22so~{Y zVX86}7>S9Arzh?3LK^?$hYwM~q!#i0lQ(XiCWroeUV9Iua@2Nd0b2hI33kFNI;tRW z$Ui!wBm_~!eI!_)+vyzJ+?C%>d$8UbxcfA&U@GJ?>3N6-Q{Qz5P&=RG^t9{p9B>)X zeZ$gZQZQqGmz+gG!X)RXg^e$Rosb}J2l7&n6q5A7<5z4zH_ORtUOIb&&q;Y!OOWpk z{UcLRff3e;TMJ=_%ec}ZpRaFiZVFpBl4X(szFy$0`(e{*R_vZoPGq!+N+iL?cIpjkbpM@x)a`n&wN5l= znSM(f+fiA36SJpg{qpAFbn)RJv&wou^C6pmCtW`Tt^g(cpKHQkT=24Acm5XFRBls| z2k!<3bN-kQDwZMspvT^T|MiVBFMXbj7s4cJC^r2O-pm_rCn0xC`Zg(9jn>V!O?Jq^ zrgVigF?;P{wzToz`2<7T_HVft2SJ5+h|(ZBtspFbnMJ(^kgi7vjh%2dB>mL{xKWEQ&c*V}4UnO|i312uQ zB)(g~cvK?ltb^16eoY*0oY(qmesMi$b0%xIe!6*C*WU@bi+<@(!ovQafyvZOJSA+D zM&ENpDZo$tg-+{*Z`^)jL9`Xf?7tT2%%K!wR=fi&Ynl;GQVn&e$r_G3%SR@K?OFCd zEVIbqfGPq(b4DQIwxxLI^T&bo9d+zZxww{Rp1E zRvUDq1<=a?uKROy0fbVZKYO3USqR#|+!lKQ?=oP5A`(NBH}|(^4E|Sh`mUarq%asv zP)uxY=g!aQ=8zZvE*laoBWsFsDWd70kQXu|@-jI$rv$u;-I9My*tk`DDtLyyoJ4yg z>*H+NKS>_Kf=@QeQcFUkhi-g<164B$Ci+2m>FmmmbPW*)(z?w=i&|fe!<~6Aa59@S zeWiL>zG^9EIyI2$9}O#LzRf?RlBRl-Viv)3 z4FO~A_gV{o^kqRWZ%vDa?mVd4>KIg=(?&4HgiAyFOK6HX^5VAG#QAw1J2z2wuxl$5^AvA zI0ytlp9xDBfQv}B6ZB6A_+n$@(I6m4q&+nRV!wj3PH#xTn)9A!T))*W`HxuNRtg;v2z^5AkgVwIweI*ebp;PRzQ0`sZx3Qzw%+itO&jBG8!FZD~+$HkW>4`|7u zwd;C63C~1Wko%i_F-b>xmn(k{09j&r_~yqk{T_CdRGPR^WTHb7k!*nCY$-sMUJ^(o z#kkh23jjn3N?OPg+si7Yhwa=6J<8WxB9{-9(=rfrYCvg&_>;<<8`>M=mIBqiX7-*I zObf*PFU`DzgP*^XMk^zy4-AWaE5-AFSQDTQI_EVt2)?*^S-Lq!uEpz_>r3svemmRo zjM_%w<9eA8aB_RQh&vrkSKB6d$s6OQqT3AUEQ<}IrzDu6qWA9=>8@bk@G~{WEE=pn ztoL!E$uV3Z4=hYbz#e}?;R@~bU?1{>V`syF8+~p(w?sQ)ZFj55QqpMS*Dc>skx{vo zFQN6>f*Jb+WJA!80TuzG(Ov=K)vCj3W+HTS2-~>@SO}#wQ)xJo@wbFwz*zG*+9Ck) zK!x6$c(5LMyj#!R(kEpaSyz95$ASb58#;mplbeq<8NL^P1Wq^Cd`@jRpR>ANgOjJX zxBqnA!v-(kDVplZ3N-owBofjTa=Lw<)O{TiWU9V7iVzsN@R#HT$-t4r?fzd&UhYkY zL7qFM-cLvq!Dl+*ql$xC$U1Ft1ku?YDt<6~WIb`9V08|8;}X`x!vc4Xi!DgM>F3Xg z+MKW$LQxy}Q|k3{;Xr}IO=H%(_8V&B7DfC?p7|c-C~hkvaWQ% zQlf^Rz;r-*u0*FgJvMr&y)C0y1$zu8*LXop4=bsXs1^_~12=@UoS*;U72r=yUHqMfb}K_Qv4{*L1`{*@c9HCt~vT=WhXz=k4OP3q?r{ zv2JUKAHjpiq+8VZ&ID6xuswA8U}(UGKb(Xv@=$HYtYLAovlHpv8_eaUC7Bto0ioz} zt3_ylr*ZV=+h3c1mwF#C)*NwQI-cElfAbnEj(e;d9H+=>lU1S)VhEKKu`-G}7zCk% z`T42lU6v}C$5IgXqIQpmn2=E=svgx$F2TT_HqqZBhr4 zm1V-|uRn@m6!(u#bl!Nj^V>-exe~kjFW*gCyuUJiO4>IK+DbBh`PX7pS7i_dDwmN+ zqQ}%)in7osLL2vti#fr&D6e976uNH7Ztkzn`MY@^rUz14bCeGVc(BbL+-_hVbc4EI zoBhiTU2;T#BLxD1(1=c?hUgaX(2;qcFc)`EC11v-_~sHqH99kI?@mg=&cnEFu7Vqo zi7NBkc1o{^3;|n$;(nj)jgn;A$9t<`E#k>i8qJ=QiFAZ85;>wT`tqI@1BKrTNKx4F zwWuPaJ@fPb-UEklo+h^*kB}Re5X)H<0Kj#T8Ie-YeP|wwnFrQo`$mvou&3u`>y^J4 z;76?6M|Mw?;;RlVg!*Tb+UU1vr}&>jw&$;w&&^09lE@HB}F?*2+*vPM?;l@h=3JhAr4j*sIByxZZ z0=i`B0$o6D=h`nY7DS{p&;)@=HsSQsEYcRLEI~kBigE;^pi-=4onutNbf}=L?}Vls zsfDa(C>F*ypiMePi*h7ve;?iY0vix}(WsNk$(vn<0{rCxQ(~3mj>YNk#Qu8wr7MFN zfrVhl^W#m``qkM8iv$UL?Li=fgU0nYLLl;dLQ+sKE(`7&c@_C#7u7#|8H-iobRK>C3hW9hl-~`~?gm5fF$r2OMcJ z0NDfi)6b#7OPlG06ayBrO0su^5uM?Gv7jFA7#s>gXr`3N5n^HHiJ@Qd!pJ%q#vYaq z9%YsE&3Z+@BXDV(F+Kw_;}_{W*<}e=>QJjo6x~rRzM3%YJfwD)04-GSLUKi^EoIZH z5x`(>Dgi8{`NI$QSLEg8ESki*1$lkZjBao6-%U(37`;C`@60$=2)RVN&x5a)TwFBj zN15NgCw>y+3h6w*^2zf^B8hvN>kKe`&=lXqKzNL-EqMC3QKrA~sqJ~&*vC7tdp_9K z(#S=b(z|%@u=JOdY76)_720aJe#g>z)9y8T+|cNwb_glE3zS;Gb@@gf=v=YiCL|36 zc1a6tNCJs-Z-8{A1o{;!wbjsG3XK z*mC}l)))4jvm$AM6-87yQf}Sm)9@yXj7HR9%Fj0u8g@v%_#|GY+Q$uch)3cX)w5aV z<(6h4D^8ZT2p@hIk`X6Ns2PV@S|*dDE52D8xkt?%>!a)}Bmr}Wrx)VvA_LcR!#}bo zCv0Fc$oFm%@J!M%r)W{ik})bt4Zkx(7~qZ)@B3=D38V&&W0PX#ak~1u$Jge(LdGl} zK~XJ*B=N%o+Ap`QvRXYDMgL?sE&Ukj&PPkpW3SHF={c;rQxW@-`#0!SQn^L{B=Pdy zv?#+Qc)31-v)2{&0A->fr_YX}nrrK{W_<2Z#()Fystyeu>LGN&7Lq9HRIjI2QWBN`lTIgQ4JAH}*kyvHVuUXd1S zaJGF*YAn!ycULK7gjR`&pwrF#!>V^;dRp#n0J6w2Pdx?5Jb?83sy80vGdHAdPbEkdu zqKhr1_xNcY;fxb~$7ax?&L3BwCwHsz23JYHQQ#4;{tgexMKfa!CDcgvdMdV? zuLp-NN)`7B$y3q~5c=W`D1rwHS@D31)mCH-U_|i0(w9PcBDI~~83wHWWXkfJSDAqx z$$6tKA$7~r|8|q=Qf7lAV+;mp<0RN1h)J4IPg;ok-StYnMgt)K@#MNC|LfN7-(#sd z&v_en?*%Tags-c-N%eAK=917#f2qs*fX{L-f;WV2?jR7O74OBB_9$omnTUSFChiSJ zxM(1Vi9dut0`~(1qAm}Ad#g#O+dJH0mLNDnI>Hn|b>YoFmp@nMp|*Z!b+k@gqEWw* zZqz9lmYYL?0L)BD^2nMjZHq!yB<9Yzo)x|ukM;PZl~p*nCo>aJc`yvLI@9QKx?y50 zECV(TRZ^P6_jZdICZUW4eOhYe`#g~OW+6i#U_p|`+J|R$8R)L z-U9QD=??-!S;DKd8`bIIsuxgQtd%S?7P;FZynh9H`1r4pw!0r^DeQ}~Vy(3W z7lBNSf4Yp%Bl)0)V;-8h{a?nro1PC)7*B(tg~?yYK9kNWc9cheNtKnme{XaAd;`uy7?%*%P77t8t3Q zP`8cncQAbQRLUDlYdSAn9l2TW2x5GY-(r4yh!6NaunW5bkfB)p!u^UlDE1^~oIy3P z^WenAmaFoQRvRUn5yiTA;E@+;8R@$9d=sAqM~dixh@5>kTlWhKi-PS64h&`k>@7K` z))rV?UmxuKTa=%h?|pc6fC74wkM}2ixN3jA5BNkV1^P>*N)2iA@TE{_5|q zwZPBLs&FF|H?X*&pZDs13sMreMo4N9Zjr&Cs9>usRhpnR$3cl08dSEJ4o^r zsm(cRCUvXyEx6(I{lYb_>&=O=|JHL*F6V^x52N4|MN`5A7s1Ub``<9`VA1EJiP-0B zmzS>p0haCi0i73dwl!D_b>K=~P2%AzaZfjYJL<{`5kU9-2F`bbiXC?ZwADh|4tJdc z0?;lJP;hEDrkd?4YI9=#PT)Z>Ky!t>^4$>ooH~;6fu^ndB`d^bF6iAZIzj;rW} zK^(n_LO_*93gxV=g+$2;f&1@ksBOx* zub&R<1pHk3UI~r9Q_#Ow&Y7*2?h_^bIf#Vw!jroWqe;ZD*Oztk-SVvcIB2U4r0rn$ z;>*hNa_`{ao9i1F+ZnIfSts%&D#T^yC3QpB##25{+y;r%!g%xfV; zb3+njMx#jYNkJ2NE6X4qhUs>uhX!z)IsWS{yI z)PzHhf36^%Iw2PR7i@<8uL-*)Sy4KpJ1yx>kk@& zFbV#GiNilNPDhS#3zrwoc5ivF8BJpQ+s&z;RML4LpK4d6K_Ac&A_1^!wjoqcM)wKj z0FJ8@UP9@p+pOO=IQf);n<+1Z3~Q#vLHXk)dd#KVGzH|Cyk-#sQuWU5Rz05`Gj>JS z;!WnJ!DMSwz~ z(%mK!b=%L9y51I)>$-alA-hi0UHOm4uTZLDq9Jj2h#VY|G_iheGnOhmSfoSNY!Od( z5|RQI-|Yy2iuhF39^M5u{Jq9?-ZXoOf?9jnw|(OY*l4DLAYP^Zeb}t2&hU6lYvES8cGLQs5iTb63E>w{PzH143V6@ypqBe8D`Qkk^ezG` zPvNdRN_Rdfa&(M#0%uxA~kswjW=Es(@ z7&s54A(f7$rQSoU@W9jZh2xdb`y*iX-i10h_ne(sf-$0`l>NLMJP3W@;*~C|-4o^I z!9}FCmH!OledXrn+LLJNOpxebbw1M*|J}Q8HQaUosj6I1P{=ZNzrPOC31qkk(I;ll zZs_p>de`n{{C+p?i|IT5hE5OcR}}IRH`O$r1A-)1yeecOAkLHCITZ(Q(r-@r!3;?= z>?y{u&2cbq7DTnlFrB(^Df6!*yg~7#M;gB0vAK;kGw(F`K;JL@&z}>g-z#~Y zvDk*{B6aMP>VVRv0W% z=o+iwVKT3ADIgb1yfln@{d0xOaZCQ+(C|0f&k#*?a`WQLqDVEwmDu+a590ZQeBM z0^mAwTYkv1ipC-TGzO-vyAh7`bUYAIY{V5T0BW;(|uWQ=oyX8!4* zBhGE{P0Rvl2M1qVYMjOcDhBUt|FrOD(6P%kI=aQn2S(m@m%HpO4+7d|<9O+m*jZZ9_b6LcIiEHY2Ef>Mu24=q@Z0L;-Z2TthpQ#BU=9~#3PVW3r zz9+R-D>NfX1iv6(snbsUmwsh%7A_w5pNJrhbs^dE%JfBiS<-*Ztf7=0gtDn-6V@KN z`T2`Rk&%e}`~p9n$-UQ|;)a_g`M+4a{c+tRgDClmmz=gwmlKa7m$zp@{#)rAnEm(F z#}7N!lu=?|eP!ed7|U!H&qz8id=AUjUlWQ~0chZ-!$X_TakBLVin3DOv8Di(^~HNW zwb8pEQ2D-yUed&jM_!E2^gH1wT)S9}Q}Gz@z|n`dE@F@T4bgdWMWO;5?(>eMpNa$$ z=K0EC*438&A#`{K=K*1S?SQ++xJ&>l?=w(MMpQXGX~biwebjKNs-hD}%qUsXpLvRb zr&QAYKB4u<_59F5eUruE-fvxoJ$RTr0P{z9G999zdN%sptOo5U`gI(=aF|XNu&J>R zMY5tp-WbK;-5cM>+oIUvvg*&NQpRas0BW94-yOg+;{43SlW^zriOV{>7dlhZPk(rc#B*=n zBP88H&Uu1i(hvT#pP8O?Zh_I3Cp_wxaFe0Y6L{fA&RTEM!ymZC{5dMS{i$XVM- zB54%^!4C-=Hu%X8x!B88N0zp5nt+ z;nzaKzDNu{7TV1Cjch#Y9@2m1AVjLMpyGKamKYC3)O=7VTdW9|EK@pFnAPK(nBSTa z>+RL#qWZT*>S`qaK zh0I=#o(BY{mA~8Dz}Bglr6P#Eql-%kguM<@U{)pp4sVTXST?P>eg7(f(X6yx%{mGC9`D{9 zl*tj>r=ILK7_LV-E@K59wq5kHSo+wV*EZsanFbqDZG2zybDVLzNXF*=2kng?{C*$E zyi%qm3?HbhW?kVJNk2O*YI$a7K)ub!_E9HQgYG3>5qY{H zrY^I?WgksZR7Reo{!v0Eui$W!0gp75Tn{oFJ zVLnaHs__k#WNU&DuFjPitO;Uo?JY7Ytgl#g5DqqS14txZjSoF@hzUO6aAt7c{StS1 z2yUh8uP6E5{kEPNr{??*h+~f1_uA-_4zIo$gi=1abg@+Ty1}OQR|#U>-Yw;K;*Woo>mbEkl#&&ug6e z#%bn;gB34~yb_1G4@SVhmQE{efBw9uH0u5yuu!YB;t2$OMmCLWcAEOrj1MW@z35^; zHy)y<`_%`{=nya6SVI4m0*7Ubr&D=d4A-V5mACrJZzthV_Ml8Ucm~Zc6Xr}2evjMu zj!LdgyS(>~=fgU%NFhlR3x6N?AEi4&g3h%jW{RfeVVv94Fg}qMp1XmC6}8$lVt~lk zZ9ak#o0@_8=0NL+>sBSDnvuQEm))7P!ZcD&{LqcKvn2BHSy-~7RCsb9emOj~l=i2n zAd`l&wKbnw#H(9pyCXhEYXRGNqbtH^BM7Z(j-56|A0{}c_@7AewZ`;*TB6XU>vlq> zJ9u(%(FIGUTR<@+C-l{{p(A_w8xOLgHskni7qcbgzqN2Q<;Y)No5M76;utju>}qqK zCZg6kz3YfmpFcpub;*|yOxh$|g1sHP%2TLU7c(7n#vc~qvWs~}4*ko~bA{OQ9(Neg z!0NLpza&oyzFMk(D9;KFX@aFalVx^6y6e1Kc{cOjVluR8kfYZfr(R(04BSy zk-$E@`Xq9BSR+{NVi)g|b;<}185Oztq6yOAQ54Ly>xJ=#9~`PiN)J`KGF7*YNZg9_*>? z{vSX|VgB*YU#K+QE%||Jdfli+O?TGMM~F6NB)^V#ZBTjbfwH`4h1Ujl1zlL;e5qk5 zl#`2I8E+A-|6r)HBYSrQ^`xvgx#E{;J6JdhW;H!jTKbcal8(VrG?w4>63R`uYF1(OL-l>OpQkHImzA8(N_t-M^>zU ze`W$7OG;;%9ZNI+{H(5{Ag_-LSA&Ct%v46*l`DqrYzuJfiYjuam7;y&achx^!$D** z1iD%I^Uvrm>`%9w_|gPNR=-Uu2$o>u_`a48G1}$S@$bTW1Df$5Sp)Rh!`>H?v5wi6 zC%Cji;kKeZM9UPx%oixML`>_t4yIOp2GzfE(X?W(>nQ^nF=qUBP>VgOuP?_DZ0$+% z`ER$&V;e&Jk`?8dNJi5LKWbWaF#|`LvqlE((z@~PE({MJTJTIw-~y_QNu(-yv3et$ z9U9@wuDTQ~CzRmm+2qfJIg*kB1%J11D~_~t*(r7cIdcCi1RyMMK23*6Z#?e8@9C(2 z_SYK?(4Anr`Gyy2a9@-!o^4=(e2bk79G#rDPEQk^;m0}-^6TDI*=6}6&JR)Ks>=pIa%KxcmJmfBEInaoMG!DaNg4WG|}HC;jnPHbIn!l z0y!|Y4o*Rz5ScKot~!_>9OTErak2DppoqIeZCmP)eQG;?x8S0s52C+2z~H!n+00RM z2c~0noX21)<^oDEn(xZCc)kpsSp_yD@t|9C&z`Tw?|h$~QJ8*A#uM8|1LyU zS%TOqB^01I;mTk@Ga_Jn#vQB1^q-fQV7pa0DFl@{1*M2bWZf(8^Gu_>fLW zZN)jzPrC~vQY94F9qGN3T}Nh9E%AOn=*k!_fS&cMd@11?9!y#Z`fTW#;fVkh!lJBs zH=Nm|zGQ?D(2F~)SWDS+(;$A4W%_R@Y2D4G`>HV3O^n8BF@|R~>R<>#RH4)q2h_RJ z{O8%CA%;Q#esHpm@IfuE4N`e1JnI!#=ER)?|7cey52RiPr5bcaPZ;}`P2jG7O4T^| zb5kL0@sN6>kUeb4dkr+AcebDUFpLQpMdyf6iPY&o(NPpBT1ftM?Z^nwuAwx@e8#%b zsf~}rC$k(tLsoXI7ojNoP7dH(*d@r%uXo&6-#PmkcR4SbOddaC*{*_)E7Z5_)}x3; z4(A?C@k^P< z&por>ROGzN-lkb{{UF`rNN$y%l)Otsgacq3vdJ$M zQ_PMUNKF<}TCo8{VI7@Oer}A=j&BVRo_Lq@e4*9*@Q2(LN;l}nOotyv8Ai`SfHxC_ z!<%u@S(LQ@-z@a|pWJb_y98epMD5%+8Q1BHa9<0^ZgX%;jKst8wz{?S{~RYIs8Ld` z4-8tU2();=8U$?H->A)es_p*LW~C1oA{!6%%|q@x?%oD2To32m%fVETA`E3 zYh7z;kUzUk1|%?F4%dO@YaBWA9OzDCg1#N{NLOqk<(y9}LED8kShxb>oaYs)bmiA# z=N70)mDvnnVPxMw2tlg2>;ZaP{Sp8vIzjRPpOuSBZU|iQ{B=);<~gYQW-L zXmrZlITEG0IQEtD?I{IURK65P3HT^um^|uSDSos+w+nWFdXY(9dZRj|6!w4&HpxCH z+0vKHEaje=5UcU*@af#JZ7_n4cW*Strk0;mWM(!Or)hK-c)gC?Ea>`6-DHl-(?aC>>)z9DViOG6GcsQd-tcU~ZVpSGG_^h`=_%V}(Yj^_jXbO{bNu zqIo?z!;UupPOlDOw<1@q?9M95$G>B`N^yXxqRr8ZM11u|#wu#<&< zEZF39J*cbpP=q2*ZFxmvU~u=pY){9MV7+XAvibe;r`X3LNcHE1mD#n`vct`)fnBtW z#V5!)t@W(<;oVoOZdE%UD)OIjHi#Uc4A3ZBkj%+ka(s^R-I7Z5@JeS(&7D@LTc2y)A?5&fr>V9LagejEyF)LFYpULoEdQgmUh>s3_ zeiKf~)EH2&XdLe@sy8ZR>z2qcT@TK2*x!jiw`l`Tm9IsprlIEj>xAx~x2*~QHd7cR z{np$trf-dY|33S?G4XnzXUvz2-}$T>)&;+oC>!ta9@flBovQ}Q!KY-fRZKeFmV>s| z)`nK4g}RM-hlgn0g%5LH31!EVAE>P3sAko2wmZ5IAI$s%4_6ya0!gM(DbjD09=C?d z^4f6ZS`{Rv#zwZ@Mv|2xdW-)&jtylrJ{?IWR7>NZA2+8MjJ;3p{?`Adl>N#uS&31kmjH|* zO+Tb;?o!VEG})fMxBr~I@DL7t6t5q(dw#UqK;!-RbB{=VY7)678DzjLhBO0r^B0+T z4v^qGHCL0hWa9}Epl3T!8HWnCZ{O73bl;33q+Ru4n(hd|U)Z`YHg~MW-oe~H!m(sR z^ywyeW^5=gIPdXJUR9Ch<>jKMCz-XCZY7X$QKiGH7SNpYy<(%OLdNpqN*hVTwQGFs zqIbpDnWMhDmH;hTUbcK{*^Wk>HXyLsNMa{5Mc$okKh1j}L6KAoAqDyAYd)?O!(aD_ zG(fv6izQL9D)qaw@%S(gsL7G5EbSNBS-0H~d?3QiYGHM5K>wz5_5@ntQCHRPNy6x_ zzlB+o9Ac8M|Dnp1;sLB86nTtTfS+%9?*@bjM>i;p)1G%Nf9Q<;k|gkC-z#aIe@Nv*~jlyQ7}AOuh<^U+dqKFdXJqv{>(zh zzKm$(xC-lj7PPTn9{I7J``2Hi@&Fa(?WcvjK0_a~V21tFmyMs(8?3@2l@GO^J9V00LfX~wgpw&SMWV-L?h z0ll;|Cs~=vFD@yKK|{(`!b$<^&(GuNR*90SQG&dA^Wvg*Qq6FA;OFouz6p+4DPBLl z_{x;TI=wxQSy`U;*_kMR5hl-kfyN-Rdq_wS?x42Gbjxoz+ z8D*jD%su=L{hx@g7psmxB-#EUQuKFpwOxdYr@3-+L^~<%?u!k=TO*? zD5$x_Vffy_Moh%;t?9aSJJCXs9#bZ{8R|D3GAC#bOOU^?l3rGd&M?=!ydzWp>zQ5^ zyPe813rasaIt{KfWGoYyN{a5TsFM73KC>xB*K+-*?2gaqfGiIa_>H^8|4;5?#J(FB zLi#sHNdSc+kNio-XcwL%O!{6Of+W`Y}{2A3sndZXV{%#zR1-@x$tMA2l%$6>02-`)Vg zNG9d4?d%xju}T*ViO;fEvhB6kLwp9rOlHAI-@mFx+8u9we91R(kVo3l(?fDkJhjx- zi0_)4_~TD-$|b12YacH+F;tFZ7eD#t+eHsfGTZZy!XYU0ihath^4+>SjpY3VW!f?C z@Icw^%!pB+tU5+kj~)@Lx}HM$*B7dz#zdx+I4fflIeo55A|qzBm}%x>dsdw6CbE7! zItU)q!~5a)a}WzFGv#zfd90wMIr|t=xoAN!1QJAo5g19;GbHgWvM8;oO-^>X>0LOe3euW+q8EbYz#+Ejq?nAjo0;Z681cR z{FN`b1#(pckv4k?>bN zN8?w6`m4q4YLX_E{pMvGL^@?;Fb2XW`4~1@e&u*pIVhuD)E^;LyD$#}Uwou>)^25C z9s=;4>JQ$sbC(#tabDPSYSIU{A|@^yXIeEwiwfqFh0JHA;?fWe;)b#URUo@A{XHGM z@&F#gw=v$1%V{yJegIg$Vo)(z)jODxs%J(cBy8bu=y`kF(Gpy>X8uuvEm@;+3zm8N z)@kJg`f5L3UF~~*R$E)|k_Q>Ty6ZE&c$+TXr(TqWQ9+q%#M|B4k4DQb_12f>jd0TI zw1qeG{;Z&}!;k+7_?9E4%y#3lGA=9Hwo4k%NB7Gz?C>LzkA0iTO^C1Fb&AxsIBn(I zwj$unm~$t@`&x!0)u0HicmL>(ue*DjT9)THefoz8Gkx^eIATNUS&UPU5P$17*slmj zoh`wQg90rqoorMi`B+wYIA-n2R-+|jJ6aZ1{PIqg10w2 z8iHiBxz484%)&}A_fpOaq|p{+qwDZbvDl>-glaG11Rt=0`0X{ZZqCx~h%yp|rgaS_ zxF0zt>R;W>jJ4^L45ORj54QB7HU&>YCUrD-si}<#6;CIVbp^V6n5ri_y=VG4%`30U zzvm%Tr>)Z?;UM|^X@0xIsU@P!c}CHBBkj0xaVe7{2TB;OK)lF*V5VF214GhzeT2fs zx|jo2R;gE43@)y$A@Q>@ejmj{o7isic6xp(%^R+zf+<;cEP&SgPVZ%6=RJA*v*I6D z7F0CVbFV^1<}V$}T%ux7CYi!_teM-3ce<&KSzsTY7-oZHsWGlH&oH>{-{%QXO|tL4UeHOSXg%^5@&(G>4B zNNb?G(Vk-j4qT%?j@vwQNcdmc2L-zGQ&|0!s9x|Px<_n$Nb|X@auV2BTwN_(X(^bl zITnK)lK;Fq@)GE+tr1(lEvHZjs zG@Xn)Xm%B-mruzieNbRQ{@b&9Ih1d=8~E$J>PDeYBJxipUENn znV#IG;Q})6@mT=dr0mQ+=7VMd!*8cmUu^e6i@g6D&O%iKIbPT#@yi^?Y^)=xRHmjf z2G$>?=Kn+h8THia%Oc$THN+?B?Xx~hNSifgann!V>Mij!>J0OIPU|)o3g$09h05koOuZ3HHVI|4vWC)lxE?))LCfYL975Q#ZHPfAy~{Vw@j zx@-XTVHF50ePi%Zm%NzDvL*!bUGJL*Z0d-BoV3oGy|)uF z!WTH2;K{eoui7~9RSAoyI&UZip)6!rFTcTeo&ZmoZ!Ua>n;Q4yfrUSU<;aC6%?Mm( z|B;c97CKt~|GfZFe(}Dj0w{wTmw-F$Bb9<{bZfC-EztvCaSZkc*0ipe$Dr+t@d+t) z*mPMDy8JhnG0zv)0RTYmEdc=e? zqSXL=^C#N@eD(`p;aA>#vFg|7^P!i$A@;Xa<5SH1F&Dx!j`?rS=Cj4d#unYa^At8V zwyEh%*wZc0&9+dtGXxvg<$Nmfc}{t}x8D2^2*HKP9DZ+{0Kq$B&ynCbM}fck2+Qop!FmK(Ax%6oYI6K$!t`kx% zV<_ZGLVj5Va#@rnk6#!Xg8aI;klADh2qN$WJ%k;Yvo=lVLcoQ+4~zamwUqTT*K%Q^ z(3A)yg4mu2d9FZ$_5z8D!Qj6d^RFt1F=7}7oSmK|Cm@2;vr~NM<$JVVt(FRWO=5hP zi5{`TD%ACW=&wcTpMc_7$L=|W{%T!SP1|*|jg3vZ zaqHu{vAIoc+oty2F2Vlh|L5*adnL(|E3squUU!d(x97{HGAnCg^}g@yCVS&D!@-Cm zAZdgFMjr&|ALt+GR|wEYeGoJdKtR#}S|DmL7=fH&k3Z;r`-xj<3T}&U$ z?0UTB*28YHyBR=LX1*6M!o%OS?bxwnoowB0npOvRf~N4&w{Kga?gne~HLB9vI(QN% zAPBzl=6Q#4sv*E4MIhm$I|NwBV>`HxbT(e2Y7wD|#(OXbrHD9W(&3n_%D7DSW@AU7 za1p>UXWja1#%ic`QG7xkWEPrPodDObOYqXRmKhyEJ{Nf*rC=1~v<9MFAC1wkN(LK0 zAGM?bXyPTc4fU^CfWD!1L9A2}f0#d40E z5{7Nh^Z6C^L!V2@4*{Hm+#(0Jy=t0d@Adc20etpX{|P;K^G}*#y*fx~xRp|Tqo91) zIr^k?{vS;ylN)<`dvtVsj5~K<=flGjnoK4d`S&o}Kf$EkL)*0ozJViOmuNIL(WgLP z+q9DQH?n0nsK7_?UTLrzVTdGkEnG1)QYtQ^NCbyRaatF%kUyMiv9iIsB+rO zngs#kjG&W-W6`2SA&u}n8b__cby%p56W7RTmMwp;(X|%p8q)2p^l^>%ubzi8YAe!A z)qaON)rFKRI|gp|f~$j2hgq)U4#w1TB_sAnG4RC`Go_PF6BNwdN0cWMz}ji@N73P;vSUQ*85!3`YFQ1lYWkZYT39uZkJKog(>9^<8tPnwJzxJ+ z=TIfX$@mS`FIGc_j`_kI7NM4A*I$;a?3y_Xd)-|vL6CJJaBY%MZm~mQY4O>AF&4!E zvN(R0c>peB?Y~$qkQ4x1E|+-t@BxNl7z*RhnDcWGU68}dH%%nx3uj3J{@yr%Pk-qj zyT?EHelzr|gA|9`DGhI^@qgBP|JB)Sc6xk#LU&(&olj0~(R4CF({;k>pUyCyouKQK z(i@t(T3beVgU;-$Y?sdSH*J6mgy2M}*QV}vfh1Bxh5}!VfdZAP+*!VIsW&Nk7Ap_!wBaONGs>uEM>v6T%9Q3J4g(u2V|uMWT&nhuP;I5iIj zNT-Qp00!gN71T0a!Wv2IpKiy%$H+g{@Rl)*wKbsJe)%a)`*~-f2~d}oSaw1?qe?q? zL47OzH)Ds+OXG8R=c0N1mR`61a;~ug_5ElE5ZtNz>+AE*T`4=CSO0E3{hSy{0l*kD zQnvPA(K!Ig0}J6dyP*|Z&TZ~m`;S#Kbg)jnI5BS zCumzC_;{Vx^Ij(Qy0(#3+gj(hjRJjMggk*U>t*ckERosytuWGtKlw@W| zx?c)Xl)As9f}8{gC>h$>D8l&{!jCwDoRCtc?RJA9Cg#HDU_SGX7Vx1Dmu)fQw-fPm z{rK#ce$zdB_-+&X%|VR)?UV*Fe&>EMG~r9L+4R=Q$xXU;|S&8Rhl5h|`BL-EUjf>T4Ucjr`|Tt}A#aX(!2~mZkg*ivXJD*({%a4Fsf| zkrRV+K`K`FW|*cN43(A>4FiQN!g`X@0nR4el;3Uy7X|rt)n_C%ciP8St$97YY}tU8 z2gKHrGjC*{OuklFx(}8sk5Anxz{z3M1R4Q8V046|s*e0&6xy2lU`)p=Kdxv`|A)@P zZrNafqTvZPWoYkGU)VAX{Q&hfN!$k$U26xzQhe)7=+_)XhoD%5*9#=x>FFy_D-z$X zMt%btZN5KS>YoZ?${5t}bI!v5M*))(gVKQI;!4|p3Cq$=BIgY@3`KxpX&_ZQ(yQE_uvO_Hfd|d-@n1kA92q8ViUp_r?ctplN&eb&fQn} z50?b4Kgx5wW$Ey$@ThqJWb%jTU%q`N0=tE;*Qp$%{-Ad<)q$g~##1S!nNUpORk zX9e>-oQ}N$4jx>x^3<)CmFR3rHxgWn7(1(0JxeD-h#&xnk@b0w9WoWgA{=tb*j;v! zi%5GrQeqI1TyH6Q9d(RGvlmkEN+YWoq17k)7=PA`!m=Q-`rYms1L6mXK(tfxpymS5 zn=zvI4m$)SyD#EDyhUQYB)f-pKS9`GG};p}B9uUF6YeGeaN=yL%mLF5e!&*xM}J~% z9tU|21KFG@r~<&2723yU&6-5fM}WVSCE}+fe~&3&Pyb61uy2s)KcCN0O2%rr;HOWY z&=BLAnZF4x&mD!!%-9ZXmzcq;)#cBB2JppS{U`3xyKlB}yFQ3%`@_cn%cJqX!l$Qd z{L=|$d;8eizkzPLr!)K=yy@)+U!!~5HkIDnP8x(jXj-rAKSJ<9(%e!;5ef!KHSU~89y(bxXIi}u?WYosPCZ{$zY79b^N zR_0`NM0O(;ztIv=E@kwH1j!t-Vj4*ufH;ASl(LiqI{nAW_0K7yA9@T!kDLaqR!dx7 zUQ&!bVjOZQ<*WeD9bxXt_szj9|Hf~8@!H4xxy%6G|FN&Sx8D4dCiUC>oRS&;N1bz@ z3%im3&MSO+;||$AzS(RKdj}`zx;=DlD~mZ)hTEwUPZPW#{f+4Gd#`-{5M(ne#-99G z>#7lIU)qsV9ZLovJKUK77rcy}?rzU9b{<)eUl@a)FwWI0)?@{@)bG;CA?ILAEZt78 z-sbm9fs`tiSwo_S4`Td=u7dziey0y#bAYiNan`!q341jRLhc1d3hM}jjy6YtbPAMj zd#%hM{W$|3KrqSSnh4PKC{3(jVYTb)X?~?H>2si;%Fuml#@n^lUi~g8FvZnwGQ@-8z!1@OXotV8RE!~XsJFaE{$s>Y`j#TmqT~!8JW66Pva;M{Pu_TJ1w7+Q3W69)E+{Nhcg_(| zyzGfnjsY_vr3CN&SYj}#K%N|G6PfgN$X*>+hg z6*j5MK(I3pKXP7BhUB98>GIIospUN%*lv6*o}{j;R5&cndIVri-(JR=Q9TRp~? zXGlF>f5xJQhwXJA>j|A_FJs6mEMyTdE2^y2;KXu>xqw*l0YC?ZsOkhdliRWsSH(Iq z1D11NOYOnqyn6<0MAq=n%IQ}tupGjce^^&!{G8DC7yex2taHcL3T&!TSKexL7PSkY z_p+|%VzJO`ntu3U=AAlgmixig;K!ekUx6|ocXAMWuD+}e9gWV&zV5i*QZ;5d2Sqyt zS^NCSfWINDGmsLXs1qoKk>Y@U=y7#*g}&b+#)$K?Gdz0qkT%;jr<9(SoSy>p)cI!K zG|hI~m-6V*f4e)nAZOq&GPIw?$Gwkz+Q0kg+iky`AEXp-4QcxemE;rrSN3MJ+b1_p zap&%}T3N`^sB%wmlKwYZ3P_Q!wX{xlfk>sh zJ>VeZ8>V_N8LTP2wxozaS{jH1mN|>EQx`&OQjIQ!#_2v{Wz-S4a?G{9_GBY34DYmh*Y+2&&h+a;`s`w*m1tv*0bE;*%r=Z<4zZ|W z(2fy9FP8zE3^W#{dOZ22Y%5iaV<&>^&&u1odd+eJG!xLMZ~BRng4dZqT)QCsZ`VPM zj&QuMeC-Kh&{++;74&c@MU#4_9gxD`;Ic`r= z9$sfIA0|hi^4|aIY&N@fd~%9AcVFYv8@FiEb(l56dX{2Wcj?N z8^D_4Th?B(0$-N2KqHvLvsmg4iDfm@0>T4$D3)O*b%BQu%Q> z=h~whtaVn#&IRl54hlY6X9G~0Hc+!9E<3+F`(EZ+rpbHL=;&D^f6hozjbEdFOp*nZ ztnANH5aWP;yT!%D8P@AHQi^zS_8gBMJ*4e+Q*ur(a?XzdJo3Id_e|SdPV@Qsn}06) z13xDYpq&h^Y_8fNZuVmw?lJQR9NedZ_rE@y&2AnapW^Pl*ZBDOlsb+4v%Nj+?cG4v z?V;%=qQ~b$mFt%6J1x4>L9%LBD$eK4(VuSF}9iM@!8M_i&`tn2r?C= z%1We7(pM%Y!W~ZAXX+?&E)`(ntgSpvtDV;BX3sXgCRATBM2 z$`S-BQf3o%JJHuXi8)=h{GAs=#VbrNaok3{Qxa&Z55pU{`kK)Z=)=fC49JpF8j}J7 z)y+M|+ys|#=b(=In%Yl{8AHm5L&gv@h9M%xjGP6uWfp~>VHmL9tgu=;H=)8YFL~OS!T%4a_vsq!gS>xfuclqIi2ejR6bIJKxDdimi4;tT|1<+`1qN203Z9*uej%DPrCJHy`OWwQ*wEO$h|>CzutB2&7-3e+`aocAD^7k zWIDm5>jdz#MIEiK=dyWzCpEmtbt?cEq|Wz2$T7}SCAFZ)_^&wu1N3SS7=T1c04fky zYr~yX)##tjIjMo2)2*_EoJXT#utq_R$_n&Bi9zILtjyJELreX2Xh$PFvt}uV3qTIG zMekH{4UkqZb7pY_PJ~EGG9gO)3`v|_J?OSxTR)_#a9Xe))vpXKCv_HMB$e+J3zGUg zy=^s3k<{6`aaA#xm1IR@6t-HQ?B{prwlz{iJ~(H2UoNPEAiGvlReSV@_!=UJ+5sA$ zLf777x8ASlO3^v~BEWIV1u;epgQWg_Oh{3(fRqw)woIX@zW*BQ^$P3N0*mDwPoF-< z(X5ZoW4=?E8NC7pDI9_?dM8pZnr}?9MKpHEFw^#k9Sd zQ+$oU?<1m*cWrmDH`~LVyRYK#=u{bg6U=6NGSo9MArIZRr@a2Al^uM(ad1IWdlB$# z5JE5{`kT=g!ND@SHO|Ik1=KIV(-7k0O9coe$}Fvqt5T_1{WYE(ImYHC0xhhDuUB zLN6*A>ey76QcHru1*}tvP%Q)7{VNs6s0ScMw#&00vlH8_Gp6}F37BO9ymN0f?YGW} zIR@ef-AMdSs5jX`3qNMPT?QXo_w;ay^<3*HLb= z8dp$l6M_(wUC{J;*Z?qRO((5ffFko2p{kh)`A0an)lnGxjGlMz= zUSPdm;^OK84<0<=r%#{IdcDpm4v$MI-y@>8ybn)W-z-jV+>S&Ce`WNy%m0~reCb#I zsr$}%{s)H)fj)Y_>@;!4-_C8`vdhQBD1O=#B%ZQ=zcruU>JIA`YqP0IhM;gma7GxKYfDd&z{n1wM;3cZ)L{s zGt-;o{6p`<6}e{H+&fNhzx_LZW%LJr#+ZPg{I%b7Z+-V0ZQ8E(V;pX#oZbNQM;*D( zh7i6wn@w*WpPb_M-B70<>d5ws+6=^GQd*;s!~vymH@#K>u}Aeeq?Dz?G%7_ zoD5YbVC_ZY7g>g4#=mPR_SrzV{yk&;SSmqO<dxxrMnT0tdq4`RCZ@Y~c`T?6h zqTdckQ3ijCPKg#241JI7c8m3Di5D-PVY6LhwOZin@{$*e1r5XSxRm@yT*|i{`FBDS z&O+0!4-Zb_pZw1M`PUl1-RGZ)$NN9_AGs%Qz0(Z+e3s(&H0Ag@bN(PX`iu|Z%hT!f z_VLM0y!6tme0+3D)2Sf*)5#Rw$bQh?0A>{3{r150epa$rq?I_j{q#`8?6x=MZP6u{AqmXL6mo7%VRN|_UC)cCo zn_51J;Z^|?1MC=8N8cVaS`$29=lI*BsPRfxMN&_jLDv_$e%7_hhk>7C#2*W;@xAn3 z@%Xd54>lZv^{1^vX&!5s}v&t#Pce(I)3GVyOhbL__nI9b77~XpG-~Y9# zzde3t9KiPK9hX*DZ5%d7F~yg;@cSLP&js(kG@DIcIzB$dOE0~`$HzBmI-8;GCYa8q znCu;+?S$SN6reI`E*t7;b&F33UetRkKi-Wg_fFj^#>+KzOiH({oOlHSuhqSTF-2tM z{@cDbJ9s>jTtd^-a3r$dHVZD%Oj=(>GLa%WeTGYoa?^UXB;T>f=bU{_(@$$vGS@ZQ z_OqrA#M+xCr&s_qGcq8uD3HLy=|zY8I#6E}l zfutLJ`k2uV3H?Tle!1$g-uBpRBR1bGsZhUXMSr(cx#L1o-erzThq|U$imx2RWxZ;PPHa z^ts^umnM_Rz2oB>c9Zn^@oF9AoAxO_Z_)*2z7nH&d6K34q&J41;FUNYB++Vj|x2u0udu~hS#W?xFA$-0T7GEABrjjoA+U= zZ7iYeIb>7U(THS#qTv@-iIkK;I=BP{D@m!$cKsqe0l*b_DCuX%v6(}l8a+`b_-vNI zskC@q*vY7wk%@BFXlW6WP_v$D^ok-Fr=XV5noTGs3UM25#Ag&_V@=_UN{(B3WUJ3B zx(&SR$MI|b*Z=w>rT+H#sXKu8fAUx8^89JL-EQ}D&bM^V z07*naRK0(6bOX0vdPQ{nrc=mhueSTzi46CIW@M{5mFo86R$f#0@j=>Ag4D~Zrdm77 zWJZlTwq8qY!vG&SmnS&@O^>e-Nr2YOu{vET* zd?`1euu#Nfly={8euWn=o?^9J;PT=e=ND%@pI^~143AUFe^3~IMvlH8Li42Sri+8a z)8V^+_V0e=)ZZRIRR{2?U-}2`$%BVYzg_O7*x%HZUGI0^f2eKR+xz$;jorMrSK{DLQhAd*-<1UPjNrjT27P{)i&4jY457H~K} z>gGGhem?<&b4gAUFl~1fLjZ;uS;iT1!J65EF>x`rsS_ zL8!*^mSUe4s%pq}u*DZj6jo_}Io(xPu=dZz;dmYFae*qLZGCQO062}V7AXtZ*mP#i z74#DTvz7*iiD0}`vve~k$xg&s)_%WUL;X2>e=SucQfr%&1yqzQPO%03J{Vxj-t5-Y z+o=EGnwZsp%il&PV+oDSWI)M8Laag|UCAX{{z(l!rGz2Mnyxrx9q!2}$p(R>)t7TY zKlIpcmYC14@Z#BHEEjWJT%O^{qlbKPc1D}c1~JArnfaRpdNVlxunm(-A13|L>8-z% z?)z6gergWj{U7-%y?FGfiT!FX#qDX%@iiiRfSmhO2;n2My}kXT<5L_R-k`~30@>6% z#dLNwviw53quwhieiH&*5ai!jevSa|0AHy(br}?hP<8T45x|WUTniz9)krG)k9Q8R z&DCp)2nIMOaLok7s7sN}Js?^9$)z9{tDhkwsLrZMRGtB8C%~(IT-gE2!~n92czRg_zl!?Aa&b^VJP9~Vnj?lJK z+13++fPPBu2|;H1y(ctX5UOtwFwiUF51r@F+D0-k2~N`f0@eka>ghETsJq-XGI{Ti zQm(73O!$KdKp9_`yhF|m?`34yY1>UstRLyJ%u-Y50tA08O|GyQTlOz3df78rDkar8 z0cL5IfjH-Ny_L-?$oqR8Lh??WA~>V!<1w_Xu}sH+!ExrHVk0ahqftFW6Leh;Dwu&f zTN6)FRKF5=G<#vqwxCgWcezR}5i~2SgG;P92(0HnVg}aru-~26BEvTF8e7n4-Q#5D zSPlTn1Y~izHolv6PoHS^<=WnhF$u{hiCV9$^wM$Oy3$Jq!%C5bp6m4*o6QpQ`6aHd zE^zkZDOQU)9zTA-j~_jx#d6Lu4c{xd{68T2BO-b`gz(Ha)8*meQT*07|HD&%d;C-! zz(+s)OYZFIY1{Xk{gmQMT*`YL(PzB(pPzKy{o~^sbo16dK0G|7$y8Q!8R4gGTlltx z({)|mxeD%iN4l^hplOu0+PG_x2F&nI52v>LI43Z9kaJ3oAq5I0;$?!(-pFKE;acN^ zb?KGbbp;opDIzg))G!1$*3z2d7ic6VF*0Uj6bj%)<}d*58YuT)50ck0d8+`NmgLS8 zKvc;|s6S+5oZy(R5`^%<1h~N6wAN-AtANVOafcJgkt8RYr zqKQMlUt+$^xx7k*k2~*vsqNa=j*f27t=sqbvv2bPa{IDR$9Sn*8V#R$UBL|7JXUBFVfGq^1)I?IZBl_TLgW#TELtL z7pl!xu2GJ`kQSs49BZpDLqJF+=#UKlNPpkUfvzMXq4O3Ec_mA!z`#f}auz*z%L8#h z1qEJNiUkrmy_4f0djnBZby&-+Xj`sm)U`o>?H=rwN`kFAjp94y4r-;K!!(*bsP`z) zm$Z_r{*0vpV^J|eu)592JTQPl4961li(aQ?G^VVxoAW1axk4Q)GqfcqpFihwa-Z=Dow{vCPy<0OJ*>{9^17U|0U*E7kKgFDV{uj$PXXBLyN_n zhrUNj`Mb>guNeHtApdRe!@FI#chPnS+c!S=f&A{6_|?1_1qD>J-UlSarL4$y0ld>86{F_L0Vu@MezIt!)Ygg})!uoiho`prFb)8b zjqh3}qU*lI;JF$qQFRYGGOA?;BwBjyR2YP9%PE;`6R#KN;GEXRdh-ogvvQ^Y>{%V4 z7`-eH6_-@`0Y$g`I0sU4A)6DhTAa;MK*}uMxj_XWHxhY8dryf$ES(T$qu`1)I>ker z(e^61XhX-+=Bun}02=BwBa?K$D7GORceCg=W^n|f!~3QP27{W<20oFn3puvKpVYB9%Rb%o3G7nomN;_;J*{OIAk zv|23@V|vP^Ma=YQ7@y zA$7htMjI3)s?=6VsWYSzClNqSG&upk*aN|hY)8bM<64pJJBrT^wYY+DaV7lAU!=H=C4xoYV}#4DNMf-V;Y0Cg*t zJx~Pz#l8e1@tP86`3@<5Q0x_U08px*LNX=tD2^+Q*d)BTUav&?oRelRq;<9ZUFsJD z0}D$;XC2Ov?K-w=km~gq7sKEO0YL3JHWn?8G)D%Pgk3GgoN`^p!C;guz^T>jIcH7z zCG9WT->>`ovexu$5nkV`NC{9>0aw=Z~@FyVpK8SwceR$l2_G*8BfBlP}|785_fBxeeKk#?x06y};FSxV0 z)&1!uW`4sFeIj`Gi<8OZ<-_A+I=*p}r?Y+P+E$r67&d%X1XgKg?{PN)0= zyb^qB9SA*q&PlDw0F@$JYMn>P8SolOv__9P>p;*rs3K{N-4h!#-D=dE$93@J~YqU&@9^NVgKrDuczPs=l}gr#Q1@~BL{H* zx_R>+9~}$XCp679&@cEPpr4c4KX~c& z`ydFv*T^4&u3jxbxfs=AVH6tMm8Fce zcHGiIpN2012%-DMk`LmRVPh= zb_~WQoqQhs{BkoZv8UcAj36T*PQZ~v<}4G4TIXj);G_29bIgc4wLB{e4+V&&02bMq zMP1p9U!7seCU|OifjE;~v@9qYF-7#-jm+?G#NfA^75aXQtN8_+0o9}D#R;m6;)1NhWu z|B*XCe-PIFdN#!2B<1uf0q^zBf3Rtq`-g|ebnEuZe59aX+qAN)uRBC&I|Sc?e9#)y zAp{ZYG+jTT`!?E=msC{txrwoPM?$sr9*mIzRux@Jkx4d9U3c+iHY0PsqQL|M<|-su z54!<((xxJ?H!x0^ee#+H#3|DZ1<=Kz&R)xlfjc4**Qnfns4*fLm z>4so|0LHS#)!Ld+y9#=Rj+{6>=aoB9bO?tan<3U83J?aCLcsr%xYZF~8!=%X3;S z7c|5n=bRswTplp^n*e=_fOm=9JFaP-cFpYK@Zf0k((U_+5z1`(UOxQve?edS!~gN; zBLn!gFa8#N^X>2Y!rRFZwn=~xZD+aj|OV1lH;lIwe*WH4lZy>k$vjsj{f7ZnuY zYK%YZPR+?U@J@+^I)hM{Kt^iPK_V#+#MtF_l!|*8xrbD^PqH%t>t4MfXT%|47zXrx zL_fr;zgOy>J&ER74vFeMnb<&S2=r>YSgSG}yq$^MZjz&!f zlFc_J?^Inpfsj#(lk`!u1|I@UPJJY$xEgUrd_ae56ujEqO5>F%1fXWo4ki-9RVag; zt0Iz?8XITT#%VJPsAvN)R;sYF97)l1$=>C9eK4c9wA|(bbYMoa8aQDKS{7~!#sDEG ziq0U>DnLPDC&uaokI$umQm$kx8T}op$1)Wt9gEDR-UB#Yl2vT~fj*x{$6&XDoOMt% z@+&UmoOMhHoHJ5k#9YMadolhYM)ZA>)ISLwSFjs(Oe(!CWy}cVF$>WLI;EFlLW~1a zPKZN~q2HqKx7e!D_x%P}SC@G4;tA%L=X`PTf;O8C4MQJGE{}4~OJ;sin7;?`0|K5A z+;bv#?wr5!&Tm2F@(`z+l-lifnRCvTKqd?>4@)E_q{lDz)16QJa(?>9|K@K*0|x#U z9l+;)={McuM{k9a^DL*~w3PA+z@PTs{c6`XuN)p85R_meX`@b(5#*q2s}4JevTcwh>0Q-q zLP#76_~}?ESZC@X2RU&m=OWOMx^2#AUsm_2&(5_z>vmkZajN?`-# z3P3Y38~8CO&sXPZ+lmZZurkG*Ulr$sl|W81AIP;j5=}$^lA7m&ArnWN59=jAOGwrw%Zji z7jwEeKSMtZl+w`WoFC+ppP=wt0GpvgR`~thA%cQ_R3krB*@tS{AA(AGS$fQ>w+>RI z8VatlWUheLldS!2zBYvh$X^N4Nu5iB&Q zzvz8ZG>{dHWE)4+*9K$?j|(dWH*55l3}A>6gGT?9)v$9xDG@1VO7Ete1}^-Rf$xCJ zGX~BHv}AAu;0e?c+>C&ZxO5;Qa*oW&3r0fBT$q?Mhz0-)1}}5U=P2cjOS&ipi_pBe zx&QKqqjN8(ci#P-9~mh8TX6tid;1S*(_e)&^!qvIJ4ATR5q;Qu|H0X8M#slDaCCf1 zv)K$?*P`t@G~GU$ri1s5sQ5Ys#`N$T;l~H5;~VGTJVvzHfFT|y{unkOq7j7wz<2^W z93sWKMivr`3lO@i6vR42B2Iw7wMT70n=s@Ajjw>5#yd_nu%pUAe0>a+ptJstK;*&5 z>d?%zDSiQgz0Nym79i!i?)J0vnzYQ&GKO?uiBWW7 z(o~dC!Oy|~iew*ki;+23)iaRKsyhcEPK2vgqGVNIS4BT;L8oOzrjDGEYr9auTdn;x zjZZlvriATwz%a-XjiHwre39G|7M=lieZNJ&?Ga;-A@+!aY}^GG3~4|ddc>^tKWE1G09*ik!OT}+tN~;Y0>B3V4gk7O zDDQL59XscpbB?qKBPWrJn3#zI6LCo?XD+1_=5KkTZ!`Ew%EOaV%9E0tGv+)G?(NNo z-uL10|M;J|Z++w6HzlUM9Ea0V(kmc*!u#;W$)vq|baafPlbbx9 zOli_h&^9f)$syWyg3vT7J1u(sUI%d``Uf+>6*k)yw(E_~>h~B1U9)}m48yR+Fbo{yKtmib zzdFNuy+n+OG+WrDoS)|uQ!dJQcaIe5hMq{=~h|**fOoUv> zx@)ytp#8e4;!=>s_#I@gU9fTAvV&BcMcee5rO*M+n@qMmgwQe?%*asF2iYzFh%SLT z0n!(8O$*LRx%Haalp+*fK(E2=L|JFaP)*shPArajHtIxMFr6{xF-=#~wo(Iz9omfj zj?p>iBm>HtqVIg}!dCWRBug>F#wA!2vYPsX1UOJI)G7^9kyB`vvb&Il^hJcdCs;($ z4nU5I(x-x9h}iZ$wyQOk>m`SBI6OQ-*LFxbY8%^Qxmcj@wkG8=HuE*;Ah_rpP_Mqf+hlpMy zav$_Ayni~KLRx+@;n%bs+NML(9muv`7Z99GwuD)#1CZvPRAIMILV+6P{B%3s1e=9ib4&o8iAEwEZH z_}R0E^y2A5tT$^)sYfa0Jf%3~lv61==TZg$-`BK$1LifrYi3?DFbA+EP-Kuq4^RWb z9)LXnCm?#o5xwrcf0>-SAAE4L*`7N*I`&;R!O`&zoZh_6dwY8nLW3qW$a$3YBLIg- z$8|R2;^Lea^SQgYIK##H`JMG|48w0DmVk8yN-ipg|>$)rQmHfXy8G@+Hc z-?^Ip6Hz7mNbRr6J_;}yx=)@eikvn59G7+g4BePx0928~rKd(t6x)2e8WAue&pf8> z1|kZF-UB(qd9N9OL_*ULWU`4mjgBhaU7SWWBIz~5APfgH>^ig~R#H>lk>F}w2&%*- z69tiH+A1?Ec@m)SV{-JpOyt*Fy&;=jha+(<f$-Sc=3duK7EK`yS?m(zUPuRh4~v`TrrmwGd2uf0=NKU1;Q4L1VWPB zgBS$IjE=xl05cHXAjUlqUiU7{K=hJ#bgyYUzrVNd_YaS7a&ikdZ{EiK{sB*>Gn!3j zG?~tX?jC}{uqe#vw=1P3M{KqmwC$8`-nxU6^AoLUQmQq|P z$qfS&k)zO^E$-j{((u_&yqf=)|LxBch<+jt;MacbzohTI`Sp-eo@7qPIj5Hayy2Yx zP-w#Y_79J6G!ICsLn`QfU-SUs%63DiUwrX@nDJ6 zoQ2_+z;$8}u>g)yyc365dH*`1L~^giKapZ3;%J)S)nB=a#Z)2{%g<+)dm%(6bq=I0 zT2uD5_M??GmeCY3{%qKdC2J6E$3i5pr2sF~c{2)9PT0mRHX9MxyqaI&>goblmoND8 z@|+&L`+e^F{-Tupjgs>dW`4}fPZ+oaumP7XKs|^e5j6cm$H0Vu34jS0hXgtT;U#kJ z9ue(1qL+wpziC@cr+Z<4|G-aYdpJHh^LSoD$ZnIVSxUDGpd~*66y9Zr;3uR1N^#k?gPPx_e`fV93S1_qvP8u z?xCrFbBM+_x{J@jlTP+|Vf~SpU47ODv?ZP1+Fr%hQ|iJ&Gynh~07*naR5>^bT4%e8 z!dEc~HltVe9YJ^i-i<~IgdW zZKDS;0NYw*@_foN`!7Z&CBT?)>lF|IK@PiZ=1VNX9%LN?NC%{-p%^1lTI@V(X%cDoJ*mS69#Ol6vor1oB8Xnzi0Tt4}MS< zi_2n8;Kz0V|H*ItH}tiC|3CSZ*3%UG8!V~+hn;huX}jkB;o%Vuk8bD+uL+vALDL<` z2d*1?y-vO{D3F4ELFKr-qM^3jQtM$@lpoP-nFZM4hiiIkODarpf=OFvhFn?n)x&Nh zw4xX!KajK0Rt*rd!(6o9)*2qNz=ByMx9d78Fqku_sDMa4{Dj5_*ydOwUjaE&?*+*= z9&u30ursHm8U_w5SxFulb)?2YDrW^U0TTt!#*Yp_1wIwu@FbmN7|MJL>n;m!v919b0DIIQ4SgGK$sFR1BYXnaPh<1P5 zPGpOZ7cD_Nxmsh!tpKtxc6dSXlV%(sGki??bpA zp%4HRaIImpw4a=pbYGo-*Db&GsA)}F)Hu9z28ipxknFY>3zm$bC@x?vI!U=%+kqC* zwEmXxEopwSu7ze9IoGzGIR|TZ0+zCi^UFCSv^v1V;&|YcI0UJ~MIul^PAe}|Nhg;K z?`0rr+>#{o4$K)$K#WNiu4pDeN)W2-$zrNPBWNh4P7&w9MKd!IG(k`-#%sm~DHl+z zB{@E*rv7?1>^G#H3JWr#8r_JhR0ysK;V_qSyX1^g^0Y8L1Y=D^o`6#j%^cB`$V~wp z5Yc=~c%YW2k!W67n! zQ7oJsZ{hjm-x#FUYPhtt|Rvk0CzCsJYsaxXb+ z+m6MNmXaakMbOWov|`vao;W>64%yKtS*C4Q*=M4_D*!laIgTVK`>aGF#A3fS0XxU6 z!tNWRz_b2dSQo4^p$;JJU5+TOKN_O`OO(z&ps11Sz(TCOrf;h;`WGgs1)a&j_}>2wZeKED6~ zSS%M{0JrYkA*V|Xo;`bZW3^g-h2xNzkvNvY6Vf)f>HPdW+oAj$JAe;<=(p(cpS>Nx zX<9fRmXhxh;TN2v4~HhaXaC>;2M0$a`u!c6rbXB6BQ%{T`vhNGVscY#u3y@G!ImvT zndGDtQoS)s?~xpZE>rIK>$WFR!PhGqwP4JMRPilXG^ zs!gp%vRNlVs*bLrN{ST=`0^lO3Bo%`vzm0fGal9XgCNo8?zKl7NmyIVA4%7 zo9!VD1N!Y6&N=ela}xqRefps3`@TsrPIAeg)NTrSA6iN81DdAA>FFID9GxNr&s{gA z>12k!>3Q2pUEfV6x}c`18Wt`%En`)cnn*!Ek+_4Fe>J?Za3{DpP=>Np z2vKS8hlhkY&auzHO3&e~TX%@yIOjs8lq2Ty)f7`-h&Rl69emlWR;wOB1bz$*pl#;# z=;C?Pr?{87+-C4=-nsWQP5aVxI>X`7DIXo*qDd>g&8F$#8*TY{d0SJMb(QNcbN@lT zq+~^7OUn_InkxdBW=PPIvsl^*O)<4rB`4WIVeKA3T1N&==`^Xze*u(p6%(7%7?*u&9nVA+c&qF_X#=$;+Q}pA~Pa9{Cn?S8;+fG>}vz?sr-Wi zz|Vc>zo6^uzf9A3Ue;-Rs#3qsOy6PVU+TN=n->?4u-#tJqF=U$ll!d!dr_AA!6v{f zfYAKAqc6ipQQmKQi6jau;{lkLhLy!eoRKzDR-Zr5Isr$KqelfAz{XHZe$l%!e@O$N z4pbSNP?5S0Qe5jFzaFY}a?XJV71Q;Lu?oe7KVnwkAPWx3kMP4q98f}Tiaz7O7pchh zU;sDm2nHnkoGSe84b-#)F@I1S2L`^8bfy)@*JwU|0;uaR+KvI(O}BvQ5f$+7#{jno zP8J{i&jq#uIOb#Jf1vf8k`ooFF&Pm?ECgsRKq6al?m~nBYz;3lFf^tg%<$}=(DfM$ zSsI2zjMQ~luGTo84>+Gs7>(~wwU!265;IcDlyi@M*`x1zbh$HYx~@mx_gM7{q^@&Z zVd2VE0<#0#$tO-8h+@PY=e+YBmdPhv;b|FXz#f61){HV05S!n_NRvPAna)o97Vo|NDKp-E_g$*;3pxz{FHh5SISj*xwbaju@G9r=_Q{iSxVb_32L*uCeL?4G-`6@_ zI`H>RBK}Wu%D=MNUf}BLnO;76m%84{pRVuRzprzvsrax)_F>W)tVuI+Ziu*-!Y%4q zU(WubjB={RRwK&<3Tkv>|4I=r052m0aSkLrcHTeyCoUz>0B-<}a?=bxnG#W;VnLvi z0zUf=N8ehDeeP&6;M8Cd2%94@`2mJsWQlcQQC73>EpX0)WkI`4p$mw%haIsGkUdd! z<_Vzc9O8{EaT>XY2JES5My6t0y1Oaiu|ZGFDyFV(qsB(hAOle@=wu^A~{(GEx~?@$dN zJj7Vr{^2-SZ8waw1?Sb)@E01}dV-)5Ja~4n%OY`rZ&t=f35WTU_+j<3w%rjQ;XN?L zP>R+eiWqHUI!-xYHgh?qgrufvfD@#|qh0ktRh=;@5U_YKh^;{ZOg#9EQfo6`0lbRw zKxYsH`9@oRR$hs~?ZCjM+Xq~Ofw=7U`GkTR?Bh>3?%-`jYR`;8Y86m*+IK7#X{*`U zvp)H*1!nX)n}M_H^dcp^m79gDYH*}lKs72 zStTOhCmB-*(`DFx3bwr!*|U9@Y=31=w#F_cBu0CV1VRz_B?!^mWUX;2wWT%VLv&Cm zq%%GNu)*=T1NsgZmzVg~&wfV_haI0zr>zXBvL*gqHhw>ujSnR?DpaU7u!o(t_=9g-NH)$WUIR3a77c^`gP{El){8J?00$2 zYPC7ODwG&x3Dw#GpNSI)NTEz~r(4(@JI*%SY|uB?*dIl8J1;+|P=#@TsFm@7(2Mut zklM;U_-ANHv{xAFDy67IMB$D>KB}v3IqttF07xX7nTsgUYKI~Kv!-0E6`WH8i$nu8 zC$I~cf<%;PUT;>y_FKG)Aw`D0A8(-zjD%w&wYG5{JFlX;PJ&>ca~4I}b5jb0!#{Jx zK40ri{wz&)tPTp2tEsekHZZwE69FW?-+>(Xi^)l~5pH6jO9?dzI1}0_YMERE}!F{P zA)$S+e^Ro`ZY6bvXfoRKase90*0K>-Xi5h7svxVs@b-hE?JxA+J#-f~hE>1l?h*}W zs=D=B<_&*sV|L|LMcXz)y9FIQ>qQ79mR;2#@Du93XpZ&M=!yo-GsZP^U z#_6eO`8p^1R!-?3FS^Bt+lxzVHd|WuD|DTaesVVhe@fo>1GdGtGbi5^eUckiq+t9Z z4vsyYTs-9y;#k{3sP4h%qrj9Bf-r7?N+{b1aXYJDKK77X6;1z7e$W+|$6tQ=?IL=% zhDB@<1?Px96eINpRw?<;IX|j_Wl&%d@wNT)HVZAb@29a6L)7FMTYbc4fF5A$&8){a zVYBNNz)<%Agj1ybnEUsnZiU7M06I?sn5QTJwX)krfwg7)3TsUe0A5u%*%5H|q$*s5 zRhyniw16Cgwj#(|ykh2}QtI59!~^rn$7bbrAylQUO=D$ns#x~jgVAU~h%1`eci{Z6i=1)uFb7w;$F!27$;JbP}c{V&SBXA0>>ooQ9)fp@!}6RI==f zIGSo>%COq6Cd2UzslKbgOV^O^(tMbO#F&05dCP&htx@aYE5W`-9RAn=CI&zRF(-gF z)Z`+i>OGVNVga@9E3!VK#D`gRn|-*`fIScyZy->*K8|u;L;lw%0eK0JEu{l*6nosC*GC^6g zHj4buC!Y$ylN@o-n6y=U1Pgu&_&WFSVPr@WBuyq_gQ9Xm?i8g|WXY&3j_H_J9+QvP zIic%%S}c}W^vhJnx>VJrNXNj>{M`b;Kl;W0obK*_oQGOgMapAU`D)_yVb1B@)q0J` zPv6t4$L|~Ir|*&b9$j~V6b2t;{SX|b&Dl`e%ySQAet2%cLAIhPB}a-|G)L?ABZDca z_d@346Y4%&$T)A>5eY}1m};A_1|i`EAjK#p*{gIrB&Lk3#TRt?w~<=90A?|YQFuWN zCPS>PpWxOSMBW8}qGF@UaQ&pHTEu&Hm=cmGrfZgc<5Mu2C<=gGa+N0b@%kT1HbS&m zM<@_v zjc7gFM8*Ru7>%>f9OkrS7d~4?&s}Q0e^hSdLCDn3{&~IaqfkWxR=sKHN)#y*YE9r& zk#m&$3RFc+6;f>(j;)|KP~F6FjKy*dIqs2CM&B>+{s$k>-P@aVI-gh2veYUW82RrM z0FK8uG!BQZPUBWzJ-+y(x-Qwcn0_*h#%f-So|Hj>X*n49%VUGcR^ERKT zmK(7LF0J)!i4e8W!|{^y0E0J9aTjObJ@Pj|aEfcN_q@kzS%~eB+kvuIFPPU@Mb-q7 zQEcs&`h`{yJDsjwa?8-Fv1a?hXb(YIap$3jnaBI*z!u_AifD7$jlMr{gIMwrU%8fw zlzp$DnvziC@aa5RNN^v*_S67!kG>$>cyh900M?$BDy?d=PvTiDF{C(n>9@sJj{C*Z zScEDq=;&11Bn3QIB$ceC2y&K*ngm4!xyv>NqzEk@ACi%W;Mk2+MVIIHfwVb5CePYd?gxLt+SlaeJ-25=-Ymrz<=E*iwhF!(!FZLB zD3X?YKEkSr?4#kr9?To~Xj2DJ0_TL~YK^jc>us5}+ne>4a@Xa=v`L^1<&>X2lfS(J z_^1E$U(%FmlxnJA^hK>_F?9^ z=O-7|(A-nJS-A&d_snxr zO0|pK##8YbqUU^YS%M6szVs4gB`8x^i5t%$0*|NhM%Dp`A%=aO**QlY*rT?Gj>B_6 zt>jg5L|o!h`agnsx)5bNY}rLd=p6i#!NFHVISwSZA2Y<>Ez7kU#l$C38)JjaCOulonDCHOdSfj;A;b15 zm}?Vxl4>5758eS2ARtsJ9%j+nUiQj?Qvyd7l;$eNo^fhl+s2T|&2&>%#k&f<(4?K} z-vI-7{@GvB`Mm4vG;K}mj}M9HSG&G{XS3a4v%S#OVoQAk{x-hF*Jr0L_Q}cSpeb3V z3&x*4EcSCN4#q`~7N(Qla}z8jY4!Hh(Pwxj@yjo;eko@7AhXAc%%%WxCYP_K#&nC` zn^SNGtUR}EK?32KedXuQn0Ff6eAOwtjAGj+?P4&y9T>Sf&^Z7^?#)kj5R1;i1(+ua z4nRtk2R;CRI_m9D%h2*_=r(8DBV^MLIKwbXZOl|!uZG}S2Zeo4N$pSz6@YRh;3$~@ zsgbq(d9#!BMJ!7}2!5uUD-!@~r8wFi3x2sHl;$oJ4#@KvB>!_MnKfICB<(p7AQj<) zY{4d`E^2H_@5$AD10!&#+WE0OG>04E$+^%RV?V{0 z=B3`FbUAxa^-><%fF?~dSVnHA-41KKa$*~=iMXJLn^ai5hE<={+jcJd^>`bJ-P9v& zrb71b7Y@k@LgIS^;bSHd2&|mVxr3H!(+Bu+2?!Erqpo_KsES4qv1-EQu|#gyMIbWt zJr1#|SOzt6KAQHQ6k4pp09oH>=AE8>{WGPM5kzKV&)MaXu-MYD1fvHBiJ%X%y{H0e zjkxUz1ngrz=uy-SJ1FeHJrGgIvMpQMLNNojde>bpevkcfVz@B7Qm zW`pg;6_(2l`o8l8otFJ6&4h~RQBtzGZ}JSr*?vz-p~d%+i<28HG}z0EOa*!{*7MLo zsxaX^qxc9Z0X7lP*}*1hZu9)v0^D3`A@=_O^Y}~Rh%+ak7CV$;zJXG_RQJq{s;Fw- z{{UeqlmET`{XwB6_zPB+D>OPE`-IG36KBmp*krwUL&zTxj+kJAssQUry*GDgRJ^Kk z0u5AsdyYNx26%gQYjK&ALtD?L$-h5M)@D^LZKqALp{o(B(_zS z$;O05#a|x$&js-1sG3f2A-t~#d$7fcyodE(`pZi^3B(1NEvtuSqRRpl1ExU1u?JFu zuL)DI+QDuHAI$RwBUk^DJqM*UBlTj$5)nqNlf7Rg%iJ!9Zsx0S+Jzga+A1??preWf z9D=JfL=j6*?ZDauC)TOAUvRQfRWm#xFHNkIi9Q*KWukuPY^9pem(G*={Rk)Fa79uQ zbZ+d*PV%w8Ly%JE9t6qRhaO;@A}sC=X*?B~TNVuph=ZLH7{zRruBveJ=6L;%ddzA^ zs8>yJdEcD)q;T5+V;O=Ih6naAOJ~3K~ybDxC(KyHQj*Oj%fgz z;nEZoWdS5vZ;iNL?u{V!`NLq=*2}B6GxM@nag2o|b|M52EDj(V6}eO=YqiLODE#~# zqfJ_(B|;9@DGQddt}VW{O{py5s3?lq8LZ*iAg*CSY&(uUhxRp8DyoR8NC9X^q$eUO zMD)`gf&Rt6_;2|C`9J=*wAW!(OSx3l4-)g&nE97h%hh6g`B<0h4VlA9c8jIXGQFae zT|xP6h9AkCev=Q{&Ad}w4ypjm=1LKnKA2d3kSe@?c4WJT;Ku%m4u1Rw;*M;RcXwS1fQ1J>U0w;3AoGN##rza4{93O4`zNU zDzLa12Uwjc8K2yH#B$ajgxxhK zE)b`L63aZ}Yx~3|z_x^WzX3&?VA4jYhLyU13ru#lB6Sj=Qaqq!h!i_-s>pFxJ+P}- z&`a5Z5zm5>7R=!Qa8S+7PQF@uN@!jcEuaKll6>c3t0W#&mJTqsfb8=ceU!2v;On|@PGba-zQ1Pbu7zT>k}gS3Ne3ou~_8QW`oPiXS7^x4fW3% z#7i$RVPKno^Wk=4KMz)uT_$$KI%fyCJYq5Yg$tm*`>&$fTpm$6T1HSPYIDSi(lq%@ zm|7r;E>#8KWJBiUc3iApeo{67%ndn894G~-den7=76d2`TGdj$#6tUfMGL;fKx{9U zKwRTLysV2;NOMN?=j4SWcyx=aY{zSw#55E+mZ%hzzC&ihVhKnAI<_A1rLZ}|-W zsIE*b9ym$u`z&pOWy?t@&|~;igkf3&$b%&_a3EV+!JsnRpPS6ugIu-WSJl~(UZBywf+B9z zIQLh6cxO&RyX0P88mR6x=Jv~8K1MzyJ9L4MkP~Fsm!)8I6kUK$1Z8qERbq6W`KD4W zs4fj7Brqu1L1Mt{(7h#!8xx#_;>b9$n~);LteDAEc4}RuL@kaPpk~i3s|N|YNnS>$ zxLJrb>~Rs;&%vyvk7D1p{3IrbxE~@@EBEIL#@Y<^(p=#y&b*QUfLaVy)xaU(K&qJQ zZp_)|HfB!XRyCL>WXfGzM{X4zS@5j(uC0c% z=UWuDze9>(Dwd3{?GmW|^MYD!A?opX&{8K6siQ(iDz)Oa|7jIKN5_xe zrY_S`R39&(pL_E=%S$W&IXOpqrtg}f%K!D3WtY+-qKNIk z^Xq`%1E`nI6b^~j6u>R?j9`+;Cm-JbOO)JJLfc4%L1c=+;CXQfP6RLtb2~x-CYDTTrpLU%BJ%OsCUvvOF=RxXhM(L#~5N(N7S|ZvQhlz}a zTHWU$B@?McU~2DG=l6j!^ZpImu+qMY1t<=BxF2wH9rkyvmbjA>jsi_DYIqP;Z8Lk* zsnp`OU=og3uyZG{Nv*hbm_MuY`CvjJvBe_8 zaK_7*pX+cQw3a%l=x9xG`f>nB>5@Kwamkyjw|P2Y1;G`7XUzQ7#iGlr^~RSN*b48i z%MZXIXC0Bx9o_QX=#c_JGIAIKHTDQzE2_T(CLeDE^6%w(07~rQ8^8{fdDI80Zc&$X zw!Es2SkH?x;IvL(L8-DeiYse zs0wPCtY;QCpirA~G_1&jrLninTP}rvT<(!8tORF9HE0~M5{#L_g;2VRzR#9l#KuY8 zGJ}hmP6X|iiW5`36c0gC=qFT6B%~nQQcR}1h*FyXh?MsIiw5~G&}*>;ryzs`P*u=* zOA>n*!vZTc9z^R7j8x+ckXTv*0+jvNVuylP77+iw{6e$;fiQFp96?1rDJ!dY$f(LnNALc9KvV|(u zmYqmNDHT(x5UD7&nv7iMwP-4W^XX*UlBWsdIO;f@F^)s6wVp-m$Sf6j^W^~W?Qg%2 z=gsU?EuF`5G~-;AwqM@4x&!%P@iF0FsNR-ve?&P2Lz4fR)E|&NCY2u>5O1|)~-=T ziK0|NnJT7QeZge)Uf-i?176&SqZ&a-YBj{wmr#TwkWC4!s+gvNT1KlR)GH82=P9%j zDVv#PHeprHo&OtcSMqI12~#*KsfnBH@ASQRC8O{AHlgae9$9_9onoItbtR@#R6V0n zB43Gamq%QkCJKauvN7_=pRLYg(H0TawFqQD8pyF304)<+)zvmR!G5e_fy6W#k@eu* z2>&x)Q3dMWrbK?-4V*L&g5*97VsptB2+VxMEz@GGraD(eYTS>Y+r~23pdQYRjQx4T2X4T zK%jQ!#&N{)aG>+~RHv!ztJHfHI*uI|;IHfj7>1EbeU`W$mx8hd;~gS>nse6GYK5-L z6nua7aawcuMJ)6%mQoxFChPw!fL1^Zm4)}iVLcU8TDz1$vyk!sCV~&G?d8Spx>iQR z$S!6#<>fvMgp@F@Aet+Lu-AsOtSZ`m`KeUYsiH`Q6fgISwFH*sJ)_GEu+(WnsRgBs z7{?KHDkydI7h~(4YPD5W(==GXarc|R0N5?jQZg&*MZZMX_vm}ysn9LZceaQk=N_rc z=sH`hDvlKhDP&TA9+nNX_DwV-K8eZ7kW5axG02gVO;!M)OlCsqH{LR`<25xf7Lj(C z?aFccd(>MB(PEg2#(VM-n4OFipcQ;->8*4m)R+>~YNOfO#%wcKT8s7YMzxi8IRSQV z12ENM{knQTPBU<8z_DRX;^)d?C4N$nBA*b;4+}kE_|H|##&KIK~X=a)v~^|2?eL)&aBWpFdUCZeEP|c^?W)F zs`4WhxfbO;kLk++pzjy->h){Byt+!qU0I6OD**4LoZnk67Q9+*(Di-uHJkmI#u2n5&BD1YFp3S<+IgzsQ; z_<{A9@T^cpP>Wc`F9p-2m`d@o-2;Hn)Mcvf*H%!d?9=T*aL-6F^IfXv5KVBGG83ONbM}N974|Fs7^|A zHl+9QEty2#Aqzs{-&S(^wt`y~stwEp8m*aQwpaW&u0w75ySa}VHk8OBWV>((}3gQ7Q=au zQVLCFf>bG@Qq(_?#YO@V_c>>_N-JY~@dVq8XWDl?7OOR_mK&^AYpmC6tTtOL7i+B6 zYt(*)z89eHA#DDEniCQT<2bd(TC_u`F0)@mqM*9YeRx}6me`S|Sy*LMZ1z%I9bT38 z`+`(s;AjwB{A3FttVj?S!OWM0ecU*|fAY55z&MV6+Rux+4SCqD*=Ip(Ga*iTYK~0a z@54AHF@Uj|x_$kqWYj6g)tQDMKubYNj54{%DmfBK4oW%!O`(v8Z(i3z0b*^fX*<6T zSZEC#fKW+Mi=Ee6#FCMt{|_&|RDqNNfvrJ5?(Z!)cw#yn_W0x{AJOr!uj4rGtH>LL zT&E<5l+uKU6~MQ?^&ypV&o?(|Nm{oAJY%9KxyyOETw>Anl(U%|vTyG(2VeWfjl$}H z715W#B5%w5DZulJG1o+_Jhusg_il|t9XI`fR(&of96*6rAn`YaT+|=DPXnMXuOy0M z167QAIZakpNmUG!&3(mDRsblqV45bBX|jucI^cLZ;(R*dbUfe`9`nOl&*we8z4-#e zaG+_LRHRNSTB~6IDk3UI6!;jxCw^-Lrq5Xb^c@gBNSrjKO!s&9eb+rtx$AIo`2>%y z-ot9O(e-*wo6RNGn@eoQ3#?ZgtT!8UQnAcEkP{{%h)xLSU3tJw2x5v&5fqdvij#O| zgGvR5t8rBWS)=UbU@1_m_-qOingeQ=)5KT4|G=Y2Gzyu5rtN3KtASAU5dpDSM5y`* z)B0#>0H`k!kuVIUC_q~A5$??48;}A6fiWjlXN!3W?LnMX4Qe{g6^37U=I|0g0|H~} z6*!T7Hfat{2mzVE{R^BuD;5;B_{?AI!k4NTi+GDVS%Pwezet5lQ>#eNrxQx87^eY; z!(Q+2?rAdT+5<^4iOPvq0k3 zs|F_=UM!ly$2|JWP75}DLYQ(WVu(u)jK(NNL2c6uAu^UnNaaQzVqyqe*2NRRB{dh~=5J8m%}ezGIm%4I_r}jN@UC!*Peheuw@3 z4yV&WPlp}7y?udUIMFzcI*!9o%A_K?C+J72xK`+(qJydq0QREtmcaKQoFf1Lx`xu9 zfb^{K(D_BL1W9DXE``mf#e=Q_+&RlHdGL)A-!pf0iLm$~ zCMgDL!phv_8hWWQ^R~ELrI`B>)FLtnpdcL)h%%}agaNEd0hDd`0ugM7;B5OI(rr^I zw%BD=N?>oUDKP7J@6SC`D=ecYW>f^?Pbg|zl%kUC{NmR-nV`+7%;aF9)TlO0PM%!I zBsx1hl{&RSTR)Qi`wgFwQrkH#wcxP7v&>&7NEPf4dwlxI$8iInEYpI`!>I)LOou=-1IOHz{0J^*T8ed+1l#i1vNpQ))I|Te{-}U+8@q1Vi+()p8rUOEs%ihXLWEqLp++lZALmeV z>ZTDtEW_JD(CQga)qx*E9Ve6uOr@X{(}>IDNWNDjwN?Y{%7kGYa5^1uJRY#$?{L`N z;qBYkdVBLZ9S=J?pH4cAr=d(mtJHf{{JDx;EA$qMR|?z_Z~}BxU<7ai=mg*lU}ynA zX{pc^1Y04w1n3fkA5xWfOR1N`FnsrXI(=~L`_=Jq$anX*^yumd-g)QiI*unA#xsWD zjLqf(vfW~_SOKm2${;Epd>wT1ib-KU`ltz#Dk=<1U?0~J+w&l=_(bYl&=DGU-C6mj zU{mY6%?y^?S!k6U#8rgLup_@ zw2;;UK;g<$hPKLu5lcDX$tS4M#C{gN)r3>Yt#Sz`lsPa0P9;|H9SX+86rYrY0Icyx z)W&_r`8WPaor+)hf@v%$lYz-)Dh3AD3Dacw(&=qjvh`w>wB1(o&fL&~x{o6<}&4do`*CH_)55qy780`>Q zUoX~ES(#L=zZauhhFeQjzx?BX^ZA6sVTZ$EkKO(b``sPx-oDZ6*PqhiaIeE~9!n`Z z5&1I}xfR7Vfaid|0q_>U!7e|Q=sF1`Fsu3sP$BczsUWRT8bEqf`?Ebnf1-p7QoRE7 z6IJ;@q<(iA$FB{;us)xU`FuLl`2L4_8qPEhXH2EoA>LH1S6fhTL6_=Gl(DJJ0Hg(& z6AA=fZ-$ap6p0N6pZ6UAT1={qR-d6&a{@!b!|qB|QOFjLGy#%dirB1VBp&BQcrZyR zKd9L390{p?O_2TPiOyf2VU8N>tU_ z)mo~LP>o`1$>%`@LO}2qIKE8+inuo-J83P>o9hm0l@<(|)f@$m;Q*l3nbv-&Vl35L z69fBd5d)>i;^3c+`f3p)tBIi038Rml4@U!{$McB$yF0zUenrRAajdoefvS88q!+pC z-ZFKkSFbJ#@P$0A0O;AXQ`+rn&nz1Su88?b*LAd9uA19VT=?0?KFB@s!hl%sZB8jJ z`30N_R4~j)Y@a!>1UcPBnR~OnM2CFKl^`@Z+L=iWmv@d0NuJ`%?I5A2!vd%a? zB=$IgN9{$GX@aO?7_5vw9QHWu?{R_uzJ2=z-Msl6`~9t+PUoT4axWtPUPb>> zf#*d0R)Hg-3A=hgHwrAEx&mQK;K$6kAn&0J`)_!uimE_a6(}ltR_GmoJE$H3oBTjwyA=wtkuT@D-w&*oVq|Fif++RjY{5{!!EuEKQZtGXgnLa`bFbBgL?3)Qkl# zP9tt^ul2J}Kc@ZufZ=?;7tucl(Wj(%B^8I&Mn|F_RG)^o3V=!}NpoF@V#|srAbhXy z7in|x)JP{jOBeJ8?#1bWwh>Y@o}8wz$8S{WV$pZM@L67k;yG?reBa@hMi08LBJ z?0u*B@=LK%U|6!N6r3olXshGwGV-yh|^BL8590v@;35Wd-_xEqHyML=U*DvVy z_7&~!ud(0XX_?A(spb13`U!wP2I!gzI|WrhmrB@@;t_zyM7$-U6@V2HJq7ShX1Yu% z5pg1Bre;oIeV>}}NTilZfIe5?Cn9FKTDDWDf0~EVj>-9JcUsus@mReV; zaI^!SH?1_rY7J0A22h!R$qyw6qi<+x+zeF+$0~0?#5OjY`i~z>YV1iXg~Zn@0AhdD zmH{mrhgiob*DOx-brFw9wPfI6ACb8iCpMb0c*a&(77a?RdT6o)5bxB%WRDSn4={7< zxx@8fUczD*UP@`wWC7cN+gn@l_9Iw~;uVXHG~HcD=ipi-aIJ{6UX^$NP}nqLnrsR+ zO$Aeoq^lFsN_EkX&H1NNFpU!qyIYLofWzT{S_^jfclhkHAJZ3Ke4)c|-l^*Esp=;H zeb#l0TjqRx^X9UMywJEH4_bhllB9)F-EvBoiPKfzFKDq?YfhO`V&CQGP-=Vi@!y`+ z!!gRVrrzI}G7C^&VQpZcK*0}o0>Xhixa!QtC!QsArfjBcj>96pc{b1JK6xg9z{)DM zpfZe;%}`ynCjP>WJYkprbUawEzk7?jySIAt=5xA!^BL~%Z}faVjiuCEsr7di`sV<> z1h9j7gItj6V-nNvzBEjjU}ZJ5o-xyN$3LD#PvH(?wO7|we` zey0(&l*hH!4{NPstyM+kIaL2r6`uk40?-3MKT_bj)cS4FVGxz?L0_(-qar3s?1rpt zZm~oiNGebXg$t+>(!?lg2_B2a|gH`%z!Jtbz=T}p)Fx(IP< zHvmYr0HjiFdo$F@t5(eeWj+pHJg7mOm8H-zVaah|(*iwA*aE zahhJ##p0`aef=C?ssKcmS#(?yXv55#oHO-(Pbsl;&^t#}6N2Pp#gWl#6{!V+$mc;g z5g{mL}H6DGzdPR_uQ{Q#y78~^4q7hNbG+;a*F_sBsn)GkE-|)6gL1~DNsaYn5IciIR4`Go0m%h5vQy=n_~PnHaSihE%k80#dup zYwrUz?`sW8LrHY9#(v z0f^L`Se6RfGV`kII@|D@JJaiXMz%Z5t^Wt2G@r|Tb;K?rtVo5jq4tR%@TlWe>HLxd zUr@sAJ?{_Sgdp?iFit?sq5*Q5u3-pceZZXZxoUMPaVbXX8HY0trvvu)ci8Q$-@mze zN!M?l5nUv^!7()SB27AtHo9_wxuC z9nPnxQ>kl#E>-o11bRilt*VNM$W*3;)A{FbuD@7xT}OS_qw5#GwaEhj7(9{VCxTQJ zb*LC=Kz1JoqxwGB5y(NlQP2L|ggYt4yN~i4?JR^N@yzslcr(@i{A}0}-(TPbRVz~N zAu=I$0!Dfb_MM#qX~n5Ns8&dtN!JW0ih+>U4tU$5@wH~EG5exf0wCQOJY8+A1R#jCdCD zZ^ffI8t0p;2LW}EPhF1b57fFe2cua%qE?j2h!v$8qN*;PthHhs2Aq7vx7*!gcXy+= zH!tb_{)TREUgB`L*J&y*rPlu-Dt`vxts1KNh=BKq_(M+oy`0iT*Y!^q%O%$9tzKL_ zq4nkxn~Nv3T==5YzBAv%E~CpGx_;psVsi_mvx=`^oF19rPxy1aaf zySK0P_U4OpIPAYSm8q91Yk~fVK+j2Or=p)$$Zx9-Jv0B@^_!Pzd-;wo7fV|73v}6R z+%hLrCu{}3oEYGdG4g0_LT}vnAb8E{#RsOKF4tDEcW!~)-Ez{G%ibvYrON!7RlkJu zC}4XBvKyMhv?4_&n;@wHXu;`^^PsBEOhx-|s+MX3h7t##ZEKNPotyPGH9RUbXyj61 zPBxpLvyy$2B1S%&#ZZ$%BkvFiy@qbpWuqsmC-9 z*x%n^7|uAI4k)Eyx4*+@pZx@{U%kNjJREARzo($zAfg|olwWY_?#7{;`rUPvu(bXu z0f4lVUW;ys0NFq$zv!_7Ur#yna&hq`U}*MNqsFw#?#>gxj);oNVaYyc2ADiD<=m

<@d~-`(Q= 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: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 B&W->8 literal 0 HcmV?d00001 diff --git a/syncplay-server.desktop b/syncplay-server.desktop new file mode 100644 index 0000000..6a7986e --- /dev/null +++ b/syncplay-server.desktop @@ -0,0 +1,9 @@ +[Desktop Entry] +Encoding=UTF-8 +Name=Syncplay server +Comment=Synchronize video playback over network +Exec=/usr/bin/syncplay-server %u +Terminal=true +Type=Application +Icon=/usr/share/icons/icon.ico +Categories=AudioVideo;Audio;Video; diff --git a/syncplay.desktop b/syncplay.desktop new file mode 100644 index 0000000..37e9667 --- /dev/null +++ b/syncplay.desktop @@ -0,0 +1,11 @@ +[Desktop Entry] +Encoding=UTF-8 +Name=Syncplay +Comment=Synchronize video playback over network +Exec=/usr/bin/syncplay %u +Terminal=true +Type=Application +Icon=/usr/share/icons/icon.ico +Categories=AudioVideo;Audio;Video;Player;Network; +MimeType=audio/ac3;audio/mp4;audio/mpeg;audio/vnd.rn-realaudio;audio/vorbis;audio/x-adpcm;audio/x-matroska;audio/x-mp2;audio/x-mp3;audio/x-ms-wma;audio/x-vorbis;audio/x-wav;audio/mpegurl;audio/x-mpegurl;audio/x-pn-realaudio;audio/x-scpls;video/avi;video/mp4;video/flv;video/mpeg;video/quicktime;video/vnd.rn-realvideo;video/x-matroska;video/x-ms-asf;video/x-msvideo;video/x-ms-wmv;video/x-ogm;video/x-theora; +NoDisplay=true diff --git a/syncplay/__init__.py b/syncplay/__init__.py new file mode 100644 index 0000000..4393e9a --- /dev/null +++ b/syncplay/__init__.py @@ -0,0 +1,3 @@ +version = '1.0.1' +milestone = 'Tequila' +projectURL = 'http://uriziel.github.com/syncplay/' \ No newline at end of file diff --git a/syncplay/client.py b/syncplay/client.py new file mode 100644 index 0000000..de400c7 --- /dev/null +++ b/syncplay/client.py @@ -0,0 +1,451 @@ +#coding:utf8 +import hashlib +import os.path +import time +from twisted.internet.protocol import ClientFactory +from twisted.internet import reactor, task +from syncplay.protocols import SyncClientProtocol + +class SyncClientFactory(ClientFactory): + def __init__(self, client, retry = 10): + self._client = client + self.retry = retry + self._timesTried = 0 + self.reconnecting = False + + def buildProtocol(self, addr): + return SyncClientProtocol(self._client) + + def startedConnecting(self, connector): + destination = connector.getDestination() + self._client.ui.showMessage('Connecting to {}:{}'.format(destination.host, destination.port)) + + def clientConnectionLost(self, connector, reason): + if self._timesTried < self.retry: + self._timesTried += 1 + message = 'Connection lost, reconnecting' + self._client.ui.showMessage(message) + self.reconnecting = True + reactor.callLater(0.1*(2**self._timesTried), connector.connect) + else: + message = 'Disconnected' + self._client.ui.showMessage(message) + + def clientConnectionFailed(self, connector, reason): + if not self.reconnecting: + message = 'Connection failed' + self._client.ui.showMessage(message) + self._client.stop() + else: + self.clientConnectionLost(connector, reason) + + def resetRetrying(self): + self._timesTried = 0 + + def stopRetrying(self): + self._timesTried = self.retry + +class SyncplayClient(object): + def __init__(self, playerClass, ui, args): + self.protocolFactory = SyncClientFactory(self) + self.ui = UiManager(self, ui) + self.userlist = SyncplayUserlist(self.ui) + if(args.room == None or args.room == ''): + room = 'default' + self.defaultRoom = room + self.playerPositionBeforeLastSeek = 0.0 + self.setUsername(args.name) + self.setRoom(room) + if(args.password): + args.password = hashlib.md5(args.password).hexdigest() + self._serverPassword = args.password + self._protocol = None + self._player = None + self._playerClass = playerClass + self._startupArgs = args + + self._running = False + self._askPlayerTimer = None + + self._lastPlayerUpdate = None + self._playerPosition = 0.0 + self._playerPaused = True + + self._lastGlobalUpdate = None + self._globalPosition = 0.0 + self._globalPaused = 0.0 + self._speedChanged = False + + def initProtocol(self, protocol): + self._protocol = protocol + + def destroyProtocol(self): + if(self._protocol): + self._protocol.drop() + self._protocol = None + + def initPlayer(self, player): + self._player = player + self.scheduleAskPlayer() + + def scheduleAskPlayer(self, when=0.1): + self._askPlayerTimer = task.LoopingCall(self.askPlayer) + self._askPlayerTimer.start(when) + + def askPlayer(self): + if(not self._running): + return + if(self._player): + self._player.askForStatus() + + def _determinePlayerStateChange(self, paused, position): + pauseChange = self.getPlayerPaused() != paused and self.getGlobalPaused() != paused + _playerDiff = abs(self.getPlayerPosition() - position) + _globalDiff = abs(self.getGlobalPosition() - position) + seeked = _playerDiff > 1 and _globalDiff > 1 + return pauseChange, seeked + + def updatePlayerStatus(self, paused, position): + pauseChange, seeked = self._determinePlayerStateChange(paused, position) + self._playerPosition = position + self._playerPaused = paused + if(self._lastGlobalUpdate): + self._lastPlayerUpdate = time.time() + if(pauseChange or seeked): + self.playerPositionBeforeLastSeek = self.getGlobalPosition() + if(self._protocol): + self._protocol.sendState(self.getPlayerPosition(), self.getPlayerPaused(), seeked, None, True) + + def getLocalState(self): + paused = self.getPlayerPaused() + position = self.getPlayerPosition() + pauseChange, seeked = self._determinePlayerStateChange(paused, position) + if(self._lastGlobalUpdate): + return position, paused, seeked, pauseChange + else: + return None, None, None, None + + def _initPlayerState(self, position, paused): + self._player.setPosition(position) + self._player.setPaused(paused) + madeChangeOnPlayer = True + return madeChangeOnPlayer + + def _rewindPlayerDueToTimeDifference(self, position, setBy): + self._player.setPosition(position) + message = "Rewinded due to time difference with <{}>".format(setBy) + self.ui.showMessage(message) + madeChangeOnPlayer = True + return madeChangeOnPlayer + + def _serverUnpaused(self, setBy): + self._player.setPaused(False) + madeChangeOnPlayer = True + message = '<{}> unpaused'.format(setBy) + self.ui.showMessage(message) + return madeChangeOnPlayer + + def _serverPaused(self, setBy, diff): + if (diff > 0): + self._player.setPosition(self.getGlobalPosition()) + self._player.setPaused(True) + madeChangeOnPlayer = True + message = '<{}> paused'.format(setBy) + self.ui.showMessage(message) + return madeChangeOnPlayer + + def _serverSeeked(self, position, setBy): + if(self.getUsername() <> setBy): + self.playerPositionBeforeLastSeek = self.getPlayerPosition() + self._player.setPosition(position) + madeChangeOnPlayer = True + else: + madeChangeOnPlayer = False + message = '<{}> jumped from {} to {}'.format(setBy, self.ui.formatTime(self.playerPositionBeforeLastSeek), self.ui.formatTime(position)) + self.ui.showMessage(message) + return madeChangeOnPlayer + + def _slowDownToCoverTimeDifference(self, diff): + if(0.4 < diff < 4): + self._player.setSpeed(0.95) + self._speedChanged = True + elif(self._speedChanged): + self._player.setSpeed(1.00) + self._speedChanged = False + madeChangeOnPlayer = True + return madeChangeOnPlayer + + def _changePlayerStateAccordingToGlobalState(self, position, paused, doSeek, setBy): + madeChangeOnPlayer = False + pauseChanged = paused != self.getGlobalPaused() + diff = self.getPlayerPosition() - position + if(self._lastGlobalUpdate is None): + madeChangeOnPlayer = self._initPlayerState(position, paused) + self._globalPaused = paused + self._globalPosition = position + self._lastGlobalUpdate = time.time() + if (doSeek): + madeChangeOnPlayer = self._serverSeeked(position, setBy) + if (diff > 4 and not doSeek): + madeChangeOnPlayer = self._rewindPlayerDueToTimeDifference(position, setBy) + if (self._player.speedSupported and not doSeek and not paused): + madeChangeOnPlayer = self._slowDownToCoverTimeDifference(diff) + if (paused == False and pauseChanged): + madeChangeOnPlayer = self._serverUnpaused(setBy) + elif (paused == True and pauseChanged): + madeChangeOnPlayer = self._serverPaused(setBy, diff) + return madeChangeOnPlayer + + def updateGlobalState(self, position, paused, doSeek, setBy, latency): + madeChangeOnPlayer = False + if(not paused): + position += latency + if(self._player): + madeChangeOnPlayer = self._changePlayerStateAccordingToGlobalState(position, paused, doSeek, setBy) + if(madeChangeOnPlayer): + self.askPlayer() + + def getPlayerPosition(self): + if(not self._lastPlayerUpdate): + if(self._lastGlobalUpdate): + return self.getGlobalPosition() + else: + return 0.0 + position = self._playerPosition + if(not self._playerPaused): + position += time.time() - self._lastPlayerUpdate + return position + + def getPlayerPaused(self): + if(not self._lastPlayerUpdate): + if(self._lastGlobalUpdate): + return self.getGlobalPaused() + else: + return True + return self._playerPaused + + def getGlobalPosition(self): + if not self._lastGlobalUpdate: + return 0.0 + position = self._globalPosition + if not self._globalPaused: + position += time.time() - self._lastGlobalUpdate + return position + + def getGlobalPaused(self): + if(not self._lastGlobalUpdate): + return True + return self._globalPaused + + def updateFile(self, filename, duration, path): + size = os.path.getsize(path) + self.userlist.currentUser.setFile(filename, duration, size) + self.sendFile() + + def sendFile(self): + file_ = self.userlist.currentUser.file + if(self._protocol and self._protocol.logged and file_): + self._protocol.sendFileSetting(file_) + + def setUsername(self, username): + self.userlist.currentUser.username = username + + def getUsername(self): + return self.userlist.currentUser.username + + def setRoom(self, roomName): + self.userlist.currentUser.room = roomName + + def getRoom(self): + return self.userlist.currentUser.room + + def getPassword(self): + return self._serverPassword + + def setPosition(self, position): + self._player.setPosition(position) + + def setPaused(self, paused): + self._player.setPaused(paused) + + def start(self, host, port): + if self._running: + return + if self._playerClass: + self._playerClass.run(self, self._startupArgs.player_path, self._startupArgs.file, self._startupArgs._args) + self._playerClass = None + self.protocolFactory = SyncClientFactory(self) + reactor.connectTCP(host, port, self.protocolFactory) + self._running = True + reactor.run() + + def stop(self, promptForAction = True): + if not self._running: + return + self._running = False + if self.protocolFactory: + self.protocolFactory.stopRetrying() + self.destroyProtocol() + if self._player: + self._player.drop() + reactor.callLater(0.1, reactor.stop) + if(promptForAction): + self.ui.promptFor("Press enter to exit\n") + +class SyncplayUser(object): + def __init__(self, username = None, room = None, file_ = None): + self.username = username + self.room = room + self.file = file_ + + def setFile(self, filename, duration, size): + file_ = { + "name": filename, + "duration": duration, + "size":size + } + self.file = file_ + + def isFileSame(self, file_): + if(not self.file): + return False + sameName = self.file['name'] == file_['name'] + sameSize = self.file['size'] == file_['size'] + sameDuration = self.file['duration'] == file_['duration'] + return sameName and sameSize and sameDuration + + def __lt__(self, other): + return self.username < other.username + +class SyncplayUserlist(object): + def __init__(self, ui): + self.currentUser = SyncplayUser() + self._users = {} + self.ui = ui + + def __showUserChangeMessage(self, username, room, file_): + if (room and not file_): + message = "<{}> has joined the room: '{}'".format(username, room) + self.ui.showMessage(message) + elif (room and file_): + duration = self.ui.formatTime(file_['duration']) + message = "<{}> is playing '{}' ({})".format(username, file_['name'], duration) + if(self.currentUser.room <> room): + message += " in room: '{}'".format(room) + self.ui.showMessage(message) + if(self.currentUser.file and not self.currentUser.isFileSame(file_)): + message = "File you are playing appears to be different from <{}>'s".format(username) + self.ui.showMessage(message) + + def addUser(self, username, room, file_, noMessage = False): + if(username == self.currentUser.username): + return + user = SyncplayUser(username, room, file_) + self._users[username] = user + if(not noMessage): + self.__showUserChangeMessage(username, room, file_) + + def removeUser(self, username): + if(self._users.has_key(username)): + self._users.pop(username) + message = "<{}> has left".format(username) + self.ui.showMessage(message) + + def __displayModUserMessage(self, username, room, file_, user): + if (file_ and not user.isFileSame(file_)): + self.__showUserChangeMessage(username, room, file_) + elif (room and room != user.room): + self.__showUserChangeMessage(username, room, None) + + def modUser(self, username, room, file_, noMessage = False): + if(self._users.has_key(username)): + user = self._users[username] + if(not noMessage): + self.__displayModUserMessage(username, room, file_, user) + else: + self.addUser(username, room, file_) + + def __addUserWithFileToList(self, rooms, user): + file_key = '\'{}\' ({})'.format(user.file['name'], self.ui.formatTime(user.file['duration'])) + if (not rooms[user.room].has_key(file_key)): + rooms[user.room][file_key] = {} + rooms[user.room][file_key][user.username] = user + + def __addUserWithoutFileToList(self, rooms, user): + if (not rooms[user.room].has_key("__noFile__")): + rooms[user.room]["__noFile__"] = {} + rooms[user.room]["__noFile__"][user.username] = user + + def __createListOfPeople(self, rooms): + for user in self._users.itervalues(): + if (not rooms.has_key(user.room)): + rooms[user.room] = {} + if(user.file): + self.__addUserWithFileToList(rooms, user) + else: + self.__addUserWithoutFileToList(rooms, user) + return rooms + + def __addDifferentFileMessageIfNecessary(self, user, message): + if(self.currentUser.file): + fileHasSameSizeAsYour = user.file['size'] != self.currentUser.file['size'] + differentFileMessage = " (but their file size is different from yours!)" + message += differentFileMessage if not fileHasSameSizeAsYour else "" + return message + + def __displayFileWatchersInRoomList(self, key, users): + self.ui.showMessage("\t\tFile: {} is being played by:".format(key), True, True) + for user in sorted(users.itervalues()): + message = user.username + message = self.__addDifferentFileMessageIfNecessary(user, message) + self.ui.showMessage("\t\t\t<" + message + ">", True, True) + + def __displayPeopleInRoomWithNoFile(self, noFileList): + if (noFileList): + self.ui.showMessage("\t\tPeople who are not playing any file:", True, True) + for user in sorted(noFileList.itervalues()): + self.ui.showMessage("\t\t\t<" + user.username + ">", True, True) + + def __displayListOfPeople(self, rooms): + for roomName in sorted(rooms.iterkeys()): + self.ui.showMessage("In room '{}':".format(roomName), True, False) + noFileList = rooms[roomName].pop("__noFile__") if (rooms[roomName].has_key("__noFile__")) else None + for key in sorted(rooms[roomName].iterkeys()): + self.__displayFileWatchersInRoomList(key, rooms[roomName][key]) + self.__displayPeopleInRoomWithNoFile(noFileList) + + def showUserList(self): + rooms = {} + self.__createListOfPeople(rooms) + self.__displayListOfPeople(rooms) + +class UiManager(object): + def __init__(self, client, ui): + self._client = client + self.__ui = ui + + def showMessage(self, message, noPlayer = False, noTimestamp = False): + if(self._client._player and not noPlayer): self._client._player.displayMessage(message) + self.__ui.showMessage(message, noTimestamp) + + def showErrorMessage(self, message): + self.__ui.showErrorMessage(message) + + def promptFor(self, prompt): + return self.__ui.promptFor(prompt) + + def formatTime(self, value): + weeks = value // 604800 + days = (value % 604800) // 86400 + hours = (value % 86400) // 3600 + minutes = (value % 3600) // 60 + seconds = value % 60 + if(weeks > 0): + return '{0:.0f}w, {1:.0f}d, {2:02.0f}:{3:02.0f}:{4:02.0f}'.format(weeks, days, hours, minutes, seconds) + elif(days > 0): + return '{0:.0f}d, {1:02.0f}:{2:02.0f}:{3:02.0f}'.format(days, hours, minutes, seconds) + elif(hours > 0): + return '{0:02.0f}:{1:02.0f}:{2:02.0f}'.format(hours, minutes, seconds) + else: + return '{0:02.0f}:{1:02.0f}'.format(minutes, seconds) + diff --git a/syncplay/clientManager.py b/syncplay/clientManager.py new file mode 100644 index 0000000..b08f903 --- /dev/null +++ b/syncplay/clientManager.py @@ -0,0 +1,61 @@ +from syncplay.client import SyncplayClient +import sys +from syncplay.ui.ConfigurationGetter import ConfigurationGetter, InvalidConfigValue +from syncplay import ui +try: + from syncplay.players.mpc import MPCHCAPIPlayer +except ImportError: + MPCHCAPIPlayer = None +from syncplay.players.mplayer import MplayerPlayer +try: + from syncplay.ui.GuiConfiguration import GuiConfiguration +except ImportError: + GuiConfiguration = None + +class SyncplayClientManager(object): + def __init__(self): + self._prepareArguments() + self.interface = ui.getUi(graphical = not self.args.no_gui) + self._checkAndSaveConfiguration() + syncplayClient = None + if(self.argsGetter.playerType == "mpc"): + syncplayClient = SyncplayClient(MPCHCAPIPlayer, self.interface, self.args) + elif(self.argsGetter.playerType == "mplayer"): + syncplayClient = SyncplayClient(MplayerPlayer, self.interface, self.args) + if(syncplayClient): + self.interface.addClient(syncplayClient) + syncplayClient.start(self.args.host, self.args.port) + else: + self.interface.showErrorMessage("Unable to start client") + + def _checkAndSaveConfiguration(self): + try: + self._promptForMissingArguments() + self.argsGetter.saveValuesIntoConfigFile() + except InvalidConfigValue: + self._checkAndSaveConfiguration() + except Exception, e: + print e.message + sys.exit() + + def _prepareArguments(self): + self.argsGetter = ConfigurationGetter() + self.args = self.argsGetter.getConfiguration() + + def _guiPromptForMissingArguments(self): + if(GuiConfiguration): + self.args = GuiConfiguration(self.args, self.args.force_gui_prompt).getProcessedConfiguration() + + def _promptForMissingArguments(self): + if(self.args.no_gui): + if (self.args.host == None): + self.args.host = self.interface.promptFor(prompt = "Hostname: ", message = "You must supply hostname on the first run, it's easier through command line arguments.") + if (self.args.name == None): + self.args.name = self.interface.promptFor(prompt = "Username: ", message = "You must supply username on the first run, it's easier through command line arguments.") + if (self.args.player_path == None): + self.args.player_path = self.interface.promptFor(prompt = "Player executable: ", message = "You must supply path to your player on the first run, it's easier through command line arguments.") + else: + self._guiPromptForMissingArguments() + + + diff --git a/syncplay/players/__init__.py b/syncplay/players/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/syncplay/players/basePlayer.py b/syncplay/players/basePlayer.py new file mode 100644 index 0000000..540ee6d --- /dev/null +++ b/syncplay/players/basePlayer.py @@ -0,0 +1,22 @@ +class BasePlayer(object): + def askForStatus(self): + raise NotImplementedError() + + def displayMessage(self): + raise NotImplementedError() + + def drop(self): + raise NotImplementedError() + + @staticmethod + def run(client, playerPath, filePath, args): + raise NotImplementedError() + + def setPaused(self): + raise NotImplementedError() + + def setPosition(self): + raise NotImplementedError() + + def setSpeed(self): + raise NotImplementedError() diff --git a/syncplay/players/mpc.py b/syncplay/players/mpc.py new file mode 100644 index 0000000..ad6ac00 --- /dev/null +++ b/syncplay/players/mpc.py @@ -0,0 +1,439 @@ +#coding:utf8 +import time +import threading +import thread +import win32con, win32api, win32gui, ctypes, ctypes.wintypes #@UnresolvedImport @UnusedImport +from functools import wraps +from syncplay.players.basePlayer import BasePlayer + +class MPCHCAPIPlayer(BasePlayer): + speedSupported = False + + def __init__(self, client): + self.__client = client + self._mpcApi = MpcHcApi() + self._mpcApi.callbacks.onUpdateFilename = lambda _: self.__makePing() + self._mpcApi.callbacks.onMpcClosed = lambda _: self.__client.stop(False) + self._mpcApi.callbacks.onFileStateChange = lambda _: self.__lockAsking() + self._mpcApi.callbacks.onUpdatePlaystate = lambda _: self.__unlockAsking() + self._mpcApi.callbacks.onGetCurrentPosition = lambda _: self.__onGetPosition() + self._mpcApi.callbacks.onVersion = lambda _: self.__versionUpdate.set() + self._mpcApi.callbacks.onSeek = None + self._mpcApi.callbacks.onUpdatePath = None + self._mpcApi.callbacks.onUpdateFileDuration = None + self.__switchPauseCalls = False + self.__preventAsking = threading.Event() + self.__positionUpdate = threading.Event() + self.__versionUpdate = threading.Event() + self.__fileUpdate = threading.RLock() + self.__versionUpdate.clear() + + def drop(self): + self.__preventAsking.set() + self.__positionUpdate.set() + self.__versionUpdate.set() + self._mpcApi.sendRawCommand(MpcHcApi.CMD_CLOSEAPP, "") + + @staticmethod + def run(client, playerPath, filePath, args): + mpc = MPCHCAPIPlayer(client) + mpc._mpcApi.callbacks.onConnected = lambda: mpc.initPlayer(filePath if(filePath) else None) + mpc._mpcApi.startMpc(playerPath, args) + return mpc + + def __lockAsking(self): + self.__preventAsking.clear() + + def __unlockAsking(self): + self.__preventAsking.set() + + def __onGetPosition(self): + self.__positionUpdate.set() + + def setSpeed(self, value): + pass + + def __dropIfNotSufficientVersion(self): + self._mpcApi.askForVersion() + if(not self.__versionUpdate.wait(0.1)): + self.__mpcError("MPC version not sufficient, please use `mpc-hc` >= `1.6.4`") + + def __testMpcReady(self): + if(not self.__preventAsking.wait(10)): + raise Exception("Player failed opening file") + + def __makePing(self): + try: + self.__testMpcReady() + self._mpcApi.callbacks.onUpdateFilename = lambda _: self.__handleUpdatedFilename() + self.__client.initPlayer(self) + self.__handleUpdatedFilename() + self.askForStatus() + except Exception, err: + self.__client.ui.showErrorMessage(err.message) + self.__client.stop() + + def initPlayer(self, filePath): + self.__dropIfNotSufficientVersion() + self.__mpcVersion = self._mpcApi.version.split('.') + if(self.__mpcVersion[0:3] == ['1', '6', '4']): + self.__switchPauseCalls = True + if(filePath): + self._mpcApi.openFile(filePath) + + def displayMessage(self, message): + self._mpcApi.sendOsd(message, 2, 3000) + + def setPaused(self, value): + try: + if self.__switchPauseCalls: + value = not value + if value: + self._mpcApi.pause() + else: + self._mpcApi.unpause() + except MpcHcApi.PlayerNotReadyException: + self.setPaused(value) + + def setPosition(self, value): + try: + self._mpcApi.seek(value) + except MpcHcApi.PlayerNotReadyException: + self.setPosition(value) + + def __getPosition(self): + self.__positionUpdate.clear() + self._mpcApi.askForCurrentPosition() + self.__positionUpdate.wait() + return self._mpcApi.lastFilePosition + + def askForStatus(self): + try: + if(self.__preventAsking.wait(0)): + position = self.__getPosition() + paused = self._mpcApi.isPaused() + position = float(position) + if(self.__fileUpdate.acquire(0)): + self.__client.updatePlayerStatus(paused, position) + self.__fileUpdate.release() + return + self.__echoGlobalStatus() + except MpcHcApi.PlayerNotReadyException: + self.askForStatus() + + def __echoGlobalStatus(self): + self.__client.updatePlayerStatus(self.__client.getGlobalPaused(), self.__client.getGlobalPosition()) + + def __forcePause(self, paused): + i = 0 + while(paused <> self._mpcApi.isPaused() and i < 15): + i+=1 + self.setPaused(paused) + time.sleep(0.1) + time.sleep(0.1) + if(paused <> self._mpcApi.isPaused()): + self.__forcePause(paused) + + def __setUpStateForNewlyOpenedFile(self): + try: + self.__forcePause(self.__client.getGlobalPaused()) + self._mpcApi.seek(self.__client.getGlobalPosition()) + time.sleep(0.1) + except MpcHcApi.PlayerNotReadyException: + time.sleep(0.1) + self.__setUpStateForNewlyOpenedFile() + + def __handleUpdatedFilename(self): + with self.__fileUpdate: + self.__setUpStateForNewlyOpenedFile() + self.__client.updateFile(self._mpcApi.filePlaying, self._mpcApi.fileDuration, self._mpcApi.filePath) + + def __mpcError(self, err=""): + self.__client.ui.showErrorMessage(err) + self.__client.stop() + +class MpcHcApi: + def __init__(self): + self.callbacks = self.__Callbacks() + self.loadState = None + self.playState = None + self.filePlaying = None + self.fileDuration = None + self.filePath = None + self.lastFilePosition = None + self.version = None + self.__playpause_warden = False + self.__locks = self.__Locks() + self.__mpcExistenceChecking = threading.Thread(target = self.__mpcReadyInSlaveMode, name ="Check MPC window") + self.__mpcExistenceChecking.setDaemon(True) + self.__listener = self.__Listener(self, self.__locks) + self.__listener.setDaemon(True) + self.__listener.start() + self.__locks.listenerStart.wait() + + def waitForFileStateReady(f): #@NoSelf + @wraps(f) + def wrapper(self, *args, **kwds): + if(not self.__locks.fileReady.wait(0.2)): + raise self.PlayerNotReadyException() + return f(self, *args, **kwds) + return wrapper + + def startMpc(self, path, args = ()): + args = "%s /slave %s" % (" ".join(args), str(self.__listener.hwnd)) + win32api.ShellExecute(0, "open", path, args, None, 1) + if(not self.__locks.mpcStart.wait(10)): + raise self.NoSlaveDetectedException("Unable to start MPC in slave mode!") + self.__mpcExistenceChecking.start() + + def openFile(self, filePath): + self.__listener.SendCommand(self.CMD_OPENFILE, filePath) + + def isPaused(self): + return (self.playState <> self.__MPC_PLAYSTATE.PS_PLAY and self.playState <> None) + + def askForVersion(self): + self.__listener.SendCommand(self.CMD_GETVERSION) + + @waitForFileStateReady + def pause(self): + self.__listener.SendCommand(self.CMD_PAUSE) + + @waitForFileStateReady + def unpause(self): + self.__listener.SendCommand(self.CMD_PLAY) + + @waitForFileStateReady + def askForCurrentPosition(self): + self.__listener.SendCommand(self.CMD_GETCURRENTPOSITION) + + @waitForFileStateReady + def seek(self, position): + self.__listener.SendCommand(self.CMD_SETPOSITION, unicode(position)) + + def sendOsd(self, message, MsgPos = 2, DurationMs = 3000): + class __OSDDATASTRUCT(ctypes.Structure): + _fields_ = [ + ('nMsgPos', ctypes.c_int32), + ('nDurationMS', ctypes.c_int32), + ('strMsg', ctypes.c_wchar * (len(message)+1)) + ] + cmessage = __OSDDATASTRUCT() + cmessage.nMsgPos = MsgPos + cmessage.nDurationMS = DurationMs + cmessage.strMsg = message + self.__listener.SendCommand(self.CMD_OSDSHOWMESSAGE, cmessage) + + def sendRawCommand(self, cmd, value): + self.__listener.SendCommand(cmd, value) + + def handleCommand(self,cmd, value): + if (cmd == self.CMD_CONNECT): + self.__listener.mpcHandle = int(value) + self.__locks.mpcStart.set() + if(self.callbacks.onConnected): + thread.start_new_thread(self.callbacks.onConnected, ()) + + elif(cmd == self.CMD_STATE): + self.loadState = int(value) + fileNotReady = self.loadState == self.__MPC_LOADSTATE.MLS_CLOSING or self.loadState == self.__MPC_LOADSTATE.MLS_LOADING + if(fileNotReady): + self.playState = None + self.__locks.fileReady.clear() + else: + self.__locks.fileReady.set() + if(self.callbacks.onFileStateChange): + thread.start_new_thread(self.callbacks.onFileStateChange, (self.loadState,)) + + elif(cmd == self.CMD_PLAYMODE): + self.playState = int(value) + if(self.callbacks.onUpdatePlaystate): + thread.start_new_thread(self.callbacks.onUpdatePlaystate,(self.playState,)) + + elif(cmd == self.CMD_NOWPLAYING): + value = value.split('|') + self.filePath = value[3] + self.filePlaying = value[3].split('\\').pop() + self.fileDuration = float(value[4]) + if(self.callbacks.onUpdatePath): + thread.start_new_thread(self.callbacks.onUpdatePath,(self.onUpdatePath,)) + if(self.callbacks.onUpdateFilename): + thread.start_new_thread(self.callbacks.onUpdateFilename,(self.filePlaying,)) + if(self.callbacks.onUpdateFileDuration): + thread.start_new_thread(self.callbacks.onUpdateFileDuration,(self.fileDuration,)) + + elif(cmd == self.CMD_CURRENTPOSITION): + self.lastFilePosition = float(value) + if(self.callbacks.onGetCurrentPosition): + thread.start_new_thread(self.callbacks.onGetCurrentPosition,(self.lastFilePosition,)) + + elif(cmd == self.CMD_NOTIFYSEEK): + if(self.lastFilePosition <> float(value)): #Notify seek is sometimes sent twice + self.lastFilePosition = float(value) + if(self.callbacks.onSeek): + thread.start_new_thread(self.callbacks.onSeek,(self.lastFilePosition,)) + + elif(cmd == self.CMD_DISCONNECT): + if(self.callbacks.onMpcClosed): + thread.start_new_thread(self.callbacks.onMpcClosed,(None,)) + + elif(cmd == self.CMD_VERSION): + if(self.callbacks.onVersion): + self.version = value + thread.start_new_thread(self.callbacks.onVersion,(value,)) + + class PlayerNotReadyException(Exception): + pass + + class __Callbacks: + def __init__(self): + self.onConnected = None + self.onSeek = None + self.onUpdatePath = None + self.onUpdateFilename = None + self.onUpdateFileDuration = None + self.onGetCurrentPosition = None + self.onUpdatePlaystate = None + self.onFileStateChange = None + self.onMpcClosed = None + self.onVersion = None + + class __Locks: + def __init__(self): + self.listenerStart = threading.Event() + self.mpcStart = threading.Event() + self.fileReady = threading.Event() + + def __mpcReadyInSlaveMode(self): + while(True): + time.sleep(10) + if not win32gui.IsWindow(self.__listener.mpcHandle): + if(self.callbacks.onMpcClosed): + self.callbacks.onMpcClosed(None) + break + + CMD_CONNECT = 0x50000000 + CMD_STATE = 0x50000001 + CMD_PLAYMODE = 0x50000002 + CMD_NOWPLAYING = 0x50000003 + CMD_LISTSUBTITLETRACKS = 0x50000004 + CMD_LISTAUDIOTRACKS = 0x50000005 + CMD_CURRENTPOSITION = 0x50000007 + CMD_NOTIFYSEEK = 0x50000008 + CMD_NOTIFYENDOFSTREAM = 0x50000009 + CMD_PLAYLIST = 0x50000006 + CMD_OPENFILE = 0xA0000000 + CMD_STOP = 0xA0000001 + CMD_CLOSEFILE = 0xA0000002 + CMD_PLAYPAUSE = 0xA0000003 + CMD_ADDTOPLAYLIST = 0xA0001000 + CMD_CLEARPLAYLIST = 0xA0001001 + CMD_STARTPLAYLIST = 0xA0001002 + CMD_REMOVEFROMPLAYLIST = 0xA0001003 # TODO + CMD_SETPOSITION = 0xA0002000 + CMD_SETAUDIODELAY = 0xA0002001 + CMD_SETSUBTITLEDELAY = 0xA0002002 + CMD_SETINDEXPLAYLIST = 0xA0002003 # DOESNT WORK + CMD_SETAUDIOTRACK = 0xA0002004 + CMD_SETSUBTITLETRACK = 0xA0002005 + CMD_GETSUBTITLETRACKS = 0xA0003000 + CMD_GETCURRENTPOSITION = 0xA0003004 + CMD_JUMPOFNSECONDS = 0xA0003005 + CMD_GETAUDIOTRACKS = 0xA0003001 + CMD_GETNOWPLAYING = 0xA0003002 + CMD_GETPLAYLIST = 0xA0003003 + CMD_TOGGLEFULLSCREEN = 0xA0004000 + CMD_JUMPFORWARDMED = 0xA0004001 + CMD_JUMPBACKWARDMED = 0xA0004002 + CMD_INCREASEVOLUME = 0xA0004003 + CMD_DECREASEVOLUME = 0xA0004004 + CMD_SHADER_TOGGLE = 0xA0004005 + CMD_CLOSEAPP = 0xA0004006 + CMD_OSDSHOWMESSAGE = 0xA0005000 + CMD_VERSION = 0x5000000A + CMD_DISCONNECT = 0x5000000B + CMD_PLAY = 0xA0000004 + CMD_PAUSE = 0xA0000005 + CMD_GETVERSION = 0xA0003006 + + class __MPC_LOADSTATE: + MLS_CLOSED = 0 + MLS_LOADING = 1 + MLS_LOADED = 2 + MLS_CLOSING = 3 + + class __MPC_PLAYSTATE: + PS_PLAY = 0 + PS_PAUSE = 1 + PS_STOP = 2 + PS_UNUSED = 3 + + class __Listener(threading.Thread): + def __init__(self, mpcApi, locks): + self.__mpcApi = mpcApi + self.locks = locks + self.mpcHandle = None + self.hwnd = None + self.__PCOPYDATASTRUCT = ctypes.POINTER(self.__COPYDATASTRUCT) + threading.Thread.__init__(self, name="MPC Listener") + + def run(self): + message_map = { + win32con.WM_COPYDATA: self.OnCopyData + } + wc = win32gui.WNDCLASS() + wc.lpfnWndProc = message_map + wc.lpszClassName = 'MPCApiListener' + hinst = wc.hInstance = win32api.GetModuleHandle(None) + classAtom = win32gui.RegisterClass(wc) + self.hwnd = win32gui.CreateWindow ( + classAtom, + "ListenerGUI", + 0, + 0, + 0, + win32con.CW_USEDEFAULT, + win32con.CW_USEDEFAULT, + 0, + 0, + hinst, + None + ) + self.locks.listenerStart.set() + win32gui.PumpMessages() + + + def OnCopyData(self, hwnd, msg, wparam, lparam): + pCDS = ctypes.cast(lparam, self.__PCOPYDATASTRUCT) + #print "API:\tin>\t 0x%X\t" % int(pCDS.contents.dwData), ctypes.wstring_at(pCDS.contents.lpData) + self.__mpcApi.handleCommand(pCDS.contents.dwData, ctypes.wstring_at(pCDS.contents.lpData)) + + def SendCommand(self, cmd, message = u''): + #print "API:\t>i", line + pass + + def printSent(self, line): +# print "o<<", line + pass + + def lineReceived(self, line): + line = line.strip() + if not line: + return + self.printReceived(line) + try: + messages = json.loads(line) + except: + self.dropWithError("Not a json encoded string") + return + self.handleMessages(messages) + + def sendMessage(self, dict_): + line = json.dumps(dict_) + self.printSent(line) + self.sendLine(line) + + def drop(self): + self.transport.loseConnection() + + def dropWithError(self, error): + raise NotImplementedError() + +class SyncClientProtocol(JSONCommandProtocol): + def __init__(self, client): + self._client = client + self.clientIgnoringOnTheFly = 0 + self.serverIgnoringOnTheFly = 0 + self.logged = False + + def connectionMade(self): + self._client.initProtocol(self) + self.sendHello() + + def connectionLost(self, reason): + self._client.destroyProtocol() + + def dropWithError(self, error): + self._client.ui.showErrorMessage(error) + self._client.protocolFactory.stopRetrying() + self.drop() + + def _extractHelloArguments(self, hello): + username = hello["username"] if hello.has_key("username") else None + roomName = hello["room"]["name"] if hello.has_key("room") else None + version = hello["version"] if hello.has_key("version") else None + return username, roomName, version + + def handleHello(self, hello): + username, roomName, version = self._extractHelloArguments(hello) + if(not username or not roomName or not version): + self.dropWithError("Not enough Hello arguments") + elif(version <> syncplay.version): #TODO: change to check only subversion + self.dropWithError("Mismatch between versions of client and server") + else: + self._client.setUsername(username) + self._client.setRoom(roomName) + message = "You have joined room {} as <{}>".format(roomName, username) + self._client.ui.showMessage(message) + self.logged = True + self._client.sendFile() + + def sendHello(self): + hello = {} + hello["username"] = self._client.getUsername() + password = self._client.getPassword() + if(password): hello["password"] = password + room = self._client.getRoom() + if(room): hello["room"] = {"name" :room} + hello["version"] = syncplay.version + self.sendMessage({"Hello": hello}) + + def _SetUser(self, users): + for user in users.iteritems(): + username = user[0] + settings = user[1] + room = settings["room"]["name"] if settings.has_key("room") else None + file_ = settings["file"] if settings.has_key("file") else None + if(settings.has_key("event")): + if(settings["event"].has_key("joined")): + self._client.userlist.addUser(username, room, file_) + elif(settings["event"].has_key("left")): + self._client.userlist.removeUser(username) + else: + self._client.userlist.modUser(username, room, file_) + + def handleSet(self, settings): + for set_ in settings.iteritems(): + command = set_[0] + if command == "room": + roomName = set_[1]["name"] if set_[1].has_key("name") else None + self._client.setRoom(roomName) + elif command == "user": + self._SetUser(set_[1]) + + def sendSet(self, setting): + self.sendMessage({"Set": setting}) + + def sendRoomSetting(self, roomName, password=None): + setting = {} + setting["name"] = roomName + if(password): setting["password"] = password + self.sendSet({"room": setting}) + + def sendFileSetting(self, file_): + self.sendSet({"file": file_}) + + def handleList(self, userList): + for room in userList.iteritems(): + roomName = room[0] + for user in room[1].iteritems(): + userName = user[0] + file_ = user[1] if user[1] <> {} else None + self._client.userlist.addUser(userName, roomName, file_, noMessage=True) + self._client.userlist.showUserList() + + def sendList(self): + self.sendMessage({"List": None}) + + def _extractStatePlaystateArguments(self, state): + position = state["playstate"]["position"] if state["playstate"].has_key("position") else 0 + paused = state["playstate"]["paused"] if state["playstate"].has_key("paused") else None + doSeek = state["playstate"]["doSeek"] if state["playstate"].has_key("doSeek") else None + setBy = state["playstate"]["setBy"] if state["playstate"].has_key("setBy") else None + return position, paused, doSeek, setBy + + def _handleStatePing(self, state): + yourLatency = state["ping"]["yourLatency"] if state["ping"].has_key("yourLatency") else 0 + senderLatency = state["ping"]["senderLatency"] if state["ping"].has_key("senderLatency") else 0 + if (state["ping"].has_key("latencyCalculation")): + latencyCalculation = state["ping"]["latencyCalculation"] + return yourLatency, senderLatency, latencyCalculation + + def handleState(self, state): + position, paused, doSeek, setBy = None, None, None, None + yourLatency, senderLatency = 0, 0 + if(state.has_key("ignoringOnTheFly")): + ignore = state["ignoringOnTheFly"] + if(ignore.has_key("server")): + self.serverIgnoringOnTheFly = ignore["server"] + self.clientIgnoringOnTheFly = 0 + elif(ignore.has_key("client")): + if(ignore['client']) == self.clientIgnoringOnTheFly: + self.clientIgnoringOnTheFly = 0 + if(state.has_key("playstate")): + position, paused, doSeek, setBy = self._extractStatePlaystateArguments(state) + if(state.has_key("ping")): + yourLatency, senderLatency, latencyCalculation = self._handleStatePing(state) + if(position is not None and paused is not None and not self.clientIgnoringOnTheFly): + latency = yourLatency + senderLatency + self._client.updateGlobalState(position, paused, doSeek, setBy, latency) + position, paused, doSeek, stateChange = self._client.getLocalState() + self.sendState(position, paused, doSeek, latencyCalculation, stateChange) + + def sendState(self, position, paused, doSeek, latencyCalculation, stateChange = False): + state = {} + positionAndPausedIsSet = position is not None and paused is not None + clientIgnoreIsNotSet = self.clientIgnoringOnTheFly == 0 or self.serverIgnoringOnTheFly != 0 + if(clientIgnoreIsNotSet and positionAndPausedIsSet): + state["playstate"] = {} + state["playstate"]["position"] = position + state["playstate"]["paused"] = paused + if(doSeek): state["playstate"]["doSeek"] = doSeek + if(latencyCalculation): + state["ping"] = {"latencyCalculation": latencyCalculation} + if(stateChange): + self.clientIgnoringOnTheFly += 1 + if(self.serverIgnoringOnTheFly or self.clientIgnoringOnTheFly): + state["ignoringOnTheFly"] = {} + if(self.serverIgnoringOnTheFly): + state["ignoringOnTheFly"]["server"] = self.serverIgnoringOnTheFly + self.serverIgnoringOnTheFly = 0 + if(self.clientIgnoringOnTheFly): + state["ignoringOnTheFly"]["client"] = self.clientIgnoringOnTheFly + self.sendMessage({"State": state}) + + def handleError(self, error): + self.dropWithError(error["message"]) #TODO: more processing and fallbacking + + def sendError(self, message): + self.sendMessage({"Error": {"message": message}}) + + +class SyncServerProtocol(JSONCommandProtocol): + def __init__(self, factory): + self._factory = factory + self._logged = False + self.clientIgnoringOnTheFly = 0 + self.serverIgnoringOnTheFly = 0 + + def __hash__(self): + return hash('|'.join(( + self.transport.getPeer().host, + str(id(self)), + ))) + + def requireLogged(f): #@NoSelf + @wraps(f) + def wrapper(self, *args, **kwds): + if(not self._logged): + self.dropWithError("You must be known to server before sending this command") + return f(self, *args, **kwds) + return wrapper + + def dropWithError(self, error): + print "Client drop: %s -- %s" % (self.transport.getPeer().host, error) + self.drop() + + def connectionLost(self, reason): + self._factory.removeWatcher(self) + + def _extractHelloArguments(self, hello): + roomName, roomPassword = None, None + username = hello["username"] if hello.has_key("username") else None + username = username.strip() + serverPassword = hello["password"] if hello.has_key("password") else None + room = hello["room"] if hello.has_key("room") else None + if(room): + roomName = room["name"] if room.has_key("name") else None + roomName = roomName.strip() + roomPassword = room["password"] if room.has_key("password") else None + version = hello["version"] if hello.has_key("version") else None + return username, serverPassword, roomName, roomPassword, version + + def _checkPassword(self, serverPassword): + if(self._factory.password): + if(not serverPassword): + self.dropWithError("Password required") + return False + if(serverPassword != self._factory.password): + self.dropWithError("Wrong password supplied") + return False + return True + + def handleHello(self, hello): + username, serverPassword, roomName, roomPassword, version = self._extractHelloArguments(hello) + if(not username or not roomName or not version): + self.dropWithError("Not enough Hello arguments") + elif(version <> syncplay.version): #TODO: change to check only major release + self.dropWithError("Mismatch between versions of client and server") + else: + if(not self._checkPassword(serverPassword)): + return + self._factory.addWatcher(self, username, roomName, roomPassword) + self._logged = True + self.sendHello() + self.sendList() + + def sendHello(self): + hello = {} + hello["username"] = self._factory.watcherGetUsername(self) + room = self._factory.watcherGetRoom(self) + if(room): hello["room"] = {"name": room} + hello["version"] = syncplay.version + self.sendMessage({"Hello": hello}) + + @requireLogged + def handleSet(self, settings): + for set_ in settings.iteritems(): + command = set_[0] + if command == "room": + roomName = set_[1]["name"] if set_[1].has_key("name") else None + self._factory.watcherSetRoom(self, roomName) + elif command == "file": + self._factory.watcherSetFile(self, set_[1]) + + def sendSet(self, setting): + self.sendMessage({"Set": setting}) + + def sendRoomSetting(self, roomName): + self.sendSet({"room": {"name": roomName}}) + + def sendUserSetting(self, username, roomName, file_, event): + room = {"name": roomName} + user = {} + user[username] = {} + user[username]["room"] = room + if(file_): + user[username]["file"] = file_ + if(event): + user[username]["event"] = event + self.sendSet({"user": user}) + + def _addUserOnList(self, userlist, watcher): + if (not userlist.has_key(watcher.room)): + userlist[watcher.room] = {} + userlist[watcher.room][watcher.name] = watcher.file if watcher.file else {} + + def sendList(self): + userlist = {} + watchers = self._factory.getAllWatchers(self) + for watcher in watchers.itervalues(): + self._addUserOnList(userlist, watcher) + self.sendMessage({"List": userlist}) + + @requireLogged + def handleList(self, _): + self.sendList() + + def sendState(self, position, paused, doSeek, setBy, senderLatency, watcherLatency, forced = False): + playstate = { + "position": position, + "paused": paused, + "doSeek": doSeek, + "setBy": setBy + } + ping = { + "yourLatency": watcherLatency, + "senderLatency": senderLatency, + "latencyCalculation": time.time() + } + state = { + "ping": ping, + "playstate": playstate, + } + if(forced): + self.serverIgnoringOnTheFly += 1 + if(self.serverIgnoringOnTheFly or self.clientIgnoringOnTheFly): + state["ignoringOnTheFly"] = {} + if(self.serverIgnoringOnTheFly): + state["ignoringOnTheFly"]["server"] = self.serverIgnoringOnTheFly + if(self.clientIgnoringOnTheFly): + state["ignoringOnTheFly"]["client"] = self.clientIgnoringOnTheFly + self.clientIgnoringOnTheFly = 0 + if(self.serverIgnoringOnTheFly == 0 or forced): + self.sendMessage({"State": state}) + + + def _extractStatePlaystateArguments(self, state): + position = state["playstate"]["position"] if state["playstate"].has_key("position") else 0 + paused = state["playstate"]["paused"] if state["playstate"].has_key("paused") else None + doSeek = state["playstate"]["doSeek"] if state["playstate"].has_key("doSeek") else None + return position, paused, doSeek + + @requireLogged + def handleState(self, state): + position, paused, doSeek, latencyCalculation = None, None, None, None + if(state.has_key("ignoringOnTheFly")): + ignore = state["ignoringOnTheFly"] + if(ignore.has_key("server")): + if(self.serverIgnoringOnTheFly == ignore["server"]): + self.serverIgnoringOnTheFly = 0 + if(ignore.has_key("client")): + self.clientIgnoringOnTheFly = ignore["client"] + if(state.has_key("playstate")): + position, paused, doSeek = self._extractStatePlaystateArguments(state) + if(state.has_key("ping")): + latencyCalculation = state["ping"]["latencyCalculation"] if state["ping"].has_key("latencyCalculation") else None + if(self.serverIgnoringOnTheFly == 0): + self._factory.updateWatcherState(self, position, paused, doSeek, latencyCalculation) + + def handleError(self, error): + self.dropWithError(error["message"]) #TODO: more processing and fallbacking + + def sendError(self, message): + self.sendMessage({"Error": {"message": message}}) + diff --git a/syncplay/server.py b/syncplay/server.py new file mode 100644 index 0000000..d48a8fc --- /dev/null +++ b/syncplay/server.py @@ -0,0 +1,234 @@ +#coding:utf8 +#TODO: #12, #13, #8; +import hashlib +from twisted.internet import task, reactor +from twisted.internet.protocol import Factory +import syncplay +from syncplay.protocols import SyncServerProtocol +import time +import threading + + +class SyncFactory(Factory): + def __init__(self, password = '', banlist = None , isolateRooms = False): + print "Welcome to Syncplay server, ver. {0}".format(syncplay.version) + if(password): + password = hashlib.md5(password).hexdigest() + self.password = password + self._rooms = {} + self._roomStates = {} + self.isolateRooms = isolateRooms + self.roomUpdateLock = threading.RLock() + self.roomUsersLock = threading.RLock() + + def buildProtocol(self, addr): + return SyncServerProtocol(self) + + def _createRoomIfDoesntExist(self, roomName): + if (not self._rooms.has_key(roomName)): + self._rooms[roomName] = {} + self._roomStates[roomName] = { + "position": 0.0, + "paused": True, + "setBy": None, + "lastUpdate": time.time() + } + + def addWatcher(self, watcherProtocol, username, roomName, roomPassword): + allnames = [] + for room in self._rooms.itervalues(): + for watcher in room.itervalues(): + allnames.append(watcher.name.lower()) + while username.lower() in allnames: + username += '_' + self._createRoomIfDoesntExist(roomName) + watcher = Watcher(self, watcherProtocol, username, roomName) + self._rooms[roomName][watcherProtocol] = watcher + print "{0}({2}) connected to room '{1}'".format(username, roomName, watcherProtocol.transport.getPeer().host) + reactor.callLater(0.1, watcher.scheduleSendState) + l = lambda w: w.sendUserSetting(username, roomName, None, {"joined": True}) + self.broadcast(watcherProtocol, l) + + def getWatcher(self, watcherProtocol): + with self.roomUsersLock: + for room in self._rooms.itervalues(): + if(room.has_key(watcherProtocol)): + return room[watcherProtocol] + + def getAllWatchers(self, watcherProtocol): + with self.roomUsersLock: + if(self.isolateRooms): + room = self.getWatcher(watcherProtocol).room + return self._rooms[room] + else: + watchers = {} + for room in self._rooms.itervalues(): + for watcher in room.itervalues(): + watchers[watcher.watcherProtocol] = watcher + return watchers + + def _removeWatcherFromTheRoom(self, watcherProtocol): + with self.roomUsersLock: + for room in self._rooms.itervalues(): + watcher = room.pop(watcherProtocol, None) + return watcher + + def _deleteRoomIfEmpty(self, room): + with self.roomUpdateLock: + with self.roomUsersLock: + if (self._rooms[room] == {}): + self._rooms.pop(room) + self._roomStates.pop(room) + + def sendState(self, watcherProtocol, doSeek = False, senderLatency = 0, forcedUpdate = False): + watcher = self.getWatcher(watcherProtocol) + if(not watcher): + return + room = watcher.room + position = self._roomStates[room]["position"] + paused = self._roomStates[room]["paused"] + if(not paused): + timePassedSinceSet = time.time() - self._roomStates[room]["lastUpdate"] + position += timePassedSinceSet + setBy = self._roomStates[room]["setBy"] + watcher.paused = paused + watcher.position = position + watcherProtocol.sendState(position, paused, doSeek, setBy, senderLatency, watcher.latency, forcedUpdate) + if(time.time() - watcher.lastUpdate > 4.1): + watcherProtocol.drop() + + def __updateWatcherPing(self, latencyCalculation, watcher): + if (latencyCalculation): + ping = (time.time() - latencyCalculation) / 2 + if (watcher.latency): + watcher.latency = watcher.latency * (0.85) + ping * (0.15) #Exponential moving average + else: + watcher.latency = ping + + def __shouldServerForceUpdateOnRoom(self, pauseChanged, doSeek): + return doSeek or pauseChanged + + def __updatePausedState(self, paused, watcher): + watcher.paused = paused + if(self._roomStates[watcher.room]["paused"] <> paused): + self._roomStates[watcher.room]["setBy"] = watcher.name + self._roomStates[watcher.room]["paused"] = paused + self._roomStates[watcher.room]["lastUpdate"] = time.time() + return True + + def __updatePositionState(self, position, doSeek, watcher): + watcher.position = position + if (doSeek): + self._roomStates[watcher.room]["position"] = position + self._roomStates[watcher.room]["setBy"] = watcher.name + self._roomStates[watcher.room]["lastUpdate"] = time.time() + else: + setter = min(self._rooms[watcher.room].values()) + self._roomStates[watcher.room]["position"] = setter.position + self._roomStates[watcher.room]["setBy"] = setter.name + self._roomStates[watcher.room]["lastUpdate"] = setter.lastUpdate + + def updateWatcherState(self, watcherProtocol, position, paused, doSeek, latencyCalculation): + watcher = self.getWatcher(watcherProtocol) + self.__updateWatcherPing(latencyCalculation, watcher) + watcher.lastUpdate = time.time() + with self.roomUpdateLock: + if(watcher.file): + if(position is not None): + self.__updatePositionState(position, doSeek, watcher) + pauseChanged = False + if(paused is not None): + pauseChanged = self.__updatePausedState(paused, watcher) + forceUpdate = self.__shouldServerForceUpdateOnRoom(pauseChanged, doSeek) + if(forceUpdate): + l = lambda w: self.sendState(w, doSeek, watcher.latency, forceUpdate) + self.broadcastRoom(watcher, l) + + def removeWatcher(self, watcherProtocol): + with self.roomUsersLock: + watcher = self._removeWatcherFromTheRoom(watcherProtocol) + if(not watcher): + return + watcher.deactivate() + print "{0} left server".format(watcher.name) + self._deleteRoomIfEmpty(watcher.room) + l = lambda w: w.sendUserSetting(watcher.name, watcher.room, None, {"left": True}) + self.broadcast(watcherProtocol, l) + + def watcherGetUsername(self, watcherProtocol): + return self.getWatcher(watcherProtocol).name + + def watcherGetRoom(self, watcherProtocol): + return self.getWatcher(watcherProtocol).room + + def watcherSetRoom(self, watcherProtocol, room): + with self.roomUsersLock: + watcher = self._removeWatcherFromTheRoom(watcherProtocol) + if(not watcher): + return + watcher.resetStateTimer() + oldRoom = watcher.room + self._createRoomIfDoesntExist(room) + self._rooms[room][watcherProtocol] = watcher + self._deleteRoomIfEmpty(oldRoom) + if(self.isolateRooms): #this is trick to inform old room about leaving + l = lambda w: w.sendUserSetting(watcher.name, room, watcher.file, None) + self.broadcast(watcherProtocol, l) + watcher.room = room + l = lambda w: w.sendUserSetting(watcher.name, watcher.room, watcher.file, None) + self.broadcast(watcherProtocol, l) + + def watcherSetFile(self, watcherProtocol, file_): + watcher = self.getWatcher(watcherProtocol) + watcher.file = file_ + l = lambda w: w.sendUserSetting(watcher.name, watcher.room, watcher.file, None) + self.broadcast(watcherProtocol, l) + + def broadcastRoom(self, sender, what): + room = self._rooms[self.watcherGetRoom(sender.watcherProtocol)] + if(room): + for receiver in room: + what(receiver) + + def broadcast(self, sender, what): + if(self.isolateRooms): + self.broadcastRoom(sender, what) + for room in self._rooms.itervalues(): + for receiver in room: + what(receiver) + +class Watcher(object): + def __init__(self, factory, watcherProtocol, name, room): + self.factory = factory + self.watcherProtocol = watcherProtocol + self.name = name + self.room = room + self.file = None + self._sendStateTimer = None + self.position = None + self.latency = 0 + self.lastUpdate = time.time() + + def __lt__(self, b): + if(self.position is None): + return False + elif(b.position is None): + return True + else: + return self.position < b.position + + def scheduleSendState(self, when=1): + self._sendStateTimer = task.LoopingCall(self.sendState) + self._sendStateTimer.start(when, True) + + def sendState(self): + self.factory.sendState(self.watcherProtocol) + + def resetStateTimer(self): + if(self._sendStateTimer): + self._sendStateTimer.reset() + + def deactivate(self): + if(self._sendStateTimer): + self._sendStateTimer.stop() + \ No newline at end of file diff --git a/syncplay/ui/ConfigurationGetter.py b/syncplay/ui/ConfigurationGetter.py new file mode 100644 index 0000000..2b5ba2a --- /dev/null +++ b/syncplay/ui/ConfigurationGetter.py @@ -0,0 +1,182 @@ +import ConfigParser +import argparse +import os +import sys + +class InvalidConfigValue(Exception): + def __init__(self, message): + Exception.__init__(self, message) + +class ConfigurationGetter(object): + def __init__(self): + self._config = None + self._args = None + self._syncplayClient = None + self._configFile = None + self._parser = None + self._configName = ".syncplay" + self.playerType = None + + def _findWorkingDir(self): + frozen = getattr(sys, 'frozen', '') + if not frozen: + path = os.path.dirname(os.path.dirname(__file__)) + elif frozen in ('dll', 'console_exe', 'windows_exe'): + path = os.path.dirname(os.path.dirname(__file__)) + else: + path = "" + return path + + def _checkForPortableFile(self): + path = self._findWorkingDir() + if(os.path.isfile(os.path.join(path, self._configName))): + return os.path.join(path, self._configName) + + def _getConfigurationFilePath(self): + self._configFile = self._checkForPortableFile() + if(not self._configFile): + if(os.name <> 'nt'): + self._configFile = os.path.join(os.getenv('HOME', '.'), self._configName) + else: + self._configFile = os.path.join(os.getenv('APPDATA', '.'), self._configName) + + def _prepareArgParser(self): + self._parser = argparse.ArgumentParser(description='Solution to synchronize playback of multiple MPlayer and MPC-HC instances over the network.', + epilog='If no options supplied values from .syncplay file will be used') + self._parser.add_argument('--no-gui', action='store_true', help='show no GUI') + self._parser.add_argument('-a', '--host', metavar='hostname', type=str, help='server\'s address') + self._parser.add_argument('-n', '--name', metavar='username', type=str, help='desired username') + self._parser.add_argument('-d','--debug', action='store_true', help='debug mode') + self._parser.add_argument('-g','--force-gui-prompt', action='store_true', help='make configuration prompt appear') + self._parser.add_argument('--no-store', action='store_true', help='don\'t store values in syncplay.ini') + self._parser.add_argument('-r', '--room', metavar='room', type=str, nargs='?', help='default room') + self._parser.add_argument('-p', '--password', metavar='password', type=str, nargs='?', help='server password') + self._parser.add_argument('--player-path', metavar='path', type=str, help='path to your player executable') + self._parser.add_argument('file', metavar='file', type=str, nargs='?', help='file to play') + self._parser.add_argument('_args', metavar='options', type=str, nargs='*', help='player options, if you need to pass options starting with - prepend them with single \'--\' argument') + + def _openConfigFile(self): + if(not self._config): + self._config = ConfigParser.RawConfigParser(allow_no_value=True) + self._config.read(self._configFile) + + def _getSectionName(self): + return 'sync' if not self._args.debug else 'debug' + + def saveValuesIntoConfigFile(self): + self._splitPortAndHost() + self._openConfigFile() + section_name = self._getSectionName() + self._validateArguments() + if(not self._args.no_store): + with open(self._configFile, 'wb') as configfile: + if(not self._config.has_section(section_name)): + self._config.add_section(section_name) + self._config.set(section_name, 'host', self._args.host) + self._config.set(section_name, 'name', self._args.name) + self._config.set(section_name, 'room', self._args.room) + self._config.set(section_name, 'password', self._args.password) + self._config.set(section_name, 'player_path', self._args.player_path) + self._config.write(configfile) + + def _validateArguments(self): + if(not (self._args.host <> "" and self._args.host is not None)): + self._args.host = None + raise InvalidConfigValue("Hostname can't be empty") + if(not (self._args.name <> "" and self._args.name is not None)): + self._args.name = None + raise InvalidConfigValue("Username can't be empty") + if(self._isPlayerMPCAndValid()): + self._addSpecialMPCFlags() + self.playerType = "mpc" + elif(self._isMplayerPathValid()): + self.playerType = "mplayer" + else: + self._args.player_path = None + raise InvalidConfigValue('Path to player is not valid') + + def _readConfigValue(self, section_name, name): + try: + return self._config.get(section_name, name) + except ConfigParser.NoOptionError: + return None + + def _readMissingValuesFromConfigFile(self): + self._openConfigFile() + section_name = self._getSectionName() + try: + self._valuesToReadFromConfig(section_name) + except ConfigParser.NoSectionError: + pass + + def _isPlayerMPCAndValid(self): + if(os.path.isfile(self._args.player_path)): + if(self._args.player_path[-10:] == 'mpc-hc.exe' or self._args.player_path[-12:] == 'mpc-hc64.exe'): + return True + if(os.path.isfile(self._args.player_path + "\\mpc-hc.exe")): + self._args.player_path += "\\mpc-hc.exe" + return True + if(os.path.isfile(self._args.player_path + "\\mpc-hc64.exe")): + self._args.player_path += "\\mpc-hc64.exe" + return True + return False + + def _addSpecialMPCFlags(self): + self._args._args.extend(['/open', '/new']) + + def _isMplayerPathValid(self): + if("mplayer" in self._args.player_path): + if os.access(self._args.player_path, os.X_OK): + return True + for path in os.environ['PATH'].split(':'): + path = os.path.join(os.path.realpath(path), self._args.player_path) + if os.access(path, os.X_OK): + self._args.player_path = path + return True + return False + + def _valuesToReadFromConfig(self, section_name): + if (self._args.host == None): + self._args.host = self._readConfigValue(section_name, 'host') + if (self._args.name == None): + self._args.name = self._readConfigValue(section_name, 'name') + if (self._args.room == None): + self._args.room = self._readConfigValue(section_name, 'room') + if (self._args.password == None): + self._args.password = self._readConfigValue(section_name, 'password') + if (self._args.player_path == None): + self._args.player_path = self._readConfigValue(section_name, 'player_path') + + def _splitPortAndHost(self): + if(self._args.host): + if ':' in self._args.host: + self._args.host, port = self._args.host.split(':', 1) + self._args.port = int(port) + else: + self._args.port = 8999 + + def setConfiguration(self, args): + self._args = args + + def getConfiguration(self): + self._getConfigurationFilePath() + self._prepareArgParser() + self._args = self._parser.parse_args() + self._readMissingValuesFromConfigFile() + self._splitPortAndHost() + return self._args + +class ServerConfigurationGetter(ConfigurationGetter): + def getConfiguration(self): + self._prepareArgParser() + self._args = self._parser.parse_args() + if(self._args.port == None): + self._args.port = 8999 + return self._args + + def _prepareArgParser(self): + self._parser = argparse.ArgumentParser(description='Solution to synchronize playback of multiple MPlayer and MPC-HC instances over the network. Server instance', + epilog='If no options supplied _config values will be used') + self._parser.add_argument('--port', metavar='port', type=str, nargs='?', help='server TCP port') + self._parser.add_argument('--password', metavar='password', type=str, nargs='?', help='server password') + self._parser.add_argument('--isolate-rooms', action='store_true', help='should rooms be isolated?') diff --git a/syncplay/ui/GuiConfiguration.py b/syncplay/ui/GuiConfiguration.py new file mode 100644 index 0000000..4e35cfb --- /dev/null +++ b/syncplay/ui/GuiConfiguration.py @@ -0,0 +1,109 @@ +import pygtk +import os +pygtk.require('2.0') +import gtk +gtk.set_interactive(False) +import cairo, gio, pango, atk, pangocairo, gobject #@UnusedImport + +class GuiConfiguration: + def __init__(self, args, force = False): + self.args = args + self.closedAndNotSaved = False + if(args.player_path == None or args.host == None or args.name == None or force): + self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) + self.window.set_title("Syncplay Configuration") + self.window.connect("delete_event", lambda w, e: self._windowClosed()) + vbox = gtk.VBox(False, 0) + self.window.add(vbox) + vbox.show() + self._addLabeledEntries(args, vbox) + + self.hostEntry.select_region(0, len(self.hostEntry.get_text())) + button = gtk.Button(stock=gtk.STOCK_SAVE) + button.connect("clicked", lambda w: self._saveDataAndLeave()) + vbox.pack_start(button, True, True, 0) + button.set_flags(gtk.CAN_DEFAULT) + button.grab_default() + button.show() + self.window.show() + gtk.main() + + def _windowClosed(self): + self.window.destroy() + gtk.main_quit() + self.closedAndNotSaved = True + + def _addLabeledEntries(self, args, vbox): + self.hostEntry = self._addLabeledEntryToVbox('Host: ', args.host, vbox, self._focusNext) + self.userEntry = self._addLabeledEntryToVbox('Username: ', args.name, vbox, self._focusNext) + self.roomEntry = self._addLabeledEntryToVbox('Default room (optional): ', args.room, vbox, self._focusNext) + self.passEntry = self._addLabeledEntryToVbox('Server password (optional): ', args.password, vbox, self._focusNext) + self._tryToFillUpMpcPath() + self.mpcEntry = self._addLabeledEntryToVbox('Path to player executable: ', self.args.player_path, vbox, self._focusNext) + + def _tryToFillUpMpcPath(self): + if(self.args.player_path == None): + paths = ["C:\Program Files (x86)\MPC-HC\mpc-hc.exe", + "C:\Program Files\MPC-HC\mpc-hc.exe", + "C:\Program Files\MPC-HC\mpc-hc64.exe", + "C:\Program Files\Media Player Classic - Home Cinema\mpc-hc.exe", + "C:\Program Files\Media Player Classic - Home Cinema\mpc-hc64.exe", + "C:\Program Files (x86)\Media Player Classic - Home Cinema\mpc-hc.exe", + "C:\Program Files (x86)\K-Lite Codec Pack\Media Player Classic\mpc-hc.exe", + "C:\Program Files\K-Lite Codec Pack\Media Player Classic\mpc-hc.exe", + "C:\Program Files (x86)\Combined Community Codec Pack\MPC\mpc-hc.exe", + "C:\Program Files\MPC HomeCinema (x64)\mpc-hc64.exe", + ] + for path in paths: + if(os.path.isfile(path)): + self.args.player_path = path + return + + + def getProcessedConfiguration(self): + if(self.closedAndNotSaved): + raise self.WindowClosed + return self.args + + def _saveDataAndLeave(self): + self.args.host = self.hostEntry.get_text() + self.args.name = self.userEntry.get_text() + self.args.room = self.roomEntry.get_text() + self.args.password = self.passEntry.get_text() + self.args.player_path = self.mpcEntry.get_text() + self.window.destroy() + gtk.main_quit() + + def _focusNext(self, widget, entry): + self.window.get_toplevel().child_focus(gtk.DIR_TAB_FORWARD) + + def _addLabeledEntryToVbox(self, label, initialEntryValue, vbox, callback): + hbox = gtk.HBox(False, 0) + hbox.set_border_width(3) + vbox.pack_start(hbox, False, False, 0) + hbox.show() + label_ = gtk.Label() + label_.set_text(label) + label_.set_alignment(xalign=0, yalign=0.5) + hbox.pack_start(label_, False, False, 0) + label_.show() + entry = gtk.Entry() + entry.connect("activate", callback, entry) + if(initialEntryValue == None): + initialEntryValue = "" + entry.set_text(initialEntryValue) + hbox.pack_end(entry, False, False, 0) + entry.set_usize(200, -1) + entry.show() + hbox = gtk.HBox(False, 0) + vbox.add(hbox) + hbox.show() + return entry + + class WindowClosed(Exception): + def __init__(self): + Exception.__init__(self) + + + + \ No newline at end of file diff --git a/syncplay/ui/__init__.py b/syncplay/ui/__init__.py new file mode 100644 index 0000000..f42f8b4 --- /dev/null +++ b/syncplay/ui/__init__.py @@ -0,0 +1,11 @@ +from syncplay.ui.gui import GraphicalUI +from syncplay.ui.consoleUI import ConsoleUI + +def getUi(graphical = True): + if(False): #graphical): #TODO: Add graphical ui + ui = GraphicalUI() + else: + ui = ConsoleUI() + ui.setDaemon(True) + ui.start() + return ui \ No newline at end of file diff --git a/syncplay/ui/consoleUI.py b/syncplay/ui/consoleUI.py new file mode 100644 index 0000000..d59a1a0 --- /dev/null +++ b/syncplay/ui/consoleUI.py @@ -0,0 +1,97 @@ +from __future__ import print_function +import threading +import re +import time +import syncplay + +class ConsoleUI(threading.Thread): + def __init__(self): + self.promptMode = threading.Event() + self.PromptResult = "" + self.promptMode.set() + self._syncplayClient = None + threading.Thread.__init__(self, name="ConsoleUI") + + def addClient(self, client): + self._syncplayClient = client + + def run(self): + try: + while True: + data = raw_input() + data = data.rstrip('\n\r') + if(not self.promptMode.isSet()): + self.PromptResult = data + self.promptMode.set() + elif(self._syncplayClient): + self._executeCommand(data) + except: + self._syncplayClient.protocolFactory.stopRetrying() + pass + + def promptFor(self, prompt = ">", message = ""): + if message <> "": + print(message) + self.promptMode.clear() + print(prompt, end='') + self.promptMode.wait() + return self.PromptResult + + def showMessage(self, message, noTimestamp): + if(noTimestamp): + print(message) + else: + print(time.strftime("[%X] ", time.localtime()) + message) + + def showDebugMessage(self, message): + print(message) + + def showErrorMessage(self, message): + print("ERROR:\t" + message) + + def __exectueSeekCmd(self, seek_type, minutes, seconds): + self._syncplayClient.playerPositionBeforeLastSeek = self._syncplayClient.player_position + if seek_type == 's': + seconds = int(seconds) if seconds <> None else 0 + seconds += int(minutes) * 60 if minutes <> None else 0 + self._syncplayClient.setPosition(seconds) + else: #seek_type s+ + seconds = int(seconds) if seconds <> None and minutes <> None else 20 + seconds += int(minutes) * 60 if minutes <> None else 60 + self._syncplayClient.setPosition(self.player_position+seconds) + + def _executeCommand(self, data): + RE_SEEK = re.compile("^([\+\-s]+) ?(-?\d+)?([^0-9](\d+))?$") + RE_ROOM = re.compile("^room( (\w+))?") + matched_seek = RE_SEEK.match(data) + matched_room = RE_ROOM.match(data) + if matched_seek : + self.__exectueSeekCmd(matched_seek.group(1), matched_seek.group(2), matched_seek.group(4)) + elif matched_room: + room = matched_room.group(2) + if room == None: + if self._syncplayClient.users.currentUser._filename <> None: + room = self._syncplayClient.users.currentUser._filename + else: + room = self._syncplayClient.defaultRoom + self._syncplayClient.protocol.sendRoomSetting(room) + elif data == "r": + tmp_pos = self._syncplayClient.getPlayerPosition() + self._syncplayClient.setPosition(self._syncplayClient.playerPositionBeforeLastSeek) + self._syncplayClient.playerPositionBeforeLastSeek = tmp_pos + elif data == "p": + self._syncplayClient.setPaused(not self._syncplayClient.getPlayerPaused()) + elif data == "k": #TODO: remove? + self._syncplayClient.stop() + elif data == 'help': + self.showMessage( "Available commands:" ) + self.showMessage( "\thelp - this help" ) + self.showMessage( "\ts [time] - seek" ) + self.showMessage( "\ts+ [time] - seek to: current position += time" ) + self.showMessage( "\tr - revert last seek" ) + self.showMessage( "\tp - toggle pause" ) + self.showMessage( "\troom [name] - change room" ) + self.showMessage("Syncplay version: %s" % syncplay.version) + self.showMessage("More info available at: %s" % syncplay.projectURL) + else: + self.showMessage( "Unrecognized command, type 'help' for list of available commands" ) diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py new file mode 100644 index 0000000..661c2f7 --- /dev/null +++ b/syncplay/ui/gui.py @@ -0,0 +1,9 @@ +''' +Created on 05-07-2012 + +@author: Uriziel +''' + +class GraphicalUI(object): + def __init__(self): + pass \ No newline at end of file diff --git a/syncplayClient.py b/syncplayClient.py new file mode 100644 index 0000000..5e289dc --- /dev/null +++ b/syncplayClient.py @@ -0,0 +1,5 @@ +#!/usr/bin/env python + +from syncplay.clientManager import SyncplayClientManager +if(__name__ == '__main__'): + SyncplayClientManager() diff --git a/syncplayClientForceConfiguration.py b/syncplayClientForceConfiguration.py new file mode 100644 index 0000000..7a89fe0 --- /dev/null +++ b/syncplayClientForceConfiguration.py @@ -0,0 +1,9 @@ +#!/usr/bin/env python + +from syncplay.clientManager import SyncplayClientManager +import sys +if(__name__ == '__main__'): + if not '-g' in sys.argv: + if(not '--force-gui-prompt' in sys.argv): + sys.argv.extend(("-g",)) + SyncplayClientManager() diff --git a/syncplayServer.py b/syncplayServer.py new file mode 100644 index 0000000..ce0555b --- /dev/null +++ b/syncplayServer.py @@ -0,0 +1,11 @@ +#coding:utf8 + +from twisted.internet import reactor + +from syncplay.server import SyncFactory +from syncplay.ui.ConfigurationGetter import ServerConfigurationGetter + +argsGetter = ServerConfigurationGetter() +args = argsGetter.getConfiguration() +reactor.listenTCP(args.port, SyncFactory(args.password, args.isolate_rooms)) +reactor.run()