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