<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Guray</title>
    <description>The latest articles on DEV Community by Guray (@gurayta).</description>
    <link>https://dev.to/gurayta</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3876249%2Fb8af69ea-dcfc-475b-8571-ed032654d1c6.jpg</url>
      <title>DEV Community: Guray</title>
      <link>https://dev.to/gurayta</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/gurayta"/>
    <language>en</language>
    <item>
      <title>Sesi modelden geri almak: karakter videolarında telaffuzu deterministik yapmak (Bölüm 3)</title>
      <dc:creator>Guray</dc:creator>
      <pubDate>Fri, 14 Aug 2026 01:15:11 +0000</pubDate>
      <link>https://dev.to/gurayta/sesi-modelden-geri-almak-karakter-videolarinda-telaffuzu-deterministik-yapmak-bolum-3-45of</link>
      <guid>https://dev.to/gurayta/sesi-modelden-geri-almak-karakter-videolarinda-telaffuzu-deterministik-yapmak-bolum-3-45of</guid>
      <description>&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sorun tam olarak neydi
&lt;/h2&gt;

&lt;p&gt;Görsel taraf iyiydi. Karakter tutarlıydı, ışık güzeldi, kamera hareketi doğaldı. Sorun ağızdan çıkanlardı:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Kelime tekrarı: "bilim insanları ile birlikte de bilim insanları"&lt;/li&gt;
&lt;li&gt;Modelin metni kendi kendine yeniden yazması: "insülin" geçen bir cümleyi baştan kurup temkinli bilim diline çevirdi&lt;/li&gt;
&lt;li&gt;Yabancı kökenli kelimeleri uydurması: "eureka" → "ürika"&lt;/li&gt;
&lt;li&gt;Anahtar kelimeyi yutması: "yayla" 0,26 saniyeye sıkışınca "aile" gibi duyuldu&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Kalite kapımız &lt;code&gt;faster-whisper large-v3&lt;/code&gt; ile çalışıyor: klibi deşifre ediyoruz, kelime olasılığı 0,80'in altına düşen varsa klip reddediliyor.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;parcalar&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transcribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wav&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;language&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tr&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;word_timestamps&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;beam_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;parcalar&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;words&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;[]:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;probability&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;probability&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;0.80&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;zayif&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;probability&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bu kapı işini yapıyordu ama üretimin önünü tıkıyordu. Prompt mühendisliğiyle çözmeye çalıştık: "do not reword", "no stutter, no repeated words", "speak slowly, over-articulating every syllable", süreyi 10 saniyeden 12'ye çıkarmak. Hepsi reddi azalttı, hiçbiri bitirmedi. Çünkü sorun prompt değil, mimari: konuşmayı üretmesini istediğimiz model, aynı zamanda görüntüyü üreten model.&lt;/p&gt;

&lt;h2&gt;
  
  
  Önce yanlış yola saptık: GPU aramak
&lt;/h2&gt;

&lt;p&gt;İlk içgüdü şuydu: ses güdümlü video modelleri var (Wan2.2-S2V, HunyuanVideo-Avatar, EchoMimicV3, OmniAvatar). Bunlara hazır ses veriyorsun, dudağı senin sesine göre üretiyorlar. Sunucuda GPU yok, o yüzden ücretsiz GPU saatleri olan bir not defteri servisine EchoMimicV3 kurmayı denedik.&lt;/p&gt;

&lt;p&gt;Üç tur sürdü ve üçü de öğretici biçimde patladı:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;ffmpeg&lt;/code&gt; veri kümesindeki wav dosyasını açamadı. Kırpmayı Python tarafına (librosa + soundfile, 16 kHz yeniden örnekleme) taşıdık.&lt;/li&gt;
&lt;li&gt;Dosya yolları beklediğimiz yere bağlanmadı. Not defterini kendini iyileştirecek hâle getirdik: &lt;code&gt;/kaggle/input&lt;/code&gt; altını &lt;code&gt;os.walk&lt;/code&gt; ile tarayıp ilk &lt;code&gt;.wav&lt;/code&gt; ve &lt;code&gt;.jpg&lt;/code&gt;'yi bulan bir bölüm.&lt;/li&gt;
&lt;li&gt;Model yüklendi, çekirdek sessizce öldü. Dönüş kodu -9, yani SIGKILL.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Üçüncü turda gerçek teşhis log'un içindeydi:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;### missing keys: 220;
['audio_injection.proj1.weight', 'audio_injection.proj1.bias', ...]
DONUS KODU: -9
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Yani ses enjeksiyon katmanının 220 ağırlığı hiç yüklenmemiş. Taban video modelinin ağırlıkları gelmiş, ses koşullama ağırlıkları gelmemiş. O hâliyle çalışsa bile ses görüntüyü sürmeyecekti. Buna ek olarak bellekten öldürülme. Bu, saatler yiyecek bir yapılandırma kazısı.&lt;/p&gt;

&lt;p&gt;Mühendislik kararı: bu yolu kapattık. Not olarak duruyor, ileride GPU'lu bir ortam olursa geri dönülür.&lt;/p&gt;

&lt;h2&gt;
  
  
  Doğru yol: problemi ikiye ayırmak
&lt;/h2&gt;

&lt;p&gt;Sonra bariz olanı gördük. Videodan istediğimiz iki şey var ve bunlar farklı zorluklar:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Sahne ve kimlik&lt;/strong&gt;: karakterin yüzü, mekân, ışık, kamera. Bunda video modeli çok iyi.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Konuşma&lt;/strong&gt;: doğru kelimeler, doğru telaffuz, doğru tempo. Bunda kötü.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ayıralım. Görüntüyü model üretsin, konuşmayı bizim TTS motorumuz okusun, dudağı sonradan sese uyduralım. Böylece telaffuz determinist hâle gelir: metinde ne yazıyorsa o okunur.&lt;/p&gt;

&lt;p&gt;Hat şöyle:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;metin -&amp;gt; edge-tts (tr-TR) -&amp;gt; Whisper kapısı (0,80)
                                   |
taban klip (arşiv/model) ----------+--&amp;gt; Wav2Lip -&amp;gt; GFPGAN -&amp;gt; etiket -&amp;gt; kuyruk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Ses: kotası olmayan bir motor
&lt;/h3&gt;

&lt;p&gt;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 &lt;code&gt;edge-tts&lt;/code&gt; kullanıyoruz: ücretsiz, kotasız, Türkçede iki ses (bir kadın, bir erkek).&lt;/p&gt;

&lt;p&gt;Tek ayar kritik çıktı: hız.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 &lt;span class="nt"&gt;-m&lt;/span&gt; edge_tts &lt;span class="nt"&gt;--voice&lt;/span&gt; tr-TR-AhmetNeural &lt;span class="nt"&gt;--rate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nt"&gt;-8&lt;/span&gt;% &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--text&lt;/span&gt; &lt;span class="s2"&gt;"Bugün masaya ters bir kart düştü. ..."&lt;/span&gt; &lt;span class="nt"&gt;--write-media&lt;/span&gt; ses.mp3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;-4%&lt;/code&gt;'te Whisper üç kelimeyi eşiğin altında gördü (&lt;code&gt;kart=0.73&lt;/code&gt;, &lt;code&gt;Para=0.74&lt;/code&gt;, &lt;code&gt;Falın=0.77&lt;/code&gt;). &lt;code&gt;-8%&lt;/code&gt;'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 (&lt;code&gt;--pitch=+10Hz&lt;/code&gt; genç ve parlak bir ton veriyor).&lt;/p&gt;

