win10(三)视频剪裁

上传一刻相册,有30M大小限制。这个软件能免费剪裁视频而且支持手机的H.265格式,这个格式目前连potplayer都支持不好。但是配合FFmpeg可以检测并且能按大小(或时间)剪裁,并上传到一刻相册上播放。

下载FFmpeg的方法:打开网站

Download FFmpeg

然后选择Windows由gyan.dev构建,再将下载的压缩包解压为文件夹,将文件夹放到自己想放的位置,并在环境变量里添加其中bin文件夹的位置。

例如解压为ffmpeg文件夹放到C盘表面后,在环境变量Path里增加C:\ffmpeg\bin即可。

以下是批量剪裁python代码:

coding=utf-8

import tkinter as tk

from tkinter import ttk, filedialog, messagebox

import subprocess

import os

import threading

import queue

import time

import json

class VideoSplitterApp:

def init(self, root):

self.root = root

self.root.title("视频分割工具")

self.root.geometry("500x400")

self.root.configure(bg='light yellow') # 设置背景为黄色

self.video_paths = []

self.split_method = tk.StringVar()

self.split_method.set("size") # 默认按大小分割

self.split_threads = []

self.output_dir = None

self.current_process = None

self.is_cancelled = False

self.progress_value = tk.DoubleVar()

self.current_video_index = 0

self.progress_queue = queue.Queue()

设置字体

self.font_style = ("FangSong", 12)

self.button_font_style = ("FangSong", 12, "bold")

糖果色按钮颜色

self.candy_colors = ['#FFB6C1', '#87CEFA', '#98FB98', '#DDA0DD', '#FFD700']

self.create_widgets()

定期检查进度队列

self.check_progress_queue()

检查FFmpeg是否可用

self.check_ffmpeg_available()

def check_ffmpeg_available(self):

"""检查系统是否安装了FFmpeg"""

try:

