Reading ffmpeg output for progress.
This commit is contained in:
		| @@ -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