"""
إنشاء فيديو Shorts عمودي: ماذا يوجد داخل كرة الغولف؟
1080x1920 | ~40 ثانية | 30fps
"""
import numpy as np
from PIL import Image, ImageDraw, ImageFont
from moviepy.editor import (
ImageClip, VideoClip, CompositeVideoClip,
AudioFileClip, concatenate_videoclips,
ColorClip, TextClip
)
from moviepy.video.fx.all import fadein, fadeout
import os
# ============ إعدادات عامة ============
W, H = 1080, 1920
FPS = 30
BG_COLOR = (10, 12, 20) # خلفية داكنة
ACCENT = (0, 220, 160) # أخضر نيون (مثل لون الغولف)
WHITE = (245, 245, 245)
GRAY = (150, 150, 160)
os.makedirs("assets", exist_ok=True)
# ============ دوال مساعدة ============
def get_font(size, bold=False):
"""محاولة تحميل خط عربي، وإلا العودة لخط افتراضي"""
paths = [
"/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf" if bold
else "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
"C:/Windows/Fonts/arialbd.ttf" if bold else "C:/Windows/Fonts/arial.ttf",
]
for p in paths:
if os.path.exists(p):
return ImageFont.truetype(p, size)
return ImageFont.load_default()
def draw_text_center(draw, text, y, font, fill, W=W):
"""رسم نص في منتصف العرض"""
bbox = draw.textbbox((0, 0), text, font=font)
tw = bbox[2] - bbox[0]
draw.text(((W - tw) // 2, y), text, font=font, fill=fill)
def make_frame(t, scene_fn):
"""تحويل دالة مشهد إلى إطار PIL ثم numpy"""
img = Image.new("RGB", (W, H), BG_COLOR)
draw = ImageDraw.Draw(img)
scene_fn(draw, t, img)
return np.array(img)
# ============ رسم كرة الغولف ============
def draw_golf_ball(draw, cx, cy, r, dimples=True, color=(255, 255, 255)):
draw.ellipse([cx-r, cy-r, cx+r, cy+r], fill=color, outline=(200, 200, 200), width=2)
if dimples:
# نقاط صغيرة تحاكي النقاط (dimples)
import random
random.seed(42)
for _ in range(int(r * 1.2)):
angle = random.uniform(0, 2*np.pi)
dist = random.uniform(0, r * 0.85)
dx = cx + dist * np.cos(angle)
dy = cy + dist * np.sin(angle)
dr = r * 0.035
draw.ellipse([dx-dr, dy-dr, dx+dr, dy+dr], fill=(215, 215, 220))
# ============ المشاهد ============
def scene_intro(draw, t, img):
"""المشهد 1: مقدمة + كرة تتحرك"""
font_big = get_font(90, bold=True)
font_med = get_font(55)
# عنوان يظهر تدريجيًا
alpha = min(1.0, t / 0.8)
title_y = 250 + int(30 * (1 - alpha))
draw_text_center(draw, "ماذا يوجد", title_y, font_big, WHITE)
draw_text_center(draw, "داخل كرة الغولف؟", title_y + 110, font_big, ACCENT)
# كرة تهبط وتستقر
ball_y = 900 - int(150 * np.exp(-t * 3) * np.cos(t * 6))
draw_golf_ball(draw, W//2, ball_y, 180)
def scene_layers(draw, t, img):
"""المشهد 2: طبقات الكرة"""
font_med = get_font(55, bold=True)
font_sm = get_font(42)
draw_text_center(draw, "الطبقات من الخارج", 180, font_med, ACCENT)
layers = [
("1. الغلاف الخارجي", "بولي يوريثان أو أيونومر", "لمقاومة الماء والخدوش"),
("2. الطبقة الوسطى", "مطاط مرن عالي الكثافة", "تعطي المسافة والقوة"),
("3. النواة الداخلية", "مطاط صلب / هلامي", "تتحكم بالإحساس والدوران"),
]
start = 380
for i, (title, sub, desc) in enumerate(layers):
appear = t - (0.6 + i * 0.9)
if appear < 0:
continue
a = min(1.0, appear / 0.4)
y = start + i * 420 + int(40 * (1 - a))
# مستطيل خلفي
draw.rounded_rectangle(
[80, y, W-80, y+340],
radius=30,
fill=(20, 26, 40),
outline=ACCENT if i == 0 else GRAY,
width=3
)
draw.text((120, y+30), title, font=get_font(50, True), fill=ACCENT)
draw.text((120, y+110), sub, font=get_font(40), fill=WHITE)
draw.text((120, y+180), desc, font=get_font(34), fill=GRAY)
def scene_cross_section(draw, t, img):
"""المشهد 3: مقطع عرضي متحرك"""
font_med = get_font(60, bold=True)
font_sm = get_font(45)
draw_text_center(draw, "المقطع العرضي", 200, font_med, WHITE)
cx, cy, R = W//2, 900, 380
# تقدم الرسم
p = min(1.0, t / 2.5)
# الطبقة الخارجية
draw.ellipse([cx-R, cy-R, cx+R, cy+R], outline=ACCENT, width=8)
# الطبقة الوسطى
if p > 0.3:
r2 = int(R * 0.78)
draw.ellipse([cx-r2, cy-r2, cx+r2, cy+r2], outline=(80, 200, 255), width=8)
# النواة
if p > 0.6:
r3 = int(R * 0.45)
draw.ellipse([cx-r3, cy-r3, cx+r3, cy+r3], fill=(255, 180, 60), outline=(255, 220, 120), width=4)
# تسميات
if p > 0.85:
draw.text((cx+R+20, cy-R), "غلاف", font=font_sm, fill=ACCENT)
draw.text((cx+int(R*0.78)+20, cy-40), "مطاط", font=font_sm, fill=(80, 200, 255))
draw.text((cx-60, cy-20), "نواة", font=font_sm, fill=(255, 180, 60))
def scene_facts(draw, t, img):
"""المشهد 4: حقائق سريعة"""
font_med = get_font(58, bold=True)
font_sm = get_font(44)
draw_text_center(draw, "حقائق سريعة", 220, font_med, ACCENT)
facts = [
"• كرة الغولف تحتوي على 300-500 نقطة غائرة",
"• السرعة عند الضربة قد تصل 250 كم/س",
"• النواة مضغوطة تحت ضغط عالٍ جدًا",
"• التصميم يؤثر على المسافة والدوران",
]
for i, fact in enumerate(facts):
appear = t - (0.3 + i * 0.7)
if appear < 0:
continue
a = min(1.0, appear / 0.3)
y = 420 + i * 280
# خط جانبي
draw.rectangle([90, y, 100, y+150], fill=ACCENT)
draw.text((130, y), fact, font=font_sm, fill=WHITE)
def scene_outro(draw, t, img):
"""المشهد 5: خاتمة"""
font_big = get_font(80, bold=True)
font_sm = get_font(50)
draw_text_center(draw, "كرة صغيرة", 700, font_big, WHITE)
draw_text_center(draw, "بتقنية كبيرة!", 810, font_big, ACCENT)
draw_text_center(draw, "تابع للمزيد 🔔", 1000, font_sm, GRAY)
draw_golf_ball(draw, W//2, 1500, 130)
# ============ تركيب الفيديو ============
scenes = [
(scene_intro, 4.0),
(scene_layers, 6.0),
(scene_cross_section, 5.0),
(scene_facts, 6.0),
(scene_outro, 3.0),
]
clips = []
for i, (fn, dur) in enumerate(scenes):
clip = VideoClip(lambda t, fn=fn: make_frame(t, fn), duration=dur)
if i > 0:
clip = clip.fadein(0.4)
if i < len(scenes) - 1:
clip = clip.fadeout(0.4)
clips.append(clip)
final = concatenate_videoclips(clips, method="compose")
# ============ الصوت (اختياري) ============
# إذا كان لديك ملف موسيقى خلفية:
# audio = AudioFileClip("assets/bg_music.mp3").subclip(0, final.duration).volumex(0.3)
# final = final.set_audio(audio)
# ============ التصدير ============
final.write_videofile(
"golf_ball_shorts.mp4",
fps=FPS,
codec="libx264",
audio_codec="aac",
preset="medium",
threads=4,
)
print("✅ تم إنشاء الفيديو: golf_ball_shorts.mp4")