Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

BTPC Documentation

Development documentation: This site documents the current main branch before the first stable release.

BTPC is a high-performance BitTorrent metainfo toolkit with a Rust core, a native btpc command-line interface, and typed Python bindings. It reads, validates, creates, inspects, edits, and verifies v1, v2, and hybrid torrents while preserving raw protocol bytes where identity matters.

Start here

BTPC source is hosted on GitHub and is available under the MIT License.

Getting started

Choose the interface that matches your application. All three interfaces use the same Rust protocol core and produce compatible canonical metainfo.

Development documentation: Commands and APIs describe the current main branch and may change before 1.0.

Installation

BTPC is pre-1.0 and is not published to package registries yet. Install it from a source checkout with the locked toolchains.

CLI

cargo build --release -p btpc-cli
./target/release/btpc --version

The binary is target/release/btpc (btpc.exe on Windows).

Python

uv sync --all-groups --locked
uv run maturin develop --release
uv run python -c "import btpc; print(btpc.__version__)"

Python supports CPython 3.11 through 3.14. Wheels currently require the CPython GIL and do not support subinterpreters.

After the first PyPI release, install the library in your Python environment:

python -m pip install btpc
python -c "import btpc; print(btpc.__version__)"

Until publication, build a wheel from the checkout or download a validated release candidate artifact. Install the wheel file with python -m pip install /path/to/btpc.whl. The wheel contains the Python library and Rust extension. It does not install the native btpc CLI.

The release workflow builds wheels for each CPython minor version on these platforms:

PlatformArchitecturesRequirement
Linuxx86-64, AArch64glibc 2.28 or later
macOSIntel, Apple SiliconAn OS version compatible with the wheel’s platform tag
Windowsx86-64Standard CPython for x86-64

PyPy, musl Linux, Windows ARM64, and free-threaded wheels are outside this tested wheel matrix. Use --only-binary=:all: when you want installation to fail if a compatible wheel is absent. Without this option, pip can try to build the source distribution. A source build requires a Rust toolchain at or above MSRV 1.85 and the platform’s compiler/linker tools. For a reproducible development build, use the pinned toolchain in rust-toolchain.toml.

Long native operations release the GIL. Progress callbacks reacquire it. A free-threaded CPython runtime must enable the GIL to import this extension. Use threads in one main interpreter or separate processes; subinterpreters are not supported.

Rust

Add btpc-core as a path dependency while working from the checkout. Registry installation instructions will be added when the crate is published.

CLI quick start

Create a tiny payload and build all three torrent modes:

mkdir payload
printf 'hello torrent\n' > payload/hello.txt
btpc create payload -o payload-v1.torrent
btpc create payload --mode v2 --piece-length 16384 -o payload-v2.torrent
btpc create payload --mode hybrid --piece-length 16384 -o payload-hybrid.torrent

Inspect and verify the hybrid result:

btpc inspect payload-hybrid.torrent
btpc validate payload-hybrid.torrent
btpc magnet payload-hybrid.torrent
btpc verify payload-hybrid.torrent payload

Use --json for versioned machine output. Human diagnostics and progress are sent to stderr. Continue with the complete CLI guide.

Python quick start

from btpc import CreateOptions, TorrentMode, create

result = create(
    "payload",
    "payload-hybrid.torrent",
    options=CreateOptions(
        mode=TorrentMode.HYBRID,
        piece_length=16_384,
        creation_date=0,
        threads=1,
    ),
)
torrent = result.metainfo
assert torrent.verify("payload").is_valid
print(torrent.magnet())
torrent.edit(comment="reviewed").write("payload-reviewed.torrent")

Textual inputs use Python str; parsed protocol bytes and torrent paths remain lossless bytes. See the Python API guide for editing, callbacks, cancellation, and errors.

Rust quick start

#![allow(unused)]
fn main() {
extern crate btpc_core;
use btpc_core::create::{CreateMode, CreateOptions, Creator, NoProgress, PieceLength};

let options = CreateOptions::builder()
    .mode(CreateMode::Hybrid)
    .piece_length(PieceLength::Exact(16_384))
    .creation_date(0)
    .build()?;
let result = Creator::new("payload").options(options).create(&NoProgress)?;
println!("{} bytes", result.bytes().len());
Ok::<(), btpc_core::Error>(())
}

Protocol logic, traversal, hashing, canonical serialization, and verification live in btpc-core. Continue with the Rust API guide.

Guides

Task-oriented guides explain how to complete workflows; generated command and API references describe individual options and objects.

Creating torrents

Select v1 for broad legacy compatibility, v2 for per-file Merkle verification, or hybrid when both representations are required.

btpc create payload --mode hybrid --piece-length 16384 -o payload.torrent

Creation traverses paths deterministically and streams payload data with bounded memory. For reproducible output, set a fixed creation date, explicit thread count, and explicit piece length where the protocol permits it. Tracker tiers preserve their order; dictionary keys are always encoded in canonical raw-byte order.

Keep payload files unchanged during creation. BTPC checks file identity, size, and available timestamps before and after hashing. These checks do not provide an atomic filesystem snapshot. An in-place edit can escape detection if the filesystem reports the same metadata.

Use btpc create --json for automation or the typed Python create/ create_bytes functions when embedding BTPC.

Inspecting and validating metainfo

Inspection reports validated metadata without reading payload files:

btpc inspect payload.torrent
btpc inspect payload.torrent --field hash-v1 --format plain
btpc inspect payload.torrent --tree --pretty

Validation checks protocol structure and can require canonical source encoding:

btpc validate payload.torrent --canonical --warnings-as-errors

Human output decodes UTF-8 only when safe. JSON represents uncertain byte strings with an explicit UTF-8 or hexadecimal encoding marker.

Verifying payloads

btpc verify payload.torrent payload
btpc verify payload.torrent payload --fail-fast --extra-files --json

v1 hashes the logical concatenated file stream. v2 verifies each file through BEP 52 Merkle roots. Hybrid torrents verify both domains. Mismatches are deterministic; unsafe paths and symlinks are never followed. Verification opens the selected payload as a filesystem capability, walks descendants relative to opened directory handles, and hashes retained no-follow file handles. Concurrent path replacement is reported as unsafe or missing rather than allowing verification to leave the selected payload root.

A completed mismatch report exits with code 6. I/O, policy, and malformed metainfo failures retain their category-specific exit codes.

Editing metainfo

Create a validated copy by default:

btpc edit payload.torrent --output edited.torrent --comment "reviewed"

Use --in-place only when atomic replacement is intended. Top-level fields such as comment and creator preserve info hashes. Changes inside the info dictionary, including private state, source, and file attributes, change applicable hashes. Top-level-only edits preserve the exact original info byte slice, even when its source encoding is noncanonical. Canonical serialization remains an explicit output choice. Hybrid real-file attribute edits update both v1 and v2 file representations atomically; padding attributes remain v1-only.

Python uses one three-state parameter per optional field: UNCHANGED preserves, None removes, and a typed value replaces.

Configuration and presets

BTPC resolves creation settings in a deterministic order: built-in defaults, global configuration, selected presets, then explicit command arguments. Use --no-config for a config-free run.

btpc config path
btpc config init
btpc config preset list
btpc config explain create --preset private ./payload
btpc create ./payload --preset private -o payload.torrent

Configuration supports tracker aliases, tracker groups, and inherited ordered presets. Inspect the resolved plan with config explain before a large creation.

Concepts

Understand the protocol choices that affect compatibility and identity:

BitTorrent v1

v1 stores SHA-1 hashes for fixed-size pieces over the logical concatenation of all payload files. Multi-file boundaries do not reset piece hashing. Its info dictionary contains either a single length or an ordered files list.

The v1 info hash is SHA-1 over the exact bencoded source slice of the info dictionary. Re-serializing semantically equivalent data can therefore change identity when the source was not canonical.

BitTorrent v2

BEP 52 represents files in a nested file tree and hashes each file with a SHA-256 Merkle tree. Large files may require top-level piece layers. Paths and dictionary keys are raw byte strings, not guaranteed UTF-8 text.

The v2 exact-topic value uses the SHA-256 info hash encoded as a multihash in magnet URIs.

Hybrid torrents

A hybrid torrent carries matching v1 and v2 representations for the same payload. BTPC creates and verifies both hash domains. Alignment padding may appear in the v1 view so file boundaries satisfy the v2 layout.

Hybrid magnets include both btih and btmh exact-topic parameters in deterministic order.

Piece length

Piece length balances metadata size, hashing granularity, and client behavior. v1 accepts power-of-two piece sizes. v2 and hybrid creation also require BEP 52 rules and file-tree consistency.

Leaving piece length automatic uses BTPC’s deterministic policy for the payload size. Set an explicit value when reproducing a known torrent or comparing tools.

Reproducibility and raw bytes

Torrent identity is defined by bytes. BTPC preserves the exact source info slice for hashing, treats protocol byte strings as bytes, and decodes text only at explicit API or presentation boundaries.

Canonical output sorts dictionary keys by unsigned raw-byte order. Reproducible creation also requires stable traversal, piece length, metadata fields, creation date, and tool configuration. A fixed creation_date=0 and explicit thread count are useful for tests and release fixtures.

CLI


title: CLI guide

CLI Guide

The generated command reference documents every visible command and option directly from the Clap command model.

btpc is a thin adapter over btpc-core. Run btpc --help or use the checked-in reference. Generated completion files and the manual page are outputs of the same Clap command definition used by the executable.

Commands

CommandPurposeMachine output
create INPUTScan and hash a file/directory, then atomically write canonical metainfo--json: btpc.create.v2
inspect TORRENTDisplay validated metadata without reading payload files--json: btpc.inspect.v1
validate TORRENTValidate bencode and protocol fields only--json: btpc.validate.v1
verify TORRENT PAYLOADCheck structure and every applicable hash domain--json: btpc.verify.v2
edit TORRENTSafely edit typed metainfo fields without reading payloads--json: btpc.edit.v2
magnet TORRENTPrint a deterministic magnet URIURI only
config ...Locate, validate, explain, and safely update TOML configurationselected subcommands support JSON
completion ...Generate, install, or uninstall shell completion sourcecompletion source
manpageGenerate btpc(1) roffroff source

Every metainfo-reading command accepts --max-input-bytes and --max-owned-bytes; --max-integer-digits independently bounds decimal digit runs. Defaults are the same conservative limits as btpc-core; violations use invalid-data exit code 4.

Path-bearing v2 JSON uses btpc.filesystem-path.v2: Unix paths carry exact bytes as hexadecimal and Windows paths carry exact UTF-16 code units. display is safe for presentation and escapes control characters. Deprecated output_display and path_display aliases remain through v2 and are removed in v3; consumers should read the exact object now. Batch creation emits one v2 create object per input in input order.

Configuration

BTPC loads the explicit global --config PATH, then BTPC_CONFIG, then the platform user configuration path ($XDG_CONFIG_HOME/btpc/config.toml or the equivalent user config directory). It never searches the current directory. --no-config disables every implicit and environment-selected file. Values merge in this order: built-in defaults, global [create], each selected preset in CLI order (including extends parents), then explicit CLI flags.

version = 1

[trackers.primary]
url = "https://tracker.example/announce"

[tracker_groups.release]
trackers = ["primary"]

[presets.base]
mode = "hybrid"
piece_length = 1048576
creation_date = 0

[presets.private]
extends = ["base"]
tracker_groups = ["release"]
private = true

Use config path|init|show|check, config tracker ..., config preset ..., and config explain create ... to manage and inspect configuration. Secret-bearing URLs are redacted by default. Configuration writes are atomic and user-private.

Creation

btpc create payload --mode hybrid --piece-length 16384 \
  --tracker https://tracker.example/announce \
  --web-seed https://seed.example/payload/ \
  --creation-date 0 --threads 1 --json \
  -o payload.torrent

Important controls:

  • --mode v1|v2|hybrid selects the representation; v1 is the default.
  • --piece-length fixes the piece size. Omit it for the deterministic automatic policy. It accepts bytes, KiB/MiB, or 2^N; v2/hybrid require BEP 52-compatible lengths of at least 16 KiB. --target-pieces with --max-piece-length selects a bounded core policy.
  • --threads 0 selects conservative automatic concurrency, 1 selects the sequential oracle, and N selects bounded per-operation workers.
  • --force is required to replace an existing destination. Writes are atomic.
  • --durable also syncs the destination directory after publication where the platform supports directory syncing.
  • Repeated --tracker values create separate tiers; repeated comma-delimited --tracker-tier values preserve a tier.
  • Config tracker aliases/groups are available through --tracker-alias and --tracker-group; --public explicitly writes private=false.
  • Metadata has symmetric clear flags, --creation-date accepts now|none|UNIX|RFC3339, and --entropy is explicit (none, reproducible hex, or opt-in OS random).
  • New torrents include created by = btpc/<version> by default. Use --created-by TEXT to override it or --no-created-by to omit it; the legacy --clear-created-by spelling remains an alias.
  • --dry-run scans and validates the plan without hashing or writing. Repeat --print path|info-hash-v1|info-hash-v2|magnet for stable script lines in the requested order.
  • Multiple inputs are accepted directly; use --output-dir for inferred outputs. --batch jobs.toml accepts schema version 1 with [[jobs]], input, optional output, presets, mode, piece length, and threads. All destinations are preflighted before hashing and results remain in input/manifest order. The current conservative scheduler executes one job at a time, so the total hashing worker budget never multiplies across jobs.
  • --include, --exclude, hidden/empty policies, root naming, symlink policy, and special-file policy are deterministic on supported operating systems. Include/exclude globs are UTF-8 text filters and fail if they encounter a non-UTF-8 payload path rather than matching a lossy replacement string.

Equivalent batch input uses a versioned manifest:

version = 1

[[jobs]]
input = "payload-a"
output = "torrents/payload-a.torrent"
presets = ["private"]

[[jobs]]
input = "payload-b"
mode = "v1"
threads = 1

Inspection and Bytes

