player.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. #!/usr/bin/env python3
  2. import argparse
  3. import re
  4. import sys
  5. from pathlib import Path
  6. from subprocess import call
  7. from time import sleep
  8. from typing import List
  9. from yandex_music import Client
  10. DEFAULT_CACHE_FOLDER = Path(__file__).resolve().parent / '.YMcache'
  11. CONFIG_NAME = 'config'
  12. MAX_ERRORS = 3
  13. parser = argparse.ArgumentParser()
  14. parser.add_argument('playlist', choices=('likes', 'user'), help='playlist type')
  15. parser.add_argument('--playlist-name', help='name of user playlist')
  16. parser.add_argument('--skip', metavar='N', type=int, help='skip first %(metavar)s tracks')
  17. parser.add_argument('--shuffle', action='store_true', help='randomize tracks order')
  18. parser.add_argument(
  19. '--token', default=DEFAULT_CACHE_FOLDER / CONFIG_NAME, help='YM API token as string or path to file'
  20. )
  21. parser.add_argument('--no-save-token', action='store_true', help="do'nt save token in cache folder")
  22. parser.add_argument('--cache-folder', type=Path, default=DEFAULT_CACHE_FOLDER, help='cached tracks folder')
  23. parser.add_argument('--audio-player', default='cvlc', help='player to use')
  24. parser.add_argument(
  25. '--audio-player-args', action='append', default=[], help='args for --audio-player (can be specified multiple times)'
  26. )
  27. parser.add_argument('--print-args', action='store_true', help='print arguments (including default values) and exit')
  28. args = parser.parse_args()
  29. if args.audio_player is parser.get_default('audio_player') and args.audio_player_args is parser.get_default(
  30. 'audio_player_args'
  31. ):
  32. args.audio_player_args = ['--play-and-exit', '--quiet']
  33. player_cmd: List[int] = args.audio_player_args
  34. player_cmd.insert(0, args.audio_player)
  35. player_cmd.append('') # will be replaced with filename
  36. if args.print_args:
  37. print(args)
  38. sys.exit()
  39. if isinstance(args.token, str) and re.match(r'^[A-Za-z0-9]{39}$', args.token):
  40. if not args.no_save_token:
  41. parser.get_default('token').write_text(args.token)
  42. else:
  43. try:
  44. args.token = Path(args.token).read_text()
  45. except FileNotFoundError:
  46. print('Config file not found. Use --token to create it')
  47. sys.exit(2)
  48. client = Client(args.token, report_unknown_fields=False).init()
  49. print('Hello,', client.me.account.first_name)
  50. if client.me.account.now and client.me.account.now.split('T')[0] == client.me.account.birthday:
  51. print('Happy birthday!')
  52. if args.playlist == 'user':
  53. user_playlists = client.users_playlists_list()
  54. if not args.playlist_name:
  55. print('specify --playlist-name', [p.title for p in user_playlists])
  56. sys.exit(1)
  57. playlist = next((p for p in user_playlists if p.title == args.playlist_name), None)
  58. if playlist is None:
  59. print(f'playlist "{args.playlist_name}" not found')
  60. sys.exit(1)
  61. total_tracks = playlist.track_count
  62. print(f'Playing {playlist.title} ({playlist.playlist_id}). {total_tracks} track(s).')
  63. tracks = playlist.tracks if playlist.tracks else playlist.fetch_tracks()
  64. elif args.playlist == 'likes':
  65. tracks = client.users_likes_tracks()
  66. total_tracks = len(tracks.tracks)
  67. print(f'Playing liked tracks. {total_tracks} track(s).')
  68. if args.shuffle:
  69. from random import shuffle
  70. shuffle(tracks.tracks)
  71. error_count = 0
  72. for i, short_track in enumerate(tracks):
  73. if args.skip and args.skip > i:
  74. continue
  75. while error_count < MAX_ERRORS:
  76. try:
  77. track = short_track.track if short_track.track else short_track.fetchTrack()
  78. print(f'Now playing {i + 1}/{total_tracks}: ', end='')
  79. print('|'.join(a.name for a in track.artists), end='')
  80. print(f" [{'|'.join(a.title for a in track.albums)}]", '~', track.title)
  81. artist_dir = Path(f'{track.artists[0].name}_{track.artists[0].id}')
  82. album_dir = Path(f'{track.albums[0].title}_{track.albums[0].id}')
  83. file_path = args.cache_folder / artist_dir / album_dir / f'{track.title}_{track.id}.mp3'
  84. if not file_path.exists():
  85. print('Downloading...')
  86. file_path.parent.mkdir(parents=True, exist_ok=True)
  87. while error_count < MAX_ERRORS:
  88. try:
  89. track.download(file_path)
  90. error_count = 0
  91. break
  92. except Exception as e:
  93. print('Error:', e)
  94. error_count += 1
  95. sleep(1)
  96. player_cmd[-1] = file_path
  97. if call(player_cmd) == 0:
  98. error_count = 0
  99. else:
  100. error_count += 1
  101. break
  102. except Exception as e:
  103. print('Error:', e)
  104. error_count += 1
  105. sleep(1)