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

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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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+=#记录当前的帧数
    return tkImage

  

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
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即可。