First, gather the list into a Bash array. If the files are in the current directory, you can use
files=(prefix_????.mp3)
Alternatively, you can use find and sort,
IFS=$'\n' ;
files=($(find . -name 'prefix_*.mp3' printf '%p\n' | sort -d))
Setting IFS
tells Bash to split only at newlines. If your file and directory names do not contain spaces, you can omit it.
Alternatively, you can read the file names from a file, say filelist
, one name per line, and no empty lines,
IFS=$'\n'
files=($(<filelist))
If you might have empty lines in there, use
IFS=$'\n'
files=($(sed -e '/$/ d' filelist))
Next, decide how many files you want in each slice, the name of the temporary accumulator file, as well as the final combined file name:
s=100
src="combined-in.mp3"
out="combined-out.mp3"
Then, we just need to slice the list, and process each sublist:
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
If sox
reports a failure, the loop will break early. Otherwise, it will output the last name in the batch processed.
We use an if
for the sox
command to detect the failure, and remove the output file if indeed a failure occurred. Because we also postpone modifying the files
array until after a successful sox
command, we can safely edit/fix individual files, and then just rerun the while
loop, to continue where we stopped.
If you are short on disk space, you can uncomment the second-to-last line, rm -f "${sub[@]}"
, to remove all files that have been successfully combined.
The above processes the initial parts over and over again.
As I explained in a comment below, the results will be much better if you concatenate the files first using ffmpeg
(without recoding using sox
), possibly followed by a recoding pass using sox
. (Or, you could recode each first, of course.)
First, you create a pipe-separated list (string) of the file names,
files="$(ls -1 prefix_????.mp3 | tr '\n' '|')"
remove the final superfluous pipe,
files="${files%|}"
and feed them to ffmpeg
, with no recoding:
ffmpeg -i "concat:$files" -codec copy output.mp3
Note that you may wish to run
ulimit -n hard
to raise the number of open files to the maximum allowed for the current process (hard limit); you can query it using ulimit -n
. (I don't recall whether ffmpeg
concat:
opens the sources sequentially or all at once.)
If you do this more than once, I'd put it all into a simple script:
#!/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
Note that because I use -codec copy
instead of -acodec copy
, the above should work for all kinds of MPEG files, not just mp3 audio files.
xargs
command? – steeldriver Sep 20 '16 at 01:00sox FAIL formats: can't open input file
x.wav': Too many open files` – xralf Sep 22 '16 at 13:00sox
but apparently when joining files you can split the whole list into smaller sublists and run them separately, joining the results.... If so there's a similar question here - it's about zipping files but the major difference is that you need to join the resulting pieces too and remove them thereafter but that should be trivial... – don_crissti Sep 22 '16 at 14:38ulimit -n 10240
before running thesox
command to raise the limit (to the maximum any user is allowed to raise it, by default), and you should avoid the error altogether. – Nominal Animal Sep 22 '16 at 17:17sox
limitation... – don_crissti Sep 22 '16 at 17:27