&lt;h3&gt;
  
  
  Dudak: Wav2Lip ve 96 piksel problemi
&lt;/h3&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Çö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.&lt;/p&gt;

&lt;p&gt;Numara şu: onarımı &lt;strong&gt;yalnızca Wav2Lip'in dokunduğu bölgeye&lt;/strong&gt; harmanla. Dokunduğu bölgeyi bulmak için maske uydurmaya gerek yok, iki karenin farkı zaten maskenin kendisi:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;fark&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;absdiff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cvtColor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;taban_kare&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;COLOR_BGR2GRAY&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                    &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cvtColor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w2l_kare&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;COLOR_BGR2GRAY&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;maske&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fark&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;astype&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;uint8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;
&lt;span class="n"&gt;maske&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dilate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;maske&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ones&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;uint8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;maske&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;GaussianBlur&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;maske&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;61&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;61&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;astype&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;float32&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;255.0&lt;/span&gt;

&lt;span class="n"&gt;son&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;taban_kare&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;maske&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;onarilmis_kare&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;maske&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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.&lt;/p&gt;

&lt;h3&gt;
  
  
  Taban klibi sese uydurmak
&lt;/h3&gt;

&lt;p&gt;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).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffmpeg &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; taban.mp4 &lt;span class="nt"&gt;-filter_complex&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
 &lt;span class="s2"&gt;"[0:v]hflip,setpts=1.15*PTS,fps=24,split[a][b];&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
  [b]reverse,trim=start=0:end=2.6,setpts=PTS-STARTPTS[r];[a][r]concat=n=2:v=1"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
 &lt;span class="nt"&gt;-an&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt;:v libx264 &lt;span class="nt"&gt;-crf&lt;/span&gt; 14 taban-uzun.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Öğrendiklerimiz: %10-15 yavaşlatma fark edilmiyor, fazlası ağır çekim gibi duruyor. Yavaş sahnelerde tersten oynayan kuyruk göze batmıyor. &lt;code&gt;hflip&lt;/code&gt; ise aynı tabanı ikinci kez kullanırken "başka çekim" hissi veriyor.&lt;/p&gt;

&lt;h2&gt;
  
  
  Beklenmedik kazanç: reddedilmiş klipler geri geldi
&lt;/h2&gt;

&lt;p&gt;Bu hattın en tatlı yan etkisi şu: arşivdeki &lt;code&gt;RED-&lt;/code&gt; ö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.&lt;/p&gt;

&lt;p&gt;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 &lt;strong&gt;tarayıcıya hiç dokunmadan&lt;/strong&gt; üretiliyor. Bir gecede aynı yöntemle dokuz klip kuyruğa girdi.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Aynı gecenin iki önbellek kazası
&lt;/h2&gt;

&lt;p&gt;Video hattıyla uğraşırken iki klasik ısırdı, ikisini de not düşüyorum.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;404 önbelleği.&lt;/strong&gt; Bir yazının görselini yüklemeyi geciktirmişsiniz ve o arada adres bir kez istenmiş. O "yok" cevabı iki katmanda kilitleniyor: nginx &lt;code&gt;open_file_cache&lt;/code&gt; (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: &lt;code&gt;curl -sI &amp;lt;url&amp;gt;&lt;/code&gt; &lt;code&gt;cf-cache-status: HIT&lt;/code&gt; derken &lt;code&gt;&amp;lt;url&amp;gt;?x=1&lt;/code&gt; 200 dönüyorsa budur. Doğru sıra: yükle, nginx'i reload et, sonra CDN'i temizle.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Beslemenin kaynak doğrusu.&lt;/strong&gt; Podcast beslemesini üreten script yerel &lt;code&gt;audio/&lt;/code&gt; 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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sayılar
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Dublaj hattı, 12 saniyelik dikey klip, 32 çekirdek CPU: Wav2Lip ~3 dk, GFPGAN ~15-25 dk, kodlama ~1 dk&lt;/li&gt;
&lt;li&gt;Whisper kapısı: &lt;code&gt;large-v3&lt;/code&gt;, eşik 0,80. Küçük modeller b/d gibi sesleri karıştırıp yanlış alarm veriyor, o yüzden küçültmüyoruz&lt;/li&gt;
&lt;li&gt;TTS: 12 saniyelik replik için 1 istek, kota yok&lt;/li&gt;
&lt;li&gt;Reddedilen klip sayısı bu hatta geçtikten sonra: sıfır (ses artık deterministik)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Sırada ne var
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>video</category>
      <category>python</category>
      <category>tts</category>
    </item>
    <item>
      <title>Dokuz sanal sunucu, üç platform, bir kota duvarı: karakter videosu hattını kurmak (Bölüm 2)</title>
      <dc:creator>Guray</dc:creator>
      <pubDate>Fri, 14 Aug 2026 00:08:18 +0000</pubDate>
      <link>https://dev.to/gurayta/dokuz-sanal-sunucu-uc-platform-bir-kota-duvari-karakter-videosu-hattini-kurmak-bolum-2-30ma</link>
      <guid>https://dev.to/gurayta/dokuz-sanal-sunucu-uc-platform-bir-kota-duvari-karakter-videosu-hattini-kurmak-bolum-2-30ma</guid>
      <description>&lt;p&gt;Birinci bölümde bir haber sitesinin yayın akışını ajana devrettiğimi yazmıştım. O yazıdan sonra sistemin en kırılgan yerini kurdum: &lt;strong&gt;sosyal medyaya konuşan sanal sunucular&lt;/strong&gt;. Dokuz kategorinin dokuz karakteri var, her biri kendi videosuyla kendi bölümünü tanıtıyor.&lt;/p&gt;

&lt;p&gt;Bu yazı o hattın kurulum günlüğü. İçinde çalışan kod da var, çöpe giden yedi deneme de.&lt;/p&gt;

&lt;h2&gt;
  
  
  Neden karakter?
&lt;/h2&gt;

&lt;p&gt;Statik bir yazı linkini X'e atınca ölçüm net: kart önizlemesi görünür, kimse durmaz. Dikey videoda konuşan bir insan varsa akış duruyor. Elimde gerçek sunucu yok, o yüzden karakterleri üretiyoruz: Elif (bilim, psikoloji), Arda (oyun), Doruk (doğa ve kamp), Dr. Sinan (tıp), Defne (kitap), Süreyya (tarot), Meriç (dünya basını), Elvan (arkeoloji), Duru (güzellik).&lt;/p&gt;

&lt;p&gt;Kural basit ve sabit: &lt;strong&gt;kategori → karakter eşlemesi değişmez.&lt;/strong&gt; Aynı etiket her zaman aynı yüz ve aynı sesle geliyor. Takipçi ikinci videoda karakteri tanıyor.&lt;/p&gt;

&lt;p&gt;Üretim hattı şöyle:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;konu seçimi → yazı yayını → başlangıç karesi (t2i)
   → konuşma metni (4 kısa cümle)
   → i2v video (12 sn, ses dahil)
   → Whisper doğrulama (eşik 0,80)
   → kafa1milyon.com etiketi (ffmpeg drawtext)
   → X + Instagram + YouTube kuyruğu
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Kritik yer dördüncü satır. Onu anlatayım.&lt;/p&gt;

&lt;h2&gt;
  
  
  Telaffuz savaşı: modelin metni "düzeltmesi"
&lt;/h2&gt;

&lt;p&gt;Video modeline Türkçe bir cümle verip "bunu oku" dediğinizde, model okumakla kalmıyor. Metni kendi kendine yeniden yazıyor.&lt;/p&gt;

&lt;p&gt;Bir inek videosu altı kez çöpe gitti. Model "bilim insanları ile birlikte de bilim insanları" diye kelimeyi tekrarladı. Tıp videosunda "insülin" kelimesini "insülün" diye söyledi ve cümleyi kendi kendine "Tip 1 diyabette beta hücreleri..." diye temkinli bilim diline çevirdi. Bir başkasında "eureka" kelimesi "ürika" oldu.&lt;/p&gt;

&lt;p&gt;Yedi denemeden sonra kural dosyasına şunlar girdi:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Konuşma metni &lt;strong&gt;en fazla 4 cümle&lt;/strong&gt;, cümle başına 4-7 kelime.&lt;/li&gt;
&lt;li&gt;Yabancı kökenli ve teknik kelime yok. "İnsülin" yerine "şekeri ayarlayan hücreler".&lt;/li&gt;
&lt;li&gt;İddialı cümle yok. Model abartıyı düzeltmeye çalışıp metni bozuyor; cümleyi baştan dürüst kurmak gerekiyor.&lt;/li&gt;
&lt;li&gt;Prompt'a "do not reword or rephrase anything, read the text exactly as written", "no stutter, no repeated words" gibi maddeler eklendi.&lt;/li&gt;
&lt;li&gt;Süre 10 saniye değil &lt;strong&gt;12 saniye&lt;/strong&gt; seçiliyor. Dört cümle 10 saniyeye sıkışınca kelimeler yutuluyor.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;En ilginç maddeyi en sona bıraktım. Bir kamp videosunda "İstanbul'a yakın bir yayla var" cümlesindeki "yayla" kelimesi 0,26 saniyeye sıkıştı ve "aile" gibi duyuldu. Anahtar kelime cümlenin başına gelince model üzerinden hızlı geçiyor. Kural: &lt;strong&gt;konunun anahtar kelimesi cümlenin ortasına, çevresinde nefes olacak şekilde konur.&lt;/strong&gt; "Bu yayla İstanbul'a çok yakın" gibi.&lt;/p&gt;

&lt;h2&gt;
  
  
  Doğrulama kapısı: kulakla değil olasılıkla
&lt;/h2&gt;

&lt;p&gt;"Kulağıma tuhaf geldi" ölçüt değil. Her klip üretimden sonra transkripsiyona giriyor ve kelime bazlı olasılıklara bakıyoruz:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;faster_whisper&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;WhisperModel&lt;/span&gt;

&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;WhisperModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;large-v3&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cpu&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;compute_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;int8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;segments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transcribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ses&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;language&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tr&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;word_timestamps&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;segments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;words&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;probability&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;0.80&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ZAYIF  &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="si"&gt;!r}&lt;/span&gt;&lt;span class="s"&gt;  p=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;probability&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;  &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                  &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;-&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; sn&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bir kelimenin olasılığı 0,80'in altındaysa klip reddediliyor ve metin yeniden yazılıyor. &lt;code&gt;small&lt;/code&gt; ve &lt;code&gt;medium&lt;/code&gt; modeller bu işte yanlış alarm veriyor: b/d gibi sesleri karıştırıp temiz klipleri suçluyorlar. Sadece &lt;code&gt;large-v3&lt;/code&gt; kullanılıyor.&lt;/p&gt;

