arifardev commited on
Commit
5acd462
·
verified ·
1 Parent(s): 150e498

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +38 -75
app.py CHANGED
@@ -4,7 +4,7 @@ import shutil
4
  import secrets
5
  from fastapi import FastAPI, Depends, HTTPException, status, File, UploadFile, Form
6
  from fastapi.security import HTTPBasic, HTTPBasicCredentials
7
- from fastapi.responses import FileResponse, JSONResponse
8
  from TTS.api import TTS
9
 
10
  # --- KONFIGURASI FOLDER TEMP ---
@@ -13,82 +13,79 @@ AUDIO_DIR = "/tmp/audio_output"
13
  os.makedirs(TEMP_DIR, exist_ok=True)
14
  os.makedirs(AUDIO_DIR, exist_ok=True)
15
 
16
- # Paksa environment variable untuk simpan modul bahasa ke /tmp
17
  os.environ["TTS_HOME"] = TEMP_DIR
18
 
19
  app = FastAPI(
20
- title="Coqui TTS API Interaktif",
21
- description="API lengkap untuk Coqui TTS dengan otentikasi. Semua cache dan unduhan diarahkan ke /tmp."
22
  )
23
  security = HTTPBasic()
24
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  # --- OTENTIKASI ---
26
  def verify_auth(credentials: HTTPBasicCredentials = Depends(security)):
27
- # Username default: admin
28
  correct_username = secrets.compare_digest(credentials.username, "admin")
29
  correct_password = secrets.compare_digest(credentials.password, "Rahasia1234")
30
  if not (correct_username and correct_password):
31
  raise HTTPException(
32
  status_code=status.HTTP_401_UNAUTHORIZED,
33
- detail="Autentikasi gagal. Cek username dan password.",
34
  headers={"WWW-Authenticate": "Basic"},
35
  )
36
  return credentials.username
37
 
38
- # --- HELPER FUNCTION ---
39
- def get_tts_instance(model_name: str) -> TTS:
40
- """Load model secara dinamis (akan download ke /tmp jika belum ada)"""
41
- try:
42
- # Gunakan CPU jika Space gratis (tidak ada GPU)
43
- return TTS(model_name=model_name, progress_bar=False, gpu=False)
44
- except Exception as e:
45
- raise HTTPException(status_code=500, detail=f"Gagal memuat model: {str(e)}")
46
-
47
  # --- ENDPOINTS ---
48
 
49
  @app.get("/")
50
  def root():
51
- return {"message": "Coqui TTS API Aktif. Akses /docs untuk interaksi UI API."}
52
 
53
- @app.get("/models", tags=["Info"])
54
- def list_models(username: str = Depends(verify_auth)):
55
- """Mengambil daftar semua model TTS dan Voice Conversion yang tersedia."""
56
- try:
57
- models = TTS().list_models()
58
- return {"models": models}
59
- except Exception as e:
60
- raise HTTPException(status_code=500, detail=str(e))
61
 
62
  @app.post("/tts", tags=["Generation"])
63
  def generate_tts(
64
- text: str = Form(..., description="Teks yang akan diubah menjadi suara"),
65
- model_name: str = Form("tts_models/en/ljspeech/vits", description="Nama model dari endpoint /models"),
66
- speaker: str = Form(None, description="Nama speaker (jika model multi-speaker)"),
67
- language: str = Form(None, description="Kode bahasa (jika model multi-lingual)"),
68
  username: str = Depends(verify_auth)
69
  ):
70
- """Sintesis teks ke suara dasar."""
71
- tts = get_tts_instance(model_name)
72
  output_path = os.path.join(AUDIO_DIR, f"tts_{uuid.uuid4().hex}.wav")
73
 
74
  try:
75
  tts.tts_to_file(text=text, speaker=speaker, language=language, file_path=output_path)
76
- return FileResponse(output_path, media_type="audio/wav", filename="output.wav")
77
  except Exception as e:
78
- raise HTTPException(status_code=500, detail=f"Error sintesis TTS: {str(e)}")
79
 
80
  @app.post("/tts_voice_clone", tags=["Generation"])
