Bash

將有序列表拆分為子列表

  • September 25, 2016

我有一個名稱為prefix_0000.mp3…的文件列表prefix_x.mp3,其中 max(x) = 9999。

我有 bash 腳本:

...
sox prefix_*.mp3 script_name_output.mp3 # this fails because maximum number is 348
rm prefix_*.mp3
...

如何最好地將 mp3 文件的有序列表拆分為子列表(保留順序)並逐漸sox將它們拆分並刪除 bash 腳本中不需要的文件?

首先,將列表收集到一個 Bash 數組中。如果文件在目前目錄中,您可以使用

files=(prefix_????.mp3)

或者,您可以使用查找和排序,

IFS=$'\n' ;
files=($(find . -name 'prefix_*.mp3' printf '%p\n' | sort -d))

設置IFS告訴 Bash 只在換行符處拆分。如果您的文件和目錄名稱不包含空格,則可以省略它。

或者,您可以從文件中讀取文件名,例如filelist,每行一個名稱,並且沒有空行,

IFS=$'\n'
files=($(<filelist))

如果那裡可能有空行,請使用

IFS=$'\n'
files=($(sed -e '/$/ d' filelist))

接下來,決定每個切片中需要多少個文件,臨時累加器文件的名稱,以及最終組合的文件名:

s=100
src="combined-in.mp3"
out="combined-out.mp3"

然後,我們只需要對列表進行切片,並處理每個子列表:

while (( ${#files[@]} > 0 )); do
   n=${#files[@]}

   # Slice files array into sub and left.
   if (( n <= s )); then
       sub=("${files[@]}")
       left=()
   else
       (( n-= s ))
       sub=("${files[@]:0:s}")
       left=("${files[@]:s:n}")
   fi

   # If there is no source file, but there is
   # a sum file, rename sum to source.
   if [ ! -e "$src" -a -e "$out" ]; then
       mv -f "$out" "$src"
   fi

   # If there is a source file, include it first.
   if [ -e "$src" ]; then
       sub=("$src" "${sub[@]}")
   fi

   # Run command.
   if ! sox "${sub[@]}" "$out" ; then
       rm -f "$out"
       echo "Failed!"
       break
   fi

   rm -f "$src"
   echo "Done up to ${sub[-1]}."
   files=("${left[@]}")

   # rm -f "${sub[@]}"
done

如果sox報告失敗,循環將提前中斷。否則,它將輸出批處理中的姓氏。

我們使用ifforsox命令來檢測故障,如果確實發生故障,則刪除輸出文件。因為我們也推遲了修改files數組,直到sox命令成功之後,我們可以安全地編輯/修復單個文件,然後重新執行while循環,從我們停止的地方繼續。

如果您的磁碟空間不足,您可以取消對倒數第二行的註釋rm -f "${sub[@]}",以刪除所有已成功合併的文件。


上面一遍又一遍地處理初始部分。

ffmpeg正如我在下面的評論中解釋的那樣,如果您首先使用(不使用重新編碼sox)連接文件,然後可能再使用重新編碼傳遞,結果會更好sox。(或者,您當然可以先重新編碼。)

首先,您創建一個以管道分隔的文件名列表(字元串),

files="$(ls -1 prefix_????.mp3 | tr '\n' '|')"

移除最後多餘的管道,

files="${files%|}"

並將它們提供給ffmpeg,無需重新編碼:

ffmpeg -i "concat:$files" -codec copy output.mp3

請注意,您可能希望執行

ulimit -n hard

將打開文件的數量提高到目前程序允許的最大值(硬限制);您可以使用ulimit -n. (我不記得是ffmpeg concat:按順序打開原始碼還是一次全部打開。)

如果您不止一次這樣做,我會將其全部放入一個簡單的腳本中:

#!/bin/bash
export LANG=C LC_ALL=C
if [ $# -le 2 -o "$1" = "-h" -o "$1" = "--help" ]; then
   exec >&2
   printf '\n'
   printf 'Usage: %s -h | --help ]\n' "$0"
   printf '       %s OUTPUT INPUT1 .. INPUTn\n' "$0"
   printf '\n'
   printf 'Inputs may be audio mp3 or MPEG media files.\n'
   printf '\n'
   exit 1
fi

output="$1"
shift 1
ulimit -n hard

inputs="$(printf '%s|' "${@}")"
inputs="${inputs%|}"

ffmpeg -i "concat:$inputs" -codec copy "$output"
retval=$?

if [ $retval -ne 0 ]; then
   rm -f "$output"
   echo "Failed!"
   exit $retval
fi

# To remove all inputs now, uncomment the following line:
# rm -f "${@}"
echo "Success."
exit 0

請注意,因為我使用-codec copy而不是-acodec copy,所以以上內容適用於所有類型的 MPEG 文件,而不僅僅是 mp3 音頻文件。

引用自:https://unix.stackexchange.com/questions/310937