[xiph-commits] r11684 - trunk/souffleur

daraku at svn.xiph.org daraku at svn.xiph.org
Sun Jul 2 08:30:44 PDT 2006


Author: daraku
Date: 2006-07-02 08:30:37 -0700 (Sun, 02 Jul 2006)
New Revision: 11684

Added:
   trunk/souffleur/GPlayer.py
   trunk/souffleur/Subtitles.py
Modified:
   trunk/souffleur/Souffleur.py
   trunk/souffleur/souffleur.glade
Log:
Add basic SRT format support, and basic media playback

Added: trunk/souffleur/GPlayer.py
===================================================================
--- trunk/souffleur/GPlayer.py	2006-07-01 21:33:56 UTC (rev 11683)
+++ trunk/souffleur/GPlayer.py	2006-07-02 15:30:37 UTC (rev 11684)
@@ -0,0 +1,135 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+import pygtk
+pygtk.require('2.0')
+
+#import sys
+
+import gobject
+gobject.threads_init()
+
+import pygst
+pygst.require('0.10')
+import gst
+import gst.interfaces
+import gtk
+
+class GstPlayer:
+    def __init__(self, videowidget):
+        self.playing = False
+        self.player = gst.element_factory_make("playbin", "player")
+        self.videowidget = videowidget
+
+        bus = self.player.get_bus()
+        bus.enable_sync_message_emission()
+        bus.add_signal_watch()
+        bus.connect('sync-message::element', self.on_sync_message)
+        bus.connect('message', self.on_message)
+
+    def on_sync_message(self, bus, message):
+        if message.structure is None:
+            return
+        if message.structure.get_name() == 'prepare-xwindow-id':
+            self.videowidget.set_sink(message.src)
+            message.src.set_property('force-aspect-ratio', True)
+            
+    def on_message(self, bus, message):
+        t = message.type
+        if t == gst.MESSAGE_ERROR:
+            err, debug = message.parse_error()
+            print "Error: %s" % err, debug
+            if self.on_eos:
+                self.on_eos()
+            self.playing = False
+        elif t == gst.MESSAGE_EOS:
+            if self.on_eos:
+                self.on_eos()
+            self.playing = False
+
+    def set_location(self, location):
+        self.player.set_state(gst.STATE_NULL)
+        self.player.set_property('uri', location)
+
+    def get_location(self):
+        return self.player.get_property('uri')
+
+    def query_position(self):
+        "Returns a (position, duration) tuple"
+        try:
+            position, format = self.player.query_position(gst.FORMAT_TIME)
+        except:
+            position = gst.CLOCK_TIME_NONE
+
+        try:
+            duration, format = self.player.query_duration(gst.FORMAT_TIME)
+        except:
+            duration = gst.CLOCK_TIME_NONE
+
+        return (position, duration)
+
+    def seek(self, location):
+        """
+        @param location: time to seek to, in nanoseconds
+        """
+        gst.debug("seeking to %r" % location)
+        event = gst.event_new_seek(1.0, gst.FORMAT_TIME,
+            gst.SEEK_FLAG_FLUSH,
+            gst.SEEK_TYPE_SET, location,
+            gst.SEEK_TYPE_NONE, 0)
+
+        res = self.player.send_event(event)
+        if res:
+            gst.info("setting new stream time to 0")
+            self.player.set_new_stream_time(0L)
+        else:
+            gst.error("seek to %r failed" % location)
+
+    def pause(self):
+        gst.info("pausing player")
+        self.player.set_state(gst.STATE_PAUSED)
+        self.playing = False
+
+    def play(self):
+        gst.info("playing player")
+        self.player.set_state(gst.STATE_PLAYING)
+        self.playing = True
+        
+    def stop(self):
+        self.player.set_state(gst.STATE_NULL)
+        gst.info("stopped player")
+
+    def get_state(self, timeout=1):
+        return self.player.get_state(timeout=timeout)
+
+    def is_playing(self):
+        return self.playing
+
+class VideoWidget:
+    def __init__(self, TArea):
+        self.Area=TArea
+        self.imagesink = None
+        self.Area.unset_flags(gtk.DOUBLE_BUFFERED)
+
+    def do_expose_event(self, event):
+        if self.imagesink:
+            self.imagesink.expose()
+            return False
+        else:
+            return True
+
+    def set_sink(self, sink):
+        assert self.Area.window.xid
+        self.imagesink = sink
+        self.imagesink.set_xwindow_id(self.Area.window.xid)

