I can't understand why I am loosing control of my GUI even though I am implementing a thread to play a .wav file. Can someone pin point what is incorrect?
#!/usr/bin/env python
import wx, pyaudio, wave, easygui, thread, time, os, sys, traceback, threading
import wx.lib.delayedresult as inbg
isPaused = False
isStopped = False
class Frame(wx.Frame):
def __init__(self):
print 'Frame'
wx.Frame.__init__(self, parent=None, id=-1, title="Jasmine", size=(720, 300))
#initialize panel
panel = wx.Panel(self, -1)
#initialize grid bag
sizer = wx.GridBagSizer(hgap=20, vgap=20)
#initialize buttons
exitButton = wx.Button(panel, wx.ID_ANY, "Exit")
pauseButton = wx.Button(panel, wx.ID_ANY, 'Pause')
prevButton = wx.Button(panel, wx.ID_ANY, 'Prev')
nextButton = wx.Button(panel, wx.ID_ANY, 'Next')
stopButton = wx.Button(panel, wx.ID_ANY, 'Stop')
#add widgets to sizer
sizer.Add(pauseButton, pos=(1,10))
sizer.Add(prevButton, pos=(1,11))
sizer.Add(nextButton, pos=(1,12))
sizer.Add(stopButton, pos=(1,13))
sizer.Add(exitButton, pos=(5,13))
#initialize song time gauge
#timeGauge = wx.Gauge(panel, 20)
#sizer.Add(timeGauge, pos=(3,10), span=(0, 0))
#initialize menuFile widget
menuFile = wx.Menu()
menuFile.Append(0, "L&oad")
menuFile.Append(1, "E&xit")
menuBar = wx.MenuBar()
menuBar.Append(menuFile, "&File")
menuAbout = wx.Menu()
menuAbout.Append(2, "A&bout...")
menuAbout.AppendSeparator()
menuBar.Append(menuAbout, "Help")
self.SetMenuBar(menuBar)
self.CreateStatusBar()
self.SetStatusText("Welcome to Jasime!")
#place sizer on panel
panel.SetSizer(sizer)
#initialize icon
self.cd_image = wx.Image('cd_icon.png', wx.BITMAP_TYPE_PNG)
self.temp = self.cd_image.ConvertToBitmap()
self.size = self.temp.GetWidth(), self.temp.GetHeight()
wx.StaticBitmap(parent=panel, bitmap=self.temp)
#set binding
self.Bind(wx.EVT_BUTTON, self.OnQuit, id=exitButton.GetId())
self.Bind(wx.EVT_BUTTON, self.pause, id=pauseButton.GetId())
self.Bind(wx.EVT_BUTTON, self.stop, id=stopButton.GetId())
self.Bind(wx.EVT_MENU, self.loadFile, id=0)
self.Bind(wx.EVT_MENU, self.OnQuit, id=1)
self.Bind(wx.EVT_MENU, self.OnAbout, id=2)
#Load file using FileDialog, and create a thread for user control while running the file
def loadFile(self, event):
foo = wx.FileDialog(self, message="Open a .wav file...", defaultDir=os.getcwd(), defaultFile="", style=wx.FD_MULTIPLE)
foo.ShowModal()
self.queue = foo.GetPaths()
self.threadID = 1
while len(self.queue) != 0:
self.song = myThread(self.threadID, self.queue[0])
self.song.start()
while self.song.isAlive():
time.sleep(2)
self.queue.pop(0)
self.threadID += 1
def OnQuit(self, event):
self.Close()
def OnAbout(self, event):
wx.MessageBox("This is a great cup of tea.", "About Jasmine", wx.OK | wx.ICON_INFORMATION, self)
def pause(self, event):
global isPaused
isPaused = not isPaused
def stop(self, event):
global isStopped
isStopped = not isStopped
class myThread (threading.Thread):
def __init__(self, threadID, wf):
self.threadID = threadID
self.wf = wf
threading.Thread.__init__(self)
def run(self):
global isPaused
global isStopped
self.waveFile = wave.open(self.wf, 'rb')
#initialize stream
self.p = pyaudio.PyAudio()
self.stream = self.p.open(format = self.p.get_format_from_width(self.waveFile.getsampwidth()), channels = self.waveFile.getnchannels(), rate = self.waveFile.getframerate(), output = True)
self.data = self.waveFile.readframes(1024)
isPaused = False
isStopped = False
#main play loop, with pause event checking
while self.data != '':
# while isPaused != True:
# if isStopped == False:
self.stream.write(self.data)
self.data = self.waveFile.readframes(1024)
# elif isStopped == True:
# self.stream.close()
# self.p.terminate()
self.stream.close()
self.p.terminate()
class App(wx.App):
def OnInit(self):
self.frame = Frame()
self.frame.Show()
self.SetTopWindow(self.frame)
return True
def main():
app = App()
app.MainLoop()
if __name__=='__main__':
main()
Your loadFile method, quite independently of the fact that it delegates song-playing to many threads (which it waits for in strange ways, but, that's another issue), is still monopolizing the wx event loop until it returns. Where you currently have a time.sleep, try adding app.Yield(True) (of course you need to make app visible at that point in your code: simplest though inelegant is to add a global app at the start of main.
Event-driven systems typically serve the event loop only when your various event handler methods return: if you have a long-running event handler, you need to explicitly yield control to the event loop once in a while. Various event systems offer different ways to do it: in wx, it's the Yield method which I just recommended you try. See the brief description un the docs.
Related
I have a complex application, with a GUI that needs to dialogue with some I/O devices and with some WebAPI. I put my wx.Frame class in the main file, as I read that the GUI should be in the main thread to avoid freezing
if __name__ == "__main__":
app = wx.App()
frame = Window()
app.MainLoop()
but still the GUI freezes very often, and sometimes it doesn't show at all and a message saying "My_app is not responding" appears.
All the I/O and webAPI management is done in separate threads that are created by frame. The only GUI elements that are not in the main file are the pages that compose my notebook
from PageOne import PageOne
from PageTwo import PageTwo
from PageThree import PageThree
...
self.page1 = PageOne(self.nb)
self.page2 = PageTwo(self.nb)
self.page3 = PageThree(self.nb)
self.nb.AddPage(self.page1, "Page1")
self.nb.AddPage(self.page2, "Page2")
self.nb.AddPage(self.page3, "Page3")
All the communications between secondary threads and the GUI are done using wx.lib.newevent.NewEvent() in the main file and wx.PostEvent(self.parent, my_evt) in the threads.
I am using wxpython 4.1.1 and Ubuntu 20.04.2 LTS.
Any suggestion on how to prevent the GUI from not responding or freezing? Is maybe a better idea to use multiprocessing instead of multithreading? I know that threads are usually better for I/O applications...but is it still true in my case where the threads are all enless loops?
def run(self):
while True:
do_stuff()
This is not an answer per se.
However it might help track down a solution.
Given that we have no idea, none, what your code is doing, no one can answer a question like this.
It's the equivalent of asking, How long is a piece of string?
The best advice is to use something like the code below, adapting the thread to perform something akin to what your thread/threads are doing and see if you can replicate the behaviour and hopefully find the cause.
import time
import wx
from threading import Thread
import wx.lib.newevent
progress_event, EVT_PROGRESS_EVENT = wx.lib.newevent.NewEvent()
class ThreadFrame(wx.Frame):
def __init__(self, title, parent=None):
wx.Frame.__init__(self, parent=parent, title=title)
panel = wx.Panel(self)
self.btn = wx.Button(panel,label='Stop Long running process', size=(200,30), pos=(10,10))
self.btn.Bind(wx.EVT_BUTTON, self.OnExit)
self.progress = wx.Gauge(panel,size=(240,10), pos=(10,50), range=240)
#Bind to the progress event issued by the thread
self.Bind(EVT_PROGRESS_EVENT, self.OnProgress)
#Bind to Exit on frame close
self.Bind(wx.EVT_CLOSE, self.OnExit)
self.Show()
self.mythread = TestThread(self)
#Enable the GUI to be responsive by briefly returning control to the main App
while self.mythread.isAlive():
time.sleep(0.1)
wx.GetApp().Yield()
continue
try:
self.OnExit(None)
except:
pass
def OnProgress(self, event):
self.progress.SetValue(event.count)
#or for indeterminate progress
#self.progress.Pulse()
def OnExit(self, event):
if self.mythread.isAlive():
self.mythread.terminate() # Shutdown the thread
self.mythread.join() # Wait for it to finish
self.Destroy()
class TestThread(Thread):
def __init__(self,parent_target):
Thread.__init__(self)
self.parent = parent_target
self.stopthread = False
self.time = time.time()
self.start() # start the thread
def run(self):
# A loop that will run for 2 minutes then terminate
while self.stopthread == False:
curr_loop = int(time.time() - self.time)
if curr_loop < 240:
time.sleep(0.1)
evt = progress_event(count=curr_loop)
#Send back current count for the progress bar
try:
wx.PostEvent(self.parent, evt)
except: # The parent frame has probably been destroyed
self.terminate()
else:
self.terminate()
def terminate(self):
self.stopthread = True
class MyPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
self.text_count = 0
self.thread_count = 0
self.parent=parent
btn = wx.Button(self, wx.ID_ANY, label='Start Long running process', size=(200,30), pos=(10,10))
btn.Bind(wx.EVT_BUTTON, self.Thread_Frame)
btn2 = wx.Button(self, wx.ID_ANY, label='Is the GUI still active?', size=(200,30), pos=(10,50))
btn2.Bind(wx.EVT_BUTTON, self.AddText)
self.txt = wx.TextCtrl(self, wx.ID_ANY, style= wx.TE_MULTILINE, pos=(10,90),size=(400,100))
def Thread_Frame(self, event):
self.thread_count += 1
frame = ThreadFrame(title='Threaded Task '+str(self.thread_count), parent=self.parent)
def AddText(self,event):
self.text_count += 1
txt = "Gui is still active " + str(self.text_count)+"\n"
self.txt.write(txt)
class MainFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title='Main Frame', size=(600,400))
panel = MyPanel(self)
self.Show()
if __name__ == '__main__':
app = wx.App(False)
frame = MainFrame()
app.MainLoop()
Python 2.7, WxPython 3.0.2
We are trying to automatically close an entire program under certain conditions. For various reasons, we can't just kill the process. We've had some level of success with it. We can close it if there's no modal dialogs, or a single modal dialog. Once we introduce the second modal dialog (nested), it fails to stop properly.
The actual error received appears to be:
wx._core.PyAssertionError: C++ assertion "IsRunning()" failed at ..\..\src\common\evtloopcmn.cpp(83) in wxEventLoopBase::Exit(): Use ScheduleExit() on not running loop
Here's a working example of our issue. The frame will automatically close after 5 seconds. Clicking the button will load a dialog. Clicking the button on the dialog will open another dialog. It works fine until the last dialog is opened.
from threading import Thread
from time import sleep
import wx
class MainFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title="TEST", size=(400, 400))
self.Show()
self.__someDialog = None
self.__myThread = None
self.__okButton = wx.Button(self, -1, "Press me")
self.Bind(wx.EVT_BUTTON, self.__onOK)
self.__myThread = Thread(target=self.__waitThenClose, name="Closer")
self.__myThread.setDaemon(True)
self.__myThread.start()
def __onOK(self, evt):
self.__someDialog = SomeDialog(self)
self.__someDialog.ShowModal()
def closeOpenDialogs(self):
lst = wx.GetTopLevelWindows()
for i in range(len(lst) - 1, 0, -1):
if isinstance(lst[i], wx.Dialog):
print "Closing " + str(lst[i])
lst[i].Close(True)
#lst[i].Destroy()
def __waitThenClose(self):
for x in range(0, 5):
print "Sleeping..."
sleep(1)
self.closeOpenDialogs()
wx.CallAfter(self.Close, True)
class SomeDialog(wx.Dialog):
def __init__(self, parent):
wx.Dialog.__init__(self, parent, id=-1, title='Some Dialog')
self.SetSize((300, 300))
self.__anotherDialog = None
self.__okButton = wx.Button(self, -1, "Press me")
self.Bind(wx.EVT_BUTTON, self.__onOK)
wx.EVT_CLOSE(self, self.__on_btn_cancel)
def __onOK(self, evt):
self.__anotherDialog = AnotherDialog(self)
self.__anotherDialog.ShowModal()
def __on_btn_cancel(self, event):
self.EndModal(wx.ID_CANCEL)
class AnotherDialog(wx.Dialog):
def __init__(self, parent):
wx.Dialog.__init__(self, None, id=-1, title='Another Dialog')
self.SetSize((200, 200))
wx.EVT_CLOSE(self, self.__on_btn_cancel)
def __on_btn_cancel(self, event):
self.EndModal(wx.ID_CANCEL)
if __name__ == "__main__":
app = wx.App()
mainFrame = MainFrame()
app.MainLoop()
I think what is happening here is that the first call to ShowModal() blocks the at the app level (not just the frame level) which is preventing the second dialog from becoming fully initialized. To work around this issue I would call Show() instead of ShowModal() and add wx.FRAME_FLOAT_ON_PARENT to the dialog style flags. You can also call Disable() on the parts of the program you don't want the user to interact with while the dialogs are open.
EDIT: Here is a working example:
from threading import Thread
from time import sleep
import wx
class MainFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title="TEST", size=(400, 400))
self.Show()
self.__someDialog = None
self.__okButton = wx.Button(self, -1, "Press me")
self.Bind(wx.EVT_BUTTON, self.__onOK)
self.__myThread = Thread(target=self.__waitThenClose, name="Closer")
self.__myThread.setDaemon(True)
self.__myThread.start()
def __onOK(self, evt):
self.__someDialog = SomeDialog(self)
self.__someDialog.ShowModal()
def closeOpenDialogs(self, evt=None):
lst = wx.GetTopLevelWindows()
for i in range(len(lst) - 1, 0, -1):
dialog = lst[i]
if isinstance(dialog, wx.Dialog):
print "Closing " + str(dialog)
# dialog.Close(True)
wx.CallAfter(dialog.Close)
# sleep(1)
# dialog.Destroy()
def __waitThenClose(self):
for x in range(0, 10):
print "Sleeping..."
sleep(1)
wx.CallAfter(self.closeOpenDialogs)
wx.CallAfter(self.Close, True)
class SomeDialog(wx.Dialog):
def __init__(self, parent):
wx.Dialog.__init__(self, parent, id=-1, title='Some Dialog')
self.SetSize((300, 300))
self.__anotherDialog = None
self.__okButton = wx.Button(self, -1, "Press me")
self.Bind(wx.EVT_BUTTON, self.__onOK)
wx.EVT_CLOSE(self, self.__on_btn_cancel)
def __onOK(self, evt):
self.__anotherDialog = AnotherDialog(self)
self.__anotherDialog.SetWindowStyleFlag(
wx.FRAME_FLOAT_ON_PARENT|wx.DEFAULT_DIALOG_STYLE)
self.__anotherDialog.Show()
def __on_btn_cancel(self, event):
event.Skip()
self.EndModal(wx.ID_CANCEL)
class AnotherDialog(wx.Dialog):
def __init__(self, parent):
wx.Dialog.__init__(self, parent, id=-1, title='Another Dialog')
self.SetSize((200, 200))
wx.EVT_CLOSE(self, self.__on_btn_cancel)
parent.Disable()
def __on_btn_cancel(self, event):
event.Skip()
self.GetParent().Enable()
# self.EndModal(wx.ID_CANCEL)
if __name__ == "__main__":
app = wx.App()
mainFrame = MainFrame()
app.MainLoop()
The only way to reliably gracefully close all the modal dialogs, whether they were explicitly opened by your own code or not, is to use wxModalDialogHook to remember all the opened dialogs and then close them all, in the reverse (i.e. LIFO) order, before quitting the application.
Unfortunately I don't know if wxModalDialogHook is available in Python.
I am working on a rather simple wxpython GUI, and would like to be able to have the escape key close the window. Right now I just have a close button that executes sys.exit(0), but I'd like the escape key to do this to.
Does anyone know a way to do this?
import win32clipboard
import wx
from time import sleep
import sys
class MainFrame(wx.Frame):
def __init__(self,title):
wx.Frame.__init__(self, None, title="-RRESI Rounder-", pos=(0,0), size=(210,160))
panel=Panel(self)
icon = wx.Icon('ruler_ico.ico', wx.BITMAP_TYPE_ICO)
self.SetIcon(icon)
class Panel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
x1=10; x2=110
y1=40; dy=25; ddy=-3
boxlength=80
self.button =wx.Button(self, label="GO", pos=(100,y1+dy*3))
self.Bind(wx.EVT_BUTTON, self.OnClick,self.button)
self.button.SetDefault()
self.button2 =wx.Button(self, label="Close", pos=(100,y1+dy*4))
self.Bind(wx.EVT_BUTTON, self.OnClose, self.button2)
self.button.SetDefault()
self.Bind(wx.EVT_KEY_UP, self.OnKeyUP)
self.label1 = wx.StaticText(self, label="Input Number:", pos=(x1,y1+dy*1))
self.Input = wx.TextCtrl(self, value="1.001", pos=(x2,ddy+y1+dy*1), size=(boxlength,-1))
self.label0 = wx.StaticText(self, label="Round to closest: 1/", pos=(x1,y1+dy*0))
self.Denominator = wx.TextCtrl(self, value="64", pos=(x2,ddy+y1+dy*0), size=(boxlength,-1))
self.label2 = wx.StaticText(self, label="Output Number:", pos=(x1,y1+dy*2))
self.display = wx.TextCtrl(self, value="1.0", pos=(x2,ddy+y1+dy*2), size=(boxlength,-1))
self.display.SetBackgroundColour(wx.Colour(232, 232, 232))
self.label3 = wx.StaticText(self, label=" ", pos=(x2+7,y1+dy*2+20)) #Copied
self.label4 = wx.StaticText(self, label="Type value and hit Enter", pos=(x1-5,y1-dy*1.5))
self.label5 = wx.StaticText(self, label="Output is copied", pos=(x1-5,y1-dy*1))
font = wx.Font(8, wx.DEFAULT, wx.NORMAL, wx.NORMAL)
self.label4.SetFont(font)
self.label5.SetFont(font)
def OnKeyUP(self, event):
keyCode = event.GetKeyCode()
if keyCode == wx.WXK_ESCAPE:
sys.exit(0)
def OnClose(self, event):
print "Closed"
sys.exit(0)
def OnClick(self,event):
print "You clicked the button!"
def openClipboard():
try:
win32clipboard.OpenClipboard()
except Exception, e:
print e
pass
def closeClipboard():
try:
win32clipboard.CloseClipboard()
except Exception, e:
print e
pass
def clearClipboard():
try:
openClipboard()
win32clipboard.EmptyClipboard()
closeClipboard()
except TypeError:
pass
def setText(txt):
openClipboard()
win32clipboard.EmptyClipboard()
win32clipboard.SetClipboardText(txt)
closeClipboard()
Denominator = float(self.Denominator.GetValue())
Input=float(self.Input.GetValue())
Output=round(Input*Denominator,0)/Denominator
self.display.SetValue(str(Output))
setText(str(Output))
self.label3.SetLabel("Copied")
self.Update()#force redraw
sleep(.5)
wx.Timer
self.label3.SetLabel(" ")
if __name__=="__main__":
app = wx.App(redirect=False) # Error messages don't go to popup window
frame = MainFrame("RRESI Rounder")
frame.Show()
app.MainLoop()
This sorta works ... albiet with some issues
[edit]ok EVT_CHAR_HOOK works much better than EVT_KEY_UP
import wx
class Test(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1, title='Event Test',
size=(200, 200))
panel = wx.Panel(self)
panel.SetFocus()
self.Bind(wx.EVT_CHAR_HOOK, self.OnKeyUP)
def OnKeyUP(self, event):
print "KEY UP!"
keyCode = event.GetKeyCode()
if keyCode == wx.WXK_ESCAPE:
self.Close()
event.Skip()
class App(wx.App):
"""Application class."""
def OnInit(self):
self.frame = Test()
self.frame.Show()
self.SetTopWindow(self.frame)
self.frame.SetFocus()
return True
if __name__ == '__main__':
app = App()
app.MainLoop()
Probably an easier way is to set the frame up as a dialogue with a cancel button (which you can hide):
class MainFrame(wx.Dialog):
...
def __init__(self, ...):
...
# the frame must have a cancel button
cancel_button = wx.Button(..., id=wx.ID_CANCEL)
# it still works if you hide it
cancel_button.Hide()
This seems to provide the default handling for the escape key. You could even bind it to an event handler to perform some actions before closing - if you do the handler must use event.Skip(True) to propagate the event or event.Skip(False) to stop the cancel operation.
Here's my situation. Let's say I would like to create a thread that continually prints the number 1. When a button is clicked the value should then change to 2. My issue is that I'm unsure how I change a variable in an already running thread. Here's my code:
import wx
from threading import Thread
import time
class testThread(Thread):
def __init__(self, parent):
self.parent = parent
Thread.__init__(self)
self.start()
def run(self):
while 1:
x = 1
print x
time.sleep(1)
class testGUI(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1, "Test", size=(500,270))
panel = wx.Panel(self, -1)
self.buttonStart = wx.Button(panel, -1, label="Start thread", pos=(0,0))
self.buttonChange = wx.Button(panel, -1, label="Change var", pos=(0,30))
panel.Bind(wx.EVT_BUTTON, self.startThread, id=self.buttonStart.GetId())
panel.Bind(wx.EVT_BUTTON, self.changeVar, id=self.buttonChange.GetId())
def startThread(self, event):
testThread(self)
def changeVar(self, event):
# DO SOMETHING HERE THAT CHANGES 'x' IN THREAD TO 2...
pass
if __name__ == '__main__':
app = wx.App(redirect=False)
frame = testGUI()
frame.Show(True)
app.MainLoop()
So the question is, what do I put in the function changeVar that will modify the contents of the variable x that is in the running thread? Thanks in advance!
You can't change local variables.
What you can do is this:
class testThread(Thread):
def __init__(self, parent):
self.parent = parent
Thread.__init__(self)
self.start()
def run(self):
self.value = 1
while 1:
print self.value
time.sleep(1)
class testGUI(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1, "Test", size=(500,270))
panel = wx.Panel(self, -1)
self.buttonStart = wx.Button(panel, -1, label="Start thread", pos=(0,0))
self.buttonChange = wx.Button(panel, -1, label="Change var", pos=(0,30))
panel.Bind(wx.EVT_BUTTON, self.startThread, id=self.buttonStart.GetId())
panel.Bind(wx.EVT_BUTTON, self.changeVar, id=self.buttonChange.GetId())
def startThread(self, event):
self.the_thread = testThread(self)
def changeVar(self, event):
# DO SOMETHING HERE THAT CHANGES 'x' IN THREAD TO 2...
self.the_thread.value = 2
if __name__ == '__main__':
app = wx.App(redirect=False)
frame = testGUI()
frame.Show(True)
app.MainLoop()
You don't need threads for that, at all. wx includes a wx.Timer that allows you to call your function with a time interval in the same thread. Chances are that wx includes a function to do what you want, too:
import wx
class testGUI(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1, "Test", size=(500,270))
panel = wx.Panel(self, -1)
self.buttonStart = wx.Button(panel, -1, label="Start timer", pos=(0,0))
self.buttonChange = wx.Button(panel, -1, label="Change var", pos=(0,30))
panel.Bind(wx.EVT_BUTTON, self.startTimer, id=self.buttonStart.GetId())
panel.Bind(wx.EVT_BUTTON, self.changeVar, id=self.buttonChange.GetId())
self.value = 1
self.timer = wx.Timer(self)
self.Bind(wx.EVT_TIMER, self.printer, self.timer)
def startTimer(self, event):
self.timer.Start(1000) # 1 second
def printer(self, event):
print self.value
def changeVar(self, event):
self.value = 2
if __name__ == '__main__':
app = wx.App(redirect=False)
frame = testGUI()
frame.Show(True)
app.MainLoop()
I'working on this nice example that shows a webcam output in a GTK widget with python and GStreamer:
http://pygstdocs.berlios.de/pygst-tutorial/webcam-viewer.html
here is the code:
#!/usr/bin/env python
import sys, os
import pygtk, gtk, gobject
import pygst
pygst.require("0.10")
import gst
class GTK_Main:
def __init__(self):
window = gtk.Window(gtk.WINDOW_TOPLEVEL)
window.set_title("Webcam-Viewer")
window.set_default_size(500, 400)
window.connect("destroy", gtk.main_quit, "WM destroy")
vbox = gtk.VBox()
window.add(vbox)
self.movie_window = gtk.DrawingArea()
vbox.add(self.movie_window)
hbox = gtk.HBox()
vbox.pack_start(hbox, False)
hbox.set_border_width(10)
hbox.pack_start(gtk.Label())
self.button = gtk.Button("Start")
self.button.connect("clicked", self.start_stop)
hbox.pack_start(self.button, False)
self.button2 = gtk.Button("Quit")
self.button2.connect("clicked", self.exit)
hbox.pack_start(self.button2, False)
hbox.add(gtk.Label())
window.show_all()
# Set up the gstreamer pipeline
self.player = gst.parse_launch ("v4l2src ! autovideosink")
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)
def start_stop(self, w):
if self.button.get_label() == "Start":
self.button.set_label("Stop")
self.player.set_state(gst.STATE_PLAYING)
else:
self.player.set_state(gst.STATE_NULL)
self.button.set_label("Start")
def exit(self, widget, data=None):
gtk.main_quit()
def on_message(self, bus, message):
t = message.type
if t == gst.MESSAGE_EOS:
self.player.set_state(gst.STATE_NULL)
self.button.set_label("Start")
elif t == gst.MESSAGE_ERROR:
err, debug = message.parse_error()
print "Error: %s" % err, debug
self.player.set_state(gst.STATE_NULL)
self.button.set_label("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":
# Assign the viewport
imagesink = message.src
imagesink.set_property("force-aspect-ratio", True)
imagesink.set_xwindow_id(self.movie_window.window.xid)
GTK_Main()
gtk.gdk.threads_init()
gtk.main()
What I'd like to do is have a method to take a snapshot of the current frame and save to disk.
I think there are 2 ways to do it:
- some gstreamer method (but i think I should at least modify the pipeline)
- grab the picture somehow with GTK itself
Any hint on this?
I have no experience with gstreamer or gtk, any help is really appreciated
Thanks a lot
Mauro
Thanks to OpenCV I managed to rewrite everything with wxPython (which i know better than pyGTK). Here is a full working example (whith snapshot!), if anyone interested.
Also checkout the OpenCV wiki here: http://opencv.willowgarage.com/wiki/wxpython
import wx
import opencv.cv as cv
import opencv.highgui as gui
class CvMovieFrame(wx.Frame):
TIMER_PLAY_ID = 101
def __init__(self, parent):
wx.Frame.__init__(self, parent, -1,)
sizer = wx.BoxSizer(wx.VERTICAL)
self.capture = gui.cvCreateCameraCapture(0)
frame = gui.cvQueryFrame(self.capture)
cv.cvCvtColor(frame, frame, cv.CV_BGR2RGB)
self.SetSize((frame.width + 300, frame.height + 100))
self.bmp = wx.BitmapFromBuffer(frame.width, frame.height, frame.imageData)
self.displayPanel= wx.StaticBitmap(self, -1, bitmap=self.bmp)
sizer.Add(self.displayPanel, 0, wx.ALL, 10)
self.shotbutton = wx.Button(self,-1, "Shot")
sizer.Add(self.shotbutton,-1, wx.GROW)
self.retrybutton = wx.Button(self,-1, "Retry")
sizer.Add(self.retrybutton,-1, wx.GROW)
self.retrybutton.Hide()
#events
self.Bind(wx.EVT_BUTTON, self.onShot, self.shotbutton)
self.Bind(wx.EVT_BUTTON, self.onRetry, self.retrybutton)
self.Bind(wx.EVT_PAINT, self.onPaint)
self.Bind(wx.EVT_CLOSE, self.onClose)
self.playTimer = wx.Timer(self, self.TIMER_PLAY_ID)
wx.EVT_TIMER(self, self.TIMER_PLAY_ID, self.onNextFrame)
self.fps = 8;
self.SetSizer(sizer)
sizer.Layout()
self.startTimer()
def startTimer(self):
if self.fps!=0: self.playTimer.Start(1000/self.fps)#every X ms
else: self.playTimer.Start(1000/15)#assuming 15 fps
def onRetry(self, event):
frame = gui.cvQueryFrame(self.capture)
cv.cvCvtColor(frame, frame, cv.CV_BGR2RGB)
self.bmp = wx.BitmapFromBuffer(frame.width, frame.height, frame.imageData)
self.startTimer()
self.shotbutton.Show()
self.retrybutton.Hide()
self.hasPicture = False
self.Layout()
event.Skip()
def onShot(self, event):
frame = gui.cvQueryFrame(self.capture)
self.playTimer.Stop()
gui.cvSaveImage("foo.png", frame)
self.hasPicture = True
self.shotbutton.Hide()
self.retrybutton.Show()
self.Layout()
event.Skip()
def onClose(self, event):
try:
self.playTimer.Stop()
except:
pass
self.Show(False)
self.Destroy()
def onPaint(self, evt):
if self.bmp:
self.displayPanel.SetBitmap(self.bmp)
evt.Skip()
def onNextFrame(self, evt):
frame = gui.cvQueryFrame(self.capture)
if frame:
cv.cvCvtColor(frame, frame, cv.CV_BGR2RGB)
self.bmp = wx.BitmapFromBuffer(frame.width, frame.height, frame.imageData)
self.Refresh()
evt.Skip()
if __name__=="__main__":
app = wx.App()
f = CvMovieFrame(None)
f.Centre()
f.Show(True)
app.MainLoop()
I'm pretty sure that you could do:
self.movie_window.window.get_image(0, 0, 500, 400)
To get a GtkImage with the last frame from the webcam. The 500 and 400 is the width and the height of the window.