mirror of https://github.com/zulip/zulip.git
thumbnailing: Move resizing functions into zerver.lib.thumbnail.
This commit is contained in:
parent
2c5dff7f59
commit
0153d6dbcd
|
@ -21,7 +21,7 @@ from PIL import Image
|
||||||
|
|
||||||
from zerver.lib.integrations import INTEGRATIONS
|
from zerver.lib.integrations import INTEGRATIONS
|
||||||
from zerver.lib.storage import static_path
|
from zerver.lib.storage import static_path
|
||||||
from zerver.lib.upload.base import DEFAULT_AVATAR_SIZE, resize_avatar
|
from zerver.lib.thumbnail import DEFAULT_AVATAR_SIZE, resize_avatar
|
||||||
|
|
||||||
|
|
||||||
def create_square_image(png: bytes) -> bytes:
|
def create_square_image(png: bytes) -> bytes:
|
||||||
|
|
|
@ -47,7 +47,8 @@ from zerver.data_import.slack_message_conversion import (
|
||||||
from zerver.lib.emoji import codepoint_to_name
|
from zerver.lib.emoji import codepoint_to_name
|
||||||
from zerver.lib.export import MESSAGE_BATCH_CHUNK_SIZE
|
from zerver.lib.export import MESSAGE_BATCH_CHUNK_SIZE
|
||||||
from zerver.lib.storage import static_path
|
from zerver.lib.storage import static_path
|
||||||
from zerver.lib.upload.base import resize_logo, sanitize_name
|
from zerver.lib.thumbnail import resize_logo
|
||||||
|
from zerver.lib.upload.base import sanitize_name
|
||||||
from zerver.models import (
|
from zerver.models import (
|
||||||
CustomProfileField,
|
CustomProfileField,
|
||||||
CustomProfileFieldValue,
|
CustomProfileFieldValue,
|
||||||
|
|
|
@ -9,8 +9,8 @@ from zerver.lib.avatar_hash import (
|
||||||
user_avatar_content_hash,
|
user_avatar_content_hash,
|
||||||
user_avatar_path_from_ids,
|
user_avatar_path_from_ids,
|
||||||
)
|
)
|
||||||
|
from zerver.lib.thumbnail import MEDIUM_AVATAR_SIZE
|
||||||
from zerver.lib.upload import get_avatar_url
|
from zerver.lib.upload import get_avatar_url
|
||||||
from zerver.lib.upload.base import MEDIUM_AVATAR_SIZE
|
|
||||||
from zerver.lib.url_encoding import append_url_query_string
|
from zerver.lib.url_encoding import append_url_query_string
|
||||||
from zerver.models import UserProfile
|
from zerver.models import UserProfile
|
||||||
|
|
||||||
|
|
|
@ -32,9 +32,10 @@ from zerver.lib.push_notifications import sends_notifications_directly
|
||||||
from zerver.lib.remote_server import maybe_enqueue_audit_log_upload
|
from zerver.lib.remote_server import maybe_enqueue_audit_log_upload
|
||||||
from zerver.lib.server_initialization import create_internal_realm, server_initialized
|
from zerver.lib.server_initialization import create_internal_realm, server_initialized
|
||||||
from zerver.lib.streams import render_stream_description
|
from zerver.lib.streams import render_stream_description
|
||||||
|
from zerver.lib.thumbnail import BadImageError
|
||||||
from zerver.lib.timestamp import datetime_to_timestamp
|
from zerver.lib.timestamp import datetime_to_timestamp
|
||||||
from zerver.lib.upload import upload_backend
|
from zerver.lib.upload import upload_backend
|
||||||
from zerver.lib.upload.base import BadImageError, sanitize_name
|
from zerver.lib.upload.base import sanitize_name
|
||||||
from zerver.lib.upload.s3 import get_bucket
|
from zerver.lib.upload.s3 import get_bucket
|
||||||
from zerver.lib.user_counts import realm_user_count_by_role
|
from zerver.lib.user_counts import realm_user_count_by_role
|
||||||
from zerver.lib.user_groups import create_system_user_groups_for_realm
|
from zerver.lib.user_groups import create_system_user_groups_for_realm
|
||||||
|
|
|
@ -1,8 +1,28 @@
|
||||||
|
import io
|
||||||
|
from typing import Optional, Tuple
|
||||||
from urllib.parse import urljoin
|
from urllib.parse import urljoin
|
||||||
|
|
||||||
from django.utils.http import url_has_allowed_host_and_scheme
|
from django.utils.http import url_has_allowed_host_and_scheme
|
||||||
|
from django.utils.translation import gettext as _
|
||||||
|
from PIL import GifImagePlugin, Image, ImageOps, PngImagePlugin
|
||||||
|
from PIL.Image import DecompressionBombError
|
||||||
|
|
||||||
from zerver.lib.camo import get_camo_url
|
from zerver.lib.camo import get_camo_url
|
||||||
|
from zerver.lib.exceptions import ErrorCode, JsonableError
|
||||||
|
|
||||||
|
DEFAULT_AVATAR_SIZE = 100
|
||||||
|
MEDIUM_AVATAR_SIZE = 500
|
||||||
|
DEFAULT_EMOJI_SIZE = 64
|
||||||
|
|
||||||
|
# These sizes were selected based on looking at the maximum common
|
||||||
|
# sizes in a library of animated custom emoji, balanced against the
|
||||||
|
# network cost of very large emoji images.
|
||||||
|
MAX_EMOJI_GIF_SIZE = 128
|
||||||
|
MAX_EMOJI_GIF_FILE_SIZE_BYTES = 128 * 1024 * 1024 # 128 kb
|
||||||
|
|
||||||
|
|
||||||
|
class BadImageError(JsonableError):
|
||||||
|
code = ErrorCode.BAD_IMAGE
|
||||||
|
|
||||||
|
|
||||||
def user_uploads_or_external(url: str) -> bool:
|
def user_uploads_or_external(url: str) -> bool:
|
||||||
|
@ -17,3 +37,124 @@ def generate_thumbnail_url(path: str, size: str = "0x0") -> str:
|
||||||
if url_has_allowed_host_and_scheme(path, allowed_hosts=None):
|
if url_has_allowed_host_and_scheme(path, allowed_hosts=None):
|
||||||
return path
|
return path
|
||||||
return get_camo_url(path)
|
return get_camo_url(path)
|
||||||
|
|
||||||
|
|
||||||
|
def resize_avatar(image_data: bytes, size: int = DEFAULT_AVATAR_SIZE) -> bytes:
|
||||||
|
try:
|
||||||
|
im = Image.open(io.BytesIO(image_data))
|
||||||
|
im = ImageOps.exif_transpose(im)
|
||||||
|
im = ImageOps.fit(im, (size, size), Image.Resampling.LANCZOS)
|
||||||
|
except OSError:
|
||||||
|
raise BadImageError(_("Could not decode image; did you upload an image file?"))
|
||||||
|
except DecompressionBombError:
|
||||||
|
raise BadImageError(_("Image size exceeds limit."))
|
||||||
|
out = io.BytesIO()
|
||||||
|
if im.mode == "CMYK":
|
||||||
|
im = im.convert("RGB")
|
||||||
|
im.save(out, format="png")
|
||||||
|
return out.getvalue()
|
||||||
|
|
||||||
|
|
||||||
|
def resize_logo(image_data: bytes) -> bytes:
|
||||||
|
try:
|
||||||
|
im = Image.open(io.BytesIO(image_data))
|
||||||
|
im = ImageOps.exif_transpose(im)
|
||||||
|
im.thumbnail((8 * DEFAULT_AVATAR_SIZE, DEFAULT_AVATAR_SIZE), Image.Resampling.LANCZOS)
|
||||||
|
except OSError:
|
||||||
|
raise BadImageError(_("Could not decode image; did you upload an image file?"))
|
||||||
|
except DecompressionBombError:
|
||||||
|
raise BadImageError(_("Image size exceeds limit."))
|
||||||
|
out = io.BytesIO()
|
||||||
|
if im.mode == "CMYK":
|
||||||
|
im = im.convert("RGB")
|
||||||
|
im.save(out, format="png")
|
||||||
|
return out.getvalue()
|
||||||
|
|
||||||
|
|
||||||
|
def resize_animated(im: Image.Image, size: int = DEFAULT_EMOJI_SIZE) -> bytes:
|
||||||
|
assert im.n_frames > 1
|
||||||
|
frames = []
|
||||||
|
duration_info = []
|
||||||
|
disposals = []
|
||||||
|
# If 'loop' info is not set then loop for infinite number of times.
|
||||||
|
loop = im.info.get("loop", 0)
|
||||||
|
for frame_num in range(im.n_frames):
|
||||||
|
im.seek(frame_num)
|
||||||
|
new_frame = im.copy()
|
||||||
|
new_frame.paste(im, (0, 0), im.convert("RGBA"))
|
||||||
|
new_frame = ImageOps.pad(new_frame, (size, size), Image.Resampling.LANCZOS)
|
||||||
|
frames.append(new_frame)
|
||||||
|
if im.info.get("duration") is None: # nocoverage
|
||||||
|
raise BadImageError(_("Corrupt animated image."))
|
||||||
|
duration_info.append(im.info["duration"])
|
||||||
|
if isinstance(im, GifImagePlugin.GifImageFile):
|
||||||
|
disposals.append(
|
||||||
|
im.disposal_method # type: ignore[attr-defined] # private member missing from stubs
|
||||||
|
)
|
||||||
|
elif isinstance(im, PngImagePlugin.PngImageFile):
|
||||||
|
disposals.append(im.info.get("disposal", PngImagePlugin.Disposal.OP_NONE))
|
||||||
|
else: # nocoverage
|
||||||
|
raise BadImageError(_("Unknown animated image format."))
|
||||||
|
out = io.BytesIO()
|
||||||
|
frames[0].save(
|
||||||
|
out,
|
||||||
|
save_all=True,
|
||||||
|
optimize=False,
|
||||||
|
format=im.format,
|
||||||
|
append_images=frames[1:],
|
||||||
|
duration=duration_info,
|
||||||
|
disposal=disposals,
|
||||||
|
loop=loop,
|
||||||
|
)
|
||||||
|
|
||||||
|
return out.getvalue()
|
||||||
|
|
||||||
|
|
||||||
|
def resize_emoji(
|
||||||
|
image_data: bytes, size: int = DEFAULT_EMOJI_SIZE
|
||||||
|
) -> Tuple[bytes, bool, Optional[bytes]]:
|
||||||
|
# This function returns three values:
|
||||||
|
# 1) Emoji image data.
|
||||||
|
# 2) If emoji is gif i.e. animated.
|
||||||
|
# 3) If is animated then return still image data i.e. first frame of gif.
|
||||||
|
|
||||||
|
try:
|
||||||
|
im = Image.open(io.BytesIO(image_data))
|
||||||
|
image_format = im.format
|
||||||
|
if getattr(im, "n_frames", 1) > 1:
|
||||||
|
# There are a number of bugs in Pillow which cause results
|
||||||
|
# in resized images being broken. To work around this we
|
||||||
|
# only resize under certain conditions to minimize the
|
||||||
|
# chance of creating ugly images.
|
||||||
|
should_resize = (
|
||||||
|
im.size[0] != im.size[1] # not square
|
||||||
|
or im.size[0] > MAX_EMOJI_GIF_SIZE # dimensions too large
|
||||||
|
or len(image_data) > MAX_EMOJI_GIF_FILE_SIZE_BYTES # filesize too large
|
||||||
|
)
|
||||||
|
|
||||||
|
# Generate a still image from the first frame. Since
|
||||||
|
# we're converting the format to PNG anyway, we resize unconditionally.
|
||||||
|
still_image = im.copy()
|
||||||
|
still_image.seek(0)
|
||||||
|
still_image = ImageOps.exif_transpose(still_image)
|
||||||
|
still_image = ImageOps.fit(still_image, (size, size), Image.Resampling.LANCZOS)
|
||||||
|
out = io.BytesIO()
|
||||||
|
still_image.save(out, format="PNG")
|
||||||
|
still_image_data = out.getvalue()
|
||||||
|
|
||||||
|
if should_resize:
|
||||||
|
image_data = resize_animated(im, size)
|
||||||
|
|
||||||
|
return image_data, True, still_image_data
|
||||||
|
else:
|
||||||
|
# Note that this is essentially duplicated in the
|
||||||
|
# still_image code path, above.
|
||||||
|
im = ImageOps.exif_transpose(im)
|
||||||
|
im = ImageOps.fit(im, (size, size), Image.Resampling.LANCZOS)
|
||||||
|
out = io.BytesIO()
|
||||||
|
im.save(out, format=image_format)
|
||||||
|
return out.getvalue(), False, None
|
||||||
|
except OSError:
|
||||||
|
raise BadImageError(_("Could not decode image; did you upload an image file?"))
|
||||||
|
except DecompressionBombError:
|
||||||
|
raise BadImageError(_("Image size exceeds limit."))
|
||||||
|
|
|
@ -1,29 +1,12 @@
|
||||||
import io
|
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import unicodedata
|
import unicodedata
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import IO, Any, BinaryIO, Callable, Iterator, List, Optional, Tuple
|
from typing import IO, Any, BinaryIO, Callable, Iterator, List, Optional, Tuple
|
||||||
|
|
||||||
from django.utils.translation import gettext as _
|
|
||||||
from PIL import GifImagePlugin, Image, ImageOps, PngImagePlugin
|
|
||||||
from PIL.Image import DecompressionBombError
|
|
||||||
|
|
||||||
from zerver.lib.exceptions import ErrorCode, JsonableError
|
|
||||||
from zerver.models import Attachment, Realm, UserProfile
|
from zerver.models import Attachment, Realm, UserProfile
|
||||||
from zerver.models.users import is_cross_realm_bot_email
|
from zerver.models.users import is_cross_realm_bot_email
|
||||||
|
|
||||||
DEFAULT_AVATAR_SIZE = 100
|
|
||||||
MEDIUM_AVATAR_SIZE = 500
|
|
||||||
DEFAULT_EMOJI_SIZE = 64
|
|
||||||
|
|
||||||
# These sizes were selected based on looking at the maximum common
|
|
||||||
# sizes in a library of animated custom emoji, balanced against the
|
|
||||||
# network cost of very large emoji images.
|
|
||||||
MAX_EMOJI_GIF_SIZE = 128
|
|
||||||
MAX_EMOJI_GIF_FILE_SIZE_BYTES = 128 * 1024 * 1024 # 128 kb
|
|
||||||
|
|
||||||
|
|
||||||
INLINE_MIME_TYPES = [
|
INLINE_MIME_TYPES = [
|
||||||
"application/pdf",
|
"application/pdf",
|
||||||
"audio/aac",
|
"audio/aac",
|
||||||
|
@ -66,131 +49,6 @@ def sanitize_name(value: str) -> str:
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
class BadImageError(JsonableError):
|
|
||||||
code = ErrorCode.BAD_IMAGE
|
|
||||||
|
|
||||||
|
|
||||||
def resize_avatar(image_data: bytes, size: int = DEFAULT_AVATAR_SIZE) -> bytes:
|
|
||||||
try:
|
|
||||||
im = Image.open(io.BytesIO(image_data))
|
|
||||||
im = ImageOps.exif_transpose(im)
|
|
||||||
im = ImageOps.fit(im, (size, size), Image.Resampling.LANCZOS)
|
|
||||||
except OSError:
|
|
||||||
raise BadImageError(_("Could not decode image; did you upload an image file?"))
|
|
||||||
except DecompressionBombError:
|
|
||||||
raise BadImageError(_("Image size exceeds limit."))
|
|
||||||
out = io.BytesIO()
|
|
||||||
if im.mode == "CMYK":
|
|
||||||
im = im.convert("RGB")
|
|
||||||
im.save(out, format="png")
|
|
||||||
return out.getvalue()
|
|
||||||
|
|
||||||
|
|
||||||
def resize_logo(image_data: bytes) -> bytes:
|
|
||||||
try:
|
|
||||||
im = Image.open(io.BytesIO(image_data))
|
|
||||||
im = ImageOps.exif_transpose(im)
|
|
||||||
im.thumbnail((8 * DEFAULT_AVATAR_SIZE, DEFAULT_AVATAR_SIZE), Image.Resampling.LANCZOS)
|
|
||||||
except OSError:
|
|
||||||
raise BadImageError(_("Could not decode image; did you upload an image file?"))
|
|
||||||
except DecompressionBombError:
|
|
||||||
raise BadImageError(_("Image size exceeds limit."))
|
|
||||||
out = io.BytesIO()
|
|
||||||
if im.mode == "CMYK":
|
|
||||||
im = im.convert("RGB")
|
|
||||||
im.save(out, format="png")
|
|
||||||
return out.getvalue()
|
|
||||||
|
|
||||||
|
|
||||||
def resize_animated(im: Image.Image, size: int = DEFAULT_EMOJI_SIZE) -> bytes:
|
|
||||||
assert im.n_frames > 1
|
|
||||||
frames = []
|
|
||||||
duration_info = []
|
|
||||||
disposals = []
|
|
||||||
# If 'loop' info is not set then loop for infinite number of times.
|
|
||||||
loop = im.info.get("loop", 0)
|
|
||||||
for frame_num in range(im.n_frames):
|
|
||||||
im.seek(frame_num)
|
|
||||||
new_frame = im.copy()
|
|
||||||
new_frame.paste(im, (0, 0), im.convert("RGBA"))
|
|
||||||
new_frame = ImageOps.pad(new_frame, (size, size), Image.Resampling.LANCZOS)
|
|
||||||
frames.append(new_frame)
|
|
||||||
if im.info.get("duration") is None: # nocoverage
|
|
||||||
raise BadImageError(_("Corrupt animated image."))
|
|
||||||
duration_info.append(im.info["duration"])
|
|
||||||
if isinstance(im, GifImagePlugin.GifImageFile):
|
|
||||||
disposals.append(
|
|
||||||
im.disposal_method # type: ignore[attr-defined] # private member missing from stubs
|
|
||||||
)
|
|
||||||
elif isinstance(im, PngImagePlugin.PngImageFile):
|
|
||||||
disposals.append(im.info.get("disposal", PngImagePlugin.Disposal.OP_NONE))
|
|
||||||
else: # nocoverage
|
|
||||||
raise BadImageError(_("Unknown animated image format."))
|
|
||||||
out = io.BytesIO()
|
|
||||||
frames[0].save(
|
|
||||||
out,
|
|
||||||
save_all=True,
|
|
||||||
optimize=False,
|
|
||||||
format=im.format,
|
|
||||||
append_images=frames[1:],
|
|
||||||
duration=duration_info,
|
|
||||||
disposal=disposals,
|
|
||||||
loop=loop,
|
|
||||||
)
|
|
||||||
|
|
||||||
return out.getvalue()
|
|
||||||
|
|
||||||
|
|
||||||
def resize_emoji(
|
|
||||||
image_data: bytes, size: int = DEFAULT_EMOJI_SIZE
|
|
||||||
) -> Tuple[bytes, bool, Optional[bytes]]:
|
|
||||||
# This function returns three values:
|
|
||||||
# 1) Emoji image data.
|
|
||||||
# 2) If emoji is gif i.e. animated.
|
|
||||||
# 3) If is animated then return still image data i.e. first frame of gif.
|
|
||||||
|
|
||||||
try:
|
|
||||||
im = Image.open(io.BytesIO(image_data))
|
|
||||||
image_format = im.format
|
|
||||||
if getattr(im, "n_frames", 1) > 1:
|
|
||||||
# There are a number of bugs in Pillow which cause results
|
|
||||||
# in resized images being broken. To work around this we
|
|
||||||
# only resize under certain conditions to minimize the
|
|
||||||
# chance of creating ugly images.
|
|
||||||
should_resize = (
|
|
||||||
im.size[0] != im.size[1] # not square
|
|
||||||
or im.size[0] > MAX_EMOJI_GIF_SIZE # dimensions too large
|
|
||||||
or len(image_data) > MAX_EMOJI_GIF_FILE_SIZE_BYTES # filesize too large
|
|
||||||
)
|
|
||||||
|
|
||||||
# Generate a still image from the first frame. Since
|
|
||||||
# we're converting the format to PNG anyway, we resize unconditionally.
|
|
||||||
still_image = im.copy()
|
|
||||||
still_image.seek(0)
|
|
||||||
still_image = ImageOps.exif_transpose(still_image)
|
|
||||||
still_image = ImageOps.fit(still_image, (size, size), Image.Resampling.LANCZOS)
|
|
||||||
out = io.BytesIO()
|
|
||||||
still_image.save(out, format="PNG")
|
|
||||||
still_image_data = out.getvalue()
|
|
||||||
|
|
||||||
if should_resize:
|
|
||||||
image_data = resize_animated(im, size)
|
|
||||||
|
|
||||||
return image_data, True, still_image_data
|
|
||||||
else:
|
|
||||||
# Note that this is essentially duplicated in the
|
|
||||||
# still_image code path, above.
|
|
||||||
im = ImageOps.exif_transpose(im)
|
|
||||||
im = ImageOps.fit(im, (size, size), Image.Resampling.LANCZOS)
|
|
||||||
out = io.BytesIO()
|
|
||||||
im.save(out, format=image_format)
|
|
||||||
return out.getvalue(), False, None
|
|
||||||
except OSError:
|
|
||||||
raise BadImageError(_("Could not decode image; did you upload an image file?"))
|
|
||||||
except DecompressionBombError:
|
|
||||||
raise BadImageError(_("Image size exceeds limit."))
|
|
||||||
|
|
||||||
|
|
||||||
class ZulipUploadBackend:
|
class ZulipUploadBackend:
|
||||||
# Message attachment uploads
|
# Message attachment uploads
|
||||||
def get_public_upload_root_url(self) -> str:
|
def get_public_upload_root_url(self) -> str:
|
||||||
|
|
|
@ -10,16 +10,9 @@ from django.conf import settings
|
||||||
from typing_extensions import override
|
from typing_extensions import override
|
||||||
|
|
||||||
from zerver.lib.avatar_hash import user_avatar_path
|
from zerver.lib.avatar_hash import user_avatar_path
|
||||||
|
from zerver.lib.thumbnail import MEDIUM_AVATAR_SIZE, resize_avatar, resize_emoji, resize_logo
|
||||||
from zerver.lib.timestamp import timestamp_to_datetime
|
from zerver.lib.timestamp import timestamp_to_datetime
|
||||||
from zerver.lib.upload.base import (
|
from zerver.lib.upload.base import ZulipUploadBackend, create_attachment, sanitize_name
|
||||||
MEDIUM_AVATAR_SIZE,
|
|
||||||
ZulipUploadBackend,
|
|
||||||
create_attachment,
|
|
||||||
resize_avatar,
|
|
||||||
resize_emoji,
|
|
||||||
resize_logo,
|
|
||||||
sanitize_name,
|
|
||||||
)
|
|
||||||
from zerver.lib.utils import assert_is_not_none
|
from zerver.lib.utils import assert_is_not_none
|
||||||
from zerver.models import Realm, RealmEmoji, UserProfile
|
from zerver.models import Realm, RealmEmoji, UserProfile
|
||||||
|
|
||||||
|
|
|
@ -14,14 +14,11 @@ from typing_extensions import override
|
||||||
|
|
||||||
from zerver.lib.avatar_hash import user_avatar_path
|
from zerver.lib.avatar_hash import user_avatar_path
|
||||||
from zerver.lib.mime_types import guess_type
|
from zerver.lib.mime_types import guess_type
|
||||||
|
from zerver.lib.thumbnail import MEDIUM_AVATAR_SIZE, resize_avatar, resize_emoji, resize_logo
|
||||||
from zerver.lib.upload.base import (
|
from zerver.lib.upload.base import (
|
||||||
INLINE_MIME_TYPES,
|
INLINE_MIME_TYPES,
|
||||||
MEDIUM_AVATAR_SIZE,
|
|
||||||
ZulipUploadBackend,
|
ZulipUploadBackend,
|
||||||
create_attachment,
|
create_attachment,
|
||||||
resize_avatar,
|
|
||||||
resize_emoji,
|
|
||||||
resize_logo,
|
|
||||||
sanitize_name,
|
sanitize_name,
|
||||||
)
|
)
|
||||||
from zerver.models import Realm, RealmEmoji, UserProfile
|
from zerver.models import Realm, RealmEmoji, UserProfile
|
||||||
|
|
|
@ -92,8 +92,8 @@ from zerver.lib.test_helpers import (
|
||||||
read_test_image_file,
|
read_test_image_file,
|
||||||
use_s3_backend,
|
use_s3_backend,
|
||||||
)
|
)
|
||||||
|
from zerver.lib.thumbnail import DEFAULT_AVATAR_SIZE, MEDIUM_AVATAR_SIZE, resize_avatar
|
||||||
from zerver.lib.types import Validator
|
from zerver.lib.types import Validator
|
||||||
from zerver.lib.upload.base import DEFAULT_AVATAR_SIZE, MEDIUM_AVATAR_SIZE, resize_avatar
|
|
||||||
from zerver.lib.user_groups import is_user_in_group
|
from zerver.lib.user_groups import is_user_in_group
|
||||||
from zerver.lib.users import get_all_api_keys, get_api_key, get_users_for_api
|
from zerver.lib.users import get_all_api_keys, get_api_key, get_users_for_api
|
||||||
from zerver.lib.utils import assert_is_not_none
|
from zerver.lib.utils import assert_is_not_none
|
||||||
|
|
|
@ -8,7 +8,7 @@ from zerver.actions.users import do_change_user_role
|
||||||
from zerver.lib.exceptions import JsonableError
|
from zerver.lib.exceptions import JsonableError
|
||||||
from zerver.lib.test_classes import ZulipTestCase
|
from zerver.lib.test_classes import ZulipTestCase
|
||||||
from zerver.lib.test_helpers import get_test_image_file
|
from zerver.lib.test_helpers import get_test_image_file
|
||||||
from zerver.lib.upload.base import BadImageError
|
from zerver.lib.thumbnail import BadImageError
|
||||||
from zerver.models import Realm, RealmEmoji, UserProfile
|
from zerver.models import Realm, RealmEmoji, UserProfile
|
||||||
from zerver.models.realms import CommonPolicyEnum, get_realm
|
from zerver.models.realms import CommonPolicyEnum, get_realm
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@ from zerver.lib.test_helpers import (
|
||||||
get_test_image_file,
|
get_test_image_file,
|
||||||
read_test_image_file,
|
read_test_image_file,
|
||||||
)
|
)
|
||||||
|
from zerver.lib.thumbnail import resize_emoji
|
||||||
from zerver.lib.transfer import (
|
from zerver.lib.transfer import (
|
||||||
transfer_avatars_to_s3,
|
transfer_avatars_to_s3,
|
||||||
transfer_emoji_to_s3,
|
transfer_emoji_to_s3,
|
||||||
|
@ -20,7 +21,6 @@ from zerver.lib.transfer import (
|
||||||
transfer_uploads_to_s3,
|
transfer_uploads_to_s3,
|
||||||
)
|
)
|
||||||
from zerver.lib.upload import upload_message_attachment
|
from zerver.lib.upload import upload_message_attachment
|
||||||
from zerver.lib.upload.base import resize_emoji
|
|
||||||
from zerver.models import Attachment, RealmEmoji
|
from zerver.models import Attachment, RealmEmoji
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -37,8 +37,9 @@ from zerver.lib.test_helpers import (
|
||||||
ratelimit_rule,
|
ratelimit_rule,
|
||||||
read_test_image_file,
|
read_test_image_file,
|
||||||
)
|
)
|
||||||
|
from zerver.lib.thumbnail import BadImageError, resize_emoji
|
||||||
from zerver.lib.upload import upload_message_attachment
|
from zerver.lib.upload import upload_message_attachment
|
||||||
from zerver.lib.upload.base import BadImageError, ZulipUploadBackend, resize_emoji, sanitize_name
|
from zerver.lib.upload.base import ZulipUploadBackend, sanitize_name
|
||||||
from zerver.lib.upload.local import LocalUploadBackend
|
from zerver.lib.upload.local import LocalUploadBackend
|
||||||
from zerver.lib.upload.s3 import S3UploadBackend
|
from zerver.lib.upload.s3 import S3UploadBackend
|
||||||
from zerver.lib.users import get_api_key
|
from zerver.lib.users import get_api_key
|
||||||
|
@ -1422,15 +1423,15 @@ class EmojiTest(UploadSerializeMixin, ZulipTestCase):
|
||||||
animated_large_img_data = read_test_image_file(f"animated_large_img.{img_format}")
|
animated_large_img_data = read_test_image_file(f"animated_large_img.{img_format}")
|
||||||
|
|
||||||
# Test an image larger than max is resized
|
# Test an image larger than max is resized
|
||||||
with patch("zerver.lib.upload.base.MAX_EMOJI_GIF_SIZE", 128):
|
with patch("zerver.lib.thumbnail.MAX_EMOJI_GIF_SIZE", 128):
|
||||||
test_resize()
|
test_resize()
|
||||||
|
|
||||||
# Test an image file larger than max is resized
|
# Test an image file larger than max is resized
|
||||||
with patch("zerver.lib.upload.base.MAX_EMOJI_GIF_FILE_SIZE_BYTES", 3 * 1024 * 1024):
|
with patch("zerver.lib.thumbnail.MAX_EMOJI_GIF_FILE_SIZE_BYTES", 3 * 1024 * 1024):
|
||||||
test_resize()
|
test_resize()
|
||||||
|
|
||||||
# Test an image smaller than max and smaller than file size max is not resized
|
# Test an image smaller than max and smaller than file size max is not resized
|
||||||
with patch("zerver.lib.upload.base.MAX_EMOJI_GIF_SIZE", 512):
|
with patch("zerver.lib.thumbnail.MAX_EMOJI_GIF_SIZE", 512):
|
||||||
test_resize(size=256)
|
test_resize(size=256)
|
||||||
|
|
||||||
# Test a non-animated GIF image which does need to be resized
|
# Test a non-animated GIF image which does need to be resized
|
||||||
|
|
|
@ -10,6 +10,7 @@ import zerver.lib.upload
|
||||||
from zerver.lib.avatar_hash import user_avatar_path
|
from zerver.lib.avatar_hash import user_avatar_path
|
||||||
from zerver.lib.test_classes import UploadSerializeMixin, ZulipTestCase
|
from zerver.lib.test_classes import UploadSerializeMixin, ZulipTestCase
|
||||||
from zerver.lib.test_helpers import get_test_image_file, read_test_image_file
|
from zerver.lib.test_helpers import get_test_image_file, read_test_image_file
|
||||||
|
from zerver.lib.thumbnail import DEFAULT_EMOJI_SIZE, MEDIUM_AVATAR_SIZE, resize_avatar
|
||||||
from zerver.lib.upload import (
|
from zerver.lib.upload import (
|
||||||
all_message_attachments,
|
all_message_attachments,
|
||||||
delete_export_tarball,
|
delete_export_tarball,
|
||||||
|
@ -20,7 +21,6 @@ from zerver.lib.upload import (
|
||||||
upload_export_tarball,
|
upload_export_tarball,
|
||||||
upload_message_attachment,
|
upload_message_attachment,
|
||||||
)
|
)
|
||||||
from zerver.lib.upload.base import DEFAULT_EMOJI_SIZE, MEDIUM_AVATAR_SIZE, resize_avatar
|
|
||||||
from zerver.lib.upload.local import write_local_file
|
from zerver.lib.upload.local import write_local_file
|
||||||
from zerver.models import Attachment, RealmEmoji
|
from zerver.models import Attachment, RealmEmoji
|
||||||
from zerver.models.realms import get_realm
|
from zerver.models.realms import get_realm
|
||||||
|
|
|
@ -20,6 +20,12 @@ from zerver.lib.test_helpers import (
|
||||||
read_test_image_file,
|
read_test_image_file,
|
||||||
use_s3_backend,
|
use_s3_backend,
|
||||||
)
|
)
|
||||||
|
from zerver.lib.thumbnail import (
|
||||||
|
DEFAULT_AVATAR_SIZE,
|
||||||
|
DEFAULT_EMOJI_SIZE,
|
||||||
|
MEDIUM_AVATAR_SIZE,
|
||||||
|
resize_avatar,
|
||||||
|
)
|
||||||
from zerver.lib.upload import (
|
from zerver.lib.upload import (
|
||||||
all_message_attachments,
|
all_message_attachments,
|
||||||
delete_export_tarball,
|
delete_export_tarball,
|
||||||
|
@ -29,12 +35,6 @@ from zerver.lib.upload import (
|
||||||
upload_export_tarball,
|
upload_export_tarball,
|
||||||
upload_message_attachment,
|
upload_message_attachment,
|
||||||
)
|
)
|
||||||
from zerver.lib.upload.base import (
|
|
||||||
DEFAULT_AVATAR_SIZE,
|
|
||||||
DEFAULT_EMOJI_SIZE,
|
|
||||||
MEDIUM_AVATAR_SIZE,
|
|
||||||
resize_avatar,
|
|
||||||
)
|
|
||||||
from zerver.lib.upload.s3 import S3UploadBackend
|
from zerver.lib.upload.s3 import S3UploadBackend
|
||||||
from zerver.models import Attachment, RealmEmoji, UserProfile
|
from zerver.models import Attachment, RealmEmoji, UserProfile
|
||||||
from zerver.models.realms import get_realm
|
from zerver.models.realms import get_realm
|
||||||
|
|
Loading…
Reference in New Issue