def mal_get_short(username, **kwargs):
    """
    gets a MAL list with a username
    kwargs reffer to the arguments (sort=3,x='w') -> ?sort=3&x=w
    """
    url = MALURL.format(user=username)+'?'
    url += '&'.join(k+'='+str(v) for k, v in kwargs.items())
    r = requests.get(url)
    if r.status_code == 200:
        return r.json()
    else:
        if r.status_code == 500:
            fprint('error', 'too many requests made, try again later')
        r.raise_for_status()
def batch_download(username,
                   statuses=[1, 2],
                   webm_folder=None,
                   mp3_folder=None,
                   anilist=False,
                   extra_ids=[]):
    """
    basically the main function
    given a username and destination, downloads all themes from MAL/AniList
    """
    fprint('progress', 'initializing program')
    download_data = get_download_data(username, statuses, anilist, extra_ids)
    download_multi_theme(download_data, webm_folder, mp3_folder)
    fprint('progress', 'finished downloading')
def al_get(username, variables={}):
    """
    gets data from AniList using 
    variables reffer to JQuery variables
    """
    if username == '':
        return []
    variables['user'] = username
    json_arg = {'query': ALQUERY, 'variables': variables}
    r = requests.post(ALURL, json=json_arg)
    if "errors" in r:
        fprint('error', f"{'; '.join(i['message'] for i in r['errors'])}\n{r['errors']}")
    if r.status_code == 200:
        return sort_al(r.json())
    else:
        r.raise_for_status()
def convert_ffmpeg(webm_filename, mp3_filename=None, save_folder=None):
    """
    convert a video file to an audio file with ffmpeg
    """
    if mp3_filename is None:
        mp3_filename = webm_filename[:-5]
    if save_folder is not None:
        mp3_filename = os.path.join(save_folder,
                                    os.path.basename(mp3_filename))
    mp3_filename += '.' + Opts.Download.audio_format
    loglevel = 'quiet' if Opts.Print.quiet else 'warning'
    ffmpeg_path = Opts.Download.ffmpeg
    os.system(
        f'{ffmpeg_path} -i "{webm_filename}" "{mp3_filename}" -y -stats -v quiet -loglevel {loglevel}'
    )
    if not os.path.isfile(mp3_filename):
        fprint('error',
               "ffmpeg didn't convert, check if it's in path or use --ffmpeg")
        quit()
    return mp3_filename
def get_proper(username, anilist=False, extra_ids=[], **mal_kwargs):
    """
    gets proper data from MAL/AniList
    sorted by status (0 = extra ids)
    """
    t = time.time()
    fprint('get', f'getting data from {"anilist.co" if anilist else "myanimelist.net"}', end='')
    if anilist:
        list_data = al_get(username)
    else:
        list_data = mal_get(username, **mal_kwargs)
    fprint(' | '+str(int((time.time()-t)*1000))+'ms')

    t = time.time()
    fprint('get', 'getting data from themes.moe', end='')
    if anilist:
        themes_data = get_themes(username, 'anilist')
    else:
        themes_data = get_themes(username, 'mal')
    extra_themes_data = []
    for malid in extra_ids:
        data = parse_anime(themes_get('id', malid))
        data['mal_data'] = {
            'status': 5,
            'score': 10,
            'priority_string': 'High',
            'anime_id': malid,
            'anime_title': ''
        }
        extra_themes_data.append(data)
    fprint(' | '+str(int((time.time()-t)*1000))+'ms')

    mal_dict = {}  # {12345:{'mal_data':{...},'status':1},...}
    for status, animes in enumerate(list_data):
        for anime in animes:
            mal_dict[anime['anime_id']] = {'mal_data': anime, 'status': status}

    mal_list = {i: [] for i in range(0, 7)}
    for anime in themes_data:
        mal_id = anime['mal_id']
        if mal_id not in mal_dict:
            continue
        status = mal_dict[mal_id]['status']
        mal_data = mal_dict[mal_id]['mal_data']
        anime['mal_data'] = mal_data
        mal_list[status].append(anime)

    mal_list[0] = extra_themes_data

    return mal_list
