root/trunk/radiomate/jukeslots/base.py @ 42

Revision 42, 5.7 KB (checked in by clauz, 4 years ago)

adding playlist randomization plus small changes

  • Property svn:keywords set to Id
Line 
1# vim:fileencoding=utf-8:nomodified
2# $Id$
3#
4#  Copyright 2010 Claudio Pisa (clauz at ninux dot org)
5#
6#  This file is part of RadioMate
7#
8#  RadioMate is free software: you can redistribute it and/or modify
9#  it under the terms of the GNU General Public License as published by
10#  the Free Software Foundation, either version 3 of the License, or
11#  (at your option) any later version.
12#
13#  RadioMate is distributed in the hope that it will be useful,
14#  but WITHOUT ANY WARRANTY; without even the implied warranty of
15#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16#  GNU General Public License for more details.
17#
18#  You should have received a copy of the GNU General Public License
19#  along with RadioMate.  If not, see <http://www.gnu.org/licenses/>.
20#
21
22# JukeSlots are played by the JukeBox. Each JukeSlot is associated to a TimeSlot
23# in which a different kind of show is transmitted. For example a PlayListJukeSlot
24# will transmit a playlist, while a LiveJukeSlot will accept a remote stream
25# and retransmit it.
26
27import time
28import shlex
29import logging
30import tempfile
31import os
32from subprocess import Popen, PIPE, STDOUT
33
34from .. import config
35from .. import dao
36from .. import mate
37
38__all__ = ["JUKESLOTTYPEDICT", "JukeSlotException", "JukeSlot", "dao", "mate", "config"]
39
40# dict to associate timeslot types to JukeSlot classes
41JUKESLOTTYPEDICT = {}
42
43class JukeSlotException(Exception):
44        pass
45
46class JukeSlot(Popen, mate.TimeSlot):
47        "A TimeSlot, but with its own life."
48        deathtime = 0
49        def __init__(self, timeslot, mainpassword=""):
50                cmd = config.LIQUIDSOAP + " -v - " # take commands from standard input
51                # spawn the process
52                Popen.__init__(self, shlex.split(cmd), bufsize=-1, universal_newlines=True, 
53                                stdin=PIPE, stdout=None, stderr=STDOUT)
54
55                self.logger = logging.getLogger("radiomate.jukebox")
56                logging.basicConfig(filename=config.LOGFILENAME, level=config.LOGGINGLEVEL)
57               
58                # initialize the connection to the database
59                try:
60                        self.cm = dao.DBConnectionManager(dbhost = config.DBHOST,
61                                        dbuser = config.DBUSER, dbpassword = config.DBPASSWORD,
62                                        database = config.DATABASE)
63                        self.pldao = dao.PlayListDAO(self.cm)
64                except Exception, e:
65                        raise JukeSlotException(str(e))
66               
67                # the list of the temporary playlist files built on the fly.
68                # Used to delete the files on JukeSlot.__del__
69                self.plistnames = []
70                # the password used to connect to the main JukeSlot
71                self.mainpassword = mainpassword
72               
73                mate.TimeSlot.__init__(self, timeslot.dictexport())
74       
75        def __setattr__(self, name, value):
76                Popen.__setattr__(self, name, value)
77               
78        def run(self, main=False):
79                "Inject the liquidsoap code into the spawned liquidsoap instance"
80                liq = self.liquidsoapcode()
81                self.logger.debug("run liquidsoap code: \n %s", liq)
82
83                if not liq:
84                        return
85                r = self.poll()
86                if r:
87                        raise JukeSlotException("liquidsoap instance not running (exitcode %d)" % r)
88
89                time.sleep(1)
90                self.stdin.write(liq)
91                self.stdin.close()
92                time.sleep(2)
93
94                if main:
95                        r = self.poll()
96                        if r:
97                                raise JukeSlotException("liquidsoap istance not running (exitcode %d)" % r)
98
99        def getPlayListName(self, playlistid):
100                "Build a playlist file on the fly and return its filename"
101
102                # put the uris of the media files in a temporary file
103                plistfileno, plistname = tempfile.mkstemp(prefix="radiomateplaylist", suffix=".txt", text=True)
104                plistfile = os.fdopen(plistfileno, 'w')
105
106                try:
107                        plist = self.pldao.getById(playlistid)
108                except dao.RadioMateDAOException, e:
109                        raise JukeSlotException(str(e))
110
111                if plist:
112                        for i, mf in enumerate(plist.mediafilelist):
113                                plistfile.write("%s\n" % mf.path)
114                                assert mf.position == i
115                        plistfile.write("\n")
116                else:
117                        plistfile.close()
118                        raise JukeSlotException("Playlist Not Found")
119
120                plistfile.close()
121
122                self.logger.debug(plistname)
123                self.plistnames.append(plistname)
124
125                return plistname
126
127        def getFallBackPlayListName(self):
128                "return a filename for the fallback playlist"
129                try:
130                        return self.getPlayListName(self.fallbackplaylist)
131                except:
132                        return self.getPlayListName(config.GLOBALFALLBACKPLAYLIST)
133
134        def getPlayListLiquidCode(self, playlistid, playlistfilename=None):
135                """return the liquidsoap code for the given playlist.
136                If playlistfilename is given, use it, otherwise call getPlayListName()"""
137
138                try:
139                        plist = self.pldao.getById(playlistid)
140                except dao.RadioMateDAOException, e:
141                        raise JukeSlotException(str(e))
142
143                if not plist:
144                        return "blank(duration=0.1)"
145
146                if plist.random:
147                        pmode = "randomize"
148                else:
149                        pmode = "normal"
150
151                if not playlistfilename:
152                        playlistfilename = self.getPlayListName(playlistid)
153
154                return 'playlist(mode="%s", "%s")' % (pmode, playlistfilename)
155       
156        def getFallBackPlayListLiquidCode(self):
157                try:
158                        pn = self.getPlayListName(self.fallbackplaylist)
159                        return self.getPlayListLiquidCode(self.fallbackplaylist, playlistfilename=pn)
160                except Exception, e:
161                        self.logger.debug("getFallBackPlayListLiquidCode: %s " % str(e))
162                try:
163                        return self.getPlayListLiquidCode(config.GLOBALFALLBACKPLAYLIST)
164                except Exception, e:
165                        self.logger.debug("getFallBackPlayListLiquidCode: %s " % str(e))
166                        return "blank()"
167               
168
169        def gracefulKill(self):
170                "try to terminate, but if it does not work then kill"
171                self.logger.debug("gracefulKill")
172                if self.poll() == None:
173                        self.terminate()
174                        time.sleep(2)
175                if self.poll() == None:
176                        self.kill()
177                        time.sleep(1)
178
179        def __del__(self):
180                self.gracefulKill()
181                try:
182                        for pln in self.plistnames:
183                                os.remove(pln)
184                except:
185                        pass
186
187        def liquidsoapcode(self):
188                "to be overridden by derived classes"
189                return "\n"
190
191
Note: See TracBrowser for help on using the browser.