""" إنشاء فيديو 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")
Hosted & Compiled with HtmlRunner.com