File size: 7,010 Bytes
0229683
 
 
 
41d1149
8b1a675
 
0229683
 
5acd462
0229683
 
18c7eb5
 
 
 
 
 
 
 
0229683
 
 
 
 
 
 
0850bc4
 
0229683
8b1a675
 
 
0229683
 
 
f6b9bca
5acd462
f6b9bca
 
5acd462
f6b9bca
 
8b1a675
5acd462
8b1a675
f6b9bca
5acd462
e251975
0229683
 
 
 
 
 
8b1a675
0229683
 
 
 
8b1a675
 
 
0229683
3e26c2a
0229683
8b1a675
 
 
3e26c2a
8b1a675
3e26c2a
8b1a675
 
f6b9bca
 
8b1a675
 
 
 
f6b9bca
8b1a675
 
0229683
8b1a675
0229683
f6b9bca
8b1a675
 
f6b9bca
 
0229683
 
f6b9bca
0229683
 
 
 
 
 
f6b9bca
 
8b1a675
f6b9bca
5acd462
0229683
8b1a675
3e26c2a
8b1a675
3e26c2a
8b1a675
f6b9bca
 
3e26c2a
 
f6b9bca
3e26c2a
8b1a675
3e26c2a
 
f6b9bca
 
3e26c2a
8b1a675
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3e26c2a
8b1a675
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
import os
import uuid
import shutil
import secrets
import traceback
import torch
import edge_tts
from fastapi import FastAPI, Depends, HTTPException, status, File, UploadFile, Form
from fastapi.security import HTTPBasic, HTTPBasicCredentials
from fastapi.responses import FileResponse
from TTS.api import TTS

# --- PATCH PYTORCH 2.6+ ---
original_torch_load = torch.load
def patched_torch_load(*args, **kwargs):
    kwargs['weights_only'] = False
    return original_torch_load(*args, **kwargs)
torch.load = patched_torch_load
# --------------------------

# --- KONFIGURASI FOLDER TEMP ---
TEMP_DIR = "/tmp/coqui_data"
AUDIO_DIR = "/tmp/audio_output"
os.makedirs(TEMP_DIR, exist_ok=True)
os.makedirs(AUDIO_DIR, exist_ok=True)

os.environ["TTS_HOME"] = TEMP_DIR
os.environ["COQUI_TOS_AGREED"] = "1"

app = FastAPI(
    title="PasBlast Ultimate Audio API",
    description="API Komplet: Coqui TTS (Cloning/Dynamic) + Edge-TTS (Native Indonesian Super Cepat).",
    version="3.0.0"
)
security = HTTPBasic()

active_models = {}

def get_tts_instance(model_name: str) -> TTS:
    if model_name not in active_models:
        try:
            print(f"Mencoba memuat model '{model_name}' ke RAM...")
            active_models[model_name] = TTS(model_name=model_name, gpu=False)
            print(f"Model '{model_name}' berhasil dimuat!")
        except Exception as e:
            raise HTTPException(status_code=500, detail=f"Gagal memuat model: {str(e)}")
    return active_models[model_name]

# --- OTENTIKASI KEAMANAN ---
def verify_auth(credentials: HTTPBasicCredentials = Depends(security)):
    correct_username = secrets.compare_digest(credentials.username, "admin")
    correct_password = secrets.compare_digest(credentials.password, "Rahasia1234")
    if not (correct_username and correct_password):
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Akses ditolak.",
            headers={"WWW-Authenticate": "Basic"},
        )
    return credentials.username

# ==========================================
# 1. COQUI TTS ENDPOINTS (VOICE CLONING DLL)
# ==========================================

@app.get("/", tags=["Status"])
def root():
    return {"status": "online", "message": "Ultimate API (Coqui + Edge-TTS) Aktif!"}

@app.get("/models", tags=["Coqui Metadata"])
def list_models(username: str = Depends(verify_auth)):
    return {"total_available_models": len(TTS.list_models()), "models": TTS.list_models()}

@app.get("/languages", tags=["Coqui Metadata"])
def list_languages(model_name: str = "tts_models/multilingual/multi-dataset/xtts_v2", username: str = Depends(verify_auth)):
    tts = get_tts_instance(model_name)
    languages = tts.languages if hasattr(tts, "languages") and tts.languages else []
    return {"model": model_name, "languages": languages}

