Skip to content

Commit

Permalink
first commit
Browse files Browse the repository at this point in the history
  • Loading branch information
FujiwaraChoki committed Jan 31, 2024
0 parents commit 2c333d4
Show file tree
Hide file tree
Showing 15 changed files with 764 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .env.example
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/
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
__pycache__
.env
sounds/*
output/*
images/*
*.zip
temp/*
56 changes: 56 additions & 0 deletions Backend/constants.py
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
]
117 changes: 117 additions & 0 deletions Backend/gpt.py
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
129 changes: 129 additions & 0 deletions Backend/main.py
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)
47 changes: 47 additions & 0 deletions Backend/search.py
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
Loading

0 comments on commit 2c333d4

Please sign in to comment.