Skip to content

Commit

Permalink
Merge pull request gordon-cs#115 from gordon-cs/verbosity
Browse files Browse the repository at this point in the history
Verbosity flag and comments
  • Loading branch information
roddy3 authored Apr 30, 2022
2 parents d13833c + fc281a8 commit 53a2323
Show file tree
Hide file tree
Showing 9 changed files with 200 additions and 126 deletions.
15 changes: 10 additions & 5 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,12 @@ func main() {
optionFlags := options.ParseFlags()

// Create a temporary folder to store temporary files
tempDirectory, err := OS.CreateDirectory(optionFlags.TemporaryDirectory)
tempDirectory, err := OS.CreateDirectory(optionFlags.TemporaryDirectory, optionFlags.Verbose)
helper.Check(err)

// Create directory if output directory does not exist
if optionFlags.OutputDirectory != "" {
_, err := OS.CreateDirectory(optionFlags.OutputDirectory)
_, err := OS.CreateDirectory(optionFlags.OutputDirectory, optionFlags.Verbose)
helper.Check(err)
}

Expand All @@ -45,20 +45,20 @@ func main() {
start := time.Now()

// Parse in the various pieces from the template
slideshow := slideshow.NewSlideshow(optionFlags.SlideshowDirectory)
slideshow := slideshow.NewSlideshow(optionFlags.SlideshowDirectory, optionFlags.Verbose)

fmt.Println("Scaling images...")
slideshow.ScaleImages(optionFlags.LowQuality)

fmt.Println("Creating video...")
slideshow.CreateVideo(optionFlags.UseOldFade, tempDirectory, optionFlags.OutputDirectory)
slideshow.CreateVideo(optionFlags.UseOldFade, tempDirectory, optionFlags.OutputDirectory, optionFlags.Verbose)

fmt.Println("Video production completed!")
duration := time.Since(start)
fmt.Sprintln(fmt.Sprintf("Time Taken: %f seconds", duration.Seconds()))

if optionFlags.OverlayVideoDirectory != "" {
fmt.Println("Creating overlay video...")
fmt.Println("-ov specified, creating overlay video with ", optionFlags.OverlayVideoDirectory)

finalVideoDirectory := tempDirectory + "/final.mp4"

Expand All @@ -74,6 +74,11 @@ func main() {

}

/* Function to search the current directory for any .slideshow files and return the first found
*
* Parameters:
* slideshowDirectory - the path to the directory to be stored
*/
func findTemplate(slideshowDirectory string) fs.WalkDirFunc {
return func(path string, d fs.DirEntry, e error) error {
slideRegEx := regexp.MustCompile(`.+(.slideshow)$`) // Regular expression to find the .slideshow file
Expand Down
170 changes: 81 additions & 89 deletions src/ffmpeg/ffmpeg.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,12 @@ import (
"github.com/sillsdev/appbuilder-storybuilder/src/helper"
)

// Function to Check FFmpeg version and choose Xfade or traditional fade accordingly
func CheckVersion() string {
/* Function to Check FFmpeg version and choose Xfade or traditional fade accordingly
*
* Returns:
* The string returned from checkFFmpegVersion, either "X" or "F"
*/
func ParseVersion() string {
cmd := CmdGetVersion()
output, err := cmd.Output()
CheckCMDError(output, err)
Expand All @@ -28,11 +32,17 @@ func CheckVersion() string {
log.Fatal(err)
}
fmt.Printf("Version is %s\n", version)
return version
return compareVersion(version)
}

// Function to Check FFmpeg version and choose Xfade or traditional fade accordingly
func checkFFmpegVersion(version string) string {
/* Function to check the ffmpeg version and choose Xfade or traditional fade accordingly
*
* Parameters:
* version - the version number to be checked
* Returns:
"X"(xfade) if version > 4.3.0, "F" (default fade) otherwise
*/
func compareVersion(version string) string {
char := []rune(version) // Convert the string "X.X.X" into a char array [X, ., X, ., X]
num, _ := strconv.Atoi(string(char[0]))
if num > 4 { // Version is > 4.x.x
Expand All @@ -51,13 +61,14 @@ func checkFFmpegVersion(version string) string {

/* Function to create temporary videos with the corresponding zoom filters for each slide without any audio
* Parameters:
* Images: ([]string) - Array of filenames for the images
* Transitions: ([]string) - Array of Xfade transition names to use
* TransitionDurations: ([]string) - Array of durations for each transition
* Timings: ([]string) - array of timing duration for the audio for each image
* Audios: ([]string) - Array of filenames for the audios to be used
* Images - Array of filenames for the images
* Timings - Array of timing duration for the audio for each image
* Audios - Array of filenames for the audios to be used
* Motions - Array of start and end rectangles to use for the zoom/pan effects
* tempPath - Filepath to the temporary directory to store each temp video
* v - verbose flag to determine what feedback to print
*/
func MakeTempVideosWithoutAudio(Images []string, Transitions []string, TransitionDurations []string, Timings []string, Audios []string, Motions [][][]float64, tempPath string) {
func MakeTempVideosWithoutAudio(Images []string, Timings []string, Audios []string, Motions [][][]float64, tempPath string, v bool) {
fmt.Println("Making temporary videos in parallel...")
totalNumImages := len(Images)

Expand All @@ -77,8 +88,13 @@ func MakeTempVideosWithoutAudio(Images []string, Transitions []string, Transitio
// At the end of the goroutine, tell the WaitGroup
// that another thread has completed.
defer wg.Done()
fmt.Printf("Making temp%d-%d.mp4 video\n", i, totalNumImages)
zoom_cmd := CreateZoomCommand(Motions[i], helper.ConvertStringToFloat(duration))
if v {
fmt.Printf("Making temp%d-%d.mp4 video with:\n Image: %s\n Duration: %s ms\n Start Rectangle (x, y, height width): %f\n End Rectangle (x, y, height width): %f\n",
i+1, totalNumImages, Images[i], duration, Motions[i][0], Motions[i][1])
} else {
fmt.Printf("Making temp%d-%d.mp4 video", i+1, totalNumImages)
}
zoom_cmd := CreateZoomCommand(Motions[i], helper.ConvertStringToFloat(duration)[0])

cmd := CmdCreateTempVideo(Images[i], duration, zoom_cmd, fmt.Sprintf(tempPath+"/temp%d-%d.mp4", i, totalNumImages))
output, err := cmd.CombinedOutput()
Expand All @@ -93,70 +109,16 @@ func MakeTempVideosWithoutAudio(Images []string, Transitions []string, Transitio
wg.Wait()
}

/* Function to create the video with all images + transitions
* Parameters:
* Images: ([]string) - Array of filenames for the images
* Transitions: ([]string) - Array of Xfade transition names to use
* TransitionDurations: ([]string) - Array of durations for each transition
* Timings: ([]string) - array of timing duration for the audio for each image
* Audios: ([]string) - Array of filenames for the audios to be used
*/
func CombineVideos(Images []string, Transitions []string, TransitionDurations []string, Timings []string, Audios []string, Motions [][][]float64, tempPath string) {
input_images := []string{}
input_filters := ""
totalNumImages := len(Images)
concatTransitions := ""

fmt.Println("Getting list of images and filters...")
for i := 0; i < totalNumImages; i++ {
// Everything needs to be concatenated so always add the image to concatTransitions
concatTransitions += fmt.Sprintf("[v%d]", i)
// Everything needs to be cropped so add the crop filter to every image
input_filters += fmt.Sprintf("[%d:v]crop=trunc(iw/2)*2:trunc(ih/2)*2", i)
if i == totalNumImages-1 { // Credits image has no timings/transitions
input_images = append(input_images, "-i", Images[i])
} else {
input_images = append(input_images, "-loop", "1", "-ss", "0ms", "-t", Timings[i]+"ms", "-i", Images[i])

if i == 0 {
input_filters += fmt.Sprintf(",fade=t=out:st=%sms:d=%sms", Timings[1], TransitionDurations[i])
} else {
half_duration, err := strconv.Atoi(TransitionDurations[i])
helper.Check(err)
// generate params for ffmpeg zoompan filter
input_filters += CreateZoomCommand(Motions[i], helper.ConvertStringToFloat(Timings[i]))
input_filters += fmt.Sprintf(",fade=t=in:st=0:d=%dms,fade=t=out:st=%sms:d=%dms", half_duration/2, Timings[i], half_duration/2)

}
}
input_filters += fmt.Sprintf("[v%d];", i)

}

concatTransitions += fmt.Sprintf("concat=n=%d:v=1:a=0,format=yuv420p[v]", totalNumImages)
input_filters += concatTransitions

input_images = append(input_images, "-i", "./narration-001.mp3",
"-max_muxing_queue_size", "9999",
"-filter_complex", input_filters, "-map", "[v]",
"-map", fmt.Sprintf("%d:a", totalNumImages),
"-shortest", "-y", tempPath+"/mergedVideo.mp4")

fmt.Println("Creating video...")
cmd := exec.Command("ffmpeg", input_images...)

output, err := cmd.CombinedOutput()
CheckCMDError(output, err)
}

/* Function to merge the temporary videos with transition filters between them
* Parameters:
* Images: ([]string) - Array of filenames for the images
* Transitions: ([]string) - Array of Xfade transition names to use
* TransitionDurations: ([]string) - Array of durations for each transition
* Timings: ([]string) - array of timing duration for the audio for each image
* Images - Array of filenames for the images
* Transitions - Array of Xfade transition names to use
* TransitionDurations - Array of durations for each transition
* Timings - array of timing duration for the audio for each image
* tempPath - path to the temp folder where the videos are stored
* v - verbose flag to determine what feedback to print
*/
func MergeTempVideos(Images []string, Transitions []string, TransitionDurations []string, Timings []string, tempPath string) {
func MergeTempVideos(Images []string, Transitions []string, TransitionDurations []string, Timings []string, tempPath string, v bool) {
fmt.Println("Merging temporary videos...")
video_fade_filter := ""
settb := ""
Expand All @@ -180,10 +142,14 @@ func MergeTempVideos(Images []string, Transitions []string, TransitionDurations

for i := 0; i < totalNumImages-1; i++ {
transition := Transitions[i]

transition_duration, err := strconv.ParseFloat(strings.TrimSpace(string(TransitionDurations[i])), 8)
helper.Check(err)
transition_duration = transition_duration / 1000

if v {
fmt.Printf("%dth merge has transition %s and duration %f\n", i, transition, transition_duration)
}
//add time to the video that is sacrificied to xfade
settb += fmt.Sprintf("[%d:v]tpad=stop_mode=clone:stop_duration=%f[v%d];", i, transition_duration/2, i)

Expand Down Expand Up @@ -230,11 +196,13 @@ func MergeTempVideos(Images []string, Transitions []string, TransitionDurations
/** Merges the temporary videos using the old fade method with just plain crossfade transitions
*
* Parameters:
* Images: ([]string) - Array of filenames for the images
* TransitionDurations: ([]string) - Array of durations for each transition
* Timings: ([]string) - array of timing duration for the audio for each image
* Images - Array of filenames for the images
* TransitionDurations - Array of durations for each transition
* Timings - array of timing duration for the audio for each image
* tempLocation - path to the temp folder where the videos are stored
* v - verbose flag to determine what feedback to print
*/
func MergeTempVideosOldFade(Images []string, TransitionDurations []string, Timings []string, tempLocation string) {
func MergeTempVideosOldFade(Images []string, TransitionDurations []string, Timings []string, tempLocation string, v bool) {
fmt.Println("Merging temporary videos with traditional fade...")
video_fade_filter := ""
last_fade_output := ""
Expand All @@ -260,6 +228,10 @@ func MergeTempVideosOldFade(Images []string, TransitionDurations []string, Timin
helper.Check(err)
transition_duration = transition_duration / 1000

if v {
fmt.Printf("%dth merge has default fade transition and duration %f\n", i, transition_duration)
}

//get the current video length in seconds
cmd := CmdGetVideoLength(fmt.Sprintf(tempLocation+"/temp%d-%d.mp4", i, totalNumImages))
output, err := cmd.CombinedOutput()
Expand Down Expand Up @@ -303,10 +275,12 @@ func MergeTempVideosOldFade(Images []string, TransitionDurations []string, Timin

/* Function to add the background and narration audio onto the video_with_no_audio.mp4
* Parameters:
* Timings: ([]string) - array of timing duration for the audio for each image
* Audios: ([]string) - Array of filenames for the audios to be used
* Timings - array of timing duration for the audio for each image
* Audios - Array of filenames for the audios to be used
* tempPath - path to the temp folder where the audioless video is stored
* v - verbose flag to determine what feedback to print
*/
func AddAudio(Timings []string, Audios []string, tempPath string) {
func AddAudio(Timings []string, Audios []string, tempPath string, v bool) {
fmt.Println("Adding audio...")
audio_inputs := []string{}

Expand All @@ -332,6 +306,10 @@ func AddAudio(Timings []string, Audios []string, tempPath string) {
//place the audio at the start of each slide
audio_filter += fmt.Sprintf("[%d:a]atrim=start=%f:duration=%sms,asetpts=expr=PTS-STARTPTS[a%d];", i+1, totalDuration, strings.TrimSpace(Timings[i]), i+1)
audio_last_filter += fmt.Sprintf("[a%d]", i+1)

if v {
fmt.Printf("Adding audio snippet from %s to video. Total duration = %.2f seconds\n", Audios[i], totalDuration)
}
}
}

Expand All @@ -340,33 +318,47 @@ func AddAudio(Timings []string, Audios []string, tempPath string) {

audio_inputs = append(audio_inputs, "-filter_complex", audio_filter, "-map", "0:v", "-map", "[a]", "-codec:v", "copy", "-codec:a", "libmp3lame", tempPath+"/merged_video.mp4")

if v {
println("Adding compiled audio to merged video and generating final result...")
}
cmd := exec.Command("ffmpeg", audio_inputs...)
output, err := cmd.CombinedOutput()
CheckCMDError(output, err)

trimEnd(tempPath)
}

/* Function to copy the final video from the temp folder to the output location specified
* and change the filename
*
* Parameters:
* tempPath - path to the temp folder
* outputFolder - path to the folder to store the final result
* name - name to label the final video
*/
func CopyFinal(tempPath string, outputFolder string, name string) {
// If -o is specified, save the final video at the specified location

var cmd *exec.Cmd

// Else save it to the folder of the executable
var outputName string
if len(outputFolder) > 0 {
cmd = CmdCopyFile(tempPath+"/final.mp4", outputFolder+"/"+name+".mp4")
outputName = outputFolder + "/" + name + ".mp4"
} else { // If -o is not specified, save the final video at the default location
cmd = CmdCopyFile(tempPath+"/final.mp4", name+".mp4")
outputName = name + ".mp4"
}

fmt.Printf("Copying final video from temp folder to %s...\n", outputName)
cmd := CmdCopyFile(tempPath+"/final.mp4", outputName)
output, err := cmd.CombinedOutput()
CheckCMDError(output, err)
}

/* Function that creates an overlaid video between created video and testing video to see the differences between the two.
One video is made half-transparent, changed to its negative image, and overlaid on the other video so that all similarities would cancel out and leave only the differences.
* One video is made half-transparent, changed to its negative image, and overlaid on the other video so that all similarities would cancel out and leave only the differences.
* Parameters:
* trueVideo: (string) - file path to the testing video
*/
* finalVideoDirectory - folder where the final video produced is held
* trueVideo - file path to the comparison video
* destinationLocation - filepath to the folder to store the overlaid video
*/
func CreateOverlaidVideoForTesting(finalVideoDirectory string, trueVideo string, destinationLocation string) {
outputDir := "./overlayVideo.mp4"
if destinationLocation != "" {
Expand Down
Loading

0 comments on commit 53a2323

Please sign in to comment.