Package flumotion :: Package component :: Package effects :: Package videoscale :: Module videoscale
[hide private]

Source Code for Module flumotion.component.effects.videoscale.videoscale

  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,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  from twisted.internet import reactor 
 19  import gobject 
 20  import gst 
 21   
 22  from flumotion.common import errors, messages, gstreamer 
 23  from flumotion.common.i18n import N_, gettexter 
 24  from flumotion.component import feedcomponent 
 25   
 26   
 27  __version__ = "$Rev$" 
 28  T_ = gettexter() 
 29   
 30   
31 -class VideoscaleBin(gst.Bin):
32 """ 33 I am a GStreamer bin that can scale a video stream from its source pad. 34 """ 35 logCategory = "videoscale" 36 37 __gproperties__ = { 38 'width': (gobject.TYPE_UINT, 'width', 39 'Output width', 40 1, 10000, 100, gobject.PARAM_READWRITE), 41 'height': (gobject.TYPE_UINT, 'height', 42 'Output height', 43 1, 10000, 100, gobject.PARAM_READWRITE), 44 'width-correction': (gobject.TYPE_UINT, 'width correction', 45 'Corrects with to be a multiple of this value', 46 0, 64, 8, gobject.PARAM_READWRITE), 47 'height-correction': (gobject.TYPE_UINT, 'height correction', 48 'Corrects height to be a multiple of this value', 49 0, 64, 0, gobject.PARAM_READWRITE), 50 'is-square': (gobject.TYPE_BOOLEAN, 'PAR 1/1', 51 'Output with PAR 1/1', 52 False, gobject.PARAM_READWRITE), 53 'add-borders': (gobject.TYPE_BOOLEAN, 'Add borders', 54 'Add black borders to keep DAR if needed', 55 False, gobject.PARAM_READWRITE)} 56
57 - def __init__(self, width, height, is_square, add_borders, 58 width_correction=8, height_correction=0):
59 gst.Bin.__init__(self) 60 self._width = width 61 self._height = height 62 self._width_correction = width_correction 63 self._height_correction = height_correction 64 self._is_square = is_square 65 self._add_borders = add_borders 66 67 self._inpar = None # will be set when active 68 self._inwidth = None 69 self._inheight = None 70 71 self._identity = gst.element_factory_make("identity") 72 self._videoscaler = gst.element_factory_make("videoscale") 73 self._capsfilter = gst.element_factory_make("capsfilter") 74 self._videobox = gst.element_factory_make("videobox") 75 self.add(self._identity, self._videoscaler, self._capsfilter, 76 self._videobox) 77 78 self._identity.link(self._videoscaler) 79 self._videoscaler.link(self._capsfilter) 80 self._capsfilter.link(self._videobox) 81 82 # Create source and sink pads 83 self._sinkPad = gst.GhostPad('sink', self._identity.get_pad('sink')) 84 self._srcPad = gst.GhostPad('src', self._videobox.get_pad('src')) 85 self.add_pad(self._sinkPad) 86 self.add_pad(self._srcPad) 87 88 self._configureOutput() 89 90 self._identity.set_property('silent', True) 91 # Add the setcaps function in the sink pad 92 self._sinkPad.set_setcaps_function(self._sinkSetCaps) 93 # Add a callback for caps changes in the videoscaler source pad 94 # to recalculate the scale correction 95 self._videoscaler.get_pad('src').connect( 96 'notify::caps', self._applyScaleCorrection)
97
98 - def _updateFilter(self, blockPad):
99 100 def unlinkAndReplace(pad, blocked): 101 self._videoscaler.set_state(gst.STATE_NULL) 102 self._capsfilter.set_state(gst.STATE_NULL) 103 self._videobox.set_state(gst.STATE_NULL) 104 105 self._configureOutput() 106 107 self._videobox.set_state(gst.STATE_PLAYING) 108 self._videoscaler.set_state(gst.STATE_PLAYING) 109 self._capsfilter.set_state(gst.STATE_PLAYING) 110 111 # unlink the sink and source pad of the old deinterlacer 112 reactor.callFromThread(blockPad.set_blocked, False)
113 114 self._sinkPad.send_event(gstreamer.flumotion_reset_event()) 115 116 # We might be called from the streaming thread 117 self.info("Replaced capsfilter") 118 reactor.callFromThread(blockPad.set_blocked_async, 119 True, unlinkAndReplace)
120
121 - def _configureOutput(self):
122 p = "" 123 if self._is_square: 124 p = ",pixel-aspect-ratio=(fraction)1/1" 125 if self._width: 126 p = "%s,width=(int)%d" % (p, self._width) 127 if self._height: 128 p = "%s,height=(int)%d" % (p, self._height) 129 p = "video/x-raw-yuv%s;video/x-raw-rgb%s" % (p, p) 130 self.info("out:%s" % p) 131 caps = gst.Caps(p) 132 133 self._capsfilter.set_property("caps", caps) 134 if gstreamer.element_has_property(self._videoscaler, 'add-borders'): 135 self._videoscaler.set_property('add-borders', self._add_borders)
136
137 - def _applyScaleCorrection(self, pad, param):
138 # The point of the width and height correction is adding a padding to 139 # the output frame so that its height and width are multiples of a 140 # given value as many encoders require for instance a width that's a 141 # multiple of 8. It's unrelated to the aspect ratio correction to get 142 # to 4:3 or 16:9 143 # FIXME: Add the option to strech or reduce the image instead of 144 # padding with a black line 145 c = pad.get_negotiated_caps() 146 if c is None: 147 return 148 149 width = c[0]['width'] 150 height = c[0]['height'] 151 152 def correctScale(value, correction, isWidth, propIsSet): 153 if correction == 0: 154 return 155 156 name = isWidth and "width" or "height" 157 scale_correction = value % correction 158 159 if scale_correction == 0: 160 return 161 162 if propIsSet: 163 self.warning('%s given, but output is not a ' 164 'multiple of %s!' % (name, correction)) 165 return 166 167 self.info("Correcting %s with %s pixels to be a multiple " 168 "of %s" % (name, scale_correction, correction)) 169 if isWidth: 170 self._videobox.set_property("right", -scale_correction) 171 else: 172 self._videobox.set_property("top", -scale_correction)
173 174 correctScale(width, self._width_correction, True, self._width) 175 correctScale(height, self._height_correction, False, self._height) 176
177 - def _sinkSetCaps(self, pad, caps):
178 self.info("in:%s" % caps.to_string()) 179 if not caps.is_fixed(): 180 return 181 struc = caps[0] 182 if struc.has_field('pixel-aspect-ratio'): 183 self._inpar = struc['pixel-aspect-ratio'] 184 self._inwidth = struc['width'] 185 self._inheight = struc['height'] 186 return True
187
188 - def do_set_property(self, property, value):
189 if property.name == 'width': 190 self._width = value 191 elif property.name == 'height': 192 self._height = value 193 elif property.name == 'width-correction': 194 self._width_correction = value 195 elif property.name == 'height-correction': 196 self._height_correction = value 197 elif property.name == 'add-borders': 198 if not gstreamer.element_has_property(self._videoscaler, 199 'add-borders'): 200 self.warning("Can't add black borders because videoscale\ 201 element doesn't have 'add-borders' property.") 202 self._add_borders = value 203 elif property.name == 'is-square': 204 self._is_square = value 205 else: 206 raise AttributeError('unknown property %s' % property.name)
207
208 - def do_get_property(self, property):
209 if property.name == 'width': 210 return self._width or 0 211 elif property.name == 'height': 212 return self._height or 0 213 elif property.name == 'width-correction': 214 return self._width_correction 215 elif property.name == 'height-correction': 216 return self._height_correction 217 elif property.name == 'add-borders': 218 return self._add_borders 219 elif property.name == 'is-square': 220 return self._is_square or False 221 else: 222 raise AttributeError('unknown property %s' % property.name)
223
224 - def apply(self):
225 peer = self._sinkPad.get_peer() 226 self._updateFilter(peer)
227 228
229 -class Videoscale(feedcomponent.PostProcEffect):
230 """ 231 I am an effect that can be added to any component that has a video scaler 232 component and a way of changing the size and PAR. 233 """ 234 logCategory = "videoscale-effect" 235
236 - def __init__(self, name, component, sourcePad, pipeline, 237 width, height, is_square, add_borders=False, 238 width_correction=8, height_correction=0):
239 """ 240 @param element: the video source element on which the post 241 processing effect will be added 242 @param pipeline: the pipeline of the element 243 """ 244 feedcomponent.PostProcEffect.__init__(self, name, sourcePad, 245 VideoscaleBin(width, height, is_square, add_borders, 246 width_correction, height_correction), pipeline) 247 self.pipeline = pipeline 248 self.component = component 249 250 vt = gstreamer.get_plugin_version('videoscale') 251 if not vt: 252 raise errors.MissingElementError('videoscale') 253 # 'add-borders' property was added in gst-plugins-base 0.10.29, 254 # and it's requiered to respect DAR by adding black borders 255 if not vt > (0, 10, 29, 0): 256 self.component.addMessage( 257 messages.Warning(T_(N_( 258 "The videoscale element correctly " 259 "works with GStreamer base newer than 0.10.29.1." 260 "You should update your version of GStreamer."))))
261
262 - def setUIState(self, state):
263 feedcomponent.Effect.setUIState(self, state) 264 if state: 265 for k in 'width', 'height', 'is-square', 'add-borders': 266 state.addKey('videoscale-%s' % k, 267 self.effectBin.get_property(k))
268
269 - def _setHeight(self, height):
270 self.effectBin.set_property('height', height) 271 self.info('Changing height to %d' % height) 272 self.uiState.set('videoscale-height', height)
273
274 - def effect_setHeight(self, height):
275 self._setHeight(height) 276 if self.effect_getIsSquare(): 277 self._setWidth(height * 278 (self.effectBin._inwidth * self.effectBin._inpar.num) / 279 (self.effectBin._inheight * self.effectBin._inpar.denom)) 280 return height
281
282 - def effect_getHeight(self):
283 return self.effectBin.get_property('height')
284
285 - def _setWidth(self, width):
286 self.effectBin.set_property('width', width) 287 self.info('Changing width to %d' % width) 288 self.uiState.set('videoscale-width', width)
289
290 - def effect_setWidth(self, width):
291 self._setWidth(width) 292 if self.effect_getIsSquare(): 293 self._setHeight(width * 294 (self.effectBin._inheight * self.effectBin._inpar.denom) / 295 (self.effectBin._inwidth * self.effectBin._inpar.num)) 296 return width
297
298 - def effect_getWidth(self):
299 return self.effectBin.get_property('width')
300
301 - def effect_setIsSquare(self, is_square):
302 self.effectBin.set_property('is-square', is_square) 303 self.info('Changing is-square to %r' % is_square) 304 self.uiState.set('videoscale-is-square', is_square) 305 return is_square
306
307 - def effect_getIsSquare(self):
308 return self.effectBin.get_property('is-square')
309
310 - def effect_setAddBorders(self, add_borders):
311 self.effectBin.set_property('add-borders', add_borders) 312 self.info('Changing add-borders to %r' % add_borders) 313 self.uiState.set('videoscale-add-borders', add_borders) 314 return add_borders
315
316 - def effect_getAddBorders(self):
317 return self.effectBin.get_property('add-borders')
318
319 - def effect_setPAR(self, par):
320 self.par = par 321 self.info('Changing PAR to %s' % str(par)) 322 #self.uiState.set('videoscale-par', self.par) 323 return repr(par) # FIXME: why does it complain with tuples returns...
324
325 - def effect_getPAR(self):
326 return self.par
327
328 - def effect_apply(self):
329 self.info("apply videoscale") 330 self.effectBin.apply() 331 return True
332