![2025-06-11[批量裁剪素材视频]_第1张图片](http://img.e-com-net.com/image/info8/ebb738ef638d4c39be07044b1e039cd6.jpg)
import subprocess
import os
import re
import math
from concurrent.futures import ThreadPoolExecutor
# 配置参数
ffmpeg_path = r"D:\03必备软件\ffmpeg-2025-06-08\bin\ffmpeg.exe"
ffprobe_path = r"D:\03必备软件\ffmpeg-2025-06-08\bin\ffprobe.exe"
input_dir = r".\input" # 输入文件夹路径
output_dir = r".\output" # 输出文件夹路径
target_width = 500 # 目标宽度
target_crop_height = 450 # 中间裁切部分的高度
top_padding = 180 # 上方黑色块高度
bottom_padding = 270 # 下方黑色块高度
total_height = top_padding + target_crop_height + bottom_padding # 总高度850
target_duration = 180 # 目标时长(秒)
# 确保输出目录存在
os.makedirs(output_dir, exist_ok=True)
def get_video_dimensions(input_path):
"""使用FFmpeg获取视频尺寸"""
cmd = [
ffmpeg_path,
"-i", input_path
]
result = subprocess.run(
cmd,
stderr=subprocess.PIPE,
stdout=subprocess.PIPE,
text=True,
creationflags=subprocess.CREATE_NO_WINDOW
)
# 解析输出信息
output = result.stderr
match = re.search(r"Stream.*Video.*?(\d{3,})x(\d{3,})", output)
if match:
return int(match.group(1)), int(match.group(2))
# 尝试另一种模式
match = re.search(r"Video:.*?(\d{3,})x(\d{3,})", output)
if match:
return int(match.group(1)), int(match.group(2))
# 尝试第三种模式
match = re.search(r" (\d{3,})x(\d{3,})[, ]", output)
if match:
return int(match.group(1)), int(match.group(2))
return None, None
def get_video_duration(input_path):
"""使用FFprobe获取视频时长"""
cmd = [
ffprobe_path,
"-v", "error",
"-show_entries", "format=duration",
"-of", "default=noprint_wrappers=1:nokey=1",
input_path
]
result = subprocess.run(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
creationflags=subprocess.CREATE_NO_WINDOW
)
try:
return float(result.stdout.strip())
except ValueError:
print(f"无法解析时长: {result.stdout}")
return 0
def process_video(input_path):
"""处理单个视频文件"""
try:
filename = os.path.basename(input_path)
output_path = os.path.join(output_dir, f"processed_{filename}")
# 获取视频信息
orig_width, orig_height = get_video_dimensions(input_path)
if orig_width is None or orig_height is None:
raise ValueError("无法获取视频分辨率")
orig_duration = get_video_duration(input_path)
if orig_duration <= 0:
raise ValueError("无法获取视频时长")
print(f"\n处理文件: {filename}")
print(f"原始尺寸: {orig_width}x{orig_height}")
print(f"原始时长: {orig_duration:.2f}秒")
# 计算缩放后的尺寸 - 只针对中间视频部分(500x500)
scale_ratio = max(target_width / orig_width, target_crop_height / orig_height)
scaled_width = round(orig_width * scale_ratio)
scaled_height = round(orig_height * scale_ratio)
print(f"缩放比例: {scale_ratio:.2f}")
print(f"缩放后尺寸: {scaled_width}x{scaled_height}")
# 计算位置参数
x_range = max(0, scaled_width - target_width)
y_range = max(0, scaled_height - target_crop_height)
# 定义9个裁剪位置 (3x3网格)
positions = []
for y in [0, y_range // 2, y_range]:
for x in [0, x_range // 2, x_range]:
positions.append((x, y))
# 计算需要的片段数量
num_segments = math.ceil(target_duration / orig_duration)
print(f"需要拼接 {num_segments} 个片段")
# 构建FFmpeg滤镜
filters = []
# 1. 缩放视频
filters.append(
f"[0:v]scale={scaled_width}:{scaled_height}:"
"force_original_aspect_ratio=decrease:force_divisible_by=2,"
"setsar=1[scaled];"
)
# 2. 拆分视频流
if num_segments > 1:
filters.append(f"[scaled]split={num_segments}")
for i in range(num_segments):
filters.append(f"[v{i}]")
filters.append(";")
# 3. 处理每个片段
for i in range(num_segments):
segment_duration = min(orig_duration, target_duration - i * orig_duration)
pos_idx = i % len(positions)
x, y = positions[pos_idx]
flip = "hflip," if i % 2 == 1 else ""
if num_segments > 1:
source = f"[v{i}]"
else:
source = "[scaled]"
filters.append(
f"{source}trim=0:{segment_duration},setpts=PTS-STARTPTS,"
f"crop={target_width}:{target_crop_height}:{x}:{y},"
f"{flip}scale={target_width}:{target_crop_height}:force_divisible_by=2[seg{i}];"
)
# 4. 拼接所有片段
if num_segments > 1:
concat_inputs = "".join(f"[seg{i}]" for i in range(num_segments))
filters.append(f"{concat_inputs}concat=n={num_segments}:v=1[video];")
else:
filters.append("[seg0]copy[video];")
# 5. 创建上下黑色填充
filters.append(
f"color=c=black:s={target_width}x{top_padding}[top];"
f"color=c=black:s={target_width}x{bottom_padding}[bottom];"
)
# 6. 合并所有层
filters.append(
f"[top][video][bottom]vstack=inputs=3[outv];"
)
# 7. 添加静音音频流
filters.append(f"aevalsrc=0:d={target_duration}[silent];")
filter_str = "".join(filters)
print("\n生成的FFmpeg滤镜:")
print(filter_str.replace(";", ";\n"))
# 构建FFmpeg命令
command = [
ffmpeg_path,
"-i", input_path,
"-filter_complex", filter_str,
"-map", "[outv]",
"-map", "[silent]",
"-c:v", "libx264",
"-preset", "fast",
"-crf", "23",
"-c:a", "aac",
"-b:a", "128k",
"-t", str(target_duration),
"-y",
output_path
]
print("\n执行的FFmpeg命令:")
print(" ".join(command))
# 执行命令并捕获输出
result = subprocess.run(
command,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
creationflags=subprocess.CREATE_NO_WINDOW
)
if result.returncode != 0:
raise ValueError(f"FFmpeg处理失败:\n{result.stderr}")
# 检查输出文件是否存在
if not os.path.exists(output_path):
raise ValueError("输出文件未生成")
print(f"✅ 成功生成: {output_path}")
print(f"文件大小: {os.path.getsize(output_path) / 1024 / 1024:.2f}MB")
return True
except Exception as e:
print(f"❌ 处理失败 {filename}: {str(e)}")
return False
if __name__ == "__main__":
# 获取所有视频文件
video_files = []
try:
for file in os.listdir(input_dir):
if file.lower().endswith(('.mp4', '.mov', '.avi', '.mkv', '.flv', '.wmv', '.mpg', '.mpeg', '.webm')):
video_files.append(os.path.join(input_dir, file))
except FileNotFoundError:
print(f"❌ 输入目录不存在: {input_dir}")
exit(1)
print(f" 发现 {len(video_files)} 个视频文件")
# 使用线程池并行处理
success_count = 0
with ThreadPoolExecutor(max_workers=1) as executor: # 暂时改为单线程以便调试
results = executor.map(process_video, video_files)
success_count = sum(1 for result in results if result)
print(f" 批量处理完成!成功: {success_count}/{len(video_files)}")