Archived
1
0

Reading ffmpeg output for progress.

This commit is contained in:
Stanislav Nikitin 2019-05-18 19:02:34 +05:00
parent 545a474559
commit e9d896427c
No known key found for this signature in database
GPG Key ID: 106900B32F8192EE

View File

@ -4,7 +4,10 @@ import (
// stdlib
"bufio"
"log"
"os"
"os/exec"
"strconv"
"strings"
"time"
)
@ -17,12 +20,19 @@ type Task struct {
// Filed in conversion.
totalFrames int
// State information.
gotInput bool
gotDuration bool
// Initial calculation state information.
previousOutput string
gotInput bool
gotDuration bool
gotTimeOrFPSParsingError bool
// After totalFrames will be filled we will use these variables
// to work with output.
gotFrame bool
// File info.
duration string
fps string
}
// Convert launches conversion procedure. Should be launched in separate
@ -85,7 +95,11 @@ func (t *Task) Convert() {
break
}
stderrScanner.Scan()
proceeding := stderrScanner.Scan()
if proceeding == false {
break
}
//log.Println(stderrScanner.Text())
t.workWithOutput(stderrScanner.Text())
}
@ -94,8 +108,110 @@ func (t *Task) Convert() {
// Printing progress for this task.
func (t *Task) workWithOutput(output string) {
if output == "" {
// Do nothing if we have empty output string or if we're not ready.
if output == "" || t.gotTimeOrFPSParsingError {
return
}
// If we have totalFrames defined, which is the very final state
// for calculations below, we should work with output in different
// manner :)
if t.totalFrames != 0 {
// We should look for current frame count.
// If we have "frame=" there - then output for next function
// call will be current frame count.
if strings.Contains(output, "frame=") {
t.gotFrame = true
// If we have only "frame" here - then actual current frame
// will be in next output. Otherwise we should fix output
// to contain only actual current frame.
// We have ASCII here, not runes, len() is fine.
if len(output) == 6 {
// Current frame will be in next output.
return
} else {
output = strings.Split(output, "frame=")[1]
}
}
// ... which we should properly use.
if t.gotFrame {
currentFrame, err := strconv.Atoi(output)
if err != nil {
log.Println("Failed to convert current frame value to int ("+output+"):", err.Error())
t.gotFrame = false
return
}
percentage := currentFrame / int(t.totalFrames/100)
// What if... we mistaken with totalFrames prediction?
if percentage > 100 {
percentage = 100
}
os.Stdout.Write([]byte("\rConverting " + t.InputFile + ": " + strconv.Itoa(percentage) + "% done (" + output + " frame of " + strconv.Itoa(t.totalFrames) + ")"))
// ... and reset it's state so next "frame=" will be the
// next stop.
t.gotFrame = false
}
return
}
// We got input keyword. Next function runs will look for duration.
if output == "Input" {
t.gotInput = true
return
}
if t.gotInput && output == "Duration:" {
t.gotDuration = true
return
}
if t.gotDuration && t.duration == "" {
t.duration = output
log.Println("File duration:", t.duration)
return
}
if t.duration != "" && output == "fps," {
t.fps = t.previousOutput
log.Println("Got FPS value:", t.fps)
// Calculate total frames approximately, because even if ffmpeg
// writes that there is 29.97 fps, it actually might be something
// like 29.971872638217638216.
// BTW, this is a duration, not a time, and to avoid all
// kind of type pr0n we will just fix gathered duration to
// be parsable by time.ParseDuration()
fileDuration := strings.Replace(t.duration, ":", "h", 1)
fileDuration = strings.Replace(fileDuration, ":", "m", 1)
fileDuration = strings.Replace(fileDuration, ".", "s", 1)
fileDuration = strings.Replace(fileDuration, ",", "", 1)
fileDuration += "ms"
totalTime, err := time.ParseDuration(fileDuration)
log.Println("Got file duration parsed:", totalTime)
seconds := totalTime.Seconds()
if err != nil {
log.Println("ERROR: failed to parse video file total time value. No progress output will be produced!")
t.gotTimeOrFPSParsingError = true
}
log.Println("Got file duration in seconds:", seconds)
fps, err1 := strconv.ParseFloat(t.fps, 64)
if err1 != nil {
log.Println("ERROR: failed to parse frames per second value: '" + t.fps + "', no progress output will be produced!")
t.gotTimeOrFPSParsingError = true
}
// We don't mind to loose 1 or 2 fps from total fps counter,
// yea? :)
t.totalFrames = int(float64(seconds) * fps)
log.Println("Total frames calculated:", t.totalFrames)
}
// Save previous output for good unless we have everything we need
// to print progress.
if t.totalFrames == 0 {
t.previousOutput = output
}
}