My app extracts frames from a video, when the function get_frames
gets called it should set some MDApp
variables and change to screen named extracting
, then when it finishes extracting the frames it should go to completed
screen.
But for some reason it never shows the extracting
screen, the code never shows any error and just goes directly to completed screen
when it finishes extracting.
What is causing this?
How can I change my code to fix this error? Code:
from kivymd.app import MDApp
from kivy.properties import StringProperty, NumericProperty
from kivy.config import Config
from kivy.uix.screenmanager import ScreenManager
from kivymd.uix.screen import MDScreen
#from marquee import Marquee
from kivy.uix.floatlayout import FloatLayout
import cv2
import time
import datetime
import os
from tkinter.filedialog import askopenfilename
from tkinter import Tk
Config.set('graphics', 'resizable', '0')
Config.set('graphics', 'width', '500')
Config.set('graphics', 'height', '200')
Config.write()
def direxist(dir):
if os.path.isdir(dir) != True:
os.mkdir(dir)
return dir
else:
a = f'{dir}(1)'
return direxist(a)
""""class Marquee():
pass"""
class Completed(MDScreen):
pass
class Extracting(MDScreen):
pass
class SelectFileScreen(MDScreen):
def get_image_one(self):
Tk().withdraw() # avoids window accompanying tkinter FileChooser
filename = askopenfilename(
initialdir = f"{os.getcwd()}",
title = "Select a File",
)
if filename != '':
MDApp.get_running_app().app_filepath = filename
self.manager.transition.direction = 'left'
self.manager.current = 'settings'
class GetFramesScreen(MDScreen):
base_dir_for_frames = StringProperty(f"{os.path.join(os.path.expanduser('~'),'Desktop')}")
def get_frames(self):
start_time = time.time()
MDApp.get_running_app().file_name = str(os.path.basename(MDApp.get_running_app().app_filepath))
saved_frame_dir = os.path.join(self.base_dir_for_frames,os.path.basename(MDApp.get_running_app().app_filepath)).replace('.', '_').replace('\01', '01')
b = direxist(saved_frame_dir)
print(f'pathe to save ->{b}')
vidcap = cv2.VideoCapture(MDApp.get_running_app().app_filepath)
fps= vidcap.get(cv2.CAP_PROP_FPS)
MDApp.get_running_app().fps = str(int(fps))
frame_count = vidcap.get(cv2.CAP_PROP_FRAME_COUNT)
capduration = int(frame_count/fps)
video_time = str(datetime.timedelta(seconds=capduration))
MDApp.get_running_app().video_duration = str(video_time)
self.manager.transition.direction = 'right'
self.manager.current = 'extracting'
success,frame = vidcap.read()
cont = 1
n_extracted_frames = 0
while success:
if cont == 1 or cont%int(fps) == 0:
seconds1 = (vidcap.get(cv2.CAP_PROP_POS_MSEC))/(1000)
video_time2 = str(datetime.timedelta(seconds=seconds1))
x = video_time2.replace(':','.')
formatted = f"frame{cont}_{x}.jpg"
dd = os.path.join(str(b),formatted)
cv2.imwrite(dd,frame)
#MDApp.get_running_app().pb = cont
#print(MDApp.get_running_app().pb)
n_extracted_frames+=1
success,frame = vidcap.read()
cont+=1
end_time =time.time() - start_time
MDApp.get_running_app().extracted_total = str(n_extracted_frames)
MDApp.get_running_app().time_taken = str(datetime.timedelta(seconds=end_time))
MDApp.get_running_app().saved_path = str(b)
self.manager.transition.direction = 'right'
self.manager.current = 'completed'
class GetframesApp(MDApp):
app_filepath = StringProperty('Not Set')
#totalf = StringProperty('-')
iiii = StringProperty('-')
extracted_total = StringProperty('-')
time_taken = StringProperty('-')
saved_path = StringProperty('-')
file_name = StringProperty('-')
fps = StringProperty('-')
video_duration = StringProperty('-')
pb = NumericProperty(0)
def build(self):
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Purple"
sm = ScreenManager()
sm.add_widget(SelectFileScreen(name='menu'))
sm.add_widget(GetFramesScreen(name='settings'))
sm.add_widget(Extracting(name = 'extracting'))
sm.add_widget(Completed(name = 'completed'))
return sm
GetframesApp().run()
kv file:
<SelectFileScreen>:
MDFillRoundFlatButton:
text: 'Select file'
md_bg_color: 1, 0, 1, 1
on_press: root.get_image_one()
pos_hint: {'center_x':0.5, 'center_y':0.5}
<GetFramesScreen>:
MDFloatLayout:
size: (500,200)
MDIconButton:
icon: "arrow-left-bold"
pos_hint: {'x':0, 'y':.79}
on_press:
root.manager.transition.direction = 'right'
root.manager.current = 'menu'
BoxLayout:
orientation: 'vertical'
padding: [25, 40, 0, 0]
MDStackLayout:
adaptive_height: True
orientation: 'rl-tb'
MDRaisedButton:
text: 'Get frames'
md_bg_color: 1, 0, 1, 1
on_press: root.get_frames()
<Extracting>:
MDFloatLayout:
size: (500,200)
MDLabel:
text: 'Extracting: '+app.file_name
halign: 'center'
pos: (0,60)
BoxLayout:
orientation: 'horizontal'
pos: (0, 10)
MDLabel:
text: 'FPS: '+app.fps
halign: 'center'
MDLabel:
text: 'Video duration: '+app.video_duration
halign: 'center'
MDProgressBar:
value: app.pb
max: 100
pos: (0, -40)
<Completed>:
MDFloatLayout:
size: (500,200)
MDLabel:
text: 'Completed frame extraction!'
halign: 'center'
pos: (0,70)
MDLabel:
text: app.extracted_total+' frames extracted.'
halign: 'center'
pos: (0,40)
MDLabel:
text: 'in '+app.time_taken+' hrs'
halign: 'center'
pos: (0,20)
MDLabel:
text: 'saved at '+app.saved_path
halign: 'center'
pos: (0,0)
MDFillRoundFlatButton:
text: 'Select new file'
halign: 'center'
pos_hint: {'center_x': 0.5, 'center_y': 0.3}
md_bg_color: 1, 0, 1, 1
on_press:
root.manager.transition.direction = 'left'
root.manager.current = 'menu'
You are running your frame extraction code on the main thread, and kivy updates the GUI on the main thread. But as long as you are holding the main thread with a long running method, kivy never gets an opportunity to update the GUI until that method completes and surrenders the main thread.
The fix is to run the frame extraction on a new thread. Here is a modified version of your GetFramesScreen
that does that:
class GetFramesScreen(MDScreen):
base_dir_for_frames = StringProperty(f"{os.path.join(os.path.expanduser('~'), 'Desktop')}")
def get_frames(self): # this is run on the main thread
self.manager.transition.direction = 'right'
self.manager.current = 'extracting'
threading.Thread(target=self.get_frames_thread, daemon=True).start()
def get_frames_thread(self): # this is run on a new thread
start_time = time.time()
MDApp.get_running_app().file_name = str(os.path.basename(MDApp.get_running_app().app_filepath))
saved_frame_dir = os.path.join(self.base_dir_for_frames,
os.path.basename(MDApp.get_running_app().app_filepath)).replace('.',
'_').replace(
'\01', '01')
b = direxist(saved_frame_dir)
print(f'pathe to save ->{b}')
vidcap = cv2.VideoCapture(MDApp.get_running_app().app_filepath)
fps = vidcap.get(cv2.CAP_PROP_FPS)
MDApp.get_running_app().fps = str(int(fps))
frame_count = vidcap.get(cv2.CAP_PROP_FRAME_COUNT)
capduration = int(frame_count / fps)
video_time = str(datetime.timedelta(seconds=capduration))
MDApp.get_running_app().video_duration = str(video_time)
success, frame = vidcap.read()
cont = 1
n_extracted_frames = 0
while success:
if cont == 1 or cont % int(fps) == 0:
seconds1 = (vidcap.get(cv2.CAP_PROP_POS_MSEC)) / (1000)
video_time2 = str(datetime.timedelta(seconds=seconds1))
x = video_time2.replace(':', '.')
formatted = f"frame{cont}_{x}.jpg"
dd = os.path.join(str(b), formatted)
cv2.imwrite(dd, frame)
# MDApp.get_running_app().pb = cont
# print(MDApp.get_running_app().pb)
n_extracted_frames += 1
success, frame = vidcap.read()
cont += 1
end_time = time.time() - start_time
MDApp.get_running_app().extracted_total = str(n_extracted_frames)
MDApp.get_running_app().time_taken = str(datetime.timedelta(seconds=end_time))
MDApp.get_running_app().saved_path = str(b)
Clock.schedule_once(self.go_to_completed) # run the switch to the completed Screen back on the main thread
def go_to_completed(self, dt): # this is run on the main thread
self.manager.transition.direction = 'right'
self.manager.current = 'completed'