videoDownloadTGbot/vk-downloader/app.py
2026-05-09 16:58:12 +03:00

188 lines
7.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
VK Video Downloader Service
Отдельный микросервис для скачивания видео с VK
"""
import os
import logging
from pathlib import Path
from flask import Flask, request, jsonify, send_file
from flask_cors import CORS
import yt_dlp
import tempfile
import uuid
# Настройка логирования
logging.basicConfig(
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
level=logging.INFO
)
logger = logging.getLogger(__name__)
app = Flask(__name__)
CORS(app) # Разрешаем CORS для взаимодействия с основным ботом
# Директория для временных файлов
DOWNLOADS_DIR = Path('downloads')
DOWNLOADS_DIR.mkdir(exist_ok=True)
def download_vk_video(url: str, max_retries: int = 3) -> Path:
"""Скачивает видео с VK"""
vk_user_agent = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
last_error = None
for attempt in range(max_retries):
try:
# Получаем информацию о видео
ydl_opts_info = {
'quiet': False,
'no_warnings': False,
'user_agent': vk_user_agent,
'socket_timeout': 60, # Увеличенный таймаут для VK
'extractor_args': {
'vk': {},
},
'http_headers': {
'User-Agent': vk_user_agent,
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'ru-RU,ru;q=0.9',
'Referer': 'https://vk.com/',
'Connection': 'keep-alive',
},
'nocheckcertificate': False,
}
with yt_dlp.YoutubeDL(ydl_opts_info) as ydl:
info = ydl.extract_info(url, download=False)
video_title = info.get('title', 'vk_video')
logger.info(f"VK: получена информация о видео: {video_title}")
# Создаем уникальное имя файла
safe_title = video_title.replace('/', '_').replace('\\', '_')[:100]
output_file = DOWNLOADS_DIR / f'{uuid.uuid4()}_{safe_title}.%(ext)s'
# Скачиваем видео
ydl_opts_download = {
'format': 'best',
'outtmpl': str(output_file),
'quiet': False,
'no_warnings': False,
'user_agent': vk_user_agent,
'socket_timeout': 60,
'extractor_args': {
'vk': {},
},
'http_headers': {
'User-Agent': vk_user_agent,
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'ru-RU,ru;q=0.9',
'Referer': 'https://vk.com/',
},
}
logger.info(f"VK: начинаем скачивание (попытка {attempt + 1}/{max_retries})")
with yt_dlp.YoutubeDL(ydl_opts_download) as ydl:
ydl.download([url])
# Находим скачанный файл
downloaded_files = list(DOWNLOADS_DIR.glob(f'{output_file.stem}*'))
if downloaded_files:
downloaded_files.sort(key=lambda x: x.stat().st_mtime, reverse=True)
return downloaded_files[0]
else:
raise Exception("Файл не был найден после скачивания")
except Exception as e:
last_error = e
logger.warning(f"VK: попытка {attempt + 1}/{max_retries} не удалась: {e}")
if attempt < max_retries - 1:
import time
time.sleep((attempt + 1) * 2)
raise last_error or Exception("Неизвестная ошибка при скачивании с VK")
@app.route('/health', methods=['GET'])
def health():
"""Health check endpoint"""
return jsonify({'status': 'ok', 'service': 'vk-downloader'}), 200
@app.route('/download', methods=['POST'])
def download():
"""Скачивает видео с VK и возвращает файл"""
try:
data = request.get_json()
if not data or 'url' not in data:
return jsonify({'error': 'URL is required'}), 400
url = data['url']
logger.info(f"Получен запрос на скачивание: {url}")
# Проверяем, что это VK URL
if 'vk.com' not in url and 'vk.ru' not in url and 'vkontakte.ru' not in url:
return jsonify({'error': 'Only VK URLs are supported'}), 400
# Скачиваем видео
video_path = download_vk_video(url)
logger.info(f"Видео скачано: {video_path}")
# Отправляем файл
return send_file(
str(video_path),
as_attachment=True,
download_name=video_path.name,
mimetype='video/mp4'
)
except Exception as e:
logger.error(f"Ошибка при скачивании: {e}")
return jsonify({'error': str(e)}), 500
@app.route('/download/stream', methods=['POST'])
def download_stream():
"""Скачивает видео с VK и возвращает бинарные данные"""
try:
data = request.get_json()
if not data or 'url' not in data:
return jsonify({'error': 'URL is required'}), 400
url = data['url']
logger.info(f"Получен запрос на скачивание (stream): {url}")
# Проверяем, что это VK URL
if 'vk.com' not in url and 'vk.ru' not in url and 'vkontakte.ru' not in url:
return jsonify({'error': 'Only VK URLs are supported'}), 400
# Скачиваем видео
video_path = download_vk_video(url)
logger.info(f"Видео скачано: {video_path}")
# Читаем файл и отправляем
with open(video_path, 'rb') as f:
video_data = f.read()
# Безопасное имя файла без кириллицы для заголовка
safe_filename = video_path.name.encode('ascii', 'ignore').decode('ascii') or 'vk_video.mp4'
if not safe_filename.endswith('.mp4'):
safe_filename = 'vk_video.mp4'
# Удаляем временный файл
video_path.unlink()
return video_data, 200, {
'Content-Type': 'video/mp4',
'Content-Disposition': f'attachment; filename="{safe_filename}"'
}
except Exception as e:
logger.error(f"Ошибка при скачивании: {e}")
return jsonify({'error': str(e)}), 500
if __name__ == '__main__':
port = int(os.getenv('PORT', 5000)) # Внутренний порт контейнера
host = os.getenv('HOST', '0.0.0.0')
logger.info(f"Запуск VK Downloader сервиса на {host}:{port}")
app.run(host=host, port=port, debug=False)