Skip to content

Commit

Permalink
Update Yandex extension
Browse files Browse the repository at this point in the history
Now it's support displace with default value. Also enhanced none checking
  • Loading branch information
helltraitor committed Feb 12, 2023
1 parent 25cbe22 commit 096f7ae
Show file tree
Hide file tree
Showing 7 changed files with 61 additions and 25 deletions.
4 changes: 4 additions & 0 deletions downloader/extensions/yandex/domain.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ class Yandex(Domain, Fetchable):
"""
def __init__(self) -> None:
self.quality = TrackQuality.STANDARD
self.displace = "_"

@staticmethod
def match(url: str) -> bool:
Expand All @@ -53,12 +54,15 @@ def activate(self, common_options: list[str], kwargs_options: dict[str, str]) ->
case _:
Logger.warning("Option %s is not supported by Yandex domain", option)

self.displace = kwargs_options.get("Displace", "_")

def fetch_from(self, url: str) -> Target:
for item in FETCH_MODELS:
if (target := item.from_url(url)) is None:
continue

target.quality = self.quality
target.displace = self.displace
return target

Logger.error("Yandex domain unable to recognize url: %s", url)
Expand Down
15 changes: 9 additions & 6 deletions downloader/extensions/yandex/models/album.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import logging
import re

from collections.abc import Sequence
from dataclasses import dataclass, field
from typing import Optional

Expand All @@ -30,6 +31,7 @@ class Album(Expandable):
alone: bool = True
available: bool = False
quality: TrackQuality = field(default=TrackQuality.STANDARD, repr=False)
displace: str = "_"

@staticmethod
def from_url(url: str) -> Optional[Album]:
Expand All @@ -38,7 +40,7 @@ def from_url(url: str) -> Optional[Album]:
return Album(album.group(1))
return None

async def expand(self, session: ClientSession, system: FileSystem) -> list[ExpandedTargets]:
async def expand(self, session: ClientSession, system: FileSystem) -> Sequence[ExpandedTargets]:
if not self.available:
Logger.warning("Album %s is not available", self)

Expand All @@ -50,12 +52,12 @@ async def expand(self, session: ClientSession, system: FileSystem) -> list[Expan
raise RuntimeError(f"{self} wasn't prepared by `prepare` method")

expanded = []
async with system.into(self.title) as album:
async with system.into(self.title, self.displace) as album:
if len(self.volumes) == 1:
return [ExpandedTargets(album, self.volumes[0])]

for part, tracks in enumerate(self.volumes, 1):
async with album.into(f"CD{part}") as subdir:
async with album.into(f"CD{part}", self.displace) as subdir:
expanded.append(ExpandedTargets(subdir, tracks))
return expanded

Expand All @@ -71,15 +73,16 @@ async def prepare(self, session: ClientSession) -> None:

self.available = meta_info["available"]

self.title = meta_info["title"]
self.title = str(meta_info["title"])
if "version" in meta_info:
self.title = f"{self.title} ({meta_info['version']})"

if self.alone:
artists = ", ".join((artist["name"] for artist in meta_info["artists"]))
self.title = artists + " - " + self.title # type: ignore
self.title = artists + " - " + self.title

for volume in meta_info["volumes"]:
self.volumes.append([
Track(self.id, str(track["id"]), alone=False, quality=self.quality) for track in volume
Track(self.id, str(track["id"]), alone=False, quality=self.quality, displace=self.displace)
for track in volume
])
5 changes: 3 additions & 2 deletions downloader/extensions/yandex/models/artist.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class Artist(Expandable):
name: str | None = field(default=None)
available: bool = False
quality: TrackQuality = field(default=TrackQuality.STANDARD, repr=False)
displace: str = "_"

@staticmethod
def from_url(url: str) -> Optional[Artist]:
Expand All @@ -48,7 +49,7 @@ async def expand(self, session: ClientSession, system: FileSystem) -> list[Expan
Logger.error("%s wasn't prepared by `prepare` method", self)
raise RuntimeError(f"{self} wasn't prepared by `prepare` method")

async with system.into(self.name) as artist:
async with system.into(self.name, self.displace) as artist:
return [ExpandedTargets(artist, self.albums)]

async def prepare(self, session: ClientSession) -> None:
Expand All @@ -65,4 +66,4 @@ async def prepare(self, session: ClientSession) -> None:
self.name = meta_info["artist"]["name"]

for album in meta_info["albums"]:
self.albums.append(Album(str(album["id"]), alone=False, quality=self.quality))
self.albums.append(Album(str(album["id"]), alone=False, quality=self.quality, displace=self.displace))
5 changes: 3 additions & 2 deletions downloader/extensions/yandex/models/label.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class Label(Expandable):
albums: list[Album] = field(default_factory=list)
name: str | None = field(default=None) # name
quality: TrackQuality = field(default=TrackQuality.STANDARD, repr=False)
displace: str = "_"

@staticmethod
def from_url(url: str) -> Optional[Label]:
Expand All @@ -40,7 +41,7 @@ async def expand(self, session: ClientSession, system: FileSystem) -> list[Expan
Logger.error("%s wasn't prepared by `prepare` method", self)
raise RuntimeError(f"{self} wasn't prepared by `prepare` method")

async with system.into(self.name) as artist:
async with system.into(self.name, self.displace) as artist:
return [ExpandedTargets(artist, self.albums)]

async def prepare(self, session: ClientSession) -> None:
Expand All @@ -56,4 +57,4 @@ async def prepare(self, session: ClientSession) -> None:
self.name = meta_info["label"]["name"]

for album in meta_info["albums"]:
self.albums.append(Album(str(album["id"]), quality=self.quality))
self.albums.append(Album(str(album["id"]), quality=self.quality, displace=self.displace))
3 changes: 2 additions & 1 deletion downloader/extensions/yandex/models/playlist.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ class Playlist(Expandable):
name: str | None = field(default=None)
available: bool = False
quality: TrackQuality = field(default=TrackQuality.STANDARD)
displace: str = "_"

@staticmethod
def from_url(url: str) -> Optional[Playlist]:
Expand All @@ -49,7 +50,7 @@ async def expand(self, session: ClientSession, system: FileSystem) -> list[Expan
Logger.error("%s wasn't prepared by `prepare` method", self)
raise RuntimeError(f"{self} wasn't prepared by `prepare` method")

async with system.into(self.name) as playlist:
async with system.into(self.name, self.displace) as playlist:
return [ExpandedTargets(playlist, self.tracks)]

async def prepare(self, session: ClientSession) -> None:
Expand Down
31 changes: 19 additions & 12 deletions downloader/extensions/yandex/models/track/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
from typing import Optional

from aiohttp import ClientSession
from defusedxml import ElementTree
# Note: Defused xml doesn't provide types for mypy
from defusedxml import ElementTree # type: ignore

from downloader.fetcher import Downloadable
from downloader.filesystem import FileSystem, IgnoredException
Expand All @@ -33,6 +34,7 @@ class Track(Downloadable):
available: bool = False
alone: bool = True
quality: TrackQuality = field(default=TrackQuality.STANDARD, repr=False)
displace: str = "_"

@staticmethod
def from_url(url: str) -> Optional[Track]:
Expand All @@ -54,7 +56,8 @@ async def download(self, session: ClientSession, system: FileSystem) -> None:
raise RuntimeError(f"{self} wasn't prepared by `prepare` method")

# Receiving and processing download info
src = self.file.resource.removeprefix("//")
# Safe: Value self.file was checked above
src = self.file.resource.removeprefix("//") # type: ignore
request = (api.TRACK_DOWN_REQUEST
.with_url_fields(parameters={"src": src}))

Expand All @@ -81,12 +84,13 @@ async def download(self, session: ClientSession, system: FileSystem) -> None:
.with_section_fields("params", parameters={
"track-id": self.id}))

title = self.meta.info.title_from(self.alone)
filename = title + "." + self.file.codec
# Safe: Values self.meta, self.file were checked above
title = self.meta.info.title_from(self.alone) # type: ignore
filename = title + "." + self.file.codec # type: ignore

# Trying to open file before downloading, because file may exist
# In that case IgnoredException or FileExistError occurs
async with system.open(filename).to_file() as file:
async with system.open(filename, self.displace).to_file() as file:
async with request.make(session) as response:
if response.status != 200:
Logger.error("Bad response %s for %s", response.status, self)
Expand All @@ -102,8 +106,9 @@ async def download(self, session: ClientSession, system: FileSystem) -> None:
print("DONE", title)

Logger.info("Begin applying tags for track %s", title)
async with system.open(filename).to_track() as track:
self.meta.apply(track)
async with system.open(filename, self.displace).to_track() as track:
# Safe: Values self.meta, self.file were checked above
self.meta.apply(track) # type: ignore

async def prepare(self, session: ClientSession) -> None:
# Both under must have been prepared in following order:
Expand All @@ -120,11 +125,12 @@ async def prepare_cover(self, session: ClientSession) -> None:
Args:
session: The client session instance that will be used for making requests.
"""
if not all((self.meta, self.meta.cover)):
if self.meta is None or self.meta.cover is None:
Logger.error("%s wasn't prepared by `prepare_meta` method", self)
raise RuntimeError(f"{self} wasn't prepared by `prepare_meta` method")

