Gstreamer webcam video feed in tkinter - python

I'm writing a program to get a video feed from a web cam and display it in a Tkinter window. I wrote the following code which I ran on Ubuntu 12.04.
#!/usr/bin/env python
import sys, os, gobject
from Tkinter import *
import pygst
pygst.require("0.10")
import gst
# Goto GUI Class
class Prototype(Frame):
def __init__(self, parent):
gobject.threads_init()
Frame.__init__(self, parent)
# Parent Object
self.parent = parent
self.parent.title("WebCam")
self.parent.geometry("640x560+0+0")
self.parent.resizable(width=FALSE, height=FALSE)
# Video Box
self.movie_window = Canvas(self, width=640, height=480, bg="black")
self.movie_window.pack(side=TOP, expand=YES, fill=BOTH)
# Buttons Box
self.ButtonBox = Frame(self, relief=RAISED, borderwidth=1)
self.ButtonBox.pack(side=BOTTOM, expand=YES, fill=BOTH)
self.closeButton = Button(self.ButtonBox, text="Close", command=self.quit)
self.closeButton.pack(side=RIGHT, padx=5, pady=5)
gotoButton = Button(self.ButtonBox, text="Start", command=self.start_stop)
gotoButton.pack(side=RIGHT, padx=5, pady=5)
# Set up the gstreamer pipeline
self.player = gst.parse_launch ("v4l2src ! video/x-raw-yuv,width=640,height=480 ! ffmpegcolorspace ! xvimagesink")
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):
if self.gotoButton["text"] == "Start":
self.gotoButton["text"] = "Stop"
self.player.set_state(gst.STATE_PLAYING)
else:
self.player.set_state(gst.STATE_NULL)
self.gotoButton["text"] = "Start"
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)
def main():
root = Tk()
app = Prototype(root)
app.pack(expand=YES, fill=BOTH)
root.mainloop()
if __name__ == '__main__':
main()
My problem is neither the ButtonBox nor the VideoBox show in the output window when the program is running. How can I fix this? I did look at other sites for possible solutions (for instance http://pygstdocs.berlios.de/#projects or Way to play video files in Tkinter?) however they have very limited information on what their code means.
After making the suggested alteration and a few others to get the buttons working, I realize that the display window is different from the main window when I run the program. Is there a way to get the video to display in the main window when using tkinter??

It looks like your Prototype class is a Tkinter Frame but you don't seem to have packed/placed it anywhere.
...
app = Prototype(root)
app.pack(expand=YES, fill=BOTH)
root.mainloop()

I finally came up with a solution to the question. I realised that the error was in the line
imagesink.set_xwindow_id(self.movie_window.window.xid)
which I changed to
imagesink.set_xwindow_id(self.movie_window.winfo_id())
The mistake is that I had used window.xid which is an attribute for gtk widgets. In tkinter winfo_id() returns the window identifier for tkinter widgets. For more information http://effbot.org/tkinterbook/widget.htm#Tkinter.Widget.winfo_id-method

Related

Tkinter not updating when changing variables

I'm tinkering with Tkinter and trying to check if my program is open on pressing a button, but the Tkinter is not updating my label. Why?
from win32gui import GetWindowRect, FindWindow
from tkinter import Tk, ttk, BooleanVar
class Bot:
root = Tk()
is_gta_open = BooleanVar(None, False)
def mta_open_string(self):
return "włączone" if self.is_gta_open.get() else "wyłączone"
def draw_gui(self):
frame = ttk.Frame(self.root, padding=10)
frame.grid()
ttk.Label(frame, text=f"Status gry: {self.mta_open_string()}").grid(column=0, row=0)
ttk.Button(frame, text="Odśwież", command=lambda: [self.try_finding_rect(), self.root.update()]).grid(column=1, row=0)
self.root.mainloop()
def try_finding_rect(self):
window_handle = FindWindow("Grand theft auto San Andreas", None)
if window_handle == 0:
self.is_gta_open.set(False)
return
self.is_gta_open.set(True)
def run(self):
self.try_finding_rect()
self.draw_gui()
if __name__ == "__main__":
Bot().run()
I'm updating the state using self.root.update method and using BooleanVar, so I don't know why this is not working.
I've put together a very minimal example app that should work as intended. I don't have a copy of GTA to test with so I used a different app, but it should function the same way with any app in principle:
import tkinter as tk
import ctypes
from tkinter import ttk
class Bot(tk.Tk): # init Tk
def __init__(self):
super.__init___()
APP_NAME = 'Grand theft auto San Andreas'
self.status_label = tk.Label(self, text='Press the button')
self.status_label.pack()
self.run_btn = ttk.Button(
self,
text='Click Me!',
command=lambda: self.check_for_app(APP_NAME)
)
self.run_btn.pack()
def check_for_app(app_name: str):
user32 = ctypes.WinDLL('user32')
if user32.FindWindowW(None, app_name):
self.status_label.config(text='Running')
else:
self.status_label.config(text='Not Running')
if __name__ == '__main__':
app = Bot()
app.mainloop()
Updating a variable won't change an f-string that uses the variable. You must explicitly configure the widget to show the new value.
To do that you'll need to keep a reference to the label widget, and then update the widget with the configure method.
def draw_gui(self):
...
self.status_label = ttk.Label(frame, text=f"Status gry: {self.mta_open_string()}")
self.status_label.grid(column=0, row=0)
...
def try_finding_rect(self):
...
self.is_gta_open.set(True)
self.status_label.configure(text=f"Status gry: {self.mta_open_string()}")
Personally I recommend using a proper method for the button rather than a complicated lambda. That will make the code easier to read and easier to debug.
For example:
def draw_gui(self):
...
ttk.Button(frame, text="Odśwież", command=self.update_status)
...
def update_status(self):
self.try_finding_rect(),
self.status_label.configure(text=f"Status gry: {self.mta_open_string()}")

Tkinter Get Text entry in Notebook page

I have created a notebook and added a frame to it:
nb = ttk.Notebook(root, style="TNotebook")
page1 = ttk.Frame(nb, style='Frame1.TFrame')
layout1(page1)
nb.add(page1, text='Welcome')
So i have a function layout1, the first page of the notebook,
i added to it a Text:
def layout1(page):
entry = Text(page, width=20)
entry.place(relx=0.03, rely=0.1, height=400)
Button(page, text='EXECUTE', command=import_entry).place(relx=0.5, rely=0.6)
And next i have my import_entry function:
def import_entry():
result = entry.get()
print(result)
I can't get the entry because of accessibilty of variables in function. So, how can i get it?
Here is an example of how you should structure your app with a class:
import tkinter
import tkinter.ttk as ttk
class App(tkinter.Tk):
def __init__(self):
super().__init__()
# assign on_closing method to window close event
self.protocol("WM_DELETE_WINDOW", self.on_closing)
self.title("Example App")
self.geometry("600x500")
self.button_1 = tkinter.Button(master=self, text="Test", command=self.button_event)
self.button_1.pack(pady=10)
# create more widgets ...
def button_event(self, event):
print("button pressed")
def on_closing(self):
# code that needs to happen when gets closed
self.destroy() # controlled closing of window with .destroy()
if __name__ == "__main__":
app = App()
app.mainloop()

How to listen to terminal on a Tkinter application?

I have a simple graphics program that shows some instructions and touch buttons to a touch screen in a remote raspberry.
Instead of executing directly, I run it through a SSH connection, so I have in my desktop al application log.
I would like to have some short of interaction from the console I run the script, like execute some functions or change values of some variables.
Is that even possible?
I do not want to create a console inside the TKinter window, as alessandro asked:
How to embed a terminal in a Tkinter application?
not sure if I should use a short of subprocess, as user1941008, but htis seemes too complicate
Write to terminal in Tkinter GUI
and I do prefer not to create a client/server setup or a middle buffer for this thing, too complex, and i will have to rewrite things to send the logs to new program.
I add a little little version of my code:
#!/usr/bin/env python3
import tkinter as tk
class _tkapp(tk.Frame):
def __init__(self, master=None):
super().__init__(master)
self.master = master
self.pack()
self.create_widgets()
def create_widgets(self):
self.redButton = tk.Button(self, text='Make me red', command=self.paintMeRed)
self.redButton.pack(side='top')
self.blueButton = tk.Button(self, text='Make me blue', command=self.paintMeBlue)
self.blueButton.pack(side='top')
self.quit = tk.Button(self, text='QUIT', fg='red', command=self.master.destroy)
self.quit.pack(side='bottom')
def paintMeRed(self):
tk_root.configure(background='red')
print('user click on RED')
def paintMeBlue(self):
tk_root.configure(background='blue')
print('user click on BLUE')
tk_root = tk.Tk()
tk_root.geometry("200x120")
tk_app = _tkapp(master=tk_root)
tk_app.mainloop()
this allow me to see on console what user cliked,
my objetive, change the color also from console
Here is the answer for your question. My other (deleted) answer didn't fit to your question.
Unfortunately you can't do it without sockets but I add some easy to adapt methods (init_remote_execution, listener, do_remite_call and cleanup) to your example that you can copy and paste in your actually application. You only need to adapt the do_remote_call method:
#!/usr/bin/env python3
import socket
import tkinter as tk
from queue import Queue
from threading import Thread
from uuid import uuid1
UDP_HOST = ""
UDP_PORT = 5005
RECV_BUFFER = 1020
class _tkapp(tk.Frame):
def __init__(self, master=None):
super().__init__(master)
self.master = master
self.pack()
self.create_widgets()
self.init_remote_execution()
self.master.protocol("WM_DELETE_WINDOW", self.cleanup) # call cleanup on exit
def init_remote_execution(self):
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.sock.bind((UDP_HOST, UDP_PORT))
self.endmsg = str(uuid1()) # Random string to stop threads
self.queue = Queue()
self.treads_running = True
self.listener_thread = Thread(target=self.listener)
self.worker_thread = Thread(target=self.do_remote_call)
self.listener_thread.start()
self.worker_thread.start()
def listener(self):
print("listen")
while self.treads_running:
data, addr = self.sock.recvfrom(RECV_BUFFER)
data = data.decode().strip()
print("from {addr}: {data}".format(addr=addr, data=data))
if data == self.endmsg:
self.treads_running = False
self.queue.put(data)
self.sock.close()
def do_remote_call(self):
while self.treads_running:
data = self.queue.get()
if data == self.endmsg:
print("Bye")
elif data == "click RED":
self.paintMeRed()
elif data == "click BLUE":
self.paintMeBlue()
else:
print(">>> unknown command")
def cleanup(self):
print("cleanup")
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.sendto(self.endmsg.encode(), ("127.0.0.1", UDP_PORT))
self.listener_thread.join()
self.worker_thread.join()
self.master.destroy()
def create_widgets(self):
self.redButton = tk.Button(self, text="Make me red", command=self.paintMeRed)
self.redButton.pack(side="top")
self.blueButton = tk.Button(self, text="Make me blue", command=self.paintMeBlue)
self.blueButton.pack(side="top")
self.quit = tk.Button(
self, text="QUIT", fg="red", command=self.cleanup
) # call cleanup!!!
self.quit.pack(side="bottom")
def paintMeRed(self):
tk_root.configure(background="red")
print("user click on RED")
def paintMeBlue(self):
tk_root.configure(background="blue")
print("user click on BLUE")
if __name__ == "__main__":
tk_root = tk.Tk()
tk_root.geometry("200x120")
tk_app = _tkapp(master=tk_root)
tk_app.mainloop()
It is important, that you call the cleanup method on the quit button (otherwise the threads will not stop and the application will hang)!
Now you can "click" the "make me blue" button by sending an udp message (click BLUE) to Port 5005 on your raspberry.
With the tool netcat on the raspberry (you may need to install it with apt) you can send the commad to click blue like this:
echo "click BLUE" | nc -uw0 127.0.0.1 5005
With python:
#!/usr/bin/env python3
import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.sendto('click BLUE'.encode(), ('127.0.0.1', 5005)
sock.close()
From your desktop you have to switch the '127.0.0.1' with the ip address of your raspberry.
Here an even simpler version using the cmd module from python. This module allows you to write an repl (read evaluete print loop) for any thing.
#!/usr/bin/env python3
import cmd
import os
import signal
import tkinter as tk
from threading import Thread
class _tkapp(tk.Frame):
def __init__(self, master=None):
super().__init__(master)
self.master = master
self.pack()
self.create_widgets()
def create_widgets(self):
self.redButton = tk.Button(self, text="Make me red", command=self.paintMeRed)
self.redButton.pack(side="top")
self.blueButton = tk.Button(self, text="Make me blue", command=self.paintMeBlue)
self.blueButton.pack(side="top")
self.quit = tk.Button(self, text="QUIT", fg="red", command=self.master.destroy)
self.quit.pack(side="bottom")
def paintMeRed(self):
tk_root.configure(background="red")
print("user click on RED")
def paintMeBlue(self):
tk_root.configure(background="blue")
print("user click on BLUE")
class Command(cmd.Cmd):
intro = "Welcome to the repl of the Tk app. Type help or ? to list commands.\n"
prompt = ""
def __init__(self, tkapp):
super().__init__()
self.tkapp = tkapp
# New commands must start with do_ and have one argument.
# The docstring is the help text.
# They must not return anything!
# only the do_quit returns True to indicate that the command loop must stop
def do_paint(self, arg):
"""Paint the background in the color red or blue: PAINT RED"""
clean_arg = arg.strip().lower()
if clean_arg == "red":
self.tkapp.paintMeRed()
elif clean_arg == "blue":
self.tkapp.paintMeBlue()
else:
print("Can't paint the color %s." % clean_arg)
def do_quit(self, arg):
"""Stop the application: QUIT"""
self.tkapp.master.destroy()
return True
if __name__ == "__main__":
tk_root = tk.Tk()
tk_root.geometry("200x120")
tk_app = _tkapp(master=tk_root)
command = Command(tk_app)
command_thread = Thread(target=command.cmdloop)
command_thread.start()
tk_app.mainloop()
os.kill(os.getpid(), signal.SIGTERM)
If you run the program via ssh, you can directly type in command help paint and quit. All outputs from the tk app will be printed here as well.
Just type help or help paint to see how it goes.

Keep subprocess as topmost on Linux OS in python3

I have a gui in python3 that I want to call another python script using subprocess (or something better?). I have the main gui full screen, which is what I want. The problem is, when I launch the subprocess, it initially becomes the topmost window on LXDE, so far so good. You can click on the main gui in the background which brings the main gui to topmost. This is what is expected from the window manager, but this covers the subprocess that I currently have blocking the main gui. I need to prevent the main gui from accepting focus while the subprocess is running or keeping the subprocess as topmost window.
maingui.py
#!/usr/bin/env python3
import tkinter as tk
import subprocess
def on_escape(event=None):
print("escaped")
root.destroy()
def do_something():
child = subprocess.run(["python3", "childgui.py"], shell=False)
######################################################################
busy=False
root = tk.Tk()
root.title("My GUI")
screen_width = root.winfo_screenwidth()
screen_height = root.winfo_screenheight()
#don't run this as the subprocess blocks #root.attributes("-fullscreen", True) # run fullscreen
root.focus_set()
root.bind("<Escape>", on_escape)
#doesn't work#root.protocol("WM_TAKE_FOCUS", on_focus)
canvas = tk.Canvas(root)
canvas.grid(row=0)
cbutton = tk.Button(root, text = "Run Child", width=50, height=50, bg = "green", compound = "top", command = do_something)
lab = tk.Label(canvas, text = 'Output Here')
lab.grid(row=0, column=1)
cbutton.grid(row=1, column=0)
# --- start ---
root.mainloop()
childgui.py
from tkinter import *
class app(Frame):
def __init__(self, master):
Frame.__init__(self, master=None)
self.master.title("Child Process")
self.master.geometry("500x200")
Button(self.master, text="Grandchild", command=self.dialog).pack()
self.data = StringVar()
self.data.set("Here is the data")
Label(self.master, textvariable=self.data).pack()
def dialog(self):
d = MyDialog(self.master, self.data, "Grandchild", "Enter Data")
self.master.wait_window(d.top)
class MyDialog:
def __init__(self, parent, data, title, labeltext = '' ):
self.data = data
self.rt = parent
self.top = Toplevel(parent)
self.top.transient(parent)
self.top.grab_set()
self.top.geometry("300x100")
if len(title) > 0: self.top.title(title)
if len(labeltext) == 0: labeltext = 'Data'
Label(self.top, text=labeltext).pack()
self.top.bind("<Return>", self.ok)
self.e = Entry(self.top, text=data.get())
self.e.bind("<Return>", self.ok)
self.e.bind("<Escape>", self.cancel)
self.e.pack(padx=15)
self.e.focus_set()
b = Button(self.top, text="OK", command=self.ok)
b.pack(pady=5)
def ok(self, event=None):
print ("The data:", self.e.get())
self.data.set(self.e.get())
self.top.destroy()
def cancel(self, event=None):
self.top.destroy()
def main():
root = Tk()
a = app(root)
root.mainloop()
if __name__ == '__main__':
main()
Edit: I should have mentioned the child gui is an app that is not mine. I only created this one for the example code to show the issue. While self.top.attributes may work, the actual child app is pretty large and I don't want to change it if I can avoid it. I don't have problems getting the focus, which is actually the problem. The main gui gets the focus back causing the subprocess to go behind. When the main gui is set to fullscreen (delete this to see #don't run this as the subprocess blocks #) the main gui is now stuck waiting for the child to close which you can't get to with the mouse

Trying to launch single threaded window, but two are launching

I'm trying to launch a threaded window because I have background processes that are running, but two identical windows are launching under the same thread and I'm not sure why. I'm guessing this code can be optimized! thx for any suggestions.
6/1: I made the modification as suggested to not run two windows in the main loop, and that works. The 2nd piece, is that once the button is clicked, the window is destroyed "self.root.destroy()", but if I try to open another window it will not open. I do a check before trying to launch the new window and there is only the main thread running, so the first thread is gone. Not sure what's going on and I'm not able to debug with threads running.
from Tkinter import *
import ttk
import threading
import thread
from pdctest.test_lib.hid_util import *
class GUI():
def __init__(self, root):
self.root = root # root is a passed Tk object
self.root.title("GUI--->user instruction window")
self.frame = Frame(self.root)
self.frame.pack()
self.label0 = Label(self.frame, text=" ")
self.label0.configure(width=50, height=1)
self.label0.pack()
self.label = Label(self.frame, text="GUI--->execute a SKP on HS within 3s...")
self.label.configure(width=50, height=1)
self.label.pack()
self.button = Button(self.root, text=" GUI--->then click this button ", command=self.buttonWasPressed, fg = 'black', bg = 'dark orange')
self.button.configure(width=40, height=5)
self.button.pack(pady=25)
def removethis(self):
print("GUI--->in removethis")
self.root.destroy()
def buttonWasPressed(self):
print( "GUI--->inside buttonWasPressed: the button was pressed! " + '\r')
self.removethis()
print("GUI--->leaving buttonWasPressed")
def guiSKP(self):
#root = Tk()
#window = GUI(root)
#root.mainloop()
# modified 6/1, fixes multi-window issue
self.root.mainloop()
class GUI_SKP_Thread(threading.Thread):
def __init__(self, name):
threading.Thread.__init__(self)
self.name = name
def run(self):
root = Tk()
window = GUI(root)
window.guiSKP()
def launch_SKP_thread():
print("beginning launch_SKP_thread" + '\r')
thread_SKP = GUI_SKP_Thread("GUI_SKP_Thread")
thread_SKP.start()
print("exiting launch_SKP_thread" + '\r')
def whatsRunning():
t = threading.enumerate()
print("--------------->whatsRunning")
print t
if __name__ == '__main__':
launch_SKP_thread()
# trying to launch 2nd window
whatsRunning()
time.sleep(4)
whatsRunning()
launch_SKP_thread()
I't not really sure what you're trying to do with the program. But I'm sure about one thing, that is, you are trying to run two frames in the same mainloop. You defined window twice in different locations, meaning that there are two frames/windows. Once in guiSKP in GUI, and once in run in GUI_SKP_Thread.
root = Tk()
window = GUI(root)
So, you should change the text in guiSKP from:
root = Tk()
window = GUI(root)
root.mainloop()
To simply:
self.root.mainloop()
I added the self to make sure it runs its own root.mainloop()

Categories

Resources