malvin noel
Corrected initial push of light AI Video Generator
8b05224
raw
history blame
10 kB
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()