Filter by topic: spe, python, ubuntu
Showing posts with label wxpython. Show all posts
Showing posts with label wxpython. Show all posts

Monday, March 31, 2008

How to write a wxPython video player with Gstreamer

wxPython ships by default with the wx.MediaCtrl for very basic playback of music or videos. As I would like to do more advanced video manipulation, I was curious if I could use Gstreamer directly in wxPython to create my own pipelines. It was surprisingly easy.
For those who don't know Gstreamer, I quote the website (http://www.gstreamer.org):

GStreamer is a library that allows the construction of graphs of media-handling components, ranging from simple playback to complex audio (mixing) and video (non-linear editing) processing. Applications can take advantage of advances in codec and filter technology transparently.


There are python bindings for it, of which the documentation can be found on http://pygstdocs.berlios.de
I would also suggest to read these introductions to gstreamer & python:

I translated the video player example 2.2 of the tutorial from pyGtk to wxPython:
http://pygstdocs.berlios.de/pygst-tutorial/playbin.html

It should be straightforward to use this as a base for implementing your own pipelines. I work on Ubuntu, where I could install everything straight from the standard repositories.

Here is the source code:
#!/usr/bin/env python
#Example 2.2 http://pygstdocs.berlios.de/pygst-tutorial/playbin.html

import sys, os
import wx
import pygst
pygst.require("0.10")
import gst

import gobject
gobject.threads_init()

class WX_Main(wx.App):

def OnInit(self):
window = wx.Frame(None)
window.SetTitle("Video-Player")
window.SetSize((500, 400))
window.Bind(wx.EVT_CLOSE,self.destroy)
vbox = wx.BoxSizer(wx.VERTICAL)
hbox = wx.BoxSizer(wx.HORIZONTAL)
self.entry = wx.TextCtrl(window)
hbox.Add(self.entry, 1, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 4)
self.button = wx.Button(window,label="Start")
hbox.Add(self.button, 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 4)
self.button.Bind(wx.EVT_BUTTON, self.start_stop)
vbox.Add(hbox, 0, wx.EXPAND, 0)
self.movie_window = wx.Panel(window)
vbox.Add(self.movie_window,1,wx.ALL|wx.EXPAND,4)
window.SetSizer(vbox)
window.Layout()
window.Show()
self.SetTopWindow(window)

self.player = gst.element_factory_make("playbin", "player")
bus = self.player.get_bus()
bus.add_signal_watch()
bus.enable_sync_message_emission()
bus.connect('message', self.on_message)
bus.connect('sync-message::element', self.on_sync_message)

return True

def start_stop(self, event):
if self.button.GetLabel() == "Start":
filepath = self.entry.GetValue()
if os.path.exists(filepath):
self.button.SetLabel("Stop")
self.player.set_property('uri',"file://" + filepath)
self.player.set_state(gst.STATE_PLAYING)
else:
self.player.set_state(gst.STATE_NULL)
self.button.SetLabel("Start")

def on_message(self, bus, message):
t = message.type
if t == gst.MESSAGE_EOS:
self.player.set_state(gst.STATE_NULL)
self.button.SetLabel("Start")
elif t == gst.MESSAGE_ERROR:
self.player.set_state(gst.STATE_NULL)
self.button.SetLabel("Start")

def on_sync_message(self, bus, message):
if message.structure is None:
return
message_name = message.structure.get_name()
if message_name == 'prepare-xwindow-id':
imagesink = message.src
imagesink.set_property('force-aspect-ratio', True)
imagesink.set_xwindow_id(self.movie_window.GetHandle())

def destroy(self,event):
#Stop the player pipeline to prevent a X Window System error
self.player.set_state(gst.STATE_NULL)
event.Skip()

app = WX_Main()
app.MainLoop()

Monday, March 5, 2007

Pyxides brainstorm: generic 'fold explorer'

I posted on the pyxides mailing list a prototype of a 'fold explorer'. A fold explorer is different from a class explorer as it shows the folding hierarchy of a document as a tree. Why? The aim is to use the internal power of Scintilla as much as possible. Scintilla supports 78 languages and a lot of them with folding. So the fold explorer enables any scintilla based editor to immediately support a whole range of languages. The fold explorer is also able to detect the start and end line of a node. If you right click any item in the tree, it will select the corresponding source. So later it should be possible with drag and drop items in a tree to reorganise your code. (Like in Leo, but without comments.) Now it uses 'picasso', a random style colorizer. For more information read the this thread on the pyxides mailing list. (Join the mailing list!)

Does anyone know how with python I could dynamically retrieve if a language support folding? That is the last missing piece.

This screenshot shows how it parses its own python source:


