Skip to content

Instantly share code, notes, and snippets.

@marcinczenko
Last active May 21, 2025 16:37
Show Gist options
  • Save marcinczenko/a87baf506bb0fd4bdff752cda4c685a1 to your computer and use it in GitHub Desktop.
Save marcinczenko/a87baf506bb0fd4bdff752cda4c685a1 to your computer and use it in GitHub Desktop.
A simple script to bencode BitTorrent info dictionary (v1, single file)
import itertools as it
import os
import json
from hashlib import sha1
def to_hex(b):
return [block.hex() for block in b]
def encode(obj):
"""
bencodes given object. Given object should be a int,
bytes, list or dict. If a str is given, it'll be
encoded as UTF-8.
>>> [encode(i) for i in (-2, 42, b"answer", b"")] \
== [b'i-2e', b'i42e', b'6:answer', b'0:']
True
>>> encode([b'a', 42, [13, 14]]) == b'l1:ai42eli13ei14eee'
True
>>> encode({b'bar': b'spam', b'foo': 42, b'mess': [1, b'c']}) \
== b'd3:bar4:spam3:fooi42e4:messli1e1:cee'
True
"""
if isinstance(obj, int):
return b"i" + str(obj).encode() + b"e"
elif isinstance(obj, bytes):
return str(len(obj)).encode() + b":" + obj
elif isinstance(obj, str):
return encode(obj.encode("utf-8"))
elif isinstance(obj, list):
return b"l" + b"".join(map(encode, obj)) + b"e"
elif isinstance(obj, dict):
if all(isinstance(i, bytes) for i in obj.keys()):
items = list(obj.items())
items.sort()
return b"d" + b"".join(map(encode, it.chain(*items))) + b"e"
else:
raise ValueError("dict keys should be bytes " + str(obj.keys()))
raise ValueError("Allowed types: int, bytes, list, dict; not %s", type(obj))
class Torrent:
data = {
"name": "data1M.bin",
"piece length": 262144,
"pieces": [
"111421FEBA308CD51E9ACF88417193A9EA60F0F84646",
"11143D4A8279853DA2DA355A574740217D446506E8EB",
"11141AD686B48B9560B15B8843FD00E7EC1B59624B09",
"11145015E7DA0C40350624C6B5A1FED1DB39720B726C"
],
"length": 1048576
}
announce = 'http://example.com/announce'
def __init__(self, path):
if path is not None:
path = os.path.normpath(path)
if os.path.exists(path):
with open(path, 'r') as f:
torrentJson = json.load(f)
self.data = torrentJson['info']
if 'announce' in torrentJson:
self.announce = torrentJson['announce']
print(f"Using values from file: {path}")
else:
print(f"File does not exist: {path}. Using example values:")
else:
print("No file provided. Using example values:")
# Convert to JSON string
json_data = json.dumps(self.data, indent=2)
print(json_data)
self.name = self.data['name']
self.piece_length = self.data['piece length']
self.pieces = self.data['pieces']
self.length = self.data['length']
# convert the pieces to bytes
self.pieces = [bytes.fromhex(h) for h in self.pieces]
# flatten the piece hashes into a single bytes object
self.pieces = bytes([byte for piece in self.pieces for byte in piece])
def createV1(self):
"""
Create a v1 metainfo dictionary.
:param tracker: tracker URL
:param hybrid: Also generate v1 fields for backwards compatibility
"""
info = {b'name': self.name, b'length': self.length, b'piece length': self.piece_length, b'pieces': self.pieces}
self.infov1 = info
return {b'announce': self.announce, b'info': info}
def info_hash_v1(self):
return sha1(encode(self.infov1)).hexdigest()
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(description='BitTorrent v1 b-encoder')
parser.add_argument('path', nargs='?',
default=None, help='JSON file info dictionary to be used - when not provided example values will be used')
args = parser.parse_args()
t = Torrent(args.path)
open(t.name + '.torrent', 'wb').write(encode(t.createV1()))
print("v1 infohash {0:s}".format(t.info_hash_v1()))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment