将有序列表拆分为子列表

将有序列表拆分为子列表

我有一个名为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报告失败,循环将提前中断。否则,它将输出已处理的批次中的姓氏。

我们使用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 音频文件。

答案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

相关内容