startup.py 16 KB

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