import 'dart:io'; import 'package:path/path.dart' as p; import '../cekirdek/kimlik.dart'; import '../cekirdek/sabitler.dart'; import '../modeller/proje.dart'; import '../modeller/segment.dart'; import 'ffmpeg/ffmpeg_servis.dart'; import 'proje_deposu.dart'; /// Bir projeye, ses süresi + segment süresi'ne göre otomatik segmentler oluşturur. class SegmentOlusturucu { /// [varsayilanlar] verilirse, oluşturulan tüm segmentlere uygulanır /// (prompt, negatifPrompt, motor, cozunurluk, oran, kameraHareketi vb.). static Future otomatikSegmentleriOlustur( Proje proje, { SegmentVarsayilan? varsayilanlar, }) async { final sesYolu = proje.kaynak.sesPath; if (sesYolu.isEmpty || !await File(sesYolu).exists()) return proje; final toplam = await FfmpegServis.sesSuresi(sesYolu); final parcaSure = Sabitler.izinliSureyeYuvarla( proje.global.segmentSuresi, proje.global.cozunurluk, ).toDouble(); int sayi = (toplam ~/ parcaSure).toInt(); final kalan = toplam - (sayi * parcaSure); if (kalan >= 5.0) sayi += 1; // Mevcut segmentler varsa sıfırlama; sadece eksikse ekle. Burada zaten // proje yeni oluşturuluyor — sıfırla. final segmentler = []; final hedefSegKlasor = p.join(proje.klasorYolu, 'ses_segmentleri'); await Directory(hedefSegKlasor).create(recursive: true); for (int i = 0; i < sayi; i++) { final basla = i * parcaSure; final bit = ((i + 1) * parcaSure > toplam) ? toplam : (i + 1) * parcaSure; var seg = Segment( id: FikoYTId.yeni(), sira: i, basla: basla, bit: bit, ); if (varsayilanlar != null) { seg = varsayilanlar.uygula(seg); } segmentler.add(seg); } final yeni = proje.copyWith(segmentler: segmentler); await ProjeDeposu.kaydet(yeni); return yeni; } /// Her segment için range-kesilmiş ses dosyası hazırlar. /// Öncelik: segment.sesOverridePath → proje.kaynak.sesPath /// İkisi de yoksa o segment için map'e konmaz. static Future> sesParcalariniHazirla(Proje proje) async { if (proje.segmentler.isEmpty) return {}; final hedefKlasor = p.join(proje.klasorYolu, 'ses_segmentleri'); await Directory(hedefKlasor).create(recursive: true); final harita = {}; for (final s in proje.segmentler) { // Kaynak ses: önce override, sonra proje sesi String? kaynakSes; if (s.sesOverridePath != null && s.sesOverridePath!.isNotEmpty) { if (await File(s.sesOverridePath!).exists()) { kaynakSes = s.sesOverridePath; } } kaynakSes ??= (proje.kaynak.sesPath.isNotEmpty && await File(proje.kaynak.sesPath).exists()) ? proje.kaynak.sesPath : null; if (kaynakSes == null) continue; // Dosya ismine kaynak hash + zaman aralığı ekle (override ve proje // sesi çakışmasın, range değişince yeni dosya) final kaynakKisa = kaynakSes.hashCode.toUnsigned(32).toRadixString(16).padLeft(8, '0'); final etiket = '${s.basla.toStringAsFixed(2)}_${s.bit.toStringAsFixed(2)}' .replaceAll('.', 'p'); final yol = p.join( hedefKlasor, 'audio_seg_${s.sira.toString().padLeft(3, '0')}_${kaynakKisa}_$etiket.mp3', ); if (!await File(yol).exists()) { final sure = s.bit - s.basla; // -ss BEFORE -i = hızlı input seek (frame-accurate audio için sonrasına da koyalım) final r = await Process.run(FfmpegServis.ffmpegYolu, [ '-y', '-ss', s.basla.toString(), '-i', kaynakSes, '-t', sure.toString(), '-acodec', 'libmp3lame', '-b:a', '192k', yol, ]); if (r.exitCode != 0 || !await File(yol).exists()) { continue; } } harita[s.id] = yol; } return harita; } } /// Otomatik segment üretiminde tüm segmentlere uygulanacak ortak ayarlar. /// Boş alanlar dokunulmadan bırakılır (segment varsayılanlarını korur). class SegmentVarsayilan { final String? prompt; final String? negatifPrompt; final String? model; final String? cozunurluk; final String? oran; final String? kameraHareketi; final double? guidanceScale; final int? steps; final bool? ses; final SegmentMotor? motor; final String? wangpReferansYuzPath; final String? wangpLoraPath; final bool? faceSwapAcik; const SegmentVarsayilan({ this.prompt, this.negatifPrompt, this.model, this.cozunurluk, this.oran, this.kameraHareketi, this.guidanceScale, this.steps, this.ses, this.motor, this.wangpReferansYuzPath, this.wangpLoraPath, this.faceSwapAcik, }); Segment uygula(Segment s) { return s.copyWith( prompt: (prompt != null && prompt!.isNotEmpty) ? prompt : s.prompt, negatifPrompt: (negatifPrompt != null && negatifPrompt!.isNotEmpty) ? negatifPrompt : s.negatifPrompt, model: (model != null && model!.isNotEmpty) ? model : s.model, cozunurluk: (cozunurluk != null && cozunurluk!.isNotEmpty) ? cozunurluk : s.cozunurluk, oran: (oran != null && oran!.isNotEmpty) ? oran : s.oran, kameraHareketi: (kameraHareketi != null && kameraHareketi!.isNotEmpty) ? kameraHareketi : s.kameraHareketi, guidanceScale: guidanceScale ?? s.guidanceScale, steps: steps ?? s.steps, ses: ses ?? s.ses, motor: motor ?? s.motor, wangpReferansYuzPath: (wangpReferansYuzPath != null && wangpReferansYuzPath!.isNotEmpty) ? wangpReferansYuzPath : s.wangpReferansYuzPath, wangpLoraPath: (wangpLoraPath != null && wangpLoraPath!.isNotEmpty) ? wangpLoraPath : s.wangpLoraPath, faceSwapAcik: faceSwapAcik ?? s.faceSwapAcik, ); } }