前文我们已经完成了一个集暂停、倍速、显示进度条功能为一体的视频播放器,今天我们再来增加一个新的功能——发送弹幕。
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 + = n #记录当前的帧数 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即可。
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现