Mbonea commited on
Commit
f259f51
1 Parent(s): 6ab5444

added descript

Browse files
App/TTS/Schemas.py CHANGED
@@ -19,6 +19,19 @@ class Speak(BaseModel):
19
  )
20
  super().__init__(**data)
21
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
 
23
  class HeyGenTTSRequest(BaseModel):
24
  voice_id: str = Field(default="d7bbcdd6964c47bdaae26decade4a933")
 
19
  )
20
  super().__init__(**data)
21
 
22
+ class DescriptSfxRequest(BaseModel):
23
+ query:str
24
+
25
+ class DescriptRequest(BaseModel):
26
+ text: str
27
+ speaker: Optional[str]=Field(default="Lawrance")
28
+ _voice_id: Optional[str]
29
+
30
+ class DescriptStatusRequest(BaseModel):
31
+ id:str
32
+
33
+
34
+
35
 
36
  class HeyGenTTSRequest(BaseModel):
37
  voice_id: str = Field(default="d7bbcdd6964c47bdaae26decade4a933")
App/TTS/TTSRoutes.py CHANGED
@@ -1,9 +1,10 @@
1
  from fastapi import APIRouter
2
 
3
 
4
- from .Schemas import StatusRequest, TTSGenerateRequest, HeyGenTTSRequest
5
  from .utils.Podcastle import PodcastleAPI
6
  from .utils.HeyGen import HeygenAPI
 
7
  import os
8
 
9
  tts_router = APIRouter(tags=["TTS"])
@@ -14,6 +15,8 @@ data = {
14
  "password": os.environ.get("HEYGEN_PASSWORD"),
15
  "token": os.environ.get("HEYGEN_TOKEN"),
16
  }
 
 
17
  heyGentts = HeygenAPI(**data)
18
 
19
 
@@ -28,6 +31,28 @@ async def generate_heygen_voice(req: HeyGenTTSRequest):
28
  print("hey gen here")
29
  return await heyGentts.tts_request(req)
30
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
 
32
  @tts_router.post("/status")
33
  async def search_id(req: StatusRequest):
 
1
  from fastapi import APIRouter
2
 
3
 
4
+ from .Schemas import StatusRequest, TTSGenerateRequest, HeyGenTTSRequest,DescriptRequest,DescriptStatusRequest,DescriptSfxRequest
5
  from .utils.Podcastle import PodcastleAPI
6
  from .utils.HeyGen import HeygenAPI
7
+ from .utils.Descript import DescriptTTS
8
  import os
9
 
10
  tts_router = APIRouter(tags=["TTS"])
 
15
  "password": os.environ.get("HEYGEN_PASSWORD"),
16
  "token": os.environ.get("HEYGEN_TOKEN"),
17
  }
18
+
19
+ descript_tts=DescriptTTS()
20
  heyGentts = HeygenAPI(**data)
21
 
22
 
 
31
  print("hey gen here")
32
  return await heyGentts.tts_request(req)
33
 
34
+ @tts_router.post("/descript_tts")
35
+ async def generate_descript_voice(req: DescriptRequest):
36
+ return await descript_tts.overdub_text(**req.__dict__)
37
+
38
+
39
+ @tts_router.post("/descript_status")
40
+ async def status_descript(req: DescriptStatusRequest):
41
+ return await descript_tts.request_status(req.id)
42
+
43
+ @tts_router.post("/descript_sfx")
44
+ async def descript_sfx(req: DescriptSfxRequest):
45
+ return await descript_tts.search_sound_effects(req.query)
46
+
47
+ @tts_router.post("/descript_unsplash")
48
+ async def descript_unsplash(req: DescriptSfxRequest):
49
+ return await descript_tts.search_unsplash_images(req.query)
50
+
51
+
52
+
53
+ @tts_router.get("/descript_voices")
54
+ async def voices_descript():
55
+ return await descript_tts.get_voices()
56
 
57
  @tts_router.post("/status")
58
  async def search_id(req: StatusRequest):
