File size: 7,132 Bytes
8f46cb8
 
 
 
 
cb6a767
8f46cb8
 
8e79ff0
8f46cb8
 
c4d4bda
8f46cb8
c4d4bda
041a66a
 
 
 
 
 
 
 
 
8f46cb8
 
 
cb6a767
041a66a
 
8f46cb8
cb6a767
041a66a
020c59d
041a66a
 
c4d4bda
020c59d
8f46cb8
020c59d
041a66a
 
 
 
 
 
 
 
 
cb6a767
041a66a
cb6a767
041a66a
cb6a767
041a66a
cb6a767
041a66a
 
 
 
020c59d
041a66a
 
020c59d
cb6a767
041a66a
 
 
 
 
 
 
 
 
020c59d
041a66a
8f46cb8
337364d
 
 
 
 
 
 
 
8f46cb8
 
acf2787
8f46cb8
 
 
 
 
 
 
 
 
cb6a767
337364d
8f46cb8
 
 
 
 
 
 
 
 
 
 
 
acf2787
 
8f46cb8
acf2787
8f46cb8
acf2787
8f46cb8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
acf2787
 
 
 
8f46cb8
 
 
337364d
 
8f46cb8
 
 
 
cb6a767
acf2787
 
 
 
 
 
 
8f46cb8
 
acf2787
8f46cb8
acf2787
8f46cb8
 
337364d
 
 
 
 
 
acf2787
 
 
 
 
8f46cb8
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
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)