&lt;p&gt;Kapı bir kez de "yanlış" çalıştı: "Dr." kısaltması 0,76 döndü. Sesi tek tek dinlettiğimde model gerçekten "Doktor Sinan" diyordu, sorun kısaltmanın kendisiydi. Bu tür durumlar için tek istisna kuralı var, ama istisna gerekçesiz yazılmıyor.&lt;/p&gt;

&lt;h2&gt;
  
  
  Etiket: köşeyi neden değiştirmiyoruz
&lt;/h2&gt;

&lt;p&gt;Her klibe küçük bir &lt;code&gt;kafa1milyon.com&lt;/code&gt; yazısı basılıyor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffmpeg &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$GIRDI&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-vf&lt;/span&gt; &lt;span class="s2"&gt;"drawtext=fontfile=&lt;/span&gt;&lt;span class="nv"&gt;$FONT&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
text='kafa1milyon.com':&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
fontcolor=white@0.78:fontsize=h/40:&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
shadowcolor=black@0.45:shadowx=2:shadowy=2:&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
x=w*0.035:y=h*0.030"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-c&lt;/span&gt;:v libx264 &lt;span class="nt"&gt;-preset&lt;/span&gt; medium &lt;span class="nt"&gt;-crf&lt;/span&gt; 19 &lt;span class="nt"&gt;-c&lt;/span&gt;:a copy &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CIKTI&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Punto sabit değil, &lt;code&gt;h/40&lt;/code&gt;. Konum sol üst ve tartışmaya kapalı: dikey formatta Reels ve Shorts arayüzünün kapatmadığı tek güvenli alan orası. Alt kısmı açıklama ve butonlar yiyor, sağ şeridi etkileşim ikonları kaplıyor.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tıklanabilir link: platform başına ayrı duvar
&lt;/h2&gt;

&lt;p&gt;Üç platform, üç ayrı problem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;X&lt;/strong&gt;: intent URL ile metin ve link doldurulabiliyor. Videolu postta link ana posta konmuyor, ilk yanıta gidiyor.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Instagram&lt;/strong&gt;: gönderi açıklamasındaki adres tıklanamaz, nokta. Çözüm hikâyedeki link etiketi:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;instagrapi.types&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;StoryLink&lt;/span&gt;

&lt;span class="n"&gt;baglanti&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;StoryLink&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;webUri&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.82&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.09&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;cl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;video_upload_to_story&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;video&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;links&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;baglanti&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reel paylaşıldıktan hemen sonra aynı video, link etiketiyle hikâyeye de düşüyor. Ayrıca profil biosuna koyulacak bir &lt;code&gt;/link/&lt;/code&gt; sayfası üretiliyor ve saatlik bir timer onu güncelliyor.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;YouTube&lt;/strong&gt;: açıklamadaki linkler tıklanabilir olsun diye kanalın telefon doğrulaması gerekiyor. Kod yazarak çözülecek bir şey değil; doğrulama tamamlanana kadar açıklamada okuru kanal profilindeki bağlantıya yönlendiriyoruz.&lt;/p&gt;

&lt;p&gt;Üç platformda tek bir "link koy" fonksiyonu yazamamak, bu işin en öğretici tarafıydı.&lt;/p&gt;

