From 6dc2d0dc56f2be30c0d99e036f7d23ae7e8a9b6d Mon Sep 17 00:00:00 2001 From: Lev Plyusnin Date: Thu, 21 Nov 2024 18:30:05 +0700 Subject: [PATCH] =?UTF-8?q?=D0=9F=D0=BE=D0=B4=D0=B4=D0=B5=D1=80=D0=B6?= =?UTF-8?q?=D0=BA=D0=B0=20LRC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 10 ++++++---- pyproject.toml | 2 +- ymd/cli.py | 29 +++++++++++++++++++++++++---- ymd/core.py | 42 +++++++++++++++++++++++++++++++++++------- 4 files changed, 67 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 026fd6d..0121b9d 100644 --- a/README.md +++ b/README.md @@ -54,9 +54,9 @@ https://yandex-music.readthedocs.io/en/main/token.html yandex-music-downloader --token "<Токен>" --quality 2 --url "https://music.yandex.ru/artist/208167" ``` -### Скачать альбом [Nevermind](https://music.yandex.ru/album/294912) в высоком качестве, загружая тексты песен +### Скачать альбом [Nevermind](https://music.yandex.ru/album/294912) в высоком качестве, загружая тексты песен в формате LRC (с временными метками) ``` -yandex-music-downloader --token "<Токен>" --quality 1 --add-lyrics --url "https://music.yandex.ru/album/294912" +yandex-music-downloader --token "<Токен>" --quality 1 --lyrics-format lrc --url "https://music.yandex.ru/album/294912" ``` ### Скачать трек [Seven Nation Army](https://music.yandex.ru/album/11644078/track/6705392) @@ -67,7 +67,8 @@ yandex-music-downloader --token "<Токен>" --url "https://music.yandex.ru/al ## Использование ``` usage: yandex-music-downloader [-h] [--quality <Качество>] [--skip-existing] - [--add-lyrics] [--embed-cover] + [--lyrics-format {none,text,lrc}] + [--embed-cover] [--cover-resolution <Разрешение обложки>] [--delay <Задержка>] [--stick-to-artist] [--only-music] @@ -88,7 +89,8 @@ options: 2 - Лучшее (FLAC) (по умолчанию: 0) --skip-existing Пропускать уже загруженные треки - --add-lyrics Загружать тексты песен + --lyrics-format {none,text,lrc} + Формат текста песни (по умолчанию: none) --embed-cover Встраивать обложку в аудиофайл --cover-resolution <Разрешение обложки> Разрешение обложки (в пикселях). Передайте "original" для загрузки в оригинальном (наилучшем) разрешении (по умолчанию: 400) diff --git a/pyproject.toml b/pyproject.toml index 4de13ac..bd4da91 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta" [project] name = "yandex-music-downloader" -version = "3.2.0b4" +version = "3.2.1b0" description = "Загрузчик музыки с сервиса Яндекс.Музыка" requires-python = ">=3.9" readme = "README.md" diff --git a/ymd/cli.py b/ymd/cli.py index 61e1bf9..699e27a 100644 --- a/ymd/cli.py +++ b/ymd/cli.py @@ -5,6 +5,7 @@ import logging import re import time import typing +from argparse import ArgumentTypeError from collections.abc import Generator, Iterable from pathlib import Path from typing import Optional, Union @@ -35,7 +36,7 @@ def quality_arg(astr: str) -> int: aint = int(astr) if 0 <= aint <= 2: return aint - raise argparse.ArgumentTypeError("Значение должно быть в промежутке от 0 до 2") + raise ArgumentTypeError("Значение должно быть в промежутке от 0 до 2") def compatibility_level_arg(astr: str) -> int: @@ -44,7 +45,7 @@ def compatibility_level_arg(astr: str) -> int: max_val = core.MAX_COMPATIBILITY_LEVEL if min_val <= aint <= max_val: return aint - raise argparse.ArgumentTypeError( + raise ArgumentTypeError( f"Значение должен быть в промежутке от {min_val} до {max_val}" ) @@ -55,6 +56,13 @@ def cover_resolution_arg(astr: str) -> int: return int(astr) +def lyrics_format_arg(astr: str) -> core.LyricsFormat: + try: + return core.LyricsFormat(astr) + except ValueError: + raise ArgumentTypeError(f"Допустимые значения: {','.join(core.LyricsFormat)}") + + def main(): parser = argparse.ArgumentParser( description="Загрузчик музыки с сервиса Яндекс.Музыка", @@ -73,7 +81,14 @@ def main(): "--skip-existing", action="store_true", help="Пропускать уже загруженные треки" ) common_group.add_argument( - "--add-lyrics", action="store_true", help="Загружать тексты песен" + "--lyrics-format", + type=lyrics_format_arg, + default=core.LyricsFormat.NONE, + help=show_default("Формат текста песни"), + choices=core.LyricsFormat, + ) + common_group.add_argument( + "--add-lyrics", action="store_true", help=argparse.SUPPRESS ) common_group.add_argument( "--embed-cover", action="store_true", help="Встраивать обложку в аудиофайл" @@ -168,6 +183,12 @@ def main(): level=logging.DEBUG if args.debug else logging.ERROR, ) + if args.add_lyrics: + print( + "Аргумент --add-lyrics устарел и будет удален в будущем. Используйте --lyrics-format" + ) + args.lyrics_format = core.LyricsFormat.TEXT + if args.url is not None: parsed_url = urlparse(args.url) path = parsed_url.path @@ -269,7 +290,7 @@ def main(): print(f"{format_info} Загружается {downloadable.path}") core.download_track( track_info=downloadable, - add_lyrics=args.add_lyrics, + lyrics_format=args.lyrics_format, embed_cover=args.embed_cover, cover_resolution=args.cover_resolution, covers_cache=covers_cache, diff --git a/ymd/core.py b/ymd/core.py index f1bdd13..b6c3244 100644 --- a/ymd/core.py +++ b/ymd/core.py @@ -2,9 +2,11 @@ import datetime as dt import random import re import typing +from collections.abc import Iterable from dataclasses import dataclass +from enum import Enum, StrEnum, auto from pathlib import Path -from typing import Optional, Union +from typing import Optional, Type, TypeVar, Union import mutagen from mutagen.flac import FLAC, Picture @@ -39,6 +41,25 @@ MAX_COMPATIBILITY_LEVEL = 1 AUDIO_FILE_SUFFIXES = {".mp3", ".flac", ".m4a"} +_E = TypeVar("_E", bound=Enum) + + +class ParameterEnum(StrEnum): + @classmethod + def available_keys(cls) -> Iterable[str]: + return cls.__members__.keys() + + @classmethod + def from_str(cls: Type[_E], arg: str) -> _E: + return cls[arg.lower()] + + +class LyricsFormat(StrEnum): + NONE = auto() + TEXT = auto() + LRC = auto() + + @dataclass class DownloadableTrack: url: str @@ -201,7 +222,7 @@ def set_tags( def download_track( track_info: DownloadableTrack, cover_resolution: int = DEFAULT_COVER_RESOLUTION, - add_lyrics: bool = False, + lyrics_format: LyricsFormat = LyricsFormat.NONE, embed_cover: bool = False, covers_cache: Optional[dict[int, bytes]] = None, compatibility_level: int = 1, @@ -217,11 +238,18 @@ def download_track( client.request.download(track_info.url, str(target_path)) - lyrics = None - if add_lyrics and (lyrics_info := track.lyrics_info): - if lyrics_info.has_available_text_lyrics: + text_lyrics = None + if lyrics_format != LyricsFormat.NONE and (lyrics_info := track.lyrics_info): + if lyrics_format == LyricsFormat.LRC and lyrics_info.has_available_sync_lyrics: + if track_lyrics := track.get_lyrics(format="LRC"): + lrc_lyrics = track_lyrics.fetch_lyrics() + lrc_path = target_path.with_suffix(".lrc") + if not lrc_path.is_file(): + with open(lrc_path, "w") as f: + f.write(lrc_lyrics) + elif lyrics_info.has_available_text_lyrics: if track_lyrics := track.get_lyrics(format="TEXT"): - lyrics = track_lyrics.fetch_lyrics() + text_lyrics = track_lyrics.fetch_lyrics() cover = None if track.cover_uri is not None: @@ -242,7 +270,7 @@ def download_track( if not cover_path.is_file(): track.download_cover(str(cover_path), cover_size) - set_tags(target_path, track, lyrics, cover, compatibility_level) + set_tags(target_path, track, text_lyrics, cover, compatibility_level) def to_downloadable_track(