Отправка нескольких видео
创建于:2024年12月19日
创建于:2024年12月19日
Erweiterung der Videooptionen
Der Benutzer hat einen Telegram-Bot-Code geteilt, der Informationen über Rennstrecken bietet. Der aktuelle Code kann bis zu zwei Videos pro Rennstrecke anhängen. Der Benutzer möchte die Möglichkeit, mehr als zwei Videos anzuhängen, möglicherweise pro Rennstrecke oder insgesamt erweitern.
Anpassen der Speicherung
Es scheint, dass der Code derzeit nur ein Video pro Rennstrecke unterstützt. Um mehrere Videos zu ermöglichen, könnte die 'tracks'-Datenstruktur aktualisiert und die 'send_video'-Funktion angepasst werden, um eine Liste von Videos zu handhaben.
Anpassen des Codes
Ich arbeite daran, dem Code die Möglichkeit zu geben, mehrere Videos zu senden, indem ich die Datenstruktur 'tracks' aktualisiere und die 'send_video'-Funktion anpasse, um jede Liste von Videos einzeln zu senden.
Anpassen des Codes
Ich aktualisiere die 'tracks'-Datenstruktur, um die Verwendung von 'videos' anstelle von 'video' zu ermöglichen. In der Funktion 'track_menu_choice' iteriere ich durch die Liste der Videos und sende jedes Video einzeln.
Gedanken für 4 Sekunden
Чтобы позволить вашему боту отправлять более двух видео для каждой трассы, необходимо внести следующие изменения:
Изменить структуру данных трасс (tracks
):
"video"
, храните список видеофайлов в поле "videos"
.Обновить обработчик выбора видео:
track_menu_choice
так, чтобы она могла обрабатывать и отправлять несколько видеофайлов.Обновить функцию отправки видео (если необходимо):
send_video
уже поддерживает отправку одного видео. Для отправки нескольких видео нужно вызывать эту функцию несколько раз.Ниже приведены подробные шаги и кодовые примеры для реализации этих изменений.
tracks
)Вместо использования поля "video"
как строки, измените его на список "videos"
. Например:
tracks = { "Нюрбургринг-Нордшляйфе": { # ... другие поля ... "videos": ["nurburgring/video1.mp4", "nurburgring/video2.mp4"], # Изменено с "video" на "videos" # ... остальные поля ... }, "Монца": { # ... другие поля ... "videos": ["monza/video1.mp4", "monza/video2.mp4", "monza/video3.mp4"], # Добавлено несколько видео # ... остальные поля ... }, "Спа-Франкоршам": { # ... другие поля ... "videos": ["spa/video1.mp4"], # Можно оставить один элемент в списке # ... остальные поля ... } }
Важно: Убедитесь, что все трассы в вашем словаре tracks
используют поле "videos"
вместо "video"
. Это необходимо для единообразия и корректной работы последующего кода.
В функции track_menu_choice
измените обработку опции "Видео трассы"
, чтобы она могла отправлять несколько видео из списка "videos"
.
Вот обновленный фрагмент кода для этой функции:
async def track_menu_choice(update: Update, context: ContextTypes.DEFAULT_TYPE): # Проверка анти-спам if not await anti_spam(update, context): return TRACK_MENU text = update.message.text current_track = context.user_data.get("current_track") if text == "Назад": # Возвращение к списку трасс await update.message.reply_text("Выберите трассу:", reply_markup=tracks_keyboard()) return CHOOSE_TRACK if current_track not in tracks: await update.message.reply_text("Ошибка. Попробуйте снова.", reply_markup=main_menu_keyboard()) return CHOOSE_MAIN track_info = tracks[current_track] if text == "Лидеры": leaders = track_info["лидеры"] response = f"**Лидеры на трассе {current_track}:**\n{leaders}" await update.message.reply_text(response, parse_mode="Markdown", reply_markup=track_menu_keyboard()) elif text == "История о трассе": history = track_info["история"] response = f"**История трассы {current_track}:**\n{history}" # Получение имени файла фото истории photo_filename = track_info.get("фото_история") if photo_filename: # Отправка фото с текстом await send_photo( context, chat_id=update.effective_chat.id, photo_filename=photo_filename, caption=response, reply_markup=track_menu_keyboard() ) else: # Если фото не задано, отправляем только текст await update.message.reply_text(response, parse_mode="Markdown", reply_markup=track_menu_keyboard()) elif text == "Опасные повороты": await update.message.reply_text("Выберите опасный поворот:", reply_markup=dangerous_turns_keyboard(current_track)) return CHOOSE_DANGEROUS_TURN elif text == "Видео трассы": # Обработка новой опции videos = track_info.get("videos", []) # Изменено с "video" на "videos" if videos: response = f"**Видео трассы {current_track}:**" await update.message.reply_text(response, parse_mode="Markdown") for video_filename in videos: await send_video( context, chat_id=update.effective_chat.id, video_filename=video_filename, caption=None, # Можно добавить описание видео, если необходимо reply_markup=track_menu_keyboard() ) else: await update.message.reply_text( "Видео для этой трассы недоступно.", parse_mode="Markdown", reply_markup=track_menu_keyboard() ) else: response = "Неизвестная команда. Попробуйте снова." await update.message.reply_text(response, parse_mode="Markdown", reply_markup=track_menu_keyboard()) return TRACK_MENU
Объяснение изменений:
Получение списка видео: Используем track_info.get("videos", [])
, чтобы получить список видеофайлов. Если список пустой или поле отсутствует, вернётся пустой список.
Отправка заголовка: Отправляем заголовок "**Видео трассы {current_track}:**"
отдельно перед отправкой видео.
Цикл по видеофайлам: Проходимся по каждому видеофайлу в списке videos
и вызываем функцию send_video
для отправки каждого из них.
Описание видео: В данном примере caption
установлен в None
, но вы можете модифицировать его, чтобы добавить описание или номер видео.
Если вы хотите добавить описание для каждого видео или улучшить функцию отправки видео, вы можете внести дополнительные изменения в функцию send_video
. Однако текущая реализация уже поддерживает отправку одного видео за раз, и, следовательно, достаточно вызывать её несколько раз в цикле для отправки нескольких видео.
Ниже представлен полный код с внесёнными изменениями для поддержки отправки более двух видео:
import logging import time import os from telegram import ( Update, ReplyKeyboardMarkup, ReplyKeyboardRemove, InputFile ) from telegram.ext import ( ApplicationBuilder, CommandHandler, MessageHandler, filters, ConversationHandler, ContextTypes ) # Настройка логирования logging.basicConfig( format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO ) logger = logging.getLogger(__name__) # Состояния диалога CHOOSE_MAIN, CHOOSE_TRACK, TRACK_MENU, CHOOSE_DANGEROUS_TURN = range(4) # Настройки анти-спама MAX_MESSAGES = 5 # Максимум сообщений за период TIME_WINDOW = 10 # Интервал в секундах для подсчёта сообщений TIMEOUT_DURATION = 10 # Длительность тайм-аута в секундах # Папки с медиафайлами PHOTOS_DIR = "photos" # Убедитесь, что такая папка существует VIDEOS_DIR = "videos" # Новая папка для видео # Разрешённые расширения файлов ALLOWED_PHOTO_EXTENSIONS = {'.jpg', '.jpeg', '.png', '.gif', '.bmp'} ALLOWED_VIDEO_EXTENSIONS = {'.mp4', '.mov', '.avi', '.mkv'} # Данные о трассах (обновлено поле 'videos' для каждой трассы) tracks = { "Нюрбургринг-Нордшляйфе": { "описание": "Узкие улочки и сложные повороты делают трассу Нюрбургринг-Нордшляйфе одной из самых престижных.", "лидеры": "Mercedes AMG ONE | *6:29.09 минут* |\n\n2. Porsche GT2 RS MR 991.2 | *6:43.30 минут* |\n\n3. Mercedes AMG GT Black Series | *6:48.04 минут* |\n\n4. Porsche GT3 RS 992 | *6:49.32 минут* |\n\n5. Lamborghini Aventador LP 770-4 SVJ | *6:49.40 минут* |\n\n6. Porsche GT2 RS MR 991.2 | *6:52.15 минут* |\n\n7. Radical SR8 LM | *6:53.20 минут* |\n\n8. Porsche GT3 992 MR | *6:55.73 минут* |\n\n9. Lamborghini Huracan LP 640-4 Performante | *6:57.25 минут* |\n\n10. Porsche GT3 992 | *6:59.93 минут* |", "история": "Трасса Нордшляйфе была спроектирована и построена с 1925 до 1927 года.Открытие состоялось 18-19 июня 1927 года. Стоимость реализации объекта под ключ составила примерно 8 100 000 Немецких Марок (в пересчёте на 2020 год это около 30 000 000 Евро). Протяжённость трассы составляет 20,8 километров. Заезды и массовые мероприятия проходят на трассе с марта до ноября. В зимнее время в связи с погодными условиями Нюрбургринг закрыт для заездов. Количество левых поворотов — 33, правых — 40. Перепад высоты составляет порядка 300 метров. Самое высокое место — участок Höhe Acht, 616,80 метров над уровнем моря. Самое низкое место — участок Breidscheid, 320 метров над уровнем моря. Максимальный подъём на трассе составляет 17%. Максимальный спуск — 11%. Стоимость проезда одного круга (20,8 км) на автомобиле или мотоцикле с Понедельника до Четверга составляет 25 евро и 30 евро с Пятницы до Воскресенья. Стоимость проживания в регионе вокруг гоночной трассы — от 50 евро в сутки (с завтраком). Стоимость эвакуации автомобиля с гоночной трассы стартует от 150 евро. Ближайшие крупные аэропорты: Франкфурт на Майне, Франкфурт-Хан, Кёльн-Бонн и Дюссельдорф. Абсолютный рекорд полного круга Нордшляйфе с временем 5:19,546 минут был установлен 29 июня 2018 года на прототипе Porsche 919 Hybrid EVO тестовым пилотом и гонщиком Тимо Бернхардом. Трасса Нюрбургринг GP для проведения Формулы 1 была сдана в эксплуатацию в 1984 году. Протяженность трассы составляет 5,148 км. Количество поворотов — 15. Рекорд трассы принадлежит Михаэлю Шумахеру, который в 2004 году на Формульном болиде проехал полный круг GP за 1:29,468 мин.", "фото_история": "nurburgring/history.jpg", "videos": ["nurburgring/video1.mp4", "nurburgring/video2.mp4"], # Обновлено "опасные_повороты": { "Karussell": { "описание": "Описание поворота Karussell — один из самых известных и сложных поворотов на Нюрбургринге. Требует максимальной концентрации и точности.", "фото": "nurburgring/karussell.jpg" }, "Brünnchen": { "описание": "Поворот Brünnchen характеризуется высокой скоростью и требованием к точности траектории.", "фото": "nurburgring/brunnchen.jpg" }, "Flugplatz": { "описание": "Поворот Flugplatz — это длинный и быстрый поворот, который требует отличного контроля над автомобилем.", "фото": "nurburgring/flugplatz.jpg" } } }, "Монца": { "описание": "Трасса Монца известна своими длинными прямыми и высокой скоростью, что делает её одной из самых быстрых трасс в мире.", "лидеры": "Баррикелло (Ferrari) | *1:21,046* |\n\n2. Хэмилтон (Mercedes) | *1:18,887* |\n\n", "история": "Автодром Монца был открыт в 1922 году и является одной из старейших трасс Формулы-1. Первоначальная трасса проходила частично по общественным дорогам. Трасса неоднократно перестраивалась и модернизировалась, но сохранила свою скоростную сущность. Монца славится своей высокой скоростью и длинными прямыми, что делает её одной из самых быстрых трасс в мире. Она также является свидетелем многих знаменательных событий и трагических аварий в истории автоспорта.", "фото_история": "monza/history.jpg", "videos": ["monza/video1.mp4", "monza/video2.mp4", "monza/video3.mp4"], # Обновлено "опасные_повороты": { "Lesmo": { "описание": "Секция Lesmo состоит из двух быстрых поворотов, которые требуют максимальной точности и контроля над автомобилем.", "фото": "monza/lesmo.jpg" }, "Curva Grande": { "описание": "Большой поворот (Curva Grande) — быстрый и протяженный поворот, где ошибки могут привести к серьезным последствиям.", "фото": "monza/curva_grande.jpg" } } }, "Спа-Франкоршам": { # Новая трасса "описание": "Трасса Спа-Франкоршам известна своим непредсказуемым погодным условиям и сложными поворотами, что делает её одной из самых технически сложных трасс в мире.", "лидеры": "Льюис Хэмилтон | *6:47.285 минут* |\n\nСебастьян Феттель | *6:49.875 минут* |\n\nМакс Ферстаппен | *6:50.123 минут* |", "история": "Автодром Спа-Франкоршам был открыт в 1921 году и является одним из старейших и самых уважаемых треков в мире автоспорта. Трасса проходит через живописные пейзажи Бельгии и известна своей историей и традициями. Спа-Франкоршам принимал множество чемпионатов мира, включая Формулу-1, и является любимой трассой многих гонщиков за её уникальную смесь высоких скоростей и технических сложностей.", "фото_история": "spa/history.jpg", "videos": ["spa/video1.mp4"], # Обновлено "опасные_повороты": { "Eau Rouge": { "описание": "Eau Rouge — знаменитый быстрый подъем с крутым поворотом, требующий точности и смелости от пилотов.", "фото": "spa/eau_rouge.jpg" }, "Pouhon": { "описание": "Pouhon — длинный и быстрый поворот, который требует отличной аэродинамики и устойчивости автомобиля.", "фото": "spa/pouhon.jpg" } } } } creators_info = "Этот бот был создан командой:\n\n1. Ратников Иван\n2. Хамраева Шукрона\n3. Новиков Елисей\n4. Елизарова Ксения\n5. Пискунов Кирилл\n6. Колосов Дмитрий." # Функция для динамического создания клавиатур с несколькими кнопками в строке def create_keyboard(buttons, buttons_per_row=2): keyboard = [] for i in range(0, len(buttons), buttons_per_row): keyboard.append(buttons[i:i + buttons_per_row]) return ReplyKeyboardMarkup(keyboard, resize_keyboard=True) # Клавиатуры def main_menu_keyboard(): buttons = ["Выбрать трассу", "Создатели"] return create_keyboard(buttons, buttons_per_row=2) def tracks_keyboard(): buttons = list(tracks.keys()) + ["Назад"] return create_keyboard(buttons, buttons_per_row=2) # 2 кнопки в строке def track_menu_keyboard(): buttons = ["Лидеры", "История о трассе", "Опасные повороты", "Видео трассы", "Назад"] return create_keyboard(buttons, buttons_per_row=2) # 2 кнопки в строке, последняя строка с одной кнопкой def dangerous_turns_keyboard(track_name): dangerous_turns = tracks[track_name]["опасные_повороты"] buttons = list(dangerous_turns.keys()) + ["Назад"] return create_keyboard(buttons, buttons_per_row=2) # 2 кнопки в строке # Функция анти-спама async def anti_spam(update: Update, context: ContextTypes.DEFAULT_TYPE): user_id = update.effective_user.id current_time = time.time() # Проверка на тайм-аута timeout_until = context.user_data.get("spam_timeout_until", 0) if current_time < timeout_until: await update.message.reply_text("Вы слишком часто отправляете сообщения. Пожалуйста, подождите немного.") return False # Инициализация списка временных меток timestamps = context.user_data.get("timestamps", []) # Добавление текущей метки времени timestamps.append(current_time) # Очистка старых меток времени timestamps = [t for t in timestamps if current_time - t <= TIME_WINDOW] context.user_data["timestamps"] = timestamps # Проверка количества сообщений за период if len(timestamps) > MAX_MESSAGES: context.user_data["spam_timeout_until"] = current_time + TIMEOUT_DURATION await update.message.reply_text("Слишком много сообщений! Пожалуйста, подождите 10 секунд.") return False return True # Вспомогательная функция для отправки фотографий с обработкой длинных подписей async def send_photo(context, chat_id, photo_filename, caption=None, reply_markup=None): # Полный путь к файлу file_path = os.path.join(PHOTOS_DIR, photo_filename) # Проверка расширения файла _, ext = os.path.splitext(photo_filename) if ext.lower() not in ALLOWED_PHOTO_EXTENSIONS: logger.warning(f"Недопустимое расширение файла: {photo_filename}") await context.bot.send_message( chat_id=chat_id, text="Невозможно отправить файл с недопустимым расширением.", reply_markup=reply_markup ) return if os.path.isfile(file_path): try: with open(file_path, 'rb') as photo_file: if caption: if len(caption) <= 1024: # Если длина подписи допустимая, отправляем фото с подписью await context.bot.send_photo( chat_id=chat_id, photo=photo_file, caption=caption, parse_mode="Markdown", reply_markup=reply_markup ) else: # Если подпись слишком длинная, отправляем фото без подписи await context.bot.send_photo( chat_id=chat_id, photo=photo_file, reply_markup=reply_markup ) # Разбиваем длинный текст на части по 1024 символа for i in range(0, len(caption), 1024): part = caption[i:i+1024] await context.bot.send_message( chat_id=chat_id, text=part, parse_mode="Markdown", reply_markup=reply_markup ) else: # Если подпись не требуется, просто отправляем фото await context.bot.send_photo( chat_id=chat_id, photo=photo_file, reply_markup=reply_markup ) except Exception as e: logger.error(f"Ошибка при отправке фото: {e}") if caption: await context.bot.send_message( chat_id=chat_id, text=caption, parse_mode="Markdown", reply_markup=reply_markup ) else: logger.warning(f"Файл фото не найден: {file_path}") if caption: await context.bot.send_message( chat_id=chat_id, text=f"{caption}\n\n*Фото не доступно.*", parse_mode="Markdown", reply_markup=reply_markup ) # Вспомогательная функция для отправки видео из локальной папки async def send_video(context, chat_id, video_filename, caption=None, reply_markup=None): # Полный путь к файлу file_path = os.path.join(VIDEOS_DIR, video_filename) # Проверка расширения файла _, ext = os.path.splitext(video_filename) if ext.lower() not in ALLOWED_VIDEO_EXTENSIONS: logger.warning(f"Недопустимое расширение видеофайла: {video_filename}") await context.bot.send_message( chat_id=chat_id, text="Невозможно отправить видео с недопустимым расширением.", reply_markup=reply_markup ) return if os.path.isfile(file_path): try: with open(file_path, 'rb') as video_file: if caption: await context.bot.send_video( chat_id=chat_id, video=video_file, caption=caption, parse_mode="Markdown", reply_markup=reply_markup ) else: await context.bot.send_video( chat_id=chat_id, video=video_file, reply_markup=reply_markup ) except Exception as e: logger.error(f"Ошибка при отправке видео: {e}") if caption: await context.bot.send_message( chat_id=chat_id, text=f"{caption}\n\n*Видео не доступно.*", parse_mode="Markdown", reply_markup=reply_markup ) else: logger.warning(f"Файл видео не найден: {file_path}") if caption: await context.bot.send_message( chat_id=chat_id, text=f"{caption}\n\n*Видео не доступно.*", parse_mode="Markdown", reply_markup=reply_markup ) # Обработчики async def start(update: Update, context: ContextTypes.DEFAULT_TYPE): # Сброс анти-спам таймера при старте context.user_data.pop("spam_timeout_until", None) context.user_data.pop("timestamps", None) await update.message.reply_text( "Добро пожаловать! Выберите опцию:", reply_markup=main_menu_keyboard() ) return CHOOSE_MAIN async def main_menu_choice(update: Update, context: ContextTypes.DEFAULT_TYPE): # Проверка анти-спам if not await anti_spam(update, context): return CHOOSE_MAIN text = update.message.text if text == "Выбрать трассу": await update.message.reply_text("Выберите трассу:", reply_markup=tracks_keyboard()) return CHOOSE_TRACK elif text == "Создатели": await update.message.reply_text(creators_info, reply_markup=main_menu_keyboard()) return CHOOSE_MAIN else: await update.message.reply_text("Неизвестная команда. Попробуйте снова.", reply_markup=main_menu_keyboard()) return CHOOSE_MAIN async def track_choice(update: Update, context: ContextTypes.DEFAULT_TYPE): # Проверка анти-спам if not await anti_spam(update, context): return CHOOSE_TRACK text = update.message.text if text == "Назад": # Возврат в главное меню await update.message.reply_text("Главное меню:", reply_markup=main_menu_keyboard()) return CHOOSE_MAIN if text in tracks: context.user_data["current_track"] = text track_info = tracks[text] description = track_info["описание"] response = f"**Трасса:** {text}\n\n**Описание:** {description}" await update.message.reply_text(response, parse_mode="Markdown", reply_markup=track_menu_keyboard()) return TRACK_MENU else: await update.message.reply_text("Пожалуйста, выберите доступную трассу или нажмите Назад.", reply_markup=tracks_keyboard()) return CHOOSE_TRACK async def track_menu_choice(update: Update, context: ContextTypes.DEFAULT_TYPE): # Проверка анти-спам if not await anti_spam(update, context): return TRACK_MENU text = update.message.text current_track = context.user_data.get("current_track") if text == "Назад": # Возвращение к списку трасс await update.message.reply_text("Выберите трассу:", reply_markup=tracks_keyboard()) return CHOOSE_TRACK if current_track not in tracks: await update.message.reply_text("Ошибка. Попробуйте снова.", reply_markup=main_menu_keyboard()) return CHOOSE_MAIN track_info = tracks[current_track] if text == "Лидеры": leaders = track_info["лидеры"] response = f"**Лидеры на трассе {current_track}:**\n{leaders}" await update.message.reply_text(response, parse_mode="Markdown", reply_markup=track_menu_keyboard()) elif text == "История о трассе": history = track_info["история"] response = f"**История трассы {current_track}:**\n{history}" # Получение имени файла фото истории photo_filename = track_info.get("фото_история") if photo_filename: # Отправка фото с текстом await send_photo( context, chat_id=update.effective_chat.id, photo_filename=photo_filename, caption=response, reply_markup=track_menu_keyboard() ) else: # Если фото не задано, отправляем только текст await update.message.reply_text(response, parse_mode="Markdown", reply_markup=track_menu_keyboard()) elif text == "Опасные повороты": await update.message.reply_text("Выберите опасный поворот:", reply_markup=dangerous_turns_keyboard(current_track)) return CHOOSE_DANGEROUS_TURN elif text == "Видео трассы": # Обработка новой опции videos = track_info.get("videos", []) # Изменено с "video" на "videos" if videos: response = f"**Видео трассы {current_track}:**" await update.message.reply_text(response, parse_mode="Markdown") for video_filename in videos: await send_video( context, chat_id=update.effective_chat.id, video_filename=video_filename, caption=None, # Можно добавить описание видео, если необходимо reply_markup=track_menu_keyboard() ) else: await update.message.reply_text( "Видео для этой трассы недоступно.", parse_mode="Markdown", reply_markup=track_menu_keyboard() ) else: response = "Неизвестная команда. Попробуйте снова." await update.message.reply_text(response, parse_mode="Markdown", reply_markup=track_menu_keyboard()) return TRACK_MENU async def dangerous_turn_choice(update: Update, context: ContextTypes.DEFAULT_TYPE): # Проверка анти-спам if not await anti_spam(update, context): return CHOOSE_DANGEROUS_TURN text = update.message.text current_track = context.user_data.get("current_track") if text == "Назад": # Возвращение к меню трассы await update.message.reply_text("Выберите опцию:", reply_markup=track_menu_keyboard()) return TRACK_MENU dangerous_turns = tracks[current_track]["опасные_повороты"] if text in dangerous_turns: turn_info = dangerous_turns[text] description = turn_info["описание"] photo_filename = turn_info.get("фото") response_text = f"**Опасный поворот {text} на трассе {current_track}:**\n{description}" markup = create_keyboard(["Назад"], buttons_per_row=1) if photo_filename: await send_photo( context, # Передача context chat_id=update.effective_chat.id, photo_filename=photo_filename, caption=response_text, reply_markup=markup ) else: await update.message.reply_text( response_text, parse_mode="Markdown", reply_markup=markup ) else: response = "Неизвестный поворот. Пожалуйста, выберите доступный поворот или нажмите Назад." markup = create_keyboard(["Назад"], buttons_per_row=1) await update.message.reply_text(response, reply_markup=markup) return CHOOSE_DANGEROUS_TURN async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE): await update.message.reply_text("Используйте /start для начала.") async def cancel(update: Update, context: ContextTypes.DEFAULT_TYPE): await update.message.reply_text("Действие отменено.", reply_markup=ReplyKeyboardRemove()) return ConversationHandler.END def main(): TOKEN = "8166041667:AAHsjC8QSxSsqIt3zxGUm6YeGyEcp9QRr08" # Замените на ваш действительный токен # Убедитесь, что папки с фотографиями и видео существуют for directory in [PHOTOS_DIR, VIDEOS_DIR]: if not os.path.isdir(directory): os.makedirs(directory) logger.info(f"Создана папка для медиафайлов: {directory}") application = ApplicationBuilder().token(TOKEN).build() conv_handler = ConversationHandler( entry_points=[CommandHandler("start", start)], states={ CHOOSE_MAIN: [MessageHandler(filters.TEXT & ~filters.COMMAND, main_menu_choice)], CHOOSE_TRACK: [MessageHandler(filters.TEXT & ~filters.COMMAND, track_choice)], TRACK_MENU: [MessageHandler(filters.TEXT & ~filters.COMMAND, track_menu_choice)], CHOOSE_DANGEROUS_TURN: [MessageHandler(filters.TEXT & ~filters.COMMAND, dangerous_turn_choice)] }, fallbacks=[CommandHandler("cancel", cancel), CommandHandler("help", help_command)] ) application.add_handler(conv_handler) application.run_polling() if __name__ == '__main__': main()
Организация видеофайлов:
videos
."videos"
для каждой трассы.Проверка размеров видео:
Оптимизация отправки:
Добавление описаний к видео:
"videos"
для каждой трассы, используя словари вместо строк. Например:"videos": [ {"filename": "nurburgring/video1.mp4", "description": "Обзор трассы"}, {"filename": "nurburgring/video2.mp4", "description": "Гонки на трассе"} ]
track_menu_choice
для обработки этого формата.Обработка ошибок:
Следуя этим шагам, вы сможете расширить функциональность вашего бота, позволяя отправлять более двух видео для каждой трассы, что сделает взаимодействие с пользователями более информативным и увлекательным.
Entdecken von Ursachen
Zeitüberschreitung beim Video-Senden könnte an zu großer Dateigröße, Netzwerkproblen, falschem Pfad oder Überlastung durch viele Dateien gleichzeitig liegen.
Lösungen prüfen
Ich schaue mir die Videogröße und Pfadgenauigkeit an, setze erneute Versuche auf, optimiere den Versandprozess und empfehle Fehlerprotokolle.
Übermittlung von Videolinks
Versand via URL statt großer Dateien fehlerfrei. Verbesserung durch dateilinks, um Zeitüberschreitungen zu vermeiden.
Übertragung optimieren
Schritte zur Risikominimierung: Netzwerkordnungen prüfen, Codeanpassungen zur effizienten Handhabung von Videodateien und Streams zur Reduzierung von Speicherplatzverbrauch.
Prüfung der Dateigröße
Ich schaue mir die Dateigröße an und achte darauf, dass sie 50 MB nicht überschreitet, um die Anforderungen von Telegrams Bot-APIs zu erfüllen.
Gedanken für 5 Sekunden
Ошибка __main__ - ERROR - Ошибка при отправке видео: Timed out
указывает на то, что ваш бот не смог отправить видео из-за превышения времени ожидания (тайм-аута). Это может происходить по нескольким причинам, наиболее вероятные из которых связаны с размером видеофайла или проблемами с сетью. Давайте рассмотрим возможные причины и способы их устранения.
Размер видеофайла превышает лимиты Telegram:
Проблемы с сетью:
Некорректный путь к файлу или отсутствующий файл:
Перегрузка сервера или частые запросы:
Убедитесь, что размер каждого видеофайла не превышает 50 МБ. Вы можете проверить размер файла с помощью файлового менеджера или программно.
Пример проверки размера файла в Python:
import os def is_file_size_allowed(file_path, max_size_mb=50): if not os.path.isfile(file_path): return False size_bytes = os.path.getsize(file_path) size_mb = size_bytes / (1024 * 1024) return size_mb <= max_size_mb
Использование в вашем коде:
Перед отправкой видео добавьте проверку размера:
async def send_video(context, chat_id, video_filename, caption=None, reply_markup=None): # Полный путь к файлу file_path = os.path.join(VIDEOS_DIR, video_filename) # Проверка расширения файла _, ext = os.path.splitext(video_filename) if ext.lower() not in ALLOWED_VIDEO_EXTENSIONS: logger.warning(f"Недопустимое расширение видеофайла: {video_filename}") await context.bot.send_message( chat_id=chat_id, text="Невозможно отправить видео с недопустимым расширением.", reply_markup=reply_markup ) return # Проверка размера файла if not is_file_size_allowed(file_path): logger.warning(f"Видео превышает допустимый размер: {file_path}") await context.bot.send_message( chat_id=chat_id, text="Видео слишком большое для отправки. Пожалуйста, выберите другое видео или сократите размер.", reply_markup=reply_markup ) return if os.path.isfile(file_path): try: with open(file_path, 'rb') as video_file: if caption: await context.bot.send_video( chat_id=chat_id, video=video_file, caption=caption, parse_mode="Markdown", reply_markup=reply_markup ) else: await context.bot.send_video( chat_id=chat_id, video=video_file, reply_markup=reply_markup ) except Exception as e: logger.error(f"Ошибка при отправке видео: {e}") if caption: await context.bot.send_message( chat_id=chat_id, text=f"{caption}\n\n*Видео не доступно.*", parse_mode="Markdown", reply_markup=reply_markup ) else: logger.warning(f"Файл видео не найден: {file_path}") if caption: await context.bot.send_message( chat_id=chat_id, text=f"{caption}\n\n*Видео не доступно.*", parse_mode="Markdown", reply_markup=reply_markup )
Если видео превышает лимит в 50 МБ, вам необходимо уменьшить его размер. Это можно сделать с помощью различных инструментов для сжатия видео, например:
FFmpeg: мощный инструмент для обработки видео из командной строки.
Пример сжатия видео с помощью FFmpeg:
ffmpeg -i input.mp4 -vcodec libx264 -crf 28 output.mp4
Здесь -crf
определяет качество видео. Чем выше значение, тем меньше размер файла и ниже качество. Значение по умолчанию — 23. Вы можете экспериментировать с этим параметром.
Онлайн-сервисы: существуют веб-сайты, позволяющие сжимать видео без установки дополнительного ПО.
Проверьте, что все пути к видеофайлам указаны правильно и файлы действительно существуют по этим путям.
Пример проверки наличия файла:
if not os.path.isfile(file_path): logger.error(f"Видео файл не найден: {file_path}") # Дополнительная обработка, например, уведомление пользователя
Если вы отправляете несколько видео одновременно, это может привести к перегрузке. Попробуйте добавить небольшую задержку между отправками, чтобы избежать тайм-аутов.
Пример добавления задержки:
import asyncio async def track_menu_choice(update: Update, context: ContextTypes.DEFAULT_TYPE): # ... ваш существующий код ... elif text == "Видео трассы": videos = track_info.get("videos", []) if videos: response = f"**Видео трассы {current_track}:**" await update.message.reply_text(response, parse_mode="Markdown") for video_filename in videos: await send_video( context, chat_id=update.effective_chat.id, video_filename=video_filename, caption=None, reply_markup=track_menu_keyboard() ) await asyncio.sleep(1) # Добавляет паузу в 1 секунду между отправками else: await update.message.reply_text( "Видео для этой трассы недоступно.", parse_mode="Markdown", reply_markup=track_menu_keyboard() )
Если размер видео слишком большой и вы не можете его уменьшить, рассмотрите возможность размещения видео на внешнем сервисе (например, YouTube, Vimeo) и отправки ссылки пользователю.
Пример отправки ссылки на видео:
async def track_menu_choice(update: Update, context: ContextTypes.DEFAULT_TYPE): # ... ваш существующий код ... elif text == "Видео трассы": videos = track_info.get("videos", []) video_links = track_info.get("video_links", []) # Добавьте это поле в ваши данные трасс if video_links: response = f"**Видео трассы {current_track}:**\n" + "\n".join(video_links) await update.message.reply_text(response, parse_mode="Markdown", reply_markup=track_menu_keyboard()) else: await update.message.reply_text( "Видео для этой трассы недоступно.", parse_mode="Markdown", reply_markup=track_menu_keyboard() )
Измените структуру данных трасс:
tracks = { "Нюрбургринг-Нордшляйфе": { # ... другие поля ... "video_links": [ "https://www.youtube.com/watch?v=example1", "https://www.youtube.com/watch?v=example2" ], # ... остальные поля ... }, # ... другие трассы ... }
Убедитесь, что вы логируете достаточную информацию для диагностики проблем. Это поможет быстрее находить и устранять ошибки.
Пример улучшенного логирования:
except Exception as e: logger.error(f"Ошибка при отправке видео '{video_filename}' на чат {chat_id}: {e}") # Дополнительные действия, например, уведомление администратора
Если ваш бот размещен на удаленном сервере, убедитесь, что у него стабильное и достаточно быстрое интернет-соединение для загрузки больших файлов.
Убедитесь, что вы используете асинхронные функции и правильно обрабатываете задачи, чтобы избежать блокировки основного потока.
Ниже приведен обновленный фрагмент вашего кода с добавленной проверкой размера видео и обработкой ошибок:
import logging import time import os import asyncio from telegram import ( Update, ReplyKeyboardMarkup, ReplyKeyboardRemove, InputFile ) from telegram.ext import ( ApplicationBuilder, CommandHandler, MessageHandler, filters, ConversationHandler, ContextTypes ) # Настройка логирования logging.basicConfig( format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO ) logger = logging.getLogger(__name__) # ... ваш существующий код ... def is_file_size_allowed(file_path, max_size_mb=50): if not os.path.isfile(file_path): return False size_bytes = os.path.getsize(file_path) size_mb = size_bytes / (1024 * 1024) return size_mb <= max_size_mb async def send_video(context, chat_id, video_filename, caption=None, reply_markup=None): # Полный путь к файлу file_path = os.path.join(VIDEOS_DIR, video_filename) # Проверка расширения файла _, ext = os.path.splitext(video_filename) if ext.lower() not in ALLOWED_VIDEO_EXTENSIONS: logger.warning(f"Недопустимое расширение видеофайла: {video_filename}") await context.bot.send_message( chat_id=chat_id, text="Невозможно отправить видео с недопустимым расширением.", reply_markup=reply_markup ) return # Проверка размера файла if not is_file_size_allowed(file_path): logger.warning(f"Видео превышает допустимый размер: {file_path}") await context.bot.send_message( chat_id=chat_id, text="Видео слишком большое для отправки. Пожалуйста, выберите другое видео или сократите размер.", reply_markup=reply_markup ) return if os.path.isfile(file_path): try: with open(file_path, 'rb') as video_file: if caption: await context.bot.send_video( chat_id=chat_id, video=video_file, caption=caption, parse_mode="Markdown", reply_markup=reply_markup ) else: await context.bot.send_video( chat_id=chat_id, video=video_file, reply_markup=reply_markup ) except Exception as e: logger.error(f"Ошибка при отправке видео '{video_filename}' на чат {chat_id}: {e}") if caption: await context.bot.send_message( chat_id=chat_id, text=f"{caption}\n\n*Видео не доступно.*", parse_mode="Markdown", reply_markup=reply_markup ) else: logger.warning(f"Файл видео не найден: {file_path}") if caption: await context.bot.send_message( chat_id=chat_id, text=f"{caption}\n\n*Видео не доступно.*", parse_mode="Markdown", reply_markup=reply_markup ) async def track_menu_choice(update: Update, context: ContextTypes.DEFAULT_TYPE): # Проверка анти-спам if not await anti_spam(update, context): return TRACK_MENU text = update.message.text current_track = context.user_data.get("current_track") if text == "Назад": # Возвращение к списку трасс await update.message.reply_text("Выберите трассу:", reply_markup=tracks_keyboard()) return CHOOSE_TRACK if current_track not in tracks: await update.message.reply_text("Ошибка. Попробуйте снова.", reply_markup=main_menu_keyboard()) return CHOOSE_MAIN track_info = tracks[current_track] if text == "Лидеры": leaders = track_info["лидеры"] response = f"**Лидеры на трассе {current_track}:**\n{leaders}" await update.message.reply_text(response, parse_mode="Markdown", reply_markup=track_menu_keyboard()) elif text == "История о трассе": history = track_info["история"] response = f"**История трассы {current_track}:**\n{history}" # Получение имени файла фото истории photo_filename = track_info.get("фото_история") if photo_filename: # Отправка фото с текстом await send_photo( context, chat_id=update.effective_chat.id, photo_filename=photo_filename, caption=response, reply_markup=track_menu_keyboard() ) else: # Если фото не задано, отправляем только текст await update.message.reply_text(response, parse_mode="Markdown", reply_markup=track_menu_keyboard()) elif text == "Опасные повороты": await update.message.reply_text("Выберите опасный поворот:", reply_markup=dangerous_turns_keyboard(current_track)) return CHOOSE_DANGEROUS_TURN elif text == "Видео трассы": videos = track_info.get("videos", []) if videos: response = f"**Видео трассы {current_track}:**" await update.message.reply_text(response, parse_mode="Markdown") for video_filename in videos: await send_video( context, chat_id=update.effective_chat.id, video_filename=video_filename, caption=None, # Можно добавить описание видео, если необходимо reply_markup=track_menu_keyboard() ) await asyncio.sleep(1) # Пауза между отправками else: await update.message.reply_text( "Видео для этой трассы недоступно.", parse_mode="Markdown", reply_markup=track_menu_keyboard() ) else: response = "Неизвестная команда. Попробуйте снова." await update.message.reply_text(response, parse_mode="Markdown", reply_markup=track_menu_keyboard()) return TRACK_MENU
Мониторинг и логирование:
Использование внешних хостингов для видео:
Обработка больших объемов данных:
Тестирование с разными видео:
Обновление библиотек и зависимостей:
python-telegram-bot
и других зависимостей. Обновления могут содержать исправления ошибок и улучшения производительности.Ошибка тайм-аута при отправке видео через Telegram бота чаще всего связана с превышением лимита размера файла или проблемами с сетью. Следуя приведенным выше рекомендациям, вы сможете диагностировать и устранить проблему. Важно также обеспечить хорошую обработку ошибок и информировать пользователей о возможных проблемах, чтобы улучшить взаимодействие с ботом.
Если после выполнения всех шагов проблема сохраняется, пожалуйста, предоставьте дополнительную информацию, такую как:
Это позволит более точно определить причину проблемы и найти оптимальное решение.