81
  def generate_tts_voice_clone(
82
  text: str = Form(...),
83
- model_name: str = Form("tts_models/multilingual/multi-dataset/xtts_v2"),
84
- language: str = Form("id", description="Kode bahasa output (misal: 'id', 'en')"),
85
- reference_audio: UploadFile = File(..., description="File audio WAV singkat untuk di-clone suaranya"),
86
  username: str = Depends(verify_auth)
87
  ):
88
- """Sintesis teks dengan meniru suara dari file audio referensi (Zero-shot Voice Cloning)."""
89
- tts = get_tts_instance(model_name)
90
 
91
- # Simpan file audio referensi ke /tmp
92
  ref_path = os.path.join(AUDIO_DIR, f"ref_{uuid.uuid4().hex}.wav")
93
  with open(ref_path, "wb") as buffer:
94
  shutil.copyfileobj(reference_audio.file, buffer)
@@ -96,41 +93,7 @@ def generate_tts_voice_clone(
96
  output_path = os.path.join(AUDIO_DIR, f"clone_{uuid.uuid4().hex}.wav")
97
 
98
  try:
99
- tts.tts_to_file(
100
- text=text,
101
- language=language,
102
- speaker_wav=ref_path,
103
- file_path=output_path
104
- )
105
- return FileResponse(output_path, media_type="audio/wav", filename="cloned_output.wav")
106
- except Exception as e:
107
- raise HTTPException(status_code=500, detail=f"Error Voice Cloning: {str(e)}")
108
-
109
- @app.post("/voice_conversion", tags=["Generation"])
110
- def voice_conversion(
111
- source_audio: UploadFile = File(..., description="Audio sumber yang ingin diubah suaranya"),
112
- reference_audio: UploadFile = File(..., description="Audio target/referensi (suara yang akan ditiru)"),
113
- model_name: str = Form("voice_conversion_models/multilingual/vctk/freevc24"),
114
- username: str = Depends(verify_auth)
115
- ):
116
- """Mengubah suara dari satu file audio menjadi suara di file audio referensi (Audio-to-Audio)."""
117
- tts = get_tts_instance(model_name)
118
-
119
- source_path = os.path.join(AUDIO_DIR, f"src_{uuid.uuid4().hex}.wav")
120
- ref_path = os.path.join(AUDIO_DIR, f"ref_vc_{uuid.uuid4().hex}.wav")
121
- output_path = os.path.join(AUDIO_DIR, f"vc_{uuid.uuid4().hex}.wav")
122
-
123
- with open(source_path, "wb") as buffer:
124
- shutil.copyfileobj(source_audio.file, buffer)
125
- with open(ref_path, "wb") as buffer:
126
- shutil.copyfileobj(reference_audio.file, buffer)
127
-
128
- try:
129
- tts.voice_conversion_to_file(
130
- source_wav=source_path,
131
- target_wav=ref_path,
132
- file_path=output_path
133
- )
134
- return FileResponse(output_path, media_type="audio/wav", filename="converted_output.wav")
135
  except Exception as e:
136
- raise HTTPException(status_code=500, detail=f"Error Voice Conversion: {str(e)}")
 
4
  import secrets
5
  from fastapi import FastAPI, Depends, HTTPException, status, File, UploadFile, Form
6
  from fastapi.security import HTTPBasic, HTTPBasicCredentials
7
+ from fastapi.responses import FileResponse
8
  from TTS.api import TTS
9
 
10
  # --- KONFIGURASI FOLDER TEMP ---
 
13
  os.makedirs(TEMP_DIR, exist_ok=True)
14
  os.makedirs(AUDIO_DIR, exist_ok=True)
15
 
16
+ # Paksa semua unduhan model masuk ke /tmp
17
  os.environ["TTS_HOME"] = TEMP_DIR
18
 
19
  app = FastAPI(
20
+ title="PasBlast XTTS-v2 API",
21
+ description="API Interaktif menggunakan model terhebat Coqui XTTS-v2 yang di-host di Hugging Face."
22
  )
23
  security = HTTPBasic()
24
 
25
+ # Global variable untuk lazy loading agar startup tidak timeout
26
+ tts_model = None
27
+
28
+ def get_xtts_instance() -> TTS:
29
+ global tts_model
30
+ if tts_model === None:
31
+ try:
32
+ # Memuat XTTS-v2 secara lokal di CPU
33
+ tts_model = TTS(model_name="tts_models/multilingual/multi-dataset/xtts_v2", gpu=False)
34
+ except Exception as e:
35
+ raise HTTPException(status_code=500, detail=f"Gagal memuat model XTTS-v2: {str(e)}")
36
+ return tts_model
37
+
38
  # --- OTENTIKASI ---
39
  def verify_auth(credentials: HTTPBasicCredentials = Depends(security)):
 
40
  correct_username = secrets.compare_digest(credentials.username, "admin")
41
  correct_password = secrets.compare_digest(credentials.password, "Rahasia1234")
42
  if not (correct_username and correct_password):
43
  raise HTTPException(
44
  status_code=status.HTTP_401_UNAUTHORIZED,
45
+ detail="Akses ditolak. Kredensial salah.",
46
  headers={"WWW-Authenticate": "Basic"},
47
  )
48
  return credentials.username
49
 
 
 
 
 
 
 
 
 
 
50
  # --- ENDPOINTS ---
51
 
52
  @app.get("/")
53
  def root():
54
+ return {"message": "XTTS-v2 API Aktif. Akses /docs untuk mencoba secara interaktif."}
55
 
56
+ @app.get("/speakers", tags=["Info"])
57
+ def list_speakers(username: str = Depends(verify_auth)):
58
+ """Melihat daftar nama karakter suara (speakers) bawaan XTTS-v2 yang bisa digunakan."""
59
+ tts = get_xtts_instance()
60
+ return {"speakers": tts.speakers}
 
 
 
61
 
62
  @app.post("/tts", tags=["Generation"])
63
  def generate_tts(
64
+ text: str = Form(..., description="Teks Syarat & Ketentuan PasBlast"),
65
+ speaker: str = Form("Ana Mendes", description="Pilih nama speaker dari endpoint /speakers"),
66
+ language: str = Form("id", description="Gunakan 'id' untuk Bahasa Indonesia"),
 
67
  username: str = Depends(verify_auth)
68
  ):
69
+ """Sintesis teks menggunakan suara karakter bawaan XTTS-v2."""
70
+ tts = get_xtts_instance()
71
  output_path = os.path.join(AUDIO_DIR, f"tts_{uuid.uuid4().hex}.wav")
72
 
73
  try:
74
  tts.tts_to_file(text=text, speaker=speaker, language=language, file_path=output_path)
75
+ return FileResponse(output_path, media_type="audio/wav", filename="pasblast_normal.wav")
76
  except Exception as e:
77
+ raise HTTPException(status_code=500, detail=f"Gagal memproses TTS: {str(e)}")
78
 
79
  @app.post("/tts_voice_clone", tags=["Generation"])
80
  def generate_tts_voice_clone(
81
  text: str = Form(...),
82
+ language: str = Form("id"),
83
+ reference_audio: UploadFile = File(..., description="File WAV suara Anda (durasi 5-10 detik) untuk ditiru"),
 
84
  username: str = Depends(verify_auth)
85
  ):
86
+ """Sintesis teks dengan meniru (*cloning*) suara dari file audio yang Anda unggah."""
87
+ tts = get_xtts_instance()
88
 
 
89
  ref_path = os.path.join(AUDIO_DIR, f"ref_{uuid.uuid4().hex}.wav")
90
  with open(ref_path, "wb") as buffer:
91
  shutil.copyfileobj(reference_audio.file, buffer)
 
93
  output_path = os.path.join(AUDIO_DIR, f"clone_{uuid.uuid4().hex}.wav")
94
 
95
  try:
96
+ tts.tts_to_file(text=text, language=language, speaker_wav=ref_path, file_path=output_path)
97
+ return FileResponse(output_path, media_type="audio/wav", filename="pasblast_cloned.wav")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
98
  except Exception as e:
99
+ raise HTTPException(status_code=500, detail=f"Gagal melakukan Voice Cloning: {str(e)}")