autoradio-3.4/0000775000175000017500000000000013615624377013123 5ustar pat1pat100000000000000autoradio-3.4/COPYING0000664000175000017500000004311013553022177014144 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-3.4/MANIFEST.in0000664000175000017500000000064413553022177014654 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 static prune amarok prune media prune debian prune autoradio.egg-info global-exclude *~ autoradio-3.4/NEWS0000664000175000017500000000702713607550263013621 0ustar pat1pat100000000000000NEWS ---- * Wed Jan 15 2020 Paolo Patruno 3.3 - added shebang for python3 ; release 3.3 (p.patruno@iperbole.bologna.it) - release 3.2 for Debian (p.patruno@iperbole.bologna.it) - migrate to django 2.2 and python3 bugs (p.patruno@iperbole.bologna.it) - bug on debian install (default locale) and python3 refinements (root@localhost.localdomain) - ready for release 3.0 (p.patruno@iperbole.bologna.it) - ported player to python 3 (p.patruno@iperbole.bologna.it) - new migration for python3 (p.patruno@iperbole.bologna.it) - porting to python3 with futurize (p.patruno@iperbole.bologna.it) - sure you specify the proper version support in your setup.py file (p.patruno@iperbole.bologna.it) - new stable release for Debian (ppatruno@arpa.emr.it) * Wed Feb 01 2017 Paolo Patruno 2.8.9-1 - * Wed Feb 01 2017 Paolo Patruno 2.8.8-1 - standard spec file (ppatruno@arpa.emr.it) - bug in spec (ppatruno@arpa.emr.it) - removed SOURCE1 fron spec file (ppatruno@arpa.emr.it) - better monit example (ppatruno@arpa.emr.it) - new package built with tito (ppatruno@arpa.emr.it) - lost autoradio-tmpfiles.conf (ppatruno@arpa.emr.it) 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-3.4/PKG-INFO0000664000175000017500000000257213615624377014226 0ustar pat1pat100000000000000Metadata-Version: 1.1 Name: autoradio Version: 3.4 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 Classifier: Programming Language :: Python :: 3 Classifier: License :: OSI Approved :: GNU General Public License v2 (GPLv2) Classifier: Operating System :: OS Independent Requires: mutagen Requires: django Requires: reportlab Requires: configobj autoradio-3.4/README0000664000175000017500000003242213612355055013775 0ustar pat1pat100000000000000= AutoRadio version 3.3 = https://github.com/pat1/autoradio 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 gstreamer * 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 >= 3.7 mutagen muatgen version >= 1.17 http://code.google.com/p/mutagen/ django http://www.djangoproject.com/ Suggested Django >= 2.2 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 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. For Debian/Ubuntu you can find autoradio in standard distribution. For Fedora use copr repo at https://copr.fedorainfracloud.org/coprs/pat1/autoradio/ 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-3.4/TODO0000664000175000017500000004023113553022177013602 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-3.4/autoplayerd0000775000175000017500000000570513607404332015375 0ustar pat1pat100000000000000#!/usr/bin/env python3 # -*- 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-3.4/autoplayergui0000775000175000017500000002476513607611646015755 0ustar pat1pat100000000000000#!/usr/bin/env python3 # -*- coding: utf-8 -*- # GPL. (C) 2013 Paolo Patruno. from __future__ import division from __future__ import print_function from future import standard_library standard_library.install_aliases() from builtins import range from builtins import object from past.utils import old_div 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 try: from gi import pygtkcompat except ImportError: pygtkcompat = None if pygtkcompat is not None: pygtkcompat.enable() pygtkcompat.enable_gtk(version='3.0') import gtk import os,stat,time,urllib.parse,urllib.request,urllib.parse,urllib.error,optparse,sys import gobject import dbus import autoradio.settings import urllib.parse import traceback 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): playbackstatus = args[1].get("PlaybackStatus",None) position = args[1].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 = old_div(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 try: 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 except dbus.exceptions.DBusException: 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_inverted(False) 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) while gtk.events_pending(): gtk.main_iteration_do(True) 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=urllib.parse.urlsplit(multimedia_file) uri=urllib.parse.urljoin("file://",urllib.parse.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(traceback.format_exc()) print("Error getting player playlist") gtk.main_quit() return listmodel def id(self, column, cell, model, iter, data=None): 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, data=None): 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, data=None): track=self.tl.GetTracksMetadata((model.get_value(iter, 0),)) dir=os.path.dirname(urllib.parse.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, data=None): track=self.tl.GetTracksMetadata((model.get_value(iter, 0),)) file=os.path.basename(urllib.parse.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-3.4/autoradio/0000775000175000017500000000000013615624377015112 5ustar pat1pat100000000000000autoradio-3.4/autoradio/__init__.py0000664000175000017500000000002013615623367017211 0ustar pat1pat100000000000000_version_="3.4" autoradio-3.4/autoradio/audaciousweb.py0000664000175000017500000002002413607403370020122 0ustar pat1pat100000000000000#!/usr/bin/env python # coding=utf-8 """ Show audacious playlist on a simple web server. """ from __future__ import print_function from builtins import str from builtins import range from builtins import object 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(object): # 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 range(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-3.4/autoradio/autoaudacious.py0000664000175000017500000001331613607403370020323 0ustar pat1pat100000000000000#!/usr/bin/env python # GPL. (C) 2007-2009 Paolo Patruno. from builtins import range from builtins import object import dbus import time import datetime import os # ------- dbus interface --------- import dbus class audacious(object): 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 range(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 range(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-3.4/autoradio/autoepris.py0000664000175000017500000001331513607403370017467 0ustar pat1pat100000000000000#!/usr/bin/env python # GPL. (C) 2007-2009 Paolo Patruno. from __future__ import print_function from builtins import range from builtins import object import dbus import time import datetime import os # ------- dbus epris player interface --------- import dbus class mediaplayer(object): 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 range(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 range(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-3.4/autoradio/autompris.py0000664000175000017500000002324113607403370017476 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 # ---------------------------------------- from builtins import str from builtins import range from builtins import object import dbus import time import datetime import os import logging import dbus class mediaplayer(object): 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 range(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 range(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 "URI" in metadata and "location" not in metadata: 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 "URI" in metadata and "location" not in metadata: 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-3.4/autoradio/autompris2.py0000664000175000017500000003061513607403370017563 0ustar pat1pat100000000000000#!/usr/bin/env python # GPL. (C) 2007-2012 Paolo Patruno. from __future__ import division from __future__ import print_function from builtins import str from builtins import range from builtins import object from past.utils import old_div import dbus import time import datetime import os,sys if sys.version_info[0] == 3: from gi.repository import GObject as gobject else: import gobject from . import settings from dbus.mainloop.glib import DBusGMainLoop from .mpris2.mediaplayer2 import MediaPlayer2 from .mpris2.player import Player from .mpris2.tracklist import TrackList from .mpris2.interfaces import Interfaces from .mpris2.some_players import Some_Players from .mpris2.utils import get_players_uri from .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(object): def __init__(self,player="AutoPlayer",session=0, busaddress=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 range(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 range(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": int(round(old_div(mtimelength,1000.))), "mtimeposition": int(round(old_div(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-3.4/autoradio/autoplayer/0000775000175000017500000000000013615624377017277 5ustar pat1pat100000000000000autoradio-3.4/autoradio/autoplayer/__init__.py0000664000175000017500000000000113553022177021366 0ustar pat1pat100000000000000 autoradio-3.4/autoradio/autoplayer/mpris2client.py0000775000175000017500000000537313607403370022264 0ustar pat1pat100000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # GPL. (C) 2013 Paolo Patruno. #Connect to player from __future__ import print_function 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-3.4/autoradio/autoplayer/player.py0000664000175000017500000010721513607403370021140 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 from __future__ import division from __future__ import print_function from __future__ import absolute_import from future import standard_library standard_library.install_aliases() from builtins import str from builtins import object from past.utils import old_div import sys, time, _thread import tempfile,os #import pygst #pygst.require("0.10") #import gobject #import gst import gi gi.require_version('Gst', '1.0') from gi.repository import GObject, Gst from . 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.items(): 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): next(self.player) @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"): try: myattr= getattr(self.player.playlist.get(id,None),attr,None) except: #very very strange to go here but it happen myattr=None if myattr is not None: if key == "mpris:length": myattr=int(round(old_div(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(object): 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 next(self) 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 next(self) # 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") next(self.playlist) 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 =old_div(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(0) #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(list(self.playlist.keys())) > 0: self.playlist.set_current(list(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") self.recoverplaymode = "Playing" 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.") #else: # print self.player.get_state(timeout=gst.CLOCK_TIME_NONE) def pause(self): logging.info( "pause") self.recoverplaymode = "Paused" 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.") #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.recoverplaymode = "Stopped" #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.") #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(old_div(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: fd,tmpfile=tempfile.mkstemp(dir=os.path.dirname(os.path.abspath(path))) self.playlist.write(tmpfile) os.close(fd) #see at https://www.logilab.org/blogentry/17873 #if os.path.exists(path): # os.unlink(path) os.rename(tmpfile, path) except: logging.error( "error saving playlist") raise finally: if os.path.exists(tmpfile): os.unlink(tmpfile) 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(old_div(self.playlist.position,1000.))))) self.setposition(self.playlist.current,int(round(old_div(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-3.4/autoradio/autoplayer/player_gstreamer0.py0000664000175000017500000007654313607403370023302 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) from __future__ import division from __future__ import print_function from __future__ import absolute_import from future import standard_library standard_library.install_aliases() from builtins import str from builtins import object from past.utils import old_div import sys, time, _thread if sys.version_info[0] == 3: from gi.repository import GObject as gobject else: import gobject import pygst pygst.require("0.10") import gst from . 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.items(): 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): next(self.player) @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=int(round(old_div(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(object): 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 next(self) 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 next(self) # 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") next(self.playlist) 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 =old_div(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(0) #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(list(self.playlist.keys())) > 0: self.playlist.set_current(list(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(old_div(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(old_div(self.playlist.position,1000.))))) self.setposition(self.playlist.current,int(round(old_div(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-3.4/autoradio/autoplayer/playlist.py0000664000175000017500000004205313607403370021503 0ustar pat1pat100000000000000#!/usr/bin/python # -*- coding: utf-8 -*- # GPL. (C) 2013 Paolo Patruno. from __future__ import division from __future__ import print_function from future import standard_library standard_library.install_aliases() from builtins import str from past.utils import old_div import logging import collections import mutagen import sys from xml.sax import make_parser, handler, SAXParseException from xml.dom.minidom import Document import urllib.request, urllib.parse, urllib.error, urllib.parse 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=urllib.parse.urlsplit(s.content) if (url.scheme == "http"): s.track['location']=url.geturl() else: if sys.version_info[0] == 3: s.track['location']=urllib.parse.urljoin(u"file://",urllib.parse.unquote(url.path)) else: s.track['location']=urllib.parse.urljoin(u"file://",urllib.parse.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(list(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(urllib.parse.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=urllib.parse.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=urllib.parse.urljoin(u"file://",urllib.parse.unquote(url.path)) track=Track._make(list(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(list(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]: if sys.version_info[0] == 3: f.write( '\t\t%s\n' \ % doc.createTextNode(track['title']).toxml() ) else: f.write( '\t\t%s\n' \ % doc.createTextNode(track['title'].encode("utf-8")).toxml() ) if track.get('artist') not in ['', None]: if sys.version_info[0] == 3: f.write('\t\t%s\n' \ % doc.createTextNode(track['artist']).toxml() ) else: f.write('\t\t%s\n' \ % doc.createTextNode(track['artist'].encode("utf-8")).toxml() ) if track.get('album') not in ['', None]: if sys.version_info[0] == 3: f.write( '\t\t%s\n' \ % doc.createTextNode(track['album']).toxml() ) else: 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 [str, 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 = old_div(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=urllib.parse.urlsplit(location) if (url.scheme == "http"): location=url.geturl() else: #here problem when file name come from gtk or command line try: location=urllib.parse.urljoin(u"file://",urllib.parse.quote(url.path)) except: if sys.version_info[0] == 3: raise else: location=urllib.parse.urljoin("file://",urllib.parse.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, str]: t = "str" v = str(v) elif t == bool: t = "bool" v = '1' if v else '0' elif t in [int, int]: t = "int" if sys.version_info[0] == 3: v = str(v) else: 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 = list(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 list(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=list(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=list(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=list(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.items(): 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=list(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-3.4/autoradio/autoradio_config.py0000664000175000017500000000551613607403370020774 0ustar pat1pat100000000000000#!/usr/bin/python # GPL. (C) 2007-2009 Paolo Patruno. from __future__ import print_function 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-3.4/autoradio/autoradio_core.py0000664000175000017500000006003213607403370020451 0ustar pat1pat100000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # GPL. (C) 2007-2009 Paolo Patruno. from __future__ import division from __future__ import print_function from __future__ import absolute_import from builtins import zip from builtins import str from builtins import range from past.utils import old_div from builtins import object 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(object): """ 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.mydjobj=djobj self.scheduledatetime=scheduledatetime self.mymedia=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.mytitle=title def __eq__(self, other): if (self.scheduledatetime is None and other.scheduledatetime is None or self.scheduledatetime == other.scheduledatetime) : return True def __ne__(self, other): return not self.__eq__(self, other) def __lt__(self, other): return self.scheduledatetime < other.scheduledatetime def __le__(self, other): return self.scheduledatetime <= other.scheduledatetime def __gt__(self, other): return self.scheduledatetime > other.scheduledatetime def __ge__(self, other): return self.scheduledatetime >= other.scheduledatetime def future (self,now=None): return self.scheduledatetime > now def filter (self): if self.scheduledatetime is None : return False return True def __repr__ (self): return self.type+" "+self.mymedia # def __iter__(self,now=None): # self.index=0 # self.now=now # if self.now is None : self.now=datetime.now() # return self # # def __next__(self): # self.index+=1 # if (self.index==1): # return self.mydjobj # if (self.index==2): # return self.mytitle # if (self.index==3): # return self.scheduledatetime # if (self.index==4): # return self.mymedia # if (self.index==5): # return str((datetime(2000,1,1)+timedelta(seconds=int(self.length))).time()) # if (self.index==6): # return self.type # if (self.index==7): # return self.emission_done # if (self.index==8): # return self.future(self.now) # raise StopIteration def get_djobj(self): return self.mydjobj def get_title(self): return self.mytitle def get_datet(self): return self.scheduledatetime def get_media(self): return self.mymedia def get_length_s(self): return str((datetime(2000,1,1)+timedelta(seconds=int(self.length))).time()) def get_tipo(self): return self.type def get_datetdone(self): return self.emission_done def get_isfuture(self): return self.future(datetime.now()) djobj=property(get_djobj) title=property(get_title) datet=property(get_datet) media=property(get_media) length_s=property(get_length_s) tipo=property(get_tipo) datetdone=property(get_datetdone) isfuture=property(get_isfuture) 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=old_div(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=old_div(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): reverse_enumerate = lambda l: zip(range(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(object): 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 ''' self.index=0 return self def __next__(self): self.index+=1 if (self.index==1): return self.title if (self.index==2): return self.datetime_start if (self.index==3): return self.datetime_end if (self.index==4): return self.code if (self.index==5): return self.type if (self.index==6): return self.subtype if (self.index==7): return self.production if (self.index==8): return self.note raise StopIteration class dates(object): 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 #return 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+(old_div((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-3.4/autoradio/autoxmms.py0000664000175000017500000001023313607403370017325 0ustar pat1pat100000000000000#!/usr/bin/env python # GPL. (C) 2007-2009 Paolo Patruno. from builtins import range 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 range(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 range(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-3.4/autoradio/daemon.py0000664000175000017500000002421413607403370016717 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. ## """ 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) """ from builtins import str from past.builtins import basestring from builtins import object 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, "ab+") se = open(self.options.stderr, "ab+", 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 as 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 as 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, "w").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, "r") except IOError as 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-3.4/autoradio/dbusdecorator/0000775000175000017500000000000013615624377017752 5ustar pat1pat100000000000000autoradio-3.4/autoradio/dbusdecorator/__init__.py0000664000175000017500000000050313607403370022046 0ustar pat1pat100000000000000''' This is not part of specification Helper class to make it work as python lib ''' from .attribute import DbusAttr from .interface import DbusInterface from .method import DbusMethod from .signal import DbusSignal from .utils import get_mainloop, get_uri, implements, \ list_all_interface, list_interfaces, list_paths autoradio-3.4/autoradio/dbusdecorator/__main__.py0000664000175000017500000000034213607403370022030 0ustar pat1pat100000000000000from . import DbusAttr from . import DbusInterface from . import DbusMethod from . import DbusSignal if __name__ == '__main__': print(DbusAttr()) print(DbusInterface()) print(DbusMethod()) print(DbusSignal()) autoradio-3.4/autoradio/dbusdecorator/attribute.py0000664000175000017500000000310413607403370022312 0ustar pat1pat100000000000000''' This is not part of specification Helper class to make it work as python lib ''' from .base import Decorator, ATTR_KEY class DbusAttr(Decorator): ''' https://docs.python.org/2/howto/descriptor.html#properties ''' def __init__(self, meth=None, produces=lambda resp: resp): self.attr = meth self.produces = produces self._update_me(meth) def __call__(self, meth): self.attr = meth self._update_me(meth) return self def __get__(self, obj, objtype=None): #static call if not obj: return self _dbus = getattr(obj, ATTR_KEY) props = _dbus.properties iface = _dbus.iface result = props.Get(iface, self.attr.__name__) produces = self.produces return produces(result) def __set__(self, obj, value): if obj: _dbus = getattr(obj, ATTR_KEY) props = _dbus.properties iface = _dbus.iface props.Set(iface, self.attr.__name__, value) else: #static call self.attr = value def __delete__(self, obj): raise AttributeError('can not delete attribute') if __name__ == '__main__': # examples from .interface import DbusInterface @DbusInterface('org.mpris.MediaPlayer2', '/org/mpris/MediaPlayer2') class Example(object): @DbusAttr def Identity(self): pass d = Example( dbus_interface_info={ 'dbus_uri': 'org.mpris.MediaPlayer2.vlc'}) assert d.Identity == 'VLC media player' autoradio-3.4/autoradio/dbusdecorator/base.py0000664000175000017500000000100213607403370021214 0ustar pat1pat100000000000000''' This is not part of specification Helper class to make it work as python lib ''' I_PROP = 'org.freedesktop.DBus.Properties' ARG_KEY = 'dbus_interface_info' ATTR_KEY = '_dbus_interface_info' class Decorator(object): def _update_me(self, target=None): if hasattr(target, "__doc__"): self.__doc__ = target.__doc__ if hasattr(target, "__name__"): self.__name__ = target.__name__ if hasattr(target, "__bases__"): self.__bases__ = target.__bases__ autoradio-3.4/autoradio/dbusdecorator/interface.py0000664000175000017500000001021413607403370022247 0ustar pat1pat100000000000000''' This is not part of specification Helper class to make it work as python lib ''' import dbus from functools import wraps from .base import Decorator, ARG_KEY, I_PROP, ATTR_KEY class _DbusInfoProperty(object): def __init__(self, iface=None, path=None, uri=None, dbus_object=None, session=None, wrapped=None): self.iface = iface self.path = path self.uri = uri self.object = dbus_object self.session = session self.wrapped = wrapped self.interface = None self.properties = None if not self.object: bus = self.session = self.session or dbus.SessionBus() self.object = bus.get_object(self.uri, self.path) if not self.interface: self.interface = dbus.Interface(self.object, dbus_interface=self.iface) if not self.properties: self.properties = dbus.Interface(self.object, I_PROP) def reconnect(self, session=None): ''' Required if you need update session/proxy object/interfaces ''' session = session or self.session if session == self.session: self.session.close() session = self.session = dbus.SessionBus() self.object = session.get_object(self.uri, self.path) self.interface = dbus.Interface(self.object, dbus_interface=self.iface) self.properties = dbus.Interface(self.object, I_PROP) class DbusInterface(Decorator): def __init__(self, iface=None, path=None, uri=None, dbus_object=None, session=None): self.iface = iface self.path = path self.uri = uri self.object = dbus_object self.session = session self.wrapped = None def __call__(self, meth): ''' Called when any decorated class is loaded''' self.wrapped = meth self._update_me(meth) @wraps(meth) def dbusWrapedInterface(*args, **kw): _args = kw.get(ARG_KEY, {}) info_property = _DbusInfoProperty( iface=_args.get('dbus_iface', self.iface), path=_args.get('dbus_path', self.path), uri=_args.get('dbus_uri', self.uri), dbus_object =_args.get('dbus_object', self.object), session =_args.get('dbus_session', self.session), wrapped=self.wrapped ) if ARG_KEY in kw: del kw[ARG_KEY] return self.dbusWrapedInterface(info_property, *args, **kw) return dbusWrapedInterface def dbusWrapedInterface(self, info_property, *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 ''' #call decorated class constructor new_obj = self.wrapped(*args, **kw) if new_obj: setattr(new_obj, ATTR_KEY, info_property) elif len(args) > 0: setattr(args[0], ATTR_KEY, info_property) return new_obj if __name__ == '__main__': # examples @DbusInterface('org.freedesktop.DBus', '/') class Example(object): pass d = Example( dbus_interface_info={ 'dbus_uri': 'org.freedesktop.DBus'}) assert d._dbus_interface_info.iface == 'org.freedesktop.DBus' assert d._dbus_interface_info.path == '/' assert d._dbus_interface_info.uri == 'org.freedesktop.DBus' class ExempleToo(object): @DbusInterface('org.freedesktop.DBus', '/') def __init__(self): pass dd = ExempleToo( dbus_interface_info={ 'dbus_uri': 'org.freedesktop.DBus'}) assert dd._dbus_interface_info.iface == 'org.freedesktop.DBus' assert dd._dbus_interface_info.path == '/' assert dd._dbus_interface_info.uri == 'org.freedesktop.DBus' autoradio-3.4/autoradio/dbusdecorator/method.py0000664000175000017500000000637513607403370021604 0ustar pat1pat100000000000000''' This is not part of specification Helper class to make it work as python lib ''' from .base import Decorator, ATTR_KEY def kw_to_dbus(**kw): return kw def args_to_dbus(*args): return args class DbusMethod(Decorator): def __init__(self, meth=None, iface=None, produces=lambda resp: resp, args_to_dbus=args_to_dbus, kw_to_dbus=kw_to_dbus, std_args=(), std_kwds={}): self.meth = meth self.handler = None self.produces = produces self.iface = iface self.args_to_dbus = args_to_dbus self.kw_to_dbus = kw_to_dbus self.std_args = std_args self.std_kwds = std_kwds self.obj = None self._update_me(meth) def __call__(self, meth=None): self.meth = meth self._update_me(meth) return self def __get__(self, obj=None, cls=None): if obj is None: return self self.obj = obj return self._call_dbus def _call_dbus(self, *args, **kwds): _dbus = getattr(self.obj, ATTR_KEY) if self.iface: iface = self.iface else: iface = _dbus.iface bus_obj = _dbus.object bus_meth = bus_obj.get_dbus_method(self.meth.__name__, iface) _args = self.merge_args(args, self.std_args) args = self.convert_args_to_dbus_args(*_args) _kwds = self.std_kwds.copy() _kwds.update(kwds) kwds = self.convert_kw_to_dbus_kw(**_kwds) result = bus_meth(*args, **kwds) return self.produces(result) @classmethod def merge_args(cls, args, std_args): _len = len(std_args) - len(args) return args + std_args[-_len:] if _len > 0 else args @classmethod def merge_kwds(cls, kwds, std_kwds): _kwds = std_kwds.copy() _kwds.update(kwds) return _kwds 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) #iterate over args result = [] for arg in args: i = args.index(arg) 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'): for key, val in kw.items(): make = kw_to_dbus.get(key, lambda v: v) kw[key] = make(val) return kw if __name__ == '__main__': # examples from .interface import DbusInterface @DbusInterface('org.freedesktop.DBus', '/') class Example(object): @DbusMethod def GetId(self): pass @DbusMethod def GetNameOwner(self, name): pass d = Example( dbus_interface_info={ 'dbus_uri': 'org.freedesktop.DBus'}) assert d.GetId() assert d.GetNameOwner('org.freedesktop.DBus') == 'org.freedesktop.DBus' autoradio-3.4/autoradio/dbusdecorator/signal.py0000664000175000017500000000326213607403370021571 0ustar pat1pat100000000000000from .base import Decorator, ATTR_KEY class DbusSignal(Decorator): ''' https://docs.python.org/2/howto/descriptor.html#properties ''' def __init__(self, meth=None, iface=None): self.attr = meth self.handler = None self.iface = iface self._update_me(meth) def __call__(self, meth): self.attr = meth self._update_me(meth) return self def __get__(self, obj, objtype=None): if obj: return self.handler #static call return self def __set__(self, obj, value): if obj: _dbus = getattr(obj, ATTR_KEY) interface = _dbus.interface def handle(*args, **kwds): h = self.handler h and h(*args, **kwds) if not self.handler: interface.connect_to_signal(self.attr.__name__, handle, dbus_interface=self.iface) self.handler = value else: #static call self.attr = value def __delete__(self, obj): self.handler = None if __name__ == '__main__': from .interface import DbusInterface from .utils import get_mainloop mainloop = get_mainloop() print('mainloop', mainloop) @DbusInterface('org.mpris.MediaPlayer2.player', '/org/mpris/MediaPlayer2') class Example(object): @DbusSignal def Seeked(self): pass d = Example( dbus_interface_info={ 'dbus_uri': 'org.mpris.MediaPlayer2.gmusicbrowser'}) def handler(self, *args): print(args) d.Seeked = handler mainloop and mainloop.run() autoradio-3.4/autoradio/dbusdecorator/utils.py0000664000175000017500000001066613607403370021462 0ustar pat1pat100000000000000''' utils functions ''' import dbus, re import xml.etree.ElementTree as ET I_INSPECT = 'org.freedesktop.DBus.Introspectable' def _match_uri(name, pattern='.+'): ''' Filter logic for get_players and get_player_uri @param name: string name to test @param pattern=None: string RegEx to test @return: boolean ''' return re.match(pattern, name) def get_session(): ''' @return: dbus.SessionBus.get_session() ''' return dbus.SessionBus.get_session() def get_uri(pattern='.'): ''' Return string of player bus name @param pattern=None: string RegEx that filter response @return: array string of players bus name ''' for item in get_session().list_names(): if _match_uri(item, pattern): yield item def get_mainloop(): ''' @return: mainloop (with 'run' method) ''' try: from dbus.mainloop.glib import DBusGMainLoop DBusGMainLoop(set_as_default=True) try: import gobject return gobject.MainLoop() except: pass try: import gi.repository.GLib return gi.repository.GLib.MainLoop() except: pass except: pass class PyQtMainLoop(): def __init__(self, app): self.app = app def run(self): if hasattr(self.app, 'exec_'): self.app.exec_() if hasattr(self.app, 'exec'): method = getattr(self.app, 'exec') method(self.app) try: from dbus.mainloop.qt import DBusQtMainLoop DBusQtMainLoop(set_as_default=True) try: from PySide.QtGui import QApplication return PyQtMainLoop(QApplication([])) except: pass try: from PyQt5.QtWidgets import QApplication return PyQtMainLoop(QApplication([])) except: pass try: from PyQt4 import Qt return PyQtMainLoop(Qt.QApplication([])) except: pass except: pass return None def _introspect_uri(bus_name, path, bus): bus = bus or dbus.SessionBus.get_session() path = path.replace('//', '/') or '/' obj = bus.get_object(bus_name, path) xml = obj.Introspect(dbus_interface=I_INSPECT) return ET.fromstring(xml) def list_paths(bus_name, path='/', bus=None): ''' Return generator with all subpaths of uri @param bus_name: string dbus object name @param path='/': string dbus object path @param bus=None: Conn dbus.Conn (commonly SessionBus or SystemBus) @return: generator with all paths that has interfaces ''' nodes = _introspect_uri(bus_name, path, bus) if [iface for iface in nodes.findall('interface')]: yield path.replace('//', '/') for node in nodes.findall('node'): sub_path = path + '/'+ node.attrib['name'] for path_name in list_paths(bus_name, sub_path, bus): yield path_name def list_interfaces(bus_name, path, bus=None): ''' Return generator with all interfaces of path @param bus_name: string dbus object name @param path: string dbus object path @param bus=None: Conn dbus.Conn (commonly SessionBus or SystemBus) @return: generator with all interfaces of path ''' nodes = _introspect_uri(bus_name, path, bus) for interface in nodes.findall('interface'): yield interface.attrib['name'] def list_all_interface(bus_name, path='/', bus=None): ''' Return generator with all interfaces of path and subpaths @param bus_name: string dbus object name @param path: string dbus object path @param bus=None: Conn dbus.Conn (commonly SessionBus or SystemBus) @return: generator with all interfaces of path and subpaths ''' paths = list_paths(bus_name, path, bus) for sub_path in paths: for interface in list_interfaces(bus_name, sub_path, bus): yield sub_path, interface def implements(uri, interface, path, bus=None): ''' ''' for iface in list_interfaces(uri, path, bus): if iface == interface: return True if __name__ == '__main__': uri = None for uri in get_uri(): if uri.count(':') == 0: print(uri) if uri: for interface in list_all_interface(uri, '/'): print('\t', interface) else: print('Nothing running') autoradio-3.4/autoradio/dir2ogg.py0000775000175000017500000007414713607403370017026 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!) ''' from __future__ import division from __future__ import print_function from future import standard_library standard_library.install_aliases() from builtins import str from builtins import range from past.utils import old_div from builtins import object 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): names = [names] if isinstance(patterns, str): 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 list(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 list(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("Warning: %s" % msg, file=sys.stderr) def fatal(msg): '''Fatal error (error + exit)''' print("Error: %s" % msg, file=sys.stderr) 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(object): '''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 as msg: import warnings,traceback; warn('Mutagen failed on %s, no tags available' % self.song) traceback.print_exc(0) print(file=sys.stderr) return if convert: convert = dict([(k.lower(), v.lower()) for k, v in list(convert.items())]) # Fix convert for key, val in list(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, str): # Convert all invalid values to unicode try: new_val.append(str(i)) except UnicodeDecodeError: warn('Ignoring UnicodeDecodeError in key %s' % key) new_val.append(str(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 list(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 list(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" % (old_div(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(object): '''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(list(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 list(title.items()): title[key] = str(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 as 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 as err: warn(err) raise self.MBError discId = disc.getId() try: myfilter = mbws.ReleaseFilter(discId=discId) results = query.getReleases(myfilter) except mbws.WebServiceError as 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 as err: warn(err) raise self.MBError isSingleArtist = release.isSingleArtistRelease() try: # try to get the CDDB ID import DiscID cddb_id = '%08lx' % int(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(object): ''' 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 list(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-3.4/autoradio/doc/0000775000175000017500000000000013615624377015657 5ustar pat1pat100000000000000autoradio-3.4/autoradio/doc/__init__.py0000664000175000017500000000000013553022177017745 0ustar pat1pat100000000000000autoradio-3.4/autoradio/doc/templates/0000775000175000017500000000000013615624377017655 5ustar pat1pat100000000000000autoradio-3.4/autoradio/doc/templates/doc/0000775000175000017500000000000013615624377020422 5ustar pat1pat100000000000000autoradio-3.4/autoradio/doc/templates/doc/doc.html0000664000175000017500000004212413553022177022047 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-3.4/autoradio/doc/templates/doc/index.html0000664000175000017500000000365613553022177022420 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-3.4/autoradio/doc/urls.py0000664000175000017500000000106613553022177017210 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-3.4/autoradio/gest_jingle.py0000664000175000017500000001321613607403370017746 0ustar pat1pat100000000000000#!/usr/bin/env python # This Python file uses the following encoding: utf-8 # GPL. (C) 2007-2009 Paolo Patruno. from __future__ import print_function from __future__ import absolute_import from builtins import object 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(object): 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): try: jingle=next(many_jingles) except StopIteration: return 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-3.4/autoradio/gest_palimpsest.py0000664000175000017500000001346113607403370020661 0ustar pat1pat100000000000000#!/usr/bin/env python # GPL. (C) 2007-2009 Paolo Patruno. from __future__ import print_function from __future__ import absolute_import from builtins import object 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(object): 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-3.4/autoradio/gest_playlist.py0000664000175000017500000001756413607403370020351 0ustar pat1pat100000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # GPL. (C) 2007-2009 Paolo Patruno. from __future__ import print_function from __future__ import absolute_import from builtins import str from builtins import object 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(object): 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=str(calendar.day_name[self.datesched_min.weekday()]) domani=str(calendar.day_name[self.datesched_max.weekday()]) 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-3.4/autoradio/gest_program.py0000664000175000017500000001161413607403370020145 0ustar pat1pat100000000000000#!/usr/bin/env python # GPL. (C) 2007-2009 Paolo Patruno. from __future__ import print_function from __future__ import absolute_import from builtins import object 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(object): def __init__ (self,now,minelab): """init of program application: now : current 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: ar_scheduledatetime+=timedelta(seconds=lengthold) lengthold=ar_length 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-3.4/autoradio/gest_spot.py0000664000175000017500000002254713607403370017472 0ustar pat1pat100000000000000#!/usr/bin/env python # This Python file uses the following encoding: utf-8 # GPL. (C) 2007-2009 Paolo Patruno. from __future__ import print_function from __future__ import absolute_import from builtins import object 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(object): 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,0o644) #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-3.4/autoradio/jingles/0000775000175000017500000000000013615624377016545 5ustar pat1pat100000000000000autoradio-3.4/autoradio/jingles/__init__.py0000664000175000017500000000000013553022177020633 0ustar pat1pat100000000000000autoradio-3.4/autoradio/jingles/admin.py0000664000175000017500000000552013607403370020176 0ustar pat1pat100000000000000from __future__ import absolute_import from builtins import object from .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(object): 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-3.4/autoradio/jingles/migrations/0000775000175000017500000000000013615624377020721 5ustar pat1pat100000000000000autoradio-3.4/autoradio/jingles/migrations/0001_initial.py0000664000175000017500000000641413553022177023360 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-3.4/autoradio/jingles/migrations/0002_auto_20180721_1059.py0000664000175000017500000000134513607403370024336 0ustar pat1pat100000000000000# -*- coding: utf-8 -*- # Generated by Django 1.10.5 on 2018-07-21 10:59 from __future__ import unicode_literals from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ ('jingles', '0001_initial'), ] operations = [ migrations.AlterField( model_name='giorno', name='name', field=models.CharField(choices=[(b'luned\xc3\xac', b'luned\xc3\xac'), (b'marted\xc3\xac', b'marted\xc3\xac'), (b'mercoled\xc3\xac', b'mercoled\xc3\xac'), (b'gioved\xc3\xac', b'gioved\xc3\xac'), (b'venerd\xc3\xac', b'venerd\xc3\xac'), (b'sabato', b'sabato'), (b'domenica', b'domenica')], help_text='weekday name', max_length=20, unique=True), ), ] autoradio-3.4/autoradio/jingles/migrations/__init__.py0000664000175000017500000000000013553022177023007 0ustar pat1pat100000000000000autoradio-3.4/autoradio/jingles/models.py0000664000175000017500000001242713607403370020375 0ustar pat1pat100000000000000from builtins import object from 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 __str__(self): return self.name class Admin(object): 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 __str__(self): return self.sezione+" "+self.active.__str__()+" "+self.emission_freq.isoformat() class Admin(object): 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 __str__(self): return self.jingle class Admin(object): 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-3.4/autoradio/jingles/urls.py0000664000175000017500000000031713553022177020074 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-3.4/autoradio/jingles/views.py0000664000175000017500000000003213553022177020236 0ustar pat1pat100000000000000# Create your views here. autoradio-3.4/autoradio/manageamarok.py0000664000175000017500000001422413607403370020077 0ustar pat1pat100000000000000#!/usr/bin/env python # GPL. (C) 2007 Paolo Patruno. from builtins import str from builtins import object 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(object): 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(object): 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(object): 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-3.4/autoradio/manageaudacious.py0000664000175000017500000001673213607403370020610 0ustar pat1pat100000000000000#!/usr/bin/env python # GPL. (C) 2007 Paolo Patruno. from __future__ import division from __future__ import absolute_import from builtins import str from past.utils import old_div from builtins import object import logging import dbus from . import autoaudacious from datetime import * from threading import * from django.conf import settings import os from . 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): from . 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(object): ''' 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 as e: logging.error(e) return aud.play_ifnot() def secondi(delta): secondi=float(delta.seconds) secondi=secondi+(old_div(delta.microseconds,100000.)) if delta.days < 0 : secondi = secondi + (3600*24*delta.days) return secondi class dummy_programma(object): 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(): from . 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-3.4/autoradio/managempris.py0000664000175000017500000002067013607403370017761 0ustar pat1pat100000000000000#!/usr/bin/env python # GPL. (C) 2007-2012 Paolo Patruno. from __future__ import division from __future__ import absolute_import from builtins import str from past.utils import old_div from builtins import object import logging import dbus from . import autompris from . import autompris2 from datetime import * from threading import * import os from . import autoradio_config import traceback 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): from . 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(object): ''' 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.decode("utf-8"),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 as e: logging.error(e) except dbus.DBusException as e: logging.error(e) except: logging.error("generic error in ManagePlayer") traceback.print_exc() return def secondi(delta): secondi=float(delta.seconds) secondi=secondi+(old_div(delta.microseconds,100000.)) if delta.days < 0 : secondi = secondi + (3600*24*delta.days) return secondi class dummy_programma(object): 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(): from . 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-3.4/autoradio/managepytone.py0000664000175000017500000000662313607403370020147 0ustar pat1pat100000000000000from __future__ import print_function from builtins import object import 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(object): 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 as 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(object): 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-3.4/autoradio/managexmms.py0000664000175000017500000001722713607403370017617 0ustar pat1pat100000000000000#!/usr/bin/env python # GPL. (C) 2007 Paolo Patruno. from __future__ import division from __future__ import absolute_import from builtins import str from past.utils import old_div from builtins import object import logging import xmms,autoxmms from datetime import * from threading import * from django.conf import settings import os from . import autoradio_config #import signal class XmmsError(Exception): pass def shuffle_playlist(infile,shuffle=False,relative_path=False,length=None): from . 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(object): ''' 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 as 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+(old_div(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(object): 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(): from . 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-3.4/autoradio/mime.py0000664000175000017500000000264413553022177016410 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-3.4/autoradio/mkplaylist.py0000775000175000017500000004015713607403370017654 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. """ from __future__ import print_function from past.builtins import cmp from future import standard_library standard_library.install_aliases() from builtins import str from builtins import object import sys import os import os.path import random import logging from itertools import chain import mutagen import urllib.request, urllib.error, urllib.parse __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(object): """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(object): """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(str(entry), file=outfile) 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('#EXTM3U', file=outfile) 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('#EXTINF:%s,%s - %s' % (time, entry['ARTIST'], entry['TITLE']), file=outfile) print(str(entry), file=outfile) 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('[playlist]', file=outfile) print('PlaylistName=Playlist', file=outfile) 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('File%d=%s' % (i, entry), file=outfile) title = entry['TITLE'] or os.path.basename(str(entry)) print('Title%d=%s' % (i, title), file=outfile) print('Length%d=%s' % (i, entry['TIME']), file=outfile) else: break print('NumberOfEntries=%d' % i, file=outfile) print('Version=2', file=outfile) 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=list(WRITERS.keys()), help="format of the output %r (default: %%default)" % list(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-3.4/autoradio/mpris2/0000775000175000017500000000000013615624377016326 5ustar pat1pat100000000000000autoradio-3.4/autoradio/mpris2/__init__.py0000664000175000017500000000745313607403370020435 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 __future__ import print_function from __future__ import absolute_import from .interfaces import Interfaces from .mediaplayer2 import MediaPlayer2 from .player import Player from .playlists import Playlists from .tracklist import TrackList from . import types as types from . import utils as utils if __name__ == '__main__': print(Interfaces) print(MediaPlayer2) print(Player) print(Playlists) print(TrackList) print(types) print(utils) autoradio-3.4/autoradio/mpris2/interfaces.py0000664000175000017500000000210013607403370021001 0ustar pat1pat100000000000000# -*- coding: UTF8 -* """ This is mprisV2.1 documentation http://www.mpris.org/2.1/spec/index.html """ from builtins import object 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-3.4/autoradio/mpris2/loop_status.py0000664000175000017500000000200613607403370021237 0ustar pat1pat100000000000000from __future__ import print_function NONE = '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__() 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 == PLAYLIST) autoradio-3.4/autoradio/mpris2/mediaplayer2.py0000664000175000017500000001527513607403370021255 0ustar pat1pat100000000000000""" This is python mprisV2.1 documentation http://www.mpris.org/2.1/spec/Root_Node.html """ from __future__ import print_function from __future__ import absolute_import from autoradio.dbusdecorator import DbusAttr from autoradio.dbusdecorator import DbusInterface from autoradio.dbusdecorator import DbusMethod from autoradio.dbusdecorator 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-3.4/autoradio/mpris2/metada_map.py0000664000175000017500000000447713607403370020771 0ustar pat1pat100000000000000from __future__ import print_function class 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-3.4/autoradio/mpris2/playback_rate.py0000664000175000017500000000104213607403370021463 0ustar pat1pat100000000000000from __future__ import print_function class 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-3.4/autoradio/mpris2/playback_status.py0000664000175000017500000000121613607403370022056 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): super(Playback_Status, self).__init__() self._status = status @property def status(self): return self._status Playback_Status.PLAYING = PLAYING Playback_Status.PAUSED = PAUSED Playback_Status.STOPPED = STOPPED autoradio-3.4/autoradio/mpris2/player.py0000664000175000017500000004203513607403370020165 0ustar pat1pat100000000000000''' This is python mprisV2.1 documentation http://www.mpris.org/2.1/spec/Player_Node.html ''' from __future__ import print_function from __future__ import absolute_import #insert projet at path # not required if installed #import sys #sys.path = ["/home/pat1/git/autoradio"] + sys.path from autoradio.dbusdecorator import DbusAttr from autoradio.dbusdecorator import DbusInterface from autoradio.dbusdecorator import DbusMethod from autoradio.dbusdecorator import DbusSignal from .interfaces import Interfaces #from types import Time_In_Us, Loop_Status, Playback_Status, \ # Playback_Rate, Metadata_Map, Volume from .time_in_us import Time_In_Us from .loop_status import Loop_Status from .playback_status import Playback_Status from .playback_rate import Playback_Rate from .metada_map import Metadata_Map from .volume import 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 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 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-3.4/autoradio/mpris2/playlist.py0000664000175000017500000000337013607403370020531 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 __bool__(self): return bool(self.Valid) def __bool__(self): return self.__nonzero__()autoradio-3.4/autoradio/mpris2/playlist_id.py0000664000175000017500000000052113553022177021202 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-3.4/autoradio/mpris2/playlist_ordering.py0000664000175000017500000000231613553022177022423 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-3.4/autoradio/mpris2/playlists.py0000664000175000017500000001145113607403370020713 0ustar pat1pat100000000000000''' This is python mprisV2.1 documentation http://www.mpris.org/2.1/spec/Playlists.html ''' from __future__ import print_function from __future__ import absolute_import from autoradio.dbusdecorator import DbusAttr from autoradio.dbusdecorator import DbusInterface from autoradio.dbusdecorator import DbusMethod from autoradio.dbusdecorator 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-3.4/autoradio/mpris2/some_players.py0000664000175000017500000000313613607403370021372 0ustar pat1pat100000000000000from builtins import object class 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-3.4/autoradio/mpris2/time_in_us.py0000664000175000017500000000071213607403370021020 0ustar pat1pat100000000000000''' Created on Nov 5, 2011 @author: hugosenari ''' from __future__ import print_function 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-3.4/autoradio/mpris2/tracklist.py0000664000175000017500000002176113607403370020674 0ustar pat1pat100000000000000""" This is python mprisV2.1 documentation http://www.mpris.org/2.1/spec/TrackList_Node.html """ from __future__ import print_function from __future__ import absolute_import from autoradio.dbusdecorator import DbusAttr from autoradio.dbusdecorator import DbusInterface from autoradio.dbusdecorator import DbusMethod from autoradio.dbusdecorator 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-3.4/autoradio/mpris2/types.py0000664000175000017500000000144713607403370020037 0ustar pat1pat100000000000000''' Created on Nov 8, 2011 @author: hugosenari ''' from __future__ import print_function from __future__ import absolute_import 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(Volume) autoradio-3.4/autoradio/mpris2/uri.py0000664000175000017500000000052613607403370017467 0ustar pat1pat100000000000000from __future__ import print_function class 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-3.4/autoradio/mpris2/utils.py0000664000175000017500000000473013607403370020031 0ustar pat1pat100000000000000''' utils functions not defined in espec Created on Nov 6, 2011 @author: hugosenari ''' from __future__ import print_function from __future__ import absolute_import 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-3.4/autoradio/mpris2/volume.py0000664000175000017500000000143613607403370020200 0ustar pat1pat100000000000000''' Audio Volume ''' from __future__ import division from __future__ import print_function from builtins import range from past.utils import old_div 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([old_div(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-3.4/autoradio/mprisweb.py0000664000175000017500000001615513607403370017311 0ustar pat1pat100000000000000#!/usr/bin/env python # coding=utf-8 """ Show mediaplayer playlist on a simple web server. """ from __future__ import print_function from __future__ import absolute_import #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: from builtins import str from builtins import range from builtins import object from . import autoradio_config import cherrypy import os import datetime from . import autompris from . import autompris2 cpversion3=cherrypy.__version__.startswith("3") cpversion5=cherrypy.__version__.startswith("5") maxplele=100 # max number of elements in playlist port=8888 # server port head=''' MediaPlayer monitor | ''' tail=''' ''' class HomePage(object): # 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 range(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 or cpversion5): cherrypy.quickstart(HomePage(iht,player,session),config=settings) else: cherrypy.config.update(settings) cherrypy.root = HomePage(iht,player,session) cherrypy.server.start() def 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") if __name__ == '__main__': main() autoradio-3.4/autoradio/player/0000775000175000017500000000000013615624377016406 5ustar pat1pat100000000000000autoradio-3.4/autoradio/player/__init__.py0000664000175000017500000000003313553022177020502 0ustar pat1pat100000000000000VERSION = (0, "1.0", None) autoradio-3.4/autoradio/player/urls.py0000664000175000017500000000037413607403370017736 0ustar pat1pat100000000000000from __future__ import absolute_import from django.conf.urls import * from . import views urlpatterns = [ # Episode detail of one show url(r'^nohtml5/(?P(.*))', views.playernohtml5cmd), url(r'^(?P(.*))', views.playercmd), ] autoradio-3.4/autoradio/player/views.py0000664000175000017500000000102313553022177020100 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-3.4/autoradio/playlists/0000775000175000017500000000000013615624377017136 5ustar pat1pat100000000000000autoradio-3.4/autoradio/playlists/__init__.py0000664000175000017500000000000013553022177021224 0ustar pat1pat100000000000000autoradio-3.4/autoradio/playlists/admin.py0000664000175000017500000000355313607403370020573 0ustar pat1pat100000000000000from __future__ import absolute_import from .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-3.4/autoradio/playlists/migrations/0000775000175000017500000000000013615624377021312 5ustar pat1pat100000000000000autoradio-3.4/autoradio/playlists/migrations/0001_initial.py0000664000175000017500000001215313553022177023746 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-3.4/autoradio/playlists/migrations/0002_auto_20180721_1059.py0000664000175000017500000000134713607403370024731 0ustar pat1pat100000000000000# -*- coding: utf-8 -*- # Generated by Django 1.10.5 on 2018-07-21 10:59 from __future__ import unicode_literals from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ ('playlists', '0001_initial'), ] operations = [ migrations.AlterField( model_name='giorno', name='name', field=models.CharField(choices=[(b'luned\xc3\xac', b'luned\xc3\xac'), (b'marted\xc3\xac', b'marted\xc3\xac'), (b'mercoled\xc3\xac', b'mercoled\xc3\xac'), (b'gioved\xc3\xac', b'gioved\xc3\xac'), (b'venerd\xc3\xac', b'venerd\xc3\xac'), (b'sabato', b'sabato'), (b'domenica', b'domenica')], help_text='weekday name', max_length=20, unique=True), ), ] autoradio-3.4/autoradio/playlists/migrations/__init__.py0000664000175000017500000000000013553022177023400 0ustar pat1pat100000000000000autoradio-3.4/autoradio/playlists/models.py0000664000175000017500000002015713607403370020765 0ustar pat1pat100000000000000from builtins import str from 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 __str__(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 __str__(self): if self.emission_starttime is None: start="-" else: start=self.emission_starttime.isoformat() if self.emission_endtime is None: end="-" else: end=self.emission_endtime.isoformat() return self.sezione+" "+self.active.__str__()+" "+start+" "+end 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 __str__(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:'), on_delete=models.CASCADE) 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 __str__(self): return str(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:'), on_delete=models.CASCADE) 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 __str__(self): return str(self.playlist) autoradio-3.4/autoradio/playlists/views.py0000664000175000017500000000003213553022177020627 0ustar pat1pat100000000000000# Create your views here. autoradio-3.4/autoradio/programs/0000775000175000017500000000000013615624377016744 5ustar pat1pat100000000000000autoradio-3.4/autoradio/programs/__init__.py0000664000175000017500000000000013553022177021032 0ustar pat1pat100000000000000autoradio-3.4/autoradio/programs/admin.py0000664000175000017500000003623213607403370020401 0ustar pat1pat100000000000000from __future__ import absolute_import from builtins import object from 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(object): 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-3.4/autoradio/programs/fixtures/0000775000175000017500000000000013615624377020615 5ustar pat1pat100000000000000autoradio-3.4/autoradio/programs/fixtures/initial_data.json0000664000175000017500000003720613553022177024131 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-3.4/autoradio/programs/managers.py0000664000175000017500000000056013553022177021103 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-3.4/autoradio/programs/migrations/0000775000175000017500000000000013615624377021120 5ustar pat1pat100000000000000autoradio-3.4/autoradio/programs/migrations/0001_initial.py0000664000175000017500000011610513553022177023556 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-3.4/autoradio/programs/migrations/0002_fixture.py0000664000175000017500000000611113607403370023605 0ustar pat1pat100000000000000# -*- coding: utf-8 -*- from __future__ import unicode_literals from __future__ import print_function 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-3.4/autoradio/programs/migrations/__init__.py0000664000175000017500000000000013553022177023206 0ustar pat1pat100000000000000autoradio-3.4/autoradio/programs/models.py0000664000175000017500000012402013607403370020565 0ustar pat1pat100000000000000 from builtins import str from builtins import range from builtins import object from django.db import models from django.contrib.auth.models import User from django.utils.translation import ugettext_lazy from autoradio.programs.managers import EpisodeManager import datetime import calendar from django.db.models import Q 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 class MediaCategory(models.Model): """Category model for Media RSS""" MEDIA_CATEGORY_CHOICES = ( ('Action & Adventure', 'Action & Adventure'), ('Ads & Promotional', 'Ads & Promotional'), ('Anime & Animation', 'Anime & Animation'), ('Art & Experimental', 'Art & Experimental'), ('Business', 'Business'), ('Children & Family', 'Children & Family'), ('Comedy', 'Comedy'), ('Dance', 'Dance'), ('Documentary', 'Documentary'), ('Drama', 'Drama'), ('Educational', 'Educational'), ('Faith & Spirituality', 'Faith & Spirituality'), ('Health & Fitness', 'Health & Fitness'), ('Foreign', 'Foreign'), ('Gaming', 'Gaming'), ('Gay & Lesbian', 'Gay & Lesbian'), ('Home Video', 'Home Video'), ('Horror', 'Horror'), ('Independent', 'Independent'), ('Mature & Adult', 'Mature & Adult'), ('Movie (feature)', 'Movie (feature)'), ('Movie (short)', 'Movie (short)'), ('Movie Trailer', 'Movie Trailer'), ('Music & Musical', 'Music & Musical'), ('Nature', 'Nature'), ('News', 'News'), ('Political', 'Political'), ('Religious', 'Religious'), ('Romance', 'Romance'), ('Independent', 'Independent'), ('Sci-Fi & Fantasy', 'Sci-Fi & Fantasy'), ('Science & Technology', 'Science & Technology'), ('Special Interest', 'Special Interest'), ('Sports', 'Sports'), ('Stock Footage', 'Stock Footage'), ('Thriller', 'Thriller'), ('Travel', 'Travel'), ('TV Show', 'TV Show'), ('Western', 'Western'), ) name = models.CharField(max_length=50, choices=MEDIA_CATEGORY_CHOICES) slug = models.SlugField(blank=True, unique=False, help_text=ugettext_lazy('A slug is a URL-friendly nickname. For example, a slug for "Games & Hobbies" is "games-hobbies".')) class Meta(object): ordering = ['slug'] verbose_name = 'category (Media RSS)' verbose_name_plural = 'categories (Media RSS)' def __str__(self): return u'%s' % (self.name) class ParentCategory(models.Model): """Parent Category model.""" PARENT_CHOICES = ( ('Arts', 'Arts'), ('Business', 'Business'), ('Comedy', 'Comedy'), ('Education', 'Education'), ('Games & Hobbies', 'Games & Hobbies'), ('Government & Organizations', 'Government & Organizations'), ('Health', 'Health'), ('Kids & Family', 'Kids & Family'), ('Music', 'Music'), ('News & Politics', 'News & Politics'), ('Religion & Spirituality', 'Religion & Spirituality'), ('Science & Medicine', 'Science & Medicine'), ('Society & Culture', 'Society & Culture'), ('Sports & Recreation', 'Sports & Recreation'), ('Technology', 'Technology'), ('TV & Film', 'TV & Film'), ) name = models.CharField(max_length=50, choices=PARENT_CHOICES, help_text=ugettext_lazy('After saving this parent category, please map it to one or more Child Categories below.')) slug = models.SlugField(blank=True, unique=False, help_text=ugettext_lazy('A slug is a URL-friendly nickname. For example, a slug for "Games & Hobbies" is "games-hobbies".')) class Meta(object): ordering = ['slug'] verbose_name = 'category (iTunes parent)' verbose_name_plural = 'categories (iTunes parent)' def __str__(self): return u'%s' % (self.name) class ChildCategory(models.Model): """Child Category model.""" CHILD_CHOICES = ( ('Arts', ( ('Design', 'Design'), ('Fashion & Beauty', 'Fashion & Beauty'), ('Food', 'Food'), ('Literature', 'Literature'), ('Performing Arts', 'Performing Arts'), ('Visual Arts', 'Visual Arts'), ) ), ('Business', ( ('Business News', 'Business News'), ('Careers', 'Careers'), ('Investing', 'Investing'), ('Management & Marketing', 'Management & Marketing'), ('Shopping', 'Shopping'), ) ), ('Education', ( ('Education Technology', 'Education Technology'), ('Higher Education', 'Higher Education'), ('K-12', 'K-12'), ('Language Courses', 'Language Courses'), ('Training', 'Training'), ) ), ('Games & Hobbies', ( ('Automotive', 'Automotive'), ('Aviation', 'Aviation'), ('Hobbies', 'Hobbies'), ('Other Games', 'Other Games'), ('Video Games', 'Video Games'), ) ), ('Government & Organizations', ( ('Local', 'Local'), ('National', 'National'), ('Non-Profit', 'Non-Profit'), ('Regional', 'Regional'), ) ), ('Health', ( ('Alternative Health', 'Alternative Health'), ('Fitness & Nutrition', 'Fitness & Nutrition'), ('Self-Help', 'Self-Help'), ('Sexuality', 'Sexuality'), ) ), ('Religion & Spirituality', ( ('Buddhism', 'Buddhism'), ('Christianity', 'Christianity'), ('Hinduism', 'Hinduism'), ('Islam', 'Islam'), ('Judaism', 'Judaism'), ('Other', 'Other'), ('Spirituality', 'Spirituality'), ) ), ('Science & Medicine', ( ('Medicine', 'Medicine'), ('Natural Sciences', 'Natural Sciences'), ('Social Sciences', 'Social Sciences'), ) ), ('Society & Culture', ( ('History', 'History'), ('Personal Journals', 'Personal Journals'), ('Philosophy', 'Philosophy'), ('Places & Travel', 'Places & Travel'), ) ), ('Sports & Recreation', ( ('Amateur', 'Amateur'), ('College & High School', 'College & High School'), ('Outdoor', 'Outdoor'), ('Professional', 'Professional'), ) ), ('Technology', ( ('Gadgets', 'Gadgets'), ('Tech News', 'Tech News'), ('Podcasting', 'Podcasting'), ('Software How-To', 'Software How-To'), ) ), ) parent = models.ForeignKey(ParentCategory, related_name='child_category_parents', on_delete=models.CASCADE) name = models.CharField(max_length=50, blank=True, choices=CHILD_CHOICES, help_text=ugettext_lazy('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.')) slug = models.SlugField(blank=True, unique=False, help_text=ugettext_lazy('A slug is a URL-friendly nickname. For exmaple, a slug for "Fashion & Beauty" is "fashion-beauty".')) class Meta(object): ordering = ['parent', 'slug'] verbose_name = 'category (iTunes child)' verbose_name_plural = 'categories (iTunes child)' def __str__(self): if self.name!='': return u'%s > %s' % (self.parent, self.name) else: return u'%s' % (self.parent) 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 __str__(self): return self.name class Configure(models.Model): sezione = models.CharField(max_length=50,unique=True\ ,default='show',editable=False) active = models.BooleanField(ugettext_lazy("Active show"),default=True,\ help_text=ugettext_lazy("activate/deactivate the intere program class")) emission_starttime = models.TimeField(ugettext_lazy('Programmed start time'),null=True,blank=True,\ help_text=ugettext_lazy("The start time from wich the programs will be active")) emission_endtime = models.TimeField(ugettext_lazy('Programmed end time'),null=True,blank=True,\ help_text=ugettext_lazy("The end time the programs will be active")) radiostation = models.CharField(max_length=50,unique=True, default='Radio',editable=True,\ help_text=ugettext_lazy("The station name for the print of programs book")) channel = models.CharField(max_length=80,unique=True, default='103', editable=True,\ help_text=ugettext_lazy("The station channel for the print of programs book")) mezzo = models.CharField(max_length=50,unique=True, default='analogico terrestre', editable=True,\ help_text=ugettext_lazy("The station kind of emission for the print of programs book")) type = models.CharField(max_length=50,unique=True, default='radiofonica', editable=True,\ help_text=ugettext_lazy("The station type for the print of programs book")) def __str__(self): if self.emission_starttime is None: emission_starttime = "-" else: emission_starttime = self.emission_starttime.isoformat() if self.emission_endtime is None: emission_endtime = "-" else: emission_endtime = self.emission_endtime.isoformat() return self.sezione+" "+self.active.__str__()+" "\ +emission_starttime+" "\ +emission_endtime class ProgramType(models.Model): code = models.CharField(ugettext_lazy("Code"),max_length=4,default=None,null=False,blank=False,unique=True) type = models.CharField(ugettext_lazy("Type"),max_length=200,default=None,null=False,blank=False) subtype = models.CharField(ugettext_lazy("SubType"),max_length=254,default=None,null=False,blank=False) description = models.TextField(ugettext_lazy("Description"),default=None,null=True,blank=True) def __str__(self): return self.type+"/"+self.subtype def Production(): for production in (ugettext_lazy("autoproduction"),ugettext_lazy("eteroproduction")): yield production, production class Show(models.Model): """Show model.""" title = models.CharField(max_length=255,help_text=ugettext_lazy("show title")) active = models.BooleanField(ugettext_lazy("Active"),default=True,help_text=ugettext_lazy("Activate the show for emission")) slug = models.SlugField(unique=True, help_text=ugettext_lazy('Auto-generated from Title.')) length = models.FloatField(ugettext_lazy("Time length (seconds)"),default=None,null=True,blank=True, help_text=ugettext_lazy('Time lenght how you want to see it in the palimpsest')) type = models.ForeignKey(ProgramType, verbose_name= ugettext_lazy('Program Type'), help_text=ugettext_lazy('The categorization that follow the italian law (you have to use it to produce the programs book') , on_delete=models.CASCADE) production = models.CharField(ugettext_lazy("Production"),max_length=30,choices=Production(),default=None,null=True,blank=True, help_text=ugettext_lazy('The type of production')) COPYRIGHT_CHOICES = ( ('Public domain', 'Public domain'), ('Creative Commons: Attribution (by)', 'Creative Commons: Attribution (by)'), ('Creative Commons: Attribution-Share Alike (by-sa)', 'Creative Commons: Attribution-Share Alike (by-sa)'), ('Creative Commons: Attribution-No Derivatives (by-nd)', 'Creative Commons: Attribution-No Derivatives (by-nd)'), ('Creative Commons: Attribution-Non-Commercial (by-nc)', 'Creative Commons: Attribution-Non-Commercial (by-nc)'), ('Creative Commons: Attribution-Non-Commercial-Share Alike (by-nc-sa)', 'Creative Commons: Attribution-Non-Commercial-Share Alike (by-nc-sa)'), ('Creative Commons: Attribution-Non-Commercial-No Dreivatives (by-nc-nd)', 'Creative Commons: Attribution-Non-Commercial-No Dreivatives (by-nc-nd)'), ('All rights reserved', 'All rights reserved'), ) EXPLICIT_CHOICES = ( ('Yes', 'Yes'), ('No', 'No'), ('Clean', 'Clean'), ) # RSS 2.0 organization = models.CharField(max_length=255, help_text=ugettext_lazy('Name of the organization, company or Web site producing the podcast.')) link = models.URLField(help_text=ugettext_lazy('URL of either the main website or the podcast section of the main website.')) description = models.TextField(help_text=ugettext_lazy('Describe subject matter, media format, episode schedule and other relevant information while incorporating keywords.')) language = models.CharField(max_length=5, default='en-us', help_text=ugettext_lazy('Default is American English. See ISO 639-1 and ISO 3166-1 for more language codes.'), blank=True) copyright = models.CharField(max_length=255, default='All rights reserved', choices=COPYRIGHT_CHOICES, help_text=ugettext_lazy('See Creative Commons licenses for more information.')) copyright_url = models.URLField('Copyright URL', blank=True, help_text=ugettext_lazy('A URL pointing to additional copyright information. Consider a Creative Commons license URL.')) author = models.ManyToManyField(User, related_name='display_authors', help_text=ugettext_lazy('Remember to save the user\'s name and e-mail address in the User application.
')) webmaster = models.ForeignKey(User, related_name='display_webmaster', blank=True, null=True, help_text=ugettext_lazy('Remember to save the user\'s name and e-mail address in the User application.') , on_delete=models.CASCADE) category_show = models.CharField('Category', max_length=255, blank=True, help_text=ugettext_lazy('Limited to one user-specified category for the sake of sanity.')) domain = models.URLField(blank=True, help_text=ugettext_lazy('A URL that identifies a categorization taxonomy.')) ttl = models.PositiveIntegerField('TTL', help_text=ugettext_lazy('"Time to Live," the number of minutes a channel can be cached before refreshing.'), blank=True, null=True) image = models.ImageField(upload_to='podcasts/shows/img/', help_text=ugettext_lazy('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.'), blank=True) feedburner = models.URLField('FeedBurner URL', help_text=ugettext_lazy('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.'), blank=True) # iTunes subtitle = models.CharField(max_length=255, help_text=ugettext_lazy('Looks best if only a few words, like a tagline.'), blank=True) summary = models.TextField(help_text=ugettext_lazy('Allows 4,000 characters. Description will be used if summary is blank.'), blank=True) category = models.ManyToManyField(ChildCategory, related_name='show_categories', help_text=ugettext_lazy('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.
'), blank=True) explicit = models.CharField(max_length=255, default='No', choices=EXPLICIT_CHOICES, help_text=ugettext_lazy('"Clean" will put the clean iTunes graphic by it.'), blank=True) block = models.BooleanField(default=False, help_text=ugettext_lazy('Check to block this show from iTunes.
Show will remain blocked until unchecked.')) redirect = models.URLField(help_text=ugettext_lazy('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.'), blank=True) keywords = models.CharField(max_length=255, help_text=ugettext_lazy('A comma-demlimited list of up to 12 words for iTunes searches. Perhaps include misspellings of the title.'), blank=True) itunes = models.URLField('iTunes Store URL', help_text=ugettext_lazy('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.'), blank=True) class Meta(object): ordering = ['organization', 'slug'] def __str__(self): return u'%s' % (self.title) #@models.permalink #def get_absolute_url(self): # return ('podcast_episodes', (), { 'slug': self.slug }) def get_absolute_url(self): from django.urls import reverse return reverse('podcast_episodes', args=[str(self.slug)]) class Episode(models.Model): STATUS_CHOICES = ( (1, 'Draft'), (2, 'Public'), (3, 'Private'), ) SECONDS_CHOICES = tuple(('%02d' % x, str(x)) for x in range(60)) EXPLICIT_CHOICES = ( ('Yes', 'Yes'), ('No', 'No'), ('Clean', 'Clean'), ) TYPE_CHOICES = ( ('Plain', 'Plain text'), ('HTML', 'HTML'), ) ROLE_CHOICES = ( ('Actor', 'Actor'), ('Adaptor', 'Adaptor'), ('Anchor person', 'Anchor person'), ('Animal Trainer', 'Animal Trainer'), ('Animator', 'Animator'), ('Announcer', 'Announcer'), ('Armourer', 'Armourer'), ('Art Director', 'Art Director'), ('Artist/Performer', 'Artist/Performer'), ('Assistant Camera', 'Assistant Camera'), ('Assistant Chief Lighting Technician', 'Assistant Chief Lighting Technician'), ('Assistant Director', 'Assistant Director'), ('Assistant Producer', 'Assistant Producer'), ('Assistant Visual Editor', 'Assistant Visual Editor'), ('Author', 'Author'), ('Broadcast Assistant', 'Broadcast Assistant'), ('Broadcast Journalist', 'Broadcast Journalist'), ('Camera Operator', 'Camera Operator'), ('Carpenter', 'Carpenter'), ('Casting', 'Casting'), ('Causeur', 'Causeur'), ('Chief Lighting Technician', 'Chief Lighting Technician'), ('Choir', 'Choir'), ('Choreographer', 'Choreographer'), ('Clapper Loader', 'Clapper Loader'), ('Commentary or Commentator', 'Commentary or Commentator'), ('Commissioning Broadcaster', 'Commissioning Broadcaster'), ('Composer', 'Composer'), ('Computer programmer', 'Computer programmer'), ('Conductor', 'Conductor'), ('Consultant', 'Consultant'), ('Continuity Checker', 'Continuity Checker'), ('Correspondent', 'Correspondent'), ('Costume Designer', 'Costume Designer'), ('Dancer', 'Dancer'), ('Dialogue Coach', 'Dialogue Coach'), ('Director', 'Director'), ('Director of Photography', 'Director of Photography'), ('Distribution Company', 'Distribution Company'), ('Draughtsman', 'Draughtsman'), ('Dresser', 'Dresser'), ('Dubber', 'Dubber'), ('Editor/Producer (News)', 'Editor/Producer (News)'), ('Editor-in-chief', 'Editor-in-chief'), ('Editor-of-the-Day', 'Editor-of-the-Day'), ('Ensemble', 'Ensemble'), ('Executive Producer', 'Executive Producer'), ('Expert', 'Expert'), ('Fight Director', 'Floor Manager'), ('Floor Manager', 'Floor Manager'), ('Focus Puller', 'Focus Puller'), ('Foley Artist', 'Foley Artist'), ('Foley Editor', 'Foley Editor'), ('Foley Mixer', 'Foley Mixer'), ('Graphic Assistant', 'Graphic Assistant'), ('Graphic Designer', 'Graphic Designer'), ('Greensman', 'Greensman'), ('Grip', 'Grip'), ('Hairdresser', 'Hairdresser'), ('Illustrator', 'Illustrator'), ('Interviewed Guest', 'Interviewed Guest'), ('Interviewer', 'Interviewer'), ('Key Character', 'Key Character'), ('Key Grip', 'Key Grip'), ('Key Talents', 'Key Talents'), ('Leadman', 'Leadman'), ('Librettist', 'Librettist'), ('Lighting director', 'Lighting director'), ('Lighting Technician', 'Lighting Technician'), ('Location Manager', 'Location Manager'), ('Lyricist', 'Lyricist'), ('Make Up Artist', 'Make Up Artist'), ('Manufacturer', 'Manufacturer'), ('Matte Artist', 'Matte Artist'), ('Music Arranger', 'Music Arranger'), ('Music Group', 'Music Group'), ('Musician', 'Musician'), ('News Reader', 'News Reader'), ('Orchestra', 'Orchestra'), ('Participant', 'Participant'), ('Photographer', 'Photographer'), ('Post-Production Editor', 'Post-Production Editor'), ('Producer', 'Producer'), ('Production Assistant', 'Production Assistant'), ('Production Company', 'Production Company'), ('Production Department', 'Production Department'), ('Production Manager', 'Production Manager'), ('Production Secretary', 'Production Secretary'), ('Programme Production Researcher', 'Programme Production Researcher'), ('Property Manager', 'Property Manager'), ('Publishing Company', 'Publishing Company'), ('Puppeteer', 'Puppeteer'), ('Pyrotechnician', 'Pyrotechnician'), ('Reporter', 'Reporter'), ('Rigger', 'Rigger'), ('Runner', 'Runner'), ('Scenario', 'Scenario'), ('Scenic Operative', 'Scenic Operative'), ('Script Supervisor', 'Script Supervisor'), ('Second Assistant Camera', 'Second Assistant Camera'), ('Second Assistant Director', 'Second Assistant Director'), ('Second Unit Director', 'Second Unit Director'), ('Set Designer', 'Set Designer'), ('Set Dresser', 'Set Dresser'), ('Sign Language', 'Sign Language'), ('Singer', 'Singer'), ('Sound Designer', 'Sound Designer'), ('Sound Mixer', 'Sound Mixer'), ('Sound Recordist', 'Sound Recordist'), ('Special Effects', 'Special Effects'), ('Stunts', 'Stunts'), ('Subtitles', 'Subtitles'), ('Technical Director', 'Technical Director'), ('Translation', 'Translation'), ('Transportation Manager', 'Transportation Manager'), ('Treatment / Programme Proposal', 'Treatment / Programme Proposal'), ('Vision Mixer', 'Vision Mixer'), ('Visual Editor', 'Visual Editor'), ('Visual Effects', 'Visual Effects'), ('Wardrobe', 'Wardrobe'), ('Witness', 'Witness'), ) STANDARD_CHOICES = ( ('Simple', 'Simple'), ('MPAA', 'MPAA'), ('V-chip', 'TV Parental Guidelines'), ) RATING_CHOICES = ( ('Simple', ( ('Adult', 'Adult'), ('Nonadult', 'Non-adult'), ) ), ('MPAA', ( ('G', 'G: General Audiences'), ('PG', 'PG: Parental Guidance Suggested'), ('PG-13', 'PG-13: Parents Strongly Cautioned'), ('R', 'R: Restricted'), ('NC-17', 'NC-17: No One 17 and Under Admitted'), ) ), ('TV Parental Guidelines', ( ('TV-Y', 'TV-Y: All children'), ('TV-Y7-FV', 'TV-Y7/TV-Y7-FV: Directed to older children'), ('TV-G', 'TV-G: General audience'), ('TV-PG', 'TV-PG: Parental guidance'), ('TV-14', 'TV-14: Parents strongly cautioned'), ('TV-MA', 'TV-MA: Mature audiences'), ) ), ) FREQUENCY_CHOICES = ( ('always', 'Always'), ('hourly', 'Hourly'), ('daily', 'Daily'), ('weekly', 'Weekly'), ('monthly', 'Monthly'), ('yearly', 'Yearly'), ('never', 'Never'), ) # RSS 2.0 show = models.ForeignKey(Show , on_delete=models.CASCADE) title = models.CharField(max_length=255, help_text=ugettext_lazy('Make it specific but avoid explicit language. Limit to 100 characters for a Google video sitemap.')) active = models.BooleanField(ugettext_lazy("Active"),default=True) date = models.DateTimeField(ugettext_lazy('Recording date'),auto_now_add=True) title_type = models.CharField('Title type', max_length=255, blank=True, default='Plain', choices=TYPE_CHOICES) slug = models.SlugField(unique=True, help_text=ugettext_lazy('Auto-generated from Title.')) author = models.ManyToManyField(User, related_name='episode_authors', help_text=ugettext_lazy('Remember to save the user\'s name and e-mail address in the User application.')) description_type = models.CharField('Description type', max_length=255, blank=True, default='Plain', choices=TYPE_CHOICES) description = models.TextField(help_text=ugettext_lazy('Avoid explicit language. Google video sitempas allow 2,048 characters.')) captions = DeletingFileField(upload_to='podcasts/episodes/captions/', help_text=ugettext_lazy('For video podcasts. Good captioning choices include SubViewer, SubRip or TimedText.'), blank=True,max_length=255) category = models.CharField(max_length=255, blank=True, help_text=ugettext_lazy('Limited to one user-specified category for the sake of sanity.')) domain = models.URLField(blank=True, help_text=ugettext_lazy('A URL that identifies a categorization taxonomy.')) frequency = models.CharField(max_length=10, choices=FREQUENCY_CHOICES, blank=True, help_text=ugettext_lazy('The frequency with which the episode\'s data changes. For sitemaps.'), default='never') priority = models.DecimalField(max_digits=2, decimal_places=1, blank=True, null=True, help_text=ugettext_lazy('The relative priority of this episode compared to others. 1.0 is the most important. For sitemaps.'), default='0.5') status = models.IntegerField(choices=STATUS_CHOICES, default=2) update = models.DateTimeField(auto_now=True) # iTunes subtitle = models.CharField(max_length=255, help_text=ugettext_lazy('Looks best if only a few words like a tagline.'), blank=True) summary = models.TextField(help_text=ugettext_lazy('Allows 4,000 characters. Description will be used if summary is blank.'), blank=True) minutes = models.PositiveIntegerField(blank=True, null=True) seconds = models.CharField(max_length=2, blank=True, null=True, choices=SECONDS_CHOICES) keywords = models.CharField(max_length=255, help_text=ugettext_lazy('A comma-delimited list of words for searches, up to 12; perhaps include misspellings.'), blank=True, null=True) explicit = models.CharField(max_length=255, choices=EXPLICIT_CHOICES, help_text=ugettext_lazy('"Clean" will put the clean iTunes graphic by it.'), default='No') block = models.BooleanField(help_text=ugettext_lazy('Check to block this episode from iTunes because
its content might cause the entire show to be
removed from iTunes.'), default=False) # Media RSS role = models.CharField(max_length=255, blank=True, choices=ROLE_CHOICES, help_text=ugettext_lazy('Role codes provided by the European Broadcasting Union.')) media_category = models.ManyToManyField(MediaCategory, related_name='episode_categories', blank=True) standard = models.CharField(max_length=255, blank=True, choices=STANDARD_CHOICES, default='Simple') rating = models.CharField(max_length=255, blank=True, choices=RATING_CHOICES, help_text=ugettext_lazy('If used, selection must match respective Scheme selection.'), default='Nonadult') image = models.ImageField(upload_to='podcasts/episodes/img/', help_text=ugettext_lazy('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!'), blank=True) text = models.TextField(blank=True, help_text=ugettext_lazy('Media RSS text transcript. Must use tags. Please see the Media RSS 2.0 specification for syntax.')) deny = models.BooleanField(default=False, help_text=ugettext_lazy('Check to deny episode to be shown to users from specified countries.')) restriction = models.CharField(max_length=255, blank=True, help_text=ugettext_lazy('A space-delimited list of ISO 3166-1-coded countries.')) # Dublin Core start = models.DateTimeField(blank=True, null=True, help_text=ugettext_lazy('Start date and time that the media is valid.')) end = models.DateTimeField(blank=True, null=True, help_text=ugettext_lazy('End date and time that the media is valid.')) scheme = models.CharField(max_length=255, blank=True, default='W3C-DTF') name = models.CharField(max_length=255, blank=True, help_text=ugettext_lazy('Any helper name to distinguish this time period.')) # Google Media preview = models.BooleanField(default=False, help_text=ugettext_lazy("Check to allow Google to show a preview of your media in search results.")) preview_start_mins = models.PositiveIntegerField('Preview start (minutes)', blank=True, null=True, help_text=ugettext_lazy('Start time (minutes) of the media\'s preview,
shown on Google.com search results before
clicking through to see full video.')) preview_start_secs = models.CharField('Preview start (seconds)', max_length=2, blank=True, null=True, choices=SECONDS_CHOICES, help_text=ugettext_lazy('Start time (seconds) of the media\'s preview.')) preview_end_mins = models.PositiveIntegerField('Preview end (minutes)', blank=True, null=True, help_text=ugettext_lazy('End time (minutes) of the media\'s preview,
shown on Google.com search results before
clicking through to see full video.')) preview_end_secs = models.CharField('Preview end (seconds)', max_length=2, blank=True, null=True, choices=SECONDS_CHOICES, help_text=ugettext_lazy('End time (seconds) of the media\'s preview.')) host = models.BooleanField(default=False, help_text=ugettext_lazy('Check to allow Google to host your media after it expires. Must set expiration date in Dublin Core.')) # Behind the scenes objects = EpisodeManager() class Meta(object): ordering = ['-date', 'slug'] def __str__(self): return u'%s' % (self.title) #@models.permalink #def get_absolute_url(self): # return ('podcast_episode', (), { 'show_slug': self.show.slug, 'episode_slug': self.slug }) def get_absolute_url(self): from django.urls import reverse return reverse('podcast_episode', args=[str(self.show.slug),str(self.slug)]) def seconds_total(self): try: return (((float(self.minutes)) * 60) + (float(self.seconds))) except: return 0 def was_recorded_today(self): return self.rec_date.date() == datetime.date.today() was_recorded_today.short_description = ugettext_lazy('Recorded today?') def __str__(self): return self.title class Enclosure(models.Model): """Enclosure model.""" MIME_CHOICES = ( ('audio/ogg', '.ogg (audio)'), ('audio/mpeg', '.mp3 (audio)'), ('audio/x-m4a', '.m4a (audio)'), ('video/mp4', '.mp4 (audio or video)'), ('video/x-m4v', '.m4v (video)'), ('video/quicktime', '.mov (video)'), ('application/pdf', '.pdf (document)'), ('image/jpeg', '.jpg, .jpeg, .jpe (image)') ) MEDIUM_CHOICES = ( ('Audio', 'Audio'), ('Video', 'Video'), ('Document', 'Document'), ('Image', 'Image'), ('Executable', 'Executable'), ) EXPRESSION_CHOICES = ( ('Sample', 'Sample'), ('Full', 'Full'), ('Nonstop', 'Non-stop'), ) ALGO_CHOICES = ( ('MD5', 'MD5'), ('SHA-1', 'SHA-1'), ) FRAME_CHOICES = (( '29.97','29.97'),) BITRATE_CHOICES = ( ( "8","8"), ("11.025","11.025"), ("16","16"), ("22.050","22.050"), ("32","32"), ("44.1","44.1"), ("48","48"), ("96","96") ) SAMPLE_CHOICHES = ( ("24","24"), ("48","48"), ("64","64"), ("96","96"), ("128","128"), ("160","160"), ("196","196"), ("320","320") ) CHANNEL_CHOICES = ( ("2","2"), ("1","1") ) title = models.CharField(max_length=255, blank=True, default=None, help_text=ugettext_lazy('Title is generally only useful with multiple enclosures.')) file = DeletingFileField(upload_to='podcasts/episodes/files/', help_text=ugettext_lazy('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.'),blank=False, null=False,max_length=255) mime = models.CharField('Format', max_length=255, choices=MIME_CHOICES, blank=True) medium = models.CharField(max_length=255, blank=True, choices=MEDIUM_CHOICES) expression = models.CharField(max_length=25, choices=EXPRESSION_CHOICES, blank=True) frame = models.CharField('Frame rate', max_length=5, help_text=ugettext_lazy('Measured in frames per second (fps), often 29.97.'),choices=FRAME_CHOICES,blank=True) bitrate = models.CharField('Bit rate', max_length=5, blank=True, help_text=ugettext_lazy('Measured in kilobits per second (kbps), often 128 or 192.'),choices=BITRATE_CHOICES) sample = models.CharField('Sample rate', max_length=5, blank=True, help_text=ugettext_lazy('Measured in kilohertz (kHz), often 44.1.'),choices=SAMPLE_CHOICHES) channel = models.CharField(max_length=5, blank=True, help_text=ugettext_lazy('Number of channels; 2 for stereo, 1 for mono.'),choices=CHANNEL_CHOICES) algo = models.CharField('Hash algorithm', max_length=50, blank=True, choices=ALGO_CHOICES) hash = models.CharField(max_length=255, blank=True, help_text=ugettext_lazy('MD-5 or SHA-1 file hash.')) player = models.URLField(help_text=ugettext_lazy('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).'), blank=True) embed = models.BooleanField(help_text=ugettext_lazy('Check to allow Google to embed your external player in search results on Google Video.'), blank=True) width = models.PositiveIntegerField(blank=True, null=True, help_text=ugettext_lazy("Width of the browser window in
which the URL should be opened.
YouTube's default is 425.")) height = models.PositiveIntegerField(blank=True, null=True, help_text=ugettext_lazy("Height of the browser window in
which the URL should be opened.
YouTube's default is 344.")) episode = models.ForeignKey(Episode, help_text=ugettext_lazy('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=models.CASCADE) class Meta(object): ordering = ['mime', 'file'] def save(self, *args, **kwargs): """ Return a default title numbered by enclosure number a missing title is not a good idea for rss and web interface """ if self.title == "": self.title = "Part "+str(Enclosure.objects.filter(Q(episode=self.episode)).all().count()+1) super(Enclosure, self).save(*args, **kwargs) def __str__(self): return u'%s' % (self.file) class Schedule(models.Model): # program = models.ForeignKey(Program, edit_inline=models.TABULAR,\ # num_in_admin=2,verbose_name='si riferisce al programma:',editable=False) episode = models.ForeignKey(Episode, \ verbose_name=ugettext_lazy('Linked episode:'), on_delete=models.CASCADE) emission_date = models.DateTimeField(ugettext_lazy('programmed date'),\ help_text=ugettext_lazy("This is the date and time when the program will be on air")) # 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('Scheduled for today?') def refepisode(self): return self.episode.title refepisode.short_description = ugettext_lazy('Linked episode:') def __str__(self): return str(self.episode.title) class ScheduleDone(models.Model): schedule = models.ForeignKey(Schedule, \ verbose_name=ugettext_lazy('Linked schedule:'), on_delete=models.CASCADE) enclosure = models.ForeignKey(Enclosure, \ verbose_name=ugettext_lazy('Linked enclosure:'), on_delete=models.CASCADE) emission_done = models.DateTimeField(ugettext_lazy('emission done')\ ,null=True,editable=False ) def __str__(self): return str(self.emission_done) class PeriodicSchedule(models.Model): # program = models.ForeignKey(Program, edit_inline=models.TABULAR,\ # num_in_admin=2,verbose_name='si riferisce al programma:',editable=False) show = models.ForeignKey(Show,verbose_name=\ ugettext_lazy('refer to show:'), on_delete=models.CASCADE) start_date = models.DateField(ugettext_lazy('Programmed start date'),null=True,blank=True,\ help_text=ugettext_lazy("The program will be in palimpsest starting from this date")) end_date = models.DateField(ugettext_lazy('Programmed end date'),null=True,blank=True,\ help_text=ugettext_lazy("The program will be in palimpsest ending this date")) time = models.TimeField(ugettext_lazy('Programmed time'),null=True,blank=True,\ help_text=ugettext_lazy("This is the time when the program is planned in palimpsest")) giorni = models.ManyToManyField(Giorno,verbose_name=ugettext_lazy('Programmed days'),blank=True,\ help_text=ugettext_lazy("The program will be in palimpsest those weekdays")) def __str__(self): return str(self.show) class AperiodicSchedule(models.Model): # program = models.ForeignKey(Program, edit_inline=models.TABULAR,\ # num_in_admin=2,verbose_name='si riferisce al programma:',editable=False) show = models.ForeignKey(Show, verbose_name=\ ugettext_lazy('refer to Show:'), on_delete=models.CASCADE) emission_date = models.DateTimeField(ugettext_lazy('Programmed date'),\ help_text=ugettext_lazy("This is the date and time when the program is planned in palimsest")) def was_scheduled_today(self): return self.emission_date.date() == datetime.date.today() was_scheduled_today.short_description = ugettext_lazy('Programmed for today?') def __str__(self): return str(self.show) autoradio-3.4/autoradio/programs/static/0000775000175000017500000000000013615624377020233 5ustar pat1pat100000000000000autoradio-3.4/autoradio/programs/static/programs/0000775000175000017500000000000013615624377022065 5ustar pat1pat100000000000000autoradio-3.4/autoradio/programs/static/programs/css/0000775000175000017500000000000013615624377022655 5ustar pat1pat100000000000000autoradio-3.4/autoradio/programs/static/programs/css/base.css0000664000175000017500000002237613553022177024302 0ustar pat1pat100000000000000/* djangoproject.com by Wilson Miner (wilson@lawrence.com) Copyright (c) 2005 Lawrence Journal-World. Please don't steal. */ /* SETUP */ body { margin:0; padding:0; background:#092e20; color:white; } body, th, td { font:12px/1.4em Verdana,sans-serif; } #container { position:relative; min-width:55em; max-width:100em; } #homepage #container { max-width:100em; } /* LINKS */ a {text-decoration: none;} a img {border: none;} a:link, a:visited { color:#ffc757; } #content-main a:link, #content-main a:visited { color:#ab5603; text-decoration:underline; } #content-secondary a:link, #content-secondary a:visited { color:#ffc757; text-decoration:none; } a:hover { color:#ffe761; } #content-main a:hover { background-color:#E0FFB8; color:#234f32; text-decoration:none; } #content-secondary a:hover { color:#ffe761; background:none; } #content-main h2 a, #content-main h3 a { text-decoration:none !important; } /* HEADER */ #header { position:relative; height:6.5em; background:#092e20; } #header h1#logo { margin:0; width:111px; height:41px; position:absolute; bottom:10px; left:25px; } /* NAV */ #nav-global { position:absolute; margin:0; bottom:0; right:0; font-family:"Trebuchet MS",sans-serif; white-space:nowrap; } #nav-global li { display:block; float:left; list-style-type:none; margin:0; padding:0; } #nav-global a { display:block; float:left; padding:5em 16px 10px 16px; background:#092e20; } #nav-global a:hover { color:white; background:#234f32; } #homepage #nav-homepage a, #overview #nav-overview a, #download #nav-download a, #documentation #nav-documentation a, #weblog #nav-weblog a, #community #nav-community a, #blogroll #nav-blogroll a, #code #nav-code a { color:white; background:#092e20 url(../img/site/nav_bg.gif) bottom repeat-x; } /* COLUMNS */ #columnwrap { background:#234f32; padding-bottom:10px; } #subwrap { background:#326342; width:73%; float:left; padding-bottom:10px; } #content-main { float:left; width:70%; background:white; color:black; padding-bottom:10px; } #generic #content-main, #code #content-main { width:100%; } #content-main * { margin-left:22px; margin-right:24px; } #content-main * * { margin-left:0; margin-right:0; } .sidebar { font-size:92%; } .sidebar * { margin-left:14px; margin-right:14px; } .sidebar * * { margin-left:0; margin-right:0; } #content-extra { float:right; width:27%; } #content-related { float:right; width:30%;} #content-secondary { clear:both; background:#487858; margin-left:0; margin-right:0; margin-top:15px; margin-bottom:-10px; padding:10px 24px; color:white; } .subcol-primary, .subcol-secondary { width:40%; float:left; padding-bottom:1.2em; } .subcol-primary { margin-right:1%; } /* CONTENT */ h1,h2,h3 { margin-top:.8em; font-family:"Trebuchet MS",sans-serif; font-weight:normal; } h1 { font-size:218%; margin-top:.6em; margin-bottom:.6em; color:#092e20; line-height:1.1em; } h2 { font-size:150%; margin-top:1em; margin-bottom:.2em; line-height:1.2em; color:#092e20; } #homepage h2 { font-size:140%; } h3 { font-size:125%; font-weight:bold; margin-bottom:.2em; color:#487858; } h4 { font-size:100%; font-weight:bold; margin-bottom:-3px; margin-top:1.2em; text-transform:uppercase; letter-spacing:1px; } h4 pre, h4 tt, h4 .literal { text-transform:none; } h5 { font-size:1em; font-weight:bold; margin-top:1.5em; margin-bottom:3px; } p, ul, dl { margin-top:.6em; margin-bottom:.8em; } hr { color:#ccc; background-color:#ccc; height:1px; border:0; } p.date { color:#487858; margin-top:-.2em; } p.more { margin-top:-.4em; } .sidebar p.date { color:#90ba9e; } #content-secondary h2, .sidebar h2 { color:white; } #content-secondary h3, .sidebar h3 { color:#9aef3f; } #content-secondary h2:first-child { margin-top:.6em; } .sidebar h2:first-child { margin-top:.8em; } #content-main h2, #content-main h3 { margin-top:1.2em; } h2.deck { margin-top:-.5em !important; margin-bottom:.6em; color:#487858; } ins { text-decoration: none; } ins a { text-decoration: none; } /* LISTS */ ul { padding-left:2em; } ol { padding-left:30px; } ul li { list-style-type:square; margin-bottom:.4em; } ul ul { padding-left:1.2em; } ul ul ul { padding-left:1em; } ul.linklist, ul.toc { padding-left:0; } ul.toc ul { margin-left:.6em; } ul.toc ul li { list-style-type:square; } ul.toc ul ul li { list-style-type:disc; } ul.linklist li, ul.toc li { list-style-type:none; } dt { font-weight:bold; margin-top:.5em; font-size:1.1em; } dd { margin-bottom:.8em; } /* RSS */ a.rss { font:bold 10px Verdana, sans-serif; padding:0 .2em; border: 1px solid; text-decoration:none; background:#f60;color: #fff; border-color:#ffc8a4 #7d3302 #3f1a01 #ff9a57; margin:0 3px; vertical-align:middle; } #content-main a.rss { color:#fff; text-decoration:none; } a.rss:hover, a.rss:link, a.rss:visited { color:#fff; text-decoration:none; } /* BLOCKQUOTES */ #weblog blockquote { padding-left:0.8em; padding-right:1em; font:125%/1.2em "Trebuchet MS", sans-serif; color:#234f32; border-left:2px solid #94da3a; } .sidebar blockquote { margin-top:1.5em; margin-bottom:1.5em; } .sidebar blockquote p { font:italic 175%/1.2em "Trebuchet MS",sans-serif; color:#94da3a; } .sidebar blockquote cite { display:block; font-style:normal; line-height:1.2em; margin-top:-.8em; color:#94da3a; } .sidebar cite strong { font-weight:normal; color:white; } /* CODE BLOCKS */ .literal { white-space:nowrap; } .literal, .literal-block { color:#234f32; } .sidebar .literal { color:white; background:transparent; font-size:11px; } pre, .literal-block { font-size:medium; background:#E0FFB8; border:1px solid #94da3a; border-width:1px 0; margin: 1em 0; padding: .3em .4em; overflow: auto; } dt .literal, table .literal { background:none; } textarea.codedump { font-size:10px; color:#234f32; width:100%; background:#E0FFB8; border:1px solid #94da3a; border-width:1px 0; padding: .3em .4em; } /* NOTES & ADMONITIONS */ .note, .admonition, .caution { padding:.8em 1em .8em; margin: 1em 0; border:1px solid #94da3a; } .admonition-title { font-weight:bold; margin-top:0 !important; margin-bottom:0 !important;} .admonition .last { margin-bottom:0 !important; } .admonition-philosophy { padding-left:65px; background:url(../img/doc/icons/docicons-philosophy.gif) .8em .8em no-repeat;} .admonition-note, .caution { padding-left:65px; background:url(../img/doc/icons/docicons-note.gif) .8em .8em no-repeat;} .admonition-behind-the-scenes { padding-left:65px; background:url(../img/doc/icons/docicons-behindscenes.gif) .8em .8em no-repeat;} /* DOCS */ #documentation h2, #documentation h3, #documentation h4 { margin-top:1.4em; } #documentation dd { margin-left:1em; } #content-main table { color:#000; } table.docutils { border-collapse:collapse; } table.docutils thead th { border-bottom:2px solid #dfdfdf; text-align:left; } table.docutils td, table.docutils th { border-bottom:1px solid #dfdfdf; padding:4px 2px;} table.docutils td p { margin-top:0; margin-bottom:.5em; } #documentation #content-related .literal { background:transparent !important; } /* BILLBOARDS */ #billboard { background:#94da3a url(../img/site/bbdsm_bg.gif) repeat-x; border-bottom:6px solid #092e20; } #billboard h2 { margin:0; } #generic #billboard { display:none; } #homepage #billboard { background-image: url(../img/site/bbd_bg.gif); } #homepage #billboard h2 { margin:0; text-indent:-5000px; height:80px; width:633px; background:url(../img/site/bbd_homepage.gif) no-repeat; } #overview #billboard h2 { margin:0; text-indent:-5000px; height:60px; width:203px; background:url(../img/site/bbd_overview.gif) no-repeat; } #download #billboard h2 { margin:0; text-indent:-5000px; height:60px; width:203px; background:url(../img/site/bbd_download.gif) no-repeat; } #documentation #billboard h2 a { display:block; margin:0; text-indent:-5000px; height:60px; width:226px; background:url(../img/site/bbd_documentation.gif) no-repeat; } #weblog #billboard h2 a { display:block; margin:0; text-indent:-5000px; height:60px; width:226px; background:url(../img/site/bbd_weblog.gif) no-repeat; } #community #billboard h2 { display:block; margin:0; text-indent:-5000px; height:60px; width:226px; background:url(../img/site/bbd_community.gif) no-repeat; } #blogroll #billboard h2 { display:block; margin:0; text-indent:-5000px; height:60px; width:168px; background:url(../img/site/bbd_blogroll.gif) no-repeat; } #code #billboard h2 a { display:block; margin:0; text-indent:-5000px; height:60px; width:184px; background:url(../img/site/bbd_code.gif) no-repeat; } /* FOOTER */ #footer { clear:both; color:#487858; padding:10px 20px; font-size:90%; } /* COMMENTS */ .comment { margin:15px 0; } div.comment p { margin-left:1em; } #weblog div.comment p.date { margin-bottom:.2em; color:#94da3a; } /* MISC */ .small { font-size:90%; } h3 .small { font-size:80%; } .quiet { font-weight:normal; } .clear { clear:both; } #content-main .quiet { color:#487858; } #content-secondary .quiet { color:#90ba9e; } /* CLEARFIX KLUDGE */ #columnwrap:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; } #columnwrap { display: inline-block; } /* Hides from IE-mac \*/ * html #columnwrap { height: 1%; } #columnwrap { display: block; } /* End hide from IE-mac */ #subwrap:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; } #subwrap { display: inline-block; } /* Hides from IE-mac \*/ * html #subwrap { height: 1%; } #subwrap { display: block; } /* End hide from IE-mac */om IE-mac */ autoradio-3.4/autoradio/programs/static/programs/css/player.css0000664000175000017500000000474013553022177024657 0ustar pat1pat100000000000000body { margin: 0; padding: 0; border: 0; font-family: arial, helvetica, sans-serif; font-size: 100%; background: #234f32; } div#header-wrap, div#footer-wrap { background: #092e20; } div#content-wrap { background: #234f32; width: 960px; margin: 0 auto; } div#header, div#content, div#footer { width: 960px; margin: 0 auto; color: #fff; } div#header, div#content { text-shadow: #000 0 0 0; } div#return-wrap { background: #92cc47; } div#return { margin: 0 auto; width: 960px; color: #386a42; } div#return p { margin: 0; padding: 8px 16px; font-size: .85em; font-weight: bold; } div#return a { color: #386a42; } div#return a:hover { color: #092e20; background: none; } div#content { background: #fff; color: #222; overflow: auto; width: 100%; } div#footer { padding: 1em 0; } h1, h2, h3, h4, h5, h6, p, dl, dt, dd, img { margin: 0; padding: 16px; border: 0; display: block; } h1 { margin: 0 16px; padding: 1em 0 0; font-size: 2em; width: 375px; height: 95px; text-indent: -9999px; } h2 { font-size: 1.25em; } h2 a { color: #092e20; } h2 a:hover { background: #e0ffb8; color: #574f32; text-decoration: none; } h3 { padding: 0 16px 8px; font-size: 1em; color: #444; font-weight: normal; } h4, h5 { padding: 0 16px 8px; } ul, li { margin: 0; padding: 0; list-style: none; } ul { margin: 0 8px 16px; } li { margin-left: 24px; list-style: square; font-size: .85em; line-height: 24px; } dl, dt, dd { padding: 8px 0; color: #000; } dt, dd { font-size: .8em; padding: 0 16px 8px; } dt { font-weight: bold; float: left; text-align: right; width: 80px; } dd { margin-left: 100px; } #content p { padding: 0 16px 16px; font-size: .8em; line-height: 1.25em; } #content p.back { padding: 16px 16px 0; } #footer p { padding: 0 16px; font-size: .7em; color: #467856; font-family: verdana, tahoma, sans-serif; } #footer a { color: #f7c757; text-decoration: none; } #footer a:hover { color: #f2e761; background: none; } div.image { float: right; margin: 0 16px 16px; border: 1px solid #94da3a; padding: 7px; background: #e0ffb8; } div.image img { padding: 0; } a { color: #b25603; } a:hover { background: #e0ffb8; color: #574f32; text-decoration: none; } div#player { width: 288px; height: 300px; position: fixed; top: 50%; margin-top: -150px ; left: 50%; margin-left:-144px; } div#player img {padding:0; visibility:show; } div#player a, #player div { display: block; width: 50px; height: 50px; background-position: 75px 262px;position: absolute; top: 10px; left: 112px; border:2px solid #666; } div#player a b, #player div b { display: none; } autoradio-3.4/autoradio/programs/static/programs/css/podcast.css0000664000175000017500000000412613553022177025016 0ustar pat1pat100000000000000body { margin: 0; padding: 0; border: 0; font-family: arial, helvetica, sans-serif; font-size: 100%; background: #234f32; } div#header-wrap, div#footer-wrap { background: #092e20; } div#content-wrap { background: #234f32; width: 960px; margin: 0 auto; } div#header, div#content, div#footer { width: 960px; margin: 0 auto; color: #fff; } div#header, div#content { text-shadow: #000 0 0 0; } div#return-wrap { background: #92cc47; } div#return { margin: 0 auto; width: 960px; color: #386a42; } div#return p { margin: 0; padding: 8px 16px; font-size: .85em; font-weight: bold; } div#return a { color: #386a42; } div#return a:hover { color: #092e20; background: none; } div#content { background: #fff; color: #222; overflow: auto; width: 100%; } div#footer { padding: 1em 0; } h1, h2, h3, h4, h5, h6, p, dl, dt, dd, img { margin: 0; padding: 16px; border: 0; display: block; } h1 { margin: 0 16px; padding: 1em 0 0; font-size: 2em; width: 375px; height: 95px; text-indent: -9999px; } h2 { font-size: 1.25em; } h2 a { color: #092e20; } h2 a:hover { background: #e0ffb8; color: #574f32; text-decoration: none; } h3 { padding: 0 16px 8px; font-size: 1em; color: #444; font-weight: normal; } h4, h5 { padding: 0 16px 8px; } ul, li { margin: 0; padding: 0; list-style: none; } ul { margin: 0 8px 16px; } li { margin-left: 24px; list-style: square; font-size: .85em; line-height: 24px; } dl, dt, dd { padding: 8px 0; color: #000; } dt, dd { font-size: .8em; padding: 0 16px 8px; } dt { font-weight: bold; float: left; text-align: right; width: 80px; } dd { margin-left: 100px; } #content p { padding: 0 16px 16px; font-size: .8em; line-height: 1.25em; } #content p.back { padding: 16px 16px 0; } #footer p { padding: 0 16px; font-size: .7em; color: #467856; font-family: verdana, tahoma, sans-serif; } #footer a { color: #f7c757; text-decoration: none; } #footer a:hover { color: #f2e761; background: none; } div.image { float: right; margin: 0 16px 16px; border: 1px solid #94da3a; padding: 7px; background: #e0ffb8; } div.image img { padding: 0; } a { color: #b25603; } a:hover { background: #e0ffb8; color: #574f32; text-decoration: none; }autoradio-3.4/autoradio/programs/static/programs/css/print.css0000664000175000017500000000111213553022177024505 0ustar pat1pat100000000000000@import url(base.css); @page { size: 7in 9.25in; margin: .75in .5in .75in 1in; } body { font-size:10pt; } /* Hide unneccessary content */ #header, #billboard, #content-extra, #documentation #content-secondary, #documentation #content-related { display:none; } /* Fix widths */ #container { width:7in; } #subwrap, #content-main { width:7in; float:none; } /* Formatting and display fixes */ #content { border-top:none; } .literal-block { overflow:visible; } /* Typographic adjustments */ #content-main p, #content-main h2, #content-main h3, #content-main h4 { margin-bottom:1em; }autoradio-3.4/autoradio/programs/static/programs/mediacastlogo.png0000664000175000017500000002352613553022177025405 0ustar pat1pat100000000000000PNG  IHDRw_HtEXtSoftwareAdobe ImageReadyqe<&IDATx] E>}Y$bI,>ED$%,>ŽʢO)O@T YXTYQ21RꙚ۷Iw}{:u)9t..@b`vqb9zrÛ8nܺg f|,g&vXPd+J AeX| /#?u(&^\IAthhprgHaF\[ˎ00p\k.7"Y& j_m,M=>􄪆hߥk+gB@lIFHlG rg.v.݈`Xh]kyB'|ipۋڻ0eMGX1R&]u&uS5khmޡ%LL:4y uNfT=U‹%5HG2.hbf Fg抅Os ${82oţC(CƗyy5;$qS8ZcyP2`1:54< qu~Spi~S2Nd]:6^_&AE5Y1\aNA§8$Tto}- E /oG !C&08=4˸>/ku44RvM鄮zUEYVz;,^?yP]sl꘸&Xŋ(QF/5HdN߾xCgTzݗŕb?otaѡ#;L1ҢB~X!vPŤ/8CB(#Ed ⸋Ts%DC#O6>[f7tj0D7>.G l dO:BP:DolCz4{Q~)~?]9{yNBLA6F?TC#gT5BPm:`&"^dmA PknoM]CCc(1NNۣss E6#T ͂iEڱ(e1R@ye0fř ^ _xvk>R~-̋ 9<|ޣÐԲ@yz~4 \N կ]97#A/v{CiU! {p F؝bOM+zE ud[ʯdXgge=6~E(ߡ26&3%i( P#O^oBIo N[P>#.MP姰 gD|>YF[Pʲ'}6I)k[I/S#{k,<@#|eQ< z':ٖKE氜(!TI>Sm}r5b)/cE¥)+߽'(]v85YGF(Z+d2|bDkio5w,JbMoG9{I"IY@lrJ *̓ -f\Q{IuSNZo\L#MW,_!G?IA=:eM<{k &fRmde Xe'JJ 7Q$&3W(MLxʬki<,oh;M-&b.o*PG XWVF [ZLk Æy6t:Hg\by|1U9uc4rI0cV3g6TvXs%t;[JBM1M=N|CIOSTQˤ e5s C8"8߮)/ê`G̋FyVa l[={fL柦3# ``B,Rz`dA>oo[CF*NT*} Gђȡ^ 0)G-Ǩ@s髽" P,Yޱ5ݏUL4uq]C OG ۉ׹> s \!}ςQf@G{ :Jxlv\̴c>c̉7̋z"w^;N 4Ss|Gɿ !1ܽ)adE|<̎i2Qz03]B:8bq+c;AR+.z/O|G#EQ(Tw-gEo$/b wm5ȱ}% `ׁZ ɣF"9MT&M+CBdn{q=!Bm$35 \xR89AY_7h\"NΑy1He5#6Yϣ(FCZ~GbT:aB:~ L"cYd0B];'AcM wO6;T$e!Y5 B% 6~Ib)B" Y9f.h{Wur*++q(/, ,̋$z Dzxu^n%3(+R(ި!I=s2D6RL\Z^$#׆PZETYjLs&:'3]hEػI f!Ր-A\c Qj$D5z+;`sA02Cf]AHIƎ{*|405UmZyQeX-^fE)}r?M$!Pi"崈jb,F9G&)3.Q/oN1M?Vo?KP@UBժ"BgU5j2zHkMΏx-L2Y [/>>}6TjPVs=V/g{GŤQp|z8^k#퇥灈n4V4b4\;L~qgոմ\;O(#I<Äi6긯bW+}P*G^s$ /ثS( Wnl@=Ψ3W& }fG/ zkx";c7ߡp4a1{ D4Iy}mяBieeJl!7 kcELzlBDv NKWp2C#*ciFy(BnI&*fJ}-P)EJF|W6bn2\B!ّ9C?&U⏡vIsK<M ϖR!mC=;ӽCI穕ߑ Hݰ@?YBfE`pxzaĜBlPo^:itBo ?l0G `ar8 3)ϰ= &51x'AF3|$ 1*uB쟒=dd5Ch|Gj̾s$m`$@>Ԅ.Z SvV}2>ϗvRv6+^ZRU&GnoTC?ӑu3!Yܢlb7r(5uzR[ Gi{R (\ Cw6avoG6qbPoAQ>6K 6R; C= دL!@GG qO tF?C9-PhYzrި#{PNJ0Qh#{T0f1s7Iӊy߇hx. 8% g88ۄI&!6o &Df7+M?&XOm"b6JXq;@FYG.$_ߠwhCwJ7NCaϑ0x~}<@OJrV@] 'kʂ2D̻b ,oG5i {vِ qHs> L$+|"@4 n9kEHm̿V$M0N&]CfJe / t|T-ksF9I3a`bٲ!)k+M%CvjwMBrhtDA]R gR`9_< E&H4L]߽`? =9*IaO`//B&ZJ~G]CUiET<҉^uvmᄄru;jI8.ܛE-mTMr]$2(*צ\ae93CQ1{9 ck6rBw$x|'r^a١S/&xd$ @Q(3:Hj#3?9dek߹_p{g^ Vxnp"BGB;a_LOjhͽ PRw쏅E0؅ rD3js|e.nkطvj;N 6xx1 Mާ$khrO'uQ7ӽ7yl:PϺ5 {K s1Ja,ȍjcC{@kึ_hp_ ݹ54v#]cw$veQԞu] .xۿ=L(8/ `90m- ?AIƙOt񆳡@ʽE=n 5khrZ{0gs jNH&ڧM\(}Ə C :Ʉo"54kh=c\,q{l^E/ kho)6a]>(LsB^3$.qm^yP)􁆆5v `#Erޯg$SC,jƺw[Ө" m8ڻY{kh]c&v% _s<}r3l,y4S͟D"lzݭ5aѫ4殱˂4u5/?x<϶#!R$ EG#s Y _ΓP]cW)ЊOc=1dZd 53 V]<!/g~,0L2ՔyFC.FRk=Ayq6nVrgzqNy>6NxkfczM ^CƮ rwE0 Z==[rcQf9  ,'4QFLCCƨh s7 \wV{q^<`LG~5pͪiq7ihr:gYp@E;¥X4ԘOh} rBCR⬵Օ3