@@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-08-10 11:02+0300\n" +"POT-Creation-Date: 2026-08-13 17:52+0300\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -766,7 +766,8 @@ msgstr "Промпт слишком длинный" msgid "" "Service is currently unavailable due to high demand. Please try again later" msgstr "" -"Сервис временно недоступен из-за высокой нагрузки. Пожалуйста, попробуйте позже" +"Сервис временно недоступен из-за высокой нагрузки. Пожалуйста, попробуйте " +"позже" #: ml_model/exceptions.py:172 msgid "Service is temporarily unavailable. Please try again later" @@ -1097,17 +1098,21 @@ msgstr "Инструкции Моделей" msgid "no model by this id" msgstr "Не найдено моделей по этому ID" -#: ml_model/services/FileService.py:110 +#: ml_model/services/FileService.py:123 #: tools/public_api/views/providers/openai_compatible.py:208 msgid "Voice not found." msgstr "Голос не найден." +#: ml_model/services/FileService.py:198 +msgid "Image is too wide or tall" +msgstr "Изображение слишком широкое или высокое" + #: ml_model/services/chatgpt.py:245 msgid "Image is ready" msgstr "Изображение готово" #: ml_model/services/chatgpt.py:361 ml_model/services/claude.py:277 -#: ml_model/services/grok.py:191 +#: ml_model/services/grok.py:192 msgid "File analysis" msgstr "Анализ файлов" @@ -1,6 +1,7 @@ import re import subprocess import zipfile +from collections.abc import Iterable from functools import wraps from uuid import UUID @@ -144,25 +145,15 @@ class ImageFileProcessingService: 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 + def get_kind(self, file_bytes: bytes) -> filetype.Type | None: + return filetype.guess(file_bytes) @__reset_image - def get_dimensions(self, max_pixels: int) -> tuple[int, int]: + def get_dimensions(self) -> tuple[int | None, int | None]: 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) + return get_image_dimensions(self.image) + except Image.DecompressionBombError as exc: + raise ImageTooLargeError(Image.MAX_IMAGE_PIXELS or 0) from exc def get_normalized_image(self, file_bytes: bytes) -> BytesIO: normalized_image = BytesIO(file_bytes) @@ -173,3 +164,35 @@ class ImageFileProcessingService: img.close() normalized_image.seek(0) return normalized_image + + def validate_kind(self, kind: filetype.Type | None) -> filetype.Type: + if not kind: + raise CorruptedFileError + return kind + + def validate_extension( + self, + kind: filetype.Type, + allowed_extensions: Iterable[str] | None = None, + ) -> None: + extensions = allowed_extensions or self.ALLOWED_EXTENSIONS + if kind.extension.upper() not in extensions: + raise FileExtensionNotSupported(extensions) + + def validate_dimensions(self, width: int | None, height: int | None, max_pixels: int) -> None: + if not (width and height): + raise CorruptedFileError + if width * height > max_pixels: + raise ImageTooLargeError(max_pixels) + + def validate_aspect_ratio( + self, + width: int | None, + height: int | None, + min_ratio: float, + max_ratio: float, + ) -> None: + if not (width and height): + raise CorruptedFileError + if not (min_ratio <= width / height <= max_ratio): + raise InvalidParameterError(_('Image is too wide or tall')) @@ -1,17 +1,15 @@ -import base64 import time from datetime import timedelta from decimal import Decimal from io import BytesIO from typing import Any -import filetype import requests from django.core.files import File - from messages.models import Message -from ml_model.exceptions import RequestBlocked, GenerationException, FileNotProvided +from ml_model.exceptions import FileNotProvided +from ml_model.services.FileService import ImageFileProcessingService from ml_model.services.base import SimpleService from ml_model.tasks import replicate_run @@ -47,17 +45,22 @@ class Kling(SimpleService): def make(self, input_message: Message, save: bool = True) -> list[Message]: mode = input_message.info.pop('mode', 'standard') duration = input_message.info.get('duration', 5) - if (balance := PaymentPlanSelector(self.store.user).get_current_balance()) < (cost := self.TOKENS_COST[mode] * duration): + if (balance := PaymentPlanSelector(self.store.user).get_current_balance()) < ( + cost := self.TOKENS_COST[mode] * duration + ): raise InsufficientBalance(balance, cost) if not input_message.file: raise FileNotProvided('Image') - callback_data = dict({'prompt': self.translate_prompt(input_message.content), 'mode': mode, **input_message.info}) - kind = filetype.guess(input_message.file.read(20)) - mime = kind.mime if kind else 'application/octet-stream' - input_message.file.seek(0) - image = f'data:{mime};base64,{base64.b64encode(input_message.file.read()).decode("utf-8")}' - input_message.file.close() - callback_data.update({'start_image': image}) + callback_data = dict( + {'prompt': self.translate_prompt(input_message.content), 'mode': mode, **input_message.info} + ) + + processor = ImageFileProcessingService(input_message.file) + width, height = processor.get_dimensions() + processor.validate_dimensions(width, height, max_pixels=36_000_000) + processor.validate_aspect_ratio(width, height, min_ratio=0.40, max_ratio=2.50) + processor.image.close() + callback_data.update({'start_image': input_message.file.url}) start_time = time.time() video = replicate_run('kwaivgi/kling-v2.1', callback_data) process_time = timedelta(seconds=(time.time() - start_time)) @@ -96,8 +96,10 @@ class Seedream(SimpleService): 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) + kind = image_processor.validate_kind(image_processor.get_kind(file_bytes)) + image_processor.validate_extension(kind) + width, height = image_processor.get_dimensions() + image_processor.validate_dimensions(width, height, max_pixels=36_000_000) image_processor.image.close() callback_data.update({'image': image.url})