HTML Video in Tkinter Window - python
is there any way to embed a HTML Video File in an tkinter window ?
I would be pleased for all kind of input!
Thanks a lot
One of solution is to use cv2 to read video frame-by-frame and replace frame on tk.Label or tk.Canvas - and you will see video.
(And cv2 uses ffmpeg for this)
cv2 can read from video file, local webcam or from remote stream (HTTP or RTMP)
Because it works with frames so you can draw some text or figures on frame before displaying - this way you can add some buttons or descriptions. You may also make modifications - crop, flip, convert to gray, etc.
But it has one problem: cv2 doesn't work with audio - so it can't play audio from file.
import tkinter as tk
from PIL import Image, ImageTk
import cv2
# --- functions ---
def update_frame():
ret, frame = video.read()
if ret: # check status - because sometimes it may have problem to read frame
# cv2 keeps image as `BGR` and it needs to convert to `RGB`
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
# resize to tkinter's window
frame = cv2.resize(frame, (800, 600))
image = Image.fromarray(frame) # convert numpy.array to PIL.Image
photo.paste(image) # copy image on photo
# update again after some time (in milliseconds) (ie. 1000ms/25fps = 40ms)
root.after(int(1000/fps), update_frame)
# --- main ---
#video = cv2.VideoCapture(0) # local webcam
video = cv2.VideoCapture(BigBuckBunny.mp4")
#w = video.get(cv2.CAP_PROP_FRAME_WIDTH)
#h = video.get(cv2.CAP_PROP_FRAME_HEIGHT)
fps = video.get(cv2.CAP_PROP_FPS)
# get first frame to create photo
ret, frame = video.read()
# cv2 keeps image as `BGR` and it needs to convert to `RGB`
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
# resize to tkinter's window
frame = cv2.resize(frame, (800, 600))
image = Image.fromarray(frame) # convert numpy.array to PIL.Image
# - GUI -
root = tk.Tk()
root.geometry('800x600')
photo = ImageTk.PhotoImage(image) # it has to be after `tk.Tk()`
canvas = tk.Canvas(root, width=photo.width(), height=photo.height())
canvas.pack(fill='both', expand=True)
image_id = canvas.create_image((0,0), image=photo, anchor='nw')
update_frame() # update it first time
root.mainloop() # run loop all time - it shows window
# - after close window -
# close stream
video.release()
Using Google I found module tkVideo.
It uses imageio to read frame-by-frame. (And imageio uses ffmpeg for this).
And it use threading instead of root.after().
It has the same problem: iamgeio doesn't work with audio - so it can't play audio from file.
EDIT:
Other method is to embed some Video Player like VLC, MPlayer, etc.. And it should gives video with audio but it doesn't allow to draw elements
I found example code in documentation for video player VLC but it seems complex because it adds also buttons to start/stop/pause video.
http://git.videolan.org/?p=vlc/bindings/python.git;a=blob;f=examples/tkvlc.py;h=55314cab09948fc2b7c84f14a76c6d1a7cbba127;hb=HEAD
#! /usr/bin/python
# -*- coding: utf-8 -*-
# tkinter example for VLC Python bindings
# Copyright (C) 2015 the VideoLAN team
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
#
"""A simple example for VLC python bindings using tkinter.
Requires Python 3.4 or later.
Author: Patrick Fay
Date: 23-09-2015
"""
# Tested with Python 3.7.4, tkinter/Tk 8.6.9 on macOS 10.13.6 only.
__version__ = '20.05.04' # mrJean1 at Gmail
# import external libraries
import vlc
# import standard libraries
import sys
if sys.version_info[0] < 3:
import Tkinter as Tk
from Tkinter import ttk
from Tkinter.filedialog import askopenfilename
from Tkinter.tkMessageBox import showerror
else:
import tkinter as Tk
from tkinter import ttk
from tkinter.filedialog import askopenfilename
from tkinter.messagebox import showerror
from os.path import basename, expanduser, isfile, join as joined
from pathlib import Path
import time
_isMacOS = sys.platform.startswith('darwin')
_isWindows = sys.platform.startswith('win')
_isLinux = sys.platform.startswith('linux')
if _isMacOS:
from ctypes import c_void_p, cdll
# libtk = cdll.LoadLibrary(ctypes.util.find_library('tk'))
# returns the tk library /usr/lib/libtk.dylib from macOS,
# but we need the tkX.Y library bundled with Python 3+,
# to match the version number of tkinter, _tkinter, etc.
try:
libtk = 'libtk%s.dylib' % (Tk.TkVersion,)
prefix = getattr(sys, 'base_prefix', sys.prefix)
libtk = joined(prefix, 'lib', libtk)
dylib = cdll.LoadLibrary(libtk)
# getNSView = dylib.TkMacOSXDrawableView is the
# proper function to call, but that is non-public
# (in Tk source file macosx/TkMacOSXSubwindows.c)
# and dylib.TkMacOSXGetRootControl happens to call
# dylib.TkMacOSXDrawableView and return the NSView
_GetNSView = dylib.TkMacOSXGetRootControl
# C signature: void *_GetNSView(void *drawable) to get
# the Cocoa/Obj-C NSWindow.contentView attribute, the
# drawable NSView object of the (drawable) NSWindow
_GetNSView.restype = c_void_p
_GetNSView.argtypes = c_void_p,
del dylib
except (NameError, OSError): # image or symbol not found
def _GetNSView(unused):
return None
libtk = "N/A"
C_Key = "Command-" # shortcut key modifier
else: # *nix, Xwindows and Windows, UNTESTED
libtk = "N/A"
C_Key = "Control-" # shortcut key modifier
class _Tk_Menu(Tk.Menu):
'''Tk.Menu extended with .add_shortcut method.
Note, this is a kludge just to get Command-key shortcuts to
work on macOS. Other modifiers like Ctrl-, Shift- and Option-
are not handled in this code.
'''
_shortcuts_entries = {}
_shortcuts_widget = None
def add_shortcut(self, label='', key='', command=None, **kwds):
'''Like Tk.menu.add_command extended with shortcut key.
If needed use modifiers like Shift- and Alt_ or Option-
as before the shortcut key character. Do not include
the Command- or Control- modifier nor the <...> brackets
since those are handled here, depending on platform and
as needed for the binding.
'''
# <https://TkDocs.com/tutorial/menus.html>
if not key:
self.add_command(label=label, command=command, **kwds)
elif _isMacOS:
# keys show as upper-case, always
self.add_command(label=label, accelerator='Command-' + key,
command=command, **kwds)
self.bind_shortcut(key, command, label)
else: # XXX not tested, not tested, not tested
self.add_command(label=label, underline=label.lower().index(key),
command=command, **kwds)
self.bind_shortcut(key, command, label)
def bind_shortcut(self, key, command, label=None):
"""Bind shortcut key, default modifier Command/Control.
"""
# The accelerator modifiers on macOS are Command-,
# Ctrl-, Option- and Shift-, but for .bind[_all] use
# <Command-..>, <Ctrl-..>, <Option_..> and <Shift-..>,
# <https://www.Tcl.Tk/man/tcl8.6/TkCmd/bind.htm#M6>
if self._shortcuts_widget:
if C_Key.lower() not in key.lower():
key = "<%s%s>" % (C_Key, key.lstrip('<').rstrip('>'))
self._shortcuts_widget.bind(key, command)
# remember the shortcut key for this menu item
if label is not None:
item = self.index(label)
self._shortcuts_entries[item] = key
# The Tk modifier for macOS' Command key is called
# Meta, but there is only Meta_L[eft], no Meta_R[ight]
# and both keyboard command keys generate Meta_L events.
# Similarly for macOS' Option key, the modifier name is
# Alt and there's only Alt_L[eft], no Alt_R[ight] and
# both keyboard option keys generate Alt_L events. See:
# <https://StackOverflow.com/questions/6378556/multiple-
# key-event-bindings-in-tkinter-control-e-command-apple-e-etc>
def bind_shortcuts_to(self, widget):
'''Set the widget for the shortcut keys, usually root.
'''
self._shortcuts_widget = widget
def entryconfig(self, item, **kwds):
"""Update shortcut key binding if menu entry changed.
"""
Tk.Menu.entryconfig(self, item, **kwds)
# adjust the shortcut key binding also
if self._shortcuts_widget:
key = self._shortcuts_entries.get(item, None)
if key is not None and "command" in kwds:
self._shortcuts_widget.bind(key, kwds["command"])
class Player(Tk.Frame):
"""The main window has to deal with events.
"""
_geometry = ''
_stopped = None
def __init__(self, parent, title=None, video=''):
Tk.Frame.__init__(self, parent)
self.parent = parent # == root
self.parent.title(title or "tkVLCplayer")
self.video = expanduser(video)
# Menu Bar
# File Menu
menubar = Tk.Menu(self.parent)
self.parent.config(menu=menubar)
fileMenu = _Tk_Menu(menubar)
fileMenu.bind_shortcuts_to(parent) # XXX must be root?
fileMenu.add_shortcut("Open...", 'o', self.OnOpen)
fileMenu.add_separator()
fileMenu.add_shortcut("Play", 'p', self.OnPlay) # Play/Pause
fileMenu.add_command(label="Stop", command=self.OnStop)
fileMenu.add_separator()
fileMenu.add_shortcut("Mute", 'm', self.OnMute)
fileMenu.add_separator()
fileMenu.add_shortcut("Close", 'w' if _isMacOS else 's', self.OnClose)
if _isMacOS: # intended for and tested on macOS
fileMenu.add_separator()
fileMenu.add_shortcut("Full Screen", 'f', self.OnFullScreen)
menubar.add_cascade(label="File", menu=fileMenu)
self.fileMenu = fileMenu
self.playIndex = fileMenu.index("Play")
self.muteIndex = fileMenu.index("Mute")
# first, top panel shows video
self.videopanel = ttk.Frame(self.parent)
self.canvas = Tk.Canvas(self.videopanel)
self.canvas.pack(fill=Tk.BOTH, expand=1)
self.videopanel.pack(fill=Tk.BOTH, expand=1)
# panel to hold buttons
self.buttons_panel = Tk.Toplevel(self.parent)
self.buttons_panel.title("")
self.is_buttons_panel_anchor_active = False
buttons = ttk.Frame(self.buttons_panel)
self.playButton = ttk.Button(buttons, text="Play", command=self.OnPlay)
stop = ttk.Button(buttons, text="Stop", command=self.OnStop)
self.muteButton = ttk.Button(buttons, text="Mute", command=self.OnMute)
self.playButton.pack(side=Tk.LEFT)
stop.pack(side=Tk.LEFT)
self.muteButton.pack(side=Tk.LEFT)
self.volMuted = False
self.volVar = Tk.IntVar()
self.volSlider = Tk.Scale(buttons, variable=self.volVar, command=self.OnVolume,
from_=0, to=100, orient=Tk.HORIZONTAL, length=200,
showvalue=0, label='Volume')
self.volSlider.pack(side=Tk.RIGHT)
buttons.pack(side=Tk.BOTTOM, fill=Tk.X)
# panel to hold player time slider
timers = ttk.Frame(self.buttons_panel)
self.timeVar = Tk.DoubleVar()
self.timeSliderLast = 0
self.timeSlider = Tk.Scale(timers, variable=self.timeVar, command=self.OnTime,
from_=0, to=1000, orient=Tk.HORIZONTAL, length=500,
showvalue=0) # label='Time',
self.timeSlider.pack(side=Tk.BOTTOM, fill=Tk.X, expand=1)
self.timeSliderUpdate = time.time()
timers.pack(side=Tk.BOTTOM, fill=Tk.X)
# VLC player
args = []
if _isLinux:
args.append('--no-xlib')
self.Instance = vlc.Instance(args)
self.player = self.Instance.media_player_new()
self.parent.bind("<Configure>", self.OnConfigure) # catch window resize, etc.
self.parent.update()
# After parent.update() otherwise panel is ignored.
self.buttons_panel.overrideredirect(True)
# Estetic, to keep our video panel at least as wide as our buttons panel.
self.parent.minsize(width=502, height=0)
if _isMacOS:
# Only tested on MacOS so far. Enable for other OS after verified tests.
self.is_buttons_panel_anchor_active = True
# Detect dragging of the buttons panel.
self.buttons_panel.bind("<Button-1>", lambda event: setattr(self, "has_clicked_on_buttons_panel", event.y < 0))
self.buttons_panel.bind("<B1-Motion>", self._DetectButtonsPanelDragging)
self.buttons_panel.bind("<ButtonRelease-1>", lambda _: setattr(self, "has_clicked_on_buttons_panel", False))
self.has_clicked_on_buttons_panel = False
else:
self.is_buttons_panel_anchor_active = False
self._AnchorButtonsPanel()
self.OnTick() # set the timer up
def OnClose(self, *unused):
"""Closes the window and quit.
"""
# print("_quit: bye")
self.parent.quit() # stops mainloop
self.parent.destroy() # this is necessary on Windows to avoid
# ... Fatal Python Error: PyEval_RestoreThread: NULL tstate
def _DetectButtonsPanelDragging(self, _):
"""If our last click was on the boarder
we disable the anchor.
"""
if self.has_clicked_on_buttons_panel:
self.is_buttons_panel_anchor_active = False
self.buttons_panel.unbind("<Button-1>")
self.buttons_panel.unbind("<B1-Motion>")
self.buttons_panel.unbind("<ButtonRelease-1>")
def _AnchorButtonsPanel(self):
video_height = self.parent.winfo_height()
panel_x = self.parent.winfo_x()
panel_y = self.parent.winfo_y() + video_height + 23 # 23 seems to put the panel just below our video.
panel_height = self.buttons_panel.winfo_height()
panel_width = self.parent.winfo_width()
self.buttons_panel.geometry("%sx%s+%s+%s" % (panel_width, panel_height, panel_x, panel_y))
def OnConfigure(self, *unused):
"""Some widget configuration changed.
"""
# <https://www.Tcl.Tk/man/tcl8.6/TkCmd/bind.htm#M12>
self._geometry = '' # force .OnResize in .OnTick, recursive?
if self.is_buttons_panel_anchor_active:
self._AnchorButtonsPanel()
def OnFullScreen(self, *unused):
"""Toggle full screen, macOS only.
"""
# <https://www.Tcl.Tk/man/tcl8.6/TkCmd/wm.htm#M10>
f = not self.parent.attributes("-fullscreen") # or .wm_attributes
if f:
self._previouscreen = self.parent.geometry()
self.parent.attributes("-fullscreen", f) # or .wm_attributes
self.parent.bind("<Escape>", self.OnFullScreen)
else:
self.parent.attributes("-fullscreen", f) # or .wm_attributes
self.parent.geometry(self._previouscreen)
self.parent.unbind("<Escape>")
def OnMute(self, *unused):
"""Mute/Unmute audio.
"""
# audio un/mute may be unreliable, see vlc.py docs.
self.volMuted = m = not self.volMuted # self.player.audio_get_mute()
self.player.audio_set_mute(m)
u = "Unmute" if m else "Mute"
self.fileMenu.entryconfig(self.muteIndex, label=u)
self.muteButton.config(text=u)
# update the volume slider text
self.OnVolume()
def OnOpen(self, *unused):
"""Pop up a new dialow window to choose a file, then play the selected file.
"""
# if a file is already running, then stop it.
self.OnStop()
# Create a file dialog opened in the current home directory, where
# you can display all kind of files, having as title "Choose a video".
video = askopenfilename(initialdir = Path(expanduser("~")),
title = "Choose a video",
filetypes = (("all files", "*.*"),
("mp4 files", "*.mp4"),
("mov files", "*.mov")))
self._Play(video)
def _Pause_Play(self, playing):
# re-label menu item and button, adjust callbacks
p = 'Pause' if playing else 'Play'
c = self.OnPlay if playing is None else self.OnPause
self.fileMenu.entryconfig(self.playIndex, label=p, command=c)
# self.fileMenu.bind_shortcut('p', c) # XXX handled
self.playButton.config(text=p, command=c)
self._stopped = False
def _Play(self, video):
# helper for OnOpen and OnPlay
if isfile(video): # Creation
m = self.Instance.media_new(str(video)) # Path, unicode
self.player.set_media(m)
self.parent.title("tkVLCplayer - %s" % (basename(video),))
# set the window id where to render VLC's video output
h = self.videopanel.winfo_id() # .winfo_visualid()?
if _isWindows:
self.player.set_hwnd(h)
elif _isMacOS:
# XXX 1) using the videopanel.winfo_id() handle
# causes the video to play in the entire panel on
# macOS, covering the buttons, sliders, etc.
# XXX 2) .winfo_id() to return NSView on macOS?
v = _GetNSView(h)
if v:
self.player.set_nsobject(v)
else:
self.player.set_xwindow(h) # plays audio, no video
else:
self.player.set_xwindow(h) # fails on Windows
# FIXME: this should be made cross-platform
self.OnPlay()
def OnPause(self, *unused):
"""Toggle between Pause and Play.
"""
if self.player.get_media():
self._Pause_Play(not self.player.is_playing())
self.player.pause() # toggles
def OnPlay(self, *unused):
"""Play video, if none is loaded, open the dialog window.
"""
# if there's no video to play or playing,
# open a Tk.FileDialog to select a file
if not self.player.get_media():
if self.video:
self._Play(expanduser(self.video))
self.video = ''
else:
self.OnOpen()
# Try to play, if this fails display an error message
elif self.player.play(): # == -1
self.showError("Unable to play the video.")
else:
self._Pause_Play(True)
# set volume slider to audio level
vol = self.player.audio_get_volume()
if vol > 0:
self.volVar.set(vol)
self.volSlider.set(vol)
def OnResize(self, *unused):
"""Adjust the window/frame to the video aspect ratio.
"""
g = self.parent.geometry()
if g != self._geometry and self.player:
u, v = self.player.video_get_size() # often (0, 0)
if v > 0 and u > 0:
# get window size and position
g, x, y = g.split('+')
w, h = g.split('x')
# alternatively, use .winfo_...
# w = self.parent.winfo_width()
# h = self.parent.winfo_height()
# x = self.parent.winfo_x()
# y = self.parent.winfo_y()
# use the video aspect ratio ...
if u > v: # ... for landscape
# adjust the window height
h = round(float(w) * v / u)
else: # ... for portrait
# adjust the window width
w = round(float(h) * u / v)
self.parent.geometry("%sx%s+%s+%s" % (w, h, x, y))
self._geometry = self.parent.geometry() # actual
def OnStop(self, *unused):
"""Stop the player, resets media.
"""
if self.player:
self.player.stop()
self._Pause_Play(None)
# reset the time slider
self.timeSlider.set(0)
self._stopped = True
# XXX on macOS libVLC prints these error messages:
# [h264 # 0x7f84fb061200] get_buffer() failed
# [h264 # 0x7f84fb061200] thread_get_buffer() failed
# [h264 # 0x7f84fb061200] decode_slice_header error
# [h264 # 0x7f84fb061200] no frame!
def OnTick(self):
"""Timer tick, update the time slider to the video time.
"""
if self.player:
# since the self.player.get_length may change while
# playing, re-set the timeSlider to the correct range
t = self.player.get_length() * 1e-3 # to seconds
if t > 0:
self.timeSlider.config(to=t)
t = self.player.get_time() * 1e-3 # to seconds
# don't change slider while user is messing with it
if t > 0 and time.time() > (self.timeSliderUpdate + 2):
self.timeSlider.set(t)
self.timeSliderLast = int(self.timeVar.get())
# start the 1 second timer again
self.parent.after(1000, self.OnTick)
# adjust window to video aspect ratio, done periodically
# on purpose since the player.video_get_size() only
# returns non-zero sizes after playing for a while
if not self._geometry:
self.OnResize()
def OnTime(self, *unused):
if self.player:
t = self.timeVar.get()
if self.timeSliderLast != int(t):
# this is a hack. The timer updates the time slider.
# This change causes this rtn (the 'slider has changed' rtn)
# to be invoked. I can't tell the difference between when
# the user has manually moved the slider and when the timer
# changed the slider. But when the user moves the slider
# tkinter only notifies this rtn about once per second and
# when the slider has quit moving.
# Also, the tkinter notification value has no fractional
# seconds. The timer update rtn saves off the last update
# value (rounded to integer seconds) in timeSliderLast if
# the notification time (sval) is the same as the last saved
# time timeSliderLast then we know that this notification is
# due to the timer changing the slider. Otherwise the
# notification is due to the user changing the slider. If
# the user is changing the slider then I have the timer
# routine wait for at least 2 seconds before it starts
# updating the slider again (so the timer doesn't start
# fighting with the user).
self.player.set_time(int(t * 1e3)) # milliseconds
self.timeSliderUpdate = time.time()
def OnVolume(self, *unused):
"""Volume slider changed, adjust the audio volume.
"""
vol = min(self.volVar.get(), 100)
v_M = "%d%s" % (vol, " (Muted)" if self.volMuted else '')
self.volSlider.config(label="Volume " + v_M)
if self.player and not self._stopped:
# .audio_set_volume returns 0 if success, -1 otherwise,
# e.g. if the player is stopped or doesn't have media
if self.player.audio_set_volume(vol): # and self.player.get_media():
self.showError("Failed to set the volume: %s." % (v_M,))
def showError(self, message):
"""Display a simple error dialog.
"""
self.OnStop()
showerror(self.parent.title(), message)
if __name__ == "__main__":
_video = ''
while len(sys.argv) > 1:
arg = sys.argv.pop(1)
if arg.lower() in ('-v', '--version'):
# show all versions, sample output on macOS:
# % python3 ./tkvlc.py -v
# tkvlc.py: 2019.07.28 (tkinter 8.6 /Library/Frameworks/Python.framework/Versions/3.7/lib/libtk8.6.dylib)
# vlc.py: 3.0.6109 (Sun Mar 31 20:14:16 2019 3.0.6)
# LibVLC version: 3.0.6 Vetinari (0x3000600)
# LibVLC compiler: clang: warning: argument unused during compilation: '-mmacosx-version-min=10.7' [-Wunused-command-line-argument]
# Plugin path: /Applications/VLC3.0.6.app/Contents/MacOS/plugins
# Python: 3.7.4 (64bit) macOS 10.13.6
# Print version of this vlc.py and of the libvlc
print('%s: %s (%s %s %s)' % (basename(__file__), __version__,
Tk.__name__, Tk.TkVersion, libtk))
try:
vlc.print_version()
vlc.print_python()
except AttributeError:
pass
sys.exit(0)
elif arg.startswith('-'):
print('usage: %s [-v | --version] [<video_file_name>]' % (sys.argv[0],))
sys.exit(1)
elif arg: # video file
_video = expanduser(arg)
if not isfile(_video):
print('%s error: no such file: %r' % (sys.argv[0], arg))
sys.exit(1)
# Create a Tk.App() to handle the windowing event loop
root = Tk.Tk()
player = Player(root, video=_video)
root.protocol("WM_DELETE_WINDOW", player.OnClose) # XXX unnecessary (on macOS)
root.mainloop()
Related
VLC causes buffer when embedded in Python Tkinter Frame
I have been working on a music player app that uses VLC to play songs directly from the internet and has features like seeking and a progress bar. But as the normal tkinter progress bar looks kinda old so I used customtkinter.CTkSlider widget but using this causes buffering. The song plays smoothly on using Tk.Scale. Here, is the code: # import external libraries import vlc import tkinter as Tk from tkinter import ttk import pyautogui import customtkinter import pafy # import standard libraries import os from threading import Thread, Event import time import platform import requests class ttkTimer(Thread): """a class serving same function as wxTimer... but there may be better ways to do this """ def __init__(self, callback, tick): Thread.__init__(self) self.callback = callback self.stopFlag = Event() self.tick = tick self.iters = 0 def run(self): while not self.stopFlag.wait(self.tick): self.iters += 1 self.callback() def stop(self): self.stopFlag.set() def get(self): return self.iters class Player(Tk.Frame): """The main window has to deal with events. """ def __init__(self, parent, title=None): Tk.Frame.__init__(self, parent) self.parent = parent if title == None: title = "tk_vlc" self.parent.title(title) # Menu Bar # File Menu menubar = Tk.Menu(self.parent) self.parent.config(menu=menubar) fileMenu = Tk.Menu(menubar) fileMenu.add_command(label="Open", underline=0, command=self.OnOpen) fileMenu.add_command(label="Exit", underline=1, command=_quit) menubar.add_cascade(label="File", menu=fileMenu) # The second panel holds controls self.player = None self.videopanel = ttk.Frame(self.parent) self.canvas = Tk.Canvas(self.videopanel).pack(fill=Tk.BOTH,expand=1) self.videopanel.pack(fill=Tk.BOTH,expand=1) ctrlpanel = ttk.Frame(self.parent) pause = ttk.Button(ctrlpanel, text="Pause", command=self.OnPause) play = ttk.Button(ctrlpanel, text="Play", command=self.OnPlay) stop = ttk.Button(ctrlpanel, text="Stop", command=self.OnStop) volume = ttk.Button(ctrlpanel, text="Volume", command=self.OnSetVolume) pause.pack(side=Tk.LEFT) play.pack(side=Tk.LEFT) stop.pack(side=Tk.LEFT) volume.pack(side=Tk.LEFT) self.volume_var = Tk.IntVar() self.volslider = Tk.Scale(ctrlpanel, variable=self.volume_var, command=self.volume_sel, from_=0, to=100, orient=Tk.HORIZONTAL, length=100) self.volslider.pack(side=Tk.LEFT) ctrlpanel.pack(side=Tk.BOTTOM) ctrlpanel2 = ttk.Frame(self.parent) self.scale_var = Tk.DoubleVar() self.timeslider_last_val = "" self.timeslider = customtkinter.CTkSlider(ctrlpanel2, variable=self.scale_var, command=self.scale_sel, from_=0, to=1000, orient=Tk.HORIZONTAL) # This causes buffer self.timeslider.pack(side=Tk.BOTTOM, fill=Tk.X,expand=1) self.timeslider_last_update = time.time() ctrlpanel2.pack(side=Tk.BOTTOM,fill=Tk.X) # VLC player controls self.Instance = vlc.Instance() self.player = self.Instance.media_player_new() self.timer = ttkTimer(self.OnTimer, 1.0) self.timer.start() self.parent.update() #self.player.set_hwnd(self.GetHandle()) # for windows, OnOpen does does this def Extract(self,topic): """Will play video on following topic, takes about 10 to 15 seconds to load""" url = 'https://www.youtube.com/results?q=' + topic count = 0 cont = '' try: cont = requests.get(url) except: print('Error','Cannot Connect.. Internet not connected or invalid URL or id.') cont = '' data = cont.content data = str(data) lst = data.split('"') for i in lst: count+=1 if i == 'WEB_PAGE_TYPE_WATCH': break if lst[count-5] == "/results": print("Error","No video found.") return "https://www.youtube.com"+lst[count-5] def OnExit(self, evt): """Closes the window. """ self.Close() def OnOpen(self): """Pop up a new dialow window to choose a file, then play the selected file. """ # if a file is already running, then stop it. self.OnStop() fullname = pafy.new(self.Extract(pyautogui.password(mask="", title="Enter Song Name:", text="Enter Song Name:"))).getbest().url self.Media = self.Instance.media_new(fullname) self.player.set_media(self.Media) # set the window id where to render VLC's video output if platform.system() == 'Windows': self.player.set_hwnd(self.GetHandle()) else: self.player.set_xwindow(self.GetHandle()) # this line messes up windows # FIXME: this should be made cross-platform self.OnPlay() # set the volume slider to the current volume #self.volslider.SetValue(self.player.audio_get_volume() / 2) self.volslider.set(self.player.audio_get_volume()) def OnPlay(self): """Toggle the status to Play/Pause. If no file is loaded, open the dialog window. """ # check if there is a file to play, otherwise open a # Tk.FileDialog to select a file if not self.player.get_media(): self.OnOpen() else: # Try to launch the media, if this fails display an error message if self.player.play() == -1: self.errorDialog("Unable to play.") def GetHandle(self): return self.videopanel.winfo_id() #def OnPause(self, evt): def OnPause(self): """Pause the player. """ self.player.pause() def OnStop(self): """Stop the player. """ self.player.stop() # reset the time slider self.timeslider.set(0) def OnTimer(self): """Update the time slider according to the current movie time. """ if self.player == None: return # since the self.player.get_length can change while playing, # re-set the timeslider to the correct range. length = self.player.get_length() dbl = length * 0.001 self.timeslider.config(to=dbl) # update the time on the slider tyme = self.player.get_time() if tyme == -1: tyme = 0 dbl = tyme * 0.001 self.timeslider_last_val = ("%.0f" % dbl) + ".0" # don't want to programatically change slider while user is messing with it. # wait 2 seconds after user lets go of slider if time.time() > (self.timeslider_last_update + 2.0): self.timeslider.set(dbl) def scale_sel(self, evt): if self.player == None: return nval = self.scale_var.get() sval = str(nval) if self.timeslider_last_val != sval: self.timeslider_last_update = time.time() mval = "%.0f" % (nval * 1000) self.player.set_time(int(mval)) # expects milliseconds def volume_sel(self, evt): if self.player == None: return volume = self.volume_var.get() if volume > 100: volume = 100 if self.player.audio_set_volume(volume) == -1: self.errorDialog("Failed to set volume") def OnToggleVolume(self, evt): """Mute/Unmute according to the audio button. """ is_mute = self.player.audio_get_mute() self.player.audio_set_mute(not is_mute) # update the volume slider; # since vlc volume range is in [0, 200], # and our volume slider has range [0, 100], just divide by 2. self.volume_var.set(self.player.audio_get_volume()) def OnSetVolume(self): """Set the volume according to the volume sider. """ volume = self.volume_var.get() # vlc.MediaPlayer.audio_set_volume returns 0 if success, -1 otherwise if volume > 100: volume = 100 if self.player.audio_set_volume(volume) == -1: self.errorDialog("Failed to set volume") def errorDialog(self, errormessage): """Display a simple error dialog. """ Tk.tkMessageBox.showerror(self, 'Error', errormessage) def Tk_get_root(): if not hasattr(Tk_get_root, "root"): #(1) Tk_get_root.root= Tk.Tk() #initialization call is inside the function return Tk_get_root.root def _quit(): print("_quit: bye") root = Tk_get_root() root.quit() # stops mainloop root.destroy() # this is necessary on Windows to prevent # Fatal Python Error: PyEval_RestoreThread: NULL tstate os._exit(1) if __name__ == "__main__": # Create a Tk.App(), which handles the windowing system event loop root = Tk_get_root() root.protocol("WM_DELETE_WINDOW", _quit) player = Player(root, title="tkinter vlc") # show the player window centred and run the application root.mainloop() Kindly help Regards.
Python centering window on "monitor" not "screen" or "desktop"
There are many questions on how to center a python tkinter window on the screen and the answer works well. My problem is my so-called "screen" looks like this: Although you can move windows partially (or entirely) to the grey areas they won't actually show up on any of my three monitors. Top left monitor is 1920x1080, top right monitor is 3840x2160 and bottom right monitor is 1920x1080. A program can be started via desktop icon which could be on any monitor or via gnome-terminal which could be on any monitor. How does one discover: Which monitor was active when python was invoked? Coordinates of active monitor within the screen real estate? Although I'm using Gnome Desktop I'd like support for all Linux flavors using X11 or Wayland. Additionally I tried out ChromeOS Linux Beta lately and support for it would also be nice. Furthermore support for Windows and OSX is highly desired. I've already installed and used many tools gi, wnck, xdotool, wmctrl that hem me into a corner. I'm hoping their is a popular python library (preferably installed via apt-get and not pip or pip3) that can expose "screen", "desktop" and "monitors" to python.
I answered my own question. It was one of those answers that stops you from falling asleep Saturday night at midnight so you get up at 1:00 am on Sunday and code until 4:30 am. Here's the code which you can adapt for non-Ubuntu environments (using the "future code" functions): #!/usr/bin/env python # -*- coding: utf-8 -*- #============================================================================== # # m - Wrapper for mserve.py # #============================================================================== ''' Splash screen for mserve. mserve has it's own list of required modules but this wrapper requires: Gnome Desktop Toolkit (Gdk) ''' from __future__ import print_function # Must be first import try: import tkinter as tk PYTHON_VER="3" except ImportError: # Python 2 import Tkinter as tk PYTHON_VER="2" import image as img # Routines for tk & photo images import mserve # Script loaded as module for .pyc # https://stackoverflow.com/a/36419702/6929343 import logging logging.getLogger('PIL').setLevel(logging.WARNING) import sys logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s', level=logging.DEBUG, stream=sys.stdout) ''' Future code ''' def get_active_window(): """ From: https://stackoverflow.com/a/36419702/6929343 Get the currently active window. Returns ------- string : Name of the currently active window. """ import sys active_window_name = None logging.info('sys.platform: ' + sys.platform) print('sys.platform:', sys.platform) if sys.platform in ['linux', 'linux2']: # Alternatives: http://unix.stackexchange.com/q/38867/4784 try: import wnck except ImportError: logging.info("wnck not installed") wnck = None if wnck is not None: screen = wnck.screen_get_default() screen.force_update() window = screen.get_active_window() if window is not None: pid = window.get_pid() with open("/proc/{pid}/cmdline".format(pid=pid)) as f: active_window_name = f.read() else: try: # Next 3 limes from: https://stackoverflow.com/a/43349245/6929343 import gi gi.require_version('Gtk', '3.0') gi.require_version('Wnck', '3.0') # Continue with original code: from gi.repository import Gtk, Wnck gi = "Installed" except ImportError: logging.info("gi.repository not installed") gi = None if gi is not None: Gtk.init([]) # necessary if not using a Gtk.main() loop screen = Wnck.Screen.get_default() screen.force_update() # recommended per Wnck documentation active_window = screen.get_active_window() pid = active_window.get_pid() with open("/proc/{pid}/cmdline".format(pid=pid)) as f: active_window_name = f.read() elif sys.platform in ['Windows', 'win32', 'cygwin']: # http://stackoverflow.com/a/608814/562769 import win32gui window = win32gui.GetForegroundWindow() active_window_name = win32gui.GetWindowText(window) elif sys.platform in ['Mac', 'darwin', 'os2', 'os2emx']: # http://stackoverflow.com/a/373310/562769 from AppKit import NSWorkspace active_window_name = (NSWorkspace.sharedWorkspace() .activeApplication()['NSApplicationName']) else: print("sys.platform={platform} is unknown. Please report." .format(platform=sys.platform)) print(sys.version) print("Active window: %s" % str(active_window_name)) return active_window_name ''' Future code ''' def get_GtkWindow(w): # From: https://askubuntu.com/a/303754/307523 import gi gi.require_version('Gdk', '3.0') gi.require_version('Gtk', '3.0') from gi.repository import Gdk, Gtk # Replace w with the GtkWindow of your application w = Gtk.Window() # Get the screen from the GtkWindow s = w.get_screen() # Using the screen of the Window, the monitor it's on can be identified m = s.get_monitor_at_window(s.get_active_window()) # Then get the geometry of that monitor monitor = s.get_monitor_geometry(m) # This is an example output print("Height: %s, Width: %s, X: %s, Y: %s" % \ (monitor.height, monitor.width, monitor.x, monitor.y)) ''' Future code ''' def get_monitors(): """ Get list of monitors in Gnome Desktop """ import gi gi.require_version('Gdk', '3.0') from gi.repository import Gdk global NUMBER_OF_MONITORS, GNOME, ACTIVE_MONITOR, MONITOR_GEOMETRY display = Gdk.Display.get_default() screen = display.get_default_screen() window = screen.get_active_window() ACTIVE_MONITOR = screen.get_monitor_at_window(window) print('ACTIVE_MONITOR:', ACTIVE_MONITOR) # Gnome version 3.22 developed new monitor object try: # Gnome 3.22 NUMBER_OF_MONITORS = display.get_n_monitors() monitor = display.get_monitor(ACTIVE_MONITOR) MONITOR_GEOMETRY = monitor.get_geometry() GNOME=3.22 except: # Gnome 3.18 NUMBER_OF_MONITORS = screen.get_n_monitors() MONITOR_GEOMETRY = screen.get_monitor_geometry(ACTIVE_MONITOR) GNOME=3.18 # collect data about monitors for index in range(NUMBER_OF_MONITORS): if GNOME==3.22: monitor = display.get_monitor(index) geometry = monitor.get_geometry() name = monitor.get_monitor_plug_name() else: geometry = screen.get_monitor_geometry(index) name = screen.get_monitor_plug_name(index) print("Monitor {} = {}x{}+{}+{}".format(index, geometry.width, \ geometry.height, geometry.x, geometry.y), name) #get_monitors() #print('ACTIVE_MONITOR:', ACTIVE_MONITOR, 'MONITOR_GEOMETRY:', MONITOR_GEOMETRY) ''' Start of REAL code used today (May 2, 2021) ''' def get_window_monitor(window): """ Returns the Gdk monitor geometry rectangle tkinter window is on. If window is off screen force it into Monitor 1 (index 0). :param window: Tkinter root or Topleel """ import gi gi.require_version('Gdk', '3.0') from gi.repository import Gdk # global variables that might be useful down the road but not on May 2, 2021 global NUMBER_OF_MONITORS, GNOME display = Gdk.Display.get_default() screen = display.get_default_screen() # Gnome version 3.22 deprecated what used to work 3.18. # Gonme wasn't built in a day but, it was burned over night in next release! try: # Gnome 3.22 NUMBER_OF_MONITORS = display.get_n_monitors() GNOME=3.22 except: # Gnome 3.18 NUMBER_OF_MONITORS = screen.get_n_monitors() GNOME=3.18 x = window.winfo_x() # Window's left coordinate on screen y = window.winfo_y() # Window's top coordinate on screen if x < 0: x = 0 # Window top left may be off screen! if y < 0: y = 0 first_monitor = None for index in range (NUMBER_OF_MONITORS): if GNOME==3.22: # Gnome version 3.22 developed new monitor object monitor = display.get_monitor(index) mon_geom = monitor.get_geometry() else: # Gnome version 3.18 uses screen object for monitor properties mon_geom = screen.get_monitor_geometry(index) # Save first monitor if needed later if not first_monitor: first_monitor = mon_geom # Copmare to monitor's coordinates on screen and monitor width x height if x < mon_geom.x: continue if x >= mon_geom.x + mon_geom.width: continue if y < mon_geom.y: continue if y >= mon_geom.y + mon_geom.height: continue # Window is comletely on this monitor. return mon_geom # If window off of screen use first monitor return first_monitor def center(window): """ From: https://stackoverflow.com/a/10018670/6929343 centers a tkinter window on monitor in multi-monitor setup :param win: the main window or Toplevel window to center """ window.update_idletasks() # Refresh window's current position mon_geom=get_window_monitor(window) # Monitor geometry window is on if mon_geom is None: logging.error("No monitors found!") return None # Calcuate X, Y of window to center within monitors X, Y, width and height x = mon_geom.width // 2 - window.winfo_width() // 2 + mon_geom.x y = mon_geom.height // 2 - window.winfo_height() // 2 + mon_geom.y if x < 0: x = 0 # Window top left may be off screen! if y < 0: y = 0 window.geometry('+{}+{}'.format(x, y)) window.deiconify() # Forces window to appear return mon_geom def main(): """ Create splash screen and invoke mserve.py which takes a second or more """ splash = tk.Tk() # "very top" toplevel splash.title("Music Server - mserve") ''' Set font style for all fonts including tkSimpleDialog.py ''' img.set_font_style() # Make messagebox text larger for HDPI monitors ''' Get splash image ''' splash_image = img.m_splash_image(300, 'white', 'lightskyblue', 'black') # create and pack the canvas. Then load image file canvas = tk.Canvas(width=300, height=300, bg='black') canvas.pack(expand=tk.YES, fill=tk.BOTH) canvas.create_image(0, 0, image=splash_image, anchor=tk.NW) splash.update_idletasks() # This is required for visibility # Cemter splash screen on monitor and get monitors geometry mon_geom=center(splash) splash.update() # This is required for visibility # At this point make window undecorated, don't do it sooner! # From: https://stackoverflow.com/a/37199655/6929343 splash.overrideredirect(True) # Undecorated to prevent close # Call mserve module about 10k lines of code mserve.main(toplevel=splash, mon_geom=mon_geom) exit() # Required to close mserve library splash.mainloop() if __name__ == "__main__": main() # End of m
Play mp4 video in python with GUI
I want to create a photo booth with a simple sequence. I used mp4 files as animations. Everything has to start in GUI with a button "start" that will run a loop for users. Exit to GUI will be by pressing the esc key. In the "user" loop there will be a start animation as mp4 video -in omx player, working over and over until the user touches the touch screen. I already did this with listener move_mouse to kill the process. I then have another animation with a countdown after which the camera takes a picture and then displays the photo on the screen in a window with two buttons repeat or print. The problem with the listener is it freezes the application after capturing the photo How can I solve this problem? try: from Tkinter import * # Python2 except ImportError: import tkinter as Tk # Python3import Tkinter as tk import sys from time import sleep from PIL import Image, ImageTk from datetime import datetime from sh import gphoto2 as gp import signal, os, subprocess, glob from pynput import mouse from pynput import keyboard shot_date = datetime.now() .strftime("%Y-%m-%d") shot_time = datetime.now() .strftime("%Y-%m-%d %H:%M:%S") picID = "PIShots" folder_name = shot_date + picID save_location = "/home/pi/Desktop/gphoto/images/" + folder_name # listener def on_move(x,y): print("mouse move") playerOff() #pynput.mouse.Listener.stop playerOn() capturePhoto() # press key listener #def on_release(key): # print('{0} released'.format( # key)) # if key == keyboard.Key.esc: # Stop listener # return False #StartAnmation and listener def Start(): omxc= subprocess.Popen(['omxplayer','-b','--loop','--no-osd',"/home/pi/Desktop/START.mp4"]) with mouse.Listener( on_move=on_move) as listener: listener.join() # get the latest file def get_latest_file(path, *paths): """Returns the name of the latest (most recent) file of the joined path(s)""" fullpath = os.path.join(path, *paths) files = glob.glob(fullpath) # You may use iglob in Python3 if not files: # I prefer using the negation return None # because it behaves like a shortcut latest_file = max(files, key=os.path.getctime) _, filename = os.path.split(latest_file) return filename #start_animation in OMX player def playerOn(): omxc= subprocess.Popen(['omxplayer','-b',"/home/pi/Desktop/animacja.mp4"]) #Player Off def playerOff(): os.system('killall omxplayer.bin') #CreateSaveFolder def createSaveFolder(): try: os.makedirs(save_location) except: print("Failed to create the new directory") os.chdir(save_location) def quit(root): root.destroy() def capturePhoto(): status = 0 createSaveFolder() sleep(6) os.system('fswebcam -r 1920x1080 -s brightness=70% -s gain=50% -S 10 --set lights=off --no-banner %H%M%S.jpg') print save_location location=get_latest_file(save_location,'*.jpg') #print location sciezkaZdj= save_location + "/" + location print sciezkaZdj im = Image.open(sciezkaZdj) width, height =im.size LEFT_HALF = 200, 0, width-400 ,height im = im.crop(LEFT_HALF) im = im.transpose(Image.ROTATE_270) ramka = Image.open("/home/pi/Desktop/ramka1.jpg") text_img = Image.new('RGBA', (1200,2000), (0, 0, 0, 0)) text_img.paste(ramka, (0,0)) text_img.paste(im, (50,30)) text_img.save("ramka.png", format="png") path = save_location + "/ramka.png" top2 = Toplevel(window) top2.geometry("1600x720") top2.overrideredirect(1) top2.title("fotobudka") top2.configure(background='black') img = ImageTk.PhotoImage(Image.open(path)) panel = Label(top2, image = img) panel.pack(side = "bottom", fill = "both", expand = "yes") playerOff() #close gui window.after(5000, lambda: top2.destroy()) #************MAIN if __name__ == "__main__": try: #Gui main********************************* window = Tk() top1 = Toplevel(window) top1.geometry("1600x720") # top1.wm_attributes('-topmost',1) #zawsze na wierzchu top1.configure(background='black') button_start = Button(window, text='start',command=Start) button_start.pack() window.mainloop() #Gui main end****************************** except KeyboardInterrupt: print "koniec"
Run Images in Loop Tkinter
I am creating an app that will allow users to scan a ticket and a message will be displayed. I have created a short GIF animation to play when the app starts to show users where to scan their ticket. I am having trouble understanding how to play a GIF image using tkinter in Python 3. I have tried many solutions and I came across a piece of code where you select the folder and the images in that folder will play in a loop but it's not working. I think I'm not understanding the code. Here is my code for my app: from tkinter import * from tkinter import messagebox import tkinter.filedialog from tkinter.filedialog import askdirectory import requests import simplejson as json import os #from json import JSONEncoder #class MyEncoder(JSONEncoder): #def default(self, o): #return o.__dict__ #Connect to API function def apiconnect(statusvar): ticektid = e1.get() def to_serializable(ticketid): return str(ticketid) url = "https://staging3.activitar.com/ticket_api/tickets" data = {'ticket_id':e1.get(),'direction': 'up'} headers = {'Content-Type': 'application/json','Authorization' :'J0XDvDqVRy9hMF9Fo7j5'} r = requests.post(url,data=json.dumps(data), headers=headers) requestpost = requests.post(url, headers=headers, json=data) response_data = requestpost.json() statusvar = (response_data["status"]) messagevar = (response_data["message"]) json.dumps(url,data) # MyEncoder().encode(ticketid) #'{"ticekt_id": "/foo/bar"}' #19 February 2018 #def from_json(json_object): # if 'ticket_id' in json_object: # return FileItem(json_object['ticket_id']) # ticketid = JSONDecoder(object_hook = from_json).decode('{"ticket_id": "/foo/bar"}') #Including GPIO config if statusvar == "failed": messagebox.showinfo("Cape Point", messagevar) else: statusvar == "successful" #Run at full screen automatically: #---------------Function & Class--------------------------------# class FullScreenApp(object): def __init__(self, master, **kwargs): self.master=master pad=3 self._geom='200x200+0+0' master.geometry("{0}x{1}+0+0".format( master.winfo_screenwidth()-pad, master.winfo_screenheight()-pad)) master.bind('<Escape>',self.toggle_geom) def toggle_geom(self,event): geom=self.master.winfo_geometry() print(geom,self._geom) self.master.geometry(self._geom) self._geom=geom #--------------------------------------------------------------------# def next_img(): img_label.img = PhotoImage(file=next(imgs)) img_label.config(image=img_label.img) #create a textbox on a form root = Tk() #-----Full Screen-------# app = FullScreenApp(root) root.title("Cape Point") root.configure(background = 'White') #________ this code below was the original that displayed a static image _____# #titlepic = PhotoImage(file = "ScanPlease.gif") #shownpic = titlepic #filename = shownpic #Label(root, image = filename).grid(row=0, sticky=W) img_dir = askdirectory(parent=root, initialdir= "C:/Users/Nickitaes/Desktop", title='Where To Scan') os.chdir(img_dir) imgs = iter(os.listdir(img_dir)) img_label = Label(root) img_label.bind("<Return>",next_img()) next_img() e1 = Entry(root) e1.grid(row=1, column=0) e1.focus_set() #set cursor focus to textbox e1.bind("<Return>", apiconnect) #Return function root.mainloop( ) Thanks for the help!
Well..., it was not hard to find other questions about this on StackOverflow. Here are some: Play Animations in GIF with Tkinter and Play an Animated GIF in python with tkinter. I have combined the answers to take care of different number of subpictures an also commented the code a bit more. from tkinter import * import time root = Tk() framelist = [] # List to hold all the frames for ix in range(1000): # range > frames in largest GIF part = 'gif -index {}'.format(ix) try: frame = PhotoImage(file='giphy.gif', format=part) except: last = len(framelist) - 1 # Save index for last frame break # Will break when GIF index is reached framelist.append(frame) def update(ix): if ix > last: ix = 0 # Reset frame counter if too big label.configure(image=framelist[ix]) # Display frame on label ix += 1 # Increase framecounter root.after(100, update, ix) # Run again after 100 ms. label = Label(root) label.pack() root.after(0, update, 0) # Start update(0) after 0 ms. root.mainloop() Adjust the for-loop for the GIF size you use, or rewrite as a while-loop. I don't know how to read the frame delay from the GIF. You'll have to try different values in after() until it looks good.
showing video on the entire screen using OpenCV and Tkiner
I'm trying to create a GUI for playing a video that fills up the entire screen, while the button for Snapshot is still visible at the bottom. Right now, What i manage to do is just set the app window itself to fullscreen, resulting a small sized video playing at the top and a huge "snapshot" button at the button. Is there a way to make the video fill up the entire screen? thanks! from PIL import Image, ImageTk import Tkinter as tk import argparse import datetime import cv2 import os class Application: def __init__(self, output_path = "./"): """ Initialize application which uses OpenCV + Tkinter. It displays a video stream in a Tkinter window and stores current snapshot on disk """ self.vs = cv2.VideoCapture('Cat Walking.mp4') # capture video frames, 0 is your default video camera self.output_path = output_path # store output path self.current_image = None # current image from the camera self.root = tk.Tk() # initialize root window self.root.title("PyImageSearch PhotoBooth") # set window title # self.destructor function gets fired when the window is closed self.root.protocol('WM_DELETE_WINDOW', self.destructor) self.panel = tk.Label(self.root) # initialize image panel self.panel.pack(padx=10, pady=10) # create a button, that when pressed, will take the current frame and save it to file btn = tk.Button(self.root, text="Snapshot!", command=self.take_snapshot) btn.pack(fill="both", expand=True, padx=10, pady=10) # start a self.video_loop that constantly pools the video sensor # for the most recently read frame self.video_loop() def video_loop(self): """ Get frame from the video stream and show it in Tkinter """ ok, frame = self.vs.read() # read frame from video stream if ok: # frame captured without any errors cv2image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGBA) # convert colors from BGR to RGBA self.current_image = Image.fromarray(cv2image) # convert image for PIL imgtk = ImageTk.PhotoImage(image=self.current_image) # convert image for tkinter self.panel.imgtk = imgtk # anchor imgtk so it does not be deleted by garbage-collector self.root.attributes("-fullscreen",True) #self.oot.wm_state('zoomed') self.panel.config(image=imgtk) # show the image self.root.after(1, self.video_loop) # call the same function after 30 milliseconds def take_snapshot(self): """ Take snapshot and save it to the file """ ts = datetime.datetime.now() # grab the current timestamp filename = "{}.jpg".format(ts.strftime("%Y-%m-%d_%H-%M-%S")) # construct filename p = os.path.join(self.output_path, filename) # construct output path self.current_image.save(p, "JPEG") # save image as jpeg file print("[INFO] saved {}".format(filename)) def destructor(self): """ Destroy the root object and release all resources """ print("[INFO] closing...") self.root.destroy() self.vs.release() # release web camera cv2.destroyAllWindows() # it is not mandatory in this application # construct the argument parse and parse the arguments ap = argparse.ArgumentParser() ap.add_argument("-o", "--output", default="./", help="path to output directory to store snapshots (default: current folder") args = vars(ap.parse_args()) # start the app print("[INFO] starting...") pba = Application(args["output"]) pba.root.mainloop()
It's not a hard task if you don't care about execution time! We knew that resizing of an image isn't a rocket science for common user, but under the hood it takes some time to resize each frame. And if you really wonder about time and options - there're many options to play around from numpy/scipy to skimage/skvideo. But let's try to do something with your code "as is" so we have two options to play with: cv2 and Image. For testing I grabbed 20 secs of "Keyboard Cat" video from youtube (480p) and resize each frame upto 1080p, and GUI looks like this (fullscreen 1920x1080): Resize Methods / timeit elapsed time of showing frames: cv2.resize() / ~81.377 s. Image.resize() / ~82.98 s. As you see - no big difference between theese two so here's a code (only Application class and video_loop changed): #imports try: import tkinter as tk except: import Tkinter as tk from PIL import Image, ImageTk import argparse import datetime import cv2 import os class Application: def __init__(self, output_path = "./"): """ Initialize application which uses OpenCV + Tkinter. It displays a video stream in a Tkinter window and stores current snapshot on disk """ self.vs = cv2.VideoCapture('KeyCat.mp4') # capture video frames, 0 is your default video camera self.output_path = output_path # store output path self.current_image = None # current image from the camera self.root = tk.Tk() # initialize root window self.root.title("PyImageSearch PhotoBooth") # set window title # self.destructor function gets fired when the window is closed self.root.protocol('WM_DELETE_WINDOW', self.destructor) self.root.attributes("-fullscreen", True) # getting size to resize! 30 - space for button self.size = (self.root.winfo_screenwidth(), self.root.winfo_screenheight() - 30) self.panel = tk.Label(self.root) # initialize image panel self.panel.pack(fill='both', expand=True) # create a button, that when pressed, will take the current frame and save it to file self.btn = tk.Button(self.root, text="Snapshot!", command=self.take_snapshot) self.btn.pack(fill='x', expand=True) # start a self.video_loop that constantly pools the video sensor # for the most recently read frame self.video_loop() def video_loop(self): """ Get frame from the video stream and show it in Tkinter """ ok, frame = self.vs.read() # read frame from video stream if ok: # frame captured without any errors cv2image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGBA) # convert colors from BGR to RGBA cv2image = cv2.resize(cv2image, self.size, interpolation=cv2.INTER_NEAREST) self.current_image = Image.fromarray(cv2image) #.resize(self.size, resample=Image.NEAREST) # convert image for PIL self.panel.imgtk = ImageTk.PhotoImage(image=self.current_image) self.panel.config(image=self.panel.imgtk) # show the image self.root.after(1, self.video_loop) # call the same function after 30 milliseconds But you knew - do such a things "on fly" isn't a good idea, so lets try to resize all frames first and then do all stuff(only Application class and video_loop method changed, resize_video method added): class Application: def __init__(self, output_path = "./"): """ Initialize application which uses OpenCV + Tkinter. It displays a video stream in a Tkinter window and stores current snapshot on disk """ self.vs = cv2.VideoCapture('KeyCat.mp4') # capture video frames, 0 is your default video camera ... # init frames self.frames = self.resize_video() self.video_loop() def resize_video(self): temp = list() try: temp_count_const = cv2.CAP_PROP_FRAME_COUNT except AttributeError: temp_count_const = cv2.cv.CV_CAP_PROP_FRAME_COUNT frames_count = self.vs.get(temp_count_const) while self.vs.isOpened(): ok, frame = self.vs.read() # read frame from video stream if ok: # frame captured without any errors cv2image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGBA) # convert colors from BGR to RGBA cv2image = cv2.resize(cv2image, self.size, interpolation=cv2.INTER_NEAREST) cv2image = Image.fromarray(cv2image) # convert image for PIL temp.append(cv2image) # simple progress print w/o sys import print('%d/%d\t%d%%' % (len(temp), frames_count, ((len(temp)/frames_count)*100))) else: return temp def video_loop(self): """ Get frame from the video stream and show it in Tkinter """ if len(self.frames) != 0: self.current_image = self.frames.pop(0) self.panel.imgtk = ImageTk.PhotoImage(self.current_image) self.panel.config(image=self.panel.imgtk) self.root.after(1, self.video_loop) # call the same function after 30 milliseconds timeit elapsed time of showing pre-resized frames: ~78.78 s. As you see - resizing isn't a main problem of your script, but a good option!