Netflix-4K-Script/netflix.py

383 lines
16 KiB
Python

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()