import os import uuid import tempfile import gradio as gr from gtts import gTTS from PIL import Image import pytesseract from moviepy import ImageClip, AudioFileClip, concatenate_videoclips def set_clip_duration(clip, duration): if hasattr(clip, "with_duration"): return clip.with_duration(duration) return clip.set_duration(duration) def set_clip_fps(clip, fps): if hasattr(clip, "with_fps"): return clip.with_fps(fps) return clip.set_fps(fps) def resize_clip_height(clip, height): if hasattr(clip, "resized"): return clip.resized(height=height) return clip.resize(height=height) def attach_audio(video_clip, audio_clip): if hasattr(video_clip, "with_audio"): return video_clip.with_audio(audio_clip) return video_clip.set_audio(audio_clip) def extract_text_from_image(image_path): """ Reads visible text from a screenshot using OCR. Works best when screenshots contain dashboard labels, KPI names, chart titles, or table text. """ image = Image.open(image_path) text = pytesseract.image_to_string(image) cleaned_lines = [] for line in text.splitlines(): line = line.strip() if line: cleaned_lines.append(line) cleaned_text = " ".join(cleaned_lines) return cleaned_text def create_ai_narration(slide_number, total_slides, extracted_text): """ Creates a simple narration script from OCR text. This is intentionally lightweight for Hugging Face Free CPU. """ if extracted_text.strip() == "": return ( f"This is slide {slide_number} of {total_slides}. " "This screen appears to show a visual dashboard or report. " "The key takeaway should be reviewed based on the visible charts, metrics, and layout." ) # Limit text so narration does not become too long max_chars = 450 short_text = extracted_text[:max_chars] if slide_number == 1: intro = "This first screen introduces the main dashboard view." elif slide_number == total_slides: intro = "This final screen summarizes the key information shown in the report." else: intro = f"This is slide {slide_number} of {total_slides}." narration = ( f"{intro} " f"The visible information includes: {short_text}. " "The main point is to review the key metrics, compare the important values, " "and identify where business attention may be required." ) return narration def create_slide_video(image_path, narration_text, output_height, temp_dir, run_id, slide_number): """ Creates one slide video: screenshot + AI-generated narration audio. """ audio_path = os.path.join( temp_dir, f"slide_audio_{run_id}_{slide_number}.mp3" ) tts = gTTS(text=narration_text, lang="en") tts.save(audio_path) audio = AudioFileClip(audio_path) audio_duration = audio.duration slide_duration = audio_duration + 0.5 image_clip = ImageClip(image_path) image_clip = set_clip_duration(image_clip, slide_duration) image_clip = resize_clip_height(image_clip, output_height) image_clip = set_clip_fps(image_clip, 24) slide_clip = attach_audio(image_clip, audio) return slide_clip, audio, audio_path, narration_text def create_video_from_screenshots(images, output_height): if not images: return None, None, "Please upload at least one screenshot or image.", "" try: output_height = int(output_height) if output_height < 240: return None, None, "Output height should be at least 240.", "" run_id = str(uuid.uuid4()) temp_dir = tempfile.gettempdir() final_video_path = os.path.join( temp_dir, f"ai_narrated_video_{run_id}.mp4" ) total_slides = len(images) slide_clips = [] audio_clips = [] audio_paths = [] generated_narrations = [] for index, image_path in enumerate(images): slide_number = index + 1 extracted_text = extract_text_from_image(image_path) narration_text = create_ai_narration( slide_number=slide_number, total_slides=total_slides, extracted_text=extracted_text ) slide_clip, audio_clip, audio_path, final_narration = create_slide_video( image_path=image_path, narration_text=narration_text, output_height=output_height, temp_dir=temp_dir, run_id=run_id, slide_number=slide_number ) slide_clips.append(slide_clip) audio_clips.append(audio_clip) audio_paths.append(audio_path) generated_narrations.append( f"Slide {slide_number}:\n{final_narration}" ) final_video = concatenate_videoclips( slide_clips, method="compose" ) final_video.write_videofile( final_video_path, codec="libx264", audio_codec="aac", fps=24, preset="ultrafast", threads=2 ) final_duration = final_video.duration final_video.close() for clip in slide_clips: clip.close() for audio in audio_clips: audio.close() narration_preview = "\n\n".join(generated_narrations) return ( final_video_path, audio_paths[0], f"Success: AI-generated narrated video created. Slides: {total_slides}. Duration: {final_duration:.1f} seconds.", narration_preview ) except Exception as e: return None, None, f"Error: {str(e)}", "" with gr.Blocks(title="AI Screenshot to Narrated Video Generator") as demo: gr.Markdown( """ # AI Screenshot to Narrated Video Generator Upload screenshots and the app will automatically: 1. Read text from each screenshot 2. Generate narration for each slide 3. Convert narration to audio 4. Create a narrated video No manual narration text is required. """ ) with gr.Row(): with gr.Column(): images_input = gr.Files( label="Upload Screenshots / Images", file_types=["image"], type="filepath" ) height_input = gr.Dropdown( label="Output Video Height", choices=[360, 480, 720], value=480 ) generate_button = gr.Button("Generate AI Narrated Video") with gr.Column(): video_output = gr.Video(label="Generated Video") audio_output = gr.Audio(label="First Slide Audio Preview") status_output = gr.Textbox(label="Status") narration_output = gr.Textbox( label="Generated Narration Preview", lines=12 ) generate_button.click( fn=create_video_from_screenshots, inputs=[ images_input, height_input ], outputs=[ video_output, audio_output, status_output, narration_output ] ) demo.queue() demo.launch()