Nedir: audio klasöründeki müzikleri sırayla alır, images klasöründeki resimlerden her 5 saniyeye 1 tane düşecek şekilde rasgele görsel seçer, fontklasöründen ttf çağırır ve şarkının ismini, sizin sosyal medya kullanıcı adını vb videoya ekler, en son output klasörüne .mp4 formatında kaydeder. Örneğin : https://www.tiktok.com/@arabeskradyo6 Bunları youtube, tiktok, instagram hesaplarınıza yükleyin (bunu da otomatik yapacak script üzerinde çalışıyorum) 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üzikleri audio/ klasörüne yerleştir.
- make_videos.py betiğini çalıştır.
cd "C:\Users\Win10\Desktop\video maker"
venv\Scripts\activate
python make_videos.py
Kaynaklar bana aittir, alıntı değildir:
Github
TR :
Python ile Otomatik Video Oluşturma (MoviePy + Pillow)
EN :
Automating Video Creation with Python (MoviePy + Pillow) # --- 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()