2025-06-11[批量裁剪素材视频]

2025-06-11[批量裁剪素材视频]_第1张图片

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)}")

你可能感兴趣的:(09_FFmepg库,音视频)