İhtiyacı olan vardır belki metini gpt ye yazdır istersen api çek metinide yazsın kodlarla oynar ayarlarsınız görselerin oldugu klasörleri seç video yapsın. Klasörden her defasında rast gele sıralama olarak fotoğraf seçer ve 5 saniye olarak yanyana koyar %50 ihtimalle yavaşça yaklaştırma %50 ihtimalle yavaşça uzaklaştırma efekti ekler hepsine geçiş efekti ekler eğer klasörde video varsa video içerisinden rast gele 3 saniye kesit alır aynalama ekler ve 3 görselden sonra 3 saniyelik kesit koyar birden fazla video varsa 3er saniye alır sonra tekrar ilk videodan rastgele 3 saniye alır hep aynı videodan almaz aynalama kullanır hepsine otomatik sese çevirir sınırsız ses düzenler isteyen kafasına göre özellik ekleyebilir



import os
import sys
import time
import re
import asyncio
import random
import threading
import gc  # Bellek (RAM) temizliği için
from datetime import datetime
import numpy as np
import PIL.Image
from PIL import ImageFilter
import tkinter as tk
from tkinter import filedialog, messagebox

# CustomTkinter İçe Aktarımı
import customtkinter as ctk

# MoviePy - Pillow 10+ uyumluluk yaması
if not hasattr(PIL.Image, 'ANTIALIAS'):
    PIL.Image.ANTIALIAS = PIL.Image.Resampling.LANCZOS

import edge_tts
from moviepy.editor import ImageClip, AudioFileClip, VideoFileClip, concatenate_videoclips, VideoClip, vfx

# ==================== TEMA AYARLARI ====================
ctk.set_appearance_mode("Dark")        # "Dark" veya "Light"
ctk.set_default_color_theme("blue")    # "blue", "green", "dark-blue"

IMAGE_DURATION = 5.0        # Her fotoğrafın kalma süresi (sn)
VIDEO_CLIP_DURATION = 3.0   # Videolardan çekilecek kesit süresi (sn)
TRANSITION_DURATION = 1.0   # Geçiş yumuşaklığı (sn)

# İNGİLİZCE SES SEÇENEKLERİ
VOICES = {
    "Erkek Ses (Christopher - Derin & Etkileyici) [EN]": "en-US-ChristopherNeural",
    "Kadın Ses (Ava - Doğal & Akıcı) [EN]": "en-US-AvaNeural",
    "Erkek Ses (Eric - Haberci Tonu) [EN]": "en-US-EricNeural",
    "Kadın Ses (Emma - Enerjik) [EN]": "en-US-EmmaNeural"
}

def clean_script_for_tts(text):
    text = re.sub(r'\(.*?\)', '', text)
    text = re.sub(r'\[.*?\]', '', text)
    text = re.sub(r'^(Narrator|Speaker|User|Host|Voiceover)\s*:\s*', '', text, flags=re.IGNORECASE | re.MULTILINE)
    text = text.replace('*', '').replace('#', '')
    text = re.sub(r'\n+', '\n', text)
    return text.strip()

async def text_to_speech(text, output_filename, voice_code):
    communicate = edge_tts.Communicate(text, voice_code)
    await communicate.save(output_filename)

