diff --git a/Magnet2Torrent.py b/Magnet2Torrent.py new file mode 100755 index 0000000..501e458 --- /dev/null +++ b/Magnet2Torrent.py @@ -0,0 +1,211 @@ +#!/usr/bin/env python3 +"""convert magnet link to torrent file. + +Created on Apr 19, 2012 @author: dan, Faless + GNU GENERAL PUBLIC LICENSE - Version 3 + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + http://www.gnu.org/licenses/gpl-3.0.txt + +""" + +import logging +import os.path as pt +import shutil +import sys +import tempfile +from argparse import ArgumentParser +from time import sleep +try: + from urllib.parse import unquote_plus +except ImportError: + from urllib import unquote_plus +import libtorrent as lt + + +class Magnet2Torrent(object): + """class for converter from magnet link to torrent.""" + + def __init__(self, magnet, output_name=None): + """init function. + + check for validity of the input. + + Raises: + ValueError: if input is not valid this error will be raise + """ + if (output_name and not pt.isdir(output_name) and + not pt.isdir(pt.dirname(pt.abspath(output_name)))): + raise ValueError("Invalid output folder: " + pt.dirname(pt.abspath(output_name))) + self.output_name = output_name + + self.tempdir = tempfile.mkdtemp() + self.ses = lt.session() + + params = { + 'url': magnet, + 'save_path': self.tempdir, + 'storage_mode': lt.storage_mode_t(2), + 'paused': False, + 'auto_managed': True, + 'duplicate_is_error': False + } + self.handle = self.ses.add_torrent(params) + + def run(self): + """run the converter. + + using the class attribute initiated at init function. + + Returns: + Filename of created torrent. + + Raises: + KeyboardInterrupt: This error caused by user to stop this. + When downloading metadata from magnet link, + it requires an additional step before the error reraised again. + """ + print("Downloading Metadata (this may take a while)") + + # used to control "Maybe..." and "or the" msgs after sleep(1) + wait_time = 1 + soft_limit = 120 + while not self.handle.has_metadata(): + try: + sleep(1) + if wait_time > soft_limit: + print("Downloading is taking a while, maybe there is an " + "issue with the magnet link or your network connection") + soft_limit += 30 + wait_time += 1 + except KeyboardInterrupt: + print("\nAborting...") + self.ses.pause() + print("Cleanup dir " + self.tempdir) + shutil.rmtree(self.tempdir) + raise + self.ses.pause() + print("Done") + + torinfo = self.handle.get_torrent_info() + torfile = lt.create_torrent(torinfo) + + output = pt.abspath(torinfo.name() + ".torrent") + + if self.output_name: + if pt.isdir(self.output_name): + output = pt.abspath(pt.join(self.output_name, + torinfo.name() + ".torrent")) + elif pt.isdir(pt.dirname(pt.abspath(self.output_name))): + output = pt.abspath(self.output_name) + else: + output = pt.abspath(torinfo.name() + ".torrent") + + print("Saving torrent file here : " + output + " ...") + with open(output, "wb") as outfile: + torcontent = lt.bencode(torfile.generate()) + outfile.write(torcontent) + + print("Saved! Cleaning up dir: " + self.tempdir) + self.ses.remove_torrent(self.handle) + shutil.rmtree(self.tempdir) + + return output + + +def open_default_app(filepath): + """open filepath with default application for each operating system.""" + import os + import subprocess + + if sys.platform.startswith('darwin'): + subprocess.call(('open', filepath)) + elif os.name == 'nt': + os.startfile(filepath) + elif os.name == 'posix': + subprocess.call(('xdg-open', filepath)) + + +def parse_args(args): + """parse some commandline arguments""" + description = ("A command line tool that converts " + "magnet links into .torrent files") + parser = ArgumentParser(description=description) + parser.add_argument('-m', '--magnet', help='The magnet url', required=True) + parser.add_argument('-o', '--output', help='The output torrent file name') + parser.add_argument('--rewrite-file', + help='Rewrite torrent file if it already exists(default)', + dest='rewrite_file', action='store_true') + parser.add_argument('--no-rewrite-file', + help='Create a new filename if torrent exists.', + dest='rewrite_file', action='store_false') + parser.set_defaults(rewrite_file=True) + parser.add_argument('--skip-file', help='Skip file if it already exists.', + dest='skip_file', action='store_true', default=False) + parser.add_argument('--open-file', help='Open file after converting.', + dest='open_file', action='store_true', default=False) + return parser.parse_args(args) + + +def main(): + """main function.""" + args = parse_args(sys.argv[1:]) + output_name = args.output + magnet = args.magnet + + # guess the name if output name is not given. + # in a magnet link it is between '&dn' and '&tr' + try: + if output_name is None: + output_name = magnet.split('&dn=')[1].split('&tr')[0] + output_name = unquote_plus(output_name) + output_name += '.torrent' + except IndexError: + logging.error('magnet: {}'.format(magnet)) + + # return if user wants to skip existing file. + if output_name is not None and pt.isfile(output_name) and args.skip_file: + print('File [{}] already exists.'.format(output_name)) + # still open file if file already exists. + if args.open_file: + open_default_app(output_name) + return + + # create fullname if file exists. + if output_name is not None and pt.isfile(output_name) and not args.rewrite_file: + new_output_name = output_name + counter = 1 + while pt.isfile(new_output_name): + non_basename, non_ext = pt.splitext(new_output_name) + if counter - 1 != 0: + non_basename = non_basename.rsplit('_{}'.format(counter - 1), 1)[0] + non_basename += '_{}'.format(counter) + new_output_name = '{}{}'.format(non_basename, non_ext) + counter += 1 + output_name = new_output_name + + # encode magnet link if it's url decoded. + if magnet != unquote_plus(magnet): + magnet = unquote_plus(magnet) + + conv = Magnet2Torrent(magnet, output_name) + conv.run() + + if args.open_file: + open_default_app(output_name) + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/Magnet_To_Torrent2.py b/Magnet_To_Torrent2.py deleted file mode 100755 index 67b04bf..0000000 --- a/Magnet_To_Torrent2.py +++ /dev/null @@ -1,144 +0,0 @@ -#!/usr/bin/env python -''' -Created on Apr 19, 2012 -@author: dan, Faless - - GNU GENERAL PUBLIC LICENSE - Version 3 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - - http://www.gnu.org/licenses/gpl-3.0.txt - -''' - -import shutil -import tempfile -import os.path as pt -import sys -import libtorrent as lt -from time import sleep -from argparse import ArgumentParser - - -def magnet2torrent(magnet, output_name=None): - if output_name and \ - not pt.isdir(output_name) and \ - not pt.isdir(pt.dirname(pt.abspath(output_name))): - print("Invalid output folder: " + pt.dirname(pt.abspath(output_name))) - print("") - sys.exit(0) - - tempdir = tempfile.mkdtemp() - ses = lt.session() - params = { - 'save_path': tempdir, - 'storage_mode': lt.storage_mode_t(2), - 'paused': False, - 'auto_managed': True, - 'duplicate_is_error': True - } - handle = lt.add_magnet_uri(ses, magnet, params) - - print("Downloading Metadata (this may take a while)") - while (not handle.has_metadata()): - try: - sleep(1) - except KeyboardInterrupt: - print("Aborting...") - ses.pause() - print("Cleanup dir " + tempdir) - shutil.rmtree(tempdir) - sys.exit(0) - ses.pause() - print("Done") - - torinfo = handle.get_torrent_info() - torfile = lt.create_torrent(torinfo) - - output = pt.abspath(torinfo.name() + ".torrent") - - if output_name: - if pt.isdir(output_name): - output = pt.abspath(pt.join( - output_name, torinfo.name() + ".torrent")) - elif pt.isdir(pt.dirname(pt.abspath(output_name))): - output = pt.abspath(output_name) - - print("Saving torrent file here : " + output + " ...") - torcontent = lt.bencode(torfile.generate()) - f = open(output, "wb") - f.write(lt.bencode(torfile.generate())) - f.close() - print("Saved! Cleaning up dir: " + tempdir) - ses.remove_torrent(handle) - shutil.rmtree(tempdir) - - return output - -def main(): - parser = ArgumentParser(description="A command line tool that converts magnet links in to .torrent files") - parser.add_argument('-m','--magnet', help='The magnet url') - parser.add_argument('-o','--output', help='The output torrent file name') - - # - # This second parser is created to force the user to provide - # the 'output' arg if they provide the 'magnet' arg. - # - # The current version of argparse does not have support - # for conditionally required arguments. That is the reason - # for creating the second parser - # - # Side note: one should look into forking argparse and adding this - # feature. - # - conditionally_required_arg_parser = ArgumentParser(description="A command line tool that converts magnet links in to .torrent files") - conditionally_required_arg_parser.add_argument('-m','--magnet', help='The magnet url') - conditionally_required_arg_parser.add_argument('-o','--output', help='The output torrent file name', required=True) - - magnet = None - output_name = None - - # - # Attempting to retrieve args using the new method - # - args = vars(parser.parse_known_args()[0]) - if args['magnet'] is not None: - magnet = args['magnet'] - argsHack = vars(conditionally_required_arg_parser.parse_known_args()[0]) - output_name = argsHack['output'] - if args['output'] is not None and output_name is None: - output_name = args['output'] - if magnet is None: - # - # This is a special case. - # This is when the user provides only the "output" args. - # We're forcing him to provide the 'magnet' args in the new method - # - print ('usage: {0} [-h] [-m MAGNET] -o OUTPUT'.format(sys.argv[0])) - print ('{0}: error: argument -m/--magnet is required'.format(sys.argv[0])) - sys.exit() - # - # Defaulting to the old of doing things - # - if output_name is None and magnet is None: - if len(sys.argv) >= 2: - magnet = sys.argv[1] - if len(sys.argv) >= 3: - output_name = sys.argv[2] - - magnet2torrent(magnet, output_name) - - -if __name__ == "__main__": - main() diff --git a/README.md b/README.md index 41f1aa8..19439ed 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,43 @@ # Magnet2Torrent -A command line tool that converts magnet links in to .torrent files. - -### This project is mostly abandoned. I will still merge most pull requests. +A command line tool that converts magnet links into .torrent files. ## Requirements -* python -* python-libtorrent (libtorrent-rasterbar version 0.16 or later) +* python2.7/3.5 +* python-libtorrent (libtorrent-rasterbar version 0.16 or later) for python 2.7 or + python3-libtorrent for python 3.5 + +### Install libtorrent-python on Mac + + brew install libtorrent-rasterbar --with-python + +### Install python-libtorrent on Ubuntu + sudo apt-get install python-libtorrent -y + +## Usage + + usage: Magnet2Torrent.py [-h] -m MAGNET [-o OUTPUT] [--rewrite-file] + [--no-rewrite-file] [--skip-file] [--open-file] + +A command line tool that converts magnet links into .torrent files + +Optional arguments: -## Install python-libtorrent on Ubuntu -`sudo apt-get install python-libtorrent -y` + -h, --help Show this help message and exit. + -m MAGNET, --magnet MAGNET + The magnet url. + -o OUTPUT, --output OUTPUT + The output torrent file name. + --rewrite-file Rewrite torrent file if it already exists (default). + --no-rewrite-file Create a new filename if torrent exists. + --skip-file Skip file if it already exists. + --open-file Open file after converting. -## Install python-libtorrent on macOS -`brew install libtorrent-rasterbar --with-python` +## Example usage -## How to Use -`python Magnet_To_Torrent2.py [torrent file]` + python MagnetToTorrent.py -m [torrent file] -### Example -`python Magnet_To_Torrent2.py -m "magnet:?xt=urn:btih:49fbd26322960d982da855c54e36df19ad3113b8&dn=ubuntu-12.04-desktop-i386.iso&tr=udp%3A%2F%2Ftracker.openbittorrent.com" -o ubunut12-04.iso` + python Magnet2Torrent.py -m "magnet:?xt=urn:btih:49fbd26322960d982da855c54e36df19ad3113b8&dn=ubuntu-12.04-desktop-i386.iso&tr=udp%3A%2F%2Ftracker.openbittorrent.com" -o ubunut12-04.iso ## Licenses All code is licensed under the [GPL version 3](http://www.gnu.org/licenses/gpl.html)