Package flumotion :: Package admin :: Package assistant :: Module save
[hide private]

Source Code for Module flumotion.admin.assistant.save

  1  # -*- Mode: Python; test-case-name: flumotion.test.test_wizard -*- 
  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  import gettext 
 23  import re 
 24   
 25  from flumotion.admin.assistant.configurationwriter import ConfigurationWriter 
 26  from flumotion.admin.assistant.models import Muxer, AudioProducer, \ 
 27       VideoProducer, AudioEncoder, VideoEncoder 
 28   
 29  _ = gettext.gettext 
 30  __version__ = "$Rev: 7777 $" 
 31   
 32   
33 -class AssistantSaver(object):
34 """I am used to link components together and generate XML for them. 35 To use me, add some components by some of the methods and then call 36 my getXML() method to get the xml configuration. 37 """ 38
39 - def __init__(self):
40 self._existingComponentNames = [] 41 self._flowComponents = [] 42 self._atmosphereComponents = [] 43 self._muxers = {} 44 self._flowName = None 45 self._audioProducer = None 46 self._videoProducer = None 47 self._audioEncoder = None 48 self._videoEncoder = None 49 self._videoOverlay = None 50 self._useCCLicense = False 51 self._muxerType = None 52 self._muxerWorker = None
53 54 # Public API 55
56 - def setFlowName(self, flowName):
57 """Sets the name of the flow we're saving. 58 @param flowName: 59 @type flowName: string 60 """ 61 self._flowName = flowName
62
63 - def setAudioProducer(self, audioProducer):
64 """Attach a audio producer for this flow 65 @param audioProducer: audio producer 66 @type audioProducer: L{AudioProducer} subclass or None 67 """ 68 if (audioProducer is not None and 69 not isinstance(audioProducer, AudioProducer)): 70 raise TypeError( 71 "audioProducer must be a AudioProducer subclass, not %r" % ( 72 audioProducer, )) 73 self._audioProducer = audioProducer
74
75 - def setVideoProducer(self, videoProducer):
76 """Attach a video producer for this flow 77 @param videoProducer: video producer 78 @type videoProducer: L{VideoProducer} subclass or None 79 """ 80 if (videoProducer is not None and 81 not isinstance(videoProducer, VideoProducer)): 82 raise TypeError( 83 "videoProducer must be a VideoProducer subclass, not %r" % ( 84 videoProducer, )) 85 self._videoProducer = videoProducer
86
87 - def setVideoOverlay(self, videoOverlay):
88 if not self._videoProducer: 89 raise ValueError( 90 "You can't add a video overlay component without " 91 "first setting a video producer") 92 self._videoOverlay = videoOverlay
93
94 - def setAudioEncoder(self, audioEncoder):
95 """Attach a audio encoder for this flow 96 @param audioEncoder: audio encoder 97 @type audioEncoder: L{AudioEncoder} subclass or None 98 """ 99 if (audioEncoder is not None and 100 not isinstance(audioEncoder, AudioEncoder)): 101 raise TypeError( 102 "audioEncoder must be a AudioEncoder subclass, not %r" % ( 103 audioEncoder, )) 104 self._audioEncoder = audioEncoder
105
106 - def setVideoEncoder(self, videoEncoder):
107 """Attach a video encoder for this flow 108 @param videoEncoder: video encoder 109 @type videoEncoder: L{VideoEncoder} subclass or None 110 """ 111 if (videoEncoder is not None and 112 not isinstance(videoEncoder, VideoEncoder)): 113 raise TypeError( 114 "videoEncoder must be a VideoEncoder subclass, not %r" % ( 115 videoEncoder, )) 116 self._videoEncoder = videoEncoder
117
118 - def setMuxer(self, muxerType, muxerWorker):
119 """Adds the necessary state to be able to create a muxer 120 for this flow. 121 @param muxerType: 122 @type muxerType: string 123 @param muxerWorker: name of the worker 124 @type muxerWorker: string 125 """ 126 self._muxerType = muxerType 127 self._muxerWorker = muxerWorker
128
129 - def addServerConsumer(self, server, consumerType):
130 """Add a server consumer. Currently limited a to http-server 131 server consumers 132 @param server: server consumer 133 @type server: 134 @param consumerType: the type of the consumer, one of 135 audio/video/audio-video 136 @type consumerType: string 137 """ 138 server.name = 'http-server-%s' % (consumerType, ) 139 self._atmosphereComponents.append(server)
140
141 - def addPorter(self, porter, consumerType):
142 """Add a porter 143 @param porter: porter 144 @type porter: 145 @param consumerType: the type of the consumer, one of 146 audio/video/audio-video 147 @type consumerType: string 148 """ 149 porter.name = 'porter-%s' % (consumerType, ) 150 self._atmosphereComponents.append(porter)
151
152 - def addConsumer(self, consumer, consumerType):
153 """Add a consumer 154 @param consumer: consumer 155 @type consumer: 156 @param consumerType: the type of the consumer, one of 157 audio/video/audio-video 158 @type consumerType: string 159 """ 160 if consumer.componentType == 'http-streamer': 161 prefix = 'http' 162 elif consumer.componentType == 'disk-consumer': 163 prefix = 'disk' 164 elif consumer.componentType == 'shout2-consumer': 165 prefix = 'shout2' 166 else: 167 raise AssertionError("unknown component: %s" % ( 168 consumer.componentType)) 169 170 # [disk,http,shout2]-[audio,video,audio-video] 171 consumer.name = prefix + '-' + consumerType 172 173 self._getMuxer(consumerType).link(consumer) 174 self._flowComponents.append(consumer)
175
176 - def setUseCCLicense(self, useCCLicense):
177 """Sets if we should use a Creative Common license on 178 the created flow. This will overlay an image if we do 179 video streaming. 180 @param useCCLicense: if we should use a CC license 181 @type useCCLicense: bool 182 """ 183 self._useCCLicense = useCCLicense
184
185 - def getXML(self):
186 """Creates an XML configuration of the state set 187 @returns: the xml configuration 188 @rtype: string 189 """ 190 self._handleProducers() 191 self._handleMuxers() 192 # Naming conflicts can only be solved after the rest is done, 193 # since some components might get removed 194 self._resolveNameConflicts() 195 self._validateComponents() 196 197 writer = ConfigurationWriter(self._flowName, 198 self._flowComponents, 199 self._atmosphereComponents) 200 xml = writer.getXML() 201 return xml
202
203 - def setExistingComponentNames(self, componentNames):
204 """Tells the saver about the existing components available, so 205 we can resolve naming conflicts before fetching the configuration xml 206 @param componentNames: existing component names 207 @type componentNames: list of strings 208 """ 209 self._existingComponentNames = componentNames
210
211 - def getFlowComponents(self):
212 """Gets the flow components of the save instance 213 @returns: the flow components 214 @rtype: list of components 215 """ 216 return self._flowComponents
217
218 - def getAtmosphereComponents(self):
219 """Gets the atmosphere components of the save instance 220 @returns: the atmosphere components 221 @rtype: list of components 222 """ 223 return self._atmosphereComponents
224 225 # Private API 226
227 - def _getAllComponents(self):
228 return self._atmosphereComponents + self._flowComponents
229
230 - def _getMuxer(self, name):
231 if name in self._muxers: 232 muxer = self._muxers[name] 233 else: 234 muxer = Muxer() 235 muxer.name = 'muxer-' + name 236 muxer.componentType = self._muxerType 237 muxer.worker = self._muxerWorker 238 self._muxers[name] = muxer 239 return muxer
240
241 - def _handleProducers(self):
246
247 - def _handleAudioProducer(self):
248 if not self._audioProducer: 249 return 250 251 if not self._audioProducer.name: 252 self._audioProducer.name = 'producer-audio' 253 254 self._flowComponents.append(self._audioProducer) 255 256 if self._audioEncoder is None: 257 raise ValueError("You need to set an audio encoder") 258 259 self._audioEncoder.name = 'encoder-audio' 260 self._flowComponents.append(self._audioEncoder) 261 262 self._audioProducer.link(self._audioEncoder)
263
264 - def _handleVideoProducer(self):
265 if not self._videoProducer: 266 return 267 268 if not self._videoProducer.name: 269 self._videoProducer.name = 'producer-video' 270 271 self._flowComponents.append(self._videoProducer) 272 273 if self._videoEncoder is None: 274 raise ValueError("You need to set a video encoder") 275 276 self._videoEncoder.name = 'encoder-video' 277 self._flowComponents.append(self._videoEncoder) 278 279 self._videoProducer.link(self._videoEncoder)
280
281 - def _handleVideoOverlay(self):
282 if not self._videoOverlay: 283 return 284 285 self._videoProducer.unlink(self._videoEncoder) 286 287 self._videoProducer.link(self._videoOverlay) 288 self._videoOverlay.link(self._videoEncoder) 289 self._flowComponents.append(self._videoOverlay) 290 291 self._videoOverlay.name = 'overlay-video' 292 293 if not self._videoOverlay.show_logo: 294 return 295 296 # FIXME: This should probably not be done here. 297 self._videoOverlay.properties.fluendo_logo = True 298 if self._muxerType == 'ogg-muxer': 299 self._videoOverlay.properties.xiph_logo = True 300 301 if self._useCCLicense: 302 self._videoOverlay.properties.cc_logo = True
303
304 - def _handleSameProducers(self):
305 # In the case video producer and audio producer is the same 306 # component and on the same worker, remove the audio producer and 307 # rename the video producer. 308 video = self._videoProducer 309 audio = self._audioProducer 310 if (video is not None and 311 audio is not None and 312 video.componentType == audio.componentType and 313 video.worker == audio.worker): 314 self._flowComponents.remove(self._audioProducer) 315 if not audio.exists: 316 self._audioProducer.name = 'producer-audio-video' 317 if not video.exists: 318 self._videoProducer.name = 'producer-audio-video' 319 self._audioProducer = self._videoProducer
320
321 - def _handleMuxers(self):
322 for muxerName, components in [('audio', [self._audioEncoder]), 323 ('video', [self._videoEncoder]), 324 ('audio-video', [self._audioEncoder, 325 self._videoEncoder])]: 326 muxer = self._getMuxer(muxerName) 327 if muxer.feeders: 328 self._flowComponents.append(muxer) 329 for component in components: 330 component.link(muxer)
331
332 - def _resolveNameConflicts(self):
335
336 - def _resolveComponentName(self, component):
337 # If the component already exists, do not suggest a new name, 338 # since we want to link to it 339 if component.exists: 340 return 341 name = component.name 342 while name in self._existingComponentNames: 343 name = self._suggestName(name) 344 345 component.name = name 346 self._existingComponentNames.append(name)
347
348 - def _suggestName(self, suggestedName):
349 # Resolve naming conflicts, using a simple algorithm 350 # First, find all the trailing digits, for instance in 351 # 'audio-producer42' -> '42' 352 pattern = re.compile('(\d*$)') 353 match = pattern.search(suggestedName) 354 trailingDigit = match.group() 355 356 # Now if we had a digit in the end, convert it to 357 # a number and increase it by one and remove the trailing 358 # digits the existing component name 359 if trailingDigit: 360 digit = int(trailingDigit) + 1 361 suggestedName = suggestedName[:-len(trailingDigit)] 362 # No number in the end, use 2 the first one so we end up 363 # with 'audio-producer' and 'audio-producer2' in case of 364 # a simple conflict 365 else: 366 digit = 2 367 return suggestedName + str(digit)
368
369 - def _validateComponents(self):
370 for component in self._getAllComponents(): 371 # There's no need to validate existing components, 372 # that allows us to provide 'fake' existing components, 373 # which simplifies sending incremental configuration snippets 374 # from the admin client 375 if component.exists: 376 continue 377 component.validate()
378