# You must make sure to run all cells in sequence using shift + enter or you might encounter errors
from pykubegrader.initialize import initialize_assignment
responses = initialize_assignment("1_star_wars_q", "week_6", "homework", assignment_points = 57.0, assignment_tag = 'week6-homework')
# Initialize Otter
import otter
grader = otter.Notebook("1_star_wars_q.ipynb")
๐ ๐ Homework: Build Your Own Star Wars Title Scene! ๐#
A Long Time Ago in a Codebase Far, Far Awayโฆ#
Young Padawan, your missionโshould you choose to accept itโis to recreate the legendary Star Wars opening crawl using modular programming! ๐ ๏ธโจ
The Force of good software design is strong with modularity. Instead of cramming everything into one giant function (which leads to the Dark Side of spaghetti code ๐), you will break the project into reusable, well-structured modules.
Your Mission ๐#
You will write a Python program that simulates the classic Star Wars title crawl, complete with:
A dramatic opening text: โA long time ago in a galaxy far, far awayโฆ.โ
A scrolling title logo: Display the iconic STAR WARS text in a large, cinematic font.
A scrolling movie crawl: The famous block of floating text that recedes into the starry void.
The Jedi Way (Modular Programming) ๐งโ๐โ๏ธ#
To master modularity, your code should be divided into clear functions/modules. Any good Jedi knows to formulate their plan before diving into battle! ๐คบ
Here we provide you the pseudocode and a flow chart to guide you in your quest. We will walk you through the steps to complete the assignment.
Pseudo Code#
Load configuration parameters
Read multiple stories from a file, using a delimiter to separate them
Choose a random story
Parse markdown text and format it into line-wrapped, clean text
Create a video writer instance for output
Render a fade-in text intro sequence
Generate the scrolling text animation using a perspective transform
Save the final video output
graph TD;
A[Start] --> B[Load Configuration]
B --> C[Read Multiple Stories]
C --> D[Choose Random Story]
D --> E[Parse Markdown & Wrap Lines]
E --> F[Create Video Writer]
F --> G[Render Fade-in Intro Sequence]
G --> H[Generate Scrolling Text Animation]
H --> I[Apply Perspective Transform]
I --> J[Save & Release Video]
J --> K[End]
Question 1 (Points: 2.0): ๐ Required Imports for the Star Wars Title Scene ๐#
1๏ธโฃ Add These Imports to Your Script#
At the beginning of your script, import the following libraries:
Computer Vision: Use OpenCV to handle images and animations.
Numerical Operations: Use NumPy for mathematical operations and transformations.
Randomization: Use the
random
module to generate dynamic effects.Regular Expressions: Use the
re
module for text manipulation and pattern matching.Type Hinting: Use
List
,Dict
, andAny
from thetyping
module to define function input and output types.
2๏ธโฃ Best Practices#
Keep all imports at the top of your script.
Use aliases when necessary to improve readability.
Only import what is required to maintain a clean and efficient code structure.
๐ Next Step: Implement modular functions for your title sequence!
# Import openCV -- cv2
...
# import numpy, random, re, and from typing: List, Dict, Any
...
grader.check("star-wars-import-statements")
Question 2 (Points: 11.0): ๐ Configuration Table for Star Wars Title Scene#
To customize the title crawl, use the following configuration parameters. The table below lists the default values and their descriptions.
Parameter |
Default Value |
Description |
---|---|---|
|
|
Output file name for the rendered video. |
|
|
Width of the video frame in pixels. |
|
|
Height of the video frame in pixels. |
|
|
Frames per second for smooth playback. |
|
|
Video codec used for encoding. |
|
|
Separator for text blocks in the crawl. |
|
|
Maximum characters per line before wrapping. |
|
|
Time in seconds for the crawl to complete. |
|
|
Perspective tilt applied to the scrolling text. |
|
|
Number of frames for fade-in/out effects. |
|
|
Number of frames the opening text is held before fading. |
๐น Modifying Values: Adjust these parameters to fine-tune the animation speed, resolution, and text behavior.
๐น Usage: This configuration dictionary is used to control how the title crawl is rendered in the script.
# Define the dictionary `CONFIG` with key value pairs based on the markdown table:
...
grader.check("initial-configuration")
Question 3 (Points: 3.0): Reading a Story from a File ๐#
We will provide the first function for you. This function reads multiple stories from a file and returns them as a list of strings.
This function returns a list of stories, each separated by a delimiter. You can use this function to load different stories for your title crawl.
We read files in Python using the open()
function. The with
statement ensures the file is properly closed after reading.
with open("filepath","r") as file:
variable = file.operation
def read_multiple_stories(file_path: str, delimiter: str = "---") -> List[str]:
with open(file_path, "r", encoding="utf-8") as f:
raw_content = f.read()
return [chunk.strip() for chunk in raw_content.split(delimiter) if chunk.strip()]
๐ Writing a Function to Choose a Random Story#
1๏ธโฃ Function Purpose#
This function randomly selects a story from a given list. If the list is empty, it returns a default message.
2๏ธโฃ Steps to Implement#
Check if the list contains stories
If the list is not empty, proceed to selection.
If it is empty, return
"No stories found!"
.
Select a random story
Use the
random.choice()
function to pick a story from the list.
Return the selected story
If the list contains stories, return the chosen one.
If the list is empty, return the default message.
3๏ธโฃ Parameters#
stories
(List[str]): A list containing multiple story strings.
4๏ธโฃ Return Value#
A randomly selected story (str).
"No stories found!"
if the list is empty.
# write the function `choose_random_story` that takes a list of stories and returns a random story
...
grader.check("File-IO")
Text Parsing and Formatting ๐#
1๏ธโฃ Function Purpose#
Generally speaking it is important you can read and parse data in their raw form into Python. This code can get messy because of the formatting of the text. This function will parse the markdown text and wrap the lines to fit the screen width.
def parse_markdown(story_chunk: str, max_line_length: int = 60) -> List[str]:
cleaned = re.sub(r"[`*?]", "", story_chunk) # Remove markdown markers
raw_lines = cleaned.splitlines()
heading_regex = re.compile(r"^\s*#+\s*(.*)$")
text_lines = []
for line in raw_lines:
line_str = line.strip()
if not line_str:
text_lines.append("")
continue
match = heading_regex.match(line_str)
if match:
text_lines.append(match.group(1).upper())
else:
text_lines.extend(word_wrap(line_str, max_line_length))
return text_lines
def word_wrap(text: str, max_len: int) -> List[str]:
words, lines, current_line = text.split(), [], []
for word in words:
if len(" ".join(current_line + [word])) <= max_len:
current_line.append(word)
else:
lines.append(" ".join(current_line))
current_line = [word]
if current_line:
lines.append(" ".join(current_line))
return lines
grader.check("text-parsing-and-formatting")
Question 4 (Points: 0.0): Helper Function to Create Video Writer ๐ฅ#
You usually will want to develop a series of helper functions to help you with the main task. We provide you with a few helper functions to get you started.
Video Writer Function#
We provide you with a create_video_writer function that initializes the video writer object with the specified output path, frame width, height, and frames per second (fps). This function uses some advanced syntax to unpack the configuration dictionary and pass the values to the VideoWriter object.
def create_video_writer(**kwargs) -> cv2.VideoWriter:
config = {**CONFIG, **kwargs}
fourcc = cv2.VideoWriter_fourcc(*config["codec"])
return cv2.VideoWriter(
config["output_path"],
fourcc,
config["fps"],
(config["frame_width"], config["frame_height"]),
)
grader.check("video-writer-function")
Text Rendering Function#
If we have text and want to render it to a screen it is useful to have a rendering function. This function will provide the styling for the text and render it to the screen. We do something tricky here with the configuration dictionary. If kwarg is passed and it is in the configuration dictionary, it will use that value. If not, it will use the default value. Recall that the later values in the dictionary will overwrite the earlier values.
def render_text(
frame: np.ndarray, text_lines: List[str], base_y: float, **kwargs
) -> None:
"""
Renders multiple lines of text on a given frame.
Parameters:
- frame (np.ndarray): The image frame where text will be rendered.
- text_lines (List[str]): A list of text lines to display.
- base_y (float): The starting vertical position for text rendering.
- **kwargs: Additional configuration parameters (e.g., font_scale, color, font_thickness).
Returns:
- None: The function modifies the frame in-place.
"""
# Default configuration for text properties, overridden by kwargs if provided
config = {"font_scale": 1.0, "font_thickness": 2, "color": (0, 255, 255), **kwargs}
# Define the font type
font = cv2.FONT_HERSHEY_SIMPLEX
# Calculate the horizontal center position of the frame
x_center = frame.shape[1] // 2
# Loop through each text line and render it
for line in text_lines:
# Check if the line is a chapter/prologue title (should be bold)
is_bold = line.lower().startswith(("chapter", "prologue"))
# Adjust font scale and thickness for bold text
scale, thickness = (
(config["font_scale"] * 1.5, config["font_thickness"] + 2) # Bold settings
if is_bold
else (config["font_scale"], config["font_thickness"]) # Default settings
)
# Calculate the text size for positioning
text_size, _ = cv2.getTextSize(line, font, scale, thickness)
# Compute x and y coordinates to center the text horizontally
x, y = x_center - (text_size[0] // 2), int(base_y)
# Render the text on the frame
cv2.putText(
frame, line, (x, y), font, scale, config["color"], thickness, cv2.LINE_AA
)
# Move the base_y down for the next line, adding spacing
base_y += text_size[1] + 10 # Adding 10 pixels of spacing between lines
grader.check("rendering-function")
๐ Applying a Perspective Transformation to the Star Wars Crawl#
1๏ธโฃ Purpose of This Function#
This function applies a perspective warp to the input frame, creating the illusion of the text receding into the distance, just like in the iconic Star Wars opening crawl.
2๏ธโฃ Steps to Implement#
๐ Step 1: Retrieve Frame Dimensions#
Extract the height (
h
) and width (w
) of the frame.
๐ Step 2: Define Source Points (src
)#
Specify the four corners of the original frame:
Top-left (0, 0)
Top-right (w - 1, 0)
Bottom-left (0, h - 1)
Bottom-right (w - 1, h - 1)
๐ Step 3: Define Destination Points (dst
)#
Modify the top corners to create a tilted effect, controlled by
tilt_factor
:Shift the top-left corner right by
tilt_factor * w / 2
.Shift the top-right corner left by
tilt_factor * w / 2
.Keep the bottom corners unchanged for the perspective effect.
๐ Step 4: Compute Perspective Transformation Matrix#
Use
cv2.getPerspectiveTransform(src, dst)
to compute the transformation matrix.
๐ Step 5: Apply Perspective Warp#
Use
cv2.warpPerspective()
to apply the transformation and return the modified frame.
3๏ธโฃ Parameters#
frame
(np.ndarray): The input image frame.tilt_factor
(float, default0.7
): Controls the amount of tilt applied to the text.
4๏ธโฃ Return Value#
Returns a transformed frame (np.ndarray) with the perspective effect applied.
# Write the apply_perspective function that applies a perspective transform to the input frame
def apply_perspective(frame: np.ndarray, tilt_factor=0.7) -> np.ndarray:
h, w = frame.shape[:2]
src = np.float32([[0, 0], [w - 1, 0], [0, h - 1], [w - 1, h - 1]])
dst = np.float32(
[
[int(tilt_factor * w / 2), 0],
[w - 1 - int(tilt_factor * w / 2), 0],
[0, h - 1],
[w - 1, h - 1],
]
)
return cv2.warpPerspective(frame, cv2.getPerspectiveTransform(src, dst), (w, h))
grader.check("perspective-transformation")
๐ Creating a Black Frame for the Star Wars Title Scene#
1๏ธโฃ Purpose#
This function generates a blank black frame that serves as the background for the scrolling text animation, ensuring a consistent space-like appearance.
2๏ธโฃ Implementation Steps#
๐ Step 1: Define Frame Dimensions#
Accept
width
andheight
as parameters to specify the frame size.
๐ Step 2: Create a Black Image#
Use
numpy.zeros()
to create a 3D array filled with zeros, representing a black image.The shape of the array is
(height, width, 3)
, where:height
โ Number of rows (image height).width
โ Number of columns (image width).3
โ Represents the RGB color channels (Red, Green, Blue).
The
dtype=np.uint8
ensures pixel values are stored efficiently as 8-bit unsigned integers (0โ255).
๐ Step 3: Return the Frame#
The function returns the created black frame as a NumPy array, ready for further processing.
3๏ธโฃ Parameters#
width
(int): Width of the frame in pixels.height
(int): Height of the frame in pixels.
4๏ธโฃ Return Value#
A black image (
np.ndarray
) with shape(height, width, 3)
.
# Write the create_black_frame function that creates a black frame with the specified width and height
...
grader.check("Background-frame")
def measure_text_height(text_lines: List[str]) -> int:
return sum(
cv2.getTextSize(line, cv2.FONT_HERSHEY_SIMPLEX, 1.0, 2)[0][1] + 10
for line in text_lines
)
grader.check("text-size-function")
๐ Implementing the Star Wars Crawl Animation#
1๏ธโฃ Purpose#
This function animates the scrolling text for the Star Wars title crawl, moving it from the bottom of the screen to the top while applying a perspective transformation.
2๏ธโฃ Implementation Steps#
๐ Step 1: Load Configuration Settings#
Merge
CONFIG
with any additional keyword arguments (kwargs
) to allow parameter overrides. See how this was done in therender_text
function.Extract key settings like
scroll_time
,fps
,frame_width
,frame_height
, andtilt_factor
. You must extract these values from the dictionary in the line with the computation to get full credit.
๐ Step 2: Compute Animation Parameters#
Determine the total number of frames based on the formula:
$\( \text{total\_frames} = \text{scroll\_time} \times \text{fps} \)$Set the vertical start and end positions for the text:
start_y
: Starts below the frame to ensure a smooth entrance. To do this we will add 50 to the start y-valueend_y
: Ends above the frame (negative value) after scrolling out. Make the end y-value the negative of the text height from the function.
Calculate the total distance the text will travel.
๐ Step 3: Loop Through Each Frame#
For each frame:
Create a black frame using
create_black_frame()
.Calculate the current Y-position of the text based on progress:
$\( \text{current\_y} = \text{start\_y} - \left(\frac{i}{\text{total\_frames}} \times \text{distance}\right) \)$Render the scrolling text at the computed position using
render_text()
.Apply perspective transformation to simulate depth using
apply_perspective()
.Write the frame to the video file using
writer.write()
.
3๏ธโฃ Parameters#
writer
(cv2.VideoWriter
): Handles writing frames to a video file.text_lines
(List[str]
): The text to be animated.**kwargs
: Optional overrides for animation settings (e.g.,fps
,scroll_time
,tilt_factor
).
4๏ธโฃ Key Functions Used#
create_black_frame(width, height)
: Generates a black background.measure_text_height(text_lines)
: Calculates total text height for proper positioning.render_text(frame, text_lines, y)
: Draws text onto the frame at a given position.apply_perspective(frame, tilt_factor)
: Applies the cinematic depth effect.writer.write(frame)
: Saves each frame to the video file.
# Write the animate_crawl function that generates a Star Wars-style crawl animation
...
grader.check("Animation-scroll")
๐ Bonus: Implementing the Star Wars Fade-In and Fade-Out Text Effect#
1๏ธโฃ Purpose#
This function animates a fading text effect, where the text gradually appears, stays visible for a set duration, and then fades out. This is useful for displaying the iconic A long time ago in a galaxy far, far awayโฆ intro.
2๏ธโฃ Implementation Steps#
๐ Step 1: Load Configuration Settings#
Merge
CONFIG
with any additional keyword arguments (kwargs
) to allow overrides.Extract key settings like
frame_width
,frame_height
,fade_frames
, andhold_frames
. You must extract these values from the dictionary in the line with the computation to get full credit.Compute
base_y
to center the text vertically in the frame. To do this you need to get the height of the frame, and the height of the text. The base y-value is the frame height - the text height divided by 2. Since the value should be in pixels use floor division to insure the value is an integer.
๐ Step 2: Implement the Fade-In Effect#
Loop through
fade_frames
iterations:Compute the alpha value (transparency level) as:
$\( \alpha = \frac{i}{\text{fade\_frames}} \)$Create a black frame.
Render text with a color intensity proportional to
alpha
(gradual appearance). This is just a tuple of 3 values from 0 to 255. Where 255 is full intensity and 0 is no intensity. Alpha is a value from 0 to 1. So you will need to multiply the color by alpha.Write the frame to the video file.
๐ Step 3: Hold the Fully Visible Text#
Loop for
hold_frames
iterations to maintain a fully visible text.Render the text in full white
(255, 255, 255)
.Write each frame to the video file.
๐ Step 4: Implement the Fade-Out Effect#
Loop through
fade_frames
iterations:Compute the alpha value for fading out:
$\( \alpha = 1.0 - \frac{i}{\text{fade\_frames}} \)$Render text with decreasing intensity.
Write the frame to the video file.
3๏ธโฃ Parameters#
writer
(cv2.VideoWriter
): Handles writing frames to a video file.text_lines
(List[str]
): The text to be animated.**kwargs
: Optional overrides for animation settings (fade_frames
,hold_frames
, etc.).
4๏ธโฃ Key Functions Used#
create_black_frame(width, height)
: Generates a black background.measure_text_height(text_lines)
: Computes total text height for positioning.render_text(frame, text_lines, y, color)
: Draws text with varying intensity.writer.write(frame)
: Saves each frame to the video file.
# Write the fade_text function that generates a fade-in/fade-out effect for text
...
grader.check("Bonus-Fade-Out")
Question 12 (Points: 13.0): Writing the Main Function ๐#
When designing python programs generally you will have a main function that will call all the other functions. This is the function that will be called when the program is run. This function will call all the other functions in the correct order to generate the video. This can also serve as an entry point from the command line, schedulers, application programming interfaces, etc.
๐ Implementing the Main Function for the Star Wars Title Crawl#
1๏ธโฃ Purpose#
The main()
function serves as the entry point for generating the Star Wars title crawl video. It orchestrates all major functions, ensuring smooth execution from text fading to the scrolling animation.
2๏ธโฃ Role of Main Functions#
๐ Step 1: Create a Video Writer#
Calls
create_video_writer()
to initialize a video writer object (cv2.VideoWriter
).This object is responsible for storing the generated frames into a video file.
You can set this equal to the
writer
variable.
๐ Step 2 (optional if you completed the bonus): Display the Opening Fade-In Text#
Calls
fade_text()
with the iconic opening phrase, as a list of two strings:"A long time ago in a galaxy far,"
"far away..."
This function fades in, holds, and then fades out the text before the story crawl begins.
๐ Step 3: Read and Choose a Random Story#
Calls
read_multiple_stories()
to load multiple stories from a text file. The function should take the path to the story file โ./stories.txtโ, and the delimiter from the configuration dictionary.Uses
CONFIG["delimiter"]
to split stories correctly.Calls
choose_random_story()
to randomly select one story for display.
๐ Step 4: Parse and Animate the Story Crawl#
Calls
parse_markdown(story)
to process the story text into a structured format.Calls
animate_crawl()
to scroll the text upwards, applying the classic Star Wars perspective effect.
๐ Step 5: Finalize and Save the Video#
Calls
writer.release()
to finalize and save the video.Prints a confirmation message with the output file location (
CONFIG["output_path"]
).
3๏ธโฃ Key Functions Used#
create_video_writer()
: Initializes the video file for writing frames.fade_text(writer, text_lines)
: Handles the fade-in and fade-out effect for the opening text.read_multiple_stories(file_path, delimiter)
: Loads multiple possible story crawls.choose_random_story(stories)
: Selects one story randomly.parse_markdown(story)
: Converts the selected story from Markdown format to structured text.animate_crawl(writer, text_lines)
: Scrolls the formatted story upwards with a perspective effect.writer.release()
: Finalizes and saves the generated video.
# Write the main entry point for the Star Wars crawl animation
# This is generally the a function called main
...
grader.check("Main-Function")
# Call the main function and Generate the Star Wars crawl animation
main()
Watching the Movie#
To watch the movie download the file generated and watch it in your favorite video player. You can also use the code below to watch the video in the notebook.
If you get an openCV error, it might be related to the dataType, ensure that your background is np.uint8.
Submitting Assignment#
Please run the following block of code using shift + enter
to submit your assignment, you should see your score.
from pykubegrader.submit.submit_assignment import submit_assignment
submit_assignment("week6-homework", "1_star_wars_q")