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

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