4

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?

andrew.46
  • 39,359

4 Answers4

2

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

andrew.46
  • 39,359
0

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.

#!/bin/bash
# convert the *.mkv and *.avi files to .mp4

# determine my name
me=$0
me=${me##*/}

# -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.
EOF
    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;;
    esac
done

# actual processing begins here
while [[ $# -gt 0 ]] ; do
      infile="$1"
      shift
      nameonly="${infile%.*}"
      outfile="${nameonly}.mp4"
      [[ "$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
      v=""
      [[ "$verbose " -ne 0 ]] && v="-v"
      if [[ "$debug" -ne 0 ]] ; then
          echo "rm \"$infile\"" >&2
      else
          rm $v "$infile"
      fi
      fi
done

exit 0
waltinator
  • 37,856
0

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

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

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

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;

do
  ffmpeg -nostdin -i "$file" -codec copy ${file%%.*}.mp4
done
Rajib
  • 101
0

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

Zanna
  • 72,312