我有一个名为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 脚本中删除不需要的文件?
答案1
首先,将列表收集到 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 音频文件。
答案2
(为了清晰起见进行了编辑,并使其更安全)
如果文件序列中没有间隙,这应该可以工作。只需替换LAST=0
为序列中的最后 4 位数字即可。你会剩下script_name_output.mp3
.
# make a backup in case anything goes wrong
mkdir backup && cp *.mp3 backup
# enter last 4-digit number in the file sequence
LAST=0
LASTNN__=$(echo ${LAST:0:2})
LAST__NN=$(echo ${LAST:2:2})
# sox 100 files at a time
for i in $(seq -f "%02g" 0 $((--LASTNN__))); do
LIST=$(paste -sd' ' <(seq -f "prefix_$i%02g.mp3" 0 99));
sox $LIST script_name_output_$i.mp3;
done
# sox the last group
LAST_LIST=$(paste -sd' ' \
<(seq -f "prefix_${LASTNN__}%02g.mp3" 0 $LAST__NN))
sox $LAST_LIST script_name_output_${LASTNN__}.mp3
# concatenate all the sox'ed files
OUTPUT_LIST=$(paste -sd' ' \
<(seq -f "script_name_output_%02g.mp3" 0 $LASTNN__))
sox $OUTPUT_LIST script_name_output.mp3
# delete the intermediate files
rm $OUTPUT_LIST
# delete input files if everything worked
rm prefix_*.mp3
答案3
您也许可以提高文件描述符限制:
ulimit -n 11000
作为普通用户,您应该能够将该限制提高到难的限制。请参阅ulimit -Hn
当前的硬限制。
非 root 进程无法提高硬限制(这就是重点,管理员设置它是为了防止普通用户滥用系统资源)。如果您通过 拥有超级用户访问权限sudo
,则可以启动一个新的非超级用户 shell,并通过以下方式提高硬限制和软限制:
sudo HOME="$HOME" zsh -c 'ulimit -HSn 100000; USERNAME=$SUDO_USER; zsh'
或者那个 sox 命令:
sudo HOME="$HOME" zsh -c 'ulimit -HSn 100000; USERNAME=$SUDO_USER
sox prefix_*.mp3 script_name_output.mp3'
如果在 Linux 上,您还可以prlimit
以 root 身份调用该命令来提高 shell(及其子 shell)的限制:
bash-4.3$ ulimit -n
1024
bash-4.3$ ulimit -Hn
65536
bash-4.3$ sudo prlimit --nofile=100000:100000 --pid="$$"
bash-4.3$ ulimit -Hn
100000
bash-4.3$ ulimit -n
100000
否则,您可以分两步完成这项工作:将文件分成 347 个文件组并连接,然后连接中间文件。
和zsh
:
intermediate_concat() sox "$@" intermediate.$((++n)).mp3
autoload zargs
n=0
zargs -n 347 prefix_*.mp3 -- intermediate_concat
sox intermediate.*.mp3(n) script_name_output.mp3