Files
AnyMusicBot/bot/modules/soundcloud/downloader.py
2024-11-02 00:10:24 +02:00

100 lines
2.7 KiB
Python

from attrs import define
from typing import Callable
from .driver import SoundCloudDriver
from .song import SongItem
import m3u8
@define
class SoundCloudBytestream:
file: bytes
filename: str
duration: int
song: SongItem
@classmethod
def from_bytes(cls, bytes_: bytes, filename: str, duration: int, song: SongItem):
return cls(
file=bytes_, filename=filename, duration=int(duration / 1000), song=song
)
@define
class Downloader:
driver: SoundCloudDriver
download_url: str
duration: int
filename: str
method: Callable
song: SongItem
@classmethod
async def build(cls, song_id: str, driver: SoundCloudDriver):
track = await driver.get_track(song_id)
song = SongItem.from_soundcloud(track)
if url := cls._try_get_progressive(track["media"]["transcodings"]):
method = cls._progressive
else:
url = track["media"]["transcodings"][0]["url"]
method = (
cls._hls
if (track["media"]["transcodings"][0]["format"]["protocol"] == "hls")
else cls._progressive
)
return cls(
driver=driver,
duration=track["duration"],
method=method,
download_url=url,
filename=f'{track["title"]}.mp3',
song=song,
)
@staticmethod
def _try_get_progressive(urls: list) -> str | None:
for transcode in urls:
if transcode["format"]["protocol"] == "progressive":
return transcode["url"]
async def _progressive(self, url: str) -> bytes:
return await self.driver.engine.read_data(
url=(await self.driver.engine.get(url))["url"]
)
async def _hls(self, url: str) -> bytes:
m3u8_obj = m3u8.loads(
(
await self.driver.engine.read_data(
(await self.driver.engine.get(url=url))["url"]
)
).decode()
)
content = bytearray()
for segment in m3u8_obj.files:
content.extend(
await self.driver.engine.read_data(url=segment, append_client_id=False)
)
return content
async def to_bytestream(self) -> SoundCloudBytestream:
return SoundCloudBytestream.from_bytes(
bytes_=await self.method(self, self.download_url),
filename=self.filename,
duration=self.duration,
song=self.song,
)
@define
class DownloaderBuilder:
driver: SoundCloudDriver
async def from_id(self, song_id: str):
return await Downloader.build(song_id=song_id, driver=self.driver)