影音轉檔壓縮紀錄

最近透過Convertio輸出多個不同格式的影音,在不同格式下選用的品質差異很大,多次嘗試後以人工比對細節發現傳統H.264的質量與webM(VP9)在等畫質情況下檔案大小差距不到15個百分點,而AV1則可以讓檔案大小差距增加到50%以上,對於縮減影音體積來說幫助更為顯著。

AV1與H.265格式建議普通畫質參數:

AOM-av1                >CRF/QP       24(普通畫質-體積最小)
SVT-av1                  >CRF/QP        28(普通畫質)
rav1e-av1              >QP                 60(普通畫質)
Medium-H.265    >CRF/QP        20(普通畫質)
Slow-H.265           >CRF/QP        22(普通畫質-體積次之,效率較高)

convertio.co輸出多個格式建議最低畫質:

(以畫面不破不殘影為基準,實際輸出可能會因為影格動態差異而有所變化)

    av1                        >CQ               39(低畫質)
webM                       >CQ               30(低畫質高一級)
 H.264                      >CRF             23(一般畫質)

 H.264                      >CRF             28(極低畫質,窄頻網路適用)

 H.264                      >CQ               39(極低畫質,窄頻網路適用)

參考資料 https://www.reddit.com/ (文件 備)

2024/12/29 更新

最近有無損剪輯影片的需求,常見的無損切割片段的工具有VidTrimLosslessCut分別針對手機跟電腦端,其中LosslessCut額外提供無損合併影片的功能,經測試相同編碼格式的無損切片確實可以順利合併,但合併後的影片在接合處容易因為畫格不足導致聲音沒跟時間戳同步的情況,這塊可以透過ffmpeg修復,修復指令如下:

ffmpeg -i input.mp4 -c:v copy -c:a aac -async 1 output_fixed.mp4

其中-async 1將以影片本身的時間戳作為基準對聲音進行校正,操作後實現影像跟聲音同步。


2025/01/21更新

在Debian快速編碼成720p畫質並限制CPU 40%使用率

apt install cpulimit

cpulimit -l 40 -- ffmpeg -i h264.mp4 -c:a copy -c:v libx264 -preset fast -crf 24 -vf "scale='if(gt(a,1280/720),min(iw,1280),if(lt(iw,1280),iw,-2))':'if(gt(a,1280/720),if(lt(ih,720),ih,-2),min(ih,720))':force_original_aspect_ratio=decrease,scale=trunc(iw/2)*2:trunc(ih/2)*2" -movflags faststart output_720p.mp4

cpulimit -l 60 -- ffmpeg -i h264.mp4 -c:a aac -b:a 96k -ac 2 -c:v libx264 -preset fast -crf 24 -vf "scale='if(gt(a,854/480),min(iw,854),if(lt(iw,854),iw,-2))':'if(gt(a,854/480),if(lt(ih,480),ih,-2),min(ih,480))':force_original_aspect_ratio=decrease,scale=trunc(iw/2)*2:trunc(ih/2)*2" -movflags faststart output_480p.mp4

主要目的是希望在控制CPU負載的情況下快速輸出。

建立批次轉檔腳本 h264 I/O限速 h265 svtav1 webM

vim batch_ffmpeg.sh

寫入以下內容

#!/bin/bash
# 設定 A 目錄與 B 目錄
INPUT_DIR="/path/to/A" # 替換成 A 目錄的實際路徑
OUTPUT_DIR="/path/to/B" # 替換成 B 目錄的實際路徑

# 確保輸出目錄存在
mkdir -p "$OUTPUT_DIR"

