""" Pixabay Video Client Fetches background videos from Pixabay API """ import logging import random import requests from typing import List, Optional logger = logging.getLogger(__name__) class PixabayClient: """Client for Pixabay API to fetch background videos""" def __init__(self, api_key: str): """ Initialize Pixabay client Args: api_key: Pixabay API key """ self.api_key = api_key self.base_url = "https://pixabay.com/api/videos/" self.joker_terms = ["nature", "abstract", "space", "ocean"] def find_video( self, search_terms: List[str], duration: float, exclude_ids: Optional[List[int]] = None, orientation: str = "portrait" ) -> Optional[dict]: """ Find a suitable video from Pixabay Args: search_terms: Keywords to search for duration: Required video duration in seconds exclude_ids: List of video IDs to exclude orientation: 'portrait' or 'landscape' Returns: Dict with 'id' and 'url' of the selected video, or None """ exclude_ids = exclude_ids or [] # Try user-provided search terms first for term in search_terms: video = self._search_and_select(term, duration, exclude_ids, orientation) if video: return video # Fall back to joker terms logger.info(f"Pixabay: No videos found for {search_terms}, using joker terms") for term in self.joker_terms: video = self._search_and_select(term, duration, exclude_ids, orientation) if video: return video return None def _search_and_select( self, query: str, min_duration: float, exclude_ids: List[int], orientation: str ) -> Optional[dict]: """Search for videos and select a suitable one""" try: logger.debug(f"Searching Pixabay for: {query} ({orientation})") response = requests.get( self.base_url, params={ "key": self.api_key, "q": query, "per_page": 30, "safesearch": "true", "video_type": "film" }, timeout=15 ) if response.status_code != 200: logger.warning(f"Pixabay API error: {response.status_code}") return None data = response.json() videos = data.get("hits", []) if not videos: logger.debug(f"Pixabay: No videos found for query: {query}") return None # Filter suitable videos suitable_videos = [] for video in videos: if video["id"] in exclude_ids: continue video_files = video.get("videos", {}) if not video_files: continue # Get large or medium quality large = video_files.get("large", {}) medium = video_files.get("medium", {}) # Prefer large, fallback to medium selected_file = large if large.get("url") else medium if not selected_file.get("url"): continue width = selected_file.get("width", 0) height = selected_file.get("height", 0) # Filter by orientation if orientation == "portrait": # Portrait: height > width (9:16) if height <= width: continue else: # Landscape: width > height (16:9) if width <= height: continue video_duration = video.get("duration", 0) suitable_videos.append({ "id": video["id"], "url": selected_file["url"], "duration": video_duration, "width": width, "height": height }) if not suitable_videos: logger.debug(f"Pixabay: No {orientation} videos found for: {query}") return None # Filter by duration if possible duration_threshold = min(min_duration * 0.5, 15) long_enough_videos = [v for v in suitable_videos if v["duration"] >= duration_threshold] if long_enough_videos: # Select FIRST (most relevant) video selected = long_enough_videos[0] logger.info(f"Pixabay: Selected video ID {selected['id']} (duration: {selected['duration']}s) for '{query}'") return selected # Fallback to first suitable video selected = suitable_videos[0] logger.info(f"Pixabay: Selected video ID {selected['id']} ({selected['duration']}s) for '{query}' (fallback)") return selected except Exception as e: logger.error(f"Pixabay search error: {e}") return None