Spaces:
Sleeping
Sleeping
import gradio as gr | |
from langchain.chains import LLMChain | |
from langchain.prompts import PromptTemplate | |
from langchain.llms import HuggingFacePipeline | |
from transformers import pipeline | |
from gtts import gTTS | |
from bs4 import BeautifulSoup | |
import tempfile | |
import requests | |
import subprocess | |
import concurrent.futures | |
from PIL import Image, ImageDraw | |
# CPU-friendly summarization model | |
summary_pipe = pipeline("text2text-generation", model="google/flan-t5-base", device=-1) | |
llm = HuggingFacePipeline(pipeline=summary_pipe) | |
# Prompt for <50-word promotional summary | |
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 main 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 = soup.find_all("p") | |
content = "\n".join(p.get_text() for p in paras if len(p.get_text())>60) | |
return content or None | |
# Create gradient background image | |
def create_background(image_path, size=(1280,720)): | |
img = Image.new("RGB", size) | |
draw = ImageDraw.Draw(img) | |
for i in range(size[1]): | |
# gradient from dark blue to black | |
r = int(10 + (i/size[1])*20) | |
g = int(20 + (i/size[1])*30) | |
b = int(50 + (i/size[1])*50) | |
draw.line([(0, i), (size[0], i)], fill=(r, g, b)) | |
img.save(image_path) | |
# Generate AV summary | |
def url_to_av_summary(url): | |
text = extract_main_content(url) | |
if not text: | |
return "Failed to extract article content.", None | |
text = text[:3000] | |
summary = summary_chain.run(text=text) | |
summary = summary.replace('"','') | |
# TTS | |
audio_path = tempfile.NamedTemporaryFile(delete=False, suffix=".mp3").name | |
gTTS(text=summary).save(audio_path) | |
# Background image | |
bg_path = tempfile.NamedTemporaryFile(delete=False, suffix=".png").name | |
create_background(bg_path) | |
# Video with animated text | |
video_path = tempfile.NamedTemporaryFile(delete=False, suffix=".mp4").name | |
cmd = [ | |
'ffmpeg', '-y', | |
'-loop', '1', '-i', bg_path, | |
'-i', audio_path, | |
'-vf', ( | |
"drawtext=fontfile=/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf:text='" + summary + | |
"':fontcolor=white:fontsize=48:box=1:[email protected]:boxborderw=5:" | |
"x=(w-text_w)/2:y=h-(t*(h+text_h)/15)" | |
), | |
'-t', '15', | |
'-c:v', 'libx264', '-c:a', 'aac', '-pix_fmt', 'yuv420p', '-shortest', video_path | |
] | |
subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) | |
return summary, video_path | |
# Timeout wrapper | |
def safe_summary(url, timeout_secs=60): | |
with concurrent.futures.ThreadPoolExecutor() as executor: | |
future = executor.submit(url_to_av_summary, url) | |
try: | |
return future.result(timeout=timeout_secs) | |
except concurrent.futures.TimeoutError: | |
return "⏱️ Processing timed out.", None | |
iface = gr.Interface( | |
fn=safe_summary, | |
inputs=gr.Textbox(label="Article URL"), | |
outputs=[gr.Textbox(label="Summary"), gr.Video(label="Video Preview")], | |
title="🎥 Promo-Style AV Summary with Gradient Background", | |
description="Generates a <15s video with animated text over a gradient background. CPU-only, HuggingFace Spaces-ready." | |
) | |
if __name__ == '__main__': | |
iface.launch() |