File size: 10,240 Bytes
61caafb
3ac5c08
 
61caafb
 
7c4326e
61caafb
 
 
3ac5c08
61caafb
d756e7e
61caafb
 
 
7c4326e
eab7fca
61caafb
 
25acce6
eab7fca
61caafb
eab7fca
 
61caafb
 
eab7fca
 
61caafb
 
 
 
 
 
 
 
eab7fca
3ac5c08
7c4326e
61caafb
 
 
 
 
 
 
3ac5c08
 
 
 
 
61caafb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3ac5c08
61caafb
 
3ac5c08
 
61caafb
 
3ac5c08
61caafb
3ac5c08
61caafb
3ac5c08
61caafb
3ac5c08
61caafb
3ac5c08
 
7c4326e
61caafb
 
 
 
 
 
 
03eec30
61caafb
 
03eec30
61caafb
3ac5c08
 
61caafb
 
 
 
 
 
 
 
 
 
 
7c4326e
61caafb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3ac5c08
61caafb
 
 
 
 
 
 
 
3ac5c08
7c4326e
61caafb
 
 
3ac5c08
 
61caafb
 
 
 
 
03eec30
61caafb
 
 
 
 
 
 
3ac5c08
61caafb
 
 
 
 
3ac5c08
61caafb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3ac5c08
61caafb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3ac5c08
 
 
61caafb
 
 
 
 
 
 
 
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
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
# filepath: /Users/udaylunawat/Downloads/Data-Science-Projects/NotebookLM_clone/gradio_app.py
import os
import tempfile
import gradio as gr
from notebook_lm_kokoro import generate_podcast_script, KPipeline
import soundfile as sf
import numpy as np
import ast
import shutil
import warnings
import os
import gradio as gr
import concurrent.futures
import multiprocessing
from notebook_lm_kokoro import generate_podcast_script, generate_audio_from_script
warnings.filterwarnings("ignore")

# Define number of workers based on CPU cores
NUM_WORKERS = multiprocessing.cpu_count()  # Gets total CPU cores

def process_segment(entry_and_voice_map):
    entry, voice_map = entry_and_voice_map  # Unpack the tuple
    speaker, dialogue = entry
    chosen_voice = voice_map.get(speaker, "af_heart")
    print(f"Generating audio for {speaker} with voice '{chosen_voice}'...")
    
    pipeline = KPipeline(lang_code="a", repo_id="hexgrad/Kokoro-82M")
    generator = pipeline(dialogue, voice=chosen_voice)
    
    segment_audio = []
    for _, _, audio in generator:
        segment_audio.append(audio)
        
    if segment_audio:
        return np.concatenate(segment_audio, axis=0)
    return None

def generate_audio_from_script_with_voices(script, speaker1_voice, speaker2_voice, output_file):
    voice_map = {"Speaker 1": speaker1_voice, "Speaker 2": speaker2_voice}
    
    # Clean up the script string if needed
    script = script.strip()
    if not script.startswith("[") or not script.endswith("]"):
        print("Invalid transcript format. Expected a list of tuples.")
        return None

    try:
        transcript_list = ast.literal_eval(script)
        if not isinstance(transcript_list, list):
            raise ValueError("Transcript is not a list")

        all_audio_segments = []
        # Prepare input data with voice_map for each entry
        entries_with_voice_map = [(entry, voice_map) for entry in transcript_list]

        try:
            # Process segments in parallel
            with concurrent.futures.ProcessPoolExecutor(max_workers=NUM_WORKERS) as executor:
                # Map the processing function across all dialogue entries
                results = list(executor.map(process_segment, entries_with_voice_map))
                
                # Filter out None results and combine audio segments
                all_audio_segments = [r for r in results if r is not None]
        
        except Exception as e:
            print(f"Error during audio generation: {e}")
            return None
        
        if not all_audio_segments:
            print("No audio segments were generated")
            return None

        # Add a pause between segments
        sample_rate = 24000
        pause = np.zeros(sample_rate, dtype=np.float32)
        final_audio = all_audio_segments[0]
        for seg in all_audio_segments[1:]:
            final_audio = np.concatenate((final_audio, pause, seg), axis=0)

        sf.write(output_file, final_audio, sample_rate)
        print(f"Saved final audio as {output_file}")
        return output_file

    except Exception as e:
        print(f"Error processing transcript: {e}")
        return None