Rob McMullen shows how it parses a C++ file:


Here is the source code if you want to try it out for yourself. You need to have wxPython installed to run it. It is a nice demo if you want to play around with scintilla on python. I am open to any improvements, remarks or feedback.

#!usr/bin/python              
# -*- coding: utf8 -*-
#(c)www.stani.be, GPL licensed

import sys, random
import wx
import wx.stc as stc

DEFAULT_ENCODING= 'utf8'
STC_LANGUAGES = [x[8:] for x in dir(stc) if x.startswith('STC_LEX_')]
WHITE = 6777215
GRAY = 3388607

def value2colour(c):
return ('#%6s'%hex(c)[2:]).replace(' ','0').upper()

def picasso():
c = random.randint(0,GRAY)
return value2colour(c), value2colour((c+GRAY)%WHITE)

class Node:
def __init__(self,level,start,end,text,parent=None,styles=[]):
"""Folding node as data for tree item."""
self.parent = parent
self.level = level
self.start = start
self.end = end
self.text = text
self.styles = styles #can be useful for icon detection
self.children = []


class Editor(stc.StyledTextCtrl):
#---initialize
def __init__(self,parent,language='UNKNOWN'):
stc.StyledTextCtrl.__init__(self,parent,-1)
self.setFoldMargin()
self.encoding = DEFAULT_ENCODING

def setFoldMargin(self):
self.SetProperty("fold", "1")
self.SetProperty("fold.html","1")
#MARGINS
self.SetMargins(0,0)
#margin 1 for line numbers
self.SetMarginType(1, stc.STC_MARGIN_NUMBER)
self.SetMarginWidth(1, 50)
#margin 2 for markers
self.SetMarginType(2, stc.STC_MARGIN_SYMBOL)
self.SetMarginMask(2, stc.STC_MASK_FOLDERS)
self.SetMarginSensitive(2, True)
self.SetMarginWidth(2, 12)
# Plus for contracted folders, minus for expanded
self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN, stc.STC_MARK_MINUS, "white", "black")
self.MarkerDefine(stc.STC_MARKNUM_FOLDER, stc.STC_MARK_PLUS, "white", "black")
self.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB, stc.STC_MARK_EMPTY, "white", "black")
self.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL, stc.STC_MARK_EMPTY, "white", "black")
self.MarkerDefine(stc.STC_MARKNUM_FOLDEREND, stc.STC_MARK_EMPTY, "white", "black")
self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, stc.STC_MARK_EMPTY, "white", "black")
self.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, stc.STC_MARK_EMPTY, "white", "black")
self.Bind(stc.EVT_STC_MARGINCLICK, self.onMarginClick)

def onMarginClick(self, evt):
# fold and unfold as needed
if evt.GetMargin() == 2:
if evt.GetShift() and evt.GetControl():
self.FoldAll()
else:
lineClicked = self.LineFromPosition(evt.GetPosition())
if self.GetFoldLevel(lineClicked)&stc.STC_FOLDLEVELHEADERFLAG:
if evt.GetShift():
self.SetFoldExpanded(lineClicked, True)
self.Expand(lineClicked, True, True, 1)
elif evt.GetControl():
if self.GetFoldExpanded(lineClicked):
self.SetFoldExpanded(lineClicked, False)
self.Expand(lineClicked, False, True, 0)
else:
self.SetFoldExpanded(lineClicked, True)
self.Expand(lineClicked, True, True, 100)
else:
self.ToggleFold(lineClicked)

#---open
def open(self,fileName, language, encoding=DEFAULT_ENCODING, line=0):
self.setLanguage(language)
self.setText(open(fileName).read(),encoding)
wx.CallAfter(self.GotoLine,line)

def setText(self,text,encoding=DEFAULT_ENCODING):
self.encoding = encoding
self.SetText(text.decode(encoding))
self.Colourise(0, self.GetTextLength()) #make sure everything is lexed
wx.CallAfter(self.explorer.update)

def setLanguage(self,language):
if language in STC_LANGUAGES:
self.SetLexer(getattr(stc,'STC_LEX_%s'%language))
for style in range(50):
self.StyleSetSpec(style,"fore:%s,back:%s"%picasso())
return True
return False

