影音數位典藏所需的自動化腳本(Bash Script)--case

影音數位典藏所需的自動化腳本(Bash Script)--case


    數位典藏中,有些工作是經常性出現的重複任務,這種狀況很適合使用腳本來減輕工作所需。其中一個例子就是轉碼,將典藏母帶轉換為工作時所需的中間素材,或產生供大眾取用的取用版本。在這樣的狀況下,可以預期會有兩種工作需要執行,因此 case ... in ... esac 會是很適合這種狀況的判斷式。

case ... in ... esac 的完整表示方式為:

case variable in
    value_n) command_n
             ;;

    value_x) command_x
             command_x
             ;;

          *) command
             ;;

esac

其中 variable 是主要用來進行判斷的變數,當變數數值與括號 ")" 前的數值相同時,則執行該數值所對應段落的指令。指令不限於一行,但每段的結尾需以雙分號 ";;" 為結尾。若數值不屬於上面任何一個指定的數值時,則執行 "*)" 這段的指令。

最簡單的 case 判斷式腳本可能長這樣:
#!/usr/bin/env bash

# Name:
      show3.sh 
# Description:
#       Show what you type
# History:
#       2020-07-02 by Mengchun

unset var
    # 將 var 這個參數還原,以免之前執行過的腳本影響這個參數

function print_out() {
        echo "Your choice is ${1:-Unknown Value.}"
        exit 0
}
    # 將重複輸出的行為寫成函式

read -p "Please input your choice: " var
    # 使用 read 顯示提示問句,並讀取變數 var

case $var in
  1) print_out "${var}." ;;
  2) print_out "${var}.." ;;
  3) print_out "${var}..." ;;
  *) echo "Usage ${0}"$'\n'"Please answer with: {1|2|3}" ;;
esac
    # 定義

     試試執行這個名為 show3.sh 的腳本:

  1. 輸入值為 "1"
    bash-4.4$ ./show3.sh 
    Please input your choice: 1
    Your choice is 1.
  2. 輸入值為 "2"
    bash-4.4$ ./show3.sh 
    Please input your choice: 2
    Your choice is 2..
  3. 輸入值為 "3"
    bash-4.4$ ./show3.sh 
    Please input your choice: 3
    Your choice is 3...
  4. 輸入值為 "4"
    bash-4.4$ ./show3.sh
    Please input your choice: 4
    Usage ./show3.sh
    Please answer with: {1|2|3}

使用 case 判斷式撰寫轉碼用腳本

    在數位典藏時,將典藏母帶轉換為工作時所需的中間素材,或產生供大眾取用的取用版本,這兩項工作,利用 case 判斷式寫成腳本能怎麼寫:
  • 變數定義區
  • 函式區
    • 顯示操作說明
    • 顯示錯誤訊息
    • 判斷輸入與輸出目標
    • 產生中間素材 (mezzanine)
    • 產生取用素材 (access)
    • 產生畫格與檔案的雜湊值 (MD5)
  • 主指令區
    • 讀取輸入參數
    • 分析輸入與輸出目標
    • 產生輸出檔案
 

 內容說明

  1. 變數定義區
    script_name="$(basename "${0}")"
        # 定義腳本名稱變數
    # color code
    RED='\033[1;31m'
    GREEN='\033[1;32m'
    YELLOW='\033[1;33m'
    BLUE='\033[1;34m'
    NC='\033[0m'
        # 定義色碼,使得顯示說明文字帶有顏色
        # 更多顏色可搜尋: ANSI_escape_code Colors
    # unset variable
    unset input_file
    unset output_file
    unset mode
        # 將廣域變數還原,確保不會有未預期的數值存在
  2. 指令確認
    # Command check
    # display a short help if no parameter in provided
    if (( "${#}" == 0)); then
    :   cat << EOF
    Help:
      ${script_name} -h
    EOF
    :   exit 1
    fi
        # 當無輸入任何參數時,提示如何顯示說明文件
  3. 函式區
    • 顯示操作說明
      # display a help message and exit
      function prompt_help(){
      :   cat <<EOF
      Syntax:
      :   ${script_name} (-a | -m) -i INPUT_FILE -o OUTPUT_FILE
      :   ${script_name} -h
      Parameters:
      :   -a generate an access file
      :   -m generate a mezzanine file
      :   -i define the INPUT_FILE
      :   -o define the OUTPUT_FILE
      :   -h display this help
      EOF
      :   exit 0
      }
          # 顯示操作語法,及各參數代表意義
    • 顯示錯誤訊息
      # display an error message and exit
      function prompt_error(){
      :   echo -e "${RED}Error: ${1:-unknown error.}${NC}"
      :   exit 1
      }
          # 顯示紅色錯誤訊息,結束後還原色碼以免汙染後續顯示文字
    • 判斷目標是否為影音檔
      # verify input ###########
      function verify_input() {
      in_file="${1}"
          # 定義要在這個函式中所使用的區域變數
      if [[ "${#}" == 0 ]]; then
      :   prompt_error "No input file passed."
          # 若無輸入目標,則顯示錯誤訊息
      elif [[ ! -f "${in_file}" ]]; then·······
      :   prompt_error  "'${in_file}' is not a file."
          # 若輸入目標非檔案,則顯示錯誤訊息
      elif ffprobe "${in_file}" 2>&1 | grep "Invalid data found" >/dev/null; then
          # 如輸入目標非影音檔,則顯示錯誤訊息
      :   prompt_error "'$(basename ${in_file})' is not an AV file."
      fi
      return 0
          # 當輸入目標不是上述狀況,結束函式並回報成功
      }
    • 判斷是否有輸出目標
      # verify_output
      function verify_output() {
      :   in_file="${1}"
          # 定義要在這個函式中所使用的區域變數
      :   if (( "${#}" == 0)); then
      :   :   prompt_error "No output file passed."
          # 若無輸入目標,則顯示錯誤訊息:   elif echo '' > "${in_file}"; (( "${?}" != 0 )); then
      :   :   prompt_error "Cannot create a file '${in_file}'."
         # echo '' > "${in_file}",產生這個名字的空檔案
         # ${?} 為前一指令狀態的回應值,在此為產生檔案的狀態
         # (( "${?}" !=0 )),表若產生檔案失敗,則顯示錯誤訊息
      :   fi
      :   return 0
      }

    • 產生中間素材 (mezzanine)
      # generate a mezzanine file from an archive master
      function make_mezzanine() {

      :   in_file="${1}"
      :   out_file="${2}"
          # 定義要在這個函式中所使用的區域變數,輸入與輸出檔
      :   ffmpeg \
      :   :   -framerate 24 -f image2 \
      :   :   -i "${in_file}" \
      :   :   -c:v prores_ks -profile:v 3 \
      :   :   "${out_file}"
          # 使用 ffmpeg 製作 24 FPS Prores HQ 檔案
          # -framerate:格率
          # -f image2:強制拆解封包為單一影像檔案串
          # -i :輸入檔案
          # -c:v prores_ks:使用 ProRes 做為影像解編碼器
          # -profile:v 3:選擇 ProRes 類型,"3"表 HQ
      :   (( "${?}" !=0 )) && prompt_error "Script stopped at mezzanine."
          # 若 ffmpeg 指令失敗,則顯示錯誤訊息
      }
    • 產生取用素材 (access)
      # generate an access file from a mezzanine file
      function make_access() {

      :   in_file="${1}"
      :   out_file="${2}"

      :   ffmpeg \
      :   :   -i "${in_file}" \
      :   :   -c:v libx264 -preset veryslow -crf 18 \
      :   :   -pix_fmt yuv420p -movflags +faststart \
      :   :   "${out_file}"
          # 使用 ffmpeg 製作 H.264 預覽檔
          # -c:v libx264使用 H.264 做為影像解編碼器
          # -preset veryslow:編碼速度,越慢表品質越佳
          # -crf 18:位元自動分配程度,"18"視覺無損壓縮,越小越佳
          # -pix_fmt yuv420p:像素格式使用 YUV 4:2:0 色彩空間
          # -movflags 
      +faststart:若影像要供網頁瀏覽使用
          # 此參數移動部份資訊至開頭,使得檔案不需全部載入即可播放
      (( "${?}" !=0 )) && prompt_error "Script stopped at access."
      }
    • 產生畫格與檔案的雜湊值 (MD5)
      # generate frame MD5 checksums
      function make_frame_MD5() {
      :   in_file="${1}"
      :   out_file="${in_file%.*}_${in_file##*.}_frame.md5"
      :   ffmpeg -i "${in_file}" -f framemd5 "${out_file}"
      }
          # 製作產生檔案逐格 MD5 資訊的檔案
      # generate file's MD5 checksums
      function make_MD5(){
      :   in_file="${1}"
      :   out_file="${in_file%.*}_${in_file##*.}.md5"
      :   md5 -r "${in_file}" > "${out_file}"
      }
          # 記錄產出檔案的 MD5,
      md5 -r,表 MD5 值在前,檔名在後
  4. 主指令區
    • 讀取輸入參數
      while getopts ":i:o:amh" opt; do
      :   case "${opt}" in
      :   :   i) input_file="${OPTARG}" ;;
      :   :   o) output_file="${OPTARG}" ;;
      :   :   a) mode='access' ;;
      :   :   m) mode='mezzanine' ;;
      :   :   h) prompt_help ;;
      :   :   :) prompt_error "The parameter '-${OPTARG}'  requires a value." ;;
      :   :   *) prompt_error "Option '-${OPTARG}' is not valid." ;;
      :   esac
      done

      • getopts "Optstring" var_name:雙引號中間的字串代表會用到的參數指標,其中冒號(**:)代表該指標要帶有參數值。在此段落中,代表使用 -i 與 -o 時,要夾帶輸入與輸出目標。
      • 參數為 -i 時,定義變數 $input_file 為後方字串
      • 參數為 -o 時,定義變數 $output_file 為後方字串
      • 參數為 -a 時,定義變數 $mode 為 "access"
      • 參數為 -m 時,定義變數 $mode 為 "mezzanine"
      • 參數為 -h 時,執行 prompt_help 函式
      • 在某些環境中,第一個冒號前無指標,表示參數為空值("")。因此顯示錯誤訊息,提示需輸入參數數值。但一般狀況下,可能直接被當成其他值 (*)。
      • 在當參數不屬於上述 ( i | o | amh ) 時,顯示這個參數無效。
    • 分析輸入與輸出目標
      # verify input and output provided
      if [[ "${mode}" == 'mezzanine' ]]; then
      :   number=$(echo "${input_file}" | grep -o -E '_[0-9]+.' | grep -o -E '[0-9]+')
      :   verify_input "${input_file%_*}_${number%?}1.${input_file##*.}"
      else
      :   verify_input "${input_file}"
      fi
      [[ ! "${output_file}" ]] && prompt_error "No output file passed."
      • 輸入檔的部份,因為用來測試的典藏母檔為一系列連續數字所組成的影像檔。因此需要透過拆分檔名的方式,讓檔案可以通過 verify_input 這個函式。因此透過 $mode 這個變數確認工作狀態。
        • grep -o -E '_[0-9]+.',表拆出字串中確實由"下底線(_)數字(0-9)點(.)"所組成的部份。再執行 grep -o -E '[0-9]+',確實拆出序列影像檔中的數字區段。
          -o 表只輸出字串中確實符合部份
          -E 表使用擴充正規表示式 (Extended Regular Express,見後附錄)
        • ${number%?}1 表取代數字串的最右一位數,更換為"1",因此例如數字 "012" 中的 "2" 會被 "1" 取代,成為 "011"。整串指令表示,使用檔名中編號為 "**1" 的檔案來進行是否為影音檔的判別。
        • 若為取用檔時 ( mode = "access" ),由於輸入檔為單一影片檔,因此可以直接將檔名傳遞給 verify_input 這個函式。
      • 輸出檔的確認,表"若不存在輸出檔,則顯示錯誤訊息"
        [[ ! "${output_file}" ]] && prompt_error "No output file passed."
        也可寫成
        [[ "${output_file}" ]] || prompt_error "No output file passed."
        利用  &&  和  ||  相反的特性來驅動後面指令。
    • 產生輸出檔案
      # select mode and generate files
      case "${mode}" in
      :   mezzanine)·
      :   :   input_file_regex="${input_file%_*}_%0${#number}d.${input_file##*.}"
      :   :   make_mezzanine "${input_file_regex}" "${output_file}"·
      :   :   ;;
      :   access) make_access "${input_file}" "${output_file}" ;;
      :   *) prompt_error "No mode (access or mezzanine) selected." ;;
      esac

      make_frame_MD5 "${output_file}"
      make_MD5 "${output_file}"
      • $mode 為 mezzanine 時,先執行重建檔名的部份,再將檔名傳遞給 make_mezzanine 這個函式。 %0${#number}為 ffmpeg 所需,序列影像檔中數字命名方式,如 %03d 表三位數連續序列。
      • $mode 為 access 時,直接將檔名傳遞給 make_access 這個函式。
      • 若非上註狀況,則顯示錯誤訊息。
      • 最後兩行則將輸出檔送至 make_frame_MD5make_MD5 函式處理,以產生相對應的畫格與檔案的 MD5 值。

    實際執行這個腳本試試:
  1. 指令確認
    bash-4.4$ ./transcode 
    Help:
      transcode -h
  2. 顯示操作說明
    bash-4.4$ ./transcode -h
    Syntax:
    transcode (-a | -m) -i INPUT_FILE -o OUTPUT_FILE
    transcode -h
    Parameters:
    -a generate an access file
    -m generate a mezzanine file
    -i define the INPUT_FILE
    -o define the OUTPUT_FILE
    -h display this help
  3. 輸入目標非檔案
    bash-4.4$ ./transcode -a -i no_file
    Error: 'no_file' is not a file.
  4. 輸入目標非影音檔
    bash-4.4$ ./transcode -a -i out_mp4.md5 
    Error: 'out_mp4.md5' is not an AV file.
  5. 判斷是否有輸出目標
    bash-4.4$ ./transcode -a -i video.mp4 
    Error: No output file passed.
  6. 產生中間素材 (mezzanine)
    bash-4.4$ ./transcode -m -i dpx/sample_001.dpx -o mezz.mov
    確認是否產生影像檔與 MD5 檔
    bash-4.4$ ls mezz*
    mezz.mov mezz_mov.md5 mezz_mov_frame.md5
  7. 產生取用素材 (access)
    bash-4.4$ ./transcode -a -i mezz.mov -o access.mp4
    確認是否產生影像檔與 MD5 檔
    bash-4.4$ ls access*
    access.mp4 access_mp4.md5 access_mp4_frame.md5
  8. 產生輸出目標時發生錯誤
    bash-4.4$ echo '' > asdf.mp4 ; chmod -w asdf.mp4
    bash-4.4$ ./transcode -a -i video.mp4 -o asdf.mp4
    (ffmpeg 輸出,省略)
    File 'asdf.mp4' already exists. Overwrite? [y/N] y
    asdf.mp4: Permission denied
    Error: Script stopped at access.

這便是在產生中間素材與取用素材的腳本中使用 case 判斷式的方式。如果需要應付不同素材時,最可能需要修改的部份為 # verify input and output provided 這個段落。確保交給 ffmpeg 所執行的輸入與輸出格式符合規則及預期目標。若錯誤訊息為 Error: Script stopped at  則表示主要用於轉碼的 ffmpeg 指令有誤,建議回顧 ffmpeg 指令介紹,可參見下附錄。

附錄,說明,此文所使用的腳本與影片可由下列連結取得:

留言

這個網誌中的熱門文章

低成本的數位影像修復 (OpenCV Inpainting)

影音類載體 -- 聲音類 -- 8軌匣式錄音帶(8 track tape)

影音數位典藏所需的自動化腳本(Bash Script)--函式