Advertisement
Guest User

Untitled

a guest
Nov 25th, 2018
271
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 27.53 KB | None | 0 0
  1. #!/usr/bin/env python
  2.  
  3. import logging
  4. import sys
  5. import os
  6. import time
  7. import datetime
  8. import argparse
  9. import getpass
  10. import json
  11. import threading
  12. import webbrowser
  13. import shutil
  14. import subprocess
  15. import requests
  16. import random
  17. from socket import timeout, error as SocketError
  18. from ssl import SSLError
  19. from string import Formatter as StringFormatter
  20. import glob
  21. from win10toast import ToastNotifier
  22. import tweepy
  23.  
  24. try:
  25.     # py2
  26.     from urllib2 import URLError
  27.     from httplib import HTTPException
  28.     from ConfigParser import SafeConfigParser
  29. except ImportError:
  30.     # py3
  31.     from urllib.error import URLError
  32.     from http.client import HTTPException
  33.     from configparser import SafeConfigParser
  34.  
  35. from instagram_private_api import (
  36.     Client, ClientError, ClientCookieExpiredError, ClientLoginRequiredError
  37. )
  38. from instagram_private_api_extensions.live import (
  39.     Downloader, logger as dash_logger
  40. )
  41. from instagram_private_api_extensions.replay import (
  42.     Downloader as ReplayDownloader, logger as replay_dash_logger
  43. )
  44.  
  45. from .utils import (
  46.     Formatter, UserConfig, check_for_updates,
  47.     to_json, from_json, generate_safe_path
  48. )
  49. from .comments import CommentsDownloader
  50.  
  51.  
  52. __version__ = '0.3.8'
  53.  
  54. USERNAME_ENV_KEY = 'IG_LOGIN_USERNAME'
  55. PASSWORD_ENV_KEY = 'IG_LOGIN_PASSWORD'
  56.  
  57. CONSUMER_KEY = "NQRIjCsVAvtU88tgx8MUJrOeh"
  58. CONSUMER_SECRET = "u91mOSzlOy94PFnatbKuWKFciZjcZGO2o6P4Uszwaq82dVfPu2"
  59. ACCESS_TOKEN = "1060871428113743873-KL70ApjIzpnNOUnRzvKe094IbgBSXO"
  60. ACCESS_TOKEN_SECRET = "nwTaOMjLdCeNo2b8bgMKWcuoNt1rb6Q8JgU3Zvod4bvDK"
  61.  
  62. logger = logging.getLogger(__file__)
  63. ch = logging.StreamHandler()
  64. ch.setLevel(logging.DEBUG)
  65. formatter = Formatter()
  66. ch.setFormatter(formatter)
  67. logger.addHandler(ch)
  68. dash_logger.addHandler(ch)
  69. replay_dash_logger.addHandler(ch)
  70.  
  71. api_logger = logging.getLogger('instagram_private_api')
  72. api_logger.addHandler(ch)
  73.  
  74. rule_line = '-' * 80
  75.  
  76.  
  77. def onlogin_callback(api, new_settings_file):
  78.     # saved auth cookies on login
  79.     cache_settings = api.settings
  80.     with open(new_settings_file, 'w') as outfile:
  81.         json.dump(cache_settings, outfile, indent=2, default=to_json)
  82.         logger.debug('Saved settings: %s' % new_settings_file)
  83.  
  84.  
  85. def check_ffmpeg(binary_path):
  86.     ffmpeg_binary = binary_path or os.getenv('FFMPEG_BINARY', 'ffmpeg')
  87.     cmd = [
  88.         ffmpeg_binary, '-version']
  89.     logger.debug('Executing: "%s"' % ' '.join(cmd))
  90.     exit_code = subprocess.call(cmd)
  91.     logger.debug('Exit code: %s' % exit_code)
  92.  
  93.  
  94. def is_replay(broadcast):
  95.     return broadcast['broadcast_status'] == 'post_live' or 'dash_playback_url' not in broadcast
  96.  
  97.  
  98. def generate_filename_prefix(broadcast, userconfig):
  99.     if is_replay(broadcast):
  100.         broadcast_start = datetime.datetime.fromtimestamp(broadcast['published_time'])
  101.         broadcast_type = 'replay'
  102.     else:
  103.         broadcast_start = datetime.datetime.now()
  104.         broadcast_type = 'live'
  105.     format_args = {
  106.         'year': broadcast_start.strftime('%Y'),
  107.         'month': broadcast_start.strftime('%m'),
  108.         'day': broadcast_start.strftime('%d'),
  109.         'hour': broadcast_start.strftime('%H'),
  110.         'minute': broadcast_start.strftime('%M'),
  111.         'username': broadcast['broadcast_owner']['username'],
  112.         'broadcastid': broadcast['id'],
  113.         'broadcasttype': broadcast_type,
  114.     }
  115.     user_format_keys = StringFormatter().parse(userconfig.filenameformat)
  116.     invalid_user_format_keys = [
  117.         i[1] for i in user_format_keys if i[1] not in format_args.keys()]
  118.     if invalid_user_format_keys:
  119.         logger.error(
  120.             'Invalid filename format parameters: %s'
  121.             % ', '.join(invalid_user_format_keys))
  122.         exit(10)
  123.     filename_prefix = userconfig.filenameformat.format(**format_args)
  124.     return filename_prefix
  125.  
  126.  
  127. def run():
  128.  
  129.     description = ('INSTAGRAM LIVESTREAM DOWNLOADER (v%s) [python=%s.%s.%s,%s]'
  130.                    % (__version__,
  131.                       sys.version_info.major, sys.version_info.minor, sys.version_info.micro,
  132.                       sys.platform))
  133.  
  134.     config_section = 'livestream_dl'
  135.     cfgparser = None
  136.     if os.path.exists('livestream_dl.cfg'):
  137.         # read config path
  138.         cfgparser = SafeConfigParser()
  139.         cfgparser.read('livestream_dl.cfg')
  140.  
  141.     parser = argparse.ArgumentParser(
  142.         description=description,
  143.         epilog='Release: v%s / %s / %s' % (__version__, sys.platform, sys.version))
  144.     parser.add_argument('instagram_user', nargs='?')
  145.     parser.add_argument('-settings', dest='settings', type=str,
  146.                         help='File path to save settings.json')
  147.     parser.add_argument('-username', '-u', dest='username', type=str,
  148.                         help='Login user name. Required if %s env var not set.'
  149.                              % USERNAME_ENV_KEY)
  150.     parser.add_argument('-password', '-p', dest='password', type=str, required=False,
  151.                         help='Login password. Can be set via %s env var.'
  152.                              % PASSWORD_ENV_KEY)
  153.     parser.add_argument('-outputdir', '-o', dest='outputdir',
  154.                         help='Output folder path.')
  155.     parser.add_argument('-commenters', metavar='COMMENTER_ID', dest='commenters', nargs='*',
  156.                         help='List of numeric IG user IDs to collect comments from.')
  157.     parser.add_argument('-collectcomments', action='store_true',
  158.                         help='Collect comments from verified users.')
  159.     parser.add_argument('-nocleanup', action='store_true',
  160.                         help='Do not clean up temporary downloaded/generated files.')
  161.     parser.add_argument('-openwhendone', action='store_true',
  162.                         help='Automatically open movie file when completed.')
  163.     parser.add_argument('-mpdtimeout', dest='mpdtimeout', type=int,
  164.                         help='Set timeout interval in seconds for mpd download. Default %d.'
  165.                              % Downloader.MPD_DOWNLOAD_TIMEOUT)
  166.     parser.add_argument('-downloadtimeout', dest='downloadtimeout', type=int,
  167.                         help='Set timeout interval in seconds for segments download. Default %d.'
  168.                              % Downloader.DOWNLOAD_TIMEOUT)
  169.     parser.add_argument('-ffmpegbinary', dest='ffmpegbinary', type=str,
  170.                         help='Custom path to ffmpeg binary.')
  171.     parser.add_argument('-skipffmpeg', dest='skipffmpeg', action='store_true',
  172.                         help='Don\'t assemble file with ffmpeg.')
  173.     parser.add_argument('-verbose', dest='verbose', action='store_true',
  174.                         help='Enable verbose debug messages.')
  175.     parser.add_argument('-log', dest='log',
  176.                         help='Log to file specified.')
  177.     parser.add_argument('-filenameformat', dest='filenameformat', type=str,
  178.                         help='Custom filename format.')
  179.     parser.add_argument('-noreplay', dest='noreplay', action='store_true',
  180.                         help='Do not download replay streams.')
  181.     parser.add_argument('-ignoreconfig', dest='ignoreconfig', action='store_true',
  182.                         help='Ignore the livestream_dl.cfg file.')
  183.     parser.add_argument('-version', dest='version_check', action='store_true',
  184.                         help='Show current version and check for new updates.')
  185.     argparser = parser.parse_args()
  186.  
  187.     # if not a version check or downloading for a selected user
  188.     if not (argparser.instagram_user or argparser.version_check):
  189.         parser.parse_args(['-h'])
  190.         exit()
  191.  
  192.     if argparser.ignoreconfig:
  193.         cfgparser = None
  194.         logger.debug('Ignoring config file.')
  195.  
  196.     default_config = {
  197.         'outputdir': 'downloaded',
  198.         'commenters': [],
  199.         'collectcomments': False,
  200.         'nocleanup': False,
  201.         'openwhendone': False,
  202.         'mpdtimeout': Downloader.MPD_DOWNLOAD_TIMEOUT,
  203.         'downloadtimeout': Downloader.DOWNLOAD_TIMEOUT,
  204.         'verbose': False,
  205.         'skipffmpeg': False,
  206.         'ffmpegbinary': None,
  207.         'filenameformat': '{year}{month}{day}_{username}_{broadcastid}_{broadcasttype}',
  208.     }
  209.     userconfig = UserConfig(
  210.         config_section, defaults=default_config,
  211.         argparser=argparser, configparser=cfgparser)
  212.  
  213.     if userconfig.verbose:
  214.         logger.setLevel(logging.DEBUG)
  215.         api_logger.setLevel(logging.DEBUG)
  216.         dash_logger.setLevel(logging.DEBUG)
  217.         replay_dash_logger.setLevel(logging.DEBUG)
  218.     else:
  219.         logger.setLevel(logging.INFO)
  220.         dash_logger.setLevel(logging.INFO)
  221.         replay_dash_logger.setLevel(logging.INFO)
  222.  
  223.     if userconfig.log:
  224.         file_handler = logging.FileHandler(userconfig.log)
  225.         file_handler.setFormatter(logging.Formatter('%(asctime)s %(levelname)s: %(message)s'))
  226.         logger.addHandler(file_handler)
  227.         dash_logger.addHandler(file_handler)
  228.         replay_dash_logger.addHandler(file_handler)
  229.         api_logger.addHandler(file_handler)
  230.  
  231.     logger.info(description)
  232.  
  233.     if userconfig.verbose:
  234.         check_ffmpeg(userconfig.ffmpegbinary)
  235.  
  236.     if argparser.version_check:
  237.         message = check_for_updates(__version__)
  238.         if message:
  239.             logger.warning(message)
  240.         else:
  241.             logger.info('[i] No new version found.')
  242.  
  243.     logger.info('=-' * 40)
  244.  
  245.     if not argparser.instagram_user:
  246.         exit()
  247.  
  248.     user_username = userconfig.username or os.getenv(USERNAME_ENV_KEY)
  249.     if not user_username:
  250.         logger.error('No login username specified.')
  251.         exit(9)
  252.  
  253.     user_password = (userconfig.password or os.getenv(PASSWORD_ENV_KEY) or
  254.                      getpass.getpass(
  255.                          prompt='Type in the password for %s and press "Enter" '
  256.                                 '\n(Your password will not show on screen): '
  257.                                 % user_username))
  258.     settings_file_path = userconfig.settings or ('%s.json' % user_username)
  259.  
  260.     # don't use default device profile
  261.     custom_device = {
  262.         'phone_manufacturer': 'samsung',
  263.         'phone_model': 'hero2lte',
  264.         'phone_device': 'SM-G935F',
  265.         'android_release': '6.0.1',
  266.         'android_version': 23,
  267.         'phone_dpi': '640dpi',
  268.         'phone_resolution': '1440x2560',
  269.         'phone_chipset': 'samsungexynos8890'
  270.     }
  271.  
  272.     api = None
  273.     try:
  274.         if not os.path.isfile(settings_file_path):
  275.             # login afresh
  276.             api = Client(
  277.                 user_username, user_password,
  278.                 on_login=lambda x: onlogin_callback(x, settings_file_path),
  279.                 **custom_device)
  280.         else:
  281.             # reuse cached auth
  282.             with open(settings_file_path) as file_data:
  283.                 cached_settings = json.load(file_data, object_hook=from_json)
  284.  
  285.             # always use latest app ver, sig key, etc from lib
  286.             for key in ('app_version', 'signature_key', 'key_version', 'ig_capabilities'):
  287.                 cached_settings.pop(key, None)
  288.             api = Client(
  289.                 user_username, user_password,
  290.                 settings=cached_settings,
  291.                 **custom_device)
  292.  
  293.     except (ClientCookieExpiredError, ClientLoginRequiredError) as e:
  294.         logger.warning('ClientCookieExpiredError/ClientLoginRequiredError: %s' % e)
  295.         api = Client(
  296.             user_username, user_password,
  297.             on_login=lambda x: onlogin_callback(x, settings_file_path),
  298.             **custom_device)
  299.  
  300.     except ClientError as e:
  301.         logger.error('ClientError %s (Code: %d, Response: %s)' % (e.msg, e.code, e.error_response))
  302.         exit(9)
  303.  
  304.     except Exception as e:
  305.         logger.error('Unexpected Exception: %s' % e)
  306.         exit(99)
  307.  
  308.     if not api:
  309.         logger.error('Unable to init api client')
  310.         exit(99)
  311.  
  312.     if user_username != api.authenticated_user_name:
  313.         logger.warning(
  314.             'Authenticated username mismatch: %s vs %s'
  315.             % (user_username, api.authenticated_user_name))
  316.  
  317.     retry_attempts = 2
  318.     res = {}
  319.     ig_user_id = ''
  320.     for i in range(1, 1 + retry_attempts):
  321.         try:
  322.             # Alow user to save an api call if they directly specify the IG numeric user ID
  323.             if argparser.instagram_user.isdigit():
  324.                 # is a numeric IG user ID
  325.                 ig_user_id = argparser.instagram_user
  326.             else:
  327.                 # regular ig user name
  328.                 user_res = api.username_info(argparser.instagram_user)
  329.                 ig_user_id = user_res['user']['pk']
  330.  
  331.             res = api.user_story_feed(ig_user_id)
  332.             break
  333.  
  334.         except ClientLoginRequiredError as e:
  335.             if i < retry_attempts:
  336.                 # Probably because user has changed password somewhere else
  337.                 logger.warning('ClientLoginRequiredError. Logging in again...')
  338.                 api = Client(
  339.                     user_username, user_password,
  340.                     on_login=lambda x: onlogin_callback(x, settings_file_path),
  341.                     **custom_device)
  342.             else:
  343.                 raise e
  344.  
  345.         except (SSLError, timeout, URLError, HTTPException, SocketError) as e:
  346.             if i < retry_attempts:
  347.                 logger.warning(str(e))
  348.                 time.sleep(userconfig.downloadtimeout)
  349.             else:
  350.                 logger.error(str(e))
  351.                 exit(99)
  352.  
  353.     if not res.get('broadcast') and (
  354.             userconfig.noreplay or
  355.             not res.get('post_live_item', {}).get('broadcasts')):
  356.         logger.info('No broadcast from %s' % ig_user_id)
  357.         exit(0)
  358.  
  359.     if res.get('broadcast'):
  360.         broadcasts = [res['broadcast']]
  361.     else:
  362.         broadcasts = res['post_live_item']['broadcasts']
  363.  
  364.     for broadcast in broadcasts:
  365.         if broadcast['broadcast_status'] not in ['active', 'post_live']:
  366.             # Usually because it's interrupted
  367.             logger.warning('Broadcast status is currently: %s' % broadcast['broadcast_status'])
  368.  
  369.         # check if output dir exists, create if otherwise
  370.         if not os.path.exists(userconfig.outputdir):
  371.             os.makedirs(userconfig.outputdir)
  372.  
  373.         is_replay_broadcast = is_replay(broadcast)
  374.  
  375.         download_start_time = int(time.time())
  376.         filename_prefix = generate_filename_prefix(broadcast, userconfig)
  377.  
  378.         # dash_abr_playback_url has the higher def stream
  379.         mpd_url = (broadcast.get('dash_manifest')
  380.                    or broadcast.get('dash_abr_playback_url')
  381.                    or broadcast['dash_playback_url'])
  382.  
  383.         # Print broadcast info to console
  384.         logger.info(rule_line)
  385.         started_mins, started_secs = divmod((int(time.time()) - broadcast['published_time']), 60)
  386.         logger.info('Broadcast by: %s \t(%s)\tType: %s' % (
  387.             broadcast['broadcast_owner']['username'],
  388.             broadcast['id'],
  389.             'Live' if not is_replay_broadcast else 'Replay')
  390.         )
  391.         if not is_replay_broadcast:
  392.             started_label = '%dm' % started_mins
  393.             if started_secs:
  394.                 started_label += ' %ds' % started_secs
  395.             logger.info(
  396.                 'Viewers: %d \t\tStarted: %s ago' % (
  397.                     broadcast.get('viewer_count', 0),
  398.                     started_label)
  399.             )
  400.             logger.info('Dash URL: %s' % mpd_url)
  401.             logger.info(rule_line)
  402.  
  403.         # Record the delay = duration of the stream that has been missed
  404.         broadcast['delay'] = ((download_start_time - broadcast['published_time'])
  405.                               if not is_replay_broadcast else 0)
  406.  
  407.         # folder path for downloaded segments
  408.         mpd_output_dir = generate_safe_path(
  409.             '%s_downloads' % filename_prefix, userconfig.outputdir, is_file=False)
  410.  
  411.         # file path to save the stream's info
  412.         meta_json_file = generate_safe_path('%s.json' % filename_prefix, userconfig.outputdir)
  413.  
  414.         # file path to save collected comments
  415.         comments_json_file = generate_safe_path('%s_comments.json' % filename_prefix, userconfig.outputdir)
  416.  
  417.         if is_replay_broadcast:
  418.             # ------------- REPLAY broadcast -------------
  419.             dl = ReplayDownloader(mpd=mpd_url, output_dir=mpd_output_dir, user_agent=api.user_agent)
  420.             duration = dl.duration
  421.             broadcast['duration'] = duration
  422.             if duration:
  423.                 duration_mins, duration_secs = divmod(duration, 60)
  424.                 if started_mins < 60:
  425.                     started_label = '%dm %ds' % (started_mins, started_secs)
  426.                 else:
  427.                     started_label = '%dh %dm' % divmod(started_mins, 60)
  428.                 logger.info(
  429.                     'Duration: %dm %ds \t\tStarted: %s ago' % (
  430.                         duration_mins, duration_secs, started_label)
  431.                 )
  432.                 logger.info(rule_line)
  433.  
  434.             # Detect if this replay has already been downloaded
  435.             if glob.glob(os.path.join(userconfig.outputdir, '%s.*' % filename_prefix)):
  436.                 # Already downloaded, so skip
  437.                 logger.warning('This broadcast is already downloaded.')
  438.                 # Remove created empty folder
  439.                 if os.path.isdir(mpd_output_dir):
  440.                     os.rmdir(mpd_output_dir)
  441.                 continue
  442.  
  443.             # Good to go
  444.             logger.info('Downloading into %s ...' % mpd_output_dir)
  445.             logger.info('[i] To interrupt the download, press CTRL+C')
  446.            
  447.             final_output = generate_safe_path('%s.mp4' % filename_prefix, userconfig.outputdir)
  448.             try:
  449.                 generated_files = dl.download(
  450.                     final_output, skipffmpeg=userconfig.skipffmpeg,
  451.                     cleartempfiles=(not userconfig.nocleanup))
  452.  
  453.                 # Save meta file later after a successful download
  454.                 # so that we don't trip up the downloaded check
  455.                 with open(meta_json_file, 'w') as outfile:
  456.                     json.dump(broadcast, outfile, indent=2)
  457.                 logger.info(rule_line)
  458.  
  459.                 if not userconfig.skipffmpeg:
  460.                     logger.info('Generated file(s): \n%s' % '\n'.join(generated_files))
  461.                 else:
  462.                     logger.info('Skipped generating file.')
  463.                 logger.info(rule_line)
  464.  
  465.                 if userconfig.commenters or userconfig.collectcomments:
  466.                     logger.info('Collecting comments...')
  467.                     cdl = CommentsDownloader(
  468.                         api=api, broadcast=broadcast, destination_file=comments_json_file,
  469.                         user_config=userconfig, logger=logger)
  470.                     cdl.get_replay()
  471.  
  472.                     # Generate srt from comments collected
  473.                     if cdl.comments:
  474.                         logger.info('Generating comments file...')
  475.                         srt_filename = final_output.replace('.mp4', '.srt')
  476.                         CommentsDownloader.generate_srt(
  477.                             cdl.comments, broadcast['published_time'], srt_filename,
  478.                             comments_delay=0)
  479.                         logger.info('Comments written to: %s' % srt_filename)
  480.                         logger.info(rule_line)
  481.  
  482.             except KeyboardInterrupt:
  483.                 logger.info('Download interrupted')
  484.             except Exception as e:
  485.                 logger.error('Unexpected Error: %s' % str(e))
  486.  
  487.             continue    # Done with all replay processing
  488.  
  489.         # ------------- LIVE broadcast -------------
  490.         with open(meta_json_file, 'w') as outfile:
  491.             json.dump(broadcast, outfile, indent=2)
  492.  
  493.         job_aborted = False
  494.  
  495.         # Callback func used by downloaded to check if broadcast is still alive
  496.         def check_status():
  497.             heartbeat_info = api.broadcast_heartbeat_and_viewercount(broadcast['id'])
  498.             logger.info('Broadcast Status Check: %s' % heartbeat_info['broadcast_status'])
  499.             return heartbeat_info['broadcast_status'] not in ['active', 'interrupted']
  500.  
  501.         dl = Downloader(
  502.             mpd=mpd_url,
  503.             output_dir=mpd_output_dir,
  504.             callback_check=check_status,
  505.             user_agent=api.user_agent,
  506.             mpd_download_timeout=userconfig.mpdtimeout,
  507.             download_timeout=userconfig.downloadtimeout,
  508.             duplicate_etag_retry=60,
  509.             ffmpegbinary=userconfig.ffmpegbinary)
  510.  
  511.         # Generate the final output filename so that we can
  512.         final_output = generate_safe_path('%s.mp4' % filename_prefix, userconfig.outputdir)
  513.  
  514.         # Call the api to collect comments for the stream
  515.         def get_comments():
  516.             logger.info('Collecting comments...')
  517.             cdl = CommentsDownloader(
  518.                 api=api, broadcast=broadcast, destination_file=comments_json_file,
  519.                 user_config=userconfig, logger=logger)
  520.             first_comment_created_at = 0
  521.             try:
  522.                 while not job_aborted:
  523.                     # Set initial_buffered_duration as soon as it's available
  524.                     if 'initial_buffered_duration' not in broadcast and dl.initial_buffered_duration:
  525.                         broadcast['initial_buffered_duration'] = dl.initial_buffered_duration
  526.                         cdl.broadcast = broadcast
  527.                     first_comment_created_at = cdl.get_live(first_comment_created_at)
  528.  
  529.             except ClientError as e:
  530.                 if 'media has been deleted' in e.error_response:
  531.                     logger.info('Stream end detected.')
  532.                 else:
  533.                     logger.error('Comment collection ClientError: %d %s' % (e.code, e.error_response))
  534.  
  535.             logger.info('%d comments collected' % len(cdl.comments))
  536.  
  537.             # do final save just in case
  538.             if cdl.comments:
  539.                 cdl.save()
  540.                 # Generate srt from comments collected
  541.                 srt_filename = final_output.replace('.mp4', '.srt')
  542.                 CommentsDownloader.generate_srt(
  543.                     cdl.comments, download_start_time, srt_filename,
  544.                     comments_delay=dl.initial_buffered_duration)
  545.                 logger.info('Comments written to: %s' % srt_filename)
  546.  
  547.         # Put comments collection into its own thread to run concurrently
  548.         comment_thread_worker = None
  549.         if userconfig.commenters or userconfig.collectcomments:
  550.             comment_thread_worker = threading.Thread(target=get_comments)
  551.             comment_thread_worker.start()
  552.  
  553.         logger.info('Downloading into %s ...' % mpd_output_dir)
  554.         logger.info('[i] To interrupt the download, press CTRL+C')
  555.        
  556.        
  557.         # Post notifications
  558.         insta_username = broadcast['broadcast_owner']['username']
  559.        
  560.         def desktop_notify_start():
  561.             toaster = ToastNotifier()
  562.             toaster.show_toast("Instagram Live","%s is live!" % (insta_username), duration=1)
  563.        
  564.         def twitter_notify():
  565.             auth = tweepy.OAuthHandler(CONSUMER_KEY, CONSUMER_SECRET)
  566.             auth.set_access_token(ACCESS_TOKEN, ACCESS_TOKEN_SECRET)
  567.        
  568.             def tweet_message():
  569.                 emojilist = ['πŸ˜€', '😁', 'πŸ˜ƒ', 'πŸ˜„', 'πŸ˜‰', '😊', 'πŸ˜‹', '😍', '😘', 'πŸ˜™', '😚',
  570.                 '☺', 'πŸ€—', '😌', '✨', 'πŸŽ‰', 'β™₯', '🌸', '🌼', '🌻', '🌺', 'πŸ’›', 'πŸ’™', 'πŸ’š', 'πŸ’œ', 'πŸ’•', 'πŸ’ž', 'πŸ’“', 'πŸ’—', 'πŸ’–', ' ']
  571.                 emojione = random.choice(emojilist)
  572.                 emojitwo = random.choice(emojilist)
  573.                 return "%s is live right now on Instagram!\n%sλ‹˜μ΄ 라이브 방솑을 μ‹œμž‘ν–ˆμŠ΅λ‹ˆλ‹€!\n#EXO #μ—‘μ†Œ @weareoneEXO %s%s" % (insta_username, insta_username, emojione, emojitwo)
  574.        
  575.             def tweet_status_without_image(message):
  576.                 try:
  577.                     tweetapi = tweepy.API(auth)
  578.                     tweetapi.update_status(status = message)
  579.                     print('Tweet posted without image')
  580.                 except tweepy.TweepError as e:
  581.                     print('Tweet without image failed.')
  582.                     print(e.reason)
  583.        
  584.             def tweet_status_with_image(filename, message):
  585.                 try:
  586.                     tweetapi = tweepy.API(auth)
  587.                     tweetapi.update_with_media(filename, status = message)
  588.                     print('Tweet posted with image')
  589.                 except tweepy.TweepError as e:
  590.                     print('Tweet with image failed.')
  591.                     print(e.reason)
  592.                
  593.             try:
  594.                 image_path = broadcast['cover_frame_url']
  595.                 filename = 'temp.jpg'
  596.                 request = requests.get(image_path, stream=True)
  597.                 if request.status_code == 200:
  598.                     with open(filename, 'wb') as image:
  599.                         for chunk in request:
  600.                             image.write(chunk)
  601.                     message = tweet_message()
  602.                     tweet_status_with_image(filename, message)
  603.                     os.remove(filename)
  604.                 else:
  605.                     print('Unable to download image')
  606.                     message = tweet_message()
  607.                     tweet_status_without_image(message)
  608.             except KeyError:
  609.                 print('KeyError encountered')
  610.                 message = tweet_message()
  611.                 tweet_status_without_image(message)
  612.        
  613.         tweet_notif = threading.Thread(target=twitter_notify)
  614.         desktop_notif_start = threading.Thread(target=desktop_notify_start)
  615.         tweet_notif.start()
  616.         desktop_notif_start.start()
  617.         tweet_notif.join()
  618.         desktop_notif_start.join()
  619.        
  620.         try:
  621.             dl.run()
  622.         except KeyboardInterrupt:
  623.             logger.warning('Download interrupted.')
  624.             # Wait for download threads to complete
  625.             if not dl.is_aborted:
  626.                 dl.stop()
  627.  
  628.         finally:
  629.             job_aborted = True
  630.  
  631.             # Record the initial_buffered_duration
  632.             broadcast['initial_buffered_duration'] = dl.initial_buffered_duration
  633.             broadcast['segments'] = dl.segment_meta
  634.             with open(meta_json_file, 'w') as outfile:
  635.                 json.dump(broadcast, outfile, indent=2)
  636.  
  637.             missing = broadcast['delay'] - int(dl.initial_buffered_duration)
  638.             logger.info('Recorded stream is missing %d seconds' % missing)
  639.  
  640.             # Wait for comments thread to complete
  641.             if comment_thread_worker and comment_thread_worker.is_alive():
  642.                 logger.info('Stopping comments download...')
  643.                 comment_thread_worker.join()
  644.  
  645.             logger.info('Assembling files....')
  646.  
  647.             generated_files = dl.stitch(
  648.                 final_output, skipffmpeg=userconfig.skipffmpeg,
  649.                 cleartempfiles=(not userconfig.nocleanup))
  650.  
  651.             logger.info(rule_line)
  652.             if not userconfig.skipffmpeg:
  653.                 logger.info('Generated file(s): \n%s' % '\n'.join(generated_files))
  654.             else:
  655.                 logger.info('Skipped generating file.')
  656.             logger.info(rule_line)
  657.  
  658.             if not userconfig.skipffmpeg and not userconfig.nocleanup:
  659.                 shutil.rmtree(mpd_output_dir, ignore_errors=True)
  660.  
  661.             if userconfig.openwhendone and os.path.exists(final_output):
  662.                 webbrowser.open_new_tab('file://' + os.path.abspath(final_output))
  663.            
  664.             # post notifications
  665.             toasterover = ToastNotifier()
  666.             toasterover.show_toast("Instagram Live",
  667.                                    "%s live is over!" % (broadcast['broadcast_owner']['username']),
  668.                                    duration=1)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement