Search code examples
pythonkivykivymd

Kivy wont display screen and manager.current = 'screen' ignored


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'

Solution

  • 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'