marks commited on
Commit
bd07fc2
·
1 Parent(s): 0954ce6

Properly awaiting now

Browse files
Files changed (2) hide show
  1. api_clients.py +42 -38
  2. app.py +117 -99
api_clients.py CHANGED
@@ -2,6 +2,7 @@ from functools import lru_cache
2
  from typing import List, Tuple, Optional
3
  import aiohttp
4
  import elevenlabs
 
5
  from contextlib import asynccontextmanager
6
  from logger import setup_logger, log_execution_time, log_async_execution_time
7
  from models import OpenRouterRequest, OpenRouterResponse, Message, OpenRouterModel
@@ -47,7 +48,7 @@ class OpenRouterClient:
47
  @lru_cache(maxsize=1)
48
  async def get_models(self) -> List[Tuple[str, str]]:
49
  """
50
- Fetch available models from OpenRouter API
51
 
52
  Returns:
53
  List of tuples containing (model_id, model_description)
@@ -56,14 +57,13 @@ class OpenRouterClient:
56
  ValueError: If API request fails
57
  """
58
  logger.info("Fetching available models from OpenRouter")
59
- response = requests.get(
60
- f"{self.base_url}/models",
61
- headers=self.headers
62
- )
63
- response.raise_for_status()
64
- models = [OpenRouterModel(**model) for model in response.json()["data"]]
65
- logger.info(f"Successfully fetched {len(models)} models")
66
- return [(model.id, model.name) for model in models]
67
 
68
  @log_async_execution_time(logger)
69
  async def generate_script(self, content: str, prompt: str, model_id: str) -> str:
@@ -93,15 +93,15 @@ class OpenRouterClient:
93
  ]
94
  )
95
 
96
- response = requests.post(
97
- f"{self.base_url}/chat/completions",
98
- headers=self.headers,
99
- data=request.json()
100
- )
101
- response.raise_for_status()
102
-
103
- router_response = OpenRouterResponse(**response.json())
104
- return router_response.choices[0].message.content
105
  except Exception as e:
106
  logger.error(f"Script generation failed", exc_info=True)
107
  raise
@@ -130,7 +130,7 @@ class ElevenLabsClient:
130
  self.get_voices.cache_clear()
131
 
132
  @lru_cache(maxsize=1)
133
- def get_voices(self) -> List[Tuple[str, str]]:
134
  """
135
  Fetch available voices from ElevenLabs
136
 
@@ -139,25 +139,29 @@ class ElevenLabsClient:
139
  where display_name includes voice description if available
140
  """
141
  logger.info("Fetching available voices from ElevenLabs")
142
- voices = elevenlabs.voices()
143
- logger.info(f"Successfully fetched {len(voices)} voices")
144
-
145
- voice_list = []
146
- for voice in voices:
147
- # Create descriptive name including accent and age if available
148
- description = f"{voice.name}"
149
- if hasattr(voice, 'labels') and voice.labels:
150
- if 'accent' in voice.labels:
151
- description += f" ({voice.labels['accent']})"
152
- if 'age' in voice.labels:
153
- description += f", {voice.labels['age']}"
154
- voice_list.append((voice.voice_id, description))
155
 
156
- logger.debug(f"Available voices: {[name for _, name in voice_list]}")
157
- return voice_list
158
-
159
- @log_execution_time(logger)
160
- def generate_audio(self, text: str, voice_id: str) -> bytes:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
161
  """
162
  Generate audio with comprehensive error handling and quality checks
163
 
@@ -171,7 +175,7 @@ class ElevenLabsClient:
171
 
172
  try:
173
  start_time = time.time()
174
- audio = elevenlabs.generate(
175
  text=text,
176
  voice=voice_id,
177
  model="eleven_monolingual_v1"
 
2
  from typing import List, Tuple, Optional
3
  import aiohttp
4
  import elevenlabs
5
+ import time
6
  from contextlib import asynccontextmanager
7
  from logger import setup_logger, log_execution_time, log_async_execution_time
8
  from models import OpenRouterRequest, OpenRouterResponse, Message, OpenRouterModel
 
48
  @lru_cache(maxsize=1)
49
  async def get_models(self) -> List[Tuple[str, str]]:
50
  """
51
+ Fetch available models from OpenRouter API using pydantic models
52
 
