Replace moviepy with directly using ffmpeg for video thumbnails
Fixes #809
This commit is contained in:
@@ -22,7 +22,6 @@ import asyncio
|
|||||||
import logging
|
import logging
|
||||||
import pickle
|
import pickle
|
||||||
import pkgutil
|
import pkgutil
|
||||||
import tempfile
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from asyncpg import UniqueViolationError
|
from asyncpg import UniqueViolationError
|
||||||
@@ -46,7 +45,7 @@ from telethon.tl.types import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
from mautrix.appservice import IntentAPI
|
from mautrix.appservice import IntentAPI
|
||||||
from mautrix.util import magic, variation_selector
|
from mautrix.util import ffmpeg, magic, variation_selector
|
||||||
|
|
||||||
from .. import abstract_user as au
|
from .. import abstract_user as au
|
||||||
from ..db import TelegramFile as DBTelegramFile
|
from ..db import TelegramFile as DBTelegramFile
|
||||||
@@ -61,11 +60,6 @@ try:
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
Image = None
|
Image = None
|
||||||
|
|
||||||
try:
|
|
||||||
from moviepy.editor import VideoFileClip
|
|
||||||
except ImportError:
|
|
||||||
VideoFileClip = None
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from mautrix.crypto.attachments import encrypt_attachment
|
from mautrix.crypto.attachments import encrypt_attachment
|
||||||
except ImportError:
|
except ImportError:
|
||||||
@@ -103,29 +97,16 @@ def convert_image(
|
|||||||
return source_mime, file, None, None
|
return source_mime, file, None, None
|
||||||
|
|
||||||
|
|
||||||
def _read_video_thumbnail(
|
async def _read_video_thumbnail(data: bytes, mime_type: str) -> tuple[bytes, int, int]:
|
||||||
data: bytes,
|
first_frame = await ffmpeg.convert_bytes(
|
||||||
video_ext: str = "mp4",
|
data,
|
||||||
frame_ext: str = "png",
|
output_extension=".png",
|
||||||
max_size: tuple[int, int] = (1024, 720),
|
output_args=("-update", "1", "-frames:v", "1"),
|
||||||
) -> tuple[bytes, int, int]:
|
input_mime=mime_type,
|
||||||
with tempfile.NamedTemporaryFile(prefix="mxtg_video_", suffix=f".{video_ext}") as file:
|
logger=log,
|
||||||
# We don't have any way to read the video from memory, so save it to disk.
|
)
|
||||||
file.write(data)
|
width, height = Image.open(BytesIO(first_frame)).size
|
||||||
|
return first_frame, width, height
|
||||||
# Read temp file and get frame
|
|
||||||
frame = VideoFileClip(file.name).get_frame(0)
|
|
||||||
|
|
||||||
# Convert to png and save to BytesIO
|
|
||||||
image = Image.fromarray(frame).convert("RGBA")
|
|
||||||
|
|
||||||
thumbnail_file = BytesIO()
|
|
||||||
if max_size:
|
|
||||||
image.thumbnail(max_size, Image.ANTIALIAS)
|
|
||||||
image.save(thumbnail_file, frame_ext)
|
|
||||||
|
|
||||||
w, h = image.size
|
|
||||||
return thumbnail_file.getvalue(), w, h
|
|
||||||
|
|
||||||
|
|
||||||
def _location_to_id(location: TypeLocation) -> str:
|
def _location_to_id(location: TypeLocation) -> str:
|
||||||
@@ -151,7 +132,7 @@ async def transfer_thumbnail_to_matrix(
|
|||||||
height: int | None = None,
|
height: int | None = None,
|
||||||
async_upload: bool = False,
|
async_upload: bool = False,
|
||||||
) -> DBTelegramFile | None:
|
) -> DBTelegramFile | None:
|
||||||
if not Image or not VideoFileClip:
|
if not Image or not ffmpeg.ffmpeg_path:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
loc_id = _location_to_id(thumbnail_loc)
|
loc_id = _location_to_id(thumbnail_loc)
|
||||||
@@ -170,10 +151,12 @@ async def transfer_thumbnail_to_matrix(
|
|||||||
video_ext = sane_mimetypes.guess_extension(mime_type)
|
video_ext = sane_mimetypes.guess_extension(mime_type)
|
||||||
if custom_data:
|
if custom_data:
|
||||||
file = custom_data
|
file = custom_data
|
||||||
elif VideoFileClip and video_ext and video:
|
elif video_ext and video:
|
||||||
|
log.debug(f"Generating thumbnail for video {loc_id} with ffmpeg")
|
||||||
try:
|
try:
|
||||||
file, width, height = _read_video_thumbnail(video, video_ext, frame_ext="png")
|
file, width, height = await _read_video_thumbnail(video, mime_type=mime_type)
|
||||||
except OSError:
|
except Exception:
|
||||||
|
log.warning(f"Failed to generate thumbnail for {loc_id}", exc_info=True)
|
||||||
return None
|
return None
|
||||||
mime_type = "image/png"
|
mime_type = "image/png"
|
||||||
else:
|
else:
|
||||||
@@ -350,10 +333,10 @@ async def _unlocked_transfer_file_to_matrix(
|
|||||||
client, intent, loc_id, location, filename, encrypt, parallel_id
|
client, intent, loc_id, location, filename, encrypt, parallel_id
|
||||||
)
|
)
|
||||||
mime_type = location.mime_type
|
mime_type = location.mime_type
|
||||||
file = None
|
unencrypted_file = None
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
file = await client.download_file(location)
|
unencrypted_file = file = await client.download_file(location)
|
||||||
except (LocationInvalidError, FileIdInvalidError):
|
except (LocationInvalidError, FileIdInvalidError):
|
||||||
return None
|
return None
|
||||||
except (AuthBytesInvalidError, AuthKeyInvalidError, SecurityError) as e:
|
except (AuthBytesInvalidError, AuthKeyInvalidError, SecurityError) as e:
|
||||||
@@ -410,7 +393,7 @@ async def _unlocked_transfer_file_to_matrix(
|
|||||||
client,
|
client,
|
||||||
intent,
|
intent,
|
||||||
thumbnail,
|
thumbnail,
|
||||||
video=file,
|
video=unencrypted_file,
|
||||||
mime_type=mime_type,
|
mime_type=mime_type,
|
||||||
encrypt=encrypt,
|
encrypt=encrypt,
|
||||||
async_upload=async_upload,
|
async_upload=async_upload,
|
||||||
|
|||||||
@@ -10,8 +10,6 @@ brotli
|
|||||||
pillow>=4,<10
|
pillow>=4,<10
|
||||||
qrcode>=6,<8
|
qrcode>=6,<8
|
||||||
|
|
||||||
#/hq_thumbnails
|
|
||||||
moviepy>=1,<2
|
|
||||||
|
|
||||||
#/formattednumbers
|
#/formattednumbers
|
||||||
phonenumbers>=8,<9
|
phonenumbers>=8,<9
|
||||||
|
|||||||
Reference in New Issue
Block a user