honey126/VoxAI
0
1/**2 * React Component Example for F5-TTS API3 *4 * This shows how to integrate the F5-TTS API into your React application5 * Copy this into your React project and customize as needed6 */7 8import React, { useState, useRef } from 'react';9 10// TypeScript types11interface TTSRequest {12 refAudio: File;13 refText: string;14 genText: string;15 removesilence?: boolean;16 targetRms?: number;17 speed?: number;18 nfeStep?: number;19}20 21interface TTSResponse {22 audioUrl: string;23 seed: number;24 sampleRate: number;25}26 27// API configuration28const API_BASE_URL = process.env.REACT_APP_TTS_API_URL || 'http://localhost:8000';29 30/**31 * Hook for F5-TTS API calls32 */33const useF5TTS = () => {34 const [loading, setLoading] = useState(false);35 const [error, setError] = useState<string | null>(null);36 37 const generateSpeech = async (request: TTSRequest): Promise<Blob | null> => {38 setLoading(true);39 setError(null);40 41 try {42 const formData = new FormData();43 formData.append('ref_audio', request.refAudio);44 formData.append('ref_text', request.refText);45 formData.append('gen_text', request.genText);46 47 if (request.removesilence !== undefined) {48 formData.append('remove_silence', String(request.removesilence));49 }50 if (request.targetRms !== undefined) {51 formData.append('target_rms', String(request.targetRms));52 }53 if (request.speed !== undefined) {54 formData.append('speed', String(request.speed));55 }56 if (request.nfeStep !== undefined) {57 formData.append('nfe_step', String(request.nfeStep));58 }59 60 const response = await fetch(`${API_BASE_URL}/api/tts`, {61 method: 'POST',62 body: formData,63 });64 65 if (!response.ok) {66 const errorData = await response.json();67 throw new Error(errorData.detail || 'Speech generation failed');68 }69 70 const audioBlob = await response.blob();71 return audioBlob;72 73 } catch (err) {74 const errorMessage = err instanceof Error ? err.message : 'Unknown error occurred';75 setError(errorMessage);76 console.error('TTS Error:', err);77 return null;78 } finally {79 setLoading(false);80 }81 };82 83 return { generateSpeech, loading, error };84};85 86/**87 * Example React Component88 */89export const F5TTSGenerator: React.FC = () => {90 const { generateSpeech, loading, error } = useF5TTS();91 92 const [refAudio, setRefAudio] = useState<File | null>(null);93 const [refText, setRefText] = useState('');94 const [genText, setGenText] = useState('');95 const [audioUrl, setAudioUrl] = useState<string | null>(null);96 const [removeSilence, setRemoveSilence] = useState(false);97 const [speed, setSpeed] = useState(1.0);98 99 const audioRef = useRef<HTMLAudioElement>(null);100 101 const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {102 if (e.target.files && e.target.files[0]) {103 setRefAudio(e.target.files[0]);104 }105 };106 107 const handleGenerate = async () => {108 if (!refAudio || !refText || !genText) {109 alert('Please provide reference audio, reference text, and generation text');110 return;111 }112 113 // Revoke previous audio URL to free memory114 if (audioUrl) {115 URL.revokeObjectURL(audioUrl);116 }117 118 const audioBlob = await generateSpeech({119 refAudio,120 refText,121 genText,122 removesilence: removeSilence,123 speed,124 });125 126 if (audioBlob) {127 const url = URL.createObjectURL(audioBlob);128 setAudioUrl(url);129 }130 };131 132 const handleDownload = () => {133 if (audioUrl) {134 const a = document.createElement('a');135 a.href = audioUrl;136 a.download = 'generated_speech.wav';137 document.body.appendChild(a);138 a.click();139 document.body.removeChild(a);140 }141 };142 143 return (144 <div className="f5-tts-generator" style={{ maxWidth: '800px', margin: '0 auto', padding: '20px' }}>145 <h1>๐ฃ๏ธ F5-TTS Voice Cloning</h1>146 147 {/* Reference Audio Upload */}148 <div className="form-group" style={{ marginBottom: '20px' }}>149 <label htmlFor="ref-audio" style={{ display: 'block', marginBottom: '8px', fontWeight: 'bold' }}>150 Reference Audio (WAV/MP3)151 </label>152 <input153 id="ref-audio"154 type="file"155 accept="audio/*"156 onChange={handleFileChange}157 style={{ width: '100%', padding: '10px', border: '1px solid #ccc', borderRadius: '4px' }}158 />159 {refAudio && <p style={{ marginTop: '8px', color: '#666' }}>Selected: {refAudio.name}</p>}160 </div>161 162 {/* Reference Text */}163 <div className="form-group" style={{ marginBottom: '20px' }}>164 <label htmlFor="ref-text" style={{ display: 'block', marginBottom: '8px', fontWeight: 'bold' }}>165 Reference Text (What the reference audio says)166 </label>167 <textarea168 id="ref-text"169 value={refText}170 onChange={(e) => setRefText(e.target.value)}171 placeholder="some call me nature, others call me mother nature."172 rows={3}173 style={{ width: '100%', padding: '10px', border: '1px solid #ccc', borderRadius: '4px', fontFamily: 'inherit' }}174 />175 </div>176 177 {/* Generation Text */}178 <div className="form-group" style={{ marginBottom: '20px' }}>179 <label htmlFor="gen-text" style={{ display: 'block', marginBottom: '8px', fontWeight: 'bold' }}>180 Text to Generate (In the same voice)181 </label>182 <textarea183 id="gen-text"184 value={genText}185 onChange={(e) => setGenText(e.target.value)}186 placeholder="Enter the text you want to generate in the reference voice..."187 rows={5}188 style={{ width: '100%', padding: '10px', border: '1px solid #ccc', borderRadius: '4px', fontFamily: 'inherit' }}189 />190 </div>191 192 {/* Advanced Options */}193 <div className="advanced-options" style={{ marginBottom: '20px', padding: '15px', background: '#f5f5f5', borderRadius: '4px' }}>194 <h3 style={{ marginTop: 0 }}>Advanced Options</h3>195 196 <div style={{ marginBottom: '12px' }}>197 <label>198 <input199 type="checkbox"200 checked={removeSilence}201 onChange={(e) => setRemoveSilence(e.target.checked)}202 style={{ marginRight: '8px' }}203 />204 Remove silence from output205 </label>206 </div>207 208 <div>209 <label htmlFor="speed" style={{ display: 'block', marginBottom: '4px' }}>210 Speed: {speed.toFixed(1)}x211 </label>212 <input213 id="speed"214 type="range"215 min="0.5"216 max="2.0"217 step="0.1"218 value={speed}219 onChange={(e) => setSpeed(parseFloat(e.target.value))}220 style={{ width: '100%' }}221 />222 </div>223 </div>224 225 {/* Generate Button */}226 <button227 onClick={handleGenerate}228 disabled={loading || !refAudio || !refText || !genText}229 style={{230 width: '100%',231 padding: '15px',232 fontSize: '16px',233 fontWeight: 'bold',234 color: 'white',235 background: loading ? '#ccc' : '#007bff',236 border: 'none',237 borderRadius: '4px',238 cursor: loading ? 'not-allowed' : 'pointer',239 marginBottom: '20px'240 }}241 >242 {loading ? '๐ Generating Speech...' : '๐๏ธ Generate Speech'}243 </button>244 245 {/* Error Display */}246 {error && (247 <div style={{ padding: '15px', background: '#fee', border: '1px solid #fcc', borderRadius: '4px', marginBottom: '20px' }}>248 <strong>Error:</strong> {error}249 </div>250 )}251 252 {/* Audio Player */}253 {audioUrl && (254 <div className="audio-output" style={{ padding: '20px', background: '#f0f8ff', borderRadius: '4px' }}>255 <h3>โ
Generated Audio</h3>256 <audio257 ref={audioRef}258 controls259 src={audioUrl}260 style={{ width: '100%', marginBottom: '15px' }}261 />262 <button263 onClick={handleDownload}264 style={{265 padding: '10px 20px',266 fontSize: '14px',267 color: 'white',268 background: '#28a745',269 border: 'none',270 borderRadius: '4px',271 cursor: 'pointer'272 }}273 >274 โฌ๏ธ Download Audio275 </button>276 </div>277 )}278 279 {/* Loading Indicator */}280 {loading && (281 <div style={{ textAlign: 'center', padding: '20px' }}>282 <div style={{283 border: '4px solid #f3f3f3',284 borderTop: '4px solid #007bff',285 borderRadius: '50%',286 width: '40px',287 height: '40px',288 animation: 'spin 1s linear infinite',289 margin: '0 auto'290 }} />291 <p style={{ marginTop: '15px', color: '#666' }}>292 Generating speech... This may take 10-30 seconds depending on text length.293 </p>294 </div>295 )}296 297 <style>{`298 @keyframes spin {299 0% { transform: rotate(0deg); }300 100% { transform: rotate(360deg); }301 }302 `}</style>303 </div>304 );305};306 307export default F5TTSGenerator;308 