Package flumotion :: Package wizard :: Module httpstreamersteps
[hide private]

Source Code for Module flumotion.wizard.httpstreamersteps

  1  # -*- Mode: Python -*- 
  2  # vi:si:et:sw=4:sts=4:ts=4 
  3  # 
  4  # Flumotion - a streaming media server 
  5  # Copyright (C) 2004,2005,2006,2007,2008 Fluendo, S.L. (www.fluendo.com). 
  6  # All rights reserved. 
  7   
  8  # This file may be distributed and/or modified under the terms of 
  9  # the GNU General Public License version 2 as published by 
 10  # the Free Software Foundation. 
 11  # This file is distributed without any warranty; without even the implied 
 12  # warranty of merchantability or fitness for a particular purpose. 
 13  # See "LICENSE.GPL" in the source distribution for more information. 
 14   
 15  # Licensees having purchased or holding a valid Flumotion Advanced 
 16  # Streaming Server license may use this file in accordance with the 
 17  # Flumotion Advanced Streaming Server Commercial License Agreement. 
 18  # See "LICENSE.Flumotion" in the source distribution for more information. 
 19   
 20  # Headers in this file shall remain intact. 
 21   
 22  """HTTP wizard integration 
 23   
 24  This provides a step which you can chose: 
 25  - http port 
 26  - bandwidth/client limit 
 27  - mount point (eg, the url it will be accessed as) 
 28  - burst on connect 
 29  - cortado java applet 
 30   
 31  A component of type 'http-streamer' will always be created. 
 32  In addition, if you include the java applet, a 'porter' and 
 33  'http-server' will be included to share the port between the streamer 
 34  and the server and to serve an html file plus the java applet itself. 
 35  On the http-server the applet will be provided with help of a plug. 
 36  """ 
 37   
 38  import gettext 
 39   
 40  import gobject 
 41  from kiwi.utils import gsignal 
 42  import gtk 
 43  from twisted.internet import defer 
 44   
 45  from flumotion.common import errors, log, messages 
 46  from flumotion.common.i18n import N_, gettexter, ngettext 
 47  from flumotion.wizard.models import Consumer 
 48  from flumotion.wizard.basesteps import ConsumerStep 
 49   
 50  __version__ = "$Rev: 7015 $" 
 51  _ = gettext.gettext 
 52  T_ = gettexter() 
 53   
 54   
