Cleanup: Remove video_analyser and unused downloader
Browse files- src/video_downloader.py +0 -724
- video_analyser/.gitignore +0 -25
- video_analyser/README.md +0 -264
- video_analyser/analysis_prompt.md +0 -246
- video_analyser/config.yaml +0 -39
- video_analyser/get_refresh_token.py +0 -21
- video_analyser/infloxa_video_analysis.csv +0 -0
- video_analyser/infloxa_video_analysis_with_folders.csv +0 -0
- video_analyser/main.py +0 -501
- video_analyser/modules/__init__.py +0 -19
- video_analyser/modules/ai_analyzer.py +0 -186
- video_analyser/modules/csv_writer.py +0 -183
- video_analyser/modules/drive_downloader.py +0 -245
- video_analyser/modules/git_ops.py +0 -121
- video_analyser/modules/scorer.py +0 -199
- video_analyser/modules/video_analyzer.py +0 -66
- video_analyser/progress/analyzed_videos.txt +0 -1028
- video_analyser/requirements.txt +0 -15
src/video_downloader.py
DELETED
|
@@ -1,724 +0,0 @@
|
|
| 1 |
-
"""
|
| 2 |
-
Video Downloader Utility
|
| 3 |
-
Download videos by filename from Google Drive
|
| 4 |
-
"""
|
| 5 |
-
|
| 6 |
-
import os
|
| 7 |
-
import csv
|
| 8 |
-
from pathlib import Path
|
| 9 |
-
import sys
|
| 10 |
-
import re
|
| 11 |
-
from typing import Optional, List, Dict
|
| 12 |
-
import shutil
|
| 13 |
-
from concurrent.futures import ThreadPoolExecutor, as_completed
|
| 14 |
-
import threading
|
| 15 |
-
|
| 16 |
-
# Try to import logger from utils, fallback to print
|
| 17 |
-
try:
|
| 18 |
-
from utils import logger
|
| 19 |
-
except ImportError:
|
| 20 |
-
class SimpleLogger:
|
| 21 |
-
@staticmethod
|
| 22 |
-
def info(msg): print(f"INFO: {msg}")
|
| 23 |
-
@staticmethod
|
| 24 |
-
def error(msg): print(f"ERROR: {msg}")
|
| 25 |
-
@staticmethod
|
| 26 |
-
def warning(msg): print(f"WARNING: {msg}")
|
| 27 |
-
logger = SimpleLogger()
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
class VideoDownloader:
|
| 31 |
-
def __init__(self, csv_path: Optional[str] = None, config_path: Optional[str] = None):
|
| 32 |
-
"""
|
| 33 |
-
Initialize VideoDownloader
|
| 34 |
-
|
| 35 |
-
Args:
|
| 36 |
-
csv_path: Path to CSV file containing video library
|
| 37 |
-
Defaults to infloxa_video_analysis.csv
|
| 38 |
-
config_path: Path to config.yaml for DriveDownloader
|
| 39 |
-
Defaults to video_analyser/config.yaml
|
| 40 |
-
"""
|
| 41 |
-
if csv_path is None:
|
| 42 |
-
csv_path = "video_analyser/infloxa_video_analysis.csv"
|
| 43 |
-
|
| 44 |
-
if config_path is None:
|
| 45 |
-
config_path = "video_analyser/config.yaml"
|
| 46 |
-
|
| 47 |
-
self.csv_path = csv_path
|
| 48 |
-
self.config_path = config_path
|
| 49 |
-
self.video_library = self._load_video_library()
|
| 50 |
-
self.drive_downloader = None
|
| 51 |
-
self.lock = threading.Lock() # Thread-safe lock for drive operations
|
| 52 |
-
|
| 53 |
-
def _load_video_library(self) -> List[Dict]:
|
| 54 |
-
"""Load video library from CSV file"""
|
| 55 |
-
try:
|
| 56 |
-
if not os.path.exists(self.csv_path):
|
| 57 |
-
logger.error(f"CSV file not found: {self.csv_path}")
|
| 58 |
-
return []
|
| 59 |
-
|
| 60 |
-
videos = []
|
| 61 |
-
with open(self.csv_path, 'r', encoding='utf-8') as f:
|
| 62 |
-
reader = csv.DictReader(f)
|
| 63 |
-
for row in reader:
|
| 64 |
-
videos.append(row)
|
| 65 |
-
|
| 66 |
-
logger.info(f"Loaded video library with {len(videos)} entries from {self.csv_path}")
|
| 67 |
-
return videos
|
| 68 |
-
|
| 69 |
-
except Exception as e:
|
| 70 |
-
logger.error(f"Failed to load video library: {e}")
|
| 71 |
-
raise
|
| 72 |
-
|
| 73 |
-
def _init_drive_downloader(self, download_path: str):
|
| 74 |
-
"""Initialize Google Drive downloader"""
|
| 75 |
-
if self.drive_downloader is not None:
|
| 76 |
-
return
|
| 77 |
-
|
| 78 |
-
try:
|
| 79 |
-
# Add video_analyser to path
|
| 80 |
-
video_analyser_path = os.path.join(os.path.dirname(__file__), '..', 'video_analyser')
|
| 81 |
-
if video_analyser_path not in sys.path:
|
| 82 |
-
sys.path.insert(0, video_analyser_path)
|
| 83 |
-
|
| 84 |
-
from modules import DriveDownloader
|
| 85 |
-
import yaml
|
| 86 |
-
|
| 87 |
-
if not os.path.exists(self.config_path):
|
| 88 |
-
logger.error(f"Config file not found: {self.config_path}")
|
| 89 |
-
raise FileNotFoundError(f"Config file not found: {self.config_path}")
|
| 90 |
-
|
| 91 |
-
with open(self.config_path, 'r') as f:
|
| 92 |
-
self.config = yaml.safe_load(f)
|
| 93 |
-
|
| 94 |
-
# Override the local directory with our download path
|
| 95 |
-
self.config['output']['local_video_dir'] = download_path
|
| 96 |
-
|
| 97 |
-
# Initialize and authenticate
|
| 98 |
-
logger.info("Initializing Google Drive connection...")
|
| 99 |
-
self.drive_downloader = DriveDownloader(self.config)
|
| 100 |
-
self.drive_downloader.authenticate()
|
| 101 |
-
logger.info("✓ Google Drive authenticated")
|
| 102 |
-
|
| 103 |
-
except Exception as e:
|
| 104 |
-
logger.error(f"Failed to initialize DriveDownloader: {e}")
|
| 105 |
-
raise
|
| 106 |
-
|
| 107 |
-
def _get_thread_service(self):
|
| 108 |
-
"""Get a thread-local Google Drive service instance"""
|
| 109 |
-
import threading
|
| 110 |
-
thread_id = threading.get_ident()
|
| 111 |
-
|
| 112 |
-
# Check if this thread already has a service
|
| 113 |
-
if not hasattr(self, '_thread_services'):
|
| 114 |
-
self._thread_services = {}
|
| 115 |
-
|
| 116 |
-
if thread_id not in self._thread_services:
|
| 117 |
-
# Create a new service for this thread
|
| 118 |
-
from modules import DriveDownloader
|
| 119 |
-
import yaml
|
| 120 |
-
|
| 121 |
-
with open(self.config_path, 'r') as f:
|
| 122 |
-
config = yaml.safe_load(f)
|
| 123 |
-
|
| 124 |
-
downloader = DriveDownloader(config)
|
| 125 |
-
downloader.authenticate()
|
| 126 |
-
self._thread_services[thread_id] = downloader.service
|
| 127 |
-
logger.info(f"Created new Drive service for thread {thread_id}")
|
| 128 |
-
|
| 129 |
-
return self._thread_services[thread_id]
|
| 130 |
-
|
| 131 |
-
def _extract_folder_id_from_link(self, drive_link: str) -> Optional[str]:
|
| 132 |
-
"""
|
| 133 |
-
Extract folder ID from Google Drive link
|
| 134 |
-
|
| 135 |
-
Args:
|
| 136 |
-
drive_link: Google Drive folder URL
|
| 137 |
-
|
| 138 |
-
Returns:
|
| 139 |
-
Folder ID or None if not found
|
| 140 |
-
"""
|
| 141 |
-
patterns = [
|
| 142 |
-
r'folders/([a-zA-Z0-9_-]+)',
|
| 143 |
-
r'id=([a-zA-Z0-9_-]+)',
|
| 144 |
-
]
|
| 145 |
-
|
| 146 |
-
for pattern in patterns:
|
| 147 |
-
match = re.search(pattern, drive_link)
|
| 148 |
-
if match:
|
| 149 |
-
return match.group(1)
|
| 150 |
-
|
| 151 |
-
logger.error(f"Could not extract folder ID from link: {drive_link}")
|
| 152 |
-
return None
|
| 153 |
-
|
| 154 |
-
def _list_folder_contents_recursive(self, folder_id: str, parent_path: str = "") -> List[Dict]:
|
| 155 |
-
"""
|
| 156 |
-
Recursively list all files in a folder and its subfolders
|
| 157 |
-
|
| 158 |
-
Args:
|
| 159 |
-
folder_id: Google Drive folder ID
|
| 160 |
-
parent_path: Path of parent folder for tracking structure
|
| 161 |
-
|
| 162 |
-
Returns:
|
| 163 |
-
List of dictionaries with file info including relative path
|
| 164 |
-
"""
|
| 165 |
-
try:
|
| 166 |
-
files_and_folders = []
|
| 167 |
-
|
| 168 |
-
# Query for all items in this folder
|
| 169 |
-
query = f"'{folder_id}' in parents and trashed=false"
|
| 170 |
-
results = self.drive_downloader.service.files().list(
|
| 171 |
-
q=query,
|
| 172 |
-
fields="files(id, name, mimeType, webViewLink)",
|
| 173 |
-
pageSize=1000
|
| 174 |
-
).execute()
|
| 175 |
-
|
| 176 |
-
items = results.get('files', [])
|
| 177 |
-
|
| 178 |
-
for item in items:
|
| 179 |
-
item_name = item['name']
|
| 180 |
-
item_id = item['id']
|
| 181 |
-
mime_type = item['mimeType']
|
| 182 |
-
|
| 183 |
-
# Build current path
|
| 184 |
-
current_path = os.path.join(parent_path, item_name) if parent_path else item_name
|
| 185 |
-
|
| 186 |
-
if mime_type == 'application/vnd.google-apps.folder':
|
| 187 |
-
# It's a folder - recurse into it
|
| 188 |
-
logger.info(f"Scanning folder: {current_path}")
|
| 189 |
-
subfolder_contents = self._list_folder_contents_recursive(item_id, current_path)
|
| 190 |
-
files_and_folders.extend(subfolder_contents)
|
| 191 |
-
else:
|
| 192 |
-
# It's a file
|
| 193 |
-
files_and_folders.append({
|
| 194 |
-
'id': item_id,
|
| 195 |
-
'name': item_name,
|
| 196 |
-
'path': current_path,
|
| 197 |
-
'mimeType': mime_type,
|
| 198 |
-
'webViewLink': item.get('webViewLink', '')
|
| 199 |
-
})
|
| 200 |
-
|
| 201 |
-
return files_and_folders
|
| 202 |
-
|
| 203 |
-
except Exception as e:
|
| 204 |
-
logger.error(f"Error listing folder contents: {e}")
|
| 205 |
-
return []
|
| 206 |
-
|
| 207 |
-
def _download_single_file(
|
| 208 |
-
self,
|
| 209 |
-
file_info: Dict,
|
| 210 |
-
download_root: str,
|
| 211 |
-
idx: int,
|
| 212 |
-
total: int
|
| 213 |
-
) -> Dict[str, any]:
|
| 214 |
-
"""
|
| 215 |
-
Download a single file from Google Drive (for parallel execution)
|
| 216 |
-
|
| 217 |
-
Args:
|
| 218 |
-
file_info: Dictionary with file information
|
| 219 |
-
download_root: Root directory for downloads
|
| 220 |
-
idx: Current file index
|
| 221 |
-
total: Total number of files
|
| 222 |
-
|
| 223 |
-
Returns:
|
| 224 |
-
Dictionary with download result
|
| 225 |
-
"""
|
| 226 |
-
result = {
|
| 227 |
-
'file': file_info['name'],
|
| 228 |
-
'status': 'unknown',
|
| 229 |
-
'path': None,
|
| 230 |
-
'error': None
|
| 231 |
-
}
|
| 232 |
-
|
| 233 |
-
try:
|
| 234 |
-
# Build local path preserving folder structure
|
| 235 |
-
relative_path = file_info['path']
|
| 236 |
-
local_path = os.path.join(download_root, relative_path)
|
| 237 |
-
local_dir = os.path.dirname(local_path)
|
| 238 |
-
|
| 239 |
-
# Check if file already exists
|
| 240 |
-
if os.path.exists(local_path):
|
| 241 |
-
logger.info(f"[{idx}/{total}] Skipped (exists): {relative_path}")
|
| 242 |
-
result['status'] = 'skipped'
|
| 243 |
-
result['path'] = local_path
|
| 244 |
-
return result
|
| 245 |
-
|
| 246 |
-
# Create directory structure BEFORE downloading
|
| 247 |
-
os.makedirs(local_dir, exist_ok=True)
|
| 248 |
-
logger.info(f"[{idx}/{total}] Downloading: {relative_path}")
|
| 249 |
-
|
| 250 |
-
# Get thread-local service instance
|
| 251 |
-
service = self._get_thread_service()
|
| 252 |
-
|
| 253 |
-
# Download file DIRECTLY to the final destination
|
| 254 |
-
request = service.files().get_media(fileId=file_info['id'])
|
| 255 |
-
|
| 256 |
-
with open(local_path, 'wb') as f:
|
| 257 |
-
from googleapiclient.http import MediaIoBaseDownload
|
| 258 |
-
downloader = MediaIoBaseDownload(f, request)
|
| 259 |
-
done = False
|
| 260 |
-
last_progress = 0
|
| 261 |
-
while not done:
|
| 262 |
-
status, done = downloader.next_chunk()
|
| 263 |
-
if status:
|
| 264 |
-
progress = int(status.progress() * 100)
|
| 265 |
-
# Log every 25% to avoid spam
|
| 266 |
-
if progress >= last_progress + 25:
|
| 267 |
-
logger.info(f" [{file_info['name']}] Progress: {progress}%")
|
| 268 |
-
last_progress = progress
|
| 269 |
-
|
| 270 |
-
logger.info(f"✓ Successfully downloaded: {local_path}")
|
| 271 |
-
result['status'] = 'downloaded'
|
| 272 |
-
result['path'] = local_path
|
| 273 |
-
|
| 274 |
-
except Exception as e:
|
| 275 |
-
logger.error(f"Failed to download {file_info['name']}: {e}")
|
| 276 |
-
result['status'] = 'failed'
|
| 277 |
-
result['error'] = str(e)
|
| 278 |
-
|
| 279 |
-
# Clean up partial download if it exists
|
| 280 |
-
if 'local_path' in locals() and os.path.exists(local_path):
|
| 281 |
-
try:
|
| 282 |
-
os.remove(local_path)
|
| 283 |
-
except:
|
| 284 |
-
pass
|
| 285 |
-
|
| 286 |
-
return result
|
| 287 |
-
|
| 288 |
-
def download_from_drive_link(
|
| 289 |
-
self,
|
| 290 |
-
drive_link: str,
|
| 291 |
-
download_root: str,
|
| 292 |
-
file_extensions: Optional[List[str]] = None,
|
| 293 |
-
max_workers: int = 10 # Number of parallel downloads
|
| 294 |
-
) -> Dict[str, any]:
|
| 295 |
-
"""
|
| 296 |
-
Download all files from a Google Drive folder link, preserving folder structure
|
| 297 |
-
(with parallel downloads)
|
| 298 |
-
|
| 299 |
-
Args:
|
| 300 |
-
drive_link: Google Drive folder URL
|
| 301 |
-
e.g., https://drive.google.com/drive/folders/1WSrVAyqvPJzpRnoUxkNx0LqK9VlDs432
|
| 302 |
-
download_root: Root directory where files should be downloaded
|
| 303 |
-
file_extensions: Optional list of file extensions to filter (e.g., ['.mp4', '.avi'])
|
| 304 |
-
If None, downloads all files
|
| 305 |
-
max_workers: Number of parallel downloads (default: 10)
|
| 306 |
-
|
| 307 |
-
Returns:
|
| 308 |
-
Dictionary with download statistics:
|
| 309 |
-
{
|
| 310 |
-
'total_files': int,
|
| 311 |
-
'downloaded': int,
|
| 312 |
-
'skipped': int,
|
| 313 |
-
'failed': int,
|
| 314 |
-
'files': List[str] # paths of downloaded files
|
| 315 |
-
}
|
| 316 |
-
|
| 317 |
-
Example:
|
| 318 |
-
>>> downloader = VideoDownloader()
|
| 319 |
-
>>> result = downloader.download_from_drive_link(
|
| 320 |
-
... drive_link="https://drive.google.com/drive/folders/1WSrVAyqvPJzpRnoUxkNx0LqK9VlDs432",
|
| 321 |
-
... download_root="downloads/my_videos",
|
| 322 |
-
... file_extensions=['.mp4', '.mov', '.avi'],
|
| 323 |
-
... max_workers=10
|
| 324 |
-
... )
|
| 325 |
-
>>> print(f"Downloaded {result['downloaded']} files")
|
| 326 |
-
"""
|
| 327 |
-
try:
|
| 328 |
-
# Initialize Drive downloader (pass None to avoid auto path setup)
|
| 329 |
-
self._init_drive_downloader(None)
|
| 330 |
-
|
| 331 |
-
# Extract folder ID from link
|
| 332 |
-
folder_id = self._extract_folder_id_from_link(drive_link)
|
| 333 |
-
if not folder_id:
|
| 334 |
-
return {
|
| 335 |
-
'total_files': 0,
|
| 336 |
-
'downloaded': 0,
|
| 337 |
-
'skipped': 0,
|
| 338 |
-
'failed': 0,
|
| 339 |
-
'files': []
|
| 340 |
-
}
|
| 341 |
-
|
| 342 |
-
logger.info(f"Scanning Google Drive folder: {folder_id}")
|
| 343 |
-
|
| 344 |
-
# Get all files recursively
|
| 345 |
-
all_files = self._list_folder_contents_recursive(folder_id)
|
| 346 |
-
|
| 347 |
-
# Filter by file extensions if provided
|
| 348 |
-
if file_extensions:
|
| 349 |
-
file_extensions = [ext.lower() if ext.startswith('.') else f'.{ext.lower()}'
|
| 350 |
-
for ext in file_extensions]
|
| 351 |
-
all_files = [f for f in all_files
|
| 352 |
-
if any(f['name'].lower().endswith(ext) for ext in file_extensions)]
|
| 353 |
-
|
| 354 |
-
logger.info(f"Found {len(all_files)} files to download")
|
| 355 |
-
|
| 356 |
-
# Statistics
|
| 357 |
-
stats = {
|
| 358 |
-
'total_files': len(all_files),
|
| 359 |
-
'downloaded': 0,
|
| 360 |
-
'skipped': 0,
|
| 361 |
-
'failed': 0,
|
| 362 |
-
'files': []
|
| 363 |
-
}
|
| 364 |
-
|
| 365 |
-
# Download files in parallel using ThreadPoolExecutor
|
| 366 |
-
logger.info(f"Starting parallel downloads with {max_workers} workers...")
|
| 367 |
-
|
| 368 |
-
with ThreadPoolExecutor(max_workers=max_workers) as executor:
|
| 369 |
-
# Submit all download tasks
|
| 370 |
-
future_to_file = {
|
| 371 |
-
executor.submit(
|
| 372 |
-
self._download_single_file,
|
| 373 |
-
file_info,
|
| 374 |
-
download_root,
|
| 375 |
-
idx,
|
| 376 |
-
len(all_files)
|
| 377 |
-
): file_info
|
| 378 |
-
for idx, file_info in enumerate(all_files, 1)
|
| 379 |
-
}
|
| 380 |
-
|
| 381 |
-
# Collect results as they complete
|
| 382 |
-
for future in as_completed(future_to_file):
|
| 383 |
-
file_info = future_to_file[future]
|
| 384 |
-
try:
|
| 385 |
-
result = future.result()
|
| 386 |
-
|
| 387 |
-
if result['status'] == 'downloaded':
|
| 388 |
-
stats['downloaded'] += 1
|
| 389 |
-
if result['path']:
|
| 390 |
-
stats['files'].append(result['path'])
|
| 391 |
-
elif result['status'] == 'skipped':
|
| 392 |
-
stats['skipped'] += 1
|
| 393 |
-
if result['path']:
|
| 394 |
-
stats['files'].append(result['path'])
|
| 395 |
-
elif result['status'] == 'failed':
|
| 396 |
-
stats['failed'] += 1
|
| 397 |
-
|
| 398 |
-
except Exception as e:
|
| 399 |
-
logger.error(f"Error processing {file_info['name']}: {e}")
|
| 400 |
-
stats['failed'] += 1
|
| 401 |
-
|
| 402 |
-
# Summary
|
| 403 |
-
logger.info("=" * 60)
|
| 404 |
-
logger.info("Download Summary:")
|
| 405 |
-
logger.info(f" Total files: {stats['total_files']}")
|
| 406 |
-
logger.info(f" Downloaded: {stats['downloaded']}")
|
| 407 |
-
logger.info(f" Skipped (already exist): {stats['skipped']}")
|
| 408 |
-
logger.info(f" Failed: {stats['failed']}")
|
| 409 |
-
logger.info("=" * 60)
|
| 410 |
-
|
| 411 |
-
return stats
|
| 412 |
-
|
| 413 |
-
except Exception as e:
|
| 414 |
-
logger.error(f"Error downloading from drive link: {e}")
|
| 415 |
-
import traceback
|
| 416 |
-
traceback.print_exc()
|
| 417 |
-
return {
|
| 418 |
-
'total_files': 0,
|
| 419 |
-
'downloaded': 0,
|
| 420 |
-
'skipped': 0,
|
| 421 |
-
'failed': 0,
|
| 422 |
-
'files': []
|
| 423 |
-
}
|
| 424 |
-
|
| 425 |
-
def get_folder_name(
|
| 426 |
-
self,
|
| 427 |
-
video_filename: str,
|
| 428 |
-
) -> str:
|
| 429 |
-
try:
|
| 430 |
-
# Initialize Drive downloader
|
| 431 |
-
self._init_drive_downloader(None)
|
| 432 |
-
|
| 433 |
-
# List all videos from Google Drive
|
| 434 |
-
logger.info(f"Searching for '{video_filename}' in Google Drive...")
|
| 435 |
-
all_videos = self.drive_downloader.list_all_videos()
|
| 436 |
-
|
| 437 |
-
# Find the video by filename
|
| 438 |
-
matching_video = None
|
| 439 |
-
for video_item in all_videos:
|
| 440 |
-
if video_item['name'] == video_filename:
|
| 441 |
-
matching_video = video_item
|
| 442 |
-
break
|
| 443 |
-
|
| 444 |
-
if not matching_video:
|
| 445 |
-
return None
|
| 446 |
-
else:
|
| 447 |
-
return matching_video["path"].split("/")[0]
|
| 448 |
-
except: return None
|
| 449 |
-
|
| 450 |
-
|
| 451 |
-
|
| 452 |
-
def get_video_link(self, video_filename: str) -> Optional[str]:
|
| 453 |
-
"""Fetches the Google Drive webViewLink for the file"""
|
| 454 |
-
try:
|
| 455 |
-
self._init_drive_downloader(None)
|
| 456 |
-
all_videos = self.drive_downloader.list_all_videos()
|
| 457 |
-
for video_item in all_videos:
|
| 458 |
-
if video_item['name'] == video_filename:
|
| 459 |
-
# Return the webLink if available, otherwise construct one from ID
|
| 460 |
-
return video_item.get('webViewLink') or f"https://drive.google.com/file/d/{video_item.get('id')}/view"
|
| 461 |
-
return None
|
| 462 |
-
except Exception as e:
|
| 463 |
-
logger.error(f"Error fetching link for {video_filename}: {e}")
|
| 464 |
-
return None
|
| 465 |
-
|
| 466 |
-
def download_video(
|
| 467 |
-
self,
|
| 468 |
-
video_filename: str,
|
| 469 |
-
download_path: str
|
| 470 |
-
) -> bool:
|
| 471 |
-
"""
|
| 472 |
-
Download a video from Google Drive by filename
|
| 473 |
-
|
| 474 |
-
Args:
|
| 475 |
-
video_filename: Name of the video file to search for in Google Drive
|
| 476 |
-
download_path: Directory path where video should be downloaded
|
| 477 |
-
|
| 478 |
-
Returns:
|
| 479 |
-
True if download successful, False otherwise
|
| 480 |
-
|
| 481 |
-
Example:
|
| 482 |
-
>>> downloader = VideoDownloader()
|
| 483 |
-
>>> downloader.download_video(
|
| 484 |
-
... video_filename="Copy of SnapInsta.to_AQM-w-YmCepAaXIWl7Frs10cKbdX1P-__zSTTzh7j0td9hh6ebAJWA409L_fJNcgiWctukdKl0ubkT2tNOxgjyRdOXs3nPBgMAgWV90.mp4",
|
| 485 |
-
... download_path="downloads/videos"
|
| 486 |
-
... )
|
| 487 |
-
"""
|
| 488 |
-
try:
|
| 489 |
-
save_path = os.path.join(download_path, video_filename)
|
| 490 |
-
if os.path.exists(save_path):
|
| 491 |
-
logger.info(f"File already exists: {save_path}")
|
| 492 |
-
return save_path
|
| 493 |
-
# Initialize Drive downloader
|
| 494 |
-
self._init_drive_downloader(download_path)
|
| 495 |
-
|
| 496 |
-
# List all videos from Google Drive
|
| 497 |
-
logger.info(f"Searching for '{video_filename}' in Google Drive...")
|
| 498 |
-
all_videos = self.drive_downloader.list_all_videos()
|
| 499 |
-
|
| 500 |
-
# Find the video by filename
|
| 501 |
-
matching_video = None
|
| 502 |
-
for video_item in all_videos:
|
| 503 |
-
if video_item['name'] == video_filename:
|
| 504 |
-
matching_video = video_item
|
| 505 |
-
break
|
| 506 |
-
|
| 507 |
-
if not matching_video:
|
| 508 |
-
logger.error(f"Video not found in Google Drive: {video_filename}")
|
| 509 |
-
logger.info(f"Searched {len(all_videos)} videos in Drive")
|
| 510 |
-
|
| 511 |
-
# Show similar filenames for debugging
|
| 512 |
-
similar = [v['name'] for v in all_videos if video_filename[:30] in v['name']]
|
| 513 |
-
if similar:
|
| 514 |
-
logger.info(f"Similar files found: {similar[:3]}")
|
| 515 |
-
return None
|
| 516 |
-
else:
|
| 517 |
-
logger.info(f"Found video: {matching_video}")
|
| 518 |
-
# Create download directory
|
| 519 |
-
os.makedirs(download_path, exist_ok=True)
|
| 520 |
-
|
| 521 |
-
# Download the video
|
| 522 |
-
logger.info(f"Downloading {video_filename}...")
|
| 523 |
-
local_path = self.drive_downloader.download_single_video(matching_video)
|
| 524 |
-
shutil.move(local_path, save_path)
|
| 525 |
-
|
| 526 |
-
if save_path:
|
| 527 |
-
logger.info(f"✓ Successfully downloaded to: {save_path}")
|
| 528 |
-
return save_path
|
| 529 |
-
else:
|
| 530 |
-
logger.error(f"Failed to download {video_filename}")
|
| 531 |
-
return None
|
| 532 |
-
|
| 533 |
-
except Exception as e:
|
| 534 |
-
logger.error(f"Error downloading video: {e}")
|
| 535 |
-
import traceback
|
| 536 |
-
traceback.print_exc()
|
| 537 |
-
return None
|
| 538 |
-
|
| 539 |
-
def get_videos_by_category(self, category: str) -> List[Dict]:
|
| 540 |
-
"""
|
| 541 |
-
Get all videos for a specific category
|
| 542 |
-
|
| 543 |
-
Args:
|
| 544 |
-
category: Video category to filter by
|
| 545 |
-
|
| 546 |
-
Returns:
|
| 547 |
-
List of dictionaries containing videos in that category
|
| 548 |
-
"""
|
| 549 |
-
return [v for v in self.video_library if v.get('category') == category]
|
| 550 |
-
|
| 551 |
-
def get_available_categories(self) -> List[str]:
|
| 552 |
-
"""Get list of all available categories"""
|
| 553 |
-
categories = set(v.get('category', '') for v in self.video_library)
|
| 554 |
-
return sorted([c for c in categories if c])
|
| 555 |
-
|
| 556 |
-
def search_videos(self, keyword: str) -> List[Dict]:
|
| 557 |
-
"""
|
| 558 |
-
Search videos by keyword in filename or description
|
| 559 |
-
|
| 560 |
-
Args:
|
| 561 |
-
keyword: Search term
|
| 562 |
-
|
| 563 |
-
Returns:
|
| 564 |
-
List of dictionaries containing matching videos
|
| 565 |
-
"""
|
| 566 |
-
keyword_lower = keyword.lower()
|
| 567 |
-
results = []
|
| 568 |
-
|
| 569 |
-
for video in self.video_library:
|
| 570 |
-
filename = video.get('VIDEO_FILENAME', '').lower()
|
| 571 |
-
short_desc = video.get('short_description', '').lower()
|
| 572 |
-
desc = video.get('description', '').lower()
|
| 573 |
-
|
| 574 |
-
if keyword_lower in filename or keyword_lower in short_desc or keyword_lower in desc:
|
| 575 |
-
results.append(video)
|
| 576 |
-
|
| 577 |
-
return results
|
| 578 |
-
|
| 579 |
-
|
| 580 |
-
def add_folder_name_column(input_csv, output_csv, downloader):
|
| 581 |
-
processed = set()
|
| 582 |
-
|
| 583 |
-
# Load already processed video filenames
|
| 584 |
-
if os.path.exists(output_csv):
|
| 585 |
-
with open(output_csv, newline="", encoding="utf-8") as f:
|
| 586 |
-
reader = csv.DictReader(f)
|
| 587 |
-
for row in reader:
|
| 588 |
-
processed.add(row["VIDEO_FILENAME"])
|
| 589 |
-
|
| 590 |
-
with open(input_csv, newline="", encoding="utf-8") as infile, \
|
| 591 |
-
open(output_csv, "a", newline="", encoding="utf-8") as outfile:
|
| 592 |
-
|
| 593 |
-
reader = csv.DictReader(infile)
|
| 594 |
-
writer = csv.writer(outfile)
|
| 595 |
-
|
| 596 |
-
# Write header only if file is new
|
| 597 |
-
if outfile.tell() == 0:
|
| 598 |
-
writer.writerow(["folder_name"] + reader.fieldnames)
|
| 599 |
-
|
| 600 |
-
for row in reader:
|
| 601 |
-
video_filename = row["VIDEO_FILENAME"]
|
| 602 |
-
|
| 603 |
-
# Skip if already processed
|
| 604 |
-
if video_filename in processed:
|
| 605 |
-
continue
|
| 606 |
-
|
| 607 |
-
folder_name = downloader.get_folder_name(video_filename)
|
| 608 |
-
|
| 609 |
-
# 🚫 Skip if folder_name is empty / None / whitespace
|
| 610 |
-
if not folder_name or not str(folder_name).strip():
|
| 611 |
-
continue
|
| 612 |
-
|
| 613 |
-
writer.writerow([folder_name] + list(row.values()))
|
| 614 |
-
outfile.flush()
|
| 615 |
-
|
| 616 |
-
|
| 617 |
-
def add_link_column(input_csv, output_csv, downloader):
|
| 618 |
-
"""Reads input_csv and writes to output_csv with an added 'video_link' column"""
|
| 619 |
-
processed = set()
|
| 620 |
-
|
| 621 |
-
# 1. Load already processed filenames to allow resuming
|
| 622 |
-
if os.path.exists(output_csv):
|
| 623 |
-
with open(output_csv, newline="", encoding="utf-8") as f:
|
| 624 |
-
reader = csv.DictReader(f)
|
| 625 |
-
if reader.fieldnames and "VIDEO_FILENAME" in reader.fieldnames:
|
| 626 |
-
for row in reader:
|
| 627 |
-
processed.add(row["VIDEO_FILENAME"].split("/")[-1])
|
| 628 |
-
|
| 629 |
-
# 2. Process the files
|
| 630 |
-
with open(input_csv, newline="", encoding="utf-8") as infile:
|
| 631 |
-
reader = csv.DictReader(infile)
|
| 632 |
-
fieldnames = reader.fieldnames
|
| 633 |
-
|
| 634 |
-
# Determine if we need to write the header
|
| 635 |
-
file_exists = os.path.exists(output_csv) and os.path.getsize(output_csv) > 0
|
| 636 |
-
|
| 637 |
-
with open(output_csv, "a", newline="", encoding="utf-8") as outfile:
|
| 638 |
-
# We want 'video_link' to be the first column
|
| 639 |
-
writer = csv.DictWriter(outfile, fieldnames=["video_link"] + fieldnames)
|
| 640 |
-
|
| 641 |
-
if not file_exists:
|
| 642 |
-
writer.writeheader()
|
| 643 |
-
|
| 644 |
-
for row in reader:
|
| 645 |
-
video_filename = row["VIDEO_FILENAME"].split("/")[-1]
|
| 646 |
-
|
| 647 |
-
if video_filename in processed:
|
| 648 |
-
continue
|
| 649 |
-
|
| 650 |
-
logger.info(f"Fetching link for: {video_filename}")
|
| 651 |
-
video_link = downloader.get_video_link(video_filename)
|
| 652 |
-
|
| 653 |
-
if not video_link:
|
| 654 |
-
logger.warning(f"Could not find link for {video_filename}")
|
| 655 |
-
continue
|
| 656 |
-
|
| 657 |
-
# Prepare new row
|
| 658 |
-
new_row = {"video_link": video_link}
|
| 659 |
-
new_row.update(row)
|
| 660 |
-
|
| 661 |
-
writer.writerow(new_row)
|
| 662 |
-
outfile.flush() # Ensure it saves frequently
|
| 663 |
-
processed.add(video_filename)
|
| 664 |
-
|
| 665 |
-
|
| 666 |
-
# Example usage
|
| 667 |
-
if __name__ == "__main__":
|
| 668 |
-
try:
|
| 669 |
-
from dotenv import load_dotenv
|
| 670 |
-
load_dotenv()
|
| 671 |
-
|
| 672 |
-
downloader = VideoDownloader()
|
| 673 |
-
downloader._init_drive_downloader(download_path="testData/video_for_workflow")
|
| 674 |
-
add_link_column("testData/infloxa_copy/videos.csv", "testData/infloxa_copy/videos_with_links.csv", downloader)
|
| 675 |
-
|
| 676 |
-
# Download from Drive folder link
|
| 677 |
-
# result = downloader.download_from_drive_link(
|
| 678 |
-
# drive_link="https://drive.google.com/drive/folders/1WSrVAyqvPJzpRnoUxkNx0LqK9VlDs432",
|
| 679 |
-
# download_root="testData/video_for_workflow",
|
| 680 |
-
# file_extensions=['.mp4', '.mov', '.avi', '.mkv'] # Only video files
|
| 681 |
-
# )
|
| 682 |
-
|
| 683 |
-
# print(f"\nDownload completed!")
|
| 684 |
-
# print(f"Total: {result['total_files']}, Downloaded: {result['downloaded']}, "
|
| 685 |
-
# f"Skipped: {result['skipped']}, Failed: {result['failed']}")
|
| 686 |
-
|
| 687 |
-
# paths = [
|
| 688 |
-
# "testData/infloxa_copy/Infloxa_ Lifestyle_125videos",
|
| 689 |
-
# "testData/infloxa_copy/Infloxa_LuxuryCars_125videos",
|
| 690 |
-
# "testData/infloxa_copy/Infloxa_LuxuryItems_125videos",
|
| 691 |
-
# "testData/infloxa_copy/Infloxa_LuxuryRealEstate_125videos",
|
| 692 |
-
# "testData/infloxa_copy/Infloxa_Models_125videos",
|
| 693 |
-
# "testData/infloxa_copy/Infloxa_PrivateJets_125videos",
|
| 694 |
-
# "testData/infloxa_copy/Infloxa_Wealth&Exclusivity_125videos",
|
| 695 |
-
# "testData/infloxa_copy/Infloxa_Yachts_125videos",
|
| 696 |
-
# ]
|
| 697 |
-
|
| 698 |
-
# output_csv = "testData/infloxa_copy/videos_with_links.csv"
|
| 699 |
-
|
| 700 |
-
# VIDEO_EXTENSIONS = {".mp4", ".mov", ".mkv", ".avi", ".webm"}
|
| 701 |
-
|
| 702 |
-
# rows = []
|
| 703 |
-
|
| 704 |
-
# for base_path in paths:
|
| 705 |
-
# base_path = Path(base_path)
|
| 706 |
-
|
| 707 |
-
# if not base_path.exists():
|
| 708 |
-
# print(f"Skipping missing folder: {base_path}")
|
| 709 |
-
# continue
|
| 710 |
-
|
| 711 |
-
# for file in base_path.iterdir():
|
| 712 |
-
# if file.is_file() and file.suffix.lower() in VIDEO_EXTENSIONS:
|
| 713 |
-
# rows.append([file.name])
|
| 714 |
-
|
| 715 |
-
# # Write CSV
|
| 716 |
-
# with open(output_csv, "w", newline="", encoding="utf-8") as f:
|
| 717 |
-
# writer = csv.writer(f)
|
| 718 |
-
# writer.writerow(["VIDEO_FILENAME"])
|
| 719 |
-
# writer.writerows(rows)
|
| 720 |
-
|
| 721 |
-
# print(f"CSV created with {len(rows)} entries → {output_csv}")
|
| 722 |
-
|
| 723 |
-
except KeyboardInterrupt:
|
| 724 |
-
print("\nStopped by Ctrl+C")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
video_analyser/.gitignore
DELETED
|
@@ -1,25 +0,0 @@
|
|
| 1 |
-
# Google OAuth (now using environment variables)
|
| 2 |
-
# credentials.json and token.json no longer needed
|
| 3 |
-
|
| 4 |
-
# Downloaded videos
|
| 5 |
-
infloxa/
|
| 6 |
-
|
| 7 |
-
# Logs
|
| 8 |
-
*.log
|
| 9 |
-
|
| 10 |
-
# Python
|
| 11 |
-
__pycache__/
|
| 12 |
-
*.py[cod]
|
| 13 |
-
*$py.class
|
| 14 |
-
*.so
|
| 15 |
-
.Python
|
| 16 |
-
env/
|
| 17 |
-
venv/
|
| 18 |
-
ENV/
|
| 19 |
-
|
| 20 |
-
# OS
|
| 21 |
-
.DS_Store
|
| 22 |
-
Thumbs.db
|
| 23 |
-
|
| 24 |
-
analyse.py
|
| 25 |
-
dedupe_videos.sh
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
video_analyser/README.md
DELETED
|
@@ -1,264 +0,0 @@
|
|
| 1 |
-
# Luxury B-Roll Video Analysis System
|
| 2 |
-
|
| 3 |
-
A comprehensive Python system for downloading, analyzing, scoring, and cataloging luxury b-roll videos for short-form social media content (Instagram Reels, TikTok, YouTube Shorts).
|
| 4 |
-
|
| 5 |
-
## Features
|
| 6 |
-
|
| 7 |
-
- **Google Drive Integration**: Download entire folder structures automatically
|
| 8 |
-
- **AI-Powered Analysis**: Uses Google Gemini AI for intelligent video scoring
|
| 9 |
-
- **Technical Analysis**: Motion detection, face detection, duration extraction
|
| 10 |
-
- **Weighted Scoring**: Configurable multi-factor scoring system
|
| 11 |
-
- **CSV Output**: Machine-readable catalog for automated video selection
|
| 12 |
-
- **Progress Tracking**: Real-time progress bars and detailed logging
|
| 13 |
-
|
| 14 |
-
## System Requirements
|
| 15 |
-
|
| 16 |
-
- Python 3.8+
|
| 17 |
-
- OpenCV (for video processing)
|
| 18 |
-
- Google Cloud credentials (for Drive API)
|
| 19 |
-
- Gemini API key (for AI analysis)
|
| 20 |
-
- Sufficient disk space (50GB+ recommended)
|
| 21 |
-
|
| 22 |
-
## Installation
|
| 23 |
-
|
| 24 |
-
### 1. Install Dependencies
|
| 25 |
-
|
| 26 |
-
```bash
|
| 27 |
-
cd video_analyser
|
| 28 |
-
pip install -r requirements.txt
|
| 29 |
-
```
|
| 30 |
-
|
| 31 |
-
### 2. Setup Google Drive API
|
| 32 |
-
|
| 33 |
-
1. Go to [Google Cloud Console](https://console.cloud.google.com/)
|
| 34 |
-
2. Create a new project (or select existing)
|
| 35 |
-
3. Enable the **Google Drive API**
|
| 36 |
-
4. Create OAuth 2.0 credentials:
|
| 37 |
-
- Go to **Credentials** → **Create Credentials** → **OAuth client ID**
|
| 38 |
-
- Application type: **Desktop app**
|
| 39 |
-
- Download the credentials
|
| 40 |
-
5. Save as `credentials.json` in the `video_analyser/` directory
|
| 41 |
-
|
| 42 |
-
### 3. Setup Gemini API
|
| 43 |
-
|
| 44 |
-
1. Get your Gemini API key from [Google AI Studio](https://makersuite.google.com/app/apikey)
|
| 45 |
-
2. Set environment variable:
|
| 46 |
-
|
| 47 |
-
```bash
|
| 48 |
-
export GEMINI_API_KEY='your-api-key-here'
|
| 49 |
-
|
| 50 |
-
# For permanent setup, add to ~/.bashrc or ~/.zshrc:
|
| 51 |
-
echo 'export GEMINI_API_KEY="your-api-key-here"' >> ~/.bashrc
|
| 52 |
-
source ~/.bashrc
|
| 53 |
-
```
|
| 54 |
-
|
| 55 |
-
### 4. Customize Analysis Prompt (Optional)
|
| 56 |
-
|
| 57 |
-
The AI analysis prompt is stored in `analysis_prompt.md`. You can edit this file to customize:
|
| 58 |
-
- Scoring criteria
|
| 59 |
-
- Description style
|
| 60 |
-
- Quality guidelines
|
| 61 |
-
- Output format
|
| 62 |
-
|
| 63 |
-
### 4. Configure Settings
|
| 64 |
-
|
| 65 |
-
Edit `config.yaml` to customize:
|
| 66 |
-
- Google Drive folder ID (default is already set)
|
| 67 |
-
- Processing settings (concurrent downloads, frame sampling)
|
| 68 |
-
- Scoring weights
|
| 69 |
-
- Output paths
|
| 70 |
-
|
| 71 |
-
## Usage
|
| 72 |
-
|
| 73 |
-
### First Run (Full Pipeline)
|
| 74 |
-
|
| 75 |
-
```bash
|
| 76 |
-
python main.py
|
| 77 |
-
```
|
| 78 |
-
|
| 79 |
-
This will:
|
| 80 |
-
1. Authenticate with Google Drive (opens browser)
|
| 81 |
-
2. Download all videos maintaining folder structure
|
| 82 |
-
3. Analyze each video with AI
|
| 83 |
-
4. Generate `infloxa_video_analysis.csv`
|
| 84 |
-
|
| 85 |
-
### Skip Download (Process Existing Videos)
|
| 86 |
-
|
| 87 |
-
If you already have videos downloaded:
|
| 88 |
-
|
| 89 |
-
```bash
|
| 90 |
-
python main.py --skip-download
|
| 91 |
-
```
|
| 92 |
-
|
| 93 |
-
### Test Mode (Process First 3 Videos)
|
| 94 |
-
|
| 95 |
-
For testing the pipeline:
|
| 96 |
-
|
| 97 |
-
```bash
|
| 98 |
-
python main.py --test-mode
|
| 99 |
-
```
|
| 100 |
-
|
| 101 |
-
### Custom Config File
|
| 102 |
-
|
| 103 |
-
```bash
|
| 104 |
-
python main.py --config custom_config.yaml
|
| 105 |
-
```
|
| 106 |
-
|
| 107 |
-
## Output Format
|
| 108 |
-
|
| 109 |
-
The system generates `infloxa_video_analysis.csv` with the following columns:
|
| 110 |
-
|
| 111 |
-
| Column | Type | Description |
|
| 112 |
-
|--------|------|-------------|
|
| 113 |
-
| `video_filename` | string | Original filename |
|
| 114 |
-
| `category` | string | Folder name (e.g., luxury_car, yacht) |
|
| 115 |
-
| `duration_seconds` | float | Video duration |
|
| 116 |
-
| `description` | string | AI-generated luxury description (detailed) |
|
| 117 |
-
| `luxury_score` | int (0-100) | Exclusivity and premium feel |
|
| 118 |
-
| `visual_clarity_score` | int (0-100) | Composition and mobile readability |
|
| 119 |
-
| `motion_score` | int (0-100) | Camera/subject motion level |
|
| 120 |
-
| `beat_editing_score` | int (0-100) | Suitability for beat-synced edits |
|
| 121 |
-
| `recommended_edit_type` | enum | static_hold, slow_pan, beat_cut, pulse_cut, transition_clip |
|
| 122 |
-
| `ideal_clip_length_seconds` | float | Recommended clip length (typically 1.0-2.5s) |
|
| 123 |
-
| `energy_level` | enum | low, medium, high |
|
| 124 |
-
| `has_text_or_watermark` | boolean | true/false |
|
| 125 |
-
| `has_faces_visible` | boolean | true/false |
|
| 126 |
-
| `usable_for_ads` | boolean | Commercial safety (true/false) |
|
| 127 |
-
| `final_selection_score` | int (0-100) | Weighted overall score |
|
| 128 |
-
|
| 129 |
-
## Project Structure
|
| 130 |
-
|
| 131 |
-
```
|
| 132 |
-
video_analyser/
|
| 133 |
-
├── config.yaml # Configuration settings
|
| 134 |
-
├── requirements.txt # Python dependencies
|
| 135 |
-
├── README.md # This file
|
| 136 |
-
├── analysis_prompt.md # Gemini AI analysis prompt
|
| 137 |
-
├── credentials.json # Google Drive credentials (you provide)
|
| 138 |
-
├── token.json # Auto-generated OAuth token
|
| 139 |
-
├── main.py # Main orchestrator
|
| 140 |
-
├── video_analysis.log # Execution logs
|
| 141 |
-
├── modules/
|
| 142 |
-
│ ├── __init__.py
|
| 143 |
-
│ ├── drive_downloader.py # Google Drive integration
|
| 144 |
-
│ ├── video_analyzer.py # Technical video analysis
|
| 145 |
-
│ ├── ai_analyzer.py # Gemini AI analysis
|
| 146 |
-
│ ├── scorer.py # Scoring algorithms
|
| 147 |
-
│ └── csv_writer.py # CSV output handler
|
| 148 |
-
├── infloxa/ # Downloaded videos (auto-created)
|
| 149 |
-
│ ├── category_1/
|
| 150 |
-
│ ├── category_2/
|
| 151 |
-
│ └── ...
|
| 152 |
-
└── infloxa_video_analysis.csv # Output CSV
|
| 153 |
-
```
|
| 154 |
-
|
| 155 |
-
## Workflow Pipeline
|
| 156 |
-
|
| 157 |
-
```
|
| 158 |
-
1. Download Videos (Google Drive)
|
| 159 |
-
↓
|
| 160 |
-
2. Extract Technical Metadata (duration, motion, faces)
|
| 161 |
-
↓
|
| 162 |
-
3. Sample Key Frames
|
| 163 |
-
↓
|
| 164 |
-
4. AI Analysis with Gemini (descriptions, luxury scoring)
|
| 165 |
-
↓
|
| 166 |
-
5. Calculate Weighted Scores
|
| 167 |
-
↓
|
| 168 |
-
6. Write Row to CSV
|
| 169 |
-
```
|
| 170 |
-
|
| 171 |
-
## Scoring Algorithm
|
| 172 |
-
|
| 173 |
-
The `final_selection_score` is calculated using weighted factors:
|
| 174 |
-
|
| 175 |
-
```python
|
| 176 |
-
final_score = (
|
| 177 |
-
luxury_score × 0.40 +
|
| 178 |
-
visual_clarity_score × 0.25 +
|
| 179 |
-
beat_editing_score × 0.20 +
|
| 180 |
-
motion_score × 0.10 +
|
| 181 |
-
usability_score × 0.05
|
| 182 |
-
)
|
| 183 |
-
```
|
| 184 |
-
|
| 185 |
-
Weights can be customized in `config.yaml`.
|
| 186 |
-
|
| 187 |
-
## Logging
|
| 188 |
-
|
| 189 |
-
Detailed logs are written to `video_analysis.log`. Log level can be adjusted in `config.yaml`:
|
| 190 |
-
- `DEBUG`: Verbose output for troubleshooting
|
| 191 |
-
- `INFO`: Standard operational messages
|
| 192 |
-
- `WARNING`: Important warnings
|
| 193 |
-
- `ERROR`: Error messages only
|
| 194 |
-
|
| 195 |
-
## Troubleshooting
|
| 196 |
-
|
| 197 |
-
### Google Drive Authentication Issues
|
| 198 |
-
|
| 199 |
-
- Delete `token.json` and re-authenticate
|
| 200 |
-
- Ensure `credentials.json` is valid OAuth 2.0 credentials
|
| 201 |
-
- Check that Drive API is enabled in Google Cloud Console
|
| 202 |
-
|
| 203 |
-
### Gemini API Errors
|
| 204 |
-
|
| 205 |
-
- Verify `GEMINI_API_KEY` environment variable is set: `echo $GEMINI_API_KEY`
|
| 206 |
-
- Check you have sufficient quota/credits
|
| 207 |
-
- Ensure `google-generativeai` package is up to date
|
| 208 |
-
|
| 209 |
-
### Video Processing Errors
|
| 210 |
-
|
| 211 |
-
- Ensure videos are in supported formats (mp4, mov, avi, mkv, webm)
|
| 212 |
-
- Check that OpenCV is properly installed
|
| 213 |
-
- For face detection issues, install `opencv-contrib-python`
|
| 214 |
-
|
| 215 |
-
### Out of Memory
|
| 216 |
-
|
| 217 |
-
- Reduce `frames_to_sample` in `config.yaml`
|
| 218 |
-
- Process videos in smaller batches
|
| 219 |
-
- Close other applications
|
| 220 |
-
|
| 221 |
-
## Advanced Usage
|
| 222 |
-
|
| 223 |
-
### Resume After Interruption
|
| 224 |
-
|
| 225 |
-
The system automatically skips:
|
| 226 |
-
- Already downloaded videos
|
| 227 |
-
- Videos already in CSV (use append mode)
|
| 228 |
-
|
| 229 |
-
Simply re-run `python main.py` to continue.
|
| 230 |
-
|
| 231 |
-
### Batch Processing
|
| 232 |
-
|
| 233 |
-
Edit `config.yaml` to adjust:
|
| 234 |
-
```yaml
|
| 235 |
-
processing:
|
| 236 |
-
max_concurrent_downloads: 3 # Parallel downloads
|
| 237 |
-
batch_size: 10 # Process in batches
|
| 238 |
-
```
|
| 239 |
-
|
| 240 |
-
### Custom Scoring Weights
|
| 241 |
-
|
| 242 |
-
Modify weights in `config.yaml` (must sum to 1.0):
|
| 243 |
-
```yaml
|
| 244 |
-
scoring:
|
| 245 |
-
luxury_weight: 0.40
|
| 246 |
-
visual_clarity_weight: 0.25
|
| 247 |
-
beat_editing_weight: 0.20
|
| 248 |
-
motion_weight: 0.10
|
| 249 |
-
usability_weight: 0.05
|
| 250 |
-
```
|
| 251 |
-
|
| 252 |
-
## API Costs
|
| 253 |
-
|
| 254 |
-
**Gemini API**: Each video analysis sends 3 frames (configurable). Monitor your usage at [Google AI Studio](https://makersuite.google.com/).
|
| 255 |
-
|
| 256 |
-
**Google Drive API**: Free tier includes 10,000 requests/day (sufficient for most use cases).
|
| 257 |
-
|
| 258 |
-
## License
|
| 259 |
-
|
| 260 |
-
This is a proprietary system for Infloxa luxury video analysis.
|
| 261 |
-
|
| 262 |
-
## Support
|
| 263 |
-
|
| 264 |
-
For issues or questions, check the logs in `video_analysis.log` and ensure all configuration is correct.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
video_analyser/analysis_prompt.md
DELETED
|
@@ -1,246 +0,0 @@
|
|
| 1 |
-
**System / Instruction Prompt**
|
| 2 |
-
|
| 3 |
-
You are analyzing **luxury b-roll stock footage** for short-form social media content (Instagram Reels, TikTok, YouTube Shorts).
|
| 4 |
-
|
| 5 |
-
This footage is sold as **premium luxury stock video** and will be used by:
|
| 6 |
-
|
| 7 |
-
* Luxury & motivation theme pages
|
| 8 |
-
* Content creators and agencies
|
| 9 |
-
* Beat-synced and pulse-based edits
|
| 10 |
-
* Premium brand advertising
|
| 11 |
-
|
| 12 |
-
Your goal is to **label, score, and describe** each video so it can be efficiently selected and edited at scale.
|
| 13 |
-
|
| 14 |
-
---
|
| 15 |
-
|
| 16 |
-
## 🎥 VIDEO CONTEXT
|
| 17 |
-
|
| 18 |
-
Each input is a **short luxury b-roll clip** (no dialogue).
|
| 19 |
-
Assume the video may be used for:
|
| 20 |
-
|
| 21 |
-
* Beat-based editing
|
| 22 |
-
* Pulse-based editing
|
| 23 |
-
* Static luxury holds
|
| 24 |
-
* Fast-paced aesthetic reels
|
| 25 |
-
|
| 26 |
-
---
|
| 27 |
-
|
| 28 |
-
## 🧠 ANALYSIS REQUIREMENTS
|
| 29 |
-
|
| 30 |
-
Analyze the video and return **ONLY valid JSON** with the following fields.
|
| 31 |
-
|
| 32 |
-
---
|
| 33 |
-
|
| 34 |
-
### 1️⃣ category (string)
|
| 35 |
-
|
| 36 |
-
Choose **ONE** primary category that best represents the clip:
|
| 37 |
-
|
| 38 |
-
* `luxury_architecture`
|
| 39 |
-
* `luxury_car`
|
| 40 |
-
* `yacht_boat`
|
| 41 |
-
* `private_jet`
|
| 42 |
-
* `helicopter`
|
| 43 |
-
* `luxury_watch`
|
| 44 |
-
* `jewelry_accessories`
|
| 45 |
-
* `luxury_fashion`
|
| 46 |
-
* `fine_dining`
|
| 47 |
-
* `champagne_wine`
|
| 48 |
-
* `luxury_hotel`
|
| 49 |
-
* `infinity_pool`
|
| 50 |
-
* `penthouse_interior`
|
| 51 |
-
* `cityscape`
|
| 52 |
-
* `skyline_night`
|
| 53 |
-
* `luxury_lifestyle`
|
| 54 |
-
* `private_estate`
|
| 55 |
-
* `golf_course`
|
| 56 |
-
* `casino_gaming`
|
| 57 |
-
* `luxury_tech`
|
| 58 |
-
* `supercars_racing`
|
| 59 |
-
* `nature_premium`
|
| 60 |
-
* `tropical_beach`
|
| 61 |
-
* `mountain_resort`
|
| 62 |
-
* `desert_luxury`
|
| 63 |
-
* `abstract_luxury`
|
| 64 |
-
* `gold_elements`
|
| 65 |
-
* `money_cash`
|
| 66 |
-
* `crypto_finance`
|
| 67 |
-
* `art_gallery`
|
| 68 |
-
* `luxury_retail`
|
| 69 |
-
* `other`
|
| 70 |
-
|
| 71 |
-
---
|
| 72 |
-
|
| 73 |
-
### 2️⃣ short_description (string)
|
| 74 |
-
|
| 75 |
-
A **concise one-line summary** (10-15 words max) of what the video shows.
|
| 76 |
-
|
| 77 |
-
**Examples**:
|
| 78 |
-
- "Luxury yacht sailing at sunset"
|
| 79 |
-
- "Modern penthouse with city skyline view"
|
| 80 |
-
- "Sports car driving through mountain road"
|
| 81 |
-
|
| 82 |
-
---
|
| 83 |
-
|
| 84 |
-
### 3️⃣ description (string)
|
| 85 |
-
|
| 86 |
-
Write a **high-quality, premium description** (3–5 sentences) that explains:
|
| 87 |
-
|
| 88 |
-
* What is visible in the video
|
| 89 |
-
* Why it feels luxurious or premium
|
| 90 |
-
* Camera movement or framing style
|
| 91 |
-
* Mood and atmosphere
|
| 92 |
-
* Why it works for luxury content
|
| 93 |
-
|
| 94 |
-
Focus on **visual storytelling**, not generic stock descriptions.
|
| 95 |
-
|
| 96 |
-
---
|
| 97 |
-
|
| 98 |
-
### 4️⃣ luxury_score (integer 0–100)
|
| 99 |
-
|
| 100 |
-
Rate the **exclusivity and aspirational quality**.
|
| 101 |
-
|
| 102 |
-
**Be strict**:
|
| 103 |
-
|
| 104 |
-
* Average clips should NOT exceed 70
|
| 105 |
-
* Only truly exceptional luxury clips should score above 85
|
| 106 |
-
|
| 107 |
-
---
|
| 108 |
-
|
| 109 |
-
### 5️⃣ visual_clarity_score (integer 0–100)
|
| 110 |
-
|
| 111 |
-
Rate how well the clip works on **mobile screens**:
|
| 112 |
-
|
| 113 |
-
* Clear subject
|
| 114 |
-
* Clean composition
|
| 115 |
-
* Not overly busy
|
| 116 |
-
* Professional framing
|
| 117 |
-
|
| 118 |
-
---
|
| 119 |
-
|
| 120 |
-
### 6️⃣ motion_score (integer 0–100)
|
| 121 |
-
|
| 122 |
-
Rate the **amount and quality of motion**:
|
| 123 |
-
|
| 124 |
-
* Camera movement
|
| 125 |
-
* Subject movement
|
| 126 |
-
* Suitability for dynamic edits
|
| 127 |
-
|
| 128 |
-
---
|
| 129 |
-
|
| 130 |
-
### 7️⃣ beat_editing_score (integer 0–100)
|
| 131 |
-
|
| 132 |
-
How well does this clip support:
|
| 133 |
-
|
| 134 |
-
* Beat-based editing
|
| 135 |
-
* Pulse-based rhythm
|
| 136 |
-
* Fast luxury reels
|
| 137 |
-
|
| 138 |
-
---
|
| 139 |
-
|
| 140 |
-
### 8️⃣ recommended_edit_type (string)
|
| 141 |
-
|
| 142 |
-
Choose **ONE**:
|
| 143 |
-
|
| 144 |
-
* `static_hold` – Minimal motion, elegant pause moments
|
| 145 |
-
* `slow_pan` – Smooth cinematic movement
|
| 146 |
-
* `beat_cut` – Sharp, intentional cuts on beats
|
| 147 |
-
* `pulse_cut` – Rhythmic pulsing edits
|
| 148 |
-
* `transition_clip` – Works well between scenes
|
| 149 |
-
|
| 150 |
-
---
|
| 151 |
-
|
| 152 |
-
### 9️⃣ ideal_clip_length_seconds (float)
|
| 153 |
-
|
| 154 |
-
Best usable duration for short-form editing
|
| 155 |
-
(typically **1.0–2.5 seconds**).
|
| 156 |
-
|
| 157 |
-
---
|
| 158 |
-
|
| 159 |
-
### 🔟 energy_level (string)
|
| 160 |
-
|
| 161 |
-
Choose **ONE**:
|
| 162 |
-
|
| 163 |
-
* `low` – Calm, slow, serene
|
| 164 |
-
* `medium` – Balanced pacing
|
| 165 |
-
* `high` – Fast, energetic, dynamic
|
| 166 |
-
|
| 167 |
-
---
|
| 168 |
-
|
| 169 |
-
### 1️⃣1️⃣ has_text_or_watermark (boolean)
|
| 170 |
-
|
| 171 |
-
Does the clip contain **any visible text, logos, or watermarks**?
|
| 172 |
-
|
| 173 |
-
---
|
| 174 |
-
|
| 175 |
-
### 1️⃣2️⃣ usable_for_ads (boolean)
|
| 176 |
-
|
| 177 |
-
Is this clip **commercially safe**?
|
| 178 |
-
|
| 179 |
-
* False if logos, brands, text, or legal risks are visible
|
| 180 |
-
|
| 181 |
-
---
|
| 182 |
-
|
| 183 |
-
### 1️⃣3️⃣ final_selection_score (integer 0–100)
|
| 184 |
-
|
| 185 |
-
Overall suitability for **Infloxa luxury content**, considering:
|
| 186 |
-
|
| 187 |
-
* Luxury quality
|
| 188 |
-
* Visual clarity
|
| 189 |
-
* Motion
|
| 190 |
-
* Beat usability
|
| 191 |
-
* Commercial safety
|
| 192 |
-
|
| 193 |
-
This score will be used to **rank and select the best clips**.
|
| 194 |
-
|
| 195 |
-
---
|
| 196 |
-
|
| 197 |
-
## 📤 OUTPUT FORMAT (STRICT)
|
| 198 |
-
|
| 199 |
-
**Output ONLY valid JSON.**
|
| 200 |
-
No explanations.
|
| 201 |
-
No markdown.
|
| 202 |
-
No extra text.
|
| 203 |
-
|
| 204 |
-
```json
|
| 205 |
-
{
|
| 206 |
-
"category": "luxury_architecture",
|
| 207 |
-
"short_description": "Futuristic yacht with glass garden at twilight",
|
| 208 |
-
"description": "Detailed 3–5 sentence luxury description...",
|
| 209 |
-
"luxury_score": 0,
|
| 210 |
-
"visual_clarity_score": 0,
|
| 211 |
-
"motion_score": 0,
|
| 212 |
-
"beat_editing_score": 0,
|
| 213 |
-
"recommended_edit_type": "slow_pan",
|
| 214 |
-
"ideal_clip_length_seconds": 1.5,
|
| 215 |
-
"energy_level": "medium",
|
| 216 |
-
"has_text_or_watermark": false,
|
| 217 |
-
"usable_for_ads": true,
|
| 218 |
-
"final_selection_score": 0
|
| 219 |
-
}
|
| 220 |
-
```
|
| 221 |
-
|
| 222 |
-
---
|
| 223 |
-
|
| 224 |
-
## 🎯 SCORING GUIDELINES
|
| 225 |
-
|
| 226 |
-
**Luxury Score Calibration**
|
| 227 |
-
|
| 228 |
-
* 0–30: Not luxury
|
| 229 |
-
* 31–50: Mild premium feel
|
| 230 |
-
* 51–70: Good luxury content
|
| 231 |
-
* 71–85: Exceptional
|
| 232 |
-
* 86–100: Rare, top-tier
|
| 233 |
-
|
| 234 |
-
**Penalize**:
|
| 235 |
-
|
| 236 |
-
* Generic stock footage
|
| 237 |
-
* Weak luxury signals
|
| 238 |
-
* Overly busy or unclear visuals
|
| 239 |
-
* Poor framing
|
| 240 |
-
|
| 241 |
-
**Favor**:
|
| 242 |
-
|
| 243 |
-
* Premium mobile aesthetics
|
| 244 |
-
* Clean looping potential
|
| 245 |
-
* Strong beat compatibility
|
| 246 |
-
* High aspirational value
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
video_analyser/config.yaml
DELETED
|
@@ -1,39 +0,0 @@
|
|
| 1 |
-
# Luxury Video Analysis System Configuration
|
| 2 |
-
|
| 3 |
-
# Google Drive Settings
|
| 4 |
-
google_drive:
|
| 5 |
-
folder_id: "1WSrVAyqvPJzpRnoUxkNx0LqK9VlDs432"
|
| 6 |
-
# OAuth credentials from environment variables:
|
| 7 |
-
# - SERVER_GOOGLE_CLIENT_ID
|
| 8 |
-
# - SERVER_GOOGLE_CLIENT_SECRET
|
| 9 |
-
# - SERVER_GOOGLE_REFRESH_TOKEN
|
| 10 |
-
|
| 11 |
-
# Gemini API Settings
|
| 12 |
-
gemini:
|
| 13 |
-
# API key is read from GEMINI_API_KEY environment variable
|
| 14 |
-
model: "gemini-3-pro-preview" # or "gemini-1.5-pro" for better quality
|
| 15 |
-
prompt_file: "analysis_prompt.md" # Path to analysis prompt
|
| 16 |
-
|
| 17 |
-
# Processing Settings
|
| 18 |
-
processing:
|
| 19 |
-
max_concurrent_downloads: 3
|
| 20 |
-
batch_size: 10 # Process videos in batches
|
| 21 |
-
frames_to_sample: 3 # Number of frames to send to AI
|
| 22 |
-
|
| 23 |
-
# Output Settings
|
| 24 |
-
output:
|
| 25 |
-
local_video_dir: "video_for_workflow" # Relative to video_analyser/
|
| 26 |
-
csv_file: "infloxa_video_analysis.csv" # Relative to video_analyser/
|
| 27 |
-
|
| 28 |
-
# Scoring Weights (must sum to 1.0)
|
| 29 |
-
scoring:
|
| 30 |
-
luxury_weight: 0.40
|
| 31 |
-
visual_clarity_weight: 0.25
|
| 32 |
-
beat_editing_weight: 0.20
|
| 33 |
-
motion_weight: 0.10
|
| 34 |
-
usability_weight: 0.05
|
| 35 |
-
|
| 36 |
-
# Logging
|
| 37 |
-
logging:
|
| 38 |
-
level: "INFO" # DEBUG, INFO, WARNING, ERROR
|
| 39 |
-
file: "video_analysis.log" # Relative to video_analyser/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
video_analyser/get_refresh_token.py
DELETED
|
@@ -1,21 +0,0 @@
|
|
| 1 |
-
from google_auth_oauthlib.flow import InstalledAppFlow
|
| 2 |
-
|
| 3 |
-
# ✅ Use YouTube scope (not Drive)
|
| 4 |
-
SCOPES = ['https://www.googleapis.com/auth/drive.readonly']
|
| 5 |
-
|
| 6 |
-
def main():
|
| 7 |
-
print("🔑 Starting OAuth flow...")
|
| 8 |
-
|
| 9 |
-
flow = InstalledAppFlow.from_client_secrets_file(
|
| 10 |
-
"whoa/client_secret_688373610660-vtr5l8q7s4is9kkvd7hla1cqg273emfs.apps.googleusercontent.com.json",
|
| 11 |
-
SCOPES
|
| 12 |
-
)
|
| 13 |
-
|
| 14 |
-
creds = flow.run_local_server(port=0)
|
| 15 |
-
|
| 16 |
-
print("\n✅ AUTH SUCCESS")
|
| 17 |
-
print("REFRESH TOKEN:\n")
|
| 18 |
-
print(creds.refresh_token)
|
| 19 |
-
|
| 20 |
-
if __name__ == "__main__":
|
| 21 |
-
main()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
video_analyser/infloxa_video_analysis.csv
DELETED
|
The diff for this file is too large to render.
See raw diff
|
|
|
video_analyser/infloxa_video_analysis_with_folders.csv
DELETED
|
The diff for this file is too large to render.
See raw diff
|
|
|
video_analyser/main.py
DELETED
|
@@ -1,501 +0,0 @@
|
|
| 1 |
-
"""
|
| 2 |
-
Luxury B-Roll Video Analysis System
|
| 3 |
-
Main orchestrator for downloading, analyzing, scoring, and cataloging luxury videos
|
| 4 |
-
"""
|
| 5 |
-
|
| 6 |
-
import os
|
| 7 |
-
import sys
|
| 8 |
-
import yaml
|
| 9 |
-
import logging
|
| 10 |
-
import argparse
|
| 11 |
-
from pathlib import Path
|
| 12 |
-
from typing import Dict, List
|
| 13 |
-
from tqdm import tqdm
|
| 14 |
-
from dotenv import load_dotenv
|
| 15 |
-
|
| 16 |
-
# Load environment variables from .env file
|
| 17 |
-
load_dotenv()
|
| 18 |
-
|
| 19 |
-
from modules import (
|
| 20 |
-
DriveDownloader,
|
| 21 |
-
VideoAnalyzer,
|
| 22 |
-
AIAnalyzer,
|
| 23 |
-
Scorer,
|
| 24 |
-
CSVWriter
|
| 25 |
-
)
|
| 26 |
-
from modules.git_ops import git_commit_progress
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
# Get script directory for relative paths
|
| 30 |
-
SCRIPT_DIR = Path(__file__).parent.absolute()
|
| 31 |
-
PROGRESS_DIR = SCRIPT_DIR / "progress"
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
def get_progress_file(job_index=None):
|
| 35 |
-
"""Get the appropriate progress file for this job."""
|
| 36 |
-
PROGRESS_DIR.mkdir(exist_ok=True)
|
| 37 |
-
|
| 38 |
-
if job_index is not None:
|
| 39 |
-
return PROGRESS_DIR / f"analyzed_videos_job{job_index}.txt"
|
| 40 |
-
return PROGRESS_DIR / "analyzed_videos.txt"
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
def load_all_analyzed_videos():
|
| 44 |
-
"""
|
| 45 |
-
Load analyzed videos from all job-specific progress files.
|
| 46 |
-
Returns a set of video filenames that have been successfully analyzed.
|
| 47 |
-
"""
|
| 48 |
-
analyzed = set()
|
| 49 |
-
|
| 50 |
-
if not PROGRESS_DIR.exists():
|
| 51 |
-
return analyzed
|
| 52 |
-
|
| 53 |
-
# Load from main progress file
|
| 54 |
-
main_progress = PROGRESS_DIR / "analyzed_videos.txt"
|
| 55 |
-
if main_progress.exists():
|
| 56 |
-
with open(main_progress, "r") as f:
|
| 57 |
-
analyzed.update(x.strip() for x in f if x.strip())
|
| 58 |
-
|
| 59 |
-
# Load from all job-specific files
|
| 60 |
-
for job_file in PROGRESS_DIR.glob("analyzed_videos_job*.txt"):
|
| 61 |
-
with open(job_file, "r") as f:
|
| 62 |
-
analyzed.update(x.strip() for x in f if x.strip())
|
| 63 |
-
|
| 64 |
-
return analyzed
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
def setup_logging(config: dict):
|
| 68 |
-
"""
|
| 69 |
-
Setup logging configuration
|
| 70 |
-
|
| 71 |
-
Args:
|
| 72 |
-
config: Configuration dictionary
|
| 73 |
-
"""
|
| 74 |
-
log_level = getattr(logging, config['logging']['level'].upper())
|
| 75 |
-
log_file = config['logging']['file']
|
| 76 |
-
|
| 77 |
-
# Create formatter
|
| 78 |
-
formatter = logging.Formatter(
|
| 79 |
-
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
| 80 |
-
)
|
| 81 |
-
|
| 82 |
-
# File handler
|
| 83 |
-
file_handler = logging.FileHandler(log_file)
|
| 84 |
-
file_handler.setLevel(log_level)
|
| 85 |
-
file_handler.setFormatter(formatter)
|
| 86 |
-
|
| 87 |
-
# Console handler
|
| 88 |
-
console_handler = logging.StreamHandler()
|
| 89 |
-
console_handler.setLevel(logging.INFO)
|
| 90 |
-
console_handler.setFormatter(formatter)
|
| 91 |
-
|
| 92 |
-
# Root logger
|
| 93 |
-
root_logger = logging.getLogger()
|
| 94 |
-
root_logger.setLevel(log_level)
|
| 95 |
-
root_logger.addHandler(file_handler)
|
| 96 |
-
root_logger.addHandler(console_handler)
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
def load_config(config_path: str = 'config.yaml') -> dict:
|
| 100 |
-
"""
|
| 101 |
-
Load configuration from YAML file
|
| 102 |
-
|
| 103 |
-
Args:
|
| 104 |
-
config_path: Path to config file (relative to script directory or absolute)
|
| 105 |
-
|
| 106 |
-
Returns:
|
| 107 |
-
Configuration dictionary
|
| 108 |
-
"""
|
| 109 |
-
try:
|
| 110 |
-
# Get the directory where this script is located
|
| 111 |
-
script_dir = Path(__file__).parent.absolute()
|
| 112 |
-
|
| 113 |
-
# If config_path is just a filename, look in script directory
|
| 114 |
-
config_file = Path(config_path)
|
| 115 |
-
if not config_file.is_absolute() and config_file.name == config_path:
|
| 116 |
-
config_file = script_dir / config_path
|
| 117 |
-
|
| 118 |
-
with open(config_file, 'r') as f:
|
| 119 |
-
config = yaml.safe_load(f)
|
| 120 |
-
return config
|
| 121 |
-
except Exception as e:
|
| 122 |
-
print(f"Error loading config: {e}")
|
| 123 |
-
sys.exit(1)
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
def validate_config(config: dict) -> bool:
|
| 127 |
-
"""
|
| 128 |
-
Validate configuration has all required fields
|
| 129 |
-
|
| 130 |
-
Args:
|
| 131 |
-
config: Configuration dictionary
|
| 132 |
-
|
| 133 |
-
Returns:
|
| 134 |
-
True if valid, False otherwise
|
| 135 |
-
"""
|
| 136 |
-
# Check Gemini API key from environment
|
| 137 |
-
if not os.getenv('GEMINI_API_KEY'):
|
| 138 |
-
print("ERROR: GEMINI_API_KEY environment variable not set")
|
| 139 |
-
print("Set it with: export GEMINI_API_KEY='your-api-key-here'")
|
| 140 |
-
return False
|
| 141 |
-
|
| 142 |
-
# Check Google OAuth credentials from environment
|
| 143 |
-
required_oauth = [
|
| 144 |
-
'SERVER_GOOGLE_CLIENT_ID',
|
| 145 |
-
'SERVER_GOOGLE_CLIENT_SECRET',
|
| 146 |
-
'SERVER_GOOGLE_REFRESH_TOKEN'
|
| 147 |
-
]
|
| 148 |
-
|
| 149 |
-
missing_oauth = [var for var in required_oauth if not os.getenv(var)]
|
| 150 |
-
if missing_oauth:
|
| 151 |
-
print("ERROR: Missing Google OAuth environment variables:")
|
| 152 |
-
for var in missing_oauth:
|
| 153 |
-
print(f" - {var}")
|
| 154 |
-
print("\nSet them with:")
|
| 155 |
-
print(" export SERVER_GOOGLE_CLIENT_ID='your-client-id'")
|
| 156 |
-
print(" export SERVER_GOOGLE_CLIENT_SECRET='your-client-secret'")
|
| 157 |
-
print(" export SERVER_GOOGLE_REFRESH_TOKEN='your-refresh-token'")
|
| 158 |
-
return False
|
| 159 |
-
|
| 160 |
-
return True
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
def process_single_video(video_path: str, config: dict,
|
| 164 |
-
video_analyzer: VideoAnalyzer,
|
| 165 |
-
ai_analyzer: AIAnalyzer,
|
| 166 |
-
scorer: Scorer,
|
| 167 |
-
csv_writer: CSVWriter,
|
| 168 |
-
progress_file: Path,
|
| 169 |
-
job_index: int = None,
|
| 170 |
-
commit: bool = False) -> bool:
|
| 171 |
-
"""
|
| 172 |
-
Process a single video through the complete pipeline
|
| 173 |
-
|
| 174 |
-
Args:
|
| 175 |
-
video_path: Path to video file
|
| 176 |
-
config: Configuration dictionary
|
| 177 |
-
video_analyzer: VideoAnalyzer instance
|
| 178 |
-
ai_analyzer: AIAnalyzer instance
|
| 179 |
-
scorer: Scorer instance
|
| 180 |
-
csv_writer: CSVWriter instance
|
| 181 |
-
progress_file: Path to progress tracking file
|
| 182 |
-
job_index: Job index for parallel processing
|
| 183 |
-
commit: Whether to commit progress to git
|
| 184 |
-
|
| 185 |
-
Returns:
|
| 186 |
-
True if successful, False otherwise
|
| 187 |
-
"""
|
| 188 |
-
logger = logging.getLogger(__name__)
|
| 189 |
-
video_filename = os.path.basename(video_path)
|
| 190 |
-
|
| 191 |
-
try:
|
| 192 |
-
# STEP 1: Technical Analysis
|
| 193 |
-
logger.info(f"Processing: {video_filename}")
|
| 194 |
-
technical_analysis = video_analyzer.analyze_video(video_path)
|
| 195 |
-
|
| 196 |
-
# STEP 2: AI Analysis
|
| 197 |
-
ai_analysis = ai_analyzer.analyze_with_gemini(
|
| 198 |
-
video_path,
|
| 199 |
-
video_filename
|
| 200 |
-
)
|
| 201 |
-
|
| 202 |
-
if not ai_analysis:
|
| 203 |
-
logger.error(f"AI analysis failed for {video_path}")
|
| 204 |
-
return False
|
| 205 |
-
|
| 206 |
-
# STEP 3: Calculate Scores
|
| 207 |
-
scoring_results = scorer.calculate_final_score(
|
| 208 |
-
technical_analysis,
|
| 209 |
-
ai_analysis
|
| 210 |
-
)
|
| 211 |
-
|
| 212 |
-
if not scoring_results:
|
| 213 |
-
logger.error(f"Scoring failed for {video_path}")
|
| 214 |
-
return False
|
| 215 |
-
|
| 216 |
-
# STEP 4: Write to CSV
|
| 217 |
-
csv_writer.write_row(
|
| 218 |
-
video_path,
|
| 219 |
-
technical_analysis,
|
| 220 |
-
ai_analysis,
|
| 221 |
-
scoring_results,
|
| 222 |
-
config['output']['local_video_dir']
|
| 223 |
-
)
|
| 224 |
-
|
| 225 |
-
# STEP 5: Track progress
|
| 226 |
-
with progress_file.open("a") as pf:
|
| 227 |
-
pf.write(f"{video_filename}\n")
|
| 228 |
-
|
| 229 |
-
logger.info(
|
| 230 |
-
f"✓ Completed: {video_filename} "
|
| 231 |
-
f"(Luxury: {scoring_results['luxury_score']}, "
|
| 232 |
-
f"Final: {scoring_results['final_selection_score']})"
|
| 233 |
-
)
|
| 234 |
-
|
| 235 |
-
# STEP 6: Commit progress (if enabled)
|
| 236 |
-
if commit:
|
| 237 |
-
git_commit_progress(
|
| 238 |
-
config['output']['csv_file'],
|
| 239 |
-
config['logging']['file'],
|
| 240 |
-
progress_file,
|
| 241 |
-
job_index,
|
| 242 |
-
commit
|
| 243 |
-
)
|
| 244 |
-
|
| 245 |
-
return True
|
| 246 |
-
|
| 247 |
-
except Exception as e:
|
| 248 |
-
logger.error(f"Error processing {video_path}: {e}", exc_info=True)
|
| 249 |
-
return False
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
def get_local_videos(local_dir: str) -> List[str]:
|
| 253 |
-
"""
|
| 254 |
-
Get list of all video files in local directory
|
| 255 |
-
|
| 256 |
-
Args:
|
| 257 |
-
local_dir: Base directory to search
|
| 258 |
-
|
| 259 |
-
Returns:
|
| 260 |
-
List of video file paths
|
| 261 |
-
"""
|
| 262 |
-
video_extensions = ['.mp4', '.mov', '.avi', '.mkv', '.webm']
|
| 263 |
-
video_files = []
|
| 264 |
-
|
| 265 |
-
for root, dirs, files in os.walk(local_dir):
|
| 266 |
-
for file in files:
|
| 267 |
-
if any(file.lower().endswith(ext) for ext in video_extensions):
|
| 268 |
-
video_files.append(os.path.join(root, file))
|
| 269 |
-
|
| 270 |
-
return sorted(video_files)
|
| 271 |
-
|
| 272 |
-
|
| 273 |
-
def main():
|
| 274 |
-
"""Main execution flow with progress tracking and parallel job support"""
|
| 275 |
-
parser = argparse.ArgumentParser(
|
| 276 |
-
description='Luxury B-Roll Video Analysis System',
|
| 277 |
-
formatter_class=argparse.RawDescriptionHelpFormatter,
|
| 278 |
-
epilog="""
|
| 279 |
-
Examples:
|
| 280 |
-
# Run without committing
|
| 281 |
-
python main.py
|
| 282 |
-
|
| 283 |
-
# Run and auto-commit progress
|
| 284 |
-
python main.py --commit
|
| 285 |
-
|
| 286 |
-
# Skip download, process existing videos
|
| 287 |
-
python main.py --skip-download --commit
|
| 288 |
-
|
| 289 |
-
# Parallel processing (job 1 of 3)
|
| 290 |
-
python main.py --commit --job-index 0 --total-jobs 3
|
| 291 |
-
|
| 292 |
-
# Test mode
|
| 293 |
-
python main.py --test-mode
|
| 294 |
-
"""
|
| 295 |
-
)
|
| 296 |
-
parser.add_argument(
|
| 297 |
-
'--skip-download',
|
| 298 |
-
action='store_true',
|
| 299 |
-
help='Skip Google Drive download and process existing local videos'
|
| 300 |
-
)
|
| 301 |
-
parser.add_argument(
|
| 302 |
-
'--test-mode',
|
| 303 |
-
action='store_true',
|
| 304 |
-
help='Process only first 3 videos for testing'
|
| 305 |
-
)
|
| 306 |
-
parser.add_argument(
|
| 307 |
-
'--config',
|
| 308 |
-
default='config.yaml',
|
| 309 |
-
help='Path to configuration file'
|
| 310 |
-
)
|
| 311 |
-
parser.add_argument(
|
| 312 |
-
'--commit',
|
| 313 |
-
action='store_true',
|
| 314 |
-
help='Commit and push results to git repository'
|
| 315 |
-
)
|
| 316 |
-
parser.add_argument(
|
| 317 |
-
'--job-index',
|
| 318 |
-
type=int,
|
| 319 |
-
default=None,
|
| 320 |
-
help='Index of this job (0-based) for parallel processing'
|
| 321 |
-
)
|
| 322 |
-
parser.add_argument(
|
| 323 |
-
'--total-jobs',
|
| 324 |
-
type=int,
|
| 325 |
-
default=None,
|
| 326 |
-
help='Total number of parallel jobs'
|
| 327 |
-
)
|
| 328 |
-
|
| 329 |
-
args = parser.parse_args()
|
| 330 |
-
|
| 331 |
-
# Allow environment variables to override args (for GitHub Actions)
|
| 332 |
-
job_index = args.job_index
|
| 333 |
-
total_jobs = args.total_jobs
|
| 334 |
-
|
| 335 |
-
if job_index is None and "JOB_INDEX" in os.environ:
|
| 336 |
-
job_index = int(os.environ["JOB_INDEX"])
|
| 337 |
-
if total_jobs is None and "TOTAL_JOBS" in os.environ:
|
| 338 |
-
total_jobs = int(os.environ["TOTAL_JOBS"])
|
| 339 |
-
|
| 340 |
-
# Load configuration
|
| 341 |
-
print("Loading configuration...")
|
| 342 |
-
config = load_config(args.config)
|
| 343 |
-
|
| 344 |
-
# Make paths relative to video_analyser directory
|
| 345 |
-
script_dir = Path(__file__).parent.absolute()
|
| 346 |
-
if not Path(config['output']['local_video_dir']).is_absolute():
|
| 347 |
-
config['output']['local_video_dir'] = str(script_dir / config['output']['local_video_dir'])
|
| 348 |
-
if not Path(config['output']['csv_file']).is_absolute():
|
| 349 |
-
config['output']['csv_file'] = str(script_dir / config['output']['csv_file'])
|
| 350 |
-
if not Path(config['logging']['file']).is_absolute():
|
| 351 |
-
config['logging']['file'] = str(script_dir / config['logging']['file'])
|
| 352 |
-
|
| 353 |
-
# Setup logging
|
| 354 |
-
setup_logging(config)
|
| 355 |
-
logger = logging.getLogger(__name__)
|
| 356 |
-
|
| 357 |
-
logger.info("=" * 60)
|
| 358 |
-
logger.info("Luxury B-Roll Video Analysis System")
|
| 359 |
-
logger.info("=" * 60)
|
| 360 |
-
|
| 361 |
-
# Validate configuration
|
| 362 |
-
if not validate_config(config):
|
| 363 |
-
sys.exit(1)
|
| 364 |
-
|
| 365 |
-
# Get progress file for this job
|
| 366 |
-
progress_file = get_progress_file(job_index)
|
| 367 |
-
|
| 368 |
-
# Load all analyzed videos (from all jobs)
|
| 369 |
-
analyzed_videos = load_all_analyzed_videos()
|
| 370 |
-
logger.info(f"📊 Found {len(analyzed_videos)} already analyzed videos (skipping these)")
|
| 371 |
-
|
| 372 |
-
# Initialize modules
|
| 373 |
-
logger.info("Initializing modules...")
|
| 374 |
-
video_analyzer = VideoAnalyzer(config)
|
| 375 |
-
ai_analyzer = AIAnalyzer(config)
|
| 376 |
-
scorer = Scorer(config)
|
| 377 |
-
csv_writer = CSVWriter(config)
|
| 378 |
-
|
| 379 |
-
# STEP 1: Get list of videos from Google Drive (don't download yet)
|
| 380 |
-
if not args.skip_download:
|
| 381 |
-
logger.info("STEP 1: Connecting to Google Drive")
|
| 382 |
-
drive_downloader = DriveDownloader(config)
|
| 383 |
-
drive_downloader.authenticate()
|
| 384 |
-
|
| 385 |
-
# Get list of all videos without downloading
|
| 386 |
-
logger.info("Fetching video list from Google Drive...")
|
| 387 |
-
all_video_items = drive_downloader.list_all_videos()
|
| 388 |
-
logger.info(f"Found {len(all_video_items)} videos in Google Drive")
|
| 389 |
-
|
| 390 |
-
else:
|
| 391 |
-
logger.info("STEP 1: Skipping download, using local videos")
|
| 392 |
-
local_dir = config['output']['local_video_dir']
|
| 393 |
-
|
| 394 |
-
if not os.path.exists(local_dir):
|
| 395 |
-
logger.error(f"Local directory does not exist: {local_dir}")
|
| 396 |
-
sys.exit(1)
|
| 397 |
-
|
| 398 |
-
# Get local videos
|
| 399 |
-
video_files = get_local_videos(local_dir)
|
| 400 |
-
logger.info(f"Found {len(video_files)} local videos")
|
| 401 |
-
all_video_items = [{'local_path': vf} for vf in video_files]
|
| 402 |
-
|
| 403 |
-
if not all_video_items:
|
| 404 |
-
logger.error("No videos to process!")
|
| 405 |
-
sys.exit(1)
|
| 406 |
-
|
| 407 |
-
# Filter out already analyzed videos
|
| 408 |
-
analyzed_videos = load_all_analyzed_videos()
|
| 409 |
-
videos_to_process = []
|
| 410 |
-
|
| 411 |
-
for item in all_video_items:
|
| 412 |
-
if 'local_path' in item:
|
| 413 |
-
filename = os.path.basename(item['local_path'])
|
| 414 |
-
else:
|
| 415 |
-
filename = item['name']
|
| 416 |
-
|
| 417 |
-
if filename not in analyzed_videos:
|
| 418 |
-
videos_to_process.append(item)
|
| 419 |
-
|
| 420 |
-
logger.info(
|
| 421 |
-
f"📹 Total videos: {len(all_video_items)}, "
|
| 422 |
-
f"Already analyzed: {len(all_video_items) - len(videos_to_process)}, "
|
| 423 |
-
f"To process: {len(videos_to_process)}"
|
| 424 |
-
)
|
| 425 |
-
|
| 426 |
-
if not videos_to_process:
|
| 427 |
-
logger.info("✅ All videos already analyzed!")
|
| 428 |
-
return
|
| 429 |
-
|
| 430 |
-
# STEP 2-4: Process each video ONE BY ONE
|
| 431 |
-
logger.info(f"\nProcessing {len(videos_to_process)} videos one-by-one...\n")
|
| 432 |
-
|
| 433 |
-
successful = 0
|
| 434 |
-
failed = 0
|
| 435 |
-
|
| 436 |
-
for idx, item in enumerate(videos_to_process, 1):
|
| 437 |
-
try:
|
| 438 |
-
# Download single video if from Drive
|
| 439 |
-
if 'local_path' not in item:
|
| 440 |
-
logger.info(f"[{idx}/{len(videos_to_process)}] Downloading: {item['name']}")
|
| 441 |
-
local_path = drive_downloader.download_single_video(item)
|
| 442 |
-
if not local_path:
|
| 443 |
-
logger.error(f"Failed to download {item['name']}")
|
| 444 |
-
failed += 1
|
| 445 |
-
continue
|
| 446 |
-
else:
|
| 447 |
-
local_path = item['local_path']
|
| 448 |
-
|
| 449 |
-
# Process the video
|
| 450 |
-
logger.info(f"[{idx}/{len(videos_to_process)}] Analyzing: {os.path.basename(local_path)}")
|
| 451 |
-
|
| 452 |
-
if process_single_video(
|
| 453 |
-
local_path, config, video_analyzer, ai_analyzer, scorer, csv_writer,
|
| 454 |
-
progress_file, job_index, args.commit
|
| 455 |
-
):
|
| 456 |
-
successful += 1
|
| 457 |
-
logger.info(f"✅ [{idx}/{len(videos_to_process)}] Completed successfully")
|
| 458 |
-
|
| 459 |
-
# Commit after each successful video
|
| 460 |
-
if args.commit:
|
| 461 |
-
git_commit_progress(
|
| 462 |
-
config['output']['csv_file'],
|
| 463 |
-
config['logging']['file'],
|
| 464 |
-
progress_file,
|
| 465 |
-
job_index,
|
| 466 |
-
args.commit
|
| 467 |
-
)
|
| 468 |
-
else:
|
| 469 |
-
failed += 1
|
| 470 |
-
logger.warning(f"⚠️ [{idx}/{len(videos_to_process)}] Processing failed")
|
| 471 |
-
|
| 472 |
-
except Exception as e:
|
| 473 |
-
logger.error(f"❌ Error processing video {idx}: {e}", exc_info=True)
|
| 474 |
-
failed += 1
|
| 475 |
-
continue
|
| 476 |
-
|
| 477 |
-
# Final commit
|
| 478 |
-
if args.commit and successful > 0:
|
| 479 |
-
git_commit_progress(
|
| 480 |
-
config['output']['csv_file'],
|
| 481 |
-
config['logging']['file'],
|
| 482 |
-
progress_file,
|
| 483 |
-
job_index,
|
| 484 |
-
args.commit
|
| 485 |
-
)
|
| 486 |
-
|
| 487 |
-
# Summary
|
| 488 |
-
job_label = f"Job {job_index}" if job_index is not None else "Analysis"
|
| 489 |
-
logger.info("\n" + "=" * 60)
|
| 490 |
-
logger.info(f"{job_label} COMPLETE")
|
| 491 |
-
logger.info("=" * 60)
|
| 492 |
-
logger.info(f"Total videos: {len(videos_to_process)}")
|
| 493 |
-
logger.info(f"Successful: {successful}")
|
| 494 |
-
logger.info(f"Failed: {failed}")
|
| 495 |
-
logger.info(f"CSV output: {config['output']['csv_file']}")
|
| 496 |
-
logger.info(f"Progress file: {progress_file}")
|
| 497 |
-
logger.info("=" * 60)
|
| 498 |
-
|
| 499 |
-
|
| 500 |
-
if __name__ == '__main__':
|
| 501 |
-
main()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
video_analyser/modules/__init__.py
DELETED
|
@@ -1,19 +0,0 @@
|
|
| 1 |
-
"""
|
| 2 |
-
Luxury Video Analysis System Modules
|
| 3 |
-
"""
|
| 4 |
-
|
| 5 |
-
from .drive_downloader import DriveDownloader
|
| 6 |
-
from .video_analyzer import VideoAnalyzer
|
| 7 |
-
from .ai_analyzer import AIAnalyzer
|
| 8 |
-
from .scorer import Scorer
|
| 9 |
-
from .csv_writer import CSVWriter
|
| 10 |
-
from .git_ops import git_commit_progress
|
| 11 |
-
|
| 12 |
-
__all__ = [
|
| 13 |
-
'DriveDownloader',
|
| 14 |
-
'VideoAnalyzer',
|
| 15 |
-
'AIAnalyzer',
|
| 16 |
-
'Scorer',
|
| 17 |
-
'CSVWriter',
|
| 18 |
-
'git_commit_progress'
|
| 19 |
-
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
video_analyser/modules/ai_analyzer.py
DELETED
|
@@ -1,186 +0,0 @@
|
|
| 1 |
-
"""
|
| 2 |
-
AI Analyzer Module
|
| 3 |
-
Uses Gemini API for intelligent video analysis and scoring
|
| 4 |
-
"""
|
| 5 |
-
|
| 6 |
-
import os
|
| 7 |
-
import logging
|
| 8 |
-
import json
|
| 9 |
-
from typing import Dict, List, Optional
|
| 10 |
-
from pathlib import Path
|
| 11 |
-
import google.generativeai as genai
|
| 12 |
-
|
| 13 |
-
logger = logging.getLogger(__name__)
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
class AIAnalyzer:
|
| 17 |
-
"""Use Gemini AI to analyze luxury video content"""
|
| 18 |
-
|
| 19 |
-
def __init__(self, config: dict):
|
| 20 |
-
"""
|
| 21 |
-
Initialize AI Analyzer
|
| 22 |
-
|
| 23 |
-
Args:
|
| 24 |
-
config: Configuration dictionary with gemini settings
|
| 25 |
-
"""
|
| 26 |
-
# Get API key from environment
|
| 27 |
-
self.api_key = os.getenv('GEMINI_API_KEY')
|
| 28 |
-
if not self.api_key:
|
| 29 |
-
raise ValueError("GEMINI_API_KEY environment variable not set")
|
| 30 |
-
|
| 31 |
-
self.model_name = config['gemini']['model']
|
| 32 |
-
self.prompt_file = config['gemini']['prompt_file']
|
| 33 |
-
|
| 34 |
-
# Load prompt from file
|
| 35 |
-
self.prompt_template = self._load_prompt()
|
| 36 |
-
|
| 37 |
-
# Configure Gemini
|
| 38 |
-
genai.configure(api_key=self.api_key)
|
| 39 |
-
self.model = genai.GenerativeModel(self.model_name)
|
| 40 |
-
|
| 41 |
-
logger.info(f"Initialized Gemini AI with model: {self.model_name}")
|
| 42 |
-
|
| 43 |
-
def _load_prompt(self) -> str:
|
| 44 |
-
"""Load analysis prompt from markdown file"""
|
| 45 |
-
try:
|
| 46 |
-
# Get path relative to this module's location
|
| 47 |
-
module_dir = Path(__file__).parent.parent.absolute()
|
| 48 |
-
prompt_path = Path(self.prompt_file)
|
| 49 |
-
|
| 50 |
-
# If it's just a filename, look in video_analyser directory
|
| 51 |
-
if not prompt_path.is_absolute() and prompt_path.name == self.prompt_file:
|
| 52 |
-
prompt_path = module_dir / self.prompt_file
|
| 53 |
-
|
| 54 |
-
with open(prompt_path, 'r', encoding='utf-8') as f:
|
| 55 |
-
prompt = f.read()
|
| 56 |
-
logger.info(f"Loaded prompt from {prompt_path}")
|
| 57 |
-
return prompt
|
| 58 |
-
except Exception as e:
|
| 59 |
-
logger.error(f"Failed to load prompt file: {e}")
|
| 60 |
-
raise
|
| 61 |
-
|
| 62 |
-
def analyze_with_gemini(self, video_path: str, video_filename: str) -> Optional[Dict]:
|
| 63 |
-
"""
|
| 64 |
-
Analyze video using Gemini AI by uploading the video file directly
|
| 65 |
-
|
| 66 |
-
Args:
|
| 67 |
-
video_path: Path to video file
|
| 68 |
-
video_filename: Name of video file for logging
|
| 69 |
-
|
| 70 |
-
Returns:
|
| 71 |
-
Dictionary with analysis results or None on failure
|
| 72 |
-
"""
|
| 73 |
-
try:
|
| 74 |
-
logger.info(f"Uploading video to Gemini: {video_filename}")
|
| 75 |
-
|
| 76 |
-
# Upload video file to Gemini
|
| 77 |
-
video_file = genai.upload_file(path=video_path)
|
| 78 |
-
|
| 79 |
-
# Wait for video to be processed
|
| 80 |
-
import time
|
| 81 |
-
while video_file.state.name == "PROCESSING":
|
| 82 |
-
time.sleep(1)
|
| 83 |
-
video_file = genai.get_file(video_file.name)
|
| 84 |
-
|
| 85 |
-
if video_file.state.name == "FAILED":
|
| 86 |
-
logger.error(f"Video processing failed for {video_filename}")
|
| 87 |
-
return None
|
| 88 |
-
|
| 89 |
-
logger.info(f"Video uploaded successfully: {video_filename}")
|
| 90 |
-
|
| 91 |
-
# Prepare content for Gemini (prompt + video)
|
| 92 |
-
content = [self.prompt_template, video_file]
|
| 93 |
-
|
| 94 |
-
# Call Gemini API
|
| 95 |
-
response = self.model.generate_content(content)
|
| 96 |
-
|
| 97 |
-
# Clean up uploaded file
|
| 98 |
-
genai.delete_file(video_file.name)
|
| 99 |
-
|
| 100 |
-
# Parse JSON response
|
| 101 |
-
analysis = self.parse_gemini_response(response.text)
|
| 102 |
-
|
| 103 |
-
if analysis:
|
| 104 |
-
logger.info(f"AI analysis completed for {video_filename}")
|
| 105 |
-
return analysis
|
| 106 |
-
else:
|
| 107 |
-
logger.error(f"Failed to parse Gemini response for {video_filename}")
|
| 108 |
-
return None
|
| 109 |
-
|
| 110 |
-
except Exception as e:
|
| 111 |
-
logger.error(f"Error during Gemini analysis for {video_filename}: {e}")
|
| 112 |
-
return None
|
| 113 |
-
|
| 114 |
-
def parse_gemini_response(self, response_text: str) -> Optional[Dict]:
|
| 115 |
-
"""
|
| 116 |
-
Parse and validate Gemini JSON response
|
| 117 |
-
|
| 118 |
-
Args:
|
| 119 |
-
response_text: Raw text response from Gemini
|
| 120 |
-
|
| 121 |
-
Returns:
|
| 122 |
-
Parsed dictionary or None if invalid
|
| 123 |
-
"""
|
| 124 |
-
try:
|
| 125 |
-
# Remove markdown code blocks if present
|
| 126 |
-
text = response_text.strip()
|
| 127 |
-
if text.startswith('```json'):
|
| 128 |
-
text = text[7:]
|
| 129 |
-
if text.startswith('```'):
|
| 130 |
-
text = text[3:]
|
| 131 |
-
if text.endswith('```'):
|
| 132 |
-
text = text[:-3]
|
| 133 |
-
text = text.strip()
|
| 134 |
-
|
| 135 |
-
# Parse JSON
|
| 136 |
-
data = json.loads(text)
|
| 137 |
-
|
| 138 |
-
# Validate required fields (updated for new prompt format)
|
| 139 |
-
required_fields = [
|
| 140 |
-
'category', 'short_description', 'description', 'luxury_score', 'visual_clarity_score',
|
| 141 |
-
'motion_score', 'beat_editing_score', 'recommended_edit_type',
|
| 142 |
-
'ideal_clip_length_seconds', 'energy_level',
|
| 143 |
-
'has_text_or_watermark', 'usable_for_ads', 'final_selection_score'
|
| 144 |
-
]
|
| 145 |
-
|
| 146 |
-
for field in required_fields:
|
| 147 |
-
if field not in data:
|
| 148 |
-
logger.error(f"Missing required field: {field}")
|
| 149 |
-
logger.debug(f"Received data: {json.dumps(data, indent=2)}")
|
| 150 |
-
return None
|
| 151 |
-
|
| 152 |
-
# Validate score ranges (all scores 0-100)
|
| 153 |
-
score_fields = [
|
| 154 |
-
'luxury_score', 'visual_clarity_score', 'motion_score',
|
| 155 |
-
'beat_editing_score', 'final_selection_score'
|
| 156 |
-
]
|
| 157 |
-
for score_field in score_fields:
|
| 158 |
-
if not (0 <= data[score_field] <= 100):
|
| 159 |
-
logger.error(f"Invalid score for {score_field}: {data[score_field]}")
|
| 160 |
-
return None
|
| 161 |
-
|
| 162 |
-
# Add has_faces_visible if Gemini didn't provide it (optional field)
|
| 163 |
-
if 'has_faces_visible' not in data:
|
| 164 |
-
data['has_faces_visible'] = False # Default to false
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
# Validate enums
|
| 168 |
-
valid_edit_types = ['static_hold', 'slow_pan', 'beat_cut', 'pulse_cut', 'transition_clip']
|
| 169 |
-
if data['recommended_edit_type'] not in valid_edit_types:
|
| 170 |
-
logger.error(f"Invalid edit type: {data['recommended_edit_type']}")
|
| 171 |
-
return None
|
| 172 |
-
|
| 173 |
-
valid_energy_levels = ['low', 'medium', 'high']
|
| 174 |
-
if data['energy_level'] not in valid_energy_levels:
|
| 175 |
-
logger.error(f"Invalid energy level: {data['energy_level']}")
|
| 176 |
-
return None
|
| 177 |
-
|
| 178 |
-
return data
|
| 179 |
-
|
| 180 |
-
except json.JSONDecodeError as e:
|
| 181 |
-
logger.error(f"Failed to parse JSON: {e}")
|
| 182 |
-
logger.debug(f"Response text: {response_text}")
|
| 183 |
-
return None
|
| 184 |
-
except Exception as e:
|
| 185 |
-
logger.error(f"Error parsing Gemini response: {e}")
|
| 186 |
-
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
video_analyser/modules/csv_writer.py
DELETED
|
@@ -1,183 +0,0 @@
|
|
| 1 |
-
"""
|
| 2 |
-
CSV Writer Module
|
| 3 |
-
Write video analysis results to CSV in specified format
|
| 4 |
-
"""
|
| 5 |
-
|
| 6 |
-
import csv
|
| 7 |
-
import os
|
| 8 |
-
import logging
|
| 9 |
-
from typing import Dict
|
| 10 |
-
from pathlib import Path
|
| 11 |
-
|
| 12 |
-
logger = logging.getLogger(__name__)
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
class CSVWriter:
|
| 16 |
-
"""Handle CSV output with exact column ordering"""
|
| 17 |
-
|
| 18 |
-
# Exact column order as specified
|
| 19 |
-
COLUMNS = [
|
| 20 |
-
'VIDEO_FILENAME',
|
| 21 |
-
'category',
|
| 22 |
-
'duration_seconds',
|
| 23 |
-
'short_description',
|
| 24 |
-
'description',
|
| 25 |
-
'luxury_score',
|
| 26 |
-
'visual_clarity_score',
|
| 27 |
-
'motion_score',
|
| 28 |
-
'beat_editing_score',
|
| 29 |
-
'recommended_edit_type',
|
| 30 |
-
'ideal_clip_length_seconds',
|
| 31 |
-
'energy_level',
|
| 32 |
-
'has_text_or_watermark',
|
| 33 |
-
'has_faces_visible',
|
| 34 |
-
'usable_for_ads',
|
| 35 |
-
'final_selection_score'
|
| 36 |
-
]
|
| 37 |
-
|
| 38 |
-
def __init__(self, config: dict):
|
| 39 |
-
"""
|
| 40 |
-
Initialize CSV Writer
|
| 41 |
-
|
| 42 |
-
Args:
|
| 43 |
-
config: Configuration dictionary with output settings
|
| 44 |
-
"""
|
| 45 |
-
self.csv_file = config['output']['csv_file']
|
| 46 |
-
self.initialized = False
|
| 47 |
-
|
| 48 |
-
def initialize(self):
|
| 49 |
-
"""Initialize CSV file with headers if it doesn't exist"""
|
| 50 |
-
if not os.path.exists(self.csv_file):
|
| 51 |
-
with open(self.csv_file, 'w', newline='', encoding='utf-8') as f:
|
| 52 |
-
writer = csv.DictWriter(f, fieldnames=self.COLUMNS)
|
| 53 |
-
writer.writeheader()
|
| 54 |
-
logger.info(f"Created CSV file: {self.csv_file}")
|
| 55 |
-
else:
|
| 56 |
-
logger.info(f"Using existing CSV file: {self.csv_file}")
|
| 57 |
-
|
| 58 |
-
self.initialized = True
|
| 59 |
-
|
| 60 |
-
def extract_category(self, video_path: str, local_dir: str) -> str:
|
| 61 |
-
"""
|
| 62 |
-
Extract category from folder structure
|
| 63 |
-
|
| 64 |
-
Args:
|
| 65 |
-
video_path: Full path to video file
|
| 66 |
-
local_dir: Base local directory
|
| 67 |
-
|
| 68 |
-
Returns:
|
| 69 |
-
Category name (folder name)
|
| 70 |
-
"""
|
| 71 |
-
try:
|
| 72 |
-
# Get relative path
|
| 73 |
-
rel_path = os.path.relpath(video_path, local_dir)
|
| 74 |
-
|
| 75 |
-
# Get parent folder name
|
| 76 |
-
parts = Path(rel_path).parts
|
| 77 |
-
|
| 78 |
-
if len(parts) > 1:
|
| 79 |
-
# Category is the first folder
|
| 80 |
-
return parts[0]
|
| 81 |
-
else:
|
| 82 |
-
return "uncategorized"
|
| 83 |
-
|
| 84 |
-
except Exception as e:
|
| 85 |
-
logger.error(f"Error extracting category from {video_path}: {e}")
|
| 86 |
-
return "uncategorized"
|
| 87 |
-
|
| 88 |
-
def validate_row(self, row: Dict) -> bool:
|
| 89 |
-
"""
|
| 90 |
-
Validate row data before writing
|
| 91 |
-
|
| 92 |
-
Args:
|
| 93 |
-
row: Dictionary with row data
|
| 94 |
-
|
| 95 |
-
Returns:
|
| 96 |
-
True if valid, False otherwise
|
| 97 |
-
"""
|
| 98 |
-
# Check all required columns present
|
| 99 |
-
for col in self.COLUMNS:
|
| 100 |
-
if col not in row:
|
| 101 |
-
logger.error(f"Missing column: {col}")
|
| 102 |
-
return False
|
| 103 |
-
|
| 104 |
-
# Validate data types
|
| 105 |
-
try:
|
| 106 |
-
# Numeric fields
|
| 107 |
-
assert isinstance(row['duration_seconds'], (int, float))
|
| 108 |
-
assert isinstance(row['luxury_score'], int)
|
| 109 |
-
assert isinstance(row['visual_clarity_score'], int)
|
| 110 |
-
assert isinstance(row['motion_score'], int)
|
| 111 |
-
assert isinstance(row['beat_editing_score'], int)
|
| 112 |
-
assert isinstance(row['ideal_clip_length_seconds'], (int, float))
|
| 113 |
-
assert isinstance(row['final_selection_score'], int)
|
| 114 |
-
|
| 115 |
-
# String fields
|
| 116 |
-
assert isinstance(row['VIDEO_FILENAME'], str)
|
| 117 |
-
assert isinstance(row['category'], str)
|
| 118 |
-
assert isinstance(row['short_description'], str)
|
| 119 |
-
assert isinstance(row['description'], str)
|
| 120 |
-
assert isinstance(row['recommended_edit_type'], str)
|
| 121 |
-
assert isinstance(row['energy_level'], str)
|
| 122 |
-
|
| 123 |
-
# Boolean fields (should be strings 'true' or 'false')
|
| 124 |
-
assert row['has_text_or_watermark'] in ['true', 'false']
|
| 125 |
-
assert row['has_faces_visible'] in ['true', 'false']
|
| 126 |
-
assert row['usable_for_ads'] in ['true', 'false']
|
| 127 |
-
|
| 128 |
-
return True
|
| 129 |
-
|
| 130 |
-
except AssertionError as e:
|
| 131 |
-
logger.error(f"Row validation failed: {e}")
|
| 132 |
-
return False
|
| 133 |
-
|
| 134 |
-
def write_row(self, video_path: str, technical_analysis: Dict,
|
| 135 |
-
ai_analysis: Dict, scoring_results: Dict, local_dir: str):
|
| 136 |
-
"""
|
| 137 |
-
Write a single row to CSV
|
| 138 |
-
|
| 139 |
-
Args:
|
| 140 |
-
video_path: Path to video file
|
| 141 |
-
technical_analysis: Results from VideoAnalyzer
|
| 142 |
-
ai_analysis: Results from AIAnalyzer
|
| 143 |
-
scoring_results: Results from Scorer
|
| 144 |
-
local_dir: Base local directory for extracting category
|
| 145 |
-
"""
|
| 146 |
-
if not self.initialized:
|
| 147 |
-
self.initialize()
|
| 148 |
-
|
| 149 |
-
# Prepare row data
|
| 150 |
-
row = {
|
| 151 |
-
'VIDEO_FILENAME': os.path.basename(video_path),
|
| 152 |
-
'category': ai_analysis.get('category', 'uncategorized'),
|
| 153 |
-
'duration_seconds': technical_analysis['duration_seconds'],
|
| 154 |
-
'short_description': ai_analysis.get('short_description', ''),
|
| 155 |
-
'description': ai_analysis['description'],
|
| 156 |
-
'luxury_score': scoring_results['luxury_score'],
|
| 157 |
-
'visual_clarity_score': scoring_results['visual_clarity_score'],
|
| 158 |
-
'motion_score': scoring_results['motion_score'],
|
| 159 |
-
'beat_editing_score': scoring_results['beat_editing_score'],
|
| 160 |
-
'recommended_edit_type': ai_analysis['recommended_edit_type'],
|
| 161 |
-
'ideal_clip_length_seconds': scoring_results['ideal_clip_length_seconds'],
|
| 162 |
-
'energy_level': ai_analysis['energy_level'],
|
| 163 |
-
'has_text_or_watermark': 'true' if ai_analysis['has_text_or_watermark'] else 'false',
|
| 164 |
-
'has_faces_visible': 'true' if ai_analysis.get('has_faces_visible', False) else 'false',
|
| 165 |
-
'usable_for_ads': 'true' if ai_analysis['usable_for_ads'] else 'false',
|
| 166 |
-
'final_selection_score': scoring_results['final_selection_score']
|
| 167 |
-
}
|
| 168 |
-
|
| 169 |
-
# Validate before writing
|
| 170 |
-
if not self.validate_row(row):
|
| 171 |
-
logger.error(f"Skipping invalid row for {video_path}")
|
| 172 |
-
return
|
| 173 |
-
|
| 174 |
-
# Write to CSV
|
| 175 |
-
try:
|
| 176 |
-
with open(self.csv_file, 'a', newline='', encoding='utf-8') as f:
|
| 177 |
-
writer = csv.DictWriter(f, fieldnames=self.COLUMNS)
|
| 178 |
-
writer.writerow(row)
|
| 179 |
-
|
| 180 |
-
logger.debug(f"Wrote row to CSV for {os.path.basename(video_path)}")
|
| 181 |
-
|
| 182 |
-
except Exception as e:
|
| 183 |
-
logger.error(f"Error writing row to CSV: {e}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
video_analyser/modules/drive_downloader.py
DELETED
|
@@ -1,245 +0,0 @@
|
|
| 1 |
-
"""
|
| 2 |
-
Google Drive Downloader Module
|
| 3 |
-
Downloads videos from Google Drive maintaining folder structure
|
| 4 |
-
"""
|
| 5 |
-
|
| 6 |
-
import os
|
| 7 |
-
import io
|
| 8 |
-
import logging
|
| 9 |
-
from pathlib import Path
|
| 10 |
-
from typing import List, Dict, Optional
|
| 11 |
-
from tqdm import tqdm
|
| 12 |
-
|
| 13 |
-
from google.auth.transport.requests import Request
|
| 14 |
-
from google.oauth2.credentials import Credentials
|
| 15 |
-
from googleapiclient.discovery import build
|
| 16 |
-
from googleapiclient.http import MediaIoBaseDownload
|
| 17 |
-
|
| 18 |
-
logger = logging.getLogger(__name__)
|
| 19 |
-
|
| 20 |
-
# Note: drive.file only allows access to files created by the app
|
| 21 |
-
# For accessing existing shared folders, we need broader scope
|
| 22 |
-
SCOPES = [
|
| 23 |
-
'https://www.googleapis.com/auth/drive.readonly'
|
| 24 |
-
]
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
class DriveDownloader:
|
| 28 |
-
"""Handle Google Drive authentication and video downloads"""
|
| 29 |
-
|
| 30 |
-
def __init__(self, config: dict):
|
| 31 |
-
"""
|
| 32 |
-
Initialize Drive Downloader
|
| 33 |
-
|
| 34 |
-
Args:
|
| 35 |
-
config: Configuration dictionary with google_drive settings
|
| 36 |
-
"""
|
| 37 |
-
self.folder_id = config['google_drive']['folder_id']
|
| 38 |
-
self.local_dir = config['output']['local_video_dir']
|
| 39 |
-
self.service = None
|
| 40 |
-
|
| 41 |
-
def authenticate(self):
|
| 42 |
-
"""Authenticate with Google Drive API using environment variables"""
|
| 43 |
-
# Get OAuth credentials from environment
|
| 44 |
-
client_id = os.getenv('SERVER_GOOGLE_CLIENT_ID')
|
| 45 |
-
client_secret = os.getenv('SERVER_GOOGLE_CLIENT_SECRET')
|
| 46 |
-
|
| 47 |
-
# Try Drive-specific token first, fall back to server token
|
| 48 |
-
refresh_token = os.getenv('SERVER_GOOGLE_REFRESH_TOKEN')
|
| 49 |
-
|
| 50 |
-
if not all([client_id, client_secret, refresh_token]):
|
| 51 |
-
raise ValueError(
|
| 52 |
-
"Missing Google OAuth credentials. Required environment variables:\n"
|
| 53 |
-
" - SERVER_GOOGLE_CLIENT_ID\n"
|
| 54 |
-
" - SERVER_GOOGLE_CLIENT_SECRET\n"
|
| 55 |
-
"Run 'python video_analyser/get_refresh_token.py' to get a Drive token."
|
| 56 |
-
)
|
| 57 |
-
|
| 58 |
-
# Create credentials from environment variables
|
| 59 |
-
creds = Credentials(
|
| 60 |
-
token=None,
|
| 61 |
-
refresh_token=refresh_token,
|
| 62 |
-
token_uri='https://oauth2.googleapis.com/token',
|
| 63 |
-
client_id=client_id,
|
| 64 |
-
client_secret=client_secret,
|
| 65 |
-
scopes=SCOPES
|
| 66 |
-
)
|
| 67 |
-
|
| 68 |
-
# Refresh the token
|
| 69 |
-
creds.refresh(Request())
|
| 70 |
-
|
| 71 |
-
self.service = build('drive', 'v3', credentials=creds)
|
| 72 |
-
logger.info("Successfully authenticated with Google Drive")
|
| 73 |
-
|
| 74 |
-
def list_folder_structure(self, folder_id: Optional[str] = None) -> List[Dict]:
|
| 75 |
-
"""
|
| 76 |
-
List all files and folders recursively
|
| 77 |
-
|
| 78 |
-
Args:
|
| 79 |
-
folder_id: Folder ID to list (defaults to root folder)
|
| 80 |
-
|
| 81 |
-
Returns:
|
| 82 |
-
List of file/folder metadata dictionaries
|
| 83 |
-
"""
|
| 84 |
-
if folder_id is None:
|
| 85 |
-
folder_id = self.folder_id
|
| 86 |
-
|
| 87 |
-
items = []
|
| 88 |
-
|
| 89 |
-
# Query for all items in folder
|
| 90 |
-
query = f"'{folder_id}' in parents and trashed=false"
|
| 91 |
-
results = self.service.files().list(
|
| 92 |
-
q=query,
|
| 93 |
-
fields="files(id, name, mimeType, size)",
|
| 94 |
-
pageSize=1000
|
| 95 |
-
).execute()
|
| 96 |
-
|
| 97 |
-
files = results.get('files', [])
|
| 98 |
-
|
| 99 |
-
for file in files:
|
| 100 |
-
if file['mimeType'] == 'application/vnd.google-apps.folder':
|
| 101 |
-
# Recursively get folder contents
|
| 102 |
-
subfolder_items = self.list_folder_structure(file['id'])
|
| 103 |
-
items.extend([
|
| 104 |
-
{**item, 'path': f"{file['name']}/{item['path']}"}
|
| 105 |
-
for item in subfolder_items
|
| 106 |
-
])
|
| 107 |
-
else:
|
| 108 |
-
# Add file with relative path
|
| 109 |
-
items.append({
|
| 110 |
-
'id': file['id'],
|
| 111 |
-
'name': file['name'],
|
| 112 |
-
'path': file['name'],
|
| 113 |
-
'size': int(file.get('size', 0)),
|
| 114 |
-
'mimeType': file['mimeType']
|
| 115 |
-
})
|
| 116 |
-
|
| 117 |
-
return items
|
| 118 |
-
|
| 119 |
-
def download_file(self, file_id: str, destination: str) -> bool:
|
| 120 |
-
"""
|
| 121 |
-
Download a single file from Google Drive
|
| 122 |
-
|
| 123 |
-
Args:
|
| 124 |
-
file_id: Google Drive file ID
|
| 125 |
-
destination: Local file path to save to
|
| 126 |
-
|
| 127 |
-
Returns:
|
| 128 |
-
True if successful, False otherwise
|
| 129 |
-
"""
|
| 130 |
-
try:
|
| 131 |
-
# Create parent directory if needed
|
| 132 |
-
os.makedirs(os.path.dirname(destination), exist_ok=True)
|
| 133 |
-
|
| 134 |
-
# Check if file already exists
|
| 135 |
-
if os.path.exists(destination):
|
| 136 |
-
logger.debug(f"Skipping existing file: {destination}")
|
| 137 |
-
return True
|
| 138 |
-
|
| 139 |
-
# Download file
|
| 140 |
-
request = self.service.files().get_media(fileId=file_id)
|
| 141 |
-
fh = io.BytesIO()
|
| 142 |
-
|
| 143 |
-
downloader = MediaIoBaseDownload(fh, request)
|
| 144 |
-
done = False
|
| 145 |
-
|
| 146 |
-
while not done:
|
| 147 |
-
status, done = downloader.next_chunk()
|
| 148 |
-
|
| 149 |
-
# Write to file
|
| 150 |
-
with open(destination, 'wb') as f:
|
| 151 |
-
fh.seek(0)
|
| 152 |
-
f.write(fh.read())
|
| 153 |
-
|
| 154 |
-
logger.debug(f"Downloaded: {destination}")
|
| 155 |
-
return True
|
| 156 |
-
|
| 157 |
-
except Exception as e:
|
| 158 |
-
logger.error(f"Failed to download {destination}: {e}")
|
| 159 |
-
return False
|
| 160 |
-
|
| 161 |
-
def download_all(self) -> List[str]:
|
| 162 |
-
"""
|
| 163 |
-
Download all videos from Drive maintaining folder structure
|
| 164 |
-
|
| 165 |
-
Returns:
|
| 166 |
-
List of local file paths that were downloaded
|
| 167 |
-
"""
|
| 168 |
-
if not self.service:
|
| 169 |
-
self.authenticate()
|
| 170 |
-
|
| 171 |
-
logger.info(f"Listing files in Google Drive folder...")
|
| 172 |
-
items = self.list_folder_structure()
|
| 173 |
-
|
| 174 |
-
# Filter for video files only
|
| 175 |
-
video_extensions = ['.mp4', '.mov', '.avi', '.mkv', '.webm']
|
| 176 |
-
video_files = [
|
| 177 |
-
item for item in items
|
| 178 |
-
if any(item['name'].lower().endswith(ext) for ext in video_extensions)
|
| 179 |
-
]
|
| 180 |
-
|
| 181 |
-
logger.info(f"Found {len(video_files)} video files to download")
|
| 182 |
-
|
| 183 |
-
downloaded_files = []
|
| 184 |
-
|
| 185 |
-
# Download with progress bar
|
| 186 |
-
with tqdm(total=len(video_files), desc="Downloading videos") as pbar:
|
| 187 |
-
for item in video_files:
|
| 188 |
-
local_path = os.path.join(self.local_dir, item['path'])
|
| 189 |
-
|
| 190 |
-
if self.download_file(item['id'], local_path):
|
| 191 |
-
downloaded_files.append(local_path)
|
| 192 |
-
|
| 193 |
-
pbar.update(1)
|
| 194 |
-
|
| 195 |
-
logger.info(f"Successfully downloaded {len(downloaded_files)} videos")
|
| 196 |
-
return downloaded_files
|
| 197 |
-
|
| 198 |
-
def list_all_videos(self) -> List[Dict]:
|
| 199 |
-
"""
|
| 200 |
-
List all videos from Google Drive without downloading
|
| 201 |
-
|
| 202 |
-
Returns:
|
| 203 |
-
List of video items with metadata (id, name, path)
|
| 204 |
-
"""
|
| 205 |
-
if not self.service:
|
| 206 |
-
self.authenticate()
|
| 207 |
-
|
| 208 |
-
logger.info("Listing files in Google Drive folder...")
|
| 209 |
-
items = self.list_folder_structure()
|
| 210 |
-
|
| 211 |
-
# Filter for video files only
|
| 212 |
-
video_extensions = ['.mp4', '.mov', '.avi', '.mkv', '.webm']
|
| 213 |
-
video_files = [
|
| 214 |
-
item for item in items
|
| 215 |
-
if any(item['name'].lower().endswith(ext) for ext in video_extensions)
|
| 216 |
-
]
|
| 217 |
-
|
| 218 |
-
logger.info(f"Found {len(video_files)} video files")
|
| 219 |
-
return video_files
|
| 220 |
-
|
| 221 |
-
def download_single_video(self, item: Dict) -> Optional[str]:
|
| 222 |
-
"""
|
| 223 |
-
Download a single video from Google Drive
|
| 224 |
-
|
| 225 |
-
Args:
|
| 226 |
-
item: Video item dict with 'id', 'name', 'path'
|
| 227 |
-
|
| 228 |
-
Returns:
|
| 229 |
-
Local path to downloaded file or None on failure
|
| 230 |
-
"""
|
| 231 |
-
local_path = os.path.join(self.local_dir, item['path'])
|
| 232 |
-
|
| 233 |
-
# Skip if already exists
|
| 234 |
-
if os.path.exists(local_path):
|
| 235 |
-
logger.debug(f"Video already exists locally: {item['name']}")
|
| 236 |
-
return local_path
|
| 237 |
-
|
| 238 |
-
# Create directory
|
| 239 |
-
os.makedirs(os.path.dirname(local_path), exist_ok=True)
|
| 240 |
-
|
| 241 |
-
# Download file
|
| 242 |
-
if self.download_file(item['id'], local_path):
|
| 243 |
-
return local_path
|
| 244 |
-
else:
|
| 245 |
-
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
video_analyser/modules/git_ops.py
DELETED
|
@@ -1,121 +0,0 @@
|
|
| 1 |
-
"""
|
| 2 |
-
Git operations for progress tracking
|
| 3 |
-
"""
|
| 4 |
-
|
| 5 |
-
import os
|
| 6 |
-
import subprocess
|
| 7 |
-
import logging
|
| 8 |
-
import time
|
| 9 |
-
import random
|
| 10 |
-
from pathlib import Path
|
| 11 |
-
|
| 12 |
-
logger = logging.getLogger(__name__)
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
def git_commit_progress(
|
| 16 |
-
csv_file: str,
|
| 17 |
-
log_file: str,
|
| 18 |
-
progress_file: Path,
|
| 19 |
-
job_index: int = None,
|
| 20 |
-
commit: bool = False
|
| 21 |
-
):
|
| 22 |
-
"""
|
| 23 |
-
Commit progress for a specific job with retry logic for parallel environments.
|
| 24 |
-
|
| 25 |
-
Args:
|
| 26 |
-
csv_file: Path to CSV file to commit
|
| 27 |
-
log_file: Path to log file to commit
|
| 28 |
-
progress_file: Path to progress tracking file
|
| 29 |
-
job_index: Job index for parallel processing
|
| 30 |
-
commit: Whether to actually commit (dry-run if False)
|
| 31 |
-
"""
|
| 32 |
-
if not commit:
|
| 33 |
-
logger.info("ℹ️ Skipping git commit (use --commit flag to enable).")
|
| 34 |
-
return
|
| 35 |
-
|
| 36 |
-
if not progress_file.exists():
|
| 37 |
-
logger.info(f"ℹ️ No progress file found. Nothing to commit.")
|
| 38 |
-
return
|
| 39 |
-
|
| 40 |
-
try:
|
| 41 |
-
# Get branch from environment or default to feature/video-revamp
|
| 42 |
-
branch = os.getenv('GIT_BRANCH', 'feature/video-revamp')
|
| 43 |
-
max_retries = 3
|
| 44 |
-
|
| 45 |
-
job_label = f"job {job_index}" if job_index is not None else "main"
|
| 46 |
-
|
| 47 |
-
# 1. Ensure we're on the correct branch
|
| 48 |
-
logger.info(f"Git: Ensuring we are on branch '{branch}' for {job_label}...")
|
| 49 |
-
subprocess.run(["git", "fetch", "origin", branch], check=True, capture_output=True)
|
| 50 |
-
subprocess.run(
|
| 51 |
-
["git", "checkout", "-B", branch, f"origin/{branch}"],
|
| 52 |
-
check=True,
|
| 53 |
-
capture_output=True
|
| 54 |
-
)
|
| 55 |
-
|
| 56 |
-
# 2. Stage and commit local changes (force add to include log files)
|
| 57 |
-
files_to_commit = [csv_file, str(progress_file)]
|
| 58 |
-
subprocess.run(["git", "add"] + files_to_commit, check=True)
|
| 59 |
-
|
| 60 |
-
commit_msg = f"🎬 Video analysis progress - {job_label}" if job_index is not None else "🎬 Video analysis progress"
|
| 61 |
-
|
| 62 |
-
commit_result = subprocess.run(
|
| 63 |
-
["git", "commit", "-m", commit_msg],
|
| 64 |
-
capture_output=True,
|
| 65 |
-
text=True
|
| 66 |
-
)
|
| 67 |
-
|
| 68 |
-
# Check if commit was successful or if nothing to commit
|
| 69 |
-
if commit_result.returncode != 0:
|
| 70 |
-
if "nothing to commit" in commit_result.stdout or "nothing to commit" in commit_result.stderr:
|
| 71 |
-
logger.info(f"ℹ️ No new progress to commit for {job_label}.")
|
| 72 |
-
return
|
| 73 |
-
else:
|
| 74 |
-
raise subprocess.CalledProcessError(
|
| 75 |
-
commit_result.returncode,
|
| 76 |
-
cmd=commit_result.args,
|
| 77 |
-
output=commit_result.stdout,
|
| 78 |
-
stderr=commit_result.stderr
|
| 79 |
-
)
|
| 80 |
-
|
| 81 |
-
# 3. Push with retry loop for race conditions
|
| 82 |
-
for attempt in range(max_retries):
|
| 83 |
-
try:
|
| 84 |
-
# Pull with rebase before push
|
| 85 |
-
logger.info(f"Git: Pulling with rebase (Attempt {attempt + 1}/{max_retries})...")
|
| 86 |
-
subprocess.run(
|
| 87 |
-
["git", "pull", "--rebase", "origin", branch],
|
| 88 |
-
check=True,
|
| 89 |
-
capture_output=True
|
| 90 |
-
)
|
| 91 |
-
|
| 92 |
-
# Attempt push
|
| 93 |
-
logger.info(f"Git: Pushing to remote (Attempt {attempt + 1}/{max_retries})...")
|
| 94 |
-
subprocess.run(
|
| 95 |
-
["git", "push", "origin", branch],
|
| 96 |
-
check=True,
|
| 97 |
-
timeout=45
|
| 98 |
-
)
|
| 99 |
-
|
| 100 |
-
logger.info(f"✓ Committed progress successfully for {job_label}")
|
| 101 |
-
return
|
| 102 |
-
|
| 103 |
-
except (subprocess.CalledProcessError, subprocess.TimeoutExpired) as e:
|
| 104 |
-
if attempt < max_retries - 1:
|
| 105 |
-
# Randomized exponential backoff
|
| 106 |
-
sleep_duration = random.uniform(2, 5) * (attempt + 1)
|
| 107 |
-
logger.warning(
|
| 108 |
-
f"Push failed for {job_label}. Retrying in {sleep_duration:.2f} seconds..."
|
| 109 |
-
)
|
| 110 |
-
time.sleep(sleep_duration)
|
| 111 |
-
else:
|
| 112 |
-
logger.error(
|
| 113 |
-
f"❌ Failed to push progress for {job_label} after {max_retries} attempts."
|
| 114 |
-
)
|
| 115 |
-
raise
|
| 116 |
-
|
| 117 |
-
except subprocess.CalledProcessError as e:
|
| 118 |
-
error_message = e.stderr if e.stderr else e.stdout
|
| 119 |
-
logger.error(f"❌ Git command failed for {job_label}: {e.cmd}\nError: {error_message}")
|
| 120 |
-
subprocess.run(["git", "reset", "--hard"])
|
| 121 |
-
raise
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
video_analyser/modules/scorer.py
DELETED
|
@@ -1,199 +0,0 @@
|
|
| 1 |
-
"""
|
| 2 |
-
Scorer Module
|
| 3 |
-
Calculate final selection scores using weighted formulas
|
| 4 |
-
"""
|
| 5 |
-
|
| 6 |
-
import logging
|
| 7 |
-
from typing import Dict
|
| 8 |
-
|
| 9 |
-
logger = logging.getLogger(__name__)
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
class Scorer:
|
| 13 |
-
"""Calculate weighted scores for video selection"""
|
| 14 |
-
|
| 15 |
-
def __init__(self, config: dict):
|
| 16 |
-
"""
|
| 17 |
-
Initialize Scorer
|
| 18 |
-
|
| 19 |
-
Args:
|
| 20 |
-
config: Configuration dictionary with scoring weights
|
| 21 |
-
"""
|
| 22 |
-
self.luxury_weight = config['scoring']['luxury_weight']
|
| 23 |
-
self.visual_clarity_weight = config['scoring']['visual_clarity_weight']
|
| 24 |
-
self.beat_editing_weight = config['scoring']['beat_editing_weight']
|
| 25 |
-
self.motion_weight = config['scoring']['motion_weight']
|
| 26 |
-
self.usability_weight = config['scoring']['usability_weight']
|
| 27 |
-
|
| 28 |
-
# Validate weights sum to 1.0
|
| 29 |
-
total = sum([
|
| 30 |
-
self.luxury_weight,
|
| 31 |
-
self.visual_clarity_weight,
|
| 32 |
-
self.beat_editing_weight,
|
| 33 |
-
self.motion_weight,
|
| 34 |
-
self.usability_weight
|
| 35 |
-
])
|
| 36 |
-
|
| 37 |
-
if abs(total - 1.0) > 0.01:
|
| 38 |
-
logger.warning(f"Scoring weights sum to {total}, not 1.0. Normalizing...")
|
| 39 |
-
norm_factor = 1.0 / total
|
| 40 |
-
self.luxury_weight *= norm_factor
|
| 41 |
-
self.visual_clarity_weight *= norm_factor
|
| 42 |
-
self.beat_editing_weight *= norm_factor
|
| 43 |
-
self.motion_weight *= norm_factor
|
| 44 |
-
self.usability_weight *= norm_factor
|
| 45 |
-
|
| 46 |
-
def validate_scores(self, scores: Dict) -> bool:
|
| 47 |
-
"""
|
| 48 |
-
Validate that all scores are within 0-100 range
|
| 49 |
-
|
| 50 |
-
Args:
|
| 51 |
-
scores: Dictionary with score values
|
| 52 |
-
|
| 53 |
-
Returns:
|
| 54 |
-
True if all scores valid, False otherwise
|
| 55 |
-
"""
|
| 56 |
-
score_fields = [
|
| 57 |
-
'luxury_score',
|
| 58 |
-
'visual_clarity_score',
|
| 59 |
-
'motion_score',
|
| 60 |
-
'beat_editing_score'
|
| 61 |
-
]
|
| 62 |
-
|
| 63 |
-
for field in score_fields:
|
| 64 |
-
if field not in scores:
|
| 65 |
-
logger.error(f"Missing score field: {field}")
|
| 66 |
-
return False
|
| 67 |
-
|
| 68 |
-
if not (0 <= scores[field] <= 100):
|
| 69 |
-
logger.error(f"Invalid score for {field}: {scores[field]}")
|
| 70 |
-
return False
|
| 71 |
-
|
| 72 |
-
return True
|
| 73 |
-
|
| 74 |
-
def calculate_usability_bonus(self, has_text: bool, has_faces: bool,
|
| 75 |
-
usable_for_ads: bool) -> int:
|
| 76 |
-
"""
|
| 77 |
-
Calculate usability bonus score
|
| 78 |
-
|
| 79 |
-
Args:
|
| 80 |
-
has_text: Whether video has text/watermarks
|
| 81 |
-
has_faces: Whether video has visible faces
|
| 82 |
-
usable_for_ads: Whether video is commercially safe
|
| 83 |
-
|
| 84 |
-
Returns:
|
| 85 |
-
Usability bonus score (0-100)
|
| 86 |
-
"""
|
| 87 |
-
score = 100
|
| 88 |
-
|
| 89 |
-
# Penalize text/watermarks
|
| 90 |
-
if has_text:
|
| 91 |
-
score -= 40
|
| 92 |
-
|
| 93 |
-
# Slight penalty for faces (can limit usage scenarios)
|
| 94 |
-
if has_faces:
|
| 95 |
-
score -= 20
|
| 96 |
-
|
| 97 |
-
# Bonus for ad-safe content
|
| 98 |
-
if not usable_for_ads:
|
| 99 |
-
score -= 30
|
| 100 |
-
|
| 101 |
-
return max(0, min(100, score))
|
| 102 |
-
|
| 103 |
-
def determine_ideal_clip_length(self, motion_score: int, energy_level: str,
|
| 104 |
-
duration: float) -> float:
|
| 105 |
-
"""
|
| 106 |
-
Determine ideal clip length based on motion and energy
|
| 107 |
-
|
| 108 |
-
Args:
|
| 109 |
-
motion_score: Motion score (0-100)
|
| 110 |
-
energy_level: Energy level (low/medium/high)
|
| 111 |
-
duration: Total video duration
|
| 112 |
-
|
| 113 |
-
Returns:
|
| 114 |
-
Ideal clip length in seconds
|
| 115 |
-
"""
|
| 116 |
-
# Base lengths by energy level
|
| 117 |
-
base_lengths = {
|
| 118 |
-
'low': 2.0,
|
| 119 |
-
'medium': 1.5,
|
| 120 |
-
'high': 1.0
|
| 121 |
-
}
|
| 122 |
-
|
| 123 |
-
base = base_lengths.get(energy_level, 1.5)
|
| 124 |
-
|
| 125 |
-
# Adjust based on motion
|
| 126 |
-
if motion_score > 70:
|
| 127 |
-
# High motion = shorter clips work better
|
| 128 |
-
base *= 0.8
|
| 129 |
-
elif motion_score < 30:
|
| 130 |
-
# Low motion = can hold longer
|
| 131 |
-
base *= 1.3
|
| 132 |
-
|
| 133 |
-
# Cap at total duration
|
| 134 |
-
return min(base, duration)
|
| 135 |
-
|
| 136 |
-
def calculate_final_score(self, technical_analysis: Dict,
|
| 137 |
-
ai_analysis: Dict) -> Dict:
|
| 138 |
-
"""
|
| 139 |
-
Calculate final selection score using weighted formula
|
| 140 |
-
|
| 141 |
-
Args:
|
| 142 |
-
technical_analysis: Results from VideoAnalyzer (duration only)
|
| 143 |
-
ai_analysis: Results from AIAnalyzer (all scores from Gemini)
|
| 144 |
-
|
| 145 |
-
Returns:
|
| 146 |
-
Dictionary with final score and all component scores
|
| 147 |
-
"""
|
| 148 |
-
# Extract scores from AI analysis (Gemini provides everything)
|
| 149 |
-
luxury_score = ai_analysis['luxury_score']
|
| 150 |
-
visual_clarity_score = ai_analysis['visual_clarity_score']
|
| 151 |
-
beat_editing_score = ai_analysis['beat_editing_score']
|
| 152 |
-
motion_score = ai_analysis['motion_score']
|
| 153 |
-
|
| 154 |
-
# Calculate usability bonus
|
| 155 |
-
usability_score = self.calculate_usability_bonus(
|
| 156 |
-
ai_analysis['has_text_or_watermark'],
|
| 157 |
-
ai_analysis.get('has_faces_visible', False),
|
| 158 |
-
ai_analysis['usable_for_ads']
|
| 159 |
-
)
|
| 160 |
-
|
| 161 |
-
# Validate scores
|
| 162 |
-
all_scores = {
|
| 163 |
-
'luxury_score': luxury_score,
|
| 164 |
-
'visual_clarity_score': visual_clarity_score,
|
| 165 |
-
'motion_score': motion_score,
|
| 166 |
-
'beat_editing_score': beat_editing_score
|
| 167 |
-
}
|
| 168 |
-
|
| 169 |
-
if not self.validate_scores(all_scores):
|
| 170 |
-
logger.error("Score validation failed")
|
| 171 |
-
return None
|
| 172 |
-
|
| 173 |
-
# Calculate weighted final score
|
| 174 |
-
final_score = (
|
| 175 |
-
luxury_score * self.luxury_weight +
|
| 176 |
-
visual_clarity_score * self.visual_clarity_weight +
|
| 177 |
-
beat_editing_score * self.beat_editing_weight +
|
| 178 |
-
motion_score * self.motion_weight +
|
| 179 |
-
usability_score * self.usability_weight
|
| 180 |
-
)
|
| 181 |
-
|
| 182 |
-
# Round to integer
|
| 183 |
-
final_score = int(round(final_score))
|
| 184 |
-
|
| 185 |
-
# Determine ideal clip length
|
| 186 |
-
ideal_clip_length = self.determine_ideal_clip_length(
|
| 187 |
-
motion_score,
|
| 188 |
-
ai_analysis['energy_level'],
|
| 189 |
-
technical_analysis['duration_seconds']
|
| 190 |
-
)
|
| 191 |
-
|
| 192 |
-
return {
|
| 193 |
-
'luxury_score': luxury_score,
|
| 194 |
-
'visual_clarity_score': visual_clarity_score,
|
| 195 |
-
'motion_score': motion_score,
|
| 196 |
-
'beat_editing_score': beat_editing_score,
|
| 197 |
-
'final_selection_score': final_score,
|
| 198 |
-
'ideal_clip_length_seconds': round(ideal_clip_length, 1)
|
| 199 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
video_analyser/modules/video_analyzer.py
DELETED
|
@@ -1,66 +0,0 @@
|
|
| 1 |
-
"""
|
| 2 |
-
Video Analyzer Module
|
| 3 |
-
Extracts basic technical metadata from video files
|
| 4 |
-
"""
|
| 5 |
-
|
| 6 |
-
import cv2
|
| 7 |
-
import logging
|
| 8 |
-
from typing import Dict
|
| 9 |
-
|
| 10 |
-
logger = logging.getLogger(__name__)
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
class VideoAnalyzer:
|
| 14 |
-
"""Extract basic technical metadata from video files"""
|
| 15 |
-
|
| 16 |
-
def __init__(self, config: dict):
|
| 17 |
-
"""
|
| 18 |
-
Initialize Video Analyzer
|
| 19 |
-
|
| 20 |
-
Args:
|
| 21 |
-
config: Configuration dictionary
|
| 22 |
-
"""
|
| 23 |
-
pass # No config needed for simple duration extraction
|
| 24 |
-
|
| 25 |
-
def get_video_duration(self, video_path: str) -> float:
|
| 26 |
-
"""
|
| 27 |
-
Get video duration in seconds
|
| 28 |
-
|
| 29 |
-
Args:
|
| 30 |
-
video_path: Path to video file
|
| 31 |
-
|
| 32 |
-
Returns:
|
| 33 |
-
Duration in seconds
|
| 34 |
-
"""
|
| 35 |
-
try:
|
| 36 |
-
cap = cv2.VideoCapture(video_path)
|
| 37 |
-
fps = cap.get(cv2.CAP_PROP_FPS)
|
| 38 |
-
frame_count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
|
| 39 |
-
cap.release()
|
| 40 |
-
|
| 41 |
-
if fps > 0:
|
| 42 |
-
duration = frame_count / fps
|
| 43 |
-
return round(duration, 2)
|
| 44 |
-
else:
|
| 45 |
-
logger.warning(f"Could not determine FPS for {video_path}")
|
| 46 |
-
return 0.0
|
| 47 |
-
|
| 48 |
-
except Exception as e:
|
| 49 |
-
logger.error(f"Error getting duration for {video_path}: {e}")
|
| 50 |
-
return 0.0
|
| 51 |
-
|
| 52 |
-
def analyze_video(self, video_path: str) -> Dict:
|
| 53 |
-
"""
|
| 54 |
-
Perform basic technical analysis of video
|
| 55 |
-
|
| 56 |
-
Args:
|
| 57 |
-
video_path: Path to video file
|
| 58 |
-
|
| 59 |
-
Returns:
|
| 60 |
-
Dictionary with basic analysis results
|
| 61 |
-
"""
|
| 62 |
-
logger.info(f"Analyzing video: {video_path}")
|
| 63 |
-
|
| 64 |
-
return {
|
| 65 |
-
'duration_seconds': self.get_video_duration(video_path)
|
| 66 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
video_analyser/progress/analyzed_videos.txt
DELETED
|
@@ -1,1028 +0,0 @@
|
|
| 1 |
-
Copy of SnapInsta.to_AQM-w-YmCepAaXIWl7Frs10cKbdX1P-__zSTTzh7j0td9hh6ebAJWA409L_fJNcgiWctukdKl0ubkT2tNOxgjyRdOXs3nPBgMAgWV90.mp4
|
| 2 |
-
Copy of SnapInsta.to_AQM2gxxnIBP-Ah5vZhdj-LKAZSNJYynCBrXUc7PmC5N43Nhcambj7NOYu0uN9GD8JtEe6V7QdHaGjxLB2H26XycERUR536H_CG4o8NI.mp4
|
| 3 |
-
Copy of SnapInsta.to_AQMKNeqwx4_JXcTg8QX5WQMDD1UJcuxqnKgiPDRi0UYoKjJkppR5AFMmLiQ66ZiLXWi5Db8Kk9chsM6ZShLMgtt5EdoSEShH7aPnfS8.mp4
|
| 4 |
-
Copy of SnapInsta.to_AQMMtHxghW_GerwQOpnEAYGYkAGqj5kQ7bddgin5kvyHny4Ogn0vuANSJemeNdwntJbLE60E3bP6V01c4G4h5CgqDbuFqvlmb1yfqKY.mp4
|
| 5 |
-
Copy of SnapInsta.to_AQMnfLf26HWnswIIriCayFTt9LyRvdv6CuIpnlzBNZkjbEAwhp-31XiHZFL15ybWSq0jyGfW8OlC2XRDXxOrM2sivaFd7esAKuZRpmk.mp4
|
| 6 |
-
Copy of SnapInsta.to_AQMJ3dwasTBsR6jCGwIhuDu4-uC1K5x7tVNwtF5DUj_uLlCnYAMC-H45aFDgCsYf-0Wjj2cvCMza_SYh1W8vgB155t-X7oLq--Rxexc.mp4
|
| 7 |
-
Copy of SnapInsta.to_AQMhX9lMqY0zpKz4doF8I_MQaDDBKVrNtEshVWGuvOQeb-lW8oMK_L7mlGt3OqqfuzQmECl_nO9Cw-wO6f9mkEV2NHbW7RoxgJA8BoI.mp4
|
| 8 |
-
Copy of SnapInsta.to_AQMJB1N4IAxMjpDfH5LNe3m14w9dYoNdsquK-3IraJoEG4km46WyNVwOEggscyv9yiOcl-9CvHryJyKvuz2e4UflgI8cfCeKImkTnIs.mp4
|
| 9 |
-
Copy of SnapInsta.to_AQMhpxUr31qzAnyFgG-CGcTZA7YauGZttnT3E3QmvdMQzIpwdaXfRNp_t4rglku1ssx0YWQTC5qS3XfrkKj891YIyJwwoMb3zjYSSE8.mp4
|
| 10 |
-
Copy of SnapInsta.to_AQMjGwqhUGTz_vUednkqlU72EgCDpeWMkoRq6V4hS2kfadbVPZeAcVHf8asV_-8urVDnd-kU4diRtYLs2L9xMru3xQKSaNXDyT9Vyr4.mp4
|
| 11 |
-
Copy of SnapInsta.to_AQMMblIb4Q9fqGa0PFRpMgDde6VC-WSvBnA5-VC_FWN8_E3GuSE9UoItdYVX4CPDHx6J9ux7Hb_0gSlIvqJc8nFNvXXXDed2s19RHdg.mp4
|
| 12 |
-
Copy of SnapInsta.to_AQMVbKl_NzLEztr9dmjtZTZ0lKUpr5LZZ-AnOEzDaoyfotSFh0rcLtB5_BylffGQ2xMmirPcwILSFVTmm_qR3Z6pRSsyNZVJeGUr7TU.mp4
|
| 13 |
-
Copy of SnapInsta.to_AQMp8l7iAUBT36_-iZYlXVUr2TmW5Y3q1cqXQ0HRtzJ8hLxpsiw4YfTZIGkOoGmULYZot81ISTgk6ctGHlW7VrmrcN9Ilg25XA-tm38.mp4
|
| 14 |
-
Copy of SnapInsta.to_AQMRA4xtD_OFQNjmoD4Ijj3xmCTUzDdZ_CIUMJm27i-DzkROgowPSwN3tEUn7U6tjrfqfDDgJf6g4A9cMqvbnmv6urUV53WKP5ERpLk.mp4
|
| 15 |
-
Copy of SnapInsta.to_AQMtt7cBbT4sG5cwVbdrOJ1eVXZNC3BMbTDHl2Wqtb9SBLYW7lUcchJDmTUFb_IUd00QjMfsvz01RG6VHuwgUvd4BOrdOXkIEZARtX8.mp4
|
| 16 |
-
Copy of SnapInsta.to_AQMNqE9oT2DvjsdWIKYSfRg03MZqIFPC1c-ips0s-EjsL5GU1InZtra9S6u6FUBEaMey764evwvevEGSOXhYGucCwvSXtGCAC1FWKOU.mp4
|
| 17 |
-
Copy of SnapInsta.to_AQMnRldXqsRBmhumUES2uyOjiVhKCfAz_DBJe1-IA7cf8Fjg7rQo75iP3QEKdm9IwQa1HLKIpwkUZKYGYua2C9dyxi2f2EFHmV-YQX8.mp4
|
| 18 |
-
Copy of SnapInsta.to_AQMUlVUdL9-pHqUjzex1uIMIJHBU2uTO588nHN-pAn02UXMxMB0AZ8gRLa3mN6pNArOEl9Di6Jte542K-8SyePfPoKtm6f8ZTd-Z_d8.mp4
|
| 19 |
-
Copy of SnapInsta.to_AQMQqfrRHbsMNGEdgk59VWjKiiQMcob0JKxpZplExBCp6-MzSO6RhrhAOJaC9rDT9wR9kCDccxAUxgNxwo7icC9aMGu2j5XekOlRZUo.mp4
|
| 20 |
-
Copy of SnapInsta.to_AQMqtr6waTHNbOU_nRabAaNlR4yLSYKVZ9cNRwuEAC_zijlbOrIjiHQWaRy5u9_TDp2Xq2TcMR7XJbsiX0UZId4u.mp4
|
| 21 |
-
Copy of SnapInsta.to_AQMSR2D0e05gi9FVMTe_7XSXb0fE_IKW-MVaUMtdG5-SM8juQuGDtJJNg-k0Foc-69Ha-hPtH_clherA_rksqYI3vemtnbpJyyEaVcU.mp4
|
| 22 |
-
Copy of SnapInsta.to_AQMVonW8Vl8BUXM5WSHgI24yYXI7cyvbmPozlZTML5rchIhpM_zn1DkWh-ow47hNd_2qR8RFAnRjOa8o3tnwTgSz2plWQMtNCrz7ar0.mp4
|
| 23 |
-
Copy of SnapInsta.to_AQN-ZmWaWlIe7yQiZukzWBSxwYahU42ceLu6kge4wOmXjGX93ifAXSV3qCDq9twQkJLpWTMqI5-udhYz8NJBdoV3kz0nZhPbnYqbQac.mp4
|
| 24 |
-
Copy of SnapInsta.to_AQMXUx2H1BriR0m_uwuP3esxRa0m_Xnx5y0DcAoSCVM1hmoxke8tQ0oFgenLuFAyoTfzBePFN5tpJR4-n1MzQ1BMBTLbI8sPRuo5bAg.mp4
|
| 25 |
-
Copy of SnapInsta.to_AQMwKLVO-lEZ3otDL9LlfAhBMlefBtAnZDcQN1S-TmGDpbgCCPW1sjLLXEIyeKRm_0JE6aKENDvmkW0PYxNcVFx_Oa5rGipeFH5Cpag.mp4
|
| 26 |
-
Copy of SnapInsta.to_AQMVO_6qUs8_TzGX7WW_xp_2T5g92hqlJH1c7IufFqOcWAS-VwdinZPWG9ltQ_-Jw-pVx9Lp956n2Ag_jdS43omUWowIgYEFF3MKJEg.mp4
|
| 27 |
-
Copy of SnapInsta.to_AQN-XiVMC5LInVoo8hCMEV31-Jl5oywpM1at9dN5rl3Udwt7GON1BW5cv2fPaTQlY78r591RcRo3YhKPwYtmwNuBxAdsaKi-nsNBK_s.mp4
|
| 28 |
-
Copy of SnapInsta.to_AQN_KFeCp0oZzSjqZNxe3i82lgEm01TfUIomhp1MPp2nsDJiLCg8Cxb0TU2gBLdIf99YsH6Oy_CeO0Ui5w7vA925vHmWVb_npzusPac.mp4
|
| 29 |
-
Copy of SnapInsta.to_AQMZbe_7iCqWDT-xsvq7fdyoch9XGYYzAN015Tl_04Fljs25mbu9fr1AmJ-ErtX9-3-vjSqCgcwzLKJ5b3WDZvZk9oNQSNjcayVZ7PY.mp4
|
| 30 |
-
Copy of SnapInsta.to_AQMVnlnpH0ivITy367-Qzlvvu8_QkjcDnpQttkWChXZnLse1GA3dCBy6u_JwqQwgr7w5g8DdIaxukOdnVL8UuYer.mp4
|
| 31 |
-
Copy of SnapInsta.to_AQMwigxgToUYIi5AUCIt1lV2QKpvADh_rG5wrwe-hUbyhDywBl3L4V1238063Fh50UcVnLo9Ht85pU88Umzi1ml4nJTJz8xbZK8-B04.mp4
|
| 32 |
-
Copy of SnapInsta.to_AQNB23wNX8ndmbIl1obUu25fjr1HQ_fhVwbIO2LEpVXx3TRSk8UQoyusbR8wbLegbbwaAl0NMzoKuRCg1osDFkbLa6553aHKCyUhr3g.mp4
|
| 33 |
-
Copy of SnapInsta.to_AQNbmIyZBO6TaHbzd-ihFi9pWpJ_Du_Bxi35hXj7vnZbyU46XWiNBwMEE14As08Xjk7rMNEC2g_PcwuaA3fT0KaBCHFvCTqFD6KXHcs.mp4
|
| 34 |
-
Copy of SnapInsta.to_AQNaziUShlmtsJcJu1mxqhoETbCFd3L9GU82Md3eeBfyHWdNwiSTlR-cfG8vA9Jp86CBrc6vXSmEJVx0SOQJgkhLkoSWe3DjYP6NzD4.mp4
|
| 35 |
-
Copy of SnapInsta.to_AQN2LexHmGhjLOmLQwvrXaixepIIjO4qgK7s_FOy6855oISLSqousqWDesQXNAyqDJW8VCnyd14kl6PUIQIQIeO1jqN8SNrgH4XL9Uk.mp4
|
| 36 |
-
Copy of SnapInsta.to_AQN8TpDHAqjLs7s65r9zJ7CKbKiBFkp6PnWIdUAeYxh43GBIz8D1OnWjIEC71hO8NzLHi19xCkGvNu48AQOjdos4c-hrrA3orfhBZn4.mp4
|
| 37 |
-
Copy of SnapInsta.to_AQN0QCqUS-Kdc2chNW-WAni2D5V3I5hgxb4e7sVeGe_w6zys7oriPnE0xhjFvkHuqWZ2Auo-FitL9HJZE6PSjFXJOOxgZR6mgILCkAw.mp4
|
| 38 |
-
Copy of SnapInsta.to_AQN4KkjrOC6aqzf-ju-U7xu1XNST8vVIOq8lw3mtKaX-3u2WAqCBhVxN07m4UzyiSk-qqQRST8Wm2EiZioG6JhoYP6rNb7z_xkWiKpw.mp4
|
| 39 |
-
Copy of SnapInsta.to_AQN0M7uhrwylRo8FdA8134TTuI-xL3R5pk6qC734VYpcw7wiQeIyXSl6HnvtnLSe6j4wS3IQ6_y1jhEFAm9K_ZDgxKL4MMGKYcqfXWI.mp4
|
| 40 |
-
Copy of SnapInsta.to_AQNBorwAKypx1gZsrLnwkH223yqR4cFsSUcn2OnYGoDxSn-EWis0OHwQyl73azOVk7QWFQFRktGIPVIODtcijRRJKW23KECTAT3LK5Y.mp4
|
| 41 |
-
Copy of SnapInsta.to_AQN9M60i5k1SFj_ozWuIWLon9Kgyw7e565ccbmrEqr_M0-N8HOVkuqAUCjyg_GKud4uy3E3ySPuTg048j2Wm6MZVH6yzPnn45Ebh5F8.mp4
|
| 42 |
-
Copy of SnapInsta.to_AQNckYpG_uQBOOLUwHAUDWMuWhlg2trg-Y9Kp3DGr2S6h4MpAdmcQaKAJBfDqeujh7E2kDgKsmACsQY2GhnTYm5DiDwvs--huo1RW0k.mp4
|
| 43 |
-
Copy of SnapInsta.to_AQNfGROswAa-Z9DDIBxZq3yu91rkXULf-jOaopJ0PqFogwhvHWDJBFmk_3irrnkef29SgmtpWI-mMJInXFA8kGpLI4CWZy00I7g0xWw.mp4
|
| 44 |
-
Copy of SnapInsta.to_AQNG5Tnyx0q11YTJ1hW_VMRnj1Eh8TjZc5ID9RGqt6fNiUJ3-lsZK1mBb9ACL_vv7aCI7t4iTwT15oJXzY94CyeFBLXUJw1M48KNHs8.mp4
|
| 45 |
-
Copy of SnapInsta.to_AQNM2sAMPV1bcw50rGWAgRhVsyC3HvhSAXfsb-Rlbl5K2IQ92hI0kyIvMtkytKMRTfWcZNzQzCy764eTJ8Z6fvZcCulMmwSGt8nLNVg.mp4
|
| 46 |
-
Copy of SnapInsta.to_AQNJVbzuPU9fiNI1KZ6VfnH_zPO2pO2O_4Q6DgfWnej3iPOFY9MllBGw22m2V0F_rqjHBuiGhPhzM5DdCRkgQnKA8dICtgOL6X_ZCd4.mp4
|
| 47 |
-
Copy of SnapInsta.to_AQNcGVi2gcFCQ4nCsAx6tcOQof_n9qlcQ4Tu9XqzBXPOaIYyT4cHGjQtrz50N-DDytf5Rmr-REWSAh0BPs2yBSLkhJqNbnSp_QmmWBk.mp4
|
| 48 |
-
Copy of SnapInsta.to_AQNkIsZQNxiE7SPGe33VjuZp_ZlfHCKO0V5TzGUxzzKSZ5WzrO1GLoPmUzUspGtuTC-9XlmVsNI9C7UdLkBiqdwD8ZHxxDQvYX7CyWA.mp4
|
| 49 |
-
Copy of SnapInsta.to_AQNDxACl7-Kb1UXEmVQHTS4ufaN8Afre7gRno1AUCsj5XeI6uN7x_s8EscnbF86W0OrFPyv2JZzbmCHDYBRT7MeiF0iZaa9i76R5MP0.mp4
|
| 50 |
-
Copy of SnapInsta.to_AQNc3jc2lOqwTDozp-QRNKmgYLbLoGW3wwBlcwCj2cyLfF0SthG2lvIRqrcTapir0p-c_rF0MvETSWCHKiiEReZeQSri4bWUMTSmqJ4.mp4
|
| 51 |
-
Copy of SnapInsta.to_AQNGgW96uyUqaSEmojIIOX0-UFHi7pW6UMtd3f-qYQM3lVCws_ZemlODQuEvGffnT6oKEurN8XAlVeLCIx3K1JtM8qQ28W4Y3dSiojo.mp4
|
| 52 |
-
Copy of SnapInsta.to_AQNqgyC6T5RK091QrV-yHlM4GUp4T-QE1vmZfCuUo9l5xE_DLThc2HM8sGlQVE_YvD1CZmUf9Wr9MlTLR-xmWJk3htxgkbmCMTQWc_c.mp4
|
| 53 |
-
Copy of SnapInsta.to_AQNOkqj1ynj2WlTnixJabKn_mgCymzAsKd1X3tK8OxrpQMebs_Czq1DIs4NigcJxMDG5fpxj6cjKThJfazk0I7Uf-u0w2A0LP4Vce7Q.mp4
|
| 54 |
-
Copy of SnapInsta.to_AQNut0rydUv3waZcTnQknkw25NNry23Yk1a5pxPawOlUHv-1RfwL_GX7lRIuqAMYAT_NBu75wFNmB6-lUcDRfQsyd9AOYGlRQ80FSXg.mp4
|
| 55 |
-
Copy of SnapInsta.to_AQNp_q8cMWZYmqj3ES8ywSEz7KUE1XveZsoRP1D2mOsdPlacBIFepEdVy-wLuecGDXXiQZleiE4azP3yKOv9o8rJ2BvvsnrIeMnydv8.mp4
|
| 56 |
-
Copy of SnapInsta.to_AQNVXtkcIv2jgFgF8i0hv8lLiXZQY9DBkodLrSptRyN0jTzWZhyjGpnNMuRyA95-swPv3fa_BDeYxXFIHqU5W4SohEKe2I802YdyytE.mp4
|
| 57 |
-
Copy of SnapInsta.to_AQNmu04hZ5TwfGCjLo9MsdrvK2dPwIwWD8RRvMUZYk6akhqwDxudyZalPk3vOYhkOBcEz_KqHuFK-7LxlIIDHCZRwbUaQBXdOnqPCuM.mp4
|
| 58 |
-
Copy of SnapInsta.to_AQNUGQ-NL5IQA-gvcuYPlcx3aowuYZQt_EcKIXMcFPnKJ-0T9IjYBcl6rgakbO6vY0LhdJMNLBtTzlZLe86wWSKzTCb4B2LqewnWGuI.mp4
|
| 59 |
-
Copy of SnapInsta.to_AQNszjEx7PwMtjaC4qeLorTbOkEimHLVkw8D6dT-fVwRrIIIvjvafklpA43RrHRpVtXC_p_-en8lvK5iullr_TMCA32WTv1WFUsL6ww.mp4
|
| 60 |
-
Copy of SnapInsta.to_AQNWwkUk5S8JdEesH7oLj2epicI9ssXWM1-ElWakE8p1kvFPqkZJKdKGMMoqUTS99msAYZPjoQlTWn8GDX8eyL7FrhAAG_7rMIRoXF4 (1).mp4
|
| 61 |
-
Copy of SnapInsta.to_AQNWyG-q8U40HB8VghttRXiZH_RcC3sE7HS16PgCm5blZrm0473hYDNqYfgIM8fy3BVbcIItU7f5_Ii-3ssyXudzJItoEAYb2vNsjso.mp4
|
| 62 |
-
Copy of SnapInsta.to_AQO5lxJemg0xxKMcCtHYpQUuWi1qV7K_SYDcHKtnT0BaRUCNQiCDd5xIehsj7m6zUc1daW4UNyJO3SbKUUN3g6Vv-CtCKuMlNo3ysQI.mp4
|
| 63 |
-
Copy of SnapInsta.to_AQNXee2QFX74CqAVz1z3zycA4qCG-_SxNEPXTK709yW_9MZ8Mj413gp7960SzKsmdUKWQ_8vnTOalMJKSeXRQ8_acDzwyM1CWMtcfDA.mp4
|
| 64 |
-
Copy of SnapInsta.to_AQO2tY5oxXgt--yGzdMZ52TaqM4n1kKdgYQobksNPHGS3RxZDwW80dfGpGOvzbZxTDeelZHLW9gqTotCZrXSAmVniD6q548qYLevh3Y.mp4
|
| 65 |
-
Copy of SnapInsta.to_AQO6ll9jrclsLIy48DIsGgQsPJRsvjlih7sNh1xFhYaTQilEXR60NeLu7C19bD5yNbmWuGk1m5FlRC9DiIe3cTn0JoPoWCdO_LQwo4Q.mp4
|
| 66 |
-
Copy of SnapInsta.to_AQO-IK36J0ApycdOPHaErWggzSqSav6ad6S2NcOHtzHaxwUctMYITAcDe6YjNZUSSfMiUCefYNxthbERC4jfOjVu.mp4
|
| 67 |
-
Copy of SnapInsta.to_AQO8UnRg0-tfvdgsR2ewKW5BPgTJQ86rDzyI8HXJ8M9aOLG91S2GygpAhRrs7Xn_1h5NVkRZIJDj48LUoI1MRlOsCx8guaMQdCIgu6o.mp4
|
| 68 |
-
Copy of SnapInsta.to_AQNZHnrmkbNK6-fbXQhC21kitv5bWCI-jjvtOouIYnl0iU2vBQhxaMb8mpBBZp2928rQLJRmnkQy7yaijwoBAoy2ej0-JGJGYRX1fqc.mp4
|
| 69 |
-
Copy of SnapInsta.to_AQO5grbn2BghaX2oV7l5-gesUc09Gazts3wqwOHPCdYpWCOiXBGqL2Zgh57YW2G9YizAesf0d4_ELAJ-4IGD-TuJGdtcf5HKV41oaqM.mp4
|
| 70 |
-
Copy of SnapInsta.to_AQNxzYITSouTSZE72xVRvhn-7nF-174LX5CeydRJkv1yfQjhkBsFfc7Ig38x-ui8majkxOS5usBNIys260X4sHNIphG86lGMZrw5SiY.mp4
|
| 71 |
-
Copy of SnapInsta.to_AQO3UrFoq9DLdKwHZzfkK68_azqA2XHaRUTCaITN1b5xShkPVSWNKy3IhJvoV5PF8r-9O1IVD_91_tBzIz31-QMrZZkSB0clooDKVWc.mp4
|
| 72 |
-
Copy of SnapInsta.to_AQOie47b-ZDnRGvuNsY9Qa0-1oWqAG-pOLUDe3x97s7J-6a6bqLUkrB0kpt0tmxm7ZRvHF_DdpnLSd8jzVjZG4YsNRTLWemBPtOYLSw.mp4
|
| 73 |
-
Copy of SnapInsta.to_AQOCNHXtxn2PLTUGROn0g0dlKxFzOiRqy1EXVEGDdl8Im8lMFwcgNxGV0RMOFn8n44REQJi9QlePITdhndS78HnfJh4k0JroXixZvrg.mp4
|
| 74 |
-
Copy of SnapInsta.to_AQOJGnxFeVsBL3kAxTI7p-CFwep-5nCVy_co9P8nfdqIJDXdV-odp5Dj0oGQBiOXL57WWGDa301BQcVavdr8oqudZt-834FNelqixQI.mp4
|
| 75 |
-
Copy of SnapInsta.to_AQOHdlWMSmXl7W6ZAQS_RIgCxnZ2D22aylN0A-A1UdnInZRbfaggAGfAPyrUiSuGe4rsaC9LXJGdBASlUVxWnMhOO7ddcDcwunGZvBA.mp4
|
| 76 |
-
Copy of SnapInsta.to_AQOj4u3P_qWSFg2qQFbYoj9xlakd3f-tPS7zVo7a9uI9MaHiB4jgrRXuOmh2BR_cWgyull4GbKUyLstCREftucbmcOo_klopgD9rnb8.mp4
|
| 77 |
-
Copy of SnapInsta.to_AQO9jZYb5UAS7vczJ2rx41VV07Kr5fevPObWU2Xy0-JEtdXbMgkY7LiZTJALUN8a2VKdo0ax-Ru5LjDPH9NOxxAk3-NrVGCoh-Vwx0o.mp4
|
| 78 |
-
Copy of SnapInsta.to_AQOf_smlyh93ARb1rFi6qbjFbX0mHeFxg3NdxCqTnXLtyet8Br5z0B_HMnWnaku0ZGxI-bT_2XQUckDM1GhAalf9_2Fwu_uUDa09xBc.mp4
|
| 79 |
-
Copy of SnapInsta.to_AQOgTMoI3xhfDVrDebf3l-OpvecPqspfbdAQq5_T0xHM15YaNk6NWglFj1m2TvTZ9Qul5wvPuhxAXbVVc9EbQcmgwjXqSgcSODYDrS0.mp4
|
| 80 |
-
Copy of SnapInsta.to_AQODK099MNYBeoOlkpGuxM53q_GE7cMiuLbUh0KoSdRYc56W0Hy8vfmN4x8jnnFC6IMVwQbkwg7Q_gCkprXoxgBULKnx1pisnezw3RY.mp4
|
| 81 |
-
Copy of SnapInsta.to_AQOcxQ8P082JwhtQjzTrcWZ-kWEf3wU8yhT-DYOU054-aSgRC2QvYQHFfLL51UpKkRanjozx9VE-1ZkIh6m8vuu6slN5DiFJe5L4VRs.mp4
|
| 82 |
-
Copy of SnapInsta.to_AQOq3_HJc8SgMrfMC-sxrO_Aim1g2vF5COJShA13XGq5orlJv5eE33Sp0Z3rwAkFHNoJG8_f-Uu-GJZ9vh9wv0zoZN1NPomttOqlWCg.mp4
|
| 83 |
-
Copy of SnapInsta.to_AQOrCMLuaUT1sgUL7IOrGhJ1dFZnhwgylXyh-u9bKoOBw75kyM2ckeL94XdzgYfUAAGFGi_h3257Bmuy47oVb9hWknzYBQp02P7XV08.mp4
|
| 84 |
-
Copy of SnapInsta.to_AQOneOr1SWdVeH4FvnvLJ-ouL0cLN8o9hhSDyh7A37xYwnIAN6k_P9lnw-7Tf17ofoTsRcYhB2Qvw3PCmrzO5dw_77PrmJyZvKdmdvU.mp4
|
| 85 |
-
Copy of SnapInsta.to_AQORAyQ2TJnXZG1_231tzfiVS_3Y7_aM0-ZKLicxq9enBao55uWKI6UifhevC5I0Shs0cPXUoUccz1Oiz463yTPEzZ3wq0F9xEfCNC0.mp4
|
| 86 |
-
Copy of SnapInsta.to_AQOKcjLcHZJfYU7Fs6SpR9DsOVGjrQGd9isJv07z9sGWxhaYH_tF2czjXziGLlc1EyQXh_1vTF7QaaZc7vipBaS5r_XW_e6PWx8qSmU.mp4
|
| 87 |
-
Copy of SnapInsta.to_AQOmSXa7DPKqoXcT56EWhcZSMUJEKp7a8dpCVr6nKXml_NY4coN_575FGejR16k0vHyv9zcilZteBmncUp0dn14qigkl7SaMtsz-xlo.mp4
|
| 88 |
-
Copy of SnapInsta.to_AQOPrWx3BibShwkUNZYkJ1xiI1MWakSn1Esd6t15vmzNNgeZp9XCwSUgTkP1fxn0AVFU8bDMv6bFVjna2uvfEZP6ybC5HJsI0-hqhWo.mp4
|
| 89 |
-
Copy of SnapInsta.to_AQOQkN01gcWn_9jXfBT-Ctz85NChZalDyOVAC05lpUAg_wfXBYWvCXSNEoEQ-F72Oypyo0S20RNQ453ZUPGik4r9xe-f5qsMwxZfwvE.mp4
|
| 90 |
-
Copy of SnapInsta.to_AQOnSzCRVuAmb_4edTrHhUHmWWL5Db2xkXAPjASJwpHbu4zsxOjwfsLSuoApDurgl6KLwGflWD97AZ11JMv2bWrhJ-Y3jnPAwqDRwqU.mp4
|
| 91 |
-
Copy of SnapInsta.to_AQOMN-1ZV_U_1s8JPnQA3ZUpsNGNAJEnlPb100nLAvnNAkoGljAi7aOPUbp_JjwBlvQdzGvXRBlioJAMmDCk8tcYoaSWqE-2vwKpL-Q.mp4
|
| 92 |
-
Copy of SnapInsta.to_AQOxJlTZQkNRwRmh_l8_PB7ahjQc7wxemPiWsfRocdqxwR1G6Pfx25LVDG-SkpEK6IZiiFjC0h_Rre6YNN3abvxKp40JvmT-5f09yxo.mp4
|
| 93 |
-
Copy of SnapInsta.to_AQOS2bo1S3Q7iDN0Q8hwMvDFMNPN1_ceQM9uLuUeC094f3bGWuStvdV85KUfb1E-V9FTM-UiOcvutPjbx4zkrOVNLF0B3HzNkRJXnZo.mp4
|
| 94 |
-
Copy of SnapInsta.to_AQOsYP-A0MHz7SwjjwagDRYV0YJ2tFfONsyEYETOiQWqbtDL4xrDovzIHz-S1DBtGZ4ywLa4Uz0-29tnG4Ha-PkRscxj-h2TPuwCxzE.mp4
|
| 95 |
-
Copy of SnapInsta.to_AQOXUoM-wwRwqCi_jgC3Bfi2LtMzhB92460mHz9-_TZmZy207wSmDkCtS8tRm5AOigV82_6UprIIqxjIUvsMv48vdjcy9RJI24mU7h0.mp4
|
| 96 |
-
Copy of SnapInsta.to_AQOtsi_R30JnRh30gaiV5Z2OQyI20Tuwttk5bw8r5gHfWEDSGu242zBej87Tq7W2naCXzo4BjQQTolzm2PEnaCQ4HJz6NGyNcOsyNJA.mp4
|
| 97 |
-
Copy of SnapInsta.to_AQOTr6BZLkDl-El9L_fLPbYN8RIZmt2C1jMS3oslwPd9MZXz_yxqxRiJ1VeRAuJwjNw0BrPpXpV2-F-olaJ2en-J4RlI0vPl6zHRilE.mp4
|
| 98 |
-
Copy of SnapInsta.to_AQOx9lpmtsfj6erRU681gfcRo0LtoBenK8TEm_2i6_TSqYfJd_4JQjhfSchJLsS-Dnj5E9AIEF6QG6J3oiThCBrE.mp4
|
| 99 |
-
Copy of SnapInsta.to_AQOW3VYdttuKMHS9dnYNNQEMMBOr_K3Ky1NGQF5B7aB_8P6HAJnpZ0KOAj7vO0i6MZKIFmpgDPDzqAGKxr-6Nen1IEE8ftMwA5DiH5k.mp4
|
| 100 |
-
Copy of SnapInsta.to_AQOyu0xEyFQRxB1moDAiTx7P_WxsevMzla30_wuvheLERk12Sy1UpQd_nRbbUsn81CCThO597ZY1shllccADKQ04J6eWBc1MvSqHTlU.mp4
|
| 101 |
-
Copy of SnapInsta.to_AQOVuXn3d0cfBOu0vWzc5sqWh_lt1GQ68VeH7IPu-CSClifisPa0O7D6m8n3gW5Jq_WORFD3Gq4yxOs0y15pOeIF4u9Spc2XYh5NHCg.mp4
|
| 102 |
-
Copy of SnapInsta.to_AQP3nzO4iWcJJW929MdCfTuADyEtRel-x2Z6KJRcdYgYdsmG-xazz5pJOyPcnXmakaodd8aU5aWdktvYNV5nqDgj0o5iSDP65qNKtcI.mp4
|
| 103 |
-
Copy of SnapInsta.to_AQP86DAQoIZCVJiTrrG6nfsfHO4Xz4uZT-mjLDEaf6P1paoKFkgN0SyR1SR4lCcc7nJ9MOh0hszj9FCMU9UNR75gCaaQcBJmOjUxLTw.mp4
|
| 104 |
-
Copy of SnapInsta.to_AQOz5JrVeD8Any2OdPur3JUp0i5W9aTBbJiZv43XXSWyoC8U8o3Yeu9ZoetZrIswlJzcGwTmAGN7QhF1cAp5XKbgPJ5z_URSxGzX2lY.mp4
|
| 105 |
-
Copy of SnapInsta.to_AQP9spO2KiP7V0PE_SD543-lOaZlmts_ilRgpcTvLJZ0jY-gA_FBYyVLgFw9hxrSxyiku2b7hVR9yLuftcaeLcGMnhfkapC-_sC2jQc.mp4
|
| 106 |
-
Copy of SnapInsta.to_AQP3XJBJez4qSqLuvRj4TLngrQHf3tSaMbRfpCBPCFGohMMsaIbm4Asm6NThIfTKUUedkl4m6EaSlS0hmW_MOH2FKzMThfWvcdhhKA0.mp4
|
| 107 |
-
Copy of SnapInsta.to_AQP8JXKxS3ep9SjRn7QtzEP_9NHEnp7F_CheFX7l0DJw167Na20HajhR-5pYkgcjsNsE9avOKy_wf1e7gt7UWQXhar8oEfEuJqnLP6Y.mp4
|
| 108 |
-
Copy of SnapInsta.to_AQP-84cnFxBU3ogW5aYcCZxNnk5Npu06VHdtBiXI8scgit9Z1iF3FRJ85BFhX2r8yvDtq5CogzDYvlMFUCi4X_ap.mp4
|
| 109 |
-
Copy of SnapInsta.to_AQP6fOo_u0HjG72G_f1L9m7AzRyA43o9eL9czREYfO0iooWLpbjJL_uiMlqJcBE2OcqLzvoXNpMydB5iisdnIGmeAbHZlwIrT6jJHh4.mp4
|
| 110 |
-
Copy of SnapInsta.to_AQOZBSVlF56HzS7TdXQko0MAjNEWCSYzvnJxFlLMDx6-yjhdu6wp4V3Sim_KZr2kAVcyaLB99VgcNHSjmb-TNJdiV9_Wxo3Dr9gqnO4.mp4
|
| 111 |
-
Copy of SnapInsta.to_AQP641BeVK_fcPYJTquNq2RSmBmrcpRmMenS3Xbo0Xcs9DeiRvOVIvmi82Hksjc1op6N5oDbyeVk1GXN5EfLiCCi8sUY5lm2Ylos9Dc.mp4
|
| 112 |
-
Copy of SnapInsta.to_AQPKvI5tzKt0rcYaoB9HdmbC-Yi91trtjtas5xAVBur6tGw1stv1d_wvF3KpxZhiuuz-N5ehEQvJs2QW4Wfjg8DbKiPCgRXAUxesOvA.mp4
|
| 113 |
-
Copy of SnapInsta.to_AQPdKMqmTouNVR32c4lic4a942XUPm9mbO3ert5cbn4TPnUBGcz20F1qexD-HMhsvfO7S3Q25Uq13JEJCZ_CTXoXLoqFH34h2Zgw1Kk.mp4
|
| 114 |
-
Copy of SnapInsta.to_AQPBh6ZbkOi9DdppSSZqeMIYc3bb_tGjsSZaa9AWvmD772lxzB5x3ZqHrqzfP-ajsh_tcbjPPxTsMoLWvK96XXxYp3Ajc0H8h77JN4Y.mp4
|
| 115 |
-
Copy of SnapInsta.to_AQPGtsWjjHydzFunbj6-_MAO3ZwrSLXHSgfpTxeq16cIFGVAsfc9SJvVaQxqs1K3C6JfZ7_C1QTckxR5x8Sj5r6Wbt2Mbka01e-bw-0.mp4
|
| 116 |
-
Copy of SnapInsta.to_AQPBncb84_kFQheHBdy548GpuIMZRuHQPs0E3jqgOJCXiMVuSuDhQi0TwudmRwRLL9b1T7t9XozpTly6FZjT6tSaVUm92Sv0yFKfmkw.mp4
|
| 117 |
-
Copy of SnapInsta.to_AQPBqVBlulo868CCerAaOgobfWOhBeo0w-cuTDWwEwI0WgjbnyU5iLDezQh7qe2tMQd_zGMowtF6QR7GywTdjYSS.mp4
|
| 118 |
-
Copy of SnapInsta.to_AQPkLtb5YAn2kL92TWPUH-6tpQax5koCpXQOqQxJr7UbOv4YXijRF06un14DfnMDtbggg4ek-s5-OxHHIK-7fYStc2e9_JeLD1UTo5Y.mp4
|
| 119 |
-
Copy of SnapInsta.to_AQPfpVXT3sWPsS6EK8esdIUBU_cY5vhS6-Q2BbBXg7-fEnJnErhKe9tVxgVDuvMC2Y2edNUxwWrCpbHceINf0LVM.mp4
|
| 120 |
-
Copy of SnapInsta.to_AQPDhbnoXg-EkALbokeGWoAuClFYCUlWQbr3ZAk7DVKMk73B9w9ZRuCgsB_Kz13KnC5bfhSqG6pHH-x1HenHh7vEqaP9MW2SqYSEnn0.mp4
|
| 121 |
-
Copy of SnapInsta.to_AQPjNgNIELsBfrGYbOhRaJ-r73lHr_f6wMgrMUzrPW5qya9TFMTy0Ncz-hGjfI6PRdjVpEGs2_UpJbagXBrDRGgma4YUTzFstKZflXQ.mp4
|
| 122 |
-
Copy of SnapInsta.to_AQPQyIZYpsfOCAtthorRzThoegE-DO1r6YMdd-44QNcGM3MeRhrKwtraac3HesG3yRWGP0SqnoZ7cQAWRUdVGUHatiZQgY1ggysi360.mp4
|
| 123 |
-
Copy of SnapInsta.to_AQPS75Q24F8RlxnYHijqrtDBGkscB-JI8s1gvFoyUyeaYWgDuzQz_hF9hSJ7d5u5_-CJnmlVlDxQOxENCuZp-EgsQJunXvqUmtsN3Qw.mp4
|
| 124 |
-
Copy of SnapInsta.to_AQPmziF9WU9q7w_9TF0a8535xUq1jqPxmq14zGHGZnWyTmR4ivlTiLIck4r2OTdSutO1DsV4oZdS-Zv27pc3VUzGIB1kjy5U8R0s2cI.mp4
|
| 125 |
-
Copy of SnapInsta.to_AQPqehWNSvv3rYQCvk5b7O4Ld-GiiOxXuzuLss-QK-0eJ56hupiU05n1XWv1vCrrDiOIwvRCQGs0gmNh3Nc5Battl_tgnKn8lNrgKtI.mp4
|
| 126 |
-
Copy of SnapInsta.to_AQPsXrX7q4aXLWlsC6VEA_BFMaEWb_NCp3c1hBm4afw-_iD5YLj8ddtPgG3MWO7HVNkGqFvpdEWAouP2hK0VYOoFUsnM4Ys9t9W8WWU.mp4
|
| 127 |
-
Copy of SnapInsta.to_AQPOi8TaqmjgR9JFBiA4ckk5c9VkzxKQanKHXuDyG5SqSQBJIqlVpArjpCR9HRc-wtS2Z0Sb1gLwUayx68FRwOckb7CB-yog-otVaSs.mp4
|
| 128 |
-
Copy of SnapInsta.to_AQPNbBZCVl0GNAu6VP0717BmrCvatqmfwUG1xyJuUHs4ikpc53URO153T1JgI81DyWuc7z1PLnUNdNdO9v7tuvHmp9fhShPCZmdGsfg.mp4
|
| 129 |
-
Copy of SnapInsta.to_AQPLPXOUYpJeVo3hu2TDcYngTealPO7JzZ2FbUWJSeoRcJV1Ij6ll2GLBowRVtTPXl5ROA9qmUC-lXyezdINjSU9xyGAnblZSG6eI2Y.mp4
|
| 130 |
-
Copy of SnapInsta.to_AQPocb0pCM1_RGF9JX4avvuOmzHrlwYHar2zYUAhb_fV50G86tKb0aUneLvyUHTPvnI9iyOwu8YepoE6xY0NjcG-v5SNy6S27Cgfws4.mp4
|
| 131 |
-
Copy of SnapInsta.to_AQPv77l86PSqUsTE8lQdViDoTCH8AKQ4u_NKmuDrPN00tfZkMaWd-ki6PKj0AXxFFv3YB0h2KxYFwMpcvLKyVI2W694E_sASTb2dN9U.mp4
|
| 132 |
-
Copy of SnapInsta.to_AQPy4LeOK7HGMrdDGy6UupOaWCfNvxrCoEq1sQ1AmWxtt-wAhh1lyRDbA-iV1gDym0P3hjy9wUPUEFbOYjvONEfCMsCmQjzzzMxAMPM.mp4
|
| 133 |
-
Copy of SnapInsta.to_AQPXKnrDd4o4TWs8IApXii2FpHkLK2pVc-ivIzccjkTyKogfBtEKF3e5Te0agwsBrlrLbtSI3YMzvkQYJs0E3iqS2avOzq7uSSrfJvY.mp4
|
| 134 |
-
Copy of model.mp4
|
| 135 |
-
Copy of SnapInsta.to_AQPYpHm9LfSCZ10HxVHA2zOGVAICYZdlcbj_2ko240t0uM1wc4GrGDyvabuqINLZ-z9GlRLcjCrOYntJx3NJb1Cd4ok4mNC60Hqgdss.mp4
|
| 136 |
-
Copy of SnapInsta.to_AQPXflYRuO1l5hRKDxz7JIxoNs3ApCXdjPaPKb77EGzxoymI7ig1FhkAPCSrCOvu4eQHlfiNpLva2xmmcL1K9SHg60-IBip5xkGasmo.mp4
|
| 137 |
-
Copy of SnapInsta.to_AQPZ_W5eGgta2gCBDMq5cVMn4hS9y7LXlyOp7lBdNMWvRUsvFDtITjdAgnK4tAi3JfgSihgvor_Yg65fkdW5XJ4uNFLlC9ovBliNA7Q.mp4
|
| 138 |
-
Copy of SnapInsta.to_AQPVeSGnntyRxeAL1AK7KAnKsHu0UMCWMOjcn12kVQbcHTrMdn8KtsqvyWY0d6-53x77X0D0zoJIx0aoJZJQO-qjcOXmgN6P-jFB47I.mp4
|
| 139 |
-
Copy of SnapInsta.to_AQPzA_PZlELUWUWo0tm-HSoVU2l-WT3iygeZjwyyAH7cE1ykYxkpUoreWPnHhNF2XxhuY12UY2-m9V5mJHoYNg35C5rvSDWivElpf9c.mp4
|
| 140 |
-
Copy of SnapInsta.to_AQPYH_-zf8tTP8LwktjY-dpxGIsmWPwyaT54UpartRzMU_j2hgnvcI0K23mFTfiCNkOaRbH1EvUdLiQLLvi62FLCSy7Xz_gJHERlQAc.mp4
|
| 141 |
-
Copy of SnapInsta.to_AQPVjEY_ZzxUJoWmIsLRGClhacfUMMfLGDTeXxUqOZwg_bcy8YrxVBVBs98T2e8_mKLhqyMEDZyATB6TOMGnBjSOegdCxMZILWKstjo.mp4
|
| 142 |
-
working.mov
|
| 143 |
-
Copy of Balcony day.MOV
|
| 144 |
-
Copy of BALCONY View.mov
|
| 145 |
-
Copy of Bath.mov
|
| 146 |
-
Copy of balcony night view.MOV
|
| 147 |
-
Copy of chilling at night.mp4
|
| 148 |
-
Copy of burj khalifa View.MOV
|
| 149 |
-
Copy of Black and white house interior.mp4
|
| 150 |
-
Copy of Beach.mov
|
| 151 |
-
Copy of Beach view from balcony.mp4
|
| 152 |
-
Copy of Beach view 09.mp4
|
| 153 |
-
Copy of billiards interior house.mp4
|
| 154 |
-
Copy of Bedroom.MOV
|
| 155 |
-
Copy of Beach Miami View.mp4
|
| 156 |
-
Copy of Big screen & Playing.mp4
|
| 157 |
-
Copy of Christ Mass Vibe.mp4
|
| 158 |
-
Copy of Chilling night .mp4
|
| 159 |
-
Copy of Drone.mp4
|
| 160 |
-
Copy of Dinner outdoor.MOV
|
| 161 |
-
Copy of chilling balcony pool.mp4
|
| 162 |
-
Copy of Dogs & house.mp4
|
| 163 |
-
Copy of Dubai 02.MOV
|
| 164 |
-
Copy of city view.mp4
|
| 165 |
-
Copy of Chilling Night & fire screen.mp4
|
| 166 |
-
Copy of Chilling.MP4
|
| 167 |
-
Copy of Dubai at Day.MOV
|
| 168 |
-
Copy of Dubai at night.mp4
|
| 169 |
-
Copy of Dubai view 05.mp4
|
| 170 |
-
Copy of Dubai View at night 02.mp4
|
| 171 |
-
Copy of Dubai view 03.mp4
|
| 172 |
-
Copy of Dubai real estate .mp4
|
| 173 |
-
Copy of Dubai view 09.mp4
|
| 174 |
-
Copy of Dubai 03.MOV
|
| 175 |
-
Copy of Dubai view 04.mp4
|
| 176 |
-
Copy of Dubai view 08.mp4
|
| 177 |
-
Copy of Late night view.mp4
|
| 178 |
-
Copy of Fire chilling & Pool.mp4
|
| 179 |
-
Copy of Dubai View.mp4
|
| 180 |
-
Copy of Enjoy View.MOV
|
| 181 |
-
Copy of entering home.mp4
|
| 182 |
-
Copy of Garden View.MOV
|
| 183 |
-
Copy of exterior view of luxury house.mp4
|
| 184 |
-
Copy of Duubai places.mp4
|
| 185 |
-
Copy of Empty luxury place.mp4
|
| 186 |
-
Copy of Luxury home 03.mp4
|
| 187 |
-
Copy of luxury beach.mp4
|
| 188 |
-
Copy of Luxury bed room.mp4
|
| 189 |
-
Copy of Luxury bedroom.mp4
|
| 190 |
-
Copy of Luxury date.mp4
|
| 191 |
-
Copy of Luxury balcony house.mp4
|
| 192 |
-
Copy of Led interior luxury house.mp4
|
| 193 |
-
Copy of luixury pool 02.mp4
|
| 194 |
-
Copy of luxury gift.mp4
|
| 195 |
-
Copy of luxury garden night.mp4
|
| 196 |
-
Copy of luxury pool.mp4
|
| 197 |
-
Copy of Luxury house night.mp4
|
| 198 |
-
Copy of Luxury house 04.mp4
|
| 199 |
-
Copy of Luxury interior.MOV
|
| 200 |
-
Copy of luxury sunset.mp4
|
| 201 |
-
Copy of Luxury pool night 07.mp4
|
| 202 |
-
Copy of Luxury pool night.mp4
|
| 203 |
-
Copy of Luxury interior house.mp4
|
| 204 |
-
Copy of luxury view.mp4
|
| 205 |
-
Copy of luxury scales.mp4
|
| 206 |
-
Copy of NY night view.mp4
|
| 207 |
-
Copy of Night View.mp4
|
| 208 |
-
Copy of Malaysia View.mp4
|
| 209 |
-
Copy of Movie time.mp4
|
| 210 |
-
Copy of Night chilling & fire.mp4
|
| 211 |
-
Copy of Luxury yard.mp4
|
| 212 |
-
Copy of Miami views.mp4
|
| 213 |
-
Copy of NY View balcony.mp4
|
| 214 |
-
Copy of Morning work & Girl bed.MOV
|
| 215 |
-
Copy of Morning View.mp4
|
| 216 |
-
Copy of Pool and & View.mov
|
| 217 |
-
Copy of Outdoor Pool.mov
|
| 218 |
-
Copy of Pool at night.mp4
|
| 219 |
-
Copy of Party indoor.MOV
|
| 220 |
-
Copy of Pool view from house.mp4
|
| 221 |
-
Copy of Pool Indoor.MOV
|
| 222 |
-
Copy of Pool Sunset & Budda.MOV
|
| 223 |
-
Copy of Paris.MOV
|
| 224 |
-
Copy of Paris 02.MOV
|
| 225 |
-
Copy of Pool & Palms.mov
|
| 226 |
-
Copy of Pool view night .mp4
|
| 227 |
-
Copy of Pool view.MOV
|
| 228 |
-
Copy of Red house interior.mp4
|
| 229 |
-
Copy of Pool view night 02.mp4
|
| 230 |
-
Copy of Recording At Balcony.mov
|
| 231 |
-
Copy of Rocks & Beach.MOV
|
| 232 |
-
Copy of Pool.MOV
|
| 233 |
-
Copy of Rolex & View.MP4
|
| 234 |
-
Copy of Rocks & Beach 02.MOV
|
| 235 |
-
Copy of Riu Plaza.MOV
|
| 236 |
-
Copy of Sunset Sea.MOV
|
| 237 |
-
Copy of Skyscrapers View.mp4
|
| 238 |
-
Copy of Sunset & Friend.MOV
|
| 239 |
-
Copy of Rooftop View & skyscrapers.MP4
|
| 240 |
-
Copy of Show View Night.mp4
|
| 241 |
-
Copy of Skyscrapers view & pool.mp4
|
| 242 |
-
Copy of Sunset & Budda.MOV
|
| 243 |
-
Copy of Rooftop View Sunset.MOV
|
| 244 |
-
Copy of Rooftop View & Pool.MOV
|
| 245 |
-
Copy of sun set chilling 2.mp4
|
| 246 |
-
Copy of Swimming pool.mp4
|
| 247 |
-
working.mov
|
| 248 |
-
Copy of Indoor & Pool.MOV
|
| 249 |
-
Copy of The view from luxury home.mp4
|
| 250 |
-
Copy of Sunset View 08.mp4
|
| 251 |
-
Copy of TV & Chilling night.mp4
|
| 252 |
-
Copy of The luxury view 03.mp4
|
| 253 |
-
Copy of Sunset view 01.mp4
|
| 254 |
-
Copy of Sunset View balcony 03.mp4
|
| 255 |
-
Copy of SunsetView.mov
|
| 256 |
-
Copy of Sunset.mp4
|
| 257 |
-
Rooftop View.MOV
|
| 258 |
-
Copy of Wins & Outdoor.MOV
|
| 259 |
-
Copy of The best view from house.mp4
|
| 260 |
-
Copy of White interior.mp4
|
| 261 |
-
Copy of Working & Sunset.MOV
|
| 262 |
-
Copy of Working & Redbull.MOV
|
| 263 |
-
Copy of Working & View.MOV
|
| 264 |
-
Copy of Walking outdoor.MOV
|
| 265 |
-
Copy of white luxury house exterior.mp4
|
| 266 |
-
Copy of LuxuryItems.mp4
|
| 267 |
-
Copy of SnapInsta.to_AQM4NKne606GLmphwWDjSOhoRmUj_GjVyqIxP4-a-hP-W3yEDsJQXMxSwXsldB5clNSr6DSITR27TFyeocTM0oj-tuWaXdoj9j-Zl0Y.mp4
|
| 268 |
-
Copy of SnapInsta.to_AQM24IC4X_a3aNzH0LEq_P9up2hQh2ez6TS8wf7L8O220fZrPfK1jF9Mw8aHvy6PRq28uiwxtbYTCEnOdFG3WDAGKBH3UqKMGMK-7pM.mp4
|
| 269 |
-
Copy of SnapInsta.to_AQM4uutpNjEmK9bbNS-tcCicGSLyLxbxOZ-XZqMAaHqvGy6bzBR0jxYXYqhWFW1yuYBmFDR8lIiNSUk0nLH6QBm-CWF3Mv2OXjL3dxM.mp4
|
| 270 |
-
Copy of SnapInsta.to_AQM-NfgsLA5vXDeOlarSpzelq4kwg-kGgzi4yJP9yIswUySb3bOYm6PHQlHssv7NOUiDPjNlVDb410rCx7k4KFTvlWFekhsevwl-Mp8.mp4
|
| 271 |
-
Copy of SnapInsta.to_AQM4M3V-O8H7cwOFee_sBy5PRIRZsHp-cB1ZPQCccPaBifuqbNtvvaQkBBW22cWMGwzRH9FTaPu_GEthSX_zEI728ODqtP1qYJiTU_U.mp4
|
| 272 |
-
Copy of SnapInsta.to_AQMCvzBzgcdY4Nf0srYDZQtth_t3iqsyOUdYHpH9heyKk1EKEGQXK6bZttnwHcOdG7HTC_uFC_dU5OH2xjONPdI-v5kGmObTT841yT4.mp4
|
| 273 |
-
Copy of SnapInsta.to_AQMepEVWahHMyZ19UYw-dtCSdBW9My4uawp5dfqJ72SjTK5lNiFMRW9mMIPHTHD0AgcgXuS3gUQ-lyBaDnksdQeaKBaPKOgbyQtF5kU.mp4
|
| 274 |
-
Copy of SnapInsta.to_AQMFlt1XquFOvRAyXFUAr8gki5RDa5ADpH_q3XIAUQwcAldd2fOuGVyHVut3JQ3q5F28gLI0K0SJYXXbpVhQilCjW33_xp9aSrukWGw.mp4
|
| 275 |
-
Copy of SnapInsta.to_AQMiDPhW6qyqiq4W2cKsy1zmD5lrFV1FB4mgkdBhBJ5uY9aFJO56E5ALfsNMLGuElJmUqrT5SL4bomjO9Yw0bPqF-247_epJS3-AiOY.mp4
|
| 276 |
-
Copy of SnapInsta.to_AQMab_NtxU2FPO8jE9hAuV0J3SLw0YzkQYLP3bmJcb5-dYZoy9i0AWJVFOyAvJ4QENfRHpOZSthu_IEe1RFp8T2BSzli-K5hrviNPeE.mp4
|
| 277 |
-
Copy of SnapInsta.to_AQM499qNlXGRSCpF13uZGkL1cVUzwDf4Sk-tyS4Bzbyj50QEYrlRNIweMqHwC4FG81wfNq3jvX1-NC7ZjJwh9_2tFMtL9JdjyRkMhZA.mp4
|
| 278 |
-
Copy of SnapInsta.to_AQMGL3eaYBQQD_qD3IX_ZQlHwVphF51DYGoN8u9BVKY2Ni69lENNUOVwEB6XcI5OM3pqz4O9S3iZnLIe2RYWCAoFBPt9quEGGFfvguQ.mp4
|
| 279 |
-
Copy of SnapInsta.to_AQMC2N_TFCxdBUJDh-Xcn7cmFL80F9iYpVeVa9SeWqkLcWKGkf174a67qnotlwfGpO2JimthRN47dW2cnjU8cTas-Qyk26SnAqoqwHU.mp4
|
| 280 |
-
Copy of SnapInsta.to_AQMGnwHh5y9BNFpMGpzePVfubsN-37uamRWIExALPL04R-GRuQO4G90cJEBTAwq6Td0T7Yxmwm5Q5z1dKA7ls5v55KbF2GXt1IdFkn4.mp4
|
| 281 |
-
Copy of SnapInsta.to_AQMfGnrXUeQij1G8e-ZJwg1Wk8uoi_meqlFIHja1d4_lx6g-0PTu92VmDDVUHiXdS_qpHC-i_EENGfMRXJCXJVn_BVs9Ce9l2acNGvs.mp4
|
| 282 |
-
Copy of SnapInsta.to_AQMslPIzohCu9Y2L3inGPkaBOwcxafqCyXCawXTgSozTXPMYJ0gkCOYtN6ApH5PgKofDRHcBjJsIozSV4k7AGaIKEUh1q_Zxal510aI.mp4
|
| 283 |
-
Copy of SnapInsta.to_AQMrdDyG3MCEgIQ94Nb0Q8paE-FDJatGd9oOyRHERhZE2tjkteg6u6JqmmN9AoiZbyWe4KZOsn3KOD5A9rrUwugqPPAbha8yLE-T2_Y.mp4
|
| 284 |
-
Copy of SnapInsta.to_AQMtW7oVDta7vKeIV0nqMvEJVCsi8rTIwn0ekf4TiIlp4J8RgKGAQj70yTW1lYNzhWV4GudvP_EWoozExaZ0bT4dJ_KiAkdqISr3i0o.mp4
|
| 285 |
-
Copy of SnapInsta.to_AQMnpQEoIBNiTuR8GRzGYg_o_d24N_QkHrr3dP-vweAF2rxlEFQmFCe9TMZImP0xsd83FW3JyYo9JW1S_Ah8RSx9g3Y9R3DSwPt3BQg.mp4
|
| 286 |
-
Copy of SnapInsta.to_AQMsNF51_pFEZwzGoh9gTxFd9qtu5xi_oo9g7PB50B347wksGbh7QPbP0suBH-bwnoc0ae0Lu9MaTESbTS6qkPnuByttADPTjGmLjDg.mp4
|
| 287 |
-
Copy of SnapInsta.to_AQMqRBkzYseE4drD-7KnGwDSh1Igz7mv1U7AeMzB_B5-vuYXvN3k4Lbj4TQDa6UigLshgSHipVMxd4uOZc_gK25SnizefSPiBoccfkk.mp4
|
| 288 |
-
Copy of SnapInsta.to_AQMQrWBgv4NuDgJ6YdBsRG3a5al7fdCw6Jfuub3nPbeXwR22pcgGLhA_AKZvW4JJmUl4SAxTDyoM1dQvxpfFFf08JtEAVH0m8Vzczls.mp4
|
| 289 |
-
Copy of SnapInsta.to_AQMqMRlhRGUWlDraQ8ZYtfmTTgSLFqQSadmbpMYiDLP84yOr512rgQ8t9-3wyHjeX_8_QRrtcS6qiqhw_-nzo5wcoAU7VSjc9uqnIGE.mp4
|
| 290 |
-
Copy of SnapInsta.to_AQMKqmIaUkOynWGIkxq2d3Ci32V-YKcSaGtB0pLxCcK7Qe3tGwBICFN4myXaGC6dolgfF8JL-9knVx6nCC9gztSoH9qVAqyonp1pAu0.mp4
|
| 291 |
-
Copy of SnapInsta.to_AQMzV8y0ZJta3mUnoaoXEFxE9Lsi-0fXZ5CopNS_uJ0yz9WuEjpqL46aVmvjZqchS9vto6nhE8esmFpVLUTegzozLMoc9_JBZ7aA-hY.mp4
|
| 292 |
-
Copy of SnapInsta.to_AQMythBH9Cw2Gz30cZh2c74mDmm4SGud5IV7SVz50d0QrFEOW1pj-9OBYvuWNHBnCjV0hC2WLYhA1uAWYZ5ihXJXqR1527MZIWpN79w.mp4
|
| 293 |
-
Copy of SnapInsta.to_AQMZuWnJCLvbHZ3Q6IkL70z5R219v2aBvtCdFkuMcmLkhaA-ipX-DWHELgmVSb8gl1aSjjwUn1wvwWJ_BNNxAdxBxhllqy-vF_YPzKk.mp4
|
| 294 |
-
Copy of SnapInsta.to_AQMu_5yrkRNAa4Ksy1hTBPInFvikF3UXsw0VUeKHA18RR7PyRKySNTOBEvONuuCRMR9MH1o7fHsC-h7AUSQYRhTBOGzznyjY9eGx74U.mp4
|
| 295 |
-
Copy of SnapInsta.to_AQMuFy8uLszNMm97MBTcu-0Eec2lfEJi0prqzmOm0NPDx9U9I7utp7ZMM-t0IMzeDcNEtOl4AkiYgdsCY_RjzO7E6ow_vSLnngvVXjg.mp4
|
| 296 |
-
Copy of SnapInsta.to_AQMxiDEW8kAQhc0Mx0AFq5GmYPCJKWtHiX7KDgoTsW6fpsM3OFz-UJpokWUE4bKFqymVhCCz-a5CSxOa6v7nJNANYEXegfLOiU_5ylM.mp4
|
| 297 |
-
Copy of SnapInsta.to_AQMZes238Mbmr3671Pa-URI2GR28P7r0OLNHXZD3QPChwzT-YdyedL7Z6U7xrUFSI6X45btaUzHqC2QKbKuOaUUx31SmQSZVR4aq59k.mp4
|
| 298 |
-
Copy of SnapInsta.to_AQMVWnU24Ou4bCQcrbaOu6bKLpKD52qQ4pcOJ5xw7qnHCtHG7Luo9TFJ0lI1tXmXFSJ9tS9Il7iTSwaTZuR-JlExMmsKJOakZx_4Doo.mp4
|
| 299 |
-
Copy of SnapInsta.to_AQMwl8pNE79BdvuP1kPzHP7Kt7jqAJu_aZLAoSPBjdr10cLGp0qiQArsKOeUZd4KxlRbZWh3wJvpWe7EO9Z760qeU75K-IVSVYYyCAc.mp4
|
| 300 |
-
Copy of SnapInsta.to_AQMxRHgEfqIe-a6U8QH5HQ-cFHcMKX3TmH4MzsIPZ2qz2aLutWk6s30rMCe1bWjJ3Mj6_LgeHFnjUfsU20b1D7YrZeBmZFds4woBjtg.mp4
|
| 301 |
-
Copy of SnapInsta.to_AQN7Z_Hu65ZFKpocBNIFZef-6Yao68UptqIbP8-Q9fcAN86-m1BjXeJG9cJ-uYIHUH4x-YrDP7JOxpN6KilEv5cGfvPUlKgniGEmnxg.mp4
|
| 302 |
-
Copy of SnapInsta.to_AQN_Cd6xn1_a2MDXLaHb3Bnqw7Ka6puJ97hitypJ50mLWCfsI5FVf-epkOAHMmHpZX3X6GZR4WiuzYjGSLCuXG5cstaGHc8-HsM4uos.mp4
|
| 303 |
-
Copy of SnapInsta.to_AQNcP6-msGaR1wMYJkBWOXyBbYZznbQpQdOU2XlftXoQgfBDZp8fJAfV_D7H7UpgYsDlgk1U0kX2xg_x11kOSiQACYDQ-VppG9Y92-g.mp4
|
| 304 |
-
Copy of SnapInsta.to_AQNExyw3x1jxZjobL8WUGzOx0VV5st-aV66N3d0hZ6Uqu1We2yBHRwUCMYip7O9gI65xP65Epw5zLy9ILDUgHDf3h-85nvybaPpR0qg.mp4
|
| 305 |
-
Copy of SnapInsta.to_AQN0OL_pAn8cKcSmPtVZ-TdXovUtHKKBrO5U1DLD17KCPHzwJzhiXJPf0fW1d2zPUenKPEnNHYmJQOLEVX4z4UDQRjQ2PxLY_ARfz_c.mp4
|
| 306 |
-
Copy of SnapInsta.to_AQN1nlaI0vwJp523CQXNb2-6yUAN3Ou-MpFgVSiLV-8SffP34XifpIKSr_F4goN8JuyxCcZTqdjdpBvgxCRo8BSfOtyePGs1uDGKziI.mp4
|
| 307 |
-
Copy of SnapInsta.to_AQNBvw6nsGYtglQUhqShLogCOBAjS27quKN93dcTpTVifQQC6_s_LEdOs2UuVq7ai1n4k7FX3jkhqm6My3tR8CZ0y9MdS-ao9qe4NDA.mp4
|
| 308 |
-
Copy of SnapInsta.to_AQN8GyobLpXHCumpKdkcvjRTE846Nq8q7Ta4xONk2cPHSXuE1nmVn4wsdKzpFwMKoyqdHuesjCwJJD5bBfY7QBO3pLEA4c3lIzphJic.mp4
|
| 309 |
-
Copy of SnapInsta.to_AQNbIE2coF_wcVOLSbfMxdfebsla2nI3kcGQJjAKZBQFgsETeWum8OgCBYsUMYhGOUb07UixAsorsoKVRw3bb5oO3aVme_X4GZMpmD0.mp4
|
| 310 |
-
Copy of SnapInsta.to_AQNED8zeNUCZcqYEgG4Sc-Fme6GDzPB1N8x4kIaC2nH-94pvEvNYQDuTv7X0iy3Y7T2WqpzNI-qnzF8HYUXuzdGGSR5BIIqNrO7xC7E.mp4
|
| 311 |
-
Copy of SnapInsta.to_AQNhPbKLdSj-mRE3J8R72_85qvM0Rst46DO5T3x3iAVAqxykabgjCVGV7PQQiEUD1qgMlNPTB0uEU5APJ5HyilNtkIdfqWctU2UEoy4.mp4
|
| 312 |
-
Copy of SnapInsta.to_AQNl82Y4740Lg_r7iyvwNRJ5T7JehpgyNeEfbNbBldQgc9bfSG93R3Mq3FVrXgOYI-Az6bgwQPEDeuPvF521J3lEbTrcvVzsVtjDtco.mp4
|
| 313 |
-
Copy of SnapInsta.to_AQNl-ZHsjXjqkTv7a1MFBhZu8Z9cdMOSvZP3MNzTnQmVgoZOYOe5XMJN4RGqSvYlwkEEDCbi2aBAsLZfumtdgcyvRxw9NIdCN84WdOk.mp4
|
| 314 |
-
Copy of SnapInsta.to_AQNf47Y5eCg5tsOGO30JLFkplLxCNWvPmWLqWtP6omfMNEVH4qXm1-1QXt69m0mwHQaluJhmUVOOw8aWbOnRSTrQTqaFBGKpD3c0wt4.mp4
|
| 315 |
-
Copy of SnapInsta.to_AQNgp_p_eirtIwjY5iS8Gz0TPJWWuzin_hvo6s5QZ6JrVkneABQhIfBTClJbeQfU8J7SUGYSaputzKINebBQ5eiXUY7LPbdX4s5rNyU.mp4
|
| 316 |
-
Copy of SnapInsta.to_AQNhCFsYTfeqS6kSUig8MjiGicEw5Al0PYH4sbi9lB72RH1ELKIqfzrCyvLlcDdoNVNIjL8-Ztk8YdktoZTTI8NgSuwuWhnm0KX_re4.mp4
|
| 317 |
-
Copy of SnapInsta.to_AQNf8LAq6ep6xLt6HMdNrLad7abc7TN6K-nHV_BK5UQsC8827Lg4jL74fNNfVWXF2pUOkaG8O6-GVleCMbyIDzIzWL6MmHXpJbf0ttM.mp4
|
| 318 |
-
Copy of SnapInsta.to_AQNkceGq0o4sKr0VD03lPuFvKQHb6z-3CvX8Vu_jSrgIPYC_mg8Bc5dDhamdMKin1E-jNhmO1xRv7p3qknCKG7dYZAJLtmvcFCVdonE.mp4
|
| 319 |
-
Copy of SnapInsta.to_AQNGfBrarF99za6ruCOd0tha3Yj4XwDlalMzLdU2d-jYXhqZBM-rDiHVohbM5-NrrJCZsk7Za73DPu_HMNhAzcgZcLwPSWwqdHneUeY.mp4
|
| 320 |
-
Copy of SnapInsta.to_AQNGBYK3U9vJadMzzLxaTWRo9czwOONZ51L2qlIB_5519uWmCXEVutiXL7DywFUBY-fPOQf8rqXIxXcENF-I4ddKX0svmjIR-zZAaaQ.mp4
|
| 321 |
-
Copy of SnapInsta.to_AQNp666dyGdpnnFEJFF7ajMYLe6dmsl4Zt_0-vdDTk4qveV4NS3qYPRsuQqSqNxuybnQB6lmuV-MszAQ6zj0hyU3NiOlYe0xhGZcQR4.mp4
|
| 322 |
-
Copy of SnapInsta.to_AQNtHjyn37NEfjGkQki-eYz51guu3KvewGdL4nTgJxFTOlbi_I8NMtkF_dyicKlDxGvJQEDS5K-ZPV_0CpzIHr7CSh7b9ZbVFvZGe0U.mp4
|
| 323 |
-
Copy of SnapInsta.to_AQNpX0rl6QiY5aKvpUT6IlBt8iJM4F0qt2G4Jj3C6O7WG2hm6dCsp6MLva42s0a3p4pxMweigX9zuRFYhyqVqZUyhq6pMZurCxedXs0.mp4
|
| 324 |
-
Copy of SnapInsta.to_AQNR7WQRuuj-vtFojxOiBaViMzu16HjQDAhEKeTAPwE0WcGoK06oElzuNNdB89cVFUlIuB8BYFodyTPybiFq0AYbjVYtqxyCUtIonS8.mp4
|
| 325 |
-
Copy of SnapInsta.to_AQNlKYm5xzZFIcgun1fpxPPich4iwgTMohBre9ClZ_0UEF5pB0nBG4rYx7UtzzeI8k1-ceGPPoPgRHo5r4fOdaFQR3jR0AP1SRQdFRw.mp4
|
| 326 |
-
Copy of SnapInsta.to_AQNp5yMXVCDelzuUj1NW6souDLNt25DaGbDspPgrhRK8jpvlZ9FeQ7QC19GSwDs-CWHDsOvKB-qEArsSy71wSYWayt0C-zsE5EE0ZQY.mp4
|
| 327 |
-
Copy of SnapInsta.to_AQNu0CeNNv0oWcIryByrvT7qAVBFBmGvfRjE157oSH0uRJl3lDV_K0Jh8eqJwyiilotvSH4PP53cn1TbVEpREQFktqEDCIcd9KGXnbA.mp4
|
| 328 |
-
Copy of SnapInsta.to_AQNTT1-r_woyS4yTmHcF2R-ZOx3-sw8XtSWGvQL13II1OeHKY1MvrGpl5Us3YXkTc-QTUX4NyUvFn8TSK20xX-zgvioqg3ntMjZGgls.mp4
|
| 329 |
-
Copy of SnapInsta.to_AQNQ_jtJZW35Nx8y40FfnPECqnqMoE0I6NqekTXFNYhVzRvt2mnS3QbJncscK-wcyNjiwGIOwCTFBiaH3wMamA3kp5y1me1aSgeTOsg.mp4
|
| 330 |
-
Copy of SnapInsta.to_AQNSM2JayaZwqGXZQO14kkxHxfMdQVI_pki1ijFaHwU77SFZXB3PykNIaBtTTZJC_FLP924Nd5az2ESpEYZRg-W0JGVMCDfozQUA4DM.mp4
|
| 331 |
-
Copy of SnapInsta.to_AQO-PsBtMC2PBXK2hCCepANlDhPqqoPebywj59hOq67N4zOWUiN1Y0HBBLP0H7QK5Y5MVMCAYkx0IU9Q7ZaX8RiqadV_B1jWVK2yRGg.mp4
|
| 332 |
-
Copy of SnapInsta.to_AQNWdkXq8gvnURx6XquMHuMhylCIokXa5iBzhRQok975ocNelFO9sDFloTI_9zRVCi5g5NkYDmFNR-pXMid7JBHXBq8EnW1d_m8hSg0.mp4
|
| 333 |
-
Copy of SnapInsta.to_AQOGGZXDD96tPOQg_9dl9V9IR9a58fXYjUa_PMRD5uJapLYx6FEEY5gi2KULqL95El_zmfQeNSVCOlUqcza6ToItJ9mRf_NxKC2enPw.mp4
|
| 334 |
-
Copy of SnapInsta.to_AQO7-wXpI7K1mf3HvhAPL1LzIaAv-pzauo5A6259tZrHi_PrTkCMpWhThct0-sQYTyNiAkNwYP9CGPOf0hyhUL4pWoqS67utA2_HE1s.mp4
|
| 335 |
-
Copy of SnapInsta.to_AQO7WX6LducJGk7omNuRpHoXTu2yq0sDx39G8-Qxc4qnABfNIo2vQJBizu3Vcjv5b_I5lY_KeURYOd_eOZgqWxjb1ZSXx3-pwjtmY4g.mp4
|
| 336 |
-
Copy of SnapInsta.to_AQOFOPYU4D99Ee21PwFmmlFz9r0eHUYY8NBjh_wdxv0TEwkjEra1X6giwsqLJq00_SjQCNhSBUh64NYqFayB7977KndM6uPWco-xpsk.mp4
|
| 337 |
-
Copy of SnapInsta.to_AQO0iYAfSSm_AlzF0UT2k_b-Tedt3MzzW-Hu5_NAW2Dgs3-21vIThTZ7P6XN6Wd8hILlSYjNpF2oROToMhCXDBjlT4MuWG_XlpGhvGo.mp4
|
| 338 |
-
Copy of SnapInsta.to_AQOE2bF-mZH3pAl99XhqzxUfrCsa3OXUl-lG-5zQx5MtvcXX3ZEsWrQeCyu65oGHT47HAkifnA6zb1VbrHkTFSyNfFLUWwLqsKRKBSE.mp4
|
| 339 |
-
Copy of SnapInsta.to_AQOf-vL4fMb9cIv9Fpq5WQPPLLoIFThg1ynJTpgH0KJgOJasQOY_RZnvLAXAcbYYkIxfwvKH5rerKBWsWtrVy7AhVy3wbmiN1xyvYec.mp4
|
| 340 |
-
Copy of SnapInsta.to_AQNyuzKG2oLHVglJ-yS7pxqzYlfJKL2uVtjRycosiuV4n-lPUNGToa62V0hXG0JhqcruHvCS-AzoE89wuj2GNyGGsDeStv-C5T9HFQQ.mp4
|
| 341 |
-
Copy of SnapInsta.to_AQOgLYjn57d1uYOHqpbccQA11Lyr9Knf5zpEOYDgN0gl1-VEUyMTczTxZCHvfPo3ciCFMb7ewyVf9m8ogKyEj4Lwy2FbEnD_qqPr-6o.mp4
|
| 342 |
-
Copy of SnapInsta.to_AQOL6i9M1Y0Wsrh63VhPeKo_XI7khygFBagEg-MJp1PF6HLilDcdrRStgnWes2zhhepzY22v5uSkCDRcsLP-Huk_OdsUyRQtDSwVNdw.mp4
|
| 343 |
-
Copy of SnapInsta.to_AQOkEdGRCBVGMh1rb23Z_NDjCL1Dyu150RWK-m9cLQCnq28EOOzuGX5cYFa3ABMZnyXtFGNo430z5eZV9cJSz3TWk31FEwDrgYLO5DY.mp4
|
| 344 |
-
Copy of SnapInsta.to_AQOk7K2s3bNNCNmfzna_jNZKlKCF8Xn4jKFaRb1fr73KI0BGUaj_WaJVCJHuZjKS9boxVf2EiI6W8pQyaf_sexvVaMBG3Yq4Qd4A8SQ.mp4
|
| 345 |
-
Copy of SnapInsta.to_AQOMEJzLVpCPdY0kXF8qyqvT6qO_8IyROk_mX_HFDUmQUehScVTkj1qTGHqmk-he7Qa-z-T6tLg9bxc8QW9X6lUKAhR9tWAA6Q126Zk.mp4
|
| 346 |
-
Copy of SnapInsta.to_AQOolfQUZh5kT4acw4RKTorJcu6XrkZZDARGRK-w3BEHy-egP3ThPLqZNUn9ZstQ63C580aIsWw5tDmrtti1aJRchJdJIxzQMNAWvLg.mp4
|
| 347 |
-
Copy of SnapInsta.to_AQOR5SYQZE5AiWTEDACdtRWpcFAERiolUws7JEjZmvVJcHzc7j1H9_E5SiVDsCrn-GBlaAcSpiwyG0AyNfRqJXwmAmJYHc1ayp52u2U.mp4
|
| 348 |
-
Copy of SnapInsta.to_AQOlqUDnx6-TzUWt-eprTR79VxnhTsvJ8kXyUm9DOZbACQWtqk0JEOER7OL8am-oE7U0aRVDSSPyP2JvvZletbS4.mp4
|
| 349 |
-
Copy of SnapInsta.to_AQORCTsQ5NmSBOLqVcNtsMsyT-gIV_wBAdjfGCgq9uf7TmVtMhPvjmbclDOipe2pLbv2JmHds47RAEno0mmk1iiQ_p5HZRu9neMicXY.mp4
|
| 350 |
-
Copy of SnapInsta.to_AQOhMR1lftT5IuBVODqchYLpim8GQIb-oR4ioAkgrwHcEJJkugwtRylxKD0_XCkL_7fnVULqgxdP_p7cbvblojBlDEFNmBrj9QiNkyA.mp4
|
| 351 |
-
Copy of SnapInsta.to_AQOt-PVeHQZp8paO7itoojdM78ttbagQpu9wpbVDNy9_-31Eq64o9mLIQaulGZmGRUfJv_teiHZq8Z7RavYYOBrXGqESi56FjHBm7rs.mp4
|
| 352 |
-
Copy of SnapInsta.to_AQOv_iCmQhIBzPG2dEcxoIExgbNUl9gKWGd7RT8cR3qLBWphcfBbi2OYDaV6alAVrt9Iow3ci_4vC0MIBsApvFjZCAQw0ns6fh_GA2s.mp4
|
| 353 |
-
Copy of SnapInsta.to_AQOTU1xiKrMrSwUX8l3s1NrtIzhOIQFg0hdaMU3WnqxaNuvdrEPjq8maHfP6KnLsOoAAjf0DzbyF0Q0UJhk9QZinZuPbjm6vmfZmRNs.mp4
|
| 354 |
-
Copy of SnapInsta.to_AQOSKe3xq6WrCrQXUoH_uQN7_J0_WipbZ3GdUdJB7Sus46BmfrcNjkIvlu1IJhW2VuaHfOoh1niF6LQQ-DU0_KTLgGj6qepshVKdIYk.mp4
|
| 355 |
-
Copy of SnapInsta.to_AQOspWd4aQEYDXgONwO0oN1WhLmUDSZXhATvIltON-ICgLHcCqgc4s9y74K-CGcbQC_waNsXPOuEkGKBbBG0waHNaJ_t7PNZB5rB0xA.mp4
|
| 356 |
-
Copy of SnapInsta.to_AQOy_GFB6KOx3yH-FtgRLu0fGl27ePyYRXH_RvCjsgPde7qMGp9awIpo1WyRblupOlUrtLGIuqi73GI-idakktlm_yh68COguVMJgmw.mp4
|
| 357 |
-
Copy of SnapInsta.to_AQOyz-mkgigIXQeVC9K2sI64pkhnLpQgEgyv44c6r61AM7TDeNbSqF6cVXvJ8mT8gDD6v4fYSJvBGRSrXf5tbiW8GzcVvspr5OzJcmc.mp4
|
| 358 |
-
Copy of SnapInsta.to_AQOu3F-Ir1ozVJdY9DExI3OqpNE-vaxhclme_lU22kFrdSQeqLz02L_dEMBAh3fJ7NFa8L4_62bmyRrnRc1DflSNQhrHOU94IQokCVU.mp4
|
| 359 |
-
Copy of SnapInsta.to_AQOsow3NzBuDArcpGIYj8wkAYqiyYR_A0Mh4flRXRDIAkadLV4YT4G3JuvOxNNC6fvNOaYW2CUC6WKYxt8X3hApB_OlEOx79cEy5ysM.mp4
|
| 360 |
-
Copy of SnapInsta.to_AQOWkszQ70n8Sqo1YR9CdyUknHOZsbk2QLNHWLpG6yjTfe9LNHc_feTjMuuh-j88vZLTtggtpS1NtdhnwTF3zNDHFIFp9BSKPara950.mp4
|
| 361 |
-
Copy of SnapInsta.to_AQOZeJcOxUap6NpBwpM2eVkhFjlEN2LTXlIc0RGDUj7CG7sSGUYNFiW5vUjw-Mlr4o4AIqW33sZOCJhuPFIiRuoRIPArd1dwU-5uaqQ.mp4
|
| 362 |
-
Copy of SnapInsta.to_AQP1T1CT75HUGz0xuFXU6YF6WYWI6TFlweSxPmncXQXLj_kK6M3Nzyb7QAZUvRbcDSBIkUwChRKljB2aDZRVWAYOc9ZailQvsymo_LM.mp4
|
| 363 |
-
Copy of SnapInsta.to_AQP49KhE7Wwr6fcqLeES0N1Tl-rR-KgHdEngf16DS_Dppc_dxD_3OSenk7Pi22dv37RH3FekJK3TJ_of1NkTCQgw_-VvFUEU7ymulPU.mp4
|
| 364 |
-
Copy of SnapInsta.to_AQP111ZcMGfzhBWiY1J4CxdiawXuQz7KlLJZcWwSGQVzU-B6n6GotRhMdX1h7hdwz5W4xkXJA7FbDsXOYXpzSwcC.mp4
|
| 365 |
-
Copy of SnapInsta.to_AQPBMyqBgikkFZd_WyR5yCWev4vRm_O0MFnL9EH_lFEwTIL69pL0eXh0KbknsMTFPKcqi8F5X51OMaG2LCEbH6z92wc5uk5qxRGDTUg.mp4
|
| 366 |
-
Copy of SnapInsta.to_AQPB4oeqXlRL9svYOO-ueuqcaUTRqNM12Ay4evGYKGAVA5yebeTrTfxzbYfw5fw6i-Wc31u1mNIuc7Yv2o7nIgdqTknNN4OXD9NrlqM.mp4
|
| 367 |
-
Copy of SnapInsta.to_AQPdf8mJYZlKpNHTGJ_tLWil5jWhR9f3NnRkeObLG4dVcUwJhTnJT2Jhh-d2_FYpb_ZcMg7hpXvtyojM50mSN11jCGuGf9XDKRf1KZ4.mp4
|
| 368 |
-
Copy of SnapInsta.to_AQOzNzPsK9WL-0mp_qgdPMpMSAGJraUHkEOudrVIwQSqaqeXn90E7PuHWa4J7ir0-irPD8Kz0bkV5hHEYRp_aqoz232d70pJ93FuvKs.mp4
|
| 369 |
-
Copy of SnapInsta.to_AQPAvoFEeRD8T_eW4twwXJQvWJN_Grpwew388rzJFibQjR7sDZXQT-Kgt4PUtrunMzY9EyY5IngBbgXZdRAvfViOjfRYJrAL2YTzOkc.mp4
|
| 370 |
-
Copy of SnapInsta.to_AQOz-szdnsGQSOjGMlZho5AmXq6L7xykDpBpBa_EnH5jYEJAjIaaghnlJToWfa5swYJQNprp7NCVo1WlkuTzmgDFj_a2noacBxFhnPc.mp4
|
| 371 |
-
Copy of SnapInsta.to_AQPJcavX7dbv6rfH2ExncIuxidIvrAC_uYaRAXS1JP09tyV52nUAkN-pPwtWIaWbt_vTlfKKxoiXQaA5Mffrym4Q.mp4
|
| 372 |
-
Copy of SnapInsta.to_AQPNHK63ahRvTGywTQ3n6SLGC4L_VOmFVmaCj8-e8iU4HW129SbFhUfZJthwmAMOeBWYkFHP8o62b7WTbU5_-mqW3tzjN0sXUEb1n14.mp4
|
| 373 |
-
Copy of SnapInsta.to_AQPh43idkTQwQxR6AJoMFneAZa_i5OkhRNJ066fZRzZNMR7C-qNV04OSTgnjBzFZVGu3B26JHMvPPyX42Tdze4MhXhOeU1fFOZMR96A.mp4
|
| 374 |
-
Copy of SnapInsta.to_AQPgisSvI0cpJUuxxZgPMklz-tljYuSAPk9xcSydgKesWUCFhfdwNVhFwDliO6QaRthJmBg1AFQyPA2o5Svg6ti3iNrOQB_8UMlJiKM.mp4
|
| 375 |
-
Copy of SnapInsta.to_AQPjP5ejS0zI-NUBE5162mxrVkOJRzpmKTyIJu44elIrvrwYd3qdhyPNq7qJwHOmBZg4MuODk-8DWtnz0DXaHeVy26P6zfsVtN_EyRQ.mp4
|
| 376 |
-
Copy of SnapInsta.to_AQPNprrmv-GKOpEAbZS36B_2BJOsavF9yOY0R-O56ChC5u80tLmUT3rX_0KA54JpvuFkdwRaBLDallQaEwESO_gMkCoAgO0Oh-JjWpQ.mp4
|
| 377 |
-
Copy of SnapInsta.to_AQPfo8-IOuBr_WmkuKHpKmg5dzTuzDjyjUMeM7Gsi3SRynd59FN58nW5BzUa2pVysuGqzaIv-A5d6AemIWNfJ6K6FjqFFTrU2yhvieI.mp4
|
| 378 |
-
Copy of SnapInsta.to_AQPhcwlpS0MCNdwfYmU9mjbrjj1rdbJQ8vKmVlSzAkCsY6DaMT4SGVKo9XUc6Ce4eZzrEsd0Wc42xM7ztf1jrFZAHs7v-zVA6Wy9laE.mp4
|
| 379 |
-
Copy of SnapInsta.to_AQPf3Cj6PWVoHIZpkLqKzWwyZnFyIC4lnx_U3vM5L17XLPeEhHCr9JFGCHMI7BDQTLLlTZG0ZJaznRyHcR3lZvhRMAaLu23axSkmRaY.mp4
|
| 380 |
-
Copy of SnapInsta.to_AQPGlIt6VyF8bmPaQRNsC1PA33wBmwqlstXeKk9OsjmAdVng1AwyViAsSZOjNTeln8PxSmyjX33koIyK6vFcMfkaSkKIxK2YnKnnJYI.mp4
|
| 381 |
-
Copy of SnapInsta.to_AQPysWY2Fi_klGNf-PF6LBMUq3Hpd99C5jQKqVgMfgWOuC6HExjeHjcZr6Lf_yN6irbMyQOEkVgf8fZkZBmI-LSrLpARIzMzDpbH13Q.mp4
|
| 382 |
-
Copy of SnapInsta.to_AQPxVBrzmBDrUfwACLs1RXZ58gEqfXYX_292-GOeUf1KN0oJ4A55Ps5__OASy8-F1yd66OyOaEIdcHdRv5zKe68NO97fjRSM2bV38xQ.mp4
|
| 383 |
-
Copy of SnapInsta.to_AQPzze3-pBPR7iwd0SwmtXIkNcPJJtgD6JzOFJQ8IqsPRIEnNHZNT3USOpdgkG7enehwCoA40NV6SRbA-RmOAM8k21gdV7HFMxylBw0.mp4
|
| 384 |
-
Copy of SnapInsta.to_AQPxJc68Pg2B0UEptDIftzAvINe7nBfhy8kmzsStiJrm9uQbcEo11Xitq8Osd7yejspyii5M6MU4VfibsjitJY4aDgpgdvLWFghzMC4.mp4
|
| 385 |
-
Copy of SnapInsta.to_AQPQVYfGHk-FuVhc5qAkmBldfGDliwAP-zLgrQbF3wnPP8UquQ7A5fpY89YJO4JtE7jA4yJcZZVAml4PXUlCf0g8HRwJ9LNohiVARZc.mp4
|
| 386 |
-
Copy of SnapInsta.to_AQPS52QGcJ-gvQT-vh8ZY0c7XwvDLo5hJvC_au9PjHFMC9WIomdK-3icG1rRLJQ7Z-QMuO5krpomkpEuWcQN1SQjgMhsWkk2KgZiJZY.mp4
|
| 387 |
-
Copy of SnapInsta.to_AQPTOaDzi57cH64K0QFXBFsTtwujHQ1mAe4cQLJ-f_RWYo6evDUKEbSBNLrM-9oKpDZlLOD0c0ie8gOEmb9hIhy61WQNaa7x76gEoOM.mp4
|
| 388 |
-
Copy of SnapInsta.to_AQPyN6onaiIOGTOGvnxxgRVhTCPXJMzOPQOB40H6VzD_rkZli3fU6gHQWJ-Xrzp2la7vkce0C1x1fm5ptAGbuC0cLECMVNi_Zv4nmmE.mp4
|
| 389 |
-
Copy of SnapInsta.to_AQPw7EDS2GMRJmF1vDPnWu06wiePb1ttWaC4ZCb4tbLDUNsOrfD-UtoIiOUdDDABbvLTwrCXNwMZwJBMFXQM2cg9QLJMbmr4UZ-zDDE.mp4
|
| 390 |
-
1.mp4
|
| 391 |
-
Copy of SnapInsta.to_AQM0AV8P5J4KHWNGtQTZJKW2_C4wjhAmJmN34wm0C3FaU6zuUcLYq03U66F3bHqf1gerH2dthxTFvhn-rHr8XOcKFHg4vSWaQdQyuuo.mp4
|
| 392 |
-
Copy of SnapInsta.to_AQMbUdclLkquwn4ij_A2AMrv5qkrrYj_UGbYYy2f1-K9HWnZsgdswEX81z7dhzMAZd0lj3SLBPuxbLpOtS4EhcybMDKLwZbC9WRZZT4.mp4
|
| 393 |
-
Copy of SnapInsta.to_AQMCuEbwvHtUeWjmChPeuhzz1Y3urXT3hFIs-Mb3Dfe5phUzHWyIAgeRZzKzwAZcUK95Pt-wHBytKa4Ztc33PgOlevA2_bltvFXEoFk.mp4
|
| 394 |
-
Copy of SnapInsta.to_AQM30cP8v7GVl__0ZTd1FmgnU4WpWz7uwVouQtcW1ztqO1N-EzKc06FSQPTd1m42OcvtJGw6i0le3trw1DvP3JFP.mp4
|
| 395 |
-
Copy of SnapInsta.to_AQM2Zf_eITyJUdcyUuQDefdg_ZTJ5OJqaKQFeaBbDw5QAKNlfxb3HDQQjTW96L_9WUoTpMAulwVgci_arX5_06H-X9ZiJ1rHq8oEruw.mp4
|
| 396 |
-
Copy of SnapInsta.to_AQM-taWG00NndZfFA6tiIIvruXWK0usqBdLa3K4mdGfkBnXIe2N-CR2G2ZIvmh3Qsn-XlHmBxmvEknSBzy_aJQ5QvkcPP47DhM8juoA.mp4
|
| 397 |
-
Copy of SnapInsta.to_AQMDehxo8MbpUMHcVdNJre36crGRgIBg_tTBvQeJdKUFi1Hk6_BOiJTtTzJEqjg0xET9SX2jQfiFqqQxSG7Uw1gngtyX3bPFcXU8KvU.mp4
|
| 398 |
-
Copy of SnapInsta.to_AQMcAe-ZHYy2ShYlOcr_0No40moUJoQIh6UwXzhCjl543GHSuRZ2Je3rP6MpPMyaB2KfLaQLxSWrVD5QM0QK0u40rl2aK7VesNVR4VQ.mp4
|
| 399 |
-
Copy of SnapInsta.to_AQMapIwB1pIZeDQdAX2vqvDRUJ5-NVUnBzO_CedUnIhILLNdP2GABJi4_Ysc9zjfrCbg2pb-XjFlOz5POp0KVuCEeGLcouqFsY_6CUs.mp4
|
| 400 |
-
Copy of SnapInsta.to_AQM_OBI46sSkn5EG1jkqOB-G8DfYFY9fIPnS7_dwzvUbFpM5llVobirWXS0xbpYFwoj5Q06ySa6135Xje0WciKfvacWtqd5ffq0i-PU.mp4
|
| 401 |
-
Copy of SnapInsta.to_AQMqMdPDWFb5kBxSUepVxwJPjjyUNQkkuLquChnl6a5T_b9-bjKoYAP891O24-i0oyBgOQjIMbbGwAcaxnoDrhc7yyN_ZTn8elJOq7w.mp4
|
| 402 |
-
Copy of SnapInsta.to_AQMHJAnu241vzf2zoU-DZuUjoDIJoOcsfBIl_D9GruAtyqGC3LprvCtyslZ_PilOrvZ2S5zjwCBVnaJl0SWb-6V0v135xXXH0rh7xPU.mp4
|
| 403 |
-
Copy of SnapInsta.to_AQMlz2zyLiIKCwJU1NVA_xgPdNndjtwkv8rGV45sBsJ5pnsKpH5uFIxwhf99zN5Gm5Rg4lw0AvgZobnwfO5yM9SPDiXoc8qg7dGWmYA.mp4
|
| 404 |
-
Copy of SnapInsta.to_AQMgGQvzv2yM4PfM1YXCQzypn0x1VZmuOMdIN8657gVeQAVU_ZjP6rHutlcsCdpNs_THn2035i4mDF5hwPKD58vg1QYGgNKpe3wtXoo.mp4
|
| 405 |
-
Copy of SnapInsta.to_AQMgVWgRvsfDGKRFakfly2zluZgYwHU1lxpSGvM_2mDUVZC7MjWOdIAiOydcvEjyRMdjOOaesFfuGf0_Mpm9hUg9sr59ICoLU1Sjkm8.mp4
|
| 406 |
-
Copy of SnapInsta.to_AQMfVEZvu6il6mZFOPH3dPAJiuIDvFJFbY8taFNbTreiOh0UvG-7EOmPk1JBy9IBCEYMpzyjbp8frwmo1eIpYA3kSr_ngKPFRaqrLa4.mp4
|
| 407 |
-
Copy of SnapInsta.to_AQMjlYNzgCZC3vL3Cg1AvvNPCTjHh6VCUu_AMgTP5GCiFIv-svvLWplivwVXP5SG0lq6DXFHVhtex6Mp2T4McgH4.mp4
|
| 408 |
-
Copy of SnapInsta.to_AQMngwNgQ4-uydt47KTKwvYl_ITu8XZLEk2sHFkM7PVPitSQ6P04-stZ0Iqvb47Y_HXDINmemv11Fi07goPr-h1S9Z-MiwhVkQDKJfA.mp4
|
| 409 |
-
Copy of SnapInsta.to_AQMfMBpd2L9Z0Czg3YDf7kh7-I8dW_UWXEMutML6dqQ7wMip9-oKjU0J_lTMyRknQK-qj_fQiHVDIMsGf3gpUb_h86BCNrAmtleEic4.mp4
|
| 410 |
-
Copy of SnapInsta.to_AQMDJPqzvNt6ieNY2f0PeRf-tHN61JBNkriivpUi5WZPeTkeSoqGNEk9jHzCjgrFCI_cUUDh89eNcnujas3EWYhCbkk_MC8Qx3-SB-Y.mp4
|
| 411 |
-
Copy of SnapInsta.to_AQMTVnkomBHRom8d66KxbavAmkQi6W974GHy2U_zo0cf_PNe0bV4RrC819qn0BIfAYcClOCN5cKz3FIHoXfK4KRGKCWOhWz9hebr11Q.mp4
|
| 412 |
-
Copy of SnapInsta.to_AQMRdknj78zhisXU1tZ_NiwJi7t9B-Po4HxFrA2MJtKPuZeuz_GvuNrSKs97kuZnhzTXyFV6Np5QV9XOfUhBr5ksdotwi0lLog3sf20.mp4
|
| 413 |
-
Copy of SnapInsta.to_AQMRHOIHRUc8ScKfu9MYMgizoedO3oD6eI7_5p21z_YAZ-RoxheXk95_-ZvLXGFvkoCiYseVvFIXO4heUKpuyYHfxmP_3PfeuDiLUH0.mp4
|
| 414 |
-
Copy of SnapInsta.to_AQMzYce0rawQdYYpxUBILMl5qhZ0B587IMAIpMde7kUL9j1DxHpofpmu-DRotI7_tgzLae01JrX40G6pr3QHW9V2G7Lmp0wUiKj1rkM.mp4
|
| 415 |
-
Copy of SnapInsta.to_AQMZDgdOPcRVexYVu_nN4beHw25I6kPmae7uSOOksfNjmlh_AvST7GAPXpplscK97R_oaX_A2VK26vNEMYQp3H6rxhavYQ1nWo8bzg0.mp4
|
| 416 |
-
Copy of SnapInsta.to_AQMUm0dWsXHc3kPxUUAV9ZXirqrvPXX6y3mWCbNZNafcMb1L9YyyrUoHAhxs02Z8_1GEQ6rxAHazBp1Rjy-Lnhr9tXLZR2CV-XN5W6g.mp4
|
| 417 |
-
Copy of SnapInsta.to_AQN-ST2ETVj0MTxPq-nlA5Zb1FLhv9YE6Og67yBR5tRsCUMXw8QMCScsMnd9wicAG9bQ_Q1aBqQuHklqUc8s9wChYt4eXL2wMXZQ_Nk.mp4
|
| 418 |
-
Copy of SnapInsta.to_AQMZ1fjIu8HoK65HVR3OSFl98GUvdc1853q5Gjv3Jv20fgpC2E6A_WWkktDEs8MTcdOau5yqzw262gsGz3rY_w3CG28U1xccvOgRbaw.mp4
|
| 419 |
-
Copy of SnapInsta.to_AQMUBWa9p1NUdN816ggFrT7Ho1-mg_i6qhPfierRWZRPzpV-GfdnXtHdKxNwoCVlkVsUVQzyrmvqMpq1fK0OlC_i3L5UHRLLmxIjnww.mp4
|
| 420 |
-
Copy of SnapInsta.to_AQMz1j3RiW38iml_uOBpuUFzEUbgvN3q1kDA2MPDnZ7xZeaq6OFi808uDmK30ud27sgNbTOwMyA9H5t-G39dEPrAsgSwM96cwcE7M3s.mp4
|
| 421 |
-
Copy of SnapInsta.to_AQN6KMBKWKEvNMXgSg_mR37nQ0fFgXlLl6foAPaiSm-gyzYr1B0YI-YQxHDbI_NIorEcE9BQBBl8ErteZc2GK69jH7VDnez0oAjD05k.mp4
|
| 422 |
-
Copy of SnapInsta.to_AQNBKEngxB2f9cQc09aGfXxc3F5dxUql6W9e8IGjSljSHu_roCg_oAxdktm3TmRIoRiP7S4B-qtdXFJzzRXyao96Gj1DgthQHtfUpnc.mp4
|
| 423 |
-
Copy of SnapInsta.to_AQNAoTFxyRjF7TFNSXiFYoJC20AdiKWzPDe7XHN9uEQPWAO_ka2Qhqh9_xaPVNKGDq047Hm6tP4lFgbKCmlT9wIIVxRrspiW3f_WUO8.mp4
|
| 424 |
-
Copy of SnapInsta.to_AQNATjzghdgmmVpB4rh-_704ZonnB8oJVTjRiNOQ-WiKsAmikhmT9orgm8XHHBz14G-sSmIp4DCOLyQyBXqQcIvMXV_EWZhNPqtlM7o.mp4
|
| 425 |
-
Copy of SnapInsta.to_AQNebO7zF3d5kXPW1sQ7H0xFkEMRGmRTECqzv_fcmtCqfbPdhaG0Ev8pzitJ1DI5zDYQXFHi_dzpjFnGuv6HCpFQzmKsC-TU3RI3Rl8.mp4
|
| 426 |
-
Copy of SnapInsta.to_AQNaGS-0CfEDIY_c5z0DkSn9_aHfzLCkuZCNAhQ8tAS9nyoFVRZZCFbcAl6QH_m20rIvuyrm2QK0BgGo7Lv-CDZ0sueZWKlcluzpGHQ.mp4
|
| 427 |
-
Copy of SnapInsta.to_AQNb9RaM193mu_9zymMeJD2XWoqhJ-IkMk1ySPikT6GufMAWcHAHeNc53UudMEgULYAL2Xbsj1BU0y-hkB9WL4abYyx85GbOgcFUysQ.mp4
|
| 428 |
-
Copy of SnapInsta.to_AQN75V5zt-xZecmPMbYmFN6QsNJrpiL-AvBGX3qLrvfoliLzlJ8n61CpQAiEt7o6Bn0ysYNs3g18fdMLTI4Dk23o5-OvHLD7LM5PqiQ.mp4
|
| 429 |
-
Copy of SnapInsta.to_AQN9y3DSgXfgSe4aiOusldpgCdV3L6xNnbMj7qULmDQAkZgzuULaXIq0PV5KAroAujZ_EtncZGS7m0D238vi48uqpWM4QzKX8qhkC_0.mp4
|
| 430 |
-
Copy of SnapInsta.to_AQN-vFLghH2BswEyhZUfit0tIJLq1qGKc4Iyfim73wXE7tF435IyEIXvOJqTJqY4eFyQpEAAqvW41FLwFcGPxqaDrXXzHYeGy3_UM9A.mp4
|
| 431 |
-
Copy of SnapInsta.to_AQNI9FZuQU6IcHrWirnODkeOtDm33SV5QhzIsQhKfChVWNS_AxihImp5kRseiQ54ZfNhfn9jyg-JASoWNwNi4_U7diFlojlfE8WaoyI.mp4
|
| 432 |
-
Copy of SnapInsta.to_AQNjA3e1-jmKp4_kpkZIijrJp5BGP6-xan9E4oqn6BIETw0CaHftisMBTwA-bz3UmJ88nW0PPbrkna03LGQQLedtpJGwbhBtYG7pBIw.mp4
|
| 433 |
-
Copy of SnapInsta.to_AQNklKv9fh-G2DamINITsMltSBHj4TGJkgxMxBrefHNarmj3tEm2dF-LTN3WgeCekgjlXktd6nJZ8BPdTX_hOBsKIYgKONvDGN5IhhQ.mp4
|
| 434 |
-
Copy of SnapInsta.to_AQNhszwxAZFaLU5ZPBjkikBIXmcmUc7fbSvIAY5so-eP5xDpKRSZfg3ocXuBv7JiDMd9npcLQST1ZmyNweaxpWOC32Pq1SdPcVF2uvQ.mp4
|
| 435 |
-
Copy of SnapInsta.to_AQNfRHElJMeWR3NCpEutWe7eRZXspB9lzHVpTHtqpwFlfVl5Tzu0l599bCPVKuqquwiyx2QKY0QLP7nm4bCgAYGoS7eRdiWfE4p0K1Y.mp4
|
| 436 |
-
Copy of SnapInsta.to_AQNNrpM_f4WMIGGK5PccaRRZKtend2dutU_WDm6FCiuqfzkbU-fesLLWDEzAizZYDsldraeI1lRmM4e-cHVSxYcZkcGQfuW_uo140GE.mp4
|
| 437 |
-
Copy of SnapInsta.to_AQNLvkR0XC2aSp_WWld8vdKQvOUCShtlKherRkk6gSDkINX6sM3j0ui0ASRxLevVODWJ17JXz5_jiiUN-O1DoRmmVSmi8dRALBDqhps.mp4
|
| 438 |
-
Copy of SnapInsta.to_AQNKjl9amWtGWMu22rI538gDPYni0hNNJZN6oAfu6VUdKVWjM9w1VU-IqvXWswuGkQqfC8ylhPDSaE0wcCdKwVFpvvDo7zyXOeZxEUs.mp4
|
| 439 |
-
Copy of SnapInsta.to_AQNislmbBbLhlXEUp6th-BUKgdURLVDvO1P8y_65z6DBEX80wLp4Dwlv-3-WaMH1aLuXXWoEpbmua-FlfUJHZzPYOp7thuwHIL0GTVI.mp4
|
| 440 |
-
Copy of SnapInsta.to_AQNo2C_RqzGbx7dwJcqT_oBtfWbO4PhwnLD7oV6ScGahuf_7CPRnI19pzHQoHp6yrZlDsM3UXptRvFn-EiMzyVv6mVUXCYU2S2ljXnE.mp4
|
| 441 |
-
Copy of SnapInsta.to_AQNXoUg4cG8kmdeuL0gD-Ycqoe0MrWvK08-ZuloXkSTcW3KerdnO22N6RYyQhaHhgkSagD9GeQqFVDgMssKBYlcIRYMYnaNlJl5Uq4I.mp4
|
| 442 |
-
Copy of SnapInsta.to_AQNqUL5ZJJeNFXONj8O2nx4jUxo5r6hwAx0xtYmwt6HGbvSEVLgUtevPZQMvO6f2JxQ_K_d58t3wXnLMVOtTH05AYMqboOSdbg72EjI.mp4
|
| 443 |
-
Copy of SnapInsta.to_AQNz8vpVK4DJBt-N6V0Jp4cTgq0DOebjnRpqhTNSBeEGKqtCT00U8lY6rioAlPEpcRKk50Kye7dH2XpQtp5SUHvE.mp4
|
| 444 |
-
Copy of SnapInsta.to_AQNs8mZelvMu0m2yDKCFN3Qu-TOriexj6b2A2P3_n3ckTgb8eEhgKdqpOCRdKo60a9YRYjvXWiGP2plvoVVIvzzQsdqsP426JTR6HQ8.mp4
|
| 445 |
-
Copy of SnapInsta.to_AQNT14sJxGf7aQwUKqKO61kwBuxexewm9Nx7RHwdArE2AGGqOq0Nyk5K12TjchF7YJ6RX7B8JIEVb5Dyy3Y8ulapfzgFf_o5sKThICw.mp4
|
| 446 |
-
Copy of SnapInsta.to_AQNSDUbMuxdO6XIjtDgowAZ74kuzyxaQwqHCJAATj4h0qmE-N6oQq8J9KeKjJk7DPQ1FU_WKTuBi5eoTG3mX_bWy.mp4
|
| 447 |
-
Copy of SnapInsta.to_AQNoqGnsb3k1CTChoI_DAMy-_KFpQUfxzivkDvinnIZmiH_VyEWjdx4Ljb9AMhvVXPhmsoO4YKoTWCiYdMVwOqIZ1nA8lNjhd2lxQBc.mp4
|
| 448 |
-
Copy of SnapInsta.to_AQNZu9ALhfGvlYTJhM_H_Cxhy5ahaxGAy7lTtEbUi3I-TyxAXebRosSvCDO1K4Z-GU-zuHPw5BtdWmkunvW5s-DYOFsMUsZiX68rQ6M.mp4
|
| 449 |
-
Copy of SnapInsta.to_AQNZpFaNKZbOTlXpTw9F8bQZV2cCO2EHzroMawHebjZio4Lhu5l31phBASNEo9Vt_FiGPk74eoole5UgKPxw2K6zWtkDhOj8nKoV67A.mp4
|
| 450 |
-
Copy of SnapInsta.to_AQO_ZcyvpzkpkYEyY2u1mih84TV2-b5ZVN_lAoMm2bheRUUKhin676PnhqEkjQHJuMk3GUJ9xqU-weKT5nTZf0xC22tnoh-eVnXCUWY.mp4
|
| 451 |
-
Copy of SnapInsta.to_AQO0YvmPetdVa4xBYsilUdLier3u19i4Fhj-Lm82fvyy5EbOrDi0FHccwwQ4DBxVhCp8KDQpkpzVOa2xr5ErIIb2twbjMyNW1TDvrbA.mp4
|
| 452 |
-
Copy of SnapInsta.to_AQO6xBGRmU8ew5pKF1O_pHcoGYH-MM4A51OIW24UHK4biiVdokf57Y411R-W6YvmbKXjgyzTWKCeRkQniW8--o6htUANSvu7veXTnvQ.mp4
|
| 453 |
-
Copy of SnapInsta.to_AQO4c-AR6EA9Q_fFyMLakB82nkwavBPlYo5fgR1xbuQuFbRaTAcJGaRmKs6uo3oalBYxlIll3aUQwPGfHn35dCggLXG3Mw99b4m_ntc.mp4
|
| 454 |
-
Copy of SnapInsta.to_AQO9G2JMub4QMQxUe3PhN3FHcSlNF84s2yUprbGOXoBfR4EuVenVQcRiJOEs13YKkX4F_xqG81bndLelfTlq-HVIInt0KChHQmlmacc.mp4
|
| 455 |
-
Copy of SnapInsta.to_AQO0cb26MOvBHXJR2elHaSV6lE0FqscsSf1jLeVnzL2IYw0AdRRN328F8k4xJyiryAkMn6gJcUpNBwxraUdHthPRse4NP_0p3NfaFWg.mp4
|
| 456 |
-
Copy of SnapInsta.to_AQO7M4qk00x1hdbwmYjYNtJOoYPipIlq1_u5EO4upOOOVsKouSXsk8UuH6t4EoZKbqZSHaUNzJJAC9fDy4SalGjrhrIuUpCHDCdZIHc.mp4
|
| 457 |
-
Copy of SnapInsta.to_AQO2_69-HvaItQbgeaRkOwCtw7KvX454BmRLJVXWM2m2c0ygSfVkFRpdGaoDW_KK-wdKr6r5B6wE5smoX8SpUgDvsCb0LBZnSOl4U3k.mp4
|
| 458 |
-
Copy of SnapInsta.to_AQO-coSM5Qu-3kAXH-LUFtLhWfVO7E8-drms0y3gL-JmEyFWs3hdI3Lw4YuUXNZLEn490T62iaRC7rVqUbjVhW8YyiTRxGdpI_f5EN0.mp4
|
| 459 |
-
Copy of SnapInsta.to_AQObFFIYf4nzHdZ6-4Bzv6nYbWm_f6bh5xwfdHevrfSk6M9-hz1DJZHBYIF4j9if04CrgifV0zAuEB8xYtrgxFMtTOcu8Op6ru4DtII.mp4
|
| 460 |
-
Copy of SnapInsta.to_AQOIUlMdKYw61Cco25ZKEgA40U4Wpzg2mbgkL0nLjFrkK8AZEQA1vgpCvhSoL1KjovumItwwn_IeKeW00aSsw9Rol9IHwFQUDnGuUYo.mp4
|
| 461 |
-
Copy of SnapInsta.to_AQOGKlViVMU2cZV7RaruGOf1BChGgc5TEoARzLXoPAGTdw-ShpM1iwA7nbJRZE3V8HAeKoVub9mEoLNGwhSbKNyxV_1fWbEDb9Sxppk.mp4
|
| 462 |
-
Copy of SnapInsta.to_AQOGbqfilykKBzWZS_MFJw6nwSttzkURAXVCwj0uHBcbO8ZLQge8-NdhTmQZS4b9waw-opg7PZECRU9eYQ8zDFG99NhyJlC0bc4DtmI.mp4
|
| 463 |
-
Copy of SnapInsta.to_AQOChIToy0bqg2Gp1aMgqhMClAvfltI-dZLstsIDlAQ1ONEIK3YnRjp_Jo-r82Thd9Ej4saotjR0eOLNTkBVIAF6Ta-VJV8lmJTzyU0.mp4
|
| 464 |
-
Copy of SnapInsta.to_AQOhT3750n7SltQrZFLryaDoy3ZJa4vdAs6Rh4FAr0omrjUBSiBU3wk2DdRFHg8I6FG-B5MvFxFBLVmPdnE3hwSCHLE-74tibK4Pz8w.mp4
|
| 465 |
-
Copy of SnapInsta.to_AQOJu1Vln4pipaEC8KGynta4CDJKyLhXc9ncX1qGg_ewNvLgEvg7lTfSVJQpDhTwFbd6AQSZXCZ_4bd0gNn6K8PB3U5Xuq45VGvSOWI.mp4
|
| 466 |
-
Copy of SnapInsta.to_AQOILWpmmXQEOfiEvYb9HjQaKEoJD-pVWp_6sqwUqY51dQWe44aj0mH_fVHzNk5f5W9xXWRRQtxPPXA7SsrQ6SBrsyiRXqvYkGEYBdw.mp4
|
| 467 |
-
Copy of SnapInsta.to_AQOhUs-2_bpEHnOLxPX19Kf56WQfyemWOqeULAWHQucNf_oW8Z0a2fdxReqzhOrfMfrz8knSyrnr64fsmblfQ2tGeAaupFRYjYy0POs.mp4
|
| 468 |
-
Copy of SnapInsta.to_AQODc7HdwKEaslKpNNxj3tPv4nywCKuzU4-21NJCg2ExpCgnIzL-t6cpI3TIKquDbYNwLL49v7DD2TjjDp4Wncnt9uv2M0HTwjJPHmU.mp4
|
| 469 |
-
Copy of SnapInsta.to_AQOiNMiNd93YZOFCkkdHpll6E4-5FIbbyKyycsLNq8WP6zkH9nhqD_yst7HFCwn27M0LaQx_KsrrG91QyUl4N0-GUZZlQE51Is-LjE0.mp4
|
| 470 |
-
Copy of SnapInsta.to_AQOQhBrh8oONSQ4Ph6IKoweNYLPO201wibOMB8M2JK3aa8LHiqqDQm44uOr1ptqf6RDj--eRssHMwivgAYqrJ5uefQPsBQ1EozgLWlM.mp4
|
| 471 |
-
Copy of SnapInsta.to_AQOkleawFlGz-DKunnOKHeKvJQ4Ve67qm5CKu4_IgnEmeNeblKOCx0MJabNdcL5tc7OWBKw7-kKoAfgRUXyy85of3-Q5fz4rNjnwxvg.mp4
|
| 472 |
-
Copy of SnapInsta.to_AQOroW8Vq-2ondJDA2kxh_jh-dr23clceTEXSA6i73einTnk0G-jrImo5vfkDljQ8CnT1IuOjhCcDM_jmg2fRNM_00rF3B93jHiZ48c.mp4
|
| 473 |
-
Copy of SnapInsta.to_AQOtYNvu9zo5thrTWvX-sxwpioUNkx_u_-kgyrhxdSyZ3JM7pgbLkFxbPdF1-xCOUAD4WodY0RykoW2zIgKTrGqwDV_WP9BEA_qx1d8.mp4
|
| 474 |
-
Copy of SnapInsta.to_AQOLTsvMiFu8x0DkW2miNUqFpfSh_Gt_6lqpk__qtgFCT62Xg3-zi2VANgbqfBr1-DCSnK_fEd7l6aD0dKYKvwQl3tgAxPHJNnSmCBI.mp4
|
| 475 |
-
Copy of SnapInsta.to_AQOJZuWderPwxXEro9pFNeRAoLCtmEs2_HpDkOM0zsuARLEJqWbcOblWVq-1tpG1LTHTjmUYZckvqnruLdu1McC9.mp4
|
| 476 |
-
Copy of SnapInsta.to_AQOKxI-cLYEN4oZ4HKkxqoY2bJ3DEP1UaKtEhI978Rf5vhPSxOjXA0MVxBDKTEckLBcmOgosVEffzbeqiVi-bHJlfl2r-dRtt_j1Iss.mp4
|
| 477 |
-
Copy of SnapInsta.to_AQOMQFGK08nY4HXiZU3nVrnkdOAcGQyjJBnrmouPj6uUQL8_AeR801Kwy-o8I4boPLYSTGS1S3Mz7iVjz9FP8YoY6Va1j4jHTNwsD8U.mp4
|
| 478 |
-
Copy of SnapInsta.to_AQOnkT7UqBYzkxguFisv4vdDnHgshvmYEiPV4mgZamIOMOw3zAGMzGMWbFNJR7fDHSgOD7BKp9_8HMzeS85Gmfaz.mp4
|
| 479 |
-
Copy of SnapInsta.to_AQOn8T9kyKRc2o0ZRH2Oxs9R5giBdbGwsMpZ9DZN0k1PBKZqwgIRke1-C9BXlY-5rM_vXclqhyF5m58segaSvXN5ryuujAFW3dnzBXQ.mp4
|
| 480 |
-
Copy of SnapInsta.to_AQOv-RAseW6ljEqvz6OaI0ylSfymid3w60DmwE07X3ohw60JrW1SdgFjfTRgnqga900PuMOv7fZDQ8LliiIjHv2vchUSGr_PVMRjyjo.mp4
|
| 481 |
-
Copy of SnapInsta.to_AQPaLPMhHCzlL9Y8E9jIsjt7C4lB0YNHaUpe_m1nVGXgHahFPfpVA_vUpD3RU6F-z2_TvwSj3b30SayD2-HhPhI1denRaMSe8GBz69E.mp4
|
| 482 |
-
Copy of SnapInsta.to_AQOuyKoHL6ileKQvw24S_-gMVQa1lRZxogMFHTZjSm6vDRRDk5XKdYyW6139o3IMDXIiyNaJyDR1k_VyBa7KGqw3bcq9_7GVGQHFZZ4.mp4
|
| 483 |
-
Copy of SnapInsta.to_AQOYh3ClXmKci--LzYCAE1g5iNaCLJaHfNKxds4IwPAVo-X11i_r9CcFk723CeAQ2yuz0Z7PdSdG5333zhhXKainaqaGW1u9gTiIX8Y.mp4
|
| 484 |
-
Copy of SnapInsta.to_AQPCP8-5E-0NrkauJafpI9hmYPyqZigla6I40_Pp9pYl37I-8RkbSisvCw63EwOze0qTw8vKGlF7bmbY2_rBIXQMEEM2q4NJnpoL1Uw.mp4
|
| 485 |
-
Copy of SnapInsta.to_AQP3vtFPE4KDyZAI5aZx7Sa72zdG3-26IU2XU7pSLYwvtWJTYGm5ZgtvRDKInfk9LzI2qpOLc-uoKbxyIDao5gXxv5qGOAvWGkjHTIM.mp4
|
| 486 |
-
Copy of SnapInsta.to_AQP0dc2gM7f4-3LD6t8iH_j82HKYBfK8OoTD8Z0ToGPG31u4J8mYj2HgFtJbPXHe4tagnT3AynzXWdTxEsaP3LAJMJvn10Mmsm_b7aI.mp4
|
| 487 |
-
Copy of SnapInsta.to_AQP-8dmhdDh2EO45RT66-UwOav42m0DzTMyk-cDMUpuvF5AwjKDJM1P2MP7NSIkhWsg5KKf3Iv_-9tr8s3VSYNrE4WywuBudnbPDmFE.mp4
|
| 488 |
-
Copy of SnapInsta.to_AQOVcPUuMZb0KohpbbOZDweMADzsVpIsqqN59REGQXsJ0nHXHxxfS8Cfqb8wXM8i8cR4z8h2QksbCjoJ4x3mnsha00Kb_pKE8Wkb6t0.mp4
|
| 489 |
-
Copy of SnapInsta.to_AQPcUf3rzkjVNkmR4eICnRJdkZQyEBmAZpmv2itAqcvo0mYGkaiWd1E0lOUwyq6JOKfafj3P0yWIzN1lKRChRB6L9-nXBuBJQbdzNL0.mp4
|
| 490 |
-
Copy of SnapInsta.to_AQPKUXFZHr4GjHjmYIVdRrPn43mEv4FuCScCXo4ZZtRyDMxtpDnCe5y3K1XzUKB4Resqc-4Xqd5ZoNORgcFqHTk-O6D0HCWIA2p4PoA.mp4
|
| 491 |
-
Copy of SnapInsta.to_AQPe5LWF3BxysTPxiPt4G7xxzJ0O_6BTxDLBvgRBXs3WcxoUIQIDtZB4D1PK5k8oTy5-QV3mAmna37rSkIIx313742PQs5F3AOGJFEw.mp4
|
| 492 |
-
Copy of SnapInsta.to_AQPdT9Fy4720t-MRnJN63HXCGZclC3aQEiAXj1rUOYYl8hEKmAOwhX6QwzgQjgfbxp7U3yBl0vJYe9Hs1GPMI-lZ.mp4
|
| 493 |
-
working.mov
|
| 494 |
-
Copy of Sunset View Balcony.MOV
|
| 495 |
-
Rooftop View.MOV
|
| 496 |
-
Copy of SnapInsta.to_AQMt6OIXFM3AepSklXmlydAKfY67KB3n25bCblD7hnaamECoYmXq-kCrhSy2G-LaPeJyxo7TZFtf7liF_2nsN2kS4qlnT7265QVa6xg.mp4
|
| 497 |
-
Copy of SnapInsta.to_AQO1ePTrmLQkqeJHeM5ZXPkLbCKEuX1JwduJmUR_ZFIT8m5TIIolHTm1S5S4l3dPlNMNhT5zpBk-t1Fcl5KhGewmyhM5q2CyjA1zj9o.mp4
|
| 498 |
-
Copy of SnapInsta.to_AQPl6nGIbYJLtoXtULbX1d_lIYM1Ja8R03wUEUVnHQdhJYgMb55E3lb-T7lZMUBuO6xIcIdHf0Odg2CWrLT8X1_u6k5dQfuNq2Fe--s.mp4
|
| 499 |
-
Copy of SnapInsta.to_AQPd5lv2ryk5zwzwvCPfJ_jgoFUep3u5y0M4KYgc0uSiWdNl2HmyA-6pHu-AvhRPHiqoVSUucIWEbeidwtERgz1Bb1-HCMAlWm6ccb0.mp4
|
| 500 |
-
Copy of SnapInsta.to_AQPhhon3KqxORKlyNsNC5cxnmaqmi5EeScdRfDkQnjxzyR_86ubWQkYw9Vph-9qeCbdjX09ChXCOrMGgwsBWs_mlgyj9AJA1sLoqGdo.mp4
|
| 501 |
-
Copy of SnapInsta.to_AQPjEvrnqY29gL6RSh-wCeL3Wdl9O-ah3tebbozcRYXTLbjsvhdjLzyLe4l-4qZLVISE_x5erjwDrS5xdZXgAJdRtKfbcfQd_HAtM-o.mp4
|
| 502 |
-
Copy of SnapInsta.to_AQPIP4uJRsS2x0XS0gX8Ix48hMkQv-ve0o1ZJZmeFFfPGQlGQ4rZzvht43yE_1g1aLYcobjXE4Z_xLRMAFtuAgUrzxQYopY5ui4eQT8.mp4
|
| 503 |
-
Copy of SnapInsta.to_AQPdefY3LXX28f-AZnDi5v8LaFIDe7i4O3q-usglx4Z6puv7qW3xkc7YKVX8FJfK9SWEZ2yA3p951jCMEHK3mRzFJVDwrbN-u5DFffA.mp4
|
| 504 |
-
Copy of SnapInsta.to_AQPKHZdQy0QDbEKJMvMK7Rrll2Lr8vw6uFZPbNGkKbYaFBkTEQLG-CIGE_qnpKOFCMFqDhG1_BMKOyVmU5hYwxhKBOqviLQzXn94PFM.mp4
|
| 505 |
-
Copy of SnapInsta.to_AQPmpqhpY3pfvPbFrfuEIh6KXny60lRPgXan61CIChQp4wCAzlmuUkiQYImHoVumLNdavy0ZJ5brbv1AI0Mw1_70Bqu3hX-Ad65-fhc.mp4
|
| 506 |
-
Copy of SnapInsta.to_AQPOVsfm2nARUwP4UPbhFK6oy1SwqNC3DB5-V2pkJ2VC8too83aJioMNlzpUsGPjI8Eax_moNTf42ZmG6J-MKaTMZssvn_J-lqCOi2o.mp4
|
| 507 |
-
Copy of SnapInsta.to_AQPPK-K6P28rP6Oi7XpsJRwQh5ORmm5KB5V3DYZispKHBNuUCidrAPbqRMI7VzLXATfScpcNF3qbu-9PiCo4hN_obMVMIB2WtYokY7Y.mp4
|
| 508 |
-
Copy of cars.mp4
|
| 509 |
-
Copy of SnapInsta.to_AQPqdMmXNxyr_MoE6ViDpYXJPt1slINEwXQs1lwK68pUzj5zzPihOl6dNTfvWxPZbIe0AI03gMz0MqR4NPSMO0qivdxCziDUPpS1uqA.mp4
|
| 510 |
-
Copy of SnapInsta.to_AQPo7Wy_j7NEEjW1HToI7w8A76LDcB8nlY_JpSOvyf3HT5q0jfQnXuQNYxtHGuHVDJX48D4gJxY0lmt91gPlDIIXNJgsz2eFRzfdzWk.mp4
|
| 511 |
-
Copy of SnapInsta.to_AQPPl9rtJLviB0jr-raGJJ3x3tbkQyDPnRWv71P0T9z4zyfEV6zp67jFpAE2Qt9B0Bpk_s352tKhtIEqZZpRyGKfQWb8ei8aZabjRzw.mp4
|
| 512 |
-
Copy of SnapInsta.to_AQPNjRl907AIGr7mckkIiB-8EKMjnxM-M3pkGt8McSnhcKmJuQDHvLbwQQGScYWi5aIsGfbHv2SpgXZwFeV9mREF0VNdAGOHalqR_ik.mp4
|
| 513 |
-
Copy of SnapInsta.to_AQPnwbFnBhAUQdg9ANIFPb9EbGvEy_HoSgM6iLrqur64Wov4UYnqSbePt8oOhrCEzna7KJsjKqEKeiJ4XzxE_NAbOw1UrUXWz8qjde0.mp4
|
| 514 |
-
Copy of SnapInsta.to_AQPpavBgWBPWhwUQvi4281NToZZekb38IVkTCQbjbqxHpba3lrSzB_Rc77TjoFQyEDNk6kc1hLE_b2fHaMTGzYOCV5b8IORTkYgCS3Y.mp4
|
| 515 |
-
cars.mp4
|
| 516 |
-
Copy of Big Yacht 2.0.mp4
|
| 517 |
-
Copy of Big black yachgt 02.mp4
|
| 518 |
-
Copy of Big yacht white.mp4
|
| 519 |
-
Copy of Big white yacht 09.mp4
|
| 520 |
-
Copy of Big luxury yacht.mp4
|
| 521 |
-
Copy of Big White Yacht 10.mp4
|
| 522 |
-
Copy of Big black Jachts 08.mp4
|
| 523 |
-
Copy of Big yacht 07.mp4
|
| 524 |
-
Copy of Best view from Yacht.mp4
|
| 525 |
-
Copy of Big white Yacht view 03.mp4
|
| 526 |
-
Copy of Blue Yacht.mp4
|
| 527 |
-
Copy of Blu & yellow yacht.mp4
|
| 528 |
-
Copy of Copuple on yacht 02.mp4
|
| 529 |
-
Copy of Black Luxury yacht.mp4
|
| 530 |
-
Copy of cooking on yacht.mp4
|
| 531 |
-
Copy of City view from yacht.mp4
|
| 532 |
-
Copy of chilling Yacht.mp4
|
| 533 |
-
Copy of Blu lights Yacht.mp4
|
| 534 |
-
Copy of Chilling at Yacht movie time.mp4
|
| 535 |
-
Copy of Chilling at Yacht.mp4
|
| 536 |
-
Copy of Family on Yacht.mp4
|
| 537 |
-
Copy of Gold yacht.mp4
|
| 538 |
-
Copy of Green Yacht.mp4
|
| 539 |
-
Copy of Copy of Yacht3.0.mp4
|
| 540 |
-
Copy of Driving yacht opn ocean .mp4
|
| 541 |
-
Copy of Ferrari yacht.mp4
|
| 542 |
-
Copy of Driving yacht.mp4
|
| 543 |
-
Copy of Copy of Yachts 2.0.mp4
|
| 544 |
-
Copy of Copy of Yachts under the bridge.mp4
|
| 545 |
-
Copy of Copy of Yachts at night.mp4
|
| 546 |
-
Copy of Interior Ycht 3.0.mp4
|
| 547 |
-
Copy of Greey yacht.mp4
|
| 548 |
-
Copy of inside white yacht.mp4
|
| 549 |
-
Copy of Interior on Yacht.mp4
|
| 550 |
-
Copy of INterior yacht.mp4
|
| 551 |
-
Copy of Interior Yacht luxury 2.0.mp4
|
| 552 |
-
Copy of Jetsky on driving on yacht.mp4
|
| 553 |
-
Copy of Interior of Big white Yacht 2.0.mp4
|
| 554 |
-
Copy of inside yacht.mp4
|
| 555 |
-
Copy of Lamborghini yacht.mp4
|
| 556 |
-
Copy of Luxury white yacht and view.mp4
|
| 557 |
-
Copy of Luxury Yacht 09.mp4
|
| 558 |
-
Copy of luxury yacht 02.mp4
|
| 559 |
-
Copy of Luxury yacht 03.mp4
|
| 560 |
-
Copy of Luxury woody Yacht.mp4
|
| 561 |
-
Copy of Luxury white yacht night.mp4
|
| 562 |
-
Copy of luxury place Yacht.mp4
|
| 563 |
-
Copy of Luxury yacht 07.mp4
|
| 564 |
-
Copy of luxury black yacht.mp4
|
| 565 |
-
Copy of luxury yacht 05.mp4
|
| 566 |
-
Copy of Party at yacht.mp4
|
| 567 |
-
Copy of Pardo Yacht.mp4
|
| 568 |
-
Copy of Ocean view.mp4
|
| 569 |
-
Copy of Purple Yacht 02.mp4
|
| 570 |
-
Copy of Party at yacht 02.mp4
|
| 571 |
-
Copy of Mountain view from yacht.mp4
|
| 572 |
-
Copy of Purple Yacht.mp4
|
| 573 |
-
Copy of POV yacht.mp4
|
| 574 |
-
Copy of Luxury yacht.mp4
|
| 575 |
-
Copy of Luxury yacht at night stading.mp4
|
| 576 |
-
Copy of Sirena Yacht 88.mp4
|
| 577 |
-
Copy of Sun set Big yacht 02.mp4
|
| 578 |
-
Copy of Sunset yacht 04.mp4
|
| 579 |
-
Copy of Sunset Yacht 07.mp4
|
| 580 |
-
Copy of Sunset view from yacht white.mp4
|
| 581 |
-
Copy of Sunset On Yacht 07.mp4
|
| 582 |
-
Copy of Sunset yacht.mp4
|
| 583 |
-
Copy of Rrolls & white yach.mp4
|
| 584 |
-
Copy of sunset view from white yacht.mp4
|
| 585 |
-
Copy of Sea Yacht.mp4
|
| 586 |
-
Copy of The view from big yacht.mp4
|
| 587 |
-
Copy of Three Yachts.mp4
|
| 588 |
-
Copy of The view from yacht 3.0.mp4
|
| 589 |
-
Copy of The view from yacht 04.mp4
|
| 590 |
-
Copy of The view from yacht 08.mp4
|
| 591 |
-
Copy of The view from yacht.mp4
|
| 592 |
-
Copy of The view from yacht 03.mp4
|
| 593 |
-
Copy of The view form big yacht.mp4
|
| 594 |
-
Copy of Throught Yachts.mp4
|
| 595 |
-
Copy of The view from yacht 2.0.mp4
|
| 596 |
-
Copy of View of Party Yacht 2.0.mp4
|
| 597 |
-
Copy of Two Yachts 2.0.mp4
|
| 598 |
-
Copy of White Big Yacht 03.mp4
|
| 599 |
-
Copy of View from Yacht 2.0.mp4
|
| 600 |
-
Copy of View from yacht.mp4
|
| 601 |
-
Copy of Walking throgh yachts.mp4
|
| 602 |
-
Copy of White luxuryu yacht.mp4
|
| 603 |
-
Copy of Up on yacht ocean view.mp4
|
| 604 |
-
Copy of White luxury yacht 04.mp4
|
| 605 |
-
Copy of White and blu Yacht.mp4
|
| 606 |
-
Copy of Yacht & helicopter.mp4
|
| 607 |
-
Copy of Yacht 4.0.mp4
|
| 608 |
-
Copy of White yacht on oncean driving 07.mp4
|
| 609 |
-
Copy of Yacht & helicopter 02.mp4
|
| 610 |
-
Copy of white Yacht night .mp4
|
| 611 |
-
Copy of White yacht luxury night.mp4
|
| 612 |
-
Copy of Yacht 02.mp4
|
| 613 |
-
Copy of Yacht & helicopter 3.0.mp4
|
| 614 |
-
Copy of White yacht on ocean 09.mp4
|
| 615 |
-
Copy of White yacht at ocean.mp4
|
| 616 |
-
Copy of Yacht luxury night 04.mp4
|
| 617 |
-
Copy of Yacht at sea.mp4
|
| 618 |
-
Copy of Yacht 07.mp4
|
| 619 |
-
Copy of Yacht night 2.0.mp4
|
| 620 |
-
Copy of Yacht date.mp4
|
| 621 |
-
Copy of Yacht 08.mp4
|
| 622 |
-
Copy of yacht luxury sea.mp4
|
| 623 |
-
Copy of Yacht at ocean 03.mp4
|
| 624 |
-
Copy of Yacht at night.mp4
|
| 625 |
-
Copy of Yacht birthday.mp4
|
| 626 |
-
Copy of yacht stairs.mp4
|
| 627 |
-
Copy of Yacht on ocean 04.mp4
|
| 628 |
-
Copy of Yacht opn board 04.mp4
|
| 629 |
-
Copy of Yacht night red Led light.mp4
|
| 630 |
-
Copy of Yacht tools.mp4
|
| 631 |
-
Copy of Yacht show.mp4
|
| 632 |
-
Copy of yacht on board.mp4
|
| 633 |
-
Copy of Yacht standing.mp4
|
| 634 |
-
Copy of Yacht standing night.mp4
|
| 635 |
-
Copy of yacht on bard 04.mp4
|
| 636 |
-
Copy of Yacht.mp4
|
| 637 |
-
Copy of Yacht view 03.mp4
|
| 638 |
-
Copy of Yacht view night.mp4
|
| 639 |
-
Copy of 1.mp4
|
| 640 |
-
Copy of Yachts under the bridge.mp4
|
| 641 |
-
Copy of Yacht walkingmp4.mp4
|
| 642 |
-
Copy of Yacht white on board.mp4
|
| 643 |
-
Copy of Yacht3.0.mp4
|
| 644 |
-
Copy of Yachts 2.0.mp4
|
| 645 |
-
Copy of Yachts at night.mp4
|
| 646 |
-
Copy of black jet 02.mp4
|
| 647 |
-
Copy of Birthay gift on jet.mp4
|
| 648 |
-
Copy of black and yellow jet.mp4
|
| 649 |
-
Copy of Black and gray Jet.mp4
|
| 650 |
-
Copy of Black G class and Jet.mp4
|
| 651 |
-
Copy of Black and white Jet interior.mp4
|
| 652 |
-
Copy of breakfast jet and view.mp4
|
| 653 |
-
Copy of Cars & Jet.mp4
|
| 654 |
-
Copy of Cars & Private Jet.mp4
|
| 655 |
-
Copy of Black jet view.mp4
|
| 656 |
-
Copy of Booking Privat Jet.mp4
|
| 657 |
-
Copy of Black Jet.mp4
|
| 658 |
-
Copy of car and jet waiting .mp4
|
| 659 |
-
Copy of Black Jet landing.mp4
|
| 660 |
-
Copy of blu jet.mp4
|
| 661 |
-
Copy of blue jet.mp4
|
| 662 |
-
Copy of chilling jet.mp4
|
| 663 |
-
Copy of City Lights and Clouds at Night.mp4
|
| 664 |
-
Copy of cars and jet 02.mp4
|
| 665 |
-
Copy of chilling on jet 02.mp4
|
| 666 |
-
Copy of Cars and jet 04.mp4
|
| 667 |
-
Copy of Cloads View.mp4
|
| 668 |
-
Copy of Celebration on jet.mp4
|
| 669 |
-
Copy of City Night view 02.mp4
|
| 670 |
-
Copy of champagne on jet.mp4
|
| 671 |
-
Copy of Chilling on Jet.MOV
|
| 672 |
-
Copy of Enter on Black and yellow Jet.mp4
|
| 673 |
-
Copy of entering on jet 02.mp4
|
| 674 |
-
Copy of flex jet.mp4
|
| 675 |
-
Copy of Closing door of jet.mp4
|
| 676 |
-
Copy of Dinner on Jet.MOV
|
| 677 |
-
Copy of clouds from jet.mp4
|
| 678 |
-
Copy of enter on jet 03.mp4
|
| 679 |
-
Copy of Cloud view froim jet.mp4
|
| 680 |
-
Copy of female on board.mp4
|
| 681 |
-
Copy of Gray and Black jet opening door.mp4
|
| 682 |
-
Copy of Interior + View Jet.mp4
|
| 683 |
-
Copy of Gril opening door of jet.mp4
|
| 684 |
-
Copy of gray jet.mp4
|
| 685 |
-
Copy of Interior Jet 04.mp4
|
| 686 |
-
Copy of Getting on a private jet.mp4
|
| 687 |
-
Copy of Green Jet.mp4
|
| 688 |
-
Copy of Interior Jet 03.mp4
|
| 689 |
-
Copy of gray & red Jet.mp4
|
| 690 |
-
Copy of Interior jet 05.mp4
|
| 691 |
-
Copy of Interior Jet.mp4
|
| 692 |
-
Copy of Interior of Jet 03.mp4
|
| 693 |
-
Copy of Interior of Jet 04.mp4
|
| 694 |
-
Copy of interior of jet.mp4
|
| 695 |
-
Copy of jet at night.mp4
|
| 696 |
-
Copy of Jet at night 03.mp4
|
| 697 |
-
Copy of Interior jet.mp4
|
| 698 |
-
Copy of jet closing windows.mp4
|
| 699 |
-
Copy of Interior jet white.mp4
|
| 700 |
-
Copy of Jet 360.mp4
|
| 701 |
-
Copy of jet night.mp4
|
| 702 |
-
Copy of jet interior white.mp4
|
| 703 |
-
Copy of Jet interior.mp4
|
| 704 |
-
Copy of jet interior 05..mp4
|
| 705 |
-
Copy of Jet on Airport.mp4
|
| 706 |
-
Copy of jet interior 04.mp4
|
| 707 |
-
Copy of jet interior 05.mp4
|
| 708 |
-
Copy of Jet landing.mp4
|
| 709 |
-
Copy of Jet landing 02.mp4
|
| 710 |
-
Copy of Jet interior 02.mp4
|
| 711 |
-
Copy of Jet rotationg 02.mp4
|
| 712 |
-
Copy of Jet start from eath.mp4
|
| 713 |
-
Copy of Jet out night.mp4
|
| 714 |
-
Copy of Jet starting 02.mp4
|
| 715 |
-
Copy of Jet Return.mp4
|
| 716 |
-
Copy of Jet starting engine.mp4
|
| 717 |
-
Copy of Jet pilots.mp4
|
| 718 |
-
Copy of Jet services.mp4
|
| 719 |
-
Copy of Jet starting.mp4
|
| 720 |
-
Copy of Night view from Jet.mp4
|
| 721 |
-
Copy of Jet View.MOV
|
| 722 |
-
Copy of jet view 02.mp4
|
| 723 |
-
Copy of Lamborghini and Private Jet.mp4
|
| 724 |
-
Copy of Military Texture Jet.mp4
|
| 725 |
-
Copy of jet view 03.mp4
|
| 726 |
-
Copy of Morning view from jet.mp4
|
| 727 |
-
Copy of jet view morning.mp4
|
| 728 |
-
Copy of Jet view night.mp4
|
| 729 |
-
Copy of Porche & Jet's.mp4
|
| 730 |
-
Copy of Rainy night .mp4
|
| 731 |
-
Copy of open door of jet.mp4
|
| 732 |
-
Copy of Rain View.mp4
|
| 733 |
-
Copy of Opening door jet.mp4
|
| 734 |
-
Copy of Ready for Jet.mp4
|
| 735 |
-
Copy of Spray parfume on Jet.mp4
|
| 736 |
-
Copy of services on jet.mp4
|
| 737 |
-
Copy of Pilot thumbs up from jet.mp4
|
| 738 |
-
Copy of rain night jet.mp4
|
| 739 |
-
Copy of sun set 02.mp4
|
| 740 |
-
Copy of the view from jet.mp4
|
| 741 |
-
Copy of The jet View.mp4
|
| 742 |
-
Copy of sunset jet 02.mp4
|
| 743 |
-
working.mov
|
| 744 |
-
working.mov
|
| 745 |
-
Copy of jet on board.mp4
|
| 746 |
-
working.mov
|
| 747 |
-
working.mov
|
| 748 |
-
Rooftop View.MOV
|
| 749 |
-
Copy of enter on jet.mp4
|
| 750 |
-
Copy of Jet starting 03.mp4
|
| 751 |
-
Copy of the view 03.mp4
|
| 752 |
-
Copy of sunset jet.mp4
|
| 753 |
-
Copy of The view from window of Jet .mp4
|
| 754 |
-
Copy of Sunset view from Jet.mp4
|
| 755 |
-
Copy of Starting the fly Jet.mp4
|
| 756 |
-
Copy of Sunset Jet 03.mp4
|
| 757 |
-
Copy of walking through jet 02.mp4
|
| 758 |
-
Copy of The view inside jet.mp4
|
| 759 |
-
Copy of walking out of jet.mp4
|
| 760 |
-
Copy of Views from pilots Private jets.mp4
|
| 761 |
-
Copy of The view of Jet champagne.mp4
|
| 762 |
-
Copy of walking on jet.mp4
|
| 763 |
-
Copy of view from jet.mp4
|
| 764 |
-
Copy of The view of Ocean from jet.mp4
|
| 765 |
-
Copy of view on night.mp4
|
| 766 |
-
Copy of view of the jet black.mp4
|
| 767 |
-
Copy of white jet.mp4
|
| 768 |
-
Copy of Welcome to dubai Night.mp4
|
| 769 |
-
Copy of walking to the jet 02.mp4
|
| 770 |
-
Copy of white jet 02.mp4
|
| 771 |
-
Copy of white Jet landing.mp4
|
| 772 |
-
Copy of white and blu jet.mp4
|
| 773 |
-
Copy of white and red jet.mp4
|
| 774 |
-
Copy of walking to the jet.mp4
|
| 775 |
-
Copy of Walking through jet.mp4
|
| 776 |
-
Copy of Black Lamborghini.MOV
|
| 777 |
-
Copy of Balcony View day.MOV
|
| 778 |
-
Copy of Backstage Club.MOV
|
| 779 |
-
Copy of Beach view 01.MOV
|
| 780 |
-
Copy of Beachparty night.MOV
|
| 781 |
-
Copy of Black Lamborghini 02 View.MOV
|
| 782 |
-
Copy of Balcony view 02.mov
|
| 783 |
-
Copy of Calisthenics at Home.mov
|
| 784 |
-
Friends.mov
|
| 785 |
-
Copy of Cars 05.mov
|
| 786 |
-
Copy of Cars race.mov
|
| 787 |
-
Copy of Cars 04.mov
|
| 788 |
-
Copy of cars 06.mov
|
| 789 |
-
Copy of Brabus.MOV
|
| 790 |
-
Copy of Cars 03.mov
|
| 791 |
-
Copy of Cars.mov
|
| 792 |
-
Copy of Cars 02.mov
|
| 793 |
-
Copy of Clubbing 02.mov
|
| 794 |
-
Copy of Clubbing 09.mov
|
| 795 |
-
Copy of Clubbing 05.mov
|
| 796 |
-
Copy of Clubbing 07.mov
|
| 797 |
-
Copy of Clubbing & Wins.MOV
|
| 798 |
-
Copy of Clubbing 03.mov
|
| 799 |
-
Copy of clubbing 10.mov
|
| 800 |
-
Copy of Clubbing 06.mov
|
| 801 |
-
Copy of Cliff jumping.mov
|
| 802 |
-
Copy of Clubbing 14.mov
|
| 803 |
-
Copy of Clubbing champagne.mov
|
| 804 |
-
Copy of Clubbing 11.MOV
|
| 805 |
-
Copy of Clubbing 16.MOV
|
| 806 |
-
Copy of Clubbing Birthday.MOV
|
| 807 |
-
Copy of Clubbing 13.mov
|
| 808 |
-
Copy of clubbing coctails.MOV
|
| 809 |
-
Copy of Clubbing 19.MOV
|
| 810 |
-
Copy of Clubbing 12.mov
|
| 811 |
-
Copy of Clubbing 18.MOV
|
| 812 |
-
Copy of Desert.mp4
|
| 813 |
-
Copy of Clubbing Strobe Lights.MOV
|
| 814 |
-
Copy of clubbing.mov
|
| 815 |
-
Copy of Clubbing Friends.mov
|
| 816 |
-
Copy of Clubbing Red Bull.MOV
|
| 817 |
-
Copy of Clubbing night view.MOV
|
| 818 |
-
Copy of Clubbing04.mp4
|
| 819 |
-
Copy of Clubbing night lights.MOV
|
| 820 |
-
Copy of Clubbing Girls.MOV
|
| 821 |
-
Copy of Clubbing Night.mov
|
| 822 |
-
Copy of Dinner.MOV
|
| 823 |
-
Copy of Drinking Red Bull.MOV
|
| 824 |
-
Copy of Driving Cars.mov
|
| 825 |
-
Copy of Driving Porche.mp4
|
| 826 |
-
Copy of drinking.mov
|
| 827 |
-
Copy of Experiences.mov
|
| 828 |
-
Copy of Driving Night.MOV
|
| 829 |
-
Copy of Driving Cars 02.mov
|
| 830 |
-
Copy of Eiffel Tower.MOV
|
| 831 |
-
Copy of Helicopter view 01.mp4
|
| 832 |
-
Copy of Ferrari 01.mp4
|
| 833 |
-
Copy of Ferrari at Night.MOV
|
| 834 |
-
Copy of Helicopter View.mp4
|
| 835 |
-
Copy of Jumpin pool at Night.mov
|
| 836 |
-
Copy of Jumping on beach.MOV
|
| 837 |
-
Copy of Ferrari Night City.MOV
|
| 838 |
-
Copy of Jet ski.MOV
|
| 839 |
-
Copy of Jumping from rocks.MOV
|
| 840 |
-
Copy of Gym workout.MOV
|
| 841 |
-
Copy of Lambo shooting fire.mp4
|
| 842 |
-
Copy of Morning Chilling.mp4
|
| 843 |
-
Copy of Laptop working.MOV
|
| 844 |
-
Copy of Motorcycle Sunset.MOV
|
| 845 |
-
Copy of Lamborgini.mp4
|
| 846 |
-
Girl in bed.MOV
|
| 847 |
-
Copy of Night Chilling.mov
|
| 848 |
-
Copy of Morning view.mp4
|
| 849 |
-
Copy of Luxury Food.MOV
|
| 850 |
-
Copy of Jumping pool night.MOV
|
| 851 |
-
Copy of Night swimming pool.MOV
|
| 852 |
-
Copy of Outside.MOV
|
| 853 |
-
Copy of Party & Money.MOV
|
| 854 |
-
Copy of Night Rooftop View.MOV
|
| 855 |
-
Copy of Night City.MOV
|
| 856 |
-
Smoking.MOV
|
| 857 |
-
Copy of Paris at Night.MOV
|
| 858 |
-
Copy of Nights cars.MOV
|
| 859 |
-
Copy of Night outside.MOV
|
| 860 |
-
Copy of Party house opening champagne.MOV
|
| 861 |
-
Copy of Pool friends.MOV
|
| 862 |
-
Copy of Pool indoor.MOV
|
| 863 |
-
Copy of Porche Night Paris 02.MOV
|
| 864 |
-
Copy of Porche Keys Night.MOV
|
| 865 |
-
Copy of Partying outside.mov
|
| 866 |
-
Copy of Porche 01.mp4
|
| 867 |
-
Copy of Pool indor02.MOV
|
| 868 |
-
Copy of Porche.mp4
|
| 869 |
-
friends.MOV
|
| 870 |
-
Copy of Porche Night Paris.MOV
|
| 871 |
-
Copy of Rooftop View.MOV
|
| 872 |
-
Copy of Sea View.mov
|
| 873 |
-
Copy of skyscrapers view.mp4
|
| 874 |
-
Copy of Recording Gym.mov
|
| 875 |
-
Copy of Sunset Walking.MOV
|
| 876 |
-
Copy of Sea View.mov
|
| 877 |
-
Copy of Rolls Royce.MOV
|
| 878 |
-
Copy of Rolex.MP4
|
| 879 |
-
Copy of Training View.mov
|
| 880 |
-
Copy of Walking through cars.MOV
|
| 881 |
-
Copy of Walking corvette.MOV
|
| 882 |
-
Copy of Walking Paris at Night.MOV
|
| 883 |
-
Copy of Walking Porche.MOV
|
| 884 |
-
Copy of Surfing02.MOV
|
| 885 |
-
Copy of Waiting Dinner.MOV
|
| 886 |
-
Copy of Taking Photos.MOV
|
| 887 |
-
Copy of Surfing.MOV
|
| 888 |
-
Copy of Workout Gym.MOV
|
| 889 |
-
Copy of workout.mov
|
| 890 |
-
laptop.mov
|
| 891 |
-
Copy of Watches and DJ.MOV
|
| 892 |
-
Copy of Working at Night.MOV
|
| 893 |
-
Copy of Working pc 02.MOV
|
| 894 |
-
Copy of Working Pc.MOV
|
| 895 |
-
Copy of 1.mov
|
| 896 |
-
Copy of Walking.mov
|
| 897 |
-
Copy of SnapInsta.to_AQM9gKyB9RffZwHGX4cJsWobiFBN8HmKK5wUkokHrxabe-bfwYGlE5YzEgiLo9I-Kvqqu-4fcGYUz8HRFPQwxEIO0cHJV-YXxp9WsQE.mp4
|
| 898 |
-
Copy of SnapInsta.to_AQM2wItVQc4lNxX9WePv5vACM94oPFbIkzg2eMVL5jvbmmbBY7X3qaEusb4VHH5owdcEgMloVAMQiKTq2QaqN75QcjWfFI0FXNA60KQ.mp4
|
| 899 |
-
Copy of SnapInsta.to_AQM3IL1m_StcI1k0lDtIpVprKv7VIa1ojdT7QdCoDIJRA63w-WMHQperAscWiBjXBkFrMmzGwzSAguu3hndUk3j__JHUZOPQg7geIEI.mp4
|
| 900 |
-
Copy of SnapInsta.to_AQM3vJOteJzhLH4N1xByWgccA3LQtv0HZyQUyO9H5Ka0CE9hBGE8ahMH-l2Oy1XYeHL7OIHa0kChBgXFqmSc7qxzhc5cr10o1kfTzFc.mp4
|
| 901 |
-
Copy of SnapInsta.to_AQM-rSVIU3fi3sUrTMpvxFf7xIoQS-00_VYvw5OrswDJr0bffbbLAeoAfDz25508aMlIXgqjzV3okhvyvVHQYr06GwnK_tAGS2p6T7Q.mp4
|
| 902 |
-
Copy of SnapInsta.to_AQMfly9VLE05_3Q5o44y4zv_6pgxTgejSntret4ZIE8L_WK0Ca-g_i1XXgljFYyCIwXQ8s5uk0NezylImRntnudmjs021jdPIO-YuwA.mp4
|
| 903 |
-
Copy of SnapInsta.to_AQMIRqwzhw2L0wXkom8hG-XZYMk3bjDTubeqNa1n6EHSVQ8y3YQKAbdvyLdRzr-UFqU9srR76Iu-oneM3l1s4NSKx71uKyfO2HqfX4c.mp4
|
| 904 |
-
Copy of SnapInsta.to_AQMady5bZSGaaA9STOXJdLfzB-DaP4JJw3_372B2bMaKdJ5I9mWw_192BleK9FtJvj4sVwjnOXLanKaPFk6kHua_5MKMXuiOaptR9Tk.mp4
|
| 905 |
-
Copy of SnapInsta.to_AQMaUMWqFVjl2s0qA-FlweXqIoEtfY6dM5vkR727pEbTSawLx8nwaJq12lu9oFaRBNrXng1oqeKDLrtjKcUJNbzoZ9tLlVqf_xUPtFU.mp4
|
| 906 |
-
Copy of SnapInsta.to_AQMe6L1GWM4K0ruc00wmQGlyO8GC-bEkb-ae5rner_dGPZxzBuXsqUZzgfjtVPlpZPFHMvsmiTYx26g6ogoWObkieQmWaWWQOvAmdb0.mp4
|
| 907 |
-
Copy of SnapInsta.to_AQMD9W0V3kKUV7k6LZfsHNRYjbBNlwVjUU0sZ3hWkxiXVM19THGT-hcpOKl3zZ1K9yARswigoXEXYvMqdYxxQ3UND7JmE37avLsS9tY.mp4
|
| 908 |
-
Copy of SnapInsta.to_AQMhz0IXIwcaA_rN0ap1agGBwUHSEkl7WLdvKdPR4hRfZLTSwwg6Mc48OsEAB-lShLvbocXW3AV4S3E6SLOnsy3-3a6g_aaYnC6_er4.mp4
|
| 909 |
-
Copy of SnapInsta.to_AQMBAGy14oXJZhnhovfilQh5mmDhQigLvHRuAMyHxepkZRP_tB7pkHpP5asYpL60xDtgUUtTliDarT63nRUDrpqyO9K2bKzfcB7F77M.mp4
|
| 910 |
-
Copy of SnapInsta.to_AQMixWX3ILZinwMBMOu7Tj0iyGJC81rAUkVX0h2AhNjVC2KAR9HfzHo6TlOYUxZcXkyO0xeGf1mSyXwvAO58SiNvh5_xnR6oExodxSE.mp4
|
| 911 |
-
Copy of SnapInsta.to_AQMevy3Zp-4ENKLpyULNrQqh6dLL_-ndWwqvlA2W44ZQgiZ06ZVIPjDlwCdFxG2F6vDElWJGsqaBhbtgqOf1wDRBbegKv62qmLTYq_A.mp4
|
| 912 |
-
Copy of SnapInsta.to_AQMYsU02r0iTPflS06eq5HbJyjchTXtH8j3EsfMBmwDIaHAef5CQ444aY0ScxGLlEy1N-W61Wm2Y3ByvRKCNt5ix.mp4
|
| 913 |
-
Copy of SnapInsta.to_AQMRka9XWTHTGVr6roin7gUr2sosmrpZAL1jlt8akIeAqt4O_Qyjeb8lnc39MtgDJrdgNRDK1Laz-PZfsbRppzL6zbuNKNZJ_6RYTL4.mp4
|
| 914 |
-
Copy of SnapInsta.to_AQMphtTMkqPNrkQf7JdUiQxNI60DrXE3xKbGPuHmPPq0x5iXs1z5ngC2mb_beQPh0rcvYiI6BSI8wqJFRYOGNZCicBEauJ5Xo116G40.mp4
|
| 915 |
-
Copy of SnapInsta.to_AQMZdPtx1kz-9m4jECgOMePiL8_wgyFhaKKa161eVfuLRTZjVEeu7yjGLtQPA9ir7Y3wnKeLTiPwUenOyMHpPnJS.mp4
|
| 916 |
-
Copy of SnapInsta.to_AQMJtG23t59ISVylNXx735eG1qHfTnf4evCT7seeO-6yBOReFGBfXch3ZVFJhIxIf9F6YNf7IXzeOO9jd5e4M-GcQev-qDOd3xWfu68.mp4
|
| 917 |
-
Copy of SnapInsta.to_AQMRXtqrUrUbGcK9YbIKs_8M1JmxKmDDZQmjEibL264XTuUA3FeUSsIFPQloL4Wq23FEhQ2Qs1IqlgiNgyAvEtvYDespbDSXLeZMo_k.mp4
|
| 918 |
-
Copy of SnapInsta.to_AQMscdyskpPKrunOvjZvp2PBjyQTDMd36SPyb-Nq4lR4qSauFTFYf1RSOKYhYoxOxHwbfRx9nNKsttZojR_QPTqAeQ1ZuQy-LfkR5qc.mp4
|
| 919 |
-
Copy of SnapInsta.to_AQMygLLqZR2pcgUR1xIItBafbreOQFeUwyqZOv5QrgiejradJJSo8Ds5uKE4DYjWNs9FVLClig0iRTcXqiGtTZOr6HCMYgm4fcSAbPM.mp4
|
| 920 |
-
Copy of SnapInsta.to_AQMyio5XAeOKjF9n_ilZbjsXt0QDTYgXpn89h-JEAxyhi43f5EwtkaTx1RMOwWMYMIyCzx_J0nLnzUkcPttggBGifYmPxoWkGVXq0v0.mp4
|
| 921 |
-
Copy of SnapInsta.to_AQMUP3mJarc7nRDIiyTx71Xqc9KwmN8vfrSh0B4Zuj_jTo5iescLCdRaz9phBMFG_U9ErjVObCkBx3z8-e9shmMaKvbfyhiVw2KcnEc.mp4
|
| 922 |
-
Copy of SnapInsta.to_AQN9UpdiT-bMDd11_GqgciB-5aXCdpr0o95KW-_xTsXgr0YYl-PtAnYglRD9mxBCCh1gGbl9ohd9CbhoZz_lCPmKCKG-rGdF6120_iE.mp4
|
| 923 |
-
Copy of SnapInsta.to_AQN0UpZc2KMOfHHIRo8s2p1zBcLbLOQSuL5iCp3p4-mT9PFDA8O6aiipRrxSRDQEEWP-oDiGnFKgbJtkfXwmIcWgCPzd7RYyySysxDk.mp4
|
| 924 |
-
Copy of SnapInsta.to_AQN_vvfxr0FH3B3EDAUlsMlMYv8pfY2CboeHNAyjHMM1Gp0c_MzvK6xPMd_BaI705qR5VsBL2xvuWATleNc9ApXvfi-vVLU_a66TRN0.mp4
|
| 925 |
-
Copy of SnapInsta.to_AQN9QoPayirxh8JuAbSWaPXLOk4CzA7iROKfyMOiaa0Lhk_5QCFXrXx7e8ZML_6HwJg0_2MnusD_imCmEq6RzV6j1oc4M1qCUhMxumY.mp4
|
| 926 |
-
Copy of SnapInsta.to_AQN1Q_lCMz8w2rSR38JC2-lGxIb824FydZnZO2DocveqNreyIzqcDK0B8n9C4lxDCJd3Bd4tGQWVVDthV7XNMVzFRfpIe9LSg0fEmgA.mp4
|
| 927 |
-
Copy of SnapInsta.to_AQN-DnEWgi9NonpZ3tk7h9T1iWc-ERctOAnZcrx_2kITTmadZqIRQfJRCkLK2zzBBawqVedhMTYXfP12p3R59ePbRV-13Ifh7F16tvA.mp4
|
| 928 |
-
Copy of SnapInsta.to_AQMzh1eWMKv6jspdRMupCEj2vl2Isf5wQoZnJ6b4rlZpKHgtVxef0PIzwaDmdOSP9DkF4H4W1WQbm065RrAb-m--0__1VKlS4caSY7I.mp4
|
| 929 |
-
Copy of SnapInsta.to_AQN341ybRObDcV0YIAKLywgqCkH08UU5Ejwmyj1Fsyq1RsbweNUa-CFe7vXf8p4nKYjmA64grom4iSlAGZygXkjiumT33793VP9ZKg4.mp4
|
| 930 |
-
Copy of SnapInsta.to_AQN0QwGmCrOEulODiWRp0McNgdHMwaphmpN34tiLtuo9FalK_7LG2O7Jip2ueDfsAtF_QJQFRdTeXGZFxIV7OfvjwTPX37eupmlY32k.mp4
|
| 931 |
-
Copy of SnapInsta.to_AQNgW7_1euWAdhEZD9SGX2EhhT2MK9hHMG7E814UWVAubGeo_zO1vvdrqaCeX1O4PdLVdVmqsTIdZj_QjNyDdWs_cB9Wrjin4ftodIk.mp4
|
| 932 |
-
Copy of SnapInsta.to_AQNFtY68SoqhdIheHHvszbK9YEXobdXg2gG9dyUE3UKPSpqVoLtateYUUNcT_KQBbW72X09SZ3A01E34BhkD-rlRUqw4X82Bqf3fzSc.mp4
|
| 933 |
-
Copy of SnapInsta.to_AQNE6tPyHQY6lk7oTAbviV3-kJgNw8QKs8xkj1PNo4y3lhJwsR0pUdZoyPfZ2CLka2EPPWjX3ZhWeJ6hhfKVxT3wvZsNVCUxS2ZJ-0M.mp4
|
| 934 |
-
Copy of SnapInsta.to_AQNbEri2vkwuQN_npC-GH5zPlEVnst7i70xQPDVQz2S7u0SwpVhefjcHbUC9OhsZ1lt4cqm4xjHlhziUOtzNyUGTslu4dO0mX1I5NVo.mp4
|
| 935 |
-
Copy of SnapInsta.to_AQNIiy_P-jZMkrOWfDSH3h9WViFdiGEMaIRvywRZweTWaUWSZhjSibBiRxBkOSIHcj5zUUkYgAVOb2V-2EpcQ_b11yuFN2bRQcs6Lws.mp4
|
| 936 |
-
Copy of SnapInsta.to_AQND0mR2ow6vTuVnNwcwrutmYgsFhnSMAGexig_PLKeM199pRWNdMsLDY6FaH0PlGlvre2yMm40EtsqVTjNM5M80q1BlkmIxktM3g-Q.mp4
|
| 937 |
-
Copy of SnapInsta.to_AQNEwP2t9F925rOYEU51Jq0LlVPIRBa9nCbCD_G-vXWKMToxJTjFXzn6Vo2PBKbADvKjFAd8biFRnOSz496WgNpu_AqYyg4qwLE_k5o.mp4
|
| 938 |
-
Copy of SnapInsta.to_AQNbG4186TN2EY484_GsMwM3q_Ri0DBbGV7uS8fi2aCdfIvikR1NP1vxyMK6QIjhX90Sdvg0209SJ654wIeBxJA1kCa62QwDj5W47r0.mp4
|
| 939 |
-
Copy of SnapInsta.to_AQNCVN6-UqXVT91ReGjppfpeCCKWQ8oto_xSRPicC2EuSs9CDRfBzPBj8agrKOmYs45Q8iEjPGrBRs8vilk1f7Lh8x83v9PCvYlmQOA.mp4
|
| 940 |
-
Copy of SnapInsta.to_AQNjOIeE5DMFtJN4X2MPhqekhwwFmv3sOF-aMG8kT8bpMvvRchxrs1NAQ1XVz3bUzxRGrjYgLnFQ9ZB_mDwJXSptvtAJRCjnbjrUzmM.mp4
|
| 941 |
-
Copy of SnapInsta.to_AQNmsggNL6Yn-t-atp-561xSbGS3FxvjHhGhMP0x46mY0HiPDIEl5CeJ99Ej5KV15o-pIs1Lrs1uMpIkhbNTbTtFOga54wTfhoNjIVo.mp4
|
| 942 |
-
Copy of SnapInsta.to_AQNM_rR45m6afGYLwTwwJu5WEYCvQwrHuUqhz0y4CC8lHYdGxWBQkicLvUmJmXufX41pl5c1IBZPg2Kag94ejM7y9DWjF_lx5VbJMfc.mp4
|
| 943 |
-
Copy of SnapInsta.to_AQNO8B-n5CjOm1VsxZyv35mPXxzrvEOOl4_4kDkDGNs_fiWjfL9J20swdueppYQSicuaeoYDTS3fJ9MhYLixz1Cdap6Lm0pA4Z2p0Qs (1).mp4
|
| 944 |
-
Copy of SnapInsta.to_AQNLLtGp2wew34B6_NV-k3fSXRKlPECoJCbInRFMRcLycpEzAf0_Yva4PD4P8YfnysK_ojHjpuPYDHVnOVTowJ1eR4A9OBXexGdwyng.mp4
|
| 945 |
-
Copy of SnapInsta.to_AQNsH7cmpN-yyag_j6vShSrnV1fP8w7lVLNG_O90rl9YnwRmgCGbo3Gi2eEDn2dqNJ1xLubPgV48OMxGYtzqUymbShmP_hxDnRf06dw.mp4
|
| 946 |
-
Copy of SnapInsta.to_AQNRr9GhZretTrJHKUK7um6WVi_AtqOyKtUjjO_tz9XtMpTMBiokwN_bc8TC_1-UlhkmxNs2TIugDp0gz2un9AErAma2P8i9WMZ3f58.mp4
|
| 947 |
-
Copy of SnapInsta.to_AQNm9topqLNq8f87eVny0iql--ZcmtpRNyfWhOonCcNJ5bqder-1TNedl5lD0bflWEaK0EX0iQC2IuKRscNMcaXRiJbVoQcuEmJjUSc.mp4
|
| 948 |
-
Copy of SnapInsta.to_AQNnvvmmaWDo28OXsNB2h_8I3qZdL7a2FLuZfq4WsoOWSNrtL1CF5fC1ybAJDZeVsygDEYzsvOO9d--shsRSeNFBTaqRuw6pzdDiwzU.mp4
|
| 949 |
-
Copy of SnapInsta.to_AQNpadk3mZr6Lo66w7inLv2rk98mWubWTZOvs4wsODcZ9Ck_Nuqu4tcA3C0ycO2l1nAE28jo9GGWz8XxlQ0JMTdY0Fehpy8LhTZP8zY.mp4
|
| 950 |
-
Copy of SnapInsta.to_AQNK9ZX4i44xF0wV6aS5e1hetjmW_1xtDOtUH1Hnt3Lj5p2vftAXm8tNEccOjhoXwrTJXmSj7OPEWvGCr1GguKg7R6HVYuEnaO8eNbE.mp4
|
| 951 |
-
Copy of SnapInsta.to_AQO7ZYIOnI_dqfLq5JdMz-UAp2A0bU037yMYx-2idJb3MBQe-B7D0bvbztnfe_SSc50F6YaZsWHV-XCfUDbFioB9zWA2EU2mizlCRyI.mp4
|
| 952 |
-
Copy of SnapInsta.to_AQOAWvgyjvaNR3iY6OXcfS-ig48x72JrkYHZWg-ys0QbyOFqgC9yrrcvJELhdnqAUUXTonBsYDi62mGyBz5gKYiqaNnY2-0_zuHgkks.mp4
|
| 953 |
-
Copy of SnapInsta.to_AQO_a7LsTYTpTxO9uVWfJ7M_gsaUBD1Sc5T13uj30TBZjGMMdfcMZRZkbViMTyxT4gsbO349YMAOpoOZOE_RrtA5WEIXOqQmZbyra8k.mp4
|
| 954 |
-
Copy of SnapInsta.to_AQO9SLDWuJxHZ79csaRWumqWCkdG2HZqgRqidCvIfzfHbWwKONGL6pgnlxsvlG8K1L8AUrg4TFKglQkY0nBdLQOtWD6nbznizQPgLIA.mp4
|
| 955 |
-
Copy of SnapInsta.to_AQNtth1ToGiyOPMlKYe0SBK0WOexby_2Z2ahLta5UaSKUgvuUkH28D0gFfkJE3VLyTIxlZ_KyJI6uvKPw7B9xnwPbYcp1CQCqcE8qSc.mp4
|
| 956 |
-
Copy of SnapInsta.to_AQOadKqLUR9JBRbG2Zk_dmd9lMoe2rXha7Apq-aUHJHxlFQ7lUgZcSCRGyGN74CXcQcpgnvBo2nAp3ToPjrxwjXYQHjXypdDkw6bS6U.mp4
|
| 957 |
-
Copy of SnapInsta.to_AQO8TSKuH7ow8_GiZ-4BR82mK6JzT37H-wHxSNXvfFNxYM4ASa1Y6QirMFbvskzOaK02pZ1XUQz2PqIn_PYbvXJ3IO0CKI_BuRwcmQg.mp4
|
| 958 |
-
Copy of SnapInsta.to_AQNSnOTvPLwxDj_QiOGsjAcgBqmgNyEQpAvWHpyKRJIedBC78EpMM_Xaot3WkV2c9y-LBN2Tf28picdOO8TSD5QU.mp4
|
| 959 |
-
Copy of SnapInsta.to_AQO2-e6TiRkzEKV4dM7pNqF97W6VXeRPz09Q2NAcJYlFRV-7UW0MfhMDFJlgr2Ibw5FWOi5NmGaKwt61h38s1YbiuwpN7LUI1Yf3EJw.mp4
|
| 960 |
-
Copy of SnapInsta.to_AQO8KTrxuIFslELZs0kFsxceWfvz99S6X4KVfMNYqNgKOInTwnv288yktfaD8YqNs7DvhcLNgVM9s0a75K3SzVueJpeH3iKCnNgMlho.mp4
|
| 961 |
-
Copy of SnapInsta.to_AQOPt0j4O-5YX1gv5qENhP__-PsmZYAstd_jzuevnElPoxyIyVgwLhQOXNKjszoQG9X9mL3DpFNFE1GU_iNIlFlAKchFlc7CNrVcRgY.mp4
|
| 962 |
-
Copy of SnapInsta.to_AQOFkG140_8Dom5YC6lz0eoNjohs0e_avku_zf-wfm1MlZ-PYgBikxZaYhgHxqteyOAk96Ff5Y4p9h46ybqKmbrt2RJgyuacq8_Xxa4.mp4
|
| 963 |
-
Copy of SnapInsta.to_AQOmiorHc8hj3hCfnU3bdZ3fEuPS-tmDVxzLwP5rU7dh4unpCiAaeJMqhBjZ3CRioZxXLBq1haU8fjPYL7h0TwUgos3BW-TpsyUBjf0.mp4
|
| 964 |
-
Copy of SnapInsta.to_AQOOJUqh8uPFj_Rh9KQxGdl2Uz-HmYD5PHZRV5jCOsm2QKQo65CPXTshdhe6sGk1qzy-Ff3kjz7doGuyqsHEnbMis_dz7MsexVgbpzU.mp4
|
| 965 |
-
Copy of SnapInsta.to_AQOayXIdW15ZLmOiQ2osZ8PI8K5lGqG-3EZzvIMNC8AYHIOc9quCkGq1dR69P4U0pR2GnMjyAWry5hlYfFAYgJ_Y1tMsQOZSfg0NXWo.mp4
|
| 966 |
-
Copy of SnapInsta.to_AQOBNKWmK1dqmGYjIIBru-HIVeY4xI0dTPpjuvkLC-2X_6U8-qqvlJNrRA-j5Ghs1yw3yjK14Ijq4C6t12bncs23oB65ETz9wMsKMQY.mp4
|
| 967 |
-
Copy of SnapInsta.to_AQOR66XEnFibNdyrRgSrdSkaL0npTqJklaTkZc-T9hOtz1zkKXLiZq_8OMpaQ-RyFnbBsnZye1zcZkeMRQBNo_2WhEYRxyZX0MMv-1o.mp4
|
| 968 |
-
Copy of SnapInsta.to_AQORQSyGGwRMdCB12i26XsFVT5zDjlgpBTkUYoB-cs92cTohDCzosOcXIuHUu6veMnHHd4PYXVZuv7XVEuPSiaD5do9UrnHkX9mSQfM.mp4
|
| 969 |
-
Copy of SnapInsta.to_AQOsN58Duo-bdep6JkkZrvFTPYAXlejvpqXTz-J_ftO7bYeFrwTb0vb3tLF7mg8B0D-JquZgIO694eXEjHAw0xhFWWItjJf6ycjYkqs.mp4
|
| 970 |
-
Copy of SnapInsta.to_AQORyO_rvP1CkzVxZSHqCyIcee3VAo1kAV42zV3XocUu09aJ4H58mDG1iwDSY8YRR9nGnfXKihJ4vOTOxdeWdQ5exMKb3Wq0c73zmFs.mp4
|
| 971 |
-
Copy of SnapInsta.to_AQOv_hAltoartNasUQQ4hSiKdCcylPpyXmCbbCbRyHKxjrHCA41awqOMbYnQR-Ti1w0CD1p6w5BwLbXRRgZjJab3NiGolZS3Grltgk8.mp4
|
| 972 |
-
Copy of SnapInsta.to_AQOygPFr8Gkln5sb1tV9c8BvTvcadLxgzY9WEUEATwYwI1sd15taCgmmZPegxwW_TkN3_2ThUdKNOPrDdt_MkAA0XEedRyj8tmTHt-M.mp4
|
| 973 |
-
Copy of SnapInsta.to_AQOUJAP8mtH4xKT4I39Kq25HUSMVkU2LASaDnh18O47MZ-V3SMH63xD69fQbZFLFXgGhP2Co4zuPuCuWv0_FPi9PPTFJl9KjgGrC0vM.mp4
|
| 974 |
-
Copy of SnapInsta.to_AQOy7o8vIxP7Y6xI3NS91ItFhlos5bBZRGxf43jlP0hoXwry4JkNx6bvrzAkwi2o9_5x6JjdZ6BzkYHwo0Z0b7rxGcEOj0-OKht11MI.mp4
|
| 975 |
-
Copy of SnapInsta.to_AQOunhidS10kZDOpHA6A9XGjMiODoeBr80xL6OiobgMdScNwcEqfqYdkv6ZfUT2qeaYpbUQGsunb_0XvGWjnUd7EAW2spOjY52CffKg.mp4
|
| 976 |
-
Copy of SnapInsta.to_AQOx8IAHWDOyhg0CHM6zhgfAdJyFXrY_dVIxkhyZTxb3k9s0zxBHmOcQO1CEMO8KsNe1NNZuVweojv1oOCUxzQjC9oP52_PB93lFz0A.mp4
|
| 977 |
-
Copy of SnapInsta.to_AQOTkgPUq8D0urBxsk19Is__Jiyq5ftumyFFjMFokXSRnR3uOKqRvCmd1AduHKU3gO0Rtf0NwW4L1jKLvyiPFId43MkHQ4suqoZBXZc.mp4
|
| 978 |
-
Copy of SnapInsta.to_AQP0yDwzuY5u4_OEeXAeTvWRCeeTvvVsOoGu5aeoAP963bZJ14i7ghlYbz6SMsyFNi36vt0kdA1vuEGmfwi6Sfb5iiGxrGJHLpO9OTg.mp4
|
| 979 |
-
Copy of SnapInsta.to_AQOsnbtSX-bXxGOt-sabN39kuJQfdqr-6KGKzSfodf6xNJssH8-XAKW2uFo88UnghD08L-_8YXC0DPyx16EqPk1nheICtMAy2QJkCIs.mp4
|
| 980 |
-
Copy of SnapInsta.to_AQOSWASFvE9nzj-lC1Itcqe5oyW762iWXVZOiyDTtUlJxawNqUSrSIsRkuM4YUpYVtJQl1WXOGe9jDeOGFFn23HdBZWdZyWfgFEOty4.mp4
|
| 981 |
-
Copy of SnapInsta.to_AQP9MoVikQxRPirOrSA-f8BSIgZjFIZ_wo8sId5h4TwdgzwUBX5CtWT_YB0b-Igte3JrDWWC9bwHYCXys5GeN8vhcgP4bCpps4xLEFQ.mp4
|
| 982 |
-
Copy of SnapInsta.to_AQP5x_-GxQhzWvPVsQoRFzl8UDQRWPO93pzrQV2qvY1Bi6iWTqhe7Cltb_4YFD3u3I0P5ynjKg8yx_uXk14trK-3ppn-vrEkOl0Zm34.mp4
|
| 983 |
-
Copy of SnapInsta.to_AQP6S-RQHa_MPiX26XQjpVDFPEueoDwW0GjhCigPeU7OEnWQbM-0vj6g3rtJpC0pJy3apsej0wTZp0SN5J8edCJMNT7F9PAFYc_hW18.mp4
|
| 984 |
-
Copy of SnapInsta.to_AQPEGvaRPB83Lv7enXGL2sdBmGOGgsQQtgcqMDAO4GhhX3lJ0Z4RzoUltbd7HMN-Q__fo1moC8hARK_aK9oSN97FUDMJzCqrrOhVVZE.mp4
|
| 985 |
-
Copy of SnapInsta.to_AQP1_On-75FfJhQjoXCVt3V7t95P59YPUqmveJLDjOt2c7ISy-80wt7WKVSz2EeAObMRakx8A2kwxeH1afbkdPfHBctujNVG7tEbgo8.mp4
|
| 986 |
-
Copy of SnapInsta.to_AQP6QdDL5_4vOzglW5A-NHotiReXbDQachk3JkBT-OHNNZP0pkvsqLoKRf_Uj40LsXL5oiTmABvTYAFghnR_U1OK0WZMWue-0lTHMM8.mp4
|
| 987 |
-
Copy of SnapInsta.to_AQPdJWwfU8eUpHNFvFiy5QZeQhWb7Db_k2FoK4YIjLhsRpTj_DPmtupEVpAbTkoHJQR2QwNuoWndb3wdw3bZ3jkZVylawCmn_PNs7RQ.mp4
|
| 988 |
-
Copy of SnapInsta.to_AQPaJ4fbqlmrgWZMENe7Isg7FLZNcLMfxZF__B5UK0tclg_D1JNPuI-nc8mg6jaqisPB1sCyKwKccsioU-_hBo2Lpwoq9_iRRy_WLk8.mp4
|
| 989 |
-
Copy of SnapInsta.to_AQPA3YzDniupzyaY57IcU_7irEO3JAvGVqpQ7at0_BLFsYQ-G9apE6g9e1lIytLEoVP3um1qX-h43JD7qkO6zqwt4lpTwniDB8gpHrk.mp4
|
| 990 |
-
Copy of SnapInsta.to_AQP9gdaQuLVvaNjxVUJ3Ylx-q1eU3bRDugtiP49H9npnnNtteaIVy9ct63yzQ7W3rNvSZCR3ttqDrSfuVpZt3ObmmIV_kGhhuiiYKa0.mp4
|
| 991 |
-
Copy of SnapInsta.to_AQPGUHI008VcSK6wV58xvdjv1NdNbwtRWay4OzkO1I2Sdv9JfSNZ191ukwSQmHXZ_vfw9Z_iFiNTA0Kba9HDvnLVC9YMKvqCjXlLJ68.mp4
|
| 992 |
-
Copy of SnapInsta.to_AQPJRsAHkSr6xCVJ1L9f5u56T3dxTSijNv8O6HGKo4iYvpRdg1uK3eGSRZSWC5vKiSWzHLU00BGsfOMPvUkfyeetf_2Vy7nhkR3eXlo.mp4
|
| 993 |
-
Copy of SnapInsta.to_AQPHYRsdPGnhgb3f225HPoXXah61TJvSkQfCJnuOEPzAo9l9_2m4vDRl8TDw1zHkOqGTk9eyBAVxxKoJcCqzn1xh.mp4
|
| 994 |
-
Copy of SnapInsta.to_AQPIAqJLErRwWYVl-kSAZro51oek2fW-bzxabdyHqgg1Sky_PBjUJpKAl5s9MiAHSOsJwA2UfsVYVw9qpXeMunzKaRQPZ3g-N7t1d34.mp4
|
| 995 |
-
working.mov
|
| 996 |
-
Rooftop View.MOV
|
| 997 |
-
Friends.mov
|
| 998 |
-
Girl in bed.MOV
|
| 999 |
-
Smoking.MOV
|
| 1000 |
-
friends.MOV
|
| 1001 |
-
Copy of Walking through cars 02.MOV
|
| 1002 |
-
laptop.mov
|
| 1003 |
-
Copy of SnapInsta.to_AQN2ykIDs-r4B94ZHW00Xk2u29SC6ULDYqc5k-QdFQN6KMtdXFL9bJ8n3x8y-8eOGDt-x4o45xTW2YIao3p4VeJHssz_FdoUQT-_5yA.mp4
|
| 1004 |
-
Copy of SnapInsta.to_AQPJV_cYuWeBgeu0QgPawjLqqc79jTt7hIaOO0oIDCvui2Ex8LhnrX_npxRdD9E61SzYsNp1z0OBPHH4Uy8vLqeYQhFRmDO96jdZ-JQ.mp4
|
| 1005 |
-
Copy of SnapInsta.to_AQPfC3cEI2ZeGovnvIimFhLElkjti7GIJ5G4TrfAy0UpjkfBaA6ZBdcvtiI2QbD8ykj-wX_G1jBt8wPtdqqJqd5Len_u7W7nH3Oz8ag.mp4
|
| 1006 |
-
Copy of SnapInsta.to_AQPhvYHIzJArhkWJzToJWw2k_8FXsgqjZPWcYE9DRjHYpleWbWvWAzx2lJ1dIa2TZs8tNUIuC6TZ4EvyVQPuDctqlQLOdUWRZ-qy3JA.mp4
|
| 1007 |
-
Copy of SnapInsta.to_AQPg7d8KL6jpUQ9h22MjfsC1uR_gGdBSf63Iyi72eSMyxJPjNW4RtIW_d-NmOpjvOZsm1Hdisb-I9ONQ65LGegZ-_SSV81IHmjahEzo.mp4
|
| 1008 |
-
Copy of SnapInsta.to_AQPhMjuLqhb9OTzDOpcgEEzLSbl1w2oi17klMc1fX7Qf22vpumdh9Atht8M0CeBDZ1aHhW_fOrcpfSvoxhOmndEefDUt717AQIHWYvk.mp4
|
| 1009 |
-
Copy of SnapInsta.to_AQPkdCEMgC-eQ3tMdXeCJb-ua1I_IEa1wWM-SX0MPW8J81pmrXNNONkgFlhnv-JGtiQFPEFlusjJZNurMISW6osyG7NB5vWQwdoG0lk.mp4
|
| 1010 |
-
Copy of SnapInsta.to_AQPURpssZNgdsC4xOneUOmzDk14rRjFZGxY6-6mFLRNeo5NkmoHXXGk9pXhIHkQGK7ayznYw1MMu9RhVvvXoZmvqzqnpSkH_n0I5Rys.mp4
|
| 1011 |
-
Copy of SnapInsta.to_AQPsGi9QwY5zNctKJzD6znNRLrASPbq9pNtehOF1g3L_jRdkADCDF9HBfx15oWYAfqHe_91dXmv2Je5SFZSSib8W1yVtcVOSZqRVues.mp4
|
| 1012 |
-
Copy of SnapInsta.to_AQPSI2P4NidOjTXcpjwfr_ROFUtW5Mqmc_CIZ7EB0kKimrUnbdSpju2_vY3CxTgPvo7sjcmSrv2Qi6zZdn2HNeeTqNKqF0tVOKKoQ6g.mp4
|
| 1013 |
-
Copy of SnapInsta.to_AQPMQBm8SSx6ZVK54qL5dnyTjruumSE_rSUXhS_Z7DFSV8EYbQWHSokiNGYO7l_j7qGfJf5tY8K5lY8ObyEG5DrdM5s02mxsRH2K8Kw.mp4
|
| 1014 |
-
Copy of SnapInsta.to_AQPnyxKiUJYh7wDrsS-7CgSbg75gPnIRKbgG0ZZbj1dhlFAjVx5NZW_xL-OH1pg2q7-55Ar5l2ObZ2zsDPd3UajS-aS3Q8z6OPKjIVQ.mp4
|
| 1015 |
-
Copy of SnapInsta.to_AQPOS2bsqrz46e8ABB_3HBEdQdvg1ZFyKG0I4RqPpwtDaBRLH-MUT6UWQvkP9b-MJDqB4qI7A2bZAOyJ3B_tKqwEmdNCIfvGjZW1TrU.mp4
|
| 1016 |
-
Copy of SnapInsta.to_AQPQ_nXb4csarb1XxzpAA_0cFG5rJjBpVQsQDKKyyaqWuJezS7B9Tjo-xNJZ5UL7e_UuFuXEdDyDAp1PY9h4Vt82IkH1muaGbFwCXDs.mp4
|
| 1017 |
-
Copy of SnapInsta.to_AQPor_9P2JDtXjCq9vraa1yTRuFo1rv1mwC5B4UHeWwhBn4uT3bXYDHP_Tz-tytuf35u0SMqAJmF4HdTYIDIK090hbF21vJUmJZUldc.mp4
|
| 1018 |
-
Copy of SnapInsta.to_AQPO641vvUf3B8a8fL9t32vUpWQlQqUokNHmfdwwg3D9iRuW6P6fw3RMwFpIjYc6HiA2ClYoklPdJgMof5tj8JJfyue0QPQAAVVltaU.mp4
|
| 1019 |
-
Copy of SnapInsta.to_AQPKSxCPu8x9JEGt-DQ5i3JR7ObxKSmz-YuQpRP4XsPxdQM5o-_digxLfsCNOXsZaBsLtvFDeP4WoV39rO_bLovrn1fMn_emhKIH4LI.mp4
|
| 1020 |
-
Copy of SnapInsta.to_AQPXFsDHAfNSFkevjj6G0NZUMoJyCo-yjUU4e9qCNgOK4p506pPRjBtG5V71zMSWWROqLVmfXPy3fPIdUgKathaMGO1-JoXHWmxLV58.mp4
|
| 1021 |
-
Copy of SnapInsta.to_AQPV_ooVDa9QcZg1mybQxz57L8zg2S6ws1GwcVuJvd3bSGfZFwORILEws48HgFi3ViGvtFS7HqciD8vVvki8EJGLK_VYnnOWabhaczk.mp4
|
| 1022 |
-
Copy of SnapInsta.to_AQPwZXc2V07XyON8A7PKft2kW3Dd9v9LxV62qKx6M9ywGwxHaSj3o4K08zTyAuG4RrDkC0_K4h6im5BHopg78xTBUYgGJvT12dUqi8k.mp4
|
| 1023 |
-
Copy of SnapInsta.to_AQPzXTUvG5jQN4B-wXhlvIsbdnrcYxoj5yGB0IsSf0SNGyNg-3Il1EeJpAfYXJdjhJ6HtmkD11Ax4GsqK7lVvL265uK0HGVDkS08jOc.mp4
|
| 1024 |
-
Copy of SnapInsta.to_AQPxefolpO59dgEywT9ip9VAN76Z7buu7nkqdbfhFCCVeA3qlK-tYU_fG7ZapjpOohimutcwBE7ylL3Fv_LJIOfCxQtAX_N6DdFYERc.mp4
|
| 1025 |
-
Copy of SnapInsta.to_AQPYaWsrr49XOX2Vftwy9p54iohuoXgPp2zM6W8HCH6o-M565QQccsDLSqf82S7co9bsXPYJjBARGx-SuhZ6aQAAD_ag0ZOQ10Orl1k.mp4
|
| 1026 |
-
Copy of SnapInsta.to_AQPVOor9gDMCBOGXjHg9jOdEXZ1dviuIV9zDLX3wY-xGCHuKhkLyd0p74Ml4Rkuxt6OK3WlOPim1cKQV__-gYMGch7d39xOaXTh4PB8.mp4
|
| 1027 |
-
Copy of SnapInsta.to_AQPwGj8omuFHL6Q8k7waKtlPmSbi8pwEEpKybmlBX2oGdWtKQCrOBylW9CQ7wF5zQfpAW6QdNq2N2pAXiB14lB94PR_lQj4WBO9CVdc.mp4
|
| 1028 |
-
Copy of SnapInsta.to_AQPw5jk4ua3oW4DtlUa3olix-qwqWsXRfY3g-CqeYHgc5S1ICnxaMZ2cz4j9-58nVItKnB2u1iDFi9vBLhYmqm96HfqwIRJmoBRQzkc.mp4
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
video_analyser/requirements.txt
DELETED
|
@@ -1,15 +0,0 @@
|
|
| 1 |
-
# Google Drive API
|
| 2 |
-
google-api-python-client>=2.100.0
|
| 3 |
-
google-auth-httplib2>=0.1.1
|
| 4 |
-
google-auth-oauthlib>=1.1.0
|
| 5 |
-
|
| 6 |
-
# Gemini AI
|
| 7 |
-
google-generativeai>=0.3.0
|
| 8 |
-
|
| 9 |
-
# Video Processing (only cv2 for duration extraction)
|
| 10 |
-
opencv-python-headless>=4.8.0
|
| 11 |
-
|
| 12 |
-
# Configuration & Utilities
|
| 13 |
-
pyyaml>=6.0
|
| 14 |
-
tqdm>=4.66.0
|
| 15 |
-
python-dotenv>=1.0.0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|