Human output decodes UTF-8 where safe. JSON never guesses: byte strings are objects with encoding set to utf-8 or hex and a value. inspect and validate never infer or read a payload path.

Default summaries remain compact. --pretty enables aligned, symbol-prefixed human output, while --quiet suppresses human summaries. Human inspect uses a stable Torrent info: summary with IEC sizes, applicable hashes, a safe magnet, and grouped tracker/web-seed URLs. --pretty adds exact-byte and validation details; -v adds optional metadata, warnings, bounded unknown fields, and a real nested file trie for multi-file torrents. --tree requests the trie directly and honors --offset, --limit, and path encoding. Machine formats remain unchanged, unstyled, and do not receive progress output.

btpc inspect payload.torrent --json
btpc inspect payload.torrent --field hash-v1 --format plain
btpc inspect payload.torrent --files --offset 0 --limit 100 --format tsv
btpc inspect payload.torrent --tree --path-encoding escaped --format json-pretty
btpc validate payload.torrent --json
btpc validate payload.torrent --canonical --warnings-as-errors

inspect --field is repeatable and preserves selector order. Available fields cover mode/name/size/pieces/files, both info hashes, private state, trackers, web seeds, nodes, comment/creator/date/source, canonicality, warnings, and unknown top-level fields. One selected scalar with --format plain writes only its value. Selected or paginated JSON uses schema btpc.inspect.selection.v1; unselected --json remains the compatible btpc.inspect.v1 schema. File paths support lossless escaped and hex encodings when UTF-8 is unavailable.

Verification

btpc verify payload.torrent payload
btpc verify payload.torrent payload --fail-fast --extra-files --json

A completed report with mismatches exits 6; operational errors use their normal category. --fail-fast stops at the first deterministic mismatch. --extra-files reports regular files not represented in metainfo. Unsafe symlink paths are reported and never followed.

Magnets

btpc magnet payload.torrent
btpc magnet payload.torrent --no-display-name --no-trackers --no-web-seeds

v1 magnets use btih, v2 magnets use multihash btmh, and hybrid magnets include both exact-topic parameters in deterministic order.

Editing

btpc edit INPUT writes <stem>.edited.torrent by default. Use --output for a different copy or explicit --in-place for atomic replacement. --dry-run validates without writing; --diff or -v includes old/new hashes. Typed set and clear flags cover trackers (including config aliases/groups), web seeds, nodes, comment, creator, creation date, private state, source, and file attributes. Top-level-only edits retain info hashes, while info-dictionary edits report the applicable changed hash domains.

Exit Codes and Streams

CodeMeaning
0Success / valid payload
1Internal error
2Usage or argument parsing error
3I/O or path error
4Invalid bencode, metainfo, or resource limit
5Unsupported policy or filesystem object
6Payload verification mismatch
130Cancellation or interrupt

Machine output is written to stdout. Human diagnostics and progress are written to stderr. Styling and the single create/verify progress display are disabled for non-terminals, quiet mode, machine output, and when NO_COLOR is set. Explicit --color always forces diagnostic styling. Errors retain stable categories and exit codes while adding available path, field, byte-offset, and remediation context; URLs are redacted before diagnostic rendering.

Versioned JSON schemas retain their existing fields. New incompatible schema shapes require a new schema identifier rather than silently changing an existing one. The legacy hidden completions SHELL command writes the same completion bytes as completion generate SHELL; its warning is stderr-only and suppressed by --quiet.

Completion and Manual Installation

btpc completion generate bash
btpc completion install bash
btpc completion install --dry-run fish
btpc completion uninstall zsh
btpc manpage > ~/.local/share/man/man1/btpc.1

When the shell argument is omitted, BTPC uses an unambiguous SHELL or PowerShell environment hint; otherwise it asks for an explicit shell. Installation uses standard per-user Bash, Zsh, Fish, PowerShell, and Elvish completion directories, creates only the required parent directories, writes atomically, and never edits shell startup files. Existing unrelated files require --force, and uninstall removes only BTPC-marked content. The hidden completions SHELL alias is retained for compatibility for one minor release. Pre-generated artifacts live in the shell completion guide and reference/btpc.1.

CLI configuration

The user-scoped TOML configuration file is versioned and can define global output settings, tracker aliases, tracker groups, and creation presets.

btpc config path
btpc config show
btpc config check
btpc config explain create --preset private ./payload

Use btpc config init to create a starter file and --no-config to ignore implicit and environment-selected configuration.

Shell completion and manual pages

Generate or install completions without editing shell startup files:

btpc completion generate bash
btpc completion install zsh --dry-run
btpc completion uninstall fish

BTPC supports Bash, Zsh, Fish, PowerShell, and Elvish user completion directories. Generate the manual page with btpc manpage and redirect it into a local manpath.

btpc

BitTorrent metainfo toolkit

See Configuration and global flags and Exit codes and streams.

Synopsis

Usage: btpc [OPTIONS] <COMMAND>

Subcommands

CommandDescription
btpc createCreate canonical v1, v2, or hybrid metainfo from a file or directory

| btpc inspect | Inspect validated metainfo without reading payload files |

| btpc validate | Validate metainfo structure without reading payload files |

| btpc verify | Verify payload files against metainfo hashes |

| btpc edit | Edit metainfo metadata without reading payload files |

| btpc magnet | Print a deterministic magnet URI |

| btpc config | Locate, inspect, validate, and update configuration |

| btpc completion | Generate, install, or uninstall shell completions |

| btpc manpage | Generate the btpc(1) manual page on stdout |

Deprecated aliases

  • btpc completions — Deprecated alias for btpc completion generate

Options

OptionDescription
-h, --helpPrint help
-V, --versionPrint version

Global options

OptionDescription
--config, <PATH>Use this configuration file when configuration loading is enabled
--no-configDisable implicit and environment-selected configuration Default: false.
--color, <COLOR>Control colored terminal output Values: auto, always, never.
-v, --verboseIncrease diagnostic verbosity; may be repeated Default: 0.
-q, --quietSuppress human summaries, warnings, and progress Default: false.

Example

$ btpc --help

btpc create

Create canonical v1, v2, or hybrid metainfo from a file or directory

Parent: btpc

See Configuration and global flags and Exit codes and streams.

Synopsis

Usage: btpc create [OPTIONS] [INPUTS]...

Arguments

ArgumentDescription
<INPUTS>Payload files or directories

Options

OptionDescription
--batch, <BATCH>Versioned TOML batch manifest
--mode, <MODE>Torrent protocol representation Values: v1, v2, hybrid.
-o, --output, <OUTPUT>Destination .torrent path (defaults beside the payload)
--output-dir, <OUTPUT_DIR>Write batch outputs beneath this directory
--jobs, <JOBS>Maximum concurrent batch creation jobs Default: 1.
--fail-fastStop scheduling batch jobs after the first failure Default: false.
-f, --forceReplace an existing destination Default: false.
--durableSync the destination directory after atomic publication where supported Default: false.
--preset, <PRESETS>Apply a named creation preset; may be repeated
--piece-length, <PIECE_LENGTH>Explicit piece length in bytes
--target-pieces, <TARGET_PIECES>Target approximate number of pieces for automatic selection
--max-piece-length, <MAX_PIECE_LENGTH>Cap target-based automatic piece length
-a, --tracker, <TRACKERS>Add a tracker as its own tier; may be repeated
--clear-trackersClear configured and preset trackers before CLI additions Default: false.
--tracker-tier, <TRACKER_TIER>Add one comma-separated tracker tier; may be repeated
--tracker-alias, <TRACKER_ALIASES>Add a configured tracker alias; may be repeated
--tracker-group, <TRACKER_GROUPS>Add a configured tracker group; may be repeated
--web-seed, <WEB_SEEDS>Add a web seed URL; may be repeated
--clear-web-seedsClear configured and preset web seeds before CLI additions Default: false.
--node, <NODES>Add a DHT node as HOST:PORT; may be repeated
--privateSet the private flag Default: false.
--publicSet the private flag to false Default: false.
--source, <SOURCE>Set the source field
--clear-sourceRemove configured or preset source metadata Default: false.
--comment, <COMMENT>Set the top-level comment
--clear-commentRemove configured or preset comment metadata Default: false.
--created-by, <CREATED_BY>Set the creator string
--no-created-by, --clear-created-byOmit the creator string instead of using the versioned default Default: false.
--creation-date, <CREATION_DATE>Include an explicit Unix creation timestamp
--entropy, <ENTROPY>Set deterministic, random, or omitted entropy policy
--name, <NAME>Override the torrent root name
--exclude-hiddenExclude dot-prefixed files and directories Default: false.
--symlinks, <SYMLINKS>Symbolic-link policy Values: reject, skip, follow.
--special-files, <SPECIAL_FILES>Special-file policy Values: reject, skip.
--exclude-empty-filesExclude zero-length files Default: false.
--reject-empty-directoriesReject empty directories instead of ignoring them Default: false.
--include, <INCLUDES>Include only paths matching this glob; may be repeated
--clear-includesClear configured and preset include patterns before CLI additions Default: false.
--exclude, <EXCLUDES>Exclude paths matching this glob; may be repeated
--clear-excludesClear configured and preset exclude patterns before CLI additions Default: false.
--threads, <THREADS>v1 hashing threads; 0 selects a conservative automatic count, 1 is sequential
--dry-runPlan creation without hashing or writing metainfo Default: false.
--print, <PRINT>Print selected result fields; may be repeated Values: path, info-hash-v1, info-hash-v2, magnet.
--jsonEmit a versioned JSON result to stdout Default: false.
--prettyUse the expanded human completion renderer Default: false.
-h, --helpPrint help

Global options

OptionDescription
--config, <PATH>Use this configuration file when configuration loading is enabled
--no-configDisable implicit and environment-selected configuration Default: false.
--color, <COLOR>Control colored terminal output Values: auto, always, never.
-v, --verboseIncrease diagnostic verbosity; may be repeated Default: 0.
-q, --quietSuppress human summaries, warnings, and progress Default: false.

Example

$ btpc create --help

btpc inspect

Inspect validated metainfo without reading payload files

Parent: btpc

See Configuration and global flags and Exit codes and streams.

Synopsis

Usage: btpc inspect [OPTIONS] <INPUT>

Arguments

ArgumentDescription
<INPUT>Metainfo file to read

Options

OptionDescription
--jsonEmit versioned JSON to stdout Default: false.
--field, <FIELDS>Select a field; may be repeated Values: mode, name, total-size, piece-length, piece-count, file-count, hash-v1, hash-v2, private, trackers, web-seeds, nodes, comment, creator, creation-date, source, canonicality, warnings, files, unknown-fields.
--filesInclude the flat file listing Default: false.
--treeRender files as a deterministic tree Default: false.
--path-encoding, <PATH_ENCODING>Encode raw torrent paths Values: utf8, escaped, hex. Default: utf8.
--offset, <OFFSET>Skip this many file rows Default: 0.
--limit, <LIMIT>Limit returned file rows
--format, <FORMAT>Select output representation Values: human, plain, json, json-pretty, tsv.
--prettyUse the expanded human renderer Default: false.
--max-input-bytes, <MAX_INPUT_BYTES>Maximum metainfo bytes accepted while loading
--max-owned-bytes, <MAX_OWNED_BYTES>Maximum estimated owned allocation while loading
--max-integer-digits, <MAX_INTEGER_DIGITS>Maximum decimal digits accepted in one bencode integer
-h, --helpPrint help

Global options

OptionDescription
--config, <PATH>Use this configuration file when configuration loading is enabled
--no-configDisable implicit and environment-selected configuration Default: false.
--color, <COLOR>Control colored terminal output Values: auto, always, never.
-v, --verboseIncrease diagnostic verbosity; may be repeated Default: 0.
-q, --quietSuppress human summaries, warnings, and progress Default: false.

Example

$ btpc inspect --help

btpc validate

Validate metainfo structure without reading payload files

Parent: btpc

See Configuration and global flags and Exit codes and streams.

Synopsis

Usage: btpc validate [OPTIONS] <INPUT>

Arguments

ArgumentDescription
<INPUT>Metainfo file to validate

Options

OptionDescription
--jsonEmit versioned JSON to stdout Default: false.
--format, <FORMAT>Select output representation Values: human, json, json-pretty.
--canonicalRequire canonical bencode Default: false.
--warnings-as-errorsReturn the warning exit code when validation warnings exist Default: false.
--prettyUse the expanded human renderer Default: false.
--max-input-bytes, <MAX_INPUT_BYTES>Maximum metainfo bytes accepted while loading
--max-owned-bytes, <MAX_OWNED_BYTES>Maximum estimated owned allocation while loading
--max-integer-digits, <MAX_INTEGER_DIGITS>Maximum decimal digits accepted in one bencode integer
-h, --helpPrint help

Global options

OptionDescription
--config, <PATH>Use this configuration file when configuration loading is enabled
--no-configDisable implicit and environment-selected configuration Default: false.
--color, <COLOR>Control colored terminal output Values: auto, always, never.
-v, --verboseIncrease diagnostic verbosity; may be repeated Default: 0.
-q, --quietSuppress human summaries, warnings, and progress Default: false.

Example

$ btpc validate --help

btpc verify

Verify payload files against metainfo hashes

Parent: btpc

See Configuration and global flags and Exit codes and streams.

Synopsis

Usage: btpc verify [OPTIONS] <TORRENT> <PAYLOAD>

Arguments

ArgumentDescription
<TORRENT>Metainfo file to read
<PAYLOAD>Payload file or directory to verify

Options

OptionDescription
--fail-fastStop after the first deterministic mismatch Default: false.
--extra-filesReport regular files absent from metainfo Default: false.
--jsonEmit versioned JSON to stdout Default: false.
--prettyUse the expanded human renderer Default: false.
--max-input-bytes, <MAX_INPUT_BYTES>Maximum metainfo bytes accepted while loading
--max-owned-bytes, <MAX_OWNED_BYTES>Maximum estimated owned allocation while loading
--max-integer-digits, <MAX_INTEGER_DIGITS>Maximum decimal digits accepted in one bencode integer
-h, --helpPrint help

