import gradio as gr import tempfile, requests, os from langchain.chains import LLMChain from langchain.prompts import PromptTemplate from langchain.chat_models import ChatOpenAI from gtts import gTTS from bs4 import BeautifulSoup from PIL import Image, ImageDraw, ImageFont import ffmpeg import textwrap # OpenAI LLM llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0.3) summary_prompt = PromptTemplate.from_template(""" Provide a crisp, promotional-style summary (under 50 words) of the following: {text} Summary: """) summary_chain = LLMChain(llm=llm, prompt=summary_prompt) # Extract article content def extract_main_content(url): resp = requests.get(url, timeout=10) soup = BeautifulSoup(resp.content, "html.parser") for tag in soup(["nav", "header", "footer", "aside", "script", "style", "noscript"]): tag.decompose() paras = [p.get_text() for p in soup.find_all("p") if len(p.get_text()) > 60] return "\n".join(paras[:20]) or None # Download C# Corner logo LOGO_URL = "https://csharpcorner-mindcrackerinc.netdna-ssl.com/App_Themes/Default/images/c-sharp-corner-logo.png" def download_logo(): logo_path = tempfile.NamedTemporaryFile(delete=False, suffix=".png").name with open(logo_path, 'wb') as f: f.write(requests.get(LOGO_URL).content) return logo_path # Create image slides from text chunks def create_slides(text, duration, output_folder, max_lines=6): font_path = "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf" font = ImageFont.truetype(font_path, 48) logo_path = download_logo() chunks = textwrap.wrap(text, width=40) slides = ["\n".join(chunks[i:i+max_lines]) for i in range(0, len(chunks), max_lines)] per_slide_time = duration / len(slides) slide_paths = [] for i, slide_text in enumerate(slides): img = Image.new("RGB", (1280, 720), color=(20, 30, 60)) draw = ImageDraw.Draw(img) lines = slide_text.split("\n") total_height = sum([font.getsize(line)[1] for line in lines]) + (len(lines)-1)*10 y = (720 - total_height) // 2 for line in lines: w, h = draw.textsize(line, font=font) draw.text(((1280 - w) // 2, y), line, font=font, fill="white") y += h + 10 logo = Image.open(logo_path).convert("RGBA").resize((120, 120)) img.paste(logo, (40, 40), logo) frame_path = os.path.join(output_folder, f"slide_{i}.png") img.save(frame_path) slide_paths.append((frame_path, per_slide_time)) return slide_paths # Generate AV summary def url_to_av_summary(url, duration): content = extract_main_content(url) if not content: return "Failed to extract article content.", None summary = summary_chain.run(text=content[:3000]).replace('"','')[:300] audio_path = tempfile.NamedTemporaryFile(delete=False, suffix=".mp3").name gTTS(text=summary).save(audio_path) frame_dir = tempfile.mkdtemp() slides = create_slides(summary, duration, frame_dir) concat_file = os.path.join(frame_dir, "frames.txt") with open(concat_file, "w") as f: for path, t in slides: f.write(f"file '{path}'\n") f.write(f"duration {t}\n") concat_img = os.path.join(frame_dir, "video_input.mp4") ffmpeg.input(concat_file, format='concat', safe=0).output( concat_img, r=1, vcodec='libx264', pix_fmt='yuv420p' ).run(overwrite_output=True, quiet=True) video_path = tempfile.NamedTemporaryFile(delete=False, suffix=".mp4").name ffmpeg.input(concat_img).output( video_path, i=audio_path, vcodec='libx264', acodec='aac', pix_fmt='yuv420p', shortest=None ).run(overwrite_output=True, quiet=True) return summary, video_path iface = gr.Interface( fn=url_to_av_summary, inputs=[ gr.Textbox(label="Article URL"), gr.Radio([5, 10], label="Video Duration (sec)", value=5) ], outputs=[ gr.Textbox(label="Summary"), gr.Video(label="Generated AV Summary") ], title="🎞️ AV Summary Generator (Multislide with Logo)", description="Generates a 5/10 sec video summary from article URL with large text, logo, and slide animation." ) if __name__ == '__main__': iface.launch()