python语言视频格式转换工具程序代码ZXQZQ

import subprocess
import os
import tkinter as tk
from tkinter import filedialog, messagebox, ttk
import threading
import re


class VideoConverter:
    def __init__(self, ffmpeg_path):
        self.ffmpeg_path = ffmpeg_path

    def convert_video(self, input_file, output_file, codec="libx264", quality="medium", progress_callback=None):
        """转换单个视频文件"""
        try:
            # 构建FFmpeg命令(根据输出格式自动调整音频编码,确保兼容性)
            cmd = [
                self.ffmpeg_path,
                "-i", input_file,
                "-c:v", codec,
                "-preset", quality,
                "-crf", "23",  # 视频质量控制,0-51,值越小质量越高
                "-c:a", "aac",  # 音频编码统一使用aac,适配多数格式
                "-y",  # 覆盖已存在的文件
                output_file
            ]

            # 执行命令并捕获输出
            process = subprocess.Popen(
                cmd,
                stdout=subprocess.PIPE,
                stderr=subprocess.STDOUT,
                universal_newlines=True,
                bufsize=1
            )

            # 解析FFmpeg输出获取真实进度
            total_duration = 0
            current_time = 0

            for line in process.stdout:
                # 获取视频总时长
                duration_match = re.search(r"Duration: (\d+:\d+:\d+\.\d+)", line)
                if duration_match and total_duration == 0:
                    duration_str = duration_match.group(1)
                    h, m, s = map(float, duration_str.split(':'))
                    total_duration = h * 3600 + m * 60 + s

                # 获取当前处理时间
                time_match = re.search(r"time=(\d+:\d+:\d+\.\d+)", line)
                if time_match and total_duration > 0:
                    time_str = time_match.group(1)
                    h, m, s = map(float, time_str.split(':'))
                    current_time = h * 3600 + m * 60 + s

                    # 计算进度百分比
                    progress = int((current_time / total_duration) * 100)
                    progress = min(progress, 100)  # 避免超过100%

                    # 更新进度
                    if progress_callback:
                        progress_callback(progress)

            # 等待命令执行完成
            process.wait()

            if process.returncode != 0:
                raise Exception(f"FFmpeg执行失败,返回代码: {process.returncode}")

            # 验证输出文件有效性
            if not os.path.exists(output_file) or os.path.getsize(output_file) == 0:
                raise Exception("转换后的文件为空或不存在")

            return True

        except Exception as e:
            print(f"转换失败: {str(e)}")
            return False

    def batch_convert(self, input_files, output_dir, naming_rule, output_format, codec="libx264", quality="medium",
                      progress_callback=None):
        """批量转换视频文件"""
        results = []
        total_files = len(input_files)

        for i, input_file in enumerate(input_files):
            # 生成输出文件名
            output_file = self.generate_output_filename(
                input_file,
                output_dir,
                naming_rule,
                output_format
            )

            # 转换单个文件(绑定进度回调)
            success = self.convert_video(
                input_file,
                output_file,
                codec=codec,
                quality=quality,
                progress_callback=lambda p: progress_callback(i, total_files, p) if progress_callback else None
            )

            results.append((input_file, output_file, success))

        return results

    def generate_output_filename(self, input_file, output_dir, naming_rule, output_format):
        """根据命名规则生成输出文件名"""
        base_name = os.path.basename(input_file)
        file_name, _ = os.path.splitext(base_name)

        # 解析命名规则
        rule_type = naming_rule["type"]

        if rule_type == "original":
            output_name = f"{file_name}.{output_format}"
        elif rule_type == "prefix":
            prefix = naming_rule.get("prefix", "")
            output_name = f"{prefix}{file_name}.{output_format}"
        elif rule_type == "suffix":
            suffix = naming_rule.get("suffix", "")
            output_name = f"{file_name}{suffix}.{output_format}"
        elif rule_type == "custom":
            custom_format = naming_rule.get("format", "{original}")
            output_name = custom_format.replace("{original}", file_name) + f".{output_format}"
        else:
            output_name = f"{file_name}.{output_format}"

        # 确保文件名唯一(若已存在则添加编号)
        output_path = os.path.join(output_dir, output_name)
        counter = 1
        while os.path.exists(output_path):
            base, ext = os.path.splitext(output_name)
            output_name = f"{base}_{counter}{ext}"
            output_path = os.path.join(output_dir, output_name)
            counter += 1

        return output_path


