Поддержка LRC
This commit is contained in:
parent
7bdbee96fe
commit
6dc2d0dc56
4 changed files with 67 additions and 16 deletions
10
README.md
10
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)
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
29
ymd/cli.py
29
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,
|
||||
|
|
|
|||
42
ymd/core.py
42
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(
|
||||
|
|
|
|||
Loading…
Reference in a new issue