Issue defining gstreamer video window on Raspbian - gstreamer

Using Gstreamer 'playbin' in python the 'set_window_handle' code tells Gstreamer the window id in which to render the video:
This works in standard Linux Mate and also on a Raspberry Pi 3 running Ubuntu-Mate. However, running on the same Raspberry but running an up to date Raspbian OS, Gstreamer totally ignores the instruction to run in a specific window, instead it creates its own window, smack in the middle of the screen.
This wouldn't be an issue, if the new window was capable of being manipulated, moved and/or resized but it can't. It cannot be moved, it cannot be closed and the mouse pointer vanishes behind it.
Does anyone know if this is a bug in Raspbian, X windows or Gstreamer or have I not spotted some change that has to be implemented for this to work on the Raspbian OS?
Here is a minimal, working example, which illustrates the behaviour described above.
#!/usr/bin/env python
import os,time
import wx
import gi
gi.require_version('Gst', '1.0')
gi.require_version('GstVideo', '1.0')
from gi.repository import Gst
from gi.repository import GstVideo
class Player(wx.App):
def OnInit(self):
window = wx.Frame(None)
window.SetTitle("Gstreamer Player Test")
window.SetSize((-1,-1))
window.Bind(wx.EVT_CLOSE,self.close)
vbox = wx.BoxSizer(wx.VERTICAL)
hbox = wx.BoxSizer(wx.HORIZONTAL)
self.fileid = wx.TextCtrl(window,style=wx.TE_PROCESS_ENTER)
self.fileid.SetToolTipString("Enter full path to media file")
hbox.Add(self.fileid, 1)
self.start = wx.Button(window,label="Start")
hbox.Add(self.start, 0)
self.start.Bind(wx.EVT_BUTTON, self.control)
self.fileid.Bind(wx.EVT_TEXT_ENTER, self.control)
vbox.Add(hbox, 0, wx.EXPAND, 0)
self.video_window = wx.Panel(window)
vbox.Add(self.video_window,1,wx.EXPAND,5)
window.SetSizer(vbox)
window.Layout()
window.Show()
self.SetTopWindow(window)
Gst.init(None) #initialise gstreamer
self.player = Gst.ElementFactory.make("playbin", "player")
bus = self.player.get_bus()
bus.add_signal_watch() #hook up bus to signals from gstreamer
bus.enable_sync_message_emission()
bus.connect('message', self.on_message)
bus.connect('sync-message::element', self.on_sync_message)
return True
def control(self, event):
if self.start.GetLabel() == "Start":
fileid = self.fileid.GetValue()
if os.path.exists(fileid):
self.start.SetLabel("Stop")
fileid = "file://"+unicode(fileid)
self.player.set_property('uri', fileid)
self.player.set_state(Gst.State.PLAYING)
else:
print "File error - No such file"
else:
self.player.set_state(Gst.State.NULL)
self.start.SetLabel("Start")
def on_message(self, bus, message):
t = message.type
if t == Gst.MessageType.EOS: # media has ended
self.player.set_state(Gst.State.NULL)
self.button.SetLabel("Start")
elif t == Gst.MessageType.ERROR:
print "Player error"
self.player.set_state(Gst.State.NULL)
self.start.SetLabel("Start")
def on_sync_message(self, bus, message):
if message.get_structure() is None:
return True
message_name = message.get_structure().get_name()
if message_name == 'prepare-window-handle': #Assign the window id to display in
imagesink = message.src
imagesink.set_property('force-aspect-ratio', True) #Force size to fit window
X_id = self.video_window.GetHandle()
print ("Window Id:", X_id)
imagesink.set_window_handle(X_id)
return True
def close(self,event):
self.player.set_state(Gst.State.NULL)
time.sleep(0.1) #Allow a little time to reach Null state
event.Skip()
app = Player()
app.MainLoop()

