How to run a script recursively on files inside folders inside folders inside another folder?

I use a script to convert my mp4 files so I can use them inside of DaVinci Resolve, namely this:

for i in *.mp4; do ffmpeg -i "$i" -c:v dnxhd -profile:v dnxhr_hq -pix_fmt yuv422p -c:a pcm_s16le "${i%.*}.mov"; done

I would like to run it recursively to batch convert a (boat)load of files located in folders inside folders inside a main folder.

How do I do this?

Edit:
ChatGPT suggests this:

#!/bin/bash

find /path/to/folder -type f -exec /path/to/your/script {} \;

I can’t try this on my own right now, what do you think?

Yeah, I would use find. I would not call your script from find though. I would replace the for loop in your script with find.

Something like:

find $1 -type f -iname "*.mp4" -exec ffmpeg -i {} -c:v dnxhd \;

I simplified the ffmpeg command to make it more readable but you could put your whole ffmpeg command there.

3 Likes

Thanks, I’ll give this a go when I get to the PC.

Converting video from H.264 or H.265 to a supported codec is only necessary on the non-Studio version (aka free version) of Resolve under Linux.

To save a little space at least, instead of pcm_s16le for audio, you can use alac (Apple Lossless Audio Codec) which will be smaller with zero loss of quality.

I know, I use the free version of Resolve.

Thanks for the tip.

1 Like

I seem to be missing something

find $1 -type f -iname "*.mp4" -exec ffmpeg -i "$i" -c:v dnxhd -profile:v dnxhr_hq -pix_fmt yuv422p -c:a alac "${i%.*}.mov"

gives me find: missing argument to -exec'

I also tried:

find $1 -type f -iname "*.mp4" -exec ffmpeg -i {} -c:v dnxhd \;

and I am getting:

ffmpeg version n7.0.2 Copyright (c) 2000-2024 the FFmpeg developers
  built with gcc 14.2.1 (GCC) 20240910
  configuration: --prefix=/usr --disable-debug --disable-static --disable-stripping --enable-amf --enable-avisynth --enable-cuda-llvm --enable-lto --enable-fontconfig --enable-frei0r --enable-gmp --enable-gnutls --enable-gpl --enable-ladspa --enable-libaom --enable-libass --enable-libbluray --enable-libbs2b --enable-libdav1d --enable-libdrm --enable-libdvdnav --enable-libdvdread --enable-libfreetype --enable-libfribidi --enable-libgsm --enable-libharfbuzz --enable-libiec61883 --enable-libjack --enable-libjxl --enable-libmodplug --enable-libmp3lame --enable-libopencore_amrnb --enable-libopencore_amrwb --enable-libopenjpeg --enable-libopenmpt --enable-libopus --enable-libplacebo --enable-libpulse --enable-librav1e --enable-librsvg --enable-librubberband --enable-libsnappy --enable-libsoxr --enable-libspeex --enable-libsrt --enable-libssh --enable-libsvtav1 --enable-libtheora --enable-libv4l2 --enable-libvidstab --enable-libvmaf --enable-libvorbis --enable-libvpl --enable-libvpx --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxcb --enable-libxml2 --enable-libxvid --enable-libzimg --enable-libzmq --enable-nvdec --enable-nvenc --enable-opencl --enable-opengl --enable-shared --enable-vapoursynth --enable-version3 --enable-vulkan
  libavutil      59.  8.100 / 59.  8.100
  libavcodec     61.  3.100 / 61.  3.100
  libavformat    61.  1.100 / 61.  1.100
  libavdevice    61.  1.100 / 61.  1.100
  libavfilter    10.  1.100 / 10.  1.100
  libswscale      8.  1.100 /  8.  1.100
  libswresample   5.  1.100 /  5.  1.100
  libpostproc    58.  1.100 / 58.  1.100
Trailing option(s) found in the command: may be ignored.
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from './Analog Frames/4K/4K Analog Kodak Frame Both.mp4':
  Metadata:
    major_brand     : mp42
    minor_version   : 0
    compatible_brands: mp42mp41
    creation_time   : 2023-09-25T18:47:37.000000Z
  Duration: 00:00:04.80, start: 0.000000, bitrate: 41226 kb/s
  Stream #0:0[0x1](eng): Video: h264 (Main) (avc1 / 0x31637661), yuv420p(tv, bt709, progressive), 3840x2160, 41186 kb/s, 29.97 fps, 29.97 tbr, 30k tbn (default)
      Metadata:
        creation_time   : 2023-09-25T18:47:37.000000Z
        handler_name    : ?Mainconcept Video Media Handler
        vendor_id       : [0][0][0][0]
        encoder         : AVC Coding
At least one output file must be specified

