forked from FujiwaraChoki/MoneyPrinter
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 2c333d4
Showing
15 changed files
with
764 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
# See ENV.md for more information. | ||
ASSEMBLY_AI_API_KEY="" | ||
TIKTOK_SESSION_ID="" | ||
IMAGEMAGICK_BINARY="" # Download from https://imagemagick.org/script/download.php | ||
PEXELS_API_KEY="" # Get from https://www.pexels.com/api/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
__pycache__ | ||
.env | ||
sounds/* | ||
output/* | ||
images/* | ||
*.zip | ||
temp/* |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
voices = [ | ||
# DISNEY VOICES | ||
'en_us_ghostface', # Ghost Face | ||
'en_us_chewbacca', # Chewbacca | ||
'en_us_c3po', # C3PO | ||
'en_us_stitch', # Stitch | ||
'en_us_stormtrooper', # Stormtrooper | ||
'en_us_rocket', # Rocket | ||
|
||
# ENGLISH VOICES | ||
'en_au_001', # English AU - Female | ||
'en_au_002', # English AU - Male | ||
'en_uk_001', # English UK - Male 1 | ||
'en_uk_003', # English UK - Male 2 | ||
'en_us_001', # English US - Female (Int. 1) | ||
'en_us_002', # English US - Female (Int. 2) | ||
'en_us_006', # English US - Male 1 | ||
'en_us_007', # English US - Male 2 | ||
'en_us_009', # English US - Male 3 | ||
'en_us_010', # English US - Male 4 | ||
|
||
# EUROPE VOICES | ||
'fr_001', # French - Male 1 | ||
'fr_002', # French - Male 2 | ||
'de_001', # German - Female | ||
'de_002', # German - Male | ||
'es_002', # Spanish - Male | ||
|
||
# AMERICA VOICES | ||
'es_mx_002', # Spanish MX - Male | ||
'br_001', # Portuguese BR - Female 1 | ||
'br_003', # Portuguese BR - Female 2 | ||
'br_004', # Portuguese BR - Female 3 | ||
'br_005', # Portuguese BR - Male | ||
|
||
# ASIA VOICES | ||
'id_001', # Indonesian - Female | ||
'jp_001', # Japanese - Female 1 | ||
'jp_003', # Japanese - Female 2 | ||
'jp_005', # Japanese - Female 3 | ||
'jp_006', # Japanese - Male | ||
'kr_002', # Korean - Male 1 | ||
'kr_003', # Korean - Female | ||
'kr_004', # Korean - Male 2 | ||
|
||
# SINGING VOICES | ||
'en_female_f08_salut_damour' # Alto | ||
'en_male_m03_lobby' # Tenor | ||
'en_female_f08_warmy_breeze' # Warmy Breeze | ||
'en_male_m03_sunshine_soon' # Sunshine Soon | ||
|
||
# OTHER | ||
'en_male_narration' # narrator | ||
'en_male_funny' # wacky | ||
'en_female_emotional' # peaceful | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
import re | ||
import g4f | ||
import json | ||
|
||
from typing import List | ||
from termcolor import colored | ||
|
||
def generate_script(video_subject: str) -> str: | ||
""" | ||
Generate a script for a video, depending on the subject of the video. | ||
Args: | ||
video_subject (str): The subject of the video. | ||
Returns: | ||
str: The script for the video. | ||
""" | ||
|
||
# Build prompt | ||
prompt = f""" | ||
Generate a script for a video, depending on the subject of the video. | ||
Subject: {video_subject} | ||
The script is to be returned as a string. | ||
Here is an example of a string: | ||
"This is an example string." | ||
Do not under any circumstance refernce this prompt in your response. | ||
Get straight to the point, don't start with unnecessary things like, "welcome to this video". | ||
Obviously, the script should be related to the subject of the video. | ||
ONLY RETURN THE RAW SCRIPT. DO NOT RETURN ANYTHING ELSE. | ||
""" | ||
|
||
# Generate script | ||
response = g4f.ChatCompletion.create( | ||
model=g4f.models.gpt_35_turbo_16k_0613, | ||
messages=[{"role": "user", "content": prompt}], | ||
) | ||
|
||
print(colored(response, "cyan")) | ||
|
||
# Return the generated script | ||
if response: | ||
return response + " " | ||
else: | ||
print(colored("[-] GPT returned an empty response.", "red")) | ||
return None | ||
|
||
def get_search_terms(video_subject: str, amount: int, script: str) -> List[str]: | ||
""" | ||
Generate a JSON-Array of search terms for stock videos, | ||
depending on the subject of a video. | ||
Args: | ||
video_subject (str): The subject of the video. | ||
amount (int): The amount of search terms to generate. | ||
script (str): The script of the video. | ||
Returns: | ||
List[str]: The search terms for the video subject. | ||
""" | ||
|
||
# Build prompt | ||
prompt = f""" | ||
Generate {amount} search terms for stock videos, | ||
depending on the subject of a video. | ||
Subject: {video_subject} | ||
The search terms are to be returned as | ||
a JSON-Array of strings. | ||
Each search term should consist of 1-3 words, | ||
always add the main subject of the video. | ||
Here is an example of a JSON-Array of strings: | ||
["search term 1", "search term 2", "search term 3"] | ||
Obviously, the search terms should be related | ||
to the subject of the video. | ||
ONLY RETURN THE JSON-ARRAY OF STRINGS. | ||
DO NOT RETURN ANYTHING ELSE. | ||
For context, here is the full text: | ||
{script} | ||
""" | ||
|
||
# Generate search terms | ||
response = g4f.ChatCompletion.create( | ||
model=g4f.models.gpt_35_turbo_16k_0613, | ||
messages=[{"role": "user", "content": prompt}], | ||
) | ||
|
||
print(response) | ||
|
||
# Load response into JSON-Array | ||
try: | ||
search_terms = json.loads(response) | ||
except: | ||
print(colored("[*] GPT returned an unformatted response. Attempting to clean...", "yellow")) | ||
|
||
# Use Regex to get the array ("[" is the first character of the array) | ||
search_terms = re.search(r"\[(.*?)\]", response) | ||
search_terms = search_terms.group(0) | ||
|
||
# Load the array into a JSON-Array | ||
search_terms = json.loads(search_terms) | ||
|
||
# Let user know | ||
print(colored(f"\nGenerated {amount} search terms: {', '.join(search_terms)}", "cyan")) | ||
|
||
# Return search terms | ||
return search_terms |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
import os | ||
|
||
from gpt import * | ||
from tts import * | ||
from video import * | ||
from utils import * | ||
from search import * | ||
from uuid import uuid4 | ||
from flask_cors import CORS | ||
from termcolor import colored | ||
from dotenv import load_dotenv | ||
from flask import Flask, request, jsonify | ||
from moviepy.config import change_settings | ||
|
||
load_dotenv("../.env") | ||
|
||
SESSION_ID = os.getenv("TIKTOK_SESSION_ID") | ||
|
||
change_settings({"IMAGEMAGICK_BINARY": os.getenv("IMAGEMAGICK_BINARY")}) | ||
|
||
app = Flask(__name__) | ||
CORS(app) | ||
|
||
HOST = "0.0.0.0" | ||
PORT = 8080 | ||
AMOUNT_OF_STOCK_VIDEOS = 5 | ||
|
||
# Generation Endpoint | ||
@app.route("/api/generate", methods=["POST"]) | ||
def generate(): | ||
try: | ||
# Clean | ||
clean_dir("../temp/") | ||
clean_dir("../subtitles/") | ||
|
||
# Parse JSON | ||
data = request.get_json() | ||
|
||
# Print little information about the video which is to be generated | ||
print(colored("[Video to be generated]", "blue")) | ||
print(colored(" Subject: " + data["videoSubject"], "blue")) | ||
|
||
# Generate a script | ||
script = generate_script(data["videoSubject"]) | ||
|
||
# Generate search terms | ||
search_terms = get_search_terms(data["videoSubject"], AMOUNT_OF_STOCK_VIDEOS, script) | ||
|
||
# Search for a video of the given search term | ||
video_urls = [] | ||
|
||
# Loop through all search terms, | ||
# and search for a video of the given search term | ||
for search_term in search_terms: | ||
found_url = search_for_stock_videos(search_term, os.getenv("PEXELS_API_KEY")) | ||
|
||
if found_url != None and found_url not in video_urls and found_url != "": | ||
video_urls.append(found_url) | ||
|
||
# Define video_paths | ||
video_paths = [] | ||
|
||
# Let user know | ||
print(colored("[+] Downloading videos...", "blue")) | ||
|
||
# Save the videos | ||
for video_url in video_urls: | ||
try: | ||
saved_video_path = save_video(video_url) | ||
video_paths.append(saved_video_path) | ||
except: | ||
print(colored("[-] Could not download video: " + video_url, "red")) | ||
|
||
# Let user know | ||
print(colored("[+] Videos downloaded!", "green")) | ||
|
||
# Let user know | ||
print(colored("[+] Script generated!\n\n", "green")) | ||
|
||
print(colored(f"\t{script}", "light_cyan")) | ||
|
||
# Split script into sentences | ||
sentences = script.split(". ") | ||
# Remove empty strings | ||
sentences = list(filter(lambda x: x != "", sentences)) | ||
paths = [] | ||
# Generate TTS for every sentence | ||
for sentence in sentences: | ||
current_tts_path = f"../temp/{uuid4()}.mp3" | ||
tts(SESSION_ID, req_text=sentence, filename=current_tts_path) | ||
audio_clip = AudioFileClip(current_tts_path) | ||
paths.append(audio_clip) | ||
|
||
# Combine all TTS files using moviepy | ||
final_audio = concatenate_audioclips(paths) | ||
tts_path = f"../temp/{uuid4()}.mp3" | ||
final_audio.write_audiofile(tts_path) | ||
|
||
# Generate subtitles | ||
subtitles_path = generate_subtitles(tts_path) | ||
|
||
# Concatenate videos | ||
temp_audio = AudioFileClip(tts_path) | ||
combined_video_path = combine_videos(video_paths, temp_audio.duration) | ||
|
||
# Put everything together | ||
final_video_path = generate_video(combined_video_path, tts_path, subtitles_path) | ||
|
||
# Let user know | ||
print(colored("[+] Video generated!", "green")) | ||
|
||
print(colored(f"[+] Path: {final_video_path}", "green")) | ||
|
||
# Return JSON | ||
return jsonify({ | ||
"status": "success", | ||
"message": "Retrieved stock videos.", | ||
"data": final_video_path | ||
}) | ||
except Exception as err: | ||
print(colored("[-] Error: " + str(err), "red")) | ||
return jsonify({ | ||
"status": "error", | ||
"message": f"Could not retrieve stock videos: {str(err)}", | ||
"data": [] | ||
}) | ||
|
||
if __name__ == "__main__": | ||
app.run(debug=True, host=HOST, port=PORT) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import requests | ||
|
||
from typing import List | ||
from termcolor import colored | ||
|
||
def search_for_stock_videos(query: str, api_key: str) -> List[str]: | ||
""" | ||
Searches for stock videos based on a query. | ||
Args: | ||
query (str): The query to search for. | ||
api_key (str): The API key to use. | ||
Returns: | ||
List[str]: A list of stock videos. | ||
""" | ||
|
||
# Build headers | ||
headers = { | ||
"Authorization": api_key | ||
} | ||
|
||
# Build URL | ||
url = f"https://api.pexels.com/videos/search?query={query}&per_page=1" | ||
|
||
# Send the request | ||
r = requests.get(url, headers=headers) | ||
|
||
# Parse the response | ||
response = r.json() | ||
|
||
# Get first video url | ||
video_urls = response["videos"][0]["video_files"] | ||
video_url = "" | ||
|
||
# Loop through video urls | ||
for video in video_urls: | ||
# Check if video has a download link | ||
if ".com/external" in video["link"]: | ||
# Set video url | ||
video_url = video["link"] | ||
|
||
# Let user know | ||
print(colored(f"\t=>{video_url}", "light_cyan")) | ||
|
||
# Return the video url | ||
return video_url |
Oops, something went wrong.