Spaces:
Running
Running
add slides lib
Browse files- SlidesLib/README.md +46 -0
- SlidesLib/__init__.py +191 -0
- SlidesLib/image_gen.py +28 -0
- SlidesLib/llm.py +34 -0
- SlidesLib/plotting.py +56 -0
- SlidesLib/ppt_gen.py +173 -0
- SlidesLib/search.py +149 -0
- SlidesLib/vqa.py +44 -0
- requirements.txt +1 -2
SlidesLib/README.md
ADDED
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# SlidesLib
|
2 |
+
|
3 |
+
SlidesLib is a Python library for slide generation, providing APIs for image generation, Google search, and slide customization.
|
4 |
+
|
5 |
+
## Features
|
6 |
+
- **Image Generation**: Create images using the DALL-E API.
|
7 |
+
- **Search Integration**: Perform Google searches, save screenshots, and retrieve images.
|
8 |
+
- **Slide Customization**: Add text, bullet points, images, and set slide backgrounds.
|
9 |
+
|
10 |
+
## Installation
|
11 |
+
1. **Dependencies**: Install required Python libraries:
|
12 |
+
```bash
|
13 |
+
pip install -r requirements.txt
|
14 |
+
```
|
15 |
+
2. **Google Chrome**: Required for search functionality:
|
16 |
+
```bash
|
17 |
+
wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
|
18 |
+
sudo dpkg -i google-chrome-stable_current_amd64.deb
|
19 |
+
sudo apt-get install -f
|
20 |
+
```
|
21 |
+
3. **OpenAI API Key**: Export your API key:
|
22 |
+
```bash
|
23 |
+
export OPENAI_API_KEY="your_api_key"
|
24 |
+
```
|
25 |
+
|
26 |
+
## Quick Start
|
27 |
+
- **Image Generation**:
|
28 |
+
```python
|
29 |
+
from slidesLib.image_gen import Dalle3
|
30 |
+
Dalle3.generate_image("A futuristic cityscape", save_path="cityscape.png")
|
31 |
+
```
|
32 |
+
|
33 |
+
- **Search Integration**:
|
34 |
+
```python
|
35 |
+
from slidesLib.search import GoogleSearch
|
36 |
+
GoogleSearch.search_result("Tallest building in the world", "result.png")
|
37 |
+
```
|
38 |
+
|
39 |
+
- **Slide Customization**:
|
40 |
+
```python
|
41 |
+
from slidesLib.ppt_gen import add_title
|
42 |
+
add_title(slide, text="Welcome to SlidesLib")
|
43 |
+
```
|
44 |
+
|
45 |
+
For more examples, refer to the code in this folder.
|
46 |
+
```
|
SlidesLib/__init__.py
ADDED
@@ -0,0 +1,191 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from .search import GoogleSearch
|
2 |
+
from .vqa import VQA
|
3 |
+
from .image_gen import Dalle3
|
4 |
+
from .llm import LLM
|
5 |
+
from .ppt_gen import SlideAgent
|
6 |
+
from pptx.util import Inches, Pt
|
7 |
+
from pptx.dml.color import RGBColor
|
8 |
+
from pptx.enum.text import MSO_AUTO_SIZE
|
9 |
+
from mysearchlib import LLM
|
10 |
+
from pptx.dml.color import RGBColor
|
11 |
+
def search_result(question: str, screenshot_path: str = "screenshot.png") -> str:
|
12 |
+
"""
|
13 |
+
Search a question on Google, and take a screenshot of the search result.
|
14 |
+
Save the screenshot to screenshot_path, and return the path.
|
15 |
+
"""
|
16 |
+
return GoogleSearch.search_result(question, screenshot_path)
|
17 |
+
|
18 |
+
def search_image(query: str, save_path: str = 'top_images') -> str:
|
19 |
+
"""
|
20 |
+
Search for an image on Google and download the result to download_path.
|
21 |
+
Return download_path.
|
22 |
+
"""
|
23 |
+
return GoogleSearch.search_image(query, save_path)
|
24 |
+
|
25 |
+
def get_answer(question: str) -> str:
|
26 |
+
"""
|
27 |
+
Calls the LLM by inputing a question,
|
28 |
+
then get the response of the LLM as the answer
|
29 |
+
"""
|
30 |
+
return LLM.get_answer(question)
|
31 |
+
|
32 |
+
def get_code(request:str, examples:str = "") -> str:
|
33 |
+
"""
|
34 |
+
Calls the LLM to generate code for a request.
|
35 |
+
request: the task that the model should conduct
|
36 |
+
examples: few-shot code examples for the request
|
37 |
+
"""
|
38 |
+
return LLM.get_answer(request, examples)
|
39 |
+
|
40 |
+
def generate_image(query: str, save_path: str = "downloaded_image.png") -> str:
|
41 |
+
"""
|
42 |
+
Generate an image based on a text query, save the image to the save_path
|
43 |
+
Return the path of the saved image.
|
44 |
+
"""
|
45 |
+
return Dalle3.generate_image(query, save_path)
|
46 |
+
|
47 |
+
|
48 |
+
def add_title(
|
49 |
+
slide, text: str, font_size: int = 44,
|
50 |
+
font_color: tuple[int, int, int] = (0, 0, 0),
|
51 |
+
background_color: tuple[int, int, int] = None,
|
52 |
+
):
|
53 |
+
"""Add a title text to the slide with custom font size and font color (RGB tuple).
|
54 |
+
Args:
|
55 |
+
slide: Slide object as in pptx library
|
56 |
+
text: str, Title text to be added
|
57 |
+
font_size: int, Font size in int (point size), e.g., 44
|
58 |
+
font_color: tuple(int,int,int), RGB color, e.g., (0, 0, 0)
|
59 |
+
background_color: Optional, tuple(int,int,int), RGB color, e.g., (255, 255, 255)
|
60 |
+
Rets:
|
61 |
+
slide: Slide object with the title added
|
62 |
+
"""
|
63 |
+
title_shape = slide.shapes.title
|
64 |
+
if title_shape is None:
|
65 |
+
# Add a new text box as the title if no placeholder is found
|
66 |
+
title_shape = slide.shapes.add_textbox(Inches(1), Inches(0.5), Inches(8), Inches(1))
|
67 |
+
title_shape.text = text
|
68 |
+
for paragraph in title_shape.text_frame.paragraphs:
|
69 |
+
paragraph.font.size = Pt(font_size)
|
70 |
+
paragraph.font.color.rgb = RGBColor(*font_color)
|
71 |
+
if background_color is not None:
|
72 |
+
title_shape.fill.solid()
|
73 |
+
title_shape.fill.fore_color.rgb = RGBColor(*background_color)
|
74 |
+
return slide
|
75 |
+
|
76 |
+
|
77 |
+
def add_text(
|
78 |
+
slide, text: str, coords: list[float],
|
79 |
+
font_size: int = 20, bold: bool = False,
|
80 |
+
color: tuple[int, int, int] = (0, 0, 0),
|
81 |
+
background_color: tuple[int, int, int] = None,
|
82 |
+
auto_size: bool = True,
|
83 |
+
):
|
84 |
+
"""Add a text box at a specified location with custom text and color settings.
|
85 |
+
Args:
|
86 |
+
slide: Slide object as in pptx library
|
87 |
+
text: str, Text to be added
|
88 |
+
coords: list(float), [left, top, width, height] in inches
|
89 |
+
font_size: int, Font size in int (point size), e.g., 20
|
90 |
+
bold: bool, True if bold-type the text, False otherwise
|
91 |
+
color: tuple(int,int,int), RGB color, e.g., (0, 0, 0)
|
92 |
+
background_color: Optional, tuple(int,int,int), RGB color, e.g., (255, 255, 255)
|
93 |
+
auto_size: bool, True if auto-size the text box, False otherwise
|
94 |
+
Rets:
|
95 |
+
slide: Slide object with the text box added
|
96 |
+
"""
|
97 |
+
# Create the text box shape
|
98 |
+
left, top, width, height = coords
|
99 |
+
text_box = slide.shapes.add_textbox(Inches(left), Inches(top), Inches(width), Inches(height))
|
100 |
+
|
101 |
+
# Set background color if provided
|
102 |
+
if background_color:
|
103 |
+
text_box.fill.solid()
|
104 |
+
text_box.fill.fore_color.rgb = RGBColor(*background_color)
|
105 |
+
else:
|
106 |
+
text_box.fill.background() # No fill if no color is specified
|
107 |
+
|
108 |
+
# Handle line breaks and adjust height
|
109 |
+
lines = text.split("\n")
|
110 |
+
adjusted_height = height * len(lines) # Adjust height based on the number of lines
|
111 |
+
text_box.height = Inches(adjusted_height)
|
112 |
+
|
113 |
+
# Set text and format it
|
114 |
+
text_frame = text_box.text_frame
|
115 |
+
text_frame.word_wrap = True
|
116 |
+
if auto_size:
|
117 |
+
text_frame.auto_size = MSO_AUTO_SIZE.SHAPE_TO_FIT_TEXT # Automatically fit the text box to the text
|
118 |
+
|
119 |
+
p = text_frame.add_paragraph()
|
120 |
+
p.text = text
|
121 |
+
p.font.size = Pt(font_size)
|
122 |
+
p.font.bold = bold
|
123 |
+
p.font.color.rgb = RGBColor(*color)
|
124 |
+
return slide
|
125 |
+
|
126 |
+
|
127 |
+
def add_bullet_points(
|
128 |
+
slide, bullet_points: list[str], coords: list[float],
|
129 |
+
font_size: int = 18, color: tuple[int, int, int] = (0, 0, 0),
|
130 |
+
background_color: tuple[int, int, int] = None,
|
131 |
+
):
|
132 |
+
"""Add a text box with bullet points.
|
133 |
+
Args:
|
134 |
+
slide: Slide object as in pptx library
|
135 |
+
bullet_points: list(str), List of texts to be added as bullet points
|
136 |
+
coords: list(float), [left, top, width, height] in inches
|
137 |
+
font_size: int, Font size in int (point size), e.g., 18
|
138 |
+
color: tuple(int,int,int), RGB color, e.g., (0, 0, 0)
|
139 |
+
background_color: Optional, tuple(int,int,int), RGB color, e.g., (255, 255, 255)
|
140 |
+
Rets:
|
141 |
+
slide: Slide object with the bullet points added
|
142 |
+
"""
|
143 |
+
left, top, width, height = coords
|
144 |
+
text_box = slide.shapes.add_textbox(Inches(left), Inches(top), Inches(width), Inches(height))
|
145 |
+
# Set background color if provided
|
146 |
+
if background_color:
|
147 |
+
text_box.fill.solid()
|
148 |
+
text_box.fill.fore_color.rgb = RGBColor(*background_color)
|
149 |
+
else:
|
150 |
+
text_box.fill.background() # No fill if no color is specified
|
151 |
+
|
152 |
+
text_frame = text_box.text_frame
|
153 |
+
text_frame.word_wrap = True
|
154 |
+
text_frame.auto_size = MSO_AUTO_SIZE.TEXT_TO_FIT_SHAPE
|
155 |
+
|
156 |
+
for point in bullet_points:
|
157 |
+
p = text_frame.add_paragraph()
|
158 |
+
p.text = point
|
159 |
+
p.font.size = Pt(font_size)
|
160 |
+
p.font.color.rgb = RGBColor(*color)
|
161 |
+
# p.level = bullet_points.index(point)
|
162 |
+
|
163 |
+
return slide
|
164 |
+
|
165 |
+
|
166 |
+
def add_image(slide, image_path: str, coords: list[float]):
|
167 |
+
"""Add an image in the provided path to the specified coords and sizes.
|
168 |
+
Args:
|
169 |
+
slide: Slide object as in pptx library
|
170 |
+
image_path: str, Path to the image file
|
171 |
+
coords: list(float), [left, top, width, height] in inches
|
172 |
+
Rets:
|
173 |
+
slide: Slide object with the image added
|
174 |
+
"""
|
175 |
+
left, top, width, height = coords
|
176 |
+
slide.shapes.add_picture(image_path, Inches(left), Inches(top), Inches(width), Inches(height))
|
177 |
+
return slide
|
178 |
+
|
179 |
+
|
180 |
+
def set_background_color(slide, color: tuple[int, int, int]):
|
181 |
+
"""Set background color for the current slide.
|
182 |
+
Args:
|
183 |
+
slide: Slide object as in pptx library
|
184 |
+
color: tuple(int, int, int), RGB color, e.g., (255, 255, 255)
|
185 |
+
Returns:
|
186 |
+
modified slide object
|
187 |
+
"""
|
188 |
+
fill = slide.background.fill
|
189 |
+
fill.solid()
|
190 |
+
fill.fore_color.rgb = RGBColor(*color) # Convert tuple to RGBColor
|
191 |
+
return slide
|
SlidesLib/image_gen.py
ADDED
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from openai import OpenAI
|
2 |
+
import requests
|
3 |
+
|
4 |
+
class Dalle3():
|
5 |
+
@classmethod
|
6 |
+
def __init_dalle__(cls):
|
7 |
+
client = OpenAI()
|
8 |
+
return client
|
9 |
+
|
10 |
+
@classmethod
|
11 |
+
def generate_image(cls, query: str, save_path: str = "downloaded_image.png"):
|
12 |
+
"""Generate an image based on a text query, save the image to the save_path"""
|
13 |
+
client = cls.__init_dalle__()
|
14 |
+
response = client.images.generate(
|
15 |
+
model="dall-e-3",
|
16 |
+
prompt=query,
|
17 |
+
size="1024x1024",
|
18 |
+
quality="standard",
|
19 |
+
n=1,
|
20 |
+
)
|
21 |
+
image_url = response.data[0].url
|
22 |
+
# Send a GET request to the URL
|
23 |
+
response = requests.get(image_url)
|
24 |
+
|
25 |
+
# Open a file in binary write mode and write the content of the response
|
26 |
+
with open(save_path, "wb") as file:
|
27 |
+
file.write(response.content)
|
28 |
+
return save_path
|
SlidesLib/llm.py
ADDED
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from openai import OpenAI
|
2 |
+
import requests
|
3 |
+
|
4 |
+
class LLM():
|
5 |
+
"""Calls the LLM"""
|
6 |
+
@classmethod
|
7 |
+
def __init_llm__(cls):
|
8 |
+
client = OpenAI()
|
9 |
+
code_prompt = "Directly Generate executable python code for the following request:\n"
|
10 |
+
return client, code_prompt
|
11 |
+
@classmethod
|
12 |
+
def get_answer(cls, question: str):
|
13 |
+
"""Calls the LLM by inputing a question,
|
14 |
+
then get the response of the LLM as the answer"""
|
15 |
+
client, code_prompt = cls.__init_llm__()
|
16 |
+
response = client.chat.completions.create(
|
17 |
+
model="gpt-4o-mini",
|
18 |
+
messages=[
|
19 |
+
{"role": "system", "content": "You are a helpful assistant."},
|
20 |
+
{"role": "user", "content": question}
|
21 |
+
]
|
22 |
+
)
|
23 |
+
return response.choices[0].message.content
|
24 |
+
|
25 |
+
@classmethod
|
26 |
+
def get_code(cls, request:str, examples:str = ""):
|
27 |
+
"""
|
28 |
+
Calls the LLM to generate code for a request.
|
29 |
+
request: the task that the model should conduct
|
30 |
+
examples: few-shot code examples for the request
|
31 |
+
"""
|
32 |
+
client, code_prompt = cls.__init_llm__()
|
33 |
+
code = cls.get_answer(code_prompt + examples + request)
|
34 |
+
return code
|
SlidesLib/plotting.py
ADDED
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Call Matplotlib Library to draw graphs (Bar/Plot...)
|
2 |
+
import matplotlib.pyplot as plt
|
3 |
+
from llm import *
|
4 |
+
class Plotting:
|
5 |
+
def bar_plot(self, data: dict, title: str, xlabel: str, ylabel: str, output_path: str = 'bar_plot.png'):
|
6 |
+
"""
|
7 |
+
Create a bar plot.
|
8 |
+
|
9 |
+
:param data: Dictionary containing data to plot (keys as labels, values as heights).
|
10 |
+
:param title: Title of the plot.
|
11 |
+
:param xlabel: Label for the X-axis.
|
12 |
+
:param ylabel: Label for the Y-axis.
|
13 |
+
:param output_path: Path to save the plot image.
|
14 |
+
"""
|
15 |
+
labels = list(data.keys())
|
16 |
+
heights = list(data.values())
|
17 |
+
|
18 |
+
plt.figure(figsize=(10, 6))
|
19 |
+
plt.bar(labels, heights, color='skyblue')
|
20 |
+
plt.title(title)
|
21 |
+
plt.xlabel(xlabel)
|
22 |
+
plt.ylabel(ylabel)
|
23 |
+
plt.tight_layout()
|
24 |
+
plt.savefig(output_path)
|
25 |
+
plt.close()
|
26 |
+
|
27 |
+
return output_path
|
28 |
+
|
29 |
+
def line_plot(self, data: dict, title: str, xlabel: str, ylabel: str, output_path: str = 'line_plot.png'):
|
30 |
+
"""
|
31 |
+
Create a line plot.
|
32 |
+
|
33 |
+
:param data: Dictionary containing data to plot (keys as x-values, values as y-values).
|
34 |
+
:param title: Title of the plot.
|
35 |
+
:param xlabel: Label for the X-axis.
|
36 |
+
:param ylabel: Label for the Y-axis.
|
37 |
+
:param output_path: Path to save the plot image.
|
38 |
+
"""
|
39 |
+
x_values = list(data.keys())
|
40 |
+
y_values = list(data.values())
|
41 |
+
|
42 |
+
plt.figure(figsize=(10, 6))
|
43 |
+
plt.plot(x_values, y_values, marker='o', color='skyblue')
|
44 |
+
plt.title(title)
|
45 |
+
plt.xlabel(xlabel)
|
46 |
+
plt.ylabel(ylabel)
|
47 |
+
plt.grid(True)
|
48 |
+
plt.tight_layout()
|
49 |
+
plt.savefig(output_path)
|
50 |
+
plt.close()
|
51 |
+
|
52 |
+
return output_path
|
53 |
+
|
54 |
+
def get_plot(self, data):
|
55 |
+
instruction = ""
|
56 |
+
|
SlidesLib/ppt_gen.py
ADDED
@@ -0,0 +1,173 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from pptx import Presentation
|
2 |
+
from pptx.util import Inches as _Inches, Pt as _Pt
|
3 |
+
from pptx.dml.color import RGBColor
|
4 |
+
from pptx.enum.text import PP_ALIGN, MSO_AUTO_SIZE
|
5 |
+
from pptx.enum.shapes import MSO_AUTO_SHAPE_TYPE, MSO_SHAPE_TYPE
|
6 |
+
from io import BytesIO
|
7 |
+
|
8 |
+
ARROW_ADD = '"""<a:tailEnd type="arrow" xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"/>"""'
|
9 |
+
|
10 |
+
class SlideAgent:
|
11 |
+
def __init__(self, slide_width=13.33, slide_height=7.5):
|
12 |
+
"""Initialize a new presentation with specified slide dimensions in inches."""
|
13 |
+
self.prs = Presentation()
|
14 |
+
self.prs.slide_width = self._inches(slide_width)
|
15 |
+
self.prs.slide_height = self._inches(slide_height)
|
16 |
+
self.slide = None
|
17 |
+
|
18 |
+
def _inches(self, val):
|
19 |
+
"""Helper method to convert to Inches."""
|
20 |
+
return _Inches(val)
|
21 |
+
|
22 |
+
def _points(self, val):
|
23 |
+
"""Helper method to convert to Points."""
|
24 |
+
return _Pt(val)
|
25 |
+
|
26 |
+
# ------- Slide APIs -------
|
27 |
+
def add_slide(self, layout=0):
|
28 |
+
"""Create a new slide with a specific layout."""
|
29 |
+
slide_layout = self.prs.slide_layouts[layout]
|
30 |
+
self.slide = self.prs.slides.add_slide(slide_layout)
|
31 |
+
|
32 |
+
# ------- Text APIs -------
|
33 |
+
def add_title(self, text, font_size=44, font_color=(0, 0, 0)):
|
34 |
+
"""Add a title to the slide with a custom font size (in points) and font color (RGB tuple)."""
|
35 |
+
title_shape = self.slide.shapes.title
|
36 |
+
title_shape.text = text
|
37 |
+
self._format_text(title_shape.text_frame, self._points(font_size), RGBColor(*font_color))
|
38 |
+
|
39 |
+
def add_text(self, text, top, left, width, height, font_size=20, bold=False, color=(0, 0, 0), background_color=None, auto_size=True):
|
40 |
+
"""Add a text box at a specified location with custom text settings and optional background color."""
|
41 |
+
# Create the text box shape
|
42 |
+
text_box = self.slide.shapes.add_textbox(self._inches(left), self._inches(top), self._inches(width), self._inches(height))
|
43 |
+
|
44 |
+
# Set background color if provided
|
45 |
+
if background_color:
|
46 |
+
text_box.fill.solid()
|
47 |
+
text_box.fill.fore_color.rgb = RGBColor(*background_color)
|
48 |
+
else:
|
49 |
+
text_box.fill.background() # No fill if no color is specified
|
50 |
+
|
51 |
+
# Handle line breaks and adjust height
|
52 |
+
lines = text.split("\n")
|
53 |
+
adjusted_height = height * len(lines) # Adjust height based on the number of lines
|
54 |
+
text_box.height = self._inches(adjusted_height)
|
55 |
+
|
56 |
+
# Set text and format it
|
57 |
+
text_frame = text_box.text_frame
|
58 |
+
text_frame.word_wrap = True
|
59 |
+
if auto_size:
|
60 |
+
text_frame.auto_size = MSO_AUTO_SIZE.SHAPE_TO_FIT_TEXT # Automatically fit the text box to the text
|
61 |
+
self._format_paragraph(text_frame, text, self._points(font_size), bold, RGBColor(*color))
|
62 |
+
|
63 |
+
def add_bullet_points(self, bullet_points, top, left, width, height, font_size=18, color=(0, 0, 0)):
|
64 |
+
"""Add a text box with bullet points."""
|
65 |
+
text_box = self.slide.shapes.add_textbox(self._inches(left), self._inches(top), self._inches(width), self._inches(height))
|
66 |
+
text_frame = text_box.text_frame
|
67 |
+
text_frame.word_wrap = True
|
68 |
+
text_frame.auto_size = MSO_AUTO_SIZE.TEXT_TO_FIT_SHAPE
|
69 |
+
|
70 |
+
for point in bullet_points:
|
71 |
+
p = text_frame.add_paragraph()
|
72 |
+
p.text = point
|
73 |
+
self._format_text(p, self._points(font_size), RGBColor(*color))
|
74 |
+
p.level = bullet_points.index(point)
|
75 |
+
|
76 |
+
# ------- Image APIs -------
|
77 |
+
def add_image(self, image_path, top, left, width, height):
|
78 |
+
"""Add an image at a specified location."""
|
79 |
+
self.slide.shapes.add_picture(image_path, self._inches(left), self._inches(top), self._inches(width), self._inches(height))
|
80 |
+
|
81 |
+
def add_image_centered(self, image_path, image_width, image_height):
|
82 |
+
"""Add an image centered on the slide."""
|
83 |
+
slide_width = self.prs.slide_width.inches
|
84 |
+
slide_height = self.prs.slide_height.inches
|
85 |
+
left = (slide_width - image_width) / 2
|
86 |
+
top = (slide_height - image_height) / 2
|
87 |
+
self.add_image(image_path, top, left, image_width, image_height)
|
88 |
+
|
89 |
+
# ------- Shape APIs -------
|
90 |
+
def add_shape(self, shape_type, top, left, width, height, fill_color=None):
|
91 |
+
"""Add a shape to the slide, supporting MSO_AUTO_SHAPE_TYPE."""
|
92 |
+
if isinstance(shape_type, str):
|
93 |
+
# Check if the shape type is a valid string, otherwise raise an error
|
94 |
+
try:
|
95 |
+
shape_type = getattr(MSO_AUTO_SHAPE_TYPE, shape_type.upper())
|
96 |
+
except AttributeError:
|
97 |
+
raise ValueError(f"Invalid shape type: {shape_type}. Must be a valid MSO_AUTO_SHAPE_TYPE.")
|
98 |
+
|
99 |
+
# Now create the shape with the validated or passed enum type
|
100 |
+
shape = self.slide.shapes.add_shape(shape_type, self._inches(left), self._inches(top), self._inches(width), self._inches(height))
|
101 |
+
|
102 |
+
if fill_color:
|
103 |
+
shape.fill.solid()
|
104 |
+
shape.fill.fore_color.rgb = RGBColor(*fill_color)
|
105 |
+
|
106 |
+
def add_straight_arrow(self, start_x, start_y, end_x, end_y):
|
107 |
+
connector = self.slide.shapes.add_connector("MSO_CONNECTOR.STRAIGHT", start_x, start_y, end_x, end_y)
|
108 |
+
|
109 |
+
|
110 |
+
def add_straight_line(self, start_x, start_y, end_x, end_y):
|
111 |
+
connector = self.slide.shapes.add_connector("MSO_CONNECTOR.STRAIGHT", start_x, start_y, end_x, end_y)
|
112 |
+
line_elem = connector.line._get_or_add_ln()
|
113 |
+
line_elem.append(parse_xml({ARROW_ADD}))
|
114 |
+
|
115 |
+
# ------- Table APIs -------
|
116 |
+
def add_table(self, rows, cols, top, left, width, height, column_widths=None):
|
117 |
+
"""Add a table to the slide."""
|
118 |
+
table = self.slide.shapes.add_table(rows, cols, left, top, width, height).table
|
119 |
+
if column_widths:
|
120 |
+
for idx, col_width in enumerate(column_widths):
|
121 |
+
table.columns[idx].width = Inches(col_width)
|
122 |
+
return table
|
123 |
+
|
124 |
+
# ------- Helper APIs -------
|
125 |
+
def set_background_color(self, color):
|
126 |
+
"""Set background color for the current slide."""
|
127 |
+
background = self.slide.background
|
128 |
+
fill = background.fill
|
129 |
+
fill.solid()
|
130 |
+
fill.fore_color.rgb = color
|
131 |
+
|
132 |
+
def duplicate_slide(self, slide_index):
|
133 |
+
"""Duplicate a slide by index."""
|
134 |
+
template_slide = self.prs.slides[slide_index]
|
135 |
+
new_slide = self.prs.slides.add_slide(template_slide.slide_layout)
|
136 |
+
for shape in template_slide.shapes:
|
137 |
+
self._copy_shape(shape, new_slide)
|
138 |
+
|
139 |
+
def save_presentation(self, file_name):
|
140 |
+
"""Save the PowerPoint presentation."""
|
141 |
+
self.prs.save(file_name)
|
142 |
+
|
143 |
+
# ------- Internal Helper Methods -------
|
144 |
+
def _format_paragraph(self, text_frame, text, font_size, bold, color):
|
145 |
+
"""Helper function to format text within a text frame."""
|
146 |
+
p = text_frame.add_paragraph()
|
147 |
+
p.text = text
|
148 |
+
p.font.size = font_size
|
149 |
+
p.font.bold = bold
|
150 |
+
p.font.color.rgb = color
|
151 |
+
|
152 |
+
def _format_text(self, text_frame, font_size, font_color):
|
153 |
+
"""Helper function to format text in a text frame."""
|
154 |
+
for paragraph in text_frame.paragraphs:
|
155 |
+
paragraph.font.size = font_size
|
156 |
+
paragraph.font.color.rgb = font_color
|
157 |
+
|
158 |
+
def _copy_shape(self, shape, slide):
|
159 |
+
"""Copy a shape from one slide to another."""
|
160 |
+
if shape.shape_type == MSO_SHAPE_TYPE.PICTURE:
|
161 |
+
image = BytesIO(shape.image.blob)
|
162 |
+
slide.shapes.add_picture(image, shape.left, shape.top, shape.width, shape.height)
|
163 |
+
elif shape.has_text_frame:
|
164 |
+
new_shape = slide.shapes.add_textbox(shape.left, shape.top, shape.width, shape.height)
|
165 |
+
new_shape.text = shape.text
|
166 |
+
self._format_text(new_shape.text_frame, shape.text_frame.paragraphs[0].font.size, shape.text_frame.paragraphs[0].font.color.rgb)
|
167 |
+
|
168 |
+
|
169 |
+
|
170 |
+
|
171 |
+
|
172 |
+
|
173 |
+
|
SlidesLib/search.py
ADDED
@@ -0,0 +1,149 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from selenium import webdriver
|
2 |
+
from selenium.webdriver.chrome.service import Service
|
3 |
+
from selenium.webdriver.chrome.options import Options
|
4 |
+
from chromedriver_py import binary_path
|
5 |
+
from selenium.webdriver.common.by import By
|
6 |
+
from selenium.webdriver.support.ui import WebDriverWait
|
7 |
+
from selenium.webdriver.support import expected_conditions as EC
|
8 |
+
import time
|
9 |
+
import requests
|
10 |
+
from selenium.webdriver.common.keys import Keys
|
11 |
+
from google_images_download import google_images_download
|
12 |
+
from bing_image_downloader import downloader
|
13 |
+
import os
|
14 |
+
import shutil
|
15 |
+
class GoogleSearch:
|
16 |
+
@classmethod
|
17 |
+
def _init_driver(cls):
|
18 |
+
chrome_options = Options()
|
19 |
+
chrome_options.add_argument("--headless")
|
20 |
+
chrome_options.add_argument("--disable-gpu")
|
21 |
+
chrome_options.add_argument("--no-sandbox")
|
22 |
+
chrome_options.add_argument("--disable-dev-shm-usage")
|
23 |
+
service = Service(binary_path)
|
24 |
+
driver = webdriver.Chrome(service=service, options=chrome_options)
|
25 |
+
wait = WebDriverWait(driver, 100)
|
26 |
+
return driver, wait
|
27 |
+
|
28 |
+
@classmethod
|
29 |
+
def search_result(cls, question: str, screenshot_path: str = "screenshot.png") -> str:
|
30 |
+
"""Search a question on Google and return a screenshot of the search result."""
|
31 |
+
driver, wait = cls._init_driver()
|
32 |
+
|
33 |
+
if not question:
|
34 |
+
raise ValueError("Please provide a question")
|
35 |
+
|
36 |
+
# Perform Google search
|
37 |
+
search_url = f"https://www.google.com/search?q={question}"
|
38 |
+
driver.get(search_url)
|
39 |
+
|
40 |
+
# Give some time for the page to load
|
41 |
+
time.sleep(3)
|
42 |
+
|
43 |
+
# Take a screenshot
|
44 |
+
driver.save_screenshot(screenshot_path)
|
45 |
+
|
46 |
+
driver.quit()
|
47 |
+
return screenshot_path
|
48 |
+
|
49 |
+
@classmethod
|
50 |
+
def search_image_org(cls, query: str, download_path: str = 'top_image.png') -> str:
|
51 |
+
"""Search for an image on Google and download the top result."""
|
52 |
+
driver, wait = cls._init_driver()
|
53 |
+
|
54 |
+
if not query:
|
55 |
+
raise ValueError("Please provide a query")
|
56 |
+
|
57 |
+
# Perform Google image search
|
58 |
+
search_url = f"https://www.google.com/search?tbm=isch&q={query}"
|
59 |
+
driver.get(search_url)
|
60 |
+
|
61 |
+
# Find all image elements
|
62 |
+
image_elements = driver.find_elements(By.CSS_SELECTOR, "img")
|
63 |
+
|
64 |
+
# Filter out Google icon images and get the first valid image URL
|
65 |
+
image_url = None
|
66 |
+
for img in image_elements:
|
67 |
+
src = img.get_attribute("src")
|
68 |
+
if src and "googlelogo" not in src:
|
69 |
+
image_url = src
|
70 |
+
try:
|
71 |
+
response = requests.get(image_url)
|
72 |
+
with open(download_path, 'wb') as file:
|
73 |
+
file.write(response.content)
|
74 |
+
|
75 |
+
driver.quit()
|
76 |
+
print(image_url)
|
77 |
+
return download_path
|
78 |
+
except Exception:
|
79 |
+
print("Error downloading image, skipping.")
|
80 |
+
continue
|
81 |
+
|
82 |
+
driver.quit()
|
83 |
+
raise Exception("No valid image found")
|
84 |
+
|
85 |
+
@classmethod
|
86 |
+
def search_image_prev(cls, query, output_dir='./downloads', limit=10):
|
87 |
+
# Download images using Bing Image Downloader
|
88 |
+
downloader.download(query, limit=limit, output_dir=output_dir, adult_filter_off=True, force_replace=False, timeout=60)
|
89 |
+
# List the files in the output directory
|
90 |
+
image_dir = os.path.join(output_dir, query)
|
91 |
+
if not os.path.exists(image_dir):
|
92 |
+
raise FileNotFoundError(f"No images found for query '{query}' in directory '{output_dir}'")
|
93 |
+
|
94 |
+
# Collect all image paths
|
95 |
+
image_paths = [os.path.join(image_dir, file) for file in os.listdir(image_dir) if file.endswith(('jpg', 'jpeg', 'png'))]
|
96 |
+
|
97 |
+
# Return the first image
|
98 |
+
return image_paths[0]
|
99 |
+
|
100 |
+
@classmethod
|
101 |
+
def search_image_prev(cls, query, output_dir='./downloads', limit=10):
|
102 |
+
# Download images using Bing Image Downloader
|
103 |
+
downloader.download(query, limit=limit, output_dir=output_dir, adult_filter_off=True, force_replace=False, timeout=60)
|
104 |
+
# List the files in the output directory
|
105 |
+
image_dir = os.path.join(output_dir, query)
|
106 |
+
if not os.path.exists(image_dir):
|
107 |
+
raise FileNotFoundError(f"No images found for query '{query}' in directory '{output_dir}'")
|
108 |
+
|
109 |
+
# Collect all image paths
|
110 |
+
image_paths = [os.path.join(image_dir, file) for file in os.listdir(image_dir) if file.endswith(('jpg', 'jpeg', 'png'))]
|
111 |
+
|
112 |
+
# Return the first image
|
113 |
+
return image_paths[0]
|
114 |
+
|
115 |
+
@classmethod
|
116 |
+
def search_image(cls, query, save_path):
|
117 |
+
"""
|
118 |
+
Search for an image based on the query and save the result to the specified path.
|
119 |
+
|
120 |
+
Args:
|
121 |
+
query (str): The query to search for.
|
122 |
+
save_path (str): The path to save the downloaded image.
|
123 |
+
|
124 |
+
Returns:
|
125 |
+
str: The path where the image was saved.
|
126 |
+
"""
|
127 |
+
# Create a temporary directory for storing downloaded images
|
128 |
+
temp_dir = "./temp_download"
|
129 |
+
os.makedirs(temp_dir, exist_ok=True)
|
130 |
+
|
131 |
+
# Download only the top image result
|
132 |
+
downloader.download(query, limit=1, output_dir=temp_dir, adult_filter_off=True, force_replace=True, timeout=60)
|
133 |
+
|
134 |
+
# Construct the expected directory and image path
|
135 |
+
image_dir = os.path.join(temp_dir, query)
|
136 |
+
image_files = [file for file in os.listdir(image_dir) if file.endswith(('jpg', 'jpeg', 'png'))]
|
137 |
+
|
138 |
+
# Check if any image files were downloaded
|
139 |
+
if not image_files:
|
140 |
+
raise FileNotFoundError(f"No images found for query '{query}'.")
|
141 |
+
|
142 |
+
# Copy the top image to the desired save path
|
143 |
+
top_image_path = os.path.join(image_dir, image_files[0])
|
144 |
+
shutil.move(top_image_path, save_path)
|
145 |
+
|
146 |
+
# Clean up temporary directory
|
147 |
+
shutil.rmtree(temp_dir)
|
148 |
+
|
149 |
+
return save_path
|
SlidesLib/vqa.py
ADDED
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from transformers import BlipProcessor, BlipForQuestionAnswering
|
2 |
+
from PIL import Image
|
3 |
+
import requests
|
4 |
+
import re
|
5 |
+
class VQA:
|
6 |
+
def __init__(self, gpu_number=0):
|
7 |
+
use_load_8bit= False
|
8 |
+
from transformers import AutoProcessor, InstructBlipForConditionalGeneration, InstructBlipProcessor
|
9 |
+
|
10 |
+
|
11 |
+
self.model = InstructBlipForConditionalGeneration.from_pretrained("Salesforce/instructblip-vicuna-7b", device_map="auto")
|
12 |
+
self.processor = InstructBlipProcessor.from_pretrained("Salesforce/instructblip-vicuna-7b")
|
13 |
+
|
14 |
+
self.model.eval()
|
15 |
+
self.qa_prompt = "Question: {} Short answer:"
|
16 |
+
self.caption_prompt = "\n<image>\na photo of"
|
17 |
+
self.max_words = 50
|
18 |
+
|
19 |
+
def pre_question(self, question):
|
20 |
+
# from LAVIS blip_processors
|
21 |
+
question = re.sub(
|
22 |
+
r"([.!\"()*#:;~])",
|
23 |
+
"",
|
24 |
+
question.lower(),
|
25 |
+
)
|
26 |
+
question = question.rstrip(" ")
|
27 |
+
|
28 |
+
# truncate question
|
29 |
+
question_words = question.split(" ")
|
30 |
+
if len(question_words) > self.max_words:
|
31 |
+
question = " ".join(question_words[: self.max_words])
|
32 |
+
|
33 |
+
return question
|
34 |
+
|
35 |
+
def qa(self, image_path, question):
|
36 |
+
image = Image.open(image_path)
|
37 |
+
question = self.pre_question(question)
|
38 |
+
inputs = self.processor(images=image, text=question, return_tensors="pt", padding="longest").to(self.model.device)
|
39 |
+
generated_ids = self.model.generate(**inputs, length_penalty=-1, num_beams=5, max_length=30, min_length=1,
|
40 |
+
do_sample=False, top_p=0.9, repetition_penalty=1.0,
|
41 |
+
num_return_sequences=1, temperature=1)
|
42 |
+
generated_text = self.processor.batch_decode(generated_ids, skip_special_tokens=True)
|
43 |
+
|
44 |
+
return generated_text[0]
|
requirements.txt
CHANGED
@@ -4,5 +4,4 @@ openai
|
|
4 |
python-pptx
|
5 |
numpy
|
6 |
colormath
|
7 |
-
scipy
|
8 |
-
-e ./SlidesAgent
|
|
|
4 |
python-pptx
|
5 |
numpy
|
6 |
colormath
|
7 |
+
scipy
|
|