94 lines
2.5 KiB
Python
94 lines
2.5 KiB
Python
from attrs import define
|
|
|
|
from io import BytesIO
|
|
|
|
from .driver import DeezerDriver
|
|
|
|
from . import track_formats
|
|
from .util import UrlDecrypter, ChunkDecrypter
|
|
from .song import FullSongItem
|
|
|
|
|
|
@define
|
|
class DeezerBytestream:
|
|
file: bytes
|
|
filename: str
|
|
song: FullSongItem
|
|
|
|
@classmethod
|
|
def from_bytestream(
|
|
cls, bytestream: BytesIO, filename: str, full_song: FullSongItem
|
|
):
|
|
bytestream.seek(0)
|
|
return cls(
|
|
file=bytestream.read(),
|
|
filename=filename,
|
|
song=full_song,
|
|
)
|
|
|
|
|
|
@define
|
|
class Downloader:
|
|
driver: DeezerDriver
|
|
song_id: str
|
|
track: dict
|
|
song: FullSongItem
|
|
|
|
@classmethod
|
|
async def build(cls, song_id: str, driver: DeezerDriver):
|
|
track = await driver.reverse_get_track(song_id)
|
|
try:
|
|
return cls(
|
|
song_id=str(song_id),
|
|
driver=driver,
|
|
track=track["results"],
|
|
song=await FullSongItem.from_deezer(track),
|
|
)
|
|
except KeyError:
|
|
from icecream import ic
|
|
|
|
ic(track)
|
|
await driver.renew_engine()
|
|
return await cls.build(song_id, driver)
|
|
|
|
async def to_bytestream(self) -> DeezerBytestream:
|
|
quality = track_formats.MP3_128
|
|
|
|
decrypter = ChunkDecrypter.from_track_id(self.song_id)
|
|
i = 0
|
|
audio = BytesIO()
|
|
|
|
async for chunk in self.driver.engine.get_data_iter(
|
|
await self._get_download_url(quality=quality)
|
|
):
|
|
if i % 3 > 0 or len(chunk) < 2 * 1024:
|
|
audio.write(chunk)
|
|
else:
|
|
audio.write(decrypter.decrypt_chunk(chunk))
|
|
i += 1
|
|
|
|
return DeezerBytestream.from_bytestream(
|
|
filename=self.song.full_name + track_formats.TRACK_FORMAT_MAP[quality].ext,
|
|
bytestream=audio,
|
|
full_song=self.song,
|
|
)
|
|
|
|
async def _get_download_url(self, quality: str = "MP3_128"):
|
|
md5_origin = self.track["MD5_ORIGIN"]
|
|
track_id = self.track["SNG_ID"]
|
|
media_version = self.track["MEDIA_VERSION"]
|
|
|
|
url_decrypter = UrlDecrypter(
|
|
md5_origin=md5_origin, track_id=track_id, media_version=media_version
|
|
)
|
|
|
|
return url_decrypter.get_url_for(track_formats.TRACK_FORMAT_MAP[quality])
|
|
|
|
|
|
@define
|
|
class DownloaderBuilder:
|
|
driver: DeezerDriver
|
|
|
|
async def from_id(self, song_id: str):
|
|
return await Downloader.build(song_id=song_id, driver=self.driver)
|