將有序列表拆分為子列表
我有一個名稱為
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
報告失敗,循環將提前中斷。否則,它將輸出批處理中的姓氏。我們使用
if
forsox
命令來檢測故障,如果確實發生故障,則刪除輸出文件。因為我們也推遲了修改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 音頻文件。