DEV Community

Sesi modelden geri almak: karakter videolarında telaffuzu deterministik yapmak (Bölüm 3)

Bölüm 2'de karakter videosu hattını kurduğumuzu ama bir duvara çarptığımızı yazmıştım: video üreten servis konuşmayı da kendisi sentezliyordu ve Türkçeyi güvenilmez okuyordu. Aynı konu için altı klip çöpe gitti. Bu bölüm o duvarı nasıl yıktığımızın hikâyesi. Kısa cevap: sesi modelden geri aldık. Sorun tam olarak neydi Görsel taraf iyiydi. Karakter tutarlıydı, ışık güzeldi, kamera hareketi doğaldı. Sorun ağızdan çıkanlardı: - Kelime tekrarı: "bilim insanları ile birlikte de bilim insanları" - Modelin metni kendi kendine yeniden yazması: "insülin" geçen bir cümleyi baştan kurup temkinli bilim diline çevirdi - Yabancı kökenli kelimeleri uydurması: "eureka" → "ürika" - Anahtar kelimeyi yutması: "yayla" 0,26 saniyeye sıkışınca "aile" gibi duyuldu Kalite kapımız faster-whisper large-v3 ile çalışıyor: klibi deşifre ediyoruz, kelime olasılığı 0,80'in altına düşen varsa klip reddediliyor. parcalar, _ = model.transcribe(wav, language="tr", word_timestamps=True, beam_size=5) for p in parcalar: for k in p.words or []: if k.probability is not None and k.probability edge-tts (tr-TR) -> Whisper kapısı (0,80) | taban klip (arşiv/model) ----------+--> Wav2Lip -> GFPGAN -> etiket -> kuyruk Ses: kotası olmayan bir motor Site seslendirmesinde (yazıların sesli anlatımı) ticari bir motor kullanıyoruz, arşivin ton tutarlılığı için orayı değiştirmiyoruz. Ama 12 saniyelik video repliği için aylık karakter kotası yakmak saçma. Video dublajı için edge-tts kullanıyoruz: ücretsiz, kotasız, Türkçede iki ses (bir kadın, bir erkek). Tek ayar kritik çıktı: hız. python3 -m edge_tts --voice tr-TR-AhmetNeural --rate=-8% \ --text "Bugün masaya ters bir kart düştü. ..." --write-media ses.mp3 -4% 'te Whisper üç kelimeyi eşiğin altında gördü (kart=0.73 , Para=0.74 , Falın=0.77 ). -8% 'te hepsi geçti. Yani kendi TTS'imiz bile aceleye gelince deşifre edilemiyor; insan kulağı için de aynısı geçerli. Karakterleri ayırmak için aynı sesi perde kaydırmasıyla çeşitlendiriyoruz (--pitch=+10Hz genç ve parlak bir ton veriyor). Dudak: Wav2Lip ve 96 piksel problemi Dudak senkronu için Wav2Lip. CPU'da 12 saniyelik dikey klip yaklaşık 3 dakika, kabul edilebilir. Ama çıktıyı büyütünce sorun görünüyor: Wav2Lip ağız bölgesini 96x96'da üretiyor, 800 piksel genişliğindeki bir yüze yapıştırınca ağız lapa gibi kalıyor. Yayınlanabilir değil, çünkü zaten "yapay duruyor" eleştirisi almış bir üretim hattından bahsediyoruz. Çözüm yüz onarımı: GFPGAN. CPU'da kare başına ~2,7 saniye. Ama naif kullanımı yeni bir sorun doğuruyor: her kareyi bağımsız onarırsan gözler, saç telleri, ten dokusu kare kare oynuyor ve video titriyor. Numara şu: onarımı yalnızca Wav2Lip'in dokunduğu bölgeye harmanla. Dokunduğu bölgeyi bulmak için maske uydurmaya gerek yok, iki karenin farkı zaten maskenin kendisi: fark = cv2.absdiff(cv2.cvtColor(taban_kare, cv2.COLOR_BGR2GRAY), cv2.cvtColor(w2l_kare, cv2.COLOR_BGR2GRAY)) maske = (fark > 8).astype("uint8") * 255 maske = cv2.dilate(maske, np.ones((25, 25), "uint8")) maske = cv2.GaussianBlur(maske, (61, 61), 0).astype("float32") / 255.0 son = taban_kare * (1 - maske) + onarilmis_kare * maske Genişletme ağız çevresine pay bırakıyor, bulanıklaştırma dikişi görünmez yapıyor. Ağız dışındaki her piksel orijinal kalıyor, dolayısıyla titreme yok. Karşılaştırmada onarılmış ağız keskinliği neredeyse kaynak kliple aynı seviyeye geliyor: dişler seçiliyor, sakal dokusu duruyor. Taban klibi sese uydurmak Ses tabandan uzun olursa Wav2Lip kareleri baştan döngüler ve ortada görünür bir kesik oluşur. Bunun yerine tabanı ffmpeg ile uzatıyoruz: hafif yavaşlatma artı sonun tersten oynatılması (bumerang). ffmpeg -y -i taban.mp4 -filter_complex \ "[0:v]hflip,setpts=1.15*PTS,fps=24,split[a][b];\ [b]reverse,trim=start=0:end=2.6,setpts=PTS-STARTPTS[r];[a][r]concat=n=2:v=1" \ -an -c:v libx264 -crf 14 taban-uzun.mp4 Öğrendiklerimiz: %10-15 yavaşlatma fark edilmiyor, fazlası ağır çekim gibi duruyor. Yavaş sahnelerde tersten oynayan kuyruk göze batmıyor. hflip ise aynı tabanı ikinci kez kullanırken "başka çekim" hissi veriyor. Beklenmedik kazanç: reddedilmiş klipler geri geldi Bu hattın en tatlı yan etkisi şu: arşivdeki RED- önekli klipler ses yüzünden reddedilmişti, görüntüleri sağlamdı. Sesi tamamen değiştirdiğimize göre o klipler artık kullanılabilir. Aynı şekilde, indirilmiş ama hiç yayınlanmamış ham çekimler (kütüphane koridoru, akşam ışığında oturma odası, park yolu) birer "sahne kütüphanesi" hâline geldi. Bir karakterin mekânı sabitse (günlük tarot bölümümüzün falcısı hep aynı mumlu masada çekiyor) taban klip her gün yeniden kullanılabiliyor. Sonuç: o bölümün videosu artık tarayıcıya hiç dokunmadan üretiliyor. Bir gecede aynı yöntemle dokuz klip kuyruğa girdi. Sınırını da dürüstçe yazalım: tek karelik fotoğrafı dublajlamak işe yaramıyor. Göz kırpma olmadığı için 12 saniyede ölü bir maske gibi duruyor. Yeni bir mekân gerekiyorsa taban için hâlâ video modeline gidiyoruz. Aynı gecenin iki önbellek kazası Video hattıyla uğraşırken iki klasik ısırdı, ikisini de not düşüyorum. 404 önbelleği. Bir yazının görselini yüklemeyi geciktirmişsiniz ve o arada adres bir kez istenmiş. O "yok" cevabı iki katmanda kilitleniyor: nginx open_file_cache (60 saniye) ve CDN (4 saat). Dosyayı yükledikten sonra CDN önbelleğini temizlemek yetmiyor, çünkü temizlikten sonraki ilk istek nginx'in hâlâ elindeki negatif kaydı yeniden önbelleğe alıyor. Teşhis basit: curl -sI cf-cache-status: HIT derken ?x=1 200 dönüyorsa budur. Doğru sıra: yükle, nginx'i reload et, sonra CDN'i temizle. Beslemenin kaynak doğrusu. Podcast beslemesini üreten script yerel audio/ klasörünü tarıyordu. Seslendirme toplu işi ise mp3'ü geçici klasöre yazıp doğrudan sunucuya gönderiyordu. Sonuç: sunucuda 31 bölüm, yerelde 26, ve besleme her üretim turunda canlı feed'i 26'ya geri çekiyordu. Beş bölüm dağıtım platformuna hiç düşmemiş. Düzeltme tek satır (mp3'ü yerel klasöre yaz, oradan gönder) ama ders daha büyük: bir dosya iki yerde yaşıyorsa, hangisinin doğruyu söylediğini yazıya dökmek gerekiyor. Sayılar - Dublaj hattı, 12 saniyelik dikey klip, 32 çekirdek CPU: Wav2Lip ~3 dk, GFPGAN ~15-25 dk, kodlama ~1 dk - Whisper kapısı: large-v3 , eşik 0,80. Küçük modeller b/d gibi sesleri karıştırıp yanlış alarm veriyor, o yüzden küçültmüyoruz - TTS: 12 saniyelik replik için 1 istek, kota yok - Reddedilen klip sayısı bu hatta geçtikten sonra: sıfır (ses artık deterministik) Sırada ne var Yeni mekân üretimi hâlâ tarayıcıdaki video servisine bağlı, yani hattın tek insan/oturum bağımlılığı orada. Bir sonraki bölümde muhtemelen o bağımlılığı anlatacağım: ücretsiz API'sini kapatan bir platforma, oturum açık bir sekme üzerinden nasıl yayın yapılıyor ve bu neden hem çalışıyor hem de kırılgan. Top comments (0)

Read on DEV Community ↗ ← Back to News

Comments

No comments yet. Start the discussion.