Skip to content

Commit 473759e

Browse files
Gradio UI
1 parent 03c975e commit 473759e

8 files changed

+370
-203
lines changed

GUI.py

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import gradio as gr
2+
from src.generateVideo import generate_video
3+
import asyncio
4+
import os
5+
6+
def generate_video_wrapper(video_source, video_file, video_url, script_type, video_topic, video_script):
7+
video_path = video_file.name if video_file else None
8+
params = {
9+
"video_path_or_url": video_source,
10+
"video_path": video_path,
11+
"video_url": video_url,
12+
"video_topic": video_topic,
13+
"video_script": video_script,
14+
"video_script_type": script_type
15+
}
16+
result = asyncio.run(generate_video(**params))
17+
return str(result)
18+
19+
with gr.Blocks() as iface:
20+
with gr.Row(): # Added a row to position elements
21+
gr.Image("public/turboreel_logo.png", label="TurboReel Logo", width=200, height=50)
22+
gr.Markdown("Generate a video based on the provided parameters.")
23+
24+
with gr.Row():
25+
video_source = gr.Radio(["video_path", "video_url"], label="Video Source")
26+
script_type = gr.Radio(["based_on_topic", "ready_made_script"], label="Video Script Type")
27+
28+
with gr.Group():
29+
video_file = gr.File(label="Select File", visible=False, file_types=["video"])
30+
video_url = gr.Textbox(label="YouTube URL", visible=False)
31+
32+
with gr.Group():
33+
video_topic = gr.Textbox(label="Video Topic", visible=False)
34+
video_script = gr.Textbox(label="Video Script", lines=5, visible=False)
35+
36+
output = gr.Textbox(label="Result")
37+
submit_btn = gr.Button("Generate Video")
38+
39+
def update_visibility(video_src, script_typ):
40+
return {
41+
video_file: gr.update(visible=video_src == "video_path"),
42+
video_url: gr.update(visible=video_src == "video_url"),
43+
video_topic: gr.update(visible=script_typ == "based_on_topic"),
44+
video_script: gr.update(visible=script_typ == "ready_made_script")
45+
}
46+
47+
video_source.change(update_visibility, [video_source, script_type], [video_file, video_url, video_topic, video_script])
48+
script_type.change(update_visibility, [video_source, script_type], [video_file, video_url, video_topic, video_script])
49+
50+
submit_btn.click(
51+
generate_video_wrapper,
52+
inputs=[video_source, video_file, video_url, script_type, video_topic, video_script],
53+
outputs=output
54+
)
55+
56+
if __name__ == "__main__":
57+
iface.launch()

app.py