def download_theme(theme_data,
                   webm_folder=None,
                   mp3_folder=None,
                   no_redownload=False):
    """
    downloads a theme with given theme data and destination
    """
    # generate a folder to save webm files
    if webm_folder is None:
        filename = os.path.join(mp3_folder, theme_data["filename"])
    else:
        filename = os.path.join(webm_folder, theme_data["filename"])
    exwebm, exmp3 = os.path.isfile(filename), os.path.isfile(
        os.path.splitext(filename)[0] + '.mp3')

    # determine what files are needed
    if no_redownload:
        needmp3 = mp3_folder is not None and not exmp3
        needwebm = (webm_folder is not None and not exwebm) or (needmp3
                                                                and not exwebm)
    else:
        needwebm = True
        needmp3 = mp3_folder is not None

    # download webm file if file does not exist
    if needwebm:
        fprint('download', theme_data['filename'])
        obj = SmartDL(theme_data["mirrors"],
                      filename,
                      progress_bar=not Opts.Print.quiet)
        try:
            obj.start()
        except Exception as e:
            fprint('error', str(e))
        except KeyboardInterrupt:
            obj.stop()
            os.remove(obj.get_dest() + '.000')
            quit()
        webm_dest = obj.get_dest()
        remove_webm = True
    else:
        webm_dest = filename
        remove_webm = False

    # download mp3 file if file does not exist
    if needmp3:
        if not needwebm:
            fprint('convert', theme_data['filename'])
        mp3dest = convert_ffmpeg(webm_dest, save_folder=mp3_folder)
        add_metadata(mp3dest, theme_data["metadata"], Opts.Download.coverart)
    else:
        mp3dest = None

    # delete webm folder is deemed neccesary
    if webm_folder is None:
        if remove_webm:
            os.remove(webm_dest)
        webm_dest = None

    return {"mp3": mp3dest, "webm": webm_dest}
def add_metadata(path, metadata, add_coverart):
    """
    adds metadata to an mp3 file
    returns True if succeeded, False if failed
    """
    if os.path.splitext(path)[1] != '.mp3':
        return False

    t = time.time()
    fprint(f'adding metadata for v2.4' +
           (' with coverart' if add_coverart else ''),
           end='')
    audiofile = eyed3.load(path)
    if (audiofile.tag == None):
        audiofile.initTag()

    audiofile.tag.album = metadata["album"]
    audiofile.tag.title = metadata["title"]
    audiofile.tag.year = metadata["year"]
    if add_coverart:
        image = requests.get(metadata["cover art"]).text.encode()
        audiofile.tag.images.set(3, image, 'image/jpeg')
    audiofile.tag.genre = 145

    try:
        audiofile.tag.save()
        fprint(' | ' + str(int((time.time() - t) * 1000)) + 'ms')
        return True
    except PermissionError as e:
        fprint('error', f"couldn't add metadata: {e}", start='\n')
        return False
    def progress_bar():
        """
        this progress bar is completely fake, but it makes it feel like something is happening so that's cool
        """
        global _LAST_DOWNLOAD_TIME
        START_TIME = time.time()
        last_increment = last_decrement = time.time()
        expected_remain = _LAST_DOWNLOAD_TIME + 0.75

        def current_time():
            return time.time() - START_TIME

        def download_string(x, y, r=2):
            x = str(x).split('.')
            y = str(y).split('.')
            x = x[0] + '.' + x[1][:r]
            y = y[0] + '.' + y[1][:r]
            return f'[*] {x}s / {y}s  '

        while True:
            fprint(download_string(current_time(), expected_remain), end='\r')

            # time updater
            difference = expected_remain - current_time()
            if difference < 1:
                # never get it too close to the expected remain
                expected_remain += 1
                last_increment = time.time()
            elif time.time() - last_increment > 0.5 and difference < 10:
                # add time to make it look like it's updating
                expected_remain += 1 / (expected_remain - current_time())
                last_increment = time.time()
            elif time.time() - last_decrement > 2:
                # remove some time because it feels good to make it faster
                expected_remain -= 2 / (expected_remain - current_time())
                last_decrement = time.time()

            if STOP_PROGRESS_BAR:
                break
            if current_time() >= timeout:
                fprint('error', 'download timed out')
                return

        if FAKE_COMPLETE:
            fprint(download_string(current_time(), current_time()))
            _LAST_DOWNLOAD_TIME = current_time()
        else:
            fprint('')
def get_download_data(username,
                      statuses=[1, 2],
                      anilist=False,
                      extra_ids=[],
                      mal_args={}):
    """
    gets download data with a MAL/AniList username, adds extra anime and fiters out unwanted stuff
    """
    # [{"filename":"...","mirrors":[...],"metadata":{...}},...]
    out = []
    data = get_proper(username, anilist, extra_ids, **mal_args)
    fprint('parse', 'getting download data')
    for status in statuses:
        for anime in data[status]:
            if (anime['mal_data']['score'] < Opts.Animelist.minscore
                    or to_mal_priority(anime['mal_data']['priority_string']) <
                    Opts.Animelist.minpriority):
                continue
            for theme, unparsed in zip(parse_download_data(anime),
                                       anime['themes']):

                theme["metadata"]["cover art"] = anime["cover"]
                out.append(theme)
    return out
