Enhance media download support for SoundCloud
Added support for SoundCloud links and cover downloads. Updated bot messages and download options.
This commit is contained in:
parent
036567a061
commit
1247008941
1 changed files with 61 additions and 19 deletions
80
bot.py
80
bot.py
|
|
@ -8,6 +8,7 @@ from config import TOKEN
|
||||||
from aiogram import Bot, Dispatcher, F, types
|
from aiogram import Bot, Dispatcher, F, types
|
||||||
from aiogram.filters import CommandStart
|
from aiogram.filters import CommandStart
|
||||||
from aiogram.types import FSInputFile, InlineKeyboardMarkup, InlineKeyboardButton
|
from aiogram.types import FSInputFile, InlineKeyboardMarkup, InlineKeyboardButton
|
||||||
|
from aiogram.types import FSInputFile, URLInputFile, InlineKeyboardMarkup, InlineKeyboardButton
|
||||||
from aiogram.fsm.context import FSMContext
|
from aiogram.fsm.context import FSMContext
|
||||||
from aiogram.exceptions import TelegramEntityTooLarge
|
from aiogram.exceptions import TelegramEntityTooLarge
|
||||||
|
|
||||||
|
|
@ -54,9 +55,15 @@ def get_user_display_name(user: types.User):
|
||||||
return f"@{user.username}"
|
return f"@{user.username}"
|
||||||
return user.first_name
|
return user.first_name
|
||||||
|
|
||||||
|
def get_cover_info(url: str):
|
||||||
|
"""Извлекает прямую ссылку на обложку и название медиа"""
|
||||||
|
ydl_opts = {'quiet': True, 'no_warnings': True}
|
||||||
|
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
||||||
|
info = ydl.extract_info(url, download=False)
|
||||||
|
return info.get('thumbnail'), info.get('title', 'Cover')
|
||||||
|
|
||||||
def download_media(url: str, mode: str) -> str:
|
def download_media(url: str, mode: str) -> str:
|
||||||
"""Синхронная функция скачивания через yt-dlp (видео или аудио)"""
|
"""Синхронная функция скачивания через yt-dlp (видео или аудио)"""
|
||||||
|
|
||||||
ydl_opts = {
|
ydl_opts = {
|
||||||
'outtmpl': '%(id)s.%(ext)s',
|
'outtmpl': '%(id)s.%(ext)s',
|
||||||
'quiet': True,
|
'quiet': True,
|
||||||
|
|
@ -65,13 +72,20 @@ def download_media(url: str, mode: str) -> str:
|
||||||
|
|
||||||
if mode == 'video':
|
if mode == 'video':
|
||||||
ydl_opts['format'] = 'bestvideo[ext=mp4][vcodec^=avc]+bestaudio[ext=m4a]/best[ext=mp4][vcodec^=avc]/best[ext=mp4]/best'
|
ydl_opts['format'] = 'bestvideo[ext=mp4][vcodec^=avc]+bestaudio[ext=m4a]/best[ext=mp4][vcodec^=avc]/best[ext=mp4]/best'
|
||||||
|
ydl_opts['merge_output_format'] = 'mp4'
|
||||||
else:
|
else:
|
||||||
ydl_opts['format'] = 'bestaudio/best'
|
ydl_opts['format'] = 'bestaudio/best'
|
||||||
ydl_opts['postprocessors'] = [{
|
ydl_opts['writethumbnail'] = True
|
||||||
'key': 'FFmpegExtractAudio',
|
ydl_opts['postprocessors'] = [
|
||||||
'preferredcodec': 'mp3',
|
{
|
||||||
'preferredquality': '320',
|
'key': 'FFmpegExtractAudio',
|
||||||
}]
|
'preferredcodec': 'mp3',
|
||||||
|
'preferredquality': '320',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'key': 'EmbedThumbnail',
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
||||||
info = ydl.extract_info(url, download=True)
|
info = ydl.extract_info(url, download=True)
|
||||||
|
|
@ -88,24 +102,32 @@ async def cmd_start(message: types.Message):
|
||||||
logger.info(f"[{user.id}] {name} начал использовать бота.")
|
logger.info(f"[{user.id}] {name} начал использовать бота.")
|
||||||
|
|
||||||
await message.answer(
|
await message.answer(
|
||||||
"Привет! Я бот для скачивания из TikTok, YouTube или Instagram.\n"
|
"Привет! Я бот для скачивания медиа.\n"
|
||||||
"Отправь мне ссылку, и я предложу скачать видео (файлом) или отдельно аудио!"
|
"Отправь мне ссылку на YouTube, TikTok, Instagram или SoundCloud, и я предложу форматы для загрузки!"
|
||||||
)
|
)
|
||||||
|
|
||||||
@dp.message(F.text.regexp(r'(?i)https?://(?:[a-zA-Z0-9-]+\.)*(tiktok\.com|youtube\.com|youtu\.be|instagram\.com)/.*'))
|
@dp.message(F.text.regexp(r'(?i)https?://(?:[a-zA-Z0-9-]+\.)*(tiktok\.com|youtube\.com|youtu\.be|instagram\.com|soundcloud\.com)/.*'))
|
||||||
async def handle_media_link(message: types.Message, state: FSMContext):
|
async def handle_media_link(message: types.Message, state: FSMContext):
|
||||||
url = message.text
|
url = message.text
|
||||||
user = message.from_user
|
user = message.from_user
|
||||||
name = get_user_display_name(user)
|
name = get_user_display_name(user)
|
||||||
logger.info(f"[{user.id}] {name} прислал ссылку: {url}")
|
logger.info(f"[{user.id}] {name} прислал ссылку: {url}")
|
||||||
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
|
||||||
[InlineKeyboardButton(text="📹 Скачать видео", callback_data='video')],
|
if 'soundcloud.com' in url.lower():
|
||||||
[InlineKeyboardButton(text="🎵 Скачать аудио", callback_data='audio')]
|
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
||||||
])
|
[InlineKeyboardButton(text="🖼 Скачать обложку трека", callback_data='cover')],
|
||||||
|
[InlineKeyboardButton(text="🎵 Скачать трек", callback_data='audio')]
|
||||||
|
])
|
||||||
|
else:
|
||||||
|
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
||||||
|
[InlineKeyboardButton(text="📹 Скачать видео", callback_data='video')],
|
||||||
|
[InlineKeyboardButton(text="🎵 Скачать аудио", callback_data='audio')]
|
||||||
|
])
|
||||||
|
|
||||||
await state.update_data(media_url=url)
|
await state.update_data(media_url=url)
|
||||||
await message.answer("Что именно нужно скачать?", reply_markup=keyboard)
|
await message.answer("Что именно нужно скачать?", reply_markup=keyboard)
|
||||||
|
|
||||||
@dp.callback_query(F.data.in_({"video", "audio"}))
|
@dp.callback_query(F.data.in_({"video", "audio", "cover"}))
|
||||||
async def button_callback(callback: types.CallbackQuery, state: FSMContext):
|
async def button_callback(callback: types.CallbackQuery, state: FSMContext):
|
||||||
await callback.answer()
|
await callback.answer()
|
||||||
|
|
||||||
|
|
@ -122,10 +144,29 @@ async def button_callback(callback: types.CallbackQuery, state: FSMContext):
|
||||||
return
|
return
|
||||||
await state.clear()
|
await state.clear()
|
||||||
|
|
||||||
status_text = "⏳ Скачиваю видео..." if mode == 'video' else "⏳ Скачиваю аудио..."
|
status_text = {
|
||||||
await callback.message.edit_text(status_text)
|
'video': "⏳ Скачиваю видео...",
|
||||||
|
'audio': "⏳ Скачиваю аудио...",
|
||||||
|
'cover': "⏳ Получаю обложку..."
|
||||||
|
}.get(mode, "⏳ Обработка...")
|
||||||
|
|
||||||
|
status_msg = await callback.message.edit_text(status_text)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
if mode == 'cover':
|
||||||
|
cover_url, title = await asyncio.to_thread(get_cover_info, media_url)
|
||||||
|
if cover_url:
|
||||||
|
safe_title = re.sub(r'[\\/*?:"<>|]', "", title)
|
||||||
|
cover_doc = URLInputFile(url=cover_url, filename=f"{safe_title}.jpg")
|
||||||
|
await callback.message.answer_document(
|
||||||
|
document=cover_doc,
|
||||||
|
disable_content_type_detection=True
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
await callback.message.answer("❌ Не удалось найти обложку для этой ссылки.")
|
||||||
|
await status_msg.delete()
|
||||||
|
return
|
||||||
|
|
||||||
file_path = await asyncio.to_thread(download_media, media_url, mode)
|
file_path = await asyncio.to_thread(download_media, media_url, mode)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
@ -141,9 +182,10 @@ async def button_callback(callback: types.CallbackQuery, state: FSMContext):
|
||||||
audio=audio
|
audio=audio
|
||||||
)
|
)
|
||||||
logger.info(f"[{user.id}] {name} -> Успешно отправлен файл {mode}.")
|
logger.info(f"[{user.id}] {name} -> Успешно отправлен файл {mode}.")
|
||||||
|
await status_msg.delete()
|
||||||
|
|
||||||
except TelegramEntityTooLarge:
|
except TelegramEntityTooLarge:
|
||||||
await callback.message.answer("❌ Файл слишком большой! Telegram позволяет отправлять ботам файлы размером не более 50 МБ.")
|
await status_msg.edit_text("❌ Файл слишком большой! Telegram позволяет отправлять ботам файлы размером не более 50 МБ.")
|
||||||
logger.warning(f"[{user.id}] {name} -> Файл превысил лимит в 50 МБ.")
|
logger.warning(f"[{user.id}] {name} -> Файл превысил лимит в 50 МБ.")
|
||||||
finally:
|
finally:
|
||||||
if os.path.exists(file_path):
|
if os.path.exists(file_path):
|
||||||
|
|
@ -151,11 +193,11 @@ async def button_callback(callback: types.CallbackQuery, state: FSMContext):
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error process_media [{mode}]: {e}")
|
logger.error(f"Error process_media [{mode}]: {e}")
|
||||||
await callback.message.answer("❌ Произошла ошибка. Возможно, видео скрыто приватностью, удалено или временно недоступно.")
|
await status_msg.edit_text("❌ Произошла ошибка. Возможно, видео/трек скрыто приватностью, удалено или временно недоступно.")
|
||||||
|
|
||||||
@dp.message()
|
@dp.message()
|
||||||
async def handle_other_messages(message: types.Message):
|
async def handle_other_messages(message: types.Message):
|
||||||
await message.answer("Пожалуйста, отправьте корректную ссылку на TikTok, YouTube или Instagram.")
|
await message.answer("Пожалуйста, отправьте корректную ссылку на YouTube, TikTok, Instagram или SoundCloud.")
|
||||||
|
|
||||||
async def main():
|
async def main():
|
||||||
print(f"Активный лог: {log_filename}")
|
print(f"Активный лог: {log_filename}")
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue