4

I am trying to get the duration of a bunch of files in current directory and its sub-directories all ending in .mp4. I'm trying to use ffprobe. To make things simple, I am not using find, but ls. If it only lists durations in seconds, it will be fine, since summing them all up would probably be beyond me!

My current effort is like this:

 ls -a * |grep .mp4$|xargs ffprobe

This shows

Argument 'SomeVideo.mp4' provided as input filename, but 'DifferentVideo.mp4' was already specified.

How can I make this work? Can ffprobe not just process one file at a time passed via pipe?

Mehdi Haghgoo
  • 543
  • 3
  • 8
  • 22
  • Seems like xargs passes all the lines of video file names. Oh, one more thing. This does not show file paths. So, ffprobe will not find the files. WTH – Mehdi Haghgoo May 19 '20 at 10:50

3 Answers3

8

With exiftool:

$ exiftool -ext mp4 -r -n -q -p '${Duration;$_ = ConvertDuration(our $total += $_)}' . | tail -n 1
4 days 22:14:59

The main difference from @don_cristi's answer to a very similar question, being the -r -ext mp4 which lets exiftool look for files with extensions recursively.

Stéphane Chazelas
  • 522,931
  • 91
  • 1,010
  • 1,501
2

To address your underlying requirement, you should iterate across the files, passing them one at a time to ffprobe. This will allow you to get the durations in hours, minutes, seconds.

for mp4 in *.mp4
do
    echo "Processing $mp4"
    ffprobe "$mp4" 2>&1 | awk '/Duration/ {print $2}'
done

Looking around somewhat more carefully, it's possible to get ffprobe to deliver seconds directly

for mp4 in *.mp4
do
    echo "Processing $mp4"
    ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "$mp4"
done

Having done that, it's not a giant leap to add them all together

for mp4 in *.mp4
do
    echo "Processing $mp4"
    ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "$mp4"
done |
    awk -F '
        { sec += $1 }                                                # Total time (seconds)
        END {
            h = int(sec / 3600);                                     # Hours
            m = int((sec - (h*3600)) / 60);                          # Minutes
            s = sec - (h*3600) - (m*60);                             # Seconds
            printf "%d (%d hour, %d min, %d sec)\n", sec, h, m, s    # Print the result
        }
    '

Finally, to process files not only in the current directory but also in all subdirectories, modify the outer loop. This next step uses a bash feature that allows ** to match subdirectories

shopt -s globstar                                                    # Enable extended globbing
for mp4 in **/*.mp4
do
    echo "Processing $mp4"
    ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "$mp4"
done |
    # Continue as before...
roaima
  • 107,089
  • 14
  • 139
  • 261
1

xargs will add as many arguments from the input as possible in the command - it does not know of the one-file limit of ffprobe

If you want to run ffprobe once per input, use

 ... | xargs -l ffprobe 

or

 ... |  xargs -L 1 ffprobe

For understanding, just compare the output of these two commands

  seq 1 3 | xargs echo
  seq 1 3 | xargs -l echo

Please note that using ls-output as input is discouraged for several reasons.

Better use find - despite your effort for simplification - best by using a NULL-separator to avoid problems with special characters:

find -maxdepth 1 -name '*mp4' -print0 | xargs -0 ffprobe

Next problem is the output of ffprobe - get seconds only like done here. Then you might just sum it up using e.g. awk.

FelixJN
  • 12,616
  • 2
  • 27
  • 48