def download_multi_theme(download_data, webm_folder=None, mp3_folder=None):
    """
    downloads multiple themes with download data
    """
    if webm_folder is None and mp3_folder is None:
        fprint('error', 'no save folder set')
    if webm_folder and not os.path.isdir(webm_folder):
        os.mkdir(webm_folder)
    if mp3_folder and not os.path.isdir(mp3_folder):
        os.mkdir(mp3_folder)

    download_chooser = (
        lambda mthd: download_theme(theme, webm_folder, mp3_folder, Opts.
                                    Download.no_redownload)
        if (mthd or webm_folder is not None) else download_theme_audio_server(
            theme, mp3_folder, Opts.Download.no_redownload))

    # 0: external, 1: local
    if Opts.Download.local_convert or webm_folder is not None:
        mthd = 1
        fprint(
            'progress', 'started downloading' +
            (' and converting' if mp3_folder is not None else ''))
    else:
        mthd = 0
        fprint('progress', 'started downloading audio files')

    for theme in download_data:
        filename = None
        while filename is None:
            filename = download_chooser(mthd)
            if filename is None and Opts.Download.try_both:
                filename = download_chooser(not mthd)

            if not Opts.Download.retry_forever:
                break
def download_theme_audio_server(theme_data,
                                mp3_folder=None,
                                no_redownload=False,
                                data_size=65536,
                                timeout=30):
    """
    downloads an audio file from from a server
    the progress bar is completely fake

    returns:
    filename (str)
    empty str if no download was made
    None if all downloads failed
    """
    def progress_bar():
        """
        this progress bar is completely fake, but it makes it feel like something is happening so that's cool
        """
        global _LAST_DOWNLOAD_TIME
        START_TIME = time.time()
        last_increment = last_decrement = time.time()
        expected_remain = _LAST_DOWNLOAD_TIME + 0.75

        def current_time():
            return time.time() - START_TIME

        def download_string(x, y, r=2):
            x = str(x).split('.')
            y = str(y).split('.')
            x = x[0] + '.' + x[1][:r]
            y = y[0] + '.' + y[1][:r]
            return f'[*] {x}s / {y}s  '

        while True:
            fprint(download_string(current_time(), expected_remain), end='\r')

            # time updater
            difference = expected_remain - current_time()
            if difference < 1:
                # never get it too close to the expected remain
                expected_remain += 1
                last_increment = time.time()
            elif time.time() - last_increment > 0.5 and difference < 10:
                # add time to make it look like it's updating
                expected_remain += 1 / (expected_remain - current_time())
                last_increment = time.time()
            elif time.time() - last_decrement > 2:
                # remove some time because it feels good to make it faster
                expected_remain -= 2 / (expected_remain - current_time())
                last_decrement = time.time()

            if STOP_PROGRESS_BAR:
                break
            if current_time() >= timeout:
                fprint('error', 'download timed out')
                return

        if FAKE_COMPLETE:
            fprint(download_string(current_time(), current_time()))
            _LAST_DOWNLOAD_TIME = current_time()
        else:
            fprint('')

    # generate a folder to save webm files
    short_filename = os.path.splitext(theme_data["filename"])[0] + '.mp3'
    filename = os.path.join(mp3_folder, short_filename)

    # download mp3 file if file does not exist
    if not (no_redownload and os.path.isfile(filename)):
        fprint('download', short_filename)
        for mirror in theme_data['audio_mirrors']:
            STOP_PROGRESS_BAR = False
            FAKE_COMPLETE = False
            thread = threading.Thread(target=progress_bar)
            thread.start()
            try:
                r = requests.get(mirror)
            except KeyboardInterrupt:
                STOP_PROGRESS_BAR = True
                thread.join()
                quit()
            except Exception as e:
                fprint('error', e)
                FAKE_COMPLETE = True
                STOP_PROGRESS_BAR = True
                thread.join()
                continue

            if r.status_code != 200:
                if r.status_code not in (500, 404):
                    FAKE_COMPLETE = True
                STOP_PROGRESS_BAR = True
                thread.join()
                fprint('error',
                       f'invalid mirror, got status code {r.status_code}')
                continue
            else:
                FAKE_COMPLETE = True
                STOP_PROGRESS_BAR = True
                thread.join()

            with open(filename, 'wb') as file:
                for data in r.iter_content(data_size):  # 64kiB
                    file.write(data)
            break
        else:
            fprint('error',
                   'all mirrors are invalid (press CTRL+C to stop program)')
            return None

        add_metadata(filename, theme_data["metadata"], Opts.Download.coverart)
        return filename
    else:
        return ''
Example #12
0
args.status = [1, 2] + args.status
args.id = args.id or []

if args.settings is not None:
    with open(args.settings) as file:
        jargs = json.load(file)
        if 'quiet' not in jargs:
            jargs['quiet'] = False
        for k in jargs:
            args.__setattr__(k, jargs[k])

Opts.update(**args.__dict__)

args.status.append(0)

if args.print_settings:
    fprint('\n'.join(
        [f'{k}={repr(v)}' for k, v in Opts.get_settings().items()]),
           end='\n\n')

if args.username == '' and len(args.id) == 0:
    fprint('error', 'no username set')
    quit()

if args.video is None and args.audio is None:
    fprint('error', 'no save folder set')
    quit()

batch_download(args.username, args.status, args.video, args.audio,
               args.anilist, args.id)