Files
downloads-all-bot/bot/downloader.py

117 lines
4.0 KiB
Python

# downloader.py
import yt_dlp
import threading
import os
import logging
import subprocess
import shutil
import asyncio
from typing import List, Optional, Callable
from config import (
DEFAULT_TIKTOK_VIDEO_QUALITY,
DEFAULT_TIKTOK_AUDIO_QUALITY,
DEFAULT_INSTAGRAM_VIDEO_QUALITY,
)
logger = logging.getLogger(__name__)
class DownloadCancelled(Exception):
pass
def _progress_hook(cancel_event, progress_cb):
def hook(d):
if cancel_event.is_set():
raise DownloadCancelled("Cancelled by user")
if d["status"] in ["downloading", "finished"] and progress_cb:
progress_cb(d)
return hook
def optimize_for_telegram(input_path: str, output_path: str) -> None:
"""
Асинхронная функция: конвертирует видео в Telegram-safe mp4
"""
file_size_mb = os.path.getsize(input_path) / (1024 ** 2)
crf = 28 if file_size_mb > 50 else 23
cmd = [
'ffmpeg',
'-i', input_path,
'-c:v', 'libx264',
'-preset', 'fast',
'-crf', str(crf),
'-c:a', 'aac',
'-b:a', '128k',
'-movflags', '+faststart',
'-vf', 'scale=trunc(iw/2)*2:trunc(ih/2)*2',
'-y',
output_path
]
process = await asyncio.create_subprocess_exec(
*cmd,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE
)
stdout, stderr = await process.communicate()
if process.returncode != 0:
logger.error(f"FFmpeg error: {stderr.decode()}")
shutil.copy2(input_path, output_path)
async def process_download(message, url: str, output_path: str, cookies_file: Optional[str] = None, cancel_event: threading.Event = None):
ydl_options = {
"format": f"bestvideo[height<={DEFAULT_INSTAGRAM_VIDEO_QUALITY}] + bestaudio/best",
"outtmpl": output_path,
"cookiefile": cookies_file if cookies_file and os.path.exists(cookies_file) else None,
"progress_hooks": [lambda d: asyncio.create_task(progress_callback(message, d))],
"quiet": True,
"no_warnings": True,
"concurrent_fragment_downloads": 4,
"http_chunk_size": 1024 ** 2,
}
async def progress_callback(msg, d):
if d["status"] == "downloading":
downloaded_bytes = d.get("downloaded_bytes", 0)
total_bytes = d.get("total_bytes_est", None)
if total_bytes:
percent_downloaded = (downloaded_bytes / total_bytes) * 100
download_speed_bps = d.get("speed", 1e-6)
estimated_remaining_time_seconds = max(0, (total_bytes - downloaded_bytes) / download_speed_bps)
progress_text = "<b>Downloading...</b>\n"
progress_text += f"Progress: {percent_downloaded:.2f}%\n"
progress_text += f"Downloaded: {downloaded_bytes / (1024 ** 2):.2f} MB\n"
progress_text += f"Total Size: {total_bytes / (1024 ** 2):.2f} MB\n"
progress_text += f"[{d.get('filename')}] @ {int(download_speed_bps) / (1024 ** 2)} MB/s\n"
progress_text += f"ETA: {estimated_remaining_time_seconds:.1f} сек."
else:
progress_text = "<b>Downloading...</b>\n"
progress_text += f"Downloaded Bytes: {downloaded_bytes}\n"
try:
await msg.edit_text(progress_text, parse_mode="HTML")
except Exception as e:
logger.error(f"Error updating progress message: {e}")
try:
with yt_dlp.YoutubeDL(ydl_options) as ydl:
ydl.download([url])
await optimize_for_telegram(input_path=output_path, output_path=output_path + "_optimized")
except Exception as e:
logger.error(f"Download or optimization error: {e}")
raise
async def download_ytdlp(url: str, ydl_options: dict):
try:
with yt_dlp.YoutubeDL(ydl_options) as ydl:
info = ydl.extract_info(url, download=True)
return info
except Exception as e:
logger.error(f"Error downloading from {url}: {e}")
raise