-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 15d2fdb
Showing
13 changed files
with
865 additions
and
0 deletions.
There are no files selected for viewing
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
__pycache__ | ||
*.log |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
FROM python:3.10 | ||
|
||
ADD . /home | ||
|
||
WORKDIR /home | ||
|
||
RUN pip install -r requirements.txt | ||
|
||
CMD ["python", "./main.py"] |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
import qbittorrentapi | ||
from typing import Union | ||
|
||
from helpers.config import SpeedrrConfig, ClientConfig | ||
from helpers.log_loader import logger | ||
from helpers.bit_convert import bit_conv | ||
|
||
|
||
|
||
class qBittorrentClient: | ||
def __init__(self, config: SpeedrrConfig, config_client: ClientConfig) -> None: | ||
self._client = qbittorrentapi.Client( | ||
host = config_client.url, | ||
username = config_client.username, | ||
password = config_client.password, | ||
FORCE_SCHEME_FROM_HOST = True, | ||
VERIFY_WEBUI_CERTIFICATE = config_client.https_verify | ||
) | ||
self._client_config = config_client | ||
self._config = config | ||
|
||
logger.debug(f"<qbit|{self._client_config.url}> Connecting to qBittorrent at {config_client.url}") | ||
|
||
try: | ||
self._client.auth_log_in() | ||
|
||
except qbittorrentapi.LoginFailed: | ||
raise Exception(f"<qbit|{self._client_config.url}> Failed to login to qBittorrent, check your credentials") | ||
|
||
except qbittorrentapi.Forbidden403Error: | ||
raise Exception(f"<qbit|{self._client_config.url}> Failed to login to qBittorrent, temporarily banned, try again later") | ||
|
||
logger.debug(f"<qbit|{self._client_config.url}> Connected to qBittorrent") | ||
|
||
|
||
def get_active_torrent_count(self) -> int: | ||
"Get the number of torrents that are currently downloading or uploading." | ||
|
||
logger.debug(f"<qbit|{self._client_config.url}> Getting active torrent count") | ||
|
||
return sum( | ||
1 for torrent in self._client.torrents_info() | ||
if torrent.state_enum.is_downloading or torrent.state_enum.is_uploading | ||
) | ||
|
||
|
||
def set_upload_speed(self, speed: Union[int, float]) -> None: | ||
"Set the upload speed limit for the client, in client units." | ||
|
||
logger.debug(f"<qbit|{self._client_config.url}> Setting upload speed to {speed}{self._config.units}") | ||
self._client.transfer_set_upload_limit( | ||
max(1, int(bit_conv(speed, self._config.units, 'b'))) | ||
) | ||
|
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
# Speedrr Configuration File | ||
# https://github.com/itschasa/speedrr | ||
|
||
|
||
# Units to be used for all speed values | ||
# Options (smallest to largest): | ||
# - bit = bit/s, Bits per second | ||
# - b = B/s, Bytes per second | ||
# - kbit = Kbit/s, Kilobits per second | ||
# - kb = KB/s, Kilobytes per second | ||
# - mbit = Mbit/s, Megabits per second (default) | ||
# - mb = MB/s, Megabytes per second | ||
units: mbit | ||
|
||
# The minimum upload speed allowed on your torrent client. | ||
# Note: Most torrent clients won't allow you to set the upload speed to 0, | ||
# so the actual minimum upload speed will be 1 Byte/s. | ||
min_upload: 8 | ||
|
||
# The maximum upload speed allowed on your torrent client. | ||
# This should be around 70-80% of your total upload speed. | ||
max_upload: 75 | ||
|
||
|
||
# The torrent clients to be used by Speedrr | ||
# Note: If you have multiple clients, Speedrr will split the upload speed between them, based on the number of seeding+downloading torrents. | ||
clients: | ||
|
||
# The type of torrent client | ||
# Options: qbittorrent | ||
- type: qbittorrent | ||
|
||
# The URL to your torrent client | ||
url: <webui_url> | ||
|
||
# The username and password to access your torrent client | ||
username: <username> | ||
password: <password> | ||
|
||
# Whether to verify the SSL certificate of the torrent client | ||
# If you are unsure what this means, leave it as is. | ||
https_verify: true | ||
|
||
|
||
# These are the modules that Speedrr will use to determine what upload speed to set. | ||
modules: | ||
|
||
# For monitoring Plex/Jellyfin streams, via Plex, Jellyfin, or Tautulli | ||
# Uses the bandwidth of the streams to determine how much upload speed to deduct. | ||
media_servers: | ||
# Supports multiple servers | ||
# Note: You should only use either plex or tautulli for every Plex Media Server you have. | ||
|
||
# The type of server to get data from | ||
# Options: plex, tautulli, jellyfin | ||
- type: <server_type> | ||
|
||
# The URL to your Plex/Tautulli/Jellyfin server | ||
url: <server_url> | ||
|
||
# PLEX ONLY, the token to access your Plex server | ||
# Help: https://support.plex.tv/articles/204059436-finding-an-authentication-token-x-plex-token/ | ||
token: <plex_x_token> | ||
|
||
# TAUTULLI AND JELLYFIN ONLY, the API key to access your Tautulli/Jellyfin server | ||
# Tautulli: Settings > Web Interface > API > API key | ||
# Jellyfin: Dashboard > Advanced > API Keys > + | ||
api_key: <api_key> | ||
|
||
# Whether to verify the SSL certificate of the Plex/Tautulli server | ||
# If you are unsure what this means, leave it as is. | ||
https_verify: true | ||
|
||
# Bandwidth reported by Plex is multiplied by this value | ||
# Plex will reserve a higher bandwidth than the actual stream requires all the time, | ||
# so this value is used to reduce the reported bandwidth, if you want to. | ||
bandwidth_multiplier: 1.0 | ||
|
||
# The interval in seconds to update the Plex stream data | ||
update_interval: 5 | ||
|
||
# Checks if a stream is local, and if it is, it will ignore it from calculations | ||
ignore_local_streams: true | ||
|
||
# After a stream has been paused for this amount of seconds, it will be ignored from calculations | ||
# Note: To disable this feature, set to -1. | ||
ignore_paused_after: 300 | ||
|
||
|
||
# Changes the upload speed based on the time of day, and what day of the week | ||
# Note: Recommended to use to set your upload speed to be lower during the day, | ||
# when lots of users are using your internet. | ||
# Note: Supports multiple schedules. | ||
schedule: | ||
# The start and end time of the schedule, in 24-hour format | ||
# Note: Uses your machine's local timezone. | ||
- start: "05:00" | ||
end: "23:30" | ||
|
||
# The days of the week to apply the schedule to | ||
# Options: all, mon, tue, wed, thu, fri, sat, sun | ||
# Note: If your end time goes past midnight, you should add the next day as well. | ||
days: [all] | ||
|
||
# The upload speed deducted in this time period. | ||
# Note: This can be a percentage of the maximum or a fixed value (uses units specified at the top of config). | ||
# Example: 50%, 10, 5, 80%, 20% | ||
upload: 60% | ||
|
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import argparse | ||
import os | ||
import logging | ||
|
||
|
||
|
||
def is_valid_file(parser: argparse.ArgumentParser, arg) -> str: | ||
if not os.path.exists(arg): | ||
parser.error(f"invalid path {arg}") | ||
else: | ||
return str(arg) | ||
|
||
|
||
def load_args() -> argparse.Namespace: | ||
argparser = argparse.ArgumentParser() | ||
argparser.add_argument( | ||
'--config_path', | ||
dest='config', | ||
help='Path to the config file', | ||
type=lambda x: is_valid_file(argparser, x), | ||
default=os.environ.get('SPEEDRR_CONFIG') | ||
) | ||
argparser.add_argument( | ||
'--log_level', | ||
dest='log_level', | ||
help='Python logging level to stdout, use 10, 20, 30, 40, 50. Default is 20 (INFO)', | ||
type=int, | ||
default=os.environ.get('SPEEDRR_LOG_LEVEL', logging.INFO) | ||
) | ||
argparser.add_argument( | ||
'--log_file_level', | ||
dest='log_file_level', | ||
help='Python logging level to file, use 10, 20, 30, 40, 50. Default is 30 (WARNING)', | ||
type=int, | ||
default=os.environ.get('SPEEDRR_LOG_FILE_LEVEL', logging.WARNING) | ||
) | ||
return argparser.parse_args() |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
from typing import Union | ||
|
||
|
||
|
||
bit_convertion_dict = { | ||
"bit": 1, | ||
"b": 8, | ||
"kbit": 1000, | ||
"kb": 8000, | ||
"mbit": 1000000, | ||
"mb": 8000000, | ||
} | ||
|
||
|
||
def bit_conv(inp: Union[int, float], inp_type: str, out_type: str) -> float: | ||
"Convert an input in one type to an output in another type, to 3dp." | ||
return round(inp * bit_convertion_dict[inp_type] / bit_convertion_dict[out_type], 3) |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
from dataclasses import dataclass | ||
from typing import List, Optional, Union, Literal | ||
from dataclass_wizard import YAMLWizard # type: ignore | ||
|
||
|
||
|
||
@dataclass(frozen=True) | ||
class ClientConfig(YAMLWizard): | ||
type: Literal['qbittorrent', 'deluge', 'transmission'] | ||
url: str | ||
username: str | ||
password: str | ||
https_verify: bool | ||
|
||
@dataclass(frozen=True) | ||
class MediaServerConfig(YAMLWizard): | ||
type: Literal['plex', 'tautulli', 'jellyfin'] | ||
url: str | ||
https_verify: bool | ||
bandwidth_multiplier: float | ||
update_interval: int | ||
ignore_local_streams: bool | ||
ignore_paused_after: int | ||
token: Optional[str] = None | ||
api_key: Optional[str] = None | ||
|
||
def __hash__(self) -> int: | ||
return super().__hash__() | ||
|
||
@dataclass(frozen=True) | ||
class ScheduleConfig(YAMLWizard): | ||
start: str | ||
end: str | ||
days: tuple[Literal['all', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun']] | ||
upload: Union[int, str] | ||
|
||
@dataclass(frozen=True) | ||
class ModulesConfig(YAMLWizard): | ||
media_servers: Optional[List[MediaServerConfig]] | ||
schedule: Optional[List[ScheduleConfig]] | ||
|
||
@dataclass(frozen=True) | ||
class SpeedrrConfig(YAMLWizard): | ||
units: Literal['bit', 'b', 'kbit', 'kb', 'mbit', 'mb'] | ||
min_upload: int | ||
max_upload: int | ||
clients: List[ClientConfig] | ||
modules: ModulesConfig | ||
|
||
|
||
def load_config(config_file: str) -> SpeedrrConfig: | ||
return SpeedrrConfig.from_yaml_file(config_file) |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import logging | ||
import datetime | ||
from colorama import Fore | ||
import sys | ||
import traceback | ||
|
||
|
||
|
||
logger_name = "speedrr" | ||
default_stdout_log_level = logging.INFO | ||
default_file_log_level = logging.WARNING | ||
file_log_name = 'logs/{:%Y-%m-%d %H.%M.%S}.log'.format(datetime.datetime.now()) | ||
log_format = '[%(asctime)s] [%(levelname)s] %(message)s (%(filename)s:%(lineno)d)' | ||
|
||
|
||
class ColourFormatter(logging.Formatter): | ||
FORMATS = { | ||
logging.DEBUG: Fore.LIGHTBLACK_EX + log_format + Fore.RESET, | ||
logging.INFO: log_format + Fore.RESET, | ||
logging.WARNING: Fore.YELLOW + log_format + Fore.RESET, | ||
logging.ERROR: Fore.LIGHTRED_EX + log_format + Fore.RESET, | ||
logging.CRITICAL: Fore.RED + log_format + Fore.RESET | ||
} | ||
|
||
def format(self, record): | ||
log_fmt = self.FORMATS.get(record.levelno) | ||
formatter = logging.Formatter(log_fmt) | ||
return formatter.format(record) | ||
|
||
|
||
logger = logging.getLogger(logger_name) | ||
logger.setLevel(logging.DEBUG) | ||
|
||
stdout_handler = logging.StreamHandler() | ||
stdout_handler.setLevel(default_stdout_log_level) | ||
stdout_handler.setFormatter(ColourFormatter()) | ||
logger.addHandler(stdout_handler) | ||
|
||
file_handler = logging.FileHandler(file_log_name, encoding="utf-8") | ||
file_handler.setLevel(default_file_log_level) | ||
file_handler.setFormatter(logging.Formatter(log_format)) | ||
logger.addHandler(file_handler) | ||
|
||
def handle_exception(exc_type, exc_value, exc_traceback): | ||
if issubclass(exc_type, KeyboardInterrupt): | ||
sys.__excepthook__(exc_type, exc_value, exc_traceback) | ||
return | ||
|
||
logger.error("Uncaught exception: " + ' '.join(traceback.format_exception(exc_type, exc_value, exc_traceback))) | ||
|
||
sys.excepthook = handle_exception |
Empty file.
Oops, something went wrong.