Assembly

The assembly file contains the functions which act to combine the various files created during the GuideFrame pipeline. The following section will list each function contained within this file and provide some insight into its use and syntax.

assemble_audio_video()

def assemble_audio_video(video_file, audio_file, output_file):
    # Check that both files exist
    if os.path.exists(video_file) and os.path.exists(audio_file):
        # Create video and audio in variables for us in combined output
        video_in = ffmpeg.input(video_file)
        audio_in = ffmpeg.input(audio_file)
        combined_output = ffmpeg.output(video_in, audio_in, output_file, vcodec='copy', acodec='copy')
        try:
            (
                combined_output.run()
            )
            print(f"Successfully created: {output_file}")
        # Attempting to extract an error via the wrapper if one occurs
        except ffmpeg.Error as e:
            # Outputting the error
            error_output = e.stderr.decode('utf-8') if e.stderr else "No error details available."
            print(f"Error combining {video_file} and {audio_file}: {error_output}")
    else:
        print(f"Missing video or audio file: {video_file}, {audio_file}")

This function takes a video_file, audio_file and output_file as arguments. It checks for these files before using the ffmpeg python package to combine them into a single file, named by the passed argument. This file then contains the combined audio and video for a single guide_step.

combine_all_videos()

def combine_all_videos(output_files, final_output):
    # Temp text file to iterate through
    file_list = "file_list.txt"

    # Write the list of video files to the same file
    with open(file_list, "w") as f:
        for video in output_files:
            if os.path.exists(video):
                f.write(f"file '{video}'\n")
            else:
                print(f"Error: {video} not found")

    try:
        # Run FFmpeg using the concat method and check for errors etc
        ffmpeg.input(file_list, format="concat", safe=0).output(final_output, vcodec='libxvid', acodec='aac').run()
        print(f"Successfully combined all videos into {final_output}")
    except ffmpeg.Error as e:
        error_output = e.stderr.decode('utf-8') if e.stderr else "No error details available."
        print(f"Error combining videos: {error_output}")
    finally:
        # Removing the txt file
        os.remove(file_list)

This function takes output_files and final_output as arguments. It takes the array of passed files and writes them to a newly created text file called file_list. The concat function from ffmpeg is used with the file_list passed. This is the input portion of the command before the output portion uses the final_output name for outputted file name.

assemble()

def assemble(number_of_steps):
    # Combine individual video and audio for each step by iterating through files and passing to above functions
    clip_number = number_of_steps + 1
    for i in range(1, clip_number):
        video_file = f"step{i}.mp4"
        audio_file = f"step{i}.mp3"
        output_file = f"output_step{i}.mp4"
        # Call the function to combine video and audio
        assemble_audio_video(video_file, audio_file, output_file)

    # Now that all video/audio combinations are complete, combine the output videos into the final one
    output_files = [f"output_step{i}.mp4" for i in range(1, clip_number)]
    # Now using the extract_script_name function from guideframe_utils.py to get the script name
    script_name = extract_script_name()
    # Creating a unique filename for the final output file via uuid and the extracted script name
    output_filename = f"{script_name}_{uuid.uuid4().hex[:6]}.mp4"
    # Combining all the videos into the final output as a single video
    combine_all_videos(output_files, output_filename)

    # Check if final_output exists and if so, clean up temporary files (the various mp3 and mp4 files we created)
    if os.path.exists(output_filename):
        print("Final output created. Cleaning up temporary files...")
        # Cleanup loop
        for i in range(1, clip_number):
            step_video = f"step{i}.mp4"
            step_audio = f"step{i}.mp3"
            output_step = f"output_step{i}.mp4"
            if os.path.exists(step_video):
                os.remove(step_video)
                print(f"Removed {step_video}")
            if os.path.exists(step_audio):
                os.remove(step_audio)
            if os.path.exists(output_step):
                os.remove(output_step)
                print(f"Removed {output_step}")
        print("Cleanup complete.")
    else:
        print("Final output not found. No cleanup performed.")

This function uses the two others from this file to perform the overall assembly and cleanup of GuideFrame step files. It takes the clip_number argument and uses it to iterate through a loop of all audio and video files in addition to creating the appropriately named output_file during the loop.

Note: 1 is added to account for steps starting at 1. This means an iteration from 1 -> clip_number will have the intended range.

Within each loop, these files are passed to the assemble_audio_video() function for combination.

An array of the files outputted by this process is then created using another loop and the clip_number variable. A script_name variable is intialised using extract_script_name() from utils. An output file is then created using the script_name and a randomly generated uuid. The array and filename are then passed to combine_all_videos() for final assembly.

Once this process is complete, a check that a matching file exists is performed. Provided this is successful, a cleanup loop occurs using the clip_number variable again to iterate through all created audio and video files with the exception of the final output.