2

I want to move all video files with a certain duration to some directory.  In this answer I found a way to probe the duration of a video file:

ffprobe -i some_video -show_entries format=duration -v quiet -of csv="p=0"

I wanted to do something like this with it:

find . -test <test for duration> -exec mv {} /path/to/target ';'

However, it seems that find does not support arbitrary scripts for the test (there is no -test switch). Did I miss something? Is this simply not possible with find? Am I better off just writing a python script?

user1785730
  • 279
  • 3
  • 15
  • which tool / os do you use? – Máté Juhász Feb 22 '18 at 10:28
  • linux and a terminal – user1785730 Feb 22 '18 at 10:29
  • 1
    `find` in Linux *does* support arbitrary scripts for tests. See [this answer of mine](https://superuser.com/a/1221882/432690). – Kamil Maciorowski Feb 22 '18 at 10:39
  • 1
    When you need complex shell processing for files discovered by a `find` command, it is usually easier to use the construct `find ... | while read -r f; do ... ; done`. This allows you to use all normal scripting on the current file `"$f"` , such as `find ... | while read -r f; do DurationTest "$f" && mv "$f" "/path/to/target"/; done`. Note that this construct handles most file names, except when there are leading or trailing blanks or embedded new-lines, though leading and trailing blanks are included in "$REPLY" if you use `read -r` without a variable. – AFH Feb 22 '18 at 11:26

1 Answers1

2

You can use -exec as your -test.  Write a script that takes one filename argument and exits 0 if the file meets your condition and non-zero otherwise.  I don’t have the FF software (e.g., ffprobe) software on my system, so I can’t check this, but something like

#!/bin/sh
if dur=$(ffprobe -i "$1" -show_entries format=duration -v quiet -of csv="p=0")
then
        [ "$dur" -ge 900 ]
        exit
else
        exit
fi

would check for a video having duration ≥ 15 minutes, assuming ffprobe outputs an integer number of seconds.  (If it reports mm:ss or ss.ddd, I leave the trivial modifications to you, but see my example below for inspiration.)  Explanation: the if fails if ffprobe fails; in that case, we drop to the lower exit and exit with the error status from ffprobe.  If ffprobe succeeds, we test its output ("$dur") and then exit with the result of that test.  This could also be written

[ "$dur" -ge 900 ]  &&  exit 0  ||  exit 1

In fact, the entire script can really be reduced to

dur=$(ffprobe -i "$1" -show_entries format=duration -v quiet -of csv="p=0")  \
                            &&  [ "$dur" -ge 900 ]

So call your script something like check_dur; then you can do

find . -exec ./check_dur {} ';'-exec mv {} /path/to/target ';'

This works because, as I said, -exec functions as a test, and things after a -exec will be processed only if the first -exec succeeded (exited with status 0).

As I said, I don’t have the FF software (e.g., ffprobe) software on my system, so I tested with this script:

#!/bin/sh
if mod=$(stat -c%y "$1")
then
        mod=${mod#*-}
        mod=${mod%%-*}
        mod=${mod#0}
        [ $(( mod%2 )) = 0 ]
        exit
else
        exit
fi

which tests whether the modification date of a file or directory is in an even-numbered month.  Note: on my (en_US) system, stat -c%y filename outputs the modification date in the format yyyy-mm-dd HH:MM:SS.ddddddddd -(TZ).  So the above code

  1. strips off the year (from the left),
  2. strips off everything after the month, and
  3. strips off a leading 0, if any (because $((…)) interprets a leading zero as an indicator that a number is octal, and so it reports an error for 08 and 09).

A couple of other notes:

  1. For mv, the command can be improved a little by changing it to

     find . -exec ./check_dur {} ';' -exec mv -t /path/to/target {} +
    

which will execute mv once with many files.  You cannot do this with the first -exec, because -exec ... + cannot be used as a test.  2. If you don’t want to write a separate script, you can use sh -c and inline the script; e.g.,

    find . -exec sh -c 'dur=$(ffprobe -i "$1" -show_entries format=duration -v quiet -of csv="p=0") && [ "$dur" -ge 900 ]' sh {} ';'-exec mv -t /path/to/target {} +

This creates a one-line mini-script (the string after the sh -c) and passes the filename ({}) to it as an argument.  Some versions of find will let you incorporate the {} into the mini-script, but this is a bad idea:

  • It doesn’t work for all versions of find, and
  • even when it works, in general, it will fail (and maybe even fail catastrophically) for some filenames.