import re import httpx from app.exceptions import ( LnktoException, SpotifyThumbnailException, YoutubeImageException, ) from app.image import Image from app.models import ( Creative, ) from .base import BaseCreativeProcessor class GoogleCreativeProcessor(BaseCreativeProcessor): YOUTUBE_IMAGE_URL = ( "https://img.youtube.com/vi/{video_id}/{resolution_prefix}default.jpg" ) YOUTUBE_IMAGE_RESOLUTION_PREFIXES = ("maxres", "hq", "mq", "sd", "") YOUTUBE_VIDEO_URL_PATTERN = r"(?:https?:\\?\/\\?\/)?(?:www\.)?(?:youtube\.com\\?\/(?:[^\/\n\s]+\/.+\/|(?:v|e(?:mbed)?)\/|\S*?[?&]v=|shorts\/)|youtu\.be\\?\/)([a-zA-Z0-9_-]{11})" SPOTIFY_EMBEDDED_URL = "https://open.spotify.com/oembed" async def _get_image_for_creative(self, creative: Creative) -> Image: if self._get_youtube_video_id_from_string(creative.source_url): image = await self._get_image_from_youtube_video_url(creative.source_url) elif "lnk.to/" in creative.source_url: image = await self._get_image_from_lnkto_url(creative.source_url) elif "open.spotify.com/playlist/" in creative.source_url: image = await self._get_image_from_spotify_url(creative.source_url) else: image = await self._get_image_from_url(creative.source_url) return image def _get_youtube_video_id_from_string(self, string: str) -> str | None: match = re.search(self.YOUTUBE_VIDEO_URL_PATTERN, string, flags=re.IGNORECASE) return match.group(1) if match else None async def _get_image_from_lnkto_url(self, url: str) -> Image: response = await self.web_client.get(url) response.raise_for_status() youtube_video_id = self._get_youtube_video_id_from_string( response.content.decode() ) if not youtube_video_id: raise LnktoException("Youtube link not found in response") return await self._get_image_for_youtube_video_id(youtube_video_id) async def _get_image_from_spotify_url(self, url: str) -> Image: creative_url_response = await self.web_client.get( self.SPOTIFY_EMBEDDED_URL, params={"url": url}, ) creative_url_response.raise_for_status() thumbnail_url = creative_url_response.json().get("thumbnail_url") if not thumbnail_url: raise SpotifyThumbnailException return await self._get_image_from_url(thumbnail_url) async def _get_image_for_youtube_video_id( self, youtube_video_id: str | None ) -> Image: # Sometimes youtube video thumbnail is not available under maxres prefix # This function checks all available prefixes for existing image and returns first available for prefix in self.YOUTUBE_IMAGE_RESOLUTION_PREFIXES: try: image = await self._get_image_from_url( self.YOUTUBE_IMAGE_URL.format( video_id=youtube_video_id, resolution_prefix=prefix ) ) return image except httpx.HTTPError: continue raise YoutubeImageException async def _get_image_from_url(self, url: str) -> Image: response = await self.web_client.get(url) response.raise_for_status() return Image(response.content) async def _get_image_from_youtube_video_url(self, url: str) -> Image: video_id = self._get_youtube_video_id_from_string(url) return await self._get_image_for_youtube_video_id(video_id)