fix: remove client-side format cache, prefer first audio track over largest

This commit is contained in:
vrubelroman 2026-05-03 17:43:24 +03:00
parent e2ead9db52
commit 60a0373d7f
3 changed files with 90 additions and 81 deletions

27
bot.py
View file

@ -22,10 +22,6 @@ HTTP_TIMEOUT = httpx.Timeout(connect=None, read=None, write=None, pool=None)
# Таймаут для запроса форматов (не такой критичный, но не должен висеть вечно) # Таймаут для запроса форматов (не такой критичный, но не должен висеть вечно)
FORMATS_TIMEOUT = httpx.Timeout(connect=15, read=30, write=15, pool=15) FORMATS_TIMEOUT = httpx.Timeout(connect=15, read=30, write=15, pool=15)
# Клиентский кэш форматов: {normalized_url: (timestamp, formats)}
_formats_cache: dict[str, tuple[float, list[dict]]] = {}
_FORMATS_CACHE_TTL = 30 * 60 # 30 минут
# Настройка логирования # Настройка логирования
logging.basicConfig( logging.basicConfig(
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
@ -971,29 +967,10 @@ async def download_tiktok_video(url: str, chat_id: int, max_retries: int = 3) ->
# ============================================================================ # ============================================================================
def _normalize_youtube_url_for_cache(url: str) -> str:
"""Нормализует URL для кэша: оставляет только video ID"""
import re
m = re.search(r'(youtu\.be/|youtube\.com/watch\?v=)([a-zA-Z0-9_-]{11})', url)
if m:
return f"https://www.youtube.com/watch?v={m.group(2)}"
return url
async def get_formats_from_service(url: str) -> list[dict] | None: async def get_formats_from_service(url: str) -> list[dict] | None:
"""Получает список доступных форматов для YouTube URL через сервис youtube-downloader""" """Получает список доступных форматов для YouTube URL через сервис youtube-downloader"""
logger.info(f"Получение форматов для YouTube: {url}") logger.info(f"Получение форматов для YouTube: {url}")
cache_key = _normalize_youtube_url_for_cache(url)
now = time.time()
if cache_key in _formats_cache:
cached_time, cached_formats = _formats_cache[cache_key]
if now - cached_time < _FORMATS_CACHE_TTL:
logger.info(f"Форматы взяты из кэша ({len(cached_formats)} шт., возраст {now - cached_time:.0f}с)")
return cached_formats
del _formats_cache[cache_key]
try: try:
async with httpx.AsyncClient(timeout=FORMATS_TIMEOUT) as client: async with httpx.AsyncClient(timeout=FORMATS_TIMEOUT) as client:
response = await client.post( response = await client.post(
@ -1003,9 +980,7 @@ async def get_formats_from_service(url: str) -> list[dict] | None:
) )
if response.status_code == 200: if response.status_code == 200:
data = response.json() data = response.json()
formats = data.get('formats', []) return data.get('formats', [])
_formats_cache[cache_key] = (time.time(), formats)
return formats
logger.warning(f"Не удалось получить форматы: {response.status_code}") logger.warning(f"Не удалось получить форматы: {response.status_code}")
return None return None
except Exception as e: except Exception as e:

View file

@ -150,8 +150,6 @@ def _make_base_ydl_opts(user_agent: str, cookies_file_path: Path | None = None)
'user_agent': user_agent, 'user_agent': user_agent,
'socket_timeout': 60, 'socket_timeout': 60,
'extractor_retries': 3, 'extractor_retries': 3,
# Предпочитаем русскую озвучку — YouTube иногда подсовывает авто-дубляж на английском
'format_sort': ['lang:ru'],
'http_headers': { 'http_headers': {
'User-Agent': user_agent, 'User-Agent': user_agent,
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
@ -175,6 +173,68 @@ def _make_base_ydl_opts(user_agent: str, cookies_file_path: Path | None = None)
return opts return opts
def _find_video_file() -> Path | None:
"""Находит видеофайл среди загрузок. Если видео+аудио раздельные — мержит ffmpeg.
Возвращает путь к готовому видеофайлу или None если видео нет."""
files = list(DOWNLOADS_DIR.glob('*'))
if not files:
return None
files.sort(key=lambda x: x.stat().st_mtime, reverse=True)
video_file = None
audio_file = None
for f in files:
if f.suffix in ('.part', '.ytdl'):
continue
if _file_has_video_stream(f):
has_audio = _file_has_audio_stream(f)
if has_audio:
return f # уже смерженный или combined — сразу возвращаем
if video_file is None:
video_file = f
elif not audio_file and _file_has_audio_stream(f):
audio_file = f
if video_file is None:
return None
if audio_file:
# Мержим видео + аудио через ffmpeg
import subprocess
merged = DOWNLOADS_DIR / f"{video_file.stem}_merged{video_file.suffix}"
logger.info(f"[MERGE] Мержим {video_file.name} + {audio_file.name}{merged.name}")
result = subprocess.run(
['ffmpeg', '-y', '-i', str(video_file), '-i', str(audio_file),
'-c', 'copy', '-map', '0:v:0', '-map', '1:a:0', str(merged)],
capture_output=True, text=True, timeout=120
)
if result.returncode != 0:
logger.error(f"[MERGE] Ошибка ffmpeg: {result.stderr[-300:]}")
return video_file # fallback — только видео
video_file.unlink(missing_ok=True)
audio_file.unlink(missing_ok=True)
return merged
return video_file
def _file_has_audio_stream(filepath: Path) -> bool:
"""Проверяет через ffprobe, содержит ли файл аудио-поток."""
import subprocess
try:
result = subprocess.run(
['ffprobe', '-v', 'error', '-select_streams', 'a:0',
'-show_entries', 'stream=codec_type', '-of', 'csv=p=0',
str(filepath)],
capture_output=True, text=True, timeout=15
)
return result.stdout.strip() == 'audio'
except Exception:
return False
def _find_latest_downloaded() -> Path | None: def _find_latest_downloaded() -> Path | None:
"""Возвращает самый свежий файл в папке загрузок.""" """Возвращает самый свежий файл в папке загрузок."""
files = list(DOWNLOADS_DIR.glob('*')) files = list(DOWNLOADS_DIR.glob('*'))
@ -287,9 +347,9 @@ def download_youtube_video(url: str, max_retries: int = 3, format_id: str | None
# Это важно, т.к. format_id из get_youtube_formats() может не совпадать # Это важно, т.к. format_id из get_youtube_formats() может не совпадать
# с format_id при повторном extract_info() в download_youtube_video(). # с format_id при повторном extract_info() в download_youtube_video().
default_format_options = [ default_format_options = [
'bestvideo[ext=mp4]+bestaudio[language=ru][ext=m4a]/bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best', 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best',
'best[ext=mp4]/best', 'best[ext=mp4]/best',
'bestvideo+bestaudio[language=ru]/bestvideo+bestaudio/best', 'bestvideo+bestaudio/best',
'best', 'best',
] ]
@ -337,7 +397,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,
# Явно включаем фикс растянутого/сплющенного aspect ratio
'postprocessors': [{'key': 'FFmpegFixupStretched'}], 'postprocessors': [{'key': 'FFmpegFixupStretched'}],
}) })
@ -360,18 +419,6 @@ def download_youtube_video(url: str, max_retries: int = 3, format_id: str | None
logger.info(f"[DOWNLOAD] Попытка {attempt + 1}: реально скачан формат: id={actual_format_id}, height={actual_height}, ext={actual_ext}, size={actual_filesize}") logger.info(f"[DOWNLOAD] Попытка {attempt + 1}: реально скачан формат: id={actual_format_id}, height={actual_height}, ext={actual_ext}, size={actual_filesize}")
logger.info(f"[DOWNLOAD] Попытка {attempt + 1}: успешно скачано с форматом {format_option}") logger.info(f"[DOWNLOAD] Попытка {attempt + 1}: успешно скачано с форматом {format_option}")
# Проверяем, что файл содержит видео-поток (только для видео-форматов)
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 download_success = True
break break
except Exception as download_error: except Exception as download_error:
@ -396,17 +443,6 @@ def download_youtube_video(url: str, max_retries: int = 3, format_id: str | None
with yt_dlp.YoutubeDL(ydl_opts_download_no_cookies) as ydl: with yt_dlp.YoutubeDL(ydl_opts_download_no_cookies) as ydl:
ydl.download([url]) ydl.download([url])
logger.info(f"[DOWNLOAD] Попытка {attempt + 1}: успешно скачано без cookies") logger.info(f"[DOWNLOAD] Попытка {attempt + 1}: успешно скачано без cookies")
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 download_success = True
cookies_valid = False # Отключаем cookies для следующих попыток cookies_valid = False # Отключаем cookies для следующих попыток
break break
@ -600,10 +636,9 @@ def get_youtube_formats(url: str) -> list[dict]:
if vcodec != 'none' and height > 0: if vcodec != 'none' and height > 0:
available_heights.add(height) available_heights.add(height)
if vcodec == 'none' and acodec != 'none': if vcodec == 'none' and acodec != 'none' and best_audio_info['format_id'] is None:
fs = _get_filesize(f) # Берём первый попавшийся аудиоформат — yt-dlp отдаёт оригинал первым
if fs > best_audio_info['size']: best_audio_info = {'size': _get_filesize(f), 'ext': f.get('ext', 'm4a'), 'format_id': format_id}
best_audio_info = {'size': fs, 'ext': f.get('ext', 'm4a'), 'format_id': format_id}
logger.info(f"[FORMATS] Доступные разрешения: {sorted(available_heights)}") logger.info(f"[FORMATS] Доступные разрешения: {sorted(available_heights)}")
logger.info(f"[FORMATS] Лучший аудиопоток: {best_audio_info['size']} bytes, {best_audio_info['ext']}, format_id={best_audio_info['format_id']}") logger.info(f"[FORMATS] Лучший аудиопоток: {best_audio_info['size']} bytes, {best_audio_info['ext']}, format_id={best_audio_info['format_id']}")
@ -620,16 +655,27 @@ def get_youtube_formats(url: str) -> list[dict]:
best_video = None best_video = None
best_video_height = 0 best_video_height = 0
is_best_dash = False
for f in formats: for f in formats:
vcodec = f.get('vcodec', 'none') vcodec = f.get('vcodec', 'none')
height = _parse_height(f) height = _parse_height(f)
if vcodec == 'none' or height <= 0: if vcodec == 'none' or height <= 0 or height > max_height:
continue continue
if height <= max_height and height > best_video_height:
is_dash = (f.get('acodec', 'none') == 'none')
pick = False
if height > best_video_height:
pick = True
elif height == best_video_height and is_dash and not is_best_dash:
pick = True
if pick:
best_video = f best_video = f
best_video_height = height best_video_height = height
is_best_dash = is_dash
if not best_video: if not best_video:
continue continue
@ -654,28 +700,16 @@ def get_youtube_formats(url: str) -> list[dict]:
logger.info(f"[FORMATS] {display_label} (height={best_video_height}): video_size={video_size}, has_audio={has_audio}, total={total_size}, format_id={video_format_id}") logger.info(f"[FORMATS] {display_label} (height={best_video_height}): video_size={video_size}, has_audio={has_audio}, total={total_size}, format_id={video_format_id}")
# format_selector: предпочитаем русскую озвучку (YouTube Shorts часто авто-дублирует)
if has_audio: if has_audio:
format_selector = ( format_selector = f"{video_format_id}/best[height<={best_video_height}]/best"
f"{video_format_id}/"
f"bestvideo[height<={best_video_height}]+bestaudio[language=ru]/"
f"bestvideo[height<={best_video_height}]+bestaudio/"
f"best[height<={best_video_height}]"
)
elif best_audio_info['format_id']: elif best_audio_info['format_id']:
format_selector = ( format_selector = (
f"{video_format_id}+{best_audio_info['format_id']}/" f"{video_format_id}+{best_audio_info['format_id']}/"
f"bestvideo[height<={best_video_height}]+bestaudio[language=ru]/"
f"bestvideo[height<={best_video_height}]+bestaudio/" f"bestvideo[height<={best_video_height}]+bestaudio/"
f"best[height<={best_video_height}]" f"best[height<={best_video_height}]"
) )
else: else:
format_selector = ( format_selector = f"{video_format_id}+bestaudio/best[height<={best_video_height}]/best"
f"{video_format_id}+bestaudio[language=ru]/"
f"bestvideo[height<={best_video_height}]+bestaudio[language=ru]/"
f"bestvideo[height<={best_video_height}]+bestaudio/"
f"best[height<={best_video_height}]"
)
result.append({ result.append({
'format_id': format_selector, 'format_id': format_selector,
@ -685,10 +719,10 @@ def get_youtube_formats(url: str) -> list[dict]:
'filesize_mb': round(total_size / 1024 / 1024, 1) if total_size else None, 'filesize_mb': round(total_size / 1024 / 1024, 1) if total_size else None,
}) })
# Добавляем аудиодорожку (M4A в приоритете — Telegram поддерживает только MP3/M4A для reply_audio) # Добавляем аудиодорожку
if best_audio_info['size']: if best_audio_info['size']:
result.append({ result.append({
'format_id': 'bestaudio[language=ru][ext=m4a]/bestaudio[language=ru][ext=mp3]/bestaudio[language=ru]/bestaudio/best', 'format_id': 'bestaudio/best',
'label': f"Audio only ({best_audio_info['ext']})", 'label': f"Audio only ({best_audio_info['ext']})",
'quality': 'audio', 'quality': 'audio',
'ext': best_audio_info['ext'], 'ext': best_audio_info['ext'],

View file

@ -27,14 +27,14 @@
.youtube.com TRUE / FALSE 1776287761000 ST-yve142 session_logininfo=AFmmF2swRQIgZPfEOdmfC8u5sHvE1aOagKEvp5rRUe5hRUeLiYmxLDwCIQDqFIR59yZ_aBb5BLYSpK7LGdJ6YZqnh32USuOyMZTC5g%3AQUQ3MjNmd0ZzX01fTjViQ2kzMDJEWG5Ed09zMGF1TlhJcm81YWt3WWdKS2RCZkY3Z2NmMVhudUF4MFVZdFlHd0YtaEU0R3VHNHQ3VmFSZHdfR1RIcnBJNUtXeWhKWVVScE1ZcXNJdzRfdkFGVi1lZzY2dWxCcVVGZ0FPSjNzVmFjTVg1YTBYS0xBajEzU1REM3dnbUc5U3E3NHVtLVRLLXRn .youtube.com TRUE / FALSE 1776287761000 ST-yve142 session_logininfo=AFmmF2swRQIgZPfEOdmfC8u5sHvE1aOagKEvp5rRUe5hRUeLiYmxLDwCIQDqFIR59yZ_aBb5BLYSpK7LGdJ6YZqnh32USuOyMZTC5g%3AQUQ3MjNmd0ZzX01fTjViQ2kzMDJEWG5Ed09zMGF1TlhJcm81YWt3WWdKS2RCZkY3Z2NmMVhudUF4MFVZdFlHd0YtaEU0R3VHNHQ3VmFSZHdfR1RIcnBJNUtXeWhKWVVScE1ZcXNJdzRfdkFGVi1lZzY2dWxCcVVGZ0FPSjNzVmFjTVg1YTBYS0xBajEzU1REM3dnbUc5U3E3NHVtLVRLLXRn
.youtube.com TRUE / TRUE 1807824503515 __Secure-1PSIDTS sidts-CjUBWhotCSAL5EMsgNfc0JD8UVvU5vyCYbx9ZFc0Nnry9Qc7YHRzl6a7o8Zm6bPYHoFyKALKlBAA .youtube.com TRUE / TRUE 1807824503515 __Secure-1PSIDTS sidts-CjUBWhotCSAL5EMsgNfc0JD8UVvU5vyCYbx9ZFc0Nnry9Qc7YHRzl6a7o8Zm6bPYHoFyKALKlBAA
.youtube.com TRUE / TRUE 1807824503517 __Secure-3PSIDTS sidts-CjUBWhotCSAL5EMsgNfc0JD8UVvU5vyCYbx9ZFc0Nnry9Qc7YHRzl6a7o8Zm6bPYHoFyKALKlBAA .youtube.com TRUE / TRUE 1807824503517 __Secure-3PSIDTS sidts-CjUBWhotCSAL5EMsgNfc0JD8UVvU5vyCYbx9ZFc0Nnry9Qc7YHRzl6a7o8Zm6bPYHoFyKALKlBAA
.youtube.com TRUE / TRUE 1793319671 VISITOR_INFO1_LIVE vFr43YvHJaE .youtube.com TRUE / TRUE 1793371363 VISITOR_INFO1_LIVE vFr43YvHJaE
.youtube.com TRUE / TRUE 1793319671 VISITOR_PRIVACY_METADATA CgJSVRIEGgAgMg%3D%3D .youtube.com TRUE / TRUE 1793371363 VISITOR_PRIVACY_METADATA CgJSVRIEGgAgMg%3D%3D
.youtube.com TRUE / FALSE 1776288519000 ST-tladcw session_logininfo=AFmmF2swRQIgZPfEOdmfC8u5sHvE1aOagKEvp5rRUe5hRUeLiYmxLDwCIQDqFIR59yZ_aBb5BLYSpK7LGdJ6YZqnh32USuOyMZTC5g%3AQUQ3MjNmd0ZzX01fTjViQ2kzMDJEWG5Ed09zMGF1TlhJcm81YWt3WWdKS2RCZkY3Z2NmMVhudUF4MFVZdFlHd0YtaEU0R3VHNHQ3VmFSZHdfR1RIcnBJNUtXeWhKWVVScE1ZcXNJdzRfdkFGVi1lZzY2dWxCcVVGZ0FPSjNzVmFjTVg1YTBYS0xBajEzU1REM3dnbUc5U3E3NHVtLVRLLXRn .youtube.com TRUE / FALSE 1776288519000 ST-tladcw session_logininfo=AFmmF2swRQIgZPfEOdmfC8u5sHvE1aOagKEvp5rRUe5hRUeLiYmxLDwCIQDqFIR59yZ_aBb5BLYSpK7LGdJ6YZqnh32USuOyMZTC5g%3AQUQ3MjNmd0ZzX01fTjViQ2kzMDJEWG5Ed09zMGF1TlhJcm81YWt3WWdKS2RCZkY3Z2NmMVhudUF4MFVZdFlHd0YtaEU0R3VHNHQ3VmFSZHdfR1RIcnBJNUtXeWhKWVVScE1ZcXNJdzRfdkFGVi1lZzY2dWxCcVVGZ0FPSjNzVmFjTVg1YTBYS0xBajEzU1REM3dnbUc5U3E3NHVtLVRLLXRn
.youtube.com TRUE / FALSE 0 PREF tz=UTC&f7=100&f6=40000000&hl=en .youtube.com TRUE / FALSE 0 PREF tz=UTC&f7=100&f6=40000000&hl=en
.youtube.com TRUE / FALSE 1776288527000 ST-xuwub9 session_logininfo=AFmmF2swRQIgZPfEOdmfC8u5sHvE1aOagKEvp5rRUe5hRUeLiYmxLDwCIQDqFIR59yZ_aBb5BLYSpK7LGdJ6YZqnh32USuOyMZTC5g%3AQUQ3MjNmd0ZzX01fTjViQ2kzMDJEWG5Ed09zMGF1TlhJcm81YWt3WWdKS2RCZkY3Z2NmMVhudUF4MFVZdFlHd0YtaEU0R3VHNHQ3VmFSZHdfR1RIcnBJNUtXeWhKWVVScE1ZcXNJdzRfdkFGVi1lZzY2dWxCcVVGZ0FPSjNzVmFjTVg1YTBYS0xBajEzU1REM3dnbUc5U3E3NHVtLVRLLXRn .youtube.com TRUE / FALSE 1776288527000 ST-xuwub9 session_logininfo=AFmmF2swRQIgZPfEOdmfC8u5sHvE1aOagKEvp5rRUe5hRUeLiYmxLDwCIQDqFIR59yZ_aBb5BLYSpK7LGdJ6YZqnh32USuOyMZTC5g%3AQUQ3MjNmd0ZzX01fTjViQ2kzMDJEWG5Ed09zMGF1TlhJcm81YWt3WWdKS2RCZkY3Z2NmMVhudUF4MFVZdFlHd0YtaEU0R3VHNHQ3VmFSZHdfR1RIcnBJNUtXeWhKWVVScE1ZcXNJdzRfdkFGVi1lZzY2dWxCcVVGZ0FPSjNzVmFjTVg1YTBYS0xBajEzU1REM3dnbUc5U3E3NHVtLVRLLXRn
.youtube.com TRUE / FALSE 1809303671 SIDCC AKEyXzUEfCEbgyGrH1tNORzoWjcQ4T0V72l_bkp7S12EcJE1BUmxvYDz5rhspZYtTtFg7hzpWkbN .youtube.com TRUE / FALSE 1809355363 SIDCC AKEyXzXpgpaN5o2kMTOlzAUXbOF4YCfxl41G6ZutKAOlEzpeIdskBxux3PemZB5T0T-hVrRplzEw
.youtube.com TRUE / TRUE 1809303671 __Secure-1PSIDCC AKEyXzWZMgqSjtmJ_uHCIE-o8z-H3jLbJznGybAMvRcbKNzCej_YDlmQ-ScNrcaJ58mYfK6iaOei .youtube.com TRUE / TRUE 1809355363 __Secure-1PSIDCC AKEyXzVOgdvJviP9-nUDUnv0MXGY5L73_iQ1heqaYPuKh8JEpSnrLPN7dDb40XFFWlASf3sYC5Td
.youtube.com TRUE / TRUE 1809303671 __Secure-3PSIDCC AKEyXzU4244NG2Eo-friqIol4JdiWYR7Ew-fg1o9TggXgU7NJqk_X_6o7WvvME6so9LxwmuM3lmj .youtube.com TRUE / TRUE 1809355363 __Secure-3PSIDCC AKEyXzUfDASoJeAIdtNkO6vz8MF2NHsJWHdYWfhJlNp6RDnIIzD3XIv2eE7uff2BsY-JaNw5ANNX
.youtube.com TRUE / FALSE 1776288585000 ST-3opvp5 session_logininfo=AFmmF2swRQIgZPfEOdmfC8u5sHvE1aOagKEvp5rRUe5hRUeLiYmxLDwCIQDqFIR59yZ_aBb5BLYSpK7LGdJ6YZqnh32USuOyMZTC5g%3AQUQ3MjNmd0ZzX01fTjViQ2kzMDJEWG5Ed09zMGF1TlhJcm81YWt3WWdKS2RCZkY3Z2NmMVhudUF4MFVZdFlHd0YtaEU0R3VHNHQ3VmFSZHdfR1RIcnBJNUtXeWhKWVVScE1ZcXNJdzRfdkFGVi1lZzY2dWxCcVVGZ0FPSjNzVmFjTVg1YTBYS0xBajEzU1REM3dnbUc5U3E3NHVtLVRLLXRn .youtube.com TRUE / FALSE 1776288585000 ST-3opvp5 session_logininfo=AFmmF2swRQIgZPfEOdmfC8u5sHvE1aOagKEvp5rRUe5hRUeLiYmxLDwCIQDqFIR59yZ_aBb5BLYSpK7LGdJ6YZqnh32USuOyMZTC5g%3AQUQ3MjNmd0ZzX01fTjViQ2kzMDJEWG5Ed09zMGF1TlhJcm81YWt3WWdKS2RCZkY3Z2NmMVhudUF4MFVZdFlHd0YtaEU0R3VHNHQ3VmFSZHdfR1RIcnBJNUtXeWhKWVVScE1ZcXNJdzRfdkFGVi1lZzY2dWxCcVVGZ0FPSjNzVmFjTVg1YTBYS0xBajEzU1REM3dnbUc5U3E3NHVtLVRLLXRn
.youtube.com TRUE / TRUE 0 YSC KTvrS45hA30 .youtube.com TRUE / TRUE 0 YSC KTvrS45hA30
.instagram.com TRUE / TRUE 1801240452128 datr hGdNaS-QqakSYV8X2eqVTIyA .instagram.com TRUE / TRUE 1801240452128 datr hGdNaS-QqakSYV8X2eqVTIyA