result = subprocess.run(['ffmpeg', '-version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)

if result.returncode != 0:

messagebox.showerror("错误", "未找到FFmpeg。请确保已正确安装FFmpeg并将其添加到系统PATH中。")

except FileNotFoundError:

messagebox.showerror("错误", "未找到FFmpeg。请确保已正确安装FFmpeg并将其添加到系统PATH中。")

def create_widgets(self):

文件选择部分

self.file_frame = tk.Frame(self.root, bg='light yellow')

self.file_frame.pack(pady=10, fill=tk.X, padx=10)

self.select_button = tk.Button(self.file_frame, text="选择视频文件",

command=self.select_video,

bg=self.candy_colors[0],

font=self.button_font_style,

relief=tk.RAISED, bd=2)

self.select_button.pack(side=tk.LEFT, padx=5)

self.file_label = tk.Label(self.file_frame, text="未选择文件",

wraplength=300, bg='light yellow',

font=self.font_style)

self.file_label.pack(side=tk.LEFT, padx=5, fill=tk.X, expand=True)

分割方式选择

self.method_frame = tk.Frame(self.root, bg='light yellow')

self.method_frame.pack(pady=10, fill=tk.X, padx=10)

self.time_radio = tk.Radiobutton(self.method_frame, text="按时间分割",

variable=self.split_method, value="time",

command=self.toggle_input,

bg='light yellow', font=self.font_style)

self.time_radio.pack(side=tk.LEFT, padx=5)

self.size_radio = tk.Radiobutton(self.method_frame, text="按大小分割",

variable=self.split_method, value="size",

command=self.toggle_input,

bg='light yellow', font=self.font_style)

self.size_radio.pack(side=tk.LEFT, padx=5)

输入框

self.input_frame = tk.Frame(self.root, bg='light yellow')

self.input_frame.pack(pady=10, fill=tk.X, padx=10)

self.time_label = tk.Label(self.input_frame, text="分割时间(秒):",

bg='light yellow', font=self.font_style)

self.time_label.pack(side=tk.LEFT, padx=5)

self.time_entry = tk.Entry(self.input_frame, width=10, font=self.font_style)

self.time_entry.pack(side=tk.LEFT, padx=5)

self.size_label = tk.Label(self.input_frame, text="分割大小(MB):",

bg='light yellow', font=self.font_style)

self.size_label.pack(side=tk.LEFT, padx=5)

self.size_entry = tk.Entry(self.input_frame, width=10, font=self.font_style)

self.size_entry.insert(0, "28") # 默认大小20MB

self.size_entry.pack(side=tk.LEFT, padx=5)

self.toggle_input() # 初始化时禁用不需要的输入框

输出目录选择

self.output_frame = tk.Frame(self.root, bg='light yellow')

self.output_frame.pack(pady=5, fill=tk.X, padx=10)

self.output_button = tk.Button(self.output_frame,

text="选择输出文件夹",

command=self.select_output_dir,

bg=self.candy_colors[1],

font=self.button_font_style,

relief=tk.RAISED, bd=2)

self.output_button.pack(side=tk.LEFT, padx=5)

self.output_label = tk.Label(self.output_frame,

text="默认在原目录创建split_output",

wraplength=300, bg='light yellow',

font=self.font_style)

self.output_label.pack(side=tk.LEFT, padx=5, fill=tk.X, expand=True)

进度条

self.progress_frame = tk.Frame(self.root, bg='light yellow')

self.progress_frame.pack(pady=10, fill=tk.X, padx=10)

self.progress_label = tk.Label(self.progress_frame, text="准备就绪",

bg='light yellow', font=self.font_style)

self.progress_label.pack(fill=tk.X)

self.progress_bar = ttk.Progressbar(self.progress_frame,

variable=self.progress_value,

maximum=100)

self.progress_bar.pack(fill=tk.X, pady=5)

操作按钮

self.button_frame = tk.Frame(self.root, bg='light yellow')

self.button_frame.pack(pady=10, fill=tk.X, padx=10)

self.start_button = tk.Button(self.button_frame, text="开始分割",

command=self.start_split,

bg=self.candy_colors[2],

font=self.button_font_style,

relief=tk.RAISED, bd=2)

self.start_button.pack(side=tk.LEFT, padx=5)

self.cancel_button = tk.Button(self.button_frame, text="取消",

command=self.cancel_split,

bg=self.candy_colors[3],

font=self.button_font_style,

relief=tk.RAISED, bd=2)

self.cancel_button.pack(side=tk.LEFT, padx=5)

def check_progress_queue(self):

"""定期检查进度队列并更新UI"""

try:

while True:

message = self.progress_queue.get_nowait()

if message.startswith("PROGRESS:"):

progress = float(message.split(":")[1])

self.progress_value.set(progress)

else:

self.progress_label.config(text=message)

except queue.Empty:

pass

finally:

每100毫秒检查一次队列

self.root.after(100, self.check_progress_queue)

def select_video(self):

file_paths = filedialog.askopenfilenames(

title="选择视频文件",

filetypes=[("视频文件", "*.mp4 *.avi *.mkv *.mov *.flv *.wmv *.webm")]

)

if file_paths:

self.video_paths = list(file_paths)

if len(file_paths) == 1:

self.file_label.config(text=os.path.basename(file_paths[0]))

else:

self.file_label.config(text=f"已选择 {len(file_paths)} 个文件")

def toggle_input(self):

if self.split_method.get() == "time":

self.time_entry.config(state="normal")

self.size_entry.config(state="disabled")

else:

self.size_entry.config(state="normal")

self.time_entry.config(state="disabled")

def validate_input(self):

if not self.video_paths:

messagebox.showerror("错误", "请选择视频文件!")

return False

验证每个文件的时长

for path in self.video_paths:

if not os.path.exists(path):

messagebox.showerror("错误", f"文件不存在:{path}")

return False

if self.split_method.get() == "time":

try:

interval = float(self.time_entry.get())

if interval <= 0:

raise ValueError

duration = self.get_video_duration(path)

if duration == 0:

return False

if interval > duration:

messagebox.showwarning("警告",

f"分割时间大于视频时长({duration:.2f}秒),将只生成一个片段")

except ValueError:

messagebox.showerror("错误", "请输入有效的分割时间!")

return False

else:

try:

size = int(self.size_entry.get())

if size <= 0:

raise ValueError

except ValueError:

messagebox.showerror("错误", "请输入有效的分割大小!")

return False

return True

def get_video_duration(self, input_video):

"""获取视频时长,支持H.265编码"""

try:

使用JSON格式输出,更容易解析

cmd = [

'ffprobe', '-v', 'quiet',

'-print_format', 'json',

'-show_format',

'-show_streams',

input_video

]

result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)

if result.returncode != 0:

如果标准方法失败,尝试使用更兼容的方法

return self.get_video_duration_fallback(input_video)

解析JSON输出

data = json.loads(result.stdout)

首先尝试从format获取时长

if 'format' in data and 'duration' in data['format']:

return float(data['format']['duration'])

然后尝试从视频流获取时长

if 'streams' in data:

for stream in data['streams']:

if stream['codec_type'] == 'video' and 'duration' in stream:

return float(stream['duration'])

如果以上方法都失败,使用备用方法

return self.get_video_duration_fallback(input_video)

except Exception as e:

print(f"获取视频时长错误: {e}")

return self.get_video_duration_fallback(input_video)

def get_video_duration_fallback(self, input_video):

"""备用方法获取视频时长"""

try:

尝试使用不同的参数获取视频时长

cmd = [

'ffprobe', '-v', 'error',

'-show_entries', 'format=duration',

'-of', 'default=noprint_wrappers=1:nokey=1',

input_video

]

result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)