Modified: trunk/souffleur/Souffleur.py
===================================================================
--- trunk/souffleur/Souffleur.py	2006-07-01 21:33:56 UTC (rev 11683)
+++ trunk/souffleur/Souffleur.py	2006-07-02 15:30:37 UTC (rev 11684)
@@ -1,7 +1,26 @@
 #!/usr/bin/env python
 
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+
 #import oggStreams
 from gstfile import GstFile
+from GPlayer import VideoWidget
+from GPlayer import GstPlayer
+from Subtitles import Subtitles
+from datetime import time
 import sys
 
 try:
@@ -23,106 +42,175 @@
     sys.exit(1)
 #now we have both gtk and gtk.glade imported
 #Also, we know we are running GTK v2
+import gst
 
 class Souffleur:
 #    gladefile=""
     def __init__(self):
-	"""
-	In this init we are going to display the main
-	Souffleur window
-	"""
-	gladefile="souffleur.glade"
-	windowname="MAIN_WINDOW"
-	self.wTree=gtk.glade.XML (gladefile,windowname)
-	self.gladefile = gladefile
-	# we only have two callbacks to register, but
-	# you could register any number, or use a
-	# special class that automatically
-	# registers all callbacks. If you wanted to pass
-	# an argument, you would use a tuple like this:
-	# dic = { "on button1_clicked" : (self.button1_clicked, arg1,arg2) , ...
-    
-	#dic = { "on_button1_clicked" : self.button1_clicked, \
-	#	"gtk_main_quit" : (gtk.mainquit) }
-	dic = { "gtk_main_quit" : (gtk.main_quit),\
-		"on_main_file_quit_activate": (gtk.main_quit), \
-		"on_main_file_open_activate": self.mainFileOpen}
-	self.wTree.signal_autoconnect (dic)
+        """
+        In this init we are going to display the main
+        Souffleur window
+        """
+        gladefile="souffleur.glade"
+        windowname="MAIN_WINDOW"
+        
+        self.update_id = -1
+        self.p_position = gst.CLOCK_TIME_NONE
+        self.p_duration = gst.CLOCK_TIME_NONE
+        self.UPDATE_INTERVAL=100
+        
+        self.Subtitle = None
+        self.curSub = -1
+        
+        #self.videoWidget=VideoWidget();
+        #gtk.glade.set_custom_handler(self.videoWidget, VideoWidget())
 
-	self.windowFileOpen=None
-	self.windowStreams=gtk.glade.XML (self.gladefile,"STREAM_WINDOW")
-	### Setup LIST_STREAMS
-	LIST = self.windowStreams.get_widget("LIST_STREAMS")
-	if LIST:
-	    self.streamsTreeStore = gtk.TreeStore(gobject.TYPE_STRING)
-	    LIST.set_model(self.streamsTreeStore)
-	    cell = gtk.CellRendererText()
-	    tvcolumn = gtk.TreeViewColumn('Streams', cell, text = 0)
-	    LIST.append_column(tvcolumn)
-	WND=self.windowStreams.get_widget("STREAM_WINDOW")
-	WND.hide()
-	return
+        #gtk.glade.set_custom_handler(self.custom_handler)
+        self.wTree=gtk.glade.XML (gladefile,windowname)
+        self.gladefile = gladefile
+        # we only have two callbacks to register, but
+        # you could register any number, or use a
+        # special class that automatically
+        # registers all callbacks. If you wanted to pass
+        # an argument, you would use a tuple like this:
+        # dic = { "on button1_clicked" : (self.button1_clicked, arg1,arg2) , ...
+        #dic = { "on_button1_clicked" : self.button1_clicked, \
+        #	"gtk_main_quit" : (gtk.mainquit) }
+        dic = { "gtk_main_quit" : (gtk.main_quit),\
+            "on_main_file_quit_activate": (gtk.main_quit), \
+            "on_main_file_open_activate": self.mainFileOpen}
+        self.wTree.signal_autoconnect (dic)
+        
+        self.windowFileOpen=None
+        self.windowStreams=gtk.glade.XML (self.gladefile,"STREAM_WINDOW")
+        ### Setup LIST_STREAMS
+        LIST = self.windowStreams.get_widget("LIST_STREAMS")
+        if LIST:
+            self.streamsTreeStore = gtk.TreeStore(gobject.TYPE_STRING)
+            LIST.set_model(self.streamsTreeStore)
+            cell = gtk.CellRendererText()
+            tvcolumn = gtk.TreeViewColumn('Streams', cell, text = 0)
+            LIST.append_column(tvcolumn)
+        WND=self.windowStreams.get_widget("STREAM_WINDOW")
+        WND.hide()
+        ### Main window setup
+        self.videoWidget = self.wTree.get_widget("VIDEO_OUT_PUT")
+        self.adjustment = self.wTree.get_widget("MEDIA_ADJUSTMENT")
+        self.SubEdit = self.wTree.get_widget("VIEW_SUB")
+        return
 #==============================================================================
     def mainFileOpen(self, widget):
-	if(self.windowFileOpen==None):
-	    self.windowFileOpen=gtk.glade.XML (self.gladefile,"OPEN_OGG")
-	    dic={"on_OPEN_BUTTON_CANCEL_clicked": self.openFileCancel,\
-		"on_OPEN_BUTTON_OPEN_clicked": self.openFileOpen }
-	    self.windowFileOpen.signal_autoconnect(dic)
-#	    WND=self.windowFileOpen.get_widget("OPEN_OGG")
-#	    Filter=gtk.FileFilter()
-#	    Filter.set_name("OGM file")
-#	    Filter.add_pattern("*.og[gm]")
-#	    WND.add_filter(Filter)
-	else:
-	    WND=self.windowFileOpen.get_widget("OPEN_OGG")
-	    if(WND==None):
-		self.windowFileOpen=None
-		self.mainFileOpen(widget)
-	    else:
-		WND.show()
-	return
+        if(self.windowFileOpen==None):
+            self.windowFileOpen=gtk.glade.XML (self.gladefile,"OPEN_OGG")
+            dic={"on_OPEN_BUTTON_CANCEL_clicked": self.openFileCancel,\
+                "on_OPEN_BUTTON_OPEN_clicked": self.openFileOpen }
+            self.windowFileOpen.signal_autoconnect(dic)
+#   	    WND=self.windowFileOpen.get_widget("OPEN_OGG")
+#	        Filter=gtk.FileFilter()
+#	        Filter.set_name("OGM file")
+#   	    Filter.add_pattern("*.og[gm]")
+#	        WND.add_filter(Filter)
+        else:
+            WND=self.windowFileOpen.get_widget("OPEN_OGG")
+            if(WND==None):
+                self.windowFileOpen=None
+                self.mainFileOpen(widget)
+            else:
+                WND.show()
+        return
 #==============================================================================
     def openFileCancel(self, widget):
-	if(self.windowFileOpen==None): return
-	WND=self.windowFileOpen.get_widget("OPEN_OGG")
-	WND.hide()
-	return
+        if(self.windowFileOpen==None): return
+        WND=self.windowFileOpen.get_widget("OPEN_OGG")
+        WND.hide()
+        return
 #==============================================================================
     def openFileOpen(self, widget):
-	WND=self.windowFileOpen.get_widget("OPEN_OGG")
-	FN=WND.get_filename()
-	Streams = None
-	if((FN!="")and(FN!=None)):
-#	    self.Streams=oggStreams.oggStreams(FN)
-	    print FN
-	    Streams = GstFile(FN)
-	    if Streams:
-		Streams.run()
-	WND.hide()
-	WND=self.windowStreams.get_widget("STREAM_WINDOW")
-	WND.show()
-	self.addStreams(Streams)
-#	self.refreshStreamsWindow()
-	return
+        WND=self.windowFileOpen.get_widget("OPEN_OGG")
+        FN=WND.get_filename()
+        Streams = None
+        if((FN!="")and(FN!=None)):
+#	        self.Streams=oggStreams.oggStreams(FN)
+#   	    print FN
+            Streams = GstFile(FN)
+            if Streams:
+                Streams.run()
+        WND.hide()
+        WND=self.windowStreams.get_widget("STREAM_WINDOW")
+        WND.show()
+        self.addStreams(Streams)
+#   	self.refreshStreamsWindow()
+        return
 #==============================================================================
