Reading ffmpeg output for progress.
This commit is contained in:
parent
545a474559
commit
e9d896427c
@ -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.
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user