Compare commits

...

4 Commits

Author SHA1 Message Date
Flatlogic Bot
2b93a1bd15 4 2026-02-23 12:32:16 +00:00
Flatlogic Bot
cc9fdb2fe8 3 2026-02-23 09:23:18 +00:00
Flatlogic Bot
b464398843 2 2026-02-23 08:48:15 +00:00
Flatlogic Bot
0c65e736c6 quran gen 2026-02-23 08:34:41 +00:00
43 changed files with 598 additions and 167 deletions

BIN
backgrounds/nature.mp4 Normal file

Binary file not shown.

BIN
backgrounds/space.mp4 Normal file

Binary file not shown.

View File

@ -155,6 +155,9 @@ STATICFILES_DIRS = [
BASE_DIR / 'node_modules', BASE_DIR / 'node_modules',
] ]
MEDIA_URL = '/outputs/'
MEDIA_ROOT = BASE_DIR / 'outputs'
# Email # Email
EMAIL_BACKEND = os.getenv( EMAIL_BACKEND = os.getenv(
"EMAIL_BACKEND", "EMAIL_BACKEND",
@ -179,4 +182,4 @@ if EMAIL_USE_SSL:
# Default primary key field type # Default primary key field type
# https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field # https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

View File

@ -27,3 +27,4 @@ urlpatterns = [
if settings.DEBUG: if settings.DEBUG:
urlpatterns += static("/assets/", document_root=settings.BASE_DIR / "assets") urlpatterns += static("/assets/", document_root=settings.BASE_DIR / "assets")
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

Binary file not shown.

View File

@ -1,3 +1,8 @@
from django.contrib import admin 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')

View File

@ -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)),
],
),
]

View File

@ -1,3 +1,26 @@
from django.db import models 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}"

View File

