使用Python实现播放".gif"文件增强版
使用Python和第三方库Pillow实现实现播放gif文件,以前介绍过一个简单实现https://blog.csdn.net/cnds123/article/details/137992560 ,那个功能比较简单,实用意义不大。现在介绍一个增强版的。
关于".gif"文件
GIF(Graphics Interchange Format,图形交换格式)是一种由CompuServe公司开发的图像格式,通过连续帧实现动画效果,文件体积小且画面连贯 。GIF本质是静态图片的无损压缩格式,虽支持动画效果,但缺乏音频流且色彩深度有限(仅8位)。
下面介绍用Python实现的控制播放器,用户可细致的控制,包括:打开、播放、暂停、停止,设置播放速度,逐帧控制------前一帧、后一帧。
界面截图:

打开:停止当前播放,加载新GIF文件。
播放:启动动画循环。
暂停:停止动画循环。
停止:暂停播放并重置到第一帧。
前一帧:允许用户逐步后退到前一帧,自动暂停播放。
后一帧:允许用户逐步前进到后一帧,自动暂停播放。
循环处理,当到达第一帧时后退会跳转到最后一帧,反之亦然。
滑动条控制:添加了速度滑动条,允许用户在10ms到500ms之间调整帧延迟。
使用GIF原始速度:可以自动设置GIF的原始平均延迟时间。
状态栏:显示播放中还是暂停状态,当前设置的延迟时间,显示当前帧位置和总帧数(例如:"帧 5/24"),当前设置的帧延迟时间。
这个GIF播放器基于以下三个核心库构建:
Tkinter - Python的标准GUI库,用于创建窗口、按钮等界面元素。
PIL/Pillow - Python图像处理库,用于读取和处理GIF文件。
tkinter.filedialog - 文件选择对话框,用于选择GIF文件。
确保已安装所需的库pillow,关于pillow简要介绍和安装可见https://blog.csdn.net/cnds123/article/details/137992560
源码如下:
python
import tkinter as tk
from tkinter import filedialog, ttk
from PIL import Image, ImageTk
class GifPlayer:
def __init__(self, master):
self.master = master
self.master.title("GIF Player")
self.master.geometry("500x550") # 增加高度以容纳新控件 ☆
#self.master.minsize(400, 450)
# 初始化变量
self.frames = [] # 存储GIF的所有帧
self.current_frame = 0 # 当前帧索引
self.is_playing = False # 播放状态
self.gif_image = None # PIL Image对象
self.frame_delay = 100 # 默认帧延迟(毫秒)
self.original_delays = [] # 存储原始GIF帧延迟
# 创建主框架,用于管理布局
main_frame = tk.Frame(self.master)
main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
# 创建显示GIF的标签
self.label = tk.Label(main_frame, text="请打开一个GIF文件", bg="white",
relief=tk.SUNKEN, bd=1)
self.label.pack(fill=tk.BOTH, expand=True)
# 创建状态显示标签
self.status_label = tk.Label(main_frame, text="状态: 未加载", relief=tk.SUNKEN, bd=1)
self.status_label.pack(fill=tk.X, pady=(5, 0))
# 创建控制面板框架
control_frame = tk.Frame(main_frame)
control_frame.pack(side=tk.BOTTOM, fill=tk.X, pady=10)
# 基本控制部分
basic_control_frame = tk.Frame(control_frame)
basic_control_frame.pack(fill=tk.X, pady=5)
# 打开按钮
self.open_button = tk.Button(basic_control_frame, text="打开", command=self.open_gif)
self.open_button.pack(side=tk.LEFT, padx=5)
# 播放按钮
self.play_button = tk.Button(basic_control_frame, text="播放", command=self.play_gif)
self.play_button.pack(side=tk.LEFT, padx=5)
# 暂停按钮
self.pause_button = tk.Button(basic_control_frame, text="暂停", command=self.pause_gif)
self.pause_button.pack(side=tk.LEFT, padx=5)
# 停止按钮
self.stop_button = tk.Button(basic_control_frame, text="停止", command=self.stop_gif)
self.stop_button.pack(side=tk.LEFT, padx=5)
# 前一帧按钮
self.prev_frame_button = tk.Button(basic_control_frame, text="前一帧", command=self.prev_frame)
self.prev_frame_button.pack(side=tk.LEFT, padx=5)
# 后一帧按钮
self.next_frame_button = tk.Button(basic_control_frame, text="后一帧", command=self.next_frame)
self.next_frame_button.pack(side=tk.LEFT, padx=5)
# 速度控制部分
speed_control_frame = tk.Frame(control_frame)
speed_control_frame.pack(fill=tk.X, pady=5)
# 速度标签
tk.Label(speed_control_frame, text="速度:").pack(side=tk.LEFT, padx=5)
# 速度滑动条
self.speed_scale = tk.Scale(
speed_control_frame,
from_=10, to=500,
orient=tk.HORIZONTAL,
showvalue=False, # 不显示默认数值False, 否者用 True
command=self.set_speed
)
self.speed_scale.set(self.frame_delay) # 设置初始值
self.speed_scale.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5)
# 使用原始延迟按钮
self.use_original_delay_button = tk.Button(
speed_control_frame,
text="使用GIF原始速度",
command=self.use_original_delay
)
self.use_original_delay_button.pack(side=tk.LEFT, padx=5)
# 初始化按钮状态
self.update_button_state()
def open_gif(self):
"""打开文件对话框选择GIF文件并加载"""
# 打开文件对话框,只显示GIF文件
file_path = filedialog.askopenfilename(
title="选择GIF文件",
filetypes=[("GIF文件", "*.gif"), ("所有文件", "*.*")]
)
# 如果用户选择了文件
if file_path:
# 停止当前播放
self.stop_gif()
try:
# 打开新的GIF文件
self.gif_image = Image.open(file_path)
# 获取屏幕尺寸,用于限制GIF显示大小
screen_width = self.master.winfo_screenwidth()
screen_height = self.master.winfo_screenheight()
# 设置最大显示尺寸(留出空间给窗口边框和按钮)
max_width = int(screen_width * 0.7)
max_height = int(screen_height * 0.7) # ☆
# 提取所有帧和原始延迟
self.frames = []
self.original_delays = []
try:
for frame in range(self.gif_image.n_frames):
self.gif_image.seek(frame) # 定位到指定帧
# 获取帧延迟时间(毫秒)
delay = self.gif_image.info.get('duration', 100)
self.original_delays.append(delay)
frame_image = self.gif_image.copy().convert("RGBA") # 复制并转换帧
# 如果GIF尺寸过大,则进行缩放
if frame_image.width > max_width or frame_image.height > max_height:
# 计算缩放比例,保持宽高比
ratio = min(max_width / frame_image.width, max_height / frame_image.height)
new_width = int(frame_image.width * ratio)
new_height = int(frame_image.height * ratio)
frame_image = frame_image.resize((new_width, new_height), Image.Resampling.LANCZOS)
photo = ImageTk.PhotoImage(frame_image) # 转换为Tkinter可用的格式
self.frames.append(photo)
except EOFError:
pass # 处理帧读取完毕的情况
# 重置当前帧索引
self.current_frame = 0
# 显示第一帧
if self.frames:
self.label.config(image=self.frames[0], text="")
# 调整窗口大小以适应GIF,但不超过最大尺寸
img_width = self.frames[0].width()
img_height = self.frames[0].height()
# 设置窗口大小,考虑按钮区域
window_width = min(img_width + 50, max_width)
window_height = min(img_height + 150, max_height) # 增加高度以容纳新控件 ☆
self.master.geometry(f"{window_width}x{window_height}")
# 更新窗口标题显示文件名
self.master.title(f"GIF Player - {file_path.split('/')[-1]}")
# 更新状态显示
self.update_status()
# 更新按钮状态
self.update_button_state()
except Exception as e:
# 如果加载失败,显示错误信息
self.label.config(image="", text=f"无法加载GIF文件:\n{str(e)}")
self.frames = []
self.update_button_state()
self.status_label.config(text="状态: 加载失败")
def update_button_state(self):
"""根据当前状态更新按钮的可用性"""
has_frames = len(self.frames) > 0
# 只有在有帧的情况下,播放、暂停和停止按钮才可用
self.play_button.config(state=tk.NORMAL if has_frames else tk.DISABLED)
self.pause_button.config(state=tk.NORMAL if has_frames else tk.DISABLED)
self.stop_button.config(state=tk.NORMAL if has_frames else tk.DISABLED)
self.prev_frame_button.config(state=tk.NORMAL if has_frames else tk.DISABLED)
self.next_frame_button.config(state=tk.NORMAL if has_frames else tk.DISABLED)
self.speed_scale.config(state=tk.NORMAL if has_frames else tk.DISABLED)
self.use_original_delay_button.config(state=tk.NORMAL if has_frames else tk.DISABLED)
def update_status(self):
"""更新状态显示"""
if self.frames:
status = f"状态: 帧 {self.current_frame+1}/{len(self.frames)}"
if self.is_playing:
status += " - 播放中"
else:
status += " - 暂停"
status += f" - 延迟: {self.frame_delay}ms"
self.status_label.config(text=status)
else:
self.status_label.config(text="状态: 未加载")
def set_speed(self, value):
"""设置播放速度"""
self.frame_delay = int(value)
self.update_status()
def use_original_delay(self):
"""使用GIF的原始延迟时间"""
if self.frames and self.original_delays:
# 计算平均延迟
avg_delay = sum(self.original_delays) // len(self.original_delays)
self.frame_delay = avg_delay
self.speed_scale.set(avg_delay)
self.update_status()
def play_gif(self):
"""开始播放GIF动画"""
if not self.is_playing and self.frames:
self.is_playing = True
self.update_status()
self.play_next_frame() # 开始播放循环
def pause_gif(self):
"""暂停播放GIF动画"""
self.is_playing = False
self.update_status()
def stop_gif(self):
"""停止播放GIF动画并回到第一帧"""
self.is_playing = False
if self.frames:
self.current_frame = 0
self.label.config(image=self.frames[self.current_frame])
self.update_status()
def prev_frame(self):
"""显示前一帧"""
if self.frames:
self.is_playing = False
self.current_frame = (self.current_frame - 1) % len(self.frames)
self.label.config(image=self.frames[self.current_frame])
self.update_status()
def next_frame(self):
"""显示后一帧"""
if self.frames:
self.is_playing = False
self.current_frame = (self.current_frame + 1) % len(self.frames)
self.label.config(image=self.frames[self.current_frame])
self.update_status()
def play_next_frame(self):
"""播放下一帧并设置定时器播放后续帧"""
if self.is_playing and self.frames:
# 显示当前帧
self.label.config(image=self.frames[self.current_frame])
# 更新到下一帧(循环播放)
self.current_frame = (self.current_frame + 1) % len(self.frames)
# 更新状态显示
self.update_status()
# 设置定时器,使用当前延迟时间播放下一帧
self.master.after(self.frame_delay, self.play_next_frame)
if __name__ == "__main__":
# 创建主窗口并启动GIF播放器
root = tk.Tk()
player = GifPlayer(root)
root.mainloop()