2024-05-05 18:23:20 +03:00
|
|
|
import asyncio
|
|
|
|
from typing import Callable, Awaitable, Iterable
|
|
|
|
|
2024-05-03 17:42:53 +03:00
|
|
|
from yt_dlp import YoutubeDL
|
|
|
|
from yt_dlp.postprocessor import FFmpegExtractAudioPP
|
|
|
|
|
2024-05-03 19:57:20 +03:00
|
|
|
import config
|
2024-05-03 17:42:53 +03:00
|
|
|
import id3pp
|
|
|
|
|
|
|
|
|
|
|
|
class _CreateYDL:
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def youtube() -> YoutubeDL:
|
|
|
|
ydl = YoutubeDL({'format': 'ba'})
|
|
|
|
ydl.add_post_processor(id3pp.InfoYouTubePP(), when='before_dl')
|
|
|
|
ydl.add_post_processor(FFmpegExtractAudioPP(preferredcodec='mp3'), when='post_process')
|
|
|
|
return ydl
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def yt_proxied() -> YoutubeDL:
|
|
|
|
ydl = _CreateYDL.youtube()
|
2024-05-04 09:27:57 +03:00
|
|
|
ydl.params['proxy'] = config.get().yt_proxy
|
2024-05-03 17:42:53 +03:00
|
|
|
return ydl
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def yandex() -> YoutubeDL:
|
2024-05-03 19:57:20 +03:00
|
|
|
return YoutubeDL()
|
2024-05-03 17:42:53 +03:00
|
|
|
|
|
|
|
|
|
|
|
create_ydl_fn = {
|
|
|
|
'youtube': _CreateYDL.youtube,
|
|
|
|
'yt_proxied': _CreateYDL.yt_proxied,
|
|
|
|
'yandex': _CreateYDL.yandex,
|
|
|
|
}
|
|
|
|
|
|
|
|
ydl_fn_keys = create_ydl_fn.keys()
|
|
|
|
|
|
|
|
|
2024-05-05 18:23:20 +03:00
|
|
|
class Downloader:
|
2024-05-03 17:42:53 +03:00
|
|
|
|
2024-05-05 18:23:20 +03:00
|
|
|
def __init__(
|
|
|
|
self,
|
|
|
|
progress_cb: Callable[[str], Awaitable],
|
|
|
|
lyrics_cb: Callable[[list[str]], Awaitable]) -> None:
|
2024-05-03 17:42:53 +03:00
|
|
|
|
|
|
|
self.ydls: dict[str, YoutubeDL | None] = {
|
|
|
|
'youtube': None,
|
|
|
|
'yt_proxied': None,
|
|
|
|
'yandex': None,
|
|
|
|
}
|
|
|
|
|
2024-05-05 18:23:20 +03:00
|
|
|
self.cur_ydl: YoutubeDL | None = None
|
|
|
|
|
|
|
|
self.progress_cb = progress_cb
|
|
|
|
self.lyrics_cb = lyrics_cb
|
|
|
|
|
|
|
|
def choose_ydl(self, site: str) -> None:
|
2024-05-03 17:42:53 +03:00
|
|
|
|
|
|
|
ydl = self.ydls[site]
|
2024-05-05 18:23:20 +03:00
|
|
|
cfg = config.get()
|
2024-05-03 19:57:20 +03:00
|
|
|
|
2024-05-03 17:42:53 +03:00
|
|
|
if ydl is None:
|
|
|
|
ydl = create_ydl_fn[site]()
|
2024-05-03 19:57:20 +03:00
|
|
|
|
2024-05-04 09:27:57 +03:00
|
|
|
ydl.params['trim_file_name'] = cfg.path_length # Note: not only filename, but path in outtmpl
|
|
|
|
ydl.params['outtmpl']['default'] = cfg.tmpl
|
2024-05-03 17:42:53 +03:00
|
|
|
ydl.add_post_processor(id3pp.ID3TagsPP(), when='post_process')
|
2024-05-03 19:57:20 +03:00
|
|
|
|
|
|
|
cookies = cfg.cookies_dir / (site + '.txt')
|
|
|
|
if cookies.exists():
|
|
|
|
ydl.params['cookiefile'] = str(cookies)
|
|
|
|
|
2024-05-05 18:23:20 +03:00
|
|
|
self.cur_ydl = ydl
|
|
|
|
|
|
|
|
def get_cur_ydl(self) -> YoutubeDL:
|
|
|
|
|
|
|
|
ydl = self.cur_ydl
|
|
|
|
if ydl is None:
|
|
|
|
raise RuntimeError('ydl object not initialized')
|
2024-05-03 17:42:53 +03:00
|
|
|
return ydl
|
|
|
|
|
2024-05-05 18:23:20 +03:00
|
|
|
async def get_playlist_items(self, url: str) -> list[str]:
|
|
|
|
|
|
|
|
return await asyncio.get_event_loop().run_in_executor(
|
|
|
|
None,
|
|
|
|
Downloader._target_get_playlist_items,
|
|
|
|
self.get_cur_ydl(),
|
|
|
|
url,
|
|
|
|
)
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def _target_get_playlist_items(ydl: YoutubeDL, url: str) -> list[str]:
|
|
|
|
|
|
|
|
info = ydl.extract_info(url, download=False, process=True)
|
|
|
|
if info is None:
|
|
|
|
raise RuntimeError('ydl.extract_info returned None')
|
|
|
|
return [
|
|
|
|
entry['track'] if 'track' in entry else entry['title']
|
|
|
|
for entry in info['entries']
|
|
|
|
]
|
|
|
|
|
|
|
|
async def download(
|
|
|
|
self,
|
|
|
|
url: str,
|
|
|
|
playlist_items: Iterable[int] | None = None) -> int:
|
|
|
|
|
|
|
|
return await asyncio.get_event_loop().run_in_executor(
|
|
|
|
None,
|
|
|
|
Downloader._target_download,
|
|
|
|
self.get_cur_ydl(),
|
|
|
|
url,
|
|
|
|
playlist_items,
|
|
|
|
)
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def _target_download(
|
|
|
|
ydl: YoutubeDL,
|
|
|
|
url: str,
|
|
|
|
playlist_items: Iterable[int] | None = None) -> int:
|
|
|
|
|
|
|
|
if playlist_items:
|
|
|
|
ydl.params['playlist_items'] = ','.join(str(i) for i in playlist_items)
|
|
|
|
|
|
|
|
ret = ydl.download(url)
|
|
|
|
del ydl.params['playlist_items']
|
|
|
|
|
|
|
|
return ret
|
|
|
|
|
2024-05-03 17:42:53 +03:00
|
|
|
def cleanup(self) -> None:
|
|
|
|
|
|
|
|
for ydl in self.ydls.values():
|
|
|
|
if ydl is not None:
|
|
|
|
ydl.close()
|