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

如何在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按钮,暂停时反之,提升交互体验。

使用注意事项

  1. 替换代码中的TEST_VIDEO_PATH为你的实际视频文件路径,确保音频文件(同路径、同文件名后缀为mp3)存在,若无需音频可删除Pygame相关代码。
  2. 需提前安装依赖库:pip install pysimplegui opencv-python pygame。
  3. 若视频画面尺寸与Image组件不匹配,可在init_video函数中添加帧缩放逻辑,调整帧大小后再显示。

内容的提问来源于stack exchange,提问作者MrLucifer

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 10:54:54