App/TTS/utils/Descript.py ADDED
@@ -0,0 +1,373 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import aiohttp
2
+ import asyncio
3
+ import json, pprint, uuid, os, datetime
4
+ import tempfile, shutil
5
+ from typing import List, Optional
6
+ from datetime import datetime, timedelta
7
+ from pydantic import BaseModel, HttpUrl
8
+
9
+
10
+ class Metadata(BaseModel):
11
+ filename: str
12
+ type: str
13
+
14
+
15
+ class Artifact(BaseModel):
16
+ asset_id: str
17
+ created_at: datetime
18
+ file_extension: str
19
+ id: str
20
+ is_segmented: bool
21
+ lookup_key: HttpUrl
22
+ md5: str
23
+ metadata: Metadata
24
+ read_url: HttpUrl
25
+ size: int
26
+ status: str
27
+ uploaded_by: str
28
+
29
+
30
+ class TTSResponse(BaseModel):
31
+ artifacts: List[Artifact]
32
+ created_at: datetime
33
+ created_by: str
34
+ id: str
35
+ lookup_key: HttpUrl
36
+ metadata: Optional[dict]
37
+
38
+
39
+ class DescriptTTS:
40
+ def __init__(self, refresh_token=None):
41
+ self.client_id = "VDfu7rg4pdCELWsrQjcw2tG63a8Qlymi"
42
+ self.refresh_token_url = "https://auth0.descript.com/oauth/token"
43
+ self.project_id = "f734c6d7-e39d-4c1d-8f41-417f94cd37ce"
44
+ self.bearer_token = None
45
+ self.voice_ids = {
46
+ "Henry": "569fffb0-05a3-48a2-96a3-bf411c376477",
47
+ "Malcom": "75f8b86e-d05d-4862-a228-8d96fdf55258",
48
+ "Lawrance": "042460c0-98a5-41ae-9f31-33672ebb9016",
49
+ ## de
50
+ }
51
+
52
+ self.refresh_token = refresh_token
53
+ self.tau_id = "90f9e0ad-594e-4203-9297-d4c7cc691e5x"
54
+
55
+ async def login_and_get_bearer_token(self):
56
+ # Step 1: Use refresh token to get a new access token
57
+ new_bearer_token, new_refresh_token = await self.refresh_access_token()
58
+
59
+ # Step 2: Update the new refresh token to the Firebase Realtime Database
60
+ await self.update_refresh_token(new_refresh_token)
61
+
62
+ # Step 3: Set the new bearer token for further use
63
+ self.bearer_token = new_bearer_token
64
+ self.refresh_token = new_refresh_token
65
+
66
+ async def refresh_access_token(self):
67
+ # Load the existing refresh token from Firebase
68
+ if self.refresh_token == None:
69
+ await self.load_existing_refresh_token()
70
+
71
+ # Prepare the payload for token refresh
72
+ payload = {
73
+ "grant_type": "refresh_token",
74
+ "refresh_token": self.refresh_token,
75
+ "client_id": self.client_id,
76
+ }
77
+
78
+ # Request a new access token using the refresh token
79
+ async with aiohttp.ClientSession() as session:
80
+ async with session.post(self.refresh_token_url, data=payload) as response:
81
+ if response.status == 200:
82
+ # Parse the response to get the new access token and refresh token
83
+ response_data = await response.json()
84
+ new_bearer_token = response_data.get("access_token")
85
+ new_refresh_token = response_data.get("refresh_token")
86
+
87
+ return new_bearer_token, new_refresh_token
88
+ else:
89
+ raise Exception(
90
+ f"Failed to refresh access token. Status code: {response.status}, Error: {await response.text()}"
91
+ )
92
+
93
+ async def load_existing_refresh_token(self):
94
+ # Load the existing refresh token from Firebase
95
+ async with aiohttp.ClientSession() as session:
96
+ async with session.get(
97
+ "https://herokuserver-185316.firebaseio.com/refresh_token_descript.json"
98
+ ) as response:
99
+ if response.status == 200:
100
+ # Parse the response to get the existing refresh token
101
+ data = await response.json()
102
+ self.refresh_token = data.get("refresh_token")
103
+ else:
104
+ raise Exception(
105
+ f"Failed to load existing refresh token. Status code: {response.status}, Error: {await response.text()}"
106
+ )
107
+
108
+ async def download_and_store_file(self, access_url):
109
+ temp_dir = tempfile.mkdtemp()
110
+ # Generate a unique random filename
111
+ random_filename = str(uuid.uuid4()) + ".wav"
112
+ file_path = os.path.join(temp_dir, random_filename)
113
+
114
+ async with aiohttp.ClientSession() as session:
115
+ async with session.get(access_url) as response:
116
+ if response.status == 200:
117
+ with open(file_path, "wb") as file:
118
+ while True:
119
+ chunk = await response.content.read(1024)
120
+ if not chunk:
121
+ break
122
+ file.write(chunk)
123
+
124
+ # Schedule the file for deletion after 10 minutes
125
+ delete_time = datetime.now() + timedelta(minutes=10)
126
+
127
+ async def schedule_delete():
128
+ while datetime.now() < delete_time:
129
+ await asyncio.sleep(60) # Check every minute
130
+ shutil.rmtree(
131
+ temp_dir, ignore_errors=True
132
+ ) # Delete the temporary directory
133
+
134
+ asyncio.ensure_future(schedule_delete())
135
+
136
+ return file_path
137
+
138
+ async def search_unsplash_images(self, query_terms):
139
+ url = "https://api.descript.com/v2/cloud_libraries/providers/unsplash/image/search"
140
+ data = {
141
+ 'tracking_info': {'project_id': self.project_id},
142
+ 'pagination_info': {'page': 2, 'page_size': 25},
143
+ 'query': {'terms': query_terms}
144
+ }
145
+
146
+ try:
147
+ response = await self.make_authenticated_request(url, method="POST", data=data)
148
+ return response
149
+ except Exception as e:
150
+ print(f"Failed to search Unsplash images: {e}")
151
+ return None
152
+
153
+
154
+
155
+
156
+ async def search_sound_effects(self, query_terms):
157
+ url = "https://api.descript.com/v2/cloud_libraries/providers/stock-sfx/audio/search"
158
+ headers = {
159
+ 'accept': 'application/json, text/plain, */*',
160
+ 'accept-language': 'en-US,en;q=0.9',
161
+ 'content-type': 'application/json',
162
+
163
+ 'authorization': f'Bearer {self.bearer_token}', # Use the valid bearer token
164
+ }
165
+ data = {
166
+ 'tracking_info': {'project_id': self.project_id},
167
+ 'pagination_info': {'page': 1, 'page_size': 25},
168
+ 'query': {'terms': query_terms}
169
+ }
170
+
171
+ try:
172
+ response = await self.make_authenticated_request(url, method="POST", data=data)
173
+ return response
174
+ except Exception as e:
175
+ print(f"Failed to search sound effects: {e}")
176
+ return {'status':str(e)}
177
+
178
+
179
+ async def get_voices(self):
180
+ url = "https://api.descript.com/v2/users/me/voices"
181
+ try:
182
+ response = await self.make_authenticated_request(url)
183
+ return response
184
+ except Exception as e:
185
+ print(f"Failed to fetch voices: {e}")
186
+ return None
187
+
188
+
189
+ async def start_token_refresh_schedule(self):
190
+ while True:
191
+ try:
192
+ new_bearer_token, new_refresh_token = await self.refresh_access_token()
193
+ self.bearer_token = new_bearer_token
194
+ self.refresh_token = new_refresh_token
195
+
196
+ # Step 2: Update the new refresh token to the Firebase Realtime Database
197
+ await self.update_refresh_token(new_refresh_token)
198
+
199
+ print("Token refreshed successfully")
200
+ except Exception as e:
201
+ print(f"Failed to refresh token: {e}")
202
+
203
+ # Wait for 24 hours before the next refresh
204
+ await asyncio.sleep(24 * 60 * 60)
205
+
206
+
207
+ async def update_refresh_token(self, new_refresh_token):
208
+ # Update the new refresh token to Firebase
209
+ data = {"refresh_token": new_refresh_token}
210
+ async with aiohttp.ClientSession() as session:
211
+ async with session.put(
212
+ "https://herokuserver-185316.firebaseio.com/refresh_token_descript.json",
213
+ json=data,
214
+ ) as response:
215
+ if response.status != 200:
216
+ raise Exception(
217
+ f"Failed to update refresh token. Status code: {response.status}, Error: {await response.text()}"
218
+ )
219
+
220
+ async def make_authenticated_request(self, url, method="GET", data=None):
221
+ if not self.bearer_token:
222
+ await self.login_and_get_bearer_token() # Make sure we have a valid bearer token
223
+
224
+ headers = {
225
+ "authority": "api.descript.com",
226
+ "accept": "application/json, text/plain, */*",
227
+ "accept-language": "en-US,en;q=0.9",
228
+ "accept-version": "v1",
229
+ "authorization": f"Bearer {self.bearer_token}",
230
+ "cache-control": "no-cache",
231
+ "content-type": "application/json",
232
+ "origin": "https://web.descript.com",
233
+ "pragma": "no-cache",
234
+ "referer": "https://web.descript.com/",
235
+ "sec-ch-ua": '"Google Chrome";v="119", "Chromium";v="119", "Not?A_Brand";v="24"',
236
+ "sec-ch-ua-mobile": "?0",
237
+ "sec-ch-ua-platform": '"Windows"',
238
+ "sec-fetch-dest": "empty",
239
+ "sec-fetch-mode": "cors",
240
+ "sec-fetch-site": "same-site",
241
+ "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36",
242
+ "x-descript-app-build-number": "20231206.146",
243
+ "x-descript-app-build-type": "release",
244
+ "x-descript-app-id": "48db7358-5ebc-4866-b672-10b412ac39c1",
245
+ "x-descript-app-name": "web",
246
+ "x-descript-app-version": "78.2.4",
247
+ "x-descript-auth": "auth0",
248
+ }
249
+
250
+ async with aiohttp.ClientSession() as session:
251
+ async with session.request(
252
+ method, url, headers=headers, json=data
253
+ ) as response:
254
+ if response.status < 300:
255
+ return await response.json()
256
+ elif response.status == 401:
257
+ # Retry the request after refreshing the token
258
+ await self.login_and_get_bearer_token()
259
+ headers["authorization"] = f"Bearer {self.bearer_token}"
260
+ async with session.request(
261
+ method, url, headers=headers, json=data
262
+ ) as retry_response:
263
+ if retry_response.status == 200:
264
+ return await retry_response.json()
265
+ else:
266
+ raise Exception(
267
+ f"Request failed even after refreshing token. Status code: {retry_response.status}, Error: {await retry_response.text()}"
268
+ )
269
+ else:
270
+ raise Exception(
271
+ f"Request failed. Status code: {response.status}, Error: {await response.text()}"
272
+ )
273
+
274
+ async def get_assets(self):
275
+ url = "https://api.descript.com/v2/projects/f734c6d7-e39d-4c1d-8f41-417f94cd37ce/media_assets?include_artifacts=true&cursor=1702016922390&include_placeholder=true"
276
+ try:
277
+ result = await self.make_authenticated_request(url)
278
+ return result
279
+ except Exception as e:
280
+ print(f"Failed to get assets: {str(e)}")
281
+
282
+ async def overdub_text(self, text, speaker="Lawrance",_voice_id=None):
283
+ url = "https://api.descript.com/v2/projects/f734c6d7-e39d-4c1d-8f41-417f94cd37ce/overdub"
284
+ voice_id = _voice_id or self.voice_ids[speaker]
285
+ data = {
286
+ "text": text,
287
+ "voice_id": voice_id,
288
+ "concatenate_audio": True,
289
+ "tau_id": self.tau_id,
290
+ "allow_prefix_expansion": True,
291
+ "allow_suffix_expansion": True,
292
+ }
293
+
294
+ try:
295
+ result = await self.make_authenticated_request(
296
+ url, method="POST", data=data
297
+ )
298
+ return result
299
+ except Exception as e:
300
+ # Retry the request after refreshing the token if the failure is due to authorization
301
+ if "authorization" in str(e).lower():
302
+ await self.login_and_get_bearer_token()
303
+ result = await self.make_authenticated_request(
304
+ url, method="POST", data=data
305
+ )
306
+ print(result)
307
+ return result
308
+ else:
309
+ print(f"Failed to perform overdub: {str(e)}")
310
+
311
+ async def overdub_staus(self, id):
312
+ url = f"https://api.descript.com/v2/projects/f734c6d7-e39d-4c1d-8f41-417f94cd37ce/overdub/{id}"
313
+
314
+ try:
315
+ result = await self.make_authenticated_request(url, method="GET")
316
+ print(result)
317
+ return result
318
+ except Exception as e:
319
+ # Retry the request after refreshing the token if the failure is due to authorization
320
+ if "authorization" in str(e).lower():
321
+ await self.login_and_get_bearer_token()
322
+ result = await self.make_authenticated_request(
323
+ url, method="POST", data=data
324
+ )
325
+ print(result)
326
+ return result
327
+ else:
328
+ print(f"Failed to perform overdub: {str(e)}")
329
+
330
+ async def request_status(self, id):
331
+ status = await self.overdub_staus(id)
332
+ if status["state"] == "done":
333
+ asset_id=status["result"]["imputation_audio_asset_id"]
334
+ overdub = await self.get_assets()
335
+ for asset in overdub["data"]:
336
+ if asset["id"] == asset_id:
337
+ data = TTSResponse(**asset)
338
+ url = data.artifacts[0].read_url
339
+ return {'url':url,'status':'done'}
340
+ return status
341
+
342
+
343
+
344
+ async def say(self, text, speaker="Henry"):
345
+ overdub = await self.overdub_text(text, speaker=speaker)
346
+
347
+ asset_id = None
348
+ while True:
349
+ status = await self.overdub_staus(overdub["id"])
350
+ # print(status)
351
+ if status["state"] == "done":
352
+ # print(status)
353
+ asset_id = status["result"]["imputation_audio_asset_id"]
354
+ break
355
+ await asyncio.sleep(3)
356
+
357
+ overdub = await self.get_assets()
358
+ for asset in overdub["data"]:
359
+ if asset["id"] == asset_id:
360
+ data = TTSResponse(**asset)
361
+ url = data.artifacts[0].read_url
362
+ print(url)
363
+ path = await self.download_and_store_file(str(url))
364
+ return path, url
365
+
366
+
367
+ # async def example_usage():
368
+ # descript_tts = DescriptTTS()
369
+ # r=await descript_tts.say('Watch the world burn')
370
+ # print(r)
371
+
372
+ # # Run the example usage asynchronously
373
+ # asyncio.run(example_usage())
App/app.py CHANGED
@@ -3,7 +3,7 @@ from fastapi import FastAPI
3
  from fastapi.middleware.gzip import GZipMiddleware
