diff --git a/backgrounds/nature.mp4 b/backgrounds/nature.mp4 new file mode 100644 index 0000000..e69de29 diff --git a/backgrounds/space.mp4 b/backgrounds/space.mp4 new file mode 100644 index 0000000..e69de29 diff --git a/core/__pycache__/admin.cpython-311.pyc b/core/__pycache__/admin.cpython-311.pyc index 2964e11..2d5aa34 100644 Binary files a/core/__pycache__/admin.cpython-311.pyc and b/core/__pycache__/admin.cpython-311.pyc differ diff --git a/core/__pycache__/models.cpython-311.pyc b/core/__pycache__/models.cpython-311.pyc index 18a063c..f82619b 100644 Binary files a/core/__pycache__/models.cpython-311.pyc and b/core/__pycache__/models.cpython-311.pyc differ diff --git a/core/__pycache__/urls.cpython-311.pyc b/core/__pycache__/urls.cpython-311.pyc index ebb8c6e..47fbe1e 100644 Binary files a/core/__pycache__/urls.cpython-311.pyc and b/core/__pycache__/urls.cpython-311.pyc differ diff --git a/core/__pycache__/video_engine.cpython-311.pyc b/core/__pycache__/video_engine.cpython-311.pyc new file mode 100644 index 0000000..6750035 Binary files /dev/null and b/core/__pycache__/video_engine.cpython-311.pyc differ diff --git a/core/__pycache__/views.cpython-311.pyc b/core/__pycache__/views.cpython-311.pyc index 8d204fa..90224ea 100644 Binary files a/core/__pycache__/views.cpython-311.pyc and b/core/__pycache__/views.cpython-311.pyc differ diff --git a/core/admin.py b/core/admin.py index 8c38f3f..2a18c7e 100644 --- a/core/admin.py +++ b/core/admin.py @@ -1,3 +1,8 @@ from django.contrib import admin +from .models import VideoTask -# Register your models here. +@admin.register(VideoTask) +class VideoTaskAdmin(admin.ModelAdmin): + list_display = ('surah_name', 'verse_start', 'verse_end', 'reciter_name', 'status', 'created_at') + list_filter = ('status', 'created_at') + search_fields = ('surah_name', 'reciter_name') \ No newline at end of file diff --git a/core/migrations/0001_initial.py b/core/migrations/0001_initial.py new file mode 100644 index 0000000..7890823 --- /dev/null +++ b/core/migrations/0001_initial.py @@ -0,0 +1,33 @@ +# Generated by Django 5.2.7 on 2026-02-23 08:03 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='VideoTask', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('surah_number', models.IntegerField()), + ('surah_name', models.CharField(max_length=255)), + ('reciter_identifier', models.CharField(max_length=255)), + ('reciter_name', models.CharField(max_length=255)), + ('verse_start', models.IntegerField()), + ('verse_end', models.IntegerField()), + ('background_video', models.CharField(max_length=255)), + ('text_color', models.CharField(default='#FFFFFF', max_length=20)), + ('status', models.CharField(choices=[('pending', 'Pending'), ('processing', 'Processing'), ('completed', 'Completed'), ('failed', 'Failed')], default='pending', max_length=20)), + ('output_path', models.CharField(blank=True, max_length=500, null=True)), + ('error_message', models.TextField(blank=True, null=True)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ], + ), + ] diff --git a/core/migrations/__pycache__/0001_initial.cpython-311.pyc b/core/migrations/__pycache__/0001_initial.cpython-311.pyc new file mode 100644 index 0000000..97f3b3b Binary files /dev/null and b/core/migrations/__pycache__/0001_initial.cpython-311.pyc differ diff --git a/core/models.py b/core/models.py index 71a8362..3d955e4 100644 --- a/core/models.py +++ b/core/models.py @@ -1,3 +1,26 @@ from django.db import models -# Create your models here. +class VideoTask(models.Model): + STATUS_CHOICES = [ + ('pending', 'Pending'), + ('processing', 'Processing'), + ('completed', 'Completed'), + ('failed', 'Failed'), + ] + + surah_number = models.IntegerField() + surah_name = models.CharField(max_length=255) + reciter_identifier = models.CharField(max_length=255) + reciter_name = models.CharField(max_length=255) + verse_start = models.IntegerField() + verse_end = models.IntegerField() + background_video = models.CharField(max_length=255) + text_color = models.CharField(max_length=20, default='#FFFFFF') + status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='pending') + output_path = models.CharField(max_length=500, blank=True, null=True) + error_message = models.TextField(blank=True, null=True) + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + def __str__(self): + return f"{self.surah_name} ({self.verse_start}-{self.verse_end}) - {self.status}" \ No newline at end of file diff --git a/core/templates/core/index.html b/core/templates/core/index.html index faec813..067d2a3 100644 --- a/core/templates/core/index.html +++ b/core/templates/core/index.html @@ -1,145 +1,214 @@ -{% extends "base.html" %} +{% load static %} + + + + + + {{ project_name }} - Quran Reels Generator + + + + + -{% block title %}{{ project_name }}{% endblock %} - -{% block head %} - - - - -{% endblock %} - -{% block content %} -
-
-

