import 'dart:io'; import 'dart:typed_data'; import 'package:path/path.dart' as p; import '../../cekirdek/log.dart'; import 'ffmpeg_servis.dart'; class KareCikarici { /// Son birkaç kareyi çıkarıp Laplacian skoru ile en keskinini seçer. static Future enKesinKare({ required String videoYolu, required String hedefYolu, int adaySayisi = 8, int sondanAtla = 2, }) async { final sure = await FfmpegServis.videoSuresi(videoYolu); final fps = await FfmpegServis.videoFps(videoYolu); if (sure <= 0) { await sonKareCikar(videoYolu, hedefYolu, sure: 0); return hedefYolu; } final tmpDir = await Directory.systemTemp.createTemp('en_kesin_kare_'); final adaylar = []; for (int i = 0; i < adaySayisi; i++) { final ofs = sondanAtla + adaySayisi - 1 - i; var seek = sure - (ofs / fps) - 0.01; if (seek < 0) seek = 0; final ay = p.join( tmpDir.path, 'aday_${i.toString().padLeft(2, '0')}.jpg', ); final r = await Process.run(FfmpegServis.ffmpegYolu, [ '-y', '-ss', seek.toString(), '-i', videoYolu, '-frames:v', '1', '-q:v', '1', ay, ]); if (r.exitCode == 0 && await File(ay).exists()) { adaylar.add(ay); } } if (adaylar.isEmpty) { await tmpDir.delete(recursive: true); await sonKareCikar(videoYolu, hedefYolu, sure: sure); return hedefYolu; } String enIyi = adaylar.last; double enYuksekSkor = -1; for (final ay in adaylar) { final skor = await _laplacianSkor(ay); if (skor > enYuksekSkor) { enYuksekSkor = skor; enIyi = ay; } } await File(enIyi).copy(hedefYolu); await tmpDir.delete(recursive: true); Log.bilgi( 'Kare', 'En keskin kare seçildi (skor: ${enYuksekSkor.toStringAsFixed(0)})', ); return hedefYolu; } static Future sonKareCikar( String video, String cikis, { required double sure, }) async { final s = await (sure > 0 ? Future.value(sure) : FfmpegServis.videoSuresi(video)); final seek = (s - 0.05).clamp(0, double.infinity); final r = await Process.run(FfmpegServis.ffmpegYolu, [ '-y', '-ss', seek.toString(), '-i', video, '-frames:v', '1', '-q:v', '1', cikis, ]); if (r.exitCode != 0) { Log.hata('Kare', 'Son kare çıkarılamadı: ${r.stderr}'); } } static Future _laplacianSkor(String resimYolu) async { final pr = await Process.start(FfmpegServis.ffmpegYolu, [ '-y', '-i', resimYolu, '-pix_fmt', 'gray', '-f', 'rawvideo', 'pipe:1', ]); final bytes = BytesBuilder(); pr.stdout.listen(bytes.add); pr.stderr.drain(); await pr.exitCode; final raw = bytes.toBytes(); if (raw.length < 100) return 0; int toplam = 0; int sayim = 0; for (int j = 0; j < raw.length - 1; j++) { final d = (raw[j] - raw[j + 1]).abs(); toplam += d * d; sayim++; } return sayim > 0 ? toplam / sayim : 0; } /// Tek bir kareyi belirli bir zamanda çıkar (thumbnail için). static Future belirliZamandaKare( String videoYolu, String cikisYolu, double saniye, ) async { await Process.run(FfmpegServis.ffmpegYolu, [ '-y', '-ss', saniye.toString(), '-i', videoYolu, '-frames:v', '1', '-q:v', '2', cikisYolu, ]); } }