arifardev commited on
Commit
3e26c2a
·
verified ·
1 Parent(s): 41d1149

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +106 -34
app.py CHANGED
@@ -14,15 +14,14 @@ AUDIO_DIR = "/tmp/audio_output"
14
  os.makedirs(TEMP_DIR, exist_ok=True)
15
  os.makedirs(AUDIO_DIR, exist_ok=True)
16
 
17
- # Paksa semua unduhan model masuk ke /tmp
18
  os.environ["TTS_HOME"] = TEMP_DIR
19
-
20
- # Persetujuan Lisensi Otomatis
21
  os.environ["COQUI_TOS_AGREED"] = "1"
22
 
23
  app = FastAPI(
24
- title="PasBlast XTTS-v2 API",
25
- description="API Interaktif menggunakan model Coqui XTTS-v2 yang di-host di Hugging Face."
 
26
  )
27
  security = HTTPBasic()
28
 
@@ -34,7 +33,7 @@ def get_xtts_instance() -> TTS:
34
  try:
35
  print("Memuat model XTTS-v2 ke RAM...")
36
  tts_model = TTS(model_name="tts_models/multilingual/multi-dataset/xtts_v2", gpu=False)
37
- print("Model berhasil dimuat ke memori!")
38
  except Exception as e:
39
  error_trace = traceback.format_exc()
40
  print(f"Error Load Model: {error_trace}")
@@ -55,58 +54,96 @@ def verify_auth(credentials: HTTPBasicCredentials = Depends(security)):
55
 
56
  # --- ENDPOINTS ---
57
 
58
- @app.get("/")
59
  def root():
60
- return {"message": "XTTS-v2 API Aktif. Akses /docs untuk mencoba secara interaktif."}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
 
62
- @app.get("/speakers", tags=["Info"])
63
  def list_speakers(username: str = Depends(verify_auth)):
64
- """Melihat daftar karakter suara bawaan XTTS-v2."""
 
 
 
 
 
 
65
  tts = get_xtts_instance()
66
- return {"speakers": tts.speakers}
 
 
67
 
68
- @app.post("/tts", tags=["Generation"])
69
  def generate_tts(
70
- text: str = Form(..., description="Teks Syarat & Ketentuan PasBlast"),
71
- speaker: str = Form("Ana Florence", description="Pilih nama speaker"),
72
- language: str = Form("id", description="Gunakan 'id' untuk Bahasa Indonesia"),
 
 
 
73
  username: str = Depends(verify_auth)
74
  ):
75
- """Sintesis teks dengan proteksi panjang karakter dan validasi speaker."""
76
  tts = get_xtts_instance()
77
  output_path = os.path.join(AUDIO_DIR, f"tts_{uuid.uuid4().hex}.wav")
78
 
79
- # 1. Validasi Speaker (Pencegah Error 500)
80
  available_speakers = tts.speakers
81
  if speaker not in available_speakers:
82
- print(f"Peringatan: Speaker '{speaker}' tidak ada. Menggunakan default: '{available_speakers[0]}'")
83
  speaker = available_speakers[0]
84
 
85
  try:
86
- print(f"Memulai sintesis suara untuk teks ({len(text)} karakter)...")
87
- # 2. split_sentences=True wajib aktif untuk mencegah crash pada teks panjang
88
  tts.tts_to_file(
89
  text=text,
90
  speaker=speaker,
91
  language=language,
92
  file_path=output_path,
93
- split_sentences=True
 
 
 
94
  )
95
- print("Sintesis berhasil, mengirim file audio ke klien.")
96
- return FileResponse(output_path, media_type="audio/wav", filename="pasblast_normal.wav")
97
  except Exception as e:
98
  error_trace = traceback.format_exc()
99
- print(f"FATAL ERROR saat sintesis: {error_trace}")
100
- raise HTTPException(status_code=500, detail=f"Gagal memproses TTS: {str(e)}\nTrace: {error_trace}")
101
 
102
- @app.post("/tts_voice_clone", tags=["Generation"])
103
  def generate_tts_voice_clone(
104
- text: str = Form(...),
105
- language: str = Form("id"),
106
- reference_audio: UploadFile = File(..., description="File WAV suara target (5-10 detik)"),
 
107
  username: str = Depends(verify_auth)
108
  ):
109
- """Sintesis teks dengan meniru (cloning) suara dari file audio unggahan."""
110
  tts = get_xtts_instance()
111
 
112
  ref_path = os.path.join(AUDIO_DIR, f"ref_{uuid.uuid4().hex}.wav")
