update image flickr prompt

This commit is contained in:
2025-05-01 17:06:51 +10:00
parent bdf09a3355
commit 163e50c2b3
+186 -209
View File
@@ -62,10 +62,16 @@ def save_json_file(filename, key, value):
# Remove duplicates by title # Remove duplicates by title
data = [item for item in data if item["title"] != key] data = [item for item in data if item["title"] != key]
data.append(entry) data.append(entry)
with open(filename, 'w') as f: # Special handling for used_images.json to save as a flat list
for item in data: if filename.endswith('used_images.json'):
json.dump(item, f) flat_data = [item["title"] for item in data if isinstance(item, dict) and "title" in item]
f.write('\n') with open(filename, 'w') as f:
json.dump(flat_data, f)
else:
with open(filename, 'w') as f:
for item in data:
json.dump(item, f)
f.write('\n')
logging.info(f"Saved '{key}' to {filename}") logging.info(f"Saved '{key}' to {filename}")
print(f"DEBUG: Saved '{key}' to {filename}") print(f"DEBUG: Saved '{key}' to {filename}")
loaded_data = load_json_file(filename, expiration_days=PRUNE_INTERVAL_DAYS) loaded_data = load_json_file(filename, expiration_days=PRUNE_INTERVAL_DAYS)
@@ -227,98 +233,165 @@ def get_image(search_query):
last_flickr_request_time = time.time() last_flickr_request_time = time.time()
try: headers = {'User-Agent': 'InsiderFoodieBot/1.0 (https://insiderfoodie.com; contact@insiderfoodie.com)'}
# Try Flickr API first
photos = flickr_api.Photo.search( # Helper function to search Flickr with a given query
text=search_query, def search_flickr(query, per_page=20):
per_page=10, try:
sort='relevance', photos = flickr_api.Photo.search(
safe_search=1, text=query,
media='photos', per_page=per_page,
license='4,5,9,10' # Commercial use licenses sort='relevance',
safe_search=1,
media='photos',
license='4,5,9,10'
)
return photos
except Exception as e:
logging.warning(f"Flickr API error for query '{query}': {e}")
return []
# Helper function to process a photo
def process_photo(photo):
tags = [tag.text.lower() for tag in photo.getTags()]
title = photo.title.lower() if photo.title else ""
matched_keywords = [kw for kw in exclude_keywords if kw in tags or kw in title]
if matched_keywords:
logging.info(f"Skipping image with unwanted keywords: {photo.id} (tags: {tags}, title: {title}, matched: {matched_keywords})")
return None
img_url = photo.getPhotoFile(size_label='Medium')
if not img_url or img_url in used_images:
return None
temp_file = None
try:
img_response = requests.get(img_url, headers=headers, timeout=10)
img_response.raise_for_status()
with tempfile.NamedTemporaryFile(delete=False, suffix='.jpg') as temp_file:
temp_file.write(img_response.content)
temp_path = temp_file.name
img = Image.open(temp_path)
text = pytesseract.image_to_string(img)
char_count = len(text.strip())
logging.info(f"OCR processed {img_url}: {char_count} characters detected")
if char_count > 200:
logging.info(f"Skipping text-heavy image (OCR): {img_url} (char_count: {char_count})")
return None
uploader = photo.owner.username
page_url = f"https://www.flickr.com/photos/{photo.owner.nsid}/{photo.id}"
used_images.add(img_url)
save_used_images()
flickr_data = {
"title": search_query,
"image_url": img_url,
"source": "Flickr",
"uploader": uploader,
"page_url": page_url,
"timestamp": datetime.now(timezone.utc).isoformat(),
"ocr_chars": char_count
}
flickr_file = "/home/shane/foodie_automator/flickr_images.json"
with open(flickr_file, 'a') as f:
json.dump(flickr_data, f)
f.write('\n')
logging.info(f"Saved Flickr image to {flickr_file}: {img_url}")
logging.info(f"Fallback Flickr image: {img_url} by {uploader} for query '{search_query}' (tags: {tags})")
return img_url, "Flickr", uploader, page_url
except requests.exceptions.HTTPError as e:
if e.response.status_code == 429:
logging.warning(f"Rate limit hit for {img_url}. Falling back to Pixabay.")
return None
else:
logging.warning(f"Download failed for {img_url}: {e}")
return None
except Exception as e:
logging.warning(f"OCR processing failed for {img_url}: {e}")
return None
finally:
if temp_file and os.path.exists(temp_path):
os.unlink(temp_path)
# Helper function to classify keywords as specific or generic
def classify_keywords(keywords):
prompt = (
"Given the following keywords from an image search query, classify each as 'specific' (e.g., brand names, unique entities) or 'generic' (e.g., common or abstract terms). "
"Return a JSON object mapping each keyword to its classification.\n\n"
"Keywords: " + ", ".join(keywords) + "\n\n"
"Example output:\n"
"```json\n"
"{\n"
" \"Wingstop\": \"specific\",\n"
" \"Smart\": \"generic\",\n"
" \"Kitchen\": \"generic\"\n"
"}\n```"
) )
try:
response = client.chat.completions.create(
model=LIGHT_TASK_MODEL,
messages=[
{"role": "system", "content": "You are a helpful assistant that classifies keywords."},
{"role": "user", "content": prompt}
],
max_tokens=100,
temperature=0.5
)
raw_response = response.choices[0].message.content
json_match = re.search(r'```json\n([\s\S]*?)\n```', raw_response)
if not json_match:
logging.warning(f"Failed to parse keyword classification JSON: {raw_response}")
return {kw: "specific" for kw in keywords}
headers = {'User-Agent': 'InsiderFoodieBot/1.0 (https://insiderfoodie.com; contact@insiderfoodie.com)'} classifications = json.loads(json_match.group(1))
return classifications
except Exception as e:
logging.warning(f"Keyword classification failed: {e}. Defaulting to all specific.")
return {kw: "specific" for kw in keywords}
for photo in photos: # Step 1: Try the original search query on Flickr
# Fetch photo metadata (tags and title) logging.info(f"Searching Flickr with original query: '{search_query}'")
tags = [tag.text.lower() for tag in photo.getTags()] photos = search_flickr(search_query)
title = photo.title.lower() if photo.title else "" for photo in photos:
result = process_photo(photo)
if result:
return result
# Filter out images with unwanted keywords in tags or title # Step 2: Break down the query into keywords and classify them
matched_keywords = [kw for kw in exclude_keywords if kw in tags or kw in title] keywords = search_query.lower().split()
if matched_keywords: if len(keywords) > 1:
logging.info(f"Skipping image with unwanted keywords: {photo.id} (tags: {tags}, title: {title}, matched: {matched_keywords})") classifications = classify_keywords(keywords)
continue logging.info(f"Keyword classifications: {classifications}")
img_url = photo.getPhotoFile(size_label='Medium') # Prioritize specific keywords
if not img_url: specific_keywords = [kw for kw, classification in classifications.items() if classification == "specific"]
continue if specific_keywords:
if img_url in used_images: for keyword in specific_keywords:
continue logging.info(f"Searching Flickr with specific keyword: '{keyword}'")
photos = search_flickr(keyword)
for photo in photos:
result = process_photo(photo)
if result:
return result
# Download the image and run OCR to check for excessive text # Step 3: Final fallback to a generic food-related query
temp_file = None # Use a simple generic query derived from context (e.g., "food dining")
try: fallback_query = "food dining" # This could be further contextualized if needed
img_response = requests.get(img_url, headers=headers, timeout=10) logging.info(f"No results found. Falling back to generic query: '{fallback_query}'")
img_response.raise_for_status() photos = search_flickr(fallback_query)
with tempfile.NamedTemporaryFile(delete=False, suffix='.jpg') as temp_file: for photo in photos:
temp_file.write(img_response.content) result = process_photo(photo)
temp_path = temp_file.name if result:
return result
img = Image.open(temp_path) logging.warning(f"No valid Flickr image found in fallback for query '{search_query}'. Trying Pixabay.")
text = pytesseract.image_to_string(img)
char_count = len(text.strip())
logging.info(f"OCR processed {img_url}: {char_count} characters detected")
if char_count > 200:
logging.info(f"Skipping text-heavy image (OCR): {img_url} (char_count: {char_count})")
continue
uploader = photo.owner.username
page_url = f"https://www.flickr.com/photos/{photo.owner.nsid}/{photo.id}"
# Add the image URL to used_images
used_images.add(img_url)
save_used_images()
# Save Flickr image metadata
flickr_data = {
"title": search_query,
"image_url": img_url,
"source": "Flickr",
"uploader": uploader,
"page_url": page_url,
"timestamp": datetime.now(timezone.utc).isoformat(),
"ocr_chars": char_count
}
flickr_file = "/home/shane/foodie_automator/flickr_images.json"
with open(flickr_file, 'a') as f:
json.dump(flickr_data, f)
f.write('\n')
logging.info(f"Saved Flickr image to {flickr_file}: {img_url}")
logging.info(f"Fallback Flickr image: {img_url} by {uploader} for query '{search_query}' (tags: {tags})")
return img_url, "Flickr", uploader, page_url
except requests.exceptions.HTTPError as e:
if e.response.status_code == 429:
logging.warning(f"Rate limit hit for {img_url}. Falling back to Pixabay.")
return None, None, None, None
else:
logging.warning(f"Download failed for {img_url}: {e}")
continue
except Exception as e:
logging.warning(f"OCR processing failed for {img_url}: {e}")
continue
finally:
if temp_file and os.path.exists(temp_path):
os.unlink(temp_path)
logging.warning(f"No valid Flickr image found in fallback for query '{search_query}'. Trying Pixabay.")
except Exception as e:
logging.warning(f"Fallback Flickr API error for query '{search_query}': {e}. Falling back to Pixabay.")
# Fallback to Pixabay # Fallback to Pixabay
try: try:
@@ -334,7 +407,6 @@ def get_image(search_query):
uploader = hit.get('user', 'Unknown') uploader = hit.get('user', 'Unknown')
page_url = hit.get('pageURL', img_url) page_url = hit.get('pageURL', img_url)
# Add the image URL to used_images
used_images.add(img_url) used_images.add(img_url)
save_used_images() save_used_images()
@@ -350,14 +422,18 @@ def get_image(search_query):
def generate_image_query(content): def generate_image_query(content):
prompt = ( prompt = (
"Given the following content, generate a concise image search query (max 5 words) that would likely yield relevant, visually appealing images on platforms like Flickr or Pixabay. Focus on concrete, visual concepts related to food, dining, or restaurants, avoiding overly abstract terms. Also provide relevance keywords (max 5 words) to filter results. Return the result as a JSON object with 'search' and 'relevance' keys.\n\n" "Given the following content, generate a concise image search query (max 5 words) that would likely yield relevant, visually appealing images on platforms like Flickr or Pixabay. "
"Identify and prioritize specific entities like brand names or unique terms over abstract or generic concepts. "
"Focus on concrete, visual concepts related to food, dining, or restaurants. "
"Also provide relevance keywords (max 5 words) to filter results, using general themes related to the content. "
"Return the result as a JSON object with 'search' and 'relevance' keys.\n\n"
"Content:\n" "Content:\n"
f"{content}\n\n" f"{content}\n\n"
"Example output:\n" "Example output:\n"
"```json\n" "```json\n"
"{\n" "{\n"
" \"search\": \"modern dining trends\",\n" " \"search\": \"Wingstop dining\",\n"
" \"relevance\": \"dining habits restaurant trends\"\n" " \"relevance\": \"fast food dining\"\n"
"}\n```" "}\n```"
) )
@@ -379,18 +455,18 @@ def generate_image_query(content):
json_match = re.search(r'```json\n([\s\S]*?)\n```', raw_response) json_match = re.search(r'```json\n([\s\S]*?)\n```', raw_response)
if not json_match: if not json_match:
logging.warning(f"Failed to parse image query JSON from GPT response: {raw_response}") logging.warning(f"Failed to parse image query JSON from GPT response: {raw_response}")
return "restaurant dining", "dining trends" return "food dining", ["dining", "trends"]
query_data = json.loads(json_match.group(1)) query_data = json.loads(json_match.group(1))
search_query = query_data.get("search", "restaurant dining") search_query = query_data.get("search", "food dining")
relevance_keywords = query_data.get("relevance", "dining trends") relevance_keywords = query_data.get("relevance", ["dining", "trends"])
logging.debug(f"Image query from content: {query_data}") logging.debug(f"Image query from content: {query_data}")
return search_query, relevance_keywords return search_query, relevance_keywords
except Exception as e: except Exception as e:
logging.warning(f"Failed to generate image query: {e}. Using fallback.") logging.warning(f"Failed to generate image query: {e}. Using fallback.")
return "restaurant dining", "dining trends" return "food dining", ["dining", "trends"]
def smart_image_and_filter(title, summary): def smart_image_and_filter(title, summary):
try: try:
@@ -893,7 +969,18 @@ if os.path.exists(used_images_file):
try: try:
with open(used_images_file, 'r') as f: with open(used_images_file, 'r') as f:
data = json.load(f) data = json.load(f)
used_images.update(data) # Handle malformed format (list of lists)
if isinstance(data, list) and data and isinstance(data[0], list):
logging.warning(f"Fixing malformed used_images.json format: {data[:2]}...")
flat_data = []
for item in data:
if isinstance(item, list):
flat_data.extend(item)
else:
flat_data.append(item)
used_images.update(flat_data)
else:
used_images.update(data)
logging.info(f"Loaded {len(used_images)} used image URLs from {used_images_file}") logging.info(f"Loaded {len(used_images)} used image URLs from {used_images_file}")
except Exception as e: except Exception as e:
logging.warning(f"Failed to load used images from {used_images_file}: {e}") logging.warning(f"Failed to load used images from {used_images_file}: {e}")
@@ -913,117 +1000,7 @@ def reset_flickr_request_count():
flickr_request_count = 0 flickr_request_count = 0
flickr_request_start_time = time.time() flickr_request_start_time = time.time()
def get_flickr_image(search_query, relevance_keywords): if keyword in ['smart', 'ai', 'ai-powered', 'kitchen', 'dining', 'experience']:
global last_flickr_request_time, flickr_request_count
reset_flickr_request_count()
flickr_request_count += 1
logging.info(f"Flickr request count: {flickr_request_count}/3600")
# Enforce a minimum delay of 1 second between Flickr requests
current_time = time.time()
time_since_last_request = current_time - last_flickr_request_time
if time_since_last_request < 1:
time.sleep(1 - time_since_last_request)
last_flickr_request_time = time.time()
try:
# Search for photos on Flickr using the API
photos = flickr_api.Photo.search(
text=search_query,
per_page=10,
sort='relevance',
safe_search=1,
media='photos',
license='4,5,9,10' # Commercial use licenses (CC BY, CC BY-SA, etc.)
)
headers = {'User-Agent': 'InsiderFoodieBot/1.0 (https://insiderfoodie.com; contact@insiderfoodie.com)'}
for photo in photos:
# Fetch photo metadata (tags and title)
tags = [tag.text.lower() for tag in photo.getTags()]
title = photo.title.lower() if photo.title else ""
# Filter out images with unwanted keywords in tags or title
matched_keywords = [kw for kw in exclude_keywords if kw in tags or kw in title]
if matched_keywords:
logging.info(f"Skipping image with unwanted keywords: {photo.id} (tags: {tags}, title: {title}, matched: {matched_keywords})")
continue
img_url = photo.getPhotoFile(size_label='Large')
if not img_url:
img_url = photo.getPhotoFile(size_label='Medium')
if not img_url:
continue
if img_url in used_images:
continue
# Download the image and run OCR to check for excessive text
temp_file = None
try:
img_response = requests.get(img_url, headers=headers, timeout=10)
img_response.raise_for_status()
with tempfile.NamedTemporaryFile(delete=False, suffix='.jpg') as temp_file:
temp_file.write(img_response.content)
temp_path = temp_file.name
img = Image.open(temp_path)
text = pytesseract.image_to_string(img)
char_count = len(text.strip())
logging.info(f"OCR processed {img_url}: {char_count} characters detected")
if char_count > 200:
logging.info(f"Skipping text-heavy image (OCR): {img_url} (char_count: {char_count})")
continue
uploader = photo.owner.username
page_url = f"https://www.flickr.com/photos/{photo.owner.nsid}/{photo.id}"
# Add the image URL to used_images
used_images.add(img_url)
save_used_images()
# Save Flickr image metadata
flickr_data = {
"title": search_query,
"image_url": img_url,
"source": "Flickr",
"uploader": uploader,
"page_url": page_url,
"timestamp": datetime.now(timezone.utc).isoformat(),
"ocr_chars": char_count
}
flickr_file = "/home/shane/foodie_automator/flickr_images.json"
with open(flickr_file, 'a') as f:
json.dump(flickr_data, f)
f.write('\n')
logging.info(f"Saved Flickr image to {flickr_file}: {img_url}")
logging.info(f"Fetched Flickr image: {img_url} by {uploader} for query '{search_query}' (tags: {tags})")
return img_url, "Flickr", uploader, page_url
except requests.exceptions.HTTPError as e:
if e.response.status_code == 429:
logging.warning(f"Rate limit hit for {img_url}. Falling back to Pixabay.")
return None, None, None, None
else:
logging.warning(f"Download failed for {img_url}: {e}")
continue
except Exception as e:
logging.warning(f"OCR processing failed for {img_url}: {e}")
continue
finally:
if temp_file and os.path.exists(temp_path):
os.unlink(temp_path)
logging.warning(f"No valid Flickr image found for query '{search_query}'.")
return None, None, None, None
except Exception as e:
logging.warning(f"Flickr API error for query '{search_query}': {e}. Falling back to Pixabay.")
return None, None, None, None
def select_best_author(summary): def select_best_author(summary):
try: try: