Reliable way to get ffmpeg to BASH conversion progress

I searched a bit here and on other sites, it seems that this question has been asked several times, but instead of giving real code, people only give a theory.

I want to create a reliable way to get progress in the transcode. It seems the best way is to use the full frames of the source file and then get the current frame that ffmpeg has enabled. As people note, since ffmpeg strangely displays its progress using Carriage Returns (/ r or ^ M) and because sometimes there is a gap between the output and sometimes not, it can be unreliable at best. Here is an example output:

frame=73834 fps=397 q=0.0 size= -0kB time=2462.14 bitrate= -0.0kbits/s frame=74028 fps=397 q=0.0 size= -0kB time=2468.64 bitrate= -0.0kbits/s frame=74133 fps=397 q=0.0 Lsize= -0kB time=2472.06 bitrate= -0.0kbits/ 

I wrote a function called while the ffmpeg conversion goes forward. Here is what I got so far:

First, to get the general frames of the source file:

 duration=( $(ffmpeg -i "$SourceFile" 2>&1 | sed -n "s/.* Duration: \([^,]*\), start: .*/\1/p") ) fps=( $(ffmpeg -i "$SourceFile" 2>&1 | sed -n "s/.*, \(.*\) tbr.*/\1/p") ) hours=( $(echo $duration | cut -d":" -f1) ) minutes=( $(echo $duration | cut -d":" -f2) ) seconds=( $(echo $duration | cut -d":" -f3) ) # Get the integer part with cut frames=( $(echo "($hours*3600+$minutes*60+$seconds)*$fps" | bc | cut -d"." -f1) ) if [ -z $frames ]; then zenity --info --title "$WindowTitle" --text "Can't calculate frames, sorry." exit echo ""$SourceFile" has $frames frames, now converting" > $ffmpeglog echo ""$SourceFile" has $frames frames, now converting" 

Then this is the progress function that I call during the conversion:

 progress() { sleep 10 #some shenanigans due to the way ffmpeg uses carriage returns cat -v $ffmpeglog | tr '^M' '\n' > $ffmpeglog1 #calculate percentage progress based on frames cframe=( $(tac $ffmpeglog1 | grep -m 1 frame= | awk '{print $1}' | cut -c 7-) ) if [ -z $cframe ]; then cframe=( $(tac $ffmpeglog1 | grep -m 1 frame= | awk '{print $2}') ) fi if is_integer $frame; then percent=$((100 * cframe / frames)) #calculate time left and taken etc fps=( $(tac $ffmpeglog1 | grep -m 1 frame= | awk '{print $3}') ) if [ "$fps" = "fps=" ]; then fps=( $(tac $ffmpeglog1 | grep -m 1 frame= | awk '{print $4}') ) fi total=$(( frames + cframe + percent + fps )) #simple check to ensure all values are numbers if is_integer $total; then #all ok continue if [ -z $fps ]; then echo -ne "\rffmpeg: $cframe of $frames frames, progress: $percent"%" and ETA: error fps:0" else if [ -z $cframe ]; then echo -ne "\rffmpeg: total $frames frames, error cframes:0" else remaining=$(( frames - cframe )) seconds=$(( remaining / fps )) h=$(( seconds / 3600 )) m=$(( ( seconds / 60 ) % 60 )) s=$(( seconds % 60 )) echo -ne "\rffmpeg: $cframe of $frames frames, progress: $percent"%" and ETA: "$h"h "$m"m "$s"s" fi fi else echo "Error, one of the values wasn't a number, trying again in 10s." fi else echo "Error, frames is 0, progress wont work, sorry." fi } 

And here, for completeness, there is an is_integer function:

 is_integer() { s=$(echo $1 | tr -d 0-9) if [ -z "$s" ]; then return 0 else return 1 fi } 

As you can see, my approach is rather rude as I write one log file to another and then process this second log file to try to handle the carriage return. The main problem I discovered is that no command that BASH can call seems to be able to cut the output of ffmpegs to just the last line due to the frankly unreasonable use of carriage returns. My awk skills are not very good, and the progress function does not work when there is no space between the frame = xxxxxx part, as is sometimes the case. I was wondering if any BASH headz could make my code a bit more robust and reliable.

+4
source share
1 answer

The reason for the carriage return, as I am sure, you know, is that the progress lines overwrite each other (the output remains on the same line, since there is no line feed).

You can try this method to retrieve the line after the last carriage return:

 $ sample=$'cherry\rbanana\rapple' $ echo "$sample" applea $ cr=$'\r' # A $ extract="${sample##*$cr}" # B $ echo "$extract" apple 

The lines labeled “A” and “B” are the main lines, the rest are for demonstration purposes only. Using this method, you can eliminate several of the circles that you need to use now.

+3
source

Source: https://habr.com/ru/post/1310729/


All Articles