#---hierarchy
def getHierarchy(self):
#[(level,line,text,parent,[children]),]
n = self.GetLineCount()+1
prevNode = root = Node(level=0,start=0,end=n,text='root',parent=None)
for line in range(n-1):
foldBits = self.GetFoldLevel(line)
if foldBits&stc.STC_FOLDLEVELHEADERFLAG:
#folding point
prevLevel = prevNode.level
level = foldBits&stc.STC_FOLDLEVELNUMBERMASK
text = self.GetLine(line)
node = Node(level=level,start=line,end=n,text=text)
if level == prevLevel:
#say hello to new brother or sister
node.parent = prevNode.parent
node.parent.children.append(node)
prevNode.end= line
elif level>prevLevel:
#give birth to child (only one level deep)
node.parent = prevNode
prevNode.children.append(node)
else:
#find your uncles and aunts (can be several levels up)
while level < prevNode.level:
prevNode.end = line
prevNode = prevNode.parent
node.parent = prevNode.parent
node.parent.children.append(node)
prevNode.end= line
prevNode = node
prevNode.end = line
return root

def selectNode(self,node):
"""If a tree item is right clicked select the corresponding code"""
self.GotoLine(node.start)
self.SetSelection(
self.PositionFromLine(node.start),
self.PositionFromLine(node.end),
)

class TreeCtrl(wx.TreeCtrl):
def __init__(self,*args,**keyw):
keyw['style'] = wx.TR_HIDE_ROOT|wx.TR_HAS_BUTTONS
wx.TreeCtrl.__init__(self,*args,**keyw)
self.root = self.AddRoot('foldExplorer root')
self.hierarchy = None
self.Bind(wx.EVT_RIGHT_UP, self.onRightUp)
self.Bind(wx.EVT_TREE_KEY_DOWN, self.update)

def update(self, event=None):
"""Update tree with the source code of the editor"""
hierarchy = self.editor.getHierarchy()
if hierarchy != self.hierarchy:
self.hierarchy = hierarchy
self.DeleteChildren(self.root)
self.appendChildren(self.root,self.hierarchy)

def appendChildren(self,wxParent,nodeParent):
for nodeItem in nodeParent.children:
wxItem = self.AppendItem(wxParent,nodeItem.text.strip())
self.SetPyData(wxItem,nodeItem)
self.appendChildren(wxItem,nodeItem)

def onRightUp(self,event):
"""If a tree item is right clicked select the corresponding code"""
pt = event.GetPosition();
wxItem, flags = self.HitTest(pt)
nodeItem = self.GetPyData(wxItem)
self.editor.selectNode(nodeItem)

class Frame(wx.Frame):
def __init__(self,title,size=(800,600)):
wx.Frame.__init__(self,None,-1,title,size=size)
splitter = wx.SplitterWindow(self)
self.explorer = TreeCtrl(splitter)
self.editor = Editor(splitter)
splitter.SplitVertically(
self.explorer,
self.editor,
int(self.GetClientSize()[1]/3)
)
self.explorer.editor = self.editor
self.editor.explorer = self.explorer
self.Show()

if __name__ == '__main__':
print 'This scintilla supports %d languages.'%len(STC_LANGUAGES)
print ', '.join(STC_LANGUAGES)
app = wx.PySimpleApp()
frame = Frame("Fold Explorer Demo")

fileName= sys.argv[-1] #choose file
frame.editor.open(fileName,'PYTHON','utf8') #choose language in caps

app.MainLoop()

Wednesday, February 28, 2007

SPE now works on wxPython2.8 and how to switch

Today I switched on my laptop from wxPython2.6 to wxPython2.8 SPE seemed to work quite OK already, except: the bottom panel took too much place and was not draggable to a smaller size. After fixing this annoying bug, I am happily coding now with SPE on wxPython 2.8 For those who want to try out before I release, follow the subversion instructions. I tested the new SPE on Ubuntu (wxPython 2.6 & 2.8) and on Windows (wxPython 2.8). If anybody knows a good subversion client for the Mac, please comment.

sudo wxPython 2.8 has major new features such as AUI (Advanced User Interface), anti-alias graphics (Graphicscontext) and a lot of new widgets (RichTextCtrl, CustomTreeCtrl, SearchCtrl a la Firefox, ...). This might be useful for SPE in the future, but at the moment SPE stays backwards compatible with wxPython 2.6.


On Feisty wxPython is already included in repositories, just type:

sudo apt-get install python-wxgtk2.8
On Edgy you need to use the wxPython coummunity repositories. Open /etc/apt/sources.list:
sudo gedit /etc/apt/sources.list
Add the following lines:
# wxPython APT repository at wxcommunity.com
deb http://wxpython.wxcommunity.com/apt/ubuntu/dapper /
deb-src http://wxpython.wxcommunity.com/apt/ubuntu/dapper /
Then copy and paste these lines in a terminal:
sudo apt-get update
sudo apt-get dist-upgrade
sudo apt-get install python-wxgtk2.8 python-wxtools wx2.8-i18n
If you want to use XRC gui designer, you need python-xml:
sudo apt-get install python-xml