replace libtorrent with torf (#506)

torf is a python library which deals with torrents, magnets and more. It
is a python only library and has no C backend.

The main reason for replacing it is that libtorrent doesn't have
bindings for pyhton 3.14, which is now the default on arch (I use arch
btw.)

---

Since I don't have a torrent downloader, someone should verify that this
still works (:

Maybe one should add tests for it as well :D

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Chores**
* Replaced the torrent parsing library and updated project dependency
accordingly.
  * Updated container build to remove the now-unused OS package.

* **Bug Fixes**
* Improved reliability of torrent info extraction during magnet and
fallback handling.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Marcel Hellwig
2026-05-01 16:24:34 +02:00
committed by GitHub
parent f5253990e0
commit c46239db86
4 changed files with 603 additions and 561 deletions

View File

@@ -13,7 +13,7 @@ RUN env PUBLIC_VERSION=${VERSION} PUBLIC_API_URL=${BASE_PATH} BASE_PATH=${BASE_P
FROM ghcr.io/astral-sh/uv:python3.13-trixie-slim AS base FROM ghcr.io/astral-sh/uv:python3.13-trixie-slim AS base
RUN apt-get update && \ RUN apt-get update && \
apt-get install -y ca-certificates bash libtorrent21 gcc bc locales postgresql media-types mailcap curl gzip unzip tar 7zip bzip2 unar gosu && \ apt-get install -y ca-certificates bash gcc bc locales postgresql media-types mailcap curl gzip unzip tar 7zip bzip2 unar gosu && \
apt-get clean && \ apt-get clean && \
rm -rf /var/lib/apt/lists/* rm -rf /var/lib/apt/lists/*

View File

@@ -6,9 +6,9 @@ import shutil
from pathlib import Path, UnsupportedOperation from pathlib import Path, UnsupportedOperation
import bencoder import bencoder
import libtorrent
import patoolib import patoolib
import requests import requests
import torf
from pathvalidate import sanitize_filename from pathvalidate import sanitize_filename
from requests.exceptions import InvalidSchema from requests.exceptions import InvalidSchema
@@ -142,7 +142,7 @@ def get_torrent_hash(torrent: IndexerQueryResult) -> str:
if torrent.download_url.startswith("magnet:"): if torrent.download_url.startswith("magnet:"):
log.info(f"Parsing torrent with magnet URL: {torrent.title}") log.info(f"Parsing torrent with magnet URL: {torrent.title}")
log.debug(f"Magnet URL: {torrent.download_url}") log.debug(f"Magnet URL: {torrent.download_url}")
torrent_hash = str(libtorrent.parse_magnet_uri(torrent.download_url).info_hash) torrent_hash = torf.Magnet.from_string(torrent.download_url).infohash
else: else:
# downloading the torrent file # downloading the torrent file
log.info(f"Downloading .torrent file of torrent: {torrent.title}") log.info(f"Downloading .torrent file of torrent: {torrent.title}")
@@ -157,7 +157,7 @@ def get_torrent_hash(torrent: IndexerQueryResult) -> str:
session=requests.Session(), session=requests.Session(),
timeout=MediaManagerConfig().indexers.prowlarr.timeout_seconds, timeout=MediaManagerConfig().indexers.prowlarr.timeout_seconds,
) )
return str(libtorrent.parse_magnet_uri(final_url).info_hash) return torf.Magnet.from_string(final_url).infohash
except Exception: except Exception:
log.exception("Failed to download torrent file") log.exception("Failed to download torrent file")
raise raise

View File

@@ -34,9 +34,9 @@ dependencies = [
"pillow>=11.3.0", "pillow>=11.3.0",
"sabnzbd-api>=0.1.2", "sabnzbd-api>=0.1.2",
"transmission-rpc>=7.0.11", "transmission-rpc>=7.0.11",
"libtorrent>=2.0.11",
"pathvalidate>=3.3.1", "pathvalidate>=3.3.1",
"asgi-correlation-id>=4.3.4", "asgi-correlation-id>=4.3.4",
"torf>=4.3.1",
] ]
[dependency-groups] [dependency-groups]

1154
uv.lock generated
View File

File diff suppressed because it is too large Load Diff