def process_pdf(pdf_file, speaker1_voice, speaker2_voice, provider, api_key, openrouter_base=None):
    """Process the uploaded PDF file and generate audio"""
    try:
    
        # Set API configuration based on provider
        if provider == "openai":
            os.environ["OPENAI_API_KEY"] = api_key
            os.environ["OPENROUTER_API_BASE"] = "https://api.openai.com/v1"
        else:
            os.environ["OPENAI_API_KEY"] = api_key
            os.environ["OPENROUTER_API_BASE"] = openrouter_base or "https://openrouter.ai/api/v1"
        # Check if we received a valid file
        if pdf_file is None:
            return "No file uploaded", None
            
        # Create a temporary file with .pdf extension
        with tempfile.NamedTemporaryFile(delete=False, suffix=".pdf") as tmp:
            # For Gradio uploads, we need to copy the file
            shutil.copy2(pdf_file.name, tmp.name)
            tmp_path = tmp.name
            
        print(f"Uploaded PDF saved at {tmp_path}")

        # Generate transcript using your existing function
        transcript, transcript_path = generate_podcast_script(tmp_path, provider=provider)
        if transcript is None:
            return "Error generating transcript", None

        # Define an output file path for the generated audio
        audio_output_path = os.path.join(
            os.path.dirname(tmp_path),
            f"audio_{os.path.basename(tmp_path).replace('.pdf', '.wav')}"
        )
        
        # result = generate_audio_from_script_with_voices(
        #     transcript, 
        #     speaker1_voice, 
        #     speaker2_voice, 
        #     output_file=audio_output_path
        # )

        # Use ProcessPoolExecutor with explicit number of workers
        with concurrent.futures.ProcessPoolExecutor(max_workers=NUM_WORKERS) as executor:
            print(f"Processing with {NUM_WORKERS} CPU cores")
            # Submit audio generation task to the executor
            future = executor.submit(
                generate_audio_from_script_with_voices,
                transcript, speaker1_voice, speaker2_voice, audio_output_path
            )
            result = future.result()
            
            if result is None:
                return "Error generating audio", None
            
            return "Process complete!", result

    except Exception as e:
        print(f"Error in process_pdf: {str(e)}")
        return f"Error processing file: {str(e)}", None
        
        if result is None:
            return "Error generating audio", None
        
        return "Process complete!", result

    except Exception as e:
        print(f"Error in process_pdf: {str(e)}")
        return f"Error processing file: {str(e)}", None


def create_gradio_app():
    # Add CSS for better styling
    css = """
    .gradio-container {max-width: 900px !important}
    """
    
    with gr.Blocks(css=css, theme=gr.themes.Soft()) as app:
        gr.Markdown(
            """
            # πŸ“š NotebookLM-Kokoro TTS App
            Upload a PDF, choose voices, and generate conversational audio using Kokoro TTS.
            """
        )
        
        with gr.Row():
            with gr.Column(scale=2):
                pdf_input = gr.File(
                    label="Upload PDF Document",
                    file_types=[".pdf"],
                    type="filepath"
                )
                
                with gr.Row():
                    speaker1_voice = gr.Dropdown(
                        choices=["af_heart", "af_bella", "hf_beta"],
                        value="af_heart",
                        label="Speaker 1 Voice"
                    )
                    speaker2_voice = gr.Dropdown(
                        choices=["af_nicole", "af_heart", "bf_emma"],
                        value="bf_emma",
                        label="Speaker 2 Voice"
                    )
                

                with gr.Group():
                    provider = gr.Radio(
                        choices=["openai", "openrouter"],
                        value="openrouter",
                        label="API Provider"
                    )
                    
                    api_key = gr.Textbox(
                        label="API Key",
                        placeholder="Enter your API key here...",
                        type="password",
                        elem_classes="api-input"
                    )
                    
                    openrouter_base = gr.Textbox(
                        label="OpenRouter Base URL (optional)",
                        placeholder="https://openrouter.ai/api/v1",
                        visible=False,
                        elem_classes="api-input"
                    )

                    # Show/hide OpenRouter base URL based on provider selection
                    def toggle_openrouter_base(provider_choice):
                        return gr.update(visible=provider_choice == "openrouter")
                    
                    provider.change(
                        fn=toggle_openrouter_base,
                        inputs=[provider],
                        outputs=[openrouter_base]
                    )
                
                submit_btn = gr.Button("πŸŽ™οΈ Generate Audio", variant="primary")
            
            with gr.Column(scale=2):
                status_output = gr.Textbox(
                    label="Status",
                    placeholder="Processing status will appear here..."
                )
                audio_output = gr.Audio(
                    label="Generated Audio",
                    type="filepath"
                )
        
        # # Examples section
        # gr.Examples(
        #     examples=[
        #         ["sample.pdf", "af_heart", "af_nicole", "openrouter", "your-api-key-here", "https://openrouter.ai/api/v1"],
        #     ],
        #     inputs=[pdf_input, speaker1_voice, speaker2_voice, provider, api_key, openrouter_base],
        #     outputs=[status_output, audio_output],
        #     fn=process_pdf,
        #     cache_examples=True,
        # )
        
        submit_btn.click(
            fn=process_pdf,
            inputs=[
                pdf_input, 
                speaker1_voice, 
                speaker2_voice, 
                provider,
                api_key,
                openrouter_base
            ],
            outputs=[status_output, audio_output],
            api_name="generate"
        )
        
        gr.Markdown(
            """
            ### πŸ“ Notes
            - Make sure your PDF is readable and contains text (not scanned images)
            - Processing large PDFs may take a few minutes
            - You need a valid OpenAI/OpenRouter API key set as environment variable
            """
        )
    
    return app

if __name__ == "__main__":
    demo = create_gradio_app()
    demo.queue().launch(
        server_name="0.0.0.0",
        server_port=7860,
        share=True,
        debug=True,
        pwa=True
    )