├── README.md ├── bencode.py └── example.py /README.md: -------------------------------------------------------------------------------- 1 | 2 | Python module / script to parse a torrent file to a python object. 3 | I just wrote this for fun one evening, and to learn a bit more about the torrent file format. 4 | 5 | See http://www.bittorrent.org/beps/bep_0003.html for more info. 6 | 7 | The decode method accepts a bytes object and returns a list. 8 | 9 | bencode.py is the module. 10 | example.py is an example script that loads a torrent file and prettyprints the python object to a file. 11 | 12 | Example: 13 | 14 | ```python 15 | >>> import bencode 16 | >>> 17 | >>> with open("archlinux-2013.04.01-dual.iso.torrent", 'rb') as fh: 18 | ... torrent_data = fh.read() 19 | ... 20 | >>> torrent = bencode.decode(torrent_data) 21 | >>> 22 | >>> print("announce: ", torrent[0][b'announce']) 23 | announce: b'http://tracker.archlinux.org:6969/announce' 24 | >>> print("name: ", torrent[0][b'info'][b'name']) 25 | name: b'archlinux-2013.04.01-dual.iso' 26 | >>> 27 | ``` 28 | -------------------------------------------------------------------------------- /bencode.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ Module to construct / parse bencoded data """ 3 | 4 | 5 | def parse_blist(bdata): 6 | """ Convert bencoded data to python list """ 7 | 8 | blist = [] 9 | 10 | if bdata[0:1] == b'l': 11 | bdata = bdata[1:] 12 | 13 | while bdata[0:1] != b'' and bdata[0:1] != b'e': 14 | 15 | parse_func = btype_dict.get(bdata[0:1], parse_bstring) 16 | elem, bdata = parse_func(bdata) 17 | 18 | blist.append(elem) 19 | 20 | return blist, bdata[1:] 21 | 22 | 23 | def parse_bdict(bdata): 24 | """ Convert bencoded data to python dict """ 25 | 26 | bdict = {} 27 | 28 | if bdata[0:1] == b'd': 29 | bdata = bdata[1:] 30 | 31 | while bdata[0:1] != b'' and bdata[0:1] != b'e': 32 | 33 | parse_func = btype_dict.get(bdata[0:1], parse_bstring) 34 | key, bdata = parse_func(bdata) 35 | 36 | if bdata[0:1] == '' or bdata[0:1] == 'e': 37 | value = None 38 | else: 39 | parse_func = btype_dict.get(bdata[0:1], parse_bstring) 40 | value, bdata = parse_func(bdata) 41 | 42 | if key in bdict: 43 | raise KeyError("Multiple keys in bencoded dictionary") 44 | 45 | bdict[key] = value 46 | 47 | return bdict, bdata[1:] 48 | 49 | 50 | def parse_bint(bdata): 51 | """ Convert bencoded data to int """ 52 | 53 | end_pos = bdata.index(ord('e')) 54 | num_str = bdata[1:end_pos] 55 | bdata = bdata[end_pos + 1:] 56 | 57 | return int(num_str), bdata 58 | 59 | 60 | def parse_bstring(bdata): 61 | """ Convert bencoded data to string """ 62 | 63 | delim_pos = bdata.index(ord(':')) 64 | length = bdata[0:delim_pos] 65 | length = int(length) 66 | 67 | delim_pos += 1 68 | bstring = bdata[delim_pos:delim_pos + length] 69 | bdata = bdata[delim_pos + length:] 70 | 71 | if len(bstring) != length: 72 | raise ValueError("Incorrect bencoded string length") 73 | 74 | return bstring, bdata 75 | 76 | 77 | def decode(bdata): 78 | """ Parse data and return a list of objects """ 79 | 80 | return parse_blist(bdata)[0] 81 | 82 | 83 | btype_dict = { 84 | b'd' : parse_bdict, 85 | b'l' : parse_blist, 86 | b'i' : parse_bint 87 | } 88 | 89 | -------------------------------------------------------------------------------- /example.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | import pprint 5 | import os 6 | import bencode 7 | 8 | 9 | if len(sys.argv) != 2: 10 | print("Usage: example.py torrentfile.torrent") 11 | 12 | with open(sys.argv[1], 'rb') as fh: 13 | torrent_bytes = fh.read() 14 | 15 | torrent = bencode.decode(torrent_bytes) 16 | outfilename = os.path.splitext(sys.argv[1])[0] + ".txt" 17 | 18 | with open(outfilename, 'w') as fh: 19 | fh.write(pprint.pformat(torrent)) 20 | 21 | 22 | --------------------------------------------------------------------------------