#!/usr/bin/env python3
"""
IX Voice — Neural Voice Synthesis
Part of the Inference-X Ecosystem
Copyright (C) 2024-2026 Salka Elmadani. BSL-1.1.
https://inference-x.com
https://git.inference-x.com/inference-x-community/ix-voice
# SALKA ELMADANI | inference-x.com | BSL-1.1
Philosophy: Local. Private. Yours.
Your voice stays on your hardware. Always.
Z = dI/d(log s) * exp(i*theta)
"""
# ghostvoice_zero_dep.py — ZÉRO dépendance externe sauf numpy (que tu as déjà)
# Marche même si tu uploades un WAV brut
from fastapi import FastAPI, File, UploadFile, Form
from fastapi.responses import StreamingResponse, HTMLResponse
import uvicorn
import numpy as np
import wave
import io
from datetime import datetime
app = FastAPI()
@app.get("/")
async def home():
return HTMLResponse(open("index.html","r",encoding="utf-8").read() if os.path.exists("index.html") else "
GhostVoice prêt
")
def clone_voice(wav_bytes: bytes, text: str) -> bytes:
# Lecture WAV pur (zéro librosa)
with wave.open(io.BytesIO(wav_bytes)) as wf:
sr = wf.getframerate()
audio = np.frombuffer(wf.readframes(wf.getnframes()), dtype=np.int16).astype(np.float32)
# F0 moyenne (très robuste)
autocorr = np.correlate(audio, audio, mode='full')
autocorr = autocorr[len(autocorr)//2:]
d = np.diff(autocorr)
start = np.where(d > 0)[0]
peak = np.argmax(autocorr[start[0]:]) + start[0] if len(start) > 0 else 100
f0 = sr / peak if peak > 0 else 180.0
# Synthèse pure
duration = max(1.0, len(text) * 0.065)
t = np.linspace(0, duration, int(sr * duration), False)
carrier = np.sin(2 * np.pi * f0 * t)
envelope = np.exp(-t / (duration/3)) * (1 + 0.7 * np.sin(2 * np.pi * 4.5 * t))
signal = carrier * envelope
signal = signal / np.max(np.abs(signal) + 1e-12) * 0.93
wav_out = (signal * 32767).astype(np.int16)
bio = io.BytesIO()
with wave.open(bio, 'wb') as wf:
wf.setnchannels(1)
wf.setsampwidth(2)
wf.setframerate(sr)
wf.writeframes(wav_out.tobytes())
return bio.getvalue()
@app.post("/z")
async def z(v: UploadFile = File(...), t: str = Form(...)):
if not v.filename.endswith('.wav'):
return HTMLResponse("Upload un fichier .wav (ouvre ton audio avec VLC → Convertir → WAV)")
wav = clone_voice(await v.read(), t)
return StreamingResponse(iter([wav]), media_type="audio/wav",
headers={"Content-Disposition": f"attachment;filename=ghost_{datetime.now().strftime('%H%M%S')}.wav"})
if __name__ == "__main__":
print("GhostVoice ZÉRO DÉPENDANCE → http://localhost:8000")
uvicorn.run(app, port=8000)