pythonpython-3.xlibtorrentlibtorrent-rasterbar

Downloading Multiple torrent files with Libtorrent in Python


I'm trying to write a torrent application that can take in a list of magnet links and then download them all together. I've been trying to read and understand the documentation at Libtorrent but I haven't been able to tell if what I try works or not. I've managed to be able to apply a SOCKS5 proxy to a Libtorrent session and download a single magnet link using this code:

import libtorrent as lt
import time
import os

ses = lt.session()
r = lt.proxy_settings()
r.hostname = "proxy_info"
r.username = "proxy_info"
r.password = "proxy_info"
r.port = 1080
r.type = lt.proxy_type_t.socks5_pw
ses.set_peer_proxy(r)
ses.set_web_seed_proxy(r)
ses.set_proxy(r)
t = ses.settings()
t.force_proxy = True
t.proxy_peer_connections = True
t.anonymous_mode = True
ses.set_settings(t)
print(ses.get_settings())
ses.peer_proxy()
ses.web_seed_proxy()
ses.set_settings(t)

magnet_link = "magnet"

params = {
"save_path": os.getcwd() + r"\torrents",
"storage_mode": lt.storage_mode_t.storage_mode_sparse,
"url": magnet_link
}

handle = lt.add_magnet_uri(ses, magnet_link, params)
ses.start_dht()

print('downloading metadata...')
while not handle.has_metadata():
    time.sleep(1)
    print('got metadata, starting torrent download...')
while handle.status().state != lt.torrent_status.seeding:
    s = handle.status()
    state_str = ['queued', 'checking', 'downloading metadata', 'downloading', 'finished', 'seeding', 'allocating']
    print('%.2f%% complete (down: %.1f kb/s up: %.1f kB/s peers: %d) %s' % (s.progress * 100, s.download_rate / 1000, s.upload_rate / 1000, s.num_peers, state_str[s.state]))
    time.sleep(5)

This is great and all for runing on its own with a single link. What I want to do is something like this:

def torrent_download(magnetic_link_list):
    for mag in range(len(magnetic_link_list)):
        handle = lt.add_magnet_uri(ses, magnetic_link_list[mag], params)

    #Then download all the files
    #Once all files complete, stop the torrents so they dont seed.

    return torrent_name_list

I'm not sure if this is even on the right track or not, but some pointers would be helpful.

UPDATE: This is what I now have and it works fine in my case

def magnet2torrent(magnet_link):
    global LIBTORRENT_SESSION, TORRENT_HANDLES
    if LIBTORRENT_SESSION is None and TORRENT_HANDLES is None:
        TORRENT_HANDLES = []
        settings = lt.default_settings()
        settings['proxy_hostname'] = CONFIG_DATA["PROXY"]["HOST"]
        settings['proxy_username'] = CONFIG_DATA["PROXY"]["USERNAME"]
        settings['proxy_password'] = CONFIG_DATA["PROXY"]["PASSWORD"]
        settings['proxy_port'] = CONFIG_DATA["PROXY"]["PORT"]
        settings['proxy_type'] = CONFIG_DATA["PROXY"]["TYPE"]
        settings['force_proxy'] = True
        settings['anonymous_mode'] = True

        LIBTORRENT_SESSION = lt.session(settings)

    params = {
        "save_path": os.getcwd() + r"/torrents",
        "storage_mode": lt.storage_mode_t.storage_mode_sparse,
        "url": magnet_link
        }

    TORRENT_HANDLES.append(LIBTORRENT_SESSION.add_torrent(params))


def check_torrents():
    global TORRENT_HANDLES
    for torrent in range(len(TORRENT_HANDLES)):
        print(TORRENT_HANDLES[torrent].status().is_seeding)

Solution

  • It's called "magnet links" (not magnetic).

    In new versions of libtorrent, the way you add a magnet link is:

    params = lt.parse_magnet_link(uri)
    handle = ses.add_torrent(params)
    

    That also gives you an opportunity to tweak the add_torrent_params object, to set the save directory for instance.

    If you're adding a lot of magnet links (or regular torrent files for that matter) and want to do it quickly, a faster way is to use:

    ses.add_torrent_async(params)
    

    That function will return immediately and the torrent_handle object can be picked up later in the add_torrent_alert.

    As for downloading multiple magnet links in parallel, your pseudo code for adding them is correct. You just want to make sure you either save off all the torrent_handle objects you get back or query all torrent handles once you're done adding them (using ses.get_torrents()). In your pseudo code you seem to overwrite the last torrent handle every time you add a new one.

    The condition you expressed for exiting was that all torrents were complete. The simplest way of doing that is simply to poll them all with handle.status().is_seeding. i.e. loop over your list of torrent handles and ask that. Keep in mind that the call to status() requires a round-trip to the libtorrent network thread, which isn't super fast.

    The faster way of doing this is to keep track of all torrents that aren't seeding yet, and "strike them off your list" as you get torrent_finished_alerts for torrents. (you get alerts by calling ses.pop_alerts()).

    Another suggestion I would make is to set up your settings_pack object first, then create the session. It's more efficient and a bit cleaner. Especially with regards to opening listen sockets and then immediately closing and re-opening them when you change settings.

    i.e.

    p = lt.settings_pack()
    p['proxy_hostname'] = '...'
    p['proxy_username'] = '...'
    p['proxy_password'] = '...'
    p['proxy_port'] = 1080
    p['proxy_type'] = lt.proxy_type_t.socks5_pw
    p['proxy_peer_connections'] = True
    
    ses = lt.session(p)