当前位置: 代码网 > it编程>前端脚本>Python > Python批量实现横屏转竖屏的视频处理工具

Python批量实现横屏转竖屏的视频处理工具

2025年02月26日 Python 我要评论
1. 简介这是一款基于python和tkinter框架开发的视频处理器应用。该应用集成了ffmpeg,用于批量横屏转竖屏视频处理,支持多种视频格式和编码选择,并提供多线程支持以提升处理效率。用户可以通

1. 简介

这是一款基于python和tkinter框架开发的视频处理器应用。该应用集成了ffmpeg,用于批量横屏转竖屏视频处理,支持多种视频格式和编码选择,并提供多线程支持以提升处理效率。用户可以通过简洁直观的图形界面导入、删除视频文件,并且对每个文件设置处理参数,如输出格式、视频编码器、音频编码器及高斯模糊效果。应用还支持暂停/继续和多线程并发处理,确保在长时间处理时能保持灵活性和高效性。

2.功能说明

2.1 文件管理

  • 支持通过拖放或文件选择对话框导入视频文件。
  • 支持删除已导入的文件。
  • 显示导入文件的基本信息,包括文件名和处理状态。

2.2 ffmpeg配置

  • 用户可以选择视频输出格式(mp4、avi、mov等)。
  • 提供不同的视频和音频编码器选项(如libx264、aac)。
  • 支持设置处理线程数,允许用户根据系统性能调整并发处理数量。
  • 可选应用高斯模糊效果以实现视频特效。

2.3 多线程处理

  • 使用threadpoolexecutor实现多线程并发处理多个视频文件。
  • 支持暂停和继续处理,用户可以在处理过程中暂停视频文件的转换,稍后继续。

2.4 输出文件管理

  • 用户可以设置输出文件夹,处理完成的视频会保存至该目录。
  • 支持在处理完成后直接打开输出文件夹。

2.5 进度监控与日志

  • 在文件列表中显示每个视频的处理进度。
  • 提供日志框,实时显示处理过程中的信息。

2.6 拖放支持

支持通过拖拽文件到窗口的方式导入视频文件,提升用户体验。

2.7 错误处理与反馈

针对文件已存在、格式不支持等情况提供相应的错误提示。

3. 运行效果

视频处理转换前(横屏状态):

视频处理转换后(竖屏状态):

4. 相关源码

import os
import ttkbootstrap as ttk
from ttkbootstrap.constants import *
from tkinter import filedialog, messagebox, end, text, stringvar, intvar, booleanvar, menu
from concurrent.futures import threadpoolexecutor, as_completed
import subprocess
import threading
import psutil
import re
import sys
 
from tkinterdnd2 import tkinterdnd, dnd_files
 
def resource_path(relative_path):
    """ get absolute path to resource, works for dev and for pyinstaller """
    try:
        # pyinstaller creates a temp folder and stores path in _meipass
        base_path = sys._meipass
    except exception:
        base_path = os.path.abspath(".")
 
    return os.path.join(base_path, relative_path)
 
