import 'dart:io'; import 'package:path/path.dart' as p; import '../cekirdek/kimlik.dart'; import '../cekirdek/log.dart'; import '../modeller/proje.dart'; import '../modeller/segment.dart'; import 'ffmpeg/ffmpeg_servis.dart'; import 'ffmpeg/kare_cikarici.dart'; /// Bir segmenti belirli bir saniyeden keser ve şarkı timeline'ını koruyarak /// projeyi yeniden düzenler. Kazanılan delta saniye, BİR SONRAKİ segmente /// eklenir. Bir segment 20s'i aşarsa otomatik olarak yeni segment(ler) bölünür /// (LTX/WanGP uyumlu). class SegmentKesici { /// LTX/WanGP üst sınırı (saniye). static const double sureLimiti = 20.0; /// Kesim sonucu — UI'ya gösterilecek özet bilgi. /// Hata olursa [hata] doludur, [yeniProje] null'dur. static Future kesVeYenidenDuzenle({ required Proje proje, required String segmentId, required double kesimSaniye, }) async { try { final segIdx = proje.segmentler.indexWhere((s) => s.id == segmentId); if (segIdx < 0) { return KesimSonucu.hata('Segment bulunamadı'); } final seg = proje.segmentler[segIdx]; final orjSure = seg.bit - seg.basla; if (kesimSaniye <= 0.05) { return KesimSonucu.hata('Kesim noktası segmentin başlangıcına çok yakın'); } if (kesimSaniye >= orjSure - 0.05) { return KesimSonucu.hata('Kesim noktası segmentin sonuna çok yakın'); } final delta = orjSure - kesimSaniye; // Sonraki segment kontrolü — yoksa kesim yapılamaz (toplam süreyi // kısaltmak istemiyoruz). final sonrakiIdx = segIdx + 1; if (sonrakiIdx >= proje.segmentler.length) { return KesimSonucu.hata( 'Bu son segment — sonrasına yer yok. Toplam süreyi korumak için ' 'kesim yapılamaz.', ); } final sonraki = proje.segmentler[sonrakiIdx]; // Aktif videoyu kes (yeniden kodlama) final aktifVideo = seg.videoYolu; String? yeniVideoYolu; if (aktifVideo != null && File(aktifVideo).existsSync()) { final dir = p.dirname(aktifVideo); final adi = p.basenameWithoutExtension(aktifVideo); final ext = p.extension(aktifVideo); final stamp = DateTime.now().millisecondsSinceEpoch; yeniVideoYolu = p.join(dir, '${adi}__kes_$stamp$ext'); await FfmpegServis.videoKes( aktifVideo, yeniVideoYolu, bitisSaniye: kesimSaniye, ); } // Yeni son kareyi çıkar String? yeniSonKare; if (yeniVideoYolu != null && File(yeniVideoYolu).existsSync()) { final klasor = Directory(p.join(proje.klasorYolu, 'kare_seciler')); if (!await klasor.exists()) await klasor.create(recursive: true); yeniSonKare = p.join( klasor.path, 's${seg.sira + 1}_son_${DateTime.now().millisecondsSinceEpoch}.jpg', ); await KareCikarici.sonKareCikar( yeniVideoYolu, yeniSonKare, sure: kesimSaniye, ); } // Sonraki segmentin yeni süresi final sonrakiYeniBasla = seg.basla + kesimSaniye; final sonrakiYeniBit = sonraki.bit; final sonrakiYeniSure = sonrakiYeniBit - sonrakiYeniBasla; // 20s aşılırsa: sonraki segment 20s olur, kalan kısım yeni segment(ler)e // bölünür. Yeni segmentler sonraki'nin ayarlarını miras alır. final yeniSegmentler = []; int eklenecekYeniSayisi = 0; if (sonrakiYeniSure <= sureLimiti) { // Düz büyütme — yeni segment yok // (sonraki segment basla'sı geri çekilir) } else { // Sonraki segment 20s alır, kalan parça yeni segmentlere bölünür double kalan = sonrakiYeniSure - sureLimiti; double cursor = sonrakiYeniBasla + sureLimiti; while (kalan > 0.05) { final chunk = kalan > sureLimiti ? sureLimiti : kalan; yeniSegmentler.add( Segment( id: FikoYTId.yeni(), sira: 0, // sonra renumber basla: cursor, bit: cursor + chunk, sure: chunk.round(), prompt: sonraki.prompt, negatifPrompt: sonraki.negatifPrompt, model: sonraki.model, cozunurluk: sonraki.cozunurluk, oran: sonraki.oran, kameraHareketi: sonraki.kameraHareketi, guidanceScale: sonraki.guidanceScale, steps: sonraki.steps, ses: sonraki.ses, faceSwapAcik: sonraki.faceSwapAcik, faceSwapKaynakOverridePath: sonraki.faceSwapKaynakOverridePath, faceSwapKaynakYollari: sonraki.faceSwapKaynakYollari, motor: sonraki.motor, wangpReferansYuzPath: sonraki.wangpReferansYuzPath, wangpLoraPath: sonraki.wangpLoraPath, durum: SegmentDurum.bos, ), ); cursor += chunk; kalan -= chunk; eklenecekYeniSayisi++; } } // Tüm segmentleri yeniden oluştur final liste = []; for (int i = 0; i < proje.segmentler.length; i++) { final s = proje.segmentler[i]; if (i == segIdx) { // Kesilen segment liste.add(s.copyWith( videoYolu: yeniVideoYolu ?? s.videoYolu, varyantYollari: yeniVideoYolu != null ? [...s.varyantYollari, yeniVideoYolu] : s.varyantYollari, bit: s.basla + kesimSaniye, sure: kesimSaniye.round(), sonKareYolu: yeniSonKare ?? s.sonKareYolu, )); } else if (i == sonrakiIdx) { // Sonraki segment — basla geri çekilir, gerekirse 20s'e kırpılır final yeniBit = sonrakiYeniSure > sureLimiti ? sonrakiYeniBasla + sureLimiti : sonrakiYeniBit; final yeniSureSn = yeniBit - sonrakiYeniBasla; liste.add(s.copyWith( basla: sonrakiYeniBasla, bit: yeniBit, sure: yeniSureSn.round(), // Override temizle → otomatik olarak prev.sonKareYolu (yeni kare) // kullanılır girisGorseliOverridePath: null, )); // Hemen ardından yeni ara segmentleri ekle liste.addAll(yeniSegmentler); } else { liste.add(s); } } // Sıraları yeniden numaralandır final renumbered = []; for (int i = 0; i < liste.length; i++) { renumbered.add(liste[i].copyWith(sira: i)); } final yeniProje = proje.copyWith(segmentler: renumbered); Log.bilgi( 'SegmentKesici', 'S${seg.sira + 1} kesildi (${orjSure.toStringAsFixed(2)}s → ' '${kesimSaniye.toStringAsFixed(2)}s) · ' 'S${sonraki.sira + 1} → ${sonrakiYeniSure.toStringAsFixed(2)}s · ' 'eklenen yeni: $eklenecekYeniSayisi', ); return KesimSonucu( yeniProje: yeniProje, kesilenSegmentSira: seg.sira, sonrakiSegmentSira: sonraki.sira, eskiSure: orjSure, yeniSure: kesimSaniye, delta: delta, sonrakiYeniSure: sonrakiYeniSure > sureLimiti ? sureLimiti : sonrakiYeniSure, eklenecekYeniSayisi: eklenecekYeniSayisi, yeniSonKareYolu: yeniSonKare, ); } catch (e, st) { Log.hata('SegmentKesici', 'Kesim hatası: $e\n$st'); return KesimSonucu.hata('$e'); } } /// Sadece önizleme — gerçek kesim yapmadan önce kullanıcıya gösterilecek özet. static KesimOnizleme onizle({ required Proje proje, required String segmentId, required double kesimSaniye, }) { final segIdx = proje.segmentler.indexWhere((s) => s.id == segmentId); if (segIdx < 0) return const KesimOnizleme(gecerli: false, hata: 'Segment yok'); final seg = proje.segmentler[segIdx]; final orjSure = seg.bit - seg.basla; if (kesimSaniye <= 0.05) { return const KesimOnizleme(gecerli: false, hata: 'Çok başa yakın'); } if (kesimSaniye >= orjSure - 0.05) { return const KesimOnizleme(gecerli: false, hata: 'Çok sona yakın'); } if (segIdx + 1 >= proje.segmentler.length) { return const KesimOnizleme( gecerli: false, hata: 'Son segment — sonrasına yer yok'); } final sonraki = proje.segmentler[segIdx + 1]; final delta = orjSure - kesimSaniye; final sonrakiYeniSure = (sonraki.bit - seg.basla) - kesimSaniye; final yeniSayisi = sonrakiYeniSure > sureLimiti ? ((sonrakiYeniSure - sureLimiti) / sureLimiti).ceil() : 0; return KesimOnizleme( gecerli: true, eskiSure: orjSure, yeniSure: kesimSaniye, delta: delta, sonrakiSira: sonraki.sira, sonrakiYeniSure: sonrakiYeniSure > sureLimiti ? sureLimiti : sonrakiYeniSure, eklenecekYeniSayisi: yeniSayisi, ); } } class KesimOnizleme { final bool gecerli; final String? hata; final double eskiSure; final double yeniSure; final double delta; final int sonrakiSira; final double sonrakiYeniSure; final int eklenecekYeniSayisi; const KesimOnizleme({ required this.gecerli, this.hata, this.eskiSure = 0, this.yeniSure = 0, this.delta = 0, this.sonrakiSira = 0, this.sonrakiYeniSure = 0, this.eklenecekYeniSayisi = 0, }); } class KesimSonucu { final Proje? yeniProje; final String? hata; final int kesilenSegmentSira; final int sonrakiSegmentSira; final double eskiSure; final double yeniSure; final double delta; final double sonrakiYeniSure; final int eklenecekYeniSayisi; final String? yeniSonKareYolu; KesimSonucu({ required this.yeniProje, this.hata, this.kesilenSegmentSira = 0, this.sonrakiSegmentSira = 0, this.eskiSure = 0, this.yeniSure = 0, this.delta = 0, this.sonrakiYeniSure = 0, this.eklenecekYeniSayisi = 0, this.yeniSonKareYolu, }); factory KesimSonucu.hata(String mesaj) => KesimSonucu(yeniProje: null, hata: mesaj); bool get basarili => yeniProje != null && hata == null; }