fix: allow_unplayable_formats removed, reply_video, auto-select 480p, remove file sizes

This commit is contained in:
vrubelroman 2026-05-03 02:39:27 +03:00
parent 839cd57f6f
commit 59b1c54668
2 changed files with 74 additions and 17 deletions

85
bot.py
View file

@ -117,7 +117,7 @@ TEXTS = {
'error_file_too_large': "❌ Видео слишком большое ({size_mb:.1f} МБ, max = 50)",
'queue_position': "🕐 Ваше видео #{position} в очереди\nВаш запрос очень важен для нас!",
'queue_first': "⬇️ Скачиваю видео...",
'select_quality': "Выберите качество видео:",
'select_quality': "Выберите качество видео:\n(через 10 сек — автоскачивание лучшего)",
'quality_cancelled': "❌ Выбор отменён",
'fetching_formats': "🔍 Получаю доступные форматы...",
},
@ -174,7 +174,7 @@ TEXTS = {
'error_file_too_large': "❌ Video is too large ({size_mb:.1f} MB, max = 50)",
'queue_position': "🕐 Your video is #{position} in queue\nYour request is very important to us!",
'queue_first': "⬇️ Downloading video...",
'select_quality': "Select video quality:",
'select_quality': "Select video quality:\n(10 sec auto-download of best quality)",
'quality_cancelled': "❌ Cancelled",
'fetching_formats': "🔍 Fetching available formats...",
}
@ -567,11 +567,12 @@ async def process_queue_item(item: QueueItem):
# Определяем имя файла для отправки
video_filename = Path(video_path).name
# Отправляем как документ — Telegram сам определит тип по расширению
await item.original_message.reply_document(
document=video_file,
# Отправляем как видео со streaming — встроенный плеер Telegram
await item.original_message.reply_video(
video=video_file,
filename=video_filename,
caption=caption,
supports_streaming=True,
read_timeout=600,
write_timeout=600,
connect_timeout=60,
@ -1021,12 +1022,7 @@ async def show_quality_selection(status_message: Message, formats: list[dict], l
"""
keyboard = []
for idx, fmt in enumerate(formats):
label = fmt.get('label', fmt.get('quality', 'Unknown'))
filesize = fmt.get('filesize_mb')
if filesize:
button_text = f"{label} ({filesize:.0f} MB)"
else:
button_text = label
button_text = fmt.get('label', fmt.get('quality', 'Unknown'))
keyboard.append([InlineKeyboardButton(
text=button_text,
callback_data=f"quality:{idx}"
@ -1055,6 +1051,10 @@ async def handle_format_selection(update: Update, context: ContextTypes.DEFAULT_
# Получаем сохраненные данные
data = context.user_data.pop(f'quality_{chat_id}', None)
# Отменяем авто-выбор качества
auto_task = context.user_data.pop(f'quality_auto_{chat_id}', None)
if auto_task:
auto_task.cancel()
if not data:
await query.edit_message_text("Session expired, please send the link again")
return
@ -1104,6 +1104,66 @@ async def handle_format_selection(update: Update, context: ContextTypes.DEFAULT_
)
async def _auto_select_after_delay(context: ContextTypes.DEFAULT_TYPE, chat_id: int, delay: int = 10):
"""Автовыбор лучшего качества через delay секунд, если пользователь не выбрал"""
try:
await asyncio.sleep(delay)
except asyncio.CancelledError:
return # пользователь выбрал вручную
data = context.user_data.pop(f'quality_{chat_id}', None)
if not data:
return # уже обработано
context.user_data.pop(f'quality_auto_{chat_id}', None) # чистим
formats_list = data.get('formats_list', [])
if not formats_list:
return
locale = data['locale']
status_message = data['status_message']
# Автовыбор: ищем 480p или ближайшее ниже
preferred_qualities = ['480p', '360p', '240p', '144p']
selected = None
for pq in preferred_qualities:
for fmt in formats_list:
if fmt.get('quality', '') == pq:
selected = fmt
break
if selected:
break
if not selected:
selected = formats_list[0] # fallback на лучшее
format_id = selected.get('format_id', '')
quality_label = selected.get('quality', selected.get('label', 'best'))
logger.info(f"Автовыбор качества для chat_id={chat_id}: {quality_label}")
await status_message.edit_text(get_text(locale, 'processing'))
item = QueueItem(
original_message=data['original_message'],
status_message=status_message,
url=data['url'],
chat_id=chat_id,
chat_type=data['chat_type'],
locale=locale,
format_id=format_id
)
position = await add_to_queue(item)
if position == 1:
await status_message.edit_text(get_text(locale, 'queue_first'))
else:
await status_message.edit_text(
get_text(locale, 'queue_position', position=position)
)
async def download_video(url: str, chat_id: int, locale: str, max_retries: int = 3, format_id: str | None = None) -> str:
"""Главная функция скачивания - вызывает нужную функцию в зависимости от источника"""
source = detect_video_source(url)
@ -1186,6 +1246,9 @@ async def handle_message(update: Update, context: ContextTypes.DEFAULT_TYPE):
'formats_list': formats, # для lookup по индексу в callback
}
await show_quality_selection(status_message, formats, locale)
# Автовыбор лучшего качества через 10 сек
auto_task = asyncio.create_task(_auto_select_after_delay(context, chat_id, 10))
context.user_data[f'quality_auto_{chat_id}'] = auto_task
return
# Если не удалось получить форматы, скачиваем как обычно (без выбора качества)
await status_message.edit_text(get_text(locale, 'processing'))

View file

@ -334,12 +334,7 @@ def download_youtube_video(url: str, max_retries: int = 3, format_id: str | None
ydl_opts_download.update({
'format': format_option,
'outtmpl': _safe_filename(video_title),
# fragment_retries — для DASH форматов (видео без аудио),
# YouTube может разрывать фрагменты; увеличиваем retries
'fragment_retries': 3,
# allow_unplayable_formats — позволяет скачивать форматы,
# которые YouTube помечает как "недоступные" для сторонних клиентов
'allow_unplayable_formats': True,
})
use_cookies_this_attempt = cookies_valid
@ -392,7 +387,6 @@ def download_youtube_video(url: str, max_retries: int = 3, format_id: str | None
'format': format_option,
'outtmpl': _safe_filename(video_title),
'fragment_retries': 3,
'allow_unplayable_formats': True,
})
try:
with yt_dlp.YoutubeDL(ydl_opts_download_no_cookies) as ydl: