视频批量横转竖提供高效便捷的视频批量横转竖视频编辑和处理工具

import os
import ttkbootstrap as ttk
from ttkbootstrap.constants import *
from tkinter import filedialog, messagebox, END, Text, StringVar, IntVar
from concurrent.futures import ThreadPoolExecutor, as_completed
import subprocess
import threading
import psutil
import re
import sys
 
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("视频处理器")
         
        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=4)  # Default to 4 threads
         
        self.create_widgets()
        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)
         
    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()
        for item in selected_items:
            index = int(self.file_tree.item(item, 'values')[0]) - 1
            del self.input_files[index]
            self.file_tree.delete(item)
        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
 
        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(),
            "-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, "处理失败")
            process = None
            self.current_index = None
            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 = ttk.Window(themename="superhero")
    app = VideoProcessor(master=root)
    root.mainloop()

视频处理器 v1.2 是一款高效便捷的视频编辑和处理工具,由貔貅开发。它提供了视频批量横转竖功能,支持导入和删除多个视频文件,可随时暂停和继续处理。用户可以自定义输出格式、视频编码器、音频编码器和线程数,并实时记录处理日志。该软件还具有用户友好界面,支持多种格式,高效多线程处理,实时日志显示等特点。


```python
import os
import ttkbootstrap as ttk
from ttkbootstrap.constants import *
from tkinter import filedialog, messagebox, END, Text, StringVar, IntVar
from concurrent.futures import ThreadPoolExecutor, as_completed
import subprocess
import threading
import psutil
import re
import sys

def resource_path(relative_path):
    """ Get absolute path to resource, works for dev and for PyInstaller """
    try:
        # PyInstaller 创建一个临时文件夹并将路径存储在 _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("视频处理器")

        self.input_files = []
        self.output_folder = ""
        self.process_thread = None
        self.pause_event = threading.Event()
        self.pause_event.set()  # 启动时处于未暂停状态
        self.ffmpeg_processes = []  # 用于跟踪所有 ffmpeg 进程的列表
        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=4)  # 默认使用 4 个线程

        self.create_widgets()
        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.start_button = ttk.Button(button_frame, text="开始处理", command=self.start_processing, bootstyle=SUCCESS)
        self.start_button.pack(side=LEFT, padx=5, pady=5)

        self.pause_button = ttk.Button(button_frame, text="暂停/继续", command=self.pause_resume_processing, bootstyle=INFO)
        self.pause_button.pack(side=LEFT, padx=5, pady=5)

        self.thread_count_label = ttk.Label(button_frame, text="线程数:")
        self.thread_count_label.pack(side=LEFT, padx=5, pady=5)

        self.thread_count_spinbox = ttk.Spinbox(button_frame, from_=1, to=os.cpu_count(), textvariable=self.thread_count)
        self.thread_count_spinbox.pack(side=LEFT, padx=5, pady=5)

    def add_files(self):
        files = filedialog.askopenfilenames(filetypes=[("视频文件", "*.mp4 *.mkv *.flv *.avi")])
        for file in files:
            self.input_files.append(file)
            self.file_tree.insert("", END, values=(len(self.input_files), os.path.basename(file), "等待处理"))

    def remove_files(self):
        selected_items = self.file_tree.selection()
        for item in selected_items:
            index = int(self.file_tree.item(item, "values")[0]) - 1
            self.input_files.pop(index)
            self.file_tree.delete(item)

    def start_processing(self):
        if not self.input_files:
            messagebox.showerror("错误", "请选择要处理的文件")
            return

        if not self.output_folder:
            messagebox.showerror("错误", "请选择输出文件夹")
            return

        self.start_button.config(state=DISABLED)
        self.add_files_button.config(state=DISABLED)
        self.remove_files_button.config(state=DISABLED)
        self.thread_count_spinbox.config(state=DISABLED)

        self.process_thread = threading.Thread(target=self.process_files)
        self.process_thread.start()

    def process_files(self):
        with ThreadPoolExecutor(max_workers=self.thread_count.get()) as executor:
            for index, file in enumerate(self.input_files):
                self.pause_event.wait()  # 暂停线程,直到收到继续信号
                future = executor.submit(self.process_file, file, index)
                while not future.done():
                    if self.pause_event.is_set():
                        self.pause_event.clear()
                    else:
                        self.pause_event.set()

    def process_file(self, file, index):
        output_file = os.path.join(self.output_folder, os.path.splitext(os.path.basename(file))[0] + "." + self.output_format.get())
        command = f'ffmpeg -i "{file}" -c:v {self.video_codec.get()} -c:a {self.audio_codec.get()} -threads {self.thread_count.get()} "{output_file}"'
        process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True)
        self.ffmpeg_processes.append(process)

        for line in process.stdout:
            if self.is_closing:
                break

            if "time=" in line:
                time_str = re.search(r"time=(\d+:\d+:\d+\.\d+)", line).group(1)
                hours, minutes, seconds = map(float, time_str.split(':'))
                total_seconds = int(hours * 3600 + minutes * 60 + seconds)
                progress = int((total_seconds / process.stderr.readline()) * 100)
                self.file_tree.item(self.input_files.index(file) + 1, values=(index + 1, os.path.basename(file), f"{progress}%"))

        process.wait()
        self.ffmpeg_processes.remove(process)

        if process.returncode == 0:
            self.file_tree.item(self.input_files.index(file) + 1, values=(index + 1, os.path.basename(file), "处理完成"))
        else:
            self.file_tree.item(self.input_files.index(file) + 1, values=(index + 1, os.path.basename(file), "处理失败"))

    def pause_resume_processing(self):
        if self.pause_event.is_set():
            self.pause_event.clear()
            self.pause_button.config(text="暂停")
        else:
            self.pause_event.set()
            self.pause_button.config(text="继续")

    def on_closing(self):
        self.is_closing = True
        for process in self.ffmpeg_processes:
            try:
                parent = psutil.Process(process.pid)
                for child in parent.children(recursive=True):
                    child.kill()
                process.kill()
            except psutil.NoSuchProcess:
                pass

        self.master.destroy()
```

  • 7
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
GiliSoft视频编辑器15是一款功能强大的视频编辑工具,它提供了丰富的编辑功能和工具,可以帮助用户轻松编辑和制作高质量的视频内容。该软件支持多种视频格式,包括MP4、AVI、WMV等常见格式,使用户可以方便地导入和编辑各种视频文件。 GiliSoft视频编辑器15具有直观的界面和易于使用的编辑工具,适合各种用户进行视频编辑。它提供视频剪辑、拼接、分割、裁剪等功能,还支持添加文字、音频、特效等元素,丰富了视频内容的表现形式。此外,该软件还内置了多种视频滤镜和转场效果,可以让用户轻松为视频添加各种视觉效果,提升视频的质感和观赏性。 GiliSoft视频编辑器15还具有强大的视频转换和压缩功能,可以帮助用户将视频转换为不同格式,满足不同设备的需求。同时,该软件还支持批量处理多个视频文件,提高了用户的工作效率。除此之外,软件还提供视频字幕编辑、水印添加、画中画效果等实用功能,丰富了视频的表现形式,使用户可以轻松制作出专业水准的视频内容。 总的来说,GiliSoft视频编辑器15是一款功能齐全、易于上手的视频编辑工具,适合各种用户进行视频编辑、制作和转换。它的丰富功能和直观操作界面,使用户能够快速高效地进行视频编辑工作,为他们的视频内容增添更多的创意和表现方式。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大飞哥软件自习室

希望支持

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值