@@ -1,21 +1,33 @@ import re import subprocess import zipfile +from functools import wraps from uuid import UUID import docx2txt +import filetype import fitz import openpyxl from io import BytesIO +from PIL import Image +from django.core.files.images import get_image_dimensions from django.db.models.fields.files import FieldFile from django.utils.translation import gettext as _ from authentication.models import CustomUserModel -from ml_model.exceptions import InvalidParameterError, UnrecognizedFileError +from ml_model.exceptions import ( + CorruptedFileError, + FileExtensionNotSupported, + ImageTooLargeError, + InvalidParameterError, + UnrecognizedFileError, +) from tools.media.models import Voice, Preset, PresetKind +# FIXME: Переработать сервисы по работе с файлами. Возможно, прибегнуть к использованию миксинов + class FileProcessingService: @classmethod @@ -110,3 +122,54 @@ class FileProcessingService: raise InvalidParameterError(_('Voice not found.')) from exc return voice.file + +class ImageFileProcessingService: + ALLOWED_EXTENSIONS = ['PNG', 'JPG', 'JPEG', 'WEBP'] + + def __init__(self, image: FieldFile) -> None: + self.image = image + + @staticmethod + def __reset_image(func): + @wraps(func) + def wrapper(self, *args, **kwargs): + try: + return func(self, *args, **kwargs) + finally: + self.image.seek(0) + + return wrapper + + @__reset_image + def get_bytes(self, size: int | None = None) -> bytes: + return self.image.read(size) + + def get_kind(self, file_bytes: bytes): + kind = filetype.guess(file_bytes) + if not kind: + raise CorruptedFileError + if kind.extension.upper() not in self.ALLOWED_EXTENSIONS: + raise FileExtensionNotSupported(self.ALLOWED_EXTENSIONS) + return kind + + @__reset_image + def get_dimensions(self, max_pixels: int) -> tuple[int, int]: + try: + w, h = get_image_dimensions(self.image) + if not (w and h): + raise CorruptedFileError + if w * h > max_pixels: + raise ImageTooLargeError(max_pixels) + return w, h + except Image.DecompressionBombError: + raise ImageTooLargeError(max_pixels) + + def get_normalized_image(self, file_bytes: bytes) -> BytesIO: + normalized_image = BytesIO(file_bytes) + with Image.open(normalized_image) as source_image: + img = source_image.convert('RGBA') + normalized_image = BytesIO() + img.save(normalized_image, format='PNG') + img.close() + normalized_image.seek(0) + return normalized_image @@ -7,12 +7,12 @@ from typing import Any import requests from django.utils.translation import gettext as _ from django.core.files import File -from replicate.exceptions import ModelError from messages.models import Message from ml_model.adapters.bytedance_model_ark import BytedanceContentType from ml_model.exceptions import InvalidParameterError from ml_model.exceptions import ModelVersionNotAvailable +from ml_model.services.FileService import ImageFileProcessingService from ml_model.services.base import SimpleService from ml_model.tasks import bytedance_model_ark_run from payments.exceptions.insufficient_balance import InsufficientBalance @@ -94,6 +94,11 @@ class Seedream(SimpleService): **input_message.info, } if image := input_message.file: + image_processor = ImageFileProcessingService(image) + file_bytes = image_processor.get_bytes(20) + image_processor.get_kind(file_bytes) + image_processor.get_dimensions(max_pixels=36000000) + image_processor.image.close() callback_data.update({'image': image.url}) images = bytedance_model_ark_run(