53
  Returns:
54
  List of tuples containing (model_id, model_description)
 
57
  ValueError: If API request fails
58
  """
59
  logger.info("Fetching available models from OpenRouter")
60
+ async with self.get_session() as session:
61
+ async with session.get(f"{self.base_url}/models") as response:
62
+ response.raise_for_status()
63
+ data = await response.json()
64
+ models = [OpenRouterModel(**model) for model in data["data"]]
65
+ logger.info(f"Successfully fetched {len(models)} models")
66
+ return [(model.id, model.name) for model in models]
 
67
 
68
  @log_async_execution_time(logger)
69
  async def generate_script(self, content: str, prompt: str, model_id: str) -> str:
 
93
  ]
94
  )
95
 
96
+ async with self.get_session() as session:
97
+ async with session.post(
98
+ f"{self.base_url}/chat/completions",
99
+ json=request.dict()
100
+ ) as response:
101
+ response.raise_for_status()
102
+ data = await response.json()
103
+ router_response = OpenRouterResponse(**data)
104
+ return router_response.choices[0].message.content
105
  except Exception as e:
106
  logger.error(f"Script generation failed", exc_info=True)
107
  raise
 
130
  self.get_voices.cache_clear()
131
 
132
  @lru_cache(maxsize=1)
133
+ async def get_voices(self) -> List[Tuple[str, str]]:
134
  """
135
  Fetch available voices from ElevenLabs
136
 
 
139
  where display_name includes voice description if available
140
  """
141
  logger.info("Fetching available voices from ElevenLabs")
142
+ try:
143
+ voices = await elevenlabs.voices() # Assuming elevenlabs supports async
144
+ logger.info(f"Successfully fetched {len(voices)} voices")
 
 
 
 
 
 
 
 
 
 
145
 
146
+ voice_list = []
147
+ for voice in voices:
148
+ # Create descriptive name including accent and age if available
149
+ description = f"{voice.name}"
150
+ if hasattr(voice, 'labels') and voice.labels:
151
+ if 'accent' in voice.labels:
152
+ description += f" ({voice.labels['accent']})"
153
+ if 'age' in voice.labels:
154
+ description += f", {voice.labels['age']}"
155
+ voice_list.append((voice.voice_id, description))
156
+
157
+ logger.debug(f"Available voices: {[name for _, name in voice_list]}")
158
+ return voice_list
159
+ except Exception as e:
160
+ logger.error("Failed to fetch voices", exc_info=True)
161
+ raise
162
+
163
+ @log_async_execution_time(logger)
164
+ async def generate_audio(self, text: str, voice_id: str) -> bytes:
165
  """
166
  Generate audio with comprehensive error handling and quality checks
167
 
 
175
 
176
  try:
177
  start_time = time.time()