# 開始處理每一個視頻檔案
for input_file in "$INPUT_DIR"/*; do
  if [ -f "$input_file" ]; then
    filename=$(basename "$input_file")
    output_file="$OUTPUT_DIR/$filename"
    
    echo "處理中: $input_file -> $output_file"

    # 捕獲 cpulimit 和 ffmpeg 的輸出,如需避開專利池-c:a aac -b:a 96k -ac 2可改用-c:a libmp3lame -q:a 5 -ac 2
    cpulimit_output=$(cpulimit -l 60 -- ffmpeg -i "$input_file" -map 0:v:0 \
      -map 0:a? -c:a aac -b:a 96k -ac 2 \
      -c:v libx264 -pix_fmt yuv420p -preset fast -crf 24 \
      -vf "scale=-2:480:force_original_aspect_ratio=decrease,crop=min(iw\,854):480:(iw-min(iw\,854))/2:0,pad=854:480:(ow-iw)/2:(oh-ih)/2:color=black,setsar=1:1" \
      -movflags faststart \
      -map 0:s? -c:s copy \
      "$output_file" 2>&1)

    # 分析 cpulimit 的輸出
    if echo "$cpulimit_output" | grep -q "Process .* detected"; then
      # 檢查是否生成輸出文件
      if [ -f "$output_file" ] && [ -s "$output_file" ]; then
        echo "成功處理: $input_file"
      else
        echo "處理失敗: $input_file - 輸出文件未生成或為空"
      fi
    else
      echo "處理失敗: $input_file - cpulimit 未檢測到進程"
    fi
  fi
done

echo "所有視頻處理完成!"

或者增加I/O使用限制並提高RAM使用率

#!/bin/bash
# 設定 A 目錄與 B 目錄
INPUT_DIR="/path/to/A" # 替換成 A 目錄的實際路徑
OUTPUT_DIR="/path/to/B" # 替換成 B 目錄的實際路徑

# 確保輸出目錄存在
mkdir -p "$OUTPUT_DIR"

# 檢查輸入目錄是否存在
if [ ! -d "$INPUT_DIR" ]; then
  echo "錯誤:輸入目錄 $INPUT_DIR 不存在"
  exit 1
fi

# 開始處理每一個視頻檔案
for input_file in "$INPUT_DIR"/*; do
  if [ -f "$input_file" ]; then
    filename=$(basename "$input_file")
    output_file="$OUTPUT_DIR/$filename"
    
    # 判斷輸出文件是否已存在,避免重複處理
    if [ -f "$output_file" ] && [ -s "$output_file" ]; then
      echo "檔案已存在,跳過: $output_file"
      continue
    fi
    
    echo "處理中: $input_file -> $output_file"
    # 捕獲 cpulimit 和 ffmpeg 的輸出
    # 注意 對於RAM資源不足的設備請加上-fflags nobuffer 並拿掉-probesize 50M -analyzeduration 50M
    cpulimit_output=$(cpulimit -l 60 -- ionice -c2 -n7 ffmpeg -i "$input_file" \
      -probesize 50M -analyzeduration 50M \
      -map 0:v:0 -map 0:a? -c:a aac -b:a 96k -ac 2 \
      -c:v libx264 -pix_fmt yuv420p -preset fast -crf 24 \
      -vf "scale=-2:480:force_original_aspect_ratio=decrease,crop=min(iw\,854):480:(iw-min(iw\,854))/2:0,pad=854:480:(ow-iw)/2:(oh-ih)/2:color=black,setsar=1:1" \
      -movflags faststart \
      -map 0:s? -c:s copy \
      "$output_file" 2>&1)
    
    # 分析 cpulimit 的輸出
    if echo "$cpulimit_output" | grep -q "Process .* detected"; then
      # 檢查是否生成輸出文件
      if [ -f "$output_file" ] && [ -s "$output_file" ]; then
        echo "成功處理: $input_file"
      else
        echo "處理失敗: $input_file - 輸出文件未生成或為空"
      fi
    else
      echo "處理失敗: $input_file - cpulimit 未檢測到進程"
    fi
  fi
done

echo "所有視頻處理完成!"

或使用webM編碼(檔案大小接近h264,畫質介於h264與h265之間)

#!/bin/bash
# 設定 A 目錄與 B 目錄
INPUT_DIR="/path/to/A" # 替換成 A 目錄的實際路徑
OUTPUT_DIR="/path/to/B" # 替換成 B 目錄的實際路徑

# 確保輸出目錄存在
mkdir -p "$OUTPUT_DIR"

# 檢查輸入目錄是否存在
if [ ! -d "$INPUT_DIR" ]; then
  echo "錯誤:輸入目錄 $INPUT_DIR 不存在"
  exit 1
fi

# 開始處理每一個視頻檔案
for input_file in "$INPUT_DIR"/*; do
  if [ -f "$input_file" ]; then
    filename=$(basename "$input_file")
    output_file="$OUTPUT_DIR/$filename"
    
    # 判斷輸出文件是否已存在,避免重複處理
    if [ -f "$output_file" ] && [ -s "$output_file" ]; then
      echo "檔案已存在,跳過: $output_file"
      continue
    fi
    
    echo "處理中: $input_file -> $output_file"
    # 捕獲 cpulimit 和 ffmpeg 的輸出
    # 注意 對於RAM資源不足的設備請加上-fflags nobuffer 並拿掉-probesize 50M -analyzeduration 50M
    # -threads 0 表示線程數量自動,建議直接改成CPU容許的線程數量,例如6核心12線程寫成-threads 12
    cpulimit_output=$(cpulimit -l 60 -- ionice -c2 -n7 ffmpeg -i "$input_file" \
      -probesize 50M -analyzeduration 50M \
      -map 0:v:0 -map 0:a? -c:a libopus -b:a 96k -ac 2 \
      -c:v libvpx-vp9 -pix_fmt yuv420p -row-mt 1 -tile-columns 2 -threads 0 -deadline good -cpu-used 2 -aq-mode 2 -crf 29 -g 240 -keyint_min 120 -lag-in-frames 20 -auto-alt-ref 1 -enable-tpl 1 \
      -vf "scale=-2:480:force_original_aspect_ratio=decrease,crop=min(iw\,854):480:(iw-min(iw\,854))/2:0,pad=854:480:(ow-iw)/2:(oh-ih)/2:color=black,setsar=1:1" \
      -map 0:s? -c:s copy \
      "$output_file" 2>&1)
    
    # 分析 cpulimit 的輸出
    if echo "$cpulimit_output" | grep -q "Process .* detected"; then
      # 檢查是否生成輸出文件
      if [ -f "$output_file" ] && [ -s "$output_file" ]; then
        echo "成功處理: $input_file"
      else
        echo "處理失敗: $input_file - 輸出文件未生成或為空"
      fi
    else
      echo "處理失敗: $input_file - cpulimit 未檢測到進程"
    fi
  fi
done

echo "所有視頻處理完成!"

或使用webM編碼2-pass(畫質比照svtav1,但檔案大小接近h264)

#!/bin/bash
# 設定 A 目錄與 B 目錄
INPUT_DIR="/path/to/A" # 替換成 A 目錄的實際路徑
OUTPUT_DIR="/path/to/B" # 替換成 B 目錄的實際路徑

# CPU 和 IO 限制設定
CPU_LIMIT_CMD="cpulimit -l 60 -- ionice -c2 -n7"

# 通用 ffmpeg 參數
PROBE_PARAMS="-probesize 50M -analyzeduration 50M"
VIDEO_FILTER="scale=-2:480:force_original_aspect_ratio=decrease,crop=min(iw\,854):480:(iw-min(iw\,854))/2:0,pad=854:480:(ow-iw)/2:(oh-ih)/2:color=black,setsar=1:1"
QUALITY_PARAMS="-crf 29 -deadline good"

# 確保輸出目錄存在
mkdir -p "$OUTPUT_DIR"

# 檢查輸入目錄是否存在
if [ ! -d "$INPUT_DIR" ]; then
  echo "錯誤:輸入目錄 $INPUT_DIR 不存在"
  exit 1
fi

# 開始處理每一個視頻檔案
for input_file in "$INPUT_DIR"/*; do
  if [ -f "$input_file" ]; then
    filename=$(basename "$input_file")
    filename_no_ext="${filename%.*}"
    output_file="$OUTPUT_DIR/$filename"
    
    # 2-pass 編碼的臨時檔案路徑
    log_file="$OUTPUT_DIR/${filename_no_ext}_2pass.log"
    
    # 判斷輸出文件是否已存在,避免重複處理
    if [ -f "$output_file" ] && [ -s "$output_file" ]; then
      echo "檔案已存在,跳過: $output_file"
      continue
    fi
    
    echo "開始 2-pass 編碼: $input_file -> $output_file"
    
    # 清理可能存在的舊 log 檔案(只在開始新的轉碼前清理)
    rm -f "${log_file}"*
    
    # Pass 1: 分析階段(只處理視頻,不處理音頻和字幕)
    echo "Pass 1/2: 分析視頻..."
    pass1_output=$($CPU_LIMIT_CMD ffmpeg -y -i "$input_file" \
      $PROBE_PARAMS \
      -map 0:v:0 \
      -c:v libvpx-vp9 -pix_fmt yuv420p -pass 1 -passlogfile "$log_file" \
      -row-mt 0 -tile-columns 1 -threads 0 $QUALITY_PARAMS -cpu-used 4 \
      -g 240 -keyint_min 120 -lag-in-frames 16 \
      -auto-alt-ref 1 -enable-tpl 1 \
      -vf "$VIDEO_FILTER" \
      -an -f webm /dev/null 2>&1)
    
    # 檢查 Pass 1 是否成功(以實際 log 檔案為準)
    if [ ! -f "${log_file}-0.log" ] || [ ! -s "${log_file}-0.log" ]; then
      echo "Pass 1 失敗: $input_file - 未生成有效的 log 檔案"
      # 記錄 cpulimit 狀態用於診斷
      if ! echo "$pass1_output" | grep -q "Process .* detected"; then
        echo "  → cpulimit 未檢測到進程"
      fi
      # 清理失敗的臨時檔案並跳過此檔案
      rm -f "${log_file}"*
      continue
    fi
    
    echo "Pass 1 完成"
    # 可選:記錄 cpulimit 狀態但不影響流程
    if ! echo "$pass1_output" | grep -q "Process .* detected"; then
      echo "注意: Pass 1 - cpulimit 未檢測到進程,但 log 檔案已生成"
    fi
    
    # Pass 2: 最終編碼(包含音頻和字幕)
    echo "Pass 2/2: 最終編碼..."
    pass2_output=$($CPU_LIMIT_CMD ffmpeg -y -i "$input_file" \
      $PROBE_PARAMS \
      -map 0:v:0 -map 0:a? -c:a libopus -b:a 96k -ac 2 \
      -c:v libvpx-vp9 -pix_fmt yuv420p -pass 2 -passlogfile "$log_file" \
      -row-mt 1 -tile-columns 2 -threads 0 $QUALITY_PARAMS -cpu-used 2 \
      -aq-mode 2 -g 240 -keyint_min 120 -lag-in-frames 25 \
      -auto-alt-ref 1 -enable-tpl 1 \
      -vf "$VIDEO_FILTER" \
      -map 0:s? -c:s copy \
      "$output_file" 2>&1)
    
    # 檢查 Pass 2 是否成功(以實際輸出檔案為準)
    if [ -f "$output_file" ] && [ -s "$output_file" ]; then
      # 檢查檔案大小是否合理
      output_size=$(stat -f%z "$output_file" 2>/dev/null || stat -c%s "$output_file" 2>/dev/null || echo 0)
      if [ "$output_size" -ge 1024 ]; then
        echo "成功處理: $input_file (輸出大小: $output_size bytes)"
        # 可選:記錄 cpulimit 狀態但不影響成功判斷
        if ! echo "$pass2_output" | grep -q "Process .* detected"; then
          echo "注意: Pass 2 - cpulimit 未檢測到進程,但檔案已生成"
        fi
      else
        echo "Pass 2 失敗: $input_file - 輸出檔案過小 ($output_size bytes)"
        rm -f "$output_file"  # 清理損壞的輸出檔案
      fi
    else
      echo "Pass 2 失敗: $input_file - 輸出檔案未生成或為空"
      # 記錄可能的失敗原因
      if ! echo "$pass2_output" | grep -q "Process .* detected"; then
        echo "  → cpulimit 未檢測到進程"
      fi
      rm -f "$output_file"  # 清理可能的空檔案
    fi
    
    # 最後統一清理 2-pass 臨時檔案(無論成功失敗都清理)
    rm -f "${log_file}"*
  fi
done

echo "所有視頻處理完成!"

或使用H265編碼(畫質跟轉碼效率比照svtav1,但檔案大小接近h264)

#!/bin/bash
# 設定 A 目錄與 B 目錄
INPUT_DIR="/path/to/A" # 替換成 A 目錄的實際路徑
OUTPUT_DIR="/path/to/B" # 替換成 B 目錄的實際路徑

# 確保輸出目錄存在
mkdir -p "$OUTPUT_DIR"

# 檢查輸入目錄是否存在
if [ ! -d "$INPUT_DIR" ]; then
  echo "錯誤:輸入目錄 $INPUT_DIR 不存在"
  exit 1
fi

# 開始處理每一個視頻檔案
for input_file in "$INPUT_DIR"/*; do
  if [ -f "$input_file" ]; then
    filename=$(basename "$input_file")
    output_file="$OUTPUT_DIR/$filename"
    
    # 判斷輸出文件是否已存在,避免重複處理
    if [ -f "$output_file" ] && [ -s "$output_file" ]; then
      echo "檔案已存在,跳過: $output_file"
      continue
    fi
    
    echo "處理中: $input_file -> $output_file"
    # 捕獲 cpulimit 和 ffmpeg 的輸出
    # 注意 對於RAM資源不足的設備請加上-fflags nobuffer 並拿掉-probesize 50M -analyzeduration 50M
    cpulimit_output=$(cpulimit -l 60 -- ionice -c2 -n7 ffmpeg -i "$input_file" \
      -probesize 50M -analyzeduration 50M \
      -map 0:v:0 -map 0:a? -c:a aac -b:a 96k -ac 2 \
      -c:v libx265 -pix_fmt yuv420p -preset medium -crf 21 -x265-params aq-mode=1 \
      -vf "scale=-2:480:force_original_aspect_ratio=decrease,crop=min(iw\,854):480:(iw-min(iw\,854))/2:0,pad=854:480:(ow-iw)/2:(oh-ih)/2:color=black,setsar=1:1" \
      -movflags faststart \
      -map 0:s? -c:s copy \
      "$output_file" 2>&1)
    
    # 分析 cpulimit 的輸出
    if echo "$cpulimit_output" | grep -q "Process .* detected"; then
      # 檢查是否生成輸出文件
      if [ -f "$output_file" ] && [ -s "$output_file" ]; then
        echo "成功處理: $input_file"
      else
        echo "處理失敗: $input_file - 輸出文件未生成或為空"
      fi
    else
      echo "處理失敗: $input_file - cpulimit 未檢測到進程"
    fi
  fi
done

echo "所有視頻處理完成!"

增加SVT-AV1編碼

#!/bin/bash
# 設定 A 目錄與 B 目錄
INPUT_DIR="/path/to/A" # 替換成 A 目錄的實際路徑
OUTPUT_DIR="/path/to/B" # 替換成 B 目錄的實際路徑

# 確保輸出目錄存在
mkdir -p "$OUTPUT_DIR"

# 檢查輸入目錄是否存在
if [ ! -d "$INPUT_DIR" ]; then
  echo "錯誤:輸入目錄 $INPUT_DIR 不存在"
  exit 1
fi

# 開始處理每一個視頻檔案
for input_file in "$INPUT_DIR"/*; do
  if [ -f "$input_file" ]; then
    filename=$(basename "$input_file")
    output_file="$OUTPUT_DIR/$filename"
    
    # 判斷輸出文件是否已存在,避免重複處理
    if [ -f "$output_file" ] && [ -s "$output_file" ]; then
      echo "檔案已存在,跳過: $output_file"
      continue
    fi
    
    echo "處理中: $input_file -> $output_file"
    # 捕獲 cpulimit 和 ffmpeg 的輸出,注意-m用來限制svtav1子進程
    cpulimit_output=$(cpulimit -l 60 -m -- ionice -c2 -n7 ffmpeg -i "$input_file" \
      -probesize 50M -analyzeduration 50M \
      -map 0:v:0 -map 0:a? -c:a aac -b:a 96k -ac 2 \
      -c:v libsvtav1 -pix_fmt yuv420p -preset 8 -crf 28 -svtav1-params tune=1 \
      -vf "scale=-2:480:force_original_aspect_ratio=decrease,crop=min(iw\,854):480:(iw-min(iw\,854))/2:0,pad=854:480:(ow-iw)/2:(oh-ih)/2:color=black,setsar=1:1" \
      -movflags faststart \
      -map 0:s? -c:s copy \
      "$output_file" 2>&1)
    
    # 分析 cpulimit 的輸出
    if echo "$cpulimit_output" | grep -q "Process .* detected"; then
      # 檢查是否生成輸出文件
      if [ -f "$output_file" ] && [ -s "$output_file" ]; then
        echo "成功處理: $input_file"
      else
        echo "處理失敗: $input_file - 輸出文件未生成或為空"
      fi
    else
      echo "處理失敗: $input_file - cpulimit 未檢測到進程"
    fi
  fi
done

echo "所有視頻處理完成!"

保存後增加執行權限

chmod +x batch_ffmpeg.sh

執行腳本

./batch_ffmpeg.sh

其他濾鏡寫法(等比例縮放不裁切)

-vf "scale='if(gt(a,854/480),min(iw,854),if(lt(iw,854),iw,-2))':'if(gt(a,854/480),if(lt(ih,480),ih,-2),min(ih,480))':force_original_aspect_ratio=decrease,scale=trunc(iw/2)*2:trunc(ih/2)*2"

進程為目的覆蓋,所以OUTPUT_DIR如果有相同檔名會直接替換。
如果要提升I/O效率可以嘗試使用RAM建置虛擬空間

mkdir /mnt/ramdisk
sudo mount -t tmpfs -o size=8G tmpfs /mnt/ramdisk

或者在Linux(Debian/Ubuntu)設定開機自動創建RAM虛擬空間


sudo nano /etc/fstab
# 在檔案末尾添加
tmpfs /mnt/ramdisk tmpfs defaults,noatime,nosuid,nodev,noexec,size=8G 0 0

留言

這個網誌中的熱門文章

2022亞太電信魔術方塊一代申裝 2025遠傳低功率轉發器(強波器)申裝

開放麒麟系統openKylin1.0安裝

AI繪圖工具整理 Stable Diffusion、Easy Diffusion、Draw Things、BingCreate