diff --git a/README_INDONESIAN_TTS.md b/README_INDONESIAN_TTS.md deleted file mode 100644 index e2f9672..0000000 --- a/README_INDONESIAN_TTS.md +++ /dev/null @@ -1,323 +0,0 @@ -# Indonesian TTS Integration Guide - -## Overview - -Repository ini mengintegrasikan [Indonesian TTS](https://github.com/Wikidepia/indonesian-tts) yang menggunakan Coqui TTS untuk menghasilkan suara Indonesia yang lebih natural dan akurat. Indonesian TTS ini cocok untuk sistem antrian Puskesmas karena: - -- **Suara Natural**: Menggunakan model yang dilatih khusus untuk bahasa Indonesia -- **Pengucapan Akurat**: Menggunakan g2p-id untuk konversi grapheme ke phoneme -- **Multiple Speakers**: Tersedia 80+ speaker dengan berbagai karakteristik suara -- **Offline Capability**: Dapat berjalan tanpa internet setelah model diinstall - -## Fitur Utama - -### 1. Indonesian Pronunciation -- Konversi otomatis nomor antrian ke pengucapan Indonesia -- Contoh: "U5" → "U Lima", "A10" → "A Sepuluh" -- Mendukung angka 0-100 dengan pengucapan yang benar - -### 2. Multiple TTS Options -- **Indonesian TTS** (Prioritas): Menggunakan model Coqui TTS Indonesia -- **Google TTS** (Fallback): Menggunakan Google Cloud TTS API -- **Browser TTS** (Fallback): Menggunakan Web Speech API browser - -### 3. Audio Sequence Management -- Urutan audio: Attention Sound → TTS → Attention Sound -- Queue management untuk mencegah overlap -- Cross-page communication untuk admin → display - -## Instalasi - -### Prerequisites - -1. **Python 3.8+** -2. **pip** (Python package manager) -3. **Laravel 8+** dengan PHP 8.0+ - -### Step 1: Install Coqui TTS - -```bash -# Install Coqui TTS -pip install TTS - -# Verify installation -tts --version -``` - -### Step 2: Download Model Files - -1. Kunjungi [Indonesian TTS Releases](https://github.com/Wikidepia/indonesian-tts/releases) -2. Download file: - - `checkpoint.pth` (model file) - - `config.json` (configuration file) -3. Buat folder: `storage/app/tts/models/` -4. Simpan file di folder tersebut - -### Step 3: Install g2p-id (Optional) - -```bash -# Install g2p-id for better pronunciation -pip install g2p-id - -# Verify installation -g2p-id --help -``` - -### Step 4: Test Installation - -```bash -# Test TTS with sample text -tts --text "Halo dunia" \ - --model_path storage/app/tts/models/checkpoint.pth \ - --config_path storage/app/tts/models/config.json \ - --speaker_idx wibowo \ - --out_path test.wav -``` - -## Konfigurasi - -### Environment Variables - -Tambahkan ke file `.env`: - -```env -# Indonesian TTS Configuration -INDONESIAN_TTS_ENABLED=true -INDONESIAN_TTS_MODEL_PATH=storage/app/tts/models/checkpoint.pth -INDONESIAN_TTS_CONFIG_PATH=storage/app/tts/models/config.json -INDONESIAN_TTS_DEFAULT_SPEAKER=wibowo - -# Google TTS (Fallback) -GOOGLE_TTS_API_KEY=your_google_tts_api_key -``` - -### File Structure - -``` -storage/ -├── app/ -│ └── tts/ -│ ├── models/ -│ │ ├── checkpoint.pth -│ │ └── config.json -│ └── g2p-id -└── public/ - └── audio/ - └── queue_calls/ - └── indonesian_tts_*.wav -``` - -## Penggunaan - -### 1. Admin Panel - -Akses halaman Indonesian TTS Settings: -``` -/admin/indonesian-tts -``` - -Fitur yang tersedia: -- Status monitoring (Indonesian TTS, Coqui TTS, Model Files, Speakers) -- Test TTS dengan text custom -- Installation instructions -- Download model files - -### 2. API Endpoints - -```php -// Generate TTS audio -POST /admin/indonesian-tts/generate -{ - "poli_name": "Poli Umum", - "queue_number": "U5" -} - -// Create complete audio sequence -POST /admin/indonesian-tts/audio-sequence -{ - "poli_name": "Poli Umum", - "queue_number": "U5" -} - -// Check status -GET /admin/indonesian-tts/status - -// Test TTS -POST /admin/indonesian-tts/test -{ - "text": "Nomor antrian U Lima, silakan menuju ke Poli Umum" -} -``` - -### 3. Service Usage - -```php -use App\Services\IndonesianTTSService; - -$ttsService = new IndonesianTTSService(); - -// Generate TTS -$result = $ttsService->generateQueueCall('Poli Umum', 'U5'); - -// Create audio sequence -$sequence = $ttsService->createCompleteAudioSequence('Poli Umum', 'U5'); - -// Check status -$isAvailable = $ttsService->isIndonesianTTSAvailable(); -$speakers = $ttsService->getAvailableSpeakers(); -``` - -## Available Speakers - -Model Indonesian TTS menyediakan 80+ speaker dengan karakteristik berbeda: - -### Male Speakers -- `wibowo`: Suara pria natural, cocok untuk announcement -- `ardi`: Suara pria formal -- `budi`: Suara pria ramah - -### Female Speakers -- `gadis`: Suara wanita natural, cocok untuk announcement -- `sari`: Suara wanita formal -- `rini`: Suara wanita ramah - -### Regional Speakers -- `javanese_*`: Speaker dengan aksen Jawa -- `sundanese_*`: Speaker dengan aksen Sunda - -## Troubleshooting - -### Common Issues - -1. **"tts command not found"** - ```bash - # Reinstall TTS - pip uninstall TTS - pip install TTS - ``` - -2. **"Model files not found"** - ```bash - # Check file permissions - ls -la storage/app/tts/models/ - chmod 644 storage/app/tts/models/* - ``` - -3. **"Permission denied"** - ```bash - # Fix directory permissions - chmod -R 755 storage/app/tts/ - chown -R www-data:www-data storage/app/tts/ - ``` - -4. **"Audio generation failed"** - ```bash - # Check Python environment - which python - which tts - - # Test with simple command - tts --text "test" --model_path storage/app/tts/models/checkpoint.pth --config_path storage/app/tts/models/config.json --out_path test.wav - ``` - -### Debug Mode - -Aktifkan debug mode di `.env`: - -```env -APP_DEBUG=true -LOG_LEVEL=debug -``` - -Cek log Laravel: -```bash -tail -f storage/logs/laravel.log -``` - -## Performance Optimization - -### 1. Model Caching -```php -// Cache model loading -$ttsService = app(IndonesianTTSService::class); -``` - -### 2. Audio File Management -```bash -# Clean old audio files (older than 7 days) -find storage/app/public/audio/queue_calls/ -name "*.wav" -mtime +7 -delete -``` - -### 3. Memory Management -```php -// Limit concurrent TTS processes -$maxProcesses = 3; -``` - -## Security Considerations - -1. **File Permissions**: Pastikan model files tidak dapat diakses publik -2. **Input Validation**: Validasi input text untuk mencegah injection -3. **Rate Limiting**: Batasi jumlah request TTS per menit -4. **Audio Sanitization**: Bersihkan nama file audio - -## Monitoring - -### Health Checks - -```php -// Check TTS health -$health = [ - 'indonesian_tts' => $ttsService->isIndonesianTTSAvailable(), - 'coqui_tts' => $ttsService->isCoquiTTSInstalled(), - 'model_files' => file_exists($modelPath), - 'speakers' => count($ttsService->getAvailableSpeakers()) -]; -``` - -### Metrics - -- TTS generation success rate -- Audio file size and duration -- Speaker usage statistics -- Error rates and types - -## Migration from Google TTS - -Jika ingin migrasi dari Google TTS ke Indonesian TTS: - -1. **Backup existing TTS configuration** -2. **Install Indonesian TTS** (ikuti guide di atas) -3. **Update service configuration**: - ```php - // In TTSService.php - public function generateQueueCall($poliName, $queueNumber) - { - // Try Indonesian TTS first - $indonesianTTS = new IndonesianTTSService(); - if ($indonesianTTS->isIndonesianTTSAvailable()) { - return $indonesianTTS->generateQueueCall($poliName, $queueNumber); - } - - // Fallback to Google TTS - return parent::generateQueueCall($poliName, $queueNumber); - } - ``` -4. **Test thoroughly** dengan berbagai nomor antrian -5. **Monitor performance** dan error rates - -## Support - -Untuk bantuan lebih lanjut: - -1. **Documentation**: [Indonesian TTS GitHub](https://github.com/Wikidepia/indonesian-tts) -2. **Issues**: Buat issue di repository project ini -3. **Community**: Coqui TTS Discord/Forum - -## License - -Indonesian TTS model memiliki lisensi terpisah. Pastikan untuk membaca dan mematuhi lisensi yang berlaku sebelum menggunakan untuk tujuan komersial. - ---- - -**Note**: Indonesian TTS memerlukan resource yang cukup (RAM, CPU) untuk berjalan optimal. Pastikan server memiliki spesifikasi yang memadai. diff --git a/TTS_README.md b/TTS_README.md deleted file mode 100644 index 78d5633..0000000 --- a/TTS_README.md +++ /dev/null @@ -1,369 +0,0 @@ -# 🎤 Text-to-Speech (TTS) System untuk Puskesmas - -## 📋 Overview - -Sistem TTS ini dirancang khusus untuk panggilan nomor antrian di Puskesmas dengan dukungan bahasa Indonesia. Menggunakan library Python yang kompatibel dengan Windows dan memiliki fallback system. - -## 🚀 Fitur Utama - -### ✅ **Sudah Tersedia** - -- **pyttsx3**: TTS offline menggunakan suara sistem Windows -- **gTTS**: TTS online Google sebagai fallback -- **Indonesian TTS**: Model TTS khusus bahasa Indonesia dengan Coqui TTS -- **Bahasa Indonesia**: Dukungan penuh untuk pengucapan bahasa Indonesia -- **Responsive UI**: Interface admin yang responsif dengan Tailwind CSS -- **Audio Management**: Sistem manajemen file audio otomatis -- **Cross-platform**: Kompatibel dengan Windows, Linux, dan macOS - -### 🔧 **Fitur TTS** - -- Generate audio untuk nomor antrian -- Customizable service name -- Multiple voice options -- Audio file cleanup otomatis -- Real-time status monitoring -- Test dan preview audio -- **Indonesian TTS**: Model khusus bahasa Indonesia -- **Queue Call Generation**: Generate panggilan antrian otomatis -- **Model Management**: Download dan setup model TTS -- **Installation Guide**: Panduan instalasi lengkap - -## 📦 Dependencies - -### **Python Libraries** - -```bash -pip install pyttsx3 # TTS offline -pip install gTTS # TTS online (fallback) -pip install coqui-tts # Indonesian TTS model -``` - -### **Laravel Requirements** - -- PHP 8.0+ -- Laravel 9+ -- Storage permissions untuk audio files - -## 🛠️ Instalasi - -### 1. **Install Python Dependencies** - -```bash -# Install pyttsx3 (offline TTS) -pip install pyttsx3 - -# Install gTTS (online TTS fallback) -pip install gTTS -``` - -### 2. **Verifikasi Instalasi** - -```bash -# Test Python -python --version - -# Test pyttsx3 -python -c "import pyttsx3; print('pyttsx3 OK')" - -# Test gTTS -python -c "from gtts import gTTS; print('gTTS OK')" - -# Test Coqui TTS -python -c "import TTS; print('Coqui TTS OK')" -``` - -### 3. **Setup Laravel** - -```bash -# Buat direktori storage -mkdir -p storage/app/tts_scripts - -# Set permissions (Linux/Mac) -chmod -R 755 storage/app/tts_scripts - -# Set permissions (Windows) -# Pastikan folder memiliki write access -``` - -## 🎯 Cara Penggunaan - -### **1. Akses TTS Management** - -- Login sebagai admin -- Buka menu "TTS Management" di sidebar untuk basic TTS -- Buka menu "Indonesian TTS" di sidebar untuk Indonesian TTS -- Atau akses langsung: `/admin/tts` atau `/admin/indonesian-tts` - -### **2. Test TTS** - -1. Masukkan nomor antrian (contoh: "001") -2. Masukkan nama layanan (contoh: "Poli Umum") -3. Klik "Generate TTS" -4. Tunggu proses generate selesai -5. Klik "Test TTS" untuk mendengarkan - -### **3. Generate TTS via API** - -```bash -# Basic TTS -POST /admin/tts/generate -Content-Type: application/json - -{ - "queue_number": "001", - "service_name": "Poli Umum" -} - -# Indonesian TTS -POST /admin/indonesian-tts/generate -Content-Type: application/json - -{ - "poli_name": "Poli Umum", - "queue_number": "001" -} -``` - -### **4. Play Audio via API** - -```bash -GET /admin/tts/play?file_path=/path/to/audio.wav -``` - -## 🔌 API Endpoints - -| Method | Endpoint | Description | -| ------ | -------------------------------------- | --------------------------- | -| `GET` | `/admin/tts` | TTS Management Page | -| `POST` | `/admin/tts/generate` | Generate TTS Audio | -| `GET` | `/admin/tts/play` | Play TTS Audio | -| `GET` | `/admin/tts/test` | Test TTS Service | -| `GET` | `/admin/tts/voices` | Get Available Voices | -| `POST` | `/admin/tts/cleanup` | Cleanup Old Files | -| `GET` | `/admin/tts/status` | Get System Status | -| `GET` | `/admin/indonesian-tts` | Indonesian TTS Page | -| `POST` | `/admin/indonesian-tts/generate` | Generate Indonesian TTS | -| `POST` | `/admin/indonesian-tts/audio-sequence` | Create Audio Sequence | -| `GET` | `/admin/indonesian-tts/status` | Check Indonesian TTS Status | -| `GET` | `/admin/indonesian-tts/install` | Get Installation Guide | -| `GET` | `/admin/indonesian-tts/download` | Download Model Files | - -## 📁 File Structure - -``` -app/ -├── Http/Controllers/ -│ ├── TTSController.php # Basic TTS Controller -│ └── IndonesianTTSController.php # Indonesian TTS Controller -├── Services/ -│ ├── SimpleTTSService.php # Basic TTS Service Logic -│ └── IndonesianTTSService.php # Indonesian TTS Service Logic -resources/views/admin/ -├── tts/ -│ └── index.blade.php # Basic TTS Management UI -└── indonesian-tts/ - └── index.blade.php # Indonesian TTS Management UI -storage/app/ -├── tts_scripts/ # Python Scripts & Audio Files -│ ├── tts_generator.py # pyttsx3 Script -│ ├── gtts_generator.py # gTTS Script -│ └── *.wav, *.mp3 # Generated Audio Files -└── tts/ # Indonesian TTS Models - └── models/ # Coqui TTS Model Files - ├── checkpoint.pth # Model Checkpoint - └── config.json # Model Configuration -``` - -## 🎵 Audio Format - -### **pyttsx3 (Offline)** - -- **Format**: WAV -- **Quality**: High -- **Size**: ~200KB per file -- **Speed**: Fast (offline) - -### **gTTS (Online)** - -- **Format**: MP3 -- **Quality**: Good -- **Size**: ~50KB per file -- **Speed**: Medium (requires internet) - -### **Indonesian TTS (Coqui TTS)** - -- **Format**: WAV -- **Quality**: Excellent (native Indonesian) -- **Size**: ~100KB per file -- **Speed**: Fast (offline, optimized) -- **Features**: Natural Indonesian pronunciation - -## 🔧 Konfigurasi - -### **Voice Settings** - -```php -// Di SimpleTTSService.php -private function getPythonScript() -{ - return '... engine.setProperty("rate", 150); // Kecepatan bicara - ... engine.setProperty("volume", 0.9); // Volume audio - ...'; -} -``` - -### **Text Format** - -```php -// Format default untuk nomor antrian -$text = "Nomor antrian {$queueNumber}"; -if (!empty($serviceName)) { - $text .= " untuk {$serviceName}"; -} -$text .= ". Silakan menuju ke loket yang tersedia."; -``` - -## 🚨 Troubleshooting - -### **Error: "Python not found"** - -```bash -# Pastikan Python ada di PATH -python --version - -# Atau gunakan python3 -python3 --version -``` - -### **Error: "pyttsx3 not found"** - -```bash -# Install ulang pyttsx3 -pip install --upgrade pyttsx3 -``` - -### **Error: "gTTS not found"** - -```bash -# Install ulang gTTS -pip install --upgrade gTTS -``` - -### **Error: "Coqui TTS not found"** - -```bash -# Install Coqui TTS -pip install TTS - -# Atau install dari source -pip install git+https://github.com/coqui-ai/TTS.git -``` - -### **Audio tidak ter-generate** - -1. Cek permissions folder storage -2. Cek log Laravel: `storage/logs/laravel.log` -3. Test Python script manual -4. Cek disk space - -### **Suara tidak terdengar** - -1. Cek volume sistem -2. Cek audio device -3. Test dengan file audio lain -4. Cek browser audio settings - -## 📊 Monitoring - -### **Status Check** - -- Python availability -- pyttsx3 status -- gTTS status -- **Indonesian TTS availability** -- **Coqui TTS installation status** -- **Model files existence** -- **Available speakers** -- Audio files count -- Storage usage - -### **Logs** - -```bash -# Laravel logs -tail -f storage/logs/laravel.log - -# Filter TTS logs -grep "TTS" storage/logs/laravel.log -``` - -## 🔄 Maintenance - -### **Auto Cleanup** - -- File audio lama otomatis dihapus setelah 1 jam -- Manual cleanup via admin panel -- Configurable cleanup interval - -### **Storage Management** - -- Monitor disk usage -- Regular cleanup old files -- Backup important audio files - -## 🌟 Best Practices - -### **Performance** - -- Gunakan pyttsx3 untuk offline TTS -- gTTS sebagai fallback -- Cleanup file audio secara berkala -- Monitor storage usage - -### **Security** - -- Validate input text -- Sanitize file paths -- Limit file access -- Regular security updates - -### **User Experience** - -- Clear error messages -- Loading indicators -- Audio preview -- Responsive design - -## 📞 Support - -### **Issues** - -- Cek troubleshooting section -- Review Laravel logs -- Test Python scripts manual -- Verify dependencies - -### **Enhancement** - -- Voice customization -- Multiple language support -- Audio quality options -- Integration with queue system -- **Indonesian TTS model optimization** -- **Custom speaker training** -- **Batch audio generation** -- **Real-time queue calling** - -## 🎉 Success Stories - -✅ **Puskesmas Jakarta Pusat**: Menggunakan TTS untuk 500+ pasien/hari -✅ **Puskesmas Surabaya**: Implementasi TTS mengurangi waktu tunggu 30% -✅ **Puskesmas Bandung**: TTS offline berfungsi sempurna tanpa internet -✅ **Puskesmas Medan**: Indonesian TTS meningkatkan akurasi pengucapan 95% -✅ **Puskesmas Makassar**: Coqui TTS model berjalan optimal untuk panggilan antrian - ---- - -**Dibuat dengan ❤️ untuk Puskesmas Indonesia** -**Versi**: 1.0.0 | **Update**: November 2024 diff --git a/app/Http/Controllers/AdminController.php b/app/Http/Controllers/AdminController.php index 5698291..5167b75 100644 --- a/app/Http/Controllers/AdminController.php +++ b/app/Http/Controllers/AdminController.php @@ -12,7 +12,7 @@ use App\Models\Antrian; use App\Models\Poli; use App\Models\RiwayatPanggilan; -use App\Services\TTSService; + use Barryvdh\DomPDF\Facade\Pdf; diff --git a/app/Http/Controllers/IndonesianTTSController.php b/app/Http/Controllers/IndonesianTTSController.php deleted file mode 100644 index 1a31be8..0000000 --- a/app/Http/Controllers/IndonesianTTSController.php +++ /dev/null @@ -1,154 +0,0 @@ -indonesianTTSService = new IndonesianTTSService(); - } - - /** - * Show Indonesian TTS settings page - */ - public function index(): View - { - return view('admin.indonesian-tts.index'); - } - - /** - * Generate TTS audio for queue call - */ - public function generateQueueCall(Request $request): JsonResponse - { - $request->validate([ - 'poli_name' => 'required|string', - 'queue_number' => 'required|string' - ]); - - $poliName = $request->input('poli_name'); - $queueNumber = $request->input('queue_number'); - - $result = $this->indonesianTTSService->generateQueueCall($poliName, $queueNumber); - - return response()->json($result); - } - - /** - * Create complete audio sequence for queue call - */ - public function createAudioSequence(Request $request): JsonResponse - { - $request->validate([ - 'poli_name' => 'required|string', - 'queue_number' => 'required|string' - ]); - - $poliName = $request->input('poli_name'); - $queueNumber = $request->input('queue_number'); - - $audioSequence = $this->indonesianTTSService->createCompleteAudioSequence($poliName, $queueNumber); - - return response()->json([ - 'success' => true, - 'audio_sequence' => $audioSequence, - 'poli_name' => $poliName, - 'queue_number' => $queueNumber - ]); - } - - /** - * Check Indonesian TTS status - */ - public function checkStatus(): JsonResponse - { - $status = [ - 'indonesian_tts_available' => $this->indonesianTTSService->isIndonesianTTSAvailable(), - 'coqui_tts_installed' => $this->indonesianTTSService->isCoquiTTSInstalled(), - 'model_files_exist' => file_exists(storage_path('app/tts/models/checkpoint.pth')) && - file_exists(storage_path('app/tts/models/config.json')), - 'available_speakers' => $this->indonesianTTSService->getAvailableSpeakers(), - 'installation_instructions' => $this->indonesianTTSService->installIndonesianTTS() - ]; - - return response()->json($status); - } - - /** - * Test Indonesian TTS with sample text - */ - public function testTTS(Request $request): JsonResponse - { - $request->validate([ - 'text' => 'required|string|max:500' - ]); - - $text = $request->input('text'); - - try { - $result = $this->indonesianTTSService->generateWithIndonesianTTS($text, 'test', 'Test'); - - return response()->json([ - 'success' => true, - 'message' => 'TTS test berhasil', - 'audio_url' => $result['audio_url'] ?? null, - 'tts_type' => $result['tts_type'] ?? 'fallback' - ]); - } catch (\Exception $e) { - return response()->json([ - 'success' => false, - 'message' => 'TTS test gagal: ' . $e->getMessage() - ]); - } - } - - /** - * Get installation instructions - */ - public function getInstallationInstructions(): JsonResponse - { - $instructions = $this->indonesianTTSService->installIndonesianTTS(); - - return response()->json([ - 'success' => true, - 'instructions' => $instructions - ]); - } - - /** - * Download Indonesian TTS model files - */ - public function downloadModelFiles(): JsonResponse - { - $modelUrls = [ - 'checkpoint.pth' => 'https://github.com/Wikidepia/indonesian-tts/releases/download/v1.2/checkpoint.pth', - 'config.json' => 'https://github.com/Wikidepia/indonesian-tts/releases/download/v1.2/config.json' - ]; - - $downloadInfo = [ - 'title' => 'Download Indonesian TTS Model Files', - 'description' => 'Download file model yang diperlukan untuk Indonesian TTS', - 'files' => $modelUrls, - 'manual_steps' => [ - '1. Kunjungi: https://github.com/Wikidepia/indonesian-tts/releases', - '2. Download file checkpoint.pth dan config.json', - '3. Buat folder: ' . storage_path('app/tts/models/'), - '4. Simpan file di folder tersebut', - '5. Pastikan permission file dapat dibaca oleh web server' - ] - ]; - - return response()->json([ - 'success' => true, - 'download_info' => $downloadInfo - ]); - } -} diff --git a/app/Http/Controllers/TTSController.php b/app/Http/Controllers/TTSController.php deleted file mode 100644 index 2012c68..0000000 --- a/app/Http/Controllers/TTSController.php +++ /dev/null @@ -1,417 +0,0 @@ -ttsService = $ttsService; - } - - /** - * Show TTS management page - */ - public function index() - { - return view('admin.tts.index'); - } - - /** - * Generate TTS untuk nomor antrian - */ - public function generateQueueTTS(Request $request) - { - $request->validate([ - 'queue_number' => 'required|string', - 'service_name' => 'nullable|string' - ]); - - try { - $queueNumber = $request->input('queue_number'); - $serviceName = $request->input('service_name', ''); - - // Generate TTS audio - $audioPath = $this->ttsService->generateQueueNumberTTS($queueNumber, $serviceName); - - if (!$audioPath) { - return response()->json([ - 'success' => false, - 'message' => 'Gagal generate TTS audio' - ], 500); - } - - // Get file info - $fileName = basename($audioPath); - $fileSize = filesize($audioPath); - $fileType = pathinfo($audioPath, PATHINFO_EXTENSION) === 'wav' ? 'audio/wav' : 'audio/mpeg'; - - return response()->json([ - 'success' => true, - 'message' => 'TTS audio berhasil di-generate', - 'data' => [ - 'file_name' => $fileName, - 'file_path' => $audioPath, - 'file_size' => $fileSize, - 'file_type' => $fileType, - 'queue_number' => $queueNumber, - 'service_name' => $serviceName - ] - ]); - - } catch (\Exception $e) { - return response()->json([ - 'success' => false, - 'message' => 'Error: ' . $e->getMessage() - ], 500); - } - } - - /** - * Play TTS audio - */ - public function playTTS(Request $request) - { - // Handle both GET and POST requests - $filePath = $request->input('file_path') ?? $request->input('file'); - - if (!$filePath) { - return response()->json([ - 'success' => false, - 'message' => 'File parameter tidak ditemukan' - ], 400); - } - - try { - // If only filename is provided, construct full path - if (!file_exists($filePath)) { - $scriptPath = storage_path('app/tts_scripts'); - $fullPath = $scriptPath . '/' . $filePath; - - if (!file_exists($fullPath)) { - return response()->json([ - 'success' => false, - 'message' => 'File audio tidak ditemukan: ' . $filePath - ], 404); - } - - $filePath = $fullPath; - } - - // Get file info - $fileName = basename($filePath); - $fileSize = filesize($filePath); - $fileType = pathinfo($filePath, PATHINFO_EXTENSION) === 'wav' ? 'audio/wav' : 'audio/mpeg'; - - // Return audio file for streaming - return response()->file($filePath, [ - 'Content-Type' => $fileType, - 'Content-Disposition' => 'inline; filename="' . $fileName . '"' - ]); - - } catch (\Exception $e) { - return response()->json([ - 'success' => false, - 'message' => 'Error: ' . $e->getMessage() - ], 500); - } - } - - /** - * Test TTS service - */ - public function testTTS() - { - try { - $result = $this->ttsService->testTTS(); - - return response()->json($result); - - } catch (\Exception $e) { - return response()->json([ - 'success' => false, - 'message' => 'Error: ' . $e->getMessage() - ], 500); - } - } - - /** - * Test TTS service for public access - */ - public function testPublicTTS() - { - try { - $result = $this->ttsService->testTTS(); - - if ($result['success']) { - // Return a simple HTML page with audio player - $audioUrl = '/tts/audio/' . basename($result['audio_path']); - - return response()->view('tts.test-public', [ - 'audioUrl' => $audioUrl, - 'result' => $result - ]); - } else { - return response()->json($result, 500); - } - - } catch (\Exception $e) { - return response()->json([ - 'success' => false, - 'message' => 'Error: ' . $e->getMessage() - ], 500); - } - } - - /** - * Get available TTS voices - */ - public function getVoices() - { - try { - // Buat script Python untuk mendapatkan daftar suara - $scriptContent = 'import pyttsx3 - -def get_voices(): - try: - engine = pyttsx3.init() - voices = engine.getProperty("voices") - - voice_list = [] - for i, voice in enumerate(voices): - voice_info = { - "id": voice.id, - "name": voice.name, - "languages": voice.languages, - "gender": voice.gender, - "age": voice.age - } - voice_list.append(voice_info) - - print("VOICES_START") - for voice in voice_list: - print(f"{voice[\'id\']}|{voice[\'name\']}|{voice[\'languages\']}|{voice[\'gender\']}|{voice[\'age\']}") - print("VOICES_END") - - except Exception as e: - print(f"Error: {str(e)}") - -if __name__ == "__main__": - get_voices()'; - - $scriptFile = storage_path('app/tts_scripts/get_voices.py'); - file_put_contents($scriptFile, $scriptContent); - - // Eksekusi script - $command = "python \"{$scriptFile}\" 2>&1"; - $output = shell_exec($command); - - // Parse output - $voices = []; - if (preg_match('/VOICES_START(.*?)VOICES_END/s', $output, $matches)) { - $lines = explode("\n", trim($matches[1])); - foreach ($lines as $line) { - if (trim($line)) { - $parts = explode('|', $line); - if (count($parts) >= 5) { - $voices[] = [ - 'id' => $parts[0], - 'name' => $parts[1], - 'languages' => $parts[2], - 'gender' => $parts[3], - 'age' => $parts[4] - ]; - } - } - } - } - - return response()->json([ - 'success' => true, - 'voices' => $voices, - 'total' => count($voices) - ]); - - } catch (\Exception $e) { - return response()->json([ - 'success' => false, - 'message' => 'Error: ' . $e->getMessage() - ], 500); - } - } - - /** - * Clean up old TTS files - */ - public function cleanupFiles(Request $request) - { - try { - $maxAge = $request->input('max_age', 3600); // Default 1 jam - $deletedCount = $this->ttsService->cleanupOldFiles($maxAge); - - return response()->json([ - 'success' => true, - 'message' => "Berhasil menghapus {$deletedCount} file lama", - 'deleted_count' => $deletedCount - ]); - - } catch (\Exception $e) { - return response()->json([ - 'success' => false, - 'message' => 'Error: ' . $e->getMessage() - ], 500); - } - } - - /** - * Get TTS status - */ - public function getStatus() - { - try { - $status = [ - 'python' => false, - 'pyttsx3' => false, - 'gtts' => false, - 'audio_files' => 0, - 'last_cleanup' => null - ]; - - // Check Python - $pythonCheck = shell_exec('python --version 2>&1'); - $status['python'] = !empty($pythonCheck) && strpos($pythonCheck, 'Python') !== false; - - // Check pyttsx3 - if ($status['python']) { - $pyttsx3Check = shell_exec('python -c "import pyttsx3; print(\'OK\')" 2>&1'); - $status['pyttsx3'] = $pyttsx3Check === 'OK'; - } - - // Check gTTS - if ($status['python']) { - $gttsCheck = shell_exec('python -c "from gtts import gTTS; print(\'OK\')" 2>&1'); - $status['gtts'] = $gttsCheck === 'OK'; - } - - // Check audio files - $scriptPath = storage_path('app/tts_scripts'); - if (file_exists($scriptPath)) { - $audioFiles = glob($scriptPath . '/*.{wav,mp3}', GLOB_BRACE); - $status['audio_files'] = count($audioFiles); - } - - // Get last cleanup time - $status['last_cleanup'] = now()->subHours(1)->format('Y-m-d H:i:s'); - - return response()->json([ - 'success' => true, - 'status' => $status - ]); - - } catch (\Exception $e) { - Log::error('TTS Status Error: ' . $e->getMessage()); - return response()->json([ - 'success' => false, - 'message' => 'Error checking TTS status: ' . $e->getMessage() - ], 500); - } - } - - /** - * Play TTS audio for public access (display page) - */ - public function playPublicAudio($filename) - { - try { - // Validate filename - if (empty($filename) || !preg_match('/^[a-zA-Z0-9_-]+\.(wav|mp3)$/', $filename)) { - return response()->json([ - 'success' => false, - 'message' => 'Invalid filename' - ], 400); - } - - $scriptPath = storage_path('app/tts_scripts'); - $filePath = $scriptPath . '/' . $filename; - - if (!file_exists($filePath)) { - return response()->json([ - 'success' => false, - 'message' => 'Audio file not found: ' . $filename - ], 404); - } - - // Get file info - $fileSize = filesize($filePath); - $fileType = pathinfo($filePath, PATHINFO_EXTENSION) === 'wav' ? 'audio/wav' : 'audio/mpeg'; - - // Return audio file for streaming - return response()->file($filePath, [ - 'Content-Type' => $fileType, - 'Content-Disposition' => 'inline; filename="' . $filename . '"', - 'Cache-Control' => 'public, max-age=3600' // Cache for 1 hour - ]); - - } catch (\Exception $e) { - Log::error('Public TTS Audio Error: ' . $e->getMessage()); - return response()->json([ - 'success' => false, - 'message' => 'Error playing audio: ' . $e->getMessage() - ], 500); - } - } - - /** - * Play audio sequence for display page (legacy support) - */ - public function playAudioSequence(Request $request) - { - try { - $request->validate([ - 'poli_name' => 'required|string', - 'queue_number' => 'required|string' - ]); - - $poliName = $request->input('poli_name'); - $queueNumber = $request->input('queue_number'); - - // Generate TTS audio using our new service - $audioPath = $this->ttsService->generateQueueNumberTTS($queueNumber, $poliName); - - if (!$audioPath || !file_exists($audioPath)) { - return response()->json([ - 'success' => false, - 'message' => 'Failed to generate TTS audio' - ], 500); - } - - // Return audio sequence format that display page expects - return response()->json([ - 'success' => true, - 'audio_sequence' => [ - [ - 'type' => 'audio_file', - 'url' => '/tts/audio/' . basename($audioPath), - 'duration' => 5000, // 5 seconds estimated - 'text' => "Nomor antrian {$queueNumber} untuk {$poliName}. Silakan menuju ke loket yang tersedia." - ] - ] - ]); - - } catch (\Exception $e) { - Log::error('TTS Audio Sequence Error: ' . $e->getMessage()); - return response()->json([ - 'success' => false, - 'message' => 'Error generating audio sequence: ' . $e->getMessage() - ], 500); - } - } -} diff --git a/app/Services/IndonesianTTSService.php b/app/Services/IndonesianTTSService.php deleted file mode 100644 index fb89530..0000000 --- a/app/Services/IndonesianTTSService.php +++ /dev/null @@ -1,385 +0,0 @@ -modelPath = storage_path('app/tts/models/checkpoint.pth'); - $this->configPath = storage_path('app/tts/models/config.json'); - $this->g2pPath = storage_path('app/tts/g2p-id'); - $this->outputPath = storage_path('app/public/audio/queue_calls'); - - // Buat direktori jika belum ada - if (!file_exists($this->outputPath)) { - mkdir($this->outputPath, 0755, true); - } - } - - /** - * Generate TTS audio using Indonesian TTS model - */ - public function generateQueueCall($poliName, $queueNumber) - { - try { - // Convert queue number to Indonesian pronunciation - $indonesianQueueNumber = $this->convertQueueNumberToIndonesian($queueNumber); - - // Create the text to be spoken - $text = "Nomor antrian {$indonesianQueueNumber}, silakan menuju ke {$poliName}"; - - // Check if Indonesian TTS model is available - if ($this->isIndonesianTTSAvailable()) { - return $this->generateWithIndonesianTTS($text, $queueNumber, $poliName); - } else { - // Fallback to Google TTS or browser TTS - return $this->generateWithFallbackTTS($text); - } - - } catch (\Exception $e) { - return [ - 'success' => false, - 'message' => 'Error generating Indonesian TTS: ' . $e->getMessage() - ]; - } - } - - /** - * Generate TTS using Indonesian TTS model - */ - public function generateWithIndonesianTTS($text, $queueNumber, $poliName) - { - try { - // Generate filename - $filename = "indonesian_tts_{$queueNumber}_{$poliName}_" . time() . ".wav"; - $filepath = $this->outputPath . '/' . $filename; - - // Prepare text for TTS (convert to phonemes if g2p-id is available) - $phonemeText = $this->convertToPhonemes($text); - - // Run Coqui TTS command - $command = $this->buildTTSCmd($phonemeText, $filepath); - - $result = Process::run($command); - - if ($result->successful()) { - return [ - 'success' => true, - 'audio_url' => asset('storage/audio/queue_calls/' . $filename), - 'filename' => $filename, - 'tts_type' => 'indonesian_tts' - ]; - } else { - throw new \Exception('TTS generation failed: ' . $result->errorOutput()); - } - - } catch (\Exception $e) { - // Fallback to other TTS methods - return $this->generateWithFallbackTTS($text); - } - } - - /** - * Build TTS command for Coqui TTS - */ - private function buildTTSCmd($text, $outputPath) - { - $speaker = 'wibowo'; // Default speaker, can be made configurable - - return [ - 'tts', - '--text', - $text, - '--model_path', - $this->modelPath, - '--config_path', - $this->configPath, - '--speaker_idx', - $speaker, - '--out_path', - $outputPath - ]; - } - - /** - * Convert text to phonemes using g2p-id - */ - private function convertToPhonemes($text) - { - // If g2p-id is available, use it to convert to phonemes - if (file_exists($this->g2pPath)) { - try { - $result = Process::run([$this->g2pPath, $text]); - if ($result->successful()) { - return trim($result->output()); - } - } catch (\Exception $e) { - // If g2p conversion fails, return original text - return $text; - } - } - - // Return original text if g2p-id is not available - return $text; - } - - /** - * Check if Indonesian TTS model is available - */ - public function isIndonesianTTSAvailable() - { - return file_exists($this->modelPath) && - file_exists($this->configPath) && - $this->isCoquiTTSInstalled(); - } - - /** - * Check if Coqui TTS is installed - */ - public function isCoquiTTSInstalled() - { - try { - $result = Process::run(['tts', '--version']); - return $result->successful(); - } catch (\Exception $e) { - return false; - } - } - - /** - * Fallback to Google TTS or browser TTS - */ - private function generateWithFallbackTTS($text) - { - // Use existing TTSService as fallback - $ttsService = new TTSService(); - - // Extract poli name and queue number from text for fallback - preg_match('/Nomor antrian (.+), silakan menuju ke ruang (.+)/', $text, $matches); - $queueNumber = $matches[1] ?? ''; - $poliName = $matches[2] ?? ''; - - return $ttsService->generateQueueCall($poliName, $queueNumber); - } - - /** - * Convert alphanumeric queue number to Indonesian pronunciation - * Example: "U5" becomes "U Lima", "A10" becomes "A Sepuluh" - */ - private function convertQueueNumberToIndonesian($queueNumber) - { - // Indonesian number words - $indonesianNumbers = [ - '0' => 'Nol', - '1' => 'Satu', - '2' => 'Dua', - '3' => 'Tiga', - '4' => 'Empat', - '5' => 'Lima', - '6' => 'Enam', - '7' => 'Tujuh', - '8' => 'Delapan', - '9' => 'Sembilan', - '10' => 'Sepuluh', - '11' => 'Sebelas', - '12' => 'Dua Belas', - '13' => 'Tiga Belas', - '14' => 'Empat Belas', - '15' => 'Lima Belas', - '16' => 'Enam Belas', - '17' => 'Tujuh Belas', - '18' => 'Delapan Belas', - '19' => 'Sembilan Belas', - '20' => 'Dua Puluh', - '30' => 'Tiga Puluh', - '40' => 'Empat Puluh', - '50' => 'Lima Puluh', - '60' => 'Enam Puluh', - '70' => 'Tujuh Puluh', - '80' => 'Delapan Puluh', - '90' => 'Sembilan Puluh', - '100' => 'Seratus' - ]; - - // If it's a pure number, convert it - if (is_numeric($queueNumber)) { - $number = (int) $queueNumber; - if (isset($indonesianNumbers[$number])) { - return $indonesianNumbers[$number]; - } else { - // For numbers > 100, build the pronunciation - if ($number < 100) { - $tens = floor($number / 10) * 10; - $ones = $number % 10; - if ($ones == 0) { - return $indonesianNumbers[$tens]; - } else { - return $indonesianNumbers[$tens] . ' ' . $indonesianNumbers[$ones]; - } - } else { - return $number; // Fallback for large numbers - } - } - } - - // For alphanumeric (like "U5", "A10"), convert the numeric part - $letters = ''; - $numbers = ''; - - // Split into letters and numbers - for ($i = 0; $i < strlen($queueNumber); $i++) { - $char = $queueNumber[$i]; - if (is_numeric($char)) { - $numbers .= $char; - } else { - $letters .= $char; - } - } - - // If we have both letters and numbers - if ($letters && $numbers) { - $numberValue = (int) $numbers; - if (isset($indonesianNumbers[$numberValue])) { - return $letters . ' ' . $indonesianNumbers[$numberValue]; - } else { - // For numbers > 100, build the pronunciation - if ($numberValue < 100) { - $tens = floor($numberValue / 10) * 10; - $ones = $numberValue % 10; - if ($ones == 0) { - return $letters . ' ' . $indonesianNumbers[$tens]; - } else { - return $letters . ' ' . $indonesianNumbers[$tens] . ' ' . $indonesianNumbers[$ones]; - } - } else { - return $queueNumber; // Fallback for large numbers - } - } - } - - // If no conversion needed, return as is - return $queueNumber; - } - - /** - * Create complete audio sequence for queue call - */ - public function createCompleteAudioSequence($poliName, $queueNumber) - { - $audioFiles = []; - - // 1. Attention sound (4 seconds - actual file duration) - $attentionSound = asset('assets/music/call-to-attention-123107.mp3'); - $audioFiles[] = [ - 'type' => 'attention', - 'url' => $attentionSound, - 'duration' => 4000 // 4 seconds - actual file duration - ]; - - // 2. TTS for poli name and number (no final attention sound) - $ttsResult = $this->generateQueueCall($poliName, $queueNumber); - if ($ttsResult['success']) { - if (isset($ttsResult['use_browser_tts']) && $ttsResult['use_browser_tts']) { - // Use browser TTS - $audioFiles[] = [ - 'type' => 'browser_tts', - 'text' => $ttsResult['text'], - 'duration' => 8000 // 8 seconds - longer for natural speech - ]; - } else { - // Use generated audio file - $audioFiles[] = [ - 'type' => 'tts', - 'url' => $ttsResult['audio_url'], - 'duration' => 8000 // 8 seconds - longer for natural speech - ]; - } - } - - return $audioFiles; - } - - /** - * Get available speakers from Indonesian TTS model - */ - public function getAvailableSpeakers() - { - if (!$this->isIndonesianTTSAvailable()) { - return []; - } - - try { - $result = Process::run([ - 'tts', - '--model_path', - $this->modelPath, - '--config_path', - $this->configPath, - '--list_speaker_idxs' - ]); - - if ($result->successful()) { - $output = $result->output(); - // Parse speaker list from output - $speakers = []; - $lines = explode("\n", $output); - foreach ($lines as $line) { - if (preg_match('/^(\d+):\s*(.+)$/', $line, $matches)) { - $speakers[$matches[1]] = trim($matches[2]); - } - } - return $speakers; - } - } catch (\Exception $e) { - // Return default speakers if listing fails - return [ - 'wibowo' => 'Wibowo (Male)', - 'ardi' => 'Ardi (Male)', - 'gadis' => 'Gadis (Female)' - ]; - } - - return []; - } - - /** - * Install Indonesian TTS model - */ - public function installIndonesianTTS() - { - $instructions = [ - 'title' => 'Instalasi Indonesian TTS', - 'steps' => [ - '1. Install Coqui TTS:', - ' pip install TTS', - '', - '2. Download model dari GitHub:', - ' - Kunjungi: https://github.com/Wikidepia/indonesian-tts/releases', - ' - Download file checkpoint.pth dan config.json', - ' - Simpan di: ' . storage_path('app/tts/models/'), - '', - '3. Install g2p-id (opsional):', - ' pip install g2p-id', - '', - '4. Test instalasi:', - ' tts --version', - '', - '5. Test model:', - ' tts --text "Halo dunia" --model_path ' . $this->modelPath . ' --config_path ' . $this->configPath . ' --out_path test.wav' - ] - ]; - - return $instructions; - } -} diff --git a/app/Services/SimpleTTSService.php b/app/Services/SimpleTTSService.php deleted file mode 100644 index 8cb9e31..0000000 --- a/app/Services/SimpleTTSService.php +++ /dev/null @@ -1,283 +0,0 @@ -pythonPath = 'python'; // atau 'python3' tergantung sistem - $this->scriptPath = storage_path('app/tts_scripts'); - - // Buat direktori jika belum ada - if (!file_exists($this->scriptPath)) { - mkdir($this->scriptPath, 0755, true); - } - } - - /** - * Generate TTS untuk nomor antrian - */ - public function generateQueueNumberTTS($queueNumber, $serviceName = '') - { - try { - // Text yang akan diucapkan - $text = $this->formatQueueText($queueNumber, $serviceName); - - // Generate audio menggunakan Python script - $audioPath = $this->generateAudioWithPython($text); - - if ($audioPath && file_exists($audioPath)) { - return $audioPath; - } - - // Fallback ke gTTS jika Python gagal - return $this->generateAudioWithGTTS($text); - - } catch (\Exception $e) { - Log::error('TTS Error: ' . $e->getMessage()); - return null; - } - } - - /** - * Format text untuk nomor antrian - */ - private function formatQueueText($queueNumber, $serviceName) - { - $text = "Nomor antrian {$queueNumber}"; - - if (!empty($serviceName)) { - $text .= " untuk {$serviceName}"; - } - - $text .= ". Silakan menuju ke loket yang tersedia."; - - return $text; - } - - /** - * Generate audio menggunakan Python script dengan pyttsx3 - */ - private function generateAudioWithPython($text) - { - try { - $scriptContent = $this->getPythonScript(); - $scriptFile = $this->scriptPath . '/tts_generator.py'; - - // Tulis script ke file - file_put_contents($scriptFile, $scriptContent); - - // Generate nama file audio - $audioFileName = 'queue_' . time() . '_' . uniqid() . '.wav'; - $audioPath = $this->scriptPath . '/' . $audioFileName; - - // Eksekusi Python script - $command = "{$this->pythonPath} \"{$scriptFile}\" \"{$text}\" \"{$audioPath}\""; - - $output = shell_exec($command . ' 2>&1'); - - if (file_exists($audioPath)) { - Log::info('TTS Audio generated successfully with Python: ' . $audioPath); - return $audioPath; - } - - Log::warning('Python TTS failed: ' . $output); - return null; - - } catch (\Exception $e) { - Log::error('Python TTS Error: ' . $e->getMessage()); - return null; - } - } - - /** - * Generate audio menggunakan gTTS (fallback) - */ - private function generateAudioWithGTTS($text) - { - try { - $scriptContent = $this->getGTTScript(); - $scriptFile = $this->scriptPath . '/gtts_generator.py'; - - // Tulis script ke file - file_put_contents($scriptFile, $scriptContent); - - // Generate nama file audio - $audioFileName = 'queue_gtts_' . time() . '_' . uniqid() . '.mp3'; - $audioPath = $this->scriptPath . '/' . $audioFileName; - - // Eksekusi Python script - $command = "{$this->pythonPath} \"{$scriptFile}\" \"{$text}\" \"{$audioPath}\""; - - $output = shell_exec($command . ' 2>&1'); - - if (file_exists($audioPath)) { - Log::info('TTS Audio generated successfully with gTTS: ' . $audioPath); - return $audioPath; - } - - Log::warning('gTTS failed: ' . $output); - return null; - - } catch (\Exception $e) { - Log::error('gTTS Error: ' . $e->getMessage()); - return null; - } - } - - /** - * Python script untuk pyttsx3 - */ - private function getPythonScript() - { - return 'import sys -import pyttsx3 -import os - -def generate_tts(text, output_path): - try: - # Inisialisasi TTS engine - engine = pyttsx3.init() - - # Set properties untuk suara Indonesia (jika tersedia) - voices = engine.getProperty("voices") - - # Cari suara yang cocok untuk bahasa Indonesia - indonesian_voice = None - for voice in voices: - if "indonesia" in voice.name.lower() or "id" in voice.id.lower(): - indonesian_voice = voice - break - - if indonesian_voice: - engine.setProperty("voice", indonesian_voice.id) - - # Set rate dan volume - engine.setProperty("rate", 150) # Kecepatan bicara - engine.setProperty("volume", 0.9) # Volume - - # Generate audio - engine.save_to_file(text, output_path) - engine.runAndWait() - - return True - - except Exception as e: - print(f"Error: {str(e)}") - return False - -if __name__ == "__main__": - if len(sys.argv) != 3: - print("Usage: python script.py ") - sys.exit(1) - - text = sys.argv[1] - output_path = sys.argv[2] - - success = generate_tts(text, output_path) - if success: - print(f"Audio generated successfully: {output_path}") - else: - print("Failed to generate audio") - sys.exit(1)'; - } - - /** - * Python script untuk gTTS - */ - private function getGTTScript() - { - return 'import sys -import os -from gtts import gTTS - -def generate_gtts(text, output_path): - try: - # Generate TTS dengan bahasa Indonesia - tts = gTTS(text=text, lang="id", slow=False) - - # Simpan ke file - tts.save(output_path) - - return True - - except Exception as e: - print(f"Error: {str(e)}") - return False - -if __name__ == "__main__": - if len(sys.argv) != 3: - print("Usage: python script.py ") - sys.exit(1) - - text = sys.argv[1] - output_path = sys.argv[2] - - success = generate_gtts(text, output_path) - if success: - print(f"Audio generated successfully: {output_path}") - else: - print("Failed to generate audio") - sys.exit(1)'; - } - - /** - * Test TTS service - */ - public function testTTS() - { - $testText = "Ini adalah test Text to Speech untuk sistem antrian Puskesmas."; - $audioPath = $this->generateQueueNumberTTS("001", "Poli Umum"); - - if ($audioPath) { - return [ - 'success' => true, - 'message' => 'TTS berhasil di-generate', - 'audio_path' => $audioPath, - 'file_size' => filesize($audioPath) . ' bytes' - ]; - } - - return [ - 'success' => false, - 'message' => 'TTS gagal di-generate' - ]; - } - - /** - * Clean up old audio files - */ - public function cleanupOldFiles($maxAge = 3600) // 1 jam - { - try { - $files = glob($this->scriptPath . '/*.{wav,mp3}', GLOB_BRACE); - $currentTime = time(); - $deletedCount = 0; - - foreach ($files as $file) { - if (is_file($file)) { - $fileAge = $currentTime - filemtime($file); - if ($fileAge > $maxAge) { - unlink($file); - $deletedCount++; - } - } - } - - Log::info("Cleaned up {$deletedCount} old TTS audio files"); - return $deletedCount; - - } catch (\Exception $e) { - Log::error('Cleanup Error: ' . $e->getMessage()); - return 0; - } - } -} diff --git a/app/Services/TTSService.php b/app/Services/TTSService.php deleted file mode 100644 index f9745ff..0000000 --- a/app/Services/TTSService.php +++ /dev/null @@ -1,243 +0,0 @@ -apiKey = config('services.google.tts_api_key'); - } - - /** - * Convert alphanumeric queue number to Indonesian pronunciation - * Example: "U5" becomes "U Lima", "A10" becomes "A Sepuluh" - */ - private function convertQueueNumberToIndonesian($queueNumber) - { - // Indonesian number words - $indonesianNumbers = [ - '0' => 'Nol', - '1' => 'Satu', - '2' => 'Dua', - '3' => 'Tiga', - '4' => 'Empat', - '5' => 'Lima', - '6' => 'Enam', - '7' => 'Tujuh', - '8' => 'Delapan', - '9' => 'Sembilan', - '10' => 'Sepuluh', - '11' => 'Sebelas', - '12' => 'Dua Belas', - '13' => 'Tiga Belas', - '14' => 'Empat Belas', - '15' => 'Lima Belas', - '16' => 'Enam Belas', - '17' => 'Tujuh Belas', - '18' => 'Delapan Belas', - '19' => 'Sembilan Belas', - '20' => 'Dua Puluh', - '30' => 'Tiga Puluh', - '40' => 'Empat Puluh', - '50' => 'Lima Puluh', - '60' => 'Enam Puluh', - '70' => 'Tujuh Puluh', - '80' => 'Delapan Puluh', - '90' => 'Sembilan Puluh', - '100' => 'Seratus' - ]; - - // If it's a pure number, convert it - if (is_numeric($queueNumber)) { - $number = (int) $queueNumber; - if (isset($indonesianNumbers[$number])) { - return $indonesianNumbers[$number]; - } else { - // For numbers > 100, build the pronunciation - if ($number < 100) { - $tens = floor($number / 10) * 10; - $ones = $number % 10; - if ($ones == 0) { - return $indonesianNumbers[$tens]; - } else { - return $indonesianNumbers[$tens] . ' ' . $indonesianNumbers[$ones]; - } - } else { - return $number; // Fallback for large numbers - } - } - } - - // For alphanumeric (like "U5", "A10"), convert the numeric part - $letters = ''; - $numbers = ''; - - // Split into letters and numbers - for ($i = 0; $i < strlen($queueNumber); $i++) { - $char = $queueNumber[$i]; - if (is_numeric($char)) { - $numbers .= $char; - } else { - $letters .= $char; - } - } - - // If we have both letters and numbers - if ($letters && $numbers) { - $numberValue = (int) $numbers; - if (isset($indonesianNumbers[$numberValue])) { - return $letters . ' ' . $indonesianNumbers[$numberValue]; - } else { - // For numbers > 100, build the pronunciation - if ($numberValue < 100) { - $tens = floor($numberValue / 10) * 10; - $ones = $numberValue % 10; - if ($ones == 0) { - return $letters . ' ' . $indonesianNumbers[$tens]; - } else { - return $letters . ' ' . $indonesianNumbers[$tens] . ' ' . $indonesianNumbers[$ones]; - } - } else { - return $queueNumber; // Fallback for large numbers - } - } - } - - // If no conversion needed, return as is - return $queueNumber; - } - - /** - * Generate TTS audio for queue call - */ - public function generateQueueCall($poliName, $queueNumber) - { - try { - // Convert queue number to Indonesian pronunciation - $indonesianQueueNumber = $this->convertQueueNumberToIndonesian($queueNumber); - - // Create the text to be spoken - $text = "antrian selanjutnya poli {$poliName} nomor {$indonesianQueueNumber}"; - - // Generate TTS audio - $audioContent = $this->synthesizeSpeech($text); - - if ($audioContent) { - // Save audio file - $filename = "queue_call_{$queueNumber}_{$poliName}_" . time() . ".mp3"; - $filepath = "audio/queue_calls/" . $filename; - - Storage::disk('public')->put($filepath, base64_decode($audioContent)); - - return [ - 'success' => true, - 'audio_url' => asset('storage/' . $filepath), - 'filename' => $filename - ]; - } - - // Fallback: return browser TTS info - return [ - 'success' => true, - 'audio_url' => null, - 'filename' => null, - 'text' => $text, - 'use_browser_tts' => true - ]; - - } catch (\Exception $e) { - return [ - 'success' => false, - 'message' => 'Error generating TTS: ' . $e->getMessage() - ]; - } - } - - /** - * Synthesize speech using Google TTS API - */ - private function synthesizeSpeech($text) - { - if (!$this->apiKey) { - // Fallback: use browser's built-in TTS - return null; - } - - $requestData = [ - 'input' => [ - 'text' => $text - ], - 'voice' => [ - 'languageCode' => 'id-ID', - 'name' => 'id-ID-Wavenet-A', // More natural and fluent Indonesian female voice - 'ssmlGender' => 'FEMALE' - ], - 'audioConfig' => [ - 'audioEncoding' => 'MP3', - 'speakingRate' => 0.85, // Slightly faster for more natural flow - 'pitch' => 0, - 'volumeGainDb' => 0 - ] - ]; - - try { - $response = Http::withHeaders([ - 'Content-Type' => 'application/json', - ])->post($this->baseUrl . '?key=' . $this->apiKey, $requestData); - - if ($response->successful()) { - $data = $response->json(); - return $data['audioContent'] ?? null; - } - - return null; - } catch (\Exception $e) { - return null; - } - } - - /** - * Create complete audio sequence for queue call - */ - public function createCompleteAudioSequence($poliName, $queueNumber) - { - $audioFiles = []; - - // 1. Attention sound (4 seconds - actual file duration) - $attentionSound = asset('assets/music/call-to-attention-123107.mp3'); - $audioFiles[] = [ - 'type' => 'attention', - 'url' => $attentionSound, - 'duration' => 4000 // 4 seconds - actual file duration - ]; - - // 2. TTS for poli name and number (no final attention sound) - $ttsResult = $this->generateQueueCall($poliName, $queueNumber); - if ($ttsResult['success']) { - if ($ttsResult['use_browser_tts']) { - // Use browser TTS - $audioFiles[] = [ - 'type' => 'browser_tts', - 'text' => $ttsResult['text'], - 'duration' => 8000 // 8 seconds - longer for natural speech - ]; - } else { - // Use generated audio file - $audioFiles[] = [ - 'type' => 'tts', - 'url' => $ttsResult['audio_url'], - 'duration' => 8000 // 8 seconds - longer for natural speech - ]; - } - } - - return $audioFiles; - } -} diff --git a/composer.lock b/composer.lock index 2f24f1d..6a7614e 100644 --- a/composer.lock +++ b/composer.lock @@ -289,33 +289,32 @@ }, { "name": "doctrine/inflector", - "version": "2.0.10", + "version": "2.1.0", "source": { "type": "git", "url": "https://github.com/doctrine/inflector.git", - "reference": "5817d0659c5b50c9b950feb9af7b9668e2c436bc" + "reference": "6d6c96277ea252fc1304627204c3d5e6e15faa3b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/inflector/zipball/5817d0659c5b50c9b950feb9af7b9668e2c436bc", - "reference": "5817d0659c5b50c9b950feb9af7b9668e2c436bc", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/6d6c96277ea252fc1304627204c3d5e6e15faa3b", + "reference": "6d6c96277ea252fc1304627204c3d5e6e15faa3b", "shasum": "" }, "require": { "php": "^7.2 || ^8.0" }, "require-dev": { - "doctrine/coding-standard": "^11.0", - "phpstan/phpstan": "^1.8", - "phpstan/phpstan-phpunit": "^1.1", - "phpstan/phpstan-strict-rules": "^1.3", - "phpunit/phpunit": "^8.5 || ^9.5", - "vimeo/psalm": "^4.25 || ^5.4" + "doctrine/coding-standard": "^12.0 || ^13.0", + "phpstan/phpstan": "^1.12 || ^2.0", + "phpstan/phpstan-phpunit": "^1.4 || ^2.0", + "phpstan/phpstan-strict-rules": "^1.6 || ^2.0", + "phpunit/phpunit": "^8.5 || ^12.2" }, "type": "library", "autoload": { "psr-4": { - "Doctrine\\Inflector\\": "lib/Doctrine/Inflector" + "Doctrine\\Inflector\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -360,7 +359,7 @@ ], "support": { "issues": "https://github.com/doctrine/inflector/issues", - "source": "https://github.com/doctrine/inflector/tree/2.0.10" + "source": "https://github.com/doctrine/inflector/tree/2.1.0" }, "funding": [ { @@ -376,7 +375,7 @@ "type": "tidelift" } ], - "time": "2024-02-18T20:23:39+00:00" + "time": "2025-08-10T19:31:58+00:00" }, { "name": "doctrine/lexer", @@ -1288,16 +1287,16 @@ }, { "name": "laravel/framework", - "version": "v12.22.0", + "version": "v12.22.1", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "6ab00c913ef6ec6fad0bd506f7452c0bb9e792c3" + "reference": "d33ee45184126f32f593d4b809a846ed88a1dc43" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/6ab00c913ef6ec6fad0bd506f7452c0bb9e792c3", - "reference": "6ab00c913ef6ec6fad0bd506f7452c0bb9e792c3", + "url": "https://api.github.com/repos/laravel/framework/zipball/d33ee45184126f32f593d4b809a846ed88a1dc43", + "reference": "d33ee45184126f32f593d4b809a846ed88a1dc43", "shasum": "" }, "require": { @@ -1499,7 +1498,7 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2025-08-07T13:49:53+00:00" + "time": "2025-08-08T13:58:03+00:00" }, { "name": "laravel/prompts", @@ -6470,16 +6469,16 @@ }, { "name": "filp/whoops", - "version": "2.18.3", + "version": "2.18.4", "source": { "type": "git", "url": "https://github.com/filp/whoops.git", - "reference": "59a123a3d459c5a23055802237cb317f609867e5" + "reference": "d2102955e48b9fd9ab24280a7ad12ed552752c4d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/59a123a3d459c5a23055802237cb317f609867e5", - "reference": "59a123a3d459c5a23055802237cb317f609867e5", + "url": "https://api.github.com/repos/filp/whoops/zipball/d2102955e48b9fd9ab24280a7ad12ed552752c4d", + "reference": "d2102955e48b9fd9ab24280a7ad12ed552752c4d", "shasum": "" }, "require": { @@ -6529,7 +6528,7 @@ ], "support": { "issues": "https://github.com/filp/whoops/issues", - "source": "https://github.com/filp/whoops/tree/2.18.3" + "source": "https://github.com/filp/whoops/tree/2.18.4" }, "funding": [ { @@ -6537,7 +6536,7 @@ "type": "github" } ], - "time": "2025-06-16T00:02:10+00:00" + "time": "2025-08-08T12:00:00+00:00" }, { "name": "hamcrest/hamcrest-php", @@ -7498,16 +7497,16 @@ }, { "name": "phpunit/phpunit", - "version": "11.5.28", + "version": "11.5.31", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "93f30aa3889e785ac63493d4976df0ae9fdecb60" + "reference": "fc44414e0779e94640663b809557b0b599548260" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/93f30aa3889e785ac63493d4976df0ae9fdecb60", - "reference": "93f30aa3889e785ac63493d4976df0ae9fdecb60", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/fc44414e0779e94640663b809557b0b599548260", + "reference": "fc44414e0779e94640663b809557b0b599548260", "shasum": "" }, "require": { @@ -7517,7 +7516,7 @@ "ext-mbstring": "*", "ext-xml": "*", "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.13.3", + "myclabs/deep-copy": "^1.13.4", "phar-io/manifest": "^2.0.4", "phar-io/version": "^3.2.1", "php": ">=8.2", @@ -7528,13 +7527,13 @@ "phpunit/php-timer": "^7.0.1", "sebastian/cli-parser": "^3.0.2", "sebastian/code-unit": "^3.0.3", - "sebastian/comparator": "^6.3.1", + "sebastian/comparator": "^6.3.2", "sebastian/diff": "^6.0.2", "sebastian/environment": "^7.2.1", "sebastian/exporter": "^6.3.0", "sebastian/global-state": "^7.0.2", "sebastian/object-enumerator": "^6.0.1", - "sebastian/type": "^5.1.2", + "sebastian/type": "^5.1.3", "sebastian/version": "^5.0.2", "staabm/side-effects-detector": "^1.0.5" }, @@ -7579,7 +7578,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.28" + "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.31" }, "funding": [ { @@ -7603,7 +7602,7 @@ "type": "tidelift" } ], - "time": "2025-07-31T07:10:28+00:00" + "time": "2025-08-11T05:27:39+00:00" }, { "name": "sebastian/cli-parser", @@ -7777,16 +7776,16 @@ }, { "name": "sebastian/comparator", - "version": "6.3.1", + "version": "6.3.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "24b8fbc2c8e201bb1308e7b05148d6ab393b6959" + "reference": "85c77556683e6eee4323e4c5468641ca0237e2e8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/24b8fbc2c8e201bb1308e7b05148d6ab393b6959", - "reference": "24b8fbc2c8e201bb1308e7b05148d6ab393b6959", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/85c77556683e6eee4323e4c5468641ca0237e2e8", + "reference": "85c77556683e6eee4323e4c5468641ca0237e2e8", "shasum": "" }, "require": { @@ -7845,15 +7844,27 @@ "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", "security": "https://github.com/sebastianbergmann/comparator/security/policy", - "source": "https://github.com/sebastianbergmann/comparator/tree/6.3.1" + "source": "https://github.com/sebastianbergmann/comparator/tree/6.3.2" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator", + "type": "tidelift" } ], - "time": "2025-03-07T06:57:01+00:00" + "time": "2025-08-10T08:07:46+00:00" }, { "name": "sebastian/complexity", @@ -8434,16 +8445,16 @@ }, { "name": "sebastian/type", - "version": "5.1.2", + "version": "5.1.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", - "reference": "a8a7e30534b0eb0c77cd9d07e82de1a114389f5e" + "reference": "f77d2d4e78738c98d9a68d2596fe5e8fa380f449" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/a8a7e30534b0eb0c77cd9d07e82de1a114389f5e", - "reference": "a8a7e30534b0eb0c77cd9d07e82de1a114389f5e", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/f77d2d4e78738c98d9a68d2596fe5e8fa380f449", + "reference": "f77d2d4e78738c98d9a68d2596fe5e8fa380f449", "shasum": "" }, "require": { @@ -8479,15 +8490,27 @@ "support": { "issues": "https://github.com/sebastianbergmann/type/issues", "security": "https://github.com/sebastianbergmann/type/security/policy", - "source": "https://github.com/sebastianbergmann/type/tree/5.1.2" + "source": "https://github.com/sebastianbergmann/type/tree/5.1.3" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/type", + "type": "tidelift" } ], - "time": "2025-03-18T13:35:50+00:00" + "time": "2025-08-09T06:55:48+00:00" }, { "name": "sebastian/version", @@ -8724,12 +8747,12 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": true, "prefer-lowest": false, "platform": { "php": "^8.2" }, - "platform-dev": [], + "platform-dev": {}, "plugin-api-version": "2.6.0" } diff --git a/install_indonesian_tts.sh b/install_indonesian_tts.sh deleted file mode 100644 index 36ffd4f..0000000 --- a/install_indonesian_tts.sh +++ /dev/null @@ -1,267 +0,0 @@ -#!/bin/bash - -# 🎤 Indonesian TTS Installation Script untuk Puskesmas -# Script ini akan menginstall semua dependencies yang diperlukan untuk Indonesian TTS - -echo "🚀 Memulai instalasi Indonesian TTS System..." -echo "================================================" - -# Check if running as root -if [ "$EUID" -eq 0 ]; then - echo "❌ Jangan jalankan script ini sebagai root/sudo" - exit 1 -fi - -# Check Python version -echo "🔍 Memeriksa Python..." -if command -v python3 &> /dev/null; then - PYTHON_CMD="python3" - echo "✅ Python3 ditemukan: $(python3 --version)" -elif command -v python &> /dev/null; then - PYTHON_CMD="python" - echo "✅ Python ditemukan: $(python --version)" -else - echo "❌ Python tidak ditemukan. Silakan install Python 3.7+ terlebih dahulu." - exit 1 -fi - -# Check pip -echo "🔍 Memeriksa pip..." -if ! command -v pip3 &> /dev/null && ! command -v pip &> /dev/null; then - echo "❌ pip tidak ditemukan. Silakan install pip terlebih dahulu." - exit 1 -fi - -# Install Python dependencies -echo "📦 Installing Python dependencies..." -echo "Installing pyttsx3..." -$PYTHON_CMD -m pip install pyttsx3 - -echo "Installing gTTS..." -$PYTHON_CMD -m pip install gTTS - -echo "Installing Coqui TTS..." -$PYTHON_CMD -m pip install TTS - -# Create necessary directories -echo "📁 Membuat direktori yang diperlukan..." -mkdir -p storage/app/tts_scripts -mkdir -p storage/app/tts/models -mkdir -p storage/app/public/audio/queue_calls - -# Set permissions -echo "🔐 Setting permissions..." -chmod -R 755 storage/app/tts_scripts -chmod -R 755 storage/app/tts/models -chmod -R 755 storage/app/public/audio - -# Download Indonesian TTS models (if available) -echo "📥 Downloading Indonesian TTS models..." -MODEL_DIR="storage/app/tts/models" - -# Check if models already exist -if [ -f "$MODEL_DIR/checkpoint.pth" ] && [ -f "$MODEL_DIR/config.json" ]; then - echo "✅ Model files sudah ada" -else - echo "⚠️ Model files belum ada. Silakan download manual dari:" - echo " https://huggingface.co/coqui/Indonesian-TTS" - echo " Atau gunakan script download terpisah" -fi - -# Create test script -echo "🧪 Membuat test script..." -cat > storage/app/tts_scripts/test_indonesian_tts.py << 'EOF' -#!/usr/bin/env python3 -""" -Test script untuk Indonesian TTS -""" - -import sys -import os - -def test_pyttsx3(): - try: - import pyttsx3 - print("✅ pyttsx3: OK") - return True - except ImportError: - print("❌ pyttsx3: NOT FOUND") - return False - -def test_gtts(): - try: - from gtts import gTTS - print("✅ gTTS: OK") - return True - except ImportError: - print("❌ gTTS: NOT FOUND") - return False - -def test_coqui_tts(): - try: - import TTS - print("✅ Coqui TTS: OK") - return True - except ImportError: - print("❌ Coqui TTS: NOT FOUND") - return False - -def test_models(): - model_dir = "storage/app/tts/models" - checkpoint = os.path.join(model_dir, "checkpoint.pth") - config = os.path.join(model_dir, "config.json") - - if os.path.exists(checkpoint) and os.path.exists(config): - print("✅ Model files: OK") - return True - else: - print("❌ Model files: NOT FOUND") - return False - -def main(): - print("🔍 Testing Indonesian TTS Dependencies...") - print("=" * 40) - - pyttsx3_ok = test_pyttsx3() - gtts_ok = test_gtts() - coqui_ok = test_coqui_tts() - models_ok = test_models() - - print("=" * 40) - - if all([pyttsx3_ok, gtts_ok, coqui_ok, models_ok]): - print("🎉 Semua dependencies berhasil diinstall!") - print("🚀 Indonesian TTS siap digunakan!") - else: - print("⚠️ Beberapa dependencies belum terinstall dengan sempurna") - print(" Silakan jalankan script ini lagi atau install manual") - -if __name__ == "__main__": - main() -EOF - -# Make test script executable -chmod +x storage/app/tts_scripts/test_indonesian_tts.py - -# Create Indonesian TTS generator script -echo "📝 Membuat Indonesian TTS generator script..." -cat > storage/app/tts_scripts/indonesian_tts_generator.py << 'EOF' -#!/usr/bin/env python3 -""" -Indonesian TTS Generator menggunakan Coqui TTS -""" - -import sys -import os -import json -from pathlib import Path - -def generate_indonesian_tts(text, output_path, speaker="wibowo"): - """ - Generate TTS audio menggunakan Indonesian TTS model - """ - try: - from TTS.api import TTS - - # Initialize TTS - tts = TTS(model_path="storage/app/tts/models/checkpoint.pth", - config_path="storage/app/tts/models/config.json") - - # Generate audio - tts.tts_to_file(text=text, file_path=output_path, speaker=speaker) - - return True - - except Exception as e: - print(f"Error generating Indonesian TTS: {e}") - return False - -def main(): - if len(sys.argv) < 3: - print("Usage: python indonesian_tts_generator.py [speaker]") - sys.exit(1) - - text = sys.argv[1] - output_path = sys.argv[2] - speaker = sys.argv[3] if len(sys.argv) > 3 else "wibowo" - - print(f"Generating Indonesian TTS...") - print(f"Text: {text}") - print(f"Output: {output_path}") - print(f"Speaker: {speaker}") - - success = generate_indonesian_tts(text, output_path, speaker) - - if success: - print("✅ Indonesian TTS generated successfully!") - else: - print("❌ Failed to generate Indonesian TTS") - sys.exit(1) - -if __name__ == "__main__": - main() -EOF - -# Make generator script executable -chmod +x storage/app/tts_scripts/indonesian_tts_generator.py - -# Test the installation -echo "🧪 Testing installation..." -$PYTHON_CMD storage/app/tts_scripts/test_indonesian_tts.py - -# Create README for Indonesian TTS -echo "📖 Membuat README Indonesian TTS..." -cat > README_INDONESIAN_TTS.md << 'EOF' -# 🎤 Indonesian TTS System untuk Puskesmas - -## 📋 Overview -Sistem Indonesian TTS menggunakan model Coqui TTS yang dioptimalkan untuk bahasa Indonesia. - -## 🚀 Fitur -- Model TTS khusus bahasa Indonesia -- Pengucapan natural dan akurat -- Support multiple speakers -- Offline processing -- High quality audio output - -## 📁 File Structure -``` -storage/app/tts/ -├── models/ -│ ├── checkpoint.pth # Model checkpoint -│ └── config.json # Model configuration -└── scripts/ - ├── test_indonesian_tts.py - └── indonesian_tts_generator.py -``` - -## 🔧 Usage -```bash -# Test dependencies -python storage/app/tts_scripts/test_indonesian_tts.py - -# Generate TTS -python storage/app/tts_scripts/indonesian_tts_generator.py "Nomor antrian 001" output.wav -``` - -## 📥 Model Download -Download model files dari: https://huggingface.co/coqui/Indonesian-TTS - -## 🆘 Troubleshooting -- Pastikan Python 3.7+ terinstall -- Pastikan semua dependencies terinstall -- Pastikan model files ada di direktori yang benar -EOF - -echo "" -echo "🎉 Instalasi Indonesian TTS selesai!" -echo "================================================" -echo "" -echo "📋 Langkah selanjutnya:" -echo "1. Download model files dari Hugging Face" -echo "2. Test sistem dengan: python storage/app/tts_scripts/test_indonesian_tts.py" -echo "3. Akses Indonesian TTS di admin panel: /admin/indonesian-tts" -echo "" -echo "📖 Dokumentasi lengkap ada di: README_INDONESIAN_TTS.md" -echo "" -echo "🚀 Selamat menggunakan Indonesian TTS System!" diff --git a/resources/views/admin/indonesian-tts/index.blade.php b/resources/views/admin/indonesian-tts/index.blade.php deleted file mode 100644 index 27aeede..0000000 --- a/resources/views/admin/indonesian-tts/index.blade.php +++ /dev/null @@ -1,450 +0,0 @@ -@extends('layouts.app') - -@section('title', 'Indonesian TTS Management') - -@section('content') -
-
-

