ffmpeg - bash script fadeout audio file - bash

I am trying to write a bash script to get the duration of an audiofile and then apply a 5 second fade-out.
#!/bin/bash
f="$*" # all args
p="${f##*/}" # file
fn="${p%.*}" # name only
e="${p##*.}" # extension
echo
echo $f
echo $p
echo $fn
echo $e
echo
_t=$(ffmpeg -i "$f" 2>&1 | grep "Duration" | grep -o " [0-9:.]*, " )
ffmpeg -i "$f" -af "afade=t=out:st=$_t:d=500" "$fn $sec sec fade-out.$e"
I getting an Invalid argument error.

Thanks for all the help - here is my solution. Pretty sure there are more elegant ways, but I am a beginner.
#!/bin/bash
f="$*" # all args
p="${f##*/}" # file
fn="${p%.*}_fade" # name only
e="${p##*.}" # extension
sec="5"
dur=$(ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "$f")
t=$(echo "$dur - $sec" | bc)
ffmpeg -i "$f" -af "afade=t=out:st=$t:d=5" "$fn $sec sec.$e"

Related

ffmpeg command exports flac with wrong 'length' metadata, works fine for mp3

I have some audio recorded in Audacity 3.2.3 that I have exported as an mp3 and a flac. Then I have this file split_by_silence.sh
Which has hardcoded input path values that take an input file, split it by detecting silence, and then finally run an ffmpeg command to split the files. If you save the below code into a file split.sh, you can call it with the command $ ./split_by_silence.sh "value1" "value2"
# ./split_by_silence.sh "full_lowq.flac" %03d_output.flac
#IN=$1
#OUT=$2
OUT="%03d_output.flac"
IN="/mnt/e/martinradio/rips/vinyl/WIP/Dogs On Fire (1983, Vinyl)/dog on fire.flac"
OUTPUT_LOCATION="/mnt/e/martinradio/rips/vinyl/WIP/Dogs On Fire (1983, Vinyl)/"
true ${SD_PARAMS:="-18dB"};
true ${MIN_FRAGMENT_DURATION:="20"};
export MIN_FRAGMENT_DURATION
if [ -z "$OUT" ]; then
echo "Usage: split_by_silence.sh full.mp3 output_template_%03d.mp3"
echo "Depends on FFmpeg, Bash, Awk, Perl 5. Not tested on Mac or Windows."
echo ""
echo "Environment variables (with their current values):"
echo " SD_PARAMS=$SD_PARAMS Parameters for FFmpeg's silencedetect filter: noise tolerance and minimal silence duration"
echo " MIN_FRAGMENT_DURATION=$MIN_FRAGMENT_DURATION Minimal fragment duration"
exit 1
fi
#
# get comma separated list of split points (use ffmpeg to determine points where audio is at SD_PARAMS [-18db] )
#
echo "_______________________"
echo "Determining split points..." >& 2
SPLITS=$(
ffmpeg -v warning -i "$IN" -af silencedetect="$SD_PARAMS",ametadata=mode=print:file=-:key=lavfi.silence_start -vn -sn -f s16le -y /dev/null \
| grep lavfi.silence_start= \
| cut -f 2-2 -d= \
| perl -ne '
our $prev;
INIT { $prev = 0.0; }
chomp;
if (($_ - $prev) >= $ENV{MIN_FRAGMENT_DURATION}) {
print "$_,";
$prev = $_;
}
' \
| sed 's!,$!!'
)
echo "SPLITS= $SPLITS"
#
# Add 5 seconds to each of the comma separated numbers
#
# Convert the comma-separated string into an array
arr=($(echo $SPLITS | tr ',' '\n'))
# Initialize a new array to store the results
new_arr=()
# Iterate through each element and add 5 seconds of padding
for i in "${arr[#]}"; do
result=$(echo "$i + 5" | bc -l)
new_arr+=("$result")
done
# Convert the array back into a comma-separated string
NEW_SPLITS=$(IFS=,; echo "${new_arr[*]}")
# Print the result
echo "NEW_SPLITS= $NEW_SPLITS"
SPLITS=$NEW_SPLITS
#
# Print how many tracks should be exported
#
res="${SPLITS//[^,]}"
CHARCOUNT="${#res}"
num=$((CHARCOUNT + 2))
echo "Exporting $num tracks"
echo "_______________________"
#
# Split audio into individual tracks
#
current_directory=$(pwd)
cd "$OUTPUT_LOCATION"
echo "Running ffmpeg command: "
ffmpeg -i "$IN" -c copy -map 0 -f segment -segment_times "$SPLITS" "$OUT"
#ffmpeg -i "full_lowq.flac" -c copy -map 0 -f segment -segment_times "302.825,552.017" "%03d_output.flac"
echo "Done."
cd $current_directory
echo "running flac command"
# check flac file intrgrity
If I call this code for my flac file:
OUT="%03d_output.flac"
IN="/mnt/e/martinradio/rips/vinyl/WIP/Dogs On Fire (1983, Vinyl)/dog on fire.flac"
The outputted files have an incorrect metadata for the length. They all report as having the same length, but if i import any of them into audacity, the file has a correct length.
but if i run this for my mp3 file, we can see the correct length metadata:
OUT="%03d_output.mp3"
IN="/mnt/e/martinradio/rips/vinyl/WIP/Dogs On Fire (1983, Vinyl)/dogs on fire.mp3"
So there is something with my ffmpeg command that causes it to export flac files with wrong 'length' metadata
ffmpeg -i "$IN" -c copy -map 0 -f segment -segment_times "$SPLITS" "$OUT"
I've tried with the flac example to change -c copy to -c:a flac, but that just gives every output flac file a length of 00:00:00
is it a problem with my ffmpeg command? Or my files? https://file.io/tIFsa1l70076
it works for mp3 files just fine, why does it have this issue with flac?