@@ -116,16 +153,51 @@ def generate_tts_voice_clone(
116
  output_path = os.path.join(AUDIO_DIR, f"clone_{uuid.uuid4().hex}.wav")
117
 
118
  try:
119
- print("Memulai proses kloning suara...")
120
  tts.tts_to_file(
121
  text=text,
122
  language=language,
123
  speaker_wav=ref_path,
124
  file_path=output_path,
125
- split_sentences=True
 
126
  )
127
  return FileResponse(output_path, media_type="audio/wav", filename="pasblast_cloned.wav")
128
  except Exception as e:
129
  error_trace = traceback.format_exc()
130
- print(f"FATAL ERROR saat kloning: {error_trace}")
131
- raise HTTPException(status_code=500, detail=f"Gagal melakukan Voice Cloning: {str(e)}\nTrace: {error_trace}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  os.makedirs(TEMP_DIR, exist_ok=True)
15
  os.makedirs(AUDIO_DIR, exist_ok=True)
16
 
17
+ # Paksa semua unduhan model masuk ke /tmp agar sesuai dengan penyimpanan ephemeral
18
  os.environ["TTS_HOME"] = TEMP_DIR
 
 
19
  os.environ["COQUI_TOS_AGREED"] = "1"
20
 
21
  app = FastAPI(
22
+ title="PasBlast Comprehensive Coqui XTTS-v2 API",
23
+ description="API Komplet untuk seluruh kapabilitas Coqui XTTS-v2 yang dimungkinkan berjalan di CPU Space.",
24
+ version="1.0.0"
25
  )
26
  security = HTTPBasic()
27
 
 
33
  try:
34
  print("Memuat model XTTS-v2 ke RAM...")
35
  tts_model = TTS(model_name="tts_models/multilingual/multi-dataset/xtts_v2", gpu=False)
36
+ print("Model berhasil dimuat!")
37
  except Exception as e:
38
  error_trace = traceback.format_exc()
39
  print(f"Error Load Model: {error_trace}")
 
54
 
55
  # --- ENDPOINTS ---
56
 
57
+ @app.get("/", tags=["Status"])
58
  def root():
59
+ return {
60
+ "status": "online",
61
+ "message": "XTTS-v2 API Komplet Aktif. Akses /docs untuk Swagger UI Interaktif.",
62
+ "storage_info": {
63
+ "temp_dir": TEMP_DIR,
64
+ "audio_dir": AUDIO_DIR
65
+ }
66
+ }
67
+
68
+ @app.get("/models", tags=["Metadata & Info"])
69
+ def list_models(username: str = Depends(verify_auth)):
70
+ """Melihat daftar seluruh model yang terdaftar secara internal di library Coqui TTS."""
71
+ try:
72
+ all_models = TTS.list_models()
73
+ return {"total_available_models": len(all_models), "models": all_models}
74
+ except Exception as e:
75
+ raise HTTPException(status_code=500, detail=f"Gagal mengambil list model: {str(e)}")
76
+
77
+ @app.get("/current_model", tags=["Metadata & Info"])
78
+ def get_current_model_status(username: str = Depends(verify_auth)):
79
+ """Mengecek status dan informasi model yang saat ini sedang aktif di memori."""
80
+ global tts_model
81
+ return {
82
+ "loaded_in_memory": tts_model is not None,
83
+ "active_model_name": "tts_models/multilingual/multi-dataset/xtts_v2" if tts_model else None,
84
+ "device": "cpu"
85
+ }
86
 
87
+ @app.get("/speakers", tags=["Metadata & Info"])
88
  def list_speakers(username: str = Depends(verify_auth)):
89
+ """Melihat daftar seluruh karakter suara (speaker) bawaan yang didukung oleh XTTS-v2."""
90
+ tts = get_xtts_instance()
91
+ return {"total_speakers": len(tts.speakers), "speakers": tts.speakers}
92
+
93
+ @app.get("/languages", tags=["Metadata & Info"])
94
+ def list_languages(username: str = Depends(verify_auth)):
95
+ """Melihat daftar kode bahasa (multilingual) yang didukung resmi oleh XTTS-v2."""
96
  tts = get_xtts_instance()
97
+ # XTTS-v2 mendukung bahasa berkode resmi seperti 'id', 'en', 'es', dll.
98
+ languages = tts.languages if hasattr(tts, "languages") else ["id", "en", "es", "fr", "de", "it", "pt", "pl", "tr", "ru", "nl", "cs", "ar", "zh-cn", "ja", "hu", "ko"]
99
+ return {"total_languages": len(languages), "languages": languages}
100
 
101
+ @app.post("/tts", tags=["Core Generation"])
102
  def generate_tts(
103
+ text: str = Form(..., description="Teks yang akan diubah menjadi suara"),
104
+ speaker: str = Form("Ana Florence", description="Nama karakter suara bawaan dari endpoint /speakers"),
105
+ language: str = Form("id", description="Kode bahasa (contoh: id, en, es)"),
106
+ temperature: float = Form(0.75, description="Kreativitas variasi suara (0.1 - 1.0)"),
107
+ length_penalty: float = Form(1.0, description="Penalti panjang kalimat"),
108
+ repetition_penalty: float = Form(5.0, description="Penalti pengulangan kata berlebih"),
109
  username: str = Depends(verify_auth)
110
  ):
111
+ """Sintesis teks dasar menggunakan salah satu dari karakter suara bawaan XTTS-v2."""
112
  tts = get_xtts_instance()
113
  output_path = os.path.join(AUDIO_DIR, f"tts_{uuid.uuid4().hex}.wav")
114
 
 
115
  available_speakers = tts.speakers
116
  if speaker not in available_speakers:
117
+ print(f"Speaker '{speaker}' tidak ditemukan. Menggunakan default: '{available_speakers[0]}'")
118
  speaker = available_speakers[0]
119
 
120
  try:
121
+ print(f"Memproses TTS Dasar untuk {len(text)} karakter teks...")
 
122
  tts.tts_to_file(
123
  text=text,
124
  speaker=speaker,
125
  language=language,
126
  file_path=output_path,
127
+ split_sentences=True,
128
+ temperature=temperature,
129
+ length_penalty=length_penalty,
130
+ repetition_penalty=repetition_penalty
131
  )
132
+ return FileResponse(output_path, media_type="audio/wav", filename="pasblast_tts.wav")
 
133
  except Exception as e:
134
  error_trace = traceback.format_exc()
135
+ print(f"FATAL ERROR pada /tts: {error_trace}")
136
+ raise HTTPException(status_code=500, detail=f"Gagal memproses TTS: {str(e)}")
137
 
138
+ @app.post("/tts_voice_clone", tags=["Core Generation"])
139
  def generate_tts_voice_clone(
140
+ text: str = Form(..., description="Teks yang ingin disuarakan oleh hasil kloning"),
141
+ language: str = Form("id", description="Kode bahasa target suara"),
142
+ reference_audio: UploadFile = File(..., description="File WAV sampel suara target (durasi ideal 5-10 detik, bersih dari noise)"),
143
+ temperature: float = Form(0.75, description="Kreativitas variasi suara"),
144
  username: str = Depends(verify_auth)
145
  ):
146
+ """Zero-shot Voice Cloning: Membuat teks berbunyi persis menyerupai file audio referensi yang diunggah."""
147
  tts = get_xtts_instance()
148
 
149
  ref_path = os.path.join(AUDIO_DIR, f"ref_{uuid.uuid4().hex}.wav")
 
153
  output_path = os.path.join(AUDIO_DIR, f"clone_{uuid.uuid4().hex}.wav")
154
 
155
  try:
156
+ print("Memproses Zero-shot Voice Cloning...")
157
  tts.tts_to_file(
158
  text=text,
159
  language=language,
160
  speaker_wav=ref_path,
161
  file_path=output_path,
162
+ split_sentences=True,
163
+ temperature=temperature
164
  )
165
  return FileResponse(output_path, media_type="audio/wav", filename="pasblast_cloned.wav")
166
  except Exception as e:
167
  error_trace = traceback.format_exc()
168
+ print(f"FATAL ERROR pada /tts_voice_clone: {error_trace}")
169
+ raise HTTPException(status_code=500, detail=f"Gagal memproses Voice Cloning: {str(e)}")
170
+
171
+ @app.post("/voice_conversion", tags=["Core Generation"])
172
+ def generate_voice_conversion(
173
+ source_audio: UploadFile = File(..., description="File audio WAV asli berisi ucapan/perkataan seseorang yang ingin diubah suaranya"),
174
+ reference_audio: UploadFile = File(..., description="File audio WAV target berisi sampel karakter suara baru yang ingin ditiru"),
175
+ username: str = Depends(verify_auth)
176
+ ):
177
+ """Voice Conversion (Audio-to-Audio): Mengubah identitas suara pada file audio sumber menjadi karakter suara target tanpa mengubah isi perkataannya."""
178
+ tts = get_xtts_instance()
179
+
180
+ source_path = os.path.join(AUDIO_DIR, f"src_{uuid.uuid4().hex}.wav")
181
+ ref_path = os.path.join(AUDIO_DIR, f"ref_vc_{uuid.uuid4().hex}.wav")
182
+ output_path = os.path.join(AUDIO_DIR, f"vc_{uuid.uuid4().hex}.wav")
183
+
184
+ with open(source_path, "wb") as buffer:
185
+ shutil.copyfileobj(source_audio.file, buffer)
186
+ with open(ref_path, "wb") as buffer:
187
+ shutil.copyfileobj(reference_audio.file, buffer)
188
+
189
+ try:
190
+ if hasattr(tts, "voice_conversion_to_file"):
191
+ print("Memproses Voice Conversion (Audio-to-Audio)...")
192
+ tts.voice_conversion_to_file(
193
+ source_wav=source_path,
194
+ target_wav=ref_path,
195
+ file_path=output_path
196
+ )
197
+ return FileResponse(output_path, media_type="audio/wav", filename="pasblast_converted.wav")
198
+ else:
199
+ raise HTTPException(status_code=400, detail="Model aktif saat ini tidak dikonfigurasi untuk fungsi Voice Conversion bawaan.")
200
+ except Exception as e:
201
+ error_trace = traceback.format_exc()
202
+ print(f"FATAL ERROR pada /voice_conversion: {error_trace}")
203
+ raise HTTPException(status_code=500, detail=f"Gagal memproses Voice Conversion: {str(e)}")