fix: remove client-side format cache, prefer first audio track over largest
This commit is contained in:
parent
e2ead9db52
commit
60a0373d7f
3 changed files with 90 additions and 81 deletions
27
bot.py
27
bot.py
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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'],
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue