Emoji renderer for Pillow with Discord emoji support and multiple emoji sources
—
Comprehensive system for fetching emoji images from various providers. The source system supports multiple emoji styles (Twitter, Apple, Microsoft, etc.), Discord custom emojis, HTTP optimization with requests library, and extensibility through custom source implementations.
Abstract base class defining the interface for all emoji sources, with methods for retrieving both Unicode emojis and Discord custom emojis.
class BaseSource(ABC):
"""Base class for emoji image sources."""
@abstractmethod
def get_emoji(self, emoji: str, /) -> Optional[BytesIO]:
"""
Retrieves emoji image as BytesIO stream.
Parameters:
- emoji: str - Unicode emoji to retrieve
Returns:
- BytesIO - Image stream, or None if not found
"""
@abstractmethod
def get_discord_emoji(self, id: int, /) -> Optional[BytesIO]:
"""
Retrieves Discord emoji image as BytesIO stream.
Parameters:
- id: int - Discord emoji snowflake ID
Returns:
- BytesIO - Image stream, or None if not found
"""Base class for sources that fetch emojis via HTTP, with automatic fallback from requests library to urllib and configurable request parameters.
class HTTPBasedSource(BaseSource):
"""Base class for HTTP-based emoji sources."""
REQUEST_KWARGS: ClassVar[Dict[str, Any]] = {
'headers': {'User-Agent': 'Mozilla/5.0'}
}
def request(self, url: str) -> bytes:
"""
Makes GET request to URL using requests library or urllib fallback.
Parameters:
- url: str - URL to request
Returns:
- bytes - Response content
Raises:
- HTTPError - Request failed
"""Mixin class that adds Discord emoji functionality to other sources, fetching from Discord's CDN.
class DiscordEmojiSourceMixin(HTTPBasedSource):
"""Mixin adding Discord emoji functionality."""
BASE_DISCORD_EMOJI_URL: ClassVar[str] = 'https://cdn.discordapp.com/emojis/'
def get_discord_emoji(self, id: int, /) -> Optional[BytesIO]:
"""
Fetches Discord emoji from Discord CDN.
Parameters:
- id: int - Discord emoji ID
Returns:
- BytesIO - Emoji image stream
"""Base class for sources using the emojicdn.elk.sh service, supporting multiple emoji styles through style parameter.
class EmojiCDNSource(DiscordEmojiSourceMixin):
"""Base source for emojicdn.elk.sh service."""
BASE_EMOJI_CDN_URL: ClassVar[str] = 'https://emojicdn.elk.sh/'
STYLE: ClassVar[str] = None # Must be overridden in subclasses
def get_emoji(self, emoji: str, /) -> Optional[BytesIO]:
"""
Fetches emoji from EmojiCDN with configured style.
Parameters:
- emoji: str - Unicode emoji
Returns:
- BytesIO - Emoji image stream
"""Ready-to-use emoji sources for major emoji providers, each with distinct visual styles and characteristics.
# Primary providers
class Twemoji(EmojiCDNSource):
"""Twitter-style emojis (default source, also used by Discord)."""
STYLE = 'twitter'
class AppleEmojiSource(EmojiCDNSource):
"""Apple-style emojis with iOS aesthetic."""
STYLE = 'apple'
class MicrosoftEmojiSource(EmojiCDNSource):
"""Microsoft-style emojis with Windows aesthetic."""
STYLE = 'microsoft'
class GoogleEmojiSource(EmojiCDNSource):
"""Google-style Noto emojis."""
STYLE = 'google'
# Additional providers
class SamsungEmojiSource(EmojiCDNSource):
"""Samsung-style emojis."""
STYLE = 'samsung'
class WhatsAppEmojiSource(EmojiCDNSource):
"""WhatsApp-style emojis."""
STYLE = 'whatsapp'
class FacebookEmojiSource(EmojiCDNSource):
"""Facebook-style emojis."""
STYLE = 'facebook'
class MessengerEmojiSource(EmojiCDNSource):
"""Facebook Messenger-style emojis."""
STYLE = 'messenger'
class JoyPixelsEmojiSource(EmojiCDNSource):
"""JoyPixels (formerly EmojiOne) emojis."""
STYLE = 'joypixels'
class OpenmojiEmojiSource(EmojiCDNSource):
"""Open-source Openmoji emojis."""
STYLE = 'openmoji'
class EmojidexEmojiSource(EmojiCDNSource):
"""Emojidex-style emojis."""
STYLE = 'emojidex'
class MozillaEmojiSource(EmojiCDNSource):
"""Mozilla-style emojis."""
STYLE = 'mozilla'
# Convenience aliases
TwemojiEmojiSource = Twemoji = TwitterEmojiSource
Openmoji = OpenmojiEmojiSource
FacebookMessengerEmojiSource = MessengerEmojiSourcefrom pilmoji import Pilmoji
from pilmoji.source import (
MicrosoftEmojiSource, AppleEmojiSource,
OpenmojiEmojiSource, JoyPixelsEmojiSource
)
from PIL import Image
image = Image.new('RGB', (300, 200), 'white')
# Use different emoji sources
sources = [
('Microsoft', MicrosoftEmojiSource),
('Apple', AppleEmojiSource),
('Openmoji', OpenmojiEmojiSource),
('JoyPixels', JoyPixelsEmojiSource)
]
y_pos = 10
for name, source_class in sources:
with Pilmoji(image, source=source_class) as pilmoji:
pilmoji.text((10, y_pos), f"{name}: 😊 🎉 🌟 ❤️", (0, 0, 0))
y_pos += 40Guide for implementing custom emoji sources by subclassing BaseSource or HTTPBasedSource.
class CustomEmojiSource(HTTPBasedSource):
"""Custom emoji source from your own CDN."""
def __init__(self, base_url: str):
super().__init__()
self.base_url = base_url.rstrip('/')
def get_emoji(self, emoji: str) -> Optional[BytesIO]:
# Convert emoji to custom URL format
emoji_code = hex(ord(emoji))[2:].upper()
url = f"{self.base_url}/emoji_{emoji_code}.png"
try:
return BytesIO(self.request(url))
except HTTPError:
return None
def get_discord_emoji(self, id: int) -> Optional[BytesIO]:
# Custom Discord emoji handling
url = f"{self.base_url}/discord/{id}.png"
try:
return BytesIO(self.request(url))
except HTTPError:
return None
# Usage
custom_source = CustomEmojiSource("https://my-emoji-cdn.com")
with Pilmoji(image, source=custom_source) as pilmoji:
pilmoji.text((10, 10), "Custom emojis! 🎨", (0, 0, 0))import os
from pathlib import Path
class LocalEmojiSource(BaseSource):
"""Source that loads emojis from local filesystem."""
def __init__(self, emoji_dir: str):
self.emoji_dir = Path(emoji_dir)
def get_emoji(self, emoji: str) -> Optional[BytesIO]:
# Convert emoji to filename
emoji_code = f"U+{ord(emoji):04X}"
emoji_file = self.emoji_dir / f"{emoji_code}.png"
if emoji_file.exists():
with open(emoji_file, 'rb') as f:
return BytesIO(f.read())
return None
def get_discord_emoji(self, id: int) -> Optional[BytesIO]:
emoji_file = self.emoji_dir / "discord" / f"{id}.png"
if emoji_file.exists():
with open(emoji_file, 'rb') as f:
return BytesIO(f.read())
return None
# Usage
local_source = LocalEmojiSource("/path/to/emoji/files")
with Pilmoji(image, source=local_source) as pilmoji:
pilmoji.text((10, 10), "Local emojis! 📁", (0, 0, 0))Install with Tessl CLI
npx tessl i tessl/pypi-pilmoji