app.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. from librespot.audio.decoders import AudioQuality
  2. from tabulate import tabulate
  3. import os
  4. from album import download_album, download_artist_albums
  5. from const import TRACK, NAME, ID, ARTIST, ARTISTS, ITEMS, TRACKS, EXPLICIT, ALBUM, ALBUMS, \
  6. OWNER, PLAYLIST, PLAYLISTS, DISPLAY_NAME
  7. from playlist import get_playlist_songs, get_playlist_info, download_from_user_playlist, download_playlist
  8. from podcast import download_episode, get_show_episodes
  9. from termoutput import Printer, PrintChannel
  10. from track import download_track, get_saved_tracks
  11. from utils import splash, split_input, regex_input_for_urls
  12. from zspotify import ZSpotify
  13. SEARCH_URL = 'https://api.spotify.com/v1/search'
  14. def client(args) -> None:
  15. """ Connects to spotify to perform query's and get songs to download """
  16. ZSpotify(args)
  17. Printer.print(PrintChannel.SPLASH, splash())
  18. if ZSpotify.check_premium():
  19. Printer.print(PrintChannel.SPLASH, '[ DETECTED PREMIUM ACCOUNT - USING VERY_HIGH QUALITY ]\n\n')
  20. ZSpotify.DOWNLOAD_QUALITY = AudioQuality.VERY_HIGH
  21. else:
  22. Printer.print(PrintChannel.SPLASH, '[ DETECTED FREE ACCOUNT - USING HIGH QUALITY ]\n\n')
  23. ZSpotify.DOWNLOAD_QUALITY = AudioQuality.HIGH
  24. if args.download:
  25. urls = []
  26. filename = args.download
  27. if os.path.exists(filename):
  28. with open(filename, 'r', encoding='utf-8') as file:
  29. urls.extend([line.strip() for line in file.readlines()])
  30. download_from_urls(urls)
  31. else:
  32. Printer.print(PrintChannel.ERRORS, f'File {filename} not found.\n')
  33. if args.urls:
  34. download_from_urls(args.urls)
  35. if args.playlist:
  36. download_from_user_playlist()
  37. if args.liked_songs:
  38. for song in get_saved_tracks():
  39. if not song[TRACK][NAME] or not song[TRACK][ID]:
  40. Printer.print(PrintChannel.SKIPS, '### SKIPPING: SONG DOES NOT EXIST ON SPOTIFY ANYMORE ###' + "\n")
  41. else:
  42. download_track('liked', song[TRACK][ID])
  43. if args.search_spotify:
  44. search_text = ''
  45. while len(search_text) == 0:
  46. search_text = input('Enter search or URL: ')
  47. if not download_from_urls([search_text]):
  48. search(search_text)
  49. def download_from_urls(urls: list[str]) -> bool:
  50. """ Downloads from a list of spotify urls """
  51. download = False
  52. for spotify_url in urls:
  53. track_id, album_id, playlist_id, episode_id, show_id, artist_id = regex_input_for_urls(
  54. spotify_url)
  55. if track_id is not None:
  56. download = True
  57. download_track('single', track_id)
  58. elif artist_id is not None:
  59. download = True
  60. download_artist_albums(artist_id)
  61. elif album_id is not None:
  62. download = True
  63. download_album(album_id)
  64. elif playlist_id is not None:
  65. download = True
  66. playlist_songs = get_playlist_songs(playlist_id)
  67. name, _ = get_playlist_info(playlist_id)
  68. enum = 1
  69. char_num = len(str(len(playlist_songs)))
  70. for song in playlist_songs:
  71. if not song[TRACK][NAME] or not song[TRACK][ID]:
  72. Printer.print(PrintChannel.SKIPS, '### SKIPPING: SONG DOES NOT EXIST ON SPOTIFY ANYMORE ###' + "\n")
  73. else:
  74. download_track('playlist', song[TRACK][ID], extra_keys=
  75. {
  76. 'playlist_song_name': song[TRACK][NAME],
  77. 'playlist': name,
  78. 'playlist_num': str(enum).zfill(char_num),
  79. 'playlist_id': playlist_id,
  80. 'playlist_track_id': song[TRACK][ID]
  81. })
  82. enum += 1
  83. elif episode_id is not None:
  84. download = True
  85. download_episode(episode_id)
  86. elif show_id is not None:
  87. download = True
  88. for episode in get_show_episodes(show_id):
  89. download_episode(episode)
  90. return download
  91. def search(search_term):
  92. """ Searches Spotify's API for relevant data """
  93. params = {'limit': '10',
  94. 'offset': '0',
  95. 'q': search_term,
  96. 'type': 'track,album,artist,playlist'}
  97. # Parse args
  98. splits = search_term.split()
  99. for split in splits:
  100. index = splits.index(split)
  101. if split[0] == '-' and len(split) > 1:
  102. if len(splits)-1 == index:
  103. raise IndexError('No parameters passed after option: {}\n'.
  104. format(split))
  105. if split == '-l' or split == '-limit':
  106. try:
  107. int(splits[index+1])
  108. except ValueError:
  109. raise ValueError('Paramater passed after {} option must be an integer.\n'.
  110. format(split))
  111. if int(splits[index+1]) > 50:
  112. raise ValueError('Invalid limit passed. Max is 50.\n')
  113. params['limit'] = splits[index+1]
  114. if split == '-t' or split == '-type':
  115. allowed_types = ['track', 'playlist', 'album', 'artist']
  116. passed_types = []
  117. for i in range(index+1, len(splits)):
  118. if splits[i][0] == '-':
  119. break
  120. if splits[i] not in allowed_types:
  121. raise ValueError('Parameters passed after {} option must be from this list:\n{}'.
  122. format(split, '\n'.join(allowed_types)))
  123. passed_types.append(splits[i])
  124. params['type'] = ','.join(passed_types)
  125. if len(params['type']) == 0:
  126. params['type'] = 'track,album,artist,playlist'
  127. # Clean search term
  128. search_term_list = []
  129. for split in splits:
  130. if split[0] == "-":
  131. break
  132. search_term_list.append(split)
  133. if not search_term_list:
  134. raise ValueError("Invalid query.")
  135. params["q"] = ' '.join(search_term_list)
  136. resp = ZSpotify.invoke_url_with_params(SEARCH_URL, **params)
  137. counter = 1
  138. dics = []
  139. total_tracks = 0
  140. if TRACK in params['type'].split(','):
  141. tracks = resp[TRACKS][ITEMS]
  142. if len(tracks) > 0:
  143. print('### TRACKS ###')
  144. track_data = []
  145. for track in tracks:
  146. if track[EXPLICIT]:
  147. explicit = '[E]'
  148. else:
  149. explicit = ''
  150. track_data.append([counter, f'{track[NAME]} {explicit}',
  151. ','.join([artist[NAME] for artist in track[ARTISTS]])])
  152. dics.append({
  153. ID: track[ID],
  154. NAME: track[NAME],
  155. 'type': TRACK,
  156. })
  157. counter += 1
  158. total_tracks = counter - 1
  159. print(tabulate(track_data, headers=[
  160. 'S.NO', 'Name', 'Artists'], tablefmt='pretty'))
  161. print('\n')
  162. del tracks
  163. del track_data
  164. total_albums = 0
  165. if ALBUM in params['type'].split(','):
  166. albums = resp[ALBUMS][ITEMS]
  167. if len(albums) > 0:
  168. print('### ALBUMS ###')
  169. album_data = []
  170. for album in albums:
  171. album_data.append([counter, album[NAME],
  172. ','.join([artist[NAME] for artist in album[ARTISTS]])])
  173. dics.append({
  174. ID: album[ID],
  175. NAME: album[NAME],
  176. 'type': ALBUM,
  177. })
  178. counter += 1
  179. total_albums = counter - total_tracks - 1
  180. print(tabulate(album_data, headers=[
  181. 'S.NO', 'Album', 'Artists'], tablefmt='pretty'))
  182. print('\n')
  183. del albums
  184. del album_data
  185. total_artists = 0
  186. if ARTIST in params['type'].split(','):
  187. artists = resp[ARTISTS][ITEMS]
  188. if len(artists) > 0:
  189. print('### ARTISTS ###')
  190. artist_data = []
  191. for artist in artists:
  192. artist_data.append([counter, artist[NAME]])
  193. dics.append({
  194. ID: artist[ID],
  195. NAME: artist[NAME],
  196. 'type': ARTIST,
  197. })
  198. counter += 1
  199. total_artists = counter - total_tracks - total_albums - 1
  200. print(tabulate(artist_data, headers=[
  201. 'S.NO', 'Name'], tablefmt='pretty'))
  202. print('\n')
  203. del artists
  204. del artist_data
  205. total_playlists = 0
  206. if PLAYLIST in params['type'].split(','):
  207. playlists = resp[PLAYLISTS][ITEMS]
  208. if len(playlists) > 0:
  209. print('### PLAYLISTS ###')
  210. playlist_data = []
  211. for playlist in playlists:
  212. playlist_data.append(
  213. [counter, playlist[NAME], playlist[OWNER][DISPLAY_NAME]])
  214. dics.append({
  215. ID: playlist[ID],
  216. NAME: playlist[NAME],
  217. 'type': PLAYLIST,
  218. })
  219. counter += 1
  220. total_playlists = counter - total_artists - total_tracks - total_albums - 1
  221. print(tabulate(playlist_data, headers=[
  222. 'S.NO', 'Name', 'Owner'], tablefmt='pretty'))
  223. print('\n')
  224. del playlists
  225. del playlist_data
  226. if total_tracks + total_albums + total_artists + total_playlists == 0:
  227. print('NO RESULTS FOUND - EXITING...')
  228. else:
  229. selection = ''
  230. print('> SELECT A DOWNLOAD OPTION BY ID')
  231. print('> SELECT A RANGE BY ADDING A DASH BETWEEN BOTH ID\'s')
  232. print('> OR PARTICULAR OPTIONS BY ADDING A COMMA BETWEEN ID\'s\n')
  233. while len(selection) == 0:
  234. selection = str(input('ID(s): '))
  235. inputs = split_input(selection)
  236. for pos in inputs:
  237. position = int(pos)
  238. for dic in dics:
  239. print_pos = dics.index(dic) + 1
  240. if print_pos == position:
  241. if dic['type'] == TRACK:
  242. download_track('single', dic[ID])
  243. elif dic['type'] == ALBUM:
  244. download_album(dic[ID])
  245. elif dic['type'] == ARTIST:
  246. download_artist_albums(dic[ID])
  247. else:
  248. download_playlist(dic)