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)