| from fastapi import APIRouter, HTTPException, File, UploadFile, Form |
| import os |
| import json |
| import shutil |
| from schemas import SubtitlePreset |
| from typing import List, Optional |
|
|
| router = APIRouter() |
|
|
| |
| ROUTER_DIR = os.path.dirname(os.path.abspath(__file__)) |
| CLIPPING_DIR = os.path.dirname(ROUTER_DIR) |
| PRESETS_DIR = os.path.join(CLIPPING_DIR, "temp_videos", "presets") |
| FONTS_DIR = os.path.join(CLIPPING_DIR, "temp_videos", "fonts") |
| os.makedirs(PRESETS_DIR, exist_ok=True) |
| os.makedirs(FONTS_DIR, exist_ok=True) |
|
|
| @router.post("/save", summary="Create or Update a Custom Preset") |
| async def save_preset( |
| name: str = Form(..., description="Unique name for the preset"), |
| font_name: str = Form("Arial", description="Font family name"), |
| font_size: int = Form(48, description="Font size in pixels"), |
| primary_color: str = Form("#FFFFFF", description="Text color (Hex)"), |
| secondary_color: str = Form("#FFFF00", description="Highlight color (Hex)"), |
| outline_color: str = Form("#000000", description="Outline color (Hex)"), |
| back_color: str = Form("#000000", description="Background box color (Hex)"), |
| outline_width: float = Form(2.0), |
| shadow_depth: float = Form(1.0), |
| alignment: int = Form(2, description="2=Bottom Center, 5=Middle Center, 8=Top Center"), |
| margin_v: int = Form(100, description="Vertical margin from edge"), |
| pop_up_scale: float = Form(1.2, description="Animation scale factor"), |
| highlight_mode: str = Form("karaoke", description="karaoke or instant"), |
| back_box_enabled: bool = Form(True), |
| display_mode: str = Form("word", description="word or sentence"), |
| max_words_per_line: int = Form(3, description="Max words per line in sentence mode"), |
| uppercase: bool = Form(False, description="Convert English text to ALL CAPS"), |
| letter_spacing: float = Form(0.0, description="Spacing between characters"), |
| line_spacing: int = Form(0, description="Vertical spacing between lines"), |
| background_opacity: float = Form(1.0, description="Opacity of background box (0.0 to 1.0)"), |
| glow_intensity: int = Form(0, description="Intensity of glow effect (0 to 10)"), |
| rotation_angle: float = Form(0.0, description="Rotation angle in degrees"), |
| margin_h: int = Form(20, description="Horizontal margin"), |
| custom_font_file: Optional[UploadFile] = File(None, description="Optional: Upload a .ttf or .otf font file") |
| ): |
| """ |
| يتيح هذا الـ Endpoint إنشاء تصميم كامل (Preset). |
| يمكنك رفع خط خاص (Custom Font) وسيتم حفظه واستخدامه تلقائياً. |
| """ |
| try: |
| |
| final_font_name = font_name |
| if custom_font_file: |
| font_ext = os.path.splitext(custom_font_file.filename)[1] |
| if font_ext.lower() not in ['.ttf', '.otf']: |
| raise HTTPException(status_code=400, detail="Only .ttf and .otf fonts are supported") |
| |
| font_path = os.path.join(FONTS_DIR, custom_font_file.filename) |
| with open(font_path, "wb") as buffer: |
| shutil.copyfileobj(custom_font_file.file, buffer) |
| |
| |
| final_font_name = os.path.splitext(custom_font_file.filename)[0] |
|
|
| |
| preset_dict = { |
| "name": name, |
| "font_name": final_font_name, |
| "font_size": font_size, |
| "primary_color": primary_color, |
| "secondary_color": secondary_color, |
| "outline_color": outline_color, |
| "back_color": back_color, |
| "outline_width": outline_width, |
| "shadow_depth": shadow_depth, |
| "alignment": alignment, |
| "margin_v": margin_v, |
| "pop_up_scale": pop_up_scale, |
| "highlight_mode": highlight_mode, |
| "back_box_enabled": back_box_enabled, |
| "display_mode": display_mode, |
| "max_words_per_line": max_words_per_line, |
| "uppercase": uppercase, |
| "letter_spacing": letter_spacing, |
| "line_spacing": line_spacing, |
| "background_opacity": background_opacity, |
| "glow_intensity": glow_intensity, |
| "rotation_angle": rotation_angle, |
| "margin_h": margin_h |
| } |
|
|
| |
| file_path = os.path.join(PRESETS_DIR, f"{name}.json") |
| with open(file_path, "w", encoding="utf-8") as f: |
| json.dump(preset_dict, f, indent=4) |
|
|
| return { |
| "status": "success", |
| "message": f"Preset '{name}' saved successfully.", |
| "data": preset_dict |
| } |
| except Exception as e: |
| raise HTTPException(status_code=500, detail=f"Failed to save preset: {str(e)}") |
|
|
| @router.get("/list", summary="List all saved presets") |
| async def list_presets(): |
| if not os.path.exists(PRESETS_DIR): |
| return [] |
| return [f.replace(".json", "") for f in os.listdir(PRESETS_DIR) if f.endswith(".json")] |
|
|
| @router.delete("/delete/{name}", summary="Delete a preset") |
| async def delete_preset(name: str): |
| file_path = os.path.join(PRESETS_DIR, f"{name}.json") |
| if os.path.exists(file_path): |
| os.remove(file_path) |
| return {"status": "success", "message": f"Preset '{name}' deleted."} |
| raise HTTPException(status_code=404, detail="Preset not found") |
|
|
| def load_preset_by_name(name: str) -> Optional[SubtitlePreset]: |
| """Helper function to load a preset for processing""" |
| file_path = os.path.join(PRESETS_DIR, f"{name}.json") |
| if os.path.exists(file_path): |
| with open(file_path, "r", encoding="utf-8") as f: |
| data = json.load(f) |
| return SubtitlePreset(**data) |
| return None |