video-audio-extractor【源码版】

软件介绍

前几天在网上看见有人分享了一个源码,大概就是py调用的ffmpeg来制作的。

这一次我带来源码版(需要py环境才可以运行),开箱即用版本(直接即可运行)

今天分享的源码版

软件安装

先安装Python和vscode

python

vscode

vscode的安装

三、源码

源码

python 复制代码
import customtkinter as ctk
import subprocess
import os
import sys
import tkinter as tk
from tkinter import filedialog, messagebox
import threading
import webbrowser
import queue
from pathlib import Path

class VideoAudioExtractor(ctk.CTk):
    def __init__(self):
        super().__init__()
        self.title("视频音频提取工具@阿幸")
        self.geometry("700x750")  # 增加窗口宽度和高度
        self.configure(fg_color="#f0f0f0")
        self.resizable(True, True)

        ctk.set_appearance_mode("Light")
        ctk.set_default_color_theme("blue")

        self.current_files = []  # 存储多个文件
        self.save_directory = os.path.expanduser("~\\Desktop")
        self.use_gpu = tk.BooleanVar(value=False)
        self.keep_folder_structure = tk.BooleanVar(value=True)  # 新增:保持文件夹结构选项

        self.create_widgets()

        self.supported_formats = [
            "mp4", "mkv", "avi", "mov", "wmv",
            "flv", "webm", "mpeg", "mpg", "3gp"
        ]

        self.ffmpeg_path = self.find_ffmpeg()
        if not self.ffmpeg_path:
            messagebox.showerror("错误", "未找到FFmpeg可执行文件!请确保ffmpeg.exe在同一目录下。")
            self.extract_button.configure(state=tk.DISABLED)

        # 新增:用于批量处理的队列和计数器
        self.processing_queue = queue.Queue()
        self.total_files = 0
        self.processed_files = 0

    def find_ffmpeg(self):
        """查找当前目录下的FFmpeg可执行文件"""
        base_dir = os.path.dirname(sys.executable) if getattr(sys, 'frozen', False) else os.path.dirname(os.path.abspath(__file__))

        ffmpeg_exe = os.path.join(base_dir, "ffmpeg.exe")
        if os.path.exists(ffmpeg_exe):
            return ffmpeg_exe

        ffmpeg_exe = os.path.join(base_dir, "ffmpeg", "ffmpeg.exe")
        if os.path.exists(ffmpeg_exe):
            return ffmpeg_exe

        return None

    def create_widgets(self):
        """创建GUI界面组件"""
        # 创建主框架,使用pack布局
        main_frame = ctk.CTkFrame(self, fg_color="#f0f0f0")
        main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)

        # 标题标签
        title_frame = ctk.CTkFrame(main_frame, fg_color="#2196F3", corner_radius=5)
        title_frame.pack(fill=tk.X, pady=(0, 10))

        title_label = ctk.CTkLabel(title_frame, text="视频音频提取工具",
                                   font=("Microsoft YaHei", 20, "bold"),
                                   text_color="#ffffff")
        title_label.pack(pady=10)

        # 文件名显示 - 改为可滚动的文本框
        file_frame = ctk.CTkFrame(main_frame, fg_color="#e0e0e0")
        file_frame.pack(fill=tk.X, pady=(3, 3))

        # 修改 height 和 width 参数来调整文本框大小
        self.file_text = tk.Text(file_frame, wrap=tk.WORD, height=3, width=3,  # 调整这两个参数的值
                                 bg="#e0e0e0", fg="#555555", font=("Microsoft YaHei", 16, "bold"))
        self.file_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=3, pady=3)
        scrollbar = ctk.CTkScrollbar(file_frame, command=self.file_text.yview)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y, pady=2)
        self.file_text.configure(yscrollcommand=scrollbar.set)
        self.file_text.insert(tk.END, "未选择文件")
        self.file_text.config(state=tk.DISABLED)

        # 保存目录选择
        save_frame = ctk.CTkFrame(main_frame, fg_color="#e0e0e0")
        save_frame.pack(fill=tk.X, pady=(0, 10))

        save_label = ctk.CTkLabel(save_frame, text="保存位置:",
                                  font=("Microsoft YaHei", 12), text_color="#333333")
        save_label.pack(side=tk.LEFT, padx=10, pady=10)

        self.save_dir_label = ctk.CTkLabel(save_frame, text=self.save_directory,
                                           font=("Microsoft YaHei", 10), text_color="#555555",
                                           wraplength=400, justify=tk.LEFT)
        self.save_dir_label.pack(side=tk.LEFT, padx=5, pady=10)

        browse_button = ctk.CTkButton(save_frame, text="浏览",
                                      font=("Microsoft YaHei", 16, "bold"),  # 添加 "bold" 使字体加粗
                                      command=self.browse_save_directory,
                                      width=60, fg_color="#2196F3", hover_color="#1976D2")
        browse_button.pack(side=tk.RIGHT, padx=10, pady=10)

        # 新增:保持文件夹结构选项
        structure_frame = ctk.CTkFrame(main_frame, fg_color="#e0e0e0")
        structure_frame.pack(fill=tk.X, pady=(0, 10))

        self.structure_checkbox = ctk.CTkCheckBox(structure_frame,
                                                  text="保持文件夹结构(适用于批量处理)",
                                                  variable=self.keep_folder_structure,
                                                  font=("Microsoft YaHei", 16, "bold"), text_color="#333333")
        self.structure_checkbox.pack(side=tk.LEFT, padx=10, pady=10)

        # 输出格式选择
        format_frame = ctk.CTkFrame(main_frame, fg_color="#e0e0e0")
        format_frame.pack(fill=tk.X, pady=(0, 10))

        format_label = ctk.CTkLabel(format_frame, text="输出音频格式:",
                                    font=("Microsoft YaHei", 12), text_color="#333333")
        format_label.pack(side=tk.LEFT, padx=10, pady=10)

        self.format_var = ctk.StringVar(value="mp3")

        format_options_frame = ctk.CTkFrame(format_frame, fg_color="#e0e0e0")
        format_options_frame.pack(side=tk.LEFT, fill=tk.X, expand=True)

        self.mp3_radio = ctk.CTkRadioButton(format_options_frame, text="MP3 (通用格式)",
                                            variable=self.format_var, value="mp3",
                                            font=("Microsoft YaHei", 10), text_color="#333333")
        self.mp3_radio.pack(side=tk.LEFT, padx=5, pady=10)

        self.wav_radio = ctk.CTkRadioButton(format_options_frame, text="WAV (无损格式)",
                                            variable=self.format_var, value="wav",
                                            font=("Microsoft YaHei", 10), text_color="#333333")
        self.wav_radio.pack(side=tk.LEFT, padx=5, pady=10)

        self.aac_radio = ctk.CTkRadioButton(format_options_frame, text="AAC (高质量)",
                                            variable=self.format_var, value="aac",
                                            font=("Microsoft YaHei", 10), text_color="#333333")
        self.aac_radio.pack(side=tk.LEFT, padx=5, pady=10)

        # GPU加速选项
        gpu_frame = ctk.CTkFrame(main_frame, fg_color="#e0e0e0")
        gpu_frame.pack(fill=tk.X, pady=(0, 10))

        gpu_label = ctk.CTkLabel(gpu_frame, text="硬件加速:",
                                 font=("Microsoft YaHei", 12), text_color="#333333")
        gpu_label.pack(side=tk.LEFT, padx=10, pady=10)

        self.gpu_checkbox = ctk.CTkCheckBox(gpu_frame, text="启用GPU加速 (NVIDIA)",
                                            variable=self.use_gpu,
                                            font=("Microsoft YaHei", 10), text_color="#333333")
        self.gpu_checkbox.pack(side=tk.LEFT, padx=5, pady=10)

        # 按钮框架 - 重新设计布局
        button_frame = ctk.CTkFrame(main_frame, fg_color="#f0f0f0")
        button_frame.pack(fill=tk.X, pady=(0, 10))

        # 左侧两个按钮框架
        left_buttons_frame = ctk.CTkFrame(button_frame, fg_color="#f0f0f0")
        left_buttons_frame.pack(side=tk.LEFT, fill=tk.X, expand=True)

        self.select_files_button = ctk.CTkButton(left_buttons_frame, text="选择文件",
                                                 font=("Microsoft YaHei", 16, "bold"),
                                                 command=self.select_files,
                                                 fg_color="#2196F3",
                                                 hover_color="#1976D2",
                                                 width=150)
        self.select_files_button.pack(side=tk.LEFT, padx=(0, 5), pady=10)

        self.select_folder_button = ctk.CTkButton(left_buttons_frame, text="选择文件夹",
                                                 font=("Microsoft YaHei", 16, "bold"),
                                                 command=self.select_folder,
                                                 fg_color="#2196F3",
                                                 hover_color="#1976D2",
                                                 width=150)
        self.select_folder_button.pack(side=tk.LEFT, padx=(0, 5), pady=10)

        # 右侧两个按钮框架
        right_buttons_frame = ctk.CTkFrame(button_frame, fg_color="#f0f0f0")
        right_buttons_frame.pack(side=tk.RIGHT, fill=tk.X, expand=True)

        self.extract_button = ctk.CTkButton(right_buttons_frame, text="提取音频",
                                            font=("Microsoft YaHei", 16, "bold"),
                                            command=self.extract_audio,
                                            state=tk.DISABLED,
                                            fg_color="#2196F3",
                                            hover_color="#1976D2",
                                            text_color="white",
                                            width=150)
        self.extract_button.pack(side=tk.RIGHT, padx=(0, 5), pady=10)

        self.promo_button = ctk.CTkButton(right_buttons_frame, text="关于阿幸",
                                          font=("Microsoft YaHei", 16, "bold"),
                                          command=lambda: webbrowser.open("https://a-xing.top/"),
                                          fg_color="#2196F3",
                                          hover_color="#1976D2",
                                          text_color="white",
                                          width=150)
        self.promo_button.pack(side=tk.RIGHT, padx=(0, 5), pady=10)

        # 进度条显示
        progress_frame = ctk.CTkFrame(main_frame, fg_color="#f0f0f0")
        progress_frame.pack(fill=tk.X, pady=(0, 10))

        progress_info_frame = ctk.CTkFrame(progress_frame, fg_color="#f0f0f0")
        progress_info_frame.pack(fill=tk.X)

        progress_text = ctk.CTkLabel(progress_info_frame, text="处理进度:",
                                     font=("Microsoft YaHei", 12), text_color="#333333")
        progress_text.pack(side=tk.LEFT, padx=(0, 10), pady=5)

        self.progress_label = ctk.CTkLabel(progress_info_frame, text="0/0",
                                           font=("Microsoft YaHei", 12), text_color="#555555")
        self.progress_label.pack(side=tk.LEFT, pady=5)

        self.progress_var = tk.DoubleVar()
        self.progress_bar = ctk.CTkProgressBar(progress_frame, variable=self.progress_var,
                                               width=600, height=10, corner_radius=5,
                                               progress_color="#2196F3")
        self.progress_bar.pack(fill=tk.X, pady=(0, 5))
        self.progress_bar.set(0)

        # 状态标签
        self.status_label = ctk.CTkLabel(main_frame, text="等待转换",
                                         font=("Microsoft YaHei", 16, "bold"), text_color="#555555")
        self.status_label.pack(pady=(0, 5))  # 减小与底部信息的间距

        # 底部信息(移到状态标签下方)
        footer_label = ctk.CTkLabel(main_frame, text="支持格式: MP4, MKV, AVI, MOV, WMV, FLV, WEBM, MPEG, MPG, 3GP",
                                    font=("Microsoft YaHei", 16, "bold"), text_color="#777777")
        footer_label.pack(fill=tk.X, pady=(0, 10))  # 调整间距

    def browse_save_directory(self):
        """浏览并选择保存目录"""
        directory = filedialog.askdirectory(initialdir=self.save_directory)
        if directory:
            self.save_directory = directory
            self.save_dir_label.configure(text=directory)

    def select_files(self):
        """选择多个文件"""
        file_paths = filedialog.askopenfilenames(
            title="选择视频文件",
            filetypes=[("视频文件", "*.mp4;*.mkv;*.avi;*.mov;*.wmv;*.flv;*.webm;*.mpeg;*.mpg;*.3gp")]
        )

        if file_paths:
            self.process_files(file_paths)

    def select_folder(self):
        """选择文件夹"""
        folder_path = filedialog.askdirectory(title="选择视频文件夹")

        if folder_path:
            # 获取文件夹中所有支持的视频文件
            video_files = []
            for root, _, files in os.walk(folder_path):
                for file in files:
                    ext = os.path.splitext(file)[1][1:].lower()
                    if ext in self.supported_formats:
                        video_files.append(os.path.join(root, file))

            if video_files:
                self.process_files(video_files)
            else:
                messagebox.showinfo("提示", "所选文件夹中未找到支持的视频文件")

    def process_files(self, file_paths):
        """处理选择的多个文件"""
        valid_files = []
        for file_path in file_paths:
            file_ext = os.path.splitext(file_path)[1][1:].lower()
            if file_ext in self.supported_formats:
                valid_files.append(file_path)
            else:
                print(f"忽略不支持的文件: {file_path}")

        if not valid_files:
            messagebox.showerror("错误", "未选择有效的视频文件")
            return

        self.current_files = valid_files

        # 更新文件列表显示
        self.file_text.config(state=tk.NORMAL)
        self.file_text.delete(1.0, tk.END)
        if len(valid_files) == 1:
            self.file_text.insert(tk.END, f"已选择: {os.path.basename(valid_files[0])}")
        else:
            self.file_text.insert(tk.END, f"已选择 {len(valid_files)} 个文件\n")
            for file in valid_files[:5]:  # 只显示前5个文件
                self.file_text.insert(tk.END, f"- {os.path.basename(file)}\n")
            if len(valid_files) > 5:
                self.file_text.insert(tk.END, f"... 等 {len(valid_files)} 个文件")
        self.file_text.config(state=tk.DISABLED)

        self.extract_button.configure(state=tk.NORMAL)
        self.status_label.configure(text="就绪")
        self.progress_bar.set(0)
        self.progress_label.configure(text="0/0")

    def extract_audio(self):
        """提取音频"""
        if not self.current_files or not self.ffmpeg_path:
            return

        self.extract_button.configure(state=tk.DISABLED)
        self.promo_button.configure(state=tk.DISABLED)

        # 初始化处理队列
        self.processing_queue.queue.clear()
        self.total_files = len(self.current_files)
        self.processed_files = 0
        self.progress_var.set(0)
        self.progress_label.configure(text=f"0/{self.total_files}")

        # 将所有文件添加到处理队列
        for file in self.current_files:
            self.processing_queue.put(file)

        # 启动处理线程
        threading.Thread(target=self.process_queue, daemon=True).start()

    def process_queue(self):
        """处理队列中的文件"""
        output_format = self.format_var.get()

        while not self.processing_queue.empty():
            file_path = self.processing_queue.get()

            # 构建输出文件路径
            if self.total_files == 1 or not self.keep_folder_structure.get():
                # 单个文件或不保持文件夹结构时,直接输出到保存目录
                base_name = os.path.basename(os.path.splitext(file_path)[0])
                output_file = os.path.join(self.save_directory, f"{base_name}.{output_format}")
            else:
                # 批量处理且保持文件夹结构
                relative_path = os.path.relpath(os.path.dirname(file_path),
                                                os.path.dirname(self.current_files[0]))
                target_dir = os.path.join(self.save_directory, relative_path)
                os.makedirs(target_dir, exist_ok=True)

                base_name = os.path.basename(os.path.splitext(file_path)[0])
                output_file = os.path.join(target_dir, f"{base_name}.{output_format}")

            # 更新状态
            self.status_label.configure(text=f"正在处理: {os.path.basename(file_path)}")

            # 执行提取
            success = self.run_ffmpeg(file_path, output_file, output_format)

            # 更新进度
            self.processed_files += 1
            progress = self.processed_files / self.total_files
            self.progress_var.set(progress)
            self.progress_label.configure(text=f"{self.processed_files}/{self.total_files}")

            # 处理完成的回调
            if success:
                self.after(0, lambda f=os.path.basename(output_file):
                           self.status_label.configure(text=f"已完成: {f}"))

        # 全部处理完成
        self.after(0, self.processing_complete)

    def run_ffmpeg(self, input_file, output_file, output_format):
        """运行FFmpeg命令提取音频"""
        try:
            # 设置FFmpeg参数
            cmd = [self.ffmpeg_path]

            # 添加GPU加速选项
            if self.use_gpu.get():
                cmd.extend(["-hwaccel", "cuda", "-hwaccel_output_format", "cuda"])

            cmd.extend(["-i", input_file, "-vn"])

            # 设置音频编码参数
            if output_format == "mp3":
                cmd.extend(["-acodec", "libmp3lame", "-b:a", "192k"])
            elif output_format == "wav":
                cmd.extend(["-acodec", "pcm_s16le"])
            else:  # aac
                cmd.extend(["-acodec", "aac", "-b:a", "128k", "-strict", "experimental"])

            # 覆盖已存在文件
            cmd.extend(["-y", output_file])

            # 使用二进制模式读取输出并处理编码
            process = subprocess.Popen(
                cmd,
                stdout=subprocess.PIPE,
                stderr=subprocess.STDOUT,
                bufsize=4090
            )

            output = []
            for line_bytes in process.stdout:
                try:
                    line = line_bytes.decode('utf-8').strip()
                except UnicodeDecodeError:
                    try:
                        line = line_bytes.decode('gbk').strip()
                    except UnicodeDecodeError:
                        line = line_bytes.decode('utf-8', errors='replace').strip()
                output.append(line)

            process.wait()

            if process.returncode == 0:
                return True
            else:
                error_msg = "\n".join(output) if output else f"未知错误,错误码: {process.returncode}"
                print(f"处理失败: {input_file}\n{error_msg}")
                return False

        except Exception as e:
            print(f"处理文件 {input_file} 时发生异常: {str(e)}")
            return False

    def processing_complete(self):
        """所有文件处理完成后的回调"""
        self.status_label.configure(text=f"全部处理完成!共处理 {self.total_files} 个文件")

        # 创建自定义弹窗
        popup = ctk.CTkToplevel(self)  # 使用 customtkinter 的顶层窗口
        popup.title("阿幸提示:处理完成")
        popup.geometry("400x200")  # 设置弹窗大小
        popup.resizable(True, True)  # 允许调整大小
        popup.configure(fg_color="#f0f0f0")  # 设置弹窗背景颜色,与主界面一致

        # 创建标签显示信息
        label = ctk.CTkLabel(popup, text=f"已成功处理 {self.total_files} 个文件", 
                             font=("Microsoft YaHei", 20, "bold"), 
                             text_color="#333333",
                             bg_color="transparent")
        label.pack(pady=20)

        # 创建按钮框架
        button_frame = ctk.CTkFrame(popup, fg_color="transparent")
        button_frame.pack(fill=tk.X, padx=20, pady=10)

        # 创建访问按钮
        def open_website():
            webbrowser.open("https://a-xing.top/")

        visit_button = ctk.CTkButton(button_frame, text="访问阿幸主页", 
                                    command=open_website, 
                                    font=("Microsoft YaHei", 16, "bold"),
                                    fg_color="#2196F3",
                                    hover_color="#1976D2",
                                    width=150)
        visit_button.pack(side=tk.LEFT, padx=(0, 10), pady=10)

        # 创建关闭按钮
        close_button = ctk.CTkButton(button_frame, text="关闭", 
                                    command=popup.destroy, 
                                    font=("Microsoft YaHei", 16, "bold"),
                                    fg_color="#2196F3",
                                    hover_color="#1976D2",
                                    width=150)
        close_button.pack(side=tk.RIGHT, padx=(10, 0), pady=10)

        self.extract_button.configure(state=tk.NORMAL if self.current_files else tk.DISABLED)
        self.promo_button.configure(state=tk.NORMAL)