Global options

OptionDescription
--config, <PATH>Use this configuration file when configuration loading is enabled
--no-configDisable implicit and environment-selected configuration Default: false.
--color, <COLOR>Control colored terminal output Values: auto, always, never.
-v, --verboseIncrease diagnostic verbosity; may be repeated Default: 0.
-q, --quietSuppress human summaries, warnings, and progress Default: false.

Example

$ btpc verify --help

btpc edit

Edit metainfo metadata without reading payload files

Parent: btpc

See Configuration and global flags and Exit codes and streams.

Synopsis

Usage: btpc edit [OPTIONS] <INPUT>

Arguments

ArgumentDescription
<INPUT>Metainfo file to edit

Options

OptionDescription
-o, --output, <OUTPUT>Write the edited metainfo to this path
--in-placeReplace the input file atomically Default: false.
-f, --forceReplace an existing output file Default: false.
--durableSync the destination directory after publication where supported Default: false.
--dry-runValidate and report changes without writing output Default: false.
--diffPrint a deterministic field-level change summary Default: false.
--jsonEmit a versioned JSON result to stdout Default: false.
-a, --tracker, <TRACKERS>Replace trackers with this tracker tier; may be repeated
--tracker-alias, <TRACKER_ALIASES>Add a configured tracker alias; may be repeated
--tracker-group, <TRACKER_GROUPS>Add a configured tracker group; may be repeated
--clear-trackersRemove all trackers Default: false.
--web-seed, <WEB_SEEDS>Replace web seeds with this URL; may be repeated
--clear-web-seedsRemove all web seeds Default: false.
--node, <NODES>Replace DHT nodes with HOST:PORT; may be repeated
--clear-nodesRemove all DHT nodes Default: false.
--comment, <COMMENT>Set the top-level comment
--clear-commentRemove the top-level comment Default: false.
--created-by, <CREATED_BY>Set the creator string
--clear-created-byRemove the creator string Default: false.
--creation-date, <CREATION_DATE>Set the Unix creation timestamp
--clear-creation-dateRemove the creation timestamp Default: false.
--privateSet the private flag Default: false.
--publicSet the private flag to false Default: false.
--clear-privateRemove the private field Default: false.
--source, <SOURCE>Set the source field
--clear-sourceRemove the source field Default: false.
--file-attributes, <FILE_ATTRIBUTES>Set file attributes as PATH=ATTRS; may be repeated
-h, --helpPrint help

Global options

OptionDescription
--config, <PATH>Use this configuration file when configuration loading is enabled
--no-configDisable implicit and environment-selected configuration Default: false.
--color, <COLOR>Control colored terminal output Values: auto, always, never.
-v, --verboseIncrease diagnostic verbosity; may be repeated Default: 0.
-q, --quietSuppress human summaries, warnings, and progress Default: false.

Example

$ btpc edit --help

btpc magnet

Print a deterministic magnet URI

Parent: btpc

See Configuration and global flags and Exit codes and streams.

Synopsis

Usage: btpc magnet [OPTIONS] <INPUT>

Arguments

ArgumentDescription
<INPUT>Metainfo file to read

Options

OptionDescription
--no-display-nameOmit the display name parameter Default: false.
--no-trackersOmit tracker parameters Default: false.
--no-web-seedsOmit web seed parameters Default: false.
--max-input-bytes, <MAX_INPUT_BYTES>Maximum metainfo bytes accepted while loading
--max-owned-bytes, <MAX_OWNED_BYTES>Maximum estimated owned allocation while loading
--max-integer-digits, <MAX_INTEGER_DIGITS>Maximum decimal digits accepted in one bencode integer
-h, --helpPrint help

Global options

OptionDescription
--config, <PATH>Use this configuration file when configuration loading is enabled
--no-configDisable implicit and environment-selected configuration Default: false.
--color, <COLOR>Control colored terminal output Values: auto, always, never.
-v, --verboseIncrease diagnostic verbosity; may be repeated Default: 0.
-q, --quietSuppress human summaries, warnings, and progress Default: false.

Example

$ btpc magnet --help

btpc config

Locate, inspect, validate, and update configuration

Parent: btpc

See Configuration and global flags and Exit codes and streams.

Synopsis

Usage: btpc config [OPTIONS] <COMMAND>

Subcommands

CommandDescription
btpc config pathPrint the selected configuration path

| btpc config init | Create a minimal configuration file |

| btpc config show | Print configuration with secrets redacted by default |

| btpc config check | Validate schema, references, cycles, conflicts, and permissions |

| btpc config explain | Explain resolved command values without executing the command |

| btpc config tracker | Manage named tracker aliases |

| btpc config preset | Manage named creation presets |

Options

OptionDescription
-h, --helpPrint help

Global options

OptionDescription
--config, <PATH>Use this configuration file when configuration loading is enabled
--no-configDisable implicit and environment-selected configuration Default: false.
--color, <COLOR>Control colored terminal output Values: auto, always, never.
-v, --verboseIncrease diagnostic verbosity; may be repeated Default: 0.
-q, --quietSuppress human summaries, warnings, and progress Default: false.

Example

$ btpc config --help

btpc config path

Print the selected configuration path

Parent: btpc config

See Configuration and global flags and Exit codes and streams.

Synopsis

Usage: btpc config path [OPTIONS]

Options

OptionDescription
-h, --helpPrint help

Global options

OptionDescription
--config, <PATH>Use this configuration file when configuration loading is enabled
--no-configDisable implicit and environment-selected configuration Default: false.
--color, <COLOR>Control colored terminal output Values: auto, always, never.
-v, --verboseIncrease diagnostic verbosity; may be repeated Default: 0.
-q, --quietSuppress human summaries, warnings, and progress Default: false.

Example

$ btpc config path --help

btpc config init

Create a minimal configuration file

Parent: btpc config

See Configuration and global flags and Exit codes and streams.

Synopsis

Usage: btpc config init [OPTIONS]

Options

OptionDescription
--forceReplace an existing file Default: false.
-h, --helpPrint help

Global options

OptionDescription
--config, <PATH>Use this configuration file when configuration loading is enabled
--no-configDisable implicit and environment-selected configuration Default: false.
--color, <COLOR>Control colored terminal output Values: auto, always, never.
-v, --verboseIncrease diagnostic verbosity; may be repeated Default: 0.
-q, --quietSuppress human summaries, warnings, and progress Default: false.

Example

$ btpc config init --help

btpc config show

Print configuration with secrets redacted by default

Parent: btpc config

See Configuration and global flags and Exit codes and streams.

Synopsis

Usage: btpc config show [OPTIONS]

Options

OptionDescription
--resolvedValidate and print the parsed deterministic representation Default: false.
--show-secretsReveal configured secrets Default: false.
--jsonEmit JSON instead of TOML Default: false.
-h, --helpPrint help

Global options

OptionDescription
--config, <PATH>Use this configuration file when configuration loading is enabled
--no-configDisable implicit and environment-selected configuration Default: false.
--color, <COLOR>Control colored terminal output Values: auto, always, never.
-v, --verboseIncrease diagnostic verbosity; may be repeated Default: 0.
-q, --quietSuppress human summaries, warnings, and progress Default: false.

Example

$ btpc config show --help

btpc config check

Validate schema, references, cycles, conflicts, and permissions

Parent: btpc config

See Configuration and global flags and Exit codes and streams.

Synopsis

Usage: btpc config check [OPTIONS]

Options

OptionDescription
-h, --helpPrint help

Global options

OptionDescription
--config, <PATH>Use this configuration file when configuration loading is enabled
--no-configDisable implicit and environment-selected configuration Default: false.
--color, <COLOR>Control colored terminal output Values: auto, always, never.
-v, --verboseIncrease diagnostic verbosity; may be repeated Default: 0.
-q, --quietSuppress human summaries, warnings, and progress Default: false.

Example

$ btpc config check --help

btpc config explain

Explain resolved command values without executing the command

Parent: btpc config

See Configuration and global flags and Exit codes and streams.

Synopsis

Usage: btpc config explain [OPTIONS] <COMMAND>

Subcommands

CommandDescription
btpc config explain createExplain effective create values and provenance

Options

OptionDescription
-h, --helpPrint help

Global options

OptionDescription
--config, <PATH>Use this configuration file when configuration loading is enabled
--no-configDisable implicit and environment-selected configuration Default: false.
--color, <COLOR>Control colored terminal output Values: auto, always, never.
-v, --verboseIncrease diagnostic verbosity; may be repeated Default: 0.
-q, --quietSuppress human summaries, warnings, and progress Default: false.

Example

$ btpc config explain --help

btpc config explain create

Explain effective create values and provenance

Parent: btpc config explain

See Configuration and global flags and Exit codes and streams.

Synopsis

Usage: btpc config explain create [OPTIONS] [INPUTS]...

Arguments

ArgumentDescription
<INPUTS>Payload files or directories

Options

OptionDescription
--batch, <BATCH>Versioned TOML batch manifest
--mode, <MODE>Torrent protocol representation Values: v1, v2, hybrid.
-o, --output, <OUTPUT>Destination .torrent path (defaults beside the payload)
--output-dir, <OUTPUT_DIR>Write batch outputs beneath this directory
--jobs, <JOBS>Maximum concurrent batch creation jobs Default: 1.
--fail-fastStop scheduling batch jobs after the first failure Default: false.
-f, --forceReplace an existing destination Default: false.
--durableSync the destination directory after atomic publication where supported Default: false.
--preset, <PRESETS>Apply a named creation preset; may be repeated
--piece-length, <PIECE_LENGTH>Explicit piece length in bytes
--target-pieces, <TARGET_PIECES>Target approximate number of pieces for automatic selection
--max-piece-length, <MAX_PIECE_LENGTH>Cap target-based automatic piece length
-a, --tracker, <TRACKERS>Add a tracker as its own tier; may be repeated
--clear-trackersClear configured and preset trackers before CLI additions Default: false.
--tracker-tier, <TRACKER_TIER>Add one comma-separated tracker tier; may be repeated
--tracker-alias, <TRACKER_ALIASES>Add a configured tracker alias; may be repeated
--tracker-group, <TRACKER_GROUPS>Add a configured tracker group; may be repeated
--web-seed, <WEB_SEEDS>Add a web seed URL; may be repeated
--clear-web-seedsClear configured and preset web seeds before CLI additions Default: false.
--node, <NODES>Add a DHT node as HOST:PORT; may be repeated
--privateSet the private flag Default: false.
--publicSet the private flag to false Default: false.
--source, <SOURCE>Set the source field
--clear-sourceRemove configured or preset source metadata Default: false.
--comment, <COMMENT>Set the top-level comment
--clear-commentRemove configured or preset comment metadata Default: false.
--created-by, <CREATED_BY>Set the creator string
--no-created-by, --clear-created-byOmit the creator string instead of using the versioned default Default: false.
--creation-date, <CREATION_DATE>Include an explicit Unix creation timestamp
--entropy, <ENTROPY>Set deterministic, random, or omitted entropy policy
--name, <NAME>Override the torrent root name
--exclude-hiddenExclude dot-prefixed files and directories Default: false.
--symlinks, <SYMLINKS>Symbolic-link policy Values: reject, skip, follow.
--special-files, <SPECIAL_FILES>Special-file policy Values: reject, skip.
--exclude-empty-filesExclude zero-length files Default: false.
--reject-empty-directoriesReject empty directories instead of ignoring them Default: false.
--include, <INCLUDES>Include only paths matching this glob; may be repeated
--clear-includesClear configured and preset include patterns before CLI additions Default: false.
--exclude, <EXCLUDES>Exclude paths matching this glob; may be repeated
--clear-excludesClear configured and preset exclude patterns before CLI additions Default: false.
--threads, <THREADS>v1 hashing threads; 0 selects a conservative automatic count, 1 is sequential
--dry-runPlan creation without hashing or writing metainfo Default: false.
--print, <PRINT>Print selected result fields; may be repeated Values: path, info-hash-v1, info-hash-v2, magnet.
--jsonEmit a versioned JSON result to stdout Default: false.
--prettyUse the expanded human completion renderer Default: false.
-h, --helpPrint help

Global options

OptionDescription
--config, <PATH>Use this configuration file when configuration loading is enabled
--no-configDisable implicit and environment-selected configuration Default: false.
--color, <COLOR>Control colored terminal output Values: auto, always, never.
-v, --verboseIncrease diagnostic verbosity; may be repeated Default: 0.
-q, --quietSuppress human summaries, warnings, and progress Default: false.

Example

$ btpc config explain create --help

btpc config tracker

Manage named tracker aliases

Parent: btpc config

See Configuration and global flags and Exit codes and streams.

Synopsis

Usage: btpc config tracker [OPTIONS] <COMMAND>

Subcommands

CommandDescription
btpc config tracker listList tracker aliases

| btpc config tracker add | Add or replace a tracker alias |

| btpc config tracker remove | Remove a tracker alias |

Options

OptionDescription
-h, --helpPrint help

Global options

OptionDescription
--config, <PATH>Use this configuration file when configuration loading is enabled
--no-configDisable implicit and environment-selected configuration Default: false.
--color, <COLOR>Control colored terminal output Values: auto, always, never.
-v, --verboseIncrease diagnostic verbosity; may be repeated Default: 0.
-q, --quietSuppress human summaries, warnings, and progress Default: false.

Example

$ btpc config tracker --help

btpc config tracker list

List tracker aliases

Parent: btpc config tracker

See Configuration and global flags and Exit codes and streams.

Synopsis

Usage: btpc config tracker list [OPTIONS]

Options

OptionDescription
--show-secretsReveal tracker URLs Default: false.
--jsonEmit JSON Default: false.
-h, --helpPrint help

Global options

