fix: allow_unplayable_formats removed, reply_video, auto-select 480p, remove file sizes
This commit is contained in:
parent
839cd57f6f
commit
59b1c54668
2 changed files with 74 additions and 17 deletions
85
bot.py
85
bot.py
|
|
@ -117,7 +117,7 @@ TEXTS = {
|
||||||
'error_file_too_large': "❌ Видео слишком большое ({size_mb:.1f} МБ, max = 50)",
|
'error_file_too_large': "❌ Видео слишком большое ({size_mb:.1f} МБ, max = 50)",
|
||||||
'queue_position': "🕐 Ваше видео #{position} в очереди\nВаш запрос очень важен для нас!",
|
'queue_position': "🕐 Ваше видео #{position} в очереди\nВаш запрос очень важен для нас!",
|
||||||
'queue_first': "⬇️ Скачиваю видео...",
|
'queue_first': "⬇️ Скачиваю видео...",
|
||||||
'select_quality': "Выберите качество видео:",
|
'select_quality': "Выберите качество видео:\n(через 10 сек — автоскачивание лучшего)",
|
||||||
'quality_cancelled': "❌ Выбор отменён",
|
'quality_cancelled': "❌ Выбор отменён",
|
||||||
'fetching_formats': "🔍 Получаю доступные форматы...",
|
'fetching_formats': "🔍 Получаю доступные форматы...",
|
||||||
},
|
},
|
||||||
|
|
@ -174,7 +174,7 @@ TEXTS = {
|
||||||
'error_file_too_large': "❌ Video is too large ({size_mb:.1f} MB, max = 50)",
|
'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_position': "🕐 Your video is #{position} in queue\nYour request is very important to us!",
|
||||||
'queue_first': "⬇️ Downloading video...",
|
'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",
|
'quality_cancelled': "❌ Cancelled",
|
||||||
'fetching_formats': "🔍 Fetching available formats...",
|
'fetching_formats': "🔍 Fetching available formats...",
|
||||||
}
|
}
|
||||||
|
|
@ -567,11 +567,12 @@ async def process_queue_item(item: QueueItem):
|
||||||
# Определяем имя файла для отправки
|
# Определяем имя файла для отправки
|
||||||
video_filename = Path(video_path).name
|
video_filename = Path(video_path).name
|
||||||
|
|
||||||
# Отправляем как документ — Telegram сам определит тип по расширению
|
# Отправляем как видео со streaming — встроенный плеер Telegram
|
||||||
await item.original_message.reply_document(
|
await item.original_message.reply_video(
|
||||||
document=video_file,
|
video=video_file,
|
||||||
filename=video_filename,
|
filename=video_filename,
|
||||||
caption=caption,
|
caption=caption,
|
||||||
|
supports_streaming=True,
|
||||||
read_timeout=600,
|
read_timeout=600,
|
||||||
write_timeout=600,
|
write_timeout=600,
|
||||||
connect_timeout=60,
|
connect_timeout=60,
|
||||||
|
|
@ -1021,12 +1022,7 @@ async def show_quality_selection(status_message: Message, formats: list[dict], l
|
||||||
"""
|
"""
|
||||||
keyboard = []
|
keyboard = []
|
||||||
for idx, fmt in enumerate(formats):
|
for idx, fmt in enumerate(formats):
|
||||||
label = fmt.get('label', fmt.get('quality', 'Unknown'))
|
button_text = 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
|
|
||||||
keyboard.append([InlineKeyboardButton(
|
keyboard.append([InlineKeyboardButton(
|
||||||
text=button_text,
|
text=button_text,
|
||||||
callback_data=f"quality:{idx}"
|
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)
|
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:
|
if not data:
|
||||||
await query.edit_message_text("Session expired, please send the link again")
|
await query.edit_message_text("Session expired, please send the link again")
|
||||||
return
|
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:
|
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)
|
source = detect_video_source(url)
|
||||||
|
|
@ -1186,6 +1246,9 @@ async def handle_message(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
'formats_list': formats, # для lookup по индексу в callback
|
'formats_list': formats, # для lookup по индексу в callback
|
||||||
}
|
}
|
||||||
await show_quality_selection(status_message, formats, locale)
|
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
|
return
|
||||||
# Если не удалось получить форматы, скачиваем как обычно (без выбора качества)
|
# Если не удалось получить форматы, скачиваем как обычно (без выбора качества)
|
||||||
await status_message.edit_text(get_text(locale, 'processing'))
|
await status_message.edit_text(get_text(locale, 'processing'))
|
||||||
|
|
|
||||||
|
|
@ -334,12 +334,7 @@ def download_youtube_video(url: str, max_retries: int = 3, format_id: str | None
|
||||||
ydl_opts_download.update({
|
ydl_opts_download.update({
|
||||||
'format': format_option,
|
'format': format_option,
|
||||||
'outtmpl': _safe_filename(video_title),
|
'outtmpl': _safe_filename(video_title),
|
||||||
# fragment_retries — для DASH форматов (видео без аудио),
|
|
||||||
# YouTube может разрывать фрагменты; увеличиваем retries
|
|
||||||
'fragment_retries': 3,
|
'fragment_retries': 3,
|
||||||
# allow_unplayable_formats — позволяет скачивать форматы,
|
|
||||||
# которые YouTube помечает как "недоступные" для сторонних клиентов
|
|
||||||
'allow_unplayable_formats': True,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
use_cookies_this_attempt = cookies_valid
|
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,
|
'format': format_option,
|
||||||
'outtmpl': _safe_filename(video_title),
|
'outtmpl': _safe_filename(video_title),
|
||||||
'fragment_retries': 3,
|
'fragment_retries': 3,
|
||||||
'allow_unplayable_formats': True,
|
|
||||||
})
|
})
|
||||||
try:
|
try:
|
||||||
with yt_dlp.YoutubeDL(ydl_opts_download_no_cookies) as ydl:
|
with yt_dlp.YoutubeDL(ydl_opts_download_no_cookies) as ydl:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue