Skip to content

Commit c84cc33

Browse files
improved captions and hook, implemented "add images" option
1 parent 1c95c03 commit c84cc33

17 files changed

+1196
-377
lines changed

.env-example

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
OPENAI_API_KEY=sk-proj-ghj...
2+
PEXELS_API_KEY=hAi...

.gitignore

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
.env
22
venv/
33
config.yaml
4-
__pycache__/
4+
__pycache__/
5+
tests.py

GUI.py

+130-46
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,141 @@
11
import gradio as gr
2-
from src.generateVideo import generate_video
2+
from src.reddit_story_engine import RedditStoryGenerator
3+
from src.ready_made_script_engine import ReadyMadeScriptGenerator
34
import asyncio
5+
import logging
6+
import traceback
47
import os
58

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)
9+
reddit_story_generator = RedditStoryGenerator()
10+
ready_made_script_generator = ReadyMadeScriptGenerator()
1811

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")
12+
logging.basicConfig(level=logging.INFO)
13+
logger = logging.getLogger(__name__)
14+
15+
def generate_video_reddit(video_source, video_file, video_url, video_topic, add_images):
16+
try:
17+
video_path = video_file.name if video_file else None
18+
params = {
19+
"video_path_or_url": video_source,
20+
"video_path": video_path,
21+
"video_url": video_url,
22+
"video_topic": video_topic,
23+
"add_images": add_images
4524
}
25+
result = asyncio.run(reddit_story_generator.generate_video(**params))
26+
return result # Return the result dictionary and None for the button update
27+
except Exception as e:
28+
logger.error(traceback.format_exc())
29+
return {"status": "error", "message": str(e)}
30+
31+
def generate_video_ready_made(video_source, video_hook, video_file, video_url, video_script, add_images):
32+
try:
33+
video_path = video_file.name if video_file else None
34+
params = {
35+
"video_path_or_url": video_source,
36+
"video_path": video_path,
37+
"video_url": video_url,
38+
"video_hook": video_hook,
39+
"video_script": video_script,
40+
"add_images": add_images
41+
}
42+
result = asyncio.run(ready_made_script_generator.generate_video(**params))
43+
return result # Return only the result dictionary
44+
except Exception as e:
45+
logger.error(traceback.format_exc())
46+
return {"status": "error", "message": str(e)}
47+
48+
with gr.Blocks() as iface:
49+
gr.Markdown("# TurboReel Video Generator")
50+
gr.Image("public/turboreel_logo.png", label="TurboReel Logo", width="full", height=60)
51+
52+
with gr.Tabs():
53+
with gr.TabItem("Reddit Story Videos"):
54+
55+
##title
56+
gr.Markdown("Generate videos based on popular Reddit stories and topics.")
57+
58+
with gr.Row():
59+
reddit_video_source = gr.Radio(["video_path", "video_url"], label="Video Source")
60+
61+
with gr.Group():
62+
reddit_video_file = gr.File(label="Select File", visible=False, file_types=["video"])
63+
reddit_video_url = gr.Textbox(label="YouTube URL", visible=False)
64+
65+
reddit_video_topic = gr.Textbox(label="Video Topic", placeholder="Enter a topic")
66+
reddit_add_images = gr.Checkbox(label="Add Images", value=True)
67+
reddit_output = gr.Textbox(label="Result")
68+
reddit_download_btn = gr.File(label="Download Generated Video", visible=False)
69+
reddit_submit_btn = gr.Button("Generate Reddit Story Video")
70+
71+
with gr.TabItem("Ready-Made Script Videos"):
72+
##title
73+
gr.Markdown("Generate videos using your own custom scripts and hooks.")
4674

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])
75+
with gr.Row():
76+
ready_made_video_source = gr.Radio(["video_path", "video_url"], label="Video Source")
77+
78+
with gr.Group():
79+
ready_made_video_file = gr.File(label="Select File", visible=False, file_types=["video"])
80+
ready_made_video_url = gr.Textbox(label="YouTube URL", visible=False)
81+
82+
ready_made_video_hook = gr.Textbox(label="Video Hook", placeholder="Enter a one-liner hook. This is the first thing that will be seen by the user. It's important because it will determine if the user watches the video or not. \n\nIf no hook is provided, we will generate one for you.", max_length=80)
83+
ready_made_video_script = gr.Textbox(label="Video Script", lines=5, placeholder="Enter a script", max_length=1000)
84+
ready_made_add_images = gr.Checkbox(label="Add Images", value=True)
85+
ready_made_output = gr.Textbox(label="Result")
86+
ready_made_download_btn = gr.File(label="Download Generated Video", visible=False)
87+
ready_made_submit_btn = gr.Button("Generate Ready-Made Script Video")
88+
89+
def update_visibility(video_src):
90+
return (
91+
gr.update(visible=video_src == "video_path"),
92+
gr.update(visible=video_src == "video_url")
93+
)
94+
95+
reddit_video_source.change(
96+
fn=update_visibility,
97+
inputs=[reddit_video_source],
98+
outputs=[reddit_video_file, reddit_video_url]
99+
)
100+
101+
ready_made_video_source.change(
102+
fn=update_visibility,
103+
inputs=[ready_made_video_source],
104+
outputs=[ready_made_video_file, ready_made_video_url]
105+
)
106+
107+
def process_result(result):
108+
if isinstance(result, str):
109+
try:
110+
result = eval(result)
111+
except:
112+
return {"status": "error", "message": result}, gr.update(visible=False), None
113+
114+
if result["status"] == "success":
115+
output_message = f"Status: {result['status']}\nMessage: {result['message']}\nOutput Path: {result['output_path']}"
116+
return output_message, gr.update(visible=False), gr.update(value=result['output_path'], visible=True)
117+
else:
118+
return f"Status: {result['status']}\nMessage: {result['message']}", gr.update(visible=False), None
119+
120+
reddit_submit_btn.click(
121+
generate_video_reddit,
122+
inputs=[reddit_video_source, reddit_video_file, reddit_video_url, reddit_video_topic, reddit_add_images],
123+
outputs=reddit_output
124+
).then(
125+
process_result,
126+
inputs=reddit_output,
127+
outputs=[reddit_output, reddit_submit_btn, reddit_download_btn]
128+
)
49129

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
130+
ready_made_submit_btn.click(
131+
generate_video_ready_made,
132+
inputs=[ready_made_video_source, ready_made_video_hook, ready_made_video_file, ready_made_video_url, ready_made_video_script, ready_made_add_images],
133+
outputs=ready_made_output
134+
).then(
135+
process_result,
136+
inputs=ready_made_output,
137+
outputs=[ready_made_output, ready_made_submit_btn, ready_made_download_btn]
54138
)
55139

