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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +85 -84
app.py CHANGED
@@ -19,26 +19,27 @@ 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
 
28
- tts_model = None
 
29
 
30
- def get_xtts_instance() -> TTS:
31
- global tts_model
32
- if tts_model is None:
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}")
40
- raise HTTPException(status_code=500, detail=f"Gagal memuat model XTTS-v2: {str(e)}")
41
- return tts_model
42
 
43
  # --- OTENTIKASI KEAMANAN ---
44
  def verify_auth(credentials: HTTPBasicCredentials = Depends(security)):
@@ -58,7 +59,7 @@ def verify_auth(credentials: HTTPBasicCredentials = Depends(security)):
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
@@ -74,62 +75,75 @@ def list_models(username: str = Depends(verify_auth)):
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}")
@@ -137,15 +151,15 @@ def generate_tts(
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")
150
  with open(ref_path, "wb") as buffer:
151
  shutil.copyfileobj(reference_audio.file, buffer)
@@ -153,51 +167,38 @@ def generate_tts_voice_clone(
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)}")
 
19
  os.environ["COQUI_TOS_AGREED"] = "1"
20
 
21
  app = FastAPI(
22
+ title="PasBlast Dynamic Coqui API",
23
+ description="API Komplet dengan Dynamic Model Loading. Memanfaatkan 16GB RAM untuk menjalankan berbagai model secara leluasa.",
24
+ version="2.0.0"
25
  )
26
  security = HTTPBasic()
27
 
28
+ # Dictionary untuk menampung beberapa model sekaligus di dalam 16GB RAM
29
+ active_models = {}
30
 
31
+ def get_tts_instance(model_name: str) -> TTS:
32
+ if model_name not in active_models:
 
33
  try:
34
+ print(f"Mencoba memuat model '{model_name}' ke RAM...")
35
+ # Muat model baru dan simpan ke dictionary
36
+ active_models[model_name] = TTS(model_name=model_name, gpu=False)
37
+ print(f"Model '{model_name}' berhasil dimuat ke memori!")
38
  except Exception as e:
39
  error_trace = traceback.format_exc()
40
+ print(f"Error Load Model {model_name}: {error_trace}")
41
+ raise HTTPException(status_code=500, detail=f"Gagal memuat model {model_name}: {str(e)}")
42
+ return active_models[model_name]
43
 
44
  # --- OTENTIKASI KEAMANAN ---
45
  def verify_auth(credentials: HTTPBasicCredentials = Depends(security)):
 
59
  def root():
