|
| 1 | +--- |
| 2 | +title: Assembly |
| 3 | +layout: default |
| 4 | +nav_order: 5 |
| 5 | +parent: Library |
| 6 | +permalink: assembly |
| 7 | +--- |
| 8 | + |
| 9 | +# Assembly |
| 10 | +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. |
| 11 | + |
| 12 | +### `assemble_audio_video()` |
| 13 | +```python |
| 14 | +def assemble_audio_video(video_file, audio_file, output_file): |
| 15 | + # Check that both files exist |
| 16 | + if os.path.exists(video_file) and os.path.exists(audio_file): |
| 17 | + # Create video and audio in variables for us in combined output |
| 18 | + video_in = ffmpeg.input(video_file) |
| 19 | + audio_in = ffmpeg.input(audio_file) |
| 20 | + combined_output = ffmpeg.output(video_in, audio_in, output_file, vcodec='copy', acodec='copy') |
| 21 | + try: |
| 22 | + ( |
| 23 | + combined_output.run() |
| 24 | + ) |
| 25 | + print(f"Successfully created: {output_file}") |
| 26 | + # Attempting to extract an error via the wrapper if one occurs |
| 27 | + except ffmpeg.Error as e: |
| 28 | + # Outputting the error |
| 29 | + error_output = e.stderr.decode('utf-8') if e.stderr else "No error details available." |
| 30 | + print(f"Error combining {video_file} and {audio_file}: {error_output}") |
| 31 | + else: |
| 32 | + print(f"Missing video or audio file: {video_file}, {audio_file}") |
| 33 | +``` |
| 34 | +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`. |
| 35 | + |
| 36 | +### `combine_all_videos()` |
| 37 | +```python |
| 38 | +def combine_all_videos(output_files, final_output): |
| 39 | + # Temp text file to iterate through |
| 40 | + file_list = "file_list.txt" |
| 41 | + |
| 42 | + # Write the list of video files to the same file |
| 43 | + with open(file_list, "w") as f: |
| 44 | + for video in output_files: |
| 45 | + if os.path.exists(video): |
| 46 | + f.write(f"file '{video}'\n") |
| 47 | + else: |
| 48 | + print(f"Error: {video} not found") |
| 49 | + |
| 50 | + try: |
| 51 | + # Run FFmpeg using the concat method and check for errors etc |
| 52 | + ffmpeg.input(file_list, format="concat", safe=0).output(final_output, vcodec='libxvid', acodec='aac').run() |
| 53 | + print(f"Successfully combined all videos into {final_output}") |
| 54 | + except ffmpeg.Error as e: |
| 55 | + error_output = e.stderr.decode('utf-8') if e.stderr else "No error details available." |
| 56 | + print(f"Error combining videos: {error_output}") |
| 57 | + finally: |
| 58 | + # Removing the txt file |
| 59 | + os.remove(file_list) |
| 60 | +``` |
| 61 | +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. |
| 62 | + |
| 63 | +### `assemble()` |
| 64 | +```python |
| 65 | +def assemble(number_of_steps): |
| 66 | + # Combine individual video and audio for each step by iterating through files and passing to above functions |
| 67 | + clip_number = number_of_steps + 1 |
| 68 | + for i in range(1, clip_number): |
| 69 | + video_file = f"step{i}.mp4" |
| 70 | + audio_file = f"step{i}.mp3" |
| 71 | + output_file = f"output_step{i}.mp4" |
| 72 | + # Call the function to combine video and audio |
| 73 | + assemble_audio_video(video_file, audio_file, output_file) |
| 74 | + |
| 75 | + # Now that all video/audio combinations are complete, combine the output videos into the final one |
| 76 | + output_files = [f"output_step{i}.mp4" for i in range(1, clip_number)] |
| 77 | + # Now using the extract_script_name function from guideframe_utils.py to get the script name |
| 78 | + script_name = extract_script_name() |
| 79 | + # Creating a unique filename for the final output file via uuid and the extracted script name |
| 80 | + output_filename = f"{script_name}_{uuid.uuid4().hex[:6]}.mp4" |
| 81 | + # Combining all the videos into the final output as a single video |
| 82 | + combine_all_videos(output_files, output_filename) |
| 83 | + |
| 84 | + # Check if final_output exists and if so, clean up temporary files (the various mp3 and mp4 files we created) |
| 85 | + if os.path.exists(output_filename): |
| 86 | + print("Final output created. Cleaning up temporary files...") |
| 87 | + # Cleanup loop |
| 88 | + for i in range(1, clip_number): |
| 89 | + step_video = f"step{i}.mp4" |
| 90 | + step_audio = f"step{i}.mp3" |
| 91 | + output_step = f"output_step{i}.mp4" |
| 92 | + if os.path.exists(step_video): |
| 93 | + os.remove(step_video) |
| 94 | + print(f"Removed {step_video}") |
| 95 | + if os.path.exists(step_audio): |
| 96 | + os.remove(step_audio) |
| 97 | + if os.path.exists(output_step): |
| 98 | + os.remove(output_step) |
| 99 | + print(f"Removed {output_step}") |
| 100 | + print("Cleanup complete.") |
| 101 | + else: |
| 102 | + print("Final output not found. No cleanup performed.") |
| 103 | +``` |
| 104 | +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. |
| 105 | + |
| 106 | +*Note: 1 is added to account for steps starting at 1. This means an iteration from 1 -> `clip_number` will have the intended range.* |
| 107 | + |
| 108 | +Within each loop, these files are passed to the `assemble_audio_video()` function for combination. |
| 109 | + |
| 110 | +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. |
| 111 | + |
| 112 | +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. |
0 commit comments