如何在PySimpleGUI中实现带声音的视频播放?
在PySimpleGUI中实现带声音的视频播放及计时器功能
要实现需求,我们可以结合OpenCV读取视频帧、Pygame处理音频播放,同时通过PySimpleGUI的定时刷新机制更新视频画面与计时器。以下是完整实现代码:
import PySimpleGUI as sg import cv2 import pygame import time import os # 初始化Pygame音频模块 pygame.mixer.init() sg.theme('darkgray12') colors = (sg.theme_background_color(), sg.theme_background_color()) counter = 1 firstTime = True # 存储每个视频的状态:键为post编号,值为视频/音频相关对象与状态 video_states = {} def post(post_num): """生成单个post帧,接收post编号作为参数""" return [sg.Frame('',[ [sg.Image('', size=(600, 600), key=f'-content-{post_num}-')], [sg.Button('PLAY', key=f'-PLAY-KEY-{post_num}-'), sg.Button('PAUSE', key=f'-PAUSE-KEY-{post_num}-', disabled=True), sg.Text('', key=f'-TIMER-{post_num}-')] ], key=f'-POST-{post_num}')] layout = [ [sg.Frame(title='', layout=[ [sg.pin(sg.Button('LOAD', key='-LOAD-POST-')), sg.Push(), sg.pin(sg.Text(f'#10', key='-POST-COUNTER-TEXT-'))], [sg.pin(sg.Column([], size=(700, 600), key='-COL1-', scrollable=True, vertical_scroll_only=True, expand_y=True, expand_x=True, element_justification='c'))], ], key='-MAIN-FRAME-')], ] window = sg.Window('Video Player in PySimpleGUI', layout, size=(800, 700), grab_anywhere=True, font=('Montserrat', 9, 'bold'), element_justification='c') def format_time(seconds): """将秒数格式化为MM:SS的字符串""" mins = int(seconds // 60) secs = int(seconds % 60) return f"{mins:02d}:{secs:02d}" def init_video(post_num, video_path): """初始化指定post的视频与音频""" # 读取视频 cap = cv2.VideoCapture(video_path) if not cap.isOpened(): sg.popup_error(f"无法打开视频文件: {video_path}") return None # 获取视频参数 fps = cap.get(cv2.CAP_PROP_FPS) total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) total_time = total_frames / fps # 初始化音频(假设音频文件与视频同路径,后缀为mp3,可根据实际调整) audio_path = os.path.splitext(video_path)[0] + '.mp3' if os.path.exists(audio_path): pygame.mixer.music.load(audio_path) else: sg.popup_warning(f"未找到对应音频文件: {audio_path}") return { 'cap': cap, 'fps': fps, 'total_frames': total_frames, 'total_time': total_time, 'current_frame': 0, 'playing': False, 'last_update': time.time() } # 示例视频路径,替换为你自己的视频文件 TEST_VIDEO_PATH = "test_video.mp4" while True: event, values = window.read(10) # 缩短超时时间,提升画面流畅度 if event == sg.WIN_CLOSED: # 释放所有视频资源与停止音频 for state in video_states.values(): if state: state['cap'].release() pygame.mixer.music.stop() pygame.mixer.quit() cv2.destroyAllWindows() break if firstTime: for i in range(10): window.extend_layout(window['-COL1-'], [post(counter)]) # 初始化每个post的视频状态(这里用同一个测试视频,可按需修改) video_states[counter] = init_video(counter, TEST_VIDEO_PATH) window.refresh() window['-COL1-'].contents_changed() counter += 1 firstTime = False if event == '-LOAD-POST-': if not counter >= 40: for i in range(10): window.extend_layout(window['-COL1-'], [post(counter)]) video_states[counter] = init_video(counter, TEST_VIDEO_PATH) window.refresh() window['-COL1-'].contents_changed() counter += 1 window['-POST-COUNTER-TEXT-'].update(f'#{counter - 1}') else: window['-POST-COUNTER-TEXT-'].update(f'#{counter - 1}(MAX)') # 处理PLAY按钮事件 if event.startswith('-PLAY-KEY-'): post_num = int(event.split('-')[-2]) state = video_states.get(post_num) if not state: continue # 标记为播放状态 state['playing'] = True # 启动音频播放 if pygame.mixer.music.get_busy() == 0: pygame.mixer.music.play() # 更新按钮状态 window[f'-PLAY-KEY-{post_num}-'].update(disabled=True) window[f'-PAUSE-KEY-{post_num}-'].update(disabled=False) state['last_update'] = time.time() # 处理PAUSE按钮事件 if event.startswith('-PAUSE-KEY-'): post_num = int(event.split('-')[-2]) state = video_states.get(post_num) if not state: continue # 标记为暂停状态 state['playing'] = False # 暂停音频 pygame.mixer.music.pause() # 更新按钮状态 window[f'-PLAY-KEY-{post_num}-'].update(disabled=False) window[f'-PAUSE-KEY-{post_num}-'].update(disabled=True) # 循环更新所有播放中的视频帧与计时器 for post_num, state in video_states.items(): if not state or not state['playing']: continue # 根据帧率计算是否需要更新帧 elapsed = time.time() - state['last_update'] if elapsed >= 1 / state['fps']: ret, frame = state['cap'].read() if not ret: # 视频播放完毕,重置到开头 state['cap'].set(cv2.CAP_PROP_POS_FRAMES, 0) ret, frame = state['cap'].read() pygame.mixer.music.rewind() if ret: # 转换OpenCV帧为PySimpleGUI可用格式 frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) img_bytes = cv2.imencode('.png', frame)[1].tobytes() window[f'-content-{post_num}-'].update(data=img_bytes) # 更新当前帧数与时间 state['current_frame'] = state['cap'].get(cv2.CAP_PROP_POS_FRAMES) current_time = state['current_frame'] / state['fps'] # 更新计时器文本 timer_text = f"{format_time(current_time)} / {format_time(state['total_time'])}" window[f'-TIMER-{post_num}-'].update(timer_text) state['last_update'] = time.time() window.refresh() window.close()
关键部分说明
- 视频与音频分离处理:OpenCV负责读取视频帧并更新PySimpleGUI的Image组件,Pygame负责音频播放,实现带声音的视频播放。
- 状态管理:用
video_states字典存储每个post的视频捕获对象、播放状态、帧率、时长等信息,方便单独控制每个视频的播放/暂停。 - 计时器格式化:
format_time函数将秒数转换为MM:SS格式,显示当前播放时间与总时长。 - 帧更新控制:通过计算帧率对应的时间间隔,确保视频画面流畅播放,避免过度刷新占用资源。
- 按钮状态切换:播放时禁用PLAY按钮、启用PAUSE按钮,暂停时反之,提升交互体验。
使用注意事项
- 替换代码中的
TEST_VIDEO_PATH为你的实际视频文件路径,确保音频文件(同路径、同文件名后缀为mp3)存在,若无需音频可删除Pygame相关代码。 - 需提前安装依赖库:
pip install pysimplegui opencv-python pygame。 - 若视频画面尺寸与Image组件不匹配,可在
init_video函数中添加帧缩放逻辑,调整帧大小后再显示。
内容的提问来源于stack exchange,提问作者MrLucifer
相关产品推荐
相关产品推荐

