使用Python实现播放“.gif”文件增强版

使用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()
相关推荐
感哥3 小时前
Django Model高级特性
python·django
李辉20034 小时前
Python简介及Pycharm
开发语言·python·pycharm
赵谨言4 小时前
基于python大数据的城市扬尘数宇化监控系统的设计与开发
大数据·开发语言·经验分享·python
Yurko134 小时前
【C语言】程序控制结构
c语言·开发语言·学习
云和数据.ChenGuang4 小时前
parser_error UnicodeDecodeError: ‘utf-8‘ codec can‘t decode bytes
python
zhangfeng11334 小时前
R和python 哪个更适合生物信息分析,或者更擅长做什么工作
开发语言·python·r语言·生物信息
听情歌落俗4 小时前
c++通讯录管理系统
开发语言·c++·算法
Peace & Love4874 小时前
C++初阶 -- 模拟实现list
开发语言·c++·笔记
liliangcsdn4 小时前
如何结合langchain、neo4j实现关联检索问答
开发语言·python·langchain·neo4j