Bulk converting mkv files into mp4 files using only one line of xargs

This answer converts a batch of mp4 into mp3 using a for loop.

I want to achieve something similar with only one line of code. After some trial and error, I figured out the following code.

ls | grep .mkv | xargs -I {} ffmpeg -i {} -codec copy {}.mp4

However, the output filenames are now video.mkv.mp4.

I know I can simply write one more line to change the extension

ls | grep .mkv.mp4 | sed -e "p;s/.mkv.mp4$/.mp4/" | xargs -n2 mv

I wonder if there is a one liner solution to it.

Asked By: Raven Cheuk


There’s no reason (apart from readability) that you can’t put a shell loop in a single line of code – and whether you use a shell loop or xargs -I{} you will end up with one invocation of ffmpeg for each file.

Regardless, I’d avoid ls | grep – instead, use a shell glob. So as a one-liner (in any POSIX-like shell such as bash):

for f in ./*.mkv; do ffmpeg -i "$f" -codec copy "${f%.mkv}.mp4"; done

If you’re determined to use xargs, then one option would be to switch to the Z-shell where you can use a glob qualifier to generate the root of each matching filename, and then add the input and output extensions as required. So for example:

print -rNC1 ./*.mkv(.N:r) | xargs -r0 -I{} ffmpeg -i {}.mkv -codec copy {}.mp4    # NB zsh not bash

Note that I switched to null delimiters for the print and xargs – that allows you to process any legal filename, including filenames containing newline characters.

Another option would be to switch from xargs to GNU parallel, which has additional options for the replacement string, including {.} to substitute the filename without extension (equivalent of the loop version’s ${f%.mkv} or the Z-shell’s :r modifier):

printf '%s' ./*.mkv | parallel -q0 ffmpeg -i {} -codec copy '{.}.mp4'    # any shell, including bash
Answered By: steeldriver