bashshell

Loop through subdirectories and use their names to edit the files within


I have a library of audio files organised as Audio/ArtistName/AlbumName/TrackName.ext. Much of the metadata is incorrect and I want to use FFmpeg Metadata to correct all the files at once.

I found this post to execute a command on files within subfolders. I still require, however, the name of subfolder ArtistName and the subfolder within AlbumName to execute the command on each audio file:

ffmpeg -i inputfile -metadata artist="Subfolder Artist Name" -metadata album="Subfolder Album Name" outputfile

How can I edit the metadata of the files based on the names of the subfolders they are in?

Edit: I am trying to do this in Linux CLI or Bash Script. I am trying to go through folders (artist) and then the subfolders within (albums) and loop through the files and run a command based on the names value of artist folder and album subfolder. I have tried this to loop through the folders but it does not work for the subfolders within to execute a command on the files in the subfolders. I was able to return the artist and album name but I still cannot get the track name. cd into the artist folder worked but not for the album folder

for artist in /tank/music/*/
do
    art= basename "${artist}"
    cd "${artist}"
    for album in ./*/
    do
        alb= basename "${album}"
        cd "${album}"
        for track in ./*/
        do
            echo "${art}"
            echo "${alb}"
            echo "${track}"
        done
    done
done

Solution

  • If you cd down a level, you have to cd back out before you process another value at the same level.

    I assume tracks are files and not directories.

    Untested:

    cd back out:

    for artist in /tank/music/*/
    do
        art=$(basename "${artist}")
        cd "${artist}"
        for album in ./*/
        do
            alb=$(basename "${album}")
            cd "${album}"
            for track in *.ext
            do
                trk=$(basename "$track" .ext)
                echo "${art}"
                echo "${alb}"
                echo "${trk}"
            done
            cd ..
        done
        cd ..
    done
    

    or use subshell so that cd out is implicit:

    for artist in /tank/music/*/
    do
        art=$(basename "${artist}")
        (
            cd "${artist}"
            for album in ./*/
            do
                alb=$(basename "${album}")
                (
                    cd "${album}"
                    for track in *.ext
                    do
                        trk=$(basename "$track" .ext)
                        echo "${art}"
                        echo "${alb}"
                        echo "${trk}"
                    done
                )
            done
        )
    done
    

    or just don't cd in the first place:

    for artist in /tank/music/*/
    do
        art=$(basename "${artist}")
        for album in "$artist"/*/
        do
            alb=$(basename "${album}")
            for track in "$album"/*.ext
            do
                trk=$(basename "$track" .ext)
                echo "${art}"
                echo "${alb}"
                echo "${trk}"
            done
        done
    done
    

    Alternative approaches:

    # reduce typing
    declare -n v=BASH_REMATCH
    
    for f in /tank/music/*/*/*.ext; do
        if [[ -f $f ]] && [[ $f =~ .+/(.+)/(.+)/(.+)".ext"$ ]]; then
            echo "art=${v[1]} alb=${v[2]} trk=${v[3]}"
        fi
    done
    
    cd /tank/music
    unset a
    for f in */*/*.ext; do
        [[ -f $f ]] || continue
        IFS=/ a=(${f%".ext"})
        echo "art=${a[0]} alb=${a[1]} trk=${a[2]}"
    done