56140
if __name__ == "__main__":
57-
iface.launch()
141+
iface.launch()

app.py

+23-19
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,26 @@
1-
import asyncio
2-
from src.generateVideo import generate_video
3-
import yaml
1+
"""
2+
Nothing to see here,
43
5-
with open('./config.yaml', 'r') as file:
6-
config = yaml.safe_load(file)
4+
1. Create an .env file with the following variables:
5+
OPENAI_API_KEY=<your_openai_api_key>
6+
PEXELS_API_KEY=<your_pexels_api_key>
77
8-
# Example usage
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']
12-
video_topic = config['video_parameters']['VIDEO_TOPIC']
13-
video_script = config['video_parameters']['VIDEO_SCRIPT']
14-
video_script_type = config['video_parameters']['VIDEO_SCRIPT_TYPE']
8+
2. Run GUI.py to start the interface
9+
python3 GUI.py
1510
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))
11+
12+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠀⠀⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
13+
⠀⠀⠀⠀⠀⠀⠀⠀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠳⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
14+
⠀⠀⠀⠀⠀⠀⣀⡴⢧⣀⠀⠀⣀⣠⠤⠤⠤⠤⣄⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
15+
⠀⠀⠀⠀⠀⠀⠀⠘⠏⢀⡴⠊⠁⠀⠀⠀⠀⠀⠀⠈⠙⠦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀
16+
⠀⠀⠀⠀⠀⠀⠀⠀⣰⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⢶⣶⣒⣶⠦⣤⣀⠀⠀
17+
⠀⠀⠀⠀⠀⠀⢀⣰⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⣟⠲⡌⠙⢦⠈⢧⠀
18+
⠀⠀⠀⣠⢴⡾⢟⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣸⡴⢃⡠⠋⣠⠋⠀
19+
⠐⠀⠞⣱⠋⢰⠁⢿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣠⠤⢖⣋⡥⢖⣫⠔⠋⠀⠀⠀
20+
⠈⠠⡀⠹⢤⣈⣙⠚⠶⠤⠤⠤⠴⠶⣒⣒⣚⣩⠭⢵⣒⣻⠭⢖⠏⠁⢀⣀⠀⠀⠀⠀
21+
⠠⠀⠈⠓⠒⠦⠭⠭⠭⣭⠭⠭⠭⠭⠿⠓⠒⠛⠉⠉⠀⠀⣠⠏⠀⠀⠘⠞⠀⠀⠀⠀
22+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠓⢤⣀⠀⠀⠀⠀⠀⠀⣀⡤⠞⠁⠀⣰⣆⠀⠀⠀⠀⠀⠀
23+
⠀⠀⠀⠀⠀⠘⠿⠀⠀⠀⠀⠀⠈⠉⠙⠒⠒⠛⠉⠁⠀⠀⠀⠉⢳⡞⠉⠀⠀⠀⠀⠀
24+
25+
26+
"""

config-example.yaml

-29
This file was deleted.

prompt_templates/reddit_thread.yaml

+7
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,13 @@ system_prompt: |
1818
- The language should be casual and relatable, as if you’re a friend sharing a story.
1919
- The story should begin with "I" and dive straight into the narrative, without titles or conclusions.
2020
21+
**Instructions for the JSON output:**
22+
- The output should be a JSON object with the following structure:
23+
{
24+
"reddit_question": "The Reddit question text",
25+
"youtube_short_story": "The YouTube short story text"
26+
}
27+
2128
user_prompt: |
2229
- New generated story. The story must be highly unusual and spicy, surprising listeners and hooking them into the narrative.
2330
- Reddit question topic:

src/captions/caption_handler.py

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import os
2+
import logging
3+
4+
from .subtitle_generator import SubtitleGenerator
5+
from .video_captioner import VideoCaptioner
6+
7+
# Load environment variables from .env file
8+
from dotenv import load_dotenv
9+
load_dotenv()
10+
11+
# Set up logging
12+
logging.basicConfig(level=logging.INFO)
13+
14+
15+
class CaptionHandler:
16+
def __init__(self):
17+
self.subtitle_generator = SubtitleGenerator()
18+
self.video_captioner = VideoCaptioner()
19+
self.default_font = "Dacherry.ttf"
20+
21+
async def process(self, audio_file: str, captions_color="white", shadow_color="cyan", font_size=30, font=None):
22+
subtitles_file = await self.subtitle_generator.generate_subtitles(audio_file)
23+
caption_clips = self.video_captioner.generate_captions_to_video(
24+
subtitles_file,
25+
font=None,
26+
captions_color=captions_color,
27+
shadow_color=shadow_color,
28+
font_size=font_size
29+
)
30+
return subtitles_file, caption_clips

src/captions/fonts/Dacherry.ttf

34.1 KB
Binary file not shown.

0 commit comments

Comments
 (0)