Reading ffmpeg output for progress.
This commit is contained in:
		| @@ -4,7 +4,10 @@ import ( | |||||||
| 	// stdlib | 	// stdlib | ||||||
| 	"bufio" | 	"bufio" | ||||||
| 	"log" | 	"log" | ||||||
|  | 	"os" | ||||||
| 	"os/exec" | 	"os/exec" | ||||||
|  | 	"strconv" | ||||||
|  | 	"strings" | ||||||
| 	"time" | 	"time" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -17,12 +20,19 @@ type Task struct { | |||||||
| 	// Filed in conversion. | 	// Filed in conversion. | ||||||
| 	totalFrames int | 	totalFrames int | ||||||
|  |  | ||||||
| 	// State information. | 	// Initial calculation state information. | ||||||
| 	gotInput    bool | 	previousOutput           string | ||||||
| 	gotDuration bool | 	gotInput                 bool | ||||||
|  | 	gotDuration              bool | ||||||
|  | 	gotTimeOrFPSParsingError bool | ||||||
|  |  | ||||||
|  | 	// After totalFrames will be filled we will use these variables | ||||||
|  | 	// to work with output. | ||||||
|  | 	gotFrame bool | ||||||
|  |  | ||||||
| 	// File info. | 	// File info. | ||||||
| 	duration string | 	duration string | ||||||
|  | 	fps      string | ||||||
| } | } | ||||||
|  |  | ||||||
| // Convert launches conversion procedure. Should be launched in separate | // Convert launches conversion procedure. Should be launched in separate | ||||||
| @@ -85,7 +95,11 @@ func (t *Task) Convert() { | |||||||
| 			break | 			break | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		stderrScanner.Scan() | 		proceeding := stderrScanner.Scan() | ||||||
|  | 		if proceeding == false { | ||||||
|  | 			break | ||||||
|  | 		} | ||||||
|  | 		//log.Println(stderrScanner.Text()) | ||||||
| 		t.workWithOutput(stderrScanner.Text()) | 		t.workWithOutput(stderrScanner.Text()) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -94,8 +108,110 @@ func (t *Task) Convert() { | |||||||
|  |  | ||||||
| // Printing progress for this task. | // Printing progress for this task. | ||||||
| func (t *Task) workWithOutput(output string) { | 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 | 		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