def __downloadTrack__(conf, track, album=None, playlist=None): msg, stream = API.getStreamUrl(track.id, conf.audioQuality) if not isNull(msg): Printf.err(track.title + "." + msg) return path = __getTrackPath__(conf, track, stream, album, playlist) # Printf.info("Download \"" + track.title + "\" Codec: " + stream.codec) check, err = downloadFileRetErr(stream.url, path + '.part', showprogress=True, stimeout=20) if not check: Printf.err("\n Download failed!" + getFileName(path)) return # encrypted -> decrypt and remove encrypted file if isNull(stream.encryptionKey): os.replace(path + '.part', path) else: key, nonce = decrypt_security_token(stream.encryptionKey) decrypt_file(path + '.part', path, key, nonce) os.remove(path + '.part') path = __convertToM4a__(path, stream.codec) __setMetaData__(track, album, path) Printf.success(getFileName(path))
def searchTrack(user, lang, field, song, conf): __loadAPI__(user) msg, obj = API.searchSong(field, song, limit=10) for item in obj: if field == 'track': print( green(f"Enter [{obj.index(item)}]: ") + f"{item.title} - {item.artist.name} - {item.album.title}") elif field == 'album': print( green(f"Enter [{obj.index(item)}]: ") + f"{item.title} - {item.artist.name}") else: print( green(f"Enter [{obj.index(item)}]: ") + f"{item.title} - {item.numberOfTracks} Songs") try: choice = int(Printf.enter(lang)) if (choice >= 0 and choice < len(obj)): if field == 'track': __track__(conf, obj[choice]) elif field == 'album': __album__(conf, obj[choice]) else: __playlist__(conf, obj[choice]) else: os.system('clear') Printf.err("Invalid option!") except ValueError: os.system('clear') Printf.err("Invalid option!")
def start(user, conf, string): __loadAPI__(user) if aigpy.string.isNull(string): Printf.err('Please enter something.') return strings = string.split(" ") for item in strings: if aigpy.string.isNull(item): continue if os.path.exists(item): __file__(user, conf, item) return msg, etype, obj = API.getByString(item) if etype == Type.Null or not aigpy.string.isNull(msg): Printf.err(msg + " [" + item + "]") return if etype == Type.Album: __album__(conf, obj) if etype == Type.Track: __track__(conf, obj) if etype == Type.Video: __loadVideoAPI__(user) __video__(conf, obj) if etype == Type.Artist: __artist__(conf, obj) if etype == Type.Playlist: __playlist__(conf, obj)
def checkLogin(): if not isNull(TOKEN.accessToken): #print('Checking Access Token...') #add to translations msg, check = API.verifyAccessToken(TOKEN.accessToken) if check == True: Printf.info( LANG.MSG_VALID_ACCESSTOKEN.format( displayTime(int(TOKEN.expiresAfter - time.time())))) return else: Printf.info(LANG.MSG_INVAILD_ACCESSTOKEN) msg, check = API.refreshAccessToken(TOKEN.refreshToken) if check == True: Printf.success( LANG.MSG_VALID_ACCESSTOKEN.format( displayTime(int(API.key.expiresIn)))) TOKEN.userid = API.key.userId TOKEN.countryCode = API.key.countryCode TOKEN.accessToken = API.key.accessToken TOKEN.expiresAfter = time.time() + int(API.key.expiresIn) TokenSettings.save(TOKEN) return else: Printf.err(msg) tmp = TokenSettings() #clears saved tokens TokenSettings.save(tmp) login() return
def __artist__(conf, obj): msg, albums = API.getArtistAlbums(obj.id, conf.includeEP) Printf.artist(obj, len(albums)) if not isNull(msg): Printf.err(msg) return for item in albums: __album__(conf, item)
def __downloadVideo__(conf, video, album=None, playlist=None): msg, stream = API.getVideoStreamUrl(video.id, conf.videoQuality) if not isNull(msg): Printf.err(video.title + "." + msg) return path = __getVideoPath__(conf, video, album, playlist) if m3u8Helper.download(stream.m3u8Url, path): Printf.success(getFileName(path)) else: Printf.err("\nDownload failed!" + getFileName(path))
def checkLogin(): if not isNull(USER.assesstoken): mag, check = API.loginByAccessToken(USER.assesstoken) if check == False: Printf.err(LANG.MSG_INVAILD_ACCESSTOKEN) if not isNull(USER.sessionid1) and not API.isValidSessionID(USER.userid, USER.sessionid1): USER.sessionid1 = "" if not isNull(USER.sessionid2) and API.isValidSessionID(USER.userid, USER.sessionid2): USER.sessionid2 = "" if isNull(USER.sessionid1) or isNull(USER.sessionid2): login(USER.username, USER.password)
def __album__(conf, obj): Printf.album(obj) msg, tracks, videos = API.getItems(obj.id, Type.Album) if not isNull(msg): Printf.err(msg) return if conf.saveCovers: __downloadCover__(conf, obj) for item in tracks: __downloadTrack__(conf, item, obj) for item in videos: __downloadVideo__(conf, item, obj)
def __playlist__(conf, obj): Printf.playlist(obj) msg, tracks, videos = API.getItems(obj.id, Type.Playlist) if not isNull(msg): Printf.err(msg) return for item in tracks: mag, album = API.getAlbum(item.album.id) __downloadTrack__(conf, item, album) for item in videos: __downloadVideo__(conf, item, None)
def login(): print(LANG.AUTH_START_LOGIN) msg, check = API.getDeviceCode() if not check: Printf.err(msg) return # print(LANG.AUTH_LOGIN_CODE.format(green(API.key.userCode))) print(LANG.AUTH_NEXT_STEP.format(green("http://" + API.key.verificationUrl + "/" + API.key.userCode), yellow(displayTime(API.key.authCheckTimeout)))) print(LANG.AUTH_WAITING) loginByWeb() return
def main(): if len(sys.argv) > 1: mainCommand() return Printf.logo() Printf.settings(CONF) checkLogin() onlineVer = getLastVersion('tidal-dl') if not isNull(onlineVer): icmp = cmpVersion(onlineVer, VERSION) if icmp > 0: Printf.info(LANG.PRINT_LATEST_VERSION + ' ' + onlineVer) while True: Printf.choices() raw = Printf.enter(LANG.PRINT_ENTER_CHOICE).strip() choice = int(re.sub('[^0-9]', '', raw)) if choice == 0: return elif choice == 1: checkLogin() elif choice == 2: changeSettings() elif choice == 3: checkLogout() elif choice == 4: Printf.searchTypes() searchRaw = Printf.enter(LANG.PRINT_ENTER_CHOICE).strip() searchType = int(re.sub('[^0-9]', '', searchRaw)) if (searchType == 3): searchRaw = Printf.enter("Enter a url or id: ").strip() start(TOKEN, CONF, searchRaw) return field = "track" if searchType == 0 else 'album' if searchType == 1 else 'playlist' if searchType >= 0 and searchType <= 2: song = Printf.enter("Enter the song name: ") searchTrack(TOKEN, LANG.PRINT_ENTER_CHOICE, field, song, CONF) else: os.system('clear') Printf.err("Invalid option!") else: os.system('clear') Printf.err("Invalid option!")
def __playlist__(conf, obj): Printf.playlist(obj) msg, tracks, videos = API.getItems(obj.uuid, Type.Playlist) if not isNull(msg): Printf.err(msg) return for index, item in enumerate(tracks): mag, album = API.getAlbum(item.album.id) item.trackNumberOnPlaylist = index + 1 __downloadTrack__(conf, item, album, obj) for item in videos: __downloadVideo__(conf, item, None)
def __downloadVideo__(conf, video, album=None, playlist=None): msg, stream = API.getVideoStreamUrl(video.id, conf.videoQuality) if not aigpy.string.isNull(msg): Printf.err(video.title + "." + msg) return path = __getVideoPath__(conf, video, album, playlist) logging.info("[DL Video] name=" + aigpy.path.getFileName(path) + "\nurl=" + stream.m3u8Url) if aigpy.m3u8.download(stream.m3u8Url, path): Printf.success(aigpy.path.getFileName(path)) else: Printf.err("\nDownload failed!" + aigpy.path.getFileName(path))
def __album__(conf, obj): Printf.album(obj) msg, tracks, videos = API.getItems(obj.id, Type.Album) if not isNull(msg): Printf.err(msg) return # if conf.saveCovers: # __downloadCover__(conf, obj) for item in tracks: if (Blueberry.should_break()): Blueberry.set_break_status(False) break __downloadTrack__(conf, item, obj)
def __album__(conf, obj): Printf.album(obj) msg, tracks, videos = API.getItems(obj.id, Type.Album) if not aigpy.string.isNull(msg): Printf.err(msg) return if conf.saveAlbumInfo: __saveAlbumInfo__(conf, obj, tracks) if conf.saveCovers: __downloadCover__(conf, obj) for item in tracks: downloadTrack(item, obj) for item in videos: downloadVideo(item, obj)
def setAccessToken(): while True: print("-------------AccessToken---------------") token = Printf.enter("accessToken('0' go back):") if token == '0': return msg, check = API.loginByAccessToken(token, USER.userid) if check == False: Printf.err(msg) continue break USER.assesstoken = token UserSettings.save(USER)
def __playlist__(conf, obj): Printf.playlist(obj) msg, tracks, videos = API.getItems(obj.uuid, Type.Playlist) if not isNull(msg): Printf.err(msg) return for index, item in enumerate(tracks): if (Blueberry.should_break()): Blueberry.set_break_status(False) break mag, album = API.getAlbum(item.album.id) item.trackNumberOnPlaylist = index + 1 __downloadTrack__(conf, item, album, obj)
def __file__(user, conf, string): txt = getFileContent(string) if isNull(txt): Printf.err("Nothing can read!") return array = txt.split('\n') for item in array: if isNull(item): continue if item[0] == '#': continue if item[0] == '[': continue start(user, conf, item)
def downloadVideo(video: Video, album=None, playlist=None): msg, stream = API.getVideoStreamUrl(video.id, CONF.videoQuality) Printf.video(video, stream) if not aigpy.string.isNull(msg): Printf.err(video.title + "." + msg) return False, msg path = getVideoPath(CONF, video, album, playlist) logging.info("[DL Video] name=" + aigpy.path.getFileName(path) + "\nurl=" + stream.m3u8Url) m3u8content = requests.get(stream.m3u8Url).content if m3u8content is None: Printf.err(video.title + ' get m3u8 content failed.') return False, "Get m3u8 content failed" urls = aigpy.m3u8.parseTsUrls(m3u8content) if len(urls) <= 0: Printf.err(video.title + ' parse ts urls failed.') logging.info("[DL Video] title=" + video.title + "\m3u8Content=" + str(m3u8content)) return False, 'Parse ts urls failed.' check, msg = aigpy.m3u8.downloadByTsUrls(urls, path) # check, msg = aigpy.m3u8.download(stream.m3u8Url, path) if check is True: Printf.success(aigpy.path.getFileName(path)) return True, '' else: Printf.err("\nDownload failed!" + msg + '(' + aigpy.path.getFileName(path) + ')') return False, msg
def file(user, conf, string): txt = aigpy.file.getContent(string) if aigpy.string.isNull(txt): Printf.err("Nothing can read!") return array = txt.split('\n') for item in array: if aigpy.string.isNull(item): continue if item[0] == '#': continue if item[0] == '[': continue start(user, conf, item)
def __playlist__(conf, obj): Printf.playlist(obj) msg, tracks, videos = API.getItems(obj.uuid, Type.Playlist) if not aigpy.string.isNull(msg): Printf.err(msg) return for index, item in enumerate(tracks): mag, album = API.getAlbum(item.album.id) item.trackNumberOnPlaylist = index + 1 downloadTrack(item, album, obj) if conf.saveCovers and not conf.usePlaylistFolder: __downloadCover__(conf, album) for item in videos: downloadVideo(item, None)
def start(user, conf, string): __loadAPI__(user) msg, etype, obj = API.getByString(string) if etype == Type.Null or not isNull(msg): Printf.err(msg) return if etype == Type.Album: __album__(conf, obj) if etype == Type.Track: __track__(conf, obj) if etype == Type.Video: __loadVideoAPI__(user) __video__(conf, obj) if etype == Type.Artist: __artist__(conf, obj) if etype == Type.Playlist: __playlist__(conf, obj)
def mainCommand(): try: opts, args = getopt.getopt( sys.argv[1:], "hvl:o:q:r:", ["help", "version", "link=", "output=", "quality", "resolution"]) except getopt.GetoptError as errmsg: Printf.err(vars(errmsg)['msg'] + ". Use 'tidal-dl -h' for useage.") return link = None for opt, val in opts: if opt in ('-h', '--help'): Printf.usage() continue if opt in ('-v', '--version'): Printf.logo() continue if opt in ('-l', '--link'): checkLogin() link = val continue if opt in ('-o', '--output'): CONF.downloadPath = val Settings.save(CONF) continue if opt in ('-q', '--quality'): CONF.audioQuality = Settings.getAudioQuality(val) Settings.save(CONF) continue if opt in ('-r', '--resolution'): CONF.videoQuality = Settings.getVideoQuality(val) Settings.save(CONF) continue if not mkdirs(CONF.downloadPath): Printf.err(LANG.MSG_PATH_ERR + CONF.downloadPath) return if link is not None: Printf.info(LANG.SETTING_DOWNLOAD_PATH + ':' + CONF.downloadPath) start(TOKEN, CONF, link)
def setAccessToken(): while True: print("-------------AccessToken---------------") token = Printf.enter("accessToken('0' go back):") if token == '0': return msg, check = API.loginByAccessToken(token, TOKEN.userid) if check == False: Printf.err(msg) continue break print("-------------RefreshToken---------------") refreshToken = Printf.enter("refreshToken('0' to skip):") if refreshToken == '0': refreshToken = TOKEN.refreshToken TOKEN.assesstoken = token TOKEN.refreshToken = refreshToken TOKEN.expiresAfter = 0 TokenSettings.save(TOKEN)
def changeSettings(): Printf.settings(CONF) choice = Printf.enter(LANG.CHANGE_START_SETTINGS) if choice == '0': return while True: choice = Printf.enter(LANG.CHANGE_DOWNLOAD_PATH) if choice == '0': choice = CONF.downloadPath elif not os.path.isdir(choice): if not mkdirs(choice): Printf.err(LANG.MSG_PATH_ERR) continue CONF.downloadPath = choice break while True: choice = Printf.enter(LANG.CHANGE_AUDIO_QUALITY) if choice != '1' and choice != '2' and choice != '3' and choice != '0': Printf.err(LANG.MSG_INPUT_ERR) continue if choice == '0': CONF.audioQuality = AudioQuality.Normal if choice == '1': CONF.audioQuality = AudioQuality.High if choice == '2': CONF.audioQuality = AudioQuality.HiFi if choice == '3': CONF.audioQuality = AudioQuality.Master break while True: choice = Printf.enter(LANG.CHANGE_VIDEO_QUALITY) if choice != '1' and choice != '2' and choice != '3' and choice != '0': Printf.err(LANG.MSG_INPUT_ERR) continue if choice == '0': CONF.videoQuality = VideoQuality.P1080 if choice == '1': CONF.videoQuality = VideoQuality.P720 if choice == '2': CONF.videoQuality = VideoQuality.P480 if choice == '3': CONF.videoQuality = VideoQuality.P360 break CONF.onlyM4a = Printf.enter(LANG.CHANGE_ONLYM4A) == '1' CONF.addExplicitTag = Printf.enter(LANG.CHANGE_ADD_EXPLICIT_TAG) == '1' CONF.addHyphen = Printf.enter(LANG.CHANGE_ADD_HYPHEN) == '1' CONF.addYear = Printf.enter(LANG.CHANGE_ADD_YEAR) == '1' CONF.useTrackNumber = Printf.enter(LANG.CHANGE_USE_TRACK_NUM) == '1' CONF.checkExist = Printf.enter(LANG.CHANGE_CHECK_EXIST) == '1' CONF.artistBeforeTitle = Printf.enter( LANG.CHANGE_ARTIST_BEFORE_TITLE) == '1' CONF.includeEP = Printf.enter(LANG.CHANGE_INCLUDE_EP) == '1' CONF.addAlbumIDBeforeFolder = Printf.enter( LANG.CHANGE_ALBUMID_BEFORE_FOLDER) == '1' CONF.saveCovers = Printf.enter(LANG.CHANGE_SAVE_COVERS) == '1' CONF.language = Printf.enter(LANG.CHANGE_LANGUAGE) LANG = setLang(CONF.language) Settings.save(CONF)
def login(): print(LANG.AUTH_START_LOGIN) msg, check = API.getDeviceCode() if check == False: Printf.err(msg) return print(LANG.AUTH_LOGIN_CODE.format(green(API.key.userCode))) print(LANG.AUTH_NEXT_STEP.format(green(API.key.verificationUrl), yellow(displayTime(API.key.authCheckTimeout)))) print(LANG.AUTH_WAITING) start = time.time() elapsed = 0 while elapsed < API.key.authCheckTimeout: elapsed = time.time() - start # print("Check auth status...") msg, check = API.checkAuthStatus() if check == False: if msg == "pending": time.sleep(API.key.authCheckInterval + 1) continue Printf.err(msg) break if check == True: Printf.success(LANG.MSG_VALID_ACCESSTOKEN.format(displayTime(int(API.key.expiresIn)))) TOKEN.userid = API.key.userId TOKEN.countryCode = API.key.countryCode TOKEN.accessToken = API.key.accessToken TOKEN.refreshToken = API.key.refreshToken TOKEN.expiresAfter = time.time() + int(API.key.expiresIn) TokenSettings.save(TOKEN) break if elapsed >= API.key.authCheckTimeout: Printf.err(LANG.AUTH_TIMEOUT) return
def mainCommand(): try: opts, args = getopt.getopt(sys.argv[1:], "ho:l:v", ["help", "output=", "link=", "version"]) link = None for opt, val in opts: if opt in ('-h', '--help'): Printf.usage() return if opt in ('-v', '--version'): Printf.logo() return if opt in ('-l', '--link'): link = val if opt in ('-o', '--output'): CONF.downloadPath = val if link is None: Printf.err( "Please enter the link(url/id/path)! Enter 'tidal-dl -h' for help!" ) return if not mkdirs(CONF.downloadPath): Printf.err(LANG.MSG_PATH_ERR + CONF.downloadPath) return checkLogin() start(USER, CONF, link) return except getopt.GetoptError: Printf.err("Argv error! Enter 'tidal -h' for help!")
def __downloadTrack__(conf: Settings, track: Track, album=None, playlist=None): try: if track.allowStreaming is False: Printf.err("Download failed! " + track.title + ' not allow streaming.') return msg, stream = API.getStreamUrl(track.id, conf.audioQuality) Printf.track(track, stream) if not aigpy.string.isNull(msg) or stream is None: Printf.err(track.title + "." + msg) return path = __getTrackPath__(conf, track, stream, album, playlist) # check exist if conf.checkExist and __isNeedDownload__(path, stream.url) == False: Printf.success( aigpy.path.getFileName(path) + " (skip:already exists!)") return logging.info("[DL Track] name=" + aigpy.path.getFileName(path) + "\nurl=" + stream.url) tool = aigpy.download.DownloadTool(path + '.part', [stream.url]) check, err = tool.start(conf.showProgress) if not check: Printf.err("Download failed! " + aigpy.path.getFileName(path) + ' (' + str(err) + ')') return # encrypted -> decrypt and remove encrypted file if aigpy.string.isNull(stream.encryptionKey): os.replace(path + '.part', path) else: key, nonce = decrypt_security_token(stream.encryptionKey) decrypt_file(path + '.part', path, key, nonce) os.remove(path + '.part') path = __convertToM4a__(path, stream.codec) # contributors contributors = API.getTrackContributors(track.id) lyrics = '' if conf.addLyrics: lyrics = __getLyrics__(track.title, track.artists[0].name, conf.lyricsServerProxy) __setMetaData__(track, album, path, contributors, lyrics) Printf.success(aigpy.path.getFileName(path)) except Exception as e: Printf.err("Download failed! " + track.title + ' (' + str(e) + ')')
def downloadTrack(track: Track, album=None, playlist=None, userProgress=None, partSize=1048576): try: msg, stream = API.getStreamUrl(track.id, CONF.audioQuality) if not aigpy.string.isNull(msg) or stream is None: Printf.err(track.title + "." + msg) return False, msg if CONF.showTrackInfo: Printf.track(track, stream) if userProgress is not None: userProgress.updateStream(stream) path = getTrackPath(CONF, track, stream, album, playlist) # check exist if skip(path, stream.url): Printf.success( aigpy.path.getFileName(path) + " (skip:already exists!)") return True, "" # download logging.info("[DL Track] name=" + aigpy.path.getFileName(path) + "\nurl=" + stream.url) tool = aigpy.download.DownloadTool(path + '.part', [stream.url]) tool.setUserProgress(userProgress) tool.setPartSize(partSize) check, err = tool.start(CONF.showProgress) if not check: Printf.err("Download failed! " + aigpy.path.getFileName(path) + ' (' + str(err) + ')') return False, str(err) # encrypted -> decrypt and remove encrypted file encrypted(stream, path + '.part', path) # convert path = convert(path, stream) # contributors msg, contributors = API.getTrackContributors(track.id) msg, tidalLyrics = API.getLyrics(track.id) lyrics = '' if tidalLyrics is None else tidalLyrics.subtitles if CONF.lyricFile: if tidalLyrics is None: Printf.info(f'Failed to get lyrics from tidal!"{track.title}"') else: lrcPath = path.rsplit(".", 1)[0] + '.lrc' aigpy.fileHelper.write(lrcPath, tidalLyrics.subtitles, 'w') setMetaData(track, album, path, contributors, lyrics) Printf.success(aigpy.path.getFileName(path)) return True, "" except Exception as e: Printf.err("Download failed! " + track.title + ' (' + str(e) + ')') return False, str(e)
def login(username="", password=""): while True: if isNull(username) or isNull(password): print("---------------" + LANG.CHOICE_LOGIN + "-----------------") username = Printf.enter(LANG.PRINT_USERNAME) password = Printf.enter(LANG.PRINT_PASSWORD) msg, check = API.login(username, password, TOKEN1) if check == False: Printf.err(msg) username = "" password = "" continue api2 = TidalAPI() msg, check = api2.login(username, password, TOKEN2) break USER.username = username USER.password = password USER.userid = API.key.userId USER.countryCode = API.key.countryCode USER.sessionid1 = API.key.sessionId USER.sessionid2 = api2.key.sessionId UserSettings.save(USER)