@ -1,145 +1,285 @@
{% extends "base.html" %} {% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ project_name }} - Quran Reels Generator</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Amiri&family=Inter:wght@400;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css">
<style>
:root {
--primary-emerald: #064E3B;
--accent-gold: #D4AF37;
--soft-cream: #FCF8F1;
--text-dark: #1F2937;
}
body {
background-color: var(--soft-cream);
font-family: 'Inter', sans-serif;
color: var(--text-dark);
}
h1, h2, h3, .arabic {
font-family: 'Amiri', serif;
}
.navbar {
background-color: var(--primary-emerald);
padding: 1rem 0;
border-bottom: 3px solid var(--accent-gold);
}
.navbar-brand {
color: var(--accent-gold) !important;
font-weight: 700;
font-size: 1.5rem;
}
.hero {
background: linear-gradient(135deg, var(--primary-emerald) 0%, #065F46 100%);
color: white;
padding: 4rem 0;
margin-bottom: 3rem;
text-align: center;
}
.card {
border: none;
border-radius: 12px;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
background: white;
margin-bottom: 2rem;
}
.card-header {
background-color: white;
border-bottom: 1px solid #F3F4F6;
font-weight: 600;
padding: 1.25rem;
color: var(--primary-emerald);
}
.btn-primary {
background-color: var(--primary-emerald);
border: none;
padding: 0.75rem 2rem;
border-radius: 8px;
font-weight: 600;
transition: all 0.3s ease;
}
.btn-primary:hover {
background-color: #065F46;
transform: translateY(-1px);
}
.status-badge {
padding: 0.4rem 0.8rem;
border-radius: 20px;
font-size: 0.85rem;
font-weight: 600;
}
.status-pending { background: #FEF3C7; color: #92400E; }
.status-processing { background: #DBEAFE; color: #1E40AF; }
.status-completed { background: #D1FAE5; color: #065F46; }
.status-failed { background: #FEE2E2; color: #991B1B; }
.form-label { font-weight: 600; margin-bottom: 0.5rem; }
.form-select, .form-control {
border-radius: 8px;
border: 1px solid #D1D5DB;
padding: 0.6rem;
}
.video-preview-btn {
cursor: pointer;
transition: color 0.2s;
}
.video-preview-btn:hover {
color: var(--primary-emerald);
}
</style>
</head>
<body>
{% block title %}{{ project_name }}{% endblock %} <nav class="navbar navbar-dark">
<div class="container">
{% block head %} <a class="navbar-brand" href="#">
<link rel="preconnect" href="https://fonts.googleapis.com"> {{ project_name }}
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> </a>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet">
<style>
:root {
--bg-color-start: #6a11cb;
--bg-color-end: #2575fc;
--text-color: #ffffff;
--card-bg-color: rgba(255, 255, 255, 0.01);
--card-border-color: rgba(255, 255, 255, 0.1);
}
* {
box-sizing: border-box;
}
body {
margin: 0;
font-family: 'Inter', sans-serif;
background: linear-gradient(45deg, var(--bg-color-start), var(--bg-color-end));
color: var(--text-color);
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
text-align: center;
overflow: hidden;
position: relative;
}
body::before {
content: '';
position: absolute;
inset: 0;
background-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='100' height='100' viewBox='0 0 100 100'><path d='M-10 10L110 10M10 -10L10 110' stroke-width='1' stroke='rgba(255,255,255,0.05)'/></svg>");
animation: bg-pan 20s linear infinite;
z-index: -1;
}
@keyframes bg-pan {
0% {
background-position: 0% 0%;
}
100% {
background-position: 100% 100%;
}
}
main {
padding: 2rem;
}
.card {
background: var(--card-bg-color);
border: 1px solid var(--card-border-color);
border-radius: 16px;
padding: 2.5rem 2rem;
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
box-shadow: 0 12px 36px rgba(0, 0, 0, 0.25);
}
h1 {
font-size: clamp(2.2rem, 3vw + 1.2rem, 3.2rem);
font-weight: 700;
margin: 0 0 1.2rem;
letter-spacing: -0.02em;
}
p {
margin: 0.5rem 0;
font-size: 1.1rem;
opacity: 0.92;
}
.loader {
margin: 1.5rem auto;
width: 56px;
height: 56px;
border: 4px solid rgba(255, 255, 255, 0.25);
border-top-color: #fff;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
.runtime code {
background: rgba(0, 0, 0, 0.25);
padding: 0.15rem 0.45rem;
border-radius: 4px;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
}
footer {
position: absolute;
bottom: 1rem;
width: 100%;
text-align: center;
font-size: 0.85rem;
opacity: 0.75;
}
</style>
{% endblock %}
{% block content %}
<main>
<div class="card">
<h1>Analyzing your requirements and generating your app…</h1>
<div class="loader" role="status" aria-live="polite" aria-label="Applying initial changes">
<span class="sr-only">Loading…</span>
</div> </div>
<p class="hint">AppWizzy AI is collecting your requirements and applying the first changes.</p> </nav>
<p class="hint">This page will refresh automatically as the plan is implemented.</p>
<p class="runtime"> <div class="hero">
Runtime: Django <code>{{ django_version }}</code> · Python <code>{{ python_version }}</code> <div class="container">
— UTC <code>{{ current_time|date:"Y-m-d H:i:s" }}</code> <h1>Create Beautiful Quran Reels</h1>
</p> <p class="lead">Select verses, pick a reciter, and generate professional videos in seconds.</p>
</div> </div>
</main> </div>
<footer>
Page updated: {{ current_time|date:"Y-m-d H:i:s" }} (UTC) <div class="container">
</footer> <div class="row">
{% endblock %} <div class="col-lg-8">
<div class="card">
<div class="card-header">Video Settings</div>
<div class="card-body">
<form action="{% url 'generate_video' %}" method="POST" id="genForm">
{% csrf_token %}
<div class="row mb-3">
<div class="col-md-6">
<label class="form-label">Select Surah</label>
<select name="surah" id="surahSelect" class="form-select" required>
<option value="">Choose...</option>
{% for s in surahs %}
<option value="{{ s.number }}">{{ s.number }}. {{ s.englishName }} ({{ s.name }})</option>
{% endfor %}
</select>
</div>
<div class="col-md-6">
<label class="form-label">Reciter</label>
<select name="reciter" class="form-select" required>
{% for r in reciters %}
<option value="{{ r.identifier }}" {% if r.identifier == "ar.alafasy" %}selected{% endif %}>{{ r.name }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="row mb-3">
<div class="col-md-6">
<label class="form-label">Start Verse</label>
<input type="number" name="verse_start" id="verseStart" class="form-control" value="1" min="1" required>
</div>
<div class="col-md-6">
<label class="form-label">End Verse</label>
<input type="number" name="verse_end" id="verseEnd" class="form-control" value="5" min="1" required>
<small class="text-muted" id="maxAyahs">Max: -</small>
</div>
</div>
<div class="row mb-4">
<div class="col-md-6">
<label class="form-label">Background Video</label>
<select name="background" class="form-select" required>
{% for b in backgrounds %}
<option value="{{ b }}">{{ b }}</option>
{% endfor %}
</select>
</div>
<div class="col-md-6">
<label class="form-label">Text Color</label>
<input type="color" name="text_color" class="form-control form-control-color w-100" value="#FFFFFF">
</div>
</div>
<button type="submit" class="btn btn-primary w-100">Generate Video</button>
</form>
</div>
</div>
</div>
<div class="col-lg-4">
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<span>Recent Generations</span>
<button class="btn btn-sm btn-link p-0 text-decoration-none" onclick="location.reload()">
<i class="bi bi-arrow-clockwise"></i> Refresh
</button>
</div>
<div class="card-body p-0">
<ul class="list-group list-group-flush">
{% for task in tasks %}
<li class="list-group-item">
<div class="d-flex justify-content-between align-items-start mb-2">
<div>
<strong class="d-block">{{ task.surah_name }}</strong>
<small class="text-muted">Ayahs {{ task.verse_start }}-{{ task.verse_end }}</small>
</div>
<span class="status-badge status-{{ task.status }}">
{{ task.status|title }}
</span>
</div>
{% if task.status == 'completed' %}
<div class="btn-group w-100 mt-2" role="group">
<button type="button" class="btn btn-sm btn-outline-primary"
onclick="previewVideo('{{ task.output_path }}', '{{ task.surah_name }} ({{ task.verse_start }}-{{ task.verse_end }})')">
<i class="bi bi-play-fill"></i> Preview
</button>
<a href="{{ task.output_path }}" class="btn btn-sm btn-outline-success" download>
<i class="bi bi-download"></i> Download
</a>
</div>
{% elif task.status == 'failed' %}
<p class="text-danger small mt-1 mb-0"><i class="bi bi-exclamation-triangle"></i> {{ task.error_message|truncatechars:100 }}</p>
{% elif task.status == 'processing' or task.status == 'pending' %}
<div class="progress mt-2" style="height: 5px;">
<div class="progress-bar progress-bar-striped progress-bar-animated bg-success" role="progressbar" style="width: 100%"></div>
</div>
<p class="text-muted small mt-1 mb-0">Processing... Please refresh in a moment.</p>
{% endif %}
</li>
{% empty %}
<li class="list-group-item text-center text-muted py-4">No tasks yet</li>
{% endfor %}
</ul>
</div>
</div>
</div>
</div>
</div>
<!-- Video Preview Modal -->
<div class="modal fade" id="videoModal" tabindex="-1" aria-labelledby="videoModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="videoModalLabel">Video Preview</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body p-0 bg-black">
<div class="ratio ratio-9x16">
<video id="previewPlayer" controls>
<source src="" type="video/mp4">
Your browser does not support the video tag.
</video>
</div>
</div>
<div class="modal-footer">
<a id="downloadBtn" href="" class="btn btn-success" download>Download Video</a>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script>
document.getElementById('surahSelect').addEventListener('change', function() {
const surahNum = this.value;
if (!surahNum) return;
fetch(`/surah-details/${surahNum}/`)
.then(response => response.json())
.then(data => {
const max = data.numberOfAyahs;
document.getElementById('maxAyahs').textContent = `Max Ayahs: ${max}`;
document.getElementById('verseStart').max = max;
document.getElementById('verseEnd').max = max;
});
});
const videoModal = new bootstrap.Modal(document.getElementById('videoModal'));
const previewPlayer = document.getElementById('previewPlayer');
const downloadBtn = document.getElementById('downloadBtn');
const modalTitle = document.getElementById('videoModalLabel');
function previewVideo(url, title) {
previewPlayer.src = url;
downloadBtn.href = url;
modalTitle.textContent = "Preview: " + title;
videoModal.show();
previewPlayer.play();
}
// Stop video when modal is closed
document.getElementById('videoModal').addEventListener('hidden.bs.modal', function () {
previewPlayer.pause();
previewPlayer.src = "";
});
</script>
</body>
</html>

View File

@ -1,7 +1,8 @@
from django.urls import path from django.urls import path
from .views import home, generate_video_view, get_surah_details
from .views import home
urlpatterns = [ urlpatterns = [
path("", home, name="home"), path("", home, name="home"),
] path("generate/", generate_video_view, name="generate_video"),
path("surah-details/<int:surah_number>/", get_surah_details, name="surah_details"),
]

164
core/video_engine.py Normal file
View File

@ -0,0 +1,164 @@
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"
# Droid Sans Fallback often supports Arabic/CJK
FONT_ARABIC = "/usr/share/fonts/truetype/droid/DroidSansFallbackFull.ttf"
# Fallback for English text
FONT_SANS = "/usr/share/fonts/truetype/liberation/LiberationSans-Bold.ttf"
def escape_ffmpeg_text(text):
if not text:
return ""
# Use ord() to check for backslash to avoid syntax errors with literals
result = ""
for char in text:
if ord(char) == 92: # Backslash
result += "\\"
elif char == "'":
result += "'\\''"
elif char == ":":
result += ":"
else:
result += char
return result
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 get_audio_duration(file_path):
result = subprocess.run([
'ffprobe', '-v', 'error', '-show_entries', 'format=duration',
'-of', 'default=noprint_wrappers=1:nokey=1', str(file_path)
], capture_output=True, text=True)
return float(result.stdout.strip())
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 and collect 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'],
'duration': get_audio_duration(audio_path)
})
# 2. Combine Audio
combined_audio = AUDIO_DIR / f"task_{task.id}_full.mp3"
filter_complex_audio = "".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_audio, '-map', '[a]', str(combined_audio)])
subprocess.run(cmd, check=True)
total_duration = get_audio_duration(combined_audio)
# 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"
# Build drawtext filters
drawtext_filters = []
# 1. Surah Name at top
escaped_surah = escape_ffmpeg_text(task.surah_name)
drawtext_filters.append(
f"drawtext=text='{escaped_surah}':fontfile={FONT_SANS}:fontcolor={task.text_color}:fontsize=48:x=(w-text_w)/2:y=100:shadowcolor=black:shadowx=2:shadowy=2"
)
# 2. Verse text in middle (sequential)
current_time = 0
for verse in verses_data:
escaped_text = escape_ffmpeg_text(verse['text'])
start = current_time
end = current_time + verse['duration']
font_to_use = FONT_ARABIC if os.path.exists(FONT_ARABIC) else FONT_SANS
drawtext_filters.append(
f"drawtext=text='{escaped_text}':fontfile={font_to_use}:fontcolor={task.text_color}:fontsize=42:x=(w-text_w)/2:y=(h-text_h)/2:shadowcolor=black:shadowx=2:shadowy=2:enable='between(t,{start},{end})'"
)
current_time += verse['duration']
vf_chain = [
"scale=720:1280:force_original_aspect_ratio=increase",
"crop=720:1280",
*drawtext_filters
]
vf_string = ",".join(vf_chain)
subprocess.run([
'ffmpeg', '-y',
'-stream_loop', '-1', '-i', str(bg_video),
'-i', str(combined_audio),
'-t', str(total_duration),
'-map', '0:v:0', '-map', '1:a:0',
'-pix_fmt', 'yuv420p',
'-vf', vf_string,
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

View File

@ -1,25 +1,86 @@
import os import os
import platform import requests
from django.shortcuts import render, redirect, get_object_or_404
from django import get_version as django_version from django.http import JsonResponse
from django.shortcuts import render from .models import VideoTask
from django.utils import timezone from .video_engine import generate_video
import threading
def home(request): def home(request):
"""Render the landing screen with loader and environment details.""" # Fetch surahs and reciters for the form
host_name = request.get_host().lower() surahs = []
agent_brand = "AppWizzy" if host_name == "appwizzy.com" else "Flatlogic" reciters = []
now = timezone.now() 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 = { context = {
"project_name": "New Style", "surahs": surahs,
"agent_brand": agent_brand, "reciters": reciters,
"django_version": django_version(), "backgrounds": backgrounds,
"python_version": platform.python_version(), "tasks": tasks,
"current_time": now, "project_name": "Quran Reels Gen",
"host_name": host_name,
"project_description": os.getenv("PROJECT_DESCRIPTION", ""),
"project_image_url": os.getenv("PROJECT_IMAGE_URL", ""),
} }
return render(request, "core/index.html", context) 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)

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.