autoradio-2.8.6/0000775000175000017500000000000013003471473013256 5ustar pat1pat100000000000000autoradio-2.8.6/TODO0000664000175000017500000004023113001105756013742 0ustar pat1pat100000000000000== TODO == 17/02/2013 * utilizzare mp3splt sper spezzare i programmi e inserirli nei podcast * alternare meglio i jingle tenendo in considerazione la priorita * link in home page to spot playlist is wrong * on programbook programtype 5 need subtype 5a * code dump in player when trackremoved activated: Core was generated by `python ./autoplayerd run'. Program terminated with signal 6, Aborted. #0 0x0000003971236285 in __GI_raise (sig=6) at ../nptl/sysdeps/unix/sysv/linux/raise.c:64 64 return INLINE_SYSCALL (tgkill, 3, pid, selftid, sig); Missing separate debuginfos, use: debuginfo-install gstreamer-python-0.10.19-2.fc15.x86_64 libid3tag-0.15.1b-11.fc15.x86_64 libmad-0.15.1b-13.fc12.x86_64 orc-0.4.16-5.fc16.x86_64 (gdb) where #0 0x0000003971236285 in __GI_raise (sig=6) at ../nptl/sysdeps/unix/sysv/linux/raise.c:64 #1 0x0000003971237b9b in __GI_abort () at abort.c:91 #2 0x00000036bce2fff5 in _dbus_abort () at dbus-sysdeps.c:94 #3 0x00000036bce26fc1 in _dbus_warn_check_failed (format= 0x36bce362b0 "arguments to %s() were incorrect, assertion \"%s\" failed in file %s line %d.\nThis is normally a bug in some application using the D-Bus library.\n") at dbus-internals.c:289 #4 0x00000036bce19cbb in dbus_message_iter_append_basic (iter=0x7fffb9abca10, type=, value=0x7fffb9abc938) at dbus-message.c:2514 #5 0x00007fb5bcea2e22 in _message_iter_append_string (appender=0x7fffb9abca10, sig_type=111, obj=, allow_object_path_attr=) at message-append.c:628 #6 0x00007fb5bcea4292 in _message_iter_append_pyobject (appender=0x7fffb9abca10, sig_iter=0x7fffb9abca60, obj=u'638', more=0x7fffb9abca8c) at message-append.c:1174 #7 0x00007fb5bcea4b51 in dbus_py_Message_append (self=0x2699d68, args=(u'638',), kwargs=) at message-append.c:1301 #8 0x0000003c6e0dfb7d in ext_do_call (nk=1, na=, flags=, pp_stack=0x7fffb9abcba8, func= ) at /usr/src/debug/Python-2.7.3/Python/ceval.c:4408 #9 PyEval_EvalFrameEx (f=, throwflag=) at /usr/src/debug/Python-2.7.3/Python/ceval.c:2779 #10 0x0000003c6e0e19a5 in PyEval_EvalCodeEx (co=, globals=, locals=, args=, argcount= 2, kws=0x2473ee8, kwcount=0, defs=0x0, defcount=0, closure= (, , , , , )) at /usr/src/debug/Python-2.7.3/Python/ceval.c:3330 #11 0x0000003c6e0dff03 in fast_function (nk=, na=2, n=, pp_stack=0x7fffb9abcd98, func=) at /usr/src/debug/Python-2.7.3/Python/ceval.c:4194 #12 call_function (oparg=, pp_stack=0x7fffb9abcd98) at /usr/src/debug/Python-2.7.3/Python/ceval.c:4119 #13 PyEval_EvalFrameEx (f=, throwflag=) at /usr/src/debug/Python-2.7.3/Python/ceval.c:2740 #14 0x0000003c6e0e19a5 in PyEval_EvalCodeEx (co=, globals=, locals=, args=, argcount= 2, kws=0x7fb5c6952068, kwcount=0, defs=0x0, defcount=0, closure=0x0) at /usr/src/debug/Python-2.7.3/Python/ceval.c:3330 #15 0x0000003c6e06e093 in function_call (func=, arg= (, _Connection__call_on_disconnection=[], _dbus_Connection_initialized=1, _bus_names=, data={'org.mpris.MediaPlayer2.AutoPlayer': }) at remote 0x2d8c5a8>, _signal_sender_matches={}, _signal_recipients_by_object_path={'/org/freedesktop/DBus': {None: {'NameOwnerChanged': []}}}) at remote 0x2a247d0>, _bus_name=) at remote 0x2b1d510>, _locations=[(<...>, '/org/mpris/MediaPlayer2', False)], _uname=':1.231', _bus=<...>, player= to continue, or q to quit--- #16 0x0000003c6e049383 in PyObject_Call (func=, arg=, kw=) at /usr/src/debug/Python-2.7.3/Objects/abstract.c:2529 #17 0x0000003c6e0dc76f in ext_do_call (nk=0, na=, flags=, pp_stack=0x7fffb9abd058, func= ) at /usr/src/debug/Python-2.7.3/Python/ceval.c:4411 #18 PyEval_EvalFrameEx (f=, throwflag=) at /usr/src/debug/Python-2.7.3/Python/ceval.c:2779 #19 0x0000003c6e0e19a5 in PyEval_EvalCodeEx (co=, globals=, locals=, args=, argcount= 3, kws=0x2bdd990, kwcount=0, defs=0x0, defcount=0, closure=0x0) at /usr/src/debug/Python-2.7.3/Python/ceval.c:3330 #20 0x0000003c6e06df9c in function_call (func=, arg= (, _Connection__call_on_disconnection=[], _dbus_Connection_initialized=1, _bus_names=, data={'org.mpris.MediaPlayer2.AutoPlayer': }) at remote 0x2d8c5a8>, _signal_sender_matches={}, _signal_recipients_by_object_path={'/org/freedesktop/DBus': {None: {'NameOwnerChanged': []}}}) at remote 0x2a247d0>, _bus_name=) at remote 0x2b1d510>, _locations=[(<...>, '/org/mpris/MediaPlayer2', False)], _uname=':1.231', _bus=<...>, player=, arg=, kw=) at /usr/src/debug/Python-2.7.3/Objects/abstract.c:2529 #22 0x0000003c6e05801f in instancemethod_call (func=, arg= (, _Connection__call_on_disconnection=[], _dbus_Connection_initialized=1, _bus_names=, data={'org.mpris.MediaPlayer2.AutoPlayer': }) at remote 0x2d8c5a8>, _signal_sender_matches={}, _signal_recipients_by_object_path={'/org/freedesktop/DBus': {None: {'NameOwnerChanged': []}}}) at remote 0x2a247d0>, _bus_name=) at remote 0x2b1d510>, _locations=[(<...>, '/org/mpris/MediaPlayer2', False)], _uname=':1.231', _bus=<...>, player=, arg=, kw=) at /usr/src/debug/Python-2.7.3/Objects/abstract.c:2529 #24 0x0000003c6e049ba0 in PyObject_CallFunctionObjArgs (callable=) at /usr/src/debug/Python-2.7.3/Objects/abstract.c:2760 #25 0x00007fb5bce9ec0b in DBusPyConnection_HandleMessage (conn=, msg=, callable=) at conn.c:79 #26 0x00007fb5bce9f8ce in _object_path_message (conn=, message=, user_data=) at conn-methods.c:119 #27 0x00000036bce1db31 in _dbus_object_tree_dispatch_and_unlock (tree=0x29cf9c0, message=0x2e54920) at dbus-object-tree.c:858 ---Type to continue, or q to quit--- #28 0x00000036bce0faa0 in dbus_connection_dispatch (connection=0x2bc6d60) at dbus-connection.c:4685 #29 0x00000036bd60abf5 in message_queue_dispatch (source=, callback=, user_data=) at dbus-gmain.c:90 #30 0x0000003973644f3d in g_main_dispatch (context=0x2a543c0) at gmain.c:2441 #31 g_main_context_dispatch (context=0x2a543c0) at gmain.c:3011 #32 0x0000003973645738 in g_main_context_iterate (context=0x2a543c0, block=, dispatch=1, self=) at gmain.c:3089 #33 0x0000003973645c85 in g_main_loop_run (loop=0x2a04f80) at gmain.c:3297 #34 0x00007fb5bf281ed1 in _wrap_g_main_loop_run (self=0x7fb5c69138d0) at pygmainloop.c:331 #35 0x0000003c6e0dff3b in call_function (oparg=, pp_stack=0x7fffb9abd9d8) at /usr/src/debug/Python-2.7.3/Python/ceval.c:4082 #36 PyEval_EvalFrameEx (f=, throwflag=) at /usr/src/debug/Python-2.7.3/Python/ceval.c:2740 #37 0x0000003c6e0e19a5 in PyEval_EvalCodeEx (co=, globals=, locals=, args=, argcount= 2, kws=0x29e8338, kwcount=0, defs=0x29a02f0, defcount=2, closure=0x0) at /usr/src/debug/Python-2.7.3/Python/ceval.c:3330 #38 0x0000003c6e0dff03 in fast_function (nk=, na=2, n=, pp_stack=0x7fffb9abdbc8, func=) at /usr/src/debug/Python-2.7.3/Python/ceval.c:4194 #39 call_function (oparg=, pp_stack=0x7fffb9abdbc8) at /usr/src/debug/Python-2.7.3/Python/ceval.c:4119 #40 PyEval_EvalFrameEx (f=, throwflag=) at /usr/src/debug/Python-2.7.3/Python/ceval.c:2740 #41 0x0000003c6e0e075e in fast_function (nk=, na=1, n=, pp_stack=0x7fffb9abdd08, func=) at /usr/src/debug/Python-2.7.3/Python/ceval.c:4184 #42 call_function (oparg=, pp_stack=0x7fffb9abdd08) at /usr/src/debug/Python-2.7.3/Python/ceval.c:4119 #43 PyEval_EvalFrameEx (f=, throwflag=) at /usr/src/debug/Python-2.7.3/Python/ceval.c:2740 #44 0x0000003c6e0e19a5 in PyEval_EvalCodeEx (co=, globals=, locals=, args=, argcount= 0, kws=0x0, kwcount=0, defs=0x0, defcount=0, closure=0x0) at /usr/src/debug/Python-2.7.3/Python/ceval.c:3330 #45 0x0000003c6e0e1ad2 in PyEval_EvalCode (co=, globals=, locals=) at /usr/src/debug/Python-2.7.3/Python/ceval.c:689 #46 0x0000003c6e0fbd5c in run_mod (mod=, filename=, globals= {'GstMad': , __doc__=, __module__='autoradio.autoplayer.player') at remote 0x2e54eb0>, 'GstURIDecodeBin': , __doc__=, __module__='autoradio.autoplayer.player') at remote 0x2470a80>, 'GstBaseAudioSink': , __doc__=, __module__='autoradio.autoplayer.player') at remote 0x2e58150>, 'player': , 'GstAutoAudioSink': , __doc__=, __module__='autoradio.autoplayer.player') at remote 0x2c09800>, 'GstPlaySink': , __doc__=, __module__='autoradio.autoplayer.playe...(truncated), locals= {'GstMad': , __doc__=, __module__='autoradio.autoplayer.player') at remote 0x2e54eb0>, 'GstURIDecodeBin': , __doc__= to continue, or q to quit--- Object.__doc__ at remote 0x7fb5c69150b0>, __module__='autoradio.autoplayer.player') at remote 0x2470a80>, 'GstBaseAudioSink': , __doc__=, __module__='autoradio.autoplayer.player') at remote 0x2e58150>, 'player': , 'GstAutoAudioSink': , __doc__=, __module__='autoradio.autoplayer.player') at remote 0x2c09800>, 'GstPlaySink': , __doc__=, __module__='autoradio.autoplayer.playe...(truncated), flags=, arena=) at /usr/src/debug/Python-2.7.3/Python/pythonrun.c:1361 #47 0x0000003c6e0fcb60 in PyRun_FileExFlags (fp=0x21f8810, filename=0x7fffb9abf304 "./autoplayerd", start=, globals= {'GstMad': , __doc__=, __module__='autoradio.autoplayer.player') at remote 0x2e54eb0>, 'GstURIDecodeBin': , __doc__=, __module__='autoradio.autoplayer.player') at remote 0x2470a80>, 'GstBaseAudioSink': , __doc__=, __module__='autoradio.autoplayer.player') at remote 0x2e58150>, 'player': , 'GstAutoAudioSink': , __doc__=, __module__='autoradio.autoplayer.player') at remote 0x2c09800>, 'GstPlaySink': , __doc__=, __module__='autoradio.autoplayer.playe...(truncated), locals= {'GstMad': , __doc__=, __module__='autoradio.autoplayer.player') at remote 0x2e54eb0>, 'GstURIDecodeBin': , __doc__=, __module__='autoradio.autoplayer.player') at remote 0x2470a80>, 'GstBaseAudioSink': , __doc__=, __module__='autoradio.autoplayer.player') at remote 0x2e58150>, 'player': , 'GstAutoAudioSink': , __doc__=, __module__='autoradio.autoplayer.player') at remote 0x2c09800>, 'GstPlaySink': , __doc__=, __module__='autoradio.autoplayer.playe...(truncated), closeit=1, flags=0x7fffb9abe030) at /usr/src/debug/Python-2.7.3/Python/pythonrun.c:1347 #48 0x0000003c6e0fd5df in PyRun_SimpleFileExFlags (fp=0x21f8810, filename=0x7fffb9abf304 "./autoplayerd", closeit=1, flags=0x7fffb9abe030) at /usr/src/debug/Python-2.7.3/Python/pythonrun.c:951 #49 0x0000003c6e10ef15 in Py_Main (argc=, argv=) at /usr/src/debug/Python-2.7.3/Modules/main.c:639 #50 0x000000397122169d in __libc_start_main (main=0x400620
, argc=3, ubp_av=0x7fffb9abe158, init=, fini=, rtld_fini=, stack_end=0x7fffb9abe148) at libc-start.c:226 #51 0x0000000000400651 in _start () autoradio-2.8.6/autoradio.wsgi0000664000175000017500000000120413001105756016131 0ustar pat1pat100000000000000# remenber to set those in envvars in debian or /etc/sysconfig/httpd in centos #LANG='en_US.UTF-8' #LC_ALL='en_US.UTF-8' import os import autoradio.settings import autoradio.autoradio_config #os.environ['DJANGO_SETTINGS_MODULE'] = 'autoradio.settings' #import django.core.handlers.wsgi #application = django.core.handlers.wsgi.WSGIHandler() ## from django 1.4 os.environ.setdefault("DJANGO_SETTINGS_MODULE", "autoradio.settings") ## This application object is used by the development server ## as well as any WSGI server configured to use this file. from django.core.wsgi import get_wsgi_application application = get_wsgi_application() autoradio-2.8.6/PKG-INFO0000664000175000017500000000232013003471473014350 0ustar pat1pat100000000000000Metadata-Version: 1.1 Name: autoradio Version: 2.8.6 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 gstreamer 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 Requires: configobj autoradio-2.8.6/README0000664000175000017500000003311013001214555014126 0ustar pat1pat100000000000000= AutoRadio version 2.8.5 = http://autoradiobc.sourceforge.net/ OVERVIEW -------- 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 * interface: 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 FEATURES -------- * manage ogg, mp3, and other media file format managed by player * 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 REQUIRES -------- autoradio requires: python >= 2.7 mutagen muatgen version >= 1.17 http://code.google.com/p/mutagen/ django http://www.djangoproject.com/ Suggested Django >= 1.7 configobj Summary : Config file reading, writing, and validation URL : http://www.voidspace.org.uk/python/configobj.html gstreamer Autoradio can use gstreamer 0.10 but is better to use gstreamer 1.0; the installed plugins establish the usable audio formats. OLD DISTRIBUTIONS ----------------- Autoradio try to work on very old distribution like fedora 8 with xmms and cherrypy2 On not so old distribution try to use audacious version >= 1.5 and cherypy3 python >= 2.5 cherrypy Summary : A pythonic, object-oriented web development framework URL : http://www.cherrypy.org/ Thera are incompatibility from cherrypy version 2 and 3 but autoradio works well with any version :) Player (xmms or audacious in alternative): for xmms player pyxmms http://people.via.ecp.fr/~flo/index.en.xhtml xmms http://www.xmms.org/ for audacious player version >= 1.5 dbus-python D-Bus Python Bindings http://www.freedesktop.org/software/dbus/ audacious http://audacious-media-player.org/ the player web server respond on port 8888 In debian/ubuntu distribution you need those packeges: python python-setuptools, python-django, python-django-extensions, python-configobj, gettext python-reportlab, audacious, python-mutagen (>=1.17) ,python-cherrypy3 , python-dbus, python-magic In Fedora distibution you need those pckages: python-mutagen >= 1.17 , Django, python-configobj, python-cherrypy, python-reportlab, python-docutils, python-magic and pyxmms, xmms or dbus-python, audacious >= 1.5 HOW TO INSTALL -------------- >>>>> Easy way: You can run autoradio daemon and web server from your root software distributed directory: python setup.py build change your preferred language and other preference in autoradio.cfg ./autoradioctrl --syncdb You have to answer to some question to setup database. ./autoradioweb run This start autoradio webserver on localhost port 8080 control+c to stop it if all works well you can detach it with ./autoradioweb restart ./autoplayerd run This start the player daemon and you can listen it if you have a sound card and loaded audio files control+c to stop it if all works well you can detach it with ./autoplayerd restart ./autoradiod run This start daemon autoradiod (that in old distribution can launch xmms/audacious ) control+c to stop it if all works well you can detach it with ./autoradiod restart ./autoplayergui This start the player GUI; with it you can load audio file/playlists and manage audio player You have to use a browser (on the same machine) pointing to http://localhost:8080 >>>>> Installed way: you need access to root administrator user and after: python setup.py install choose a normal user to run the daemons and create it and login, make and go in your preferred user working writable directory modify /etc/autoradio/autoradio-site.cfg or from the normal user copy it in your working directory with name autoradio.cfg and modify it specify your personal settings for installed files if you want you can set user's global settings coping configuration file in ~/.autoradio.cfg after from root: autoradioctrl --syncdb --changeuser autoradioweb restart You can run autoradiod and autoplayerd in one host and autoradioweb in other if you use server Data Base like mysql and specify it and where autoradiod is running in the autoradio configuration (.cfg) files. The /usr/share/autoradio of the machine where run autoradioweb will be accessible read (and write) from machine where run autoradiod. In addition to do this you have to have a tcp enabled version of dbus running autoradiodbusd somewhere. On machine where you want run autoradiod (the player side), after autoradio installation, from root user create a new user and set password and activate interactive login like this: useradd autoradio passwd autoradio usermod -s /bin/bash autoradio login in autoradio user in a X (graphics) session and: autoradiod run or autoradiod restart For a pubblic web server do not use django internal web server: autoradioweb stop but use apache instead: https://docs.djangoproject.com/en/dev/howto/deployment/wsgi/ you can find an example configuration file in doc directory: doc/apache_modwsgi_example.conf set SERVE_STATIC=False in /etc/autoradio-site.cfg >>>> Pachaged way: for Fedora Centos and Debian/Ubuntu you have the possibility to install from pachages in a easy way. The pachage create the autoradio user for you and set everything in a standard way for an easy use. to start everythings from root user (prepend sudo command for Ubuntu): autoradioctrl --syncdb --changeuser autoradioweb restart You can run autoradiod and autoplayerd in one host and autoradioweb in other if you use server Data Base like mysql and specify it and where autoradiod is running in the autoradio configuration (.cfg) files. The /usr/share/autoradio of the machine where run autoradioweb will be accessible read (and write) from machine where run autoradiod. On machine where you want run autoplayerd (the player side), after autoradio installation, from root user create a new user and if you want activate interactive login like this: useradd autoradio passwd autoradio usermod -s /bin/bash autoradio login in autoradio user in a X (graphics) session and: autoradiod run or autoradiod restart If you want you can activate monit daemon to control autoradio daemons; an example conf file to add to monit is in: doc/monit_autoradio_example.conf For a pubblic web server do not use django internal web server: autoradioweb stop and use apache instead: http://docs.djangoproject.com/en/dev/howto/deployment/modpython/#howto-deployment-modpython you can find an example configuration file in doc directory: doc/apache_mod_python_example.conf set SERVE_STATIC=False in /etc/autoradio-site.cfg HOW IT WORKS ------------ In player's playlist you need a queue of media for a minumun of some hours and for this you have to program some playlist. Player cannot stay stopped or paused (if stopped it will be started, if paused it stay paused) for a corret work. When time will be right jingle, programs and spots will be placed in playlist the first position after the last file inserted before by autoradiod HOT TO USE IT ------------- autoradioctrl provide some administration commands like --sincdb to inizialite the data base. autoradioweb or other web serber like apache provide a web interface to program every thinks you cannot find in configuration files; you have to run it like a permanent daemon; use a browser pointing it at the machine and port of the web server (http://localhost:8080 is the default for autoradioweb). Run autoplayerd to start to play music. You need an audio card. You can run autoradiod to start the automation of the player; autoradiod manage the player with the programmed schedules. You can run autoplayerd/autoradiod from the same machine where run auroradioweb; it use sqlite local file. If you use a database client/server like mysql you can access the DB from an other machine but you have to read the media files from all machines involved (with nfs services). Where you have an X server you can run autoplayergui to interact with graphical interface with the player. Read doc/user_guide.txt or the documentation in the web admin interface for the features enabled in the autoradio suite. CONTRIBUTED SOFTWARE -------------------- module daemon come from http://www.livinglogic.de/Python/index.html ## Copyright 2007-2009 by LivingLogic AG, Bayreuth/Germany. ## Copyright 2007-2009 by Walter Drwald ## OSI Approved :: MIT License module mkplaylist come from http://bj.spline.de/mkplaylist-man.html # Author: Marc 'BlackJack' Rintsch # Copyright: (c) 2004-2009 # Licence: GPL module dir2ogg come from http://jak-linux.org/projects/dir2ogg/ # Copyright (C) 2007-2009 Julian Andres Klode # Copyright (C) 2003-2006 Darren Kirby # Licence: GPL django-podcast http://code.google.com/p/django-podcast/ https://github.com/jefftriplett/django-podcast # Copyright (c) 2008, django-podcast Project Members # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # * Neither the name of the django-podcast Project nor the names of its # contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED # OF THE POSSIBILITY OF SUCH DAMAGE. SWFObject v2.2 is released under the MIT License jQuery JavaScript Library v1.4.1 http://jquery.com/ Copyright 2010, John Resig Dual licensed under the MIT or GPL Version 2 licenses. http://jquery.org/license Includes Sizzle.js http://sizzlejs.com/ Copyright 2010, The Dojo Foundation Released under the MIT, BSD, and GPL Licenses. jquery Open Window plugin http://plugins.jquery.com/project/open GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Cortado - a video player java applet Copyright (C) 2004 Fluendo S.L. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. anoggplayer http://code.google.com/p/anoggplayer/ GNU LESSER GENERAL PUBLIC LICENSE fogg http://bazaar.launchpad.net/~arkadini/fogg/trunk GNU LESSER GENERAL PUBLIC LICENSE ---------------------------------------------------------------------------- Italiano: Autoradio una suite di programmi che partendo da file audio digitali permette la gestione automatica dell'emissione di una stazione radiofonica. queste sono le componenti: * Player: 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 * Scheduler: gestisce in tempo reale l'emissione di particolari file o audio quali i jingles, pubblicit, playlist e programmi; interagisce col player controllandolo e impartendo comandi * L'intefaccia utente: utilizzando una interfaccia WEB pemette il monitoraggio dello scheduler e del player e permette la programmazione del palinsesto. L'interfaccia web permette anche di pubblicare facilmente podcasts conforme alle specifiche RSS 2.0 e iTunes RSS. autoradio-2.8.6/NEWS0000664000175000017500000000456013001105756013756 0ustar pat1pat100000000000000NEWS ---- version 2.8.2 ------------- * ported to django 1.6 version 2.8.1 ------------- * ported to django 1.5, gstreamer1, haxe 3.0 version 2.7.0 ------------- * now autoradio have an internal player based on python, dbus and gstreamer * autoradioweb now use dbus for the integrated player and autoradiod do not start thread for cherrypy web server version 2.6.1 ------------- * (bug corrected) firefox chrome etc. when upload files say different mime type * logo.gif file path was wrong in template version 2.6 ----------- * lot of bugs corrected * new config parameter to check presence of tags in file uploaded * now mysql with innodb (transaction) works well version 2.5 ----------- working version version 2.4 ----------- * file uplodated now are validated: new config parameter permit_no_playable_files (default = False): enclosure can be ogg 44100Hz only for best web player functionality; in all other cases files can be audio file mp3/ogg/flac version 2.3 ----------- * autoradiod now take in account audacious and audacious2 executables * changed SITE_MEDIA_PREFIX to MEDIA_SITE_PREFIX in config files * close bug on wrong path for static media * avoid to save enclosure without title, naming enclosure with auto part number version 2.2 ----------- * close bug on web player ( multiple windows on multiple enclosure) * close bug on web player ( do not play for media path error ) * close bug that don not show multiple enclosure for rss and atom feed * better site configuration * revisited look and feel of web ogg player (THANKS to Francesco Siviero !) version 2.1 ----------- * close bug autoradiod crash on playlist management - ID: 3388949 * a lot of new documentation * userguide in italian * player in podcast now is open in new windows using jquery version 2.0 ----------- The section program redesigned for podcasting: * a program now can have different part * programs now are available for podcasting * web interface for podcasting A palimpsest in pdf format available in italian law standard format Integrated ogg web player that is compatible with a great number of user's browser You can use audaucious2 for player The communication with player now use Dbus Compatible with cherrypython3 and Django 1.3 Tested and packaged for Fedora 15, Ubuntu 11.04, ubuntu server 11.04, debian 6.0.2 and testing. On line web documentation autoradio-2.8.6/centos/0000775000175000017500000000000013003471473014551 5ustar pat1pat100000000000000autoradio-2.8.6/centos/autoradio.spec0000644000175000017500000001551613003444011017406 0ustar pat1pat100000000000000%{!?python_sitelib: %define python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print get_python_lib()" )} %{!?python_sitearch: %define python_sitearch %(%{__python} -c "from distutils.sysconfig import get_python_lib; print get_python_lib( 1)")} %define name autoradio %define version 2.8.6 %define release 1%{?dist} Summary: radio automation software Name: %{name} Version: %{version} Release: %{release} Source0: %{name}-%{version}.tar.gz # tmpfiles.d configuration for the /var/run directory Source1: %{name}-tmpfiles.conf License: GNU GPL v2 Group: Development/Libraries BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot Prefix: %{_prefix} BuildArch: noarch Vendor: Paolo Patruno Url: http://autoradiobc.sf.net BuildRequires: python-configobj , python-django >= 1.4.0 , help2man, python-setuptools Requires:python-mutagen >= 1.17 , python-django >= 1.4.0, python-configobj, python-cherrypy, python-reportlab >= 2.0, python-docutils, sqlite >= 3.6.22, speex-tools, python-magic, python-pillow, python-six #, python-django-extensions Requires: initscripts #%if 0%{?fedora} < 10 #Requires: pyxmms, xmms #%else ## Requires: dbus-python, audacious >= 1.5 Requires: dbus-python, gstreamer, gstreamer-plugins-base, gstreamer-plugins-good, gstreamer-python #, gstreamer-plugins-bad, gstreamer-plugins-bad-free, gstreamer-plugins-bad-free-extras #%endif # Compile options: # --with cherrypy : do not need cherrypy2 ##%if 0%{?fedora} < 10 ##%if 0%{?_with_} ##Requires: python-cherrypy ##%else ##Requires: python-cherrypy2 ##%endif %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 (gstreamer) 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 The web interface provide a "full compatible" ogg player. Developed with Python, Django, Dbus it works in an production enviroment %prep %setup -n %{name}-%{version} -n %{name}-%{version} %build %{__python} setup.py build %install %{__python} setup.py install --single-version-externally-managed --root=$RPM_BUILD_ROOT ##%{__install} -d -m 0710 %{buildroot}%{_var}/{run/autoradio,log/autoradio} mkdir -p %{buildroot}%{_localstatedir}/run/ mkdir -p %{buildroot}%{_localstatedir}/log/ %{__install} -d -m 0710 %{buildroot}%{_localstatedir}/{run/autoradio,log/autoradio} mkdir -p %{buildroot}%{_sysconfdir}/tmpfiles.d %{__install} -m 0644 %{SOURCE1} %{buildroot}%{_sysconfdir}/tmpfiles.d/%{name}.conf %clean rm -rf $RPM_BUILD_ROOT %files %defattr(-,root,root) %doc COPYING README doc/* %config(noreplace) %{_sysconfdir}/%{name}/autoradio-site.cfg %config(noreplace) %{_sysconfdir}/%{name}/dbus-autoradio.conf %dir %{python_sitelib}/%{name} %{python_sitelib}/%{name}/* %{python_sitelib}/%{name}-* %{_mandir}/man1/* %config(noreplace) %{_sysconfdir}/tmpfiles.d/%{name}.conf #%{_datadir}/autoradio/* %{_bindir}/autoradiod %{_bindir}/autoradioweb %{_bindir}/autoradioctrl %{_bindir}/autoplayerd %{_bindir}/autoplayergui %{_bindir}/autoradiodbusd %{_bindir}/jackdaemon %attr(-,autoradio,autoradio) %dir %{_datadir}/autoradio %attr(-,autoradio,autoradio) %{_datadir}/%{name}/* %attr(-,autoradio,autoradio) %dir %{_var}/log/%{name}/ %attr(-,autoradio,autoradio) %dir %{_var}/run/%{name}/ %pre /usr/bin/getent group autoradio >/dev/null || /usr/sbin/groupadd autoradio /usr/bin/getent passwd autoradio >/dev/null || \ /usr/sbin/useradd -g autoradio -d %{_datadir}/autoradio -M \ -c "autoradio user for radio automation software" autoradio #/usr/bin/getent group autoradio >/dev/null || /usr/sbin/groupadd -r autoradio #/usr/bin/getent passwd autoradio >/dev/null || \ # /usr/sbin/useradd -r -s /sbin/nologin -d %{_datadir}/autoradio -g autoradio \ # -c "autoradio user for radio automation software" autoradio ## Fix homedir for upgrades #/usr/sbin/usermod --home %{_datadir}/autoradio autoradio &>/dev/null ##exit 0 #%post # ## set some useful variables #AUTORADIO="autoradio" #CHOWN="/bin/chown" #ADDUSER="/usr/sbin/adduser" #USERDEL="/usr/sbin/userdel" #USERADD="/usr/sbin/useradd" #GROUPDEL="/usr/sbin/groupdel" #GROUPMOD="/usr/sbin/groupmod" #ID="/usr/bin/id" # #set -e # #### ## 1. get current autoradio uid and gid if user exists. #if $ID $AUTORADIO > /dev/null 2>&1; then # IUID=`$ID --user $AUTORADIO` # IGID=`$ID --group $AUTORADIO` #else # IUID="NONE" # IGID="NONE" #fi # ##### ### 2. Ensure that no standard account or group will remain before adding the ### new user ##if [ "$IUID" = "NONE" ] || [ $IUID -ge 1000 ]; then # we must do sth :) ## if ! [ "$IUID" = "NONE" ] && [ $IUID -ge 1000 ]; then ## # autoradio user exists but isn't a system user... delete it. ## $USERDEL $PEERCAST ## $GROUPDEL $PEERCAST ## fi ## ##### # ## 3. Add the system account. ## Issue a debconf warning if it fails. # if $GROUPMOD $AUTORADIO > /dev/null 2>&1; then # # peercast group already exists, use --ingroup # if ! $ADDUSER --system --disabled-password --disabled-login --home /usr/share/autoradio --no-create-home --ingroup $AUTORADIO $AUTORADIO; then # echo "The adduser command failed." # fi # else # if ! $ADDUSER --system --disabled-password --disabled-login --home /usr/share/peercast --no-create-home --group $AUTORADIO; then # echo "The adduser command failed." # fi # fi #fi #set +e # #### ## 4. change ownership of directory #$CHOWN -R $AUTORADIO:$AUTORADIO /usr/share/autoradio/ #$CHOWN -R $AUTORADIO:$AUTORADIO /var/log/autoradio/ #$CHOWN -R $AUTORADIO:$AUTORADIO /etc/autoradio/ #$CHOWN -R $AUTORADIO:$AUTORADIO /var/run/autoradio/ %changelog * Sat Aug 10 2013 Paolo Patruno - 2.8.0-1%{?dist} - bumped to version 2.8 * Mon Feb 18 2013 Paolo Patruno - 2.7.0-1%{?dist} - autoradio 2.7 with pygst * Sat Apr 14 2012 Paolo Patruno - 2.3-2%{?dist} - tmpfiles.d is a service provided by both systemd and upstart in Fedora 15 and later for managing temporary files and directories for daemons https://fedoraproject.org/wiki/Packaging:Tmpfiles.d * Sat Apr 14 2012 Paolo Patruno - 2.3-1%{?dist} - updated to 2.3 * Fri Aug 12 2011 Paolo Patruno - 2.1beta-1%{?dist} - upstream version 2.1beta autoradio-2.8.6/manage.py0000775000175000017500000000036713001105756015065 0ustar pat1pat100000000000000#!/usr/bin/env python import os, sys if __name__ == "__main__": os.environ.setdefault("DJANGO_SETTINGS_MODULE", "autoradio.settings") from django.core.management import execute_from_command_line execute_from_command_line(sys.argv) autoradio-2.8.6/autoplayerd0000775000175000017500000000570413001105756015537 0ustar pat1pat100000000000000#!/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 formatter=logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s",datefmt="%Y-%m-%d %H:%M:%S") handler = logging.handlers.RotatingFileHandler(autoradio.settings.logfileplayer, maxBytes=5000000, backupCount=10) handler.setFormatter(formatter) # Add the log message handler to the root logger logging.getLogger().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-2.8.6/setup.py0000664000175000017500000003405313001506132014762 0ustar pat1pat100000000000000from distutils.core import setup import os from distutils.command.build import build as build_ from setuptools.command.develop import develop as develop_ from distutils.core import Command #from buildutils.cmd import Command #from distutils.cmd import Command from django.core import management from autoradio import _version_ os.environ['DJANGO_SETTINGS_MODULE'] = 'autoradio.settings' from django.conf import settings class distclean(Command): description = "remove man pages and *.mo files" user_options = [] boolean_options = [] def initialize_options(self): pass def finalize_options(self): pass def run(self): import shutil from os.path import join try: shutil.rmtree("man") except: pass for root, dirs, files in os.walk('locale'): for name in files: if name[-3:] == ".mo": os.remove(join(root, name)) # remove all the .pyc files for root, dirs, files in os.walk(os.getcwd(), topdown=False): for name in files: if name.endswith('.pyc') and os.path.isfile(os.path.join(root, name)): print 'removing: %s' % os.path.join(root, name) if not(self.dry_run): os.remove(os.path.join(root, name)) try: os.remove("autoradio/programs/static/programs/playogg/js/jquery-1.12.4.min.js") except: print "autoradio/programs/static/programs/playogg/js/jquery-1.12.4.min.js not removed" try: os.remove("autoradio/programs/static/programs/playogg/js/jquery.min.js") except: print "autoradio/programs/static/programs/playogg/js/jquery.min.js not removed" try: os.remove("autoradio/programs/static/programs/playogg/flash/AnOgg.swf") except: print "autoradio/programs/static/programs/playogg/flash/AnOgg.swf not removed" try: os.remove("anoggplayer/anoggplayer/AnOgg.swf") except: print "anoggplayer/anoggplayer/AnOgg.swf not removed" try: os.remove("autoradio/programs/static/programs/playogg/java/cortado.jar") except: print "autoradio/programs/static/programs/playogg/java/cortado.jar not removed" try: os.remove("autoradio/programs/static/programs/playogg/java/cortado-ovt-stripped-0.6.0.jar") except: print "autoradio/programs/static/programs/playogg/java/cortado-ovt-stripped-0.6.0.jar not removed" try: os.remove("autoradio/programs/static/programs/playogg/swfobject/expressInstall.swf") except: print "autoradio/programs/static/programs/playogg/swfobject/expressInstall.swf not removed" try: os.remove("autoradio/programs/static/programs/playogg/swfobject/swfobject.js") except: print "autoradio/programs/static/programs/playogg/swfobject/swfobject.js not removed" class build(build_): sub_commands = build_.sub_commands[:] sub_commands.append(('compilemessages', None)) sub_commands.append(('createmanpages', None)) class buildall(build_): description = "compile and install binary" user_options = [] boolean_options = [] sub_commands = Command.sub_commands[:] sub_commands.append(('haxecompileanoggplayer', None)) sub_commands.append(('installbin', None)) class compilemessages(Command): description = "generate .mo files from .po" user_options = [] boolean_options = [] def initialize_options(self): pass def finalize_options(self): pass def run(self): management.call_command("compilemessages") class createmanpages(Command): description = "generate man page with help2man" user_options = [] boolean_options = [] def initialize_options(self): pass def finalize_options(self): pass def run(self): try: import subprocess subprocess.check_call(["mkdir","-p", "man/man1"]) subprocess.check_call(["help2man","-n","autoradiod daemon for autoradio suite","-N","-o","man/man1/autoradiod.1","./autoradiod"]) subprocess.check_call(["gzip","-f", "man/man1/autoradiod.1"]) subprocess.check_call(["help2man","-n","autoradioweb daemon for autoradio suite","-N","-o","man/man1/autoradioweb.1","./autoradioweb"]) subprocess.check_call(["gzip", "-f","man/man1/autoradioweb.1"]) subprocess.check_call(["help2man","-n","autoradio controller tool","-N","-o","man/man1/autoradioctrl.1","./autoradioctrl"]) subprocess.check_call(["gzip", "-f","man/man1/autoradioctrl.1"]) subprocess.check_call(["help2man","-n","autoradio dbus daemon","-N","-o","man/man1/autoradiodbusd.1","./autoradiodbusd"]) subprocess.check_call(["gzip", "-f","man/man1/autoradiodbusd.1"]) subprocess.check_call(["help2man","-n","autoradio jack daemon","-N","-o","man/man1/jackdaemon.1","./jackdaemon"]) subprocess.check_call(["gzip", "-f","man/man1/jackdaemon.1"]) subprocess.check_call(["help2man","-n","autoradio player daemon","-N","-o","man/man1/autoplayerd.1","./autoplayerd"]) subprocess.check_call(["gzip", "-f","man/man1/autoplayerd.1"]) subprocess.check_call(["help2man","-n","autoradio player GUI","-N","-o","man/man1/autoplayergui.1","./autoplayergui"]) subprocess.check_call(["gzip", "-f","man/man1/autoplayergui.1"]) except: pass class haxecompileanoggplayer(Command): description = "generate anoggplayer executable with haxe" user_options = [] boolean_options = [] def initialize_options(self): pass def finalize_options(self): pass def run(self): try: import subprocess import os os.chdir("anoggplayer/anoggplayer") subprocess.check_call(["make"]) os.chdir("../..") except: print "WARNING !!!!! anoggplayer not created" class installbin(Command): description = "install flash and java binary for full distribution not debian compliant" user_options = [] boolean_options = [] def initialize_options(self): pass def finalize_options(self): pass def run(self): import os #if (not os.path.exists("../../autoradio/programs/static/programs/playogg/flash")): # os.mkdir("../../autoradio/programs/static/programs/playogg/flash") os.link("jquery/jquery-1.12.4.min.js","autoradio/programs/static/programs/playogg/js/jquery-1.12.4.min.js") os.link("jquery/jquery.min.js","autoradio/programs/static/programs/playogg/js/jquery.min.js") os.link("anoggplayer/anoggplayer/AnOgg.swf","autoradio/programs/static/programs/playogg/flash/AnOgg.swf") os.link("cortado/cortado.jar","autoradio/programs/static/programs/playogg/java/cortado.jar") os.link("cortado/cortado-ovt-stripped-0.6.0.jar","autoradio/programs/static/programs/playogg/java/cortado-ovt-stripped-0.6.0.jar") os.link("expressinstall/expressInstall.swf", "autoradio/programs/static/programs/playogg/swfobject/expressInstall.swf") os.link("expressinstall/swfobject.js", "autoradio/programs/static/programs/playogg/swfobject/swfobject.js") # Compile the list of files available, because distutils doesn't have # an easy way to do this. package_data = [] data_files = [] for dirpath, dirnames, filenames in os.walk('man'): # Ignore dirnames that start with '.' for i, dirname in enumerate(dirnames): if dirname.startswith('.'): del dirnames[i] if filenames: data_files.append(['share/'+dirpath, [os.path.join(dirpath, f) for f in filenames]]) for dirpath, dirnames, filenames in os.walk('doc'): # Ignore dirnames that start with '.' for i, dirname in enumerate(dirnames): if dirname.startswith('.'): del dirnames[i] if filenames: data_files.append(['share/autoradio/'+dirpath, [os.path.join(dirpath, f) for f in filenames]]) #for dirpath, dirnames, filenames in os.walk('amarok'): # # Ignore dirnames that start with '.' # for i, dirname in enumerate(dirnames): # if dirname.startswith('.'): del dirnames[i] # if filenames: # data_files.append(['share/autoradio/'+dirpath, [os.path.join(dirpath, f) for f in filenames]]) for dirpath, dirnames, filenames in os.walk('locale'): # Ignore dirnames that start with '.' for i, dirname in enumerate(dirnames): if dirname.startswith('.'): del dirnames[i] if filenames: data_files.append(['share/autoradio/'+dirpath, [os.path.join(dirpath, f) for f in filenames]]) for dirpath, dirnames, filenames in os.walk('templates'): # Ignore dirnames that start with '.' for i, dirname in enumerate(dirnames): if dirname.startswith('.'): del dirnames[i] if filenames: data_files.append(['share/autoradio/'+dirpath, [os.path.join(dirpath, f) for f in filenames]]) data_files.append(['share/autoradio/server/',['autoradio.wsgi']]) for dirpath, dirnames, filenames in os.walk('static'): # Ignore dirnames that start with '.' for i, dirname in enumerate(dirnames): if dirname.startswith('.'): del dirnames[i] if filenames: data_files.append(['share/autoradio/'+dirpath, [os.path.join(dirpath, f) for f in filenames]]) data_files.append(('share/autoradio/media',[])) data_files.append(('share/autoradio/static',[])) data_files.append(('/etc/autoradio',['autoradio-site.cfg'])) data_files.append(('/etc/autoradio',['dbus-autoradio.conf'])) #for dirpath, dirnames, filenames in os.walk('autoradio/templates'): # # Ignore dirnames that start with '.' # for i, dirname in enumerate(dirnames): # if dirname.startswith('.'): del dirnames[i] # if filenames: # for file in filenames: # package_data.append('templates/'+ os.path.join(dirname, file)) # #for dirpath, dirnames, filenames in os.walk('autoradio/locale'): # # Ignore dirnames that start with '.' # for i, dirname in enumerate(dirnames): # if dirname.startswith('.'): del dirnames[i] # if filenames: # for file in filenames: # package_data.append('locale/'+ os.path.join(dirname, file)) #package_data.append('autoradio_config') #package_data.append('settings') setup(name='autoradio', version=_version_, description='radio automation software', author='Paolo Patruno', author_email='p.patruno@iperbole.bologna.it', platforms = ["any"], url='http://autoradiobc.sf.net', cmdclass={'build': build,'compilemessages':compilemessages,'createmanpages':createmanpages,"distclean":distclean,"haxecompileanoggplayer":haxecompileanoggplayer,"installbin":installbin,"buildall":buildall}, packages=['autoradio', 'autoradio.playlists','autoradio.spots', 'autoradio.jingles', 'autoradio.programs', 'autoradio.playlists.migrations','autoradio.spots.migrations', 'autoradio.jingles.migrations', 'autoradio.programs.migrations', 'autoradio.player', 'autoradio.doc', 'autoradio.autoplayer', 'autoradio.mpris2', 'autoradio.pydbusdecorator',], package_data={ 'autoradio.doc': ['templates/doc/*'], 'autoradio.programs': ['fixtures/*.json', 'static/programs/*.png', 'static/programs/css/*', 'static/programs/css/*', 'static/programs/playogg/*.png', 'static/programs/playogg/flash/*', 'static/programs/playogg/java/*', 'static/programs/playogg/js/*', 'static/programs/playogg/swfobject/*', 'templates/*.html', 'templates/palimpsest/*.html', 'templates/player/*.html', 'templates/podcast/*.html', 'templates/schedule/*.html', 'templates/xmms/*.html' ] }, scripts=['autoradiod','autoradioweb','autoradioctrl', 'autoplayerd','autoplayergui','autoradiodbusd','jackdaemon'], data_files = data_files, license = "GNU GPL v2", requires= [ "mutagen","django","reportlab","configobj"], long_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 gstreamer 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 """ ) #package_data = {'autoradio': package_data}, #py_modules = [ 'autoradio_config', 'settings'], # #package_data = {'autoradio': ['templates/base_incasinato.html']}, #ackage_data={'autoradio' : ['templates']}, #packages=['autoradio.mutagen', 'autoradio.programs','autoradio.jingles','autoradio.spots','autoradio.playlists'], #py_modules = [ 'autoradio.dir2ogg', 'autoradio.mkplaylist', 'autoradio.xmmsweb', 'autoradio_config', 'autoradio.gest_playlist',\ # 'autoradio.gest_spot', 'autoradio.manageamarok', 'autoradio.managepytone',\ # 'setup', 'autoradio.autoradio_core',\ # 'autoradio.autoxmms', 'autoradio.gest_jingle', 'autoradio.gest_program',\ # 'autoradio.managexmms', 'settings', 'autoradio.urls'], autoradio-2.8.6/MANIFEST.in0000664000175000017500000000062713003471466015023 0ustar pat1pat100000000000000include COPYING include NEWS include README include TODO include *.txt include autoradio.wsgi global-include *.txt *.py *.sh *.in *.cfg *.json *.conf recursive-include doc * recursive-include locale *.po recursive-include templates * recursive-include global_static * recursive-include centos * recursive-include fedora * prune amarok prune media prune debian prune autoradio.egg-info global-exclude *~ autoradio-2.8.6/jackdaemon0000775000175000017500000000340613001105756015277 0ustar pat1pat100000000000000#!/usr/bin/python # GPL. (C) 2007-2009 Paolo Patruno. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # from autoradio import daemon import autoradio.settings jackd = daemon.Daemon( stdin="/dev/null", stdout=autoradio.settings.logfilejack, stderr=autoradio.settings.errfilejack, pidfile=autoradio.settings.lockfilejack, user=autoradio.settings.userjack, group=autoradio.settings.groupjack, env={"DBUS_SESSION_BUS_ADDRESS":autoradio.settings.busaddressplayer} ) def main(self): import subprocess self.procs=[subprocess.Popen(["/usr/bin/jackd", "-R", "-dalsa", "-dhw:0", "-r44100", "-p1024", "-n4", "-i2", "-o2", "-H", "-M", "-I300", "-O300"],cwd=self.cwd)] if __name__ == '__main__': import sys, os jackd.cwd=os.getcwd() if jackd.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(jackd) # (this code was run as script) for proc in jackd.procs: proc.wait() sys.exit(0) autoradio-2.8.6/autoradio.cfg0000664000175000017500000001205013001105756015720 0ustar pat1pat100000000000000[autoradiod] #player to use (AutoPlayer, xmms, audacious) player="AutoPlayer" #directory where write new playlists generated by autoradiod playlistdir="spots" # path to working file logfile="/tmp/autoradiod.log" errfile = '/tmp/autoradiod.err' lockfile = "/tmp/autoradiod.lock" timestampfile = "/tmp/autoradiod.timestamp" # host xmms is running on xmms_host="localhost" #backward and forward time intervat to check for schedule conflict minelab=180 # tollerance time interval to recovery schedule not done ( backward time when start autoradiod ) # to adjust the programming you have to make changes minsched minutes before minsched=5 locale="it_IT.UTF-8" #user = pat1 #group = pat1 # # [[ env ]] # DISPLAY=':0.0' # LANG=$locale [autoradioweb] logfile = '/tmp/autoradioweb.log' errfile = '/tmp/autoradioweb.err' lockfile = '/tmp/autoradioweb.lock' #user = myuser #group = mygroup #port = '8888' # the web player can only manage ogg vorbis files at 44100Hz: enable or disable check on uploads permit_no_playable_files = False # tags help in the web view: enable or disable check on uploads # if file do not have tags will be rejected # this enable or disable check for 44100Hz sample rate compatibility for enclosure require_tags_in_enclosure = True [django] DEBUG = True TEMPLATE_DEBUG = True FILE_UPLOAD_PERMISSIONS = 420 # Make this unique, and don't share it with anybody. SECRET_KEY = random-string-of-ascii #SESSION_COOKIE_DOMAIN = autoradio # Local time zone for this installation. Choices can be found here: # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name # although not all choices may be available on all operating systems. # If running in a Windows environment this must be set to the same as your # system time zone. TIME_ZONE = 'Europe/Rome' # Language code for this installation. All choices can be found here: # http://www.i18nguy.com/unicode/language-identifiers.html LANGUAGE_CODE = 'en-us' #LANGUAGE_CODE = 'it-it' SITE_ID = 1 # If you set this to False, Django will make some optimizations so as not # to load the internationalization machinery. USE_I18N = True # directories where Django looks for translation files. LOCALE_PATHS='locale', ADMINS=Your Name your_email@domain.com, MANAGERS=Your Name your_email@domain.com, # Absolute filesystem path to the directory that will hold user-uploaded files. # set to "%s" special value to insert current directory MEDIA_ROOT ="%s/media/" #URL that handles the media served from MEDIA_ROOT, used for managing #stored files. It must end in a slash if set to a non-empty value. You #will need to configure these files to be served in both development #and production environments. MEDIA_URL="/media/" # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". # Always use forward slashes, even on Windows. # Don't forget to use absolute paths, not relative paths. TEMPLATE_DIRS = "%s/templates", #The absolute path to the directory where collectstatic will collect static files for deployment STATIC_ROOT = "%s/static" #URL to use when referring to static files located in STATIC_ROOT. STATIC_URL = '/static/' # set to true if django have to serve static file # set to false if you use other web server like apache SERVE_STATIC=True # The URL where requests are redirected for login, especially when # using the login_required() decorator. LOGIN_URL='/login/' [database] DATABASE_ENGINE = 'sqlite3' # 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'ado_mssql'. DATABASE_NAME = 'autoradio.sqlite3' # Or path to database file if using sqlite3. #DATABASE_ENGINE = 'mysql' # 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'ado_mssql'. #DATABASE_NAME = 'autoradio' # Or path to database file if using sqlite3. #DATABASE_USER = 'autoradio' # Not used with sqlite3. #DATABASE_PASSWORD = 'autoradio' # Not used with sqlite3. #DATABASE_HOST = 'autoradio' # Set to empty string for localhost. Not used with sqlite3. #DATABASE_PORT = '' # Set to empty string for default. Not used with sqlite3. [autoplayer] #user = myuser #group = mygroup logfile = '/tmp/autoplayer.log' errfile = '/tmp/autoplayer.err' lockfile = '/tmp/autoplayer.lock' # Add an address that the dbus-daemon should listen on. The address is in the # standard D-Bus format that contains a transport name plus possible # parameters/options. # Example: unix:path=/tmp/foo # Example: tcp:host=localhost,port=1234 # http://stackoverflow.com/questions/10158684/connecting-to-dbus-over-tcp #busaddress='tcp:host=localhost,port=1234' #audiosink = autoaudiosink # set to autoaudiosink / jackaudiosink [autoradiodbus] #user = myuser #group = mygroup logfile = '/tmp/autoradiodbus.log' errfile = '/tmp/autoradiodbus.err' lockfile = '/tmp/autoradiodbus.lock' conffile = 'dbus-autoradio.conf' [jackdaemon] user = autoradio group = audio logfile = '/tmp/jackdaemon.log' errfile = '/tmp/jackdaemon.err' lockfile = '/tmp/jackdaemon.lock' autoradio-2.8.6/autoradioctrl0000775000175000017500000000366113001105756016062 0ustar pat1pat100000000000000#!/usr/bin/env python # GPL. (C) 2007-2009 Paolo Patruno. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # import os os.environ['DJANGO_SETTINGS_MODULE'] = 'autoradio.settings' from django.conf import settings from django.core import management import autoradio.settings import autoradio.autoradio_config import sys, optparse, autoradio.daemon from autoradio import _version_ def initdb(cwd): p = optparse.OptionParser(usage="usage: %prog [options]",version="%prog "+_version_) p.add_option("--syncdb", action="store_true",dest="syncdb", help="initialize autoradio DB (default %default)", default=False) p.add_option("--changeuser", action="store_true",dest="changeuser", help="change user to the user in config file (default %default)", default=False) (options, args) = p.parse_args() import django django.setup() if (not options.syncdb): p.print_help() if (options.changeuser): dae=autoradio.daemon.Daemon() dae.switchuser(user=autoradio.autoradio_config.user,group=autoradio.autoradio_config.group,env=None) #os.chdir(cwd) if (options.syncdb): management.call_command("migrate",no_initial_data=True ) if __name__ == '__main__': cwd=os.getcwd() sys.exit(initdb(cwd)) # (this code was run as script) autoradio-2.8.6/autoradioweb0000775000175000017500000000675113001105756015676 0ustar pat1pat100000000000000#!/usr/bin/env python # GPL. (C) 2007-2009 Paolo Patruno. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # ToDo: # controllare gli inserimenti al livello di django ADMIN INTERFACE # controllare altri conflitti in districa oltre ai jingles # utilizzare mp3splt per spezzare i programmi per fare gli inserimenti pubblicitari # alternare meglio i jingle tenendo in considerazione la priorita # a cavallo della mezzanotte verificare il funzionamento import os os.environ['DJANGO_SETTINGS_MODULE'] = 'autoradio.settings' from django.conf import settings from autoradio import daemon from autoradio import _version_ from django.core import management import autoradio.autoradio_config import autoradio.settings autoradioweb = daemon.Daemon( stdin="/dev/null", stdout=autoradio.settings.logfileweb, stderr=autoradio.settings.errfileweb, pidfile=autoradio.settings.lockfileweb, user=autoradio.settings.userweb, group=autoradio.settings.groupweb ) #class mydaemon(daemon): # # def optionparser(self): # op = super(miodaemon, self).optionparser() # op.add_option("-s", "--syncdb",action="store_false") # return op def main(): import os,logging,logging.handlers formatter=logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s",datefmt="%Y-%m-%d %H:%M:%S") handler = logging.handlers.RotatingFileHandler(autoradio.settings.logfileweb, maxBytes=5000000, backupCount=10) handler.setFormatter(formatter) # Add the log message handler to the root logger logging.getLogger().addHandler(handler) logging.getLogger().setLevel(logging.INFO) logging.info('Starting up autoradioweb version '+_version_) import django django.setup() management.call_command("runserver",autoradio.settings.port,use_reloader=False) if __name__ == '__main__': 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.lockfileweb) if (not os.path.lexists(path) and path == "/var/run/autoradio" ): os.mkdir(path) if (os.getuid() == 0): user=autoradio.settings.userweb group=autoradio.settings.groupweb 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 autoradioweb.service(): sys.stdout.write("Autoradioweb 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-2.8.6/autoplayergui0000775000175000017500000002336513001105756016103 0ustar pat1pat100000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # GPL. (C) 2013 Paolo Patruno. 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 pygtk, gtk import os,stat,time,urlparse,urllib,optparse,sys import gobject import dbus import autoradio.settings import urlparse busaddress=autoradio.settings.busaddressplayer def convert_ns(t): s,ns = divmod(t, 1000000) 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) class Main(object): def delete_event(self, widget, event, data=None): gtk.main_quit() return False def playhandler(self, *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 playbackstatus == "Stopped": self.play_button.set_sensitive(True) self.pause_button.set_sensitive(False) self.stop_button.set_sensitive(False) elif playbackstatus == "Playing": self.play_button.set_sensitive(True) self.pause_button.set_sensitive(True) self.stop_button.set_sensitive(True) elif playbackstatus == "Paused": self.play_button.set_sensitive(True) self.pause_button.set_sensitive(True) self.stop_button.set_sensitive(True) if position is not None: id = self.play.Metadata.get(u'mpris:trackid',None) if id is not None: length=self.tl.GetTracksMetadata((id,))[0].get(u'mpris:length',None) if position <= length and length != 0: frac = float(position)/float(length) else: frac = 0 else: frac = 0 self.pbar.set_fraction(frac) else: self.pbar.pulse() def __init__(self): self.connected=False #Connect to player DBusGMainLoop(set_as_default=True) uris = get_players_uri(pattern=".",busaddress=busaddress) if len(uris) >0 : uri=uris[0] if busaddress is None: bus = dbus.SessionBus() else: bus = dbus.bus.BusConnection(busaddress) self.mp2 = MediaPlayer2(dbus_interface_info={'dbus_uri': uri,'dbus_session':bus}) self.play = Player(dbus_interface_info={'dbus_uri': uri,'dbus_session':bus}) else: print "No players availables" return if hasattr (self.mp2, 'HasTrackList'): if self.mp2.HasTrackList: self.tl = TrackList(dbus_interface_info={'dbus_uri': uri,'dbus_session':bus}) #self.tl.PropertiesChanged = self.update self.tl.TrackListReplaced = self.update self.tl.TrackAdded = self.update self.tl.TrackRemoved = self.update self.tl.TrackMetadataChanged = self.update else: self.tl = None else: self.tl = None self.play.PropertiesChanged = self.playhandler self.connected = True # Create the GUI self.win = gtk.Window(gtk.WINDOW_TOPLEVEL) self.win.set_size_request(400, 600) self.win.set_title("AutoPlayer gui") self.win.connect("delete_event", self.delete_event) vbox = gtk.VBox(False, 0) hbox = gtk.HBox(False, 0) self.load_file = gtk.FileChooserButton("Choose Audio File") self.play_button = gtk.ToolButton( gtk.STOCK_MEDIA_PLAY) self.pause_button = gtk.ToolButton( gtk.STOCK_MEDIA_PAUSE) self.stop_button = gtk.ToolButton( gtk.STOCK_MEDIA_STOP) self.delete_button = gtk.Button('Delete') self.delete_button.connect('clicked', self.on_delete_button_clicked) self.load_file.connect("selection-changed",self.on_file_selected) self.play_button.connect("clicked", self.on_play_clicked) self.pause_button.connect("clicked", self.on_pause_clicked) self.stop_button.connect("clicked", self.on_stop_clicked) hbox.pack_start(self.play_button, False, True, 0) hbox.pack_start(self.pause_button, False, True, 0) hbox.pack_start(self.stop_button, False, True, 0) hbox.pack_start(self.delete_button, False, False, 1) # # Create a centering alignment object # align = gtk.Alignment(0.5, 0.5, 0, 0) # vbox.pack_start(align, False, False, 5) # align.show() # Create the ProgressBar self.pbar = gtk.ProgressBar() self.pbar.set_orientation(gtk.PROGRESS_LEFT_TO_RIGHT) self.pbar.set_fraction(.5) hbox.pack_start(self.pbar) self.pbar.show() vbox.pack_start(self.load_file, False, True, 0) vbox.pack_start(hbox, False, True, 0) # vbox.pack_start(self.pbar) # self.pbar.show() # self.pbar.pulse() #separator = gtk.HSeparator() #vbox.pack_start(separator, False, False, 0) # create the TreeView column_names = ['ID', 'Len', 'Artist', 'Title'] cell_data_funcs = (self.id, self.Len, self.Artist,self.Title) self.treeview = gtk.TreeView() # create the TreeViewColumns to display the data self.tvcolumn = [None] * len(column_names) listmodel = self.make_list() for n in range(0, len(column_names)): cell = gtk.CellRendererText() self.tvcolumn[n] = gtk.TreeViewColumn(column_names[n], cell) if n == 1: cell.set_property('xalign', 1.0) self.tvcolumn[n].set_cell_data_func(cell, cell_data_funcs[n]) self.treeview.append_column(self.tvcolumn[n]) self.treeview.connect('row-activated', self.open_file) self.scrolledwindow = gtk.ScrolledWindow() self.scrolledwindow.add(self.treeview) vbox.pack_start(self.scrolledwindow, True, True, 0) self.treeview.set_model(listmodel) self.treeview.get_selection().set_mode(gtk.SELECTION_MULTIPLE) self.win.add(vbox) self.win.show_all() #gobject.timeout_add(6000,self.update) def update(self, *args, **kw): print "update gtktree" try: new_model = self.make_list() self.treeview.set_model(new_model) return True except: gtk.main_quit() return False def on_delete_button_clicked(self, button): # Get the TreeView selected row(s) selection = self.treeview.get_selection() # get_selected_rows() returns a tuple # The first element is a ListStore # The second element is a list of tree paths # of all selected rows model, paths = selection.get_selected_rows() # Get the TreeIter instance for each path for path in paths: iter = model.get_iter(path) # Remove the ListStore row referenced by iter print "remove: ",model.get_value(iter, 0) self.tl.RemoveTrack(model.get_value(iter, 0)) #model.remove(iter) def on_file_selected(self, widget): multimedia_file = self.load_file.get_filename() url=urlparse.urlsplit(multimedia_file) uri=urlparse.urljoin("file://",urllib.unquote(url.path)) new_model = self.make_list() self.treeview.set_model(new_model) self.tl.AddTrack(uri, self.play.Metadata.get(u'mpris:trackid',""), False) def on_play_clicked(self, widget): self.play.Play() def on_pause_clicked(self, widget): self.play.PlayPause() def on_stop_clicked(self, widget): self.play.Stop() def open_file(self, treeview, path, column): model = treeview.get_model() iter = model.get_iter(path) print "goto: ",model.get_value(iter, 0) self.tl.GoTo(model.get_value(iter, 0)) new_model = self.make_list() self.treeview.set_model(new_model) def make_list(self): listmodel = gtk.ListStore(object) try: if self.tl is not None: if len(self.tl.Tracks) > 0: # attributes and methods together for track in self.tl.GetTracksMetadata( self.tl.Tracks): listmodel.append([track.get(u'mpris:trackid',"")]) except: print "Error getting player playlist" gtk.main_quit() return listmodel def id(self, column, cell, model, iter): cell.set_property('text', model.get_value(iter, 0)) if model.get_value(iter, 0) == self.play.Metadata.get(u'mpris:trackid',None): cell.set_property('cell-background',"red") else: cell.set_property('cell-background',"green") return def Len(self, column, cell, model, iter): track=self.tl.GetTracksMetadata((model.get_value(iter, 0),)) cell.set_property('text', convert_ns(track[0].get(u'mpris:length',""))) return def Artist(self, column, cell, model, iter): track=self.tl.GetTracksMetadata((model.get_value(iter, 0),)) dir=os.path.dirname(urlparse.urlparse(track[0].get(u'xesam:url',"")).path).split('/')[-1] cell.set_property('text', track[0].get(u'xesam:artist', dir)) return def Title(self, column, cell, model, iter): track=self.tl.GetTracksMetadata((model.get_value(iter, 0),)) file=os.path.basename(urlparse.urlparse(track[0].get(u'xesam:url',"")).path) cell.set_property('text', track[0].get(u'xesam:title',file)) return if __name__ == "__main__": from autoradio import _version_ p = optparse.OptionParser(usage="usage: %prog", description="%prog graphic user interface for autoradio player",version="%prog "+_version_) args = sys.argv if args is not None: p.parse_args(args) if len(args) > 1: sys.exit(1) try: if Main().connected: gtk.main() except KeyboardInterrupt : # Clean up print 'Keyboard Exiting' gtk.main_quit() autoradio-2.8.6/doc/0000775000175000017500000000000013003471473014023 5ustar pat1pat100000000000000autoradio-2.8.6/doc/autoradio.png0000664000175000017500000012370013003436206016516 0ustar pat1pat100000000000000PNG  IHDR!ȋ@sBITO IDATxw\Wyk]fr{M(B5 ݹ[HHr@!I cL ` )66-:eX3sdCD9Gj>=ҚϼyIDPQQQQQQQQ3E-********TgYTTlضm>믿{=p@B4]vyw饗/߸qr/bm's%[ŋ0DDhDñh8B-3бpxls}]`],3Ƙ%}{?^KEEP2&rޕɎ?Pts'EXOEB=kR>SĺIU맠XG1|}{,[ 4};ַUkkX" Ǝ7練 nXt' "Px R)5猨O@*Vx|:'>re){c3-c" CWC @)R0DFQѓWGLpiŧJ]s0k_ګjyի^uUW)UTT<`w{5Uf.wd)=;'D m~*Wz@@x#&DbkkWᨺGb+" T[ևOpk֛$%Zk0wދ+}sz^FD{-"+**= |6猨d8 ^O4A D%ź$QeVZ㓋Wy}94&Q `UCq< OvY3#RFF$fΊb.-+&-~MV9ZdEEϖoMo&En}a}`m DH)M)RD^ /b'DXċx$E (54D[QEҋt%~Ge E nIeu׼Sg֟v{q0l 1},"tk^ef{կ~?Z^EE ۑmvs[_8_:.FgZ9 QLkŊ4)^ Qjh\}O^eÇw/~,"P,` X}V<ֹ+//~^h!qsc6>K^UTT8xkoZas nްmnj6+]u!ͧ\:G`WKa $`!'+t(֤V 'EV|c[gfqBqsn؛uD9U-&,֭T6wb!$ +%Dy?F?\})'u>%Qtȱ"|8#C _]3nVf-JWzV28ƓOJ:'ړ롿mvw(&OTKq"ű2L A*k&]M-fb{ya>vK?xܗW^wu6\cX"ҳ)Rx;vnik}Mv] OZ'J?YsZ;99o߾Zvꩧj5R+Se䰱9I8:%iY5T**V2ܙn>h~]7ǟyMtY6/]yP/rĦ+b'W^]Tq7g}W!U &8ME{b̲"V23Ex(3DHt[uzK6rB|ϼCU oۻX%D"P&C-59ߺ.;=j:y"M hB_c)YgEtnclR^;N+Z5\{]źFK[Թ>ḍcWA>nSLN2o2@@BΓ/]_75O>au}5gp~'~֙m[B?' b)ַ~>MMM\0݋1"<+** Ļ-_9\}>fN;˲(˲:g D),οGw%10CM>ƺ 923{ΙkJ,"eiw}Q:%9oOI֎6PRxoEK{NI~5U&~aj,P,Vr׾|ښ36 #zRjhlݹⷾaTi2!l637}}> "HFZVYGnr[Rc7֏t۝,˻217Oz)ag{#13 Sɉgf$ת.Xz dc!fY9Os]5F+vF_u?HKD>-9i7=i{g9sc+T6vXr;v4еe"V[ʳ={#'.Zijh48o|7nܸXf*{|4Q+ *UQqPf" JxK&N9"yLu`X)Ue=ޫmĂX@(W+ZXpĆHa\!z>Z(L,<{EKs_Y?{|ޚ|cq{<^=yWȫ7^p—;ǟu'AǖGJX {oo>q( )K|;O<[Os.?qbBEK$qjcccQMMM}#y_+2 ,AO(UiydGٶ{lFepyoab>/[uw@4 6^G׸KkU>؆zuQEA нa X"@H K:Ŭ {.&R}{;u9zٹ8}k{Y@tzE,d C1s{@cE=/ѺDFvI sĭPGsz?`iAUTA_AV7:#x{x+?Z3Zg 4!*>rq`5پOǪf!P=,&s=ʲlvv>Ȭz1Qp÷x^^<"dn}ðJcUT-:66{ P¾Pk .Y1 Kll[/A0hXz/4W abYXA]^ˡVj\n 9Qz.Z!C&;Z(ȿo,./+Hc3+lh 5$!Xfʲ՚7 V&җQ̙õRC'm{={DizXZDeWf3FER*,/x)`t=Va{om率B  BƐY=hEWް鵗5gH@>$B H)E4ai뾹GCOlsڷ;WE*!!#/l pFRK?ﯨioz[h,y}!l68h||<$I2d!A,!f%kB3{i[zMp>v?u<5%a9[hFL"ʄ}we^ONkGA)boƎ18e:}NKnQGIű(\(*YIDZ۶mG>˗z͚աٰ=ڄJU P U{EQA~[n B R~!ŗк% C! .!@{h{)BVcpd#Ș^4K4V{3k2;_I2p%R7܃Qn@ /^-_jV4eɮMsh6Z8/.9[n+$T%~=rG"y+?exc5*=Ϫ:<|`5 0YŽ-$PaeŗmDiWB, ̂!YZAA` g [ݰAcA2q]!:n/PZ Dj+߶i& (Ebds.l);vg|ghRi5~ƮJ#ZZ,< S+Riϼ  ynaaNXZ8CꐙnQ!UАRVٽï{k֯_֣G?UɊUT ! ^@,,~*^0n̑-P{&#/f|mӞ݂@`uJ+0CDLk ^z)ÃNn 8CfJadɾ$ jXH wnٮㆎF4|wkogqKl#lHX@}km:n&5KgGUcJpSA"+{֖fYy|ll,w+/}gY G |{Ti+SlE7>4bpٲt>tƉ<0 H42 /BMDQqh* g6/FBH5 ' Ç_7_6$δu$"Jdxx{?Xڸ"[EQneիڵkg>e/~Z=y|- }^NJ!8***Vn^W`53 G*`YF& e ?|IJ$R #HD`(')DYtdp//̮=(b{W]nZ/;'D,g&Ow 7}UgҾKDzk,w%DQfCkE20\ݵ> jd=w;7#CJ;m)gMCz˞a$RF@4c̒$qZ C0MSkmY((Eyjzj7p8ԼW >+.;&l { %Dm746Ct6k4?+**%8ִn pͼaY9 G]D^B(ǂ(4YeGw#+ oP.,DaX]-,(Mt/9En=FfH({XHF'm6w߹ly y}Xn%PDT17h iұy۞'R#ٵw~j&3$&sҙ;ugHe}橯ZzVްqvKMϹ_'B 6q 4"A1 5XqiECZeDJ0؀pDyک)f!Uq86$*sp# 당V4TOGz!bGC洢On3'= ½8D 9,-xꇲzǞH^D&I;!{ޅzBqyޅD!D<{.2caQ!|E= O͵!kϸ+7ASҐbFIH2H҆^'~ׁNcm\gz)Y^% (;gɬR0v3{;UÄP;QJ- 3R^/f&|P N 9>uݹWEx$P*-'W H+8N4(hVv+˲Z$WenwݡK }'_PeݮݏY)BXNMεGͳKn>P=Y5®jNEŊMROy! Z ,S BY$,)|; cZArf/Ah̋wp!SWċS6?8y5Dd:B8̃[f`cId3KKrDZ 9ЍQRJi%3Ce֙v3G3\n]!h2 Η\̶gvH$B[sobL'ǫ(%%Z»(P?ibxaQJk48zpbYa\y^yQENS;"c X8XIT G΢˽:mTVإLյE[DFLJ YTTT9s;P&l>ĬAc=b(=ZL͉ _shUW@ISc}/tՏe%,xbDk;'a0iM+瞹~:bɌgsY;'W0 /&.jX@<'<5e<#m&xȸ3{? @$, %JD54\DW%O~ i; 4:Voz8XoOZقPJ4!!@$iXA] YIJ6^k7 Ӈo "( ueuOnQKcHKdݾ~Y<1Ӟm'Y)a$']'{3 Uk9 HʂV|79[SGԿ;+ )ڬV5X"|݅px!ɖ$g_((RJFQ J<ϵ֏^GV{&I<>\i?Bd[욘֕օ׊% 739@0Jkq*Tć?!xnEg q Iq8/ӭ{{ E !kMD110We!EI<1sy7tbe PDڃX)rqײNexXSM¾tj )+#RZGʬ-:3(ӎgR%\W#$䬊ϩ,ݙt&Q,DRRjVqȼE5y߯40"HH)1&5{?8+8&CT<UUU֑q,jQmi"[S+g%2s3@f@uuXgD6tg6z +L"D *ul#A+;xpI"EJL$ڨC{,aPW!Q8b/Qq < ~-q!9d܂̃g6&fZ  cր+!WHLn)U>h>:JDҫ3{t WLD$x|M?XJ(;;>2S3&O^<3R,7k= U BP4Vz(0LڵkGFF NSe5 GavwݍƏFu3-<=BrSQQ1@lffiĴz9న KfRg jSSPTFĎzFXI%Tz?D`a!_I]]0H]K ,Ŗ(-#]vbfg>Ay,Mua[_z^5Z&IؗŪ8JbLh4t,`/3"CMsmwՔ2!?^xb3u|DGÓPAr-+-}(RJYkׯ_?::#ƱovsgZݙVWkHF-iqr_E:|8\Vފ***@s89Tcrx v"m"#!I"J\.< B.ʻBDR/Qثyx4+pBivQ>[åaԩ!sb翳$e 9ec@ k;E$LJ) N,MeHj#ZPH"EUZ 9&n$u366fkg%UD9u=x$`":dCB<Zw$ AE1sE ˲z+QILLL>I$I#GяW{;yɋؘhJduX=[KK,xîX-؅ɻB  ~+JRD}EqV2.}ԗY ыixCR!1 -5ii[uajpXţ<$3{|]d!:F}=z3)0ql~`RH)e+NATxyJ#hij+GGI 0a%,!g *,V??"ꮂà>A!((e4H+RZ)(qE,B?UZ+x"2Q6j'@a ,Vh!l2) 秧ga^nw[̕:Rő5G/ƪXI3/lO Яb ̡Z`b1J~Ⱥ Ƙu-k녷ͯ=mOr} Y临}/pkϛc6Hԙ E2Ezy?0uZ۬}-^ ɷb|N, ˬB X)a8 )y!J+@H+P ak4iB& NZ,)NCA(8ۛ#&m>euF8$N E g(Ay&&m(a}Ͽ,OKBr.}fRCr&a%^*㞲{ ,+POEJ7;\|;sOҠGj5KFy DB Ԇwu|%^D l͚/| IYiGeڤt]>U؍΢>ycQ+ Y,VuvȄ o=,+XGNoWaT{RQ"1p`Z '{e-:{pޟ#qfm<@ZH-p&q *6PL&>kսGuW$3=3瓡{֝9sڻe,2qLdZG؅a}*| GHYYr:0mյŽTr)#Rg L0}ϮAcYpvT |/n1 ȟ4`+=h (rbl=MJ{uᄉ 2Id`b>|e宋YɌ fسtF`bV^O,xh'LDJCcGd5\Ҁ?l8xK*JU4bbE9"RZ ,rPlZڵo )b1,xٸ(\G9l̝W_ܴ{aC ^9=X3(_`,ci-K1iZk=<8D[[8E$5 B)^L)s"4û΅^7 MrIx;l1YMMVJ u31 k&BNJa4zff ͳ\moSV.;gudqmL 9 &cV:cg u.PrgEiI<2)p(N#+UnΦͳLe\M5Ό)]{0챲`Yd! Wg'c|K0 e9>sA?~:]7rB0J)yTUq*~g+mۙ1cFMM1fJ&]]]4٥(" EDJPâPMҦ]k(7c䜁*T }41,)@s1 ˅NKR:T8H%0V^6C-0̥MΈ~۲*c8a93:V-ul֮*gǓk, ;VuՙpYɶl[m`"eiopᫌS:^WgGlNHȲ*^^{"=2ܖd(jC#D&4U7>𞥎rRn2Dz,ᰟͰP`zRT<,//9ljF%#HA)*,kEG ' ݥ|T0Ⱦʇg02qA 䛯`xPƃM#B:I$R z*`@X)lԆsdL.`p˵I8C0lhrX+M {Ľ@NxǽՋ@Ih,CJ) [J,+6nZ:mQ`V++ u:hw IDAT-C̤cC_yY" `Q/a–֞uŠ_x9ER4RJ)E rRJW۶}'BZwuu&q͸n&wZo{4Je2YW8rd]HBSy{1!CD`CA1yee8P>4)*AU hhx 2iʤI͠;UTPƶYف2:'@,61揽wN|Ttϰ)bOL6)7`&J!C9Njk,fd1OUxM"le!\Uhocㇲ"Q[ϴ7ļtq, NȦUW4G.Y ̰`ۯ9ڵTX)cr,e3Si_޽jeTcv2c+eY,q|}[H$DZ,dޞ^X] 'uW"FfL/"P{d(I& L] (' Bt(wd0#σfedYW5 2NNɠ>sh3҇/Md/$TxߌNXk2lpCoWpaZN5"[,Y4 'g4!)؊!Rܫk3n&f[8"?^~'+ ldl ڪY+p#|2à{6rPrXE]dXe)ͤ+hiO֕TDöEd)m9*$,hsn0^(IYÊ-u^;eZxvM3WۺEt-W^pvy*F1X)g]o,ߠi7͛7c fHA۞ Lj~Hf]}g =CY,`WZYEWccKj1ڹ1ˠ"UaFE|mou~|+:meIpBqܸ`C ߷݀ 8C&jglccuog -^6YYc@Àٚ7+R[4ܤgG-eS8RN[;{yg <1׿NK&3V),ˎDUvъמw=ӎ( B*D=ľ}{ث=9ia2; ʑHdڴiX7\o}n&㽽ήֶ-[y}qưRAeeCBɑ!k"m"f܏Om!_KIC2rX9sse"ZZrE3Z;ϵm;W_c,N}yye!3HSXbc/i0)b/WZb?X7PGDUӬއ~S$ -Setqϥ]Ɇ"8P̰2 ;K.]18!YҖ*HNk♀6vjݳ>Rt(f?Oʎ\Q򣌆B!6[\u]7Htuu}uks"U ۲,Edݠ1 йP A~0t*J======۶mݺu1K.kpp.R!2W8(b DΚqnyf-i9e#+L֋+;o!.l’2Z椳=2L67Fr*- ڧ:i)&6`v:mYgf3/M^T`Yoz|RjOطhP"K1Wlvؖ&C!&lSpC~_}3eVLC'xs}{:ieV*d9e6ȸX6u|'90 ;p!K+uav a*ikkknnniiB]vٜ9sijjA "/s? @L%fCSVba u5-"̉eexPv%Ԁ5!28fEqbb80tވnY \M R;tb,BYH-Xzk{lB)VMc>u~o%;c=Tu]L꿟20K8lGY;\wED;@ZtM$R==]۶moiiٺuKKKq{ <ЂSpcDϓ?6L PֺǁR3hy9“Ѥ Y7D>\ϺS|݋X{0$^!!|KNCyy.<YlUG46eW}׎5X5"K!x6WmHXTqlEpʌ.\~?;ӏ6~&x^5Gbkwz~f<";WD"ddggggggsss{{kOOϩziY cի|O=ZU*5|=W.VeA!2Ƹ;]mx1A+`5h3aߚbe}Yʛ(B03 | 'ێ@{`C3ԋU[)H)Z!j#ZtO/R Aزl'G*k{}ߣ5irI9#~+B%~ćciu(LvwwrZ[[nO;|#Xs)KyWZ0F_w͕~'\{%.b)?dKk\rÖ'C>Cv:/herqrFܫ=?U${Iih^'3W.gȏQ:5Mq9;?;_~qX++ zzݩTS":s\z饍J| ./K !38?ieee"Ρתbvm߽$N:l@2LHw2:A(7]}Uaa|}TI ufoAC[z}Fp,Mn6l߷ڔS>OɞvZƋ7}碹3,lۀb +`vs=38?ayy8Jf/kN<ҥKotI L\dt*S)Di ?GJV~`[Pi |rz dsPZ) DFtLsуD} @c1f"Zk[~ U]߿SKς( nj,c{zzyD"Wz뭏<Ȗ-[2 r _ݹˇh:aP裏>ӏ?  뺁V2_}5d. IDAT /܌`VZY۩?us8C]{ 4efzxhZE$o B7`hfCd7w㦛_~-ަ(ݯ}f=۬AD0PYB!JrPVoX.ɑ =ID*++ ߠش ŖO&Nom<~PW OX V0坱 UtƖ. b*V2`"i, N7čos2D Mu30w;Grܹs7l fsAv vŏ]h*|+xʬy%칶ґJ&BR> }^ED3iUZlyhQH+qdg ̚gX14hGe2)*ĻgACD~qU(ӧ{Gv&KV,Kwz⍛T!'wKk6`)D"+STV*&XTWrjʆ!Ic1`fa+);EK4S  yXyLAVaR_{cB_SiEDX  Pb(UCr %Zjt)ƒʂ 0*c|iR;:n1?@ F:#?grO**aA0kcT=A cLKK͛7mڴy榦d_,=8j,Q]]]UUUSS33fK $` 5Ru]֏T' b3sEap]_~7xumܸSYΝdɒ%K,]t}ihhk b 5VWWWsss("x<d63+++cX<UZ<_`͜9SFainn~2kֶ,?|$-///++wvt} 8 D2L$d"θnuNDӧO_|!r衇|3f &]vibaJ?wtzêOQ7}3nO^V^n;cہ0?7𝑔^U]뿕afҙL&;;;:;z{z{{zgD" 'pꩧᄏ( LaDcb&9۷oկ~_@-XhE -7s+,"_}B06J"d[6EqH$ZY]mԞ%mmHRO=SO=uu-Y'?YSS3&¤&p5&-D{ 7쬨lŊ},9)e2~lN "\( `4oh>cZ]mokݶekssc^/~]we]vnEAaM6}~XvG,["3+k"˰ab2P;iEYU * V?ȲںʪE{,Τ3--[6onnޢ=/{niQA& adSDc t:}gP^QSN==ǕWT򺪟3HYP97YHL iv,Y' l ,ۮo>cԲS;|g,^j?\HvF0k U(KSf#"c ;")UXN \Zp_x{衇NK,'@w%+7_g1Yis{;򨣍plÆq*/r\ ]Y1b9Fx/\-.D`(E%C!͚r9|bHEN(;r~E{,,+eY -KQ`*B3aE/*,#tw>_Y5O_y "?_Z0aPd;Dce B۩4%H\p7|svbXÏ\}aϙ;q2+ dV|۱ Vye|%d@U O]?ʾ\iӦ͛7uwuW:oF   5ɐ7vOȋ0=sWYV74{b}̙s rO'RA|+o2}U^Y#*UtvtlڲukKD<\Z;ƿA(-Cި# Lŋ4h}џgsOoooO+*_x=.c.lhh Á;X@`eS^= &I~ GLuΎi+++?GqMaFB C" )AOO<<7nZ_̂  h,A5$  KAa%C6AAAX0 Ĕ% @4 "ZA%@ÕAX0:F,1e >ad%,A)h,A WbA KvUbAŮ LDN ; "#UA,b]dߕKAرAAX  h,AAG4  #KAa% 0AA}Dc  >AAFX  h,AAG4  #KAa% 0AA}Dc  >AAFX  h,AAG4  #KAa% 0AA}Dc  >AAFX  h,AAG4  #KAa% 0AA}Dc  >AAF5N̻AA&(Ic(0AAܔ9Uje+Ex KIhIk]vv;0Z7"E|,Uq92vi+K1>LDJJ/qX;rbf `'8FtCv]ȮCgWRSQNc%SwaZt*7fx+ [%ޅF:^tո0*5"%jPl%jRb_iUj[F :^tUQJwXcX%Wa()E]MpJKipL\տ*1Zih_x)-QW+-QW DZM8JPl ݫ,^cylcThЩ@SLդĕ MI)A41_H" ceКjtAWU؝D]M V,QWQZ5֤2_)FՠUčXj](|:E5W: ,=&ž&jRPһTRXojJT5} >M@=p1.8;L<Y?hX~%Cfw"8k$P9"wv,H(|MF`Bq`f77)kW?~,Ըg^XIfLjz;rgFSfj 2~q(qg)MC(-9>5@N~} 0TQQ×뛲4p e .47v N5w'\4L4vZc^##|#̪3~ 2߅W7uXK_of|/O5hWk U\>Tkx*_STSqlƚopK(c8bܶ?y{套f^ ^OӞwm_~y_|mi ΃μdj}rW|W\ѕL0w߂/}霛og2AA?x9W^>==js\s _۱f-V-KW]u̒%QǩF?_}u~OfWUͮ3G?LRv.gPjF,g_9}1t%PLVl-^ۍX f[-U(ؿ~[×{Jm:@ϭaA.Jj2&w1YY\^v/lԑHΙ9tu->3 _U'xٟU/|,}Lֶk_ mnu=[ӿǿq~6쳃:gvyQs~|5L2S`?p8r"ƴ(x2p7il؎쯫ڂ)3EvlnEř?f4u܌iDd|n\}2B}8 mB˛p~9=,_ rr?W!y?qwMvMLq9_acer鷶oUU|_ZrO{e]q]Ol#[S_)O~2Jº[{. ˊ1wY,y1`BMW҄%~u  \XP%ԙ>[Ro l~D ehn¹AŰε@ X|p 7frJLN~y.#*>cQKӸ%}\Ⱟ0WfါqX>W?Xy% fʓFZ.Kaq=>qdV1\ysp +{TKE<ﶷW͝{C3 mm)_WVvk6wvn~idI93Ak Qv|]7t IDAT$77J,YSY/X G ZQ6_; N"2k 2FnX%2k2n~izt#𖎑CH܌*ނ'O %x`O15l$e%ĥߝַH|h,+ iZEiM Wv"ݴ(8>ŊҚ&I8ik9ȠYI]N:ĥϭ,A֤*@ Z7_Dl:ŖV5)נo5q鷚O'QZ"lvA/f )痏( HU!&4.QZ~+vWcO4h£ %ꇈU?DiM8J\]"JkBS"g4VCe,_P\O [&R'#H([%V5()u3+aRy'&ôh ta2/j0HO5*.NUUj> # lԓF$آ2x;ON5h5> At20V{ vqXK^ra;l@cǰd è2dN5T5 huqa8{w5GUqKUH !QV9Č3Q`BXtDH4Ȱ#DdBdɌ0Ȣ("Ddf -czR]]x!yꪻ>Ua2VF \[jOO,H0oCު&{$iT9eUTDdnESW;i'y,*<ܓv ,rnU^'oUͺڛЌS%/Y.i`4SW~2\rum Qރ㹱]GBo>c=ڏh+Nmcw 3BAw>#iXf3szژ]fsj,g_0ݸD*C[$f$d )kif5u~_Jq=VxUpOF4}X>v+Tml UYJh%-ZSU;@7U]>: {oGt߷07:t~bZt>Vط cU\kT@Qϝ=ѝ`ikWuj ] XmFs7E໷F o=#+%tWpY>/6J;1 GٛI_ a,՗C{̢X^ ruB-_t6 S9]Y܀[| d$S7LשAw껳;cH骝ӷDL:Cuݷx[UʙሇrL~k3IW=olr:_pܱuC .ȺVsbIWם9g 8znhbpd2}"D+3[߶ +"CRUHg4xYb{uݏc5h?oRX1nXqcZ_ rYFUxؕ>s2V!OV#|_1&Zh*2@ƊJc#8^2]b:Ħ+CJ+6 R)YUbVc?6V"XnF *`_Ia'g:X"fӵEŰ48CƊup󵚺)V>u1KKj' a(uNaO26V?bKWGAJ ZҀ%sҕb.]NRV#m4 vИD+-bMopNp8-G\dP74"&Jصc5!;VîJSF*!%dq|XFjC}УteX#c+͘e좉ըt޴+C+qI ^sRy>&kp lP4">{22In"VC`i8Fҕ!` (g놑}tF*Gq#KWʉ]7إP1dV/]鳠2:EE1#V3fE"&ՐC|uÈGX?A+CB/d,GPWӇeJޯ)(XA2 1}@ZxU>Z,i&*HmMcy?aRy"8^&)7ʊeb Q7L\+L6ۓC!c(]1+336*M:)FX(! #| }2,CB9* .bu{h:ӭ# 2 9f $ LTu?-#cUA2#j%N2[CBuB6#,?A>=u(.h1嫌m{;j ,h1v3QTD^P HYh5PԨî UQ'jrOV:ULg\4`TFƂeB02'],>=q:Xp"V?\KG_=fрQ x*= R5\%^{ X| gTp_h; Ê JT V{~ :7:⎇-Oum-eXǂ놌 )RUEf $St-Ќa ^g Kq1$vG,el  rx@d.Pm!֑) ^oLW-2o3=ubg5o,_$9]H9ipdX @Қf6ix@C1Xh~ * d5YEF7 V#j"cABDQZ,3\#cAR2&Tv)EўZ,3< c%M ٥&]~ cA V܍FBėZ<Ҥ  ru6f# "NW<,4|"cA(SS, | k;{҄6IX_q1݀IW+lcu>ucYZ#,hQ5 xP#N{:,Ӻ>yڿU,32<@ Ol4o6C  :d#uCGؙAs`Yc  ZAeA 8 XGV|.5UOXe aM93ӅJN'`A2"Y\2XYNd,Ua6 K)mY{9XuÂ< `Q1᐀==Arg`ae Ӓ-u!uÞ*?}X ʝ"gP,HCƂPJLT<,DƂ,R1+Sw`>dz~V XDEݰf".(8VYly&xAd놭Pse1 Y  X+SY.fDv]>ҕX6q_bJ:uC!]ƱXd,"a4 Jf˄ d,Xt%VDR-2U)Iŋ1$h,HFBxokn>%vE0A,2e)_RM}n9 =9MW<˖~{2 BGV֣XҢHTqB;2B2 Y eGkptb$mZ/ ` l,HCB0.R_7袷bHҨI.Ԑ\+%v^TtTfϑd,EH[j'n>MRJ^QDX@rBHHW< fU~$/b -_I-ҕ.ŖO\@@d,k U7X&q b#d,h_7^&aaWrY{!ov6tUb"c!.Fkh"8tW7TqVOӛ- 2|`:oaĈOd,xJzr7)MW5{Gt.̂d,_7[n+ vWYX@],_9BJA^@[7d,8"VKaLnHW ~VRT8| N:$f >3YҎ[¼AOî"#:>-"c9DKMJW*IWɒpԿuvEԕBJJIJb"ci=]58)9& pXIir`$"!c '[;Ԥ/s,\ cEMPtUw"@J. X/E$սbf{N7d,XTd:n] 1KQ` NhRmi"Xb&u+eD(d,ؔTm.i(/58|R+q! %5BՙK"|URMVWXAr.glgz%3+ wXLZ'YU" IUJDƂ 9\B   ыluX@ VZPO[" X]5Nl!c(nvӭI$4WGDuIB!sX?WJIZOWݯoդI䬣%  v=B ]JXO D?8k 1uje@IA"ő`Aë zKW]Hj P =ș? ~ u*jA|"f2WQvFذ~/h|U}T A!f82'D9%>؏{{nJI9`e(GQ'fIKW7%~XEd,T"`?e˿GP\_h+X`?놡n,N 'b!cYEWrbtz n\|d,Ho+]7T:DֺbAOqd,T8|ꆊҕLS4=b!chF`84?W !`Ρ?ZABiG挞:#݆u[bEi]c6,Xu,8 (]i@Df(NE2 ;XGB!gh&-R!8D_#f jV% 0EGncCsO->JT]P!*iX!c ucAJ놊65,Y civ/r58bc&6Yd, h{/TT7uBb4=-NY \ c/uy[*KiBW_]d, bUZaƫM4vuT5@Bo5`",oi"caIBHjQT7y;1',b$vD8;z"GƂJ.v?T,h0]bN界;uK +UkLW-b+ON2Z3bĬtBd*ꃪUM҂% d0dv0S`骝}EE, c!XU|ElPƨ=2y+>qjV7o{ Yp$~ c-Q%X0F"؉Aî\X7Gh!fE 9dN ɦ9uw~2 VYX2/"]2:KՉ2rpDJ)*#-hD o@,D6 21Ra& uCJO* fE4!=YT}tUJ%ej$C2VxRرXN%tUMغ!Ǩ+PE&2VH#Ew؆ໂtUGغ!@ +b c.p,>H(ĐǏ QX {+>Hr!T0:hwq4X  RB OHaco\ުQ+EN<ʛutR@?RӔ HW&xO+yDoHW&2Aҋ ]4LsA+LJ 1K)2V A0(*q5"c% t%scaMO !cx;ߎttiYZRs\HW7DžY`S+x:+\OQ6 1K2V ޖHWqp]7h>#fiAƂ~_?u!n$Do-!sXs <*bbV턀H?DpU]S.Rn}"`!=2VܥׯOJ뺡y46bwo!]ErZv,"^s ˺IYb OB;uC]͉Y2ybSIWE~/y]Rh0*5 g%]auʝ%sf뇭 bo3d32PzBS[ڊA+gnX |z; K A ֹ\m;L]"]Uy-iINw@e~.̲^7+b[;._4ֱFB)V7eYO-,7,lk#~jqÐMFGsӿ}uС5uDheR~ӂ+f^yR NU9U,_U@ ϨZ:\+5 ʯŷ/L׺o߭hVWS?./:@5oS6ݧ".?X i[T-`q <䓟$4ˁ9[lU Q X"t_nq|)B§kQ::6O߁-N+4Xǒn\NA[Y;%}4˚X*+U<4aٽzuԙ`QUBFTCPO̲?\ <U][:}N;yT+$ ꫴ XJD@MR 7d,AZϊI5XP+bsys^M*0&D}5Zo@),HvOvGUQ+Z!f]O`[:}d,X#cGW7D,y%XbsW;tUB!c OvI/|sz+Ui.5/h,^l< l%Ḳѣĉ_p`vpyis欳m=AVذ?\o<;?|8?3sU79C|`ֿ֔Ɔw_xuv؉#>Z/ CYHθݷáS XFߪF{9a9^1#cfX3s9H3}z 60ӧ_zKw1ly͈ͽ:b@8xys^~?vE~Cogσ_n?sa|cz4ůN~˛Ս8xYȯ_1Xl[%˥ͥtއ;u6B5,Xfc5\u0gb /&O4 - O9{k灳;[aG6Y~z7LJ/"صد?oG7tu׻ڙfagR9?vݜby>q=?x {d9?f|Ƶ?%K\y%nA0Bo@76fLs1.`̻W~'y c̚k34giMߚ3!n =dz?l?l68V]t7y)򦍞;|Ģ7fe H+Ï0iRگk^}9J+7>rQwF?g|g1_ sGX㳊:VHfv#FO6=?7< ?f̘0[me.wmS>?{I/=̒ Ov54_cCC;/~{/k؏zS^u5zҤ]q[g^lk]OA_ߝqwyC}ʋL]^~%>4ֱxmox,X`f4l?e3yb3v1<6Lj1bN5;l73;cvxch}'?Ɵ;6{~従_n-7;b+뱓o7 ~SGos?ݛ:vf֕@qSXNFW<3=+8+Uˮj4Q}r =<+兗;|ġ}nxOoeǺ({ԉnQ߽;𘍏=ꄏo3[9/˜ /LS'=a2P p3׼G` >2}d,X#cG` >2}d,X#c 214Fz <,:?h`꤭ch0IX5?|R7 IENDB`autoradio-2.8.6/doc/monit_autoradio_example.conf0000664000175000017500000000434013001115362021571 0ustar pat1pat100000000000000## 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 if 2 restarts within 3 cycles then exec "/usr/bin/pkill -x --signal SIGKILL autoplayerd" ## 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 if 2 restarts within 3 cycles then exec "/usr/bin/pkill -x --signal SIGKILL autoradiod" ## 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-2.8.6/doc/spots.png0000664000175000017500000006772413001105756015715 0ustar pat1pat100000000000000PNG  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-2.8.6/doc/apache_modwsgi_example.conf0000664000175000017500000000125613001506415021354 0ustar pat1pat100000000000000User autoradio Group autoradio WSGIDaemonProcess autoradio user=autoradio group=autoradio processes=2 threads=25 WSGIProcessGroup autoradio WSGIApplicationGroup %{GLOBAL} WSGIScriptAlias / /usr/share/autoradio/server/autoradio.wsgi Require all granted Alias /static/ /usr/share/autoradio/static/ Require all granted SetHandler None Alias /media /usr/share/autoradio/media Require all granted SetHandler None autoradio-2.8.6/doc/autoradio.dia0000664000175000017500000000500213003436174016465 0ustar pat1pat100000000000000]Yo8~_a_3 c<`]`}瀶GӲd}y8-%d:zfX*WbTˬ,Άr9oLj*=؟(ނ~yv :t:<2<`k>[}׵znΆc=1b: V&e^V ^t5y^{gf\}jh_Juzair!bcH<ƬF-bvokxOs5=hzYVlʱk@bb0%T5]Ê+.[^,ʪtVoentյ.g9ѹbCˬ7Rm?Zۮ;bD,ٴ\aHdlϊzogoǣӏ aV0{]gS|c2j[>nۅ 9k/"ZM,prWh3Г:yxl=_fR^bn?,Ȧg— bJ-D8H&x 篬Ag^I`F2_LʪS # LeU>?> 6.>JCTA8ef.^B %Uy{{vtdq[~fhe "vh.vbd# fzsƴ}-7Ʃʢn>\ygr8X-ކoLMsl@@aJE } މ]D@k {VMiykz75ݬ?Һ]<= &Sbab2شڞu_φrӬM##ϳ 56a'Ш<&T+~лp@8 ^ۺe QL 1>22hճ?8B(>*C*V5 c^ l-*_%1UIͮT(~IӆLRGS*% {(E,+#N AlD@9~1' 1+|2..Ůû'R*)'lD 1A4al l܎ ;ϣEanVFv͍K:ch=k=&`͘x0vaTK~.nqϓC# 0Ƒ% $>ݷU<)[P:xsаwkB/C5D]ppY wQ )lp|A =Ar=K$}k%̤{~@b̝KPȵYP1e ~^8|/rؿfھ"!1 LC,k҅ ,0M=jU50gmnP>%a(RĽS)0!q;LH؇Ǧz677T qBÄ)mxhػA '+q:As *EcCa HDb ӯ90Uz~kVӋautoradio-2.8.6/doc/jingles.png0000664000175000017500000004746113001105756016174 0ustar pat1pat100000000000000PNG  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-2.8.6/doc/user_guide/0000775000175000017500000000000013003471473016156 5ustar pat1pat100000000000000autoradio-2.8.6/doc/user_guide/en/0000775000175000017500000000000013003471473016560 5ustar pat1pat100000000000000autoradio-2.8.6/doc/user_guide/en/feature/0000775000175000017500000000000013003471473020213 5ustar pat1pat100000000000000autoradio-2.8.6/doc/user_guide/en/feature/index.html0000664000175000017500000000430613003435504022206 0ustar pat1pat100000000000000 | Django site admin

Documentation Home

  • 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

autoradio-2.8.6/doc/user_guide/en/programbook/0000775000175000017500000000000013003471473021102 5ustar pat1pat100000000000000autoradio-2.8.6/doc/user_guide/en/programbook/index.html0000664000175000017500000000310613003435504023072 0ustar pat1pat100000000000000 | Django site admin

Documentation Home

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-2.8.6/doc/user_guide/en/jingle/0000775000175000017500000000000013003471473020030 5ustar pat1pat100000000000000autoradio-2.8.6/doc/user_guide/en/jingle/index.html0000664000175000017500000000327213003435504022024 0ustar pat1pat100000000000000 | Django site admin

Documentation Home

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-2.8.6/doc/user_guide/en/scheduler/0000775000175000017500000000000013003471473020536 5ustar pat1pat100000000000000autoradio-2.8.6/doc/user_guide/en/scheduler/index.html0000664000175000017500000000503313003435504022527 0ustar pat1pat100000000000000 | Django site admin

Documentation Home

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-2.8.6/doc/user_guide/en/playlist/0000775000175000017500000000000013003471473020421 5ustar pat1pat100000000000000autoradio-2.8.6/doc/user_guide/en/playlist/index.html0000664000175000017500000000405313003435504022413 0ustar pat1pat100000000000000 | Django site admin

Documentation Home

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-2.8.6/doc/user_guide/en/index.html0000664000175000017500000000507213003435504020554 0ustar pat1pat100000000000000 | Django site admin

Documentation

Autoradio overview

A global vision of Autoradio suite.

Autoradio features

What you can do with Autoradio.

Main components

The components of autoradio suite.

Autoradio player

What you can do with Autoradio.

Autoradio scheduler

Real time emission of your schedule.

User inteface

The web interface.

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

How to start to play music.

Jingle

How to start to use jingles to promote your brand.

Spot

Organize radio advertisement.

Program

Record your programs and set when it will be on air.

Podcast

How to distribute your audio on the net.

Program's book

Palimpsest for the italian law.


autoradio-2.8.6/doc/user_guide/en/player/0000775000175000017500000000000013003471473020054 5ustar pat1pat100000000000000autoradio-2.8.6/doc/user_guide/en/player/index.html0000664000175000017500000001003113003435504022037 0ustar pat1pat100000000000000 | Django site admin

Documentation Home

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à:
  • Autoplayerd: Questo player è basato su gstreamer e quindi disponibile su tutte le nuove distribuzioni. Il formato dei file audio è determinato dai plugin di gstreamer installati. Questo player non ha bisogno di un terminale per l'interazione umana. Espone una interfaccia a standard mpris2 su dbus e quindi qualsiasi applicazione che interagisca con questa interfaccia può impartire comandi al player. Autoradio fornisce autoplayergui che ha una interfaccia grafica tramite la quale è possibile eseguire tutte le funzioni elementari quali play, pause, stop etc. Il vantaggio è che un computer senza terminale quale un server può funzionare e avere questo player in modalità daemon, ossia sempre attiva. Il player permette l'invio diretto a un server per lo streaming per la realizzazione di web radio. Questo è il player preferito per l'utilizzo con AutoRadio.
  • 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.
  • 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-2.8.6/doc/user_guide/en/spot/0000775000175000017500000000000013003471473017545 5ustar pat1pat100000000000000autoradio-2.8.6/doc/user_guide/en/spot/index.html0000664000175000017500000000354313003435504021542 0ustar pat1pat100000000000000 | Django site admin

Documentation Home

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-2.8.6/doc/user_guide/en/program/0000775000175000017500000000000013003471473020227 5ustar pat1pat100000000000000autoradio-2.8.6/doc/user_guide/en/program/index.html0000664000175000017500000001406713003435504022227 0ustar pat1pat100000000000000 | Django site admin

Documentation Home

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 and iTunes URLs

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:

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

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

http://feeds.feedburner.com/TitleOfShow

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:

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

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.

Ping iTunes for new content

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:

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.

Yahoo! Media RSS feed submission

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:

Google video sitemaps

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:

Additionally, you can add the video sitemap URL to your robots.txt file:

Google allows the submission of a media RSS feed instead of the sitemap to Google Webmaster Tools if you prefer.

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.

What is the Dublin Core namespace?

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.


autoradio-2.8.6/doc/user_guide/en/overview/0000775000175000017500000000000013003471473020426 5ustar pat1pat100000000000000autoradio-2.8.6/doc/user_guide/en/overview/index.html0000664000175000017500000000562513003435504022426 0ustar pat1pat100000000000000 Page not found at /robots.txt

Page not found (404)

Request Method: GET
Request URL: http://127.0.0.1:8080/robots.txt

Using the URLconf defined in autoradio.urls, Django tried these URL patterns, in this order:

  1. ^admin/doc/
  2. ^admin/
  3. ^ ^$
  4. ^ ^xmms/$
  5. ^ ^programsbook/$
  6. ^podcasts/
  7. ^player/
  8. ^doc/
  9. ^media\/(?P<path>.*)$

The current URL, robots.txt, didn't match any of these.

You're seeing this error because you have DEBUG = True in your Django settings file. Change that to False, and Django will display a standard 404 page.

autoradio-2.8.6/doc/user_guide/en/podcast/0000775000175000017500000000000013003471473020215 5ustar pat1pat100000000000000autoradio-2.8.6/doc/user_guide/en/podcast/index.html0000664000175000017500000000461313003435504022211 0ustar pat1pat100000000000000 | Django site admin

Documentation Home

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-2.8.6/doc/user_guide/it/0000775000175000017500000000000013003471473016572 5ustar pat1pat100000000000000autoradio-2.8.6/doc/user_guide/it/feature/0000775000175000017500000000000013003471473020225 5ustar pat1pat100000000000000autoradio-2.8.6/doc/user_guide/it/feature/index.html0000664000175000017500000000432413003435505022221 0ustar pat1pat100000000000000 | Amministrazione

Indice documentazione

  • 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

autoradio-2.8.6/doc/user_guide/it/programbook/0000775000175000017500000000000013003471473021114 5ustar pat1pat100000000000000autoradio-2.8.6/doc/user_guide/it/programbook/index.html0000664000175000017500000000312413003435505023105 0ustar pat1pat100000000000000 | 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-2.8.6/doc/user_guide/it/jingle/0000775000175000017500000000000013003471473020042 5ustar pat1pat100000000000000autoradio-2.8.6/doc/user_guide/it/jingle/index.html0000664000175000017500000000331013003435505022030 0ustar pat1pat100000000000000 | 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-2.8.6/doc/user_guide/it/scheduler/0000775000175000017500000000000013003471473020550 5ustar pat1pat100000000000000autoradio-2.8.6/doc/user_guide/it/scheduler/index.html0000664000175000017500000000505113003435505022542 0ustar pat1pat100000000000000 | 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-2.8.6/doc/user_guide/it/playlist/0000775000175000017500000000000013003471473020433 5ustar pat1pat100000000000000autoradio-2.8.6/doc/user_guide/it/playlist/index.html0000664000175000017500000000407113003435505022426 0ustar pat1pat100000000000000 | 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-2.8.6/doc/user_guide/it/index.html0000664000175000017500000000523113003435505020564 0ustar pat1pat100000000000000 | 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-2.8.6/doc/user_guide/it/player/0000775000175000017500000000000013003471473020066 5ustar pat1pat100000000000000autoradio-2.8.6/doc/user_guide/it/player/index.html0000664000175000017500000001004713003435505022061 0ustar pat1pat100000000000000 | 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à:
  • Autoplayerd: Questo player è basato su gstreamer e quindi disponibile su tutte le nuove distribuzioni. Il formato dei file audio è determinato dai plugin di gstreamer installati. Questo player non ha bisogno di un terminale per l'interazione umana. Espone una interfaccia a standard mpris2 su dbus e quindi qualsiasi applicazione che interagisca con questa interfaccia può impartire comandi al player. Autoradio fornisce autoplayergui che ha una interfaccia grafica tramite la quale è possibile eseguire tutte le funzioni elementari quali play, pause, stop etc. Il vantaggio è che un computer senza terminale quale un server può funzionare e avere questo player in modalità daemon, ossia sempre attiva. Il player permette l'invio diretto a un server per lo streaming per la realizzazione di web radio. Questo è il player preferito per l'utilizzo con AutoRadio.
  • 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.
  • 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-2.8.6/doc/user_guide/it/spot/0000775000175000017500000000000013003471473017557 5ustar pat1pat100000000000000autoradio-2.8.6/doc/user_guide/it/spot/index.html0000664000175000017500000000356113003435505021555 0ustar pat1pat100000000000000 | 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-2.8.6/doc/user_guide/it/program/0000775000175000017500000000000013003471473020241 5ustar pat1pat100000000000000autoradio-2.8.6/doc/user_guide/it/program/index.html0000664000175000017500000001457013003435505022241 0ustar pat1pat100000000000000 | 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-2.8.6/doc/user_guide/it/overview/0000775000175000017500000000000013003471473020440 5ustar pat1pat100000000000000autoradio-2.8.6/doc/user_guide/it/overview/index.html0000664000175000017500000000406613003435505022437 0ustar pat1pat100000000000000 | Amministrazione

Indice documentazione

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 (gstreamer): 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

autoradio-2.8.6/doc/user_guide/it/podcast/0000775000175000017500000000000013003471473020227 5ustar pat1pat100000000000000autoradio-2.8.6/doc/user_guide/it/podcast/index.html0000664000175000017500000000463113003435505022224 0ustar pat1pat100000000000000 | 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-2.8.6/doc/user_guide/generate.sh0000664000175000017500000000050313001105756020276 0ustar pat1pat100000000000000rm -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-2.8.6/doc/programs.png0000664000175000017500000063264613001105756016400 0ustar pat1pat100000000000000PNG  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-2.8.6/doc/playlists.png0000664000175000017500000007571713001105756016572 0ustar pat1pat100000000000000PNG  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-2.8.6/locale/0000775000175000017500000000000013003471473014515 5ustar pat1pat100000000000000autoradio-2.8.6/locale/en/0000775000175000017500000000000013003471473015117 5ustar pat1pat100000000000000autoradio-2.8.6/locale/en/LC_MESSAGES/0000775000175000017500000000000013003471473016704 5ustar pat1pat100000000000000autoradio-2.8.6/locale/en/LC_MESSAGES/django.po0000664000175000017500000000113613003130141020467 0ustar pat1pat100000000000000# Translations template for autoradio. # Copyright (C) 2016 ORGANIZATION # This file is distributed under the same license as the autoradio project. # FIRST AUTHOR , 2016. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: autoradio 2.8.5\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "POT-Creation-Date: 2016-10-23 14:43+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 0.9.6\n" autoradio-2.8.6/locale/it/0000775000175000017500000000000013003471473015131 5ustar pat1pat100000000000000autoradio-2.8.6/locale/it/LC_MESSAGES/0000775000175000017500000000000013003471473016716 5ustar pat1pat100000000000000autoradio-2.8.6/locale/it/LC_MESSAGES/django.po0000664000175000017500000020202113001105756020511 0ustar pat1pat100000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2011-08-28 09:03+0200\n" "PO-Revision-Date: 2011-08-28 09:48+0200\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: autoradio/jingles/models.py:55 autoradio/playlists/models.py:56 #: autoradio/programs/models.py:253 autoradio/spots/models.py:57 #: build/lib/autoradio/jingles/models.py:55 #: build/lib/autoradio/playlists/models.py:56 #: build/lib/autoradio/programs/models.py:253 #: build/lib/autoradio/spots/models.py:57 msgid "weekday name" msgstr "giorno della settimana" #: autoradio/jingles/models.py:66 build/lib/autoradio/jingles/models.py:66 msgid "Activate Jingle" msgstr "Attivazione jingle" #: autoradio/jingles/models.py:67 build/lib/autoradio/jingles/models.py:67 msgid "activate/deactivate the intere jingle class" msgstr "attivazione/disattivazione dell'intera classe jingle" #: autoradio/jingles/models.py:68 build/lib/autoradio/jingles/models.py:68 msgid "Frequency" msgstr "Frequenza" #: autoradio/jingles/models.py:80 build/lib/autoradio/jingles/models.py:80 msgid "Jingle name" msgstr "Nome jingle" #: autoradio/jingles/models.py:81 autoradio/playlists/models.py:80 #: autoradio/spots/models.py:160 build/lib/autoradio/jingles/models.py:81 #: build/lib/autoradio/playlists/models.py:80 #: build/lib/autoradio/spots/models.py:160 templates/schedule/index.html:15 msgid "File" msgstr "File" #: autoradio/jingles/models.py:82 build/lib/autoradio/jingles/models.py:82 msgid "The jingle file to upload" msgstr "Il file con il jingle da caricare" #: autoradio/jingles/models.py:83 autoradio/programs/models.py:543 #: build/lib/autoradio/jingles/models.py:83 #: build/lib/autoradio/programs/models.py:547 msgid "Recording date" msgstr "Data registrazione" #: autoradio/jingles/models.py:84 build/lib/autoradio/jingles/models.py:84 msgid "When the jingle was done (for reference only)" msgstr "Quando il jingle è stata fatto (solo per riferimento)" #: autoradio/jingles/models.py:85 autoradio/playlists/models.py:84 #: autoradio/programs/models.py:304 autoradio/programs/models.py:542 #: autoradio/spots/models.py:122 autoradio/spots/models.py:164 #: build/lib/autoradio/jingles/models.py:85 #: build/lib/autoradio/playlists/models.py:84 #: build/lib/autoradio/programs/models.py:308 #: build/lib/autoradio/programs/models.py:546 #: build/lib/autoradio/spots/models.py:122 #: build/lib/autoradio/spots/models.py:164 msgid "Active" msgstr "Attivo" #: autoradio/jingles/models.py:86 build/lib/autoradio/jingles/models.py:86 msgid "Activate the jingle for emission" msgstr "Attiva il jingle per la messa in onda" #: autoradio/jingles/models.py:87 build/lib/autoradio/jingles/models.py:87 msgid "Emission starting date" msgstr "Data inizio emissione" #: autoradio/jingles/models.py:88 autoradio/jingles/models.py:92 #: build/lib/autoradio/jingles/models.py:88 #: build/lib/autoradio/jingles/models.py:92 msgid "The jingle will be scheduled starting from this date" msgstr "Il jingle potrrà essere programmato a partire da questa data e ora" #: autoradio/jingles/models.py:89 build/lib/autoradio/jingles/models.py:89 msgid "Emission end date" msgstr "Data finale emissione" #: autoradio/jingles/models.py:90 autoradio/jingles/models.py:94 #: build/lib/autoradio/jingles/models.py:90 #: build/lib/autoradio/jingles/models.py:94 msgid "The jingle will be scheduled ending this date" msgstr "Il jingle sarà programmato fino a questa data" #: autoradio/jingles/models.py:91 build/lib/autoradio/jingles/models.py:91 msgid "Emission start time" msgstr "Ora iniziale emissione" #: autoradio/jingles/models.py:93 build/lib/autoradio/jingles/models.py:93 msgid "Emission end time" msgstr "Ora finale emissione" #: autoradio/jingles/models.py:95 build/lib/autoradio/jingles/models.py:95 msgid "Scheduled days" msgstr "Giorni programmati" #: autoradio/jingles/models.py:96 build/lib/autoradio/jingles/models.py:96 msgid "The jingle will be scheduled those weekdays" msgstr "Il jingle sarà programmato per questi giorni" #: autoradio/jingles/models.py:97 autoradio/spots/models.py:178 #: build/lib/autoradio/jingles/models.py:97 #: build/lib/autoradio/spots/models.py:178 msgid "Priority" msgstr "Priorità" #: autoradio/jingles/models.py:98 build/lib/autoradio/jingles/models.py:98 msgid "" "When there are more jingle that wait for emission from the same time, the " "emission will be ordered by this numer" msgstr "" "Quando ci sono jingle che attendono di essere emessi dallo stesso tempo, " "l'ordine di emissione è determinato da questo numero" #: autoradio/jingles/models.py:99 autoradio/programs/models.py:733 #: build/lib/autoradio/jingles/models.py:99 #: build/lib/autoradio/programs/models.py:737 msgid "emission done" msgstr "emissione effettuata" #: autoradio/jingles/models.py:105 autoradio/programs/models.py:610 #: autoradio/spots/models.py:188 build/lib/autoradio/jingles/models.py:105 #: build/lib/autoradio/programs/models.py:614 #: build/lib/autoradio/spots/models.py:188 msgid "Recorded today?" msgstr "Registrato oggi?" #: autoradio/playlists/models.py:64 build/lib/autoradio/playlists/models.py:64 msgid "Activate Playlist" msgstr "Playlist attive" #: autoradio/playlists/models.py:65 build/lib/autoradio/playlists/models.py:65 msgid "activate/deactivate the intere playlist class" msgstr "attivazione/disattivazione dell'intera classe playlist" #: autoradio/playlists/models.py:66 autoradio/playlists/models.py:68 #: autoradio/programs/models.py:264 autoradio/spots/models.py:103 #: build/lib/autoradio/playlists/models.py:66 #: build/lib/autoradio/playlists/models.py:68 #: build/lib/autoradio/programs/models.py:264 #: build/lib/autoradio/spots/models.py:103 msgid "Programmed start time" msgstr "Ora inizio programmazione" #: autoradio/playlists/models.py:67 build/lib/autoradio/playlists/models.py:67 msgid "The start time from wich the playlist will be active" msgstr "ora da cui la playlist sarà attiva" #: autoradio/playlists/models.py:69 build/lib/autoradio/playlists/models.py:69 msgid "The end time the playlist will be active" msgstr "ora a cui la playlist sarà disattivata" #: autoradio/playlists/models.py:79 build/lib/autoradio/playlists/models.py:79 msgid "Playlist name" msgstr "Nome Playlist" #: autoradio/playlists/models.py:81 build/lib/autoradio/playlists/models.py:81 msgid "The playlist file to upload, format should be extm3u, m3u, pls" msgstr "Il file della playlist da caricare, formati possibili extm3u, m3u, pls" #: autoradio/playlists/models.py:82 build/lib/autoradio/playlists/models.py:82 msgid "Generation date" msgstr "Data generazione" #: autoradio/playlists/models.py:83 build/lib/autoradio/playlists/models.py:83 msgid "When the playlist was done (for reference only)" msgstr "Quando la playlist è stata fatta (solo per riferimento)" #: autoradio/playlists/models.py:85 build/lib/autoradio/playlists/models.py:85 msgid "Activate the playlist for emission" msgstr "Attiva le playlist per la messa in onda" #: autoradio/playlists/models.py:91 build/lib/autoradio/playlists/models.py:91 msgid "Generated today?" msgstr "Registrato oggi?" #: autoradio/playlists/models.py:103 autoradio/playlists/models.py:143 #: build/lib/autoradio/playlists/models.py:103 #: build/lib/autoradio/playlists/models.py:143 msgid "refer to playlist:" msgstr "riferito alla playlist" #: autoradio/playlists/models.py:105 autoradio/playlists/models.py:145 #: build/lib/autoradio/playlists/models.py:105 #: build/lib/autoradio/playlists/models.py:145 msgid "Shuffle Playlist on start" msgstr "Disordina la playlist alla partenza" #: autoradio/playlists/models.py:106 autoradio/playlists/models.py:146 #: build/lib/autoradio/playlists/models.py:106 #: build/lib/autoradio/playlists/models.py:146 msgid "" "Every time the playlist will be scheduled it's order will be randomly changed" msgstr "" "Ogni volta che la playlist sarà programmata il suo ordine sarà cambiato in " "modo casuale" #: autoradio/playlists/models.py:107 autoradio/playlists/models.py:147 #: build/lib/autoradio/playlists/models.py:107 #: build/lib/autoradio/playlists/models.py:147 msgid "Max time length (seconds)" msgstr "Durata massima (secondi)" #: autoradio/playlists/models.py:108 autoradio/playlists/models.py:148 #: build/lib/autoradio/playlists/models.py:108 #: build/lib/autoradio/playlists/models.py:148 msgid "If this time is set the playlist will be truncated" msgstr "Se questo tempo sarà impostato la playlist sarà troncata" #: autoradio/playlists/models.py:109 autoradio/programs/models.py:770 #: build/lib/autoradio/playlists/models.py:109 #: build/lib/autoradio/programs/models.py:774 msgid "Programmed date" msgstr "Data programmata" #: autoradio/playlists/models.py:110 #: build/lib/autoradio/playlists/models.py:110 msgid "This is the date and time when the playlist will be on air" msgstr "Questa è la data e l'ora alle quali la playlist sarà mandata in onda" #: autoradio/playlists/models.py:118 autoradio/playlists/models.py:157 #: autoradio/spots/models.py:124 build/lib/autoradio/playlists/models.py:118 #: build/lib/autoradio/playlists/models.py:157 #: build/lib/autoradio/spots/models.py:124 msgid "Emission done" msgstr "Emissione effettuata" #: autoradio/playlists/models.py:128 autoradio/programs/models.py:776 #: build/lib/autoradio/playlists/models.py:128 #: build/lib/autoradio/programs/models.py:780 msgid "Programmed for today?" msgstr "Programmato per oggi?" #: autoradio/playlists/models.py:132 autoradio/playlists/models.py:166 #: build/lib/autoradio/playlists/models.py:132 #: build/lib/autoradio/playlists/models.py:166 msgid "Linked Playlist" msgstr "Playlist correlata" #: autoradio/playlists/models.py:149 autoradio/programs/models.py:749 #: build/lib/autoradio/playlists/models.py:149 #: build/lib/autoradio/programs/models.py:753 msgid "Programmed start date" msgstr "Data inizio programmazione" #: autoradio/playlists/models.py:150 #: build/lib/autoradio/playlists/models.py:150 msgid "The playlist will be scheduled starting from this date" msgstr "La playlist potrrà essere programmata a partire da questa data e ora" #: autoradio/playlists/models.py:151 autoradio/programs/models.py:751 #: build/lib/autoradio/playlists/models.py:151 #: build/lib/autoradio/programs/models.py:755 msgid "Programmed end date" msgstr "Data fine programmazione" #: autoradio/playlists/models.py:152 #: build/lib/autoradio/playlists/models.py:152 msgid "The playlist will be scheduled ending this date" msgstr "La playlist sarà programmata fino a questa data" #: autoradio/playlists/models.py:153 autoradio/programs/models.py:753 #: build/lib/autoradio/playlists/models.py:153 #: build/lib/autoradio/programs/models.py:757 msgid "Programmed time" msgstr "Ora programmata" #: autoradio/playlists/models.py:154 #: build/lib/autoradio/playlists/models.py:154 msgid "This is the time when the playlist will be on air" msgstr "Questa è l'ora alle quali la playlist sarà mandata in onda" #: autoradio/playlists/models.py:155 autoradio/programs/models.py:755 #: autoradio/spots/models.py:173 build/lib/autoradio/playlists/models.py:155 #: build/lib/autoradio/programs/models.py:759 #: build/lib/autoradio/spots/models.py:173 msgid "Programmed days" msgstr "Giorni programmati" #: autoradio/playlists/models.py:156 #: build/lib/autoradio/playlists/models.py:156 msgid "The playlist will be scheduled those weekdays" msgstr "La playlist sarà programmata per questi giorni" #: autoradio/programs/models.py:94 autoradio/programs/models.py:126 #: build/lib/autoradio/programs/models.py:94 #: build/lib/autoradio/programs/models.py:126 msgid "" "A slug is a URL-friendly nickname. For example, a slug for \"Games & " "Hobbies\" is \"games-hobbies\"." msgstr "" #: autoradio/programs/models.py:125 build/lib/autoradio/programs/models.py:125 msgid "" "After saving this parent category, please map it to one or more Child " "Categories below." msgstr "" #: autoradio/programs/models.py:227 build/lib/autoradio/programs/models.py:227 msgid "" "Please choose a child category that corresponds to its respective parent " "category (e.g., \"Design\" is a child category of \"Arts\").
If no such " "child category exists for a parent category (e.g., Comedy, Kids & Family, " "Music, News & Politics, or TV & Film), simply leave this blank and save." msgstr "" #: autoradio/programs/models.py:228 build/lib/autoradio/programs/models.py:228 msgid "" "A slug is a URL-friendly nickname. For exmaple, a slug for \"Fashion & " "Beauty\" is \"fashion-beauty\"." msgstr "" #: autoradio/programs/models.py:262 build/lib/autoradio/programs/models.py:262 msgid "Active show" msgstr "Show attivo" #: autoradio/programs/models.py:263 build/lib/autoradio/programs/models.py:263 msgid "activate/deactivate the intere program class" msgstr "attivazione/disattivazione dell'intera classe programmi" #: autoradio/programs/models.py:265 msgid "The start time from wich the programs will be active" msgstr "ora da cui i programmi saranno attivi" #: autoradio/programs/models.py:266 autoradio/spots/models.py:105 #: build/lib/autoradio/programs/models.py:266 #: build/lib/autoradio/spots/models.py:105 msgid "Programmed end time" msgstr "Ora fine programmazione" #: autoradio/programs/models.py:267 msgid "The end time the programs will be active" msgstr "ora a cui i programmi saranno disattivati" #: autoradio/programs/models.py:271 msgid "The station name for the print of programs book" msgstr "Il nome della emittente per la stampa del libro programmi" #: autoradio/programs/models.py:273 msgid "The station channel for the print of programs book" msgstr "Il canale della emittente per la stampa del libro programmi" #: autoradio/programs/models.py:275 msgid "The station kind of emission for the print of programs book" msgstr "Il tipo di emittente per la stampa del libro programmi" #: autoradio/programs/models.py:277 msgid "The station type for the print of programs book" msgstr "Il tipo della emittente per la stampa del libro programmi" #: autoradio/programs/models.py:287 build/lib/autoradio/programs/models.py:291 msgid "Code" msgstr "Codice" #: autoradio/programs/models.py:288 build/lib/autoradio/programs/models.py:292 #: templates/schedule/index.html:15 msgid "Type" msgstr "Tipo" #: autoradio/programs/models.py:289 build/lib/autoradio/programs/models.py:293 msgid "SubType" msgstr "Sotto Tipo" #: autoradio/programs/models.py:290 build/lib/autoradio/programs/models.py:294 msgid "Description" msgstr "Descrizione" #: autoradio/programs/models.py:296 build/lib/autoradio/programs/models.py:300 msgid "autoproduction" msgstr "autoproduzione" #: autoradio/programs/models.py:296 build/lib/autoradio/programs/models.py:300 msgid "eteroproduction" msgstr "eteroproduzione" #: autoradio/programs/models.py:303 build/lib/autoradio/programs/models.py:307 msgid "show title" msgstr "Titolo dello show" #: autoradio/programs/models.py:304 build/lib/autoradio/programs/models.py:308 msgid "Activate the show for emission" msgstr "Attiva lo show per la messa in onda" #: autoradio/programs/models.py:305 autoradio/programs/models.py:546 #: build/lib/autoradio/programs/models.py:309 #: build/lib/autoradio/programs/models.py:550 msgid "Auto-generated from Title." msgstr "auto generato dal titolo" #: autoradio/programs/models.py:306 build/lib/autoradio/programs/models.py:310 msgid "Time length (seconds)" msgstr "Durata (secondi)" #: autoradio/programs/models.py:306 build/lib/autoradio/programs/models.py:310 msgid "Time lenght how you want to see it in the palimpsest" msgstr "Durata dello show come dovrà comparire nel palinsesto" #: autoradio/programs/models.py:307 build/lib/autoradio/programs/models.py:311 msgid "Program Type" msgstr "Tipo del programma" #: autoradio/programs/models.py:307 build/lib/autoradio/programs/models.py:311 msgid "" "The categorization that follow the italian law (you have to use it to " "produce the programs book" msgstr "La categorizzazione dettata dalla legislazione italiana" #: autoradio/programs/models.py:309 build/lib/autoradio/programs/models.py:313 msgid "Production" msgstr "Produttore" #: autoradio/programs/models.py:309 build/lib/autoradio/programs/models.py:313 msgid "The type of production" msgstr "Il tipo di produzione" #: autoradio/programs/models.py:326 build/lib/autoradio/programs/models.py:330 msgid "Name of the organization, company or Web site producing the podcast." msgstr "" #: autoradio/programs/models.py:327 build/lib/autoradio/programs/models.py:331 msgid "" "URL of either the main website or the podcast section of the main website." msgstr "" #: autoradio/programs/models.py:328 build/lib/autoradio/programs/models.py:332 msgid "" "Describe subject matter, media format, episode schedule and other relevant " "information while incorporating keywords." msgstr "" #: autoradio/programs/models.py:329 build/lib/autoradio/programs/models.py:333 msgid "" "Default is American English. See ISO 639-1 and ISO 3166-1 for more language codes." msgstr "" #: autoradio/programs/models.py:330 build/lib/autoradio/programs/models.py:334 msgid "" "See Creative Commons " "licenses for more information." msgstr "" #: autoradio/programs/models.py:331 build/lib/autoradio/programs/models.py:335 msgid "" "A URL pointing to additional copyright information. Consider a Creative Commons license URL." msgstr "" #: autoradio/programs/models.py:332 build/lib/autoradio/programs/models.py:336 msgid "" "Remember to save the user's name and e-mail address in the User application.
" msgstr "" #: autoradio/programs/models.py:333 autoradio/programs/models.py:547 #: build/lib/autoradio/programs/models.py:337 #: build/lib/autoradio/programs/models.py:551 msgid "" "Remember to save the user's name and e-mail address in the User application." msgstr "" #: autoradio/programs/models.py:334 autoradio/programs/models.py:551 #: build/lib/autoradio/programs/models.py:338 #: build/lib/autoradio/programs/models.py:555 msgid "Limited to one user-specified category for the sake of sanity." msgstr "" #: autoradio/programs/models.py:335 autoradio/programs/models.py:552 #: build/lib/autoradio/programs/models.py:339 #: build/lib/autoradio/programs/models.py:556 msgid "A URL that identifies a categorization taxonomy." msgstr "" #: autoradio/programs/models.py:336 build/lib/autoradio/programs/models.py:340 msgid "" "\"Time to Live,\" the number of minutes a channel can be cached before " "refreshing." msgstr "" #: autoradio/programs/models.py:337 build/lib/autoradio/programs/models.py:341 msgid "" "An attractive, original square JPEG (.jpg) or PNG (.png) image of 600x600 " "pixels. Image will be scaled down to 50x50 pixels at smallest in iTunes." msgstr "" #: autoradio/programs/models.py:338 build/lib/autoradio/programs/models.py:342 msgid "" "Fill this out after saving this show and at least one episode. URL should " "look like \"http://feeds.feedburner.com/TitleOfShow\". See documentation for more." msgstr "" #: autoradio/programs/models.py:340 build/lib/autoradio/programs/models.py:344 msgid "Looks best if only a few words, like a tagline." msgstr "" #: autoradio/programs/models.py:341 autoradio/programs/models.py:559 #: build/lib/autoradio/programs/models.py:345 #: build/lib/autoradio/programs/models.py:563 msgid "Allows 4,000 characters. Description will be used if summary is blank." msgstr "" #: autoradio/programs/models.py:342 build/lib/autoradio/programs/models.py:346 msgid "" "If selecting a category group with no child category (e.g., Comedy, Kids & " "Family, Music, News & Politics or TV & Film), save that parent category with " "a blank child category.
Selecting " "multiple category groups makes the podcast more likely to be found by users." "
" msgstr "" #: autoradio/programs/models.py:343 autoradio/programs/models.py:563 #: build/lib/autoradio/programs/models.py:347 #: build/lib/autoradio/programs/models.py:567 msgid "\"Clean\" will put the clean iTunes graphic by it." msgstr "" #: autoradio/programs/models.py:344 build/lib/autoradio/programs/models.py:348 msgid "" "Check to block this show from iTunes.
Show will remain blocked until " "unchecked." msgstr "" #: autoradio/programs/models.py:345 build/lib/autoradio/programs/models.py:349 msgid "" "The show's new URL feed if changing the URL of the current show feed. Must " "continue old feed for at least two weeks and write a 301 redirect for old " "feed." msgstr "" #: autoradio/programs/models.py:346 build/lib/autoradio/programs/models.py:350 msgid "" "A comma-demlimited list of up to 12 words for iTunes searches. Perhaps " "include misspellings of the title." msgstr "" #: autoradio/programs/models.py:347 build/lib/autoradio/programs/models.py:351 msgid "" "Fill this out after saving this show and at least one episode. URL should " "look like \"http://phobos.apple.com/WebObjects/MZStore.woa/wa/viewPodcast?" "id=000000000\". See documentation for more." msgstr "" #: autoradio/programs/models.py:541 build/lib/autoradio/programs/models.py:545 msgid "" "Make it specific but avoid explicit language. Limit to 100 characters for a " "Google video sitemap." msgstr "" #: autoradio/programs/models.py:549 build/lib/autoradio/programs/models.py:553 msgid "Avoid explicit language. Google video sitempas allow 2,048 characters." msgstr "" #: autoradio/programs/models.py:550 build/lib/autoradio/programs/models.py:554 msgid "" "For video podcasts. Good captioning choices include SubViewer, SubRip or TimedText." msgstr "" #: autoradio/programs/models.py:553 build/lib/autoradio/programs/models.py:557 msgid "The frequency with which the episode's data changes. For sitemaps." msgstr "" #: autoradio/programs/models.py:554 build/lib/autoradio/programs/models.py:558 msgid "" "The relative priority of this episode compared to others. 1.0 is the most " "important. For sitemaps." msgstr "" #: autoradio/programs/models.py:558 build/lib/autoradio/programs/models.py:562 msgid "Looks best if only a few words like a tagline." msgstr "" #: autoradio/programs/models.py:562 build/lib/autoradio/programs/models.py:566 msgid "" "A comma-delimited list of words for searches, up to 12; perhaps include " "misspellings." msgstr "" #: autoradio/programs/models.py:564 build/lib/autoradio/programs/models.py:568 msgid "" "Check to block this episode from iTunes because
its content might " "cause the entire show to be
removed from iTunes." msgstr "" #: autoradio/programs/models.py:566 build/lib/autoradio/programs/models.py:570 msgid "" "Role codes provided by the European Broadcasting Union." msgstr "" #: autoradio/programs/models.py:569 build/lib/autoradio/programs/models.py:573 msgid "If used, selection must match respective Scheme selection." msgstr "" #: autoradio/programs/models.py:570 build/lib/autoradio/programs/models.py:574 msgid "" "A still image from a video file, but for episode artwork to display in " "iTunes, image must be saved to file's metadata " "before episode uploading!" msgstr "" #: autoradio/programs/models.py:571 build/lib/autoradio/programs/models.py:575 msgid "" "Media RSS text transcript. Must use tags. Please see the Media RSS 2.0 specification for syntax." msgstr "" #: autoradio/programs/models.py:572 build/lib/autoradio/programs/models.py:576 msgid "Check to deny episode to be shown to users from specified countries." msgstr "" #: autoradio/programs/models.py:573 build/lib/autoradio/programs/models.py:577 msgid "" "A space-delimited list of ISO 3166-1-coded countries." msgstr "" #: autoradio/programs/models.py:575 build/lib/autoradio/programs/models.py:579 msgid "Start date and time that the media is valid." msgstr "" #: autoradio/programs/models.py:576 build/lib/autoradio/programs/models.py:580 msgid "End date and time that the media is valid." msgstr "" #: autoradio/programs/models.py:578 build/lib/autoradio/programs/models.py:582 msgid "Any helper name to distinguish this time period." msgstr "" #: autoradio/programs/models.py:580 build/lib/autoradio/programs/models.py:584 msgid "" "Check to allow Google to show a preview of your media in search results." msgstr "" #: autoradio/programs/models.py:581 build/lib/autoradio/programs/models.py:585 msgid "" "Start time (minutes) of the media's preview,
shown on Google.com " "search results before
clicking through to see full video." msgstr "" #: autoradio/programs/models.py:582 build/lib/autoradio/programs/models.py:586 msgid "Start time (seconds) of the media's preview." msgstr "" #: autoradio/programs/models.py:583 build/lib/autoradio/programs/models.py:587 msgid "" "End time (minutes) of the media's preview,
shown on Google.com search " "results before
clicking through to see full video." msgstr "" #: autoradio/programs/models.py:584 build/lib/autoradio/programs/models.py:588 msgid "End time (seconds) of the media's preview." msgstr "" #: autoradio/programs/models.py:585 build/lib/autoradio/programs/models.py:589 msgid "" "Check to allow Google to host your media after it expires. Must set " "expiration date in Dublin Core." msgstr "" #: autoradio/programs/models.py:671 build/lib/autoradio/programs/models.py:675 msgid "Title is generally only useful with multiple enclosures." msgstr "" #: autoradio/programs/models.py:672 build/lib/autoradio/programs/models.py:676 msgid "" "Either upload or use the \"Player\" text box below. If uploading, file must " "be less than or equal to 30 MB for a Google video sitemap." msgstr "" #: autoradio/programs/models.py:676 build/lib/autoradio/programs/models.py:680 msgid "Measured in frames per second (fps), often 29.97." msgstr "" #: autoradio/programs/models.py:677 build/lib/autoradio/programs/models.py:681 msgid "Measured in kilobits per second (kbps), often 128 or 192." msgstr "" #: autoradio/programs/models.py:678 build/lib/autoradio/programs/models.py:682 msgid "Measured in kilohertz (kHz), often 44.1." msgstr "" #: autoradio/programs/models.py:679 build/lib/autoradio/programs/models.py:683 msgid "Number of channels; 2 for stereo, 1 for mono." msgstr "" #: autoradio/programs/models.py:681 build/lib/autoradio/programs/models.py:685 msgid "MD-5 or SHA-1 file hash." msgstr "" #: autoradio/programs/models.py:682 build/lib/autoradio/programs/models.py:686 msgid "" "URL of the player console that plays the media. Could be your own .swf, but " "most likely a YouTube URL, such as http://www.youtube.com/v/UZCfK8pVztw (not the permalink, " "which looks like http://www.youtube.com/watch?v=UZCfK8pVztw)." msgstr "" #: autoradio/programs/models.py:683 build/lib/autoradio/programs/models.py:687 msgid "" "Check to allow Google to embed your external player in search results on Google Video." msgstr "" #: autoradio/programs/models.py:684 build/lib/autoradio/programs/models.py:688 msgid "" "Width of the browser window in
which the URL should be opened.
YouTube's default is 425." msgstr "" #: autoradio/programs/models.py:685 build/lib/autoradio/programs/models.py:689 msgid "" "Height of the browser window in
which the URL should be opened.
YouTube's default is 344." msgstr "" #: autoradio/programs/models.py:686 build/lib/autoradio/programs/models.py:690 msgid "" "Include any number of media files; for example, perhaps include an iPhone-" "optimized, AppleTV-optimized and Flash Video set of video files. Note that " "the iTunes feed only accepts the first file. More uploading is available " "after clicking \"Save and continue editing.\"" msgstr "" #: autoradio/programs/models.py:701 autoradio/programs/models.py:718 #: build/lib/autoradio/programs/models.py:705 #: build/lib/autoradio/programs/models.py:722 msgid "Linked episode:" msgstr "Programma correlato" #: autoradio/programs/models.py:703 build/lib/autoradio/programs/models.py:707 msgid "programmed date" msgstr "data programmata" #: autoradio/programs/models.py:704 build/lib/autoradio/programs/models.py:708 msgid "This is the date and time when the program will be on air" msgstr "Questa è la data e l'ora alle quali il programma sarà mandato in onda" #: autoradio/programs/models.py:714 build/lib/autoradio/programs/models.py:718 msgid "Scheduled for today?" msgstr "Programmato per oggi?" #: autoradio/programs/models.py:728 build/lib/autoradio/programs/models.py:732 msgid "Linked schedule:" msgstr "Programmazione collegata" #: autoradio/programs/models.py:731 build/lib/autoradio/programs/models.py:735 msgid "Linked enclosure:" msgstr "Parti correlate" #: autoradio/programs/models.py:747 build/lib/autoradio/programs/models.py:751 msgid "refer to show:" msgstr "riferito allo show" #: autoradio/programs/models.py:750 build/lib/autoradio/programs/models.py:754 msgid "The program will be in palimpsest starting from this date" msgstr "Lo show sarà in palinsesto a partire da questa data e ora" #: autoradio/programs/models.py:752 build/lib/autoradio/programs/models.py:756 msgid "The program will be in palimpsest ending this date" msgstr "Lo show sarà in palinseto fino a questa data" #: autoradio/programs/models.py:754 build/lib/autoradio/programs/models.py:758 msgid "This is the time when the program is planned in palimpsest" msgstr "Questa è l'ora alla quale lo show è previsto in palinsesto" #: autoradio/programs/models.py:756 build/lib/autoradio/programs/models.py:760 msgid "The program will be in palimpsest those weekdays" msgstr "Lo show sarà in palinsesto per questi giorni" #: autoradio/programs/models.py:768 build/lib/autoradio/programs/models.py:772 msgid "refer to Show:" msgstr "riferito allo Show" #: autoradio/programs/models.py:771 build/lib/autoradio/programs/models.py:775 msgid "This is the date and time when the program is planned in palimsest" msgstr "Questa è la data e l'ora alle quali lo show è previsto in palinsesto" #: autoradio/programs/views.py:80 build/lib/autoradio/programs/views.py:80 msgid "Starting date & time" msgstr "Data e ora iniziale" #: autoradio/programs/views.py:80 build/lib/autoradio/programs/views.py:80 msgid "Elaborate palimpsest starting from this date and time" msgstr "Elabora il palinsesto iniziando da questa data e ora" #: autoradio/programs/views.py:82 build/lib/autoradio/programs/views.py:82 msgid "Ending date & time" msgstr "Data e ora finale" #: autoradio/programs/views.py:82 build/lib/autoradio/programs/views.py:82 msgid "Elaborate palimpsest ending to this date and time" msgstr "Elabor ail palinsesto terminando questa data e ora" #: autoradio/spots/models.py:101 build/lib/autoradio/spots/models.py:101 msgid "Activate Spot" msgstr "Attivare Spot" #: autoradio/spots/models.py:102 build/lib/autoradio/spots/models.py:102 msgid "activate/deactivate the intere spot class" msgstr "attivazione/disattivazione dell'intera classe spot" #: autoradio/spots/models.py:104 build/lib/autoradio/spots/models.py:104 msgid "The start time from wich the spot will be active" msgstr "ora da cui lo spot sarà attivo" #: autoradio/spots/models.py:106 build/lib/autoradio/spots/models.py:106 msgid "The end time the spot will be active" msgstr "ora a cui lo spot sarà disattivato" #: autoradio/spots/models.py:119 build/lib/autoradio/spots/models.py:119 msgid "The name of commercial break" msgstr "Nome della fascia pubblicitaria" #: autoradio/spots/models.py:121 build/lib/autoradio/spots/models.py:121 msgid "This is the date and time when the commercial break will be on air" msgstr "" "Questa è la data e l'ora alle quali la fascia pubblitaria sarà mandata in " "onda" #: autoradio/spots/models.py:123 build/lib/autoradio/spots/models.py:123 msgid "Activate the commercial break for emission" msgstr "Attiva la fascia pubblicitaria per la messa in onda" #: autoradio/spots/models.py:158 build/lib/autoradio/spots/models.py:158 msgid "Spot Name" msgstr "Nome Spot" #: autoradio/spots/models.py:159 build/lib/autoradio/spots/models.py:159 msgid "The name of the spot" msgstr "Il nome dello spot" #: autoradio/spots/models.py:161 build/lib/autoradio/spots/models.py:161 msgid "The spot file to upload" msgstr "Il file con lo spot da caricare" #: autoradio/spots/models.py:162 build/lib/autoradio/spots/models.py:162 msgid "Record date" msgstr "Data registrazione" #: autoradio/spots/models.py:163 build/lib/autoradio/spots/models.py:163 msgid "When the spot was done (for reference only)" msgstr "Quando lo spot è stato fatto (solo per riferimento)" #: autoradio/spots/models.py:165 build/lib/autoradio/spots/models.py:165 msgid "Activate the spot for emission" msgstr "Attiva lo spot per la messa in onda" #: autoradio/spots/models.py:166 build/lib/autoradio/spots/models.py:166 msgid "Programmed starting date" msgstr "Data inizio programmazione" #: autoradio/spots/models.py:167 build/lib/autoradio/spots/models.py:167 msgid "The spot will be scheduled starting from this date" msgstr "Lo spot potrrà essere programmato a partire da questa data e ora" #: autoradio/spots/models.py:168 build/lib/autoradio/spots/models.py:168 msgid "Programmed ending date" msgstr "Data fine programmazione" #: autoradio/spots/models.py:169 build/lib/autoradio/spots/models.py:169 msgid "The spot will be scheduled ending this date" msgstr "Lo spot sarà programmato fino a questa data" #: autoradio/spots/models.py:174 build/lib/autoradio/spots/models.py:174 msgid "The spot will be scheduled those weekdays" msgstr "Lo spot sarà programmato per questi giorni" #: autoradio/spots/models.py:176 build/lib/autoradio/spots/models.py:176 msgid "The spot will be included in those commercial break" msgstr "Lo spot sarà incluso in queste fascie pubblicitarie" #: autoradio/spots/models.py:179 build/lib/autoradio/spots/models.py:179 msgid "" "The order of the spots in commercial breaks will be ordered by this numer" msgstr "" "L'ordine di emissione dello spot all'interno della fascia pubblicitaria è " "determinato da questo numero" #: autoradio/spots/models.py:180 build/lib/autoradio/spots/models.py:180 msgid "Prologue" msgstr "Prologo" #: autoradio/spots/models.py:181 build/lib/autoradio/spots/models.py:181 msgid "This spot will be the firt in commercial breaks to introduce the others" msgstr "" "Questo sarà il primo spot della fascia pubblicitaria usato come introduzione " "agli altri" #: autoradio/spots/models.py:182 build/lib/autoradio/spots/models.py:182 msgid "Epilogue" msgstr "Epilogo" #: autoradio/spots/models.py:183 build/lib/autoradio/spots/models.py:183 msgid "This spot will be the last in commercial breaks to leave-taking" msgstr "" "Questo sarà l'ultimo spot della fascia pubblicitaria usato come commiato" #: build/lib/autoradio/programs/models.py:265 msgid "The start time from wich the program will be active" msgstr "ora da cui il programma sarà attivo" #: build/lib/autoradio/programs/models.py:267 msgid "The end time the program will be active" msgstr "ora a cui il programma sarà disattivato" #: templates/base.html:35 msgid "Home" msgstr "" #: templates/base.html:36 msgid "Player" msgstr "" #: templates/base.html:37 msgid "Admin" msgstr "Amministra" #: templates/base.html:38 templates/doc/index.html:51 msgid "Program" msgstr "Programmi" #: templates/base.html:39 templates/doc/index.html:45 msgid "Jingle" msgstr "" #: templates/base.html:40 templates/doc/index.html:48 msgid "Spot" msgstr "" #: templates/base.html:41 templates/doc/index.html:42 msgid "Playlist" msgstr "" #: templates/base.html:42 msgid "Mediacast" msgstr "" #: templates/base.html:43 msgid "ProgBook" msgstr "Libro Programmi" #: templates/admin/base_site.html:4 msgid "Django site admin" msgstr "Amministrazione" #: templates/admin/base_site.html:7 msgid "Documentation" msgstr "Documentazione" #: templates/doc/doc.html:6 msgid "Documentation Home" msgstr "Indice documentazione" #: templates/doc/doc.html:11 msgid "" "\n" "Radio automation software. Simple to use, starting from digital audio\n" "files, manage on-air broadcasting over a radio-station or\n" "web-radio. The main components are:\n" "

    \n" "
  • Player (Xmms/Audacious): plays all your media files and send digital " "sound\n" " to an audio device or audio server
  • \n" " \n" "
  • Scheduler: real time manager for emission of special audio files\n" " like jingles, spots, playlist and programs; interact with player\n" " like supervisor User
  • \n" "\n" "
  • inteface: WEB interface to monitor the player and scheduler and\n" " admin the schedules for the complete control over your station\n" " format. The web interface allows you to easily publish podcasts\n" " that conform to the RSS 2.0 and iTunes RSS podcast specifications\n" " The web interface provide a \"full compatible\" ogg player.
  • \n" "\n" "
\n" "Developed with Python, Django, Dbus it works in an production enviroment\n" "\n" msgstr "" "\n" "Software per l'automazione di una radio. Semplice da usare, facendo\n" "uso di file audio digitali, gestisce la mess ain onda di una emittente\n" "radiofonica o di una web-radio. Le componenti principali sono:\n" "\n" "
    \n" "\n" "
  • Player (Xmms/Audacious): suona tutti i media file e invia il\n" " suono digitale a un dispositivo audio o a un server audio
  • \n" "\n" "
  • Scheduler: gestore in tempo reale di particolari file audio\n" " quali jingle, spot, playlist e programmi; interagisce con il player\n" " come se fosse un utente supervisore mandando in onda le\n" " programmazioni al momento giusto.\n" "\n" "
  • Interfaccia: interfaccia WEB per monitore il player e lo\n" " scheduler e per amministare la programmazione per il pieno controllo\n" " delle emissione della tua emittente radiofonica.L'intefaccia web\n" " permette anche con facilità di di pubblicare facilmente il podcast\n" " dei programmi coerentemente con gli standard RSS 2.0 e iTunes\n" " RSS. L'interfaccia web fornisce un player ogg integrato pienamente\n" " compatibile.
\n" "\n" "Sviluppato con Python, Django, Dbus, funziona operativamente in\n" "ambiente produttivo.\n" "\n" "\n" #: templates/doc/doc.html:36 msgid "" "\n" "
    \n" "
  • manage ogg, mp3, wav and other media file format
  • \n" "
  • it's designed as client server
  • \n" "
  • manage playlists, inserting on it jingles, spots and programs
  • \n" "
  • programmable rules for schedule and period schedule
  • \n" "
  • do not overlap schedules: anticipate, postone or delete
  • \n" "
  • player is monitored by web interface
  • \n" "
  • spots are grouped and ordered by your preference
  • \n" "
  • programs are available for podcasting in a very complete rss feed web " "interface
  • \n" "
  • integrated web player for ogg vorbis that is very compatible with most " "user's systems
  • \n" "
  • can produce a palimpsest and a printable version is available " "following the the italian law standard
  • \n" "
  • integrated daemon system with logging
  • \n" "
  • provide enhanced version of dir2ogg.py and mkplaylist.py to manage\n" " files with music (convert to ogg and make playlist)
  • \n" "
  • do not use DataBases to manage music; you can use your preferred " "application to produce playlists
  • \n" "
  • on line web documentation
  • \n" "
\n" "\n" msgstr "" "\n" "
    \n" "
  • gestisce ogg, mp3, wav e altri formati audio digitale
  • \n" "
  • è disegnato con funzionalità client, server
  • \n" "
  • gestisce playlists, inserendo in esse jingle, spot e programmi
  • \n" "
  • regole programmabili for calendarizzazione anche periodica
  • \n" "
  • non sovrapposizione degli eventi programmati: anticipa, ritarda o " "cancella seguendo regole evolute
  • \n" "
  • il player è monitorato tramite l'interfaccia web
  • \n" "
  • spots sono raggruppati e orditati secondo le proprie preferenze
  • \n" "
  • i programmi sono disponibili pe ril podcasting in una completa " "interfaccia web con rss feed in vari standard
  • \n" "
  • un player per ogg vorbis integrato che è molto compatibile con la " "maggioranza dei sistemi disponibili agli utenti
  • \n" "
  • può produrre un palinsesto in una versione stampabile che segue lo " "standard stabilito dalla legislazione italiana
  • \n" "
  • sistema integrato di esecuzione in background completo di " "messagistica
  • \n" "
  • fornisce una versione evoluta di dir2ogg.py e mkplaylist.py per la " "gestione di file musicali (conversione a ogg e creazione di playlist)
  • \n" "
  • non utilizza Data Base per gestire la musica; puoi usare la tua " "applicazione preferita per produrre le playlist musicali
  • \n" "
  • documentazione disponibile in linea su web
  • \n" "
\n" #: templates/doc/doc.html:61 msgid "" "\n" "Partendo da una playlist è in grado di gestire differenti formati di\n" "audio digitali per poi inviare il suono o a una scheda audio o a un\n" "server audio.\n" "

\n" msgstr "" #: templates/doc/doc.html:68 msgid "" "\n" "Esistono varie possibilità:\n" "
    \n" "
  • Audacious2:\n" " Questo player è disponibile su tutte le nuove distribuzioni.\n" " Permette l'invio diretto a un server per lo streaming per la realizzazione " "di web radio.\n" " Questo è il player preferito pe l'utilizzo con AutoRadio.
  • \n" "
  • Xmms:\n" " E' un player “antico” ma molto robusto.\n" " Consigliato solo su vecchie distribuzioni
  • \n" "
\n" msgstr "" #: templates/doc/doc.html:81 msgid "" "\n" "Questi sono i meccanismi di funzionamento principale:\n" msgstr "" #: templates/doc/doc.html:86 msgid "" "deve essere sempre presente nel player una playlist di brani\n" " musicali ciascuno di durata non superiore a 7/8 minuti; brani piu'\n" " lunghi potrebbero comportare ritardi e cattiva gestione\n" " dell'emissione automatica. Per mantenere sempre piena la playlist si\n" " consiglia di prevedere almeno due volte al giorno il caricamento\n" " automatico di una playlist voluminosa." msgstr "" #: templates/doc/doc.html:93 msgid "" "quando una schedula raggiunge il tempo per cui è stata programmata\n" " viene inserita nella prima posizione successiva a quella attualmente\n" " in play, e successiva anche ad ogni file precedentemente inserito da una\n" " precedente schedula." msgstr "" #: templates/doc/doc.html:98 msgid "" "tutto thread save, ossia le funzioni fatte sul player dalle varie\n" " schedule saranno sempre consistenti." msgstr "" #: templates/doc/doc.html:101 msgid "" "le operazioni di inserimento e cancellazione dalla playlist vengono\n" " fatte solo quando mancano piu' di 10 secondi alla fine del brano per\n" " non cadere in situazioni critiche e inconsistenti." msgstr "" #: templates/doc/doc.html:105 msgid "" "la testa della playlist, che se tutto è programmato correttamente\n" " tende sempre a crescere, viene tagliata a 10 brani." msgstr "" #: templates/doc/doc.html:108 msgid "" "la coda della playlist che se tutto è programmato correttamente\n" " tende sempre a crescere viene tagliata a 500 brani." msgstr "" #: templates/doc/doc.html:111 msgid "il player se in stato \"stop\" viene sempre rimesso in stato \"play\"." msgstr "" #: templates/doc/doc.html:113 msgid "" "il player se in stato \"pause\" rimarrà sempre in \"pause\" se non ci\n" " sarà un intervento manuale." msgstr "" #: templates/doc/doc.html:116 msgid "è possibile visualizzare lo stato del player con interfaccia web." msgstr "" #: templates/doc/doc.html:124 msgid "" "\n" "Lo scheduler è un programma che lanciato separatamente comanda\n" "all'istante di tempo opportuno il Player per attivare l'emissione\n" "delle programmazione preimpostata. Svolge anche altre funzioni logiche\n" "e di controllo quali l'esecuzione del player se non dovesse risultare\n" "attivo. Ogni volta che una programmazione è stata inserita con\n" "successo nella playlist del player nel database di autoradio essa\n" "risulta come se fosse stata effettivamente messa in onda.Ovviamente se\n" "sul player vengono fatte operazioni manuali lo scheduler non ne puo\n" "tenere conto.\n" msgstr "" #: templates/doc/doc.html:138 msgid "" "\n" "Vengono estratte tutte le schedule in un intervallo di tempo a cavallo\n" "tra passato e futuro. Spot e programmi programmati nel passato e non\n" "ancora emessi vengono programmati immediatamente se il ritardo non è\n" "eccessivo. Le pubblicità che cadono durante l'emissione di un\n" "programma vengono anticipare o ritardate a seconda della vicinanza\n" "temporale all'inizio o alla fine delle parti del programma. I jingles\n" "che cadono durante l'emissione di programmi o publicità vengono\n" "eliminati.\n" msgstr "" #: templates/doc/doc.html:151 msgid "" "\n" "Lo scheduler provvede anche alla generazione dinamica delle playlist\n" "delle fasce pubblicitarie per l'eventuale emissione manuale della\n" "pubblicità. Queste playlist vengono generate poco prima dell'orario\n" "programato per l'emissione e si possono trovare nella cartella\n" "specificata nel file di configurazione (playlistdir).\n" msgstr "" #: templates/doc/doc.html:165 msgid "" "\n" "Le playlist sono il \"tappeto\" musicale dell'emissione radiofonica.\n" "Ogni playlist puo' essere programmata per un istante preciso oppure\n" "per una emissione periodica ad iniziare da una una data specifica fino\n" "a una data finale per alcuni giorni della settimana specificati. Le\n" "playlist prima di essere caricate vengono controllate e i brani\n" "musicali corrotti o mancanti vengono eliminati prima di essere\n" "inseriti. E' possibile specificare la durata della playlist che verrà\n" "inserita nel player. Una opzione permette di attivare la funzione di\n" "mescolamento dell'ordine della sequenza dei brani.\n" msgstr "" #: templates/doc/doc.html:179 msgid "" "\n" "Per poter funzionare Autoradio deve sempre avere un discreto numero\n" "di brani musicali caricati nella playlist tra i quali inserire le\n" "altre programmazioni. Quando una playlist programmata viene mandata in\n" "onda essa viene inserita in testa ai brani già presenti nella lista\n" "del player.\n" msgstr "" #: templates/doc/doc.html:193 msgid "" "\n" "I jingles vengono emessi ad intervalli di tempo fissi. Per ogni jingle\n" "è possibile impostare da quale data a quale data effettuare\n" "l'emissione, da che ora a che ora effettuare l'emissione e in quali\n" "giorni della settimana. E' cosi' facile attivare promo di programmi o\n" "altro ad orari specifici.\n" msgstr "" #: templates/doc/doc.html:203 msgid "" "\n" "Il jingle programmato sarà quello con ultima data di emissione piu'\n" "vecchia; se ci sono piu' jingle con la stessa ultiuma data di\n" "emissione, i jingle vengono ordinati per il parametro impostabile\n" "della priorità.\n" msgstr "" #: templates/doc/doc.html:216 msgid "" "\n" "E' possibile impostare qualsiasi numero di fasce pubblicitarie\n" "caratterizzate da un orario di emissione; ogni fascia è attivabile o\n" "disattivabile singolarmente. Una fascia pubblicitaria è composta da\n" "spot. Ogni fascia pubblicitaria ha uno o piu' spot definiti come\n" "prologo che annunciano la pubblicità. Ogni fascia pubblicitaria ha uno\n" "o piu' spot definiti come epilogo che annunciano la fine della\n" "pubblicità.\n" msgstr "" #: templates/doc/doc.html:228 msgid "" "\n" "Per ogni spot (o prologo o epilogo) è possibile stabilire da quale data a " "quale\n" "data effettuare l'emissione, in quali giorni della settimana e in\n" "quale fascia pubblicitaria. Ogni spot ( o prologo o epilogo) ha una\n" "priorità che determina l'oridine di emissione.\n" msgstr "" #: templates/doc/doc.html:240 msgid "" "\n" "La gestione dei programmi è la sezione più articolata di Autoradio.\n" "Uno show è composto da episodi che a loro volta sono composti da\n" "enclosure (parti). Uno show ah alcuni parametri che lo definiscono nel\n" "palinsesto. Un episodio ha dei parametri che definiscono quando deve\n" "essere mandato in onda da autoradio. Le enclosure (parti) permettono\n" "di spezzare episodi di lunga durata per facilitarne la messa in onda,\n" "il download e gli inserimenti pubblicitari. Quando dal menù\n" "principale si seleziona programmi viene presentato il modulo per\n" "l'inserimento di un episodio di uno show. Se lo show a cui appartiene\n" "un episodio non è stato ancora definito bisogna farlo come prima\n" "operazione; selezionando il + a fianco della voce Show è possibile\n" "farlo.\n" msgstr "" #: templates/doc/doc.html:256 msgid "La definizione di uno Show" msgstr "" #: templates/doc/doc.html:258 msgid "" "\n" "Nella sezione principale vengono richieste alcune informazioni sullo show e " "vengono utilizzate alcune categorie definite dalla legislazione italiana.\n" msgstr "" #: templates/doc/doc.html:262 msgid "" "\n" "Nella sezione \"Podcast options\" e \"iTunes options\"\n" "vengono richieste informazioni relative al servizio podcast ben\n" "descritto alle voci successive di questa documentazione.\n" msgstr "" #: templates/doc/doc.html:268 msgid "" "\n" "Nella sezione \"Periodic Schedules\" e \"APeriodic Schedules\" vengono\n" "richieste informazioni necessarie alla compilazione del palinsesto e\n" "alla stampa del libro programmi richiesto dalla legislazione italiana,\n" "funzione ben descritta alle voce successiva di questa documentazione.\n" msgstr "" #: templates/doc/doc.html:275 msgid "" "\n" "Ogni Show puo' essere inserito in palinsesto a un istante preciso oppure\n" "per una emissione periodica ad iniziare da una una data specifica fino\n" "a una data finale per alcuni giorni della settimana specificati.\n" msgstr "" #: templates/doc/doc.html:281 msgid "FeedBurner and iTunes URLs" msgstr "FeedBurner e iTunes URL" #: templates/doc/doc.html:283 msgid "" "\n" "After saving at least one show and one episode, consider submitting\n" "your feed URL to FeedBurner for keeping track of podcast subscriber\n" "statistics. Your feed URL should be something like, where\n" "title-of-show is the slug of your show:\n" msgstr "\nDopo 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:\n" #: templates/doc/doc.html:292 msgid "" "\n" "Remember to check the checkbox for \"I'm a podcaster!\" Your new\n" "FeedBurner URL should be something like:\n" msgstr "\nRicordati di spuntare la casella \"I'm a podcaster!\" La tua nuova URL FeedBurner dovrebbe essere qualche cosa del tipo:\n" #: templates/doc/doc.html:299 msgid "" "\n" "You can now return to your website's admin and paste this URL into\n" "your Show's FeedBurner textbox. For bonus points, submit your\n" "FeedBurner URL to the iTunes Store. Your iTunes podcast URL should\n" "then be something like:\n" msgstr "" "\n" "Puoi tornare all'amministrazione del sito e ricopiare la URL nella casella\n" " 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:\n" #: templates/doc/doc.html:308 msgid "" "\n" "The advantage of submitting your FeedBurner URL to the iTunes Store\n" "allows you to track show statistics while also giving users the\n" "advantage of using the friendly iTunes interface. Return to the admin\n" "again and paste the iTunes show URL into the Show's iTunes URL\n" "textbox.\n" msgstr "\nI 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.\n" #: templates/doc/doc.html:317 msgid "Ping iTunes for new content" msgstr "Sollecita iTunes per i nuovi contenuti" #: templates/doc/doc.html:319 msgid "" "\n" "The iTunes Store checks new content daily but you might want to make a\n" "new episode available immediately in the iTunes Store. Visit your\n" "show's ping URL to make that episode available, which would be\n" "something like:\n" msgstr "\niTunes 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:\n" #: templates/doc/doc.html:328 msgid "" "\n" "Alternatively, if you're a savvy developer, you could set up a cron\n" "job to handle this, but note that pinging too often could result in a\n" "removal from the iTunes Store.\n" msgstr "\nIn 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.\n" #: templates/doc/doc.html:336 msgid "Yahoo! Media RSS feed submission" msgstr "Registrazione a Yahoo! Media RSS" #: templates/doc/doc.html:338 msgid "" "\n" "Likewise, considering submitting your podcast to Yahoo! Search, which\n" "specifically accepts any kind of regularly published media-based\n" "(audio, video, image, document, etc.) RSS 2.0 feed or Media RSS feed.\n" "Your Media RSS feed should be something like:\n" msgstr "" "\n" "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.\n" "Il tuo flusso Media RSS dovrebbe essere qualche cosa del genere:\n" #: templates/doc/doc.html:347 msgid "Google video sitemaps" msgstr "" #: templates/doc/doc.html:349 msgid "" "\n" "If you're creating a video podcast, you can submit a video sitemap to\n" "Google Webmaster Tools. The video sitemap will help Google index\n" "videos in Google Video. The video sitemap URL should be something like:\n" msgstr "" "\n" "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.\n" "La URL del video sitemap dovrebbe essere qualche cosa del genere:\n" #: templates/doc/doc.html:357 msgid "" "\n" "Additionally, you can add the video sitemap URL to your robots.txt file:\n" msgstr "\nIn aggiunta puoi inserire la URL del video sitemap nel tuo file robots.txt:\n" #: templates/doc/doc.html:363 msgid "" "\n" "Google allows the submission of a media RSS feed instead of the\n" "sitemap to Google Webmaster Tools if you prefer.\n" msgstr "\nGoogle permette la registrazione a Google Webmaster Tools di un flusso media RSS invece del sitemap se preferisci.\n" #: templates/doc/doc.html:368 msgid "La definizione di un Episodio" msgstr "" #: templates/doc/doc.html:370 msgid "" "\n" "Un episodio è composto da una o piu' enclosure (parti) associate a un\n" "titolo e un file audio da caricare\n" msgstr "" #: templates/doc/doc.html:375 msgid "" "\n" "Un episodio ha una o più schedule che definiscono quando dovrà essere\n" "mandato in onda automaticamente da autoradio (prima emissione ed\n" "eventuali repliche).\n" msgstr "" #: templates/doc/doc.html:381 msgid "" "\n" "Per ogni episodio è possibile inserire dei metadati utili per\n" "effettuare un efficiente podcast/mediacast.\n" msgstr "" #: templates/doc/doc.html:386 msgid "What is the Dublin Core namespace?" msgstr "Cosa è il Dublin Core namespace?" #: templates/doc/doc.html:388 msgid "" "\n" "The Dublin Core namespace allows for meta data to be associated with\n" "content contained in an RSS feed. Additional details on\n" "the Dublin\n" "Core or the DC extension.\n" msgstr "\nIl 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.\n" #: templates/doc/doc.html:400 msgid "Che cosa è il podcasting/mediacast?" msgstr "" #: templates/doc/doc.html:401 msgid "" "\n" "Il podcasting o in senso più generale il Mediacast è un sistema che\n" "mette a disposizione brani audio e video attraverso Internet in\n" "formato feed RSS, in pratica è un servizio che automaticamente informa\n" "ed eventualmente scarica i nuovi file audio messi a disposizione su un\n" "sito. Tramite un programma in grado di leggere e decifrare questi\n" "feed, è possibile essere informati non appena un nuovo file audio\n" "viene pubblicato. Il podcasting consente un ascolto personalizzato dei\n" "contenuti: gli utenti scelgono quando ascoltare, dove ascoltare e come\n" "ascoltare i file audio.\n" msgstr "" #: templates/doc/doc.html:413 msgid "Come funziona il podcasting/mediacast?" msgstr "" #: templates/doc/doc.html:414 msgid "" "\n" "Il procedimento è semplice: occorre scaricare ed installare sul\n" "proprio pc un software per il podcasting. Una volta\n" "installato il programma, bisogna indicare da quali fonti scaricare i\n" "file e con quale frequenza cercare nuovi brani.\n" msgstr "" #: templates/doc/doc.html:422 msgid "Autoradio: un efficiente motore per il podcasting/mediacast" msgstr "" #: templates/doc/doc.html:424 msgid "" "\n" "Autoradio fornisce una interfaccia web accessibile dal menu principale\n" "alla voce Mediacast per navigare i programmi e i relativi episodi\n" "fornendo i flussi web necessari per un efficiente podcasting della\n" "propria programmazione radiofonica.\n" msgstr "" #: templates/doc/doc.html:436 msgid "" "\n" "Autoradio permette la stampa del Libro Programmi secondo la\n" "legislazione italiana. Selezionata l'apposita voce dal menu principale\n" "è possibile procedere alla stampa inserendo gli estremi delle date\n" "richieste. Viene generato un file PDF pronto per la stampa. Alcune\n" "voci presenti nella stampa sono definite nella tabella \"configure\"\n" "della sezione \"programmi\" e modificabili dal pannello di\n" "amministrazione.\n" msgstr "" #: templates/doc/index.html:9 msgid "overview" msgstr "Panoramica" #: templates/doc/index.html:10 msgid "A global vision of Autoradio suite." msgstr "Visione globale della suite di programmi di autoradio" #: templates/doc/index.html:12 msgid "features" msgstr "Funzionalità" #: templates/doc/index.html:13 templates/doc/index.html.py:19 msgid "What you can do with Autoradio." msgstr "Cosa si puoò fare con Autoradio" #: templates/doc/index.html:15 msgid "Main components" msgstr "Componenti Principali" #: templates/doc/index.html:16 msgid "The components of autoradio suite." msgstr "Le componenti della suite Autoradio" #: templates/doc/index.html:18 msgid "player" msgstr "" #: templates/doc/index.html:21 msgid "scheduler" msgstr "" #: templates/doc/index.html:22 msgid "Real time emission of your schedule." msgstr "Emissione in tempo reale della programmazione." #: templates/doc/index.html:26 msgid "User inteface" msgstr "Interfaccia utente." #: templates/doc/index.html:27 msgid "The web interface." msgstr "Interfaccia Web." #: templates/doc/index.html:29 msgid "" "\n" "\n" "

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

\n" "\n" "

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

\n" "\n" msgstr "" #: templates/doc/index.html:43 msgid "How to start to play music." msgstr "Come cominciare a emettere musica." #: templates/doc/index.html:46 msgid "How to start to use jingles to promote your brand." msgstr "Come usare i jingle per promuovere il tuo marchio." #: templates/doc/index.html:49 msgid "Organize radio advertisement." msgstr "Organizza la pubblicità." #: templates/doc/index.html:52 msgid "Record your programs and set when it will be on air." msgstr "Registra i tuoi programmi e imposta quando dovranno andare in onda." #: templates/doc/index.html:54 msgid "Podcast" msgstr "" #: templates/doc/index.html:55 msgid "How to distribute your audio on the net." msgstr "Come distribuire il tuo audio in rete." #: templates/doc/index.html:57 msgid "Program's book" msgstr "Libro Programmi" #: templates/doc/index.html:58 msgid "Palimpsest for the italian law." msgstr "Il libro programmi per la legislazione italiana." #: templates/palimpsest/extreme.html:10 msgid "Specify a time range to produce the report for the program's book" msgstr "" "Specifica un intervallo temporale per la produzione del report per il libro " "programmi" #: templates/palimpsest/extreme.html:16 msgid "Submit" msgstr "Invia" #: templates/podcast/base.html:32 msgid "Autoradio Mediacast" msgstr "" #: templates/podcast/episode_detail.html:8 msgid "A detail of one episode of one show" msgstr "Dettagli di un episodio del programma" #: templates/podcast/episode_detail.html:16 msgid "Return to episodes" msgstr "Torna agli episodi" #: templates/podcast/episode_detail.html:22 #: templates/podcast/episode_list.html:54 msgid "episode screenshot" msgstr "Immagine dell'episodio" #: templates/podcast/episode_detail.html:25 msgid "Date" msgstr "Data" #: templates/podcast/episode_detail.html:27 msgid "Show" msgstr "Programma" #: templates/podcast/episode_detail.html:29 #: templates/podcast/episode_list.html:31 msgid "Author" msgstr "Autore" #: templates/podcast/episode_detail.html:32 #: templates/podcast/episode_list.html:36 msgid "Subscribe Feed RSS 2.0 and iTunes" msgstr "Iscitviti al flusso RSS 2.0 e iTunes" #: templates/podcast/episode_detail.html:33 #: templates/podcast/episode_list.html:37 msgid "Subscribe Atom" msgstr "Iscrizione flusso Atom" #: templates/podcast/episode_detail.html:34 #: templates/podcast/episode_list.html:38 msgid "Subscribe Media RSS" msgstr "Iscrizione flusso Media RSS" #: templates/podcast/episode_detail.html:36 #: templates/podcast/episode_detail.html:38 #: templates/podcast/episode_detail.html:71 #: templates/podcast/episode_list.html:40 #: templates/podcast/episode_list.html:42 msgid "Subscribe" msgstr "Iscrizione" #: templates/podcast/episode_detail.html:43 msgid "Listen this episode" msgstr "Ascolta questo episodio" #: templates/podcast/episode_detail.html:48 msgid "Download file" msgstr "Scarica il file" #: templates/podcast/episode_detail.html:48 msgid "Listen" msgstr "Ascolta" #: templates/podcast/episode_detail.html:62 msgid "Download the closed captions" msgstr "Scarica i sottotitoli" #: templates/podcast/episode_list.html:8 msgid "A list of episodes of one show" msgstr "Lista degli episodi di un programma" #: templates/podcast/episode_list.html:17 msgid "Return to shows" msgstr "Torna ai programmi" #: templates/podcast/episode_list.html:25 msgid "Explicit" msgstr "Esplicito" #: templates/podcast/episode_list.html:28 msgid "Category" msgstr "Categoria" #: templates/podcast/episode_list.html:35 msgid "fedd web/RSS" msgstr "flusso web/RSS" #: templates/podcast/episode_list.html:39 msgid "FeedBurner" msgstr "" #: templates/podcast/show_list.html:7 msgid "A list of shows" msgstr "Lista dei programmi" #: templates/schedule/index.html:9 templates/xmms/index.html:12 msgid "Automatic update every" msgstr "Aggiornamento automatico ogni" #: templates/schedule/index.html:9 msgid "minuts" msgstr "minuti" #: templates/schedule/index.html:9 templates/xmms/index.html:12 msgid "Last update" msgstr "Ultimo aggiornamento" #: templates/schedule/index.html:10 msgid "This is on air status" msgstr "Questo è lo stato della messa in onda" #: templates/schedule/index.html:15 msgid "Title" msgstr "Titolo" #: templates/schedule/index.html:15 msgid "Programmed for" msgstr "Programmato per" #: templates/schedule/index.html:15 msgid "That is" msgstr "Ossia" #: templates/schedule/index.html:15 msgid "Last emission done" msgstr "Ultima emissione effettuata" #: templates/schedule/index.html:15 msgid "Length" msgstr "Durata" #: templates/schedule/index.html:32 msgid "in" msgstr "tra" #: templates/schedule/index.html:32 templates/schedule/index.html.py:33 msgid "ago" msgstr "fa" #: templates/schedule/index.html:35 msgid "Download" msgstr "Scarica" #: templates/schedule/index.html:41 msgid "No schedule are available" msgstr "Nessuna programmazione disponibile" #: templates/xmms/index.html:12 msgid "seconds" msgstr "secondi" #, fuzzy #~ msgid "Recordinf date" #~ msgstr "Registrato oggi?" #, fuzzy #~ msgid "Activate palimpsest" #~ msgstr "Playlist attive" #~ msgid "Program name" #~ msgstr "Nome del programma" #~ msgid "Active programs" #~ msgstr "Programmi attivi" #~ msgid "Xmms" #~ msgstr "Player" #~ msgid "Name" #~ msgstr "Nome" #~ msgid "Date of recording" #~ msgstr "Data della registrazione" autoradio-2.8.6/autoradio-site.cfg0000664000175000017500000001257713001105756016700 0ustar pat1pat100000000000000[autoradiod] #player to use (AutoPlayer, xmms, audacious) player="AutoPlayer" #directory where write new playlists generated by autoradiod playlistdir="/usr/share/autoradio/django/media/spots" # path to working file logfile="/var/log/autoradio/autoradiod.log" errfile = "/var/log/autoradio/autoradiod.err" lockfile = "/var/run/autoradio/autoradiod.lock" timestampfile = "/var/run/autoradio/autoradiod.timestamp" # host xmms is running on xmms_host="localhost" #backward and forward time intervat to check for schedule conflict minelab=180 # tollerance time interval to recovery schedule not done ( backward time when start autoradiod ) # to adjust the programming you have to make changes minsched minutes before minsched=5 locale="it_IT.UTF-8" user = autoradio group = autoradio [[ env ]] DISPLAY=':0.0' LANG=$locale [autoradioweb] logfile = '/var/log/autoradio/autoradioweb.log' errfile = '/var/log/autoradio/autoradioweb.err' lockfile = '/var/run/autoradio/autoradioweb.lock' user = autoradio group = autoradio #port = '8888' # the web player can only manage ogg vorbis files at 44100Hz: enable or disable check on uploads permit_no_playable_files = False # tags help in the web view: enable or disable check on uploads # if file do not have tags will be rejected # this enable or disable check for 44100Hz sample rate compatibility for enclosure require_tags_in_enclosure = True [django] DEBUG = True TEMPLATE_DEBUG = True FILE_UPLOAD_PERMISSIONS = 420 # Make this unique, and don't share it with anybody. SECRET_KEY = random-string-of-ascii #SESSION_COOKIE_DOMAIN = autoradio # Local time zone for this installation. Choices can be found here: # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name # although not all choices may be available on all operating systems. # If running in a Windows environment this must be set to the same as your # system time zone. TIME_ZONE = 'Europe/Rome' # Language code for this installation. All choices can be found here: # http://www.i18nguy.com/unicode/language-identifiers.html LANGUAGE_CODE = 'en-us' #LANGUAGE_CODE = 'it-it' SITE_ID = 1 # If you set this to False, Django will make some optimizations so as not # to load the internationalization machinery. USE_I18N = True # directories where Django looks for translation files. LOCALE_PATHS='/usr/share/autoradio/locale', ADMINS=Your Name your_email@domain.com, MANAGERS=Your Name your_email@domain.com, # Absolute path to the directory that holds media. # Example: "/home/media/media.lawrence.com/" MEDIA_ROOT = '/usr/share/autoradio/media/' #URL that handles the media served from MEDIA_ROOT, used for managing #stored files. It must end in a slash if set to a non-empty value. You #will need to configure these files to be served in both development #and production environments. MEDIA_URL="/media/" # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". # Always use forward slashes, even on Windows. # Don't forget to use absolute paths, not relative paths. TEMPLATE_DIRS = "/usr/share/autoradio/templates", #The absolute path to the directory where collectstatic will collect static files for deployment STATIC_ROOT = "/usr/share/autoradio/static" #URL to use when referring to static files located in STATIC_ROOT. STATIC_URL = '/static/' # set to true if django have to serve static file # set to false if you use other web server like apache SERVE_STATIC=True # The URL where requests are redirected for login, especially when # using the login_required() decorator. LOGIN_URL='/login/' [database] DATABASE_ENGINE = 'sqlite3' # 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'ado_mssql'. DATABASE_NAME = '/usr/share/autoradio/autoradio.sqlite3' # Or path to database file if using sqlite3. #DATABASE_ENGINE = 'mysql' # 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'ado_mssql'. #DATABASE_NAME = 'autoradio' # Or path to database file if using sqlite3. #DATABASE_USER = 'autoradio' # Not used with sqlite3. #DATABASE_PASSWORD = 'autoradio' # Not used with sqlite3. #DATABASE_HOST = 'autoradio' # Set to empty string for localhost. Not used with sqlite3. #DATABASE_PORT = '' # Set to empty string for default. Not used with sqlite3. [autoplayer] user = autoradio group = autoradio logfile = '/var/log/autoradio/autoplayer.log' errfile = '/var/log/autoradio/autoplayer.err' lockfile = '/var/run/autoradio/autoplayer.lock' # Add an address that the dbus-daemon should listen on. The address is in the # standard D-Bus format that contains a transport name plus possible # parameters/options. # Example: unix:path=/tmp/foo # Example: tcp:host=localhost,port=1234 # http://stackoverflow.com/questions/10158684/connecting-to-dbus-over-tcp #busaddress='tcp:host=localhost,port=1234' #audiosink = jackaudiosink # set to autoaudiosink / jackaudiosink [autoradiodbus] user = autoradio group = autoradio logfile = '/var/log/autoradio/autoradiodbus.log' errfile = '/var/log/autoradio/autoradiodbus.err' lockfile = '/var/run/autoradio/autoradiodbus.lock' conffile = '/etc/autoradio/dbus-autoradio.conf' [jackdaemon] user = autoradio group = audio logfile = '/var/log/autoradio/jackdaemon.log' errfile = '/var/log/autoradio/jackdaemon.err' lockfile = '/var/run/autoradio/jackdaemon.lock' autoradio-2.8.6/fedora/0000775000175000017500000000000013003471473014516 5ustar pat1pat100000000000000autoradio-2.8.6/fedora/autoradio.spec0000644000175000017500000001544313003443664017367 0ustar pat1pat100000000000000%{!?python_sitelib: %define python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print get_python_lib()" )} %{!?python_sitearch: %define python_sitearch %(%{__python} -c "from distutils.sysconfig import get_python_lib; print get_python_lib( 1)")} %define name autoradio %define version 2.8.6 %define release 1%{?dist} Summary: radio automation software Name: %{name} Version: %{version} Release: %{release} Source0: %{name}-%{version}.tar.gz # tmpfiles.d configuration for the /var/run directory Source1: %{name}-tmpfiles.conf License: GNU GPL v2 Group: Development/Libraries BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot Prefix: %{_prefix} BuildArch: noarch Vendor: Paolo Patruno Url: http://autoradiobc.sf.net BuildRequires: python-configobj , python-django >= 1.7.0 , help2man, python-setuptools Requires:python-mutagen >= 1.17 , python-django >= 1.7.0, python-configobj, python-cherrypy, python-reportlab >= 2.0, python-docutils, sqlite >= 3.6.22, speex-tools, python-magic, python-pillow, python-six #, python-django-extensions Requires: initscripts %if 0%{?fedora} < 10 Requires: pyxmms, xmms %else ## Requires: dbus-python, audacious >= 1.5 Requires: dbus-python, gstreamer, gstreamer-plugins-base, gstreamer-plugins-good, gstreamer-plugins-bad, gstreamer-plugins-bad-free, gstreamer-plugins-bad-free-extras, gstreamer-python %endif # Compile options: # --with cherrypy : do not need cherrypy2 ##%if 0%{?fedora} < 10 ##%if 0%{?_with_} ##Requires: python-cherrypy ##%else ##Requires: python-cherrypy2 ##%endif %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 (gstreamer) 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 The web interface provide a "full compatible" ogg player. Developed with Python, Django, Dbus it works in an production enviroment %prep %setup -n %{name}-%{version} -n %{name}-%{version} %build %{__python} setup.py build %install %{__python} setup.py install --root=$RPM_BUILD_ROOT ##%{__install} -d -m 0710 %{buildroot}%{_var}/{run/autoradio,log/autoradio} mkdir -p %{buildroot}%{_localstatedir}/run/ mkdir -p %{buildroot}%{_localstatedir}/log/ %{__install} -d -m 0710 %{buildroot}%{_localstatedir}/{run/autoradio,log/autoradio} mkdir -p %{buildroot}%{_sysconfdir}/tmpfiles.d %{__install} -m 0644 %{SOURCE1} %{buildroot}%{_sysconfdir}/tmpfiles.d/%{name}.conf %clean rm -rf $RPM_BUILD_ROOT %files %defattr(-,root,root) %doc COPYING README doc/* %config(noreplace) %{_sysconfdir}/%{name}/autoradio-site.cfg %config(noreplace) %{_sysconfdir}/%{name}/dbus-autoradio.conf %dir %{python_sitelib}/%{name} %{python_sitelib}/%{name}/* %{python_sitelib}/%{name}-* %{_mandir}/man1/* %config(noreplace) %{_sysconfdir}/tmpfiles.d/%{name}.conf #%{_datadir}/autoradio/* %{_bindir}/autoradiod %{_bindir}/autoradioweb %{_bindir}/autoradioctrl %{_bindir}/autoplayerd %{_bindir}/autoplayergui %{_bindir}/autoradiodbusd %{_bindir}/jackdaemon %attr(-,autoradio,autoradio) %dir %{_datadir}/autoradio %attr(-,autoradio,autoradio) %{_datadir}/%{name}/* %attr(-,autoradio,autoradio) %dir %{_var}/log/%{name}/ %attr(-,autoradio,autoradio) %dir %{_var}/run/%{name}/ %pre /usr/bin/getent group autoradio >/dev/null || /usr/sbin/groupadd autoradio /usr/bin/getent passwd autoradio >/dev/null || \ /usr/sbin/useradd -g autoradio -d %{_datadir}/autoradio -M \ -c "autoradio user for radio automation software" autoradio #/usr/bin/getent group autoradio >/dev/null || /usr/sbin/groupadd -r autoradio #/usr/bin/getent passwd autoradio >/dev/null || \ # /usr/sbin/useradd -r -s /sbin/nologin -d %{_datadir}/autoradio -g autoradio \ # -c "autoradio user for radio automation software" autoradio ## Fix homedir for upgrades #/usr/sbin/usermod --home %{_datadir}/autoradio autoradio &>/dev/null ##exit 0 #%post # ## set some useful variables #AUTORADIO="autoradio" #CHOWN="/bin/chown" #ADDUSER="/usr/sbin/adduser" #USERDEL="/usr/sbin/userdel" #USERADD="/usr/sbin/useradd" #GROUPDEL="/usr/sbin/groupdel" #GROUPMOD="/usr/sbin/groupmod" #ID="/usr/bin/id" # #set -e # #### ## 1. get current autoradio uid and gid if user exists. #if $ID $AUTORADIO > /dev/null 2>&1; then # IUID=`$ID --user $AUTORADIO` # IGID=`$ID --group $AUTORADIO` #else # IUID="NONE" # IGID="NONE" #fi # ##### ### 2. Ensure that no standard account or group will remain before adding the ### new user ##if [ "$IUID" = "NONE" ] || [ $IUID -ge 1000 ]; then # we must do sth :) ## if ! [ "$IUID" = "NONE" ] && [ $IUID -ge 1000 ]; then ## # autoradio user exists but isn't a system user... delete it. ## $USERDEL $PEERCAST ## $GROUPDEL $PEERCAST ## fi ## ##### # ## 3. Add the system account. ## Issue a debconf warning if it fails. # if $GROUPMOD $AUTORADIO > /dev/null 2>&1; then # # peercast group already exists, use --ingroup # if ! $ADDUSER --system --disabled-password --disabled-login --home /usr/share/autoradio --no-create-home --ingroup $AUTORADIO $AUTORADIO; then # echo "The adduser command failed." # fi # else # if ! $ADDUSER --system --disabled-password --disabled-login --home /usr/share/peercast --no-create-home --group $AUTORADIO; then # echo "The adduser command failed." # fi # fi #fi #set +e # #### ## 4. change ownership of directory #$CHOWN -R $AUTORADIO:$AUTORADIO /usr/share/autoradio/ #$CHOWN -R $AUTORADIO:$AUTORADIO /var/log/autoradio/ #$CHOWN -R $AUTORADIO:$AUTORADIO /etc/autoradio/ #$CHOWN -R $AUTORADIO:$AUTORADIO /var/run/autoradio/ %changelog * Sat Aug 10 2013 Paolo Patruno - 2.8.0-1%{?dist} - bumped to version 2.8 * Mon Feb 18 2013 Paolo Patruno - 2.7.0-1%{?dist} - autoradio 2.7 with pygst * Sat Apr 14 2012 Paolo Patruno - 2.3-2%{?dist} - tmpfiles.d is a service provided by both systemd and upstart in Fedora 15 and later for managing temporary files and directories for daemons https://fedoraproject.org/wiki/Packaging:Tmpfiles.d * Sat Apr 14 2012 Paolo Patruno - 2.3-1%{?dist} - updated to 2.3 * Fri Aug 12 2011 Paolo Patruno - 2.1beta-1%{?dist} - upstream version 2.1beta autoradio-2.8.6/setup.cfg0000664000175000017500000000007313003471473015077 0ustar pat1pat100000000000000[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 autoradio-2.8.6/COPYING0000664000175000017500000004311013001105756014304 0ustar pat1pat100000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. autoradio-2.8.6/dbus-autoradio.conf0000664000175000017500000000462313001105756017050 0ustar pat1pat100000000000000 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-2.8.6/autoradiod0000775000175000017500000002606413001105756015343 0ustar pat1pat100000000000000#!/usr/bin/env python # GPL. (C) 2007-2012 Paolo Patruno. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # ToDo: # controllare altri conflitti in districa oltre ai jingles # utilizzare mp3splt per spezzare i programmi per fare gli inserimenti pubblicitari # alternare meglio i jingle tenendo in considerazione la priorita import os os.environ['DJANGO_SETTINGS_MODULE'] = 'autoradio.settings' from django.conf import settings import autoradio.settings import autoradio.autoradio_config from autoradio import daemon from autoradio import _version_ autoradiod = daemon.Daemon( stdin="/dev/null", stdout=autoradio.autoradio_config.logfile, stderr=autoradio.autoradio_config.errfile, pidfile=autoradio.autoradio_config.lockfile, user=autoradio.autoradio_config.user, group=autoradio.autoradio_config.group, env=autoradio.autoradio_config.env ) def main(): import logging,os,sys,errno,signal,logging.handlers import subprocess import thread import datetime import time as timesleep #from threading import * import django django.setup() import autoradio.autoradio_core formatter=logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s",datefmt="%Y-%m-%d %H:%M:%S") handler = logging.handlers.RotatingFileHandler(autoradio.autoradio_config.logfile, maxBytes=5000000, backupCount=10) handler.setFormatter(formatter) # Add the log message handler to the root logger logging.getLogger().addHandler(handler) logging.getLogger().setLevel(logging.INFO) logging.info('Starting up autoradiod version '+_version_) if (autoradio.autoradio_config.player == "amarok1") : import autoradio.manageamarok as manageplayer elif (autoradio.autoradio_config.player == "xmms"): # from managepytone import * import autoradio.managexmms as manageplayer import autoradio.xmmsweb as playerweb elif (autoradio.autoradio_config.player == "audacious" or autoradio.autoradio_config.player == "amarok" or autoradio.autoradio_config.player == "vlc" or autoradio.autoradio_config.player == "AutoPlayer"): # import autoradio.manageaudacious as manageplayer # import autoradio.audaciousweb as playerweb import autoradio.managempris as manageplayer import autoradio.mprisweb as playerweb else: logging.error('wrong player %s' % autoradio.autoradio_config.player) raise Exception() #def signalhandler(signum, frame): # logging.info( 'Signal handler called with signal %d', signum) #signal.signal(signal.SIGALRM, signalhandler) f = open(autoradio.autoradio_config.timestampfile, "w") f.write(str(datetime.datetime.now())) f.close() #we want to run a single process try: # now we can start to do our work # time constant # this is the first and last time that I set now with the current time now=datetime.datetime.now() first = True if (autoradio.autoradio_config.player == "amarok1") : kapp=KdeInit() function=manageplayer.ManageAmarok ok=amarok_watchdog(kapp) else: # function=ManagePytone session=0 # parte un thread pe monitorare via web il player #t = Timer(5.0, xmmsweb.start_http_server) #t.start() #os.system("python xmmsweb.py&") #p = subprocess.Popen("xmmsweb.py") if (autoradio.autoradio_config.player == "amarok1"): ok=manageplayer.amarok_watchdog(kapp) elif (autoradio.autoradio_config.player == "xmms"): t = thread.start_new_thread(playerweb.start_http_server,()) ok=manageplayeer.xmms_watchdog(session) elif (autoradio.autoradio_config.player == "audacious" or autoradio.autoradio_config.player == "amarok" or autoradio.autoradio_config.player == "vlc"): t = thread.start_new_thread(playerweb.start_http_server,()) ok=manageplayer.player_watchdog(autoradio.autoradio_config.player,session) elif (autoradio.autoradio_config.player == "AutoPlayer"): ok=manageplayer.player_watchdog(autoradio.autoradio_config.player,session) while ( True): scheds=autoradio.autoradio_core.schedules([]) for schedule in scheds.get_all_refine(now): scheduledatetime=schedule.scheduledatetime filename=schedule.filename type=schedule.type emission_done=schedule.emission_done if ( emission_done <> None ): if ( type == "program" ): #la trasmissione ha una schedula con un'unica emissione prevista logging.debug( " %s %s %s schedula already done; ignore it !",type,scheduledatetime,emission_done) continue if ( type == "spot" ): # considero una emissione effettuata se e' avvenuta nell'intorno delle 3 ore if ( abs(emission_done - scheduledatetime) < datetime.timedelta(minutes=180)): logging.debug(" %s %s %s schedula already done; ignore it !", type,scheduledatetime,emission_done) continue if ( type == "playlist" ): # I assume the emission is done if it happen around 3 hours if ( abs(emission_done - scheduledatetime) < datetime.timedelta(minutes=180)): logging.debug (" %s %s %s schedula already done; ignore it !",type,scheduledatetime,emission_done) continue delta=( scheduledatetime - now) sec=manageplayer.secondi(delta) #schedule for the nest minsched minutes starting from minsched minuti forward #if it is the first time I start from minsched minuti in the past if (first and ( type == "program" or type == "spot" or type == "playlist")): #recovery programmi, playlist e pubblicita' not emitted in a ragionable past time range startschedsec=-60 * autoradio.autoradio_config.minsched elif (first and ( type == "jingle")): startschedsec=0 else: startschedsec=60*autoradio.autoradio_config.minsched endschedsec = 60*autoradio.autoradio_config.minsched*2 if ( startschedsec < sec and sec <= endschedsec ): #print "ora schedulata", scheduledatetime #print "ora attuale", datetime.now() if (autoradio.autoradio_config.player == "amarok1") : threadschedule=manageplayer.ScheduleProgram(kapp,function,operation,filename,scheduledatetime,obj) else: #threadschedule=ScheduleProgram(session,function,operation,filename,scheduledatetime,obj,shuffle,length) threadschedule=manageplayer.ScheduleProgram(autoradio.autoradio_config.player,session,schedule) logging.debug (" %s %s programmed for %s seconds forward", type,filename,threadschedule.deltasec) threadschedule.start() first = False sleepsec=autoradio.autoradio_config.minsched/5.*60. #now I advance minsched and wait the right time to proced now=now+datetime.timedelta(0,60*autoradio.autoradio_config.minsched) while ( datetime.datetime.now() < now): if (autoradio.autoradio_config.player == "amarok1"): ok=manageplayer.save_status(kapp) else: ok=manageplayer.save_status(session) if ( ok ) : f = open(autoradio.autoradio_config.timestampfile, "w") f.write(str(datetime.datetime.now())) f.close() logging.debug ( "sleeping for %s seconds:%s %d",sleepsec,__name__,os.getpid()) #signal.alarm(0) timesleep.sleep(sleepsec) #os.system("sleep 60") #target = time.time() #while True: # now = time.time() # if now >= target+60: break # os.sleep(target-now) #logging.debug ( "wake up: %s %d",__name__,os.getpid() ) #sometime adjust playlist (will be deleted) if (autoradio.autoradio_config.player == "amarok1"): ok=manageplayer.amarok_watchdog(kapp) elif (autoradio.autoradio_config.player == "xmms"): ok=manageplayer.xmms_watchdog(session) elif (autoradio.autoradio_config.player == "audacious" or autoradio.autoradio_config.player == "amarok" or autoradio.autoradio_config.player == "vlc" or autoradio.autoradio_config.player == "AutoPlayer"): ok=manageplayer.player_watchdog(autoradio.autoradio_config.player,session) except KeyboardInterrupt : logging.info('All threads terminated for keyboard interrupt' ) return 1 except SystemExit: logging.info('Stopped OK') return 0 except: import traceback msg = traceback.format_exc() logging.error(msg) logging.info('Stopping') return 2 else: msg = "error without traceback; what happens ?!?!" logging.error(msg) logging.info('Stopping') return 3 if __name__ == '__main__': 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.autoradio_config.lockfile) if (not os.path.lexists(path) and path == "/var/run/autoradio" ): os.mkdir(path) if (os.getuid() == 0): user=autoradio.autoradio_config.user group=autoradio.autoradio_config.group 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 autoradiod.service(): sys.stdout.write("Autoradiod 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-2.8.6/init.sh0000775000175000017500000000051613001105756014556 0ustar pat1pat100000000000000# init db and set admin ./autoradioctrl --syncdb # create locale files #django-admin.py makemessages --settings=settings --pythonpath=autoradio -a #django-admin.py compilemessages --settings=settings --pythonpath=autoradio # start autoradiod daemon ./autoradiod restart # run django web server on port 8080 ./autoradioweb restart autoradio-2.8.6/templates/0000775000175000017500000000000013003471473015254 5ustar pat1pat100000000000000autoradio-2.8.6/templates/admin/0000775000175000017500000000000013003471473016344 5ustar pat1pat100000000000000autoradio-2.8.6/templates/admin/base_site.html0000664000175000017500000000064413001133431021157 0ustar pat1pat100000000000000{% extends "admin/base.html" %} {% load i18n %} {% block title %}{{ title }} | {{ site_title|default:_('Django site admin') }}{% endblock %} {% block branding %} AutoRadio Home / AutoRadio {% trans 'Documentation' %}

{{ site_header|default:_('Django administration') }}

{% endblock %} {% block nav-global %}{% endblock %} autoradio-2.8.6/autoradio.egg-info/0000775000175000017500000000000013003471473016737 5ustar pat1pat100000000000000autoradio-2.8.6/autoradio.egg-info/SOURCES.txt0000664000175000017500000001275013003471473020630 0ustar pat1pat100000000000000COPYING MANIFEST.in NEWS README TODO autoplayerd autoplayergui autoradio-site.cfg autoradio.cfg autoradio.wsgi autoradioctrl autoradiod autoradiodbusd autoradioweb dbus-autoradio.conf init.sh jackdaemon manage.py setup.py autoradio/__init__.py autoradio/audaciousweb.py autoradio/autoaudacious.py autoradio/autoepris.py autoradio/autompris.py autoradio/autompris2.py autoradio/autoradio_config.py autoradio/autoradio_core.py autoradio/autoxmms.py autoradio/daemon.py autoradio/dir2ogg.py autoradio/gest_jingle.py autoradio/gest_palimpsest.py autoradio/gest_playlist.py autoradio/gest_program.py autoradio/gest_spot.py autoradio/manageamarok.py autoradio/manageaudacious.py autoradio/managempris.py autoradio/managepytone.py autoradio/managexmms.py autoradio/mime.py autoradio/mkplaylist.py autoradio/mprisweb.py autoradio/settings.py autoradio/urls.py autoradio/xmmsweb.py autoradio/autoplayer/__init__.py autoradio/autoplayer/mpris2client.py autoradio/autoplayer/player.py autoradio/autoplayer/player_gstreamer0.py autoradio/autoplayer/playlist.py autoradio/doc/__init__.py autoradio/doc/urls.py autoradio/doc/templates/doc/doc.html autoradio/doc/templates/doc/index.html autoradio/jingles/__init__.py autoradio/jingles/admin.py autoradio/jingles/models.py autoradio/jingles/urls.py autoradio/jingles/views.py autoradio/jingles/migrations/0001_initial.py autoradio/jingles/migrations/__init__.py autoradio/mpris2/__init__.py autoradio/mpris2/interfaces.py autoradio/mpris2/loop_status.py autoradio/mpris2/mediaplayer2.py autoradio/mpris2/metada_map.py autoradio/mpris2/playback_rate.py autoradio/mpris2/playback_status.py autoradio/mpris2/player.py autoradio/mpris2/playlist.py autoradio/mpris2/playlist_id.py autoradio/mpris2/playlist_ordering.py autoradio/mpris2/playlists.py autoradio/mpris2/some_players.py autoradio/mpris2/time_in_us.py autoradio/mpris2/tracklist.py autoradio/mpris2/types.py autoradio/mpris2/uri.py autoradio/mpris2/utils.py autoradio/mpris2/volume.py autoradio/player/__init__.py autoradio/player/urls.py autoradio/player/views.py autoradio/playlists/__init__.py autoradio/playlists/admin.py autoradio/playlists/models.py autoradio/playlists/views.py autoradio/playlists/migrations/0001_initial.py autoradio/playlists/migrations/__init__.py autoradio/programs/__init__.py autoradio/programs/admin.py autoradio/programs/managers.py autoradio/programs/models.py autoradio/programs/urls.py autoradio/programs/urls_podcast.py autoradio/programs/views.py autoradio/programs/widgets.py autoradio/programs/fixtures/initial_data.json autoradio/programs/migrations/0001_initial.py autoradio/programs/migrations/0002_fixture.py autoradio/programs/migrations/__init__.py autoradio/programs/static/programs/mediacastlogo.png autoradio/programs/static/programs/css/base.css autoradio/programs/static/programs/css/player.css autoradio/programs/static/programs/css/podcast.css autoradio/programs/static/programs/css/print.css autoradio/programs/static/programs/playogg/player.png autoradio/programs/static/programs/playogg/js/jquery.open.js autoradio/programs/static/programs/playogg/js/player.js autoradio/programs/templates/base.html autoradio/programs/templates/palimpsest/extreme.html autoradio/programs/templates/player/base.html autoradio/programs/templates/player/player.html autoradio/programs/templates/player/playernohtml5.html autoradio/programs/templates/podcast/base.html autoradio/programs/templates/podcast/episode_detail.html autoradio/programs/templates/podcast/episode_list.html autoradio/programs/templates/podcast/episode_sitemap.html autoradio/programs/templates/podcast/show_feed.html autoradio/programs/templates/podcast/show_feed_atom.html autoradio/programs/templates/podcast/show_feed_media.html autoradio/programs/templates/podcast/show_list.html autoradio/programs/templates/schedule/index.html autoradio/programs/templates/xmms/index.html autoradio/pydbusdecorator/__init__.py autoradio/pydbusdecorator/dbus_attr.py autoradio/pydbusdecorator/dbus_data.py autoradio/pydbusdecorator/dbus_decorator.py autoradio/pydbusdecorator/dbus_interface.py autoradio/pydbusdecorator/dbus_method.py autoradio/pydbusdecorator/dbus_signal.py autoradio/pydbusdecorator/undefined_param.py autoradio/spots/__init__.py autoradio/spots/admin.py autoradio/spots/models.py autoradio/spots/urls.py autoradio/spots/views.py autoradio/spots/migrations/0001_initial.py autoradio/spots/migrations/__init__.py centos/autoradio.spec doc/apache_mod_python_example.conf doc/apache_modwsgi_example.conf doc/autoradio.dia doc/autoradio.png doc/dcop_howto.txt doc/jingles.png doc/monit_autoradio_example.conf doc/playlists.png doc/programs.png doc/spots.png doc/user_guide/generate.sh doc/user_guide/en/index.html doc/user_guide/en/feature/index.html doc/user_guide/en/jingle/index.html doc/user_guide/en/overview/index.html doc/user_guide/en/player/index.html doc/user_guide/en/playlist/index.html doc/user_guide/en/podcast/index.html doc/user_guide/en/program/index.html doc/user_guide/en/programbook/index.html doc/user_guide/en/scheduler/index.html doc/user_guide/en/spot/index.html doc/user_guide/it/index.html doc/user_guide/it/feature/index.html doc/user_guide/it/jingle/index.html doc/user_guide/it/overview/index.html doc/user_guide/it/player/index.html doc/user_guide/it/playlist/index.html doc/user_guide/it/podcast/index.html doc/user_guide/it/program/index.html doc/user_guide/it/programbook/index.html doc/user_guide/it/scheduler/index.html doc/user_guide/it/spot/index.html fedora/autoradio.spec locale/en/LC_MESSAGES/django.po locale/it/LC_MESSAGES/django.po templates/admin/base_site.htmlautoradio-2.8.6/autoradio/0000775000175000017500000000000013003471473015245 5ustar pat1pat100000000000000autoradio-2.8.6/autoradio/settings.py0000664000175000017500000002730713001105756017464 0ustar pat1pat100000000000000# 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_URL']="string(default='/media/')" configspec['django']['TEMPLATE_DIRS']="list(default=list('%s/templates',))" % os.getcwd() configspec['django']['STATIC_ROOT'] = "string(default='%s/static/')" % os.getcwd() configspec['django']['STATIC_URL']="string(default='/static/')" 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'] 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_URL = config['django']['MEDIA_URL'] STATIC_URL = config['django']['STATIC_URL'] STATIC_ROOT = config['django']['STATIC_ROOT'] if "%s" in STATIC_ROOT: STATIC_ROOT = STATIC_ROOT % os.getcwd() SERVE_STATIC = config['django']['SERVE_STATIC'] # 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', ] templatedirs=[] for templatedir in config['django']['TEMPLATE_DIRS'] : if "%s" in templatedir: templatedirs.append(templatedir % os.getcwd()) else: templatedirs.append(templatedir) TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': templatedirs , 'APP_DIRS': True, 'OPTIONS': { # List of callables that know how to import templates from various sources. 'context_processors': [ 'django.contrib.auth.context_processors.auth', 'django.template.context_processors.debug', 'django.template.context_processors.i18n', 'django.template.context_processors.media', 'django.template.context_processors.static', 'django.template.context_processors.tz', 'django.contrib.messages.context_processors.messages', ], 'debug' : config['django']['TEMPLATE_DEBUG'] } }, ] MIDDLEWARE_CLASSES = [ 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.locale.LocaleMiddleware', 'django.middleware.common.CommonMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.admindocs.middleware.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",] STATICFILES_DIRS = [ "global_static", ] try: import django_extensions INSTALLED_APPS += 'django_extensions', except ImportError: print "django_extensions is not installed; I do not use it" pass autoradio-2.8.6/autoradio/playlists/0000775000175000017500000000000013003471473017271 5ustar pat1pat100000000000000autoradio-2.8.6/autoradio/playlists/views.py0000664000175000017500000000003213001105756020767 0ustar pat1pat100000000000000# Create your views here. autoradio-2.8.6/autoradio/playlists/__init__.py0000664000175000017500000000000013001105756021364 0ustar pat1pat100000000000000autoradio-2.8.6/autoradio/playlists/admin.py0000664000175000017500000000350313001105756020730 0ustar pat1pat100000000000000from 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-2.8.6/autoradio/playlists/models.py0000664000175000017500000001740613001105756021132 0ustar pat1pat100000000000000from 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'),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-2.8.6/autoradio/playlists/migrations/0000775000175000017500000000000013003471473021445 5ustar pat1pat100000000000000autoradio-2.8.6/autoradio/playlists/migrations/__init__.py0000664000175000017500000000000013001105756023540 0ustar pat1pat100000000000000autoradio-2.8.6/autoradio/playlists/migrations/0001_initial.py0000664000175000017500000001215313001105756024106 0ustar pat1pat100000000000000# -*- coding: utf-8 -*- # Generated by Django 1.9 on 2016-01-25 17:58 from __future__ import unicode_literals import autoradio.playlists.models from django.db import migrations, models import django.db.models.deletion class Migration(migrations.Migration): initial = True dependencies = [ ] operations = [ migrations.CreateModel( name='Configure', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('sezione', models.CharField(default=b'playlist', editable=False, max_length=50, unique=True)), ('active', models.BooleanField(default=True, help_text='activate/deactivate the intere playlist class', verbose_name='Activate Playlist')), ('emission_starttime', models.TimeField(blank=True, help_text='The start time from wich the playlist will be active', null=True, verbose_name='Programmed start time')), ('emission_endtime', models.TimeField(blank=True, help_text='The end time the playlist will be active', null=True, verbose_name='Programmed start time')), ], ), migrations.CreateModel( name='Giorno', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('name', models.CharField(choices=[('luned\xec', 'luned\xec'), ('marted\xec', 'marted\xec'), ('mercoled\xec', 'mercoled\xec'), ('gioved\xec', 'gioved\xec'), ('venerd\xec', 'venerd\xec'), ('sabato', 'sabato'), ('domenica', 'domenica')], help_text='weekday name', max_length=20, unique=True)), ], ), migrations.CreateModel( name='PeriodicSchedule', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('shuffle', models.BooleanField(default=True, help_text="Every time the playlist will be scheduled it's order will be randomly changed", verbose_name='Shuffle Playlist on start')), ('length', models.FloatField(blank=True, default=None, help_text='If this time is set the playlist will be truncated', null=True, verbose_name='Max time length (seconds)')), ('start_date', models.DateField(blank=True, help_text='The playlist will be scheduled starting from this date', null=True, verbose_name='Programmed start date')), ('end_date', models.DateField(blank=True, help_text='The playlist will be scheduled ending this date', null=True, verbose_name='Programmed end date')), ('time', models.TimeField(blank=True, help_text='This is the time when the playlist will be on air', null=True, verbose_name='Programmed time')), ('emission_done', models.DateTimeField(editable=False, null=True, verbose_name='Emission done')), ('giorni', models.ManyToManyField(blank=True, help_text='The playlist will be scheduled those weekdays', to='playlists.Giorno', verbose_name='Programmed days')), ], ), migrations.CreateModel( name='Playlist', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('playlist', models.CharField(max_length=200, verbose_name='Playlist name')), ('file', autoradio.playlists.models.DeletingFileField(help_text='The playlist file to upload, format should be extm3u, m3u, pls', max_length=255, upload_to=b'playlist', verbose_name='File')), ('rec_date', models.DateTimeField(help_text='When the playlist was done (for reference only)', verbose_name='Generation date')), ('active', models.BooleanField(default=True, help_text='Activate the playlist for emission', verbose_name='Active')), ], ), migrations.CreateModel( name='Schedule', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('shuffle', models.BooleanField(default=True, help_text="Every time the playlist will be scheduled it's order will be randomly changed", verbose_name='Shuffle Playlist on start')), ('length', models.FloatField(blank=True, default=None, help_text='If this time is set the playlist will be truncated', null=True, verbose_name='Max time length (seconds)')), ('emission_date', models.DateTimeField(help_text='This is the date and time when the playlist will be on air', verbose_name='Programmed date')), ('emission_done', models.DateTimeField(editable=False, null=True, verbose_name='Emission done')), ('playlist', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='playlists.Playlist', verbose_name='refer to playlist:')), ], ), migrations.AddField( model_name='periodicschedule', name='playlist', field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='playlists.Playlist', verbose_name='refer to playlist:'), ), ] autoradio-2.8.6/autoradio/mkplaylist.py0000775000175000017500000003752713001105756020025 0ustar pat1pat100000000000000#!/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-2.8.6/autoradio/autoradio_config.py0000664000175000017500000000544713001105756021141 0ustar pat1pat100000000000000#!/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-2.8.6/autoradio/dir2ogg.py0000775000175000017500000007343413001105756017166 0ustar pat1pat100000000000000#!/usr/bin/python # # Copyright (C) 2007-2009 Julian Andres Klode # Copyright (C) 2003-2006 Darren Kirby # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # 10-08-2009 modified by Paolo Patruno ''' dir2ogg converts mp3, m4a, and wav files to the free open source OGG format. Oggs are about 20-25% smaller than mp3s with the same relative sound quality. Your mileage may vary. Keep in mind that converting from mp3 or m4a to ogg is a conversion between two lossy formats. This is fine if you just want to free up some disk space, but if you're a hard-core audiophile you may be dissapointed. I really can't notice a difference in quality with 'naked' ears myself. This script converts mp3s to wavs using mpg123 then converts the wavs to oggs using oggenc. m4a conversions require faad. Id3 tag support requires mutagen for mp3s. Scratch tags using the filename will be written for wav files (and mp3s with no tags!) ''' import sys import os, os.path import re from fnmatch import _cache, translate from optparse import OptionParser from subprocess import Popen, call, PIPE __version__ = '0.11.7' __date__ = '2009-05-03' FILTERS = {'mp3': ('*.mp3',), 'm4a': ('*.aac', '*.m4a', '*.mp4'), 'wma': ('*.asf', '*.wma', '*.wmf'), 'flash': ('*.flv', ), 'flac': ('*.flac',), 'wav': ('*.wav', ), 'speex': ('*.spx','*.speex' ), } def mmatch(names, patterns, rbool=True): '''names/patterns=str/list/tuple''' results = [] if isinstance(names, (str, unicode)): names = [names] if isinstance(patterns, (str, unicode)): patterns = [patterns] for pat in patterns: pat = pat.lower() if not pat in _cache: _cache[pat] = re.compile(translate(pat)) match = _cache[pat].match for name in names: if match(name.lower()): if rbool: return True else: results.append(name) if rbool: return bool(results) else: return results def read_opts(): if not '--version' in sys.argv: show_banner() if len(sys.argv[1:]) == 0: fatal('No arguments specified, see --help for usage.') parser = OptionParser(usage='%prog [options] [arguments]', version='%prog ' + __version__) parser.add_option('-l', '--license', action='callback', callback=show_license, help='display license informations') parser.add_option('-d', '--directory', action='store_true', help='convert files in all directories specified as arguments') parser.add_option('-r', '--recursive', action='store_true', help='convert files in all subdirectories of all directories specified as arguments') parser.add_option('-c', '--cdda', action='store_true', help="convert audio cd in all devices specified as arguments (or default: /dev/cdrom) [EXPERIMENTAL]") parser.add_option('-q', '--quality', metavar='N', default=3.0, type='float', help='quality. N is a number from 1-10 (default %default)') parser.add_option('-t', '--smart-mp3', action='store_true', help='try to use similar quality as original mp3 file (overwrites -q)') parser.add_option('-T', '--smart-mp3-correction', metavar='N', default=0.0, type='float', help='decrease detected quality (implies -t)') parser.add_option('-n', '--no-mp3', dest='convert_mp3', action='store_false', default=True, help="don't convert mp3s (use with '-d' or '-r')") parser.add_option('-a', '--convert-all', action='store_true', help="convert all supported formats") parser.add_option('-f', '--convert-flac', action='store_true', help="convert flac files (use with '-d')") parser.add_option('-x', '--convert-speex', action='store_true', help="convert speex files (use with '-d')") parser.add_option('-m', '--convert-m4a', action='store_true', help="convert m4a files (use with '-d')") parser.add_option('-w', '--convert-wav', action='store_true', help="convert wav files (use with '-d')") parser.add_option('-W', '--convert-wma', action='store_true', help="convert wma files (use with '-d').") parser.add_option('-F', '--convert-flash', action='store_true', help="convert flash files (use with '-d').") parser.add_option('--delete-input', action='store_true', help='delete input files') parser.add_option('-p', '--preserve-wav', action='store_true', help='keep the wav files (also includes -P)') parser.add_option('-P', '--no-pipe', action='store_true', help='Do not use pipes, use temporary wav files') parser.add_option('-v', '--verbose', action='store_true', help='verbose output') # Setup decoders commands = {'mp3': ('mpg123', 'mpg321', 'lame', 'mplayer'), 'wma': ('mplayer',), 'm4a': ('faad', 'mplayer'), 'flash': ('mplayer',), 'flac': ('flac', 'ogg123', 'mplayer'), 'speex': ('speexdec',), 'cd': ('cdparanoia', 'icedax','cdda2wav', 'mplayer'), } for ext, dec in commands.items(): default, choices = None, [] for command in dec: in_path = [prefix for prefix in os.environ['PATH'].split(os.pathsep) if os.path.exists(os.path.join(prefix, command))] if in_path: choices.append(command) default = default or command parser.add_option('--' + ext + '-decoder', type="choice", metavar=default, default=default, choices=choices, help="decoder for %s files (choices: %s)" % (ext, ', '.join(choices))) # End of decoder options options, args = parser.parse_args() options.convert_cd = options.cdda options.filters = [] for ext, pat in FILTERS.items(): # Activate Encoders for files on the commandline if options.convert_all or mmatch(args, pat): setattr(options, 'convert_' + ext, True) if getattr(options, 'convert_' + ext): options.filters += pat # Missing decoders if ext != 'wav' and getattr(options, 'convert_' + ext) and not getattr(options, ext + '_decoder'): fatal('%s was enabled, but no decoder has been found.' % ext) if len(args) == 0 and not options.cdda: fatal('No files/directories specified.') return options, args def info(msg): print 'Information: %s' % msg def warn(msg): '''print errors to the screen (red)''' print >> sys.stderr, "Warning: %s" % msg def fatal(msg): '''Fatal error (error + exit)''' print >> sys.stderr, "Error: %s" % msg sys.exit(1) def return_dirs(root): mydirs = {} for pdir, dirs, files in os.walk(root): if not pdir in mydirs: mydirs[pdir] = files return mydirs class Id3TagHandler: '''Class for handling meta-tags. (Needs mutagen)''' accept = ['album', 'album_subtitle', 'albumartist', 'albumartistsort', 'albumsort', 'artist', 'artistsort', 'asin', 'bpm', 'comment', 'compilation', 'composer', 'composersort', 'conductor', 'copyright', 'date', 'discid', 'discnumber', 'encodedby', 'engineer', 'gapless', 'genre', 'grouping', 'isrc', 'label', 'lyricist', 'lyrics', 'mood', 'musicbrainz_albumartistid', 'musicbrainz_albumid', 'musicbrainz_artistid', 'musicbrainz_discid', 'musicbrainz_sortname', 'musicbrainz_trackid', 'musicbrainz_trmid', 'musicip_puid', 'podcast', 'podcasturl', 'releasecountry', 'musicbrainz_albumstatus', 'musicbrainz_albumtype', 'remixer', 'show', 'showsort', 'subtitle', 'title', 'titlesort', 'tracknumber', 'tracktotal'] def __init__(self, song): self.song = song self.tags = {} def grab_common(self, handler, convert=None, error=None): '''Common grabber, starts the handler and applies the tags to self.tags''' try: mydict = handler(self.song) except error, msg: import warnings,traceback; warn('Mutagen failed on %s, no tags available' % self.song) traceback.print_exc(0) print >> sys.stderr return if convert: convert = dict([(k.lower(), v.lower()) for k, v in convert.items()]) # Fix convert for key, val in mydict.items(): key = key.lower() key = convert and (key in convert and convert[key] or key) or key if not key in self.accept: continue if not convert: # Hack for FLAC, which uses Vorbis tags pass elif hasattr(val, 'text'): val = val.text if convert: new_val = [] if not isinstance(val, list): val = [val] for i in val: if not isinstance(i, unicode): # Convert all invalid values to unicode try: new_val.append(unicode(i)) except UnicodeDecodeError: warn('Ignoring UnicodeDecodeError in key %s' % key) new_val.append(unicode(i, errors='ignore')) else: new_val.append(i) val = new_val del new_val self.tags[key] = val def grab_m4a_tags(self): '''Import MP4 tags handler, set convert and call commonGrab''' convert = {'----:com.apple.iTunes:ASIN': 'asin', '----:com.apple.iTunes:MusicBrainz Album Artist Id': 'musicbrainz_albumartistid', '----:com.apple.iTunes:MusicBrainz Album Id': 'musicbrainz_albumid', '----:com.apple.iTunes:MusicBrainz Album Release Country': 'releasecountry', '----:com.apple.iTunes:MusicBrainz Album Status': 'musicbrainz_albumstatus', '----:com.apple.iTunes:MusicBrainz Album Type': 'musicbrainz_albumtype', '----:com.apple.iTunes:MusicBrainz Artist Id': 'musicbrainz_artistid', '----:com.apple.iTunes:MusicBrainz Disc Id': 'musicbrainz_discid', '----:com.apple.iTunes:MusicBrainz TRM Id': 'musicbrainz_trmid', '----:com.apple.iTunes:MusicBrainz Track Id': 'musicbrainz_trackid', '----:com.apple.iTunes:MusicIP PUID': 'musicip_puid', 'aART': 'albumartist', 'cpil': 'compilation', 'cprt': 'copyright', 'pcst': 'podcast', 'pgap': 'gapless', 'purl': 'podcasturl', 'soaa': 'albumartistsort', 'soal': 'albumsort', 'soar': 'artistsort', 'soco': 'composersort', 'sonm': 'titlesort', 'sosn': 'showsort', 'trkn': 'tracknumber', 'tvsh': 'show', '\xa9ART': 'artist', '\xa9alb': 'album', '\xa9cmt': 'comment', '\xa9day': 'date', '\xa9gen': 'genre', '\xa9grp': 'grouping', '\xa9lyr': 'lyrics', '\xa9nam': 'title', '\xa9too': 'encodedby','\xa9wrt': 'composer'} try: from mutagen.mp4 import MP4, error except ImportError: from mutagen.m4a import M4A as MP4, error self.grab_common(MP4, convert, error) def grab_wma_tags(self): '''Import ASF tags handler, set convert and call commonGrab''' convert = {'Author': 'artist', 'Description': 'comment', 'MusicBrainz/Album Artist Id': 'musicbrainz_albumartistid', 'MusicBrainz/Album Id': 'musicbrainz_albumid', 'MusicBrainz/Album Release Country': 'releasecountry', 'MusicBrainz/Album Status': 'musicbrainz_albumstatus', 'MusicBrainz/Album Type': 'musicbrainz_albumtype', 'MusicBrainz/Artist Id': 'musicbrainz_artistid', 'MusicBrainz/Disc Id': 'musicbrainz_discid', 'MusicBrainz/TRM Id': 'musicbrainz_trmid', 'MusicBrainz/Track Id': 'musicbrainz_trackid', 'MusicIP/PUID': 'musicip_puid', 'WM/AlbumArtist': 'albumartist', 'WM/AlbumArtistSortOrder': 'albumartistsort', 'WM/AlbumSortOrder': 'albumsort', 'WM/AlbumTitle': 'album', 'WM/ArtistSortOrder': 'artistsort', 'WM/BeatsPerMinute': 'bpm', 'WM/Composer': 'composer', 'WM/Conductor': 'conductor', 'WM/ContentGroupDescription': 'grouping', 'WM/Copyright': 'copyright', 'WM/EncodedBy': 'encodedby', 'WM/Genre': 'genre', 'WM/ISRC': 'isrc', 'WM/Lyrics': 'lyrics', 'WM/ModifiedBy': 'remixer', 'WM/Mood': 'mood', 'WM/PartOfSet': 'discnumber', 'WM/Producer': 'engineer', 'WM/Publisher': 'label', 'WM/SetSubTitle': 'album_subtitle', 'WM/SubTitle': 'subtitle', 'WM/TitleSortOrder': 'titlesort', 'WM/TrackNumber': 'tracknumber', 'WM/Writer': 'lyricist', 'WM/Year': 'date', } from mutagen.asf import ASF, error self.grab_common(ASF, convert, error) def grab_flac_tags(self): '''Import FLAC tags handler, and call commonGrab''' from mutagen.flac import FLAC, error self.grab_common(FLAC, error=error) def grab_flash_tags(self): '''Import FLAC tags handler, and call commonGrab''' pass def grab_speex_tags(self): '''Import speex tags handler, and call commonGrab''' from mutagen.oggspeex import OggSpeex, error self.grab_common(OggSpeex, error=error) def grab_mp3_tags(self): '''Import MP3 tags handler, and call commonGrab''' from mutagen.id3 import ID3, error convert = {'TPE1': 'artist', 'TPE2': 'albumartist', 'TPE3': 'conductor', 'TPE4': 'remixer', 'TCOM': 'composer', 'TCON': 'genre', 'TALB': 'album', 'TIT1': 'grouping', 'TIT2': 'title', 'TIT3': 'subtitle', 'TSST': 'discsubtitle', 'TEXT': 'lyricist', 'TCMP': 'compilation', 'TDRC': 'date', 'COMM': 'comment', 'TMOO': 'mood', 'TMED': 'media', 'TBPM': 'bpm', 'WOAR': 'website', 'TSRC': 'isrc', 'TENC': 'encodedby', 'TCOP': 'copyright', 'TSOA': 'albumsort', 'TSOP': 'artistsort', 'TSOT': 'titlesort','TPUB': 'label', 'TRCK': 'tracknumber'} self.grab_common(ID3, convert, error) def list_if_verbose(self): info('Meta-tags I will write:') for key, val in self.tags.items(): if type(val) == list: info(key + ': ' + ','.join(val)) else: info(key + ': ' + val) class Convert(Id3TagHandler): ''' Base conversion Class. __init__ creates some useful attributes, grabs the id3 tags, and sets a flag to remove files. Methods are the conversions we can do ''' def __init__(self, song, conf): self.device = "" self.track = "" Id3TagHandler.__init__(self, song) self.conf = conf song_root = os.path.splitext(song)[0] + "." self.songwav = song_root + 'wav' self.songogg = song_root + 'ogg' self.decoder = '' if (os.path.exists(self.songogg)): warn('try to convert to an already present file: %s' % self.songogg) return # (smartmp3) I have to remember default quality for next files original_quality = self.conf.quality for ext, pat in FILTERS.items(): if mmatch(self.song, pat) and ext != 'wav': self.decoder = getattr(self.conf, ext + '_decoder') getattr(self, 'grab_%s_tags' % ext)() if ext == 'mp3' and (self.conf.smart_mp3 or \ self.conf.smart_mp3_correction): self.smart_mp3() #self.songogg = "%(artist)s/%(album)s/%(track)s - %(title)s.ogg" % self.tags #self.songogg = "%(artist)s/%(album)s - %(title)s.ogg" % self.tags self.convert() # (smartmp3) Replacing quality by default value self.conf.quality = original_quality def smart_mp3(self): # initial Code by Marek Palatinus , 2007 # Table of quality = relation between mp3 bitrate and vorbis quality. Source: wikipedia # quality_table = {45:-1, 64:0, 80:1, 96:2, 112:3, 128:4, 160:5, 192:6, 224:7, 256:8, 320:9, 500:10 } # log(0.015*bitrate, 1.19) is logaritmic regression of table above. Useful for mp3s in VBR :-). try: from mutagen.mp3 import MP3, HeaderNotFoundError except ImportError: warn('(smartmp3) You dont have mutagen installed. Bitrate detection failed. Using default quality %.02f' % self.conf.quality) return try: mp3info = MP3(self.song) bitrate = mp3info.info.bitrate except HeaderNotFoundError: info('(smartmp3) File is not an mp3 stream. Using default quality %.02f' % self.conf.quality) return import math self.conf.quality = round(5.383 * math.log(0.01616 * bitrate/1000.) - self.conf.smart_mp3_correction, 2) self.conf.quality = max(self.conf.quality, -1) # Lowest quality is -1 self.conf.quality = min(self.conf.quality, 6) # Highest quality is 6 info("(smartmp3) Detected bitrate: %d kbps" % (bitrate/1000)) info("(smartmp3) Assumed vorbis quality: %.02f" % self.conf.quality) def decode(self): # Used for mplayer tempwav = 'dir2ogg-%s-temp.wav' % os.getpid() if self.decoder not in ('mplayer','speexdec') and not self.conf.no_pipe and not self.conf.preserve_wav: outfile, outfile1 = '-', '/dev/stdout' use_pipe = 1 else: outfile = outfile1 = self.songwav use_pipe = 0 decoder = {'mpg123': ['mpg123', '-w', outfile1, self.song], 'mpg321': ['mpg321', '-w', outfile, self.song], 'faad': ['faad', '-o' , outfile1, self.song], 'ogg123': ['ogg123', '-dwav', '-f' , outfile, self.song], 'flac': ['flac', '-o', outfile, '-d', self.song], 'speexdec':['speexdec', self.song, outfile], 'lame': ['lame', '--quiet', '--decode', self.song, outfile], 'mplayer': ['mplayer', '-vo', 'null', '-vc' ,'dummy', '-af', 'resample=44100', '-ao', 'pcm:file=' + tempwav, self.song], 'alac-decoder': ['alac-decoder', self.song], 'cd-cdparanoia': ['cdparanoia', '-Z', '-q', '-w', '-d', self.device, str(self.track), outfile], 'cd-icedax': ['icedax', '-H', '-t', str(self.track), '-D',self.device], 'cd-cdda2wav': ['cdda2wav', '-H', '-t', str(self.track), '-D',self.device], 'cd-mplayer': ['mplayer', '-vo', 'null', '-vc' ,'dummy', '-af', 'resample=44100', '-ao', 'pcm:file=temp.wav', '-cdrom-device', self.device, "cdda://" + str(self.track)]} if use_pipe: return True, Popen(decoder[self.decoder], stdout=PIPE) else: decoder['cd-cdparanoia'].remove('-q') decoder['lame'].remove('--quiet') retcode = call(decoder[self.decoder]) if self.decoder == 'mplayer': # Move the file for mplayer (which uses tempwav), so it works # for --preserve-wav. os.rename(tempwav, self.songwav) if retcode != 0: return (False, None) else: return (True, None) def convert(self): ''' Convert wav -> ogg.''' if self.songwav == self.song: success = True dec = None else: success, dec = self.decode() if not success: warn('Decoding of "%s" failed.' % self.song) return if dec and self.decoder == 'mpg123': import mutagen try: info("additional option:" ) opts=['-R', str(mutagen.File(self.song).info.sample_rate)] info(str(opts)) except: opts=[] else: opts=[] if dec: enc = Popen(['oggenc', '-Q', '-o', self.songogg, '-q', str(self.conf.quality).replace('.', ','), '-'] + opts, stdin=dec.stdout) enc.communicate() dec.wait() if dec.returncode < 0: warn('Decoding of "%s" failed.' % self.song) return False elif enc.returncode < 0: warn('Encoding of "%s" failed.' % self.song) return False else: enc = call(['oggenc', '-o', self.songogg, '-q', str(self.conf.quality).replace('.', ','), self.songwav]) if enc != 0: warn('Encoding of "%s" failed.' % self.songwav) return False elif not self.conf.preserve_wav and self.song != self.songwav: os.remove(self.songwav) if self.tags != {}: try: # Add tags to the ogg file from mutagen.oggvorbis import OggVorbis myogg = OggVorbis(self.songogg) myogg.update(self.tags) myogg.save() except: warn('Could not save the tags') import traceback traceback.print_exc() return False elif self.songwav != self.song or 'cd-' in self.decoder: warn('No tags found...') if self.conf.delete_input: os.remove(self.song) return True class ConvertTrack(Convert): '''Wrapper around Convert for CD Tracks''' def __init__(self, device, conf, track, tags): self.device, self.track, self.tags, self.conf = device, track, tags, conf self.song = '' self.songwav = "audio.wav" self.songogg = "%(artist)s/%(album)s/%(ntracknumber)s - %(title)s.ogg" % tags self.conf.preserve_wav = False self.decoder = 'cd-' + self.conf.cd_decoder self.convert() class ConvertDisc: '''Wrapper around ConvertTrack to Convert complete cds Currently uses MusicBrainz, but a CDDB fallback will be added, too.''' def __init__(self, dev, conf): warn("Converting CDs is not well supported, please use another " "solution.") self.dev, self.conf = dev, conf try: self.get_mb() except self.MBError: warn('MusicBrainz failed. Trying FreeDB...') self.get_cddb() class MBError(Exception): '''Empty''' def get_cddb(self): try: import CDDB, DiscID except ImportError: fatal('You need python-cddb (http://cddb-py.sf.net) to convert cds. Please install it.') disc_id = DiscID.disc_id(DiscID.open(self.dev)) query_info = CDDB.query(disc_id)[1] if not query_info: fatal('The disk is not listed in FreeDB, dir2ogg only supports disk listed in MusicBrainz or FreeDB') if isinstance(query_info, list): query_info = query_info[0] read_info = CDDB.read(query_info['category'], query_info['disc_id'])[1] for track in range(disc_id[1]): title = {} title['discid'] = query_info['disc_id'] title['artist'], title['album'] = (track.strip() for track in query_info['title'].split("/")) title['genre'] = read_info['DGENRE'] title['date'] = read_info['DYEAR'] title['title'] = read_info['TTITLE' + str(track)] title['tracktotal'] = str(len(range(disc_id[1])) + 1) title['ntracknumber'] = '0' * (len(title['tracktotal'] ) - len(str(track+1)) ) + str(track+1) title['tracknumber'] = str(track+1) for key, val in title.items(): title[key] = unicode(str(val), "ISO-8859-1") ConvertTrack(self.dev, self.conf, track+1, title) def get_mb(self): try: import musicbrainz2.disc as mbdisc import musicbrainz2.webservice as mbws except ImportError, err: warn('You need python-musicbrainz2 (or python-cddb) to convert cds. Please install it. Trying cddb.') raise self.MBError, err service = mbws.WebService() query = mbws.Query(service) # Read the disc in the drive try: disc = mbdisc.readDisc(self.dev) except mbdisc.DiscError, err: warn(err) raise self.MBError discId = disc.getId() try: myfilter = mbws.ReleaseFilter(discId=discId) results = query.getReleases(myfilter) except mbws.WebServiceError, err: warn(err) raise self.MBError if len(results) == 0: print "Disc is not yet in the MusicBrainz database." print "Consider adding it via", mbdisc.getSubmissionUrl(disc) raise self.MBError try: inc = mbws.ReleaseIncludes(artist=True, tracks=True, releaseEvents=True) release = query.getReleaseById(results[0].release.getId(), inc) except mbws.WebServiceError, err: warn(err) raise self.MBError isSingleArtist = release.isSingleArtistRelease() try: # try to get the CDDB ID import DiscID cddb_id = '%08lx' % long(DiscID.disc_id(DiscID.open(self.dev))[0]) except: cddb_id = False trackn = 1 for track in release.tracks: title = {} title['artist'] = isSingleArtist and release.artist.name or track.artist if cddb_id: title['discid'] = cddb_id title['album'] = release.title title['date'] = release.getEarliestReleaseDate() title['musicbrainz_albumartistid'] = release.artist.id.split("/")[-1] title['musicbrainz_albumid'] = release.id.split("/")[-1] title['musicbrainz_discid'] = discId title['musicbrainz_sortname'] = release.artist.sortName title['musicbrainz_trackid'] = track.id.split("/")[-1] title['title'] = track.title title['tracktotal'] = str(len(release.tracks)) title['ntracknumber'] = "%02d" % trackn title['tracknumber'] = str(trackn) ConvertTrack(self.dev, self.conf, trackn, title) trackn+=1 class ConvertDirectory: ''' This class is just a wrapper for Convert. Grab the songs to convert, then feed them one by one to the Convert class. ''' def __init__(self, conf, directory, files): ''' Decide which files will be converted.''' if os.path.exists(directory) == 0: fatal('Directory: "%s" not found' % directory) self.directory = directory = os.path.normpath(directory) + os.path.sep self.songs = mmatch(files, conf.filters, False) if conf.verbose: self.print_if_verbose() for song in self.songs: try: Convert(directory + song, conf) except: warn('File: %s error in ogg conversion' % directory + song) def print_if_verbose(self): ''' Echo files to be converted if verbose flag is set.''' info('In %s I am going to convert:' % self.directory) for song in self.songs: print " ", song def show_banner(): print 'dir2ogg %s (%s), converts audio files into ogg vorbis.\n' % (__version__, __date__) def show_license(*args, **kwargs): print 'Copyright (C) 2007-2008 Julian Andres Klode ' print 'Copyright (C) 2003-2006 Darren Kirby \n' print 'This program is distributed in the hope that it will be useful,' print 'but WITHOUT ANY WARRANTY; without even the implied warranty of' print 'MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the' print 'GNU General Public License for more details.\n' print 'Currently developed by Julian Andres Klode .' sys.exit(0) def main(): conf = read_opts() conf_args, conf = conf[1], conf[0] if conf.cdda: discs = len(conf_args) and conf_args or ("/dev/cdrom",) for disc in discs: ConvertDisc(disc, conf) elif conf.directory or conf.recursive: rdirs = {} for path in conf_args: if not os.path.isdir(path): fatal('Path: %s does not exists' % path) elif conf.recursive: rdirs.update(return_dirs(path)) elif conf.directory: rdirs.update({path: os.listdir(path)}) for directory, files in rdirs.items(): try: ConvertDirectory(conf, directory, files) except: warn('DIR: %s ;Files: %s error in ogg conversion' % (directory,files)) else: for path in conf_args: if not os.path.isfile(path): fatal('Path: %s does not exists' % path) for filename in conf_args: try: Convert(filename, conf) except: warn('File: %s error in ogg conversion' % filename) sys.exit(0) if __name__ == '__main__': main() autoradio-2.8.6/autoradio/urls.py0000664000175000017500000000210713001105756016600 0ustar pat1pat100000000000000from django.conf.urls import * import settings from django.conf.urls.static import static # Uncomment the next two lines to enable the admin: from django.contrib import admin admin.autodiscover() from django.contrib.staticfiles import views urlpatterns = [ # 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: url(r'^admin/doc/', include('django.contrib.admindocs.urls')), # Uncomment the next line to enable the admin: url(r'^admin/', include(admin.site.urls)), url(r'^', include('autoradio.programs.urls')), # url(r'^', include('autoradio.palimpsest.urls')), url(r'^podcasts/', include('autoradio.programs.urls_podcast')), url(r'^player/', include('autoradio.player.urls')), url(r'^doc/', include('autoradio.doc.urls')), ] if ( settings.SERVE_STATIC ): #serve media files urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) autoradio-2.8.6/autoradio/__init__.py0000664000175000017500000000002213003436035017343 0ustar pat1pat100000000000000_version_="2.8.6" autoradio-2.8.6/autoradio/jingles/0000775000175000017500000000000013003471473016700 5ustar pat1pat100000000000000autoradio-2.8.6/autoradio/jingles/views.py0000664000175000017500000000003213001105756020376 0ustar pat1pat100000000000000# Create your views here. autoradio-2.8.6/autoradio/jingles/urls.py0000664000175000017500000000031713001105756020234 0ustar pat1pat100000000000000from django.conf.urls.defaults import * #from models import Fascia, Spot # #urlpatterns = patterns('', # (r'^$', 'programs.views.index'), # (r'^/schedule/(?P\d+)/$', 'views.detail'), # #) autoradio-2.8.6/autoradio/jingles/__init__.py0000664000175000017500000000000013001105756020773 0ustar pat1pat100000000000000autoradio-2.8.6/autoradio/jingles/admin.py0000664000175000017500000000540413001105756020341 0ustar pat1pat100000000000000from 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 fields = '__all__' 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-2.8.6/autoradio/jingles/models.py0000664000175000017500000001200013001105756020522 0ustar pat1pat100000000000000from 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'),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-2.8.6/autoradio/jingles/migrations/0000775000175000017500000000000013003471473021054 5ustar pat1pat100000000000000autoradio-2.8.6/autoradio/jingles/migrations/__init__.py0000664000175000017500000000000013001105756023147 0ustar pat1pat100000000000000autoradio-2.8.6/autoradio/jingles/migrations/0001_initial.py0000664000175000017500000000641413001105756023520 0ustar pat1pat100000000000000# -*- coding: utf-8 -*- # Generated by Django 1.9 on 2016-01-25 17:57 from __future__ import unicode_literals import autoradio.jingles.models from django.db import migrations, models class Migration(migrations.Migration): initial = True dependencies = [ ] operations = [ migrations.CreateModel( name='Configure', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('sezione', models.CharField(default=b'jingle', editable=False, max_length=50, unique=True)), ('active', models.BooleanField(default=True, help_text='activate/deactivate the intere jingle class', verbose_name='Activate Jingle')), ('emission_freq', models.TimeField(verbose_name='Frequency')), ], ), migrations.CreateModel( name='Giorno', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('name', models.CharField(choices=[('luned\xec', 'luned\xec'), ('marted\xec', 'marted\xec'), ('mercoled\xec', 'mercoled\xec'), ('gioved\xec', 'gioved\xec'), ('venerd\xec', 'venerd\xec'), ('sabato', 'sabato'), ('domenica', 'domenica')], help_text='weekday name', max_length=20, unique=True)), ], ), migrations.CreateModel( name='Jingle', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('jingle', models.CharField(max_length=80, unique=True, verbose_name='Jingle name')), ('file', autoradio.jingles.models.DeletingFileField(help_text='The jingle file to upload', max_length=255, upload_to=b'jingles', verbose_name='File')), ('rec_date', models.DateTimeField(help_text='When the jingle was done (for reference only)', verbose_name='Recording date')), ('active', models.BooleanField(default=True, help_text='Activate the jingle for emission', verbose_name='Active')), ('start_date', models.DateField(blank=True, help_text='The jingle will be scheduled starting from this date', null=True, verbose_name='Emission starting date')), ('end_date', models.DateField(blank=True, help_text='The jingle will be scheduled ending this date', null=True, verbose_name='Emission end date')), ('start_time', models.TimeField(blank=True, help_text='The jingle will be scheduled starting from this date', null=True, verbose_name='Emission start time')), ('end_time', models.TimeField(blank=True, help_text='The jingle will be scheduled ending this date', null=True, verbose_name='Emission end time')), ('priorita', models.IntegerField(default=50, help_text='When there are more jingle that wait for emission from the same time, the emission will be ordered by this numer', verbose_name='Priority')), ('emission_done', models.DateTimeField(editable=False, null=True, verbose_name='emission done')), ('giorni', models.ManyToManyField(blank=True, help_text='The jingle will be scheduled those weekdays', to='jingles.Giorno', verbose_name='Scheduled days')), ], ), ] autoradio-2.8.6/autoradio/autompris2.py0000664000175000017500000003024413001105756017723 0ustar pat1pat100000000000000#!/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-2.8.6/autoradio/autoplayer/0000775000175000017500000000000013003471473017432 5ustar pat1pat100000000000000autoradio-2.8.6/autoradio/autoplayer/player.py0000664000175000017500000010556013001105756021303 0ustar pat1pat100000000000000#!/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) # attentions! # for now we use TrackListReplaced with a empty and wrong signature # everywhere we change the tracklist # we use this in autoplayergui to update the tracklist 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) # TrackListReplaced (ao: Tracks, o: CurrentTrack) @dbus.service.signal(TRACKLIST_IFACE,signature='') def TrackListReplaced(self): logging.debug("TrackListReplaced") # 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) # TrackRemoved (o: TrackId) @dbus.service.signal(TRACKLIST_IFACE,signature='o') def TrackRemoved(self,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() self.TrackListReplaced() #TODO #self.TrackAdded(uri,"0") #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) self.TrackListReplaced() @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 ?? logging.debug("TrackRemoved %s" % trackid) #TODO #self.TrackRemoved(trackid) self.TrackListReplaced() @dbus.service.method(TRACKLIST_IFACE,in_signature='s', out_signature='') def GoTo(self, trackid): self.player.goto(trackid) self.TrackListReplaced() @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") # ReplayGain if (Gst.ElementFactory.find('rgvolume') and Gst.ElementFactory.find('rglimiter')): self.audioconvert = Gst.ElementFactory.make('audioconvert',None) self.rgvolume = Gst.ElementFactory.make('rgvolume',None) self.rgvolume.set_property('album-mode', False) self.rgvolume.set_property('pre-amp', 0) self.rgvolume.set_property('fallback-gain', 0) self.rgvolume.set_property('headroom',0) self.rgvolume.set_property('pre-amp',0) self.rglimiter = Gst.ElementFactory.make('rglimiter',None) self.rglimiter.set_property('enabled', True) self.rgfilter = Gst.Bin() self.rgfilter.add(self.rgvolume) self.rgfilter.add(self.rglimiter) self.rgvolume.link(self.rglimiter) self.rgfilter.add_pad(Gst.GhostPad.new('sink', self.rgvolume.get_static_pad('sink'))) self.rgfilter.add_pad(Gst.GhostPad.new('src', self.rglimiter.get_static_pad('src'))) try: self.player.set_property('audio-filter', self.rgfilter) except: logging.error( "setting replaygain player") #raise Exception("cannot manage replaygain!") # TODO replaygain #+++++++ # #Example 40 # #From project rhythmbox-multiple-libraries, under directory plugins/replaygain/replaygain, in source file player.py. # #def setup_playbin2_mode(self): # print "using output filter for rgvolume and rglimiter" # self.rgvolume = gst.element_factory_make("rgvolume") # self.rgvolume.connect("notify::target-gain", self.playbin2_target_gain_cb) # self.rglimiter = gst.element_factory_make("rglimiter") # # # on track changes, we need to reset the rgvolume state, otherwise it # # carries over the tags from the previous track # self.pec_id = self.shell_player.connect('playing-song-changed', self.playing_entry_changed) # # # watch playbin2's uri property to see when a new track is opened # playbin = self.player.props.playbin # if playbin is None: # self.player.connect("notify::playbin", self.playbin2_notify_cb) # else: # playbin.connect("notify::uri", self.playbin2_uri_notify_cb) # # self.rgfilter = gst.Bin() # self.rgfilter.add(self.rgvolume, self.rglimiter) # self.rgvolume.link(self.rglimiter) # self.rgfilter.add_pad(gst.GhostPad("sink", self.rgvolume.get_static_pad("sink"))) # self.rgfilter.add_pad(gst.GhostPad("src", self.rglimiter.get_static_pad("src"))) # self.player.add_filter(self.rgfilter) # #+++++++++ 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") currenturi = self.playlist.get_current().path logging.warning("current media: %s" % currenturi) 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(STATUS_PLAYLIST) #plmpris=playlist.Playlist_mpris2(pl,pl.current,pl.position) plmpris=playlist.Playlist_mpris2(pl) #save to update playlist to make monit happy pl.write(STATUS_PLAYLIST) 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,STATUS_PLAYLIST) #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-2.8.6/autoradio/autoplayer/__init__.py0000664000175000017500000000000113001105756021526 0ustar pat1pat100000000000000 autoradio-2.8.6/autoradio/autoplayer/player_gstreamer0.py0000664000175000017500000007570313001105756023441 0ustar pat1pat100000000000000#!/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-2.8.6/autoradio/autoplayer/playlist.py0000664000175000017500000003755613001105756021661 0ustar pat1pat100000000000000#!/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) if (url.scheme == "http"): s.track['location']=url.geturl() else: 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"))) if (url.scheme == "http"): location=url.geturl() else: 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) if (url.scheme == "http"): location=url.geturl() else: #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-2.8.6/autoradio/autoplayer/mpris2client.py0000775000175000017500000000531613001105756022423 0ustar pat1pat100000000000000#!/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-2.8.6/autoradio/spots/0000775000175000017500000000000013003471473016415 5ustar pat1pat100000000000000autoradio-2.8.6/autoradio/spots/views.py0000664000175000017500000000003213001105756020113 0ustar pat1pat100000000000000# Create your views here. autoradio-2.8.6/autoradio/spots/urls.py0000664000175000017500000000056013001105756017751 0ustar pat1pat100000000000000from django.conf.urls.defaults import * #from models import Program, Schedule #urlpatterns = patterns('', # (r'^$', 'programs.views.index'), # (r'^/schedule/(?P\d+)/$', 'views.detail'), # (r'^schedule/(?P\d+)/results/$', 'mysite.polls.views.results'), # (r'^schedule/(?P\d+)/vote/$', 'mysite.polls.views.vote'), #) autoradio-2.8.6/autoradio/spots/__init__.py0000664000175000017500000000000013001105756020510 0ustar pat1pat100000000000000autoradio-2.8.6/autoradio/spots/admin.py0000664000175000017500000000634713001105756020065 0ustar pat1pat100000000000000from models import Giorno, Configure, Fascia, Spot 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 MySpotAdminForm(forms.ModelForm): """ Check file if it is a known media file. """ class Meta: model = Spot fields = '__all__' 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_starttime','emission_endtime') admin.site.register(Configure, ConfigureAdmin) class FasciaAdmin(admin.ModelAdmin): search_fields = ['name','spots'] list_display = ('name','emission_time','emission_done','active','spots') admin.site.register(Fascia, FasciaAdmin) class SpotAdmin(admin.ModelAdmin): fieldsets = ( (None, {'fields': ('spot','file','rec_date')}), ('Emission information', {'fields': \ ('start_date','end_date','giorni','fasce','priorita','prologo','epilogo')}), ) # list_display = ('spot', 'rec_date', 'was_recorded_today','giorni','fasce','priorita') list_filter = ['start_date','end_date','rec_date','fasce','giorni',"prologo","epilogo"] # rec_date sarebbe standard, ma per eliminare cose vecchie meglio usare end_date #date_hierarchy = 'rec_date' date_hierarchy = 'end_date' search_fields = ['spot','giorni__name','fasce__name',] list_display = ('spot','file','rec_date','priorita') form = MySpotAdminForm admin.site.register(Spot, SpotAdmin) autoradio-2.8.6/autoradio/spots/models.py0000664000175000017500000002024013001105756020244 0ustar pat1pat100000000000000from 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 Giorno(models.Model): ## #DAY_CHOICES = ( ## # ('1', 'Lunedi'), ## # ('2', 'Martedi'), ## # ('3', 'Mercoledi'), ## # ('4', 'Giovedi'), ## # ('5', 'Venerdi'), ## # ('6', 'Sabato'), ## # ('7', 'Domenica'), ## # ) ## DAY_CHOICES = ( ## ( 'Lunedi','Lunedi'), ## ( 'Martedi','Martedi'), ## ( 'Mercoledi','Mercoledi'), ## ( 'Giovedi','Giovedi'), ## ( 'Venerdi','Venerdi'), ## ( 'Sabato','Sabato'), ## ( 'Domenica','Domenica'), ## ) ## name = models.CharField(max_length=20,choices=DAY_CHOICES,unique=True) ## #name = models.CharField(max_length=20) ## def __unicode__(self): ## return self.name ## class Admin: ## search_fields = ['name'] class Configure(models.Model): sezione = models.CharField(max_length=50,unique=True,default='spot',editable=False) active = models.BooleanField(ugettext_lazy("Activate Spot"),default=True,\ help_text=ugettext_lazy("activate/deactivate the intere spot class")) emission_starttime = models.TimeField(ugettext_lazy('Programmed start time'),null=True,blank=True,\ help_text=ugettext_lazy("The start time from wich the spot will be active")) emission_endtime = models.TimeField(ugettext_lazy('Programmed end time'),null=True,blank=True,\ help_text=ugettext_lazy("The end time the spot will be active")) def __unicode__(self): return self.sezione+" "+self.active.__str__()+" "+self.emission_starttime.isoformat()\ +" "+self.emission_endtime.isoformat() class Admin: list_display = ('sezione','active','emission_starttime','emission_endtime') class Fascia(models.Model): name = models.CharField(max_length=50,unique=True,\ help_text=ugettext_lazy("The name of commercial break")) emission_time = models.TimeField(unique=True,\ help_text=ugettext_lazy("This is the date and time when the commercial break will be on air")) active = models.BooleanField(ugettext_lazy("Active"),default=True,\ help_text=ugettext_lazy("Activate the commercial break for emission")) emission_done = models.DateTimeField(ugettext_lazy('Emission done'),null=True,editable=False ) def spots(self): # print self.spot_set.filter(prologo__exact=True).order_by('priorita').append(\ # self.spot_set.exclude(prologo__exact=True).exclude(epilogo__exact=True).order_by('priorita')).append(\ # self.spot_set.filter(epilogo__exact=True).order_by('priorita')) return self.spot_set.all().order_by('prologo').order_by('priorita').order_by('epilogo') # filter(prologo__exact=True) # return di # di={} # di["bb"]="ciccia" # di["cc"]="cacca" # return di # sps="" # for sp in di.iterkeys(): # sps=sps+di[sp] # return sps # .append(\ # self.spot_set.exclude(prologo__exact=True).exclude(epilogo__exact=True).order_by('priorita')).append(\ # self.spot_set.filter(epilogo__exact=True).order_by('priorita')) def __unicode__(self): return self.name+" "+self.emission_time.isoformat() class Admin: search_fields = ['name','emission_time','emission_done','active','spots'] list_display = ('name','emission_time','emission_done','active','spots') class Spot(models.Model): spot = models.CharField(ugettext_lazy("Spot Name"),max_length=80,unique=True,\ help_text=ugettext_lazy("The name of the spot")) file = DeletingFileField(ugettext_lazy('File'),upload_to='spots',max_length=255,\ help_text=ugettext_lazy("The spot file to upload")) rec_date = models.DateTimeField(ugettext_lazy('Record date'),\ help_text=ugettext_lazy("When the spot was done (for reference only)")) active = models.BooleanField(ugettext_lazy("Active"),default=True,\ help_text=ugettext_lazy("Activate the spot for emission")) start_date = models.DateTimeField(ugettext_lazy('Programmed starting date'),null=True,blank=True,\ help_text=ugettext_lazy("The spot will be scheduled starting from this date")) end_date = models.DateTimeField(ugettext_lazy('Programmed ending date'),null=True,blank=True,\ help_text=ugettext_lazy("The spot will be scheduled ending this date")) # giorni = models.PositiveIntegerField( choices=DAY_CHOICES) # giorni = models.ForeignKey(Giorno,verbose_name='Giorni programmati',editable=False) giorni = models.ManyToManyField(Giorno,verbose_name=ugettext_lazy('Programmed days'),blank=True,\ help_text=ugettext_lazy("The spot will be scheduled those weekdays")) fasce = models.ManyToManyField(Fascia,blank=True,\ help_text=ugettext_lazy("The spot will be included in those commercial break")) priorita = models.IntegerField(ugettext_lazy("Priority"),default=50,\ help_text=ugettext_lazy("The order of the spots in commercial breaks will be ordered by this numer")) prologo = models.BooleanField(ugettext_lazy("Prologue"),default=False,\ help_text=ugettext_lazy("This spot will be the firt in commercial breaks to introduce the others")) epilogo = models.BooleanField(ugettext_lazy("Epilogue"),default=False,\ help_text=ugettext_lazy("This spot will be the last in commercial breaks to leave-taking")) 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.spot class Admin: fields = ( (None, {'fields': ('spot','file','rec_date')}), ('Emission information', {'fields': \ ('start_date','end_date','giorni','fasce','priorita','prologo','epilogo')}), ) # list_display = ('spot', 'rec_date', 'was_recorded_today','giorni','fasce','priorita') list_filter = ['start_date','end_date','fasce','giorni',"prologo","epilogo"] date_hierarchy = 'rec_date' search_fields = ['spot','giorni','fascie'] list_display = ('spot','file','rec_date','priorita') #class Meta: # unique_together = ("prologo", "epilogo","fasce") autoradio-2.8.6/autoradio/spots/migrations/0000775000175000017500000000000013003471473020571 5ustar pat1pat100000000000000autoradio-2.8.6/autoradio/spots/migrations/__init__.py0000664000175000017500000000000013001105756022664 0ustar pat1pat100000000000000autoradio-2.8.6/autoradio/spots/migrations/0001_initial.py0000664000175000017500000001040413001105756023227 0ustar pat1pat100000000000000# -*- coding: utf-8 -*- # Generated by Django 1.9 on 2016-01-25 17:57 from __future__ import unicode_literals import autoradio.spots.models from django.db import migrations, models class Migration(migrations.Migration): initial = True dependencies = [ ] operations = [ migrations.CreateModel( name='Configure', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('sezione', models.CharField(default=b'spot', editable=False, max_length=50, unique=True)), ('active', models.BooleanField(default=True, help_text='activate/deactivate the intere spot class', verbose_name='Activate Spot')), ('emission_starttime', models.TimeField(blank=True, help_text='The start time from wich the spot will be active', null=True, verbose_name='Programmed start time')), ('emission_endtime', models.TimeField(blank=True, help_text='The end time the spot will be active', null=True, verbose_name='Programmed end time')), ], ), migrations.CreateModel( name='Fascia', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('name', models.CharField(help_text='The name of commercial break', max_length=50, unique=True)), ('emission_time', models.TimeField(help_text='This is the date and time when the commercial break will be on air', unique=True)), ('active', models.BooleanField(default=True, help_text='Activate the commercial break for emission', verbose_name='Active')), ('emission_done', models.DateTimeField(editable=False, null=True, verbose_name='Emission done')), ], ), migrations.CreateModel( name='Giorno', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('name', models.CharField(choices=[('luned\xec', 'luned\xec'), ('marted\xec', 'marted\xec'), ('mercoled\xec', 'mercoled\xec'), ('gioved\xec', 'gioved\xec'), ('venerd\xec', 'venerd\xec'), ('sabato', 'sabato'), ('domenica', 'domenica')], help_text='weekday name', max_length=20, unique=True)), ], ), migrations.CreateModel( name='Spot', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('spot', models.CharField(help_text='The name of the spot', max_length=80, unique=True, verbose_name='Spot Name')), ('file', autoradio.spots.models.DeletingFileField(help_text='The spot file to upload', max_length=255, upload_to=b'spots', verbose_name='File')), ('rec_date', models.DateTimeField(help_text='When the spot was done (for reference only)', verbose_name='Record date')), ('active', models.BooleanField(default=True, help_text='Activate the spot for emission', verbose_name='Active')), ('start_date', models.DateTimeField(blank=True, help_text='The spot will be scheduled starting from this date', null=True, verbose_name='Programmed starting date')), ('end_date', models.DateTimeField(blank=True, help_text='The spot will be scheduled ending this date', null=True, verbose_name='Programmed ending date')), ('priorita', models.IntegerField(default=50, help_text='The order of the spots in commercial breaks will be ordered by this numer', verbose_name='Priority')), ('prologo', models.BooleanField(default=False, help_text='This spot will be the firt in commercial breaks to introduce the others', verbose_name='Prologue')), ('epilogo', models.BooleanField(default=False, help_text='This spot will be the last in commercial breaks to leave-taking', verbose_name='Epilogue')), ('fasce', models.ManyToManyField(blank=True, help_text='The spot will be included in those commercial break', to='spots.Fascia')), ('giorni', models.ManyToManyField(blank=True, help_text='The spot will be scheduled those weekdays', to='spots.Giorno', verbose_name='Programmed days')), ], ), ] autoradio-2.8.6/autoradio/autompris.py0000664000175000017500000002314713001105756017645 0ustar pat1pat100000000000000#!/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-2.8.6/autoradio/player/0000775000175000017500000000000013003471473016541 5ustar pat1pat100000000000000autoradio-2.8.6/autoradio/player/views.py0000664000175000017500000000102313001105756020240 0ustar pat1pat100000000000000from django.shortcuts import render_to_response import autoradio.settings def playercmd(request, media): """ player commander Template: ``player/player.html`` Context: play a media file """ return render_to_response('player/player.html', {'media': media}) def playernohtml5cmd(request, media): """ player commander for no html5 Template: ``player/playernohtml5.html`` Context: play a media file """ return render_to_response('player/playernohtml5.html', {'media': media}) autoradio-2.8.6/autoradio/player/urls.py0000664000175000017500000000032713001105756020076 0ustar pat1pat100000000000000from django.conf.urls import * import views urlpatterns = [ # Episode detail of one show url(r'^nohtml5/(?P(.*))', views.playernohtml5cmd), url(r'^(?P(.*))', views.playercmd), ] autoradio-2.8.6/autoradio/player/__init__.py0000664000175000017500000000003313001105756020642 0ustar pat1pat100000000000000VERSION = (0, "1.0", None) autoradio-2.8.6/autoradio/doc/0000775000175000017500000000000013003471473016012 5ustar pat1pat100000000000000autoradio-2.8.6/autoradio/doc/urls.py0000664000175000017500000000106613003130234017337 0ustar pat1pat100000000000000from 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 = [ url(r'^$', TemplateView.as_view(template_name="doc/index.html")), url(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-2.8.6/autoradio/doc/__init__.py0000664000175000017500000000000013001105756020105 0ustar pat1pat100000000000000autoradio-2.8.6/autoradio/doc/templates/0000775000175000017500000000000013003471473020010 5ustar pat1pat100000000000000autoradio-2.8.6/autoradio/doc/templates/doc/0000775000175000017500000000000013003471473020555 5ustar pat1pat100000000000000autoradio-2.8.6/autoradio/doc/templates/doc/doc.html0000664000175000017500000004212413001224411022175 0ustar pat1pat100000000000000{% extends "admin/base_site.html" %} {% load i18n %} {% block content %}

{% trans 'Documentation Home' %}

{% if 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 (gstreamer): 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 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 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à:
  • Autoplayerd: Questo player è basato su gstreamer e quindi disponibile su tutte le nuove distribuzioni. Il formato dei file audio è determinato dai plugin di gstreamer installati. Questo player non ha bisogno di un terminale per l'interazione umana. Espone una interfaccia a standard mpris2 su dbus e quindi qualsiasi applicazione che interagisca con questa interfaccia può impartire comandi al player. Autoradio fornisce autoplayergui che ha una interfaccia grafica tramite la quale è possibile eseguire tutte le funzioni elementari quali play, pause, stop etc. Il vantaggio è che un computer senza terminale quale un server può funzionare e avere questo player in modalità daemon, ossia sempre attiva. Il player permette l'invio diretto a un server per lo streaming per la realizzazione di web radio. Questo è il player preferito per l'utilizzo con AutoRadio.
  • 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.
  • 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 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 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 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 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 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 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-2.8.6/autoradio/doc/templates/doc/index.html0000664000175000017500000000365613001105756022560 0ustar pat1pat100000000000000{% 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-2.8.6/autoradio/programs/0000775000175000017500000000000013003471473017077 5ustar pat1pat100000000000000autoradio-2.8.6/autoradio/programs/urls_podcast.py0000664000175000017500000000221213001105756022144 0ustar pat1pat100000000000000from django.conf.urls import * #from django.contrib import admin #from models import Program, Schedule from views import show_list, episode_list, show_list_feed, show_list_atom, show_list_media, episode_sitemap, episode_detail urlpatterns = [ # Show list of all shows url(r'^$', view=show_list.as_view(), name='podcast_shows'), # Episode list of one show url(r'^(?P[-\w]+)/$', view=episode_list.as_view(), name='podcast_episodes'), # Episode list feed by show (RSS 2.0 and iTunes) url(r'^(?P[-\w]+)/feed/$', view=show_list_feed.as_view(), name='podcast_feed'), # Episode list feed by show (Atom) url(r'^(?P[-\w]+)/atom/$', view=show_list_atom.as_view(), name='podcast_atom'), # Episode list feed by show (Media RSS) url(r'^(?P[-\w]+)/media/$', view=show_list_media.as_view(), name='podcast_media'), # Episode sitemap list of one show url(r'^(?P[-\w]+)/sitemap.xml$', view=episode_sitemap.as_view(), name='podcast_sitemap'), # Episode detail of one show url(r'^(?P[-\w]+)/(?P[-\w]+)/$', view=episode_detail.as_view(), name='podcast_episode'), ] autoradio-2.8.6/autoradio/programs/fixtures/0000775000175000017500000000000013003471473020750 5ustar pat1pat100000000000000autoradio-2.8.6/autoradio/programs/fixtures/initial_data.json0000664000175000017500000003720613001105756024271 0ustar pat1pat100000000000000 [ {"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-2.8.6/autoradio/programs/views.py0000664000175000017500000004155513001105756020614 0ustar pat1pat100000000000000# Create your views here. from django.shortcuts import render_to_response from models import Schedule from django.http import HttpResponse,HttpResponseRedirect from datetime import date,datetime,timedelta,time import autoradio.autoradio_config import autoradio.autoradio_core import autoradio.settings import autoradio.autompris2 import os #from django.forms.extras.widgets import SelectDateWidget from widgets import MySelectDateWidget from django.utils.translation import ugettext_lazy #---------------------------------------------------- # section for programs #def index_old(request): # latesst_schedule_list = Schedule.objects.filter(emission_date__gte=datetime.now()).order_by('emission_date')[:5] # latest_schedule_done = Schedule.objects.filter(emission_date__lt=datetime.now()).order_by('emission_date')[:5] # return render_to_response('schedule/index.html', {'latest_schedule_done': latest_schedule_done , 'latest_schedule_list': latest_schedule_list}) # #def detail(request, schedule_id): # return HttpResponse("You're looking at %s." % schedule_id) def index(request): scheds=autoradio.autoradio_core.schedules([]) return render_to_response('schedule/index.html', {'schedule': scheds.get_all_refine(genfile=False)}) def stato(request): import urllib2 xmmsweb="" try: url=urllib2.urlopen("http://"+autoradio.autoradio_config.XMMSHOST+":8888/") for line in url: xmmsweb=xmmsweb+line except: xmmsweb="

Error getting player status !!

" xmmsweb=xmmsweb+"

Start autoradiod or verify settings

" return render_to_response('xmms/index.html', {'xmmsweb': xmmsweb,}) def dbusstato(request): maxplele=100 player = autoradio.autoradio_config.player htmlresponse="" if player == "vlc" or player == "AutoPlayer": try: mp= autoradio.autompris2.mediaplayer(player=player,session=0) except: htmlresponse +="

Error getting player status !!

" htmlresponse +=htmlresponse+"

Start autoradiod or verify settings

" return render_to_response('xmms/index.html', {'xmmsweb': htmlresponse,}) else: return stato(request) #htmlresponse += "Invalid player for dbus interface" try: cpos=mp.get_playlist_pos() if cpos is None: cpos=0 cpos=int(cpos) isplaying= mp.isplaying() 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=timedelta(seconds=timedelta(milliseconds=metadata["mtimelength"]).seconds) timeposition=timedelta(seconds=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+='' htmlresponse+='
positionlenght // remainmedia
%i %s // %s %s // %s%i %s // %s %s
' if len > maxplele : htmlresponse+="

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

" except: htmlresponse +="

Error getting player status !!

" htmlresponse +=htmlresponse+"

Start autoradiod or verify settings

" del mp return render_to_response('xmms/index.html', {'xmmsweb': htmlresponse,}) #---------------------------------------------------- # section for palimpsest from django import forms from reportlab.platypus import * def decode(code): car="" digit="" for i in range(len(code)): if code[i-1].isdigit(): digit+=code[i-1] else: car+=code[i-1] if len(car) > 0 : car=digit+car return digit,car class ExtremeForm(forms.Form): initial_start=date.today()-timedelta(days=10) initial_end=date.today() # datetime_start = forms.DateTimeField(required=True,initial=initial_start,widget=SelectDateWidget(years=(2010,etc))) # datetime_end = forms.DateTimeField(required=True,initial=initial_end,widget=SelectDateWidget(years=(2010,etc))) datetime_start = forms.DateTimeField(required=True,initial=initial_start,widget=MySelectDateWidget(),label=ugettext_lazy("Starting date & time"),help_text=ugettext_lazy("Elaborate palimpsest starting from this date and time")) datetime_end = forms.DateTimeField(required=True,initial=initial_end,widget=MySelectDateWidget(),label=ugettext_lazy("Ending date & time"),help_text=ugettext_lazy("Elaborate palimpsest ending to this date and time")) def programsbook(request): from reportlab.lib.styles import getSampleStyleSheet from reportlab.rl_config import defaultPageSize from reportlab.lib.units import inch from reportlab.lib import colors if request.method == 'POST': # If the form has been submitted... form = ExtremeForm(request.POST) # A form bound to the POST data if form.is_valid(): # All validation rules pass def myPages(canvas, doc): pageinfo="Libro Programmi" canvas.saveState() canvas.setFont('Times-Roman',9) canvas.drawString(inch, 0.75 * inch, "Pagina %d %s" % (doc.page, pageinfo)) canvas.restoreState() # time constants now=datetime.now() minelab=1 datetime_start=form.cleaned_data['datetime_start'] datetime_end=form.cleaned_data['datetime_end'] datetime_start = datetime.combine(datetime_start.date(),time(00)) datetime_end = datetime.combine(datetime_end.date(),time(23,59)) #datetime_start=(now-timedelta(days=4)) #datetime_end=now pro=autoradio.gest_palimpsest.gest_palimpsest(now,minelab) emittente,canale,mezzo,trasmissione=pro.get_info() author = "Autoradio Radio Automation Free Software" # Create the HttpResponse object with the appropriate PDF headers. response = HttpResponse(content_type='application/pdf') response['Content-Disposition'] = 'attachment; filename=somefilename.pdf' PAGE_HEIGHT=defaultPageSize[1] styles = getSampleStyleSheet() MezzoTrasmissione=Paragraph("Mezzo di diffusione: "+unicode(mezzo)+ " // Tipo di trasmissione: "+unicode(trasmissione), styles["Normal"]) EmittenteCanale=Paragraph("Denominazione dell'emittente: "+unicode(emittente)+ " // Denominazione del canale: "+unicode(canale), styles["Normal"]) Space=Spacer(inch, 0.25 * inch) # First the top row, with all the text centered and in Times-Bold, # and one line above, one line below. ts = [('ALIGN', (1,1), (-1,-1), 'CENTER'), ('LINEABOVE', (0,0), (-1,0), 1, colors.purple), ('LINEBELOW', (0,0), (-1,0), 1, colors.purple), ('FONT', (0,0), (-1,0), 'Times-Bold'), # The bottom row has one line above, and three lines below of # various colors and spacing. #('LINEABOVE', (0,-1), (-1,-1), 1, colors.purple), #('LINEBELOW', (0,-1), (-1,-1), 0.5, colors.purple, # 1, None, None, 4,1), ('LINEBELOW', (0,-1), (-1,-1), 1, colors.red), ('FONT', (0,-1), (-1,-1), 'Times-Bold'), ('INNERGRID', (0,0), (-1,-1), 0.25, colors.black), ('BOX', (0,0), (-1,-1), 0.25, colors.black)] # Draw things on the PDF. Here's where the PDF generation happens. # See the ReportLab documentation for the full list of functionality. #p.drawString(100, 100, "Hello world.") dati=[["data", Paragraph("titolo programma", styles["Normal"]), Paragraph("ora inizio", styles["Normal"]), Paragraph("ora fine", styles["Normal"]), "tipologia","dettagli","produzione","note"]] pali=autoradio.autoradio_core.palimpsests([]) for title,pdatetime_start,pdatetime_end,code,type,subtype,production,note in pali.get_palimpsest(datetime_start,datetime_end): # if you want extensive text comment this line type,subtype=decode(code) dati.append([str(pdatetime_start.date()),Paragraph(title, styles["Normal"]), pdatetime_start.time().strftime("%H:%M"),pdatetime_end.time().strftime("%H:%M"), Paragraph(type, styles["Normal"]),Paragraph(subtype, styles["Normal"]),production,note]) #dati.append(["totale","fine"]) Tabella=Table(dati,style=ts,repeatRows=1) Elements = [MezzoTrasmissione,EmittenteCanale,Space,Tabella] # Create the PDF object, using the response object as its "file." p = SimpleDocTemplate(response,title="Libro programmi: "+unicode(emittente),author=author) p.build(Elements, onFirstPage=myPages, onLaterPages=myPages) return response else: form = ExtremeForm() # An unbound form return render_to_response('palimpsest/extreme.html', { 'form': form,}) #---------------------------------------------------- # section for podcast from django.views.generic.list import ListView from django.views.generic.detail import DetailView #from django.views.generic.list_detail import object_list from autoradio.programs.models import Episode, Show, Enclosure from django.core.urlresolvers import reverse class episode_detail(DetailView): #class episode_detail(ListView): """ Episode detail Template: ``podcast/episode_detail.html`` Context: object_detail Detail of episode. """ template_name='podcast/episode_detail.html' date_field = "date" def get_context_data(self, **kwargs): context = super(episode_detail, self).get_context_data(**kwargs) extra_context={ 'enclosure_list': Enclosure.objects.filter (episode__show__slug__exact=self.show_slug).filter(episode__slug__exact=self.episode_slug).order_by('-episode__date'),} context.update(extra_context) return context def get(self, request, *args, **kwargs): self.show_slug = kwargs.get('show_slug') self.episode_slug = kwargs.get('slug') self.queryset=Episode.objects.published().filter(show__slug__exact=self.show_slug) return super(episode_detail, self).get(request, *args, **kwargs) class episode_list(ListView): # Qui slug e' passato ma non si sa come !!!!! #, slug): """ Episode list Template: ``podcast/episode_list.html`` Context: object_list List of episodes. """ # return object_list( # request, # queryset=Episode.objects.published().filter(show__slug__exact=slug), # extra_context={'site_media_url':autoradio.settings.SITE_MEDIA_URL}, # template_name='podcast/episode_list.html') # from: http://code.google.com/p/django-podcast/issues/detail?id=12 template_name='podcast/episode_list.html' def get_context_data(self, **kwargs): context = super(episode_list, self).get_context_data(**kwargs) # todo da implementare questo try nel passaggio a django 1.5 # try: show = Show.objects.get(slug=self.slug) # except: # return HttpResponseRedirect(reverse('podcast_shows')) extra_context = {'show':show,} context.update(extra_context) return context def get(self, request, *args, **kwargs): self.slug = kwargs.get('slug') self.queryset=Episode.objects.published().filter(show__slug__exact=self.slug) return super(episode_list, self).get(request, *args, **kwargs) class episode_sitemap(ListView): """ Episode sitemap Template: ``podcast/episode_sitemap.html`` Context: object_list List of episodes. """ template_name='podcast/episode_sitemap.html' def get_context_data(self, **kwargs): context = super(episode_sitemap, self).get_context_data(**kwargs) extra_context={ 'enclosure_list': Enclosure.objects.filter (episode__show__slug__exact=self.slug).order_by('-episode__date'),}, context.update(extra_context) return context def get(self, request, *args, **kwargs): self.slug = kwargs.get('slug') self.queryset=Episode.objects.published().filter(show__slug__exact=self.slug).order_by('-date') return super(episode_sitemap, self).get(request, *args, **kwargs) def render_to_response(self, context, **kwargs): return super(episode_sitemap, self).render_to_response(context, content_type='application/xml', **kwargs) class show_list(ListView): #def show_list(request): """ Episode list Template: ``podcast/show_list.html`` Context: object_list List of shows. """ queryset=Show.objects.all().order_by('title') template_name='podcast/show_list.html' def get_context_data(self, **kwargs): context = super(show_list, self).get_context_data(**kwargs) context.update() return context class show_list_feed(ListView): """ Episode feed by show Template: ``podcast/show_feed.html`` Context: object_list List of episodes by show. """ template_name='podcast/show_feed.html' def get_context_data(self, **kwargs): context = super(show_list_feed, self).get_context_data(**kwargs) return context def get(self, request, *args, **kwargs): slug = kwargs.get('slug') self.queryset=Episode.objects.filter(show__slug__exact=slug).order_by('-date')[0:21] return super(show_list_feed, self).get(request, *args, **kwargs) def render_to_response(self, context, **kwargs): return super(show_list_feed, self).render_to_response(context, content_type='application/xml', **kwargs) class show_list_media(ListView): """ Episode feed by show Template: ``podcast/show_feed_media.html`` Context: object_list List of episodes by show. """ template_name='podcast/show_feed_media.html' def get_context_data(self, **kwargs): context = super(show_list_media, self).get_context_data(**kwargs) extra_context={ 'enclosure_list': Enclosure.objects.filter (episode__show__slug__exact=self.slug).order_by('-episode__date')} context.update(extra_context) return context def get(self, request, *args, **kwargs): self.slug = kwargs.get('slug') self.queryset=Episode.objects.filter(show__slug__exact=self.slug).order_by('-date')[0:21] return super(show_list_media, self).get(request, *args, **kwargs) def render_to_response(self, context, **kwargs): return super(show_list_media, self).render_to_response(context, content_type='application/xml', **kwargs) class show_list_atom(ListView): """ Episode feed by show Template: ``podcast/show_feed_atom.html`` Context: object_list List of episodes by show. """ template_name='podcast/show_feed_atom.html' def get_context_data(self, **kwargs): context = super(show_list_atom, self).get_context_data(**kwargs) extra_context={ 'enclosure_list': Enclosure.objects.filter (episode__show__slug__exact=self.slug).order_by('-episode__date')} context.update(extra_context) return context def get(self, request, *args, **kwargs): self.slug = kwargs.get('slug') self.queryset=Episode.objects.filter(show__slug__exact=self.slug).order_by('-date')[0:21] return super(show_list_atom, self).get(request, *args, **kwargs) def render_to_response(self, context, **kwargs): return super(show_list_atom, self).render_to_response(context, content_type='application/xml', **kwargs) autoradio-2.8.6/autoradio/programs/urls.py0000664000175000017500000000037513001105756020437 0ustar pat1pat100000000000000from django.conf.urls import * #from django.contrib import admin #from models import Program, Schedule import views urlpatterns = [ url(r'^$', views.index), url(r'^xmms/$', views.dbusstato), url(r'^programsbook/$', views.programsbook), ] autoradio-2.8.6/autoradio/programs/__init__.py0000664000175000017500000000000013001105756021172 0ustar pat1pat100000000000000autoradio-2.8.6/autoradio/programs/admin.py0000664000175000017500000003611613001105756020544 0ustar pat1pat100000000000000from django.contrib import admin from models import Giorno, Configure, ProgramType, Show, Schedule, \ PeriodicSchedule,AperiodicSchedule,Episode,Enclosure,ScheduleDone from autoradio.programs.models import ParentCategory, ChildCategory, MediaCategory from django import forms from django.utils.translation import ugettext_lazy import autoradio.settings import autoradio.mime import magic ma = magic.open(magic.MAGIC_MIME_TYPE) ma.load() class MyEnclosureInlineFormset(forms.models.BaseInlineFormSet): def clean(self): import mutagen, os # get forms that actually have valid data count = 0 for form in self.forms: try: if form.cleaned_data: count += 1 file = form.cleaned_data.get('file',False) if file: if autoradio.settings.permit_no_playable_files: try: type = file.content_type in webmime_audio except: #here when the file is not uploaded (modify for example) return file if not type: raise forms.ValidationError(ugettext_lazy("Browser say that Content-Type is not audio")) 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")) else: try: type = file.content_type in webmime_ogg except: #here when the file is not uploaded (modify for example) return file if not type: raise forms.ValidationError(ugettext_lazy("Browser say that Content-Type is not audio ogg vorbis")) if not os.path.splitext(file.name)[1] in websuffix_ogg: raise forms.ValidationError(ugettext_lazy("Doesn't have proper extension: .ogg, .oga")) try: mime = ma.file(file.temporary_file_path()) audio = mime in mymime_ogg except: audio=False if not audio: raise forms.ValidationError(ugettext_lazy("Not a valid ogg/oga vorbis 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: mut=mutagen.File(file.temporary_file_path()) audio = not mut is None sample_rate=mut.info.sample_rate except: audio = False sample_rate=0 if not audio: raise forms.ValidationError(ugettext_lazy("Not a valid ogg/oga vorbis audio file: probably no tags present")) if not sample_rate == 44100: raise forms.ValidationError(ugettext_lazy("Sample rate is Not 44100Hz: cannot use it in podcasting web interface")) return file else: raise forms.ValidationError(ugettext_lazy("Couldn't read uploaded file")) except AttributeError: # annoyingly, if a subform is invalid Django explicity raises # an AttributeError for cleaned_data pass if count < 1: raise forms.ValidationError(ugettext_lazy('You must have at least one Enclosure')) class MyEnclosureAdminForm(forms.ModelForm): """ Check file if it is a known media file. """ class Meta: model = Enclosure fields = '__all__' def clean_file(self): import mutagen, os file = self.cleaned_data.get('file',False) if file: if autoradio.settings.permit_no_playable_files: try: type = file.content_type in ["audio/mpeg","audio/flac","video/ogg"] 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 [".mp3",".wav",".ogg",".oga",".flac", ".Mp3",".Wav",".Ogg",".Oga",".Flac", ".MP3",".WAV",".OGG",".OGA",".FLAC" ]: raise forms.ValidationError(ugettext_lazy("Doesn't have proper extension: .mp3, .wav, .ogg, .oga, .flac")) #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")) return file else: try: type = file.content_type in ["video/ogg","audio/oga"] except: return file if not type: raise forms.ValidationError(ugettext_lazy("Content-Type is not audio/oga or video/ogg")) if not os.path.splitext(file.name)[1] in [".ogg",".oga",".Ogg",".Oga",".OGG"]: raise forms.ValidationError(ugettext_lazy("Doesn't have proper extension: .ogg, .oga")) #Check file if it is a known media file. The check is based on mutagen file test. try: mut=mutagen.File(file.temporary_file_path()) audio = not mut is None sample_rate=mut.info.sample_rate except: audio = False sample_rate=0 if not audio: raise forms.ValidationError(ugettext_lazy("Not a valid audio file")) if not sample_rate == 44100: raise forms.ValidationError(ugettext_lazy("Sample rate is Not 44100Hz: cannot use it in podcasting web interface")) return file else: raise forms.ValidationError(ugettext_lazy("Couldn't read uploaded file")) class CategoryInline(admin.StackedInline): model = ChildCategory extra = 3 class ParentCategoryAdmin(admin.ModelAdmin): list_display = ('name',) inlines = [CategoryInline,] class ChildCategoryAdmin(admin.ModelAdmin): list_display = ('parent', 'name') class MediaCategoryAdmin(admin.ModelAdmin): list_display = ('name',) admin.site.register(ParentCategory, ParentCategoryAdmin) admin.site.register(ChildCategory, ChildCategoryAdmin) admin.site.register(MediaCategory, MediaCategoryAdmin) class EnclosureInline(admin.StackedInline): model = Enclosure extra=1 max_num=10 fieldsets = ( (None, { 'fields': ('title', 'file',) }), ('Podcast options', { 'classes': ('collapse',), 'fields': ('mime', 'medium','expression','frame','bitrate',\ 'sample','channel','algo','hash','player','embed','width','height') }), ) formset = MyEnclosureInlineFormset class ScheduleInline(admin.StackedInline): #class ScheduleInline(admin.TabularInline): model = Schedule extra=2 max_num=10 class PeriodicScheduleInline(admin.StackedInline): model = PeriodicSchedule extra=2 class AperiodicScheduleInline(admin.StackedInline): model = AperiodicSchedule extra=2 class EpisodeInline(admin.StackedInline): model = Episode extra=1 # not supported #inline=(ScheduleInline,EnclosureInline,) fieldsets = ( (None, { 'fields': ('show', 'author', 'title_type', 'title', 'slug', 'description_type', 'description') }), ('podcast options', { 'classes': ('collapse',), 'fields': ('captions', 'category', 'domain', 'frequency', 'priority', 'status') }), ('iTunes options', { 'classes': ('collapse',), 'fields': ('subtitle', 'summary', ('minutes', 'seconds'), 'keywords', ('explicit', 'block')) }), ('Media RSS options', { 'classes': ('collapse',), 'fields': ('role', 'media_category', ('standard', 'rating'), 'image', 'text', ('deny', 'restriction')) }), ('Dublin Core options', { 'classes': ('collapse',), 'fields': (('start', 'end'), 'scheme', 'name') }), ('Google Media options', { 'classes': ('collapse',), 'fields': ('preview', ('preview_start_mins', 'preview_start_secs'), ('preview_end_mins', 'preview_end_secs'), 'host') }), ) class GiornoAdmin(admin.ModelAdmin): search_fields = ['name'] admin.site.register(Giorno, GiornoAdmin) class ConfigureAdmin(admin.ModelAdmin): list_display = ('sezione','radiostation','channel','active',\ 'emission_starttime'\ ,'emission_endtime') admin.site.register(Configure, ConfigureAdmin) class ProgramTypeAdmin(admin.ModelAdmin): list_display = ('code','type','subtype','description') admin.site.register(ProgramType, ProgramTypeAdmin) class ShowAdmin(admin.ModelAdmin): prepopulated_fields = {'slug': ("title",)} fieldsets = ( (None, {'fields': ('title','slug','length','type','production',\ 'organization','link','description','author')}), ('Podcast options', { 'classes': ('collapse',), 'fields': ('language','copyright','copyright_url',\ 'webmaster','category_show',\ 'domain','ttl','image','feedburner')}), ('iTunes options', { 'classes': ('collapse',), 'fields': ('subtitle','summary','category','explicit',\ 'block','redirect','keywords','itunes')}) ) list_display = ('title',) #list_filter = ['end_date',] search_fields = ['title',] # is better without EpisodeInline and start from Episode # inlines = [ # EpisodeInline,PeriodicScheduleInline,AperiodicScheduleInline # ] inlines = [ PeriodicScheduleInline,AperiodicScheduleInline ] admin.site.register(Show, ShowAdmin) class EpisodeAdmin(admin.ModelAdmin): inlines = [ ScheduleInline,EnclosureInline ] prepopulated_fields = {'slug': ("title",)} search_fields = ['title',] list_display = ('title', 'update', 'show') list_filter = ('show', 'update') radio_fields = {'title_type': admin.HORIZONTAL, 'description_type': admin.HORIZONTAL, 'status': admin.HORIZONTAL} fieldsets = ( (None, { 'fields': ('show', 'author', 'title_type', 'title', 'slug', 'description_type', 'description') }), ('podcast options', { 'classes': ('collapse',), 'fields': ('captions', 'category', 'domain', 'frequency', 'priority', 'status') }), ('iTunes options', { 'classes': ('collapse',), 'fields': ('subtitle', 'summary', ('minutes', 'seconds'), 'keywords', ('explicit', 'block')) }), ('Media RSS options', { 'classes': ('collapse',), 'fields': ('role', 'media_category', ('standard', 'rating'), 'image', 'text', ('deny', 'restriction')) }), ('Dublin Core options', { 'classes': ('collapse',), 'fields': (('start', 'end'), 'scheme', 'name') }), ('Google Media options', { 'classes': ('collapse',), 'fields': ('preview', ('preview_start_mins', 'preview_start_secs'), ('preview_end_mins', 'preview_end_secs'), 'host') }), ) admin.site.register(Episode, EpisodeAdmin) class ScheduleAdmin(admin.ModelAdmin): list_display = ('episode', 'emission_date'\ ,'was_scheduled_today') list_filter = ['emission_date'] search_fields = ['episode','emission_date'] date_hierarchy = 'emission_date' admin.site.register(Schedule, ScheduleAdmin) class PeriodicScheduleAdmin(admin.ModelAdmin): list_display = ('start_date','end_date','time') list_filter = ['start_date','end_date','time','giorni'] search_fields = ['playlist','giorni'] date_hierarchy = 'start_date' admin.site.register(PeriodicSchedule, PeriodicScheduleAdmin) class AperiodicScheduleAdmin(admin.ModelAdmin): list_display = ('emission_date','show') search_fields = ['show'] date_hierarchy = 'emission_date' admin.site.register(AperiodicSchedule, AperiodicScheduleAdmin) class ScheduleDoneAdmin(admin.ModelAdmin): list_display = ('emission_done','schedule','enclosure') search_fields = ['enclosure'] date_hierarchy = 'emission_done' admin.site.register(ScheduleDone, ScheduleDoneAdmin) class EnclosureAdmin(admin.ModelAdmin): list_display = ('episode','title',) list_filter = ['medium','mime','bitrate'] search_fields = ['title','file'] fieldsets = ( (None, { 'fields': ('episode','title', 'file',) }), ('Podcast options', { 'classes': ('collapse',), 'fields': ('mime', 'medium','expression','frame','bitrate',\ 'sample','channel','algo','hash','player','embed','width','height') }), ) form = MyEnclosureAdminForm admin.site.register(Enclosure, EnclosureAdmin) autoradio-2.8.6/autoradio/programs/widgets.py0000664000175000017500000000626513001105756021124 0ustar pat1pat100000000000000""" 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

{% endblock %} autoradio-2.8.6/autoradio/programs/templates/xmms/0000775000175000017500000000000013003471473022061 5ustar pat1pat100000000000000autoradio-2.8.6/autoradio/programs/templates/xmms/index.html0000664000175000017500000000056513001105756024060 0ustar pat1pat100000000000000{% extends "base.html" %} {% load i18n %} {% load static %} {% 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-2.8.6/autoradio/programs/templates/schedule/0000775000175000017500000000000013003471473022671 5ustar pat1pat100000000000000autoradio-2.8.6/autoradio/programs/templates/schedule/index.html0000664000175000017500000000407313001105756024666 0ustar pat1pat100000000000000{% 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-2.8.6/autoradio/programs/templates/podcast/0000775000175000017500000000000013003471473022532 5ustar pat1pat100000000000000autoradio-2.8.6/autoradio/programs/templates/podcast/show_feed_atom.html0000664000175000017500000000316513001105756026404 0ustar pat1pat100000000000000 {% 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-2.8.6/autoradio/programs/templates/podcast/show_feed.html0000664000175000017500000001312413001105756025360 0ustar pat1pat100000000000000 {% 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-2.8.6/autoradio/programs/templates/podcast/show_list.html0000664000175000017500000000143513001105756025432 0ustar pat1pat100000000000000{% 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-2.8.6/autoradio/programs/templates/podcast/episode_detail.html0000664000175000017500000000635413001105756026376 0ustar pat1pat100000000000000{% 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-2.8.6/autoradio/programs/templates/podcast/episode_sitemap.html0000664000175000017500000000362113001105756026570 0ustar pat1pat100000000000000 {% 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-2.8.6/autoradio/programs/templates/podcast/base.html0000664000175000017500000000245513001105756024334 0ustar pat1pat100000000000000{% load i18n %} {% load static %} {% block head %} {% block title %}Django Podcast{% endblock %} {% endblock %}
{% block allcontent %} {% endblock %} autoradio-2.8.6/autoradio/programs/templates/podcast/episode_list.html0000664000175000017500000000566313001105756026111 0ustar pat1pat100000000000000{% 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-2.8.6/autoradio/programs/templates/podcast/show_feed_media.html0000664000175000017500000001150013001105756026513 0ustar pat1pat100000000000000 {% 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-2.8.6/autoradio/programs/migrations/0000775000175000017500000000000013003471473021253 5ustar pat1pat100000000000000autoradio-2.8.6/autoradio/programs/migrations/0002_fixture.py0000664000175000017500000000604213001105756023752 0ustar pat1pat100000000000000# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.db import models, migrations from django.core import serializers import os fixture_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '../fixtures')) #fixture_filename = 'initial_data.json' def load_fixture(apps, schema_editor): #print "" #print "Insert password for user 'autoradio' (administrator superuser)" #call_command("createsuperuser",username="autoradio",email="autoradio@casa.it") from django.core.management import call_command call_command("createsuperuser",username="autoradio",email="autoradio@casa.it",interactive=False) from django.contrib.auth.models import User u = User.objects.get(username__exact='autoradio') u.set_password('autoradio') u.save() for fixture_filename in os.listdir(fixture_dir): if fixture_filename[-5:] == ".json": fixture_file = os.path.join(fixture_dir, fixture_filename) print "load fixture from file: ",fixture_file fixture = open(fixture_file, 'rb') objects = serializers.deserialize('json', fixture, ignorenonexistent=True) for obj in objects: obj.save() fixture.close() #def load_fixture(apps, schema_editor): # call_command('loaddata', 'initial_data', app_label='stations') def unload_fixture(apps, schema_editor): "Brutally deleting all entries for this model..." MyModel = apps.get_model("programs", "MediaCategory") MyModel.objects.all().delete() MyModel = apps.get_model("programs", "ParentCategory") MyModel.objects.all().delete() MyModel = apps.get_model("programs", "ChildCategory") MyModel.objects.all().delete() MyModel = apps.get_model("programs", "Giorno") MyModel.objects.all().delete() MyModel = apps.get_model("programs", "Configure") MyModel.objects.all().delete() MyModel = apps.get_model("programs", "ProgramType") MyModel.objects.all().delete() MyModel = apps.get_model("programs", "Show") MyModel.objects.all().delete() MyModel = apps.get_model("programs", "Episode") MyModel.objects.all().delete() MyModel = apps.get_model("programs", "Enclosure") MyModel.objects.all().delete() MyModel = apps.get_model("programs", "Schedule") MyModel.objects.all().delete() MyModel = apps.get_model("programs", "ScheduleDone") MyModel.objects.all().delete() MyModel = apps.get_model("programs", "PeriodicSchedule") MyModel.objects.all().delete() MyModel = apps.get_model("programs", "AperiodicSchedule") MyModel.objects.all().delete() # MyModel = apps.get_model("stations", "UserProfile") # MyModel.objects.get(user=apps.get_model("auth", "User").objects.get(username="autoradio")).delete() # apps.get_model("auth", "User").objects.get(username="autoradio").delete() class Migration(migrations.Migration): dependencies = [ ('programs', '0001_initial'), ] operations = [ migrations.RunPython(load_fixture, reverse_code=unload_fixture), ] autoradio-2.8.6/autoradio/programs/migrations/__init__.py0000664000175000017500000000000013001105756023346 0ustar pat1pat100000000000000autoradio-2.8.6/autoradio/programs/migrations/0001_initial.py0000664000175000017500000011610513001105756023716 0ustar pat1pat100000000000000# -*- coding: utf-8 -*- # Generated by Django 1.9 on 2016-01-25 17:56 from __future__ import unicode_literals import autoradio.programs.models from django.conf import settings from django.db import migrations, models import django.db.models.deletion class Migration(migrations.Migration): initial = True dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] operations = [ migrations.CreateModel( name='AperiodicSchedule', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('emission_date', models.DateTimeField(help_text='This is the date and time when the program is planned in palimsest', verbose_name='Programmed date')), ], ), migrations.CreateModel( name='ChildCategory', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('name', models.CharField(blank=True, choices=[(b'Arts', ((b'Design', b'Design'), (b'Fashion & Beauty', b'Fashion & Beauty'), (b'Food', b'Food'), (b'Literature', b'Literature'), (b'Performing Arts', b'Performing Arts'), (b'Visual Arts', b'Visual Arts'))), (b'Business', ((b'Business News', b'Business News'), (b'Careers', b'Careers'), (b'Investing', b'Investing'), (b'Management & Marketing', b'Management & Marketing'), (b'Shopping', b'Shopping'))), (b'Education', ((b'Education Technology', b'Education Technology'), (b'Higher Education', b'Higher Education'), (b'K-12', b'K-12'), (b'Language Courses', b'Language Courses'), (b'Training', b'Training'))), (b'Games & Hobbies', ((b'Automotive', b'Automotive'), (b'Aviation', b'Aviation'), (b'Hobbies', b'Hobbies'), (b'Other Games', b'Other Games'), (b'Video Games', b'Video Games'))), (b'Government & Organizations', ((b'Local', b'Local'), (b'National', b'National'), (b'Non-Profit', b'Non-Profit'), (b'Regional', b'Regional'))), (b'Health', ((b'Alternative Health', b'Alternative Health'), (b'Fitness & Nutrition', b'Fitness & Nutrition'), (b'Self-Help', b'Self-Help'), (b'Sexuality', b'Sexuality'))), (b'Religion & Spirituality', ((b'Buddhism', b'Buddhism'), (b'Christianity', b'Christianity'), (b'Hinduism', b'Hinduism'), (b'Islam', b'Islam'), (b'Judaism', b'Judaism'), (b'Other', b'Other'), (b'Spirituality', b'Spirituality'))), (b'Science & Medicine', ((b'Medicine', b'Medicine'), (b'Natural Sciences', b'Natural Sciences'), (b'Social Sciences', b'Social Sciences'))), (b'Society & Culture', ((b'History', b'History'), (b'Personal Journals', b'Personal Journals'), (b'Philosophy', b'Philosophy'), (b'Places & Travel', b'Places & Travel'))), (b'Sports & Recreation', ((b'Amateur', b'Amateur'), (b'College & High School', b'College & High School'), (b'Outdoor', b'Outdoor'), (b'Professional', b'Professional'))), (b'Technology', ((b'Gadgets', b'Gadgets'), (b'Tech News', b'Tech News'), (b'Podcasting', b'Podcasting'), (b'Software How-To', b'Software How-To')))], help_text='Please choose a child category that corresponds to its respective parent category (e.g., "Design" is a child category of "Arts").
If no such child category exists for a parent category (e.g., Comedy, Kids & Family, Music, News & Politics, or TV & Film), simply leave this blank and save.', max_length=50)), ('slug', models.SlugField(blank=True, help_text='A slug is a URL-friendly nickname. For exmaple, a slug for "Fashion & Beauty" is "fashion-beauty".')), ], options={ 'ordering': ['parent', 'slug'], 'verbose_name': 'category (iTunes child)', 'verbose_name_plural': 'categories (iTunes child)', }, ), migrations.CreateModel( name='Configure', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('sezione', models.CharField(default=b'show', editable=False, max_length=50, unique=True)), ('active', models.BooleanField(default=True, help_text='activate/deactivate the intere program class', verbose_name='Active show')), ('emission_starttime', models.TimeField(blank=True, help_text='The start time from wich the programs will be active', null=True, verbose_name='Programmed start time')), ('emission_endtime', models.TimeField(blank=True, help_text='The end time the programs will be active', null=True, verbose_name='Programmed end time')), ('radiostation', models.CharField(default=b'Radio', help_text='The station name for the print of programs book', max_length=50, unique=True)), ('channel', models.CharField(default=b'103', help_text='The station channel for the print of programs book', max_length=80, unique=True)), ('mezzo', models.CharField(default=b'analogico terrestre', help_text='The station kind of emission for the print of programs book', max_length=50, unique=True)), ('type', models.CharField(default=b'radiofonica', help_text='The station type for the print of programs book', max_length=50, unique=True)), ], ), migrations.CreateModel( name='Enclosure', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('title', models.CharField(blank=True, default=None, help_text='Title is generally only useful with multiple enclosures.', max_length=255)), ('file', autoradio.programs.models.DeletingFileField(help_text='Either upload or use the "Player" text box below. If uploading, file must be less than or equal to 30 MB for a Google video sitemap.', max_length=255, upload_to=b'podcasts/episodes/files/')), ('mime', models.CharField(blank=True, choices=[(b'audio/ogg', b'.ogg (audio)'), (b'audio/mpeg', b'.mp3 (audio)'), (b'audio/x-m4a', b'.m4a (audio)'), (b'video/mp4', b'.mp4 (audio or video)'), (b'video/x-m4v', b'.m4v (video)'), (b'video/quicktime', b'.mov (video)'), (b'application/pdf', b'.pdf (document)'), (b'image/jpeg', b'.jpg, .jpeg, .jpe (image)')], max_length=255, verbose_name=b'Format')), ('medium', models.CharField(blank=True, choices=[(b'Audio', b'Audio'), (b'Video', b'Video'), (b'Document', b'Document'), (b'Image', b'Image'), (b'Executable', b'Executable')], max_length=255)), ('expression', models.CharField(blank=True, choices=[(b'Sample', b'Sample'), (b'Full', b'Full'), (b'Nonstop', b'Non-stop')], max_length=25)), ('frame', models.CharField(blank=True, choices=[(b'29.97', b'29.97')], help_text='Measured in frames per second (fps), often 29.97.', max_length=5, verbose_name=b'Frame rate')), ('bitrate', models.CharField(blank=True, choices=[(b'8', b'8'), (b'11.025', b'11.025'), (b'16', b'16'), (b'22.050', b'22.050'), (b'32', b'32'), (b'44.1', b'44.1'), (b'48', b'48'), (b'96', b'96')], help_text='Measured in kilobits per second (kbps), often 128 or 192.', max_length=5, verbose_name=b'Bit rate')), ('sample', models.CharField(blank=True, choices=[(b'24', b'24'), (b'48', b'48'), (b'64', b'64'), (b'96', b'96'), (b'128', b'128'), (b'160', b'160'), (b'196', b'196'), (b'320', b'320')], help_text='Measured in kilohertz (kHz), often 44.1.', max_length=5, verbose_name=b'Sample rate')), ('channel', models.CharField(blank=True, choices=[(b'2', b'2'), (b'1', b'1')], help_text='Number of channels; 2 for stereo, 1 for mono.', max_length=5)), ('algo', models.CharField(blank=True, choices=[(b'MD5', b'MD5'), (b'SHA-1', b'SHA-1')], max_length=50, verbose_name=b'Hash algorithm')), ('hash', models.CharField(blank=True, help_text='MD-5 or SHA-1 file hash.', max_length=255)), ('player', models.URLField(blank=True, help_text='URL of the player console that plays the media. Could be your own .swf, but most likely a YouTube URL, such as http://www.youtube.com/v/UZCfK8pVztw (not the permalink, which looks like http://www.youtube.com/watch?v=UZCfK8pVztw).')), ('embed', models.BooleanField(help_text='Check to allow Google to embed your external player in search results on Google Video.')), ('width', models.PositiveIntegerField(blank=True, help_text="Width of the browser window in
which the URL should be opened.
YouTube's default is 425.", null=True)), ('height', models.PositiveIntegerField(blank=True, help_text="Height of the browser window in
which the URL should be opened.
YouTube's default is 344.", null=True)), ], options={ 'ordering': ['mime', 'file'], }, ), migrations.CreateModel( name='Episode', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('title', models.CharField(help_text='Make it specific but avoid explicit language. Limit to 100 characters for a Google video sitemap.', max_length=255)), ('active', models.BooleanField(default=True, verbose_name='Active')), ('date', models.DateTimeField(auto_now_add=True, verbose_name='Recording date')), ('title_type', models.CharField(blank=True, choices=[(b'Plain', b'Plain text'), (b'HTML', b'HTML')], default=b'Plain', max_length=255, verbose_name=b'Title type')), ('slug', models.SlugField(help_text='Auto-generated from Title.', unique=True)), ('description_type', models.CharField(blank=True, choices=[(b'Plain', b'Plain text'), (b'HTML', b'HTML')], default=b'Plain', max_length=255, verbose_name=b'Description type')), ('description', models.TextField(help_text='Avoid explicit language. Google video sitempas allow 2,048 characters.')), ('captions', autoradio.programs.models.DeletingFileField(blank=True, help_text='For video podcasts. Good captioning choices include SubViewer, SubRip or TimedText.', max_length=255, upload_to=b'podcasts/episodes/captions/')), ('category', models.CharField(blank=True, help_text='Limited to one user-specified category for the sake of sanity.', max_length=255)), ('domain', models.URLField(blank=True, help_text='A URL that identifies a categorization taxonomy.')), ('frequency', models.CharField(blank=True, choices=[(b'always', b'Always'), (b'hourly', b'Hourly'), (b'daily', b'Daily'), (b'weekly', b'Weekly'), (b'monthly', b'Monthly'), (b'yearly', b'Yearly'), (b'never', b'Never')], default=b'never', help_text="The frequency with which the episode's data changes. For sitemaps.", max_length=10)), ('priority', models.DecimalField(blank=True, decimal_places=1, default=b'0.5', help_text='The relative priority of this episode compared to others. 1.0 is the most important. For sitemaps.', max_digits=2, null=True)), ('status', models.IntegerField(choices=[(1, b'Draft'), (2, b'Public'), (3, b'Private')], default=2)), ('update', models.DateTimeField(auto_now=True)), ('subtitle', models.CharField(blank=True, help_text='Looks best if only a few words like a tagline.', max_length=255)), ('summary', models.TextField(blank=True, help_text='Allows 4,000 characters. Description will be used if summary is blank.')), ('minutes', models.PositiveIntegerField(blank=True, null=True)), ('seconds', models.CharField(blank=True, choices=[(b'00', b'0'), (b'01', b'1'), (b'02', b'2'), (b'03', b'3'), (b'04', b'4'), (b'05', b'5'), (b'06', b'6'), (b'07', b'7'), (b'08', b'8'), (b'09', b'9'), (b'10', b'10'), (b'11', b'11'), (b'12', b'12'), (b'13', b'13'), (b'14', b'14'), (b'15', b'15'), (b'16', b'16'), (b'17', b'17'), (b'18', b'18'), (b'19', b'19'), (b'20', b'20'), (b'21', b'21'), (b'22', b'22'), (b'23', b'23'), (b'24', b'24'), (b'25', b'25'), (b'26', b'26'), (b'27', b'27'), (b'28', b'28'), (b'29', b'29'), (b'30', b'30'), (b'31', b'31'), (b'32', b'32'), (b'33', b'33'), (b'34', b'34'), (b'35', b'35'), (b'36', b'36'), (b'37', b'37'), (b'38', b'38'), (b'39', b'39'), (b'40', b'40'), (b'41', b'41'), (b'42', b'42'), (b'43', b'43'), (b'44', b'44'), (b'45', b'45'), (b'46', b'46'), (b'47', b'47'), (b'48', b'48'), (b'49', b'49'), (b'50', b'50'), (b'51', b'51'), (b'52', b'52'), (b'53', b'53'), (b'54', b'54'), (b'55', b'55'), (b'56', b'56'), (b'57', b'57'), (b'58', b'58'), (b'59', b'59')], max_length=2, null=True)), ('keywords', models.CharField(blank=True, help_text='A comma-delimited list of words for searches, up to 12; perhaps include misspellings.', max_length=255, null=True)), ('explicit', models.CharField(choices=[(b'Yes', b'Yes'), (b'No', b'No'), (b'Clean', b'Clean')], default=b'No', help_text='"Clean" will put the clean iTunes graphic by it.', max_length=255)), ('block', models.BooleanField(default=False, help_text='Check to block this episode from iTunes because
its content might cause the entire show to be
removed from iTunes.')), ('role', models.CharField(blank=True, choices=[(b'Actor', b'Actor'), (b'Adaptor', b'Adaptor'), (b'Anchor person', b'Anchor person'), (b'Animal Trainer', b'Animal Trainer'), (b'Animator', b'Animator'), (b'Announcer', b'Announcer'), (b'Armourer', b'Armourer'), (b'Art Director', b'Art Director'), (b'Artist/Performer', b'Artist/Performer'), (b'Assistant Camera', b'Assistant Camera'), (b'Assistant Chief Lighting Technician', b'Assistant Chief Lighting Technician'), (b'Assistant Director', b'Assistant Director'), (b'Assistant Producer', b'Assistant Producer'), (b'Assistant Visual Editor', b'Assistant Visual Editor'), (b'Author', b'Author'), (b'Broadcast Assistant', b'Broadcast Assistant'), (b'Broadcast Journalist', b'Broadcast Journalist'), (b'Camera Operator', b'Camera Operator'), (b'Carpenter', b'Carpenter'), (b'Casting', b'Casting'), (b'Causeur', b'Causeur'), (b'Chief Lighting Technician', b'Chief Lighting Technician'), (b'Choir', b'Choir'), (b'Choreographer', b'Choreographer'), (b'Clapper Loader', b'Clapper Loader'), (b'Commentary or Commentator', b'Commentary or Commentator'), (b'Commissioning Broadcaster', b'Commissioning Broadcaster'), (b'Composer', b'Composer'), (b'Computer programmer', b'Computer programmer'), (b'Conductor', b'Conductor'), (b'Consultant', b'Consultant'), (b'Continuity Checker', b'Continuity Checker'), (b'Correspondent', b'Correspondent'), (b'Costume Designer', b'Costume Designer'), (b'Dancer', b'Dancer'), (b'Dialogue Coach', b'Dialogue Coach'), (b'Director', b'Director'), (b'Director of Photography', b'Director of Photography'), (b'Distribution Company', b'Distribution Company'), (b'Draughtsman', b'Draughtsman'), (b'Dresser', b'Dresser'), (b'Dubber', b'Dubber'), (b'Editor/Producer (News)', b'Editor/Producer (News)'), (b'Editor-in-chief', b'Editor-in-chief'), (b'Editor-of-the-Day', b'Editor-of-the-Day'), (b'Ensemble', b'Ensemble'), (b'Executive Producer', b'Executive Producer'), (b'Expert', b'Expert'), (b'Fight Director', b'Floor Manager'), (b'Floor Manager', b'Floor Manager'), (b'Focus Puller', b'Focus Puller'), (b'Foley Artist', b'Foley Artist'), (b'Foley Editor', b'Foley Editor'), (b'Foley Mixer', b'Foley Mixer'), (b'Graphic Assistant', b'Graphic Assistant'), (b'Graphic Designer', b'Graphic Designer'), (b'Greensman', b'Greensman'), (b'Grip', b'Grip'), (b'Hairdresser', b'Hairdresser'), (b'Illustrator', b'Illustrator'), (b'Interviewed Guest', b'Interviewed Guest'), (b'Interviewer', b'Interviewer'), (b'Key Character', b'Key Character'), (b'Key Grip', b'Key Grip'), (b'Key Talents', b'Key Talents'), (b'Leadman', b'Leadman'), (b'Librettist', b'Librettist'), (b'Lighting director', b'Lighting director'), (b'Lighting Technician', b'Lighting Technician'), (b'Location Manager', b'Location Manager'), (b'Lyricist', b'Lyricist'), (b'Make Up Artist', b'Make Up Artist'), (b'Manufacturer', b'Manufacturer'), (b'Matte Artist', b'Matte Artist'), (b'Music Arranger', b'Music Arranger'), (b'Music Group', b'Music Group'), (b'Musician', b'Musician'), (b'News Reader', b'News Reader'), (b'Orchestra', b'Orchestra'), (b'Participant', b'Participant'), (b'Photographer', b'Photographer'), (b'Post-Production Editor', b'Post-Production Editor'), (b'Producer', b'Producer'), (b'Production Assistant', b'Production Assistant'), (b'Production Company', b'Production Company'), (b'Production Department', b'Production Department'), (b'Production Manager', b'Production Manager'), (b'Production Secretary', b'Production Secretary'), (b'Programme Production Researcher', b'Programme Production Researcher'), (b'Property Manager', b'Property Manager'), (b'Publishing Company', b'Publishing Company'), (b'Puppeteer', b'Puppeteer'), (b'Pyrotechnician', b'Pyrotechnician'), (b'Reporter', b'Reporter'), (b'Rigger', b'Rigger'), (b'Runner', b'Runner'), (b'Scenario', b'Scenario'), (b'Scenic Operative', b'Scenic Operative'), (b'Script Supervisor', b'Script Supervisor'), (b'Second Assistant Camera', b'Second Assistant Camera'), (b'Second Assistant Director', b'Second Assistant Director'), (b'Second Unit Director', b'Second Unit Director'), (b'Set Designer', b'Set Designer'), (b'Set Dresser', b'Set Dresser'), (b'Sign Language', b'Sign Language'), (b'Singer', b'Singer'), (b'Sound Designer', b'Sound Designer'), (b'Sound Mixer', b'Sound Mixer'), (b'Sound Recordist', b'Sound Recordist'), (b'Special Effects', b'Special Effects'), (b'Stunts', b'Stunts'), (b'Subtitles', b'Subtitles'), (b'Technical Director', b'Technical Director'), (b'Translation', b'Translation'), (b'Transportation Manager', b'Transportation Manager'), (b'Treatment / Programme Proposal', b'Treatment / Programme Proposal'), (b'Vision Mixer', b'Vision Mixer'), (b'Visual Editor', b'Visual Editor'), (b'Visual Effects', b'Visual Effects'), (b'Wardrobe', b'Wardrobe'), (b'Witness', b'Witness')], help_text='Role codes provided by the European Broadcasting Union.', max_length=255)), ('standard', models.CharField(blank=True, choices=[(b'Simple', b'Simple'), (b'MPAA', b'MPAA'), (b'V-chip', b'TV Parental Guidelines')], default=b'Simple', max_length=255)), ('rating', models.CharField(blank=True, choices=[(b'Simple', ((b'Adult', b'Adult'), (b'Nonadult', b'Non-adult'))), (b'MPAA', ((b'G', b'G: General Audiences'), (b'PG', b'PG: Parental Guidance Suggested'), (b'PG-13', b'PG-13: Parents Strongly Cautioned'), (b'R', b'R: Restricted'), (b'NC-17', b'NC-17: No One 17 and Under Admitted'))), (b'TV Parental Guidelines', ((b'TV-Y', b'TV-Y: All children'), (b'TV-Y7-FV', b'TV-Y7/TV-Y7-FV: Directed to older children'), (b'TV-G', b'TV-G: General audience'), (b'TV-PG', b'TV-PG: Parental guidance'), (b'TV-14', b'TV-14: Parents strongly cautioned'), (b'TV-MA', b'TV-MA: Mature audiences')))], default=b'Nonadult', help_text='If used, selection must match respective Scheme selection.', max_length=255)), ('image', models.ImageField(blank=True, help_text='A still image from a video file, but for episode artwork to display in iTunes, image must be saved to file\'s metadata before episode uploading!', upload_to=b'podcasts/episodes/img/')), ('text', models.TextField(blank=True, help_text='Media RSS text transcript. Must use tags. Please see the Media RSS 2.0 specification for syntax.')), ('deny', models.BooleanField(default=False, help_text='Check to deny episode to be shown to users from specified countries.')), ('restriction', models.CharField(blank=True, help_text='A space-delimited list of ISO 3166-1-coded countries.', max_length=255)), ('start', models.DateTimeField(blank=True, help_text='Start date and time that the media is valid.', null=True)), ('end', models.DateTimeField(blank=True, help_text='End date and time that the media is valid.', null=True)), ('scheme', models.CharField(blank=True, default=b'W3C-DTF', max_length=255)), ('name', models.CharField(blank=True, help_text='Any helper name to distinguish this time period.', max_length=255)), ('preview', models.BooleanField(default=False, help_text='Check to allow Google to show a preview of your media in search results.')), ('preview_start_mins', models.PositiveIntegerField(blank=True, help_text="Start time (minutes) of the media's preview,
shown on Google.com search results before
clicking through to see full video.", null=True, verbose_name=b'Preview start (minutes)')), ('preview_start_secs', models.CharField(blank=True, choices=[(b'00', b'0'), (b'01', b'1'), (b'02', b'2'), (b'03', b'3'), (b'04', b'4'), (b'05', b'5'), (b'06', b'6'), (b'07', b'7'), (b'08', b'8'), (b'09', b'9'), (b'10', b'10'), (b'11', b'11'), (b'12', b'12'), (b'13', b'13'), (b'14', b'14'), (b'15', b'15'), (b'16', b'16'), (b'17', b'17'), (b'18', b'18'), (b'19', b'19'), (b'20', b'20'), (b'21', b'21'), (b'22', b'22'), (b'23', b'23'), (b'24', b'24'), (b'25', b'25'), (b'26', b'26'), (b'27', b'27'), (b'28', b'28'), (b'29', b'29'), (b'30', b'30'), (b'31', b'31'), (b'32', b'32'), (b'33', b'33'), (b'34', b'34'), (b'35', b'35'), (b'36', b'36'), (b'37', b'37'), (b'38', b'38'), (b'39', b'39'), (b'40', b'40'), (b'41', b'41'), (b'42', b'42'), (b'43', b'43'), (b'44', b'44'), (b'45', b'45'), (b'46', b'46'), (b'47', b'47'), (b'48', b'48'), (b'49', b'49'), (b'50', b'50'), (b'51', b'51'), (b'52', b'52'), (b'53', b'53'), (b'54', b'54'), (b'55', b'55'), (b'56', b'56'), (b'57', b'57'), (b'58', b'58'), (b'59', b'59')], help_text="Start time (seconds) of the media's preview.", max_length=2, null=True, verbose_name=b'Preview start (seconds)')), ('preview_end_mins', models.PositiveIntegerField(blank=True, help_text="End time (minutes) of the media's preview,
shown on Google.com search results before
clicking through to see full video.", null=True, verbose_name=b'Preview end (minutes)')), ('preview_end_secs', models.CharField(blank=True, choices=[(b'00', b'0'), (b'01', b'1'), (b'02', b'2'), (b'03', b'3'), (b'04', b'4'), (b'05', b'5'), (b'06', b'6'), (b'07', b'7'), (b'08', b'8'), (b'09', b'9'), (b'10', b'10'), (b'11', b'11'), (b'12', b'12'), (b'13', b'13'), (b'14', b'14'), (b'15', b'15'), (b'16', b'16'), (b'17', b'17'), (b'18', b'18'), (b'19', b'19'), (b'20', b'20'), (b'21', b'21'), (b'22', b'22'), (b'23', b'23'), (b'24', b'24'), (b'25', b'25'), (b'26', b'26'), (b'27', b'27'), (b'28', b'28'), (b'29', b'29'), (b'30', b'30'), (b'31', b'31'), (b'32', b'32'), (b'33', b'33'), (b'34', b'34'), (b'35', b'35'), (b'36', b'36'), (b'37', b'37'), (b'38', b'38'), (b'39', b'39'), (b'40', b'40'), (b'41', b'41'), (b'42', b'42'), (b'43', b'43'), (b'44', b'44'), (b'45', b'45'), (b'46', b'46'), (b'47', b'47'), (b'48', b'48'), (b'49', b'49'), (b'50', b'50'), (b'51', b'51'), (b'52', b'52'), (b'53', b'53'), (b'54', b'54'), (b'55', b'55'), (b'56', b'56'), (b'57', b'57'), (b'58', b'58'), (b'59', b'59')], help_text="End time (seconds) of the media's preview.", max_length=2, null=True, verbose_name=b'Preview end (seconds)')), ('host', models.BooleanField(default=False, help_text='Check to allow Google to host your media after it expires. Must set expiration date in Dublin Core.')), ('author', models.ManyToManyField(help_text='Remember to save the user\'s name and e-mail address in the User application.', related_name='episode_authors', to=settings.AUTH_USER_MODEL)), ], options={ 'ordering': ['-date', 'slug'], }, ), migrations.CreateModel( name='Giorno', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('name', models.CharField(choices=[('Monday', 'Monday'), ('Tuesday', 'Tuesday'), ('Wednesday', 'Wednesday'), ('Thursday', 'Thursday'), ('Friday', 'Friday'), ('Saturday', 'Saturday'), ('Sunday', 'Sunday')], help_text='weekday name', max_length=20, unique=True)), ], ), migrations.CreateModel( name='MediaCategory', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('name', models.CharField(choices=[(b'Action & Adventure', b'Action & Adventure'), (b'Ads & Promotional', b'Ads & Promotional'), (b'Anime & Animation', b'Anime & Animation'), (b'Art & Experimental', b'Art & Experimental'), (b'Business', b'Business'), (b'Children & Family', b'Children & Family'), (b'Comedy', b'Comedy'), (b'Dance', b'Dance'), (b'Documentary', b'Documentary'), (b'Drama', b'Drama'), (b'Educational', b'Educational'), (b'Faith & Spirituality', b'Faith & Spirituality'), (b'Health & Fitness', b'Health & Fitness'), (b'Foreign', b'Foreign'), (b'Gaming', b'Gaming'), (b'Gay & Lesbian', b'Gay & Lesbian'), (b'Home Video', b'Home Video'), (b'Horror', b'Horror'), (b'Independent', b'Independent'), (b'Mature & Adult', b'Mature & Adult'), (b'Movie (feature)', b'Movie (feature)'), (b'Movie (short)', b'Movie (short)'), (b'Movie Trailer', b'Movie Trailer'), (b'Music & Musical', b'Music & Musical'), (b'Nature', b'Nature'), (b'News', b'News'), (b'Political', b'Political'), (b'Religious', b'Religious'), (b'Romance', b'Romance'), (b'Independent', b'Independent'), (b'Sci-Fi & Fantasy', b'Sci-Fi & Fantasy'), (b'Science & Technology', b'Science & Technology'), (b'Special Interest', b'Special Interest'), (b'Sports', b'Sports'), (b'Stock Footage', b'Stock Footage'), (b'Thriller', b'Thriller'), (b'Travel', b'Travel'), (b'TV Show', b'TV Show'), (b'Western', b'Western')], max_length=50)), ('slug', models.SlugField(blank=True, help_text='A slug is a URL-friendly nickname. For example, a slug for "Games & Hobbies" is "games-hobbies".')), ], options={ 'ordering': ['slug'], 'verbose_name': 'category (Media RSS)', 'verbose_name_plural': 'categories (Media RSS)', }, ), migrations.CreateModel( name='ParentCategory', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('name', models.CharField(choices=[(b'Arts', b'Arts'), (b'Business', b'Business'), (b'Comedy', b'Comedy'), (b'Education', b'Education'), (b'Games & Hobbies', b'Games & Hobbies'), (b'Government & Organizations', b'Government & Organizations'), (b'Health', b'Health'), (b'Kids & Family', b'Kids & Family'), (b'Music', b'Music'), (b'News & Politics', b'News & Politics'), (b'Religion & Spirituality', b'Religion & Spirituality'), (b'Science & Medicine', b'Science & Medicine'), (b'Society & Culture', b'Society & Culture'), (b'Sports & Recreation', b'Sports & Recreation'), (b'Technology', b'Technology'), (b'TV & Film', b'TV & Film')], help_text='After saving this parent category, please map it to one or more Child Categories below.', max_length=50)), ('slug', models.SlugField(blank=True, help_text='A slug is a URL-friendly nickname. For example, a slug for "Games & Hobbies" is "games-hobbies".')), ], options={ 'ordering': ['slug'], 'verbose_name': 'category (iTunes parent)', 'verbose_name_plural': 'categories (iTunes parent)', }, ), migrations.CreateModel( name='PeriodicSchedule', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('start_date', models.DateField(blank=True, help_text='The program will be in palimpsest starting from this date', null=True, verbose_name='Programmed start date')), ('end_date', models.DateField(blank=True, help_text='The program will be in palimpsest ending this date', null=True, verbose_name='Programmed end date')), ('time', models.TimeField(blank=True, help_text='This is the time when the program is planned in palimpsest', null=True, verbose_name='Programmed time')), ('giorni', models.ManyToManyField(blank=True, help_text='The program will be in palimpsest those weekdays', to='programs.Giorno', verbose_name='Programmed days')), ], ), migrations.CreateModel( name='ProgramType', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('code', models.CharField(default=None, max_length=4, unique=True, verbose_name='Code')), ('type', models.CharField(default=None, max_length=200, verbose_name='Type')), ('subtype', models.CharField(default=None, max_length=254, verbose_name='SubType')), ('description', models.TextField(blank=True, default=None, null=True, verbose_name='Description')), ], ), migrations.CreateModel( name='Schedule', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('emission_date', models.DateTimeField(help_text='This is the date and time when the program will be on air', verbose_name='programmed date')), ('episode', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='programs.Episode', verbose_name='Linked episode:')), ], ), migrations.CreateModel( name='ScheduleDone', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('emission_done', models.DateTimeField(editable=False, null=True, verbose_name='emission done')), ('enclosure', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='programs.Enclosure', verbose_name='Linked enclosure:')), ('schedule', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='programs.Schedule', verbose_name='Linked schedule:')), ], ), migrations.CreateModel( name='Show', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('title', models.CharField(help_text='show title', max_length=255)), ('active', models.BooleanField(default=True, help_text='Activate the show for emission', verbose_name='Active')), ('slug', models.SlugField(help_text='Auto-generated from Title.', unique=True)), ('length', models.FloatField(blank=True, default=None, help_text='Time lenght how you want to see it in the palimpsest', null=True, verbose_name='Time length (seconds)')), ('production', models.CharField(blank=True, choices=[('autoproduction', 'autoproduction'), ('eteroproduction', 'eteroproduction')], default=None, help_text='The type of production', max_length=30, null=True, verbose_name='Production')), ('organization', models.CharField(help_text='Name of the organization, company or Web site producing the podcast.', max_length=255)), ('link', models.URLField(help_text='URL of either the main website or the podcast section of the main website.')), ('description', models.TextField(help_text='Describe subject matter, media format, episode schedule and other relevant information while incorporating keywords.')), ('language', models.CharField(blank=True, default=b'en-us', help_text='Default is American English. See ISO 639-1 and ISO 3166-1 for more language codes.', max_length=5)), ('copyright', models.CharField(choices=[(b'Public domain', b'Public domain'), (b'Creative Commons: Attribution (by)', b'Creative Commons: Attribution (by)'), (b'Creative Commons: Attribution-Share Alike (by-sa)', b'Creative Commons: Attribution-Share Alike (by-sa)'), (b'Creative Commons: Attribution-No Derivatives (by-nd)', b'Creative Commons: Attribution-No Derivatives (by-nd)'), (b'Creative Commons: Attribution-Non-Commercial (by-nc)', b'Creative Commons: Attribution-Non-Commercial (by-nc)'), (b'Creative Commons: Attribution-Non-Commercial-Share Alike (by-nc-sa)', b'Creative Commons: Attribution-Non-Commercial-Share Alike (by-nc-sa)'), (b'Creative Commons: Attribution-Non-Commercial-No Dreivatives (by-nc-nd)', b'Creative Commons: Attribution-Non-Commercial-No Dreivatives (by-nc-nd)'), (b'All rights reserved', b'All rights reserved')], default=b'All rights reserved', help_text='See Creative Commons licenses for more information.', max_length=255)), ('copyright_url', models.URLField(blank=True, help_text='A URL pointing to additional copyright information. Consider a Creative Commons license URL.', verbose_name=b'Copyright URL')), ('category_show', models.CharField(blank=True, help_text='Limited to one user-specified category for the sake of sanity.', max_length=255, verbose_name=b'Category')), ('domain', models.URLField(blank=True, help_text='A URL that identifies a categorization taxonomy.')), ('ttl', models.PositiveIntegerField(blank=True, help_text='"Time to Live," the number of minutes a channel can be cached before refreshing.', null=True, verbose_name=b'TTL')), ('image', models.ImageField(blank=True, help_text='An attractive, original square JPEG (.jpg) or PNG (.png) image of 600x600 pixels. Image will be scaled down to 50x50 pixels at smallest in iTunes.', upload_to=b'podcasts/shows/img/')), ('feedburner', models.URLField(blank=True, help_text='Fill this out after saving this show and at least one episode. URL should look like "http://feeds.feedburner.com/TitleOfShow". See documentation for more.', verbose_name=b'FeedBurner URL')), ('subtitle', models.CharField(blank=True, help_text='Looks best if only a few words, like a tagline.', max_length=255)), ('summary', models.TextField(blank=True, help_text='Allows 4,000 characters. Description will be used if summary is blank.')), ('explicit', models.CharField(blank=True, choices=[(b'Yes', b'Yes'), (b'No', b'No'), (b'Clean', b'Clean')], default=b'No', help_text='"Clean" will put the clean iTunes graphic by it.', max_length=255)), ('block', models.BooleanField(default=False, help_text='Check to block this show from iTunes.
Show will remain blocked until unchecked.')), ('redirect', models.URLField(blank=True, help_text="The show's new URL feed if changing the URL of the current show feed. Must continue old feed for at least two weeks and write a 301 redirect for old feed.")), ('keywords', models.CharField(blank=True, help_text='A comma-demlimited list of up to 12 words for iTunes searches. Perhaps include misspellings of the title.', max_length=255)), ('itunes', models.URLField(blank=True, help_text='Fill this out after saving this show and at least one episode. URL should look like "http://phobos.apple.com/WebObjects/MZStore.woa/wa/viewPodcast?id=000000000". See documentation for more.', verbose_name=b'iTunes Store URL')), ('author', models.ManyToManyField(help_text='Remember to save the user\'s name and e-mail address in the User application.
', related_name='display_authors', to=settings.AUTH_USER_MODEL)), ('category', models.ManyToManyField(blank=True, help_text='If selecting a category group with no child category (e.g., Comedy, Kids & Family, Music, News & Politics or TV & Film), save that parent category with a blank child category.
Selecting multiple category groups makes the podcast more likely to be found by users.
', related_name='show_categories', to='programs.ChildCategory')), ('type', models.ForeignKey(help_text='The categorization that follow the italian law (you have to use it to produce the programs book', on_delete=django.db.models.deletion.CASCADE, to='programs.ProgramType', verbose_name='Program Type')), ('webmaster', models.ForeignKey(blank=True, help_text='Remember to save the user\'s name and e-mail address in the User application.', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='display_webmaster', to=settings.AUTH_USER_MODEL)), ], options={ 'ordering': ['organization', 'slug'], }, ), migrations.AddField( model_name='periodicschedule', name='show', field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='programs.Show', verbose_name='refer to show:'), ), migrations.AddField( model_name='episode', name='media_category', field=models.ManyToManyField(blank=True, related_name='episode_categories', to='programs.MediaCategory'), ), migrations.AddField( model_name='episode', name='show', field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='programs.Show'), ), migrations.AddField( model_name='enclosure', name='episode', field=models.ForeignKey(help_text='Include any number of media files; for example, perhaps include an iPhone-optimized, AppleTV-optimized and Flash Video set of video files. Note that the iTunes feed only accepts the first file. More uploading is available after clicking "Save and continue editing."', on_delete=django.db.models.deletion.CASCADE, to='programs.Episode'), ), migrations.AddField( model_name='childcategory', name='parent', field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='child_category_parents', to='programs.ParentCategory'), ), migrations.AddField( model_name='aperiodicschedule', name='show', field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='programs.Show', verbose_name='refer to Show:'), ), ] autoradio-2.8.6/autoradio/programs/managers.py0000664000175000017500000000056013001105756021243 0ustar pat1pat100000000000000from django.db.models import Manager import datetime class EpisodeManager(Manager): """Returns public posts that are not in the future.""" def __init__(self, *args, **kwargs): super(EpisodeManager, self).__init__(*args, **kwargs) def published(self): return self.get_queryset().filter(status__exact=2, date__lte=datetime.datetime.now()) autoradio-2.8.6/autoradio/audaciousweb.py0000664000175000017500000001762413001105756020300 0ustar pat1pat100000000000000#!/usr/bin/env python # coding=utf-8 """ Show audacious playlist on a simple web server. """ maxplele=100 # max number of elements in playlist port=8888 # server port #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 import os cpversion3=cherrypy.__version__.startswith("3") import datetime # ------- dbus interface --------- import dbus bus = dbus.SessionBus() head=''' AUDACIOUS monitor | ''' tail=''' ''' class HomePage: # def Main(self): # # Let's link to another method here. # htmlresponse='Goto audacious status for autoradio!
' # htmlresponse+='Goto audacious playlist for autoradio!
' # return htmlresponse # Main.exposed = True def __init__(self,iht): self.iht=iht def test(self): "return test page" return "Test Page" test.exposed = True def status(self): "return audacious 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 "audacious is playing" else: return "audacious is stopped" status.exposed = True def index(self): "return audacious playlist" if (self.iht) : htmlresponse=head else: htmlresponse="" try: # ----------------------------------------------------------- root_obj = bus.get_object("org.atheme.audacious", '/') player_obj = bus.get_object("org.atheme.audacious", '/Player') tracklist_obj = bus.get_object("org.atheme.audacious", '/TrackList') org_obj = bus.get_object("org.atheme.audacious", '/org/atheme/audacious') root = dbus.Interface(root_obj, dbus_interface='org.freedesktop.MediaPlayer') player = dbus.Interface(player_obj, dbus_interface='org.freedesktop.MediaPlayer') tracklist = dbus.Interface(tracklist_obj, dbus_interface='org.freedesktop.MediaPlayer') org = dbus.Interface(org_obj, dbus_interface='org.atheme.audacious') # ----------------------------------------------------------- except: return "error intializing dbus" try: cpos=int(tracklist.GetCurrentTrack()) except: return "error tracklist.GetCurentTrack()" try: isplaying= org.Playing() except: return "error org.Playing()" try: len=tracklist.GetLength() htmlresponse+='

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

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

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

" if (self.iht) : htmlresponse+=tail return htmlresponse index.exposed = True def start_http_server(iht=False): """ start web server to monitor audacious 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/audaciousweb.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),config=settings) else: cherrypy.config.update(settings) cherrypy.root = HomePage(iht) 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) except: print "Error" raise finally: print "Terminated" autoradio-2.8.6/autoradio/autoradio_core.py0000664000175000017500000005440013001105756020615 0ustar pat1pat100000000000000#!/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-2.8.6/autoradio/mprisweb.py0000664000175000017500000001537713001105756017460 0ustar pat1pat100000000000000#!/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-2.8.6/autoradio/gest_playlist.py0000664000175000017500000001736413001105756020511 0ustar pat1pat100000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # GPL. (C) 2007-2009 Paolo Patruno. import os os.environ['DJANGO_SETTINGS_MODULE'] = 'autoradio.settings' from django.conf import settings import logging import datetime from autoradio_config import * from django.db.models import Q from playlists.models import Configure from playlists.models import PeriodicSchedule from playlists.models import Schedule from playlists.models import Playlist if (player == "amarok") : from autoradiod import amarok import os,calendar class gest_playlist: def __init__ (self,now,minelab): """init of playlist application: now : currenti datetime minelab: minutes to elaborate execute the right data retrival to get the schedued playlists""" self.now = now ora=self.now.time() self.oggi=self.now.date() self.giorno=calendar.day_name[self.now.weekday()] self.schedule=() self.periodicschedule=() self.datesched_min=self.now - datetime.timedelta( seconds=60*minelab) self.datesched_max=self.now + datetime.timedelta( milliseconds=60000*minelab-1) # 1 millisecond tollerance self.timesched_min=self.datesched_min.time() self.timesched_max=self.datesched_max.time() logging.debug( "PLAYLIST: elaborate date from %s to %s",self.datesched_min, self.datesched_max) logging.debug( "PLAYLIST: elaborate time from %s to %s",self.timesched_min, self.timesched_max) if (Configure.objects.filter(active__exact=False).count() == 1): return #todo: the use of ora here is not exact if (Configure.objects.filter(emission_starttime__gt=ora).count() == 1) : return if (Configure.objects.filter(emission_endtime__lt=ora).count() == 1): return # retrive the right records relative to schedule self.schedule=Schedule.objects.select_related()\ .filter(emission_date__gte=self.datesched_min)\ .filter(emission_date__lte=self.datesched_max)\ .filter(playlist__active__exact=True)\ .order_by('emission_date') # retrive the right records relative to periodicschedule if (self.timesched_min < self.timesched_max): self.periodicschedule=PeriodicSchedule.objects\ .filter(Q(start_date__lte=self.oggi) | Q(start_date__isnull=True))\ .filter(Q(end_date__gte=self.oggi) | Q(end_date__isnull=True))\ .filter(time__gte=self.timesched_min)\ .filter(time__lte=self.timesched_max)\ .filter(giorni__name__exact=self.giorno)\ .filter(playlist__active__exact=True)\ .order_by('time') else: # warning here we are around midnight logging.debug("PLAYLIST: around midnight") ieri=unicode(calendar.day_name[self.datesched_min.weekday()], 'utf-8') domani=unicode(calendar.day_name[self.datesched_max.weekday()], 'utf-8') self.periodicschedule=PeriodicSchedule.objects.filter \ (Q(start_date__lte=self.oggi) | Q(start_date__isnull=True),\ Q(end_date__gte=self.oggi) | Q(end_date__isnull=True),\ (Q(time__gte=self.timesched_min) & Q(giorni__name__exact=ieri))\ |\ (Q(time__lte=self.timesched_max) & Q(giorni__name__exact=domani))\ ,\ playlist__active__exact=True)\ .order_by('time') def get_playlist(self): "iterable to get playlist" for playlist in self.schedule: logging.debug("PLAYLIST: schedule %s %s %s", playlist.playlist.playlist, ' --> '\ ,playlist.emission_date.isoformat()) # amarok vuole il nome della playlist che deve gia' esistere del suo elenco # gli altri vogliono il file della playlist if (player == "amarok"): playlist.ar_filename=playlist.playlist.playlist else: playlist.ar_filename=playlist.playlist.file.path playlist.ar_url=playlist.playlist.file.url playlist.ar_scheduledatetime=playlist.emission_date playlist.ar_emission_done=playlist.emission_done # # calcolo la lunghezza del programma # relpath= os.path.basename(playlist.ar_filename) # basedir=os.path.dirname(playlist.ar_filename) # try: # meta = metadata.metadata_from_file(relpath, \ # basedir, tracknrandtitlere, postprocessors) # playlist.ar_length=meta.length # except: # playlist.ar_length=3600 playlist.ar_length=playlist.length if playlist.ar_length is None : playlist.ar_length=3600*24-1 playlist.ar_shuffle=playlist.shuffle yield playlist for playlist in self.periodicschedule: logging.debug("PLAYLIST: periodic schedule %s %s %s", playlist.playlist.playlist, ' --> '\ , playlist.time.isoformat()) # amarok vuole il nome della playlist che deve gia' esistere del suo elenco # gli altri vogliono il file della playlist if (player == "amarok"): playlist.ar_filename=playlist.playlist.playlist else: playlist.ar_filename=playlist.playlist.file.path playlist.ar_url=playlist.playlist.file.url #print self.timesched_min, self.timesched_max if (self.timesched_min < self.timesched_max): #print self.datesched_min.date() #print playlist.time playlist.ar_scheduledatetime=datetime.datetime.combine(self.datesched_min.date(), playlist.time) else: # we are around midnight we have to check the correct date (today, tomorrow) if playlist.time > datetime.time(12): playlist.ar_scheduledatetime=datetime.datetime.combine(self.datesched_min.date(), playlist.time) else: playlist.ar_scheduledatetime=datetime.datetime.combine(self.datesched_max.date(), playlist.time) playlist.ar_emission_done=playlist.emission_done # # calcolo la lunghezza del programma # relpath= os.path.basename(playlist.ar_filename) # basedir=os.path.dirname(playlist.ar_filename) # try: # meta = metadata.metadata_from_file(relpath, \ # basedir, tracknrandtitlere, postprocessors) # playlist.ar_length=meta.length # except: # playlist.ar_length=3600 playlist.ar_length=playlist.length if playlist.ar_length is None : playlist.ar_length=3600*24-1 playlist.ar_shuffle=playlist.shuffle yield playlist def main(): # logging.basicConfig(level=logging.DEBUG,) logging.basicConfig(level=logging.INFO,) # time constants now=datetime.datetime.now() for hour in (0,3,6,9,12,15,18,21): now=now.replace(hour=hour) print print "Runnig for date: ",now # get the playlists of my insterest pla=gest_playlist(now,minelab) # I do a list for playlist in pla.get_playlist(): print "--------------------------------" print "found schedule: ",playlist print playlist.ar_filename print playlist.ar_url print playlist.ar_scheduledatetime print playlist.ar_length print "playlist",playlist.playlist #.program.get_file_filename() print "--------------------------------" if __name__ == '__main__': main() # (this code was run as script) autoradio-2.8.6/autoradio/managexmms.py0000664000175000017500000001671413001105756017761 0ustar pat1pat100000000000000#!/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-2.8.6/autoradio/daemon.py0000664000175000017500000002405613001105756017065 0ustar pat1pat100000000000000# -*- 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-2.8.6/autoradio/managempris.py0000664000175000017500000002024213001105756020116 0ustar pat1pat100000000000000#!/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" # A thread can be flagged as a "daemon thread". # The significance of this flag is that the entire Python program # exits when only daemon threads are left. # The initial value is inherited from the creating thread. # The flag can be set through the daemon property. self.timer.daemon=True self.timer.start() def cancel (self): "cancel programmed schedule" self.timer.cancel() 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-2.8.6/autoradio/xmmsweb.py0000775000175000017500000001237113001105756017304 0ustar pat1pat100000000000000#!/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-2.8.6/autoradio/gest_palimpsest.py0000664000175000017500000001326713001105756021027 0ustar pat1pat100000000000000#!/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 django.db.models import Q from programs.models import Configure from programs.models import PeriodicSchedule from programs.models import AperiodicSchedule import os,calendar class gest_palimpsest: def __init__ (self,datetimeelab,minelab): """init of palimpsest application: datetimeelab : datetime to elaborate execute the right data retrival to get the schedued programs""" self.radiostation=None self.channel=None self.mezzo=None self.type=None self.datetimeelab = datetimeelab self.oggi=self.datetimeelab.date() ora=datetimeelab.time() self.giorno=calendar.day_name[self.datetimeelab.weekday()] self.schedule=() self.periodicschedule=() self.datesched_min=self.datetimeelab - timedelta( seconds=60*minelab) self.datesched_max=self.datetimeelab + timedelta( milliseconds=60000*minelab-1) #1 millisecond tollerance self.timesched_min=self.datesched_min.time() self.timesched_max=self.datesched_max.time() logging.debug( "PALIMPSEST: elaborate date from %s to %s",self.datesched_min, self.datesched_max) logging.debug( "PALIMPSEST: elaborate time from %s to %s",self.timesched_min, self.timesched_max) if (Configure.objects.filter(active__exact=False).count() == 1): return #todo: the use of ora here is not exact if (Configure.objects.filter(emission_starttime__gt=ora).count() == 1) : return if (Configure.objects.filter(emission_endtime__lt=ora).count() == 1): return # retrive the right records relative to schedule self.schedule=AperiodicSchedule.objects.select_related()\ .filter(emission_date__gte=self.datesched_min)\ .filter(emission_date__lte=self.datesched_max)\ .filter(show__active__exact=True)\ .order_by('emission_date') # retrive the right records relative to periodicschedule if (self.timesched_min < self.timesched_max): self.periodicschedule=PeriodicSchedule.objects\ .filter(Q(start_date__lte=self.oggi) | Q(start_date__isnull=True))\ .filter(Q(end_date__gte=self.oggi) | Q(end_date__isnull=True))\ .filter(time__gte=self.timesched_min)\ .filter(time__lte=self.timesched_max)\ .filter(giorni__name__exact=self.giorno)\ .filter(show__active__exact=True)\ .order_by('time') else: # warning here we are around midnight logging.debug("PALIMPSEST: around midnight") domani=calendar.day_name[self.datesched_max.weekday()] self.periodicschedule=PeriodicSchedule.objects\ .filter(Q(start_date__lte=self.oggi) | Q(start_date__isnull=True))\ .filter(Q(end_date__gte=self.oggi) | Q(end_date__isnull=True))\ .filter(Q(time__gte=self.timesched_min) & Q(giorni__name__exact=self.giorno) |\ Q(time__lte=self.timesched_max) & Q(giorni__name__exact=domani))\ .filter(show__active__exact=True)\ .order_by('time') infos=Configure.objects.filter(active__exact=True) if (infos.count() == 1): for info in infos: self.radiostation=info.radiostation self.channel=info.channel self.mezzo=info.mezzo self.type=info.type def get_program(self): "iterable to get programs" ora=self.datetimeelab.time() for program in self.schedule: logging.debug("PALIMPSEST: schedule %s %s", program.show.title, ' --> '\ ,program.emission_date.isoformat()) program.ar_scheduledatetime=program.emission_date yield program for program in self.periodicschedule: logging.debug("PALIMPSEST: periodic schedule %s %s", program.show.title, ' --> '\ , program.time.isoformat()) if (self.timesched_min < self.timesched_max): program.ar_scheduledatetime=datetime.combine(self.datesched_min.date(), program.time) else: # we are around midnight we have to check the correct date (today, tomorrow) if program.time > time(12): program.ar_scheduledatetime=datetime.combine(self.datesched_min.date(), program.time) else: program.ar_scheduledatetime=datetime.combine(self.datesched_max.date(), program.time) yield program def get_info(self): """ get station info yield: radiostation channel mezzo type """ yield self.radiostation yield self.channel yield self.mezzo yield self.type def main(): logging.basicConfig(level=logging.DEBUG,) # time constants datetimeelab=datetime.now() minelab=60*4 # get the programs of my insterest pro=gest_palimpsest(datetimeelab,minelab) for info in pro.get_info(): print "info: ",info # I do a list for program in pro.get_program(): #pass print program print program.ar_scheduledatetime print program.program.length print "program",program.program if __name__ == '__main__': main() # (this code was run as script) autoradio-2.8.6/autoradio/mime.py0000664000175000017500000000264413001105756016550 0ustar pat1pat100000000000000 # 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-2.8.6/autoradio/gest_program.py0000664000175000017500000001144613001105756020312 0ustar pat1pat100000000000000#!/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-2.8.6/autoradio/gest_jingle.py0000664000175000017500000001263413001105756020113 0ustar pat1pat100000000000000#!/usr/bin/env python # This Python file uses the following encoding: utf-8 # 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 django.db.models import Q from django.core.exceptions import ObjectDoesNotExist from itertools import * import calendar from jingles.models import Configure from jingles.models import Jingle from jingles.models import Giorno # used to get metadata from audio files import mutagen import os freq_default=time(00,15,00) def time_iterator(datesched_min,datesched_max,emission_freq): datai=datesched_min.date() delta=timedelta(hours=emission_freq.hour,minutes=emission_freq.minute,\ seconds=emission_freq.second) datac=datetime.combine(datai,time(00,00,00)) while datac < datesched_min: datac=datac+delta yield datac while datesched_max>= datac: datac=datac+delta yield datac class gest_jingle: def __init__ (self,now,minelab): """init of jingle application: now : currenti datetime minelab: minutes to elaborate execute the right data retrival to get the schedued jingles""" self.now=now self.minelab=minelab self.ora=now.time() self.oggi=now.date() self.giorno=calendar.day_name[now.weekday()] self.datesched_min=self.now - timedelta( seconds=60*self.minelab) self.datesched_max=self.now + timedelta( seconds=60*self.minelab) logging.debug( "JINGLE: elaborate from %s to %s",self.datesched_min, self.datesched_max) #self.timesched_min=self.datesched_min.time() #self.timesched_max=self.datesched_max.time() #logging.debug( "JINGLE: elaborate from %s to %s",timesched_min, timesched_max) try: self.emission_freq = Configure.objects.get().emission_freq except ObjectDoesNotExist: logging.warning( "JINGLE: emission_freq doesn't exist. Setting default") self.emission_freq = freq_default logging.debug("JINGLE: frequenza di emissione %s",self.emission_freq) if (Configure.objects.filter(active__exact=False).count() == 1): self.jingles=() return #todo: ma i NULL nel sort dove stanno? all'inizio o alla fine? #todo: l'order by qui non funziona in quanto vale praticamente sempre #quello che è stato emesso piu' in la nel tempo #la priorità di fatto non viene considerata # if (timesched_min < timesched_max): # we select every jingle active at "now" # if not selected some time limits is like 00 for start and 24 for end # warning: if you set 18:00 for start and nothing for end it start 18:00 and stop at 24:00 self.jingles= Jingle.objects.filter\ (Q(start_date__lte=self.oggi) | Q(start_date__isnull=True),\ Q(end_date__gte=self.oggi) | Q(end_date__isnull=True),\ Q(start_time__lte=self.ora) | Q(start_time__isnull=True),\ Q(end_time__gte=self.ora) | Q(end_time__isnull=True),\ Q(giorni__name__exact=self.giorno) , Q(active__exact=True))\ .order_by('emission_done','priorita') # TODO: we have to add case were start_time > end_time # this is only a special case; no good # else: # # warning here we are around midnight # # we select every jingle active at "now" # # but we have a value of 24 and 00 for implicit max and min # self.jingles= Jingle.objects.filter\ # (Q(start_date__lte=self.oggi) | Q(start_date__isnull=True),\ # Q(end_date__gte=self.oggi) | Q(end_date__isnull=True),\ # Q(start_time__lte=self.ora) | Q(start_time__isnull=True) | Q(end_time__gte=self.ora) | Q(end_time__isnull=True),\ # Q(giorni__name__exact=self.giorno) , Q(active__exact=True))\ # .order_by('emission_done','priorita') def get_jingle(self): many_jingles=cycle(self.jingles) # for datac in time_iterator(self.datesched_min,self.datesched_max,self.emission_freq): for datac in time_iterator(self.now,self.datesched_max,self.emission_freq): jingle=many_jingles.next() jingle.ar_filename=jingle.file.path.encode("utf8") jingle.ar_url=jingle.file.url # jingle.ar_filename=jingle.get_file_filename() jingle.ar_scheduledatetime=datac jingle.ar_emission_done=jingle.emission_done # elaborate the media time length try: jingle.ar_length=mutagen.File(jingle.ar_filename).info.length logging.debug("JINGLE: time length: %s",jingle.ar_length) except: logging.error("JINGLE: error establish time length; use an estimation %s", jingle.ar_filename) jingle.ar_length=30 yield jingle def main(): now=datetime.now() jingles=gest_jingle(now,minelab) for jingle in jingles.get_jingle(): print "----------------------------" print jingle print jingle.ar_url print jingle.ar_filename print jingle.ar_scheduledatetime print jingle.ar_length print jingle.ar_emission_done for giorno in jingle.giorni.all(): print giorno if __name__ == '__main__': main() # (this code was run as script) autoradio-2.8.6/autoradio/gest_spot.py0000664000175000017500000002232513001105756017626 0ustar pat1pat100000000000000#!/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 import datetime 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 import time #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 - datetime.timedelta( seconds=60*self.minelab) datesched_max=self.now + datetime.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.ar_url=self.get_fascia_playlist_media(genfile) self.ar_scheduledatetime=datetime.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 - datetime.timedelta( seconds=60*self.minelab) datesched_max=self.now + datetime.timedelta( seconds=60*self.minelab) if not (datesched_min <= self.ar_scheduledatetime and self.ar_scheduledatetime <= datesched_max ): if self.now.time() < datetime.time(12): self.ar_scheduledatetime=datetime.datetime.combine(datesched_min.date(), fascia.emission_time) else: self.ar_scheduledatetime=datetime.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): name=self.fascia.name+".m3u" url=os.path.join(os.path.join(settings.MEDIA_URL, playlistdir),name) playlistname =os.path.join(self.playlistpath,name) 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(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) #sometime I get: #shutil.move(tmpfile,playlistname) #File "/usr/lib64/python2.7/shutil.py", line 301, in move #copy2(src, real_dst) #File "/usr/lib64/python2.7/shutil.py", line 130, in copy2 #copyfile(src, dst) #File "/usr/lib64/python2.7/shutil.py", line 83, in copyfile #with open(dst, 'wb') as fdst: # IOError: [Errno 11] Risorsa temporaneamente non disponibile: u'/home/autoradio/media/pubblicita/ore 13.30.m3u' # so I try to do it in a delayed loop ntry=0 while True: try: shutil.move(tmpfile,playlistname) logging.debug("SPOT: moved the playlist %s in %s",tmpfile,playlistname) break except: logging.warning("SPOT: error moving the playlist %s in %s",tmpfile,playlistname) ntry +=1 if ntry > 5: logging.error("SPOT: cannot move the playlist %s in %s",tmpfile,playlistname) break time.sleep(1) return playlistname,url def main(): logging.basicConfig(level=logging.DEBUG,) now=datetime.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-2.8.6/autoradio/autoxmms.py0000664000175000017500000001020213001105756017463 0ustar pat1pat100000000000000#!/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-2.8.6/autoradio/pydbusdecorator/0000775000175000017500000000000013003471473020456 5ustar pat1pat100000000000000autoradio-2.8.6/autoradio/pydbusdecorator/undefined_param.py0000664000175000017500000000075413001105756024153 0ustar pat1pat100000000000000''' Created on Nov 5, 2011 @author: hugosenari ''' class Undefined(): '''Undefined''' def __nonzero__(self=None): return False def __repr__(self=None): return "Undefined" class UndefinedParam(Undefined): ''' UndefinedParam ''' def __cmp__(self, other): return isinstance(other, Undefined) UNDEFINED = Undefined() UNDEFINED_PARAM = UndefinedParam() if __name__ == "__main__": print "True" if bool(UNDEFINED_PARAM) else "False"autoradio-2.8.6/autoradio/pydbusdecorator/dbus_decorator.py0000664000175000017500000000037213001105756024025 0ustar pat1pat100000000000000''' Created on Nov 5, 2011 @author: hugosenari ''' import dbus class DbusDecorator(object): ''' Decorator root class ''' dbus_lib = dbus def __init__(self, *args, **kw): ''' Constructor ''' passautoradio-2.8.6/autoradio/pydbusdecorator/__init__.py0000664000175000017500000000047213001105756022566 0ustar pat1pat100000000000000''' Created on Nov 5, 2011 @author: hugosenari ''' from dbus_decorator import DbusDecorator from dbus_interface import DbusInterface from dbus_attr import DbusAttr from dbus_method import DbusMethod from dbus_signal import DbusSignal from undefined_param import UndefinedParam if __name__ == '__main__': passautoradio-2.8.6/autoradio/pydbusdecorator/dbus_data.py0000664000175000017500000000106013001105756022747 0ustar pat1pat100000000000000#''' #Created on Nov 5, 2011 # #@author: hugosenari #''' #from functools import wraps #class DbusData(object): # ''' # Wrapps class as data # ''' # # def __init__(self, *args, **kw): # ''' # Constructor # ''' # # def __call__(self, meth, *args, **kw): # ''' # wrap function # ''' # @wraps(meth) # def dbusWrapedData(*args, **kw): # return meth(*args, **kw) # return dbusWrapedData # # def __get__(self, obj, objtype=None): # return obj if obj else self autoradio-2.8.6/autoradio/pydbusdecorator/dbus_signal.py0000664000175000017500000000512413001105756023320 0ustar pat1pat100000000000000''' Created on Nov 5, 2011 @author: hugosenari ''' from dbus_decorator import DbusDecorator from dbus_interface import DbusInterface from functools import wraps class DbusSignal(DbusDecorator): ''' Wrapps some method as attribute ''' def __init__(self, meth=None, iface=None, *args, **kw): ''' @param meth: function, wrapped ''' super(DbusSignal, self).__init__(*args, **kw) self.meth = meth self.iface = iface self._obj = None self._handler = meth def _watch_dbus(self, obj, *args, **kw): ''' set self._handler to be called when obj fire signal wrapped ''' def handler(*args, **kw): if self.meth is not self._handler: DbusInterface.store_result(obj, self.meth(obj, *args, **kw)) return self._handler(self._obj, *args, **kw) if self.iface: bus_iface = DbusInterface.iface(obj, self.iface) bus_iface.connect_to_signal(self.meth.__name__, handler, dbus_interface=self.iface, *args, **kw) else: bus_obj = DbusInterface.get_bus_obj(obj) bus_obj.connect_to_signal(self.meth.__name__, handler, dbus_interface=DbusInterface.get_bus_iface(obj), *args, **kw) def _remove_watch_dbus(self, obj): pass def __call__(self, __call__meth=None, *args, **kw): if self.meth and self._obj: if __call__meth: largs = list(args) largs.insert(0, __call__meth) args = tuple(largs) return self._watch_dbus(self._obj, *args, **kw) else: self.meth = __call__meth self._handler = __call__meth @wraps(self.meth) def dbusWrapedMethod(obj, *args, **kw): return self._watch_dbus(obj, *args, **kw) return self def __get__(self, obj, objtype=None): if obj is None: return self self._obj = obj return self._handler def __set__(self, obj, handler, *args, **kw): self._obj = obj self._handler = handler self._watch_dbus(obj, *args, **kw) @property def meth(self): return self._meth @meth.setter def meth(self, value): self._meth = value if hasattr(value, "__doc__"): self.__doc__ = value.__doc__ autoradio-2.8.6/autoradio/pydbusdecorator/dbus_method.py0000664000175000017500000001062513001105756023325 0ustar pat1pat100000000000000''' Created on Nov 5, 2011 @author: hugosenari ''' from dbus_decorator import DbusDecorator from dbus_interface import DbusInterface from functools import wraps def kw_to_dbus(**kw): return kw def args_to_dbus(*args): return args class DbusMethod(DbusDecorator): ''' Wrapps some method calling dbus method ''' DbusMethodId = 0 def __init__(self, meth=None, iface=None, produces=None, args_to_dbus=args_to_dbus, kw_to_dbus=kw_to_dbus, override_none_return = True, *args, **kw): ''' Wrapps some method calling dbus method @param meth: method to be wrapped @param produces: callable to convert dbus response into other type: produces(dbus_returned) if is not callable but iterable try convert params calling list item in same order @param args_to_dbus: callable to convert function params into dbus typee: args_to_dbus([array of args]) if is not callable but dict try convert params calling dicts item whith the same keyword @param kw_to_dbus: callable to convert function params (received as keywords dict) into dbus types: kw_to_dbus({dict of keywords}) @param override_none_return: Set if override response of call decorated attr (method) with value of dbus attr response ''' self.uniq = DbusMethod.DbusMethodId DbusMethod.DbusMethodId = self.uniq + 1 super(DbusMethod, self).__init__(*args, **kw) self.meth = meth self.iface = iface self.obj = None self.produces = produces self.args_to_dbus = args_to_dbus self.kw_to_dbus = kw_to_dbus self.override_return = True def _call_dbus(self, obj, *args, **kw): # print self.meth.__name__, args, kw bus_obj = DbusInterface.get_bus_obj(obj) bus_interface = self.iface if self.iface else\ DbusInterface.get_bus_iface(obj) bus_meth = bus_obj.get_dbus_method(self.meth.__name__, bus_interface) args = self.convert_args_to_dbus_args(*args) kw = self.convert_kw_to_dbus_kw(**kw) dbus_result = bus_meth(*args, **kw) DbusInterface.store_result(obj, dbus_result) produces = self.produces if produces: return produces(dbus_result) meth = self.meth result = None if meth: result = meth(obj, *args, **kw) if result is None and self.override_return: result = dbus_result return result def __call__(self, _meth=None, *args, **kw): meth = _meth or self.meth if self.meth and self.obj: if _meth: largs = list(args) largs.insert(0, meth) args = tuple(largs) return self._call_dbus(self.obj, *args, **kw) else: self.meth = meth @wraps(self.meth) def dbusWrapedMethod(obj, *args, **kw): return self._call_dbus(obj, *args, **kw) return dbusWrapedMethod def __get__(self, obj=None, *args, **kw): if obj is None: return self self.obj = obj return self.__call__ def convert_args_to_dbus_args(self, *args): args_to_dbus = self.args_to_dbus if callable(args_to_dbus): return args_to_dbus(*args) result = [] #iterate over args for i in range(len(args)): arg = args[i] if i < len(args_to_dbus): make = args_to_dbus[i] if callable(make): arg = make(arg) result.append(arg) return tuple(result) def convert_kw_to_dbus_kw(self, **kw): kw_to_dbus = self.kw_to_dbus if callable(kw_to_dbus): return kw_to_dbus(**kw) if hasattr(self.kw_to_dbus, 'keys'): to_dbus_keys = kw_to_dbus.keys() for key in kw.keys(): if key in to_dbus_keys: kw[key] = kw_to_dbus[key](kw[key]) return kw @property def meth(self): return self._meth @meth.setter def meth(self, value): self._meth = value if hasattr(value, "__doc__"): self.__doc__ = value.__doc__ autoradio-2.8.6/autoradio/pydbusdecorator/dbus_attr.py0000664000175000017500000000623613001105756023022 0ustar pat1pat100000000000000''' Created on Nov 5, 2011 @author: hugosenari ''' from dbus_decorator import DbusDecorator from undefined_param import UNDEFINED_PARAM from dbus_interface import DbusInterface class DbusAttr(DbusDecorator): ''' Wrap some method as attribute Works like @property, but for dbus ''' def __init__(self, meth=None, iface=None, produces=lambda resp: resp, to_primitive=lambda resp: resp, override_none_val=UNDEFINED_PARAM, override_none_return=True, *args, **kw): ''' Instantiate one new DbusAttr decoreator By default pass received val to method @param meth: function overrided @param iface: str dbus intercafe string with this property @param produces: callable, function or class with one param, that converts received data @param to_primitive: callable, function or class with one param, that converts data to send @param override_none_val: Set if override val to call decorated attr (method) with value of dbus attr response Any value of val different than UNDEFINED_PARAM is not 'None' @param override_none_return: Set if override response of call decorated attr (method) with value of dbus attr response ''' super(DbusAttr, self).__init__(*args, **kw) self.attr = meth self.iface = iface self.produces = produces self.to_primitive = to_primitive self.override_val = override_none_val self.override_return = override_none_return def __call__(self, meth, *args, **kw): self.attr = meth return self def _get_set_dbus(self, obj, val=UNDEFINED_PARAM, *args, **kw): properties = DbusInterface.get_bus_properties(obj) iface = self.iface or DbusInterface.get_bus_iface(obj) #vals is UndefinedParam, try to get val from object if val is UNDEFINED_PARAM: mval = properties.Get(iface, self.attr.__name__) DbusInterface.store_result(obj, mval) if self.override_val: val = mval #else set val in property (meth.__name__) else: to_primitve = self.to_primitive properties.Set(iface, self.attr.__name__, val) DbusInterface.store_result(obj, to_primitve(val)) result = self.attr(val, *args, **kw) if result is None and self.override_return: result = properties.Get(iface, self.attr.__name__) produces = self.produces return produces(result) def __get__(self, obj, objtype=None): if obj is None: return self return self._get_set_dbus(obj) def __set__(self, obj, value): if obj: self._get_set_dbus(obj, value) else: self.attr = value def __delete__(self, obj): raise AttributeError, "can't delete attribute" @property def attr(self): return self._attr @attr.setter def attr(self, value): self._attr = value if hasattr(value, "__doc__"): self.__doc__ = value.__doc__ autoradio-2.8.6/autoradio/pydbusdecorator/dbus_interface.py0000664000175000017500000003202313001105756024001 0ustar pat1pat100000000000000''' Created on Nov 5, 2011 @author: hugosenari ''' from dbus_decorator import DbusDecorator from undefined_param import UNDEFINED_PARAM from functools import wraps class DBUS_INJECTED_ATTRS(object): #defalt var names in object dbus_interface_info_at = 'dbus_interface_info' #default keywords params for decorated class constructor iface_at = 'dbus_iface' path_at = 'dbus_path' uri_at = 'dbus_uri' obj_at = 'dbus_object' session_at = 'dbus_session' last_fn_return_at = 'last_fn_return' on_change_at = 'on_change' prop_iface_at ='dbus_prop_iface' class DBUS_DEFAULT_ATTRS(object): #default values iface = None path = None uri = None obj = None session = None retur = None on_change = None prop_iface = "org.freedesktop.DBus.Properties" dbus_interface_info = None class DbusInterfaceInfo(DbusDecorator): ''' Object with this lib vars ''' def __init__(self, #default values dbus_iface=DBUS_DEFAULT_ATTRS.iface, dbus_path=DBUS_DEFAULT_ATTRS.path, dbus_uri=DBUS_DEFAULT_ATTRS.uri, dbus_object=DBUS_DEFAULT_ATTRS.obj, dbus_session=DBUS_DEFAULT_ATTRS.session, last_fn_return=DBUS_DEFAULT_ATTRS.retur, on_change=DBUS_DEFAULT_ATTRS.on_change, dbus_prop_iface=DBUS_DEFAULT_ATTRS.iface, *args, **kw): ''' Constructor ''' self.dbus_interfaces = {} self.dbus_obj = dbus_object self.dbus_obj_uri = dbus_uri self.dbus_path = dbus_path self.dbus_session = dbus_session self.dbus_iface = dbus_iface self.dbus_prop_iface = dbus_prop_iface self.last_return = last_fn_return self.on_change = on_change class DbusInterface(DbusDecorator): ''' Wraps some class that define dbus interface ''' #constructor of decorator def __init__(self, #default values iface=DBUS_DEFAULT_ATTRS.iface, path=DBUS_DEFAULT_ATTRS.path, uri=DBUS_DEFAULT_ATTRS.uri, obj=DBUS_DEFAULT_ATTRS.obj, session=DBUS_DEFAULT_ATTRS.session, retur=DBUS_DEFAULT_ATTRS.retur, on_change=DBUS_DEFAULT_ATTRS.on_change, prop_iface=DBUS_DEFAULT_ATTRS.prop_iface, dbus_interface_info=DBUS_DEFAULT_ATTRS.dbus_interface_info, #defalt attr name in object dbus_interface_info_at=DBUS_INJECTED_ATTRS.dbus_interface_info_at, *args, **kw): ''' Init this decorator @param iface: str dbus object interface @param path: str dbus object path @param uri: str dbus object uri (org.mpris.MediaPlayer2.banshee) @param obj: dbus.proxies.ProxyObject @param session: dbus dbus.SessionBus.get_session() @param retur: object, last function return @param on_change: callable, reserved key but not in use @param prop_iface: str dbus object default interface for properties @param dbus_interface_info: DbusInterfaceInfo, object with DbusInterface infos @param dbus_interface_info_at: str where store DbusInterface properties in this obj @see: mpris2.mediaplayer2 to see some examples ''' super(DbusInterface, self).__init__(*args, **kw) self._dbus_default_attrs = { 'dbus_iface' : iface, 'dbus_path' : path, 'dbus_uri' : uri, 'dbus_object' : obj, 'dbus_session' : session, 'last_fn_return' : retur, 'on_change' : on_change, 'dbus_prop_iface' : prop_iface, 'dbus_interface_info' : dbus_interface_info } self._dbus_default_keys = {} self._dbus_interface_info_at = dbus_interface_info_at self._dbus_interface_info = dbus_interface_info self._meth = None #constructor of decorated class def __call__(self, meth, *args, **kw): ''' Called when any decorated class is loaded''' self._meth = meth @wraps(meth) def dbusWrapedInterface(*args, **kw): return self.dbusWrapedInterface(*args, **kw) return dbusWrapedInterface def dbusWrapedInterface(self, *args, **kw): ''' Called when some decoreted class was called Inject attrs from decorator at new object then return object @param *args: list of args to call constructor @param **kw: dict of keywords, can redefine class default parameters @return: instance of decoreted class, with new attributes @see: mpris2.mediaplayer2 to see some examples ''' #shift dbus interface info from keywords kw = self.remove_interface_info_from_kw(**kw) #call decorated class constructor new_obj = self._meth(*args, **kw) if new_obj: self.inject_interface_info(new_obj) elif len(args) > 0: self.inject_interface_info(args[0]) #inject dbus interface info return new_obj def remove_interface_info_from_kw(self, **kw): ''' Remove dbus interface info from keyords and set it in _dbus_interface_info ''' constructor_keys = {} #dict to create new DbusInterfaceInfo dbus_info_at = DBUS_INJECTED_ATTRS.dbus_interface_info_at attr_keys = (DBUS_INJECTED_ATTRS.dbus_interface_info_at, DBUS_INJECTED_ATTRS.iface_at, DBUS_INJECTED_ATTRS.path_at, DBUS_INJECTED_ATTRS.uri_at, DBUS_INJECTED_ATTRS.obj_at, DBUS_INJECTED_ATTRS.session_at, DBUS_INJECTED_ATTRS.last_fn_return_at, DBUS_INJECTED_ATTRS.on_change_at, DBUS_INJECTED_ATTRS.prop_iface_at) keys_for_keys = {DBUS_INJECTED_ATTRS.dbus_interface_info_at : 'dbus_interface_info', DBUS_INJECTED_ATTRS.iface_at : 'dbus_iface', DBUS_INJECTED_ATTRS.path_at : 'dbus_path', DBUS_INJECTED_ATTRS.uri_at : 'dbus_uri', DBUS_INJECTED_ATTRS.obj_at : 'dbus_object', DBUS_INJECTED_ATTRS.session_at : 'dbus_session', DBUS_INJECTED_ATTRS.last_fn_return_at : 'last_fn_return', DBUS_INJECTED_ATTRS.on_change_at : 'on_change', DBUS_INJECTED_ATTRS.prop_iface_at : 'dbus_prop_iface' } self._dbus_default_keys = keys_for_keys # 'from to' for kw and constructor_keys has_same_key_in_kw = False # infor if this constructor changes dbus_interface_info not_user_interface_info = True # inform if this constructor NOT pass dbus_interface_info param #remove dbus interface info keyword from kw for constructor dbus_interface_info = kw.get(dbus_info_at) if dbus_interface_info: del kw[dbus_info_at] for key in attr_keys: default_val = self._dbus_default_attrs.get(key) if dbus_interface_info and key in dbus_interface_info and dbus_interface_info[key] is not default_val: has_same_key_in_kw = True constructor_keys[keys_for_keys[key]] = dbus_interface_info[key] else: constructor_keys[keys_for_keys[key]] = self._dbus_default_attrs.get(key) #kw has dbus_interface_info param? if not_user_interface_info: #kw has info that diff from class definition? #or we not had dbus_interface_info? if has_same_key_in_kw \ or not self._dbus_interface_info: self._dbus_interface_info = DbusInterfaceInfo(**constructor_keys) return kw def inject_interface_info(self, new_obj): #set dbus_interface_info as attr in new_obj setattr(new_obj, DBUS_INJECTED_ATTRS.dbus_interface_info_at, self._dbus_interface_info) #core info getter @staticmethod def attr_name_at(at, attr_id=DBUS_INJECTED_ATTRS.dbus_interface_info_at, val=UNDEFINED_PARAM): ''' gets/sets objs attributes @param at: object where get/set @param attr_id: str internal name of attribute @param val: object, new value @return: attribute value or val ''' #return None if at, attr_id if not (at and attr_id and #And if at has attr attr_id hasattr(at, attr_id)): return None if UNDEFINED_PARAM == val: setattr(at, attr_id, val) return getattr(at, attr_id) @staticmethod def get_dbus_interface_info(at): ''' Use to get dbus_interface_info in object @param at: object where get dbus_interface_info @return session object ''' return DbusInterface.attr_name_at(at, DBUS_INJECTED_ATTRS.dbus_interface_info_at) #simple info getters @staticmethod def get_path(at): ''' return dbus object path attribute @param at: object where get this path @return: str object dbus path ''' dbus_interface_info = DbusInterface.get_dbus_interface_info(at) if dbus_interface_info: return dbus_interface_info.dbus_path @staticmethod def get_uri(at): ''' return dbus object uri attribute @param at: object where get this uri @return: str object dbus uri ''' dbus_interface_info = DbusInterface.get_dbus_interface_info(at) if dbus_interface_info: return dbus_interface_info.dbus_obj_uri @staticmethod def get_bus_iface(at): ''' return dbus_iface str of 'at' @param at: object where get this dbus object @return: str of dbus interface ''' dbus_interface_info = DbusInterface.get_dbus_interface_info(at) if dbus_interface_info: return dbus_interface_info.dbus_iface @staticmethod def get_bus_prop_iface(at): ''' return bus_prop_iface str of 'at' @param at: object where get this dbus object @return: str of dbus interface for properties ''' dbus_interface_info = DbusInterface.get_dbus_interface_info(at) if dbus_interface_info: return dbus_interface_info.dbus_prop_iface \ or DbusInterface.get_bus_iface(at) #complex info getter @staticmethod def get_session(at): ''' Use to get dbus_session in object @param at: object where get session previously criated @return session object ''' dbus_interface_info = DbusInterface.get_dbus_interface_info(at) if dbus_interface_info: dbus_session = dbus_interface_info.dbus_session if not dbus_session: dbus_session = DbusInterface.dbus_lib.SessionBus.get_session() dbus_interface_info.dbus_session = dbus_session return dbus_session @staticmethod def get_bus_obj(at): ''' return dbus object @param at: object where get this dbus object @return: dbus proxy object ''' dbus_interface_info = DbusInterface.get_dbus_interface_info(at) if dbus_interface_info: obj = dbus_interface_info.dbus_obj if not obj: uri = DbusInterface.get_uri(at) if uri: path = DbusInterface.get_path(at) if path: session = DbusInterface.get_session(at) obj = session.get_object(uri, path) dbus_interface_info.dbus_obj = obj return obj @staticmethod def get_bus_properties(at, iface=None): ''' return bus propeterty proxy for 'at' @param at: object where get this dbus object @param iface: str for property dbus interfacece (dbus properties by default) @return: dbus proxy object of iface ''' return DbusInterface.iface(at, iface) #utilities @staticmethod def iface(at, dbus_str_iface=None, dbus_obj=None): ''' Get iface from dict or create one and append it''' dbus_interface_info = DbusInterface.get_dbus_interface_info(at) if dbus_interface_info: dbus_str_iface = dbus_str_iface \ or dbus_interface_info.dbus_prop_iface result = dbus_interface_info.dbus_interfaces.get(dbus_str_iface) if not result: result = DbusInterface.dbus_lib.Interface( dbus_obj or DbusInterface.get_bus_obj(at), dbus_str_iface) dbus_interface_info.dbus_interfaces[dbus_str_iface] = result return result @staticmethod def store_result(at, val=UNDEFINED_PARAM): ''' use to set last call result @param at: object where set @param val: object to set @return: val ''' dbus_interface_info = DbusInterface.get_dbus_interface_info(at) if dbus_interface_info: if val is UNDEFINED_PARAM: dbus_interface_info.last_return = val else: val = dbus_interface_info.last_return return val autoradio-2.8.6/autoradio/managepytone.py0000664000175000017500000000646613001105756020316 0ustar pat1pat100000000000000import os,sys import events, network, requests, version, helper from datetime import * from threading import * def ar_emitted(self): self.emission_done=datetime.now() self.save() class ScheduleProgram: def __init__ (self,function,operation,media,scheduledatetime,programma): "init schedule" self.function=function self.operation=operation self.media=media self.scheduledatetime=scheduledatetime self.programma=programma scheduledatetime print "differenza ",datetime.now(),self.scheduledatetime delta=( self.scheduledatetime - datetime.now()) print delta #self.deltasec=max(secondi(delta),1) self.deltasec=secondi(delta) self.deltasec self.timer = Timer(self.deltasec, self.function, [self.operation,self.media,self.programma]) def start (self): "start of programmed schedule" self.timer.start() def ManagePytone (operation,media,programma): "Manage pytone to do operation on media" unixsocketfile = os.path.expanduser("~/.pytone/pytonectl") networklocation = unixsocketfile print networklocation try: channel = network.clientchannel(networklocation) except Exception, e: print "Error: cannot connect to PyTone server: %s" % e sys.exit(2) channel.start() root,ext=os.path.splitext(media) if (ext == ".m3u"): medias=[] f = open(media, "r") for line in f.readlines(): medias.append(line[:-1]) f.close() else: medias=(media,) for mediasplit in medias: print "invio ->",mediasplit if operation == "queueMedia": song = channel.request(\ requests.autoregisterer_queryregistersong("main", mediasplit)) channel.notify(events.playlistaddsongtop(song)) channel.quit() print "scrivo in django" print programma ar_emitted(programma) print "scritto in django" def secondi(delta): secondi=delta.seconds # 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 "faccio finta di salvarlo" def main(): programma=dummy_programma() function=ManagePytone operation="queueMedia" # media = "/home/pat1/django/autoradio/media/spots/spot-bibbiano.ogg" media="/home/pat1/django/autoradio/media/playlist/pomeridiana_ore_14_00.m3u" #media = raw_input("dammi il media? ") scheduledatetime=datetime.now()+timedelta(seconds=15) schedule=ScheduleProgram(function,operation,media,scheduledatetime,programma) schedule.start() scheduledatetime=datetime.now()+timedelta(seconds=100) media = "/home/pat1/django/autoradio/media/spots/spot-nocino.ogg" schedule=ScheduleProgram(function,operation,media,scheduledatetime,programma) schedule.start() scheduledatetime=datetime.now()+timedelta(seconds=200) media = "/home/pat1/django/autoradio/media/spots/spot-panedellamoni.ogg" schedule=ScheduleProgram(function,operation,media,scheduledatetime,programma) schedule.start() if __name__ == '__main__': main() # (this code was run as script) autoradio-2.8.6/autoradio/manageaudacious.py0000664000175000017500000001641013001105756020743 0ustar pat1pat100000000000000#!/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-2.8.6/autoradio/autoaudacious.py0000664000175000017500000001322113001105756020460 0ustar pat1pat100000000000000#!/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-2.8.6/autoradio/autoepris.py0000664000175000017500000001314713001105756017634 0ustar pat1pat100000000000000#!/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-2.8.6/autoradio/mpris2/0000775000175000017500000000000013003471473016461 5ustar pat1pat100000000000000autoradio-2.8.6/autoradio/mpris2/types.py0000664000175000017500000000130313001105756020170 0ustar pat1pat100000000000000''' 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-2.8.6/autoradio/mpris2/uri.py0000664000175000017500000000045713001105756017634 0ustar pat1pat100000000000000class 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-2.8.6/autoradio/mpris2/player.py0000664000175000017500000004127013001105756020327 0ustar pat1pat100000000000000''' 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-2.8.6/autoradio/mpris2/some_players.py0000664000175000017500000000310213001105756021525 0ustar pat1pat100000000000000class 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-2.8.6/autoradio/mpris2/__init__.py0000664000175000017500000000730413001105756020572 0ustar pat1pat100000000000000#!/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-2.8.6/autoradio/mpris2/playlists.py0000664000175000017500000001141613001105756021056 0ustar pat1pat100000000000000''' 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-2.8.6/autoradio/mpris2/playback_rate.py0000664000175000017500000000077313001105756021637 0ustar pat1pat100000000000000class 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-2.8.6/autoradio/mpris2/playlist_id.py0000664000175000017500000000052113001105756021342 0ustar pat1pat100000000000000''' 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-2.8.6/autoradio/mpris2/playlist.py0000664000175000017500000000337313001105756020676 0ustar pat1pat100000000000000from 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-2.8.6/autoradio/mpris2/metada_map.py0000664000175000017500000000443013001105756021120 0ustar pat1pat100000000000000class 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-2.8.6/autoradio/mpris2/playlist_ordering.py0000664000175000017500000000231613001105756022563 0ustar pat1pat100000000000000''' 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-2.8.6/autoradio/mpris2/tracklist.py0000664000175000017500000002173213001105756021034 0ustar pat1pat100000000000000""" 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): #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-2.8.6/autoradio/mpris2/loop_status.py0000664000175000017500000000175613001105756021414 0ustar pat1pat100000000000000NONE = '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-2.8.6/autoradio/mpris2/time_in_us.py0000664000175000017500000000064313001105756021165 0ustar pat1pat100000000000000''' 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-2.8.6/autoradio/mpris2/mediaplayer2.py0000664000175000017500000001524713001105756021416 0ustar pat1pat100000000000000""" 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-2.8.6/autoradio/mpris2/utils.py0000664000175000017500000000460513001105756020174 0ustar pat1pat100000000000000''' 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-2.8.6/autoradio/mpris2/interfaces.py0000664000175000017500000000204413001105756021152 0ustar pat1pat100000000000000# -*- 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-2.8.6/autoradio/mpris2/volume.py0000664000175000017500000000122413001105756020335 0ustar pat1pat100000000000000''' 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-2.8.6/autoradio/mpris2/playback_status.py0000664000175000017500000000125513001105756022223 0ustar pat1pat100000000000000PLAYING = '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-2.8.6/autoradio/manageamarok.py0000664000175000017500000001410713001105756020241 0ustar pat1pat100000000000000#!/usr/bin/env python # GPL. (C) 2007 Paolo Patruno. import logging from qt import * import dcopext from kdecore import * from datetime import * from threading import * def ar_emitted(self): self.emission_done=datetime.now() self.save() def KdeInit(): "inizializzo kde" aboutdata = KAboutData("AutoAmarok","Autoamarok","1.0", "Amarok radio station", KAboutData.License_GPL, "Copyright (C) 2007 Paolo Patruno") KCmdLineArgs.init(aboutdata) return KApplication () class Kde: def __init__ (self,kapp): "init of kde application" self.dcopclient = kapp.dcopClient() def connect(self,application): "connetto with kde application" self.dcopapplication = dcopext.DCOPApp(application, self.dcopclient) class ScheduleProgram: def __init__ (self,kapp,function,operation,media,scheduledatetime,programma): "init schedule" self.kapp=kapp self.function=function self.operation=operation self.media=media self.scheduledatetime=scheduledatetime self.programma=programma scheduledatetime #print "differenza ",datetime.now(),self.scheduledatetime delta=( self.scheduledatetime - datetime.now()) #print delta #self.deltasec=max(secondi(delta),1) self.deltasec=secondi(delta) self.deltasec self.timer = Timer(self.deltasec, self.function, [self.kapp,self.operation,self.media,self.programma]) def start (self): "start of programmed schedule" #self.function (self.kde,self.media) self.timer.start() def ManageAmarok (kapp,operation,media,programma): "Manage amarok to do operation on media" kde=Kde(kapp) kde.connect("amarok") logging.info( "kde operation: %s",operation) if ( operation == "queueMedia"): # ok,result = kde.dcopapplication.playlist.adjustDynamicPrevious() # print "kde.dcopapplication.playlist.adjustDynamicPrevious" # print 'status is:',ok,result ok,result = kde.dcopapplication.playlist.queueMedia(KURL(media)) logging.info( "kde.dcopapplication.playlist.queueMedia %s",media) test_status(ok,result) ok,result = kde.dcopapplication.player.isPlaying() logging.info( "kde.dcopapplication.player.isPlaying") test_status(ok,result) if ( not result): ok,result = kde.dcopapplication.player.play() logging.info ("kde.dcopapplication.player.play") test_status(ok,result) elif (operation == "loadPlaylist"): ok,result = kde.dcopapplication.playlistbrowser.loadPlaylist(QString(media)) logging.info( "kde.dcopapplication.playlistbrowser.loadPlaylist %s",media) test_status(ok,result) ok,result = kde.dcopapplication.playlist.repopulate() logging.info( "kde.dcopapplication.playlistbrowser.repopulate") test_status(ok,result) ok,result = kde.dcopapplication.player.isPlaying() logging.info( "kde.dcopapplication.player.isPlaying") test_status(ok,result) if ( not result): ok,result = kde.dcopapplication.player.play() logging.info( "kde.dcopapplication.player.play") test_status(ok,result) if (ok): logging.info( "scrivo in django: %s",programma) ar_emitted(programma) logging.info( "scritto in django: %s",programma) def secondi(delta): secondi=delta.seconds # 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): pass #print "faccio finta di salvarlo" def amarok_watchdog(kapp): try: kde=Kde(kapp) kde.connect("amarok") ok,result = kde.dcopapplication.playlist.getTotalTrackCount() logging.info( "kde.dcopapplication.playlist.getTotalTrackCount") except: ok=False result="error on kde.dcopapplication.playlist.getTotalTrackCount()" test_status(ok,result) if (result > 40 or result < 5 ): logging.error("Ho trovato troppa (poca) roba in playlist ! ci sono %s tracce",result) ok,result = kde.dcopapplication.playlist.saveM3u(QString("/tmp/autoradio.m3u"), False) logging.info( "kde.dcopapplication.playlist.saveM3u") test_status(ok,result) ok,result = kde.dcopapplication.playlist.repopulate() logging.info( "kde.dcopapplication.playlist.repopulate") test_status(ok,result) return ok def save_status(kapp): kde=Kde(kapp) kde.connect("amarok") ok,result = kde.dcopapplication.playlist.saveCurrentPlaylist() logging.info ( "kde.dcopapplication.playlist.saveCurrentPlaylist") test_status(ok,result) return ok def test_status( ok,result): # print ok,result if (ok == True): logging.info( "status is: %s result: %s",ok,str(result)) else: logging.error( "status is: %s result: %s",ok,str(result)) def main(): kapp=KdeInit() programma=dummy_programma() function=ManageAmarok operation="queueMedia" media = "/home/pat1/Musica/mp3/goran_bregovic/ederlezi/talijanska.mp3" #media = raw_input("dammi il media? ") scheduledatetime=datetime.now()+timedelta(seconds=15) schedule=ScheduleProgram(kapp,function,operation,media,scheduledatetime,programma) schedule.start() scheduledatetime=datetime.now()+timedelta(seconds=30) media = "/home/pat1/Musica/mp3/goran_bregovic/ederlezi/underground_tango.mp3" schedule=ScheduleProgram(kapp,function,operation,media,scheduledatetime,programma) schedule.start() scheduledatetime=datetime.now()+timedelta(seconds=45) media = "/home/pat1/Musica/mp3/goran_bregovic/ederlezi/lullabye.mp3" schedule=ScheduleProgram(kapp,function,operation,media,scheduledatetime,programma) schedule.start() if __name__ == '__main__': main() # (this code was run as script) autoradio-2.8.6/autoradiodbusd0000775000175000017500000000355713001105756016223 0ustar pat1pat100000000000000#!/usr/bin/python # GPL. (C) 2007-2009 Paolo Patruno. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # from autoradio import daemon import autoradio.settings dbusd = daemon.Daemon( stdin="/dev/null", stdout=autoradio.settings.logfiledbus, stderr=autoradio.settings.errfiledbus, pidfile=autoradio.settings.lockfiledbus, user=autoradio.settings.userdbus, group=autoradio.settings.groupdbus ) def main(self): import subprocess busaddress = autoradio.settings.busaddressplayer if busaddress is not None: self.procs=[subprocess.Popen(["dbus-daemon", "--config-file="+autoradio.settings.conffiledbus, '--address='+busaddress, "--nofork"],cwd=self.cwd)] else: print "busaddress is not defined in config file: dbus-daemon not started" if __name__ == '__main__': import sys, os dbusd.cwd=os.getcwd() if dbusd.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(dbusd) # (this code was run as script) for proc in dbusd.procs: proc.wait() sys.exit(0)