• Tkinter做一个本地视频播放器


    前两天一直在跟文本和图片打交道,今天我们更进一步,做一个能够播放本地视频文件的播放器。

    主要用到了opencv库,原理和实时的摄像头显示是一样,只是把每一帧图像经过转换后封装到tkinter上。但是这个图像的显示,要想没有延迟、且不占用过多内存,只能使用canvas画布来实现。只想把视频播放出来的话,也可以用label显示图片,然后调用.after()方法更新,但是这种方法至少要把更新间隔设为10ms(i7处理器),否则会无法正常显示,而且内存也会逐渐增长。

    我们直接来看完整代码:

    import pygame as py
    import _thread
    import time
    import tkinter as tk
    from tkinter import *
    import cv2
    from PIL import Image, ImageTk
    import multiprocessing
    
    window_width=960
    window_height=720
    image_width=int(window_width*0.5)
    image_height=int(window_height*0.5)
    imagepos_x=0
    imagepos_y=0
    butpos_x=450
    butpos_y=450
    vc1 = cv2.VideoCapture('25.mp4')  #读取视频
    
    #图像转换,用于在画布中显示
    def tkImage(vc):
        ref,frame = vc.read()
        cvimage = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
        pilImage = Image.fromarray(cvimage)
        pilImage = pilImage.resize((image_width, image_height),Image.ANTIALIAS)
        tkImage =  ImageTk.PhotoImage(image=pilImage)
        return tkImage
    #图像的显示与更新
    def video():
        def video_loop():
           try:
                while True:
                    picture1=tkImage(vc1)
                    canvas1.create_image(0,0,anchor='nw',image=picture1)  
                    canvas2.create_image(0,0,anchor='nw',image=picture1)
                    canvas3.create_image(0,0,anchor='nw',image=picture1)
                    canvas4.create_image(0,0,anchor='nw',image=picture1)
                    win.update_idletasks()  #最重要的更新是靠这两句来实现
                    win.update()
           except:
                pass
              
        video_loop()
        win.mainloop()
        vc1.release()
        cv2.destroyAllWindows()
    
    '''布局'''
    win = tk.Tk()
    win.geometry(str(window_width)+'x'+str(window_height))
    canvas1 =Canvas(win,bg='white',width=image_width,height=image_height)
    canvas1.place(x=imagepos_x,y=imagepos_y)
    canvas2 =Canvas(win,bg='white',width=image_width,height=image_height)
    canvas2.place(x=480,y=0)
    canvas3 =Canvas(win,bg='white',width=image_width,height=image_height)
    canvas3.place(x=imagepos_x,y=360)   
    canvas4 =Canvas(win,bg='white',width=image_width,height=image_height)
    canvas4.place(x=480,y=360) 
    
    if __name__ == '__main__': 
        p1 = multiprocessing.Process(target=video)
        p1.start()

    你可能会很奇怪,为什么要用到多进程?这里我先卖一个关子,现在这个程序里其实并不需要多进程,但是一会我们就用到了。

    如果我们实现了以上内容,我们发现了一个很严重的问题——没有声音!这是因为cv2.VideoCapture是无法获取声音的,可是看视频没声怎么行,总不能只看卓别林和叶逢春吧?

    我琢磨了许久,看来要想播放声音,只能单独提取出音频文件,和视频一起播放了。提取mp4中的音频,并写入mp3文件,需要moviepy这个库,代码很简单:

    from moviepy.editor import*  
    video = VideoFileClip('25.mp4') 
    audio = video.audio 
    audio.write_audiofile('25.mp3')

    800M的视频,提取出的音频文件只有50M左右,还算能接受吧。

    接下来只要在播放视频的同时播放音频就可以了,最开始我尝试了用多线程,发现音频会影响tkinter的刷新,导致视频十分卡顿,所以我就改用了多进程,视频终于不卡了(如果画布太大还是会略有延迟,我现在设的大小基本没有延迟了)。

    用pygame来播放mp3文件:

    def voice():
        py.mixer.init()
        # 文件加载
        track=py.mixer.music.load('25.mp3')
        # 播放,第一个是播放值 -1代表循环播放, 第二个参数代表开始播放的时间
        py.mixer.music.play(-1, 0)
        while 1:  #一定要有whlie让程序暂停在这,否则会自动停止
            pass

    最后的主函数改为:(多进程的实现一定要放在主函数里)

    if __name__ == '__main__': 
        p1 = multiprocessing.Process(target=voice)
        p2 = multiprocessing.Process(target=video)
        p1.start() 
        p2.start()

    (好吧,其实不用多进程也行,在播放视频前先执行播放音频的语句就行,音频会在后台自动运行,但是会让视频变卡)

    这样一个简单的本地视频播放器就实现了,但是每看一个视频都要提取出音频,未免太智障了吧?所以今天这个程序玩玩就行,用处不大……(除非你爱看相声,提取出的音频还能放到手机里随时听)

    但是,你以为到这就结束了吗?

    刚才我们同时创建了四个画布,一起播放视频。同样的方法,是不是可以用来做视频监控呢?就像电影里演的那样,屏幕上显示好几个摄像头的监控影像,其实用tkinter就能实现了!当然,如果同时显示太多图像,延迟肯定会增加。

    那么作业来了——

    小作业:制作一个多摄像头的实时监控软件,同时检测图像中是否有人物出现,一旦有人则立刻报警。(提示:摄像头图像的人脸识别上网一搜就能找到,需要调用opencv官方提供的人脸分类器文件;报警的方式则有很多,如果不嫌麻烦的话,可以用twilio给自己发短信)

    你以为这又结束了?呵呵,你还是不了解我啊……

    既然是视频软件,怎么少得了暂停与倍速的功能呢?

    先说暂停,我们用单机左键暂停,再点一下继续。我们需要加一个lock变量作为视频是否播放的判断条件,初始值设为0,每次点击左键就加一;

    至于倍速功能,则绑定右键事件,倍速值也是每点击一次则加一,并且设置倍速上限为4倍;倍速的实现在tkImage函数里。

    增加和修改的代码如下:

    lock=0  #暂停标志
    n=1  #初始倍速
    
    def tkImage(n):
        #倍速在这里实现
        for i in range(n):
            ref,frame = vc1.read()
        cvimage = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)  #注意这句,后面再说明
        pilImage = Image.fromarray(cvimage)
        pilImage = pilImage.resize((image_width, image_height),Image.ANTIALIAS)
        tkImage1 =  ImageTk.PhotoImage(image=pilImage)
        return tkImage1
    
    def video():    
        def video_loop():
           try:
                while True:
                    if lock % 2 == 0:
                        picture1=tkImage(n)
                        canvas1.create_image(0,0,anchor='nw',image=picture1)  
                        canvas2.create_image(0,0,anchor='nw',image=picture1)
                        canvas3.create_image(0,0,anchor='nw',image=picture1)
                        canvas4.create_image(0,0,anchor='nw',image=picture1)
                        win.update_idletasks()  #最重要的更新是靠这两句来实现
                        win.update()
                    else:
                        win.update_idletasks()  #最重要的更新是靠这两句来实现
                        win.update()
           except:
                pass
              
        video_loop()
        win.mainloop()
        vc1.release()
        cv2.destroyAllWindows()
    def right(self):
        global n
        n+=1
        if n>4:
            n=1
    def left(self):
        global lock
        lock+=1
    
    #放在创建canvas的后面
    canvas1.bind('<Button-1>', left)
    canvas1.bind('<Button-3>', right)

    当然,这两个功能仅限于视频,另一个进程的音频文件是无法暂停和倍速的。所以啊,还是得看默片。

    注意事项:

    tkImage函数中的cvimage = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY),和前文同样位置的cvimage = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)不同,前者是灰度图,后者是彩色图。如果我们用灰度图的话,会有丢帧现象,播放速度变成正常速度的1.5倍左右。

    抛开音频不谈,这个播放器还是差点意思——没有时间和进度条啊!时间好说,cv2.VideoCapture读取视频后,可以用.get()获取总帧数和帧率,做除法就是总时间(比如1000和40,那么时长就是40秒),然后在每次读帧的时候计数,每过一个帧率就是一秒,最后用label显示出来就行了。

    至于进度条咋办呢?一样不难!用canvas.create_rectangle绘制整个进度条的矩形框,然后用canvas.coords来填充。你可以每过一个帧率就填充一次,也可以自定义填充频率,只要根据矩形框的宽度,计算好每次填充的大小就行。注意:这两个函数的参数都包括了矩形框的对角线坐标,但是这个坐标不是绝对坐标,而是相对于矩形框所在的canvas的坐标。

    怎么样,能暂停、开始,能倍速,能显示时长和进度条的视频播放器就此完成了。如果你喜欢看默剧的话,快点玩起来吧!

  • 相关阅读:
    【hihocoder】1237 : Farthest Point 微软2016校招在线笔试题
    POJ 1061青蛙的约会。求解(x+mT)%L=(y+nT)%L的最小步数T。
    Gym 100633G Nano alarm-clocks
    shell script 的追踪与 debug
    vim使用心得
    Linux修改vimrc配置文件,让vi更贴心
    shell script中的case……esac判断
    stl容器区别: vector list deque set map-底层实现
    const对象为什么可以在头文件中定义
    shell script中的syntax error near unexpected token `then' 问题
  • 原文地址:https://www.cnblogs.com/Skypeduty1225/p/16259306.html
Copyright © 2020-2023  润新知