From d7cb86626fc0ee6996a120db4872e3455ed41ac2 Mon Sep 17 00:00:00 2001 From: IGlek Date: Mon, 30 Jun 2025 23:59:17 +0300 Subject: [PATCH] v. 1.0 --- code/bot.py | 18 ++++++ code/config.py | 4 ++ code/handlers.py | 122 ++++++++++++++++++++++++++++++++++++++ code/init.py | 32 ++++++++++ code/scripts.py | 13 ++-- data/circles/.gitkeep | 0 data/video_notes/.gitkeep | 0 data/videos/.gitkeep | 0 8 files changed, 184 insertions(+), 5 deletions(-) create mode 100644 code/bot.py create mode 100644 code/config.py create mode 100644 code/handlers.py create mode 100644 code/init.py create mode 100644 data/circles/.gitkeep create mode 100644 data/video_notes/.gitkeep create mode 100644 data/videos/.gitkeep diff --git a/code/bot.py b/code/bot.py new file mode 100644 index 0000000..74ee4b2 --- /dev/null +++ b/code/bot.py @@ -0,0 +1,18 @@ +import asyncio, logging, warnings + +from init import * +from handlers import router + + +async def main() -> None: + dp.include_router(router) + await bot.delete_webhook(drop_pending_updates=True) + await dp.start_polling(bot, allowed_updates=dp.resolve_used_update_types(), skipUpdates=True) + + +if __name__ == "__main__": + logging.basicConfig(level=logging.INFO) + # warnings.filterwarnings("ignore") + + try: asyncio.run(main()) + except KeyboardInterrupt: pass diff --git a/code/config.py b/code/config.py new file mode 100644 index 0000000..31d1e5a --- /dev/null +++ b/code/config.py @@ -0,0 +1,4 @@ +botToken = "XXXXXXXXXXXXXXXXXXXXXXXX" # @circlechek_bot + +WARN = ("Предупреждение! Кружочек может быть с некорректными метаданными, в этом случае программа выдаст ошибку " + "либо произведёт видео с браком! Обязательно перепроверяйте получившиеся видео!\n\n") diff --git a/code/handlers.py b/code/handlers.py new file mode 100644 index 0000000..9108e25 --- /dev/null +++ b/code/handlers.py @@ -0,0 +1,122 @@ +from aiogram import types, F, Router +from aiogram.types import Message, CallbackQuery, ContentType, InlineKeyboardButton, InlineKeyboardMarkup, FSInputFile +from aiogram.filters import Command + +import shutil + +from scripts import * +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, row_width=2) + + 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 = "../data/circles/" + str(msg.chat.id) + ".mp4" + + await msg.reply("Началась обработка видео! Оно должно быть квадратным и не дольше одной минуты, в ином " + "случае бот в ответ вернёт вам изначальное видео, а не кружочек!") + await msg.bot.download(file=msg.video.file_id, destination=video) + await msg.answer_video_note(video_note=FSInputFile(video)) + os.remove(video) + + +# ОБРАБОТЧИК КРУЖОЧКОВ +@router.message(F.content_type == ContentType.VIDEO_NOTE) +async def video_note(msg: Message) -> None: + buttons = [[InlineKeyboardButton(text="Градиент", callback_data="grad"), + InlineKeyboardButton(text="Блюр", callback_data="blur")]] + keyboard = types.InlineKeyboardMarkup(inline_keyboard=buttons, row_width=2) + + msg_answer = await msg.reply("Кружочек загружается...") + await msg.bot.download(file=msg.video_note.file_id, destination="../data/video_notes/" + str(msg.chat.id) + ".mp4") + await msg_answer.edit_text(text=WARN + "Какой тип фона в углах вы выберите?", reply_markup=keyboard) + + +@router.callback_query(lambda call: call.data == "grad" or call.data == "blur") +async def work_part(call: CallbackQuery) -> None: + msg = await call.message.edit_text(WARN + "Начало обработки!") + + video_name = str(msg.chat.id) + video_file = video_name + ".mp4" + + path = f"../data/videos/{video_name}" + os.mkdir(path) + + path_video = path + "/" + video_file + os.replace("../data/video_notes/" + video_file, path_video) + + path_frames = path + f"/frames" + os.mkdir(path_frames) + + path_background = path + f"/background" + os.mkdir(path_background) + + msg = await msg.edit_text(WARN + "Этап: 1 - Обработка видео.") + video = Movie(path_video, video_name, path) + procces = video.split_into_frames() + + if not procces: + await msg.edit_text("Возникла ошибка! Кружочек невозможно обработать!") + shutil.rmtree(path) + return + + msg = await msg.edit_text(WARN + "Этап: 2 - Обработка кадров.") + frames = list(sorted(os.listdir(path_frames))) + + for frame in frames: + img = Frame(path_frames + "/" + frame, path_background + "/" + f'{frame[:-5]}-g.jpeg') + width, height = img.size() + + if call.data == "blur": + img.blur(width, height) + else: + r, g, b = img.medium_color(); nearly = 10 + img.gradient(width, height, (r - nearly, g - nearly, b - nearly), + (r + nearly, g + nearly, b + nearly), (True, False, False)) + + img.unity_image() + + msg = await msg.edit_text(WARN + "Этап: 3 - Объединение кадров.") + video.unity_into_video(frames) + + msg_final = await msg.edit_text(WARN + "Готово! Видео отправляется...") + + await msg.answer_video(video=FSInputFile(path + "/acs-" + video_file), caption=WARN, + reply_to_message_id=call.message.reply_to_message.message_id) + await msg_final.delete() + + shutil.rmtree(path) diff --git a/code/init.py b/code/init.py new file mode 100644 index 0000000..5154444 --- /dev/null +++ b/code/init.py @@ -0,0 +1,32 @@ +from aiogram import Bot, Dispatcher +from aiogram.enums.parse_mode import ParseMode +from aiogram.fsm.storage.memory import MemoryStorage +from aiogram.client.bot import DefaultBotProperties + +from config import botToken + +bot = Bot(token=botToken, default=DefaultBotProperties(parse_mode=ParseMode.HTML)) +dp = Dispatcher(storage=MemoryStorage()) + + +import logging, functools, sys + +# logging.basicConfig(level=logging.INFO, filename='../data/info.log',filemode="w", format="%(asctime)s %(levelname)s %(message)s") + +def bug_report(func): + @functools.wraps(func) + + def wrapper(*args, **kwargs): + + try: + result = func(*args, **kwargs) + logging.info(f"Successful result") + return result + except Exception as err: + logging.error("Ошибка", exc_info=True) + """with open('../data/log.txt', "a") as f: + f.write(repr(e))""" + sys.exit() + + return wrapper + diff --git a/code/scripts.py b/code/scripts.py index 0b51146..f1a87f4 100644 --- a/code/scripts.py +++ b/code/scripts.py @@ -1,7 +1,9 @@ from PIL import Image, ImageDraw, ImageFilter -from moviepy.editor import * +from moviepy import * import numpy, os +from init import bug_report + class Movie: def __init__(self, video_file, video_name, path): @@ -13,7 +15,7 @@ class Movie: def split_into_frames(self): video_clip = VideoFileClip(self.video_file) - video_clip.audio.write_audiofile(self.path + f"/{self.video_name}-audio.mp3", verbose=False, logger=None) + video_clip.audio.write_audiofile(self.path + f"/{self.video_name}-audio.mp3", logger=None) self.path_frames = self.path + "/frames" step = 1 / 30.0 if video_clip.fps > 60.0 else 1 / video_clip.fps @@ -32,6 +34,7 @@ class Movie: video_clip.close() return True + @bug_report def unity_into_video(self, frames): video_clip = VideoFileClip(self.video_file) fps = 30.0 if video_clip.fps > 60.0 else video_clip.fps @@ -39,14 +42,14 @@ class Movie: os.remove(self.video_file) clip = ImageSequenceClip(list(map(lambda x: self.path_frames + "/" + x, frames)), fps=fps) - clip.write_videofile(self.video_file, verbose=False, logger=None) + clip.write_videofile(self.video_file, logger=None) clip.close() video_clip = VideoFileClip(self.video_file) audio_clip = AudioFileClip(self.path + f"/{self.video_name}-audio.mp3") - video_clip_with_audio = video_clip.set_audio(audio_clip) - video_clip_with_audio.write_videofile(self.path + "/acs-" + self.video_name + ".mp4", verbose=False, logger=None) + video_clip_with_audio = video_clip.with_audio(audio_clip) + video_clip_with_audio.write_videofile(self.path + "/acs-" + self.video_name + ".mp4", logger=None) video_clip.close() audio_clip.close() diff --git a/data/circles/.gitkeep b/data/circles/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/data/video_notes/.gitkeep b/data/video_notes/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/data/videos/.gitkeep b/data/videos/.gitkeep new file mode 100644 index 0000000..e69de29