startup.py 16 KB

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