From 60adcb02456fe7a995c28c672dcf4ef5d54590c2 Mon Sep 17 00:00:00 2001 From: chflame163 Date: Tue, 17 Oct 2023 21:21:52 +0800 Subject: [PATCH] first commit --- README.MD | 18 +++ __init__.py | 24 +++ image/ComfyUI_MSSpeech_TTS.png | Bin 0 -> 44958 bytes msspeech.json | 4 + msspeech.py | 269 +++++++++++++++++++++++++++++++++ py/msspeechTTS.py | 64 ++++++++ requirements.txt | 4 + 7 files changed, 383 insertions(+) create mode 100644 README.MD create mode 100644 __init__.py create mode 100644 image/ComfyUI_MSSpeech_TTS.png create mode 100644 msspeech.json create mode 100644 msspeech.py create mode 100644 py/msspeechTTS.py create mode 100644 requirements.txt diff --git a/README.MD b/README.MD new file mode 100644 index 0000000..9acf47f --- /dev/null +++ b/README.MD @@ -0,0 +1,18 @@ +# ComfyUI_MSSpeech_TTS +ComfyUI下使用的文本转语音插件。使用Microsoft speech TTS 接口将文本内容转为MP3格式的语音文件。 + +![image](image/ComfyUI_MSSpeech_TTS..png) + +### 插件调整项: +**voice:** 语音种类。 +**rate:** 语音速度。默认是0,调整范围从-200到200。数字越大速度越快。 +**filename_prefix:** 文件名前缀。 + +### 输出: +MP3 File,字符串类型,其内容是语音文件地址。 + +## 安装方法: +- 解压zip文件,将"ComfyUI_MSSpeech_TTS"文件夹复制到 ComfyUI\custom_nodes\ +- 安装依赖包,在资源管理器ComfyUI\custom_nodes\ComfyUI_MSSpeech_TTS\ 这个位置打开cmd窗口,输入以下命令: +```..\..\..\python_embeded\python.exe -s -m pip install -r requirements.txt``` +- 重新打开ComfyUI diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..b2913a9 --- /dev/null +++ b/__init__.py @@ -0,0 +1,24 @@ +import importlib.util +import glob +import os +import sys +from .msspeech import init, get_ext_dir + +NODE_CLASS_MAPPINGS = {} +NODE_DISPLAY_NAME_MAPPINGS = {} + +if init(): + py = get_ext_dir("py") + files = glob.glob("*.py", root_dir=py, recursive=False) + for file in files: + name = os.path.splitext(file)[0] + spec = importlib.util.spec_from_file_location(name, os.path.join(py, file)) + module = importlib.util.module_from_spec(spec) + sys.modules[name] = module + spec.loader.exec_module(module) + if hasattr(module, "NODE_CLASS_MAPPINGS") and getattr(module, "NODE_CLASS_MAPPINGS") is not None: + NODE_CLASS_MAPPINGS.update(module.NODE_CLASS_MAPPINGS) + if hasattr(module, "NODE_DISPLAY_NAME_MAPPINGS") and getattr(module, "NODE_DISPLAY_NAME_MAPPINGS") is not None: + NODE_DISPLAY_NAME_MAPPINGS.update(module.NODE_DISPLAY_NAME_MAPPINGS) + +__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"] diff --git a/image/ComfyUI_MSSpeech_TTS.png b/image/ComfyUI_MSSpeech_TTS.png new file mode 100644 index 0000000000000000000000000000000000000000..627a5d588d81eea65dcccb8fac5fabe7f2effc01 GIT binary patch literal 44958 zcmeFZ2UL@3+cip)G6De^r3V!i9Z^7pPyzu_v4Rfos7N1#C?piAp#=m%ho++f0tvwa zIwCF7OGFeviqDuYEta z&)HbZe53M>kdTnf$rDGv7ZMU>3JHl=Ns0sik^wT01pX2Z_}==6P$5Ze9QZ*DZh6*H zNT?)r^Saw6;Abhn6ZQc@Lfdu={s^D^e%Gv!P?7%0qn7^)b)M#4%h+`hl@FTC;_`J_ z=RwaLlCSl8-n%Q8l`OnD&QkK^i6h%@6MGSI{brZ6MM@;aPDJ-WVlG|Tj95Y*h4f4r zdg^)NEs)#XU7Pd+lHu&6bmf_)x150?l-HwD>@AarTR#Qu*aqv; zbi3uTPdJTc2;+^w24+_jU~>vYTi$2egKg73A*N=cU5p0*sgh>Hn&4@#Dk9Pcf9Kxx zkdjCgIc;CAA?O@v;)zziAsK5H-s@f!?l?10XX+Gus|TF21H5YL;A7^+7VEONg_WWF zdM<#h%%0uDX7@tNKY5&kn{k1r-5kuM7jZUW*Wci`w8u2b&pnDeZEJ%Bs~z5DS!?Op zDl1PDUB@1MtA(U`r^F(ti>?mCM!hZk%SfgHF-AX!KReAE7k<)Lx%#U-l%YHV?2#|Z#B{I z)|b{uEUvu#1X&koHz60?b<)-*8BCj4eE#ylM}28o+JxIPiJ*`76u?E6I^5S}-7QfErVT4+1|41ez4ME-UBk<(V&ohXs#b-o?uV;8;AeNZL1-mSX z1mvRbM$e>Dh3ld{76*%WD94AL(=szILrpC_`1)JViS7nY)l8@MEsXYLxN2s|F)g}XR!aXvi~n^;{vfjod40}Yaa)MG=dPoOMtd%Xy z0A&<~d_mE247={d9%O<$%Rd<#EiA;2Jlh-kZKNhsInNN3GetDql^E(CZdiS`3?*XL z*!8(Vk2XGHL{4)8Ia(SL?)O4Bq8kumfnV4I0^-PGhyEEP5Lpf_NXNoI$%VwL`Flm4 zU%7u`BP<^k6*;V(Xha1f_Tar7#KSFKY@v;a?6q(-Unj-8c4It#(sM0z>h;Ocaq`IKCV+3OkVq1{~!DL>|T z{vJ@XPP0F7MvYp4$Wp#L<#FU;tri>%(u#Kb0bSF0^W!`qn%)_FR6M>Co>Jj^_sROE3GBaa;_V`l zErCus82W;n7Lv+b+(UcveKe0W4xh{ZbS_m7dhQ;)fcwi!a|C%xb=QCZED?Yh>O|g2 z>+sM972CSkdb)=-^%nI)-MbQy;?cUT8$UE?zf-Rpq}-{*Ta`^99>! zN+!9rBs{phtmGy53*H;j!zmUQfYl6leL-~qUc=NMdWHwEHhZx6w{LA=UL2{FNe!xT z@4RCgq_ovlfZPX_1OQbuOt^KT!OiN>7bp*4ukrn{CX?epcKaR10tm0XT6&^339DXF z6QtsoPb#Si>Ag@S`X9BS-FHy5?^C}Z-8ASu(fV&eQ4|&0cjmz63id1uRQqDJrCHVx z#=(VGpk0FB-OHqNCT0xMT~=QiwAL@3ExJ}8%)S_2HT(;_z#e{nrP^o%mN(zQX8(5} z?3f1|aTo_%nUXNz^I$vl(NIj_`kR1 z7}Tp4V{OLpcH?^_I#qdbFpi@}kKokMeRvFCejbYSiF{cfjrfmeY9Km_-)ZB;o7Ujd zf-}P2lj;0MI)qZYJ`voX17pV!HTVfI=dDbL!hbq|hY%cn`99}W$dBk&+CX0|J96eK zZ&-)_Nc|&;{<=MwbO+5Gl%Nq@_za4HR?b{>aK93d2S@YZL|*>1IyaMT{~wP|)2grP zv}uTU>d=&9fQ`jWjip*lwx2nNO+j-h#F>a8Jkbiw8#I_ii2bL-qn@xBJF^B`Z-f=} zZBIF8V+1^;uSya6NPUxV!;K#Q+#=cQ0WieWWtnF+lA~YoP>fOLNC@9>t4o@g*m);T;tvE~sfKE+Hx|iR@@W0(jBpB_>YfP* z1sAS-2PCpmH;2g2rIXDePlw7u^?Q)m90+|SuS*s6scbfB_r#v)GcQ7v*ckW_UMoUqNzrQSHwwgx{TJ9!itw z$=7qn>zTPvUqoW{CK}5FHX&$j) z5UU4e%ld<`bG7*pG#i5Mtetj(l%tzNve{b^5nE|f^OFsJp(O0cTg(A{9a(>u96j0{ zLFUz3YR!7PRgh*nqJ?ti4}6hG==eV=B*c;Ag#U|p){*sAZx`2>&+^UMUxc#ZgajXt z1Uqw^E|yvK`44_bd#LEUrNX=`*8PO7O{g?;llBXV1ey0=G!8($$S;zfD9FzNB%@krwTW*^_QNnzk$I#R;?OC6SW%b+C014(5oSd`rS*yU_ypnaG z$d@;s!+XyCr7SgSUGrsZ(dCgBy(?~^>(9rse=e+%6kk}(1*=&By4kN>h=Dwj0^_YP z>X++$1A4FJ8>B`pJkHWGpSw*QO?Mf9PTG^bP^18Crz2|g!RCf)gD>RKjSDwtKO}Cw z=-6TY2A{*73It@e@EeGzePyUf2bgd-!LNvvA^z9h|IGViB-(HzD?r>$>c{n`ZMUe5Xz zwgh@s!rLg2+5X*zW~Q(~Tqo(*TMxXr)TK<*Z!-M!Ss0+Y%!g)$_|b07a`gtSUHRBBFE($PfZlySpnLtPPUN)!R@Inc zNOdRXdl1dePEW4{RjvIJm=%K+kDc6{}$;x?)W*a%0T*x zx3syeez<6;g$ew=dO#atdsgQ4B`~f@piTZoX3O{eUGm!i9FNB?RM4>Eea{2hSPs;$ zQJtmLo3jJ;bIHE1DA9G(NZ0Ah7%i&g$a+qCk&% zEKJq*ryL%%!PrJjC=lT=4wYdE3cG_00{tnHJWjxW&fzWO)BH&4y~g5*6WvG5o|Oso z=072@X!GB(?}c1B`@O&mK&F?da&pI-iIVSkh3f7MUZsR2`1%(X#` zN)39rd^)O(v%rVt%|wiuLS~|tYiM_(I?qDNC{M}Vu96e_$7z?cB!kLQ**Qb8>^BVr7Wn|M; zRd2RXk@1EyE+8I*Lzw}j%z%m|eUi~*OuV}$#yo9Pc|uSOq$k5%M{rD;M`!uD0;#Qe zXLDfsip#@w^RGxpuVs(#zPLaKj_gYBMYcnplEP+{Mx zgPp#JM`v4VJ%KG(k)7}n8tdqD8oweIp(2Q&hu9y4BOmkq5cjcB+e4Vs`bamLGGJtH3r#0=iV|{A%yVoNTS&c%2pou-0d)$V`&+NR889c@Xo)P zURBkI@ug!A(CgqKw2QLUk)s7Jkrg%lFIW4<%qdF^I+cSrerR2`guz7Sj+;xr0E!Pu zK=DC}eEz(j>zm^p`fWo?vp*8}+!Bk~8ck+P$?eVaFW1u ztqaZLrj6$aYwK*XFK0x9ztT)uXoff3-fZ}@Dgg4jo^JzneZI||S6L9rn=l@VQ^+ie z7~MZzHYRPGgGqHS-{EGGY}38{_ATJk0&AoOu8m5c-D68!dQlU(2myl6^tq6FO|v|Y z!NyI$hu_bjFKk5j2l8g;*&F3MD(64!?_4#imXT_<>|PPHb*sh8D2rzYTeO>gXe+Wx z!kAuM>&6Rn1$mb9$(z77C3N=9rGz+s(waE}Lnm1{W=Z^!^UicM8XSVqBLdjrgynFe z6YF|ZtHWd1iw`c0mOxqZiFvjBg*r3A?Qpl;gwWUSK)tZgT8rJQ?oC%tApoe)pGlisP zO{4i!WFCTMOPFg?rL>8zt-6a11I4T09cbp482%jPy!gw-GX4rY+y6MwmVbh2-wxyz z!R9#nMD3t5_acA7rOc{d69?x8)%zs-+E=qNGqs-J-@3K!Pu&_bU#?=by6~=ZK7pv~ zLd|o{8aal#U*<-GR^jWLP_5sFF%R!{AuRQJ)9zgx!-NcEq6+Nniova-T{qTqzl6%X zj0jXqu@-LFx`n?bzMI5fS{%E)VbwoxL9Qslu6x?#*+Os|{r@Mior&t8_i_Ah#f z-8^*ewk?7`#PNZ4Y_jey{<4?W{n#F)M*#;+BBv1HKrl%UddXa6`x37Co(F~oC?aB ze6;!T?S;U(kCA@-+dyuszU^PGUv|>>-Bx&>tg>yRk%u`7w$Kt8regv)*KiU~SgxBv z3KDcd75v-QRQX##hz;7_`0EPoLk%pISCk23vIs;{*1H*)qM!|BkjNTgJ)K`#C(AXg zs5(pYC9h5SqFNn#%}tkMCv4!WUv`b`6{TMf*=}--s6?;rh-W@rsr{H>eVZO1#u3&> zb$mtF_T^`ndYQCRzAb2;|I4%MfiQI#0H$!t8L}=eQ8155*4HdjImgW=&Zk~Bz>Io- z)9ns`)jMtgVjA1~6S1LD6Qb868Sx+*8zq}C)}Cuc6rh8^0qq1$Tu#LNd*XP0&H!|M z2;^%#lBC~z4bUeAAG?pT55M=^_MRqe4xZ1X4AyH1oSLPkRO%u3H%?a+S67!R2z8||*}N;9r8^1GCtSkXGM`7UV2>ow zA2!tjVA%VUAd(nZTF}dpv=5lRkg1-#o(F)P9t?($m8>hR78s`9?+{-&I4AWU+GbMT ztDq)@Eljj^+=&ZGpK-^Ftf9s*>GAVPY%+|n`Y>npVHf_qr>IHpOuVYkiS+4tRl=Mq zKO`B=)5C$-!-|B;)fa@d7v*~dz?;54>caD2H>1~^iGmbhcxFyLrz3|$gpZwOgl(*3~5;x`Q2gv;O96p{a_p*iybiGVY7z2b&MuvMP#xx`^lJ8?N zV?$nBNo-$4ye%sLKl>E$uaKQqy3w8Nr*BOxEc(1`0bZK17?(JtFa2QXxIcdKiXfh! zQB6ypWm>~bWQMJv487EoPO2V;I5wL@;B&@H8>+^zwDlVF@(ge0BY)l9LSv74`-(4r znqUyV%yKeF^?tFkLnt`EaliB2PYho`smkO7YiP@E)Zi7P#!tIcZ+V0I?3<^ru~Oe| z-&}gVQZ!6M%r7dmjR9&1#&dUbUO&|d{C;I9!zgri@E7;n!16$&lw=y)Ul&>daZ$Y5 zs?Rta9Jxe|GMYgy&`?R&w5?&VmhSyKpiqeRgi5DE*I-Ph*$ba7&6ObsZCvi(nS4)0 zfFO$7<@{yBLc%oMU1CWF40|D+q3i(CNsMZ)Y97B4y#gX`2sx&0X7LpPX$Rj#TSjCU zaWZdKN`~TvG5E0vnh*@VV2$ny56&L1Ma{8Y_>*WP`2)Q5fU5On3-tTb&L4RMz0BdY z$s*hI7)mX}U_U=SsjVdq2pQB<)j%7$3p9K^jC+1<8XBCBhf<}DR=Y6}kP}EyG8NDm zQRU+Rf~-vp7q*-0<1JOVNHP1zBa@WdZB53X91bNnih^gOyXyA#NAn&OMLd-Wjav{T z`0@GKxm0wm6E6h49C9&!TBSX~eitspC2MLbOwn1#8B%zXz{vFe=#iGJc>u?THGw#7 z_3&09c&`8wLHH}F{2rbyycGyAh}}Snrk-1ywx{z21MFX-p{9U{Ci!-K0O+cK8gE1! zfhhYgsDpAlY@Hns3$dHD74}l^YBu2rX-&=e4G(J?48qrrEh1^Dl+Ow|D@Vg3lHLz}!vv^1jm90GGJ<#FJ%IR~7cE?;F#$xxUz6S( z!oz|v`(|@zyBP>TxF;u^?^C0}@ENPPIb^GlKRW*2pMl+E1=O2t#?a97YA{)iy3BWAFU4wIpqeOmxsEsqQTu+G) z=uYkU2C7KWAct2hNtPhAoXhpSl*ox3{hZad%*dz5q3|gOgi8p&*)f$+1$o=g6BVNRo&5|zRmkxaQBtKsv3+{0R&~+XqziwoyV>zFW9FAj)u_ zkV_FXCJkcv|raT z)6eRh8#e|)#;U@e!$)!iPtgwLt_9%SCaBsm<$iIR^o zJzPpPZtSP1z|$oHT^>&z4~UR^!xx1`+>zHvnq8Y0-V_wc=gEM>90vbvBDtMeMjcsrS1S3ardYazkXd%Zkv z*GScvAl2r!ZUfFxxr%5Q>`#DgnEGJ{3vU^I-jFSA(zzC2Wu0+rJH*yv#@QAVNe^ji zbzUk33Li_p5{;W_4KgJw<56agVu9s4rmby~LQ#I`g~u9B%O8c+DkYC)N0QTGR{Ua5 z3$2(`XVl+~2FqPGKK&seuqZ24BfdAlC7NY2r&5BTFVmVQ&labg-t=RfFu60t8vVvd zSjyRy5<&Q>x_Vhiiz3AA5b`8r9oHy_yo#)%XQ;Ztfn0ZIT)g-k(eT zZ6wiA9mf=(#)N~T<`mI-K{}_lwM)Io?$1_NX^Sx7X{@}K=p~FvG38%ZcXn&~{PZpi zda+#k=*o2bup|>9LDR31wQ8@*y|kPVeX0fBKj~b-koI|>ut|<=s(Q{{6)#tD_0|;^ z`AioJ@ge)H@Pao=P8>wd5OH$`*=Q0mgJ70^ed0Q{@ z5`Psrwgamfj-CBV5~>IV`=mlOe_2?dFvxSyOOvOM8tQw0n9p^YtCS_;~zT zKo=c3?kV4OI`&_5!)C)2<@TDuq7Ft`;`sUHTouovvk2dD+geg$q2i*`Od0EEXZ8tW$FkH9<}{Q% z*!1oh=bVgt3o3;EW4N-pdQpf}VqwLJbIFmam-CCt-GBa6cmE^T^Q{CWP5)5t=Pl94 zCid;zdA;&)F;AN5M=~CJ(ohp8K;F*-G#554P~BMB4|W#^7ZF&S`|T}ss>He1Y{M^e z48FH`wpZ%iq*JW(mUj41n+fE*kp!eCtzuYZp%REwK3CJ~r=GEeG>W z1@;C2Av)arI%x%2NQk&>d!5izkY%>GDe#sXDhno$lW>E&anPQV)VA5 z!b$sugRBCWYP%FEZXZjMwCocywn_f5JC~(A(HNu{QqP9ghouz#plTvMynjk^!9P`q zS${lYD+M8={6>RJc5g!ip}caQ%j&yTP-F8N^X9o((Zz^m&ZEgW%hP@DFw-rpVX(pH zgZwW3iEo&49P(3XD|iP=2K91vd|;mo0R6T}Gr8v^4kdl5Kxh1vA0JH$d8D9Qx*a3@Y$su>=a#V&^KWo9N=IcIp{k*-IS8UEvoD8x8Hw%&FC-qWY)dyIVM+d6G zfiT2)eRn~#|7+;dj^ny&*Mu=R^~dL)$wMR}1eo}HEAT2O0k-bO$qe@1y>sXH3SXT* zJ1Yy2jp_8O@=nan>#hR;jl&k2ZnF}`jEzaF!NhK`l)W+_I8hhTCglrJe%HIx^{I3E z;dvQ;O;o1jbLjPv7c3J;h`H)5%4TMNaU)3hb}<-^UR?#MbR)r4tguk~w45Fu%fx87 zz%M;FnxyR8E#a7b()0c%b`M_w%4BLLxy3MizIDAi)dN%YP2TE@P0bQ}nj-BEZA0tooBPW&%!it?OB)U00&F0;c4OZ`uP&!}5JJg*md@v(UF=gd zCK?u56sHiU%QZf?JdTQK1j~i3HUu!+a~o0v9L98WBikiM61Gk2NYyZ%0B z{iNyPdR3#;x6!g)X_PE}0xQ9)YZ;(xWZKA`k=+g#H;9aIB&E=a$m*}@tcOARp3+I~ zAtu~)$+rcxKO^n2XM2rw-5xWQJ@w7hzKz=w{|%YcS~;RVlVPa6(+;=m?N30oRX2zyI8Y& zu9^!}*4#aRXp_Uw$5jIF5`X>8A%njj*5>Gg)?36E_}0qDDVP0Yw*sc`ykWgBLJUIvs{`t8v7rr7O}+|J zz_-D@5w|M%z!t_^&LMHN}Xxqu9lvoXrM@`GExFR~ z$hukMLpY#nelB3}b&LV~kdb6*yg*gGf=3Oov6P5GQH`spM%97#?7)vF$FDaCs{0Q{ z?IC#~X3B!%odaJWM)m>3*m?Bx%;|$wPf&nN(OWC153QEDaLO10y#T05ab8{ymQ0HT zxEQT=(rLykM}cgYv)08BSctMJYDW(c-)rUc9kf<*=e=;@P8g&R8F2IxJWH_KX>9Cr zE!@MD>f*J+hVj|sVT9Sugmyvg>CBi1RKQ-n>$h?)eYmZK?TMd%oMPg26G=;PUWB+| zMoSkHw&Q%iIz++Fn*rsBL~>ts4c<7BW{rcFq?l(06@E^Z8%7L3lg8cXrGmH)_&q;B z#e8@jATYGj(>Y$)37DPopL~k<^k72Eh(jVxl4d(Q%TI<5nd!Xt?S zIpD#f{Hy{#?8`4`%X&kdMGYo}#<6{L$t&m`T&(H%uU*1&gk~EgA8)&)b?|^I-JQC<2KG7&sAMmFFWI*eT1Z`7yOld#6T-iu(xk7% zBFz_E4BOWKPdQnzsB9%jbZh)A>p}M_mWduLFG$qwxEP659T|7e`#pYDm~u7XrAf@a z(N$awp=yo-6~>HGt&i+&Z1s zj6u5cd-mk%rH*-+(Ap&SBS0L~&`rf9@j&zKw@yo3S^_lI(Nq0?*=XMC{#FH&GWB(- z{4iBQEkf>f7+XAWtxjwjD81{*j6}Y*4-|=FVBY+bC8?1J2dnM|>lL_@R~R8ib;!d_^$|mC6i+q}|5RHaO`1oDLdc2Z z(2#PSAy*Wsx_LnYw$gxXfv=)OO&YtTdb78ZZ-i<{C&10#1poWqznW7Dxu*4PNJ3Tf zEaJBj+!FJVTYhG3_KUg-@lbzp-|oUrPi3IkJy0@{#0opw;{gND}T ze2SgK;_o*5&Gpz0`sM{}%Lxmj*z4fnn$qziCY{5gx3<4H*%z4F8<>7`maN)7R>y-5 zCYeq?a83Zq7=IDTHged*Z}4uWstmB3_Kn^tlm2UymY1+xm6B-vF&)(V(-&k8z>O9= zu}iNEVh+Xny$=AkyVU0-71!Q-*o7w#f@=JbMr?c*^?(Q`OkfNlpGNviINA=>F(T`t zvHd9YUL2gGTN4p+>CG$cXqbSCc=tjGpf~f!MZ&HTsQ;4STt>N*uXAQ^gwfuz=vly zw}Dr$eAgB~El?ZYdoY8feaYS9Hmdrsw}`kbm8O>s{H#xbE+d36S~z{ASyLZDn$<#* z*aR5}bq|<$o?qTsz7uEN-z3Wvo6=alt8op;);y0n`-Mtg_TnLU{6FF2E8ksl%gUXK zd#eRoeLoFQuke2Ls8g5qO&97fsPam5mf40 zyH(iC{1tQaV4|#lf>B~UlGZUfpq;X+-8}y^#82X%aCghUT15P$1IFU7iJgj?88~U? zpp{3AZ&@773^)ST5N{)C8Z}?M=RvV2zxncL*%B^QrlTo(Xf1+yH`2*Lu|6OhbX2KK zj*{zp%tFG)oO1D=(eZ6kOgYLUh5FTR%CxhagT`LZKg7Fsc? zr|#L9T=T7P*bei}=0LeICy_;-K9(Zn6 ze<4YKiW>4Z{dGWd&{UsL>KJq2C5A32w+1^rmw@-VJI9UHQ{3<(VQ1X4szW-*Vq=dH zJP)`&3y)inoN$E80@3sHhlOKrjQ^2R^ozJtY(#lws6K|l7^0!6_=#vVk$X(g?= z`uPV%2$+)VbZ<_}zbj zY#lbi`;1?Zs2o<{abZUR;!s}nE~*PAD)UvC;tNhS!3H%UGd3Jv(jMIZ7ROJJRriur zGb;9U7MHO6)jxsQ3ZN!;TC1Amu>VFT_R(hE5nh0BKh?StUwSF-goJfWWAb=IhQOEWY{HiIO zQUhswk?dnZuL^)??J&9x_yp(`zAiN1u@Y0W>&?g6_JDIt)&PauV>^{j!71u)O{M(C zl$~S$D&GEohH|%VYe%!jq6!6t4F@owB9#lQE#Crs^6qZN&!-Pw%bfwx^jO)3G{s3Q z`&x5o-O{dk2QIU8Q_*>^fCQbk=?3k3MFDrq{8bx)PY7e0(&8a1ZYpDTHpWk@D0Qp{ zJ_G@o*4Am(Ku2tT{2ke3vfzRF@H<79KJlPDc;ttdZ@n4dkh;Pjr5qSx*!vY%&;J@i^YaQGUv`gIc##rWB zcZ)zbyVYhDQpvdxC8H`F3Fv3UrDBp zzI~)RpmAeLlcaWFqV~AZvKFmC5`PvjH2y0AW1nLz7*2+8e5OI@mBuVUV#g2xvY0`F zsX>8Sw$H#y6U2}VSld6Y0vrfdn*f+o>DSEHI#To45RqkTz>zfY5wHrnT&ow9`2h|D z$`X_pzu0{R6$siK>ma{4!K`4F0U=P=2PI%vDU+nEo73->YsbuVF!Y;a8_YP2z*{VW z0MtqI<7o6y34N`*BGbXA>M5%0G5C|sNYyj>vP`FX@$w%z&rBEdFQhw%U8|OU>-~+u zZfIIklr;w{l`pKF|G=1dQRKr4qtxWFn|IdhaC1SCS9=xv5rC(oPCU}!&$YnkX1LCC zA6)A)_!?W=+&q?qn`a~O(5*{1$Le#rq?e#H7rm_L!p5p zGMD;bacA2DF#<}&qz`a_A)`1I=o+Vl>6Rlh4HzNvl2ZWDV1K6!)BAS&H(d|bJX--D z0|=Y3m_w7O=kATd58#g)(ES|-=NnDs~@ai84k``@f-Be38(&oic>B32+rD-uiBn) z9IDg~NtI+ac#1Cq5NIpq(O1XLc3Kz{zoisuf4|-pEWmhai6x>Y8eoMc-Yke;jGC~^ z$vh}3pJvZ0Z9V9ne=I|@4}VsSKmiuayZh!b5fiz#x%obXXs4u*o)~Yl%gT`efcm3} zVWpXpFKcb)+|G326NMw$88Sd0#DZ+ZJ1!vJQ3{p+diZUL!hkD=ecQAA0Q)*n-jWt) zyY9v1giSmrE>c|RwJYy*d;o)IO~FIkgfRiZHMMpk3%n|fn>rD0>^w-_^Xct%0zVhI zvN)%IF(JMZo+2>F3hb;G4m5kOwOE^%Rtb!WwwxgWIwM{9(+Z#L^7fZH470%=QlB?R6GCr5)~6!`+Xku3IR(<*RZfKX?S8Jaya z8DDh~fIYDdz8%p6W_DmWbG+S76r1SX4yO!NWxzPOMwA``r+>z$B0X&AN|oTKO-l^) zd&JynbES-YJSz^Qj3YF5q*NK@#bnNAFKqhpl{gwUqD;Oz)?7aj!6r^e zHL(NM*=RX%#$^jolk|jxaog8?*h`kFSBrtr5e}#J$uJs#TE%?@l2k|BICVAfJ4v!_ z*vEUlgy%B?QLA>9=<5@EXi_ja2*81L@fDigbds66$PqE23?{_R@&SuP4GM4|^qVWD z0`Bd|-ceRyzG)d`_m9SD=!VVC?bSx6jipdXYhXVhVT^#6E3IiH`6KWh7V^ipAJLoH zEiW#$H%{q>WX!EmNv}wF34DlqTlMu^)ZEAP`om71w|p{2xVMo_4>rNd@1g*je9N0i ztpKK|4kQESx5E1*@#ceqb#Kyg}2-KBQu6<{m$VH!51erFWr?f2}w z-xhB=a9UFD)m*Jkpy^zM-+L0F#Dokcf%b(KHIWaKoI_xUTdvTWRKTcaGGn)H7LMdy zK+q9alf$<7)M$cBqrow%V@2HFqL;sf_YtB&XCD!YuUf!WXy)NlhcZ)9c;Je`PoU!i zi-jRC&*X$zfDBc-Y>h|nm-7-hCV|e1qG7UsF2w_zb8gWKs7@SKNX5?!ES$ZR{duBrl5s5Q;q@ctEl>dcVbbjL4$!swwNFKb-(0jhs-(If zr+?sv%d$6lyGYL?VNB-Hy`y#5)0omxS3@DYbTXR9o>r#llc52`d3?-W5JsvHFftP8 z^&s*=vcZaDBKaI*AoWKR%h)jwI?Cmpo@%t){&HW#SHV`iW*Beo!Scr`1CJ*TbO~7P z!MP!k^v8NgO<@dZuBP>4!jE9O_MNIyUF_KxF#tO+zIqOVQ*Ea_z94wH(DP7WZnDRT z1DDnYWzub9@$q5c?#)d9yolh8@NE0By9BA3>@rVdlPx8Vilk7yEafpfWLx_xrX3Ts zw_2_nbuT`g&KdA#be7;WqjMlwDv;;x_K;3(>G33pW-o8K0fJLd=y6h_>(PvTtEb3B zNoHh&j$jtd3P)3xA?CGvB(Jp2J4SNeF3H_M6%Csunp}S_M3PJr+b5!hoibKBbmr1- zBQxGys>?%CssHhGi$qP;c*0g%`ps_Et%M%ZltD7DB^feMS?Z(sMw4xvMW!c;2YNMK zt!9-v8M(c-4=c1Avs$x_oUoe)E}olINRkmg|p|VNN=+2X=Rl z%495}8nHKXX9AroZ#y#N{RbN+VbHuQRM21s|fdfncyE#9i0fKkmZab8}% zl%hYd4vx9IY{Fx0ei9)$_a1nsl?*9Ka*{NXWH6MmShbDs6H-I_Wwm%KjiPvYq!Fhy zcc(6IuGe3CZm3losyLLnsuqwazED3Z^<=@$$i`mhR=DnLcnBfQ04J&;)ItBSjz4NK zR{FNUx7z+I8c+Tf$c{94)<0meTNZ9^kD6$7;Jp3w~u=G9@==bwYvHiZ#_= zB?9)!qs!WQG0%&pN9y9X-@BQ*c;o!>zG&~dB71jmYq2)u8~5IG7Bk|of%K|IBZ9Ss z&eL#xykt$+PsQM}O!awF)g{2^LA}6A!A4p@Rh3XhDk?`v31W_PGt;+b&Q_#7&GsI= zl#Z(nKx903H5nA&<2>*f?$W6=Oq!jb2I)6RGaY|v*sZ!SQ_7Lm4!3aGl_BBy#=@*l z9LUYqFTY4`U25%ByoBS|m%QN&j{zLu;fa~i9X+cIgPTb<*ER&F@2flM*e4A)#92Ag zu>HGIkfYMFjXMNh-X(-7T%LI~FkM03-mu!uaC4y3uPb;rnk13ehe(oB(Gh$TR57`F@mtUbShGd4^25RZQ~oXsAo_>q}t@hCVHd z8kgDTEgV%^?+c8+?5e{C~$l?5%oiOlRTT3iZxk)eh21tvkhottR@IJV2eHd3WsfW=tn zX_b@lsvfjMI!`U@4&(IO=6)y&`+@U7JEBYl-USyu9Fz50I?@FnuNnUuF3bh$Z!R3Q zha-yUIYpYRl5fFb)<$8BM|{Yka$TL&pc*6=RQ~RB`JrdNEy3xM+V(4#cHyj~t>I=q zYAW;rdEGOaxzl*JfVybe65qM{qpCRvAe%KV%lX;ohvTcFk+&@Rxa~rojn3J*j7Aaj z!?#9zY;VZPVzopxcN{X;RbErUvl7JTVk|=(hMR)h)hFt=(kh&HVF`9x?f*Z(Y>ChTfUpYBinMf)Tc^lqSD z*QGv#cLO0W6e-O#{tJ~c#x*CKFsjZwN=x({lLhV->B}^Wv`gjXzBvj#@UFt=5a&UjaIG#5*a}wTeo)-w_beE>|SgEKY7doHo|G%9{LE z!$udR)v6ztZ@1o5;brYB&^D?IPgh2g2M1;S) z7FuB@>M^W^9i{Oe_*;|hH93K)$6U_kXyMqOc4wQ7dHMy4;5`O~L7^7m@i*JgNc`kV zDn7APQsd}ik>DrS)i{>~hq@ElTk18qOzR@C(6mvMefqRPc4AA{P-KkjAzBo9i}0>A z@|$KLdE)f>05g3i0~gc7rn|Nze`X%-1emhRmiAoL^;05Y*~xpN7yF-^4T=Z$-Ab7r zHM@aqdjpQSnU!1uD{)y+T#4RA`WQ@rsD%<3+Nbm9(Kl^{nyU}jJem^l) zD=mAYz>TVXb=NiRYlrbofzqC_YtW5?@livd_Chak1-H}V3SiY#x%LVqts6nFucMZ@ zTdRJ=N11ALwfm0w2ZLKB@PaEsAf~c-^C=6sNX{KZoE)6Za61Fyq-|V{1YWto*x|=c zC7mm#qLdR%aqRI!@D817=A*O5i37WDRBt@;%H$55Z?JBHQ8>vX!5$rm9~WN$F0+&? z-?(=b&-G&=Edf=Nwa}ujN`wpUB6NM;c*l@)tl&0+flDm-t^I;hJYb;PxwF+aqKNDH z#(?91T2lK5f#Km1i-gK;kp-sJ8#k-`L~fZghIlOY!LQlCgJ{yZ5SEYE34y`quHG#_ z(rsJ;AN4ou=X0^Z?N2&yT{o-VAt<*W-L|{Xls@4n`*nLyfdKH^cNjOlNh58myVD(i zUV?xiUw^eMS{C2ff9)zBxm93dkz(pQhYsERlx+<93Rl5@5>}TSkC+&zZrGfZABA_T zl}}7|t$i){Jr@^r^7V1|)91#*cILrp8}1})@wn?YgMqQeS|R20KGTt3Ba-`~VY;ea zqVc-IGWrc0gG*Cb3zxI%sgY}|NTvEQ*;e& z;Zj_lJN38b4{cg7-&wh#^v(agD4PHE-3m3SFZ=Pyg|CMFf52P?|G|QehufOEpYPdg z#zmATC>jFUbxqE`@q1N&yj=a{&qbf)1Pec!P*3mjQKI}G(I8a-GBkO3Jr;IaD7cQ`4;n|nWGd8S&zXXjuD9z8$}cpS^vlyBM)M z;(HU-4?4B8Zx{u5l9vs@+0u+2Z!B=DRHXSD+7Ga#zg*a?1ISTJyJTChM81EyM`W$T zTBBbrpiPfh(hR1_2IWjA;B|m|RoNIVYs1G_ONxG*XLw$OIOaHTu_kir)X3?*`EJ-c zL*OzNATUD(uihy4RdT~9n20SFxKuqrEJ%kfu<=2H8-u_VY3O6;SR0Ejfg|W@ZCIEgpCW7a|c;~(?kDV zO(pw1{BZH%LTd1abQ?G>zLkWDUohMYxUd=7{w%E9apN5BIATJ{)BjF? zG5~Dz)dqr}62{c{9XBGEpA~%dNn9rBZ^HM#M8cLc1IWKB(c|$8|K{+k*!(8=y1YNG zaj$hR-kdq~`fHJ1u6-oW?=X&PnEd{EKDM_Ha0#6m6WrZy85$Y4MTZ3lE&*d8+xm=B ziri=)a#>%;gqzVeMhS@UrW>xj?uz=d)gchNCp&cb_V}T!O+^iw8mlc!$vd)ifCzX< zT&yAAZ2H=Upuhlb5=OCO|5tn89Tw%)<}F1^q=+O+M=T+UC@Rt!5K%NLpfMJLfQT>( zL+@1#2C#uf14>sERHVqrAVsQzN*NFZhM`ECp@TBCp?&vxhN75v-~D#?y>`EE&0o22 zV4iZ$eeUur(vz{{%-XyrAX_Q++nB8!HT#tgzpHvfSJDdfR}8j6&M{gatAF-B z>8k$rkZFn~mrKQ+7{F85#5ir}?_NCGPv>Sl9f=H3phoYKxhZ@xyFf#w0dengeiTEr z*;7P!)NpP9c@&$xfiT~$up@eOIVNh1>q$RWk#F>qA{;poB3|rp#-c{{?}|@-9aUO7 zU8g_;8*{x(b133heljHi3{PXPzVtprh9H2)tZ6pu4WSn&&>hKlr+FND%u0RNzvvP{ z>KihEqi4kujA*wZGwxZ6p)1P8o5zYt4(oo+>(aBcY61xdWwP%G@n{7@3(}L*CS!%G zl#otE{b0FM9!)Xjj1&<5l}jYuj%Sx4k~|FvJ`U=6`;_k!d$ERI_u&*P$-#?9bMB}P zl_~6=X+H)FHS{{PQKZU|f{m-n#32ALDGrG?`2Z-H2C%GOhVr>J?aL>VPW}+fZO-M= zo%)?=rsX0Sg(5ubY45eBNf;ki9Oa}qB$W!zbnUX4KuN*(poqO+l6RE|Jzu2STZf&779;q)zcA*jFf zjC8F(Q(msz+~7SF(Y$~6_is$sbdHoa{{*SjX59lJDaX|tYHu%Ud_&d9Tmu+;r(*BW zH-5o0RiNtg(8GI2ImNb6omv^?23*(#+@)!Rt(wBckMcXv?c(#=*UK9Y%Pj3d3))s| z$^9<9U*TzWx0?xi9TU$z*Nmq(qj>;OLCA;QBq7hi)$KVD(gW2h7emu(_|Z3=UL(C{ z@N(aX-%E%o9~msv1rDwB8tY?99La3qqk1Q=DbR>FxEQFVY_ocxqD?PkN%XTV&#$`H zyb!7;`blZe0R7Wcn9%~;(mgvS@?gTj7;I;(u)olV3W<@PcWL{$!+wnAzQj4q(|O$T z*07%PCCIbVt&b9cLHlHYd|D49h*pzBwwGl!V$khbB2Mtd-#LVpE=HRvRTpU&DDLJK zkT|9w@9)1FF3|s}zgLZ4h2v;;vrkch#t%SoH?PUh-fKb?o@Xh*zT)wggh$cU84>^ppd`IWQJLr0?j{|&N;5*cNq8x z4fYJx(FUhOZn~U|Bo#PMw3wZ75uo=FuR2m^PQBd^xgqHac0{(nw0x`3=@~#F7?k5H z*T%{_@-uaRXX>&u5mb|lF5+83aY`1Y@1KBfgL%Ct3pazyQa)G8&fMCtF1a)Zjdx`F z#Z~=5Xn_y~_*0N&;v(O#jTK%#%pg*v#NvKS(Uxx8)DLg)3MZpoYTod= zv2h=}+5~yHqv3}N>oOE6;~GG|!A1)WV~i9+_6iT=+AMLPp z**?0dKk-vcG>m$pQl7?MyZg9|WFfToccuuuMBPb!5YKq+9QnbA4()b0rPAygE~20Cww zKvVE@?%xB|SPuRUn3UhNE!7qIrW;yGWo>e*1>plf$==U# zheFe7G)GiAYU<;Xt)ZWMrX(=Xx`DUzIM;G1^z3C+yr!oH&gOLhUia6b3kKeI#t{uR z8kdl#eW0v{`V*6#Syv#@%H_aFO4IDJJh>=3qLb)ggGf<>LK=G30)p=O9N}R(?dvzV zInew2dW(2vBP%fwZ-RrXMkp+&dtoN^YPVo=AE`k1R{W-@v1s=(+50hoK;9sLu4s5Q zk7nN)1M3t;*}KAJs$RZ;!~Atsg_%;Uo$h8ql9jvqqO2Q6e>lx@7&vwsXK6Rew4oZ^+n{D~u)f>(=H@cqdjw`!72F_shiUcz4uW3c9eed4G}m+`sVT#l*b$ z6@V*c8sek=vW! znCRqi)lpquW-a_x4b5Et!yM9O*U?;K#L$Pr;@(*vW`trT_7A=P#%u} z8e$$N?W+lWW&{sMV#eGh2M-SZz|^foN&Fa{EGcLP#5)C@OvoJ !JB>xTDK?ALbKC{z1`5agWBTAchp7PDYSMt)4yuku@ z1f}v7_dieNk%m#LVW>4r1SLMG_VX$G|22CjbW3c>(PWo_rixkux}TRHSGYNH=a%Dt za&uff!MEV;+?Id-W3T#V#1&|NdH%h=^fh4>L+LcGMcasl$1&I!CzK16{s;ug>41vm zsMF+!l7tBv^bSgwPHDyp?vefgs!gWCZ;8|GM-~%u4heKND$qEc#99dvv)`CsaUmky ze{p8Le@mH%*#mapd4|1U7yi`qu;f)0Mz{|H;qF6=={!k+EQ&OyIT3u zSS}+;4YID!k;M@7RlIyv0!dn3KO6kUdzzDoaINK_Y`B?&`R5@f>J-T-pi&*vtV1Y+ zX0ATOjzoe*)fNizB9-R%CyqpYf*x`Dmb(JjE%h8hyyE9p>7(6)+H{CNNMr5AUJ6mx z=9tp5rgZ{zij&yO#N@1pxJ#bQu7reAPA*1=S#&K#=2N#|S{nVvd%&yNT66rB0jWUI z$TA4PxG&3co3~Ve>waM@9j05851 zh)yV_Yt4+)fSlYnkVAHjQGVK=)l)yNp|2Qg*Rgq2q*V%n^l`)Hn7%lSHdj+(dOjJy zHrZl$ds1{-0M1z-;I8hlU}|&qn@L*o7WP@R=aU9M87B^DGZog5!!rqvZ)sQ zU3DOxqgsx5RZ6FXXdocBY8%;`ra--|e~)bna>YYlaa(F+Pqm%U)DL}8vh}eyCu7yK z&RuF;3@=wag4od>yhO@WgGEvieS*!pEp_0*E>J9nh$Ls;~VBXumu&n9QJ2A_U^>;*sEgNX#QpkH zR2b}e>v{btPv@oMGf#f-(TUo}0W21AQM^?)K94t4=>6C!rT7wx(T*S1a-CFbxmNj7 zBlJQS>Tggjc<&yWb7(de+g@xoQPl`xs?au?V!OgJ4s^8plKA2deyPsmmtZ7b7^Thd9?Q;k)y zx;~`}O@yIvcs3HB>hi#lwyozKq@`;aE8f1@2T8r65Ij?_{euW zyD&J7w8{Fs`nfd+&o%k&%?sLG=G|0!b0ug3>~DUM?agV+u0*bmDiL*}x%fM;tZb?E z?W?Wt^7d zEHeF+e@zZ7)+r)C;d#|))}?K#!sqTpzcS(w0{Ztavm2wA3BKED>CgPycc7UM!80acijC@%*f|$DHS~gs0 z3*o2gvAf2{&_lqkLq zjY`z3KFbe$L#|r&kXUQsD(+uLM}dPwZSXdmwzS}c7I3a0;n*akR=X_8`@qz@6yJ1y zs*m#PsHvZx@N{yTvpc#`9`yO5Yo|EB-FZZ6aZd@Ic)A7fg3lZMAibFb zB0+|h;K=XEmqdGH)uluht~`^InR}@N>82`{+F5Rgz6GB}jIpbnWejnda#>5f3Q?zl z*s;n(k_LSwljX`0ZHg_|^6)7?0{WfTHs?dJl5_Bu=TX*VhjIaj=3YPj8GnUNf;9P+ z%Mit>Sdr1|l9#Kl3lUf-Aug&BYF@&j znxG-$LtXj&N`n%}^hP~=nkgz#Qr7#?Jl`n|hfsJ=EKLyoDIgE2V5WH=xy0IRBQb4% zdRAU&(ODuj9?=zXSXxNvmQurV(!lo)Ibq(DZPIpm^_NHgd=v3}S1Rr}C}vnr@OF87 zEmOW1@$8JmV2h%1a9gpnLtdC+C;f4`4*q?{PjFt=J`$}w`k9-=N)p6J-{#+}^agQE#uRq|EY!8V7xe+8;HV0zWSc0t>wCP13~`mP-BCG+sC?*GNCfz%5#Ey zd2R#pDO7olU?Dvf!&j#PRV;@nsw{TJt*0##wb!O z^NC}Vy#FKq7VjfYrBIo5TQwpKZmj*b5a?1aWKbRXPI-A~=J%HsC=N*~rq62VJg^V) ztqUn1c{((-@Hrk=^i^8UA136Q`cM8; zTI6}o2r5ca^i!_fiAH+qc0N0;hT53Ltr(c=oLPrPf{4ql2HM>g*4S|5t)TMd4o{EV zlN%$ncD447AK|lG%*g|czCUPv^I?P@m%>|)wkO<5H)cH1d7G_#KSJ#?PizdYp%%k_ zvCP>ap3cZ!A=Xubbjd~w-`3x3la)8D?^m-!5*iPLndVrg+f&F(H*hZ$e$x@p<90hC z#AAelPwDcP+O$xtgMLIE)KVKvZh$uvg@9Rcro6;-BfET3yA*Ddr&Cw(vf>@ryhq-lmbRp>hp)XZocuRL2%vN0n1OpI94E25tHoXxci!%!4e zWNzO9j6%f*?wZKlNorR!59%)q&P-0mNvqhOD9^bJwDILSjhPpAHy`yx6?f1hZXHI~ z(U0j54D*KUp*~l4?L7^`seg@7$7zySdmltAjqo6Z!RZ-LK(q={KX>I&&!ru+lhW9e zL0;Gp+VSa$v4fF6yFJB3hRgOvs>(*%y)dTYy969$BdbAqcG}xO6BA4@j3#hJ9_+)e zbyocY=<#TD`UimBFI*J}z`9UBo)fy+XdgYSkinj!{dxTh`NVS5JY9~`t8V_7jLNCy zWo?OXPb8+O#w?~b$BR{-yV`_{&__4|yEW)>I-U}~og-eRYMuL+oAtnAp%tJf`BP%0 z%p^sMcb*PBeHZI?jK}8fZVu5rkzsnwdh-{c22pm;?FZ=k14yd)A!}M3EVt%{7E%J5 zxT5$4-Rj#)1)jd`ZJ6Lh=`x?(M_R|!7g}vB6|>%1D1qg|G6F}=n-~8JpaiV5?O!Od zf!)F12+jP>J_y$d6i`G0DvHkpL+7=RrP-O#CWb2fD&q%UkeviSdc0Kd7h@+nOvWV`hS1) zqT8Uq@!n~mGr~CGnG{@I361w);qeCuOK#{h6acRXSoGLU(0g#0?NTHRL7(ZI@ z(Xc2@D9xV*(Xj+<_Po?tLnbj}0QGoSLQssMbF*6Ie&8yr0^{=CRm#7^xSU!9GQyp# zaXHAF!C|!~O_j~3Bv`T>nzL_w=O9*%T~*|Po`JAmupkx;KG&ZHF`c#*It|&?h@5(5 zGqny)sDs{CSn|!C#1o?fcG#LFdY{Mr?dao!7x94H6Kz_GtJ`3IuSX5zj(mC?n5C8&m zXtxK}amXDzPGB9A0?+z__!7q9XNfQEJmtjB1af{j$N}7&mqW|0j*DroZI-`>LFFV8 zK_$CRB|Y~C&Yc((v3PyJQEr zE)Uk5o)~D(%foWTijP7gDslCnic})gYsjHz=ejibpRX}2=)Wl{|MO}f^vYF90(!k` z6csFy-^C||?+lc{=~z7(7b7^U`Ohxl$t-aA_1oj|g7QCL$LJn9xlsG6qMW#l^&m=; zc3N5X`uQ@db}N@iIPG`peOyAqzrjzm75ldFy(DBnE? zxw{p%sMv&2l(bjx;|&EiByK)(zL-Oui?TSpyXu#fZLHfwstc^Xp?xBhTHg-(Rb>O5cGeGJaqwRn(Uv=(TWf0rN`%LcY^i*p7?TFypZqh&Cs8f>M5X9k4Ue=0|RJQ`$(kvSg^0} z)uf|4+}&-Dy;Y2ty^FP%zaNfg!>Ah1SVHE8_1py_h}x;h{Sz-SG9Q83lCm_}2Au*DVXZi0Vj>ZHM=@|`< z`^0mJ#N*;Ha&^B91pwl zKbm7slMBtIz0qTVXTjQ;XxSu}(+|oOjcnLLD^&)ykx90aW_-bdhar-#_ES%DY_t^d$O-HF2vT>CDAD9e7$YV z6j=88c=e4BO!p5YcZYF9Y+U5%j+2MjtQn$}Vd%7ek~8x%(u+MKOkEJs1xN&w4%XBELIo+BiLw$G9i2N9Pb=S99Mu z+hYdG*63*+Rm5mJezu~#RfV^vF9l2?X1u9dGf~asQKW&!zJig#Dnj_M)b(v5jWH9bqEHo-$e zhUkgRcH?6SEML{7!EtZQrkLTX*>Zw$Q@b^2X3 z545uS87Xloz6jAY^#O?{B{Ew@8AByJ&w)hlc~x=NaQp_Yp$a)s)>hx{S6LIw{WSCT z(vs&kwoVbW1mEt2dTZ;nIq%+c6N*))-{0i8tr0*JE*r;f62B*{XBw4q*nHcD(SR)2 zhuIJ1qqR-FKefdL48~LalPsd~U-V6Gnfowi3V#=Rsz>A7Izf$bMMDdH;I#c%1hL!u zWKam##oL2x(j5SJp0dVB_mq|U@zS)GEgRE|9DdFVo0j)6FLdX0s<`16nC3Q7rcu02 z-w}XwrdAwGReB`x;n^l`@^|M{`q}e%;6{6Q#Dhocf`LQQ$H52915-@{ipXg?qS`>~ zS=7oaKX2btvaGDlF21_%d8(w?`s9>zPer-x>91qeeM~Ul2CI*$nr;XB((Wse@Rw~K zzGAu73xxCxS7E;Nqc=C{kT(Zb3r{tJ2VN1SOQD3~w3^`4zE=7SvR~E!uPNhdW$87{ zwxN+x{DX217=b@tQF$w0cAdD)b4refNOe)J1yTHCbxQGEC>uF8Ac!NKfUK{V`m+I^{QaUST_3?7LY5KdszFR&z#?Mu2 zwt$y#@khUCGb=`!RIP*&B~9n~2*EiU9F}ctgxnyT*lll;}`_Z<|RWF^RCI!6NUpl$!sHbWyuaB#`Xdtu-XwsEa&dzJW;Dt}H z#L4-pJ7*3ht*5|KLg!MlH1geeln_&|p^{ayP4WdHbNGZ8W(3X&&h%P}9+*+QCsS1- zy0eGt+O1Au>`JFB;++UiGx12#zc1C*_D$L5aa&Vw+@P*$L^IK1FIXxZlx4-MR6;bu z1!a!YBB(9>NRsP_;*ML_&um}&t(oNMNWbCJ4Svn$%p7zf`^^)PnYfLpU0RjuMdbzp zMdqZJN`QO&09+T)4S8 z*nY|+>~w5hD9`w$R5kq`ob}e-cQCW_E$I5#>ofl2*M}!bYfud|XC{ZJLJt{9h`9Kq z#F^CKnJW3|O&QJly(iKkqwsPXd|yD4L44sFCAlFL!%eU~J;Mbv+*FSF2R@n7!*ZFn7=9nS#S{5kDE%~uWKC52o>af-m^k5OWwitb zO^WgQo{m;S_8LR+A~Ia=ZRMeoYe<3SLA$3bLXAQI&E`^5ZD1oM(P&<;-~8KsZZLO0 z6j4IT;zQS)G=iw-fM_$Jnkc=ViW0fy`Q~R?$`Qfsxclj|Z|{g;u)C86&^M{#hL!pG zh>p| z`)h#wgnT-8vO_+l!F?PFf!Yb+jc|O!u{E={lZH6V!@K}-rv=;+3R*5R^^_amBHseL z3eeD{1-s>#cbjwp$DMAF9OdW~}sbU&s}_ilMMcVa-mS3NEjkm}@k@nAfkc}$-y_r0m^;n)~cmK(W0 ztOs5sp$CiiW`>OF^jI4;`|!sz%5D$Z`IsGHrcD|kxN6m6y&kE`{&Au)eC|t-eTl0x zcgP^fT&f$p;_9MTN5H&83LXseLAigN*M1$?#m`5UAj@40s@BVBRqu4$CE)|?|fYyn{;0an^wV5YMb79X~Q+>On#m74Bh zUw1@s(ISGdXojs*_~_hYCF}`cEgKstbsuc0eflY@p}Hes+EK%dLlNA8Hp83We)>>S zWkdr!4?qY{_vmH(wfIx8m5hwJX7?@W9cwGeYJf0H=sW+SfKwvf+EI-5x3tIAM;Cu; zVZ(iT8-vhKf%bub_@8ET0YKmM-+8mp>Z?Anl_jqN_qT<5Rj&1vSF3cGa@}W()?Usn z#2QXMIF{}CKD=zEIQATjkiDO@@r?qX0JVGei{na2o<3IAm%z)ch~&w-k@NEwY4P6+ z@^kb5yxlzQe%SEu1m7RiJz&tK=y5Ej?J3~gBk`rly9s0w>aW)UqXaJ=&Lh?ewn;2q z_`yGRNwek?oY+RaItcWu-sJ~lu1M`h`Qzml5FrA6Bb#9}FH3bFccMjL&{Tb=k_+w< zVRzVLjeN~3AHuJ6iCX7b4P8DcO}gi`pL!(a9*M*QNy_1vEjaTh@A5sx8{_LzRvX2i zf2_eau`LuImm+$(%dnW4x)CBT$#bJQNyglLPF@|1yCtgpmc&eMglDM|;5U1yV{7C= zc~NC-C*y^Ko_W|t^__or#>@w-`6JnC=zS_|0c=gRfNUT70;P5HK{zTGJGejnRQg=* z0o!WfRiIKg&C|Z_D&O1&aAm%Zt-v9vtQrrzkV41E`(=c+yo%AZMi=RCo;Y7KOzZtIa$j-<(0G-O%4@4w3uto8D??@Bc z5s7KF^%^PJNfk4ofHHTlOP~at2l-QoX-on`W-YbTuVT;+u;cO<@o@Xp1$kAb^IgdQ(`!Y(#AG1J4l=!<0N!6KDt334i&_rT<&e><0O-q`ua8CZMcw- zsQ&bg!2Y@)JOduQ(kar`Sy6dYuG``nE3?P)Hpt-np{qlO#)%MBp;+l%J64*YG8I?! zw%yEUHq&GM;2*mtiWL=UmMg9E{&*c?+FN{F$w4R`q)^_4qM4CTT{J~sx(P*@Jf$o> zWAYkmm4%#Os)2cqrMYKueM-HC`P;bw_8@8F?~oV1!|x}H*eIqp>1O;mvf*Xuk7jZP zkP&uJI<2|PJwqRB684SWBqf=Y-n4lsGH-2&s>p4t^JUM%vy=*!`Mkgz+Pv)tH6MrL z)pZtsUmDIQRbSJkxtjcIZn%&xe@%q{Gl?(^R1*qL72ioch*dREml-~PHS%0*lU*v- z6k%gk*PSiLAS7YMU|)+!>$N;$M@^iP!GX_nxdh!1n_=eLnw4FmTnkk``QSnO6NS21 zJ6%jb>`%YqQwQaHmQp5vnQy?CIE+4}sgOgBLs)$m(AtE=4q8^wfB9aphdP_-VL$lC zn!L3lQX^+bGA-9*Ag0~X6vqXlE|Lh!yHGUiE*dFwWkQErKxiVp%BI_MB_((ha3Ore z+7!jB6>5Cn(M03s0@%KVK6>%##B;+CARn7*llJqPd$5n3kt6GxDu`4QFAtj;WtBX! z8UC%9pi?|J@7D}%raNqGn&U?BEbaN&SnV0^*=|GNZSAVAOKsuBn~Yq8>L4D1MuL$o z4#;z;HTY?#V!mtlX)EP_SHtCNy7WIfUHXr=(xxd+RN4%;-qDh!C93=*OW?nscj@m; J-f`mh{{wsG$7BEi literal 0 HcmV?d00001 diff --git a/msspeech.json b/msspeech.json new file mode 100644 index 0000000..71b02b1 --- /dev/null +++ b/msspeech.json @@ -0,0 +1,4 @@ +{ + "name": "MSSpeech_TTS ", + "logging": false +} \ No newline at end of file diff --git a/msspeech.py b/msspeech.py new file mode 100644 index 0000000..d51664c --- /dev/null +++ b/msspeech.py @@ -0,0 +1,269 @@ +import asyncio +import os +import json +import shutil +import inspect +import aiohttp +from server import PromptServer +from tqdm import tqdm + +config = None + + +def is_logging_enabled(): + config = get_extension_config() + if "logging" not in config: + return False + return config["logging"] + + +def log(message, type=None, always=False, name=None): + if not always and not is_logging_enabled(): + return + + if type is not None: + message = f"[{type}] {message}" + + if name is None: + name = get_extension_config()["name"] + + print(f"(MSSpeech:{name}) {message}") + + +def get_ext_dir(subpath=None, mkdir=False): + dir = os.path.dirname(__file__) + if subpath is not None: + dir = os.path.join(dir, subpath) + + dir = os.path.abspath(dir) + + if mkdir and not os.path.exists(dir): + os.makedirs(dir) + return dir + + +def get_comfy_dir(subpath=None, mkdir=False): + dir = os.path.dirname(inspect.getfile(PromptServer)) + if subpath is not None: + dir = os.path.join(dir, subpath) + + dir = os.path.abspath(dir) + + if mkdir and not os.path.exists(dir): + os.makedirs(dir) + return dir + + +def get_web_ext_dir(): + config = get_extension_config() + name = config["name"] + dir = get_comfy_dir("web/extensions/msspeech") + if not os.path.exists(dir): + os.makedirs(dir) + dir = os.path.join(dir, name) + return dir + + +def get_extension_config(reload=False): + global config + if reload == False and config is not None: + return config + + config_path = get_ext_dir("msspeech.json") + if not os.path.exists(config_path): + log("Missing json, this extension may not work correctly. Please reinstall the extension.", + type="ERROR", always=True, name="???") + print(f"Extension path: {get_ext_dir()}") + return {"name": "Unknown", "version": -1} + with open(config_path, "r") as f: + config = json.loads(f.read()) + return config + + +def link_js(src, dst): + src = os.path.abspath(src) + dst = os.path.abspath(dst) + if os.name == "nt": + try: + import _winapi + _winapi.CreateJunction(src, dst) + return True + except: + pass + try: + os.symlink(src, dst) + return True + except: + import logging + logging.exception('') + return False + +def is_junction(path): + if os.name != "nt": + return False + try: + return bool(os.readlink(path)) + except OSError: + return False + +def install_js(): + src_dir = get_ext_dir("js") + if not os.path.exists(src_dir): + log("No JS") + return + + dst_dir = get_web_ext_dir() + + if os.path.exists(dst_dir): + if os.path.islink(dst_dir) or is_junction(dst_dir): + log("JS already linked") + return + elif link_js(src_dir, dst_dir): + log("JS linked") + return + + log("Copying JS files") + shutil.copytree(src_dir, dst_dir, dirs_exist_ok=True) + + +def init(check_imports=None): + log("Init") + + if check_imports is not None: + import importlib.util + for imp in check_imports: + spec = importlib.util.find_spec(imp) + if spec is None: + log(f"{imp} is required, please check requirements are installed.", + type="ERROR", always=True) + return False + + install_js() + return True + + +def get_async_loop(): + loop = None + try: + loop = asyncio.get_event_loop() + except: + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + return loop + + +def get_http_session(): + loop = get_async_loop() + return aiohttp.ClientSession(loop=loop) + + +async def download(url, stream, update_callback=None, session=None): + close_session = False + if session is None: + close_session = True + session = get_http_session() + try: + async with session.get(url) as response: + size = int(response.headers.get('content-length', 0)) or None + + with tqdm( + unit='B', unit_scale=True, miniters=1, desc=url.split('/')[-1], total=size, + ) as progressbar: + perc = 0 + async for chunk in response.content.iter_chunked(2048): + stream.write(chunk) + progressbar.update(len(chunk)) + if update_callback is not None and progressbar.total is not None and progressbar.total != 0: + last = perc + perc = round(progressbar.n / progressbar.total, 2) + if perc != last: + last = perc + await update_callback(perc) + finally: + if close_session and session is not None: + await session.close() + + +async def download_to_file(url, destination, update_callback=None, is_ext_subpath=True, session=None): + if is_ext_subpath: + destination = get_ext_dir(destination) + with open(destination, mode='wb') as f: + download(url, f, update_callback, session) + + +def wait_for_async(async_fn, loop=None): + res = [] + + async def run_async(): + r = await async_fn() + res.append(r) + + if loop is None: + try: + loop = asyncio.get_event_loop() + except: + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + + loop.run_until_complete(run_async()) + + return res[0] + + +def update_node_status(client_id, node, text, progress=None): + if client_id is None: + client_id = PromptServer.instance.client_id + + if client_id is None: + return + + PromptServer.instance.send_sync("azure/update_status", { + "node": node, + "progress": progress, + "text": text + }, client_id) + + +async def update_node_status_async(client_id, node, text, progress=None): + if client_id is None: + client_id = PromptServer.instance.client_id + + if client_id is None: + return + + await PromptServer.instance.send("azure/update_status", { + "node": node, + "progress": progress, + "text": text + }, client_id) + + +def get_config_value(key, default=None, throw=False): + split = key.split(".") + obj = get_extension_config() + for s in split: + if s in split: + obj = obj[s] + else: + if throw: + raise KeyError("Configuration key missing: " + key) + else: + return default + return obj + + +def is_inside_dir(root_dir, check_path): + root_dir = os.path.abspath(root_dir) + if not os.path.isabs(check_path): + check_path = os.path.abspath(os.path.join(root_dir, check_path)) + return os.path.commonpath([check_path, root_dir]) == root_dir + + +def get_child_dir(root_dir, child_path, throw_if_outside=True): + child_path = os.path.abspath(os.path.join(root_dir, child_path)) + if is_inside_dir(root_dir, child_path): + return child_path + if throw_if_outside: + raise NotADirectoryError( + "Saving outside the target folder is not allowed.") + return None diff --git a/py/msspeechTTS.py b/py/msspeechTTS.py new file mode 100644 index 0000000..0f14c76 --- /dev/null +++ b/py/msspeechTTS.py @@ -0,0 +1,64 @@ +import asyncio +import datetime + +import edge_tts +import numpy as np +import folder_paths +import os + +async def gen_tts(_text,_voice,_rate,filename): + tts = edge_tts.Communicate(text = _text, voice = _voice, rate = _rate) + await tts.save(filename) + +class Text2AutioEdgeTts: + def __init__(self): + self.output_dir = os.path.join(folder_paths.get_output_directory(), 'audio') + if not os.path.exists(self.output_dir): + os.makedirs(self.output_dir) + + @classmethod + def INPUT_TYPES(cls): + VOICES=['zh-CN-XiaoxiaoNeural','zh-CN-XiaoyiNeural','zh-CN-YunjianNeural','zh-CN-YunxiNeural','zh-CN-YunxiaNeural', +'zh-CN-YunyangNeural','zh-CN-liaoning-XiaobeiNeural','zh-CN-shaanxi-XiaoniNeural','zh-HK-HiuGaaiNeural', +'zh-HK-HiuMaanNeural','zh-HK-WanLungNeural','zh-TW-HsiaoChenNeural','zh-TW-HsiaoYuNeural','zh-TW-YunJheNeural'] + return { + "required": { + "voice": (VOICES, ), + "rate": ("INT", {"default": 0, "min": -200, "max": 200}), + "filename_prefix": ("STRING", {"default": "comfyUI"}), + "text": ("STRING", {"multiline": True}) + } + } + RETURN_TYPES = ("STRING",) + RETURN_NAMES = ("MP3 file: String",) + FUNCTION = "text_2_autio" + OUTPUT_NODE = True + + CATEGORY = "MicorsoftSpeech_TTS" + + def text_2_autio(self,voice,filename_prefix,text,rate): + full_output_folder, filename, counter, subfolder, filename_prefix = folder_paths.get_save_image_path(filename_prefix, self.output_dir) + _datetime = datetime.datetime.now().strftime("%Y_%m_%d") + _datetime = _datetime + "_" + datetime.datetime.now().strftime("%H_%M_%S") + file = f"{filename}_{_datetime}_{counter:02}.mp3" + autio_path=os.path.join(full_output_folder, file) + _rate = str(rate) + "%" if rate < 0 else "+" + str(rate) + "%" + print(f"MicrosoftSpeech TTS: Generating voice files, voice=鈥榹voice}鈥�, rate={rate}, audiofile_path='{autio_path}, 'text='{text}'") + # asyncio.run(edge_tts_text_2_aution(voice,text,autio_path)) + asyncio.run(gen_tts(text,voice,_rate,autio_path)) + + return {"ui": {"text": "Audio file saved to锛�"+os.path.join(full_output_folder, file), + 'autios':[{'filename':file,'type':'output','subfolder':'autio'}]}, "result": (autio_path, )} + + +async def edge_tts_text_2_aution(VOICE,TEXT,OUTPUT_FILE) -> None: + communicate = edge_tts.Communicate(TEXT, VOICE) + await communicate.save(OUTPUT_FILE) + +NODE_CLASS_MAPPINGS = { + "MicorsoftSpeech_TTS": Text2AutioEdgeTts +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "MicorsoftSpeech_TTS": "MicorsoftSpeech_TTS" +} diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..753a1af --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +numpy==1.23.4 +edge_tts +asyncio +