if __name__ == "__main__":
    app = VideoAudioExtractor()
    app.mainloop()    

运行界面

源码下载

夸克网盘:

https://pan.quark.cn/s/2d4d8e8505ee

相关推荐
橙露2 分钟前
Python 对接 API:自动化拉取、清洗、入库一站式教程
开发语言·python·自动化
Omigeq8 分钟前
1.4 - 曲线生成轨迹优化算法(以BSpline和ReedsShepp为例) - Python运动规划库教程(Python Motion Planning)
开发语言·人工智能·python·算法·机器人
2301_8084143810 分钟前
自动化测试的实施
开发语言·python
无限码力14 分钟前
华为OD技术面真题 - Python开发 - 4
python·华为od·华为od技术面真题·华为od面试八股文·华为od面试真题·华为odpython开发真题·华为od技术面题目
l1t1 小时前
用wsl自带的python 3.10下载适用于3.12的pandas版本结合uv安装python 3.12模拟离线安装场景
python·pandas·uv
飞Link1 小时前
【AI大模型实战】万字长文肝透大语言模型(LLM):从底层原理解析到企业级Python项目落地
开发语言·人工智能·python·语言模型·自然语言处理
翻斗包菜1 小时前
第 03 章 Python 操作 MySQL 数据库实战全解
数据库·python·mysql
xcjbqd01 小时前
如何修改Oracle服务器默认的日期格式_NLS_DATE_FORMAT全局配置
jvm·数据库·python
white-persist2 小时前
【vulhub spring CVE-2018-1270】CVE-2018-1270 Spring Messaging 远程命令执行漏洞 完整复现详细分析解释
java·服务器·网络·数据库·后端·python·spring
EnCi Zheng2 小时前
P2G-Python字符串方法完全指南-split、join、strip、replace的Python编程利器
开发语言·python