+14-172
Original file line numberDiff line numberDiff line change
@@ -1,180 +1,22 @@
1-
import yaml
2-
import logging
3-
from moviepy.editor import VideoFileClip, AudioFileClip, CompositeVideoClip
4-
import random
51
import asyncio
6-
from openai import OpenAI
7-
import os
8-
9-
# Set up logging
10-
logging.basicConfig(level=logging.INFO)
11-
12-
from src.ImageHandler import ImageHandler
13-
from src.AIShortGenerator import AIShortGenerator
14-
15-
def load_config(file_path):
16-
"""Load the YAML configuration file."""
17-
try:
18-
with open(file_path, 'r') as file:
19-
config = yaml.safe_load(file)
20-
return config
21-
except FileNotFoundError:
22-
logging.error(f"Config file {file_path} not found.")
23-
raise
24-
except Exception as e:
25-
logging.error(f"Error loading config file: {e}")
26-
raise
27-
28-
def load_prompt(file_path):
29-
"""Load the YAML prompt template file."""
30-
try:
31-
with open(file_path, 'r') as file:
32-
prompt_template_file = yaml.safe_load(file)
33-
return prompt_template_file
34-
except FileNotFoundError:
35-
logging.error(f"Prompt file {file_path} not found.")
36-
raise
37-
except Exception as e:
38-
logging.error(f"Error loading prompt file: {e}")
39-
raise
40-
41-
config = load_config('config.yaml')
42-
43-
# Accessing configuration values
44-
openai_api_key = os.getenv('OPENAI_API_KEY', config['api_keys']['OPENAI_API_KEY'])
45-
pexels_api_key = os.getenv('PEXELS_API_KEY', config['api_keys']['PEXELS_API_KEY'])
46-
47-
openai = OpenAI(api_key=openai_api_key)
48-
49-
def gpt_summary_of_script(video_script):
50-
try:
51-
completion = openai.chat.completions.create(
52-
model="gpt-3.5-turbo-0125",
53-
temperature=0.25,
54-
max_tokens=250,
55-
messages=[
56-
{"role": "user", "content": f"Summarize the following video script; it is very important that you keep it to one line. \n Script: {video_script}"}
57-
]
58-
)
59-
logging.info("Script generated successfully.")
60-
return completion.choices[0].message.content
61-
except Exception as e:
62-
logging.error(f"Error generating script summary: {e}")
63-
return "" # Return an empty string on error
64-
65-
async def generate_video(video_topic='', video_url='', video_script='', video_script_type='based_on_topic'):
66-
"""Generate a video based on the provided topic or ready-made script.
67-
68-
Args:
69-
video_topic (str): The topic of the video if script type is 'based_on_topic'.
70-
video_url (str): The URL of the video to download.
71-
video_script (str): The ready-made script if script type is 'ready_made_script'.
72-
video_script_type (str): The type of script generation ('based_on_topic' or 'ready_made_script').
73-
74-
Returns:
75-
dict: A dictionary with the status of the video generation and a message.
76-
"""
77-
try:
78-
# Ensure the script type is provided
79-
if not video_script_type:
80-
raise ValueError("The video script type must be provided.")
81-
82-
# Handle the different cases for script generation
83-
if video_script_type == 'ready_made_script':
84-
if not video_script:
85-
raise ValueError("For 'ready_made_script', the video script should not be null.")
86-
if len(video_script) > 400:
87-
raise ValueError("The video script exceeds the 400 character limit.")
88-
89-
elif video_script_type == 'based_on_topic':
90-
if not video_topic:
91-
raise ValueError("For 'based_on_topic', the video topic should not be null.")
92-
# video_script = generate_script_from_topic(video_topic) # You'd need to implement this
93-
94-
else:
95-
raise ValueError("Invalid video script type. It must be either 'ready_made_script' or 'based_on_topic'.")
96-
97-
# Load prompt template
98-
prompt_template = load_prompt('prompt_templates/reddit_thread.yaml')
99-
100-
logging.info(f"Starting video generation process for: {video_script_type}")
101-
ai_short_gen = AIShortGenerator(openai_api_key)
102-
image_handler = ImageHandler(pexels_api_key, openai_api_key)
103-
104-
video_path = ai_short_gen.download_video(video_url)
105-
if not video_path:
106-
logging.error("Failed to download video.")
107-
return {"status": "error", "message": "No video path provided."}
108-
109-
script = video_script if video_script_type == 'ready_made_script' else await ai_short_gen.generate_script(video_topic, prompt_template)
110-
if not script:
111-
logging.error("Failed to generate script.")
112-
return {"status": "error", "message": "Failed to generate script."}
113-
114-
audio_path = await ai_short_gen.generate_voice(script)
115-
if not audio_path:
116-
logging.error("Failed to generate audio.")
117-
return {"status": "error", "message": "Failed to generate audio."}
118-
119-
audio_clip = AudioFileClip(str(audio_path))
120-
audio_length = audio_clip.duration
121-
audio_clip.close()
122-
123-
video_length = VideoFileClip(video_path).duration
124-
max_start_time = video_length - audio_length
125-
126-
if max_start_time <= 0:
127-
logging.error("Calculated start time is invalid.")
128-
return {"status": "error", "message": "Calculated start time is invalid."}
129-
130-
start_time = random.uniform(0, max_start_time)
131-
end_time = start_time + audio_length
132-
133-
cut_video_path = ai_short_gen.cut_video(video_path, start_time, end_time)
134-
if not cut_video_path:
135-
logging.error("Failed to cut video.")
136-
return {"status": "error", "message": "Failed to cut video."}
137-
138-
subtitles_path = ai_short_gen.generate_subtitles(audio_path)
139-
logging.info(f"Subtitles generated successfully.")
140-
141-
video_context = video_script if video_script_type == 'ready_made_script' else video_topic
142-
image_paths = image_handler.get_images_from_subtitles(subtitles_path, video_context)
143-
logging.info(f"Downloaded images successfully.")
144-
145-
clip = ai_short_gen.add_audio_and_captions_to_video(cut_video_path, audio_path, subtitles_path)
146-
ai_short_gen.add_images_to_video(clip, image_paths)
147-
148-
# Cleanup: Ensure temporary files are removed
149-
cleanup_files([audio_path, cut_video_path, subtitles_path])
150-
151-
return {"status": "success", "message": "Video generated successfully."}
152-
153-
except Exception as e:
154-
logging.error(f"Error in video generation: {e}")
155-
return {"status": "error", "message": f"Error in video generation: {str(e)}"}
156-
157-
158-
def cleanup_files(file_paths):
159-
"""Delete temporary files to clean up the workspace."""
160-
for file_path in file_paths:
161-
try:
162-
if os.path.exists(file_path):
163-
os.remove(file_path)
164-
logging.info(f"Deleted temporary file: {file_path}")
165-
else:
166-
logging.warning(f"File not found: {file_path}")
167-
except Exception as e:
168-
logging.error(f"Error deleting file {file_path}: {e}")
2+
from src.generateVideo import generate_video
3+
import yaml
1694

5+
with open('./config.yaml', 'r') as file:
6+
config = yaml.safe_load(file)
1707