def create_capcut_style_clip(img_path, duration=5.0, target_size=(1920, 1080)):
    tw, th = target_size
    img = PIL.Image.open(img_path).convert("RGB")
    img_w, img_h = img.size

    # 1. Arka Plan (Bulanık Zemin)
    ratio_bg = max(tw / img_w, th / img_h)
    bg_w, bg_h = int(img_w * ratio_bg), int(img_h * ratio_bg)
    bg_img = img.resize((bg_w, bg_h), PIL.Image.Resampling.LANCZOS)
    left = (bg_w - tw) // 2
    top = (bg_h - th) // 2
    bg_img = bg_img.crop((left, top, left + tw, top + th)).filter(ImageFilter.GaussianBlur(radius=30))

    # 2. Ön Plan Temel Boyutlandırması
    ratio_fg = min(tw / img_w, th / img_h)
    fg_w, fg_h = int(img_w * ratio_fg), int(img_h * ratio_fg)
    fg_base = img.resize((fg_w, fg_h), PIL.Image.Resampling.LANCZOS)

    zoom_direction = random.choice(["in", "out"])

    # 3. Canlı Ken Burns Zoom Efekt Üreteci
    def make_frame(t):
        progress = t / duration if duration > 0 else 0
        scale = 1.0 + (0.12 * progress) if zoom_direction == "in" else 1.12 - (0.12 * progress)

        curr_w = int(fg_w * scale)
        curr_h = int(fg_h * scale)

        fg_resized = fg_base.resize((curr_w, curr_h), PIL.Image.Resampling.LANCZOS)

        frame = bg_img.copy()
        paste_x = (tw - curr_w) // 2
        paste_y = (th - curr_h) // 2
        frame.paste(fg_resized, (paste_x, paste_y))

        return np.array(frame)

    return VideoClip(make_frame, duration=duration)

def get_random_video_snippet(video_path, clip_duration=3.0, target_size=(1920, 1080)):
    """Seçilen videodan ANLIK olarak rastgele 3sn sessiz, %50 aynalanmış kesit ve ana dosyayı döndürür."""
    try:
        full_video = VideoFileClip(video_path)
        dur = full_video.duration

        if dur <= 0.5:
            full_video.close()
            return None, None

        # 1. Her çağrıldığında RASTGELE bir saniye seçer
        if dur > clip_duration:
            start_time = random.uniform(0, dur - clip_duration)
            sub_clip = full_video.subclip(start_time, start_time + clip_duration)
        else:
            sub_clip = full_video.subclip(0, dur)

        # 2. SESİ KAPAT
        sub_clip = sub_clip.without_audio()

        # 3. %50 İHTİMALLE YATAY AYNALAMA
        if random.choice([True, False]):
            sub_clip = sub_clip.fx(vfx.mirror_x)

        # 4. 1080p Boyutlandırma
        sub_clip = sub_clip.resize(height=target_size[1])
        if sub_clip.w > target_size[0]:
            sub_clip = sub_clip.crop(x_center=sub_clip.w/2, width=target_size[0])

        return sub_clip, full_video
    except Exception:
        return None, None

