+# Title string normalization
+def title_string_norm(title):
+       title = ''.join((x in string.ascii_letters or x in string.digits) and x or ' ' for x in title)
+       title = '_'.join(title.split())
+       title = title.lower()
+       return title
+
+# Generic download step
+def download_step(return_data_flag, step_title, step_error, url, post_data=None):
+       try:
+               cond_print('%s... ' % step_title)
+               data = perform_request(url, data=post_data).read()
+               cond_print('done.\n')
+               if return_data_flag:
+                       return data
+               return None
+
+       except (urllib2.URLError, ValueError, httplib.HTTPException, TypeError, socket.error):
+               cond_print('failed.\n')
+               error_advice_exit(step_error)
+
+       except KeyboardInterrupt:
+               sys.exit('\n')
+
+# Generic extract step
+def extract_step(step_title, step_error, regexp, data):
+       try:
+               cond_print('%s... ' % step_title)
+               match = regexp.search(data)
+               
+               if match is None:
+                       cond_print('failed.\n')
+                       error_advice_exit(step_error)
+               
+               extracted_data = match.group(1)
+               cond_print('done.\n')
+               return extracted_data
+       
+       except KeyboardInterrupt:
+               sys.exit('\n')
+
+# Calculate new block size based on previous block size
+def new_block_size(before, after, bytes):
+       new_min = max(bytes / 2.0, 1.0)
+       new_max = max(bytes * 2.0, 1.0)
+       dif = after - before
+       if dif < const_epsilon:
+               return int(new_max)
+       rate = bytes / dif
+       if rate > new_max:
+               return int(new_max)
+       if rate < new_min:
+               return int(new_min)
+       return int(rate)
+
+# Get optimum 1k exponent to represent a number of bytes
+def optimum_k_exp(num_bytes):
+       global const_1k
+       if num_bytes == 0:
+               return 0
+       return long(math.log(num_bytes, const_1k))
+
+# Get optimum representation of number of bytes
+def format_bytes(num_bytes):
+       global const_1k
+       try:
+               exp = optimum_k_exp(num_bytes)
+               suffix = 'bkMGTPEZY'[exp]
+               if exp == 0:
+                       return '%s%s' % (num_bytes, suffix)
+               converted = float(num_bytes) / float(const_1k**exp)
+               return '%.2f%s' % (converted, suffix)
+       except IndexError:
+               sys.exit('Error: internal error formatting number of bytes.')
+
+# Calculate ETA and return it in string format as MM:SS
+def calc_eta(start, now, total, current):
+       dif = now - start
+       if current == 0 or dif < const_epsilon:
+               return '--:--'
+       rate = float(current) / dif
+       eta = long((total - current) / rate)
+       (eta_mins, eta_secs) = divmod(eta, 60)
+       if eta_mins > 99:
+               return '--:--'
+       return '%02d:%02d' % (eta_mins, eta_secs)
+
+# Calculate speed and return it in string format
+def calc_speed(start, now, bytes):
+       dif = now - start
+       if bytes == 0 or dif < const_epsilon:
+               return 'N/A b'
+       return format_bytes(float(bytes) / dif)
+
+
+# Title string minimal transformation
+def title_string_touch(title):
+       return title.replace(os.sep, '%')
+