+    def loadSubtitle(self, FileName):
+        pass
+#==============================================================================
     def addStreams(self, Streams):
-	if not Streams:
-	    return
-	iter = self.streamsTreeStore.append(None)
-	self.streamsTreeStore.set(iter, 0, Streams.MIME + " " + Streams.SOURCE)
-	for i in Streams.STREAMS.keys():
-	    child = self.streamsTreeStore.append(iter)
-	    self.streamsTreeStore.set(child, 0, i +" " + Streams.STREAMS[i])
-	    print i +" " + Streams.STREAMS[i]
-	return
-#    def refreshStreamsWindow(self):
-#	TStreams=self.Streams.getStreams()
-#	for OGGStream in Streams:
-	    #TODO
-#	    pass
-#	return
+        if not Streams:
+            return
+        iter = self.streamsTreeStore.append(None)
+        self.streamsTreeStore.set(iter, 0, Streams.MIME + " " + Streams.SOURCE)
+        for i in Streams.STREAMS.keys():
+            child = self.streamsTreeStore.append(iter)
+            self.streamsTreeStore.set(child, 0, i +" " + Streams.STREAMS[i])
+            print i +" " + Streams.STREAMS[i]
+        if "subtitle" in Streams.MIME:
+            self.Subtitle = Subtitles()
+            self.Subtitle.subLoad(Streams.SOURCE)
+        else:
+            self.videoWidgetGst=VideoWidget(self.videoWidget)
+            self.player=GstPlayer(self.videoWidgetGst)
+            self.player.set_location("file://"+Streams.SOURCE)
+            if self.videoWidget.flags() & gtk.REALIZED:
+                self.play_toggled()
+            else:
+                self.videoWidget.connect_after('realize',
+                                           lambda *x: self.play_toggled())
+        return
 #==============================================================================
+    def play_toggled(self):
+        if self.player.is_playing():
+            self.player.pause()
+            #self.button.set_label(gtk.STOCK_MEDIA_PLAY)
+        else:
+            self.player.play()
+            if self.update_id == -1:
+                self.update_id = gobject.timeout_add(self.UPDATE_INTERVAL,
+                                                     self.update_scale_cb)
+            #self.button.set_label(gtk.STOCK_MEDIA_PAUSE)
+#==============================================================================
+    def update_scale_cb(self):
+        had_duration = self.p_duration != gst.CLOCK_TIME_NONE
+        self.p_position, self.p_duration = self.player.query_position()
+        if self.Subtitle:
+            tmSec= self.p_position/1000000
+            MSec = tmSec%1000
+            tmSec = tmSec/1000
+            Sec = tmSec%60
+            tmSec = tmSec/60
+            Min = tmSec%60
+            Hour=tmSec/60
+            ST = time( Hour, Min, Sec, MSec )
+            TText = self.Subtitle.getSub(ST)
+            if TText:
+                if (TText.N!=self.curSub):
+                    BUF=gtk.TextBuffer()
+                    BUF.set_text(TText.text)
+                    self.SubEdit.set_buffer(BUF)
+                    self.curSub=TText.N
+            else:
+                if (self.curSub!=-1):
+                    BUF=gtk.TextBuffer()
+                    BUF.set_text("")
+                    self.SubEdit.set_buffer(BUF)
+                    self.curSub=-1
+        if self.p_position != gst.CLOCK_TIME_NONE:
+            value = self.p_position * 100.0 / self.p_duration
+            self.adjustment.set_value(value)
+            #if not had_duration:
+            #    self.cutin.set_time(0)
+        return True
+#==============================================================================
 #	MAIN:
 #==============================================================================
 souffleur=Souffleur()

