--- license: afl-3.0 datasets: - HuggingFaceFW/finewiki language: - ja metrics: - character base_model: - deepseek-ai/DeepSeek-OCR new_version: deepseek-ai/DeepSeek-OCR pipeline_tag: text-to-speech library_name: asteroid tags: - art - code --- ## π§ Backend β FastAPI (Python) **File: backend/main.py** ```python from fastapi import FastAPI, Form from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import FileResponse, JSONResponse import requests, os, hashlib from pathlib import Path app = FastAPI() # CORS for frontend app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) HF_TOKEN = os.environ.get("HF_TOKEN") HF_URL = "https://api-inference.huggingface.co/models/{model_id}" headers = {"Authorization": f"Bearer {HF_TOKEN}"} CACHE_DIR = Path("cache") CACHE_DIR.mkdir(exist_ok=True) CHARACTER_PRESETS = { "Ichika": {"model": "suno/bark", "style": "mature, calm, older-sister tone"}, "Nino": {"model": "facebook/mms-tts-en", "style": "confident, tsundere tone"}, "Miku": {"model": "espnet/kan-bayashi_ljspeech_vits", "style": "shy, gentle tone"}, "Yotsuba": {"model": "espnet/kan-bayashi_ljspeech_fastspeech2", "style": "cheerful, energetic tone"}, "Itsuki": {"model": "parler-tts/parler-tts-mini-v1", "style": "serious, sincere tone"}, } def get_cache_filename(character, text): key = hashlib.sha256(f"{character}:{text}".encode()).hexdigest() return CACHE_DIR / f"{key}.wav" @app.post("/api/tts") def generate_tts(character: str = Form(...), text: str = Form(...)): preset = CHARACTER_PRESETS.get(character) if not preset: return JSONResponse({"error": "Character not found"}, status_code=400) cache_file = get_cache_filename(character, text) if cache_file.exists(): return FileResponse(cache_file, media_type="audio/wav") model_id = preset["model"] style_prompt = preset["style"] payload = {"inputs": f"[{style_prompt}] {text}"} response = requests.post(HF_URL.format(model_id=model_id), headers=headers, json=payload) if response.status_code != 200: return JSONResponse({"error": response.text}, status_code=500) with open(cache_file, "wb") as f: f.write(response.content) return FileResponse(cache_file, media_type="audio/wav") ``` --- ## π¨ Frontend β React (Vite) **File: frontend/src/App.jsx** ```jsx import React, { useState } from 'react'; export default function App() { const [text, setText] = useState('γγγ«γ‘γ―'); // Default greeting const [character, setCharacter] = useState('Miku'); const [audioUrl, setAudioUrl] = useState(null); const [loading, setLoading] = useState(false); const [history, setHistory] = useState([]); const characters = ['Ichika', 'Nino', 'Miku', 'Yotsuba', 'Itsuki']; async function handleSpeak(e) { e.preventDefault(); setLoading(true); const form = new FormData(); form.append('character', character); form.append('text', text); const res = await fetch('http://localhost:8000/api/tts', { method: 'POST', body: form, }); if (res.ok) { const blob = await res.blob(); const url = URL.createObjectURL(blob); setAudioUrl(url); setHistory((prev) => [{ text, character, url }, ...prev]); } else { alert('Error generating speech'); } setLoading(false); } function handleDownload(url, name) { const link = document.createElement('a'); link.href = url; link.download = `${name}.wav`; link.click(); } return (