[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