178
+ audio = await elevenlabs.generate( # Assuming elevenlabs supports async
179
  text=text,
180
  voice=voice_id,
181
  model="eleven_monolingual_v1"
app.py CHANGED
@@ -7,6 +7,10 @@ from scraper import scrape_url
7
  from podcast_generator import PodcastGenerator
8
  from tts import text_to_speech
9
  from api_clients import OpenRouterClient, ElevenLabsClient
 
 
 
 
10
 
11
  load_dotenv()
12
 
@@ -14,114 +18,128 @@ load_dotenv()
14
  default_voices = [("", "Enter API key to load voices")]
15
  default_models = [("", "Enter API key to load models")]
16
 
17
- async def generate_podcast(
18
- url: str,
19
- openrouter_key: str,
20
- model_id: str,
21
- elevenlabs_key: str,
22
- voice_id: str
23
- ) -> Optional[str]:
24
- try:
25
- content = scrape_url(url)
26
-
27
- # Initialize API clients
28
- openrouter = OpenRouterClient(openrouter_key)
29
- elevenlabs = ElevenLabsClient(elevenlabs_key)
30
-
31
- # Generate script using OpenRouter
32
- script = await openrouter.generate_script(content, model_id)
33
-
34
- # Convert to audio using ElevenLabs
35
- audio_file = elevenlabs.generate_audio(script, voice_id)
36
- return audio_file
37
- except Exception as e:
38
- return f"Error: {str(e)}"
39
 
40
- def create_ui():
41
- with gr.Blocks(title='URL to Podcast Generator', theme='huggingface') as interface:
42
- gr.Markdown('# URL to Podcast Generator')
43
- gr.Markdown('Enter a URL to generate a podcast episode based on its content.')
 
 
 
 
 
44
 
45
- with gr.Row():
46
- with gr.Column(scale=2):
47
- url_input = gr.Textbox(
48
- label="Website URL",
49
- placeholder="Enter the URL of the website you want to convert to a podcast"
50
- )
51
-
52
- with gr.Row():
53
- with gr.Column():
54
- openrouter_key = gr.Textbox(
55
- label='OpenRouter API Key',
56
- type='password',
57
- placeholder='Enter key...'
58
- )
59
- openrouter_model = gr.Dropdown(
60
- label='AI Model',
61
- choices=default_models,
62
- value=None,
63
- allow_custom_value=True
64
- )
65
-
66
- with gr.Column():
67
- elevenlabs_key = gr.Textbox(
68
- label='ElevenLabs API Key',
69
- type='password',
70
- placeholder='Enter key...'
71
- )
72
- voice_model = gr.Dropdown(
73
- label='Voice',
74
- choices=default_voices,
75
- value=None,
76
- allow_custom_value=True
77
- )
78
-
79
- submit_btn = gr.Button('Generate Podcast', variant='primary')
80
 
81
- with gr.Column(scale=1):
82
- audio_output = gr.Audio(label="Generated Podcast")
83
- status = gr.Textbox(label='Status', interactive=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
 
85
- # Event handlers for API key changes
86
- async def update_openrouter_models(key):
87
- if not key:
88
- return gr.Dropdown(choices=default_models)
89
- try:
90
- client = OpenRouterClient(key)
91
- models = await client.get_models()
92
- return gr.Dropdown(choices=models)
93
- except Exception as e:
94
- return gr.Dropdown(choices=[("error", f"Error: {str(e)}")])
95
 
96
- def update_elevenlabs_voices(key):
97
- if not key:
98
- return gr.Dropdown(choices=default_voices)
99
- try:
100
- client = ElevenLabsClient(key)
101
- voices = client.get_voices()
102
- return gr.Dropdown(
103
- choices={name: id for id, name in voices}
104
- )
105
- except Exception as e:
106
- return gr.Dropdown(choices={"Error": None})
107
 
108
- # Set up event handlers
109
- openrouter_key.change(fn=update_openrouter_models, inputs=openrouter_key, outputs=openrouter_model)
110
- elevenlabs_key.change(fn=update_elevenlabs_voices, inputs=elevenlabs_key, outputs=voice_model)
111
-
112
- submit_btn.click(
113
- fn=lambda *args: asyncio.run(generate_podcast(*args)),
114
- inputs=[url_input, openrouter_key, openrouter_model, elevenlabs_key, voice_model],
115
- outputs=[audio_output]
116
- )
 
 
117
 
118
- return interface
 
 
 
 
 
 
 
 
119
 
120
- if __name__ == '__main__':
121
 
122
- demo = create_ui()
123
- demo.launch(
 
 
 
 
 
 
 
 
124
  server_name="0.0.0.0",
125
  server_port=7860,
126
  share=True
127
- )
 
 
 
 
7
  from podcast_generator import PodcastGenerator
8
  from tts import text_to_speech
9
  from api_clients import OpenRouterClient, ElevenLabsClient
10
+ from logger import setup_logger
11
+ from config import Config
12
+
13
+ logger = setup_logger("app")
14
 
15
  load_dotenv()
16
 
 
18
  default_voices = [("", "Enter API key to load voices")]
19
  default_models = [("", "Enter API key to load models")]
20
 
21
+ class PodcasterUI:
22
+ def __init__(self, config: Config):
23
+ self.config = config
24
+ self.router_client = OpenRouterClient(config.openrouter_api_key)
25
+ self.elevenlabs_client = ElevenLabsClient(config.elevenlabs_api_key)
26
+ # Store models and voices as instance variables
27
+ self.models = []
28
+ self.voices = []
29
+
30
+ async def initialize(self):
31
+ """Initialize API clients and fetch models/voices"""
32
+ try:
33
+ self.models = await self.router_client.get_models()
34
+ self.voices = await self.elevenlabs_client.get_voices()
35
+ except Exception as e:
36
+ logger.error("Failed to initialize API clients", exc_info=True)
37
+ raise
 
 
 
 
 
38
 
39
+ async def on_submit(self, content: str, model_id: str, voice_id: str, prompt: str = "") -> tuple:
40
+ """Handle form submission with async API calls"""
41
+ try:
42
+ script = await self.router_client.generate_script(content, prompt, model_id)
43
+ audio = await self.elevenlabs_client.generate_audio(script, voice_id)
44
+ return script, audio
45
+ except Exception as e:
46
+ logger.error("Failed to generate podcast", exc_info=True)
47
+ return str(e), None
48
 
49
+ def create_ui(self) -> gr.Interface:
50
+ with gr.Blocks(title='URL to Podcast Generator', theme='huggingface') as interface:
51
+ gr.Markdown('# URL to Podcast Generator')
52
+ gr.Markdown('Enter a URL to generate a podcast episode based on its content.')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
 
54
+ with gr.Row():
55
+ with gr.Column(scale=2):
56
+ url_input = gr.Textbox(
57
+ label="Website URL",
58
+ placeholder="Enter the URL of the website you want to convert to a podcast"
59
+ )
60
+
61
+ with gr.Row():
62
+ with gr.Column():
63
+ openrouter_key = gr.Textbox(
64
+ label='OpenRouter API Key',
65
+ type='password',
66
+ placeholder='Enter key...'
67
+ )
68
+ openrouter_model = gr.Dropdown(
69
+ label='AI Model',
70
+ choices=default_models,
71
+ value=None,
72
+ allow_custom_value=True
73
+ )
74
+
75
+ with gr.Column():
76
+ elevenlabs_key = gr.Textbox(
77
+ label='ElevenLabs API Key',
78
+ type='password',
79
+ placeholder='Enter key...'
80
+ )
81
+ voice_model = gr.Dropdown(
82
+ label='Voice',
83
+ choices=default_voices,
84
+ value=None,
85
+ allow_custom_value=True
86
+ )
87
+
88
+ submit_btn = gr.Button('Generate Podcast', variant='primary')
89
 
90
+ with gr.Column(scale=1):
91
+ audio_output = gr.Audio(label="Generated Podcast")
92
+ status = gr.Textbox(label='Status', interactive=False)
 
 
 
 
 
 
 
93
 
94
+ # Event handlers for API key changes
95
+ async def update_openrouter_models(key):
96
+ if not key:
97
+ return gr.Dropdown(choices=default_models)
98
+ try:
99
+ client = OpenRouterClient(key)
100
+ models = await client.get_models()
101
+ return gr.Dropdown(choices=models)
102
+ except Exception as e:
103
+ return gr.Dropdown(choices=[("error", f"Error: {str(e)}")])
 
104
 
105
+ async def update_elevenlabs_voices(key):
106
+ if not key:
107
+ return gr.Dropdown(choices=default_voices)
108
+ try:
109
+ client = ElevenLabsClient(key)
110
+ voices = await client.get_voices()
111
+ return gr.Dropdown(
112
+ choices={name: id for id, name in voices}
113
+ )
114
+ except Exception as e:
115
+ return gr.Dropdown(choices={"Error": None})
116
 
117
+ # Set up event handlers
118
+ openrouter_key.change(fn=update_openrouter_models, inputs=openrouter_key, outputs=openrouter_model)
119
+ elevenlabs_key.change(fn=update_elevenlabs_voices, inputs=elevenlabs_key, outputs=voice_model)
120
+
121
+ submit_btn.click(
122
+ fn=self.on_submit,
123
+ inputs=[url_input, openrouter_model, voice_model],
124
+ outputs=[audio_output]
125
+ )
126
 
127
+ return interface
128
 
129
+ def main():
130
+ config = Config()
131
+ app = PodcasterUI(config)
132
+
133
+ # Create and run initialization coroutine
134
+ loop = asyncio.get_event_loop()
135
+ loop.run_until_complete(app.initialize())
136
+
137
+ interface = app.create_ui()
138
+ interface.launch(
139
  server_name="0.0.0.0",
140
  server_port=7860,
141
  share=True
142
+ )
143
+
144
+ if __name__ == "__main__":
145
+ main()