class videoprocessor:
    def __init__(self, master):
        self.master = master
        self.master.title("视频处理器  吾爱作者:是谁的大海(是貔貅呀) 版本:1.3")
         
        self.input_files = []
        self.output_folder = ""
        self.process_thread = none
        self.pause_event = threading.event()
        self.pause_event.set()  # start in the unpaused state
        self.ffmpeg_processes = []  # list to keep track of all ffmpeg processes
        self.is_closing = false
         
        self.output_format = stringvar(value="mp4")
        self.video_codec = stringvar(value="libx264")
        self.audio_codec = stringvar(value="aac")
        self.thread_count = intvar(value=1)  # default to 1 threads
        self.apply_blur = booleanvar(value=false)  # boolean to check if blur should be applied
         
        self.create_widgets()
        self.create_menu()
        self.master.protocol("wm_delete_window", self.on_closing)
         
    def create_widgets(self):
        frame = ttk.frame(self.master, padding=10)
        frame.pack(fill=both, expand=yes)
 
        self.file_list_frame = ttk.frame(frame)
        self.file_list_frame.pack(fill=both, expand=yes, pady=5)
 
        columns = ('序号', '文件夹名字', '进度')
        self.file_tree = ttk.treeview(self.file_list_frame, columns=columns, show='headings')
        self.file_tree.heading('序号', text='序号')
        self.file_tree.heading('文件夹名字', text='文件夹名字')
        self.file_tree.heading('进度', text='进度')
         
        self.file_tree.column('序号', width=100, anchor='center')
        self.file_tree.column('文件夹名字', width=400, anchor='w')
        self.file_tree.column('进度', width=100, anchor='center')
         
        self.file_tree.pack(side=left, fill=both, expand=yes)
         
        scrollbar = ttk.scrollbar(self.file_list_frame, orient="vertical", command=self.file_tree.yview)
        self.file_tree.configure(yscrollcommand=scrollbar.set)
        scrollbar.pack(side=right, fill=y)
 
        self.log_text = text(frame, height=5, state='disabled')
        self.log_text.pack(fill=both, expand=yes, pady=5)
 
        button_frame = ttk.frame(frame)
        button_frame.pack(fill=both, expand=yes)
 
        self.add_files_button = ttk.button(button_frame, text="导入文件", command=self.add_files, bootstyle=primary)
        self.add_files_button.pack(side=left, padx=5, pady=5)
         
        self.remove_files_button = ttk.button(button_frame, text="删除文件", command=self.remove_files, bootstyle=danger)
        self.remove_files_button.pack(side=left, padx=5, pady=5)
         
        self.pause_button = ttk.button(button_frame, text="暂停/继续", command=self.toggle_pause, bootstyle=warning)
        self.pause_button.pack(side=left, padx=5, pady=5)
         
        self.open_output_folder_button = ttk.button(button_frame, text="打开文件", command=self.open_output_folder, bootstyle=success)
        self.open_output_folder_button.pack(side=left, padx=5, pady=5)
         
        self.set_output_folder_button = ttk.button(button_frame, text="导出文件", command=self.set_output_folder, bootstyle=success)
        self.set_output_folder_button.pack(side=left, padx=5, pady=5)
         
        self.process_button = ttk.button(button_frame, text="开始处理文件", command=self.start_processing, bootstyle=info)
        self.process_button.pack(side=right, padx=5, pady=5)
         
        config_frame = ttk.labelframe(frame, text="ffmpeg 配置")
        config_frame.pack(fill=both, expand=yes, pady=5)
         
        ttk.label(config_frame, text="输出格式:").pack(side=left, padx=5, pady=5)
        ttk.optionmenu(config_frame, self.output_format, "mp4", "mp4", "avi", "mov").pack(side=left, padx=5, pady=5)
         
        ttk.label(config_frame, text="视频编码器:").pack(side=left, padx=5, pady=5)
        ttk.optionmenu(config_frame, self.video_codec, "libx264", "libx264", "libx265", "mpeg4").pack(side=left, padx=5, pady=5)
         
        ttk.label(config_frame, text="音频编码器:").pack(side=left, padx=5, pady=5)
        ttk.optionmenu(config_frame, self.audio_codec, "aac", "aac", "mp3", "ac3").pack(side=left, padx=5, pady=5)
         
        ttk.label(config_frame, text="线程数:").pack(side=left, padx=5, pady=5)
        ttk.entry(config_frame, textvariable=self.thread_count).pack(side=left, padx=5, pady=5)
 
        self.blur_checkbox = ttk.checkbutton(config_frame, text="应用高斯模糊效果", variable=self.apply_blur)
        self.blur_checkbox.pack(side=left, padx=5, pady=5)
 
        # set up drag and drop
        self.master.drop_target_register(dnd_files)
        self.master.dnd_bind('<<drop>>', self.drop_files)
     
    def create_menu(self):
        menu_bar = menu(self.master)
        self.master.config(menu=menu_bar)
 
        help_menu = menu(menu_bar, tearoff=0)
        menu_bar.add_cascade(label="帮助", menu=help_menu)
         
        help_menu.add_command(label="使用说明", command=self.show_usage_instructions)
        help_menu.add_command(label="软件具体说明", command=self.show_software_details)
     
    def show_usage_instructions(self):
        instructions = (
            "使用说明:\n"
            "1. 导入文件:点击“导入文件”按钮,选择需要处理的视频文件,或将视频文件拖拽到软件窗口中。\n"
            "2. 设置输出文件夹:点击“导出文件”按钮,选择一个文件夹作为输出文件夹。\n"
            "3. 配置ffmpeg参数:在“ffmpeg 配置”区域,选择输出格式、视频编码器、音频编码器、线程数,并可选择是否应用高斯模糊效果。\n"
            "4. 开始处理:点击“开始处理文件”按钮,开始批量处理视频文件。处理过程中可以查看处理进度和日志信息。\n"
            "5. 查看输出文件:点击“打开文件”按钮,打开输出文件夹查看处理完成的视频文件。\n"
            "6. 删除文件:选择文件列表中的文件,点击“删除文件”按钮删除不需要处理的文件。\n"
        )
        messagebox.showinfo("使用说明", instructions)
     
    def show_software_details(self):
        details = (
            "仅供学习,切勿使用到其他用途\n"
            "1. 输出格式:支持mp4、avi和mov等常见格式,用户可自定义选择。\n"
            "2. 视频压缩:默认使用libx264视频编码器和aac音频编码器,支持高效视频压缩,用户可自定义选择其他编码器。\n"
            "3. 视频裁剪:适用于将1920x1080横屏视频裁剪成9:16竖屏视频,不会变形。\n"
            "4. 高斯模糊:可选应用高斯模糊效果,适用于特殊视频效果需求。\n"
            "5. 多线程处理:支持多线程并发处理,用户可自定义线程数,提高处理效率。\n"
 
        )
        messagebox.showinfo("软件具体说明", details)
         
    def drop_files(self, event):
        files = self.master.tk.splitlist(event.data)
        for file in files:
            if file not in self.input_files and file.lower().endswith(('.mp4', '.avi', '.mov')):
                self.input_files.append(file)
                self.file_tree.insert('', end, values=(len(self.input_files), os.path.basename(file), "未处理"))
                self.log(f"导入文件: {file}")
            else:
                messagebox.showwarning("警告", f"文件已存在或不支持的文件类型: {os.path.basename(file)}")
     
    def add_files(self):
        files = filedialog.askopenfilenames(title="选择视频文件", filetypes=[("视频文件", "*.mp4 *.avi *.mov")])
        for file in files:
            if file not in self.input_files:
                self.input_files.append(file)
                self.file_tree.insert('', end, values=(len(self.input_files), os.path.basename(file), "未处理"))
                self.log(f"导入文件: {file}")
            else:
                messagebox.showwarning("警告", f"文件已存在: {os.path.basename(file)}")
         
    def remove_files(self):
        selected_items = self.file_tree.selection()
        indices_to_remove = []
         
        for item in selected_items:
            values = self.file_tree.item(item, 'values')
            if values:
                index = int(values[0]) - 1
                indices_to_remove.append(index)
                self.file_tree.delete(item)
         
        # 删除索引列表中的元素(倒序删除避免索引问题)
        for index in sorted(indices_to_remove, reverse=true):
            del self.input_files[index]
         
        self.log("删除选中文件")
        self.refresh_file_list()
     
    def refresh_file_list(self):
        for item in self.file_tree.get_children():
            self.file_tree.delete(item)
        for index, file in enumerate(self.input_files):
            self.file_tree.insert('', end, values=(index + 1, os.path.basename(file), "未处理"))
 
    def set_output_folder(self):
        self.output_folder = filedialog.askdirectory(title="选择输出文件夹")
        self.log(f"设置输出文件夹: {self.output_folder}")
         
    def start_processing(self):
        if not self.input_files or not self.output_folder:
            messagebox.showerror("错误", "请添加文件并设置输出文件夹。")
            return
         
        self.process_thread = threading.thread(target=self.process_videos_concurrently)
        self.process_thread.start()
         
    def toggle_pause(self):
        if self.pause_event.is_set():
            self.pause_event.clear()
            self.log("处理暂停")
            for process in self.ffmpeg_processes:
                proc = psutil.process(process.pid)
                proc.suspend()
        else:
            self.pause_event.set()
            self.log("处理继续")
            for process in self.ffmpeg_processes:
                proc = psutil.process(process.pid)
                proc.resume()
     
    def open_output_folder(self):
        if self.output_folder:
            os.startfile(self.output_folder)
            self.log(f"打开输出文件夹: {self.output_folder}")
        else:
            messagebox.showerror("错误", "请先设置输出文件夹。")
     
    def log(self, message):
        if not self.is_closing:
            self.master.after(0, self._log, message)
     
    def _log(self, message):
        if not self.is_closing:
            self.log_text.configure(state='normal')
            self.log_text.insert(end, message + '\n')
            self.log_text.configure(state='disabled')
            self.log_text.yview(end)
     
    def update_tree_status(self, index, status):
        if not self.is_closing:
            self.master.after(0, self._update_tree_status, index, status)
     
    def _update_tree_status(self, index, status):
        if not self.is_closing:
            self.file_tree.item(self.file_tree.get_children()[index], values=(index + 1, os.path.basename(self.input_files[index]), status))
     
    def process_videos_concurrently(self):
        max_workers = self.thread_count.get()
        with threadpoolexecutor(max_workers=max_workers) as executor:
            futures = [executor.submit(self.process_video, index, input_file) for index, input_file in enumerate(self.input_files)]
            for future in as_completed(futures):
                future.result()
 
    def process_video(self, index, input_file):
        ffmpeg_path = resource_path(os.path.join("ffmpeg_folder", "ffmpeg"))
        filename = os.path.basename(input_file)
        output_file = os.path.join(self.output_folder, f"processed_{filename}.{self.output_format.get()}")
 
        if os.path.exists(output_file):
            overwrite = messagebox.askyesno("文件已存在", f"{output_file} 已存在,是否覆盖?")
            if not overwrite:
                self.update_tree_status(index, "跳过")
                return
 
        if self.apply_blur.get():
            cmd = [
                ffmpeg_path,
                "-y",  # 自动覆盖输出文件
                "-i", input_file,
                "-vf", "split[a][b];[a]scale=1080:1920,boxblur=10:5[1];[b]scale=1080:ih*1080/iw[2];[1][2]overlay=0:(h-h)/2",
                "-c:v", self.video_codec.get(),
                "-crf", "18",
                "-preset", "veryfast",
                "-aspect", "9:16",
                "-c:a", self.audio_codec.get(),
                output_file
            ]
        else:
            cmd = [
                ffmpeg_path,
                "-y",  # 自动覆盖输出文件
                "-i", input_file,
                "-vf", "scale='if(gt(iw/ih,9/16),1080,-2)':'if(gt(iw/ih,9/16),-2,1920)',pad=1080:1920:(1080-iw)/2:(1920-ih)/2",
                "-c:v", self.video_codec.get(),
                "-crf", "18",
                "-preset", "veryfast",
                "-c:a", self.audio_codec.get(),
                output_file
            ]
 
        self.log(f"开始处理: {filename}")
        self.update_tree_status(index, "处理中")
 
        try:
            startupinfo = subprocess.startupinfo()
            startupinfo.dwflags |= subprocess.startf_useshowwindow
 
            process = subprocess.popen(cmd, stderr=subprocess.pipe, universal_newlines=true, encoding='utf-8', startupinfo=startupinfo)
            self.ffmpeg_processes.append(process)
 
            for line in process.stderr:
                if self.is_closing:
                    break
                progress = self.parse_progress(line)
                if progress:
                    self.update_tree_status(index, progress)
 
            process.wait()
        except exception as e:
            self.log(f"处理文件时出错: {filename} - {str(e)}")
            self.update_tree_status(index, "处理失败")
            return
 
        if self.is_closing:
            self.update_tree_status(index, "未完成")
        else:
            self.log(f"完成处理: {filename}")
            self.update_tree_status(index, "已完成")
            self.ffmpeg_processes.remove(process)
     
    def parse_progress(self, line):
        match = re.search(r'time=(\d+:\d+:\d+\.\d+)', line)
        if match:
            return f"进度: {match.group(1)}"
        return none
     
    def on_closing(self):
        self.is_closing = true
        for process in self.ffmpeg_processes:
            proc = psutil.process(process.pid)
            proc.terminate()
        self.master.destroy()
 
if __name__ == "__main__":
    root = tkinterdnd.tk()
    root.title("视频处理器")
    root.geometry("870x520")  # set the window size to 870x520
    root.resizable(false, false)  # make the window non-resizable
    app = videoprocessor(master=root)
    root.mainloop()

5.总结

该视频处理器应用通过python与tkinter提供了一个强大而简洁的图形界面,允许用户批量处理视频文件。借助ffmpeg,用户不仅可以自由选择输出格式和编码器,还可以根据需求应用视频特效,如高斯模糊。多线程的支持使得处理多个视频文件更加高效,确保了在处理过程中能够灵活控制暂停、继续和取消操作。通过增强的文件管理和进度监控,用户能够轻松掌控整个视频处理过程。此工具对于需要批量转换视频格式或应用特效的用户非常实用,尤其适合需要高效处理大量视频文件的场景。

以上就是python批量实现横屏转竖屏的视频处理工具的详细内容,更多关于python视频横屏转竖屏的资料请关注代码网其它相关文章!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com