Добавил новый загрузчик c YouTube
This commit is contained in:
@@ -25,6 +25,12 @@ class ChunkUploadBackend(ABC):
|
||||
"""Прерывания загрузки"""
|
||||
|
||||
|
||||
class UploadBackend(ABC):
|
||||
@abstractmethod
|
||||
def upload(self, name: str, file: bytes | str) -> str:
|
||||
"""Загрузка файла"""
|
||||
|
||||
|
||||
class DiskChunkUploadBackend(ChunkUploadBackend):
|
||||
def __init__(self, key_prefix: str = ""):
|
||||
self.base_path = str(settings.MEDIA_DIR)
|
||||
@@ -157,3 +163,26 @@ class HybridDiskS3UploadBackend(ChunkUploadBackend):
|
||||
)
|
||||
os.remove(filepath)
|
||||
return response["Location"]
|
||||
|
||||
|
||||
class S3UploadBackend(UploadBackend):
|
||||
def __init__(self, key_prefix=""):
|
||||
self.s3 = boto3.client(
|
||||
service_name="s3",
|
||||
aws_access_key_id=settings.S3_ACCESS_KEY,
|
||||
aws_secret_access_key=settings.S3_SECRET_KEY,
|
||||
endpoint_url=settings.S3_ENDPOINT_URL,
|
||||
region_name=settings.S3_REGION_NAME,
|
||||
use_ssl=True,
|
||||
config=Config(signature_version=settings.S3_SIGNATURE_VERSION),
|
||||
)
|
||||
self.bucket = settings.S3_BUCKET_NAME
|
||||
self.key_prefix = key_prefix
|
||||
|
||||
def upload(self, name: str, file: bytes | str):
|
||||
response = self.s3.upload_file(
|
||||
Filename=file,
|
||||
Bucket=self.bucket,
|
||||
Key=f"{self.key_prefix}{name}",
|
||||
)
|
||||
return response["Location"]
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import subprocess
|
||||
from typing import Literal
|
||||
from dataclasses import dataclass
|
||||
|
||||
from app.utils.uploader import ChunkUploadBackend
|
||||
|
||||
129
app/utils/youtubeV2.py
Normal file
129
app/utils/youtubeV2.py
Normal file
@@ -0,0 +1,129 @@
|
||||
import yt_dlp
|
||||
from app.core.config import settings
|
||||
|
||||
class MediaInfo:
|
||||
def __init__(self, format: dict, id: int):
|
||||
self.__format = format
|
||||
self.id = id
|
||||
|
||||
@property
|
||||
def filesize(self):
|
||||
return self.__format.get("filesize")
|
||||
|
||||
@property
|
||||
def codec(self):
|
||||
if vcodec := self.__format.get("vcodec"):
|
||||
return vcodec
|
||||
return self.__format.get("acodec")
|
||||
|
||||
|
||||
class YtDlpManager:
|
||||
def __init__(self, url: str):
|
||||
self.url = url
|
||||
self._extract_info()
|
||||
self._set_video_codecs()
|
||||
|
||||
@property
|
||||
def resolutions(self):
|
||||
return sorted(self._resolutions.keys())
|
||||
|
||||
def best_audio(self) -> MediaInfo | None:
|
||||
"""
|
||||
Получает аудио дорожку с наилучшим качеством звучания
|
||||
"""
|
||||
if self.info.get("acodec") in ("none", None):
|
||||
return None
|
||||
ids = str(self.info.get("format_id", "")).split("+")
|
||||
if len(ids) == 2:
|
||||
audio_id = ids[1]
|
||||
else:
|
||||
audio_id = ids[0]
|
||||
|
||||
for f in self.info["formats"]:
|
||||
if f.get("format_id") == audio_id:
|
||||
return MediaInfo(f, audio_id)
|
||||
return None
|
||||
|
||||
def best_video(self, height: int | None = None, codec: str | None = None):
|
||||
"""
|
||||
Возвращает видео дорожку с наилучшим качеством с указанными параметрами
|
||||
"""
|
||||
if height is None:
|
||||
if codec is None:
|
||||
height = self.info.get("height")
|
||||
else:
|
||||
height = max(self._codecs.get(codec, [0]))
|
||||
|
||||
if not self._video_exist(codec, height):
|
||||
return None
|
||||
|
||||
iterator = reversed(self.info["formats"])
|
||||
for f in iterator:
|
||||
if f.get("height", 0) == height:
|
||||
if codec is None:
|
||||
return MediaInfo(f, f.get("format_id"))
|
||||
if f.get("codec") == codec:
|
||||
return MediaInfo(f, f.get("format_id"))
|
||||
return None
|
||||
|
||||
def download(self, video_id: str | None = None, audio_id: str | None = None):
|
||||
if video_id is None and audio_id is None:
|
||||
format_id = self.info.get(
|
||||
"format_id",
|
||||
)
|
||||
else:
|
||||
format_id = "" + str(video_id) if video_id is not None else ""
|
||||
if audio_id is not None:
|
||||
if len(format_id) > 0:
|
||||
format_id += "+"
|
||||
format_id += str(audio_id)
|
||||
|
||||
ydl_opts = {
|
||||
"format": f"{video_id}+{audio_id}",
|
||||
"merge_output_format": "mp4",
|
||||
"outtmpl": f"{settings.MEDIA_DIR}/%(title)s.%(ext)s",
|
||||
}
|
||||
|
||||
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
||||
info = ydl.extract_info(self.url, download=True)
|
||||
return ydl.prepare_filename(info)
|
||||
|
||||
def _extract_info(self):
|
||||
ydl_opts = {
|
||||
"quiet": True,
|
||||
"no_warnings": True,
|
||||
"extract_flat": False,
|
||||
}
|
||||
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
||||
self.info = ydl.extract_info(self.url, download=False)
|
||||
if self.info is None:
|
||||
raise Exception("Не удалось получить информацию о видео")
|
||||
|
||||
def _set_video_codecs(self):
|
||||
self._codecs: dict[str, list[int]] = {}
|
||||
self._resolutions: dict[int, list[str]] = {}
|
||||
for f in self.info["formats"]:
|
||||
codec = f.get("vcodec")
|
||||
if codec in ("none", None):
|
||||
continue
|
||||
|
||||
height = f.get("height")
|
||||
if height not in self._resolutions:
|
||||
self._resolutions[height] = []
|
||||
if codec not in self._codecs:
|
||||
self._codecs[codec] = []
|
||||
self._codecs[codec].append(height)
|
||||
self._resolutions[height].append(codec)
|
||||
|
||||
def _video_exist(self, codec: str = "", resolution: int = 0):
|
||||
if codec:
|
||||
if resolution:
|
||||
if resolutions := self._codecs.get(codec, []):
|
||||
return resolution in resolutions
|
||||
return True
|
||||
if resolution:
|
||||
if codec:
|
||||
if codecs := self._resolutions.get(resolution, []):
|
||||
return codec in codecs
|
||||
return True
|
||||
return False
|
||||
6
plans.md
Normal file
6
plans.md
Normal file
@@ -0,0 +1,6 @@
|
||||
|
||||
|
||||
- Реализовать ЭП, который возвращает список разрешений и размер файла
|
||||
- Переделать загрузку (видео и аудио склеиваются) сперва скачать на диск, затем готовый файл отправить в S3 в случае если будет ошибка, поставить таймаут на 15 минут
|
||||
- Реализовать статус доступности S3
|
||||
- Перед загрузкой проверить нет ли случайно данное видео в S3 если есть дать на неё ссылку
|
||||
Reference in New Issue
Block a user