Analyzing your requirements and generating your app…

-
- Loading… +
-
- -{% endblock %} \ No newline at end of file + + +
+
+

Create Beautiful Quran Reels

+

Select verses, pick a reciter, and generate professional videos in seconds.

+
+
+ +
+
+
+
+
Video Settings
+
+
+ {% csrf_token %} +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + + Max: - +
+
+ +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ +
+
+
Recent Generations
+
+
    + {% for task in tasks %} +
  • +
    +
    + {{ task.surah_name }} + Ayahs {{ task.verse_start }}-{{ task.verse_end }} +
    + + {{ task.status|title }} + +
    + {% if task.status == 'completed' %} + Download + {% elif task.status == 'failed' %} +

    {{ task.error_message|truncatechars:50 }}

    + {% endif %} +
  • + {% empty %} +
  • No tasks yet
  • + {% endfor %} +
+
+
+
+
+
+ + + + + diff --git a/core/urls.py b/core/urls.py index 6299e3d..835cf8d 100644 --- a/core/urls.py +++ b/core/urls.py @@ -1,7 +1,8 @@ from django.urls import path - -from .views import home +from .views import home, generate_video_view, get_surah_details urlpatterns = [ path("", home, name="home"), -] + path("generate/", generate_video_view, name="generate_video"), + path("surah-details//", get_surah_details, name="surah_details"), +] \ No newline at end of file diff --git a/core/video_engine.py b/core/video_engine.py new file mode 100644 index 0000000..edab27c --- /dev/null +++ b/core/video_engine.py @@ -0,0 +1,92 @@ +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 generate_video(task_id): + from core.models import VideoTask + task = VideoTask.objects.get(id=task_id) + task.status = 'processing' + task.save() + + try: + # 1. Fetch Verses Text and Audio + # For simplicity in this slice, we fetch from api.alquran.cloud + # We need both the text (for ImageMagick) and the audio (for FFmpeg) + + verses_data = [] + audio_files = [] + + # We'll use the 'ar.alafasy' or user-selected reciter + # Example: https://api.alquran.cloud/v1/surah/1/ar.alafasy + 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 and prepare text + 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" + # ffmpeg -i "concat:file1.mp3|file2.mp3" -acodec copy output.mp3 + concat_str = "|".join(audio_files) + subprocess.run([ + 'ffmpeg', '-y', '-i', f'concat:{concat_str}', '-acodec', 'libmp3lame', str(combined_audio) + ], check=True) + + # 3. Generate Video with FFmpeg + # We use a background video and overlay the audio + bg_video = BACKGROUNDS_DIR / task.background_video + if not bg_video.exists(): + # Fallback to a placeholder or first available background + available = list(BACKGROUNDS_DIR.glob("*.mp4")) + if available: + bg_video = available[0] + else: + raise Exception("No background video found in backgrounds/ folder") + + output_video = FINAL_VIDEO_DIR / f"reels_{task.id}.mp4" + + # Simple FFmpeg command: loop background, add audio, trim to audio length + # For text overlay, we'd ideally use drawtext or ImageMagick. + # Here we do a basic version: + 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"drawtext=text='{task.surah_name}':fontcolor={task.text_color}:fontsize=48:x=(w-text_w)/2:y=(h-text_h)/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() + raise e diff --git a/core/views.py b/core/views.py index c9aed12..ff007ee 100644 --- a/core/views.py +++ b/core/views.py @@ -1,25 +1,86 @@ import os -import platform - -from django import get_version as django_version -from django.shortcuts import render -from django.utils import timezone - +import requests +from django.shortcuts import render, redirect, get_object_or_404 +from django.http import JsonResponse +from .models import VideoTask +from .video_engine import generate_video +import threading def home(request): - """Render the landing screen with loader and environment details.""" - host_name = request.get_host().lower() - agent_brand = "AppWizzy" if host_name == "appwizzy.com" else "Flatlogic" - now = timezone.now() + # Fetch surahs and reciters for the form + surahs = [] + reciters = [] + try: + surah_resp = requests.get("https://api.alquran.cloud/v1/surah", timeout=5) + if surah_resp.status_code == 200: + surahs = surah_resp.json()['data'] + + reciter_resp = requests.get("https://api.alquran.cloud/v1/edition?format=audio&language=ar&type=versebyverse", timeout=5) + if reciter_resp.status_code == 200: + reciters = reciter_resp.json()['data'] + except: + pass + + backgrounds = [] + if os.path.exists('backgrounds'): + backgrounds = [f for f in os.listdir('backgrounds') if f.endswith(('.mp4', '.mov'))] + + if not backgrounds: + backgrounds = ["nature.mp4"] + + tasks = VideoTask.objects.all().order_by('-created_at')[:10] context = { - "project_name": "New Style", - "agent_brand": agent_brand, - "django_version": django_version(), - "python_version": platform.python_version(), - "current_time": now, - "host_name": host_name, - "project_description": os.getenv("PROJECT_DESCRIPTION", ""), - "project_image_url": os.getenv("PROJECT_IMAGE_URL", ""), + "surahs": surahs, + "reciters": reciters, + "backgrounds": backgrounds, + "tasks": tasks, + "project_name": "Quran Reels Gen", } return render(request, "core/index.html", context) + +def generate_video_view(request): + if request.method == "POST": + surah_number = request.POST.get('surah') + reciter = request.POST.get('reciter') + verse_start = request.POST.get('verse_start') + verse_end = request.POST.get('verse_end') + background = request.POST.get('background') + text_color = request.POST.get('text_color', '#FFFFFF') + + # Get surah name + surah_name = "Unknown" + try: + surah_resp = requests.get(f"https://api.alquran.cloud/v1/surah/{surah_number}", timeout=5) + if surah_resp.status_code == 200: + surah_name = surah_resp.json()['data']['englishName'] + except: + pass + + task = VideoTask.objects.create( + surah_number=surah_number, + surah_name=surah_name, + reciter_identifier=reciter, + reciter_name=reciter, + verse_start=verse_start, + verse_end=verse_end, + background_video=background, + text_color=text_color, + status='pending' + ) + + # Run generation in background thread + thread = threading.Thread(target=generate_video, args=(task.id,)) + thread.start() + + return redirect('home') + return redirect('home') + +def get_surah_details(request, surah_number): + try: + resp = requests.get(f"https://api.alquran.cloud/v1/surah/{surah_number}", timeout=5) + if resp.status_code == 200: + return JsonResponse(resp.json()['data']) + except: + pass + return JsonResponse({'error': 'Failed to fetch'}, status=400) diff --git a/outputs/temp_audio/task_1_ayah_0.mp3 b/outputs/temp_audio/task_1_ayah_0.mp3 new file mode 100644 index 0000000..ef80dc5 Binary files /dev/null and b/outputs/temp_audio/task_1_ayah_0.mp3 differ diff --git a/outputs/temp_audio/task_1_ayah_1.mp3 b/outputs/temp_audio/task_1_ayah_1.mp3 new file mode 100644 index 0000000..8e5b998 Binary files /dev/null and b/outputs/temp_audio/task_1_ayah_1.mp3 differ diff --git a/outputs/temp_audio/task_1_ayah_2.mp3 b/outputs/temp_audio/task_1_ayah_2.mp3 new file mode 100644 index 0000000..473ad04 Binary files /dev/null and b/outputs/temp_audio/task_1_ayah_2.mp3 differ diff --git a/outputs/temp_audio/task_1_ayah_3.mp3 b/outputs/temp_audio/task_1_ayah_3.mp3 new file mode 100644 index 0000000..601922e Binary files /dev/null and b/outputs/temp_audio/task_1_ayah_3.mp3 differ diff --git a/outputs/temp_audio/task_1_ayah_4.mp3 b/outputs/temp_audio/task_1_ayah_4.mp3 new file mode 100644 index 0000000..b36c662 Binary files /dev/null and b/outputs/temp_audio/task_1_ayah_4.mp3 differ diff --git a/outputs/temp_audio/task_2_ayah_0.mp3 b/outputs/temp_audio/task_2_ayah_0.mp3 new file mode 100644 index 0000000..b43dfc9 Binary files /dev/null and b/outputs/temp_audio/task_2_ayah_0.mp3 differ