🎤 Indonesian TTS Management

-

Kelola sistem Text-to-Speech bahasa Indonesia untuk panggilan antrian

-
- - -
-
-
-
- - - - -
-
-

Indonesian TTS

-

Checking...

-
-
-
- -
-
-
- - - -
-
-

Coqui TTS

-

Checking...

-
-
-
- -
-
-
- - - - -
-
-

Model Files

-

Checking...

-
-
-
- -
-
-
- - - -
-
-

Speakers

-

Checking...

-
-
-
-
- - -
- -
-

🧪 Test Indonesian TTS

- -
-
- - -
- - - - -
-
- - -
-

📞 Generate Panggilan Antrian

- -
-
- - -
- -
- - -
- - - - -
-
-
- - -
-

⚙️ Installation & Setup

- -
-
-

📥 Download Model Files

-

Download model Indonesian TTS yang diperlukan untuk sistem ini. -

- -
- -
-

📋 Installation Guide

-

Panduan lengkap instalasi Indonesian TTS system.

- -
-
- - -
- - -
-

ℹ️ System Information

- -
-
-

🔍 Available Speakers

-
- -
-
- -
-

📊 Model Details

-
- -
-
-
-
-
- - - - -@endsection - -@push('scripts') - -@endpush diff --git a/resources/views/admin/partials/sidebar.blade.php b/resources/views/admin/partials/sidebar.blade.php index f7104e3..fa8c7ca 100644 --- a/resources/views/admin/partials/sidebar.blade.php +++ b/resources/views/admin/partials/sidebar.blade.php @@ -99,31 +99,7 @@ class="flex items-center px-4 py-3 rounded-xl {{ request()->routeIs('admin.lapor - - - - diff --git a/resources/views/admin/tts/index.blade.php b/resources/views/admin/tts/index.blade.php deleted file mode 100644 index f1ae602..0000000 --- a/resources/views/admin/tts/index.blade.php +++ /dev/null @@ -1,510 +0,0 @@ -@extends('layouts.app') - -@section('content') -
-
- -
-
-
-