src = self.meta.cover.resource.replace("%%", self.quality.cover())
# Safe: Values self.meta was checked above
src = self.meta.cover.resource.replace("%%", self.quality.cover()) # type: ignore
request = (api.TRACK_COVER_REQUEST
.with_url_fields(parameters={"src": src}))

Expand All @@ -142,8 +148,9 @@ async def prepare_cover(self, session: ClientSession) -> None:
Logger.error("Bad content type `application/octet-stream` (RFC2616) for %s", self)
raise RuntimeError("Bad content type `application/octet-stream` (RFC2616)")

self.meta.cover.content = await response.read()
self.meta.cover.mimetype = response.headers["CONTENT-TYPE"]
# Safe: Values self.meta was checked above
self.meta.cover.content = await response.read() # type: ignore
self.meta.cover.mimetype = response.headers["CONTENT-TYPE"] # type: ignore
Logger.info("Cover was successfully prepared for %s", self)

async def prepare_file(self, session: ClientSession) -> None:
Expand All @@ -161,7 +168,7 @@ async def prepare_file(self, session: ClientSession) -> None:

request = (api.TRACK_META_REQUEST
.with_url_fields(parameters={"album": self.album, "track": self.id})
.with_section_fields("params", parameters={"hq": self.quality.hq()}))
.with_section_fields("params", parameters={"hq": str(self.quality.hq())}))