4
 
5
  from .TTS.TTSRoutes import tts_router
6
-
7
  from .Embedding.EmbeddingRoutes import embeddigs_router
8
  from .Chat.PoeChatrouter import chat_router
9
 
@@ -11,7 +11,7 @@ from fastapi.middleware.cors import CORSMiddleware
11
 
12
  from fastapi_cache import FastAPICache
13
  from fastapi_cache.backends.inmemory import InMemoryBackend
14
-
15
  import logging
16
 
17
 
@@ -39,6 +39,7 @@ app.add_middleware(GZipMiddleware, minimum_size=1000)
39
  @app.on_event("startup")
40
  async def startup():
41
  FastAPICache.init(InMemoryBackend())
 
42
 
43
 
44
  @app.get("/")
 
3
  from fastapi.middleware.gzip import GZipMiddleware
4
 
5
  from .TTS.TTSRoutes import tts_router
6
+ from .TTS.utils.Descript import DescriptTTS
7
  from .Embedding.EmbeddingRoutes import embeddigs_router
8
  from .Chat.PoeChatrouter import chat_router
9
 
 
11
 
12
  from fastapi_cache import FastAPICache
13
  from fastapi_cache.backends.inmemory import InMemoryBackend
14
+ tts_object=DescriptTTS()
15
  import logging
16
 
17
 
 
39
  @app.on_event("startup")
40
  async def startup():
41
  FastAPICache.init(InMemoryBackend())
42
+ await tts_object.start_token_refresh_schedule()
43
 
44
 
45
  @app.get("/")