OptionDescription
--config, <PATH>Use this configuration file when configuration loading is enabled
--no-configDisable implicit and environment-selected configuration Default: false.
--color, <COLOR>Control colored terminal output Values: auto, always, never.
-v, --verboseIncrease diagnostic verbosity; may be repeated Default: 0.
-q, --quietSuppress human summaries, warnings, and progress Default: false.

Example

$ btpc config tracker list --help

btpc config tracker add

Add or replace a tracker alias

Parent: btpc config tracker

See Configuration and global flags and Exit codes and streams.

Synopsis

Usage: btpc config tracker add [OPTIONS] <NAME> <URL>

Arguments

ArgumentDescription
<NAME>Alias name
<URL>Tracker announce URL

Options

OptionDescription
-h, --helpPrint help

Global options

OptionDescription
--config, <PATH>Use this configuration file when configuration loading is enabled
--no-configDisable implicit and environment-selected configuration Default: false.
--color, <COLOR>Control colored terminal output Values: auto, always, never.
-v, --verboseIncrease diagnostic verbosity; may be repeated Default: 0.
-q, --quietSuppress human summaries, warnings, and progress Default: false.

Example

$ btpc config tracker add --help

btpc config tracker remove

Remove a tracker alias

Parent: btpc config tracker

See Configuration and global flags and Exit codes and streams.

Synopsis

Usage: btpc config tracker remove [OPTIONS] <NAME>

Arguments

ArgumentDescription
<NAME>Alias name

Options

OptionDescription
-h, --helpPrint help

Global options

OptionDescription
--config, <PATH>Use this configuration file when configuration loading is enabled
--no-configDisable implicit and environment-selected configuration Default: false.
--color, <COLOR>Control colored terminal output Values: auto, always, never.
-v, --verboseIncrease diagnostic verbosity; may be repeated Default: 0.
-q, --quietSuppress human summaries, warnings, and progress Default: false.

Example

$ btpc config tracker remove --help

btpc config preset

Manage named creation presets

Parent: btpc config

See Configuration and global flags and Exit codes and streams.

Synopsis

Usage: btpc config preset [OPTIONS] <COMMAND>

Subcommands

CommandDescription
btpc config preset listList preset names

| btpc config preset show | Show one preset |

| btpc config preset save | Save or replace a preset |

| btpc config preset remove | Remove a preset |

Options

OptionDescription
-h, --helpPrint help

Global options

OptionDescription
--config, <PATH>Use this configuration file when configuration loading is enabled
--no-configDisable implicit and environment-selected configuration Default: false.
--color, <COLOR>Control colored terminal output Values: auto, always, never.
-v, --verboseIncrease diagnostic verbosity; may be repeated Default: 0.
-q, --quietSuppress human summaries, warnings, and progress Default: false.

Example

$ btpc config preset --help

btpc config preset list

List preset names

Parent: btpc config preset

See Configuration and global flags and Exit codes and streams.

Synopsis

Usage: btpc config preset list [OPTIONS]

Options

OptionDescription
--jsonEmit JSON Default: false.
-h, --helpPrint help

Global options

OptionDescription
--config, <PATH>Use this configuration file when configuration loading is enabled
--no-configDisable implicit and environment-selected configuration Default: false.
--color, <COLOR>Control colored terminal output Values: auto, always, never.
-v, --verboseIncrease diagnostic verbosity; may be repeated Default: 0.
-q, --quietSuppress human summaries, warnings, and progress Default: false.

Example

$ btpc config preset list --help

btpc config preset show

Show one preset

Parent: btpc config preset

See Configuration and global flags and Exit codes and streams.

Synopsis

Usage: btpc config preset show [OPTIONS] <NAME>

Arguments

ArgumentDescription
<NAME>Preset name

Options

OptionDescription
--show-secretsReveal configured URLs Default: false.
--jsonEmit JSON Default: false.
-h, --helpPrint help

Global options

OptionDescription
--config, <PATH>Use this configuration file when configuration loading is enabled
--no-configDisable implicit and environment-selected configuration Default: false.
--color, <COLOR>Control colored terminal output Values: auto, always, never.
-v, --verboseIncrease diagnostic verbosity; may be repeated Default: 0.
-q, --quietSuppress human summaries, warnings, and progress Default: false.

Example

$ btpc config preset show --help

btpc config preset save

Save or replace a preset

Parent: btpc config preset

See Configuration and global flags and Exit codes and streams.

Synopsis

Usage: btpc config preset save [OPTIONS] <NAME>

Arguments

ArgumentDescription
<NAME>Preset name

Options

OptionDescription
--extends, <EXTENDS>Parent preset; may be repeated
--mode, <MODE>Torrent protocol representation Values: v1, v2, hybrid.
--piece-length, <PIECE_LENGTH>Explicit piece length in bytes
--privateSet the private flag Default: false.
--source, <SOURCE>Set the source field
--comment, <COMMENT>Set the top-level comment
--created-by, <CREATED_BY>Set the creator string
--creation-date, <CREATION_DATE>Set the Unix creation timestamp
--name, <NAME_OVERRIDE>Override the torrent root name
--exclude-hiddenExclude dot-prefixed files and directories Default: false.
--symlinks, <SYMLINKS>Symbolic-link policy Values: reject, skip, follow.
--special-files, <SPECIAL_FILES>Special-file policy Values: reject, skip.
--exclude-empty-filesExclude zero-length files Default: false.
--reject-empty-directoriesReject empty directories instead of ignoring them Default: false.
--tracker, <TRACKERS>Add a tracker as its own tier; may be repeated
--tracker-alias, <TRACKER_ALIASES>Add a configured tracker alias; may be repeated
--tracker-group, <TRACKER_GROUPS>Add a configured tracker group; may be repeated
--web-seed, <WEB_SEEDS>Add a web seed URL; may be repeated
--include, <INCLUDES>Include only paths matching this glob; may be repeated
--exclude, <EXCLUDES>Exclude paths matching this glob; may be repeated
--threads, <THREADS>v1 hashing threads; 0 selects automatic, 1 is sequential
-h, --helpPrint help

Global options

OptionDescription
--config, <PATH>Use this configuration file when configuration loading is enabled
--no-configDisable implicit and environment-selected configuration Default: false.
--color, <COLOR>Control colored terminal output Values: auto, always, never.
-v, --verboseIncrease diagnostic verbosity; may be repeated Default: 0.
-q, --quietSuppress human summaries, warnings, and progress Default: false.

Example

$ btpc config preset save --help

btpc config preset remove

Remove a preset

Parent: btpc config preset

See Configuration and global flags and Exit codes and streams.

Synopsis

Usage: btpc config preset remove [OPTIONS] <NAME>

Arguments

ArgumentDescription
<NAME>Preset name

Options

OptionDescription
-h, --helpPrint help

Global options

OptionDescription
--config, <PATH>Use this configuration file when configuration loading is enabled
--no-configDisable implicit and environment-selected configuration Default: false.
--color, <COLOR>Control colored terminal output Values: auto, always, never.
-v, --verboseIncrease diagnostic verbosity; may be repeated Default: 0.
-q, --quietSuppress human summaries, warnings, and progress Default: false.

Example

$ btpc config preset remove --help

btpc completion

Generate, install, or uninstall shell completions

Parent: btpc

See Configuration and global flags and Exit codes and streams.

Synopsis

Usage: btpc completion [OPTIONS] <COMMAND>

Subcommands

CommandDescription
btpc completion generateGenerate a shell completion script on stdout

| btpc completion install | Install shell completions in the standard per-user directory |

| btpc completion uninstall | Remove BTPC-generated shell completions |

Options

OptionDescription
-h, --helpPrint help

Global options

OptionDescription
--config, <PATH>Use this configuration file when configuration loading is enabled
--no-configDisable implicit and environment-selected configuration Default: false.
--color, <COLOR>Control colored terminal output Values: auto, always, never.
-v, --verboseIncrease diagnostic verbosity; may be repeated Default: 0.
-q, --quietSuppress human summaries, warnings, and progress Default: false.

Example

$ btpc completion --help

btpc completion generate

Generate a shell completion script on stdout

Parent: btpc completion

See Configuration and global flags and Exit codes and streams.

Synopsis

Usage: btpc completion generate [OPTIONS] <SHELL>

Arguments

ArgumentDescription
<SHELL>Shell whose completion syntax should be generated Values: bash, elvish, fish, powershell, zsh.

Options

OptionDescription
-h, --helpPrint help

Global options

OptionDescription
--config, <PATH>Use this configuration file when configuration loading is enabled
--no-configDisable implicit and environment-selected configuration Default: false.
--color, <COLOR>Control colored terminal output Values: auto, always, never.
-v, --verboseIncrease diagnostic verbosity; may be repeated Default: 0.
-q, --quietSuppress human summaries, warnings, and progress Default: false.

Example

$ btpc completion generate --help

btpc completion install

Install shell completions in the standard per-user directory

Parent: btpc completion

See Configuration and global flags and Exit codes and streams.

Synopsis

Usage: btpc completion install [OPTIONS] [SHELL]

Arguments

ArgumentDescription
<SHELL>Shell to install; detected from environment hints when omitted Values: bash, elvish, fish, powershell, zsh.

Options

OptionDescription
--dry-runPrint the target and generated content without changing files Default: false.
--forceReplace an unrelated existing completion file Default: false.
-h, --helpPrint help

Global options

OptionDescription
--config, <PATH>Use this configuration file when configuration loading is enabled
--no-configDisable implicit and environment-selected configuration Default: false.
--color, <COLOR>Control colored terminal output Values: auto, always, never.
-v, --verboseIncrease diagnostic verbosity; may be repeated Default: 0.
-q, --quietSuppress human summaries, warnings, and progress Default: false.

Example

$ btpc completion install --help

btpc completion uninstall

Remove BTPC-generated shell completions

Parent: btpc completion

See Configuration and global flags and Exit codes and streams.

Synopsis

Usage: btpc completion uninstall [OPTIONS] [SHELL]

Arguments

ArgumentDescription
<SHELL>Shell to install; detected from environment hints when omitted Values: bash, elvish, fish, powershell, zsh.

Options

OptionDescription
--dry-runPrint the target and generated content without changing files Default: false.
--forceReplace an unrelated existing completion file Default: false.
-h, --helpPrint help

Global options

OptionDescription
--config, <PATH>Use this configuration file when configuration loading is enabled
--no-configDisable implicit and environment-selected configuration Default: false.
--color, <COLOR>Control colored terminal output Values: auto, always, never.
-v, --verboseIncrease diagnostic verbosity; may be repeated Default: 0.
-q, --quietSuppress human summaries, warnings, and progress Default: false.

Example

$ btpc completion uninstall --help

btpc manpage

Generate the btpc(1) manual page on stdout

Parent: btpc

See Configuration and global flags and Exit codes and streams.

Synopsis

Usage: btpc manpage [OPTIONS]

Options

OptionDescription
-h, --helpPrint help

Global options

OptionDescription
--config, <PATH>Use this configuration file when configuration loading is enabled
--no-configDisable implicit and environment-selected configuration Default: false.
--color, <COLOR>Control colored terminal output Values: auto, always, never.
-v, --verboseIncrease diagnostic verbosity; may be repeated Default: 0.
-q, --quietSuppress human summaries, warnings, and progress Default: false.

Example

$ btpc manpage --help

Python API Guide

Public Modules

BTPC supports concise root imports and canonical domain imports with identical objects. The stable public modules are:

  • btpc.errors for the exception hierarchy.
  • btpc.types for protocol modes, hashes, parse options, and byte/path values.
  • btpc.metainfo for parsed metainfo, files, and validation reports.
  • btpc.creation for creation options, results, cancellation, and functions.
  • btpc.verification for mismatch values, reports, and verification.

For example, btpc.Metainfo is btpc.metainfo.Metainfo. The compiled btpc._native module and btpc._conversion helpers are private implementation details and are not exported through btpc.__all__.

The public package is btpc; btpc._native is private. The package is typed, ships py.typed, supports CPython 3.11–3.14, and delegates parsing, hashing, traversal, Merkle construction, creation, and verification to Rust.

Workflow Reference

Public API Index

Root imports are aliases of the canonical objects below. Each object is documented once on its defining-module page.

Package version

btpc.__version__ is the installed package version string. The module names btpc.creation, btpc.metainfo, btpc.verification, btpc.types, and btpc.errors are also available from the package root.

Create, Read, Verify

from pathlib import Path

from btpc import CreateOptions, TorrentMode, create

payload = Path("payload")
result = create(
    payload,
    "payload.torrent",
    options=CreateOptions(
        mode=TorrentMode.HYBRID,
        piece_length=16_384,
        threads=1,
        creation_date=0,
        trackers=(("https://tracker.example/announce",),),
        nodes=(("router.example", 6881),),
    ),
)
torrent = result.metainfo
assert torrent.mode is TorrentMode.HYBRID
assert torrent.verify(payload).is_valid
print(torrent.info_hash_v1, torrent.info_hash_v2)
print(torrent.magnet())

create_bytes() returns the same CreateResult without writing a destination. result.metainfo creates and caches a validated object on first access without copying the generated bytes through Python or reading the payload again. CreateOptions accepts sequences and copies tracker tiers, web seeds, and nodes into immutable tuples at construction. Invalid sequence shapes and element types raise TypeError at construction. Creation includes created by = btpc/<version> by default. Set CreateOptions(created_by="my-tool") to override it or CreateOptions(omit_created_by=True) to omit the field. Creator metadata is top-level and does not change applicable info hashes. create() writes atomically and requires overwrite=True to replace a file. Pass durable=True to also sync the destination directory after publication where the platform supports directory syncing. Creation options match core defaults: v1, automatic piece length, conservative automatic threads, no timestamp, and no optional metadata.

Byte-Safe Inspection

Protocol identities are bytes:

from btpc import Metainfo

torrent = Metainfo.read("payload.torrent")
print(torrent.name)       # bytes
print(torrent.name_text)  # str | None
for file in torrent.files:
    print(file.path, file.path_text, file.is_padding)

