1

I am trying to add album art to m4a files using AtomicParsley. The following works for the given folder (has multiple m4a files but only one cover.jpg) :

for f in *.m4a; do AtomicParsley "$f" --artwork cover.jpg --overWrite; done

As I want to do this recursively (each folder has one unique cover.jpg and multiple m4a files) and all the subfolders have the same image name (i.e., cover.jpg though they are different), I was trying the below but it picks the cover.jpg from the first subfolder and applies to all other subfolders (that is wrong since every subfolder has its own cover.jpg)

for f in **/*.m4a; do
  for j in **/cover.jpg; do
    AtomicParsley "$f" --artwork $j --overWrite
  done
done
jww
  • 97,681
  • 90
  • 411
  • 885
kuruvi
  • 641
  • 1
  • 11
  • 28
  • Possible duplicate of [How can I recursively find all files in current and subfolders based on wildcard matching?](https://stackoverflow.com/q/5905054/608639) – jww Sep 15 '18 at 20:07

4 Answers4

3

It's easier to think of this as finding each directory that contains a cover.jpg, then look for all the video files in that directory.

shopt -s globstar
shopt -s nullglob
for c in **/cover.jpg; do
    d=${c%/cover.jpg}
    for f in "$d"/*.m4a; do
        AtomicParsley "$f" --artwork "$c" --overWrite
    done
done
Benjamin W.
  • 46,058
  • 19
  • 106
  • 116
chepner
  • 497,756
  • 71
  • 530
  • 681
  • Shouldn't there be a check in case the globs fail? – Fred Feb 12 '17 at 01:47
  • `shopt -s nullglob`? – codeforester Feb 12 '17 at 01:48
  • Yeah; I wasn't worried about it for the first glob (or else, why is the command even being run?), but it could be a legitmate issue for the second glob. – chepner Feb 12 '17 at 01:49
  • I think this will not catch files located in the current directory. Possibly not an issue depending on the structure, but worth noting. – Fred Feb 12 '17 at 01:50
  • @Fred - `**` does apply to the current directory. – codeforester Feb 12 '17 at 01:51
  • @codeforester I thought it didn't following a test I did before posting the commend, and a second test leads me to conclude that it requires `shopt - globstar` to work. – Fred Feb 12 '17 at 01:54
  • It does require `shopt -s globstar`, but I assume the OP knows that, given `**` is used in the question. – chepner Feb 12 '17 at 01:55
  • @Fred you are right. @chepner, it is good to add `globstar` to the answer. – codeforester Feb 12 '17 at 01:56
  • awesome, can you describe your answer, I have no idea about shopt -s globstar and shopt -s nullglob what is need of this? – kuruvi Feb 12 '17 at 03:34
  • I'm also getting (on mac OS); shopt: globstar: invalid shell option name – kuruvi Feb 12 '17 at 03:55
  • @kuruvi - you need Bash version 4 and above for `globstar`. – codeforester Feb 12 '17 at 05:20
  • @kuruvi install a newer version of bash using MacPorts and use that, or use one of the solutions that uses `find` such as the one provided by @Fred because the `find` based solution doesn't require these newer features – Julian Feb 12 '17 at 05:52
2

Try this:

for file in **/*.m4a; do
    AtomicParsley "$file" --artwork "$(dirname "$file")/cover.jpg" --overWrite
done
Julian
  • 2,837
  • 17
  • 15
  • You may want to add a check in the loop in case the glob fails. – Fred Feb 12 '17 at 01:48
  • I believe that I'm facing multiple issues 1) space in the folder/file name doesn't work 2) --artwork "$(/Volumes/music/$file)/cover.jpg looks like, it is adding filename ($file) to the file path which doesn't work (I may wrong here) – kuruvi Feb 12 '17 at 03:26
  • You're right for (1) spaces are not supported. I've made an update, please check. – Julian Feb 12 '17 at 04:30
  • For (2) do not replace `dirname` with your directory name, it should read `dirname` as is - that is the name of a command. For example `dirname /foo/bar` returns `/foo`. – Julian Feb 12 '17 at 04:33
1

Try this :

main_dir="/path/to/dir"
while IFS= read -r -d '' m4a_file
do
  AtomicParsley "$m4a_file" --artwork "${m4a_file%/*}/cover.jpg" --overWrite
done < <(find "$main_dir" -type f -name "*.m4a" -print0)

This is also possible :

shopt -s globstar
shopt -s nullglob
main_dir="/path/to/dir"
cd "$main_dir"
for m4a_file in **/*.m4a
do
  AtomicParsley "$m4a_file" --artwork "${m4a_file%/*}/cover.jpg" --overWrite
done

The globstar shell option enables ** globbing recursively (i.e. entering sub-directories, sub-directories of sub-directories, etc.).

The default behavior or globbing is that if there is no match, the glob pattern expands to itself. When this happens in a for loop, you have to handle the non-existent file case, as a failed glob will cause the loop body to be executed once with an invalid file name in the variable. The nullglob option changes that default behavior so that a failed glob will expand to nothing. In the case of a for loop, the loop will not execute at all.

Fred
  • 6,590
  • 9
  • 20
  • @chepner I am too used to needing various processing with the files I find (I actually do not use `-exec` a lot). I would need to make sure how to do the expansion to extract the directory path. Guess I went with the way I usually do things. – Fred Feb 12 '17 at 01:44
  • On second thought, I retracted that because of the processing needed for the cover file name; you'd have to do something like `-exec sh -c '...' _ {}`, which gets just as messy. – chepner Feb 12 '17 at 01:47
  • first solution got syntax error near unexpected token `<' (last line). Removing the rest after "done" in the first script, freezes the script. – kuruvi Feb 12 '17 at 03:18
  • Second method does work well, yes the root directory doesn't have m4a files – kuruvi Feb 12 '17 at 03:19
0

For a more complex solution (bit of overkill) that allows you to add more functionality and use the same routines do different processing later you can do something like this:

parsley_file()
{
    AtomicParsley "$1" --artwork cover.jpg --overWrite
}
doforeach_arg()
{
    local callback="$1"
    shift
    local arg
    for arg in "${@}"; do "${callback}" "${arg}"; done
}
parsley_dir()
( # use '(',')' for subshell so shopt only affects this function
    local dir="${1}"
    shopt -s nullglob
    doforeach_arg parsley_file "${dir}"/*.m4a
)

find_dirs()
{
  find "${1}" -type d | sort
}
doforeach_line()
{
  local callback="${1}"
  local line
  while read line; do "${callback}" "${line}"; done
}
parsley_dirs()
{
    find_dirs "${1}" | doforeach_line parsley_dir
}

parsley_dirs "."

Otherwise something more simple for almost a one-liner if that's what you want:

shopt -s nullglob
find "$PWD" - type d | while read dir ; do cd "$dir" && [[ -f cover.jpg ]] && ( for f in *.m4a ; do AtomicParsley "$f" -cover cover.jpg ; done ) ; done
Chunko
  • 352
  • 1
  • 8
  • As far as I know, '**' is a common java / ant file matching syntax and does not work in bash unless it's a recent extension. (It will still only match one directory level, will not recurse) – Chunko Feb 12 '17 at 01:57
  • 1
    `**` was introduced in Bash 4.0 and has to be enabled with `shopt -s globstar`. – Benjamin W. Feb 12 '17 at 02:43
  • That's great to know Benjamin W! Will make a lot of things easier – Chunko Feb 12 '17 at 02:59