&lt;h2&gt;
  
  
  Kota arkeolojisi: ücretsiz katmanı tersine mühendislik
&lt;/h2&gt;

&lt;p&gt;Seslendirme tarafında ticari kotam bitti ve arşivde 76 sessiz yazı birikti. Ücretsiz TTS kotasının nasıl davrandığını ölçerek çıkarmak zorunda kaldım.&lt;/p&gt;

&lt;p&gt;Önce 429 gövdesini okuyup gerçek metriği buldum:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="err"&gt;generate_content_free_tier_requests&lt;/span&gt;
&lt;span class="err"&gt;GenerateRequestsPerDayPerProjectPerModel-FreeTier&lt;/span&gt;    &lt;span class="py"&gt;deger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cümlenin içinde üç kelime var ve üçü de önemli: &lt;strong&gt;PerProject&lt;/strong&gt;, &lt;strong&gt;PerModel&lt;/strong&gt;, &lt;strong&gt;PerDay&lt;/strong&gt;. Yani limit hesap başına değil, proje ve model başına. İki projeyle 20 istek.&lt;/p&gt;

&lt;p&gt;Buradan üç hamle çıktı:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Parça boyu.&lt;/strong&gt; Modelin çıktı sınırı 16.384 token, saniyede 32 token, yani ~8,3 dakika ses. Türkçe'de bu kabaca 6.500 karakter. Parçalar 3.500 karakterde kesiliyordu; 5.800'e çıkardım. Çoğu yazı artık iki istek yerine tek istekte bitiyor. 9.400 karakteri tek istekte denemek ise sessiz başarısızlıkla dönüyor: yanıt geliyor ama içinde ses parçası olmuyor.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Model havuzu.&lt;/strong&gt; Kota model başına olduğu için model listesini taradım ve &lt;code&gt;batchGenerateContent&lt;/code&gt; destekleyen yeni bir TTS modeli buldum. Ayrı kova, taze kota. Kısa cümlede kusursuz çalıştı, 900 karakter ve üstünde ise ne unary ne streaming uçtan yanıt döndü; beş dakika askıda kalıp zaman aşımına düştü. Preview modellerin bedeli bu. Havuzdan çıkardım ama gerekçesini koda yazdım:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# gemini-3.1-flash-tts-preview ayri bir kota kovasi ve cazip gorunuyor ama
# 14 Agu 2026'da denendi: 50 karakterlik cumlede calisiyor, 900 karakter ve
# ustunde ne unary ne streaming ucta yanit donuyor. Preview duzelirse eklenir.
&lt;/span&gt;&lt;span class="n"&gt;MODELLER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gemini-2.5-flash-preview-tts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Toplu iş.&lt;/strong&gt; Batch API bu modelde destekli görünüyor, çağrı ise &lt;code&gt;FAILED_PRECONDITION&lt;/code&gt; veriyor: ücretsiz katmanda kapalı.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;En faydalı bulgu tesadüfen çıktı. Akşam kotayı bitirmiş bir anahtar, gece yarısından sonra tek bir istek kabul etti, sonra yine kapandı. Demek ki günlük sayaç gece bir kerede sıfırlanmıyor, &lt;strong&gt;kayan pencerede eski istekler düştükçe slot boşalıyor&lt;/strong&gt;. Bunun üzerine "günde iki tur" olan zamanlayıcıyı iki saate indirdim:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight systemd"&gt;&lt;code&gt;&lt;span class="nt"&gt;OnCalendar&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;*-*-* 00/2:10:00
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Kota kuruysa betik saniyeler içinde ve temiz dönüyor. Boşalan her slot aynı gün kullanılıyor.&lt;/p&gt;

&lt;p&gt;Bir de hata kodu ayrımı ekledim. Eskiden herhangi bir başarısızlık bütün turu kesiyordu:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;KOD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$?&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$KOD&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"3"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;          &lt;span class="c"&gt;# gunluk kota bitti: tur biter&lt;/span&gt;
  &lt;span class="nb"&gt;break
&lt;/span&gt;&lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$KOD&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s2"&gt;"0"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;       &lt;span class="c"&gt;# gecici hata: bu yaziyi atla, devam et&lt;/span&gt;
  &lt;span class="k"&gt;continue
fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Paylaşımı ajandan koparmak
&lt;/h2&gt;

&lt;p&gt;Bu hattın son kusuru şuydu: postları ajan atıyordu, yani oturum kapalıysa post yoktu.&lt;/p&gt;