from_bytes() accepts bytes, bytearray, and contiguous buffer objects. Exact source bytes remain in original_bytes; to_bytes() returns canonical bencode. For parseable noncanonical input those byte sequences, and therefore source versus canonical info hashes, can differ by design.

Metainfo and CreateResult are immutable, non-subclassable facades over owned native Rust objects. Parsing does not eagerly create Python copies of source or canonical bytes, file entries, trackers, web seeds, unknown fields, or validation details. Those values are converted once on first property access and then cached with stable property identity. Canonical serialization is also lazy in the Rust core. These native-backed objects are intentionally not picklable; serialize a Metainfo with to_bytes(canonical=False) or to_bytes(), and serialize a CreateResult through its bytes property.

The private extension returns typed immutable _NativeMetainfo, _NativeTorrentFile, _NativeValidationReport, _NativeCreateResult, _NativePayloadMismatch, and _NativeVerificationReport objects rather than snapshot dictionaries. Public applications should continue using the btpc facades; the underscore-prefixed native classes remain implementation details.

Hybrid files includes validated v1 alignment entries. These have TorrentFile.is_padding == True, a reserved .pad path, and no v2 pieces root.

Both loading methods use conservative native limits by default. Pass ParseOptions(max_total_input=..., max_owned_allocation=..., max_integer_digits=...) to set explicit limits; violations raise ResourceLimitError before an oversized input, digit run, or owned snapshot is accepted.

Editing

Metainfo.edit() returns a newly validated object. Top-level-only edits such as trackers or comments preserve the exact original info bytes and hashes, including noncanonical source encoding. Edits inside info, including private, source, or file attributes, canonicalize the updated dictionary and recompute applicable hashes. to_bytes() remains the explicit fully canonical output choice.

Save edits with edited.write("edited.torrent"). This publishes atomically and preserves the edited object’s exact encoding and info hashes by default. Existing files require overwrite=True; durable=True requests parent-directory syncing. write(canonical=True) explicitly normalizes bencoding. For noncanonical source info this can change hashes. The compatibility default of to_bytes() remains canonical; use to_bytes(canonical=False) for exact bytes without file I/O.

from btpc import UNCHANGED

edited = torrent.edit(comment="release")
assert edited.info_hash_v1 == torrent.info_hash_v1

unchanged = edited.edit(comment=UNCHANGED)
removed = edited.edit(comment=None)

Optional edit fields use a three-state contract: UNCHANGED preserves the current value, None removes it, and a typed value sets it. The former paired set_* keywords were intentionally removed before 1.0 rather than silently reinterpreted.

Metainfo equality compares exact original source bytes in native code, including noncanonical encodings and top-level metadata. Metainfo is intentionally unhashable before 1.0 so equality never requires copying the full source into a Python bytes object or committing to a process-hash implementation.

Progress and Cancellation

from btpc import CancellationToken, CreateOptions, create_bytes

cancel = CancellationToken()

def progress(bytes_hashed: int, total_bytes: int, pieces_hashed: int) -> None:
    if bytes_hashed > total_bytes // 2:
        cancel.cancel()

create_bytes(
    "payload",
    options=CreateOptions(threads=1),
    progress=progress,
    cancellation=cancel,
)

Long-running native work releases the GIL. Callbacks reacquire it safely and an exception from a callback cancels the core operation before propagating.

The extension itself currently requires the GIL, including on free-threaded CPython builds. Subinterpreters are rejected by PyO3 and are not supported; see the compatibility guide.

Errors

All public failures derive from BtpcError and may expose offset, field, or path:

  • BencodeError: syntax or canonical bencode failure.
  • MetainfoError: invalid protocol field or invariant.
  • PathError: filesystem/path failure.
  • ResourceLimitError: configured parse or ownership budget exceeded.
  • VerificationError: verification-category failure.
  • UnsupportedError: unsupported policy or object.
  • CancelledError: cooperative cancellation.

Native filesystem errors attach a platform-native pathlib.Path directly. Unix byte paths use Python’s filesystem surrogate handling, while Windows paths retain their original UTF-16 units without an intermediate UTF-8 conversion.

A normal verification mismatch is returned in PayloadVerificationReport rather than raised. Inspect mismatches, each containing a stable MismatchKind, path, and optional piece index.

Optional metadata inspection

Validated inspection exposes trackers, web_seeds, nodes, source, comment, created_by, and creation_date from the same native optional-metadata model used by Rust and the CLI. Hosts and textual metadata remain bytes; node ports and creation dates are integers.

The comment_text, source_text, and created_by_text properties return UTF-8 text or None when absent or undecodable. trackers_text, web_seeds_text, and nodes_text return decoded immutable collections, or None if any string is not UTF-8. Empty collections remain empty tuples. Raw properties retain all bytes. Check the raw property to distinguish absence from a decoding failure before using a nullable text view in an edit, because None removes a field.

torrent.validation exposes the cached report from construction. validate() returns the same report for compatibility; it does not repeat validation. Invalid metainfo raises during construction, so this report’s is_valid is always true.

Unknown extension values

Metainfo.unknown_fields returns immutable UnknownField objects rather than keys alone. Each object includes its raw byte key, a recursive value made from Python integers and bytes plus BencodeList/BencodeDictionary, the exact encoded source bytes for the key/value entry, and its half-open span in original_bytes. Arbitrary-size integers and non-UTF-8 dictionary keys remain lossless.

Pass the same recursive values to Metainfo.edit(raw_top_level=...) to preserve or replace extension dictionaries and lists. Reserved protocol keys remain rejected, and BencodeDictionary rejects duplicate keys while sorting unique raw keys canonically.

BencodeList supports iteration, indexing, slicing, membership, count, and index. Slicing returns another immutable BencodeList. BencodeDictionary supports key iteration, lookup, membership, get, keys(), values(), and dict(value) conversion. Its existing items attribute remains an immutable tuple, not a method.

Python examples

The quick start covers creation and verification. Use Metainfo.from_bytes for contiguous buffers and Metainfo.read for direct path I/O. Parsed raw values remain bytes, while optional text views return None when UTF-8 decoding is not lossless.

from btpc import UNCHANGED, Metainfo

torrent = Metainfo.read("payload.torrent")
reviewed = torrent.edit(comment="reviewed", source=UNCHANGED)
reviewed.write("payload-reviewed.torrent")

Top-level edits preserve info hashes; info-dictionary edits change them. write() preserves the edited object’s encoding and publishes atomically. Pass overwrite=True to replace an existing destination. Pass canonical=True to normalize bencoding, which can change info hashes for noncanonical input.

Reuse creation results directly:

from btpc import create_bytes

result = create_bytes("payload")
print(result.metainfo.magnet())
assert result.metainfo.verify("payload").is_valid

Copy a UTF-8 comment to another torrent without replacement decoding:

from btpc import Metainfo

source = Metainfo.read("source.torrent")
destination = Metainfo.read("destination.torrent")
comment = source.comment_text
if source.comment is not None and comment is None:
    raise ValueError("source comment is not UTF-8")
destination.edit(comment=comment).write("destination-reviewed.torrent")

Inspect an immutable extension value with ordinary collection operations:

from btpc import BencodeDictionary, BencodeList

values = BencodeList((1, b"raw"))
extension = BencodeDictionary(((b"values", values),))
assert values[1:] == BencodeList((b"raw",))
assert extension[b"values"] == values
assert dict(extension) == {b"values": values}

Creation API

btpc.creation

Create v1, v2, and hybrid torrent metainfo from filesystem payloads.

CancellationToken

class CancellationToken()

Request cooperative cancellation of creation or verification.

The same token may be passed to one or more operations. Calling cancel is thread-safe; active operations stop at a core cancellation checkpoint and raise CancelledError.

Examples:

>>> from btpc import CancellationToken
>>> token = CancellationToken()
>>> token.cancel()
>>> token.cancelled
True

cancel

cancel() -> None

Request cancellation.

cancelled

cancelled: bool

Return whether cancellation was requested.

CreateMetrics

class CreateMetrics(scan_ms: float, hash_ms: float, serialize_ms: float)

Record elapsed time for creation phases.

Attributes:

  • scan_ms: Filesystem traversal time in milliseconds.
  • hash_ms: Payload hashing time in milliseconds.
  • serialize_ms: Canonical metainfo serialization time in milliseconds.

scan_ms

scan_ms: float

hash_ms

hash_ms: float

serialize_ms

serialize_ms: float

CreateOptions

class CreateOptions(mode: TorrentMode = TorrentMode.V1, piece_length: int | None = None, threads: int | None = None, trackers: Sequence[Sequence[str]] = (), web_seeds: Sequence[str] = (), nodes: Sequence[tuple[str, int]] = (), private: bool | None = None, source: str | None = None, comment: str | None = None, created_by: str | None = None, omit_created_by: bool = False, creation_date: int | None = None)

Configure deterministic torrent creation.

Tracker tiers, web seeds, and nodes are copied into immutable tuples when options are constructed. Later changes to caller-owned lists have no effect.

Attributes:

  • mode: Protocol representation to create. The default is TorrentMode.V1.
  • piece_length: Explicit piece length in bytes. None selects the core’s deterministic automatic policy; v2 and hybrid values must satisfy BEP 52 constraints.
  • threads: Maximum hashing worker count. None lets the core choose from available parallelism.
  • trackers: Tracker tiers as a sequence of URL-string sequences. Text is encoded as strict UTF-8 at the native boundary.
  • web_seeds: Web seed URLs encoded as strict UTF-8.
  • nodes: DHT bootstrap nodes as (host, port) pairs.
  • private: Optional private flag stored in the info dictionary. Changing it changes the info hash.
  • source: Optional source string stored in the info dictionary. Changing it changes the info hash.
  • comment: Optional top-level comment. Changing it does not change the info hash.
  • created_by: Explicit top-level creator text. When omitted, BTPC writes btpc/<version> unless omit_created_by is true.
  • omit_created_by: Suppress the default creator field. It cannot be combined with an explicit created_by value.
  • creation_date: Unix timestamp stored at the top level. Set a fixed value, commonly 0, for reproducible output.

Examples:

>>> from btpc import CreateOptions, TorrentMode
>>> options = CreateOptions(
...     mode=TorrentMode.HYBRID,
...     piece_length=16_384,
...     creation_date=0,
...     threads=1,
... )
>>> options.mode is TorrentMode.HYBRID
True

mode

mode: TorrentMode

piece_length

piece_length: int | None

threads

threads: int | None

trackers

trackers: Sequence[Sequence[str]]

web_seeds

web_seeds: Sequence[str]

nodes

nodes: Sequence[tuple[str, int]]

private

private: bool | None

source

source: str | None

comment

comment: str | None

created_by

created_by: str | None

omit_created_by

omit_created_by: bool

creation_date

creation_date: int | None

CreateResult

class CreateResult

Expose generated metainfo, hashes, and metrics without eager copies.

metainfo

metainfo: Metainfo

Return cached metainfo for inspection, editing, magnets, or verification.

The generated metainfo is parsed once on first access without copying it through Python bytes or reading the payload again.

Examples:

>>> from pathlib import Path
>>> from tempfile import TemporaryDirectory
>>> from btpc import create_bytes
>>> with TemporaryDirectory() as directory:
...     payload = Path(directory) / "hello.txt"
...     _ = payload.write_bytes(b"hello torrent\n")
...     result = create_bytes(payload)
...     assert result.metainfo.verify(payload).is_valid
...     assert result.metainfo is result.metainfo

bytes

bytes: bytes

Return generated metainfo bytes, copied on first request.

mode

mode: TorrentMode

Return the created protocol mode.

info_hash_v1

info_hash_v1: HashValue | None

Return the cached v1 info hash when applicable.

info_hash_v2

info_hash_v2: HashValue | None

Return the cached v2 info hash when applicable.

file_count

file_count: int

Return payload file count.

payload_bytes

payload_bytes: int

Return total payload bytes.

piece_count

piece_count: int

Return created piece count.

piece_length

piece_length: int

Return selected piece length.

piece_length_policy

piece_length_policy: str | None

Return automatic policy identifier for automatic selection.

metrics

metrics: CreateMetrics

Return cached phase timing metrics.

create

create(path: str | PathLike[str], destination: str | PathLike[str], *, options: CreateOptions | None = None, overwrite: bool = False, durable: bool = False, progress: Callable[[int, int, int], None] | None = None, cancellation: CancellationToken | None = None) -> CreateResult

Create canonical metainfo and atomically write a torrent file.

The destination is written through a temporary file and renamed into place. Existing files are rejected unless overwrite is true. With durable enabled, BTPC also requests file and parent-directory synchronization before returning.

Args:

  • path: Payload file or directory to traverse deterministically.
  • destination: Torrent file to create atomically.
  • options: Creation settings. None uses :class:CreateOptions defaults.
  • overwrite: Replace an existing destination when true.
  • durable: Request durable file and directory synchronization.
  • progress: Optional callback receiving ``(completed_bytes, total_bytes, completed_pieces)`. Callback exceptions propagate unchanged.
  • cancellation: Cooperative cancellation token.

Returns:

  • **The same result data returned by :**func:create_bytes.

Raises:

  • PathError: If payload or destination I/O fails, including a disallowed overwrite.
  • MetainfoError: If options or payload structure violate protocol rules.
  • CancelledError: If cancellation is requested.
  • Exception: Any exception raised by progress.

Examples:

>>> from pathlib import Path
>>> from tempfile import TemporaryDirectory
>>> from btpc import CreateOptions, create
>>> with TemporaryDirectory() as directory:
...     root = Path(directory)
...     payload = root / "hello.txt"
...     destination = root / "hello.torrent"
...     _ = payload.write_bytes(b"hello torrent\n")
...     result = create(
...         payload,
...         destination,
...         options=CreateOptions(creation_date=0, threads=1),
...     )
...     assert destination.read_bytes() == result.bytes

create_bytes

create_bytes(path: str | PathLike[str], *, options: CreateOptions | None = None, progress: Callable[[int, int, int], None] | None = None, cancellation: CancellationToken | None = None) -> CreateResult

Create canonical metainfo without writing a torrent file.

Args:

  • path: Payload file or directory to traverse deterministically.
  • options: Creation settings. None uses :class:CreateOptions defaults.
  • progress: Optional callback receiving ``(completed_bytes, total_bytes, completed_pieces)`. Exceptions raised by the callback propagate to the caller and stop creation.
  • cancellation: Cooperative cancellation token checked during traversal and hashing.

