Skip to content

Commit

Permalink
Add support for multi-threaded video generation
Browse files Browse the repository at this point in the history
  • Loading branch information
FujiwaraChoki committed Feb 11, 2024
1 parent 3945355 commit 09785fd
Show file tree
Hide file tree
Showing 6 changed files with 167 additions and 102 deletions.
8 changes: 4 additions & 4 deletions Backend/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ def generate():
data = request.get_json()
paragraph_number = int(data.get('paragraphNumber', 1)) # Default to 1 if not provided
ai_model = data.get('aiModel') # Get the AI model selected by the user
n_threads = data.get('threads') # Amount of threads to use for video generation

# Get 'useMusic' from the request data and default to False if not provided
use_music = data.get('useMusic', False)
Expand Down Expand Up @@ -211,12 +212,11 @@ def generate():

# Concatenate videos
temp_audio = AudioFileClip(tts_path)
print(video_paths)
combined_video_path = combine_videos(video_paths, temp_audio.duration, 5)
combined_video_path = combine_videos(video_paths, temp_audio.duration, 5, n_threads or 2)

# Put everything together
try:
final_video_path = generate_video(combined_video_path, tts_path, subtitles_path)
final_video_path = generate_video(combined_video_path, tts_path, subtitles_path, n_threads or 2)
except Exception as e:
print(colored(f"[-] Error generating final video: {e}", "red"))
final_video_path = None
Expand Down Expand Up @@ -289,7 +289,7 @@ def generate():
video_clip = video_clip.set_audio(comp_audio)
video_clip = video_clip.set_fps(30)
video_clip = video_clip.set_duration(original_duration)
video_clip.write_videofile(f"../{final_video_path}", threads=2)
video_clip.write_videofile(f"../{final_video_path}", threads=n_threads or 1)


# Let user know
Expand Down
3 changes: 3 additions & 0 deletions Backend/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ def fetch_songs(zip_url: str) -> None:
if not os.path.exists(files_dir):
os.mkdir(files_dir)
logger.info(colored(f"Created directory: {files_dir}", "green"))
else:
# Skip if songs are already downloaded
return

# Download songs
response = requests.get(zip_url)
Expand Down
8 changes: 5 additions & 3 deletions Backend/video.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,14 +130,15 @@ def equalize_subtitles(srt_path: str, max_chars: int = 10) -> None:
return subtitles_path


def combine_videos(video_paths: List[str], max_duration: int, max_clip_duration: int) -> str:
def combine_videos(video_paths: List[str], max_duration: int, max_clip_duration: int, threads: int) -> str:
"""
Combines a list of videos into one video and returns the path to the combined video.
Args:
video_paths (List): A list of paths to the videos to combine.
max_duration (int): The maximum duration of the combined video.
max_clip_duration (int): The maximum duration of each clip.
threads (int): The number of threads to use for the video processing.
Returns:
str: The path to the combined video.
Expand Down Expand Up @@ -188,19 +189,20 @@ def combine_videos(video_paths: List[str], max_duration: int, max_clip_duration:

final_clip = concatenate_videoclips(clips)
final_clip = final_clip.set_fps(30)
final_clip.write_videofile(combined_video_path, threads=2)
final_clip.write_videofile(combined_video_path, threads=threads)

return combined_video_path


def generate_video(combined_video_path: str, tts_path: str, subtitles_path: str) -> str:
def generate_video(combined_video_path: str, tts_path: str, subtitles_path: str, threads: int) -> str:
"""
This function creates the final video, with subtitles and audio.
Args:
combined_video_path (str): The path to the combined video.
tts_path (str): The path to the text-to-speech audio.
subtitles_path (str): The path to the subtitles.
threads (int): The number of threads to use for the video processing.
Returns:
str: The path to the final video.
Expand Down
14 changes: 14 additions & 0 deletions Frontend/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,18 @@ const useMusicToggle = document.querySelector("#useMusicToggle");
const generateButton = document.querySelector("#generateButton");
const cancelButton = document.querySelector("#cancelButton");

