3
This commit is contained in:
parent
b464398843
commit
cc9fdb2fe8
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -11,6 +11,28 @@ TEMP_AUDIO_DIR = OUTPUTS_DIR / "temp_audio"
|
|||||||
AUDIO_DIR = OUTPUTS_DIR / "audio"
|
AUDIO_DIR = OUTPUTS_DIR / "audio"
|
||||||
BACKGROUNDS_DIR = WORKSPACE_ROOT / "backgrounds"
|
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):
|
def generate_video(task_id):
|
||||||
from core.models import VideoTask
|
from core.models import VideoTask
|
||||||
task = VideoTask.objects.get(id=task_id)
|
task = VideoTask.objects.get(id=task_id)
|
||||||
@ -18,15 +40,14 @@ def generate_video(task_id):
|
|||||||
task.save()
|
task.save()
|
||||||
|
|
||||||
try:
|
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
|
# 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 = []
|
verses_data = []
|
||||||
audio_files = []
|
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}"
|
api_url = f"https://api.alquran.cloud/v1/surah/{task.surah_number}/{task.reciter_identifier}"
|
||||||
resp = requests.get(api_url)
|
resp = requests.get(api_url)
|
||||||
resp.raise_for_status()
|
resp.raise_for_status()
|
||||||
@ -35,7 +56,7 @@ def generate_video(task_id):
|
|||||||
all_ayahs = data['ayahs']
|
all_ayahs = data['ayahs']
|
||||||
selected_ayahs = [a for a in all_ayahs if task.verse_start <= a['numberInSurah'] <= task.verse_end]
|
selected_ayahs = [a for a in all_ayahs if task.verse_start <= a['numberInSurah'] <= task.verse_end]
|
||||||
|
|
||||||
# Download audio files and prepare text
|
# Download audio files
|
||||||
for i, ayah in enumerate(selected_ayahs):
|
for i, ayah in enumerate(selected_ayahs):
|
||||||
audio_url = ayah['audio']
|
audio_url = ayah['audio']
|
||||||
audio_path = TEMP_AUDIO_DIR / f"task_{task.id}_ayah_{i}.mp3"
|
audio_path = TEMP_AUDIO_DIR / f"task_{task.id}_ayah_{i}.mp3"
|
||||||
@ -52,32 +73,30 @@ def generate_video(task_id):
|
|||||||
|
|
||||||
# 2. Combine Audio
|
# 2. Combine Audio
|
||||||
combined_audio = AUDIO_DIR / f"task_{task.id}_full.mp3"
|
combined_audio = AUDIO_DIR / f"task_{task.id}_full.mp3"
|
||||||
# ffmpeg -i "concat:file1.mp3|file2.mp3" -acodec copy output.mp3
|
# We use a filter_complex to concat audio as it's more reliable than concat protocol for different mp3s
|
||||||
concat_str = "|".join(audio_files)
|
filter_complex = "".join([f"[{i}:a]" for i in range(len(audio_files))]) + f"concat=n={len(audio_files)}:v=0:a=1[a]"
|
||||||
subprocess.run([
|
cmd = ['ffmpeg', '-y']
|
||||||
'ffmpeg', '-y', '-i', f'concat:{concat_str}', '-acodec', 'libmp3lame', str(combined_audio)
|
for f in audio_files:
|
||||||
], check=True)
|
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
|
# 3. Generate Video with FFmpeg
|
||||||
# We use a background video and overlay the audio
|
bg_video = BACKGROUNDS_DIR / (task.background_video or "nature.mp4")
|
||||||
bg_video = BACKGROUNDS_DIR / task.background_video
|
ensure_bg_video(bg_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"
|
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
|
# Simple FFmpeg command: loop background, add audio, trim to audio length
|
||||||
# For text overlay, we'd ideally use drawtext or ImageMagick.
|
# Using a vertical aspect ratio 720x1280 (common for Reels)
|
||||||
# Here we do a basic version:
|
|
||||||
subprocess.run([
|
subprocess.run([
|
||||||
'ffmpeg', '-y', '-stream_loop', '-1', '-i', str(bg_video),
|
'ffmpeg', '-y', '-stream_loop', '-1', '-i', str(bg_video),
|
||||||
'-i', str(combined_audio), '-shortest', '-map', '0:v:0', '-map', '1:a:0',
|
'-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",
|
'-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)
|
str(output_video)
|
||||||
], check=True)
|
], check=True)
|
||||||
|
|
||||||
@ -89,4 +108,6 @@ def generate_video(task_id):
|
|||||||
task.status = 'failed'
|
task.status = 'failed'
|
||||||
task.error_message = str(e)
|
task.error_message = str(e)
|
||||||
task.save()
|
task.save()
|
||||||
raise e
|
import traceback
|
||||||
|
print(traceback.format_exc())
|
||||||
|
raise e
|
||||||
BIN
outputs/audio/task_4_full.mp3
Normal file
BIN
outputs/audio/task_4_full.mp3
Normal file
Binary file not shown.
BIN
outputs/audio/task_5_full.mp3
Normal file
BIN
outputs/audio/task_5_full.mp3
Normal file
Binary file not shown.
BIN
outputs/final_video/reels_5.mp4
Normal file
BIN
outputs/final_video/reels_5.mp4
Normal file
Binary file not shown.
BIN
outputs/temp_audio/task_4_ayah_0.mp3
Normal file
BIN
outputs/temp_audio/task_4_ayah_0.mp3
Normal file
Binary file not shown.
BIN
outputs/temp_audio/task_5_ayah_0.mp3
Normal file
BIN
outputs/temp_audio/task_5_ayah_0.mp3
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user