| """ |
| Base Template Class |
| كل تمبلت جديد بيرث من الكلاس ده |
| """ |
|
|
| from abc import ABC, abstractmethod |
| from PIL import Image |
| from dataclasses import dataclass |
| from typing import Optional |
|
|
|
|
| @dataclass |
| class RenderRequest: |
| """البيانات اللي بتيجي من n8n""" |
| title: str |
| discount: str = "" |
| badge: str = "" |
| phone: str = "" |
| website: str = "" |
| image_path: str = "" |
| music_path: str = "" |
| output_path: str = "/tmp/output.mp4" |
| |
| bg_left: str = "" |
| bg_right: str = "" |
| duration: int = 6 |
| fps: int = 30 |
| width: int = 1280 |
| height: int = 720 |
| music_volume: float = 0.20 |
|
|
|
|
| class BaseTemplate(ABC): |
| """ |
| الكلاس الأساسي — كل تمبلت بيرث منه |
| |
| عشان تعمل تمبلت جديد: |
| 1. عمل ملف في templates/ |
| 2. ترث من BaseTemplate |
| 3. تعمل make_frame بتاعك |
| """ |
|
|
| NAME = "base" |
| DESCRIPTION = "Base template" |
| AUTHOR = "" |
|
|
| def ease_out(self, t: float) -> float: |
| return 1 - (1 - t) ** 3 |
|
|
| def ease_in_out(self, t: float) -> float: |
| return t * t * (3 - 2 * t) |
|
|
| def load_font(self, size: int): |
| from PIL import ImageFont |
| candidates = [ |
| '/tmp/arabic.ttf', |
| '/usr/share/fonts/truetype/noto/NotoNaskhArabic-Bold.ttf', |
| '/usr/share/fonts/truetype/noto/NotoSansArabic-Bold.ttf', |
| '/usr/share/fonts/opentype/noto/NotoNaskhArabic-Bold.otf', |
| 'C:/Windows/Fonts/arial.ttf', |
| ] |
| for path in candidates: |
| import os |
| if os.path.exists(path): |
| try: |
| return ImageFont.truetype(path, size) |
| except: |
| continue |
| return ImageFont.load_default() |
|
|
| def parse_color(self, s: str, default: tuple) -> tuple: |
| try: |
| return tuple(int(x) for x in s.split(',')) |
| except: |
| return default |
|
|
| @abstractmethod |
| def make_frame(self, t: float, req: RenderRequest, product_img, logo_img) -> Image.Image: |
| """ |
| ارسم frame واحد |
| t = الوقت الحالي بالثواني |
| يرجع PIL Image RGB |
| """ |
| pass |
|
|
| @property |
| def info(self) -> dict: |
| return { |
| "name": self.NAME, |
| "description": self.DESCRIPTION, |
| "author": self.AUTHOR, |
| } |
|
|