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