# ==================== KUYRUKLU MODERN ARAYÜZ ====================
class VideoBotApp(ctk.CTk):
    def __init__(self):
        super().__init__()

        self.title("🎬 YouTube Content Studio Pro v4.0 (GPU & RAM Accelerated)")
        self.geometry("1080x880")
        self.resizable(True, True)

        self.selected_image_folder = os.path.abspath("images")
        if not os.path.exists(self.selected_image_folder):
            os.makedirs(self.selected_image_folder)

        self.queue = []
        self.build_gui()

    def build_gui(self):
        header_frame = ctk.CTkFrame(self, fg_color="transparent")
        header_frame.pack(fill="x", padx=20, pady=(15, 5))

        title_label = ctk.CTkLabel(
            header_frame, text="🎬 YOUTUBE CONTENT STUDIO (NVIDIA NVENC GPU ACCELERATED)", 
            font=ctk.CTkFont(family="Segoe UI", size=18, weight="bold")
        )
        title_label.pack(side="left")

        self.status_badge = ctk.CTkLabel(
            header_frame, text="● SİSTEM HAZIR", 
            font=ctk.CTkFont(family="Segoe UI", size=11, weight="bold"),
            text_color="#10b981", fg_color="#1f2937", corner_radius=8, padx=12, pady=4
        )
        self.status_badge.pack(side="right")

        main_layout = ctk.CTkFrame(self, fg_color="transparent")
        main_layout.pack(fill="both", expand=True, padx=20, pady=5)

        left_column = ctk.CTkFrame(main_layout, fg_color="transparent")
        left_column.pack(side="left", fill="both", expand=True, padx=(0, 10))

        right_column = ctk.CTkFrame(main_layout, width=340, corner_radius=12)
        right_column.pack(side="right", fill="both", padx=(10, 0))

        # --- SOL KOLON ---
        editor_card = ctk.CTkFrame(left_column, corner_radius=12)
        editor_card.pack(fill="both", expand=True, pady=(0, 10))

        editor_header = ctk.CTkFrame(editor_card, fg_color="transparent")
        editor_header.pack(fill="x", padx=15, pady=(10, 5))

        ctk.CTkLabel(editor_header, text="📝 İngilizce Senaryo / Metin Girişi", font=ctk.CTkFont(size=13, weight="bold")).pack(side="left")

        btn_clear = ctk.CTkButton(
            editor_header, text="🗑️ Temizle", width=70, height=24, 
            fg_color="transparent", hover_color="#374151", text_color="#9ca3af",
            command=self.clear_text
        )
        btn_clear.pack(side="right")

        self.script_input = ctk.CTkTextbox(editor_card, font=("Consolas", 12), corner_radius=8, height=180)
        self.script_input.pack(fill="both", expand=True, padx=15, pady=5)
        self.script_input.bind("<KeyRelease>", self.update_stats)

        stats_frame = ctk.CTkFrame(editor_card, fg_color="transparent")
        stats_frame.pack(fill="x", padx=15, pady=(5, 10))

        self.lbl_chars = ctk.CTkLabel(stats_frame, text="Karakter: 0", font=ctk.CTkFont(size=11, weight="bold"), text_color="#3b82f6")
        self.lbl_chars.pack(side="left", padx=(0, 15))

        self.lbl_time = ctk.CTkLabel(stats_frame, text="Süre: ~0 sn", font=ctk.CTkFont(size=11), text_color="#9ca3af")
        self.lbl_time.pack(side="left")

        settings_card = ctk.CTkFrame(left_column, corner_radius=12)
        settings_card.pack(fill="x", pady=5)

        settings_inner = ctk.CTkFrame(settings_card, fg_color="transparent")
        settings_inner.pack(fill="x", padx=15, pady=10)

        ctk.CTkLabel(settings_inner, text="🎙️ İngilizce Ses:", font=ctk.CTkFont(size=11, weight="bold")).grid(row=0, column=0, sticky="w", pady=4)
        self.voice_var = ctk.StringVar(value=list(VOICES.keys())[0])
        voice_dropdown = ctk.CTkOptionMenu(settings_inner, variable=self.voice_var, values=list(VOICES.keys()), width=350, corner_radius=8)
        voice_dropdown.grid(row=0, column=1, sticky="w", pady=4, padx=(10, 0))

        ctk.CTkLabel(settings_inner, text="📁 Medya Klasörü:", font=ctk.CTkFont(size=11, weight="bold")).grid(row=1, column=0, sticky="w", pady=4)
        folder_frame = ctk.CTkFrame(settings_inner, fg_color="transparent")
        folder_frame.grid(row=1, column=1, sticky="ew", pady=4, padx=(10, 0))

        self.lbl_folder_path = ctk.CTkLabel(
            folder_frame, text=self.selected_image_folder, font=ctk.CTkFont(size=10),
            text_color="#9ca3af", fg_color="#111827", corner_radius=8, anchor="w", padx=10, height=30, width=240
        )
        self.lbl_folder_path.pack(side="left", padx=(0, 5))

        btn_select_folder = ctk.CTkButton(folder_frame, text="📂 Seç", width=60, height=30, corner_radius=8, command=self.select_folder)
        btn_select_folder.pack(side="right")

        btn_add_queue = ctk.CTkButton(
            left_column, text="➕ BU VİDEOYU KUYRUĞA EKLE", font=ctk.CTkFont(size=12, weight="bold"),
            fg_color="#10b981", hover_color="#059669", height=38, corner_radius=8, command=self.add_to_queue
        )
        btn_add_queue.pack(fill="x", pady=(5, 0))

        # --- SAĞ KOLON ---
        queue_header = ctk.CTkFrame(right_column, fg_color="transparent")
        queue_header.pack(fill="x", padx=15, pady=(12, 5))

        ctk.CTkLabel(queue_header, text="📋 İş Kuyruğu", font=ctk.CTkFont(size=13, weight="bold")).pack(side="left")

        btn_clear_queue = ctk.CTkButton(
            queue_header, text="Kuyruğu Temizle", width=90, height=22, 
            fg_color="transparent", hover_color="#ef4444", text_color="#f87171", command=self.clear_queue
        )
        btn_clear_queue.pack(side="right")

        self.queue_box = ctk.CTkTextbox(right_column, font=("Consolas", 10), corner_radius=8)
        self.queue_box.pack(fill="both", expand=True, padx=12, pady=5)
        self.queue_box.configure(state="disabled")

        self.lbl_queue_count = ctk.CTkLabel(right_column, text="Bekleyen İş: 0 Video", font=ctk.CTkFont(size=11, weight="bold"), text_color="#9ca3af")
        self.lbl_queue_count.pack(pady=(5, 12))

        # --- ALT AKSİYON & LOG ---
        action_frame = ctk.CTkFrame(self, fg_color="transparent")
        action_frame.pack(fill="x", padx=20, pady=5)

        self.btn_start_batch = ctk.CTkButton(
            action_frame, text="⚡ KUYRUĞU BAŞLAT (SABAHA KADAR RENDER ET)", font=ctk.CTkFont(size=13, weight="bold"),
            height=45, corner_radius=10, command=self.start_batch_process
        )
        self.btn_start_batch.pack(fill="x", pady=(0, 5))

        self.progress = ctk.CTkProgressBar(action_frame, height=10, corner_radius=5)
        self.progress.pack(fill="x")
        self.progress.set(0)

        log_card = ctk.CTkFrame(self, corner_radius=12)
        log_card.pack(fill="both", expand=True, padx=20, pady=(5, 15))

        self.log_area = ctk.CTkTextbox(log_card, font=("Consolas", 10), text_color="#10b981", fg_color="#090a0f", corner_radius=8, height=140)
        self.log_area.pack(fill="both", expand=True, padx=12, pady=10)

        self.log("🚀 Sistem hazır! GPU Hızlandırması (NVIDIA NVENC) Etkinleştirildi.")

    def select_folder(self):
        folder = filedialog.askdirectory(title="Görsellerin ve Videoların Olduğu Klasörü Seçin", initialdir=self.selected_image_folder)
        if folder:
            self.selected_image_folder = os.path.abspath(folder)
            display_path = self.selected_image_folder if len(self.selected_image_folder) < 32 else "..." + self.selected_image_folder[-28:]
            self.lbl_folder_path.configure(text=display_path)

    def clear_text(self):
        self.script_input.delete("1.0", tk.END)
        self.update_stats()

    def update_stats(self, event=None):
        text = self.script_input.get("1.0", tk.END).strip()
        char_count = len(text)
        est_seconds = int(char_count / 14.5) if char_count > 0 else 0
        self.lbl_chars.configure(text=f"Karakter: {char_count:,}")
        self.lbl_time.configure(text=f"Süre: ~{est_seconds} sn")

    def add_to_queue(self):
        text = self.script_input.get("1.0", tk.END).strip()
        if not text:
            messagebox.showwarning("Eksik Metin", "Lütfen önce İngilizce senaryo metnini girin kanka!")
            return

        item = {
            "id": len(self.queue) + 1,
            "text": text,
            "folder": self.selected_image_folder,
            "voice_name": self.voice_var.get(),
            "voice_code": VOICES[self.voice_var.get()]
        }

        self.queue.append(item)
        self.refresh_queue_ui()
        self.log(f"➕ [KUYRUK] {item['id']}. Video eklendi! ({len(text)} Karakter | Klasör: {os.path.basename(item['folder'])})")
        self.clear_text()

    def clear_queue(self):
        self.queue.clear()
        self.refresh_queue_ui()
        self.log("🧹 İş kuyruğu tamamen temizlendi.")

    def refresh_queue_ui(self):
        self.queue_box.configure(state="normal")
        self.queue_box.delete("1.0", tk.END)

        for i, item in enumerate(self.queue, 1):
            folder_name = os.path.basename(item['folder'])
            snippet = item['text'][:25].replace('\n', ' ') + "..."
            self.queue_box.insert(tk.END, f"#{i} | {folder_name}\n    📝 {snippet}\n-----------------------------------\n")

        self.queue_box.configure(state="disabled")
        self.lbl_queue_count.configure(text=f"Bekleyen İş: {len(self.queue)} Video")

    def log(self, message):
        now = datetime.now().strftime("%H:%M:%S")
        self.log_area.insert(tk.END, f"[{now}] {message}\n")
        self.log_area.see(tk.END)

    def set_progress(self, val):
        self.progress.set(val / 100.0)

    def set_status(self, text, is_working=False):
        color = "#f59e0b" if is_working else "#10b981"
        self.status_badge.configure(text=f"● {text.upper()}", text_color=color)

    def start_batch_process(self):
        if not self.queue:
            messagebox.showwarning("Kuyruk Boş", "Kuyrukta hiç video yok! Önce metin girip 'Kuyruğa Ekle' butonuna bas kanka.")
            return

        self.btn_start_batch.configure(state="disabled", text="⏳ GECE MODU AKTİF (GPU RENDER EDİLİYOR...)")
        self.set_status("GPU RENDER EDİLİYOR", is_working=True)
        threading.Thread(target=self.run_batch_queue, daemon=True).start()

    def run_batch_queue(self):
        total_jobs = len(self.queue)
        self.log(f"🚀 TOPLU İŞLEM BAŞLATILDI! Toplam {total_jobs} video NVIDIA GPU desteğiyle render edilecek.")

        for index, item in enumerate(list(self.queue), 1):
            # Her döngüde RAM temizliği için açılan tüm clip nesnelerini burada toplayacağız
            clips_to_close = []
            audio = None
            video = None
            final_video = None

            try:
                self.log(f"\n🎬 === [VİDEO {index}/{total_jobs} BAŞLADI] ===")
                self.set_progress(5)

                # 1. Metin & İngilizce Ses
                clean_text = clean_script_for_tts(item['text'])
                self.log(f"🎙️ İngilizce Ses üretiliyor ({item['voice_name']})...")
                audio_path = f"temp_voice_{index}.mp3"
                asyncio.run(text_to_speech(clean_text, audio_path, item['voice_code']))
                
                audio = AudioFileClip(audio_path)
                audio_duration = audio.duration
                self.set_progress(25)

                # 2. Klasördeki Resim ve Videoları Arama
                media_folder = item['folder']
                all_files = os.listdir(media_folder)

                raw_images = [os.path.join(media_folder, f) for f in all_files if f.lower().endswith(('.png', '.jpg', '.jpeg'))]
                raw_videos = [os.path.join(media_folder, f) for f in all_files if f.lower().endswith(('.mp4', '.mov', '.avi', '.mkv'))]

                if not raw_images and not raw_videos:
                    self.log(f"❌ HATA: '{media_folder}' klasöründe hiç resim veya video yok! Atlanıyor.")
                    if audio: audio.close()
                    if os.path.exists(audio_path): os.remove(audio_path)
                    continue

                self.log(f"📁 Klasör İçeriği: {len(raw_images)} Resim, {len(raw_videos)} Video bulundu.")

                # Fotoğraf Klip Listesi
                random.shuffle(raw_images)
                img_clips = []
                for img_p in raw_images:
                    ic = create_capcut_style_clip(img_p, duration=IMAGE_DURATION)
                    img_clips.append(ic)
                    clips_to_close.append(ic)

                # 3. Klipleri Harmanla (Tüm Videolar Arasında Eşit Sirkülasyon + Dinamik Kesim)
                final_clips_sequence = []
                current_timeline_duration = 0
                img_idx = 0
                video_pointer = 0  # Videoları sırayla eşit gezmek için imleç

                if raw_videos:
                    random.shuffle(raw_videos)

                while current_timeline_duration < audio_duration + 2.0:
                    # 2 Fotoğraf Ekle
                    for _ in range(2):
                        if img_clips:
                            c = img_clips[img_idx % len(img_clips)]
                            final_clips_sequence.append(c)
                            current_timeline_duration += (IMAGE_DURATION - TRANSITION_DURATION)
                            img_idx += 1

                    # 1 Video Kesiti Ekle (Eğer klasörde video varsa)
                    if raw_videos:
                        selected_video_path = raw_videos[video_pointer % len(raw_videos)]
                        video_pointer += 1

                        # O an videodan RASTGELE sıfırdan 3 saniye kes
                        vc, full_vid = get_random_video_snippet(selected_video_path, clip_duration=VIDEO_CLIP_DURATION)
                        if vc:
                            final_clips_sequence.append(vc)
                            current_timeline_duration += (VIDEO_CLIP_DURATION - TRANSITION_DURATION)
                            clips_to_close.append(vc)
                            if full_vid:
                                clips_to_close.append(full_vid)

                # Yumuşak Geçişler (Crossfade)
                processed_clips = []
                for i, clip in enumerate(final_clips_sequence):
                    if i > 0:
                        clip = clip.crossfadein(TRANSITION_DURATION)
                    processed_clips.append(clip)

                self.set_progress(60)

                # 4. GPU (NVIDIA NVENC) RENDER
                output_name = f"final_video_{index}_{datetime.now().strftime('%H%M%S')}.mp4"
                self.log(f"🔥 GPU Render Alınıyor (NVIDIA NVENC + Ryzen 12 Çekirdek) -> {output_name}")

                video = concatenate_videoclips(processed_clips, padding=-TRANSITION_DURATION, method="compose")
                video = video.set_duration(audio_duration)
                final_video = video.set_audio(audio)

                # =========================================================
                # 8 GB NVIDIA EKRAN KARTI (NVENC) VE RYZEN 5 5600 DESTEKLİ RENDER
                # =========================================================
                final_video.write_videofile(
                    output_name,
                    fps=24,
                    codec="h264_nvenc",     # İşlemci (libx264) YERİNE NVIDIA GPU NVENC ÇİPİ
                    audio_codec="aac",      # Standart yüksek kaliteli ses
                    preset="p1",            # En hızlı GPU donanım profili
                    threads=12              # Ryzen 5 5600'ün 12 çekirdeğini sonuna kadar çalıştırır
                )

                self.set_progress(100)
                self.log(f"✅ [VİDEO {index}/{total_jobs} BİTTİ] Başarıyla tamamlandı!")

            except Exception as e:
                self.log(f"❌ Video #{index} işlenirken HATA oluştu: {e}. Sıradaki videoya geçiliyor...")

            finally:
                # ================= TEMİZLİK VE RAM TEMİZLİĞİ =================
                self.log("🧹 Bellek (RAM) ve geçici dosyalar temizleniyor...")
                if final_video:
                    try: final_video.close()
                    except: pass
                if video:
                    try: video.close()
                    except: pass
                if audio:
                    try: audio.close()
                    except: pass

                for c in clips_to_close:
                    try: c.close()
                    except: pass

                clips_to_close.clear()

                if os.path.exists(audio_path):
                    try: os.remove(audio_path)
                    except: pass

                # Garbage Collector çalıştırarak RAM'i sıfırlıyoruz
                gc.collect()

        self.log("\n🎉🎉 TÜM KUYRUK TAMAMLANDI! Videoların hazır kanka!")
        messagebox.showinfo("İşlem Bitti", f"Kuyruktaki {total_jobs} video başarıyla oluşturuldu!")
        
        self.queue.clear()
        self.refresh_queue_ui()
        self.set_status("HAZIR", is_working=False)
        self.btn_start_batch.configure(state="normal", text="⚡ KUYRUĞU BAŞLAT (SABAHA KADAR RENDER ET)")

if __name__ == "__main__":
    app = VideoBotApp()
    app.mainloop()