Want to convert my m4a files into mp3 files using a script. It would save some time... I have over 100 GB of music files.
OS: OSX10.14 / Terminal vs Bash script
I can run ffmpeg -v 5 -y -i musicFile.m4a -acodec libmp3lame -ac 2 -b:a 320k musicFile.mp3 from the terminal. It converts the file and I can see and play the file from itunes.
When I run the same from a bash script it fails to convert.
ffmpeg -v 5 -y -i $ENTRY_FILE -acodec libmp3lame -ac 2 -b:a 320k $MP3NAME
My ipod nano just died and I got a new mp3 player. Now I need to convert my itunes files from AAC format to MP3.
ffmpeg is an established video and music file converter.
When I run it from the bash script I tried a few things.
I added ./ in front of the file, that failed because it was installed under /usr/local/bin and not under the same directory.
I also tried sh ffmpeg... and that gave me the cannot execute a binary file.
#!/usr/bin/env bash
# convert m4a file to mp3
set -e
file_convert() {
ENTRY_FILE=$(printf %q "${entry}")
FILE_NAME=$(printf %q "$(basename "${entry}")")
DIR=$(printf %q "$(dirname "${entry}")")
NAME="${FILE_NAME%.*}"
EXT="${FILE_NAME##*.}"
MP3NAME="${DIR}/${NAME}.mp3"
printf "%*s%s\n" $((indent+2)) '' "$ENTRY_FILE"
printf "%*s\tNew File :\t%s\n" $((indent+2)) '' "$MP3NAME"
if [ $EXT == "m4a" ]
then
printf "%*s\tConverting: \t%s\n" $((index+2)) '' "$ENTRY_FILE"
ffmpeg -v 5 -y -i $ENTRY_FILE -acodec libmp3lame -ac 2 -b:a 320k $MP3NAME
fi
}
walk() {
local indent="${2:-0}"
printf "\n%*s%s\n\n" "$indent" '' "$1"
# If the entry is a file convert it
for entry in "$1"/*; do [[ -f "$entry" ]] && file_convert; done
# If the entry is a directory recurse
for entry in "$1"/*; do [[ -d "$entry" ]] && walk "$entry" $((indent+2)); done
}
# If the path is empty use the current, otherwise convert relative to absolute; Exec walk()
[[ -z "${1}" ]] && ABS_PATH="${PWD}" || pushd "${1}" && ABS_PATH="${PWD}"
walk "${ABS_PATH}"
popd
echo
I expect >./aacToMp3.sh ./music to traverses the music directory and convert each m4a file to .mp3.
It is walking the file system and printing out correct files, with the spaces escaped. When it hits the ffmpeg line it halts. I put the set -e at the top of the file to force it to fail if the command fails. Without the set -e it happily walks all the music files and prints them to the stdout.
If you have lots of files to process and a decent multi-core CPU and fast disk, I would recommend GNU Parallel which you can install with homebrew:
brew install parallel
Then make a copy of a few files in a test directory and try:
parallel --dry-run ffmpeg -v 5 -y -i {} -acodec libmp3lame -ac 2 -b:a 320k {.}.mp3 ::: *.m4a
If that looks good, replace --dry-run with --progress.
If that looks good, you can (make a backup first) and do the whole lot:
find path/to/music -name "*.m4a" -print0 | parallel -0 --progress ffmpeg -v 5 -y -i {} -acodec libmp3lame -ac 2 -b:a 320k {.}.mp3
Thanks for all the input.
After a while I ended up writing all the ffmpeg lines to a script file. Glad I did. I was able to quickly scan the file and see some errors and fix them.
This is what I came up with.
Basically I am writing straight text to stdout and directing it to a file. Which I converted to a shell script to convert each file. One at a time. Ran the generated script overnight.
file_convert() {
ENTRY_FILE=$(printf %q "${entry}")
FILE_NAME=$(printf %q "$(basename "${entry}")")
DIR=$(printf %q "$(dirname "${entry}")")
NAME="${FILE_NAME%.*}"
EXT="${FILE_NAME##*.}"
MP3NAME="${DIR}/${NAME}.mp3"
if [ $EXT == "m4a" ]
then
printf 'echo "Converting %s ..."\n' "$FILE_NAME"
printf 'ffmpeg -v 5 -y -i %s -acodec libmp3lame -ac 2 -b:a 320k %s\n\n' "$ENTRY_FILE" "$MP3NAME"
fi
}
The output looked like.
#!/usr/bin/env bash
echo "Converting 03\ It\'s\ Not\ My\ Time.m4a ..."
ffmpeg -v 5 -y -i /Users/arthuranderson/Documents/work/projects/mp3Convert/music/3\ Doors\ Down/3\ Doors\ Down\ \(Bonus\ Track\ Version\)/03\ It\'s\ Not\ My\ Time.m4a -acodec libmp3lame -ac 2 -b:a 320k /Users/arthuranderson/Documents/work/projects/mp3Convert/music/3\ Doors\ Down/3\ Doors\ Down\ \(Bonus\ Track\ Version\)/03\ It\'s\ Not\ My\ Time.mp3
echo "Converting 01\ Down.m4a ..."
ffmpeg -v 5 -y -i /Users/arthuranderson/Documents/work/projects/mp3Convert/music/311/Greatest\ Hits\ \'93-\'03/01\ Down.m4a -acodec libmp3lame -ac 2 -b:a 320k /Users/arthuranderson/Documents/work/projects/mp3Convert/music/311/Greatest\ Hits\ \'93-\'03/01\ Down.mp3
Mark I will try parallel when I purchase some more music next time.
Thanks everyone!
Related
This question already has answers here:
How do you convert an entire directory with ffmpeg?
(34 answers)
Closed 1 year ago.
I used with script to add dynamic watermark to one video, how to adapt it to convert multiple videos?
ffmpeg -i test.mp4 -i logo.png -filter_complex "[1]colorchannelmixer=aa=0.6,scale=iw*0.7:-1[a];[0][a]overlay=x='if(lt(mod(t\,16)\,8)\,W-w-W*5/100\,W*5/100)':y='if(lt(mod(t+4\,16)\,8)\,H-h-H*2.5/100\,H*2.5/100)'" -c:v libx264 -an out.mp4
You just need to iterate the video files:
source_folder=$1
target_folder=$2
mkdir -p $target_folder
echo "procesing..."
for file in $source_folder/*.mp4 $source_folder/**/*.mp4 ; do
if [[ -f $file ]]; then
filename=$(basename -- "$file")
echo "source video:"$file "new :"$target_folder/$filename
ffmpeg -i "$file" -i logo.png -filter_complex "[1]colorchannelmixer=aa=0.6,scale=iw*0.7:-1[a];[0][a]overlay=x='if(lt(mod(t\,16)\,8)\,W-w-W*5/100\,W*5/100)':y='if(lt(mod(t+4\,16)\,8)\,H-h-H*2.5/100\,H*2.5/100)'" -c:v libx264 -an "$target_folder/$filename"
fi
done;
echo ""
echo "result: $target_folder"
find $target_folder | sort
Example:
bash script.sh /input /foo/bar/output
I have a simple script that loops through files in a folder and then converts them from flv to mp4. How in bash do I skip over the files in the folder that have a fcntl lock on them and then come back when the lock is removed?
#!/bin/bash
for file in /video_recordings/stream/*.flv; do
today=`date '+%Y_%m_%d_%H_%M_%S'`;
cp -v "$file" /video_recordings/stream/backups/$today.flv;
[ -e "${file%.flv}".mp4 ] || ffmpeg -threads 1 -i "$file" -c:v libx264 -c:a aac -b:v 768k -b:a 96k -vf "scale=720:trunc(ow/a/2)*2" -tune fastdecode -preset ultrafast -crf 25 /video_recordings/stream/temp/$today.mp4
cp -v /video_recordings/stream/temp/$today.mp4 /video_recordings/stream/vod/$today.mp4
rm /video_recordings/stream/temp/$today.mp4
sleep 15s;
rm "$file";
done
I don't know much about fcntl, but according to this answer such locks should be listed by fuser. If something is accessing a file fuser exits with status code 0 and 1 otherwise.
To come back to the locked files later use an array as if it was a queue:
#!/bin/bash
queue=(/video_recordings/stream/*.flv)
while (( ${#queue[#]} > 0 )); do
file="${queue[0]}"
queue=("${queue[#]:1}")
if fuser -s "$file"; then
echo "$file in use. Try later."
queue+=("$file")
continue
fi
# insert commands for conversion of `$file` here
done
I have a script called automateutube that I edit in VIM and execute in the terminal with sh ./automateutube.sh This script pulls youtube links from a file called songs.txt and downloads the video from youtube then extracts the audio.
The songs.txt file looks like this
https://www.youtube.com/watch?v=IxQOlZ3pqtI
https://www.youtube.com/watch?v=IxQOlZ3pqtI
https://www.youtube.com/watch?v=IxQOlZ3pqtI
https://www.youtube.com/watch?v=IxQOlZ3pqtI
It is just a bunch of links, one per line.
The script looks like this
#!/bin/bash
while read p; do
x=/tmp/.youtube-dl-$(date +%y.%m.%d_%H.%M.%S)-$RANDOM.flv
youtube-dl --audio-quality 160k --output=$x --format=18 "$p"
ffmpeg -i $x -acodec libmp3lame -ac 2 -ab 128k -vn -y "$p"
rm $x
done <songs.txt
Now the first part executes. It downloads the video and starts to unpack it.
It is the second part that fails. ffmpeg -i $x -acodec libmp3lame -ac 2 -ab 128k -vn -y "$p"
This is because "$p" is supposed to be in format "filename.mp3" However as it is p takes the value of a youtube link, without ".mp3" appended.
This works for the first line
youtube-dl --audio-quality 160k --output=$x --format=18 "$p"
because "$p" is supposed to be in the form of a link there.
Now I have tried adding three lines in
a="$.mp3"
b="$p"
c=$b$a
and making ffmpeg -i $x -acodec libmp3lame -ac 2 -ab 128k -vn -y "$p"
into ffmpeg -i $x -acodec libmp3lame -ac 2 -ab 128k -vn -y "$c"
but I am still getting an error. Any ideas?
parse error, at least 3 arguments were expected, only 1 given in string 'om/watch?v=sOAHOxbMOJY'
So after some experimentation using advice from the comments, I came to this, which works.
#!/bin/sh
while read -r p; do
x=/tmp/.youtube-dl-$(date +%y.%m.%d_%H.%M.%S)-$RANDOM.flv
youtube-dl --audio-quality 160k --output="$x" --format=18 "$p"
SUBSTRING=$(echo "$p"| cut -c33-50)
ffmpeg -i "$x" -acodec libmp3lame -ac 2 -ab 128k -vn -y "$SUBSTRING.mp3" </dev/null
rm "$x"
done <songs.txt
What this fixes is keeping /'s out of the file name, and eliminating parser error.
I am trying to setup a Shell Script to work within an automator watch folder...
Everything works with the exception of the Run Shell Scrip portion...
Essentially when a file shows up in the watch folder, it runs the shell scrip which calls FFMPEG and then will move the file to an archive folder for safe keeping. However right now automator is telling me everything worked but now file is being created.
I have the Shell set to /bin/sh and Pass input set to as arguments
Here is my script:
for f in "$#"
do
name=$(basename "$f")
dir=$(dirname "$f")
ffmpeg -i "$f" -b 250k -strict experimental -deinterlace -vcodec h264 -acodec aac "$dir/mp4/${name%.*}.mp4"
echo "$dir/mp4/${name%.*}.mp4"
done
it does echo the correct filename, but does not actually run ffmpeg
I have tried adding -exec before it like I have seen in some scripts but still nothing...
FFmpeg searches STDIN while it is running. This is to allow the user to hit q to stop encoding, among other tasks. This could cause a problem with a script. Perhaps to workaround try this:
# notice --+
# |
# v
ffmpeg -nostdin -i "$f" -b 250k -strict experimental -deinterlace \
-vcodec h264 -acodec aac "$dir/mp4/${name%.*}.mp4"
mysterious error with ffmpeg on OSX
Not sure if it is because I am running on a Mac OS X Server but I imagine it is, I had to include the absolute path to ffmpeg...which fixed it
for f in "$#"
do
name=$(basename "$f")
dir=$(dirname "$f")
/opt/local/bin/ffmpeg -i "$f" -b 250k -strict experimental -deinterlace -vcodec h264 -acodec aac "$dir/mp4/${name%.*}.mp4"
echo "$dir/mp4/${name%.*}.mp4"
done
for f in ~/Desktop/Uploads/*.flv; do
if /usr/local/bin/ffprobe ${f} 2>&1 | egrep 'Stream #0:0.+flv1'; then
<what do I put here for the batch file to skip the file and continue?>
else
if /usr/local/bin/ffprobe ${f} 2>&1 | egrep 'Stream #0:1.+speex'; then
/usr/local/bin/ffmpeg -i ${f} -vn -acodec libfaac -ab 128k -ar 48000 -async 1 ${f/%.flv/.m4a}
SPEEX_ADD="-i ${f/%.flv/.m4a}"
SPEEX_MAP="-map 1:0"
SPEEX_TRASH="rmtrash ${f/%.flv/.m4a}"
~/Desktop/Uploads/ffmpeg/ffmpeg -i ${f} ${SPEEX_ADD} -vcodec copy -acodec copy -map 0:0 ${SPEEX_MAP} ${f/%.flv/.mp4} && rmtrash ${f} && ${SPEEX_TRASH}
else
~/Desktop/Uploads/ffmpeg/ffmpeg -i ${f} -vcodec copy -an ${f/%.flv/.mp4} && rmtrash ${f}
fi
done
I'm using a batch file to convert video files but I need help. What can I put in that third line that will make the batch file skip over files with flv1 video but continue onto the next file? Thanks for your help.
continue. Check the bash man page for details.
You can use the continue keyword. Have a look at the Loop Control page in the Advanced Bash-Scripting Guide