9

Given the situation that audio needs to be extracted from video files residing in subdirectories, how would one go about properly naming the resulting files?

The following one-liner works, but retains the old file extension.

find -name "*.mp4" -exec ffmpeg -i {} -vn -acodec copy {}.aac -hide_banner \;

Current outcome:

foobar.mp4 --> foobar.mp4.aac

Desired outcome:

foobar.mp4 --> foobar.aac
Kamil Maciorowski
  • 69,815
  • 22
  • 136
  • 202
Daktari
  • 123
  • 2
  • 8

2 Answers2

10

There is no mechanism in find itself that allows you to get a substring from whatever is substituted for {}. Even adding a suffix (like you did: {}.aac) may not be supported. POSIX says:

A utility_name or argument containing only the two characters "{}" shall be replaced by the current pathname. If a utility_name or argument string contains the two characters "{}", but not just the two characters "{}", it is implementation-defined whether find replaces those two characters or uses the string without change.

With many implementations things like foo{}bar do work though, nevertheless be warned. To do something more you definitely need a tool that will manipulate the string; usually a shell. In your case:

find . -name "*.mp4" -exec sh -c 'ffmpeg -i "$1" -vn -acodec copy "${1%.*}.aac" -hide_banner' sh {} \;

Here ${1%.*} is responsible for getting the pathname without extension.

You may be tempted to replace ffmpeg -i "$1" with ffmpeg -i "{}" inside the command string passed to sh. Don't do this. My other answer explains why the latter is wrong (and why find . -name … is better than find -name …).

Kamil Maciorowski
  • 69,815
  • 22
  • 136
  • 202
  • Your solution works. Thanks also for linking to your other answer -- it clearly explains some questions I had about this solution. Much appreciated. – Daktari Apr 18 '19 at 01:06
  • amazing, thank you. I used this for renaming `gunzip` files to append an extension – Merlin Jul 30 '19 at 18:50
-1

Another way would be :

for file in $(find . -name "*mp4")
do
ffmpeg -i $file -vn -acodec copy [other parameters if required] ${file%%.mp4}.aac
done

EDIT: I have no idea why I might have thought of such a loop, when the simpler one suffices:

for file in *.mp4
do
ffmpeg -i $file -vn -acodec copy [other parameters if required] ${file%.mp4}.aac
done

However if a recursive find is really required, based on the comment by Scott I decided to improve the last answer. I use a while loop with input from find through process substitution:

while IFS= read -r file
do
ffmpeg -nostdin -i $file -vn -acodec copy [other parameters if required] ${file%.mp4}.aac
done< <(find . -name "*.mp4")

Note that ffmpeg requires -nostdin in a while loop. exec and xargs in general may be more efficient than while loops, but since this instance would have ffmpeg do the processing, I don't feel there is a significant performance hit.

Rajib
  • 3,056
  • 23
  • 26
  • 1
    (1) [Why is looping over find's output bad practice?](https://unix.stackexchange.com/q/321697/23408)  (2) `"*mp4"` ≠ `"*.mp4"`  (3) [Security implications of forgetting to quote a variable in bash/POSIX shells](https://unix.stackexchange.com/q/171346/23408).  (4) Using the double `%` in `${file%%.mp4}` is unnecessary and confusing.  (5) General reference: [Why does my shell script choke on whitespace or other special characters?](https://unix.stackexchange.com/q/131766/23408) – Scott - Слава Україні Dec 20 '19 at 06:20