Last active
April 18, 2025 13:14
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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