const advancedOptionsToggle = document.querySelector("#advancedOptionsToggle");

advancedOptionsToggle.addEventListener("click", () => {
// Change Emoji, from ▼ to ▲ and vice versa
const emoji = advancedOptionsToggle.textContent;
advancedOptionsToggle.textContent = emoji.includes("▼")
? "Show less Options ▲"
: "Show Advanced Options ▼";
const advancedOptions = document.querySelector("#advancedOptions");
advancedOptions.classList.toggle("hidden");
});

const cancelGeneration = () => {
console.log("Canceling generation...");
// Send request to /cancel
Expand Down Expand Up @@ -52,6 +64,7 @@ const generateVideo = () => {
const paragraphNumberValue = paragraphNumber.value;
const youtubeUpload = youtubeToggle.checked;
const useMusicToggleState = useMusicToggle.checked;
const threads = document.querySelector("#threads").value;
const zipUrlValue = zipUrl.value;

const url = "http://localhost:8080/api/generate";
Expand All @@ -65,6 +78,7 @@ const generateVideo = () => {
automateYoutubeUpload: youtubeUpload,
useMusic: useMusicToggleState,
zipUrl: zipUrlValue,
threads: threads,
};

// Send the actual request to the server
Expand Down
209 changes: 114 additions & 95 deletions Frontend/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -24,73 +24,6 @@ <h1 class="text-4xl text-center mb-4">MoneyPrinter</h1>

<div class="flex justify-center mt-8">
<div class="max-w-xl flex flex-col space-y-4 w-full">
<label for="aiModel" class="text-blue-600">AI Model</label>
<select
name="aiModel"
id="aiModel"
class="w-full border-2 border-blue-300 p-2 rounded-md focus:outline-none focus:border-blue-500"
>
<option value="g4f">g4f (Free)</option>
<option value="gpt3.5-turbo">OpenAI GPT-3.5</option>
<option value="gpt4">OpenAI GPT-4</option>
</select>
<label for="voice" class="text-blue-600">Voice</label>
<select
name="voice"
id="voice"
class="w-min border-2 border-blue-300 p-2 rounded-md focus:outline-none focus:border-blue-500"
>
<option value="en_us_ghostface">Ghost Face</option>
<option value="en_us_chewbacca">Chewbacca</option>
<option value="en_us_c3po">C3PO</option>
<option value="en_us_stitch">Stitch</option>
<option value="en_us_stormtrooper">Stormtrooper</option>
<option value="en_us_rocket">Rocket</option>
<option value="en_au_001">English AU - Female</option>
<option value="en_au_002">English AU - Male</option>
<option value="en_uk_001">English UK - Male 1</option>
<option value="en_uk_003">English UK - Male 2</option>
<option value="en_us_001">English US - Female (Int. 1)</option>
<option value="en_us_002">English US - Female (Int. 2)</option>
<option value="en_us_006">English US - Male 1</option>
<option value="en_us_007">English US - Male 2</option>
<option value="en_us_009">English US - Male 3</option>
<option value="en_us_010">English US - Male 4</option>
<option value="fr_001">French - Male 1</option>
<option value="fr_002">French - Male 2</option>
<option value="de_001">German - Female</option>
<option value="de_002">German - Male</option>
<option value="es_002">Spanish - Male</option>
<option value="es_mx_002">Spanish MX - Male</option>
<option value="br_001">Portuguese BR - Female 1</option>
<option value="br_003">Portuguese BR - Female 2</option>
<option value="br_004">Portuguese BR - Female 3</option>
<option value="br_005">Portuguese BR - Male</option>
<option value="id_001">Indonesian - Female</option>
<option value="jp_001">Japanese - Female 1</option>
<option value="jp_003">Japanese - Female 2</option>
<option value="jp_005">Japanese - Female 3</option>
<option value="jp_006">Japanese - Male</option>
<option value="kr_002">Korean - Male 1</option>
<option value="kr_003">Korean - Female</option>
<option value="kr_004">Korean - Male 2</option>
<option value="en_female_f08_salut_damour">Alto</option>
<option value="en_male_m03_lobby">Tenor</option>
<option value="en_female_f08_warmy_breeze">Warmy Breeze</option>
<option value="en_male_m03_sunshine_soon">Sunshine Soon</option>
<option value="en_male_narration">narrator</option>
<option value="en_male_funny">wacky</option>
<option value="en_female_emotional">peaceful</option>
</select>
<label for="zipUrl" class="text-blue-600"
>Zip URL (Leave empty for default)</label
>
<input
type="text"
name="zipUrl"
id="zipUrl"
class="border-2 border-blue-300 p-2 rounded-md focus:outline-none focus:border-blue-500"
/>
<label for="videoSubject" class="text-blue-600">Subject</label>
<textarea
rows="3"
Expand All @@ -99,39 +32,125 @@ <h1 class="text-4xl text-center mb-4">MoneyPrinter</h1>
id="videoSubject"
class="border-2 border-blue-300 p-2 rounded-md focus:outline-none focus:border-blue-500"
></textarea>
<label for="paragraphNumber" class="text-blue-600"
>Paragraph Number</label
>
<input
type="number"
name="paragraphNumber"
id="paragraphNumber"
class="border-2 border-blue-300 p-2 rounded-md focus:outline-none focus:border-blue-500"
value="1"
min="1"
max="100"
/>
<label
for="youtubeUploadToggle"
class="flex items-center text-blue-600"
<button id="advancedOptionsToggle" class="text-blue-600">
Show Advanced Options ▼
</button>
<div
class="flex flex-col space-y-4 hidden transition-all duration-150 linear"
id="advancedOptions"
>
<label for="aiModel" class="text-blue-600">AI Model</label>
<select
name="aiModel"
id="aiModel"
class="w-full border-2 border-blue-300 p-2 rounded-md focus:outline-none focus:border-blue-500"
>
<option value="g4f">g4f (Free)</option>
<option value="gpt3.5-turbo">OpenAI GPT-3.5</option>
<option value="gpt4">OpenAI GPT-4</option>
</select>
<label for="voice" class="text-blue-600">Voice</label>
<select
name="voice"
id="voice"
class="w-min border-2 border-blue-300 p-2 rounded-md focus:outline-none focus:border-blue-500"
>
<option value="en_us_ghostface">Ghost Face</option>
<option value="en_us_chewbacca">Chewbacca</option>
<option value="en_us_c3po">C3PO</option>
<option value="en_us_stitch">Stitch</option>
<option value="en_us_stormtrooper">Stormtrooper</option>
<option value="en_us_rocket">Rocket</option>
<option value="en_au_001">English AU - Female</option>
<option value="en_au_002">English AU - Male</option>
<option value="en_uk_001">English UK - Male 1</option>
<option value="en_uk_003">English UK - Male 2</option>
<option value="en_us_001">English US - Female (Int. 1)</option>
<option value="en_us_002">English US - Female (Int. 2)</option>
<option value="en_us_006">English US - Male 1</option>
<option value="en_us_007">English US - Male 2</option>
<option value="en_us_009">English US - Male 3</option>
<option value="en_us_010">English US - Male 4</option>
<option value="fr_001">French - Male 1</option>
<option value="fr_002">French - Male 2</option>
<option value="de_001">German - Female</option>
<option value="de_002">German - Male</option>
<option value="es_002">Spanish - Male</option>
<option value="es_mx_002">Spanish MX - Male</option>
<option value="br_001">Portuguese BR - Female 1</option>
<option value="br_003">Portuguese BR - Female 2</option>
<option value="br_004">Portuguese BR - Female 3</option>
<option value="br_005">Portuguese BR - Male</option>
<option value="id_001">Indonesian - Female</option>
<option value="jp_001">Japanese - Female 1</option>
<option value="jp_003">Japanese - Female 2</option>
<option value="jp_005">Japanese - Female 3</option>
<option value="jp_006">Japanese - Male</option>
<option value="kr_002">Korean - Male 1</option>
<option value="kr_003">Korean - Female</option>
<option value="kr_004">Korean - Male 2</option>
<option value="en_female_f08_salut_damour">Alto</option>
<option value="en_male_m03_lobby">Tenor</option>
<option value="en_female_f08_warmy_breeze">Warmy Breeze</option>
<option value="en_male_m03_sunshine_soon">Sunshine Soon</option>
<option value="en_male_narration">narrator</option>
<option value="en_male_funny">wacky</option>
<option value="en_female_emotional">peaceful</option>
</select>
<label for="zipUrl" class="text-blue-600"
>Zip URL (Leave empty for default)</label
>
<input
type="checkbox"
name="youtubeUploadToggle"
id="youtubeUploadToggle"
class="mr-2"
type="text"
name="zipUrl"
id="zipUrl"
class="border-2 border-blue-300 p-2 rounded-md focus:outline-none focus:border-blue-500"
/>
Upload to YouTube
</label>
<label for="useMusicToggle" class="flex items-center text-blue-600">
<label for="threads" class="text-blue-600">Threads</label>
<input
type="checkbox"
name="useMusicToggle"
id="useMusicToggle"
class="mr-2"
type="number"
name="threads"
id="threads"
class="border-2 border-blue-300 p-2 rounded-md focus:outline-none focus:border-blue-500"
value="2"
min="1"
max="100"
placeholder="2 (Default)"
/>
Use Music
</label>
<label for="paragraphNumber" class="text-blue-600"
>Paragraph Number</label
>
<input
type="number"
name="paragraphNumber"
id="paragraphNumber"
class="border-2 border-blue-300 p-2 rounded-md focus:outline-none focus:border-blue-500"
value="1"
min="1"
max="100"
/>
<label
for="youtubeUploadToggle"
class="flex items-center text-blue-600"
>
<input
type="checkbox"
name="youtubeUploadToggle"
id="youtubeUploadToggle"
class="mr-2"
/>
Upload to YouTube
</label>
<label for="useMusicToggle" class="flex items-center text-blue-600">
<input
type="checkbox"
name="useMusicToggle"
id="useMusicToggle"
class="mr-2"
/>
Use Music
</label>
</div>
<button
id="generateButton"
class="bg-blue-500 hover:bg-blue-700 duration-100 linear text-white px-4 py-2 rounded-md"
Expand Down
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ To use your own music, compress all your MP3 Files into a ZIP file and upload it

It is recommended to use Services such as [Filebin](https://filebin.net) to upload your ZIP file.

You can also just move your MP3 files into the `Songs` folder.

## Fonts 🅰

Add your fonts to the `fonts/` folder, and load them by specifying the font name on line `124` in `Backend/video.py`.
Expand Down Expand Up @@ -80,6 +82,31 @@ Videos are uploaded as private by default. For a completely automated workflow,

For videos that have been locked as private due to upload via an unverified API service, you will not be able to appeal. You’ll need to re-upload the video via a verified API service or via the YouTube app/site. The unverified API service can also apply for an API audit. So make sure to verify your API, see [OAuth App Verification Help Center](https://support.google.com/cloud/answer/13463073) for more information.

## FAQ 🤔

### How do I get the TikTok session ID?

You can obtain your TikTok session ID by logging into TikTok in your browser and copying the value of the `sessionid` cookie.

### My ImageMagick binary is not being detected

Make sure you set your path to the ImageMagick binary correctly in the `.env` file, it should look something like this:

```env
IMAGEMAGICK_BINARY="C:\\Program Files\\ImageMagick-7.1.0-Q16\\magick.exe"
```

Don't forget to use double backslashes (`\\`) in the path, instead of one.

### I can't install `playsound`: Wheel failed to build

If you're having trouble installing `playsound`, you can try installing it using the following command:

```bash
pip install -U wheel
pip install -U playsound
```

## Donate 🎁

If you like and enjoy `MoneyPrinter`, and would like to donate, you can do that by clicking on the button on the right hand side of the repository. ❤️
Expand Down

0 comments on commit 09785fd

Please sign in to comment.