I'm developing a Kodi add-on that uses a custom overlay. It's almost completely working now, except for one runtime error I get every time I start a video, after reaching 5 seconds in the video ("Error Contents: Control does not exist in window"). Here's the code for addon.py below:
import xbmc, xbmcaddon, xbmcgui, os
ADDON = xbmcaddon.Addon()
addonpath = ADDON.getAddonInfo('path')
class OverlayBackground(object):
def __init__(self):
self.showing = False
self.window = xbmcgui.Window()
origin_x = 0
origin_y = 0
window_w = self.window.getWidth()
window_h = self.window.getHeight()
self._background = xbmcgui.ControlImage(origin_x, origin_y, window_w, window_h, os.path.join(addonpath,"resources","skins","default","media","background.png"))
def show(self):
self.showing=True
self.window.addControl(self._background)
def hide(self):
self.showing=False
self.window.removeControl(self._background)
def _close(self):
if self.showing:
self.hide()
else:
pass
try:
self.window.clearProperties()
except: pass
if (__name__ == '__main__'):
monitor = xbmc.Monitor()
while not monitor.abortRequested():
if xbmc.Player().isPlaying():
theOverlay = OverlayBackground()
if(xbmc.Player().getTime() < 5):
theOverlay.show()
else:
theOverlay.hide()
xbmc.sleep(1000)
The error came from "self.window.removeControl(self._background)", as mentioned in this paste bin. It seems odd, because I'm sure that the control was added prior to running this command, so I'm not sure what's causing this command to crash the add-on.
I tried asking over in the Kodi forums for help, and was told that I need to get the ID of the player that I'm controlling (and linked to the documentation for Control), but I'm not sure how to implement that, even after researching the documentation extensively.
I would highly appreciate any clarification on this. Thank you so much in advance!
you need to check if the overlay already removed
def show(self):
if not self.showing:
self.showing=True
self.window.addControl(self._background)
def hide(self):
if self.showing:
self.showing=False
self.window.removeControl(self._background)
Related
I’m writing a thing in maya and have run in to trouble. Really don’t know what I did, I was going to adress something else when this happened, the last thing I did was add a button to the layout.
I have been messing around with this for a long time now and as far as I can tell, the uiScript flag doesn’t like arguments passed in the method call…? what happens is, it never sets the restore flag to True so that bit never hits. resulting in it spawning multiple windows in maya. I’m also trying to figure out where the cmds.deleteUI try clause should go, not entrirely sure where I had it before this happened. If anyone could offer any insight I would be most grateful, cheers /S
in the script below, I have replaced an instance of my ui with just a button, it makes no difference on the behaviour.
code:
//////////
from PySide2 import QtWidgets, QtCore
from maya.app.general.mayaMixin import MayaQWidgetDockableMixin
import maya.OpenMayaUI as mui
import maya.cmds as cmds
import weakref
if not 'customMixinWindow' in globals():
customMixinWindow = None
class DockableWidget(MayaQWidgetDockableMixin, QtWidgets.QWidget):
instances = list()
CONTROL_NAME = 'customMixinWindow'
def __init__(self, parent=None):
super(DockableWidget, self).__init__(parent=parent)
DockableWidget.delete_instances()
self.__class__.instances.append(weakref.proxy(self))
self.main_layout = QtWidgets.QVBoxLayout()
self.button = QtWidgets.QPushButton()
self.main_layout.addWidget(self.button)
self.setLayout(self.main_layout)
self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
#staticmethod
def delete_instances():
print "deleting"
for ins in DockableWidget.instances:
try:
ins.setParent(None)
ins.deleteLater()
except:
pass
DockableWidget.instances.remove(ins)
del ins
def DockableWidgetUIScript(restore=False):
global customMixinWindow
if restore == True:
restoredControl = mui.MQtUtil.getCurrentParent()
customMixinWindow = DockableWidget()
if customMixinWindow is None:
#customMixinWindow = DockableWidget()
customMixinWindow.setObjectName('customMayaMixinWindow')
if restore == True:
mixinPtr = mui.MQtUtil.findControl(customMixinWindow.objectName())
mui.MQtUtil.addWidgetToMayaLayout(long(mixinPtr), long(restoredControl))
else:
try:
cmds.workspaceControl('customMayaMixinWindowWorkspaceControl', e=True, close=True)
cmds.deleteUI('customMayaMixinWindowWorkspaceControl')
except:
pass
customMixinWindow.show(dockable=True, restore=True, height=400, width=400, uiScript='import dockWin; dockWin.DockableWidgetUIScript(restore=True)')
def main():
ui = DockableWidgetUIScript()
return ui
if __name__ == 'dockWin':
main()
ok, problem solved.
after a lot of troubleshooting, it turned out to be a QSpacerItem in my ui that caused the window to open once and then crash maya the second time. which made me think it had something to do with the deleteUI stuff.
I had the QSpacerItem added like so:
self.spacerFive = QtWidgets.QSpacerItem(5 , 5)
self.myLayout.addSpacerItem(self.spacerFive)
this made maya crash out completely when deleteing the UI/workspaceControl…
no idea why, garbage collection?
this fixed it:
self.myLayout.addSpacerItem(QtWidgets.QSpacerItem(5, 5))
alright, everything working as expected again. cheers /S
I'm trying to develop a software with PyQt, but I often get stuck on software crashes without debug information (only the exit code 0xC0000409). I'm using QThread, and I wrote a system like this:
class serialThreadC(QThread):
updateOutBox = QtCore.pyqtSignal(str)
updateStatus = QtCore.pyqtSignal(int)
def __init__(self):
super(serialThreadC, self).__init__()
self.ser = False
self.state = 0
self.serialEnabled = False
def run(self):
while True:
if self.state == -3 or self.state == -2:
if self.SerialEnabled:
self.updatePB(20)
elif self.state == 0:
if self.serialEnabled:
self.updatePB(20)
def ConnDisconn(self):
self.serialEnabled = not self.serialEnabled
def updatePB(self, stat):
self.state = stat
self.updateStatus.emit(self.state)
serialThread = serialThreadC()
serialThread.start()
## sw is a QDialog already loaded
serialThread.updateOutBox.connect(sw.updateOutBox)
serialThread.updateStatus.connect(sw.updateStatus)
sw.PB_ConnDisconn.clicked.connect(serialThread.ConnDisconn)
I have crashes when I read/write serialEnabled in run() or in ConnDisconn(). I know that PyQt is not thread-safe and that a wrong handling of variables gives crashes of my type, but I can't understand what is wrong with my code. My idea (maybe wrong) is that all serialThread methods are executed on the same thread, also if they are connected to a gui (main thread). Is that wrong? In the same way, I emit events from serialThread and I connected them to the GUI, but that never gave me problems.
Can you see the mistake I made? Is there a way to debug the code if there is a crash without other infos? (I use PyCharm 2017.1.3).
PyQt is thread-safe to the same extent that Qt is thread-safe. The Qt docs will tell you which parts of their API are guaranteed to be so, and under what circumstances.
Cross-thread signals are thread-safe, so calling the updatePB method in your example is okay. Your ConnDisconn method is not thread-safe, but that has got nothing to do with PyQt or Qt - it's just a consequence of how you wrote it. The serialEnabled attribute could be read/written by two threads simultaneously, so the behaviour is strictly undefined. A thread-safe way of writing this would be to use a mutex, like so:
class serialThreadC(QThread):
updateOutBox = QtCore.pyqtSignal(str)
updateStatus = QtCore.pyqtSignal(int)
def __init__(self):
super(serialThreadC, self).__init__()
self.ser = False
self.state = 0
self._mutex = QMutex()
self.serialEnabled = False
def ConnDisconn(self):
self._mutex.lock()
self.serialEnabled = not self.serialEnabled
self._mutex.unlock()
def run(self):
while True:
if self.state == -3 or self.state == -2:
self._mutex.lock()
if self.serialEnabled:
self.updatePB(20)
self._mutex.unlock()
elif self.state == 0:
self._mutex.lock()
if self.serialEnabled:
self.updatePB(20)
self._mutex.unlock()
(NB: if you're using any kind of IDE or debugger, and you are getting unexpected errors or crashes, your first step in diagnosing the problem should always be to test the code in a standard console. Quite often, the IDE or debugger itself can be the cause of the problem, or may mask error messages comming either from Python or from underlying libraries, such as Qt).
I set all float variables to int values and it worked
before
var1 = 10.0
var2 = 26.0
after
var1 = 10
var2 = 26
Here is the code sample:
class RunGui (QtGui.QMainWindow)
def __init__(self, parent=None):
...
QtCore.Qobject.connect(self.ui.actionNew, QtCore.SIGNAL("triggered()"), self.new_select)
...
def normal_output_written(self, qprocess):
self.ui.text_edit.append("caught outputReady signal") #works
self.ui.text_edit.append(str(qprocess.readAllStandardOutput())) # doesn't work
def new_select(self):
...
dialog_np = NewProjectDialog()
dialog_np.exec_()
if dialog_np.is_OK:
section = dialog_np.get_section()
project = dialog_np.get_project()
...
np = NewProject()
np.outputReady.connect(lambda: self.normal_output_written(np.qprocess))
np.errorReady.connect(lambda: self.error_output_written(np.qprocess))
np.inputNeeded.connect(lambda: self.input_from_line_edit(np.qprocess))
np.params = partial(np.create_new_project, section, project, otherargs)
np.start()
class NewProject(QtCore.QThread):
outputReady = QtCore.pyqtSignal(object)
errorReady = QtCore.pyqtSignal(object)
inputNeeded = QtCore.pyqtSignal(object)
params = None
message = ""
def __init__(self):
super(NewProject, self).__init__()
self.qprocess = QtCore.QProcess()
self.qprocess.moveToThread(self)
self._inputQueue = Queue()
def run(self):
self.params()
def create_new_project(self, section, project, otherargs):
...
# PyDev for some reason skips the breakpoints inside the thread
self.qprocess.start(command)
self.qprocess.waitForReadyRead()
self.outputReady.emit(self.qprocess) # works - I'm getting signal in RunGui.normal_output_written()
print(str(self.qprocess.readAllStandardOutput())) # prints empty line
.... # other actions inside the method requiring "command" to finish properly.
The idea is beaten to death - get the GUI to run scripts and communicate with the processes. The challenge in this particular example is that the script started in QProcess as command runs an app, that requires user input (confirmation) along the way. Therefore I have to be able to start the script, get all output and parse it, wait for the question to appear in the output and then communicate back the answer, allow it to finish and only then to proceed further with other actions inside create_new_project()
I don't know if this will fix your overall issue, but there are a few design issues I see here.
You are passing around the qprocess between threads instead of just emitting your custom signals with the results of the qprocess
You are using class-level attributes that should probably be instance attributes
Technically you don't even need the QProcess, since you are running it in your thread and actively using blocking calls. It could easily be a subprocess.Popen...but anyways, I might suggest changes like this:
class RunGui (QtGui.QMainWindow)
...
def normal_output_written(self, msg):
self.ui.text_edit.append(msg)
def new_select(self):
...
np = NewProject()
np.outputReady.connect(self.normal_output_written)
np.params = partial(np.create_new_project, section, project, otherargs)
np.start()
class NewProject(QtCore.QThread):
outputReady = QtCore.pyqtSignal(object)
errorReady = QtCore.pyqtSignal(object)
inputNeeded = QtCore.pyqtSignal(object)
def __init__(self):
super(NewProject, self).__init__()
self._inputQueue = Queue()
self.params = None
def run(self):
self.params()
def create_new_project(self, section, project, otherargs):
...
qprocess = QtCore.QProcess()
qprocess.start(command)
if not qprocess.waitForStarted():
# handle a failed command here
return
if not qprocess.waitForReadyRead():
# handle a timeout or error here
return
msg = str(self.qprocess.readAllStandardOutput())
self.outputReady.emit(msg)
Don't pass around the QProcess. Just emit the data. And create it from within the threads method so that it is automatically owned by that thread. Your outside classes should really not have any knowledge of that QProcess object. It doesn't even need to be a member attribute since its only needed during the operation.
Also make sure you are properly checking that your command both successfully started, and is running and outputting data.
Update
To clarify some problems you might be having (per the comments), I wanted to suggest that QProcess might not be the best option if you need to have interactive control with processes that expect periodic user input. It should work find for running scripts that just produce output from start to finish, though really using subprocess would be much easier. For scripts that need user input over time, your best bet may be to use pexpect. It allows you to spawn a process, and then watch for various patterns that you know will indicate the need for input:
foo.py
import time
i = raw_input("Please enter something: ")
print "Output:", i
time.sleep(.1)
print "Another line"
time.sleep(.1)
print "Done"
test.py
import pexpect
import time
child = pexpect.spawn("python foo.py")
child.setecho(False)
ret = -1
while ret < 0:
time.sleep(.05)
ret = child.expect("Please enter something: ")
child.sendline('FOO')
while True:
line = child.readline()
if not line:
break
print line.strip()
# Output: FOO
# Another line
# Done
I have 4 video files (different scenes of a movie).
There's a starting scene that will be played when I run the player.
And before that scene ends, let's say the video player reads an int value (0-100) from external file (all happens at runtime), and depending on that int value, it has to determine which scene to play next.
pseudo example:
if (x > 0 && x < 30)
videoSource = scene2
else if (x >= 30 && x < 60)
videoSource = scene3
else if (x >= 60 && x <= 100)
videoSource = scene 4
How can I make it change video sources at runtime, depending on that variable?
I don't care about the format of the video file, (Avi, mp4...) whatever works will be fine.
I don't know how to approach this problem. I've searched for something that has the potential to accomplish this, like pyglet or GStreamer, but I didn't find a clear solution.
EDIT: I have the basic player and video player with pyglet, and I was able to play the video without depending on a variable using this code:
import pyglet
vidPath="sample.mpg"
window = pyglet.window.Window()
player = pyglet.media.Player()
source = pyglet.media.StreamingSource()
MediaLoad = pyglet.media.load(vidPath)
player.queue(MediaLoad)
player.play()
#window.event
def on_draw():
window.clear()
if player.source and player.source.video_format:
player.get_texture().blit(0,0)
pyglet.app.run()
How would I go about this? Guidance in the right direction and/or some sample code would be highly appreciated.
Thanks in advance.
Answer revised based on comments
If your goal is to constantly read a file that is receiving writes from the output of another process, you have a couple aspects that need to be solved...
You either need to read a file periodically that is constantly being overwritten, or you need to tail the output of a file that is being appended to with new values.
Your script currently blocks when you start the pyglet event loop, so this file check will have to be in a different thread, and then you would have to communicate the update event.
I can't fully comment on step 2 because I have never used pyglet and I am not familiar with how it uses events or signals. But I can at least suggest half of it with a thread.
Here is a super basic example of using a thread to read a file and report when a line is found:
import time
from threading import Thread
class Monitor(object):
def __init__(self):
self._stop = False
def run(self, inputFile, secs=3):
self._stop = False
with open(inputFile) as monitor:
while True:
line = monitor.readline().strip()
if line.isdigit():
# this is where you would notify somehow
print int(line)
time.sleep(secs)
if self._stop:
return
def stop(self):
self._stop = True
if __name__ == "__main__":
inputFile = "write.txt"
monitor = Monitor()
monitorThread = Thread(target=monitor.run, args=(inputFile, 1))
monitorThread.start()
try:
while True:
time.sleep(.25)
except:
monitor.stop()
The sleep loop at the end of the code is just a way to emulate your event loop and block.
Here is a test to show how it would work. First I open a python shell and open a new file:
>>> f = open("write.txt", 'w+', 10)
Then you can start this script. And back in the shell you can start writing lines:
>>> f.write('50\n'); f.flush()
In your script terminal you will see it read and print the lines.
The other way would be if your process that is writing to this file is constantly overwriting it, you would instead just reread the file by setting monitor.seek(0) and calling readline().
Again this is a really simple example to get you started. There are more advanced ways of solving this I am sure. The next step would be to figure out how you can signal the pyglet event loop to call a method that will change your video source.
Update
You should review this section of the pyglet docs on how to create your own event dispatcher: http://pyglet.org/doc/programming_guide/creating_your_own_event_dispatcher.html
Again, without much knowledge of pyglet, here is what it might look like:
class VideoNotifier(pyglet.event.EventDispatcher):
def updateIndex(self, value):
self.dispatch_events('on_update_index', value)
VideoNotifier.register_event('on_update_index')
videoNotifier = VideoNotifier()
#videoNotifier.event
def on_update_index(newIndex):
# thread has notified of an update
# Change the video here
pass
And for your thread class, you would pass in the dispatcher instance, and use the updateIndex() event to notify:
class Monitor(object):
def __init__(self, dispatcher):
self._stop = False
self._dispatcher = dispatcher
def run(self, inputFile, secs=3):
...
...
# should notify video of new value
line = int(line_from_file)
self._dispatcher.updateIndex(line)
...
...
Hope that gets you started!
This question is unlikely to help any future visitors; it is only relevant to a small geographic area, a specific moment in time, or an extraordinarily narrow situation that is not generally applicable to the worldwide audience of the internet. For help making this question more broadly applicable, visit the help center.
Closed 11 years ago.
I have a small python application, which uses pyttsx for some text to speech.
How it works:
simply say whatever is there in the clipboard.
The program works as expected inside eclipse. But if run on cmd.exe it only works partly if the text on the clipboard is too large(a few paras). Why ?
when run from cmd, it prints statements , but the actual 'talking' doesn't work(if the clipboard text is too large
Here is a of the program part which actually does the talking: As can be seen the 'talking' part is handled inside a thread.
def saythread(queue , text , pauselocation, startingPoint):
saythread.pauselocation = pauselocation
saythread.pause = 0
saythread.engine = pyttsx.init()
saythread.pausequeue1 = False
def onWord(name, location, length):
saythread.pausequeue1 = queue.get(False)
saythread.pause = location
saythread.pauselocation.append(location)
if saythread.pausequeue1 == True :
saythread.engine.stop()
def onFinishUtterance(name, completed):
if completed == True:
os._exit(0)
def engineRun():
if len(saythread.pauselocation) == 1:
rate = saythread.engine.getProperty('rate')
print rate
saythread.engine.setProperty('rate', rate-30)
textMod = text[startingPoint:]
saythread.engine.say(text[startingPoint:])
token = saythread.engine.connect("started-word" , onWord )
saythread.engine.connect("finished-utterance" , onFinishUtterance )
saythread.engine.startLoop(True)
engineRun()
if saythread.pausequeue1 == False:
os._exit(1)
def runNewThread(wordsToSay, startingPoint):
global queue, pauselocation
e1 = (queue, wordsToSay, pauselocation, startingPoint)
t1 = threading.Thread(target=saythread,args=e1)
t1.start()
#wordsToSay = CLIPBOARD CONTENTS
runNewThread(wordsToSay,0)
Thanks
Edit: I have checked than the python version used is the same 2.7 . The command used to run the program in cmd : python d:\python\play\speech\speechplay.py
Checked that the problem is not in the code that reads the text from the clipboard.
You should check if your eclipse setup specifies custom environment variables for the project which do not exist outside Eclipse. Especially:
PYTHONPATH (and also additional projects on which your program could depend in your setup)
PATH
Use
import os
print os.environ['PATH']
print os.environ['PYTHONPATH']
at the beginning of your program to compare both settings.
Misc stylistic advices:
don't use os._exit, prefer sys.exit (you should only use os._exit in a child process after a call to os.fork, which is not available on Windows)
I think a threading.Event would be more appropriate than a queue.Queue
I'd use a subclass approach for the thread with methods rather than a function with inner functions
For example:
import threading
import sys
import pyttsx
class SayThread(threading.Thread):
def __init__(self, queue, text, pauselocation, startingPoint, debug=False):
threading.Thread.__init__(self)
self.queue = queue
self.text = text
self.pauselocation = pauselocation
self.startingPoint = startingPoint
self.pause = 0
self.engine = pyttsx.init(debug=debug)
self.pausequeue1 = False
def run(self):
if len(self.pauselocation) == 1:
rate = self.engine.getProperty('rate')
print rate
self.engine.setProperty('rate', rate-30)
textMod = self.text[self.startingPoint:]
self.engine.say(self.text[self.startingPoint:])
self.engine.connect("started-word", self.onWord )
self.engine.connect("finished-utterance", self.onFinishUtterance )
self.engine.startLoop(True)
if self.pausequeue1 == False:
sys.exit(1)
def onWord(self, name, location, length):
self.pausequeue1 = self.queue.get(False)
self.pause = location
self.pauselocation.append(location)
if self.pausequeue1 == True :
self.engine.stop()
def onFinishUtterance(self, name, completed):
if completed == True:
sys.exit(0)
def runNewThread(wordsToSay, startingPoint):
global queue, pauselocation
t1 = SayThread(queue, wordsToSay,
pauselocation, startingPoint)
t1.start()
#wordsToSay = CLIPBOARD CONTENTS
runNewThread(wordsToSay,0)
In fact, eclipse itself uses a commandline command to start it's apps.
You should check what command eclipse is giving to start the program. It might be a bit verbose, but you can start from there and test what is necessary and what isn't.
You can find out the commandline eclipse uses by running the program and then selecting the output in the debug window. Right-click it, select properties and you're done.
If you don't have a debug window you can open it window/show view/(other possibly)/debug.
turns out pythonpath wasn't set properly on my system.
Edit: turns out pythonpath isn't the problem. I have no idea whats the problem. arghhhhhhhhhhhhhhhhhhhhhhhh