Форматирование кода с помощью yapf
This commit is contained in:
parent
0ed96e7360
commit
4e9bdf4de7
1 changed files with 114 additions and 67 deletions
181
main.py
181
main.py
|
|
@ -40,7 +40,8 @@ class CoverInfo:
|
||||||
|
|
||||||
def cover_url(self, resolution: int) -> Optional[str]:
|
def cover_url(self, resolution: int) -> Optional[str]:
|
||||||
if self.cover_url_template is not None:
|
if self.cover_url_template is not None:
|
||||||
return self.cover_url_template.replace('%%', f'{resolution}x{resolution}')
|
return self.cover_url_template.replace(
|
||||||
|
'%%', f'{resolution}x{resolution}')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_json(cls, data: dict) -> 'CoverInfo':
|
def from_json(cls, data: dict) -> 'CoverInfo':
|
||||||
|
|
@ -84,14 +85,18 @@ class BasicAlbumInfo:
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_json(cls, data: dict) -> Optional['BasicAlbumInfo']:
|
def from_json(cls, data: dict) -> Optional['BasicAlbumInfo']:
|
||||||
if data['metaType'] != 'music':
|
if data['metaType'] != 'music':
|
||||||
logging.info('"%s" пропущен т.к. не является музыкальным альбомом', data['title'])
|
logging.info('"%s" пропущен т.к. не является музыкальным альбомом',
|
||||||
|
data['title'])
|
||||||
return None
|
return None
|
||||||
artists = parse_artists(data['artists'])
|
artists = parse_artists(data['artists'])
|
||||||
title = parse_title(data)
|
title = parse_title(data)
|
||||||
if release_date := data.get('releaseDate'):
|
if release_date := data.get('releaseDate'):
|
||||||
release_date = dt.datetime.fromisoformat(release_date)
|
release_date = dt.datetime.fromisoformat(release_date)
|
||||||
return cls(id=data['id'], title=title, year=data.get('year'),
|
return cls(id=data['id'],
|
||||||
artists=artists, release_date=release_date)
|
title=title,
|
||||||
|
year=data.get('year'),
|
||||||
|
artists=artists,
|
||||||
|
release_date=release_date)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
|
@ -120,15 +125,24 @@ class BasicTrackInfo:
|
||||||
album = BasicAlbumInfo.from_json(album_data)
|
album = BasicAlbumInfo.from_json(album_data)
|
||||||
track_position = album_data.get('trackPosition', track_position)
|
track_position = album_data.get('trackPosition', track_position)
|
||||||
else:
|
else:
|
||||||
album = BasicAlbumInfo(id=track_id, title=title, release_date=None,
|
album = BasicAlbumInfo(id=track_id,
|
||||||
year=None, artists=artists)
|
title=title,
|
||||||
|
release_date=None,
|
||||||
|
year=None,
|
||||||
|
artists=artists)
|
||||||
if album is None:
|
if album is None:
|
||||||
raise ValueError
|
raise ValueError
|
||||||
cover_info = CoverInfo.from_json(data)
|
cover_info = CoverInfo.from_json(data)
|
||||||
has_lyrics = data['lyricsInfo']['hasAvailableTextLyrics']
|
has_lyrics = data['lyricsInfo']['hasAvailableTextLyrics']
|
||||||
return cls(title=title, id=track_id, real_id=data['realId'],
|
return cls(title=title,
|
||||||
number=track_position['index'], disc_number=track_position['volume'],
|
id=track_id,
|
||||||
artists=artists, album=album, has_lyrics=has_lyrics, cover_info=cover_info)
|
real_id=data['realId'],
|
||||||
|
number=track_position['index'],
|
||||||
|
disc_number=track_position['volume'],
|
||||||
|
artists=artists,
|
||||||
|
album=album,
|
||||||
|
has_lyrics=has_lyrics,
|
||||||
|
cover_info=cover_info)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
|
@ -169,11 +183,14 @@ class ArtistInfo:
|
||||||
albums = map(BasicAlbumInfo.from_json, data.get('albums', []))
|
albums = map(BasicAlbumInfo.from_json, data.get('albums', []))
|
||||||
albums = [a for a in albums if a is not None]
|
albums = [a for a in albums if a is not None]
|
||||||
cover_info = CoverInfo.from_json(data)
|
cover_info = CoverInfo.from_json(data)
|
||||||
return cls(id=str(artist['id']), name=artist['name'],
|
return cls(id=str(artist['id']),
|
||||||
cover_info=cover_info, albums=albums)
|
name=artist['name'],
|
||||||
|
cover_info=cover_info,
|
||||||
|
albums=albums)
|
||||||
|
|
||||||
|
|
||||||
def get_track_download_url(session: Session, track: BasicTrackInfo, hq: bool) -> str:
|
def get_track_download_url(session: Session, track: BasicTrackInfo,
|
||||||
|
hq: bool) -> str:
|
||||||
resp = session.get('https://music.yandex.ru/api/v2.1/handlers/track'
|
resp = session.get('https://music.yandex.ru/api/v2.1/handlers/track'
|
||||||
f'/{track.id}:{track.album.id}'
|
f'/{track.id}:{track.album.id}'
|
||||||
'/web-album_track-track-track-main/download/m'
|
'/web-album_track-track-track-main/download/m'
|
||||||
|
|
@ -204,47 +221,39 @@ def download_bytes(session: Session, url: str) -> bytes:
|
||||||
|
|
||||||
|
|
||||||
def get_full_track_info(session: Session, track_id: str) -> FullTrackInfo:
|
def get_full_track_info(session: Session, track_id: str) -> FullTrackInfo:
|
||||||
params = {
|
params = {'track': track_id, 'lang': 'ru'}
|
||||||
'track': track_id,
|
resp = session.get('https://music.yandex.ru/handlers/track.jsx',
|
||||||
'lang': 'ru'
|
params=params)
|
||||||
}
|
|
||||||
resp = session.get('https://music.yandex.ru/handlers/track.jsx', params=params)
|
|
||||||
return FullTrackInfo.from_json(resp.json())
|
return FullTrackInfo.from_json(resp.json())
|
||||||
|
|
||||||
|
|
||||||
def get_full_album_info(session: Session, album_id: str) -> FullAlbumInfo:
|
def get_full_album_info(session: Session, album_id: str) -> FullAlbumInfo:
|
||||||
params = {
|
params = {'album': album_id, 'lang': 'ru'}
|
||||||
'album': album_id,
|
resp = session.get('https://music.yandex.ru/handlers/album.jsx',
|
||||||
'lang': 'ru'
|
params=params)
|
||||||
}
|
|
||||||
resp = session.get('https://music.yandex.ru/handlers/album.jsx', params=params)
|
|
||||||
return FullAlbumInfo.from_json(resp.json())
|
return FullAlbumInfo.from_json(resp.json())
|
||||||
|
|
||||||
|
|
||||||
def get_artist_info(session: Session, artist_id: str) -> ArtistInfo:
|
def get_artist_info(session: Session, artist_id: str) -> ArtistInfo:
|
||||||
params = {
|
params = {'artist': artist_id, 'what': 'albums', 'lang': 'ru'}
|
||||||
'artist': artist_id,
|
resp = session.get('https://music.yandex.ru/handlers/artist.jsx',
|
||||||
'what': 'albums',
|
params=params)
|
||||||
'lang': 'ru'
|
|
||||||
}
|
|
||||||
resp = session.get('https://music.yandex.ru/handlers/artist.jsx', params=params)
|
|
||||||
return ArtistInfo.from_json(resp.json())
|
return ArtistInfo.from_json(resp.json())
|
||||||
|
|
||||||
|
|
||||||
def get_playlist(session: Session, playlist: PlaylistId) -> list[BasicTrackInfo]:
|
def get_playlist(session: Session,
|
||||||
params = {
|
playlist: PlaylistId) -> list[BasicTrackInfo]:
|
||||||
'owner': playlist.owner,
|
params = {'owner': playlist.owner, 'kinds': playlist.kind, 'lang': 'ru'}
|
||||||
'kinds': playlist.kind,
|
resp = session.get('https://music.yandex.ru/handlers/playlist.jsx',
|
||||||
'lang': 'ru'
|
params=params)
|
||||||
}
|
|
||||||
resp = session.get('https://music.yandex.ru/handlers/playlist.jsx', params=params)
|
|
||||||
raw_tracks = resp.json()['playlist'].get('tracks', [])
|
raw_tracks = resp.json()['playlist'].get('tracks', [])
|
||||||
tracks = map(BasicTrackInfo.from_json, raw_tracks)
|
tracks = map(BasicTrackInfo.from_json, raw_tracks)
|
||||||
tracks = [t for t in tracks if t is not None]
|
tracks = [t for t in tracks if t is not None]
|
||||||
return tracks
|
return tracks
|
||||||
|
|
||||||
|
|
||||||
def prepare_track_path(path: Path, prepare_path: bool, track: BasicTrackInfo) -> Path:
|
def prepare_track_path(path: Path, prepare_path: bool,
|
||||||
|
track: BasicTrackInfo) -> Path:
|
||||||
path_str = str(path)
|
path_str = str(path)
|
||||||
repl_dict = {
|
repl_dict = {
|
||||||
'#number': track.number,
|
'#number': track.number,
|
||||||
|
|
@ -266,7 +275,8 @@ def prepare_track_path(path: Path, prepare_path: bool, track: BasicTrackInfo) ->
|
||||||
def set_id3_tags(path: Path, track: BasicTrackInfo, lyrics: Optional[str],
|
def set_id3_tags(path: Path, track: BasicTrackInfo, lyrics: Optional[str],
|
||||||
album_cover: Optional[bytes]) -> None:
|
album_cover: Optional[bytes]) -> None:
|
||||||
if track.album.release_date is not None:
|
if track.album.release_date is not None:
|
||||||
release_date = eyed3.core.Date(*track.album.release_date.timetuple()[:6])
|
release_date = eyed3.core.Date(
|
||||||
|
*track.album.release_date.timetuple()[:6])
|
||||||
else:
|
else:
|
||||||
release_date = track.album.year
|
release_date = track.album.year
|
||||||
audiofile = eyed3.load(path)
|
audiofile = eyed3.load(path)
|
||||||
|
|
@ -293,7 +303,8 @@ def set_id3_tags(path: Path, track: BasicTrackInfo, lyrics: Optional[str],
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
eyed3.log.setLevel("ERROR")
|
eyed3.log.setLevel("ERROR")
|
||||||
parser = argparse.ArgumentParser(description='Загрузчик музыки с сервиса Яндекс.Музыка')
|
parser = argparse.ArgumentParser(
|
||||||
|
description='Загрузчик музыки с сервиса Яндекс.Музыка')
|
||||||
|
|
||||||
def help_str(text: Optional[str] = None) -> str:
|
def help_str(text: Optional[str] = None) -> str:
|
||||||
default = 'по умолчанию: %(default)s'
|
default = 'по умолчанию: %(default)s'
|
||||||
|
|
@ -302,54 +313,84 @@ if __name__ == '__main__':
|
||||||
return f'{text} ({default})'
|
return f'{text} ({default})'
|
||||||
|
|
||||||
common_group = parser.add_argument_group('Общие параметры')
|
common_group = parser.add_argument_group('Общие параметры')
|
||||||
common_group.add_argument('--hq', action='store_true',
|
common_group.add_argument('--hq',
|
||||||
|
action='store_true',
|
||||||
help='Загружать треки в высоком качестве')
|
help='Загружать треки в высоком качестве')
|
||||||
common_group.add_argument('--skip-existing', action='store_true',
|
common_group.add_argument('--skip-existing',
|
||||||
|
action='store_true',
|
||||||
help='Пропускать уже загруженные треки')
|
help='Пропускать уже загруженные треки')
|
||||||
common_group.add_argument('--add-lyrics', action='store_true',
|
common_group.add_argument('--add-lyrics',
|
||||||
|
action='store_true',
|
||||||
help='Загружать тексты песен')
|
help='Загружать тексты песен')
|
||||||
common_group.add_argument('--embed-cover', action='store_true',
|
common_group.add_argument('--embed-cover',
|
||||||
|
action='store_true',
|
||||||
help='Встраивать обложку в .mp3 файл')
|
help='Встраивать обложку в .mp3 файл')
|
||||||
common_group.add_argument('--stick-to-artist', action='store_true',
|
common_group.add_argument(
|
||||||
help='Загружать только альбомы созданные данным исполнителем')
|
'--stick-to-artist',
|
||||||
common_group.add_argument('--cover-resolution', default=DEFAULT_COVER_RESOLUTION,
|
action='store_true',
|
||||||
metavar='<Разрешение обложки>', type=int, help=help_str(None))
|
help='Загружать только альбомы созданные данным исполнителем')
|
||||||
common_group.add_argument('--delay', default=DEFAULT_DELAY, metavar='<Задержка>',
|
common_group.add_argument('--cover-resolution',
|
||||||
type=int, help=help_str('Задержка между запросами, в секундах'))
|
default=DEFAULT_COVER_RESOLUTION,
|
||||||
common_group.add_argument('--log-level', default=DEFAULT_LOG_LEVEL,
|
metavar='<Разрешение обложки>',
|
||||||
|
type=int,
|
||||||
|
help=help_str(None))
|
||||||
|
common_group.add_argument(
|
||||||
|
'--delay',
|
||||||
|
default=DEFAULT_DELAY,
|
||||||
|
metavar='<Задержка>',
|
||||||
|
type=int,
|
||||||
|
help=help_str('Задержка между запросами, в секундах'))
|
||||||
|
common_group.add_argument('--log-level',
|
||||||
|
default=DEFAULT_LOG_LEVEL,
|
||||||
choices=logging._nameToLevel.keys())
|
choices=logging._nameToLevel.keys())
|
||||||
|
|
||||||
def args_playlist_id(arg: str) -> PlaylistId:
|
def args_playlist_id(arg: str) -> PlaylistId:
|
||||||
arr = arg.split('/')
|
arr = arg.split('/')
|
||||||
return PlaylistId(owner=arr[0], kind=int(arr[1]))
|
return PlaylistId(owner=arr[0], kind=int(arr[1]))
|
||||||
|
|
||||||
id_group_meta = parser.add_argument_group('ID')
|
id_group_meta = parser.add_argument_group('ID')
|
||||||
id_group = id_group_meta.add_mutually_exclusive_group(required=True)
|
id_group = id_group_meta.add_mutually_exclusive_group(required=True)
|
||||||
id_group.add_argument('--artist-id', metavar='<ID исполнителя>')
|
id_group.add_argument('--artist-id', metavar='<ID исполнителя>')
|
||||||
id_group.add_argument('--album-id', metavar='<ID альбома>')
|
id_group.add_argument('--album-id', metavar='<ID альбома>')
|
||||||
id_group.add_argument('--track-id', metavar='<ID трека>')
|
id_group.add_argument('--track-id', metavar='<ID трека>')
|
||||||
id_group.add_argument('--playlist-id', type=args_playlist_id,
|
id_group.add_argument('--playlist-id',
|
||||||
|
type=args_playlist_id,
|
||||||
metavar='<владелец плейлиста>/<тип плейлиста>')
|
metavar='<владелец плейлиста>/<тип плейлиста>')
|
||||||
id_group.add_argument('-u', '--url', help='URL исполнителя/альбома/трека/плейлиста')
|
id_group.add_argument('-u',
|
||||||
|
'--url',
|
||||||
|
help='URL исполнителя/альбома/трека/плейлиста')
|
||||||
|
|
||||||
path_group = parser.add_argument_group('Указание пути')
|
path_group = parser.add_argument_group('Указание пути')
|
||||||
path_group.add_argument('--unsafe-path', action='store_true',
|
path_group.add_argument('--unsafe-path',
|
||||||
|
action='store_true',
|
||||||
help='Не очищать путь от недопустимых символов')
|
help='Не очищать путь от недопустимых символов')
|
||||||
path_group.add_argument('--dir', default='.', metavar='<Папка>',
|
path_group.add_argument('--dir',
|
||||||
help=help_str('Папка для загрузки музыки'), type=Path)
|
default='.',
|
||||||
path_group.add_argument('--path-pattern', default=DEFAULT_PATH_PATTERN,
|
metavar='<Папка>',
|
||||||
metavar='<Паттерн>', type=Path,
|
help=help_str('Папка для загрузки музыки'),
|
||||||
help=help_str('Поддерживает следующие заполнители:'
|
type=Path)
|
||||||
' #number, #artist, #album-artist, #title,'
|
path_group.add_argument('--path-pattern',
|
||||||
' #album, #year'))
|
default=DEFAULT_PATH_PATTERN,
|
||||||
|
metavar='<Паттерн>',
|
||||||
|
type=Path,
|
||||||
|
help=help_str(
|
||||||
|
'Поддерживает следующие заполнители:'
|
||||||
|
' #number, #artist, #album-artist, #title,'
|
||||||
|
' #album, #year'))
|
||||||
|
|
||||||
auth_group = parser.add_argument_group('Авторизация')
|
auth_group = parser.add_argument_group('Авторизация')
|
||||||
auth_group.add_argument('--session-id', required=True, metavar='<ID сессии>')
|
auth_group.add_argument('--session-id',
|
||||||
auth_group.add_argument('--user-agent', default=DEFAULT_USER_AGENT, metavar='<User-Agent>',
|
required=True,
|
||||||
|
metavar='<ID сессии>')
|
||||||
|
auth_group.add_argument('--user-agent',
|
||||||
|
default=DEFAULT_USER_AGENT,
|
||||||
|
metavar='<User-Agent>',
|
||||||
help=help_str())
|
help=help_str())
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
logging.basicConfig(format='%(asctime)s |%(levelname)s| %(message)s',
|
logging.basicConfig(format='%(asctime)s |%(levelname)s| %(message)s',
|
||||||
datefmt='%H:%M:%S', level=args.log_level.upper())
|
datefmt='%H:%M:%S',
|
||||||
|
level=args.log_level.upper())
|
||||||
|
|
||||||
def response_hook(resp, *_args, **_kwargs):
|
def response_hook(resp, *_args, **_kwargs):
|
||||||
if logging.root.isEnabledFor(logging.DEBUG):
|
if logging.root.isEnabledFor(logging.DEBUG):
|
||||||
|
|
@ -361,7 +402,8 @@ if __name__ == '__main__':
|
||||||
session.hooks = {'response': response_hook}
|
session.hooks = {'response': response_hook}
|
||||||
session.cookies.set('Session_id', args.session_id, domain='yandex.ru')
|
session.cookies.set('Session_id', args.session_id, domain='yandex.ru')
|
||||||
session.headers['User-Agent'] = args.user_agent
|
session.headers['User-Agent'] = args.user_agent
|
||||||
session.headers['X-Retpath-Y'] = urllib.parse.quote_plus('https://music.yandex.ru')
|
session.headers['X-Retpath-Y'] = urllib.parse.quote_plus(
|
||||||
|
'https://music.yandex.ru')
|
||||||
|
|
||||||
result_tracks: list[BasicTrackInfo] = []
|
result_tracks: list[BasicTrackInfo] = []
|
||||||
|
|
||||||
|
|
@ -373,7 +415,8 @@ if __name__ == '__main__':
|
||||||
elif match := TRACK_RE.search(args.url):
|
elif match := TRACK_RE.search(args.url):
|
||||||
args.track_id = match.group(1)
|
args.track_id = match.group(1)
|
||||||
elif match := PLAYLIST_RE.search(args.url):
|
elif match := PLAYLIST_RE.search(args.url):
|
||||||
args.playlist_id = PlaylistId(owner=match.group(1), kind=int(match.group(2)))
|
args.playlist_id = PlaylistId(owner=match.group(1),
|
||||||
|
kind=int(match.group(2)))
|
||||||
else:
|
else:
|
||||||
print('Параметер url указан в неверном формате')
|
print('Параметер url указан в неверном формате')
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
@ -387,7 +430,9 @@ if __name__ == '__main__':
|
||||||
albums_count = 0
|
albums_count = 0
|
||||||
for album in artist_info.albums:
|
for album in artist_info.albums:
|
||||||
if args.stick_to_artist and album.artists[0] != artist_info.name:
|
if args.stick_to_artist and album.artists[0] != artist_info.name:
|
||||||
logging.info('Альбом "%s" пропущен из-за флага --stick-to-artist', album.title)
|
logging.info(
|
||||||
|
'Альбом "%s" пропущен из-за флага --stick-to-artist',
|
||||||
|
album.title)
|
||||||
continue
|
continue
|
||||||
full_album = get_full_album_info(session, album.id)
|
full_album = get_full_album_info(session, album.id)
|
||||||
result_tracks.extend(full_album.tracks)
|
result_tracks.extend(full_album.tracks)
|
||||||
|
|
@ -408,7 +453,8 @@ if __name__ == '__main__':
|
||||||
|
|
||||||
for track in result_tracks:
|
for track in result_tracks:
|
||||||
album = track.album
|
album = track.album
|
||||||
save_path = args.dir / prepare_track_path(args.path_pattern, not args.unsafe_path, track)
|
save_path = args.dir / prepare_track_path(args.path_pattern,
|
||||||
|
not args.unsafe_path, track)
|
||||||
if args.skip_existing and save_path.is_file():
|
if args.skip_existing and save_path.is_file():
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|
@ -435,7 +481,8 @@ if __name__ == '__main__':
|
||||||
if cached_cover := covers.get(album.id):
|
if cached_cover := covers.get(album.id):
|
||||||
cover = cached_cover
|
cover = cached_cover
|
||||||
else:
|
else:
|
||||||
cover = covers[album.id] = download_bytes(session, cover_url)
|
cover = covers[album.id] = download_bytes(
|
||||||
|
session, cover_url)
|
||||||
else:
|
else:
|
||||||
cover_path = save_dir / 'cover.jpg'
|
cover_path = save_dir / 'cover.jpg'
|
||||||
if not cover_path.is_file():
|
if not cover_path.is_file():
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue