分享一个获取视频信息、自动截图的脚本
该脚本仅针对普通视频(通常指 mp4,mkv),能够打印出 mediainfo、通过 ffmpeg 截取 N 张图或者拼图、自动上传至 pixhost 图床并打印 bbcode
的文本
使用方法
# 文件名规则: # 单帧: 源文件名_01.png, 源文件名_02.png, ...
# 拼图: 源文件名_grid.png
# 使用示例:
vi /usr/local/bin/vid
:q 回车
chmod +x /usr/local/bin/vid
# 完成可执行文件的配置后,就可以使用了。先进入要生成的目录,可以指定操作的媒体资源,也可以不指定,不指定的情况下,会自动寻找目录下排序后的第一个媒体资源vid --count 3 # 生成3张单帧
vid --grid 4x3 # 生成4x3拼图
vid --retry # 重试生成上传
vid --info # 显示媒体信息
vid --media 1.mp4 # 指定视频文件
#!/bin/bash
# 安装必要依赖
install_dependencies() {
local missing=()
for cmd in ffmpeg ffprobe curl jq mediainfo; do
if ! command -v $cmd &>/dev/null; then
missing+=("$cmd")
fi
done
if [ ${#missing[@]} -gt 0 ]; then
echo "正在安装依赖: ${missing[*]}"
if command -v apt-get &>/dev/null; then
sudo apt-get update && sudo apt-get install -y "${missing[@]}"
elif command -v yum &>/dev/null; then
sudo yum install -y "${missing[@]}"
else
echo "请手动安装依赖: ${missing[*]}"
exit 1
fi
fi
}
# 获取下一个可用序号
get_next_sequence() {
local prefix="$1"
local max_num=0
for file in "${prefix}_"[0-9][0-9].png; do
if [[ -f "$file" ]]; then
local num=${file##*_}
num=${num%.png}
num=$((10#$num)) # 去除前导零
((num > max_num)) && max_num=$num
fi
done
printf "%02d" $((max_num + 1))
}
# 查找视频文件
find_video_file() {
local files=()
for ext in mp4 mkv avi mov; do
files+=($(find . -maxdepth 1 -type f -iname "*.$ext" | sort))
done
[ ${#files[@]} -eq 0 ] && { echo "未找到视频文件"; exit 1; }
echo "${files[0]}"
}
# 上传图片到图床
upload_to_pixhost() {
local file="$1"
local max_size_mb=10
local max_retry=3
local retry_count=0
while ((retry_count < max_retry)); do
# 检查文件大小
local size=$(stat -c%s "$file")
if ((size > max_size_mb * 1024 * 1024)); then
echo "文件过大($((size/1024/1024))MB),重新截取..."
rm -f "$file"
return 1
fi
# echo -n "上传中: $(basename "$file")..."
local response=$(curl -s -F "name=$(basename "$file")" \
-F "ajax=yes" -F "content_type=0" -F "file=@$file" \
"https://pixhost.to/new-upload/")
if [ -z "$response" ]; then
echo "失败(空响应)"
else
local error=$(echo "$response" | jq -r '.error.description' 2>/dev/null)
if [ "$error" != "null" ]; then
echo "失败($error)"
else
local url=$(echo "$response" | jq -r '.show_url' | sed 's|\\||g;s|pixhost\.to/show|img1.pixhost.to/images|')
# echo "成功!"
echo "[img]$url[/img]"
return 0
fi
fi
((retry_count++))
sleep 1
done
echo "上传失败: 超过最大重试次数"
return 1
}
# 截取并优化单帧
capture_frames() {
local video_file="$1"
local count="$2"
local base_name=$(basename "$video_file" | sed 's/\.[^.]*$//')
local duration=$(ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "$video_file" | cut -d. -f1)
local margin=$((duration / 20))
local valid_duration=$((duration - 2 * margin))
local section_length=$(awk "BEGIN {print $valid_duration/$count}")
for ((i=0; i<count; i++)); do
local seq=$(get_next_sequence "$base_name")
local outfile="${base_name}_${seq}.png"
local start=$(awk "BEGIN {print $margin + $i*$section_length}")
local end=$(awk "BEGIN {print $margin + ($i+1)*$section_length}")
local timestamp=$(awk -v s=$start -v e=$end 'BEGIN {srand(); print s + rand()*(e-s)}')
ffmpeg -loglevel error -ss "$timestamp" -i "$video_file" \
-frames:v 1 -pix_fmt yuv420p -compression_level 9 -y "$outfile"
if ! upload_to_pixhost "$outfile"; then
# 上传失败且文件被删除时重新尝试
((i--))
fi
done
}
# 生成拼图
create_grid() {
local video_file="$1"
local cols="${2%x*}" # 列数
local rows="${2#*x}" # 行数
local base_name=$(basename "$video_file" | sed 's/\.[^.]*$//') # 提取文件名(不带扩展名)
local outfile="${base_name}_grid.png" # 拼图输出文件名
local duration=$(ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "$video_file") # 获取视频总时长
local margin=180 # 跳过前后3分钟 (180秒)
local valid_duration=$(awk "BEGIN {print $duration - 2 * $margin}") # 计算有效时长,跳过前后3分钟
local total_frames=$((cols * rows)) # 计算拼图中需要的帧数
# 计算每个帧的时间戳间隔(单位:秒)
local interval=$(awk "BEGIN {print $valid_duration/$total_frames}")
# 临时文件名数组,用于存储每帧图片
local temp_files=()
# 循环截取并保存每一帧
for i in $(seq 0 $((total_frames - 1))); do
local timestamp=$(awk "BEGIN {print $margin + $i * $interval}")
local temp_file="${base_name}_frame_${i}.png"
# 使用 ffmpeg 截取每一帧并保存为文件,跳过前后3分钟
ffmpeg -loglevel error -ss "$timestamp" -i "$video_file" -frames:v 1 \
-vf "scale=512:-1" -pix_fmt yuv420p -map_metadata -1 -y "$temp_file"
# 将临时文件名加入数组
temp_files+=("$temp_file")
done
# 使用 ffmpeg 生成拼图
ffmpeg -loglevel error -i "concat:$(IFS='|'; echo "${temp_files[*]}")" -filter_complex "[0:v]tile=${cols}x${rows}[v]" -map "[v]" -y "$outfile"
# 清理临时文件
rm -f "${temp_files[@]}"
# 上传拼图
upload_to_pixhost "$outfile"
}
# 重试上传
retry_upload() {
local video_file="$1"
local base_name=$(basename "$video_file" | sed 's/\.[^.]*$//')
local duration=$(ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "$video_file" | cut -d. -f1)
# 在中间1/3时间段随机截取
local margin=$((duration / 20))
local valid_duration=$((duration - 2 * margin))
local start=$(awk "BEGIN {print $margin + $valid_duration/3}")
local end=$(awk "BEGIN {print $margin + $valid_duration*2/3}")
local timestamp=$(awk -v s=$start -v e=$end 'BEGIN {srand(); print s + rand()*(e-s)}')
local seq=$(get_next_sequence "$base_name")
local outfile="${base_name}_${seq}.png"
ffmpeg -loglevel error -ss "$timestamp" -i "$video_file" \
-frames:v 1 -q:v 0 -y "$outfile"
upload_to_pixhost "$outfile"
}
# 主函数
main() {
install_dependencies
local video_file=""
local action=""
local value=""
local show_info=false
# 解析参数
while [[ $# -gt 0 ]]; do
case "$1" in
--media)
video_file="$2"
shift 2
;;
--count|--grid)
action="${1#--}"
value="$2"
shift 2
;;
--retry)
action="retry"
shift
;;
--info)
show_info=true
shift
;;
*)
echo "未知参数: $1"
exit 1
;;
esac
done
# 自动检测视频文件
[ -z "$video_file" ] && video_file=$(find_video_file)
# 显示媒体信息
if $show_info; then
mediainfo "$video_file"
[ -z "$action" ] && exit
fi
# 执行对应操作
case "$action" in
count)
capture_frames "$video_file" "$value"
;;
grid)
create_grid "$video_file" "$value"
;;
retry)
retry_upload "$video_file"
;;
"")
echo "请指定操作: --count, --grid 或 --retry"
exit 1
;;
esac
}
main "$@"