分享一个获取视频信息、自动截图的脚本

7 15~20 分钟

该脚本仅针对普通视频(通常指 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 "$@"