import 'dart:io'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../app/tema.dart'; import '../comfy/comfy_istemci.dart'; import '../comfy/uretim_kuyrugu.dart'; import '../comfy/workflow/ses_klon_workflow.dart'; import '../veri/modeller/uretim_kaydi.dart'; /// 🎙 Seslendirme & Ses Klonlama — EdgeTTS (hızlı) / F5 / XTTS (klon). Future showSeslendirmeDialog(BuildContext context, WidgetRef ref) async { await showDialog( context: context, builder: (_) => const _SeslendirmeDialog()); } class _SeslendirmeDialog extends ConsumerStatefulWidget { const _SeslendirmeDialog(); @override ConsumerState<_SeslendirmeDialog> createState() => _SeslendirmeDialogState(); } class _SeslendirmeDialogState extends ConsumerState<_SeslendirmeDialog> { final _metin = TextEditingController(); TtsMotor motor = TtsMotor.edge; String edgeSes = 'tr-TR-AhmetNeural'; String dil = 'tr'; double hiz = 1.0; String referansAd = ''; bool yukleniyor = false; @override void dispose() { _metin.dispose(); super.dispose(); } Future _uret() async { if (_metin.text.trim().isEmpty) return; final p = SesKlonParametreleri( motor: motor, metin: _metin.text.trim(), referansSes: referansAd, edgeSes: edgeSes, dil: dil, hiz: hiz, ); await ref.read(uretimKuyruguProvider.notifier).uretimEkle( tip: UretimTipi.muzik, baslik: '🎙 ${motor == TtsMotor.edge ? "Seslendirme" : "Ses Klonu"} • ${_metin.text.trim().split(' ').take(4).join(' ')}...', parametrelerJson: p.jsonString(), workflow: sesKlonWorkflowOlustur(p), ); if (mounted) Navigator.pop(context); } @override Widget build(BuildContext context) { final klon = motor != TtsMotor.edge; return AlertDialog( title: const Row(children: [ Icon(Icons.record_voice_over, size: 18, color: FikoRenkler.neonCyan), SizedBox(width: 8), Text('Seslendirme & Ses Klonlama', style: TextStyle(fontSize: 15)), ]), content: SizedBox( width: 480, child: Column(mainAxisSize: MainAxisSize.min, children: [ SegmentedButton( style: const ButtonStyle(visualDensity: VisualDensity.compact), segments: const [ ButtonSegment(value: TtsMotor.edge, label: Text('Edge (hızlı)')), ButtonSegment(value: TtsMotor.f5, label: Text('F5 Klon')), ButtonSegment(value: TtsMotor.xtts, label: Text('XTTS Klon')), ], selected: {motor}, onSelectionChanged: (s) => setState(() => motor = s.first), ), const SizedBox(height: 10), TextField( controller: _metin, minLines: 2, maxLines: 4, decoration: const InputDecoration( labelText: 'Söylenecek metin', isDense: true), ), const SizedBox(height: 10), if (motor == TtsMotor.edge) DropdownButtonFormField( initialValue: edgeSes, isExpanded: true, decoration: const InputDecoration(labelText: 'Ses', isDense: true), items: [ for (final s in edgeSesleri) DropdownMenuItem( value: s, child: Text(s, style: const TextStyle(fontSize: 11.5))), ], onChanged: (v) => setState(() => edgeSes = v ?? edgeSes), ), if (motor == TtsMotor.xtts) DropdownButtonFormField( initialValue: dil, decoration: const InputDecoration(labelText: 'Dil', isDense: true), items: [ for (final d in xttsDilleri) DropdownMenuItem(value: d, child: Text(d)), ], onChanged: (v) => setState(() => dil = v ?? dil), ), if (klon) ...[ const SizedBox(height: 8), Row(children: [ Expanded( child: Text( referansAd.isEmpty ? 'Klonlanacak referans ses seç (5-30 sn)' : '🎤 $referansAd', maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle(fontSize: 11)), ), OutlinedButton.icon( style: OutlinedButton.styleFrom(visualDensity: VisualDensity.compact), icon: yukleniyor ? const SizedBox( width: 12, height: 12, child: CircularProgressIndicator(strokeWidth: 2)) : const Icon(Icons.upload_file, size: 14), label: const Text('Referans Ses', style: TextStyle(fontSize: 11)), onPressed: () async { final s = await FilePicker.platform .pickFiles(type: FileType.audio); final yol = s?.files.firstOrNull?.path; if (yol == null || !File(yol).existsSync()) return; setState(() => yukleniyor = true); String? ad; try { ad = await ref.read(comfyIstemciProvider).resimYukle(yol); } catch (_) {} if (!context.mounted) return; setState(() { if (ad != null) referansAd = ad; yukleniyor = false; }); }, ), ]), ], const SizedBox(height: 6), Row(children: [ const Text('Hız', style: TextStyle(fontSize: 11)), Expanded( child: Slider( value: hiz.clamp(0.5, 2.0), min: 0.5, max: 2.0, onChanged: (v) => setState(() => hiz = v), ), ), Text('${hiz.toStringAsFixed(2)}x', style: const TextStyle(fontSize: 11)), ]), ]), ), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('Vazgeç')), FilledButton.icon( icon: const Icon(Icons.record_voice_over, size: 16), label: const Text('Üret'), onPressed: (klon && referansAd.isEmpty) ? null : _uret, ), ], ); } }