if result.returncode == 0:

return float(result.stdout)

如果仍然失败,尝试使用更兼容的方法

cmd = [

'ffprobe', '-i', input_video,

'-show_entries', 'format=duration',

'-v', 'quiet',

'-of', 'csv=p=0'

]

result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)

if result.returncode == 0:

return float(result.stdout)

如果所有方法都失败,显示错误信息

error_msg = f"无法获取视频时长: {input_video}\n"

error_msg += "可能的原因:\n"

error_msg += "1. 视频文件损坏\n"

error_msg += "2. 不支持的视频编码格式(如H.265)\n"

error_msg += "3. FFmpeg版本过旧\n"

error_msg += "请尝试更新FFmpeg到最新版本"

self.progress_queue.put(error_msg)

return 0

except Exception as e:

error_msg = f"获取视频时长错误: {str(e)}"

self.progress_queue.put(error_msg)

return 0

def split_video(self, input_video, interval_sec=0, size_mb=0):

if self.is_cancelled:

return

更新进度标签

self.progress_queue.put(f"处理中: {os.path.basename(input_video)}")

确定输出目录

if self.output_dir:

output_dir = self.output_dir

else:

original_dir = os.path.dirname(input_video)

output_dir = os.path.join(original_dir, "split_output")

os.makedirs(output_dir, exist_ok=True)

生成输出路径

base_name = os.path.basename(input_video)

base, ext = os.path.splitext(base_name)

output_template = os.path.join(output_dir, f"{base}_%03d{ext}")

获取视频时长

duration = self.get_video_duration(input_video)

if duration == 0:

self.progress_queue.put(f"错误: 无法获取 {os.path.basename(input_video)} 的时长")

return

if interval_sec:

按时间分割

cmd = [

'ffmpeg', '-i', input_video, '-c', 'copy',

'-f', 'segment', '-segment_time', str(interval_sec),

'-reset_timestamps', '1', '-y', output_template

]

else:

按大小分割

file_size = os.path.getsize(input_video)

计算目标比特率 (bps)

target_size_bits = size_mb * 1024 * 1024 * 8

计算分割时间

segment_time = (target_size_bits * duration) / (file_size * 8)

cmd = [

'ffmpeg', '-i', input_video, '-c', 'copy',

'-f', 'segment', '-segment_time', str(segment_time),

'-reset_timestamps', '1', '-y', output_template

]

try:

启动进程

process = subprocess.Popen(

cmd,

stdout=subprocess.PIPE,

stderr=subprocess.STDOUT,

universal_newlines=True,

bufsize=1

)

读取输出并更新进度

for line in process.stdout:

if self.is_cancelled:

