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$"
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.system_clock_obtain()
268 clock.set_property('clock-type', 'realtime')
269
270
271 self.basetime = clock.get_time()
272
273
274 pipeline.use_clock(clock)
275
276 pipeline.set_new_stream_time(gst.CLOCK_TIME_NONE)
277
278 self.debug("Setting basetime of %d", self.basetime)
279 pipeline.set_base_time(self.basetime)
280
285
287 return self.pipeline.query_position(gst.FORMAT_TIME)[0]
288
290 """
291 Schedule a given playlist item in our playback compositions.
292 """
293 start = item.timestamp - self.basetime
294 self.debug("Starting item %s at %d seconds from start: %s", item.uri,
295 start/gst.SECOND, _tsToString(item.timestamp))
296
297
298
299
300
301
302
303 now = self.getCurrentPosition()
304 neareststarttime = now + 5 * gst.SECOND
305
306 if start < neareststarttime:
307 if start + item.duration < neareststarttime:
308 self.debug("Item too late; skipping entirely")
309 return False
310 else:
311 change = neareststarttime - start
312 self.debug("Starting item with offset %d", change)
313 item.duration -= change
314 item.offset += change
315 start = neareststarttime
316
317 end = start + item.duration
318 timeuntilend = end - now
319
320
321 reactor.callLater(timeuntilend/gst.SECOND + 5,
322 self.unscheduleItem, item)
323
324 if self._hasVideo and item.hasVideo:
325 self.debug("Adding video source with start %d, duration %d, "
326 "offset %d", start, item.duration, item.offset)
327 vsrc = file_gnl_src(None, item.uri, self.videocaps,
328 start, item.duration, item.offset, 0)
329 self.videocomp.add(vsrc)
330 self._vsrcs[item] = vsrc
331 if self._hasAudio and item.hasAudio:
332 self.debug("Adding audio source with start %d, duration %d, "
333 "offset %d", start, item.duration, item.offset)
334 asrc = file_gnl_src(None, item.uri, self.audiocaps,
335 start, item.duration, item.offset, 0)
336 self.audiocomp.add(asrc)
337 self._asrcs[item] = asrc
338 self.debug("Done scheduling: start at %s, end at %s",
339 _tsToString(start + self.basetime),
340 _tsToString(start + self.basetime + item.duration))
341
342 self.uiState.append("playlist", (item.timestamp,
343 item.uri,
344 item.duration,
345 item.offset,
346 item.hasAudio,
347 item.hasVideo))
348 return True
349
351 self.debug("Unscheduling item at uri %s", item.uri)
352 if self._hasVideo and item.hasVideo and item in self._vsrcs:
353 vsrc = self._vsrcs.pop(item)
354 self.videocomp.remove(vsrc)
355 vsrc.set_state(gst.STATE_NULL)
356 if self._hasAudio and item.hasAudio and item in self._asrcs:
357 asrc = self._asrcs.pop(item)
358 self.audiocomp.remove(asrc)
359 asrc.set_state(gst.STATE_NULL)
360 for entry in self.uiState.get("playlist"):
361 if entry[0] == item.timestamp:
362 self.uiState.remove("playlist", entry)
363
365 if self._hasVideo and item.hasVideo:
366 vsrc = self._vsrcs[item]
367 vsrc.props.start = item.timestamp - self.basetime
368 vsrc.props.duration = item.duration
369 vsrc.props.media_duration = item.duration
370 if self._hasAudio and item.hasAudio:
371 asrc = self._asrcs[item]
372 asrc.props.start = item.timestamp - self.basetime
373 asrc.props.duration = item.duration
374 asrc.props.media_duration = item.duration
375
378
380 props = self.config['properties']
381
382 self._playlistfile = props.get('playlist', None)
383 self._playlistdirectory = props.get('playlist-directory', None)
384 self._baseDirectory = props.get('base-directory', None)
385
386 self._width = props.get('width', 320)
387 self._height = props.get('height', 240)
388 self._framerate = props.get('framerate', (15, 1))
389 self._samplerate = props.get('samplerate', 44100)
390 self._channels = props.get('channels', 2)
391
392 self._hasAudio = props.get('audio', True)
393 self._hasVideo = props.get('video', True)
394
395 pipeline = self._buildPipeline()
396 self._setupClock(pipeline)
397
398 self._createDefaultSources(props)
399
400 return pipeline
401
417
425
427
428 msgid = ("playlist-parse-error", file)
429 for m in self.state.get('messages'):
430 if m.id == msgid:
431 self.state.remove('messages', m)
432
455
467
468 for el in ["gnlsource", "gnlcomposition"]:
469 check_gnl(el)
470
487