本文目录导读:

我来为你详细介绍视频处理工具封装的方法,包含完整的案例代码。
基础视频处理工具类封装
1 使用MoviePy的封装示例
import moviepy.editor as mp
from moviepy.video.fx import resize, rotate
import numpy as np
from typing import Optional, Tuple, List
import os
class VideoProcessor:
"""视频处理工具类"""
def __init__(self, video_path: str):
"""
初始化视频处理器
Args:
video_path: 视频文件路径
"""
self.video_path = video_path
self.video = None
self._load_video()
def _load_video(self):
"""加载视频文件"""
if not os.path.exists(self.video_path):
raise FileNotFoundError(f"视频文件不存在: {self.video_path}")
self.video = mp.VideoFileClip(self.video_path)
def cut_video(self, start_time: float, end_time: float,
output_path: str) -> bool:
"""
截取视频片段
Args:
start_time: 开始时间(秒)
end_time: 结束时间(秒)
output_path: 输出路径
Returns:
bool: 是否成功
"""
try:
clip = self.video.subclip(start_time, end_time)
clip.write_videofile(output_path, codec="libx264")
clip.close()
return True
except Exception as e:
print(f"截取失败: {e}")
return False
def resize_video(self, width: int, height: int,
output_path: str) -> bool:
"""
调整视频尺寸
Args:
width: 目标宽度
height: 目标高度
output_path: 输出路径
"""
try:
resized = self.video.resize((width, height))
resized.write_videofile(output_path, codec="libx264")
resized.close()
return True
except Exception as e:
print(f"调整尺寸失败: {e}")
return False
def add_text(self, text: str, position: Tuple[int, int],
duration: float, output_path: str,
font_size: int = 24, color: str = "white") -> bool:
"""
添加文本字幕
Args:
text: 文本内容
position: 位置 (x, y)
duration: 持续时间(秒)
output_path: 输出路径
font_size: 字体大小
color: 字体颜色
"""
try:
txt_clip = mp.TextClip(text, fontsize=font_size,
color=color, font="Arial")
txt_clip = txt_clip.set_position(position).set_duration(duration)
video_with_text = mp.CompositeVideoClip([self.video, txt_clip])
video_with_text.write_videofile(output_path, codec="libx264")
txt_clip.close()
video_with_text.close()
return True
except Exception as e:
print(f"添加文字失败: {e}")
return False
def merge_videos(self, video_list: List[str],
output_path: str) -> bool:
"""
合并多个视频
Args:
video_list: 视频文件路径列表
output_path: 输出路径
"""
try:
clips = [mp.VideoFileClip(v) for v in video_list]
final_clip = mp.concatenate_videoclips(clips)
final_clip.write_videofile(output_path, codec="libx264")
for clip in clips:
clip.close()
final_clip.close()
return True
except Exception as e:
print(f"合并视频失败: {e}")
return False
def extract_audio(self, output_path: str,
audio_format: str = "mp3") -> bool:
"""
提取音频
Args:
output_path: 输出路径
audio_format: 音频格式
"""
try:
audio = self.video.audio
audio.write_audiofile(output_path, codec=audio_format)
audio.close()
return True
except Exception as e:
print(f"提取音频失败: {e}")
return False
def add_audio(self, audio_path: str, output_path: str,
volume: float = 1.0) -> bool:
"""
添加音频
Args:
audio_path: 音频文件路径
output_path: 输出路径
volume: 音量 (0.0-1.0)
"""
try:
audio_clip = mp.AudioFileClip(audio_path).volumex(volume)
video_with_audio = self.video.set_audio(audio_clip)
video_with_audio.write_videofile(output_path, codec="libx264")
audio_clip.close()
video_with_audio.close()
return True
except Exception as e:
print(f"添加音频失败: {e}")
return False
def get_video_info(self) -> dict:
"""
获取视频信息
Returns:
dict: 视频信息字典
"""
return {
"duration": self.video.duration,
"fps": self.video.fps,
"width": self.video.w,
"height": self.video.h,
"size": self.video.size,
"rotation": self.video.rotation
}
def close(self):
"""关闭视频文件"""
if self.video:
self.video.close()
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.close()
2 使用OpenCV的封装示例
import cv2
import numpy as np
from pathlib import Path
import tempfile
import subprocess
class OpenCVVideoProcessor:
"""使用OpenCV的视频处理工具类"""
def __init__(self, video_path: str):
self.video_path = video_path
self.cap = None
self.fps = 0
self.total_frames = 0
self.width = 0
self.height = 0
self._load_video()
def _load_video(self):
"""加载视频"""
self.cap = cv2.VideoCapture(self.video_path)
if not self.cap.isOpened():
raise ValueError(f"无法打开视频文件: {self.video_path}")
self.fps = int(self.cap.get(cv2.CAP_PROP_FPS))
self.total_frames = int(self.cap.get(cv2.CAP_PROP_FRAME_COUNT))
self.width = int(self.cap.get(cv2.CAP_PROP_FRAME_WIDTH))
self.height = int(self.cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
def apply_filter(self, filter_type: str = "gray",
output_path: str = None) -> bool:
"""
应用滤镜
Args:
filter_type: 滤镜类型 (gray, blur, edge, invert)
output_path: 输出路径
"""
if output_path is None:
output_path = f"filtered_{Path(self.video_path).name}"
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
out = cv2.VideoWriter(output_path, fourcc, self.fps,
(self.width, self.height))
while True:
ret, frame = self.cap.read()
if not ret:
break
if filter_type == "gray":
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
frame = cv2.cvtColor(frame, cv2.COLOR_GRAY2BGR)
elif filter_type == "blur":
frame = cv2.GaussianBlur(frame, (15, 15), 0)
elif filter_type == "edge":
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
frame = cv2.Canny(gray, 100, 200)
frame = cv2.cvtColor(frame, cv2.COLOR_GRAY2BGR)
elif filter_type == "invert":
frame = cv2.bitwise_not(frame)
out.write(frame)
out.release()
self.cap.release()
return True
def extract_frames(self, frame_interval: int = 1,
output_dir: str = "frames") -> List[str]:
"""
提取帧
Args:
frame_interval: 帧间隔
output_dir: 输出目录
Returns:
List[str]: 保存的帧文件路径列表
"""
Path(output_dir).mkdir(parents=True, exist_ok=True)
frame_files = []
frame_count = 0
while True:
ret, frame = self.cap.read()
if not ret:
break
if frame_count % frame_interval == 0:
frame_path = f"{output_dir}/frame_{frame_count:06d}.jpg"
cv2.imwrite(frame_path, frame)
frame_files.append(frame_path)
frame_count += 1
self.cap.release()
return frame_files
def __del__(self):
"""析构函数"""
if self.cap:
self.cap.release()
3 使用FFmpeg的高级封装
import subprocess
import json
from pathlib import Path
from typing import Optional
class FFmpegVideoProcessor:
"""使用FFmpeg的高级视频处理"""
def __init__(self, ffmpeg_path: str = "ffmpeg"):
self.ffmpeg_path = ffmpeg_path
def compress_video(self, input_path: str, output_path: str,
target_size_mb: int = 10) -> bool:
"""
压缩视频到目标大小
Args:
input_path: 输入路径
output_path: 输出路径
target_size_mb: 目标大小(MB)
"""
# 获取视频信息
probe_cmd = [
self.ffmpeg_path,
"-i", input_path,
"-f", "null",
"-"
]
# 计算比特率
duration = self._get_duration(input_path)
target_bitrate = (target_size_mb * 8 * 1024 * 1024) / duration
cmd = [
self.ffmpeg_path,
"-i", input_path,
"-b:v", f"{int(target_bitrate)}",
"-bufsize", f"{int(target_bitrate * 2)}",
"-maxrate", f"{int(target_bitrate * 1.5)}",
"-y",
output_path
]
try:
subprocess.run(cmd, check=True, capture_output=True)
return True
except subprocess.CalledProcessError as e:
print(f"压缩失败: {e.stderr.decode()}")
return False
def add_subtitles(self, input_path: str, subtitle_path: str,
output_path: str) -> bool:
"""
添加字幕
Args:
input_path: 输入路径
subtitle_path: 字幕文件路径
output_path: 输出路径
"""
cmd = [
self.ffmpeg_path,
"-i", input_path,
"-vf", f"subtitles={subtitle_path}",
"-y",
output_path
]
try:
subprocess.run(cmd, check=True)
return True
except subprocess.CalledProcessError as e:
print(f"添加字幕失败: {e}")
return False
def change_speed(self, input_path: str, output_path: str,
speed_factor: float = 1.5) -> bool:
"""
改变视频速度
Args:
input_path: 输入路径
output_path: 输出路径
speed_factor: 速度因子 (1.0=正常)
"""
# 计算相应的音频和视频滤镜
video_filter = f"setpts={1/speed_factor}*PTS"
audio_filter = f"atempo={min(speed_factor, 2.0)}"
# 如果速度因子大于2.0,需要多次应用音频滤镜
if speed_factor > 2.0:
audio_filter = f"atempo={speed_factor:.2f}[a1];{audio_filter}"
cmd = [
self.ffmpeg_path,
"-i", input_path,
"-vf", video_filter,
"-af", audio_filter,
"-y",
output_path
]
try:
subprocess.run(cmd, check=True)
return True
except subprocess.CalledProcessError as e:
print(f"改变速度失败: {e}")
return False
def _get_duration(self, video_path: str) -> float:
"""获取视频时长(秒)"""
cmd = [
self.ffmpeg_path,
"-i", video_path,
"-f", "null",
"-"
]
result = subprocess.run(cmd, capture_output=True, text=True)
# 从输出中提取时长
import re
duration_match = re.search(r"Duration: (\d+):(\d+):(\d+\.\d+)",
result.stderr)
if duration_match:
hours = int(duration_match.group(1))
minutes = int(duration_match.group(2))
seconds = float(duration_match.group(3))
return hours * 3600 + minutes * 60 + seconds
return 0.0
GUI视频处理工具
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
from pathlib import Path
import threading
class VideoProcessorGUI:
"""视频处理GUI工具"""
def __init__(self):
self.root = tk.Tk()
self.root.title("视频处理工具")
self.root.geometry("600x400")
self.processor = None
self.input_path = ""
self.output_path = ""
self._setup_ui()
def _setup_ui(self):
"""设置UI"""
# 文件选择
file_frame = ttk.LabelFrame(self.root, text="文件选择", padding=10)
file_frame.pack(fill="x", padx=10, pady=5)
ttk.Button(file_frame, text="选择视频",
command=self._select_file).pack(side="left", padx=5)
self.file_label = ttk.Label(file_frame, text="未选择文件")
self.file_label.pack(side="left", padx=10)
# 操作选择
operation_frame = ttk.LabelFrame(self.root, text="操作", padding=10)
operation_frame.pack(fill="x", padx=10, pady=5)
self.operation_var = tk.StringVar(value="cut")
operations = [
("截取视频", "cut"),
("调整大小", "resize"),
("添加文字", "text"),
("提取音频", "audio"),
("添加滤镜", "filter")
]
for text, value in operations:
ttk.Radiobutton(operation_frame, text=text,
variable=self.operation_var,
value=value).pack(side="left", padx=10)
# 参数设置
param_frame = ttk.LabelFrame(self.root, text="参数设置", padding=10)
param_frame.pack(fill="both", expand=True, padx=10, pady=5)
# 不同的操作显示不同的参数
self.param_frame = param_frame
self._show_cut_params()
# 操作按钮
button_frame = ttk.Frame(self.root, padding=10)
button_frame.pack(fill="x", padx=10, pady=5)
ttk.Button(button_frame, text="选择输出路径",
command=self._select_output).pack(side="left", padx=5)
ttk.Button(button_frame, text="开始处理",
command=self._start_processing).pack(side="left", padx=5)
ttk.Button(button_frame, text="退出",
command=self.root.quit).pack(side="right", padx=5)
# 进度条
self.progress = ttk.Progressbar(self.root, mode="indeterminate")
self.progress.pack(fill="x", padx=10, pady=5)
def _show_cut_params(self):
"""显示截取参数"""
for widget in self.param_frame.winfo_children():
widget.destroy()
ttk.Label(self.param_frame, text="开始时间(秒):").grid(row=0, column=0, padx=5, pady=5)
self.start_time = ttk.Entry(self.param_frame)
self.start_time.grid(row=0, column=1, padx=5, pady=5)
self.start_time.insert(0, "0")
ttk.Label(self.param_frame, text="结束时间(秒):").grid(row=1, column=0, padx=5, pady=5)
self.end_time = ttk.Entry(self.param_frame)
self.end_time.grid(row=1, column=1, padx=5, pady=5)
self.end_time.insert(0, "10")
def _select_file(self):
"""选择文件"""
file_path = filedialog.askopenfilename(
title="选择视频文件",
filetypes=[("视频文件", "*.mp4 *.avi *.mov *.mkv")]
)
if file_path:
self.input_path = file_path
self.file_label.config(text=Path(file_path).name)
def _select_output(self):
"""选择输出路径"""
file_path = filedialog.asksaveasfilename(
title="选择输出路径",
defaultextension=".mp4",
filetypes=[("MP4文件", "*.mp4"), ("所有文件", "*.*")]
)
if file_path:
self.output_path = file_path
def _start_processing(self):
"""开始处理"""
if not self.input_path or not self.output_path:
messagebox.showerror("错误", "请选择输入和输出文件")
return
# 在新线程中处理,避免阻塞GUI
thread = threading.Thread(target=self._process_video)
thread.start()
def _process_video(self):
"""处理视频"""
self.progress.start()
try:
# 根据选择的操
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播