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 # -------------------------------------------------- # Startup # -------------------------------------------------- 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) # -------------------------------------------------- # Fonts # -------------------------------------------------- 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) # -------------------------------------------------- # Main Process # -------------------------------------------------- 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) # ===================================== # BACKGROUND # ===================================== 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" ) # ===================================== # JSON -> QVM # ===================================== 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 # ===================================== # ASS # ===================================== 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()) # ===================================== # RENDER 1 FRAME # ===================================== 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) # ========================================== # VALIDATION # ========================================== 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 # -------------------------------------------------- # UI # -------------------------------------------------- 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()