The answer to this is that it is a bug.
Bugzilla link
Whether it is in gstreamer or the Rpi stack seems a moot point.
The resolution is to specify the videosink in the playbin pipeline.
So for this particular program is should read as follows:
#!/usr/bin/env python
import os,time
import wx
import gi
gi.require_version('Gst', '1.0')
gi.require_version('GstVideo', '1.0')
from gi.repository import Gst
from gi.repository import GstVideo
class Player(wx.App):
def OnInit(self):
window = wx.Frame(None)
window.SetTitle("Gstreamer Player Test")
window.SetSize((-1,-1))
window.Bind(wx.EVT_CLOSE,self.close)
vbox = wx.BoxSizer(wx.VERTICAL)
hbox = wx.BoxSizer(wx.HORIZONTAL)
self.fileid = wx.TextCtrl(window,style=wx.TE_PROCESS_ENTER)
self.fileid.SetToolTipString("Enter full path to media file")
hbox.Add(self.fileid, 1)
self.start = wx.Button(window,label="Start")
hbox.Add(self.start, 0)
self.start.Bind(wx.EVT_BUTTON, self.control)
self.fileid.Bind(wx.EVT_TEXT_ENTER, self.control)
vbox.Add(hbox, 0, wx.EXPAND, 0)
video_window = wx.Panel(window)
vbox.Add(video_window,1,wx.EXPAND,5)
window.SetSizer(vbox)
window.Layout()
window.Show()
self.SetTopWindow(window)
Gst.init(None) #initialise gstreamer
self.X_id = video_window.GetHandle()
self.player = Gst.ElementFactory.make("playbin", "player")
# self.ximagesink = Gst.ElementFactory.make("xvimagesink", None)
self.ximagesink = Gst.ElementFactory.make("ximagesink", None)
self.player.set_property('video-sink', self.ximagesink)
bus = self.player.get_bus()
bus.add_signal_watch() #hook up bus to signals from gstreamer
bus.enable_sync_message_emission()
bus.connect('message', self.on_message)
bus.connect('sync-message::element', self.on_sync_message)
return True
def control(self, event):
if self.start.GetLabel() == "Start":
fileid = self.fileid.GetValue()
if os.path.exists(fileid):
self.start.SetLabel("Stop")
fileid = "file://"+unicode(fileid)
self.player.set_property('uri', fileid)
self.player.set_state(Gst.State.PLAYING)
else:
print "File error - No such file"
else:
self.player.set_state(Gst.State.NULL)
self.start.SetLabel("Start")
def on_message(self, bus, message):
t = message.type
if t == Gst.MessageType.EOS: # media has ended
self.player.set_state(Gst.State.NULL)
self.button.SetLabel("Start")
elif t == Gst.MessageType.ERROR:
print "Player error"
self.player.set_state(Gst.State.NULL)
self.start.SetLabel("Start")
def on_sync_message(self, bus, message):
if message.get_structure() is None:
return True
message_name = message.get_structure().get_name()
if message_name == 'prepare-window-handle': #Assign the window id to display in
imagesink = message.src
# imagesink.set_property('force-aspect-ratio', True) #Defaults anyway
imagesink.set_window_handle(self.X_id)
return True
def close(self,event):
self.player.set_state(Gst.State.NULL)
time.sleep(0.1) #Allow a little time to reach Null state
event.Skip()
app = Player()
app.MainLoop()
However, if you are getting the same issue using "com.sun.star.comp.avmedia.Manager_GStreamer" in a LibreOffice macro, this does you no good whatsoever, because the Libreoffice developers made the same assumption that I did and there appears to be no way to define the videosink from within Libreoffice. In this case we will simply have to wait until the bug fix makes its way into a general release.
Notes: use ximagesink
using xvimagesink gives an error on Raspbian, as the Xv software has an issue where no adaptors are available.
Using glimagesink gives us the dreaded window stuck in the middle of the screen and it seems that eglesssink has been quietly retired from service, as it is not longer included in the gstreamer plugins on the Raspberry.

Related

PyQt4/QProcess issues with Nuke v9

