import os import requests import logging import random from typing import Dict, Any, Optional, List from dotenv import load_dotenv # Load environment variables load_dotenv() # Set up logging logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) # Get API key from environment UNSPLASH_ACCESS_KEY = os.environ.get("UNSPLASH_ACCESS_KEY") UNSPLASH_SECRET_KEY = os.environ.get("UNSPLASH_SECRET_KEY") # Function to get the singleton instance def get_image_provider() -> 'ImageIntegration': return image_provider class ImageIntegration: """ Class to handle image retrieval for articulation practice words Uses Unsplash API to fetch relevant images """ def __init__(self): self.has_unsplash_api = bool(UNSPLASH_ACCESS_KEY) if self.has_unsplash_api: logger.info("Unsplash API key found and configured") else: logger.warning("No Unsplash API key found. Using fallback image sources.") def get_image_for_word(self, word: str) -> Dict[str, Any]: """ Get an image for a specific word Args: word: The word to find an image for Returns: Dictionary with image URL and attribution information """ if self.has_unsplash_api: try: return self._get_unsplash_image(word) except Exception as e: logger.error(f"Error getting Unsplash image for '{word}': {str(e)}") return self._get_fallback_image(word) else: return self._get_fallback_image(word) def _get_unsplash_image(self, query: str) -> Dict[str, Any]: """ Get an image from Unsplash API Args: query: Search term for the image Returns: Dictionary with image data """ try: url = "https://api.unsplash.com/search/photos" params = { "query": query, "per_page": 1, "orientation": "landscape", "content_filter": "high" # Only get high-quality images appropriate for all users } headers = { "Authorization": f"Client-ID {UNSPLASH_ACCESS_KEY}" } response = requests.get(url, params=params, headers=headers) response.raise_for_status() data = response.json() if "results" in data and len(data["results"]) > 0: result = data["results"][0] # Extract relevant image data image_data = { "url": result["urls"]["regular"], "thumb_url": result["urls"]["thumb"], "attribution": f"Photo by {result['user']['name']} on Unsplash", "attribution_url": result['user']['links']['html'], "source": "unsplash" } return image_data else: # If no results, try a more general search logger.info(f"No results for '{query}', trying more general search") return self._get_general_category_image(query) except Exception as e: logger.error(f"Error in Unsplash API call: {str(e)}") raise def _get_general_category_image(self, word: str) -> Dict[str, Any]: """Get a more general category image when specific word has no results""" # Map specific words to more general categories that are likely to have images category_mapping = { # Animals "cat": "cat", "dog": "dog", "fish": "fish", # Objects "chair": "furniture", "table": "furniture", "lamp": "lighting", # Food "apple": "fruit", "orange": "fruit", "banana": "fruit", # Default categories "default": ["object", "item", "thing", "photo"] } # Try to find a category, or use defaults category = category_mapping.get(word.lower()) if not category: # Check if the word ends with common suffixes and map accordingly if word.endswith(("ing", "tion", "ment")): category = "action" elif word.endswith(("ness", "ity", "ty")): category = "concept" else: # Use a random default category category = random.choice(category_mapping["default"]) if isinstance(category, list): category = random.choice(category) # Now try with the category try: url = "https://api.unsplash.com/search/photos" params = { "query": category, "per_page": 30, # Get more results "orientation": "landscape" } headers = { "Authorization": f"Client-ID {UNSPLASH_ACCESS_KEY}" } response = requests.get(url, params=params, headers=headers) response.raise_for_status() data = response.json() if "results" in data and len(data["results"]) > 0: # Select a random image from results result = random.choice(data["results"]) image_data = { "url": result["urls"]["regular"], "thumb_url": result["urls"]["thumb"], "attribution": f"Photo by {result['user']['name']} on Unsplash", "attribution_url": result['user']['links']['html'], "source": "unsplash" } return image_data else: return self._get_fallback_image(word) except Exception as e: logger.error(f"Error in general category search: {str(e)}") return self._get_fallback_image(word) def _get_fallback_image(self, word: str) -> Dict[str, Any]: """ Get a fallback image when API fails or is not available Args: word: The word to get an image for Returns: Dictionary with fallback image data """ # Use free placeholder services or default images image_services = [ # Format: (url_template, attribution) (f"https://source.unsplash.com/400x400/?{word}", "Image from Unsplash"), (f"https://placehold.co/400x400/random/ffffff?text={word}", "Generated placeholder image"), (f"https://dummyimage.com/400x400/3498db/ffffff&text={word}", "Generated placeholder image") ] # Select a random service service = random.choice(image_services) return { "url": service[0], "attribution": service[1], "source": "fallback" } def get_images_for_words(self, words: List[Dict[str, Any]]) -> List[Dict[str, Any]]: """ Get images for a list of words Args: words: List of word dictionaries Returns: Words with added image data """ result = [] for word_item in words: try: word = word_item["word"] image_data = self.get_image_for_word(word) # Add image data to the word item word_item["image"] = image_data result.append(word_item) except Exception as e: logger.error(f"Error getting image for word '{word_item.get('word', '')}': {str(e)}") # Add the word without an image result.append(word_item) return result # Create singleton instance image_provider = ImageIntegration()