diff --git a/ncm/api.py b/ncm/api.py index f182880..6d28514 100644 --- a/ncm/api.py +++ b/ncm/api.py @@ -4,7 +4,7 @@ import requests import time from ncm.encrypt import encrypted_request -from ncm.constants import headers +from ncm.constants import headers, get_program_url, program_download_url from ncm.constants import song_download_url from ncm.constants import get_song_url from ncm.constants import get_album_url @@ -52,9 +52,31 @@ class CloudApi(object): """ url = get_song_url(song_id) result = self.get_request(url) - return result['songs'][0] + def get_program(self, program_id): + """ + Get program info by its id + :param bit_rate: + :param program_id: + :return: + """ + url = get_program_url(program_id) + csrf = '' + result = self.post_request(url, {'id': program_id, 'csrf_token': csrf}) + return result['program'] + + def get_program_url(self, program, encode_type="aac", level="standard"): + """ + Get the download url of the program + :param program + :param encode_type: + :param level: + :return: + """ + id = program['mainSong']['id'] + return self.get_song_url(id) + def get_album_songs(self, album_id): """ Get all album songs info by album id diff --git a/ncm/constants.py b/ncm/constants.py index 954a379..c490129 100644 --- a/ncm/constants.py +++ b/ncm/constants.py @@ -7,22 +7,26 @@ modulus = '00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3 nonce = '0CoJUm6Qyw8W8jud' pub_key = '010001' - headers = { 'Accept': '*/*', 'Host': 'music.163.com', 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36', 'Referer': 'http://music.163.com', - 'Cookie': 'appver=2.0.2; _ntes_nuid={}; NMTID={}'.format(''.join(random.choice(string.ascii_letters + string.digits) for _ in range(32)), - ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(32))) + 'Cookie': 'appver=2.0.2; _ntes_nuid={}; NMTID={}'.format( + ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(32)), + ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(32))) } song_download_url = 'http://music.163.com/weapi/song/enhance/player/url?csrf_token=' - +program_download_url = 'http://music.163.com/weapi/song/enhance/player/url/v1?csrf_token=' def get_song_url(song_id): return 'http://music.163.com/api/song/detail/?ids=[{}]'.format(song_id) +def get_program_url(program_id): + return 'http://music.163.com/weapi/dj/program/detail?csrf_token=' + + def get_album_url(album_id): return 'http://music.163.com/api/album/{}/'.format(album_id) diff --git a/ncm/downloader.py b/ncm/downloader.py index b35ce7c..f611807 100644 --- a/ncm/downloader.py +++ b/ncm/downloader.py @@ -22,13 +22,17 @@ def download_song_by_id(song_id, download_folder, sub_folder=True): download_song_by_song(song, download_folder, sub_folder) -def download_song_by_song(song, download_folder, sub_folder=True): +def download_song_by_song(song, download_folder, sub_folder=True, program=False): # get song info api = CloudApi() song_id = song['id'] song_name = format_string(song['name']) - artist_name = format_string(song['artists'][0]['name']) - album_name = format_string(song['album']['name']) + if program: + artist_name = format_string(song['dj']['nickname']) + album_name = format_string(song['dj']['brand']) + else: + artist_name = format_string(song['artists'][0]['name']) + album_name = format_string(song['album']['name']) # update song file name by config song_file_name = '{}.mp3'.format(song_name) @@ -51,7 +55,11 @@ def download_song_by_song(song, download_folder, sub_folder=True): song_download_folder = download_folder # download song - song_url = api.get_song_url(song_id) + if program: + song_url = api.get_program_url(song, level="standard") + else: + song_url = api.get_song_url(song_id) + if song_url is None: print('Song <<{}>> is not available due to copyright issue!'.format(song_name)) return @@ -61,9 +69,16 @@ def download_song_by_song(song, download_folder, sub_folder=True): return # download cover - cover_url = song['album']['blurPicUrl'] + if program: + cover_url = song['coverUrl'] + else: + cover_url = song['album']['coverUrl'] + if cover_url is None: - cover_url = song['album']['picUrl'] + if program: + cover_url = song['mainSong']['album']['picUrl'] + else: + cover_url = song['album']['picUrl'] cover_file_name = 'cover_{}.jpg'.format(song_id) download_file(cover_url, cover_file_name, song_download_folder) @@ -73,14 +88,13 @@ def download_song_by_song(song, download_folder, sub_folder=True): # add metadata for song song_file_path = os.path.join(song_download_folder, song_file_name) cover_file_path = os.path.join(song_download_folder, cover_file_name) - add_metadata_to_song(song_file_path, cover_file_path, song) + add_metadata_to_song(song_file_path, cover_file_path, song, program) # delete cover file os.remove(cover_file_path) def download_file(file_url, file_name, folder): - if not os.path.exists(folder): os.makedirs(folder) file_path = os.path.join(folder, file_name) @@ -113,8 +127,8 @@ class ProgressBar(object): self.end_str = '\r' def __get_info(self): - return 'Progress: {:6.2f}%, {:8.2f}KB, [{:.30}]'\ - .format(self.count/self.total*100, self.total/1024, self.file_name) + return 'Progress: {:6.2f}%, {:8.2f}KB, [{:.30}]' \ + .format(self.count / self.total * 100, self.total / 1024, self.file_name) def refresh(self, count): self.count += count diff --git a/ncm/file_util.py b/ncm/file_util.py index 2f1a221..3be0745 100644 --- a/ncm/file_util.py +++ b/ncm/file_util.py @@ -18,7 +18,7 @@ def resize_img(file_path, max_size=(640, 640), quality=90): img.save(file_path, quality=quality) -def add_metadata_to_song(file_path, cover_path, song): +def add_metadata_to_song(file_path, cover_path, song, is_program = False): # If no ID3 tags in mp3 file try: audio = MP3(file_path, ID3=ID3) @@ -52,12 +52,21 @@ def add_metadata_to_song(file_path, cover_path, song): ) ) # add artist name - id3.add( - TPE1( - encoding=3, - text=song['artists'][0]['name'] + if is_program: + id3.add( + TPE1( + encoding=3, + text=song['dj']['nickname'] + ) ) - ) + else: + id3.add( + TPE1( + encoding=3, + text=song['artists'][0]['name'] + ) + ) + # add song name id3.add( TIT2( @@ -66,17 +75,27 @@ def add_metadata_to_song(file_path, cover_path, song): ) ) # add album name - id3.add( - TALB( - encoding=3, - text=song['album']['name'] + if is_program: + id3.add( + TALB( + encoding=3, + text=song['dj']['brand'] + ) + ) + else: + id3.add( + TALB( + encoding=3, + text=song['album']['name'] + ) ) - ) #add track no - id3.add( - TRCK( - encoding=3, - text="%s/%s" %(song['no'],song['album']['size']) + if not is_program: + id3.add( + TRCK( + encoding=3, + text="%s/%s" %(song['no'],song['album']['size']) + ) ) - ) + # programs doesn't have a valid album info. id3.save(v2_version=3) diff --git a/ncm/start.py b/ncm/start.py index b5240e8..61e8461 100644 --- a/ncm/start.py +++ b/ncm/start.py @@ -34,6 +34,13 @@ def download_album_songs(album_id): download_song_by_song(song, folder_path, False) +def download_program(program_id): + program = api.get_program(program_id) + folder_name = format_string(program['dj']['brand']) + ' - album' + folder_path = os.path.join(config.DOWNLOAD_DIR, folder_name) + download_song_by_song(program, folder_path, False, True) + + def download_playlist_songs(playlist_id): songs, playlist_name = api.get_playlist_songs(playlist_id) folder_name = format_string(playlist_name) + ' - playlist' @@ -62,6 +69,8 @@ def main(): help='Download an artist hot 50 songs by artist_id') parser.add_argument('-a', metavar='album_id', dest='album_id', help='Download an album all songs by album_id') + parser.add_argument('-dj', metavar='program_id', dest='program_id', + help='Download a program by program_id') parser.add_argument('-p', metavar='playlist_id', dest='playlist_id', help='Download a playlist all songs by playlist_id') args = parser.parse_args() @@ -76,6 +85,8 @@ def main(): download_album_songs(get_parse_id(args.album_id)) elif args.playlist_id: download_playlist_songs(get_parse_id(args.playlist_id)) + elif args.program_id: + download_program(get_parse_id(args.program_id)) if __name__ == '__main__':