Most likely this is because you are trying to use $i from your old for loop. Try replacing $i with {}. You also need \; at the end. Basically, {} is replaced with the file from find and \; tells find where the end of the command is.

Also, that bash substitution you are using won’t work with find results easily. You will need to do that a different way.

That is because this is a partial command so you are missing some of the arguments to ffmpeg

I guess I overestimated my abilities. I’m at a loss. Might I get a more specific command to run?

find . -name '*.mp4' -print0|xargs -I{} -0 echo {} \;
#insert yr command instead of echo
OR
find . -iname \*.mp4 -exec /your/image/conversion/script.sh {} \;

Prompt: How do I run a script recursively on files inside folders inside folders inside another folder?
Response: Let’s break down how to run a script recursively on files within nested folders.

Understanding the Problem:
You want your script to process all files, regardless of their location within a complex folder structure. This is where recursion comes in handy! It allows your script to “dive” into subfolders and handle each file individually.

The Solution: Using find with -maxdepth (and optionally xargs)

Here’s how you can achieve this using the powerful combination of find, -maxdepth, and potentially xargs:

  1. Find Files:
    find . -type f -print0 | xargs -I {} sh -c 'echo "Processing file: {}".' 
    
  2. Explanation
    • find . -type f: This searches for files (not directories) within the current directory (.).
    • -print0: Prints each found file separated by a null character, which helps handle filenames with spaces or special characters.
    • xargs -I {} sh -c 'echo "Processing file: {}".': This pipes the output of find to xargs, which then passes each filename ({}) as an argument to the command sh -c. The -c option tells sh to execute a shell command. In this case, it prints a message indicating that the file is being processed.

Example:
Let’s say you have these files in your structure:

/my_project/data/file1.txt
/my_project/data/subfolder/file2.csv
/my_project/scripts/script.py 

The command above would find all the text files and print a message for each one, regardless of their location within your project’s structure.

Key Points:

  • Flexibility: This approach is highly flexible as you can easily modify the script to perform any desired action on the found files (e.g., processing them with Python).
  • Recursion: The find command recursively searches through all subdirectories, ensuring that every file within your nested folder structure is processed.

Additional Tips:

  • You might want to use a more specific pattern in your find command if you have files of different types or need to exclude certain files from processing.
  • Error Handling: You can incorporate error handling (e.g., using if) within the script’s execution to handle unexpected situations, such as file not found errors.
1 Like

This gave me

bash: syntax error near unexpected token `do'

OR

And this gave me

[in#0 @ 0x5d0da9fafe80] Error opening input: No such file or directory
Error opening input file *.mp4.
Error opening input files: No such file or directory

Which is really strange.

I appreciate this very much, but sadly it’s of little use to me. I’m finding it very hard to understand a lot of the concepts in there. So much so that I wouldnt even know where to start with this.

I’m sorry, but this was wasted on me, I’m not nearly knowledgable enough to take this information and actually apply it.

Roughly how many different directories of videos do you think you’ll need to convert? I can suggest a simpler, and perhaps safer (see below) command on a per directory basis.

This will convert all MP4’s in the current directory, to MOV, re-encoding them in codecs support be Resolve free.

ls *.mp4 | parallel ffmpeg -i {} -c:v dnxhd -profile:v dnxhr_hq -pix_fmt yuv422p -c:a alac {.}.mov

I want to caution, that converting from H.264 / H.265 to a intermediate codec like dnxhd (or Cineform for argument sake), is going to produce massively bigger files.

It’s not something I would readily do to say hundreds of videos at once, unless I knew I had oodles of storage space and a lot of time spare to run the conversion.

There’s a main directory, containing two sub-folders, where each have 8 subfolders of their own, some of which, in turn, have their own subfolder, which contains the actual .mp4 files.

This command just gives me a > and a blinking cursor. Then nothing happens.

Now regarding this,

I am aware of that and it’s fine. We’re talking about files that are really small, some of which are not even a second long. This is a sample pack of all sorts of overlays and transitions. Namely this.

Ah, there was a " mistakenly placed on the end. I’ve edited my earlier post now to remove that.

$ ls *.mp4 | parallel ffmpeg -i {} -c:v dnxhd -profile:v dnxhr_hq -pix_fmt yuv422p -c:a alac {.}.mov
bash: parallel: command not found
ls: cannot access '*.mp4': No such file or directory
sudo pacman -S parallel

That tool (parallel) can significantly speed up bulk transcoding tasks.

Make sure you’re running that in a directory that contains the mp4 files.

ls: cannot access '*.mp4': No such file or directory

I am running the command inside the main folder…

Are there mp4 files in that directory?

Not directly inside. There are mp4 files about 2 folders deep inside that directory.