前文我們已經(jīng)完成了一個(gè)集暫停、倍速、顯示進(jìn)度條功能為一體的視頻播放器,今天我們?cè)賮?lái)增加一個(gè)新的功能——發(fā)送彈幕。
tkinter播放視頻的原理,就是讀取每一幀的圖片,然后刷新畫布。所以如果想實(shí)現(xiàn)彈幕功能,只要獲取輸入文本框中的文本,再寫到圖片上就好了。cv2.putText能夠?qū)崿F(xiàn)這個(gè)功能,但無(wú)法添加中文,所以我們需要換一種方法。
首先,我們需要from PIL import ImageDraw,ImageFont
然后用ImageDraw.Draw轉(zhuǎn)換圖片格式,轉(zhuǎn)換后就可以調(diào)用.text()往圖片上寫中文了。
具體實(shí)現(xiàn)過(guò)程,只需要修改前文的一個(gè)函數(shù):
def tkImage(n):
global nowt,pointx,txt
#倍速在這里實(shí)現(xiàn)
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") #參數(shù)1:字體文件路徑,參數(shù)2:字體大小
#txt是讀取的Entry內(nèi)容,在button關(guān)聯(lián)的函數(shù)中獲取
if txt!="":
draw.text((pointx, pointy), txt, (255, 255, 255), font=font) #pointx, pointy是文字添加的位置
pointx-=10 #每次向左移動(dòng)10個(gè)單位
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 #記錄當(dāng)前的幀數(shù)
return tkImage
現(xiàn)在我們把前文的顯示四張圖像改成一張,重新整理一下代碼:
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 #界面長(zhǎng)
image_width=int(window_width*0.5) #圖像寬
image_height=int(window_height*0.5) #圖像長(zhǎng)
imagepos_x=0 #畫布位置x
imagepos_y=0 #畫布位置Y
lock=0 #暫停標(biāo)志
n=1 #初始倍速
nowt=0 #當(dāng)前幀數(shù)
nows=0 #當(dāng)前播放時(shí)長(zhǎng)(秒)
pointx=0 #彈幕起始點(diǎn)橫坐標(biāo)
pointy=0 #彈幕起始點(diǎn)縱坐標(biāo)
txt="" #彈幕內(nèi)容
#獲取當(dāng)前圖像
def tkImage(n):
#倍速在這里實(shí)現(xià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")#參數(shù)1:字體文件路徑,參數(shù)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 #每過(guò)一秒則清零重計(jì)
nows+=1 #每過(guò)一秒,當(dāng)前播放時(shí)間也加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 #當(dāng)前時(shí)間
show=showt+"/"+totalt #最終顯示格式(當(dāng)前時(shí)間/總時(shí)長(zhǎng))
tlabel.config(text=show)
canvas.coords(fill_line, (0, 0, int(window_width/tt*nows), 15)) #填充進(jìn)度條
canvas1.create_image(0,0,anchor='nw',image=picture1)
win.update_idletasks()
win.update()
else:
win.update_idletasks()
win.update()
except:
return
#每次開(kāi)始播放前初始化
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坐標(biāo)
pointy=random.randint(50,150) #初始y坐標(biāo)隨機(jī)
E1.delete(0, END)
#播放選中文件
def start():
global vc1,size,frames_num,fps,totalt,tt,pointx
val = theLB.get() #獲得下拉框當(dāng)前內(nèi)容
vc1 = cv2.VideoCapture(filePath+val) #讀取視頻
size = (int(vc1.get(cv2.CAP_PROP_FRAME_HEIGHT)), int(vc1.get(cv2.CAP_PROP_FRAME_WIDTH))) #視頻圖像的長(zhǎng)和寬
frames_num=vc1.get(cv2.CAP_PROP_FRAME_COUNT) #總幀數(shù)
fps = vc1.get(cv2.CAP_PROP_FPS) #幀率
totalt=str(int(frames_num/fps/60))+":"+str(int(frames_num/fps)%60) #視頻時(shí)長(zhǎng)
tt=int(frames_num/fps) #進(jìn)度條疊滿所需次數(shù)
pointx=size[1] #彈幕起始坐標(biāo)x
video()
#跳轉(zhuǎn)到指定位置
def kj():
global nows,vc1
time=int(int(s1.get())*frames_num/fps/100) #要跳轉(zhuǎn)到的時(shí)長(zhǎng)
vc1.set(cv2.CAP_PROP_POS_FRAMES,int(time*fps)) #讀取到指定幀數(shù)
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)
#顯示進(jìn)度條的畫布
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') #坐標(biāo)是相對(duì)于畫布的
#彈幕輸入框
E1 = Entry(win, bd =5,width=100)
E1.place(x=0, y=745)
#發(fā)送彈幕按鈕
B1 = Button(win, text="發(fā)送", command=get_txt,font=('黑體', 10),fg='blue',width=10,height=2)
B1.place(x=750, y=742)
#顯示時(shí)間的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) #默認(rèn)選第一個(gè)
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)
#跳轉(zhuǎn)按鈕
B3 = Button(win, text="跳轉(zhuǎn)", command=kj,font=('黑體', 10),fg='red',width=10,height=2)
B3.place(x=980, y=150)
#選擇跳轉(zhuǎn)位置的滾動(dòng)條
s1 = Scale(win,from_=0,to=99,orient=HORIZONTAL) #orient=HORIZONTAL設(shè)置水平方向顯示
s1.place(x=970, y=100)
win.mainloop()
上述代碼不僅實(shí)現(xiàn)了我們所說(shuō)的功能,我還用下拉框Combobox自動(dòng)加載文件夾下的所有文件名,以供選擇文件播放;并且能夠拖動(dòng)滾動(dòng)條跳轉(zhuǎn)到指定位置,利用了cv2.VideoCapture的.set()方法。
怎么樣,更像一個(gè)視頻播放器了吧?如果你還想往下做,可以加入“添加到播放列表”的功能,選擇其他路徑下的文件添加到全局變量mlist里,然后更新一下Combobox的values即可。