Optimize find -exec {} with multiple conditions : specific files in a dir and specific subdirectories in this dir

My problem is related to music encoding on my NAS and optimisation of linux find command.

I have write a script that is totally functionnal but I am sure this not the best way.

To summarize:

1/ Script checks for directories that contain flac to encode

  • all directories containing some .flac files
  • the directories NOT containing a subdirectory named mp3/ or Mp3/

For that specific part I have done something like this :

FILE="./DirContaininfFlacToConvertToMp3.txt"
find . -type f -iname '*.flac' -printf '%hn' | sort -u > fic1
find . -iname 'mp3' -type d | sort | sed 's//Mp3//' | sed 's//mp3//' > fic2
comm -3 fic1 fic2 > $FILE
  • The $FILE will be filled with all the directories matching these 2 conditions.
  • fic1 file contains all directories containing flac files.
  • fic2 file contains all directories containing mp3 subdir (sed is used to eliminate mp3/ or Mp3/ at the end of the path for the comm command to work)
  • comm -3 provide me my result in $FILE

It works all right but I am pretty sure this is not the best way to do and it can be optimized just using find parameters or passing it to shell ..

2/ Script Encodes the flac files and move to a Mp3/ subdir

Once the file ($FILE) give the result of the directory that match the conditions next steps I do these:

  • I encode the files in mp3 in the defined directory
  • I move them to a subdirectory named Mp3 (keeping original structure)

For that part I done that in 2 loops:

#1st loop to encode
IFS=$'n'
for next in `cat $FILE`
do
  find $next -type f  -name "*.flac" -exec ffmpeg -i {} -qscale:a 2 -map_metadata 0 -id3v2_version 3 {}.mp3 ;
done

#2nd loop for moving to subdir
for next in `cat $FILE`
do
  find $next -type f -name '*.flac.mp3' -execdir mkdir -p Mp3 ; -execdir mv {} Mp3/ ;
done

Also here I am pretty sure I could avoid iterating in 2 loops.

However you could find for any purpose the full script here (not very well coded I know … 🙁

Full script

#!/bin/bash
# Convert .flac en .mp3 file for Synology with ffmpeg
# usage : 1st construct list of directory to convert : run:  ./script.sh V
#         2nd verify and to convert run : nohup ./script C & 

#result of list of directories to encode
FILE="./ToConvertToMp3.txt"

# parameter validation
if [ "$#" -ne  "1" ]
  then
     echo "1 parameter required : ./script.sh (V)erify (C)onvert "
 exit 1;
fi


# generate list of directory for encoding
if [ "$1" ==  "V" ]
  then
    echo "-------- VERIFY AND CONSTRUCT LIST -------"
    #1 find dir that contains .flac file
    find . -type f -iname '*.flac' -printf '%hn' | sort -u > fic1

    # for those directory search if there is some *mp3* subdir
    IFS=$'n'
    for dirflac in `cat fic1`
    do
      find $dirflac -iname '*mp3*' -type d -exec dirname {} ; >> fic2
    done

    # sort and unicity
    cat fic2 | sort -u > fic3

    # final construc of the list of directory to encode
    comm -3 fic1 fic2 > $FILE
    rm -f fic1 fic2 fic3

    echo "-----DIRECTORY TO CONVERT-------"
    cat $FILE
    echo "-------------------------------------"
    exit 0
fi

# Converting .flav files from directories listed in $FILE
if [ "$1" ==  "C" ]
  then
    echo "-------- CONVERTING FLAC -------"

    if [ -e $FILE ]; then
      echo "$FILE exist : OK"
    else
      echo "$FILE does not exist Please (V)erification first"
    exit 3
    fi

  if [ -s "$FILE" ]; then
    echo "$FILE contains directories : OK"
    cat $FILE
  else
    echo "$FILE is empty , nothing to do"
    exit 2
  fi

  echo "-------- CONVERTING -------"
  IFS=$'n'
  for next in `cat $FILE`
  do
    find $next -type f  -name "*.flac" -exec ffmpeg -i {} -qscale:a 2 -map_metadata 0 -id3v2_version 3 {}.mp3 ;
    album="$(basename $directory)-Mp3";
    echo "  Processing $directory : creating subdirectory : $album, moving files in progress"
    find $directory -type f -name '*.flac.mp3' -execdir mkdir -p "$album" ; -execdir mv {} "$album/" ;

  done
  echo "-------- END OF CONVERSION -------"

  # renaming $FILE with timestamp
  ficdate=$(date +%Y%M%d-%Hh%m);
  mv $FILE $FILE-$ficdate.done;

  exit 0
fi

echo "WARNING : 1 parameter : usage : ./script.sh (V)erification (C)onversion"

Answer

You could do:

find . -type f -name '*.flac' -execdir sh -c '
  if [ ! -d mp3 ] && [ ! -d Mp3 ]; then
    for file do
      ffmpeg -i "$file" -qscale:a 2 -map_metadata 0 -id3v2_version 3 "${file%.*}.mp3"
    done
  fi' sh {} +

The idea being that with -execdir cmd {} +, (with some versions of GNU find), find will run cmd for all the matching file in a given directory.

I say some versions of GNU find because it used to work like that, but then was broken in some versions of find (where you’d get one cmd invocation per file as if you had used -execdir cmd {} ;) and it was fixed again in a later version.

You can check if you have a correct version with:

find . -execdir echo {} +

You should get one line per directory with a correct version, or one line per file with the less correct ones.

If you have a correct version, and provided you don’t have thousands of flac files per directory, you can do both actions in one go:

find . -type f -name '*.flac' -execdir sh -c '
  if [ ! -d mp3 ] && [ ! -d Mp3 ]; then
    mkdir Mp3 || exit
    for file do
      ffmpeg -i "$file" -qscale:a 2 -map_metadata 0 -id3v2_version 3 "Mp3/${file%.*}.mp3"
    done
  fi' sh {} +

Leave a Reply

Your email address will not be published. Required fields are marked *