diff --git a/converter/task.go b/converter/task.go index 243880b..79b3669 100644 --- a/converter/task.go +++ b/converter/task.go @@ -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 + } }