1718
# Example usage
172-
video_url = config['assets']['YOUTUBE_URL']
9+
video_path_or_url = config['assets']['VIDEO_PATH_OR_URL']
10+
video_url = config['assets']['VIDEO_YOUTUBE_URL']
11+
video_path = config['assets']['VIDEO_PATH']
17312
video_topic = config['video_parameters']['VIDEO_TOPIC']
17413
video_script = config['video_parameters']['VIDEO_SCRIPT']
17514
video_script_type = config['video_parameters']['VIDEO_SCRIPT_TYPE']
17615

177-
asyncio.run(generate_video(video_topic=video_topic,
178-
video_url=video_url,
179-
video_script=video_script,
180-
video_script_type=video_script_type))
16+
asyncio.run(generate_video(
17+
video_path_or_url=video_path_or_url,
18+
video_path=video_path,
19+
video_url=video_url,
20+
video_topic=video_topic,
21+
video_script=video_script,
22+
video_script_type=video_script_type))

config-example.yaml

+4-2
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@ api_keys:
55
PEXELS_API_KEY: "kGH..." # Your Pexels API key
66

77
assets:
8-
YOUTUBE_URL: "https://www.youtube.com/watch?v=eeUqAE9EH6w" # URL of the YouTube video
8+
VIDEO_PATH_OR_URL: "video_path" #options (video_path, video_url) this is used to determine which path to use in generateVideo.py
9+
VIDEO_PATH: "downloads/minecraft_parkour.mp4"
10+
VIDEO_YOUTUBE_URL: "https://www.youtube.com/watch?v=eeUqAE9EH6w" # URL of the YouTube video
911

1012
video_parameters:
1113
VIDEO_SCRIPT_TYPE: "based_on_topic" #options (based_on_topic, ready_made_script)
1214
VIDEO_TOPIC: "football" # The topic of the video
13-
VIDEO_SCRIPT: | #leave empty to generate based on the video_topic
15+
VIDEO_SCRIPT: |
1416
1517
Hey everyone! Let’s dive into something that’s transforming our world: artificial intelligence!
1618

public/turboreel_logo.png

10.8 KB
Loading

requirements.txt

+35-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1+
aiofiles==23.2.1
12
annotated-types==0.7.0
23
anyio==4.6.0
4+
asgiref==3.8.1
35
blinker==1.8.2
6+
boto3==1.35.38
7+
botocore==1.35.38
48
Brotli==1.1.0
59
certifi==2024.8.30
610
chardet==5.2.0
@@ -9,37 +13,66 @@ click==8.1.7
913
decorator==4.4.2
1014
distro==1.9.0
1115
exceptiongroup==1.2.2
16+
fastapi==0.115.0
17+
ffmpy==0.4.0
18+
filelock==3.16.1
19+
Flask==3.0.3
20+
fsspec==2024.9.0
21+
gradio==5.0.1
22+
gradio_client==1.4.0
23+
gunicorn==23.0.0
1224
h11==0.14.0
1325
httpcore==1.0.6
1426
httpx==0.27.2
27+
huggingface-hub==0.25.2
1528
idna==3.10
1629
imageio==2.35.1
1730
imageio-ffmpeg==0.5.1
1831
itsdangerous==2.2.0
1932
Jinja2==3.1.4
2033
jiter==0.5.0
21-
MarkupSafe==3.0.0
34+
jmespath==1.0.1
35+
markdown-it-py==3.0.0
36+
MarkupSafe==2.1.5
37+
mdurl==0.1.2
2238
moviepy==1.0.3
2339
mutagen==1.47.0
2440
numpy==2.1.1
2541
openai==1.51.0
2642
opencv-python==4.10.0.84
43+
orjson==3.10.7
2744
packaging==24.1
45+
pandas==2.2.3
2846
pillow==10.4.0
2947
proglog==0.1.10
3048
pycryptodomex==3.21.0
3149
pydantic==2.9.2
3250
pydantic_core==2.23.4
51+
pydub==0.25.1
52+
Pygments==2.18.0
3353
pysrt==1.1.2
54+
python-dateutil==2.9.0.post0
3455
python-dotenv==1.0.1
56+
python-multipart==0.0.12
57+
pytz==2024.2
3558
PyYAML==6.0.2
3659
requests==2.32.3
60+
rich==13.9.2
61+
ruff==0.6.9
62+
s3transfer==0.10.3
63+
semantic-version==2.10.0
64+
shellingham==1.5.4
3765
six==1.16.0
3866
sniffio==1.3.1
67+
starlette==0.38.6
68+
tomlkit==0.12.0
3969
tqdm==4.66.5
70+
typer==0.12.5
4071
typing_extensions==4.12.2
72+
tzdata==2024.2
4173
urllib3==2.2.3
42-
websockets==13.1
74+
uvicorn==0.31.1
75+
websockets==12.0
4376
Werkzeug==3.0.4
4477
whisper==1.1.10
4578
youtube-dl==2021.12.17

0 commit comments

Comments
 (0)