async with request.make(session) as response:
if response.status != 200:
Expand Down
23 changes: 21 additions & 2 deletions downloader/extensions/yandex/models/track/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ def post_init(self):
self.title = f"{self.title} ({self.version})"

def apply(self, file: FileType) -> None:
if file.tags is None:
Logger.error("FileType hasn't provided tags. At least empty tags must be set")
raise RuntimeError("FileType.tags attribute is None, but must be at least an empty instance")

self.post_init()

# The chosen one album translates all information in track
Expand Down Expand Up @@ -87,6 +91,10 @@ def post_init(self):
self.title = f"{self.title} ({self.version})"

def apply(self, file: FileType) -> None:
if file.tags is None:
Logger.error("FileType hasn't provided tags. At least empty tags must be set")
raise RuntimeError("FileType.tags attribute is None, but must be at least an empty instance")

self.post_init()

artists = [artist.name for artist in self.artists if not artist.composer] or [""]
Expand Down Expand Up @@ -151,6 +159,10 @@ def apply(self, file: FileType) -> None:
Logger.warning("Apply was calling for track but no cover provided")
return

if file.tags is None:
Logger.error("FileType hasn't provided tags. At least empty tags must be set")
raise RuntimeError("FileType.tags attribute is None, but must be at least an empty instance")

file.tags.add(id3.APIC(
data=self.content,
desc="Cover",
Expand Down Expand Up @@ -182,17 +194,24 @@ def apply(self, file: FileType) -> None:
"""
if self.text is None:
Logger.warning("Apply was calling for track but no lyrics provided")
return

if self.authors is None:
Logger.warning("Apply was calling for track but no lyrics authors provided")
return

if file.tags is None:
Logger.error("FileType hasn't provided tags. At least empty tags must be set")
raise RuntimeError("FileType.tags attribute is None, but must be at least an empty instance")

file.tags.add(id3.TEXT(
encoding=3, # 3 for UTF-8
# see mutagen _specs.py
text=(self.authors or "").split(", ")))
text=self.authors.split(", ")))
file.tags.add(id3.USLT(
encoding=3, # 3 for UTF-8
# see mutagen _specs.py
text=(self.text or "")))
text=self.text))


@dataclass
Expand Down

0 comments on commit 096f7ae

Please sign in to comment.