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