Tiny BASH MPV Cut & Join Script - Bash does not read line correctly in my script

I have created a script that takes the filename of screenshots from MPV player and grabs the time codes and cuts the video.
I like MPV because it is very fast on big movie files and hitting s (screenshot) for every in and out cut is very easy. I have not found any bash script (I can only do bash or are learning bash) that can do this, only lua and java scripts.
The bash script:
#!/bin/bash
clear
DATE=$(date +"%Y%m%d_%H%M%S")
x-terminal-emulator -geometry 50x20+3100+0 -e "bash -c 'while true; do clear;ls *.jpg;sleep 1;done' &"
rm *.jpg CUT*.mp4 cutLines.* cutMerge.*
mpv --screenshot-template="~/%F-(%P)-%03n" "$1"
echo
read -p "--- Hit ENTER to CUT ---"
echo
ls *.jpg | cut -c 24-35 > cutLines.txt
IFS=$'\n'
while IFS= read -r ONE; do read -r TWO
echo " Making cut for duration: $ONE - $TWO stored as: CUT_${ONE}.mp4"
ffmpeg -nostdin -loglevel quiet -ss "${ONE}" -to "${TWO}" -i "${1}" -c copy CUT_"${ONE}".mp4
echo CUT_"${ONE}".mp4 >> cutMerge.tmp
done < cutLines.txt
cat cutMerge.tmp | sed "s/^/file '/" |sed "s/$/'/" > cutMerge.txt
ffmpeg -f concat -safe 0 -i cutMerge.txt -c copy CUTmerge_"$DATE".mp4
The script works for the clips.
Here is the link where you see what I struggle with.
It looks like read line does not read all the data or something?
Video showing what the problem is
Thanks to Ed Morton's TIPS the script is now working!
It looks like the problem was missing double quotes and the ffmpeg option -nostdin that was the main problem for this script.
#!/bin/bash
clear
rm *.jpg cutLines.txt cutMerge.txt cutMerge.tmp
DATE=$(date +"%Y%m%d_%H%M%S")
x-terminal-emulator -geometry 50x20+3100+0 -e "bash -c 'while true; do clear;ls *.jpg;sleep 1;done' &"
mpv --screenshot-template="~/%F-(%P)-%03n" "$1"
echo
read -p "--- Hit ENTER to CUT ---"
echo
ls *.jpg | cut -c 24-35 > cutLines.txt
IFS=$'\n'
while IFS= read -r ONE; do read -r TWO
echo " Making cut for duration: $ONE - $TWO stored as: CUT_${ONE}.mp4"
ffmpeg -nostdin -loglevel quiet -ss "${ONE}" -to "${TWO}" -i "${1}" -c copy CUT_"${ONE}".mp4
echo CUT_"${ONE}".mp4 >> cutMerge.tmp
done < cutLines.txt
cat cutMerge.tmp | sed "s/^/file '/" |sed "s/$/'/" > cutMerge.txt
ffmpeg -f concat -safe 0 -i cutMerge.txt -c copy VideoMerged_"$DATE".mp4
echo -e "\n--- cutLines"
cat cutLines.txt
echo -e "\n--- cutMerge\n"
cat cutMerge.txt
rm *.jpg cutLines.txt cutMerge.txt cutMerge.tmp
mpv VideoMerged_"$DATE".mp4

