SHOW:
|
|
- or go back to the newest paste.
1 | #!/usr/bin/env python3 | |
2 | """ | |
3 | - | A simple little script for getting at the m3u8 for ufc.tv videos |
3 | + | A simple little script for getting at the m3u8 for ufc.tv videos |
4 | - | For free and ethical stream viewing free from proprietary players |
4 | + | For free and ethical stream viewing free from proprietary players |
5 | ||
6 | - | Doesn't circumvent geo-blocks or login requirements |
6 | + | Doesn't circumvent geo-blocks or login requirements |
7 | - | You can get around the ufc.tv geo blocks via DNS proxies |
7 | + | You can get around the ufc.tv geo blocks via DNS proxies |
8 | ||
9 | - | I have this saved as ufctv in a bin dir on my $PATH, so I can call it from wherever |
9 | + | I have this saved as ufctv in a bin dir on my $PATH, so I can call it from wherever |
10 | ||
11 | - | To login, run: ufctv login |
11 | + | To login, run: ufctv login |
12 | - | That will begin an interactive login prompt |
12 | + | That will begin an interactive login prompt |
13 | - | The login details themselves aren't saved, just the cookies for the session |
13 | + | The login details themselves aren't saved, just the cookies for the session |
14 | - | And the active session is reset any time you login with the "Keep Me Signed In" box anywhere else |
14 | + | And the active session is reset any time you login with the "Keep Me Signed In" box anywhere else |
15 | ||
16 | - | To get a video's m3u8: ufctv m3u8 $UFCTV-URL |
16 | + | To get a video's m3u8: ufctv m3u8 $UFCTV-URL |
17 | - | So for example if I want to go back and watch Belfort vs Hendo 3 |
17 | + | So for example if I want to go back and watch Belfort vs Hendo 3 |
18 | - | % ufctv m3u8 http://www.ufc.tv/video/belfort-vs-henderson-3 |
18 | + | % ufctv m3u8 http://www.ufc.tv/video/belfort-vs-henderson-3 |
19 | - | From there you an do what you wish with the m3u8! |
19 | + | From there you an do what you wish with the m3u8! |
20 | ||
21 | - | You may wish to pipe the result direct to mpv (which lets you seek around and switch between qualities) |
21 | + | You may wish to pipe the result direct to mpv (which lets you seek around and switch between qualities) |
22 | - | % ufctv m3u8 http://www.ufc.tv/video/belfort-vs-henderson-3 | xargs mpv |
22 | + | % ufctv m3u8 http://www.ufc.tv/video/belfort-vs-henderson-3 | xargs mpv |
23 | ||
24 | - | Or start ripping with livestreamer |
24 | + | Or start ripping with livestreamer |
25 | - | % ufctv m3u8 http://www.ufc.tv/video/belfort-vs-henderson-3 | xargs -I M3U8 livestreamer hlsvariant://M3U8 best -o belf-vs-hend.ts |
25 | + | % ufctv m3u8 http://www.ufc.tv/video/belfort-vs-henderson-3 | xargs -I M3U8 livestreamer hlsvariant://M3U8 best -o belf-vs-hend.ts |
26 | - | You can also rip the stream with ffmpeg, though I've found livestreamer does it a bit cleaner |
26 | + | You can also rip the stream with ffmpeg, though I've found livestreamer does it a bit cleaner |
27 | ||
28 | - | You may also wish to expand the program to add commands to do this for you instead of via shell piping |
28 | + | You may also wish to expand the program to add commands to do this for you instead of via shell piping |
29 | """ | |
30 | ||
31 | import argparse | |
32 | import getpass | |
33 | import json | |
34 | import os | |
35 | import pickle | |
36 | import re | |
37 | import subprocess | |
38 | import sys | |
39 | ||
40 | from requests import session | |
41 | ||
42 | def main(): | |
43 | - | parser = argparse.ArgumentParser(description= |
43 | + | parser = argparse.ArgumentParser(description= |
44 | - | """ |
44 | + | """ |
45 | - | This is a command line tool to help enjoy quality UFC Fight Pass |
45 | + | This is a command line tool to help enjoy quality UFC Fight Pass |
46 | - | content in an ethical manner that respects your freedoms |
46 | + | content in an ethical manner that respects your freedoms |
47 | - | """) |
47 | + | """) |
48 | ||
49 | - | parser.add_argument('operation', |
49 | + | parser.add_argument('operation', |
50 | - | choices=['login', 'm3u8']) |
50 | + | choices=['login', 'm3u8']) |
51 | ||
52 | - | parser.add_argument('rest', nargs='*') |
52 | + | parser.add_argument('rest', nargs='*') |
53 | ||
54 | - | args = parser.parse_args() |
54 | + | args = parser.parse_args() |
55 | ||
56 | - | op = args.operation |
56 | + | op = args.operation |
57 | - | if op == 'login': |
57 | + | if op == 'login': |
58 | - | return ufctv_login() |
58 | + | return ufctv_login() |
59 | ||
60 | - | if len(args.rest) < 1: |
60 | + | if len(args.rest) < 1: |
61 | - | print("Missing argument: video_url", file=sys.stderr) |
61 | + | print("Missing argument: video_url", file=sys.stderr) |
62 | - | return 1 |
62 | + | return 1 |
63 | ||
64 | - | vid_url = args.rest[0] |
64 | + | vid_url = args.rest[0] |
65 | - | uri = get_m3u8(vid_url) |
65 | + | uri = get_m3u8(vid_url) |
66 | - | if uri is None: |
66 | + | if uri is None: |
67 | - | return 2 |
67 | + | return 2 |
68 | ||
69 | - | if args.operation == 'm3u8': |
69 | + | if args.operation == 'm3u8': |
70 | - | print(uri) |
70 | + | print(uri) |
71 | - | return 0 |
71 | + | return 0 |
72 | ||
73 | def fake_mobile_session(): | |
74 | - | with session() as c: |
74 | + | with session() as c: |
75 | - | load_cookies(c) |
75 | + | load_cookies(c) |
76 | - | c.headers.update({'User-Agent': ( |
76 | + | c.headers.update({'User-Agent': ( |
77 | - | "Mozilla/5.0 (iPhone; U; CPU iPhone OS 3_0 like Mac OS X; en-us)" |
77 | + | "Mozilla/5.0 (iPhone; U; CPU iPhone OS 3_0 like Mac OS X; en-us)" |
78 | - | " AppleWebKit/528.18 (KHTML, like Gecko)" |
78 | + | " AppleWebKit/528.18 (KHTML, like Gecko)" |
79 | - | " Version/4.0 Mobile/7A341 Safari/528.16")}) |
79 | + | " Version/4.0 Mobile/7A341 Safari/528.16")}) |
80 | - | return c |
80 | + | return c |
81 | ||
82 | def get_m3u8_by_id(session, vid_id): | |
83 | - | puburi = "http://www.ufc.tv/service/publishpoint?type=video&id={}&format=json" |
83 | + | puburi = "http://www.ufc.tv/service/publishpoint?type=video&id={}&format=json" |
84 | - | r = session.get(puburi.format(vid_id)) |
84 | + | r = session.get(puburi.format(vid_id)) |
85 | - | stream_info = json.loads(r.text) |
85 | + | stream_info = json.loads(r.text) |
86 | - | if 'path' not in stream_info: |
86 | + | if 'path' not in stream_info: |
87 | - | return None |
87 | + | return None |
88 | - | return stream_info['path'].replace("_iphone", "") |
88 | + | return stream_info['path'].replace("_iphone", "") |
89 | ||
90 | def get_m3u8(uri): | |
91 | - | with fake_mobile_session() as c: |
91 | + | with fake_mobile_session() as c: |
92 | - | r = c.get(uri) |
92 | + | r = c.get(uri) |
93 | ||
94 | - | vid_id = find_video_id(r.text) |
94 | + | vid_id = find_video_id(r.text) |
95 | - | if vid_id is None: |
95 | + | if vid_id is None: |
96 | - | print("Failed to find video ID in page.", file=sys.stderr) |
96 | + | print("Failed to find video ID in page.", file=sys.stderr) |
97 | - | return None |
97 | + | return None |
98 | ||
99 | - | if not logged_in(r.text): |
99 | + | if not logged_in(r.text): |
100 | - | print("Goof Alert: You are not logged in to UFC.TV", file=sys.stderr) |
100 | + | print("Goof Alert: You are not logged in to UFC.TV", file=sys.stderr) |
101 | ||
102 | - | if not video_allowed(r.text): |
102 | + | if not video_allowed(r.text): |
103 | - | print("Video is not available", file=sys.stderr) |
103 | + | print("Video is not available", file=sys.stderr) |
104 | - | return None |
104 | + | return None |
105 | ||
106 | - | res = get_m3u8_by_id(c, vid_id) |
106 | + | res = get_m3u8_by_id(c, vid_id) |
107 | - | save_cookies(c) |
107 | + | save_cookies(c) |
108 | - | return res |
108 | + | return res |
109 | ||
110 | ufctv_sesssion_path = os.path.expanduser("~/.ufctv") | |
111 | ||
112 | def save_cookies(session): | |
113 | - | with open(ufctv_sesssion_path, 'wb') as f: |
113 | + | with open(ufctv_sesssion_path, 'wb') as f: |
114 | - | pickle.dump(session.cookies, f) |
114 | + | pickle.dump(session.cookies, f) |
115 | ||
116 | def load_cookies(session): | |
117 | - | if not os.path.isfile(ufctv_sesssion_path): |
117 | + | if not os.path.isfile(ufctv_sesssion_path): |
118 | - | print("No saved cookies file found -- starting from scratch", file=sys.stderr) |
118 | + | print("No saved cookies file found -- starting from scratch", file=sys.stderr) |
119 | - | return |
119 | + | return |
120 | ||
121 | - | with open(ufctv_sesssion_path, 'rb') as f: |
121 | + | with open(ufctv_sesssion_path, 'rb') as f: |
122 | - | session.cookies.update(pickle.load(f)) |
122 | + | session.cookies.update(pickle.load(f)) |
123 | ||
124 | def video_allowed(page_html): | |
125 | - | return '<div class="noAccess">' not in page_html |
125 | + | return '<div class="noAccess">' not in page_html |
126 | ||
127 | def logged_in(page_html): | |
128 | - | return "Sign Out" in page_html |
128 | + | return "Sign Out" in page_html |
129 | ||
130 | def ufctv_login(): | |
131 | - | with fake_mobile_session() as c: |
131 | + | with fake_mobile_session() as c: |
132 | - | login(c) |
132 | + | login(c) |
133 | - | save_cookies(c) |
133 | + | save_cookies(c) |
134 | ||
135 | def login(sesh): | |
136 | - | username = input("Please enter your ufc.tv username: ") |
136 | + | username = input("Please enter your ufc.tv username: ") |
137 | - | password = getpass.getpass("Please enter your ufc.tv password: ") |
137 | + | password = getpass.getpass("Please enter your ufc.tv password: ") |
138 | - | longchoice = input("Tick the 'Keep Me Signed In' box for a long lasting session? [y/n] ") |
138 | + | longchoice = input("Tick the 'Keep Me Signed In' box for a long lasting session? [y/n] ") |
139 | - | longsesh = 'true' if longchoice is 'y' else 'false' |
139 | + | longsesh = 'true' if longchoice is 'y' else 'false' |
140 | ||
141 | - | login = { |
141 | + | login = { |
142 | - | 'username': username, |
142 | + | 'username': username, |
143 | - | 'password': password, |
143 | + | 'password': password, |
144 | - | 'cookielink': longsesh |
144 | + | 'cookielink': longsesh |
145 | - | } |
145 | + | } |
146 | ||
147 | - | r = sesh.post("https://www.ufc.tv/secure/authenticate", data=login) |
147 | + | r = sesh.post("https://www.ufc.tv/secure/authenticate", data=login) |
148 | ||
149 | - | if 'loginsuccess' not in r.text: |
149 | + | if 'loginsuccess' not in r.text: |
150 | - | print("Login failure -- full response:\n{}\n".format(r.text), file=sys.stderr) |
150 | + | print("Login failure -- full response:\n{}\n".format(r.text), file=sys.stderr) |
151 | - | return False |
151 | + | return False |
152 | ||
153 | - | return True |
153 | + | return True |
154 | ||
155 | def find_video_id(page_html): | |
156 | - | m = re.search("rel=\"image_src\" href=\".*?([0-9]+?)_.*?\.(jpg|png)\"", page_html) |
156 | + | m = re.search("rel=\"image_src\" href=\".*?([0-9]+?)_.*?\.(jpg|png)\"", page_html) |
157 | - | if m is None: |
157 | + | if m is None: |
158 | - | return m |
158 | + | return m |
159 | - | return m.group(1) |
159 | + | return m.group(1) |
160 | ||
161 | if __name__ == "__main__": | |
162 | - | sys.exit(main()) |
162 | + | sys.exit(main()) |