"""Gemini-based qualification analysis for Artist Socials TikTok videos.""" import json import os import tempfile import time from pathlib import Path from google import genai from google.genai import types from pydantic import BaseModel class QualificationResult(BaseModel): is_qualified: bool disqualification_reason: str | None = None QUALIFICATION_PROMPT = """Analyze this TikTok video and determine if it qualifies for use in a music video clipper tool. A video qualifies ONLY if it meets BOTH of these criteria: 1. No on-screen text or captions are visible anywhere in the video. This includes subtitles, lyrics overlays, TikTok text stickers, promotional text, watermarks, song titles, artist names burned into frames, or any other text elements visible on screen. 2. At least one scene shows a 3/4 or fuller view of a person — the body must be visible from the head down to at least the knees in at least one moment. Return: - is_qualified: true only if BOTH criteria are met simultaneously, false if either criterion is not met - disqualification_reason: a single concise sentence explaining why the video does not qualify (required only when is_qualified is false; state which criterion failed and why)""" class SocialVideoAnalyzer: def __init__(self) -> None: from app.core.config import settings api_key = settings.GEMINI_API_KEY or os.environ.get("GEMINI_API_KEY", "") if not api_key: raise RuntimeError("GEMINI_API_KEY is not configured") self.client = genai.Client(api_key=api_key) self.model_name = settings.GEMINI_MODEL def analyze(self, video_path: str) -> QualificationResult: """Downsample, upload, and qualify a video. Cleans up Gemini file on exit.""" import os as _os from fansifter_clipper.ffmpeg_utils import downsample_video src = Path(video_path) with tempfile.TemporaryDirectory() as tmpdir: downsampled = Path(tmpdir) / "downsampled.mp4" # Set env var so the lib's settings singleton picks up the key if _os.environ.get("GEMINI_API_KEY") is None: from app.core.config import settings if settings.GEMINI_API_KEY: _os.environ["GEMINI_API_KEY"] = settings.GEMINI_API_KEY downsample_video(src, downsampled) video_file = self.client.files.upload(file=str(downsampled)) try: deadline = time.monotonic() + 120 while video_file.state.name == "PROCESSING": if time.monotonic() > deadline: raise RuntimeError("Timed out waiting for Gemini file to become ACTIVE") time.sleep(1) video_file = self.client.files.get(name=video_file.name) if video_file.state.name != "ACTIVE": raise RuntimeError(f"Gemini upload failed with state: {video_file.state.name}") config = types.GenerateContentConfig( response_mime_type="application/json", response_schema=QualificationResult, media_resolution=types.MediaResolution.MEDIA_RESOLUTION_LOW, ) response = self.client.models.generate_content( model=self.model_name, contents=[video_file, QUALIFICATION_PROMPT], config=config, ) return QualificationResult(**json.loads(response.text)) finally: try: self.client.files.delete(name=video_file.name) except Exception: pass