I'm trying to get FFmpeg to go through all files and sub-folders under the path I run the command in, to convert any MKV file to an MP4 container instead.

I've got the below command working but the new MP4 file that is created still has the MKV extension in addition to the new MP4 extension.

find ./ -iname '*.avi' -o -iname '*.mkv' -exec bash -c 'ffmpeg -i "{}" -vcodec copy -acodec copy "{}".mp4' \;

For example :

abcd.mkv (original file)  
abcd.mkv.mp4 (new file)

How do I adjust the command to get rid of the original file extension?

  • Do you want to rename the existing *.mp4 files or (re-)create them with the proper extension in the first place? – PerlDuck Sep 30 '18 at 13:47
  • 1
    You might be able to construct something by handing the filename over to sed, edit it there and then hand the edited filename over to ffmepg. But my gut feeling is not to go through that hassle and write a little script in your language of choice (Ruby, Python, Perl, Bash). I'd store the output of find in a file and go through that file in a script line by line. – Henning Kockerbeck Sep 30 '18 at 14:11
  • Yes I think the way to go is to just run a rename after the initial conversion process, as suggested. I will go with that for now. Cheers – Andy Pittas Sep 30 '18 at 15:03

There is an easier way to do this, by putting your ffmpeg command into a bash script, and using bash tools. Instead of:

find ./ -iname '*.avi' -o -iname '*.mkv' -exec \
    bash -c 'ffmpeg -i "{}" -vcodec copy -acodec copy "{}".mp4' \;

Use find and xargs to pass a list of the filenames (including filenames with spaces) to, for example, $HOME/bin/fixthem (read man find;man xargs):

find . -type f -iname '*.avi' -o -iname '*.mkv' -print0  |\
    xargs -0 -r $HOME/bin/fixthem

With $HOME/bin/fixthem being something like (and chmod +x'd):

Note: untested, but run through /usr/bin/shellcheck.

# convert the *.mkv and *.avi files to .mp4

# determine my name

# -h or --help 
help () {
    cat >&2 <<EOF
${me} [-h|--help] [-d|--debug] [-v|--verbose] [-n|--noaction] file file ...
${me}: -h or --help      This message
${me}: -d or --debug     List the commands we execute, as we process
${me}:                   the files.
${me}: -v or --verbose   List the filenames we process, as we process
${me}:                   the files.
${me}: -n or --noaction  Don't actually execute the commands. Used in
${me}:                   connection with --debug for testing.
${me}: -r or --remove    Remove the input file unless --debug is set.
    exit 2

declare -i debug=0 verbose=0 noaction=0 remove=0

# from /usr/share/doc/util-linux/examples/getopt-parse.bash 2015-Sep-06

TEMP=$(getopt -o dhvnr --long debug,help,verbose,noaction,remove \
     -n 'fixthem.bash' -- "$@")

if [[ $? != 0 ]] ; then echo "${me} --help for help." >&2 ; exit 1 ; fi

# Note the quotes around `$TEMP': they are essential!
eval set -- "$TEMP"

while true ; do
    case "$1" in
        -d|--debug) debug=1; shift;;
        -h|--help) help; shift;;
        -v|--verbose) verbose=1; shift;;
        -n|--noaction) noaction=1; shift;;
        -r|--remove) remove=1; shift;;
        --) shift; break;;
        *) echo "Internal error! ${me} --help for help";exit 1;;

# actual processing begins here
while [[ $# -gt 0 ]] ; do
      [[ "$verbose" -ne 0 ]] && echo "$infile -> $outfile" >&2
      command="ffmpeg -i \"$infile\" -vcodec copy -acodec copy \"$outfile\""
      [[ "$debug" -ne 0 ]] && echo "command" >&2
      [[ "$noaction" -ne 0 ]] || eval "$command"
      if [[ "$remove" -ne 0 ]] ; then
      [[ "$verbose " -ne 0 ]] && v="-v"
      if [[ "$debug" -ne 0 ]] ; then
          echo "rm \"$infile\"" >&2
          rm $v "$infile"

exit 0

If you are keen to keep your existing syntax (which probably makes sense to you as it is) the following slight modification should keep this meaning while also accomplishing the required filename renaming:

find -iname '*.avi' -o -iname '*.mkv' -exec \
    bash -c 'ffmpeg -i "{}" -codec copy \
    $(echo "{}" | sed -r 's/.{3}$/mp4/')' \;

Note that I have also simplified your copy syntax with FFmpeg...


Another way, if your filenames do not have spaces, newlines or other strange characters:

for file in $(find -iname '*.avi' -o -iname '*.mkv')

ffmpeg -i $file -codec copy ${file%%.*}.mp4

This removes any extension after the last dot (.) and then adds the .mp4. If there is the possibility of spaces in filenames, use this instead:

find . -iname '*.avi' -o -iname '*.mkv' -print0 | while read -d $'\0' file;

  ffmpeg -nostdin -i "$file" -codec copy ${file%%.*}.mp4

Since it was bit of a trouble to mess with much code, I just added a rename command before encoding starts and removed the .mp4 extension.

ren Troc*.mp4 Troc*.
for %%a in ("Troc*.") do "F:\Dropbox\PortableApps\ffmpeg" -i "%%a" -c:v libx264  -crf 22 -c:a aac -b:a 128k "%%a(ff).mp4"

Here's a screenshot from my folder:

thumbnails appear correct for files with adjusted names

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.