startup.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. import argparse
  2. import configparser
  3. import os
  4. import logging
  5. import platform
  6. import subprocess
  7. from urllib.parse import urlparse
  8. try:
  9. import pil
  10. import auth
  11. import logger
  12. import helpers
  13. import downloader
  14. import assembler
  15. import dlfuncs
  16. from constants import Constants
  17. except ImportError:
  18. from . import pil
  19. from . import auth
  20. from . import logger
  21. from . import helpers
  22. from . import downloader
  23. from . import assembler
  24. from . import dlfuncs
  25. from .constants import Constants
  26. def validate_inputs(config, args, unknown_args):
  27. error_arr = []
  28. try:
  29. config.read(pil.config_path)
  30. if args.download:
  31. pil.dl_user = args.download
  32. if args.downloadfollowing or args.batchfile:
  33. logger.banner()
  34. logger.warn("Please use only one download method. Use -h for more information.")
  35. logger.separator()
  36. return False
  37. elif not args.clean and not args.info and not args.assemble and not args.downloadfollowing and not args.batchfile:
  38. logger.banner()
  39. logger.error("Please use a download method. Use -h for more information.")
  40. logger.separator()
  41. return False
  42. if helpers.bool_str_parse(config.get('pyinstalive', 'log_to_file')) == "Invalid":
  43. pil.log_to_file = True
  44. error_arr.append(['log_to_file', 'True'])
  45. elif helpers.bool_str_parse(config.get('pyinstalive', 'log_to_file')):
  46. pil.log_to_file = True
  47. else:
  48. pil.log_to_file = False
  49. logger.banner()
  50. if args.batchfile:
  51. if os.path.isfile(args.batchfile):
  52. pil.dl_batchusers = [user.rstrip('\n') for user in open(args.batchfile)]
  53. if not pil.dl_batchusers:
  54. logger.error("The specified file is empty.")
  55. logger.separator()
  56. return False
  57. else:
  58. logger.info("Downloading {:d} users from batch file.".format(len(pil.dl_batchusers)))
  59. logger.separator()
  60. else:
  61. logger.error('The specified file does not exist.')
  62. logger.separator()
  63. return False
  64. if unknown_args:
  65. pil.uargs = unknown_args
  66. logger.warn("The following unknown argument(s) were provided and will be ignored: ")
  67. logger.warn(' ' + ' '.join(unknown_args))
  68. logger.separator()
  69. pil.ig_user = config.get('pyinstalive', 'username')
  70. pil.ig_pass = config.get('pyinstalive', 'password')
  71. pil.dl_path = config.get('pyinstalive', 'download_path')
  72. pil.run_at_start = config.get('pyinstalive', 'run_at_start')
  73. pil.run_at_finish = config.get('pyinstalive', 'run_at_finish')
  74. pil.ffmpeg_path = config.get('pyinstalive', 'ffmpeg_path')
  75. pil.args = args
  76. pil.config = config
  77. pil.proxy = config.get('pyinstalive', 'proxy', fallback=None)
  78. if args.configpath:
  79. pil.config_path = args.configpath
  80. if not os.path.isfile(pil.config_path):
  81. pil.config_path = os.path.join(os.getcwd(), "pyinstalive.ini")
  82. logger.warn("Custom config path is invalid, falling back to default path: {:s}".format(pil.config_path))
  83. logger.separator()
  84. if args.dlpath:
  85. pil.dl_path = args.dlpath
  86. if helpers.bool_str_parse(config.get('pyinstalive', 'show_cookie_expiry')) == "Invalid":
  87. pil.show_cookie_expiry = False
  88. error_arr.append(['show_cookie_expiry', 'False'])
  89. elif helpers.bool_str_parse(config.get('pyinstalive', 'show_cookie_expiry')):
  90. pil.show_cookie_expiry = True
  91. else:
  92. pil.show_cookie_expiry = False
  93. if helpers.bool_str_parse(config.get('pyinstalive', 'use_locks')) == "Invalid":
  94. pil.use_locks = False
  95. error_arr.append(['use_locks', 'False'])
  96. elif helpers.bool_str_parse(config.get('pyinstalive', 'use_locks')):
  97. pil.use_locks = True
  98. else:
  99. pil.use_locks = False
  100. if helpers.bool_str_parse(config.get('pyinstalive', 'clear_temp_files')) == "Invalid":
  101. pil.clear_temp_files = False
  102. error_arr.append(['clear_temp_files', 'False'])
  103. elif helpers.bool_str_parse(config.get('pyinstalive', 'clear_temp_files')):
  104. pil.clear_temp_files = True
  105. else:
  106. pil.clear_temp_files = False
  107. if helpers.bool_str_parse(config.get('pyinstalive', 'do_heartbeat')) == "Invalid":
  108. pil.do_heartbeat = True
  109. error_arr.append(['do_heartbeat', 'True'])
  110. if helpers.bool_str_parse(config.get('pyinstalive', 'do_heartbeat')):
  111. pil.do_heartbeat = True
  112. if args.noheartbeat or not helpers.bool_str_parse(config.get('pyinstalive', 'do_heartbeat')):
  113. pil.do_heartbeat = False
  114. logger.warn("Getting livestream heartbeat is disabled, this may cause degraded performance.")
  115. logger.separator()
  116. if not args.nolives and helpers.bool_str_parse(config.get('pyinstalive', 'download_lives')) == "Invalid":
  117. pil.dl_lives = True
  118. error_arr.append(['download_lives', 'True'])
  119. elif helpers.bool_str_parse(config.get('pyinstalive', 'download_lives')):
  120. pil.dl_lives = True
  121. else:
  122. pil.dl_lives = False
  123. if not args.noreplays and helpers.bool_str_parse(config.get('pyinstalive', 'download_replays')) == "Invalid":
  124. pil.dl_replays = True
  125. error_arr.append(['download_replays', 'True'])
  126. elif helpers.bool_str_parse(config.get('pyinstalive', 'download_replays')):
  127. pil.dl_replays = True
  128. else:
  129. pil.dl_replays = False
  130. if helpers.bool_str_parse(config.get('pyinstalive', 'download_comments')) == "Invalid":
  131. pil.dl_comments = True
  132. error_arr.append(['download_comments', 'True'])
  133. elif helpers.bool_str_parse(config.get('pyinstalive', 'download_comments')):
  134. pil.dl_comments = True
  135. else:
  136. pil.dl_comments = False
  137. if args.nolives:
  138. pil.dl_lives = False
  139. if args.noreplays:
  140. pil.dl_replays = False
  141. if not pil.dl_lives and not pil.dl_replays:
  142. logger.error("You have disabled both livestream and replay downloading.")
  143. logger.error("Please enable at least one of them and try again.")
  144. logger.separator()
  145. return False
  146. if pil.ffmpeg_path:
  147. if not os.path.isfile(pil.ffmpeg_path):
  148. pil.ffmpeg_path = None
  149. cmd = "where" if platform.system() == "Windows" else "which"
  150. logger.warn("Custom ffmpeg binary path is invalid, falling back to default path: {:s}".format(
  151. subprocess.check_output([cmd, 'ffmpeg']).decode('UTF-8').rstrip()))
  152. else:
  153. logger.binfo("Overriding ffmpeg binary path: {:s}".format(pil.ffmpeg_path))
  154. if not pil.ig_user or not len(pil.ig_user):
  155. raise Exception("Invalid value for 'username'. This value is required.")
  156. if not pil.ig_pass or not len(pil.ig_pass):
  157. raise Exception("Invalid value for 'password'. This value is required.")
  158. if not pil.dl_path.endswith('/'):
  159. pil.dl_path = pil.dl_path + '/'
  160. if not pil.dl_path or not os.path.exists(pil.dl_path):
  161. pil.dl_path = os.getcwd()
  162. if not args.dlpath:
  163. error_arr.append(['download_path', os.getcwd()])
  164. else:
  165. logger.warn("Custom config path is invalid, falling back to default path: {:s}".format(pil.dl_path))
  166. logger.separator()
  167. if pil.proxy and pil.proxy != '':
  168. parsed_url = urlparse(pil.proxy)
  169. if not parsed_url.netloc or not parsed_url.scheme:
  170. error_arr.append(['proxy', 'None'])
  171. pil.proxy = None
  172. if error_arr:
  173. for error in error_arr:
  174. logger.warn("Invalid value for '{:s}'. Using default value: {:s}".format(error[0], error[1]))
  175. logger.separator()
  176. if args.info:
  177. helpers.show_info()
  178. return False
  179. elif args.clean:
  180. helpers.clean_download_dir()
  181. return False
  182. elif args.assemble:
  183. pil.assemble_arg = args.assemble
  184. assembler.assemble()
  185. return False
  186. return True
  187. except Exception as e:
  188. logger.error("An error occurred: {:s}".format(str(e)))
  189. logger.error("Make sure the config file and given arguments are valid and try again.")
  190. logger.separator()
  191. return False
  192. def run():
  193. pil.initialize()
  194. logging.disable(logging.CRITICAL)
  195. config = configparser.ConfigParser()
  196. parser = argparse.ArgumentParser(
  197. description="You are running PyInstaLive {:s} using Python {:s}".format(Constants.SCRIPT_VER,
  198. Constants.PYTHON_VER))
  199. parser.add_argument('-u', '--username', dest='username', type=str, required=False,
  200. help="Instagram username to login with.")
  201. parser.add_argument('-p', '--password', dest='password', type=str, required=False,
  202. help="Instagram password to login with.")
  203. parser.add_argument('-d', '--download', dest='download', type=str, required=False,
  204. help="The username of the user whose livestream or replay you want to save.")
  205. parser.add_argument('-b,', '--batch-file', dest='batchfile', type=str, required=False,
  206. help="Read a text file of usernames to download livestreams or replays from.")
  207. parser.add_argument('-i', '--info', dest='info', action='store_true', help="View information about PyInstaLive.")
  208. parser.add_argument('-nr', '--no-replays', dest='noreplays', action='store_true',
  209. help="When used, do not check for any available replays.")
  210. parser.add_argument('-nl', '--no-lives', dest='nolives', action='store_true',
  211. help="When used, do not check for any available livestreams.")
  212. parser.add_argument('-cl', '--clean', dest='clean', action='store_true',
  213. help="PyInstaLive will clean the current download folder of all leftover files.")
  214. parser.add_argument('-cp', '--config-path', dest='configpath', type=str, required=False,
  215. help="Path to a PyInstaLive configuration file.")
  216. parser.add_argument('-dp', '--download-path', dest='dlpath', type=str, required=False,
  217. help="Path to folder where PyInstaLive should save livestreams and replays.")
  218. parser.add_argument('-as', '--assemble', dest='assemble', type=str, required=False,
  219. help="Path to json file required by the assembler to generate a video file from the segments.")
  220. parser.add_argument('-df', '--download-following', dest='downloadfollowing', action='store_true',
  221. help="PyInstaLive will check for available livestreams and replays from users the account "
  222. "used to login follows.")
  223. parser.add_argument('-nhb', '--no-heartbeat', dest='noheartbeat', action='store_true', help="Disable heartbeat "
  224. "check for "
  225. "livestreams.")
  226. # Workaround to 'disable' argument abbreviations
  227. parser.add_argument('--usernamx', help=argparse.SUPPRESS, metavar='IGNORE')
  228. parser.add_argument('--passworx', help=argparse.SUPPRESS, metavar='IGNORE')
  229. parser.add_argument('--infx', help=argparse.SUPPRESS, metavar='IGNORE')
  230. parser.add_argument('--noreplayx', help=argparse.SUPPRESS, metavar='IGNORE')
  231. parser.add_argument('--cleax', help=argparse.SUPPRESS, metavar='IGNORE')
  232. parser.add_argument('--downloadfollowinx', help=argparse.SUPPRESS, metavar='IGNORE')
  233. parser.add_argument('--configpatx', help=argparse.SUPPRESS, metavar='IGNORE')
  234. parser.add_argument('--confix', help=argparse.SUPPRESS, metavar='IGNORE')
  235. parser.add_argument('-cx', help=argparse.SUPPRESS, metavar='IGNORE')
  236. parser.add_argument('-nx', help=argparse.SUPPRESS, metavar='IGNORE')
  237. parser.add_argument('-dx', help=argparse.SUPPRESS, metavar='IGNORE')
  238. args, unknown_args = parser.parse_known_args() # Parse arguments
  239. if not os.path.exists(pil.config_path): # Create new config if it doesn't exist
  240. logger.banner()
  241. helpers.new_config()
  242. return
  243. if validate_inputs(config, args, unknown_args):
  244. if not args.username and not args.password:
  245. pil.ig_api = auth.authenticate(username=pil.ig_user, password=pil.ig_pass)
  246. elif (args.username and not args.password) or (args.password and not args.username):
  247. logger.warn("Missing --username or --password argument. Falling back to config file.")
  248. logger.separator()
  249. pil.ig_api = auth.authenticate(username=pil.ig_user, password=pil.ig_pass)
  250. elif args.username and args.password:
  251. pil.ig_api = auth.authenticate(username=args.username, password=args.password, force_use_login_args=True)
  252. if pil.ig_api:
  253. if pil.dl_user or pil.args.downloadfollowing:
  254. downloader.start()
  255. elif pil.dl_batchusers:
  256. if not helpers.command_exists("pyinstalive"):
  257. logger.error("PyInstaLive must be properly installed when using the -b argument.")
  258. logger.separator()
  259. else:
  260. dlfuncs.iterate_users(pil.dl_batchusers)