diff --git a/app/client_bot/routers/content.py b/app/client_bot/routers/content.py
index 44b7e50..3e6162d 100644
--- a/app/client_bot/routers/content.py
+++ b/app/client_bot/routers/content.py
@@ -139,6 +139,9 @@ async def t_inline_query_node_content(query: types.InlineQuery, memory=None, use
audio_url=decrypted_content.web_url + '?seconds_limit=30',
title=title,
performer=performer,
+ caption=user.translated('p_playerContext_preview'),
+ # audio_duration=
+ parse_mode='html',
reply_markup=get_inline_keyboard([
[{
'text': user.translated('shareTrack_button'),
diff --git a/app/core/content/audio.py b/app/core/content/audio.py
new file mode 100644
index 0000000..08d1ffc
--- /dev/null
+++ b/app/core/content/audio.py
@@ -0,0 +1,71 @@
+import os
+
+from pydub import AudioSegment
+
+from app.core._config import UPLOADS_DIR
+from app.core.logger import make_log
+import traceback
+
+
+class AudioContentMixin:
+ def as_audio_segment(self) -> AudioSegment:
+ audio = None
+ open_log = f"StoredContent.as_audio_segment({self.id}): " + '\n'
+ file_ext = self.filename.split('.')[-1]
+ if not audio:
+ try:
+ if file_ext == 'mp3':
+ audio = AudioSegment.from_mp3(self.filepath)
+ elif file_ext == 'wav':
+ audio = AudioSegment.from_wav(self.filepath)
+ elif file_ext == 'ogg':
+ audio = AudioSegment.from_ogg(self.filepath)
+ elif file_ext == 'flv':
+ audio = AudioSegment.from_flv(self.filepath)
+ except BaseException as e:
+ open_log += f"Error loading audio from file (using encoding): {e}" + '\n' + traceback.format_exc()
+
+ if not audio:
+ try:
+ audio = AudioSegment.from_file(self.filepath)
+ except BaseException as e:
+ open_log += f"Error loading audio from file: {e}" + '\n' + traceback.format_exc()
+
+ if not audio:
+ try:
+ audio = AudioSegment(self.file_bin)
+ except BaseException as e:
+ open_log += f"Error loading audio from binary: {e}" + '\n' + traceback.format_exc()
+
+ make_log("Storage", open_log, level="debug")
+ assert audio, "Can't load audio as AudioSegment"
+ return audio
+
+ def as_mp3(self, seconds_limit=None):
+ tempfile_path = os.path.join(UPLOADS_DIR, f"temporary_{self.hash}.mp3")
+ audio = self.as_audio_segment()
+ if seconds_limit:
+ seconds_limit = int(seconds_limit)
+ tempfile_path += f"_f{seconds_limit}"
+
+ if not os.path.exists(tempfile_path):
+ try:
+ cover_content = self.__class__.from_cid(self.meta.get('cover_cid'))
+ cover_tempfile_path = cover_content.as_jpeg()
+ except BaseException as e:
+ make_log("Storage", f"Error getting cover content: {e}", level="debug")
+ cover_content = None
+ cover_tempfile_path = None
+
+ try:
+ audio = audio[:seconds_limit * 1000] if seconds_limit else audio
+ audio.export(tempfile_path, format="mp3", cover=cover_tempfile_path)
+ except BaseException as e:
+ make_log("Storage", f"Error converting audio: {e}" + '\n' + traceback.format_exc(), level="error")
+
+ assert os.path.exists(tempfile_path), "Can't convert audio to mp3"
+
+ return tempfile_path
+
+
+
diff --git a/app/core/content/image.py b/app/core/content/image.py
new file mode 100644
index 0000000..96d9d21
--- /dev/null
+++ b/app/core/content/image.py
@@ -0,0 +1,26 @@
+import os
+
+from PIL import Image
+
+from app.core._config import UPLOADS_DIR
+from app.core.logger import make_log
+import traceback
+
+
+class ImageContentMixin:
+ def as_jpeg(self) -> str:
+ tempfile_path = os.path.join(UPLOADS_DIR, f"temporary_{self.hash}.jpeg")
+ if not os.path.exists(tempfile_path):
+ cover_image = Image.open(self.filepath)
+ cover_image = cover_image.convert('RGB')
+ quality = 95
+ while quality > 10:
+ cover_image.save(tempfile_path, 'JPEG', quality=quality)
+ if os.path.getsize(tempfile_path) <= 200 * 1024:
+ break
+
+ quality -= 5
+
+ assert os.path.exists(tempfile_path), "Image not found"
+
+ return tempfile_path
diff --git a/app/core/models/content/user_content.py b/app/core/models/content/user_content.py
index 9ec2caa..4f1d4e0 100644
--- a/app/core/models/content/user_content.py
+++ b/app/core/models/content/user_content.py
@@ -28,6 +28,7 @@ class UserContent(AlchemyBase, UserContentIndexationMixin):
wallet_connection = relationship('WalletConnection', uselist=False, foreign_keys=[wallet_connection_id])
content = relationship('StoredContent', uselist=False, foreign_keys=[content_id])
+
class UserAction(AlchemyBase):
__tablename__ = 'users_actions'
diff --git a/app/core/models/node_storage.py b/app/core/models/node_storage.py
index 1ebfd2b..0a07463 100644
--- a/app/core/models/node_storage.py
+++ b/app/core/models/node_storage.py
@@ -7,11 +7,13 @@ from app.core.logger import make_log
from app.core._config import UPLOADS_DIR, PROJECT_HOST
import os
from app.core.content.content_id import ContentId
+from app.core.content.audio import AudioContentMixin
+from app.core.content.image import ImageContentMixin
# from app.core.models.content.indexation_mixins import NodeStorageIndexationMixin
from .base import AlchemyBase
-class StoredContent(AlchemyBase):
+class StoredContent(AlchemyBase, AudioContentMixin):
__tablename__ = 'node_storage'
id = Column(Integer, autoincrement=True, primary_key=True)
@@ -68,6 +70,31 @@ class StoredContent(AlchemyBase):
return bool(self.key_id or self.decrypted_content_id)
+ def open_content(self, db_session, content_type=None):
+ try:
+ # Получение StoredContent в прод-виде с указанием типа данных и доступный для перевода в другие форматы
+ decrypted_content = self if not self.encrypted else None
+ encrypted_content = self if self.encrypted else None
+ content_type = None
+ if not decrypted_content:
+ decrypted_content = db_session.query(StoredContent).filter(StoredContent.id == self.decrypted_content_id).first()
+ else:
+ encrypted_content = db_session.query(StoredContent).filter(StoredContent.decrypted_content_id == self.id).first()
+
+ assert decrypted_content, "Can't get decrypted content"
+ assert encrypted_content, "Can't get encrypted content"
+ content_type = content_type or decrypted_content.json_format()['content_type']
+ content_type, content_encoding = content_type.split('/')
+
+ return {
+ 'encrypted_content': encrypted_content,
+ 'decrypted_content': decrypted_content,
+ 'content_type': content_type or 'application/x-binary'
+ }
+ except BaseException as e:
+ make_log("NodeStorage.open_content", f"Can't open content: {self.id} {e}", level='error')
+ raise e
+
def json_format(self):
extra_fields = {}
if self.btfs_cid:
diff --git a/locale/en/LC_MESSAGES/sanic_telegram_bot.mo b/locale/en/LC_MESSAGES/sanic_telegram_bot.mo
index 195f635..75abcd4 100644
Binary files a/locale/en/LC_MESSAGES/sanic_telegram_bot.mo and b/locale/en/LC_MESSAGES/sanic_telegram_bot.mo differ
diff --git a/locale/en/LC_MESSAGES/sanic_telegram_bot.po b/locale/en/LC_MESSAGES/sanic_telegram_bot.po
index 1501c82..0e02e43 100644
--- a/locale/en/LC_MESSAGES/sanic_telegram_bot.po
+++ b/locale/en/LC_MESSAGES/sanic_telegram_bot.po
@@ -128,7 +128,8 @@ msgstr "⚠️ Unsupported content"
#: app/core/models/_telegram/templates/player.py:111
msgid "p_playerContext_purchaseRequested"
msgstr "Confirm the purchase in your wallet. After that, you will be able to "
-"listen to the full track version."
+"listen to the full track version.\n\n"
+"This can take up to few minutes"
#: app/core/models/_telegram/templates/player.py:114
msgid "buyTrackListenLicense_button"
@@ -149,7 +150,7 @@ msgstr "Error: content price is not set"
#: app/client_bot/routers/content.py:133
msgid "viewTrack_button"
-msgstr "View track"
+msgstr "Buy track"
#: app/client_bot/routers/content.py:138
msgid "cancelPurchase_button"