3

I have an album of 11 .flac audio files. (edit: since this issue has been resolved, it's now clear that the precise names and content of the files are irrelevant, so I've renamed them):

> find . -name "*.flac"
./two.flac
./ten.flac
./nine.flac
./eight.flac
./seven.flac
./three.flac
./four.flac
./five.flac
./one.flac
./eleven.flac
./six.flac

I was converting these to .wav files with a specific sample rate and bit depth. I used ffmpeg in a Bash shell to do this. A command like this one works perfectly for each of the 11 files if I call it manually:

ffmpeg -i "./six.flac" -sample_fmt s16 -ar 44100 "./six.wav"

However, when I wrote a while loop to run this command for each file:

find . -name "*.flac" | sed "s/\.flac$//" | while read f; do ffmpeg -i "$f.flac" -sample_fmt s16 -ar 44100 "$f.wav"; done

This worked, but only for 8/11 of the files, with ffmpeg giving the following error messages for the three failures:

/ten.flac: No such file or directory
ree.flac: No such file or directory
ix.flac: No such file or directory

For ./ten.flac, the leading . in the relative file path was truncated, resulting in an absolute path, and the other two, ./three.flac and ./six.flac, lose even more characters, including from the start of their basenames. Some of the other eight files had ./ truncated, but this resulted in the correct relative path, so ffmpeg was able to continue in these cases.

If I use the same loop structure, but with echo "$f" instead of calling ffmpeg, I see no problems with the output:

> find . -name "*.flac" | sed "s/\.flac$//" | while read f; do echo "$f"; done
./two
./ten
./nine
./seven
./three
./four
./five
./one
./eleven
./six

So I'm convinced the structure of the loop is fine, with "$f" expanding how I expect it to at each iteration. Somehow, when passing "$f.flac" into ffmpeg, part of the string is truncated, but only for some of the files.

I just want to understand why my first attempt exhibited this behaviour. I'm not looking for an alternative way to loop over or convert the files (my second attempt, with a different kind of loop, was successful for all files).


edit: I accidentally discovered that piping yes | into ffmpeg seems to fix this issue. I added this so I wouldn't be prompted to overwrite existing .wav files.

edit: Thanks @roaima for the explanation! Turns out that ffmpeg and read both inherit the same stdin handle, so ffmpeg could consume characters from the start of each line before read got a chance, thus mangling some of the file paths. This explains why yes | ffmpeg ... worked, since it gave ffmpeg a different stdin handle. The original loop works fine with ffmpeg -nostdin .... See http://mywiki.wooledge.org/BashFAQ/089.

  • Strange the error shows 9. Stranger.flac when the file appears to be 09. Stranger.flac. Lots of things in bash/shell can strip leading 0s on numbers but I don't see anything you are doing that would, and it's inconsistent. – jesse_b Sep 22 '22 at 14:51
  • 1
    https://unix.stackexchange.com/questions/321697/why-is-looping-over-finds-output-bad-practice – gapsf Sep 22 '22 at 14:53
  • https://mywiki.wooledge.org/BashFAQ/001 – gapsf Sep 22 '22 at 14:58
  • I appreciate the links, gapsf, but as I said in the post, I just want to understand what is happening in this particular case; I'm not looking for another way to convert the files (I actually had them all converted hours ago). – colmbaston Sep 22 '22 at 15:00
  • sed "s/.flac//" -> sed "s/.flac//" – gapsf Sep 22 '22 at 15:10
  • You're right I should have escaped the dot in sed, but it makes no difference in this case, and certainly doesn't contribute to the substance of the question... – colmbaston Sep 22 '22 at 15:14
  • Check output of find . -name "*.flac" | sed "s/.flac//" – gapsf Sep 22 '22 at 15:14
  • The output of find . -name "*.flac" | sed "s/\.flac//" is identical to the loop with echo "$f" in the post. – colmbaston Sep 22 '22 at 15:16
  • Yes but you just edit and escape dot in regexp. Check your first variant find . -name "*.flac" | sed "s/.flac//" – gapsf Sep 22 '22 at 15:19
  • I did check, and as I said, escaping the dot makes no difference in this case since none of the file paths contain "flac" anywhere other than in the extension. Exactly the same thing happens when I run the loop with the escaped dot in sed. – colmbaston Sep 22 '22 at 15:21
  • @jesse_b Setting IFS=0 would do that. – Kusalananda Sep 22 '22 at 17:27

1 Answers1

2

Unless you have a directory tree of files (not mentioned, and not shown in your example dataset), you can use a simple loop to process the files

for f in *.flac
do
    w="${f%.flac}.wav"
    ffmpeg -i "$f" -sample_fmt s16 -ar 44100 "$w"
done

If you really do have a hierarchy of files you can incorporate this into a find search

find -type f -name '*.flac" -exec sh -c 'for f in "$@"; do w="${f%.flac}.wav"; ffmpeg -i "$f" -sample_fmt s16 -ar 44100 "$w"; done' _ {} +

For efficiency you might want to skip processing of any flac that already has a corresponding wav. After the w= assignment, add [ -s "$w" ] && continue. If you really don't want that then you can further optimise the command thus,

find -type f -name '*.flac" -exec sh -c 'ffmpeg -i "$1" -sample_fmt s16 -ar 44100 "${1%.flac}.wav";' _ {} \;

For pre-run testing, prefix ffmpeg with echo to see what would get executed without it actually doing so. (Quotes won't be shown, though, so bear that in mind.)


It turns out that the question is actually about ffmpeg chewing up the filenames it's supposed to be processing. This is because ffmpeg reads commands from stdin, and the list of files has been presented as a pipe to a while read … loop (also using stdin).

Solution: ffmpeg -nostdin … or ffmpeg … </dev/null

See

Chris Davies
  • 116,213
  • 16
  • 160
  • 287
  • Why do we need loop with find at all? Simply exec ffmpeg without loop – gapsf Sep 22 '22 at 15:04
  • @gapsf in the find? Partly I've used find … + to roll-up multiple flac files, and partly because I've written the code to more easily be modified to skip existing wav outputs – Chris Davies Sep 22 '22 at 15:12
  • @roaima It's not to do with word splitting. I mentioned in another comment that even when I renamed all the files so they contained no spaces, the same thing occurred. Even when I name the files one.flac through to eleven.flac, I get the same issue, with error messages like ree.flac: No such file or directory (truncating the th in three.flac). I appreciate your alternative suggestions, but I wrote the question because I wanted to understand why the loop as I wrote it behaves as it does, and I'm no closer to understanding at the moment. – colmbaston Sep 22 '22 at 16:05
  • 1
    @Colm oh I see, I didn't realise that was the issue you wanted solving. ffmpeg reads from stdin, so you need to ffmpeg … </dev/null or ffmpeg -nostdin …. Answer updated – Chris Davies Sep 22 '22 at 16:37
  • @roaima Thank you, ffmpeg -nostdin ... works! That also explains why yes | ffmpeg ... seemed to fix the issue, since ffmpeg takes stdin from the pipe. – colmbaston Sep 22 '22 at 16:46
  • "This is because ffmpeg reads commands from stdin" ... uhh, ok. But why does it read just a few characters here and there? :o – ilkkachu Sep 22 '22 at 19:17
  • @ilkkachu https://stackoverflow.com/q/48635380/2344631 – Chris Davies Sep 22 '22 at 20:49