def __init__(self, threadNum=3):
        self.config = TidalConfig()
        self.tool = TidalTool()
        self.thread = ThreadTool(int(threadNum))
        self.ffmpeg = FFmpegTool(mergerTimeout=45)
        self.progress = ProgressTool(100)
        self.check = CheckTool()

        self.showpro = False
        if self.config.showprogress == 'True':
            self.showpro = True
Esempio n. 2
0
    def __init__(self, threadNum=3):
        self.config = TidalConfig()
        self.tool = TidalTool()
        self.thread = ThreadTool(int(threadNum))
        self.ffmpeg = FFmpegTool(mergerTimeout=45)
        self.progress = ProgressTool(100)
        self.check = CheckTool()

        pathHelper.mkdirs(self.config.outputdir + "/Album/")
        pathHelper.mkdirs(self.config.outputdir + "/Playlist/")
        pathHelper.mkdirs(self.config.outputdir + "/Video/")
        pathHelper.mkdirs(self.config.outputdir + "/Favorite/")
Esempio n. 3
0
    def __init__(self):
        self.config = TidalConfig()
        self.errmsg = ""
        self.tmpfileFlag = 'TIDAL_TMP_'
        self.ffmpeg = FFmpegTool(1)

        self.header = {'X-Tidal-SessionId': self.config.sessionid}
        self.header2 = {'X-Tidal-SessionId': self.config.sessionid2}

        self.header3 = None
        if self.config.accesstoken != '':
            self.header3 = {'authorization': 'Bearer {}'.format(self.config.accesstoken)}
Esempio n. 4
0
class Download(object):
    def __init__(self, threadNum=3):
        self.config = TidalConfig()
        self.tool = TidalTool()
        self.thread = ThreadTool(int(threadNum))
        self.ffmpeg = FFmpegTool()
        self.progress = ProgressTool(100)
        self.check = CheckTool()

        pathHelper.mkdirs(self.config.outputdir + "/Album/")
        pathHelper.mkdirs(self.config.outputdir + "/Track/")
        pathHelper.mkdirs(self.config.outputdir + "/Playlist/")
        pathHelper.mkdirs(self.config.outputdir + "/Video/")
        pathHelper.mkdirs(self.config.outputdir + "/Favorite/")

    def __isNeedDownload(self, path, url):
        curSize = fileHelper.getFileSize(path)
        if curSize <= 0:
            return True
        netSize = netHelper.getFileSize(url)
        if curSize >= netSize:
            return False
        return True

    # dowmload track thread
    def __thradfunc_dl(self, paraList):
        count = 1
        printRet = True
        pstr = paraList['title'] + "(Download Err!)"
        redownload = True
        needDl = True
        bIsSuccess = False
        albumInfo = None
        index = None

        if 'redownload' in paraList:
            redownload = paraList['redownload']
        if 'retry' in paraList:
            count = count + paraList['retry']
        if 'show' in paraList:
            printRet = paraList['show']
        if 'album' in paraList:
            albumInfo = paraList['album']
        if 'index' in paraList:
            index = paraList['index']

        if redownload is False:
            needDl = self.__isNeedDownload(paraList['path'], paraList['url'])

        if needDl:
            try:
                while count > 0:
                    count = count - 1
                    check = netHelper.downloadFile(paraList['url'],
                                                   paraList['path'])
                    if check is True:
                        if paraList['key'] == '':
                            break
                        key, nonce = decrypt_security_token(paraList['key'])
                        decrypt_file(paraList['path'], key, nonce)
                        break
                if check:
                    self.tool.setTrackMetadata(paraList['trackinfo'],
                                               paraList['path'], albumInfo,
                                               index)
                    pstr = paraList['title']
                    bIsSuccess = True
            except:
                pass
        else:
            pstr = paraList['title']
            bIsSuccess = True

        if printRet:
            if (bIsSuccess):
                printSUCCESS(14, pstr)
            else:
                printErr(14, pstr)
        return

    # creat album output dir
    def __creatAlbumDir(self, albumInfo):
        # creat outputdir
        title = pathHelper.replaceLimitChar(albumInfo['title'], '-')
        author = pathHelper.replaceLimitChar(albumInfo['artist']['name'], '-')
        targetDir = self.config.outputdir + "/Album/" + title + '(' + author + ')'
        targetDir = os.path.abspath(targetDir)
        pathHelper.mkdirs(targetDir)
        # creat volumes dir
        count = 0
        numOfVolumes = int(albumInfo['numberOfVolumes'])
        if numOfVolumes > 1:
            while count < numOfVolumes:
                volumeDir = targetDir + "/Volume" + str(count)
                pathHelper.mkdirs(volumeDir)
                count = count + 1
        return targetDir

    def _getSongExtension(self, downloadUrl):
        if downloadUrl.find('.flac?') != -1:
            return '.flac'
        if downloadUrl.find('.m4a?') != -1:
            return '.m4a'
        if downloadUrl.find('.mp4?') != -1:
            return '.mp4'
        return '.m4a'

    def __getAlbumSongSavePath(self, targetDir, albumInfo, item, extension):
        if extension == None:
            extension = ".m4a"

        numOfVolumes = int(albumInfo['numberOfVolumes'])
        if numOfVolumes <= 1:
            filePath = targetDir + "/" + pathHelper.replaceLimitChar(
                item['title'], '-') + extension
        else:
            index = item['volumeNumber']
            filePath = targetDir + "/Volume" + str(
                index - 1) + "/" + pathHelper.replaceLimitChar(
                    item['title'], '-') + extension
        return filePath

    def __getExistFiles(self, paths):
        ret = []
        for item in paths:
            if os.path.isfile(item):
                ret.append(item)
        return ret

    def __getVideoResolutionIndex(self, reslist):
        array = []
        for item in reslist:
            subs = item.split('x')
            array.append(int(subs[1]))
        cmp = int(self.config.resolution)
        ret = 0
        for item in array:
            if cmp >= item:
                return ret
            ret += 1
        return len(array) - 1

    def downloadAlbum(self):
        while True:
            print("----------------ALBUM------------------")
            sID = printChoice("Enter AlbumID(Enter '0' go back):", True, 0)
            if sID == 0:
                return

            aAlbumInfo = self.tool.getAlbum(sID)
            if self.tool.errmsg != "":
                printErr(0, "Get AlbumInfo Err! " + self.tool.errmsg)
                continue

            print("[Title]       %s" % (aAlbumInfo['title']))
            print("[SongNum]     %s\n" % (aAlbumInfo['numberOfTracks']))

            # Get Tracks
            aAlbumTracks = self.tool.getAlbumTracks(sID)
            if self.tool.errmsg != "":
                printErr(0, "Get AlbumTracks Err!" + self.tool.errmsg)
                return
            # Creat OutputDir
            targetDir = self.__creatAlbumDir(aAlbumInfo)
            # write msg
            string = self.tool.convertAlbumInfoToString(
                aAlbumInfo, aAlbumTracks)
            with open(targetDir + "/AlbumInfo.txt", 'w',
                      encoding='utf-8') as fd:
                fd.write(string)
            # download cover
            coverPath = targetDir + '/' + pathHelper.replaceLimitChar(
                aAlbumInfo['title'], '-') + '.jpg'
            coverUrl = self.tool.getAlbumArtworkUrl(aAlbumInfo['cover'])
            netHelper.downloadFile(coverUrl, coverPath)
            # check exist files
            redownload = True
            existFiles = pathHelper.getDirFiles(targetDir)
            for item in existFiles:
                if '.txt' in item:
                    continue
                if '.jpg' in item:
                    continue
                check = printChoice(
                    "Some TrackFile Exist.Is Redownload?(y/n):")
                if check != 'y' and check != 'yes':
                    redownload = False
                break
            # download album tracks
            for item in aAlbumTracks['items']:
                streamInfo = self.tool.getStreamUrl(str(item['id']),
                                                    self.config.quality)
                if self.tool.errmsg != "":
                    printErr(
                        14, item['title'] + "(Get Stream Url Err!" +
                        self.tool.errmsg + ")")
                    continue

                fileType = self._getSongExtension(streamInfo['url'])
                filePath = self.__getAlbumSongSavePath(targetDir, aAlbumInfo,
                                                       item, fileType)
                paraList = {
                    'album': aAlbumInfo,
                    'redownload': redownload,
                    'title': item['title'],
                    'trackinfo': item,
                    'url': streamInfo['url'],
                    'path': filePath,
                    'retry': 3,
                    'key': streamInfo['encryptionKey']
                }
                self.thread.start(self.__thradfunc_dl, paraList)
            # wait all download thread
            self.thread.waitAll()
            self.tool.removeTmpFile(targetDir)
        return

    def downloadTrack(self):
        while True:
            targetDir = self.config.outputdir + "/Track/"
            print("----------------TRACK------------------")
            sID = printChoice("Enter TrackID(Enter '0' go back):", True, 0)
            if sID == 0:
                return

            aTrackInfo = self.tool.getTrack(sID)
            if self.tool.errmsg != "":
                printErr(0, "Get TrackInfo Err! " + self.tool.errmsg)
                return

            print("[TrackTitle ]       %s" % (aTrackInfo['title']))
            print("[Duration   ]       %s" % (aTrackInfo['duration']))
            print("[TrackNumber]       %s" % (aTrackInfo['trackNumber']))
            print("[Version    ]       %s\n" % (aTrackInfo['version']))
            # download
            streamInfo = self.tool.getStreamUrl(sID, self.config.quality)
            if self.tool.errmsg != "":
                printErr(
                    14, aTrackInfo['title'] + "(Get Stream Url Err!" +
                    self.tool.errmsg + ")")
                continue

            fileType = self._getSongExtension(streamInfo['url'])
            filePath = targetDir + "/" + pathHelper.replaceLimitChar(
                aTrackInfo['title'], '-') + fileType
            paraList = {
                'title': aTrackInfo['title'],
                'trackinfo': aTrackInfo,
                'url': streamInfo['url'],
                'path': filePath,
                'retry': 3,
                'key': streamInfo['encryptionKey']
            }
            self.thread.start(self.__thradfunc_dl, paraList)
            # wait all download thread
            self.thread.waitAll()
            self.tool.removeTmpFile(targetDir)
        return

    def downloadVideo(self):
        while True:
            targetDir = self.config.outputdir + "/Video/"
            print("----------------VIDEO------------------")
            sID = printChoice("Enter VideoID(Enter '0' go back):", True, 0)
            if sID == 0:
                return
            aVideoInfo = self.tool.getVideo(sID)
            if self.tool.errmsg != "":
                printErr(0, "Get VideoInfo Err! " + self.tool.errmsg)
                continue

            print("[Title      ]       %s" % (aVideoInfo['title']))
            print("[Duration   ]       %s" % (aVideoInfo['duration']))
            print("[TrackNumber]       %s" % (aVideoInfo['trackNumber']))
            print("[Type       ]       %s\n" % (aVideoInfo['type']))

            # get resolution
            index = 0
            resolutionList, urlList = self.tool.getVideoResolutionList(sID)
            if self.tool.errmsg != "":
                printErr(14, self.tool.errmsg)
                continue
            # print("-Index--Resolution--")
            # for item in resolutionList:
            #     print('   ' + str(index) + "    " + resolutionList[index])
            #     index = index + 1
            # print("--------------------")
            # while True:
            #     index = printChoice("Enter ResolutionIndex:", True, 0)
            #     if index == '' or index == None or int(index) >= len(resolutionList):
            #         printErr(0, "ResolutionIndex is err")
            #         continue
            #     break
            index = self.__getVideoResolutionIndex(resolutionList)
            path = targetDir + "/" + pathHelper.replaceLimitChar(
                aVideoInfo['title'], '-') + ".mp4"
            path = os.path.abspath(path)
            if os.access(path, 0):
                os.remove(path)

            if self.ffmpeg.mergerByM3u8_Multithreading(urlList[int(index)],
                                                       path, True):
                printSUCCESS(14, aVideoInfo['title'])
            else:
                printErr(14, aVideoInfo['title'])
        return

    def downloadPlaylist(self):
        while True:
            targetDir = self.config.outputdir + "/Playlist/"
            print("--------------PLAYLIST-----------------")
            sID = printChoice("Enter PlayListID(Enter '0' go back):")
            if sID == '0':
                return

            aPlaylistInfo, aItemInfo = self.tool.getPlaylist(sID)
            if self.tool.errmsg != "":
                printErr(0, "Get PlaylistInfo Err! " + self.tool.errmsg)
                return

            print("[Title]                %s" % (aPlaylistInfo['title']))
            print("[Type]                 %s" % (aPlaylistInfo['type']))
            print("[NumberOfTracks]       %s" %
                  (aPlaylistInfo['numberOfTracks']))
            print("[NumberOfVideos]       %s" %
                  (aPlaylistInfo['numberOfVideos']))
            print("[Duration]             %s\n" % (aPlaylistInfo['duration']))

            # Creat OutputDir
            targetDir = targetDir + pathHelper.replaceLimitChar(
                aPlaylistInfo['title'], '-')
            targetDir = os.path.abspath(targetDir)
            pathHelper.mkdirs(targetDir)
            # write msg
            string = self.tool.convertPlaylistInfoToString(
                aPlaylistInfo, aItemInfo)
            with open(targetDir + "/PlaylistInfo.txt", 'w',
                      encoding='utf-8') as fd:
                fd.write(string)

            # download track
            bBreakFlag = False
            bFirstTime = True
            errIndex = []
            index = 0

            while bBreakFlag is False:
                self.check.clear()
                index = 0
                for item in aItemInfo:
                    type = item['type']
                    item = item['item']
                    if type != 'track':
                        continue

                    index = index + 1
                    if bFirstTime is False:
                        if self.check.isInErr(index - 1, errIndex) == False:
                            continue

                    streamInfo = self.tool.getStreamUrl(
                        str(item['id']), self.config.quality)
                    if self.tool.errmsg != "":
                        printErr(
                            14, item['title'] + "(Get Stream Url Err!!" +
                            self.tool.errmsg + ")")
                        continue

                    fileType = self._getSongExtension(streamInfo['url'])
                    filePath = targetDir + '/' + pathHelper.replaceLimitChar(
                        item['title'], '-') + fileType
                    paraList = {
                        'index': index,
                        'title': item['title'],
                        'trackinfo': item,
                        'url': streamInfo['url'],
                        'path': filePath,
                        'retry': 3,
                        'key': streamInfo['encryptionKey']
                    }
                    self.check.addPath(filePath)
                    if not os.path.isfile(filePath):
                        self.thread.start(self.__thradfunc_dl, paraList)
                self.thread.waitAll()
                self.tool.removeTmpFile(targetDir)

                bBreakFlag = True
                bFirstTime = False

                # check
                isErr, errIndex = self.check.checkPaths()
                if isErr:
                    check = printChoice(
                        "[Err]\t\t" + len(errIndex) +
                        " Tracks Download Failed.Try Again?(y/n):")
                    if check == 'y' or check == 'Y':
                        bBreakFlag = False

            # download video
            for item in aItemInfo:
                type = item['type']
                item = item['item']
                if type != 'video':
                    continue

                filePath = targetDir + '/' + pathHelper.replaceLimitChar(
                    item['title'], '-') + ".mp4"
                filePath = os.path.abspath(filePath)
                if os.access(filePath, 0):
                    os.remove(filePath)

                videoID = item['id']
                resolutionList, urlList = self.tool.getVideoResolutionList(
                    videoID)
                if urlList is None:
                    printErr(14, item['title'] + '(' + self.tool.errmsg + ')')
                else:
                    selectIndex = self.__getVideoResolutionIndex(
                        resolutionList)
                    if self.ffmpeg.mergerByM3u8_Multithreading(
                            urlList[selectIndex], filePath,
                            showprogress=False):
                        printSUCCESS(14, item['title'])
                    else:
                        printErr(14,
                                 item['title'] + "(Download Or Merger Err!)")
        return

    def downloadFavorite(self):
        targetDir = self.config.outputdir + "/Favorite/"
        pathHelper.mkdirs(targetDir)

        trackList, videoList = self.tool.getFavorite(self.config.userid)
        if self.tool.errmsg != "":
            printErr(0, "Get FavoriteList Err! " + self.tool.errmsg)
            return

        print("[NumberOfTracks]       %s" % (len(trackList)))
        print("[NumberOfVideos]       %s" % (len(videoList)))
        # download track
        for item in trackList:
            item = item['item']
            streamInfo = self.tool.getStreamUrl(str(item['id']),
                                                self.config.quality)
            if self.tool.errmsg != "":
                printErr(
                    14, item['title'] + "(Get Stream Url Err!!" +
                    self.tool.errmsg + ")")
                continue

            fileType = self._getSongExtension(streamInfo['url'])
            filePath = targetDir + '/' + pathHelper.replaceLimitChar(
                item['title'], '-') + fileType
            paraList = {
                'title': item['title'],
                'trackinfo': item,
                'url': streamInfo['url'],
                'path': filePath,
                'retry': 3,
                'key': streamInfo['encryptionKey']
            }
            self.thread.start(self.__thradfunc_dl, paraList)
        self.thread.waitAll()

        # download video
        for item in videoList:
            item = item['item']

            filePath = targetDir + '/' + pathHelper.replaceLimitChar(
                item['title'], '-') + ".mp4"
            filePath = os.path.abspath(filePath)
            if os.access(filePath, 0):
                os.remove(filePath)

            resolutionList, urlList = self.tool.getVideoResolutionList(
                item['id'])
            selectIndex = self.__getVideoResolutionIndex(resolutionList)
            if self.ffmpeg.mergerByM3u8_Multithreading(urlList[selectIndex],
                                                       filePath,
                                                       showprogress=False):
                printSUCCESS(14, item['title'])
            else:
                printErr(14, item['title'])
        return
