惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

D
Docker
爱范儿
爱范儿
T
The Exploit Database - CXSecurity.com
量子位
T
Tailwind CSS Blog
T
Threatpost
The GitHub Blog
The GitHub Blog
AWS News Blog
AWS News Blog
云风的 BLOG
云风的 BLOG
K
Kaspersky official blog
P
Proofpoint News Feed
博客园 - 司徒正美
L
LangChain Blog
T
Threat Research - Cisco Blogs
C
CERT Recently Published Vulnerability Notes
罗磊的独立博客
酷 壳 – CoolShell
酷 壳 – CoolShell
博客园 - 叶小钗
S
Secure Thoughts
The Last Watchdog
The Last Watchdog
Spread Privacy
Spread Privacy
H
Hacker News: Front Page
T
Troy Hunt's Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Google DeepMind News
Google DeepMind News
W
WeLiveSecurity
A
Arctic Wolf
Apple Machine Learning Research
Apple Machine Learning Research
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
P
Proofpoint News Feed
T
Tor Project blog
T
The Blog of Author Tim Ferriss
I
Intezer
P
Privacy & Cybersecurity Law Blog
美团技术团队
N
Netflix TechBlog - Medium
博客园_首页
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
V
Vulnerabilities – Threatpost
Application and Cybersecurity Blog
Application and Cybersecurity Blog
G
Google Developers Blog
Attack and Defense Labs
Attack and Defense Labs
T
Tenable Blog
月光博客
月光博客
Stack Overflow Blog
Stack Overflow Blog
J
Java Code Geeks
腾讯CDC
Microsoft Security Blog
Microsoft Security Blog
A
About on SuperTechFans
Last Week in AI
Last Week in AI

博客园 - Nihaorz

告别闪烁,拥抱流畅:在 Windows Terminal 中完美配置 Cygwin 环境 Nginx 透明代理 + 自动回源存储:访问即缓存,落盘即文件 创建 docker ipvlan,让 docke 容器获取独立ip 电犀牛 R68s iStoreOS 2.5G 网口速率优化 解决 openwrt ssh 命令行终端 home、end 键不可用问题 ffmpeg 转码参数 docker save 远程 ssh 主机直接 load,不产生本地文件 AutoHotKey 脚本 - win10 自动连接无线显示器 SSH 登录/退出实时监控脚本 OpenClaw 安装部署,配置 deepseek curl 断点续传下载 debian iso 镜像下载地址 linux 安装 zerotier,加入网络 基于 Fail2ban 的 SSH 入侵自动反制方案 ssh 配置密钥登录,关闭密码登录 memc - 基于 shell 的交互式清理内存脚本 基于 Fail2ban 的 OpenWRT SSH 入侵自动反制方案 Linux Screen 命令速查 使用 ofelia 在 docker 容器中执行计划任务 linux 磁盘挂载示例
一键添加视频封面脚本
Nihaorz · 2026-03-19 · via 博客园 - Nihaorz

脚本内容:

#!/bin/bash

# 一键添加封面脚本 - 使用视频第一帧作为封面
# 支持: 单个文件、目录、递归子目录、通配符(* ?)

SCRIPT_NAME=$(basename "$0")
TARGETS=()
RECURSIVE=0
DRY_RUN=0

# 显示帮助信息
show_help() {
    cat << EOF
========================================
MP4 添加封面工具 - 使用视频第一帧作为封面
========================================

用法:
    bash ${SCRIPT_NAME} [选项] <文件或目录...>

选项:
    -h, --help          显示本帮助信息
    -r, --recursive     递归处理子目录
    -d, --dry-run       试运行模式(只显示要处理的文件,不执行)

参数:
    <文件或目录>        要处理的MP4文件、目录,或通配符模式
                        支持多个参数,支持通配符(* ?)
                        不传参数时显示此帮助信息

示例:
    # 处理单个文件
    bash ${SCRIPT_NAME} video.mp4

    # 处理多个文件
    bash ${SCRIPT_NAME} video1.mp4 video2.mp4 video3.mp4

    # 使用通配符处理多个文件
    bash ${SCRIPT_NAME} *.mp4
    bash ${SCRIPT_NAME} video*.mp4
    bash ${SCRIPT_NAME} ????.mp4

    # 处理当前目录下所有mp4(不递归子目录)
    bash ${SCRIPT_NAME} .

    # 处理指定目录下所有mp4(递归子目录)
    bash ${SCRIPT_NAME} -r /e/ff_output_mp4

    # 试运行模式(查看会处理哪些文件)
    bash ${SCRIPT_NAME} -d *.mp4

执行逻辑:
    1. 检查输入参数是否存在
    2. 如果是文件: 直接处理该文件
    3. 如果是目录: 查找该目录下所有mp4文件
       - 默认只处理当前目录,不进入子目录
       - 加 -r 参数会递归处理所有子目录
    4. 如果是通配符: 展开后处理匹配的文件
    5. 对每个mp4文件:
       a. 提取视频第一帧作为临时封面图片
       b. 将封面嵌入mp4文件(不重新编码视频)
       c. 替换原文件,清理临时文件
    6. 输出处理统计报告

注意事项:
    - 原文件会被覆盖,请提前备份重要文件
    - 处理后的文件会保留原有的faststart特性
    - 封面图片使用mjpeg格式,质量较高
    - 失败时会保留原文件,不会损坏数据

========================================
EOF
}

