import gradio as gr import os import shutil from typing import List, Optional from scripts.generate_scripts import generate_script, generate_title, generate_description from scripts.generate_voice import generate_voice from scripts.get_footage import get_video_montage_from_folder from scripts.edit_video import edit_video from scripts.generate_subtitles import ( transcribe_audio_to_subs, chunk_text_by_words, add_subtitles_to_video, ) # ────────────────────────────────────────────────────────────────────────────── # Constants & helper utils # ────────────────────────────────────────────────────────────────────────────── WORDS_PER_SECOND = 2.3 # ≃ 140 wpm def safe_copy(src: str, dst: str) -> str: if os.path.abspath(src) == os.path.abspath(dst): return src shutil.copy(src, dst) return dst # ────────────────────────────────────────────────────────────────────────────── # Core processing pipeline # ────────────────────────────────────────────────────────────────────────────── def process_video( context: str, instruction: str, target_duration: int, script_mode: str, custom_script: Optional[str], lum: float, contrast: float, gamma: float, add_subs: bool, accumulated_videos: List[str] | None = None, user_music: Optional[str] = None, show_progress_bar: bool = True, ): """Build the final video using user‑defined visual parameters (brightness, contrast, gamma).""" if not accumulated_videos: raise ValueError("❌ Please upload at least one background video (.mp4) before generating.") approx_words = int(target_duration * WORDS_PER_SECOND) # --- 1. Script (AI or custom) --- if script_mode == "Use my script": if not custom_script or not custom_script.strip(): raise ValueError("❌ You selected 'Use my script' but the script field is empty!") script = custom_script.strip() title = generate_title(script) description = generate_description(script) else: prompt = ( f"You are a video creation expert. Here is the context: {context.strip()}\n" f"Instruction: {instruction.strip()}\n" f"🔴 Strict target duration: {target_duration}s — ≈ {approx_words} words (must be respected)." ) script = generate_script(prompt) title = generate_title(script) description = generate_description(script) # --- 2. Prepare folders --- for folder in ("./assets/audio", "./assets/backgrounds", "./assets/output"): os.makedirs(folder, exist_ok=True) voice_path = "./assets/audio/voice.mp3" final_no_subs = "./assets/output/final_video.mp4" final_with_subs = "./assets/output/final_video_subtitles.mp4" # --- 3. Copy videos --- for f in os.listdir("./assets/backgrounds"): if f.lower().endswith(".mp4"): os.remove(os.path.join("./assets/backgrounds", f)) for idx, v in enumerate(accumulated_videos): if not os.path.isfile(v) or not v.lower().endswith(".mp4"): raise ValueError(f"❌ Invalid file: {v}") safe_copy(v, os.path.join("./assets/backgrounds", f"video_{idx:03d}.mp4")) # --- 4. AI voice --- generate_voice(script, voice_path) # --- 5. Video montage --- music_path = user_music if user_music and os.path.isfile(user_music) else None _, out_no_audio = get_video_montage_from_folder( folder_path="./assets/backgrounds", audio_path=voice_path, output_dir="./assets/video_music", lum=lum, contrast=contrast, gamma=gamma, show_progress_bar=show_progress_bar, ) # --- 6. Mixing & subtitles --- edit_video(out_no_audio, voice_path, music_path, final_no_subs) if add_subs: segments = transcribe_audio_to_subs(voice_path) subs = chunk_text_by_words(segments, max_words=3) add_subtitles_to_video(final_no_subs, subs, final_with_subs) return script, title, description, final_with_subs else: return script, title, description, final_no_subs # ────────────────────────────────────────────────────────────────────────────── # Upload helper # ────────────────────────────────────────────────────────────────────────────── def accumulate_files(new: List[str], state: List[str] | None): state = state or [] for f in new or []: if isinstance(f, str) and os.path.isfile(f) and f.lower().endswith(".mp4") and f not in state: state.append(f) return state # ────────────────────────────────────────────────────────────────────────────── # Gradio UI # ────────────────────────────────────────────────────────────────────────────── with gr.Blocks(theme="gradio/soft") as demo: gr.Markdown("# 🎬 AI Video Generator — Advanced Controls") # ------------------- Parameters ------------------- with gr.Tab("🛠️ Settings"): with gr.Row(): context_input = gr.Textbox(label="🧠 Context", lines=4) instruction_input = gr.Textbox(label="🎯 Instruction", lines=4) duration_slider = gr.Slider(5, 120, 1, 60, label="⏱️ Target duration (s)") script_mode = gr.Radio([ "Generate script with AI", "Use my script", ], value="Generate script with AI", label="Script mode") custom_script_input = gr.Textbox(label="✍️ My script", lines=8, interactive=False) def toggle_script_input(mode): return gr.update(interactive=(mode == "Use my script")) script_mode.change(toggle_script_input, inputs=script_mode, outputs=custom_script_input) with gr.Accordion("🎨 Video Settings (brightness/contrast/gamma)", open=False): lum_slider = gr.Slider(0, 20, 6, step=0.5, label="Brightness (0–20)") contrast_slider = gr.Slider(0.5, 2.0, 1.0, step=0.05, label="Contrast (0.5–2.0)") gamma_slider = gr.Slider(0.5, 2.0, 1.0, step=0.05, label="Gamma (0.5–2.0)") with gr.Row(): add_subs_checkbox = gr.Checkbox(label="Add dynamic subtitles", value=True) with gr.Row(): show_bar = gr.Checkbox(label="Show progress bar", value=True) # Upload videos videos_dropzone = gr.Files(label="🎞️ Background videos (MP4)", file_types=[".mp4"], type="filepath") videos_state = gr.State([]) video_list_display = gr.Textbox(label="✅ Selected videos", interactive=False, lines=4) videos_dropzone.upload(accumulate_files, [videos_dropzone, videos_state], videos_state, queue=False) videos_state.change(lambda s: "\n".join(os.path.basename(f) for f in s), videos_state, video_list_display, queue=False) user_music = gr.File(label="🎵 Background music (MP3, optional)", file_types=[".mp3"], type="filepath") generate_btn = gr.Button("🚀 Generate the video", variant="primary") with gr.Tab("📤 Results"): video_output = gr.Video(label="🎬 Generated Video") # Script + copy button script_output = gr.Textbox(label="📝 Script", lines=6, interactive=False) copy_script_btn = gr.Button("📋 Copy") copy_script_btn.click( None, inputs=[script_output], outputs=None, js="(text) => navigator.clipboard.writeText(text)" ) # Title + copy button title_output = gr.Textbox(label="🎬 Title", lines=1, interactive=False) copy_title_btn = gr.Button("📋 Copy") copy_title_btn.click(None, inputs=title_output, outputs=None, js="(text) => {navigator.clipboard.writeText(text);}") # Description + copy button desc_output = gr.Textbox(label="📄 Description", lines=3, interactive=False) copy_desc_btn = gr.Button("📋 Copy") copy_desc_btn.click(None, inputs=desc_output, outputs=None, js="(text) => {navigator.clipboard.writeText(text);}") # ------------------- Generation Callback ------------------- generate_btn.click( fn=process_video, inputs=[ context_input, instruction_input, duration_slider, script_mode, custom_script_input, lum_slider, contrast_slider, gamma_slider, add_subs_checkbox, videos_state, user_music, show_bar, ], outputs=[script_output, title_output, desc_output, video_output], ) demo.launch(block=True)