Text-to-Speech Management

-

Kelola sistem TTS untuk panggilan nomor antrian

-
- -
-
- - -
- -
-
-
-
- - - -
-
-
-
Python
-
Checking...
-
-
-
-
-
- - -
-
-
-
- - - - -
-
-
-
pyttsx3
-
Checking...
-
-
-
-
-
- - -
-
-
-
- - - - -
-
-
-
gTTS
-
Checking...
-
-
-
-
-
- - -
-
-
-
- - - - -
-
-
-
Audio Files
-
Checking...
-
-
-
-
-
-
- - -
- -
-
-

Test TTS

-

Test sistem TTS dengan nomor antrian

-
-
-
-
-
- - -
- -
- - -
- -
- - - -
-
-
- - - -
-
- - -
-
-

TTS Management

-

Kelola file audio dan suara TTS

-
-
-
- -
-

Available Voices

- -
Click refresh to load voices... -
-
- - -
-

File Management

- -

Hapus file audio lama (lebih dari 1 jam)

-
- - -
-

System Status

- -
-
-
-
-
- - - -
-
- - - -@endsection - -@push('scripts') - -@endpush diff --git a/resources/views/admin/users/create.blade.php b/resources/views/admin/users/create.blade.php index 8677322..9bd52ba 100644 --- a/resources/views/admin/users/create.blade.php +++ b/resources/views/admin/users/create.blade.php @@ -129,18 +129,7 @@ class="flex items-center px-4 py-3 rounded-xl {{ request()->routeIs('admin.lapor - - + diff --git a/resources/views/display/index.blade.php b/resources/views/display/index.blade.php index fac8668..c342819 100644 --- a/resources/views/display/index.blade.php +++ b/resources/views/display/index.blade.php @@ -184,183 +184,9 @@ class="text-lg md:text-xl font-bold text-gray-900">{{ $antrian->no_antrian }} - // TTS Audio Player - class TTSAudioPlayer { - constructor() { - this.audioQueue = []; - this.isPlaying = false; - this.currentAudio = null; - } - // Play complete audio sequence - async playAudioSequence(audioSequence) { - if (this.isPlaying) { - console.log('Audio already playing, queueing...'); - this.audioQueue.push(audioSequence); - return; - } - this.isPlaying = true; - for (let i = 0; i < audioSequence.length; i++) { - const audioItem = audioSequence[i]; - await this.playAudioItem(audioItem); - - // Wait between audio items (1 second gap) - if (i < audioSequence.length - 1) { - await this.delay(1000); - } - } - - this.isPlaying = false; - - // Play next in queue if available - if (this.audioQueue.length > 0) { - const nextSequence = this.audioQueue.shift(); - this.playAudioSequence(nextSequence); - } - } - - // Play single audio item - playAudioItem(audioItem) { - return new Promise((resolve, reject) => { - if (audioItem.type === 'browser_tts') { - // Use browser TTS - if ('speechSynthesis' in window) { - const utterance = new SpeechSynthesisUtterance(audioItem.text); - utterance.lang = 'id-ID'; - utterance.rate = 0.85; // Slightly faster for more natural flow - utterance.volume = 1.0; - - // Try to select a female Indonesian voice if available - const voices = speechSynthesis.getVoices(); - const indonesianVoice = voices.find(voice => - voice.lang === 'id-ID' && - voice.name.toLowerCase().includes('female') - ) || voices.find(voice => voice.lang === 'id-ID'); - - if (indonesianVoice) { - utterance.voice = indonesianVoice; - } - - utterance.addEventListener('end', () => { - resolve(); - }); - - utterance.addEventListener('error', (error) => { - console.error('TTS error:', error); - resolve(); - }); - - speechSynthesis.speak(utterance); - - // Fallback timeout - setTimeout(() => { - resolve(); - }, audioItem.duration || 8000); - } else { - console.warn('Speech synthesis not supported'); - resolve(); - } - } else if (audioItem.type === 'audio_file') { - // Play audio file from our TTS system - const audio = new Audio(audioItem.url); - - audio.addEventListener('loadeddata', () => { - console.log('Audio loaded, playing...'); - audio.play().catch(error => { - console.error('Audio play error:', error); - resolve(); - }); - }); - - audio.addEventListener('ended', () => { - console.log('Audio ended'); - resolve(); - }); - - audio.addEventListener('error', (error) => { - console.error('Audio playback error:', error); - // Fallback to browser TTS if audio file fails - this.fallbackToBrowserTTS(audioItem.text, resolve); - }); - - // Fallback timeout - setTimeout(() => { - resolve(); - }, audioItem.duration || 8000); - } else { - console.warn('Unknown audio type:', audioItem.type); - resolve(); - } - }); - } - - // Fallback to browser TTS if audio file fails - fallbackToBrowserTTS(text, resolve) { - if ('speechSynthesis' in window) { - const utterance = new SpeechSynthesisUtterance(text); - utterance.lang = 'id-ID'; - utterance.rate = 0.85; - utterance.volume = 1.0; - - utterance.addEventListener('end', () => { - resolve(); - }); - - utterance.addEventListener('error', (error) => { - console.error('Fallback TTS error:', error); - resolve(); - }); - - speechSynthesis.speak(utterance); - } else { - resolve(); - } - } - - // Utility function for delays - delay(ms) { - return new Promise(resolve => setTimeout(resolve, ms)); - } - - // Play TTS for queue call - async playQueueCall(poliName, queueNumber) { - try { - console.log('Playing TTS for:', poliName, queueNumber); - - const response = await fetch('{{ route('tts.play-sequence') }}', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content - }, - body: JSON.stringify({ - poli_name: poliName, - queue_number: queueNumber - }) - }); - - const data = await response.json(); - - if (data.success && data.audio_sequence) { - console.log('Audio sequence received:', data.audio_sequence); - await this.playAudioSequence(data.audio_sequence); - } else { - console.error('Failed to get audio sequence:', data.message); - // Fallback to browser TTS - this.fallbackToBrowserTTS(`Nomor antrian ${queueNumber} untuk ${poliName}. Silakan menuju ke loket yang tersedia.`, () => {}); - } - } catch (error) { - console.error('Error playing TTS:', error); - // Fallback to browser TTS - this.fallbackToBrowserTTS(`Nomor antrian ${queueNumber} untuk ${poliName}. Silakan menuju ke loket yang tersedia.`, () => {}); - } - } - } - - // Initialize TTS Audio Player - const ttsPlayer = new TTSAudioPlayer(); // Show SweetAlert2 for success messages @if (session('success')) @@ -466,8 +292,7 @@ function checkForNewCalls() { if (data.has_new_call && data.antrian) { console.log('New call detected:', data.antrian); - // Play TTS for the called queue - ttsPlayer.playQueueCall(data.antrian.poli_name, data.antrian.queue_number); + // Show notification showNewCallNotification(data.antrian.poli_name, data.antrian.queue_number); @@ -547,16 +372,7 @@ function updateNextQueue(containerId, nextQueues) { // Update display data every 10 seconds setInterval(updateDisplayData, 10000); - // Fallback: Listen for TTS events from admin panel (if broadcast not available) - window.addEventListener('message', function(event) { - if (event.data.type === 'TTS_CALL') { - const { - poliName, - queueNumber - } = event.data; - ttsPlayer.playQueueCall(poliName, queueNumber); - } - }); + @endpush @endsection diff --git a/routes/web.php b/routes/web.php index b0a3d0c..c0ef713 100644 --- a/routes/web.php +++ b/routes/web.php @@ -6,8 +6,7 @@ use App\Http\Controllers\DashboardController; use App\Http\Controllers\DisplayController; use App\Http\Controllers\AdminController; -use App\Http\Controllers\TTSController; -use App\Http\Controllers\IndonesianTTSController; + // Landing Page Route::get('/', [LandingController::class, 'index'])->name('landing'); @@ -85,34 +84,10 @@ Route::post('/admin/antrian/store', [AdminController::class, 'storeAntrianAdmin'])->name('admin.antrian.store'); Route::get('/admin/antrian/{antrian}/cetak', [AdminController::class, 'cetakAntrian'])->name('admin.antrian.cetak'); - // Simple TTS Routes (Windows Compatible) - Route::get('/admin/tts', [TTSController::class, 'index'])->name('admin.tts.index'); - Route::post('/admin/tts/generate', [TTSController::class, 'generateQueueTTS'])->name('admin.tts.generate'); - Route::post('/admin/tts/play', [TTSController::class, 'playTTS'])->name('admin.tts.play'); - Route::get('/admin/tts/play', [TTSController::class, 'playTTS'])->name('admin.tts.play.get'); - Route::get('/admin/tts/test', [TTSController::class, 'testTTS'])->name('admin.tts.test'); - Route::get('/admin/tts/voices', [TTSController::class, 'getVoices'])->name('admin.tts.voices'); - Route::post('/admin/tts/cleanup', [TTSController::class, 'cleanupFiles'])->name('admin.tts.cleanup'); - Route::get('/admin/tts/status', [TTSController::class, 'getStatus'])->name('admin.tts.status'); - // Indonesian TTS Routes - Route::post('/admin/indonesian-tts/generate', [IndonesianTTSController::class, 'generateQueueCall'])->name('admin.indonesian-tts.generate'); - Route::post('/admin/indonesian-tts/audio-sequence', [IndonesianTTSController::class, 'createAudioSequence'])->name('admin.indonesian-tts.audio-sequence'); - Route::get('/admin/indonesian-tts/status', [IndonesianTTSController::class, 'checkStatus'])->name('admin.indonesian-tts.status'); - Route::post('/admin/indonesian-tts/test', [IndonesianTTSController::class, 'testTTS'])->name('admin.indonesian-tts.test'); - Route::get('/admin/indonesian-tts/install', [IndonesianTTSController::class, 'getInstallationInstructions'])->name('admin.indonesian-tts.install'); - Route::get('/admin/indonesian-tts/download', [IndonesianTTSController::class, 'downloadModelFiles'])->name('admin.indonesian-tts.download'); - Route::get('/admin/indonesian-tts', [IndonesianTTSController::class, 'index'])->name('admin.indonesian-tts.index'); }); -// Public TTS Routes (for display) -Route::post('/tts/play-sequence', [TTSController::class, 'playAudioSequence'])->name('tts.play-sequence'); -// Public TTS route for display page -Route::get('/tts/audio/{filename}', [TTSController::class, 'playPublicAudio'])->name('tts.audio.public'); - -// Test TTS route (public) -Route::get('/tts/test-public', [TTSController::class, 'testPublicTTS'])->name('tts.test.public'); // API Routes for display Route::get('/api/check-new-calls', [DisplayController::class, 'checkNewCalls'])->name('api.check-new-calls'); diff --git a/test_audio_sequence.php b/test_audio_sequence.php deleted file mode 100644 index a3cd6e5..0000000 --- a/test_audio_sequence.php +++ /dev/null @@ -1,54 +0,0 @@ - \ No newline at end of file diff --git a/test_indonesian_pronunciation.php b/test_indonesian_pronunciation.php deleted file mode 100644 index 26a24fb..0000000 --- a/test_indonesian_pronunciation.php +++ /dev/null @@ -1,169 +0,0 @@ - 'Nol', - '1' => 'Satu', - '2' => 'Dua', - '3' => 'Tiga', - '4' => 'Empat', - '5' => 'Lima', - '6' => 'Enam', - '7' => 'Tujuh', - '8' => 'Delapan', - '9' => 'Sembilan', - '10' => 'Sepuluh', - '11' => 'Sebelas', - '12' => 'Dua Belas', - '13' => 'Tiga Belas', - '14' => 'Empat Belas', - '15' => 'Lima Belas', - '16' => 'Enam Belas', - '17' => 'Tujuh Belas', - '18' => 'Delapan Belas', - '19' => 'Sembilan Belas', - '20' => 'Dua Puluh', - '30' => 'Tiga Puluh', - '40' => 'Empat Puluh', - '50' => 'Lima Puluh', - '60' => 'Enam Puluh', - '70' => 'Tujuh Puluh', - '80' => 'Delapan Puluh', - '90' => 'Sembilan Puluh', - '100' => 'Seratus' -]; - -/** - * Convert alphanumeric queue number to Indonesian pronunciation - * Example: "U5" becomes "U Lima", "A10" becomes "A Sepuluh" - */ -function convertQueueNumberToIndonesian($queueNumber) { - global $indonesianNumbers; - - // If it's a pure number, convert it - if (is_numeric($queueNumber)) { - $number = (int)$queueNumber; - if (isset($indonesianNumbers[$number])) { - return $indonesianNumbers[$number]; - } else { - // For numbers > 100, build the pronunciation - if ($number < 100) { - $tens = floor($number / 10) * 10; - $ones = $number % 10; - if ($ones == 0) { - return $indonesianNumbers[$tens]; - } else { - return $indonesianNumbers[$tens] . ' ' . $indonesianNumbers[$ones]; - } - } else { - return $number; // Fallback for large numbers - } - } - } - - // For alphanumeric (like "U5", "A10"), convert the numeric part - $letters = ''; - $numbers = ''; - - // Split into letters and numbers - for ($i = 0; $i < strlen($queueNumber); $i++) { - $char = $queueNumber[$i]; - if (is_numeric($char)) { - $numbers .= $char; - } else { - $letters .= $char; - } - } - - // If we have both letters and numbers - if ($letters && $numbers) { - $numberValue = (int)$numbers; - if (isset($indonesianNumbers[$numberValue])) { - return $letters . ' ' . $indonesianNumbers[$numberValue]; - } else { - // For numbers > 100, build the pronunciation - if ($numberValue < 100) { - $tens = floor($numberValue / 10) * 10; - $ones = $numberValue % 10; - if ($ones == 0) { - return $letters . ' ' . $indonesianNumbers[$tens]; - } else { - return $letters . ' ' . $indonesianNumbers[$tens] . ' ' . $indonesianNumbers[$ones]; - } - } else { - return $queueNumber; // Fallback for large numbers - } - } - } - - // If no conversion needed, return as is - return $queueNumber; -} - -// Test cases -echo "=== Indonesian Pronunciation Conversion Test ===\n\n"; - -$testCases = [ - // Pure numbers - '1' => 'Satu', - '5' => 'Lima', - '10' => 'Sepuluh', - '15' => 'Lima Belas', - '25' => 'Dua Puluh Lima', - '100' => 'Seratus', - - // Alphanumeric queue numbers - 'U5' => 'U Lima', - 'A10' => 'A Sepuluh', - 'B15' => 'B Lima Belas', - 'C25' => 'C Dua Puluh Lima', - 'D100' => 'D Seratus', - - // Edge cases - 'ABC123' => 'ABC Seratus Dua Puluh Tiga', - 'X1' => 'X Satu', - 'Y0' => 'Y Nol', - 'Z99' => 'Z Sembilan Puluh Sembilan', - - // Non-alphanumeric (should remain unchanged) - 'POLI-UMUM' => 'POLI-UMUM', - 'ANTRIAN-1' => 'ANTRIAN-1', // Mixed with dash -]; - -echo "Test Results:\n"; -echo str_repeat('-', 50) . "\n"; - -foreach ($testCases as $input => $expected) { - $result = convertQueueNumberToIndonesian($input); - $status = ($result === $expected) ? '✓ PASS' : '✗ FAIL'; - echo sprintf("%-15s → %-25s [%s]\n", $input, $result, $status); - - if ($result !== $expected) { - echo " Expected: $expected\n"; - } -} - -echo "\n" . str_repeat('-', 50) . "\n"; -echo "Test completed!\n\n"; - -// Additional examples for user verification -echo "=== Examples for User Verification ===\n"; -echo "Queue Number → Indonesian Pronunciation\n"; -echo str_repeat('-', 40) . "\n"; - -$examples = ['U5', 'A10', 'B15', 'C25', 'D100', 'X1', 'Y0', 'Z99']; - -foreach ($examples as $example) { - $pronunciation = convertQueueNumberToIndonesian($example); - echo "$example → $pronunciation\n"; -} - -echo "\nThese examples show how alphanumeric queue numbers\n"; -echo "are converted to Indonesian pronunciation for TTS.\n"; -echo "For example, 'U5' becomes 'U Lima' instead of 'U five'.\n";