""" 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)