Metadata-Version: 2.4
Name: torrdown
Version: 0.2.0
Summary: A single-session libtorrent download CLI with resume, dedup, and batch support.
Author: aryannxroot
License: MIT
License-File: LICENSE
Keywords: bittorrent,cli,downloader,libtorrent,torrent
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: End Users/Desktop
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: MacOS
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Communications :: File Sharing
Classifier: Topic :: Internet
Requires-Python: <3.14,>=3.9
Requires-Dist: libtorrent<2.1,>=2.0
Requires-Dist: rich>=13
Provides-Extra: dev
Requires-Dist: pytest>=7; extra == 'dev'
Description-Content-Type: text/markdown

# TorrDown

[![PyPI](https://img.shields.io/pypi/v/torrdown)](https://pypi.org/project/torrdown/)

A command-line torrent downloader built on a single shared `libtorrent` session.
It downloads several torrents at once, survives restarts, and installs as a real
`torrdown` command you can run from anywhere.

TorrDown started as a Google Colab notebook (still in the repo as
`legacy_download_gcollab.ipynb`) and grew into a proper, installable CLI.

## Features

- **Single shared libtorrent session** — all torrents run in one session (the way
  libtorrent is meant to be used), not one session per torrent.
- **Concurrent downloads** with a fairness gate — only a bounded number compete
  for metadata/peers at once; the rest queue, so a stuck magnet can't starve the
  others or unfairly burn its own timeout.
- **Magnet links and `.torrent` files**, individually or in batches.
- **Resume, on request** — Ctrl+C, a crash, or a reboot doesn't lose progress;
  libtorrent resume data is checkpointed to disk and `torrdown resume` picks the
  downloads you choose back up. Other commands deliberately don't auto-resume, so
  asking for one film downloads one film.
- **Info-hash dedup** — the same torrent won't be added twice.
- **Metadata-fetch timeout** — a magnet with no metadata-holding peers is skipped
  after a while instead of hanging forever.
- **Batch mode** — download every magnet in a file; completed/failed entries are
  commented out in place so re-runs don't repeat them.
- **Graceful shutdown** — Ctrl+C saves state cleanly before exiting.
- **Live progress bars** (via `rich`) for every active download.
- **Per-user config** with sensible defaults and clear override precedence.

## Requirements

- **macOS or Linux.** (Windows isn't supported yet.)
- **Python 3.9-3.13** somewhere on the machine. `libtorrent` publishes binary
  wheels only for these versions, so TorrDown caps at `<3.14`. The installer
  finds a compatible interpreter automatically; if you only have 3.14+, it tells
  you how to get a supported one.

## Install

The easiest way, on a machine with Python 3.9-3.13:

```bash
pipx install torrdown        # isolated, puts `torrdown` on your PATH
# or
pip install torrdown
```

That's it — `torrdown` is now a command you can run from anywhere.

### From source

The source repo is currently private. If you have access, you can clone it and
use the bundled installer (creates an isolated venv and symlinks the command):

```bash
git clone https://github.com/aryannxroot/TorrDown.git
cd TorrDown
./install.sh                 # then ./update.sh / ./uninstall.sh to manage it
```

For development:

```bash
python3.13 -m venv torr
source torr/bin/activate
pip install -e ".[dev]"      # editable install + test deps
pytest                       # run the test suite
```

You can also run it without installing via `python -m torrdown`.

### Cutting a release (maintainers)

Bump `__version__` in `torrdown/__init__.py` (it's the single source of truth), then:

```bash
./torr/bin/pip install build twine   # once
rm -rf dist && python -m build       # build sdist + wheel
twine check dist/*                   # validate metadata
twine upload dist/*                  # username __token__, PyPI token as password
```

A published version number can't be reused, so bump before every upload.

## Usage

```bash
torrdown                       # interactive menu (list files / paste magnet / batch)
torrdown add "magnet:?xt=..."  # add a magnet (quote it - the & chars matter)
torrdown add movie.torrent     # add a local .torrent file
torrdown add 1,3               # add local .torrent files by menu index
torrdown search "big buck bunny"  # search a source, pick an item + files
torrdown sources               # show configured search sources (--check probes them)
torrdown batch                 # download every magnet in the configured magnets.txt
torrdown batch links.txt       # ...or from a specific file
torrdown resume                # pick which interrupted downloads to continue
torrdown resume 1,3            # ...or say which up front ('2-5' and 'all' work too)
torrdown resume --all          # continue everything, no prompt
torrdown status                # show tracked torrents with progress (ID, %, size)
torrdown remove <id>           # drop a tracked entry by ID (from status) or label
torrdown clear                 # clear completed/failed entries (--all clears everything)
torrdown list                  # list local .torrent files
torrdown init                  # create the config/download/torrents directories
torrdown config                # show resolved config and file locations
```

`status` shows how far each download got; `remove`/`clear` only edit the tracking
records — your downloaded files are never touched. For live speed on an active
download, use `torrdown resume` (it shows the running progress bars).

### Search

`torrdown search "<query>"` lists results in a table, then lets you pick an item and
which files to download. The default source is the
[Internet Archive](https://archive.org) (millions of public-domain and
Creative-Commons items).

```bash
torrdown search "sintel"                 # interactive: pick a result, then pick files
torrdown search "ubuntu" --type software # filter by mediatype (movies/audio/texts/software/...)
torrdown search "sintel" --all-types     # don't filter by mediatype at all
torrdown search "sintel" --download 1 --files 1,3   # non-interactive
```

Results are narrowed two ways to keep them scannable, both measured against the live
API: terms are matched against the **title** field rather than all metadata, and
results are sorted by **download count** rather than IA's relevance order. Unscoped
and relevance-sorted, a search for "pulp fiction" returned audio clips with 4
downloads, and "sintel" ranked *Elephants Dream* first. Only `movies` is searched by
default; `--all-types` or `--type <x>` widens it. If a title-scoped search finds
nothing, it automatically retries across all metadata rather than dead-ending.

File selection uses libtorrent priorities, so only the files you choose are downloaded.
### Resuming interrupted downloads

Progress is checkpointed continuously, but picking it back up is deliberate rather
than automatic:

```bash
torrdown resume          # numbered list of interrupted downloads, pick 1,3 / 2-5 / all
torrdown resume 1,3      # same, non-interactive
torrdown resume --all    # everything
```

`add`, `batch` and `search` **do not** resume other interrupted work — they download
what you asked for and print a one-line note about anything left waiting:

```
Note: 1 interrupted download not resumed
      Air (2023) [1080p]  (4%  92.1 MB/2.1 GB)
      run `torrdown resume` to continue it
```

Pass `--resume` to any of them to pick everything up in the same run. Nothing is
ever lost by not resuming — the resume data stays on disk until you `remove` the
entry.

### Search sources

`--source` picks the backend, defaulting to `search_source` from your config:

| Source | What it is | State |
|---|---|---|
| `ytslu` | yts.lu-style aggregator. Two-stage: TMDB title search, then torrents per title from upstream indexers (Knaben, 1337x, …). Magnets arrive pre-built. | working, **the default** |
| `ia` | Internet Archive — public-domain / CC items. Fixed endpoint. | working |
| `yts` | YTS's own documented `/api/v2/list_movies.json`. | **dormant** — see below |

```bash
torrdown search "pulp fiction"                    # aggregator (the default)
torrdown search "pulp fiction" --quality 1080p    # only 1080p releases
torrdown search "pulp fiction" --all              # include untagged releases
torrdown search "sintel" --source ia              # Internet Archive instead
torrdown sources                                  # list endpoints
torrdown sources --check                          # which ones answer?
```

Endpoints for both `yts` and `ytslu` are **config lists** (`yts_base_urls`,
`ytslu_base_urls`), tried in order, never hardcoded — these domains rotate and
disappear constantly, so a dead one is fixed by editing `config.json` (or setting
`TORRDOWN_YTSLU_BASE_URLS`) rather than by shipping a release. `sources --check`
probes each and reports precisely why it failed: DNS, timeout, HTTP status, or —
the one that matters — an HTML block/browser-check page served with a **200**, which
naive code reads as success.

To use IA by default instead, set `"search_source": "ia"` in `config.json`.

### Quality on `ytslu`

Results are aggregated from several indexers, so a popular title returns 100–180
releases — from a 700 MB encode to a 115 GB remux — and the API carries **no quality
field**. Resolution is therefore parsed out of the release name: explicit tiers
(`1080p`, `2160P`, `m1080p`), dimensions (`1920x1080`, and `1920x800`, whose width
makes it a 1080p scope crop), and named forms (`4K`, `UHD`, `FullHD`). Codec tags are
rejected, so `2160 x265` reads as 2160p rather than a 2160×265 resolution.

That covers about 69% of hits. The rest are tagged only by **source type** —
`BDRip`, `HDRip-AVC`, `DVDRip`, `HDCAM` — which state no resolution at all; a BDRip
may be anything from 480p to 1080p, so guessing would be inventing data. Those are
**hidden by default** and the count is reported:

```
The Dark Knight - showing 5 of 93, 68 hidden with no resolution in the title
                  (--all to show), best-seeded first
```

`--all` includes them, shown as `?`. Worth doing occasionally: some of the
best-seeded releases are untagged RuTracker remuxes. If *no* release has a
detectable resolution, all are shown rather than presenting an empty list.

Everything is ranked by seed count and capped to `--rows`, always with a count of
what was held back. `--files` doesn't apply to either magnet-based source: file
priorities need torrent metadata, and a bare magnet carries none at add time.

> **`yts` is dormant and unverified.** When it was written no reachable endpoint
> existed — of 17 hosts checked, the canonical domain had no global DNS record,
> several resolved only to an ISP sinkhole, two served a broken clone returning
> HTTP 500, and the rest were unreachable. Its parser follows YTS's documented API
> v2 contract and is defensively written and unit-tested, but it has never seen a
> live payload. `sources --check` will report it as failing; that's expected. Use
> `ytslu` instead.

## Configuration

Defaults (all overridable):

| Setting | Default |
|---|---|
| Download directory | `~/Downloads/torrdown` |
| Torrents directory (`.torrent` files for the menu / `list`) | `~/Downloads/torrdown/torrents` |
| Config / state directory | `~/.config/torrdown` (`%APPDATA%\torrdown` on Windows) |
| Listen port | 6881 |
| Max concurrent (metadata slots) | 3 |
| Metadata fetch timeout | 180s |
| Default search source (`search_source`) | `ytslu` |
| YTS endpoints (`yts_base_urls`) | tried in order; see `torrdown sources` |
| Aggregator endpoints (`ytslu_base_urls`) | tried in order; see `torrdown sources` |

Drop `.torrent` files into the torrents directory to have them show up in the
interactive menu and `torrdown list`. Magnet links don't need any folder — just
`torrdown add "magnet:?..."`.

Precedence, highest first: **CLI flags** (`--download-dir`, `--config-dir`) >
**environment** (`TORRDOWN_DOWNLOAD_DIR`, `TORRDOWN_PORT`, ...) >
**`config.json`** (in the config dir) > **built-in defaults**.

Run `torrdown config` to see the resolved values and where the files live.

## How it works

- **`engine.py`** owns one `libtorrent` session and runs a single alert-driven
  loop on the main thread: it adds torrents, watches libtorrent alerts
  (metadata received, finished, error, resume-data saved), and polls each
  torrent's status for the progress bars. Per-torrent status is read one handle
  at a time on purpose — libtorrent's batched `get_torrent_status()` invokes a
  Python callback from an internal worker thread and segfaults in this build.
- **`state.py`** persists a small `state.json` keyed by info-hash, including
  base64-encoded libtorrent resume data, so interrupted torrents resume without
  re-fetching metadata or re-hashing completed pieces. Writes are atomic and a
  corrupt file is set aside rather than silently wiped.
- **`sources.py`** turns a user token (a magnet, a `.torrent` path, or a menu
  index) into a concrete download source. This is the single seam where future
  fetch adapters (e.g. searching a legitimate index) will plug in.
- **`config.py`** resolves paths and tunables across the precedence layers above.
- **`cli.py`** is the argparse front end (the subcommands above) plus the
  interactive menu when run bare.

## Project layout

```
torrdown/            the package
  cli.py             argparse subcommands + interactive menu
  config.py          path + settings resolution
  sources.py         token -> download source (future fetch seam)
  ia.py              Internet Archive search adapter
  yts.py             YTS API v2 adapter (config-driven endpoints; dormant)
  ytslu.py           yts.lu-style aggregator adapter (config-driven endpoints)
  _http.py           shared fetch + per-endpoint failure classification
  engine.py          the shared-session libtorrent engine
  state.py           resume-state persistence
  magnets.py         batch magnets file read + archive
tests/               pure-logic pytest suite
install.sh           installer (macOS + Linux)
uninstall.sh         uninstaller
update.sh            updater
pyproject.toml       packaging + dependencies
PLAN.md              what we're building
PROGRESS.md          living status tracker
ROADMAP.md           deferred work (auto-fetch, PyPI, perf notes)
```

## Notes & limitations

- **Speed** is bounded by your internet connection and the swarm, not by
  TorrDown — see [ROADMAP.md](ROADMAP.md) for the full performance discussion.
- **Automated fetching** of torrents from external sources isn't built yet;
  when it is, it will target only legitimate sources (Internet Archive, Linux
  distros, Creative Commons). See [ROADMAP.md](ROADMAP.md).
- **Windows** is not supported yet.