class VideoConverterGUI:
    def __init__(self, root):
        self.root = root
        self.root.title("视频格式转换工具")
        self.root.geometry("750x620")  # 适当增大窗口,适配新增内容
        self.root.resizable(True, True)

        self.ffmpeg_path = ""
        self.converter = None
        self.input_files = []
        self.output_dir = ""
        self.output_format = "mp4"
        self.codec = "libx264"
        self.quality = "medium"
        self.naming_rule = {"type": "original"}

        self.create_widgets()

    def create_widgets(self):
        """创建GUI界面组件"""
        main_frame = ttk.Frame(self.root, padding="10")
        main_frame.pack(fill=tk.BOTH, expand=True)

        # 1. FFmpeg路径设置区域
        ffmpeg_frame = ttk.LabelFrame(main_frame, text="FFmpeg设置", padding="10")
        ffmpeg_frame.pack(fill=tk.X, pady=5)

        ttk.Button(ffmpeg_frame, text="选择ffmpeg.exe", command=self.select_ffmpeg).pack(side=tk.LEFT, padx=5)
        self.ffmpeg_path_var = tk.StringVar()
        self.ffmpeg_path_var.set("未选择ffmpeg.exe文件")
        ttk.Label(ffmpeg_frame, textvariable=self.ffmpeg_path_var).pack(side=tk.LEFT, padx=5, fill=tk.X, expand=True)

        # 2. 文件选择区域
        file_frame = ttk.LabelFrame(main_frame, text="文件选择", padding="10")
        file_frame.pack(fill=tk.X, pady=5)

        ttk.Button(file_frame, text="添加文件", command=self.select_files).pack(side=tk.LEFT, padx=5)
        ttk.Button(file_frame, text="添加文件夹", command=self.select_directory).pack(side=tk.LEFT, padx=5)
        ttk.Button(file_frame, text="清空列表", command=self.clear_files).pack(side=tk.LEFT, padx=5)

        # 3. 文件列表区域
        list_frame = ttk.LabelFrame(main_frame, text="文件列表", padding="10")
        list_frame.pack(fill=tk.BOTH, expand=True, pady=5)

        self.file_listbox = tk.Listbox(list_frame, height=8, selectmode=tk.EXTENDED)
        self.file_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)

        scrollbar = ttk.Scrollbar(list_frame, orient=tk.VERTICAL, command=self.file_listbox.yview)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        self.file_listbox.config(yscrollcommand=scrollbar.set)

        # 4. 输出设置区域
        output_frame = ttk.LabelFrame(main_frame, text="输出设置", padding="10")
        output_frame.pack(fill=tk.X, pady=5)

        ttk.Button(output_frame, text="选择输出文件夹", command=self.select_output_dir).pack(side=tk.LEFT, padx=5)
        self.output_dir_var = tk.StringVar()
        ttk.Label(output_frame, textvariable=self.output_dir_var).pack(side=tk.LEFT, padx=5)

        # 5. 转换设置区域(新增格式和编码适配)
        settings_frame = ttk.LabelFrame(main_frame, text="转换设置", padding="10")
        settings_frame.pack(fill=tk.X, pady=5)

        # 5.1 输出格式(新增flv、webm、3gp、m4v)
        ttk.Label(settings_frame, text="输出格式:").grid(row=0, column=0, padx=5, pady=5, sticky=tk.W)
        # 原有5种 + 新增4种,共9种格式
        format_values = ["mp4", "avi", "mkv", "mov", "wmv", "flv", "webm", "3gp", "m4v"]
        self.format_combo = ttk.Combobox(settings_frame, values=format_values, width=10)
        self.format_combo.set(self.output_format)
        self.format_combo.grid(row=0, column=1, padx=5, pady=5)
        # 绑定格式选择事件,自动推荐匹配的编码器
        self.format_combo.bind("<>", self.on_format_changed)

        # 5.2 编码格式(根据格式动态适配)
        ttk.Label(settings_frame, text="编码格式:").grid(row=0, column=2, padx=5, pady=5, sticky=tk.W)
        # 扩展编码器列表,适配新增格式
        codec_values = [
            "libx264",    # 适用于mp4、mkv、flv等
            "libx265",    # 高效压缩,适用于mp4、mkv
            "mpeg4",      # 适用于avi、mp4、3gp
            "libvpx-vp9", # 适用于webm
            "libvpx",     # 适用于webm(vp8)
            "flv",        # 适用于flv格式
            "h263",       # 适用于3gp
            "mpeg2video"  # 通用编码,兼容性好
        ]
        self.codec_combo = ttk.Combobox(settings_frame, values=codec_values, width=12)
        self.codec_combo.set(self.codec)
        self.codec_combo.grid(row=0, column=3, padx=5, pady=5)

        # 5.3 质量预设
        ttk.Label(settings_frame, text="质量预设:").grid(row=0, column=4, padx=5, pady=5, sticky=tk.W)
        quality_values = ["ultrafast", "superfast", "veryfast", "faster", "fast", "medium", "slow", "slower", "veryslow"]
        self.quality_combo = ttk.Combobox(settings_frame, values=quality_values, width=10)
        self.quality_combo.set(self.quality)
        self.quality_combo.grid(row=0, column=5, padx=5, pady=5)

        # 5.4 文件名规则设置
        ttk.Label(settings_frame, text="文件名规则:").grid(row=1, column=0, padx=5, pady=5, sticky=tk.W)
        naming_values = ["保持原文件名", "添加前缀", "添加后缀", "自定义格式"]
        self.naming_combo = ttk.Combobox(settings_frame, values=naming_values, width=15)
        self.naming_combo.set(naming_values[0])
        self.naming_combo.grid(row=1, column=1, padx=5, pady=5)
        self.naming_combo.bind("<>", self.on_naming_rule_changed)

        self.naming_entry = ttk.Entry(settings_frame, width=30)
        self.naming_entry.grid(row=1, column=2, columnspan=3, padx=5, pady=5, sticky=tk.W)
        self.naming_entry.insert(0, "输入前缀/后缀/自定义格式")
        self.naming_entry.config(state=tk.DISABLED)

        ttk.Label(settings_frame, text="(支持{original}占位符)").grid(row=1, column=5, padx=5, pady=5, sticky=tk.W)

        # 6. 进度条
        self.progress_var = tk.DoubleVar()
        self.progress_bar = ttk.Progressbar(main_frame, variable=self.progress_var, maximum=100)
        self.progress_bar.pack(fill=tk.X, pady=5)

        # 7. 状态标签
        self.status_var = tk.StringVar()
        self.status_var.set("就绪")
        ttk.Label(main_frame, textvariable=self.status_var).pack(anchor=tk.W, pady=5)

        # 8. 转换按钮
        ttk.Button(main_frame, text="开始转换", command=self.start_conversion).pack(pady=10)

    def on_format_changed(self, event):
        """当输出格式改变时,自动推荐匹配的编码器"""
        selected_format = self.format_combo.get()
        # 格式与编码器的推荐匹配关系
        format_codec_map = {
            "mp4": "libx264",
            "avi": "mpeg4",
            "mkv": "libx264",
            "mov": "libx264",
            "wmv": "mpeg4",
            "flv": "flv",       # flv格式推荐flv编码器
            "webm": "libvpx-vp9",  # webm推荐vp9编码
            "3gp": "h263",      # 3gp推荐h263编码
            "m4v": "libx264"    # m4v与mp4兼容,推荐x264
        }
        # 自动选择推荐的编码器
        if selected_format in format_codec_map:
            self.codec_combo.set(format_codec_map[selected_format])

    def select_ffmpeg(self):
        """选择ffmpeg.exe文件"""
        ffmpeg_file = filedialog.askopenfilename(
            title="选择ffmpeg.exe",
            filetypes=[("可执行文件", "ffmpeg.exe")]
        )
        if ffmpeg_file:
            self.ffmpeg_path = ffmpeg_file
            self.ffmpeg_path_var.set(ffmpeg_file)
            self.converter = VideoConverter(ffmpeg_file)

    def select_files(self):
        """选择多个视频文件(包含新增格式)"""
        files = filedialog.askopenfilenames(
            title="选择视频文件",
            # 支持的文件类型扩展为9种
            filetypes=[("视频文件", "*.mp4 *.avi *.mkv *.mov *.wmv *.flv *.webm *.3gp *.m4v")]
        )
        if files:
            self.input_files.extend(files)
            self.update_file_listbox()

    def select_directory(self):
        """选择包含视频文件的文件夹(支持新增格式)"""
        directory = filedialog.askdirectory(title="选择文件夹")
        if directory:
            # 支持的扩展名列表(包含新增的4种)
            video_extensions = [".mp4", ".avi", ".mkv", ".mov", ".wmv", ".flv", ".webm", ".3gp", ".m4v"]
            files = [
                os.path.join(directory, f)
                for f in os.listdir(directory)
                if os.path.isfile(os.path.join(directory, f))
                and os.path.splitext(f)[1].lower() in video_extensions
            ]
            if files:
                self.input_files.extend(files)
                self.update_file_listbox()
            else:
                messagebox.showinfo("提示", "所选文件夹中没有找到支持的视频文件")

    def clear_files(self):
        """清空文件列表"""
        self.input_files = []
        self.update_file_listbox()

    def update_file_listbox(self):
        """更新文件列表显示"""
        self.file_listbox.delete(0, tk.END)
        for file_path in self.input_files:
            self.file_listbox.insert(tk.END, os.path.basename(file_path))

    def select_output_dir(self):
        """选择输出文件夹"""
        directory = filedialog.askdirectory(title="选择输出文件夹")
        if directory:
            self.output_dir = directory
            self.output_dir_var.set(directory)

    def on_naming_rule_changed(self, event):
        """当命名规则选择变化时更新输入框状态"""
        selection = self.naming_combo.get()
        if selection == "保持原文件名":
            self.naming_entry.config(state=tk.DISABLED)
            self.naming_entry.delete(0, tk.END)
            self.naming_entry.insert(0, "输入前缀/后缀/自定义格式")
        else:
            self.naming_entry.config(state=tk.NORMAL)
            if selection == "添加前缀" and self.naming_entry.get() == "输入前缀/后缀/自定义格式":
                self.naming_entry.delete(0, tk.END)
                self.naming_entry.insert(0, "new_")
            elif selection == "添加后缀" and self.naming_entry.get() == "输入前缀/后缀/自定义格式":
                self.naming_entry.delete(0, tk.END)
                self.naming_entry.insert(0, "_converted")
            elif selection == "自定义格式" and self.naming_entry.get() == "输入前缀/后缀/自定义格式":
                self.naming_entry.delete(0, tk.END)
                self.naming_entry.insert(0, "{original}_new")

    def update_progress(self, current_file, total_files, progress):
        """更新进度条和状态文本"""
        overall_progress = ((current_file / total_files) + (progress / 100 / total_files)) * 100
        self.progress_var.set(overall_progress)
        if progress < 100:
            self.status_var.set(f"正在转换文件 {current_file + 1}/{total_files}: {progress}%")
        else:
            self.status_var.set(f"文件 {current_file + 1}/{total_files} 转换完成")

    def conversion_complete(self, results):
        """转换完成后的处理"""
        self.progress_var.set(100)
        success_count = sum(1 for _, _, success in results if success)
        failed_count = len(results) - success_count
        message = f"转换完成!\n成功: {success_count}\n失败: {failed_count}"
        if failed_count > 0:
            failed_files = [os.path.basename(f) for f, _, s in results if not s]
            message += f"\n\n失败的文件:\n{', '.join(failed_files)}"
        messagebox.showinfo("转换结果", message)
        self.status_var.set("就绪")

    def start_conversion(self):
        """开始转换过程(增加格式与编码兼容性检查)"""
        # 检查FFmpeg是否已设置
        if not self.ffmpeg_path or not self.converter:
            messagebox.showwarning("警告", "请先选择有效的ffmpeg.exe文件")
            return
        # 检查输入文件
        if not self.input_files:
            messagebox.showwarning("警告", "请先选择要转换的视频文件")
            return
        # 检查输出目录
        if not self.output_dir:
            messagebox.showwarning("警告", "请先选择输出文件夹")
            return

        # 获取用户设置
        self.output_format = self.format_combo.get()
        self.codec = self.codec_combo.get()
        self.quality = self.quality_combo.get()

        # 获取命名规则
        naming_selection = self.naming_combo.get()
        naming_text = self.naming_entry.get()
        if naming_selection == "保持原文件名":
            self.naming_rule = {"type": "original"}
        elif naming_selection == "添加前缀":
            self.naming_rule = {"type": "prefix", "prefix": naming_text}
        elif naming_selection == "添加后缀":
            self.naming_rule = {"type": "suffix", "suffix": naming_text}
        elif naming_selection == "自定义格式":
            self.naming_rule = {"type": "custom", "format": naming_text}

        # 在新线程中执行转换,避免UI卡顿
        thread = threading.Thread(
            target=self.perform_conversion,
            daemon=True
        )
        thread.start()

    def perform_conversion(self):
        """执行实际的转换过程"""
        self.status_var.set("开始转换...")
        results = self.converter.batch_convert(
            self.input_files,
            self.output_dir,
            self.naming_rule,
            self.output_format,
            codec=self.codec,
            quality=self.quality,
            progress_callback=self.update_progress
        )
        # 在主线程中更新UI
        self.root.after(0, lambda: self.conversion_complete(results))


def main():
    root = tk.Tk()
    app = VideoConverterGUI(root)
    root.mainloop()


if __name__ == "__main__":
    main()

你可能感兴趣的:(python,开发语言)