PyQt4/QProcess issues with Nuke v9...
I am trying to utilize a QProcess to run renders in Nuke at my workplace. The reason why I want to use a QProcess is because I've setup this Task Manager with the help of the community at stackoverflow, which takes a list of commands and sequentially runs it one by one, and also allows me to display an output. You can view the question I posted here:
How to update UI with output from QProcess loop without the UI freezing?
Now I am trying to basically run Nuke renders through this "Task Manager". But every time I do it just gives me an error that the QProcess is destroyed while still running. I mean I tested this with subprocess and that worked totally fine. So i am not sure why the renders are not working through QProcess.
So to do more testing I just wrote a simplified version at home. The first issue I ran into though is that apparently PyQt4 couldn't be found from Nuke's python.exe. Even though I have PyQt4 for my main Python version. However apparently there is a compatibility issue with my installed PyQt4 since my main Python version is 2.7.12, while my Nuke's python version is 2.7.3. So i thought "fine then i'll just directly install PyQt4 inside my Nuke directory". So i grabbed this link and installed this PyQt version into my Nuke directory:
http://sourceforge.net/projects/pyqt/files/PyQt4/PyQt-4.10.3/PyQt4-4.10.3-gpl-Py2.7-Qt4.8.5-x64.exe
So i run my little test and seems to be doing the same thing as it does in my workplace, where the QProcess just gets destoryed. So i thought maybe adding "waitForFinished()" would maybe do something different, but then it gives me this error that reads:
The procedure entry point ??4QString##QEAAAEAV0#$$QEAV0##Z could not be located in the dynamic link library QtCore4.dll
And gives me this error as well:
ImportError: Failed to load C:\Program Files\Nuke9.0v8\nuke-9.0.8.dll
Now at this point I can't really do any more testing at home, and my studio is closed for the holidays. So i have two questions i'd like to ask:
1) What is this error I am seeing about "procedure entry point"? It only happens when i try to call something in a QProcess instance.
2) Why is my QProcess being destroyed before the render is finished?? How come this doesn't happen with subprocess? How can I submit a Nuke render job while acheiving the same results as subprocess?
Here is my test code:
import os
import sys
import subprocess
import PyQt4
from PyQt4 import QtCore
class Task:
def __init__(self, program, args=None):
self._program = program
self._args = args or []
#property
def program(self):
return self._program
#property
def args(self):
return self._args
class SequentialManager(QtCore.QObject):
started = QtCore.pyqtSignal()
finished = QtCore.pyqtSignal()
progressChanged = QtCore.pyqtSignal(int)
dataChanged = QtCore.pyqtSignal(str)
#^ this is how we can send a signal and can declare what type
# of information we want to pass with this signal
def __init__(self, parent=None):
# super(SequentialManager, self).__init__(parent)
# QtCore.QObject.__init__(self,parent)
QtCore.QObject.__init__(self)
self._progress = 0
self._tasks = []
self._process = QtCore.QProcess(self)
self._process.setProcessChannelMode(QtCore.QProcess.MergedChannels)
self._process.finished.connect(self._on_finished)
self._process.readyReadStandardOutput.connect(self._on_readyReadStandardOutput)
def execute(self, tasks):
self._tasks = iter(tasks)
#this 'iter()' method creates an iterator object
self.started.emit()
self._progress = 0
self.progressChanged.emit(self._progress)
self._execute_next()
def _execute_next(self):
try:
task = next(self._tasks)
except StopIteration:
return False
else:
print 'starting %s' % task.args
self._process.start(task.program, task.args)
return True
def _on_finished(self):
self._process_task()
if not self._execute_next():
self.finished.emit()
def _on_readyReadStandardOutput(self):
output = self._process.readAllStandardOutput()
result = output.data().decode()
self.dataChanged.emit(result)
def _process_task(self):
self._progress += 1
self.progressChanged.emit(self._progress)
class outputLog(QtCore.QObject):
def __init__(self, parent=None, parentWindow=None):
QtCore.QObject.__init__(self)
self._manager = SequentialManager(self)
def startProcess(self, tasks):
# self._manager.progressChanged.connect(self._progressbar.setValue)
self._manager.dataChanged.connect(self.on_dataChanged)
self._manager.started.connect(self.on_started)
self._manager.finished.connect(self.on_finished)
self._manager.execute(tasks)
#QtCore.pyqtSlot()
def on_started(self):
print 'process started'
#QtCore.pyqtSlot()
def on_finished(self):
print 'finished'
#QtCore.pyqtSlot(str)
def on_dataChanged(self, message):
if message:
print message
def nukeTestRender():
import nuke
nuke.scriptOpen('D:/PC6/Documents/nukeTestRender/nukeTestRender.nk')
writeNode = None
for node in nuke.allNodes():
if node.Class() == 'Write':
writeNode = node
framesList = [1, 20, 30, 40]
fr = nuke.FrameRanges(framesList)
# nuke.execute(writeNode, fr)
for x in range(20):
print 'random'
def run():
nukePythonEXE = 'C:/Program Files/Nuke9.0v8/python.exe'
thisFile = os.path.dirname(os.path.abspath("__file__"))
print thisFile
cmd = '"%s" %s renderCheck' %(nukePythonEXE, __file__)
cmd2 = [__file__, 'renderCheck']
cmdList = [Task(nukePythonEXE, cmd2)]
# subprocess.call(cmd, stdin=None, stdout=None, stderr=None, shell=False)
taskManager = outputLog()
taskManager.startProcess(cmdList)
taskManager._manager._process.waitForFinished()
if __name__ == "__main__":
print sys.argv
if len(sys.argv) == 1:
run()
elif len(sys.argv) == 2:
nukeTestRender()
I have managed to come up with an answer, so I will write in the details below:
Basically, I was getting the error with the installed PyQt4 because it was not compatible with my version of Nuke, so it is apparently more recommended to use PySide included in Nuke. However Nuke's Python executable cannot natively find PySide, a few paths needed to be added to the sys.path:
paths = ['C:\\Program Files\\Nuke9.0v8\\lib\\site-packages,
C:\\Users\\Desktop02\\.nuke',
'C:\\Program Files\\Nuke9.0v8\\plugins',
'C:\\Program Files\\Nuke9.0v8\\pythonextensions\\site-packages\\setuptools-0.6c11-py2.6.egg',
'C:\\Program Files\\Nuke9.0v8\\pythonextensions\\site-packages\\protobuf-2.5.0-py2.6.egg',
'C:\\Program Files\\Nuke9.0v8\\pythonextensions\\site-packages',
'C:\\Program Files\\Nuke9.0v8\\plugins\\modules',
'C:\\Program Files\\Nuke9.0v8\\configs\\Python\\site-packages',
'C:\\Users\\Desktop02\\.nuke\\Python\\site-packages']
for path in paths:
sys.path.append(path)
I found the missing paths by opening up both Nuke in GUI mode and the Python executable, and comparing both sys.path to see what the Python executable was lacking.
And to answer my own main question: if I call waitForFinished(-1) on the QProcess instance, this ignores the default 30sec limit of this function... Answer came from this thread:
QProcess and shell : Destroyed while process is still running
So here is my resulting working code:
import os
import sys
import subprocess
sysArgs = sys.argv
try:
import nuke
from PySide import QtCore
except ImportError:
raise ImportError('nuke not currently importable')
class Task:
def __init__(self, program, args=None):
self._program = program
self._args = args or []
#property
def program(self):
return self._program
#property
def args(self):
return self._args
class SequentialManager(QtCore.QObject):
started = QtCore.Signal()
finished = QtCore.Signal()
progressChanged = QtCore.Signal(int)
dataChanged = QtCore.Signal(str)
#^ this is how we can send a signal and can declare what type
# of information we want to pass with this signal
def __init__(self, parent=None):
# super(SequentialManager, self).__init__(parent)
# QtCore.QObject.__init__(self,parent)
QtCore.QObject.__init__(self)
self._progress = 0
self._tasks = []
self._process = QtCore.QProcess(self)
self._process.setProcessChannelMode(QtCore.QProcess.MergedChannels)
self._process.finished.connect(self._on_finished)
self._process.readyReadStandardOutput.connect(self._on_readyReadStandardOutput)
def execute(self, tasks):
self._tasks = iter(tasks)
#this 'iter()' method creates an iterator object
self.started.emit()
self._progress = 0
self.progressChanged.emit(self._progress)
self._execute_next()
def _execute_next(self):
try:
task = next(self._tasks)
except StopIteration:
return False
else:
print 'starting %s' % task.args
self._process.start(task.program, task.args)
return True
def _on_finished(self):
self._process_task()
if not self._execute_next():
self.finished.emit()
def _on_readyReadStandardOutput(self):
output = self._process.readAllStandardOutput()
result = output.data().decode()
self.dataChanged.emit(result)
def _process_task(self):
self._progress += 1
self.progressChanged.emit(self._progress)
class outputLog(QtCore.QObject):
def __init__(self, parent=None, parentWindow=None):
QtCore.QObject.__init__(self)
self._manager = SequentialManager(self)
def startProcess(self, tasks):
# self._manager.progressChanged.connect(self._progressbar.setValue)
self._manager.dataChanged.connect(self.on_dataChanged)
self._manager.started.connect(self.on_started)
self._manager.finished.connect(self.on_finished)
self._manager.execute(tasks)
#QtCore.Slot()
def on_started(self):
print 'process started'
#QtCore.Slot()
def on_finished(self):
print 'finished'
#QtCore.Slot(str)
def on_dataChanged(self, message):
if message:
print message
def nukeTestRender():
import nuke
nuke.scriptOpen('D:/PC6/Documents/nukeTestRender/nukeTestRender.nk')
writeNode = None
for node in nuke.allNodes():
if node.Class() == 'Write':
writeNode = node
framesList = [1, 20, 30, 40]
fr = nuke.FrameRanges(framesList)
nuke.execute(writeNode, fr)
# nuke.execute(writeNode, start=1, end=285)
for x in range(20):
print 'random'
def run():
nukePythonEXE = 'C:/Program Files/Nuke9.0v8/python.exe'
thisFile = os.path.dirname(os.path.abspath("__file__"))
print thisFile
cmd = '"%s" %s renderCheck' %(nukePythonEXE, sysArgs[0])
cmd2 = [sysArgs[0], 'renderCheck']
cmdList = [Task(nukePythonEXE, cmd2)]
# subprocess.call(cmd, stdin=None, stdout=None, stderr=None, shell=False)
taskManager = outputLog()
taskManager.startProcess(cmdList)
taskManager._manager._process.waitForFinished(-1)
if __name__ == "__main__":
print sys.argv
if len(sysArgs) == 1:
run()
elif len(sysArgs) == 2:
nukeTestRender()
For whatever reason, PySide refuses to load for me without the nuke module imported first. and also theres a known error when importing nuke it deletes all sys.argv arguments so thats gotta be stored somewhere first before the nuke import...