60
  return {
61
  "status": "online",
62
+ "message": "Dynamic TTS API Aktif. Menggunakan 16GB RAM Space.",
63
  "storage_info": {
64
  "temp_dir": TEMP_DIR,
65
  "audio_dir": AUDIO_DIR
 
75
  except Exception as e:
76
  raise HTTPException(status_code=500, detail=f"Gagal mengambil list model: {str(e)}")
77
 
78
+ @app.get("/current_models", tags=["Metadata & Info"])
79
  def get_current_model_status(username: str = Depends(verify_auth)):
80
+ """Mengecek model apa saja yang saat ini SEDANG AKTIF dan bersarang di RAM 16GB Anda."""
 
81
  return {
82
+ "total_loaded_models": len(active_models),
83
+ "loaded_models_list": list(active_models.keys()),
84
  "device": "cpu"
85
  }
86
 
87
  @app.get("/speakers", tags=["Metadata & Info"])
88
+ def list_speakers(
89
+ model_name: str = "tts_models/multilingual/multi-dataset/xtts_v2",
90
+ username: str = Depends(verify_auth)
91
+ ):
92
+ """Melihat daftar seluruh karakter suara bawaan dari model TERTENTU."""
93
+ tts = get_tts_instance(model_name)
94
+ speakers = tts.speakers if hasattr(tts, "speakers") and tts.speakers else []
95
+ return {"model": model_name, "total_speakers": len(speakers), "speakers": speakers}
96
 
97
  @app.get("/languages", tags=["Metadata & Info"])
98
+ def list_languages(
99
+ model_name: str = "tts_models/multilingual/multi-dataset/xtts_v2",
100
+ username: str = Depends(verify_auth)
101
+ ):
102
+ """Melihat daftar kode bahasa yang didukung oleh model TERTENTU."""
103
+ tts = get_tts_instance(model_name)
104
+ languages = tts.languages if hasattr(tts, "languages") and tts.languages else []
105
+ return {"model": model_name, "total_languages": len(languages), "languages": languages}
106
 
107
  @app.post("/tts", tags=["Core Generation"])
108
  def generate_tts(
109
  text: str = Form(..., description="Teks yang akan diubah menjadi suara"),
110
+ model_name: str = Form("tts_models/multilingual/multi-dataset/xtts_v2", description="Nama model dari endpoint /models"),
111
+ speaker: str = Form(None, description="Nama karakter suara (kosongkan jika model bukan multi-speaker)"),
112
+ language: str = Form(None, description="Kode bahasa (kosongkan jika model bukan multi-lingual)"),
113
  temperature: float = Form(0.75, description="Kreativitas variasi suara (0.1 - 1.0)"),
114
  length_penalty: float = Form(1.0, description="Penalti panjang kalimat"),
115
  repetition_penalty: float = Form(5.0, description="Penalti pengulangan kata berlebih"),
116
  username: str = Depends(verify_auth)
117
  ):
118
+ """Sintesis teks dasar menggunakan MODEL APAPUN yang Anda pilih secara dinamis."""
119
+ tts = get_tts_instance(model_name)
120
  output_path = os.path.join(AUDIO_DIR, f"tts_{uuid.uuid4().hex}.wav")
121
 
122
+ # Validasi dan auto-fallback speaker jika model membutuhkan speaker
123
+ if hasattr(tts, "speakers") and tts.speakers:
124
+ if speaker not in tts.speakers:
125
+ print(f"Speaker '{speaker}' tidak valid untuk model {model_name}. Menggunakan default.")
126
+ speaker = tts.speakers[0]
127
+
128
+ # Validasi language
129
+ if hasattr(tts, "languages") and tts.languages:
130
+ if language not in tts.languages:
131
+ language = tts.languages[0]
132
 
133
  try:
134
+ print(f"Memproses TTS menggunakan {model_name}...")
135
+
136
+ # Eksekusi berdasarkan apakah model XTTS atau VITS/FastPitch biasa
137
+ kwargs = {"text": text, "file_path": output_path}
138
+ if speaker: kwargs["speaker"] = speaker
139
+ if language: kwargs["language"] = language
140
+
141
+ # XTTS mendukung parameter tuning tambahan, model lama mungkin tidak
142
+ if "xtts" in model_name.lower():
143
+ kwargs.update({"split_sentences": True, "temperature": temperature, "length_penalty": length_penalty, "repetition_penalty": repetition_penalty})
144
+
145
+ tts.tts_to_file(**kwargs)
146
+ return FileResponse(output_path, media_type="audio/wav", filename="pasblast_tts_dynamic.wav")
147
  except Exception as e:
148
  error_trace = traceback.format_exc()
149
  print(f"FATAL ERROR pada /tts: {error_trace}")
 
151
 
152
  @app.post("/tts_voice_clone", tags=["Core Generation"])
153
  def generate_tts_voice_clone(
154
+ text: str = Form(...),
155
+ model_name: str = Form("tts_models/multilingual/multi-dataset/xtts_v2", description="Harus model yang mendukung kloning (seperti xtts_v2 atau your_tts)"),
156
+ language: str = Form("id"),
157
+ reference_audio: UploadFile = File(...),
158
+ temperature: float = Form(0.75),
159
  username: str = Depends(verify_auth)
160
  ):
161
+ """Zero-shot Voice Cloning menggunakan model yang didukung."""
162
+ tts = get_tts_instance(model_name)
 
163
  ref_path = os.path.join(AUDIO_DIR, f"ref_{uuid.uuid4().hex}.wav")
164
  with open(ref_path, "wb") as buffer:
165
  shutil.copyfileobj(reference_audio.file, buffer)
 
167
  output_path = os.path.join(AUDIO_DIR, f"clone_{uuid.uuid4().hex}.wav")
168
 
169
  try:
170
+ kwargs = {"text": text, "speaker_wav": ref_path, "file_path": output_path}
171
+ if hasattr(tts, "languages") and tts.languages: kwargs["language"] = language
172
+ if "xtts" in model_name.lower():
173
+ kwargs.update({"split_sentences": True, "temperature": temperature})
174
+
175
+ tts.tts_to_file(**kwargs)
 
 
 
176
  return FileResponse(output_path, media_type="audio/wav", filename="pasblast_cloned.wav")
177
  except Exception as e:
178
  error_trace = traceback.format_exc()
 
179
  raise HTTPException(status_code=500, detail=f"Gagal memproses Voice Cloning: {str(e)}")
180
 
181
  @app.post("/voice_conversion", tags=["Core Generation"])
182
  def generate_voice_conversion(
183
+ model_name: str = Form("voice_conversion_models/multilingual/vctk/freevc24", description="Model khusus Voice Conversion"),
184
+ source_audio: UploadFile = File(...),
185
+ reference_audio: UploadFile = File(...),
186
  username: str = Depends(verify_auth)
187
  ):
188
+ """Voice Conversion (Audio-to-Audio) secara dinamis."""
189
+ tts = get_tts_instance(model_name)
 
190
  source_path = os.path.join(AUDIO_DIR, f"src_{uuid.uuid4().hex}.wav")
191
  ref_path = os.path.join(AUDIO_DIR, f"ref_vc_{uuid.uuid4().hex}.wav")
192
  output_path = os.path.join(AUDIO_DIR, f"vc_{uuid.uuid4().hex}.wav")
193
 
194
+ with open(source_path, "wb") as buffer: shutil.copyfileobj(source_audio.file, buffer)
195
+ with open(ref_path, "wb") as buffer: shutil.copyfileobj(reference_audio.file, buffer)
 
 
196
 
197
  try:
198
  if hasattr(tts, "voice_conversion_to_file"):
199
+ tts.voice_conversion_to_file(source_wav=source_path, target_wav=ref_path, file_path=output_path)
 
 
 
 
 
200
  return FileResponse(output_path, media_type="audio/wav", filename="pasblast_converted.wav")
201
  else:
202
+ raise HTTPException(status_code=400, detail=f"Model {model_name} tidak memiliki fitur Voice Conversion.")
203
  except Exception as e:
 
 
204
  raise HTTPException(status_code=500, detail=f"Gagal memproses Voice Conversion: {str(e)}")