support for lottie json stickers
This commit is contained in:
parent
c60ad506df
commit
cae47b697c
5 changed files with 123 additions and 15 deletions
|
|
@ -18,6 +18,8 @@ def setup_logging():
|
||||||
level=level,
|
level=level,
|
||||||
handlers=handlers
|
handlers=handlers
|
||||||
)
|
)
|
||||||
|
# Suppress PIL debug logs which are very noisy during Lottie conversion
|
||||||
|
logging.getLogger('PIL').setLevel(logging.INFO)
|
||||||
|
|
||||||
def relaunch_in_terminal():
|
def relaunch_in_terminal():
|
||||||
"""Detects if running without a terminal on Linux and relaunches in one."""
|
"""Detects if running without a terminal on Linux and relaunches in one."""
|
||||||
|
|
|
||||||
|
|
@ -5,3 +5,6 @@ rich # Terminal formatting and rich text
|
||||||
textual # Terminal UI framework
|
textual # Terminal UI framework
|
||||||
PyYAML # YAML parsing and serialization
|
PyYAML # YAML parsing and serialization
|
||||||
pydantic # Data validation using Python type hints
|
pydantic # Data validation using Python type hints
|
||||||
|
lottie # Lottie file manipulation and conversion
|
||||||
|
Pillow # Image processing (required for GIF rendering)
|
||||||
|
cairosvg # SVG rendering (required for Lottie conversion)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import discord
|
import discord
|
||||||
import logging
|
import logging
|
||||||
from typing import AsyncGenerator, Dict, Any
|
from typing import AsyncGenerator, Dict, Any, Union
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
@ -227,9 +227,44 @@ class DiscordReader:
|
||||||
"""Downloads a Discord emoji into memory."""
|
"""Downloads a Discord emoji into memory."""
|
||||||
return await emoji.read()
|
return await emoji.read()
|
||||||
|
|
||||||
async def download_sticker(self, sticker: discord.GuildSticker) -> bytes:
|
async def download_sticker(self, sticker: Union[discord.GuildSticker, discord.StickerItem]) -> bytes:
|
||||||
"""Downloads a Discord sticker into memory."""
|
"""Downloads a Discord sticker into memory."""
|
||||||
|
logger.debug(f"Attempting to download sticker: {getattr(sticker, 'name', 'unknown')} (type: {type(sticker)})")
|
||||||
|
|
||||||
|
# 1. Try directly reading
|
||||||
|
if hasattr(sticker, 'read'):
|
||||||
|
try:
|
||||||
return await sticker.read()
|
return await sticker.read()
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug(f"Direct read failed for sticker: {e}")
|
||||||
|
|
||||||
|
# 2. Try converting to full sticker (only for StickerItem)
|
||||||
|
if hasattr(sticker, 'to_sticker'):
|
||||||
|
try:
|
||||||
|
logger.debug(f"Attempting to_sticker() for {getattr(sticker, 'name', 'unknown')}")
|
||||||
|
full_sticker = await sticker.to_sticker()
|
||||||
|
if hasattr(full_sticker, 'read'):
|
||||||
|
return await full_sticker.read()
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug(f"to_sticker fallback failed: {e}")
|
||||||
|
|
||||||
|
# 3. Try downloading from URL as last resort
|
||||||
|
url = getattr(sticker, 'url', None)
|
||||||
|
if url:
|
||||||
|
try:
|
||||||
|
import aiohttp
|
||||||
|
logger.debug(f"Attempting URL download for sticker from {url}")
|
||||||
|
async with aiohttp.ClientSession() as session:
|
||||||
|
async with session.get(str(url)) as resp:
|
||||||
|
if resp.status == 200:
|
||||||
|
return await resp.read()
|
||||||
|
else:
|
||||||
|
logger.debug(f"URL download failed with status {resp.status}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug(f"URL download failed for sticker: {e}")
|
||||||
|
|
||||||
|
logger.warning(f"Failed to download sticker {getattr(sticker, 'name', 'unknown')} after all attempts")
|
||||||
|
return b""
|
||||||
|
|
||||||
async def download_attachment(self, attachment: discord.Attachment) -> bytes:
|
async def download_attachment(self, attachment: discord.Attachment) -> bytes:
|
||||||
"""Downloads a Discord attachment into memory."""
|
"""Downloads a Discord attachment into memory."""
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,17 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
|
import json
|
||||||
|
import io
|
||||||
from typing import Callable, Awaitable, Dict, Any
|
from typing import Callable, Awaitable, Dict, Any
|
||||||
|
|
||||||
|
try:
|
||||||
|
from lottie.objects import Animation
|
||||||
|
from lottie.exporters.gif import export_gif
|
||||||
|
HAS_LOTTIE = True
|
||||||
|
except ImportError:
|
||||||
|
HAS_LOTTIE = False
|
||||||
|
|
||||||
from src.core.base import MigrationContext
|
from src.core.base import MigrationContext
|
||||||
from src.core.utils import resolve_discord_links
|
from src.core.utils import resolve_discord_links
|
||||||
|
|
||||||
|
|
@ -242,18 +251,43 @@ async def migrate_messages(
|
||||||
sticker_data = await context.discord_reader.download_sticker(s)
|
sticker_data = await context.discord_reader.download_sticker(s)
|
||||||
if sticker_data:
|
if sticker_data:
|
||||||
# Use format to determine extension
|
# Use format to determine extension
|
||||||
ext = getattr(s, 'format', 'png')
|
format_val = getattr(s, 'format', 'png')
|
||||||
if hasattr(ext, 'name'): # discord.py StickerFormat enum
|
logger.debug(f"Sticker {getattr(s, 'name', 'unknown')} format_val type: {type(format_val)}, value: {format_val}")
|
||||||
ext = ext.name
|
|
||||||
|
|
||||||
# Handle Lottie (json)
|
if hasattr(format_val, 'name'): # discord.py StickerFormat enum
|
||||||
|
ext = format_val.name.lower()
|
||||||
|
elif isinstance(format_val, int):
|
||||||
|
# Map common StickerFormat values if it's an int
|
||||||
|
format_map = {1: 'png', 2: 'apng', 3: 'lottie', 4: 'gif'}
|
||||||
|
ext = format_map.get(format_val, 'png')
|
||||||
|
else:
|
||||||
|
ext = str(format_val).lower()
|
||||||
|
|
||||||
|
logger.debug(f"Determined sticker extension: {ext}")
|
||||||
|
|
||||||
|
# Handle Lottie (json) -> Convert to GIF
|
||||||
if ext == 'lottie':
|
if ext == 'lottie':
|
||||||
|
if HAS_LOTTIE:
|
||||||
|
try:
|
||||||
|
logger.debug(f"Converting Lottie sticker {s.name} to GIF...")
|
||||||
|
lottie_data = json.loads(sticker_data)
|
||||||
|
animation = Animation.load(lottie_data)
|
||||||
|
output = io.BytesIO()
|
||||||
|
export_gif(animation, output)
|
||||||
|
sticker_data = output.getvalue()
|
||||||
|
ext = 'gif'
|
||||||
|
logger.debug(f"Successfully converted Lottie sticker {s.name} to GIF")
|
||||||
|
except Exception as conv_err:
|
||||||
|
logger.error(f"Failed to convert Lottie sticker {s.name} to GIF: {conv_err}")
|
||||||
|
ext = 'json' # Fallback to json if conversion fails
|
||||||
|
else:
|
||||||
|
logger.warning(f"Lottie library not available, sending sticker {s.name} as raw JSON")
|
||||||
ext = 'json'
|
ext = 'json'
|
||||||
|
|
||||||
filename = f"sticker_{s.name}_{s.id}.{ext}"
|
filename = f"sticker_{s.name}_{s.id}.{ext}"
|
||||||
files.append({"filename": filename, "data": sticker_data})
|
files.append({"filename": filename, "data": sticker_data})
|
||||||
stats["attachments"] += 1
|
stats["attachments"] += 1
|
||||||
logger.debug(f"Added sticker {s.name} as attachment")
|
logger.debug(f"Added sticker {s.name} as attachment (extension: {ext})")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Failed to download sticker {getattr(s, 'name', 'unknown')}: {e}")
|
logger.error(f"Failed to download sticker {getattr(s, 'name', 'unknown')}: {e}")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,17 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
|
import json
|
||||||
|
import io
|
||||||
from typing import Callable, Awaitable, Dict, Any
|
from typing import Callable, Awaitable, Dict, Any
|
||||||
|
|
||||||
|
try:
|
||||||
|
from lottie.objects import Animation
|
||||||
|
from lottie.exporters.gif import export_gif
|
||||||
|
HAS_LOTTIE = True
|
||||||
|
except ImportError:
|
||||||
|
HAS_LOTTIE = False
|
||||||
|
|
||||||
from src.core.base import MigrationContext
|
from src.core.base import MigrationContext
|
||||||
from src.core.utils import resolve_discord_links
|
from src.core.utils import resolve_discord_links
|
||||||
|
|
||||||
|
|
@ -245,18 +254,43 @@ async def migrate_messages(
|
||||||
sticker_data = await context.discord_reader.download_sticker(s)
|
sticker_data = await context.discord_reader.download_sticker(s)
|
||||||
if sticker_data:
|
if sticker_data:
|
||||||
# Use format to determine extension
|
# Use format to determine extension
|
||||||
ext = getattr(s, 'format', 'png')
|
format_val = getattr(s, 'format', 'png')
|
||||||
if hasattr(ext, 'name'): # discord.py StickerFormat enum
|
logger.debug(f"Sticker {getattr(s, 'name', 'unknown')} format_val type: {type(format_val)}, value: {format_val}")
|
||||||
ext = ext.name
|
|
||||||
|
|
||||||
# Handle Lottie (json)
|
if hasattr(format_val, 'name'): # discord.py StickerFormat enum
|
||||||
|
ext = format_val.name.lower()
|
||||||
|
elif isinstance(format_val, int):
|
||||||
|
# Map common StickerFormat values if it's an int
|
||||||
|
format_map = {1: 'png', 2: 'apng', 3: 'lottie', 4: 'gif'}
|
||||||
|
ext = format_map.get(format_val, 'png')
|
||||||
|
else:
|
||||||
|
ext = str(format_val).lower()
|
||||||
|
|
||||||
|
logger.debug(f"Determined sticker extension: {ext}")
|
||||||
|
|
||||||
|
# Handle Lottie (json) -> Convert to GIF
|
||||||
if ext == 'lottie':
|
if ext == 'lottie':
|
||||||
|
if HAS_LOTTIE:
|
||||||
|
try:
|
||||||
|
logger.debug(f"Converting Lottie sticker {s.name} to GIF...")
|
||||||
|
lottie_data = json.loads(sticker_data)
|
||||||
|
animation = Animation.load(lottie_data)
|
||||||
|
output = io.BytesIO()
|
||||||
|
export_gif(animation, output)
|
||||||
|
sticker_data = output.getvalue()
|
||||||
|
ext = 'gif'
|
||||||
|
logger.debug(f"Successfully converted Lottie sticker {s.name} to GIF")
|
||||||
|
except Exception as conv_err:
|
||||||
|
logger.error(f"Failed to convert Lottie sticker {s.name} to GIF: {conv_err}")
|
||||||
|
ext = 'json' # Fallback to json if conversion fails
|
||||||
|
else:
|
||||||
|
logger.warning(f"Lottie library not available, sending sticker {s.name} as raw JSON")
|
||||||
ext = 'json'
|
ext = 'json'
|
||||||
|
|
||||||
filename = f"sticker_{s.name}_{s.id}.{ext}"
|
filename = f"sticker_{s.name}_{s.id}.{ext}"
|
||||||
files.append({"filename": filename, "data": sticker_data})
|
files.append({"filename": filename, "data": sticker_data})
|
||||||
stats["attachments"] += 1
|
stats["attachments"] += 1
|
||||||
logger.debug(f"Added sticker {s.name} as attachment")
|
logger.debug(f"Added sticker {s.name} as attachment (extension: {ext})")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Failed to download sticker {getattr(s, 'name', 'unknown')}: {e}")
|
logger.error(f"Failed to download sticker {getattr(s, 'name', 'unknown')}: {e}")
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue