Search code examples
bashffmpegterminalechosubdirectory

path issues with FFMPEG Bash script to concat and encode across multiple subfolders


I'm trying to write a bash script for Mac OSx Terminal to compress a series of GoPro .MP4 videos from the SDcard directly into a smaller .MP4s on a local network server. The GoPro saves .MP4s in the 100GOPRO folder on the card. After filming, I will through that folder and manually put .MP4s from each game into subfolders within the 100GOPRO folder, named A1, A2, A3, etc.

Folder structure

/GoPro/DCIM/100GOPRO/
               -------/A1/
                       -----GX01xxx1.mp4
                       -----GX01xxx2.mp4
               -------/A2/
                       -----GX01xxx3.mp4
                       -----GX01xxx4.mp4
                       -----GX01xxx5.mp4
                       -----GX01xxx6.mp4

...etc

I would like then like to run a script from the 100GOPRO folder that will do these steps:

  1. Within each subfolder, auto-create a file.txt with the names of the subfolder's .MP4s in the format to concat the files (each line has "file 'GX01xxx3.mp4'")
  2. Pass that subfolder's file.txt as the input to ffmpeg to reencode and save to a network folder with the name A1.mp4 or A2.mp4
  3. Repeat for each subfolder and quit.

I'm getting hung up on the dynamic path to the subfolder's file.txt. My code just creates a file.txt in the 100GOPRO folder, and appends all the subfolder contents into that single long combined text file. The output then would create a correct first MP4, but second MP4 contains folder 1 and 2, then 3 contains 1, 2, and 3, etc.

Here's the script I ran:

#!/bin/bash
for f in A*/*.mp4 ; do
echo file \'$f\' >> list.txt ;
done && ffmpeg -f concat -safe 0 -i list.txt /Volume/Server/Videos/A$f.mp4 && rm list.txt

Clearly, failing in how that path for echo to save in the subfolder A*, how to call that subfolder's file.txt as the input for ffmpeg, and how to name the output after the folder.

Thanks for any help you can offer.


Solution

  • Basically you need to get the directories, and then make the list of files for that directory. You could use find if you want more control, or a shell glob for simplicity (but this could accidentally match against other files).

    for d in $(find . -maxdepth 1 -type d -name 'A*'); do
        printf "file '%s'\n" $(cd $d && echo *.mp4) > $d/inputs.txt
        ffmpeg -f concat -safe 0 -i $d/inputs.txt /Volume/Server/Videos/${d:2}.mp4
    done
    

    or the following loop using a shell glob:

    for d in A*; do
        # same code as above
    done
    

    Explanation

    • The find looks for directories matching A*, and limited to subdirectories only 1-level deep; note, specifying a directory(the .) is necessary on MacOS when using the default find command (BSD flavour), but with the GNU flavour (installed with brew or on Linux) the default directory is the current directory (.)
    • Since ffmpeg expects the file paths to be relative to the list, we cd into the directory in a subshell, and generate the list of files with the glob *.mp4, and the printf formats that into a file list but iterating over it
    • the ffmpeg command is as you specified, the output filename uses the directory name, except it trims the first two characters (transforming ./A1 -> A1)