Skip to content

Commit 4eda1bb

Browse files
committed
task(GUIDEFRAME-52): adding docs to site
Signed-off-by: Pat O'Connor <[email protected]>
1 parent 22bd9fa commit 4eda1bb

15 files changed

+1050
-201
lines changed

.DS_Store

-2 KB
Binary file not shown.

docs/README.md

Lines changed: 0 additions & 174 deletions
This file was deleted.

docs/assembly.md

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
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.

docs/audio.md

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
---
2+
title: Audio
3+
layout: default
4+
nav_order: 3
5+
parent: Library
6+
permalink: audio
7+
---
8+
9+
# Audio
10+
The audio file contains functions designed to provide the voiceover for each GuideFrame step. It interacts with both `gTTS` and markdown in order to create these mp3 files. The following section will list each function contained within this file and provide some insight into its use and syntax.
11+
12+
### `export_gtts()`
13+
```python
14+
def export_gtts(text, file_name):
15+
tts = gTTS(text)
16+
tts.save(file_name)
17+
print("Exported", file_name)
18+
```
19+
This function uses the `gTTS` python package in order to generate audio based on the user-prescribed text. It takes the `text` argument and passes it, along with a `file_name` to the native `gTTS` functions. This then writes an audio file, with the passed name and featuring the prescribed text, to the local directory.
20+
21+
### `sleep_based_on_vo()`
22+
```python
23+
def sleep_based_on_vo(file_name):
24+
audio = MP3(file_name)
25+
print("Sleeping for", audio.info.length, "seconds")
26+
time.sleep(audio.info.length)
27+
```
28+
This function is designed to prevent the main script's interactions from accelerating beyond the recorded voiceover. It achieves this by taking the `file_name` of the .mp3 file created during the above function. It then parses the length of this audio file before using the `sleep` function from the `time` package to sleep based on the length found in seconds. This ensures that an interaction cannot occur until the requisite voiceover clip has completed.
29+
30+
### `pull_vo_from_markdown()`
31+
```python
32+
def pull_vo_from_markdown(md_file, step_number):
33+
# Open the markdown file and read
34+
with open(md_file, "r", encoding="utf-8") as file:
35+
md_content = file.read()
36+
37+
'''
38+
Regex pattern breakdown:
39+
40+
## Step {step_number} -> The step heading to match
41+
\s* -> Any whitespace characters before the content
42+
(.*?) -> The content under the step heading
43+
(?=\n##|\Z) -> A lookahead to match the next step heading (##) or the end of the file
44+
'''
45+
46+
# Define the regex pattern for the step heading (explained above)
47+
step_heading = rf"## Step {step_number}\s*(.*?)\s*(?=\n##|\Z)"
48+
49+
# Search the markdown content for the step heading
50+
match = re.search(step_heading, md_content, re.DOTALL)
51+
52+
# Return the content under the step heading if found
53+
return match.group(1).strip() if match else None
54+
```
55+
This function takes the `md_file` and `step_number` as arguments. It uses these to extract the text content of the markdown file by opening it and then using the `re` package to perform a regex parse (outlined in above code comments). This pattern ensures that the text must follow a `##` heading with text matching "Step n*". Provided a match is found, it is then returned.
56+
57+
### `generate_voiceover()`
58+
```python
59+
def generate_voicover(md_file, step_number):
60+
# Extract voiceover text from the .md file
61+
voiceover = pull_vo_from_markdown(md_file, step_number) # parsing the markdown
62+
63+
# Check if content was found
64+
if not voiceover:
65+
print(f"Warning: No content found for Step {step_number}")
66+
return
67+
68+
# Export the voiceover to an MP3 file
69+
export_gtts(voiceover, f"step{step_number}.mp3")
70+
# Sleeping based on the length of the voiceover
71+
sleep_based_on_vo(f"step{step_number}.mp3")
72+
```
73+
This function brings the above functions together. It takes `md_file` and `step_number` as arguments before passing these into a call to `pull_vo_from_markdown()`. It then checks for the presence of resulting voiceover.
74+
75+
It then uses the `export_gtts()` function where it passes the voiceover created above along with a file name created using the `step_number` variable. Finally, it calls the `sleep_based_on_vo()` function to complete the generation cycle.

0 commit comments

Comments
 (0)