File size: 7,966 Bytes
7fa9d90
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22e5b51
 
7fa9d90
 
 
22e5b51
 
7fa9d90
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22e5b51
 
7fa9d90
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
import requests
import logging
from typing import List, Optional
from pathlib import Path
import random

logger = logging.getLogger(__name__)


class PexelsClient:
    """Client for Pexels API to fetch background videos"""
    
    def __init__(self, api_key: str):
        """
        Initialize Pexels client
        
        Args:
            api_key: Pexels API key
        """
        self.api_key = api_key
        self.base_url = "https://api.pexels.com/videos"
        self.headers = {"Authorization": api_key}
        self.joker_terms = ["nature", "globe", "space", "ocean"]
    
    def find_video(
        self,
        search_terms: List[str],
        duration: float,
        exclude_ids: Optional[List[int]] = None,
        orientation: str = "portrait"
    ) -> dict:
        """
        Find a suitable video from Pexels
        
        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
        """
        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"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
        
        raise Exception("No suitable videos found on Pexels")
    
    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 Pexels for: {query} ({orientation})")
            
            response = requests.get(
                f"{self.base_url}/search",
                headers=self.headers,
                params={
                    "query": query,
                    "orientation": orientation,
                    "per_page": 15,
                    "size": "medium"  # Good balance of quality and file size
                },
                timeout=10
            )
            
            if response.status_code != 200:
                logger.warning(f"Pexels API error: {response.status_code}")
                return None
            
            data = response.json()
            videos = data.get("videos", [])
            
            if not videos:
                logger.debug(f"No videos found for query: {query}")
                return None
            
            # Filter suitable videos
            suitable_videos = []
            for video in videos:
                if video["id"] in exclude_ids:
                    continue
                
                # Get video file URL (HD or SD)
                video_files = video.get("video_files", [])
                if not video_files:
                    continue
                
                # Sort by quality and find a good match
                video_files = sorted(
                    video_files,
                    key=lambda x: x.get("width", 0) * x.get("height", 0),
                    reverse=True
                )
                
                # Find appropriate quality based on orientation
                target_width = 1080 if orientation == "portrait" else 1920
                target_height = 1920 if orientation == "portrait" else 1080
                
                selected_file = None
                for vf in video_files:
                    # Look for files close to our target resolution
                    if vf.get("width") and vf.get("height"):
                        if (abs(vf["width"] - target_width) < 300 and 
                            abs(vf["height"] - target_height) < 300):
                            selected_file = vf
                            break
                
                # Fallback to highest quality if no exact match
                if not selected_file and video_files:
                    selected_file = video_files[0]
                
                if selected_file and selected_file.get("link"):
                    suitable_videos.append({
                        "id": video["id"],
                        "url": selected_file["link"],
                        "duration": video.get("duration", 0)
                    })
            
            if not suitable_videos:
                return None
            
            # Filter by duration if possible
            # Try to find videos that are at least 50% of the requested duration
            # to avoid stitching too many tiny clips
            duration_threshold = min(min_duration * 0.5, 15)  # Cap at 15s requirement
            long_enough_videos = [v for v in suitable_videos if v["duration"] >= duration_threshold]
            
            if long_enough_videos:
                # Select FIRST (most relevant) video instead of random
                selected = long_enough_videos[0]
                logger.info(f"Selected Pexels video ID {selected['id']} (duration: {selected['duration']}s) for query '{query}'")
                return selected
            
            # Fallback to first suitable video
            selected = suitable_videos[0]
            logger.info(f"Selected Pexels video ID {selected['id']} (duration: {selected['duration']}s) for query '{query}' (fallback)")
            return selected
            
        except Exception as e:
            logger.error(f"Error searching Pexels: {e}")
            return None

    def find_photo(
        self,
        query: str,
        orientation: str = "portrait"
    ) -> Optional[dict]:
        """
        Find a suitable photo from Pexels
        
        Args:
            query: Search term
            orientation: 'portrait' or 'landscape'
            
        Returns:
            Dict with 'id' and 'url' of the photo
        """
        try:
            logger.debug(f"Searching Pexels for photo: {query} ({orientation})")
            
            # Pexels Photo API endpoint
            url = "https://api.pexels.com/v1/search"
            
            response = requests.get(
                url,
                headers=self.headers,
                params={
                    "query": query,
                    "orientation": orientation,
                    "per_page": 15,
                    "size": "large"
                },
                timeout=10
            )
            
            if response.status_code != 200:
                logger.warning(f"Pexels Photo API error: {response.status_code}")
                return None
            
            data = response.json()
            photos = data.get("photos", [])
            
            if not photos:
                logger.debug(f"No photos found for query: {query}")
                return None
            
            # Select FIRST (most relevant) photo instead of random
            photo = photos[0]
            
            # Get URL (prefer original or large2x)
            src = photo.get("src", {})
            url = src.get("original") or src.get("large2x") or src.get("large")
            
            if not url:
                return None
                
            logger.info(f"Selected Pexels photo ID {photo['id']} for query '{query}'")
            return {
                "id": photo["id"],
                "url": url,
                "type": "photo"
            }
            
        except Exception as e:
            logger.error(f"Error searching Pexels photos: {e}")
            return None