import os import subprocess import requests import json from pathlib import Path WORKSPACE_ROOT = Path(__file__).resolve().parent.parent OUTPUTS_DIR = WORKSPACE_ROOT / "outputs" FINAL_VIDEO_DIR = OUTPUTS_DIR / "final_video" TEMP_AUDIO_DIR = OUTPUTS_DIR / "temp_audio" AUDIO_DIR = OUTPUTS_DIR / "audio" BACKGROUNDS_DIR = WORKSPACE_ROOT / "backgrounds" def escape_ffmpeg_text(text): # Escape for ffmpeg drawtext filter # Single quotes need special handling: ' becomes '\'' # Also colons and other special chars might need escaping depending on context return text.replace("'", "'\\''").replace(":", "\\:") def ensure_bg_video(bg_path): """Ensures a background video exists and is not empty. Creates a placeholder if needed.""" if not bg_path.exists() or bg_path.stat().st_size == 0: bg_path.parent.mkdir(parents=True, exist_ok=True) # Create a 5-second 720x1280 (vertical) placeholder video color = "black" if "nature" in bg_path.name: color = "#064E3B" # Deep Emerald elif "space" in bg_path.name: color = "#1E1B4B" # Deep Indigo subprocess.run([ 'ffmpeg', '-y', '-f', 'lavfi', '-i', f'color=c={color}:s=720x1280:d=5', '-pix_fmt', 'yuv420p', str(bg_path) ], check=True) def generate_video(task_id): from core.models import VideoTask task = VideoTask.objects.get(id=task_id) task.status = 'processing' task.save() try: # Ensure directories exist for d in [FINAL_VIDEO_DIR, TEMP_AUDIO_DIR, AUDIO_DIR, BACKGROUNDS_DIR]: d.mkdir(parents=True, exist_ok=True) # 1. Fetch Verses Text and Audio verses_data = [] audio_files = [] api_url = f"https://api.alquran.cloud/v1/surah/{task.surah_number}/{task.reciter_identifier}" resp = requests.get(api_url) resp.raise_for_status() data = resp.json()['data'] all_ayahs = data['ayahs'] selected_ayahs = [a for a in all_ayahs if task.verse_start <= a['numberInSurah'] <= task.verse_end] # Download audio files for i, ayah in enumerate(selected_ayahs): audio_url = ayah['audio'] audio_path = TEMP_AUDIO_DIR / f"task_{task.id}_ayah_{i}.mp3" with requests.get(audio_url, stream=True) as r: r.raise_for_status() with open(audio_path, 'wb') as f: for chunk in r.iter_content(chunk_size=8192): f.write(chunk) audio_files.append(str(audio_path)) verses_data.append({ 'text': ayah['text'], 'audio': str(audio_path) }) # 2. Combine Audio combined_audio = AUDIO_DIR / f"task_{task.id}_full.mp3" # We use a filter_complex to concat audio as it's more reliable than concat protocol for different mp3s filter_complex = "".join([f"[{i}:a]" for i in range(len(audio_files))]) + f"concat=n={len(audio_files)}:v=0:a=1[a]" cmd = ['ffmpeg', '-y'] for f in audio_files: cmd.extend(['-i', f]) cmd.extend(['-filter_complex', filter_complex, '-map', '[a]', str(combined_audio)]) subprocess.run(cmd, check=True) # 3. Generate Video with FFmpeg bg_video = BACKGROUNDS_DIR / (task.background_video or "nature.mp4") ensure_bg_video(bg_video) output_video = FINAL_VIDEO_DIR / f"reels_{task.id}.mp4" # Escape text for drawtext escaped_surah = escape_ffmpeg_text(task.surah_name) # Simple FFmpeg command: loop background, add audio, trim to audio length # Using a vertical aspect ratio 720x1280 (common for Reels) subprocess.run([ 'ffmpeg', '-y', '-stream_loop', '-1', '-i', str(bg_video), '-i', str(combined_audio), '-shortest', '-map', '0:v:0', '-map', '1:a:0', '-pix_fmt', 'yuv420p', '-vf', f"scale=720:1280:force_original_aspect_ratio=increase,crop=720:1280,drawtext=text='{escaped_surah}':fontcolor={task.text_color}:fontsize=64:x=(w-text_w)/2:y=(h-text_h)/2:shadowcolor=black:shadowx=2:shadowy=2", str(output_video) ], check=True) task.status = 'completed' task.output_path = f"/outputs/final_video/{output_video.name}" task.save() except Exception as e: task.status = 'failed' task.error_message = str(e) task.save() import traceback print(traceback.format_exc()) raise e