@ctrl/torrent-file
v4.5.1
Published
Parse a torrent file (name, hash, files, pieces)
Downloads
14,159
Readme
torrent-file 
Parse, inspect, and encode BitTorrent metainfo files.
Supports BitTorrent v1 (BEP 3), v2 (BEP 52), and hybrid torrent files containing both formats.
This project is based on parse-torrent and node-bencode. It includes a strict bencode encoder and decoder built around Uint8Array rather than Node.js Buffer.
Demo: https://torrent-file.pages.dev
Install
npm install @ctrl/torrent-fileAPI
info(file)
Returns display metadata, trackers, web seeds, and the detected version: 'v1', 'v2', or 'hybrid'.
import fs from 'fs';
import { info } from '@ctrl/torrent-file';
const torrentInfo = info(fs.readFileSync('myfile'));
console.log({ torrentInfo });files(file)
Returns file and piece information:
lengthis the sum of the listed file lengths.offsetis the file's byte offset in the protocol piece space. BEP 52 aligns every non-empty v2 file to a piece boundary, so v2 offsets can contain gaps that are not included inlength.piecescontains hexadecimal SHA-1 hashes for v1/hybrid torrents and is absent for v2-only torrents.piecesRootis a hexadecimal SHA-256 Merkle root on v2/hybrid files.pieceLayersmaps hexadecimal pieces roots to arrays of hexadecimal SHA-256 hashes.
Paths use the host platform's path separator. For v2 torrents, the advisory torrent name is prepended to the paths returned from the BEP 52 file tree.
import fs from 'fs';
import { files } from '@ctrl/torrent-file';
const torrentFiles = files(fs.readFileSync('myfile'));
console.log({ torrentFiles });hash(file)
Returns the v1 SHA-1 info hash. It throws for a v2-only torrent because that torrent does not have a v1 identity.
import fs from 'fs';
import { hash } from '@ctrl/torrent-file';
const torrentHash = hash(fs.readFileSync('myfile'));
console.log({ torrentHash });hashV2(file)
Returns the full 32-byte v2 SHA-256 info hash as hexadecimal. It throws for a v1-only torrent.
import fs from 'fs';
import { hashV2 } from '@ctrl/torrent-file';
const torrentHashV2 = hashV2(fs.readFileSync('myfile'));
console.log({ torrentHashV2 });hashes(file)
Returns the available v1 (SHA-1) and v2 (SHA-256) info hashes along with the detected torrent version. infoHash is present for v1 and hybrid torrents; infoHashV2 is present for v2 and hybrid torrents.
import fs from 'fs';
import { hashes } from '@ctrl/torrent-file';
const h = hashes(fs.readFileSync('myfile'));
console.log(h.version); // 'v1', 'v2', or 'hybrid'
console.log(h.infoHash); // SHA-1 (v1/hybrid only)
console.log(h.infoHashV2); // SHA-256 (v2/hybrid only)Info hashes are computed from the exact bencoded info dictionary bytes found in the file, as required by BEP 3 and BEP 52. They are not computed from a decode/encode round trip.
Low-level bencode
decode() returns bencoded byte strings as Uint8Array, including byte strings used as dictionary values. Decoded dictionaries have a null prototype, so keys such as __proto__, constructor, and toString are ordinary own properties rather than inherited JavaScript behavior. encode() accepts strings, Uint8Array, safe integers, arrays, and dictionaries.
The decoder rejects noncanonical or incomplete bencode, including unordered/duplicate dictionary keys, leading-zero integers, negative zero, unsafe integers, truncated values, and trailing data. The higher-level parsing and hashing functions use the same strict decoder and therefore throw on malformed input.
Dictionary keys can be arbitrary bytes. Decoded dictionaries retain enough internal information for encode(decode(data)) to preserve binary keys such as BEP 52 pieces roots. Newly constructed textual dictionary keys are UTF-8 encoded.
Encode
Convert a parsed torrent object back into a .torrent file buffer.
import fs from 'fs';
import { toTorrentFile } from '@ctrl/torrent-file';
// Minimal example: create a .torrent buffer from fields
const buf = toTorrentFile({
info: {
// Required info fields
'piece length': 16384,
pieces: new Uint8Array(/* 20-byte SHA1 hashes concatenated */),
name: 'example.txt',
length: 12345,
},
// Optional fields
announce: ['udp://tracker.publicbt.com:80/announce'],
urlList: ['https://example.com/example.txt'],
private: false,
created: new Date(),
createdBy: 'my-app/1.0.0',
comment: 'Generated by @ctrl/torrent-file',
});
fs.writeFileSync('example.torrent', buf);toTorrentFile() serializes supplied metadata; it does not read file contents, calculate piece hashes, build v2 Merkle trees, or insert hybrid padding files.
For v2 pieceLayers, dictionary keys are raw 32-byte pieces roots rather than hexadecimal text. When constructing them manually, represent each byte with one JavaScript character:
const rawDictionaryKey = (bytes: Uint8Array) => String.fromCharCode(...bytes);
const buf = toTorrentFile({
info: v2Info,
pieceLayers: {
[rawDictionaryKey(piecesRoot)]: concatenatedLayerHashes,
},
});The pieceLayers returned by files() is a display-friendly hexadecimal representation and is not the raw input shape accepted by toTorrentFile().
Validation scope
The parser validates canonical bencoding and the v1/v2 file-information structure it consumes, including v2 metadata version, piece size, file-tree shape, file lengths, pieces roots, and hash byte lengths.
It does not recompute file hashes, cryptographically verify piece layers against Merkle roots, or prove that the v1 and v2 sides of a hybrid torrent describe identical content. Applications downloading or trusting content must perform those integrity checks separately.
Demo
Run a local demo UI to drop a .torrent file and view parsed output:
pnpm install
pnpm demo:watchTo build the demo for static hosting:
pnpm demo:build