Returns:

The immutable result, including generated bytes, applicable info hashes, selected piece length, and phase metrics.

Raises:

  • PathError: If the payload cannot be traversed or read.
  • MetainfoError: If options or payload structure violate protocol rules.
  • CancelledError: If cancellation is requested.
  • Exception: Any exception raised by progress.

Examples:

>>> from pathlib import Path
>>> from tempfile import TemporaryDirectory
>>> from btpc import CreateOptions, create_bytes
>>> with TemporaryDirectory() as directory:
...     payload = Path(directory) / "hello.txt"
...     _ = payload.write_bytes(b"hello torrent\n")
...     result = create_bytes(
...         payload,
...         options=CreateOptions(creation_date=0, threads=1),
...     )
...     assert result.file_count == 1

Metainfo API

btpc.metainfo

Parse, inspect, validate, edit, serialize, and verify torrent metainfo.

BencodeDictionary

class BencodeDictionary(items: tuple[tuple[bytes, BencodeValue], ...])

Store an immutable bencode dictionary in canonical raw-key order.

Iteration yields keys in canonical order. Indexing, membership, get, keys, and values provide dictionary-style access. The items attribute remains an immutable tuple of entries for compatibility.

Attributes:

  • items: Unique raw-byte keys and recursive values.

items

items: tuple[tuple[bytes, BencodeValue], ...]

get

get(key: bytes, default: _DefaultValue | None = None) -> BencodeValue | _DefaultValue | None

Return the value for a raw key, or the supplied default when absent.

keys

keys() -> tuple[bytes, ...]

Return raw keys in canonical order.

values

values() -> tuple[BencodeValue, ...]

Return values in canonical key order.

BencodeList

class BencodeList(values: tuple[BencodeValue, ...])

Store an immutable ordered bencode list.

Attributes:

  • values: Recursive values in source order.

values

values: tuple[BencodeValue, ...]

BencodeValue

BencodeValue: TypeAlias

Metainfo

class Metainfo

Represent validated metainfo while preserving its exact source identity.

Parsing retains the original metainfo bytes and computes info hashes from the exact raw info dictionary slice, not from re-serialization. Equality also compares exact original bytes. Use write to save while preserving info hashes, to_bytes for canonical output, and original_bytes when byte-for-byte source identity is required. Raw protocol strings and paths remain bytes; optional *_text views decode only valid UTF-8.

Examples:

>>> from btpc import Metainfo, TorrentMode
>>> data = (
...     b"d4:infod6:lengthi1e4:name1:x12:piece lengthi16384e"
...     b"6:pieces20:00000000000000000000ee"
... )
>>> torrent = Metainfo.from_bytes(data)
>>> torrent.mode is TorrentMode.V1
True
>>> torrent.original_bytes == data
True

from_bytes

from_bytes(data: object, *, options: ParseOptions | None = None) -> Metainfo

Parse metainfo from a contiguous Python buffer.

Args:

  • data: bytes, bytearray, or another readable contiguous buffer. Non-contiguous memoryviews are rejected.
  • options: Parser input, allocation, and integer-digit limits.

Returns:

A validated immutable metainfo object retaining exact source bytes.

Raises:

  • TypeError: If data does not provide a contiguous buffer.
  • BencodeError: If bencode syntax is invalid.
  • MetainfoError: If decoded fields violate torrent protocol rules.
  • ResourceLimitError: If a configured parse limit is exceeded.

Examples:

>>> from btpc import Metainfo
>>> data = (
...     b"d4:infod6:lengthi1e4:name1:x12:piece lengthi16384e"
...     b"6:pieces20:00000000000000000000ee"
... )
>>> Metainfo.from_bytes(memoryview(data)).name
b'x'

read

read(path: str | PathLike[str], *, options: ParseOptions | None = None) -> Metainfo

Read and parse metainfo directly from a filesystem path.

Args:

  • path: Torrent file path.
  • options: Parser input, allocation, and integer-digit limits.

Returns:

A validated immutable metainfo object.

Raises:

  • PathError: If the file cannot be opened or read.
  • BencodeError: If bencode syntax is invalid.
  • MetainfoError: If decoded fields violate torrent protocol rules.
  • ResourceLimitError: If a configured parse limit is exceeded.

Examples:

>>> from pathlib import Path
>>> from tempfile import TemporaryDirectory
>>> from btpc import Metainfo
>>> data = (
...     b"d4:infod6:lengthi1e4:name1:x12:piece lengthi16384e"
...     b"6:pieces20:00000000000000000000ee"
... )
>>> with TemporaryDirectory() as directory:
...     path = Path(directory) / "sample.torrent"
...     _ = path.write_bytes(data)
...     assert Metainfo.read(path).original_bytes == data

verify

verify(payload: str | PathLike[str], *, fail_fast: bool = False, extra_files: bool = False, progress: Callable[[int, int, int], None] | None = None, cancellation: CancellationToken | None = None) -> PayloadVerificationReport

Verify payload files using every hash domain present in this torrent.

payload is resolved as the torrent’s payload root: a single-file torrent may point directly to its file, while a multi-file torrent points to the containing directory. v1 verifies the concatenated piece stream, v2 verifies per-file Merkle roots, and hybrid verifies both. Mismatches are returned in deterministic order; filesystem and policy failures raise exceptions.

Args:

  • payload: Payload file or root directory.
  • fail_fast: Stop after the first mismatch when true.
  • extra_files: Report files not represented by the torrent.
  • progress: Optional callback receiving ``(completed_bytes, total_bytes, completed_pieces)`. Callback exceptions propagate unchanged.
  • cancellation: Cooperative cancellation token.

Returns:

A report containing zero or more deterministic mismatches.

Raises:

  • PathError: If required payload paths cannot be read safely.
  • VerificationError: If verification cannot be performed as requested.
  • CancelledError: If cancellation is requested.
  • Exception: Any exception raised by progress.

Examples:

>>> from pathlib import Path
>>> from tempfile import TemporaryDirectory
>>> from btpc import CreateOptions, Metainfo, create_bytes
>>> with TemporaryDirectory() as directory:
...     payload = Path(directory) / "hello.txt"
...     _ = payload.write_bytes(b"hello torrent\n")
...     created = create_bytes(
...         payload,
...         options=CreateOptions(creation_date=0, threads=1),
...     )
...     assert Metainfo.from_bytes(created.bytes).verify(payload).is_valid

magnet

magnet(*, display_name: bool = True, trackers: bool = True, web_seeds: bool = True) -> str

Generate a deterministic magnet URI.

Args:

  • display_name: Include the decoded display name when valid UTF-8.
  • trackers: Include tracker URLs.
  • web_seeds: Include web seed URLs.

Returns:

A magnet URI containing every applicable v1 and v2 exact-topic hash.

Examples:

>>> from btpc import Metainfo
>>> data = (
...     b"d4:infod6:lengthi1e4:name1:x12:piece lengthi16384e"
...     b"6:pieces20:00000000000000000000ee"
... )
>>> magnet = Metainfo.from_bytes(data).magnet(trackers=False)
>>> magnet.startswith("magnet:?xt=")
True

edit

edit(*, trackers: Sequence[Sequence[str]] | None | UnchangedType = UNCHANGED, web_seeds: Sequence[str] | None | UnchangedType = UNCHANGED, nodes: Sequence[tuple[str, int]] | None | UnchangedType = UNCHANGED, private: bool | None | UnchangedType = UNCHANGED, source: str | None | UnchangedType = UNCHANGED, comment: str | None | UnchangedType = UNCHANGED, created_by: str | None | UnchangedType = UNCHANGED, creation_date: int | None | UnchangedType = UNCHANGED, raw_top_level: Mapping[bytes, BencodeValue] | None = None, file_attributes: dict[tuple[bytes, ...], bytes] | None = None) -> Metainfo

Return a validated copy with explicit preserve, remove, or set edits.

Optional fields use three states: :data:btpc.UNCHANGED preserves the current value, None removes it, and a typed value replaces it. Trackers, web seeds, nodes, comments, creator, and creation date are top-level fields and do not affect info hashes. private, source, and file attributes edit the info dictionary and therefore change applicable info hashes. Top-level-only edits retain the exact original info bytes, including a noncanonical source encoding. Use to_bytes for an explicit fully canonical serialization.

Args:

  • trackers: Tracker tiers, None to remove, or UNCHANGED.
  • web_seeds: Web seeds, None to remove, or UNCHANGED.
  • nodes: DHT nodes, None to remove, or UNCHANGED.
  • private: Private flag, None to remove, or UNCHANGED.
  • source: Source string, None to remove, or UNCHANGED.
  • comment: Comment, None to remove, or UNCHANGED.
  • created_by: Creator text, None to remove, or UNCHANGED.
  • creation_date: Unix timestamp, None to remove, or UNCHANGED.
  • raw_top_level: Raw recursive bencode extension replacements.
  • file_attributes: Raw attributes keyed by raw torrent path components.

Returns:

A newly validated immutable object retaining untouched info bytes.

Raises:

  • TypeError: If a textual field receives a non-string value.
  • MetainfoError: If an edit would produce invalid metainfo.

Examples:

>>> from btpc import UNCHANGED, Metainfo
>>> data = (
...     b"d4:infod6:lengthi1e4:name1:x12:piece lengthi16384e"
...     b"6:pieces20:00000000000000000000ee"
... )
>>> torrent = Metainfo.from_bytes(data)
>>> with_comment = torrent.edit(comment="reviewed")
>>> preserved = with_comment.edit(comment=UNCHANGED)
>>> removed = with_comment.edit(comment=None)
>>> preserved.info_hash_v1 == removed.info_hash_v1 == torrent.info_hash_v1
True
>>> torrent.edit(source="release").info_hash_v1 != torrent.info_hash_v1
True

mode

mode: TorrentMode

Return the validated protocol representation.

name

name: bytes

Return raw torrent name bytes.

piece_length

piece_length: int

Return piece length in bytes.

total_length

total_length: int

Return total payload bytes.

piece_count

piece_count: int

Return logical piece count.

files

files: tuple[TorrentFile, ...]

Return cached immutable payload file objects.

trackers

trackers: tuple[tuple[bytes, ...], ...]

Return cached immutable tracker tiers.

trackers_text

trackers_text: tuple[tuple[str, ...], ...] | None

Return decoded tracker tiers, or None if any URL is not UTF-8.

web_seeds

web_seeds: tuple[bytes, ...]

Return cached immutable web seeds.

web_seeds_text

web_seeds_text: tuple[str, ...] | None

Return decoded web seeds, or None if any URL is not UTF-8.

private

private: bool | None

Return the explicit private flag.

nodes

nodes: tuple[tuple[bytes, int], ...]

Return cached immutable DHT bootstrap nodes as raw hosts and ports.

nodes_text

nodes_text: tuple[tuple[str, int], ...] | None

Return decoded DHT nodes, or None if any host is not UTF-8.

source

source: bytes | None

Return raw source bytes from the info dictionary.

source_text

source_text: str | None

Return the source as UTF-8, or None when absent or undecodable.

comment

comment: bytes | None

Return raw top-level comment bytes.

comment_text

comment_text: str | None

Return the comment as UTF-8, or None when absent or undecodable.

created_by

created_by: bytes | None

Return raw top-level creator bytes.

created_by_text

created_by_text: str | None

Return creator text as UTF-8, or None when absent or undecodable.

creation_date

creation_date: int | None

Return the non-negative Unix creation timestamp.

info_hash_v1

info_hash_v1: HashValue | None

Return the cached v1 info hash when applicable.

info_hash_v2

info_hash_v2: HashValue | None

Return the cached v2 info hash when applicable.

original_bytes

original_bytes: bytes

Return exact source bytes, copied only on first request.

unknown_fields

unknown_fields: tuple[UnknownField, ...]

Return cached unknown fields with values and exact source encodings.

name_text

name_text: str | None

Return the name as UTF-8 when valid.

write

write(destination: str | PathLike[str], *, canonical: bool = False, overwrite: bool = False, durable: bool = False) -> None

Atomically save metainfo while preserving its info hashes by default.

Default output retains this object’s exact bytes, including edits already applied. canonical=True normalizes bencoding and can change info hashes when the source info dictionary is noncanonical. Payload files are not read.

Args:

  • destination: Torrent file to publish.
  • canonical: Normalize all bencoding when true. The default preserves this object’s encoding and torrent identity.
  • overwrite: Replace an existing destination when true.
  • durable: Request file and parent-directory synchronization.

Raises:

  • PathError: If publication fails or overwrite is disallowed. A directory synchronization failure can occur after complete publication.

Examples:

>>> from pathlib import Path
>>> from tempfile import TemporaryDirectory
>>> from btpc import Metainfo
>>> source = (
...     b"d4:infod6:lengthi00e4:name1:x12:piece lengthi16384e6:pieces0:ee"
... )
>>> torrent = Metainfo.from_bytes(source)
>>> with TemporaryDirectory() as directory:
...     path = Path(directory) / "reviewed.torrent"
...     torrent.edit(comment="reviewed").write(path)
...     assert Metainfo.read(path).info_hash_v1 == torrent.info_hash_v1

to_bytes

to_bytes(*, canonical: bool = True) -> bytes

Serialize canonical metainfo or return the exact original bytes.

The compatibility default is canonical output. For noncanonical source info this can change info hashes on reparse. Use canonical=False or write to preserve this object’s torrent identity.

Args:

  • canonical: Sort dictionary keys by unsigned raw-byte order and normalize bencode when true. False returns original_bytes unchanged.

Returns:

Serialized metainfo bytes.

Examples:

>>> from btpc import Metainfo
>>> data = (
...     b"d4:infod6:lengthi1e4:name1:x12:piece lengthi16384e"
...     b"6:pieces20:00000000000000000000ee"
... )
>>> torrent = Metainfo.from_bytes(data)
>>> torrent.to_bytes(canonical=False) == data
True
>>> reparsed = Metainfo.from_bytes(torrent.to_bytes())
>>> reparsed.info_hash_v1 == torrent.info_hash_v1
True

validation

validation: ValidationReport

Return the construction-time validation report.

validate

validate() -> ValidationReport

Return validation for compatibility; no new check is performed.

TorrentFile

class TorrentFile(length: int, path: tuple[bytes, ...], attributes: bytes, pieces_root: HashValue | None, is_padding: bool = False)

Describe one file in the torrent’s logical payload.

Attributes:

  • length: File length in bytes.
  • path: Raw torrent path components. Components remain bytes because torrent paths are not required to be UTF-8.
  • attributes: Raw BEP 47/BEP 52 file attribute bytes.
  • pieces_root: v2 Merkle root when present.
  • is_padding: Whether the entry is a padding file.

length

length: int

path

path: tuple[bytes, ...]

attributes

attributes: bytes

pieces_root

pieces_root: HashValue | None

is_padding

is_padding: bool

path_text

path_text: tuple[str, ...] | None

Return UTF-8 components when every component decodes.

torrent_path

torrent_path: TorrentPath

Return the raw-identity typed torrent path.

UnknownField

class UnknownField(key: bytes, value: BencodeValue, encoded: bytes, span: tuple[int, int])

Describe one unknown top-level field and its exact source encoding.

Attributes:

  • key: Raw top-level dictionary key.
  • value: Recursive semantic bencode value.
  • encoded: Exact source bytes covering the encoded key and value.
  • span: Half-open offsets for encoded within original_bytes.

key

key: bytes

value

value: BencodeValue

encoded

encoded: bytes

span

span: tuple[int, int]

ValidationReport

class ValidationReport(warnings: tuple[str, ...], canonical: bool, canonical_offset: int | None = None, canonical_message: str | None = None)

Summarize validation and canonical-encoding status.

Attributes:

  • warnings: Non-fatal protocol or interoperability warnings.
  • canonical: Whether the original source used canonical bencoding.
  • canonical_offset: Source offset of the first canonicalization issue.
  • canonical_message: Human-readable canonicalization issue.

warnings

warnings: tuple[str, ...]

canonical

canonical: bool

canonical_offset

canonical_offset: int | None

canonical_message

canonical_message: str | None

is_valid

is_valid: bool

Return true because invalid metainfo fails construction.

Verification API

btpc.verification

Verify payload files against v1 pieces and v2 Merkle roots.

MismatchKind

class MismatchKind

Stable payload verification mismatch category.

MISSING

WRONG_SIZE

EXTRA

UNSAFE_PATH

V1_HASH

V2_HASH

PayloadMismatch

class PayloadMismatch(kind: MismatchKind, path: Path, piece: int | None)

Describe one deterministic payload mismatch.

Attributes:

  • kind: Stable mismatch category.
  • path: Payload-relative filesystem path.
  • piece: Zero-based v1 piece index when the mismatch is piece-specific.

kind

kind: MismatchKind

path

path: Path

piece

piece: int | None

PayloadVerificationReport

class PayloadVerificationReport(mismatches: tuple[PayloadMismatch, ...])

Collect deterministic mismatches from a completed verification.

Attributes:

  • mismatches: Mismatches in deterministic path and piece order.

mismatches

mismatches: tuple[PayloadMismatch, ...]

is_valid

is_valid: bool

Return whether every enabled check passed.

verify

verify(metainfo: Metainfo, payload: str | PathLike[str], *, fail_fast: bool = False, extra_files: bool = False, progress: Callable[[int, int, int], None] | None = None, cancellation: CancellationToken | None = None) -> PayloadVerificationReport

Verify payload content against all hashes represented by metainfo.

payload points directly to a single-file payload or to the root directory of a multi-file torrent. v1 verification hashes the logical concatenated file stream; v2 verification checks each file’s Merkle root; hybrid torrents check both. Missing files, unsafe paths, and content mismatches are returned in the report. Operational I/O failures raise exceptions.

Relative roots use the current directory. Torrent paths must match actual directory entries exactly, including on case-insensitive filesystems, and must not cross symlinks. Verification keeps a bounded number of files open and checks saved file identity and state before and after hashing and before it reports successful verification. These checks do not provide an atomic filesystem snapshot. Imported v2 torrents can use BEP 52 piece lengths above the creation policy’s 16 MiB limit.

Args:

  • metainfo: Parsed torrent describing the expected payload.
  • payload: Payload file or root directory.
  • fail_fast: Stop after the first mismatch when true.
  • extra_files: Include files absent from the torrent as mismatches.
  • progress: Optional callback receiving ``(completed_bytes, total_bytes, completed_pieces)`. Callback exceptions propagate unchanged.
  • cancellation: Cooperative cancellation token.

Returns:

A report containing zero or more deterministic mismatches.

Raises:

  • PathError: If an operational filesystem read fails.
  • VerificationError: If verification cannot be completed under the selected policy.
  • CancelledError: If cancellation is requested.
  • Exception: Any exception raised by progress.

Examples:

>>> from pathlib import Path
>>> from tempfile import TemporaryDirectory
>>> from btpc import CreateOptions, Metainfo, create_bytes, verify
>>> with TemporaryDirectory() as directory:
...     payload = Path(directory) / "hello.txt"
...     _ = payload.write_bytes(b"hello torrent\n")
...     created = create_bytes(
...         payload,
...         options=CreateOptions(creation_date=0, threads=1),
...     )
...     torrent = Metainfo.from_bytes(created.bytes)
...     assert verify(torrent, payload).is_valid

Public Types API

btpc.types

Shared immutable values for protocol modes, hashes, bytes, and paths.

UNCHANGED

HashValue

class HashValue(bytes: bytes)

Store a protocol hash as immutable raw bytes.

Attributes:

  • bytes: Raw digest bytes.

bytes

bytes: bytes

hex

hex: str

Return lowercase hexadecimal text.

ParseOptions

class ParseOptions(max_total_input: int | None = None, max_owned_allocation: int | None = None, max_integer_digits: int | None = None)

Limit parser resource use before accepting owned metainfo.

None uses the core’s safe default for each limit. Limits apply equally to Metainfo.from_bytes and Metainfo.read.

Attributes:

  • max_total_input: Maximum encoded metainfo bytes accepted before parsing.
  • max_owned_allocation: Maximum cumulative allocation for the owned parse.
  • max_integer_digits: Maximum decimal digits in one bencoded integer.

Examples:

>>> from btpc import Metainfo, ParseOptions, ResourceLimitError
>>> options = ParseOptions(max_total_input=4)
>>> try:
...     Metainfo.from_bytes(b"d4:infoe", options=options)
... except ResourceLimitError as error:
...     assert error.limit == "total input"

max_total_input

max_total_input: int | None

max_owned_allocation

max_owned_allocation: int | None

max_integer_digits

max_integer_digits: int | None

TorrentBytes

class TorrentBytes(raw: bytes)

Preserve a torrent byte string without assuming text encoding.

Attributes:

  • raw: Exact protocol bytes.

raw

raw: bytes

text

text: str | None

Return UTF-8 text only when decoding is lossless.

TorrentMode

class TorrentMode

Validated BitTorrent protocol representation.

V1

V2

HYBRID

TorrentPath

class TorrentPath(components: tuple[TorrentBytes, ...])

Preserve and validate a torrent path as raw byte components.

Attributes:

  • components: Non-empty safe path components in torrent order.

components

components: tuple[TorrentBytes, ...]

text

text: tuple[str, ...] | None

Return decoded components only when every component is UTF-8.

to_path

to_path() -> Path | None

Return a platform path only when every component decodes safely.

Error API

btpc.errors

Catch structured errors from parsing, creation, editing, and verification.

BencodeError

class BencodeError

Report malformed bencode syntax or an invalid canonical encoding.

BtpcError

class BtpcError(message: str, *, offset: int | None = None, field: str | None = None, path: Path | None = None, limit: str | None = None, actual: int | None = None, maximum: int | None = None)

Base class for stable BTPC error categories.

Inspect the optional attributes instead of parsing the human-readable message. Only attributes relevant to a particular failure are populated.

Attributes:

  • offset: Encoded byte offset for syntax or canonicalization failures.
  • field: Protocol field associated with a validation failure.
  • path: Filesystem path associated with an I/O or safety failure.
  • limit: Resource-limit name.
  • actual: Observed resource value.
  • maximum: Configured maximum resource value.

offset

field

path

limit

actual

maximum

CancelledError

class CancelledError

Report cooperative cancellation requested through a cancellation token.

MetainfoError

class MetainfoError

Report torrent fields that violate v1, v2, or hybrid protocol rules.

PathError

class PathError

Report filesystem I/O, unsafe path, traversal, or destination failures.

ResourceLimitError

class ResourceLimitError

Configured parser or ownership resource limit was exceeded.

raise_exceeded

raise_exceeded(limit: str, actual: int, maximum: int) -> Never

Raise a structured limit error matching the native diagnostic.

UnsupportedError

class UnsupportedError

Report a requested feature or policy that BTPC does not support.

VerificationError

class VerificationError

Report an operational failure that prevents payload verification.

Rust API Guide

btpc-core is the protocol source of truth. It forbids unsafe Rust, preserves raw bytes, exposes structured errors, and uses per-operation configurable concurrency rather than an ambient global thread pool. The workspace MSRV is Rust 1.85.

Development documentation: The embedded btpc-core rustdoc is generated from the current main branch and may change before a stable release. The crate is prepared and validated for crates.io, but the owner has not performed the first publish, so no live docs.rs release page is claimed yet.

Use the crate source for implementation context and the executable consumer examples for independently compiled facade usage.

Quick Start

Parse and Inspect

#![allow(unused)]
fn main() {
extern crate btpc_core;
use btpc_core::{Metainfo, TorrentMode};

let bytes = b"d4:infod6:lengthi0e4:name5:empty12:piece lengthi16e6:pieces0:ee";
let torrent = Metainfo::from_bytes(bytes)?;
assert_eq!(torrent.mode(), TorrentMode::V1);
assert_eq!(torrent.original_bytes(), bytes);
assert!(torrent.validate().is_valid());
Ok::<(), btpc_core::Error>(())
}

Metainfo is owned and immutable. original_bytes() and write_original() retain source identity; to_bytes() and write_canonical() emit deterministic canonical bencode. Canonical bytes are materialized and cached only when one of those canonical-output methods is called, so ordinary inspection does not serialize the owned bencode tree. Source info hashes always use the exact raw info slice.

Multi-file path graphs reject duplicate and file/directory-prefix collisions. Hybrid inspection includes validated alignment padding in files() and exposes it through TorrentFile::is_padding(); callers that need only payload files should filter that flag explicitly.

Default loading applies conservative input, depth, item, byte-string, parser-tree, and owned-snapshot limits. Advanced callers can pass ParseOptions to Metainfo::from_bytes_with_options or Metainfo::from_path_with_options. Path loading preflights regular-file length and also caps the actual read, so growth races and non-regular streams cannot bypass max_total_input.

Callers that already own the input buffer can use Metainfo::from_vec or Metainfo::from_vec_with_options to transfer it into the parsed object without an additional input copy. The object retains those exact source bytes for original serialization and source info hashes.

The low-level bencode::Integer borrows the complete signed decimal bytes and supports fallible to_i64()/to_u64() conversion. OwnedValue::integer_bytes preserves arbitrary-precision canonical integers without a heavyweight arithmetic dependency; typed metainfo fields still reject values outside their protocol domain with field context. ParseLimits::with_max_integer_digits bounds hostile digit runs independently of numeric conversion.

Create

#![allow(unused)]
fn main() {
extern crate btpc_core;
use btpc_core::create::{
    CreateMode, CreateOptions, Creator, HashThreads, NoProgress, PieceLength,
};

let options = CreateOptions::builder()
    .mode(CreateMode::Hybrid)
    .piece_length(PieceLength::Exact(16_384))
    .hash_threads(HashThreads::Exact(1))
    .creation_date(0)
    .build()?;
let result = Creator::new("payload")
    .options(options)
    .create(&NoProgress)?;
assert!(result.info_hash_v1().is_some());
assert!(result.info_hash_v2().is_some());
Ok::<(), btpc_core::Error>(())
}

Creation scans deterministically, streams payload files with bounded memory, selects deterministic automatic piece lengths, and supports v1, v2, and hybrid. ManifestOptionsBuilder::include and exclude are UTF-8 glob APIs and reject a non-UTF-8 payload path when active. Use include_raw_paths and exclude_raw_paths for exact component-by-component byte matching. Top-level symlinks are rejected by default; skip also reports an error because it cannot produce a meaningful manifest, while follow retains the selected link name and still enforces nested escape and cycle checks.

Manifest entries snapshot file type, size, available timestamps, and device/inode on Unix or volume/file identity on Windows. Every hasher validates both the pathname and opened handle before and after reading. This detects replacement, rename-over-open, truncation/regrowth, and most concurrent mutation. Filesystems that reuse identity and restore every exposed timestamp can still defeat metadata snapshotting, so applications requiring a frozen tree should provide one at the filesystem or storage layer.

Use Creator::create_to_path for atomic output with an explicit overwrite policy. Creator::create_to_path_with_durability additionally accepts DurabilityPolicy::FileAndDirectory, which syncs the parent directory after publication where the platform supports directory syncing. Deny mode never replaces an existing path, including a symlink. Replace mode replaces the symlink entry rather than its target and preserves permissions when replacing a regular file. HashThreads::Exact(1) retains the sequential oracle; automatic and explicit parallel modes remain bounded to the operation.

