Ansnaeem's picture
Update app.py
cb6a767 verified
import gradio as gr
import os
import requests
import re
GROQ_API_KEY = os.environ.get("GROQ_API_KEY")
GROQ_API_URL = "https://api.groq.com/openai/v1/chat/completions"
MODEL_NAME = "llama-3.1-8b-instant"
SYSTEM_PROMPT = """You are 'ScriptForge AI', a professional YouTube Script Writer.
Your goal is to write highly engaging scripts in the 2nd person (using 'You', 'Your').
FORMATTING RULES (STRICT):
1. Use ONLY these two tags: [VISUAL] for scenes, and [AUDIO] for spoken words.
2. [VISUAL]: Describe the visuals, camera shots, or on-screen text.
3. [AUDIO]: Write ONLY the spoken words. No actor directions, no "Host:", no markdown headers.
4. Do not output any intro text. Start directly with a [VISUAL] or [AUDIO] tag.
5. Example:
[VISUAL]
Wide shot of a clear blue sky.
[AUDIO]
Today is going to be amazing.
"""
def parse_script(full_text):
full_text = re.sub(r'\[?(?:SCENE DESCRIPTION|SCENE|VISUALS)\]?:?', '[VISUAL]', full_text, flags=re.IGNORECASE)
full_text = re.sub(r'\[?(?:SCRIPT|NARRATION|AUDIO)\]?:?', '[AUDIO]', full_text, flags=re.IGNORECASE)
parts = re.split(r'(\[(?:VISUAL|AUDIO)\])', full_text, flags=re.IGNORECASE)
clean_audio = []
clean_visuals = []
current_tag = None
for part in parts:
part = part.strip()
if not part:
continue
if part.upper() == "[AUDIO]":
current_tag = "AUDIO"
elif part.upper() == "[VISUAL]":
current_tag = "VISUAL"
elif current_tag == "AUDIO":
content = re.sub(r'\(.*?\)', '', part, flags=re.DOTALL)
content = re.sub(r'^#+.*$', '', content, flags=re.MULTILINE)
content = re.sub(r'^\w+:\s*', '', content, flags=re.MULTILINE)
content = content.replace("**", "").replace("*", "")
if content.strip():
clean_audio.append(content.strip())
elif current_tag == "VISUAL":
clean_visuals.append(part.strip())
if not clean_audio and not clean_visuals:
lines = full_text.split('\n')
for line in lines:
line = line.strip()
if not line: continue
if line.startswith('(') or line.startswith('[') or "EXT." in line or "INT." in line:
clean_visuals.append(line)
else:
clean_audio.append(line)
return "\n\n".join(clean_audio), "\n\n".join(clean_visuals)
def save_to_file(script_text):
if not script_text:
return None
file_path = "youtube_script.txt"
with open(file_path, "w", encoding="utf-8") as f:
f.write(script_text)
return file_path
def query_groq(topic, tone, duration, hook_strength, chat_history):
if not GROQ_API_KEY:
return "Error: GROQ_API_KEY not found in environment secrets. Please add it in Settings > Secrets.", "", ""
headers = {
"Authorization": f"Bearer {GROQ_API_KEY}",
"Content-Type": "application/json"
}
user_input = f"Topic: {topic}\nTone: {tone}\nTarget Duration: {duration}\nAction: Write a full YouTube script."
messages = [{"role": "system", "content": SYSTEM_PROMPT}]
messages.extend(chat_history[-6:])
messages.append({"role": "user", "content": user_input})
try:
response = requests.post(GROQ_API_URL, headers=headers, json={
"model": MODEL_NAME,
"messages": messages,
"temperature": hook_strength
}, timeout=30)
if response.status_code == 200:
full_reply = response.json()["choices"][0]["message"]["content"]
tts_script, scenes = parse_script(full_reply)
return full_reply, tts_script, scenes
else:
return f"Error {response.status_code}: {response.text}", "", ""
except Exception as e:
return f"Request failed: {str(e)}", "", ""
css = """
footer {visibility: hidden}
"""
with gr.Blocks() as demo:
gr.Markdown("# 🎬 ScriptForge AI: YouTube Script Master")
gr.Markdown("Transform your video ideas into high-retention, audience-first scripts. *Powered by GROQ*")
with gr.Row():
with gr.Column(scale=1):
topic = gr.Textbox(label="Video Topic/Description", placeholder="E.g., How to build a PC in 2025", lines=3)
tone = gr.Dropdown(
choices=["High Energy", "Storytelling", "Educational", "Minimalist", "Aggressive/Hype"],
label="Video Tone",
value="High Energy"
)
duration = gr.Dropdown(
choices=["Shorts (<60s)", "Standard (5-10 mins)", "Deep Dive (15+ mins)"],
label="Target Duration",
value="Standard (5-10 mins)"
)
hook_strength = gr.Slider(minimum=0.1, maximum=1.5, value=0.7, step=0.1, label="Hook Strength (Creativity)")
generate_btn = gr.Button("🚀 Generate Script", variant="primary")
clear = gr.Button("Clear")
with gr.Column(scale=2):
with gr.Tabs():
with gr.TabItem("Combined View"):
chatbot = gr.Chatbot(
value=[{"role": "assistant", "content": "Hi, my name is Script Forge: your YouTube script writer. Give me a topic so I can show my creativity."}],
height=500
)
with gr.TabItem("TTS Only (Dialogue)"):
tts_output = gr.Textbox(label="Copy this for Text-to-Speech", lines=20)
download_btn = gr.Button("💾 Download Script (.txt)")
download_file = gr.File(label="Download prepared file")
with gr.TabItem("Visuals Only (Shot List)"):
scenes_output = gr.Textbox(label="Video Scene Descriptions", lines=20)
state = gr.State([{"role": "assistant", "content": "Hi, my name is Script Forge: your YouTube script writer. Give me a topic so I can show my creativity."}])
def respond_wrapper(topic, tone, duration, hook_strength, chat_history):
full_reply, tts_script, scenes = query_groq(topic, tone, duration, hook_strength, chat_history)
chat_history.append({"role": "user", "content": topic})
chat_history.append({"role": "assistant", "content": full_reply})
return chat_history, tts_script, scenes
generate_btn.click(
respond_wrapper,
[topic, tone, duration, hook_strength, state],
[chatbot, tts_output, scenes_output]
)
download_btn.click(
save_to_file,
[tts_output],
[download_file]
)
clear.click(
lambda: (None, [{"role": "assistant", "content": "Hi, my name is Script Forge: your YouTube script writer. Give me a topic so I can show my creativity."}], "", "", None),
None,
[topic, chatbot, tts_output, scenes_output, download_file]
)
if __name__ == "__main__":
demo.launch(theme=gr.themes.Soft(), css=css)