msmith - in flumotion/branches/playlist-1: .
flumotion/component/producers/playlist
flumotion-commit at lists.fluendo.com
flumotion-commit at lists.fluendo.com
Tue May 8 10:52:34 CEST 2007
Author: msmith
Date: Tue May 8 10:52:31 2007
New Revision: 4880
Added:
flumotion/branches/playlist-1/flumotion/component/producers/playlist/playlistparser.py
Modified:
flumotion/branches/playlist-1/ChangeLog
flumotion/branches/playlist-1/flumotion/component/producers/playlist/playlist.py
Log:
* flumotion/component/producers/playlist/playlist.py:
* flumotion/component/producers/playlist/playlistparser.py:
Add a playlist parser.
Doesn't yet use discoverer; duration is mandatory.
Modified: flumotion/branches/playlist-1/ChangeLog
==============================================================================
--- flumotion/branches/playlist-1/ChangeLog (original)
+++ flumotion/branches/playlist-1/ChangeLog Tue May 8 10:52:31 2007
@@ -1,4 +1,11 @@
-2007-05-03 Michael Smith,,, <set EMAIL_ADDRESS environment variable>
+2007-05-08 Michael Smith <msmith at fluendo.com>
+
+ * flumotion/component/producers/playlist/playlist.py:
+ * flumotion/component/producers/playlist/playlistparser.py:
+ Add a playlist parser.
+ Doesn't yet use discoverer; duration is mandatory.
+
+2007-05-03 Michael Smith <msmith at fluendo.com>
* flumotion/component/producers/playlist/playlist.py:
* flumotion/component/producers/playlist/playlist.xml:
Modified: flumotion/branches/playlist-1/flumotion/component/producers/playlist/playlist.py
==============================================================================
--- flumotion/branches/playlist-1/flumotion/component/producers/playlist/playlist.py (original)
+++ flumotion/branches/playlist-1/flumotion/component/producers/playlist/playlist.py Tue May 8 10:52:31 2007
@@ -25,13 +25,14 @@
from twisted.internet import defer
-from flumotion.common import errors, messages
+from flumotion.common import errors, messages, log
from flumotion.component import feedcomponent
from flumotion.common.messages import N_
import smartscale
import singledecodebin
+import playlistparser
T_ = messages.gettexter('flumotion')
@@ -75,7 +76,6 @@
return gnlsrc
-
class PlaylistProducerMedium(feedcomponent.FeedComponentMedium):
def __init__(self, comp):
feedcomponent.FeedComponentMedium.__init__(self, comp)
@@ -96,16 +96,23 @@
self.audiocaps = gst.Caps("audio/x-raw-int;audio/x-raw-float")
def _buildAudioPipeline(self, pipeline, queue):
+ audiorate = gst.element_factory_make("audiorate")
audioconvert = gst.element_factory_make('audioconvert')
audioresample = gst.element_factory_make('audioresample')
- pipeline.add(audioconvert, audioresample)
+ outcaps = gst.Caps("audio/x-raw-int,channels=2,rate=44100")
+ capsfilter = gst.element_factory_make("capsfilter")
+ capsfilter.props.caps = outcaps
+
+ pipeline.add(audiorate, audioconvert, audioresample, capsfilter)
queue.link(audioconvert)
audioconvert.link(audioresample)
+ audioresample.link(audiorate)
+ audiorate.link(capsfilter)
- return audioresample.get_pad('src')
+ return capsfilter.get_pad('src')
def _buildVideoPipeline(self, pipeline, queue):
- outcaps = gst.caps_from_string(
+ outcaps = gst.Caps(
"video/x-raw-yuv,width=%d,height=%d,framerate=%d/%d" %
(320, 240, 15, 1))
@@ -192,48 +199,40 @@
self.debug("Setting basetime of %d", self.basetime)
pipeline.set_base_time(self.basetime)
- def scheduleFile(self, uri, starttime, duration, offset,
- video=True, audio=True):
+ def scheduleItem(self, item):
"""
- Schedule a file at a given URI to start playback at starttime
- (specified as nanoseconds since the epoch), for the given duration.
- Use audio and/or video.
+ Schedule a given playlist item in our playback compositions.
"""
- start = starttime - self.basetime
+ start = item.timestamp - self.basetime
# This works around a bug in videotestsrc and videorate. TODO! This
# can be removed once we upgrade to a future version of gst-plugins-base
start = (start / gst.SECOND) * gst.SECOND
+ self.debug("Starting item %s in %d seconds", item.uri, start/gst.SECOND)
if start < 0:
- if start + duration < 0:
+ if start + item.duration < 0:
return
else:
# If we're not too late for part of the file to be playable,
# then play it!
- offset = -start
- duration = duration + start
+ item.offset = -start
+ item.duration = item.duration + start
start = 0
- if video:
- vsrc = file_gnl_src(None, uri, self.videocaps,
- start, duration, offset, 0)
- self.videocomp.add(vsrc)
- if audio:
- asrc = file_gnl_src(None, uri, self.audiocaps,
- start, duration, offset, 0)
- self.audiocomp.add(asrc)
-
- def testSchedule(self, pipeline):
- # Now, we want to schedule something for a particular date...
- uri = "file:///home/msmith/media/testfiles/thehillshaveeyes_h480p.mov"
-
- for minute in xrange(8, 50):
- when = (2007, 5, 3, 11, minute, 0, 0, 123, 0)
- whengst = int(time.mktime(when)) * gst.SECOND
-
- duration = 45 * gst.SECOND
- offset = 15 * gst.SECOND
- self.scheduleFile(uri, whengst, duration, offset, True, False)
+ if item.hasVideo:
+ item.vsrc = file_gnl_src(None, item.uri, self.videocaps,
+ start, item.duration, item.offset, 0)
+ self.videocomp.add(item.vsrc)
+ if item.hasAudio:
+ item.asrc = file_gnl_src(None, item.uri, self.audiocaps,
+ start, item.duration, item.offset, 0)
+ self.audiocomp.add(item.asrc)
+
+ def unscheduleItem(self, item):
+ if item.hasVideo:
+ self.videocomp.remove(item.vsrc)
+ if item.hasAudio:
+ self.audiocomp.remove(item.asrc)
def create_pipeline(self):
pipeline = self._buildPipeline()
@@ -241,7 +240,8 @@
self._createDefaultSources()
- self.testSchedule(pipeline)
+ self.playlist = playlistparser.Playlist(self)
+ self.playlist.parseFile("/tmp/playlist.xml")
self.connect_feeders(pipeline)
return pipeline
Added: flumotion/branches/playlist-1/flumotion/component/producers/playlist/playlistparser.py
==============================================================================
--- (empty file)
+++ flumotion/branches/playlist-1/flumotion/component/producers/playlist/playlistparser.py Tue May 8 10:52:31 2007
@@ -0,0 +1,172 @@
+# -*- Mode: Python -*-
+# vi:si:et:sw=4:sts=4:ts=4
+#
+# Flumotion - a streaming media server
+# Copyright (C) 2004,2005,2006,2007 Fluendo, S.L. (www.fluendo.com).
+# All rights reserved.
+
+# This file may be distributed and/or modified under the terms of
+# the GNU General Public License version 2 as published by
+# the Free Software Foundation.
+# This file is distributed without any warranty; without even the implied
+# warranty of merchantability or fitness for a particular purpose.
+# See "LICENSE.GPL" in the source distribution for more information.
+
+# Licensees having purchased or holding a valid Flumotion Advanced
+# Streaming Server license may use this file in accordance with the
+# Flumotion Advanced Streaming Server Commercial License Agreement.
+# See "LICENSE.Flumotion" in the source distribution for more information.
+
+# Headers in this file shall remain intact.
+
+import gst
+import time
+
+from xml.dom import Node
+
+from flumotion.common import log, fxml
+
+import singledecodebin
+
+def file_gnl_src(name, uri, caps, start, duration, offset, priority):
+ src = singledecodebin.SingleDecodeBin(caps, uri)
+ gnlsrc = gst.element_factory_make('gnlsource', name)
+ gnlsrc.props.start = start
+ gnlsrc.props.duration = duration
+ gnlsrc.props.media_start = offset
+ gnlsrc.props.media_duration = duration
+ gnlsrc.props.priority = priority
+ gnlsrc.add(src)
+
+ return gnlsrc
+
+class PlaylistItem(object, log.Loggable):
+ def __init__(self, timestamp, uri, offset, duration):
+ self.timestamp = timestamp
+ self.uri = uri
+ self.offset = offset
+ self.duration = duration
+
+ # Currently always set to true; later this should come from what the
+ # discoverer says.
+ self.hasAudio = True
+ self.hasVideo = True
+
+ # Audio and video gnlsource objects
+ self.vsrc = None
+ self.asrc = None
+
+ self.next = None
+
+class Playlist(object, log.Loggable):
+ def __init__(self, producer):
+ """
+ Create an initially empty playlist
+ """
+ self.items = None # PlaylistItem linked list
+
+ self.producer = producer
+
+ def addItem(self, timestamp, uri, offset, duration):
+ """
+ Add an item to the playlist.
+ The duration of previous and this entry may be adjusted to make it fit.
+ """
+ newitem = PlaylistItem(timestamp, uri, offset, duration)
+ prev = next = None
+ item = self.items
+ while item:
+ if item.timestamp < newitem.timestamp:
+ prev = item
+ elif not next and item.timestamp > newitem.timestamp:
+ next = item
+ break
+ item = item.next
+
+ if prev and next and prev.next != next:
+ # Then things between prev and next are to be deleted. Do so.
+ cur = prev.next
+ while cur != next:
+ self.producer.unscheduleItem(cur)
+ cur = cur.next
+
+ if prev:
+ prev.next = newitem
+ else:
+ self.items = newitem
+
+ if next:
+ newitem.next = next
+
+ # Duration adjustments -> Reflect into gnonlin timeline
+ if prev and prev.timestamp + prev.duration > newitem.timestamp:
+ prev.duration = newitem.timestamp - prev.timestamp
+ prev.asrc.props.duration = prev.duration
+ prev.vsrc.props.duration = prev.duration
+ if next and timestamp + newitem.duration > next.timestamp:
+ newitem.duration = next.timestamp - newitem.timestamp
+ newitem.asrc.props.duration = newitem.duration
+ newitem.vsrc.props.duration = newitem.duration
+
+ # Then we need to actually add newitem into the gnonlin timeline
+ self.producer.scheduleItem(newitem)
+
+ def expireOldEntries(self):
+ """
+ Delete references to old playlist entries that have passed.
+ TODO: is this needed? It's to save memory, but probably not very much
+ memory...
+ """
+ pass
+
+
+ def parseFile(self, file):
+ """
+ Parse a playlist file. Adds the contents of the file to the existing
+ playlist, overwriting any existing entries for the same time period.
+ """
+ parser = fxml.Parser()
+
+ root = parser.getRoot(file)
+
+ node = root.documentElement
+ self.debug("Parsing playlist from file %s", file)
+ if node.nodeName != 'playlist':
+ raise fxml.ParserError("Root node is not 'playlist'")
+
+ for child in node.childNodes:
+ if child.nodeType == Node.ELEMENT_NODE and \
+ child.nodeName == 'entry':
+ self.debug("Parsing entry")
+ self._parsePlaylistEntry(parser, child)
+
+ def _parsePlaylistEntry(self, parser, entry):
+ # TODO: Once we use the discoverer, we should move duration to optional
+ mandatory = ['filename', 'time', 'duration']
+ optional = ['offset']
+
+ (filename, timestamp, duration, offset) = parser.parseAttributes(
+ entry, mandatory, optional)
+
+ duration = int(float(duration) * gst.SECOND)
+ if offset is None:
+ offset = 0
+ offset = int(offset)
+
+ timestamp = self._parseTimestamp(timestamp)
+
+ uri = 'file://'+filename
+
+ self.debug("Adding item")
+ self.addItem(timestamp, uri, offset, duration)
+
+ def _parseTimestamp(self, ts):
+ # Take TS in YYYY-MM-DDThh:mm:ssZ format, return timestamp in
+ # nanoseconds since the epoch
+ format = "%Y-%m-%dT%H:%M:%SZ"
+
+ timestruct = time.strptime(ts, format)
+
+ return int(time.mktime(timestruct) * gst.SECOND)
+
+
More information about the flumotion-commit
mailing list