Added: trunk/souffleur/Subtitles.py
===================================================================
--- trunk/souffleur/Subtitles.py	2006-07-01 21:33:56 UTC (rev 11683)
+++ trunk/souffleur/Subtitles.py	2006-07-02 15:30:37 UTC (rev 11684)
@@ -0,0 +1,79 @@
+import os
+import string
+from datetime import time
+#from array import array
+
+SUB_NONE=0
+SUB_SRT=1
+
+class Sub:
+    def __init__(self):
+        self.text=""
+        self.start_time=None
+        self.end_time=None
+        self.subType=SUB_NONE
+        self.N=0
+
+    def isInTime(self, time):
+        if( (time>=self.start_time) and (time<=self.end_time) ):
+            return 1
+        else:
+            return 0
+
+
+class Subtitles:
+    def __init__(self):
+        self.subs=[]
+        self.subSource=None
+        self.subType=SUB_SRT
+
+    def subLoad(self, fileName):
+        FILE=os.open(fileName, os.O_RDONLY)
+        FS=os.fstat(FILE)
+        DATA=os.read(FILE,FS.st_size)
+        os.close(FILE)
+
+        self._subLoadFromString(DATA)
+
+        self.subSource=fileName
+
+    def _subLoadFromString(self, DATA):
+        DATA=string.split(DATA,"\n")
+        i=0
+        while(i<len(DATA)):
+            #i=i+1
+            if(i>=len(DATA)):
+                break
+            N = DATA[i]
+            i+=1
+            if(i>=len(DATA)):
+                break
+            Timing = DATA[i]
+            Text="";
+            i+=1
+            if(i>=len(DATA)):
+                break
+            while(DATA[i]!=""):
+                Text=Text+DATA[i]+"\n"
+                i+=1
+            i+=1
+            ST=time(int(Timing[0:2]), int(Timing[3:5]), int(Timing[6:8]), int(Timing[9:12])*1000)
+            ET=time(int(Timing[17:19]), int(Timing[20:22]), int(Timing[23:25]), int(Timing[26:29])*1000)
+            
+            TS=Sub()
+            TS.text=Text
+            TS.start_time=ST
+            TS.end_time=ET
+            TS.subType=self.subType
+            TS.N=N
+            self.subs.append(TS)
+
+    def getSub(self, time):
+        i=0
+        while(time>=self.subs[i].start_time):
+            if(self.subs[i].isInTime(time)==1):
+                return self.subs[i]
+            i=i+1
+            if(i>(len(self.subs)+1)):
+                return None
+        return None

Modified: trunk/souffleur/souffleur.glade
===================================================================
--- trunk/souffleur/souffleur.glade	2006-07-01 21:33:56 UTC (rev 11683)
+++ trunk/souffleur/souffleur.glade	2006-07-02 15:30:37 UTC (rev 11684)
@@ -184,8 +184,8 @@
 
       <child>
 	<widget class="GtkDrawingArea" id="VIDEO_OUT_PUT">
-	  <property name="width_request">320</property>
-	  <property name="height_request">240</property>
+	  <property name="width_request">640</property>
+	  <property name="height_request">320</property>
 	  <property name="visible">True</property>
 	</widget>
 	<packing>
@@ -468,6 +468,20 @@
       </child>
 
       <child>
+	<widget class="GtkHScrollbar" id="MEDIA_ADJUSTMENT">
+	  <property name="visible">True</property>
+	  <property name="update_policy">GTK_UPDATE_CONTINUOUS</property>
+	  <property name="inverted">False</property>
+	  <property name="adjustment">0 0 100 0.10000000149 1 1</property>
+	</widget>
+	<packing>
+	  <property name="padding">0</property>
+	  <property name="expand">False</property>
+	  <property name="fill">False</property>
+	</packing>
+      </child>
+
+      <child>
 	<widget class="GtkVBox" id="vbox5">
 	  <property name="border_width">8</property>
 	  <property name="visible">True</property>
@@ -705,7 +719,7 @@
 		</widget>
 		<packing>
 		  <property name="shrink">True</property>
-		  <property name="resize">False</property>
+		  <property name="resize">True</property>
 		</packing>
 	      </child>
 



More information about the commits mailing list