character ai
Browse files- App/TTS/Schemas.py +6 -0
- App/TTS/TTSRoutes.py +17 -9
- App/TTS/utils/CharacterAi.py +162 -0
- Dockerfile +1 -2
- requirements.txt +1 -1
App/TTS/Schemas.py
CHANGED
@@ -30,6 +30,12 @@ class DescriptTranscript(BaseModel):
|
|
30 |
file_extenstion: str = ".wav"
|
31 |
|
32 |
|
|
|
|
|
|
|
|
|
|
|
|
|
33 |
class PiTTSRequest(BaseModel):
|
34 |
text: str
|
35 |
voice: Optional[str]
|
|
|
30 |
file_extenstion: str = ".wav"
|
31 |
|
32 |
|
33 |
+
# Define the request model
|
34 |
+
class CharacterTTSRequest(BaseModel):
|
35 |
+
text: str
|
36 |
+
voice: Optional[str] = None
|
37 |
+
|
38 |
+
|
39 |
class PiTTSRequest(BaseModel):
|
40 |
text: str
|
41 |
voice: Optional[str]
|
App/TTS/TTSRoutes.py
CHANGED
@@ -10,16 +10,18 @@ from .Schemas import (
|
|
10 |
DescriptSfxRequest,
|
11 |
DescriptTranscript,
|
12 |
PiTTSRequest,
|
|
|
13 |
)
|
14 |
from .utils.Podcastle import PodcastleAPI
|
15 |
from .utils.HeyGen import HeygenAPI
|
16 |
-
|
17 |
from .utils.Descript import DescriptTTS
|
18 |
import os
|
19 |
import asyncio
|
20 |
-
from .utils.Pi import PiAIClient
|
21 |
|
22 |
-
from
|
|
|
|
|
23 |
from fastapi.responses import StreamingResponse, FileResponse
|
24 |
import os
|
25 |
import aiofiles
|
@@ -35,7 +37,7 @@ data = {
|
|
35 |
|
36 |
descript_tts = DescriptTTS()
|
37 |
heyGentts = HeygenAPI(**data)
|
38 |
-
pi = PiAIClient(headless=True)
|
39 |
|
40 |
|
41 |
@tts_router.post("/generate_tts")
|
@@ -97,11 +99,17 @@ async def search_id(req: StatusRequest):
|
|
97 |
return await tts.check_status(req)
|
98 |
|
99 |
|
100 |
-
@tts_router.post("/
|
101 |
-
async def
|
102 |
-
|
103 |
-
|
104 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
105 |
|
106 |
|
107 |
@tts_router.get("/audio/{audio_name}")
|
|
|
10 |
DescriptSfxRequest,
|
11 |
DescriptTranscript,
|
12 |
PiTTSRequest,
|
13 |
+
CharacterTTSRequest,
|
14 |
)
|
15 |
from .utils.Podcastle import PodcastleAPI
|
16 |
from .utils.HeyGen import HeygenAPI
|
17 |
+
from .utils.CharacterAi import CharacterTTS
|
18 |
from .utils.Descript import DescriptTTS
|
19 |
import os
|
20 |
import asyncio
|
|
|
21 |
|
22 |
+
# from .utils.Pi import PiAIClient
|
23 |
+
|
24 |
+
from fastapi import Request, HTTPException
|
25 |
from fastapi.responses import StreamingResponse, FileResponse
|
26 |
import os
|
27 |
import aiofiles
|
|
|
37 |
|
38 |
descript_tts = DescriptTTS()
|
39 |
heyGentts = HeygenAPI(**data)
|
40 |
+
# pi = PiAIClient(headless=True)
|
41 |
|
42 |
|
43 |
@tts_router.post("/generate_tts")
|
|
|
99 |
return await tts.check_status(req)
|
100 |
|
101 |
|
102 |
+
@tts_router.post("/cai_tts")
|
103 |
+
async def cai_tts(req: CharacterTTSRequest):
|
104 |
+
cai = CharacterTTS()
|
105 |
+
return await cai.say(req)
|
106 |
+
|
107 |
+
|
108 |
+
# @tts_router.post("/pi_tts")
|
109 |
+
# async def pi_tts(req: PiTTSRequest):
|
110 |
+
# if not pi.initialized:
|
111 |
+
# await pi.setup()
|
112 |
+
# return await pi.say(req.text, voice=req.voice)
|
113 |
|
114 |
|
115 |
@tts_router.get("/audio/{audio_name}")
|
App/TTS/utils/CharacterAi.py
ADDED
@@ -0,0 +1,162 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import asyncio
|
2 |
+
import os
|
3 |
+
from characterai import aiocai
|
4 |
+
from typing import Optional
|
5 |
+
from pydantic import BaseModel
|
6 |
+
import aiohttp
|
7 |
+
|
8 |
+
from App.TTS.Schemas import CharacterTTSRequest
|
9 |
+
|
10 |
+
|
11 |
+
# class CharacterTTSRequest(BaseModel):
|
12 |
+
# text: str
|
13 |
+
# voice: Optional[str] = None
|
14 |
+
|
15 |
+
|
16 |
+
# Voice ID selected by the user
|
17 |
+
|
18 |
+
|
19 |
+
class CharacterTTS:
|
20 |
+
TTS_ENDPOINT = "https://neo.character.ai/multimodal/api/v1/memo/replay"
|
21 |
+
|
22 |
+
def __init__(self):
|
23 |
+
"""
|
24 |
+
Initialize the CharacterTTS instance.
|
25 |
+
|
26 |
+
Args:
|
27 |
+
token (str): The authorization token for Character AI.
|
28 |
+
"""
|
29 |
+
self.token = "110626c85c57978218fa0066f58da72807e179d2"
|
30 |
+
self.client = aiocai.Client(self.token)
|
31 |
+
|
32 |
+
async def say(
|
33 |
+
self,
|
34 |
+
request: CharacterTTSRequest,
|
35 |
+
char_id: str = "nrXc-uQ5vtunnBTqKxB39vwNyJg2TARqD9r2wHVRO4U",
|
36 |
+
) -> str:
|
37 |
+
"""
|
38 |
+
Send a message to the character AI and generate TTS.
|
39 |
+
|
40 |
+
Args:
|
41 |
+
char_id (str): The character ID to chat with.
|
42 |
+
request (CharacterTTSRequest): The TTS request containing text and optional voice ID.
|
43 |
+
|
44 |
+
Returns:
|
45 |
+
str: The replay URL for the generated TTS audio.
|
46 |
+
"""
|
47 |
+
# Connect to the AI client and start a new chat
|
48 |
+
me = await self.client.get_me()
|
49 |
+
chat = await self.client.connect()
|
50 |
+
async with chat as chat_session:
|
51 |
+
new_chat, answer = await chat_session.new_chat(char_id, me.id)
|
52 |
+
chat_id = new_chat.chat_id
|
53 |
+
|
54 |
+
# Send the user's message
|
55 |
+
message = await chat_session.send_message(
|
56 |
+
char=char_id, chat_id=chat_id, text=request.text
|
57 |
+
)
|
58 |
+
|
59 |
+
# Extract necessary identifiers
|
60 |
+
room_id = chat_id
|
61 |
+
turn_id = message.turn_key.turn_id
|
62 |
+
primary_candidate = next(
|
63 |
+
(
|
64 |
+
c
|
65 |
+
for c in message.candidates
|
66 |
+
if c.candidate_id == message.primary_candidate_id
|
67 |
+
),
|
68 |
+
None,
|
69 |
+
)
|
70 |
+
|
71 |
+
if not primary_candidate:
|
72 |
+
raise Exception("Primary candidate not found in the message response.")
|
73 |
+
|
74 |
+
candidate_id = primary_candidate.candidate_id
|
75 |
+
voice_id = request.voice if request.voice else self.default_voice()
|
76 |
+
|
77 |
+
# Generate TTS and get the replay URL
|
78 |
+
replay_url = await self.generate_tts(
|
79 |
+
room_id, turn_id, candidate_id, voice_id
|
80 |
+
)
|
81 |
+
|
82 |
+
return replay_url
|
83 |
+
|
84 |
+
def default_voice(self) -> str:
|
85 |
+
"""
|
86 |
+
Return a default voice ID if none is provided.
|
87 |
+
|
88 |
+
Returns:
|
89 |
+
str: The default voice ID.
|
90 |
+
"""
|
91 |
+
return (
|
92 |
+
"0ef6c2d5-14e1-420c-a634-693c3f13fade" # Replace with your default voice ID
|
93 |
+
)
|
94 |
+
|
95 |
+
async def generate_tts(
|
96 |
+
self, room_id: str, turn_id: str, candidate_id: str, voice_id: str
|
97 |
+
) -> str:
|
98 |
+
"""
|
99 |
+
Generate TTS by making a POST request to the TTS endpoint.
|
100 |
+
|
101 |
+
Args:
|
102 |
+
room_id (str): The chat room ID.
|
103 |
+
turn_id (str): The turn ID of the message.
|
104 |
+
candidate_id (str): The candidate ID of the message.
|
105 |
+
voice_id (str): The selected voice ID.
|
106 |
+
|
107 |
+
Returns:
|
108 |
+
str: The replay URL for the generated TTS audio.
|
109 |
+
"""
|
110 |
+
payload = {
|
111 |
+
"roomId": room_id,
|
112 |
+
"turnId": turn_id,
|
113 |
+
"candidateId": candidate_id,
|
114 |
+
"voiceId": voice_id,
|
115 |
+
}
|
116 |
+
|
117 |
+
headers = {
|
118 |
+
"accept": "application/json, text/plain, */*",
|
119 |
+
"accept-language": "en-US,en;q=0.9",
|
120 |
+
"authorization": f"Token {self.token}",
|
121 |
+
"content-type": "application/json",
|
122 |
+
"origin": "https://character.ai",
|
123 |
+
"priority": "u=1, i",
|
124 |
+
"referer": "https://character.ai/",
|
125 |
+
"sec-ch-ua": '"Google Chrome";v="129", "Not=A?Brand";v="8", "Chromium";v="129"',
|
126 |
+
"sec-ch-ua-mobile": "?0",
|
127 |
+
"sec-ch-ua-platform": '"Windows"',
|
128 |
+
"sec-fetch-dest": "empty",
|
129 |
+
"sec-fetch-mode": "cors",
|
130 |
+
"sec-fetch-site": "same-site",
|
131 |
+
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
|
132 |
+
"AppleWebKit/537.36 (KHTML, like Gecko) "
|
133 |
+
"Chrome/129.0.0.0 Safari/537.36",
|
134 |
+
}
|
135 |
+
|
136 |
+
async with aiohttp.ClientSession() as session:
|
137 |
+
async with session.post(
|
138 |
+
self.TTS_ENDPOINT, json=payload, headers=headers
|
139 |
+
) as response:
|
140 |
+
if response.status == 200:
|
141 |
+
data = await response.json()
|
142 |
+
replay_url = data.get("replayUrl")
|
143 |
+
if replay_url:
|
144 |
+
print(f"TTS generated successfully. Replay URL: {replay_url}")
|
145 |
+
return replay_url
|
146 |
+
else:
|
147 |
+
raise Exception("Replay URL not found in the response.")
|
148 |
+
else:
|
149 |
+
error = await response.text()
|
150 |
+
raise Exception(
|
151 |
+
f"TTS generation failed with status {response.status}: {error}"
|
152 |
+
)
|
153 |
+
|
154 |
+
|
155 |
+
# async def main():
|
156 |
+
# cai = CharacterTTS()
|
157 |
+
# x = await cai.say(CharacterTTSRequest(text="Hello, how are you?"))
|
158 |
+
# print(x)
|
159 |
+
|
160 |
+
|
161 |
+
# if __name__ == "__main__":
|
162 |
+
# asyncio.run(main())
|
Dockerfile
CHANGED
@@ -28,7 +28,6 @@ RUN apt-get update && \
|
|
28 |
#copy requirements
|
29 |
COPY requirements.txt .
|
30 |
RUN pip install --no-cache-dir -r requirements.txt
|
31 |
-
RUN playwright install-deps
|
32 |
|
33 |
|
34 |
# Copy the application code
|
@@ -37,6 +36,6 @@ USER admin
|
|
37 |
COPY --chown=admin . /srv
|
38 |
|
39 |
# Command to run the application
|
40 |
-
CMD
|
41 |
# Expose the server port
|
42 |
EXPOSE 7860
|
|
|
28 |
#copy requirements
|
29 |
COPY requirements.txt .
|
30 |
RUN pip install --no-cache-dir -r requirements.txt
|
|
|
31 |
|
32 |
|
33 |
# Copy the application code
|
|
|
36 |
COPY --chown=admin . /srv
|
37 |
|
38 |
# Command to run the application
|
39 |
+
CMD uvicorn App.app:app --host 0.0.0.0 --port 7860 --workers 1
|
40 |
# Expose the server port
|
41 |
EXPOSE 7860
|
requirements.txt
CHANGED
@@ -30,7 +30,7 @@ glfw
|
|
30 |
Pillow
|
31 |
numpy
|
32 |
broken-source
|
33 |
-
|
34 |
|
35 |
# git+https://github.com/snowby666/poe-api-wrapper.git@2c91207598ad930901cb9fb09734705056b7b6a9
|
36 |
# git+https://github.com/Mbonea-Mjema/ballyregan.git
|
|
|
30 |
Pillow
|
31 |
numpy
|
32 |
broken-source
|
33 |
+
git+https://github.com/Xtr4F/PyCharacterAI.git
|
34 |
|
35 |
# git+https://github.com/snowby666/poe-api-wrapper.git@2c91207598ad930901cb9fb09734705056b7b6a9
|
36 |
# git+https://github.com/Mbonea-Mjema/ballyregan.git
|