process.terminate()

break

解析ffmpeg输出以获取进度

if "time=" in line:

time_pos = line.find("time=")

if time_pos != -1:

time_str = line[time_pos+5:].split()[0]

try:

将时间转换为秒

h, m, s = time_str.split(':')

current_time = int(h)*3600 + int(m)*60 + float(s)

计算进度百分比

if duration > 0:

progress = (current_time / duration) * 100

self.progress_queue.put(f"PROGRESS:{progress}")

except:

pass

等待进程完成

process.wait()

if process.returncode == 0 and not self.is_cancelled:

self.progress_queue.put(f"完成: {os.path.basename(input_video)}")

elif self.is_cancelled:

self.progress_queue.put("已取消")

else:

self.progress_queue.put(f"错误: {os.path.basename(input_video)}")

except Exception as e:

self.progress_queue.put(f"错误: {str(e)}")

def start_split(self):

if not self.validate_input():

return

self.is_cancelled = False

self.current_video_index = 0

self.progress_value.set(0)

self.progress_queue.put("开始处理...")

禁用开始按钮,防止重复点击

self.start_button.config(state=tk.DISABLED)

启动一个线程来处理所有视频

worker_thread = threading.Thread(target=self.process_all_videos)

worker_thread.daemon = True # 设置为守护线程,主程序退出时自动结束

worker_thread.start()

def process_all_videos(self):

"""处理所有视频的线程函数"""

for i, video_path in enumerate(self.video_paths):

if self.is_cancelled:

break

self.current_video_index = i

if self.split_method.get() == "time":

interval = float(self.time_entry.get())

self.split_video(video_path, interval)

else:

size = int(self.size_entry.get())

self.split_video(video_path, 0, size)

if not self.is_cancelled:

self.progress_queue.put("所有任务完成!")

在主线程中显示消息框

self.root.after(0, lambda: messagebox.showinfo("完成", "所有视频分割完成!"))

重新启用开始按钮

self.root.after(0, lambda: self.start_button.config(state=tk.NORMAL))

def select_output_dir(self):

"""选择输出目录"""

dir_path = filedialog.askdirectory(title="选择输出文件夹")

if dir_path:

self.output_dir = dir_path

self.output_label.config(text=f"输出目录: {dir_path}")

def cancel_split(self):

self.is_cancelled = True

if self.current_process:

self.current_process.terminate()

self.progress_queue.put("取消中...")

重新启用开始按钮

self.start_button.config(state=tk.NORMAL)

def main():

root = tk.Tk()

app = VideoSplitterApp(root)

root.mainloop()

if name == "main":

main()

相关推荐
ai产品老杨8 小时前
驱动物流创新与协同,助力物流行业可持续发展的智慧物流开源了
人工智能·开源·音视频·能源
xingxing_F9 小时前
SoundSource for Mac 音频控制工具
macos·音视频
音视频牛哥10 小时前
AI+ 行动意见解读:音视频直播SDK如何加速行业智能化
人工智能·音视频·人工智能+·ai+ 行动意见·rtsp/rtmp 播放器·低空经济视频链路·工业巡检视频传输
BUG创建者12 小时前
uni 拍照上传拍视频上传以及相册
前端·javascript·音视频
无线图像传输研究探索12 小时前
无定位更安全:5G 高清视频终端的保密场景适配之道
5g·安全·音视频·无人机·5g单兵图传·单兵图传·无人机图传
音视频牛哥13 小时前
音视频技术全景:从采集到低延迟播放的完整链路解析
音视频·gb28181·rtsp播放器·rtmp播放器·gb28181-2022·rtmp摄像头推流·rtsp转rtmp推送
菜鸟的日志13 小时前
【音频字幕】构建一个离线视频字幕生成系统:使用 WhisperX 和 Faster-Whisper 的 Python 实现
python·whisper·音视频
Antonio91515 小时前
【音视频】WebRTC P2P、SFU 和 MCU 架构
音视频·webrtc·p2p
山河君16 小时前
webrtc之高通滤波——HighPassFilter源码及原理分析
算法·音视频·webrtc·信号处理