1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import time
23
24 import gst
25 from twisted.internet import defer, reactor
26
27 from flumotion.common import messages, fxml, gstreamer, documentation
28 from flumotion.common.i18n import N_, gettexter
29 from flumotion.component import feedcomponent
30 from flumotion.component.base import watcher
31
32 import smartscale
33 import singledecodebin
34 import playlistparser
35
36 __version__ = "$Rev: 7974 $"
37 T_ = gettexter()
38
39
41 """
42 Return a string in local time from a gstreamer timestamp value
43 """
44 return time.ctime(ts/gst.SECOND)
45
46
48 src = gst.element_factory_make('videotestsrc')
49 if pattern:
50 src.props.pattern = pattern
51 else:
52
53 src.props.pattern = 2
54 gnlsrc = gst.element_factory_make('gnlsource', name)
55 gnlsrc.props.start = start
56 gnlsrc.props.duration = duration
57 gnlsrc.props.media_start = 0
58 gnlsrc.props.media_duration = duration
59 gnlsrc.props.priority = priority
60 gnlsrc.add(src)
61
62 return gnlsrc
63
64
66 src = gst.element_factory_make('audiotestsrc')
67 if wave:
68 src.props.wave = wave
69 else:
70
71 src.props.wave = 4
72 gnlsrc = gst.element_factory_make('gnlsource', name)
73 gnlsrc.props.start = start
74 gnlsrc.props.duration = duration
75 gnlsrc.props.media_start = 0
76 gnlsrc.props.media_duration = duration
77 gnlsrc.props.priority = priority
78 gnlsrc.add(src)
79
80 return gnlsrc
81
82
83 -def file_gnl_src(name, uri, caps, start, duration, offset, priority):
95
96
104
105
107 logCategory = 'playlist-prod'
108 componentMediumClass = PlaylistProducerMedium
109
111 self.basetime = -1
112
113 self._hasAudio = True
114 self._hasVideo = True
115
116
117 self.videocomp = None
118 self.audiocomp = None
119
120 self.videocaps = gst.Caps("video/x-raw-yuv;video/x-raw-rgb")
121 self.audiocaps = gst.Caps("audio/x-raw-int;audio/x-raw-float")
122
123 self._vsrcs = {}
124 self._asrcs = {}
125
126 self.uiState.addListKey("playlist")
127
129 audiorate = gst.element_factory_make("audiorate")
130 audioconvert = gst.element_factory_make('audioconvert')
131 resampler = 'audioresample'
132 if gstreamer.element_factory_exists('legacyresample'):
133 resampler = 'legacyresample'
134 audioresample = gst.element_factory_make(resampler)
135 outcaps = gst.Caps(
136 "audio/x-raw-int,channels=%d,rate=%d,width=16,depth=16" %
137 (self._channels, self._samplerate))
138
139 capsfilter = gst.element_factory_make("capsfilter")
140 capsfilter.props.caps = outcaps
141
142 pipeline.add(audiorate, audioconvert, audioresample, capsfilter)
143 src.link(audioconvert)
144 audioconvert.link(audioresample)
145 audioresample.link(audiorate)
146 audiorate.link(capsfilter)
147
148 return capsfilter.get_pad('src')
149
151 outcaps = gst.Caps(
152 "video/x-raw-yuv,width=%d,height=%d,framerate=%d/%d,"
153 "pixel-aspect-ratio=1/1" %
154 (self._width, self._height, self._framerate[0],
155 self._framerate[1]))
156
157 cspace = gst.element_factory_make("ffmpegcolorspace")
158 scaler = smartscale.SmartVideoScale()
159 scaler.set_caps(outcaps)
160 videorate = gst.element_factory_make("videorate")
161 capsfilter = gst.element_factory_make("capsfilter")
162 capsfilter.props.caps = outcaps
163
164 pipeline.add(cspace, scaler, videorate, capsfilter)
165
166 src.link(cspace)
167 cspace.link(scaler)
168 scaler.link(videorate)
169 videorate.link(capsfilter)
170 return capsfilter.get_pad('src')
171
173 pipeline = gst.Pipeline()
174
175 for mediatype in ['audio', 'video']:
176 if (mediatype == 'audio' and not self._hasAudio) or (
177 mediatype == 'video' and not self._hasVideo):
178 continue
179
180
181
182
183
184
185
186 composition = gst.element_factory_make("gnlcomposition",
187 mediatype + "-composition")
188
189 segmentidentity = gst.element_factory_make("identity")
190 segmentidentity.set_property("single-segment", True)
191 segmentidentity.set_property("silent", True)
192 syncidentity = gst.element_factory_make("identity")
193 syncidentity.set_property("silent", True)
194 syncidentity.set_property("sync", True)
195
196 pipeline.add(composition, segmentidentity, syncidentity)
197
198 def _padAddedCb(element, pad, target):
199 self.debug("Pad added, linking")
200 pad.link(target)
201 composition.connect('pad-added', _padAddedCb,
202 syncidentity.get_pad("sink"))
203 syncidentity.link(segmentidentity)
204
205 if mediatype == 'audio':
206 self.audiocomp = composition
207 srcpad = self._buildAudioPipeline(pipeline, segmentidentity)
208 else:
209 self.videocomp = composition
210 srcpad = self._buildVideoPipeline(pipeline, segmentidentity)
211
212 feedername = self.feeders[mediatype].elementName
213
214 feederchunk = \
215 feedcomponent.ParseLaunchComponent.FEEDER_TMPL \
216 % {'name': feedername}
217
218 binstr = "bin.("+feederchunk+" )"
219 self.debug("Parse for media composition is %s", binstr)
220
221 bin = gst.parse_launch(binstr)
222 pad = bin.find_unconnected_pad(gst.PAD_SINK)
223 ghostpad = gst.GhostPad(mediatype + "-feederpad", pad)
224 bin.add_pad(ghostpad)
225
226 pipeline.add(bin)
227 srcpad.link(ghostpad)
228
229 return pipeline
230
232 if self._hasVideo:
233 vsrc = videotest_gnl_src("videotestdefault", 0, 2**63 - 1,
234 2**31 - 1, properties.get('video-pattern', None))
235 self.videocomp.add(vsrc)
236
237 if self._hasAudio:
238 asrc = audiotest_gnl_src("videotestdefault", 0, 2**63 - 1,
239 2**31 - 1, properties.get('audio-wave', None))
240 self.audiocomp.add(asrc)
241
243 raise NotImplementedError("Playlist producer doesn't support slaving")
244
246
247
248 if self.medium:
249 ip = self.medium.getIP()
250 else:
251 ip = "127.0.0.1"
252
253 clock = self.pipeline.get_clock()
254 self.clock_provider = gst.NetTimeProvider(clock, None, port)
255
256 self.clock_provider.set_property('active', False)
257
258 self._master_clock_info = (ip, port, self.basetime)
259
260 return defer.succeed(self._master_clock_info)
261
263 return self._master_clock_info
264
266
267 clock = gst.SystemClock()
268
269
270 self.basetime = clock.get_time()
271
272
273 pipeline.use_clock(clock)
274
275 pipeline.set_new_stream_time(gst.CLOCK_TIME_NONE)
276
277 self.debug("Setting basetime of %d", self.basetime)
278 pipeline.set_base_time(self.basetime)
279
284
286 return self.pipeline.query_position(gst.FORMAT_TIME)[0]
287
289 """
290 Schedule a given playlist item in our playback compositions.
291 """
292 start = item.timestamp - self.basetime
293 self.debug("Starting item %s at %d seconds from start: %s", item.uri,
294 start/gst.SECOND, _tsToString(item.timestamp))
295
296
297
298
299
300
301
302 now = self.getCurrentPosition()
303 neareststarttime = now + 5 * gst.SECOND
304
305 if start < neareststarttime:
306 if start + item.duration < neareststarttime:
307 self.debug("Item too late; skipping entirely")
308 return False
309 else:
310 change = neareststarttime - start
311 self.debug("Starting item with offset %d", change)
312 item.duration -= change
313 item.offset += change
314 start = neareststarttime
315
316 end = start + item.duration
317 timeuntilend = end - now
318
319
320 reactor.callLater(timeuntilend/gst.SECOND + 5,
321 self.unscheduleItem, item)
322
323 if self._hasVideo and item.hasVideo:
324 self.debug("Adding video source with start %d, duration %d, "
325 "offset %d", start, item.duration, item.offset)
326 vsrc = file_gnl_src(None, item.uri, self.videocaps,
327 start, item.duration, item.offset, 0)
328 self.videocomp.add(vsrc)
329 self._vsrcs[item] = vsrc
330 if self._hasAudio and item.hasAudio:
331 self.debug("Adding audio source with start %d, duration %d, "
332 "offset %d", start, item.duration, item.offset)
333 asrc = file_gnl_src(None, item.uri, self.audiocaps,
334 start, item.duration, item.offset, 0)
335 self.audiocomp.add(asrc)
336 self._asrcs[item] = asrc
337 self.debug("Done scheduling: start at %s, end at %s",
338 _tsToString(start + self.basetime),
339 _tsToString(start + self.basetime + item.duration))
340
341 self.uiState.append("playlist", (item.timestamp,
342 item.uri,
343 item.duration,
344 item.offset,
345 item.hasAudio,
346 item.hasVideo))
347 return True
348
350 self.debug("Unscheduling item at uri %s", item.uri)
351 if self._hasVideo and item.hasVideo and item in self._vsrcs:
352 vsrc = self._vsrcs.pop(item)
353 self.videocomp.remove(vsrc)
354 vsrc.set_state(gst.STATE_NULL)
355 if self._hasAudio and item.hasAudio and item in self._asrcs:
356 asrc = self._asrcs.pop(item)
357 self.audiocomp.remove(asrc)
358 asrc.set_state(gst.STATE_NULL)
359 for entry in self.uiState.get("playlist"):
360 if entry[0] == item.timestamp:
361 self.uiState.remove("playlist", entry)
362
364 if self._hasVideo and item.hasVideo:
365 vsrc = self._vsrcs[item]
366 vsrc.props.start = item.timestamp - self.basetime
367 vsrc.props.duration = item.duration
368 vsrc.props.media_duration = item.duration
369 if self._hasAudio and item.hasAudio:
370 asrc = self._asrcs[item]
371 asrc.props.start = item.timestamp - self.basetime
372 asrc.props.duration = item.duration
373 asrc.props.media_duration = item.duration
374
377
379 props = self.config['properties']
380
381 self._playlistfile = props.get('playlist', None)
382 self._playlistdirectory = props.get('playlist-directory', None)
383 self._baseDirectory = props.get('base-directory', None)
384
385 self._width = props.get('width', 320)
386 self._height = props.get('height', 240)
387 self._framerate = props.get('framerate', (15, 1))
388 self._samplerate = props.get('samplerate', 44100)
389 self._channels = props.get('channels', 2)
390
391 self._hasAudio = props.get('audio', True)
392 self._hasVideo = props.get('video', True)
393
394 pipeline = self._buildPipeline()
395 self._setupClock(pipeline)
396
397 self._createDefaultSources(props)
398
399 return pipeline
400
416
424
426
427 msgid = ("playlist-parse-error", file)
428 for m in self.state.get('messages'):
429 if m.id == msgid:
430 self.state.remove('messages', m)
431
454
466
467 for el in ["gnlsource", "gnlcomposition"]:
468 check_gnl(el)
469
486