Wget prints out speed information to stdout in a very clear manner, where the speed is shown and refreshed whilst the file is downloaded, and a bar scrolls across the screen. I’d like to replicate this sort of output, in a Python program. How is this possible?
I thought the curses library should be able to do this; this is what I came up with:-
import curses, time
class Speeder( object ):
"""Show and refresh time and download speed in a curses interface."""
t1 = 0. # start time
t = 0. # current time
tf = 0. # end time
def start(self, filename=None ):
"""Start timer"""
self.t1 = self.t = time.time()
curses.use_env(True)
curses.initscr()
self.win = curses.newwin(4, 0)
if filename is not None:
self.win.addnstr(filename, 50 )
self.win.refresh()
def update(self, rbytes):
"""Refresh speed."""
t = time.time()
td = t - self.t
self.t = t
speed = rbytes / td
self.win.addstr(0,54,str('{0:.02f} B/s'.format(speed)))
self.win.refresh()
def end(self):
"""End timer"""
self.tf = time.time()
curses.endwin()
try:
speed = Speeder()
speed.start(filename='foo.bar')
for i in xrange(10):
time.sleep(0.5)
speed.update(200)
finally:
speed.end()
The problem is that it takes up an entire window, and I only really need a line at a time. I’d rather not push all the command line history back above the terminal window. Please correct me if I’m wrong, but I currently think curses apps always take up the entire terminal window.
So I had a look at the tty and termios modules, but I can’t find any examples doing what I want to do.
Finally, I came across a blog post which does it by simply writing some special characters to sys.stdout. Applying his code to my Speeder class, I came up with this:-
import sys
class Speeder( object ):
"""Show and refresh time and download speed."""
t1 = 0. # start time
t = 0. # current time
tf = 0. # end time
def start(self, filename=None ):
"""Start timer"""
self.t1 = self.t = time.time()
if filename is not None:
sys.stdout.write( '\r\x1b[K' + filename.ljust(50) )
sys.stdout.flush()
def update(self, rbytes):
"""Refresh speed."""
t = time.time()
td = t - self.t
self.t = t
speed = '{0} B/s'.format( rbytes / td )
sys.stdout.write( "\r\x1b[K" + speed.rjust(65) )
sys.stdout.flush()
def end(self):
"""End timer"""
self.tf = time.time()
sys.stdout.write('\n')
This has fewer lines of code, which I like, but tbh I don’t have a clue what’s going on, or how to modify it to only update part of the line, similar to the curses.addstr function. Currently, the filename is overwritten with white-space. I could of course modify the class to construct the entire line each time from python, but I’d like to understand this in order to tailor it to my own needs.
The author says this is possible only on VT100 terminals. So I guess this would only work on Unix terminals then? That’s fine, but are there any Unix terminals that this would not work on?
Is there better documentation on the subject, or more complicated examples (e.g. updating multiple lines, one line of many, or single characters on a line), preferably for Python? This doesn’t seem like a feature of Python, but the tty, which is why I guess I’ve failed to find anything. Is there a man page or something I should read?
The code above is using ANSI escape codes: https://en.wikipedia.org/wiki/ANSI_escape_code. I think that’s the documentation you are looking for when trying to grok the code you found.