Skip to content

Instantly share code, notes, and snippets.

@Fooftilly
Last active April 18, 2025 13:14
Show Gist options
  • Save Fooftilly/33dcfcc84bcfb479d973df54912850ea to your computer and use it in GitHub Desktop.
Save Fooftilly/33dcfcc84bcfb479d973df54912850ea to your computer and use it in GitHub Desktop.
Limit upload speed in qBittorrent based on the upload ratio of individual torrents
#!/usr/bin/python
import qbittorrentapi #Download here: https://pypi.org/project/qbittorrent-api/
import time
import signal
import sys
import psutil
from collections import deque
from datetime import datetime
# SETTINGS:
# Upload speed of an individual torrent won't be limited until it reached this ratio
UNLIMITED_RATIO = 5
UNLIMITED_SPEED = -1 # Default upload speed for torrents below UNLIMITED_RATIO (set to -1 for infinite)
# Proportionally lower upload speeds from MAX_SPEED to MIN_SPEED until the ratio of a individual torrent reaches MAX_RATIO.
MIN_RATIO = 5
MIN_SPEED = 100 # Minimum upload speed in KiB/s
MAX_RATIO = 20
MAX_SPEED = 400 # Maximum upload speed in KiB/s
# Size priority settings
SIZE_PRIORITY_THRESHOLD = 10 * 1024 * 1024 # 10 GB in KiB
MAX_SIZE_PRIORITY_BONUS = 2.0 # Maximum speed multiplier for large torrents
MIN_SIZE_PRIORITY_FACTOR = 0.5 # Minimum speed multiplier for small torrents
# Ratio priority settings
RATIO_PRIORITY_THRESHOLD = 4.0 # Ratio threshold for priority boost
MAX_RATIO_PRIORITY_BONUS = 2.0 # Maximum speed multiplier for low ratio torrents
MIN_RATIO_PRIORITY_FACTOR = 0.8 # Minimum speed multiplier for high ratio torrents
# Bandwidth management settings
TOTAL_MAX_UPLOAD = 10000 # Maximum total upload speed in KiB/s
TARGET_BANDWIDTH_USAGE = 0.95 # Target bandwidth usage (85% of max)
BANDWIDTH_SAMPLE_PERIOD = 60 # How many seconds of bandwidth history to keep
BANDWIDTH_CHECK_INTERVAL = 10 # How often to check bandwidth usage in seconds
BANDWIDTH_ADJUST_THRESHOLD = 0.1 # Adjust speeds if usage differs from target by this fraction
CHECK_INTERVAL_SECONDS = 300 # Check torrent ratios every 300 seconds (5 minutes)
# ANSI escape codes for text formatting
RED = '\033[91m'
GREEN = '\033[92m'
BLUE = '\033[94m'
YELLOW = '\033[93m'
ENDC = '\033[0m'
# Global variables for bandwidth monitoring
bandwidth_history = deque(maxlen=int(BANDWIDTH_SAMPLE_PERIOD / BANDWIDTH_CHECK_INTERVAL))
last_bandwidth_check = 0
last_network_io = None
def get_current_bandwidth_usage():
"""Get current system-wide upload bandwidth usage in KiB/s"""
global last_network_io, last_bandwidth_check
current_time = time.time()
if last_network_io is None:
last_network_io = psutil.net_io_counters()
last_bandwidth_check = current_time
return 0
current_io = psutil.net_io_counters()
time_delta = current_time - last_bandwidth_check
# Calculate bandwidth in KiB/s
upload_speed = (current_io.bytes_sent - last_network_io.bytes_sent) / (1024 * time_delta)
last_network_io = current_io
last_bandwidth_check = current_time
return upload_speed
def update_bandwidth_history():
"""Update bandwidth history with current usage"""
current_usage = get_current_bandwidth_usage()
bandwidth_history.append(current_usage)
return current_usage
def get_average_bandwidth_usage():
"""Get average bandwidth usage over the sample period"""
if not bandwidth_history:
return 0
return sum(bandwidth_history) / len(bandwidth_history)
def calculate_speed_adjustment(current_usage):
"""Calculate speed adjustment factor based on current bandwidth usage"""
usage_ratio = current_usage / TOTAL_MAX_UPLOAD
if abs(usage_ratio - TARGET_BANDWIDTH_USAGE) < BANDWIDTH_ADJUST_THRESHOLD:
return 1.0 # No adjustment needed
if usage_ratio > TARGET_BANDWIDTH_USAGE:
# Reduce speeds
return TARGET_BANDWIDTH_USAGE / usage_ratio
else:
# Increase speeds
return min(1.5, TARGET_BANDWIDTH_USAGE / max(0.1, usage_ratio))
def colorize_output(message, color):
return f"{color}{message}{ENDC}"
def calculate_size_priority(torrent_size):
"""Calculate priority factor based on torrent size"""
# Convert torrent size to GiB for easier calculation
size_gb = torrent_size / (1024 * 1024 * 1024)
if size_gb >= 10: # For torrents >= 10GB
return MAX_SIZE_PRIORITY_BONUS
elif size_gb <= 0.3: # For torrents <= 300MB
return MIN_SIZE_PRIORITY_FACTOR
else:
# Linear interpolation between MIN_FACTOR and MAX_BONUS
# based on size between 300MB and 10GB
size_factor = (size_gb - 0.3) / (10 - 0.3)
return MIN_SIZE_PRIORITY_FACTOR + (MAX_SIZE_PRIORITY_BONUS - MIN_SIZE_PRIORITY_FACTOR) * size_factor
def calculate_ratio_priority(ratio):
"""Calculate priority factor based on torrent ratio"""
if ratio >= RATIO_PRIORITY_THRESHOLD:
return MIN_RATIO_PRIORITY_FACTOR
else:
# Give higher priority to torrents with lower ratios
# Linear interpolation between MAX_BONUS and MIN_FACTOR
ratio_factor = ratio / RATIO_PRIORITY_THRESHOLD
return MAX_RATIO_PRIORITY_BONUS - (MAX_RATIO_PRIORITY_BONUS - MIN_RATIO_PRIORITY_FACTOR) * ratio_factor
def calculate_combined_priority(size_priority, ratio_priority):
"""Combine size and ratio priorities with weighted average"""
# Give slightly more weight to ratio priority (60%) than size priority (40%)
return (size_priority * 0.4) + (ratio_priority * 0.6)
def adjust_upload_limit(qb_client, torrent, current_ratio, bandwidth_factor=1.0):
torrent_hash = torrent['hash']
size_priority = calculate_size_priority(torrent['size'])
ratio_priority = calculate_ratio_priority(current_ratio)
combined_priority = calculate_combined_priority(size_priority, ratio_priority)
final_factor = bandwidth_factor * combined_priority
if current_ratio < UNLIMITED_RATIO:
qb_client.torrents_set_upload_limit(torrent_hashes=[torrent_hash], limit=UNLIMITED_SPEED)
print(colorize_output(
f"Torrent {torrent['name']} ({torrent['size'] / (1024*1024*1024):.1f} GB) - "
f"Ratio: {current_ratio:.2f} - Upload Limit: Unlimited "
f"(Size Priority: {size_priority:.2f}, Ratio Priority: {ratio_priority:.2f})",
GREEN
))
elif MIN_RATIO <= current_ratio <= MAX_RATIO:
base_speed = max(MAX_SPEED - int((current_ratio - MIN_RATIO) * 20), MIN_SPEED)
adjusted_speed = max(MIN_SPEED, int(base_speed * final_factor))
qb_client.torrents_set_upload_limit(torrent_hashes=[torrent_hash], limit=adjusted_speed * 1024)
print(colorize_output(
f"Torrent {torrent['name']} ({torrent['size'] / (1024*1024*1024):.1f} GB) - "
f"Ratio: {current_ratio:.2f} - Upload Limit: {adjusted_speed} KiB/s "
f"(BW Factor: {bandwidth_factor:.2f}, Size Priority: {size_priority:.2f}, "
f"Ratio Priority: {ratio_priority:.2f}, Combined: {combined_priority:.2f})",
BLUE
))
else:
adjusted_speed = max(MIN_SPEED, int(MIN_SPEED * final_factor))
qb_client.torrents_set_upload_limit(torrent_hashes=[torrent_hash], limit=adjusted_speed * 1024)
print(colorize_output(
f"Torrent {torrent['name']} ({torrent['size'] / (1024*1024*1024):.1f} GB) - "
f"Ratio: {current_ratio:.2f} - Upload Limit: {adjusted_speed} KiB/s "
f"(BW Factor: {bandwidth_factor:.2f}, Size Priority: {size_priority:.2f}, "
f"Ratio Priority: {ratio_priority:.2f}, Combined: {combined_priority:.2f})",
RED
))
def remove_all_limits(qb_client):
print(colorize_output("\nRemoving all upload limits...", BLUE))
torrents = qb_client.torrents.info.completed()
for torrent in torrents:
if 'public' in torrent['tags'] and torrent['size'] > 300 * 1024 * 1024:
qb_client.torrents_set_upload_limit(torrent_hashes=[torrent['hash']], limit=UNLIMITED_SPEED)
print(colorize_output(f"Removed limit for: {torrent['name']}", GREEN))
print(colorize_output("All limits removed. Exiting...", GREEN))
def signal_handler(signum, frame):
if hasattr(signal_handler, 'qbt_client'):
remove_all_limits(signal_handler.qbt_client)
sys.exit(0)
def main():
qbt_client = qbittorrentapi.Client(host='localhost:8080', username='admin', password='adminadmin')
# Store qbt_client for the signal handler
signal_handler.qbt_client = qbt_client
# Register signal handlers
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
print(colorize_output("Started torrent upload speed limiter (Press Ctrl+C to exit and remove all limits)", BLUE))
print(colorize_output(f"Target bandwidth usage: {TARGET_BANDWIDTH_USAGE * 100}% of {TOTAL_MAX_UPLOAD} KiB/s", YELLOW))
last_torrent_check = 0
while True:
current_time = time.time()
# Update bandwidth history
current_usage = update_bandwidth_history()
avg_usage = get_average_bandwidth_usage()
bandwidth_factor = calculate_speed_adjustment(avg_usage)
# Print bandwidth status every BANDWIDTH_CHECK_INTERVAL
print(colorize_output(
f"Current Upload: {current_usage:.1f} KiB/s, Avg: {avg_usage:.1f} KiB/s, Adjustment Factor: {bandwidth_factor:.2f}",
YELLOW
))
# Check and adjust torrent speeds at CHECK_INTERVAL_SECONDS
if current_time - last_torrent_check >= CHECK_INTERVAL_SECONDS:
torrents = qbt_client.torrents.info.completed()
for torrent in torrents:
if 'public' in torrent['tags'] and torrent['size'] > 300 * 1024 * 1024:
current_ratio = torrent['ratio']
adjust_upload_limit(qbt_client, torrent, current_ratio, bandwidth_factor)
last_torrent_check = current_time
time.sleep(BANDWIDTH_CHECK_INTERVAL)
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment