autoradio-upstream-2.8.2+dfsg.orig/0000755000175000017500000000000012420474320017460 5ustar capriottcapriottautoradio-upstream-2.8.2+dfsg.orig/PKG-INFO0000644000175000017500000000226212332265077020570 0ustar capriottcapriottMetadata-Version: 1.1 Name: autoradio Version: 2.8.2 Summary: radio automation software Home-page: http://autoradiobc.sf.net Author: Paolo Patruno Author-email: p.patruno@iperbole.bologna.it License: GNU GPL v2 Description: \ Radio automation software. Simple to use, starting from digital audio files, manage on-air broadcasting over a radio-station or web-radio. The main components are: * Player (integrated or external Xmms/Audacious): plays all your media files and send digital sound to an audio device or audio server * Scheduler: real time manager for emission of special audio files like jingles, spots, playlist and programs; interact with player like supervisor User * inteface: WEB interface to monitor the player and scheduler and admin the schedules for the complete control over your station format. The web interface allows you to easily publish podcasts that conform to the RSS 2.0 and iTunes RSS podcast specifications Platform: any Requires: mutagen Requires: django Requires: reportlab autoradio-upstream-2.8.2+dfsg.orig/autoplayerd0000755000175000017500000000567512332264612021757 0ustar capriottcapriott#!/usr/bin/env python # -*- coding: utf-8 -*- # GPL. (C) 2013 Paolo Patruno. # Authors: Paolo Patruno # Based on : # mpDris2 from Jean-Philippe Braun , # Mantas Mikulėnas # mpDris from: Erik Karlsson # Some bits taken from quodlibet mpris plugin by import os,autoradio.daemon as daemon from autoradio import _version_ import autoradio.autoradio_config import autoradio.settings from autoradio import _version_ playerd = daemon.Daemon( stdin="/dev/null", stdout=autoradio.settings.logfileplayer, stderr=autoradio.settings.errfileplayer, pidfile=autoradio.settings.lockfileplayer, user=autoradio.settings.userplayer, group=autoradio.settings.groupplayer ) def main (): import logging,logging.handlers handler = logging.handlers.RotatingFileHandler(autoradio.autoradio_config.logfile, maxBytes=5000000, backupCount=10) formatter=logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") handler.setFormatter(formatter) # Add the log message handler to the root logger logging.getLogger('autoplayerd').addHandler(handler) logging.getLogger('').setLevel(logging.INFO) logging.info('Starting up autoplayerd version '+_version_) # # Use logging for ouput at different *levels*. # # # logging.getLogger().setLevel(logging.INFO) # log = logging.getLogger("autoplayer") # handler = logging.StreamHandler(sys.stderr) # log.addHandler(handler) try: from autoradio.autoplayer import player except: logging.info('gstreamer1 import error') logging.info('try to use old gstreamer0') from autoradio.autoplayer import player_gstreamer0 as player player.main(autoradio.settings.busaddressplayer,autoradio.settings.audiosinkplayer) if __name__ == '__main__': # main()# (this code was run as script) import sys, os # this is a triky for ubuntu and debian that remove /var/run every boot # ATTENTION, this should be a security problem path=os.path.dirname(autoradio.settings.lockfileplayer) if (not os.path.lexists(path) and path == "/var/run/autoradio" ): os.mkdir(path) if (os.getuid() == 0): user=autoradio.settings.userplayer group=autoradio.settings.groupplayer if user is not None and group is not None: from pwd import getpwnam from grp import getgrnam uid = getpwnam<(user)[2] gid = getgrnam(group)[2] os.chown(path,uid,gid) if playerd.service(noptions=1000): sys.stdout.write("Playerd version "+_version_+"\n") sys.stdout.write("Daemon started with pid %d\n" % os.getpid()) sys.stdout.write("Daemon stdout output\n") sys.stderr.write("Daemon stderr output\n") sys.exit(main()) # (this code was run as script) autoradio-upstream-2.8.2+dfsg.orig/doc/0000755000175000017500000000000012332265077020236 5ustar capriottcapriottautoradio-upstream-2.8.2+dfsg.orig/doc/playlists.png0000644000175000017500000007571712332264612023003 0ustar capriottcapriottPNG  IHDRh˹bKGD IDATxwxTUw:ɤJB)Ai*E`A,"((bAbE]" (e[ IIdGȐIf&dҿɳ;ssp̝9kt!Ba!nB!h^d)B!jD&B!Fd)B!jD&B!Fd)B!jD&B!Fd)B!jD&B!FRx/p8a"D67}rc7C!htrR!BԈL B!DR!BԈL B!DR!BHVa g$Nji8˩5:Fjy,2o='ޑX>>OQNm6;Iv=,~mu[!&d qz|`hla|vCo" W GO uujJk2 ?~wv[=<᷐~h}stZú󳱱Roe-BA&@Ii1)ϥ.`߆Q8tf9nLY8tw !ntrpW= 8ui%;-Ξ >O]g8^\H>¹+{OLͶ(O@b#FB!D HnwMIOZ)eܷ{;'t@ώ#SοyRaMQf]$Gft̽ 緰q"t:-bzDO8 9 hڜ_R\?UFF,֦B|t&K_pNkG&kҝoпnBdB!@ !B B!@ !B B!@ !B B!:BɵV[hҼ B!DP qu^ /[=B4Y X۴mf!2(^N<>V&KU)7YVA]oO`˳Ov޲_lۙs;ټG<1Lk2M)J4U g$6vud dܨ,˝9@rR.{XT!%^NM   ?vq.'"*BrV@>aalvvBSvU*ڷs'95 ȊW*^[yTE]{@KPӭ|\ۺko*aoo˭moV'c/B`VtZ)?X|1 pHV#3PW^O&$g*#G7_reZA2B!DVoI&`` z;[gUо;Qe_c_s ec2lp  dwgɩ8ק Qc]N^!hnz5t9T?&AE _5נIu-QKH(4Xp/1z<{!e dd;wR ر;77+O2ER R1tPa?*4-89ّ,sXSJ~~ `1_d͏R\Ru|vL6};%9x@VXdwiحIsˁ MϨ߸ok]^ޞxM:1%El=2u|_a?_ Cg#z8#=gxhw>c`_͡t q[M[!Y+h6X±DC􋹇Gp`Þ/),VɿA }vARY&ɉ ۸)vb"v<  ]|kVSǾxzo!),PhjJeu:v?_z`cck_[c !Ds"H!Dŀt yŅ#0.0Svq_Cퟘ<^2>a,^m5Cg֓Nm@ qpZAQ.vGX-͉L ))GptX͑sq{ piA{oaEfv+hJj-N~4eEri5{^e-/nʢc !Ds":dvܓ[_&2'Zm '/`wIq']q,a>' B! S)`ݎ }is/~6>OQJ-A'ށj̾T֒9g>L}~l]$xNܝ^k;.1|W|5coJ;B4<~ф?HW]Ove@PVlس3QP\Rk{Ok [ WMbpId&qn}Cy#Ue.$AӢKi٫Y^fK*uDnH/>zՎmIo6u|d< !D v=ԥ?9~2ygߧ=`ƶ+cLCSRȟ㻍r7{g>X>Z?|$:Œyad`зd)|@U)JF{4!~xh\N:zwzc$(!E;~Qcg0ap۰ٹ$'KLgtj8Jnǟ#$0ABMbw9{{$}rOQ:7_l'JK|op;p1=πDw}~J;t-cڔq{l W'}FtB4˩'?u\_[/U3s}>k"w-Yv=n,j*mƦ-c!VƺWE.HUግ ))Y:}+,rM vve+l~Џ)U%$؇~ôCd5o!#9zqAR-2N˟coĀnqyJ~w6_Z:|C$.G[~% ٫^&Y|W\M` >sm8s+=1zyS3<;JucR4%cFrh"3f.LJxi'qJ/NAa17`n/?RQ{eٳ4O<=m>6[wM[L[(4wB O:g+0M+]ۮύnY9߱rfOWⳕɛ*/U߮?X9, 3g&j^&!m/C:p%W&MzϤ2M*[Ͱ=t׃]̇6ҽ|}cVOh>[jv^k9MltSHÊﷰ=x1y1Uڃ<\Ȩ8q޻5`ʕ4.H !)9R(ڰ uzn ?Re1չ!p ɀn\U`>lf~'d j:s 2Gնi/Wx!3`t<<8Uƻ݉Ӊ'u2x~يd>妆lb.b鷧qp'boo˷Lƹ}{WG<1LkMeɝhDKԢDʣC'r6vsZ~};vlml֭5nxq"ppr#,w\=4-.ҡERd@p`ؠc粗pW!DC23 _*a~Oƻ>aalvvBSbm]ٵ7CGӰAt6طTBYjggK L2 ʵ? 7oچ럔 =>Μ9*ˎVc|㲼{l$gϩHQslEťrtUƳr{'ݞj8^m9p8M~/h25i\keK[{U'GsJ.pR.'Ner.1qhlN#,?k/t:23 sq涁cN΢5gd<%NkT%Ϫȵ_$7OBo.PTTr,aIYsVr.IFf!:tft:,ʕ3wn Wz>W'h5钌g dlvND)68rr,eIYsVkXElmhʰA9͎$WT ȦWm%bxaooK~A EWcW'h5钌`9B4w-=;pQٷs~X֒9g>L}~ljj'TyofwZSǘ \+KX5s h Z|yfK*_r: $@@?Y5cIo6u|F< !Z@ ъxN 4ė`;ϸ| 9zR:F{k8w. &N>fP ߓg-D'- !1G%ꇛ3q88Rۏp}M2MSO|U?][:xt!IWs>Lzp(?}sVpp"yo %f _s3~@}xy;_/Dz;~M#@qs E|hs?YEwlOV˝?BAA1Rx'xEF~hWl\n IDATb҃CۙKUڡLSԻSҳyo'4ė_BN2g'1ؾ?`3$Obk+`̨XXm72YOٝ0,״z:uJ_US#v!>M'~HN&x !Z@ ʤf_r6\eϘ}`}C~ =mGL>zqćRSTނdXMd%5,PPe6^t kF/t/g? }zGܲq09rhΦ<wg<*~A:w cTl|# a7Kmd U6,j*<xuɺk2B9>mg2m˿jJ*\d;ǴsӺeje+N*bjΦ%@%SPՑf<0fh^Y~pJ?0w s%:¼a2tM|cX<֒ڧi$$&SCʁ.:c}jQ8E*KG.O妏 _N][ukǻoMd뗵=.w.mx{]Kl r`[Rӱko*aoo˭ ]rU8hKQa)ӥZ[],\.&;Snb/RvEFFPRNSXXmٻNQ9:ҡnj:PrD.tClo3 5Rt:r[{ŀTSSu>m]Mmc턦{WO _*a~O۸v 6ʬ,z|P7't[jB{e\" K]#AJj!e?)((߭/;oMbA&pbjg[6 5 72\ ^XkJ9:/zÞ#|cUi%Xۓa3ś`W;./O'D@_B^qZ"VGfV҄4vM!8H}# Rko mG Lܨ kG>5Uוky$W;eLg/:v$/OS$ꝝ-#d2Jxg*TؼCп p#ӒPce]!럻kdVu<Gn^0[dl';'2t^&Bfe{:>׃ 76{@bF0oƠ`mʳqiX/y~A2:v[y{~;2;sߟ\ڳ^P6j>j[մ)|%+6gDZ=.TSL.8;۳gpr]Vr MmMW8u6vč5dVN]w ŹM?W5WɁȐ gϩ? ,! 2qU8pP[TNǥ+yю5vӌW!D]5 d'F7v3h ټ*))°Ax{93U+^qx O?._1x\sa[٩yp,>eZ|V䶊_;eZ|װWY[d !,%5h%6oʵ$5FcԝmIUmǍԚf>@Gl"<ȒFvjG3~fYmTߙw-g,kj{%UQ;M Mae"ں3`:vo/TwPIVVU뮘})$߯e<;\PW"=XsZogΜSq6![uel[oZT=6T(9},bkyUƳr{'ݞj8^^m9p8M ъRVbp._¥.\ĩL%f3nt;-c$ŹTW:̬" oX>%tYjd<%UQW2XEr4 -nEEnɎ\ 'Og^&j ۜkK,"Yv[::&UdZUg*ߵ.$UQ@ JmWDt@paC-7x`0v${_ v5(&9Q5"v6 ueؠPLf3ww+zBTr Lƹ}{WsYGV65_#؀}ZaU1_SȅkZr @V~-n Md1^lr RTZm5<l2+LI.Bԏʯp$Xui*gn˶kdX"z/r"8PSFF\3"IUmpbg`k\8! Hԙ,4m]qG[~] 0|+]=v"TeO> 87ua&~ԜHFk[u:k*g<Ӊ早Th4ZFE3\*9)=t\MOrz^sPrh:ݺ0\S@jbڱ'9dTG(f~IFk[ du9s/sqcC#r5Z'pBQ=S/GG;/"#ܱ KpU8?.5H2ZتHKs/stY<2KҚSN]ӆOrz^sףTZPDGy`cӼޏ:h?V@V3W)%?r..ƛk.ʏss5ZHU>?ɅB)Qso an]} XhO~xN@MmHZV@˙3Yl~Fe5&V__X9Dߝѵs̲3V\\zFEY}ڜr-|\.KXNفCebݺxJvN~cYwP:O/KZ)%1V\Ys-W)Č%/˹/GG[K'=;*yg-IumV˕xoEYՔr-L h,ΙN-{WM"jTyt\93,) !Z.@ &gmʴvIFb<[\*,WK) !Z. Эvb~Mi9sӡGex!WulzԻr-I͏?')Yϕ/7W~p ZS%˵}jON^?Y2BXˁMe*g5O'bеroKɖo-!.TSL.8;۳gpr]VhZswo-[[oY;S[r>Ȱ᳌ny՛ P;o߽jQ;` IdSi59МsʳߊKPɆ?/HHddˉ)8{NELl??gׄfK*jwW/#}p'U~\7K^`_ܥs[GO?zMU'u͖4\eZEjcFW֔T~pbg`kl9Q`OnfѨt:|Z6mys9S7󔖖1:ߛ̀]@AU d<^d\~{M1^č{$55K5wsuru ܽkJ2VN M_SaKKɖM2fYT}I(JBT1oO,*W};i*^z7yB^9 }0˗Lg>se3?qo v rS\,6Yqs EuX|_C3vL?3t~ Ȋc}z:}Ȗsssl_<=,12wad *L\X|˳SkTj7Bќm;q>L2}~0?&=Tvum?Oj+Jea"o<&_L>oІu{'ۙ?|^ ?LDHYfɒ:V5I,r-y7.$x_=1o5ԥW҈a.[Ƭl~~S1&[VydfXfq[Te>V;_ɖBꥤdq)Yocc %_Mش0l:UX^/nR_kuZ]m궴-|EZ3Yk[]ε~7o5ԥX܆̪d&\OyeզX\b6-pS[*l9!.]V|t YT[uyS3<;J6|EcFrh"3f.LJxռR.O]Rt`SǖVV]뷤ȱ?w%!!>(*kY+d&\K3jƤVkXE6nVca+|%[N!`kҹSf#QPPDL׈ў>Oh>+l3g}g{e+cjuN|[˙B}xf2M*ŋ= kymյ~KO2ﻍ+6rնOCˁj9!fSk)[["=<0aPВZsMÉf}#>mEBn-ΜfۙR._^Ȉ@bvu$NXӞ5vm;7ΝEkȐ`EMAK̯3vu~Ij1gL&˖c YvZv׻Gn1"D+eʬIg팹ɲB4@ J:sieY/um,;!DCi_a !%יN.NVΘ;z;m6HRV%יN.<W_/0;x Ց,;!5WB-1\v̺t,{3x~ Ց,;!5Y=rdѷ?6y¸e>pƵm- EF3~L;0ϲR=Rki9!O6*g~V9mȶ Ukjr9)tO FE`/:kÉQlv Z\iU(6(DuQS_'ZjNӆld6 N Wr,Uk_$nd@D*3nɗGЖ?1ڛʡirk bx[J';:/[Ÿ}W2j?.gutruuv|J:lYx;)ZWDk#u)iy՝c߻K[5:^zYqxy:72+HNcgkCnvMaЀ`:ض3Oe[Ly!nfj:2 %'uX2>*9ALg/:v$/O0! %VwNƎe.riŶ[Of5F2Ttww$KY^#]:yPأh9ק QH8Mlҿ9|_rj-ѻ.._r<رY 5(U/QKH(4XС/i X4>:mB%ZrNUϺdPz@kJ/'GsDK:F5YxsQ|d;wR ر;77iy9'K2?sNk ٞj dLgoNbk57Hc<GQoa{ Sjmiڔq\ `Yf;ŋ{{S]cI)z-Ukρlu VYůlmm`AfUɫ%gٵH!&)hZK~]m $2"ؾMWd kse3?qoW:%NXd)D+eն.kfЭk, XZrZuJWB-1Β촚U .йS[^xqVogx}D2ʽ,;!DC DK̯3VۺjOyu">_ W}J򜼆Ƚ,slMVJB4q-1\vZmM?fؓaccCD? b;1 eا3顡8;;LSV}c{'YvMiR93gH!@ !jRW %g4ə !Dc IΜ k:!hz#9s3'9sB!Dg IΜ !-U–9꒜9$gN!h:9ə,KBYu)9s3'9sMKB5H!)Bԗ&X$gN!rrR H!Uc|x7Ytic7C!%@ !Z+VT*B,RrevMBfI&BVĉt:bbb)B,R8qBADDDc7E!(^N<>S>fS3oQ"#?K";ت瞢v߸ sm\_Eu[>V&KU)7Y鈏K.gh!@ƍ޺/q̦*%5NGa\ՏDܨ6oZ?idYBA!.e[ӿ Qs'N? !DXuYձe5N"8ȅbsWrḾa= %(\Շڔ]r"8P *W2h`0]:y3"͚主9א!3OGTE( 5Rt:r[a![phK"Emrgt:X]:y1䶲]ɨ=mjufǭ1`kr.ۺko*aoo˭mo>NhJ-]||<Çof!DeK4)9A.ބSlʵmHI'EOP~۶I0x`0q"HK/sG/ II̶ͧqkvj:2 %'.sس?wӣ/l2,wً<˫߫qeff,W :L8~:s Bh+p..vVuݑpssĚn6\HN'UY@`c#sX(B,˵+*.o7w=!|6/H-ngvN1_wFjal _6DEӧ#a{D~>[Ǐ l!SjewE,:TڡMnOk T9?+!tB[_5{ut£wjͨB7_:^ dc9]w+þ}رk:ٳ/))Y{ƍݨn|Zɺt!7`J͑^ݛvOeSxy:hGrkz4a1|8yh&=#̬K-쀛F /{~oBcm Dhk/|U-̗lnnJ"k1r&0*|S8-e+ٙ' mG<)tlWWG.فiW)J1.S!&'6T 2sL&NhPDDV@6sdHҧe^B*IfV.sߏc65)$&&jRD U ؏""G f,\طݻ_駟:biii8qB3""D Yt)ˑ#Gl}ׯ ,`Μ9̚5H:|0a($J Ȃ ZX,.]jߘ1c3gSNwߵu8RE|X%|DD*H;l2J˦M9ɉ HLLC+Mvvf ED*.''ŋŋ9͍WWW BJJC+RD)֯_ORRRV+˗//|\;HOOuHRA :ZC d t˖IMM￯ꎀ֭[GTTG{TTTZu"",33%YdI5DT_a7NEDD(d@JΊ+lۼy3oVnnnZR믿n됤mHkР#F(`6R ޛ=~x7nO?m됤 Ӊ!$$֡*J Eh̘1$''3ayl\Ftt4aH%S)R> L0ƍ3l0[$pL&AAAEDVQ)RNӧO'66QFfg됤aJR̙Cjj*ÇgÆ :$)AddfEDT@{uf`GM6CDQ)RANNN|1`7 HP)r ޛf0кuk[""R(B>>>]$ fAppCu@TfGGG5DTT&IEEDHJR75ÇW *߿l.޽;VO?eԩ:ѱbU.ƍ㥗^GaE3Я_?ڷoϦM߲e =zo߾h"z֭[ k߾=G-rnꫯҹsgM7DJJ /"ӇvIHH[D*bW/^lFn݌0 GGc 0pcڵa?`aNNNO?Txb1 0 #&&hذab&Md8996l(h|嗆aƦM]XƎ; 03g52 0>C4 0c۷y^zFbba(vlmafcʕo*[v8;;V[z8{,РARSSqtt$##Rx|VVY-0?~<| 6mcǎ8::r9\]]ӓmۘ1cϟl6?cZIKK}DDDs|Gٲe #Fo/,eSWσҮ];vMXX[D2 9+9FTqcQ*db1dȐ·\]\=iӦO?nݺ 8okrmzݷz+VٰaC:q͛74""M _~`ʕ%ӵkW~6nH׮]->9 2T`l޼:9>))Pϟd"//tܹ4̜9-[2qDƏol3bǎoooHPif͚? @|||]̙ /@xx8/s̱AիIJJ"77[2`z!fϞ]ӧN~puu%<< nݺ=S굼HII!,,}i&nH.]8uT&8~x#Ν;5""H@ڙg ׯ_O@@mڴa~:te۷N:1tP>r?:pANmVEƍܹsZꊞCxL5Z씃ƍݝ4Νk됤 :vlfL4\s=YpaFX;;voWԆ[Dt3YH\lgժU >^x~:͚5GaɶEDV3"60tP̙ԩS+ͩS u(""naȃ>ȉ'{_~V?+FDD*f El襗^bĈ >}:ZlƑ^J ElBRqǎ[""Rk) ;w!ٵǏG*R/$66;\[d..".""O H _]X#R*F3""UO H ҽ{w.]{+bpұchѢՔ@0Ce֬YL6UV:ɓ'@T1%"5ɓ3f [nu85R^^^G'OjHS!qjΜ9|8@~x5p^{5Gi;}Z@ѣGY|9O>$< SDQ)RzjvA޽9}4̙366ZnSrss iHRtؑ˗l2*N:U8㖓ÛoimUV|^{*J EDܹs#j7 %%ņFppp<HRлwoRSS)?33s 2 .u_nn.s-\-""G H W`?ma6cbp7.ʥRdРA8qsx)b|c]}.TD:(\\\X~=?8...8:^?77_~O]Ѻu"?;880m4ZliDDj?%"5g&&&~zꕚH9s>#f3~~~<{LI`&lc/%}:')RuLZ4D^^ѧvbs? wxՄ鸈H-ۻQFiRĖވE3M:y i)v+7!-~Wɶ_kU6Q$i~~M:quO%F+y$7n[VƍgDʦqg!3αbsE=tn=M:ҵ8wy:\ٔSuR?hoaƙ:z%"R)G.q_&Mk9";;KGҹSXsc,Ʒ[dedfgoz: //퇾(:~W>(A}V:}@OD.-l;ЫW/mX,q&61ŒGFD$$[}o;Z4ȎC\:2[{ ts,䎇ϕvGDDPiL&SV+GQT"㛟_'i~[xm7Odb8pmrrxdb9]Ob4n蒈Hg2 {BRc۷N:ֲeKmT_~Ģ_u6a2qs`ݫyrC!a "R4i':vHHHHvT1wO^3[u8""r%v.mZ3f#:险$+.Hu iҤ o:L j׮C:LEjluRC84ݳ@5NuuFӺE[!"""WN'b7Z;媐V=g^;>QdŇb1}qt,ۓ5?|W^s7,u6ȕ =:$K,{q$5*سLmsHww Û_mORs-4kV?$6pƚg*d[zp:.Ñ)E g.[e4IYZs`x~ؾ!ݻb6pKLN ƳEzl= 8:5(rlnQ+\DJ"v揓9ζmiʋ筶&frl&ChIrʐ>,o/gdlϝΖ؝PxN^AbRf:ʯ<WwlcO(r0""A N`IDATwO?tn(:ʿ}\9?4%p)|IDt\z6G%Ө !At 1.P\D2Ojßa=E8Y\9/10wrq̜'r2L H2op{Dd2B.\ ]nLZZgfYpSb{:""E d51ɺmuWc xa $$UNubZz_NS􋓝..!,7EzlۙXyX,f8wʁCIfk#ۚKzzB#j|;) _w-"?bݶ;9 ]k~keyy|ӌ,<3soUU` >ϝF3%TQ ,}Ǭ2_1ݮyMF^Ql""JhD+Vn"MiHm["v(nܕcӟ_?}"vřw8ӟ]Ĉۮ;m^S9<9oOd(f< _| !J?@NnܕK&qIL0s3̂K@tt, @v#' ?YEͷ1ϙD~ y]_3ntnO'V""H;ofӣذqw+Ne}9 ngg kΆvӼRuz0yn;^]3fS_Kآ1F% ))i@+{t tXgǮ"X mƍȤ[0`mEYv=e ^Dľ)C&%a?6z nL2p{ ԅ5mg vMa%r4ܓ߶Ep?߬>t[~/ܶlŏDTm}8K_zEObO^CРH#/ 6.1FQ4KHmWwa*\yxg7aEC@rȾ]˸%s罯qt"x/};1Y$'?A||2^Ä73y<?qcoZ6乗_E6x~٬n;n.;{o4/ZFӦ O늈+@ءX|xIEWBOH7 .. WWgINIGoyqrrfrݯҵKk^wƎÙ3LB}mӌU $ȏgg-gX2岷/տ>u,mC1dt YthXG' cԈXh=VXYDӯ2LKJv"fك3-}C$%gĤ%,>kcr>|/{9{m ֛[:_r쎓˥)W6h2`~rgFモs\!um9VR>f<QYzKgܪodLuWmcΑ>Ӕz?f'qE8L.y50 Ǧ-JDDD/͟K֛aDI&7nLUV>\lr er8s6ȂS]<A d)Zz_NS􋓦*ARr›ҢY=Ld<,3;g$RRm%==V!ƧԐ{кUߠDJCb#h*mBoPYnafDpss(H &m㍋mB SiEiiաmD$æ М\r$*w7GzuoB>eڞ~M;88mO#"jDDD6T:RuTJ9y'nԅ.;.:+7&J쭈HRĎ[_| ?r`"}o~s}$8#OU!Q_Zv#' hWW rme\z+.Z} " >J/"bTRZ mƍȤ[0`mD'H{ ?;vEzu2.kaCC){B3"PllQdaIl\ #*]y\_k,b+,k"")sD< ?|l|a76\ir;{o4/ZFӦ On=%"c?!Cb>a]+WYt0NE jHd mai)۶L>WwдPzE<Ձ,@Jݥ:"RSh9pCo So}m^dˏqpuO'"""" ߯!nN$w2juwDK=\ӳ]l^-O3oɱ)c\y_e=ʍТEc>{bh,z\xnxF l~t k9? U<)A|B2?2iL3kW/μOfǙs,{@x?y~ƝXsrܢssߚ#O˾J? T]YtZa+G'MʹLyfAb%##AÀj|22;vF)yFᖛ{ptb\DDDh˘4mڐĤsX:9oY٣-+Lm+q?:iF\EYG|}*t-Y@VY~yzzf"5T:#5MhƘWt]{?hJX uzP5UURDj  {gׄW8ꑞO䭷b؄T-xjG%ܮm?b*ީjH]R\Xl2נXlk> ~3*yk^%yz}u͂/s ]X4>tE-h󅗗TiA~}Z*"uC5=9%|/cl?>Y$~y]giE 5(W\) C'N \I-WTF *陗WbGӿ| ly^h;-rUMUkt [Vgظ$rr:81Ky>9ʴ UBqqI}\5CqPMUmtHVu fRH)-l׭riڬ#{HL3r!@HMg EDDD\@H(rQ)""""RDDDDEe|Dj>|=g^;>Ƒr!GG3-[g@\]*SUx=efi_t,tU+"RU@rw\@ű|pw?)ap''nf֥,k5m\EDH;ƟNa4vuMsͧ8jТy=nМo )9Uk1tp ~} {?K\|J^ywWulHl= 8:5>9Y̴  '{%>!oٹ 7d[q :8?Ntl:mZyz.lNN#7נMQ-ZpuF|1*S^AbR&IY߮alϝΖ؝p}*/cwiL5tx?9&Z۶#ҡ7mZyqFWDjhRĎ %GO$s> @.8990vD&ӥZvcddP݂_wVF ] E=~-|}\O)|IDg_FпOS\]i#L?ߟUҾ-lpTr})m xz8>ԛHJʺd#Ӭi^NwO?lߕ@zzΕHQ)bG%ѳ[cلEIMf&_/Œ`%x*ydΜ,`$ E%ałDb嫗q,^nL(%~;RĎ9 1' lA|B?o%Y}V9FN\sps+|hk/Vw<àOo<=HIub@9yDMMHg*K5#Q3rgg`*ēt՝{YZ0(u߅J˺bX2~A-=K`/_ߢG;J E5=]{qt4CN stmp "6A-ײ& 7gkNle$&B[{Өjn_YYf?<rs ?ˑս aW\r_R8ff^^e;rQIa%ܤ ;i)[mN.qn0DD [DDDDG H)%""""R.J EDDD\ [DDDDE3""""R.J EDDD\@H(rQ)""""RDDDDE H)%""""R.U%m?m IENDB`autoradio-upstream-2.8.2+dfsg.orig/doc/jingles.png0000644000175000017500000004746112332264613022406 0ustar capriottcapriottPNG  IHDRhbKGD IDATxwx漣B*B"% HT)V"x^AA bED!b"EzIB)J&Kn$d7?׵dfggvޝJQ!XwB )JB!L%!&CB!EI!ɐ$dHQBa2( !0RB LկXm¤M =:<^AoJB!L%!&CB!EI!ɐ$dHQ2fڤ?+BsRK…}f  !RLLNHJ䅿Y43D su槿f_fTtk=xS(3/*zwN05;XT=BTMߙ{w~e2:arw#l?Ͳ?fww6md=:('\z{gҲ`@!|͜Tv Lɦ_1㬄B9|W^[ *J?*aVfa,wkW-MR=HI[ 9 /i {axgʹ_9.t.cPpsgI '?>֠]…}M ^9г(b$৿ީf !DIQ2CqW2onB\.dHQBa2( !0RB )JB!LF E2V[0iM= B4x5lv> .=B,lw3hЌ; cpue҄2×.?MZz>3v3LzF>K=OMnaG^ﺆ]KuS1;qzke !jEi` |;s.h٠N֠`Md?v%/ٮB`sM pd<-bc/Q9}6͝،2E7JV@Zz>k_`ؠ L؉d`oEDtB΂ٮAޗ,ڸynQ"^Q-F{M%pP2Gߠ]kWZtMhHJUkh E̹t>vtZ:v\a ~#E9:'^qj(4w")%{Ѵ59߸ARrIKϧ?8p8gQ֒,5'OYPa"rr iLbr]9z]4lCF #.,&|&BN/^Q]Vu$#c'RqqJޜ̹ T*hʕ& uT_$,ԅ|پ3=$;V?k3ilq!)*R8z2po:ߡ;N]{!+Wbi"<fve#^Q]Y! $?OB )JB!L%!&CB!EI!ɐ$dHQBa2^ܼGѭ 727 W^׎7V?ydn==FmB4F7EWxyǵ{zuE%oF~{>ې[?{S.dKKZn]øp!IM*3_EQxU|d=M<2or>l-j>{t (ȋDbR~gIM&0fT;ϋ|͑xh;}==-|9 KюúWhf;ۡmPz{';'{Hѐݮ='9t8Syn9ˀ7e73a\zDd}~/EO1m;~79GY~}:h;+xY[Ҙn2orD]:'.2i-`9{UnmGCǟƲ?/#wڵZ!DCU:z2vTIM+Z/=H3w'ӮY;+ӦD?6M"_}? ꇗXO`kȺy+˅_Ngn\BDgCѱea6X ԧFaȅN&_GU*G.{܈s$i:}7QX!1)[\:o'VFms_շ<~c1`W&(/QGږy!@)VʶG2#A{ݞc/{hLrr{qR2˖ 7r?3?dxQ //W{W:3Wh6{Nո-ltx[)fxúdF6l:yi:b}|ڵ փV5BC|x;o%=&Uқ(|ݙҰ%9%.C1,[QtֲQ.\LΈec_t9G݉}gϫ,!j) Qzn+`eeɀNOӽ[8kbRѾ}szm1%LHoJBԾz?$BhHQBa2( !0RB )JB!Lы0Wj :24HrZr>d1$7IaZzG>@µ&̲(@ǟ|JH7Z靦0IJJݦuɏD$9N<p-P=ɰ9tfۣ 9ua '"cC^a_x{0mq(j-"HJ'dzu_fn*A=o\I>Eaݟ_G8ͥ\I>ii0~(S|ʔi;o%A-'Ҫ@IK~C6O8#=Cu!>%~'9xv잣3t|+`,#gמ[%0q|Es@7/Km82bXwF NSǒ[sﯖlǟwRkEO`᯴j/ZOeVǚufNm9f2K>ʂgMEc=5ƠVO$7ua̢c6 >ϿGogǮ|z?/#wڵ[/9o,'::oLۥ39~"s^_s m˲3Ab:vhӗj}w9ɡ÷-l4q IIi*zQԅEysy8ieaH+{-R ""|nRzU Ú5%tuoy((8;x,E3ST8;zԆƤV KNqq\M` $VK,\'5ḓ0T%*0Qݍ y$jݖCKP3as"|0K91IWFQe὞e`ē89x7ms_"53wWe>cP9%G;T*\O*OXX诏e_3q:SYv3Wh6{*nٹ$'}S\|>Z+?:_0'QpX㊕"u?9^sUPGǕ{P·+GʫA$vQ7̹΢d4 1qKnn>Œx|a'Gygpo-ʨxz:99Q3$%aggKrJ:ٌݗ3yezb.xlLE?MpZ.Ð+{-Qb$Ȼ=3k$Y}`I<%! S<%}:rqOp'B4 &SG˯ !0RB )JB!LɜSBWG(juV}7C"EIWAt m]{QھvM, cpue҄zn阿0VV4jʀ5&.^`'ܠe{IKgԎFmoc"}k,4<5-VV )~Wƺ{Q:x8W[F14k7pc`Md?E=~KcҴWޮ&۷~IǙs%R91- 1p4ȨE)9%_%)9EQps_o?|}{gΤV+:2x@ @Zz>k_`Đ`cӴ5GO )9)._c{we$Ǥ`ee=|h͘al-/͝9z)9+;̱7)ފm݉R7֍LE٧g#`yEKpW-M}܎E [_4lQ@m >NN\KllFzo(5~7lPPgc܏ZjL ))|14y&s8:ۺ3`._f$/W[F ՛\K̡UK2 HK/lZI 훱iS*F544 iys*;`t %}:R[HD/ #AO{Kkv^=|`t 995JmM>͝pt& _Ȣee{-ퟍq?2jQڴ2'Oѣ={x꧒+3p.91bBQ ;7ʜ66EQHMgc㭛VR٭lS}۳$AQ~Qsҷբ( gΥSTɗǵĦ-Jѭퟍq?2jQ~#O{\oDrJ.%Д5/ЩC3u99ۗmNx lDз/6dd0bh0bg6?u1J>H-g$&yΑ7k錢w\im%YU巧1C;=hڤ?ݙIJ%-=} (rp ϦVcmm%YYjNN##<4wjQRlvu.Ү+VVҩ;y8ʑ4w"KI:cn<ƶWwPP5rBe_ÆK\0<5CwjqK Dǩ5EE G\\VM;J"+Omϼͻmln=,؉T\\l2\HVϙsTк+Ml-h񓩜IX }{}g{'bggIv~-~7d@Age~$yJBdjO0/tBa( !0RB )JB!L%!&CB!EI!ɨtTMu-DT*pq%mKݕ[Cׯ消qEh _ZPM孷XZYпoG~17Q NV~An=@jbTV(nHMy&W%%\lu;_cmpthwQKx ҋƒ{|,-xyǵ{z ZResPY'?[_ntUiN-8Zf:MEyw ̼2霦ҹ*ζ)705ܖGbF .3>[V6SNgɧ7g5ܖًٷ +L!H?)< c/0l{]{Nrp,|yGѵK4n][1(}v)<7nfezg~}:h;+xY[Ҙn2orD]:'.2i-`9{U-!'lfƏݹv-zŸZ4|6lDL!AFǁC%K[2lpåBt(s񪖥Q:kg@הk4-ǵۚ*yygxy}_Ngͺ!]6u$*x2ϴ '5kњyQ}s'5-d:=3Dͺy1~L?Qr^b#6> v^^.,r:Æv"z=}- Cqƃ2{,S# I IDAT'*]c .]ʼnSa<k$/L siJJ.y!7s35ܖf{G$FiaVFmg}dfp2'(,,l~z&:[~{+JqRCi:g:oαعʨj!%q^MfO:M׊fw !0fS|BOB )JB!L%!&CB!EI!0望ʓ<%Q_ʿ y=4t++ 5e@T-o+Jɜʓ<%QʿPB(79z66 '9F-Jܙ0Gkm,+O/_ڄV+1''yJ>3SgP< _i^GO )9)._c{w5J/-;s Sr(.Vw0c'nSۺѥWEGm?MW]-oȪ2wrr Ev$ilII}9smyo0f`Rz=jBfVi\MQBPޯnǭ%7PTySؽ/_?_vKБJǕʡZR̝ GO$6>O7Qg'چ`ii%U3xll,8Rݥ+恑!=Nbrϖƽ|!]m5Z_MƨZhPm)ꎾJL"xbccɸC* $ ++ _$7Gk|c_UHh3?@d_?XfǙs霍F[kU_הo%3$I}b|Nں~L uF2vhEQHMgc㭛VRy}h^~~۫)F-JNOcO*ODM79ˮ}4e tЌ* ӗg/Q( T=iG%2ԙ6o9ra-Q+M_$aJQ=|ؐ}cRRefOΞddpD*..6[UMӳ7]a݆kZ!_4JnIRpv.TkȶζuGkSw 9q2#1 iDD/2{_ 'T*m-) :H?XZw$?NM)*R8z2po:P8 }}Xپ^SK<%! $?I!-J$iW!D&EI!ɐ$dHQBa2( !0uv]u !$&b9F[Os\u{-|ٿkAN[?#꧿< 05c4[_^OF[O7Q NV~An=@jbTW(U7b-#eUڿ52zS抨{,~Gݝ&VemO5fszkҴi]jc+}\їaZResPY'?[ܟMҽW z;pTj4/o?wՕT-ÎB،CGR(.RÇL26ekdCLc9eۧ( o/I^=Gn}2о9I<9y}BuՒ 7Ӆg/&::K+Ku ݹ(((93,_t<]0>7QcgoCFz6o(wzG>@µ-|W-@]Xs|+} 3p\oEiUۣ@͹ثGnn~eT5=ǟƲ?/#wڵTgm2jQٰq3w/ÇUx#COjz$d@ګɆvmJ&+2Y"];{6;d=5+ǵ26JgtQҢY;+ӦD?6Ў`Oځzn\.˳5X 8Kwx6 NGDk{o  d>Uura)..ֹ70xxΝ+Ӫǥ)DzV8]kUM~A^=VӘ1lp.L_ĩTep߰eWU.edv1ih0]Os\/11S/9J*ϿT܎?;"ټ5M2j;kcْ:XBy}(Jqm(G;T*A묫=RYVն.fjzկdԢ dTӿAMeyoSTyT<>ƒ#J[p/%l r X x΢*Nk;w}t`#HO;"cL4l`o!s3w'g^+˯Nی9{:z.ݼٲ ?8ٷjҹ, RYKuKcɀzR抸}s^ǔE 1&<@/ڵ MI7%!jL_!ɐ$dHQBa2( !0RBz-Jnޣsz{|onI1/ܼGUxydv\Un==V,0};o?It~L23;u^^;qߞ.@B(y߯=agg(Zk0,-,p1 fݞ ~f-$%„̘z[h q L<WHLNm{>ې[?{so⛯wW4ibëȸ RV[0.\HdRPE4M1{6~d׷inOx+sjV]z{te¸o>Drs 43X>l-ޏk0~iݺbQ,JWm˲3rD]:'.2嵱*9%=<^ B_n?̢c6 eLןx)Y;_}hj^>rsۧ|o,[c2rxw]KŞBZ)J.Ll=/3lh75kњyo~x ~Z-*yU&(ГNjZY;+ӦD?6`4k4? ܝF6t9^jc jCi7c<<څ9%M_[:vzvWo4y3V`̇yyZoHLLe.HLJC.ce K*( B[ׂnCim\5#hj(%%gsJ tÇEY>.]Nä́x"IFw wUaQ,KSxdl?ݝwf.B>S{p֬woB}ڄnB$䛒O}'dHQBa2( !0RB )JB!LI]^8Nl\QJߦBEp3}T:ݫo,(8"9WӻUWF{dw/ }ϩ!=&Uj3|4E7xO &[+T7`_MKٯ$QRUy<7')9''/Q.cy|m ^LttVtƻs'QPPsxXK?d3#ҧj-~ҧtvQ> *KmFgTYIRRZ|$MױmأT^$Q)UccmŤIJJVk.,bΛ%Gg/f3X2#qwz֐l&]yDTSe3Tp%R+UEӯmê!KB(U%|<xy]Ӯ+S2ᅬziLiTSe3d(C3ʶaUː%!DUzJU1kIG&ϧH6ofìuXd+Pae3GT~~nwY U&yZ!UejT Z/ !bԢd 8~*Gkm,+O/_ڄgpPScqjjCvjRSqq!-$!9%_%)9EQps_o?0Fgk( |m]׻ ~kd-[ܺ wqRvvX[iu}IIʂ{Ю[~ݺ٢.,6Bƨ%Noㆿ#e_zk9NaD|}x|}ؽ/CGRMO/@.&9Dt@]_;*̳T̽ I9$&m}gIIˈ\״u]VVr=Ĥhʵ6꺝 iyG?E󺈍d$ ش )e=*yݶkJ.ܼY(gг7ݻzjw.6Ⱦ~: d@`g'چҴ5ylZRIɹx{z`d#IMd u]X ,-UgpRN6xyVInvvfdwgkIe?K{BCP/^߽zХr-!Qn4r+SG6֖r}S$7H, v-9y:ݼÛU?A 4ęXTjbp6QeۭY م\W梈rj=%sFPr^gk8BN-CKlv+ 7ZJhe|KҬ٭ (Il}Wк+)$&V;0|w;Ksv ctӓ)is_BgoJ=zOxy`tHފ"|18bee-V>OmWX"ڸbeeANn!yE {{kV|C[}{!+Wbi"<feSvIFfNb&!QP2o㆝˾?%O>(ƣ(%s}mͫ)S.D37%?ΞKTT}{sHXqXUDڔK;!yJTqBFfNMm4N$Dk_) .w~>K;!aBa2( !05:|ge}D`ai_5R BcwB!L%3tnB )Jf&11ӧw3VHQ23>' U?Y!̌%3rznB\}gF EQ:t@LLL !oJf~ʪeGܹs"!0.)JfoA.ɧ~ !q;3q1:tPfX͉ !7%3cmm]f9tP=H!OPoV{NZB4(r ڵ^zիWB'df`ʕȎ;EBQ;(BVXAAA֬X[%Cۼy3iiizǫjVZU|B#)J&~p]ylڴZ$G y՝.}H!jWE!**̰;wWno/QB'#M;!&CB!EI!ɐ$dHQBa2( !0RB )JB!L%!&CB!E4y !!(50[l1y;y !HQ2Smڴ!22wyWWWp+%IDAT?~>}p]wq|||2e fˋ~΂ *K!"ʊ+@ٸq(*VVV񖖖(J>} 6((۶mS((/(\rEqww0/!+]af4yyyڒ6ʊBqFff&VVVjl&MWf^BQW𝙲(t*JxJ=o(J畏W$!oRԞ={Xz]ve۶mlݺ]YۄL׿E`` C yA… ͼy(../V !푢d֬YCV8~x9Զm[vUa9kB6)Jfj888͢E9BaR BatrB!EI!ɐ$dHQBa2( !0RB )JB!L%!&CB!EI!ɐ TlRfؾ} {ﭳ !DmYA7n+V !Dw&I&<bu7n\H!j%7vXjuqrrbu"!=RL=܃֌=ڠoSBa(8+++Əjqvȅf`׮]K8ooo^|B?y'3wu[[[#HAB4nfT*Ϋj5cƌV !;3q1:tPfX͉ !7%3Ѿ}{BCC[[[c_HQ2#=Zfر"!0.9|gF EQ:t@LLL}7I!J)ڵkĉ5Ba|R̀;!D$fرc:$N)'OҦMnB%!&C) !0ҟ{? .p}7 ‚"B֨҅ģWh4:&!LBa2( !0RB )JB!LFС>9r_Ymk;.&׿#~͌#)57EBGRrseo̟"3;wgCo~L! *BRe.Mnlv &OAHIDNHJ] =;dv]ņ(VX43O8BRDvHAa.; *J#D)ա9I;,3;ӿ2 `u2zN'>0 0zN'$X[r,n Nlػ[{ ABS!EX`t,Ǣq26{Vf8@d}#{gҲ`b|THQCWRN./~ˆypUoIHBd  hDp)S[uZAD@ڑ-- 8:,b7l$7fIn\&-9<3''mQ庸#b?V+1 2/vVWx,$%}B<}Z/g3-&1r0'JGD=Fv:W?HQO3~ ̍h1soňi\[,gtk/cEy#"zJo}2:.c->.CD LDD0J""b %1 BIDD C$""PP(atZvR[}2z ;LIDD CϾkfxxZ&Wͤ\S3.u-5z/Www?lٖC\إ۝;oks7XHPj&uB,NՔcfk z'pݮ=E %??Ǝp|sW( "]BOӭQQ|G2 VЮudfIN~a%ٹ&t YɆO(3װqӏL58xke~ Cpuuõkh,db]5<69ޡc%" 7ө3TJ7&Μ[ %՜.&)D=fΉ{ EjJ,}ga~{g 6 K1z;sԂ;7 Škg06[OnC.n.I2ӧ15V"zCDBtBIDD C$""PP(a(DD0J""b %ؕM]Jd4?*'Ov|=WCF̾>{&o!#f+jKOt0#? Ez3t\]ymٿ;} ۷,ʽ %9> 6OV3FL`4Âb{#o$'Eҋ1b5qydF +V~FsqYjޞ<7w ws?xﳙ2PyK ˈaPZZ19mkLHc$\[7R' kQ[6ٓ `ݵ:QLL̬oha?%6&o"2'թCCM^9Ipbvm҇3uobNa棩;rJh\E?|yvV85k_PP?+44)(,kغcߺ[4l&G/F?&k%&SZޱ_㝩`oZTˠ)9)3$Ų}666'X)k.r$6|g`wbYGxuwuDDQTd~ELIsFDx3y2||(*6c>w,w}D1!uMm>QlyX}s/xIrjԺgϼ{odگX~ !!KKDSjGZOID.D3%1 BIDD C$""Pw߉h$""PP(a(DD0J""b %1 BIDD C$""!8A8IENDB`autoradio-upstream-2.8.2+dfsg.orig/doc/user_guide/0000755000175000017500000000000012332265077022371 5ustar capriottcapriottautoradio-upstream-2.8.2+dfsg.orig/doc/user_guide/it/0000755000175000017500000000000012332265077023005 5ustar capriottcapriottautoradio-upstream-2.8.2+dfsg.orig/doc/user_guide/it/index.html0000644000175000017500000000561712332264612025005 0ustar capriottcapriott | Amministrazione

Documentation

Autoradio Panoramica

Visione globale della suite di programmi di autoradio

Autoradio Funzionalità

Cosa si puoò fare con Autoradio

Componenti Principali

Le componenti della suite Autoradio

Autoradio player

Cosa si puoò fare con Autoradio

Autoradio scheduler

Emissione in tempo reale della programmazione.

Interfaccia utente.

Interfaccia Web.

Ogni classe di configurazione dispone della voce "configure" dalla quale è possibile impostare alcune caratteristiche per l'intera classe quali attivazione/disattivazione o limiti di orario della classe.

La voce "giorno" permette di inserire i nomi dei giorni nella lingua impostata

Playlist

Come cominciare a emettere musica.

Jingle

Come usare i jingle per promuovere il tuo marchio.

Spot

Organizza la pubblicità.

Programmi

Registra i tuoi programmi e imposta quando dovranno andare in onda.

Podcast

Come distribuire il tuo audio in rete.

Libro Programmi

Il libro programmi per la legislazione italiana.


autoradio-upstream-2.8.2+dfsg.orig/doc/user_guide/it/scheduler/0000755000175000017500000000000012332265077024763 5ustar capriottcapriottautoradio-upstream-2.8.2+dfsg.orig/doc/user_guide/it/scheduler/index.html0000644000175000017500000000535712332264612026764 0ustar capriottcapriott | Amministrazione

Indice documentazione

Lo scheduler è un programma che lanciato separatamente comanda all'istante di tempo opportuno il Player per attivare l'emissione delle programmazione preimpostata. Svolge anche altre funzioni logiche e di controllo quali l'esecuzione del player se non dovesse risultare attivo. Ogni volta che una programmazione è stata inserita con successo nella playlist del player nel database di autoradio essa risulta come se fosse stata effettivamente messa in onda.Ovviamente se sul player vengono fatte operazioni manuali lo scheduler non ne puo tenere conto.

Vengono estratte tutte le schedule in un intervallo di tempo a cavallo tra passato e futuro. Spot e programmi programmati nel passato e non ancora emessi vengono programmati immediatamente se il ritardo non è eccessivo. Le pubblicità che cadono durante l'emissione di un programma vengono anticipare o ritardate a seconda della vicinanza temporale all'inizio o alla fine delle parti del programma. I jingles che cadono durante l'emissione di programmi o publicità vengono eliminati.

Lo scheduler provvede anche alla generazione dinamica delle playlist delle fasce pubblicitarie per l'eventuale emissione manuale della pubblicità. Queste playlist vengono generate poco prima dell'orario programato per l'emissione e si possono trovare nella cartella specificata nel file di configurazione (playlistdir).


autoradio-upstream-2.8.2+dfsg.orig/doc/user_guide/it/podcast/0000755000175000017500000000000012332265077024442 5ustar capriottcapriottautoradio-upstream-2.8.2+dfsg.orig/doc/user_guide/it/podcast/index.html0000644000175000017500000000513712332264612026437 0ustar capriottcapriott | Amministrazione

Indice documentazione

Podcast

Che cosa è il podcasting/mediacast?

Il podcasting o in senso più generale il Mediacast è un sistema che mette a disposizione brani audio e video attraverso Internet in formato feed RSS, in pratica è un servizio che automaticamente informa ed eventualmente scarica i nuovi file audio messi a disposizione su un sito. Tramite un programma in grado di leggere e decifrare questi feed, è possibile essere informati non appena un nuovo file audio viene pubblicato. Il podcasting consente un ascolto personalizzato dei contenuti: gli utenti scelgono quando ascoltare, dove ascoltare e come ascoltare i file audio.

Come funziona il podcasting/mediacast?

Il procedimento è semplice: occorre scaricare ed installare sul proprio pc un software per il podcasting. Una volta installato il programma, bisogna indicare da quali fonti scaricare i file e con quale frequenza cercare nuovi brani.

Autoradio: un efficiente motore per il podcasting/mediacast

Autoradio fornisce una interfaccia web accessibile dal menu principale alla voce Mediacast per navigare i programmi e i relativi episodi fornendo i flussi web necessari per un efficiente podcasting della propria programmazione radiofonica.


autoradio-upstream-2.8.2+dfsg.orig/doc/user_guide/it/overview/0000755000175000017500000000000012332265077024653 5ustar capriottcapriottautoradio-upstream-2.8.2+dfsg.orig/doc/user_guide/it/overview/index.html0000644000175000017500000000467412332264612026655 0ustar capriottcapriott | Amministrazione

Indice documentazione

Software per l'automazione di una radio. Semplice da usare, facendo uso di file audio digitali, gestisce la mess ain onda di una emittente radiofonica o di una web-radio. Le componenti principali sono:
  • Player (Xmms/Audacious): suona tutti i media file e invia il suono digitale a un dispositivo audio o a un server audio
  • Scheduler: gestore in tempo reale di particolari file audio quali jingle, spot, playlist e programmi; interagisce con il player come se fosse un utente supervisore mandando in onda le programmazioni al momento giusto.
  • Interfaccia: interfaccia WEB per monitore il player e lo scheduler e per amministare la programmazione per il pieno controllo delle emissione della tua emittente radiofonica.L'intefaccia web permette anche con facilità di di pubblicare facilmente il podcast dei programmi coerentemente con gli standard RSS 2.0 e iTunes RSS. L'interfaccia web fornisce un player ogg integrato pienamente compatibile.
Sviluppato con Python, Django, Dbus, funziona operativamente in ambiente produttivo.

autoradio-upstream-2.8.2+dfsg.orig/doc/user_guide/it/program/0000755000175000017500000000000012332265077024454 5ustar capriottcapriottautoradio-upstream-2.8.2+dfsg.orig/doc/user_guide/it/program/index.html0000644000175000017500000001507612332264612026454 0ustar capriottcapriott | Amministrazione

Indice documentazione

La gestione dei programmi è la sezione più articolata di Autoradio. Uno show è composto da episodi che a loro volta sono composti da enclosure (parti). Uno show ah alcuni parametri che lo definiscono nel palinsesto. Un episodio ha dei parametri che definiscono quando deve essere mandato in onda da autoradio. Le enclosure (parti) permettono di spezzare episodi di lunga durata per facilitarne la messa in onda, il download e gli inserimenti pubblicitari. Quando dal menù principale si seleziona programmi viene presentato il modulo per l'inserimento di un episodio di uno show. Se lo show a cui appartiene un episodio non è stato ancora definito bisogna farlo come prima operazione; selezionando il + a fianco della voce Show è possibile farlo.

La definizione di uno Show

Nella sezione principale vengono richieste alcune informazioni sullo show e vengono utilizzate alcune categorie definite dalla legislazione italiana.

Nella sezione "Podcast options" e "iTunes options" vengono richieste informazioni relative al servizio podcast ben descritto alle voci successive di questa documentazione.

Nella sezione "Periodic Schedules" e "APeriodic Schedules" vengono richieste informazioni necessarie alla compilazione del palinsesto e alla stampa del libro programmi richiesto dalla legislazione italiana, funzione ben descritta alle voce successiva di questa documentazione.

Ogni Show puo' essere inserito in palinsesto a un istante preciso oppure per una emissione periodica ad iniziare da una una data specifica fino a una data finale per alcuni giorni della settimana specificati.

FeedBurner e iTunes URL

Dopo aver salvato almeno uno show e un episodio prendi in considerazione la possibilità di registrare la URL del tuo flusso a FeedBurner per tener traccia delle iscrizioni al tuo flusso con elaborazioni statistiche. La URL del tuo flusso dovrebbe essere qualche cosa del genere, dove "title-of-show" è lo slug del tuo show:

http://www.example.com/podcasts/title-of-show/feed/

Ricordati di spuntare la casella "I'm a podcaster!" La tua nuova URL FeedBurner dovrebbe essere qualche cosa del tipo:

http://feeds.feedburner.com/TitleOfShow

Puoi tornare all'amministrazione del sito e ricopiare la URL nella casella FeedBurner dello show. Per avere ulteriori benefici, invia la tua FeedBurner URL a iTunes Store. La tua iTunes podcast URL dovrebbe essere qualche cosa del genere:

http://phobos.apple.com/WebObjects/MZStore.woa/wa/viewPodcast?id=000000000

I vantaggi di inviare la tua FeedBurner URL a iTunes Store permette di accumulare statistiche e di dare la possibilità agli utenti di usare l'interfacci amichevole di ITunes. Ritorna all'amministrazione e incolla la iTunes show URL nella casella iTunes URL dello show.

Sollecita iTunes per i nuovi contenuti

iTunes Store testa i nuovi contenuti quotidianamente, ma tu potresti volere un nuovo episodio disponibile immediatamente nel iTunes Store. Visita la URL di sollecito (ping) del tuo show per rendere quell'episodio disponibile, che dovrebbe assomigliare ad una cosa del genere:

In alternativa se sei uno sviluppatore evoluto puoi impostare un "cron job" per gestire automaticamente questa cosa, ma non fare il sollecito troppo frequentemente perchè iTunes Store potrebbe procedere con una rimozione.

Registrazione a Yahoo! Media RSS

D'altro conto considera di registrare il tuo flusso al motore di ricerca Yahoo!, che nello specifico accetta qualsiasi tipo di media (audio, video, immagini, documenti, etc.) pubblicati regolarmente con flussi RSS 2.0 o Media RSS. Il tuo flusso Media RSS dovrebbe essere qualche cosa del genere:

Google video sitemaps

Se stai creando un flusso video, puoi inviare un video sitemap a Google Webmaster Tools. Il video sitemap aiuta Google ad incicizzare il video in Google Video. La URL del video sitemap dovrebbe essere qualche cosa del genere:

In aggiunta puoi inserire la URL del video sitemap nel tuo file robots.txt:

Google permette la registrazione a Google Webmaster Tools di un flusso media RSS invece del sitemap se preferisci.

La definizione di un Episodio

Un episodio è composto da una o piu' enclosure (parti) associate a un titolo e un file audio da caricare

Un episodio ha una o più schedule che definiscono quando dovrà essere mandato in onda automaticamente da autoradio (prima emissione ed eventuali repliche).

Per ogni episodio è possibile inserire dei metadati utili per effettuare un efficiente podcast/mediacast.

Cosa è il Dublin Core namespace?

Il Dublin Core namespace permette ai metadati di essere associati con il contenuto di un flusso RSS. Ulteriori dettagli a Dublin Core or the DC extension.


autoradio-upstream-2.8.2+dfsg.orig/doc/user_guide/it/playlist/0000755000175000017500000000000012332265077024646 5ustar capriottcapriottautoradio-upstream-2.8.2+dfsg.orig/doc/user_guide/it/playlist/index.html0000644000175000017500000000437712332264612026650 0ustar capriottcapriott | Amministrazione

Indice documentazione

Le playlist sono il "tappeto" musicale dell'emissione radiofonica. Ogni playlist puo' essere programmata per un istante preciso oppure per una emissione periodica ad iniziare da una una data specifica fino a una data finale per alcuni giorni della settimana specificati. Le playlist prima di essere caricate vengono controllate e i brani musicali corrotti o mancanti vengono eliminati prima di essere inseriti. E' possibile specificare la durata della playlist che verrà inserita nel player. Una opzione permette di attivare la funzione di mescolamento dell'ordine della sequenza dei brani.

Per poter funzionare Autoradio deve sempre avere un discreto numero di brani musicali caricati nella playlist tra i quali inserire le altre programmazioni. Quando una playlist programmata viene mandata in onda essa viene inserita in testa ai brani già presenti nella lista del player.


autoradio-upstream-2.8.2+dfsg.orig/doc/user_guide/it/programbook/0000755000175000017500000000000012332265077025327 5ustar capriottcapriottautoradio-upstream-2.8.2+dfsg.orig/doc/user_guide/it/programbook/index.html0000644000175000017500000000343212332264612027320 0ustar capriottcapriott | Amministrazione

Indice documentazione

Autoradio permette la stampa del Libro Programmi secondo la legislazione italiana. Selezionata l'apposita voce dal menu principale è possibile procedere alla stampa inserendo gli estremi delle date richieste. Viene generato un file PDF pronto per la stampa. Alcune voci presenti nella stampa sono definite nella tabella "configure" della sezione "programmi" e modificabili dal pannello di amministrazione.


autoradio-upstream-2.8.2+dfsg.orig/doc/user_guide/it/player/0000755000175000017500000000000012332265077024301 5ustar capriottcapriottautoradio-upstream-2.8.2+dfsg.orig/doc/user_guide/it/player/index.html0000644000175000017500000000662712332264612026303 0ustar capriottcapriott | Amministrazione

Indice documentazione

Partendo da una playlist è in grado di gestire differenti formati di audio digitali per poi inviare il suono o a una scheda audio o a un server audio.

Esistono varie possibilità:
  • Audacious2: Questo player è disponibile su tutte le nuove distribuzioni. Permette l'invio diretto a un server per lo streaming per la realizzazione di web radio. Questo è il player preferito pe l'utilizzo con AutoRadio.
  • Xmms: E' un player “antico” ma molto robusto. Consigliato solo su vecchie distribuzioni
Questi sono i meccanismi di funzionamento principale:
  • deve essere sempre presente nel player una playlist di brani musicali ciascuno di durata non superiore a 7/8 minuti; brani piu' lunghi potrebbero comportare ritardi e cattiva gestione dell'emissione automatica. Per mantenere sempre piena la playlist si consiglia di prevedere almeno due volte al giorno il caricamento automatico di una playlist voluminosa.
  • quando una schedula raggiunge il tempo per cui è stata programmata viene inserita nella prima posizione successiva a quella attualmente in play, e successiva anche ad ogni file precedentemente inserito da una precedente schedula.
  • tutto thread save, ossia le funzioni fatte sul player dalle varie schedule saranno sempre consistenti.
  • le operazioni di inserimento e cancellazione dalla playlist vengono fatte solo quando mancano piu' di 10 secondi alla fine del brano per non cadere in situazioni critiche e inconsistenti.
  • la testa della playlist, che se tutto è programmato correttamente tende sempre a crescere, viene tagliata a 10 brani.
  • la coda della playlist che se tutto è programmato correttamente tende sempre a crescere viene tagliata a 500 brani.
  • il player se in stato "stop" viene sempre rimesso in stato "play".
  • il player se in stato "pause" rimarrà sempre in "pause" se non ci sarà un intervento manuale.
  • è possibile visualizzare lo stato del player con interfaccia web.

autoradio-upstream-2.8.2+dfsg.orig/doc/user_guide/it/feature/0000755000175000017500000000000012332265077024440 5ustar capriottcapriottautoradio-upstream-2.8.2+dfsg.orig/doc/user_guide/it/feature/index.html0000644000175000017500000000521712332264612026434 0ustar capriottcapriott | Amministrazione

Indice documentazione

  • gestisce ogg, mp3, wav e altri formati audio digitale
  • è disegnato con funzionalità client, server
  • gestisce playlists, inserendo in esse jingle, spot e programmi
  • regole programmabili for calendarizzazione anche periodica
  • non sovrapposizione degli eventi programmati: anticipa, ritarda o cancella seguendo regole evolute
  • il player è monitorato tramite l'interfaccia web
  • spots sono raggruppati e orditati secondo le proprie preferenze
  • i programmi sono disponibili pe ril podcasting in una completa interfaccia web con rss feed in vari standard
  • un player per ogg vorbis integrato che è molto compatibile con la maggioranza dei sistemi disponibili agli utenti
  • può produrre un palinsesto in una versione stampabile che segue lo standard stabilito dalla legislazione italiana
  • sistema integrato di esecuzione in background completo di messagistica
  • fornisce una versione evoluta di dir2ogg.py e mkplaylist.py per la gestione di file musicali (conversione a ogg e creazione di playlist)
  • non utilizza Data Base per gestire la musica; puoi usare la tua applicazione preferita per produrre le playlist musicali
  • documentazione disponibile in linea su web

autoradio-upstream-2.8.2+dfsg.orig/doc/user_guide/it/spot/0000755000175000017500000000000012332265077023772 5ustar capriottcapriottautoradio-upstream-2.8.2+dfsg.orig/doc/user_guide/it/spot/index.html0000644000175000017500000000406712332264612025770 0ustar capriottcapriott | Amministrazione

Indice documentazione

E' possibile impostare qualsiasi numero di fasce pubblicitarie caratterizzate da un orario di emissione; ogni fascia è attivabile o disattivabile singolarmente. Una fascia pubblicitaria è composta da spot. Ogni fascia pubblicitaria ha uno o piu' spot definiti come prologo che annunciano la pubblicità. Ogni fascia pubblicitaria ha uno o piu' spot definiti come epilogo che annunciano la fine della pubblicità.

Per ogni spot (o prologo o epilogo) è possibile stabilire da quale data a quale data effettuare l'emissione, in quali giorni della settimana e in quale fascia pubblicitaria. Ogni spot ( o prologo o epilogo) ha una priorità che determina l'oridine di emissione.


autoradio-upstream-2.8.2+dfsg.orig/doc/user_guide/it/jingle/0000755000175000017500000000000012332265077024255 5ustar capriottcapriottautoradio-upstream-2.8.2+dfsg.orig/doc/user_guide/it/jingle/index.html0000644000175000017500000000361612332264612026252 0ustar capriottcapriott | Amministrazione

Indice documentazione

I jingles vengono emessi ad intervalli di tempo fissi. Per ogni jingle è possibile impostare da quale data a quale data effettuare l'emissione, da che ora a che ora effettuare l'emissione e in quali giorni della settimana. E' cosi' facile attivare promo di programmi o altro ad orari specifici.

Il jingle programmato sarà quello con ultima data di emissione piu' vecchia; se ci sono piu' jingle con la stessa ultiuma data di emissione, i jingle vengono ordinati per il parametro impostabile della priorità.


autoradio-upstream-2.8.2+dfsg.orig/doc/user_guide/generate.sh0000644000175000017500000000050312332264612024507 0ustar capriottcapriottrm -r en it 127.0.0.1\:8080 wget -rkL --header='Accept-Charset: iso-8859-2' --header='Accept-Language: en' http://127.0.0.1:8080/doc/ mv 127.0.0.1\:8080/doc/ en wget -rkL --header='Accept-Charset: iso-8859-2' --header='Accept-Language: it' http://127.0.0.1:8080/doc/ mv 127.0.0.1\:8080/doc/ it rmdir 127.0.0.1\:8080 autoradio-upstream-2.8.2+dfsg.orig/doc/apache_modwsgi_example.conf0000644000175000017500000000170412332264612025566 0ustar capriottcapriott User autoradio Group autoradio # on Debian we can use libcortado-java package if installed AliasMatch ^/media/sito/playogg/java/cortado.jar /usr/share/cortado/cortado.jar #Debian/Ubuntu Alias /django/media/admin /usr/share/pyshared/django/contrib/admin/media #Fedora #django 1.3 #Alias /django/media/admin /usr/lib/python2.7/site-packages/django/contrib/admin/media #django 1.4 #WSGIDaemonProcess autoradio user=autoradio group=autoradio processes=5 threads=1 #WSGIProcessGroup site-1 WSGIDaemonProcess autoradio processes=5 threads=5 WSGIProcessGroup autoradio #Alias /django/media/ /usr/lib/python2.7/site-packages/django/contrib/admin/static/ Alias /media /usr/share/autoradio/media Alias /dmedia /usr/share/autoradio/media WSGIScriptAlias / /usr/bin/autoradio.wsgi SetHandler None SetHandler None SetHandler None autoradio-upstream-2.8.2+dfsg.orig/doc/programs.png0000644000175000017500000063264612332264613022612 0ustar capriottcapriottPNG  IHDRL}EbKGD IDATx{|Nf66l_c9[HAI9s:h"T_%9qα;fvv<6vݴu}>ܺ3ؼ] F[>V`L4|`Umz\ &;=+?p= ɍPi=FTl(,`Lf "IH3:{iÿleFCt(W9}Jz}8w\Q˔JQ?^fB#s!F1tpn>h,A&;peȇG}-Fӫ]=!NKQϗs{c_ uz $W++?@ix~+앫+k[i=5O׃2x;5L"""""wtޭi1WO3-u=7} Gl|7;L|H̭\|3&ʚOsl#0yk%aRqj{2-kՠF[y?hLտVt!3lm0R2vB,F8;ZœX8VV8;1Oa㧿VТ?Cym{Vh,7cC ġ3?Pr#uCk4'/ӭ>M$ 9VJ~\|4.NiUYܮy^,4;4S[\P$""""rǵgx?p}z~^"ِĉ yF\=mд#DńӾy X*5ʥDFR K/爾uUMC9JLzKwbs]ʘk""""R>yMB9O8ѷye܊f-]ZԷ-I& :{e?:m FWBoX,C ykX 1CO1C jDDDDDDP$"""""b&3<aƖ.AU䛯\+""""D|eD \ptnuIDDDD25Ôwgк閯\{x&{B"YI39߯PچܲgXVӻz0{ܽ~q<0H{zc_ŲN 1qzR>G ]:T5\!(&)[//'~b{ .Mrt$>5q-Fk^I{5g7g ^C9zp,cKi kk+JYgڌ+ի9{oakkM6UhXB&|*mb|}r +OZ.$Z4IFm߸Mm_j8t>4! @y{{x`QőjQő{PiHxm+uslM iȕ?_!4,.ݸ|R$""""Y:{. vmмi%ʔMJEӑXS}]Rn˻9rt]:zpr u-Vmĵbi|kEJt*nSѮNDDDD,3,[d`49u&d#~|̴Hju..qNL= "VR+.&R c([֎[w,GDd<;Tqt$쬱!::OFu3!6u+Zb/WhҸ"v=@ٹQ7Kӛ$AoR7|R$""""Yj̍ =K)ʔ-/N )Oi{pp_Im_:`箫g06{֫ߧ"p59v3AQ8Vi֤Riߦ ?^bZQ \}O @R27V}1C jDDDDDDP$"""""b&30IDDDDD ׊Ha<` K`q*/t)%־:w|+P0ǠB ˺u>})G7m'p6*yaBz89:X? 2@lyo8;ccSp7͹#M?UrJ1V$"""";~. llmh٢6oE-_^K>^ҥKybpg^嫶֯R.eيtC`#k?H\: &<5*Q"eJ˜W1pCLǓl&>>K'ףիDPգ"qqn?hfʠ9z^^šHNNNmOxm$mԧB||ТYmlSNjcܩclUBbS$poԭIۅ˓#3~G=vqciSmS%v - 3h٢dGߙ {3OR3:46-т_L75K!OtLS_Y Yk9x0;~5s\0qq t DΜ􊋋=99V ?5kw0dPGn͵k0+K dVq/a{m$+?-{W -?VVD5/7/Hڞ)զ{xѴiUxo,^͍:PjE#S33J92DE4>X=wm :&δ +?@-t9V&^8y Rmye8:tVV7޳ϘOR4nT] I:wʹ,}nre0} vrtHw|}<4= srR@a.0>)P[or.6|`-DH9f|8O#j2l?>h4p=2[=7>):zys(:vh̠'K͚x)5+&w;ӷwk[ǯ1i£DqpM<ݫ1yrFβ??wbvtC:`OhX$wn˭ rrkfʸh4HUs^=u3ax._hQK9_dܺADD_#>!' l:{Vn7NBm66G#˫ׇk7䎋 SګHw_}J}onn|6cy[>i=FTl(,`Lf "IH3:{6/Qqa%R*;L"""X__}rӆmf#o/н:gՆGZi]g'7~ܷ Rt@yX|?_~ʐV[_α?Y}2<\'"R0XyuEW_;g >,aNg?ze88ؗiGz4??|ط+vgǟ˸yzk538{e_43rQs|#ptpx""AaFRαږןMriR7 ɦuFCp0g'w6;}ZUG5TsOyjq/_Θw8KP$""bU*Ҷ \9pz+\Pq^ӫN\oo^X%*:{lXlKط Nmfu7Ŀ$"RαRqqLpuնSwi9Эvs|=Sr#uCkt(Nd7ȽIDD2ĤNzNЙm|kEP3m>F8vg]6+1/ILg߉MvOQѹ*DhnƆk6o.:D#Lv*gG7!F\̚ g?ƞhZbBiߊ<틈%)y""RǧAO<=%ODDDDD<);CiZ6s`)bϨu-\Y6~F<yu.G5r4Ϭ\{x&{@U$""""bPʻ؛&ꆦ{ю.b4x9#nP ta.Zo2)mIQv+++Ӥ{o0UЯ&Uٽ7e^Z掓s$խ$Z5w'!~jޔWҝf'ɹPAzS˅B[]a Sg" '$sMZ5wT)*-|?֚nw; 'G;TvqXߚs\n {J jNtf7ұ*mXSg"9}6*{-fLs$sR4[CϲހG}iύhצ JY`n%w*3P$""""l2MKwڶ̗RnHcuKͱKy7IH0P++M[R`4 gg~%Q`9A&!F=!GLL {*m2DJ7nV W5-Qaq7olz]i`u+2eY ohc;Ku=IL2t&u|3~EhLP)ΈZb/WhҸ"v-gF̾/WHAɹQF9B5ʖ+KIDDJv ҷ)Qڶ̎_凋4_[[kn%ѴqEn'qpNh*~WOaemE{<8оM~ĺgnm*vMd]:xRF(`4kR R;NΑwk997Z6s#fGR2el i2\$)>bpRe:~ϊONbgg͘ JqhJylK)pJWѹU&)bGIpL$_;';)EDJ65L""R"fyxy9ݓӑ(ǵ[>aJ{e'՜wҕӫ{l_SFlHY*~p#6oܦ | :eN<9~)N-"Ra-mNG2lxz Ss`66lQ7"!!)kU5F_DHh$n. ҅Iƚ R2eΫ8!tLǜ2m%g2||&^^šHNNNmOxm$mԧB||ТYmLͨ[~^|i_:wz y19Ly'{7a9UA_!ӎѩclOHLbʴu˓#3~G=v~);[F FHHo%sf#1)ׁ0u9{cMdn̯z_,ČYk9x0;~5mܹ`dtݽyn OHHLtc~˚;2#}zڵ|߽IDDDD2شezף a}/J r݅W 0%lݶyd.OW=hڴ[o< wTZ"YdL/칫|h1# 22ռe7!?_FvMMԿu~^8SRΟi)%ODDDD2ao+++SƕL/ϯuaپwy^ּ֬awgi e6vNk{&.!poLf,Z5kw0tpgڴK*{'zzϪLiiп=W u Q׶p&PD5L""RZ&Mx.}k MsEǻ;Cuӕ;_6+RY""seÒ>oٚ)yV,wbiKXtiݳvlݶ~9D5JoL{%ODDJsY+o50<s"91sW+-2d[}NLLRMƿfSգ"[ńI1/hټ[5;vٲޜccmͣ}l70Hc.kK&9z7CwG1}mqeHrez5/5[ʲvqCq^K*::kɞ?O3 ع,п=C>iJ 8/<SOYFS֊O*DF|t֌r[3 }i;{ xS^5.]cԬY77tϝ5pu-Gpp+G]܌O||"k?M{yY99)0VF&>xTk7r]ci-Ow<=?Lҹ ]puuAjy)$""^A0Iѱot9'DŊhqΎ?P3D݌] ҅) .;~aOVQ*Zkrpok?x8.ݧfׁFVOV9l9graWau˺lڼO?k++5C mj Ǻ'*L´hf|}r\2EKHHtyZUMvxQ_erf2VVklUϚLw@4bX|ewq2墽:BB"2d=尥SOvvE+."""5k|jVQtrJ2jx7BB"x/3sI̘7W\.iIWV\ݧ̶U_<=rIj<݋,'cd "^S$""""dUCRTr#\4sՐ2rOm+kY͓%SOf9lid "^-y""""AvYVVVr22fŤL *+Owo+'kYj,Ŗz{CN'i jDDDD$ 3fnyZ*}zk,3bb ͥ)L''d>5L""""AA儙EOtyZ*}0okv$poLeKz=)d(R{T3arX j绷1cZ-KӯσRgn9L"ErDDD1R :em|x3o6"͒Xƞ'gFԫu+N 8yZyu|7bbnӹ,Z8gGv f3aNl6S&US+Tǧ -ʊ*U*9^,%yI>fkҲEm.\ts]2m%=E99sc0UIt9# }>nUt=V\DDJd,]=O ̧_ߏm7L2es\0qq $&%z&K~eÒcjˢ%YvCwMz\w®G1;/ok78z8CdT,- bG 8yZ&Mx.}k NV09ɔ1݅O`0L,%'Y9-[S~6e /<׊2ԑUNTT, xsrn6-7w^ {{;nOW%Է`&LD=ݒ'""%N^V tXcm3˄15)c$K=k+++dVVe*+'7k!읪,&L9Auhݪٱ˖uGlݶkk۶XdÈȽIDDJdtԄ#Gہtz1#.36}&L2eʕ+ի|կ^ddj߈.[_R9׏vpƚysGϓ<vx+˱oϐoeDR$""%N^D^Q1|/,_ݻ5cs}&Lm͔;k83f_Z̯LMVNNqʤǸ|9+ѤU*rڍ\XeKo:O!442ӱtnBy'\]iPZ^ADJ(0HW9Lro-.];Qb9v#.- #Q7cii2ÆtaNs2{d~^׊l\}~O۷4s^}OcckMxVu .n9l9پ}0HҺe]6mç`̡6^Dd ?IecKk-Z_l?Wй^E'.eJ3X[¯?p.w7} a+sIDDD֭yl<gG~ϪݳqZci>Grr2uj{k#iѼ)7nOboN|5uٔi+8t3i 5t[|RM .׫([d3,_:-^ݝUl'8$uG3{Ptg6{;, =Ɲ:66*!1)(cEDDfccO*jH:z1wp>Y9c/2յ,[֬Aӻ5׮[?5_+/9f_ t5'1cLks97UV^Eݑ;9f:46-т_L75KDqcc+gc߽[3 6w=_"5L""""bVjV`qU>X 8nSg*Urt;cŴLkJNr²*ꬭkwuw ꄧ+QQ)On>S}-s.RaR3,\2L:~jޖ)+ +s9f뿘Nms)<ϼa]s5DV9aYzu`ewҬ8]3-lcǃ@ӫ݃ }>mYjC0rr{d+4l?N?ܵ)cuTZlnV^sbb 4[ݟsssѸ%gz~.h42 w,n>s+'9k9=_]alM}1BB"pp'4,CfXvwkV7Ɵ)S?:}O^x1JOY<1t9fvLl Q>qKuشa&5O;omZ#ަ|ŌiW׋3ޕiղu0e*BB"L?ׯW (y_dD$o0Hc4f^ahܨ!<=K>BRb2/O}6ed ڵZKV%Ɇ<<6ŧFz5qp(|˞tm:BB"2dkNd{ G ~ݯY2"78eenD[_2g01;%$5S\6i-여K:VN2S@㔧s8y)8y3u^Seu ~͒Q$""%NvY ^^:$A5/7k ˆIfLAgd]ױrS9)1:ٍqfɈH<)qfʐY. w`]N6o˚2]ٚ3l_r]۱>rzu%)0} t*c=!'5,5L""R$ $'edUvI^uPmf=]VXjC0y*7MYqfZ.cǃ@ӫ݃ }>'ǫ8f;L""RU .CDҩ G獷PcF];fLlvr^vwcxv&>6^ޕ\ǫ8fabϾL-e2DJ'=FdT  W@n͘\,*+/ *S$[NjMf2xuЩCcmz8^1L9L""""slmm8pKϏ-[;X'.eJ3X[¯?p.wƦஏ͹#M?Ur`qrt(q5L"""̊+0`NNN.%???ϟOPP>>>.G)/ $4G`[HJL婏3bXW^K>^ҥKybpg^嫶SJiaّ`#k?H\: &>K'ףիDPգ"qqn?hfʠ9z^^šHNNNmOxm$mԧB||ТYmlSEY'{Pò?J3=JHLbʴuvM\ScRlOaN鎍W!c{9U;L"""ƍf.%K=z 11;vзo_K#۝BYbqغm?}{fu&>̦Pٽ>ҥ&"j׮]?~zҥ䙟111ܹҥH2k-ːlGgQӲ*TWmxtR7oNf,]JUVFeufr$-ٌcRČO\¿,˔fK潅_\vM]qw/ϛs}Z[%V/'GQ$""R]~׳h"Ko~~~|W.Er߀9 yg\ FjAx}*T+9|}[tR|b+K╩Ζݱfђ'|zzuwV}zT ..'CЁ#G˫8tԩmTOZ4uJU^3԰lxzҩclwBbS$poԭIۅKBdmm_s3dP'<=] i|8{*,JyetLiwwV~<^=[(HJ IDATprM4:x699[婏3n?*9c 5L"""EO?ę3gxg,]JGQT̕-@ٺm?;~:ģ}be/mڊNLT6HpHE/7MYqfZf:.{̞,\2LjCcAxp>m4cuW;L"""EG}DvhذK)0~~~1(__1qF€2-7}C%qJn+a\ʚm;w`$>Ӌw%L|Fmݽ<5+gSWhw-6Бs̝U+q 0!׮]o-1WR=# onR$7>s ]:7|Ҡ~tʦ-{䳟x'ƏÌiW׋3 >(._H t( lٲE(DD<"""_#Yڧ޽bG!"&"""`t:477#??_(DDbDDD$wyHKK;ʠ@bb"GD&"""Ƿmd2 *^^ 6LDDD"ٴiFt:JKKQ\\,v"a"""&vAVQ&"0̙3ؿ?_U(dHMMeDDC&"""[&L@JJQDPPPFĆh577oyH$&55v; Klه~ݎg}V(jHJJ<"0 u'@``QD;vn[lQAAN>^xA(CBff&q!u  ZnfΜ3feHGll,ѐņhTWWcӦM#Rt:6LD4da"""$j5x ):OƵkĎBD&""Ap8ogqdabG!"  عs'^^(CJBjj*ĎBD&""An:`„ bGt:,v"N0 +W`ΝCVXՊݻw6LDDDFhh(zQ̜9W#! jkk{g|;bt:v!v""'6LDDDhӦMw y+WDEE ŎBDĆh[DXXQDDDDpZ )lȩSp^.t:6LD4a""" ֭C||<,Y"vaCɓ; 6LDDD͛ظq#yH$ K,Gh`DDD4>C8<3bGVb۶mbG!"h@[O>$Ď2t:塥E(DDl[AAΜ9Ë=ܣ4X, Q[~=f̘ӧ>F܆aڴiw>kPCD&""~TSSM6;JA;[:./>\bDDDO1{lv8zѣG_Dze7|pY,XNJ+PWWigΜܹss⫯L6 -Bzz:t}$44HNN… 1gΜN9+Wڵk4iRtU/DwXl&NzA$$$`ҥ=ձp!… _+aԩ eAƍBPP xba Ÿg  Ad!77WAػw, uuu BNNpܹNtPT+Ja߾}Μ'NAsp8D"~ y\.n* >פ$aϞ= Bvv ˝ QǺJB[[f̘ZׯrdG-, |}}Q__Bi_r6 JfVų>G#쳻9PXXBPyV;:)S8N۸SPC"GDD^{5Y'NL&bop=x HjϞ=~p8_wy_~%4  6l؀PYׯGtt4/^7|/\իWC"t9;{=(JL6 6l@CCCQ_Ww^ CRR֮]påDwlp ::>(x xu?1jjjRĎCD#ю;pu|{;thnnF~~QhbDDDGo6RRR0~xx$&&"''G(D4Ba"""k׮aǎxŎt:&" &"">_ѣGC׋c\8}QhbDDDtV+}^R9s&BBB8DD`DDDt`0~W(M*bŊlHlb DDDt::tuuubG! =tË= ˗C*"77W(D4°a"""o6Ñ&vA`ѢEغuQhaDDDtZ[[a|߅T_%==vj;  )ODDt6mڄ^Z(#NCcc# ĎBD#&"""QRRe둙PR\hPa"""r Ǐŋ8s xt:6LD4$ b ""򐒒LݎGFYY.^D"vg߾}HNN/v"8DDDBMM $ v;ѥݻw`0Q__/r‘g޼yje"AÆBp8 n޼ Ë/h`d2a"AÆȅZl6, z-|[rBt(((@ccQh`DDDhpB)S L6FT8ڵK(D4a"""rh4fuB@xx8 F3F6V `۶mbG!  ]LVP 88!cΝ ID  ]r#??ƍ!cáCĎBD  d2x{{c޽)@||4 ŎBD  { iiiضmQða""""466</@D4רn_gWEhIMM ?.v"lFO6Y4DjX`0QaDD4YZq.Q)nV|Gm\bLx)8we0[$Ȑ:̻qh!h4q=FMK_Epp>OTՖt/0Zވu1֗+ UX3hY djFK_nL&;yh˔u}F9W#[P)}?,~“:_;s>e\87?.@2c5_*bN_w1aŸ580 Rs^Ć?¾_ǸW222PWW<&"a.H3\y{pOMu>oVBHiS?\>O;sڄv%7 _97-'ys+kSm7H%RT\DQum@05ni A`„ ʼnpJ0Eq\&:-8{;:ߕHx/Sb´ X{֑8׽4n-Qt:rrro<G9//MX&)\,>eL^<I vZK''K_\̅vӼ|T@KMɫ0J2Cu55UW>ze^zX(D8DD4i!OvwZ / Ao<|~d]u82%>iSlDŞpa>^8D4Ii CWa#^s$-cxo|޽{ŎBDYr%ߏ:0džshΦ yF1#NlG)A=#RTHm:__4""",_\8D4a""Cu UO{0Cu Z$y,"}'Q|V * 7oZqMm]fla|UF3va Wjƻw hhlRB́Ҳ&LH~hpzlܸ'v"F8%<֬!gp~DMp 0ib ^zab\l6ʮ6\&Uu>a--v_ӂ1wW˨4a1~y$"HGJ5 V;3/z*N>-v"F$ FD4t>;Q5;s8 /gb!a#LDDD4"HRddd`۶mbG!a (,,h;  lhXt) rrrĎBD&"""1|}}tRdee 6LDDD4t:޽bG!a (0L;  lhD ǴiӰ}v0O$"^2̚X*~:aaaaDD=KDQY P9ͨ2wX&.4`ŲHܟ8 nX2 u.g& 3͎+0Zh~iQ`_As6XD7bzftݝ7ԙZ$L hnLanH$HKKCvvQhcDDbiFy!.3PrQXL" Wx(3SơF@kݹ QƦ6|Vi0w:;??)3LJ heffbG!!L6Z:]doBanfDG=_ÅtRdeea""8%QkfC!v`0 ,, 7oʕ+ŎCDCGh=z4fΜ۷(6LDDD4\p8_D&"""z=F#:$v"0ш6ydrZu xضm1hbDDDD#^zz:q%ÆF B"++K(D4İa""0:I<vc5.t1u=/s>v75qw_=m: L&N<"NDEXh}98F_~Hp{Ih(O>Z0GYZ|ŵwŧﴞ xw!r3lGޠg ~Чq̎po5m_Դj/4RR2@gNtRSS!ˑ-v"B0Gٷ0lNeŚ7?[>+o+wYqykiipo5m_چ 7X̌9'~~~XhDDa")+0&ŇCb&~R&$`Gnرݵ՘=%.&˝?{/Gpϖ233k.Fz/Uz 0=[VNMH$ xg6LD4"EEkFlxXڰcX1GXkM~ie0vlƆ> >7nM,6LDSh{ Ñ%Ҋ)QbU_k/?>܍>GHH@Q?Xr%oAĎBDCDO" OԚPzEn9y$x=z3g;#LDDDD6m"""}vz۶m; lSNʕ+bG!"a""""ŋh!DĆN > &"bDDDDԝL۷bG!"a""""F䈜$;֬-B` ;ʀ4P*̐H?,Z?%E]DcUF[w4U@@ϟ۷㩧; H((;^Sk'[JT0whH%9fmex J%v,Z>Z<42\/aZP(ĎCD"`DDc#$& L_Ua0?)Sp~~ DE} Z\iUB֩V]:~hHOOo[aT^haDD F&%iQ(-kĸJfL }g>ZZmkjۧFYܩ_L? -(8T ??C!e֬Y CVV&Sz )AZ>TeZ-viتXhT*1n/Όu޼9c@NULVR[l{w7kz&:+<R$ ӱm6$ 2Ihh7|*v>QkfC!v ANN222p GzZQ& Q/l2dee&""""7dffb޽hnn;  "6LDDDDnHKKfCnnQha""""rCpp0}v bDDDD䦕+W";;v]f6LDDDDnJOOG]] ĎBD HD4fmTXtSQiB*TV!Q~X ~Js>kuYUn:w֞h8q"}v$''&""7ӢZ >R BsFC*16o+3OMT*c $un2Et7222ebG!AַUFG˥TH1>V%CG"#|z=P?ϟǤIĎCD/GI%?qL&+B08 @JJpЀ"q(ʻcu qL-&b47[;zy3*}>`0Xeq1xXldaٱw{  -X4? (TXҹMCCVb1C@]yjv:޷DCܹsm۶uZ҂ϋ &"HJ$LlVa:p㏙Ӄ(܈ˍI1->>>D+%&iDkkUʌqk||dHOp*56ჍJCUoן'hRiiiBEEyt:W񈨟7y,ɝ݂d&x\ގ?BanfDG=_ÅQnsW]L۵'l ''}Q]] B}hg2T*&"q$"t m*L.Aƨ /46:(#wa/UdZiتX?X*_p.hV+  8I>;Q5;l6cԩr lyR7fsŰXX8/S&ianA޾r-@/]Jc5]2tcdoy8W\ VO?^{ O{<_#"RRڄÅXGaW [0` B*.4 *5lC\?'kqG54ju(0aez4ҖG`lA~ApUf;YGƣb#U8Uf6_p 2+va}YنCf[/ЂEàOBu *#&CC@]yjv:srF:SI8>VΓh8tO.v "lȣ\,m /c`J.7B&`q0TdaqH#nji (tc2F@n1-w55a0dtvJ)b\/Rk?[@Ĕ@)bUS2ND$-hmʌqkx{fRQƦ6|Vi0w:v'bƴ`p /j?ŎADȣL6Z:]dno2B=8??Erggq]BDr驑veWPv&ΞåFӢ]2=K../s!$\/oFtdHu8\hC\{מ8W\Y1/i >\iՎzt=='wKNj&"(MJӢ`9PZքqvaj[lؽYd4lFw}O}gV,Y8~ز o7>N*S n̘ꑻ:f 8| jUԶO rSrvh[Pp~~ lD†<ʂP䚮M%%Q8\h@}C+L GɊܫ8{y?%.6X݂[SwҬ1ؓn) ,TEc}sfL .ԙZ\*mG`]7^xlU,ˊ/I4ys`ɽɁ˥0`i vYCԆgM7PZqBD%ӿ )b &.&y =O`|&QOԚPzEml&N;?G};y0^􁈈N#(0N#"""+%li,pvoxet_1x^뱈wl_cF|(|%7 v?Rg~* aDDDDԍQEDuU.݃x)ՐXS̜ /vfK#@/9|؜: $ȘC,z,ڿOLR~Y2QY{ *SG\>7&"8ホ(㈦҄CU2;?vт0)OY[eYL?VeDWa1ZyC3E?ïŸaƃ cc}O 䇨YU rY]\haDD:Xr$<&A=gXL?p8iCg6#$00mB**j. -ˇnajR$,d)3 Qwվ0&"(@Ɋᑕ1*Q c A6P "|s$$& L_Ua0?)%hq'!K ) bKNa XFBљ6*6l,by2I cu QG-;Z.B.NO^fw/ǹziU]G4ؖL>(8dR9^ &FEEE _8s[Aptz4~T2ѐH׽m_po";GDON6X[`0`0ӢP]cJz2'd#UH@IibY$O]y7`nst u.g& 3͎+=֨1퍘!n5;˛QYevnǍ8u 1q|S峾yw/n)= |qΕ罌 񓧷")Wp uMUcOqWtsWwڮ&|`U }QqFS#"pRf_} +: o B\?"}qhJ.7"$[r֋ίhHF׷ʌFxŻ l|b>F͝\nP(R;Y v  Ox3'ai3Kӽ>^$%\e]ɝ]ƭ1& 5N:0 ?CanfDG=_Å/~~.oퟏd[}}m#ln;xSEվo0шRS>U-$7MwFDc(Xm5ab\ 3z3_>| jUFqn10TP%]""!a"eޜ1U '*L&+r)-6XZn )AZ>TeZ5@ 6@*Qp̘^Fqn`r| Nùz<""$ͥXzLÛZ3Jc흭ɒb 6LD4ӢDZFSMl)R!9!Hpᙧ&@*۱jR:xj| VeWob|ƭgwÆ\& 5No6 ?BanfDG=_Å-$7M.hhlRB́Ҳ&LWŒi8cGV+Qmo~ܩQL? -(8T ??_\;Cu Z$ys:mj_lB!J%͛V+GcS[}tlAь{oթZ|.Q;</z$Nbf7WJEK{wylޜ1U '*L&+r)-6XZ흶[ m*2Xvw:iتXhT*1n/Όu^O~/ܩѬ!gpKK{wR#$`@h ĸl]mg*ϼfYެނ,qh`'d!MH 8B($K]`xlw#7l,*uY2#5H2~%͜ssQk}h^"~>YF`_;myq dSrkos%̘x+vݽ&"r᪶DyT3{MEC~?2 H7T0tCH7T0tCH7T0tCH7tw1~Z6}CdmQZրi)!,Hh׬9뵌P[ާ[Gif;6EƯdDv~dۚ=~rNRUD=+zo|_k9?G{=|{>[~ɀ)r!Q$"**x<&?.``n+ouo20`XL,Yy%{i& dz; N\\O*jkvIN % /fZJiQYCh`V[ƅsd;nOwXٛSY Lf仹iB~oz>EKOټ%'\{M:K/Ojkd붓lvE^FyEm+lg40}Z4vOwpn".7lgSf2mr$TcӾsؾU2}Z4l9qޑc`kc3B>;S,Z5#fG+~rUStV\nss8-<{E28[Z{۬[oIΊV^#봬#qr9YZɁ{Z;_7wp(2T0OiqsyX=2IIQl5}g8)gK̟ƁW)};pwd_ϭw' "^ID|Jx6o=ko,,YD)>< :9# -45f3_`+od˾2?+"^~=?LJ"G1|i)RV҅Iӛ;'߹/y=#:Un&(_E0n^S,~Q&nX1S<˓v,֛sߪ坶xX~Oq̚9_?~7'd_E IQq9o"##pϛV}/\/h  3}8YZyqY,3qՏ}}/t:DG1erA+ s""TADžz% ḶH{ME}Zw $ɵn^LTT(s&,¡} \.7uv-wܺ~p〵=~d&flx{;/m FiOnU3nDF )E@gD &4>!?3L"vB+Iz ~tIDDDD1 C>L""""""P$""""" L""""""P$""""" M "#ƅp/dmQZq,5ksz-#-떥yakYMܫi4{杤O}.qDƯ|o4HKc_+krXr].{pe>_OnIDUT6yM~\2'Vx8ߺe$[jmdѼDQ^GJ=4493?OoӲܼ:reL[N`+oV"ut統\)QStV\nڟJ`1ipL8}c0?6,=:om;{2:7'..ضx?;v~=deNWN+EUχof,#>od7pEz˹tzGQq9س/S}c 2ID|pR0W~Gɚܬx^y#]|2ʦN"Zhb+oԶ3Ab.++39l&vS>Nc%j8Rb2k{wW>=s}M[V~w_]ó8gr}}3?41Fy&^.-/}8& ~>zء/1t D &)驡dGiC^-$tMEeܱۧ1A(;03uflqw cљj6n*&sfwa q[o##=ky#>+%$D?iiqXjn]v+p'E M=NM;ZvM&rCq]W7<|{ej<ˎ=)i|gm[oof9<od;۱74;.?A>}O@d(iHq\4-C;IK  ݙ;'`n,nwo"x\qVbvVQn |I)>+%9)g:L=##8pG5$oyG0ihf?-wpf?˯)}̚9_?~ww{<ٱ(+~ʮ_tk X~/pϲ(ΓX`+-\DEEdyj{{ ~}dN/t Dv4DDWm}!%,og80~dGx"""2"mqr'\ä8"`iND^v"r5L""""""P$""""" L""""""5L"""rN,aX2Ȑ&n k]Ov(CdmQZրi)!,Hh|Yski\,Ow P]Uwl"""Al70dNFquxFÀb1dayP`ۿi5ADD7L"SllMdD$1:)سN͖%>RMbBj^u.m8TK?ӦD93v@{V7_9s{C !&ZZ\ZY07)"{ᚵ9DDo=fqcΊa_N!!&RSt9ɑxL"p5?"CiDEE1}to""AOٺ$Vk#%4&>Vi{m?Xɴɑ$'S[jپr>QFb 1§;yPw_lMPb ¬4;Z@9qtHo+\.7UMGbҲrؾU2}Z4l9S"0.z$F y֭[0D< 3X e|Y @:u̝l44p,%OTd Gen-;=:){lTW70(ȏkLp:/tԳ|1}WCt(fg;ͯ%6&гNn~[e%`6ٽOرc~CDΓ &)l.jf17+W;k . Ù{#\1).'=LС/ؾʊ ":oСl딟&v[l4Abʻ0WSSCUUEDΓ &)mCbc8Qbr!ٶKәqc(5LZ`ѹͼ8/VqS13ؾ DxX@%",Lmi8[\cHZȶJ 1|ƌHD|`2wN<>< :9# -455,kV,$..`z{533/˫%̘.Qn ZǟztaRs1?+"^~=?LJi3bspPfU4O8v~~~{;9O=tVm}߹Fmi# gߎw݇IDDDdk$[jmdѼDQ^GJټ%'\{M:K/OjkE IDATd붓=uU4::،PKpnyjWStV\nr[ycxw`%S'E0a\8Iq\kH&IafNdXj(5pjD`"RBYYFn~-1urkldrZ?"^V\\А<ID|o?\#d͎cnV<5WRT\:U˫kһmBzS "<}S;S,N`PQizD)uZOmbc8Qbwr$RSBx47khf^Gٸ̙qlYb"<,m 3S[g&{iy05OXY)!!&]a/77acxBM݉.Z4~~^{3- 1’EI7xǟR~yFSSskvc~VQz.8Ng+1с֙=##8pG y2i<aph٪y;~eb0d\Vx o""3L""""L7*DDDDL"""":͐'#T0 \ *DDDDP^^&ѣG{;*DDDDP~~>iiiy;*DDDDf-3(k]Ov(̾5ͬ^ugyG|hƍ r3>SFZ(-KS[EL#m|붓X,H4+h[)&DPowR^DV&M 7\uY*ӧE=8p:]duWrSU݄z{ Ɔ폖NqI=e `V2uRƅS_MBTXX@jj#3L"Ⓜyq Trs=W,c58e5͎VƏ gӇ'ͯ ,L\l {>/':jc3BIK 泝exz~\se a;u^|(ЖG׶+WatRy5= y^VfihhoDFQ$">匧l.jf17+Wl626#u & `0`PQi{7fW,\.yks/8.[8߽kJmbr.܉ˆv("2@T0O۝WA)c;_kSQ@lL'J읖M9KڮG 3S[g&{iy0o#xKr| h PG&?jj^w8]˫< 5w:Cԓ,#=ky#>+%$ĤK20Dd`b˼lLɬ1ϝϦOBNHCc Mͭ0* QJ`_Dp.OaӇ'صa\aVNv'Qy鹍v}98pp3AA*dXS${ 3; 3:=Ͻk(_E츁 S[ḶR ,233y' ͒'"ASs+| -MAACD䉈Y6l'bOH566RVV!y">FHFo!"ÐM'"""2T0&L""""@`M*DDDDM*DDDD &ߤIDD:=^X~7P֬Hzj]L2BnYz:1VPP@ffIDćY,&,LvSX\~\K l74 EȅnaS$">v+{s72?+s31ZEWyY \4- gr&& 3tn͖%>RMTdȸ1aIcJl \nvqP%,ALEXn_ݛ\Q V[#nȈ/HbtR0kl1a2injeD&O2ގTb"5%B?&t&ߥDħձ}.Keh>r[ygiN>Q¹ܰu띌ƔI:R=nݵLq;n\nn&`%Oar>QFb 1§;yy:.'[jmdѼDQ^GJ=5493??>MmNqI=e _?&4R$">˼F16#Y3b7塡fNn;bf,ίB3e%v۞̋c N9x񥣼>Ifq,-%HN fɢḓqYGd ._LUMhvz 53eb!!&ZQjm>;Џ&L"KEDħ-TT6u@M`輑.{2Ю6/ n_<{Eoy.'l.jf17+W:8/6C_~L7L"K03uflqw cϾ&'v=*k`lFu.۵c-odgh8ͻ9.嵝qjqc(5LZ`0n]Qw 6&%O_oFz}L7iJqߥID|6ڋx\ LNLt9l/nfڔHf͈vFQ[*_Pov'Q]̒Ʉhmu`%jsIf<3]w;'MݍL[ Ξ{AM*D|vw1(BDdDyTÐ~"33'|ۡӤ""""TPP@zzAIDDDt&ߦIDDD4oS$"""*D| &~=D| &~Д"MH?`mq8k]O P]URG1{Fl]֬|o0@Lt /M\̹ȷc{u39:]FZ(-KS}PPP@ffAIDFi{?{!y>X,K&QWN"yy1og榯9폡>k/s(*KAA7tAID|ʺ%4uycC>ic㳝V/SٗSAHѣٲG if{d_@K_:BSS+ &0eR7gfqcطʪf\.7;v8p{C M"sf,Ulz F A,YLdD'osӾlJFD\.X\4-Et9gn-&Lf#M,geWv^9 A:szEncX6-]G}MM O9gL|$ԔJpjm冊&ʬ $a6).]{l?XILN} f) -̚K?>9;oZ 5{_9(#1e`e[OPrεפFnkg{޺M ©;堺AIV7ƆW]V,Ӣ` l]B8N<3]{i#IN ijj8.2CpTU7y-O=oД"O!46PT| gIl$wZ77yY ̼8O:)P3ͭ҇T]̚9L"S$%Z8^xgarf#qAHZȶJ 1xa{fh41kƆQfk`Ӈ'(>_ qalG\ Ԕ|8]_w]n&rY4/03uflqw cϾ>7=[y#۶b |L*si/?c2iJq7< 5%$&%9䫳͞Km7 I>Z͇p3f1cyX$3cHIafn 1’EIgSnSC1 ``~VEz.~&'&ܦ7l/2"frWDD9`1tԗzO3a2DDk׼\ vQ[`HY;'{;Y21Jvr.f͚~;o""DgDDg$%XX Wabb=ECaa!7pAIDD|FRn0dp8i<iEDDDCQQ.KS$"""r݃Iש`9PDd`9R\dP$"""r IIt"N䉈Y6}oz[?)%pgĞs̃aDrŜZ;̾Oλj'Cߎkuow뼰5ͬ^uр,Cqy; d*DDiw}^L`X0:v7qΫ9: u~s.(_PUBox; d*D'x#45`nS&Evt9ɑ˩ $lZ#$&Q[렶@Wm|k^Wyg1TV5rٱƁCZ3mJ3cjf5`0HLbd"#<9ڽeUb5rϷ&r?iQ,m;Z6` HsS+ %2ybY]vej9DDoE:jkjJp}OTd@Z2ʼn'4$OdGd" -̚K?> @n^wYTO-'7P\ROiYi#IN ѧo6 XT{C afv+e$&X ,|=y JNڹt^mm}lQW}m_6a\8uT8()fs2gpg7_o18p:]drn/YDrb0K%en7wYvU1u"b"!ҧܷ 53eb!!&Z\z:~5Wx*9x񥣞GO1t|^V3/!(H;|Eaa!0-"շv{ M.7t~Crm`4e{] BͬNǀP3~~}VpFp͕sㅧ8Ej>d2B~ac0)'MЧ>Yaz_YD5L"2b34 guL!ٶKԟl9`4qc(5LZ`06SN.]DjJo}殯鮯LJp,-}Ѿ3=~ùmr?Or*(( 99???o""."2g%^˯o`pb;O5+frWDDoםL|q W_alCm1cyX$3cHIafn 1’EgS_SC1 `tI]:K{gR[*hEEE'2Bn0T""ҽjk=6jLI`?v?sۇ z,aGprWͺu 2a )—j8x V&>?*|_v"2T0thᶛ{; 9O:~2THMpފѴ""""砲SN`!T0"@`)T0 ȠIDDDϨQz_YD=L""""@`YT0+=ik_9 mGdʳ7ߜƣo[7ش~s}[3;ɁOr7nnZ_ۃOА.6U<{ φ'!+sv{7ͬ^611a}g9==r{Kp^x~\.W^=M|zBϊ#5%W.$))Sڙ:%r۞׻ݲxlsRFLDz>0'">/wtZ`0tj4tg㣏i>>ش_8kvne}gOrYW}il `0)~?~g %%F~?̞99wHH W]1ߍȊk{0<*DħQXdcilrvWXw(>QwU<],Z5nqjj,&}9y+X=/Vx//? ů^!&:S"00??#?v'w>Mq7^?[+~ODKaa!aaa*"raS$">½X(LܰbSy''E~ IDAT8X7/U;m7#/r5s<~n&Ona;ۉ~wr֭DFF<អxa˙Co_xǐsbş+~|-O{l5]{ɥӉ&::)54J&|y $""T>!jr~\w˹DE2wj,̠rS[ggpǭKx7~%,( n6ذaC!3L""/sfOdyo[0 L~rq&2"dHh!y]t!3L"2 VV 0y;3LGjj*{/>C!3L""20dr:h<Fa郒Z[[5IDDDt&ICDd؋ȅ !a{0 o""CHHh8IDDDT0@*DDdXaݺu8No"#DaafT0ȰdZͮ]y; b*DDdX0av(2ܬ3L"# &-ZI$2r`akѢE|'I]aa!~~~$''{;b*DDdZ` I]QQ L&o""CL [I&|T0Ȱd(hJqK kIBaa&|T0Ȱd(`T0ȰdR[[!y"# &t {0iI {IS{3L"# &t "X,ED@ {ISAA&|T0OuL2X4OdS$"">A1`є"# & IKaafT0OuL2Z'2`dv5$OdS$"">C1@k &K ]$BTTC/Q$"">C1@ӄ"IDD<>kɾ'$! d ;"*A\PԊvqZV-Z(ZEв|Y5$&{`M2Gd$&:Ycs9{|riV\Jǎ DZ8%L""Ҭ9&7DD 4+zI.%%L"IDDyiԨQնE/KUUuKHIDD }iʕ>i222l!G 4;#F`ݺu 6aÆ1p@6o(㩧k{[ K/D޽1bƍ={0h ΠAرc7n$11k?l[kkdH3~0y0 cǎUW](7g}fa[ׯanaaҥK}Ç7/_na^>|d\0 / la 6ؾ}{mKѥKl6UUUC"-0 IȥF\\'Ndl6s)|||"((ӧOc6䷿-7nn[oCbӓJ|||l0`G,g-Mf1C"-\nnndzbŊ:/ 櫯b̘1̝;ę1Qcnרoٲe\K߿4DZ=$""RǎYn6C;L&ד(+,,g}} , 99_W̚5 ΛoIVV> $%%kԩS;w.eeeږ!88#G0dȐ:QsHe3L""lO9+0uZeC"-nfBHNNVXW"".IDD#Fal6[Zs9~8ӄIsHP$""ְa())a˖-Ecǎa2h۶CS$""VΝmyrގ;FTT^^^ED\L 4kzI.ıcXDZ6%L""Ҭ9{I>ǎ DP$""͜c IDP$""͚c qqݒ'"&iӧOG\\CF@ 4{zIDZc$"&i3 aP$""-cq1BBB tu("(arssYp!Cwm`R7of|WܹlADj0:+o߾,^:nͳiő+p憏W_}5111k׎p: HEU0HTQQu/9y$TVVbDDD8RqAnwXVRRRHIIq=z+BS$""͎a=kx;Oߒg2sEHȮ]N~Yo-"͛&iz-"""pwwUVYYg0:vXgY֭KlUa? `p jaîXk1iuysi!3껩,cɺlܽJvpJDDD'C\BYoNdex)gdvgӇW?OSnx#-}+`ہIY^S7x2 ykR*sݫ0A7Ąw=""-&!, OOƱz|y92oBYc\?6xճ\÷E+ZZHV^*}HNc0L,Y?ӌ{/2r3=U$"W&7F_NKtNbHtHN7]A &#a2  U/򏼀0JDDDo/zwSc@u3}FU~=a.Q,^G?nW;-'u9XS~+_y4gD|)a&+eg.mϪ5_@XJC',C@2K8ZT#a:{e՜>; @Aa9Kelja<%=}#ps3Vg2vfĶgllvchRBk[e74DDoDi2Jز®=y$v s`N:4C^~yetJc ҎQـ$֛ "yL[Srظ)(?nOt7emG 9Ι9Vw[3:^=ZͪXrJky%"IDDE Iop|}uEc`!&:SBpmyGOw=R{ԈD3jDw;dfԨ`Z!¼I_pp̣34DtK4F8)08p*W g:wCXky;XxA4DZ6%L""ŷ$;fAOeRPX5cm9?Xf //wNoEꨰUQRRIDŽ ,%X}^=ر+N O=INڑb:'<_JD@ 4DPT\{k֗F"L&9o/w:'g_>G)!CY>o7g&v e߁VKgvTUړǡ"| К>Ф([T&t &Oc4DdqDDD\8֢MCEn_W!(""""""N(aqB JDDDDDDP$"""""&'08IDD֓?xÐm=玟ͺ_[HX|Ƌ OD(#22_DZ|~w#\~k^} {8a%WD0Hg1$?E붓i?tGyii9>LhuG~"I篳?ΐsv6ϴn; [%_;8u%jV fUZlreu~>L& ~ee8fݽn?\cg2c<ҿog\ߗe_o͍o\ZD 4I?7Lğz?n{lnG0 b.|b;}H^ \zp9/ūo\~*..61X/|ٳ~vHae䉈8IDDDDD %L""""""N(aqB 4YO=c;$q2q-[|D6SԿq2=Q~)(Z;~6;;tQHӤ䉈H_!MySg־|˳zn͍)722_DZ Ԝ"" IDD=@jZw7fiӦs^[R 05 m{}7@ll$ C&?=3;& g#Îs6O08'ѡCtݭ: !yZ1#G<UY:tn24V%ODDO?| юh÷ض=β3\/k<Ľ3`W4h}H `2NS IIIgǞx >Eii_ Rrѐ9ʫ` c$3324JDDEY;viˍc1}L&jW칹jt&.6I6(*O0I=U:]8'22o`?À!;>RRkѐ9dɄ]ՋHSIDDZ֓jwvAVvjzh{-^CH;g2U'T&~L~\~+wԪqT,g-v| ^nԌT'"͗a&Er츅oϤe_owKߌOЌ<ۉ|LddZ{'HoX2F_ۛ¬EĄa^t"4iIDDɷ 7'YಧL.m4"$vB\]:eϐ{f-"i`W9o{_Lvv>>^Xr )ᖽՐ90cM~0,\kLEe0a:s(;hi<4a4 ZaqB JDDDDDDP$"""""&'08IDDDDD Rl۝}"=g'!^Lő 쬱m6>6״¿\qiqL߾Am:;f3sU4^<0Hu`/G”<6n<5<08v4 ׶uuhu:>_4wDZ%L""$YrJY6lK)aa1DGv}(f3mK IDAT cdQđ3v#R}wwn9oaMlߙФ(ʛ6O7:vC vÒSni{TkG0NnuQx9+mc=g'~xxQ^V!tRgg;3wbsT VIgBU/G׊e_Hf}٥M8rrX>[-lߙKaL1V6m֛ "yL'OUBL+UUIM+-ٌKfI,9.n7/(#{0L\+)9lܔEtM':ʏض#޲9/gs匒Jfe8SCsf4dlfaמ<йc0O.S&inCdYJ*)`@<=ݙ|[&S}5U?b6qh1eyڏ9F0oY.""r_^UT\;plkOB|vQ#b6ÁCL-epu0r6W {6mͦx'܁1l9z!IQxz5%ʋJB0H_@RH'惏߮M?dI&ӏvãSQaSB&Xxߪ/K ws>(5V *lbu;4hN&A 4IyeDr2>6KN)6e6%ˎһg+VEII%5إS0_}}a0bH4AA{<6[#tN<4\Ρ140DOҫg;vЩcӲ9> Ԟ+BCH|@sJYm&Z]ib0H4x`kV>HFIi%{QV^}ؙK|@ k(z]:7^[LzФ([T&t &nǻV%_D6ކ vPZ~f hM^l͕򪟆w2G{ ׬I 1QHRQvk&W!"xz/hi<'08IDDDDD %L""""""N(aqB JDDDDDDfa3sUfC&>q"3fA^ I.!w1˲YvMh֟sv)].y g2q|옆_0Hu`/%L.1WTe;Np'1~N9טy<6kұZmVa$|in9s g/y"x(a&njM:ꅭ(䔲jm:ٖR 4ċbش@Aa9Ke lܔ9n M"[.Epc BXq*;vQcvu3cxX%ZRWbێUCZ8j#9U}WDDDD-,ړGb:w il K1$q䖱v}&@HcHM+-ٌKfI,9'2ܼ>f'OUvw3ѫG+V7e1|p4vl8gvA~ASCsXE 4IExnT زzV iMB| ^zح٩Ou όf?gE'3F +"ɨa2j}?I:Z++ +e[W]Zdjam +"&iSo3 /yeDr2Z [%%yRTI8lvҎ9)rTJ+fglJ ctqŶѐ뛿"Ҹ<i[vg4 ؚ.?jlv*RZZu M",ԋEٗG٪o«jVK:+N`pm[b՘]l 9+"0 AKEqE\HCQ JDDDDDDP$"""""&'08IDDDDD %L""""""N诤HfC&>9; b.]l;ra226̬Ln.C .nΘ=gg}qL߾A;;f3sU4MJDDںBH#I٭βKَܼ2;Ff2ik6~'wt`ǶOם燈HC)a&PPXΒeGIG\<ؚS 0ظ);s0EbS^^E~~9+s[FaxA= j9ںBUu7%!> /vZRIvyo?eeU EXrJY6lK)aa1^ "gR.O7:vÙQe7X&}  Vi#"~"""Mҭ7HN&3β3RӊnK6cFҫG+YuKNi9V9edePV^E!OUHn_n9߱/!>[~}"(b ֬ ;CIGNnkg:),f߾: 3!e]{HB>m<#"&iQ*̛@  R5Nqw7zOIdL In90jt&(ГĮzR^^nT ز@yE__wαTT\;p|2Kjѐ8=$)M9"-j$7% >Ӎ Ҏf2ѹc0O3ŷD`)DiV>dssrNq{~?׋4?_k׍j[8ƶ\OUС}ŧ*.op?P'76gApWUtEҐ9>RI@%-,<iRZZu .EX>J/`Uʧ;!޴ e?0iڛGbP 6Po&M@h6dLs˄sަ7x`k=XV ٍJʫ?щG>t_[DZ0aƹqX6: g0G:.i <4a4 uxV}Zgݓ;*۱]V^ko^ ."iIDDƲdW^ ͓VD~&fF7i'08IDDDDD %L""""""N(af'$0,#ӹ/i}; ]<ҌYhI>wlVwvL3m=bKoǟn`$ m??,V.%k'22_DZ 璵#"-&i=ƣ#%% w;uҩcLB[O"C47L3HM ?k1J{b>?ZG~8z4c-g-v5o?S%}knQQ>]V؋ U[o/MVѓ?o ooOzN~6ǞNmԎQDZݒ'""MңcgyՇHّ3Ͻ5 ,\א4+ǎ[.cr<'oLhȘUT8}l۞ʗK73Ox핇3}sF9 ;F0a@23/E 4I!L{d.#d֟a+ ,]'_Nn^eUsu}Yq/;wa]O49 c{'0dP~G.mqL?ՋoGbe_o=~'>/)K&N 4I^uX*oVlg5|tYZ? ñfݽf 7wtq9}s)K!?v;wO޿aucvV>g ~/q.ٶMT㧈LJDDIԿϋ/LeYZkKFF>|-\Æ`*J8x̬| lۧSc/Er츅oϤe_o妟L7;vY i0H#ؓp˳ ~};Ҭ{k³w i*Մfʉ9=kz@tTrx{yW0nu{'&5&? [[ r$1 =B(7şΠLlښǟɝps3]<5<ƱSur4_JDDIZξza; `llvchRB-c嚓de`r3˨m d ?PfΟF˽z!ċ~ < `xA= }yr,;J8RvAlja<%=}#ps31{N~}E[dssrzC%"-&iSo3 /AA{<6[#tNbWG9u5bm_N@,9lؔE\,;J:(qX6R|\UQRRYcucBYV>Iaؕ@Wmug_Qmo:R;| YyUﹹyշEr2zѱ8go~HˢIDD}"(*`|=5;Mb8>JlK`[Կ5+לO0(?F!$؋Jg\:ԟK`' 䔒-[ k(z]:7^8op vPZ~f hM^?m+.6o/w|}=hV3+ފr{u)z3ibmd݆Lkůsl͊'YB0()p6?Vd,\YeDZmru5\οt g05xqn_W!(hIDDKϰ:ܙVaގ*^{k>KjpH+L""$4n7(* 0ww@.VD~&fTs""rwDDDDDDP$"""""&'08IDDgzeo'$qzv!|>9JٙFhIu~֬}Auտ?YscԼKo9{?s_!M8󚣸Hޝ=n]]P?_^|vDx0в^D.VDDIUo~ nxkI0 ^؎wӹ/yU{1OѺdbOa9x( [%O<.KTRXdW f孷'e=< "*.*LyR2uG~"I篳?3\BI7q N\l9|V n7jB[OÏٿ>z d.}יִ7DeR$""M+~΂+L?|GٗK73Ox핇3}sGǦς#g{S^+~6{QmÇ(-}l۞(;ˍc1e5|ZOçH͂y3: f~};Oӿ_gfQ%GikO>rw93]ܟ祿5#֛ٿ6;>}יִ7DeR$""MҲi$([;viˍc1}L&j"#B\}Yom`挛uM/Y'LQ_dd0ߜkC?U;{#i@W{"spbb/8u}8/w^ۯٱ0"fKL&zam)5ʝs效LzIDD$n8@E:+~Vv^uX*oVlg5|tUv{:psw~>N}/uQ s6^* TVV[OBh6ox9y%"-&iF_ۛ¬EĄa:"9v‚gRZVr|2bxO, ً]3rXO~˿˩7Gvv%'Np>R,B""Ə@4~r+=9 <0׷/ͺQf˹><ۉ ML+,{rם#>mo3 xktHbɗ:2;f&x~R2xϑϽ~w9i^H;go^Hd2 8a"""UQvk&WQChI L)n_W!(""""""N< !e&'08IDDDDD %L""""""N(aqBol۝}"=g'!^Lő5쬵/: IDAT>.}Cp?̜vy\4gJDDD-{9qxu#ƥ1m_"ߗ.g0HTe7X>`Ķ(.`t-Ah#6Ɵsvٖ:w f6|Q Y(cHٙK@i{TkG09;Ӎ*[rrX$YY%LDG2jxBC\q.FA>0ظ);s0EbοzZξza_«@~&iV wң{nDM[Y>RF &yl9e]8T ʾlj֛ "yL'OU֔6n":ʏ&MYlۑ㨳}#u\s +7kڑm)e+An_PTﳥݖlƌWV|$ulkO]C1ӧmD 4I0oL- 8x,K P^Q8LTTٱ;#GrDy BO.-LFWshnLY1I$bB * *Ϟ߷ntyYRoUK׻FVހt ;ym .WwRuIեNi|GgIj4 i\~[<xp fk@B\!B )+Ì= dEË̻3 Fjj]ҹ\>m`Z{`n v7v0Xo`ttwy*ѼnrhX,zN=E2TV93t6}vƦ.J%!0 ! %st+ʚ2S vKjոx{噣lE~^ ?+gع`^<-t`2j s㇩_ۋkە(@J+SL#[Ve^{VE +q'EEp88ՊժhJ$G(` BۊsVbd,] !n JB!B!#LB!&aB0 q0 !B!D0 !B!D0 !B!D0 !B!D0 !B!DŵB!=DFhX4?a2%ջ(@}2ӣY,sڸlkٙf6<5+崵wܳw]w݄LB!CGج As7ޮDpD*yU|33PUcVɤcscdNѺLtZ\# BLH®:ΜmSHb8^v쪥ޅ( ZJA;ml"`oi#Gʚj,+L&?/@PaGq-HI6pxq8<] 8Ղd2gv, PU=6)I&Ni%ʤKOBQgBۗE!Ąt#e̙#eR]P#%u46vri E2ijO> jhm&Nld̝5؛8t-ɋ᎔(NoCG[@J7dlboi5ioe壩CCOwY3mnfiQr|(;,~O`:}ګͳz`Pz;յ7}_l̜nwsFqۓ&!҅*' 5|T*8wsmt{ҟl'.6l3Q;@U:X8 ^ͱ͸pze*Zbc"9{s3h԰~M:> 7|>{;gR(&XHd)jq[&!dηst+* >>KSF; 8IdvB!&aB1!ܪ *8^z4Y8@L,2$U2$BjjoB!&Z!B!C&!B!C&!B!C&!B!C}B1)J9m<]7ʰY wqXdUK EQX<(5#3#ʈ2t{XB^mdRԡP#JWoeG#eMhj&R+!$nB1!]x ˗{U|#\.?-~ D#8SLB!&,3M]':Zυ v7v0Xo`- !HMk+?EݑbrM'{KpvxIKt_N|Q!YOB|ʷX8zer2391y%$`B1!-ĩVV=F6tq`^<-t`2j sGIlY[.gCU|Zmg Km> FQݹ\>6p1H:^ koVѪȝa%>5y%TʵDB!m빌Q:bJGcw5-ȢB!BLB!BLB!BLB!BLB!BLB!BLB!B|qB m՜9Fv]1z%opRCfz4+`qS6gʽ26n*Zv g(pJ9m<]]7!$B VL:[qxWE oWigq"jC*>ժ1+dұzEj1rh]&:LBLB!&_,.l'HI2t)kBU0 TKv"qvpvxC#}G*(2t{XB^@PaWIgζ)dGt"|Y( 엧˵jyjÝ(<&l3͸~tA^z'%̞jiw( 165:5ӧYhY3:2ңe@PaGq-ۈ1oj!n_o!>=' *?3;';j7uqp'O2'/,3ʫt_/wx}A)kfXy(Z5FGSF7fưun?fiQr|(;,~O`v,.KS(ZIS]%4} m%ة=l_Ur8Уݯ=_Z{Sյde:D00$#;vhBQblV-O%-5j:[^FzԠu+TQ\6bc КB[AM"bBsvx63'/m{dg͡M-m %II6ǚLGY dL.Mx̚y}S˦ĄH[t`#k`8ŁCv53gv,_wហ4K =k_ϗ&`^t^^_ۏѨeLWn(/J^_Wݕ38zer23Bd{S{JLfwgIU-Ժn,u+;LcS%ꉎ6BL0 !zUco"?/̋'P8~LF-$1gpl;wЃdfDa`4ꈋ5!Mj.\DRa1Xs_)&lV[\y 4Z3Eϩӭ+k&;LQdqvKjոx7eɃֵEp88ՊժhJ$RE~7!b|y=q9J6Vs={;gRhBܮ,] !n Bq<I~eB!&aB1! 64AӋ9ZF#7EFJaB!Zfb)yB!BLB!BLB!BLB!BLB!&8f|cne$=1g6\}h7IzYqk*yB!&.9RWĭu%1ƿx+/I)Aq&!SX[}>CjSlx8w u| 9~uϒi[;=`??Zw \Y$=Abᒯ3cx(1̜E^>??ǼE_ W4.\uy? :*CW[v_Y滸ܞxB[J&!~8o^6_JG@δ^sph%?K_%no~1:]/ ?/߯ _9q"]^~eO}^ fq=o4.\h˃q6`Ͼ>R1l~˯lϞ\#/&VI!Ą`o?:Oi^{IEŅ:6=ήR> RScimtgVn]ן} >r?<) .%$X1յw5\YbKff".yWGxDEErE~^'>΂J.I~7c/㟾Fjjl!&6'bBo~^#!cO|j%N^~ԔXIccM;\?OAS# IDAT{#*K}Yi+ܿML')Z_[&%96>\5|6^{u'sB۔st!n.W7O"6̒a8uƻj>"`!mA !bY(@R1gNOw0 !dI[GFJaB!B0$`B!B0$`B!B0$`B!B0$`B!B0dYq!Sڋs5~{ꉌаh~-qSYw  k2pm`wGMeجy*+;̆FTF}Fz !n= B8tĎjL:VHa;[>լ'56pmo(Z Z\._y\lE2ifbM12FjǼ !C&!V_0G뉊hw3sW݁JbGq-ۈ1CٛرF{c3jy*wË<&)kBU0qiFS3}koUPvN}Cmߛ ѲfudfDF#q}W5 (,-L!1 VFFz԰G =7BI%'̜NQzvl!ӭtvBʥ)ˤîz>h66TT:g0wN[w`oÜ"hnm_]I};\V1wN. XBP=uÖ *yB`qPq{I!Ĥe4jY0/T]tRqd̋h:Gy`{8xgdvb#6p~<V٬'?)s\&/8l^=z7=:u !n/)bbS IuFl^6 %0ߪ4_Os.5=.qede È8y(H$b24tt8]ކqh3ΎmӲ,(BcS%ꉎs! !HMk@~^_ۏŢS.?He9iy}AW:8]U me$}v9y(ȔZ5.?b sw- 1ڛ^>_[ych[5šHM1Vme$PREJ!z.rx!"}D:&KxWCۂ0 !B!D2$bBWB!B!B!B!B!B!B!B!B!B!ߒ&bJaML]} 7Q!3=R0G끱iӍifY#?>/RN[{7={uM11I$B\q eӪ&FoWigq"jC*>ժ1+dұzEj1ro !HI$bBjj7*d#WAo`?o^?Os9[uwn6x-k&:ZǡMR)kBU0[¡#v֤mb1PvOt?KX$ٳb7ucW-.E!f`TRzf %Doȥש>25l ;k9]Fl?xSGq!P[Y{ F{%u#Jct g8UƁv>h66!UvRt[J'6 ΉcM]cv@FZT赜l3(K.ع?KhlbeaWI}(M{/HvVO  *yB`?Gg9~Y6fN9 #I$bBfee*Zk3F֨ex&py8WN\l9fΏ*.8;J3YOf={$VAkt{4FkCJ^=z71>}01eRS !֯ru'UT]ԙVW:xt}VC\\vg\.?-~ pJEum'Y=#Aδ`#ec'Pu{5(\Ȓ$^߮z&ԧ ;Ϯ.BLz0 !6pN.O%#=߭g6N.Gq8ɾz:06Eéh]&>*'3s?3-ϙmlVMDhdaL3/!HM2>2Eɾzu%bʺz-KcU!F-֒] !8(\~v% lbʞV.M}r؍q\m̙++ϊ6vO mYa2[\y 4Z3E܃bc"xb4vgвHvޒIlY[.gCU 0٢ 8^Njjc4j%hbR) ~8Zg,#ĨYxWCq x=q9JǻBL tLYA!B!I!B!I!B!I!B!˜Tˊˠ(AjGrQzx3W}ڈ0EXٰ;O'g.aL6T=re !B1L cN+g~t{o;S# rヿ Y)sYSnl!B!ؙt>`ϑ;*^7x6o=#)*TYh, KS=_gP<Y4[cЛa弧h\>6;zwMJ J~GS%NTn F7h^* {|ZnzE 74T !B1M-:)K̝ k5ΗI ORc? @-˗oX>>G_aܧX=K|⣯QdžW[/`WXurz?_xYSW?[]/QTN yͰiPS,Q }?{@驷ߜUCs$k;!b8 v7ytiQγxO=]፛쓝ifYlTjrn_)瞽kuBL.`J;~Da'k>GYǜ*{]9.v~Pfm?NCKsbތ|%撍x;Y/0%?{[?~ `JWngFZif,aq'{:LAjnf7y$زu͠[pJ%`B1RVul/F\WرY dұzEj12h]&:s%>.`jq[TuwHoQKTbw/X皗U*u(`R+Ӫ՚~) A%l=AШ{quM_6`1% z|*zzK BfۋkhhpRHI6z F !Hs]q>D00Orjiw( 16S@JSgZ2ӳP/Okc԰5E4Lf`z\z=S{K9RքVfYa2y18lgz>h6>>^ͺ9u n6xu~:ZLtA+TQ\6bc  !I0^Z+~XKܙ#g?2c緐4'^aż8r :-}:̦A8]ULZB|3Jbȯ'F%r܇[3QUե [pt5YϮQIm Β6bm/'|:6<5lZZ9qlQ7ŵ9hRU*>5v/L2SY天ك`LMm3d"<^w3A\5olXwkt9B[wՖL7jY 3[Gl֓gcjzrm?H[[ylΝo\hGlQ5| /$z$&D";WN\l9f23w #V%7L ͌Wφ?4R5T9zjia2zCGp7ᨅŤ } {k@?/aw>o!oåoGg{ B~M;Rδr믌0| u{5(\Ȓ$^NJBWmʉZbtS姹oו %\ZLb*'JOB^*`B1ul">]JFz4o[Eww&p8c^vsKϴx#5Cw߬6y V>yX8zer23B u{5)&]~ᓏf/hLr2Eɾzu2$@B1!.JBQ+II6zeb +ըwH͘dqQ:r ˇVofF4 1b c^`Ya21^{wޫ w0%I\ -?+;wp9}-@^Z9]6`L1y%o#w>ъ=cY!Fe^ǻB[빌Q:E鹷ߞٱ.UFĠHd)jq[&!&tx' W !dY!I z5<9ZF#!D$Bq*lVoI!&2'B!aH$B!aД$7%c"ĨAB!7H*Js[%jw5 ֒] !6>lTjWikg"M_NW0vd3aaݸlkٙf6<M?Uc"8_WxWC{̦B4}[3MHXFb|ίo\1~<`*ZnDw|;>_KLfQCGجL:VH =7FtTjk!^c^9m^gO%u|2ʫo^G3Vr= hY3:ҢQ\6bc .jܕkgZF7*ߛ7߹@[{7?3(=dĩ\n?&9c)XZzz`3ң[ȑ&Z5 |Ab LBL=uOs]q>D00Ć+33ٸ(^M')By}565{^\CCZEJ+ &ˊ3fZZ -a۵omQжZhw ¦=]sbY4eN 93oJËgBQuظ @J mח\qs#k;ops'[ȟect+.j\zhn0#i*GGY &6l ٤$[cM4Bk'-+*?3;';j7u+wbҨU̝coi+TxOpOD ]7l׾o{^\CmGgt] Tc1,]{>mt+/m^j] 3s#593Б9y1ܑ ;\۽|À\1^nmK Y0/qr}Gnv4ӲȱL˻1|Wꕩܑwp^=z7qr,(5C1uzzFzrmLZ|e1뙝k#:ZG?½Nڲ2UNV-KᵲڻٸtFC~hfgjT]tr($ӈ>;{ _'K'xaBxJh԰~MzhTI7DRoR 'ChgzNQvR:܍.OB]k'qgIG_>7~~!>gL[4^/?4$d6_N~シn9na;#[@bp)Z̜'s3?w=Y9v~_뺩y|dA||T$+e.k B5}u -o{E::}ܻ;GbB`od=&c߱h!FL'pz9qUѨ4g;P`L 3s/Vy~wod7BimUT#QG=Y),.;{Ζ/Vyb?h#ԷǠ7yOQǹ}lXw,z/~KN{G#zZn4nOt.<Q1UJO ՠǧ膬gQpOoZ:{n$+QdW/P,0k>zS2̨* )=#H΋-$&2tƬ}7JQL\Qsy,#Ĩ#1Y ƻ#K0%?EV<_Xss ߪcNeżزyb;_&5>|u5ӼJCG~Fc|Ggߧ+s⟢yx7)> ulXSC|sMIoqylwQ MPQ{gE9h?UIP 𛏾Ǔ6cތ5ߜU#`S%öx/>q+˸] !Fa;9y1DFjyg14y]!MW!n&O)h"A?+=%Ķ$͊yO8k:u(SU7hPu`!Ta{,^kBLN)&>H$O57K{oYΈ׳LŔ_ ;1kI6;sVsCw2}?+G}Ag"=1Q4{IKcopR _,z?5QV$֨DxkQ6qt4oa~ysgWw֨$ 6gIUB!I$3?w=?@8ry|SUO&ަF[J HTvٔ";ȨFgtTgq\AQ/ CY lKҽ4MҤ6-mhK_ s{rro36&Lfo(gĄfOذxk eɝ,9.Ceܰ8*kM{CCp@4߁,ƢfM~oRʤrf&f0zP#O}'[T6f/8?̛fK.V7{娥Bѽɤv(L;ȤB\kWS7 !B![M]CH!SPBE{kUUkt=g_o坷aB:Ȣ’û:n+HVPB|'D`LtZ'u,[䵄xۦ[ZG\apxyXh;lߗ{_7?/%!/rШU06Bz> -Rp[]>>jMv>6;Aqe:`:r?P1f^y;iLNy{c$jk,{sV܄PNxX 5%gҏ &:? -zmێpYYAŕ"汍^s?*QP*DFۧES뉉 a!<{tcFY=(-\ ]E [CE~w/6QL6ɧŷꒈ:^zeU-|S9 v? ' ?)W̦ VP*fڤtvҳ?%R#:8Z?Q .֗7ƢQ+ٵ%mx{0lH0)a( -GZbe('騵;؜DZ2XmVc iźjkLهd)XmW f0k8.!0ؼ5 HehD]i~ȥQ+/I 2S{!WWѻkǟz]O'x{CB}Nb~/,}u5g2f@7ɓcSjxV.d<jjs>L&wt!&S V[-^7  Y9 o65=2s M0#2y-+* 1!<;fwu߶`%>r}OQ^adqW6_ /-7eXdTsY71bX6_Pdb^36$7œgd^={cW!Q>6/Hv*d"g]&)Xw[>{8tt HUz3C>Y,m-?uzF>zHOQmkZ$J·Nn^-Gru|[La:?&?{o?\]wL!&& #k ^yԔAL4Ҳo=\ÄqC8p s<:w.sH+c߁lS/<<~Π>4k4,B߮Өo=y7#.6;N"::ҲUT:$E|ٺNAUlr>\?2= &nTM9Sɔ Q̙ɜrB=ILgPA N %98?ݖȰAʮG6mr41QL^0$I@S#I6Z7S(ے Cb`O̞}u_Zjjx{yfREe x(W7HCwq_;o=mظ6|.V~Β -AvcШ=x@|~/z}S'w.]MBPPXXFHmgu*UVj:uծQ|yt?x0&y cSZϋY3Y^TJ%od_;yfs8s3.rx)r*S\bnc iLEsus]sY M9no' Pd"7qu_=^=z% vmaS.DzHθV}hUZ86hk=\ !:`?_P+/>IqK)/7f)OC`nXMIbgs9"K9{N{j.ι>xw &s -s7q09K\EhHCERٹ;L_4hAB0]g"|{UVN&.֏/>b'0PCEXmvrT201/O E&*$k֝eu!O P-q]004} SSwKf#C9~sII`B||mWqI|a\s-4 !zV|=.=vxu.Y4,++VnfڝDxys2y8[3|}CP3timzEӞE:$:O."};FGTd0%W'|f ?yx9o =mt0dpl{A@иT:&M@T&Gzy>4xbRfןp<<:8tS9x{pCJF̨0**k8|@ -֛yp^?(`S'Ez޸lro֟c`JM6̖Z`Bjd\_!Dp8t&Ɗ]Ԙϻ3qז"s xg;42~ʯi<ۮJ=' hy&9?ܫ»BV)ӷb̨V)y^zߏB\M nn$|?ڌR`ذ=W*~ ssquI^:qUΙsZ9{j4`"B2`Ʀ$yN/0=;JLtZ DWr~Ar@_FWv^[X,7ψs$ϋB[נJIH,u JJH~䜩p_q[fŷ)/yB!z0 MHΏ:YJ__$ݖwb^-yy$ϋB;H,Dw!9?:ppT9'p+h^Wyy$ϋB;ȀInBr~tDY Ed,jV+jU\hXV5MʨR]mkw^$ϋB3ȀInBr~t̉S(0hOm]9Vʙs H d(3~w!^^*K;tPNe[nmW^$ϋB3tZ+qښˤ1A<, hIΐ$[)y^k-OIOi+snGjg9ǵv~j+LW㤣uFyX,5g`'_Ct[,#%J)y^,JfLC摒&O̡?|(tPysٚVgس!,n1/W~nwV+=G]jg~Cb?Z2c[fY͵LɐAA-nrI)p'~Ͳ)yFn/Ǣ7Ho$=#Q͎֌g95Xvf7ڮ%rT1%g+auM(.1N"ms2 BAauAuT)Wc2hX? q8@7aw] )W͡#%JK-s[#{ C9) @b? %Mr0,5lV>mܐзn &:`~yrT2eBsf5YoO̞}uW],5Vqfj\&U-9S]uJ!hʷ3 1ύR)>] E|?Lfs/b^_MDlor[8;綵gom Cb;sBwr_38[řs9wK9S-7mކM*#uL8R#XYNeZ]ڔ3`/)y^u55vCΩQQ@ΙJ P(AT?0/"#>]h%>ϙU^.g> Pd"7quW/e=^vkb  !ɭ5ߞb㋯`\>IqIePo.[-~qˤnKΔ9SAz fּVgCE!>/%wEzFs㠁:\{$:ʇ3.<2pv+BQ}Ey\s[#{䑡?Q$sw!>>j\J?7FO!ĕp-yc"P|E_}sHMf LZlc||F%&fKnג1Re5XaR# | Vkm)3,mnw0G}u6Ԗ]B^$}>Ѿ-Ԫy|Cz\po"¼p8T*H )[몏 ୿sx e,~8U&_ՌNW=˘YjZ&ONa;8p Y?M=~PScTva2Y\Yӧ 1jjY_gȒpY}yͯ`FY4ԔA;ohO3~hYQq_|}7/( # 捻: ؃) tZ, !"2` X؛n/Kp?sy|\8w1**fZNC /?W_dᔖ]$}!L&?uuE@6{o?ngPRn5GEoiT7?<BƝ 'LiF** "^kklsfժY^6m9HlPB!K-yBD[MclظW7X ~#~ynA$4j~w=#'>6_6hW׿P~ExyiQ,}~yQ\RL3a-r;BY ]ngccw8r?Obbf֬ID:)5p8L?UgRZ5Ӌ f=Sͭ3tH|u\r+Ewя` J8.O#̸~r1ʛ{} `4B!ڡazoEe+ۤo/SŘQa e3W( 0@Kp$C%3oN_-D{\n:iZ<2{xm-;ϸK職J}vJ#{Ɠ-Oc sd%Vu~/^z={OKaޓە׆ƌ{|l=ݵv~jUW7 Ժefڤh,5g`'_Ct[b/WJCĚwGQ* ֗WV^EsNq.jo~Mb(0pyoOJ/XpH_M;ݹԄ[L,CɄHgre3Q(k4>I:j6q, -Vz\mc(2ykz AN˔S7oY52gf\FtZ>,#%Jаd&.ַ}hO !\ K;+1!hNk)ok˿?O~˅ ,՛ܶ`A !**C%-?6*aέ=!|*3S =L`so ;'Z,^k,+(7 @\\8{2:|9yZ?ϸ򨩔u:eu;wڜ֒`6_BtWnLS=zfLja!l|Cɹd#%9n`>0tZm3MLExl([jIjW*<bܼ* ۴i_!DR))˴aPRGO:e8B^17=ꓭ_4)>_o[^48۶:_|[nF,̦LY?8K|grx~͑xw+Q1}|>6tZz4+6r;g,WUNIb?GOW8kO\7-o$ ۻ^?7b(-7g_KM?<|諯xӾBq-h)oZ?Nc(`+h@~96h IDATΤ6l %gl#ERhoK??O}:]ѻXu Y'.0tH<;?XZz4tFri^\1WoMsk il\{׳ь$r,Mf||F%&fK-Ca2ղe[^bheͷgnc.v?CtOЖӾBq-xE JgYt+LgksOζG:a7;MuYvO,ﻑ#S'WdEOgjj4k>{ĄH~cRbGOzSO,$:*~z}^^Z EWNݒ; =nm"ǫe ,D/0 q%<#ӑ%wm]<6 oQ*DžW s} 3i3jvˤ֞C7t^kݵv~j궗|Yv[4`NH#9E[>w屩ϿRe4ÿ|R lwqx˜۞CF!>):ʇ%wuB\U-呁KW<47ԝߍΜ"aa<;8CŘx5m'MƲ>gM 儇r]X|T*%A [CE~w/6}r\*g<6cHiI+F2+4?1z}Y/9uVݏ2WOkW:BT?Btrh}B箺+}Xx1_-7Y^~{_o;+2? _:@\lw.Dtt0e]%RikGgصVB!n{KAk@ EӜ14Mꝷail| tfW~D2.1R/*wIkTmІ)my b;nWǮ:$B}ȀI.Ԗ\ mq"W^|ɓ㎻R^nlܜ~;< z]y:wIkTt].VXv'j ]%{#_۪uH!-yBtiƃ ~?~xu-⟧oc={'ni>]*J<6i?g~X6ÌMIꔺ۪uH!=Sne3ϼ9}TGK뼷"rKZ}֨MoXG\omVkw9=cYeinnیMI|6۰\uR3є<|k6|=6$0֌|(jucԪձ`1gW2sZ,>>ߎhد#%]'eJ MG}y &A:-S&F'_5jIk6ކpW#ޒ.PKڬxr((vR*1,ʎ]LΩd=ncİ6l鴿ݝ;nwPZfv>l?dhKgБ1 UUi!UUV` GؽhEUHTK *҇ w{ ,fؐ`On]{?]N| \,d)APpPղZ3MLExl(pnWmNBV~-_uiɜrB=ILgPA:5 @_ݨ춴Y$ [:|!DAPƧFpLe姲΁&G˴1̮pF|nKdXj+n8π;cwzKZ['ڇĄղZM`Ja@munaH??5&x꒿&@~vhQ\bn#Kcu׵JfbBR\|U׫EvN% We~wi֖A}M*#uL8R#XYN53 B2`R]moP4TTjHf'L%{vvs[O돾D!k:ԮC*-F+SQi 19bj6n9xXLlUH|?֬;BpC*]$HgM};wzS9uW\JJ8p B^\t%=LCqD[qӌ+vBj$lT s[3*-%0PR卦|HUsde bKJmCGJ8S7D0jD(-6+`f1 }IIo)al^qF+k= 76ƗibSx'-f9ᡤdl<+&=0 љ$O1H*%@@!B!Z &!B!h BG}a!B!B@LB!B0 !B!D d$B!-p{ZW-D{um !ޟys2q**kON5[Ƿ[yy} ݖc:Ȣ’B\_`$B Q(!>֏9F !ܭSLiQ{(Jq8-v*/Z^~`lJh:{v8Q3mRW۬BS\bf9h*nR`^=}u @T.釄SѾ,[|eA8;Gcĺ1mqjF1֫?VqإgfJ&F2tpPuXmv?!ĵaqy>p8o}HL @Kb6|lv0 "2(վg~H^ne~0lp1QTT4ZWEAa5{cW!Q>6/Hv*d"5Xv֝TM6Ré&evJAH'j DFx;gכ<>ٚQ\ hLAu.chK[vvv2a?V9ܣg֍q†0מ}u率t HUս*&v%Vχ@A fJς` Xmc0,5li3CBwr)t%F0vtNeW0mr41QL^VqX7D$Sc2vD{SFo0~i 2nӧPZnf>Zg@@T*٧+8{"ü\ߖ8+*kxGdN9!$&3zT( tx럏O$yd(ޝzR_{- ~ E[a 5X,uQk}B[yC+'EgMǥjԪ/[;+kHIsaS.DzHθV}| Y$1!3( l -;;~Pk4(.17aAB\_v>l9H]X"yU􍫻tx);號.PxǭCBwr)"; VSB?1BC5\`u>j.mo_釄W­aTTXjMMJmCGJ8S7D0jD;Z<<x.8nl\:Xj Ei燧V`-:Ά&Fx?F i@ !o=3*-%0PtVBz>l9H_p' cێm/pN+>uRt鵷jCB+p8Xy TT0lp^^| Z?[{? &uLxWԬ3._t Bʕ+YhQW":Q<Ɗ]]B\+Lё> aú8!Y7 Ժm$:/rШU06Bz> -R4>>jMo;iQ{ϷWFL]裏>_P9ɭBޥ̦ VP*fڤ6PVnaͺMg< )˲QkX̵L$v8UFpPdrYN%*݁Zlh~Cb?Z2cfY͵LɐAAmn!F&1,OM\/;vٟY Mz!z8.]ҥKq8.ㄇs7F'ߦ ̞`"=#[oI@%mV<&&"mvY,m-?yBߺ|~UQPXMvN%;uc#a EFz!z0u۷7+5`B\3oZ<9g*2!9⚬7nӧPZnf>Z Ct1md`|j$#Chsy*nK$_AAkXڵm^U?<|q2`O=*p8y=W!D".j*4 UVJ̙3YnBq,HK``b FoesM۰)o7GQ1.5Mh}kiԪ\zuWy=^ʻ>NY G@-Kw>D6ZhQ\bfLa+[/O!.`Xr%555԰rJl6[`dddPUUBC,۾/`@b 'D`\Ia\3[2/`"|nky荒GR)X1,&=#%Z۶Oݷ$_ư~$OQOL]`ƍ9qv7k,V+7ovGxBqMHJ/r꛳DE0mrhCa2ղe[F0xx(60[j[,{̨0'8|cYenjky荂s` jc6]M7ķ~B!?S'[v-&&k׶ӧs9:B!0u+VRR:tK^jj*AAA2[B!n$NT^^κu뚝fn:UJbڴi_a !B!~ N_P[>|.n"==WB!BJLhŊ.)ݖw7RSSÖ-[]B!0uBl-[PXXخ:1bI!B7,ldժUW#<Үzf̘'|ҮmJ^1X<]Iz#RPXB ~LpqY h:R°ADRQY֓uς` Xmc0,5lXg7{lտ vef`gTsY71bX6_P8}u RUe: # S4c ~_b2pVqXXN*PM2,5-OP!I:vSVf t%F(9xjSXֿ̟gO8/ߏ>їj's $18_]H B/٧+Fd" h٘L&vu(Bqi* 6h5*ƥF0ǥuOqOlzHn^Ky)+4ݠhֿ,Y;! S6{l6oތCB!BVɀ1c&E!Bk zX*cB!d͘1CB!d͞=,Ξ=ա!Bq͑Yzq˺u~![-[.P$uu( x-[䵄xۦZZY[XxcBt2`Z-SNe2`B8iQ{m||L|U1HGBWd ̜9'xłVpmd&>y!, c뇇`ZS#f< )aszDzʈ,`.=3P2!5x{8^R>mxXmPB.'_gϦE!*7jsRa!Vv*dҸ(HߞOzF>zHOQoБ "&ʗgy9ܣg֍q†0bW{>^v2aaS>j޳t HUsKed ő$ !z< CapV;&0}Z f3`%t%F0vt6-9{Q_=Ug{PTgw7t#4Ms\5$1hL J.377Lv=jלǜs?֯~LM;gRɘ`eP%ނDDAnH_^?TQ>.{yk=d}yZ=jq}lO<0( Rdr:o~H""2L&/kF]E(?|X :ߕU s78k/Ds_SaZm[DZJ_?'dggG:9 -5͞ﳗq9ۂ-΂w aJEy.`^V. ̼Ht%jQs}l"tҔ8:PDD"bˋ-T :ڌds)(pP{^m2[2HI?OI]X8"_)i} zu]' 0"Bw^*++礽]vaX裏=b2dϞ=EЀOϹnzzX:h~_/oKknXecslt"~Y@կ~E bL]@Ddʰͷ\щ HMe[qfÒ0_"2ה0- :HMM ۷ot8""BVt2M/ki)((gÑEDDDD䉠i)--CDDDD䉠i)++ի477G:yO SRRBll,}YC0-0l߾]c%ozۋbEi}ZDT ?tݾMy_KRb ?ia­ϻ<1A TVV/KΞ=֭[##pMRb#'<[^'CEy.Ǫx#,z:';ŽqC9X5FDhŊpA%L"Ƿg]s..նmfkQkV'E7N7?cMW]cWFpknbXQ<00; 'N5(>M>@`ű=ElٜNݾ./3W}6OcuaWwrK`2 Cs"d޽TVVF:VZũS(..H "d2QYYɞ={"̡#sCdA.0D0-`rH"""""2/)aZ8x`0-peee\|PDDDDD%L ۱Z|gEDDDDdQ´dzuV]$""""2%LBii)EDDDDI(--G:yEOc}Y9x /by$}]Eطvh3y9v^{e)dZcO 7q>(?7vMpXy(aN'UUU#;w,CjΰzjsɎU+#N' X,H#"29c4n?}78n:V37b6X"qº 0 :?_spq|TM cMܨh'T7vֱ6ɱ*"&t:/ζm"Ȥp9ӂn;$k<@0D}C/+AV& 1?oB~hĥZ^ ϥY0lp'fόntɎU0Ɉ 8|&6Ow;IL=r"(CF>MM>H⩧YE\)1 DMd/gGR \lqѼ9/LlX~b:GOܥ֬N":ڌlα6ٱ*"0 e.ݻG_8q+WD:L&ٳ'ҡ797ku-ׯy6-D;ll͑Cd^Md իWillze_c0h-,\Jdu<Ypbf~B~uIƈDcl6CTd&""S$8N=JC(%L2餯H"""""QJdl֮]#HD)a >|8aD\+*//7 D:YQy ش>mגZ8:SfL+-^LfͶmk& v+03nq&in~}GMDLJdB8H#"2+.\r6a(=dNϿTK/.l2q< 9)/g,+~\X-fV8079>w628^̍.RcCDd,믿ɓ'|ED$!] ks]m>Uߥ7wQ3Gu=~f>I1T厫/?/>ZZ|qͥv>\=2.q\+6~V>6Nkm#my}A6oX@ _Nݛ} v?9G[+<( q}nT8\i)3B!.+]377{'Ia27G5IH?osR:`p̃};{=jqyǴM}hß2ذ.8MY/aeffn:p:GDdBVK& 'H{Eo]946p;U'殼I-d#ߛL['q4wOXm> U'gϻx"ĘF#ǚQEѦ%l)J֏Y>᎕ڟn?K Lt.aLk<@0D}C/+;8p6r9v>G 'AIs.s۬{n1[_[NJZ^ ϥY1ښ`úTCGؼa ghf舙l{t86{;phy |Ԝin}0CSdR:E:Im- %9t?m $uq,E҉2Ǐsm23l6t59qyҺ6-)\ǟ6m9Wdžu9{-xxis:_H0ϥ$/* 91SPsY)my1{CwxDG'/FceMX]ܨҔ<d0?ݻG iii_yw#04E={D:CF<="Ȃ`]ͱ9a aIEEEQZZJUUUCsJdJNzzz^YDDDD)Ik188ȉ'"ȜR$SJMMeƍ'"""" &=BDDDDd!Q$t:w/_t("""""sF L˺uHKKȑ#EDDDDd)l2-&2կ~pDD&Uy ش>mN_;dDG 7.¤IM/n/ve?~e_KRb ?iocaX~noΛVwDd)ais:'?H#"օKnc:yr2}6%Y rCHbL[2SK#\U7F>/NY@Ey.虙X3 bg ?0ɴ̡ܹCسgOϱ껴z1Mdfıd)ǫgyqlaI1l9eYߍ:$ŐnWcUFp^V1|Mju`'s4\\m#:֢ ֬N<0yvv 9QX< ?x~1l#4hP\sk'lcmI W7sЌcYDp/rH""±4jδ`[>b'"OS䡕yL"qE҉2Ǐsm23l6t59q-/cPuOh3^_^x8xG J_]FVm~/[2HI?OI];1>n)i} zu]JE1aFX @eee#y81?iii!-Mޕg2#sCdA.LRmfefu20+~͂j?HIq&  nF])1ocYQ ޤZ'q}ArR _y7]6Ks&:On#]mnc%JK s6BE<&96LFNv"O ">>H"" ^ΞwQZ/]̑wqF{}A6oX@ _N ]ky+:X*+ D*|᣺.mřTdM ?z3*rphi|2xab<ϵR%՟ߛPȠ? ~ 1u,<90cˣF:Y`fq"'q}*`pVrGg -AElXJ\&ZoEb IDATKsШROӕ`ekVV&aElwz{:1XyrSD:Y@< 1{+ߛL NuYˑcMܨh',3~qǁ16ôbԱ(P$to>)((t8"@8VzzToeוG͙v G<іG7m}_\pl -5͞ 1:E'x>PDdZAJr &@ 0HIlZ$^F]AE>D(dP22mly1{CwxDGY*o1:E&0Wٰw^*++#{뭷|駑EDee%{t(2xzE: (cs4$3trq|T$""""P$3#ȌQ$3"33{N1SE ̘2=IDDDD*Jd8N멫t("""""3B ̘"t("""""3B ̘(vܩDDDD䩡Ɍ*--}}}G:1%)10ҡ,HZ<ԜiՋ vm$nf]~noΛVwDɤIfTYY@GoF:';GX-Q&. l3ͦkf$ks\NEy.hMIfTZZ7nJ ̪v?Ǫd6ǎ8VN㫯 r)}-E1Q!{dd 9jۈ6(5G,Rcx\РAqQϭI[VwᒛAXCv< x 0q|?ٌ0 br˲FbL1rY-fV8pBNv}628^̍.RcCDd,2t̺cwi]yQ]s/.TgSxÙKSs\#< ק?0ȉSY.^ᅵ9r.6H(. p\+%[2 P)weO^+G%krVIEy.m~Nִ 7B]WdVE7WufU+W$#"&q455qH""OiV5R.VC/7cgϏv#ʚU$$Xn,by~ק7oIHfu{ՅIl)iL?oKs2qqQ*UycژN ..`úT4)Gd_̸7FUUk׮t8"UCcS wzis_um}!ݙ1zceehJGSsy9C#A׿yoW䓔39ru]mZ–taU13Սgߵ?#" &qf2կ~pD)umx,r|I!rp\+6[֗2pXRQK _3e R.mb%B#fҲCRyXgy |ԜinU,`)aYQZZ__ӃáyE9V}?~Tdfر-AvFNZ!O#&Q& I$uqx IJ"Nn-#$kiz[^L艻TÚIDG'/tl4zzzD+qqJD(aԫlػw/duvvƿۿ8Sd2QYYɞ={"̡#sCdA.0DAfErr2EEETUUE:GIf >ڔ HS$fݴq̙H"""""H0ɬyg)((д<yb)aYt:0K *իWillt("""""M ̪5$""""O$=VfULL *UUU?CL+-ޑnۚI $%>r׎.?7vMputu;?rl"dR$tOO|>bcc. ""O?Q=VK/Ĺ .>s? fiڲ,(;tSQ%ZkD(aYW^^sqNgȾ,Xfg0 w'q}ArR _bYVDZj,o<_4(.5ɜ>RmfefurwᒛAXCv< x `ޯ-<*yLJ!3peYQvv 9S`xu37HI! K?ȬdݺuIDfd% B=krVIEy.m~Nִ2xab<ϵR%՟f}/gϻ({-.|ڼ@β'Êd q}fvwJB!.+]GKwZ}|+:X*+ ΎyO ^\Df#ʳI| oKs2 V֬Nzo0 -@ 7,NY6O nzژLc Ǒ`eͪdTŮQ{W; 6K%.NrD*˜p:?z*k׮t8"42{X7(ڴ-Eq#<%n%>ނeD^۾hЯI1d0.նmfkQkV'sm\nBB!orm;vxNs+v[45?E7gpU'Z̬(pPʵm^B!s\ށgSؼ! 24Ra͸> 9)/g,+}kYX))dua҄61`xu37HI! ƮYKD8zhC'\'vu278w={@ `ye9~w+?@W- V.OV/7{',mՋa#d@{V8%KaB]~:ցd/rrϵao哙aV.~6Uu=\.ۊ3(ϥɚr^_0S; ~Lu|v+:X*+ ٠KDqF-OD[\\4֥R\@QˢUa,NY6O n!?7h3 {ilOBFmLwOX6w?Hc}˲l\^Z>j<{k{'Yecyooc[K3ٱm)th7ٹc)~_J#ʳI|`x^-.msqQ֥ >"OJ8N_00LS~hDa2xw\Xn:pN/!Y•]e#lRm;idqV/OlllSa&ak42WG5qMKR>88cWS!\ի׉<0I8N~_se~D:yBy<.^n0t]̃+=V*s C7rz$rp#!`[qFGʴp>֥۰Zͤ/ݍ~ B|[?4`rn/GORJϬp`]6ZwHKngFgckX~^65gZ-xLDd^S$n:233R$",nnm>֬NnZX[28i?$*D3. /'D\L*mnKHO0 ܈@x8x((YJk|[߃-.6lٰp}tKա;YDt/dǰMNE+iy Ø`Z޽{p$ww\zgF:yL&*++ٳgOC94osnwzV.0DA"tryn+1%LQ*>,ҡw蒈*%LQ񔔔""""2/)as:9r@@E D餧H"""""2& 5-ODDDD%L2/8N>H!""""2\+o}Pwȋ3?o!vQ֧Iv_;dűܱ%X-;}:1=(?7vM+putu;x"O %L2/K$''SUUID慊\,s;%7I1}&2mYQEoᣍ?YHu}07}:ݰءӸH" &(--O?wߍt8"طx .+ymRC'Őn[TFtEÝq} `kSؾ5˵r9}5kVڝLJn;ݜ?'m~UߥՋl"3#%K9^ @Ww?>͋pd3.0HNaY,ˊm׿S,w/m 2x~mvٷxLLV&݆䤘)j1+tt < h> ittmk8 eWwLۊ3'׉DOҜ  nF]q ;VH]4x4X<>ZZܬlNV/.~Z]^2&LUߥ7wQ3Gu=~f>I1TR]sǶL*sikseܶU)7(. p\+%[2 P1= y!rޤ0x8\sdf[df8}_Mpq~D6wᒛ+:X:;U }@(dyKv:VG;q(5X">=Cd*Jdعs'fCE:yEa]*E4,]E,O`Ts6pƦ,dM}es46' ,ˆ#{i|xFuX# lߚqF>;w,CI`mha2 V֬Nzo0 -@`Gg -چtuo-DEQŷ7{ر-ضonׯ W[CyqcLVf؃};{=jqyG0m\\uiTW"ᠸO>={D:yBLd sb1c֝^B<Յ+4FĥvbH^<*͡;4ܹ:7wYȱ&nuQi [ÇcoۣWΘFQLw&HvE&L$$X2Qs5l\t 26e=SaZ6<#)ay7F bD:yx<.^nT#.^+!zY|hg9xa8sedehs RI_b㋋mXfҗą Yd q3~.7#SmdfI|bV's)|ygV8p`>xmjl 8 Q L0%jQs}l<"21Mɓye׮]PSSPD `M}7WwfuwSZAJr &@ 0H[Y$`2&3PesAƒ8b1 ߏ&LhS:QQ&Q=Mfۆ|fU2> 'N5t m Wrsamx֥tyG!umM&uFi(pP{^m8166OcuaWwrKSDdteݻG2ZrPd2LTVVj3osnwxV⢛֮N&66D:ҡMʺ(cs;|JD|ޜpdqʢ =JxY6k7:1NK#L԰uV+=V&i&aayd",pJdމtG:Y0ɼ{nΜ9CgggCL K;wl6sСH""""" &/剈HD2oUTT_@ b 3?o!vQ֧IvC͙VZZ̐mgLV`fMyӪ?:}]<0ɼt:/IMM ۷ot8"Tbۉ.IJyQͥ?Q=VK/Ĺ .>s? fiڲ,(;t>'& Xj0Ȅ%n%>ނeD^۾hHAR 6L@|KmDGZ8xP<6[\ێn!';\_&ny|xMM}k9͙/\߭jrX]Z[&23Qtusۼa O6r0 Ų1v1*r'WMJN}!5.xJ_$'LW.4Y 81^k bI x:A^ޒq/S.̊ǸCǫQEJr `h~fy7O?t"2`y~kW'ss#˺B ΞwQZ/]̑wqX"tuap`Dhirw²9vZZ\>B(JKFz܄4`eY6\./nK gxzءc7}k&rƭܱn?/%mi7jڏ7cgϏvu4p V֬J&!J#틞{W;p< 6K%.NrD*˼VTTDJJ p.'3Q(L&OxA,3y9vne` 3NÕ]e#lRm;idqV/Ol{xwGÝ^W|[ÛƬwX7(ڴ-E1ˇmµ;0pB1;kdz?GSsy9C#A׿yoW䓔>T7vק0XDNJd^b׮]|'?sy pr;(?pXRQK ˇ+|& ۊ3]6:LV;ٰ.%6؆j&}I\xEN>icxC'iqmL˅ nb(wϵ΁ntt'? R.mb%Bcv_LeaX~^65gZ-xLDdД>>ؽ{QNp<7 Ν}W_:ٸOl̈כ 2=sfc;k^sp1>muܿ͞5 uE°Nk^KD+SO=RxyyIlXܼP?SQa@TUujO Zf?s7mcɱm\DRaDc=R{bʕR!"..j8;PXTC1;6Jui ¹b(rLǨHO6 ](/N 쌓imٷpuQ#aav칅BӅOFAn&Vaq֨RP߈SF\\^/O NvրGF 06 L(/~X~qI=S2 N9=]>gjC_,AiYF3EtZ4NJD„>(-kg+gm 5-x91%y;&_j 1^sJ*tK;Z^:V[zkȞW\]]1}tΖGD&U: wET'^/Gڙ"Ӳ z# 1wvCGÇZ rhl>[ZdfU]7$(,Q%((b 0a/tz#>H?[K1juGMG!ᨁj8V`@ʉVS!7Oa7'EuHItskƙ8V $#_i8{m7g_pw@V,CK۴ޙsM.*AΨҙ(C 0M?Ֆs-+L̟?^hoLd߮jLC^0$xȼUPW(rd߮ݜj8kTӴjFVuCހ9Aaa- l112..*|J?F{ɩD\]h,~Яw]ةX87Z*h2WP0sz nf6 pA޶syR{0qi[YܫTYߚ~ k[F>uZ"W W^ѣG1gd2}|ZL&Vk@Ii}絵Tr֝*tF nP 2J?'dުVGhżkC;vRYz΂Cp7w}WfV%u9OąA\]P(dHbn9y/%3V˿7Y4k~u4C=bDNXXw^LDVKPU4ipX{<ԨR#n^(#0|H"㫃wa3tz]R Tcl7|58}j~NŒ0W!|P{+mT@`gR6\O: E8|~ . uҶuF;rۦjz*ZҞQĂEaƍXvQHb.jȪ@QqFEz~718_dB!b;6Me \'5]Z7$w ~>B 83kϒc|PYå+epwWI٫otc!9̂Yc(pr)nfUBĤ ~7\:Q#'7O;2.[l8z 0n\7nV2x{;ID}{$~t={V(d0Q`88[qqq82X:1m?FDQqEaQp@@ tns@c`6::-=1'"ďYU:W萛Ec!"3!./@aa~Nf%kV[gx l.^.Ũ>55Kr=i )yLe jENig -UE%AΦdžqb ]#~)y(,Ì)zK7|Ú 1Qc蜶 ǜl & qqq>&";hoLd߮jLC^0$xȼUPW(rd߮ݜj8kTӴjFVuCހ9Aaa-  K,UpqQSb16NNiv .;5 X6aBC\lDEzZldZƨpuUĢp̙zm*tw繹WJ5ֶFGxcNDMoK.!**J8DԋdoZL&Vk@Ii} Tr֝*tF  u5Pe8Q?G9!VZ=B],^m ݜdߩBj\VYfo6d- YEuɭAXHSOЕke8^'~(9W#f/&Y|u4Cs"M,&7ؽ{7 &";q| uam]qsSJyʮ!Mϋ掯ޅQ̘uJ94ȾSbr:Y^K\QX\oB>{=`LFC8@:H+@  DiYvjl7}[s0a/h4*?TIip>o'vy9xcND\ ")) qq#Eu֦.~ Ra qL..삻j?G!og1>ҕ2 |x?$Y59#NJ\-ÅbX_W4m<`i`?'Mӊ?:=az'{wFEz@΀W3A9&Bt4 ˗/$&&J6$%%aѢEw~JLC&!11˖-: "]]h+Z=֗+?Sm10͘5k^8Ddrغ#첕ˆc׀~""{Ƃl#̙={`"yi J=dS␜ `D6e8xQ`"I&Kl*X0͉CRRlY""""f,,\eeeHMM: s%lNDD"""ӧKz)D>(0~Uڣ]oR~4-IO}y < :MKduW4pC~٤ /7Q?r\<X0 F!\\ Zg :Yy996u u &IqqqxwpUDFFJz`Q_߈i1r'F3EtZ4NJD„>(-k@r=B&! 3HJ.{FPiwFUr 憋KQT\9`q\.3-ɴB(R)DQqEaQp@@ tƻ2QAӧ 2lޖgE`gl(p$%Wz'-~4B6iҤI={BD=L[k1>h5<8V $#_i8{)E0̛¢:aq|8݁R2QVހ˥dxavY{Z̪©B̝Б{(*n:u1%qBQ\Rcj 0:_w cɭA~Am~/b ꎚ}]" a"$1|ڵ q1j'N-Byyff%`@8PެJ8"4S0|dMe7k;8Cݰc-,.'odU` wEh39][xbQ8nܬ@AQ-(4辟IUH;Sh,]` tp>ގdjCb{(`" 6R!&>$ w rVKl)<uH&..*.3|5 YfaϞ=,ho46 \\Y8)1i< $Y59iQ#>u & ŋBDDDD & &L?{M6'RDo)# cbZ9-*_y5z~LE^~).]nR$Ln7Cgmokk*Ξ}{_⃵/?~^ͿpVذ0?9ND~~YgO  Zy`"۶{{5uVh/?$ms0qu'%ߝ];cLG/D::!>xjtzp88$Aޭ43綷s)Ƅ)2=:S1+/-L|u s<xq aDv%!!(..: u˿'~p IDATuq_{r%ɚ2Ss8YNP;]Msj<8bccN''wwn{;(+f2id6 qz/~ Y8d2l2P/߅2MUZL]gⷫHɬz@04nA'… qQ7W`pďP:E%'/%)S`׮]Xxq4H?8WHIDmR||< ^/u""B}/9-LdQYYGJ0LdBCC;wJ0LdgF-!JJJADDd &[ӧBdN:ܺuK(DDDfq<[=;wbĉ@DVqF 6 duW47/.3=p1>=-.ꊄa6\CeΪǻ4lI`j;z+ky۝-=뚈 &kصk֬Y#u"m6Qڈ wa9Ww(B#?[SrMgrѨ0szo'Ǧhy-y,Ȯ%$$wիW)u"w^TTT`F'>ގ8{F)1xh'N\F1J9cT'GRrqz9<7|F \\TprRȱ\AON Ġ@=Fcym9z`g<>;UU:+gm,|<üNԡJ߼4F@ڙ"\R m'%FzaX岦ipZ5*GWif'գFQ^(*x]g * >%ؾ+ ee |h f1"Z%oYlh뚈 ? ___ΖG$M6!66AAAjG!ᨁj8V`@ʉ55{rrk_P<aƔ EqI=[rLe jENig -8i(Z{\SިHO8Jg8V $#_i8{ z#FGqI= kQЈ=uJa֢~Ne:&No#{ty:sz;(PV^o1|W4_:`"&]vIȮ⫯Š+ݖ"xF ވY5C]1n7[ȼU ㏱pr2?E33eH?ԠkVv`X`ْ!t;6;y&1 M2sF 1sFSzIcޜ(2dުpsUDZclmN/EaQ|/D::Nnj u]ާYYߚ~ k[etm뚈%KHHG} 4H8Dv?JBBBUړdMhַ^[k0-o}%rČ?|#hծL{g۱hljyĶt @R@&AaH 0|{Ǡ'ipJ)*t0{:}[Z1M`Z6=))-cٽG}...e"E7oFBB\\\zt;nnj? *aoq|1jzj;^XHS' pN56~u؎N߈ZżS|kw萦^{y5H`жMCÊPPTa;_SM]~? CWwSg9[tm뚈=̝;Q/y&<=1t/2ko6Fxt1>+ezХ¾wTQ[g@}ay1}1: Wal`Xv5uu8z: K]gʴͱޘ4yZly yZL1{ xz @/Ro/G(߿n}1:gK:{]QȄQOh*11Q$駟FAACdHLLIJeˤB`q](N meZ&N-BeQptTbgAB4koH/ČĽSL=  R sBP )) ?яCdؼy3-[֥b/bLV.ޥ@ nܬevČ)]n|xm>D{$e޼yP:~8M D &š: M3HX0}111GCكj$"~>7|z*6oތٳgWBcTDd\$u>Q XjRRR0k,ٌ"8pׯ6 '&z0x` ^?%%%RG"I,,<),,rJB曦:8uVnn.ݻg`0ߗ0bDduϦMhK}޽{q Fc.\@mm;@{555"M,,.LD<+W'N0 WզרoKNN^oXcc#~6畈Q;8,lذ#FĉBf[mz'Y=*A"Rܼy첂|gȶ`"ju]CC黗r oBSN鹠%552L7|B^LDdX0ufݨ˥BfYlRq)))PTfFܸqTDQ8,k>S̙3RGܽ{}Y=H*1qt+ 曽ȶ`"u^nn.:x}w466BPX|Ncc# E]R]]˗/F:u 'NTDr<,Y۶mÚ5kBԧm޼nnnXhQȌѣG^C^^pdggq28pF#r~ל1L 6`ƌEAArssQ]] 777 RR{<<<ڵksI&`"ꄖX0wi\v ~QJ%,u1~V"k'$ΖGԾ7bĈmQ Cuu5JKKBdX0ug#La˖-XrQNxx8֭['!M,:Y ?Fmm-`ڴix뭷w?/͎#0c ^; ::SL;tR}8pBGӧ !PرcT*sN!^^^m"qvvxիWB(Jk.!Ca"==]c_mڴIjT*ř3gDYYd"##C 777!ӦMΝBq1zh!111"99Y!޽{;}ٳB!JJJ+?.HLLlX8~xqA!Dμ~^Baz>? mJŋez@'s(-- <==QUUR:T*P(4#o-_~'ODvv6/^_,s>@vv6bbbpI;v ¤IW_S]d2c:=ŋc͚58s Ǝ+ueX>|xoݻXbe{նP(|rh4.־ϱaI* {Ql & GbbQZ~- &eF۶mFrI&_+ۚ1cΜ9@^Wr̂8,Y۷o:Q:~8rrr8҆4ϒG#88wܑ:MaDd/FvvIlݺQQQ: YIggɣ=LDǂ Z#Ez_|{lhDee%{lLHH &"+cDd%GСC(--SO=%ur`dcQZZ V+u"J8,lٖ-[4 4ٖ``/`"#[U[[={w4La-,qX٢$aҥRG!+j&WWWLDVĂȊ8,l֭[ vq<"bDdEGx6V#''GD6$566ĉx$LEԱ?7n4<; OHzv޽{R Jٚŋ;֭[t$%%ހ8]j*?^^^_v-v# @6ٳxwR0k,<ؼy3Ν WWW㑕UTT%c LDV$ڵ B+P*xcRT8rHҐ걝;wV,P(LCpAFDGGcƍc )++Q Bnn1luӶm 7HII`T,);VBۧT6}x"}Y`ʔ)شiR$+(//96*00555уcDMSNd2 ӛ9;;f,CN'''7Oz'N!z9Ya]<LD ilʕ+R.d9r$"##{9QTT*L8k֬TSX0ٮ悉?Y &"+X`^|Ep&sx{ꩧ`0.S(Xre/'] &BرbJ .899>&"+aDd% fh␼yV IDAT1cƘ؈KJ6Yj'Ν;9 amDÂJc(6r9u3LD=W_ŤIXMq8FkJzRsIlWPPLD) lݺx,XӟT${njPOdRdDda&00v20`~_j{;8`P`dr9|||/u~Q/Xht:1l_=`Ya) 8$pk~TVV%;"c{, {샏{C:]+p>1VeI} ĩSAﱇpBc{, {샟JJJ8Q7`"""3> $NBԿ`"""3aX0u &""";Â>rss%NBԿq<"f;? GOWڝT|WTTUY(,?}2[D6,쀃]LDĂ$Y؈y/a ?{0lN:MLvR X0dflAq\JFEu!5UXA8;zbG[HrO9&?Ci|P-‰I(_>ÂcQ5X0Zu5R2 &N`D{H2. V$aCc`z/C85Xjqa_V9m9nÂɿ̱?Ƶ۩L&GCP(8 !;%{a/.&nbDD9|#Y {/9%9(΃+ z\ëy/'۩8#"d2TJܸ{ ~!!\;Iǰ`/~~~GMGDyt̳pxcEŭPȕxg'1!H#̞=իWcW"Ν;ΡCѣi胈H!yKLL 999.DR$""ҁ*a@4I0t aX #77աYJDDD:%LODDCR$""ҁF\\DQQQai%L"""h$00aEEVVi0t ax"""$ q:G&c: f)..&::7: 6K F!ɽ]HY,Vz:-+. JDa݄xsjl_ e̜3Exzq}pno3qg7wmIA0km{jiKz5c!77ݎ`pu8"m&fHq)jbwsnѶلx;0{2nt㹟o՟EGF@=LLDDpD%L"Ͱsw.tu9p0o6W<\HAddp8Ftvٽ9?9 @Aa+V㺫y[6{a.σ}:( 7oY<ΛUm|9spcX qlͮ6A SUR^^&&ק"p򴉌nfϾ<gL&CkK+GrݻCN$!7_@h7)ٶ36g͙lߕ8f_PxT_mҍlښpIZ{KА:IkEF H H;Z 4??u6Å5!U5~#iU2nL< qyCGFVI2CrRFvG{WSK[)""EH{Mm>Ρ#TVy} |йFTuvφv0++@3xyyP"rww',,|W"&)aiI]ʱacZc+FӥS[wpp!V OO7).r`EZe[+)){r0% gמ<ztw4^HFFz&<_/mhp*44Z0!(2w>!!^yCs:R{@Fz$0fd6aL|}1~8pןfUg_Gҋಡ1 IϣDz|aW"#|kh4j8^&3v{ÇɅ ҥK]8\C I>(9w׾oǏ'&&7|ա9ZADDPS&&R$""A>i?´JH)a qiH)a J^ǥI0ta4I0tqRQQAaaCis0tqHD%L"KxOb0dČIn;LʍO9;Vwj Rqs.cAX̔f&rhH^U0iXHy:-N7&9?AMO?݂~cn)[7w'::<*2َ"nWԁEDDJDB H0IpXL^_濼־jv?>-\ͺۧ^3ʬ ?.]y* >.a"?>~N͞??ɮGg1a1S-{VtrݰZ1T1lrk^IJ3sI!1A{144<@r8GBlߑv}>_y}/<=3_ec5lϏu1vǶkfu$KYL{ bY'sg:O=q'o/=g_89z41r+GN;KS;:V/)!1L8 }{+SQQz:0--.4JD.+7ѻW'?&a0:VPg]DzX"(*2?}=zM9!,zmM9 }gZk8VgƴS nƫp%L&Q$refL!s*6̬Wj  wl{o:F 6*2Sb-7@PκV0?s15w6{x 'z"!yIi4ILf مDE8/11'Yp&rV}'֞GQ=Xv;!ѡtM܃NЯo"mٱ8VW]y)婹Nvq"Su^Ў*8814SD&fzrsyweG^lmܥs@mYivi, <kBTtPa???$HSm8CV1#HHNn)l`˶lvΥp&]f6o &e|"'O,gs:Le!ٴ5Wu|9f_PJ~Aa0%lۙw3g ILʩw׺ڴZ)3u[1٩ncgrlˣ_Pzvd^ڕH @U/zDG=L"Mt$)$3 (+U˵;S''c0WR|7~8fRZA'1|q"}HN "KdvH \XΛr<@rR0~rqc <̗CG 9Vv7Q^jjA^mY)>kݖiG>ČvPRRܪժIt/&&P$D_$0|H4#U ?fIOgբG)/#9 rJk,*` AC| Zujǩ}Pwզkojha^G"T}%L!y"IrJic{.AdXvs&Y`e[+))ϯ_!| lv;cFEы X+l`gry2QƑoAڎdfS\2 ]{=:z_jiKp-ca =ջ$-:aҲdru"m&&1,5_b% .NiYkw.I]:(~8pןګ(k n݉`XVO4= BdD`6[Y1 tN`=g_Gҋಡ1 $}׺ڴ͙20"c9{⅟&iŀz4$O vSW"fh<vNCZPPfs9i0sxzz1J5I0sEEE$@&%L"IDD3'@UT8G H;g4$@UT=MDIDD+..V$}i %L"""h$8cZjաJD.) 1aI&WDBr{ws&#h Rqs.cAX̔f&r!hT "DDY ov ÇfX&]Ntt(O?uyTdo0ZzYڗb"##]g'L.FmP$D`t=2O?u7=8.,f s 13g.Z#lz cY?M~2'+I5Hll&SicS\7V/\MBBc Ok WcnW8Ꙁv"}j ~q4f-Xܕ}_ZᎩW0|hoȾѶM }ypx{{;]3u[^nHiblߑ+0﹏xWW3}wF/)!1L8p XC)a"SHEG0|sv&U[-Ƣ5Ć{f?>l#;۩2's:!=|&zĵsKxf|z+%:Y6<6}y)jK`$ժJy"S$Do2o֏cڝ|f-[g+7tkk^nwlZ+pwusW/V]ȬGn;jߥ{{|Sz(_]23 8dfԼOF THc(ai_OdSw3fn}. ̙|C.՟g'2"‹~u)~ddr0"/Ƞ=j{!611'Yp&rV}AKٵ(O}pZ'<<^|f_0?yyPu;gT~ )sGүobcfL'sXd II1DE(iVjhoooaiq,rQtRG"0mDO8q2oKxx#$8؟;_iktTۋ+4zCz>}֯_ȑ#];wf̙L>ա aqaCzMZ ʓխOhTb: 6C ڋ_uuV' Iai-%""Ҏ)as`Z]HIDDS$h}DIDD+** ???W"D`` CP$""ҎL&݃IjԢ"IDD3LZ!OjТ"UDa݄xsZufΐQZͼkmKJ ↉]]\c-9HAa3IqɤKR"0%L"Զلx;>w4x2Wu]+eB"ꈗM [ H#(aiv6gcwnK>a@UD'^nV2zd}zRivi, .֏r̜3r2V:FD**lAJK+|D,}{%_p^nt\~mݟ? g(rJYi-vB{y߇eO٪6qA{Çu.`0Sqv'9pC1>֐%ٵ'݃9xH?NtncՙMDV 3 <{z\Z%Oq4IKx7-Oj$2·s{`v##5RO_۬AFrN1G9a没1 $b dV<<(TPZV٤zu} OP%j+aٴ0 v iRSSXt#g6S^z۲="c9wobB':GΝ1c?CVw%55}qqX)d߁| @d/cFƹ:,igFz}i ƈH|?aH;!yr.vq4"0S%%%l6_F:RW &(aiL&zW Ҧ(ai0Të~H0SJ>AMP$""NF@ {18I H3<SWL-Z3Z9]Bx-|w OJ&7i|Fr8?8ա;ѡ<]Q!-V[opv9)3Nj?>-\ͺۧ^Jrݰ*+m{#,/YمDGpG8fθww7bЭ[,` 66u$$D0rd_e3dpl3UHqyQThɿ[&_^#ً1z~{xvy)OL&zDyy#i4$O>1űLig2ػwM7pt.׎S_+0﹏xWW3}wEu)/r$aϘ2a}v/㼣G3XʱVTYn<i3/YS`?]+B~#&s)Y\c_Nnw$Gnul]z4%LRD%L"-d℡x{{m]t/Mv쏊 &:>Xw.VD^v`O_ꪗЪ8Б9wM{>7>q;@tt^fknWz>[7T+.mh/9޿L8K|^W6iiJ|t&ѐ<mq#0 vt3YO3q\fV Jǖo;ᄈn;\ZW}w^;$G{a/>.<ÐA=6Wn="ҌF&>Lfő JDd]HTToE~W㸬^Z9'OpU9y*'Yp&rV}'u 2ܝy{レ`RJ:KLnn/՟g'2"F덻sM[rcݚgv}V\\ITQQD%L"01,p=>Lg@DD0}tqlB|+Vnbc6&a,*엙2oOnq$&ŴOʒkY&bC[xwk;g7sd }%t#.63y߇_>oOvvy]{DL&'T߇n7pmqT.]H͔sfhfq<Ɏ}a1Sb&O8q2oKxx#$8؟;_ ׫=ҖF|xxx{:ie K.eԩESH )(4ѽ=ji6bؐ^l[7w\="d"!!aiJDZHH?[{=k쫾.ÆbWs/{\&1JDDF H qww[RÐVLشJH>L"""Vɓxyy:6C H;e2 }Cf___W f(ai4I08G H;dZ)--Ր"αde[턅z3x:hӍ݂kP]_]:,vÍQc'Ʊ6{'*FP$"\ U$'ѿOy[c_aa9VRۜI\?oH".֟6g}W[gg_zҳ{&թnٖ͎ݹΤk9ymY|}!ޤOi%lۙSgJ, M֟irRvn)]:!/9)R(+\ YYƌ#eB"9|!qNu{$uJl6;GŏNu}ٴ5Wu|96D.!yZ%ODy04.dpx>w3ثOB\$p8EˠK#sU:(.LLsz?u$8yѷW(XJ/ JC͟u _0(+IIb\l^W:Ωn^"c9os*qD^՚~"Zu¤U|H8O4Og=tֱvT}uYf8`hbm"}αp򴉮]C}>fqcJ!5O8+ί{ >$cx -`6WWZcs9I3'ZAꣿY"S$Lf;s1Эk91ݓ.aקd@8У{0V 6fI9Ƚ).r`Er]αL;b1.˪zʭT/bХ|kN2tP4dIHwU 狊/Eы X+l`grͺJTodOOφIˊ8G H3zq8 8tAFRYigϾ<eCcxI$6"c9{⅟GcFƱn6nj!єU@>v5ȑ`ƁC|4^ũ8Z0Ѝe DzWopވa1+W_P<<(TPZVYu5<< wz[!m?W)..VK7qnhtRG"0mDmmF"BhdW O`;Pҥ wӟ\HEDDD!ɤ""-@CDa洟:2LZ!ODIDD2L$"0CJDDZ&vHICq&vh4! JDDD!'"20Qa1S2bF{%<.Bں0)7>AlTwSs["氘)>:fȈLiVl"-l6Id4]HeEڨxF҂H'5 'H |[pws/|MU_^K֐z&MFFF+**R$"04ъ6ϧe<{!,zmُ&b/l?wiG0,]:Gq۔LJ_P\+N tż>go,XZϺ$v&ֱ$$DPTdnNaȇ;j\cMՉkfI 5Y6<6ݟBddp3^H0~'胈H)aidӸ_ VkcYp YY'SXdfɛߧ2z%  wl{o:FьzciuWGnqg.sbճwz˜zrsbbB]uHǜ>פsV݆c Ok WcnW8Z#lzzL\Rڼ$҂ 92%"NҐ<lߑxSod.9kg%ꎫ,5Ay#iٵ(Y\wF[X>tmWԪ ڋg8y*VRS߇ҏf^lX9kb %:uhf™XJY6XsUW^ʮ=Gyj{LJ]؈W(raL&O:U/+."Q$@~yr)[1:Vc?-䅗V0<=nxU~=~̞ʃ/3!e>|HMq̌isd!))%MjF%L GDc)C.T.]H͔pu"NhdW fԩEZ/kB5R!$""ҎRYYIPPCVJߕ4&vd2n%ulJEIDD^M ԥVZS9JDDDeťNVUFP$""ҎTbKII ^^^CP$""ҎT'L"uэEG H;9LҐr|}}]HI 1):a1S2bF=FJ;w3式umR竇s>ױ:똋" 1x{{P*..֐ny˩9w} 1}hu@uHnUK:TB|MX>py 0Qnu6W::gE\XS^ai%L"M‹xRoäw|=/ j|Yoy!.# ;w_+So?rwh&K9Wb n<i}Nr1 |c$wc3罸3>zs2's]˯~(3;߈\ʣs_tZ]&һW'wy_˹bϸƑr+GN;KSu"d6{ *..aJDh՗ۘv }+VnwN\;~0ӧM`/Vo%:*i/`՗ۘX ;9F]q _wLQ^tt^uԊ#**ȣUo+>?C{?tmSFN~AqB^==yeמd1_{C's:!cδkW hҐr iai"t?gf=Ԫ IDAT5*WqYelf*meu&^ܝ-(uZQnnSQQYo9sMN_ĕf> Kf3!!!CP$DW]y)婹Nvvc_bb4OdxL,rO/'g2y3z>B3^ž?g1w_ÜG6GVV/-r}&B~quCٹ;?}5/\ a-U5L]ZVfeW:'yFk||#9_=4:gE\I>HC, ;7m!H H͘v='Nxb q|h3{*f̄9xxr#7?<|3DLt(x nbWR ^=igx_%#3{~?Ro=4:gE\@W!Xyy9ޮC0v{ÇɅ ҥK]8\) /B':9˸qѣ ,pu(J 0kEMТ"""}XVB4䉴a\2&Is^%%%xzz: 6C=L"""Hqq&z!EG HEyyym0,X,: 6CCDDDڨ7Xҥ EEE|WTVVOLL jPYYIII AAAEP$""F]veXB 9|0Z+WbZfffh5(..Т"IDD3fLmTVV: RSS, PuccP$JDa݄xs\Jr&̆dd`p΁GP2>oZےabWʯEKRPXi?krl"8p X,:1 <1*i͔04&iUrJYq:^\6,7۲sj -v-Oƍw<2!O#Ç믿|'99Ik!y"IdXXi-vB{y<77xzQVZqJu9pX?)2;z&8עkYnY؝үOkȦ/DrRդ`ov\RA`@դ osJ.Ka Ch7q1T\^nt\+s Kv Za#rcDzaÆZA Jg͚储NkW"-`݆3deY32 ͆ KCESng`Ͼ< #!."c;V}奥ٴ5Wu|9uqRtiY nJIr$K ӆڡFRתDf_PxTn4W=Ɂ ͈1afqcJ!5O8Nj/OwG4S ex"?3`ƎȤ52]i$%LI׮]Yvy8qgyb$-(7jYTN:s@s,lؔg^ oow8XPcx]]ת`/^LHZa##=[AFV9AU oڒ'!۔v35 6fXE.qƱsN<ƟgG%QqqnZ+HꏽHH׮]Ȉ7 <̇)7t#,ěfac 4)azMi8SoCFѧW({s`~~J./ܑ,_⨤5*yxTd&d3 HHEHMDVmV[}ofնnXF(@X !Le2L2cȐmNus9Lyy:a!׿u[jy}!L8XscA+:i5fRStO;SE{B8+ &WpɫՊRow}нɓ'ٱcCbĐBК5k-hXXfF$\)2B3;Q d~nش'9|||8q"G!00o!aJa$aBIIIL4Gv(0P(HMM%))Eщrqö=!2m49OS4Ô\'D=LC;@RuXR;\B`s=.D guuuRV^i]7vZD$b4ꪫxe@tKF=INcΜ9 )J̙N'!蛠 z!W!9Iޓ֭[׮ȥB`ݺu.H!hj]*++ puB(07!a]B!.UUU: !F%Yd | K,!00Q$0|5 cu>_m&8ȗ`g3NoӨܔ,?<"珿;;,[|eϜjuf@vN1o961XW zn#Ma:555B$L.r뭷8/DoşW>> cu.a:q7 ?TJ%ܰI܄J5paa ӿB1TVV%yB$L.rN/F_>ooF.̳oO6?l ՄElp=*DEy$V-w<͙3=VĔv<~w _ر̘6RIY=QQO?3ƑlPYY㏭cy sMFbG>>MM^^60^秔꫈/%츎g\ZZZHN椥tCoOV!^`̟o~&#<<4⡛햅5[ 9) cC BMUU %0Veƌ̘1Cnw r- ywy}1}(3g$sض`- yR\Rc8x#7&7̒ ͟C8KUo%u&?x_0V7%#X0ocfοv$Kq=:Ó ǎ_nvlӶf g ѿ>Ǧ?|?~o=_?忼sn];kMII B$LB0c=D?|A6nooO>רTJ~p_o&0qQ=oY}414ԟx '|0>$J._X?/}C C>h??||nǓg~B Iarŋ;*jpR)jج9U*dْ|Iq믛Ӯ og6-fǾ' Ť8v9X[;6{=9ΎG<3>^se,~M7;OA1\17oPݫc70 v Va-Y 8!=ߜw,a-0K15 =OW]9Gry7 9#sp4g;Ͽ=NW򫗯rxl}=񺩬MjmD.QPɧTlbtLB$LBlz^^|c,?Z;MM6k;[}KxX5m"=ϭ7/3-Z8oc ?x#Ε˟0eXtAT8-k֖yWyArny__pSX0oO=yg=?=dݭ tPVMuO<[W>OgvlXxuLJ/I(1 EO.8fwʒY?_YWN}e$L If'u5V7c^̍n=VZ# [o걭?>ʸ2gF?~)z~'}x*k8x3W"Ĉ"#LB U$|SWwed-BAjjOi-;mUW]cf?'&|ooR~n*'vl#y}uo_q [c긥7!nRo$9Wθs6pĿ>y.CuZq!DH$B*CY[̎[6&&JQIOI5u4m^8߽>&f _m5֭\wjh6s2om6<f\Ǎ 4Ҋ3FrŔuR]W} ʝ9zv/iߜ߉ ʲ>Y%}u\wO7ړGy﫧.8W[cY.YF%~@o{J~ihW(J}y;ț|O}Ifxfwwwj*W#Ĉ"#L}7ZcWL7_c)e=B!\-0.cNj6+l{ݎÏhf'SQaHy$aꣅ'>y]'b'QV]먬+&nXXm-MR&o{aKǦg2 wu}^o<=T̜6pv^ޜEUuOvޖŋƐ8֯8. >֗U9W\#ENQOm44˗QUWi"IY(|13:Û ֈ c[|?u+EY1g6$ ;GsW>Ƌ<T;Ұry, W ;Q9@ =V,Y4ʗ_zGk8*ĥjkP/0U^^H$D_Si^4YX藎ك>7iǺc:ky9=ξЀ8Cj]Ŝ+vymGT:iPҨz,}ڶCf^=3psS27-)=Xm ty!z=u'螌0Qa}*ىgͫ+05vu,/Pݴ#ξ 3IĵWr =س]Vţвgo)93KjJ '؛a`|RF e協hlja|ٌ҅X,V|9WTOIi 9VAjJ Q:ojj4643cZ(MvxݻM6dڨjD_fLbm}4OvZ5;oYvU SRt{!2S6$%S_|&CLF#aO>ϔ} yz='Baֱ@#Fn=95=F-7%PG`H<= ԙjNg02ӧ`6[9|ZV_; έWCXKQ(.dc3;F#4a@0PU5tP5kƴ}`cx~: }}dr|4%hhhB1\zI#I  W͸P?k+م;EW10GZvIC`(I#'BAR4jUV{JNS@v;JZZl-+/z&8~O;͔W4 xq2t>N/ h{0Cnz…>>.kR\\5nC+.C{K֏:)?J |)8|> ׍*~ 8kxo[11>L#fK xyu]E]YU^7tZ ٳ_v-Z-V8jڍuOCMc4[ɫ%)uqLp1\A 4^OxxbDI>JFcS3OTr88_fMMAK#*8Sˍg3mʅo|}4ΩPfbbJ 3_] cJMMG* [ܽ4rT;.%1]7?]]| IDATOOF'GBy^w~F;_69fx'qOfN kri[\/!:eee?a1")l*0H܃9akbn,w;]٠«b҄ f־jjͤƫ]=w;N!vC*힏Z5M,j%=77&M-_#کΉelܸա1DcS Ͻp OSXw%2B3;Q d~W-*6ƖN_$ }$Bql6Tb/?X~*Ķp'F&IQ0I] !1jkLTU7p^$1cu,#t5Q:j؜_6|燻{*s-ihn'. &aB!c:u^]@R?NTwq Qk[mU=֏/JKKd‡{̑0fF1⌚:Lùh1 ]`{i8

kbFF ==ٳy$Bƨv.u5FCIi6&6(hT@Dz 0>YU;`pR*haRa/ڹt+RVWJbg!EF[ &v*^}P]mb;tىbuy?Ԕ@tޝ`jf0+_~]mGÜ]Ʀ'\K!PEEEF֮]?W_}g}ltwJLP8~%ZݯUFm5F;Sc34[Eo`":ʻrc"ͫuabi_ )hnX,V6ŋnd$d/ ɫe\,wqX4KGKggg~ZlN}>gv8gvzٟ 159`Ojj풶xXT*ٹ5!,!, ˪HN/P(Z_PXX~.aR*=Ό7c vۙ1cƀ0va1EEjQ),'<ԓp/sk1-FPXd춿[uyW/q"aI -9XCz֒w'+9Su+O@V y[ [/kg/Y}լ~,m/d7µD][QGN^-JDaBKnGe޼y|yqwyf~a,X@\\ , (ŋ>}:~a{gSO=jCa'L 52F777%:-yg>50-( 걿F SS@Bs粼/ [?̧+"hjpg{əp՛g3z2-ARyY֭'޶</r=bBKCqR ӆ ذaƍٸqC=mx4av!$FQIi$px%'52< )f+ygOR8jd5q| &S ;.b.NQͥاAN6mbiFk$fEx'6(oJE*"uB 3np||YF nnJLLJws",oL{əp՛g3mF(cx66 p0ud+oZ/~5Jwa͓ٹ j." Oxb7vdP_.$=IH֎kܵ._ *L0\Fj#OvtGG^pf.---$}'sR.Re<hC7ۯ7}~vߧ‹3Ą??T_E.s[z^3ѹBI'IB\bCy<"#{??byv+u<º[(cBY}\1OfעPИ[wL߅ؘ0n]`jjN[X?/}+\p<ngO2x䡛YJBBF#]qq1CM&! u.$?zyvc/-d9W}+VR}Հ<)^cmמeoiד[үcz0VGe[Gڭ=I{BNdO_>O>ix^3ױ+xuNBC/ἢ"@j0 _Ri#@:L;wbEjMd]뒇d%Fv}SJIi %F0_̀ųc}YuMSw˛nb}mi煸@0 ;vpB !!B' "!.q@KRyE#oFa( f,a^=3psS27-)XmlYĉ*t^Ԙ5qdV{3 =^5=RapGJjxl6x$o-+o䋝6P*Exh^47[y,Nv7Na5qlٚ˹sa*f7cRq@KKǐ ;1642CӾݮoZS.ZI؎3u,GMLwA tlI!ٳgquBx+TWՐ۾ 3IĵWr =;wכcXjW 7^@;+ŒS˷,*)|C 5%(75f>3go)-7GeR.sS]mb26ΗzFJ 460>)bg!EF[ &v*vnlhfƴP-xb}()mfބf=*7i`('1c}ae#Y*⾉'bVʪFǣ|s҆.Q[$%S_o鲏b(K$ jH-7YCᖛHM kY(F*5[:l: }1-[Cv9Ù=#q͏$J͢Q/Rbi4ǠR)έ!?_ a]_ٺ( 'su\c:Gm>OO`Fb&!ƨN\]WCjdnVe_O pרԾFc3lz&?=w>jb=v'ùi3uFBP(I#':=+px).1َ+v2MLgDEjQ),'<ԓp/sk1-FtGh=;WTXvd%/$UMS߷Mw6xluu:ۿ!|bcc]#%UPՐ[\/2;Ƈ3uR0KBC(,8EBCC3~~jj5\JN^-I ~h4* e&v}[KMC?(54BL %v>8>)xјng[XxE$1>~MMߏ]x 8d#mf8*䣋вh~{'&FDᡞl6}zzXzw )!*қ2/:Gm͜JJrGWr"J.B ŘfabH&'#@:Lw@MԔ@<=xSΔ^cySLD̰AX\Fg?/Xo56sW1Lxü]L0ߞ={3gDFv{ULdg9vWiolj [;][OB!ŗdfeD괔ٛa֌H)??FCDDCbēIa&R嶵c]uhPFWKQOZ)i;:]ݴz^@狷㽇_ꕍLgϞe̘1( _S$(T*ww$KBz?֓ j7NpS)lrm8+dJq!0 !d?˂:m4F#4f闅26ΏsELJNeWsxp&zeV_?mx;Rsk%PLQqLfd2G&!Kc(8WOZqd%grjnE\Ǖ;un`9p؞t\>;IJ--6 wAibˤ^p嗻: !FQ0i<\Bc*,ȪjwZ. Fh!q55fLDk4~W6<g$LZ4W bI; yle:v*}xz0#yUSos̍MLn 'OUѨHcἡnZ xlTn wZkria2hdabT"e׆}{qmwq]h6Z09cꦹp_0Ͼ}5kyyy2$5#LB\*]km>Qȗ_w:qw۶2nEpss#*JnWb H$DuWWj7z:!YCQ*QO \69˰HO "܋_7lHDz;jLj%M-Kבr~ѮmmzꜿHʈ2Pu\e/:Kll,nngAJ+q={KEhiU<-{rpcR`F {2o v.fbzu\KYy#_*.>i`jf0+_~|6D秂2B̙3$$$: !F I裶uEO 7yg-IΛEE>WhZ7,+_EQTV7&sKqjSsukm8FdTun(7{m]B| !+eggK$Ox!ʉ"^9_DVi3ØΛ[rg4P*0-- ubkxb'yo[?kSK]!hÍ705$a+Gw2eRT0.ѹI + 8sQ:-[ @m1ޝV+qwWQ]cnlr&¨ϋ7ԕݤnTy:babI$DuWWdZZl9V^n\>+iSBj{p>QebJnnJL46u~Yhlq4(dA=_]Ֆi F;_69Аh%ueF7'F\ƎH=NVIl˖-YɄZvu8B 2B!(Mtt$KB IB!F]$LB!ӧe!$LB!DnnL I!b0L%yB 0IB!Fl60 1$aB!rrrP*0 1psw *fN g2 wuɮ sL {xB<٪Z@#gxmߵe5qN:/o΢'xBqiƌCbTI~8h Eij̗͋n9Om\>~9j`EkߵcBL I>z\Q>&k+ź0OV8CGʩlj7z:!YCl⋝6P*Exh^,GMơ29j}3 IDAT wߑ (Sc^=3psS27-)L&Z5jHI>fiLXmlYĉ*t^Ԙ5qdl6[1lz&wtZ][1'(KuNbdee1~xW!Ĩ#_5 G7^@;+vx.F]cC3~2go)-7GeR.㋝nEG7عsE6tzZ_KJLU#--6Ωz]Ô`>^i ffMl}_Ϯ8hȱ RSySSkvS f,+qX6*N:sL8>D-=BAvII2$㸻X4/]ߔh~$nzrL5kVF`D닛ZLxkDkӳyKl7+%;N0!9z:N;[h4J)61xyX4>TSkN91ǔ[@zZCe444;ݟBUUU^IA bXp8(ՠR) Skb`Kc(8WOZqd%grjnESS{ʸ? 0)hl7K]g7R*mZiԪv}NcBa8!FI'f:}.FTnYb>QȔIA>RD?~O]WDû}7]q|IV:4jX<KZ:ԓ}v՞FPfb׷%hFs*޴狾ĮoJQɓ'Cӹ:!FI~8>_u\ SC|V8EF~7# gڔfR)xA>-GvhsB ]i;ټ-Mov]6#ı~d 'oڱ@Ж3͜JJrGWr"Jb< #BVƚ \0PSk&5%OO7^}*s_^vCⒷ|ry]|e(@dg9v,P!zr .\0$aBa R嶵\b@.b=LB!#XVV6M&!$LB!#XVVƺ:!F%IB!F'NR) 1'K!b;qbI$B1I &!̒'D?lz&wZPFWg2;,e5qN:/o΢'xBKl&77W&!$LB屨dvj,xiU)[1ܜ>}fID0 2Q e9p k"½Uz F`; De"NdULM'cسW2ܔM`bJzh_5j%c:zc{5//B L&M0\P(/sȪ"mfsysK`P*ofsv-l(:/.BtQ~: !F5I"xQXdt,メĮoKzih󇷟Z +biWKRB{k.eWgE_fb7%߿B dIA& 1D[h榤IA$#H9yxk/\>7-@wx'a£~\ѯl?sZ()=^ɉ*$O19R@l6W!H`ً`w@MԔ@<=xSNүC' &W %/ ?8ա1WBXdg9vt_ݑ#G2ebԓIim8W1H !FL]$B1´pqabH$B1dggc4IMMuu(Bz0 !B0GEV3aW"Ĩ' B!saQ=,IB!FGKB %O~s<]E|/+7gQU$ߝ%@#Yߴb1$t۾X|/sϻZb0>|aqII~8vo+E:KVfѼHZlG!_~]HݶW屨d]1UWWSPP #LB IF ~Z Ʊge)Ĕ@l6[[6,DVAXai7P^HRW.MnӑwtZ\H%M-Kבe\1CխNӨio:KmPf9>>jbVΜ?! r!O!_ Gߜ@;7^OvN-׳B e.?`ȱ & )џ[DŽϪbSUWX`jf0+_~] e\]1X6*ї76m[瓝sE67SO!Ñ#G'<<աqIIr: }1-[">5/^^nLBzZyNbh( 156woWm 75f^yqFɴ޶QohzO}8ub0ddd0m4W!%C> Fc3&064w 6[_svծ7CVuޮx{lc( P*zIbWO޶<~psHzء:;l}IaY#(BT[_wo.{ZlHZ* Y# e` d%L2&r59k>g|w^nvc *DHH5>,OOt_ør/Jٱ o矐6T4czR[Jk4@0">u, .IUuvhr8ePHQ~=O&eTpODq1f̘avaCH&[/cC_kP>{]p͑+ldʤpfN`x2va'yb]zg%pi+Z یj0Ml6{=}ՕDGuV=iYӣ=~""}-;;̙3͎"2lX2DCL ̎!2Ŭ4;Ȱ?'|=/ADDDdx"L D &A\ݿ$T0 &~IDDDd"%% +*DDDDݿ$bL""""@VV_1V^CX{Ez2Զ]yC͙";vSTlɉA,Op7} KIezΖyTV5+{MD|eeetpxW40.5\77> @eU#;%)'(țeke2KؗS<1?Oe+`⑤b%`96@oN^8JCC3̍c҄pJq!%AxI`dB`1 @k!dhmn۶e5G+p.kw^ƎKHȅDG"%4glܼt))'cǙ[W`&MJ>.嶛S Su;*7={KXrCSDyiJuFKM )b fw2sz4Ml;PRRςy,OOwשjp2s **b{Ҷ{KY&OcܘPaDdXfƌfT0R[_uY$g{1cZ8_UKjJ03G'{=Y,LH'Ccc3+nJaTT57Ʀf:,]Uib ܏{}m[1cZDL"tq/j9tyܼtT][UE.IنvodQ~sQI-=ATgo +jm7o9ᣕ͊anZ,nQoOwУ=KwH/S\\ܹs͎"2,` $Ŀ6rIImG+i`]ArkHM&G3vÞ慄P]dNy5K׸Ϙőclgb vnٖ9]h,=i۔QճcwAAq*"Ν;gԩfGtIH/͊;' `႖̋O`@o|^#Ls#MN}}3۶^Ngnry|f".+~hClYĎEH֛RLoXygl<;ihlzNmgMf0>;Tᣕ$ODܞ={9s&^^"baF:c 8a1+͎ 2M:owfG$"""2@qA̙cvaKҀ"&R$"""2@ܹc>x\dS$"""2@ٳ"Ú &rk.]'b2L""""Б#Gր"&S$"""2ڵp&NhvaM߀&"Fq_9ἕ7&qdEo3cg~:\LJr0,59Zne}(Uzʋ("ґ]v1{l,}k\V*Dd <=-ltdoP`f i_ӓRǻ w1D=L"TV۱xXg#8硵#:UWF} W82qlz .e"n9Zo9ED dd|=mrIJ p?m5 frh%VNi8vFQH/m8M7/EDJJqGzzX:%bˀgzLƎ3Գ`^<ӓ)+oEup8\LAYy%v0.ۢհgo KnHbH6o=MiY=ӧFˎ][BrRdžr)ZePQ~8; (*wfLƸ19Dd۳g̚5("Þ &^jkӖkn~<˖$h`&Ol)\|8>/WYqS ٥465ߓI_՜,%$؇h>yf<_RS9= 0=QAIY=6,55M{QTb~jnjiQ^Dή]:u*麗-饥(8UG5Qˡ#ϫ楣Z N荞/y)$mV sbyvx{bX񱐚B^~  Ƅꛇm6'g `;p-aXp:h6ء`nO>#͛gv AHmx$u& 1Ɩ"ՓZVR].?@t? m].;a\o#ҟnry|f"سʪF1wv,>LN}}3۶^~ښ5=PV<3;vrq5טEDa]_""ت3ij(0;E1{1Aڬ#"COS>gvA=L"CZCc3AbGquיCD5$"Cǃ{Op@Dd!;;4;Id:[NoMN""tIGqWjv9Gm6ݿ$2`jjj8p ,0;IDDDdK"} V!,ʽwo,?_OfMėϙ";vSTlɉA,Op@ڧ;\\Jr0,uֳU^l"2<}GL:U/ 0*Dx{uv}ZDT~W'Wωb!3;wãx_˯ĞH_K" &^2 ]%)˃iqL'٥lUm7P[-H_Ȅqa^Cpޔ7&y's f@>q#*q8 Y|C"VO2Yjd{'Y$ل;YJin6Xx$)X9X$(ź5s4!Ҳz~\HIi=afk#f%>6֒ۃ1C.ڶI㶚][3 9|p+벶 MUUU8pGy("r}*Ky5[’:%[OSZVQDFcW1{_^^QSDjJ0S&sh%YlTaEv>*e_N9S&EpӍɜ*UnnN ,:%FKM )b fw2sz4Ml~g()gx'SV;TU5pHR\ \ڶfLƸ19.OÈȐD.L"y^3sz`{OO 7\7zlv'_Y01-yiq䟬t'[͞MrRwLey=CMkSY.$؇ 7h*؛R665dDwRuM^8~cV8fL_"rm 6;GEzfsR~6rhJeֱX:X򣴬SuJj9q8t={KX`ѢEfGN~+͎ ""Ⱦ}(//W$2IDDD$7o&11u@IDDD$7oVIDDDuuuڵKIDDD۶mrpBHT0g̙EDQD.AW߫t߹4iv^^J G_[M -Fh-Gj"C͛;̎!"P$"R@7 M0 8Uǧ⯌4;ZZk<=o/uH{?~\/ *D.WS^rڜ \Y|v,6/L`h g8r )17$kl}/aDǏcF0zT!!!̙3(" })r¤ a:Z'٥g/cWf1q%ve}OJٗSΔItc2 mdf[*2vɑæXBFrbpԨ.۳ymհgo KnHbH6o=MiY=;PRRςy,OOw׳;=#&$oy9UXGQ}7Oe0ƍ qZWD̶yf.\g ٗeŌiQ46,'kzv{j.H׋p?j0Z=#O\ˎlvn.`lj)LW]ĺGonώͼ*ڽ󪈌%5%@vRLjVܔǫ(.sJ0i|Y%TV6v>hyKǃeKmJ`N'[n3; &Kd9WK˹2۰_ELy5TU7bZ aPQ_<ƻwԞ=`9)?n-8|Y1M:g\Tw;tώ{A~RoNuu5K,1; &Kd9_NuMKCjJXƤP\jmzE>= 1!46(-ggf1#I]〖[oNa? 78_.h= NCuӓq8]0.5 :ʟӅ>}=QճcwAA]yLBbbQDT0\@o|^IEe#S&3szt3Elϫ&ߋg2}j N@N9)=#~^̞;Ġ|ylx$Gywڞ@ZOc_DE1wN,n;;`0<;ihl>?Elm͚MuM 4/M"CƍYb1D,atpqVISCA 3a1+͎ 2h=z &gfϞmv'"""Oy9sQDT0 6p7S0B?"""";w|rEP$"""{=Wbv*DDDD[o_O``` ȀIDDD2s8l޼e˖ED. &,##nFE׊g?JeU#kr r/y;dy_;؜&Y[VO-ɘ!ϗ-FXtEDڸq#ӦM#11("rT0lyz2^}ٛP/z" &N3kUؗBDwy;" *D.59Y z/ //1yb8.>ZIDns &9)x0HJ d TV5ὓ,_џ|=RfV ^VHJ dWfmVwUDp">JDUUM8.]{$NdӔճ7Oe0ƍ :='Y)gʤn1S62JBCXp **()dLjEp+vZEn^MmVwUDoM||<ӧO7; &KʼnAd/)̜䞨&D51- ;vމfψ&9);W2ޏᠺu/ㅗ?ǃ"/zGQ:l.qFӱX,fG^_xKbfsR~ݠ6ewsFY,=`a2_c =w7NءcEWٳg?͎""IP]dNy5K !ߋzv."('=?_/JٙYL 6wiWD28A1">u, ѣV'UMor8ePHQ~=gmVʨ^W^x ̎""4xϨDiqlkxzY?6H?"#|iCգζGCC+8SNʨ`fψ`pdBnj~ɗ͒E|4{mFam6=鞾HFھYӣ{u\EdxyXlV("K 鐭:c 8a1+͎ 2 ϫʊ+̎#"ADDDD.7|???,Ybv*DDDD.s7׳'Ed`R$"""裏̎""HH۰a>>>OdP$"""֯_ϒ%K0;\"L""""}-[rF T0z OOO͎""}@_\+r V!,ʽw7;J}(UzJ\3E6v.؎X0? o} KIezΖm."}_gED &P691xX,df[sX<<,}o^k<=o/uū>`ڵfG>I;PN~A _~$9ϲ/ //1yb8̣5Àgv(8]Gp,NpT~o {KI ߻osYlv'^LxxXZz:¬rx{{ph%VN;_Yy[2NS\la!>Ο׎ PUU>=x7&:{-):;mNw"2ֲi&}Y"ELy5TU7b9)?nP݉)!a0nL( xzXؗSNlq䞨fsDZ,WƗl .NT_ԐE-Tp<6Q~sQI-=ATgo +z%maS>ZIڬyu9 ٱh /o&.n("҇T0\P+ޜ%cBB|ayz2ƥhLƛohHvw6}axyy`w&[ADzUWm."Gqq1~!/QDY 0_LDlՙ45z}hI͊t" Yiv=S׿___HҠ"? NァEDdp{T, A$OX}<DDcǎ?nv T0˅#Ƞ/2rHZeKDDDDz0 ^|E*H?""""gN87 eIDDDyLW\avLT0`]4%X&P+=Ez2kzٱ GjdՃWkd‘o|)ܲlTuEd۴iqfGH%XWKGm־RB*I@7 MgSOm΋/9rQD2R$r M2*I/InQI`7>464sx&0 ve/ //1yb8e l8Mq8^;;L@*ѫuj=I][3 9|p+^ *555lܸ?fGL\Su۹B,_ܣuNfψgͫaܐ)lzҲzd"}Q"%d8s^֠rTT6PRVOɘЋ^ϫ"2—Ԕ`I1'ݗmR@rb0͏gܘCQuM^8ӧEj]WfdB{^&D5 kvR_ /"˖-#4tx.T0\N,c|_IنvN.NT_ԐE-Tp<ˆin6hߘ;:.|;;Gn`ΎC;Y,=9"2|lݺ7x("T0&G3v/VOjk>ZIuMS Ƈ8.k†wOR[kHJ _iľt2mjԖV'U۸x^{:$ا]QW:;m  V0n:͎""@H{h\.߹{ip`1?٪3ij(0;Ȁ"gɒ%9rqƙGDFg}yXFT0@yy9o&͎""HH r.CNT0ڵk;7;#L""""{fG~IDDDk׮+`̙fG~IDDD uuuAdR$~<[ۙ5ML4NxqcA<d|ـy>Ggg̚yE]wev1Z降Gdvo\ àS|I1o_==qBVLL[P ֮]-BDDQDaO?'_ bF5 ܼ݇3sj3!N__cU#zL{<5]}̜#??oEUu 98~1s;>^Y1<{Asq_c9,_:bu:**c_9/fP;s3{<]ol0 ~$qS zA69<0n.96{C_2!ѣܹS c*DzW=!w}}7-CQQEOȰϒ_ov1 &^zÚ'G3j{uIu ߏG |11<giKy)ideoq]en4~IJJ*RZVEU=rVo_v_?r~d~r+ao?r%eTL|WmߕO%,41 L(x==flWN&I_q7ɓ-V^s˯fzQϞ={gv1B":cH/\56]cyB~fG`VX(ro'g' maa辰7] "Cٳgy饗x̎""VDАz Xݯ`Sd([nV("2`aӣݗT%Prxg7I``qDd%y""""("2@`9駟fѢE;("2@< 77>7x("2IDDDxg1b˖ Ie,דYӣ͎2^@Jr0,ߞ;BuMaV{|째΋pʛG8+ku߫tΖyTV5+4`byg~>O/`GYJ `;a袦 <=-ltdV@7 MpO_NDz_ofGFH/5 >q#*q8 Y|C"55Mlz 464sx&cyTT4vwaƌqg讽.c9yﱈp+Ϗ`?+Wiv`qH/}UʾrL9Uh#3g()gx'SV;B,_Ln^ {$NdӔՓOepFR]g2ve[R `Wf1TU5p=*:K4463a\XSoyzZNqXڸ149\| ƅ]t{;ePQ~8; (*wjkov{l0ƍ t;v<fGHH/ȯ`hse*S&-AEU{[z/X*"#|IM f( D5'Z;wN,sfF/8[  d-:<'K'( OO '9YPKH1~-.J씔e!]0i|AA78;:ۣ'9kX1.Kg﫶Z1cZ@?̙33gED %a`X[Nqh%ib+ulCmv'.Wv[/rật3' )!a0nL pq|v,U5M̞}_=mv誽;gw;th r9[oK/ED(0Ҩ`vfsZ^x8WP~(N.Xь$$ć |Tf\͔$ `Ǟ"+dwLj˽+n;3ul4c\xO \UZVOqcxb[znDF~9Cwmܙm9}VʨEdon|IRRR̎""zDz)mV N@N9)=#>vw6}axyy`w >Vɶ\7?M^~-O/ džGxFr>=KL?Z11-fOx^5^\=;S:̘o"#<}"4Ċa!V<|gN۸+=mos5?-U[GS]g* KE K<] _a`xW`X0/̟z?_ K{ "]}}=k֬%zDzzeP]Dp`ٖzd0_<ɓ'3; `a<<,.%`rx'{T,HT0ȰqF=kfv4$ +O>$Lxq&\fKG}Ķm̎""}ak_Ǐg߾}fGAB=L"""2,o/ED$^uu5555fjjj6;H<dzb I5βep8fG˖-nETUUn:z!t & eDDDG.Ύ;Ls}QooNhh(MMMf'x///> If, RׯKwqfGVYYO<A``qDdQ$Îaw}[;D2G?0 ֬YcvS̼[w}hU}QhvT0ɰxLLɉd '477~D"+,,3/0; B*dXo~SO=XS`Ct:ӟdv~brSOP"oKLL QDdR$O<#0ɥ>﩯7;eό7 i&&MԏDZzxGLN""C &, _Wٶm999}X,<==/XNI_XjQ.FÃロmWU]%O=f!B ySL0 BCChyѓO~|2yyy/u 44[obʔ)fƓa0 ~2sLoGDL2,<̙3kRTT`r2J~PUU_W\v?;?CQQ=k&'ge׮]VϦ)L2qFz!|}}wbΝ&$668Ν;q\|;___-[͎&PAAV?1ӦM3; 1*dȈ#XbE-FP>J֮]kv+S+WsNLJ%Ña|;aĈAD. L2UUUn:~aݫ$">>G}Fŋ|8AAAoI8ׯ_gΝ5JP?lٲt-O׳b ֮]! ID3g*WPj022bxzzr5ɷ( nݺ=z4[V;!D!" (P޽˺u?~<Mo~-9ɓjeݻwVZL0!Gϭ̯rnss׺-~{8991c B2z(j!Dv'Oѣ߿ glmmӍZX1={qqqh4FsbbbҽN ɓ'ҥ |*U2vcI|||ԩϞ=90YrL]g``@llnr=K!z؄xSʤIŋ+Ϟ=SEQǏ+(qqqnoMMMT홚*qqq(Jttn\ֵiaad|uVwQ"##PſxiIQ>+!!A177O5Maaa2lذVZ)'NTOeĈʑ#Gz)UQRk||RhQd/$?_YxRre),dܹs===?Eו(VUl#^"T\\\ggg;*P<$={ ƪU jժ\===Z-ׯ϶mۀjM@ժU9u@\\\8|0޸π4i)R$gZHq!?N5}sSbŘ9s&K.WlٲsI\]]iԨs 8pGf͚Y%$44)SPbE B޽u(ev~%ƍKq-ZgϞu%^"8p weǎwT>36!ѣ}}}Uiժe…YFiѢ*(m۶Uj׮x{{+ 4P6lԩSG9v옢((UTQ7o̟?_111QEQ\4h@iܸҨQTurʺoEgggvÇEI} S*͛7WW8qB~zynKLLT\]]z)CU7n(*F9t萢(J)RD:uҬY3I9q™(0w9RqvvV4ihB P%k}&_LRJ)y)J-3g*JÇazQ/~<&S&EGGSR%zŷ~-m:t;;;z-\|׳kӧ>>>HMOO_={iܚjuRNϟСC'GٳvqmGa^^^,]O?TpiE>j*/M}}}zꅛ}eҥYwԩ|,[,˯f͚7/{NjѢŊ{"8}4=z`̘1, !aV\\oߞ%KEaʯbccyqpp`jo־ 4e˖xzzʌxB}v89e˖ .DEaРAl߾;wfвBE&hZf̘Anݨ\-[}!11Qp9͛7KPƎ˪Uؾ};!D$a֭[~:&MR;!]ѢEٺu+N*l=<<ؿ?j"T0uT.\Ȇ hٲ!DH$ EQ>}:]tjժj#DY&֭fjڵkGLL T;˾;V^MNG!L&oر___&Nv(B(-[ɓ \dccܖW,_QFtR>CBWbvBdС< , O>cϟQe-ZVE +W2p@fϞg}v8B$a9w?ڡ\{nR-?}4)5k ++ -}'XXXЯ_?n޼ɺu yxx0qDΜ9CzG˗駟2sLƌv8BZEQB4m}}}8v("Ջ~-KFGGcbb3gйsg֮]9GGGt¬YEA1{lƎv8B^>}Ç3~xC*ٳgҧO,ԭ[K.Jfٳ' _Bd ID6gVJEW^ǧȈ^za`Px111aĉܼyN:1vXX`j%kNn+@ { v8Bmdg?8::|r>#*jǏӰa\* b޼y,[ 333  DѢE-C7oG +EQ1bK,aŊZQPK|޼ydQi4>4o˳A*Dw.]o;w0`fΜ=ӦM#""BղeK4 S;ӧ?3[ndIQ I$Tw*W;G $$+V0jԨ ]GSݖ<;JQ-̘1w2d.\='..NRQF޽[PkgǎٳGnBXrKP7͚5C__Djժ='O޽{#sNe/_f͚*E=}s`lmm;w.:uR;.\ F___pD=}:޽{qqqQ;$!12$TcHLLʕ+ܹ-ZDXXBmS IDATo߾)F%YʀӧO븸йsgݹzڡxxxʱcEdQ`` q1I$LBuAAA)>kZEL4 sNpѣ|KK\LEݻwE*GU^ӧOӾ}{ڷoϴiTQFXZZ-\ݻ石aLLLI!r$LBuAAA5Y!9rkkk"y -Z`̘1j)RUVdLB~RMݞ iٲ$LyLbb"#F`,X !D#ӊ 9::KKK/ULUW^zjQݻ]ʶm۰׭[ǀx1ڷHٳgooo<==ܹ! !j$a+^8)R'N)E/^M6-[?%KZߡ*U 6Z"w'Oؾ};uU;$!P U)ӧOuk48p$KB䲷~cǎFƍyAmeeEezq:u WWW9}$KB0 =~X|۷MYpCx[v"TyFx?hт(>} 0{l.\ȣGY$٦M<==133S;$! @n?_ѰeI^{\}Z0D.Οl=Jqww?̕;m۶e„ $ZYf1n8f̘! BD" Uf<<=u6?'r9TRx{{N˖-9|ndllLٽ{7y_=~c-ZI!aB`~¶#)W:7[Ry~_""&I= _eʔDEEѦM"""rmMttt_]r=ʡC$YBW #L_8v V,&9nslJTFp'"!аV7,J̉R4 ]}Cjpb)aYA*j-b(_b4n?| y~ u1|] F_`iVJ3.k)cU?5BOO>X:ڟЧ?\)'_CF#iG%ul>'}7y+ؿ?nnnxxxw^)}iӆO?oooڶmmFWfԩSbkkvHBH”^8\nչ)-YIB-.nσkXYԉs7vs'no]'ĠШVw<ѡT0UؕNBb<;n# +ZΠ]ÑxGSy]”3v罹ؕ EL,S†#r?oܜ{4Wy`~3lҫL]Al<#Yftܙ;v`ddmݻ%azQQQ <5k0vXOBW% !^Ii+g$$Ƴ{n9 ==}66*'< MGr;Vs8J[U' }7$g` 3xb؜x]zd}u|+\= ڎ_5kd׮]:u^zo͛sNvٳ%YB$ %//S?Ej?Aj9gJhҬ6%t?F^COOyL*֭Ξ={0`@}Vuؑ{qҥ7)l޼8 !$!1P xkԤ{艛soC"%+UF, fϴVdQhW7g~?&XgiVf.Hn+ [JXP˱oMnaӤIlBǎ111aҥqEڵuˣ{.ժU{> XF?!C7oj%$LBlw/ӰfWz:C>+024-ͽ _ŸRh4ub!ݛOEh,̬)bb1^{ȘpE=aB\v9黙g~f֌걑X|y0Qnݚ 6еkWLMMY h`dd$7nwRzzziӆݻwӿvޭҥ 6 72rv͛7ٴi]tQ;$!(0$alxw 6h \{-4hf}y]z}J[9f'ˬdϣBZ{%uZ%'gSmܛ,ЍgQ|wσl_o}v/== Ɔf'r 1|)=zmS%t OOO>C̘6mn]pp0*Udɒܹs'ӶE… ͋;ȋ-#ǐ_{{{Ξ=KʕI! I(o1GV3L_:>^b"s@PmID=8KiX+ZE˃\Ē^()ݻw'::cll̤Iy&͛7<k׮y;(޽m۶k.BBB044$>>@71ŋʫ8q"~-| =&&&j%$LyTsrp.q=}'{#CS&sS+鿷Ĺ7zM}C]GOnalcgnZ;NUM!@ix,aא4kռkhyޠ4khuy\:,09"xٞT!*)߬hFسG'ĦX(\~fȐ!X[[3vX044f…cΝZJ,9Y/334&oߦW^\|իWӧOCBKfˣʕN];[%KRc#36[^+e^<( SΩ+[8s/^+o%bs쬫ߔ0Pm~UZ^3ѫLJUШVwNn%,i2v Gp;ϟ/S !D.):tn ~o|;}d" ф{6Fh2 Eˬfք#isub<~$}_ͧhBS=iERթP&zs!^Znݺdɒ j9s+W͠AhӦ %&&[Eo߾\ L<&MPvm.]D˖-s9J!(d҇<[|x \{MS2M/=H K[Np]F˨_WRzG>pi֪ |r+;IKFsh4L:@N8~?hѢ4fWΓ'ORSHl_-_~%k֬=z4ź'vXx1JQ !Dᦧd^.%އ ~'2߰jVvYW{'*_P؂}p4 gdž|qmN]^j!rI~y۷aÆqt&sss155MÇӼysڔ_;vFekjXl 퍻; 2~WyVI!T$#L"WdWR*ͨZ=QXW;,yz*?3'N$222QQQlܸ}ێ;ǏgΜ9)/ '2dw}}}Α#G4h7ndȑ̜9SUBS6ӧdridq18v"5 pOF!..HN:9s&hР/^%]n1c)O͍{QBbccYz<$y0|%~Hŗ(^̘{WM|uc5v:{,X{CC 8a`9nx>utSOi`oA}A+Ƽy5j^^^|||f͚o``MY& Xٳg\prʥB5,yB0miV0TwV8Z,=̕o/33C<~-yRrttdǎ>|jժ >=gY|.ȯ SHH͛7'222gE!<<OOOBap1ER9Grz%Ox7éTтGAQr%KݺGv^mc{v*pl0?!2*"t*J$}72Ԥh;YP9'OqRޭ_խRlU kդI.]ի߿?6mbʕ&Aݺucƌg"""xH<1c TG9B<a99+OQ8oU.H+hI UQ%w"HBAG3ƣ=g/pt 6ef<ȹ!}ZаOr?ɯS u ]$Zi4>c={ĉ~NN:9]Cf,%bذaB&! (4_## g/rTpTssC.>gTvL=[~IY[LM (aeʍ[坤o#Ʊj >]߯D93d ,`Ω3ݎ kennӳD@ҭxׯᨲWraǏ;:!{yy,LBH$DAϊpV8 ?|E_xʄNKsҳM'1)&o2TH_dY޶r9I>|x$ l899L5t?%JP+d!! S6>W; KphAPH4O=hQB1bMXx,MlP.i|oP>ϞszORHTT- ]/?J嬏TYZ6'h3UX_nٲe,^cccTB:uprrf͚899aggrB!2# T:xߡ+fD"[Acb[Ÿr5;ٔup,~?,o7*:.o1M,i~σބQ]0m,]4}[1|O^fsw[ֶ [O%KAa Lj5xr?3|ڕ?LSہDGѪeP SW9w/K//BI͏>=}} ys`U(U*۱}Iv'MAѢ,q!!IvEhڅfC_Vh4z)M}wοpɟۏBɥKc#tS߻k^۾Qg,B!dI$Q^ԤנѼ[J*(Z%ź XJjrT8~oݲ PPo?-KEņ\ƧaQ'tOնiXɆM%$KGtW1"!0 Q4s˾w&ע' hΕ =n9>n/+1\|7`k[p]{^0kV":&c%?kǖm2b#uBQ/Jũh_&}x@ '{NƱdhB!$a;#,_V-0rX'1&66_eJgn =qqܻσ88Tb퓿ɐॴ񘌱!߈Nd&U_3e|3ub~ަ7alפrwSScC |XrF FSPZmγ{*AOih$MoTKGv;*xQ!ˤS6KuI ͨLvȮ8/1y'KbԡkD&wȨf@-y4zlm~#Sz7>V>X& IDATOuU6ά<((\wrT}J&!dZl'l^+adQbt'{dI< w\$p>>l57wmavl΃I=fa(wB!2& S*;ےؔtςp/ K^4zz-kf2])["FDG2>tҘN]GfW;2tkkwrFtgT Lh egܗ+1#үԩ4n RZӽ3}#=Zm"F *57F w\:\=LbSJ!,K)ɒB,Yriڅ=qQ|=^3yd '`毀^'YdTUNQdNxwϦw?Ső% yǟ.ζ$ǿ,7]:K'^˹su~%,hpIӱ찌p6{'}YTؒ{> `κN,Ѓgsw!")*U!}?}Mdww;KhZ]C;{趫P=ak[аgsr}sT˧k5J&n3%KX#㋈=ǝ/׾ym\166dg9t"Yc0!DYwZ|eٓeОS?Yǟp <dT|g Ly3 !(|䖼\*z=\Ʀ-7 'tmS\:.^V-Ѥ iIոZA{FgkSݿOaĨ6s=u\wѢn?q};67Bq4xo uKOsy-6!$L ((%vqAZэdVM<&88Rg†jN܍߭̜뒖<1f_g8rpn}w.> >{ B! %/ٖk_z3|h&튭M  tL'ժdzCttlքǏ25nUCy0ntNS9{SW翲QѮ+V)[233q/fݬ/fNeG[B!D!uQZuPA߹w?{S Ҍ/#i O#i>>1ntT:7iaz\ٛL77дImfN닝mI jWr6u?@7[UdTI RI!D2%O^ݪxuyӣf͊LwcUnMkpwWã̊nk4,\fp|3s!ze]kL!ꑄ)-HRϵ*gZ,9=v%166̵~skp=}njY*Xl',qm[PBiVO`P6I~FGRݑ>c׽Ս˾wTM_tU ^soR+sGV-;"br !U3LPɡlM u jOU#,:Vcuw[ֶ [O%K4s<{̈́Vd>ZuZcY9~σބQh!B.I(2=VX^c_4cgW'[j}vmi+{YV? NЃ<|B!DvIS]LU{*a/ױ*[ZV^VfE[læ#ԩ7ۏtҪ5l2>E8{%sdCh&ON)ʹLcB!y<$Dr jOU#׮ [gȈe EQp+K} ~y}J=͝x63foֶ+#+(>VB4kpeT{*a[+V9~CY7P^566!Z-r.G H.Yy d[i!BdÔҪÔ<@FEi Eŋq絛ŗR-sSYj?mVz^',hE1m)gk})emJdT.ɹ!h/KV<K!hx~YjTʖxϞ&1Q{ptҘKW@Q)ZwD7,S5Lxqclʘ72PR.\zLѢT(oq&j<0JX͖PPdT{Jj !BdNQ 4qZ<EӤ m y/fv͒DFst n m*pD~O'-*v͒~@pHtzݿIE3+3-st.YJN)b9c #>^CŤDLU $GQY:Ns\ՊVb<-硠( B!r$L)ZԐ@fvs.ظD>FԨ^bO{8Pk~clb,K!B$aR'I.ƒ6eOhcO|;Oy1@\ƾq_i:!,3M)-Y9NDs#5$*G!B!@nSMze(jn}bb3e)aĕ-~X4͖JXеS%s#z9;8dzޛSZru딢zarKB!VR):LY~sYʨ&WS<)Y‚C:q?M\ٛhhڤ63ζ$=5tMewذn|Oo G,)Bu"CKıMI^G)1hXh;gzř3RX ͲgW!ꒄI0fr.\G@.U3cTM1(FdT ZMڍtУh|>֭|/EU\?ݻ1sZ_tζ$66%t6=`'1sF~^##G.Yx8]P4'0( [DGRݑ>c׽Ս˾w?i/&111kz?֯UTT:U$%]EMhW/U ؇]IܛJ6m9Fշ숌ɱs#BW'0 Qgn뚱.\벴GtGMX-훿 kKw x+w9wtPƃiڅ=qQ|]0m,]4}[1|O^vw[ֶ [O%KAa Lj5xr?3|ڕ?LSہDGѪeP SW9w/KǾo8}Qj9ڶva}>ozhR ;DD$i,snxϞ(\t1V{2$/aVEZ{R{[նhmoi]kVQ/"j/.h Ԁ/HH2f'd~DFbrY|<̙{>ǃ=|6l[2Sc&ܫCmk7ow=@=dal7}@ Wz^/ު7nv5U 8zuL]zf[2{'c6} X^gVvC11aSIÏ#/Xt1/cvCkКOh5m,whV{A:n-; WԸfϗ$ _;ZRScO{3i+)q&< լkKҸ_O~_Ϣ-{[QQ!mGh{Yz?ӭнwϕ$ݼ`JJ*JfLXaAÒ[~XE7M}^YC칏[ze`~ZJO/~>B' 72%9_ O\~;pBӂ/׫jLĄվ@`Nt&0-{vt!]\ՙoRf :RF Y:el--5D]߫ڼ<[z=tEg][_[禳aVs[ΊjMn]4C?yFg:~>8pIZY|eph%1JLZU- u~l6fd$z(Ν*ުQþ.}48@oA`B%uZ|LJԌ$Ejwɩ$Mn}y\{˕(AΊsA9Җ5`FPxQPE mڞKVh4;KlQJr6l,Ҷ2}4ebF hDFضFtP6oxZIQ5) ՚7f_"{2Nx6v.aJJ m1|Hc_y=[W'kd%Ey=7u77UV^y5~vWF9rsfL#Oe/m5-14\uGoh@Z|pb uǔiS4x7t$M'G;vKj=i%1?TyǫTT\cyڽLs4ngE^y}_b\"Ԕ }P98e?NIZ}e{jjB`BsNѱ*>ZG+g_:5oNyxWx ˳l:.Q>&55+{_~tvnTIi]DT; }֭@W!0uQeK/OTJr{gJf|-_ŵ@@K̞=eviP9 kT|[ Ya٩r56+p)kEB`B/Ӻ|^ܒm15MuB_l*T@[~8.FNgv*UlllUUw}:Ӧ$룃ڷ\VB5={21^WӊU954dPZtL jO¬ 4ЩX:Lش.gEFP@Y_~~:m]-unNSgM o\__]:&y^7:g7~A~֑jZG|p>`KAv-IRttNNN֍Y[ VXN$YW&kϾ65K#?瑎֦2ZhM[ڶD#G*x6n)p4jVZY'UT6(=-D#Ehov6ny-l/ֆJҔoӆں3fMmƏUYNet\Hem(ԗ!ڭYb1.$&f്2Ə/ܽEoaBa =L3<#Iz7z{pI 0.0=]y&0@`&0@`&0@`&0@`&0@`&0@`&0@`&0@`&0@`&0@`&0@`&0@`&0@`&0@`&0@`Bot mnY};;&1ͦ>}?\6lЄ 4m43F˗/=ܣ'VFF.rM0A6m򴋍կk͜9SC SO=%IZtrrr4uTver˟qAYh$iʕZjZeeeԒ%KK.D2d%I~~~ZvL =S=zvܩnM;v$YV\RuEDd63  ^;z-UVV* @MMM g}\UUUr$ZY,h1򔓓#$٬zJUWWf7LV/\P&M$%֪b]fdXza3LZn$)++K#FPyy "Ize2ܦĉJ<'x«}0nG`YٶmfΜK˖-c= M>]m֦?^'OM7ݤiӦy+RcǎՉ':PCz=0L8c.BA`&0@`&0@`&0@`&0@`&0@`&駟djz7oOt@0nwOާP^-((P\\\Wt?fЮ8M6Mm|}}5m4[&Zx:tZxq7Vt/.Ƀ!á\v?X,  i֬Y2m>3͚5ka 5:xb555x8qI:TWWHԴRPe[/IDAT@c _/fXtp#0-Z.K-%y8FĨ\. 3L8-٬EjjjѢE%\Lʂ Р-X+n[>>-Y&+U+&I?O=v7t @<0Cuܼy@"CS4=]z)St_=g}OND`!.&0@`&0Cz|v7+8[o|DNJvw~DU5e$%)jnn#YZ*8qF}3Lڮ?Gf>~1tOv>{&A5XCQ~4NW|aꫴIѷ*!r +9v||*(=?}B6se/?'vnPS/M uZ2mj+Gekwkm\}NY~z31Mf(( ZfO8Y}@Z̾͟ ]:Z:MaɊ ~?@>&_]=.M@9Z-}jv7:ïPdL>ݛq{z'z_ #fzxآT8>QZR衺>cmtx,f?uG__ע~av!h}i?$=r$n߬!3ӪU^Y`X˷ǹxU2c읚']jطqLwbרڶWbֺ/iйZ|o$4k=q촵Ǩ*% 5PeMBlў;~Zxůc2F\Gּyj躖'ɒvgKQ5(WBl mn׉z覷׽(9@Njv/w(0Q+^~XeTs<;wnS1 h59`:p38F/xCi Ժ/[#s,WNj$brwtmɟv?} U$}mU֔H5z/;qL&}҇>ZFOvpa${.1L`L`xJ^<* IH2Ё!=]z1SJIW\@=]z1S7[Nŭg6^^rGzZ,up]љ@tLi.{vgmi!@C`fsgemtH\f>ZBe]a~^&I,ycڗ&S7۾DO3kov"#jlN+CFrө3;!IR^ќRq]_)UuMlfccc$Y->톱.%9H6ib>21^#EjsfyǫTPX[ڵT#k0UUzթN4p@r;Ea^$)}3jF52l{ R<    [hT|"%-֭w^KVRRMucmp5'>אIs {CwcJrmߞMv9~j1ۮTQQ~ؤ~\|I˞zW?stCvzw}=ִQmߙ5NQOX¢r{\UVjq:T]uIRCKs{^_m9^3?|-kN;z@JrnHWJV#zWΡzՒʪZO~xQLJrͿabcT]]~C;{$}N꫑ԔX-Z8MIIQr::03RIұbu{N}xsk>"Iْt]szG L-äoȏxAv3 ԣK[ ˵/;OqHnwZF$emٶbeLW 鸂l~> Ae+=ɗ oޛcu7[c8Fޟ/I1ojk5bxj6dp?~qz]K\jKN{޹W{97^WՊ ;}&"W΍j;eVdBt襘a&0@`&0@`&0@`.-=Sa~c.&efС#mY)ʘ Sj_6RCt5ߨ˳UC^tֵE`ig84%&TPX[*w:m?6E32=Z~P32תݩ%ΐ45iq.WB|9+|N4rx4,jhhn\jZ|4p@h1,QpE)AڰHvlє1,US'Wcogn.3_OTkޜ}e:u-ڵT#E()!HΊ.ÇK^y=[W'kd%Ey=777UV^y5~vWF9rsfL#Oe/m5-14\uG 3L%f;MüsB4iBVU.Tc/р;^Z˫}eڟwy57;+=_?@<:M9TLt)qJ&O-ۋUS'S7suU >Z=t0שysmlj-I1 [w&iIMMn^W,k?݃URZC7uyԍVtDU.M SetHandler python-program PythonHandler django.core.handlers.modpython SetEnv DJANGO_SETTINGS_MODULE autoradio.settings PythonDebug On SetHandler None SetHandler None SetHandler None autoradio-upstream-2.8.2+dfsg.orig/doc/monit_autoradio_example.conf0000644000175000017500000000404412332264613026012 0ustar capriottcapriott## activate this where you run autoradiodbusd ## ###################################################### check process autoradiodbusd with pidfile /var/run/autoradio/autoradiodbus.lock start program = "/usr/bin/autoradiodbusd restart" stop program = "/usr/bin/autoradiodbusd stop" if failed host localhost port 1234 then restart ## activate this where you run jackdaemon ## ###################################################### check process jackdaemon with pidfile /var/run/autoradio/jackdaemon.lock start program = "/usr/bin/jackdaemon restart" stop program = "/usr/bin/jackdaemon stop" depends on autoradiodbusd ## activate this where you run autoplayerd ## ################################################### check file autoplayertimestamp with path /usr/share/autoradio/autoplayer.xspf if timestamp > 3 minutes then restart check process autoplayerd with pidfile /var/run/autoradio/autoplayer.lock start program = "/usr/bin/autoplayerd restart" stop program = "/usr/bin/autoplayerd stop" depends on autoplayertimestamp, jackdaemon, autoradiodbusd ## activate this where you run autoradiod ## ################################################## ## Check a file's timestamp. In this example, we test if a file is older ## than 3 minutes and assume something is wrong if its not updated. check file autoradiotimestamp with path /var/run/autoradio/autoradiod.timestamp if timestamp > 3 minutes then restart check process autoradiod with pidfile /var/run/autoradio/autoradiod.lock start program = "/usr/bin/autoradiod restart" stop program = "/usr/bin/autoradiod stop" depends on autoradiotimestamp, autoplayerd ## activate this where you run autoradioweb ## #################################################### ## if you use apache you do not need this # check process autoradioweb with pidfile /var/run/autoradio/autoradioweb.lock # start program = "/usr/bin/autoradioweb restart" # stop program = "/usr/bin/autoradioweb stop" # depends on autoplayerd autoradio-upstream-2.8.2+dfsg.orig/doc/spots.png0000644000175000017500000006772412332264612022126 0ustar capriottcapriottPNG  IHDRxbKGD IDATxw|SW:ޓR^eYuݡ (*"ū\q" (" (K"{[6tIwڜ5!I6my>}H3$sr>REA!r#B!)B!IqB!LBafR\B3*BW!̤ !f&U!03K68=XzB*#W!̤ !f&U!03)B!IqB!L95pnOs38{u7?|;^ȨdfEfBr* f NŮѩ9|a@Ito=<BZH\A>=IiW9; 4sbIɸEZf<8za# 4=ڎɗD\1ey>XjHqQ3y[tgyGt dopI͸En~CzL <c\>\=^%!V!D%UbNn#"+~i҃nᾈddd%bػa"-G( +&+':6LA~KKnB) ɇ_|+YiݿEQ+~!ĽA0(+m qquӸ9ү$<]8B6{Mv_NdQ7_;'\|JٍB Uf\@--4)1 =\FS==ڌCg&vh ҥF_YjڨdBRq*H{aҜ-=zCRq˗`B!iaQiOCBZI\B3*BW!̤ !f&U!03ZXO#p;5 f 7Г~k5h[WKCQ5ȵ?>=OMl^ϑ),42KKc7簵I1[{ph`MN; uᡍ +Zabkb_L@p6q3iGp789/wGὴr;q[پ&gΥi@c顙ݹ i4iJ\B6bҋW#F5?] 5-0d@'rt2Y89Ц];beUEggkUfF=IXѫ{[zPCQwaB9$.>CG9L4kNfC3\n'N&n^N5jD cI=O`#09rnFѮGcb3(AChƛ߶ 1)z!ۤ(R:=Щ܅4U4iJD{ѩ+rbL~Ր@gEp3󺟸b˸W\Pt_BM"kU*eQ E4 >"Z4띾OzפwRVVs]]P!(+ I9-%a I97!!:Ĺ ilm5gΥ_jB hF|b6[wܠ}[/L iGّaǰBQhACQIq5Qgst v8:ԛ?/RAf4Y;gR|5pwzs-޺'gΧcMoDad.Ʀh]k\x;>BOn"Gn"!,Mn"!DpX.hB!LBafR\B3*BW!̤ !f&U!03)59-=Rsh4<ܸy[Vux)3oL^i*M!IT>] M:ys5=2'Xu<2v.{w|i>Ot/Y4 @H_} 4  ''n#%o>qc"9y2/O\fA֓O14i@X[hg Vj _.LP7}b&4㵙}M.`+KYf͛[{H!*FN 8x^O&sx,7o3xPg&˪㓅0tkgӥs3潷ʺ^ I`)'':Y ʔgq'3} 99v,s盥/}*Xҥxrr`G\`Ͼ39ï2|dN_ĩƧ'?lV&bnťT^Bʓ#W#|=Bze[O2|ݣ.__w&{(,j ۶E$<=\hĺ/0lGyTkHha?_a/9v"vp'n;K/׮'ѵs:=ou i<:@wl|6CulrWg>~+II^’ՈşOe1;g?eҿ>=-9#m{O[z'M!R1v>BG82kRvvr(/›Z_XS9{:ch4F!>!4}Yr !Dm!GF<܊KݹOٖqGZZT>]+o$0Gl8v"f,fSrf{ěCbb5A77T&<ڇSccmM^+y0<굻2\8RVLf_:sZo~5}"2o*Ҙ\ }qp'1),2LNƾ-FubL2kדX|+aaKB*窧"yco= yT^"yB *BՖBZJ\B3*BW!̤ !f&U!03ZS-d?8O/Zz]C}XfSelc1JZdɯX[f7W'4@k9VA`]tg;jάUmBˤѻߋ܊K|̺^N7(feVd%Uq/jğӝfw&Ŧf.ENh6XLZ]Z j荃}eϴ@P7ogEnB\dS GYzXZ9cp򶹼es[<= 73nBT9r5 Rc%2H낊\U6,S !*)f`Ji}SmVQv`X FS+Y?dܪr'|.-Gp2f+_^%!s7Eujd7UfmEw3YA:_va@3y׼ 4Jaݽ(ijj|~2yJ5K\T$UP\ZdOD OY3iz/Y9?C ݉' G{76ά%ȧynwܹŊ-3 Υ[Gqo[0OOgXS/sqECZflv*o/@B%>\=] oGNv{O@̍CxWh>cemnϰl y[& xGTW!EoI"rlbnbն׋=p;:OH8]g\?6 9#3%<ʊ[/p"w~;vThYlR\ % "fΤzޝp*?w iX}[w/g>7? 7'_#X>z,*n~&A'4 !Dl;-1ql9P1d2IJ oødDv(:{Jܵdh縓OsOזmqR5܂r34mx?Tq !Dݾȇ'0K<2^sP k{zCfIL=ВJsޏrmHp;_\|;ٷ >N>Lt<]ͤK#W K)tA;)F[zhFU#W!cbnftAh3Fi񎥇%HqL)}mr\:yǠB++!~LY1;K3|n׈l#+73oZ|›VKqY|<진ӵKJ)4ď?Y_eFy3^%GVQHq-4 sT*o Y &45. *I̷YIBBjWXg6IHL㟓  yu<#+.œ ~䳏&'0u"NE_|s}isJ'bnm=g9ǎ2w|O_euyMYx.X̥]=wǴgIggkS?HBB*wy1~syjrrÔ1{}R\M/h L6~}ۆh(XN_&->t=w2sJ-&3dkzL>LP2fT/ʕY!D MJ2AZ)(VXYb,z.f0Y31xe.3dO @j|L*ԾQ2i#+ V RON;ﭦO<Y\soкU({n!11Z2ddde?cyկ,6$GvpuH[KCpR\+PiVg;Xd3s>lp9rJg4f,f?0qB_ILJ#-=cXv^_o>a لT?T*!|3tܢc*Irdk6.Cu[*/wh#whVД{ tm\\l vfΛ9=ꂲNeFƮ%ds!&Xq?b?J\%RX Cpp"N'] mZyѵ/VVEZYF=IXѫ{[zPԹR 73БDNF'Ӻ͚_SrKD;M{9 ֨ax3lP(%@<N~8'$<BJjG$&6?mm W!D@t(-]HZEƮDr˹SjHP3|yO\Bve\M۫at(V]|/ !ꦿ.>EQ_LP/uIi l^cd˻h)+ɹ.~.(syu$UQ-}qؒ]Rِ`gM܅4j V[s玚3RI/|u!4 w#>1;nоO&ّaǰBQhAW!Dї|NNGG:G;1 Z4󠁽5݉>D3o`<de}ZTvɠ(,T8tm?<^؜ukbQ< Vu$}zZ̔^ڠd?C\7 V]_猩}BZ˔K\~4EQz=Y3ٓ{ !j?)F$&ϛ$$( ݐ'}SQB93#~t6lPdsxs4x)ڵ"g`=^5EIc7NF'Fes(NC83?>-vV3=?';w"!! {(۹;9z6mZy1|p(ofqpB0լ;wIMf\ M)w8J>F{Ls n}S@N~5떁[sBO\9< ӈO܅XB.]k'_?: uƊW2-ɖ'6vU̾\JMQ3an,/O_L+e|ۣo}jG8@jjلSYqXRPBQIq5m9s.]ݟUkc(%>￲-@ 5DRUC(BJj_8-w/2f90q)e\vw !j?)FNǑ7t7q%1)= va+thM=`%"<[QzxՔ|EGr)EۮeSLs3(ʯ-͡ !j?)F7ذ*[z`ccEvNzW3)?qƮt< RhoM``ZWMR~T4 r_d.F{Ls3qn^aUX}B~G\IkO17 f_C/🷿 XX7o zcJ=ۗMZiFLjO>W!Dqvk {dN xX[YGxd\HQm}xe)y sy !ř9p<+LX^[~p ~䳏&'0u"NE_a3ض>x=ph ,yﭦs~^;.1|W:x~H2r5{ 8~VAY,]aOHe3ø׾`;9>}o,sR<99 x#j.i__S^>e˷2a\Çv#..IqBo9r,:Ly6m9 3eL7EEIqBϧYڝ2_sC@ h4 |@Zzs IDAT˿z'e.dQJ= 5>m4`ӺVInStheg[?WGfWjN^&įUMy ԍtUa6=>RxwSDEe摖EX?W%ltrrٴ0#vc;Fr'3c'bЮnyCt؉X^IO %>xBC\g55?f<(AP;_ԗ޺U({XLy?Ё'/1wJ6"11 R\f1k)FQ)o7&'~CfcQ#{ҺU(/5?k0,_σ/ 5:ٯ/s}7 fי x[llٖys,T̗}&N苃=IiW0NӦ$-JX?uJm5Ue35cc*ӵRh*lVXsG4Q2_Cƫ Q;Hmjb|ԼBIdpwaSfϰBض&YYj^E߯3U2^W#nm QY p_dwvg4Þ@'NM)66$cOJ,C*fʵ1N֪J.s8T654Ew@Vv$4 =ж풌W!Dm ض7oe1bHcz E%s[$MKG^b,cкJ9~EwOl8s\jY[hƛ,5{#;*w* =y5b/gЧW Cn0DJZ.g::Z3d`2ʢ1کOt֥eڹ[Q5NWW;Z,Ŏ=prA֔mƫ'.!زM/F µ\w8}6ҸteZW7HƨU͉MH,:ru3[:$UQHq5b+TӷwCBMj(CyZ2F[vٕɉ5<@^lfxB9-lD.X[a],?oB`Dm“BvIn8۲aUF3I3KG_Z6ΜK-VKٽi51ersby[^GQ@0 uUu2/>W=*,^s-+mgaXɞp+пo *a^;KKkuV+Ɣz@JJKOr+뙜NΚ[zhek=R\-aSGd;0?wTjF ߈_7]~k{ON&!1I7G]/zV[ޤ'ILFQ8p8S.ц6کÏo(JNY[Hd@Z!׾r/ D<iʋC~3JJݞfMɸOjZ>7(,Th^nQnOx'QTއǒ{ 'F?F`{sxzʻ'@vN];Ǯ[[7zUa6#qbٜP_ȥt䋝5GS^SX+66V\ANnN;񷫺^g'R:>xk5 r&<̍?]_TCi\I׵\U\52rsUsN 55xnjlBpCrשaj*0C=d&g}ĶE\A~p7T*S˴(BJj_8-w/4k^s`52gkHma*0C}؍C\ILaρxB]X zpww6nFQTY$n M݈OfoD4uCQ0>C;`M'@EԶv)B͟;naUZDwhEn^ϤpmҵP}|*;vdmJ5EL9+`myھv(w gEbKad.Ʀh]sZ^#C PQ/t%=#SSpwѦ ꩎>dD~kI*y!y>{a%U!Cz=Y飲T~kIZ!F7UVOVejC~kI*yBʓjDyٟڞ,[rrLhmo-I\b䯆&)][K*K巖^乂 !N駫Lyհʽھ7Jm<ךCyXzV~I04MӈEJ*m$Vx!0 G;Ӳ1vto}jRbboL:_'.QXXH yIzto4 sVYlb/[<]-}+w=yI  YIBBw-s١'mcW0㕥^͂5Z&Ҋ#0_7dLSWt;]^ uA!\}g8r4g.X2ccyo >?' a{)ΦKf{o5-U7Ĥ4:̬\f^ft s$e3{ref D7sR<99|b\eu竹sSWlO~fL݈K$)BYa?-3xPgNJ7= EG&}3~~de?-sB6m9̼d >^l w'ѽk BH6"%m2eKɼ{??w~1!u1oʺ]Okt?Gj̔orWg>ʔS7䴰lS9{zYRuVTzċ?ʟu1~zwˆ,[2W,{2J)+seԱ\-d]xa4fc׋=Pic}^UUYwy-2vVr W!لqZ"˖L''7M[3bhSs+.w>ETd[miiY7tHW\*>cc!11s狲J[ eᄈ.W;{\2nS@y+iЋ4soFBxZ4aػѺUh󣰳a#sѹSMyz3^́6r_`Kcy_C+M9~log0e|i $$`ObRio?_YWyeTuݦ?mƎͲ[Yzg?|Xjkel\U:_]{UB\knWپR㻗D ]s{O5<"QIq-zʼn1;`_wʊ+WL+k?+ДbQK9+Wz-ADֵy\{Nۻ6+kUKPڧ aLߗ][pZnuW3C8m?{5SLy+o:hGmæ>Wf˵yJ{ONQwiagewX)=oU'S E 2EݬʝG~uW$$_~# ;}v훫)QsvvT*|݊'> !.)&j 9} aWNyzyyj_arQ^K7ŒL&N[o\W!{' }[FIV4$,BM V\2PV׺j5Z乚N\EӭKsoێJE6-=,QԵ<ʪ֒$ϵHq-U\u)B1WϏbƬ dEe|\_U͵MVpjTu,O]\T5՘ݞ&6WOƊ!.C.9^PWؤufs1}J;Tz qr_dCELNF'cggjgvZEoabk# I07) \]pv%!1fM'{nEjǩdehCV^t䋕ލ5˜Nəs8OzF>ӧ3:֊Mk؍$&e]7`ҸEaHƊ^hғĤyEÞ>Й[;+r HeW߱qq%sWQtu,R-*M 2Ӿ 8~*W;zt --/2sl ͚ӾO&@<66*:[cI=_td:kMOU(*\J>F׭(4 J`$7q`[$$+[r?w1alSs x`n+w[+:}}БDNF'Ӿv',RqojGG:u!?_S\+ ؈VojC<81 1ŊŘti揝O&;U\39ڟ._ؗ7 ix{5 <̕g'R:#qbٜPt ~^~n>7W;Z5RSw&7t.w5R~=;;+K";RqjR}UzZ{Դ.+k:_זNqWEQHIٸ^]޸ [B~v3RŏY6]dhe- !)&Rsm2EG4܍l;= s ILa8mAWk[t$RtUA1nEqّaǰBQhAp7~QYȍ:5v%!)pq vĮA!YY;Vi~)&puBlI9nI>¢L/Ʀh] 4qd2~~8;ْ"+KWR(ș~A])=YX&k#vG7Wi+s +,9 3N}]:ϩ)h#llq0uUOY}5ٟzH"i鉃 ˾;5|e[uUQ&sM8qbgRP>>D BT;4 a 9r^8,B!LBafR\B3*BW!̤W-s-b1.Mh\s5=Β'BHq ti|B6!wwgCly,wof$7VQQR\, wsC;4 << wBDM1SZVlMqu#+[͝L5))yّVDS~%%%<EOӲ}z]{Ȃb8%7VQQۈG>۴$(Йb_I\|.4096--ZCXcW(k'?u2-ZWO&Q.wg  %v.}ZhAf۹'dWHf֫ϔ}Yj *J;x$&.mnl4kNffm !,K12n}uK\ t_T@VGGk l;攺ؒ[{nH\B6 9-Z#` Rr9t$(;wU{"cm"R:W?7/ɔ}Yjύ- oPt9Z:('p#4sV4.;3|2߉SIȧka]"<̍XT4knrnjy59NɍBT({8t4`Yӿ2B[l(:2W[aNNw/0\Z1O fGt/mJ|:`83^BrjDξsVdjusw 7{l~ljh qt˾/-=N}+(IRqLőժQ;}wC*SmQ/H1Z5y|e4 lJ&ueBC\Hyi4 am]{/M#W!9rJ{ž2N!D!W !f&U!03)B!IqB!L.h*ǖ9{!Ʈ jTs5>}*(rh Pj乖&yBj7ߟ 3K߮ѻG@Y mض٨T8/277;}SQB93#s*G(55ɓ'[zBzHk-( &M ""#<=Z^>s} FQHqy bjgL]3X~G#o2o~!M,T*U_\Ea̘18p#B'R\k>9sxVk5puu[Q 8p 111ZK,Yӧz\ReP= IDATo...~/,,$++x@z`U&ŵXz5?lh4x{{[`T[ VuX!DHq72aw%[ã*W՜PQbGR<\\\`NʢE駟0`d<ףXj,[8>s,gyBrCKcߵk-[_~;xxxFEGGs`̘1(ߦ4oޜ Z0_`֬Yt֍>}бcG/_{GbȐ!sizEϞ=4h)));wHzMn8x%7MQQ  8]tQl٢(|79kkkEQ%22Rټy(c%22RQESOʺuEQ7n(^^^%geeT*eѢEʑ#GEQ۷o+ފ((lذA9sҧOe׮](ʧ~XBݻrQEQJv,5BrHN װXx1'N,)))ۓ+j(:,((ٙd)((ӓ lllV7}^^4h@S]( nnnL0233bϞ=j}Yѣ5jM4ٙTk@׮]u_~9/DtXְ5k`mm͈#J=(T*߯1tUTͨQXh۷oGj]G}ĥKظq# b…T*4MeT*6mڄ%6AQ;j*K7oξ}ޝUUIe0,sR3RHSMfeLoy7K)I3$+!!M55d2'dP82pyzZv^>k3PkҥK~K.]/X)**>EXv-*,ϟ/?<Ǐ_~{ݻe˖Ɨ_~ @bb" ,0!<6Tڴi͛1bDwfmۖkx{iaffFYY+W}Y 333quu{w'a\]]qvvsssPL2J7n$33ɓ'R(((`ҥؗ'h\ 7o6XZZVEPPNbȑ!Rq tR.]B4m2ǐ6mĈ#jLPidd${fĉ,_L0k׮}vc"h@h׮wfG܆Aaaamیi;WCٸq#ء4qDvAzzCB4q\ dӦM5J4cÆ Vv&!$W8~8gΜ9kkkƌúuj7nחݻ;@'N9rء!0ILQbbb=zн{w:u$=u<dԩBZIrmd7n$$$Pc"$22+++֬YcPM$FT\\͛3fCzdccäIX|y !HrmTwի;ء=:u*.]믿6v(B&HԈ"##9<0v( 4;wVYKa|Ƣhغu+E4iӦ{nHLLd…x{{HbbBLHnJQQF2v(@XXYYYk|i^!Ck#OyiݺCz͛ٸq#OԔRmbBI ##ݻweP%''Te]EbB7F5Æ 3v(BYzN B$672|plmmг~gy3("!DS$URRR8|l2:t耹CB4Q\>ٙ;H,--ٲe 666BBA֯_ODDD ECtt4E~s&U~WΞ=+ b޼y5޽oB$ƍ  ,,ء;w.}_UHrՓR.cjjJtt4[GB-IzgRSSy'0֭[eT*v.MDEEҮ];c"k׮~1B$Ann.[la„ EԩS1vB&@lڴ EQ=zCFviBCCdt=ٳ'DGG;$$$h0+.(99}vc"IBy,@| nnn 8ء!h"εEaDFFЋ|w}S; !IFh~sSO;&鏴L9b0pDEEqbPB4!\oFaL8ء!hb$ ..*+ 3fBTIriӰb̙$&&叄BH&j.]OϞ=ٳ'C 1rdXJruR~`SZFzV ʰ0!M*+..߿˗3eyyaW&~d=OaFBI:h~aҥKybݺu!fq ;krߤk&&f\{_vHYY )Lj6W5-gOv$ >'z*xċ2,JC䪃ZU^t oooC$!3S Zk?g]O>sv{s#Vxٔٙ5-fDY\H?E'#aΥ ] *~{`ciϙs Ŭ[ 9#ɵjjjʳ>kfĝ䛃!~m)*Χ3?ƭ">8ɷ?/cWudN紱7U qq…jyٺuvnՖ %4vBTSӸc!(V\q󝫹9HbBQ#I\hժ~-666FJ!DS&ɵKKK;BLk=rrr(++c۶m9"!M$zdeea*B'\QVVƻ+cYBLT>};Bs$V!J^"!&E8'!n_l w}S+Q~&[BAL ހ-YvGK&~xՅ<^#Eּs YI<&L33~w VtRKϧ%#]o>^^-C0!>/tw6Q @qq_n/=N֜=m˿B]J0Wiœ6mػ2gqjeIqIYZ'mɕ<r$w7oI []ȶ0ofkhJ1#pDI]qH:NdbffB0wBiUe2E^䟚 .^>f[ON(_[wGWd 6k6leelc.W?]<478ɠe6I4U&bU& s ;Q_If+ 5ʾ z<.eoVY{#5_㉻sç$lt>BQYJ2 į;¨~M*_\PAg 7uG!Ye]GVPTDŽ/ߟ̺ھw0fxuaKyB=U+Wy LTFҥ$ef<}Aȹ7GR:BGtMBthyuhB!IB!Ir;@qox&L~KjrC3TF+j˘'| ZE4(6!fաIQՙ/|lmhLML_w8o #7VmB$ j65,xkGox퍏9~"Rxx(MP_3<;e0+Vn7fe,\kv`eecyr\?#_a|k Y<: ޮ|nҳp ?=_$+:o91swtԤY Ng[X1IOmb(.)ef/,Y%?x'> /HWgX'ξ;j=: ?{]Xҳ>-7=wP_#wĤ%??cI:n%j"acݹr%jE* λ c#zDVv1aHJIeG~I۴qf|d]P6.D쉫M۶}p܅Cow5ʟv%rL_ZS>kT}uڕ3:+.fҭNj׸*e;vL3ќHrW1$xq-[15=Ҳ=""poIYBZzvc_F1NkEs$\Ѯ:2jYi1ڥǃK;(eJxdNWqjoNcdcE־Ch4g/C?~KrBQk^&bXD>_+x#P%WWG|}V־Vmu@^(*yhdz>sYc:Xs_2pt)ښӿuy6zBaxzOq'Ң9mرwegʒ2}U5-GBRNZ]-)d ٶ>͑ ~;} M^ 6fvp.Gn&5&znkǡ#; = U}K֕B7_38y! j.Vײ zۓ|.~8:X>؇ظLIݖQ#pБ4~==L!+@TSQIɹ>t m̯R1J!5sˤz}eP jLMU$0oKP@UWB!G%׮]-Y88X`ccfq61 9bei @Pdq|.K֦h4Ҿgf ؖRvss'gN7'aj"8Ug1V] !hz*>ε[rpAiԒeUw"̮qW1s+To4'zk:Q>]ARr*Yi1 V8W?UҐyCsяV|M6j~SoW֭~ YLzz==U[(;nAϰa^VnrYY.BP3OyC?r?Ngp\ڌw<#"x---_K6zsx%ג͔i9<꼦SST)K몫N`GN Vb}R1kҽ[p2t^|u%?h*X\7g]s_:{5G~9˧Q3߇w<7P~kCcLꗫu5\EsBw\7g]sVVcW,9+gԺJ _8eIDATS֞4[\57Mi.چ" ;UL>*Yל ?{Ʊ{1c#DyFͬvW3Z_ٷC׹h+4\ NK\*\k 'JߙL"jM+{lh7N$ʬL`Xx'#C}S5h*_$ 7gg,gH<,xۚw՗F2 {b>eByo׸S),,f{a\=s4m_׸}b?Ū/ұ}hh,?cp.\$jpqq+Hw8ף ,XGc053_{Y`"^C4Ƹ}"\E#ƹOƊ9Ą|O~C`j>\2pH#B4uzO"pwƂoM`^1OPZZJPsݣ Kzg fL07r,;{,OWcbVo+l<=/{ʺogLDzcwK@LMtŸZ/ӱ޸K5g-1%݂ !!D(Cqj'8ozygS|N>ϼkXۻvݣ׵{1N̞NSR/⑇;2O!D%4N09,[I),ho5iYoW"˫599btuu` qB!j(ɵqxq-[15;) CcѹtS4(F;[*=J.=Pe]qBq1Xácoeݴex~ ^Ea_FvqBq1gόÉ).%==kkK22ըs4;o8aH{LzF>ђ؇Wiœϔ)+e:ѧsD&ff& s'VFganB;V5F q[䟈`H%==>=<C~:p_b38v*/k8~8:X>op%-v^/"[]+JKHJtShkvDFf~Q .ĩkT*Ll\&n˨~xrHϬs[eimY!/nRT\ƏSk|)31V.x NFH{G9pFq#ծN\<>̏D5iy'I*,*%\.p”qoDOKL8G.%ٚf˷Ɋx/i$|g2'7~neG=_lIOpjeD5 I9gouE{duuZ[{VoiA`GĦ]XgӮ2+cRJ=ܱ0!6.Vz!U/r&><Ʀ/(Sn[x_sN;)s)**#0 4^+iFs|VtQB}zowՙZ}VX6Nkkjj2Y_%Q%~!%W[nwW,wZ{]mtM-4|ڴ`ێ?ckz>@Qq)yy%T.P(9񡸤so**.#1NZVy=+( '-#=?\SG'@`;{ZUV[~CStiG?ߖgs+ha.wI;Wa0uwcؾh!@hF֭~ ;{RCm\]Y$nh íLGѭkP|k=jM>ޮ[^l{ڷ[PAea(\(śXfVV̝='sKzg fLzj ˫5..:{5qqɘҵK ߙLQQIyj+%KdߓŁyy㘚-w'm,QkXFU50kZb6'8 M^v[iimUW=զ5C𹬍ڍ1OPZZJPs Syj֭z2+wIO~PQ[oN0| 5ѷOz륮 ! a%+-FD~YjR}{d(ban,}YPM}8]Vlm{(ݺ)9JԚ-mGvu}c|#RaTe'*}0MPZ9r 7/sfVeʤG斟Pbnꩦ*>AJ!d3gg)r߽ՎuVv}mX_m9r*ew|GQyM`m?L6<: /LJ<ަ3#Dڶq!bdO\]h}80ˢNbׇxº_> ?xualDo<=ʾ^5 2}j8reWO"9,[I),hoT<^_eՆqb&z]~Kj! I IKU:gk[RU?>WOgo#:f_o?BԚk\ijyPLoVqq7>'N}p[ b6g+x}f4 ua}eiIui! A0W_ jcW,[}&L~+Y,~g2}zwd̓P5챡݈;+V3e#Zaqd ,Ӊ u˺z~#mFWm`҄tϿW;& FuB$Wa0 9#AH[>ϫ/dk|.ޢ),,q}eӧSXXOø:2{hۺӇsb&Q熋 v˺zJOfRva-3#ʓKkkK22ըs4݇/Q,|{^FuB***U4Xs3dؼq 8"!DS'Uzt,oB#B$W!B$ !z&B;W!B$ !z&U!3IB!IrB!LBg\B=*B$W!BpǺi,IENDB`autoradio-upstream-2.8.2+dfsg.orig/doc/dcop_howto.txt0000644000175000017500000000104512332264612023136 0ustar capriottcapriottaggiunge una playlist alla playlistwrobser (laterale): dcop amarok playlistbrowser addPlaylist /tmp/save.m3u dice che deve essere suonata: dcop amarok playlistbrowser loadPlaylist save questo carica una playlist dinamica: dcop amarok playlistbrowser loadPlaylist dimenticate che subit dopo va ripopolata: dcop amarok playlist repopulate questo ripulisce una playlist: dcop amarok playlist adjustDynamicPrevious questo termina bene dcop amarok MainApplication-Interface questo salva lo stato dcop amarok playlist saveCurrentPlaylist autoradio-upstream-2.8.2+dfsg.orig/templates/0000755000175000017500000000000012332265077021467 5ustar capriottcapriottautoradio-upstream-2.8.2+dfsg.orig/templates/doc/0000755000175000017500000000000012332265077022234 5ustar capriottcapriottautoradio-upstream-2.8.2+dfsg.orig/templates/doc/doc.html0000644000175000017500000004050712332264612023667 0ustar capriottcapriott{% extends "admin/base_site.html" %} {% load i18n %} {% block content %}

{% trans 'Documentation Home' %}

{% if params.docitem == "overview" %} {% blocktrans %} Radio automation software. Simple to use, starting from digital audio files, manage on-air broadcasting over a radio-station or web-radio. The main components are:
  • Player (Xmms/Audacious): plays all your media files and send digital sound to an audio device or audio server
  • Scheduler: real time manager for emission of special audio files like jingles, spots, playlist and programs; interact with player like supervisor User
  • inteface: WEB interface to monitor the player and scheduler and admin the schedules for the complete control over your station format. The web interface allows you to easily publish podcasts that conform to the RSS 2.0 and iTunes RSS podcast specifications The web interface provide a "full compatible" ogg player.
Developed with Python, Django, Dbus it works in an production enviroment {% endblocktrans %} {% endif %} {% if params.docitem == "feature" %} {% blocktrans %}
  • manage ogg, mp3, wav and other media file format
  • it's designed as client server
  • manage playlists, inserting on it jingles, spots and programs
  • programmable rules for schedule and period schedule
  • do not overlap schedules: anticipate, postone or delete
  • player is monitored by web interface
  • spots are grouped and ordered by your preference
  • programs are available for podcasting in a very complete rss feed web interface
  • integrated web player for ogg vorbis that is very compatible with most user's systems
  • can produce a palimpsest and a printable version is available following the the italian law standard
  • integrated daemon system with logging
  • provide enhanced version of dir2ogg.py and mkplaylist.py to manage files with music (convert to ogg and make playlist)
  • do not use DataBases to manage music; you can use your preferred application to produce playlists
  • on line web documentation
{% endblocktrans %} {% endif %} {% if params.docitem == "player" %}

{% blocktrans %} Partendo da una playlist è in grado di gestire differenti formati di audio digitali per poi inviare il suono o a una scheda audio o a un server audio.

{% endblocktrans %} {% blocktrans %} Esistono varie possibilità:
  • Audacious2: Questo player è disponibile su tutte le nuove distribuzioni. Permette l'invio diretto a un server per lo streaming per la realizzazione di web radio. Questo è il player preferito pe l'utilizzo con AutoRadio.
  • Xmms: E' un player “antico” ma molto robusto. Consigliato solo su vecchie distribuzioni
{% endblocktrans %} {% blocktrans %} Questi sono i meccanismi di funzionamento principale: {% endblocktrans %}
  • {% blocktrans %}deve essere sempre presente nel player una playlist di brani musicali ciascuno di durata non superiore a 7/8 minuti; brani piu' lunghi potrebbero comportare ritardi e cattiva gestione dell'emissione automatica. Per mantenere sempre piena la playlist si consiglia di prevedere almeno due volte al giorno il caricamento automatico di una playlist voluminosa.{% endblocktrans %}
  • {% blocktrans %}quando una schedula raggiunge il tempo per cui è stata programmata viene inserita nella prima posizione successiva a quella attualmente in play, e successiva anche ad ogni file precedentemente inserito da una precedente schedula.{% endblocktrans %}
  • {% blocktrans %}tutto thread save, ossia le funzioni fatte sul player dalle varie schedule saranno sempre consistenti.{% endblocktrans %}
  • {% blocktrans %}le operazioni di inserimento e cancellazione dalla playlist vengono fatte solo quando mancano piu' di 10 secondi alla fine del brano per non cadere in situazioni critiche e inconsistenti.{% endblocktrans %}
  • {% blocktrans %}la testa della playlist, che se tutto è programmato correttamente tende sempre a crescere, viene tagliata a 10 brani.{% endblocktrans %}
  • {% blocktrans %}la coda della playlist che se tutto è programmato correttamente tende sempre a crescere viene tagliata a 500 brani.{% endblocktrans %}
  • {% blocktrans %}il player se in stato "stop" viene sempre rimesso in stato "play".{% endblocktrans %}
  • {% blocktrans %}il player se in stato "pause" rimarrà sempre in "pause" se non ci sarà un intervento manuale.{% endblocktrans %}
  • {% blocktrans %}è possibile visualizzare lo stato del player con interfaccia web.{% endblocktrans %}
{% endif %} {% if params.docitem == "scheduler" %}

{% blocktrans %} Lo scheduler è un programma che lanciato separatamente comanda all'istante di tempo opportuno il Player per attivare l'emissione delle programmazione preimpostata. Svolge anche altre funzioni logiche e di controllo quali l'esecuzione del player se non dovesse risultare attivo. Ogni volta che una programmazione è stata inserita con successo nella playlist del player nel database di autoradio essa risulta come se fosse stata effettivamente messa in onda.Ovviamente se sul player vengono fatte operazioni manuali lo scheduler non ne puo tenere conto. {% endblocktrans %}

{% blocktrans %} Vengono estratte tutte le schedule in un intervallo di tempo a cavallo tra passato e futuro. Spot e programmi programmati nel passato e non ancora emessi vengono programmati immediatamente se il ritardo non è eccessivo. Le pubblicità che cadono durante l'emissione di un programma vengono anticipare o ritardate a seconda della vicinanza temporale all'inizio o alla fine delle parti del programma. I jingles che cadono durante l'emissione di programmi o publicità vengono eliminati. {% endblocktrans %}

{% blocktrans %} Lo scheduler provvede anche alla generazione dinamica delle playlist delle fasce pubblicitarie per l'eventuale emissione manuale della pubblicità. Queste playlist vengono generate poco prima dell'orario programato per l'emissione e si possono trovare nella cartella specificata nel file di configurazione (playlistdir). {% endblocktrans %}

{% endif %} {% if params.docitem == "playlist" %}

{% blocktrans %} Le playlist sono il "tappeto" musicale dell'emissione radiofonica. Ogni playlist puo' essere programmata per un istante preciso oppure per una emissione periodica ad iniziare da una una data specifica fino a una data finale per alcuni giorni della settimana specificati. Le playlist prima di essere caricate vengono controllate e i brani musicali corrotti o mancanti vengono eliminati prima di essere inseriti. E' possibile specificare la durata della playlist che verrà inserita nel player. Una opzione permette di attivare la funzione di mescolamento dell'ordine della sequenza dei brani. {% endblocktrans %}

{% blocktrans %} Per poter funzionare Autoradio deve sempre avere un discreto numero di brani musicali caricati nella playlist tra i quali inserire le altre programmazioni. Quando una playlist programmata viene mandata in onda essa viene inserita in testa ai brani già presenti nella lista del player. {% endblocktrans %}

{% endif %} {% if params.docitem == "jingle" %}

{% blocktrans %} I jingles vengono emessi ad intervalli di tempo fissi. Per ogni jingle è possibile impostare da quale data a quale data effettuare l'emissione, da che ora a che ora effettuare l'emissione e in quali giorni della settimana. E' cosi' facile attivare promo di programmi o altro ad orari specifici. {% endblocktrans %}

{% blocktrans %} Il jingle programmato sarà quello con ultima data di emissione piu' vecchia; se ci sono piu' jingle con la stessa ultiuma data di emissione, i jingle vengono ordinati per il parametro impostabile della priorità. {% endblocktrans %}

{% endif %} {% if params.docitem == "spot" %}

{% blocktrans %} E' possibile impostare qualsiasi numero di fasce pubblicitarie caratterizzate da un orario di emissione; ogni fascia è attivabile o disattivabile singolarmente. Una fascia pubblicitaria è composta da spot. Ogni fascia pubblicitaria ha uno o piu' spot definiti come prologo che annunciano la pubblicità. Ogni fascia pubblicitaria ha uno o piu' spot definiti come epilogo che annunciano la fine della pubblicità. {% endblocktrans %}

{% blocktrans %} Per ogni spot (o prologo o epilogo) è possibile stabilire da quale data a quale data effettuare l'emissione, in quali giorni della settimana e in quale fascia pubblicitaria. Ogni spot ( o prologo o epilogo) ha una priorità che determina l'oridine di emissione. {% endblocktrans %}

{% endif %} {% if params.docitem == "program" %}

{% blocktrans %} La gestione dei programmi è la sezione più articolata di Autoradio. Uno show è composto da episodi che a loro volta sono composti da enclosure (parti). Uno show ah alcuni parametri che lo definiscono nel palinsesto. Un episodio ha dei parametri che definiscono quando deve essere mandato in onda da autoradio. Le enclosure (parti) permettono di spezzare episodi di lunga durata per facilitarne la messa in onda, il download e gli inserimenti pubblicitari. Quando dal menù principale si seleziona programmi viene presentato il modulo per l'inserimento di un episodio di uno show. Se lo show a cui appartiene un episodio non è stato ancora definito bisogna farlo come prima operazione; selezionando il + a fianco della voce Show è possibile farlo. {% endblocktrans %}

{% trans 'La definizione di uno Show' %}

{% blocktrans %} Nella sezione principale vengono richieste alcune informazioni sullo show e vengono utilizzate alcune categorie definite dalla legislazione italiana. {% endblocktrans %}

{% blocktrans %} Nella sezione "Podcast options" e "iTunes options" vengono richieste informazioni relative al servizio podcast ben descritto alle voci successive di questa documentazione. {% endblocktrans %}

{% blocktrans %} Nella sezione "Periodic Schedules" e "APeriodic Schedules" vengono richieste informazioni necessarie alla compilazione del palinsesto e alla stampa del libro programmi richiesto dalla legislazione italiana, funzione ben descritta alle voce successiva di questa documentazione. {% endblocktrans %}

{% blocktrans %} Ogni Show puo' essere inserito in palinsesto a un istante preciso oppure per una emissione periodica ad iniziare da una una data specifica fino a una data finale per alcuni giorni della settimana specificati. {% endblocktrans %}

{% trans "FeedBurner and iTunes URLs" %}

{% blocktrans %} After saving at least one show and one episode, consider submitting your feed URL to FeedBurner for keeping track of podcast subscriber statistics. Your feed URL should be something like, where title-of-show is the slug of your show: {% endblocktrans %}

http://www.example.com/podcasts/title-of-show/feed/

{% blocktrans %} Remember to check the checkbox for "I'm a podcaster!" Your new FeedBurner URL should be something like: {% endblocktrans %}

http://feeds.feedburner.com/TitleOfShow

{% blocktrans %} You can now return to your website's admin and paste this URL into your Show's FeedBurner textbox. For bonus points, submit your FeedBurner URL to the iTunes Store. Your iTunes podcast URL should then be something like: {% endblocktrans %}

http://phobos.apple.com/WebObjects/MZStore.woa/wa/viewPodcast?id=000000000

{% blocktrans %} The advantage of submitting your FeedBurner URL to the iTunes Store allows you to track show statistics while also giving users the advantage of using the friendly iTunes interface. Return to the admin again and paste the iTunes show URL into the Show's iTunes URL textbox. {% endblocktrans %}

{% trans "Ping iTunes for new content" %}

{% blocktrans %} The iTunes Store checks new content daily but you might want to make a new episode available immediately in the iTunes Store. Visit your show's ping URL to make that episode available, which would be something like: {% endblocktrans %}

{% blocktrans %} Alternatively, if you're a savvy developer, you could set up a cron job to handle this, but note that pinging too often could result in a removal from the iTunes Store. {% endblocktrans %}

{% trans "Yahoo! Media RSS feed submission" %}

{% blocktrans %} Likewise, considering submitting your podcast to Yahoo! Search, which specifically accepts any kind of regularly published media-based (audio, video, image, document, etc.) RSS 2.0 feed or Media RSS feed. Your Media RSS feed should be something like: {% endblocktrans %}

{% trans "Google video sitemaps" %}

{% blocktrans %} If you're creating a video podcast, you can submit a video sitemap to Google Webmaster Tools. The video sitemap will help Google index videos in Google Video. The video sitemap URL should be something like: {% endblocktrans %}

{% blocktrans %} Additionally, you can add the video sitemap URL to your robots.txt file: {% endblocktrans %}

{% blocktrans %} Google allows the submission of a media RSS feed instead of the sitemap to Google Webmaster Tools if you prefer. {% endblocktrans %}

{% trans 'La definizione di un Episodio' %}

{% blocktrans %} Un episodio è composto da una o piu' enclosure (parti) associate a un titolo e un file audio da caricare {% endblocktrans %}

{% blocktrans %} Un episodio ha una o più schedule che definiscono quando dovrà essere mandato in onda automaticamente da autoradio (prima emissione ed eventuali repliche). {% endblocktrans %}

{% blocktrans %} Per ogni episodio è possibile inserire dei metadati utili per effettuare un efficiente podcast/mediacast. {% endblocktrans %}

{% trans "What is the Dublin Core namespace?" %}

{% blocktrans %} The Dublin Core namespace allows for meta data to be associated with content contained in an RSS feed. Additional details on the Dublin Core or the DC extension. {% endblocktrans %}

Podcast

{% trans 'Che cosa è il podcasting/mediacast?' %}

{% blocktrans %} Il podcasting o in senso più generale il Mediacast è un sistema che mette a disposizione brani audio e video attraverso Internet in formato feed RSS, in pratica è un servizio che automaticamente informa ed eventualmente scarica i nuovi file audio messi a disposizione su un sito. Tramite un programma in grado di leggere e decifrare questi feed, è possibile essere informati non appena un nuovo file audio viene pubblicato. Il podcasting consente un ascolto personalizzato dei contenuti: gli utenti scelgono quando ascoltare, dove ascoltare e come ascoltare i file audio. {% endblocktrans %}

{% trans 'Come funziona il podcasting/mediacast?' %}

{% blocktrans %} Il procedimento è semplice: occorre scaricare ed installare sul proprio pc un software per il podcasting. Una volta installato il programma, bisogna indicare da quali fonti scaricare i file e con quale frequenza cercare nuovi brani. {% endblocktrans %}

{% trans 'Autoradio: un efficiente motore per il podcasting/mediacast' %}

{% blocktrans %} Autoradio fornisce una interfaccia web accessibile dal menu principale alla voce Mediacast per navigare i programmi e i relativi episodi fornendo i flussi web necessari per un efficiente podcasting della propria programmazione radiofonica. {% endblocktrans %}

{% endif %} {% if params.docitem == "programbook" %}

{% blocktrans %} Autoradio permette la stampa del Libro Programmi secondo la legislazione italiana. Selezionata l'apposita voce dal menu principale è possibile procedere alla stampa inserendo gli estremi delle date richieste. Viene generato un file PDF pronto per la stampa. Alcune voci presenti nella stampa sono definite nella tabella "configure" della sezione "programmi" e modificabili dal pannello di amministrazione. {% endblocktrans %}

{% endif %}
{% endblock %} autoradio-upstream-2.8.2+dfsg.orig/templates/doc/index.html0000644000175000017500000000365612332264612024235 0ustar capriottcapriott{% extends "admin/base_site.html" %} {% load i18n %} {% block content %}

Documentation

Autoradio {% trans "overview" %}

{% trans "A global vision of Autoradio suite." %}

Autoradio {% trans "features" %}

{% trans "What you can do with Autoradio." %}

{% trans "Main components" %}

{% trans "The components of autoradio suite." %}

Autoradio {% trans "player" %}

{% trans "What you can do with Autoradio." %}

Autoradio {% trans "scheduler" %}

{% trans "Real time emission of your schedule." %}

{% trans "User inteface" %}

{% trans "The web interface." %}

{% blocktrans %}

Ogni classe di configurazione dispone della voce "configure" dalla quale è possibile impostare alcune caratteristiche per l'intera classe quali attivazione/disattivazione o limiti di orario della classe.

La voce "giorno" permette di inserire i nomi dei giorni nella lingua impostata

{% endblocktrans %}

{% trans "Playlist" %}

{% trans "How to start to play music." %}

{% trans "Jingle" %}

{% trans "How to start to use jingles to promote your brand." %}

{% trans "Spot" %}

{% trans "Organize radio advertisement." %}

{% trans "Program" %}

{% trans "Record your programs and set when it will be on air." %}

{% trans "Podcast" %}

{% trans "How to distribute your audio on the net." %}

{% trans "Program's book" %}

{% trans "Palimpsest for the italian law." %}

{% endblock %} autoradio-upstream-2.8.2+dfsg.orig/templates/base.html0000644000175000017500000000544412332264612023270 0ustar capriottcapriott{% load i18n %} AutoRadio | {% block title %} Radio Automation Software {% endblock %} {% block refresh %} {% endblock %} {% block extrahead %}{% endblock %}
{% block billboard %}{% endblock %}
{% block columnwrap %}
{% block content %}{% endblock %}
{% endblock %}
autoradio-upstream-2.8.2+dfsg.orig/templates/palimpsest/0000755000175000017500000000000012332265077023650 5ustar capriottcapriottautoradio-upstream-2.8.2+dfsg.orig/templates/palimpsest/extreme.html0000644000175000017500000000053512332264612026204 0ustar capriottcapriott{% extends "base.html" %} {% load i18n %} {% block title %}{{ section.title }}{% endblock %} {% block content %}

{%trans "Specify a time range to produce the report for the program's book" %}

{{ form.as_p }}

{% endblock %} autoradio-upstream-2.8.2+dfsg.orig/templates/podcast/0000755000175000017500000000000012332265077023124 5ustar capriottcapriottautoradio-upstream-2.8.2+dfsg.orig/templates/podcast/show_feed_atom.html0000644000175000017500000000316512332264612026774 0ustar capriottcapriott {% regroup object_list by show as show_list %}{% for show in show_list %} {{ show.grouper.title }} {{ show.list.0.date|date:"Y-m-d" }}T{{ show.list.0.date|date:"H:i:s" }}Z {% for author in show.grouper.author.all %}{% if forloop.first %}{% else %}{% if forloop.last %} and {% else %}, {% endif %}{% endif %}{% if author.first_name or author.last_name %}{% if author.first_name and author.last_name %}{{ author.first_name }} {{ author.last_name }}{% endif %}{% if author.first_name and not author.last_name %}{{ author.first_name }}{% endif %}{% if author.last_name and not author.first_name %}{{ author.last_name }}{% endif %}{% else %}{{ author.username }}{% endif %}{% endfor %} urn:uuid:60a76c80-d399-11d9-b93C-0003939e0af6 {% for episode in show.list %} {% for enclosure in episode.enclosure_set.all %} {{ episode.title }}{% if enclosure.title %}: {{ enclosure.title }}{% endif %} tag:{{ enclosure.file.url }},{{ episode.title }},{{ enclosure.title }},{{ episode.date|date:"H:i:s" }},{{ enclosure.id }} {{ episode.date|date:"Y-m-d" }}T{{ episode.date|date:"H:i:s" }}Z {% if episode.summary %}{{ episode.summary }}{% else %}{{ episode.description }}{% endif %} {% endfor %} {% endfor %} {% endfor %} autoradio-upstream-2.8.2+dfsg.orig/templates/podcast/show_feed_media.html0000644000175000017500000001150012332264612027103 0ustar capriottcapriott {% regroup object_list by show as show_list %}{% for show in show_list %} {{ show.grouper.title }} {{ show.grouper.link }} {{ show.grouper.description }} {% ifequal show.grouper.copyright "All rights reserved" %}{% now "Y" %} {{ show.grouper.organization }}. {{ show.grouper.copyright }}. {% else %} {% ifequal show.grouper.copyright "Public domain" %}{% now "Y" %} {{ show.grouper.organization }}. {{ show.grouper.copyright }}. {% else %} {{ show.grouper.copyright }}{% endifequal %}{% endifequal %} {% for episode in show.list %} {{ episode.title }} {{ episode.date|date:"r" }} GMT {{ episode.description }} {% for enclosure in episode.enclosure_set.all %} {% if enclosure.player %}{% endif %} {% if enclosure.title %}{{ enclosure.title }}{% endif %} {{ episode.show.organization }} {% if episode.role %}{{ episode.show.author.all.0.first_name }} {{ episode.show.author.all.0.last_name }}{% endif %} {% if episode.media_category %}{% for category in episode.media_category.all %} {{ category.name }} {% endfor %}{% endif %} {% if enclosure.hash %}{{ enclosure.hash }}{% endif %} {% if episode.text %}{{ episode.text }}{% endif %} {% if episode.image %}{% endif %} {% if episode.rating %}{{ episode.standard|lower }}{% endif %} {% if episode.deny %}{{ episode.restriction }}{% endif %} {% if episode.keywords %}{{ episode.keywords }}{% endif %} {% if episode.start %}start={{ episode.start|date:"r" }}; {% endif %}{% if episode.end %}end={{ episode.end|date:"r" }}; {% endif %}{% if episode.scheme %}scheme={{ episode.scheme }}; {% endif %}{% if episode.name %}name={{ episode.name }};{% endif %} {% if episode.preview %}{% endif %} {% if episode.start and episode.end and episode.host %}{% endif %} {% endfor %} {% endfor %} {% endfor %} autoradio-upstream-2.8.2+dfsg.orig/templates/podcast/base.html0000644000175000017500000000243012332264612024715 0ustar capriottcapriott{% load i18n %} {% block head %} {% block title %}Django Podcast{% endblock %} {% endblock %}
{% block allcontent %} {% endblock %} autoradio-upstream-2.8.2+dfsg.orig/templates/podcast/episode_list.html0000644000175000017500000000566312332264612026501 0ustar capriottcapriott{% extends "podcast/base.html" %} {% load i18n %} {% block allcontent %}

{% trans "A list of episodes of one show" %}

{% block content %}

{% trans "Return to shows" %}

{% regroup object_list by show as show_list %} {% for show in show_list %}

{{ show.grouper.title }}

{{ show.grouper.subtitle }}

{% if show.grouper.explicity %}

{% trans "Explicit" %}

{% endif %}
{% if show.grouper.category.all %}
{% trans "Category" %}
{% for category in show.grouper.category.all|slice:":1" %}{{ category.name }}{% endfor %}
{% endif %}
{% trans "Author" %}
{% for author in show.grouper.author.all %}
{% if author.email %}{% endif %}{% if author.first_name or author.last_name %}{% if author.first_name and author.last_name %}{{ author.first_name }} {{ author.last_name }}{% endif %}{% if author.first_name and not author.last_name %}{{ author.first_name }}{% endif %}{% if author.last_name and not author.first_name %}{{ author.last_name }}{% endif %}{% else %}{{ author.username }}{% endif %}{% if author.email %}{% endif %}
{% endfor %}
{% trans "fedd web/RSS" %}
{% trans "Subscribe Feed RSS 2.0 and iTunes" %}
{% trans "Subscribe Atom" %}
{% trans "Subscribe Media RSS" %}
{% if show.grouper.feedburner %}
{% trans "FeedBurner" %}
{% trans "Subscribe" %}
{% endif %} {% if show.grouper.itunes %}
iTunes
{% trans "Subscribe" %}
{% endif %}
{% if show.grouper.image %}
{{ show.grouper.organization }} show logo
{% endif %}

{% if show.grouper.summary %}{{ show.grouper.summary }}{% else %}{{ show.grouper.description|striptags }}{% endif %}

{% for episode in show.list %}

{{ episode.title }}

{{ episode.subtitle }}
{% if episode.image %}
{{ episode.title }} {% trans
{% endif %}

{% if episode.summary %}{{ episode.summary }}{% else %}{{ episode.description|striptags }}{% endif %}

{% endfor %} {% endfor %} {% endblock %}
{% endblock %} autoradio-upstream-2.8.2+dfsg.orig/templates/podcast/show_feed.html0000644000175000017500000001312412332264612025750 0ustar capriottcapriott {% regroup object_list by show as show_list %}{% for show in show_list %} {{ show.grouper.title }} {{ show.grouper.link }} {{ show.grouper.description|striptags }} {% if show.grouper.language %}{{ show.grouper.language }}{% endif %} ℗ & © {% now "Y" %} {{ show.grouper.organization }}. {{ show.grouper.copyright }}. {% for author in show.grouper.author.all %}{% if forloop.first %}{% else %}{% if forloop.last %} and {% else %}, {% endif %}{% endif %}{{ author.email }}{% endfor %} {% if show.grouper.author.email or show.grouper.webmaster.email %}{% if show.grouper.webmaster.email %}{{ show.grouper.webmaster.email }}{% else %}{% endif %}{% endif %} {{ show.list.0.date|date:"r" }} {% if show.grouper.category_show %}{{ show.grouper.category_show }}{% endif %} Django Web Framework http://blogs.law.harvard.edu/tech/rss {% if show.grouper.ttl %}{{ show.grouper.ttl }}{% endif %} {% if show.grouper.image %} {{ show.grouper.image.url }} {{ show.grouper.title }} {{ show.grouper.link }} {% endif %} {{ show.grouper.organization }} {% for author in show.grouper.author.all %}{% if forloop.first %}{% else %}{% if forloop.last %} and {% else %}, {% endif %}{% endif %}{% if author.first_name or author.last_name %}{% if author.first_name and author.last_name %}{{ author.first_name }} {{ author.last_name }}{% endif %}{% if author.first_name and not author.last_name %}{{ author.first_name }}{% endif %}{% if author.last_name and not author.first_name %}{{ author.last_name }}{% endif %}{% else %}{{ author.username }}{% endif %}{% endfor %} {% for author in show.grouper.author.all %}{{ author.email }}{% if forloop.last %}{% else %}, {% endif %}{% endfor %} {% if show.grouper.subtitle %}{{ show.grouper.subtitle }}{% endif %} {% if show.grouper.summary %}{{ show.grouper.summary|striptags }}{% else %}{{ show.grouper.description|striptags }}{% endif %} {% if show.grouper.image %}{% endif %} {% if show.grouper.category.all %}{% for category in show.grouper.category.all %}{% if category.name %} {% else %} {% endif %}{% endfor %}{% endif %} {% if show.grouper.explicit %}{{ show.grouper.explicit|lower }}{% endif %} {% if show.grouper.block %}yes{% endif %} {% if show.grouper.redirect %}{{ show.grouper.redirect }}{% endif %} {% for episode in show.list %} {% for enclosure in episode.enclosure_set.all %} {% if enclosure.file %} {{ episode.title }}{% if enclosure.title %}: {{ enclosure.title }}{% endif %} {{ enclosure.file.url }} {{ episode.description|striptags }} {% for author in show.grouper.author.all %}{{ author.email }}{% if forloop.last %}{% else %}, {% endif %}{% endfor %} {% if episode.category %}{{ episode.category }}{% endif %} {{ enclosure.file.url }} {{ episode.date|date:"r" }} GMT {% for author in episode.author.all %}{% if forloop.first %}{% else %}{% if forloop.last %} and {% else %}, {% endif %}{% endif %}{% if author.first_name or author.last_name %}{% if author.first_name and author.last_name %}{{ author.first_name }} {{ author.last_name }}{% endif %}{% if author.first_name and not author.last_name %}{{ author.first_name }}{% endif %}{% if author.last_name and not author.first_name %}{{ author.last_name }}{% endif %}{% else %}{{ author.username }}{% endif %}{% endfor %} {% if episode.subtitle %}{{ episode.subtitle }}{% endif %} {% if episode.summary %}{{ episode.summary|striptags }}{% else %}{{ episode.description|striptags }}{% endif %} {% if episode.minutes and episode.seconds %}{{ episode.minutes }}:{{ episode.seconds }}{% endif %} {% if episode.keywords %}{{ episode.keywords }}{% endif %} {% if episode.explicit %}{{ episode.explicit|lower }}{% endif %} {% if episode.block %}yes{% endif %} {% endif %} {% endfor %} {% endfor %} {% endfor %} autoradio-upstream-2.8.2+dfsg.orig/templates/podcast/show_list.html0000644000175000017500000000143512332264612026022 0ustar capriottcapriott{% extends "podcast/base.html" %} {% load i18n %} {% block allcontent %}

{% trans "A list of shows" %}

{% block content %} {% for show in object_list %}

{{ show.title }}

{{ show.organization }}

{% if show.image %}
{{ show.organization }} show logo
{% endif %}

{% if show.summary %}{{ show.summary }}{% else %}{{ show.description|striptags }}{% endif %}

{% endfor %} {% endblock %}
{% endblock %} autoradio-upstream-2.8.2+dfsg.orig/templates/podcast/episode_detail.html0000644000175000017500000000635412332264612026766 0ustar capriottcapriott{% extends "podcast/base.html" %} {% load i18n %} {% block allcontent %}

{% trans "A detail of one episode of one show" %}

{% block content %}

{% trans "Return to episodes" %}

{{ object.title }}

{% if object.subtitle %}

{{ object.subtitle }}

{% endif %} {% if object.image %}
{{ object.title }} {% trans
{% endif %}
{% trans "Date" %}
{{ object.date|date:"F g, Y, g:m a" }}
{% trans "Show" %}
{{ object.show.title }}
{% trans "Author" %}
{% for author in object.author.all %}
{% if author.email %}{% endif %}{% if author.first_name or author.last_name %}{% if author.first_name and author.last_name %}{{ author.first_name }} {{ author.last_name }}{% endif %}{% if author.first_name and not author.last_name %}{{ author.first_name }}{% endif %}{% if author.last_name and not author.first_name %}{{ author.last_name }}{% endif %}{% else %}{{ author.username }}{% endif %}{% if author.email %}{% endif %}
{% endfor %}
feed web/RSS
{% trans "Subscribe Feed RSS 2.0 and iTunes" %}
{% trans "Subscribe Atom" %}
{% trans "Subscribe Media RSS" %}
{% if object.show.feedburner %}
FeedBurner
{% trans "Subscribe" %}
{% endif %} {% if object.show.itunes %}
iTunes
{% trans "Subscribe" %}
{% endif %}

{% if object.summary %}{{ object.summary }}{% else %}{{ object.description|striptags }}{% endif %}

{% trans "Listen this episode" %}

    {% for enclosure in enclosure_list %} {% if enclosure.file %}
  • {% if enclosure.title %}{{ enclosure.title }}{% else %}{{ object.title }}{% endif %}: {% trans "Download file" %} ({{ enclosure.file.size|filesizeformat }}) --> {% trans "Listen" %}
  • {% endif %} {% endfor %}
{% if object.captions %}

{% trans "Download the closed captions" %}.

{% endif %} {% endblock %}
{% endblock %} {% block footer %}

© {% now "Y" %} {{ object.show.organization }}. {% trans "Subscribe" %}.

{% endblock %} autoradio-upstream-2.8.2+dfsg.orig/templates/podcast/episode_sitemap.html0000644000175000017500000000362112332264612027160 0ustar capriottcapriott {% regroup object_list by show as show_list %}{% for show in show_list %}{% for episode in show.list %} {{ episode.show.link }} {{ episode.update|date:"Y-m-D" }}T{{ episode.update|date:"G:i:s" }}+{{ episode.update|date:"O" }} {{ episode.frequency }} {{ episode.priority }} {% for enclosure in episode.enclosure_set.all %} {{ enclosure.file.url }} {% endfor %} {% for enclosure in episode.enclosure_set.all %} {{ enclosure.player }}{% endfor %} {% if episode.image %}{{ episode.image.url }}{% endif %} {{ episode.title }} {% if episode.summary %}{{ episode.summary|striptags }}{% else %}{{ episode.description|striptags }}{% endif %} {{ episode.update|date:"Y-m-D" }}T{{ episode.update|date:"G:i:s" }}+{{ episode.update|date:"O" }} {% ifequal episode.explicit "Yes" %}no{% endifequal %}{% ifequal episode.explicit "No" %}yes{% endifequal %}{% ifequal episode.explicit "clean" %}yes{% endifequal %} {{ episode.seconds_total }} {% endfor %}{% endfor %} autoradio-upstream-2.8.2+dfsg.orig/templates/schedule/0000755000175000017500000000000012332265077023263 5ustar capriottcapriottautoradio-upstream-2.8.2+dfsg.orig/templates/schedule/index.html0000644000175000017500000000407312332264612025256 0ustar capriottcapriott{% extends "base.html" %} {% load i18n %} {% block title %}{{ section.title }}{% endblock %} {% block content %}

{% trans "Automatic update every" %} 5 {% trans "minuts" %}. {% trans "Last update" %}: {% now "Y-m-d H:i:s" %}

{% trans "This is on air status" %}:

{% if schedule %} {% for djobj,title,datet,media,length,tipo,datetdone,future in schedule %} {% endfor %}
{% trans "Type" %} {% trans "Title" %} {% trans "Programmed for" %} {% trans "That is" %} {% trans "Last emission done" %} {% trans "Length" %} {% trans "File" %}
{{ tipo }} {{ title }} {{ datet|date:"Y-m-d H:i:s" }} {%if datet %} {% if future %} {% trans "in" %} {{ datet|timeuntil }} {% else %} {{ datet|timesince }} {% trans "ago" %} {% endif %}{% endif %} {{ datetdone|timesince }} {%if datetdone %} {% trans "ago" %} {% endif %} {{ length }} {% trans "Download" %}
{% else %}

{% trans "No schedule are available" %}.

{% endif %} {% endblock %} autoradio-upstream-2.8.2+dfsg.orig/templates/admin/0000755000175000017500000000000012332265077022557 5ustar capriottcapriottautoradio-upstream-2.8.2+dfsg.orig/templates/admin/base_site.html0000644000175000017500000000046712332264612025404 0ustar capriottcapriott{% extends "admin/base.html" %} {% load i18n %} {% block title %}{{ title }} | {% trans 'Django site admin' %}{% endblock %} {% block branding %}

AutoRadio Home / AutoRadio {% trans 'Documentation' %}

{% endblock %} {% block nav-global %}{% endblock %} autoradio-upstream-2.8.2+dfsg.orig/templates/xmms/0000755000175000017500000000000012332265077022453 5ustar capriottcapriottautoradio-upstream-2.8.2+dfsg.orig/templates/xmms/index.html0000644000175000017500000000054712332264612024450 0ustar capriottcapriott{% extends "base.html" %} {% load i18n %} {% block refresh %} {% endblock %} {% block title %}{{ section.title }}{% endblock %} {% block content %}

{% trans "Automatic update every" %} 15 {% trans "seconds" %}. {% trans "Last update" %}: {% now "Y-m-d H:i:s" %}

{{ xmmsweb|safe }} {% endblock %} autoradio-upstream-2.8.2+dfsg.orig/templates/player/0000755000175000017500000000000012332265077022763 5ustar capriottcapriottautoradio-upstream-2.8.2+dfsg.orig/templates/player/base.html0000644000175000017500000000234312332264612024557 0ustar capriottcapriott{% load i18n %} {% block head %} {% block title %}Universal Ogg player {% endblock %} {% endblock %} {% block header %}
{% endblock %} {% block allcontent %} {% endblock %} autoradio-upstream-2.8.2+dfsg.orig/templates/player/player.html0000644000175000017500000000472212332264612025144 0ustar capriottcapriott{% extends "player/base.html" %} {% load i18n %} {% block head %} Universal Ogg player {% endblock %} {% block header %} {% endblock %} {% block allcontent %}
{% endblock %} autoradio-upstream-2.8.2+dfsg.orig/templates/player/playernohtml5.html0000644000175000017500000000462412332264612026454 0ustar capriottcapriott{% extends "player/base.html" %} {% load i18n %} {% block head %} Universal Ogg player {% endblock %} {% block header %} {% endblock %} {% block allcontent %}
{% endblock %} autoradio-upstream-2.8.2+dfsg.orig/dbus-autoradio.conf0000644000175000017500000000462312332264613023262 0ustar capriottcapriott session unix:tmpdir=/tmp tcp:host=localhost,bind=*,port=1234,family=ipv4 ANONYMOUS 1000000000 250000000 1000000000 250000000 1000000000 4096 120000 240000 100000 10000 100000 10000 50000 50000 50000 autoradio-upstream-2.8.2+dfsg.orig/autoradio/0000755000175000017500000000000012332265077021460 5ustar capriottcapriottautoradio-upstream-2.8.2+dfsg.orig/autoradio/playlists/0000755000175000017500000000000012332265077023504 5ustar capriottcapriottautoradio-upstream-2.8.2+dfsg.orig/autoradio/playlists/admin.py0000644000175000017500000000350312332264611025140 0ustar capriottcapriottfrom models import Giorno, Configure, Playlist, Schedule, PeriodicSchedule from django.contrib import admin class ScheduleInline(admin.StackedInline): #class ScheduleInline(admin.TabularInline): model = Schedule extra=2 class PeriodicScheduleInline(admin.StackedInline): model = PeriodicSchedule extra=2 class GiornoAdmin(admin.ModelAdmin): search_fields = ['name'] admin.site.register(Giorno, GiornoAdmin) class ConfigureAdmin(admin.ModelAdmin): list_display = ('sezione','active',\ 'emission_starttime'\ ,'emission_endtime') admin.site.register(Configure, ConfigureAdmin) class PlaylistAdmin(admin.ModelAdmin): fieldsets = ( (None, {'fields': ('playlist','file')}), ('Date information', {'fields': ('rec_date','active',)}), ) list_display = ('playlist','rec_date','was_recorded_today','active') search_fields = ['playlist','file'] date_hierarchy = 'rec_date' list_filter = ['rec_date','active'] inlines = [ ScheduleInline,PeriodicScheduleInline, ] admin.site.register(Playlist, PlaylistAdmin) class ScheduleAdmin(admin.ModelAdmin): list_display = ('file', 'shuffle','length','emission_date','emission_done'\ ,'was_scheduled_today') list_filter = ['emission_date','emission_done'] search_fields = ['playlist','emission_date'] date_hierarchy = 'emission_date' admin.site.register(Schedule, ScheduleAdmin) class PeriodicScheduleAdmin(admin.ModelAdmin): list_display = ('file', 'shuffle','length','start_date','end_date','time'\ ,'emission_done') list_filter = ['start_date','end_date','time','giorni','emission_done'] search_fields = ['playlist','giorni'] date_hierarchy = 'start_date' admin.site.register(PeriodicSchedule, PeriodicScheduleAdmin) autoradio-upstream-2.8.2+dfsg.orig/autoradio/playlists/views.py0000644000175000017500000000003212332264611025177 0ustar capriottcapriott# Create your views here. autoradio-upstream-2.8.2+dfsg.orig/autoradio/playlists/__init__.py0000644000175000017500000000000012332264611025574 0ustar capriottcapriottautoradio-upstream-2.8.2+dfsg.orig/autoradio/playlists/models.py0000644000175000017500000001742012332264611025336 0ustar capriottcapriottfrom django.db import models from django.utils.translation import ugettext_lazy import datetime import calendar from autoradio.autoradio_config import * from django import VERSION as djversion if ((djversion[0] == 1 and djversion[1] >= 3) or djversion[0] > 1): from django.db import models from django.db.models import signals class DeletingFileField(models.FileField): """ FileField subclass that deletes the refernced file when the model object itself is deleted. WARNING: Be careful using this class - it can cause data loss! This class makes at attempt to see if the file's referenced elsewhere, but it can get it wrong in any number of cases. """ def contribute_to_class(self, cls, name): super(DeletingFileField, self).contribute_to_class(cls, name) signals.post_delete.connect(self.delete_file, sender=cls) def delete_file(self, instance, sender, **kwargs): file = getattr(instance, self.attname) # If no other object of this type references the file, # and it's not the default value for future objects, # delete it from the backend. if file and file.name != self.default and \ not sender._default_manager.filter(**{self.name: file.name}): file.delete(save=False) elif file: # Otherwise, just close the file, so it doesn't tie up resources. file.close() else: DeletingFileField=models.FileField def giorno_giorno(): giorni=[] for giorno in (calendar.day_name): giorno=giorno.decode('utf-8') giorni.append(( giorno, giorno)) return giorni # yield 'Tutti','Tutti' class Giorno(models.Model): name = models.CharField(max_length=20,choices=giorno_giorno(),unique=True,\ help_text=ugettext_lazy("weekday name")) def __unicode__(self): return self.name class Configure(models.Model): sezione = models.CharField(max_length=50,unique=True\ ,default='playlist',editable=False) active = models.BooleanField(ugettext_lazy("Activate Playlist"),default=True,\ help_text=ugettext_lazy("activate/deactivate the intere playlist class")) emission_starttime = models.TimeField(ugettext_lazy('Programmed start time'),null=True,blank=True,\ help_text=ugettext_lazy("The start time from wich the playlist will be active")) emission_endtime = models.TimeField(ugettext_lazy('Programmed start time'),null=True,blank=True,\ help_text=ugettext_lazy("The end time the playlist will be active")) def __unicode__(self): return self.sezione+" "+self.active.__str__()+" "\ +self.emission_starttime.isoformat()+" "\ +self.emission_endtime.isoformat() class Playlist(models.Model): playlist = models.CharField(ugettext_lazy('Playlist name'),max_length=200) file = DeletingFileField(ugettext_lazy('File'),upload_to='playlist',max_length=255,\ help_text=ugettext_lazy("The playlist file to upload, format should be extm3u, m3u, pls")) rec_date = models.DateTimeField(ugettext_lazy('Generation date'),\ help_text=ugettext_lazy("When the playlist was done (for reference only)")) active = models.BooleanField(ugettext_lazy("Active"),default=True,\ help_text=ugettext_lazy("Activate the playlist for emission")) def was_recorded_today(self): return self.rec_date.date() == datetime.date.today() was_recorded_today.short_description = ugettext_lazy('Generated today?') def __unicode__(self): #return self.playlist+" "+self.rec_date.isoformat()+" "+self.active.__str__() return self.playlist class Schedule(models.Model): # program = models.ForeignKey(Program, edit_inline=models.TABULAR,\ # num_in_admin=2,verbose_name='si riferisce al programma:',editable=False) playlist = models.ForeignKey(Playlist, verbose_name=\ ugettext_lazy('refer to playlist:')) shuffle = models.BooleanField(ugettext_lazy("Shuffle Playlist on start"),default=True,\ help_text=ugettext_lazy("Every time the playlist will be scheduled it's order will be randomly changed")) length = models.FloatField(ugettext_lazy("Max time length (seconds)"),default=None,null=True,blank=True,\ help_text=ugettext_lazy("If this time is set the playlist will be truncated")) emission_date = models.DateTimeField(ugettext_lazy('Programmed date'),\ help_text=ugettext_lazy("This is the date and time when the playlist will be on air")) # da reinserire ! # start_date = models.DateField('Data inizio programmazione',null=True,blank=True) # end_date = models.DateField('Data fine programmazione',null=True,blank=True) # time = models.TimeField('Ora programmazione',null=True,blank=True) # giorni = models.ManyToManyField(Giorno,verbose_name='Giorni programmati',null=True,blank=True) emission_done = models.DateTimeField(ugettext_lazy('Emission done')\ ,null=True,editable=False ) # def emitted(self): # return self.emission_done != None # emitted.short_description = 'Trasmesso' def was_scheduled_today(self): return self.emission_date.date() == datetime.date.today() was_scheduled_today.short_description = ugettext_lazy('Programmed for today?') def file(self): return self.playlist.playlist file.short_description = ugettext_lazy('Linked Playlist') def __unicode__(self): return unicode(self.playlist) class PeriodicSchedule(models.Model): # program = models.ForeignKey(Program, edit_inline=models.TABULAR,\ # num_in_admin=2,verbose_name='si riferisce al programma:',editable=False) playlist = models.ForeignKey(Playlist,verbose_name=\ ugettext_lazy('refer to playlist:')) shuffle = models.BooleanField(ugettext_lazy("Shuffle Playlist on start"),default=True,\ help_text=ugettext_lazy("Every time the playlist will be scheduled it's order will be randomly changed")) length = models.FloatField(ugettext_lazy("Max time length (seconds)"),default=None,null=True,blank=True,\ help_text=ugettext_lazy("If this time is set the playlist will be truncated")) start_date = models.DateField(ugettext_lazy('Programmed start date'),null=True,blank=True,\ help_text=ugettext_lazy("The playlist will be scheduled starting from this date")) end_date = models.DateField(ugettext_lazy('Programmed end date'),null=True,blank=True,\ help_text=ugettext_lazy("The playlist will be scheduled ending this date")) time = models.TimeField(ugettext_lazy('Programmed time'),null=True,blank=True,\ help_text=ugettext_lazy("This is the time when the playlist will be on air")) giorni = models.ManyToManyField(Giorno,verbose_name=ugettext_lazy('Programmed days'),null=True,blank=True,\ help_text=ugettext_lazy("The playlist will be scheduled those weekdays")) emission_done = models.DateTimeField(ugettext_lazy('Emission done')\ ,null=True,editable=False ) # def emitted(self): # return self.emission_done != None # emitted.short_description = 'Trasmesso' def file(self): return self.playlist.playlist file.short_description = ugettext_lazy('Linked Playlist') def __unicode__(self): return unicode(self.playlist) autoradio-upstream-2.8.2+dfsg.orig/autoradio/autompris2.py0000644000175000017500000003024212332264611024131 0ustar capriottcapriott#!/usr/bin/env python # GPL. (C) 2007-2012 Paolo Patruno. import dbus import time import datetime import os import gobject import autoradio.settings from dbus.mainloop.glib import DBusGMainLoop from autoradio.mpris2.mediaplayer2 import MediaPlayer2 from autoradio.mpris2.player import Player from autoradio.mpris2.tracklist import TrackList from autoradio.mpris2.interfaces import Interfaces from autoradio.mpris2.some_players import Some_Players from autoradio.mpris2.utils import get_players_uri from autoradio.mpris2.utils import get_session # ------- dbus mpris2 interface --------- # http://specifications.freedesktop.org/mpris-spec/latest/index.html # this is only a draft becouse when I try in fedora 16 # audacious do not have mpris2 interface # audacious 3.2.2 have mpris2 plugin but do not implement the optional # org.mpris.MediaPlayer2.TrackList and org.mpris.MediaPlayer2.Playlists interfaces # amarok 2.5.0 have mpris2 interface but do not implement the optional # org.mpris.MediaPlayer2.TrackList and org.mpris.MediaPlayer2.Playlists interfaces # vlc-1.1.13 do not have mpris2 interface; we need vlc >= 2.0 (http://wiki.videolan.org/Twoflower) # that is available from pat1 repo for Fedora 16 # About mpris2 and audacious: #Issue #106 has been updated by John Lindgren. # #Status changed from New to Rejected # #These interfaces require a different type of playlist structure than that used in Audacious, so they will not be implemented. #---------------------------------------- #Feature #106: mpris2 plugin do not implement optional org.mpris.MediaPlayer2.TrackList interface and org.mpris.MediaPlayer2.Playlists interface #http://redmine.audacious-media-player.org/issues/106#change-309 # #Author: Paolo Patruno #Status: Rejected #Priority: Minor #Assignee: #Category: plugins/mpris2 #Target version: #Affects version: 3.2.2 # # #at http://specifications.freedesktop.org/mpris-spec/latest/index.html # #Interface MediaPlayer2.Playlists #Provides access to the media player's playlists. # #Interface MediaPlayer2.TrackList #Provides access to a short list of tracks which were recently played or will be played shortly. This is intended to provide context to the #currently-playing track, rather than giving complete access to the media player's playlist. # #Those interfaces, if I am right, are not implemented in mpris2 plugin. #----------------------------------------------------------------------- class mediaplayer: def __init__(self,player="AutoPlayer",session=0, busaddress=autoradio.settings.busaddressplayer): #qdbus --literal org.mpris.MediaPlayer2.vlc /org/mpris/MediaPlayer2 org.freedesktop.DBus.Properties.Get org.mpris.MediaPlayer2.TrackList Tracks # import gobject # gobject.threads_init() # # from dbus import glib # glib.init_threads() DBusGMainLoop(set_as_default=True) uris = get_players_uri(pattern=".*"+player+"$",busaddress=busaddress) if len(uris) >0 : uri=uris[0] if busaddress is None: self.bus = dbus.SessionBus() else: self.bus = dbus.bus.BusConnection(busaddress) self.mp2 = MediaPlayer2(dbus_interface_info={'dbus_uri': uri,'dbus_session':self.bus}) self.play = Player(dbus_interface_info={'dbus_uri': uri,'dbus_session':self.bus}) else: print "No players availables" return if self.mp2.HasTrackList: self.tl = TrackList(dbus_interface_info={'dbus_uri': uri,'dbus_session':self.bus}) else: self.tl = None def __str__(self): return self.play.PlaybackStatus def play_ifnot(self): ''' start playing if not. ''' # I check if mediaplayer is playing .... otherside I try to play if (not self.isplaying()): self.play.Play() def isplaying(self): ''' return true if is playing. ''' return self.play.PlaybackStatus == "Playing" def get_playlist_securepos(self,securesec=10): ''' Try to secure that there are some time (securesec) to complete all operations in time: if the player change song during operation will be a big problem ''' try: self.play_ifnot() #force to play mintimed=datetime.timedelta(seconds=securesec) toend=datetime.timedelta(seconds=0) volte=0 while ( toend < mintimed ): # take the current position pos=self.get_playlist_pos() metadata=self.get_metadata(pos) mtimelength=metadata["mtimelength"] mtimeposition=metadata["mtimeposition"] timed=datetime.timedelta(seconds=datetime.timedelta(milliseconds=mtimelength).seconds) toend=timed-datetime.timedelta(seconds=datetime.timedelta(milliseconds=mtimeposition).seconds) newpos=self.get_playlist_pos() if (pos != newpos): #inconsistenza: retry #print "retry" toend=datetime.timedelta(seconds=0) if ( toend < mintimed ): volte +=1 if volte > 10 : break # timeout , I have to play time.sleep(securesec+1) return pos except : return None def playlist_clear_up(self,atlast=10): ''' clear playlist starting from current position up. "atlast" numer of song are retained ''' try: self.play_ifnot() #force to play # take the current position (if error set pos=0) pos=self.get_playlist_securepos() if pos is None: return False # delete the old ones if pos > atlast : op=self.get_playlist() for prm in xrange(0,pos-atlast): print "remove up: ",op[prm] self.tl.RemoveTrack( str(op[prm])) time.sleep(1) return True except: return False def playlist_clear_down(self,atlast=500): ''' clear playlist starting from current position + atlast doen. "atlast" numer of song are retained for future play ''' try: self.play_ifnot() #force to play # take the current position (if error set pos=0) pos=self.get_playlist_securepos() if pos is None: return False length=self.get_playlist_len() #elimino il troppo if length-pos > atlast : op=self.get_playlist() for prm in xrange(length-1,pos+atlast,-1): print "remove down: ",op[prm] self.tl.RemoveTrack( str(op[prm]) ) time.sleep(1) return True except: return False def get_playlist_posauto(self,autopath,securesec=10): ''' get playlist position skipping file with path equal to autopath. Try to secure that there are some time (securesec) to complete all operations in time: if player change song during operation will be a big problem ''' try: pos=self.get_playlist_securepos(securesec=securesec) if pos is None or pos+1 == self.get_playlist_len(): return pos pos+=1 metadata=self.get_metadata(pos) try: file=metadata["file"] except: return pos filepath=os.path.dirname(file) #print "file://"+autopath #print os.path.commonprefix ((filepath,"file://"+autopath)) # ora controllo se ci sono gia dei file accodati nella playlist da autoradio # l'unica possibilita di saperlo e verificare il path del file while ( os.path.commonprefix ((filepath,"file://"+autopath)) == "file://"+autopath and pos+1 < self.get_playlist_len()): pos+=1 metadata=self.get_metadata(pos) try: file=metadata["file"] except: return pos filepath=os.path.dirname(file) # here I have found the first file added by autoradio return pos-1 except : return None def get_playlist(self): "get playlist" if self.tl is not None: return self.tl.Tracks else: raise Error def get_playlist_len(self): "get playlist lenght" if self.tl is not None: return len(self.tl.Tracks) else: return None def get_playlist_pos(self): "get current position" try: current=self.play.Metadata["mpris:trackid"] except: return None metadatas=self.tl.GetTracksMetadata(self.get_playlist()) id=0 for metadata in metadatas: if metadata["mpris:trackid"] == current: return id id +=1 return None def get_metadata(self,pos=None): "get metadata for position" if pos is None: return None metadatas=self.tl.GetTracksMetadata(self.get_playlist()) metadata=metadatas[pos] try: file=metadata["xesam:url"] except: file=None try: title=metadata["xesam:title"] if title=="": title=None except: title=None try: artist=metadata["xesam:artist"] if artist=="": artist=None except: artist=None try: mtimelength=metadata["mpris:length"] except: mtimelength=0 try: # get current truck current=self.play.Metadata["mpris:trackid"] if metadata["mpris:trackid"] == current : mtimeposition=self.play.Position else: mtimeposition=0 except: mtimeposition=0 mymeta={ "file": file, "title": title, "artist": artist, "mtimelength": long(round(mtimelength/1000.)), "mtimeposition": long(round(mtimeposition/1000.)) } return mymeta def playlist_add_atpos(self,media,pos): "add media at pos postion in the playlist" if pos is not None: self.tl.AddTrack(media,self.get_playlist()[pos],False) else: # the playlist is empty self.tl.AddTrack(media,"/org/mpris/MediaPlayer2/TrackList/NoTrack",False) time.sleep(1) return None # old style syntax: # # def trackremoved_callback(self,op): # print "removed:",op # # def trackadded_callback(self,diz,op): # print "added:",diz # print "added:",op # # def connect(self): # self.tracklist.connect_to_signal('TrackRemoved', self.trackremoved_callback) # self.tracklist.connect_to_signal('TrackAdded', self.trackadded_callback) def loop(self): '''start the main loop''' mainloop = gobject.MainLoop() mainloop.run() def main(): # must be done before connecting to DBus # DBusGMainLoop(set_as_default=True, mp=mediaplayer(player="AutoPlayer") print "status",mp # mp.play_ifnot() # print mp # for id in xrange(mp.get_playlist_len()): # print mp.get_metadata(id) #mp.connect() #print "connected" #mp.loop() print "pos",mp.get_playlist_pos() print "securepos" print mp.get_playlist_securepos() print "clear_up" print mp.playlist_clear_up(atlast=2) print "clear_down" print mp.playlist_clear_down(atlast=3) print "playlist" print mp.get_playlist() posauto=mp.get_playlist_posauto(autopath="/casa") print "posauto",posauto print "add_atpos" mp.playlist_add_atpos("file:///home",posauto) ##mp.playlist_add_atpos("file:///home",3) if __name__ == '__main__': main() # (this code was run as script) autoradio-upstream-2.8.2+dfsg.orig/autoradio/gest_spot.py0000644000175000017500000002003612332264611024033 0ustar capriottcapriott#!/usr/bin/env python # This Python file uses the following encoding: utf-8 # GPL. (C) 2007-2009 Paolo Patruno. import os, sys os.environ['DJANGO_SETTINGS_MODULE'] = 'autoradio.settings' from django.conf import settings import logging from datetime import * from autoradio_config import * from django.db.models import Q from spots.models import Configure from spots.models import Spot from spots.models import Fascia from spots.models import Giorno #used to get metadata from audio files import mutagen import tempfile,shutil class gest_spot: def __init__ (self,now,minelab,playlistdir): """init of spot application: now : currenti datetime minelab: minutes to elaborate execute the right data retrival to get the schedued spots""" import calendar playlistpath=os.path.join(settings.MEDIA_ROOT, playlistdir) try: # Create the date-based directory if it doesn't exist. os.makedirs(playlistpath) except OSError: # Directory probably already exists. pass self.now=now self.minelab=minelab self.playlistpath=playlistpath ora=self.now.time() self.oggi=self.now.date() self.giorno=calendar.day_name[self.now.weekday()] datesched_min=self.now - timedelta( seconds=60*self.minelab) datesched_max=self.now + timedelta( milliseconds=60000*self.minelab-1) # 1 millisec tollerance logging.debug( "SPOT: elaborate from %s to %s",datesched_min, datesched_max) timesched_min=datesched_min.time() timesched_max=datesched_max.time() logging.debug( "SPOT: elaborate from %s to %s",timesched_min, timesched_max) if (Configure.objects.filter(active__exact=False).count() == 1): self.fasce=() return #todo: the use of ora here is not exact if (Configure.objects.filter(emission_starttime__gt=ora).count() == 1) : self.fasce=() return if (Configure.objects.filter(emission_endtime__lt=ora).count() == 1): self.fasce=() return if (timesched_min < timesched_max): self.fasce=Fascia.objects.filter(\ Q(emission_time__gte=timesched_min),Q( emission_time__lte=timesched_max),\ Q(active__exact = True)).order_by('emission_time') else: # here we are around midnight self.fasce=Fascia.objects.filter(\ Q(emission_time__gte=timesched_min)|Q( emission_time__lte=timesched_max),\ Q(active__exact = True)).order_by('emission_time') def get_fasce(self,genfile=True): for fascia in self.fasce: self.fascia=fascia # count the spots self.ar_spots_in_fascia=self.count_spots() self.ar_filename=self.get_fascia_playlist_media(genfile) # TODO: add the real http to the file path self.ar_url=self.ar_filename self.ar_scheduledatetime=datetime.combine(self.oggi, fascia.emission_time) # if we are around midnight we have to check the correct date (today, iesterday, tomorrow) datesched_min=self.now - timedelta( seconds=60*self.minelab) datesched_max=self.now + timedelta( seconds=60*self.minelab) if not (datesched_min <= self.ar_scheduledatetime and self.ar_scheduledatetime <= datesched_max ): if self.now.time() < time(12): self.ar_scheduledatetime=datetime.combine(datesched_min.date(), fascia.emission_time) else: self.ar_scheduledatetime=datetime.combine(datesched_max.date(), fascia.emission_time) self.ar_emission_done=fascia.emission_done yield fascia def get_prologhi(self): prologhi= self.fascia.spot_set.filter(Q(start_date__lte=self.now) | Q(start_date__isnull=True),\ Q(end_date__gte=self.now) | Q(end_date__isnull=True),\ Q(giorni__name__exact=self.giorno) , Q(prologo__exact=True)).order_by('priorita') for prologo in prologhi: logging.debug( 'SPOT: prologo: %s',prologo) yield prologo def count_spots(self): return self.fascia.spot_set.filter(Q(start_date__lte=self.now) | Q(start_date__isnull=True),\ Q(end_date__gte=self.now) | Q(end_date__isnull=True),\ Q(giorni__name__exact=self.giorno)).exclude(prologo__exact=True)\ .exclude(epilogo__exact=True).count() def get_spots(self): spots=self.fascia.spot_set.filter(Q(start_date__lte=self.now) | Q(start_date__isnull=True),\ Q(end_date__gte=self.now) | Q(end_date__isnull=True),\ Q(giorni__name__exact=self.giorno)).exclude(prologo__exact=True)\ .exclude(epilogo__exact=True).order_by('priorita') for spot in spots: logging.debug('SPOT: spot: %s',spot) yield spot def get_epiloghi(self): epiloghi=self.fascia.spot_set.filter(Q(start_date__lte=self.now) | Q(start_date__isnull=True),\ Q(end_date__gte=self.now) | Q(end_date__isnull=True),\ Q(giorni__name__exact=self.giorno) , Q(epilogo__exact=True)).order_by('priorita') for epilogo in epiloghi: logging.debug ('SPOT: epilogo: %s',epilogo) yield epilogo def get_fascia_spots(self): if (self.ar_spots_in_fascia == 0): # I have found an empty fascia return for prologo in self.get_prologhi(): yield prologo for spot in self.get_spots(): yield spot for epilogo in self.get_epiloghi(): yield epilogo def get_fascia_playlist_media(self,genfile=True): playlistname =self.playlistpath+"/"+self.fascia.name+".m3u" if genfile : # os.umask(002) # f = open(playlistname, "w") fd,tmpfile=tempfile.mkstemp() f=os.fdopen(fd,"w") # f = open(tmpfile, "w") # f=tempfile.TemporaryFile() length=0 for spot in self.get_fascia_spots(): filename=spot.file.path # filename=spot.get_file_filename() #print >>f, os.path.basename(filename) logging.debug( "SPOT: include %s", filename) if genfile : # this work if LANG is set #f.write(os.path.basename(filename.encode(sys.getfilesystemencoding()))) f.write(os.path.basename(filename.encode("UTF-8"))) f.write("\n") # calcolo la lunghezza della fascia try: filename=filename.encode("utf8") onelength=mutagen.File(filename).info.length logging.debug("SPOT: computed the partial time length: %d",onelength) length=length+onelength except: logging.error( "SPOT: error establish time length; use an estimation %s", filename) length=length+30 self.ar_length=length logging.debug("SPOT: computed total time length: %d",self.ar_length) if genfile : f.close() os.chmod(tmpfile,0644) shutil.move(tmpfile,playlistname) logging.debug("SPOT: move the playlist %s in %s",tmpfile,playlistname) return playlistname def main(): logging.basicConfig(level=logging.DEBUG,) now=datetime.now() spots=gest_spot(now,minelab,"/tmp/") for fascia in spots.get_fasce(genfile=True): #print "elaborate fascia >>",fascia for spot in spots.get_fascia_spots(): pass #print "fascia and spot ->",spots.fascia,spot print spots.ar_filename print spots.ar_scheduledatetime print spots.ar_length print spots.ar_spots_in_fascia if __name__ == '__main__': main() # (this code was run as script) autoradio-upstream-2.8.2+dfsg.orig/autoradio/autoaudacious.py0000644000175000017500000001322112332264611024670 0ustar capriottcapriott#!/usr/bin/env python # GPL. (C) 2007-2009 Paolo Patruno. import dbus import time import datetime import os # ------- dbus interface --------- import dbus class audacious: def __init__(self,session=0): try: self.bus = dbus.SessionBus() # ----------------------------------------------------------- root_obj = self.bus.get_object("org.mpris.audacious", '/') player_obj = self.bus.get_object("org.mpris.audacious", '/Player') tracklist_obj = self.bus.get_object("org.mpris.audacious", '/TrackList') org_obj = self.bus.get_object("org.mpris.audacious", '/org/atheme/audacious') self.root = dbus.Interface(root_obj, dbus_interface='org.freedesktop.MediaPlayer') self.player = dbus.Interface(player_obj, dbus_interface='org.freedesktop.MediaPlayer') self.tracklist = dbus.Interface(tracklist_obj, dbus_interface='org.freedesktop.MediaPlayer') self.org = dbus.Interface(org_obj, dbus_interface='org.atheme.audacious') # ----------------------------------------------------------- except: raise def __str__(self): return "org.atheme.audacious" def play_ifnot(self): ''' start playng if not. ''' # I check if audacious is playng .... otherside I try to play isplaying= self.org.Playing() if (not isplaying): self.player.Play() def get_playlist_securepos(self,securesec=10): ''' Try to secure that there are some time (securesec) to complete all operations in time: if audacious change song during operation will be a big problem ''' try: self.play_ifnot() #force to play mintimed=datetime.timedelta(seconds=securesec) toend=datetime.timedelta(seconds=0) volte=0 while ( toend < mintimed ): # take the current position pos=self.tracklist.GetCurrentTrack() metadata=self.tracklist.GetMetadata(pos) #print metadata mtimelength=metadata["mtime"] mtimeposition=self.player.PositionGet() timed=datetime.timedelta(seconds=datetime.timedelta(milliseconds=mtimelength).seconds) toend=timed-datetime.timedelta(seconds=datetime.timedelta(milliseconds=mtimeposition).seconds) newpos=self.tracklist.GetCurrentTrack() if (pos != newpos): #inconsistenza: retry toend=datetime.timedelta(seconds=0) if ( toend < mintimed ): volte +=1 if volte > 10 : break # timeout , I have to play time.sleep(securesec+1) return pos except : return None def playlist_clear_up(self,atlast=10): ''' clear playlist starting from current position up. "atlast" numer of song are retained ''' try: self.play_ifnot() #force to play # take the current position (if error set pos=0) pos=self.get_playlist_securepos() if pos is None: return False # delete the old ones if pos > atlast : for prm in xrange(0,pos-atlast): self.tracklist.DelTrack(0) return True except: return False def playlist_clear_down(self,atlast=500): ''' clear playlist starting from current position + atlast doen. "atlast" numer of song are retained for future play ''' try: self.play_ifnot() #force to play # take the current position (if error set pos=0) pos=self.get_playlist_securepos() if pos is None: return False length=self.tracklist.GetLength() #elimino il troppo if length-pos > atlast : for prm in xrange(length,pos+atlast,-1): self.tracklist.DelTrack(prm) return True except: return False def get_playlist_posauto(self,autopath,securesec=10): ''' get playlist position skipping file with path equal to autopath. Try to secure that there are some time (securesec) to complete all operations in time: if xmms change song during operation will be a big problem ''' try: pos=self.get_playlist_securepos(securesec=securesec) if pos is None: return pos pos+=1 metadata=self.tracklist.GetMetadata(pos) try: file=metadata["URI"] except: return pos filepath=os.path.dirname(file) # ora controllo se ci sono gia dei file accodati nella playlist da autoradio # l'unica possibilita di saperlo e verificare il path del file while ( os.path.commonprefix ((filepath,"file://"+autopath)) == "file://"+autopath ): pos+=1 metadata=self.tracklist.GetMetadata(pos) try: file=metadata["URI"] except: return pos filepath=os.path.dirname(file) # here I have found the first file added by autoradio return pos except : return None def get_playlist_pos(self): "get current position" return self.tracklist.GetCurrentTrack() autoradio-upstream-2.8.2+dfsg.orig/autoradio/mkplaylist.py0000755000175000017500000003752712332264611024235 0ustar capriottcapriott#!/usr/bin/env python # -*- coding: utf-8 -*- #----------------------------------------------------------------------------- # Name: mkplaylist.py # Purpose: ReCreate playlists from directory scans or m3u playlist. # # Author: Marc 'BlackJack' Rintsch # Paolo Patruno # Created: 2004-11-09 # Last modified: 2012-08-04 # Copyright: (c) 2004-2009 # Licence: GPL #----------------------------------------------------------------------------- """Make a playlist file. :var factory: instance of a `PlaylistEntryFactory`. :var WRITERS: dictionary that maps playlist format names to functions that write a sequence of `PlaylistEntry` objects in that format to a file. :todo: Check if docstrings and code are still in sync. :todo: Refactor cache code. Introduce a Cache class. Maybe subclassing `PlaylistEntryFactory` with a caching version. Keep in mind that this scales, i.e. implementing an SQLite cache or using AmaroK's db should be considered too. :todo: Find a strategically favourable place to minimise the contents of the meta data dictionaries to the bare minimum to cut down the cache file size. It is not necessary to have the version of the vorbis library in ogg meta data for example. """ import sys import os import os.path import random import logging from itertools import chain import mutagen import urllib2 __author__ = "Marc 'BlackJack' Rintsch " __version__ = '0.6.0' __date__ = '$Date: 2012-11-26 $' __docformat__ = 'reStructuredText' # Disable `pylint` name convention warning for names on module level that # are not ``def``\ed functions and still have no conventional constant names, # i.e. only capital letters. # # pylint: disable-msg=C0103 # # Use logging for ouput at different *levels*. # logging.getLogger().setLevel(logging.INFO) log = logging.getLogger("mkplaylist") handler = logging.StreamHandler(sys.stderr) log.addHandler(handler) #----------------------------------------------------------------------------- # Functions to read meta data from media files. # # now use mutagen # # The functions return a dictionary with the meta data. The minimal # postcondition is an empty dictionary if no information could be # read from the file. # # Keys to use (yes, they are all uppercase!): # # ARTIST, TITLE, TIME (playing time in seconds) # # All other keys are currently not used by any output format. def metadata_reader(path): """Reads info from media file.""" info = dict() if path[:5] == "http:": info['TIME'] = None info['ARTIST'] = "streaming" info['ALBUM'] = "streaming" info['TITLE'] = path else: try: m=mutagen.File(path,easy=True) # in seconds (type float). info['TIME'] = m.info.length for value_name, key in (('artist', 'ARTIST'), ('album', 'ALBUM'), ('title', 'TITLE')): value = m.get(value_name) if value: info[key] = value[0] except: log.info("Could not read info from file: %s ",path) return info #----------------------------------------------------------------------------- class PlaylistEntry: """A generic playlist entry with a `path` attribute and dictionary like behavior for meta data. `PlaylistEntry` objects can be converted to strings and are comparable with their `path` attribute as key. The meta data contains at least the path of the media file. :ivar path: path of the media file. :type path: str :ivar metadata: meta data of the media file. :type metadata: dict of str -> str :invariant: self['path'] is not None """ def __init__(self, path, metadata=None): """Creates a playlist entry. :param path: the path of the media file. :type path: str :param metadata: a dictionary with meta data. :type metadata: dict of str -> str :precondition: The meta data must not contain a key 'path'. :postcondition: The meta data contains the `path` as key. """ self.path = path if metadata is None: metadata = dict() metadata['PATH'] = self.path self.metadata = metadata # TODO: Some black magic to fill the metadata from examining # the file name if the dict is empty. def __getitem__(self, key): return self.metadata.get(key, '') def __setitem__(self, key, value): self.metadata[key] = value if key == 'PATH': self.path = key def __cmp__(self, other): return cmp(self.path, other.path) def __str__(self): return self.path def __unicode__(self): try: return self.path.decode("UTF-8") except: log.info("Could not decode UTF-8: %s ",self.path) return "" class PlaylistEntryFactory: """A media file factory allows registritation of media file types, their file name extensions and functions for reading meta data from the files. This is not a "real" class but more an abuse of a class as a namespace. If the program grows so large that it will become unavoidable to split it into several modules, the contents of this class may be moved to the top level of a module. :ivar types: dictionary that maps a file name extension to a tuple containing a descriptive name of the type and a sequence of functions to read meta data from this file type. :type types: dict :ivar cachefile: pathname of a file to store the pickled data from cached PlaylistEntries :type cachefile: string """ def __init__(self): pass def is_media_file(self, path): """Check file if it is a known media file. The check is based on mutagen file test :param path: filename of the file to check. :type path: str :returns: `True` if known media file, `False` otherwise. :rtype: bool """ try: return not mutagen.File(path) is None except: return False def is_media_url(self, path): """Check file if it is a url. The check is based on the prefix thet will be http: :param path: filename of the file to check. :type path: str :returns: `True` if url, `False` otherwise. :rtype: bool """ return path[:5] in ("http:","file:") def create_entry(self, path): """Reads metadata and returns PlaylistEntry objects. :param path: path to the media file. :type path: str :return: dictionary with meta data. :rtype: dict of str -> str """ playlist_entry = PlaylistEntry(path, metadata_reader(path)) log.debug("(new)") return playlist_entry # # Create a PlaylistEntryFactory instance and populate it with the # known file extensions. # factory = PlaylistEntryFactory() #----------------------------------------------------------------------------- # Playlist writers. def write_m3u(playlist, outfile,timelen=None): """Writes the playlist in m3u format.""" totaltime=0. i=0 for entry in playlist: if not entry['TIME'] is None : totaltime += entry['TIME'] else: if not timelen is None : log.error("error evalutate time length on file: %s ",entry ) continue if totaltime < timelen or timelen is None : print >> outfile, unicode(entry) i+=1 else: break log.info("That's %d out file(s).", i) def write_extm3u(playlist, outfile,timelen=None): """Writes the playlist in extended m3u format.""" totaltime=0. i=0 print >> outfile, '#EXTM3U' for entry in playlist: if not entry['TIME'] is None : totaltime += entry['TIME'] else: if not timelen is None : log.error("error evalutate time length on file: %s",entry ) continue if totaltime < timelen or timelen is None : time=entry['TIME'] if time is None : time = -1 if entry['ARTIST'] and entry['TITLE']: print >> outfile, '#EXTINF:%s,%s - %s' % (time, entry['ARTIST'], entry['TITLE']) print >> outfile, unicode(entry) i+=1 else: break log.info("That's %d out file(s).", i) def write_pls(playlist, outfile,timelen=None): """Write the `playlist` in PLS format. :todo: Guess playlist name from `outfile.name`. :todo: Add command line option for playlist title. """ totaltime=0. print >> outfile, '[playlist]' print >> outfile, 'PlaylistName=Playlist' i = 0 for i, entry in enumerate(playlist): if not entry['TIME'] is None : totaltime += entry['TIME'] else: log.error("evaluate time length on file: %s",entry ) continue if totaltime < timelen or timelen is None : i += 1 print >> outfile, 'File%d=%s' % (i, entry) title = entry['TITLE'] or os.path.basename(str(entry)) print >> outfile, 'Title%d=%s' % (i, title) print >> outfile, 'Length%d=%s' % (i, entry['TIME']) else: break print >> outfile, 'NumberOfEntries=%d' % i print >> outfile, 'Version=2' log.info("That's %d out file(s).", i) WRITERS = { 'm3u': write_m3u, 'extm3u': write_extm3u, 'pls': write_pls } #----------------------------------------------------------------------------- def read_playlist(infile, absolute_paths=True): """Iterates over media files in playlist. Extended M3U directives are skipped. :param infile: filename of playlist :type infile: str :param absolute_paths: converts relative path names to absolute ones if set to `True`. :type absolute_paths: bool :returns: iterator over `PlaylistEntry` objects. :rtype: iterable of `PlaylistEntry` """ root = os.getcwd() if infile == '-': finfile = sys.stdin else: finfile = file(infile, "r") root = os.path.join(root , os.path.dirname(infile)) log.debug("root dir: %s", root) for filename in finfile.read().strip().split("\n"): log.debug("entry: %s", filename) if len(filename) == 0 or filename[0] == "#": # skip empty and comment lines log.debug("ignore %s", filename) continue # convert thinks like %20 in file name filename=urllib2.url2pathname(filename) if filename[:7] == "file://" : filename=filename[7:] if factory.is_media_file(filename): log.debug("found %s", filename) if filename[0] != "/" : full_name = os.path.join(root, filename) else: full_name=filename log.debug("full_name: %s", full_name) if not os.path.isfile(full_name): log.info("ignore %s do not exist", full_name) continue if absolute_paths: full_name = os.path.abspath(full_name) else: l=len(os.path.commonprefix((root,full_name)).rpartition("/")[0]) if l>0 : full_name=full_name[l+1:] log.debug("post full_name: %s", full_name) yield factory.create_entry(full_name) elif factory.is_media_url(filename): yield factory.create_entry(filename) else: log.info("ignore %s", filename) finfile.close() def search(path, absolute_paths=True): """Iterates over media files in `path` (recursivly). Symlinked directories are skipped to avoid endless scanning due to possible cycles in directory structure. :param path: root of the directory tree to search. :type path: str :param absolute_paths: converts relative path names to absolute ones if set to `True`. :type absolute_paths: bool :returns: iterator over `PlaylistEntry` objects. :rtype: iterable of `PlaylistEntry` """ for (root, dummy, filenames) in os.walk(path): log.info("Scanning %s...", root) # TODO: Check if there are linked directories. for filename in filenames: full_name = os.path.join(root, filename) if factory.is_media_file(full_name): log.debug("found %s", filename) if absolute_paths: full_name = os.path.abspath(full_name) yield factory.create_entry(full_name) def main(): """Main function.""" import codecs from optparse import OptionParser usage = ("usage: %prog [-h|--help|--version]\n" " %prog [options] directory [directory ...]") parser = OptionParser(usage=usage, version="%prog " + __version__) parser.add_option("-i", "--input", type="string", dest="infile", default='-', help="name of the m3u playlist input file or '-' for stdout (default)") parser.add_option("-t", "--timelen", type="float", dest="timelen", default=None, help="Len of Playlist in seconds (default= infinite)") parser.add_option("-o", "--output", type="string", dest="outfile", default='-', help="name of the output file or '-' for stdout (default)") parser.add_option("-f", "--output-format", type="choice", dest="output_format", default="extm3u", choices=WRITERS.keys(), help="format of the output %r (default: %%default)" % WRITERS.keys()) parser.add_option("-r", "--relative-paths", action="store_true", dest="relative_paths", default=False, help="write relative paths. (default: absolute paths)") parser.add_option("--shuffle", action="store_true", default=False, help="shuffle the playlist before saving it.") parser.add_option("--sort", action="store_true", default=False, help="sort the playlist before saving it.") parser.add_option("-v", "--verbose", action="store_true", default=False, dest="verbose", help="be more verbose.") parser.add_option("-q", "--quiet", action="store_true", default=False, dest="quiet", help="be really quiet.") (options, args) = parser.parse_args() # if len(args) == 0: # parser.error("wrong number of arguments") write_playlist = WRITERS[options.output_format] if options.quiet: log.setLevel(logging.WARNING) if options.verbose: log.setLevel(logging.DEBUG) # # Input. # if len(args) == 0: log.info("Read %s file.", options.infile) media_files=list(read_playlist(options.infile, not options.relative_paths)) else: media_files = list(chain(*[search(path, not options.relative_paths) for path in args])) # # Processing. # if options.shuffle: random.shuffle(media_files) elif options.sort: media_files.sort() # # Output. # outfile = sys.stdout if options.outfile != '-': #outfile = file(options.outfile, "w") outfile = codecs.open(options.outfile, "w", encoding="UTF-8") write_playlist(media_files, outfile,options.timelen) outfile.close() log.info("That's %d good input file(s).", len(media_files)) if __name__ == '__main__': main() autoradio-upstream-2.8.2+dfsg.orig/autoradio/xmmsweb.py0000755000175000017500000001237112332264611023514 0ustar capriottcapriott#!/usr/bin/env python # coding=utf-8 """ Show xmms playlist on a simple web server. """ session=0 # sessione di xmms maxplele=100 # massimo numero di elementi della playlist iht=False # emetti header e tail port=8888 # port for server #try: # import sys,glob # from distutils.sysconfig import get_python_lib # compatCherryPyPath = glob.glob( get_python_lib()+"/CherryPy-2.*").pop() # sys.path.insert(0, compatCherryPyPath) #finally: import cherrypy cpversion3=cherrypy.__version__.startswith("3") import xmms import datetime head=''' XMMS monitor | ''' tail=''' ''' class HomePage: # def Main(self): # # Let's link to another method here. # htmlresponse='Goto xmms status for autoradio!
' # htmlresponse+='Goto xmms playlist for autoradio!
' # return htmlresponse # Main.exposed = True def test(self): "return test page" return "Test Page" test.exposed = True def status(self): "return xmms status" ok=xmms.control.is_playing(0) if ok: return "xmms is playing" else: return "xmms is stopped" status.exposed = True def index(self): "return xmms playlist" if (iht) : htmlresponse=head else: htmlresponse="" try: cpos=xmms.control.get_playlist_pos(session) ok=True except: return "error xmms.control.get_playlist_pos" try: isplaying= xmms.control.is_playing(session) except: return "error xmms.control.is_playing" try: len=xmms.control.get_playlist_length(session) htmlresponse+='

xmms ha %i brani in playlist // selezionato brano numero %i

' % (len,cpos+1) htmlresponse+='' htmlresponse+='' for pos in xrange(0,min(len,maxplele)): htmlresponse+='' file=xmms.control.get_playlist_file(pos, session) title=xmms.control.get_playlist_title(pos, session) time=datetime.timedelta(seconds=datetime.timedelta(milliseconds=xmms.control.get_playlist_time(pos, session)).seconds) if pos == cpos and isplaying: col="#FF0000" toend=time-datetime.timedelta(seconds=datetime.timedelta(milliseconds=xmms.control.get_output_time(session)).seconds) elif pos < cpos : col="#0000FF" toend=None else: col="#00FF00" toend=None htmlresponse+='' % (col,pos+1,str(time),str(toend),file,title) htmlresponse+='' except: htmlresponse+='error xmms.control.get_playlist_length' htmlresponse+='
posizioneduratabrano
%i %s // %s %s
' if len > maxplele : htmlresponse+="

ATTENZIONE: ci sono molti elementi nella playlist e gli ultimi non sono visualizzati

" if (iht) : htmlresponse+=tail return htmlresponse index.exposed = True def start_http_server(): #import os #pid = os.fork() settings = { 'global': { 'server.socket_port' : port, 'server.socket_host': "", 'server.socket_file': "", 'server.socket_queue_size': 5, 'server.protocol_version': "HTTP/1.0", 'server.log_to_screen': False, 'server.log_file': "/tmp/xmmsweb.log", 'server.reverse_dns': False, 'server.thread_pool': 10, # 'server.environment': "development" 'server.environment': "production" }, } # CherryPy always starts with cherrypy.root when trying to map request URIs # to objects, so we need to mount a request handler object here. A request # to '/' will be mapped to cherrypy.root.index(). if (cpversion3): cherrypy.quickstart(HomePage(),config=settings) else: cherrypy.config.update(settings) cherrypy.root = HomePage() cherrypy.server.start() if __name__ == '__main__': # Set the signal handler #import signal #signal.signal(signal.SIGINT, signal.SIG_IGN) # Start the CherryPy server. start_http_server() autoradio-upstream-2.8.2+dfsg.orig/autoradio/autoplayer/0000755000175000017500000000000012332265077023645 5ustar capriottcapriottautoradio-upstream-2.8.2+dfsg.orig/autoradio/autoplayer/player.py0000644000175000017500000007754612332264611025527 0ustar capriottcapriott#!/usr/bin/env python # -*- coding: utf-8 -*- # GPL. (C) 2013 Paolo Patruno. # Authors: Paolo Patruno # Based on : # mpDris2 from Jean-Philippe Braun , # Mantas Mikulėnas # mpDris from: Erik Karlsson # Some bits taken from quodlibet mpris plugin by #TODO # manage signal # Interface MediaPlayer2.Player # Signals # Seeked (x: Position) # # Interface MediaPlayer2.TrackList # Signals # TrackListReplaced (ao: Tracks, o: CurrentTrack) # TrackAdded (a{sv}: Metadata, o: AfterTrack) # TrackRemoved (o: TrackId) # TrackMetadataChanged (o: TrackId, a{sv}: Metadata) import sys, time, thread #import pygst #pygst.require("0.10") #import gobject #import gst import gi gi.require_version('Gst', '1.0') from gi.repository import GObject, Gst import playlist import dbus import dbus.service import dbus.mainloop.glib import logging import signal IDENTITY = "Auto Player" STATUS_PLAYLIST="autoplayer.xspf" # python dbus bindings don't include annotations and properties MPRIS2_INTROSPECTION = """ """ PLAYER_IFACE="org.mpris.MediaPlayer2.Player" TRACKLIST_IFACE="org.mpris.MediaPlayer2.TrackList" IFACE="org.mpris.MediaPlayer2" class NotSupportedException(dbus.DBusException): _dbus_error_name = 'org.mpris.MediaPlayer2.Player.NotSupported' class AutoPlayer(dbus.service.Object): ''' The base object of an MPRIS player ''' __name = "org.mpris.MediaPlayer2.AutoPlayer" __path = "/org/mpris/MediaPlayer2" __introspect_interface = "org.freedesktop.DBus.Introspectable" __prop_interface = dbus.PROPERTIES_IFACE def __init__(self,busaddress=None): if busaddress is None: self._bus = dbus.SessionBus() else: self._bus =dbus.bus.BusConnection(busaddress) dbus.service.Object.__init__(self, self._bus, AutoPlayer.__path) self._uname = self._bus.get_unique_name() self._dbus_obj = self._bus.get_object("org.freedesktop.DBus", "/org/freedesktop/DBus") self._dbus_obj.connect_to_signal("NameOwnerChanged", self._name_owner_changed_callback, arg0=self.__name) self.acquire_name() def _name_owner_changed_callback(self, name, old_owner, new_owner): if name == self.__name and old_owner == self._uname and new_owner != "": try: pid = self._dbus_obj.GetConnectionUnixProcessID(new_owner) except: pid = None logging.info("Replaced by %s (PID %s)" % (new_owner, pid or "unknown")) self.player.loop.quit() def acquire_name(self): self._bus_name = dbus.service.BusName(AutoPlayer.__name, bus=self._bus, allow_replacement=True, replace_existing=True) def release_name(self): if hasattr(self, "_bus_name"): del self._bus_name def __PlaybackStatus(self): return self.player.playmode def __Metadata(self): meta=self.GetTracksMetadata((self.player.playlist.current,)) if len(meta) > 0: return dbus.Dictionary(meta[0], signature='sv') else: return dbus.Dictionary({}, signature='sv') #return {"mpris:trackid":self.player.playlist.current,} def __Position(self): position = self.player.position() if position is None: return dbus.Int64(0) else: return dbus.Int64(position) def __CanPlay(self): if self.player.playlist.current is None : return False else: return True def __Tracks(self): tracks=dbus.Array([], signature='s') for track in self.player.playlist: tracks.append(track) return tracks __root_interface = IFACE __root_props = { "CanQuit": (True, None), "CanRaise": (False, None), "DesktopEntry": ("AutoPlayer", None), "HasTrackList": (True, None), "Identity": (IDENTITY, None), "SupportedUriSchemes": (dbus.Array(signature="s"), None), "SupportedMimeTypes": (dbus.Array(signature="s"), None), "CanSetFullscreen": (False, None), } __player_interface = PLAYER_IFACE __player_props = { "PlaybackStatus": (__PlaybackStatus, None), "LoopStatus": (False, None), "Rate": (1.0, None), "Shuffle": (False, None), "Metadata": (__Metadata, None), "Volume": (1.0, None), "Position": (__Position, None), "MinimumRate": (1.0, None), "MaximumRate": (1.0, None), "CanGoNext": (True, None), "CanGoPrevious": (True, None), "CanPlay": (__CanPlay, None), "CanPause": (True, None), "CanSeek": (True, None), "CanControl": (True, None), } __tracklist_interface = TRACKLIST_IFACE __tracklist_props = { "CanEditTracks": (True, None), "Tracks": (__Tracks, None), } __prop_mapping = { __player_interface: __player_props, __root_interface: __root_props, __tracklist_interface: __tracklist_props, } @dbus.service.method(__introspect_interface) def Introspect(self): return MPRIS2_INTROSPECTION @dbus.service.signal(__prop_interface, signature="sa{sv}as") def PropertiesChanged(self, interface, changed_properties, invalidated_properties): pass @dbus.service.method(__prop_interface, in_signature="ss", out_signature="v") def Get(self, interface, prop): getter, setter = self.__prop_mapping[interface][prop] if callable(getter): return getter(self) return getter @dbus.service.method(__prop_interface, in_signature="ssv", out_signature="") def Set(self, interface, prop, value): getter, setter = self.__prop_mapping[interface][prop] if setter is not None: setter(self,value) @dbus.service.method(__prop_interface, in_signature="s", out_signature="a{sv}") def GetAll(self, interface): read_props = {} props = self.__prop_mapping[interface] for key, (getter, setter) in props.iteritems(): if callable(getter): getter = getter(self) read_props[key] = getter return read_props def update_property(self, interface, prop): getter, setter = self.__prop_mapping[interface][prop] if callable(getter): value = getter(self) else: value = getter logging.debug('Updated property: %s = %s' % (prop, value)) self.PropertiesChanged(interface, {prop: value}, []) return value def attach_player(self,player): self.player=player @dbus.service.signal(PLAYER_IFACE,signature='x') def Seeked(self, position): logging.debug("Seeked to %i" % position) return float(position) # TrackAdded (a{sv}: Metadata, o: AfterTrack) @dbus.service.signal(TRACKLIST_IFACE,signature='a{sv}o') def TrackAdded(self, metadata,aftertrack): logging.debug("TrackAdded to %s" % aftertrack) pass # TrackRemoved (o: TrackId) @dbus.service.signal(TRACKLIST_IFACE,signature='o') def TrackRemoved(self,trackid): logging.debug("TrackRemoved %s" % trackid) # here seem pydbus bug # disabled for now #process 22558: arguments to dbus_message_iter_append_basic() were incorrect, assertion "_dbus_check_is_valid_path (*string_p)" failed in file dbus-message.c line 2531. #This is normally a bug in some application using the D-Bus library. # D-Bus not built with -rdynamic so unable to print a backtrace #Annullato (core dumped) try: obp=dbus.ObjectPath("/org/mpris/MediaPlayer2/TrackList/"+trackid) except: logging.error("building ObjectPath to return in TrackRemoved %s" % trackid) obp=dbus.ObjectPath("/org/mpris/MediaPlayer2/TrackList/NoTrack") return obp @dbus.service.method(IFACE) def Raise(self): pass @dbus.service.method(IFACE) def Quit(self): self.player.exit() self.release_name() @dbus.service.method(PLAYER_IFACE) def Next(self): self.player.next() @dbus.service.method(PLAYER_IFACE) def Previous(self): self.player.previous() @dbus.service.method(PLAYER_IFACE) def Pause(self): self.player.pause() @dbus.service.method(PLAYER_IFACE) def PlayPause(self): self.player.playpause() @dbus.service.method(PLAYER_IFACE) def Stop(self): self.player.stop() @dbus.service.method(PLAYER_IFACE) def Play(self): logging.info( "Play") self.player.loaduri() self.player.play() @dbus.service.method(PLAYER_IFACE,in_signature='x') def Seek(self,offset): position=self.player.seek(offset) if position is not None: self.Seeked(position) @dbus.service.method(PLAYER_IFACE,in_signature='sx') def SetPosition(self,trackid,position): self.player.setposition(trackid,position) self.Seeked(position) @dbus.service.method(PLAYER_IFACE,in_signature='s') def OpenUri(self,uri): self.player.addtrack(uri,setascurrent=True) self.Stop() self.Play() #TODO #self.TrackAdded() #self.update_property(TRACKLIST_IFACE,'TrackListReplaced') # If the media player implements the TrackList interface, then the opened # track should be made part of the tracklist, the # org.mpris.MediaPlayer2.TrackList.TrackAdded # or # org.mpris.MediaPlayer2.TrackList.TrackListReplaced # signal should be fired, as well as the # org.freedesktop.DBus.Properties.PropertiesChanged # signal on the tracklist interface. #tracklist @dbus.service.method(TRACKLIST_IFACE,in_signature='ssb', out_signature='') def AddTrack(self,uri, aftertrack, setascurrent): self.player.addtrack(uri, aftertrack, setascurrent) @dbus.service.method(TRACKLIST_IFACE,in_signature='s', out_signature='') def RemoveTrack(self, trackid): if self.player.playlist.current == trackid: self.Next() self.player.removetrack(trackid) #disable for a bug in pydbus ?? #self.TrackRemoved(trackid) @dbus.service.method(TRACKLIST_IFACE,in_signature='s', out_signature='') def GoTo(self, trackid): self.player.goto(trackid) @dbus.service.method(TRACKLIST_IFACE,in_signature='as', out_signature='aa{sv}') def GetTracksMetadata(self,trackids): metadata=dbus.Array([], signature='aa{sv}') for id in trackids: if id is not None: meta={} for key,attr in ("mpris:trackid","id"),("mpris:length","time"),("xesam:title","title"),("xesam:artist","artist"),("xesam:url","path"): myattr= getattr(self.player.playlist[id],attr,None) if myattr is not None: if key == "mpris:length": myattr=long(round(myattr/1000.)) meta[key]=myattr metadata.append(dbus.Dictionary(meta, signature='sv')) return metadata def updateinfo(self): if self.player.statuschanged: self.update_property(PLAYER_IFACE,"PlaybackStatus") self.player.statuschanged=False self.update_property(PLAYER_IFACE,"Position") return True # Handle signals more gracefully def handle_sigint(self,signum, frame): logging.debug('Caught SIGINT, exiting.') self.Quit() class Player: def __init__(self,myplaylist=None,loop=None,starttoplay=False,myaudiosink=None): self.playlist=myplaylist #self.player = gst.element_factory_make("playbin2", "playbin2") Gst.init(None) self.player = Gst.ElementFactory.make("playbin", None) self.playmode = "Stopped" self.recoverplaymode = "Stopped" self.statuschanged = False self.starttoplay=starttoplay self.loop=loop if self.player is None: logging.error( "creating player") raise Exception("cannot create player!") #fakesink = gst.element_factory_make("fakesink", "fakesink") fakesink = Gst.ElementFactory.make("fakesink", None) self.player.set_property("video-sink", fakesink) ##icecast #print "Icecast selected" #bin = gst.Bin("my-bin") #audioconvert = gst.element_factory_make("audioconvert") #bin.add(audioconvert) #pad = audioconvert.get_pad("sink") #ghostpad = gst.GhostPad("sink", pad) #bin.add_pad(ghostpad) #audioresample = gst.element_factory_make("audioresample") #audioresample.set_property("quality", 0) #bin.add(audioresample) #capsfilter = gst.element_factory_make('capsfilter') #capsfilter.set_property('caps', gst.caps_from_string('audio/x-raw,rate=44100,channels=2')) ##bin.add(capsfilter) #vorbisenc = gst.element_factory_make("vorbisenc") #vorbisenc.set_property("quality", 0) #bin.add(vorbisenc) #oggmux = gst.element_factory_make("oggmux") #bin.add(oggmux) #streamsink = gst.element_factory_make("shout2send", "streamsink") #streamsink.set_property("ip", "localhost") ##streamsink.set_property("username", "source") #streamsink.set_property("password", "ackme") #streamsink.set_property("port", 8000) #streamsink.set_property("mount", "/myradio.ogg") #bin.add(streamsink) ### Link the elements #queue = gst.element_factory_make("queue", "queue") ##queue.link(audioresample, capsfilter) #bin.add(queue) #gst.element_link_many(audioconvert,audioresample,queue,vorbisenc,oggmux,streamsink) #self.player.set_property("audio-sink", bin) #audiosink = gst.element_factory_make("autoaudiosink") #audiosink = gst.element_factory_make("jackaudiosink") if myaudiosink is None: myaudiosink = "autoaudiosink" audiosink = Gst.ElementFactory.make(myaudiosink,None) self.player.set_property("audio-sink", audiosink) # # self.player.set_property("audio-sink", streamsink) bus = self.player.get_bus() bus.add_signal_watch() # bus.connect("message", self.on_message) bus.connect('message::eos', self.on_message_eos) bus.connect('message::error', self.on_message_error) bus.connect("message::state-changed", self.on_message_state_changed) # def on_message(self,bus, message): # logging.debug('gst-bus: %s' % str(message)) # # log all error messages # if message.type == gst.MESSAGE_ERROR: # error, debug = map(str, message.parse_error()) # logging.error('gstreamer_autoplayer: %s'%error) # logging.debug('gstreamer_autoplayer: %s'%debug) def on_message_eos(self, bus, message): t = message.type logging.debug("Message type %s received; source %s" % (t,type(message.src))) logging.info( "fine file") #self.player.set_state(Gst.State.NULL) #self.playmode = "Stopped" #self.statuschanged = True self.next() def on_message_error(self, bus, message): t = message.type logging.debug("Message type %s received; source %s" % (t,type(message.src))) self.player.set_state(Gst.State.NULL) err, debug = message.parse_error() logging.error( " %s: %s " % (err, debug)) logging.warning("restart to play after an ERROR skipping current media") self.playmode= self.recoverplaymode self.next() # if err.domain == gst.RESOURCE_ERROR : # logging.warning("restart to play after an RESOURCE_ERROR") # self.playmode= self.recoverplaymode # self.next() # else: # logging.warning("stop to play after an ERROR") # self.stop() # self.playmode = "Stopped" # self.statuschanged = True def on_message_state_changed(self, bus, message): t = message.type logging.debug("Message type %s received; source %s" % (t,type(message.src))) #if isinstance(message.src, gst.Pipeline): if isinstance(message.src, Gst.Pipeline): old_state, new_state, pending_state = message.parse_state_changed() # Gst.State.NULL the NULL state or initial state of an element # Gst.State.PAUSED the element is PAUSED # Gst.State.PLAYING the element is PLAYING # Gst.State.READY the element is ready to go to PAUSED # Gst.State.VOID_PENDING no pending state if pending_state == Gst.State.VOID_PENDING: logging.debug("Pipeline state changed from %s to %s. Pendig: %s"% (Gst.Element.state_get_name(old_state), Gst.Element.state_get_name (new_state), Gst.Element.state_get_name (pending_state))) if new_state == Gst.State.READY : self.playmode = "Stopped" self.statuschanged = True elif new_state == Gst.State.PAUSED: self.playmode = "Paused" self.statuschanged = True elif new_state == Gst.State.PLAYING : self.playmode = "Playing" self.statuschanged = True def next(self): logging.info( "next") self.playlist.next() if self.playlist.current is None: logging.info( "end playlist") self.stop() else: playmode=self.playmode self.stop() self.loaduri() if playmode == "Playing": self.play() elif playmode == "Paused": self.pause() def previous(self): logging.info( "previous") self.playlist.previous() if self.playlist.current is None: logging.info( "head playlist") self.stop() else: playmode=self.playmode self.stop() self.loaduri() if playmode == "Playing": self.play() elif playmode == "Paused": self.pause() def convert_ns(self, t): s,ns = divmod(t, 1000000000) m,s = divmod(s, 60) if m < 60: return "%02i:%02i" %(m,s) else: h,m = divmod(m, 60) return "%i:%02i:%02i" %(h,m,s) def seek(self,t): """ t in microseconds """ logging.info("seek") try: pos_int = self.player.query_position(Gst.Format.TIME)[1] pos_int =pos_int/1000 + t logging.info("seek %s" % str(pos_int)) self.setposition(self.playlist.current,pos_int) return pos_int except: logging.error( "in seek") return None def setposition(self,trackid,t): """ t in microseconds """ if trackid != self.playlist.current: logging.warning( "setposition trackid is not current trackid") try: logging.info("set position") pos_int = self.player.query_duration(Gst.Format.TIME)[1] tnano=t*1000 if tnano >= 0 and tnano <= pos_int : logging.debug("set position to: %s; len: %s" % (str(t),str(pos_int))) #if wait: self.playbin.get_state(timeout=50*gst.MSECOND) event = Gst.Event.new_seek(1.0, Gst.Format.TIME, Gst.SeekFlags.FLUSH|Gst.SeekFlags.ACCURATE, Gst.SeekType.SET, tnano, Gst.SeekType.NONE, 0) res = self.player.send_event(event) if res: #self.player.set_new_stream_time(0L) self.player.set_start_time(0L) #if wait: self.playbin.get_state(timeout=50*gst.MSECOND) # this cause a doble seek with playbin2 #self.player.seek_simple(Gst.Format.TIME, Gst.SeekFlags.FLUSH, t) except: logging.error( "in setposition") def loaduri(self): logging.info( "loaduri") if self.playlist.current is None: if len(self.playlist.keys()) > 0: self.playlist.set_current(self.playlist.keys()[0]) uri = self.playlist.get_current().path if uri is not None: self.player.set_property("uri", uri) ret = self.player.set_state(Gst.State.READY) #if ret == Gst.State.CHANGE_FAILURE: if ret == Gst.StateChangeReturn.FAILURE: logging.error( "Unable to set the pipeline to the READY state.") def play(self): logging.info( "play") ret = self.player.set_state(Gst.State.PLAYING) #if ret == Gst.State.CHANGE_FAILURE: if ret == Gst.StateChangeReturn.FAILURE: logging.error( "Unable to set the pipeline to the PLAYING state.") self.recoverplaymode = "Playing" #else: # print self.player.get_state(timeout=gst.CLOCK_TIME_NONE) def pause(self): logging.info( "pause") ret = self.player.set_state(Gst.State.PAUSED) #if ret == Gst.State.CHANGE_FAILURE: if ret == Gst.StateChangeReturn.FAILURE: logging.error( "Unable to set the pipeline to the PAUSED state.") self.recoverplaymode = "Paused" #else: # print self.player.get_state(timeout=gst.CLOCK_TIME_NONE) def playpause(self): if self.playmode == "Playing": self.pause() elif self.playmode == "Stopped": self.loaduri() self.play() elif self.playmode == "Paused": self.play() def stop(self): logging.info( "stop") #self.loaduri() ret = self.player.set_state(Gst.State.READY) #if ret == Gst.State.CHANGE_FAILURE: if ret == Gst.StateChangeReturn.FAILURE: logging.error( "Unable to set the pipeline to the READY state.") self.recoverplaymode = "Stopped" #else: # print self.player.get_state(timeout=gst.CLOCK_TIME_NONE) def position(self): """ return microseconds """ try: pos_int = self.player.query_position(Gst.Format.TIME)[1] # this should be better but how have we to do in gstreamer 1 ? #except(Gst.QueryError): except Exception as e: logging.warning( "in query_position:"+str(e) ) return None return int(round(pos_int/1000.)) def printinfo(self): try: pos_int = self.player.query_position(Gst.Format.TIME)[1] dur_int = self.player.query_duration(Gst.Format.TIME)[1] # if dur_int == -1: # print "bho" print self.playmode,self.convert_ns(pos_int)+"//"+self.convert_ns(dur_int) except(Gst.QueryError): #print "error printinfo" pass return True def save_playlist(self,path): position=self.position() if position is None: self.playlist.position=position else: self.playlist.position=self.position()*1000 try: self.playlist.write(path) except: logging.error( "error saving playlist") raise logging.info ( "playlist saved %s" % path) return True def initialize(self): self.loaduri() self.pause() return False def recoverstatus(self): if self.playmode != "Paused": logging.info ( "wait for player going paused: %s" % self.playmode) return True time.sleep(1) logging.info ( "recover last status from disk: position %s" % self.playlist.position) if self.playlist.position is not None: logging.info ( "set current %s and position %s " % (self.playlist.current,int(round(self.playlist.position/1000.)))) self.setposition(self.playlist.current,int(round(self.playlist.position/1000.))) if self.starttoplay: time.sleep(1) self.play() return False def addtrack(self,uri, aftertrack=None, setascurrent=False): if aftertrack == "/org/mpris/MediaPlayer2/TrackList/NoTrack": aftertrack=None current = self.playlist.current self.playlist=self.playlist.addtrack(uri,aftertrack,setascurrent) if setascurrent: playmode=self.playmode if self.playlist.current != current: self.stop() self.loaduri() if playmode == "Playing": self.play() elif playmode == "Paused": self.pause() def removetrack(self,trackid): self.playlist=self.playlist.removetrack(trackid) #print "indice: ",str(self.playlist.keys().index(trackid)) #for id,track in enumerate(self.playlist): # print id,track def goto(self,trackid): self.playlist.set_current(trackid) self.stop() self.loaduri() self.play() def exit(self): logging.info("save playlist: %s" % STATUS_PLAYLIST ) self.save_playlist(STATUS_PLAYLIST) self.stop() self.loop.quit() def main(busaddress=None,myaudiosink=None): # Use logging for ouput at different *levels*. # logging.getLogger().setLevel(logging.INFO) log = logging.getLogger("autoplayer") handler = logging.StreamHandler(sys.stderr) log.addHandler(handler) # logging.basicConfig(level=logging.INFO,) # try: # os.chdir(cwd) # except: # pass pl=playlist.Playlist() pl.read("autoplayer.xspf") #plmpris=playlist.Playlist_mpris2(pl,pl.current,pl.position) plmpris=playlist.Playlist_mpris2(pl) if len(sys.argv) >= 2: #if you come from autoplayerd argv[1] is run/start/stop ... for media in sys.argv[2:]: logging.info( "add media: %s" %media) # mmm here seems not work ... the new plmpris is not good !!! plmpris=plmpris.addtrack(media,setascurrent=True) try: dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) #loop = gobject.MainLoop() loop=GObject.MainLoop() mp = Player(plmpris,loop=loop,starttoplay=True,myaudiosink=myaudiosink) # Export our DBUS service #if not dbus_service: #dbus_service = MPRIS2Interface() #else: # Add our service to the session bus # dbus_service.acquire_name() ap = AutoPlayer(busaddress=busaddress) ap.attach_player(mp) #gobject.timeout_add( 100,ap.player.initialize) #gobject.timeout_add( 200,ap.player.recoverstatus) #gobject.timeout_add( 500,ap.updateinfo) #gobject.timeout_add(60000,ap.player.save_playlist,"autoplayer.xspf") ##gobject.timeout_add( 1000,ap.player.printinfo) GObject.timeout_add( 100,ap.player.initialize) GObject.timeout_add( 200,ap.player.recoverstatus) GObject.timeout_add( 500,ap.updateinfo) GObject.timeout_add(60000,ap.player.save_playlist,"autoplayer.xspf") #GObject.timeout_add( 1000,ap.player.printinfo) signal.signal(signal.SIGINT, ap.handle_sigint) loop.run() # Clean up logging.debug('Exiting') except KeyboardInterrupt : # Clean up logging.debug('Keyboard Exiting') ap.Quit() # thread.start_new_thread(mp.loop, ()) # object.threads_init() # context = loop.get_context() # gobject.MainLoop().run() # while True: # context.iteration(True) if __name__ == '__main__': main()# (this code was run as script) autoradio-upstream-2.8.2+dfsg.orig/autoradio/autoplayer/__init__.py0000644000175000017500000000000112332264611025736 0ustar capriottcapriott autoradio-upstream-2.8.2+dfsg.orig/autoradio/autoplayer/player_gstreamer0.py0000644000175000017500000007570312332264611027651 0ustar capriottcapriott#!/usr/bin/env python # -*- coding: utf-8 -*- # GPL. (C) 2013 Paolo Patruno. # Authors: Paolo Patruno # Based on : # mpDris2 from Jean-Philippe Braun , # Mantas Mikulėnas # mpDris from: Erik Karlsson # Some bits taken from quodlibet mpris plugin by #TODO # manage signal # Interface MediaPlayer2.Player # Signals # Seeked (x: Position) # # Interface MediaPlayer2.TrackList # Signals # TrackListReplaced (ao: Tracks, o: CurrentTrack) # TrackAdded (a{sv}: Metadata, o: AfterTrack) # TrackRemoved (o: TrackId) # TrackMetadataChanged (o: TrackId, a{sv}: Metadata) import sys, time, thread import gobject import pygst pygst.require("0.10") import gst import playlist import dbus import dbus.service import dbus.mainloop.glib import logging import signal IDENTITY = "Auto Player" STATUS_PLAYLIST="autoplayer.xspf" # python dbus bindings don't include annotations and properties MPRIS2_INTROSPECTION = """ """ PLAYER_IFACE="org.mpris.MediaPlayer2.Player" TRACKLIST_IFACE="org.mpris.MediaPlayer2.TrackList" IFACE="org.mpris.MediaPlayer2" class NotSupportedException(dbus.DBusException): _dbus_error_name = 'org.mpris.MediaPlayer2.Player.NotSupported' class AutoPlayer(dbus.service.Object): ''' The base object of an MPRIS player ''' __name = "org.mpris.MediaPlayer2.AutoPlayer" __path = "/org/mpris/MediaPlayer2" __introspect_interface = "org.freedesktop.DBus.Introspectable" __prop_interface = dbus.PROPERTIES_IFACE def __init__(self,busaddress=None): if busaddress is None: self._bus = dbus.SessionBus() else: self._bus =dbus.bus.BusConnection(busaddress) dbus.service.Object.__init__(self, self._bus, AutoPlayer.__path) self._uname = self._bus.get_unique_name() self._dbus_obj = self._bus.get_object("org.freedesktop.DBus", "/org/freedesktop/DBus") self._dbus_obj.connect_to_signal("NameOwnerChanged", self._name_owner_changed_callback, arg0=self.__name) self.acquire_name() def _name_owner_changed_callback(self, name, old_owner, new_owner): if name == self.__name and old_owner == self._uname and new_owner != "": try: pid = self._dbus_obj.GetConnectionUnixProcessID(new_owner) except: pid = None logging.info("Replaced by %s (PID %s)" % (new_owner, pid or "unknown")) self.player.loop.quit() def acquire_name(self): self._bus_name = dbus.service.BusName(AutoPlayer.__name, bus=self._bus, allow_replacement=True, replace_existing=True) def release_name(self): if hasattr(self, "_bus_name"): del self._bus_name def __PlaybackStatus(self): return self.player.playmode def __Metadata(self): meta=self.GetTracksMetadata((self.player.playlist.current,)) if len(meta) > 0: return dbus.Dictionary(meta[0], signature='sv') else: return dbus.Dictionary({}, signature='sv') #return {"mpris:trackid":self.player.playlist.current,} def __Position(self): position = self.player.position() if position is None: return dbus.Int64(0) else: return dbus.Int64(position) def __CanPlay(self): if self.player.playlist.current is None : return False else: return True def __Tracks(self): tracks=dbus.Array([], signature='s') for track in self.player.playlist: tracks.append(track) return tracks __root_interface = IFACE __root_props = { "CanQuit": (True, None), "CanRaise": (False, None), "DesktopEntry": ("AutoPlayer", None), "HasTrackList": (True, None), "Identity": (IDENTITY, None), "SupportedUriSchemes": (dbus.Array(signature="s"), None), "SupportedMimeTypes": (dbus.Array(signature="s"), None), "CanSetFullscreen": (False, None), } __player_interface = PLAYER_IFACE __player_props = { "PlaybackStatus": (__PlaybackStatus, None), "LoopStatus": (False, None), "Rate": (1.0, None), "Shuffle": (False, None), "Metadata": (__Metadata, None), "Volume": (1.0, None), "Position": (__Position, None), "MinimumRate": (1.0, None), "MaximumRate": (1.0, None), "CanGoNext": (True, None), "CanGoPrevious": (True, None), "CanPlay": (__CanPlay, None), "CanPause": (True, None), "CanSeek": (True, None), "CanControl": (True, None), } __tracklist_interface = TRACKLIST_IFACE __tracklist_props = { "CanEditTracks": (True, None), "Tracks": (__Tracks, None), } __prop_mapping = { __player_interface: __player_props, __root_interface: __root_props, __tracklist_interface: __tracklist_props, } @dbus.service.method(__introspect_interface) def Introspect(self): return MPRIS2_INTROSPECTION @dbus.service.signal(__prop_interface, signature="sa{sv}as") def PropertiesChanged(self, interface, changed_properties, invalidated_properties): pass @dbus.service.method(__prop_interface, in_signature="ss", out_signature="v") def Get(self, interface, prop): getter, setter = self.__prop_mapping[interface][prop] if callable(getter): return getter(self) return getter @dbus.service.method(__prop_interface, in_signature="ssv", out_signature="") def Set(self, interface, prop, value): getter, setter = self.__prop_mapping[interface][prop] if setter is not None: setter(self,value) @dbus.service.method(__prop_interface, in_signature="s", out_signature="a{sv}") def GetAll(self, interface): read_props = {} props = self.__prop_mapping[interface] for key, (getter, setter) in props.iteritems(): if callable(getter): getter = getter(self) read_props[key] = getter return read_props def update_property(self, interface, prop): getter, setter = self.__prop_mapping[interface][prop] if callable(getter): value = getter(self) else: value = getter logging.debug('Updated property: %s = %s' % (prop, value)) self.PropertiesChanged(interface, {prop: value}, []) return value def attach_player(self,player): self.player=player @dbus.service.signal(PLAYER_IFACE,signature='x') def Seeked(self, position): logging.debug("Seeked to %i" % position) return float(position) # TrackAdded (a{sv}: Metadata, o: AfterTrack) @dbus.service.signal(TRACKLIST_IFACE,signature='a{sv}o') def TrackAdded(self, metadata,aftertrack): logging.debug("TrackAdded to %s" % aftertrack) pass # TrackRemoved (o: TrackId) @dbus.service.signal(TRACKLIST_IFACE,signature='o') def TrackRemoved(self,trackid): logging.debug("TrackRemoved %s" % trackid) # here seem pydbus bug # disabled for now #process 22558: arguments to dbus_message_iter_append_basic() were incorrect, assertion "_dbus_check_is_valid_path (*string_p)" failed in file dbus-message.c line 2531. #This is normally a bug in some application using the D-Bus library. # D-Bus not built with -rdynamic so unable to print a backtrace #Annullato (core dumped) try: obp=dbus.ObjectPath("/org/mpris/MediaPlayer2/TrackList/"+trackid) except: logging.error("building ObjectPath to return in TrackRemoved %s" % trackid) obp=dbus.ObjectPath("/org/mpris/MediaPlayer2/TrackList/NoTrack") return obp @dbus.service.method(IFACE) def Raise(self): pass @dbus.service.method(IFACE) def Quit(self): self.player.exit() self.release_name() @dbus.service.method(PLAYER_IFACE) def Next(self): self.player.next() @dbus.service.method(PLAYER_IFACE) def Previous(self): self.player.previous() @dbus.service.method(PLAYER_IFACE) def Pause(self): self.player.pause() @dbus.service.method(PLAYER_IFACE) def PlayPause(self): self.player.playpause() @dbus.service.method(PLAYER_IFACE) def Stop(self): self.player.stop() @dbus.service.method(PLAYER_IFACE) def Play(self): logging.info( "Play") self.player.loaduri() self.player.play() @dbus.service.method(PLAYER_IFACE,in_signature='x') def Seek(self,offset): position=self.player.seek(offset) if position is not None: self.Seeked(position) @dbus.service.method(PLAYER_IFACE,in_signature='sx') def SetPosition(self,trackid,position): self.player.setposition(trackid,position) self.Seeked(position) @dbus.service.method(PLAYER_IFACE,in_signature='s') def OpenUri(self,uri): self.player.addtrack(uri,setascurrent=True) self.Stop() self.Play() #TODO #self.TrackAdded() #self.update_property(TRACKLIST_IFACE,'TrackListReplaced') # If the media player implements the TrackList interface, then the opened # track should be made part of the tracklist, the # org.mpris.MediaPlayer2.TrackList.TrackAdded # or # org.mpris.MediaPlayer2.TrackList.TrackListReplaced # signal should be fired, as well as the # org.freedesktop.DBus.Properties.PropertiesChanged # signal on the tracklist interface. #tracklist @dbus.service.method(TRACKLIST_IFACE,in_signature='ssb', out_signature='') def AddTrack(self,uri, aftertrack, setascurrent): self.player.addtrack(uri, aftertrack, setascurrent) @dbus.service.method(TRACKLIST_IFACE,in_signature='s', out_signature='') def RemoveTrack(self, trackid): if self.player.playlist.current == trackid: self.Next() self.player.removetrack(trackid) #disable for a bug in pydbus ?? #self.TrackRemoved(trackid) @dbus.service.method(TRACKLIST_IFACE,in_signature='s', out_signature='') def GoTo(self, trackid): self.player.goto(trackid) @dbus.service.method(TRACKLIST_IFACE,in_signature='as', out_signature='aa{sv}') def GetTracksMetadata(self,trackids): metadata=dbus.Array([], signature='aa{sv}') for id in trackids: if id is not None: meta={} for key,attr in ("mpris:trackid","id"),("mpris:length","time"),("xesam:title","title"),("xesam:artist","artist"),("xesam:url","path"): myattr= getattr(self.player.playlist[id],attr,None) if myattr is not None: if key == "mpris:length": myattr=long(round(myattr/1000.)) meta[key]=myattr metadata.append(dbus.Dictionary(meta, signature='sv')) return metadata def updateinfo(self): if self.player.statuschanged: self.update_property(PLAYER_IFACE,"PlaybackStatus") self.player.statuschanged=False self.update_property(PLAYER_IFACE,"Position") return True # Handle signals more gracefully def handle_sigint(self,signum, frame): logging.debug('Caught SIGINT, exiting.') self.Quit() class Player: def __init__(self,myplaylist=None,loop=None,starttoplay=False,myaudiosink=None): self.playlist=myplaylist self.player = gst.element_factory_make("playbin2", "playbin2") self.playmode = "Stopped" self.recoverplaymode = "Stopped" self.statuschanged = False self.starttoplay=starttoplay self.loop=loop if self.player is None: logging.error( "creating player") fakesink = gst.element_factory_make("fakesink", "fakesink") self.player.set_property("video-sink", fakesink) ##icecast #print "Icecast selected" #bin = gst.Bin("my-bin") #audioconvert = gst.element_factory_make("audioconvert") #bin.add(audioconvert) #pad = audioconvert.get_pad("sink") #ghostpad = gst.GhostPad("sink", pad) #bin.add_pad(ghostpad) #audioresample = gst.element_factory_make("audioresample") #audioresample.set_property("quality", 0) #bin.add(audioresample) #capsfilter = gst.element_factory_make('capsfilter') #capsfilter.set_property('caps', gst.caps_from_string('audio/x-raw,rate=44100,channels=2')) ##bin.add(capsfilter) #vorbisenc = gst.element_factory_make("vorbisenc") #vorbisenc.set_property("quality", 0) #bin.add(vorbisenc) #oggmux = gst.element_factory_make("oggmux") #bin.add(oggmux) #streamsink = gst.element_factory_make("shout2send", "streamsink") #streamsink.set_property("ip", "localhost") ##streamsink.set_property("username", "source") #streamsink.set_property("password", "ackme") #streamsink.set_property("port", 8000) #streamsink.set_property("mount", "/myradio.ogg") #bin.add(streamsink) ### Link the elements #queue = gst.element_factory_make("queue", "queue") ##queue.link(audioresample, capsfilter) #bin.add(queue) #gst.element_link_many(audioconvert,audioresample,queue,vorbisenc,oggmux,streamsink) #self.player.set_property("audio-sink", bin) #audiosink = gst.element_factory_make("autoaudiosink") #audiosink = gst.element_factory_make("jackaudiosink") if myaudiosink is None: myaudiosink = "autoaudiosink" audiosink = gst.element_factory_make(myaudiosink) self.player.set_property("audio-sink", audiosink) # # self.player.set_property("audio-sink", streamsink) bus = self.player.get_bus() bus.add_signal_watch() # bus.connect("message", self.on_message) bus.connect('message::eos', self.on_message_eos) bus.connect('message::error', self.on_message_error) bus.connect("message::state-changed", self.on_message_state_changed) # def on_message(self,bus, message): # logging.debug('gst-bus: %s' % str(message)) # # log all error messages # if message.type == gst.MESSAGE_ERROR: # error, debug = map(str, message.parse_error()) # logging.error('gstreamer_autoplayer: %s'%error) # logging.debug('gstreamer_autoplayer: %s'%debug) def on_message_eos(self, bus, message): t = message.type logging.debug("Message type %s received; source %s" % (t,type(message.src))) logging.info( "fine file") #self.player.set_state(gst.STATE_NULL) #self.playmode = "Stopped" #self.statuschanged = True self.next() def on_message_error(self, bus, message): t = message.type logging.debug("Message type %s received; source %s" % (t,type(message.src))) self.player.set_state(gst.STATE_NULL) err, debug = message.parse_error() logging.error( " %s: %s " % (err, debug)) logging.warning("restart to play after an ERROR skipping current media") self.playmode= self.recoverplaymode self.next() # if err.domain == gst.RESOURCE_ERROR : # logging.warning("restart to play after an RESOURCE_ERROR") # self.playmode= self.recoverplaymode # self.next() # else: # logging.warning("stop to play after an ERROR") # self.stop() # self.playmode = "Stopped" # self.statuschanged = True def on_message_state_changed(self, bus, message): t = message.type logging.debug("Message type %s received; source %s" % (t,type(message.src))) if isinstance(message.src, gst.Pipeline): old_state, new_state, pending_state = message.parse_state_changed() # gst.STATE_NULL the NULL state or initial state of an element # gst.STATE_PAUSED the element is PAUSED # gst.STATE_PLAYING the element is PLAYING # gst.STATE_READY the element is ready to go to PAUSED # gst.STATE_VOID_PENDING no pending state if pending_state == gst.STATE_VOID_PENDING: logging.debug("Pipeline state changed from %s to %s. Pendig: %s"% (gst.element_state_get_name(old_state), gst.element_state_get_name (new_state), gst.element_state_get_name (pending_state))) if new_state == gst.STATE_READY : self.playmode = "Stopped" self.statuschanged = True elif new_state == gst.STATE_PAUSED: self.playmode = "Paused" self.statuschanged = True elif new_state == gst.STATE_PLAYING : self.playmode = "Playing" self.statuschanged = True def next(self): logging.info( "next") self.playlist.next() if self.playlist.current is None: logging.info( "end playlist") self.stop() else: playmode=self.playmode self.stop() self.loaduri() if playmode == "Playing": self.play() elif playmode == "Paused": self.pause() def previous(self): logging.info( "previous") self.playlist.previous() if self.playlist.current is None: logging.info( "head playlist") self.stop() else: playmode=self.playmode self.stop() self.loaduri() if playmode == "Playing": self.play() elif playmode == "Paused": self.pause() def convert_ns(self, t): s,ns = divmod(t, 1000000000) m,s = divmod(s, 60) if m < 60: return "%02i:%02i" %(m,s) else: h,m = divmod(m, 60) return "%i:%02i:%02i" %(h,m,s) def seek(self,t): """ t in microseconds """ logging.info("seek") try: pos_int = self.player.query_position(gst.FORMAT_TIME, None)[0] pos_int =pos_int/1000 + t logging.info("seek %s" % str(pos_int)) self.setposition(self.playlist.current,pos_int) return pos_int except: logging.error( "in seek") return None def setposition(self,trackid,t): """ t in microseconds """ if trackid != self.playlist.current: logging.warning( "setposition trackid is not current trackid") try: logging.info("set position") pos_int = self.player.query_duration(gst.FORMAT_TIME, None)[0] tnano=t*1000 if tnano >= 0 and tnano <= pos_int : logging.debug("set position to: %s; len: %s" % (str(t),str(pos_int))) #if wait: self.playbin.get_state(timeout=50*gst.MSECOND) event = gst.event_new_seek(1.0, gst.FORMAT_TIME, gst.SEEK_FLAG_FLUSH|gst.SEEK_FLAG_ACCURATE, gst.SEEK_TYPE_SET, tnano, gst.SEEK_TYPE_NONE, 0) res = self.player.send_event(event) if res: self.player.set_new_stream_time(0L) #if wait: self.playbin.get_state(timeout=50*gst.MSECOND) # this cause a doble seek with playbin2 #self.player.seek_simple(gst.FORMAT_TIME, gst.SEEK_FLAG_FLUSH, t) except: logging.error( "in setposition") def loaduri(self): logging.info( "loaduri") if self.playlist.current is None: if len(self.playlist.keys()) > 0: self.playlist.set_current(self.playlist.keys()[0]) uri = self.playlist.get_current().path if uri is not None: self.player.set_property("uri", uri) ret = self.player.set_state(gst.STATE_READY) if ret == gst.STATE_CHANGE_FAILURE: logging.error( "Unable to set the pipeline to the READY state.") def play(self): logging.info( "play") ret = self.player.set_state(gst.STATE_PLAYING) if ret == gst.STATE_CHANGE_FAILURE: logging.error( "Unable to set the pipeline to the PLAYING state.") self.recoverplaymode = "Playing" #else: # print self.player.get_state(timeout=gst.CLOCK_TIME_NONE) def pause(self): logging.info( "pause") ret = self.player.set_state(gst.STATE_PAUSED) if ret == gst.STATE_CHANGE_FAILURE: logging.error( "Unable to set the pipeline to the PAUSED state.") self.recoverplaymode = "Paused" #else: # print self.player.get_state(timeout=gst.CLOCK_TIME_NONE) def playpause(self): if self.playmode == "Playing": self.pause() elif self.playmode == "Stopped": self.loaduri() self.play() elif self.playmode == "Paused": self.play() def stop(self): logging.info( "stop") #self.loaduri() ret = self.player.set_state(gst.STATE_READY) if ret == gst.STATE_CHANGE_FAILURE: logging.error( "Unable to set the pipeline to the READY state.") self.recoverplaymode = "Stopped" #else: # print self.player.get_state(timeout=gst.CLOCK_TIME_NONE) def position(self): """ return microseconds """ try: pos_int = self.player.query_position(gst.FORMAT_TIME, None)[0] except(gst.QueryError): logging.warning( "gst.QueryError in query_position" ) return None return int(round(pos_int/1000.)) def printinfo(self): try: pos_int = self.player.query_position(gst.FORMAT_TIME, None)[0] dur_int = self.player.query_duration(gst.FORMAT_TIME, None)[0] # if dur_int == -1: # print "bho" print self.playmode,self.convert_ns(pos_int)+"//"+self.convert_ns(dur_int) except(gst.QueryError): #print "error printinfo" pass return True def save_playlist(self,path): position=self.position() if position is None: self.playlist.position=position else: self.playlist.position=self.position()*1000 try: self.playlist.write(path) except: logging.error( "error saving playlist") raise logging.info ( "playlist saved %s" % path) return True def initialize(self): self.loaduri() self.pause() return False def recoverstatus(self): if self.playmode != "Paused": logging.info ( "wait for player going paused: %s" % self.playmode) return True time.sleep(1) logging.info ( "recover last status from disk: position %s" % self.playlist.position) if self.playlist.position is not None: logging.info ( "set current %s and position %s " % (self.playlist.current,int(round(self.playlist.position/1000.)))) self.setposition(self.playlist.current,int(round(self.playlist.position/1000.))) if self.starttoplay: time.sleep(1) self.play() return False def addtrack(self,uri, aftertrack=None, setascurrent=False): if aftertrack == "/org/mpris/MediaPlayer2/TrackList/NoTrack": aftertrack=None current = self.playlist.current self.playlist=self.playlist.addtrack(uri,aftertrack,setascurrent) if setascurrent: playmode=self.playmode if self.playlist.current != current: self.stop() self.loaduri() if playmode == "Playing": self.play() elif playmode == "Paused": self.pause() def removetrack(self,trackid): self.playlist=self.playlist.removetrack(trackid) #print "indice: ",str(self.playlist.keys().index(trackid)) #for id,track in enumerate(self.playlist): # print id,track def goto(self,trackid): self.playlist.set_current(trackid) self.stop() self.loaduri() self.play() def exit(self): logging.info("save playlist: %s" % STATUS_PLAYLIST ) self.save_playlist(STATUS_PLAYLIST) self.stop() self.loop.quit() def main(busaddress=None,myaudiosink=None): # Use logging for ouput at different *levels*. # logging.getLogger().setLevel(logging.INFO) log = logging.getLogger("autoplayer") handler = logging.StreamHandler(sys.stderr) log.addHandler(handler) # logging.basicConfig(level=logging.INFO,) # try: # os.chdir(cwd) # except: # pass pl=playlist.Playlist() pl.read("autoplayer.xspf") #plmpris=playlist.Playlist_mpris2(pl,pl.current,pl.position) plmpris=playlist.Playlist_mpris2(pl) if len(sys.argv) >= 2: #if you come from autoplayerd argv[1] is run/start/stop ... for media in sys.argv[2:]: logging.info( "add media: %s" %media) # mmm here seems not work ... the new plmpris is not good !!! plmpris=plmpris.addtrack(media,setascurrent=True) try: dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) loop = gobject.MainLoop() mp = Player(plmpris,loop=loop,starttoplay=True,myaudiosink=myaudiosink) # Export our DBUS service #if not dbus_service: #dbus_service = MPRIS2Interface() #else: # Add our service to the session bus # dbus_service.acquire_name() ap = AutoPlayer(busaddress=busaddress) ap.attach_player(mp) gobject.timeout_add( 100,ap.player.initialize) gobject.timeout_add( 200,ap.player.recoverstatus) gobject.timeout_add( 500,ap.updateinfo) gobject.timeout_add(60000,ap.player.save_playlist,"autoplayer.xspf") #gobject.timeout_add( 1000,ap.player.printinfo) signal.signal(signal.SIGINT, ap.handle_sigint) loop.run() # Clean up logging.debug('Exiting') except KeyboardInterrupt : # Clean up logging.debug('Keyboard Exiting') ap.Quit() # thread.start_new_thread(mp.loop, ()) # object.threads_init() # context = loop.get_context() # gobject.MainLoop().run() # while True: # context.iteration(True) if __name__ == '__main__': main()# (this code was run as script) autoradio-upstream-2.8.2+dfsg.orig/autoradio/autoplayer/mpris2client.py0000755000175000017500000000531612332264611026633 0ustar capriottcapriott#!/usr/bin/env python # -*- coding: utf-8 -*- # GPL. (C) 2013 Paolo Patruno. #Connect to player from autoradio.mpris2.mediaplayer2 import MediaPlayer2 from autoradio.mpris2.player import Player from autoradio.mpris2.tracklist import TrackList from autoradio.mpris2.interfaces import Interfaces from autoradio.mpris2.some_players import Some_Players from autoradio.mpris2.utils import get_players_uri from autoradio.mpris2.utils import get_session from dbus.mainloop.glib import DBusGMainLoop import dbus def playhandler( *args, **kw): #print args, kw playbackstatus = args[2].get("PlaybackStatus",None) position = args[2].get("Position",None) if playbackstatus is not None: print "PlaybackStatus",playbackstatus if position is not None: print "Position", position def trackhandler( *args, **kw): print args, kw DBusGMainLoop(set_as_default=True) import gobject #busaddress='tcp:host=localhost,port=1234' busaddress=None mloop = gobject.MainLoop() uris = get_players_uri(pattern=".",busaddress=busaddress) if len(uris) >0 : uri=uris[0] #uri = Interfaces.MEDIA_PLAYER + '.' + Some_Players.AUDACIOUS #uri = Interfaces.MEDIA_PLAYER + '.' + Some_Players.AUTOPLAYER #uri = Interfaces.MEDIA_PLAYER + '.' +'AutoPlayer' print uri if busaddress is None: bus = dbus.SessionBus() else: bus =dbus.bus.BusConnection(busaddress) mp2 = MediaPlayer2(dbus_interface_info={'dbus_uri': uri,'dbus_session':bus}) play = Player(dbus_interface_info={'dbus_uri': uri,'dbus_session':bus}) #Call methods #play.Next() # play next media #Get attributes #print play.Metadata #current media data print play.PlaybackStatus play.PropertiesChanged = playhandler try: if mp2.HasTrackList: tl = TrackList(dbus_interface_info={'dbus_uri': uri}) # attributes and methods together for track in tl.GetTracksMetadata( tl.Tracks): print track.get(u'mpris:trackid',None),track.get(u'mpris:length',None),track.get(u'xesam:artist',None), track.get(u'xesam:title',None) tl.PropertiesChanged = trackhandler except: print "mmm audacious mpris2 interface is buggy" mloop.run() else: print "No players availables" #s = get_session() #s.add_signal_receiver(handler, # "PropertiesChanged", # "org.freedesktop.DBus.Properties", # path="/org/mpris/MediaPlayer2") # Interfaces.SIGNAL, # Interfaces.PROPERTIES, # uri, # Interfaces.OBJECT_PATH) #def my_handler(self, Position): # print 'handled', Position, type(Position) # print 'self handled', self.last_fn_return, type(self.last_fn_return) autoradio-upstream-2.8.2+dfsg.orig/autoradio/autoplayer/playlist.py0000644000175000017500000003714612332264611026064 0ustar capriottcapriott#!/usr/bin/python # -*- coding: utf-8 -*- # GPL. (C) 2013 Paolo Patruno. import logging import collections import mutagen import sys from xml.sax import make_parser, handler, SAXParseException from xml.dom.minidom import Document import urllib, urlparse class Track(collections.namedtuple('Track',("path","time","artist","album","title","id"))): __slots__ = () def get_metadata(self): metadata=collections.OrderedDict() metadata["path"] =self.path metadata["time"] = 0 metadata["artist"]=None metadata["album"]=None metadata["title"]=None metadata["id"]=self.id try: # m=mutagen.File(self.path[7:].encode(sys.getfilesystemencoding()),easy=True) m=mutagen.File(self.path[7:],easy=True) # in seconds (type float). metadata["time"] = int(m.info.length*1000000000) value = m.get("artist") if value: metadata["artist"]=value[0]#.encode("UTF-8") value = m.get("album") if value: metadata["album"]=value[0]#.encode("UTF-8") value = m.get("title") if value: metadata["title"]=value[0]#.encode("UTF-8") except: logging.error("Could not read info from file: %s ",self.path) return metadata def parse_pls(lines): # titles = {} songs = {} for line in lines: spl = line.split( '=', 1) if len(spl) == 2: name, value = spl if name.lower().startswith('file'): num = name[4:] try: n = int(num) except: pass else: songs["%05d" % n] = value #elif name.lower().startswith('title'): # num = name[4:] # try: # n = int(num) # except: # pass # else: # titles["%05d" % n] = value else: logging.debug( "PLAYLIST: skip this line from pls playlist: %s",line) ret = [] for k in sorted(songs.keys()): # ret.append( (songs[k], titles.get(k, None) ) ) ret.append(songs[k]) return ret def parse_xspf2(data): handler = XSPFParser2() parser = make_parser() parser.setContentHandler(handler) parser.feed(data) return handler class XSPFParser2(handler.ContentHandler): def __init__(s): s.path = u"" s.tracks = [] s.current=None s.position=None s.extensionapplication=None def parseFile(s, fileName): try: parser = make_parser() parser.setContentHandler(s) parser.parse(fileName) return True except SAXParseException: return False def startElement(s, name, attrs): s.path += "/%s" % name s.content = "" if s.path == "/playlist/trackList/track": s.track = {} elif s.path == "/playlist/extension": #if name == 'extension': s.extensionapplication= attrs.get('application',None) def characters(s, content): s.content += content def endElement(s, name): if s.path == "/playlist/title": s.title = s.content elif s.path == "/playlist/extension/current": if s.extensionapplication == "autoplayer": s.current = str(s.content) elif s.path == "/playlist/extension/position": if s.extensionapplication == "autoplayer": s.position = int(s.content) elif s.path == "/playlist/trackList/track/location": # mmmm this is for audacious but I think is wrong ##s.track['location'] = urllib.unquote(s.content) #s.track['location'] = urllib.unquote(s.content.encode("UTF-8")) url=urlparse.urlsplit(s.content) s.track['location']=urlparse.urljoin("file://",urllib.unquote(url.path.encode("UTF-8"))) elif s.path == "/playlist/trackList/track/title": s.track['title'] = s.content elif s.path == "/playlist/trackList/track/creator": s.track['creator'] = s.content elif s.path == "/playlist/trackList/track/album": s.track['album'] = s.content elif s.path == "/playlist/trackList/track/extension/id": s.track['id'] = s.content elif s.path == "/playlist/trackList/track": if s.track.get('location'): s.tracks.append(s.track) del s.track s.path = s.path.rsplit("/", 1)[0] class Playlist(list): def __init__(self,media=None,tracks=None,current=None,position=None): super( Playlist, self ).__init__([]) self.current=current self.position=position if media is not None: for ele in media: if ele.lower().endswith(".xspf") or \ ele.lower().endswith(".m3u") or \ ele.lower().endswith(".pls") : self.read(ele) else: track_meta=Track(ele,None,None,None,None,None) #print track_meta.get_metadata().values() tr=Track._make(track_meta.get_metadata().values()) self.append(tr) if tracks is not None: for ele in tracks: self.append(ele) def read(s, path): try: with open(urlparse.urlsplit(path).path, "r") as f: data = f.read() except IOError : logging.warning( "PLAYLIST: error opening file %s" % path) return if data.strip() == "": #empty logging.info( "PLAYLIST: empty") return logging.debug( "PLAYLIST: parse") parser = make_parser () try: parser.feed(data) except: lines = data.split('\n') lines = map(lambda line: line.strip().rstrip(), lines) lines = filter(lambda line: line if line != "" and line[0] != '#' else None, lines) if lines == []: return #detect type of playlist if '[playlist]' in lines: logging.debug( "PLAYLIST: is PLS") lines = parse_pls(lines) for location in lines: url=urlparse.urlsplit(location) # mmmmmm encode / decode every time do not work ! #location=urlparse.urljoin("file://",urllib.unquote(url.path.encode("UTF-8"))) location=urlparse.urljoin("file://",urllib.unquote(url.path)) track=Track._make(Track(location,None,None,None,None,None).get_metadata().values()) s.append(track) else: logging.debug( "PLAYLIST: is XML") p = parse_xspf2(data) logging.debug( "PLAYLIST: xspf parsed") for ele in p.tracks: track=Track._make(Track(ele.get('location',None),ele.get('time',None),ele.get('creator',None), ele.get('album',None),ele.get('title',None),ele.get('id',None)).get_metadata().values()) s.append(track) #TODO read from file !!!! #s.current=s[2][5] #s.position=0 s.current=p.current s.position=p.position #s.current="1" #s.position=180000000000 logging.info ( "read from xspf current: %s" % s.current) logging.info ( "read from xspf position: %s" % s.position) def write(s,path): doc = Document() xspf_vlc_compatibility=False xspf_audacious_compatibility=False xspf_qmmp_compatibility=False with open(path, "w") as f: #head f.write('\n') if xspf_vlc_compatibility: f.write('\n' % VLC_NS) else: f.write('\n') logging.info ( "writing to xspf current: %s" % s.current) logging.info ( "writing to xspf position: %s" % s.position) if s.current is not None or s.position is not None: f.write('\t\n') if s.current is not None : k="current" t="int" v = doc.createTextNode(str(s.current)).toxml() f.write(u"\t\t<%s type='%s'>%s\n" % (k, t, v, k)) if s.position is not None: k="position" t="int" v = doc.createTextNode(str(s.position)).toxml() f.write(u"\t\t<%s type='%s'>%s\n" % (k, t, v, k)) f.write('\t\n') f.write('\n') for track in s: track=track._asdict() f.write('\t\n') if track.get('title') not in ['', None]: f.write( '\t\t%s\n' \ % doc.createTextNode(track['title'].encode("utf-8")).toxml() ) if track.get('artist') not in ['', None]: f.write('\t\t%s\n' \ % doc.createTextNode(track['artist'].encode("utf-8")).toxml() ) if track.get('album') not in ['', None]: f.write( '\t\t%s\n' \ % doc.createTextNode(track['album'].encode("utf-8")).toxml() ) if track.get('tracknum') not in ['', None]: if type(track['tracknum']) == int: no = track['tracknum'] elif type(track['tracknum']) in [unicode, str]: cnum=track['tracknum'].split("/")[0].lstrip('0') if cnum != "": no = int( track['tracknum'].split("/")[0].lstrip('0') ) else: no=0 else: no = 0 if no > 0: f.write( '\t\t%i\n' % no ) #if float are seconds; if integer nanosec # out should be millisec if type(track.get('time')) == float: tm = track['time']*1000000 elif type(track.get('time')) == int: tm = track['time']/1000000. else: tm= None if tm is not None: tm = int(round(tm)) f.write('\t\t%i\n' % tm ) #write location #make valid quoted location location = track['path'] url=urlparse.urlsplit(location) #here problem when file name come fron gtk or command line try: location=urlparse.urljoin("file://",urllib.quote(url.path)) except: location=urlparse.urljoin("file://",urllib.quote(url.path.encode("UTF-8"))) ##location = location.encode("utf-8") #if not 'http://' in location.lower() and \ # not 'file://' in location.lower(): # location = 'file://' + location #location = urllib.quote( location ) #write the location f.write( '\t\t%s\n' \ % doc.createTextNode(location).toxml() ) #write other info: keys = set(track.keys()) keys.discard('title') keys.discard('artist') keys.discard('album') keys.discard('tracknum') keys.discard('time') keys.discard('path') if len(keys) > 0: f.write('\t\t\n') for k in sorted(keys): if track[k] != None: v = track[k] t = type(v) if t in [str, unicode]: t = "str" v = unicode(v) elif t == bool: t = "bool" v = '1' if v else '0' elif t in [int, long]: t = "int" v = str(v).encode("utf-8") elif t == float: t = "float" v = repr(v) else: continue v = doc.createTextNode(v).toxml() f.write(u"\t\t\t<%s type='%s'>%s\n" % (k, t, v, k)) f.write('\t\t\n') f.write('\t\n') #tail f.write('\n') f.write('\n') class Playlist_mpris2(collections.OrderedDict): def __init__(self,playlist=Playlist([]),current=None,position=None): super( Playlist_mpris2, self ).__init__(collections.OrderedDict()) remakeid=False for track in playlist: if (track.id is None): remakeid=True break for id,track in enumerate(playlist): if (remakeid): self[str(id)]=Track._make((track.path,track.time,track.artist,track.album,track.title,str(id))) else: self[track.id]=track if current is None: if playlist.current is None: if len (self) == 0 : self.current = None else: self.current = self.keys()[0] else: self.current=playlist.current else: self.current=current if position is None: self.position=playlist.position else: self.position=position def get_current(self): if self.current is not None: return self[self.current] else: return Track(None,None,None,None,None,None) def set_current(self,id): if id in self.keys(): self.current=id else: logging.warning ("set_current: invalid id") def next(self): self.current = self.nextid(self.current) logging.info ( "current: %s" % self.current) def nextid(self,id): if id is None: return None keys=self.keys() ind = keys.index(id) if len(keys)-1 <= ind : return None ind += 1 return keys[ind] def previous(self): self.current = self.previousid(self.current) logging.info ( "current: %s" % self.current) def previousid(self,id): if id is None: return None keys=self.keys() ind = keys.index(id) if ind == 0 : return None ind -= 1 return keys[ind] def addtrack(self,uri,aftertrack=None,setascurrent=False): keys=self.keys() if aftertrack is None: ind = max(len(keys)-1,0) else: try: ind = keys.index(aftertrack) except: logging.warning ("invalid aftertrack in addtrack") ind = max(len(keys)-1,0) # found id as index of position after we have to insert if len(keys) > 0: startnewid=max([int(x) for x in keys]) + 1 newself=Playlist_mpris2() aftertrack=keys[ind] else: return Playlist_mpris2(Playlist([uri])) # here we have empty new list were copy old and new for id,track in self.iteritems(): newself[id]=track if id == aftertrack: p=Playlist([uri]) for id,track in enumerate(p,startnewid): newself[str(id)]=Track._make((track.path,track.time,track.artist,track.album,track.title,str(id))) # newself[str(newid)]=Track._make(track.get_metadata().values()) newself.current=self.current if setascurrent: if len(newself) >=0: newself.current=str(startnewid) return newself def removetrack(self,trackid): newself=self if trackid == newself.current: #newself.previous() newself.current=None newself.pop(trackid,None) return newself def write(self,path): Playlist(tracks=self.values(),current=self.current,position=self.position).write(path) def main(): import logging logging.basicConfig(level=logging.DEBUG,) media=( u"file:///home/pat1/Musica/Paolo Benvegnù/Piccoli fragilissimi film/3 - Io e te.flac", u"file:///home/pat1/Musica/Paolo Benvegnù/Piccoli fragilissimi film/5 - Fiamme.flac", u"file:///home/pat1/Musica/Paolo Benvegnù/Piccoli fragilissimi film/9 - Only for You.flac", ) uri=u"file:///home/pat1/Musica/Paolo Benvegnù/Piccoli fragilissimi film/2 - Cerchi nell'acqua.flac" print "-------------- playlist ------------------" p=Playlist(media) print "--------- playlist ord dict -----------------------" op=Playlist_mpris2(p) op.write("/tmp/tmp.xspf") print "--------- playlist from file -----------------------" p=Playlist(["/tmp/tmp.xspf"]) print "--------- playlist from file ord dict -----------------------" op=Playlist_mpris2(p) op=op.addtrack(uri,aftertrack="1") op=op.addtrack(uri,aftertrack="1",setascurrent=True) print op op.write("/tmp/tmpout.xspf") print "--------- reread playlist from file ord dict -----------------------" p=Playlist(["/tmp/tmpout.xspf"]) op=Playlist_mpris2(p) print "remove ",op.current op=op.removetrack("0") print op op.write("/tmp/tmpout2.xspf") if __name__ == '__main__': main() # (this code was run as script) autoradio-upstream-2.8.2+dfsg.orig/autoradio/doc/0000755000175000017500000000000012332265077022225 5ustar capriottcapriottautoradio-upstream-2.8.2+dfsg.orig/autoradio/doc/__init__.py0000644000175000017500000000000012332264611024315 0ustar capriottcapriottautoradio-upstream-2.8.2+dfsg.orig/autoradio/doc/urls.py0000644000175000017500000000107212332264611023555 0ustar capriottcapriottfrom django.conf.urls import * #from django.views.generic.simple import direct_to_template from django.conf.urls import patterns from django.views.generic import TemplateView urlpatterns = patterns('', (r'^$', TemplateView.as_view(template_name="doc/index.html")), (r'^(?P\w+)/$', TemplateView.as_view(template_name="doc/doc.html")), ) #urlpatterns = patterns('autoradio.doc.views', # (r'^$', direct_to_template , {'template' : 'doc/index.html'}), # (r'^(?P\w+)/$', direct_to_template , {'template' : 'doc/doc.html'}), #) autoradio-upstream-2.8.2+dfsg.orig/autoradio/mime.py0000644000175000017500000000264412332264611022760 0ustar capriottcapriott # audio/basic: mulaw audio at 8 kHz, 1 channel; Defined in RFC 2046 # audio/L24: 24bit Linear PCM audio at 8-48kHz, 1-N channels; Defined in RFC 3190 # audio/mp4: MP4 audio # audio/mpeg: MP3 or other MPEG audio; Defined in RFC 3003 # audio/ogg: Ogg Vorbis, Speex, Flac and other audio; Defined in RFC 5334 # audio/vorbis: Vorbis encoded audio; Defined in RFC 5215 # audio/x-ms-wma: Windows Media Audio; Documented in MS kb288102[9] # audio/x-ms-wax: Windows Media Audio Redirector; Documented in MS kb288102[9] # audio/vnd.rn-realaudio: RealAudio; Documented in RealPlayer Help[10] # audio/vnd.wave: WAV audio; Defined in RFC 2361 # audio/webm: WebM open media format mymime_audio=("application/ogg","audio/mpeg", "audio/mp4", "audio/x-flac", "audio/x-wav") mymime_ogg=("application/ogg",) webmime_audio = ("audio/mpeg","audio/mp3","audio/flac","video/ogg","audio/ogg","audio/oga", \ "audio/basic","audio/L24","audio/mp4","audio/vorbis","audio/x-ms-wma2",\ "audio/x-ms-wax","audio/vnd.rn-realaudio","audio/vnd.wave","audio/webm",\ "application/ogg","audio/wav") websuffix_audio = (".mp3",".wav",".ogg",".oga",".flac",".Mp3",".Wav",".Ogg",".Oga",".Flac",".MP3",".WAV",".OGG",".OGA",".FLAC" ) webmime_ogg = ("video/ogg","audio/oga","audio/ogg","audio/oga","audio/vorbis","application/ogg") websuffix_ogg = (".ogg",".oga",".Ogg",".Oga",".OGG") autoradio-upstream-2.8.2+dfsg.orig/autoradio/mpris2/0000755000175000017500000000000012332265077022674 5ustar capriottcapriottautoradio-upstream-2.8.2+dfsg.orig/autoradio/mpris2/playlists.py0000644000175000017500000001141612332264611025266 0ustar capriottcapriott''' This is python mprisV2.1 documentation http://www.mpris.org/2.1/spec/Playlists.html ''' from autoradio.pydbusdecorator.dbus_attr import DbusAttr from autoradio.pydbusdecorator.dbus_interface import DbusInterface from autoradio.pydbusdecorator.dbus_method import DbusMethod from autoradio.pydbusdecorator.dbus_signal import DbusSignal from interfaces import Interfaces from types import Playlist, Maybe_Playlist from dbus import UInt32 class Playlists(Interfaces): ''' Provides access to the media player's playlists. Since D-Bus does not provide an easy way to check for what interfaces are exported on an object, clients should attempt to get one of the properties on this interface to see if it is implemented. ''' @DbusInterface(Interfaces.PLAYLISTS, Interfaces.OBJECT_PATH) def __init__(self): '''Constructor''' pass @DbusMethod def ActivatePlaylist(self, PlaylistId): ''' **Parameters:** * PlaylistId - o The id of the playlist to activate. Starts playing the given playlist. Note that this must be implemented. If the media player does not allow clients to change the playlist, it should not implement this interface at all. It is up to the media player whether this completely replaces the current tracklist, or whether it is merely inserted into the tracklist and the first track starts. For example, if the media player is operating in a "jukebox" mode, it may just append the playlist to the list of upcoming tracks, and skip to the first track in the playlist. ''' pass @DbusMethod(produces=lambda playlist_list: \ [Playlist(playlist) for playlist in playlist_list], args_to_dbus=[UInt32, UInt32, str, bool]) def GetPlaylists(self, Index, MaxCount, Order, ReverseOrder=False): ''' **Parameters:** * Index - u The index of the first playlist to be fetched (according to the ordering). * MaxCount - u The maximum number of playlists to fetch. * Order - s (Playlist_Ordering) The ordering that should be used. * ReverseOrder - b Whether the order should be reversed. **Returns** * Playlists - a(oss) (Playlist_List) A list of (at most MaxCount) playlists. Gets a set of playlists. ''' pass @DbusSignal def PlaylistChanged(self, Playlist): ''' **Parameters** * Playlist - (oss) (Playlist) The playlist whose details have changed. Indicates that the name or icon for a playlist has changed. Note that, for this signal to operate correctly, the id of the playlist must not change when the name changes. ''' pass @DbusAttr def PlaylistCount(self): ''' **Returns** Read only When this property changes, the org.freedesktop.DBus.Properties.PropertiesChanged signal is emitted with the new value. The number of playlists available. ''' pass @DbusAttr def Orderings(self): ''' **Returns** Read only When this property changes, the org.freedesktop.DBus.Properties.PropertiesChanged signal is emitted with the new value. The avaislable orderings. At least one must be offered. ''' pass @DbusAttr(produces=Maybe_Playlist) def ActivePlaylist(self): ''' **Returns** Read only When this property changes, the org.freedesktop.DBus.Properties.PropertiesChanged signal is emitted with the new value. The currently-active playlist. If there is no currently-active playlist, the structure's Valid field will be false, and the Playlist details are undefined. Note that this may not have a value even after ActivatePlaylist is called with a valid playlist id as ActivatePlaylist implementations have the option of simply inserting the contents of the playlist into the current tracklist. ''' pass if __name__ == '__main__': from mpris2.utils import SomePlayers uri = Interfaces.MEDIA_PLAYER + '.' + SomePlayers.RHYTHMBOX mp2 = Playlists(dbus_interface_info={'dbus_uri': uri}) print mp2.ActivePlaylist print 'Active is valid playlist: ', bool(mp2.ActivePlaylist) if mp2.ActivePlaylist: print 'Active playlist name:', mp2.ActivePlaylist.Playlist.Name from mpris2.types import Playlist_Ordering print hasattr('anystring', 'eusequenaotem') print 'bla', mp2.GetPlaylists(0, 20, Playlist_Ordering.ALPHABETICAL, False) autoradio-upstream-2.8.2+dfsg.orig/autoradio/mpris2/playlist_id.py0000644000175000017500000000052112332264611025552 0ustar capriottcapriott''' Created on Nov 12, 2011 @author: hugosenari ''' class Playlist_Id(str): ''' Unique playlist identifier. ''' def __init__(self, playlist_id, *args, **kw): ''' Constructor ''' self._playlist_id = playlist_id super(Playlist_Id, self).__init__(playlist_id, *args, **kw) autoradio-upstream-2.8.2+dfsg.orig/autoradio/mpris2/metada_map.py0000644000175000017500000000443012332264611025330 0ustar capriottcapriottclass Metadata_Map(dict): ''' A mapping from metadata attribute names to values. The mpris:trackid attribute must always be present. This contains a string that uniquely identifies the track within the scope of the playlist. If the length of the track is known, it should be provided in the metadata property with the "mpris:length" key. The length must be given in microseconds, and be represented as a signed 64-bit integer. If there is an image associated with the track, a URL for it may be provided using the "mpris:artUrl" key. For other metadata, fields defined by the Xesam ontology should be used, prefixed by "xesam:". See http://wiki.xmms2.xmms.se/wiki/MPRIS_Metadata for a list of common fields. Lists of strings should be passed using the array-of-string ("as") D-Bus type. Dates should be passed as strings using the ISO 8601 extended format (eg: 2007-04-29T14:35:51). If the timezone is known, RFC 3339's internet profile should be used (eg: 2007-04-29T14:35:51+02:00). * Attribute - s The name of the attribute; see http://wiki.xmms2.xmms.se/wiki/MPRIS_Metadata for guidelines on names to use. * Value - v The value of the attribute, in the most appropriate format. ''' ART_URI = 'mpris:artUrl' TRACKID = 'mpris:trackid' LENGTH = 'mpris:length' ALBUM = 'xesam:album' ALBUM_ARTIST = 'xesam:albumArtist' ARTIST = 'xesam:artist' AS_TEXT = 'xesam:asText' AUDIO_BPM = 'xesam:audioBPM' AUTO_RATING = 'xesam:autoRating' COMMENT = 'xesam:comment' COMPOSER = 'xesam:composer' CONTENT_CREATED = 'xesam:contentCreated' DISC_NUMBER = 'xesam:discNumber' FIRST_USED = 'xesam:firstUsed' GENRE = 'xesam:genre' LAST_USED = 'xesam:lastUsed' LYRICIST = 'xesam:lyricist' TITLE = 'xesam:title' TRACK_NUMBER = 'xesam:trackNumber' URL = 'xesam:url' USE_COUNT = 'xesam:useCount' USER_RATING = 'xesam:userRating' def __init__(self, metadata, *args, **kw): self._metadata = metadata super(Metadata_Map, self).__init__(metadata,*args, **kw) @property def metadata(self): return self._metadata if __name__ == "__main__": mdm = Metadata_Map({Metadata_Map.ALBUM : "Marcelo Nova Ao Vivo"}) print mdm[Metadata_Map.ALBUM]autoradio-upstream-2.8.2+dfsg.orig/autoradio/mpris2/loop_status.py0000644000175000017500000000175612332264611025624 0ustar capriottcapriottNONE = 'None' TRACK = 'Track' PLAYLIST = 'Playlist' class Loop_Status(str): ''' A repeat / loop status * None (None) The playback will stop when there are no more tracks to play * Track (Track) The current track will start again from the begining once it has finished playing * Playlist (Playlist) The playback loops through a list of tracks ''' VALUES = (NONE, TRACK, PLAYLIST) def __init__(self, status, *args, **kw): super(Loop_Status, self).__init__(status, *args, **kw) self._status = status def __int__(self, *args, **kwargs): return int(Loop_Status.VALUES.index(self._status), *args, **kwargs) Loop_Status.NONE = Loop_Status(NONE) Loop_Status.TRACK = Loop_Status(TRACK) Loop_Status.PLAYLIST = Loop_Status(PLAYLIST) if __name__ == "__main__": print Loop_Status.PLAYLIST print type(Loop_Status.PLAYLIST) print Loop_Status.PLAYLIST == NONE print Loop_Status.PLAYLIST == PLAYLISTautoradio-upstream-2.8.2+dfsg.orig/autoradio/mpris2/player.py0000644000175000017500000004127012332264611024537 0ustar capriottcapriott''' This is python mprisV2.1 documentation http://www.mpris.org/2.1/spec/Player_Node.html ''' from autoradio.pydbusdecorator.dbus_attr import DbusAttr from autoradio.pydbusdecorator.dbus_interface import DbusInterface from autoradio.pydbusdecorator.dbus_method import DbusMethod from autoradio.pydbusdecorator.dbus_signal import DbusSignal from interfaces import Interfaces from types import Time_In_Us, Loop_Status, Playback_Status, \ Playback_Rate, Metadata_Map, Volume class Player(Interfaces): ''' This interface implements the methods for querying and providing basic control over what is currently playing. ''' @DbusInterface(Interfaces.PLAYER, Interfaces.OBJECT_PATH) def __init__(self): '''Constructor''' pass @DbusMethod def Next(self): ''' Skips to the next track in the tracklist. If there is no next track (and endless playback and track repeat are both off), stop playback. If playback is paused or stopped, it remains that way. If CanGoNext is false, attempting to call this method should have no effect. ''' pass @DbusMethod def Previous(self): ''' Skips to the previous track in the tracklist. If there is no previous track (and endless playback and track repeat are both off), stop playback. If playback is paused or stopped, it remains that way. If CanGoPrevious is false, attempting to call this method should have no effect. ''' pass @DbusMethod def Pause(self): ''' Pauses playback. If playback is already paused, this has no effect. Calling Play after this should cause playback to start again from the same position. If CanPause is false, attempting to call this method should have no effect. ''' pass @DbusMethod def PlayPause(self): ''' Pauses playback. If playback is already paused, resumes playback. If playback is stopped, starts playback. If CanPause is false, attempting to call this method should have no effect and raise an error. ''' pass @DbusMethod def Stop(self): ''' Stops playback. If playback is already stopped, this has no effect. Calling Play after this should cause playback to start again from the beginning of the track. If CanControl is false, attempting to call this method should have no effect and raise an error. ''' pass @DbusMethod def Play(self): ''' Starts or resumes playback. If already playing, this has no effect. If there is no track to play, this has no effect. If CanPlay is false, attempting to call this method should have no effect. ''' pass @DbusMethod def Seek(self, Offet): ''' **Parameters:** * Offset - x (Time_In_Us) The number of microseconds to seek forward. Seeks forward in the current track by the specified number of microseconds. A negative value seeks back. If this would mean seeking back further than the start of the track, the position is set to 0. If the value passed in would mean seeking beyond the end of the track, acts like a call to Next. If the CanSeek property is false, this has no effect. ''' pass @DbusMethod def SetPosition(self, TrackId, Position): ''' **Parameters** * TrackId - o (Track_Id) The currently playing track's identifier. If this does not match the id of the currently-playing track, the call is ignored as "stale". * Position - x (Time_In_Us) Track position in microseconds. This must be between 0 and . Sets the current track position in microseconds. If the Position argument is less than 0, do nothing. If the Position argument is greater than the track length, do nothing. If the CanSeek property is false, this has no effect. ''' pass @DbusMethod def OpenUri(self, Uri): ''' **Parameters:** * Uri - s (Uri) Uri of the track to load. Its uri scheme should be an element of the org.mpris.MediaPlayer2.SupportedUriSchemes property and the mime-type should match one of the elements of the org.mpris.MediaPlayer2.SupportedMimeTypes. Opens the Uri given as an argument If the playback is stopped, starts playing If the uri scheme or the mime-type of the uri to open is not supported, this method does nothing and may raise an error. In particular, if the list of available uri schemes is empty, this method may not be implemented. Clients should not assume that the Uri has been opened as soon as this method returns. They should wait until the mpris:trackid field in the Metadata property changes. If the media player implements the TrackList interface, then the opened track should be made part of the tracklist, the org.mpris.MediaPlayer2.TrackList.TrackAdded or org.mpris.MediaPlayer2.TrackList.TrackListReplaced signal should be fired, as well as the org.freedesktop.DBus.Properties.PropertiesChanged signal on the tracklist interface. ''' pass @DbusSignal def Seeked(self, Position): ''' **Parameters:** * Position - x (Time_In_Us) The new position, in microseconds. Indicates that the track position has changed in a way that is inconsistant with the current playing state. When this signal is not received, clients should assume that: * When playing, the position progresses according to the rate property. * When paused, it remains constant. This signal does not need to be emitted when playback starts or when the track changes, unless the track is starting at an unexpected position. An expected position would be the last known one when going from Paused to Playing, and 0 when going from Stopped to Playing. ''' return Time_In_Us(Position) @DbusSignal(iface=Interfaces.PROPERTIES) def PropertiesChanged(self, *args, **kw): """ **Parameters** * args - list unnamed parameters passed by dbus signal * kw - dict named parameters passed by dbus signal Every time that some property change, signal will be called """ pass @DbusAttr(produces=Playback_Status) def PlaybackStatus(self): ''' **Returns** Read only When this property changes, the org.freedesktop.DBus.Properties.PropertiesChanged signal is emitted with the new value. The current playback status. May be "Playing", "Paused" or "Stopped". ''' pass @DbusAttr(produces=Loop_Status) def LoopStatus(self): ''' **Returns** Read/Write When this property changes, the org.freedesktop.DBus.Properties.PropertiesChanged signal is emitted with the new value. The current loop / repeat status May be: * "None" if the playback will stop when there are no more tracks to play * "Track" if the current track will start again from the begining once it has finished playing * "Playlist" if the playback loops through a list of tracks This property is optional, and clients should deal with NotSupported errors gracefully. If CanControl is false, attempting to set this property should have no effect and raise an error. ''' pass @DbusAttr(produces=Playback_Rate) def Rate(self): ''' **Returns** Read/Write When this property changes, the org.freedesktop.DBus.Properties.PropertiesChanged signal is emitted with the new value. The current playback rate. The value must fall in the range described by MinimumRate and MaximumRate, and must not be 0.0. If playback is paused, the PlaybackStatus property should be used to indicate this. A value of 0.0 should not be set by the client. If it is, the media player should act as though Pause was called. If the media player has no ability to play at speeds other than the normal playback rate, this must still be implemented, and must return 1.0. The MinimumRate and MaximumRate properties must also be set to 1.0. Not all values may be accepted by the media player. It is left to media player implementations to decide how to deal with values they cannot use; they may either ignore them or pick a "best fit" value. Clients are recommended to only use sensible fractions or multiples of 1 (eg: 0.5, 0.25, 1.5, 2.0, etc). ''' pass @DbusAttr def Shuffle(self): ''' **Returns** Read/Write When this property changes, the org.freedesktop.DBus.Properties.PropertiesChanged signal is emitted with the new value. A value of false indicates that playback is progressing linearly through a playlist, while true means playback is progressing through a playlist in some other order. This property is optional, and clients should deal with NotSupported errors gracefully. If CanControl is false, attempting to set this property should have no effect and raise an error. ''' pass @DbusAttr(produces=Metadata_Map) def Metadata(self): ''' **Returns** Read only When this property changes, the org.freedesktop.DBus.Properties.PropertiesChanged signal is emitted with the new value. The metadata of the current element. If there is a current track, this must have a "mpris:trackid" entry at the very least, which contains a string that uniquely identifies this track. See the type documentation for more details. ''' pass @DbusAttr(produces=Volume) def Volume(self): ''' **Returns** Read/Write When this property changes, the org.freedesktop.DBus.Properties.PropertiesChanged signal is emitted with the new value. The volume level. When setting, if a negative value is passed, the volume should be set to 0.0. If CanControl is false, attempting to set this property should have no effect and raise an error. ''' pass @DbusAttr def Position(self): ''' **Returns** Read only The org.freedesktop.DBus.Properties.PropertiesChanged signal is not emitted when this property changes. The current track position in microseconds, between 0 and the 'mpris:length' metadata entry (see Metadata). .. note:: If the media player allows it, the current playback position can be changed either the SetPosition method or the Seek method on this interface. If this is not the case, the CanSeek property is false, and setting this property has no effect and can raise an error. If the playback progresses in a way that is inconstistant with the Rate property, the Seeked signal is emited. ''' pass @DbusAttr def MinimumRate(self): ''' **Returns** Read only When this property changes, the org.freedesktop.DBus.Properties.PropertiesChanged signal is emitted with the new value. The minimum value which the Rate property can take. Clients should not attempt to set the Rate property below this value. Note that even if this value is 0.0 or negative, clients should not attempt to set the Rate property to 0.0. This value should always be 1.0 or less. ''' pass @DbusAttr def MaximumRate(self): ''' **Returns** Read only When this property changes, the org.freedesktop.DBus.Properties.PropertiesChanged signal is emitted with the new value. The maximum value which the Rate property can take. Clients should not attempt to set the Rate property above this value. This value should always be 1.0 or greater. ''' pass @DbusAttr def CanGoNext(self): ''' **Returns** Read only When this property changes, the org.freedesktop.DBus.Properties.PropertiesChanged signal is emitted with the new value. Whether the client can call the Next method on this interface and expect the current track to change. If CanControl is false, this property should also be false. ''' pass @DbusAttr def CanGoPrevious(self): ''' **Returns** Read only When this property changes, the org.freedesktop.DBus.Properties.PropertiesChanged signal is emitted with the new value. Whether the client can call the Previous method on this interface and expect the current track to change. If CanControl is false, this property should also be false. ''' pass @DbusAttr def CanPlay(self): ''' **Returns** Read only When this property changes, the org.freedesktop.DBus.Properties.PropertiesChanged signal is emitted with the new value. Whether playback can be started using Play or PlayPause. Note that this is related to whether there is a "current track": the value should not depend on whether the track is currently paused or playing. In fact, if a track is currently playing CanControl is true), this should be true. If CanControl is false, this property should also be false. ''' pass @DbusAttr def CanPause(self): ''' **Returns** Read only When this property changes, the org.freedesktop.DBus.Properties.PropertiesChanged signal is emitted with the new value. Whether playback can be paused using Pause or PlayPause. Note that this is an intrinsic property of the current track: its value should not depend on whether the track is currently paused or playing. In fact, if playback is currently paused (and CanControl is true), this should be true. If CanControl is false, this property should also be false. ''' pass @DbusAttr def CanSeek(self): ''' **Returns** Read only When this property changes, the org.freedesktop.DBus.Properties.PropertiesChanged signal is emitted with the new value. Whether the client can control the playback position using Seek and SetPosition. This may be different for different tracks. If CanControl is false, this property should also be false. ''' pass @DbusAttr def CanControl(self): ''' **Returns** Read only The org.freedesktop.DBus.Properties.PropertiesChanged signal is not emitted when this property changes. Whether the media player may be controlled over this interface. This property is not expected to change, as it describes an intrinsic capability of the implementation. If this is false, clients should assume that all properties on this interface are read-only (and will raise errors if writing to them is attempted); all methods are not implemented and all other properties starting with "Can" are also false. ''' pass if __name__ == '__main__': from mpris2.utils import SomePlayers #uri = Interfaces.MEDIA_PLAYER + '.' + SomePlayers.GMUSICBROWSER #mp2 = Player(dbus_interface_info={'dbus_uri': uri}) #print mp2.LoopStatus #print mp2.Shuffle #mp2.Shuffle = False if mp2.Shuffle else True #print mp2.Shuffle from dbus.mainloop.glib import DBusGMainLoop DBusGMainLoop(set_as_default=True) import gobject def my_handler(self, Position): print 'handled', Position, type(Position) print 'self handled', self.last_fn_return, type(self.last_fn_return) def another_handler(self, *args, **kw): print args, kw mloop = gobject.MainLoop() #print mp2.Seeked #mp2.Seeked = my_handler #mp2.PropertiesChanged = another_handler from mpris2.utils import get_session s = get_session() s.add_signal_receiver(another_handler, "PropertiesChanged", "org.freedesktop.DBus.Properties", path="/org/mpris/MediaPlayer2") mloop.run() autoradio-upstream-2.8.2+dfsg.orig/autoradio/mpris2/__init__.py0000644000175000017500000000730412332264611025002 0ustar capriottcapriott#!/usr/bin/python # -*- coding: UTF8 -* ''' This is mprisV2.1 documentation http://www.mpris.org/2.1/spec/index.html Also works as python lib. Version 2.1 =========== Copyright © 2006-2010 the VideoLAN team(Mirsal Ennaime, Rafaël Carré, Jean-Paul Saman) Copyright © 2005-2008 Milosz Derezynski Copyright © 2008 Nick Welch Copyright © 2010 Alex Merry This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. About ===== The Media Player Remote Interfacing Specification is a standard D-Bus interface which aims to provide a common programmatic API for controlling media players. It provides a mechanism for compliant media players discovery, basic playback and media player state control as well as a tracklist interface which is used to add context to the current item. Changes ======= From 2.0 to 2.1: Added the optional org.mpris.MediaPlayer2.Playlists interface. Bus Name Policy =============== Each media player *must* request a unique bus name which begins with *org.mpris.MediaPlayer2*. For example: * org.mpris.MediaPlayer2.audacious * org.mpris.MediaPlayer2.vlc * org.mpris.MediaPlayer2.bmp * org.mpris.MediaPlayer2.xmms2 This allows clients to list available media players (either already running or which can be started via D-Bus activation) In the case where the media player allows multiple instances running simultaneously, each additional instance should request a unique bus name, adding a dot and a unique identifier (such as a UNIX process id) to its usual bus name. For example, this could be * org.mpris.MediaPlayer2.vlc.7389 Entry point =========== The media player *must* expose the */org/mpris/MediaPlayer2* object path, which *must* implement the following interfaces: * org.mpris.MediaPlayer2 * org.mpris.MediaPlayer2.Player The */org/mpris/MediaPlayer2* object may implement the *org.mpris.MediaPlayer2.TrackList* interface. The */org/mpris/MediaPlayer2* object may implement the *org.mpris.MediaPlayer2.Playlists* interface. The PropertiesChanged signal ============================ The MPRIS uses the org.freedesktop.DBus.Properties.PropertiesChanged signal to notify clients of changes in the media player state. If a client implementation uses D-Bus bindings which do not support this signal, then it should connect to it manually. If a media player implementation uses D-Bus bindings which do not support this signal, then it should send it manually Corrections =========== 2010-09-26: Added EmitsChangedSignal annotation to Volume property on the Player interface. 2011-01-26: Added PlaylistChanged signal to the Playlists interface. Interfaces ========== * org.mpris.MediaPlayer2 * org.mpris.MediaPlayer2.TrackList * org.mpris.MediaPlayer2.Player * org.mpris.MediaPlayer2.Playlists ''' from interfaces import Interfaces from mediaplayer2 import MediaPlayer2 from player import Player from playlists import Playlists from tracklist import TrackList import types as types import utils as utils if __name__ == '__main__': print Interfaces print MediaPlayer2 print Player print Playlists print TrackList print types print utils autoradio-upstream-2.8.2+dfsg.orig/autoradio/mpris2/playback_status.py0000644000175000017500000000125512332264611026433 0ustar capriottcapriottPLAYING = 'Playing' PAUSED = 'Paused' STOPPED = 'Stopped' class Playback_Status(str): ''' A playback state. * Playing (Playing) A track is currently playing. * Paused (Paused) A track is currently paused. * Stopped (Stopped) There is no track currently playing. ''' VALUES = (PLAYING, PAUSED, STOPPED) def __init__(self, status, *args, **kw): super(Playback_Status, self).__init__(status, *args, **kw) self._status = status @property def status(self): return self._status Playback_Status.PLAYING = PLAYING Playback_Status.PAUSED = PAUSED Playback_Status.STOPPED = STOPPEDautoradio-upstream-2.8.2+dfsg.orig/autoradio/mpris2/some_players.py0000644000175000017500000000310212332264611025735 0ustar capriottcapriottclass Some_Players(object): ''' Not defined in documentation Maybe this player (and other) implement mpris2 **Some players** * AUDACIOUS "audacious" * AUTOPLAYER "AutoPlayer" * BANSHEE "banshee" * BEATBOX "beatbox" * BMP "bmp" * CLEMENTINE "clementine" * DRAGONPLAYER "dragonplayer" * EXAILE "exaile" * GMUSICBROWSER "gmusicbrowser" * GMPC "gmpc" * GUAYADEQUE "guayadeque" * MOPIDY "mopidy" * MPDRIS "mpDris" * QUODLIBET "quodlibet" * RAVEND "ravend" * RHYTHMBOX "rhythmbox" * SPOTIFY "spotify" * VLC "vlc" * XBMC "xbmc" * XMMS2 "xmms2" * XNOISE "xnoise" ''' #Some players AUDACIOUS = "audacious" AUTOPLAYER = "AutoPlayer" BANSHEE = "banshee" BEATBOX = "beatbox" BMP = "bmp" CLEMENTINE = "clementine" DRAGONPLAYER = "dragonplayer" EXAILE = "exaile" GMUSICBROWSER = "gmusicbrowser" GMPC = "gmpc" GUAYADEQUE = "guayadeque" MOPIDY = "mopidy" MPDRIS = "mpDris" QUODLIBET = "quodlibet" RAVEND = "ravend" RHYTHMBOX = "rhythmbox" SPOTIFY = "spotify" VLC = "vlc" XBMC = "xbmc" XMMS2 = "xmms2" XNOISE = "xnoise" @staticmethod def get_dict(): result = {} for key in dir(Some_Players): if key[0] not in ('_', 'g'): result[key] = getattr(Some_Players, key) return result autoradio-upstream-2.8.2+dfsg.orig/autoradio/mpris2/uri.py0000644000175000017500000000045712332264611024044 0ustar capriottcapriottclass Uri(str): '''A unique resource identifier.''' def __init__(self, uri, *args, **kw): super(Uri, self).__init__(uri, *args, **kw) self._uri = uri @property def uri(self): return self._uri if __name__ == "__main__": print Uri('http://www.com.br')autoradio-upstream-2.8.2+dfsg.orig/autoradio/mpris2/utils.py0000644000175000017500000000460512332264611024404 0ustar capriottcapriott''' utils functions not defined in espec Created on Nov 6, 2011 @author: hugosenari ''' import dbus, re from some_players import Some_Players as SomePlayers from interfaces import Interfaces def _match_players_uri(name, pattern='.+'): ''' Filter logic for get_players and get_player_uri @param name: string name to test @param pattern=None: string regexp to test @return: boolean ''' return \ re.match('org.mpris.MediaPlayer2', name)\ and re.match(pattern, name) def get_session(busaddress=None): ''' @return: dbus.SessionBus.get_session() ''' if busaddress is None: return dbus.SessionBus.get_session() else: return dbus.bus.BusConnection(busaddress) def get_players_uri(pattern='.',busaddress=None): """ Return string of player bus name @param pattern=None: string regex that filter response @return: array string of players bus name """ return [item for item in get_session(busaddress).list_names() if _match_players_uri(item, pattern)] def get_player_id_from_uri(uri): """ Returns player mpris2 id from uri @param uri: string mpris2 player dbus uri @return: string mrpis2 id """ print uri mateched = re.match(Interfaces.MEDIA_PLAYER + '\.(.+)', uri or '') return mateched.groups()[0]\ if mateched\ else '' def get_players_id(pattern=None): """ Return string of player mpris2 id @param pattern=None: string regex that filter response @return: array string of players bus name """ for item in get_session().list_names(): if _match_players_uri(item, pattern): yield get_player_id_from_uri(item) def get_intances_of(what_to_instantiate, pattern): """ Return new instance of what_to_instantiate @param what_to_instantiate: class or function with dbus_uri only param @param pattern=None: string regexo that filter response @return: array string of players bus name """ return [what_to_instantiate(dbus_uri=item) for item in get_session().list_names() if _match_players_uri(item, pattern)] def unix_path_to_uri(): pass if __name__ == '__main__': print get_players_uri() print SomePlayers.get_dict() print get_player_id_from_uri('org.mpris.MediaPlayer2.banshee') autoradio-upstream-2.8.2+dfsg.orig/autoradio/mpris2/interfaces.py0000644000175000017500000000204412332264611025362 0ustar capriottcapriott# -*- coding: UTF8 -* """ This is mprisV2.1 documentation http://www.mpris.org/2.1/spec/index.html """ class Interfaces(object): """ This class contains the constants defined at index of MPRIS2 definition: **Interfaces:** * MEDIA_PLAYER 'org.mpris.MediaPlayer2' * TRACK_LIST 'org.mpris.MediaPlayer2.TrackList' * PLAYER 'org.mpris.MediaPlayer2.Player' * PLAYLISTS 'org.mpris.MediaPlayer2.Playlists' * PROPERTIES 'org.freedesktop.DBus.Properties' **Signals:** * SIGNAL 'PropertiesChanged' **Objects:** * OBJECT_PATH '/org/mpris/MediaPlayer2' """ #interface MEDIA_PLAYER = 'org.mpris.MediaPlayer2' TRACK_LIST = 'org.mpris.MediaPlayer2.TrackList' PLAYER = 'org.mpris.MediaPlayer2.Player' PLAYLISTS = 'org.mpris.MediaPlayer2.Playlists' PROPERTIES = 'org.freedesktop.DBus.Properties' #signal SIGNAL = 'PropertiesChanged' #Object OBJECT_PATH = '/org/mpris/MediaPlayer2' autoradio-upstream-2.8.2+dfsg.orig/autoradio/mpris2/volume.py0000644000175000017500000000122412332264611024545 0ustar capriottcapriott''' Audio Volume ''' class Volume(float): ''' Audio volume level * 0.0 means mute. * 1.0 is a sensible maximum volume level (ex: 0dB). Note that the volume may be higher than 1.0, although generally clients should not attempt to set it above 1.0. ''' MIN = 0.0 MAX = 1.0 RANGE = set([n/10.0 for n in range(11)]) def __init__(self, volume=1.0, *args, **kw): super(Volume, self).__init__(volume, *args, **kw) self._volume = volume @property def volume(self): '''Get volume atrribute''' return self._volume if __name__ == "__main__": print Volume(1)autoradio-upstream-2.8.2+dfsg.orig/autoradio/mpris2/time_in_us.py0000644000175000017500000000064312332264611025375 0ustar capriottcapriott''' Created on Nov 5, 2011 @author: hugosenari ''' class Time_In_Us(int): '''Time in microseconds.''' def __init__(self, time=0, *args, **kw): '''constructor''' super(Time_In_Us, self).__init__(time, *args, **kw) self._time = time @property def time(self): '''get time value''' return self._time if __name__ == "__main__": print Time_In_Us(10)autoradio-upstream-2.8.2+dfsg.orig/autoradio/mpris2/tracklist.py0000644000175000017500000002167012332264611025245 0ustar capriottcapriott""" This is python mprisV2.1 documentation http://www.mpris.org/2.1/spec/TrackList_Node.html """ from autoradio.pydbusdecorator.dbus_attr import DbusAttr from autoradio.pydbusdecorator.dbus_interface import DbusInterface from autoradio.pydbusdecorator.dbus_method import DbusMethod from autoradio.pydbusdecorator.dbus_signal import DbusSignal from interfaces import Interfaces from types import Metadata_Map class TrackList(Interfaces): ''' Interface for TrackList (org.mpris.MediaPlayer2.TrackList) Provides access to a short list of tracks which were recently played or will be played shortly. This is intended to provide context to the currently-playing track, rather than giving complete access to the media player's playlist. Example use cases are the list of tracks from the same album as the currently playing song or the Rhythmbox play queue. Each track in the tracklist has a unique identifier. The intention is that this uniquely identifies the track within the scope of the tracklist. In particular, if a media item (a particular music file, say) occurs twice in the track list, each occurrence should have a different identifier. If a track is removed from the middle of the playlist, it should not affect the track ids of any other tracks in the tracklist. As a result, the traditional track identifiers of URLs and position in the playlist cannot be used. Any scheme which satisfies the uniqueness requirements is valid, as clients should not make any assumptions about the value of the track id beyond the fact that it is a unique identifier. Note that the (memory and processing) burden of implementing the TrackList interface and maintaining unique track ids for the playlist can be mitigated by only exposing a subset of the playlist when it is very long (the 20 or so tracks around the currently playing track, for example). This is a recommended practice as the tracklist interface is not designed to enable browsing through a large list of tracks, but rather to provide clients with context about the currently playing track. ''' PROPERTIES_TACKS = 'Tracks' PROPERTIES_CAN_EDIT_TRACKS = 'CanEditTracks' SIGNALS_TRACK_LIST_REPLACED = 'TrackListReplaced' SIGNALS_TRACK_ADDED = 'TrackAdded' SIGNALS_TRACK_REMOVED = 'TrackRemoved' SIGNALS_TRACK_METADATA_CHANGED = 'TrackMetadataChanged' SIGNALS_PROPERTIES_CHANGED = 'PropertiesChanged' @DbusInterface(Interfaces.TRACK_LIST, Interfaces.OBJECT_PATH) def __init__(self): '''Constructor''' pass @DbusMethod(produces=lambda map_list:\ [Metadata_Map(metadata_map) for metadata_map in map_list]) def GetTracksMetadata(self, TrackIds): ''' **Parameters:** * TrackIds - ao (Track_Id_List) The list of track ids for which metadata is requested. **Returns** * Metadata - aa{sv} (Metadata_Map_List) Metadata of the set of tracks given as input. See the type documentation for more details. Gets all the metadata available for a set of tracks. Each set of metadata must have a "mpris:trackid" entry at the very least, which contains a string that uniquely identifies this track within the scope of the tracklist. ''' pass @DbusMethod def AddTrack(self, Uri, AfterTrack='', SetAsCurrent=False): ''' **Parameters:** * Uri - s (Uri) The uri of the item to add. Its uri scheme should be an element of the org.mpris.MediaPlayer2.SupportedUriSchemes property and the mime-type should match one of the elements of the org.mpris.MediaPlayer2.SupportedMimeTypes * AfterTrack - o (Track_Id) The identifier of the track after which the new item should be inserted. An empty string means at the begining of the track list. * SetAsCurrent - b Whether the newly inserted track should be considered as the current track. Setting this to trye has the same effect as calling GoTo afterwards. Adds a URI in the TrackList. If the CanEditTracks property is false, this has no effect. .. note:: Clients should not assume that the track has been added at the time when this method returns. They should wait for a TrackAdded (or TrackListReplaced) signal. ''' pass @DbusMethod def RemoveTrack(self, TrackId): ''' **Parameters:** * TrackId - o (TrackId) Identifier of the track to be removed. Removes an item from the TrackList. If the track is not part of this tracklist, this has no effect. If the CanEditTracks property is false, this has no effect. .. note:: Clients should not assume that the track has been removed at the time when this method returns. They should wait for a TrackRemoved (or TrackListReplaced) signal. ''' pass @DbusMethod def GoTo(self, TrackId): ''' **Parameters:** * TrackId - o (Track_Id) Identifier of the track to skip to. Skip to the specified TrackId. If the track is not part of this tracklist, this has no effect. If this object is not /org/mpris/MediaPlayer2, the current TrackList's tracks should be replaced with the contents of this TrackList, and the TrackListReplaced signal should be fired from /org/mpris/MediaPlayer2. ''' @DbusSignal def TrackListReplaced(self, Tracks, CurrentTrack): ''' **Parameters:** * Tracks - ao (Track_Id_List) The new content of the tracklist. * CurrentTrack - o (Track_Id) The identifier of the track to be considered as current. Indicates that the entire tracklist has been replaced. It is left up to the implementation to decide when a change to the track list is invasive enough that this signal should be emitted instead of a series of TrackAdded and TrackRemoved signals. ''' pass @DbusSignal def TrackAdded(self, Metadata, AfterTrack=''): ''' **Parameters:** * Metadata - a{sv} (Metadata_Map) The metadata of the newly added item. This must include a mpris:trackid entry. See the type documentation for more details. * AfterTrack - o (Track_Id) The identifier of the track after which the new track was inserted. An empty string means at the begining of the tracklist. Indicates that a track has been added to the track list. ''' pass @DbusSignal def TrackRemoved(self, TrackId): ''' **Parameters:** * TrackId - o (Track_Id) The identifier of the track being removed. Indicates that a track has been removed from the track list. ''' pass @DbusSignal def TrackMetadataChanged(self, TrackId, Metadata): ''' **Parameters:** * TrackId - o (Track_Id) The id of the track which metadata has changed. * Metadata - a{sv} (Metadata_Map) The new track metadata. This must include a mpris:trackid entry. See the type documentation for more details. Indicates that the metadata of a track in the tracklist has changed. This may indicate that a track has been replaced, in which case the mpris:trackid metadata entry is different from the TrackId argument. ''' pass @DbusAttr def Tracks(self): ''' **Returns:** Read only When this property changes, the org.freedesktop.DBus.Properties.PropertiesChanged signal is emitted, but the new value is not sent. An array which contains the identifier of each track in the tracklist, in order. The org.freedesktop.DBus.Properties.PropertiesChanged signal is emited every time this property changes, but the signal message does not contain the new value. Client implementations should rather rely on the TrackAdded, TrackRemoved and TrackListReplaced signals to keep their representation of the tracklist up to date. ''' pass @DbusAttr def CanEditTracks(self): ''' **Returns:** Read only When this property changes, the org.freedesktop.DBus.Properties.PropertiesChanged signal is emitted with the new value. If false, calling AddTrack or RemoveTrack will have no effect, and may raise a NotSupported error. ''' pass if __name__ == '__main__': from mpris2.utils import SomePlayers uri = Interfaces.MEDIA_PLAYER + '.' + SomePlayers.GMUSICBROWSER mp2 = TrackList(dbus_interface_info={'dbus_uri': uri}) #some one know any player that support it? print mp2 autoradio-upstream-2.8.2+dfsg.orig/autoradio/mpris2/playback_rate.py0000644000175000017500000000077312332264611026047 0ustar capriottcapriottclass Playback_Rate(float): ''' A playback rate This is a multiplier, so a value of 0.5 indicates that playback is happening at half speed, while 1.5 means that 1.5 seconds of "track time" is consumed every second. ''' def __init__(self, rate=1.0): self._rate = rate super(Playback_Rate, self).__init__(rate) @property def rate(self): return self._rate if __name__ == "__main__": pr = Playback_Rate(12) print pr == '12'autoradio-upstream-2.8.2+dfsg.orig/autoradio/mpris2/mediaplayer2.py0000644000175000017500000001524712332264611025626 0ustar capriottcapriott""" This is python mprisV2.1 documentation http://www.mpris.org/2.1/spec/Root_Node.html """ from autoradio.pydbusdecorator.dbus_attr import DbusAttr from autoradio.pydbusdecorator.dbus_interface import DbusInterface from autoradio.pydbusdecorator.dbus_method import DbusMethod from autoradio.pydbusdecorator.dbus_signal import DbusSignal from interfaces import Interfaces class MediaPlayer2(Interfaces): ''' Interface for MediaPlayer2 (org.mpris.MediaPlayer2) ''' PROPERTIES_CAN_QUIT = 'CanQuit' PROPERTIES_CAN_RAISE = 'CanRaise' PROPERTIES_HAS_TRACK_LIST = 'HasTrackList' PROPERTIES_IDENTITY = 'Identity' PROPERTIES_DESKTOP_ENTRY = 'DesktopEntry' PROPERTIES_SUPPORTED_URI_SCHEMES = 'SupportedUriSchemes' PROPERTIES_SUPPORTED_MINE_TYPES = 'SupportedMimeTypes' SIGNALS_PROPERTIES_CHANGED = 'PropertiesChanged' @DbusInterface(Interfaces.MEDIA_PLAYER, Interfaces.OBJECT_PATH) def __init__(self): '''Constructor''' pass @DbusMethod def Raise(self): """ Brings the media player's user interface to the front using any appropriate mechanism available. The media player may be unable to control how its user interface is displayed, or it may not have a graphical user interface at all. In this case, the CanRaise property is false and this method does nothing. """ return None @DbusMethod def Quit(self): """ Causes the media player to stop running. The media player may refuse to allow clients to shut it down. In this case, the CanQuit property is false and this method does nothing. ..note:: Media players which can be D-Bus activated, or for which there is no sensibly easy way to terminate a running instance (via the main interface or a notification area icon for example) should allow clients to use this method. Otherwise, it should not be needed. If the media player does not have a UI, this should be implemented """ pass @DbusAttr def CanQuit(self): """ **Returns** Read only Inject attrs from decorator at new object then return obje When this property changes, the org.freedesktop.DBus.Properties.PropertiesChanged signal is emitted with the new value. If false, calling Quit will have no effect, and may raise a NotSupported error. If true, calling Quit will cause the media application to attempt to quit (although it may still be prevented from quitting by the user, for example). """ pass @DbusAttr def CanRaise(self): """ **Returns** Read only When this property changes, the org.freedesktop.DBus.Properties.PropertiesChanged signal is emitted with the new value. If false, calling Raise will have no effect, and may raise a NotSupported error. If true, calling Raise will cause the media application to attempt to bring its user interface to the front, although it may be prevented from doing so (by the window manager, for example). """ pass @DbusAttr def HasTrackList(self): """ **Returns** Read only When this property changes, the org.freedesktop.DBus.Properties.PropertiesChanged signal is emitted with the new value. Indicates whether the /org/mpris/MediaPlayer2 object implements the org.mpris.MediaPlayer2.TrackList interface. """ pass @DbusAttr def Identity(self): """ **Returns** Read only When this property changes, the org.freedesktop.DBus.Properties.PropertiesChanged signal is emitted with the new value. A friendly name to identify the media player to users. This should usually match the name found in .desktop files (eg: "VLC media player"). """ pass @DbusAttr def DesktopEntry(self): """ **Returns** Read only When this property changes, the org.freedesktop.DBus.Properties.PropertiesChanged signal is emitted with the new value. The basename of an installed .desktop file which complies with the Desktop entry specification, with the ".desktop" extension stripped. Example: The desktop entry file is "/usr/share/applications/vlc.desktop", and this property contains "vlc" This property is optional. Clients should handle its absence gracefully """ pass @DbusAttr def SupportedUriSchemes(self): """ **Returns** Read only When this property changes, the org.freedesktop.DBus.Properties.PropertiesChanged signal is emitted with the new value. The URI schemes supported by the media player. This can be viewed as protocols supported by the player in almost all cases. Almost every media player will include support for the "file" scheme. Other common schemes are "http" and "rtsp". Note that URI schemes should be lower-case. .. note:: This is important for clients to know when using the editing capabilities of the Playlist interface, for example. """ pass @DbusAttr def SupportedMimeTypes(self): """ **Returns** Read only When this property changes, the org.freedesktop.DBus.Properties.PropertiesChanged signal is emitted with the new value. The mime-types supported by the media player. Mime-types should be in the standard format (eg: audio/mpeg or application/ogg). .. note:: This is important for clients to know when using the editing capabilities of the Playlist interface, for example. """ pass @DbusSignal(iface=Interfaces.PROPERTIES) def PropertiesChanged(self, *args, **kw): """ **Parameters:** * args - list unnamed parameters passed by dbus signal * kw - dict named parameters passed by dbus signal Every time that some property change, signal will be called """ pass if __name__ == '__main__': from mpris2.utils import SomePlayers uri = Interfaces.MEDIA_PLAYER + '.' + SomePlayers.GMUSICBROWSER mp2 = MediaPlayer2(dbus_interface_info={'dbus_uri': uri}) print mp2.SupportedUriSchemes # # # from dbus.mainloop.glib import DBusGMainLoop # DBusGMainLoop(set_as_default=True) # import gobject # # def another_handler(self, *args, **kw): # print args, '\n', kw # # mloop = gobject.MainLoop() # mp2.PropertiesChanged = another_handler # mloop.run() autoradio-upstream-2.8.2+dfsg.orig/autoradio/mpris2/playlist.py0000644000175000017500000000337312332264611025106 0ustar capriottcapriottfrom dbus import Struct class Playlist(Struct): ''' A data structure describing a playlist. * Id - o (Playlist_Id) A unique identifier for the playlist. This should remain the same if the playlist is renamed. * Name - s The name of the playlist, typically given by the user. * Icon - s (Uri) The URI of an (optional) icon. ''' def __init__(self, playlist): Struct.__init__( self, iter(playlist), signature=playlist.signature, variant_level=playlist.variant_level ) @property def Id(self): return self[0] @property def Name(self): return self[1] @property def Icon(self): return self[2] class Maybe_Playlist(Struct): ''' * Valid - b Whether this structure refers to a valid playlist. * Playlist - (oss) (Playlist) The playlist, providing Valid is true, otherwise undefined. When constructing this type, it should be noted that the playlist ID must be a valid object path, or D-Bus implementations may reject it. This is true even when Valid is false. It is suggested that "/" is used as the playlist ID in this case. ''' def __init__(self, maybe_playlist=None): Struct.__init__( self, (maybe_playlist[0], maybe_playlist[1]), signature=maybe_playlist.signature, variant_level=maybe_playlist.variant_level ) @property def Valid(self): return self[0] @property def Playlist(self): return Playlist(self[1]) def __nonzero__(self): return bool(self.Valid) def __bool__(self): return self.__nonzero__()autoradio-upstream-2.8.2+dfsg.orig/autoradio/mpris2/playlist_ordering.py0000644000175000017500000000231612332264611026773 0ustar capriottcapriott''' Created on Nov 12, 2011 @author: hugosenari ''' ALPHABETICAL = 'Alphabetical' CREATION_DATE = 'CreationDate' MODIFIED_DATE = 'ModifiedDate' LAST_PLAY_DATE = 'LastPlayDate' USER_DEFINE = 'UserDefined' class Playlist_Ordering(str): ''' Specifies the ordering of returned playlists. * Alphabetical (Alphabetical) Alphabetical ordering by name, ascending. * CreationDate (Created) Ordering by creation date, oldest first. * ModifiedDate (Modified) Ordering by last modified date, oldest first. * LastPlayDate (Played) Ordering by date of last playback, oldest first. * UserDefined (User) A user-defined ordering. ''' ALPHABETICAL = ALPHABETICAL CREATION_DATE = CREATION_DATE MODIFIED_DATE = MODIFIED_DATE LAST_PLAY_DATE = LAST_PLAY_DATE USER_DEFINE = USER_DEFINE VALUES = (ALPHABETICAL,CREATION_DATE,MODIFIED_DATE,LAST_PLAY_DATE,USER_DEFINE) def __init__(self, ordering, *args, **kw): ''' Constructor ''' self._ordering = ordering super(Playlist_Ordering, self).__init__(ordering, *args, **kw) @property def ordering(self): return self._ordering autoradio-upstream-2.8.2+dfsg.orig/autoradio/mpris2/types.py0000644000175000017500000000130312332264611024400 0ustar capriottcapriott''' Created on Nov 8, 2011 @author: hugosenari ''' from loop_status import Loop_Status from metada_map import Metadata_Map from playback_rate import Playback_Rate from playback_status import Playback_Status from playlist import Playlist from playlist import Maybe_Playlist from playlist_id import Playlist_Id from playlist_ordering import Playlist_Ordering from time_in_us import Time_In_Us from uri import Uri from volume import Volume if __name__ == '__main__': print Loop_Status print Metadata_Map print Playback_Rate print Playback_Status print Playlist print Playlist_Id print Playlist_Ordering print Maybe_Playlist print Time_In_Us print Uri print Volumeautoradio-upstream-2.8.2+dfsg.orig/autoradio/managempris.py0000644000175000017500000001736512332264611024342 0ustar capriottcapriott#!/usr/bin/env python # GPL. (C) 2007-2012 Paolo Patruno. import logging import dbus import autompris import autompris2 from datetime import * from threading import * import os import autoradio_config os.environ['DJANGO_SETTINGS_MODULE'] = 'settings' from django.conf import settings class PlayerError(Exception): def __str__(self): return repr(self.args[0]) def shuffle_playlist(infile,shuffle=False,relative_path=False,length=None): import mkplaylist import os,random,tempfile,codecs media_files=list(mkplaylist.read_playlist(infile, not relative_path)) if shuffle: random.shuffle(media_files) # else: # media_files.sort() fd,outfile=tempfile.mkstemp(".m3u") #ffoutfile = os.fdopen(fd,"w") foutfile = codecs.open(outfile, "w", encoding="UTF-8") mkplaylist.write_extm3u(media_files, foutfile,length) foutfile.close() os.close(fd) return outfile lock = Lock() def ar_emitted(self): ''' Save in django datatime when emission is done ''' self.emission_done=datetime.now() self.save() class ScheduleProgram: ''' activate a schedule setting it for a time in the future ''' def __init__ (self,player,session,schedule): "init schedule" self.deltasec=secondi( schedule.scheduledatetime - datetime.now()) # round to nearest future if self.deltasec < 5 : self.deltasec = 5 self.player=player self.session=session self.function=ManagePlayer self.schedule=schedule self.timer = Timer(self.deltasec, self.function,[self.player,self.session,self.schedule]) def start (self): "start of programmed schedule" self.timer.start() def ManagePlayer (player,session,schedule): "Manage player to do operation on media" try: if ( schedule.type == "spot" ): operation="queueMedia" elif ( schedule.type == "program" ): operation="queueMedia" elif ( schedule.type == "jingle" ): operation="queueMedia" elif ( schedule.type == "playlist" ): operation="loadPlaylist" else: raise PlayerError("Managempris: type not supported: %s"% schedule.type) try: if operation == "loadPlaylist": media=shuffle_playlist(schedule.filename,schedule.shuffle,relative_path=False,length=schedule.maxlength) else: media=schedule.filename if player == "vlc" or player == "AutoPlayer": aud = autompris2.mediaplayer(player=player,session=session) else: aud = autompris.mediaplayer(player=player,session=session) except: raise PlayerError("Managempris: error connecting to player dbus") # Regione critica lock.acquire() try: if not aud.playlist_clear_up(atlast=10): raise PlayerError("Managempris: ERROR in playlist_clear_up") #print settings.MEDIA_ROOT pos=aud.get_playlist_posauto(autopath=settings.MEDIA_ROOT,securesec=10) curpos=aud.get_playlist_pos() # inserisco il file nella playlist if pos is None: raise PlayerError("Managempris: ERROR in xmms.control.get_playlist_posauto") logging.info( "ManageXmms: insert media: %s at position %d",media,pos) aud.playlist_add_atpos("file://"+media,pos) # recheck for consistency newpos=aud.get_playlist_pos() if curpos != newpos: raise PlayerError("Managempris: strange ERROR: consinstency problem; pos: %s , newpos: %s"% (str(curpos),str(newpos))) if not aud.playlist_clear_down(atlast=500): raise PlayerError("Managempris: ERROR in playlist_clear_down") finally: #signal.alarm(0) lock.release() # here we have a problem ... sometime the player is not ready when the file is deleted ! # so we comment it out # if schedule.shuffle: # os.remove(media) logging.info( "Managempris: write in django: %s",schedule.djobj) ar_emitted(schedule.djobj) logging.info( "Managempris: written in django: %s",schedule.djobj) aud.play_ifnot() except PlayerError, e: logging.error(e) except dbus.DBusException, e: logging.error(e) except: logging.error("generic error in ManagePlayer") return def secondi(delta): secondi=float(delta.seconds) secondi=secondi+(delta.microseconds/100000.) if delta.days < 0 : secondi = secondi + (3600*24*delta.days) return secondi class dummy_programma: def __init__(self): pass def save(self): #print "masquerade as we save it" pass def player_watchdog(player,session): logging.debug( "player_watchdog: test if player is running" ) try: if player == "vlc" or player == "AutoPlayer": aud = autompris2.mediaplayer(player=player,session=session) else: aud = autompris.mediaplayer(player=player,session=session) except: logging.error("player_watchdog: player do not communicate on d-bus") if player == "audacious" or player == "xmms": import subprocess try: logging.info("player_watchdog: try launching player") subprocess.Popen(player , shell=True) except: logging.error("player_watchdog: error launching "+player) if player == "xmms": try: logging.info("player_watchdog: try launching "+player+"2") subprocess.Popen(player+"2" , shell=True) except: logging.error("player_watchdog: error launching "+player+"2") import time time.sleep(5) logging.info("player_watchdog: player executed") try: if player == "vlc" or player == "AutoPlayer": aud = autompris2.mediaplayer(player=player,session=session) else: aud = autompris.mediaplayer(player=player,session=session) except: logging.error("player_watchdog serious problem: player do not comunicate on d-bus") try: aud.play_ifnot() logging.debug("player_watchdog: start playing if not") except: logging.error("player_watchdog: cannot start playing if not") return True def save_status(session): """ Do nothing """ logging.debug ( "DUMMY xmms.saveCurrentPlaylist") return True def main(): import autoradio_core player="AutoPlayer" session=0 logging.getLogger('').setLevel(logging.DEBUG) programma=dummy_programma() player_watchdog(player=player,session=session) shuffle=False maxlength=None type="program" media = "/home/pat1/svn/autoradio/trunk/media/pippo.mp3" #media = "/home/pat1/Musica/STOP AL PANICO/ISOLA POSSE STOP AL PANICO.mp3" #media = "/home/autoradio/django/media/playlist/tappeto_musicale.m3u" #media = raw_input("dammi il media? ") scheduledatetime=datetime.now()+timedelta(seconds=5) sched=autoradio_core.schedule(programma,scheduledatetime,media,filename=media,type=type,shuffle=shuffle,maxlength=maxlength) threadschedule=ScheduleProgram(player,session,sched) threadschedule.start() # scheduledatetime=datetime.now()+timedelta(seconds=8) # media = "/home/autoradio/django/media/programs/borsellino_giordano.mp3" # schedule=ScheduleProgram(session,function,operation,media,scheduledatetime,programma,shuffle) # schedule.start() # scheduledatetime=datetime.now()+timedelta(seconds=10) # media = "/home/autoradio/django/media/programs/mister_follow_follow.mp3" # schedule=ScheduleProgram(session,function,operation,media,scheduledatetime,programma,shuffle) # schedule.start() if __name__ == '__main__': main() # (this code was run as script) autoradio-upstream-2.8.2+dfsg.orig/autoradio/autompris.py0000644000175000017500000002314712332264611024055 0ustar capriottcapriott#!/usr/bin/env python # GPL. (C) 2007-2009 Paolo Patruno. # ------- dbus mpris 1 interface --------- # note that this work for audacious only # mpris version 1 do not provide interface to insert media # at specified position in playlist # so we have to wait players to implement mpris2 specification to generalize this interface. # Audacious provide non standard interface to do this # ---------------------------------------- import dbus import time import datetime import os import logging import dbus class mediaplayer: def __init__(self,player="audacious",session=0): self.mediaplayer=player self.session=session try: self.bus = dbus.SessionBus() # ----------------------------------------------------------- root_obj = self.bus.get_object("org.mpris."+player, '/') player_obj = self.bus.get_object("org.mpris."+player, '/Player') tracklist_obj = self.bus.get_object("org.mpris."+player, '/TrackList') self.root = dbus.Interface(root_obj, dbus_interface='org.freedesktop.MediaPlayer') self.player = dbus.Interface(player_obj, dbus_interface='org.freedesktop.MediaPlayer') self.tracklist = dbus.Interface(tracklist_obj, dbus_interface='org.freedesktop.MediaPlayer') if player == "audacious": org_obj = self.bus.get_object("org.mpris.audacious", '/org/atheme/audacious') self.org = dbus.Interface(org_obj, dbus_interface='org.atheme.audacious') # ----------------------------------------------------------- except: raise if player == "audacious": from distutils.version import LooseVersion reqversion=LooseVersion("1.5") version=LooseVersion("0.0") try: # aud.root.Identity() version=LooseVersion(self.org.Version()) logging.info("mediaplayer: audacious version: %s" % str(version)) except: logging.error("mediaplayer: eror gettin audacious version") if ( version < reqversion ): logging.error("mediaplayer: audacious %s version is wrong (>=1.5) " % version ) raise Exception def __str__(self): return "mpris 1 interface" def play_ifnot(self): ''' start playng if not. GetStatus Return the status of "Media Player" as a struct of 4 ints: First integer: 0 = Playing, 1 = Paused, 2 = Stopped. Second interger: 0 = Playing linearly , 1 = Playing randomly. Third integer: 0 = Go to the next element once the current has finished playing , 1 = Repeat the current element Fourth integer: 0 = Stop playing once the last element has been played, 1 = Never give up playing ''' status=self.player.GetStatus() if status[0] == 0 : pass elif status[0] == 1 : self.player.Pause() elif status[0] == 2 : self.player.Play() def isplaying(self): ''' return true if is playing. ''' status=self.player.GetStatus() return status[0] == 0 def get_playlist_securepos(self,securesec=10): ''' Try to secure that there are some time (securesec) to complete all operations in time: if audacious change song during operation will be a big problem ''' try: self.play_ifnot() #force to play mintimed=datetime.timedelta(seconds=securesec) toend=datetime.timedelta(seconds=0) volte=0 while ( toend < mintimed ): # take the current position pos=self.tracklist.GetCurrentTrack() metadata=self.tracklist.GetMetadata(pos) #print metadata mtimelength=metadata["mtime"] mtimeposition=self.player.PositionGet() timed=datetime.timedelta(seconds=datetime.timedelta(milliseconds=mtimelength).seconds) toend=timed-datetime.timedelta(seconds=datetime.timedelta(milliseconds=mtimeposition).seconds) newpos=self.tracklist.GetCurrentTrack() if (pos != newpos): #inconsistenza: retry toend=datetime.timedelta(seconds=0) if ( toend < mintimed ): volte +=1 if volte > 10 : break # timeout , I have to play time.sleep(securesec+1) return pos except : return None def playlist_clear_up(self,atlast=10): ''' clear playlist starting from current position up. "atlast" numer of song are retained ''' try: self.play_ifnot() #force to play # take the current position (if error set pos=0) pos=self.get_playlist_securepos() if pos is None: return False # delete the old ones if pos > atlast : for prm in xrange(0,pos-atlast): self.tracklist.DelTrack(0) return True except: return False def playlist_clear_down(self,atlast=500): ''' clear playlist starting from current position + atlast doen. "atlast" numer of song are retained for future play ''' try: self.play_ifnot() #force to play # take the current position (if error set pos=0) pos=self.get_playlist_securepos() if pos is None: return False length=self.tracklist.GetLength() #elimino il troppo if length-pos > atlast : for prm in xrange(length,pos+atlast,-1): self.tracklist.DelTrack(prm) return True except: return False def get_playlist_posauto(self,autopath,securesec=10): ''' get playlist position skipping file with path equal to autopath. Try to secure that there are some time (securesec) to complete all operations in time: if xmms change song during operation will be a big problem ''' try: pos=self.get_playlist_securepos(securesec=securesec) if pos is None: return pos pos+=1 metadata=self.tracklist.GetMetadata(pos) try: #Fix how older versions of Audacious misreport the URI of the song. if metadata is not None: if metadata.has_key ("URI") and not metadata.has_key ("location"): metadata["location"] = metadata["URI"] file=metadata["location"] except: return pos filepath=os.path.dirname(file) #print "file://"+autopath #print os.path.commonprefix ((filepath,"file://"+autopath)) # ora controllo se ci sono gia dei file accodati nella playlist da autoradio # l'unica possibilita di saperlo e verificare il path del file while ( os.path.commonprefix ((filepath,"file://"+autopath)) == "file://"+autopath ): pos+=1 metadata=self.tracklist.GetMetadata(pos) try: #Fix how older versions of Audacious misreport the URI of the song. if metadata is not None: if metadata.has_key ("URI") and not metadata.has_key ("location"): metadata["location"] = metadata["URI"] file=metadata["location"] except: return pos filepath=os.path.dirname(file) # here I have found the first file added by autoradio return pos except : return None def get_playlist_len(self): "get playlist lenght" return self.tracklist.GetLength() def get_playlist_pos(self): "get current position" return self.tracklist.GetCurrentTrack() def get_metadata(self,pos): "get metadata for position" metadata=self.tracklist.GetMetadata(pos) try: file=metadata["location"] except: file=None try: title=metadata["title"] if title=="": title=None except: title=None try: artist=metadata["artist"] if artist=="": artist=None except: artist=None try: mtimelength=metadata["mtime"] except: mtimelength=0 try: mtimeposition=self.player.PositionGet() except: mtimeposition=0 mymeta={ "file": file, "title": title, "artist": artist, "mtimelength": mtimelength, "mtimeposition": mtimeposition } return mymeta def playlist_add_atpos(self,media,pos): "add media at pos postion in the playlist" if self.mediaplayer == "audacious": self.org.PlaylistInsUrlString(media,pos) return None else: logging.error("playlist_add_atpos: mpris interface cannot add media where I want for player "+self.mediaplayer ) raise Exception def main(): mp=mediaplayer(player="audacious") mp.play_ifnot() if __name__ == '__main__': main() # (this code was run as script) autoradio-upstream-2.8.2+dfsg.orig/autoradio/managexmms.py0000644000175000017500000001671412332264611024171 0ustar capriottcapriott#!/usr/bin/env python # GPL. (C) 2007 Paolo Patruno. import logging import xmms,autoxmms from datetime import * from threading import * from django.conf import settings import os import autoradio_config #import signal class XmmsError(Exception): pass def shuffle_playlist(infile,shuffle=False,relative_path=False,length=None): import mkplaylist import os,random,tempfile,codecs media_files=list(mkplaylist.read_playlist(infile, not relative_path)) if shuffle: random.shuffle(media_files) # else: # media_files.sort() fd,outfile=tempfile.mkstemp(".m3u") #ffoutfile = os.fdopen(fd,"w") foutfile = codecs.open(outfile, "w", encoding="UTF-8") mkplaylist.write_extm3u(media_files, foutfile,length) foutfile.close() os.close(fd) return outfile lock = Lock() def ar_emitted(self): ''' Save in django datatime when emission is done ''' self.emission_done=datetime.now() self.save() class ScheduleProgram: ''' activate a schedule setting it for a time in the future ''' def __init__ (self,session,schedule): #session,function,operation, #media,scheduledatetime,programma,shuffle=None,length=None): "init schedule" #self.function=function #self.operation=operation #self.schedule=schedule #self.media=media #self.scheduledatetime=scheduledatetime #self.programma=programma #self.shuffle=shuffle #self.length=length #scheduledatetime #print "difference ",datetime.now(),self.scheduledatetime #self.deltasec=max(secondi( schedule.scheduledatetime - datetime.now()),1) self.deltasec=secondi( schedule.scheduledatetime - datetime.now()) self.session=session self.function=ManageXmms self.schedule=schedule self.timer = Timer(self.deltasec, self.function,[self.session,self.schedule]) # [self.session,self.operation,self.chedule # self.media,self.programma,self.shuffle,self.length]) def start (self): "start of programmed schedule" self.timer.start() def ManageXmms (session,schedule): "Manage xmms to do operation on media" try: if ( schedule.type == "spot" ): operation="queueMedia" elif ( schedule.type == "program" ): operation="queueMedia" elif ( schedule.type == "jingle" ): operation="queueMedia" elif ( schedule.type == "playlist" ): operation="loadPlaylist" else: raise XmmsError("ManageXmms: type not supported: %s"% schedule.type) media=schedule.media if operation == "loadPlaylist": media=shuffle_playlist(schedule.media,schedule.shuffle,relative_path=False,length=schedule.maxlength) # Regione critica lock.acquire() try: if not autoxmms.playlist_clear_up(atlast=10,session=session): raise XmmsError("ManageXmms: ERROR in xmms.control.playlist_clear_up") pos=autoxmms.get_playlist_posauto(autopath=settings.MEDIA_ROOT,securesec=10,session=session) curpos=xmms.control.get_playlist_pos(session) # inserisco il file nella playlist if pos is None: raise XmmsError("ManageXmms: ERROR in xmms.control.get_playlist_posauto") logging.info( "ManageXmms: insert media: %s at position %d",media,pos) xmms.control.playlist_ins_url_string(media,pos,session) #error test impossible # recheck for consistency newpos=xmms.control.get_playlist_pos(session) if curpos != newpos: raise XmmsError("ManageXmms: strange ERROR: consinstency problem; pos: %d , newpos: %d"% (curpos,newpos)) if not autoxmms.playlist_clear_down(atlast=500,session=session): raise XmmsError("ManageXmms: ERROR in xmms.control.playlist_clear_down") finally: #signal.alarm(0) lock.release() if schedule.shuffle: os.remove(media) logging.info( "ManageXmms: write in django: %s",schedule.djobj) ar_emitted(schedule.djobj) logging.info( "ManageXmms: write in django: %s",schedule.djobj) except XmmsError, e: logging.error(e.message) #except: # logging.error( "ManageXmms: ERRORE type: %s, media: %s",schedule.type,schedule.media) return autoxmms.play_ifnot(session=session) def secondi(delta): secondi=float(delta.seconds) secondi=secondi+(delta.microseconds/100000.) # correggo i viaggi che si fa seconds if delta.days < 0 : secondi = secondi + (3600*24*delta.days) return secondi class dummy_programma: def __init__(self): pass def save(self): #print "masquerade as we save it" pass def xmms_watchdog(session): logging.debug( "xmms_watchdog: test if xmms.is running" ) try: ok = xmms.control.is_running(session) logging.debug("xmms_watchdog: xmms.is_running return %s", str(ok)) except: ok=False logging.error("xmms_watchdog: error on xmms.is_running") if (not ok ): logging.error("xmms_watchdog: xmms is not running") try: xmms.control.enqueue_and_play_launch_if_session_not_started("file",session=session, stdout_to_dev_null=True, stderr_to_dev_null=True) ok=True logging.info("xmms_watchdog: pyxmms < 2.07 xmms.control.enqueue_and_play_launch_if_session_not_started") except: try: xmms.control.enqueue_and_play_launch_if_session_not_started("file",session=session) ok=True logging.info("xmms_watchdog: pyxmms >= 2.07 xmms.control.enqueue_and_play_launch_if_session_not_started") except: ok=False logging.error("xmms_watchdog: error xmms.control.enqueue_and_play_launch_if_session_not_started") return ok def save_status(session): # file dovra essere prima salvato usando: # xmms.get_playlist_length(session) (restituisce il numero di brani nella playlist # get_playlist_file(index, session=0) -> absolute filename (string) # get_playlist_pos(session=0) -> position (integer) logging.debug ( "DUMMY xmms.saveCurrentPlaylist") return True def main(): import autoradio_core programma=dummy_programma() session=0 shuffle=True maxlength=None type="playlist" media = "/home/autoradio/django/media/playlist/playlistmingogozza.m3u" #media = "/home/autoradio/django/media/playlist/tappeto_musicale.m3u" #media = raw_input("dammi il media? ") scheduledatetime=datetime.now()+timedelta(seconds=5) sched=autoradio_core.schedule(programma,scheduledatetime,media,type=type,shuffle=shuffle,maxlength=maxlength) threadschedule=ScheduleProgram(session,sched) threadschedule.start() # scheduledatetime=datetime.now()+timedelta(seconds=8) # media = "/home/autoradio/django/media/programs/borsellino_giordano.mp3" # schedule=ScheduleProgram(session,function,operation,media,scheduledatetime,programma,shuffle) # schedule.start() # scheduledatetime=datetime.now()+timedelta(seconds=10) # media = "/home/autoradio/django/media/programs/mister_follow_follow.mp3" # schedule=ScheduleProgram(session,function,operation,media,scheduledatetime,programma,shuffle) # schedule.start() if __name__ == '__main__': main() # (this code was run as script) autoradio-upstream-2.8.2+dfsg.orig/autoradio/__init__.py0000644000175000017500000000002212332264611023554 0ustar capriottcapriott_version_="2.8.2" autoradio-upstream-2.8.2+dfsg.orig/autoradio/gest_program.py0000644000175000017500000001144612332264611024522 0ustar capriottcapriott#!/usr/bin/env python # GPL. (C) 2007-2009 Paolo Patruno. import os os.environ['DJANGO_SETTINGS_MODULE'] = 'autoradio.settings' from django.conf import settings import logging from datetime import * from autoradio_config import * from programs.models import Schedule from programs.models import ScheduleDone from programs.models import Show from programs.models import Configure # to get metadata from audio files import mutagen import os class gest_program: def __init__ (self,now,minelab): """init of program application: now : currenti datetime minelab: minutes to elaborate execute the right data retrival to get the schedued programs""" self.now = now self.minelab = minelab ora=self.now.time() self.schedules=() datesched_min=self.now - timedelta( seconds=60*self.minelab) datesched_max=self.now + timedelta( milliseconds=60000*self.minelab-1) # 1 millisecond tollerance timesched_min=datesched_min.time() timesched_max=datesched_max.time() logging.debug( "PROGRAM: elaborate from %s to %s",datesched_min,datesched_max) if (Configure.objects.filter(active__exact=False).count() == 1): self.schedules=() return #todo: the use of ora here is not exact if (Configure.objects.filter(emission_starttime__gt=ora).count() == 1) : self.schedules=() return if (Configure.objects.filter(emission_endtime__lt=ora).count() == 1): self.schedules=() return # estraggo i record di mio interesse self.schedules=Schedule.objects.select_related()\ .filter(emission_date__gte=datesched_min)\ .filter(emission_date__lte=datesched_max)\ .filter(episode__active__exact=True)\ .order_by('emission_date') # .filter(emission_done__isnull=True).order_by('emission_date') def get_program(self): "iterate to get program" for schedule in self.schedules: # logging.debug("PROGRAM: %s %s %s", programma.program.file , ' --> '\ # , programma.emission_date.isoformat()) logging.debug("PROGRAM: %s %s %s", schedule.episode , ' --> '\ , schedule.emission_date.isoformat()) firth=True for enclosure in schedule.episode.enclosure_set.order_by('id'): logging.debug("PROGRAM: files: %s", enclosure.file.path) ar_filename=enclosure.file.path.encode("UTF-8") ar_url=enclosure.file.url ar_title=schedule.episode.show.title+" / "\ +schedule.episode.title+" / "\ +enclosure.title query=ScheduleDone.objects.filter(enclosure=enclosure,schedule=schedule) if query: scheduledone=query.all()[0] else: #create new entry in table if necessary scheduledone=ScheduleDone(schedule=schedule,enclosure=enclosure) scheduledone.save() ar_emission_done=scheduledone.emission_done # calcolo la lunghezza del programma try: ar_length=mutagen.File(ar_filename).info.length logging.debug("PROGRAM: elaborate time length: %s",ar_length) except: logging.error("PROGRAM: error establish time length; use an estimation %s", ar_filename) ar_length=3600 # the schedule time is postponed every enclosure if firth: ar_scheduledatetime=schedule.emission_date lengthold=ar_length firth=False else: lengthold=ar_length ar_scheduledatetime=ar_scheduledatetime+timedelta(seconds=lengthold) programma=scheduledone programma.ar_filename=ar_filename programma.ar_url=ar_url programma.ar_length=ar_length programma.ar_title=ar_title programma.ar_emission_done=ar_emission_done programma.ar_scheduledatetime=ar_scheduledatetime yield programma def main(): logging.basicConfig(level=logging.DEBUG,) # time constants now=datetime.now() #select the programs pro=gest_program(now,minelab) # do a list for programma in pro.get_program(): #pass print programma.ar_filename print programma.ar_url print programma.ar_scheduledatetime print programma.ar_length #programma.program.get_file_filename() if __name__ == '__main__': main() # (this code was run as script) autoradio-upstream-2.8.2+dfsg.orig/autoradio/autoradio_config.py0000644000175000017500000000544712332264611025351 0ustar capriottcapriott#!/usr/bin/python # GPL. (C) 2007-2009 Paolo Patruno. import os from configobj import ConfigObj,flatten_errors from validate import Validator configspec={} configspec['autoradiod']={} configspec['autoradiod']['player'] = "string(default='xmms')" configspec['autoradiod']['playlistdir'] = "string(default='spots')" configspec['autoradiod']['logfile'] = "string(default='/tmp/autoradiod.log')" configspec['autoradiod']['errfile'] = "string(default='/tmp/autoradiod.err')" configspec['autoradiod']['lockfile'] = "string(default='/tmp/autoradiod.lock')" configspec['autoradiod']['timestampfile'] = "string(default='/tmp/autoradiod.timestamp')" configspec['autoradiod']['xmms_host'] = "string(default='localhost')" configspec['autoradiod']['minelab'] = "integer(60,360,default=180)" configspec['autoradiod']['minsched'] = "integer(3,20,default=5)" configspec['autoradiod']['locale'] = "string(default='it_IT.UTF-8')" configspec['autoradiod']['user'] = "string(default=None)" configspec['autoradiod']['group'] = "string(default=None)" configspec['autoradiod']['env']={} #configspec['autoradiod']['env']['display'] = "string(default=':0.0')" config = ConfigObj ('/etc/autoradio/autoradio-site.cfg',file_error=False,configspec=configspec,interpolation="Template") usrconfig = ConfigObj (os.path.expanduser('~/.autoradio.cfg'),file_error=False,interpolation="Template") config.merge(usrconfig) usrconfig = ConfigObj ('autoradio.cfg',file_error=False,interpolation="Template") config.merge(usrconfig) val = Validator() test = config.validate(val,preserve_errors=True) for entry in flatten_errors(config, test): # each entry is a tuple section_list, key, error = entry if key is not None: section_list.append(key) else: section_list.append('[missing section]') section_string = ', '.join(section_list) if error == False: error = 'Missing value or section.' print section_string, ' = ', error raise error # section autoradiod # to use the amarok player (obsolete) #player="amarok" #this work on old systems #player="xmms" #on last distributions #player="audacious" player = config['autoradiod']['player'] playlistdir = config['autoradiod']['playlistdir'] logfile = config['autoradiod']['logfile'] errfile = config['autoradiod']['errfile'] lockfile = config['autoradiod']['lockfile'] timestampfile = config['autoradiod']['timestampfile'] XMMSHOST = config['autoradiod']['xmms_host'] minelab = config['autoradiod']['minelab'] minsched = config['autoradiod']['minsched'] user = config['autoradiod']['user'] group = config['autoradiod']['group'] env = config['autoradiod']['env'] import locale locale.setlocale(locale.LC_ALL, config['autoradiod']['locale']) autoradio-upstream-2.8.2+dfsg.orig/autoradio/mprisweb.py0000644000175000017500000001537712332264611023670 0ustar capriottcapriott#!/usr/bin/env python # coding=utf-8 """ Show mediaplayer playlist on a simple web server. """ #try: # import sys,glob # from distutils.sysconfig import get_python_lib # compatCherryPyPath = glob.glob( get_python_lib()+"/CherryPy-2.*").pop() # sys.path.insert(0, compatCherryPyPath) #finally: import autoradio_config import cherrypy import os import datetime import autompris import autompris2 cpversion3=cherrypy.__version__.startswith("3") maxplele=100 # max number of elements in playlist port=8888 # server port head=''' MediaPlayer monitor | ''' tail=''' ''' class HomePage: # def Main(self): # # Let's link to another method here. # htmlresponse='Goto player status for autoradio!
' # htmlresponse+='Goto player playlist for autoradio!
' # return htmlresponse # Main.exposed = True def __init__(self,iht,player,session): self.iht=iht self.player=player self.session=session def test(self): "return test page" return "Test Page" test.exposed = True # def status(self): # "return media player status" # # try: # # --------------------------------- # org_obj = bus.get_object("org.atheme.audacious", '/org/atheme/audacious') # org = dbus.Interface(org_obj, dbus_interface='org.atheme.audacious') # # --------------------------------- # except: # # return "error intializing dbus" # # if (org.Playing()): # return "player is playing" # else: # return "player is stopped" # # # # status.exposed = True def index(self): "return media player playlist" if (self.iht) : htmlresponse=head else: htmlresponse="" try: if self.player == "vlc" or self.player == "AutoPlayer": mp= autompris2.mediaplayer(player=self.player,session=0) else: mp= autompris.mediaplayer(player=self.player,session=0) except: return "error intializing dbus" try: cpos=mp.get_playlist_pos() if cpos is None: cpos=0 cpos=int(cpos) except: return "error get_playlist_pos()" try: isplaying= mp.isplaying() except: return "error isplaying()" try: len=mp.get_playlist_len() htmlresponse+='

player have %i songs in playlist // song number %i selected

' % (len,cpos+1) htmlresponse+='' htmlresponse+='' for pos in xrange(0,min(len,maxplele)): htmlresponse+='' metadata=mp.get_metadata(pos) timelength=datetime.timedelta(seconds=datetime.timedelta(milliseconds=metadata["mtimelength"]).seconds) timeposition=datetime.timedelta(seconds=datetime.timedelta(milliseconds=metadata["mtimeposition"]).seconds) if pos == cpos and isplaying: col="#FF0000" toend=timelength-timeposition elif pos < cpos : col="#0000FF" toend="" else: col="#00FF00" toend="" if (metadata["artist"] is not None) or (metadata["title"] is not None): htmlresponse+='' % \ (col,pos+1,str(timelength),str(toend),metadata["file"],metadata["artist"],metadata["title"]) else: purefilename=os.path.splitext(metadata["file"])[0] htmlresponse+='' % \ (col,pos+1,str(timelength),str(toend),metadata["file"],os.path.basename(purefilename)) htmlresponse+='' except: htmlresponse+='error getting player information' htmlresponse+='
positionlenght // remainmedia
%i %s // %s %s // %s%i %s // %s %s
' try: if len > maxplele : htmlresponse+="

ATTENTION: there are more file than you can see here.

" except: pass if (self.iht) : htmlresponse+=tail return htmlresponse index.exposed = True def start_http_server(iht=False,player="AutoPlayer",session=0): """ start web server to monitor player iht=False # do not emit header e tail """ #import os #pid = os.fork() settings = { 'global': { 'server.socket_port' : port, 'server.socket_host': "0.0.0.0", 'server.socket_file': "", 'server.socket_queue_size': 5, 'server.protocol_version': "HTTP/1.0", 'server.log_to_screen': False, 'server.log_file': "/tmp/mprisweb.log", 'server.reverse_dns': False, 'server.thread_pool': 10, 'server.environment': "development", #'server.environment': "production", 'tools.encode.on':True, # 'tools.encode.encoding':'utf8', }, } # CherryPy always starts with cherrypy.root when trying to map request URIs # to objects, so we need to mount a request handler object here. A request # to '/' will be mapped to cherrypy.root.index(). if (cpversion3): cherrypy.quickstart(HomePage(iht,player,session),config=settings) else: cherrypy.config.update(settings) cherrypy.root = HomePage(iht,player,session) cherrypy.server.start() if __name__ == '__main__': # Set the signal handler #import signal #signal.signal(signal.SIGINT, signal.SIG_IGN) # Start the CherryPy server. try: start_http_server(iht=True,player=autoradio_config.player,session=0) except: print "Error" raise finally: print "Terminated" autoradio-upstream-2.8.2+dfsg.orig/autoradio/manageaudacious.py0000644000175000017500000001641012332264611025153 0ustar capriottcapriott#!/usr/bin/env python # GPL. (C) 2007 Paolo Patruno. import logging import dbus import autoaudacious from datetime import * from threading import * from django.conf import settings import os import autoradio_config class AudaciousError(Exception): def __str__(self): return repr(self.args[0]) def shuffle_playlist(infile,shuffle=False,relative_path=False,length=None): import mkplaylist import os,random,tempfile,codecs media_files=list(mkplaylist.read_playlist(infile, not relative_path)) if shuffle: random.shuffle(media_files) # else: # media_files.sort() fd,outfile=tempfile.mkstemp(".m3u") #ffoutfile = os.fdopen(fd,"w") foutfile = codecs.open(outfile, "w", encoding="UTF-8") mkplaylist.write_extm3u(media_files, foutfile,length) foutfile.close() os.close(fd) return outfile lock = Lock() def ar_emitted(self): ''' Save in django datatime when emission is done ''' self.emission_done=datetime.now() self.save() class ScheduleProgram: ''' activate a schedule setting it for a time in the future ''' def __init__ (self,session,schedule): "init schedule" self.deltasec=secondi( schedule.scheduledatetime - datetime.now()) self.session=session self.function=ManageAudacious self.schedule=schedule self.timer = Timer(self.deltasec, self.function,[self.session,self.schedule]) def start (self): "start of programmed schedule" self.timer.start() def ManageAudacious (session,schedule): "Manage audacious to do operation on media" try: if ( schedule.type == "spot" ): operation="queueMedia" elif ( schedule.type == "program" ): operation="queueMedia" elif ( schedule.type == "jingle" ): operation="queueMedia" elif ( schedule.type == "playlist" ): operation="loadPlaylist" else: raise AudaciousError("ManageAudacious: type not supported: %s"% schedule.type) if operation == "loadPlaylist": media=shuffle_playlist(schedule.filename,schedule.shuffle,relative_path=False,length=schedule.maxlength) else: media=schedule.filename aud=autoaudacious.audacious() # Regione critica lock.acquire() try: if not aud.playlist_clear_up(atlast=10): raise AudaciousError("ManageAudacious: ERROR in playlist_clear_up") #print settings.MEDIA_ROOT #pos=aud.get_playlist_posauto(autopath=settings.MEDIA_ROOT,securesec=10) pos=aud.get_playlist_posauto(autopath="/cacca",securesec=10) curpos=aud.get_playlist_pos() # inserisco il file nella playlist if pos is None: raise AudaciousError("ManageXmms: ERROR in xmms.control.get_playlist_posauto") logging.info( "ManageXmms: insert media: %s at position %d",media,pos) aud.org.PlaylistInsUrlString("file://"+media,pos) # recheck for consistency newpos=aud.get_playlist_pos() if curpos != newpos: raise AudaciousError("Manageaudacious: strange ERROR: consinstency problem; pos: %d , newpos: %d"% (curpos,newpos)) if not aud.playlist_clear_down(atlast=500): raise AudaciousError("ManageAudacious: ERROR in playlist_clear_down") finally: #signal.alarm(0) lock.release() if schedule.shuffle: os.remove(media) logging.info( "ManageAudacious: write in django: %s",schedule.djobj) ar_emitted(schedule.djobj) logging.info( "ManageAudacious: write in django: %s",schedule.djobj) except AudaciousError, e: logging.error(e) return aud.play_ifnot() def secondi(delta): secondi=float(delta.seconds) secondi=secondi+(delta.microseconds/100000.) if delta.days < 0 : secondi = secondi + (3600*24*delta.days) return secondi class dummy_programma: def __init__(self): pass def save(self): #print "masquerade as we save it" pass def audacious_watchdog(session): from distutils.version import LooseVersion reqversion=LooseVersion("1.5") version=LooseVersion("0.0") logging.debug( "audacious_watchdog: test if audacious is running" ) try: aud=autoaudacious.audacious() except: logging.error("audacious_watchdog: audacious is not running or error on is_running") import subprocess try: logging.info("audacious_watchdog: try launching audacious") subprocess.Popen("audacious" , shell=True) except: logging.error("audacious_watchdog: error launching audacious") try: logging.info("audacious_watchdog: try launching audacious2") subprocess.Popen("audacious2" , shell=True) except: logging.error("audacious_watchdog: error launching audacious2") import time time.sleep(5) logging.info("audacious_watchdog: launch_audacious") aud=autoaudacious.audacious() try: aud=autoaudacious.audacious() except: logging.error("audacious_watchdog: audacious2 not started: try with audacious") import subprocess subprocess.Popen("audacious" , shell=True) import time time.sleep(5) logging.info("audacious_watchdog: launch_audacious") aud=autoaudacious.audacious() try: # aud.root.Identity() version=LooseVersion(aud.org.Version()) logging.info("audacious_watchdog: audacious version: %s" % str(version)) except: logging.error("audacious_watchdog: eror gettin audacious version") return True if ( version < reqversion ): logging.error("audacious_watchdog: audacious %s version is wrong (>=1.5) " % version ) raise Exception aud.play_ifnot() logging.debug("audacious_watchdog: audacious start playing if not") return True def save_status(session): """ Do nothing """ logging.debug ( "DUMMY xmms.saveCurrentPlaylist") return True def main(): import autoradio_core programma=dummy_programma() audacious_watchdog(0) session=0 shuffle=False maxlength=None type="program" media = "/home/pat1/tmp/pippo.mp3" #media = "/home/pat1/Musica/STOP AL PANICO/ISOLA POSSE STOP AL PANICO.mp3" #media = "/home/autoradio/django/media/playlist/tappeto_musicale.m3u" #media = raw_input("dammi il media? ") scheduledatetime=datetime.now()+timedelta(seconds=5) sched=autoradio_core.schedule(programma,scheduledatetime,media,filename=media,type=type,shuffle=shuffle,maxlength=maxlength) threadschedule=ScheduleProgram(session,sched) threadschedule.start() # scheduledatetime=datetime.now()+timedelta(seconds=8) # media = "/home/autoradio/django/media/programs/borsellino_giordano.mp3" # schedule=ScheduleProgram(session,function,operation,media,scheduledatetime,programma,shuffle) # schedule.start() # scheduledatetime=datetime.now()+timedelta(seconds=10) # media = "/home/autoradio/django/media/programs/mister_follow_follow.mp3" # schedule=ScheduleProgram(session,function,operation,media,scheduledatetime,programma,shuffle) # schedule.start() if __name__ == '__main__': main() # (this code was run as script) autoradio-upstream-2.8.2+dfsg.orig/autoradio/urls.py0000644000175000017500000000243512332264611023014 0ustar capriottcapriottfrom django.conf.urls import * import settings # Uncomment the next two lines to enable the admin: from django.contrib import admin admin.autodiscover() urlpatterns = patterns('', # Example: # (r'^autoradio/', include('autoradio.foo.urls')), # (r'^xmms/', include('programs.urls')), # (r'^$', include('spots.urls')), # (r'^$', include('jingles.urls')), # Uncomment the next line to enable admin documentation: (r'^admin/doc/', include('django.contrib.admindocs.urls')), # Uncomment the next line to enable the admin: (r'^admin/', include(admin.site.urls)), (r'^', include('autoradio.programs.urls')), # (r'^', include('autoradio.palimpsest.urls')), (r'^podcasts/', include('autoradio.programs.urls_podcast')), (r'^player/', include('autoradio.player.urls')), (r'^doc/', include('autoradio.doc.urls')), ) if ( settings.SERVE_STATIC ): #serve local static files urlpatterns += patterns('', (r'^'+settings.MEDIA_PREFIX[1:]+'(.*)', 'django.views.static.serve', {'document_root': settings.MEDIA_ROOT, 'show_indexes': True}), (r'^'+settings.MEDIA_SITE_PREFIX[1:]+'(.*)', 'django.views.static.serve', {'document_root': settings.MEDIA_SITE_ROOT, 'show_indexes': True}), ) autoradio-upstream-2.8.2+dfsg.orig/autoradio/daemon.py0000644000175000017500000002405612332264611023275 0ustar capriottcapriott# -*- coding: utf-8 -*- # modified by Paolo Patruno September 2009 ## Copyright 1999-2009 by LivingLogic AG, Bayreuth/Germany ## Copyright 1999-2009 by Walter Dörwald ## ## All Rights Reserved ## ## Permission is hereby granted, free of charge, to any person obtaining a copy ## of this software and associated documentation files (the "Software"), to deal ## in the Software without restriction, including without limitation the rights ## to use, copy, modify, merge, publish, distribute, sublicense, and/or sell ## copies of the Software, and to permit persons to whom the Software is ## furnished to do so, subject to the following conditions: ## ## The above copyright notice and this permission notice shall be included in ## all copies or substantial portions of the Software. ## ## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR ## IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, ## FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE ## AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER ## LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, ## OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN ## THE SOFTWARE. ## ur""" This module can be used on UNIX to fork a daemon process. It is based on `Jürgen Hermann's Cookbook recipe`__. __ http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/66012 An example script might look like this:: from autoradio import daemon tmp = daemon.Daemon( stdin="/dev/null", stdout="/tmp/tmp.log", stderr="/tmp/tmp.err", pidfile="/tmp/tmp.lock", # user=user, # group=group, # env=env ) def main(self): import subprocess self.procs=[subprocess.Popen(["sleep","60"],cwd=self.cwd)] self.procs.append(subprocess.Popen(["sleep","130"],cwd=self.cwd)) if __name__ == '__main__': import sys, os tmp.cwd=os.getcwd() if tmp.service(): sys.stdout.write("Daemon started with pid %d\n" % os.getpid()) sys.stdout.write("Daemon stdout output\n") sys.stderr.write("Daemon stderr output\n") main(tmp) # (this code was run as script) for proc in tmp.procs: proc.wait() sys.exit(0) """ import sys, os, signal, pwd, grp, optparse __docformat__ = "reStructuredText" class Daemon(object): """ The :class:`Daemon` class provides methods for starting and stopping a daemon process as well as handling command line arguments. """ def __init__(self, stdin="/dev/null", stdout="/dev/null", stderr="/dev/null", pidfile=None, user=None, group=None,env=None): """ The :var:`stdin`, :var:`stdout`, and :var:`stderr` arguments are file names that will be opened and be used to replace the standard file descriptors in ``sys.stdin``, ``sys.stdout``, and ``sys.stderr``. These arguments are optional and default to ``"/dev/null"``. Note that stderr is opened unbuffered, so if it shares a file with stdout then interleaved output may not appear in the order that you expect. :var:`pidfile` must be the name of a file. :meth:`start` will write the pid of the newly forked daemon to this file. :meth:`stop` uses this file to kill the daemon. :var:`user` can be the name or uid of a user. :meth:`start` will switch to this user for running the service. If :var:`user` is :const:`None` no user switching will be done. In the same way :var:`group` can be the name or gid of a group. :meth:`start` will switch to this group. :env: {} set the ENVIROMENT variables """ options = dict( stdin=stdin, stdout=stdout, stderr=stderr, pidfile=pidfile, user=user, group=group ) self.env=env self.options = optparse.Values(options) self.procs=() self.cwd=os.getcwd() def openstreams(self): """ Open the standard file descriptors stdin, stdout and stderr as specified in the constructor. """ si = open(self.options.stdin, "r") so = open(self.options.stdout, "a+") se = open(self.options.stderr, "a+", 0) os.dup2(si.fileno(), sys.stdin.fileno()) os.dup2(so.fileno(), sys.stdout.fileno()) os.dup2(se.fileno(), sys.stderr.fileno()) def handlesighup(self, signum, frame): """ Handle a ``SIG_HUP`` signal: Reopen standard file descriptors. """ import subprocess self.openstreams() for proc in self.procs: if (isinstance(proc,subprocess.Popen)): #proc.send_signal(signum) # work in py ver 2.6 os.kill(proc.pid,signum) if (isistance(proc,int)): os.kill(proc,signum) def handlesigterm(self, signum, frame): """ Handle a ``SIG_TERM`` signal: Remove the pid file and exit. """ import subprocess if self.options.pidfile is not None: try: os.remove(self.options.pidfile) except Exception: pass for proc in self.procs: if (isinstance(proc,subprocess.Popen)): #proc.send_signal(signum) # work in py ver 2.6 os.kill(proc.pid,signum) if (isinstance(proc,int)): os.kill(proc,signum) sys.exit(0) def switchuser(self, user, group, env): """ Switch the effective user and group. If :var:`user` and :var:`group` are both :const:`None` nothing will be done. :var:`user` and :var:`group` can be an :class:`int` (i.e. a user/group id) or :class:`str` (a user/group name). """ groups = [] if group is not None: if isinstance(group, list): for gr in group: if isinstance(gr, basestring): groups.append(grp.getgrnam(gr).gr_gid) group = group[0] if isinstance(group, basestring): group = grp.getgrnam(group).gr_gid try: os.setgroups(groups) except: pass os.setgid(group) os.setegid(group) if user is not None: if isinstance(user, basestring): user = pwd.getpwnam(user).pw_uid os.setuid(user) os.seteuid(user) # todo: check why home is set here #if not "HOME" in os.environ: os.environ["HOME"] = pwd.getpwuid(user).pw_dir if env is not None: for variable in env: os.environ[variable] = env[variable] if "HOME" in os.environ: self.cwd=os.environ["HOME"] try: os.chdir(self.cwd) except: pass def start(self): """ Daemonize the running script. When this method returns the process is completely decoupled from the parent environment. """ # Finish up with the current stdout/stderr sys.stdout.flush() sys.stderr.flush() # Do first fork try: pid = os.fork() if pid > 0: sys.exit(0) # Exit first parent except OSError, exc: sys.exit("%s: fork #1 failed: (%d) %s\n" % (sys.argv[0], exc.errno, exc.strerror)) # Decouple from parent environment os.chdir("/") os.umask(0) os.setsid() # Do second fork try: pid = os.fork() if pid > 0: sys.exit(0) # Exit second parent except OSError, exc: sys.exit("%s: fork #2 failed: (%d) %s\n" % (sys.argv[0], exc.errno, exc.strerror)) # Now I am a daemon! # Switch user self.switchuser(self.options.user, self.options.group, self.env) # Redirect standard file descriptors (will belong to the new user) self.openstreams() # Write pid file (will belong to the new user) if self.options.pidfile is not None: open(self.options.pidfile, "wb").write(str(os.getpid())) # Reopen file descriptors on SIGHUP signal.signal(signal.SIGHUP, self.handlesighup) # Remove pid file and exit on SIGTERM signal.signal(signal.SIGTERM, self.handlesigterm) def stop(self): """ Send a ``SIGTERM`` signal to a running daemon. The pid of the daemon will be read from the pidfile specified in the constructor. """ if self.options.pidfile is None: sys.exit("no pidfile specified") try: pidfile = open(self.options.pidfile, "rb") except IOError, exc: sys.exit("can't open pidfile %s: %s" % (self.options.pidfile, str(exc))) data = pidfile.read() try: pid = int(data) except ValueError: sys.exit("mangled pidfile %s: %r" % (self.options.pidfile, data)) os.kill(pid, signal.SIGTERM) def optionparser(self): """ Return an :mod:`optparse` parser for parsing the command line options. This can be overwritten in subclasses to add more options. """ from . import _version_ p = optparse.OptionParser(usage="usage: %prog [options] (action=start|stop|restart|run|version)", description="%prog daemon for autoradio suite",version="%prog "+_version_) p.add_option("--pidfile", dest="pidfile", help="PID filename (default %default)", default=self.options.pidfile) p.add_option("--stdin", dest="stdin", help="stdin filename (default %default)", default=self.options.stdin) p.add_option("--stdout", dest="stdout", help="stdout filename (default %default)", default=self.options.stdout) p.add_option("--stderr", dest="stderr", help="stderr filename (default %default)", default=self.options.stderr) p.add_option("--user", dest="user", help="user name or id (default %default)", default=self.options.user) p.add_option("--group", dest="group", help="group name or id (default %default)", default=self.options.group) return p def service(self, args=None,noptions=0): """ Handle command line arguments and start or stop the daemon accordingly. :var:`args` must be a list of command line arguments (including the program name in ``args[0]``). If :var:`args` is :const`None` or unspecified ``sys.argv`` is used. :var:`noptions` max number of options in command line or args The return value is true when a starting option has been specified as the command line argument, i.e. if the daemon should be started. The :mod:`optparse` options and arguments are available afterwards as ``self.options`` and ``self.args``. """ p = self.optionparser() if args is None: args = sys.argv (self.options, self.args) = p.parse_args(args) if len(self.args) == 1 or len(self.args) > noptions+2: p.error("incorrect number of arguments") sys.exit(1) if self.args[1] == "run": return True elif self.args[1] == "restart": try: self.stop() finally: self.start() return True elif self.args[1] == "start": self.start() return True elif self.args[1] == "stop": self.stop() return False else: p.error("incorrect argument %s" % self.args[1]) sys.exit(1) autoradio-upstream-2.8.2+dfsg.orig/autoradio/autoepris.py0000644000175000017500000001314712332264611024044 0ustar capriottcapriott#!/usr/bin/env python # GPL. (C) 2007-2009 Paolo Patruno. import dbus import time import datetime import os # ------- dbus epris player interface --------- import dbus class mediaplayer: def __init__(self,session=0): try: self.bus = dbus.SessionBus() # ----------------------------------------------------------- mediaplayer_obj = self.bus.get_object("org.mpris.epris", '/org/mpris/epris') current_obj = self.bus.get_object("org.mpris.epris", '/org/mpris/epris/lists/current') self.player = dbus.Interface(mediaplayer_obj, dbus_interface='org.mpris.EprisPlayer') self.tracklist = dbus.Interface(current_obj, dbus_interface='org.mpris.EprisTrackList') # ----------------------------------------------------------- except: raise def __str__(self): return self.player.Identity def play_ifnot(self): ''' start playing if not. ''' # I check if mediaplayer is playing .... otherside I try to play print self.tracklist.ListTracks() print self.tracklist.Current # if (not self.player.PlaybackStatus == "Playing"): # self.player.Play() def get_playlist_securepos(self,securesec=10): # DO NOT WORK ''' Try to secure that there are some time (securesec) to complete all operations in time: if mediaplayer change song during operation will be a big problem ''' try: self.play_ifnot() #force to play mintimed=datetime.timedelta(seconds=securesec) toend=datetime.timedelta(seconds=0) volte=0 while ( toend < mintimed ): # take the current position pos=self.tracklist.GetCurrentTrack() metadata=self.tracklist.GetMetadata(pos) #print metadata mtimelength=metadata["mtime"] mtimeposition=self.player.PositionGet() timed=datetime.timedelta(seconds=datetime.timedelta(milliseconds=mtimelength).seconds) toend=timed-datetime.timedelta(seconds=datetime.timedelta(milliseconds=mtimeposition).seconds) newpos=self.tracklist.GetCurrentTrack() if (pos != newpos): #inconsistenza: retry toend=datetime.timedelta(seconds=0) if ( toend < mintimed ): volte +=1 if volte > 10 : break # timeout , I have to play time.sleep(securesec+1) return pos except : return None def playlist_clear_up(self,atlast=10): # DO NOT WORK ''' clear playlist starting from current position up. "atlast" numer of song are retained ''' try: self.play_ifnot() #force to play # take the current position (if error set pos=0) pos=self.get_playlist_securepos() if pos is None: return False # delete the old ones if pos > atlast : for prm in xrange(0,pos-atlast): self.tracklist.DelTrack(0) return True except: return False def playlist_clear_down(self,atlast=500): # DO NOT WORK ''' clear playlist starting from current position + atlast doen. "atlast" numer of song are retained for future play ''' try: self.play_ifnot() #force to play # take the current position (if error set pos=0) pos=self.get_playlist_securepos() if pos is None: return False length=self.tracklist.GetLength() #elimino il troppo if length-pos > atlast : for prm in xrange(length,pos+atlast,-1): self.tracklist.DelTrack(prm) return True except: return False def get_playlist_posauto(self,autopath,securesec=10): # DO NOT WORK ''' get playlist position skipping file with path equal to autopath. Try to secure that there are some time (securesec) to complete all operations in time: if xmms change song during operation will be a big problem ''' try: pos=self.get_playlist_securepos(securesec=securesec) if pos is None: return pos pos+=1 metadata=self.tracklist.GetMetadata(pos) try: file=metadata["URI"] except: return pos filepath=os.path.dirname(file) # ora controllo se ci sono gia dei file accodati nella playlist da autoradio # l'unica possibilita di saperlo e verificare il path del file while ( os.path.commonprefix ((filepath,"file://"+autopath)) == "file://"+autopath ): pos+=1 metadata=self.tracklist.GetMetadata(pos) try: file=metadata["URI"] except: return pos filepath=os.path.dirname(file) # here I have found the first file added by autoradio return pos except : return None def get_playlist_pos(self): # DO NOT WORK "get current position" return self.tracklist.GetCurrentTrack() def main(): mp=mediaplayer() print mp mp.play_ifnot() if __name__ == '__main__': main() # (this code was run as script) autoradio-upstream-2.8.2+dfsg.orig/autoradio/jingles/0000755000175000017500000000000012332265077023113 5ustar capriottcapriottautoradio-upstream-2.8.2+dfsg.orig/autoradio/jingles/admin.py0000644000175000017500000000535112332264611024552 0ustar capriottcapriottfrom models import Giorno, Configure, Jingle from django.contrib import admin from django import forms from django.utils.translation import ugettext_lazy import autoradio.settings import magic import autoradio.mime ma = magic.open(magic.MAGIC_MIME_TYPE) ma.load() class MyJingleAdminForm(forms.ModelForm): """ Check file if it is a known media file. """ class Meta: model = Jingle def clean_file(self): import mutagen, os file = self.cleaned_data.get('file',False) if file: #if file._size > 40*1024*1024: # raise forms.ValidationError("Audio file too large ( > 4mb )") try: type = file.content_type in webmime_audio except: return file if not type: raise forms.ValidationError(ugettext_lazy("Content-Type is not audio/mpeg or audio/flac or video/ogg")) if not os.path.splitext(file.name)[1] in websuffix_audio: raise forms.ValidationError(ugettext_lazy("Doesn't have proper extension: .mp3, .wav, .ogg, .oga, .flac")) try: mime = ma.file(file.temporary_file_path()) audio = mime in mymime_audio except: audio=False if not audio: raise forms.ValidationError(ugettext_lazy("Not a valid audio file")) if autoradio.settings.require_tags_in_enclosure: #Check file if it is a known media file. The check is based on mutagen file test. try: audio = not (mutagen.File(file.temporary_file_path()) is None) except: audio = False if not audio: raise forms.ValidationError(ugettext_lazy("Not a valid audio file: probably no tags present")) return file else: raise forms.ValidationError(ugettext_lazy("Couldn't read uploaded file")) class GiornoAdmin(admin.ModelAdmin): search_fields = ['name'] admin.site.register(Giorno, GiornoAdmin) class ConfigureAdmin(admin.ModelAdmin): list_display = ('sezione','active','emission_freq',) admin.site.register(Configure, ConfigureAdmin) class JingleAdmin(admin.ModelAdmin): fieldsets = ( (None, {'fields': ('jingle','file','rec_date','active')}), ('Emission information', {'fields': ('start_date','end_date','start_time','end_time','giorni','priorita')}), ) list_display = ('jingle','file','rec_date','emission_done','active') list_filter = ['active','start_date','end_date','start_time','end_time','rec_date','giorni'] date_hierarchy = 'rec_date' search_fields = ['jingle','file'] form = MyJingleAdminForm admin.site.register(Jingle, JingleAdmin) autoradio-upstream-2.8.2+dfsg.orig/autoradio/jingles/views.py0000644000175000017500000000003212332264611024606 0ustar capriottcapriott# Create your views here. autoradio-upstream-2.8.2+dfsg.orig/autoradio/jingles/__init__.py0000644000175000017500000000000012332264611025203 0ustar capriottcapriottautoradio-upstream-2.8.2+dfsg.orig/autoradio/jingles/urls.py0000644000175000017500000000031712332264611024444 0ustar capriottcapriottfrom django.conf.urls.defaults import * #from models import Fascia, Spot # #urlpatterns = patterns('', # (r'^$', 'programs.views.index'), # (r'^/schedule/(?P\d+)/$', 'views.detail'), # #) autoradio-upstream-2.8.2+dfsg.orig/autoradio/jingles/models.py0000644000175000017500000001201212332264611024735 0ustar capriottcapriottfrom django.db import models from django.utils.translation import ugettext_lazy import calendar from autoradio.autoradio_config import * from django import VERSION as djversion if ((djversion[0] == 1 and djversion[1] >= 3) or djversion[0] > 1): from django.db import models from django.db.models import signals class DeletingFileField(models.FileField): """ FileField subclass that deletes the refernced file when the model object itself is deleted. WARNING: Be careful using this class - it can cause data loss! This class makes at attempt to see if the file's referenced elsewhere, but it can get it wrong in any number of cases. """ def contribute_to_class(self, cls, name): super(DeletingFileField, self).contribute_to_class(cls, name) signals.post_delete.connect(self.delete_file, sender=cls) def delete_file(self, instance, sender, **kwargs): file = getattr(instance, self.attname) # If no other object of this type references the file, # and it's not the default value for future objects, # delete it from the backend. if file and file.name != self.default and \ not sender._default_manager.filter(**{self.name: file.name}): file.delete(save=False) elif file: # Otherwise, just close the file, so it doesn't tie up resources. file.close() else: DeletingFileField=models.FileField def giorno_giorno(): giorni=[] for giorno in (calendar.day_name): giorno=giorno.decode('utf-8') giorni.append(( giorno, giorno)) return giorni # yield 'Tutti','Tutti' class Giorno(models.Model): name = models.CharField(max_length=20,choices=giorno_giorno(),unique=True,\ help_text=ugettext_lazy("weekday name")) def __unicode__(self): return self.name class Admin: search_fields = ['name'] class Configure(models.Model): sezione = models.CharField(max_length=50,unique=True,default='jingle',editable=False) active = models.BooleanField(ugettext_lazy("Activate Jingle"),default=True, help_text=ugettext_lazy("activate/deactivate the intere jingle class")) emission_freq = models.TimeField(ugettext_lazy('Frequency')) def __unicode__(self): return self.sezione+" "+self.active.__str__()+" "+self.emission_freq.isoformat() class Admin: list_display = ('sezione','active','emission_freq',) class Jingle(models.Model): jingle = models.CharField(ugettext_lazy("Jingle name"),max_length=80,unique=True) file = DeletingFileField(ugettext_lazy('File'),upload_to='jingles',max_length=255,\ help_text=ugettext_lazy("The jingle file to upload")) rec_date = models.DateTimeField(ugettext_lazy('Recording date'),\ help_text=ugettext_lazy("When the jingle was done (for reference only)")) active = models.BooleanField(ugettext_lazy("Active"),default=True,\ help_text=ugettext_lazy("Activate the jingle for emission")) start_date = models.DateField(ugettext_lazy('Emission starting date'),null=True,blank=True,\ help_text=ugettext_lazy("The jingle will be scheduled starting from this date")) end_date = models.DateField(ugettext_lazy('Emission end date'),null=True,blank=True,\ help_text=ugettext_lazy("The jingle will be scheduled ending this date")) start_time = models.TimeField(ugettext_lazy('Emission start time'),null=True,blank=True,\ help_text=ugettext_lazy("The jingle will be scheduled starting from this date")) end_time = models.TimeField(ugettext_lazy('Emission end time'),null=True,blank=True,\ help_text=ugettext_lazy("The jingle will be scheduled ending this date")) giorni = models.ManyToManyField(Giorno,verbose_name=ugettext_lazy('Scheduled days'),null=True,blank=True,\ help_text=ugettext_lazy("The jingle will be scheduled those weekdays")) priorita = models.IntegerField(ugettext_lazy("Priority"),default=50,\ help_text=ugettext_lazy("When there are more jingle that wait for emission from the same time, the emission will be ordered by this numer")) emission_done = models.DateTimeField(ugettext_lazy('emission done'),null=True,editable=False ) def was_recorded_today(self): return self.rec_date.date() == datetime.date.today() was_recorded_today.short_description = ugettext_lazy('Recorded today?') def __unicode__(self): return self.jingle class Admin: fields = ( (None, {'fields': ('jingle','file','rec_date','active')}), ('Emission information', {'fields': ('start_date','end_date','start_time','end_time','giorni','priorita')}), ) list_display = ('jingle','file','rec_date','emission_done') list_filter = ['start_date','end_date','start_time','end_time','giorni'] date_hierarchy = 'rec_date' search_fields = ['jingle'] #class Meta: # unique_together = ("prologo", "epilogo","fasce") autoradio-upstream-2.8.2+dfsg.orig/autoradio/autoxmms.py0000644000175000017500000001020212332264611023673 0ustar capriottcapriott#!/usr/bin/env python # GPL. (C) 2007-2009 Paolo Patruno. import xmms import time import datetime import os def play_ifnot(session=0): ''' start playng if not. ''' # I check if xmms is playng .... otherside I try to play # if xmms is in pause any check is impossible try: ok=xmms.control.is_playing(session) if (not ok): ok = xmms.control.play(session) except: return False def get_playlist_securepos(session=0,securesec=10): ''' Try to secure that there are some time (securesec) to complete all operations in time: if xmms change song during operation will be a big problem ''' try: play_ifnot(session=session) #force to play mintimed=datetime.timedelta(seconds=securesec) toend=datetime.timedelta(seconds=0) volte=0 while ( toend < mintimed ): # take the current position pos=xmms.control.get_playlist_pos(session) timed=datetime.timedelta(seconds=datetime.timedelta(milliseconds=xmms.control.get_playlist_time(pos, session)).seconds) toend=timed-datetime.timedelta(seconds=datetime.timedelta(milliseconds=xmms.control.get_output_time(session)).seconds) newpos=xmms.control.get_playlist_pos(session) if (pos != newpos): #inconsistenza: retry toend=datetime.timedelta(seconds=0) if ( toend < mintimed ): volte +=1 if volte > 10 : break # timeout , I have to play time.sleep(securesec+1) return pos except : return None def playlist_clear_up(atlast=10,session=0): ''' clear playlist starting from current position up. "atlast" numer of song are retained ''' try: play_ifnot(session=session) #force to play # take the current position (if error set pos=0) pos=get_playlist_securepos(session) if pos is None: return False # delete the old ones if pos > atlast : for prm in xrange(0,pos-atlast): xmms.control.playlist_delete(0,session) return True except: return False def playlist_clear_down(atlast=500,session=0): ''' clear playlist starting from current position + atlast doen. "atlast" numer of song are retained for future play ''' try: play_ifnot(session=session) #force to play # take the current position (if error set pos=0) pos=get_playlist_securepos(session) if pos is None: return False length=xmms.get_playlist_length(session) #elimino il troppo if length-pos > atlast : for prm in xrange(length,pos+atlast,-1): xmms.control.playlist_delete(prm,session) return True except: return False def get_playlist_posauto(autopath,session=0,securesec=10): ''' get playlist position skipping file with path equal to autopath. Try to secure that there are some time (securesec) to complete all operations in time: if xmms change song during operation will be a big problem ''' try: pos=get_playlist_securepos(session=session,securesec=securesec) if pos is None: return pos pos+=1 file=xmms.control.get_playlist_file(pos, session) if file is None : return pos filepath=os.path.dirname(file) # ora controllo se ci sono gia dei file accodati nella playlist da autoradio # l'unica possibilita di saperlo e verificare il path del file while ( os.path.commonprefix ((filepath,autopath)) == autopath ): pos+=1 file=xmms.control.get_playlist_file(pos, session) if file is None : return pos filepath=os.path.dirname(file) # here I have found the first file added by autoradio return pos except : return None #xmms.control.playlist_clear_up=playlist_clear_up #xmms.control.get_playlist_posauto=get_playlist_posauto #xmms.control.play_ifnot=play_ifnot autoradio-upstream-2.8.2+dfsg.orig/autoradio/autoradio_core.py0000644000175000017500000005440012332264611025025 0ustar capriottcapriott#!/usr/bin/env python # -*- coding: utf-8 -*- # GPL. (C) 2007-2009 Paolo Patruno. from autoradio_config import * from gest_program import * from gest_spot import * from gest_jingle import * from gest_playlist import * from gest_palimpsest import * class schedule: """ Single schedule object attributes: djobj : dajngo retrive object scheduledatetime : datetime of schedule media : url of media filename : path of media length=None : time length in seconds type=None : "spot"/""playlist"/"jingle"/"programma" emission_done=None shuffle=False title=None): """ def __init__ (self,djobj,scheduledatetime,media,filename,length=None,type=None,emission_done=None,\ shuffle=False,maxlength=None,title=None): """ init of schedule object: """ self.djobj=djobj self.scheduledatetime=scheduledatetime self.media=media self.filename=filename #self.mediaweb = self.media[len(settings.MEDIA_URL)+1:] self.length=length self.type=type self.emission_done=emission_done self.shuffle=shuffle self.maxlength=maxlength self.title=title def __cmp__ (self, b): if self.scheduledatetime is None and b.scheduledatetime is None : return 0 if self.scheduledatetime is None : return -1 if b.scheduledatetime is None : return 1 if self.scheduledatetime == b.scheduledatetime : return 0 elif self.scheduledatetime < b.scheduledatetime : return -1 elif self.scheduledatetime > b.scheduledatetime : return 1 def future (self,now=None): self.future=self.scheduledatetime > now return self.future def filter (self): if self.scheduledatetime is None : return False return True def __str__ (self): return self.type+" "+self.media def __iter__(self,now=None): ''' return a list nome,datet,media,len,tipo,datetdone,future ''' if now is None : now=datetime.now() #return iter((self.djobj,self.scheduledatetime,self.media,self.length,self.type,\ # self.emission_done,self.shuffle,self.future(now))) yield self.djobj yield self.title yield self.scheduledatetime yield self.media yield str((datetime(2000,1,1)+timedelta(seconds=int(self.length))).time()) yield self.type yield self.emission_done yield self.future(now) class schedules(list): """ multiple schedule object """ def districa(self): ''' english: try to extricate from an schedules ensemble the more easy operation is to delete jingles inside programs and spots italiano: cerca di districarsi tra un insieme di schedule la prima operazione da fare e' togliere i jingle che coincidono con programmi e pubblicita' return True if need other call to self to manage new modification ''' logging.debug("execute districa") needrecompute=False #Spots #v=0 for v,schedulej in enumerate(self): # add the default adjustedlength !!! Attention schedulej.adjustedlength= schedulej.length scheduledatetimej=schedulej.scheduledatetime if ( scheduledatetimej == None ): continue lengthj=schedulej.length typej=schedulej.type endscheduledatetimej=scheduledatetimej+timedelta(seconds=lengthj) #print "elaborate ",typej,scheduledatetimej,endscheduledatetimej if (typej == "spot"): for schedule in self: scheduledatetime=schedule.scheduledatetime if ( scheduledatetime== None ): continue length=schedule.length type=schedule.type endscheduledatetime=scheduledatetime+timedelta(seconds=length) halfscheduledatetime=scheduledatetime+timedelta(seconds=length/2) if (type == "spot" or type == "playlist" or type == "jingle" ): continue # here we have spot versus programs #starting in the firth half of program if ( scheduledatetime < scheduledatetimej and scheduledatetimej < halfscheduledatetime ): logging.debug( "anticipate this spot overlapped start time in the firth half %s", str(self[v])) ##we have to anticipate a epsilon to be shure to go before #self[v].scheduledatetime=scheduledatetime-timedelta(seconds=30) #we have to anticipate start program - spot length self[v].scheduledatetime=scheduledatetime-timedelta(seconds=lengthj) #recompute scheduledatetimej=self[v].scheduledatetime endscheduledatetimej=scheduledatetimej+timedelta(seconds=lengthj) #ending in the firth half of program if ( endscheduledatetimej > scheduledatetime and endscheduledatetimej < halfscheduledatetime ): logging.debug( "anticipate this spot overlapped end time in the firth half %s", str(self[v])) #we have to anticipate start program - spot length self[v].scheduledatetime=scheduledatetime-timedelta(seconds=lengthj) #recompute scheduledatetimej=self[v].scheduledatetime endscheduledatetimej=scheduledatetimej+timedelta(seconds=lengthj) #start in the second half of program if ( scheduledatetimej >= halfscheduledatetime and scheduledatetimej < endscheduledatetime ): logging.debug( "postpone this spot overlapped in the second half %s", str(self[v])) #we have to postpone start program - spot length self[v].scheduledatetime=endscheduledatetime #recompute scheduledatetimej=self[v].scheduledatetime endscheduledatetimej=scheduledatetimej+timedelta(seconds=lengthj) # this case is not so simple # after moving spots we have spots overlapped # this is possible when we have programs without time interval for spots like more enclosure in one episode # here is more simple to simulate one enclosure more long to include spots length # recompute programs length overlapped with spots if ( scheduledatetime < scheduledatetimej and scheduledatetimej < endscheduledatetime ): logging.debug( "adding time to program; this spot overlapped %s", str(self[v])) schedule.adjustedlength=schedule.length+lengthj needrecompute=True #v += 1 #now we can have programs overlapped bt programs for v,schedulej in enumerate(self): scheduledatetimej=schedulej.scheduledatetime if ( scheduledatetimej == None ): continue lengthj=schedulej.adjustedlength typej=schedulej.type endscheduledatetimej=scheduledatetimej+timedelta(seconds=lengthj) #print "elaborate ",typej,scheduledatetimej,endscheduledatetimej if (typej == "program"): for vv,schedule in enumerate(self): #do not compare with itself if schedule == schedulej and str(schedule) == str(schedulej): continue scheduledatetime=schedule.scheduledatetime if ( scheduledatetime== None ): continue length=schedule.adjustedlength type=schedule.type endscheduledatetime=scheduledatetime+timedelta(seconds=length) halfscheduledatetime=scheduledatetime+timedelta(seconds=length/2) if (type == "spot" or type == "playlist" or type == "jingle" ): continue # here we have program versus programs #starting in the firth half of program if ( scheduledatetime <= scheduledatetimej and scheduledatetimej < halfscheduledatetime ): logging.debug( "postpone this program overlapped start time in the firth half") logging.debug( "postpone %s, over %s", str(self[v]),str(self[vv])) #we have to postpone start program - spot length self[v].scheduledatetime=endscheduledatetime #recompute scheduledatetimej=self[v].scheduledatetime endscheduledatetimej=scheduledatetimej+timedelta(seconds=lengthj) needrecompute=True #ending in the firth half of program if ( endscheduledatetimej > scheduledatetime and endscheduledatetimej < halfscheduledatetime ): logging.debug( "anticipate this program overlapped end time in the firth half") logging.debug( "anticipate %s, over %s", str(self[v]),str(self[vv])) #we have to anticipate start program - spot length self[v].scheduledatetime=scheduledatetime-timedelta(seconds=lengthj) #recompute scheduledatetimej=self[v].scheduledatetime endscheduledatetimej=scheduledatetimej+timedelta(seconds=lengthj) needrecompute=True #start in the second half of program if ( scheduledatetimej >= halfscheduledatetime and scheduledatetimej < endscheduledatetime ): logging.debug( "postpone this program overlapped in the second half") logging.debug( "postpone %s, over %s", str(self[v]),str(self[vv])) #we have to postpone self[v].scheduledatetime=endscheduledatetime #recompute scheduledatetimej=self[v].scheduledatetime endscheduledatetimej=scheduledatetimej+timedelta(seconds=lengthj) needrecompute=True #Jingles # remove jingles overlapped with programs and spots #v=0 for v,schedulej in enumerate(self): scheduledatetimej=schedulej.scheduledatetime if ( scheduledatetimej == None ): continue lengthj=schedulej.length typej=schedulej.type endscheduledatetimej=scheduledatetimej+timedelta(seconds=lengthj) #print "elaboro ",typej,scheduledatetimej,endscheduledatetimej if (typej == "jingle"): for schedule in self: scheduledatetime=schedule.scheduledatetime if ( scheduledatetime== None ): continue length=schedule.length type=schedule.type endscheduledatetime=scheduledatetime+timedelta(seconds=length) if (type == "jingle" or type == "playlist"): continue # here we have jingle versus programs and spot if (( scheduledatetime < scheduledatetimej and scheduledatetimej < endscheduledatetime )\ or \ ( scheduledatetime < endscheduledatetimej and endscheduledatetimej < endscheduledatetime )): logging.debug( "remove this jingle overlapped %s", str(self[v])) self[v].scheduledatetime=None #v += 1 return needrecompute def purge(self): from itertools import izip reverse_enumerate = lambda l: izip(xrange(len(l)-1, -1, -1), reversed(l)) for ind,schedula in reverse_enumerate(self): if not schedula.filter(): logging.debug( "purge %s", str(schedula)) del self[ind] def get_all(self,now=None,genfile=True): # time constants #this is the first and last time that I set the current time if now is None : now=datetime.now() spots=gest_spot(now,minelab,playlistdir) for fascia in spots.get_fasce(genfile): media = spots.ar_url filename = spots.ar_filename scheduledatetime=spots.ar_scheduledatetime length=spots.ar_length emission_done=spots.ar_emission_done number=spots.ar_spots_in_fascia #print scheduledatetime,media,length,number,emission_done if (number <> 0 ): self.append(schedule(fascia,scheduledatetime,media,filename,length,"spot",emission_done,title=str(fascia))) programs=gest_program(now,minelab) for programma in programs.get_program(): media = programma.ar_url filename = programma.ar_filename scheduledatetime=programma.ar_scheduledatetime length=programma.ar_length emission_done=programma.ar_emission_done title=programma.ar_title #print scheduledatetime,media,length,emission_done self.append(schedule(programma,scheduledatetime,media,filename,length,"program",\ emission_done,title=title)) playlists=gest_playlist(now,minelab) for playlist in playlists.get_playlist(): media = playlist.ar_url filename = playlist.ar_filename scheduledatetime=playlist.ar_scheduledatetime length=playlist.ar_length maxlength=playlist.length emission_done=playlist.ar_emission_done shuffle=playlist.ar_shuffle #print scheduledatetime,media,length,emission_done self.append(schedule(playlist,scheduledatetime,media,filename,length,"playlist",\ emission_done,shuffle,maxlength,title=str(playlist))) jingles=gest_jingle(now,minelab) for jingle in jingles.get_jingle(): media = jingle.ar_url filename = jingle.ar_filename scheduledatetime=jingle.ar_scheduledatetime length=jingle.ar_length emission_done=jingle.ar_emission_done #print scheduledatetime,media,length,emission_done self.append(schedule(jingle,scheduledatetime,media,filename,length,"jingle",\ emission_done,title=str(jingle))) return self def get_all_refine(self,now=None,genfile=True): self.get_all(now,genfile) while self.districa(): pass self.purge() self.sort() return self class palimpsest: def __init__ (self,title=None,datetime_start=None,datetime_end=None, code=None,type=None,subtype=None,production=None,note=None): """ init of palimpsest object """ self.title=title self.datetime_start=datetime_start self.datetime_end=datetime_end self.code=code self.type=type self.subtype=subtype self.production=production self.note=note def __cmp__ (self, b): #check start datetime if self.datetime_start is None and b.datetime_start is None : return 0 if self.datetime_start is None : return -1 if b.datetime_start is None : return 1 if self.datetime_start == b.datetime_start : #check end datetime if self.datetime_end is None and b.datetime_end is None : return 0 if self.datetime_end is None : return -1 if b.datetime_end is None : return 1 if self.datetime_end == b.datetime_end : return 0 elif self.datetime_end < b.datetime_end : return -1 elif self.datetime_end > b.datetime_end : return 1 elif self.datetime_start < b.datetime_start : return -1 elif self.datetime_start > b.datetime_start : return 1 def __str__ (self): return self.title+" "+str(self.datetime_start)+" "+\ str(self.datetime_end)+" "+str(self.type)+" "+\ str(self.subtype)+" "+str(self.production)+" "+str(self.note) def __iter__(self): ''' return a list ''' yield self.title yield self.datetime_start yield self.datetime_end yield self.code yield self.type yield self.subtype yield self.production yield self.note class dates: def __init__(self,datetime_start, datetime_end,step): self.step=step self.datetime_start=datetime_start-self.step self.datetime_end=datetime_end def __iter__(self): return self def next(self): self.datetime_start=self.datetime_start+self.step if self.datetime_start <= self.datetime_end: return self.datetime_start else: raise StopIteration class palimpsests(list): def get_palimpsest(self,datetime_start,datetime_end): step=timedelta(minutes=minelab*2) for datetimeelab in dates(datetime_start, datetime_end, step): pro=gest_palimpsest(datetimeelab,minelab) for program in pro.get_program(): length=program.show.length if length is None: logging.warning("get_palimpsest: %s legth is None; setting default to 3600 sec",str(program)) length = 3600 pdatetime_start=program.ar_scheduledatetime title=str(program) pdatetime_end=program.ar_scheduledatetime+timedelta(seconds=length) code=program.show.type.code type=program.show.type.type subtype=program.show.type.subtype production=program.show.production note="" if pdatetime_start >= datetime_start and pdatetime_end < datetime_end : self.append(palimpsest(title,pdatetime_start,pdatetime_end, code,type,subtype,production,note)) self.sort() #print "prima:" #for program in self: # print program # timing adjust: # 1) overlay # 2) insert music no stop for interval >15 minutes musicanostop=palimpsests([]) for i in range(len(self)-1): if self[i].datetime_end > self[i+1].datetime_start: self[i].datetime_end=self[i+1].datetime_start elif self[i].datetime_end < self[i+1].datetime_start-timedelta(minutes=15): musicanostop.append(palimpsest("Musica no stop",self[i].datetime_end, self[i+1].datetime_start,code="13f", type="13",subtype="13f",production="autoproduzione",note=None)) for element in musicanostop: self.append(element) self.sort() for i in range(len(self)-1): # 3) chain little interval if self[i].datetime_end != self[i+1].datetime_start: dtmean=self[i].datetime_end+((self[i+1].datetime_start-self[i].datetime_end)/2) self[i].datetime_end=dtmean self[i+1].datetime_start=dtmean # add head and tail: # chain little interval if len(self) > 0 : if self[0].datetime_start != datetime_start : self.insert(0,palimpsest("Musica no stop",datetime_start, self[0].datetime_start,code="13f", type="13",subtype="13f",production="autoproduzione",note=None)) if self[len(self)-1].datetime_end != datetime_end : self.append(palimpsest("Musica no stop",self[len(self)-1].datetime_end, datetime_end,code="13f", type="13",subtype="13f",production="autoproduzione",note=None)) #print "dopo:" #for program in self: # print program # Spots for datetimeelab in dates(datetime_start, datetime_end, step): #print datetimeelab,minelab spots=gest_spot(datetimeelab,minelab,playlistdir) for fascia in spots.get_fasce(genfile=False): length=spots.ar_length #pdatetime_start=spots.ar_emission_done pdatetime_start=spots.ar_scheduledatetime number=spots.ar_spots_in_fascia #title=str(fascia) title="Pubblicità" pdatetime_end=pdatetime_start+timedelta(seconds=length) type="5" subtype="5a" production="" note="%d Spot" % number #if (number <> 0 and pdatetime_start.date() == dateelab): if number <> 0 and pdatetime_start >= datetime_start and pdatetime_end < datetime_end : self.append(palimpsest(title,pdatetime_start,pdatetime_end, type,subtype,production,note)) self.sort() return self def main(): logging.basicConfig(level=logging.INFO,) # pali=palimpsests([]) # print "------- palimpsest --------" # for prog in pali.get_palimpsest(datetime.now()-timedelta(days=1),datetime.now()): # print "------- program --------" # # print prog # # #for elemento in prog: # # print elemento scheds=schedules([]) # get the schedule of my insterest # I do a list print "------- schedules --------" for sched in scheds.get_all_refine(): print "------- schedule --------" for elemento in sched: print elemento print sched.type print sched.media print sched.scheduledatetime print sched.shuffle print sched.length if __name__ == '__main__': main() # (this code was run as script) autoradio-upstream-2.8.2+dfsg.orig/autoradio/settings.py0000644000175000017500000002672412332264611023676 0ustar capriottcapriott# Django settings for autoradio project. import os from configobj import ConfigObj,flatten_errors from validate import Validator configspec={} configspec['django']={} configspec['django']['DEBUG']="boolean(default=True)" configspec['django']['TEMPLATE_DEBUG']="boolean(default=True)" configspec['django']['FILE_UPLOAD_PERMISSIONS']="integer(default=420)" configspec['django']['SECRET_KEY']="string(default='random-string-of-ascii')" configspec['django']['SESSION_COOKIE_DOMAIN']="string(default=None)" configspec['django']['SERVER_EMAIL']="string(default='localhost')" configspec['django']['EMAIL_HOST']="string(default='localhost')" configspec['django']['TIME_ZONE']="string(default='Europe/Rome')" configspec['django']['LANGUAGE_CODE']="string(default='en-us')" configspec['django']['SITE_ID']="integer(default=1)" configspec['django']['USE_I18N']="boolean(default=True)" configspec['django']['LOCALE_PATHS']="list(default=list('locale',))" configspec['django']['ADMINS']="list(default=list('',))" configspec['django']['MANAGERS']="list(default=list('',))" configspec['django']['MEDIA_ROOT']="string(default='%s/media/')" % os.getcwd() configspec['django']['MEDIA_SITE_ROOT']="string(default='%s/media/')" % os.getcwd() configspec['django']['TEMPLATE_DIRS']="list(default=list('templates',))" configspec['django']['BASE_URL']="string(default='/django/')" configspec['django']['ADMIN_MEDIA_PREFIX']="string(default='/django/media/admin/')" configspec['django']['STATIC_URL']="string(default='/django/media/')" configspec['django']['STATIC_ROOT'] = "string(default='/usr/lib/python2.7/site-packages/django/contrib/admin/static/admin/')" configspec['django']['MEDIA_PREFIX']="string(default='/media/')" configspec['django']['MEDIA_SITE_PREFIX']="string(default='/media/sito/')" configspec['django']['SERVE_STATIC']="boolean(default=True)" configspec['autoradioweb']={} configspec['autoradioweb']['logfile'] = "string(default='/tmp/autoradioweb.log')" configspec['autoradioweb']['errfile'] = "string(default='/tmp/autoradioweb.err')" configspec['autoradioweb']['lockfile'] = "string(default='/tmp/autoradioweb.lock')" configspec['autoradioweb']['user'] = "string(default=None)" configspec['autoradioweb']['group'] = "string(default=None)" configspec['autoradioweb']['port'] = "string(default='8080')" configspec['autoradioweb']['permit_no_playable_files'] = "boolean(default=False)" configspec['autoradioweb']['require_tags_in_enclosure'] = "boolean(default=False)" configspec['database']={} configspec['database']['DATABASE_USER']="string(default='')" configspec['database']['DATABASE_PASSWORD']="string(default='')" configspec['database']['DATABASE_HOST']="string(default='localhost')" configspec['database']['DATABASE_PORT']="integer(default=3306)" configspec['database']['DATABASE_ENGINE']="string(default='sqlite3')" configspec['database']['DATABASE_NAME']="string(default='%s/autoradio.sqlite3')" % os.getcwd() configspec['autoplayer']={} configspec['autoplayer']['logfile'] = "string(default='/tmp/autoplayer.log')" configspec['autoplayer']['errfile'] = "string(default='/tmp/autoplayer.err')" configspec['autoplayer']['lockfile'] = "string(default='/tmp/autoplayer.lock')" configspec['autoplayer']['user'] = "string(default=None)" configspec['autoplayer']['group'] = "string(default=None)" configspec['autoplayer']['busaddress'] = "string(default=None)" configspec['autoplayer']['audiosink'] = "string(default=None)" configspec['autoradiodbus']={} configspec['autoradiodbus']['logfile'] = "string(default='/tmp/autoradiodbus.log')" configspec['autoradiodbus']['errfile'] = "string(default='/tmp/autoradiodbus.err')" configspec['autoradiodbus']['lockfile'] = "string(default='/tmp/autoradiodbus.lock')" configspec['autoradiodbus']['conffile'] = "string(default='dbus-autoradio.conf')" configspec['autoradiodbus']['user'] = "string(default=None)" configspec['autoradiodbus']['group'] = "string(default=None)" configspec['jackdaemon']={} configspec['jackdaemon']['logfile'] = "string(default='/tmp/jackdaemon.log')" configspec['jackdaemon']['errfile'] = "string(default='/tmp/jackdaemon.err')" configspec['jackdaemon']['lockfile'] = "string(default='/tmp/jackdaemon.lock')" configspec['jackdaemon']['user'] = "string(default=None)" configspec['jackdaemon']['group'] = "string(default=None)" config = ConfigObj ('/etc/autoradio/autoradio-site.cfg',file_error=False,configspec=configspec) usrconfig = ConfigObj (os.path.expanduser('~/.autoradio.cfg'),file_error=False) config.merge(usrconfig) usrconfig = ConfigObj ('autoradio.cfg',file_error=False) config.merge(usrconfig) val = Validator() test = config.validate(val,preserve_errors=True) for entry in flatten_errors(config, test): # each entry is a tuple section_list, key, error = entry if key is not None: section_list.append(key) else: section_list.append('[missing section]') section_string = ', '.join(section_list) if error == False: error = 'Missing value or section.' print section_string, ' = ', error raise error # section django DEBUG = config['django']['DEBUG'] TEMPLATE_DEBUG = config['django']['TEMPLATE_DEBUG'] FILE_UPLOAD_PERMISSIONS = config['django']['FILE_UPLOAD_PERMISSIONS'] SECRET_KEY = config['django']['SECRET_KEY'] SESSION_COOKIE_DOMAIN = config['django']['SESSION_COOKIE_DOMAIN'] SERVER_EMAIL = config['django']['SERVER_EMAIL'] EMAIL_HOST = config['django']['EMAIL_HOST'] TIME_ZONE = config['django']['TIME_ZONE'] LANGUAGE_CODE = config['django']['LANGUAGE_CODE'] SITE_ID = config['django']['SITE_ID'] USE_I18N = config['django']['USE_I18N'] LOCALE_PATHS = config['django']['LOCALE_PATHS'] ADMINS = config['django']['ADMINS'] MANAGERS = config['django']['MANAGERS'] MEDIA_ROOT = config['django']['MEDIA_ROOT'] if "%s" in MEDIA_ROOT: MEDIA_ROOT = MEDIA_ROOT % os.getcwd() MEDIA_SITE_ROOT = config['django']['MEDIA_SITE_ROOT'] if "%s" in MEDIA_SITE_ROOT: MEDIA_SITE_ROOT = MEDIA_SITE_ROOT % os.getcwd() TEMPLATE_DIRS = config['django']['TEMPLATE_DIRS'] BASE_URL = config['django']['BASE_URL'] ADMIN_MEDIA_PREFIX = config['django']['ADMIN_MEDIA_PREFIX'] STATIC_URL = config['django']['STATIC_URL'] STATIC_ROOT = config['django']['STATIC_ROOT'] MEDIA_PREFIX = config['django']['MEDIA_PREFIX'] MEDIA_SITE_PREFIX = config['django']['MEDIA_SITE_PREFIX'] SERVE_STATIC = config['django']['SERVE_STATIC'] MEDIA_URL = BASE_URL+MEDIA_PREFIX SITE_MEDIA_URL = BASE_URL+MEDIA_SITE_PREFIX # section autoradioweb logfileweb = config['autoradioweb']['logfile'] errfileweb = config['autoradioweb']['errfile'] lockfileweb = config['autoradioweb']['lockfile'] userweb = config['autoradioweb']['user'] groupweb = config['autoradioweb']['group'] port = config['autoradioweb']['port'] permit_no_playable_files= config['autoradioweb']['permit_no_playable_files'] require_tags_in_enclosure= config['autoradioweb']['require_tags_in_enclosure'] # section database DATABASE_USER = config['database']['DATABASE_USER'] DATABASE_PASSWORD = config['database']['DATABASE_PASSWORD'] DATABASE_HOST = config['database']['DATABASE_HOST'] DATABASE_PORT = config['database']['DATABASE_PORT'] DATABASE_ENGINE = config['database']['DATABASE_ENGINE'] DATABASE_NAME = config['database']['DATABASE_NAME'] # section autoplayer logfileplayer = config['autoplayer']['logfile'] errfileplayer = config['autoplayer']['errfile'] lockfileplayer = config['autoplayer']['lockfile'] userplayer = config['autoplayer']['user'] groupplayer = config['autoplayer']['group'] busaddressplayer = config['autoplayer']['busaddress'] audiosinkplayer = config['autoplayer']['audiosink'] # section autoradiodbus logfiledbus = config['autoradiodbus']['logfile'] errfiledbus = config['autoradiodbus']['errfile'] lockfiledbus = config['autoradiodbus']['lockfile'] conffiledbus = config['autoradiodbus']['conffile'] userdbus = config['autoradiodbus']['user'] groupdbus = config['autoradiodbus']['group'] # section jackdaemon logfilejack = config['jackdaemon']['logfile'] errfilejack = config['jackdaemon']['errfile'] lockfilejack = config['jackdaemon']['lockfile'] userjack = config['jackdaemon']['user'] groupjack = config['jackdaemon']['group'] if DATABASE_ENGINE == "mysql": # Recommended for MySQL. See http://code.djangoproject.com/ticket/13906 # to avoid "Lost connection to MySQL server at 'reading authorization packet', system error: 0" # connect_timeout=30 DATABASES = { 'default': { 'ENGINE': 'django.db.backends.'+DATABASE_ENGINE, 'NAME': DATABASE_NAME, 'USER': DATABASE_USER, 'PASSWORD':DATABASE_PASSWORD, 'HOST': DATABASE_HOST, 'PORT': DATABASE_PORT, 'OPTIONS': {'init_command': 'SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED', 'connect_timeout':60}, } } else: DATABASES = { 'default': { 'ENGINE': 'django.db.backends.'+DATABASE_ENGINE, 'NAME': DATABASE_NAME, 'USER': DATABASE_USER, 'PASSWORD':DATABASE_PASSWORD, 'HOST': DATABASE_HOST, 'PORT': DATABASE_PORT, } } # List of callables that know how to import templates from various sources. TEMPLATE_LOADERS = ( 'django.template.loaders.filesystem.Loader', 'django.template.loaders.app_directories.Loader', # 'django.template.loaders.eggs.load_template_source', ) MIDDLEWARE_CLASSES = ( 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.locale.LocaleMiddleware', 'django.middleware.common.CommonMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.middleware.doc.XViewMiddleware', 'django.contrib.messages.middleware.MessageMiddleware') ROOT_URLCONF = 'autoradio.urls' INSTALLED_APPS = ( 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.admin', 'django.contrib.admindocs', 'django.contrib.staticfiles', 'autoradio.programs', 'autoradio.jingles', 'autoradio.spots', 'autoradio.playlists', 'autoradio.doc', ) # django save the files on memory, but large files are saved in a path. # The size of "large file" can be defined in settings using # FILE_UPLOAD_MAX_MEMORY_SIZE and The FILE_UPLOAD_HANDLERS by default are: #("django.core.files.uploadhandler.MemoryFileUploadHandler", # "django.core.files.uploadhandler.TemporaryFileUploadHandler",) # remove MemoryFileUploadHandler FILE_UPLOAD_HANDLERS = ( "django.core.files.uploadhandler.TemporaryFileUploadHandler",) try: import django_extensions INSTALLED_APPS += 'django_extensions', except ImportError: print "django_extensions is not installed; I do not use it" pass autoradio-upstream-2.8.2+dfsg.orig/autoradio/programs/0000755000175000017500000000000012332265077023312 5ustar capriottcapriottautoradio-upstream-2.8.2+dfsg.orig/autoradio/programs/fixtures/0000755000175000017500000000000012332265077025163 5ustar capriottcapriottautoradio-upstream-2.8.2+dfsg.orig/autoradio/programs/fixtures/initial_data.json0000644000175000017500000003720612332264611030501 0ustar capriottcapriott [ {"pk": 1, "model": "programs.programtype", "fields": { "code":"1a", "type": "Notiziari", "subtype": "Telegiornale","description": "Trasmissione a carattere informativo con programmazione quotidiana all'interno di fasce orarie prestabilite"}}, {"pk": 2, "model": "programs.programtype", "fields": { "code":"1b", "type": "Notiziari","subtype": "Telegiornale sportivo" ,"description": "Trasmissione di informazione sportiva con programmazione quotidiana all'interno di fasce orarie prestabilite." }}, {"pk": 3, "model": "programs.programtype", "fields": { "code":"1c" ,"type": "Notiziari", "subtype":"Servizi teletext" }}, {"pk": 4, "model": "programs.programtype", "fields": { "code":"2a" ,"type": "Giochi", "subtype":"Telequiz","description":"Trasmissioni di quiz in diretta o registrati, in studio e con concorrenti, caratterizzati dal succedersi di domande e risposte con vincite di premi non simbolici." }}, {"pk": 5, "model": "programs.programtype", "fields": { "code":"2b" ,"type": "Giochi","subtype": "Giochi televisivi" ,"description":"Trasmissioni di giochi in studio con concorrenti o telespettaori che vi partecipano, con vincite di premi non simbolici o denaro." }}, {"pk": 6, "model": "programs.programtype", "fields": { "code":"3" , "type": "Talk Show", "subtype":"Talk Show" , "description":"Programmi con ospiti in studio (ed eventualmente anche pubblico) che dibattono argomenti vari con un intrattenitore che media tra i vari interventi per animare la conversazione." }}, {"pk": 7, "model": "programs.programtype", "fields": { "code":"4", "type": "Manifestazioni sportive","subtype": "Manifestazioni sportive","description": "Manifestazioni (in diretta o in differita) a carattere sportivo (sport riconosciuti dal CONI)." }}, {"pk": 8, "model": "programs.programtype", "fields": { "code":"5a", "type": "Pubblicit\u00e0","subtype": "Pubblicit\u00e0" }}, {"pk": 9, "model": "programs.programtype", "fields": { "code":"5b", "type": "Pubblicit\u00e0","subtype": "Telepromozioni" }}, {"pk": 10, "model": "programs.programtype", "fields": { "code":"5c", "type": "Pubblicit\u00e0", "subtype":"Sponsorizzazioni" }}, {"pk": 11, "model": "programs.programtype", "fields": { "code":"6", "type":"Televendite" ,"subtype":"Televendite" }}, {"pk": 12, "model": "programs.programtype", "fields": { "code":"7a","type": "Film", "subtype":"Film cinematografici", "description": "Produzioni filmiche destinate principalmente al circuito cinematografico e prodotte su pellicola." }}, {"pk": 13, "model": "programs.programtype", "fields": { "code":"7b", "type":"Film", "subtype":"Film TV", "description": "Produzioni filmiche su supporto magnetico, di durata massima di 200 minuti, eccezionalmente composte di due episodi." }}, {"pk": 14, "model": "programs.programtype", "fields": { "code":"8a", "type":"Fiction", "subtype":"Miniserie - sceneggiato", "description": "Fiction di produzione italiana che contenga un numero minimo di 5 puntate. Le puntate di circa 60 minuti hanno il finale aperto che si chiude con l'ultima puntata." }}, {"pk": 15, "model": "programs.programtype", "fields": { "code":"8b", "type":"Fiction", "subtype":"Telefilm", "description": "Serie costituita da episodi che non superano mai i 60 minuti che propongono storie autonome (con finale chiuso). La continuit\u00e0 narrativa \u00e8 assicurata dalla presenza di personaggi fissi, da una ambientazione che raramente varia e da caratteri strutturali comuni." }}, {"pk": 16, "model": "programs.programtype", "fields": { "code":"8c", "type":"Fiction", "subtype":"Situation comedies", "description": "Serie costituita da episodi 30 minuti con finale solitamente chiuso. Girate solitamente in interni, mettono in scena vicende soprattutto familiari con un impronta comico-grottesca." }}, {"pk": 17, "model": "programs.programtype", "fields": { "code":"8d", "type":"Fiction", "subtype":"Soap operas - telenovelas", "description": "Serial in puntate da 20 a 35 minuti con finale aperto." }}, {"pk": 18, "model": "programs.programtype", "fields": { "code":"8e","type": "Fiction", "subtype":"Comiche d'epoca", "description": "Genere usato per i film comici d'epoca." }}, {"pk": 19, "model": "programs.programtype", "fields": { "code":"9a", "type":"Documentari", "subtype":"Storia - geografia", "description": "Trasmissioni il cui scopo è documentare con filmati ed immagini la realt\u00e0 storico-geografica." }}, {"pk": 20, "model": "programs.programtype", "fields": { "code":"9b", "type":"Documentari", "subtype":"Scienza", "description": "Trasmissioni il cui scopo è documentare con filmati ed immagini la realt\u00e0 animale, vegetale, etc." }}, {"pk": 21, "model": "programs.programtype", "fields": { "code":"10a", "type":"Programmi informativi / approfondimento", "subtype":"Informazione parlamentare", "description": "Telegiornale informativo con collocazione periodica (quotidiana o settimanale) su temi che attengono quasi esclusivamente alla politica o il parlamento" }}, {"pk": 22, "model": "programs.programtype", "fields": { "code":"10b", "type":"Programmi informativi / approfondimento", "subtype":"Dichiarazioni parlamentari", "description": "Riprese in diretta di dibattiti in Parlamento, dichiarazioni del Pres. Del Consiglio, della repubblica, etc." }}, {"pk": 23, "model": "programs.programtype", "fields": { "code":"10c", "type":"Programmi informativi / approfondimento", "subtype":"Inchieste", "description": "Programma giornalistico di approfondimento (spesso anche con filmati) solitamente su singole tematiche." }}, {"pk": 24, "model": "programs.programtype", "fields": { "code":"10d", "type":"Programmi informativi / approfondimento", "subtype":"Rubriche di approfondimento delle testate giornalistiche", "description":"Programmi di approfondimento su tematiche di attualit\u00e0. Supplementi informativi alle edizioni dei TG a cura delle testate giornalistiche" }}, {"pk": 25, "model": "programs.programtype", "fields": { "code":"10e", "type":"Programmi informativi / approfondimento", "subtype":"Costume e societ\u00e0", "description": "Trasmissioni che documentano usi, costumi, tradizioni, viaggi, curiosit\u00e0, della societ\u00e0 moderna. Programmi che trattano del profilo e della vita di personaggi celebri scomparsi." }}, {"pk": 26, "model": "programs.programtype", "fields": { "code":"10f", "type":"Programmi informativi / approfondimento", "subtype":"Rubriche religiose", "description": "Programmi a carattere religioso, di qualunque 'credo', registrati in studio" }}, {"pk": 27, "model": "programs.programtype", "fields": { "code":"10g", "type":"Programmi informativi / approfondimento", "subtype":"Dibattiti", "description": "Programmi che prevedono un dibattito in studio o fuori studio per l'approfondimento di temi solitamente di attualit\u00e0 sociale o politica. Possono essere legati alla trasmissione di un film che li precede o li segue." }}, {"pk": 28, "model": "programs.programtype", "fields": { "code":"10h", "type":"Programmi informativi / approfondimento", "subtype":"Rubriche di approfondimento sportivo", "description": "Trasmissioni di approfondimento sportivo a programmazione periodica. Possono essere anche monografie di personaggi o episodi sportivi o fungere da contenitore di manifestazioni sportive." }}, {"pk": 29, "model": "programs.programtype", "fields": { "code":"10i", "type":"Programmi informativi / approfondimento", "subtype":"Teledidattica", "description": "Programmi puramente didattico-informativi. Programmi generalmente caratterizzati dal logo 'DSE', 'Video Sapere' e RAI Educational" }}, {"pk": 30, "model": "programs.programtype", "fields": { "code":"10j", "type":"Programmi informativi / approfondimento", "subtype":"Approfondimento culturale", "description": "Programmi, anche con eventuali dibattiti, a carattere culturale su temi di storia, geografia, scienza, ambiente, letteratura, arte, etc." }}, {"pk": 31, "model": "programs.programtype", "fields": { "code":"11a", "type":"Programmi culturali con parti autonome", "subtype":"Concerti", "description": "Programma il cui contenuto coincide con la messa in onda di concerti di musica leggera o sinfonici." }}, {"pk": 32, "model": "programs.programtype", "fields": { "code":"11b", "type":"Programmi culturali con parti autonome", "subtype":"Balletti","description": "Rappresentazione di uno spettacolo di danza classica" }}, {"pk": 33, "model": "programs.programtype", "fields": { "code":"11c", "type":"Programmi culturali con parti autonome", "subtype":"Lirica", "description":"Trasmissione il cui contenuto prevede l'esecuzione di 'Opere liriche'" }}, {"pk": 34, "model": "programs.programtype", "fields": { "code":"11d", "type":"Programmi culturali con parti autonome", "subtype":"Prosa", "description": "Rappresentazione di spettacoli di prosa teatrale o televisiva" }}, {"pk": 35, "model": "programs.programtype", "fields": { "code":"12", "type":"Cartoni animati per bambini","subtype":"Cartoni animati per bambini", "description": "Programma di animazione della durata massima di 60 min. destinato ad un pubblico infantile" }}, {"pk": 36, "model": "programs.programtype", "fields": { "code":"13a","type":"Intrattenimento","subtype":"Programmi musicali","description": "Programmi girati in studio che si occupano del panorama della musica leggera: clip musicali, classifiche, retrospettive. Possono fungere da contenitore di concerti." }}, {"pk": 37, "model": "programs.programtype", "fields": { "code":"13b","type":"Intrattenimento,Reality show", "subtype":"Programmi basati sulla trasmissione di riprese effettuate dal vivo ed in diretta", "description": "aventi come target esclusivo la riproduzione televisiva di scene di vita reale o comunque di attivit\u00e0 non preordinate svolte da parte di una o pi\u00f9 persone all'interno di uno studio televisivo o un ambiente predefinito" }}, {"pk": 38, "model": "programs.programtype", "fields": { "code":"13c","type":"Intrattenimento", "subtype":"Programmi di montaggio", "description": "Programmi basati sull'accostamento di immagini registrate, montate secondo una specifica linea interpretativa" }}, {"pk": 39, "model": "programs.programtype", "fields": { "code":"13d","type":"Intrattenimento","subtype":"Variet\u00e0", "description": "Trasmissioni di intrattenimento leggero. Le componenti che caratterizzano questo prodotto sono: un'impostazione di derivazione teatrale, una scenografia ad effetto, la presenza di balletti, di canzoni e di sketch nonche' di uno o pi\u00f9 conduttori." }}, {"pk": 40, "model": "programs.programtype", "fields": { "code":"13e","type":"Intrattenimento","subtype":"Astrologia - cartomanzia","description": "Programmi girati in studio e caratterizzati dalla presenza di un astrologo o cartomante, in genere in contatto telefonico con i telespettatori" }}, {"pk": 41, "model": "programs.programtype", "fields": { "code":"13f","type":"Intrattenimento","subtype":"Programma contenitore radiofonico" }}, {"pk": 42, "model": "programs.programtype", "fields": { "code":"13g","type":"Intrattenimento", "subtype":"Cartoni animati per adulti","description": "Programma di animazione della durata massima di 60 min. destinato ad un pubblico adulto" }}, {"pk": 43, "model": "programs.programtype", "fields": { "code":"13h","type":"Intrattenimento", "subtype":"Trasmissioni per bambini","description": "Trasmissioni destinate ad un pubblico infantile, condotte in studio o in esterno con o senza la partecipazione di bambini. Possono contenere giochi o quiz e spesso cartoni animati." }}, {"pk": 44, "model": "programs.programtype", "fields": { "code":"14a","type":"Attualit\u00e0","subtype":"Anteprima","description": "Programmi che hanno lo scopo di dare informazione o promuovere l'imminente programmazione cinematografica." }}, {"pk": 45, "model": "programs.programtype", "fields": { "code":"14b","type":"Attualit\u00e0", "subtype":"Promo","description": "Auto-promozione di eventi che saranno trasmessi sulla stessa rete o su altre reti dello stesso gruppo." }}, {"pk": 46, "model": "programs.programtype", "fields": { "code":"14c","type":"Attualit\u00e0", "subtype":"Rotocalchi","description": "Trasmissioni 'informative' a carattere di cronaca rosa e di curiosit\u00e0 varie." }}, {"pk": 47, "model": "programs.programtype", "fields": { "code":"14d","type":"Attualit\u00e0", "subtype":"Meteo","description": "Programma di previsioni metereologiche" }}, {"pk": 48, "model": "programs.programtype", "fields": { "code":"14e","type":"Attualit\u00e0","subtype":"Lotterie","description": "Estrazioni del Lotto" }}, {"pk": 49, "model": "programs.programtype", "fields": { "code":"14f","type":"Attualit\u00e0","subtype":"Rubriche di servizio","description": "Trasmissioni non condotte in studio che offrono informazioni su: modalit\u00e0 per il voto, viabilit\u00e0 e bollettini sul traffico, numeri telefonici utili." }}, {"pk": 50, "model": "programs.programtype", "fields": { "code":"14g","type":"Attualit\u00e0","subtype":"Trasmissioni di servizio","description": "Programmi condotti in studio con lo scopo di offrire un servizio socio-informativo." }}, {"pk": 51, "model": "programs.programtype", "fields": { "code":"14h","type":"Attualit\u00e0","subtype":"Inaugurazioni","description": "Trasmissioni, generalmente in diretta, che documentano inaugurazioni" }}, {"pk": 52, "model": "programs.programtype", "fields": { "code":"14i","type":"Attualit\u00e0","subtype":"Premiazioni","description": "Trasmissioni, generalmente in diretta, che documentano premi letterari e premiazioni" }}, {"pk": 53, "model": "programs.programtype", "fields": { "code":"14j","type":"Attualit\u00e0","subtype":"Manifestazioni di piazza","description": "Trasmissioni, generalmente in diretta, che documentano manifestazioni di piazza" }}, {"pk": 54, "model": "programs.programtype", "fields": { "code":"15a","type":"Eventi religiosi","subtype":"Santa Messa","description": "Trasmissioni, generalmente domenicali ed in diretta, che seguono la Santa Messa" }}, {"pk": 55, "model": "programs.programtype", "fields": { "code":"15b","type":"Eventi religiosi","subtype":"Eventi religiosi","description": "Trasmissioni, generalmente in diretta, che documentano manifestazioni religiose" }}, {"pk": 56, "model": "programs.programtype", "fields": { "code":"16a","type":"Programmi accessori", "subtype":"Annunci","description": "Programmi aventi carattere accessorio rispetto al palinsesto" }}, {"pk": 57, "model": "programs.programtype", "fields": { "code":"16b","type":"Programmi accessori", "subtype":"Sigle","description": "Programmi aventi carattere accessorio rispetto al palinsesto" }}, {"pk": 58, "model": "programs.programtype", "fields": { "code":"16c","type":"Programmi accessori", "subtype":"Intervalli","description": "Programmi aventi carattere accessorio rispetto al palinsesto" }}, {"pk": 59, "model": "programs.programtype", "fields": { "code":"16d","type":"Programmi accessori", "subtype":"Segnale orario","description": "Programmi aventi carattere accessorio rispetto al palinsesto" }}, {"pk": 60, "model": "programs.programtype", "fields": { "code":"17", "type":"Messaggi politici autogestiti gratuiti", "subtype":"Messaggi politici autogestiti gratuiti","description": "Messaggi politici autogestiti a titolo gratuito ai sensi dell'art. 3 della legge 22 Febbraio 2000 n. 28" }}, {"pk": 61, "model": "programs.programtype", "fields": { "code":"18","type":"Messaggi politici autogestiti a pagamento","subtype":"Messaggi politici autogestiti a pagamento","description": "Messaggi politici autogestiti a pagamento ai sensi dell'art. 3 della legge 22 Febbraio 2000 n. 28" }}, {"pk": 62, "model": "programs.programtype", "fields": { "code":"19","type":"Comunicazione politica","subtype":"Comunicazione politica","description": "Programmi di comunicazione politica ai sensi dell'art. 2 della legge 22 Febbraio 2000 n. 28" }}, {"pk": 63, "model": "programs.programtype", "fields": { "code":"20","type":"Immagini fisse o ripetitive","subtype":"Immagini fisse o ripetitive" }} ] autoradio-upstream-2.8.2+dfsg.orig/autoradio/programs/widgets.py0000644000175000017500000000626512332264611025334 0ustar capriottcapriott""" widgets.py from http://djangosnippets.org/snippets/1834/ """ import datetime import re from django.forms.widgets import Widget, Select, TextInput from django.utils.dates import MONTHS from django.utils.safestring import mark_safe __all__ = ('MySelectDateWidget',) RE_DATE = re.compile(r'(\d{4})-(\d\d?)-(\d\d?)$') class MySelectDateWidget(Widget): """ A Widget that splits date input into three