rsalmei / alive-progress

A new kind of Progress Bar, with real-time throughput, ETA, and very cool animations!
MIT License
5.45k stars 205 forks source link

Using alive_bar without with in telethon #276

Closed GAS85 closed 1 month ago

GAS85 commented 1 month ago

Hey, I was thinking how to implement this bar inside of callbacks within Telethon and didn't find a good way. The issue: There is a Method download_media with progress_callback exist that is designed to callback function accepting two parameters: (received bytes, total). So I can implement progress like this:

# Printing download progress
def callback(current, total):
    print('Downloaded', current, 'out of', total,
          'bytes: {:.2%}'.format(current / total), end='\r')

await client.download_media(message, progress_callback=callback)

it will looks like this in one line only:

image

But how to do it with alive_bar? I tried:

from alive_progress import alive_bar
from alive_progress import config_handler

config_handler.set_global(unit='B', scale='SI', precision=1, stats=False, elapsed=False)
client = TelegramClient('session_name', api_id, api_hash)

def callback(current, total):
    with alive_bar(total) as bar:
        bar(current)

def downloader():
    client.start()
    for message in client.iter_messages(search_channel, search=search_line_update(search_hashtag, search_serie_number)):
        print(message.id, message.text, '\n\n###\n')
        client.download_media(message, file=download_location, progress_callback=callback)

But this sill spam my console:

|▏⚠︎                                      | (!) 1MB/475MB [0%] 
|▏⚠︎                                      | (!) 1.3MB/475MB [0%] 
|▏⚠︎                                      | (!) 1.6MB/475MB [0%] 
|▏⚠︎                                      | (!) 1.8MB/475MB [0%] 
|▏⚠︎                                      | (!) 2.1MB/475MB [0%] 
|▎⚠︎                                      | (!) 2.4MB/475MB [0%] 
|▎⚠︎                                      | (!) 2.6MB/475MB [1%] 
|▎⚠︎                                      | (!) 2.9MB/475MB [1%] 
|▎⚠︎                                      | (!) 3.1MB/475MB [1%] 
|▎⚠︎                                      | (!) 3.4MB/475MB [1%] 
|▎⚠︎                                      | (!) 3.7MB/475MB [1%] 
|▍⚠︎                                      | (!) 3.9MB/475MB [1%] 
|▍⚠︎                                      | (!) 4.2MB/475MB [1%] 

and I need to disable stats and elapsed as those values will show some false data. Any idea how to proceed with this callback?

rsalmei commented 1 month ago

Yes, you can't enter the with context directly on the callback, since that will create another alive-progress bar each time a new update is received. And, unfortunately, it seems you do not have the total beforehand. It would be much simpler to create the bar within the for message, before calling client.download_media...

In that case, you have to keep the alive-progress context alive, which you can do with a generator. And, you also need a way to keep the bar object, which you can do with a closure. I think something like this could work:

def bar_gen(total):
    with alive_bar(total) as bar:
        yield bar

def callback_gen():
    progress, bar = None, None
    def callback(current, total):
        nonlocal progress, bar
        if bar is None:
            progress = bar_gen(total)
            bar = next(progress)
        bar(current)
    return callback

Then, in your code:

from alive_progress import alive_bar
from alive_progress import config_handler

config_handler.set_global(unit='B', scale='SI', precision=1, stats=False, elapsed=False)
client = TelegramClient('session_name', api_id, api_hash)

def downloader():
    client.start()
    for message in client.iter_messages(search_channel, search=search_line_update(search_hashtag, search_serie_number)):
        print(message.id, message.text, '\n\n###\n')
        callback = callback_gen()
        client.download_media(message, file=download_location, progress_callback=callback)

That should work.

GAS85 commented 1 month ago

изображение

Cool, now it works, but running our of values :facepalm:. I do not know why it is taking total as correct, but current progress is calculated in a wrong way... I made some additional output to troubleshoot and seems it makes sum of previous current and actual value, so it "explodes". It shown as "on" value.

def callback_gen():
    progress, bar = None, None
    def callback(current, total):
        nonlocal progress, bar
        if bar is None:
            progress = bar_gen(total)
            bar = next(progress)
        bar(current)
        print('Downloaded', current, 'out of', total,
          'bytes: {:.2%}'.format(current / total), end='\r')
on 262144: Downloaded 262144 out of 474950838 bytes: 0.06%
on 786432: Downloaded 524288 out of 474950838 bytes: 0.11%
on 1572864: Downloaded 786432 out of 474950838 bytes: 0.17%
on 2621440: Downloaded 1048576 out of 474950838 bytes: 0.22%
on 3932160: Downloaded 1310720 out of 474950838 bytes: 0.28%
on 5505024: Downloaded 1572864 out of 474950838 bytes: 0.33%
on 7340032: Downloaded 1835008 out of 474950838 bytes: 0.39%
on 9437184: Downloaded 2097152 out of 474950838 bytes: 0.44%
on 11796480: Downloaded 2359296 out of 474950838 bytes: 0.50%
rsalmei commented 1 month ago

Ahh yes, my mistake. We should take the difference of the previous value. And, I now made the bar correctly shut down, stopping its internal thread. Here it is:

def callback_gen():
    progress, bar = None, None
    def callback(current, total):
        nonlocal progress, bar
        if bar is None:
            progress = bar_gen(total)
            bar = next(progress)
        bar(current - bar.current)
        if current == total:
            del progress
    return callback
GAS85 commented 1 month ago

Wow, this works perfect, thanks! I'm only since Monday in python and it seems a lot of fun here :smile: