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 import gobject
26 from twisted.internet import defer, reactor
27
28 from flumotion.common import errors, messages, log, fxml
29 from flumotion.common.i18n import N_, gettexter
30 from flumotion.component import feedcomponent
31 from flumotion.component.base import watcher
32
33 import smartscale
34 import singledecodebin
35 import playlistparser
36
37 __version__ = "$Rev: 6695 $"
38 T_ = gettexter()
39
40
42 """
43 Return a string in local time from a gstreamer timestamp value
44 """
45 return time.ctime(ts/gst.SECOND)
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
65 src = gst.element_factory_make('audiotestsrc')
66 if wave:
67 src.props.wave = wave
68 else:
69
70 src.props.wave = 4
71 gnlsrc = gst.element_factory_make('gnlsource', name)
72 gnlsrc.props.start = start
73 gnlsrc.props.duration = duration
74 gnlsrc.props.media_start = 0
75 gnlsrc.props.media_duration = duration
76 gnlsrc.props.priority = priority
77 gnlsrc.add(src)
78
79 return gnlsrc
80
81 -def file_gnl_src(name, uri, caps, start, duration, offset, priority):
92
99
101 logCategory = 'playlist-prod'
102 componentMediumClass = PlaylistProducerMedium
103
105 self.basetime = -1
106
107 self._hasAudio = True
108 self._hasVideo = True
109
110
111 self.videocomp = None
112 self.audiocomp = None
113
114 self.videocaps = gst.Caps("video/x-raw-yuv;video/x-raw-rgb")
115 self.audiocaps = gst.Caps("audio/x-raw-int;audio/x-raw-float")
116
117 self._vsrcs = {}
118 self._asrcs = {}
119
121 audiorate = gst.element_factory_make("audiorate")
122 audioconvert = gst.element_factory_make('audioconvert')
123 audioresample = gst.element_factory_make('audioresample')
124 outcaps = gst.Caps(
125 "audio/x-raw-int,channels=%d,rate=%d,width=16,depth=16" %
126 (self._channels, self._samplerate))
127
128 capsfilter = gst.element_factory_make("capsfilter")
129 capsfilter.props.caps = outcaps
130
131 pipeline.add(audiorate, audioconvert, audioresample, capsfilter)
132 src.link(audioconvert)
133 audioconvert.link(audioresample)
134 audioresample.link(audiorate)
135 audiorate.link(capsfilter)
136
137 return capsfilter.get_pad('src')
138
140 outcaps = gst.Caps(
141 "video/x-raw-yuv,width=%d,height=%d,framerate=%d/%d,"
142 "pixel-aspect-ratio=1/1" %
143 (self._width, self._height, self._framerate[0],
144 self._framerate[1]))
145
146 cspace = gst.element_factory_make("ffmpegcolorspace")
147 scaler = smartscale.SmartVideoScale()
148 scaler.set_caps(outcaps)
149 videorate = gst.element_factory_make("videorate")
150 capsfilter = gst.element_factory_make("capsfilter")
151 capsfilter.props.caps = outcaps
152
153 pipeline.add(cspace, scaler, videorate, capsfilter)
154
155 src.link(cspace)
156 cspace.link(scaler)
157 scaler.link(videorate)
158 videorate.link(capsfilter)
159 return capsfilter.get_pad('src')
160
162 pipeline = gst.Pipeline()
163
164 for mediatype in ['audio', 'video']:
165 if (mediatype == 'audio' and not self._hasAudio) or (
166 mediatype == 'video' and not self._hasVideo):
167 continue
168
169
170
171
172
173
174
175 composition = gst.element_factory_make("gnlcomposition",
176 mediatype + "-composition")
177
178 segmentidentity = gst.element_factory_make("identity")
179 segmentidentity.set_property("single-segment", True)
180 segmentidentity.set_property("silent", True)
181 syncidentity = gst.element_factory_make("identity")
182 syncidentity.set_property("silent", True)
183 syncidentity.set_property("sync", True)
184
185 pipeline.add(composition, segmentidentity, syncidentity)
186
187 def _padAddedCb(element, pad, target):
188 self.debug("Pad added, linking")
189 pad.link(target)
190 composition.connect('pad-added', _padAddedCb,
191 segmentidentity.get_pad("sink"))
192
193 if mediatype == 'audio':
194 self.audiocomp = composition
195 srcpad = self._buildAudioPipeline(pipeline, segmentidentity)
196 else:
197 self.videocomp = composition
198 srcpad = self._buildVideoPipeline(pipeline, segmentidentity)
199
200 srcpad.link(syncidentity.get_pad('sink'))
201
202 feederchunk = self.get_feeder_template(mediatype)
203 binstr = "bin.("+feederchunk+" )"
204 self.debug("Parse for media composition is %s", binstr)
205
206 bin = gst.parse_launch(binstr)
207 pad = bin.find_unconnected_pad(gst.PAD_SINK)
208 ghostpad = gst.GhostPad(mediatype + "-feederpad", pad)
209 bin.add_pad(ghostpad)
210
211 pipeline.add(bin)
212 syncidentity.get_pad('src').link(ghostpad)
213
214 return pipeline
215
217 if self._hasVideo:
218 vsrc = videotest_gnl_src("videotestdefault", 0, 2**63 - 1,
219 2**31 - 1, properties.get('video-pattern', None))
220 self.videocomp.add(vsrc)
221
222 if self._hasAudio:
223 asrc = audiotest_gnl_src("videotestdefault", 0, 2**63 - 1,
224 2**31 - 1, properties.get('audio-wave', None))
225 self.audiocomp.add(asrc)
226
228 raise NotImplementedError("Playlist producer doesn't support slaving")
229
231
232
233 if self.medium:
234 ip = self.medium.getIP()
235 else:
236 ip = "127.0.0.1"
237
238 clock = self.pipeline.get_clock()
239 self.clock_provider = gst.NetTimeProvider(clock, None, port)
240
241 self.clock_provider.set_property('active', False)
242
243 self._master_clock_info = (ip, port, self.basetime)
244
245 return defer.succeed(self._master_clock_info)
246
248 return self._master_clock_info
249
251
252 clock = gst.SystemClock()
253
254
255 self.basetime = clock.get_time()
256
257
258 pipeline.use_clock(clock)
259
260 pipeline.set_new_stream_time(gst.CLOCK_TIME_NONE)
261
262 self.debug("Setting basetime of %d", self.basetime)
263 pipeline.set_base_time(self.basetime)
264
269
271 return self.pipeline.query_position(gst.FORMAT_TIME)[0]
272
274 """
275 Schedule a given playlist item in our playback compositions.
276 """
277 start = item.timestamp - self.basetime
278 self.debug("Starting item %s at %d seconds from start: %s", item.uri,
279 start/gst.SECOND, _tsToString(item.timestamp))
280
281
282
283
284
285
286
287 now = self.getCurrentPosition()
288 neareststarttime = now + 5 * gst.SECOND
289
290 if start < neareststarttime:
291 if start + item.duration < neareststarttime:
292 self.debug("Item too late; skipping entirely")
293 return False
294 else:
295 change = neareststarttime - start
296 self.debug("Starting item with offset %d", change)
297 item.duration -= change
298 item.offset += change
299 start = neareststarttime
300
301 end = start + item.duration
302 timeuntilend = end - now
303
304
305 reactor.callLater(timeuntilend/gst.SECOND + 5,
306 self.unscheduleItem, item)
307
308 if self._hasVideo and item.hasVideo:
309 self.debug("Adding video source with start %d, duration %d, "
310 "offset %d", start, item.duration, item.offset)
311 vsrc = file_gnl_src(None, item.uri, self.videocaps,
312 start, item.duration, item.offset, 0)
313 self.videocomp.add(vsrc)
314 self._vsrcs[item] = vsrc
315 if self._hasAudio and item.hasAudio:
316 self.debug("Adding audio source with start %d, duration %d, "
317 "offset %d", start, item.duration, item.offset)
318 asrc = file_gnl_src(None, item.uri, self.audiocaps,
319 start, item.duration, item.offset, 0)
320 self.audiocomp.add(asrc)
321 self._asrcs[item] = asrc
322 self.debug("Done scheduling: start at %s, end at %s",
323 _tsToString(start + self.basetime),
324 _tsToString(start + self.basetime + item.duration))
325 return True
326
328 self.debug("Unscheduling item at uri %s", item.uri)
329 if self._hasVideo and item.hasVideo and item in self._vsrcs:
330 vsrc = self._vsrcs.pop(item)
331 self.videocomp.remove(vsrc)
332 vsrc.set_state(gst.STATE_NULL)
333 if self._hasAudio and item.hasAudio and item in self._asrcs:
334 asrc = self._asrcs.pop(item)
335 self.audiocomp.remove(asrc)
336 asrc.set_state(gst.STATE_NULL)
337
349
352
354 props = self.config['properties'];
355
356 self._playlistfile = props.get('playlist', None)
357 self._playlistdirectory = props.get('playlist-directory', None)
358 self._baseDirectory = props.get('base-directory', None)
359
360 self._width = props.get('width', 320)
361 self._height = props.get('height', 240)
362 self._framerate = props.get('framerate', (15, 1))
363 self._samplerate = props.get('samplerate', 44100)
364 self._channels = props.get('channels', 2)
365
366 self._hasAudio = props.get('audio', True)
367 self._hasVideo = props.get('video', True)
368
369 pipeline = self._buildPipeline()
370 self._setupClock(pipeline)
371
372 self._createDefaultSources(props)
373
374 return pipeline
375
391
399
401
402 msgid = ("playlist-parse-error", file)
403 for m in self.state.get('messages'):
404 if m.id == msgid:
405 self.state.remove('messages', m)
406
429
446