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