You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Kivy无法切换至extracting屏幕,manager.current设置失效问题排查

问题描述

我开发的视频抽帧App中,调用get_frames函数时,本应切换至extracting屏幕显示抽帧状态,完成后再跳转至completed屏幕。但实际运行时,extracting屏幕从未显示,无任何报错,抽帧完成后直接跳转到completed屏幕。

原因分析

Kivy的UI渲染和事件处理都在主线程中执行。当前get_frames函数里,切换到extracting屏幕的指令之后,紧接着是耗时的视频抽帧循环(cv2读取帧、保存图片的操作),这部分代码会完全阻塞主线程。主线程被阻塞时,无法及时渲染extracting屏幕的UI内容,直到整个抽帧循环执行完毕,才会处理后续的屏幕切换指令,因此用户看不到extracting屏幕的过渡状态。

修复方案

将耗时的抽帧操作放到子线程中执行,让主线程专注于UI更新和事件响应。同时,更新UI元素(比如进度条)时,必须通过Kivy的Clock.schedule_once回到主线程操作(因为Kivy不允许在子线程中直接修改UI)。

修改后的核心代码

1. 补充导入模块

在代码顶部添加线程和Clock的导入:

from threading import Thread
from kivy.clock import Clock

2. 重构get_frames方法

将抽帧逻辑拆分到子线程函数中,主线程只负责切换屏幕和触发子线程:

class GetFramesScreen(MDScreen):
    base_dir_for_frames = StringProperty(f"{os.path.join(os.path.expanduser('~'),'Desktop')}")
    
    def get_frames(self):
        # 立即切换到extracting屏幕,主线程优先渲染UI
        self.manager.transition.direction = 'right'
        self.manager.current = 'extracting'
        
        # 预准备抽帧参数
        app = MDApp.get_running_app()
        app.file_name = str(os.path.basename(app.app_filepath))
        saved_frame_dir = os.path.join(self.base_dir_for_frames, os.path.basename(app.app_filepath)).replace('.', '_').replace('\01', '01')
        save_dir = direxist(saved_frame_dir)
        print(f'保存路径 -> {save_dir}')
        
        vidcap = cv2.VideoCapture(app.app_filepath)
        fps = vidcap.get(cv2.CAP_PROP_FPS)
        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))
        app.video_duration = str(video_time)
        
        # 启动子线程执行抽帧,避免阻塞主线程
        Thread(target=self._extract_frames, args=(vidcap, save_dir, fps, frame_count), daemon=True).start()
    
    def _extract_frames(self, vidcap, save_dir, fps, total_frames):
        start_time = time.time()
        success, frame = vidcap.read()
        cont = 1
        n_extracted_frames = 0
        app = MDApp.get_running_app()
        
        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(save_dir), formatted)
                cv2.imwrite(dd, frame)
                n_extracted_frames += 1
            
            # 通过Clock回到主线程更新进度条
            progress = (cont / total_frames) * 100
            Clock.schedule_once(lambda dt: setattr(app, 'pb', progress))
            
            success, frame = vidcap.read()
            cont += 1
        
        vidcap.release()  # 释放视频资源
        end_time = time.time() - start_time
        
        # 抽帧完成后,回到主线程切换屏幕并更新统计数据
        Clock.schedule_once(lambda dt: self._on_extract_complete(n_extracted_frames, end_time, save_dir))
    
    def _on_extract_complete(self, n_extracted_frames, time_taken, save_dir):
        app = MDApp.get_running_app()
        app.extracted_total = str(n_extracted_frames)
        app.time_taken = str(datetime.timedelta(seconds=time_taken))
        app.saved_path = str(save_dir)
        
        self.manager.transition.direction = 'right'
        self.manager.current = 'completed'

3. 进度条功能激活

原代码中pb变量已定义但未使用,修改后进度条会根据抽帧进度实时更新,让用户直观看到抽帧状态。

效果验证

修改后,点击"Get frames"按钮会立即切换到extracting屏幕,显示视频信息和实时更新的进度条,抽帧完成后自动跳转到completed屏幕,整个流程的UI状态过渡正常。

内容的提问来源于stack exchange,提问作者Abraham Márquez

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.25 11:36:14