# 解析参数
while [[ $# -gt 0 ]]; do
    case $1 in
        -h|--help)
            show_help
            exit 0
            ;;
        -r|--recursive)
            RECURSIVE=1
            shift
            ;;
        -d|--dry-run)
            DRY_RUN=1
            shift
            ;;
        -*)
            echo "错误: 未知选项 $1"
            echo "使用 -h 或 --help 查看帮助信息"
            exit 1
            ;;
        *)
            # 收集所有非选项参数(支持通配符展开)
            TARGETS+=("$1")
            shift
            ;;
    esac
done

# 检查是否提供了目标参数
if [ ${#TARGETS[@]} -eq 0 ]; then
    echo "错误: 未指定文件或目录"
    echo ""
    show_help
    exit 1
fi

# 收集所有要处理的文件
FILES=()

# 处理每个目标参数
for target in "${TARGETS[@]}"; do
    # 检查目标是否存在
    if [ ! -e "$target" ]; then
        echo "警告: 路径不存在,跳过: $target"
        continue
    fi

    if [ -f "$target" ]; then
        # 单个文件
        if [[ "$target" == *.mp4 ]] || [[ "$target" == *.MP4 ]]; then
            FILES+=("$target")
        else
            echo "警告: 不是MP4文件,跳过: $target"
        fi
    elif [ -d "$target" ]; then
        # 目录
        if [ $RECURSIVE -eq 1 ]; then
            # 递归查找
            while IFS= read -r -d '' file; do
                FILES+=("$file")
            done < <(find "$target" -type f \( -name "*.mp4" -o -name "*.MP4" \) -print0 2>/dev/null)
        else
            # 非递归,仅当前目录
            for file in "$target"/*.mp4 "$target"/*.MP4; do
                [ -e "$file" ] && FILES+=("$file")
            done
        fi
    fi
done

# 去重(防止通配符和显式参数重复)
UNIQUE_FILES=()
for file in "${FILES[@]}"; do
    is_duplicate=0
    for unique in "${UNIQUE_FILES[@]}"; do
        if [ "$file" = "$unique" ]; then
            is_duplicate=1
            break
        fi
    done
    if [ $is_duplicate -eq 0 ]; then
        UNIQUE_FILES+=("$file")
    fi
done
FILES=("${UNIQUE_FILES[@]}")

# 检查是否找到文件
TOTAL=${#FILES[@]}
if [ $TOTAL -eq 0 ]; then
    echo "错误: 未找到有效的MP4文件"
    exit 1
fi

# 显示执行信息
echo "========================================"
echo "MP4 添加封面工具"
echo "========================================"
echo "找到: $TOTAL 个MP4文件"
if [ $DRY_RUN -eq 1 ]; then
    echo "模式: 试运行(不实际执行)"
fi
echo "========================================"
echo ""

# 试运行模式:只显示列表
if [ $DRY_RUN -eq 1 ]; then
    echo "【试运行模式】将要处理的文件:"
    for i in "${!FILES[@]}"; do
        idx=$((i + 1))
        echo "  [$idx] ${FILES[$i]}"
    done
    echo ""
    echo "试运行完成,未实际处理文件"
    echo "去掉 -d 参数即可正式执行"
    exit 0
fi

# 处理文件
SUCCESS=0
FAILED=0
SKIPPED=0

for i in "${!FILES[@]}"; do
    idx=$((i + 1))
    file="${FILES[$i]}"
    filename=$(basename "$file")

    echo "[$idx/$TOTAL] 处理: $filename"

    # 检查文件是否可写
    if [ ! -w "$file" ]; then
        echo "    ✗ 文件不可写,跳过"
        SKIPPED=$((SKIPPED + 1))
        continue
    fi

    # 创建临时封面路径(与原文件同目录)
    cover_jpg="${file}.cover.tmp.jpg"
    temp_output="${file}.tmp.mp4"

    # 步骤1: 提取第一帧
    ffmpeg -i "$file" -ss 00:00:00.1 -vframes 1 -q:v 2 -y "$cover_jpg" 2>/dev/null

    if [ ! -f "$cover_jpg" ]; then
        echo "    ✗ 提取封面失败"
        FAILED=$((FAILED + 1))
        continue
    fi

    # 步骤2: 嵌入封面
    ffmpeg -i "$file" -i "$cover_jpg" -map 0 -map 1 -c copy -disposition:v:1 attached_pic -movflags +faststart -y "$temp_output" 2>/dev/null

    if [ $? -eq 0 ] && [ -f "$temp_output" ]; then
        # 获取文件大小
        orig_size=$(ls -lh "$file" 2>/dev/null | awk '{print $5}')
        new_size=$(ls -lh "$temp_output" 2>/dev/null | awk '{print $5}')

        # 替换原文件
        mv "$temp_output" "$file"
        rm -f "$cover_jpg"

        echo "    ✓ 成功 (${orig_size} → ${new_size})"
        SUCCESS=$((SUCCESS + 1))
    else
        echo "    ✗ 嵌入封面失败"
        rm -f "$temp_output" "$cover_jpg"
        FAILED=$((FAILED + 1))
    fi
done

# 输出统计
echo ""
echo "========================================"
echo "处理完成"
echo "========================================"
echo "总计:   $TOTAL 个文件"
echo "成功:   $SUCCESS 个"
echo "失败:   $FAILED 个"
echo "跳过:   $SKIPPED 个"
echo "========================================"

if [ $FAILED -gt 0 ]; then
    exit 1
else
    exit 0
fi