Python kullanarak görselleri rastgele karıştıran, müziğin ilk 30 saniyesini alan ve üzerine şarkı adı ile kullanıcı adını ekleyerek 30 saniyelik videolar üreten bir bot hazırladık. MoviePy ve Pillow kütüphaneleriyle kendi video otomasyon sistemini nasıl kurabileceğini adım adım anlatıyoruz.
Bu yazıda Python kullanarak tamamen otomatik çalışan bir video oluşturma botu geliştiriyoruz. Elimizdeki görselleri rastgele karıştırıyor, seçtiğimiz müzik dosyalarının ilk 30 saniyesini alıyor, şarkı adını ve kullanıcı adımızı ekrana basıyor. Tüm bunları MoviePy ve Pillow kütüphaneleri ile 30 saniyelik MP4 formatında çıktı olarak alıyoruz.
Böyle bir sistem, YouTube Shorts veya Instagram Reels gibi platformlara hızlı içerik üretmek isteyenler için harika bir otomasyon fikri. Kendi klasöründeki müzikleri ve görselleri kullanarak her çalıştırmada farklı sonuçlar üreten bir mini video üretim motoru aslında.

Kurulum Adımları
- Python ve FFmpeg kur.
- MoviePy, Pillow, Numpy kütüphanelerini yükle.
- Görselleri
images/, müzikleriaudio/klasörüne yerleştir. make_videos.pybetiğini çalıştır.
cd "C:\Users\Win10\Desktop\video maker"
venv\Scripts\activate
python make_videos.py
Her müzik için 30 saniyelik dikey videolar üretilir. Üzerinde şarkı ismi ve kullanıcı adınız görünür. Python ile yaratıcı üretim süreçlerini otomatikleştirmenin en eğlenceli örneklerinden biri!
# --- Pillow 10+ uyumluluk yaması (MoviePy için) ---
import PIL
from PIL import Image
if not hasattr(Image, "ANTIALIAS"):
# Pillow 10+ sürümlerinde kaldırıldı -> LANCZOS ile eşleştir
Image.ANTIALIAS = Image.Resampling.LANCZOS
# ---------------------------------------------------
import os
import random
import re
from pathlib import Path
from typing import List, Tuple
from PIL import Image, ImageDraw, ImageFont
import numpy as np
from moviepy.editor import (
AudioFileClip,
ImageClip,
CompositeVideoClip,
concatenate_videoclips,
vfx,
)
# ==== KULLANICI AYARLARI ====
USERNAME = "@arabeskradyo6" # Videoda görünecek kullanıcı adı
OUTPUT_SIZE = (1080, 1920) # (genişlik, yükseklik) 9:16 Reels/TikTok tarzı. İstersen (1920,1080) yap.
FPS = 30 # Çıktı FPS
DURATION = 30 # Video süresi (sabit 30 sn)
IMAGES_PER_VIDEO = 6 # 6 görsel -> her biri ~5sn. images/ azsa otomatik düşer.
FONT_PATH = "fonts/Rubik-SemiBold.ttf" # Türkçe karakter destekli bir TTF. Yoksa default yedek kullanır.
TITLE_FONT_SIZE = 64
USER_FONT_SIZE = 42
TEXT_MARGIN = 40 # Kenarlardan iç boşluk (px)
TEXT_BG_ALPHA = 140 # Metin arkaplan saydamlığı (0-255)
# ==== KAYNAK KLASÖRLER ====
AUDIO_DIR = Path("audio")
IMAGES_DIR = Path("images")
OUTPUT_DIR = Path("output")
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
# ==== YARDIMCI FONKSİYONLAR ====
def list_media_files(folder: Path, exts: Tuple[str, ...]) -> List[Path]:
return sorted([p for p in folder.glob("*") if p.suffix.lower() in exts])
def infer_title_from_filename(path: Path) -> str:
# "Artist - Song Name (feat.) .mp3" -> "Artist - Song Name (feat.)"
title = path.stem
# Kaba temizlik: underscore -> boşluk, çift boşlukları düzelt
title = re.sub(r"[_]+", " ", title).strip()
title = re.sub(r"\s{2,}", " ", title)
return title
def load_font(size: int) -> ImageFont.FreeTypeFont:
try:
if Path(FONT_PATH).exists():
return ImageFont.truetype(FONT_PATH, size=size)
# Sistem fontlarına düşmek için isim vermeden dener (platforma göre değişir)
return ImageFont.truetype("DejaVuSans.ttf", size=size)
except Exception:
# Son çare: PIL default bitmap font (Türkçe destek sınırlı olabilir)
return ImageFont.load_default()
def make_text_overlay(
title: str,
username: str,
size: Tuple[int, int],
margin: int = 40,
title_size: int = 64,
user_size: int = 42,
bg_alpha: int = 140,
) -> str:
"""
Metinleri (şarkı adı + kullanıcı adı) şeffaf PNG'ye yazar ve dosya yolunu döner.
Pillow 10/11 uyumlu: textbbox() varsa onu, yoksa font.getsize() kullanır.
"""
W, H = size
title_font = load_font(title_size)
user_font = load_font(user_size)
# Ölçüm yardımcıları
from PIL import ImageDraw as _ImageDraw
def measure(draw_obj, text, font):
# Pillow 8+ : textbbox mevcut, en doğru ölçüm
if hasattr(draw_obj, "textbbox"):
l, t, r, b = draw_obj.textbbox((0, 0), text, font=font)
return (r - l, b - t)
# Eski sürümler için yedek
return font.getsize(text)
# Satır kaydırma
draw_tmp = ImageDraw.Draw(Image.new("RGBA", (W, H)))
max_text_width = W - 2 * margin
def wrap_text(text, font, max_width):
words = text.split()
lines, line = [], ""
for w in words:
test = (line + " " + w).strip()
tw, _ = measure(draw_tmp, test, font)
if tw <= max_text_width or not line:
line = test
else:
lines.append(line)
line = w
if line:
lines.append(line)
return lines
title_lines = wrap_text(title, title_font, max_text_width)
user_lines = wrap_text(f"@{username}", user_font, max_text_width)
# Yükseklik hesapla
line_spacing = 8
def block_height(lines, font):
if not lines:
return 0
heights = [measure(draw_tmp, ln, font)[1] for ln in lines]
return sum(heights) + line_spacing * (len(lines) - 1)
title_h = block_height(title_lines, title_font)
user_h = block_height(user_lines, user_font)
block_pad_v = 16
total_h = title_h + user_h + block_pad_v * 2 + 10
overlay = Image.new("RGBA", (W, total_h), (0, 0, 0, 0))
bg = Image.new("RGBA", (W, total_h), (0, 0, 0, bg_alpha))
overlay.paste(bg, (0, 0))
draw = ImageDraw.Draw(overlay)
# Başlığı merkezle
y = block_pad_v
for ln in title_lines:
tw, th = measure(draw, ln, title_font)
draw.text(((W - tw) // 2, y), ln, font=title_font, fill=(255, 255, 255, 255))
y += th + line_spacing
y += 2 # küçük boşluk
# Kullanıcı adını merkezle
for ln in user_lines:
tw, th = measure(draw, ln, user_font)
draw.text(((W - tw) // 2, y), ln, font=user_font, fill=(220, 220, 220, 255))
y += th + line_spacing
tmp_path = OUTPUT_DIR / f"overlay_{abs(hash(title))}.png"
overlay.save(tmp_path)
return str(tmp_path)
def make_image_clip(img_path: Path, duration: float, target_size: Tuple[int, int]) -> ImageClip:
"""
Görseli ekranı dolduracak şekilde letterbox olmadan kırp/zoom et (cover),
hafif bir yavaş zoom efekti ekle.
"""
W, H = target_size
clip = ImageClip(str(img_path)).set_duration(duration)
# Önce ortalama bir cover yapalım:
clip = clip.resize(height=H) if clip.h < clip.w else clip.resize(width=W)
# Cover: gerekiyorsa kırp
# Eni boyu aşan kenarları merkezden kırp
if clip.w < W:
clip = clip.resize(width=W)
if clip.h < H:
clip = clip.resize(height=H)
x_center = (clip.w - W) / 2
y_center = (clip.h - H) / 2
clip = clip.crop(x1=x_center, y1=y_center, x2=x_center + W, y2=y_center + H)
# Yumuşak zoom (Ken Burns hissi)
# 30 sn'de %6 büyüme için ~0.002/ sn
zoom_rate = 0.002 # istersen arttır/azalt
clip = clip.resize(lambda t: 1 + zoom_rate * t)
return clip
def build_video_for_audio(audio_path: Path, images: List[Path]) -> Path:
# Başlık + overlay PNG
title = infer_title_from_filename(audio_path)
overlay_png = make_text_overlay(
title=title,
username=USERNAME,
size=OUTPUT_SIZE,
margin=TEXT_MARGIN,
title_size=TITLE_FONT_SIZE,
user_size=USER_FONT_SIZE,
bg_alpha=TEXT_BG_ALPHA,
)
# 30 sn ses
audio = AudioFileClip(str(audio_path)).subclip(0, DURATION)
# Görsel süre/adet
n_images = min(IMAGES_PER_VIDEO, len(images)) if len(images) > 0 else 1
if n_images <= 0:
raise RuntimeError("images/ klasöründe görsel bulunamadı.")
segment = DURATION / n_images
chosen = random.sample(images, n_images) if len(images) >= n_images else images[:]
while len(chosen) < n_images:
chosen += random.sample(images, min(len(images), n_images - len(chosen)))
# Arka plan klipleri (Ken Burns hafif zoom içerir)
clips = [make_image_clip(p, segment, OUTPUT_SIZE) for p in chosen]
bg = concatenate_videoclips(clips, method="chain").set_duration(DURATION)
# Overlay bant
overlay_clip = (
ImageClip(overlay_png)
.set_duration(DURATION)
.set_position(("center", "top"))
)
# Final kompozit
final = CompositeVideoClip([bg, overlay_clip], size=OUTPUT_SIZE).set_audio(audio)
# Çıktı adı
safe_title = re.sub(r"[^\w\-]+", "_", title)[:80]
out_path = OUTPUT_DIR / f"{safe_title or 'video'}.mp4"
# Yaz ve temizle
try:
final.write_videofile(
str(out_path),
fps=FPS,
codec="libx264",
audio_codec="aac",
threads=os.cpu_count() or 4,
preset="medium",
ffmpeg_params=["-movflags", "+faststart", "-pix_fmt", "yuv420p"],
temp_audiofile=str(OUTPUT_DIR / "temp-audio.m4a"),
remove_temp=True,
)
finally:
# Geçici overlay PNG'sini sil (yazım başarısız olsa bile)
try:
os.remove(overlay_png)
except Exception:
pass
return out_path
def main():
audio_files = list_media_files(AUDIO_DIR, (".mp3", ".wav", ".m4a", ".flac", ".aac", ".ogg"))
image_files = list_media_files(IMAGES_DIR, (".jpg", ".jpeg", ".png", ".webp"))
if not audio_files:
raise SystemExit("audio/ klasöründe ses dosyası bulunamadı.")
if not image_files:
raise SystemExit("images/ klasöründe görsel bulunamadı.")
random.shuffle(image_files)
print(f"{len(audio_files)} şarkı bulundu, {len(image_files)} görsel bulundu.")
for i, a in enumerate(audio_files, 1):
print(f"[{i}/{len(audio_files)}] {a.name} -> video oluşturuluyor...")
try:
out = build_video_for_audio(a, image_files)
print(f" ✔ {out}")
except Exception as e:
print(f" ✖ Hata: {a.name}: {e}")
if __name__ == "__main__":
main()

Bir yanıt yazın