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
  • 38,003
  • 27
  • 156
  • 232
  • 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

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
  • 38,003
  • 27
  • 156
  • 232
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
  • 36,399
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
  • 70,465