File size: 4,280 Bytes
7dd982b
106c061
7dd982b
 
106c061
7dd982b
 
cd6257a
6ac3507
 
7dd982b
106c061
 
7dd982b
f06c20a
7dd982b
 
 
 
 
 
 
106c061
7dd982b
f06c20a
 
106c061
2b8e4f0
eec85c6
f06c20a
6e4263d
 
d36132c
cd6257a
6e4263d
 
cd6257a
 
 
 
 
 
6e4263d
cd6257a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76e3793
6ac3507
eec85c6
 
 
106c061
eec85c6
2b8e4f0
106c061
 
2b8e4f0
cd6257a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2b8e4f0
106c061
cd6257a
 
 
 
 
76e3793
106c061
b82995c
7dd982b
eec85c6
106c061
 
 
 
 
 
 
 
6e4263d
 
7dd982b
 
106c061
6e4263d
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
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

# Convert uploaded PNG logo to local use (as SVG substitute if needed)
def get_uploaded_logo():
    from_path = "CSHARP logo.png"
    logo_path = tempfile.NamedTemporaryFile(delete=False, suffix=".png").name
    with open(from_path, 'rb') as src, open(logo_path, 'wb') as dst:
        dst.write(src.read())
    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 = get_uploaded_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 Uploaded Logo)",
    description="Generates a 5/10 sec video summary from article URL with large text, uploaded logo, and slide animation."
)

if __name__ == '__main__':
    iface.launch()