import argparse import logging import sys import requests import re import json try: from http.cookiejar import CookieJar except ImportError: from cookielib import CookieJar import colorama from pywidevine.clients.netflix.client import NetflixClient from pywidevine.clients.netflix.config import NetflixConfig from pywidevine.clients.netflix.profiles import NetflixProfiles from pywidevine.downloader.wvdownloader import WvDownloader from pywidevine.downloader.wvdownloaderconfig import WvDownloaderConfig parser = argparse.ArgumentParser( description="netflix content downloader" ) parser.add_argument('-t', '--title', help='title id', nargs='+', type=int, required=True) parser.add_argument('-o', '--outputfile', default='out', nargs='?', help='output filename (no extension)') parser.add_argument('-q', '--quality', help='video resolution', choices=['480p', '720p', '1080p', '2160p']) parser.add_argument('-a', '--audiolang', help='audio language', type=lambda x: x.split(',')) parser.add_argument('-p', '--profile', default='h264', #choices=['h264', 'hevc', 'hdr', 'all'], choices=['h264_main', 'h264_high', 'hevc', 'hdr', 'vp9', 'all'], #choices=['h264', 'h264_hpl', 'hevc', 'hdr', 'vp9', 'all'], help='video type to download') parser.add_argument('-k', '--skip-cleanup', action='store_true', help='skip cleanup step') parser.add_argument('-m', '--dont-mux', action='store_true', help='move unmuxed tracks instead of muxing') parser.add_argument('-i', '--info', action='store_true', help='print track information and exit') parser.add_argument('-d', '--debug', action='store_true', help='print debug statements') parser.add_argument('-S', '--subs-only', action='store_true', help='download subtitles and exit') parser.add_argument('-u', '--sub-type', default='srt', choices=['srt', 'ass', 'none'], help='subtitle type (or none)') parser.add_argument('-s', '--season', type=int, help='lookup and download season from title id') parser.add_argument('-e', '--episode_start', dest="episode_start", help="Recursively rip season number that provided viewable ID belongs to, starting at the episode provided") parser.add_argument('--skip', type=int, default=0, help='skip episodes in season mode') parser.add_argument('--region', default='us', choices=['us', 'uk', 'jp', 'ca', 'se', 'ru'], help='region to proxy') parser.add_argument('--license', action='store_true', help='do license request and print decryption keys only') args = parser.parse_args() DEBUG_LEVELKEY_NUM = 21 logging.addLevelName(DEBUG_LEVELKEY_NUM, "LOGKEY") def logkey(self, message, *args, **kws): # Yes, logger takes its '*args' as 'args'. if self.isEnabledFor(DEBUG_LEVELKEY_NUM): self._log(DEBUG_LEVELKEY_NUM, message, args, **kws) logging.Logger.logkey = logkey logger = logging.getLogger() if args.license: logger.setLevel(21) else: logger.setLevel(logging.INFO) if args.debug: logger.setLevel(logging.DEBUG) ch = logging.StreamHandler(sys.stdout) ch.setLevel(logging.DEBUG) formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(module)s - %(message)s') ch.setFormatter(formatter) logger.addHandler(ch) colorama.init() BUILD = '' SESSION = requests.Session() """ login_pag = SESSION.get("https://www.netflix.com/login").text authURL = re.search('name="authURL" value="([^"]+)"', login_pag) print(authURL) #authURL = re.search(r'authURL\" value\=\"(.*?)\"', login_pag) authURL = authURL[1] def login(username, password): # post_data = { 'email': username, 'password': password, 'rememberMe': 'true', 'mode': 'login', 'action': 'loginAction', 'withFields': 'email,password,rememberMe,nextPage,showPassword', 'nextPage': '', 'showPassword': '', 'authURL': authURL } req = SESSION.post('https://www.netflix.com/login', post_data) #match =re.search (r'.*"BUILD_IDENTIFIER":"([a-z0-9]+)"', req.text) match = re.search(r'"BUILD_IDENTIFIER":"([a-z0-9]+)"', req.text) #fix by Castle / https://gist.github.com/xor10/8f65c1e66a34386e1131f8c28ff6bf64#gistcomment-2668063 if match is not None: return match.group(1) else: return None """ def login(username, password): r = SESSION.get('https://www.netflix.com/login', stream=True, allow_redirects=False, headers={'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:65.0) Gecko/20100101 Firefox/65.0'}) loc = None while 'Location' in r.headers: loc = r.headers['Location'] r = SESSION.get(loc, stream=True, allow_redirects=False, headers={'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:65.0) Gecko/20100101 Firefox/65.0'}) x = re.search('name="authURL" value="([^"]+)"', r.text) if not x: return authURL = x.group(1) post_data = {'userLoginId':username, 'password':password, 'rememberMe':'true', 'mode':'login', 'flow':'websiteSignUp', 'action':'loginAction', 'authURL':authURL, 'withFields':'userLoginId,password,rememberMe,nextPage,showPassword', 'nextPage':'', 'showPassword':''} req = SESSION.post(loc, post_data, headers={'User-Agent': 'Mozilla/5.0 (X11; Linux armv7l) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.90 Safari/537.36 CrKey/1.17.46278'}) try: req.raise_for_status() except requests.exceptions.HTTPError as e: print(e) logger.error(e) sys.exit(1) match = re.search('"BUILD_IDENTIFIER":"([a-z0-9]+)"', req.text) if match is not None: return match.group(1) else: return """ def fetch_metadata(movieid): #Fetches metadata for a netflix id req = SESSION.get('https://www.netflix.com/api/shakti/' + BUILD + '/metadata?movieid=' + movieid) return json.loads(req.text) """ def parseCookieFile(cookiefile): """Parse a cookies.txt file and return a dictionary of key value pairs compatible with requests.""" cookies = {} with open (cookiefile, 'r') as fp: for line in fp: if not re.match(r'^\#', line): lineFields = line.strip().split('\t') cookies[lineFields[5]] = lineFields[6] return cookies #proxies = {"https": "159.100.246.156:45382"} def get_build(): cookies = parseCookieFile('cookies.txt') post_data = '' #req1 = SESSION.get('https://www.netflix.com', headers={'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Safari/537.36'}, cookies=cookies, proxies=proxies) #print(req1.text) #exit(1) req = SESSION.post('https://www.netflix.com/browse', post_data, headers={'User-Agent': 'Gibbon/2018.1.6.3/2018.1.6.3: Netflix/2018.1.6.3 (DEVTYPE=NFANDROID2-PRV-FIRETVSTICK2016; CERTVER=0)'}, cookies=cookies) #req = SESSION.get('https://www.netflix.com/browse', headers={'User-Agent': 'Gibbon/2018.1.6.3/2018.1.6.3: Netflix/2018.1.6.3 (DEVTYPE=NFANDROID2-PRV-FIRETVSTICK2016; CERTVER=0)'}, cookies=cookies, proxies=proxies) match = re.search(r'"BUILD_IDENTIFIER":"([a-z0-9]+)"', req.text) #fix by Castle / https://gist.github.com/xor10/8f65c1e66a34386e1131f8c28ff6bf64#gistcomment-2668063 return match.group(1) def fetch_metadata(movieid): global BUILD cookies = parseCookieFile('cookies.txt') #BUILD = get_build() BUILD = 'vafe38bd5' print(BUILD) #cookies = 'on' #if BUILD == '': # BUILD = login(username, password) """ if cookies == 'off': req = SESSION.get('https://www.netflix.com/api/shakti/' + BUILD + '/metadata?movieid=' + movieid + '&drmSystem=widevine&isWatchlistEnabled=false&isShortformEnabled=false&isVolatileBillboardsEnabled=false') else: req = requests.get('https://www.netflix.com/api/shakti/' + BUILD + '/metadata?movieid=' + movieid + '&drmSystem=widevine&isWatchlistEnabled=false&isShortformEnabled=false&isVolatileBillboardsEnabled=false', cookies=cookies) """ req = requests.get('https://www.netflix.com/api/shakti/' + BUILD + '/metadata?movieid=' + movieid + '&drmSystem=widevine&isWatchlistEnabled=false&isShortformEnabled=false&isVolatileBillboardsEnabled=false', cookies=cookies) return json.loads(req.text) def fetch_metadata_movie(BUILD, movieid): #global BUILD #cookies = 'on' #if BUILD == '': # BUILD = login(username, password) cookies = parseCookieFile('cookies.txt') #BUILD = get_build() BUILD = 'vafe38bd5' print(BUILD) """ if cookies == 'off': req = SESSION.get('https://www.netflix.com/api/shakti/' + BUILD + '/metadata?movieid=' + movieid + '&drmSystem=widevine&isWatchlistEnabled=false&isShortformEnabled=false&isVolatileBillboardsEnabled=false') else: req = requests.get('https://www.netflix.com/api/shakti/' + BUILD + '/metadata?movieid=' + movieid + '&drmSystem=widevine&isWatchlistEnabled=false&isShortformEnabled=false&isVolatileBillboardsEnabled=false', cookies=cookies) """ req = requests.get('https://www.netflix.com/api/shakti/' + BUILD + '/metadata?movieid=' + movieid + '&drmSystem=widevine&isWatchlistEnabled=false&isShortformEnabled=false&isVolatileBillboardsEnabled=false', cookies=cookies) return json.loads(req.text) episodes = [] if args.season: nf_cfg = NetflixConfig(0, None, None, [], ['all'], None, args.region) username, password = nf_cfg.get_login() #BUILD = login(username, password) if BUILD is not None: info = fetch_metadata(str(args.title[0])) serial_title = info['video']['title'] serial_title = re.sub(r'[/\\:*?"<>|]', '', serial_title) for season in info['video']['seasons']: if season['seq'] == args.season: episode_list = season['episodes'] #print(len(episode_list)) if args.episode_start: #episode_list = episode_list[(int(args.episode_start) - 1):] episode_list = [episode_list[(int(args.episode_start) - 1)]] #print(episode_list) for episode in episode_list: if episode['seq'] > args.skip: episodes.append(( episode['episodeId'], "{}.S{}E{}.{}".format( serial_title.replace(' ', '.').replace('"', '.').replace('"', '.').replace('(', '').replace(')', ''), str(season['seq']).zfill(2), str(episode['seq']).zfill(2), episode['title'].replace(',', '').replace(':', '').replace('?', '').replace("'", '').replace(' ', '.').replace('/', '').replace('"', '.').replace('"', '.').replace('(', '').replace(')', '')))) else: episodes = [(args.title[0], args.outputfile)] def get_movie_name(): #nf_cfg = NetflixConfig(0, None, None, [], ['all'], None, args.region) #username, password = nf_cfg.get_login() #BUILD = '' #BUILD = login(username, password) #print(BUILD) BUILD = globals()['BUILD'] if BUILD is not None: info = fetch_metadata_movie(BUILD, str(args.title[0])) serial_title = info['video']['title'] #serial_title = 'title' synopsis = info['video']['synopsis'] #synopsis = '' year = info['video']['year'] #year = str('2019') try: boxart = info['video']['boxart'][0]['url'] except IndexError: boxart = '' serial_title = re.sub(r'[/\\:*?"<>|]', '', serial_title) #print(serial_title) logger.info("ripping {} {}".format(serial_title, serial_title.replace('"', '.').replace('"', '.').replace('(', '').replace(')', ''))) logger.info("boxart {} ".format(boxart)) logger.info("synopsis {} ".format(synopsis)) logger.info("year {} ".format(year)) return str(serial_title.replace('"', '.').replace('"', '.').replace('(', '').replace(')', '')) else: return str('') nf_profiles = NetflixProfiles(args.profile, args.quality) for title, outputfile in episodes: if args.season: if args.profile == 'h264': codec_name = 'x264' if args.profile == 'h264_main': codec_name = 'x264' if args.profile == 'h264_high': codec_name = 'x264' if args.profile == 'hevc': codec_name = 'h265' if args.profile == 'hdr': codec_name = 'hdr' if args.profile == 'vp9': codec_name = 'VP9' if args.profile == 'all': codec_name = 'x264' group = 'MI' logger.info("ripping {}: {}".format(title, outputfile)) outputfile = outputfile + '.' + str(args.quality) + '.NF.WEB-DL.' + 'AUDIOCODEC' + '.' + codec_name + '-' + group outputfile1 = outputfile + '.' + str(args.quality) + '.NF.WEB-DL.' + 'AUDIOCODEC' + '.' + codec_name + '-' + group if not args.season: if args.profile == 'h264': codec_name = 'x264' if args.profile == 'h264_main': codec_name = 'x264' if args.profile == 'h264_high': codec_name = 'x264' if args.profile == 'hevc': codec_name = 'h265' if args.profile == 'hdr': codec_name = 'hdr' if args.profile == 'vp9': codec_name = 'VP9' if args.profile == 'all': codec_name = 'x264' group = 'NFT' logger.info("ripping {}: {}".format(title, outputfile)) info = fetch_metadata_movie(BUILD, str(args.title[0])) #print(info) year = info['video']['year'] #year = str('2019') outputfile = get_movie_name().replace("'", '').replace(' ', '.').replace('"', '.').replace('"', '.').replace('(', '').replace(')', '') + '.'+ str(year) + '.' + str(args.quality) + '.NF.WEB-DL.'+ 'AUDIOCODEC' + '.' + codec_name + '-' + group outputfile1 = get_movie_name().replace("'", '').replace(' ', '.').replace('"', '.').replace('"', '.').replace('(', '').replace(')', '') + '.'+ str(year) + '.' + str(args.quality) + '.NF.WEB-DL.'+ 'AUDIOCODEC' + '.' + codec_name + '-' + group if args.audiolang: audiolang = args.audiolang else: audiolang = None if args.quality is not None: profiles = nf_profiles.get_all() else: profiles = nf_profiles.get_all() nf_cfg = NetflixConfig(title, profiles, None, [], ['all'], audiolang, args.region) nf_client = NetflixClient(nf_cfg) """ if not args.season: outputfile = outputfile + '_' + str(args.profile) outputfile1 = outputfile + '_' + str(args.profile) else: outputfile1 = outputfile + '_' + str(args.profile) outputfile = outputfile + '_' + str(args.profile) """ wvdownloader_config = WvDownloaderConfig(nf_client, outputfile, args.sub_type, args.info, args.skip_cleanup, args.dont_mux, args.subs_only, args.license, args.quality, args.profile) wvdownloader = WvDownloader(wvdownloader_config) wvdownloader.run()