Esempio n. 5
0
 def __init__(self):
     self.config = TidalConfig()
     self.errmsg = ""
     self.tmpfileFlag = 'TIDAL_TMP_'
     self.ffmpeg = FFmpegTool(1)
Esempio n. 6
0
class TidalTool(object):
    def __init__(self):
        self.config = TidalConfig()
        self.errmsg = ""
        self.tmpfileFlag = 'TIDAL_TMP_'
        self.ffmpeg = FFmpegTool(1)

    def _get(self, url, params={}):
        retry = 3
        sessionid = self.config.sessionid
        if 'soundQuality' in params:
            if params['soundQuality'] == 'LOSSLESS':
                sessionid = self.config.sessionid2

        while retry > 0:
            retry -= 1
            try:
                self.errmsg = ""
                params['countryCode'] = self.config.countrycode
                resp = requests.get(URL_PRE + url,
                                    headers={
                                        'X-Tidal-SessionId': sessionid
                                    },
                                    params=params).json()
                if 'status' in resp and resp['status'] == 404 and resp[
                        'subStatus'] == 2001:
                    self.errmsg = '{}. This might be region-locked.'.format(
                        resp['userMessage'])
                elif 'status' in resp and resp['status'] == 401 and resp[
                        'subStatus'] == 4005:  #'Asset is not ready for playback'
                    sessionid = self.config.sessionid2
                    continue
                elif 'status' in resp and not resp['status'] == 200:
                    self.errmsg = '{}. Get operation err!'.format(
                        resp['userMessage'])
                    # self.errmsg = "Get operation err!"
                return resp
            except Exception as e:
                if retry <= 0:
                    self.errmsg = 'Function `Http-Get` Err! ' + str(e)
                    return None

    def setTag(self, tag, srcfile, coverpath=None):
        path = pathHelper.getDirName(srcfile)
        name = pathHelper.getFileNameWithoutExtension(srcfile)
        ext = pathHelper.getFileExtension(srcfile)
        oext = ext

        if 'm4a' in ext or 'mp4' in ext:
            oext = '.mp3'
        if 'mp3' not in oext:
            coverpath = None
        tmpfile = path + '/' + 'TMP' + name + oext

        try:
            data = AudioSegment.from_file(srcfile, format=ext[1:])
            check = data.export(tmpfile,
                                format=oext[1:],
                                tags=tag,
                                cover=coverpath)
            check.close()
        except Exception as e:
            pathHelper.remove(tmpfile)
            return

        if fileHelper.getFileSize(tmpfile) > 0:
            pathHelper.remove(srcfile)
            os.rename(tmpfile, path + '/' + name + oext)
        else:
            pathHelper.remove(tmpfile)

    def setTrackMetadata_old(self, track_info, file_path, album_info, index,
                             coverpath):
        tag = {
            'Artist': track_info['artist']['name'],
            'Album': track_info['album']['title'],
            'Title': track_info['title'],
            'CopyRight': track_info['copyright'],
            'Track': track_info['trackNumber']
        }
        if index is not None:
            tag['Track'] = str(index)
        if album_info is not None:
            tag['Date'] = album_info['releaseDate']
            tag['Year'] = album_info['releaseDate'].split('-')[0]
        self.setTag(tag, file_path, coverpath)
        return

    def covertMp4toM4a(self, file_path):
        if self.config.onlym4a != "True":
            return file_path
        if '.mp4' not in file_path:
            return file_path
        if not self.ffmpeg.enable:
            return file_path
        new_path = file_path.replace('.mp4', '.m4a')

        pathHelper.remove(new_path)
        if self.ffmpeg.covertFile(file_path, new_path):
            pathHelper.remove(file_path)
            return new_path
        else:
            return file_path

    def _parseContributors(self, roleType, Contributors):
        if Contributors is None:
            return None
        try:
            ret = []
            for item in Contributors['items']:
                if item['role'] == roleType:
                    ret.append(item['name'])
            return ret
        except:
            return None

    def setTrackMetadata(self, track_info, file_path, album_info, index,
                         coverpath, Contributors):
        # isrc,replayGain,releasedate
        obj = tagHelper.TagTool(file_path)
        obj.album = track_info['album']['title']
        obj.title = track_info['title']
        obj.artist = self._getArtists(track_info['artists'])
        obj.copyright = track_info['copyright']
        obj.tracknumber = track_info['trackNumber']
        obj.discnumber = track_info['volumeNumber']
        obj.isrc = track_info['isrc']
        obj.composer = self._parseContributors('Composer', Contributors)
        if index is not None:
            obj.tracknumber = str(index)
        if album_info is not None:
            obj.albumartist = self._getArtists(album_info['artists'])
            obj.date = album_info['releaseDate']
            obj.totaldisc = album_info['numberOfVolumes']
            if obj.totaldisc <= 1:
                obj.totaltrack = album_info['numberOfTracks']
        obj.save(coverpath)
        return

    def removeTmpFile(self, path):
        for root, dirs, files in os.walk(path):
            for name in files:
                if self.tmpfileFlag in name:
                    pathHelper.remove(os.path.join(root, name))

    def getStreamUrl(self, track_id, quality):
        url = self._get('tracks/' + str(track_id) + '/streamUrl',
                        {'soundQuality': quality})
        if not url:
            resp = self._get(
                'tracks/{}/playbackinfopostpaywall'.format(track_id), {
                    'audioquality': quality,
                    'playbackmode': 'STREAM',
                    'assetpresentation': 'FULL'
                })
            if resp and 'trackId' in resp:
                #  printWarning(14, "Redirecting: {} -> {}".format(track_id, resp['trackId']))
                track_id = resp['trackId']
                url = self._get('tracks/' + str(track_id) + '/streamUrl',
                                {'soundQuality': quality})
        return url

    def _getArtists(self, pHash):
        ret = []
        for item in pHash:
            ret.append(item['name'])
        return ret

    def getIndexStr(self, index, sum):
        pre = "0"
        if sum > 99:
            pre = "00"
        if index < 10:
            return pre + str(index) + " "
        if index < 99 and sum > 99:
            return "0" + str(index) + " "
        return str(index) + " "

    def _fixSameTrackName(self, tracks, isOnLayer2=False):
        same = {}
        for item in tracks:
            if isOnLayer2:
                item = item['item']
            if 'version' in item:
                if item['version'] is not None:
                    item['title'] = item['title'] + ' - ' + item['version']
            if item['title'] in same:
                same[item['title']] += 1
            else:
                same[item['title']] = 1
        for item in same:
            if same[item] <= 1:
                continue
            index = 1
            for track in tracks:
                if track:
                    track = track['item']
                if track['title'] != item:
                    continue
                track['title'] += str(index)
                index += 1
        return tracks

    def getPlaylist(self, playlist_id):
        info = self._get('playlists/' + playlist_id)
        if self.errmsg != "":
            return None, None
        list = self.__getItemsList('playlists/' + playlist_id + '/items')
        list = self._fixSameTrackName(list, True)
        return info, list

    def getAlbumTracks(self, album_id):
        info = self._get('albums/' + str(album_id) + '/tracks')
        if self.errmsg != "":
            return info
        # sum = info['totalNumberOfItems']
        for item in info['items']:
            if 'version' in item and item['version'] is not None:
                item['title'] += ' - ' + item['version']
        #     indexs = self._getIndexStr(item['trackNumber'],sum)
        #     item['title'] = indexs + " " + item['title']
        # info['items'] = self._fixSameTrackName(info['items'])
        return info

    def getAlbumVideos(self, album_id):
        # info = self._get('albums/' + str(album_id) + '/items')
        info = self.__getItemsList('albums/' + str(album_id) + '/items')
        if self.errmsg != "":
            return []
        ret = []
        for item in info:
            if item['type'] == 'video':
                ret.append(item)
        return ret

    def getTrack(self, track_id):
        item = self._get('tracks/' + str(track_id))
        if 'version' in item and item['version'] is not None:
            item['title'] += ' - ' + item['version']
        return item

    def getAlbum(self, album_id):
        return self._get('albums/' + str(album_id))

    def getVideo(self, video_id):
        return self._get('videos/' + str(video_id))

    def getFavorite(self, user_id):
        trackList = self.__getItemsList('users/' + str(user_id) +
                                        '/favorites/tracks')
        tracklist = self._fixSameTrackName(trackList, True)
        videoList = self.__getItemsList('users/' + str(user_id) +
                                        '/favorites/videos')
        return trackList, videoList

    def getArtistAlbum(self, artist_id, includeSingles):
        if includeSingles:
            items1 = self.__getItemsList(
                'artists/' + str(artist_id) + '/albums',
                {'filter': 'EPSANDSINGLES'})
        else:
            items1 = []
        # items2 = self.__getItemsList('artists/' + str(artist_id) + '/albums',{'filter': 'COMPILATIONS'})
        items3 = self.__getItemsList('artists/' + str(artist_id) + '/albums')
        itemall = items1 + items3
        return itemall

    def __getItemsList(self, url, in_dirs={}):
        in_dirs['limit'] = 0
        ret = self._get(url, in_dirs)
        count = ret['totalNumberOfItems']
        offset = 0
        limit = 100
        retList = []
        while offset < count:
            in_dirs['limit'] = limit
            in_dirs['offset'] = offset
            items = self._get(url, in_dirs)
            if self.errmsg != "":
                if self.errmsg.find('Too big page') >= 0:
                    limit = limit - 10
                    continue
                else:
                    return retList
            offset = offset + limit
            if retList == None:
                retList = items['items']
            else:
                retList.extend(items['items'])
        return retList

    def getTrackContributors(self, track_id):
        return self._get('tracks/' + str(track_id) + '/contributors')

    def getAlbumArtworkUrl(self, coverid, size=1280):
        if coverid is not None:
            return 'https://resources.tidal.com/images/{0}/{1}x{1}.jpg'.format(
                coverid.replace('-', '/'), size)
        else:
            return ''

    def getPlaylistArtworkUrl(self, playlist_uuid, size=1280):
        return 'http://images.tidalhifi.com/im/im?w={1}&h={2}&uuid={0}&rows=2&cols=3&noph'.format(
            playlist_uuid, size, size)

    def getVideoResolutionList(self, video_id):
        info = self._get('videos/' + str(video_id) + '/streamurl')
        if self.errmsg != "":
            return None, None
        content = netHelper.downloadString(info['url'], None)
        resolutionList, urlList = self.__parseVideoMasterAll(str(content))
        return resolutionList, urlList

    def getVideoMediaPlaylist(self, url):
        urlList = self.__parseVideoMediaPlaylist(url)
        return urlList

    def searchTrack(self, query):
        ret = self._get('search/tracks', {
            'query': query,
            'offset': 0,
            'limit': 99
        })
        return ret

    def __parseVideoMasterAll(self, content):
        pattern = re.compile(r"(?<=RESOLUTION=).+?(?=\\n)")
        resolutionList = pattern.findall(content)
        pattern = re.compile(r"(?<=http).+?(?=\\n)")
        pList = pattern.findall(content)
        urlList = []
        for item in pList:
            urlList.append("http" + item)

        return resolutionList, urlList

    def __parseVideoMediaPlaylist(self, url):
        content = netHelper.downloadString(url, None)
        pattern = re.compile(r"(?<=http).+?(?=\\n)")
        plist = pattern.findall(str(content))
        urllist = []
        for item in plist:
            urllist.append("http" + item)
        return urllist

    def convertAlbumInfoToString(self, aAlbumInfo, aAlbumTracks):
        str = ""
        str += "[ID]          %d\n" % (aAlbumInfo['id'])
        str += "[Title]       %s\n" % (aAlbumInfo['title'])
        str += "[Artists]     %s\n" % (aAlbumInfo['artist']['name'])
        str += "[ReleaseDate] %s\n" % (aAlbumInfo['releaseDate'])
        str += "[SongNum]     %s\n" % (aAlbumInfo['numberOfTracks'])
        str += "[Duration]    %s\n" % (aAlbumInfo['duration'])
        str += '\n'

        i = 0
        while True:
            if i >= int(aAlbumInfo['numberOfVolumes']):
                break
            i = i + 1
            str += "===========Volume %d=============\n" % i
            for item in aAlbumTracks['items']:
                if item['volumeNumber'] != i:
                    continue
                str += '{:<8}'.format("[%d]" % item['trackNumber'])
                str += "%s\n" % item['title']
        return str

    def convertPlaylistInfoToString(seld, aPlaylistInfo, aTrackItems):
        str = ""
        str += "[Title]           %s\n" % (aPlaylistInfo['title'])
        str += "[Type]            %s\n" % (aPlaylistInfo['type'])
        str += "[NumberOfTracks]  %s\n" % (aPlaylistInfo['numberOfTracks'])
        str += "[NumberOfVideos]  %s\n" % (aPlaylistInfo['numberOfVideos'])
        str += "[Duration]        %s\n" % (aPlaylistInfo['duration'])

        i = 0
        str += "===========Track=============\n"
        for item in aTrackItems:
            type = item['type']
            item = item['item']
            if type != 'track':
                continue

            i = i + 1
            str += '{:<8}'.format("[%d]" % i) + item['title'] + '\n'

        i = 0
        str += "\n===========Video=============\n"
        for item in aTrackItems:
            type = item['type']
            item = item['item']
            if type != 'video':
                continue

            i = i + 1
            str += '{:<8}'.format("[%d]" % i) + item['title'] + '\n'
        return str

    def parseLink(self, link):
        link = link.strip()
        if link.find('http') < 0:
            return None, None

        urlpres = ['tidal.com/', 'tidal.com/browse/']
        for pre in urlpres:
            stype = re.findall(pre + "(.+?)/", link)
            if len(stype) <= 0 or stype[0] not in TYPE_ARR:
                continue
            sid = re.findall(pre + stype[0] + "/(.+)/", link)
            if len(sid) <= 0:
                sid = re.findall(pre + stype[0] + "/(.+)", link)
            if len(sid) <= 0:
                return None, None
            return stype[0], sid[0]
        return None, None

    def parseFile(self, path):
        cfp = configHelper.ParseNoEqual(path)
        ret = cfp
        if 'album' not in ret:
            ret['album'] = []
        if 'artist' not in ret:
            ret['artist'] = []
        if 'track' not in ret:
            ret['track'] = []
        if 'video' not in ret:
            ret['video'] = []
        if 'url' not in ret:
            ret['url'] = []
        return ret