55 -class HTTPStreamer(Consumer):
56 """I am a model representing the configuration file for a 57 HTTP streamer component. 58 @ivar has_client_limit: If a client limit was set 59 @ivar has_bandwidth_limit: If a bandwidth limit was set 60 @ivar has_cortado: If we should embed cortado 61 @ivar hostname: the hostname this will be streamed on 62 """ 63 componentType = 'http-streamer'
64 - def __init__(self, common):
65 super(HTTPStreamer, self).__init__() 66 self._common = common 67 self.has_cortado = False 68 self.has_plugins = False 69 self.hostname = None
70 71 # Public 72
73 - def getURL(self):
74 """Fetch the url to this stream 75 @returns: the url 76 """ 77 return 'http://%s:%d%s' % ( 78 self.properties.get('hostname', self.hostname), 79 self.getPorter().getPort(), 80 self.properties.mount_point)
81 82 # Component 83
84 - def getProperties(self):
85 properties = super(HTTPStreamer, self).getProperties() 86 if self._common.has_bandwidth_limit: 87 properties.bandwidth_limit = int( 88 self._common.bandwidth_limit * 1e6) 89 90 porter = self.getPorter() 91 properties.porter_socket_path = porter.getSocketPath() 92 properties.porter_username = porter.getUsername() 93 properties.porter_password = porter.getPassword() 94 properties.type = 'slave' 95 properties.burst_on_connect = self._common.burst_on_connect 96 97 return properties
98 99 # Private
100 - def _getPort(self):
101 return self._common.port
102 103
104 -class PlugPluginLine(gtk.VBox):
105 """I am a line in the plug plugin area representing a single plugin. 106 Rendered, I am visible as a checkbutton containing a label with the 107 description of the plugin. 108 Signals:: 109 - enable-changed: emitted when I am enabled/disabled 110 @ivar plugin: plugin instance 111 """ 112 gsignal('enable-changed')
113 - def __init__(self, plugin, description):
114 """ 115 @param plugin: plugin instance 116 @param description: description of the plugin 117 """ 118 gtk.VBox.__init__(self) 119 self.plugin = plugin 120 self.checkbutton = gtk.CheckButton(description) 121 self.checkbutton.connect('toggled', 122 self._on_checkbutton__toggled) 123 self.checkbutton.set_active(True) 124 self.pack_start(self.checkbutton) 125 self.checkbutton.show()
126
127 - def isEnabled(self):
128 """Find out if the plugin is going to be enabled or not 129 @returns: enabled 130 @rtype: bool 131 """ 132 return self.checkbutton.get_active()
133
134 - def _on_checkbutton__toggled(self, checkbutton):
135 self.emit('enable-changed')
136 gobject.type_register(PlugPluginLine) 137 138
139 -class PlugPluginArea(gtk.VBox):
140 """I am plugin area representing all available plugins. I keep track 141 of the plugins and their internal state. You can ask me to add new plugins 142 or get the internal models of the plugins. 143 """
144 - def __init__(self, streamer):
145 self.streamer = streamer 146 gtk.VBox.__init__(self, spacing=6) 147 self._lines = []
148 149 # Public 150
151 - def addPlug(self, plugin, description):
152 """Add a plug, eg a checkbutton with a description such as 153 'Cortado Java applet'. 154 @param plugin: plugin instance 155 @param description: label description 156 """ 157 line = PlugPluginLine(plugin, description) 158 line.connect('enable-changed', self._on_plugline__enable_changed) 159 self._lines.append(line) 160 self.pack_start(line, False, False) 161 line.show() 162 self._updateStreamer()
163
164 - def getServerConsumers(self, audio_producer, video_producer):
165 """Fetch a list of server consumers which are going to be used by all 166 available plugins. 167 @returns: consumers 168 @rtype: a sequence of L{HTTPServer} subclasses 169 """ 170 for plugin in self._getEnabledPlugins(): 171 yield plugin.getConsumer(self.streamer, audio_producer, 172 video_producer)
173 174 # Private 175
176 - def _hasEnabledPlugins(self):
177 for line in self._lines: 178 if line.isEnabled(): 179 return True 180 return False
181
182 - def _getEnabledPlugins(self):
183 for line in self._lines: 184 if line.isEnabled(): 185 yield line.plugin
186
187 - def _updateStreamer(self):
188 self.streamer.has_plugins = self._hasEnabledPlugins()
189 190 # Callbacks 191
192 - def _on_plugline__enable_changed(self, line):
193 self._updateStreamer()
194 195
196 -class HTTPSpecificStep(ConsumerStep):
197 """I am a step of the configuration wizard which allows you 198 to configure a stream to be served over HTTP. 199 """ 200 gladeFile = 'httpstreamer-wizard.glade' 201
202 - def __init__(self, wizard):
203 consumptionStep = wizard.getStep('Consumption') 204 self.model = HTTPStreamer(consumptionStep.getHTTPCommon()) 205 self.model.setPorter(consumptionStep.getHTTPPorter()) 206 ConsumerStep.__init__(self, wizard)
207 208 # ConsumerStep 209
210 - def getConsumerModel(self):
211 return self.model
212
213 - def getComponentType(self):
214 return 'http-streamer'
215
216 - def getServerConsumers(self):
217 return self.plugarea.getServerConsumers( 218 self.wizard.getAudioProducer(), 219 self.wizard.getVideoProducer())
220
221 - def getDefaultMountPath(self):
222 encodingStep = self.wizard.getStep('Encoding') 223 return '/%s-%s/' % (str(encodingStep.getMuxerFormat()), 224 self.getConsumerType(),)
225 226 # WizardStep 227
228 - def setup(self):
229 self.mount_point.data_type = str 230 231 self.plugarea = PlugPluginArea(self.model) 232 self.main_vbox.pack_start(self.plugarea, False, False) 233 self.plugarea.show() 234 235 self._populatePlugins() 236 237 self.model.properties.mount_point = self.getDefaultMountPath() 238 self.add_proxy(self.model.properties, ['mount_point'])
239
240 - def activated(self):
241 self._checkElements() 242 self._verify()
243
244 - def workerChanged(self, worker):
245 self.model.worker = worker 246 self._checkElements()
247 248 # Private 249
250 - def _populatePlugins(self):
251 def gotEntries(entries): 252 log.debug('httpwizard', 'got %r' % (entries,)) 253 for entry in entries: 254 if not self._canAddPlug(entry): 255 continue 256 def response(factory, entry): 257 # FIXME: verify that factory implements IHTTPConsumerPlugin 258 plugin = factory(self.wizard) 259 if hasattr(plugin, 'workerChanged'): 260 d = plugin.workerChanged(self.worker) 261 def cb(found, plugin, entry): 262 if found: 263 self._addPlug( 264 plugin, N_(entry.description))
265 d.addCallback(cb, plugin, entry) 266 else: 267 self._addPlug(plugin, N_(entry.description))
268 d = self.wizard.getWizardPlugEntry(entry.componentType) 269 d.addCallback(response, entry) 270 271 d = self.wizard.getWizardEntries(wizardTypes=['http-consumer']) 272 d.addCallbacks(gotEntries) 273
274 - def _canAddPlug(self, entry):
275 # This function filters out entries which are 276 # not matching the accepted media types of the entry 277 muxerTypes = [] 278 audioTypes = [] 279 videoTypes = [] 280 for mediaType in entry.getAcceptedMediaTypes(): 281 kind, name = mediaType.split(':', 1) 282 if kind == 'muxer': 283 muxerTypes.append(name) 284 elif kind == 'video': 285 videoTypes.append(name) 286 elif kind == 'audio': 287 audioTypes.append(name) 288 else: 289 raise AssertionError 290 291 encoding_step = self.wizard.getStep('Encoding') 292 if encoding_step.getMuxerFormat() not in muxerTypes: 293 return False 294 295 audioFormat = encoding_step.getAudioFormat() 296 videoFormat = encoding_step.getVideoFormat() 297 if ((audioFormat and audioFormat not in audioTypes) or 298 (videoFormat and videoFormat not in videoTypes)): 299 return False 300 301 return True
302
303 - def _addPlug(self, plugin, description):
304 self.plugarea.addPlug(plugin, description)
305
306 - def _checkElements(self):
307 self.wizard.waitForTask('http streamer check') 308 309 def importError(failure): 310 print 'FIXME: trap', failure, 'in .../httpstreamer/wizard_gtk.py' 311 self.info('could not import twisted-web') 312 message = messages.Warning(T_(N_( 313 "Worker '%s' cannot import module '%s'."), 314 self.worker, 'twisted.web')) 315 message.add(T_(N_("\nThis module is part of the '%s'."), 316 'Twisted Project')) 317 message.add(T_(N_("\nThe project's homepage is %s"), 318 'http://www.twistedmatrix.com/')) 319 message.id = 'module-twisted-web' 320 self.wizard.add_msg(message) 321 self.wizard.taskFinished(True)
322 323 def finished(hostname): 324 self.model.hostname = hostname 325 self.wizard.taskFinished() 326 327 def checkWorkerHostname(unused): 328 d = self.wizard.runInWorker( 329 self.worker, 'flumotion.worker.checks.http', 330 'runHTTPStreamerChecks') 331 d.addCallback(finished) 332 333 def checkElements(elements): 334 if elements: 335 f = ngettext("Worker '%s' is missing GStreamer element '%s'.", 336 "Worker '%s' is missing GStreamer elements '%s'.", 337 len(elements)) 338 message = messages.Warning( 339 T_(f, self.worker, "', '".join(elements)), id='httpstreamer') 340 self.wizard.add_msg(message) 341 self.wizard.taskFinished(True) 342 return defer.fail(errors.FlumotionError('missing multifdsink element')) 343 344 self.wizard.clear_msg('httpstreamer') 345 346 # now check import 347 d = self.wizard.checkImport(self.worker, 'twisted.web') 348 d.addCallback(checkWorkerHostname) 349 d.addErrback(importError) 350 351 # first check elements 352 d = self.wizard.requireElements(self.worker, 'multifdsink') 353 d.addCallback(checkElements) 354 355 # requireElements calls checkElements which unconditionally 356 # unblocks the next call. Work around that behavior here. 357 d.addErrback(lambda unused: self.wizard.taskFinished(True)) 358
359 - def _verify(self):
360 self._update_blocked()
361
362 - def _update_blocked(self):
363 # FIXME: This should be updated and only called when all pending 364 # tasks are done. 365 self.wizard.blockNext( 366 self.wizard.pendingTask() or self.mount_point.get_text() == '')
367 368 # Callbacks 369
370 - def on_mount_point_changed(self, entry):
371 self._verify() 372 self.wizard.blockNext(self.model.has_cortado and 373 entry.get_text() == self.getDefaultMountPath())
374 375
376 -class HTTPBothStep(HTTPSpecificStep):
377 name = 'HTTPStreamerBoth' 378 title = _('HTTP Streamer (audio and video)') 379 sidebarName = _('HTTP audio/video') 380 381 # ConsumerStep 382
383 - def getConsumerType(self):
384 return 'audio-video'
385 386
387 -class HTTPAudioStep(HTTPSpecificStep):
388 name = 'HTTPStreamerAudio' 389 title = _('HTTP Streamer (audio only)') 390 sidebarName = _('HTTP audio') 391 392 # ConsumerStep 393
394 - def getConsumerType(self):
395 return 'audio'
396 397
398 -class HTTPVideoStep(HTTPSpecificStep):
399 name = 'HTTPStreamerVideo' 400 title = _('HTTP Streamer (video only)') 401 sidebarName = _('HTTP video') 402 403 # ConsumerStep 404
405 - def getConsumerType(self):
406 return 'video'
407