import asyncio
import logging
import os
import shutil
import tempfile
from aiogram import F, Router
from aiogram.exceptions import TelegramBadRequest
from aiogram.types import Message, CallbackQuery, ContentType, InlineKeyboardButton, InlineKeyboardMarkup, FSInputFile
from aiogram.filters import Command
from scripts import Movie, process_frames
from config import WARN
router = Router()
# СТАРТОВАЯ КОМАНДА
@router.message(Command("start", "help"))
async def helps(msg: Message) -> None:
buttons = [[InlineKeyboardButton(text="ФУНКЦИИ", callback_data="fun"),
InlineKeyboardButton(text="АВТОР", callback_data="auth")]]
keyboard = InlineKeyboardMarkup(inline_keyboard=buttons)
await msg.answer(text="Кружочичек — бот для обработки видео и кружочков в Телеграме. Для начала работы "
"вам достаточно скинуть квадратное видео не дольше минуты в чат, чтобы получить кружок. "
"Или скинуть кружок, выбрать формат обработки углов и получить готовый видеоролик! "
"НО у Телеграмма бывают кружочки с некорректными метаданными, из-за чего его "
"обработка становится невозможной, или в итоге видео будет с браком, поэтому "
"обязательно проверяйте полученный результат!", reply_markup=keyboard)
@router.callback_query(F.data == "auth")
async def author(call: CallbackQuery) -> None:
await call.message.answer('| АВТОР |\n\n>> Этот телеграм бот, крайне прост и примитивен. В его '
'распоряжении есть всего лишь две функции, а именно: превращение квадратных '
'видеороликов в кружочки и скачивание кружочков с последующей обработкой краёв в '
'двух предложенных вариантах.'
'\n\nЯ же пишу подобные небольшие проекты, о которых вы можете узнать '
'больше на моём GitHub.')
@router.callback_query(F.data == "fun")
async def function(call: CallbackQuery) -> None:
await call.message.answer('| ФУНКЦИИ |\n\n1. Обработка кружочка с градиентным или размытым фоном на '
'выбор по бокам\n2. Получение из квадратного видео длиною не больше минуты кружочек')
# ОБРАБОТЧИК ВИДЕО
@router.message(F.content_type == ContentType.VIDEO)
async def video_to_circle(msg: Message) -> None:
video = f"../data/circles/{msg.chat.id}_{msg.message_id}.mp4"
await msg.reply("Началась обработка видео! Оно должно быть квадратным и не дольше одной минуты, в ином "
"случае бот в ответ вернёт вам изначальное видео, а не кружочек!")
try:
await msg.bot.download(file=msg.video.file_id, destination=video)
await msg.answer_video_note(video_note=FSInputFile(video))
except TelegramBadRequest as err:
logging.warning("Не удалось сделать кружочек: %s", err)
await msg.answer("Не получилось сделать кружочек. Видео должно быть квадратным, не дольше минуты "
"и не больше 20 МБ.")
finally:
if os.path.exists(video):
os.remove(video)
# ОБРАБОТЧИК КРУЖОЧКОВ
@router.message(F.content_type == ContentType.VIDEO_NOTE)
async def video_note(msg: Message) -> None:
# номер сообщения в кнопках: если прислать несколько кружочков подряд, каждый обработается свой
buttons = [[InlineKeyboardButton(text="Градиент", callback_data=f"grad:{msg.message_id}"),
InlineKeyboardButton(text="Блюр", callback_data=f"blur:{msg.message_id}")]]
keyboard = InlineKeyboardMarkup(inline_keyboard=buttons)
msg_answer = await msg.reply("Кружочек загружается...")
await msg.bot.download(file=msg.video_note.file_id,
destination=f"../data/video_notes/{msg.chat.id}_{msg.message_id}.mp4")
await msg_answer.edit_text(text=WARN + "Какой тип фона в углах вы выберите?", reply_markup=keyboard)
@router.callback_query(F.data.regexp(r"^(grad|blur):\d+$"))
async def work_part(call: CallbackQuery) -> None:
mode, note_id = call.data.split(":")
video_name = f"{call.message.chat.id}_{note_id}"
video_file = video_name + ".mp4"
source = "../data/video_notes/" + video_file
if not os.path.exists(source):
# кнопку нажали повторно или кружочек уже обработан
await call.answer("Этот кружочек уже обработан. Пришлите его ещё раз.", show_alert=True)
return
msg = await call.message.edit_text(WARN + "Начало обработки!")
# отдельная папка на каждую обработку и уборка в любом случае — иначе после первой же ошибки
# повторная обработка в этом чате падала на уже существующей папке
path = tempfile.mkdtemp(prefix=video_name + "-", dir="../data/videos")
try:
path_video = path + "/" + video_file
os.replace(source, path_video)
path_frames = path + "/frames"
os.mkdir(path_frames)
path_background = path + "/background"
os.mkdir(path_background)
# тяжёлая обработка — в отдельном потоке, чтобы бот не замирал для остальных пользователей
msg = await msg.edit_text(WARN + "Этап: 1 - Обработка видео.")
video = Movie(path_video, video_name, path)
if not await asyncio.to_thread(video.split_into_frames):
await msg.edit_text("Возникла ошибка! Кружочек невозможно обработать!")
return
msg = await msg.edit_text(WARN + "Этап: 2 - Обработка кадров.")
frames = await asyncio.to_thread(process_frames, path_frames, path_background, mode)
msg = await msg.edit_text(WARN + "Этап: 3 - Объединение кадров.")
result = await asyncio.to_thread(video.unity_into_video, frames)
msg_final = await msg.edit_text(WARN + "Готово! Видео отправляется...")
reply_to = call.message.reply_to_message
await msg.answer_video(video=FSInputFile(result), caption=WARN,
reply_to_message_id=reply_to.message_id if reply_to else None)
await msg_final.delete()
except Exception:
logging.exception("Ошибка обработки кружочка")
await msg.edit_text("Возникла ошибка! Кружочек невозможно обработать!")
finally:
shutil.rmtree(path, ignore_errors=True)