Dividindo a lista ordenada em sublistas

3

Eu tenho uma lista de arquivos com nomes prefix_0000.mp3 ... prefix_x.mp3 , onde max (x) = 9999.

Eu tenho o script bash:

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

Como posso dividir melhor a lista ordenada de arquivos mp3 em sub-listas (com ordem de retenção) e gradualmente sox deles e remover arquivos desnecessários em um script bash?

    
por xralf 20.09.2016 / 00:36

3 respostas

2

Primeiro, reúna a lista em um array Bash. Se os arquivos estiverem no diretório atual, você pode usar

files=(prefix_????.mp3)

Alternativamente, você pode usar localizar e classificar,

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

A configuração IFS diz ao Bash para dividir apenas as novas linhas. Se seus nomes de arquivos e diretórios não contiverem espaços, você poderá omiti-los.

Como alternativa, você pode ler os nomes dos arquivos de um arquivo, digamos filelist , um nome por linha e nenhuma linha vazia,

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

Se você tiver linhas vazias, use

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

Em seguida, decida quantos arquivos você deseja em cada fatia, o nome do arquivo acumulador temporário, bem como o nome do arquivo combinado final:

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

Então, só precisamos dividir a lista e processar cada sublistagem:

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

Se sox relatar uma falha, o loop será interrompido antecipadamente. Caso contrário, será emitido o sobrenome no lote processado.

Usamos um if para o comando sox para detectar a falha e removemos o arquivo de saída se, de fato, ocorreu uma falha. Como também adiamos modificar o files array até depois de um comando sox bem-sucedido, podemos editar / corrigir arquivos individuais com segurança e, em seguida, apenas executar novamente o while loop para continuar onde paramos.

Se você estiver com pouco espaço em disco, poderá remover o comentário da segunda à última linha, rm -f "${sub[@]}" , para remover todos os arquivos que foram combinados com êxito.

O acima processa as partes iniciais repetidas vezes.

Como expliquei em um comentário abaixo, os resultados serão muito melhores se você concatenar os arquivos primeiro usando ffmpeg (sem recodificar usando sox ), possivelmente seguido por uma passagem de recodificação usando sox . (Ou você pode recodificar cada primeiro, é claro).

Primeiro, você cria uma lista separada por pipe (string) dos nomes dos arquivos,

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

remova o tubo supérfluo final,

files="${files%|}"

e alimente-os para ffmpeg sem recodificação:

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

Note que você pode querer correr

ulimit -n hard

para aumentar o número de arquivos abertos para o máximo permitido para o processo atual ( hard limit ); você pode consultá-lo usando ulimit -n . (Não me lembro se ffmpeg concat: abre as fontes sequencialmente ou todas de uma vez.)

Se você fizer isso mais de uma vez, eu colocaria tudo em um script simples:

#!/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

Observe que, como eu uso -codec copy em vez de -acodec copy , o acima deve funcionar para todos os tipos de arquivos MPEG, não apenas arquivos de áudio mp3.

    
por 22.09.2016 / 20:03
2

(editado para maior clareza e para torná-lo mais seguro)

Isso deve funcionar se não houver lacunas na seqüência do arquivo. Apenas substitua LAST=0 pelo último número de 4 dígitos na sua sequência. Você ficará com 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
    
por 20.09.2016 / 06:26
1

Você pode aumentar o limite do descritor de arquivo:

ulimit -n 11000

Como usuário comum, você deve ser capaz de aumentar esse limite até o limite hard . Veja ulimit -Hn para o limite rígido atual.

Um processo não-raiz não pode aumentar o limite rígido (esse é o ponto principal, o administrador o define para impedir que usuários comuns abusem dos recursos do sistema). Se você tiver acesso de superusuário via sudo , você pode iniciar um novo shell não-superusuário com o limite rígido e flexível gerado com:

sudo HOME="$HOME" zsh -c 'ulimit -HSn 100000; USERNAME=$SUDO_USER; zsh'

Ou esse comando sox:

sudo HOME="$HOME" zsh -c 'ulimit -HSn 100000; USERNAME=$SUDO_USER
                          sox prefix_*.mp3 script_name_output.mp3'

Se no Linux, você também pode chamar o comando prlimit como root para aumentar o limite do seu shell (e seus filhos):

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

Caso contrário, você poderia fazer o trabalho em duas etapas: concatenar os arquivos em grupos de 347 arquivos e depois concatenar os arquivos intermediários.

com 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
    
por 23.09.2016 / 18:40