Sync two audio files

I have 2 audio files:
correct.wav (duration 3:07)
incorrect.wav (duration 3:10)
They are almost the same, but was generated with different sound fonts.
The problem: The second file is late for a few seconds.
How can I sync second file with the first one? Maybe there some bash software that could detect first loud sounds appearance in the first sound and compare correct.wav with incorrect.wav, shorten the end of the incorrect.wav file.
I know I can do it manually, but I need automated soulution for a lot of files.
Here is approximate solutions I found:
1) for detecting sound syncing to use this Python script - https://github.com/jeorgen/align-videos-by-sound but it's not perfect, not detecting 100%.
2) use sox for cutting/trimming/comparing/detecting sound durations (code extraction):
length1ok=$(sox correct.wav -n stat 2>&1 | sed -n 's#^Length (seconds):[^0-9]*\([0-9.]*\)$#\1#p')
length2ok=$(sox incorrect.wav -n stat 2>&1 | sed -n 's#^Length (seconds):[^0-9]*\([0-9.]*\)$#\1#p')
if [[ $length1ok == $length2ok ]]; then
echo "Everything OK: $length1ok = $length2ok"
else
echo "Fatal error: Not the same final files"
fi
diff=$(echo "$length2 - $length1" | bc -l)
echo "difference = $diff"
echo "webm $length1 not greater than fluid2 $length2"
sox correct.wav incorrect.wav pad 0 $diff
Comment to UltrasoundJelly's answer:
Here what result I get for your code:
Here what result I need:
Here's one solution:
Use ffmpeg to find the leading silence in each file
If the new file has a longer leading silence, trim the difference with sox
If the new file has a shorter leading silence, pad the start with sox
Trim the new file to the same length as the original with sox
Bash Script:
FILEONE=$1
FILETWO=$2
MINSILENCE=0.1
THRESH="-50dB"
S1=$(ffmpeg -i $FILEONE -af silencedetect=noise=$THRESH:d=$MINSILENCE -f null - 2>&1 | grep silence_duration -m 1 | awk '{print $NF}')
S2=$(ffmpeg -i $FILETWO -af silencedetect=noise=$THRESH:d=$MINSILENCE -f null - 2>&1 | grep silence_duration -m 1 | awk '{print $NF}')
if [ -z "$S1" ]; then echo "no starting silence found in $FILEONE" && exit 1;fi
if [ -z "$S2" ]; then echo "no starting silence found in $FILETWO" && exit 1;fi
DIFF=$(echo "$S1-$S2"|bc)
ISNEG=$(echo $DIFF'>0'| bc -l)
DIFF=${DIFF#-}
BASE="${FILETWO%.*}"
if [ $ISNEG -eq 1 ]
then
echo "$1>$2 ... padding $2"
SAMPRATE=$(sox --i -r $FILETWO)
sox -n -r $SAMPRATE -c 2 silence.wav trim 0.0 $DIFF
sox silence.wav $FILETWO $BASE.shift.wav
rm silence.wav
else
echo "$1<$2 ... trimming $2"
sox $FILETWO $BASE.trim.wav trim $DIFF
fi
length1=$(sox $FILEONE -n stat 2>&1 | sed -n 's#^Length (seconds):[^0-9]*\([0-9.]*\)$#\1#p')
length2=$(sox $BASE.trim.wav -n stat 2>&1 | sed -n 's#^Length (seconds):[^0-9]*\([0-9.]*\)$#\1#p')
if (( $(echo "$length2 > $length1" | bc -l) )); then
diff=$(echo "$length2 - $length1" | bc -l)
echo "difference = $diff"
sox $BASE.trim.wav finished.wav trim 0 -$diff
fi

bash: displaying filtered & dynamic output of ffmpeg

After this question whose the answer had partially resolved my problem.
I would like to have a selected result of ffmpeg.
So, with this command:
ffmpeg -y -i "${M3U2}" -vcodec copy -acodec copy "${Directory}/${PROG}_${ID}.mkv" 2>&1 | egrep -e '^[[:blank:]]*(Duration|Output|frame)'
The result is:
Duration: 00:12:28.52, start: 0.100667, bitrate: 0 kb/s
Output #0, matroska, to '/home/path/file.mkv':
But in the result I am missing this dynamic line:
frame= 1834 fps=166 q=-1.0 Lsize= 7120kB time=00:01:13.36 bitrate= 795.0kbits/s
This line changes every second. How can I modify the command line to display this line? My program should read this line and display the "time" updating in-place. Thanks
solution:
ffmpeg -y -i "${M3U2}" -vcodec copy -acodec copy "${Directory}/${PROG}_${ID}.mkv" 2>&1 |
{ while read line
do
if $(echo "$line" | grep -q "Duration"); then
echo "$line"
fi
if $(echo "$line" | grep -q "Output"); then
echo "$line"
fi
if $(echo "$line" | grep -q "Stream #0:1 -> #0:1"); then
break
fi
done;
while read -d $'\x0D' line
do
if $(echo "$line" | grep -q "time="); then
echo -en "\r$line"
fi
done; }
Thanks to ofrommel
You need to parse the output with CR (carriage return) as a delimiter, because this is what ffmpeg uses for printing on the same line. First use another loop with the regular separator to iterate over the first lines to get "Duration" and "Output":
ffmpeg -y -i inputfile -vcodec copy -acodec copy outputfile 2>&1 |
{ while read line
do
if $(echo "$line" | grep -q "Duration"); then
echo "$line"
fi
if $(echo "$line" | grep -q "Output"); then
echo "$line"
fi
if $(echo "$line" | grep -q "Stream mapping"); then
break
fi
done;
while read -d $'\x0D' line
do
if $(echo "$line" | grep -q "time="); then
echo "$line" | awk '{ printf "%s\r", $8 }'
fi
done; }

Programmatically convert multiple midi files to wave using timidity, ffmpeg, and bash

I am trying to build a script to do as the title says, but I am somewhat unfamiliar with Bash and other online resources have only been so helpful.
#! /bin/bash
function inout #Create Function inout
{
output[0]=" " #Initialize variables
input[0]=" "
count=1
while [ "$count" -lt 10 ]; #Start loop to get all filenames
do
echo "Grabbing filename" #User feedback
input=$(ls | grep 0$count | grep MID | sed 's/ /\\ /g') #Grab filename
#Replace ' ' character with '\ '
output=$(echo $input | tr 'MID' 'mp3')
#set output filename
echo $count #Output variables for testing
echo $input
echo $output
let count+=1 #Increment counter
echo "converting $input to $output." #User feedback
foo="timidity $input -Ow -o - | ffmpeg -i - -acodec libmp3lame -ab 320k $output"
echo $foo
#The last two lines are for the purpose of testing the full output
#I can get the program to run if I copy and paste the output from above
#but if I run it directly with the script it fails
done
}
inout
I am trying to figure out why I can't just run it from inside the script, and why I must copy/paste the output of $foo
Any ideas?
It's impossible to tell from your code how the input files are named; I'll assume something like song_02.MID:
inout () {
for input in song_*.MID; do
output=${input%.MID}.mp3
timidity "$input" -Ow -o - | ffmpeg -i - -acodec libmp3lame -ab 320k "$output"
done
}
They key is to define an appropriate pattern to match your input files, then iterate over the matching files with a for loop.
Also, your use of tr is incorrect: that call would replace any occurrence of M, I, or D with m, p, and 3, respectively; it does not replace occurrences of the 3-character string MID with mp3.

Resources