• Tkinter做一个本地视频播放器(2)——弹幕


    前文我们已经完成了一个集暂停、倍速、显示进度条功能为一体的视频播放器,今天我们再来增加一个新的功能——发送弹幕。

    tkinter播放视频的原理,就是读取每一帧的图片,然后刷新画布。所以如果想实现弹幕功能,只要获取输入文本框中的文本,再写到图片上就好了。cv2.putText能够实现这个功能,但无法添加中文,所以我们需要换一种方法。

    首先,我们需要from PIL import ImageDraw,ImageFont

    然后用ImageDraw.Draw转换图片格式,转换后就可以调用.text()往图片上写中文了。

    具体实现过程,只需要修改前文的一个函数:

    def tkImage(n):
        global nowt,pointx,txt
        #倍速在这里实现
        for i in range(n):
            ref,frame = vc1.read()
        pilImage = Image.fromarray(frame)
        draw = ImageDraw.Draw(pilImage)
        font = ImageFont.truetype("simhei.ttf", 40, encoding="utf-8")  #参数1:字体文件路径,参数2:字体大小
        #txt是读取的Entry内容,在button关联的函数中获取
        if txt!="":
            draw.text((pointx, pointy), txt, (255, 255, 255), font=font)  #pointx, pointy是文字添加的位置
            pointx-=10  #每次向左移动10个单位
            if(pointx==0):
                pointx=size[1]
                txt=""
        pilImage = cv2.cvtColor(np.array(pilImage), cv2.COLOR_BGR2RGB)
        pilImage = Image.fromarray(pilImage)
        pilImage = pilImage.resize((window_width, window_height),Image.ANTIALIAS)
        tkImage =  ImageTk.PhotoImage(image=pilImage)
        
        nowt+=n  #记录当前的帧数
        return tkImage
    

      

    现在我们把前文的显示四张图像改成一张,重新整理一下代码:

    import time
    import tkinter as tk
    from tkinter import *
    from tkinter import ttk
    import cv2
    from PIL import Image, ImageTk,ImageFilter,ImageDraw,ImageFont
    import multiprocessing
    import numpy as np
    import random
    import os
    
    filePath = 'D:\\movie\\'  #电影存放路径
    mlist=os.listdir(filePath)  #文件夹下所有文件名称
    window_width=960  #界面宽
    window_height=760  #界面长
    image_width=int(window_width*0.5)  #图像宽
    image_height=int(window_height*0.5)  #图像长
    imagepos_x=0  #画布位置x
    imagepos_y=0  #画布位置Y
    lock=0  #暂停标志
    n=1  #初始倍速
    nowt=0  #当前帧数
    nows=0  #当前播放时长(秒)
    pointx=0  #弹幕起始点横坐标
    pointy=0  #弹幕起始点纵坐标
    txt=""  #弹幕内容
    
    #获取当前图像
    def tkImage(n):
        #倍速在这里实现
        global nowt,pointx,pointy,txt
        for i in range(n):
            ref,frame = vc1.read()
        pilImage = Image.fromarray(frame)
        draw = ImageDraw.Draw(pilImage)
        font = ImageFont.truetype("simhei.ttf", 40, encoding="utf-8")#参数1:字体文件路径,参数2:字体大小
        if txt!="":
            draw.text((pointx, pointy), txt, (255, 255, 255), font=font)
            pointx-=10
            if(pointx==-50):
                pointx=size[1]
                txt=""
        pilImage = cv2.cvtColor(np.array(pilImage), cv2.COLOR_BGR2RGB)
        pilImage = Image.fromarray(pilImage)
        pilImage = pilImage.resize((window_width, 720),Image.ANTIALIAS)
        tkImage1 =  ImageTk.PhotoImage(image=pilImage)
        
        nowt+=n
        return tkImage1
    
    #图像的显示与更新
    def video():    
        global nows,nowt
        def video_loop():
           global nows,nowt
           try:
                while True:
                    if lock % 2 == 0:  #是否暂停
                        picture1=tkImage(n)
                        if nowt >= fps:
                            nowt=0  #每过一秒则清零重计
                            nows+=1  #每过一秒,当前播放时间也加1
                            mi=str(int(nows/60))  #分钟
                            se=str(int(nows%60))  #秒
                            if int(mi)<10:
                                mi="0"+mi
                            if int(se)<10:
                                se="0"+se
                            showt=mi+":"+se  #当前时间
                            show=showt+"/"+totalt  #最终显示格式(当前时间/总时长)
                            tlabel.config(text=show)
                            canvas.coords(fill_line, (0, 0, int(window_width/tt*nows), 15))  #填充进度条
                        canvas1.create_image(0,0,anchor='nw',image=picture1)  
                        win.update_idletasks()  
                        win.update()
                    else:
                        win.update_idletasks()  
                        win.update()
           except:
                return
        #每次开始播放前初始化
        nows,nowt=0,0
        canvas.coords(fill_line, (0, 0, 0, 15))        
        
        video_loop()
        vc1.release()
        cv2.destroyAllWindows()
    
    #右键事件:倍速
    def right(self):
        global n
        n+=1
        if n>4:
            n=1
    #左键事件:暂停
    def left(self):
        global lock
        lock+=1
    #按钮事件:获取弹幕
    def get_txt():
        global txt,pointx,pointy
        txt=E1.get()
        pointx=size[1]  #初始x坐标
        pointy=random.randint(50,150)  #初始y坐标随机
        E1.delete(0, END)
    #播放选中文件
    def start():
        global vc1,size,frames_num,fps,totalt,tt,pointx
        val = theLB.get()  #获得下拉框当前内容
        vc1 = cv2.VideoCapture(filePath+val)  #读取视频
        size = (int(vc1.get(cv2.CAP_PROP_FRAME_HEIGHT)), int(vc1.get(cv2.CAP_PROP_FRAME_WIDTH)))  #视频图像的长和宽
        frames_num=vc1.get(cv2.CAP_PROP_FRAME_COUNT)  #总帧数
        fps = vc1.get(cv2.CAP_PROP_FPS)  #帧率
        totalt=str(int(frames_num/fps/60))+":"+str(int(frames_num/fps)%60)  #视频时长
        tt=int(frames_num/fps)  #进度条叠满所需次数
        pointx=size[1]  #弹幕起始坐标x
        video()
    #跳转到指定位置
    def kj():
        global nows,vc1
        time=int(int(s1.get())*frames_num/fps/100)  #要跳转到的时长
        vc1.set(cv2.CAP_PROP_POS_FRAMES,int(time*fps))  #读取到指定帧数
        ref,frame = vc1.read()
        nows=time
    
    '''布局'''
    win = tk.Tk()
    win.geometry(str(window_width+120)+'x'+str(window_height+20))
    #显示视频的画布
    canvas1 =Canvas(win,bg='white',width=window_width,height=720)
    canvas1.place(x=imagepos_x,y=imagepos_y)
    canvas1.bind('<Button-1>', left)
    canvas1.bind('<Button-3>', right)
    #显示进度条的画布
    canvas = Canvas(win, width=image_width*2, height=15, bg="white")
    canvas.place(x=0, y=722)
    fill_line=canvas.create_rectangle(0,0,0,15,fill = 'LightGreen')  #坐标是相对于画布的
    #弹幕输入框
    E1 = Entry(win, bd =5,width=100)
    E1.place(x=0, y=745)
    #发送弹幕按钮
    B1 = Button(win, text="发送", command=get_txt,font=('黑体', 10),fg='blue',width=10,height=2)
    B1.place(x=750, y=742)
    #显示时间的Label
    tlabel = Label(win,font=('黑体', 13),text='')
    tlabel.place(x=850, y=750)
    #选择影片下拉框
    theLB = ttk.Combobox(win,width=12,height=10)
    theLB["values"] = mlist
    theLB.current(0)  #默认选第一个
    theLB.place(x=965, y=0)
    #播放影片按钮
    B2 = Button(win, text="播放", command=start,font=('黑体', 10),fg='red',width=10,height=2)
    B2.place(x=980, y=50)
    #跳转按钮
    B3 = Button(win, text="跳转", command=kj,font=('黑体', 10),fg='red',width=10,height=2)
    B3.place(x=980, y=150)
    #选择跳转位置的滚动条
    s1 = Scale(win,from_=0,to=99,orient=HORIZONTAL)  #orient=HORIZONTAL设置水平方向显示
    s1.place(x=970, y=100)
    win.mainloop()
    

      

    上述代码不仅实现了我们所说的功能,我还用下拉框Combobox自动加载文件夹下的所有文件名,以供选择文件播放;并且能够拖动滚动条跳转到指定位置,利用了cv2.VideoCapture的.set()方法。

    怎么样,更像一个视频播放器了吧?如果你还想往下做,可以加入“添加到播放列表”的功能,选择其他路径下的文件添加到全局变量mlist里,然后更新一下Combobox的values即可。

  • 相关阅读:
    【转载】关于Java String, StringBuilder, StringBuffer, Hashtable, HashMap的面试题
    LeetCode: 【L4】N-Queens 解题报告
    【转载】在美国找工作秘籍
    Lintcode: Kth Largest Element 解题报告
    LeetCode: Reverse Integer 解题报告
    Lintcode: First Bad Version 解题报告
    九章面试题:Find first K frequency numbers 解题报告
    tomcat之虚拟目录
    百度搜索结果如何屏蔽百家号内容
    CentOS7之Rsync+Inotify架构实现实时同步文件和文件夹
  • 原文地址:https://www.cnblogs.com/Skypeduty1225/p/16259317.html
Copyright © 2020-2023  润新知