class Download(object):
    def __init__(self, threadNum=3):
        self.config = TidalConfig()
        self.tool = TidalTool()
        self.thread = ThreadTool(int(threadNum))
        self.ffmpeg = FFmpegTool(mergerTimeout=45)
        self.progress = ProgressTool(100)
        self.check = CheckTool()

        self.showpro = False
        if self.config.showprogress == 'True':
            self.showpro = True

        pathHelper.mkdirs(self.config.outputdir + "/Album/")
        pathHelper.mkdirs(self.config.outputdir + "/Playlist/")
        pathHelper.mkdirs(self.config.outputdir + "/Video/")
        pathHelper.mkdirs(self.config.outputdir + "/Favorite/")

    def __isNeedDownload(self, path, url):
        curSize = fileHelper.getFileSize(path)
        if curSize <= 0:
            return True
        netSize = netHelper.getFileSize(url)
        if curSize >= netSize:
            return False
        return True

    # dowmload track thread
    def __thradfunc_dl(self, paraList):
        count = 1
        printRet = True
        pstr = paraList['title'] + "(Download Err!)"
        redownload = True
        needDl = True
        bIsSuccess = False
        albumInfo = None
        index = None
        coverpath = None

        if 'redownload' in paraList:
            redownload = paraList['redownload']
        if 'retry' in paraList:
            count = count + paraList['retry']
        if 'show' in paraList:
            printRet = paraList['show']
        if 'album' in paraList:
            albumInfo = paraList['album']
        if 'index' in paraList:
            index = paraList['index']
        if 'coverpath' in paraList:
            coverpath = paraList['coverpath']

        if redownload is False:
            needDl = self.__isNeedDownload(paraList['path'], paraList['url'])

        # DEBUG
        # self.tool.setTrackMetadata(paraList['trackinfo'], paraList['path'], albumInfo, index, coverpath)
        showprogress = False
        if int(self.config.threadnum) <= 1 and self.showpro:
            showprogress = True

        Contributors = self.tool.getTrackContributors(
            paraList['trackinfo']['id'])
        if needDl:
            try:
                while count > 0:
                    count = count - 1
                    check = netHelper.downloadFile(paraList['url'],
                                                   paraList['path'] + '.part',
                                                   showprogress=showprogress,
                                                   stimeout=20)
                    if check is True:
                        if paraList['key'] == '':
                            # unencrypted -> just move into place
                            os.replace(paraList['path'] + '.part',
                                       paraList['path'])
                            break
                        else:
                            # encrypted -> decrypt and remove encrypted file
                            key, nonce = decrypt_security_token(
                                paraList['key'])
                            decrypt_file(paraList['path'] + '.part',
                                         paraList['path'], key, nonce)
                            os.remove(paraList['path'] + '.part')
                        break
                if check:
                    bIsSuccess = True
                    paraList['path'] = self.tool.covertMp4toM4a(
                        paraList['path'])
                    self.tool.setTrackMetadata(paraList['trackinfo'],
                                               paraList['path'], albumInfo,
                                               index, coverpath, Contributors)
                    pstr = paraList['title']
            except Exception as e:
                printErr(14, str(e) + " while downloading " + paraList['url'])
        else:
            pstr = paraList['title']
            bIsSuccess = True

        if printRet:
            if (bIsSuccess):
                printSUCCESS(14, pstr)
            else:
                printErr(14, pstr)
        return

    # creat album output dir
    def __creatAlbumDir(self, albumInfo):
        # creat outputdir
        title = pathHelper.replaceLimitChar(albumInfo['title'], '-')
        author = pathHelper.replaceLimitChar(albumInfo['artist']['name'], '-')
        if self.config.addyear != 'No':
            if self.config.addyear == 'Before':
                title = '[' + str(
                    datetime.strptime(albumInfo['releaseDate'],
                                      '%Y-%m-%d').year) + '] ' + title
            elif self.config.addyear == 'After':
                title = title + ' [' + str(
                    datetime.strptime(albumInfo['releaseDate'],
                                      '%Y-%m-%d').year) + ']'
            else:
                title = title
        targetDir = self.config.outputdir + "/Album/" + author + '/' + title
        # targetDir = self.config.outputdir + "/Album/" + title + '(' + author + ')'
        targetDir = os.path.abspath(targetDir)
        pathHelper.mkdirs(targetDir)
        # creat volumes dir
        count = 1
        numOfVolumes = int(albumInfo['numberOfVolumes'])
        if numOfVolumes > 1:
            while count < numOfVolumes + 1:
                volumeDir = targetDir + "/Volume" + str(count)
                pathHelper.mkdirs(volumeDir)
                count = count + 1
        return targetDir

    def _getSongExtension(self, downloadUrl):
        if downloadUrl.find('.flac?') != -1:
            return '.flac'
        if downloadUrl.find('.m4a?') != -1:
            return '.m4a'
        if downloadUrl.find('.mp4?') != -1:
            return '.mp4'
        return '.m4a'

    def _IsExplicitString(self, IsExplicit):
        String = None
        if IsExplicit:
            String = 'Explicit'
        return String

    def __getAlbumSongSavePath(self, targetDir, albumInfo, item, extension):
        if extension is None:
            extension = ".m4a"

        seq = self.tool.getIndexStr(item['trackNumber'],
                                    albumInfo['numberOfTracks'])
        name = seq + pathHelper.replaceLimitChar(item['title'], '-')
        fileExplicit = self._IsExplicitString(item['explicit'])
        if self.config.addhyphen == 'True':
            name = seq + '- ' + pathHelper.replaceLimitChar(item['title'], '-')
        if self.config.addexplicit == "True" and fileExplicit is not None:
            name = name + " - " + fileExplicit

        seq = item['volumeNumber']
        path = targetDir + "/"
        if int(albumInfo['numberOfVolumes']) > 1:
            path += 'Volume' + str(seq) + "/"

        maxlen = 255
        if systemHelper.isLinux():
            maxlen = 4090
        # truncate filename when it's longer than system's
        # filename limit which is 255
        len_sum = len(path) + len(name) + len(extension)
        if len_sum > maxlen:
            diff = maxlen - len_sum
            name = name[:len(name) + diff]

        filePath = path + name + extension
        checklen = len(filePath)
        return filePath

    def __getExistFiles(self, paths):
        ret = []
        for item in paths:
            if os.path.isfile(item):
                ret.append(item)
        return ret

    def __getVideoResolutionIndex(self, reslist):
        array = []
        # if reslist != None:
        #     for item in reslist:
        #         subs = item.split('x')
        #         subs = subs[1].split(',')
        #         array.append(int(subs[0]))
        for item in reslist:
            subs = item.split('x')
            subs = subs[1].split(',')
            array.append(int(subs[0]))
        cmp = int(self.config.resolution)
        ret = 0
        for item in array:
            if cmp >= item:
                return ret
            ret += 1
        return len(array) - 1

    def downloadAlbum(self, album_id=None, redl_flag=None):
        while_count = 9999
        while while_count > 0:
            while_count -= 1

            if album_id is not None:
                while_count = 0
                sID = album_id
            else:
                print("----------------ALBUM------------------")
                sID = printChoice("Enter AlbumID(Enter '0' go back):", True, 0)
                if sID == 0:
                    return

            aAlbumInfo = self.tool.getAlbum(sID)
            if self.tool.errmsg != "":
                printErr(0, "Get AlbumInfo Err! " + self.tool.errmsg)
                continue

            print("[Title]       %s" % (aAlbumInfo['title']))
            print("[SongNum]     %s\n" % (aAlbumInfo['numberOfTracks']))

            # Get Tracks
            aAlbumTracks = self.tool.getAlbumTracks(sID)
            if self.tool.errmsg != "":
                printErr(0, "Get AlbumTracks Err!" + self.tool.errmsg)
                continue
            aAlbumVideos = self.tool.getAlbumVideos(sID)

            # Creat OutputDir
            targetDir = self.__creatAlbumDir(aAlbumInfo)
            # write msg
            string = self.tool.convertAlbumInfoToString(
                aAlbumInfo, aAlbumTracks)
            with codecs.open(targetDir + "/AlbumInfo.txt", 'w', 'utf-8') as fd:
                fd.write(string)
            # download cover
            coverPath = targetDir + '/' + pathHelper.replaceLimitChar(
                aAlbumInfo['title'], '-') + '.jpg'
            if aAlbumInfo['cover'] is not None:
                coverUrl = self.tool.getAlbumArtworkUrl(aAlbumInfo['cover'])
                netHelper.downloadFile(coverUrl, coverPath)
            # check exist files
            redownload = True
            if redl_flag is None:
                existFiles = pathHelper.getDirFiles(targetDir)
                for item in existFiles:
                    if '.txt' in item:
                        continue
                    if '.jpg' in item:
                        continue
                    check = printChoice(
                        "Some tracks already exist. Redownload?(y/n):")
                    if not cmdHelper.isInputYes(check):
                        redownload = False
                    break
            else:
                redownload = redl_flag

            # download album tracks
            for item in aAlbumTracks['items']:
                streamInfo = self.tool.getStreamUrl(str(item['id']),
                                                    self.config.quality)
                if self.tool.errmsg != "" or not streamInfo:
                    printErr(
                        14, item['title'] + "(Get Stream Url Err!" +
                        self.tool.errmsg + ")")
                    continue

                fileType = self._getSongExtension(streamInfo['url'])
                filePath = self.__getAlbumSongSavePath(targetDir, aAlbumInfo,
                                                       item, fileType)
                paraList = {
                    'album': aAlbumInfo,
                    'redownload': redownload,
                    'title': item['title'],
                    'trackinfo': item,
                    'url': streamInfo['url'],
                    'path': filePath,
                    'retry': 3,
                    'key': streamInfo['encryptionKey'],
                    'coverpath': coverPath
                }
                self.thread.start(self.__thradfunc_dl, paraList)
            # wait all download thread
            self.thread.waitAll()
            self.tool.removeTmpFile(targetDir)

            # remove cover
            if self.config.savephoto != 'True':
                pathHelper.remove(coverPath)

            # download video

            for item in aAlbumVideos:
                item = item['item']
                filePath = targetDir + '/' + pathHelper.replaceLimitChar(
                    item['title'], '-') + ".mp4"
                filePath = os.path.abspath(filePath)
                if os.access(filePath, 0):
                    os.remove(filePath)

                try:
                    resolutionList, urlList = self.tool.getVideoResolutionList(
                        item['id'])
                    selectIndex = self.__getVideoResolutionIndex(
                        resolutionList)
                    if self.ffmpeg.mergerByM3u8_Multithreading2(
                            urlList[int(selectIndex)],
                            filePath,
                            showprogress=self.showpro):
                        printSUCCESS(14, item['title'])
                    else:
                        printErr(14, item['title'])
                except:
                    printErr(14, item['title'])
            # return

        return

    def downloadArtistAlbum(self, includeSingles=True, artistID=None):
        while True:
            print("-------------ARTIST ALBUM--------------")
            if artistID is not None:
                sID = artistID
            else:
                sID = printChoice("Enter Artist ID(Enter '0' go back):", True,
                                  0)
                if sID == 0:
                    return

            array = self.tool.getArtistAlbum(sID, includeSingles)
            if self.tool.errmsg != "":
                printErr(0, "Get AlbumList Err! " + self.tool.errmsg)
                continue

            redownload = True
            if artistID is None:
                check = printChoice("Skip downloaded files?(y/n):")
                if not cmdHelper.isInputYes(check):
                    redownload = False

            for index, item in enumerate(array):
                print("----Album[{0}/{1}]----".format(index + 1, len(array)))
                self.downloadAlbum(item['id'], redownload)

            if artistID is not None:
                # Break out of the function if we are only downloading one artist's albums
                return

    def downloadTrack(self, track_id=None):
        while_count = 9999
        while while_count > 0:
            while_count -= 1

            if track_id is not None:
                while_count = 0
                sID = track_id
            else:
                print("----------------TRACK------------------")
                sID = printChoice("Enter TrackID(Enter '0' go back):", True, 0)
                if sID == 0:
                    return
            aTrackInfo = self.tool.getTrack(sID)
            if self.tool.errmsg != "":
                printErr(0, "Get TrackInfo Err! " + self.tool.errmsg)
                return
            aAlbumInfo = self.tool.getAlbum(aTrackInfo['album']['id'])
            if self.tool.errmsg != "":
                printErr(0, "Get TrackInfo Err! " + self.tool.errmsg)
                return

            # t = self.tool.getTrackContributors(sID)

            print("[AlbumTitle ]       %s" % (aAlbumInfo['title']))
            print("[TrackTitle ]       %s" % (aTrackInfo['title']))
            print("[Duration   ]       %s" % (aTrackInfo['duration']))
            print("[TrackNumber]       %s" % (aTrackInfo['trackNumber']))
            print("[Explicit   ]       %s" % (aAlbumInfo['explicit']))
            # print("[Version    ]       %s\n" % (aTrackInfo['version']))

            # Creat OutputDir
            targetDir = self.__creatAlbumDir(aAlbumInfo)
            # download cover
            coverPath = targetDir + '/' + pathHelper.replaceLimitChar(
                aAlbumInfo['title'], '-') + '.jpg'
            if aAlbumInfo['cover'] is not None:
                coverUrl = self.tool.getAlbumArtworkUrl(aAlbumInfo['cover'])
                netHelper.downloadFile(coverUrl, coverPath)

            # download
            streamInfo = self.tool.getStreamUrl(sID, self.config.quality)
            if self.tool.errmsg != "" or not streamInfo:
                printErr(
                    14, aTrackInfo['title'] + "(Get Stream Url Err!" +
                    self.tool.errmsg + ")")
                continue

            fileType = self._getSongExtension(streamInfo['url'])
            filePath = self.__getAlbumSongSavePath(targetDir, aAlbumInfo,
                                                   aTrackInfo, fileType)
            paraList = {
                'album': aAlbumInfo,
                'title': aTrackInfo['title'],
                'trackinfo': aTrackInfo,
                'url': streamInfo['url'],
                'path': filePath,
                'retry': 3,
                'key': streamInfo['encryptionKey'],
                'coverpath': coverPath
            }

            self.thread.start(self.__thradfunc_dl, paraList)
            # wait all download thread
            self.thread.waitAll()
            self.tool.removeTmpFile(targetDir)

            # remove cover
            if self.config.savephoto != 'True':
                pathHelper.remove(coverPath)
        return

    def downloadVideo(self, video_id=None):
        flag = True
        while flag:
            targetDir = self.config.outputdir + "/Video/"
            if video_id is None:
                print("----------------VIDEO------------------")
                sID = printChoice("Enter VideoID(Enter '0' go back):", True, 0)
                if sID == 0:
                    return
            else:
                flag = False
                sID = video_id

            aVideoInfo = self.tool.getVideo(sID)
            if self.tool.errmsg != "":
                printErr(0, "Get VideoInfo Err! " + self.tool.errmsg)
                continue

            print("[Title      ]       %s" % (aVideoInfo['title']))
            print("[Duration   ]       %s" % (aVideoInfo['duration']))
            print("[TrackNumber]       %s" % (aVideoInfo['trackNumber']))
            print("[Type       ]       %s\n" % (aVideoInfo['type']))

            # get resolution
            index = 0
            resolutionList, urlList = self.tool.getVideoResolutionList(sID)
            if self.tool.errmsg != "":
                printErr(14, self.tool.errmsg)
                continue

            index = self.__getVideoResolutionIndex(resolutionList)
            path = targetDir + "/" + pathHelper.replaceLimitChar(
                aVideoInfo['title'], '-') + ".mp4"
            path = os.path.abspath(path)
            if os.access(path, 0):
                os.remove(path)

            if self.ffmpeg.mergerByM3u8_Multithreading2(
                    urlList[int(index)], path, True):
                printSUCCESS(14, aVideoInfo['title'])
            else:
                printErr(14, aVideoInfo['title'])
        return

    def downloadPlaylist(self, playlist_id=None):
        while True:
            targetDir = self.config.outputdir + "/Playlist/"
            if playlist_id is None:
                print("--------------PLAYLIST-----------------")
                sID = printChoice("Enter PlayListID(Enter '0' go back):")
                if sID == '0':
                    return
            else:
                sID = playlist_id

            aPlaylistInfo, aItemInfo = self.tool.getPlaylist(sID)
            if self.tool.errmsg != "":
                printErr(0, "Get PlaylistInfo Err! " + self.tool.errmsg)
                return

            print("[Title]                %s" % (aPlaylistInfo['title']))
            print("[Type]                 %s" % (aPlaylistInfo['type']))
            print("[NumberOfTracks]       %s" %
                  (aPlaylistInfo['numberOfTracks']))
            print("[NumberOfVideos]       %s" %
                  (aPlaylistInfo['numberOfVideos']))
            print("[Duration]             %s\n" % (aPlaylistInfo['duration']))

            # Creat OutputDir
            targetDir = targetDir + pathHelper.replaceLimitChar(
                aPlaylistInfo['title'], '-')
            targetDir = os.path.abspath(targetDir).strip()
            pathHelper.mkdirs(targetDir)
            # write msg
            string = self.tool.convertPlaylistInfoToString(
                aPlaylistInfo, aItemInfo)
            with codecs.open(targetDir + "/PlaylistInfo.txt", 'w',
                             'utf-8') as fd:
                fd.write(string)
            # download cover
            coverPath = targetDir + '/' + pathHelper.replaceLimitChar(
                aPlaylistInfo['title'], '-') + '.jpg'
            coverUrl = self.tool.getPlaylistArtworkUrl(aPlaylistInfo['uuid'])
            check = netHelper.downloadFile(coverUrl, coverPath)

            # download track
            bBreakFlag = False
            bFirstTime = True
            errIndex = []
            index = 0

            while bBreakFlag is False:
                self.check.clear()
                index = 0
                tmpcoverpath = []
                for item in aItemInfo:
                    type = item['type']
                    item = item['item']
                    if type != 'track':
                        continue

                    index = index + 1
                    if bFirstTime is False:
                        if self.check.isInErr(index - 1, errIndex) == False:
                            continue

                    streamInfo = self.tool.getStreamUrl(
                        str(item['id']), self.config.quality)
                    # streamInfo = self.tool.getStreamUrl(str(item['id']), 'DOLBY_ATMOS')
                    if self.tool.errmsg != "" or not streamInfo:
                        printErr(
                            14, item['title'] + "(Get Stream Url Err!!" +
                            self.tool.errmsg + ")")
                        continue
                    aAlbumInfo = self.tool.getAlbum(item['album']['id'])
                    fileType = self._getSongExtension(streamInfo['url'])

                    # change targetDir
                    targetDir2 = targetDir
                    if self.config.plfile2arfolder == "True":
                        targetDir2 = self.__creatAlbumDir(aAlbumInfo)
                        filePath = self.__getAlbumSongSavePath(
                            targetDir2, aAlbumInfo, item, fileType)
                        paraList = {
                            'album': aAlbumInfo,
                            'title': item['title'],
                            'trackinfo': item,
                            'url': streamInfo['url'],
                            'path': filePath,
                            'retry': 3,
                            'key': streamInfo['encryptionKey']
                        }
                    else:
                        seq = self.tool.getIndexStr(index, len(aItemInfo))
                        filePath = targetDir2 + '/' + seq + " " + pathHelper.replaceLimitChar(
                            item['title'], '-') + fileType
                        paraList = {
                            'album': aAlbumInfo,
                            'index': index,
                            'title': item['title'],
                            'trackinfo': item,
                            'url': streamInfo['url'],
                            'path': filePath,
                            'retry': 3,
                            'key': streamInfo['encryptionKey']
                        }

                    try:
                        coverPath = targetDir2 + '/' + pathHelper.replaceLimitChar(
                            aAlbumInfo['title'], '-') + '.jpg'
                        coverUrl = self.tool.getAlbumArtworkUrl(
                            aAlbumInfo['cover'])
                        netHelper.downloadFile(coverUrl, coverPath)
                        paraList['coverpath'] = coverPath
                        tmpcoverpath.append(coverPath)
                    except:
                        cmdHelper.myprint(
                            "Could not download artwork for '{}'".format(
                                item['title']), cmdHelper.TextColor.Red, None)

                    if self.config.onlym4a == "True":
                        self.check.addPath(filePath.replace(".mp4", ".m4a"))
                    else:
                        self.check.addPath(filePath)
                    self.thread.start(self.__thradfunc_dl, paraList)
                self.thread.waitAll()
                self.tool.removeTmpFile(targetDir)

                # remove cover
                if self.config.savephoto != 'True':
                    for item in tmpcoverpath:
                        pathHelper.remove(item)

                bBreakFlag = True
                bFirstTime = False

                # check
                isErr, errIndex = self.check.checkPaths()
                if isErr:
                    check = printChoice(
                        "[Err]\t\t" + str(len(errIndex)) +
                        " Tracks Download Failed.Try Again?(y/n):")
                    if check == 'y' or check == 'Y':
                        bBreakFlag = False

            # download video
            for item in aItemInfo:
                type = item['type']
                item = item['item']
                if type != 'video':
                    continue

                filePath = targetDir + '/' + pathHelper.replaceLimitChar(
                    item['title'], '-') + ".mp4"
                filePath = os.path.abspath(filePath)
                if os.access(filePath, 0):
                    os.remove(filePath)

                videoID = item['id']
                resolutionList, urlList = self.tool.getVideoResolutionList(
                    videoID)
                if urlList is None:
                    printErr(14, item['title'] + '(' + self.tool.errmsg + ')')
                else:
                    selectIndex = self.__getVideoResolutionIndex(
                        resolutionList)
                    if self.ffmpeg.mergerByM3u8_Multithreading2(
                            urlList[int(selectIndex)],
                            filePath,
                            showprogress=self.showpro):
                        printSUCCESS(14, item['title'])
                    else:
                        printErr(14,
                                 item['title'] + "(Download Or Merger Err!)")
            if playlist_id is not None:
                return
        return

    def downloadFavorite(self):
        targetDir = self.config.outputdir + "/Favorite/"
        pathHelper.mkdirs(targetDir)

        trackList, videoList = self.tool.getFavorite(self.config.userid)
        if self.tool.errmsg != "":
            printErr(0, "Get FavoriteList Err! " + self.tool.errmsg)
            return

        print("[NumberOfTracks]       %s" % (len(trackList)))
        print("[NumberOfVideos]       %s" % (len(videoList)))
        # download track
        for item in trackList:
            item = item['item']
            streamInfo = self.tool.getStreamUrl(str(item['id']),
                                                self.config.quality)
            if self.tool.errmsg != "" or not streamInfo:
                printErr(
                    14, item['title'] + "(Get Stream Url Err!!" +
                    self.tool.errmsg + ")")
                continue

            fileType = self._getSongExtension(streamInfo['url'])
            filePath = targetDir + '/' + pathHelper.replaceLimitChar(
                item['title'], '-') + fileType
            aAlbumInfo = self.tool.getAlbum(item['album']['id'])
            paraList = {
                'album': aAlbumInfo,
                'title': item['title'],
                'trackinfo': item,
                'url': streamInfo['url'],
                'path': filePath,
                'retry': 3,
                'key': streamInfo['encryptionKey']
            }
            self.thread.start(self.__thradfunc_dl, paraList)
        self.thread.waitAll()

        # download video
        for item in videoList:
            item = item['item']

            filePath = targetDir + '/' + pathHelper.replaceLimitChar(
                item['title'], '-') + ".mp4"
            filePath = os.path.abspath(filePath)
            if os.access(filePath, 0):
                os.remove(filePath)

            resolutionList, urlList = self.tool.getVideoResolutionList(
                item['id'])
            selectIndex = self.__getVideoResolutionIndex(resolutionList)
            if self.ffmpeg.mergerByM3u8_Multithreading2(
                    urlList[int(selectIndex)], filePath,
                    showprogress=self.showpro):
                printSUCCESS(14, item['title'])
            else:
                printErr(14, item['title'])
        return

    def downloadUrl(self, link):
        stype, sid = self.tool.parseLink(link)
        if stype is None or sid is None:
            return
        if stype == "album":
            print("----------------ALBUM------------------")
            self.downloadAlbum(sid)
        elif stype == "track":
            print("----------------TRACK------------------")
            self.downloadTrack(sid)
        elif stype == "video":
            print("----------------VIDEO------------------")
            self.downloadVideo(sid)
        elif stype == "playlist":
            print("--------------PLAYLIST-----------------")
            self.downloadPlaylist(sid)
        elif stype == "artist":
            print("----------------ARTIST-----------------")
            self.downloadArtistAlbum(self.config.includesingle == "True", sid)

    def downloadByFile(self, path):
        if not os.path.exists(path):
            return
        arr = self.tool.parseFile(path)
        print("----------------FILE------------------")
        print("[Number of albums]       %s" % (len(arr['album'])))
        print("[Number of artists]      %s" % (len(arr['artist'])))
        print("[Number of tracks]       %s" % (len(arr['track'])))
        print("[Number of videos]       %s" % (len(arr['video'])))
        print("[Number of URLs]         %s" % (len(arr['url'])))

        if len(arr['album']) > 0:
            redownload = True
            check = printChoice("Skip downloaded files?(y/n):")
            if not cmdHelper.isInputYes(check):
                redownload = False

        for index, item in enumerate(arr['album']):
            print("----Album[{0}/{1}]----".format(index + 1,
                                                  len(arr['album'])))
            print("[ID]          %s" % (item))
            self.downloadAlbum(item, redownload)
        for index, item in enumerate(arr['artist']):
            print(index)
            print("----Artist[{0}/{1}]----".format(index + 1,
                                                   len(arr['artist'])))
            print("[ID]          %s" % (item))
            includeSingles = self.config.includesingle == "True"
            self.downloadArtistAlbum(includeSingles, item)
        for index, item in enumerate(arr['track']):
            print("----Track[{0}/{1}]----".format(index + 1,
                                                  len(arr['track'])))
            print("[ID]                %s" % (item))
            self.downloadTrack(item)
        for index, item in enumerate(arr['video']):
            print("----Video[{0}/{1}]----".format(index + 1,
                                                  len(arr['video'])))
            print("[ID]                %s" % (item))
            self.downloadVideo(item)
        for index, item in enumerate(arr['url']):
            print("----Url[{0}/{1}]----".format(index + 1, len(arr['url'])))
            print("[link]        %s" % (item))
            stype, sid = self.tool.parseLink(item)
            if stype is None or sid is None:
                printErr(14, 'Link can`t parse!')
                continue
            print("[ID]          %s" % (sid))
            if stype == "album":
                print("[Type]        %s" % ("album"))
                self.downloadAlbum(sid)
            if stype == "track":
                print("[Type]        %s" % ("track"))
                self.downloadTrack(sid)
            if stype == "video":
                print("[Type]        %s" % ("video"))
                self.downloadVideo(sid)