@app.get("/speakers", tags=["Coqui Metadata"])
def list_speakers(model_name: str = "tts_models/multilingual/multi-dataset/xtts_v2", username: str = Depends(verify_auth)):
    tts = get_tts_instance(model_name)
    speakers = tts.speakers if hasattr(tts, "speakers") and tts.speakers else []
    return {"model": model_name, "speakers": speakers}

@app.post("/tts_voice_clone", tags=["Coqui Generation"])
def generate_tts_voice_clone(
    text: str = Form(...),
    model_name: str = Form("tts_models/multilingual/multi-dataset/xtts_v2"),
    language: str = Form("es", description="Untuk Bahasa Indonesia gunakan logat 'es' atau 'it'"),
    reference_audio: UploadFile = File(...),
    temperature: float = Form(0.75),
    username: str = Depends(verify_auth)
):
    tts = get_tts_instance(model_name)
    ref_path = os.path.join(AUDIO_DIR, f"ref_{uuid.uuid4().hex}.wav")
    with open(ref_path, "wb") as buffer:
        shutil.copyfileobj(reference_audio.file, buffer)
        
    output_path = os.path.join(AUDIO_DIR, f"clone_{uuid.uuid4().hex}.wav")
    try:
        kwargs = {"text": text, "speaker_wav": ref_path, "file_path": output_path}
        if hasattr(tts, "languages") and tts.languages: kwargs["language"] = language
        if "xtts" in model_name.lower(): kwargs.update({"split_sentences": True, "temperature": temperature})
        tts.tts_to_file(**kwargs)
        return FileResponse(output_path, media_type="audio/wav", filename="pasblast_cloned.wav")
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@app.post("/voice_conversion", tags=["Coqui Generation"])
def generate_voice_conversion(
    model_name: str = Form("voice_conversion_models/multilingual/vctk/freevc24"),
    source_audio: UploadFile = File(...),
    reference_audio: UploadFile = File(...),
    username: str = Depends(verify_auth)
):
    tts = get_tts_instance(model_name)
    source_path = os.path.join(AUDIO_DIR, f"src_{uuid.uuid4().hex}.wav")
    ref_path = os.path.join(AUDIO_DIR, f"ref_{uuid.uuid4().hex}.wav")
    output_path = os.path.join(AUDIO_DIR, f"vc_{uuid.uuid4().hex}.wav")
    
    with open(source_path, "wb") as buffer: shutil.copyfileobj(source_audio.file, buffer)
    with open(ref_path, "wb") as buffer: shutil.copyfileobj(reference_audio.file, buffer)
    try:
        tts.voice_conversion_to_file(source_wav=source_path, target_wav=ref_path, file_path=output_path)
        return FileResponse(output_path, media_type="audio/wav")
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

# ==========================================
# 2. EDGE TTS ENDPOINTS (NATIVE INDONESIAN)
# ==========================================

@app.get("/edge_speakers", tags=["Native Indonesian (Edge-TTS)"])
async def list_edge_speakers(username: str = Depends(verify_auth)):
    """Melihat daftar suara native Bahasa Indonesia dari mesin Microsoft Edge."""
    try:
        voices = await edge_tts.list_voices()
        id_voices = [{"Name": v["Name"], "Gender": v["Gender"]} for v in voices if "id-ID" in v["Locale"]]
        return {"total_indonesia_voices": len(id_voices), "voices": id_voices}
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@app.post("/tts_indonesia", tags=["Native Indonesian (Edge-TTS)"])
async def generate_tts_indonesia(
    text: str = Form(..., description="Teks Syarat & Ketentuan PasBlast"),
    speaker: str = Form("id-ID-GadisNeural", description="Pilihan: id-ID-GadisNeural (Wanita) atau id-ID-ArdiNeural (Pria)"),
    rate: str = Form("+0%", description="Kecepatan bicara (contoh: +10% atau -10%)"),
    username: str = Depends(verify_auth)
):
    """Sintesis suara Bahasa Indonesia super cepat, natural, dan TANPA beban RAM!"""
    output_path = os.path.join(AUDIO_DIR, f"edge_{uuid.uuid4().hex}.mp3")
    try:
        communicate = edge_tts.Communicate(text, speaker, rate=rate)
        await communicate.save(output_path)
        return FileResponse(output_path, media_type="audio/mpeg", filename="pasblast_id_native.mp3")
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Gagal memproses Edge TTS: {str(e)}")