fix: audio-only format, m4a/mp3 support, source URL in caption

This commit is contained in:
vrubelroman 2026-05-03 01:56:31 +03:00
parent 053f6c8afc
commit 839cd57f6f
3 changed files with 50 additions and 38 deletions

View file

@ -295,12 +295,20 @@ def download_youtube_video(url: str, max_retries: int = 3, format_id: str | None
combined_fallback = ['best[ext=mp4]/best', 'best']
requested_height = None # высота, запрошенная пользователем
is_audio_only = False
if format_id:
is_specific_code = not ('[' in format_id or ']' in format_id)
# Если format_id запрашивает только аудио, не подмешиваем видео-форматы
# и не валидируем наличие видео-потока
first_selector = format_id.split('/')[0]
is_audio_only = 'bestaudio' in first_selector
requested_height = _extract_height_from_format_id(format_id)
if requested_height is not None:
if is_audio_only:
format_options = [format_id]
logger.info(f"[DOWNLOAD] Аудио-only режим, format_id: {format_id}")
elif requested_height is not None:
# Конкретный format_id (из /formats) ставим ПЕРВЫМ —
# он точно указывает выбранные пользователем format codes.
# Height-ограниченный селектор идёт как fallback
@ -354,17 +362,16 @@ def download_youtube_video(url: str, max_retries: int = 3, format_id: str | None
logger.info(f"[DOWNLOAD] Попытка {attempt + 1}: успешно скачано с форматом {format_option}")
# Проверяем, что файл содержит видео-поток, а не только аудио
# (yt-dlp c allow_unplayable_formats может скачать av01 формат
# и отказаться от мержа, вернув только аудио)
downloaded = _find_latest_downloaded()
if downloaded and not _file_has_video_stream(downloaded):
logger.warning(f"[DOWNLOAD] Файл {downloaded.name} не содержит видео-потока (только аудио). Удаляем и пробуем следующий формат...")
try:
downloaded.unlink()
except Exception:
pass
continue
# Проверяем, что файл содержит видео-поток (только для видео-форматов)
if not is_audio_only:
downloaded = _find_latest_downloaded()
if downloaded and not _file_has_video_stream(downloaded):
logger.warning(f"[DOWNLOAD] Файл {downloaded.name} не содержит видео-потока (только аудио). Удаляем и пробуем следующий формат...")
try:
downloaded.unlink()
except Exception:
pass
continue
download_success = True
break
@ -392,14 +399,15 @@ def download_youtube_video(url: str, max_retries: int = 3, format_id: str | None
ydl.download([url])
logger.info(f"[DOWNLOAD] Попытка {attempt + 1}: успешно скачано без cookies")
downloaded = _find_latest_downloaded()
if downloaded and not _file_has_video_stream(downloaded):
logger.warning(f"[DOWNLOAD] Файл {downloaded.name} без видео-потока. Удаляем и пробуем следующий формат...")
try:
downloaded.unlink()
except Exception:
pass
continue
if not is_audio_only:
downloaded = _find_latest_downloaded()
if downloaded and not _file_has_video_stream(downloaded):
logger.warning(f"[DOWNLOAD] Файл {downloaded.name} без видео-потока. Удаляем и пробуем следующий формат...")
try:
downloaded.unlink()
except Exception:
pass
continue
download_success = True
cookies_valid = False # Отключаем cookies для следующих попыток
@ -668,10 +676,10 @@ def get_youtube_formats(url: str) -> list[dict]:
'filesize_mb': round(total_size / 1024 / 1024, 1) if total_size else None,
})
# Добавляем аудиодорожку
# Добавляем аудиодорожку (M4A в приоритете — Telegram поддерживает только MP3/M4A для reply_audio)
if best_audio_info['size']:
result.append({
'format_id': 'bestaudio/best',
'format_id': 'bestaudio[ext=m4a]/bestaudio[ext=mp3]/bestaudio/best',
'label': f"Audio only ({best_audio_info['ext']})",
'quality': 'audio',
'ext': best_audio_info['ext'],
@ -871,15 +879,19 @@ def download_stream():
# Безопасное имя файла без кириллицы для заголовка
safe_filename = video_path.name.encode('ascii', 'ignore').decode('ascii') or 'youtube_video.mp4'
if not safe_filename.endswith(('.mp4', '.webm', '.mkv')):
if not safe_filename.endswith(('.mp4', '.webm', '.mkv', '.m4a', '.mp3')):
safe_filename = 'youtube_video.mp4'
# Определяем content-type
content_type = 'video/mp4'
if video_path.suffix == '.webm':
content_type = 'video/webm'
elif video_path.suffix == '.mkv':
content_type = 'video/x-matroska'
# Определяем content-type по реальному расширению файла
ext = video_path.suffix.lower()
content_type_map = {
'.webm': 'video/webm',
'.mkv': 'video/x-matroska',
'.mp4': 'video/mp4',
'.m4a': 'audio/mp4',
'.mp3': 'audio/mpeg',
}
content_type = content_type_map.get(ext, 'video/mp4')
logger.info(f"[REQUEST {request_id}] Отправляем файл: {safe_filename}, Content-Type: {content_type}, размер: {len(video_data)} байт")