From 5c48fd9c4a8a5c94a8d989d523a7f812b74a7287 Mon Sep 17 00:00:00 2001 From: Philip de Nier Date: Tue, 23 Jan 2024 19:21:45 +0000 Subject: [PATCH 1/2] Support null is_key_frame and temporal_offset GSF version changed to 9. This change supports cases where is_key_frame and temporal_offset are unknown, e.g. the processor that encapsulated the media in grains was not capable emough to extract the info. Readers are then able to decide to process the media differently if the values are known to be unknown. sem-ver: api-break --- examples/coded_video_9.gsf | Bin 0 -> 68317 bytes gsf_docs/gsf.md | 14 ++-- gsf_docs/ssb.md | 3 +- mediagrains/comparison/_internal.py | 2 +- mediagrains/grains/CodedVideoGrain.py | 46 ++++++++----- mediagrains/gsf.py | 52 +++++++++++--- mediagrains/typing.py | 4 +- mediagrains/utils/h264_grain_wrapper.py | 6 +- tests/test_gsf.py | 87 +++++++++++++++++++++++- 9 files changed, 171 insertions(+), 43 deletions(-) create mode 100644 examples/coded_video_9.gsf diff --git a/examples/coded_video_9.gsf b/examples/coded_video_9.gsf new file mode 100644 index 0000000000000000000000000000000000000000..eb1ff7a2d71f91a45234b165281129b296119f74 GIT binary patch literal 68317 zcmeFZcT|&Gw=cY&bXo|#NC{O`N=IV zDkw_mC@3f@B5nwxqFa<;BP!E zVX<*xQUHMP5KgcITz`#j=HJv;_*iXtxBBqi4UgWE5j7<#lemzuo%--JBDtXTKVKjX zk4ipav7Cse1ClGk0>gv z#IWKQtKZ#zGwgb!*z_5F*x=)IqSaDsx*0tvIyQuEY0ad^S}kQ*(m9Nv;AO#!;R#oB zc!7nd*J>9tYr4ZmCwOIWNDw^X6wOVF3JHy;TQL}m&8!%fOW?uq_;~Je3yVE__LwI` z1cyXNadw$U$A(!*-eMjezcUJ66U~i}h~5>qoF2ppQ8c9!(ukie+uph!E$ za`;E*?BYZv#f3=r=sQE~f+INexZQzpi$UkcCBfy+h{TZK0LiVA3j$&}yTU^3ESJ)Q z!egU%asuF$mh|}8kf^ALICup!kr^Bm4>y8hcEV*a6py{pyF%=&EKMyf>7kss_yBHP zWCR!9^v?&xa08-4L*qguH^qm?!W-i3nDnUV=tz!4xB~ul%#t1#6%hoR@~;C7`mWgD zuLz3R$%&UVBVt#4NNf}bUIdQ^M(vK}Bn1RT@8oiRHwcm<9tzto_y~AmEJt!pXe?)E zh@{p~nS<>{a* z|IysvyLOzwSo+RDNEHbg10&$eB<*9s4_&euA01_9Y0jX>NE&CyFkcEcxsuoXUUL%d zmfFAraq%HsJ8ODEU_!u7PU7!JEcyKi$&tX_p>Yv=AzdZUge(jQj}5ll1u0Drjfjd0 zkq{<=3z-Ea0KUo^@;Szi0e%Z7f)FI@kem#CV-r9Lxch(SGqi2@zrX*cz`rT*ZwmaI z0{^DKzbWwlCIw=i0T$>1iB6-aAAS}c0VM_+mc__Dk}QX;nkd}_}GvWU!gG#E1ls3whz1L0IYVw}qDJX(JbylQ*-ZCV7r7n_fp%zsHY z0k$o4+V~BIAX~1fd7bWlNCX$;`jBN=QwATL^!lu7BQu9mIBE<5t<5~e9+&_ea#aLZ z6xBXJAY4P~R&yx1j;0KBwrjzs)sC}}*F~itIfL5$vw7#b1)$K1)wVSDvA3xO*#CMH zUx#|kXePA+Y%=Na6*9AHDhQRIF^zR+2khXh_2gbRZ(e%6I4wNiHQ!#upuO*LLRTyk zy45AyFF7(zx@u6Mc%oIfblt-?nRNz}g?vPz2&~CNz1*@E-aVqrmv=V+wbHKIpZGL< z8x^2Bc%jzX*wHs@!)%Tm!w&Kh5Xw+KCYUV?-nJgW+XI!JnJ`;=UJsE)D%Mf~ngK#` z!-{0cC$d;}k<$aC%lY8e_5-veKmPQ40tV|CrIS%H~Wea zU`rR!@a?k=jZ;9;Rh3UZqq-%PO?`tm18Q1=)S3d~dq5J@f#tby722E$htou_fn!0JAT;v94T`=@uU3 zHHEcP{iDZw`4>s7*TnZCJf+DqeE^KQ9ecak7pR^7)`Mfc5+?**jUsu~Os7}0UJO>6 zeY$?cUT)1hAsi$G)BY^nuND0-t1Tk(9L{HuKVkUCgrfr8^`GUI3jup;m?M~HLNwon z^AAu{h1bU%)sXQi%G66%?A|Vrar~sVPPzgy2^5AM#9$W_WfwlESW~b~_{u|Wf2JZx zVCKCOE&!ohn1M>CZRLyn1DwV5|SEX5MIj z^9n$C9?|e9;tVF5r(_okp4n{U!2v{Y1o||!v~+VH1F7WPXSg>#1)n=0-%Earl0-WN z<2WBVt&Caw(b#o&Bt#4|!C#K_OPuJucG=L7FA=-kn$6SS6;*nYq$ek8fd`JCkz;=U zN$sb=q1WYepkDWQM(d9Ie4^CB3Cnx(_-2J`o4}%d33HwYqE{TP2oeGZCpDaA{FVFQ zv6>8>##A~y+g>>QlJY>$JVzCqPGbdaTC@d;2khkJltmivqd zx8VAtWw+utC!4Ic;H#$1l&rIt40akxE!J7ZL7K1k+)-MSXxy*e>Bh!G59O{P3xr_&L{_L`jl)xP;o5L zc%6)`so)sX*hN68uz%n*Z+#%Avsi>jmt8RL(;~&KPxT_*Nw?)mdV1EDqqlD43ClsZ zFXo|4mw!O|Y20?4h0_!W4)5_t_t;u*H}mi7S2~ljH2y-I8z0sX`mT=uf~IYWyE~y zL$)*PC5r$KV{CvbZiN=~(Oe2J-~6HL%>E+tVh_@Ga>yn2^4B2ibd2pcn z?L2`kt61YvB3*U^I5OC0(%DYZW+kQ}z`A8_@uI60s`DE41>gjV5J|1RW2X-d)k7d9fh6Wq|#_v`} zMfrc#s8CC{r)e#n6wLwJVl8lSOEHB)yKun)j|rIzVFP}=zjE`x{SFSQS|9UED7~hO z5@5V+taUE?g!@)mOf*GC5uvnofrm1Mzs6)2hCKhI<1|SZ`UhTgJxf)I9uosKQQ7FB zOjAAVt=ih2r2F@r1e1DiR1cpjyE##d@EM&tn?saivdT7h&BNx9pH}tRKrh2TQD?;a z8E_B)x*?!RWdxdbUQNIRNqK%rdE2%1Ui^xA2I%S^jrzgjo{BX3g>Ml3SU%x@R<7d{ zKv8?25Bx&1QwS@1;>td;Qnk!)r`xBaG`u4f4i|a8IWmGO_E+~_f$Aib1?c*D^N`7d zx*quav@wwuptEw5nShn5*p3n?3KnC@3AnZgQx9e!5*S2R z1#IuS+5YnGmN_ay)qkUf(^}mJ-`pGD% zX+6=8;S;tsu+=E*K8*99a7S8iltDm7V1QQ5ER-9Nmt?hAELC{=x|W{4{cSPWJ4erT zy|{m-Evp2Tjd%wokHpyx(Mj3@ijQ)po~ut$iy%`YRN03tJ2H!Pv!&^Hvb8(YzmD@m zw?r)&$m@+fVw@>#nHAZ-P3mYk&tI|KsVDiB4q-H~q#;Iz!uyGBzV-gH9RrG94+>bT zH_!k(=JQwEL=58#5FEjM;?rR)*Ua!n8x#12DS2+s55D?Aox);Uv`D^;LF(6Gf{o=tUfb3J_!YNwc^*?KI%N|Ny@2By-!^D(sQ zNv&4R(AP7ByClos1(Qsr(vC`-TUfz9D~2Az)p$WbAK@|f)Pm~( zCv^fyVX1BER&TWQ(#^TVs!g;5Wy!Gj`pP)Ns(gz6sz;#*j$^ox-c-8|(ADxw49wS5 zsZV)NU_4zEM~KHomba}^|88G>48y&9Zq3=v)_P?f)UDSqt=Z=1g;O$merswF zDMM9pN_cL@%mf^`lf>3y(TRHf&mBC-6M?<11B>5PP!?Ap z(fR>M-imzDV(_GtPrCnH94{-STw);+PUT6RJ}cLLR&IfOqMXsAyk6kyyub|;xz0fwEKh_=IIHg?;Cf%$GI_rY zS~R|QNR+&#I~BS+%hHBSq1ZvHAjAwyZ=dCNg(h9~FYNg;efu-cRP&*<^WPP|nwRId zW0SIdG++0A&Y|9t*Vnk08e874 zfIb+(vFNl!)0|#}&FN^mcD|I}d8uYbq(Ec7F3-&Pr2SVib=qJR zxsW3`khfLO*_4ezgFwz7iMQ;e*#D6C^h&GL)19XBaXKx3|HLo>d1{BF%`c$~kYVAp zsz&R$!_9X(F=2%!uuFWeI4)-~B#_P4r4O7GhjmU3-%e>uZ#SVbxLck%HSyQ~R7Oti zpIJHy!V`3$j{!Io!^8JXs&Y;1^Oj)(!<6GV=TifgD0Ra?IIY_W0@y{J6>2Mc|IplR zP(I_O_@qK(?OSbHF`MzXO6qrjAervqp4OrWRKf12L}}CdZCCV}a@yx|W0_1~ZrY%!F?ETrU zigqFJQ0c_&-~aXo6aS06!B{%+eoLYR2)O-) zUX!DknH>{TsoPyQDlZ>9r8d;fc?O)LZ+>jSTDzT`ZR51CEqepIjx74$z!Hd6pdD~) z_|x<3BM1x?^3_b9c4M|FK#c)tIN7afYgA~r^PO*sr9&5pV`Q{jqJ?0#BPo+s*>>lb z(EMcO>-h;QVL$NNT}{(5XVoJQFs8+-m2Y&4`-Dz`B|YBx>h`lg-YeG5ZdThCc;M@h zMCAtog*7q{(@C;Q$XEO*8M>+#R2Vn#lZ0uopD+G@VT>-8EmGUgsb5nQMd|@{OXu z8-mg@_WlxVTj6wX?JHy|yL8K%0t|muxy7--?>#0ltyV>+7r1T=EIK_O6M=m%ZCis) zxHT<2$V(w$6|!+=?p}tiN9I|v`+tX(c(diY+9-9Gz~c6_X1L(?hx>BO-!U4J8I>U% zQkz4wWSgLpEwN9jJALo{p0O*9)2mm1@!j4y1!~Xdf}1HRQiS-P@&F-{<2vsg!)Xqq z0RpSls)xYb*+)%BQ|s=4wYgP5=CwFQfseGYuV#hs93HJR;|ttg(w8C|{lL6?eV=4p z-F4;G?Bk{Q5&lSh7o)uG%?j;PHPd(WS9iGHf{2FO#}{9VY17#Lo;m$75PXmyUpz)r zQ?1DgtH?eX2M7kr_QDUm;my2n8{G@}#0rK92ivrd- zY}>>OXb4lKUtg(qX}NoqH2QPf_Gd^&(mHa}v{~A+d=S%!8$PB~w!aiv^IlD_-$_D_NA&H&y> ztFpi4Cld$x{-OQ3{4l3w;v|4m_w0j=^31CF$+g`mnr7bv`Rw*R`KKP@(meTO=qe_e z$7qlfLDLjqEeG#6AD%q?rWxL$P&)7E6(;%MhoxO_*tlV}y?B-MY_XokN0Pu&ILyyJ zm*c#zJf~9XsB>aQSQEGqCfUCYEoZ7mYSWqt)Ou0Fh~i@5 z5*Z_oNz=v(iOI8&(zJ59{a=JhHR~q(d9KJq%KUf3ZycocAF^h)x z4~X3Gbi-4qA)I#E?HNqX{$-zAD?(KxPnaiaIne25?~*?Ls-QvtqWQ+jVG5w&Mv5Dh zTVbH7se8*G9Vk=r`Obp-0-ZJNA2}+!1{YAQn3wu-y6LGKogXd!MwWWpdr+2XtNpt5 z8P4jw`33)oEbVTfDV^%^*-_@26M8k{jH2%gI>jV}U&S441Jc8@VlBAWN-QOEk+@cV zT9!W@&=@e3i+#BKIT|Z$72saxi(*rz&Hcxvsur8hUPlRvQ~ICp(3L~(t=vnmpvWie zN?gs^4x(l|fDbBrtz3x(=<|}Ehc5vLz!DS7VJL+1O770-KY`HQ#@f_UD6fCw>niCR z?5>zJl8gG>K9Npf<9eCMvQLvTo!tVrSpg9*I~{1IYq@_c(d$$JAr4&r+#-rEY zEwPxaA$hX0IdS_SS}213hEM+smB!p~XV zmYl%_tOph1Go1H$)%qR{MI0ZQkdijN#Bh}Xzc{z6k=sAGzULpbwW77E@+2I~Pg|8G z&87{uvHT1H{RuIw9AOLDA{lZzaiW&+fZKAtfb*l1Q4*?9VxO_kS5l6Cxha(k{ozZn5_=OZJaEh^C$K8$9pT5L&qsr#Wv_$ytNC(s8ABB|)pLPCD)Q^XjDZ~k0tlG}RoB$2pPy2<1cD)0Q5#j9Zd)xwM4q!FH^CVvUygSM14 z3tv@TAyHft++39_4X_m0H%hl}vM$ zZzdd2zga6EZ%cXZSD(0g-nMAW9>adTzx))SfTjXr1aV74_*2_Y6XROIkE84wVPaSn zSGP~dlo37xd89rf-`y`4tj34RpvASz!#a$ALWh|nldK)xQu7a?@=pOaw^lF#MQ+b* zA|hz$f*^2@aSv`IgMhe-H|p0krXaIe(_#mXtXt8XpIaqEe&8Bb;q~ zf}20xSW}X}0QAn%&8t;8XHRVIDi;&=T%UGSPo{0q4wMM)`d`T@U@LseImFDWw!}VZjYNm9l;pZ@g7{Cm)G+DR^YU+%)+Tk>1%4tx3T^lG4$v`Fj0t z1v36;CR@fE`zV7edLJS_V5p=F>+NPs-IOU8J+*9nvgi!D)7D{{zqgezqEIFjL@bI$ zr6R}PLxqrR#vAm1GwAV;G(=_@u^Q^9&Vkt;v zJg$R7-FVebjkIo$lxRXgL<_a(qtodX=rXU88+PM=b4Y(tnx~--Q@sk6QOBdo{sMQ8 z71Ls{x$c6T)*C=naNwOfcvt4d6mG@?r0hDBJPVC$-Annv?jN^iJ9rAb8sV4DHBPlj zVi4f)mQ^*gyL2lpmW6n2%3=_uE$0FRFsBq{_H9vJlKP!&lsA}y@wv4cSAy0h6`**+ z3#-qgu+It#sS7Vs+_xUk3qY^E(wQS4)f`>AR|oIP{1GCz4}Airs!$>ajS!(x|6ms2*&`bC} zz$o?E{Nu_0R6Nzjl*T90n-8-`RhO$5lwjRi%ki`%lO1D%%7V-BETY^1 zpAt=_HOBMQD>JT|(raIf!KwFK9`U-fqh9Bz2Hu-@EvPMZ1rVg=w+|X^H6t@`ejF~T3M^- zZ~dz`gG)zSJxsQZzWdzH_>=ctsERBz5wy8K6uQdb_W^IY+Re3G9?d-s?75#AnQ$o*mqO#Y)kG)sw{9JGzT4Gmkz2~9Ll@?eEY2)(~ zB~wa0r5vOCb2{u>21O3+Eb*Z?3y$r5wbZtAao>97ZigII51kvLq-%RUQ+! z$m-G%S^dqR0t_wr7l~(E-|`0D^7j9DUwtWqs}I$9^=*u`N)GN!>EAuqGLbY2BCezzVHzV~r4k9C*J%R23 z5CBCgZA3NAZR_nGZpL5<5sy@w@&#nT<+k)mm>jv$~4^ zz>tzzn^V68^%)ljA~d_Mh*aEzUs=c7i#GPQ#n#%=bsD*z^UI-&RhzwFC)%MhQ(9s+ zI|gL;j@^p_&Wjcg9C}>w&R~g-^VLfY)}UT?{vsk^+{>RP~P)8~>wj0b(VIAnmEfmvpJ6+VufUWl` zybT&TNdt7R<0n9!UdV1H-SH>e=0NZrbw`}Mm=0K#%Md4&0;9VS$Usv_=^@~n2kGov zA;K#QOoTBjyOai0_fJzEH1+C`H)WhzqW6H8cCOkXv>IJIR!6FBhm{A;&Z~k$ot=4@ z;ADtjn;R~rrCWd(P%1Mx$2<$tG;%f^6K*(gMtDlmMm#10$?>HchHPnr>2`~g-O<8N z9BsrnkFJW_235}U<;{bRWgoyEXjgR$PybX9F zyD)NA1miMnb>9PdQnx%s51sAjz$W$mxfat;XRr1>&#u$YH1qm#P23IK{A#5Z;d@|s zE(~t3HkWsADdbb}WV*Ym-^`57u!>4KYGxSZ-0z46pIKN6%109MOT~G&rd&CdnLNI* zQ^toK+$@^_>K7XrK!PE&46o8Us>5I~g>i#P{EHR5fHe7b#uj3c5(*8LC3mQAJ+YjD zTyZ)DGzC*qs{jrldEqZ#KZqAx+Lny_V#GXrt{5Z7Y%YAjNckB(%s}4dtr<7042yxuBn}x$dFQ-(TdAQLL3N zWkLPN+LVBs=S1D1^45vTKz1aFjVt)X;{vVPxlCz`1+x$#(WD*Ln_KT+k@%g0cYL8m z;+&0-_{t_%dxV^*Jn)7d@>iqp!YB<+4_O@{o_q%4t)*W zxO^EMgsovvA`=pm}$r>{%|x*HT`vM!C}STN?|H!h}OECw|P>Z@fOi zl%j@y*Vk9w*M5Yy0#D{+M=X8gD3r7Qa{OQ$FFx`1h=`jP!atSkVQNy&A6M=zTLBy> zPnQ%#YCc_ZAR1AV}$P(9Ht%Qz$*@A<0WwSy9#Y5k{l;Kk!B<%4b8 z9{Gl)KMJWORGhVE&kI#6^QKJ+xvJnsLCdNkizHD@6hMTaD7{UIsQ&hlGH8XyS{a}`p zE+Bw`um9B7|3JO}T1t>xPY)lk-l-_C04pA>MtF0;Me8_%-=fT*>o4D~rLSNLOGx}+ z3;?9|Gbd1+-9ww%O-o<3h`2ecYR750iEu56MD!H&<>d7B9E2gJ$QJ{&m7TKW@#-7m zF9W&Ns|Io_pWy&tF#$ViV)9^l3QzFQyLq}b&u{N+fpQiM(<78@&Fv>wN_9z1(i{>F zukp&1(*4#ARiG5Z_L=<+ZP}{eU}tA*XsY-m z*;8PUEgb*>B_R|tRS3|YlM3v*gL9H}<{K?zO+RjF1oS@<j3C}{WBDjNkU23@j4jXCTP{?DJxFfa^Du@*W1ti z^Gq|IrhQLQx!p-p)a4HpSS~2z&ARf3LTevs7O}G7$Y3dF0!wtaVmNTJ#OAa1LMnKd z=Oe3m=ehb$=bXXmqJPlDGzF;N-m?-*)`{0tc5|1-<`O_HLR3wOGC(|A0%Q*27sT3B zjw&fPyOyNc-{Pg`R!%NiK*jBPSf%5;lCG~{5p(*qn}HZaCmIC;x~)AXGCE&!zPL&v z2*-x`>df1hd^&qoEU;`!hCAp}?!!4VsO2z#_UBIPK~LkBRGR(-4*f_R>hSG&jRE3jMGfJq<>)DHBK7bWbM|e&%TlhI0rv zq)h?35YVWAHjmOVKA64sACFD^@JNdTYfBSnEOZMk8v87GEW<3SQv=baO2)N2-ru0@7^SMmi4PF4P$^ z!`_Bx)obciJ_AmCiS?(csq+W&iBYg*z{9$BobgCVv&=SBwuR=}O8M0yW#~ye<%*^2 zHfNR%kw5%Ys9px8(7?oz;)=M^dripBs@qePypB$+$sOgTg&Wl`pfUn}weZIk9tq*E zJ;TKvY8aeK+O58s>&GLj8=#f`cJK>jQ-OWF>hSi|f|=}(us92^ko{>9O3*#_!a*K` z5jT(fG#I~AWwE5VV5{OtsKRB6?$1u2j*hiM_fS;v)p-hHPT(qnR3&T1W@*is9M^PP zm5`EJV*5O-edsS39@uMk)P>NHhgTcR^t)siSWPUef4Y!QY<@H+%Xg^}1kfioYAss$ zWpBUV98hpxIRE;c7btHnFN^=3>lCF!C(!)suG%n!=gH?eeEibsISY`VFS4F>|#c(w}aTSdSJhzeRy_p&8p)o{*j}n-RLm;mVn#RrYg$3BbI2) zs0SbW`aVwe!zzp%c4QHPjEm$Wi?N=^wLeO-;i4Fd{QLwq37X$R6}?uMUX;o6LvHzoloC$a*O0C4xXov>Td8U#4-Z?a zPGbTDtF77+2SUqd;b48(u7F0L8BTXxeu$Sotd2O6_eRdigQc7Kc_>iaJ}=vLdx<}Y@b>>ZZp>&m_PUYJ76nztCx!VU@^ZJUp2Gjvpk=y_sG3)PlswBLp=gr?ME=7w|P$Kgt_(2<*OJA{NSkm9{7Y*Gmxq zly^#lj9UTV%9*meCogGKzgp}tEOIyf3T!@AeVMdUG;33=U=3 zn+uUZN1Ije1hz}PchMDkZz&)rz}6G6>hcq;?Vux>yHb+YLMLJ~o2w>JZO&BQmfix8 z{T-#Gjcb40zrGPQ4xQVSb4qSQnTFC!rTve6yp5mK$s{|D@#Y5!t@%~1hp+Wy?Nu?k zX}6CrBWD;mh-6*cjiEYP%ldi(Pk(Q^wNoq|*TdbdJoW;XBUz9$ysWkzC%4W0v48Ko zJ?15j{3J7$t*JaYERa0U`^J|Z7F*tQIUXdj?FbiXtHaV3z+z5+VG4Mxv3cP6ajeMr zOezb>-m$pLh`jBL5dYWn^{pcCmUOD=*_?sn4n}If_@S@q!E$uY>jX}a2oet~n|MFB z7*MWeUROsc-C|=vK|+oI!td@g@zN(oA5ubP0yQy_7Ey%AW#pMZ*)PDgUapottg!uw zvSC|1>ld%mS@V*8M(cLH#ZxS`iNEUp2;hB6geLo#hB}~V>0dR0kvvqJ9RQPNkCMQe zE+pqa+1(olmPpE4VKq=!VUl=H&C1VkKu8ROx~!ZPH2%;D`WG_#PaAuF&JLyAdfzg% zfA+`pouAV!W79+X_q-pw@)I80w&(qwpAcN_8RA{JHQlmpb_||x8N2dh_WhNg(_=0B zXUAGn;0a#KHu25(E&t8q|I0gLBtS_(UD92?dvX#(_CDK3RgvU6p_Kft*nT)qw&U)- zI2u$MBbV1%YK(8QRW72QSMU!IOk-Ia;ymY%!4Q#K!;zh*j|X6UqIq_o;+7rRg5`e} zV>7**EH;_>-{>AcyA>0WH*M;HHYBOMFz(Xj$rBUUx3@UKFYCek=YhP?V1X`j9w%qkMuPw|4WNP6y+Xuu_&cP3DbAUdCv=`|6V-! zv%8Y_yNY)0RNu#seGfjsSgbH5gK)L$TehUd@1u9#=oTOfeR)@{46r$>@+bZ;!ItmY zzd`J-dpT26W1>}1Nhz&LmJRzeTt|ZCXa*;`WH(1{$up_H2Ok#`FV_{=yIv$2bfcOf z@YbxUDd~vv7ZF*dVPdj0DsPHgu#ZK}rddGzh~O;y;s9|=@ihduq!#0o)mSe=O|RMK z!0RmjgDp_`z%rDnKB&zxXyf1ggjL4`DM@j3s?(pTh-rHayS3PZH@^56*ng~&Aaj>j znsZo)d`t8pw>pL{g{9_%Ov4-Ed}-n71xcsw9AfPC8=TfsI$uglKurvyakTQLx@;QLK3XTv?75c#>W2shr5PhTi0sP{r%LuoDK z;}!!vKCgg2;@L3t&n|Z9>8-7hZX1JbM4jyVxy!`Z9C!KwPssF@On-pHY0qSlVbq)O zXy@r8JLh9!;H%-`Fj{pnS(5*E+!RbwydAx|G$g?u7CFxtw@z_j*)3_bg)e;7T!DKV zZx1LdLBdwNni206`gPY*{3uwMTn&8g@rh7FQgkduOrJ-Ohm732XMAm_uv5jWV=YYGLqwsiQC6hQkF{R)nA0o61k zz?QBcRcwz53$EP#a~;;k#G?-LLW-r-V|Ln{k~*7OJGEuy2EQHJBI$F3hc0U+HZKYu zHyu0}b9V8v-2vOW$~7Z0@im%875RTPwhNzah|ro#~E@Q{|(Z&|M4c8_DRs zr5>zxAL5_YJ>QTt3se)g9bRwCiM~4rR2Nl0bnKKV^jHSzsOQ&}6H@rO+NxI;6M#t5 zzj4X|D};JsyFoC%sHFu~SIW>y0-K=s`ckA<%M_W(Kp3s9qGzhM8xv6EO`hnxx?jeU zk@nswx}V|3er@dGGsvN>cOXcT>Qm}g+IK#_a-p!$bkVR6S* zZE2^r>qam4K9+~md0>q7QNNvY22JMiT6TzVmB)LQ9CGhhv~YQXi~6=_$l#Z7hlp3n z!mT06>K%FZd&DbL$C!(&Z0gAE9$@ z0Ao@(V@@mHtg7JIKAIkoviL-xBSuA%Eo^K;NYqd$dg$%_mo6M1l@|4sI`O(O=mS)M z*WfbgnDt)rC+dNM6LNO>;{)z1XVIQ(hx*JvtcmGC1aU1QvAXsOg97uZm9*9Jj4wkx zTlpZjUD*l~2gNhbsMpT!Yng3ex% zJv$WUMQjz|EU$k#!bT9y$4Z(5Z6-BBlHk>B1=3vIJurOF!AhlRQJ$YCR(tq*V%x~w zw~TQuLRO-T?A{K}g+Db{>Byb(mA}cmc)dGfdxxmV%^9~wQEEYY=AuzA!-~_*UimHG z?RgNk@iVsKGxs&E-f5$HUNHse)Q7Pg54`+qv=wl+Hrgm+ro62oi-9Yvqv>W?5Hl~f@Q*Y3=-@W4Je&)5F zLL|n=4bLx?388A-m_`mIxLEG?SR_hJA9Vdb$FQq&k1}!W4EXu|7Vz+SqN(3Y{W)8R zeZ=-v`zMgEmx_yfr>ZagpQ@EBD|7&k0KHp;1?bO9{V3sV0ljE4TUl0%ph)A=Tmw@( zC24C{YF>tH&d9CiMvFzLe9RekAAZRm*4E{gyO#x)$%)e+(Lg|dx|qHmSquDyW4L(7 z8!pm*V?TM*5M2=ZD4s#I-M}pTK)&4sU(fJ$e>>_#X-YjT6BO10jfwzxDZ}XUu~#_M zy_D-E6(sLbZSN@)jF?Pmzijmr2% z-n;d%Y1%5IeyQ@m@l)zibvEyGZTlB9()7$pS~>0+5bWv6oIcQ#y<@xKL_3G=Ccr-k z?Oe&E?Rj5;Osf_X{c0A~hJv_0hWy!n-3u{+V_0ZO0SF#Nbb;AST^>tz zLI#!=Jdzah&oK#%{8BEQduJ&Eden6HN0-x*Ly(XtR5$JxbYkA6Kkc`dK1}r{+$c({ zMZik*Nz;`G8@!ap+cxbzyT{STo2z>D~2=`QP zX-~g7oa-RLWVBi<+bL}Y&a0JmAwp!{zK5Pg6>Un=y<=rEZ19eN^;esDts9~MPzl`ryCq-K0Hq~u`0&hfEt6Uqhq z?>IhCec^IH*Cdbb>ZpZ87+9$$svW#5%wapsrc>?Zfj%y&J*@xudQye?W;AN`O~VR6 zBdDH3%QdW^(pBGDhHudSQ@GS(h$(``6sL@{H3-f+_0=QC+um2!SaW-rh|cx-(Txu6E{`&TzaoCOgoh z8bPE;=Tpy*`VaK;P$>;@4`pNC^p|;mdT!7`HQq8=(F<}q!&!D*(v>n^&MUfQlP#T= z8jIo1JIT2wBt=bv44 z1y*(S`VK|Zz<1)@453Z}`0O=4rpezq>LBP+l-{T6^TANwda1B((gGWTl1Vsjczm(G za|k9h7`wEF-X4W6$89-M|@*W=w^NYeiCo-d=LsQe{svE|`I zZn{(?Wji0InFs})Uf`%sH=+jRs1Uh!KxbyBU_%}Z)Ue66t%&Mh890;SU>tKe7|_~` zQn~n6obghmRV%6<(Tb{FV{TGgh~ulD(W>yebn^7y{K8GCM2k1qy+~VAa0f12KC%qs zTTljX(MjoQ&%Wl>nv}r7+?|31R)5W_+qCf>`luGcZt2v<%`A}7dwg&+a|$_x8fpLV zCdgkBj0=_~`B4^W24MSo@O(H-CR+kEA{A~-kMo7dN6VQNsBF1_YYXKkWVL$rK2qQ3STOUAD-5Qf??VRgWc$!4Re4MvX(foB<*xp2?q}Vn$gl;0CO0M z|5!?*&n)$YZ6yl~I=Z5bZB?)qz+{&C&XCG7ox)W0&`WCxzQOxEMUPNRU^)EDLoX~2em|-1VZ2{aeg%3M{%fCZ+!H30dYHFf*>~PcIITGZ zmd*jC#-49OdRnDYnTJ+>euK!y53mS~T%MT5r&*qG?{5A?(O>%9S!mPPZ1vAplQubC?#m}nL;YMlDaKDBGK}W0ES%4^a_Bc$ z{b52f^|h-~RWSZOO$Rb0PiTD+_T47*2XOK{2lEdf8iBq;NX6f;ke>e9@2}~wOkwhe zq++45VnE@eyL<{P-&4@+=s&5jgZqVPEE6{lOIV87hC;7TU!QK@zJlQ6MN(ZTWyF8) zvFhIL$!dU8sh&Flq~#J5rC&fs&~86l?7li|f>=k7&%!kcr--oz;fhZ!BoPm7GzT$S zMF0D{jmp`6R~+X`GE+$|mcVy2rLBg#*5EdTBsocoGwccSE(A^8^c+wL_R*G8p6=f> zw*PDP^cZi#cV0M-N}WiWeY+qu2g?kpMC+SIVxTLa1S^(*_9&C$EdR1m^Lg zaLNtC?2?Jv44@G5z~RYBH+K7gpvbI$yp;|~k>BbxCQ(O&^80%h54y%-sHp?vvbP4D zlTQGZYI=NtD(l0-7zHJm9x_+2a?Jh1Hg|j}Q}wSU^*#MR7q6x3-cw%|J*p1AbQ)fv zRfc_pQWXf6AqBej3l5#X`+y*pYH}f2H`kOSSNFz8Q~@|o>qW!H4Vq_Vs3S7~q~dn% zi%fo|W0Fv)oVQ1Oi~r#Lk;`}Y{-2LT*a^J$^gce3_tK+g`SM_-j4G)hY~@4SmyW`M zLYNBGw=y8aEnH9-CxaAT;aXF@*IW>A@j51g{6zuSgpdI30I=V)2MLT@Oo%Bk>N7ce zfky*yel9FlmrO$EOX}&d?&zSeHBT==%VGT-uh2kQJjz8Xruk&+<&E*`G`&Gn0&ri~ z27To#ylf&MHT_-`^1T$OB!*45$CU*R4ChVh9RV4xrymaT&%J57i(b4}&B%~~v*M(@ z@1|+D@z(JKcz{3sAWl{di-tzU)A-Mb%QbTF9ap}1IGAB46b#)eKwrPzVvK8vvq-MvTF0* z)&VmjRzqO}G&53t^7iGR6SKEq(s%wY!{>99eq(~W7A|*g4ziGBM(V{Le>Jmb}YQymCn$ew>kacj}JRL8LyuquS#o~lq{4?(_ckvk%<_Hz`S^yx(kVkJAQpq8c@NLF9}&K3?37FB_3U(q&8 z7>$MQu@fQG&ZT)pSm>b>6fTia`>EGXtlh4D-E`aJX(Efd&48Qc&~~K$ z_NC*-xMEOg}WFwOXvn{{D)U)GVm(X{`PB%ZzJxT zK=`JXItQyX=uUrOi)EHyIkfyY>8p5|u1(CNSeKi7xLqhxfnO9#!lmO>=|;^HG*AIA z3BN?VG+H-ZDeM5%Ob}|Oqp?OxqGJW~OYcUXDMcR%&3?T;VHAD5Dt7kmT)jp=zYjPg4@ zmy4b|-JHsYDfG-{%P2d*RrW_hB{h>*%JRZdh4wnYF9_CZKB-CB6|Xc3q>k-lOpBfygabejh}r&qHc_zY=TuY z(qhv3_AWX(GImQ~0Bf_O$&(J@n$bWH)#r?zr6Pv{O;_X?KmHc4wbg9JGQKI}@r^qr zr5?ZSK|=iRrkx8`D(0BCSgz%zKsW}SFrS@9i~3N=R*P7lp1JWel6yHG+rrt0uq zUj&q%2XcYA$HwGrg#AYg9*D2^hUE};KVH3U;z!!DXD=vjKBE6yP4aO5|J7joKh7-6 zpebjUiyYHQOjI;D=$u)?=g?2LwxkLZGzQG9Rf7t=hgfP@>$hM$@7#OemJs4t0z^a3n-C zZ8rOq#H~R4_!^%~XoGS^bA%y99;V3bL3ZJ}=jlx_T)Zk(YuTjSRXIdKsLF$Y&*;9bkF_F4=ZJePF zr(k23f0{r8n(|oxdy|Oj-t^H!qzpJ4e^+8^zYPl1SsItE>8b_&>QM>63N*Q?6Ho2P zS%-xv3o!9ScV)0Uo20_Nu^)AEs#z5%0%i8r0W-U;(W6L6G6TQ%>7J>d%QT;++%*|9 zX}Q;kGzXHD3MZ$3f_skaysc$zksE9%4no)>w64ur=a;zJKfiRHe@rehy#m-aG4@vO zD=uw?k(HC<>696Ln=Xg&z#QS^T{n}P9Dxwmn`tlZsPYTByVtaL$sTKskngB6&4bK(&IFQ_z#;)nrQe%fohU{ zn@o06RQ4r>){|FE5)a<3ZJpjGY5#B}F`>P>QL!wjS`H=8T}P0U-m^oj4_8r4Ob-tT zyu!AM<*yh}V(4`tX1m=2R`mT6#8dK(&rZ|=zWs56CW&Rd;;chh#U?qv2GUS*E&gj!^g-m z(L-|Ei=!!*5L{gjzGfbOG`4r|B?F(Adl2cEG-EvsE9rpSOENrcx!)zQ0D-X3IxZ@p0JKi zjt+u=A0rV$p^zV-9~xbpXf!>Kk#Qti*48-XosR+URP1r9E|)^~rdn={Dz7}EByQ1G z*a(&%DC=DJwBL0f$INaFpe0QGN4aq8#R3Tu`Ow{jd@*vasW~r6TC!ids^p45 zCL1#u&ot5vym(1vAr)yz6qQL-iKl7gqM~s_`~C>4eDYZt=y>*Yf8r8xi!e*{ z!F~4;dO*`pTU$WBAeZvPjkqeVWo&Y6jOSBnvc+&0j{BjmeaLlthAl2ottcxzacc0t z&gcH8gzNuy2-AydT)@}@sN(5?y030o^jkXA3m5qvUNByQH7lN6{TS^Sc7MWv&q9N! z7*$w!Bx1^nIw9hONP7H5bbCC4iKkjj3ZvF6F@RGdal(X4B$+(i-<+!}KCdbVlqZGV zb+9-fXDk=n6uJ#3@>x9KNi$W|m@S9_R8{MT0^@f15O#f}_}rOenZy?DEr z;4mf}X3)xy+9qS=3@2169uG=kNE~l}I=<%0dgYFlBh5GZe=akv;@)wJzXynShDAIt zc`KP=O|=^_*$U1KFhh?s;-^472VcD+cw>}01=O~-00nOk%)D`TACbuHj)Q8EFmYk+ zjqJFE(dI3Asyp)ImtTKJxXAc1BjjfQb$e!&+(YKTlx`Gi=)g>ZF@9Nw>DEQ+N);7Z zls5_%v`~`6g*?XgTBF)#zjGRk?yzx?wtZd@%o*g>af`=80jbVK{vQK4r)Y+oeo5~V zpwge2Ha_u?@|fH*D-^_n95WgPTjT1<&bMAokv{VLMu`NM*u*}2>uBjr`Ned!1~Lt0 z)W%=<4rbs0yWqX?jlD8SHK+FQm%d)v+Er$GDgG`{iiLe=i}refs$CprsZ9r4pf8S| zuoVZ0Vhrky`Nk+I=>NfxhWG^*c~q)|? zEgVV2BZubn2n7ZEAUyJ)0q-}^F~hO1+>Aoo+B~XmzPy{wu+FOlX(NQ|G^ApQhQ@@y zWXP**)!JR%eR!UFImf*LHVXPfTz&e14c#g=C30ZB2z7GiQnJEdRg2Y}PcGaYtuAe{ zMUtuiiBI(;RAt+_;8A;{P+^JnYsFr~A@@}3uuSQHzA!XfcV!V-ub^b~%9`cr>KPOi zH!agMxKi9C^b*C~bH&{7>8$Lf zI-(^1q!9idX(j1gZEf9d2)(7~JM}F#z^lZY4Num8J6P7Yup$s(*q$86!dt<8+!^^k;eXqXdjMyKDR>uQO#Vyw2!xpIJPhyu^|Sx= zd6-w%#xi$C5w5&UO--BmB*qX|k5>qz5IH?G>2>wVSo>X~$2`kPcS04OK$f|sqmoQ) z0?_KkU|)%XC<>9uC1E{&%1&FXr#_}FxxJpUgHLygY`{z`cXZuCAP_IQN=duq5wjl$ z1Ac_<7pla%^CpM)gGLt`x(C@|Ouh_u_&e{lCl}*$teNl}?O!B5RHT!N-E#x!qMqIoR#+$Luta*f*$jxq%?ZXMz(q(BD_3trbw%1+fOUxQXPO4PyU+EVMoJw@I zlCcPeWjz0-H1qDDHI?2+GdQguJETHNvWyvmnVcrAjqHr&t^of?(Hw7J93e0+5P&E& z%3f|fKuvdt@4QKB>I<+#RxaXx^k*6@*0>tLMdQTsKvsUPj79b7TJsaMmx4{9tc9n(xTKoN1lT`juyBYdCL)bo2463Dk@W2Px> zd5$Jas+|l_vA4S%?wzEQxm!(*b;YyPg1U3(%g8M@lN^{@%Oe`VguKg%VF|z8VwhnC zuO{gga8%6&=OT|WCch;;KgvOAQAKE~Ke7^lH*=GW!+L0dNnLwYhCiRzbR}#_xt47~ zEI`D>N6)K0S=gmWDzEzUDMT{6L+*;|gwh9L2WY-3QzwP#K#~~tN;(mUQfkKS@{nI z=$GB@w(u!H=>xhpdD;j`@WoojEmkSRbKZV`Z)XW)3o^d+D$v_+w}j-kFTQX2C?Dq= zSk|^!=K17;WBU~1BOR4kYBt0LMVQCS~{CbXELV5F&Q(*XY`gv70&z#}`2qUkN$A_W4`}HwzUn z8@OdvwJY_-%iy`@uVj;;tUkoBm-E>=3u+?XYO$x=CV}bPl=!wb%N(uCjjGFFY6RkS z<_J8Z5^96e4>7*MqD{rJ)g$f=;QErnE_IsreQyrM?)c+hQ}dV>!M=$HJaIS9R*T~l zIY$2ID_#bm6On!^)X0VK4%3LcG-W2sF{T>V8>rpg7bV)|rTmK3!JR4wUoGgkD}Tyz z-D5$$L~P6Kuq{wA0%*YKcK&AVf?HcgY{thG<%%5yd=qbKJKDsoLULwLPhWt$!jauGEMYb5A2f1aJIW5$HyJyJ3cqJEWd;t zU772qK21te6byZ5D8&26n{^_nBXkUPo`BOd&`7$t$JSZ}TvkrX?^6f3K>>=|x1Nxr zH*RnHy;)ttidbXcnULMDnKd&(ecIly~&A~b4+uG2sRWMEs_`*#xyth7*9JaFYx zF^)b}NT3ugzDM-nj}Z;3;R9m$*HJMZ_BXY-Snyh^H$CvGI4p$f`Q z9o+2}3-;|Hsl`68h85##nax~)fFm>>U;rx&AB$%$Uwgvdg0v>NJCzib!$T8iHm{)} z#i0SmmE|(R9tuzao}SUKrhr3%gWltT28Op!9Vmo7>q6wZsg;fN`LsA6d50nY1l9XF z3Sq!FA=`QPs*yKDkzOTc<_k3YTlXXlN3D|4ibi0`Nr?bG?-D5=1&d*UDchFwWu*1( z`g^i8YM-(>IT~I`KOgHu?Q(hZSX9r>fkv=06_L~&;^Bt4m^&qj@*CZo2d6d@cmS~` zj}DBU%`m!--S7Ydb+<^B8z3uGkrm)m=x%M^@s?imr8U?Nds%@Pt`a%+g~T~@_aPV2 zYoC$)Oca(3*m-vE5bv)Z6~*+U~PUB;$~fol2VBfig@;* zK|Fd7W&_9Mzhb`C5MjV`QTkJ^-JQz4gD6+2uPhhqoqcl) zZwMe+nuR*H;tvfMpFan-)zoyW1{LO;{Lt9(_GsI1(5HnVV=gB99_3c)Elf2QT;MCN zO}liFjmlV$jz>`n1DYv?v1qDO@rfX7FxD0FaTtG>l|As)Un?XbNZuZx0D!;$ohS0# zd7Da%69t=Kb;Dx*B#neWYL_GhumDp-fCC0KsDbEwc??F|zxCjH6ae3+Eg!=fcLnc? zQi6Fu8JY>-V(4(=5Kpz?2~Y(fz)`ELk(A5(^b9?$QzxbDd9{@@x&*OJnZ=nUOT6L7 zu_M3068bV3)-<8wL1+9Mxu0^H+TO`eECCMeE-xrKBdL(uU=|07OjPQ*p4c;;%L$NbMadLKTVU1xKff_g?u54Kyq`Z>(D8lGAqY;+OA%A^*ZM^1%0)DCre6g6-6c8(Zs}N^M$NYl@eyp$ zu4ML0hpRCOtulV#lgt}hn#MOsE>%#J@l44EmukqMVnWpFUToq5h60)hJSwv6z!NYOk$nWnwcJp($|0snJ{A4}%Pnf9=dC5HExh9S+fdD94F;fD_|jx? zd6?#$tf)E$24M3?!O;Kyw-=`R-!M18>mDqiO) z=Be(7m8UQ1lq+)Aq;tG3_92q=*TK@i7s#r6BkBbFEODjvV0RJtV5)^{ONx8QBi^Mt z>g-gPunv=v13qyjwkQUfV3f3f7)jo0@qGPYKlXi0)6M!EMD~mq=y6jXPka^ovf~tj zM+i5f3yzISTok*&B?cH@xATiK<)VRFx+*lj2BH57>r@XLn7KF1zH6T1*z#E@V8P`< zay%wds`b9^r2!tDmCJg3#^k90Vm?72bSeH~#n@ou_5ozE|E}V_}L+B`TanFxiF8vt+cM6wI za@8JPDlq^=1aIot9syLymugOe;DLl(jk=9{+fO)J0Y-r9OrP}#URCKuSK$R7>P7o~ z?pk#HBKm7I*<{)!j(^C{?w>M@QS2j*IiS8T9p@7ez~+@$zh>i#G?sx7`D0>sgO51I)S}BUkUcbDSBH9FJ!~Yb#|^Un*86Sx z)lZ7f^6+;$EV$pRA;%T*g0^32Yu?Ir?bSh>W;CCt+4(c_c%uas&*;G9?J#Oc=dKAP73NwBk!^fDSkhSzXMi3kPr1!94ZBRn{7 zp_AP(jPnrrpRz@>T!;Am>e!MLz;$mdWm&hgq@>>(V0O?q_WZ2nie@e2S^3Ab=$0M- zn!~i9a-)G`$-FBVw2}!}$;#~RS!FIlPmJ zQbHu;Fylm;BG;|=??u215sHN2EEERpUt!>yq&(YBDfWluyeL3fnxq_1p$zyx&wldT z$*1e8`&tIo^C7{XWl)_hP(>D6p6M6uXS+Ss(Gq9jYRKo^O(D~IrD#?_rJkL6W&@Mc zG*(doBy`{R9~+=WAk`=TJ)OW`#Rc5+$(!`;e`g03`9n4hGac3D1@h{8w*ZtptfAWO zih0l#O)f@k=e11M-<0E)D?;MR74Ix>Qh4R+O;{GHSJH2c`?|i&-_+tJQb%}|b#4y_ z9q?Y%Z4)4V^`+gE+bEv{)4DAHAgWrU05}q5S)wi|gg^9K0Bl=TEz!)+V7`eo7fl(X ziWm(Q#? zzx)PV6ZFe?Ta6TyacvhwBh)9uomQm4JJtY2HeI6kL6n#;KzAEUIb ziEu`S@aDhHpmI)^g`r7bmmUGzDl%B|)J)*G_?IyO`*;E#a>?135vt}}7;gyyp}}zE zK(Hc)ffX0%O|+j;7}}zEem_^u3?FKJ_xeQJe|Uqqa}~|G(cD9JQ2F2-qHgb8qyQ?c zwGiL%HEY?6WU>STxL}665o}H8f$cFrS8^;oy&)kW9h^o>kva`8e$Yty#33Lj3Z;wr zB0B_)V1}BTE7VK5;FZ+K1u00sCZcYifMoB-i=~g)P0GA1gY}SDB|BwiiiD4ZrM3=n z?@1gY(Q(j8H-;~(kDxF#BV#r?I-rg8z5(0KbxSx4#VY0a3=J^4#Cx&M%Wlf`d!*3- zQJKC%4=JRR@)Ir-*77j9X2y82CA+e(kv6Rp9m?hz0rdn6~>2jZ_(n-i*>j) zmy+Qlfg=`-%Gw_fd$k|$rmku^eNg#X99*MEw#WN_7iYBg4&Utm0HTCFxI zcABuuLn3)2r1>aN`HhhND3E_8I9^AmU+r+N6>U!RlG>0%#90=^!6NwpF}*_}Z*t|16u$>%6BtaJRe$!L)Pkt=C+`fuuI{ z@bNFT8D*4B8p0BE)-xYPC(M2`(sJc#?tqC`Liq!Q5a&z5sMS-OL|e0g9uwA zRt>4J)O;N1Fi5FgsqteA(ef4R(FYb7kN4qiY=4bmT+H1I1B$6#U)a%Ev zfkQ-xRHQHI=xlS3L&Xx-YVL}&>9c8((NqK!r}B_X z;goZyb?Q1qfOO*OgH!rt*c^OiaXD#7s^C3WsGg-H~gY|4KO5w_H zH_A5|A@uW6PzP`nB3h)S4GU2&+f#sR-N}IKCZ4x~v6>V{6IbR#i%Y0nbBihi4{-H7 z%*&$^y9`4ARP&Tv4%1N^o`Ig%5xOtjTZtVvV&|IUJwJwGzppvINZ7YVZg+UBmD;Bu zAgP%J{juf@0SM%-SYB***|_FdTisb8(Xr=Zz%jtN5!Nyn+Ps6CL9V{!zgV^mY)bf1 zfbweC>iV>}{X^7Cb{P+K9;h+^!(ad-BZerVUiX5){Z9(k0b=zL7ub|e{fv!@t17=c*MC2jG6yScZmFMeyIivh`S zRR}$*qv7U%aEti?{he*wfLzb86<;D+MK#bco}l7zPv6VAMQ?o{LDf;s+Ve8Bq$yfF zo_~HdNGkakeh|iQX->_cQn^|hf~nv>_aTXza&Y)x&#EP4w^YPw1^6ZxY`0PW`;WlH zz(x@JsVNF+JOyZ6!!L&)sBVcZhxFA0ZS3iWQDNxGUJx-82q=s<@!)0o+}zP-+b^w( zlPzS94&yDpPYQL+g0J(e4ba&4uv!t8;w3Z`RO6uYeBs#gMu)-16>Z zrbG?|+)CiCl~1MQv?29Vnt&@F@r#HDK82!9DHI@GFu%td|Ifhu`0wt1KX8!oGJKy4j*G7!?GI$ukLH)4q7uY1 zZ2LCVJvn(@HCgnGlsF>EvNV1Y1jP3=XW+Kz@(+|uj{xJJHb>fBI2?J+=T*Yr<_4{- zff7oNNx4~@Jy^;85Lo8J>+yr!X%RGO{|G5NKw`1nx?x>8!JGGv0`jpgv~wMMQn1^H zy5Ixag&tVNceyCE^R)e$tDVC%)Wu-B%_kKk9RZI$x4(&E5DYseOj=5hsp5V2vrz6$ zt!RV#Lwr~%1#Ldf`?@jE6vQ^5r~%me=2PDbHhd6Q)e^fc-M88;z&8FMU3!J_i?&T2 z|$y37(_h_{@6vd zZ6Jq4i>syc>$aRO=HYKm&F`&H zQMP5%j(6(%*hd~JJN(lLfjSD15b)5HV?FaK?dqku2C(vPfdV^TJ%jd1&e(6HNdR2h zb$9%QrK}jBKG2|)i5TR!+*!Jpp{ic-+Vv!&wr&EQ?T~cfPbbGKd~yc8yfShkkbCX6 zXW#Uo7+Nk%yU*!%+i$I#Z%Y?x^7+P2MH~LGCzdC^iPzlGx2q0}9@is;do&9hqJVTY z&?*zVv8isI{*Fa!kl*!XcY2+eS;upNmK3>}BD?jq9-+GTl=ER!Y~K zMtBC+aNKYP2w;rug6l1*x>ak1wHOY=$$+U^Gw)nU24fd(6T1HV3fi|JSgED*;*;?f zt~fzryn5OUpgJ;@=sX9z}?O!RY{XC?Z;Yv^Mfk#TyaJR7^diKVVUDps!~;}TWpd2 zoh;5YIqKqL%YS-*d3&6WAM`l5eZgN|2knilsHv07F*9JIED zGdssgMwXU)Q>~@q#WDhxEOStcJu%s!yk;6T|Gh?W*TA9y zf_Xv&YsSEFr&pkdh#|LTfn(~Rr7(--3`t(JD8ZUUmZaMKw9>4EzJQF#rRoc#4t6Z! zMcJ)WGf+bo6#^n{Lmy1y#nUhsZKd+N17D%4WTgaBP{A9u#v^c58n|burr9(4RJHn} zlz+zjDpYm2enG`iGudR}7(=HX(cgAsSy_R6j_^67Wlw2A_xKeSig=<08mqq0pV#>YQGVu2ZCg5d>BEH41pmZdbOx6TTvn$q9- z_H|LhVc6t=76SPT&*?j|Whc4#-!dBwucEfwn=8w$YR_q}4a(AgKOb)lmr42?UEArA z6=LSlf^4>yTf3ixtN1q3^y7Z>?_34K0b|(08uC9)gX-0Nc-OK^dE)DovTn6O@5FUp zv~(C#`d$z4rPM$-6fQ?_DxwSe>fxf0etT{v7tf=x@GYcxZw1AwC^193DN(bCR%! z$wzcn!IDRZMH+r3&F(~W;ArA>j*gtLA2naG-RVoR4)-PkQJ=VHqWP8~dt=ikP7X&P z#eh-rE|Fhcc)6Ri#<&zQF)ZA_Q_c#)q^(XHhm9o1s?_g%`wwU?Y+SF9oi!uA&a;F8 z*Ocgk^8wC2JMY zCA>2s35MnHv}^ag#as$)C8KR@f5GSTjft->gJlGLM+gYbUo3#@lKfrs;|PHAxG9H? zb|G`;)35)vx-sliFJrk!o@Zf3lU|)5L909b$JG4#Ol_=H=EB425k%%Z@`>s5mg;o( zTay+=dBTylk}HmDIvK3b1`52|#<8xX8;m6@9^H(}*L>1NA-RUrlmMF_z)&*@k~lNx zM+jQC&!Iqnt1tz7sh&qt67s3vM4(evuZL7}{iuV~v!Q~zCO+0gH`tpmcXr>)0$3FM z-YlC?my0>Fun4PzBPv82wRu2MZoRzT(o1P|>yV-;&G9&r*>%_(XRutjpOG>L1B0r| zCg)LiPP*)nSuYvGfIQXPQK{3a+8+}Z_rEnK61vNif4_^x)o=k#aZ3TfD#jZFXJITj zwwrSWygd)$+Lzv8HyILOC*+}~E4MJvM>ywA>910t=vX@B**l0ltXJ9Dg-Rq(Pg^19 z)Xa(o1iF7>^}jndi;s0?@!CH4ZUBX?~hk_wW!x(26WUXv=jP zxIafdWQB0v1&}3-pnp!$cWi1!uu{@Sy9&HEWFGpWDr|xochl3D_|$iW7aSj{+@^Kh zq|fpsxNVCEC#w%%6H|-(UfxgD`67aq(0x02RGIb13bl3DDILlZ_Y5-wXOGP*e+cvE zb<$zUBuaWl2%!#AN*NIZ{W~k1PJ8Z(W8ZRjC|E{}J3++DlyixN1HyS>oB3J`vkOH* zyU!38qwHaZs1;|*+hV@#o0NU2uTT&-S~$c#p?Lk{{TKIpr9*b1j_qeaJVFz9Y1U{u z{Z7xfCy*}x%U}Fq8LT>U1cQ?0ODW~gYFDu#iwITMAA0!g_+5zlS(8(DA4H}egct^? zBA2J3O69`w)B-}M?kcnhmcY_1o}?5049_Kx3<<#9QLVh{nl@X=>?e9R1kFlgus#1e zfUic?LDLFY?&2DUPv&zaI??N*^qr2+!{}tol0}kts>FNhfRqqbfIGE0V+Q?L)D8wf z=8AGs_q98EJ98Tt#H$k1tTqL?rqFpMRi~4?x100@=P|0!2*Q~R|Y^LrS}WzoA>7w$f|2ZMEB{8GYqT@x({ z9|&}$q2f=At=qcEJXCu-cfuiv_@mnPh{#;`d@2OH&<-!gS2nLb4ErAguN8qP7Fc2B zisFjOKp?!0h9l;W{;vlRoP%l>>o9jPIC?H1&ub5l*jBJN{(6ov4f+die>*Kbaj96l zCgjw=$HSaep{~8z{O1n*A!uXMP0J-lZ`?`pZnX-*=sO>4a5UGS>Ou}%(Wx8Py%2#i zM^SR+YDaH`X563J2}*9AO+6Y2{&qQ!c(Heh=k{*pIM~^s-)il;t1%CC&*m}+rV@4W z6t8!`z2?IrP9Lj^n~qOO3Lo{jF1Je9xd>Wh*5R{4VNd|8KKA8K?PA7Epy6W^IQA?> z^&cpnLaWb#=b!f-s8e{YO*kE_EGHa6U$v0Aa{b*Yx+4543>PkV;-R8@@pKWL^yem)MqOG|NIdPApm_Q-voh)1hIUWQD(PX zLMUhVni9hrmXif>y#7lJF*)bLav8^z96VAH^)f@-MojMX%hzD{VoVvKjDGd+_~&e< zmUR@L(HY-Bf2xvq!1H;-}RQMWFj zK!pz-JRyZG_M7yPu=aun25*Z2=NaP#ah2){{o@~=zo35#@fHnh326N;1?Cv$dTd$b z7b#@;v&i^`19k=mO|U&6O=ene+j;lj@aIv%-)<69H1v4z;IVPr($D=1d=m@Cz9VJL zk%wTI8oo>TLj*HN^|OyruHU!1njlsHBnzCcc1GI;K~x+Vl@S6yc+-I9cxZ@}7Uk7j zJtWVL3B~(;io*D($g8zcVFSp7W_+zEkb~Y zNG)0ft=wpNR$&#CZ*1>3rm@eC#0`cB~=%Ca#zg)v?m+BASF>5Mr7NWyc3Gk;8CD-k z5uHuagQ%Fb`)fO^80Zgd%OU1v9aEHq1}x!EB12S{@d=UPU38!tPz+%i1{w_{3Rpnz zEYszZ#v{2xfI@Rz$s&ZCV>M5-FVa^$hx$JHY{?{CJxzm;esg-a=Iso3)wdQ5Y`9=)0I@8O%C>a@gy89oP7otrt@f!d$Df)ajBetL5)w(kw1< zeNtTvmd-OE0j-Si@AozAzR9Wc$z-+u5vdOZJONwK)nC<`FYGXvSQnp?@Hu{p0v#fM z9&ymW+(Z8>E*0`b37bz}(ig*76~BL?>sKp5iO4H2_NEA@Y~Tlv%KXbrfMW)8%gh*1 zP^iwAcOX_S2E#?&(m|8UXcGasluhF5rEx-G4D=c}LT&De{`-m=aaMe^?BB`h0=62q zI2vF6c+fNFAsV)1wWL+1vCiW@3O~97U>^M0nQe$@I?hs_Mm(w{~b%h?*CT< zs!@dg7V$+j{`rr${KpxH{ritQL;!wn{QVh%0RNjvzyB62@ZT@~cMJTx1^(Rv|89YQ zx4^$!;NLCq|GO4I_Jjw7Dxv{EfjxmC!Kna%b=wm+{VCaCQ=V^Q?Qkr0=iO?*Kg>Pu z^Hn##MC?f1vk{dgacK*D-3k-b|FUZe&p=e?--l>FXirG675op^g8h3#2msKr|JoOA zF|Zyl?r_*ah^~MKf6wFLRti4$ga=1*M4tsejbscrcoYhY!2fn{ferrzgPG8K$zUbi`^41-c7u`UUWQdVz_a~ zu%A~|{9`{O+|ZjBN9Fkj;ls1x!!xr74+#dV^j02~?Y`M^Zp!0ek57yBo9FW3ftgC( z5AEEIBB+~(QCETNG+ck9e_Tv7YrW=Yc#A=mPLqhHv! zUk$!`kDmLw8*N87t&(rbfAGu1FU)N_$Ey8D+t$PKyuA~{i|els=zdllIjpy0YW85n zY}lPgZy7bi_lV(BoSR|hyFa$? z_;Bp{N~4^r+}!(>8!P?RSB9;JFW8=+d;HJ!&pFrOSE4@``NUW5{N(rhN{Qb!Y}l07 z@w~arsWfhGciQI%jeo9Be5cG#a+=ZePZp^`fdB3VmzmP-RpTVS(H)zq zVmK{GnXdi*v^1+!{i-|mL7HtI4@j*BzS_J@ZrrmnVyt)$orpT~bD~(-c6j$jaAT&u- z@r|C&@J*ki6|>_MdC9Gx3GqetMF?TKIFjb5U$BE}LOmxCRz@!QA-QFxP^snVXefbW zy*Cja{g7nAGmnf9yw;)X2A%uFU&$R1L{c@zCIt6xUvrOC`#mw3kg`c~kYZzMucMVs z-v2!-mB=Tc?2rD6oli@%H_(u_=8?9@IQ%jLK@2JfpEHm!KnuVULF-Jk9mvew?j zXN!IWY*&I@&L1)ap0f0azGm1(su#SI9jcDow!3g$s?EQPnteD;0ger)5o@Q=Tq zBI$vUSoBrroM5NU?PS7KsnpV3p<=j9J$&!b+LX_!=L%oJdu529f!GuOMK8*lIh%zu zf~_Cs2gSE0osg!NsC&ZIm)kfU_tXMyiH zPV?wGy*R~nD`=^me+~wOzu0-|km;2(jxrVptA9n^kK}3n`1*VGl!lG>S%m(Dg^FE9 zp&Rs{S%?*Hd^Eepwlb)7;ow>0!aJm*S;pE*TY;^|T5k5d)O_^u&l*YZ{F%$id^97b2!jZ|yVAox9hxXmsr#lbsRv+f+WBWL2o{YI?mdw5lW6&uM-=cLC$8 z@~MM*;P}xurRf(Q1JmiMEZPeVynJ8R`-JUA^Y^bI_CKZ;qp+DTM`{$D?!Od16YO+7 z|Ne0*Y8~-c)NTdalNoBkx=7<}*2X~<+!0!(Y3`oq^Xnp~to3$frs*{`pVeEZIZ6|%B}wGeshv#BNUnk+CO1}W3BR`ZhFyQ%IHt5&HbyP z9vGamFa}c;)3CRy?ajTgA=!vSaVN`s(mK`UC|9F=haVn38ICmY~3;& zvOnrBHOb6dYuApkka7*Fl&5xsk**hGmla^Yem`g$Ti`O zZRw;?b|fL~Hj^{-keq-bzx5Z+9;qwhVr}l7*1599ehVSQwcoy!ht>{#{n}PIZV}d` zeRoe${5J0IkCZsf!o?0Iki~jBKB!qOYXK1(%5w1?lrGl}O0WDQELm_^+16h+JMmcK%^M;~WmdS@G%Xso zShlr!Maejl=HFih&+-Pc;hI|M9mH5ifhVLv7j%(VGwYIq7Pwf!WKiuA2voZI~ zO2<72rpE2$!SrVpYVU?9@!QfqUEauNBYp$YuvI+s%0LWhjZ}MnT%+#W2S?tGmz!;u z{wJc&;*Rw*LZFO$njn&vWG&N7XIIqU*3ziyA^5*v<=2tO~d#uPhC=S%k{CuzO7f8p3z zi2J5Q5^)9%b*xl}gnPZvQ1Z~>X0yqW z>^!BQov-7jBWvGfEjf6z7Pa}B&8DWPD&sS)>CYUjd#fZ5S*hCOSJtVTJNilpTa1?o z;)GTAlSf)&R~wbhL5&J1l=>Jav*Lh&}#Nxq!^?LGDdiMHzSLF)n^Bqgw zWs8G1pU+tun1@r{wI0il`zO|6W@gYcW-)I3Tj7c+$HzY|Jm?A{&TD%MGbc2!nQt}i ze}4L~rNZH@jd~l;S;m^$1!Vg<=~h~z+qH=%VhT&O&dFuF%2i^Vmug$w%|O=uRWHT; zg5?oTn)9tfrNEM!(_|B^Yz{?5?kc0*VACZupYL_?4SQi}xOtsJKeKf2F!g4$-8t9i z@XmSszHjsPCFcm~0}z&I@Iz3IZBb>o$5itM8z6hZW+D1WS@G?=M^8 zybP_&vmM^IIAK7{@8qAdz@Tji37-Q41Brtt?jzvNr`Gzqt7(bjE(tZ!MaX(mY}5DX zV=9*f`K_A)LbUE&v)$H&lHvv@B?d$wc(z8r2PbDd~ShIT>Ho9)!YudLUeU_T1jD3|vT%uzFy>FT9O;N|I~)#n&XxYMK70et+TTWpZb`&T zEw@a4zVSTQD7Ua$$X@*MkK3vxi9H=|cUqLZ;rv?Zx}+`6NF}Ip+3UMEueis5SDj4z zxa8vLwJFnThfF^0yINVnxBFG66+-Uamzq>C=OLl%=KCmwc8^?1iPuXOY>W3US-*lN zo2>Wf(5em3B6)TVvAB1mvX~mk^te~H+P3+2?ZR!%epB9qLmzx+h-9svo;RI%mJfqwqPVb0)Hy?eef{L7np5 zZEEYv{KlyH_JXdj>?6G=vb77P&fi#FFuZEcf~EdINhzkfx71@db?-$z zWN~2ZiwYi%7mN&bKF2@%DVflV*R*JmoKob(giyH2YyWJDz1#jYRxfM&boOWJPBA;u zrzB8&FBM5$Pw|s*sPrM*-*~4$g z9~w%%ttvfONh$4{kjz+`P*rMs2aH zX7<*ZUoah=8r)y(pJa5X*H?*yu@X)#{F-Iim06V3NyI!fa8@Rw!~B^2p^BR(crHKj z3$MP0|B%#d*lvW<9>Om?6AUX;hQ-I`v2Q*K`@-Z}Q;t1Sy)%+F@mSbgn2VNLNX{>) zev9sR@qPHb;UNa4QAhqU=4aH}73b5Hp3!Gy;$;T#M};GQX2<4f{VMLh_%+VzF+&$m zy0*{cEH${8*4;jnwcf6~jgGbPQ*qe#RBV(~s3x zKR31L25|*Sid?67dM0{RwQZ6%GtPFhd2yqutl3QD4eKY@UT#ITAC>D!QeVyqN5FcU z3_Wcv$sy1v94|{AtLTd6(WNkfSZ|{KQsWNksmu%%qq_Inx#Ng<>xVE&v`_dU>9b)> ztwYE2jkUv>T0ag5=ZiKU!M=U4s)KnYI{+o_uIByJH@Fe(iJKA%{V%(}OFFmeQET;} ztlzV?&ahCY4`#aw4eEj>!t_?sp!y+x0_HKTbaFUz`rN^E?eNP2^nvff$$K7sM>8wU zT1^EUOTjj*O=4KQtle|#k%uvkw4uA7Hj1BLK#+Sx3}CN>_3;L*EYUI~5ienVW3$?U7L(xYP+xNj=fKX8?p%jQ3hU9N;i z2E>wr$J&Q!HoC=oOVYmkwJ(}pZ`R;06h|6*%<(@ zo7ewz!;(=k-2-Kh=+kE-sp~p7Pxyv$6M5!_ghj);p_4moY5OIgIxK3V#Qg}rKQMoj zH#se_>t|Kq_xh|`p;O?tg3}^!3(1TdIQWh$(fGM(ZuU#VV5Nn}eBnRiTBCtyTBOrL z{|9UD8P!zutPckWkkE@rCv+5)-diXNNLOhh(wmAjL0TZ8Na!GfpmebyNKsMg1PD!v zCHUAY>#j`BIV)kUaFV@e_I~D>XQJ!9TKoewxsp5}A5Mte zX_gjt5r5KTyl3Kwht(oar)_qmeE#kgIlD0)aMGbVWzx=xEgYeDOhP9BVEU(`8In5lnP?!-(T7!J zjx;izfl<0YqQ{`!As*+03@wtNQ~lrHAh-0pXB80Xtzj2!Z`Y zXyf*P`Jci5?Jd^?1vT%*3Y6?V_FxXmm(@o(la@|JZ?UnHuJqNdE<=eo7Y>#9+NTHU zq#`;uTNb^URE?(NHoNw&6OxK^4f;4vFj4grWM@KE(T8ST~ zy(Dn7Kn3 zzV9%eHy76-DcS6W%%2>8YOMjmRhk^o)_J7cY@73aZ|WtTfAmI0ii!Gori>~_^!C}6 zAW9D4KeMtavI?oVAL|oR3pJ$LM6D)+-_yMpB6;IPPx=Czo^XBDcdF$0!-}LYlAyI9 z0rcJyq>)6SL^se)q!tB$*#Up*_QosYE=Ytkbud}93F+@}sla8#@s~!^&*c*ziM=~S zlDrgOzec|AaC^yfMGa_Y6i2w~n=NkVkV+l_4O67L zo3I4Q1?JXm0)S~W=Gv{)3$I<20Qm{IWQUorFCMvmO1BihyPTr`CaVm{Omw2B3*|Y< ztte}sjT=&_f-k)=o|A;J47?TM%*;w*#4Z{3J2LJyNKV-F6i`}c1ie*LWdVGiDf_COQFK|q zged8rK>j)^mHb%9&-A&QZy?twAn9J>{@Bb~n8E%X2w4U{4A!ckteoky{`{|vQa3kG zo)O#j54#O&g5SOul{0nNu*I^3kwYMQ=#ZXycYo0oL6GAs8d=`i4J$-kdeQA&ta)Ct zG{ekZ#j=3yn#qN0{pTm(9S(o)zbUi+lOW~Gxhe_%#Pq#8?eEn4KR3?%QPk!geI%>q zlrtl2JhLG9+C-~JCXu6WqDGO$A;@oHQr`r%O|EAe3B+2(PI!-sz^dsX(epYnYH%8A z>@h5A9UrBRdddlk-4eTd?f@#ID+$_8fTwi;fO)ABO_!$_CPAcJdj3Z(0W#HoOYEGx zW%2oL@Gl7h0B1ty0Q9rIDH+b%+*ygQltOD6jwl5)8Un35MS6?iK`& zefaHce!G6$+^q5`MnA#yXE!f3t2yEhk5&NnbWyaEvwB;?X#H~KM^dbPg#8IQAf*_- z7pftocJR6bYOz=I1imAZY;S7+auOCn_wcx@`*2PWWBH>iUB;Vk$vNL2MPBR^%p&G{ zJF4el?r2LA=<{y#D|=sbc%53&{9c+^XtcMZp`HZRXS&eD7R~Ot4tAldhx-^!J=`VG z&ly=Jh78>PARhhqHyU;ByOp4(G&fA>w)7Yc05i+C7)ifWFl7LENZWNna}Hpe5gj&j zT!DXI>R*tHO_PR2J|4ud*(`P+E(HOYW1<4SSq{DTRzvvvF9cL3EmrzXSa@Lpw{;=aw9sFW?vvQJt*q|6~X zaT@eyOm@)D?0oSV+^X=5zCm^JCSd3#33Rhklc-#l+$nLi`9r0y!s#!1 z+BiMFwXN(f&#g>(HvMtrPDp~P^a1m7HzB>}B~Y(a8D%+hdX0YV2rEnXbP!d6F1-X%Kte~te zV3k??frjJK)RcekCP2S_O6xLJ-HH~M9T&J>8WH)1*VLvMrv?)sD^kzz&2V-3f=UqEKk@R38+&X#YwQaY z2DU0R&_&=+(}cPwi|%M>9#bP6oXM)4*_Xo#R*@*0%DPWt%$lwwwz=Ua^DQJv~F)jX}{B0xW#JkZZE< zWOolwNp&{9y#e@(8qL$7J?Q9tdh_~&KgNf|CrP6BH_cn%6_ibTPVYwQh@Ddx&a*W$iLGmIDoZjBXwuenP-K^KKAzj&Oe5H0LO zv8GFJqi%HLk6fQA#|@gN=Gkcc{*2$Jw^R9B%NK7k`P_JmVE3+-3ZXZ^lMgm7e%l6c zBP4C!7BvFID~QqgeHLeXDgH*3m zH}B^BA)#Jbrmtieujbc#w?tnx$-)`NmJ9~46^mKTzV?!H%+$C^>~2&fCnhF3IUMq2 zn(#RnkzVIc)#!A|W8$x?IN7CWYXr_LKYDF1WYqkesPbkiZ%V3Ob26mA@7%k6@r2*4 zLFojj3SL_Enq9%8D_#SHvBgr>r@vW4&6N+oGxk6Afz(z!cB39 zKU1R-#RA@h^yKGnl9}}I>Z~Y%_ybZI)D(T= z0w$sYvDMV9Bf+!h7|XmyrM6&%s6;KU4wG`*6j6jrl|ujhJ&eSu@JS(Q>1$TOYY#tv z9sG~cK(eY=*#zu%6{rM!QtVZ}NOo+o zXbP5A4~te3pCP@0g|dktKfwEPS|~+=MAGmjp0M3=MkD(jDm!UQnf@79hl-Qoi`!+? z9fix}IWCx#G27&Kxg_k8OqFi>BoG^n9vgAtTl@T}*V zj}=OS+hck94z9;i)CNrVB!f}MlxJ20bkXTA;}>sXcSG15ipu4B`o3pldekr>?a5V{ z78OU$RZIEN&k#gPBD4ap9-5fQKDo;#ZnD@p!OStT&~()hH)*Ik`F{UcFkAg@-xMm2 zj*aQXT3b0Y0;9|WZ{OZB4dRrg5{qq5E2uj}^4ThiuuXDJ=wqbwG|OlTNi6)**JCBn zXu@CWm-r?Ac%XhfSqi)!sx|FA5)*OqOR|xKeg4z)j+~SeY+m zo(d+1%FfiKyNdDlHGeJV?ORPDRM3QcAH+{OD`AG%6Ma^A320Wi#@iDdFxW%lh6fEe zjq1ON^r~5NrX)C%%m%#Bmi4ci*p!@|DXS->H{N~~O>9-AdLRz2A5)82r>4r7pqS3Y zS=XD_H{?s)U(^{81U=`3)-oZrpl|WVhR85%-1Ue+k??qaxlJ(Fac^`{<^-xsA}dw+<-19NT0wLu>+- z=t_ojyX7!!0qH#9M6UY$=euL2tn}10MDC1Y#eMu$0))u(2_lk9k*a@GMmLKjAUR4Y zQamoGUgV5)glAz)N|BxzLlIuD2Ha*X8n1u!&7U6M3LTgb?eath_geK8;IfIOB`HU*y$Qs0re zU{CU{L{lVf0=qzDrU&Pn_`5O5O9+8eIJBZk19EsmF1^GXNYnD=(WP=%jy6H1URj!c z=XARy;eDEpv8kbw;1mnfkt1rqCvxK`Yr5+I{2oeM_0hQQjXOST-RK+1N%+mzQ0|<} z#0^)~W_TKkTTS(@YBI%Q4t6*U3JWm~A*Jz9p_K6{l4buC9BmH#{bt}9x6uvC>HrI6 zt4uc+C!StSU3O^n%O~aT_(<^{UDQCZq5ngJ8RPxm@Ewqk8UBawK)d`u<~!)p{-ys1 z-@)?^fbZQ1v|>hf_l*`zjkggU&MhHAOO%)Ga7#e=X6NEn8m$wg+P8>eQfT;v%%RRO zkpdZ7Qa^1YGbgdOV33$^K&Z%OK;$NK>_{PkT6HXB#Jg_Hl)=-sPCCrfR=1OGL29*z zzZjnfmcCDO%@r&v;-lQi9!s3nV8I=vGV~yn3kRJP#45H+Ya$K!L174jLXy+l%iAo> zdF$$EqW+3$9&9iuEOJa}yl7|86s1qc!ETqm!2ERk z4OC^KK5lnEeu;&$n-oZYtqSCUKk-F%#bOZEZXX(Tsqu4M|H-V#$qHS;pVT!vfzFcF z9W1N$c$*w~Y8u@1=j(3b{L5A?DfsmVo~ z9(nzs@Y6E~mRfI&chIa9rqY?f-Hz^SSu759m+Rm4`_&+CpAstWT1zO+xH1+#pe;-8 z)D@<#Jf7L^pW(;8;7Ud)hfo49VQ#MrJuvO3Wv`dqFO&W4<9HBN-rn|_Yc)0H&(ljT zwTv2Y})0=T!NAatyPfU9yXd5`_YK(zI^tV&<_ME{h z)*iWi=S`zC^z-|!=;p2rwRccLl=Lr5yx&)9TxHkhRx@Ni_XfAcZbRQkgBSE9MSV|ej)eCq*xnXX;>5kZ? zCsaX5&V2{OpHqj2o*m+$O|?B6q_-VXcuAHPC9C4HZW#P@x|B#RRn3?rl}3ng;)j#M z@_H{K0|DVmk}Hk^8vnlEG8T22#~Gi4=UesquK8V|Z`;yU4giipMVH3CjtuLpQmR_Y zKZxqbZ{AIt_LB^`zbxH%L2gT1xZIdxc_vSCBfWFf@37%OB=lK(jt?eFE<@GOoSgfJ zD#ynrxfbe+)_8u(xwCe)$>ai4@;0O}8@Q`{c(S;J^Yu29_BNA@QE++uS0IYRL=zwn8wHBOCQ9sWlTu>4{lhdMHk-oGfyI}LZ=NB@4O zD{G&QaCK|#qbrP|K9(;L+U`qhs>^JcJyPw6)Hfit#q1K_8>m8DwYBJv!sEl21^ieQ zSNG0I;e5c^CO-DXQ?jbW)1>#zZ;jQGcLE>vOzu2|*|b0SJCvZ1+XKms(?y*$YLQ5f zzq`RXDlyg_XXhD$9$ zKyXcyFSC@9su&LgY@g)j zK=DnNUm_7tQ$2oOxFlc6m>|n1;W3snTaoveCD#a0(pA+?VQwkmr`-sW>paRI3nGa9 zF#^=MSYy`M$LrEd##RWu7yD>lD_!7GnsLS83}4&=m<-$rC#tVcBE{*tmB8-;yECff zd8#ftgDkmov$!(n;um0y5X1U5oEJ%F|LN^Gmpw@4a@Fl8536xL-gx8YWhPPMBbN&` zYR>e(j-q*w>4Wu4=RAW8`~BPA;Jj>JoP0nMOPyOoV+HCMLGJ<6pm^yjA=z@7=#n#_JDISIxw!=Aww()!LeeG z#H2`FvEs2n(4Jx3&JJX-t6M8kfg~JPy&HYG=b%feqy_RKiNtX_LFx+94F1;I3X;mU z8~O26CoaX>6?;E)$9*jPP>^e28MjHEFE&C|ydE1wfv+)eW^R+wyj(Dk`;P|9JLnAE z6{I0rsoY^+cGTu!>t)sJ;+Js8c4-z;Ar<^Mt1C(nd(KCMe|T%fug>P!@(J2p(*uv8 z(#hb|)3DuQ+WuFC@Y1upg#N@3p7Qx$cQshp`w~l;>1v#Um_zc70I5|S zV>b>Bx%R?B6G8!O;CKjB%*2Q28nHep$DPDHxH|v5{{>RtO*L_1sRg^r^*IDyl-{`= zx7;KA)pK;)5WANj0dafA#?x$QK#_d`xOYLH-6eKNN&jm}`uON3%^DQi7dBF2^0JWv z69PXN=zX!&ZEDW=J}bJKT>tUEA$M|$nvld=OYH{Ugm5slOlsERldyBu`$mkm@+xVgYVean_6jnw9Tw>ik zj4y1X&H7LLh?`HOsOp~rQ`mOnnQ?CgB#-Nhbwe1FST}DaSCY+cre-?|Y0Rl4mO=XO z3R=5#WHu~`=at(nnp1-4%^NirRSZHC2JnM+zdq0z`ASHF6gRKG46(70w-r;kfdZUk z&dz`jSc5=o1PdX9zbhiMQ&+-dNJFT!K(82nR`tn;3ud)BmzP{MuH_34HOR%ZH<HkFRj1?glv1o39RJg0y87SJ3UfQ_*7=Cc1 zT|#FTB=pW|-`j~?nl&MO2cEtM9_XDCE~=DLd}~8`n>o}5&dfo8yf@Mdqe+kzTaWr^ zwI223ZFXe?da_e#=n4j5o^EM&Ub`D2Vb+6c?hhwDJ!8vO(n9TC90<6sTdpdxdR_NX zUI4Y^LU2n551GjWh%wRtxDa|^bH2mI8~stb#oX79yE>7 zS@Z!G!dWl&B8PaeclvH<>R4LY|Xq%*?ouP0o z2&pcVtXpr<>_qUFnKmm+`Z5ki-c z*s~B}(a2f!1E>h>3IhGk`MP}+nhli9Y~Z z9O}+2z+smj-q|A;bLpcf$A+yP${AnLLEH&|Cr-x1ZYN*Da*`#=ocb9$^BlskF)stnA;Z>Tp-pESd{^=6@^>l62v z_P%EPXX(Asz}0C3dijc`FmJYGFB=uU!WB9Atk~kh`d#;I-r49hSIw$9$MRF+LV_m0*XkSTfUF8h0@^Yux zk+x(si3Ux-3Ki2lv;Liwsd@XxhnGtQ_9*`dl6DsnKAd&E3rE|P!h~3W`kd2=Zrj^A z>qqC$aHx`2mij_DbeGyO7q#oB4Ogk4EB;xAnMUf6E)k4iF{#sOU9h4z+PEyFdgyaX zzzCflL*w~B%A=tT_YTc-XYaeyd_MO(b?;k)gHTX}->aGX@(zKJtmp1LDz`MS7z@QU zuN@aZ4!d8PF40?mUl|^eVpwS{vRCq^d<*T+yU#BRu@nwML`O9vL~=Q;uGR^5gTi{G zdV1ZN%SO?oF;$(Tiws@I?yi9f2N{hivI1%~rn+fbrc+o1X(;{)5wnaRJY*kCMoH{x zaoir8qId|5?3(bOcYj(^lE7Wbm%Z~dC=`0<8B~^6861iB6EEE%Dipd&^*#`O7aZ)U zGr(quyTJ3^B!nOtNWYWR4)F^= z24I!%(_l2ZGfl0nBOuA3?5k&%%YzTf;vpywK7n{?^yDYO_g;Z_P7lgN&0lL2@-e@vVfPp4mC*Es7lS zWrR83=PKK(`>uttG!hQqlr}PcsnTnY5jyf#`-|J}2Q!cYL1D9e2S)j0Qrbp9=n8Pp zvDc+gUUTTyIRBJHR*5Jj=0S)*JVufopy`!fH9Tv{ga^%e#}Kr! zViD!GY=PLZSjCfPvrv%v!=hgoRVN0a7k_x-bCC0G=Q0FX5v)8wXwq(glrye6(FDgv zukIPvQ}7-8P4+rP4X?)`ac~6qmF?29DWFBKe!qhF>_u*}g9ae=*I9Z|PxxWwBO-A; zF>xb#r*9}j5!?w$IX@H_m8}#3QiVYyVdo`yybRGmt5+^ z6MJ#;e0Q%4;f$&BtxxeB|5{0;fc0rOuG!J&?yOYt&pyCvcUSyTA1K_;;e1!+y+F>n zzQ+)<)82O*VX~b}{&e%j!2MpGv}=w}lsLY8-5?D`8a$-ar+%2>0xonLl}31ee(Q`^ zafcXlO=WH=OZm=c?(CpCo*`fqmx+DCoYFP~%er+WNn~geiR7+I+zm}k`i0dWA?M{o zq@|G=sT0hCoFn9IdO3_p1Bx>}WfW*d1%)m{pQv(in0`C#ym}oAHh0mSP<5gY(2v|9 z2<+)4lR6>*tjnQ(0A@n20#{*0Bcj~|Fgn<)lE#7QQAQN5F{wu^GW53Sso`HiO|fgH5So|I-+V4peCk(OF;aKLYuVlG0@g7 zTeM5jRjP0JAfEUPFi6K(UclO#DQpsSp^-)$hsP7u{u3Pb`>k0#2N)H8#vaX(zC_QO zO5)9GyuKw}gdBF$xjo2rAiurm?{SmQ-~(%vSlyk$kzf zxrSLX3M2esA8m5sz|L6^5g#QZPrl5zZ`yq%#gNjMcyf$?!C(1D#Q4MMU7Bn2RN9s@ zh3=R{G2Wuv9~PunpjfLha+0!{#Eb;?20b9SbDsQv2CabkN=r`)7>`t5o4b|7LGmkw z9Z&{DJ{^jV#wq!C;`ijh03r}EG#fx z9w$iq-&C9gT65nf{<@@iLjA|Wcr5Q%m-wm;Jj=6Z6H!=Ax;l9p!% zV8t+~i2o~IBLA({EI# z!}VG*@W~&b9^y+QKX!amf8$BsN*64I2I7pVwoptY(nJL;_bU$UwtJL4`FFkLlO5pkvYQ**U$ryz* zE&C&dn^5?g@1xVqkB$hBl*;_F~IVm0$DIVnnxqI(kNp(llxzo3;^m3kET;xA5ng7M&MfU|b1ZMjW z6_WPa`683bOO27MKEtcdy!e$bBS&7l23IYo#Ie=gp_#wQpV2oYn45J9R%JR3yGkmL zGAMXk{oi)<@+|xFb?U{XtHtXa+FvdhHQ{ zIEj%D9SqT-sk%`8PL}mNfRZ`mP^gW%^(<^ zFN#D1L;B5(I6G_@l?{b1eOT!=6jc%LD($#T7fljjN-h8ZPrBTNgRk)5qHDq!>=?Yh z2xcMRMbBCiPJpjBN|NHUBgqb)%w*MM#4ANU_zu?GOgYa^7Cd|n#08=h6CK1?6m$0MLaMDndp~Z5ShI3Jp zW==A1zvtGd=+ESKGjr3I$Z$W@;dRGRVsLSHo$3xynj|s;eR1I=2 z^=bjHk|i9ZW3OU0#=*brrnAEn(wd*UnR|wPgEx6T;?KVt0rC(dndu3O@C2GVm_+-7 zwWVi3Q9yi6A%Z}Op1DY8hbhXS75@CfHGfE4MlPpH;Iy8LD=o5*baey)b-ac;%6;8! zLvJ9-)}e77x+n)xYfcyb3O-T(o0gSq5%T+sY$m6x_!q(v=N-gp`kWIvHIX0qM|#;g z+VC|(OeSoPre>Jk0-4UH+aA3#(x0D^)X^wQ`p$ONVJ7Ahpxt& z=aWB-iXo}B9dd5IFpoD`b0GR<*=x&+*V-n|D_apAb2;j-A2uPAIND!Dd^kgN2?swi z6Qbwr{l-tcNt-vjCIJs-YV32;<{Se>IPg_ym5&TNuc5pl?rll0iS8mv)Cejc-p`9^ z#)K-$_Oke0tPV5$j5%LOI>5r2cq7^Vm?$nPaAzHo(jn{VG4g;S`_YT9m3@eB$3&L# zkTlCrYGusuDfQN5T|@@}&y+GwM#3ELa4BnLW{Eg3e}#x}L(m?%<%@V9rIC%?$x8ME z1+4J=6?Tl$1eNdCwWkr?ycVG%yvD0OP5>Z6cloncQBfT7AL!OM6h%~2&{O-Mrwa0r zep$bKH)(HlwJjNMKkRErmje4^#+r{2oZm zdV~8l&`iut`lN%KtRUybG|TJZr-MuxoNq}9KXsB7carMJ67 z_`(RAD^KiEz$8F%v6#QFOb4ltO;Vjt+)xJ=5JU6jDL03g{q79!3@sP3K3Rako6gh&O(^tqOlo1xhBpmyRq^VyG z6R6ayeqA0wp_>LP1X(ik0letvt#M$myRcm~5k@1#)8Yz|D>>^evhTca1pQT$9bbb) zEojO{E^QNOdi@7gb50Ktol(xn#fKFM)MoBFlEUL-OqoZD-+BVPcg#)LUTqNou({|P z+v$gKa~;KAN>H4R5-o_~;zP@rQ?e$E5Pjz^L^tq^W6-e5pLQ|lQ4)_&iNr~7eW4#2 zT~Dqq%x%d@k3`FV$9I+eeFf|8Qu6R1~}P{(iqD`Yp|1bGjAFf8C0|J3*Cl z(_aoo$DjEnK6P1Nhf#8lbCJmJdE5UW6(}7VUyp$EE~Q9^GV3xEuT~wcF|tVb36xua+sgb1{xzHW)<%cLlHWhEzKuxYUFv?i{- zY=?I{7(d78wKen`dJuOVJ=NCO^O$W(=m`ft@0!fmatbWr#c#w%;|c$z_9cA1(tF`D zv1L%NTAPa`j-`Rd={6ht74)3mmsqVR)dhvNY4f6%Seb?dn{b~?Ux$aEyV;DO zsU4Ya68)hkA7+AG6lDj7vu8K`Z-j?HT5+-3^1=^8d(W@tjt*$*7$W-KFxB%7kwK-8 z2EWMc#OO%L^t*+SN_f7+m%`xtz4z1D){zreLv2ZGiwkB$>0x1nHcU_`Ln@ACk?+Yh zGa+Lk&dQ`Co%aBqG~TliA}uIt)B|sE9Fw3KKh0lbnYp4_o<+YYu+3K&Hw?|kLOEV^ z$c0MO8=1ZVY-^nZ0E3KR)}sSszZ`CM#SNaWt_&gSn#Ec1YR_|Lr_(v3?%vL`aioWg zVJ!f1InAC^&sodMN)mH86AY*4%~)5<2#>IN0!2sNq9nZ2A+HNqktNL$~56`5`#W3D{qdwkY zr?WEGyf}G>6Uk4v^Fa{NTw%S=yWY;+6B51XrDyhBQ4^)bkl_kwgQO)7O+O)Fsp>qU z8Lx^p%y$j88-e!f>j6IN#Q11=4^p2&u53`a22H>(1iP#y?sRv@5B8z%W_1K0xfrbt z1JAwQ@FBybOPU~fd7W*Wr%C=<21`Fz&O(rW{ROn$J(c`ggSu>_igo*Zh3c%G;AvG#C7*-TTif>(mhgW zq${{PP(#ZUa0%Pop+K9bC#}wTx`CJ!_t9XX+{S++dt;so@IVE>8*WOD2-O zGzAc&863?Ll>KOa!>=0{XSglE%trmV23`Fpa2dp6W%Y8T;G8mx6%d)WDOjM# zw7ci7p!HfxRSmx?UYgu%tG{R|@HV2EbI?WeakC0-QmQgJAlGo;`%D=-ahIrnH_u>Ol`kWQjmpFQ82eW_Ht8m#?wIbFnnxZ|$K; z-)rOBDFJj}7x4c1Oiyf~*NbSRE2P&!%neaC=6?|K`_G6X7~{tF>qEPdT)K~=4R@>M zX21hC;JE$|&$zK)etc^#FmBmFo-yt^gX#A}dKkin@HTz>GE7K0kYz2f(@@D$m+cM4eMoq!uq~j~MWj^?avkn=`>U+Ro zA4r<`^OIT0$2C)LL3`^PpNT*E%ws-@qtA6P>b{yJK5o?A`&rn;wlUrnoxoi#33c1FO$}g+;9G}@hJ$e(fI(Rk0+?_Dl8T(g+h{EHw zNiKmpE1yJsIhe$IdDTVPE7JR#fDw9J7;Tt2lB+$p2u>CCv@mXVuWQ2ouCxcm({<1j zMRerUQ0pQJSbfjuGg$&;CzEV><|JNaIiFECu~3Sth+8bdU|#p!`s6DiD{g%_-)HFO zkU<)5`+%O6YY61WrV7&41ZPQOHp*p@3TNN_$bFpCMdb_Mdx3QG_w0G?1myk(BC_iS zA6L>-1;x8yuS%D(vkC?NS%WnVR|(l~9^861)mJLcCKDPwzKo%Qr|G^2ww_=9*!PM{ z`!_s2Z%>PJ+nr14=>iv7^~s)L*9IZiAl{SzFOM)tE=3%Pavw1Ts4S00fZtx3Fb*u@ z6XMqc9P~0nRD!a1e<%Ex?^D7nIr+W&2DDwT$!?MF*5w`2wn6xQi?*ul%UkegFzUS* zQR6O4Y#yjrsWkdcD#NZudWs8L!(?o4m|b#8?9N)qe`et>Iym~Hs_Gg{B#53Syh-Fn z#Jc-E3($y<7xcnao?1iRB=0LHyv7lyb3RtxH9xw2!&bbVBB`Zw?HdoHFhQcNU5O&A zt7fk$@l*W#01NKrzeKm=()JLQ|8r1aSd{sf!HjVpc1hp6y5 zAFKqJlFGy7Bo9f$Gi9(3{@A`o-F*iU`tF=PSBYICKL+f^0mcP&2GVcOWzn|~GNsU+ zzFRCY@r@?gYl&dAt03hH@imSG3w;vGzR1msX$KOwEFC7s6gBK#79E|s@_gFfb{I-zne<6Mmy ziYBPs6ZsFSU*=gRVvcd*da$MkzmDn{qok1}1Qg;L8a3HUVs(zZpnOX|q%1GbYBS|_CK4uHS(KkUzq^M6C+!|pTw z5B5j9{6EJ2hW`m${r`oFDrNs+e}&=i+Se>oYHMjqRiw;z8$DVMqrG{u)dWDRfvK(S ztxzlqUyq{V8(vv+w)`Pv*G?;q3^-bueY@2>?dbm@e4-3YcK_(;NaT-SOruC{c_ka` zHASIs9l}SV9NlOMx%<0OZ(c9d)^Vrf@bYbquC=v~b87!!3$!{Nozc_wQ!dZ@A1X)E z#?KWc(m%1Il-T+^wY8*?_SDst?dx`(j!sg%L%BG`zsk0&Mfl@}+HE+*aYjIHYdfud zYd4o+{8|9z~!IWf}adfW`R<9WtpMknqpQbt35tu=q)lCG?BMEHJ-m)Q<_}Oq2uDpKZEsw4--z z8!P(O&jP@$lmEW+Btr$)>Bh&R?PNI{2D5Kn+?Wt8sN3v`m{NL`E^pr00_?imEk`FI zW8e+9BG<=H?2}M;`f(vN#VvUgHVmVM|2!jcJWVBX5YC#(5KaK)HfmX?K_PB5 zRx3VCNQEF%<>!H`t!-FRAhn>vz1rB9&iI(tFY)KWJR&rC+q+X}sC`sa1l|3QVOqhD z<||ogj5YsWLDq6beU3Txz|`O0`rOQPMHtq=%Z&fNmC7`7ht2%_>tbaET6uIEE;x0K zX8!ovKa*XDq|B~u(wAEGJ9~F(X=3nl9?(o<;I4C2obIfKhsFX$b!+ReeLMSXxq<@EI|ZJmR#`7cn1`dbI1O}3 zG~*Q(c$fRktdlHN?~}y1UyF+$?_-ENtML!8BHk!|nqK@gIqb08bzvjtm!5Ff13B+j z)!xhL!;YtetKzJr@~y3{_A2$1jjUIfCsVez%GCAdkeIa7UBAWtZIvH4JQ5&Q+Rpr) z^Yb`pT6=rv@=6aBB}|KfT+FIUG&Dfwp=0+GRsQJ3;rTB#+U!M!p!M0uhhu>o;;+Zr z8Z&4GtQNEy*1(~>%JWA)qX&iJO1=D%?bG@!R)*a5Fkg|6jp*sBr`i|m!&H@MIa=#p zT%GIm*Rc`st$CMB2=hLA9KC7V$2~j4KjIc)%p;IoTENtRD1y{5z#njh+;5-(LYW=v!D~ZbQ+&oV#8wq*+qyG$8#P>5$Z{ZM zYWFwq=NXyvJ}503Uvg`eb-J#WWTM>d`Kx`NY-91|U7?Tsfb#{6MWjp{PI2v9do{_- zTYye$+}W|M0n5zXLSS&CHC2m>UyveIh^XH3+ER;aZ=V;|ieP6T&O}9Z&NCP-@|ikl z(9A8K;d+WYQ)FSC%_TT!!%dC8+qFUVA9`VF&bosCS|6r{oI+%Q?JW%znFkyl9U`O~ z>S`&(i95DDO+kV~O5!DPc#YcHdP~7K&fMsEFwMD^cG%3$XV1=6?htPX&}e$Obf^Zd z+S=N7Y_YvAUjDOVlv!IN?zo|j<|Y`$M5q^&RnD@bVe!)W7HI3sXg7249y5X*iFTu1 z|CgD|su3S^tMGG&@&7x9KYm(M-6-wZ*9L8s;(}R6IC#gfvg9?KHYlj^s;F^S60CZ8 z(!j>soo-(aVa@FH6{JmHZ}E#Fy!-&bPVIvZ38I#f@O*+~t(9Nx`=1GtTO)q$w6pGi zonwKtm9lRt#^Dm5=N5pzM*lf|!`4RDCfIb$t}?SRc&qi|_Ta6Pu9`S_KaKLuD54e1 zIzQdDCsOG4Z>YU7)sSDp-F3y&EipPPWucFv6PWvoN*>#)tI-&hKY6R9TU&Zc{@#zg zem^`5IqkZP&$s7U!S!S^|MTf-SdL&2j6rzT0;F#<+TI!!zO_7~l>=mCfm3;0uSA=n z=AY6t8*o}r32|y72R(pHPm9jBTC%f>g0lj>jPw3Z5Uw#Q(b9Qz{PY9ZP zLs)#7;k~Zf;OF+GwXlE66R=Uw*~vm`IP63vC6GEiw=&KbZn?{ElJ`!s(jb=!sUPbD znP3&t*c8K}WyI6VgN`$veLUm81o_Xg(B@L)pX*=#{5ihxI3KYP+78u{o$IU1Sh?y| zKLh?%-26Z@5yd}9WA(V`vOmvu@ztXV=1F^7i!yne(S#4~wx+OC?rkTh{b5RM?=^?W z%+H?^j?ZjEVZK;;QTNki{P}8-|4na%(-y@J!gx1D?Ys-U=d2d&1%JRsMoyccNHj|n zT+lfb;5S>A-s}8g(-sY+DLrH(094IG+Q6R8on<1wZ@jZZEZ4>w(^LuY-q)I(SvLeeN5LcEbH3oIhn%Fhv=68GLR@W^HY5`L<9|lY8m{`CfZ{6CC4Ey{Aui*Nr;o~!Y zT12WJ{NC_!JUzXa1+V_Mz5NU8(}%Re`nggk^;pyj8=EAyD_6-PmJ>X-Y$+Ul8D zH0DbC)pLKI;M#(d!k@&zB>ttX{AVruMW)f4cFSApbQ}4iruQi=RuHw|FMup0;{9{E z&_Ac}8Np&BzZ*;jpxAai;o&W^#IxzPnGsI~qx<`tZn0-}-R}E_Ay8EHfuTZ) z_vi|erx}FA%mn-$=St;#OtLxi$_8wb>>`S#6M$k?-P~jwj>FC!pY)uTFLj*@v{2lz zEXt|jK?~R2xCg)Pz2ew>rT5;JyU_Gq?nsl@!Rp<*F3GxDA_VOi?PE7Q>^VdiM-&eB zwxzAB%vp;bIj-VV`#dF^!Z~cMy0^BwBpcf)TPItdfEQM~6|hvZr;3tSm)M@70)?(9 z{`&(&%gPsjikwi1qcT*yw&9)-*=Bx1mHz*0?YqO-djI}ILSjb5Ua^DPyQtW77&Tf| zMX6QQ+QKJ|AohrsqNRkksH)M4s=Y_iMb%1zDxa3nR}?wlV_Q z=w|w>+CQr_1Doe@|Mw6os3))A&C557D=GGd0yZ8-OHC}xy}r7-MtNnaIHb$hrTY2kY2I4mbG`I=N@$B0!vR` zcU(5r^CaFs@M4rXhOC^<0OG9#h&_SBiwl2rJyCr>5MpRVe;+`}X;dKZ*_od+V~iRp zi2N-%JU~j#xJv5PY4RYEZ$wXf#U?_h6SXp5>?JLv`Yp^DY;BWJVAIpF$?brO;L>%% z6vC6~>v3^z|3z0;o=x}V-(eJPUVz+T?zrL%ue`Wj($Csgx3$G?%Z{5mfGCEC;NeUm6x(|ncINm&(qFt=*MDb@W4cE%bdh;&7I3Z=;4DO>!|y#MJb~LhW3CY#6NKPmhIVwm*DND&%+0 z-AV-Rj~u@wa_1<$~EJMOizUxZ-%)BLJxVnGEWBWGs zEW*E^t|cCz9jJc6U;*4`>?LJeen z`+g5ddoC9eU=8jr+T3v~M@#4iwIk$yH?4VuWs_8~kVtxIVw8;GJ zg`LPH4^+sVjrykRhH|$!KT*VOH@?@mw;A80*S=h#@HaL#K5djYS%BxYU5)(surCAw zT&N#>3kwTCsyMn3sm~AOod0}mujD$i74_i2L>uA`#l}m;clF8u;q=qO+y@Jzg7`ha zm&U*QPi}n}_J7H(`O(mSb8DdaAI`070BPy}A-C>UBXC@fKBnE^$16~)9-_XxbA?yA zo`QkbtX$Cj0s@tW##4T?@iQ^x)9RPRpUMw%`h;{3ke>+TUpmZR7}j}|iJpQ@ONgk- zfq$*dLLyL(^N(3*UM15;C+Upc&T!o>wE7*F)RcBtD7mlL=mwPeo;S(dw2+Ho-gCe- zEMnRtd6gL2E)KRQ=Y!SB9lAMU9q)q_RNAR`(ojOLTSg`$1O<)P+p)jxi|c3+)4-k9 zZyt9PZ3R{YwcqV&DyyIL&k^#x({}fQd3sr+;G*9o@4fQ<)~gpW!YYC;iQIM=jQ~Ht zm6b*HAwZBJXD3F|K^^umz>n?m4yE7M$;k<+V_&&=t5le=UYBO0CU+9=nVsowRV|EY zQ|FEq?q{q=aQUAf8ck>;zk(@=^tb7AYYXG9Pp$9nS>4!v+@`oZzQ3>aLv~53W^>!obLhfpU9gX#!?sXjDfBF?fspPcqz8}8xJsCgoS|xs$%9(En zl7);M11S9<;%W_670lGpty!O!U}$43&Q;)=A9W=J<@b@Gs9 z@JfA`)w11J#CE82Z}~@|6WZvNxk93GUEGOIU3uffzi8xiK(EfGhut@__pA45Etrdt zaq9udo&aY|b32+7veX?w65gz!35lN+gjQqlz*H<+Dk=aS+1RF$`l0 z%0F*r(GGuhxzf4cArP{3jf&Gvds$h|@2&0FA0P{B<^#QwvlR~Z_Vx=skW5WzbeP@k zY(17%ltQOhJ&Z`>(7o1t#)%Craq|uOByYUFDYR)Q=rke^x>PJdWnLQp^tTpZA$)f( zG%me24$fYSP_wBgtnt&_ailEa}&-O?GuAD(ql`v?j0joup3g*w=KeP6^@kQE2Hm zI{^bP4#Jb$(r47kAmoE3*q*HB07}0wi5jjn#2&QMrJi{Lz}jX=WxP!u(u+9KK>=Oj z!aeO1*!R^RzJgX<5qdsWY*?uY0O)>yR(UH&kG{L;fDe;uxVLg!WV2Ypg1H_Q9qyFc zKT>|l-N!DK+%u$jUd>PI`UQ>hp} zW5Z5gW9ZOm758ZOJ)B>GJw6n|OZC;_pX{^RjTnv1=f*ncRU~4Zd!FrB7fPZW`IH(~mQ5mP`qUGuvc_5Z!;eb5Tb|)l zptwSGf^qHx%KD*$HifL3CP`N2v)ieBWC~_+yr>&@yH7hhiK?BI{8&1|@j;`!E_5>m z%sj%s)hNG}UR8>3lOs}-Mxs@JQ;H6uM&Pf459GyTc_GlgI?>xZ=VwjLI&5ZsLQpoW zl0rT}`6Fh85xTdCY@Y8L3CT!O?Dgn8nkC3mK8#vY^z-pO_aVJt!h|ZfPQp<(C+5x5 zxRY6tvs;CQeD`Yq4IJA4Ows>qHvv;m*uQ}T(EJYv4(Na4hX2n^U|Eg8?o{3$)6Mzp zpOELt|EcF8SzQ129BBTBdoK4s_Ph+FOaL>r|A9XPHiiHDt9_%L``+ zxsv~{rAL{pAy5K|@0+^EwAbJ{@uX1!fSmD1aSfaQ(=b2ueWd$fJ1HT4_$T%7rRT+C zkpMNbu^{Ms;wRV3Xremb^Y$;yct>@Vr}?A0(K}QVpg4?RCduI5jO*}DUT$bPxXgZK z7R9po-XHDqcEZ}N_iAb4cfK_hAI3$jS$Vk*ba!twOB`Eq=Dz!?n-N}eqEl1+c+`{d zZ-8o5^N;WUtU5L_5))jkO?L)I&1~1=E-1?gW1_fk1r&R zP}0NqRwy9cGd7|opWZ1MHt$(E+;1MnaS~!Gth?LOJ_#|a^Wm=%vGa7P2#{#69q21~ zr&m*QdBRiRiR*%doQ=$S6Vamyf-fH=af*k}(S?JFsV=!hW*;AeR{=3Xl9XRf$qPcqgor zbB<+%ygYxmNlU%q*wEM!on_j}Y}zvp68oLC9G1RcnnmLA+?Bhah8kVAvUI*co~%Hg z%#Ru0Fx2M$mj+gT20Tkm31PRi5uxX^S)z5ssB~|`XPL7Jkph;@zafac(7r0d9$n|8 zt*x*Cv1EPNBpab*{Laz_M@E39@`1yVa z^>tY4Bdm@OrgU3szeFT@B7VOiIlF$xb4l&XL7*zYm7bX|;2v0g4>xuW3{JRWTT9%bR4(ciPPFsNr#VJsvS_9l>vWwXNul zMFF(a7fvqOZyp6T(M^})I9CzrX9D{fQzrK#>sqp?0MECkwRQ9TFTlo@sdOLo$Q4Z0k^wHVme>kYc}T+DGljf7A7=b4>)`I>k_Wba(x>z&J}{W@D4;&+~G0Pb|t zxS@e0kZkXU6;dlJ|@SjtsJd`HzkZRw>mZc-#bzyYb|YCYpe<)qMiC47+6> zT4T1;xCk~%RW00#@~XVQ{MD#2(egKL@>^nrkMm%}p<)@Vq@|rbWNz{?GNdx=kDNps zfWFm1VRk(r`YEI_Nc$noa@IC_a3^9Ddmqw-6bGq2VC#xu=;@Ht2{4=o&CMN~#MxC$ zqZ4G4u#`KM`MFTWVQY&XG{J<4fmFL#P@R`VKlxqV>ETYXr-tU%M{0=8@jI$*{egZt z?)0fd$sFNXnJ#$1fphe;pZ{<(eJVnW8l_t7mcn);ZOqe-vhb4{K3s#)jvm}bPgv;^ z)f|}R?xUw_PHLQ}4pW9{1SQe+l^;nqgvp^>S&wsj6&_mHG%7DVF&z8oR6G$KpepH& zuCmVGj+oCrRMKa-HD-Tu`6IQ~b+xbmW?LLcL^o1Iw{_x^t=Fq3@)43@4Xi?ySMH$6lr; z#`cG}O#Jxr4$8i~*u4a-uXzF+oi2$*V2mv)?dw7z)&v#2-F)&xMYG{>e zE(Rv&d=gQo0M!#!2d|p=;^OvzR3~`hTr&QSLn`RocEGxD9-4XD*bIjPAOCXDYDYAK ze}eba-?uns$#OyIuTf})^336WoxKy^ zlcbGh&o@FV2q~f*xigyern}Zfo7QXumbfk8&odNKwe!+u!)TvXEYKEWnyK{FXoWz+ zL*MN&mnTJ~oYDsUZ64zPVj+$Hk`fd$&#X!|KR9F-(3q3W9!!{RkYzW+;lA%C3v+W7 zX>&-!CEgAd4;T_acm;WEZ1iGu5eQDiDP8hsa~P&~ z77hhm9kOw%K=J!{kB<-kl|w->nTBHufgLZu9Wh&k;p3P%Ukx3%ffMdaNGa$9*5b)_ zuck8^76`EQVSs$t-WPyH16Y}0mBPx>1gKttG{uu=giZlB+*X*_2=@h^f~Df}n7{C9 z3z9*B&vjjT*G?n($*Y@$>M1C1uS^p>jHp2T;vgMn9aVsT2NAV?buDT=oF6XS8@a5F zigfYYkln7;DHK2Nl6zq=#2qaWS-lxMSRUHG;C(KiU|*9cL!|DnCmPkabtuz1%(Y0v zvdq-f9T-NIQNDP^=&lc7m|)EK>h0(iaH{lOjZwNJ^~vWqB*_^gxO11cvpk)WI=Ltk2Pv`*^+j zji%@G-2|=Ya~)mM9^YkEQ~1c|Skl@X%b?SBOA3`TNLRr_Di+l`}PaGWFE02oiN&f^*9By2X@9&?Jnp7LJf4ijJ-y zn4DyjiPcn-3EU7DUeBbXCi=py3CGL29k=vma0@p>sr$Qm>1)kSJ{LK!#qPJ+WmMvQ>a-Ls^}QFx zDvz&igeG7^c_t)SimTg#UEP5js}!Q5Z&@!MTu5b{_;m)lHr+nOGeJ}Dc|^zws?Iec^9g;&z;IvB z?J&Z-Guwo~Q^g0;N$I%D0>AR=AT_wK$Dq?v+(#$YQhZOQDmuTl@I z%kRHP!D&F^z;v?+kj!p?g{U_yxc?^r1lE$@%6_Y?h7{(?Cic=g{Co#}wMElS#6|Ob zeiY23Hm_eRV>svM=E=y2$FcL;3lBNnHt6;2e&i<<_I>c!Xm#+AtK7ohGT$nyQiAd& z$BI%_8ZW@Fkn)i3nRpP^i8>G^*!9a5V(yE?kQhcwuw!f)ex>@@z=-{vi^d0sHlJUf zMPTiP+Fy9O2A1mY9vD@#pCDPy8z`=UnH@RPsmV%pz0W9Y?vsX83}61>I)|G$j}-epjJ#pK@NO?t^G z;rxQCeSU5q;3nq3C`hs5pPZ34MwprB^N0&(3l0t5v##NLkWh8^=Qm$*wU%Z}qFC3i zD<7x11a!_a6siijiyB%4vG+I#K-|UZ3?fchVi|fHHWjUvpf4m)*u^1*OQ|c(u=9LZ z9fnrRk-V}cPleQmr;J{ zk=u+fG!RJciSaQJl?BV7>e!wui&9ZfY^ZUEf9GGNdpfa-O=5EThl4D-{Vr!#?wnt5 zd4UPuDhbHDIXQ}VV=3z^dE9Ia3IDKZ^S-s=k=I-E&zi~~UmHEYbYWtr&motQfNw(e z25W7Mim}g)2^d@}gCo|NFq|XcFSM8LRkFvWy>T1OJOw!W%qX7M{&=8VG^Y;&+Yw1C z6w??SP~1yU+O(9e_6&UyvYd={mJl_FKr~y#2Gn{#BC=b4cs?;D`-MU`US_^GhQV2e z0ck!t0{~znTTP{|QuyHo&LX)G5aZ_W5P_I=HXuNM^Jq$fO;)ZYfrEM%V;p1#GfcnZ zDK09Sewl`nB^2W?QzXF?9gj4I0l9F`*&~cm2*kYBiI^_7nY=>D!>>px}QJ{YX?2SVX* zn69KPO8+j4RGu~bNj?fAW`zCRUe?_IF4?H&s}IR?$>N%}4!711SxZKP`^i7a2y*4= zY1*F~MXKm?c$Lqcc=(rnU9hzkmW^(1E&)$DBJ*=ff~o{z7V1zF1#MFj^ED9_AKFQkLzzZz6D$SiODQZ z_ugGo2iS1Q%TDWT&a@g{j&+W<^r5u152*?%noj0^-4TWPsUmVeGv%q;`P=A7{m zYOYa%dtA5zM}T<$fc-bC-%E$DYW2HMc!%GCyuqr^cOQ>W1?QZlEyu6}X=hzQbp|~} z`M>BccKc&fx5xGp66EgxFh?yU^K&-t%dLAfhcU7_U2qWvKq(%I{zVtzOLf!oo7KO* zV-?K5bd(`g_9#+d16oKt(k4KOnDUV~MM!5hcR>Ce)@n0L1R~Z%tV&aSj$d=a%V&#T zFW}wFRdKs&dA3BHN+Tx^mH(+H*c#uvht( z+`2sPq2Rum6NYdT7{flFGsDkMp;*k0i$U0hF~+pxY8u4wudVqW=^N#uwJjfZmnX61 z!AZowtZBI!XV-2G zj7wdSc~Y{>(3U@t7mTX;5QDv+T}k2bM24{^tPX=F5y4vnBalQ?wM}5iDsQ{b&JA)y9_%g>W%u^FMB=a!NS0feFjT zd}r&ybQB-C#m-J1tJFm^er3Wd^tV-U)CCSs-Pd{G$1ru-M*lB6gO052;;0^;yxXsf zR8^9CT?HI0%bz@K$|VO|jD+&peUMAGqG-&@i2E+lqhMnG=`znXU9%vY^x3Og3x1{U za1Y~$(7v5LV9=x;&QzBD!%w~V-GecXgXCI9daR(YIrOTS+>p#?|K^qpC@Zfx0n5*G z@TxlP7_n^%fkuwJCdgn^qCnynr)FV#AP{akNS!4*dG0MMfu$_NCRsJ?H z?Ap=BG^(nozDgWlFueq!Bh`Px{)XIr9;m4tnz2E(+#az3?ZK81fp3aOt|M`o2tBqhNtkz+qUZ_ag) zxE6nYg$N4LaYagE_VUTfq+X#vs_tK-;55yA{I6}C~c&lGNyL?RK+!XJYgj-7R=26zf`t6B+n&ue6Mhe&3&<_Oz(hzj}UW*&td zHO|TW??c_;_i*O(9!%cvMRUI+0r&f(5jsKu|55}9m;s#IpLhMW;af-kqU-r^+XAeO z8lON)=?QPs@s2TOY=r;&>k`@|b}pp%OUjuCC5w-k?r6WjhkmvUY1Im}vkHp-TD9ev zgyc`Q2xYkfkcpN!`S4qJK(kV`ulnUcKCE1ZF303|WDy_l|4bg;=J%y(+1}SBoKkDl zm&~rh1^ItXV{GJt56VWgLI~?RJ+b`yKFV`qXj}ULSo48k7mBjtV{vYfEbT>X6m7h1fPZV>uOtuRb~YC1j1?y{J8=&Q)AsPJN8{ zKq4mW*qmTbx^gc>ioJQWS45K7nzS2Lj4WzBFb=0+cFlvClQ$34U~$mBCY_7B)hcDk ze&|9^KE!m0Wy(}4R)LC9jK+a+?nHo_%&XBMYqSqY`e+g42Jh$44RcIZIwI_#Jmd|- z-qg_8GIj4cdt}w9wDkdveqD98W=I@r4%Vqw9n_bE+tN$%_Lq}JPLjP)qT(~;s=F0t zfhN?0D4tP=FAuNv!8j4Nl&oV%O++S%HItxj{wSNL~Q#MfZTA+y9`ETN?IT;7= zc8FT6udsr6E!;pO7pG92AwU3TlrtkL=59NdrU4ZdK?E1YfT0833e=omE4vlV#-o2P z(=f?7>KTU*_m6#r|fr%O>cJ#4T4bn+24 z#&X($!?LL<=0$7tj&;NgUAf|2 zoX-B&j8f_C*7)Z;bdhXKwSUU#J3l9XZ$xZXV7}^#UvK)gnK@A&Pow91b!8SDa7=s( z*}I<5)qgg;qc63qi>hHzB4U-)7UB29o-Sac51kf{%YcyR^JIcn5%K9F3z7bWRH}`> zo39Cj+UIR^Cdced4yHf4sZNPCkN)`i%YlN|5!2}s zgMRDnhs@YRo2^#ePr6o8cP#9}z<*sSyiO}-0aa4dM)~WWA9TLY9})Fs79@~ literal 0 HcmV?d00001 diff --git a/gsf_docs/gsf.md b/gsf_docs/gsf.md index c99360a..846bd21 100644 --- a/gsf_docs/gsf.md +++ b/gsf_docs/gsf.md @@ -1,10 +1,10 @@ # Grain Sequence Format -**Version 8.0** +**Version 9.0** A Grain Sequence Format (GSF) file contains a sequence of grains from one or more flows. It has the mimetype application/x-ips-gsf and a filename typically uses the suffix `.gsf`. -The GSF file uses version **2.0** of the [SSB format](ssb.md) that defines the base file structure and data types. +The GSF file uses version **2.1** of the [SSB format](ssb.md) that defines the base file structure and data types. ## General File Structure @@ -15,10 +15,10 @@ Each file begins with a 12 octet [SSB header](ssb.md#general-file-structure): |---------------|------------|----------|----------| | signature | "SSBB" | Tag | 4 octets | | file_type | "grsg" | Tag | 4 octets | -| major_version | 0x0008 | Unsigned | 2 octets | +| major_version | 0x0009 | Unsigned | 2 octets | | minor_version | 0x0000 | Unsigned | 2 octets | -The current GSF version is 8.0. See the [SSB Versioning ](ssb.md#versioning) section for a description of how versioning works from a reader's perspective. +The current GSF version is 9.0. See the [SSB Versioning ](ssb.md#versioning) section for a description of how versioning works from a reader's perspective. Every GSF file starts with a single [head](#head-block) block, which itself contains other types of blocks, followed by a (possibly empty) sequence of [grai](#grai-block) blocks and finally a [grai](#grai-block) terminator block. @@ -359,7 +359,11 @@ The *format* and *layout* parameters are enumerated values as defined in [cogenu | UNKNOWN | 0xfffffffe | | INVALID | 0xffffffff | -The *layouts* are the same as those described in the [vghd](#vghd-block) block. The *origin_width* and *origin_height* are the original frame dimensions that were input to the encoder and is the output of the decoder after applying any clipping. The *coded_width* and *coded_height* are the frame dimensions used to encode from, eg. including padding to meet the fixed macroblock size requirement. The *key_frame* is set to true if the video frame is a key frame, eg. an I-frame. The *temporal_offset* is the offset between display and stored order for inter-frame coding schemes (offset = display - stored). +The *layouts* are the same as those described in the [vghd](#vghd-block) block. The *origin_width* and *origin_height* are the original frame dimensions that were input to the encoder and is the output of the decoder after applying any clipping. The *coded_width* and *coded_height* are the frame dimensions used to encode from, eg. including padding to meet the fixed macroblock size requirement. + +The *key_frame* is set to 1 if the video frame is a key frame, eg. an I-frame, or 0 if it is not a key frame. A value >= 2 indicates that the *key_frame* value is unknown. + +The *temporal_offset* is the offset between display and stored order for inter-frame coding schemes (offset = display - stored). A value 2147483647 (0x7fffffff) indicates the *temporal_offset* value is unknown. The [cghd](#cghd-block) block is followed by an optional [unof](#unof-block) block (with any other blocks in-between). diff --git a/gsf_docs/ssb.md b/gsf_docs/ssb.md index d5db1f0..b199a5f 100644 --- a/gsf_docs/ssb.md +++ b/gsf_docs/ssb.md @@ -1,6 +1,6 @@ # Sequence Store Binary Format -**Version 2.0** +**Version 2.1** The Sequence Store Binary (SSB) format is a basic format for binary encoding data for storage or transfer. It is the basis for the storage segment data files as well as the [Grain Sequence Format (GSF)](gsf.md) used for external storage and transfer. A SSB file would typically use the filename suffix `.ssb`, but some file types would have their own preferred suffix, eg. `.gsf` for GSF. @@ -20,6 +20,7 @@ A number of data types are defined for the SSB format structures and the data it | Unsigned | | 1 to 8 | An unsigned integer | | Signed | | 1 to 8 | A two's-complement signed integer | | Boolean | | 1 | 0 is false and 1 is true. Readers must treat a non-0 as true | +| | | | unless the property definition states otherwise. | | Rational | | 8 | An unsigned rational number. A null value is where the | | | | | Numerator equals 0. Readers need to be aware that the | | | | | Denominator can also be 0 and treat it as null or invalid. | diff --git a/mediagrains/comparison/_internal.py b/mediagrains/comparison/_internal.py index 06d0100..e8a7516 100644 --- a/mediagrains/comparison/_internal.py +++ b/mediagrains/comparison/_internal.py @@ -786,7 +786,7 @@ def compare_coded_video_grains(self, a: CodedVideoGrain, b: CodedVideoGrain, chi 'cog_frame_layout']: path = self._identifier + '.' + key children[key] = EqualityComparisonResult( - path, getattr(a, key), getattr(b, key), options=self._options, attr=key) + path, getattr(a, key, None), getattr(b, key, None), options=self._options, attr=key) for key in ['unit_offsets']: path = self._identifier + '.' + key diff --git a/mediagrains/grains/CodedVideoGrain.py b/mediagrains/grains/CodedVideoGrain.py index 9244db6..6d86ac4 100644 --- a/mediagrains/grains/CodedVideoGrain.py +++ b/mediagrains/grains/CodedVideoGrain.py @@ -97,7 +97,7 @@ class CodedVideoGrain(Grain): The coded video height in pixels temporal_offset - A signed integer value indicating the offset from the origin timestamp of + An optional signed integer value indicating the offset from the origin timestamp of this grain to the expected presentation time of the picture in frames. unit_offsets @@ -119,8 +119,8 @@ def __init__(self, origin_height: int = 1080, coded_width: Optional[int] = None, coded_height: Optional[int] = None, - is_key_frame: bool = False, - temporal_offset: int = 0, + is_key_frame: Optional[bool] = None, + temporal_offset: Optional[int] = None, length: Optional[int] = None, cog_frame_layout: CogFrameLayout = CogFrameLayout.UNKNOWN, unit_offsets: Optional[List[int]] = None): @@ -170,12 +170,14 @@ def __init__(self, "origin_height": origin_height, "coded_width": coded_width, "coded_height": coded_height, - "layout": cog_frame_layout, - "is_key_frame": is_key_frame, - "temporal_offset": temporal_offset + "layout": cog_frame_layout } }, } + if is_key_frame is not None: + meta["grain"]["cog_coded_frame"]["is_key_frame"] = is_key_frame + if temporal_offset is not None: + meta["grain"]["cog_coded_frame"]["temporal_offset"] = temporal_offset if data is None: data = bytearray(length) @@ -202,12 +204,8 @@ def __init__(self, self.meta['grain']['cog_coded_frame']['coded_width'] = 0 if 'coded_height' not in self.meta['grain']['cog_coded_frame']: self.meta['grain']['cog_coded_frame']['coded_height'] = 0 - if 'temporal_offset' not in self.meta['grain']['cog_coded_frame']: - self.meta['grain']['cog_coded_frame']['temporal_offset'] = 0 if 'length' not in self.meta['grain']['cog_coded_frame']: self.meta['grain']['cog_coded_frame']['length'] = 0 - if 'is_key_frame' not in self.meta['grain']['cog_coded_frame']: - self.meta['grain']['cog_coded_frame']['is_key_frame'] = False self.meta['grain']['cog_coded_frame']['format'] = int(self.meta['grain']['cog_coded_frame']['format']) self.meta['grain']['cog_coded_frame']['layout'] = int(self.meta['grain']['cog_coded_frame']['layout']) @@ -276,20 +274,32 @@ def coded_height(self, value: int) -> None: self.meta['grain']['cog_coded_frame']['coded_height'] = value @property - def is_key_frame(self) -> bool: - return self.meta['grain']['cog_coded_frame']['is_key_frame'] + def is_key_frame(self) -> bool | None: + return self.meta['grain']['cog_coded_frame'].get('is_key_frame') @is_key_frame.setter - def is_key_frame(self, value: bool) -> None: - self.meta['grain']['cog_coded_frame']['is_key_frame'] = bool(value) + def is_key_frame(self, value: bool | None) -> None: + if value is not None: + self.meta['grain']['cog_coded_frame']['is_key_frame'] = bool(value) + else: + try: + del self.meta['grain']['cog_coded_frame']['is_key_frame'] + except KeyError: + pass @property - def temporal_offset(self) -> int: - return self.meta['grain']['cog_coded_frame']['temporal_offset'] + def temporal_offset(self) -> int | None: + return self.meta['grain']['cog_coded_frame'].get('temporal_offset') @temporal_offset.setter - def temporal_offset(self, value: int) -> None: - self.meta['grain']['cog_coded_frame']['temporal_offset'] = value + def temporal_offset(self, value: int | None) -> None: + if value is not None: + self.meta['grain']['cog_coded_frame']['temporal_offset'] = value + else: + try: + del self.meta['grain']['cog_coded_frame']['temporal_offset'] + except KeyError: + pass @property def source_aspect_ratio(self) -> Optional[Fraction]: diff --git a/mediagrains/gsf.py b/mediagrains/gsf.py index ea9f653..39c2622 100644 --- a/mediagrains/gsf.py +++ b/mediagrains/gsf.py @@ -586,6 +586,17 @@ def read_bool(self): n = self.read_uint(1) return (n != 0) + def read_optional_bool(self): + """Read an optional boolean value + + :returns: Boolean | None value + :raises EOFError: If there are no more bytes left in the source""" + n = self.read_uint(1) + if n > 1: + return None + else: + return (n != 0) + def read_sint(self, length: int) -> int: """Read a 2's complement signed integer @@ -598,6 +609,21 @@ def read_sint(self, length: int) -> int: r -= (1 << (8*length)) return r + def read_optional_sint(self, length: int, null_value: int) -> int | None: + """Read an optional 2's complement signed integer + + :param length: Number of bytes used to store the integer + :param null_value: Value that indicates it is null + :returns: Signed integer + :raises EOFError: If there are fewer than `length` bytes left in the source + """ + r = self.read_uint(length) + if r == null_value: + return None + if (r >> ((8*length) - 1)) == 1: + r -= (1 << (8*length)) + return r + def read_string(self, length: int) -> str: """Read a fixed-length string, treating it as UTF-8 @@ -896,8 +922,16 @@ def _sync_decode_gbhd(self, gbhd_block: SyncGSFBlock) -> GrainMetadataDict: meta['grain']['cog_coded_frame']['origin_height'] = gbhd_child.read_uint(4) meta['grain']['cog_coded_frame']['coded_width'] = gbhd_child.read_uint(4) meta['grain']['cog_coded_frame']['coded_height'] = gbhd_child.read_uint(4) - meta['grain']['cog_coded_frame']['is_key_frame'] = gbhd_child.read_bool() - meta['grain']['cog_coded_frame']['temporal_offset'] = gbhd_child.read_sint(4) + if self.major < 9: + meta['grain']['cog_coded_frame']['is_key_frame'] = gbhd_child.read_bool() + meta['grain']['cog_coded_frame']['temporal_offset'] = gbhd_child.read_sint(4) + else: + is_key_frame = gbhd_child.read_optional_bool() + if is_key_frame is not None: + meta['grain']['cog_coded_frame']['is_key_frame'] = is_key_frame + temporal_offset = gbhd_child.read_optional_sint(4, 0x7fffffff) + if temporal_offset is not None: + meta['grain']['cog_coded_frame']['temporal_offset'] = temporal_offset for unof_block in gbhd_child.child_blocks(): if unof_block.tag != 'unof': @@ -979,7 +1013,7 @@ async def _decode_file_headers(self) -> None: :raises GSFDecodeError: If the file doesn't have a "head" block """ (self.major, self.minor) = await self._decode_ssb_header() - if self.major not in [7, 8]: + if self.major not in [7, 8, 9]: raise GSFDecodeBadVersionError(f"Unknown Version {self.major}.{self.minor}", 0, self.major, self.minor) try: @@ -1136,7 +1170,7 @@ def _decode_file_headers(self) -> None: :raises GSFDecodeError: If the file doesn't have a "head" block """ (self.major, self.minor) = self._decode_ssb_header() - if self.major not in [7, 8]: + if self.major not in [7, 8, 9]: raise GSFDecodeBadVersionError(f"Unknown Version {self.major}.{self.minor}", 0, self.major, self.minor) try: @@ -1770,12 +1804,10 @@ class GSFEncoder(object): tags -- a tuple of tags segments -- a frozendict of GSFEncoderSegments - The current version of the library is designed for compatibility with v.8.0 of the GSF format. Setting a - different version number will simply change the reported version number in the file, but will not alter the - syntax at all. If future versions of this code add support for other versions of GSF then this will change.""" + The current version of the library is designed for compatibility with v.9.0 of the GSF format.""" def __init__(self, file: Union[IO[bytes], AsyncBinaryIO, OpenAsyncBinaryIO], - major: int = 8, + major: int = 9, minor: int = 0, id: Optional[UUID] = None, created: Optional[datetime] = None, @@ -2281,8 +2313,8 @@ def _encode_cghd_for_grain(self, grain: CodedVideoGrain) -> bytes: _encode_uint(int(grain.origin_height), 4) + _encode_uint(int(grain.coded_width), 4) + _encode_uint(int(grain.coded_height), 4) + - _encode_uint(1 if grain.is_key_frame else 0, 1) + - _encode_uint(int(grain.temporal_offset), 4)) + _encode_uint(int(grain.is_key_frame) if grain.is_key_frame is not None else 3, 1) + + _encode_sint(int(grain.temporal_offset) if grain.temporal_offset is not None else 0x7fffffff, 4)) if len(grain.unit_offsets) > 0: data += (b"unof" + diff --git a/mediagrains/typing.py b/mediagrains/typing.py index 028254d..939da83 100644 --- a/mediagrains/typing.py +++ b/mediagrains/typing.py @@ -173,11 +173,11 @@ class _GrainGrainMetadataDict_cogcodedframe_MANDATORY (TypedDict): coded_width: int coded_height: int layout: Union[int, CogFrameLayout] - is_key_frame: bool - temporal_offset: int class _GrainGrainMetadataDict_cogcodedframe (_GrainGrainMetadataDict_cogcodedframe_MANDATORY, total=False): + is_key_frame: bool + temporal_offset: int unit_offsets: Sequence[int] length: int source_aspect_ratio: Union[FractionDict, Fraction, RationalTypes] diff --git a/mediagrains/utils/h264_grain_wrapper.py b/mediagrains/utils/h264_grain_wrapper.py index ca548ab..86cde04 100644 --- a/mediagrains/utils/h264_grain_wrapper.py +++ b/mediagrains/utils/h264_grain_wrapper.py @@ -146,11 +146,11 @@ def grains(self) -> typing.Iterator[CodedVideoGrain]: new_grain = copy.deepcopy(self.template_grain) new_grain.origin_timestamp = norm_origin_ts - if frame_info is not None and frame_info.key_frame is not None: + if frame_info is not None: new_grain.is_key_frame = frame_info.key_frame else: - new_grain.is_key_frame = False - new_grain.temporal_offset = 0 # Not parsed + new_grain.is_key_frame = None + new_grain.temporal_offset = None # Not parsed new_grain.unit_offsets = unit_offsets # type: ignore new_grain.data = frame_data diff --git a/tests/test_gsf.py b/tests/test_gsf.py index bb6ad3e..4c7abc3 100644 --- a/tests/test_gsf.py +++ b/tests/test_gsf.py @@ -47,6 +47,9 @@ with open('examples/coded_video_8.gsf', 'rb') as f: CODED_VIDEO_DATA_8 = f.read() +with open('examples/coded_video_9.gsf', 'rb') as f: + CODED_VIDEO_DATA_9 = f.read() + with open('examples/audio_8.gsf', 'rb') as f: AUDIO_DATA_8 = f.read() @@ -1465,6 +1468,16 @@ def test_read_bool(self): self.assertTrue(UUT.read_bool()) self.assertTrue(UUT.read_bool()) + def test_read_optional_bool(self): + test_data = b"\x00\x01\x02" # False, True (0x01 != 0), Null (0x02 != 0) + + with BytesIO(test_data) as fp: + UUT = SyncGSFBlock(fp) + + self.assertFalse(UUT.read_optional_bool()) + self.assertTrue(UUT.read_optional_bool()) + self.assertIsNone(UUT.read_optional_bool()) + def test_read_sint(self): test_number = -12856 test_data = b"\xC8\xCD\xFF" @@ -1474,6 +1487,25 @@ def test_read_sint(self): self.assertEqual(test_number, UUT.read_sint(3)) + def test_read_optional_sint_notnull(self): + test_null_number = 100 + test_number = -12856 + test_data = b"\xC8\xCD\xFF" + + with BytesIO(test_data) as fp: + UUT = SyncGSFBlock(fp) + + self.assertEqual(test_number, UUT.read_optional_sint(3, test_null_number)) + + def test_read_optional_sint_null(self): + test_null_number = 100 + test_data = b"\x64\x00\x00\x00" + + with BytesIO(test_data) as fp: + UUT = SyncGSFBlock(fp) + + self.assertIsNone(UUT.read_optional_sint(4, test_null_number)) + def test_read_string(self): """Test we can read a string, with Unicode characters""" test_string = u"Strings😁✔" @@ -2215,7 +2247,7 @@ def test_loads_audio(self): total_samples += grain.samples ots = start_ots + TimeOffset.from_count(total_samples, grain.sample_rate) - def test_loads_coded_video(self): + def test_loads_coded_video_version_8(self): (head, segments) = loads(CODED_VIDEO_DATA_8) self.assertEqual(head['created'], datetime(2023, 6, 8, 11, 34, 3, tzinfo=timezone.utc)) @@ -2252,10 +2284,53 @@ def test_loads_coded_video(self): self.assertEqual(grain.coded_width, 0) self.assertEqual(grain.coded_height, 0) self.assertEqual(grain.length, unit_offsets[0][1]) + self.assertTrue(isinstance(grain.is_key_frame, bool)) self.assertEqual(grain.temporal_offset, 0) self.assertEqual(grain.unit_offsets, unit_offsets[0][0]) unit_offsets.pop(0) + def test_loads_coded_video(self): + (head, segments) = loads(CODED_VIDEO_DATA_9) + + self.assertEqual(head['created'], datetime(2024, 1, 23, 19, 9, 40, tzinfo=timezone.utc)) + self.assertEqual(head['id'], UUID('f56fbcc0-ba22-11ee-ac24-c9ac92c955d2')) + self.assertEqual(len(head['segments']), 1) + self.assertEqual(head['segments'][0]['id'], UUID('f56fbcc1-ba22-11ee-ac24-c9ac92c955d2')) + self.assertIn(head['segments'][0]['local_id'], segments) + self.assertEqual(len(segments[head['segments'][0]['local_id']]), head['segments'][0]['count']) + + ots = Timestamp(1420102800, 0) + unit_offsets = [ + ([0, 6, 34, 42, 711, 719], 36114), + ([0, 6, 14], 380), + ([0, 6, 14], 8277), + ([0, 6, 14], 4914), + ([0, 6, 14], 4961), + ([0, 6, 14], 3777), + ([0, 6, 14], 1950), + ([0, 6, 14], 31), + ([0, 6, 14], 25), + ([0, 6, 14], 6241)] + for grain in segments[head['segments'][0]['local_id']]: + self.assertIsInstance(grain, CodedVideoGrain) + self.assertEqual(grain.grain_type, "coded_video") + self.assertEqual(grain.source_id, UUID('49578552-fb9e-4d3e-a197-3e3c437a895d')) + self.assertEqual(grain.flow_id, UUID('b6b05efb-6067-4ff8-afac-ec735a85674e')) + self.assertEqual(grain.origin_timestamp, ots) + ots += TimeOffset.from_nanosec(20000000) + + self.assertEqual(grain.format, CogFrameFormat.H264) + self.assertEqual(grain.layout, CogFrameLayout.FULL_FRAME) + self.assertEqual(grain.origin_width, 1920) + self.assertEqual(grain.origin_height, 1080) + self.assertEqual(grain.coded_width, 0) + self.assertEqual(grain.coded_height, 0) + self.assertEqual(grain.length, unit_offsets[0][1]) + self.assertIsNone(grain.temporal_offset) + self.assertTrue(grain.is_key_frame or grain.is_key_frame is None) + self.assertEqual(grain.unit_offsets, unit_offsets[0][0]) + unit_offsets.pop(0) + def test_loads_flow(self): (head, segments) = loads(VIDEO_DATA_8) @@ -2286,6 +2361,12 @@ def test_loads_accepts_version_8_file(self): b"\xd1\x9c\x0b\x91\x15\x90\x11\xe8\x85\x80\xdc\xa9\x04\x82N\xec" + b"\xbf\x07\x03\x1d\x0f\x0f\x0f") + def test_loads_accepts_version_9_file(self): + loads(b"SSBBgrsg\x09\x00\x00\x00" + + b"head\x1f\x00\x00\x00" + + b"\xd1\x9c\x0b\x91\x15\x90\x11\xe8\x85\x80\xdc\xa9\x04\x82N\xec" + + b"\xbf\x07\x03\x1d\x0f\x0f\x0f") + def test_loads_accepts_unknown_minor_version_file(self): loads(b"SSBBgrsg\x07\x00\x03\x00" + b"head\x1f\x00\x00\x00" + @@ -2294,9 +2375,9 @@ def test_loads_accepts_unknown_minor_version_file(self): def test_loads_rejects_unknown_version_file(self): with self.assertRaises(GSFDecodeBadVersionError) as cm: - loads(b"SSBBgrsg\x09\x00\x03\x00") + loads(b"SSBBgrsg\x0a\x00\x03\x00") self.assertEqual(cm.exception.offset, 0) - self.assertEqual(cm.exception.major, 9) + self.assertEqual(cm.exception.major, 10) self.assertEqual(cm.exception.minor, 3) def test_loads_rejects_bad_head_tag(self): From d6618a60844c95cd4e5f60a0a656575da3bd6193 Mon Sep 17 00:00:00 2001 From: Philip de Nier Date: Wed, 24 Jan 2024 15:39:20 +0000 Subject: [PATCH 2/2] extract_gsf: add option to dump all grains --- mediagrains/tools/extract_from_gsf.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/mediagrains/tools/extract_from_gsf.py b/mediagrains/tools/extract_from_gsf.py index 16e44e8..d54b2eb 100644 --- a/mediagrains/tools/extract_from_gsf.py +++ b/mediagrains/tools/extract_from_gsf.py @@ -57,6 +57,8 @@ def gsf_probe(): parser.add_argument("input_file", help="Input file. Specify - for stdin", type=str) parser.add_argument("-a", "--all-timestamps", help="Print all the grain timestamps in the file", action="store_true") + parser.add_argument("-g", "--all-grains", help="Print all the grains in the file", + action="store_true") args = parser.parse_args() @@ -70,12 +72,19 @@ def gsf_probe(): for grain, local_id in decoder.grains(load_lazily=True): this_segment = file_data["segments"][local_id] + if args.all_grains: + try: + this_segment["grain_data"].append(grain.meta["grain"]) + except KeyError: + this_segment["grain_data"] = [grain.meta["grain"]] + try: this_segment["timerange"] = \ this_segment["timerange"].extend_to_encompass_timerange(grain.origin_timerange()) except KeyError: this_segment["timerange"] = grain.origin_timerange() - this_segment["grain_data"] = grain.meta["grain"] + if not args.all_grains: + this_segment["grain_data"] = grain.meta["grain"] if args.all_timestamps: grain_ts_data = {