@@ -31,6 +31,7 @@ from ml_model.services.lyria import Lyria from ml_model.services.midjourney import Midjourney from ml_model.services.minimaxvideo import Minimaxvideo from ml_model.services.minimaxmusic import Minimaxmusic +from ml_model.services.minimaxmusic_lite import Minimaxmusic_Lite from ml_model.services.mistral import Mistral from ml_model.services.musicgen import Musicgen from ml_model.services.nanobanana import Nanobanana @@ -55,4 +56,5 @@ from ml_model.services.upscaleai import Upscaleai from ml_model.services.veo import Veo from ml_model.services.vicuna import Vicuna from ml_model.services.wan import Wan +from ml_model.services.wan_lite import Wan_Lite from ml_model.services.whisper import Whisper @@ -16,11 +16,10 @@ from ml_model.tasks import replicate_run from payments.exceptions.insufficient_balance import InsufficientBalance from payments.selectors.payment_plan_selector import PaymentPlanSelector -from tools.media.models import Preset class Minimaxmusic(SimpleService): - TOKENS_COST = Decimal('10.5') + TOKENS_COST = Decimal('9') @classmethod def predict_price(cls, content: str, file_exists: bool, info: dict[str, Any]) -> Decimal | None: @@ -41,22 +40,19 @@ class Minimaxmusic(SimpleService): return [msg] def make(self, input_message: Message, save: bool = True) -> list[Message]: - speaker = input_message.info.get('speaker', 'russian_1').lower() - instrumental = input_message.info.get('instrumental', 'classical').lower() if (balance := PaymentPlanSelector(self.store.user).get_current_balance()) < self.TOKENS_COST: raise InsufficientBalance(balance, self.TOKENS_COST) - callback_data = {'lyrics': input_message.content, **input_message.info} - if speaker: - file_url = Preset.objects.get(slug=speaker).file.url - callback_data.update({'voice_file': file_url}) - if input_message.file: - callback_data.update({'instrumental_file': input_message.file.url}) - else: - file_url = Preset.objects.get(slug=instrumental).file.url - callback_data.update({'instrumental_file': file_url}) + callback_data = { + 'prompt': f'High-quality professional music production, rich instrumentation, detailed arrangement, ' + f'studio-quality mixing and mastering, wide stereo imaging, clear vocals, emotional ' + f'performance, dynamic progression, polished sound design, immersive atmosphere ' + f'for {input_message.info.pop("style")}', + 'lyrics': input_message.content, + **input_message.info, + } start_time = time.time() try: - audio = replicate_run(f'minimax/music-01', callback_data) + audio = replicate_run('minimax/music-1.5', callback_data) except ModelError as exc: if 'lyrics is too long' in str(exc): raise InvalidParameterError(_('Lyrics is too long')) @@ -0,0 +1,69 @@ +import time +from datetime import timedelta +from decimal import Decimal +from io import BytesIO +from typing import Any + +import requests +from django.core.files import File +from django.utils.translation import gettext as _ +from replicate.exceptions import ModelError + +from messages.models import Message +from ml_model.exceptions import RequestBlocked, InvalidParameterError, GenerationException +from ml_model.services.base import SimpleService +from ml_model.tasks import replicate_run + +from payments.exceptions.insufficient_balance import InsufficientBalance +from payments.selectors.payment_plan_selector import PaymentPlanSelector +from tools.media.models import Preset + + +class Minimaxmusic_Lite(SimpleService): + TOKENS_COST = Decimal('7') + + @classmethod + def predict_price(cls, content: str, file_exists: bool, info: dict[str, Any]) -> Decimal | None: + return cls.TOKENS_COST + + def calculate_price(self) -> Decimal: + return self.TOKENS_COST + + def save_results(self, content: str, t: timedelta, video: str, save: bool = True) -> list[Message]: + msg = Message( + content=content, + content_object=self.store, + elapsed_time=t, + file=File(BytesIO(requests.get(video).content), '.mp3'), + ) + if save: + return Message.objects.bulk_create([msg]) + return [msg] + + def make(self, input_message: Message, save: bool = True) -> list[Message]: + speaker = input_message.info.get('speaker', 'russian_1').lower() + instrumental = input_message.info.get('instrumental', 'classical').lower() + if (balance := PaymentPlanSelector(self.store.user).get_current_balance()) < self.TOKENS_COST: + raise InsufficientBalance(balance, self.TOKENS_COST) + callback_data = {'lyrics': input_message.content, **input_message.info} + if speaker: + file_url = Preset.objects.get(slug=speaker).file.url + callback_data.update({'voice_file': file_url}) + if input_message.file: + callback_data.update({'instrumental_file': input_message.file.url}) + else: + file_url = Preset.objects.get(slug=instrumental).file.url + callback_data.update({'instrumental_file': file_url}) + start_time = time.time() + try: + audio = replicate_run('minimax/music-01', callback_data) + except ModelError as exc: + if 'lyrics is too long' in str(exc): + raise InvalidParameterError(_('Lyrics is too long')) + if any(error in str(exc) for error in ('E005', 'E006', 'sexual')): + raise RequestBlocked + raise GenerationException from exc + process_time = timedelta(seconds=(time.time() - start_time)) + self.handle_invoice(input_message.content_object.model) + msgs = self.save_results(input_message.content, process_time, audio, save) + return msgs @@ -1,13 +1,16 @@ +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 FileNotProvided from ml_model.services.base import SimpleService from ml_model.tasks import replicate_run @@ -16,23 +19,16 @@ from payments.selectors.payment_plan_selector import PaymentPlanSelector class Wan(SimpleService): - """ - Wan Service - contains abstract method make, which makes a generation - """ - - TOKENS_COST = { - '480p': Decimal('25'), - '720p': Decimal('50') - } + TOKENS_COST = {'720p': Decimal('25'), '1080p': Decimal('37.5')} @classmethod def predict_price(cls, content: str, file_exists: bool, info: dict[str, Any]) -> Decimal | None: resolution = info['resolution'] - return cls.TOKENS_COST[resolution].quantize(Decimal('0.1'), rounding='ROUND_UP') + duration = info['duration'] + return (cls.TOKENS_COST[resolution] * duration).quantize(Decimal('0.1'), rounding='ROUND_UP') - def calculate_price(self, resolution: str) -> Decimal: - return self.TOKENS_COST[resolution].quantize(Decimal('0.1'), rounding='ROUND_UP') + def calculate_price(self, resolution: str, duration: int) -> Decimal: + return (self.TOKENS_COST[resolution] * duration).quantize(Decimal('0.1'), rounding='ROUND_UP') def save_results(self, content: str, t: timedelta, video: str, save: bool = True) -> list[Message]: msg = Message( @@ -47,12 +43,30 @@ class Wan(SimpleService): def make(self, input_message: Message, save: bool = True) -> list[Message]: resolution = input_message.info.pop('resolution', '720p') - if (balance := PaymentPlanSelector(self.store.user).get_current_balance()) < self.TOKENS_COST[resolution]: - raise InsufficientBalance(balance, self.TOKENS_COST[resolution]) - callback_data = dict({'prompt': self.translate_prompt(input_message.content), 'resolution': resolution, **input_message.info}) + duration = input_message.info.pop('duration', 5) + if (balance := PaymentPlanSelector(self.store.user).get_current_balance()) < ( + cost := self.TOKENS_COST[resolution] * duration + ): + raise InsufficientBalance(balance, cost) + if not input_message.file: + raise FileNotProvided('Image') + 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 = dict( + { + 'prompt': self.translate_prompt(input_message.content), + 'image': image, + 'resolution': resolution, + 'duration': duration, + **input_message.info, + } + ) start_time = time.time() - video = replicate_run('wan-video/wan-2.2-t2v-fast', callback_data) + video = replicate_run('wan-video/wan2.6-i2v-flash', callback_data) process_time = timedelta(seconds=(time.time() - start_time)) - self.handle_invoice(input_message.content_object.model, resolution) + self.handle_invoice(input_message.content_object.model, resolution=resolution, duration=duration) msgs = self.save_results(input_message.content, process_time, video, save) return msgs @@ -0,0 +1,65 @@ +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.services.base import SimpleService +from ml_model.tasks import replicate_run + +from payments.exceptions.insufficient_balance import InsufficientBalance +from payments.selectors.payment_plan_selector import PaymentPlanSelector + + +class Wan_Lite(SimpleService): + TOKENS_COST = {'480p': Decimal('2.5'), '720p': Decimal('5')} + + @classmethod + def predict_price(cls, content: str, file_exists: bool, info: dict[str, Any]) -> Decimal | None: + resolution = info['resolution'] + return cls.TOKENS_COST[resolution].quantize(Decimal('0.1'), rounding='ROUND_UP') + + def calculate_price(self, resolution: str) -> Decimal: + return self.TOKENS_COST[resolution].quantize(Decimal('0.1'), rounding='ROUND_UP') + + def save_results(self, content: str, t: timedelta, video: str, save: bool = True) -> list[Message]: + msg = Message( + content=content, + content_object=self.store, + elapsed_time=t, + file=File(BytesIO(requests.get(video).content), '.mp4'), + ) + if save: + return Message.objects.bulk_create([msg]) + return [msg] + + def make(self, input_message: Message, save: bool = True) -> list[Message]: + resolution = input_message.info.pop('resolution', '720p') + if (balance := PaymentPlanSelector(self.store.user).get_current_balance()) < self.TOKENS_COST[resolution]: + raise InsufficientBalance(balance, self.TOKENS_COST[resolution]) + callback_data = dict( + { + 'prompt': self.translate_prompt(input_message.content), + 'resolution': resolution, + **input_message.info, + } + ) + if input_message.file: + 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({'image': image}) + start_time = time.time() + video = replicate_run('wan-video/wan-2.2-5b-fast', callback_data) + process_time = timedelta(seconds=(time.time() - start_time)) + self.handle_invoice(input_message.content_object.model, resolution=resolution) + msgs = self.save_results(input_message.content, process_time, video, save) + return msgs