import 'dart:io'; import 'package:path/path.dart' as p; import '../cekirdek/log.dart'; /// Mevcut FaceFusion kurulumunu (video-uretim/FaceFusion/) headless modda çağırır. /// Bir resim/kare üzerinde yüz değişimi yapar. class FaceFusionRunner { static const _faceFusionDir = r'D:\__sistem__\Masaustu\FikoYT.Yeni.Cag\video-uretim\FaceFusion'; static String get _venvPython => p.join(_faceFusionDir, 'venv', 'Scripts', 'python.exe'); static String get _scriptYolu { final ana = p.join(_faceFusionDir, 'facefusion', 'facefusion.py'); final fallback = p.join(_faceFusionDir, 'facefusion', 'run.py'); return File(ana).existsSync() ? ana : fallback; } static const _cudaToolkitBin = r'C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v12.6\bin'; static Future kuruluMu() async { return await File(_venvPython).exists() && await File(_scriptYolu).exists(); } /// Tek yüz veya çok yüz değişimi yapar. /// /// [kaynakYuzler] sıralı liste: /// - 0. eleman → hedef resimdeki **0. konumdaki** (soldan ilk) yüze takılır /// - 1. eleman → 1. konumdaki yüze takılır /// - vs. /// /// Çoklu yüz için **reference mod chain** kullanılır: her bir kaynak yüz /// için ayrı FaceFusion çağrısı yapılır, hedef resimdeki SADECE o /// pozisyondaki yüz değiştirilir. Sonuç bir sonraki çağrının girişi olur. /// Bu sayede yüzler karışmaz, her kişiye doğru yüz gider. static Future resimUzerindeUygula({ required List kaynakYuzler, required String hedefResim, required String cikis, Duration zamanAsimi = const Duration(seconds: 180), }) async { if (!await kuruluMu()) { Log.hata('FaceFusion', 'Kurulu değil: $_venvPython'); return false; } if (kaynakYuzler.isEmpty) { Log.hata('FaceFusion', 'Hiç kaynak yüz verilmedi'); return false; } final hedefAbs = File(hedefResim).absolute.path; final cikisAbs = File(cikis).absolute.path; if (!await File(hedefAbs).exists()) { Log.hata('FaceFusion', 'Hedef resim bulunamadı: $hedefAbs'); return false; } final kaynakMutlakYollar = []; for (final yol in kaynakYuzler) { final mutlak = File(yol).absolute.path; if (!await File(mutlak).exists()) { Log.uyari('FaceFusion', 'Kaynak yüz atlandı (yok): $mutlak'); continue; } kaynakMutlakYollar.add(mutlak); } if (kaynakMutlakYollar.isEmpty) { Log.hata('FaceFusion', 'Geçerli kaynak yüz bulunamadı'); return false; } final env = Map.from(Platform.environment); final pathEklentileri = [ p.join(_faceFusionDir, 'venv', 'Scripts'), if (await Directory(_cudaToolkitBin).exists()) _cudaToolkitBin, ]; final nvidiaDir = Directory(p.join(_faceFusionDir, 'venv', 'Lib', 'site-packages', 'nvidia')); if (await nvidiaDir.exists()) { await for (final d in nvidiaDir.list()) { if (d is Directory) { final binDir = p.join(d.path, 'bin'); final libDir = p.join(d.path, 'lib'); if (await Directory(binDir).exists()) pathEklentileri.add(binDir); if (await Directory(libDir).exists()) pathEklentileri.add(libDir); } } } env['PATH'] = '${pathEklentileri.join(';')};${env['PATH'] ?? ''}'; if (kaynakMutlakYollar.length == 1) { // Tek yüz → one mod, en belirgin yüze uygulanır return await _tekCagri( kaynakYuzler: kaynakMutlakYollar, hedef: hedefAbs, cikis: cikisAbs, env: env, secimMod: 'one', referansPozisyon: null, zamanAsimi: zamanAsimi, ); } // Çoklu yüz → reference mod chain: her yüzü ayrı ayrı uygula Log.bilgi( 'FaceFusion', '${kaynakMutlakYollar.length} yüz CHAIN modunda işlenecek (her kişiye ayrı yüz)', ); final tmpKlasor = await Directory.systemTemp.createTemp('ff_chain_'); String mevcutGiris = hedefAbs; for (int i = 0; i < kaynakMutlakYollar.length; i++) { final araYol = (i == kaynakMutlakYollar.length - 1) ? cikisAbs : p.join(tmpKlasor.path, 'ara_$i.jpg'); final ok = await _tekCagri( kaynakYuzler: [kaynakMutlakYollar[i]], hedef: mevcutGiris, cikis: araYol, env: env, secimMod: 'reference', referansPozisyon: i, zamanAsimi: zamanAsimi, ); if (!ok) { Log.hata('FaceFusion', 'Chain $i başarısız (poz=$i)'); await tmpKlasor.delete(recursive: true); return false; } mevcutGiris = araYol; } await tmpKlasor.delete(recursive: true); return await File(cikisAbs).exists(); } static Future _tekCagri({ required List kaynakYuzler, required String hedef, required String cikis, required Map env, required String secimMod, // one | many | reference int? referansPozisyon, required Duration zamanAsimi, }) async { final args = [ _scriptYolu, 'headless-run', '--source-paths', ...kaynakYuzler, '--target-path', hedef, '--output-path', cikis, '--processors', 'face_swapper', '--execution-providers', 'cuda', '--face-selector-mode', secimMod, '--face-detector-score', '0.5', '--output-image-quality', '95', ]; if (referansPozisyon != null) { args.addAll(['--reference-face-position', referansPozisyon.toString()]); } Log.bilgi( 'FaceFusion', '$secimMod${referansPozisyon != null ? "(poz=$referansPozisyon)" : ""} → ${p.basename(cikis)}', ); try { final r = await Process.run( _venvPython, args, environment: env, workingDirectory: p.join(_faceFusionDir, 'facefusion'), ).timeout(zamanAsimi); if (await File(cikis).exists()) { return true; } final hata = r.stderr.toString(); Log.hata( 'FaceFusion', 'Çıkış oluşmadı. stderr: ${hata.substring(0, hata.length.clamp(0, 500))}', ); return false; } catch (e) { Log.hata('FaceFusion', 'Hata: $e'); return false; } } }