2

I have a directory with several subdirs which contain .flac files. I want to batch convert them to .mp3. The path names contain spaces. Now I want to do something like this

find . -type f -regex .*.flac -exec ffmpeg -i {} -c:a mp3 -b:a 320k  \;

But the question is, how can I use {} and substitute flac for mp3 to give ffmpeg an output file name? In bash, one could use variable substitution ${file%.flac}.mp3, but I cannot apply this here. sed-based approaches don't seem to work with find either. Is there any simple solution to this?

oarfish
  • 4,116
  • 4
  • 37
  • 66

2 Answers2

4

If you're using bash, I'd use globstar, which will expand to the list of all your .flac files:

shopt -s globstar nullglob
for file in **/*.flac; do
    [[ -f $file ]] || continue
    out_file=${file%.flac}.mp3
    ffmpeg # your options here, using "$out_file"
done

The check [[ -f $file ]] skips any directories that end in .flac. You could probably skip this if you don't have any of those.

I've also enabled the nullglob shell option, so that a pattern which doesn't match any files expands to nothing (so the loop won't run).

Tom Fenech
  • 72,334
  • 12
  • 107
  • 141
2

If you really want to use find (e.g., you're stuck in the past with an antique version of Bash and globstar isn't available — so using Tom Fenech's solution is not an option), you have to spawn a shell to do the substitution. The standard way would be:

find . -name '*.flac' -type f -exec sh -c 'ffmpeg -i "$1" -c:a mp3 -b:a 320k  "${1%.flac}.mp3"' dummy {} \;

sh -c will run its first argument, and the subsequence arguments are set to the positional parameters starting from 0. That's why I put a dummy parameter (and I called it dummy, but anything else could do).

Note that in find's -name, the argument *.flac needs to be quoted. For some reason you edited your post and replaced the -name predicate with a -regex predicate: it's really useless, and you still need to quote the argument anyways.

gniourf_gniourf
  • 44,650
  • 9
  • 93
  • 104
  • Is it not possible to write `sh -c 'ffmpeg -i "$0" -c:a mp3 -b:a 320k "${1%.flac}.mp3"' {} \;` – Мона_Сах Jun 19 '16 at 12:33
  • 1
    @mona_sax: nope, that's not quite valid since in that case, the positional parameter `$1` is not set! maybe you mean: `sh -c 'ffmpeg -i "$0" -c:a mp3 -b:a 320k "${0%.flac}.mp3"' {} \;`, and that's perfectly valid. – gniourf_gniourf Jun 19 '16 at 12:35
  • 1
    Well yes !! I mean't what you've just pointed out. :). I wanted avoid using a dummy variable. This is indeed an elegant one liner :) +1 – Мона_Сах Jun 19 '16 at 12:38
  • Re quoting: `-regex .*.flac` works unquoted at least on my machine. – oarfish Jun 19 '16 at 13:16
  • @oarfish: that's because you don't have any hidden files with extension `flac`. Now try to create such files, e.g., `touch .gniourf.flac .oarfish.flac` and launch your command… surprise. Moreover, your regex is wrong: you will also match files like `hello-flac`, because a single (non-backslashed) dot corresponds to any character. – gniourf_gniourf Jun 19 '16 at 13:18
  • Yes, the pattern is not valid for all cases, but it's fine if I know what's in my directory. – oarfish Jun 19 '16 at 13:20
  • 1
    @oarfish: Why don't you do it right if you know it's wrong? `-name '*.flac'` is the way to do it. Also, `-regex` is not standard (so won't work with all versions of `find`). – gniourf_gniourf Jun 19 '16 at 13:21
  • Is there an advantage to use `dummy {}` and `$1` over just `{}` and `$0`? Avoiding unusual usage of `$0`, maybe? – Benjamin W. Jun 19 '16 at 19:24
  • 1
    @BenjaminW.: In this case, no advantage, really. But it's useful when using the `-exec ... +` construct and you want to loop on the arguments: `find ... -exec sh -c 'for file; do echo "found $file"; done' _ {} +` (here I used the more common `_` for the dummy argument). – gniourf_gniourf Jun 19 '16 at 21:02