Change the way thumb size selection works

This commit is contained in:
Lonami Exo 2021-10-16 13:56:38 +02:00
parent 03de901b7f
commit e2132d5f7c
5 changed files with 139 additions and 72 deletions

View File

@ -706,3 +706,9 @@ formatting_entities stays because otherwise it's the only feasible way to manual
todo update send_message and send_file docs (well review all functions)
album overhaul. use a list of Message instead.
size selector for download_profile_photo and download_media is now different
still thumb because otherwise documents are weird.
keep support for explicit size instance?

View File

@ -7,7 +7,7 @@ import inspect
import asyncio
from .._crypto import AES
from .._misc import utils, helpers, requestiter, tlobject, hints
from .._misc import utils, helpers, requestiter, tlobject, hints, enums
from .. import errors, _tl
try:
@ -180,7 +180,7 @@ async def download_profile_photo(
entity: 'hints.EntityLike',
file: 'hints.FileLike' = None,
*,
download_big: bool = True) -> typing.Optional[str]:
thumb) -> typing.Optional[str]:
# hex(crc32(x.encode('ascii'))) for x in
# ('User', 'Chat', 'UserFull', 'ChatFull')
ENTITIES = (0x2da17977, 0xc5af5d94, 0x1f4661b9, 0xd49a2697)
@ -189,8 +189,6 @@ async def download_profile_photo(
if not isinstance(entity, tlobject.TLObject) or entity.SUBCLASS_OF_ID in INPUTS:
entity = await self.get_entity(entity)
thumb = -1 if download_big else 0
possible_names = []
if entity.SUBCLASS_OF_ID not in ENTITIES:
photo = entity
@ -212,11 +210,13 @@ async def download_profile_photo(
photo = entity.photo
if isinstance(photo, (_tl.UserProfilePhoto, _tl.ChatPhoto)):
thumb = enums.Size.ORIGINAL if thumb == () else enums.parse_photo_size(thumb)
dc_id = photo.dc_id
loc = _tl.InputPeerPhotoFileLocation(
peer=await self.get_input_entity(entity),
photo_id=photo.photo_id,
big=download_big
big=thumb >= enums.Size.LARGE
)
else:
# It doesn't make any sense to check if `photo` can be used
@ -259,7 +259,7 @@ async def download_media(
message: 'hints.MessageLike',
file: 'hints.FileLike' = None,
*,
thumb: 'typing.Union[int, _tl.TypePhotoSize]' = None,
size = (),
progress_callback: 'hints.ProgressCallback' = None) -> typing.Optional[typing.Union[str, bytes]]:
# Downloading large documents may be slow enough to require a new file reference
# to be obtained mid-download. Store (input chat, message id) so that the message
@ -292,11 +292,11 @@ async def download_media(
return await _download_document(
self, media, file, date, thumb, progress_callback, msg_data
)
elif isinstance(media, _tl.MessageMediaContact) and thumb is None:
elif isinstance(media, _tl.MessageMediaContact):
return _download_contact(
self, media, file
)
elif isinstance(media, (_tl.WebDocument, _tl.WebDocumentNoProxy)) and thumb is None:
elif isinstance(media, (_tl.WebDocument, _tl.WebDocumentNoProxy)):
return await _download_web_document(
self, media, file, progress_callback
)
@ -491,44 +491,15 @@ def _iter_download(
def _get_thumb(thumbs, thumb):
# Seems Telegram has changed the order and put `PhotoStrippedSize`
# last while this is the smallest (layer 116). Ensure we have the
# sizes sorted correctly with a custom function.
def sort_thumbs(thumb):
if isinstance(thumb, _tl.PhotoStrippedSize):
return 1, len(thumb.bytes)
if isinstance(thumb, _tl.PhotoCachedSize):
return 1, len(thumb.bytes)
if isinstance(thumb, _tl.PhotoSize):
return 1, thumb.size
if isinstance(thumb, _tl.PhotoSizeProgressive):
return 1, max(thumb.sizes)
if isinstance(thumb, _tl.VideoSize):
return 2, thumb.size
# Empty size or invalid should go last
return 0, 0
thumbs = list(sorted(thumbs, key=sort_thumbs))
for i in reversed(range(len(thumbs))):
# :tl:`PhotoPathSize` is used for animated stickers preview, and the thumb is actually
# a SVG path of the outline. Users expect thumbnails to be JPEG files, so pretend this
# thumb size doesn't actually exist (#1655).
if isinstance(thumbs[i], _tl.PhotoPathSize):
thumbs.pop(i)
if thumb is None:
return thumbs[-1]
elif isinstance(thumb, int):
return thumbs[thumb]
elif isinstance(thumb, str):
return next((t for t in thumbs if t.type == thumb), None)
elif isinstance(thumb, (_tl.PhotoSize, _tl.PhotoCachedSize,
_tl.PhotoStrippedSize, _tl.VideoSize)):
if isinstance(thumb, tlobject.TLObject):
return thumb
else:
return None
thumb = enums.parse_photo_size(thumb)
return min(
thumbs,
default=None,
key=lambda t: abs(thumb - enums.parse_photo_size(t.type))
)
def _download_cached_photo_size(self: 'TelegramClient', size, file):
# No need to download anything, simply write the bytes
@ -623,7 +594,7 @@ async def _download_document(
if not isinstance(document, _tl.Document):
return
if thumb is None:
if thumb == ():
kind, possible_names = _get_kind_and_names(document.attributes)
file = _get_proper_filename(
file, kind, utils.get_extension(document),

View File

@ -1610,7 +1610,7 @@ class TelegramClient:
entity: 'hints.EntityLike',
file: 'hints.FileLike' = None,
*,
download_big: bool = True) -> typing.Optional[str]:
thumb: typing.Union[str, enums.Size] = ()) -> typing.Optional[str]:
"""
Downloads the profile photo from the given user, chat or channel.
@ -1634,8 +1634,15 @@ class TelegramClient:
If file is the type `bytes`, it will be downloaded in-memory
as a bytestring (e.g. ``file=bytes``).
download_big (`bool`, optional):
Whether to use the big version of the available photos.
thumb (optional):
The thumbnail size to download. A different size may be chosen
if the specified size doesn't exist. The category of the size
you choose will be respected when possible (e.g. if you
specify a cropped size, a cropped variant of similar size will
be preferred over a boxed variant of similar size). Cropped
images are considered to be smaller than boxed images.
By default, the largest size (original) is downloaded.
Returns
`None` if no photo was provided, or if it was Empty. On success
@ -1655,7 +1662,7 @@ class TelegramClient:
message: 'hints.MessageLike',
file: 'hints.FileLike' = None,
*,
thumb: 'typing.Union[int, _tl.TypePhotoSize]' = None,
thumb: typing.Union[str, enums.Size] = (),
progress_callback: 'hints.ProgressCallback' = None) -> typing.Optional[typing.Union[str, bytes]]:
"""
Downloads the given media from a message object.
@ -1680,29 +1687,15 @@ class TelegramClient:
A callback function accepting two parameters:
``(received bytes, total)``.
thumb (`int` | :tl:`PhotoSize`, optional):
Which thumbnail size from the document or photo to download,
instead of downloading the document or photo itself.
thumb (optional):
The thumbnail size to download. A different size may be chosen
if the specified size doesn't exist. The category of the size
you choose will be respected when possible (e.g. if you
specify a cropped size, a cropped variant of similar size will
be preferred over a boxed variant of similar size). Cropped
images are considered to be smaller than boxed images.
If it's specified but the file does not have a thumbnail,
this method will return `None`.
The parameter should be an integer index between ``0`` and
``len(sizes)``. ``0`` will download the smallest thumbnail,
and ``len(sizes) - 1`` will download the largest thumbnail.
You can also use negative indices, which work the same as
they do in Python's `list`.
You can also pass the :tl:`PhotoSize` instance to use.
Alternatively, the thumb size type `str` may be used.
In short, use ``thumb=0`` if you want the smallest thumbnail
and ``thumb=-1`` if you want the largest thumbnail.
.. note::
The largest thumbnail may be a video instead of a photo,
as they are available since layer 116 and are bigger than
any of the photos.
By default, the original media is downloaded.
Returns
`None` if no media was provided, or if it was Empty. On success

View File

@ -1,6 +1,16 @@
from enum import Enum
def _impl_op(which):
def op(self, other):
if not isinstance(other, type(self)):
return NotImplemented
return getattr(self._val(), which)(other._val())
return op
class ConnectionMode(Enum):
FULL = 'full'
INTERMEDIATE = 'intermediate'
@ -38,6 +48,91 @@ class Action(Enum):
CANCEL = 'cancel'
class Size(Enum):
"""
See https://core.telegram.org/api/files#image-thumbnail-types.
* ``'s'``. The image fits within a box of 100x100.
* ``'m'``. The image fits within a box of 320x320.
* ``'x'``. The image fits within a box of 800x800.
* ``'y'``. The image fits within a box of 1280x1280.
* ``'w'``. The image fits within a box of 2560x2560.
* ``'a'``. The image was cropped to be at most 160x160.
* ``'b'``. The image was cropped to be at most 320x320.
* ``'c'``. The image was cropped to be at most 640x640.
* ``'d'``. The image was cropped to be at most 1280x1280.
* ``'i'``. The image comes inline (no need to download anything).
* ``'j'``. Only the image outline is present (for stickers).
* ``'u'``. The image is actually a short MPEG4 animated video.
* ``'v'``. The image is actually a short MPEG4 video preview.
The sorting order is first dimensions, then ``cropped < boxed < video < other``.
"""
SMALL = 's'
MEDIUM = 'm'
LARGE = 'x'
EXTRA_LARGE = 'y'
ORIGINAL = 'w'
CROPPED_SMALL = 'a'
CROPPED_MEDIUM = 'b'
CROPPED_LARGE = 'c'
CROPPED_EXTRA_LARGE = 'd'
INLINE = 'i'
OUTLINE = 'j'
ANIMATED = 'u'
VIDEO = 'v'
def __hash__(self):
return object.__hash__(self)
__sub__ = _impl_op('__sub__')
__lt__ = _impl_op('__lt__')
__le__ = _impl_op('__le__')
__eq__ = _impl_op('__eq__')
__ne__ = _impl_op('__ne__')
__gt__ = _impl_op('__gt__')
__ge__ = _impl_op('__ge__')
def _val(self):
return self._category() * 100 + self._size()
def _category(self):
return {
Size.SMALL: 2,
Size.MEDIUM: 2,
Size.LARGE: 2,
Size.EXTRA_LARGE: 2,
Size.ORIGINAL: 2,
Size.CROPPED_SMALL: 1,
Size.CROPPED_MEDIUM: 1,
Size.CROPPED_LARGE: 1,
Size.CROPPED_EXTRA_LARGE: 1,
Size.INLINE: 4,
Size.OUTLINE: 5,
Size.ANIMATED: 3,
Size.VIDEO: 3,
}[self]
def _size(self):
return {
Size.SMALL: 1,
Size.MEDIUM: 3,
Size.LARGE: 5,
Size.EXTRA_LARGE: 6,
Size.ORIGINAL: 7,
Size.CROPPED_SMALL: 2,
Size.CROPPED_MEDIUM: 3,
Size.CROPPED_LARGE: 4,
Size.CROPPED_EXTRA_LARGE: 6,
# 0, since they're not the original photo at all
Size.INLINE: 0,
Size.OUTLINE: 0,
# same size as original or extra large (videos are large)
Size.ANIMATED: 7,
Size.VIDEO: 6,
}[self]
def _mk_parser(cls):
def parser(value):
if isinstance(value, cls):
@ -57,3 +152,4 @@ def _mk_parser(cls):
parse_conn_mode = _mk_parser(ConnectionMode)
parse_participant = _mk_parser(Participant)
parse_typing_action = _mk_parser(Action)
parse_photo_size = _mk_parser(Size)

View File

@ -2,4 +2,5 @@ from ._misc.enums import (
ConnectionMode,
Participant,
Action,
Size,
)