Por que esse loop não funciona?

3

Isso está no Ubuntu 12.04. Estou tentando descobrir como fazer com que o ffmpeg faça uma conversão em lote de FLACs para MP3, de forma recursiva. Se eu cd em um diretório e use

for f in *.flac; do ffmpeg -i "$f" -c:a libmp3lame -q:a 2 "${f/%flac/mp3}"; done

funciona perfeitamente bem. No entanto, quando tento isso, não funciona:

for f in "$(find . -type f -name *.flac)"; do ffmpeg -i "$f" -c:a libmp3lame -q:a 2 "${f/%flac/mp3}"; done

Ele nem sequer gera erros úteis (mas aqui está a saída, não é necessário reclamar):

evilsoup@enchantment:~/Music/Jean Sibelius$ for f in "$(find . -type f -name *.flac)"; do ffmpeg -i "$f" -c:a libmp3lame -q:a 2 "${f/%flac/mp3}"; done
ffmpeg version git-2012-12-18-b7e085a Copyright (c) 2000-2012 the FFmpeg developers
  built on Dec 18 2012 19:23:11 with gcc 4.6 (Ubuntu/Linaro 4.6.3-1ubuntu5)
  configuration: --enable-gpl --enable-libfaac --enable-libfdk-aac --enable-libmp3lame --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-librtmp --enable-libtheora --enable-libvorbis --enable-libvpx --enable-x11grab --enable-libx264 --enable-nonfree --enable-version3
  libavutil      52. 12.100 / 52. 12.100
  libavcodec     54. 80.100 / 54. 80.100
  libavformat    54. 49.102 / 54. 49.102
  libavdevice    54.  3.102 / 54.  3.102
  libavfilter     3. 28.100 /  3. 28.100
  libswscale      2.  1.103 /  2.  1.103
  libswresample   0. 17.102 /  0. 17.102
  libpostproc    52.  2.100 / 52.  2.100
./Symphonies 1, 2, 3 & 5 (Oslo Philharmonic Orchestra Conducted by Mariss Jansons) Disc 1/02. Symphony No.1.flac
./Symphonies 1, 2, 3 & 5 (Oslo Philharmonic Orchestra Conducted by Mariss Jansons) Disc 1/03. Symphony No.1.flac
./Symphonies 1, 2, 3 & 5 (Oslo Philharmonic Orchestra Conducted by Mariss Jansons) Disc 1/stripped2.flac
./Symphonies 1, 2, 3 & 5 (Oslo Philharmonic Orchestra Conducted by Mariss Jansons) Disc 1/05. Symphony No.1.flac
./Symphonies 1, 2, 3 & 5 (Oslo Philharmonic Orchestra Conducted by Mariss Jansons) Disc 1/stripped3.flac
./Symphonies 1, 2, 3 & 5 (Oslo Philharmonic Orchestra Conducted by Mariss Jansons) Disc 1/09. Andante festivo.flac
./Symphonies 1, 2, 3 & 5 (Oslo Philharmonic Orchestra Conducted by Mariss Jansons) Disc 1/08. Symphony No.3.flac
./Symphonies 1, 2, 3 & 5 (Oslo Philharmonic Orchestra Conducted by Mariss Jansons) Disc 1/01. Finlandia.flac
./Symphonies 1, 2, 3 & 5 (Oslo Philharmonic Orchestra Conducted by Mariss Jansons) Disc 1/07. Symphony No.3.flac
./Symphonies 1, 2, 3 & 5

Eu testei o comando find sozinho, e ele funciona como esperado, então o problema tem a ver com a interação entre find e for .

Estou ciente de que eu poderia fazer algo com a opção find -exec , mas não consigo encontrar nenhuma maneira de fazer a substituição de string como posso com um loop bash for , e eu em vez disso, não temos um monte de file.flac.mp3 s para lidar, mesmo se eles pudessem ser corrigidos com um simples rename .

    
por evilsoup 19.12.2012 / 14:57

3 respostas

3

As aspas duplas em torno do comando $(find ...) fazem com que a saída de find seja vista como um único nome de arquivo, o que obviamente não é o que você deseja.

Existem várias maneiras de conseguir o que você deseja:

  • Faça a busca executar o ffmpeg diretamente (sem tubulação, sem loops):

    find . -type f -name *.flac -exec bash -c '
       ffmpeg -i "$0" -c:a libmp3lame -q:a 2 "${0/%flac/mp3}"
    ' {} \;
    
  • Use find ... -print0 | xargs -0 ... em vez de para, que é feito especificamente para essas finalidades:

    find . -type f -name *.flac -print0 | xargs -0 -I {} bash -c '
        ffmpeg -i "$0" -c:a libmp3lame -q:a 2 "${0/%flac/mp3}"
    ' {}
    
  • Altere IFS apenas para nova linha, use o mesmo comando de antes, menos as aspas duplas:

    IFS=$'\n'
    for f in $(find . -type f -name *.flac); do
        ffmpeg -i "$f" -c:a libmp3lame -q:a 2 "${f/%flac/mp3}"
    done
    unset IFS
    

    O comando unset IFS altera o IFS de volta para seu valor padrão (não é necessário dentro de um script de shell, a menos que interfira nos comandos subseqüentes).

por 19.12.2012 / 15:11
3

Por favor, leia BashFAQ / 020 - Wiki de Greg . Para iterar adequadamente a saída de find , aqui está um método que deve lidar com todos os tipos de caracteres estranhos em nomes de arquivos (espaços, novas linhas, globbing, etc.)

while IFS= read -r -d '' file; do
   ffmpeg -i "$f" -c:a libmp3lame -q:a 2 "${f/%flac/mp3}"
done < <(find . -type f -name "*.flac" -print0)

Não se esqueça de citar "*.flac" para impedir a expansão se houver arquivos no diretório atual que terminem com .flac .

    
por 19.12.2012 / 15:15
0

Além das ótimas respostas listadas aqui, descobri que o GNU Parallel pode fazer isso:

find . -type f -name "*.flac" | parallel ffmpeg -i {} -c:a libmp3lame -q:a 2 {.}.mp3

{.} retira a extensão do arquivo do nome do arquivo. O GNU Parallel usa nova linha como um separador por padrão, portanto não há necessidade de usar find ... -print0 | parallel -0 ... , a menos que você esteja antecipando nomes de arquivos contendo novas linhas.

Como o próprio nome sugere, o GNU Parallel executa processos em paralelo - um trabalho por núcleo de CPU, por padrão. Por isso, pode acelerar um pouco as coisas; não muito neste caso em particular, já que o ffmpeg é multi-thread por si mesmo ... mas ainda assim.

    
por 25.02.2013 / 01:19