12

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

3 Answers 3

14

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 …).

2
  • 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, 2019 at 1:06
  • amazing, thank you. I used this for renaming gunzip files to append an extension
    – Merlin
    Jul 30, 2019 at 18:50
0

Using the fd command (a find alternative), this could be solved with:

fd -e mp4 -x ffmpeg -i {} -vn -acodec copy {.}.aac -hide_banner

where:

  • fd invoke fd command
  • -e mp4 search for files with the mp4 extension
  • -x exec command for each search result
  • ffmpeg -i {}placeholder {} contains the current search result passed to ffmpeg as input
  • {.} output path without file extension (note the dot)
-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.

1

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .