补充说明
该脚本基于 Intel QSV(Quick Sync Video)硬件加速,将视频转码为 HEVC/H.265 并写入 hvc1 标签。特别适配 SR‑IOV VF 虚拟 GPU 环境(CPU 侧解码 + hevc_qsv 硬件编码,VBR 码率模式),并支持 setsid 后台静默执行,兼容 SSH 断线 / curl 管道调用。
提供「交互式 / curl 调用 / 目录批量 / 单文件」四种模式:交互模式列出当前目录 mp4 / mkv / mov / avi 视频,可选单个或输入 666 批量全部;命令行传入目录即目录批量、传入文件即单文件转码,均可指定输出目录、后缀开关与前后台开关。
脚本逐个串行转码,避免多个 ffmpeg 同时抢占 QSV 硬件队列导致花屏/卡死;输出文件已存在时自动跳过,可安全断点续跑。
一键脚本
bash <(curl -sL gitee.com/meimolihan/cmdbox/raw/master/sh/ffmpeg_video_hvc1.sh)
bash <(curl -sL ../sh/ffmpeg_video_hvc1.sh)
交互模式示例(先 cd 到视频目录再执行):
cd /vol2/1000/media/电影/我的片单
bash ffmpeg_video_hvc1.sh
命令行模式示例:
# 单个视频转码(默认输出到源文件同目录、添加后缀、后台运行)
bash ffmpeg_video_hvc1.sh "/vol2/1000/media/电影/示例.mp4"
# 单个视频:指定输出目录、使用原文件名(不加后缀)、前台运行
bash ffmpeg_video_hvc1.sh "/vol2/1000/media/电影/示例.mp4" "/vol2/1000/media/转码输出" false false
# 目录批量:源目录 → 输出目录,添加后缀、后台运行
bash ffmpeg_video_hvc1.sh "/vol2/1000/media/电影/我的片单" "/vol2/1000/media/转码输出" true true
强制停止转码
# 杀掉所有 ffmpeg 进程
pkill -9 -f ffmpeg && echo "已杀干净" || echo "没有找到 ffmpeg 进程"
# 终止全部hevc_qsv转码进程
pkill -f "hevc_qsv"
# 释放任务锁文件(异常强杀后使用)
rm -f /tmp/ffmpeg_batch_encode.lock
手动安装 ffmpeg
脚本 install_deps 会自动检测 ffmpeg 与 hevc_qsv 编码器并可选校验 vainfo;缺失时按发行版自动安装(apt/dnf/yum/pacman/zypper/apk/brew,不依赖 sudo)。也可手动安装:
# Debian/Ubuntu
sudo apt-get update && sudo apt-get install -y ffmpeg vainfo
# CentOS/RHEL
sudo yum install -y epel-release && sudo yum install -y ffmpeg vainfo
# Arch Linux
sudo pacman -S --noconfirm ffmpeg vainfo
# Alpine
sudo apk add ffmpeg vainfo
# macOS
brew install ffmpeg
功能特点
- Intel QSV 硬件编码(hevc_qsv)+ CPU 解码,适配 SR‑IOV VF 虚拟 GPU 环境
- VBR 码率模式:
-b:v 2600k -maxrate 5200k -bufsize 10400k(顶部配置区可调) - 逐个串行转码,不并发抢占 QSV 硬件队列,稳定性更好
- 支持 mp4、mkv、mov、avi 常见格式(不区分大小写)
- 交互模式:列出当前目录所有视频,可选择单个文件或输入
666批量全部 - 命令行模式:
源文件/源目录 [输出目录] [后缀开关] [后台开关],自动区分单文件 / 目录批量 - 后台运行:交互输入
y或命令行$4=true后通过 setsid 脱离终端静默转码,兼容 SSH 断线 / curl 调用 - 命名控制:输出到当前目录时强制加后缀,输出到其他目录可选原文件名
- 输出已存在自动跳过,断点中断后可继续补转
- PID 文件锁防重复启动(
/tmp/ffmpeg_batch_encode.lock),锁残留自动清理 - 检测到 CIFS/NFS/SMB 网络文件系统时提示 IO 等待可能升高
- 每个文件独立日志 + 后台
batch_main.log,转码进度一目了然 - 自检 hevc_qsv 编码器是否可用(
ffmpeg -h encoder=hevc_qsv)与 vainfo VA‑API 环境
参数说明
| 模式 | 参数 | 说明 | 默认值 |
|---|---|---|---|
| 交互 | 序号 | 选择单个视频转码 | — |
| 交互 | 666 |
批量转码所有视频 | — |
| 交互 | 保存路径 | 转码输出目录,回车等于当前目录 | 当前目录 |
| 交互 | 命名方式 1/2 |
原文件名 / 添加默认后缀 | 2 |
| 交互 | 后台运行 y/N |
是否 setsid 后台转码 | 否 |
| 命令行 | $1 |
源视频路径或源目录(必填,自动识别) | — |
| 命令行 | $2 |
输出目录(可选) | 源文件同目录 / 源目录同级 |
| 命令行 | $3 |
后缀开关 true/false |
true |
| 命令行 | $4 |
后台开关 true/false |
true |
输出命名与日志
| 项目 | 规则 |
|---|---|
| 添加后缀 | 名称_HEVC-hvc1.扩展名 |
| 原文件名 | 名称.扩展名 |
| 单文件日志 | 输出目录/名称.log |
| 后台总日志 | 输出目录/batch_main.log |
转码命令详解
脚本底层实际执行的 ffmpeg 转码命令(参数来自顶部「可配置参数区」):
ffmpeg -threads auto -probesize 32M -avioflags direct \
-i "input.mkv" -y \
-c:v hevc_qsv -preset fast -b:v 2600k -maxrate 5200k -bufsize 10400k -tag:v hvc1 \
-c:a copy "output_HEVC-hvc1.mp4"
各参数说明:
| 参数 | 作用 |
|---|---|
-threads auto |
输入线程数自动(FFMPEG_THREADS 配置区可调) |
-probesize 32M |
加大探测缓冲,提高流信息识别成功率 |
-avioflags direct |
直读 IO,减少缓冲开销 |
-c:v hevc_qsv |
使用 QSV 硬件 HEVC/H.265 编码器 |
-preset fast |
编码速度预设(越慢画质越好) |
-b:v 2600k |
目标视频码率 |
-maxrate 5200k |
峰值码率上限(两倍目标) |
-bufsize 10400k |
VBV 缓冲大小(两倍 maxrate) |
-tag:v hvc1 |
写入 hvc1 标签,Apple/主流播放器兼容性最好 |
-c:a copy |
音频流直接拷贝,不重新编码,保证音质与速度 |
该版本默认不启用
-movflags +faststart,与目标 ffmpeg 参考命令严格对齐(如需网页秒开可在配置区将MOVFLAGS_FASTSTART=true);亦不显式指定-qsv_device,由 QSV 驱动自动选择设备,因此可正常跑在 SR‑IOV VF 虚拟 GPU 上。
QSV 支持自检
# 确认系统已识别 Intel 显卡
lspci | grep -i vga
# 确认 ffmpeg 包含 hevc_qsv 编码器
ffmpeg -hide_banner -h encoder=hevc_qsv
若为无 QSV 硬件的纯软件环境,可去掉
-c:v hevc_qsv,改用-c:v libx265 -preset fast -crf 23保底转码。
注意事项
- 需要 Intel CPU 内置核显(支持 Quick Sync)且系统已安装 Media SDK / intel-media-driver,否则 QSV 无法使用
- 适配 SR‑IOV VF 虚拟 GPU:脚本不指定
-qsv_device,由驱动自动选择设备节点 - 转码是串行任务,批量时请排队执行,避免多进程抢占 QSV 导致转码花屏
- 输出目录为当前目录时强制添加后缀,防止覆盖源文件
- 目标文件已存在会自动跳过,重复执行不会重复转码
- 默认关闭
-movflags +faststart(与参考命令对齐),如需网页秒开请在配置区打开 - 音频默认
copy拷贝,仅转换视频轨,不重编码音频 hvc1标签对 Apple 及主流盒子播放器兼容性最好;个别 Android 播放器可能需要hev1标签- 后台运行使用 setsid 完全脱离 SSH 会话,建议通过
tail -f 输出目录/batch_main.log查看进度 - PID 锁文件
/tmp/ffmpeg_batch_encode.lock:正常退出/中断自动清理,异常强杀后可手动删除 - 网络文件系统(CIFS/NFS/SMB)上运行会自动提示 IO 等待可能升高
- 脚本以 bash 编写,确保运行环境支持 bash
脚本源码
#!/bin/bash
# ==============================================================================
# HEVC hvc1 QSV批量转码脚本|setsid后台静默执行
# 适配SR‑IOV VF虚拟GPU|CPU解码 + hevc_qsv硬件编码 VBR模式
# 模式:交互式 / curl调用 / 目录批量 / 单文件
# ==============================================================================
set -uo pipefail
########################### 【可配置参数区】###########################
LOCKFILE="/tmp/ffmpeg_batch_encode.lock"
# QSV编码参数【已对齐目标ffmpeg命令】
QSV_PRESET="fast"
QSV_BV="2600k"
QSV_MAXRATE="5200k"
QSV_BUFSIZE="10400k"
# mp4 faststart:已禁用,匹配参考命令无‑movflags +faststart
MOVFLAGS_FASTSTART=false
# ffmpeg输入参数
FFMPEG_THREADS="auto"
PROBESIZE="32M"
AVIOFLAGS="direct"
######################################################################
list_color_init() {
export gl_hui=$'\033[38;5;59m'
export gl_hong=$'\033[38;5;9m'
export gl_lv=$'\033[38;5;10m'
export gl_huang=$'\033[38;5;11m'
export gl_lan=$'\033[38;5;32m'
export gl_bai=$'\033[38;5;15m'
export gl_zi=$'\033[38;5;13m'
export gl_bufan=$'\033[38;5;14m'
}
list_color_init
break_end() {
echo -e "${gl_lv}操作完成${gl_bai}"
echo -e "${gl_bai}按任意键继续 ${gl_hong}.${gl_huang}.${gl_lv}.${gl_bai}\c"
read -r -n 1 -s -r -p ""
echo ""
clear
}
abspath() {
local p="$1"
if command -v realpath &>/dev/null; then
realpath "$p" 2>/dev/null || readlink -f "$p" 2>/dev/null || echo "$p"
else
case "$p" in
/*) echo "$p" ;;
*) echo "$PWD/$p" ;;
esac
fi
}
lock_acquire() {
if [[ -f "${LOCKFILE}" ]]; then
local oldpid
oldpid=$(<"${LOCKFILE}")
if [[ -n "${oldpid}" ]] && kill -0 "${oldpid}" 2>/dev/null; then
echo -e "\033[38;5;9m⚠️ 检测到已有转码任务(PID:${oldpid})运行,拒绝重复启动\033[0m"
exit 1
else
echo -e "${gl_huang}⚠️ 锁文件残留,进程已死亡,自动清理旧锁${gl_bai}"
rm -f "${LOCKFILE}"
fi
fi
trap 'rm -f "${LOCKFILE}"' EXIT INT TERM HUP QUIT
echo "$$" > "${LOCKFILE}"
}
install_deps() {
echo -e "${gl_zi}>>> 检查依赖${gl_bai}"
if command -v ffmpeg &>/dev/null; then
echo -e "${gl_lv}ffmpeg 已安装: $(command -v ffmpeg)${gl_bai}"
if ffmpeg -h encoder=hevc_qsv >/dev/null 2>&1; then
echo -e "${gl_lv}hevc_qsv 硬件编码器 ✅${gl_bai}"
if command -v vainfo &>/dev/null; then
if vainfo >/dev/null 2>&1; then
echo -e "${gl_lv}VA‑API硬件环境正常 ✅${gl_bai}"
else
echo -e "${gl_huang}⚠ vainfo检测异常:VA‑API驱动/权限异常,QSV可能失效${gl_bai}"
fi
else
echo -e "${gl_huang}ℹ 未安装vainfo,跳过硬件校验${gl_bai}"
fi
else
echo -e "${gl_hong}❌ ffmpeg不支持hevc_qsv,硬件转码不可用${gl_bai}"
exit 1
fi
return 0
fi
echo -e "${gl_huang}ffmpeg未找到,尝试自动安装${gl_bai}"
if command -v apt &>/dev/null; then
apt update && apt install -y ffmpeg vainfo
elif command -v dnf &>/dev/null; then
dnf install -y ffmpeg vainfo
elif command -v yum &>/dev/null; then
yum install -y epel-release && yum install -y ffmpeg vainfo
elif command -v pacman &>/dev/null; then
pacman -S --noconfirm ffmpeg vainfo
elif command -v zypper &>/dev/null; then
zypper install -y ffmpeg vainfo
elif command -v apk &>/dev/null; then
apk add ffmpeg vainfo
elif command -v brew &>/dev/null; then
brew install ffmpeg
else
echo -e "${gl_hong}无法自动安装ffmpeg,请手动安装后重试${gl_bai}"
exit 1
fi
if ! command -v ffmpeg &>/dev/null; then
echo -e "${gl_hong}ffmpeg安装失败${gl_bai}"
exit 1
fi
echo -e "${gl_lv}ffmpeg安装成功${gl_bai}"
return 0
}
scan_videos() {
local dir="$1"
local exts=(mp4 mkv mov avi)
local find_args=()
for ext in "${exts[@]}"; do
find_args+=(-iname "*.${ext}")
find_args+=(-o)
done
if (( ${#find_args[@]} > 0 )); then
unset 'find_args[${#find_args[@]}-1]'
fi
find "$dir" -maxdepth 1 -type f \( "${find_args[@]}" \) -print0 2>/dev/null | sort -z
}
do_encode() {
local src="$1"
local dst="$2"
local log="$3"
local fname
fname=$(basename "$src")
mkdir -p "$(dirname "$log")" 2>/dev/null
# 前置校验
if [[ ! -s "${src}" ]]; then
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ✘ 源文件为空或不存在: ${src}" | tee -a "$log"
return 1
fi
if [[ ! -w "$(dirname "$dst")" ]]; then
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ✘ 输出目录无写入权限 $(dirname "$dst")" | tee -a "$log"
return 1
fi
echo "------------------------------------------------------------" >> "$log"
echo "[$(date '+%Y-%m-%d %H:%M:%S')] >>> 开始转码: ${fname}" | tee -a "$log"
echo "[$(date '+%Y-%m-%d %H:%M:%S')] 输入: ${src}" >> "$log"
echo "[$(date '+%Y-%m-%d %H:%M:%S')] 输出: ${dst}" >> "$log"
# 组装ffmpeg参数|严格对齐目标命令:无movflags faststart,音频 c:a copy
local ffmpeg_cmd=(
ffmpeg
-threads "${FFMPEG_THREADS}"
-probesize "${PROBESIZE}"
-avioflags "${AVIOFLAGS}"
-i "$src"
-y
-c:v hevc_qsv
-preset "${QSV_PRESET}"
-b:v "${QSV_BV}"
-maxrate "${QSV_MAXRATE}"
-bufsize "${QSV_BUFSIZE}"
-tag:v hvc1
)
ffmpeg_cmd+=(-c:a copy "$dst")
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ffmpeg_cmd: ${ffmpeg_cmd[*]}" >> "$log"
"${ffmpeg_cmd[@]}" >> "$log" 2>&1
local ret=$?
if [[ $ret -eq 0 ]]; then
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ✔ 完成: ${dst}" | tee -a "$log"
else
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ✘ 失败: ${src} (exit=${ret})" | tee -a "$log"
fi
return "${ret}"
}
run_queue() {
local out_dir="$1"
local use_suffix="$2"
shift 2
local files=("$@")
local ok=0 fail=0
local total=${#files[@]}
if mount | grep -qE "cifs|nfs|smb"; then
echo -e "${gl_huang}⚠ 检测到网络文件系统,IO等待wa可能升高${gl_bai}"
fi
for ((i=0; i<total; i++)); do
local src="${files[$i]}"
local fname=$(basename "$src")
local name="${fname%.*}"
local ext="${fname##*.}"
local dst
if [[ "$use_suffix" == "true" ]]; then
dst="${out_dir}/${name}_HEVC-hvc1.${ext}"
else
dst="${out_dir}/${fname}"
fi
local log="${out_dir}/${name}.log"
echo -e "${gl_bufan}[$((i+1))/${total}]${gl_bai} ${fname}"
echo -e "${gl_hui}------------------------------------------------------------${gl_bai}"
if [[ -f "$dst" ]]; then
echo -e "${gl_huang}跳过(已存在): ${dst}${gl_bai}"
((ok++))
continue
fi
if do_encode "$src" "$dst" "$log"; then
((ok++))
else
((fail++))
fi
done
echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
echo -e "${gl_hui}转码结束:成功 ${gl_lv}${ok}${gl_hui} / 失败 ${gl_hong}${fail}${gl_hui} / 总计 ${total}${gl_bai}"
return 0
}
launch_bg() {
local out_dir="$1"
local use_suffix="$2"
local tmpfiles="$3"
env \
"PATH=$PATH" \
"LD_LIBRARY_PATH=${LD_LIBRARY_PATH:-}" \
"LIBVA_DRIVERS_PATH=${LIBVA_DRIVERS_PATH:-}" \
"LIBVA_DRIVER_NAME=${LIBVA_DRIVER_NAME:-}" \
"LOCKFILE=${LOCKFILE}" \
"QSV_PRESET=${QSV_PRESET}" \
"QSV_BV=${QSV_BV}" \
"QSV_MAXRATE=${QSV_MAXRATE}" \
"QSV_BUFSIZE=${QSV_BUFSIZE}" \
"MOVFLAGS_FASTSTART=${MOVFLAGS_FASTSTART}" \
"FFMPEG_THREADS=${FFMPEG_THREADS}" \
"PROBESIZE=${PROBESIZE}" \
"AVIOFLAGS=${AVIOFLAGS}" \
setsid bash -c "
out_dir='${out_dir}'
use_suffix='${use_suffix}'
tmpfiles='${tmpfiles}'
files_list=()
while IFS= read -r -d '' f; do
[[ -n \"\$f\" ]] && files_list+=(\"\$f\")
done < \"\$tmpfiles\"
$(declare -f list_color_init abspath do_encode run_queue)
list_color_init
run_queue \"\$out_dir\" \"\$use_suffix\" \"\${files_list[@]}\"
rm -f \"\$tmpfiles\"
" > "${out_dir}/batch_main.log" 2>&1 &
disown
echo "$!"
}
interactive_mode() {
clear
echo -e "${gl_zi}>>> 交互式 HEVC hvc1 QSV转码${gl_bai}"
echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
echo -e "${gl_huang}当前目录: ${gl_lv}${PWD}${gl_bai}"
echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
echo -e ""
echo -e "${gl_hui}>>> 视频文件列表${gl_bai}"
echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
local files=()
while IFS= read -r -d '' f; do
[[ -n "$f" ]] && files+=("$f")
done < <(scan_videos "$PWD")
local cnt=${#files[@]}
if (( cnt == 0 )); then
echo -e "${gl_hong}>>> [WARN] 当前目录未找到视频文件${gl_bai}"
break_end
return 1
fi
for ((i=0; i<cnt; i++)); do
printf "${gl_bufan}%${#cnt}d.${gl_bai} %s\n" "$((i+1))" "$(basename "${files[$i]}")"
done
echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
echo -e "${gl_huang}666.${gl_bai} 批量转码全部视频"
echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
read -r -e -p "$(echo -e "${gl_bai}请输入序号选择单个视频,或输入 ${gl_huang}666${gl_bai} 处理全部: ${gl_bai}")" input
local selected=()
if [[ "$input" == "666" ]]; then
selected=("${files[@]}")
elif [[ "$input" =~ ^[0-9]+$ ]]; then
local idx=$((input - 1))
if (( idx >= 0 && idx < cnt )); then
selected=("${files[$idx]}")
else
echo -e "${gl_hong}序号超出范围${gl_bai}"
return 1
fi
else
echo -e "${gl_hong}无效输入${gl_bai}"
return 1
fi
read -r -e -p "$(echo -e "${gl_bai}转码后保存路径(${gl_huang}回车${gl_lv}=${gl_huang}当前目录${gl_bai}): ${gl_bai}")" out_dir
out_dir="${out_dir:-$PWD}"
mkdir -p "$out_dir" || { echo -e "${gl_hong}无法创建目录${gl_bai}"; return 1; }
local use_suffix="true"
local abs_src abs_out
abs_src=$(abspath "$PWD")
abs_out=$(abspath "$out_dir")
if [[ "$abs_src" == "$abs_out" ]]; then
use_suffix="true"
echo -e "${gl_huang}输出到当前目录,强制添加后缀${gl_bai}"
else
echo -e ""
echo -e "${gl_hui}命名方式:${gl_bai}"
echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
echo -e " ${gl_bufan}1. ${gl_bai}使用原视频文件名"
echo -e " ${gl_bufan}2. ${gl_bai}添加后缀 (_HEVC-hvc1)"
echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
read -r -e -p "$(echo -e "${gl_bai}请输入你的选择 [${gl_huang}1${gl_bai}/${gl_lv}2${gl_bai}],默认 ${gl_lv}2${gl_bai}: ")" naming
naming="${naming:-2}"
[[ "$naming" == "1" ]] && use_suffix="false" || use_suffix="true"
fi
local bg_choice
read -r -e -p "$(echo -e "${gl_bai}是否在后台运行?(${gl_lv}y${gl_bai}/${gl_hong}N${gl_bai}): ${gl_bai}")" bg_choice
if [[ "$bg_choice" =~ ^[Yy]$ ]]; then
echo -e "${gl_lv}正在后台启动 ${gl_hong}.${gl_huang}.${gl_lv}.${gl_bai}"
echo -e "${gl_hui}日志将保存在: ${gl_lan}${out_dir}${gl_bai}"
local tmpfiles
tmpfiles=$(mktemp /tmp/encode_XXXXXX.files)
printf '%s\0' "${selected[@]}" > "$tmpfiles"
local pid
pid=$(launch_bg "$out_dir" "$use_suffix" "$tmpfiles")
echo -e ""
echo -e "${gl_lv}后台任务已启动 (PID: ${pid})${gl_bai}"
echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
echo -e "${gl_hui}查看总日志: tail -f ${out_dir}/batch_main.log${gl_bai}"
echo -e "${gl_hui}查看单文件: tail -f ${out_dir}/<视频名>.log${gl_bai}"
echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
return 0
fi
run_queue "$out_dir" "$use_suffix" "${selected[@]}"
break_end
}
cli_mode() {
local src="$1"
local out_dir="${2:-$(dirname "$src")}"
local use_suffix_str="${3:-true}"
local run_bg_str="${4:-true}"
local use_suffix="true"
[[ "$use_suffix_str" == "false" ]] && use_suffix="false"
local run_bg="true"
[[ "$run_bg_str" == "false" ]] && run_bg="false"
mkdir -p "$out_dir" || return 1
if [[ -d "$src" ]]; then
clear
echo -e "${gl_zi}>>> 命令行目录批量模式 QSV‑hvc1${gl_bai}"
echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
echo -e "${gl_hui}源 目 录: ${gl_bai}${src}"
echo -e "${gl_hui}输出目录: ${gl_bai}${out_dir}"
echo -e "${gl_hui}后台运行: ${gl_bai}${run_bg}"
echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
local files=()
while IFS= read -r -d '' f; do
[[ -n "$f" ]] && files+=("$f")
done < <(scan_videos "$src")
local cnt=${#files[@]}
if (( cnt == 0 )); then
echo -e "${gl_hong}错误:目录内未找到支持的视频文件${gl_bai}"
return 1
fi
echo -e "${gl_lv}找到 ${gl_huang}${cnt}${gl_lv} 个待处理视频${gl_bai}"
if [[ "$run_bg" == "true" ]]; then
local tmpfiles
tmpfiles=$(mktemp /tmp/encode_XXXXXX.files)
printf '%s\0' "${files[@]}" > "$tmpfiles"
local pid
pid=$(launch_bg "$out_dir" "$use_suffix" "$tmpfiles")
echo -e "${gl_lv}✅ 后台任务已启动 (PID: ${pid})${gl_bai}"
echo -e "${gl_hui}总 日 志:tail -f ${out_dir}/batch_main.log${gl_bai}"
echo -e "${gl_hui}强制停止:pkill -9 -f ffmpeg; rm -f ${LOCKFILE}"
return 0
fi
run_queue "$out_dir" "$use_suffix" "${files[@]}"
return $?
fi
if [[ ! -f "$src" ]]; then
echo -e "${gl_hong}错误:文件不存在: ${src}${gl_bai}"
return 1
fi
local fname=$(basename "$src")
local name="${fname%.*}"
local ext="${fname##*.}"
local dst
if [[ "$use_suffix" == "true" ]]; then
dst="${out_dir}/${name}_HEVC-hvc1.${ext}"
else
dst="${out_dir}/${fname}"
fi
local log="${out_dir}/${name}.log"
echo -e ""
echo -e "${gl_zi}>>> 命令行单文件转码 QSV‑hvc1${gl_bai}"
echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
echo -e "${gl_hui}输入: ${gl_bai}${src}"
echo -e "${gl_hui}输出: ${gl_bai}${dst}"
echo -e "${gl_hui}日志: ${gl_bai}${log}"
echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
if [[ -f "$dst" ]]; then
echo -e "${gl_huang}跳过(已存在): ${dst}${gl_bai}"
return 0
fi
if [[ "$run_bg" == "true" ]]; then
env \
"PATH=$PATH" "LD_LIBRARY_PATH=${LD_LIBRARY_PATH:-}" \
"QSV_PRESET=${QSV_PRESET}" "QSV_BV=${QSV_BV}" \
"QSV_MAXRATE=${QSV_MAXRATE}" "QSV_BUFSIZE=${QSV_BUFSIZE}" \
"MOVFLAGS_FASTSTART=${MOVFLAGS_FASTSTART}" "FFMPEG_THREADS=${FFMPEG_THREADS}" \
"PROBESIZE=${PROBESIZE}" "AVIOFLAGS=${AVIOFLAGS}" \
setsid bash -c "
$(declare -f list_color_init abspath do_encode)
list_color_init
do_encode '${src}' '${dst}' '${log}'
" >/dev/null 2>&1 &
disown
echo -e "${gl_lv}✅ 单文件后台启动 PID:$!${gl_bai}"
echo -e "${gl_hui}查看日志: ${gl_lan}tail -f ${log}${gl_bai}"
echo -e "${gl_hui}结束进程: ${gl_lan}pkill -9 -f ffmpeg${gl_bai}"
return 0
fi
do_encode "$src" "$dst" "$log"
return $?
}
main() {
lock_acquire
install_deps || exit 1
if [[ $# -ge 1 ]]; then
cli_mode "$@"
else
interactive_mode
fi
}
main "$@"
视频截图对比(清晰度)
# ====================== 全局变量定义 ======================
SRC_VIDEO="/vol2/1000/tmp/071222.mp4" # 原始源视频绝对路径
DST_VIDEO="/vol2/1000/tmp/071222_HEVC-hvc1.mp4" # 转码后视频绝对路径
OUT_SNAP="/vol2/1000/tmp/compare_snapshot.png" # 输出对比截图保存绝对路径
SNAP_TIME="00:03:26" # 截图时间点,源视频与转码视频同一时刻
TEXT_LEFT="H.264" # 左侧画面文字标注
TEXT_RIGHT="HEVC hvc1" # 右侧画面文字标注
# ====================== 生成左右并排对比截图 ======================
ffmpeg \
-ss "${SNAP_TIME}" -i "${SRC_VIDEO}" \
-ss "${SNAP_TIME}" -i "${DST_VIDEO}" \
-filter_complex \
"[0:v]scale=960:-1,drawtext=fontsize=32:text='${TEXT_LEFT}':x=10:y=10:fontcolor=white:bordercolor=black:borderw=2[left];
[1:v]scale=960:-1,drawtext=fontsize=32:text='${TEXT_RIGHT}':x=10:y=10:fontcolor=white:bordercolor=black:borderw=2[right];
[left][right]hstack[v]" \
-map "[v]" \
-frames:v 1 \
-y "${OUT_SNAP}"
创建本地脚本
new_script="ffmpeg_video_hvc1.sh"
cat > "$new_script" << 'EOF'
#!/bin/bash
# 粘贴上方脚本源码
EOF
# 保留本地脚本,去掉 rm -f "$new_script"
chmod +x "$new_script" && ./"$new_script" && rm -f "$new_script"