Esempio n. 8
0
class Download(object):
    def __init__(self, threadNum=3):
        self.config = TidalConfig()
        self.tool = TidalTool()
        self.thread = ThreadTool(int(threadNum))
        self.ffmpeg = FFmpegTool(mergerTimeout=45)
        self.progress = ProgressTool(100)
        self.check = CheckTool()

        self.showpro = False
        if self.config.showprogress == 'True':
            self.showpro = True

        pathHelper.mkdirs(self.config.outputdir + "/Album/")
        pathHelper.mkdirs(self.config.outputdir + "/Playlist/")
        pathHelper.mkdirs(self.config.outputdir + "/Video/")
        pathHelper.mkdirs(self.config.outputdir + "/Favorite/")

    def __isNeedDownload(self, path, url):
        curSize = fileHelper.getFileSize(path)
        if curSize <= 0:
            return True
        netSize = netHelper.getFileSize(url)
        if curSize >= netSize:
            return False
        return True

    # dowmload track thread
    def __thradfunc_dl(self, paraList):
        count = 1
        printRet = True
        pstr = paraList['title'] + "(Download Err!)"
        redownload = True
        needDl = True
        bIsSuccess = False
        albumInfo = None
        index = None
        coverpath = None

        if 'redownload' in paraList:
            redownload = paraList['redownload']
        if 'retry' in paraList:
            count = count + paraList['retry']
        if 'show' in paraList:
            printRet = paraList['show']
        if 'album' in paraList:
            albumInfo = paraList['album']
        if 'index' in paraList:
            index = paraList['index']
        if 'coverpath' in paraList:
            coverpath = paraList['coverpath']

        if redownload is False:
            needDl = self.__isNeedDownload(paraList['path'], paraList['url'])

        # DEBUG
        # self.tool.setTrackMetadata(paraList['trackinfo'], paraList['path'], albumInfo, index, coverpath)
        showprogress = False
        if int(self.config.threadnum) <= 1 and self.showpro:
            showprogress = True

        Contributors = self.tool.getTrackContributors(
            paraList['trackinfo']['id'])
        if needDl:
            try:
                while count > 0:
                    count = count - 1
                    check = netHelper.downloadFile(paraList['url'],
                                                   paraList['path'],
                                                   showprogress=showprogress)
                    if check is True:
                        if paraList['key'] == '':
                            break
                        key, nonce = decrypt_security_token(paraList['key'])
                        decrypt_file(paraList['path'], key, nonce)
                        break
                if check:
                    bIsSuccess = True
                    paraList['path'] = self.tool.covertMp4toM4a(
                        paraList['path'])
                    self.tool.setTrackMetadata(paraList['trackinfo'],
                                               paraList['path'], albumInfo,
                                               index, coverpath, Contributors)
                    pstr = paraList['title']
            except:
                pass
        else:
            pstr = paraList['title']
            bIsSuccess = True

        if printRet:
            if (bIsSuccess):
                printSUCCESS(14, pstr)
            else:
                printErr(14, pstr)
        return

    # creat album output dir
    def __creatAlbumDir(self, albumInfo):
        # creat outputdir
        title = pathHelper.replaceLimitChar(albumInfo['title'], '-')
        author = pathHelper.replaceLimitChar(albumInfo['artist']['name'], '-')
        targetDir = self.config.outputdir + "/Album/" + author + '/' + title
        # targetDir = self.config.outputdir + "/Album/" + title + '(' + author + ')'
        targetDir = os.path.abspath(targetDir)
        pathHelper.mkdirs(targetDir)
        # creat volumes dir
        count = 1
        numOfVolumes = int(albumInfo['numberOfVolumes'])
        if numOfVolumes > 1:
            while count < numOfVolumes + 1:
                volumeDir = targetDir + "/Volume" + str(count)
                pathHelper.mkdirs(volumeDir)
                count = count + 1
        return targetDir

    def _getSongExtension(self, downloadUrl):
        if downloadUrl.find('.flac?') != -1:
            return '.flac'
        if downloadUrl.find('.m4a?') != -1:
            return '.m4a'
        if downloadUrl.find('.mp4?') != -1:
            return '.mp4'
        return '.m4a'

    def __getAlbumSongSavePath(self, targetDir, albumInfo, item, extension):
        if extension is None:
            extension = ".m4a"

        seq = self.tool.getIndexStr(item['trackNumber'],
                                    albumInfo['numberOfTracks'])
        name = seq + pathHelper.replaceLimitChar(item['title'], '-')
        if self.config.addhyphen == 'True':
            name = seq + '- ' + pathHelper.replaceLimitChar(item['title'], '-')

        seq = item['volumeNumber']
        path = targetDir + "/"
        if int(albumInfo['numberOfVolumes']) > 1:
            path += 'Volume' + str(seq) + "/"

        filePath = path + name + extension
        return filePath

    def __getExistFiles(self, paths):
        ret = []
        for item in paths:
            if os.path.isfile(item):
                ret.append(item)
        return ret

    def __getVideoResolutionIndex(self, reslist):
        array = []
        for item in reslist:
            subs = item.split('x')
            subs = subs[1].split(',')
            array.append(int(subs[0]))
        cmp = int(self.config.resolution)
        ret = 0
        for item in array:
            if cmp >= item:
                return ret
            ret += 1
        return len(array) - 1

    def downloadAlbum(self, album_id=None):
        while_count = 9999
        while while_count > 0:
            while_count -= 1

            if album_id is not None:
                while_count = 0
                sID = album_id
            else:
                print("----------------ALBUM------------------")
                sID = printChoice("Enter AlbumID(Enter '0' go back):", True, 0)
                if sID == 0:
                    return

            aAlbumInfo = self.tool.getAlbum(sID)
            if self.tool.errmsg != "":
                printErr(0, "Get AlbumInfo Err! " + self.tool.errmsg)
                continue

            print("[Title]       %s" % (aAlbumInfo['title']))
            print("[SongNum]     %s\n" % (aAlbumInfo['numberOfTracks']))

            # Get Tracks
            aAlbumTracks = self.tool.getAlbumTracks(sID)
            if self.tool.errmsg != "":
                printErr(0, "Get AlbumTracks Err!" + self.tool.errmsg)
                continue
            aAlbumVideos = self.tool.getAlbumVideos(sID)

            # Creat OutputDir
            targetDir = self.__creatAlbumDir(aAlbumInfo)
            # write msg
            string = self.tool.convertAlbumInfoToString(
                aAlbumInfo, aAlbumTracks)
            with codecs.open(targetDir + "/AlbumInfo.txt", 'w', 'utf-8') as fd:
                fd.write(string)
            # download cover
            coverPath = targetDir + '/' + pathHelper.replaceLimitChar(
                aAlbumInfo['title'], '-') + '.jpg'
            coverUrl = self.tool.getAlbumArtworkUrl(aAlbumInfo['cover'])
            netHelper.downloadFile(coverUrl, coverPath)
            # check exist files
            redownload = True
            existFiles = pathHelper.getDirFiles(targetDir)
            for item in existFiles:
                if '.txt' in item:
                    continue
                if '.jpg' in item:
                    continue
                check = printChoice(
                    "Some TrackFile Exist.Is Redownload?(y/n):")
                if check != 'y' and check != 'yes':
                    redownload = False
                break

            # download album tracks
            for item in aAlbumTracks['items']:
                streamInfo = self.tool.getStreamUrl(str(item['id']),
                                                    self.config.quality)
                if self.tool.errmsg != "":
                    printErr(
                        14, item['title'] + "(Get Stream Url Err!" +
                        self.tool.errmsg + ")")
                    continue

                fileType = self._getSongExtension(streamInfo['url'])
                filePath = self.__getAlbumSongSavePath(targetDir, aAlbumInfo,
                                                       item, fileType)
                paraList = {
                    'album': aAlbumInfo,
                    'redownload': redownload,
                    'title': item['title'],
                    'trackinfo': item,
                    'url': streamInfo['url'],
                    'path': filePath,
                    'retry': 3,
                    'key': streamInfo['encryptionKey'],
                    'coverpath': coverPath
                }
                self.thread.start(self.__thradfunc_dl, paraList)
            # wait all download thread
            self.thread.waitAll()
            self.tool.removeTmpFile(targetDir)

            # download video

            for item in aAlbumVideos:
                item = item['item']
                filePath = targetDir + '/' + pathHelper.replaceLimitChar(
                    item['title'], '-') + ".mp4"
                filePath = os.path.abspath(filePath)
                if os.access(filePath, 0):
                    os.remove(filePath)

                resolutionList, urlList = self.tool.getVideoResolutionList(
                    item['id'])
                selectIndex = self.__getVideoResolutionIndex(resolutionList)
                if self.ffmpeg.mergerByM3u8_Multithreading(
                        urlList[selectIndex], filePath,
                        showprogress=self.showpro):
                    printSUCCESS(14, item['title'])
                else:
                    printErr(14, item['title'])
            return

        return

    def downloadArtistAlbum(self):
        while True:
            print("-------------ARTIST ALBUM--------------")
            sID = printChoice("Enter ArtistID(Enter '0' go back):", True, 0)
            if sID == 0:
                return

            array = self.tool.getArtistAlbum(sID)
            if self.tool.errmsg != "":
                printErr(0, "Get AlbumList Err! " + self.tool.errmsg)
                continue

            for index, item in enumerate(array):
                print("----Album[{0}/{1}]----".format(index + 1, len(array)))
                self.downloadAlbum(item['id'])

    def downloadTrack(self, track_id=None):
        while_count = 9999
        while while_count > 0:
            while_count -= 1

            if track_id is not None:
                while_count = 0
                sID = track_id
            else:
                print("----------------TRACK------------------")
                sID = printChoice("Enter TrackID(Enter '0' go back):", True, 0)
                if sID == 0:
                    return
            aTrackInfo = self.tool.getTrack(sID)
            if self.tool.errmsg != "":
                printErr(0, "Get TrackInfo Err! " + self.tool.errmsg)
                return
            aAlbumInfo = self.tool.getAlbum(aTrackInfo['album']['id'])
            if self.tool.errmsg != "":
                printErr(0, "Get TrackInfo Err! " + self.tool.errmsg)
                return

            # t = self.tool.getTrackContributors(sID)

            print("[AlbumTitle ]       %s" % (aAlbumInfo['title']))
            print("[TrackTitle ]       %s" % (aTrackInfo['title']))
            print("[Duration   ]       %s" % (aTrackInfo['duration']))
            print("[TrackNumber]       %s" % (aTrackInfo['trackNumber']))
            print("[Version    ]       %s\n" % (aTrackInfo['version']))

            # Creat OutputDir
            targetDir = self.__creatAlbumDir(aAlbumInfo)
            # download cover
            coverPath = targetDir + '/' + pathHelper.replaceLimitChar(
                aAlbumInfo['title'], '-') + '.jpg'
            coverUrl = self.tool.getAlbumArtworkUrl(aAlbumInfo['cover'])
            netHelper.downloadFile(coverUrl, coverPath)

            # download
            streamInfo = self.tool.getStreamUrl(sID, self.config.quality)
            if self.tool.errmsg != "":
                printErr(
                    14, aTrackInfo['title'] + "(Get Stream Url Err!" +
                    self.tool.errmsg + ")")
                continue

            fileType = self._getSongExtension(streamInfo['url'])
            filePath = self.__getAlbumSongSavePath(targetDir, aAlbumInfo,
                                                   aTrackInfo, fileType)
            # filePath = targetDir + "/" + pathHelper.replaceLimitChar(aTrackInfo['title'],'-') + fileType
            paraList = {
                'album': aAlbumInfo,
                'title': aTrackInfo['title'],
                'trackinfo': aTrackInfo,
                'url': streamInfo['url'],
                'path': filePath,
                'retry': 3,
                'key': streamInfo['encryptionKey'],
                'coverpath': coverPath
            }
            self.thread.start(self.__thradfunc_dl, paraList)
            # wait all download thread
            self.thread.waitAll()
            self.tool.removeTmpFile(targetDir)
        return

    def downloadVideo(self, video_id=None):
        flag = True
        while flag:
            targetDir = self.config.outputdir + "/Video/"
            if video_id is None:
                print("----------------VIDEO------------------")
                sID = printChoice("Enter VideoID(Enter '0' go back):", True, 0)
                if sID == 0:
                    return
            else:
                flag = False
                sID = video_id

            aVideoInfo = self.tool.getVideo(sID)
            if self.tool.errmsg != "":
                printErr(0, "Get VideoInfo Err! " + self.tool.errmsg)
                continue

            print("[Title      ]       %s" % (aVideoInfo['title']))
            print("[Duration   ]       %s" % (aVideoInfo['duration']))
            print("[TrackNumber]       %s" % (aVideoInfo['trackNumber']))
            print("[Type       ]       %s\n" % (aVideoInfo['type']))

            # get resolution
            index = 0
            resolutionList, urlList = self.tool.getVideoResolutionList(sID)
            if self.tool.errmsg != "":
                printErr(14, self.tool.errmsg)
                continue

            index = self.__getVideoResolutionIndex(resolutionList)
            path = targetDir + "/" + pathHelper.replaceLimitChar(
                aVideoInfo['title'], '-') + ".mp4"
            path = os.path.abspath(path)
            if os.access(path, 0):
                os.remove(path)

            if self.ffmpeg.mergerByM3u8_Multithreading2(
                    urlList[int(index)], path, True):
                printSUCCESS(14, aVideoInfo['title'])
            else:
                printErr(14, aVideoInfo['title'])
        return

    def downloadPlaylist(self):
        while True:
            targetDir = self.config.outputdir + "/Playlist/"
            print("--------------PLAYLIST-----------------")
            sID = printChoice("Enter PlayListID(Enter '0' go back):")
            if sID == '0':
                return

            aPlaylistInfo, aItemInfo = self.tool.getPlaylist(sID)
            if self.tool.errmsg != "":
                printErr(0, "Get PlaylistInfo Err! " + self.tool.errmsg)
                return

            print("[Title]                %s" % (aPlaylistInfo['title']))
            print("[Type]                 %s" % (aPlaylistInfo['type']))
            print("[NumberOfTracks]       %s" %
                  (aPlaylistInfo['numberOfTracks']))
            print("[NumberOfVideos]       %s" %
                  (aPlaylistInfo['numberOfVideos']))
            print("[Duration]             %s\n" % (aPlaylistInfo['duration']))

            # Creat OutputDir
            targetDir = targetDir + pathHelper.replaceLimitChar(
                aPlaylistInfo['title'], '-')
            targetDir = os.path.abspath(targetDir)
            pathHelper.mkdirs(targetDir)
            # write msg
            string = self.tool.convertPlaylistInfoToString(
                aPlaylistInfo, aItemInfo)
            with codecs.open(targetDir + "/PlaylistInfo.txt", 'w',
                             'utf-8') as fd:
                fd.write(string)
            # download cover
            coverPath = targetDir + '/' + pathHelper.replaceLimitChar(
                aPlaylistInfo['title'], '-') + '.jpg'
            coverUrl = self.tool.getPlaylistArtworkUrl(aPlaylistInfo['uuid'])
            check = netHelper.downloadFile(coverUrl, coverPath)

            # download track
            bBreakFlag = False
            bFirstTime = True
            errIndex = []
            index = 0

            while bBreakFlag is False:
                self.check.clear()
                index = 0
                for item in aItemInfo:
                    type = item['type']
                    item = item['item']
                    if type != 'track':
                        continue

                    index = index + 1
                    if bFirstTime is False:
                        if self.check.isInErr(index - 1, errIndex) == False:
                            continue

                    streamInfo = self.tool.getStreamUrl(
                        str(item['id']), self.config.quality)
                    if self.tool.errmsg != "":
                        printErr(
                            14, item['title'] + "(Get Stream Url Err!!" +
                            self.tool.errmsg + ")")
                        continue

                    fileType = self._getSongExtension(streamInfo['url'])
                    filePath = targetDir + '/' + pathHelper.replaceLimitChar(
                        item['title'], '-') + fileType
                    paraList = {
                        'index': index,
                        'title': item['title'],
                        'trackinfo': item,
                        'url': streamInfo['url'],
                        'path': filePath,
                        'retry': 3,
                        'key': streamInfo['encryptionKey']
                    }
                    self.check.addPath(filePath)
                    if not os.path.isfile(filePath):
                        self.thread.start(self.__thradfunc_dl, paraList)
                self.thread.waitAll()
                self.tool.removeTmpFile(targetDir)

                bBreakFlag = True
                bFirstTime = False

                # check
                isErr, errIndex = self.check.checkPaths()
                if isErr:
                    check = printChoice(
                        "[Err]\t\t" + str(len(errIndex)) +
                        " Tracks Download Failed.Try Again?(y/n):")
                    if check == 'y' or check == 'Y':
                        bBreakFlag = False

            # download video
            for item in aItemInfo:
                type = item['type']
                item = item['item']
                if type != 'video':
                    continue

                filePath = targetDir + '/' + pathHelper.replaceLimitChar(
                    item['title'], '-') + ".mp4"
                filePath = os.path.abspath(filePath)
                if os.access(filePath, 0):
                    os.remove(filePath)

                videoID = item['id']
                resolutionList, urlList = self.tool.getVideoResolutionList(
                    videoID)
                if urlList is None:
                    printErr(14, item['title'] + '(' + self.tool.errmsg + ')')
                else:
                    selectIndex = self.__getVideoResolutionIndex(
                        resolutionList)
                    if self.ffmpeg.mergerByM3u8_Multithreading(
                            urlList[selectIndex],
                            filePath,
                            showprogress=self.showpro):
                        printSUCCESS(14, item['title'])
                    else:
                        printErr(14,
                                 item['title'] + "(Download Or Merger Err!)")
        return

    def downloadFavorite(self):
        targetDir = self.config.outputdir + "/Favorite/"
        pathHelper.mkdirs(targetDir)

        trackList, videoList = self.tool.getFavorite(self.config.userid)
        if self.tool.errmsg != "":
            printErr(0, "Get FavoriteList Err! " + self.tool.errmsg)
            return

        print("[NumberOfTracks]       %s" % (len(trackList)))
        print("[NumberOfVideos]       %s" % (len(videoList)))
        # download track
        for item in trackList:
            item = item['item']
            streamInfo = self.tool.getStreamUrl(str(item['id']),
                                                self.config.quality)
            if self.tool.errmsg != "":
                printErr(
                    14, item['title'] + "(Get Stream Url Err!!" +
                    self.tool.errmsg + ")")
                continue

            fileType = self._getSongExtension(streamInfo['url'])
            filePath = targetDir + '/' + pathHelper.replaceLimitChar(
                item['title'], '-') + fileType
            paraList = {
                'title': item['title'],
                'trackinfo': item,
                'url': streamInfo['url'],
                'path': filePath,
                'retry': 3,
                'key': streamInfo['encryptionKey']
            }
            self.thread.start(self.__thradfunc_dl, paraList)
        self.thread.waitAll()

        # download video
        for item in videoList:
            item = item['item']

            filePath = targetDir + '/' + pathHelper.replaceLimitChar(
                item['title'], '-') + ".mp4"
            filePath = os.path.abspath(filePath)
            if os.access(filePath, 0):
                os.remove(filePath)

            resolutionList, urlList = self.tool.getVideoResolutionList(
                item['id'])
            selectIndex = self.__getVideoResolutionIndex(resolutionList)
            if self.ffmpeg.mergerByM3u8_Multithreading(
                    urlList[selectIndex], filePath, showprogress=self.showpro):
                printSUCCESS(14, item['title'])
            else:
                printErr(14, item['title'])
        return

    def downloadUrl(self, link):
        stype, sid = self.tool.parseLink(link)
        if stype is None or sid is None:
            return
        if stype == "album":
            print("----------------ALBUM------------------")
            self.downloadAlbum(sid)
        elif stype == "track":
            print("----------------TRACK------------------")
            self.downloadTrack(sid)
        elif stype == "video":
            print("----------------VIDEO------------------")
            self.downloadVideo(sid)

    def downloadByFile(self, path):
        if not os.path.exists(path):
            return
        arr = self.tool.parseFile(path)
        print("----------------FILE------------------")
        print("[NumOfAlbum]       %s" % (len(arr['album'])))
        print("[NumOfTrack]       %s" % (len(arr['track'])))
        print("[NumOfVideo]       %s" % (len(arr['video'])))
        print("[NumOfUrl]         %s" % (len(arr['url'])))
        for index, item in enumerate(arr['album']):
            print("----Album[{0}/{1}]----".format(index + 1,
                                                  len(arr['album'])))
            print("[ID]          %s" % (item))
            self.downloadAlbum(item)
        for index, item in enumerate(arr['track']):
            print("----Track[{0}/{1}]----".format(index + 1,
                                                  len(arr['track'])))
            print("[ID]                %s" % (item))
            self.downloadTrack(item)
        for index, item in enumerate(arr['video']):
            print("----Video[{0}/{1}]----".format(index + 1,
                                                  len(arr['video'])))
            print("[ID]                %s" % (item))
            self.downloadVideo(item)
        for index, item in enumerate(arr['url']):
            print("----Url[{0}/{1}]----".format(index + 1, len(arr['url'])))
            print("[link]        %s" % (item))
            stype, sid = self.tool.parseLink(item)
            if stype is None or sid is None:
                printErr(14, 'Link can`t parse!')
                continue
            print("[ID]          %s" % (sid))
            if stype == "album":
                print("[Type]        %s" % ("album"))
                self.downloadAlbum(sid)
            if stype == "track":
                print("[Type]        %s" % ("track"))
                self.downloadTrack(sid)
            if stype == "video":
                print("[Type]        %s" % ("video"))
                self.downloadVideo(sid)