&lt;p&gt;Instagram tarafı API üzerinden çalıştığı için oradaki kuyruğu tamamen sisteme devrettim. Kurallar artık betiğin içinde:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;PENCERE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;     &lt;span class="c1"&gt;# yayin penceresi, TSI
&lt;/span&gt;&lt;span class="n"&gt;GUNLUK_TAVAN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
&lt;span class="n"&gt;ARA_DAKIKA&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;75&lt;/span&gt;        &lt;span class="c1"&gt;# ardisik postlar arasi en az bosluk
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;zaman_uygun&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;oge&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;simdi&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TSI&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;basla&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt; &lt;span class="nf"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;oge&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;{}).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kat&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Tarot&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;PENCERE&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;basla&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;simdi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hour&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;PENCERE&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pencere disi (&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;simdi&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="n"&gt;H&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="n"&gt;M&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Timer yarım saatte bir ateşliyor, karar betiğe ait. Tarot 09:00 istisnası da burada yaşıyor: günlük fal geciktirilmiyor, videosu yetişmezse o günün görseliyle atılıyor.&lt;/p&gt;

&lt;p&gt;Bir de küçük ama pahalı bir dersin kodu var: paylaşımdan önce her yazı adresi doğrulanıyor. Bir gün ezberden yazılmış bir slug 404 verdi ve postlar elle silinmek zorunda kaldı. Artık kuyruktaki adres kullanılıyor ve yine de HTTP durumu kontrol ediliyor.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tarayıcıya bağımlılık ve bir sonraki adım
&lt;/h2&gt;

&lt;p&gt;Hattın en zayıf halkası hâlâ video üretiminin tarayıcı oturumuna bağlı olması. Oturum düşünce üretim duruyor.&lt;/p&gt;

&lt;p&gt;Şu an denediğim yol: sesi kendimiz üretip, karakteri ses güdümlü bir modelle oynatmak. Böylece telaffuz riski tamamen ortadan kalkıyor, çünkü konuşmayı video modeli üretmiyor. Sunucuda GPU yok, o yüzden hesap ücretsiz GPU sağlayan bir platformda ve tarayıcı olmadan, API ile:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kaggle kernels push &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;        &lt;span class="c"&gt;# notebook + GPU + internet&lt;/span&gt;
kaggle kernels status &amp;lt;ref&amp;gt;
kaggle kernels output &amp;lt;ref&amp;gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;İlk tur sessizce öldü: log'da traceback yoktu, model yükleme mesajlarından sonra çıktı bitiyordu. Traceback olmadan ölmek genelde tek bir şey demek, bellek. İkinci turda bellek kipleri sırayla deniyor ve her denemenin dönüş kodu basılıyor; &lt;code&gt;-9&lt;/code&gt; görürsem tanı kesinleşecek.&lt;/p&gt;

&lt;p&gt;Sonucu bir sonraki bölümde yazacağım. Çalışırsa hat tarayıcıdan tamamen kurtulacak; çalışmazsa onu da yazacağım.&lt;/p&gt;




&lt;p&gt;Bu seride kod kadar başarısız denemeleri de tutuyorum, çünkü altı ay sonra "bunu neden böyle yaptık" sorusunun cevabı orada duruyor.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>automation</category>
      <category>python</category>
      <category>devops</category>
    </item>
    <item>
      <title>Revizyon 1: Statik yayın sitesine bir günde deneysel sosyal katman — park bankı, caz kulübü, fısıltı bulutu</title>
      <dc:creator>Guray</dc:creator>
      <pubDate>Wed, 12 Aug 2026 20:53:35 +0000</pubDate>
      <link>https://dev.to/gurayta/revizyon-1-statik-yayin-sitesine-bir-gunde-deneysel-sosyal-katman-park-banki-caz-kulubu-5730</link>
      <guid>https://dev.to/gurayta/revizyon-1-statik-yayin-sitesine-bir-gunde-deneysel-sosyal-katman-park-banki-caz-kulubu-5730</guid>
      <description>&lt;p&gt;&lt;em&gt;Bu serinin yazarı Güray: AI geliştirmecisi, prompt ve analiz mühendisi. Kodun büyük kısmını bir LLM ajanıyla pair programming yaparak yazıyorum. Seri, &lt;a href="https://kafa1milyon.com" rel="noopener noreferrer"&gt;kafa1milyon.com&lt;/a&gt;'un perde arkasını mühendis gözüyle belgeliyor. &lt;a href="https://dev.to/gurayta/bir-web-sitesini-yapay-zekaya-teslim-ettim-kafa1milyonun-otonom-yayin-mimarisi-bolum-1-1klc"&gt;Bölüm 1'de&lt;/a&gt; genel mimariyi anlatmıştım.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Önce dürüst bir uyarı: bu bir revizyon günlüğü
&lt;/h2&gt;

&lt;p&gt;Bu yazıdaki her şey &lt;strong&gt;tek bir günde&lt;/strong&gt; eklendi ve tamamı &lt;strong&gt;deneysel&lt;/strong&gt;. Hepsi canlıda test edilecek; bir kısmı kalıcılaşacak, bir kısmı kaldırılacak, bir kısmı da bambaşka versiyonlara evrilecek. Bunu pazarlama alçakgönüllülüğü olarak değil, mühendislik gerçeği olarak yazıyorum: statik bir yayın sitesine sosyal ve interaktif katman eklemek, hangi özelliğin "mekânın ruhuna" oturacağını önceden bilemeyeceğiniz bir deney alanıdır. Ölçüp, izleyip, budayacağız.&lt;/p&gt;

&lt;p&gt;Günün sonunda üç büyük blok çıktı ortaya:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Okur katmanı&lt;/strong&gt;: vurgulama, not, okuma modu, sözlük, filtreler, sesli oynatıcı ve bir dizi "sessiz" etkileşim.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Park Bankı&lt;/strong&gt;: aynı yazıyı okuyan iki anonim okuru eşleştiren mikro sohbet.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Caz Kulübü&lt;/strong&gt;: masalara oturulan, müzik paylaşılan, sesli sohbete geçilebilen dijital bir mekân. (Gün biterken kapısına kilit vurup rezervasyon moduna aldık; birazdan anlatacağım.)&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Kısıt seti: veritabanı yok, WebSocket yok, hesap yok
&lt;/h2&gt;

&lt;p&gt;Site tamamen statik HTML + nginx + Cloudflare. Bu revizyonda da kural bozulmadı. Sosyal özelliklerin tamamı şu üç ilkeyle yazıldı:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Bellek içi Python backend'ler&lt;/strong&gt; (stdlib-only, &lt;code&gt;ThreadingHTTPServer&lt;/code&gt;): park bankı ve kulüp API'leri süreç belleğinde yaşar. Süreç yeniden başlarsa masalar boşalır. Bu bir eksik değil, tasarım: "o akşam orada olanlar" geçiciliği mekânın kimliği.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WebSocket yerine kısa yoklama (short polling)&lt;/strong&gt;: 2-3 saniyede bir &lt;code&gt;GET&lt;/code&gt;. Cloudflare arkasında WS oturumu yönetmek yerine, olay listesini &lt;code&gt;ts &amp;gt; sonra&lt;/code&gt; filtresiyle artımlı çekiyoruz. 30 eşzamanlı kullanıcı tavanında bunun maliyeti saniyede ~15 hafif JSON isteği; nginx bunu esner bile.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kimlik = sessionStorage'daki rastgele uid&lt;/strong&gt;: kayıt yok, e-posta yok, çerez yok. Sekme kapanınca kimlik de gider.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;istemci (vanilla JS) ──2 sn'de bir──&amp;gt; nginx /bar/api/ ──&amp;gt; 127.0.0.1:8796
                                                          bellek içi durum:
                                                          MASALAR, KISILER,
                                                          BEKLEYEN, SINYAL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Okur katmanı: metnin üstüne bir kat daha
&lt;/h2&gt;

&lt;p&gt;Makale sayfalarına bir günde eklenen etkileşim seti:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Vurgulama + not&lt;/strong&gt;: &lt;code&gt;window.getSelection()&lt;/code&gt; ile seçim, &lt;code&gt;&amp;lt;mark&amp;gt;&lt;/code&gt; sarma, DOM yolu + metin ofsetiyle LocalStorage'a yazma. Sayfa yeniden yüklendiğinde vurgular yeniden uygulanıyor. Sunucu hiçbir şey görmüyor.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Okuma modu çubuğu&lt;/strong&gt;: sepya/gece teması, 16-22px font ölçeği, odak modu (header/footer gizlenir, sadece metin + ilerleme çubuğu kalır).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ruh hali + süre filtreleri&lt;/strong&gt;: içerik meta verisine &lt;code&gt;readTimeMinutes&lt;/code&gt; ve &lt;code&gt;mood&lt;/code&gt; eklendi; liste sayfaları istemci tarafında filtreleniyor.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kavram sözlüğü&lt;/strong&gt;: &lt;code&gt;data-definition&lt;/code&gt; taşıyan terimlere hover/tık popover'ı.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MediaSession API'li sesli oynatıcı&lt;/strong&gt;: kilit ekranından oynat/durdur, arka plan oynatma. Yanına podcast RSS beslemesi kuruldu ve Spotify'a bağlandı.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fısıltı Bulutu (Mind Cloud)&lt;/strong&gt;: makale başına 10 saniyelik ses kayıtları (MediaRecorder) veya 140 karakterlik notlar. Her gönderi yayınlanmadan önce LLM moderasyonundan geçiyor; ses önce transkribe edilip aynı süzgece giriyor.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Daha sessiz deneyler&lt;/strong&gt;: paragraf altı "görünmez mürekkep" şerhleri, okurların nerede durakladığını gösteren anonim odak haritası, cümle kenarına 140 karakterlik itiraz bırakılan marjin notları, scrollytelling şablonu, üretken sanat tuvali ve "şimdi ne okumalıyım?" karar ağacı.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Bunların onboarding'i de ayrı bir tasarım problemiydi: ekranı kaplayan tur yerine, ilk ziyarette 2-3 saniye görünüp kaybolan mikro ipuçları ve "özelliği tam kullanacağın anda öğreten" bağlamsal tetikleyiciler kullandık. Kullanıcı bir özelliği bir kez kullandığında ipucu bir daha çıkmıyor (LocalStorage bayrağı).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Revizyon notu&lt;/strong&gt;: Bu setin tamamı ölçüme tabi. Odak haritası gibi "güzel ama belki kimsenin fark etmeyeceği" özellikler ilk budama adayları.&lt;/p&gt;

&lt;h2&gt;
  
  
  Park Bankı: iki yabancı, bir bank, 140 karakter
&lt;/h2&gt;

&lt;p&gt;Aynı yazıyı okuyan iki anonim okur varsa, köşede bir davet beliriyor: &lt;em&gt;"Parkta sizinle aynı makaleyi okuyan biri var. Bir bankta oturup iki cümle laflamak ister misiniz?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Kabul edenler SVG ile çizilmiş bir park sahnesinde (gece/gündüz temasına duyarlı) karşılıklı iki banka oturuyor. Mesajlar karakterin kafasının üstünde konuşma balonu olarak beliriyor ve 6 saniyede sönüyor. Kalkan tarafın yerine "diğer okur banktan kalktı" notu düşüyor.&lt;/p&gt;

&lt;p&gt;Teknik olarak en tartışmalı karar: spesifikasyon Socket.io diyordu, biz kısa yoklamayla yazdık. İki kişilik, 140 karakterlik, saniye hassasiyeti gerektirmeyen bir sohbet için WS altyapısı taşımak istemedik. Doğru karar mıydı? Kulüp tarafında göreceğiz; oradaki yük profili farklı.&lt;/p&gt;

&lt;h2&gt;
  
  
  Caz Kulübü: günün en büyük parçası
&lt;/h2&gt;

&lt;p&gt;10 masa (5'i dört, 5'i iki kişilik), caz efsanelerinin adlarını taşıyor: Ella, Coltrane, Nina, Miles... Kuşbakışı SVG salon planında dolu koltuklar kehribar yanıyor. Masalar dolarsa bar alanında kuyruk; koltuk boşalınca 30 saniyelik rezervasyonla çağrı.&lt;/p&gt;

&lt;p&gt;Masa içinde:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Atmosfer&lt;/strong&gt;: ışık (spot/loş/gece) ve ikram (kahve/meze/mum) masadaki herkese canlı yansıyor. Mum alevi ve kahve buharı CSS animasyonu.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Medya ekranı&lt;/strong&gt;: yalnızca YouTube/Spotify. YouTube linkleri sunucuda oEmbed ile doğrulanıyor (video gerçekten var mı, gömülebilir mi) ve başlık NSFW/şiddet kelime süzgecinden geçiyor.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sesli sohbet&lt;/strong&gt;: masadaki HERKES onaylarsa açılıyor (oy birliği). Ses P2P WebRTC mesh; SDP/ICE sinyalleşmesi ayrı bir kanal yerine mevcut yoklama döngüsünün içinde taşınıyor. Sunucuya ses akışı hiç uğramıyor. Bilinen sınır: TURN sunucumuz yok, simetrik NAT arkasındaki azınlık ses kuramayabilir. Kabul edilmiş bir ödün.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Gece yarısına doğru altı "kült mekân" özelliği daha eklendi:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;İstek peçetesi (jukebox kuyruğu)&lt;/strong&gt;: link anında çalmıyor; sıraya giriyor, kim istedi görünüyor. "🍷 İkram Et" oyu şarkıyı öne alıyor. Otomatik parça geçişi bilerek yok: YouTube oEmbed süre bilgisi vermiyor, Data API'ye anahtar bağlamak istemedik. Geçiş masadakilerin elinde ("⏭").&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ortak peçete&lt;/strong&gt;: masanın ortasında herkesin aynı anda çizebildiği kanvas + kısa notlar. Çizgiler nokta dizisi olarak sunucuya gidiyor, diğer istemciler artımlı indeksle çekiyor. Masa dağılınca peçete siliniyor.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Soru kartı&lt;/strong&gt;: sohbet tıkanınca "🃏 Masaya Soru At" arşiv tonunda ~30 felsefi sorudan birini masaya düşürüyor. Aynı masada son 10 soru tekrarlanmıyor.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Adisyon&lt;/strong&gt;: 5 dakika etkileşimsiz kalan misafirin önüne adisyon geliyor; 60 saniyede "buradayım" demezse koltuk kuyruktakine devrediliyor. Tamamen istemci tarafında; sunucunun 25 saniyelik yoklama düşmesi zaten güvenlik ağı.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kişisel ambiyans mikseri&lt;/strong&gt;: plak cızırtısı / yağmur / orman / kütüphane / sessiz + ses kaydırıcısı. Bilinçli karar: ortam sesi masaya değil, kişiye ait. Kimseye atmosfer dayatılmıyor; sessizlik her zaman bir tık uzakta.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Yavaş sohbet protokolü&lt;/strong&gt;: her mesajdan sonra 8 saniyelik "yudum molası". Sunucu 429 dönüyor, istemci girdiyi kilitleyip sayaç gösteriyor. Amaç, bar temposunu klavye yarışına kurban etmemek.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Ve günün son kararı: kapıya kilit
&lt;/h2&gt;

&lt;p&gt;Gün biterken ürün kararı değişti: kulüp &lt;strong&gt;üyelik zorunlu&lt;/strong&gt; olacak ve sistem açılmadan önce daha da geliştirilecek. Bu yüzden şu an canlıda kapı kapalı:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Backend'de tek satırlık bir bayrak (&lt;code&gt;KULUP_ACIK = False&lt;/code&gt;) oturma ve bekleme uçlarını 403 ile kesiyor.&lt;/li&gt;
&lt;li&gt;"Kapıdan Gir" düğmesi "🪑 Yerini Rezerve Et" oldu. Popup yalnızca e-posta + isteğe bağlı "hangi gün/saatlerde gelmek istersin?" alıyor. Kayıtlar diske yazılıyor (kulübün geri kalanının aksine, çünkü bunlar açılış davetiyesi listesi).&lt;/li&gt;
&lt;li&gt;Salon, masalar, ses, peçete: kodun tamamı yerinde duruyor. Bayrak &lt;code&gt;True&lt;/code&gt; yapıldığında mekân olduğu gibi geri gelecek, muhtemelen üyelik katmanıyla sarılmış olarak.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Bir günde inşa edip aynı gün kapısına "yakında" tabelası asmak tuhaf görünebilir. Ama bence sıralama doğru: önce mekânın çalıştığını uçtan uca kanıtla (iki sekmeli WebRTC testleri dahil), sonra kimlerin gireceğine karar ver. Tersi, üyelik sistemini boş bir mekân için kurmak olurdu.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bir günün dürüst bilançosu
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;İyi çalışan şeyler&lt;/strong&gt;: yoklama tabanlı mimari beklediğimden sağlam çıktı; WebRTC sinyalini aynı kanaldan taşımak ayrı bir sinyal sunucusunu tamamen ortadan kaldırdı; bellek içi durum + "her şey masada kalır" ilkesi hem gizlilik hem operasyon yükü açısından ucuz.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bilinen borçlar&lt;/strong&gt;: TURN yok; YouTube süre bilgisi yok (manuel geçiş); adisyon istemci tarafında (sekme açık ama kullanıcı yoksa 5+1 dakika koltuk işgali mümkün); yoklama aralıkları 30 kullanıcı üstü için yeniden düşünülmeli.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Akıbet&lt;/strong&gt;: Bu revizyondaki her özellik gözlem altında. Kullanılmayanlar kaldırılacak, tutunanlar üyelik sistemiyle birlikte v2'ye taşınacak. Bir sonraki bölümde muhtemelen ya üyelik mimarisini ya da bu deneylerden hangilerinin hayatta kaldığını yazacağım.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Soruları yorumlara bırakabilirsiniz; özellikle "polling yerine WS kullansaydınız" itirazlarını memnuniyetle tartışırım.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>python</category>
      <category>webrtc</category>
    </item>
    <item>
      <title>Bir web sitesini yapay zekâya teslim ettim: kafa1milyon'un otonom yayın mimarisi (Bölüm 1)</title>
      <dc:creator>Guray</dc:creator>
      <pubDate>Wed, 12 Aug 2026 14:08:42 +0000</pubDate>
      <link>https://dev.to/gurayta/bir-web-sitesini-yapay-zekaya-teslim-ettim-kafa1milyonun-otonom-yayin-mimarisi-bolum-1-1klc</link>
      <guid>https://dev.to/gurayta/bir-web-sitesini-yapay-zekaya-teslim-ettim-kafa1milyonun-otonom-yayin-mimarisi-bolum-1-1klc</guid>
      <description>&lt;p&gt;&lt;em&gt;Bu serinin yazarı Güray: AI geliştirmecisi, prompt ve analiz mühendisi. Kodun büyük kısmını bir LLM ajanıyla pair programming yaparak yazıyorum; benim işim mimariyi, protokolleri ve kalite çıtasını tasarlamak. Bu seri, &lt;a href="https://kafa1milyon.com" rel="noopener noreferrer"&gt;kafa1milyon.com&lt;/a&gt;'un perde arkasını mühendis gözüyle belgeliyor.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Tez: Bu bir web sitesi değil, kendi kendini yayınlayan bir sistem
&lt;/h2&gt;

&lt;p&gt;kafa1milyon.com dışarıdan bakınca sıradan bir Türkçe içerik portalı: bilim, tarih, psikoloji yazıları, günlük tarot falı, arkeoloji haberleri, sesli anlatımlar, X ve Instagram'da video fragmanlar.&lt;/p&gt;

&lt;p&gt;Perde arkasında ise insan müdahalesi olmadan şunları yapan bir sistem çalışıyor:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;RSS radarlarıyla dünya basınını ve bilimsel dergileri tarıyor&lt;/li&gt;
&lt;li&gt;Konu adaylarını kalite kriterlerine göre kendisi süzüyor (onay beklemiyor)&lt;/li&gt;
&lt;li&gt;Birincil kaynaklardan araştırıp 700-1.100 kelimelik yazılar üretiyor&lt;/li&gt;
&lt;li&gt;Her yazıya hero görseli, JSON-LD, sitemap kaydı, IndexNow ping'i ekliyor&lt;/li&gt;
&lt;li&gt;Yayın kalitesinde Türkçe seslendirme üretiyor (kota yönetimiyle birlikte)&lt;/li&gt;
&lt;li&gt;Sanal bir sunucu karakterin (Elif) dikey tanıtım videolarını üretiyor&lt;/li&gt;
&lt;li&gt;X ve Instagram'a günlük limitlere ve bekletme tarihlerine uyarak paylaşıyor&lt;/li&gt;
&lt;li&gt;Her adımı doğruluyor: HTTP 200 kontrolü, ffprobe, hatta ürettiği videoyu Whisper ile &lt;strong&gt;geri dinleyip&lt;/strong&gt; metnin doğru söylenip söylenmediğini teyit ediyor&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Bu yazı serinin ilki: sistemin genel mimarisi ve en önemli tasarım kararı olan "kural dosyaları = sistemin uzun süreli hafızası" yaklaşımı.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mimari: LLM ajan + kural dosyaları + kuyruklar
&lt;/h2&gt;

&lt;p&gt;Kaba şema şöyle:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────────────────────────┐
│  LLM AJAN (Cursor içinde çalışan agent)             │
│  - araştırma, yazım, kod üretimi, tarayıcı kontrolü │
└──────────────┬──────────────────────────────────────┘
               │ her oturumda okur
┌──────────────▼──────────────────────────────────────┐
│  KURAL DOSYALARI (.cursor/rules/*.mdc)              │
│  yazım stili · SEO protokolü · seslendirme akışı    │
│  X/Instagram standardı · tarot formatı · video      │
│  standardı · taslak kuyruğu politikası              │
└──────────────┬──────────────────────────────────────┘
               │ üretir / günceller
┌──────────────▼──────────────────────────────────────┐
│  DURUM (dosya tabanlı, git'lenebilir)               │
│  xpost/queue.json · instagram/queue.json            │
│  tarot/gecmis.json · radar/son-tarama.json          │
│  taslak-durum.json (VPS'te küçük bir API)           │
└──────────────┬──────────────────────────────────────┘
               │ rsync
┌──────────────▼──────────────────────────────────────┐
│  VPS (nginx, statik HTML) + Cloudflare              │
└─────────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dikkat çekmek istediğim üç karar var.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Veritabanı yok, framework yok: dosya tabanlı durum
&lt;/h3&gt;

&lt;p&gt;Sitenin tamamı statik HTML. Kuyruklar, geçmiş kayıtları ve radar çıktıları düz JSON dosyaları. Örneğin sosyal medya kuyruğundaki bir öğe:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Tıraş ettikçe kıl gürleşir diyorlar.&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;Bilim buna neredeyse yüz yıldır aynı cevabı veriyor."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://kafa1milyon.com/yazi/tiras-efsanesi/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"kat"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bilim"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"video"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"video-lab/xvideo/elif-tiras-efsanesi.mp4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"hold_until"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-08-12"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"posted"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;hold_until&lt;/code&gt; alanı sistemin tempo kontrolü: ajan istediği kadar içerik üretebilir ama X'in spam filtresine yakalanmamak için günde 2-3 paylaşım limiti var. Üretim ve yayın birbirinden ayrıştı; üretim hızlı, yayın sabırlı.&lt;/p&gt;

&lt;p&gt;Bir LLM ajanla çalışırken dosya tabanlı durumun büyük avantajı şu: ajan durumu &lt;code&gt;cat&lt;/code&gt; ile okuyabiliyor, &lt;code&gt;python3 -&lt;/code&gt; ile güncelleyebiliyor, hata yaptığında diff'ten geri alınabiliyor. ORM'siz, migration'sız, şeffaf.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Kural dosyaları = derlenmiş deneyim
&lt;/h3&gt;

&lt;p&gt;Sistemin asıl "zekâsı" modellerde değil, kural dosyalarında birikiyor. Her &lt;code&gt;.mdc&lt;/code&gt; dosyası, yaşanmış bir problemin damıtılmış çözümü. Gerçek bir örnek, yazım stili kuralından:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## Uzunluk: KISA TUT (kullanıcı geri bildirimi, ZORUNLU)&lt;/span&gt;

Yazılar haddinden uzun olunca okur detaylarda boğulup yarıda bırakıyor.
&lt;span class="p"&gt;-&lt;/span&gt; Hedef: 700-1.100 kelime (4-7 dk okuma).
&lt;span class="p"&gt;-&lt;/span&gt; Giriş 2 paragrafı geçmez; ilk cümle doğrudan sahneye/soruya girer.
&lt;span class="p"&gt;-&lt;/span&gt; Aynı iddiayı ikinci örnekle kanıtlama; en güçlü tek örneği seç, gerisini at.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bu satırlar bir prompt değil, &lt;strong&gt;protokol&lt;/strong&gt;. Okur yazıları yarıda bırakınca analiz ettik, kararı verdik, kurala işledik. Artık sistem her yeni yazıda bu dersi hatırlıyor. "Kendi kendine gelişen sistem" derken kastettiğim mekanizma bu: her hata ve her geri bildirim, bir kural dosyası güncellemesine dönüşüyor. Model unutur; dosya unutmaz.&lt;/p&gt;

&lt;p&gt;Şu an ~10 kural dosyası var: yazım stili, SEO standardı, seslendirme akışı, X paylaşım formatı (fragman tarzı, spoiler yasağı), Instagram aynalama, günlük tarot formatı (üç kart + "bize ne olacak" bölümü), arkeoloji radarının görsel politikası, sanal sunucu Elif'in sabit karakter tanımı...&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Doğrulama döngüleri: ajan kendi çıktısına güvenmez
&lt;/h3&gt;

&lt;p&gt;LLM tabanlı otomasyonda en tehlikeli an, sistemin "bitti" dediği an. Bu yüzden her üretim adımının bir doğrulayıcısı var:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Yayın&lt;/strong&gt;: deploy sonrası her yeni URL'ye HTTP isteği, 200 kontrolü.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ses&lt;/strong&gt;: üretilen MP3 ffprobe ile süre/bitrate kontrolünden geçiyor.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Video&lt;/strong&gt;: en sert kontrol burada. Görüntü-videoya (image-to-video) modeli sunucu karakterimize bir metin söyletiyor; ama model bazen metni değiştiriyor ya da eski bir çekimin sesini kullanıyor. Çözüm: üretilen her video faster-whisper ile &lt;strong&gt;transkript ediliyor&lt;/strong&gt; ve söylenen metin, istenen metinle karşılaştırılıyor. Uymuyorsa video çöpe gidiyor, yayına giremiyor.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;segments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;info&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transcribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;elif-clip.mp4&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;language&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tr&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;soylenen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;segments&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# istenen metinle benzerlik düşükse: RED, yeniden üret
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bu döngü bizi en az bir kez kurtardı: model, yeni konu için ürettiğimiz videoda eski bir çekimin kapanış cümlesini söylemişti. Transkript kontrolü olmasaydı yanlış video yayına girecekti.&lt;/p&gt;

&lt;h2&gt;
  
  
  Otonomi modeli: onay kuyruğundan onaysız üretime
&lt;/h2&gt;

&lt;p&gt;Başta her konu adayı kullanıcı onayı bekliyordu. Bir taslak sayfası, VPS'te minik bir onay API'si (&lt;code&gt;{"onayli": [], "yazildi": []}&lt;/code&gt;), seçilenlerin yazılması... Sonra fark ettik ki dar boğaz onay adımının kendisi. Kalite kriterleri zaten kurallarda yazılıysa, süzgeci ajan kendisi işletebilir.&lt;/p&gt;

&lt;p&gt;Şimdiki model: ajan adayları bulur, kalite/etik süzgecinden geçirir (tıbbi tavsiye yok, siyaset yok, zayıf hikâye elenir), uygun olanları kuyruğa alır ve yazar. İnsan istediği an taslak sayfasından kuyruğa öncelik verebilir ama sistemin ilerlemesi insana &lt;strong&gt;bağlı değil&lt;/strong&gt;. İnsan onayı bir kapı olmaktan çıkıp bir yönlendirme sinyaline dönüştü.&lt;/p&gt;

&lt;p&gt;Bu, prompt mühendisliğinden çok &lt;strong&gt;analiz mühendisliği&lt;/strong&gt;: hangi kararların gerçekten insan yargısı gerektirdiğini, hangilerinin yazılı kritere indirgenebileceğini ayrıştırmak. İndirgenebilen her karar, otonom sistemin bir parçası oluyor.&lt;/p&gt;

&lt;h2&gt;
  
  
  Başarısızlıklar da mimarinin parçası
&lt;/h2&gt;

&lt;p&gt;Bu seride pembe tablo çizmeyeceğim; sistemin bugünkü hâli, çöpe giden denemelerin üstünde duruyor:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;X'in ücretsiz API'si öldü&lt;/strong&gt; (Şubat 2026, yazma uçları 402 dönüyor). Çözümü tarayıcı otomasyonu + intent URL'lerde bulduk; X'in React composer'ına programatik metin yazmak ise imkânsıza yakın çıktı. Video eklemek için Chrome DevTools Protocol ile &lt;code&gt;DataTransfer&lt;/code&gt; enjeksiyonu gerekti. (Bölüm 2'nin konusu.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Wav2Lip ile dudak senkronu&lt;/strong&gt; teknik olarak çalıştı, kalite olarak reddedildi: robotik, bozuk. Bazen "çalışan çözüm" doğru çözüm değildir; onun yerine ses üretimini video modelinin kendisine bıraktık.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sinematik tanıtım filmi&lt;/strong&gt; projesi tamamen iptal edildi. Uzun formda kesme/geçiş/senkron kalitesini otomasyonla tutturamadık. Ders: kısa dikey format (10 sn, tek çekim, tek karakter) otomasyonun bugünkü tatlı noktası; uzun form değil.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Sıradaki bölümler
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;X otomasyonu savaşı&lt;/strong&gt; — ücretsiz API'nin ölümü, intent URL'ler, CDP ile dosya enjeksiyonu, nginx &lt;code&gt;^~&lt;/code&gt; CORS detayı&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Seslendirme boru hattı&lt;/strong&gt; — Türkçe TTS'te prozodi, request stitching, karakter kotası yönetimi&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Radar sistemleri&lt;/strong&gt; — RSS + LLM süzgeciyle günlük konu keşfi, tekrar kontrolü&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Elif&lt;/strong&gt; — sabit karakterli sanal sunucu: image-to-video, kimlik tutarlılığı, transkripsiyon doğrulama&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SEO otonomisi&lt;/strong&gt; — JSON-LD, IndexNow, iç bağlantı otomasyonu&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Sorusu olan yorumlara beklerim. Sistemin ürettiklerini görmek isterseniz: &lt;a href="https://kafa1milyon.com" rel="noopener noreferrer"&gt;kafa1milyon.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>automation</category>
      <category>llm</category>
      <category>python</category>
    </item>
  </channel>
</rss>
