| import os |
| import json |
| import shutil |
| import gradio as gr |
| import subprocess |
| import cv2 |
| import tempfile |
|
|
|
|
|
|
| from fonts import setup_fonts |
| from converter import convert_qurancaption_to_qvm |
| from subtitle import generate_ass |
| from background import generate_background |
| from background_video import prepare_background_video |
| from renderer import render_final_video |
| from intro import prepend_intro |
|
|
| |
| |
| |
|
|
| setup_fonts() |
|
|
| for f in os.listdir("fonts"): |
|
|
| if f.endswith(".ttf") or f.endswith(".otf"): |
|
|
| path = os.path.join( |
| "fonts", |
| f |
| ) |
|
|
| print("\n" + "=" * 60) |
| print("FONT INFO:", f) |
| print("=" * 60) |
|
|
| try: |
|
|
| result = subprocess.run( |
| ["fc-scan", path], |
| capture_output=True, |
| text=True |
| ) |
|
|
| print(result.stdout[:5000]) |
|
|
| except Exception as e: |
|
|
| print(e) |
|
|
| os.makedirs("uploads", exist_ok=True) |
| os.makedirs("temp", exist_ok=True) |
| os.makedirs("outputs", exist_ok=True) |
|
|
|
|
| |
| |
| |
|
|
| def get_available_fonts(): |
|
|
| fonts = [] |
|
|
| if os.path.exists("fonts"): |
|
|
| for f in os.listdir("fonts"): |
|
|
| if ( |
| f.endswith(".ttf") |
| or f.endswith(".otf") |
| ): |
|
|
| fonts.append( |
| f.replace(".ttf", "") |
| .replace(".otf", "") |
| ) |
|
|
| if not fonts: |
|
|
| fonts = [ |
| "Amiri-Regular" |
| ] |
|
|
| return sorted(fonts) |
|
|
|
|
| |
| |
| |
|
|
| def preview_layout( |
| images, |
| background_video, |
| timing_json, |
| arabic_font, |
| arabic_y, |
| translation_gap, |
| arabic_font_size, |
| translation_font_size |
| ): |
|
|
| os.makedirs("temp", exist_ok=True) |
|
|
| |
| |
| |
|
|
| if images: |
|
|
| background_image = images[0].name |
|
|
| elif background_video: |
|
|
| background_image = "temp/preview_frame.jpg" |
|
|
| subprocess.run( |
| [ |
| "ffmpeg", |
| "-y", |
| "-i", |
| background_video, |
| "-vframes", |
| "1", |
| background_image |
| ], |
| check=True |
| ) |
|
|
| else: |
|
|
| raise Exception( |
| "Upload Images atau Background Video" |
| ) |
|
|
| |
| |
| |
|
|
| qvm_json = "temp/preview_qvm.json" |
|
|
| convert_qurancaption_to_qvm( |
| timing_json.name, |
| qvm_json |
| ) |
|
|
|
|
|
|
|
|
| with open( |
| qvm_json, |
| "r", |
| encoding="utf-8" |
| ) as f: |
| |
| data = json.load(f) |
| |
| data[0]["start_time"] = 0 |
| data[0]["end_time"] = 999 |
| |
| |
| |
| |
| |
|
|
| preview_ass = "temp/preview.ass" |
|
|
| generate_ass( |
| qvm_json, |
| preview_ass, |
| arabic_font, |
| arabic_y, |
| translation_gap, |
| arabic_font_size, |
| translation_font_size |
| ) |
|
|
| print("=" * 60) |
| print("PREVIEW ASS") |
| print("=" * 60) |
| |
| print( |
| "ASS EXISTS:", |
| os.path.exists(preview_ass) |
| ) |
| |
| if os.path.exists(preview_ass): |
| |
| print( |
| "ASS SIZE:", |
| os.path.getsize(preview_ass) |
| ) |
| |
| with open( |
| preview_ass, |
| "r", |
| encoding="utf-8" |
| ) as f: |
| |
| print(f.read()) |
|
|
| |
| |
| |
|
|
| preview_output = "temp/preview.jpg" |
|
|
| subprocess.run( |
| [ |
| "ffmpeg", |
| "-y", |
| |
| "-loop", "1", |
| "-i", background_image, |
| |
| "-vf", |
| f"ass={preview_ass}:fontsdir=fonts", |
| |
| "-ss", |
| "1", |
| |
| "-frames:v", |
| "1", |
| |
| "-update", |
| "1", |
| |
| preview_output |
| ], |
| check=True |
| ) |
|
|
| return preview_output |
| |
| def process( |
| images, |
| background_video, |
| audio, |
| timing_json, |
| arabic_font, |
| intro_video, |
| arabic_y, |
| translation_gap, |
| arabic_font_size, |
| translation_font_size |
| ): |
|
|
| print("=" * 60) |
| print("SELECTED UI FONT") |
| print("=" * 60) |
| print(arabic_font) |
|
|
|
|
| |
| |
| |
|
|
| if not images and not background_video: |
| raise Exception( |
| "Upload Images atau Background Video" |
| ) |
| |
| for folder in [ |
| "uploads", |
| "temp" |
| ]: |
|
|
| if os.path.exists(folder): |
| shutil.rmtree(folder) |
|
|
| os.makedirs(folder) |
|
|
| image_paths = [] |
|
|
| if images: |
|
|
| for i, img in enumerate(images): |
|
|
| ext = os.path.splitext( |
| img.name |
| )[1] |
|
|
| dst = ( |
| f"uploads/{i:03d}{ext}" |
| ) |
|
|
| shutil.copy( |
| img.name, |
| dst |
| ) |
|
|
| image_paths.append( |
| dst |
| ) |
|
|
| audio_path = ( |
| "uploads/audio.mp3" |
| ) |
|
|
| shutil.copy( |
| audio, |
| audio_path |
| ) |
|
|
| json_path = ( |
| "uploads/timing.json" |
| ) |
|
|
| shutil.copy( |
| timing_json.name, |
| json_path |
| ) |
|
|
| qvm_json = ( |
| "temp/qvm_timing.json" |
| ) |
|
|
| convert_qurancaption_to_qvm( |
| json_path, |
| qvm_json |
| ) |
|
|
| with open( |
| qvm_json, |
| "r", |
| encoding="utf-8" |
| ) as f: |
|
|
| preview = json.load(f) |
|
|
|
|
|
|
| print( |
| "TOTAL SUBTITLES:", |
| len(preview) |
| ) |
|
|
| if preview: |
|
|
| print( |
| "FIRST:", |
| preview[0] |
| ) |
|
|
| background_path = ( |
| "temp/background.mp4" |
| ) |
|
|
| if background_video: |
|
|
| prepare_background_video( |
| background_video, |
| background_path |
| ) |
|
|
| else: |
|
|
| generate_background( |
| image_paths, |
| background_path |
| ) |
|
|
| subtitle_path = ( |
| "temp/subtitles.ass" |
| ) |
|
|
| generate_ass( |
| qvm_json, |
| subtitle_path, |
| arabic_font, |
| arabic_y, |
| translation_gap, |
| arabic_font_size, |
| translation_font_size |
| ) |
|
|
| main_video = ( |
| "outputs/main.mp4" |
| ) |
|
|
| render_final_video( |
| background_path, |
| audio_path, |
| subtitle_path, |
| main_video |
| ) |
| |
| if intro_video: |
|
|
| final_video = "outputs/final.mp4" |
|
|
| prepend_intro( |
| intro_video.name, |
| main_video, |
| final_video |
| ) |
|
|
| return final_video |
|
|
| return main_video |
|
|
|
|
| |
| |
| |
|
|
| with gr.Blocks() as demo: |
|
|
| gr.Markdown( |
| """ |
| # Quran Cinematic Video Generator |
| |
| Upload: |
| |
| - Images |
| - Audio MP3 |
| - QuranCaption JSON |
| |
| Generate cinematic Quran video automatically. |
| """ |
| ) |
|
|
| intro_video = gr.File( |
| label="Intro Video (Optional)" |
| ) |
| |
| images = gr.Files( |
| label="Upload Images", |
| file_count="multiple" |
| ) |
|
|
| background_video = gr.Video( |
| label="Background Video (Optional)" |
| ) |
|
|
| audio = gr.Audio( |
| type="filepath", |
| label="Upload Audio MP3" |
| ) |
|
|
| timing_json = gr.File( |
| label="Upload QuranCaption JSON" |
| ) |
|
|
| arabic_font = gr.Dropdown( |
| choices=get_available_fonts(), |
| value=get_available_fonts()[0], |
| label="Arabic Font" |
| ) |
|
|
| arabic_y = gr.Slider( |
| minimum=200, |
| maximum=1200, |
| value=380, |
| step=10, |
| label="Arabic Position Y" |
| ) |
| |
| translation_gap = gr.Slider( |
| minimum=0, |
| maximum=120, |
| value=25, |
| step=5, |
| label="Translation Gap" |
| ) |
|
|
| arabic_font_size = gr.Slider( |
| 80, |
| 180, |
| value=120, |
| step=2, |
| label="Arabic Font Size" |
| ) |
| |
| translation_font_size = gr.Slider( |
| 20, |
| 60, |
| value=34, |
| step=1, |
| label="Translation Font Size" |
| ) |
|
|
| preview_btn = gr.Button( |
| "Preview Layout" |
| ) |
| |
| preview_image = gr.Image( |
| label="Preview" |
| ) |
|
|
| generate_btn = gr.Button( |
| "Generate Video", |
| variant="primary" |
| ) |
|
|
| output_video = gr.Video( |
| label="Result" |
| ) |
|
|
| preview_btn.click( |
| fn=preview_layout, |
| inputs=[ |
| images, |
| background_video, |
| timing_json, |
| arabic_font, |
| arabic_y, |
| translation_gap, |
| arabic_font_size, |
| translation_font_size |
| ], |
| outputs=preview_image |
| ) |
|
|
| generate_btn.click( |
| fn=process, |
| inputs=[ |
| images, |
| background_video, |
| audio, |
| timing_json, |
| arabic_font, |
| intro_video, |
| arabic_y, |
| translation_gap, |
| arabic_font_size, |
| translation_font_size |
| ], |
| outputs=output_video |
| ) |
|
|
| demo.launch() |