Composition
In our example, the classes
Composition is a principle where one class includes another class as a component and uses it to accomplish its tasks.
Composition creates a "has-a" relationship. One object contains (has) another object and utilizes its functionality.
In our example, the classes
ScriptWriter and PromptsWriter create an instance of the Writer class and use it to perform text generation tasks. This is a classic example of composition.Composition is a principle where one class includes another class as a component and uses it to accomplish its tasks.
Composition creates a "has-a" relationship. One object contains (has) another object and utilizes its functionality.
π₯8
"Is-a" or "Has-a"
These principles help determine when to use inheritance and when to use composition.
1. Inheritance (is-a). If one class is a specialized or extended version of another, use inheritance.
Example: ScriptWriter, ScriptDivider, PromptsWriter, and other classes are (is-a) types of BaseGenerator. These classes have the functionality of BaseGenerator and extend it by adding their own logic.
βοΈ Inheritance is suitable when classes have a "generalization-specialization" relationship: BaseGenerator (generalization) - ScriptWriter (specialization).
2. Composition (has-a). If one class contains or uses another class as a component, use composition.
Example: ScriptWriter and PromptsWriter include (has-a) an instance of Writer and use its functionality for text generation.
βοΈ Composition is preferred when classes have a "part-whole" relationship: Writer (part) - ScriptWriter (whole).
* Despite the name Writer, it may seem like ScriptWriter is a type of Writer, but this is not the case. ScriptWriter (whole) simply uses Writer (part) as a component.
These principles help determine when to use inheritance and when to use composition.
1. Inheritance (is-a). If one class is a specialized or extended version of another, use inheritance.
Example: ScriptWriter, ScriptDivider, PromptsWriter, and other classes are (is-a) types of BaseGenerator. These classes have the functionality of BaseGenerator and extend it by adding their own logic.
βοΈ Inheritance is suitable when classes have a "generalization-specialization" relationship: BaseGenerator (generalization) - ScriptWriter (specialization).
2. Composition (has-a). If one class contains or uses another class as a component, use composition.
Example: ScriptWriter and PromptsWriter include (has-a) an instance of Writer and use its functionality for text generation.
βοΈ Composition is preferred when classes have a "part-whole" relationship: Writer (part) - ScriptWriter (whole).
* Despite the name Writer, it may seem like ScriptWriter is a type of Writer, but this is not the case. ScriptWriter (whole) simply uses Writer (part) as a component.
π₯11β€2π1
Now we need to refine the project structure.
We see that the modules package contains three files related to text generation:
Let's improve the project structure.
In the modules package, we'll create a separate
This will create a clear separation of responsibility and make it easier to manage the logic related to text generation.
* Don't forget to update the imports in the
We see that the modules package contains three files related to text generation:
writer.py, script_writer.py, and prompts_writer.py.Let's improve the project structure.
In the modules package, we'll create a separate
writer package and move the files writer.py, script_writer.py, and prompts_writer.py into it.This will create a clear separation of responsibility and make it easier to manage the logic related to text generation.
* Don't forget to update the imports in the
main.py file accordingly:from modules.writer.script_writer import ScriptWriterfrom modules.writer.prompts_writer import PromptsWriterπ₯15
Let's start with the tasks for the PromptsWriter:
The initial data can be prepared like this:
1. Read the data from the file(s) obtained in the previous stage (
2. Pack the data into a list of lists. Each script is a list of strings or a list of dictionaries. The scenes of the script are the elements of the list (strings, or the values of the dictionary keys).
3. Iterate over each script (list) in a loop.
4. Iterate over the list elements (scenes) in a loop. For each scene, create a prompt by inserting the scene's value.
5. Pass the prepared prompt to the
* Example of a prompt to be passed to the execute method:
For scene [scene] write an image prompt. The goal is to generate visuals for this scene using this prompt.
The work of the
1. Accept the prompt in the method.
2. Send the request to generate text through the
3. Receive the response and save the result in a separate CSV file.
The initial data can be prepared like this:
1. Read the data from the file(s) obtained in the previous stage (
ScriptDivider).2. Pack the data into a list of lists. Each script is a list of strings or a list of dictionaries. The scenes of the script are the elements of the list (strings, or the values of the dictionary keys).
3. Iterate over each script (list) in a loop.
4. Iterate over the list elements (scenes) in a loop. For each scene, create a prompt by inserting the scene's value.
5. Pass the prepared prompt to the
execute method of the PromptsWriter class.* Example of a prompt to be passed to the execute method:
For scene [scene] write an image prompt. The goal is to generate visuals for this scene using this prompt.
The work of the
execute method:1. Accept the prompt in the method.
2. Send the request to generate text through the
generate_text method of the self.writer object, which was created in the constructor of the class.3. Receive the response and save the result in a separate CSV file.
π₯4
Hint 1. The result may look like this (image). There is a separate folder for the generated prompts, and each script has its own CSV file with the generated prompts. The rows in the file represent the prompts for each scene of the script.
Hint 2. To simplify extracting the result, request a dictionary format in the initial prompt. Then, write a method to extract the values of the dictionary keys from the text, similar to what we did in the ScriptDivider class.
Hint 2. To simplify extracting the result, request a dictionary format in the initial prompt. Then, write a method to extract the values of the dictionary keys from the text, similar to what we did in the ScriptDivider class.
π₯11β€1
ImageGenerator
How can we implement AI image generation? Here are a few options:
Paid:
β’ OpenAI API (DALLΒ·E 3 model) or other APIs
Free:
β’ gpt4free (
β’ Own generator
Speaking of free options, for better reliability, speed, and quality, we can build our own generator. Such a generator is demonstrated in the bot's demo.
Itβs built using browser automation with Playwright and one of the free AI generators based on the DALLΒ·E 3 model.
Next, Iβll guide you on how to write such a generator in the
How can we implement AI image generation? Here are a few options:
Paid:
β’ OpenAI API (DALLΒ·E 3 model) or other APIs
Free:
β’ gpt4free (
client.images.generate)β’ Own generator
Speaking of free options, for better reliability, speed, and quality, we can build our own generator. Such a generator is demonstrated in the bot's demo.
Itβs built using browser automation with Playwright and one of the free AI generators based on the DALLΒ·E 3 model.
Next, Iβll guide you on how to write such a generator in the
ImageGenerator class.π₯13β€2β€βπ₯2
Playwright + Bing Image Creator
1. Create a Microsoft Bing account.
2. Go to https://www.bing.com/images/create.
3. Copy cookies using the EditThisCookie extension.
4. Save the cookies to a
5. In the constructor of the
6. Write a method that contains the driver object. Example of driver setup:
Options like headless mode can be set in the browser object, and you can disable the AutomationControlled flag.
You can also set the User Agent and change the viewport in the context object.
Return the context from the method.
7. Create an attribute containing the driver object, and assign it the result of the method.
8. Create an attribute for the browser page:
...
1. Create a Microsoft Bing account.
2. Go to https://www.bing.com/images/create.
3. Copy cookies using the EditThisCookie extension.
4. Save the cookies to a
cookies.json file in the data folder of our bot.5. In the constructor of the
ImageGenerator class, create an attribute that holds the path to the cookies file.6. Write a method that contains the driver object. Example of driver setup:
playwright = sync_playwright().start()
browser = playwright.chromium.launch()
context = browser.new_context()Options like headless mode can be set in the browser object, and you can disable the AutomationControlled flag.
You can also set the User Agent and change the viewport in the context object.
Return the context from the method.
7. Create an attribute containing the driver object, and assign it the result of the method.
8. Create an attribute for the browser page:
self.page = None. This is necessary for accessing the page object in other methods....
π₯7π€3
Writing the login method:
1. Open a new page:
2. Check if the cookies file exists. If it does, read its contents (for this, write a separate method) and load them into the browser:
1. Open a new page:
self.page = self.driver.new_page()2. Check if the cookies file exists. If it does, read its contents (for this, write a separate method) and load them into the browser:
self.driver.add_cookies(cookies)β€6π₯3
Writing the execute method:
1. Navigate to the page https://www.bing.com/images/create.
The page URL can be set as a class attribute self.URL:
2. Find the selector for the text input form.
3. Enter the text (received as an argument) into the form.
4. Find the selector for the "Create" button.
5. Click the button (or simulate pressing the Enter key).
6. Find the selector for the block containing the generated images.
7. Wait for this block to appear:
8. Find the selector for the images inside the block.
9. Extract the image URLs and download them into the folder for the specific scene.
1. Navigate to the page https://www.bing.com/images/create.
The page URL can be set as a class attribute self.URL:
self.page.goto(self.URL)2. Find the selector for the text input form.
3. Enter the text (received as an argument) into the form.
4. Find the selector for the "Create" button.
5. Click the button (or simulate pressing the Enter key).
6. Find the selector for the block containing the generated images.
7. Wait for this block to appear:
self.page.wait_for_selector(selector)8. Find the selector for the images inside the block.
9. Extract the image URLs and download them into the folder for the specific scene.
π₯7β€2π1
Data for passing to the
1. Loop through the CSV files with prompts from the
2. Return a list of lists. Each list contains prompts for a separate script, and each prompt is an element of the list (a string or a dictionary value).
3. Loop through each list (representing a script).
4. Loop through each element (prompt) and pass it to the
execute method can be prepared like this:1. Loop through the CSV files with prompts from the
prompts folder and read their contents.2. Return a list of lists. Each list contains prompts for a separate script, and each prompt is an element of the list (a string or a dictionary value).
3. Loop through each list (representing a script).
4. Loop through each element (prompt) and pass it to the
execute method.π₯9
Hint 1: When the cookies expire, obtain new ones and resave them in the
Hint 2: Look for selectors using the browser inspector (Elements tab).
Hint 3: After creating an instance of the
Hint 4: Extract full-size images of 1024x1024, not thumbnails.
cookies.json file.Hint 2: Look for selectors using the browser inspector (Elements tab).
Hint 3: After creating an instance of the
ImageGenerator class in the main.py file, first call the login method and then the execute method.Hint 4: Extract full-size images of 1024x1024, not thumbnails.
π₯7
All that's left is to write two simple classes for generating voiceovers and downloading footage, after which we will move on to the final and most interesting class -
VideoGenerator.π₯11β‘2π2
VoiceGenerator
For generating voiceovers, we will use the ElevenLabs API and its Python library:
https://github.com/elevenlabs/elevenlabs-python
Hereβs how this class can be implemented:
Class constructor:
1. Set the API key, voice, and model parameters in the constructor. Set the default value for the voice parameter to, for example, '
2. Create an instance attribute that contains the client object. To do this, import the
3. Create an instance attribute
4. Create an instance attribute
...
For generating voiceovers, we will use the ElevenLabs API and its Python library:
https://github.com/elevenlabs/elevenlabs-python
Hereβs how this class can be implemented:
Class constructor:
1. Set the API key, voice, and model parameters in the constructor. Set the default value for the voice parameter to, for example, '
Brian', and for the model parameter to 'eleven_multilingual_v2'.2. Create an instance attribute that contains the client object. To do this, import the
ElevenLabs class, create its instance, and pass the API key as an argument:self.client = ElevenLabs(api_key=api_key)3. Create an instance attribute
self.voice4. Create an instance attribute
self.model...
π₯7π2
Method execute:
1. Accept the text for the voiceover.
2. Send a request to generate the voiceover by calling the
3. Form the path to the save folder. This is the folder corresponding to the scene of the script where the generated images are stored.
4. Save the file to this folder using the
1. Accept the text for the voiceover.
2. Send a request to generate the voiceover by calling the
generate method on the self.client object. Pass the text, voice, and model as arguments (image). Store the result in a variable. You can write a separate method for this.3. Form the path to the save folder. This is the folder corresponding to the scene of the script where the generated images are stored.
4. Save the file to this folder using the
save function (from elevenlabs import save).π₯7
Data for passing to the execute method can be prepared like this:
1. Read the data from the file(s) where the divided script scenes are saved (
2. Return a list of lists. Each list is a script. Each scene is an element of the list (a string or a dictionary key).
3. Loop through each list (script).
4. Loop through each element (scene) and pass it to the execute method.
1. Read the data from the file(s) where the divided script scenes are saved (
script_scenes.csv).2. Return a list of lists. Each list is a script. Each scene is an element of the list (a string or a dictionary key).
3. Loop through each list (script).
4. Loop through each element (scene) and pass it to the execute method.
π4π₯3
Hint 1: Obtain the API key on the ElevenLabs website and save it in a
Hint 2: You can get the available voice names on the page https://elevenlabs.io/app/speech-synthesis/text-to-speech or by using the method voices:
.env file. Then, when creating a VoiceGenerator object in the main.py file, retrieve the API key from environment variables through the Config class attribute (Config.ELEVENLABS_API_KEY) and pass it to the class constructor.Hint 2: You can get the available voice names on the page https://elevenlabs.io/app/speech-synthesis/text-to-speech or by using the method voices:
self.client.voices.get_all()π₯8π1