How to find the mouse hover positions (preferably timestamps) on a video?

I am trying to save the mouse hover positions in a given video. I need to load the video on a 'Load Video' button press. When the mouse is outside the canvas no (x,y) should be saved. I also want the video to streamed at much lower rate (say, 4 times slower). Presently, I have the following code:`
import Tkinter as tk
from Tkinter import *
import PIL.Image,PIL.ImageTk
import time
import cv2
class App:
def __init__(self, window, window_title, video_source=0):
self.window = window
self.window.title(window_title)
self.video_source = video_source
self.video_loaded=False
# open video source (by default this will try to open the computer webcam)
self.vid = MyVideoCapture(self.video_source)
# Create a canvas that can fit the above video source size
self.canvas = tk.Canvas(window, width = self.vid.width, height =
self.vid.height)
self.canvas.pack()
self.canvas.bind('<Motion>',self.canvas.motion)
#self.canvas.bind("<Enter>", self.on_enter)
#self.canvas.bind("<Leave>", self.on_leave)
# Button that lets the user take a snapshot
self.btn_snapshot=tk.Button(window, text="Snapshot", width=50,
command=self.snapshot)
self.btn_snapshot.pack(anchor=tk.CENTER, expand=True)
self.btn_collapse=tk.Button(window, text="Collapse", width=50,
command=self.collapse)
self.btn_collapse.pack(anchor=tk.CENTER, expand=True)
self.btn_load_video=tk.Button(window, text="Load Video", width=50,
command=self.load_video)
self.btn_load_video.pack(anchor=tk.CENTER, expand=True)
#if self.video_loaded==True:
# After it is called once, the update method will be automatically
called every delay milliseconds
self.delay = 15
self.update()
self.window.mainloop()
def load_video(self):
# open video source (by default this will try to open the computer
webcam)
self.vid = MyVideoCapture(self.video_source)
self.video_loaded=True
def snapshot(self):
# Get a frame from the video source
ret, frame = self.vid.get_frame()
if ret:
cv2.imwrite("frame-" + time.strftime("%d-%m-%Y-%H-%M-%S") + ".jpg",
cv2.cvtColor(frame, cv2.COLOR_RGB2BGR))
def collapse(self):
self.window.quit()
def motion(self):
self.x=self.canvas.winfo_pointerx
self.y=self.canvas.winfo_pointery
print('{},{}'.format(self.x, self.y))
#self.canvas.itemconfigure(text='({x},{y})'.format(x = self.x,
y=self.y))
#print('{},{}'.format(self.x, self.y))
#def motion(self):
# x, y = self.x, self.y
# print('{}, {}'.format(x, y))
#def on_enter(self, event):
# self.l2.configure(text="Hello world")
#def on_leave(self, enter):
# self.l2.configure(text="")
def update(self):
# Get a frame from the video source
ret, frame = self.vid.get_frame()
if ret:
self.photo = PIL.ImageTk.PhotoImage(image =
PIL.Image.fromarray(frame))
self.canvas.create_image(0, 0, image = self.photo, anchor = tk.NW)
self.window.after(self.delay, self.update)
class MyVideoCapture:
def __init__(self, video_source=0):
# Open the video source
video_source='./videofilename.wmv'
self.vid = cv2.VideoCapture(video_source)
if not self.vid.isOpened():
raise ValueError("Unable to open video source", video_source)
# Get video source width and height
self.width = self.vid.get(cv2.CAP_PROP_FRAME_WIDTH)
self.height = self.vid.get(cv2.CAP_PROP_FRAME_HEIGHT)
def get_frame(self):
if self.vid.isOpened():
ret, frame = self.vid.read()
if ret:
# Return a boolean success flag and the current frame converted
to BGR
return (ret, cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
else:
return (ret, None)
else:
return (ret, None)
# Release the video source when the object is destroyed
def __del__(self):
if self.vid.isOpened():
self.vid.release()
# Create a window and pass it to the Application object
root = tk.Tk()
App(root, "Tkinter and OpenCV")
When I run this, I get the following error:
Exception in Tkinter callback
Traceback (most recent call last):
File "/home/anaconda2/envs/my_env/lib/python2.7/lib-tk/Tkinter.py", line
1541, in __call__
return self.func(*args)
TypeError: motion() takes exactly 1 argument (2 given)
I want the function motion() to return the mouse hover positions. Appreciate help. Thanks in advance.
Here is the link from which the I got the main code.
The bind() function sends an event object to the callback function, but the motion() function only accepts self. Try:
def motion(self, event):
self.x=event.x
self.y=event.y
Binding the function for saving mouse positions can be done as in teh example below.
from tkinter import *
root = Tk()
root.geometry('300x200+800+50')
c = Canvas(root, bg='tan')
c.pack(fill='both', expand='yes')
def motion(event):
if follow:
print(event.x, event.y)
follow = False
def follow_motion(event):
global follow
follow = not follow
c.bind('<Motion>', motion)
c.bind('<Button-1>', follow_motion)
root.mainloop()
Click the left mouse button on the canvas and the function motion() is enabled. One more click disables it.

PyQt4 - can't get video to run with QMovie or Phonon

I'm having problems getting any video player to work with my PyQt4 setup (having tried both phonon and QMovie). The below QMovie script is from an example where several users commented it as functional. For me, it runs but only opens a window (with Loading... centered) that never actually plays the .gif (I've tried several working .gif files from numerous examples online, so the file is not the problem). I've commented out the results from running the three debugging steps as well.
What can I do next?
import sys
import os
import sip
sip.setapi('QVariant', 2)
from PyQt4 import QtGui, QtCore
class BusyLabel(QtGui.QWidget):
def __init__(self, gif, parent = None, text = None):
QtGui.QWidget.__init__(self, parent)
self.hlayout = QtGui.QHBoxLayout(self)
self.hlayout.setSpacing(0)
self.hlayout.setContentsMargins(0, 0, 0, 0)
self.setLayout(self.hlayout)
# Movie
self.movieLabel = QtGui.QLabel(self)
self.movieLabel.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding)
self.movieLabel.setAlignment(QtCore.Qt.AlignCenter)
self.movie = QtGui.QMovie(gif, QtCore.QByteArray(), self)
self.movie.setScaledSize(QtCore.QSize(20,20))
self.movie.setCacheMode(QtGui.QMovie.CacheAll)
self.movie.setSpeed(100)
print self.movie.isValid() #output = False
print self.movie.supportedFormats() #output = []
self.movieLabel.setMovie(self.movie)
self.hlayout.addWidget(self.movieLabel)
# Label
self.label = QtGui.QLabel(text)
self.hlayout.addWidget(self.label)
self.movie.start()
def setText(self, text):
self.label.setText(text)
def start(self):
self.show()
self.movie.start()
def stop(self):
self.hide()
self.movie.stop()
if __name__ == "__main__":
gif = 'test1.gif'
print os.path.exists(gif) #output = True
app = QtGui.QApplication(sys.argv)
player = BusyLabel(gif)
player.setText('Loading...')
player.start()
player.show()
sys.exit(app.exec_())
output:
True
False
[]
(For those curious about my other attempts, running a popular Phonon script gave the error: phonon backend plugin could not be loaded... I'll take anything at this point)
I'm providing here complete, working code that I've written to answer this (my) problem. All you need is PyQt4 and Matplotlib, I hope this might help someone else facing similar troubles down the road:
https://github.com/evanseitz/PyQt4_VideoPlayer

tkinter avoid GUI from freezing

I developed a simple Python application doing some stuff, then I decided to add a simple GUI using Tkinter.
The problem is that, while the I call a function called startprocess and begin doing stuff which is processor heavy and the window freezes.
I know it's a common problem and I've already read that I should use multithreads (very complicated, because the function updates the GUI too) or divide my code in different function, each one working for a little time. anyways is there any modification needed in below code to avoid GUI freezing?
import threading
import tkinter as tk
from tkinter import filedialog
from tkinter import messagebox
import os, datetime, sys, subprocess
import parselog_v1
# diplay messagebox window
def MessageBox(windowLable,msg):
messagebox.showinfo(windowLable, msg)
# check if Dir empty
def checkDirEmpty(work_path):
if os.path.isdir(work_path):
if not os.listdir(work_path):
print ("No Files found in directory")
MessageBox('Log Parser', 'No Files found in directory.')
else:
return True
# launch app in center of screen
def center_window(width=300, height=200):
# get screen width and height
screen_width = root.winfo_screenwidth()
screen_height = root.winfo_screenheight()
# calculate position x and y coordinates
x = (screen_width/2) - (width/2)
y = (screen_height/2) - (height/2)
root.geometry('%dx%d+%d+%d' % (width, height, x, y))
# application frame
class Application(tk.Frame):
def __init__(self, master=None):
tk.Frame.__init__(self, master)
self.pack()
self.createWidgets()
self.master.title("Log Parser")
def createWidgets(self):
self.Run_Main = tk.Button(self)
self.Run_Main["text"] = "Browse for logs"
self.Run_Main["fg"] = "blue"
self.Run_Main["command"] = self.startProcess
self.Run_Main.pack(side='left',padx=0)
self.QUIT = tk.Button(self)
self.QUIT["text"] = "Quit!"
self.QUIT["fg"] = "red"
self.QUIT["command"] = self.quit
self.QUIT.pack(side='right',padx=5)
def startProcess(self):
global Src_foldername
Src_foldername = filedialog.askdirectory()
Src_foldername = Src_foldername.replace("/", "\\")
print("Source folder: " + Src_foldername)
if checkDirEmpty(Src_foldername):
# process logs
# multithread
print("Processing...")
self.refresh()
threading.Thread(target=parselog_v1.main(Src_foldername))
# scroll text inside application frame
class scrollTxtArea:
def __init__(self, root):
frame = tk.Frame(root)
frame.pack()
self.textPad(frame)
return
class IORedirector(object):
'''A general class for redirecting I/O to this Text widget.'''
def __init__(self, text_area):
self.text_area = text_area
class StdoutRedirector(IORedirector):
'''A class for redirecting stdout to this Text widget.'''
def textPad(self, frame):
# add a frame and put a text area into it
textPad = tk.Frame(frame)
self.text = tk.Text(textPad, height=21, width=68)
self.text.config()
# add a vertical scroll bar to the text area
scroll = tk.Scrollbar(textPad)
self.text.configure(yscrollcommand=scroll.set,background="black", foreground="green")
# pack everything
self.text.pack(side=tk.LEFT, pady=2)
scroll.pack(side=tk.RIGHT, fill=tk.Y)
textPad.pack(side=tk.TOP)
self.text.insert("end", "Begin by selecting log folder..." + "\n")
self.text.configure(state='disabled') # disable text editing
sys.stdout = (self) # to begin logging stdio to GUI
return
def write(self, txt):
self.text.configure(state='normal')
self.text.insert('end', txt)
self.text.configure(state='disabled')
root = tk.Tk()
root.resizable(width=False, height=False)
center_window(500, 300) # launch in center of screen
app = Application(master=root)
scrollFrame = scrollTxtArea(root)
app.mainloop()
root.destroy()
You use thread in wrong way.
First: target= needs function name without () and arguments.
You can assign arguments to args= (it have to be tuple even if you have only one argument)
threading.Thread(target=parselog_v1.main, args=(Src_foldername,) )
Now your code runs parselog_v1.main as normal function, waits for result and it will assign this result as function name to taget= - so you have something like this:
result = parselog_v1.main(Src_foldername)
threading.Thread(target=result)
It stops mainloop so it can't get mouse/keyboard events, refresh window, etc. so it looks like window freeze.
Second: after you create thread correctly you have to start it
my_thread = threading.Thread(target=parselog_v1.main, args=(Src_foldername,) )
my_thread.start()

How to fade in/out on a Tkinter Frame

How can you fade in and/or out on a Tkinter.Frame or any other widget for that matter. All of the examples that I have seen have been for either root (Tkinter.Tk) or Toplevel setting the alpha e.g.
https://stackoverflow.com/a/22491808/1552953
Is it possible to apply this to an individual widget?
You can't do it on individual widgets. Transparency in Tkinter is only available for instances of Tk and Toplevel.
Based on Bryan's answer I came up with this solution, which he inadvertently provided most of the code for also.
One thing to note is if you move the main window the toplevel doesn't move with it...
import Tkinter
import Queue
class Flash(Tkinter.Toplevel):
def __init__(self, root, **options):
Tkinter.Toplevel.__init__(self, root, width=100, height=20, **options)
self.overrideredirect(True) # remove header from toplevel
self.root = root
self.attributes("-alpha", 0.0) # set transparency to 100%
self.queue = Queue.Queue()
self.update_me()
def write(self, message):
self.queue.put(message) # insert message into the queue
def update_me(self):
#This makes our tkinter widget threadsafe
# http://effbot.org/zone/tkinter-threads.htm
try:
while 1:
message = self.queue.get_nowait() # get message from the queue
# if a message is received code will execute from here otherwise exception
# http://stackoverflow.com/questions/11156766/placing-child-window-relative-to-parent-in-tkinter-pythons
x = root.winfo_rootx() # set x coordinate of root
y = root.winfo_rooty() # set y coordinate of root
width = root.winfo_width() # get the width of root
self.geometry("+%d+%d" % (x+width-self.winfo_width() ,y)) # place in the top right cornder of root
self.fade_in() # fade in when a message is received
label_flash = Tkinter.Label(self, text=message, bg='black', fg='white', padx=5, pady=5)
label_flash.pack(anchor='e')
self.lift(self.root)
def callback():
label_flash.after(2000, label_flash.destroy) # destroy the label after 5 seconds
self.fade_away() # fade away after 3 seconds
label_flash.after(3000, callback)
except Queue.Empty:
pass
self.after(100, self.update_me) # check queue every 100th of a second
# http://stackoverflow.com/questions/3399882/having-trouble-with-tkinter-transparency
def fade_in(self):
alpha = self.attributes("-alpha")
alpha = min(alpha + .01, 1.0)
self.attributes("-alpha", alpha)
if alpha < 1.0:
self.after(10, self.fade_in)
# http://stackoverflow.com/questions/22491488/how-to-create-a-fade-out-effect-in-tkinter-my-code-crashes
def fade_away(self):
alpha = self.attributes("-alpha")
if alpha > 0:
alpha -= .1
self.attributes("-alpha", alpha)
self.after(10, self.fade_away)
if __name__ == '__main__':
root = Tkinter.Tk()
root.minsize(700, 300)
root.geometry("700x500")
flash = Flash(root) # create toplevel instance
def callback():
# put a delay between each message so we can check the behaviour depending on the lenght of the delay between messages
import time
flash.write('Hello World')
time.sleep(1)
flash.write('Ready!')
time.sleep(2)
flash.write('Steady!')
time.sleep(4)
flash.write('Go!')
# create a thread to prevent the delays from blocking our GUI
import threading
t = threading.Thread(target=callback)
t.daemon = True
t.start()
root.mainloop()
exit()