Advertisement
Guest User

UFC.tv ethical viewing script

a guest
Nov 30th, 2018
625
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 12.01 KB | None | 0 0
  1. #!/usr/bin/env python3
  2.  
  3. """
  4.  A simple little script for getting at the m3u8 for ufc.tv videos
  5.  For free and ethical stream viewing free from proprietary players
  6.  
  7.  I have this saved as ufctv in a bin dir on my $PATH, so I can call it from wherever
  8.  
  9.  To login, run: ufctv login
  10.  That will begin an interactive login prompt
  11.  The login details themselves aren't saved, just the cookies for the session
  12.  And the active session is reset any time you login with the "Keep Me Signed In" box anywhere else
  13.  
  14.  To get a video's m3u8 (can be piped into mpv or others): ufctv uri $UFCTV-URL
  15.  So for example if I want to go back and watch Belfort vs Hendo 3
  16.  % ufctv uri http://www.ufc.tv/video/belfort-vs-henderson-3
  17.  From there you an do what you wish with the m3u8!
  18.  
  19.  You may wish to pipe the result direct to mpv (which lets you seek around and switch between qualities)
  20.  % ufctv uri http://www.ufc.tv/video/belfort-vs-henderson-3 | xargs mpv
  21.  or
  22.  % ufctv watch http://www.ufc.tv/video/belfort-vs-henderson-3
  23.  
  24.  Or start ripping with streamlink
  25.  % ufctv uri http://www.ufc.tv/video/belfort-vs-henderson-3 | xargs -I M3U8 streamlink hlsvariant://M3U8 best -o belf-vs-hend.ts
  26.  or
  27.  % ufctv rip http://www.ufc.tv/video/belfort-vs-henderson-3
  28.  You can also rip the stream with ffmpeg, though I've found streamlink does it a bit cleaner
  29.  
  30.  Right now having trouble with direct to static quality URIs
  31.  Seems Akamai or whoever wants the client to load the playlist first at minimum
  32.  So for ffmpeg ripping it's a bit of hassle as ffmpeg will try to eat every stream in the playlist from what I see
  33.  Streamlink doesn't have that issue
  34.  
  35.  If you're having trouble with 403 forbiddens still
  36.  Try jumping around between fake devices
  37.  Find user_agents in the script, maybe add some more recent ones, and set it as the fake device
  38. """
  39.  
  40. import argparse
  41. import getpass
  42. import json
  43. import os
  44. import pickle
  45. import re
  46. import subprocess
  47. import sys
  48. import logging
  49. import urllib
  50.  
  51. from requests import session
  52.  
  53. ufctv_session_path = os.path.expanduser("~/.ufctv")
  54. proxy_settings = {}
  55.  
  56. def main():
  57.     logging.basicConfig(level=logging.INFO)
  58.     parser = argparse.ArgumentParser(description=
  59.         """
  60.        This is a command line tool to help enjoy quality UFC Fight Pass
  61.        content in an ethical manner that respects your freedoms
  62.        """)
  63.  
  64.     parser.add_argument(
  65.         '-p', '--proxy',
  66.         help="Try to use a proxy to get around geoblock"
  67.     )
  68.  
  69.     parser.add_argument('operation', choices=[
  70.         'login',
  71.         'uri',
  72.         'watch',
  73.         'rip'])
  74.     args, rest = parser.parse_known_args()
  75.  
  76.     if args.proxy:
  77.         proxy_settings['http'] = args.proxy
  78.         proxy_settings['https'] = args.proxy
  79.  
  80.     if args.operation == 'login':
  81.         return ufctv_login()
  82.  
  83.     parser.add_argument('video_url')
  84.  
  85.     parser.add_argument('--byid', action='store_true')
  86.  
  87.     cams = ['main', 'espanol', 'blue', 'red', 'fence', 'overhead', 'all']
  88.     parser.add_argument(
  89.         '-c', '--cam',
  90.         choices=cams,
  91.         default='main',
  92.         help="Requests a different camera -- only works for PPVs!"
  93.     )
  94.  
  95.     parser.add_argument(
  96.         '-q', '--quality',
  97.         default='best',
  98.         help='Change quality level (e.g. 3000,4500)'
  99.     )
  100.  
  101.     parser.add_argument(
  102.         '-sq', '--staticquality',
  103.         default='',
  104.         help='Specifically (URI-level) set quality level (e.g. 3000,4500)'
  105.     )
  106.  
  107.     parser.add_argument(
  108.         '--tstart',
  109.         default=''
  110.     )
  111.  
  112.     parser.add_argument(
  113.         '--tend',
  114.         default=''
  115.     )
  116.  
  117.     args, rest = parser.parse_known_args()
  118.  
  119.     with ufctv_session() as c:
  120.         if args.cam == 'all':
  121.             streams = get_all_cams(c, args.video_url)
  122.             if not streams:
  123.                 logging.warning("No streams found...")
  124.                 return 2
  125.             for stream in streams:
  126.                 print(stream)
  127.         else:
  128.             stream = None
  129.             if args.byid:
  130.               logging.info("Retrieving m3u8 by using id")
  131.               stream = get_m3u8_by_id(c, args.video_url, cams.index(args.cam), args.staticquality)
  132.             else:
  133.               logging.info("Retrieving m3u8 by scanning page for id")
  134.               stream = get_single_cam(c, args.video_url, cams.index(args.cam), args.staticquality)
  135.  
  136.             if stream is None:
  137.                 logging.warning("No stream was found...")
  138.                 return 2
  139.  
  140.             print(stream)
  141.  
  142.     if args.operation == 'rip':
  143.         parser.add_argument(
  144.             '--program',
  145.             default='streamlink',
  146.             help='Choose program to use'
  147.         )
  148.        
  149.         parser.add_argument(
  150.             '--threads',
  151.             default='1',
  152.             help='Change amount of ripping threads'
  153.         )
  154.  
  155.         args, rest = parser.parse_known_args()
  156.         i = 0
  157.         save_path = "{}-{}.ts".format(args.video_url.split('/')[-1], i)
  158.         while os.path.exists(save_path):
  159.             i += 1
  160.             save_path = "{}-{}.ts".format(args.video_url.split('/')[-1], i)
  161.  
  162.         sq_used = False
  163.         if args.staticquality:
  164.           sq_used = True
  165.  
  166.         rip(
  167.           stream,
  168.           save_path,
  169.           quality=args.quality,
  170.           program=args.program,
  171.           static_quality_used=sq_used,
  172.           start=args.tstart,
  173.           end=args.tend
  174.         )
  175.  
  176.     if args.operation == 'watch':
  177.         watch(stream, quality=args.quality)
  178.  
  179.     return 0
  180.  
  181. user_agents = {
  182.   'iphone':
  183.       "Mozilla/5.0 (iPhone; U; CPU iPhone OS 3_0 like Mac OS X; en-us)"
  184.       " AppleWebKit/528.18 (KHTML, like Gecko)"
  185.       " Version/4.0 Mobile/7A341 Safari/528.16"
  186.   ,
  187.   'ipad':
  188.       "Mozilla/5.0 (iPad; U; CPU OS 3_2 like Mac OS X; en-us)"
  189.       " AppleWebKit/531.21.10 (KHTML, like Gecko)"
  190.       " Version/4.0.4 Mobile/7B334b Safari/531.21.10"
  191.   ,
  192.   'chromecast':
  193.       "Mozilla/5.0 (CrKey armv7l 1.4.15250)"
  194.       " AppleWebKit/537.36 (KHTML, like Gecko)"
  195.       " Chrome/31.0.1650.0 Safari/537.36"
  196.   ,
  197.   'firefox':
  198.       "Mozilla/5.0 (X11; Linux x86_64; rv:43.0) Gecko/20100101 Firefox/43.0"
  199. }
  200. fake_device = 'chromecast' #'iphone' #'chromecast'
  201.  
  202. app_user_agent = user_agents[fake_device]
  203.  
  204. def watch(uri, program='streamlink', quality='best'):
  205.   if program == 'streamlink':
  206.     cmd = [
  207.       'streamlink',
  208.       "hlsvariant://{}".format(uri),
  209.       'best',
  210.       '--http-header', 'User-Agent={}'.format(app_user_agent),
  211.       '--hls-segment-threads', '2',
  212.       '--player', 'mpv'#,
  213.       #'-cache', '8192'
  214.     ]
  215.  
  216.   if program == 'mpv':
  217.     cmd = [
  218.       'mpv',
  219.       uri,
  220.       '--keep-open',
  221.     ]
  222.  
  223.   print(" ".join(cmd))
  224.   return subprocess.call(cmd)
  225.  
  226. def rip(uri, path, quality='best', program='ffmpeg', static_quality_used=False, start=None, end=None):
  227.   cmd = []
  228.  
  229.   if program == 'streamlink':
  230.     proto = "hlsvariant"
  231.     if static_quality_used:
  232.       proto = "hls"
  233.  
  234.     cmd = [
  235.       'streamlink',
  236.       "{}://{}".format(proto, uri),
  237.       quality,
  238.       '-o', path,
  239.       '--http-header', 'User-Agent={}'.format(app_user_agent),
  240.       '--hls-segment-threads', '1',
  241.       #'--hls-live-edge', '5',
  242.       #'--hls-segment-timeout', '120',
  243.       #'--hls-segment-attempts', '20',
  244.       #'--stream-segment-attempts', '20',
  245.       #'--stream-segment-timeout', '120',
  246.       #'--stream-timeout', '120',
  247.       '--retry-open', '5',
  248.     ]
  249.  
  250.   if program == 'ffmpeg':
  251.     cmd = [
  252.       'ffmpeg',
  253.       '-report',
  254.       '-loglevel', '48',
  255.       '-rw_timeout', '10000000',
  256.       '-timeout', '10000000',
  257.       '-multiple_requests', '1',
  258.       '-headers', 'User-Agent: {}'.format(app_user_agent)
  259.     ]
  260.  
  261.     if start:
  262.       cmd.extend(['-ss', start])
  263.  
  264.     cmd.extend([
  265.       '-i', uri,
  266.     ])
  267.  
  268.     if end:
  269.       cmd.extend(['-to', end])
  270.  
  271.     cmd.extend([
  272.       '-c:a', 'copy', '-c:v', 'copy', path
  273.     ])
  274.  
  275.   print(" ".join(cmd))
  276.   return subprocess.call(cmd)
  277.  
  278. def get_single_cam(session, uri, cam, static_qual = ""):
  279.     vid_id = get_video_id(session, uri)
  280.     if vid_id:
  281.       return get_m3u8_by_id(session, vid_id, cam, static_qual)
  282.  
  283. def get_all_cams(session, uri, static_qual = ""):
  284.     vid_id = get_video_id(session, uri)
  285.  
  286.     streams = []
  287.     for cam in range(0, 6):
  288.         stream = get_m3u8_by_id(session, vid_id, cam, static_qual)
  289.         if stream is None: break
  290.         streams.append(stream)
  291.  
  292.     return streams
  293.  
  294. def get_video_id(session, uri):
  295.     r = session.get(uri)
  296.     if not logged_in(r.text):
  297.         logging.warning("Goof Alert: You are not logged in to UFC.TV")
  298.  
  299.     if video_geo_blocked(r.text):
  300.         logging.warning("Looks like you're geo-blocked, kid")
  301.  
  302.     vid_id = find_video_id(r.text)
  303.     if vid_id is None:
  304.       logging.warning("Failed to find video ID in page, likely a bug in function find_video_id")
  305.       return None
  306.  
  307.     logging.info("Video ID found: {}".format(vid_id))
  308.     return vid_id
  309.  
  310. def find_video_id(page_html):
  311.     m = re.search('rel="image_src" href=".*?([0-9]+?)_.*?"', page_html)
  312.     if m: return m.group(1)
  313.  
  314. def get_m3u8_by_id(session, vid_id, cam, static_qual = ""):
  315.     puburi = "https://www.ufc.tv/service/publishpoint"
  316.     request = {
  317.       'format': 'json',
  318.       'type': 'video',
  319.       'id': vid_id,
  320.       'cam': cam,
  321.     }
  322.  
  323.     """
  324.    if 'https' in proxy_settings:
  325.      logging.info("Using HTTPS proxy")
  326.      r = session.post(puburi, data=request, proxies=proxy_settings)
  327.    elif 'http' in proxy_settings:
  328.      logging.info("Using HTTP proxy")
  329.      r = session.post(puburi, data=request, proxies=proxy_settings)
  330.    else:
  331.    """
  332.     r = session.post(puburi, data=request)
  333.     #r = session.post(puburi, data=request, proxies=proxy_settings)
  334.  
  335.     if r.text:
  336.         stream_info = json.loads(r.text)
  337.         if 'path' not in stream_info:
  338.             logging.warning("No stream m3u8 found, sorry.")
  339.             logging.warning(r.text)
  340.             return None
  341.     else:
  342.         logging.warning("No response from endpoint")
  343.         return None
  344.  
  345.     if static_qual:
  346.         return stream_info['path'].replace("hd_", "hd_" + static_qual + "_")
  347.     elif fake_device == 'iphone':
  348.         return stream_info['path'].replace("_iphone", "")
  349.     else:
  350.         return stream_info['path']
  351.  
  352. def save_cookies(session):
  353.     with open(ufctv_session_path, 'wb') as f:
  354.         pickle.dump(session.cookies, f)
  355.  
  356. def load_cookies(session):
  357.     if not os.path.isfile(ufctv_session_path):
  358.         return
  359.  
  360.     with open(ufctv_session_path, 'rb') as f:
  361.         session.cookies.update(pickle.load(f))
  362.  
  363. def video_geo_blocked(html):
  364.     return '<div class="blackout-content">' in html
  365.  
  366. def video_allowed(page_html):
  367.     return '<div class="noAccess">' not in page_html
  368.  
  369. def logged_in(page_html):
  370.   return "var isLoggedIn = true;" in page_html
  371.  
  372. def ufctv_login():
  373.     if os.path.isfile(ufctv_session_path):
  374.         os.remove(ufctv_session_path)
  375.  
  376.     with ufctv_session() as c:
  377.         login(c)
  378.         save_cookies(c)
  379.  
  380. def login(sesh):
  381.     username = input("Please enter your ufc.tv username: ")
  382.     password = getpass.getpass("Please enter your ufc.tv password: ")
  383.     longchoice = input("Tick the 'Keep Me Signed In' box for a long lasting session? [y/n] ")
  384.     longsesh = 'true' if longchoice is 'y' else 'false'
  385.  
  386.     login = {
  387.         'username': username,
  388.         'password': password,
  389.         'cookielink': longsesh
  390.     }
  391.    
  392.     r = sesh.post("https://www.ufc.tv/secure/authenticate", data=login)
  393.  
  394.     if 'loginsuccess' not in r.text:
  395.         print("Login failure -- full response:\n{}\n".format(r.text), file=sys.stderr)
  396.         return False
  397.  
  398.     return True
  399.  
  400. def ufctv_session():
  401.     with session() as c:
  402.         load_cookies(c)
  403.         if fake_device:
  404.             c.headers.update({'User-Agent': app_user_agent})
  405.         # let ufc.tv "feel the proxy"
  406.         if 'https' in proxy_settings:
  407.             logging.info("Testing HTTPS proxy...")
  408.             c.get("https://www.ufc.tv/", proxies=proxy_settings)
  409.  
  410.  
  411.         return c
  412.  
  413. if __name__ == "__main__":
  414.     sys.exit(main())
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement