Spaces:
Sleeping
Sleeping
Create app.py
Browse files
app.py
ADDED
@@ -0,0 +1,269 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import requests
|
3 |
+
import zipfile
|
4 |
+
import gradio as gr
|
5 |
+
import shutil
|
6 |
+
from PIL import Image
|
7 |
+
import json
|
8 |
+
from datetime import date
|
9 |
+
import random
|
10 |
+
|
11 |
+
# Configuration
|
12 |
+
OUTPUT_DIR = "downloaded_images"
|
13 |
+
IMAGES_DIR = os.path.join(OUTPUT_DIR, "images")
|
14 |
+
ZIP_FILE = os.path.join(OUTPUT_DIR, "images.zip")
|
15 |
+
TRACKING_FILE = os.path.join(OUTPUT_DIR, "used_pages.json")
|
16 |
+
|
17 |
+
# Ensure output directory exists
|
18 |
+
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
19 |
+
|
20 |
+
# Constants
|
21 |
+
ITEMS_PER_PAGE = 40
|
22 |
+
DAILY_IMAGE_LIMIT = 2000 # Adjusted for free tier limits
|
23 |
+
MAX_PAGES = DAILY_IMAGE_LIMIT // ITEMS_PER_PAGE
|
24 |
+
|
25 |
+
# API Configurations
|
26 |
+
API_CONFIGS = {
|
27 |
+
"pexels": {
|
28 |
+
"base_url": "https://api.pexels.com/v1/search",
|
29 |
+
"headers": {"Authorization": "Your_Pexels_API_Key"},
|
30 |
+
"image_key": "src.medium",
|
31 |
+
"result_key": "photos"
|
32 |
+
},
|
33 |
+
"unsplash": {
|
34 |
+
"base_url": "https://api.unsplash.com/search/photos",
|
35 |
+
"headers": {"Authorization": "Client-ID Your_Unsplash_API_Key"},
|
36 |
+
"image_key": "urls.small",
|
37 |
+
"result_key": "results"
|
38 |
+
},
|
39 |
+
"pixabay": {
|
40 |
+
"base_url": "https://pixabay.com/api/?key=Your_Pixabay_API_Key&q={category}&per_page={ITEMS_PER_PAGE}&page={page}",
|
41 |
+
"headers": {},
|
42 |
+
"image_key": "webformatURL",
|
43 |
+
"result_key": "hits"
|
44 |
+
}
|
45 |
+
}
|
46 |
+
|
47 |
+
def load_used_pages():
|
48 |
+
"""Load or initialize the used pages tracking file."""
|
49 |
+
today = str(date.today())
|
50 |
+
if os.path.exists(TRACKING_FILE):
|
51 |
+
with open(TRACKING_FILE, "r") as f:
|
52 |
+
data = json.load(f)
|
53 |
+
if data.get("date") != today:
|
54 |
+
data = {"date": today, "used_pages": []}
|
55 |
+
else:
|
56 |
+
data = {"date": today, "used_pages": []}
|
57 |
+
return data
|
58 |
+
|
59 |
+
def save_used_pages(data):
|
60 |
+
"""Save the used pages tracking file."""
|
61 |
+
with open(TRACKING_FILE, "w") as f:
|
62 |
+
json.dump(data, f)
|
63 |
+
|
64 |
+
def get_available_pages(num_pages_needed, api_name):
|
65 |
+
"""Get a list of unused page numbers for the specified API."""
|
66 |
+
data = load_used_pages()
|
67 |
+
used_pages = set(data["used_pages"].get(api_name, []))
|
68 |
+
all_pages = set(range(1, MAX_PAGES + 1))
|
69 |
+
available_pages = list(all_pages - used_pages)
|
70 |
+
|
71 |
+
if len(available_pages) < num_pages_needed:
|
72 |
+
return None
|
73 |
+
|
74 |
+
selected_pages = random.sample(available_pages, num_pages_needed)
|
75 |
+
if api_name not in data["used_pages"]:
|
76 |
+
data["used_pages"][api_name] = []
|
77 |
+
data["used_pages"][api_name].extend(selected_pages)
|
78 |
+
save_used_pages(data)
|
79 |
+
return selected_pages
|
80 |
+
|
81 |
+
def fetch_image_urls(api_name, category, num_images):
|
82 |
+
"""Fetch image URLs from the specified API based on category and desired number of images."""
|
83 |
+
config = API_CONFIGS[api_name]
|
84 |
+
num_pages_needed = (num_images + ITEMS_PER_PAGE - 1) // ITEMS_PER_PAGE
|
85 |
+
pages = get_available_pages(num_pages_needed, api_name)
|
86 |
+
|
87 |
+
if not pages:
|
88 |
+
return []
|
89 |
+
|
90 |
+
image_urls = []
|
91 |
+
for page in pages:
|
92 |
+
if api_name == "pixabay":
|
93 |
+
url = config["base_url"].format(category=category.lower(), page=page, ITEMS_PER_PAGE=ITEMS_PER_PAGE)
|
94 |
+
else:
|
95 |
+
url = f"{config['base_url']}?query={category}&per_page={ITEMS_PER_PAGE}&page={page}"
|
96 |
+
try:
|
97 |
+
response = requests.get(url, headers=config["headers"])
|
98 |
+
response.raise_for_status()
|
99 |
+
data = response.json()
|
100 |
+
|
101 |
+
if config["result_key"] not in data or not data[config["result_key"]]:
|
102 |
+
break
|
103 |
+
|
104 |
+
for item in data[config["result_key"]]:
|
105 |
+
if len(image_urls) >= num_images:
|
106 |
+
break
|
107 |
+
image_url = item.get(config["image_key"])
|
108 |
+
if image_url:
|
109 |
+
image_urls.append(image_url)
|
110 |
+
except requests.exceptions.RequestException as e:
|
111 |
+
print(f"Error fetching page {page} from {api_name}: {e}")
|
112 |
+
break
|
113 |
+
|
114 |
+
return image_urls[:num_images]
|
115 |
+
|
116 |
+
def download_images(image_urls):
|
117 |
+
"""Download images from the provided URLs and save to IMAGES_DIR."""
|
118 |
+
if os.path.exists(IMAGES_DIR):
|
119 |
+
shutil.rmtree(IMAGES_DIR)
|
120 |
+
os.makedirs(IMAGES_DIR, exist_ok=True)
|
121 |
+
|
122 |
+
downloaded_count = 0
|
123 |
+
image_paths = []
|
124 |
+
for idx, url in enumerate(image_urls, 1):
|
125 |
+
try:
|
126 |
+
response = requests.get(url, stream=True)
|
127 |
+
response.raise_for_status()
|
128 |
+
image_path = os.path.join(IMAGES_DIR, f"img{idx}.jpg")
|
129 |
+
with open(image_path, "wb") as f:
|
130 |
+
for chunk in response.iter_content(chunk_size=8192):
|
131 |
+
if chunk:
|
132 |
+
f.write(chunk)
|
133 |
+
Image.open(image_path).verify()
|
134 |
+
downloaded_count += 1
|
135 |
+
image_paths.append(image_path)
|
136 |
+
print(f"Downloaded {idx}/{len(image_urls)}: {url}")
|
137 |
+
except Exception as e:
|
138 |
+
print(f"Error downloading {url}: {e}")
|
139 |
+
|
140 |
+
return downloaded_count, image_paths
|
141 |
+
|
142 |
+
def create_zip_file(selected_image_paths):
|
143 |
+
"""Create a ZIP file of the selected images."""
|
144 |
+
if os.path.exists(ZIP_FILE):
|
145 |
+
os.remove(ZIP_FILE)
|
146 |
+
with zipfile.ZipFile(ZIP_FILE, 'w', zipfile.ZIP_DEFLATED) as zipf:
|
147 |
+
for image_path in selected_image_paths:
|
148 |
+
arcname = os.path.relpath(image_path, OUTPUT_DIR)
|
149 |
+
zipf.write(image_path, arcname)
|
150 |
+
return ZIP_FILE
|
151 |
+
|
152 |
+
def process_and_display(api_name, category, num_images):
|
153 |
+
"""Fetch and download images, then prepare data for display."""
|
154 |
+
num_images = int(num_images)
|
155 |
+
if num_images > 24:
|
156 |
+
num_images = 24
|
157 |
+
|
158 |
+
image_urls = fetch_image_urls(api_name, category, num_images)
|
159 |
+
if not image_urls:
|
160 |
+
return "No unique images available today or API limit reached.", None, None, [None] * 24, [False] * 24
|
161 |
+
|
162 |
+
downloaded_count, image_paths = download_images(image_urls)
|
163 |
+
if downloaded_count == 0:
|
164 |
+
return "No images were successfully downloaded.", None, None, [None] * 24, [False] * 24
|
165 |
+
|
166 |
+
status = f"Successfully downloaded {downloaded_count}/{num_images} images from {api_name}. Select images to include in ZIP below."
|
167 |
+
image_outputs = [image_paths[i] if i < len(image_paths) else None for i in range(24)]
|
168 |
+
checkbox_outputs = [True if i < len(image_paths) else False for i in range(24)]
|
169 |
+
|
170 |
+
return status, None, image_paths, image_outputs, checkbox_outputs
|
171 |
+
|
172 |
+
def process_zip_submission(image_paths, *checkbox_states):
|
173 |
+
"""Create a ZIP file based on the selected images."""
|
174 |
+
if not image_paths:
|
175 |
+
return "No images available to process.", None
|
176 |
+
|
177 |
+
selected_image_paths = [image_paths[i] for i, state in enumerate(checkbox_states) if state]
|
178 |
+
if not selected_image_paths:
|
179 |
+
return "No images selected for ZIP.", None
|
180 |
+
|
181 |
+
zip_path = create_zip_file(selected_image_paths)
|
182 |
+
return f"ZIP file created with {len(selected_image_paths)} images at {zip_path}", zip_path
|
183 |
+
|
184 |
+
# Gradio Interface
|
185 |
+
with gr.Blocks(title="Stock Photo Downloader") as demo:
|
186 |
+
gr.Markdown("### Select Parameters to Download Stock Photos")
|
187 |
+
api_input = gr.Dropdown(
|
188 |
+
label="API Source",
|
189 |
+
choices=["pexels", "unsplash", "pixabay"],
|
190 |
+
value="pexels"
|
191 |
+
)
|
192 |
+
category_input = gr.Dropdown(
|
193 |
+
label="Category",
|
194 |
+
choices=["nature", "business", "people", "technology", "food", "travel", "animals", "fashion"],
|
195 |
+
value="nature",
|
196 |
+
allow_custom_value=True
|
197 |
+
)
|
198 |
+
num_images_input = gr.Dropdown(
|
199 |
+
label="Number of Images (Max 24)",
|
200 |
+
choices=["4", "8", "12", "16", "20", "24"],
|
201 |
+
value="4"
|
202 |
+
)
|
203 |
+
download_button = gr.Button("Fetch and Display Images")
|
204 |
+
|
205 |
+
gr.Markdown("### Download Status")
|
206 |
+
status_output = gr.Textbox(label="Status", interactive=False)
|
207 |
+
|
208 |
+
gr.Markdown("### Download Your Images")
|
209 |
+
zip_output = gr.File(label="Download ZIP", visible=False)
|
210 |
+
|
211 |
+
gr.Markdown("### Image Gallery (Click Thumbnails to View Full Size)")
|
212 |
+
image_paths_state = gr.State()
|
213 |
+
|
214 |
+
IMAGES_PER_ROW = 4
|
215 |
+
MAX_ROWS = 6
|
216 |
+
TOTAL_IMAGES = IMAGES_PER_ROW * MAX_ROWS
|
217 |
+
|
218 |
+
image_outputs = []
|
219 |
+
checkbox_outputs = []
|
220 |
+
|
221 |
+
for row in range(MAX_ROWS):
|
222 |
+
with gr.Row():
|
223 |
+
for col in range(IMAGES_PER_ROW):
|
224 |
+
idx = row * IMAGES_PER_ROW + col
|
225 |
+
with gr.Column(min_width=150):
|
226 |
+
image_output = gr.Image(
|
227 |
+
label=f"Image {idx+1}",
|
228 |
+
visible=False,
|
229 |
+
height=150,
|
230 |
+
width=150
|
231 |
+
)
|
232 |
+
checkbox_output = gr.Checkbox(
|
233 |
+
label=f"Include in ZIP",
|
234 |
+
value=True,
|
235 |
+
visible=False
|
236 |
+
)
|
237 |
+
image_outputs.append(image_output)
|
238 |
+
checkbox_outputs.append(checkbox_output)
|
239 |
+
|
240 |
+
gr.Markdown("### Submit Selections")
|
241 |
+
submit_button = gr.Button("Create ZIP of Selected Images")
|
242 |
+
|
243 |
+
def on_download(api_name, category, num_images):
|
244 |
+
status, zip_path, image_paths, image_outs, checkbox_outs = process_and_display(api_name, category, num_images)
|
245 |
+
return (
|
246 |
+
status,
|
247 |
+
zip_path,
|
248 |
+
image_paths,
|
249 |
+
*[gr.Image(value=img, visible=img is not None, label=f"Image {i+1}", height=150, width=150) if img else gr.Image(value=None, visible=False) for i, img in enumerate(image_outs)],
|
250 |
+
*[gr.Checkbox(value=chk, visible=chk, label=f"Include in ZIP") if chk else gr.Checkbox(value=False, visible=False) for chk in checkbox_outs]
|
251 |
+
)
|
252 |
+
|
253 |
+
def on_submit(image_paths, *checkbox_states):
|
254 |
+
status, zip_path = process_zip_submission(image_paths, *checkbox_states)
|
255 |
+
return status, gr.File(value=zip_path, visible=True) if zip_path else gr.File(visible=False)
|
256 |
+
|
257 |
+
download_button.click(
|
258 |
+
fn=on_download,
|
259 |
+
inputs=[api_input, category_input, num_images_input],
|
260 |
+
outputs=[status_output, zip_output, image_paths_state] + image_outputs + checkbox_outputs
|
261 |
+
)
|
262 |
+
|
263 |
+
submit_button.click(
|
264 |
+
fn=on_submit,
|
265 |
+
inputs=[image_paths_state] + checkbox_outputs,
|
266 |
+
outputs=[status_output, zip_output]
|
267 |
+
)
|
268 |
+
|
269 |
+
demo.launch()
|