init commit

This commit is contained in:
2025-08-14 02:38:56 +04:00
commit 5108bbbe76
14 changed files with 547 additions and 0 deletions

View File

@@ -0,0 +1,10 @@
from .base import Downloader
from .youtube import YouTubeDownloader
def get_downloader(service_name: str) -> Downloader:
"""Фабрика загрузчиков"""
downloaders = {
"youtube": YouTubeDownloader,
# Здесь можно регистрировать новые загрузчики
}
return downloaders.get(service_name.lower())

View File

@@ -0,0 +1,23 @@
import abc
import os
import re
from typing import Optional, Tuple
class Downloader(abc.ABC):
"""Базовый класс для загрузчиков"""
@abc.abstractmethod
def is_supported_url(self, url: str) -> bool:
"""Проверяет, поддерживается ли URL"""
pass
@abc.abstractmethod
async def download_audio(self, url: str, config: dict) -> Tuple[Optional[str], Optional[str], Optional[int]]:
"""Скачивает аудио и возвращает (путь к файлу, название, длительность)"""
pass
@staticmethod
def sanitize_filename(name: str) -> str:
"""Очищает название файла от недопустимых символов"""
return re.sub(r'[^\w\-_\. ]', '', name)[:100]

View File

@@ -0,0 +1,62 @@
import asyncio
import os
import uuid
import yt_dlp
from typing import Optional, Tuple
from .base import Downloader
from bot.utils import run_blocking
class YouTubeDownloader(Downloader):
"""Загрузчик для YouTube"""
def is_supported_url(self, url: str) -> bool:
return "youtube.com" in url or "youtu.be" in url
async def download_audio(self, url: str, config: dict) -> Tuple[Optional[str], Optional[str], Optional[int]]:
"""Скачивает аудио с YouTube"""
# Настройки yt-dlp
ydl_opts = {
'format': 'bestaudio/best',
'outtmpl': os.path.join(config['tmp_dir'], f'%(id)s.%(ext)s'),
'postprocessors': [{
'key': 'FFmpegExtractAudio',
'preferredcodec': 'mp3',
'preferredquality': str(config.get('audio_quality', 192)),
}],
'quiet': True,
'no_warnings': True,
'proxy': config.get('proxy'),
'max_filesize': 50 * 1024 * 1024, # 100MB
'noplaylist': True,
}
# Ограничение длительности
if max_duration := config.get('max_duration'):
ydl_opts['match_filter'] = yt_dlp.match_filter_func(
f"duration < {max_duration}"
)
try:
# Запускаем в отдельном процессе
result = await run_blocking(self._download, url, ydl_opts)
return result
except Exception as e:
return None, str(e)
def _download(self, url: str, opts: dict) -> Tuple[str, str, int]:
"""Синхронная загрузка (выполняется в отдельном процессе)"""
with yt_dlp.YoutubeDL(opts) as ydl:
info = ydl.extract_info(url, download=True)
filename = ydl.prepare_filename(info)
base, _ = os.path.splitext(filename)
mp3_path = base + '.mp3'
# Получаем название
title = info.get('title', 'audio') or 'audio'
clean_title = self.sanitize_filename(title)
# Получаем длительность в миллисекундах
duration_ms = int(info.get('duration', 0) * 1000)
return mp3_path, clean_title, duration_ms