Verify

#![allow(unused)]
fn main() {
extern crate btpc_core;
use btpc_core::Metainfo;
use btpc_core::create::NoProgress;
use btpc_core::verify::Verifier;

let torrent = Metainfo::from_path("payload.torrent")?;
let report = Verifier::new(&torrent, "payload").verify(&NoProgress)?;
assert!(report.is_valid());
Ok::<(), btpc_core::Error>(())
}

Verification reports deterministic mismatch values and checks every hash domain applicable to the mode. Symlinks are rejected rather than followed. Options select fail-fast behavior and extra-file reporting.

Progress, Cancellation, and Errors

Implement create::ProgressSink for presentation-independent progress snapshots. Clone create::CancellationToken into the caller and operation to request cooperative cancellation. Public functions return btpc_core::Result<T> with a structured ErrorCategory, contextual byte offset/field/path where applicable, and preserved I/O sources.

Layering

Applications should depend on btpc-core for protocol behavior. Do not duplicate bencode parsing, hashing, traversal, Merkle, canonicalization, or verification in adapters. The CLI and Python packages demonstrate the intended thin-wrapper model.

Stability And Features

The supported namespaces are the crate-root facade plus bencode, create, edit, magnet, metainfo, and verify. Private implementation modules remain private, and public signatures use standard-library or BTPC-owned types rather than exposing hashing, glob, CLI, Python, URL, or executor dependencies.

btpc-core currently has no optional behavior: its explicit default feature set is empty, and default, no-default-feature, and all-feature builds are equivalent. New features require a concrete dependency or platform benefit and must remain additive. tests/rust-consumer compiles documented facade paths as an independent crate. Pull requests run cargo-semver-checks against the base revision; release branches run scripts/check_rust_api.sh <accepted-tag-or-revision> before version publication.

Performance

BTPC measures creation, parsing, verification, and Python-boundary behavior with validated outputs before accepting timings. Performance claims record tool versions, hardware, dataset, cache state, elapsed distribution, throughput, and peak RSS.

The checked-in benchmark harness supports reproducible synthetic fixtures and a canonical Debian ISO preflight. Large cross-tool results are report-oriented rather than flaky per-commit gates. See the repository’s benchmark methodology for commands and accepted baselines.

Compatibility and Release Guide

Supported Development Matrix

  • Rust: current stable and MSRV 1.85.
  • Operating systems: Linux, macOS, and Windows.
  • Python: CPython 3.11, 3.12, 3.13, and 3.14.
  • Torrent modes: v1, v2 (BEP 52), and hybrid.

The matrix describes source and CI support. BTPC is pre-1.0 and no public package registry release has been made. Final wheels and native archives must pass clean artifact smoke tests before publication.

The release wheel targets are Linux x86-64/AArch64 with glibc 2.28 or later, macOS Intel/Apple Silicon, and Windows x86-64. The installation guide explains wheel selection and source-build requirements. The Python wheel contains the library; the CLI is distributed separately.

Python Runtime Model

BTPC currently requires the CPython global interpreter lock. The extension explicitly declares gil_used = true to PyO3, so a free-threaded CPython build may import it only with the GIL enabled. Long-running Rust work still releases the GIL, allowing independent BTPC operations to execute concurrently.

Subinterpreters are not supported. PyO3 0.29 rejects initialization from a second interpreter because extension-module state and cached Python objects are not yet per-interpreter. Use one main interpreter with threads or separate processes. Runtime callers can inspect btpc._native.__gil_required__ and btpc._native.__subinterpreters_supported__.

Interoperability

The committed fixture corpus covers mktorrent 1.1, mkbrr 1.23.0, torf-cli 5.2.1, and torrenttools 0.6.2 in every mode each tool supports. It also records malformed, noncanonical, and prior-fuzz cases with explicit accept/preserve/reject behavior. See the interoperability fixture documentation in the repository.

Stability

Before 1.0, Rust/Python APIs and CLI JSON schemas may evolve, but intentional compatibility changes must update specs, tests, documentation, and release notes. CLI exit categories and currently named JSON schema identifiers are tested. Unknown protocol byte strings remain byte-safe; text convenience views are best-effort only.

Artifact Expectations

Release wheels and archives must be produced from one verified version, install in clean environments, run all three torrent modes, inspect and magnetize output, and verify payloads. Publication should use least privilege and provenance. Normative requirements live in the release specification.

The manual workflow resolves a release tag to one commit before it starts checks and builds. It runs the full CI and documentation gates against that commit. Each wheel runs behavior checks and external Pyrefly/Pyright consumers in a fresh environment outside the checkout. A TestPyPI publication exercises the registry download and install path before the owner selects PyPI. See the release checklist for registry setup and publication steps.

The Cargo workspace package version is the single source. Start a release bump with make version VERSION=X.Y.Z, update CHANGELOG.md, then validate the tag with uv run python scripts/check_version.py --tag vX.Y.Z.

Performance Claims

No universal performance claim is valid without the canonical benchmark preflight, pinned competitor versions, repeated randomized measurements, raw result files, and machine/tool metadata. Reports must name limitations. See the benchmark documentation.

Optional metadata validation

BTPC preserves optional metadata as raw bytes while applying one domain policy in parse, create, edit, CLI, and Python surfaces. Empty tracker or web-seed URLs, empty tracker tiers, empty DHT hosts, DHT port 0, ports above 65535, malformed node pairs, non-integer or negative creation dates, and wrong bencode types for recognized text fields are errors. Ports 1 and 65535, duplicate non-empty values, empty text values, and non-UTF-8 text bytes are valid and lossless.

For compatibility, an explicitly empty announce-list is ignored in favor of a valid announce value and produces a validation warning. Creation never emits that warned form. An empty url-list means no web seeds and is accepted without a warning.

Repository security operations

Maintainers own dependency, CodeQL, Scorecard, secret-scanning, and private vulnerability alerts. The maintainer handling an alert records severity, affected versions, an owner, remediation plan, and disclosure decision in the corresponding private report or tracking issue.

GitHub settings checklist

  • Enable Dependabot alerts and security updates. Dependabot version updates remain review-only; do not enable automatic merging.
  • Enable private vulnerability reporting and publish SECURITY.md as the reporting policy.
  • Enable secret scanning and push protection, including alerts for contributors where the repository plan supports them.
  • Protect main with the required checks in CONTRIBUTING.md, required approving review, stale-review dismissal, conversation resolution, and current-branch enforcement. Disable force pushes and deletion.
  • Restrict workflow permissions to read-only by default and allow GitHub Actions to create pull requests only if a reviewed workflow later requires it.
  • Review CodeQL and Scorecard SARIF in code scanning. Assign every actionable alert before dismissal and document false-positive rationale.

Dependency exceptions

Dependency Review rejects high or critical vulnerabilities and dependencies outside the license allowlist shared with deny.toml. An exception requires maintainer approval in a dedicated issue containing the advisory/license, affected dependency, rationale, compensating controls, owner, remediation target, and an expiration date no more than 90 days away. The configuration change must link to that issue. Remove expired exceptions immediately; renewal requires a new review.

Coverage policy

Rust and Python coverage are informational during pre-1.0 development and are not merged into a misleading cross-language percentage. Artifacts retain the native LCOV and Coverage.py XML formats. A future threshold must be based on an accepted baseline and prevent regression in protocol-critical modules rather than reward low-value line coverage.

Contributing

Run the same focused gates used by CI before opening a pull request:

make check
make docs-check

Use make docs-serve to preview the project-subpath site. Handwritten pages live in docs/; generated Python and Rust API text comes from source docstrings and rustdoc, while CLI references come from the Clap model. Keep generated site/ output untracked and never hand-edit generated CLI files or HTML.

make docs-check enforces offline links and anchors, canonical URLs, required entry points, private-name protection, and budgets of 16,000,000 uncompressed bytes and 4,500,000 normalized gzip bytes. Budget increases require recorded artifact measurements.

Implementation contracts are maintained separately for contributors in the normative specifications. Public guides describe supported behavior rather than requirement identifiers.

Release checklist

BTPC has not been published to a package registry. Complete registry setup before the first publication. The workflow keeps publication disabled by default and selects TestPyPI as its default registry.

Registry setup

Sign in to the PyPI account that will own btpc. Enable two-factor authentication. Confirm that the project name is available or that the account can publish an existing project. For a new project, add a pending Trusted Publisher. For an existing project, add the publisher in its Publishing settings. Repeat this setup on TestPyPI; the two registries have separate accounts and publisher records.

Use these fields for each GitHub Actions publisher:

FieldPyPITestPyPI
Project namebtpcbtpc
Repository ownerburritothiefburritothief
Repository namebtpcbtpc
Workflow filenamerelease.ymlrelease.yml
Environment namepypitestpypi

Create the matching pypi and testpypi GitHub environments. Add a required maintainer reviewer and allow deployment only from version tags matching v*. For a sole maintainer, the reviewer must be able to approve their own run. With another available reviewer, enable prevention of self-review. Configure these rules before dispatching a publishing run. The workflow requests a short-lived OIDC credential; no PyPI API token belongs in repository secrets. Protect release version tags from updates and deletion.

See PyPI’s pending publisher guide and publisher usage guide. Record ownership and publisher setup privately. Do not publish account credentials.

Validate a release candidate

  1. Choose a version and update it with make version VERSION=X.Y.Z. Update the changelog and its release date when publication is scheduled. Confirm that the README identifies the actual publication state and installation commands. Refresh the local Python build with uv sync --refresh-package btpc --reinstall-package btpc before testing. This clears cached Python metadata from the previous Cargo workspace version.
  2. Run the required source gates in AGENTS.md, make docs-check, and cargo deny check. Confirm that the spec and generated CLI checks pass.
  3. Build a release wheel with uv run maturin build --release --locked --out dist. Run uv run python scripts/check_wheel.py dist/<wheel>.whl. This gate installs only that wheel in a fresh environment outside the checkout. It checks all torrent modes, edits, hash failures, progress, cancellation, native stub signatures, and external Pyrefly/Pyright consumers.
  4. Create a version-matching tag on the reviewed commit. Run Release artifacts with that tag and publish=false. Review every quality, documentation, wheel, sdist, CLI, packaged-crate, API, and assembled-artifact result. Each checkout must use the commit resolved by the version job.
  5. Inspect wheel/sdist metadata, license files, supported platform tags, CLI archives, the source archive, and SHA256SUMS. The Python wheel does not contain the CLI. Confirm that all artifacts use the same version.

Rehearse on TestPyPI

Dispatch the workflow from the release tag, with the matching tag input:

gh workflow run release.yml --ref vX.Y.Z -f tag=vX.Y.Z -f publish=true -f repository=testpypi

Approve the testpypi environment after reviewing the candidate. This run publishes Python artifacts and provenance. It does not publish the Rust crate or create a GitHub release. The registry check downloads the published CPython 3.11 Linux wheel from TestPyPI and repeats the clean installation checks. Review the TestPyPI project page, README rendering, files, and ownership.

You can also install it in a fresh environment:

python -m pip install --index-url https://test.pypi.org/simple/ --only-binary=:all: --no-deps btpc==X.Y.Z
python -c "import btpc; print(btpc.__version__)"

BTPC has no Python runtime dependencies. Use only the selected registry for this rehearsal. Uploaded files cannot be replaced, so use a new version for a changed candidate. See the TestPyPI guide.

Publish and inspect

After the TestPyPI rehearsal succeeds, dispatch the same tag with repository=pypi and publish=true. Leave publish_crate=false for a Python-only release. Approve the pypi environment. The workflow also prepares a draft GitHub release.

To publish btpc-core in the same run, select publish_crate=true. Configure the crates-io environment and its narrowly scoped CRATES_IO_TOKEN first. This optional job requests a separate approval and runs only for PyPI publication.

Confirm the production registry download check, project ownership, metadata, README rendering, complete wheel matrix, sdist, and provenance. Check installation with python -m pip install --only-binary=:all: btpc==X.Y.Z. Update documentation to describe registry pages as live only after publication succeeds. Review the draft release notes before making the GitHub release public.

Registry uploads cannot be replaced. If a released artifact is defective, publish a corrected version and consider yanking the defective version with an explicit reason. Do not delete release history or reuse a version number. A failed post-publication check does not roll back an upload.

Documentation and Rust crate checks

  • Run make docs-check from a clean checkout.
  • Confirm the production documentation exposes Getting Started, CLI, Python, and Rust entry points.
  • Verify repository, edit, issue, license, canonical, and sitemap links use the burritothief/btpc project and /btpc/ Pages subpath.
  • Confirm the current main branch documentation label remains visible. Do not describe it as versioned release documentation until a separate versioning policy is implemented.
  • Inspect the successful Documentation workflow and github-pages deployment URL for the release commit.
  • Smoke-test the custom 404, search index, local assets, and embedded rustdoc over HTTPS before publishing release notes.
  • Run scripts/check_crate_package.sh 1.85.0 and scripts/check_crate_package.sh 1.94.1, then inspect target/package/btpc-core-<version>.crate for README.md, LICENSE, sources, and examples/inspect.rs. The script downloads locked dependencies before testing the extracted package and an external consumer offline. Confirm that it also passes with an empty Cargo cache on the release runner.
  • Run cargo publish -p btpc-core --locked --dry-run and the Rust API compatibility check against the previous release tag when one exists. Install the CI-pinned checker with cargo install cargo-semver-checks --version 0.50.0 --locked. Run scripts/check_rust_api.sh v<previous-version> with Rust 1.94.1 before creating a tag. Check its result for both API changes and tool errors.
  • Protect the crates-io GitHub environment, configure its narrowly scoped CRATES_IO_TOKEN, and approve the manual release job only for an existing version-matching tag. Ordinary pushes never publish the crate.
  • For the first publish, confirm crate ownership and the resulting crates.io and docs.rs pages manually before adding links that describe either page as live.