upload
This commit is contained in:
parent
9df940f1fd
commit
bf3c3712dd
13
#movie.bat
Normal file
13
#movie.bat
Normal file
@ -0,0 +1,13 @@
|
||||
@ECHO OFF
|
||||
ECHO Put Netflix Id here :
|
||||
set /p MPD=
|
||||
|
||||
ECHO Quality Select :
|
||||
set /p qu=
|
||||
NFripper.py %MPD% -o Downloads -q %qu% --main --prv ca-tor.pvdata.host --alang hin eng
|
||||
|
||||
echo
|
||||
pause
|
||||
|
||||
|
||||
@ECHO OFF
|
16
#series.bat
Normal file
16
#series.bat
Normal file
@ -0,0 +1,16 @@
|
||||
@ECHO OFF
|
||||
ECHO Put Netflix Id here :
|
||||
set /p MPD=
|
||||
|
||||
ECHO Season No :
|
||||
set /p se=
|
||||
|
||||
ECHO Quality Select :
|
||||
set /p qu=
|
||||
NFripper.py %MPD% -o Downloads -q %qu% -s %se% --main --prv ca-tor.pvdata.host --alang hin eng
|
||||
|
||||
echo
|
||||
pause
|
||||
|
||||
|
||||
@ECHO OFF
|
128
NFripper.py
Normal file
128
NFripper.py
Normal file
@ -0,0 +1,128 @@
|
||||
import argparse, json, os, logging
|
||||
from configs.config import tool
|
||||
from helpers.proxy_environ import proxy_env
|
||||
from datetime import datetime
|
||||
from services.netflix import netflix
|
||||
|
||||
script_name = "NF Ripper"
|
||||
script_ver = "2.0.1.0"
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
parser = argparse.ArgumentParser(description=f">>> {script_name} {script_ver} <<<")
|
||||
parser.add_argument("content", nargs="?", help="Content URL or ID")
|
||||
parser.add_argument("-q", dest="customquality", nargs=1, help="For configure quality of video.", default=[])
|
||||
parser.add_argument("-o", dest="output", help="download all assets to directory provided.")
|
||||
parser.add_argument("-f", dest="output_folder", help="force mux .mkv files to directory provided", action="store", default=None)
|
||||
parser.add_argument("--nv", dest="novideo", help="dont download video", action="store_true")
|
||||
parser.add_argument("--na", dest="noaudio", help="dont download audio", action="store_true")
|
||||
parser.add_argument("--ns", dest="nosubs", help="dont download subs", action="store_true")
|
||||
parser.add_argument("-e", dest="episodeStart", help="it will start downloading the season from that episode.", default=None)
|
||||
parser.add_argument("-s", dest="season", help="it will start downloading the from that season.", default=None)
|
||||
parser.add_argument("--keep", dest="keep", help="well keep all files after mux, by default all erased.", action="store_true")
|
||||
parser.add_argument("--only-2ch-audio", dest="only_2ch_audio", help="to force get only eac3 2.0 Ch audios.", action="store_true")
|
||||
parser.add_argument("--alang", dest="audiolang", nargs="*", help="download only selected audio languages", default=[],)
|
||||
parser.add_argument("--AD", '--adlang', dest="AD", nargs="*", help="download only selected audio languages", default=[],)
|
||||
parser.add_argument("--slang", dest="sublang", nargs="*", help="download only selected subtitle languages", default=[],)
|
||||
parser.add_argument("--flang", dest="forcedlang", nargs="*", help="download only selected forced subtitle languages", default=[],)
|
||||
parser.add_argument('-t', "--title", dest="titlecustom", nargs=1, help="Customize the title of the show", default=[],)
|
||||
parser.add_argument('-p', "--prompt", dest="prompt", help="will Enable the yes/no prompt when URLs are grabbed.", action="store_true")
|
||||
parser.add_argument('-keys', "--license", dest="license", help="print all profiles keys and exit.", action="store_true")
|
||||
parser.add_argument("--audio-bitrate", dest="custom_audio_bitrate", nargs=1, help="For configure bitrate of audio.", default=[])
|
||||
parser.add_argument("--aformat-2ch","--audio-format-2ch", dest="aformat_2ch",nargs=1, help="For configure format of audio.", default=[],)
|
||||
parser.add_argument("--aformat-51ch","--audio-format-51ch", dest="aformat_51ch",nargs=1, help="For configure format of audio.", default=[],)
|
||||
parser.add_argument("--android-login", dest="android_login", help="will log netflix using android api and save cookies nd build.", action="store_true",)
|
||||
parser.add_argument("--search", action="store", dest="search", help="download using netflix search for the movie/show.", default=0,)
|
||||
parser.add_argument("--hevc", dest="hevc", help="will return HEVC profile", action="store_true")
|
||||
parser.add_argument("--hdr", dest="hdr", help="will return HDR profile", action="store_true")
|
||||
parser.add_argument("--high", dest="video_high", help="return MSL High Video manifest for hpl videos, usually small size low bitrate.", action="store_true",)
|
||||
parser.add_argument("--main", dest="video_main", help="return MSL Main Video manifest for mpl videos, usually Big size High bitrate.", action="store_true",)
|
||||
parser.add_argument("--check", dest="check", help="hpl vs mpl.", action="store_true",)
|
||||
parser.add_argument("--all-audios", dest="allaudios", help="all download audios of the movie/show", action="store_true",)
|
||||
parser.add_argument("--all-forced", dest="allforcedlang", help="all download forced subs of the movie/show", action="store_true",)
|
||||
parser.add_argument("--no-aria2c", dest="noaria2c", help="not use aria2c for download, will use python downloader.", action="store_true",)
|
||||
|
||||
# PROXY
|
||||
parser.add_argument("--nrd", action="store", dest="nordvpn", help="add country for nordvpn proxies.", default=0,)
|
||||
parser.add_argument("--prv", action="store", dest="privtvpn", help="add country for privtvpn proxies.", default=0,)
|
||||
parser.add_argument("--no-dl-proxy", dest="no_download_proxy", help="do not use proxy will downloading files", action="store_true", default=False,)
|
||||
|
||||
# PACK
|
||||
parser.add_argument("--gr", dest="muxer_group", help="add group name to use that will override the one in config", action="store", default=None)
|
||||
parser.add_argument("--upload", dest="upload_ftp", help="upload the release after packing", action="store_true", default=None)
|
||||
parser.add_argument("--pack", dest="muxer_pack", help="pack the release", action="store_true", default=None)
|
||||
parser.add_argument("--confirm", dest="confirm_upload", help="ask confirming before upload the packed release", action="store_true", default=None)
|
||||
parser.add_argument("--imdb", dest="muxer_imdb", help="add imdb for the title for packing", action="store", default=None)
|
||||
parser.add_argument("--scheme", dest="muxer_scheme", help="set muxer scheme name", default=None)
|
||||
# cleaner
|
||||
parser.add_argument("--clean-add", dest="clean_add", nargs="*", help="add more extension of files to be deleted", default=[],)
|
||||
parser.add_argument("--clean-exclude", dest="clean_exclude", nargs="*", help="add more extension of files to not be deleted", default=[],)
|
||||
parser.add_argument("--log-level", default="info", dest="log_level", choices=["debug", "info", "error", "warning"], help="choose level")
|
||||
parser.add_argument("--log-file", dest="log_file", help="set log file for debug", default=None)
|
||||
args = parser.parse_args()
|
||||
|
||||
start = datetime.now()
|
||||
|
||||
if args.log_file:
|
||||
logging.basicConfig(
|
||||
filename=args.log_file,
|
||||
format="%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - %(message)s",
|
||||
datefmt="%Y-%m-%d %I:%M:%S %p",
|
||||
level=logging.DEBUG,
|
||||
)
|
||||
|
||||
else:
|
||||
if args.log_level.lower() == "info":
|
||||
logging.basicConfig(format="%(message)s", level=logging.INFO)
|
||||
elif args.log_level.lower() == "debug":
|
||||
logging.basicConfig(
|
||||
format="%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - %(message)s",
|
||||
datefmt="%Y-%m-%d %I:%M:%S %p",
|
||||
level=logging.DEBUG,
|
||||
)
|
||||
elif args.log_level.lower() == "warning":
|
||||
logging.basicConfig(
|
||||
format="%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - %(message)s",
|
||||
datefmt="%Y-%m-%d %I:%M:%S %p",
|
||||
level=logging.WARNING,
|
||||
)
|
||||
elif args.log_level.lower() == "error":
|
||||
logging.basicConfig(
|
||||
format="%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - %(message)s",
|
||||
datefmt="%Y-%m-%d %I:%M:%S %p",
|
||||
level=logging.ERROR,
|
||||
)
|
||||
|
||||
logging.getLogger(__name__)
|
||||
|
||||
group = {
|
||||
"UPLOAD": args.upload_ftp,
|
||||
"IMDB": args.muxer_imdb,
|
||||
"SCHEME": args.muxer_scheme,
|
||||
"PACK": args.muxer_pack,
|
||||
"GROUP": args.muxer_group,
|
||||
"CONFIRM": args.confirm_upload,
|
||||
"EXTRA_FOLDER": args.output_folder,
|
||||
}
|
||||
|
||||
# ~ commands
|
||||
proxy, ip = proxy_env(args).Load()
|
||||
commands = {"aria2c_extra_commands": proxy, "group": group}
|
||||
logging.debug(commands)
|
||||
|
||||
if args.license:
|
||||
args.prompt = False
|
||||
|
||||
l = "\n__________________________\n"
|
||||
print(
|
||||
f"\n-- {script_name} --{l}\nVERSION: {script_ver}{l}\nIP: {ip}{l}"
|
||||
)
|
||||
|
||||
netflix_ = netflix(args, commands)
|
||||
netflix_.main_netflix()
|
||||
|
||||
print(
|
||||
"\nNFripper took {} Sec".format(
|
||||
int(float((datetime.now() - start).total_seconds()))
|
||||
)
|
||||
) # total seconds
|
BIN
bin/BeHappy/plugins32/LSMASHSource.dll
Normal file
BIN
bin/BeHappy/plugins32/LSMASHSource.dll
Normal file
Binary file not shown.
BIN
bin/BeHappy/plugins32/TimeStretch.dll
Normal file
BIN
bin/BeHappy/plugins32/TimeStretch.dll
Normal file
Binary file not shown.
BIN
bin/BeHappy/plugins32/desktop.ini
Normal file
BIN
bin/BeHappy/plugins32/desktop.ini
Normal file
Binary file not shown.
BIN
bin/tools/MediaInfo.exe
Normal file
BIN
bin/tools/MediaInfo.exe
Normal file
Binary file not shown.
BIN
bin/tools/aria2c.exe
Normal file
BIN
bin/tools/aria2c.exe
Normal file
Binary file not shown.
BIN
bin/tools/ffmpeg.exe
Normal file
BIN
bin/tools/ffmpeg.exe
Normal file
Binary file not shown.
BIN
bin/tools/ffplay.exe
Normal file
BIN
bin/tools/ffplay.exe
Normal file
Binary file not shown.
BIN
bin/tools/ffprobe.exe
Normal file
BIN
bin/tools/ffprobe.exe
Normal file
Binary file not shown.
BIN
bin/tools/mkvmerge.exe
Normal file
BIN
bin/tools/mkvmerge.exe
Normal file
Binary file not shown.
BIN
bin/tools/mp4decrypt.exe
Normal file
BIN
bin/tools/mp4decrypt.exe
Normal file
Binary file not shown.
BIN
bin/tools/mp4dump.exe
Normal file
BIN
bin/tools/mp4dump.exe
Normal file
Binary file not shown.
13
checker.bat
Normal file
13
checker.bat
Normal file
@ -0,0 +1,13 @@
|
||||
@ECHO OFF
|
||||
ECHO Put Netflix Id here :
|
||||
set /p MPD=
|
||||
|
||||
ECHO Quality Select :
|
||||
set /p qu=
|
||||
NFripper.py %MPD% -o Downloads -q %qu% --check
|
||||
|
||||
echo
|
||||
pause
|
||||
|
||||
|
||||
@ECHO OFF
|
113
configs/Cookies/cookies_nf.txt
Normal file
113
configs/Cookies/cookies_nf.txt
Normal file
@ -0,0 +1,113 @@
|
||||
{
|
||||
"BUILD_IDENTIFIER": "v970d0ec4",
|
||||
"cookies": {
|
||||
"_ga": [
|
||||
"GA1.2.1140108570.1617215940",
|
||||
0
|
||||
],
|
||||
"NetflixId": [
|
||||
"v%3D2%26ct%3DBQAOAAEBEHOzu6KP28kdADXLobUUrROB0PoBjq3JxcX1vGfDcXUv4xVDQq5H6XsQyE1Frj5SPGO99pNqlZl9A5O3CZUdxiur6u9F5dqAyA0veRgT2bVpQnJ-h7kBmZdPSr3HyGHD9fMnj07AjgioUxeWFsJPky9pVuFRADdnSQXFFqK1NpW2TpDRpGEFRInaAZ9Hr-y6y4J7ggTgHQhbL8CI6NgAHFs8QgPEC9jcyvyFKQmRIBUeU-HS2-WSNlNwSxquZ4bNGBasoTIMGE_8A3R2Mo4lc-qio2Df8Qdv-elVwj8S6VeH8v-brCDpPk-VcDbQp1O2sPb9oKnbCS0DNYL3TqGXzMBNVrL_nHfxDEbPss8929Tc9Ra5HA3635DKbxEJc-tq3u9xUeb_DszXHIS5O-sg6595Pn-LPWBxphkPKdftdLYsQD1RzR0ena_UNrvr-8lIE5TxCXJhLXK0msAfypuevVH1XKXN2FsHJyR0JbbzkOYN2KNxCmRfNkYezY28fKRoufM1F6gF5HDMxuLzw_KjrDV6is18lbNbcmP32_TY2aJnWfHqSaGw5DWnQ3shDKo4SltLUjCuJzYGY7YJAEM-3ifbtwN-QgKyWTUsCwtrrYlDnpUy4rs3rnJyAu8EElWymMkV%26bt%3Ddbl%26ch%3DAQEAEAABABSprqjfXbW6YL0tVF0tz16hvWyTr9LF4jE.%26mac%3DAQEAEAABABRoyiGmz8qurX494b0O21l13oiiV94g9CU.",
|
||||
0
|
||||
],
|
||||
"OptanonConsent": [
|
||||
"isIABGlobal=false&datestamp=Fri+Jun+18+2021+07%3A58%3A49+GMT-0700+(Pacific+Daylight+Time)&version=6.6.0&consentId=a93027f9-5a1c-410f-8272-e0a1ea3e658d&interactionCount=1&landingPath=NotLandingPage&groups=C0001%3A1%2CC0002%3A1%2CC0004%3A0&hosts=H1%3A1%2CH12%3A1%2CH13%3A1%2CH27%3A0%2CH28%3A0%2CH30%3A0&AwaitingReconsent=false",
|
||||
0
|
||||
],
|
||||
"SecureNetflixId": [
|
||||
"v%3D2%26mac%3DAQEAEQABABS0Wg3y7pHcIuzNaFVA9OL1hOBszVcpdKc.%26dt%3D1623992368587",
|
||||
0
|
||||
],
|
||||
"cf_token": [
|
||||
"5d16d7e4-f422-4505-b81a-f13f81d06b71",
|
||||
0
|
||||
],
|
||||
"flwssn": [
|
||||
"f30c6737-15f8-4a97-8102-a0162bac69bc",
|
||||
0
|
||||
],
|
||||
"memclid": [
|
||||
"5eb11c58-f94f-46a1-9916-a877c78cc950",
|
||||
0
|
||||
],
|
||||
"nfvdid": [
|
||||
"BQFmAAEBEHVhRA5F47wt0VzEXxA4lotgv2JbGEnTuU3t2ACmhEmOKuDHWsixD22MEvRfk1SyBSysA4pNyu4UnaMbEBYYAAhCvmGD6sG8LGI_wNxCLpENpUPoF1FF8p3ZOUGBcLkMl5UDunPJGTSHibJ6rP5eZwgX",
|
||||
0
|
||||
],
|
||||
"pas": [
|
||||
"%7B%22supplementals%22%3A%7B%22muted%22%3Atrue%7D%7D",
|
||||
0
|
||||
],
|
||||
"playerPerfMetrics": [
|
||||
"%7B%22uiValue%22%3A%7B%22throughput%22%3A24045%2C%22throughputNiqr%22%3A0.1387332986123521%7D%2C%22mostRecentValue%22%3A%7B%22throughput%22%3A24044.9%2C%22throughputNiqr%22%3A0.1387332986123521%7D%7D",
|
||||
0
|
||||
],
|
||||
"profilesNewSession": [
|
||||
"0",
|
||||
0
|
||||
],
|
||||
"__cfduid": [
|
||||
"ddc5ba21ab87d546424a73381309e8af61619034211",
|
||||
0
|
||||
],
|
||||
"_admrla": [
|
||||
"2.0-a88ab21a-c44f-b78a-d6e3-05b3cde370fc",
|
||||
0
|
||||
],
|
||||
"_awl": [
|
||||
"2.1619452424.0.4-7dc0a353-a88ab21ac44fb78ad6e305b3cde370fc-6763652d617369612d6561737431-6086e208-1",
|
||||
0
|
||||
],
|
||||
"_gid": [
|
||||
"GA1.2.962657418.1619405560",
|
||||
0
|
||||
],
|
||||
"ccpaApplies": [
|
||||
"false",
|
||||
0
|
||||
],
|
||||
"ccpaUUID": [
|
||||
"e05cd352-14f3-4d80-9500-b1d81fee7cd0",
|
||||
0
|
||||
],
|
||||
"dnsDisplayed": [
|
||||
"true",
|
||||
0
|
||||
],
|
||||
"signedLspa": [
|
||||
"false",
|
||||
0
|
||||
],
|
||||
"thx_guid": [
|
||||
"8a067b33cfaa47539ea40975e2b80069",
|
||||
0
|
||||
],
|
||||
"_sp_v1_csv": [
|
||||
"null",
|
||||
0
|
||||
],
|
||||
"_sp_v1_data": [
|
||||
"2:334049:1619034213:0:9:0:9:0:0:_:-1",
|
||||
0
|
||||
],
|
||||
"_sp_v1_lt": [
|
||||
"1:",
|
||||
0
|
||||
],
|
||||
"_sp_v1_opt": [
|
||||
"1:",
|
||||
0
|
||||
],
|
||||
"_sp_v1_ss": [
|
||||
"1:H4sIAAAAAAAAAItWqo5RKimOUbKKhjHySnNydGKUUpHYJWCJ6traWFwSSjrUNwiffqVYAG6Fhl26AAAA",
|
||||
0
|
||||
],
|
||||
"_sp_v1_uid": [
|
||||
"1:365:378b6189-c59f-4807-abd1-95bcd2c5779d",
|
||||
0
|
||||
],
|
||||
"consentUUID": [
|
||||
"ef33150e-dde3-41af-b14c-18d858e5a51b",
|
||||
0
|
||||
]
|
||||
}
|
||||
}
|
2025
configs/KEYS/netflix.keys
Normal file
2025
configs/KEYS/netflix.keys
Normal file
File diff suppressed because it is too large
Load Diff
11
configs/Tokens/netflix_token.json
Normal file
11
configs/Tokens/netflix_token.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"mastertoken": {
|
||||
"tokendata": "eyJzZXNzaW9uZGF0YSI6IkJRQ0FBQUVCRUF6SENReUt4ZklvZ3dQZ2FvMndhNGVCd0xTckZoK29QTGJHL20vWU9oQjQyZHloUUYxTmxkZm5NamNSTy96NUVKdnZJOGp4MllFYytWZk0rSlJ0RURMZFA1VTcyUWx5bS9RbnJXTmFuYlEyM3Fpa2YvbHRGYWFqbXlJU3NzdnpRU2VvL3hPL1p0TFZieGR6cWhYMG9DdmtLWlk1RFRXN3Y2aVh3dERZZlJhR0JRYXVLZjBpdERsY0ZWVVN1alExR2xSVklGZk42YVE3WEFKd3JFTFIvTlVqQmJmYnl6K2FrN1ZZVEhBZnFScHQ4WjBXZk0xa0VySjVJYjgvcE9iUU9FZHdPTlY3YW9lSjRMT2hNTnMycE9sZ3MwNkd6eUZ1Nk13RytPSHFqMURaVzdNL3hUOWdkM01ncGdKejFkY0c4ODlTU0hUdVVSc2JST0hmVy80akU2SlhySTUrSjZLT0UyeGRBdVVoNjY3TnlNMmtoOURBM25lOFBLaHRrTjlmdG1HdEszZ2l1S01xTERmdlQ3RDVGUGVaU1RKY25wenFjMk9YWW12RHBoY0JmWFVXZEhTUWRUbUtjU01zL1o4TUt5UVB3emhLMzFVVldWWllUbEIyODMzWG1WNDArSXZrK1gzdUN1MGlMUjJaVkRXYzh2T1V4dEFSWi9vM3ZEOC9heUtQMTh3UmI1WndzNkl2R0dRSkhPU2MwTHB6RS8zUmdxUXU2Z2FYVU5tWjdlMTEzWEVMdFZZQktzRmFZMktJQVViNFVvT1F1VnVkVnNMNTExUk5OQzJZYnA4Q1hvWlZlY2tNaU9ydDZNS3hNUFE9IiwicmVuZXdhbHdpbmRvdyI6MTYyNDA4NjI2Miwic2VyaWFsbnVtYmVyIjo1MDkzNTMyMzUxNTM2MjQsImV4cGlyYXRpb24iOjE2MjUyMDk0NjIsInNlcXVlbmNlbnVtYmVyIjoxfQ==",
|
||||
"signature": "AQEAgQABASD1vbC9zQfnxwNr/7nLFkWyxRW7Z52QGWLEGk97hYBSYtNcJnM="
|
||||
},
|
||||
"sequence_number": 1,
|
||||
"encryption_key": "YAeCFbGwVf/aukgoxE3tSg==",
|
||||
"sign_key": "75QOynO/ysXBPimd9vCBSCI5/JQ7VCxAe4Ev/6hLJc4=",
|
||||
"RSA_KEY": "-----BEGIN RSA PRIVATE KEY-----\nMIIEogIBAAKCAQEAvMQdqQ7cG30cZdxYRamhySuvTWvUtsZp6EVmV4U1msqzceb5\nevK1D8rac87mdUUL9e5syaKTarVJe9bzl5lPnugSDPUEWuXS3vAynJA9pzUHEJdN\nZVkv43IdyXI+eBV+aJ4f+DB06MYvUxyDgPyfvCIKJPZ1Pj0ZHN2rhpK2bx2xnZh4\nBAoSIqlSYeChWB9O55sDYkCooAXd9zNJ7EBerqNlQr5sImY0izD1sPdfJiEfZLKj\n/+xr0PPrdTKeQmcR/r30K+4lVD1tYVPSy4MZsDVu3KgHtMBDPvx+DyaXdtdAdxpC\nkR6uqw+Q0H7KoedzdmlOMyvTVHLhgbVtxDEkuwIDAQABAoH/P6ydjoIypF12y3oE\nPDElo+f3dA14Dj/4geVauSLTCvifj1CUQpqUFeCyU9okZyhW+UvOsNqiOlyJjiwU\n9wYStIekZdNI1lhddsXRhorfSW8VoGpQf6WeWH3LK1o9I5+H0uu1eeoYXEvIP5Nm\n+iPqlKykv1+MwZsk1TTHyFLu1Afd7hG80c2UHHvvGioD0YregsrKXcLXi5OEkAS+\n5SiwKpbelwLcqK58SVN+ajnRvz6na6Fm4wjWq4wynrtX0RPD2P8+aw0X805o9fsP\nU3zRx4Dy3huazIBnadTqAEGbF73OWSA+Tow+LMRvUSqdWZMiX2ikLRwkTKFaaq+N\nObPhAoGBANqo5TbysRdRTerXxXqNlewEiYNH3Vn5pz4t1W/iRIT8BDKAD2FHcZ25\n4bfGcRmQ8wO3gcPPgVyXGf100RU0IR6lVjrcJT3w29lPs7AOhElDWMBC0fcA5JSd\nkWFMDUSobaXxZ8HYQJG6eQTEuQebm2PffppZUnVKTY2rhDaOCpmLAoGBAN0AXWiC\ngA8/6P48EYc8sQq/7KZ1g2Urxqxhc/wNU6FdKs582HMEeRYBA2PJsamYh0n/Wp5k\nfFJfb7HU4yCISzT7iHHjhLuuUR0UMmMkwY/x4YE5Ri1hlrS3SdGdfV6ufqe9jiGg\nboJ1rI0ieJmdyVWmhTK9CXuF/gB/6ik55CeRAoGBAMOr9orIfW9HY7mvY1n7T9lI\naiJf8hZtUZtT+rdHvVdgCwWCEcFU5LhnujTx0Q425zFBS0+F5taLpUdp/RzDbIv3\nGwZLMMyQOLzsFPmM1BaXvNk4MpqeYu8XXhy6qPjy3ERulhIiyg1e2KNKw+Wp+1FR\nlALdweuSFXqcrREA5T1nAoGBAIYoeou+7M5VFbN/84QNK8xCxf4myCTadjie0DHq\nRSJn1FyVHTB1PqxE4THqdpdlqHsbMH+GsJGwrbVebqKJGl6Hc0TvwNvN7h+g6xWU\ncoxXYXV4t0lFPJ9nxMAiwsB/XROm1mlDYtJ/bMggbOWUC2ybMbCjYOZDaPYUsKlm\nI0KBAoGAC0EsDBZZ6/ymQgenaNbTErN04i6Zii0U9XZ4Do72cRQTYsRELKqXyNwF\nwy//GzwqOXPFTaaY/RL0ISaV3xWU2MkI2Scmr+oGyEEKeE1uul9ZCo9l/2Jin2X9\n6gsH4QryiP+we7L1p2e1sdYmY3QWufuWjYI4e6iJ/vJX+WHM95g=\n-----END RSA PRIVATE KEY-----",
|
||||
"expiration": 1625209462
|
||||
}
|
0
configs/__init__.py
Normal file
0
configs/__init__.py
Normal file
BIN
configs/__pycache__/__init__.cpython-36.pyc
Normal file
BIN
configs/__pycache__/__init__.cpython-36.pyc
Normal file
Binary file not shown.
BIN
configs/__pycache__/__init__.cpython-39.pyc
Normal file
BIN
configs/__pycache__/__init__.cpython-39.pyc
Normal file
Binary file not shown.
BIN
configs/__pycache__/config.cpython-36.pyc
Normal file
BIN
configs/__pycache__/config.cpython-36.pyc
Normal file
Binary file not shown.
BIN
configs/__pycache__/config.cpython-39.pyc
Normal file
BIN
configs/__pycache__/config.cpython-39.pyc
Normal file
Binary file not shown.
163
configs/config.py
Normal file
163
configs/config.py
Normal file
@ -0,0 +1,163 @@
|
||||
import sys, os, random, string, platform
|
||||
from os.path import dirname
|
||||
from os.path import join
|
||||
from pywidevine.cdm import cdm, deviceconfig
|
||||
|
||||
dirPath = dirname(dirname(__file__)).replace("\\", "/")
|
||||
|
||||
class utils:
|
||||
def __init__(self):
|
||||
self.dir = dirPath
|
||||
|
||||
def random_hex(self, length: int) -> str:
|
||||
"""return {length} of random string"""
|
||||
return "".join(random.choice("0123456789ABCDEF") for _ in range(length))
|
||||
|
||||
utils_ = utils()
|
||||
|
||||
#####################################(DEVICES)#####################################
|
||||
|
||||
devices_dict = {
|
||||
"android_general": deviceconfig.device_android_general,
|
||||
}
|
||||
|
||||
DEVICES = {
|
||||
"NETFLIX-MANIFEST": devices_dict["android_general"],
|
||||
"NETFLIX-LICENSE": devices_dict["android_general"],
|
||||
}
|
||||
|
||||
#####################################(MUXER)#####################################
|
||||
|
||||
MUXER = {
|
||||
"muxer_file": f"{dirPath}/bin/muxer.json",
|
||||
"mkv_folder": None,
|
||||
"DEFAULT": False, # to use the normal renaming. EX: Stranger Things S01E01 [1080p].mkv
|
||||
"AUDIO": "hin", # default audio language.
|
||||
"SUB": "None", # default subtitle language. EX: "eng" or "spa"
|
||||
"GROUP": "Tandav", # to change the group name!. it's also possible to use this "--gr LOL", on the ripping commands.
|
||||
"noTitle": False, # this will remove titles from the episodes EX: (The Witcher S01E01). insstead of (The Witcher S01E01 The End's Beginning).
|
||||
"scheme": "p2p", # add/change any needed scheme naming. it's also possible to use this "--muxscheme repack", on the ripping commands.
|
||||
"schemeslist": {
|
||||
"p2p": "{t}.{r}.{s}.WEB-DL.{ac}.{vc}-{gr}",
|
||||
"test": "{t}.{r}.{s}.WEB-DL-{gr}",
|
||||
},
|
||||
"EXTRAS": [], # extra mkvmerge.exe commands.
|
||||
"FPS24": [],
|
||||
}
|
||||
|
||||
#####################################(PATHS)#####################################
|
||||
|
||||
PATHS = {
|
||||
"DL_FOLDER": "E:/#rips", #
|
||||
"DIR_PATH": f"{dirPath}",
|
||||
"BINARY_PATH": f"{dirPath}/bin",
|
||||
"COOKIES_PATH": f"{dirPath}/configs/Cookies",
|
||||
"KEYS_PATH": f"{dirPath}/configs/KEYS",
|
||||
"TOKENS_PATH": f"{dirPath}/configs/Tokens",
|
||||
"JSON_PATH": f"{dirPath}/json",
|
||||
"LOGA_PATH": f"{dirPath}/bin/tools/aria2c",
|
||||
}
|
||||
|
||||
ARIA2C = {
|
||||
"enable_logging": False, # True
|
||||
}
|
||||
|
||||
SETTINGS = {
|
||||
"skip_video_demux": [],
|
||||
}
|
||||
|
||||
#####################################(VPN)#####################################
|
||||
|
||||
VPN = {
|
||||
"proxies": None, # "http://151.253.165.70:8080",
|
||||
"nordvpn": {
|
||||
"port": "80",
|
||||
"email": "xxx",
|
||||
"passwd": "xxx",
|
||||
"http": "http://{email}:{passwd}@{ip}:{port}",
|
||||
},
|
||||
"private": {
|
||||
"port": "8080",
|
||||
"email": "abdalhmohmd8@gmail.com",
|
||||
"passwd": "123456",
|
||||
"http": "http://{email}:{passwd}@{ip}:{port}",
|
||||
},
|
||||
}
|
||||
|
||||
#####################################(BIN)#####################################
|
||||
|
||||
BIN = {
|
||||
"mp4decrypt_moded": f"{dirPath}/bin/tools/mp4decrypt.exe",
|
||||
"mp4dump": f"{dirPath}/bin/tools/mp4dump.exe",
|
||||
"ffmpeg": f"{dirPath}/bin/tools/ffmpeg.exe",
|
||||
"ffprobe": f"{dirPath}/bin/tools/ffprobe.exe",
|
||||
"MediaInfo": f"{dirPath}/bin/tools/MediaInfo.exe",
|
||||
"mkvmerge": f"{dirPath}/bin/tools/mkvmerge.exe",
|
||||
"aria2c": f"{dirPath}/bin/tools/aria2c.exe",
|
||||
}
|
||||
|
||||
#####################################(Config)#####################################
|
||||
|
||||
Config = {}
|
||||
|
||||
Config["NETFLIX"] = {
|
||||
"cookies_file": f"{dirPath}/configs/Cookies/cookies_nf.txt",
|
||||
"cookies_txt": f"{dirPath}/configs/Cookies/cookies.txt",
|
||||
"keys_file": f"{dirPath}/configs/KEYS/netflix.keys",
|
||||
"token_file": f"{dirPath}/configs/Tokens/netflix_token.json",
|
||||
"email": "Cfklop@max07.club",
|
||||
"password": "1111",
|
||||
"manifest_language": "en-US",
|
||||
"metada_language": "en",
|
||||
"manifestEsn": "NFCDIE-03-{}".format(utils().random_hex(30)),
|
||||
"androidEsn": "NFANDROID1-PRV-P-GOOGLEPIXEL=4=XL-8162-" + utils_.random_hex(64),
|
||||
}
|
||||
|
||||
#####################################(DIRS & FILES)##############################
|
||||
|
||||
def make_dirs():
|
||||
FILES = []
|
||||
|
||||
DIRS = [
|
||||
f"{dirPath}/configs/Cookies",
|
||||
f"{dirPath}/configs/Tokens",
|
||||
f"{dirPath}/bin/tools/aria2c",
|
||||
]
|
||||
|
||||
for dirs in DIRS:
|
||||
if not os.path.exists(dirs):
|
||||
os.makedirs(dirs)
|
||||
|
||||
for files in FILES:
|
||||
if not os.path.isfile(files):
|
||||
with open(files, "w") as f:
|
||||
f.write("\n")
|
||||
|
||||
make_dirs()
|
||||
|
||||
#####################################(tool)#####################################
|
||||
|
||||
class tool:
|
||||
def config(self, service):
|
||||
return Config[service]
|
||||
|
||||
def bin(self):
|
||||
return BIN
|
||||
|
||||
def vpn(self):
|
||||
return VPN
|
||||
|
||||
def paths(self):
|
||||
return PATHS
|
||||
|
||||
def muxer(self):
|
||||
return MUXER
|
||||
|
||||
def devices(self):
|
||||
return DEVICES
|
||||
|
||||
def aria2c(self):
|
||||
return ARIA2C
|
||||
|
||||
def video_settings(self):
|
||||
return SETTINGS
|
629
helpers/Muxer.py
Normal file
629
helpers/Muxer.py
Normal file
@ -0,0 +1,629 @@
|
||||
|
||||
import re, os, sys, subprocess, contextlib, json, glob
|
||||
from configs.config import tool
|
||||
from helpers.ripprocess import ripprocess
|
||||
from pymediainfo import MediaInfo
|
||||
import logging
|
||||
|
||||
|
||||
class Muxer(object):
|
||||
def __init__(self, **kwargs):
|
||||
self.logger = logging.getLogger(__name__)
|
||||
self.CurrentName_Original = kwargs.get("CurrentName", None)
|
||||
self.CurrentName = kwargs.get("CurrentName", None)
|
||||
self.SeasonFolder = kwargs.get("SeasonFolder", None)
|
||||
self.CurrentHeigh = kwargs.get("CurrentHeigh", None)
|
||||
self.CurrentWidth = kwargs.get("CurrentWidth", None)
|
||||
self.source_tag = kwargs.get("Source", None)
|
||||
self.AudioProfile = self.get_audio_id() # kwargs.get("AudioProfile", None)
|
||||
self.VideoProfile = self.get_video_id() # kwargs.get("VideoProfile", None)
|
||||
self.mkvmerge = tool().bin()["mkvmerge"]
|
||||
self.merge = []
|
||||
self.muxer_settings = tool().muxer()
|
||||
|
||||
##############################################################################
|
||||
self.packer = kwargs.get("group", None)
|
||||
self.extra_output_folder = self.packer["EXTRA_FOLDER"]
|
||||
self.Group = (
|
||||
self.packer["GROUP"]
|
||||
if self.packer["GROUP"]
|
||||
else self.muxer_settings["GROUP"]
|
||||
)
|
||||
self.muxer_scheme = (
|
||||
self.packer["SCHEME"]
|
||||
if self.packer["SCHEME"]
|
||||
else self.muxer_settings["scheme"]
|
||||
)
|
||||
|
||||
self.scheme = self.muxer_settings["schemeslist"][self.muxer_scheme]
|
||||
self.Extras = self.muxer_settings["EXTRAS"]
|
||||
self.fps24 = True if self.source_tag in self.muxer_settings["FPS24"] else False
|
||||
self.default_mux = True if self.muxer_settings["DEFAULT"] else False
|
||||
self.PrepareMuxer()
|
||||
|
||||
def is_extra_folder(self):
|
||||
extra_folder = None
|
||||
if self.extra_output_folder:
|
||||
if not os.path.isabs(self.extra_output_folder):
|
||||
raise ValueError("Error you should provide full path dir: {}.".format(self.extra_output_folder))
|
||||
if not os.path.exists(self.extra_output_folder):
|
||||
try:
|
||||
os.makedirs(self.extra_output_folder)
|
||||
except Exception as e:
|
||||
raise ValueError("Error when create folder dir [{}]: {}.".format(e, self.extra_output_folder))
|
||||
extra_folder = self.extra_output_folder
|
||||
return extra_folder
|
||||
|
||||
if self.muxer_settings["mkv_folder"]:
|
||||
if not os.path.isabs(self.muxer_settings["mkv_folder"]):
|
||||
raise ValueError("Error you should provide full path dir: {}.".format(self.muxer_settings["mkv_folder"]))
|
||||
if not os.path.exists(self.muxer_settings["mkv_folder"]):
|
||||
try:
|
||||
os.makedirs(self.muxer_settings["mkv_folder"])
|
||||
except Exception as e:
|
||||
raise ValueError("Error when create folder dir [{}]: {}.".format(e, self.muxer_settings["mkv_folder"]))
|
||||
extra_folder = self.muxer_settings["mkv_folder"]
|
||||
return extra_folder
|
||||
|
||||
return extra_folder
|
||||
|
||||
def PrepareMuxer(self):
|
||||
if self.muxer_settings["noTitle"]:
|
||||
self.CurrentName = self.noTitle()
|
||||
|
||||
extra_folder = self.is_extra_folder()
|
||||
|
||||
if extra_folder:
|
||||
self.SeasonFolder = extra_folder
|
||||
else:
|
||||
if not self.default_mux:
|
||||
if self.SeasonFolder:
|
||||
self.SeasonFolder = self.setFolder()
|
||||
|
||||
return
|
||||
|
||||
def SortFilesBySize(self):
|
||||
file_list = []
|
||||
audio_tracks = (
|
||||
glob.glob(f"{self.CurrentName_Original}*.eac3")
|
||||
+ glob.glob(f"{self.CurrentName_Original}*.ac3")
|
||||
+ glob.glob(f"{self.CurrentName_Original}*.aac")
|
||||
+ glob.glob(f"{self.CurrentName_Original}*.m4a")
|
||||
+ glob.glob(f"{self.CurrentName_Original}*.dts")
|
||||
)
|
||||
|
||||
if audio_tracks == []:
|
||||
raise FileNotFoundError("no audio files found")
|
||||
|
||||
for file in audio_tracks:
|
||||
file_list.append({"file": file, "size": os.path.getsize(file)})
|
||||
|
||||
file_list = sorted(file_list, key=lambda k: int(k["size"]))
|
||||
return file_list[-1]["file"]
|
||||
|
||||
def GetVideoFile(self):
|
||||
videofiles = [
|
||||
"{} [{}p]_Demuxed.mp4",
|
||||
"{} [{}p]_Demuxed.mp4",
|
||||
"{} [{}p] [UHD]_Demuxed.mp4",
|
||||
"{} [{}p] [UHD]_Demuxed.mp4",
|
||||
"{} [{}p] [VP9]_Demuxed.mp4",
|
||||
"{} [{}p] [HIGH]_Demuxed.mp4",
|
||||
"{} [{}p] [VP9]_Demuxed.mp4",
|
||||
"{} [{}p] [HEVC]_Demuxed.mp4",
|
||||
"{} [{}p] [HDR]_Demuxed.mp4",
|
||||
"{} [{}p] [HDR-DV]_Demuxed.mp4",
|
||||
]
|
||||
|
||||
for videofile in videofiles:
|
||||
filename = videofile.format(self.CurrentName_Original, self.CurrentHeigh)
|
||||
if os.path.isfile(filename):
|
||||
return filename
|
||||
|
||||
return None
|
||||
|
||||
def get_video_id(self):
|
||||
video_file = self.GetVideoFile()
|
||||
if not video_file:
|
||||
raise ValueError("No Video file in Dir...")
|
||||
|
||||
media_info = MediaInfo.parse(video_file)
|
||||
track = [track for track in media_info.tracks if track.track_type == "Video"][0]
|
||||
|
||||
if track.format == "AVC":
|
||||
if track.encoding_settings:
|
||||
return "x264"
|
||||
return "H.264"
|
||||
elif track.format == "HEVC":
|
||||
if track.commercial_name == "HDR10" and track.color_primaries:
|
||||
return "HDR.HEVC"
|
||||
if track.commercial_name == "HEVC" and track.color_primaries:
|
||||
return "HEVC"
|
||||
|
||||
return "DV.HEVC"
|
||||
|
||||
return None
|
||||
|
||||
def get_audio_id(self):
|
||||
audio_id = None
|
||||
media_info = MediaInfo.parse(self.SortFilesBySize())
|
||||
track = [track for track in media_info.tracks if track.track_type == "Audio"][0]
|
||||
|
||||
if track.format == "E-AC-3":
|
||||
audioCodec = "DDP"
|
||||
elif track.format == "AC-3":
|
||||
audioCodec = "DD"
|
||||
elif track.format == "AAC":
|
||||
audioCodec = "AAC"
|
||||
elif track.format == "DTS":
|
||||
audioCodec = "DTS"
|
||||
elif "DTS" in track.format:
|
||||
audioCodec = "DTS"
|
||||
else:
|
||||
audioCodec = "DDP"
|
||||
|
||||
if track.channel_s == 8:
|
||||
channels = "7.1"
|
||||
elif track.channel_s == 6:
|
||||
channels = "5.1"
|
||||
elif track.channel_s == 2:
|
||||
channels = "2.0"
|
||||
elif track.channel_s == 1:
|
||||
channels = "1.0"
|
||||
else:
|
||||
channels = "5.1"
|
||||
|
||||
audio_id = (
|
||||
f"{audioCodec}{channels}.Atmos"
|
||||
if "Atmos" in track.commercial_name
|
||||
else f"{audioCodec}{channels}"
|
||||
)
|
||||
|
||||
return audio_id
|
||||
|
||||
def Heigh(self):
|
||||
try:
|
||||
Width = int(self.CurrentWidth)
|
||||
Heigh = int(self.CurrentHeigh)
|
||||
except Exception:
|
||||
return self.CurrentHeigh
|
||||
|
||||
res1080p = "1080p"
|
||||
res720p = "720p"
|
||||
sd = ""
|
||||
|
||||
if Width >= 3840:
|
||||
return "2160p"
|
||||
|
||||
if Width >= 2560:
|
||||
return "1440p"
|
||||
|
||||
if Width > 1920:
|
||||
if Heigh > 1440:
|
||||
return "2160p"
|
||||
return "1440p"
|
||||
|
||||
if Width == 1920:
|
||||
return res1080p
|
||||
elif Width == 1280:
|
||||
return res720p
|
||||
|
||||
if Width >= 1400:
|
||||
return res1080p
|
||||
|
||||
if Width < 1400 and Width >= 1100:
|
||||
return res720p
|
||||
|
||||
if Heigh == 1080:
|
||||
return res1080p
|
||||
elif Heigh == 720:
|
||||
return res720p
|
||||
|
||||
if Heigh >= 900:
|
||||
return res1080p
|
||||
|
||||
if Heigh < 900 and Heigh >= 700:
|
||||
return res720p
|
||||
|
||||
return sd
|
||||
|
||||
def noTitle(self):
|
||||
regex = re.compile("(.*) [S]([0-9]+)[E]([0-9]+)")
|
||||
if regex.search(self.CurrentName):
|
||||
return regex.search(self.CurrentName).group(0)
|
||||
|
||||
return self.CurrentName
|
||||
|
||||
def Run(self, command):
|
||||
self.logger.debug("muxing command: {}".format(command))
|
||||
|
||||
def unbuffered(proc, stream="stdout"):
|
||||
newlines = ["\n", "\r\n", "\r"]
|
||||
stream = getattr(proc, stream)
|
||||
with contextlib.closing(stream):
|
||||
while True:
|
||||
out = []
|
||||
last = stream.read(1)
|
||||
# Don't loop forever
|
||||
if last == "" and proc.poll() is not None:
|
||||
break
|
||||
while last not in newlines:
|
||||
# Don't loop forever
|
||||
if last == "" and proc.poll() is not None:
|
||||
break
|
||||
out.append(last)
|
||||
last = stream.read(1)
|
||||
out = "".join(out)
|
||||
yield out
|
||||
|
||||
proc = subprocess.Popen(
|
||||
command,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
bufsize=1,
|
||||
universal_newlines=True,
|
||||
)
|
||||
self.logger.info("\nStart Muxing...")
|
||||
for line in unbuffered(proc):
|
||||
if "Progress:" in line:
|
||||
sys.stdout.write("\r%s" % (line))
|
||||
sys.stdout.flush()
|
||||
elif "Multiplexing" in line:
|
||||
sys.stdout.write("\r%s" % (line.replace("Multiplexing", "Muxing")))
|
||||
sys.stdout.flush()
|
||||
elif "Error" in line:
|
||||
sys.stdout.write("\r%s" % (line))
|
||||
sys.stdout.flush()
|
||||
|
||||
self.logger.info("")
|
||||
|
||||
def setName(self):
|
||||
|
||||
outputVideo = (
|
||||
self.scheme.replace(
|
||||
"{t}", ripprocess().CleanMyFileNamePlease(self.CurrentName)
|
||||
)
|
||||
.replace("{r}", self.Heigh())
|
||||
.replace("{s}", self.source_tag)
|
||||
.replace("{ac}", self.AudioProfile)
|
||||
.replace("{vc}", self.VideoProfile)
|
||||
.replace("{gr}", self.Group)
|
||||
)
|
||||
|
||||
for i in range(10):
|
||||
outputVideo = re.sub(r"(\.\.)", ".", outputVideo)
|
||||
|
||||
if self.SeasonFolder:
|
||||
outputVideo = os.path.join(os.path.abspath(self.SeasonFolder), outputVideo)
|
||||
outputVideo = outputVideo.replace("\\", "/")
|
||||
|
||||
return f"{outputVideo}.mkv"
|
||||
|
||||
def setFolder(self):
|
||||
folder = (
|
||||
self.scheme.replace(
|
||||
"{t}", ripprocess().CleanMyFileNamePlease(self.SeasonFolder)
|
||||
)
|
||||
.replace("{r}", self.Heigh())
|
||||
.replace("{s}", self.source_tag)
|
||||
.replace("{ac}", self.AudioProfile)
|
||||
.replace("{vc}", self.VideoProfile)
|
||||
.replace("{gr}", self.Group)
|
||||
)
|
||||
|
||||
for i in range(10):
|
||||
folder = re.sub(r"(\.\.)", ".", folder)
|
||||
|
||||
return folder
|
||||
|
||||
def LanguageList(self):
|
||||
LanguageList = [
|
||||
["Hindi", "hin", "hin", "Hindi"],
|
||||
["Tamil", "tam", "tam", "Tamil"],
|
||||
["Telugu", "tel", "tel", "Telugu"],
|
||||
["English", "eng", "eng", "English"],
|
||||
["Afrikaans", "af", "afr", "Afrikaans"],
|
||||
["Arabic", "ara", "ara", "Arabic"],
|
||||
["Arabic (Syria)", "araSy", "ara", "Arabic Syria"],
|
||||
["Arabic (Egypt)", "araEG", "ara", "Arabic Egypt"],
|
||||
["Arabic (Kuwait)", "araKW", "ara", "Arabic Kuwait"],
|
||||
["Arabic (Lebanon)", "araLB", "ara", "Arabic Lebanon"],
|
||||
["Arabic (Algeria)", "araDZ", "ara", "Arabic Algeria"],
|
||||
["Arabic (Bahrain)", "araBH", "ara", "Arabic Bahrain"],
|
||||
["Arabic (Iraq)", "araIQ", "ara", "Arabic Iraq"],
|
||||
["Arabic (Jordan)", "araJO", "ara", "Arabic Jordan"],
|
||||
["Arabic (Libya)", "araLY", "ara", "Arabic Libya"],
|
||||
["Arabic (Morocco)", "araMA", "ara", "Arabic Morocco"],
|
||||
["Arabic (Oman)", "araOM", "ara", "Arabic Oman"],
|
||||
["Arabic (Saudi Arabia)", "araSA", "ara", "Arabic Saudi Arabia"],
|
||||
["Arabic (Tunisia)", "araTN", "ara", "Arabic Tunisia"],
|
||||
[
|
||||
"Arabic (United Arab Emirates)",
|
||||
"araAE",
|
||||
"ara",
|
||||
"Arabic United Arab Emirates",
|
||||
],
|
||||
["Arabic (Yemen)", "araYE", "ara", "Arabic Yemen"],
|
||||
["Armenian", "hye", "arm", "Armenian"],
|
||||
["Assamese", "asm", "asm", "Assamese"],
|
||||
["Bengali", "ben", "ben", "Bengali"],
|
||||
["Basque", "eus", "baq", "Basque"],
|
||||
["British English", "enGB", "eng", "British English"],
|
||||
["Bulgarian", "bul", "bul", "Bulgarian"],
|
||||
["Cantonese", "None", "chi", "Cantonese"],
|
||||
["Catalan", "cat", "cat", "Catalan"],
|
||||
["Simplified Chinese", "zhoS", "chi", "Chinese Simplified"],
|
||||
["Traditional Chinese", "zhoT", "chi", "Chinese Traditional"],
|
||||
["Croatian", "hrv", "hrv", "Croatian"],
|
||||
["Czech", "ces", "cze", "Czech"],
|
||||
["Danish", "dan", "dan", "Danish"],
|
||||
["Dutch", "nld", "dut", "Dutch"],
|
||||
["Estonian", "est", "est", "Estonian"],
|
||||
["Filipino", "fil", "fil", "Filipino"],
|
||||
["Finnish", "fin", "fin", "Finnish"],
|
||||
["Flemish", "nlBE", "dut", "Flemish"],
|
||||
["French", "fra", "fre", "French"],
|
||||
["French Canadian", "caFra", "fre", "French Canadian"],
|
||||
["Canadian French", "caFra", "fre", "Canadian French"],
|
||||
["German", "deu", "ger", "German"],
|
||||
["Greek", "ell", "gre", "Greek"],
|
||||
["Gujarati", "guj", "guj", "Gujarati"],
|
||||
["Hebrew", "heb", "heb", "Hebrew"],
|
||||
["Hungarian", "hun", "hun", "Hungarian"],
|
||||
["Icelandic", "isl", "ice", "Icelandic"],
|
||||
["Indonesian", "ind", "ind", "Indonesian"],
|
||||
["Italian", "ita", "ita", "Italian"],
|
||||
["Japanese", "jpn", "jpn", "Japanese"],
|
||||
["Kannada (India)", "kan", "kan", "Kannada (India)"],
|
||||
["Khmer", "khm", "khm", "Khmer"],
|
||||
["Klingon", "tlh", "tlh", "Klingon"],
|
||||
["Korean", "kor", "kor", "Korean"],
|
||||
["Lithuanian", "lit", "lit", "Lithuanian"],
|
||||
["Latvian", "lav", "lav", "Latvian"],
|
||||
["Malay", "msa", "may", "Malay"],
|
||||
["Malayalam", "mal", "mal", "Malayalam"],
|
||||
["Mandarin", "None", "chi", "Mandarin"],
|
||||
["Mandarin Chinese (Simplified)", "zh-Hans", "chi", "Simplified"],
|
||||
["Mandarin Chinese (Traditional)", "zh-Hant", "chi", "Traditional"],
|
||||
["Yue Chinese", "yue", "chi", "(Yue Chinese)"],
|
||||
["Manipuri", "mni", "mni", "Manipuri"],
|
||||
["Marathi", "mar", "mar", "Marathi"],
|
||||
["No Dialogue", "zxx", "zxx", "No Dialogue"],
|
||||
["Norwegian", "nor", "nor", "Norwegian"],
|
||||
["Norwegian Bokmal", "nob", "nob", "Norwegian Bokmal"],
|
||||
["Persian", "fas", "per", "Persian"],
|
||||
["Polish", "pol", "pol", "Polish"],
|
||||
["Portuguese", "por", "por", "Portuguese"],
|
||||
["Brazilian Portuguese", "brPor", "por", "Brazilian Portuguese"],
|
||||
["Punjabi", "pan", "pan", "Punjabi"],
|
||||
["Panjabi", "pan", "pan", "Panjabi"],
|
||||
["Romanian", "ron", "rum", "Romanian"],
|
||||
["Russian", "rus", "rus", "Russian"],
|
||||
["Serbian", "srp", "srp", "Serbian"],
|
||||
["Sinhala", "sin", "sin", "Sinhala"],
|
||||
["Slovak", "slk", "slo", "Slovak"],
|
||||
["Slovenian", "slv", "slv", "Slovenian"],
|
||||
["Spanish", "spa", "spa", "Spanish"],
|
||||
["European Spanish", "euSpa", "spa", "European Spanish"],
|
||||
["Swedish", "swe", "swe", "Swedish"],
|
||||
["Thai", "tha", "tha", "Thai"],
|
||||
["Tagalog", "tgl", "tgl", "Tagalog"],
|
||||
["Turkish", "tur", "tur", "Turkish"],
|
||||
["Ukrainian", "ukr", "ukr", "Ukrainian"],
|
||||
["Urdu", "urd", "urd", "Urdu"],
|
||||
["Vietnamese", "vie", "vie", "Vietnamese"],
|
||||
]
|
||||
|
||||
return LanguageList
|
||||
|
||||
def ExtraLanguageList(self):
|
||||
ExtraLanguageList = [
|
||||
["Polish - Dubbing", "pol", "pol", "Polish - Dubbing"],
|
||||
["Polish - Lektor", "pol", "pol", "Polish - Lektor"],
|
||||
]
|
||||
|
||||
return ExtraLanguageList
|
||||
|
||||
def AddChapters(self):
|
||||
if os.path.isfile(self.CurrentName_Original + " Chapters.txt"):
|
||||
self.merge += [
|
||||
"--chapter-charset",
|
||||
"UTF-8",
|
||||
"--chapters",
|
||||
self.CurrentName_Original + " Chapters.txt",
|
||||
]
|
||||
|
||||
return
|
||||
|
||||
def AddVideo(self):
|
||||
inputVideo = None
|
||||
|
||||
videofiles = [
|
||||
"{} [{}p]_Demuxed.mp4",
|
||||
"{} [{}p]_Demuxed.mp4",
|
||||
"{} [{}p] [UHD]_Demuxed.mp4",
|
||||
"{} [{}p] [UHD]_Demuxed.mp4",
|
||||
"{} [{}p] [VP9]_Demuxed.mp4",
|
||||
"{} [{}p] [HIGH]_Demuxed.mp4",
|
||||
"{} [{}p] [VP9]_Demuxed.mp4",
|
||||
"{} [{}p] [HEVC]_Demuxed.mp4",
|
||||
"{} [{}p] [HDR]_Demuxed.mp4",
|
||||
"{} [{}p] [HDR-DV]_Demuxed.mp4",
|
||||
]
|
||||
|
||||
for videofile in videofiles:
|
||||
filename = videofile.format(self.CurrentName_Original, self.CurrentHeigh)
|
||||
if os.path.isfile(filename):
|
||||
inputVideo = filename
|
||||
break
|
||||
|
||||
if not inputVideo:
|
||||
self.logger.info("cannot found video file.")
|
||||
exit(-1)
|
||||
|
||||
if self.default_mux:
|
||||
outputVideo = (
|
||||
re.compile("|".join([".h264", ".h265", ".vp9", ".mp4"])).sub("", inputVideo)
|
||||
+ ".mkv"
|
||||
)
|
||||
if self.SeasonFolder:
|
||||
outputVideo = os.path.join(
|
||||
os.path.abspath(self.SeasonFolder), outputVideo
|
||||
)
|
||||
outputVideo = outputVideo.replace("\\", "/")
|
||||
else:
|
||||
outputVideo = self.setName()
|
||||
|
||||
self.outputVideo = outputVideo
|
||||
|
||||
if self.fps24:
|
||||
self.merge += [
|
||||
self.mkvmerge,
|
||||
"--output",
|
||||
outputVideo,
|
||||
"--default-duration",
|
||||
"0:24000/1001p",
|
||||
"--language",
|
||||
"0:und",
|
||||
"--default-track",
|
||||
"0:yes",
|
||||
"(",
|
||||
inputVideo,
|
||||
")",
|
||||
]
|
||||
else:
|
||||
self.merge += [
|
||||
self.mkvmerge,
|
||||
"--output",
|
||||
outputVideo,
|
||||
"--title",
|
||||
'RAB',
|
||||
"(",
|
||||
inputVideo,
|
||||
")",
|
||||
]
|
||||
|
||||
return
|
||||
|
||||
def AddAudio(self):
|
||||
|
||||
audiofiles = [
|
||||
"{} {}.ac3",
|
||||
"{} {} - Audio Description.ac3",
|
||||
"{} {}.eac3",
|
||||
"{} {} - Audio Description.eac3",
|
||||
"{} {}.aac",
|
||||
"{} {} - Audio Description.aac",
|
||||
]
|
||||
|
||||
for (audio_language, subs_language, language_id, language_name,) in (
|
||||
self.LanguageList() + self.ExtraLanguageList()
|
||||
):
|
||||
for audiofile in audiofiles:
|
||||
filename = audiofile.format(self.CurrentName_Original, audio_language)
|
||||
if os.path.isfile(filename):
|
||||
self.merge += [
|
||||
"--language",
|
||||
f"0:{language_id}",
|
||||
"--track-name",
|
||||
"0:Audio Description" if 'Audio Description' in filename
|
||||
else f"0:{language_name}",
|
||||
"--default-track",
|
||||
"0:yes"
|
||||
if subs_language == self.muxer_settings["AUDIO"]
|
||||
else "0:no",
|
||||
"(",
|
||||
filename,
|
||||
")",
|
||||
]
|
||||
|
||||
return
|
||||
|
||||
def AddSubtitles(self):
|
||||
|
||||
srts = [
|
||||
"{} {}.srt",
|
||||
]
|
||||
forceds = [
|
||||
"{} forced-{}.srt",
|
||||
]
|
||||
sdhs = [
|
||||
"{} sdh-{}.srt",
|
||||
]
|
||||
|
||||
for (
|
||||
audio_language,
|
||||
subs_language,
|
||||
language_id,
|
||||
language_name,
|
||||
) in self.LanguageList():
|
||||
for subtitle in srts:
|
||||
filename = subtitle.format(self.CurrentName_Original, subs_language)
|
||||
if os.path.isfile(filename):
|
||||
self.merge += [
|
||||
"--language",
|
||||
f"0:{language_id}",
|
||||
"--track-name",
|
||||
f"0:{language_name}",
|
||||
"--forced-track",
|
||||
"0:no",
|
||||
"--default-track",
|
||||
"0:yes"
|
||||
if subs_language == self.muxer_settings["SUB"]
|
||||
else "0:no",
|
||||
"--compression",
|
||||
"0:none",
|
||||
"(",
|
||||
filename,
|
||||
")",
|
||||
]
|
||||
|
||||
for subtitle in forceds:
|
||||
filename = subtitle.format(self.CurrentName_Original, subs_language)
|
||||
if os.path.isfile(filename):
|
||||
self.merge += [
|
||||
"--language",
|
||||
f"0:{language_id}",
|
||||
"--track-name",
|
||||
f"0:Forced",
|
||||
"--forced-track",
|
||||
"0:yes",
|
||||
"--default-track",
|
||||
"0:no",
|
||||
"--compression",
|
||||
"0:none",
|
||||
"(",
|
||||
filename,
|
||||
")",
|
||||
]
|
||||
|
||||
for subtitle in sdhs:
|
||||
filename = subtitle.format(self.CurrentName_Original, subs_language)
|
||||
if os.path.isfile(filename):
|
||||
self.merge += [
|
||||
"--language",
|
||||
f"0:{language_id}",
|
||||
"--track-name",
|
||||
f"0:SDH",
|
||||
"--forced-track",
|
||||
"0:no",
|
||||
"--default-track",
|
||||
"0:no",
|
||||
"--compression",
|
||||
"0:none",
|
||||
"(",
|
||||
filename,
|
||||
")",
|
||||
]
|
||||
|
||||
return
|
||||
|
||||
def startMux(self):
|
||||
self.AddVideo()
|
||||
self.AddAudio()
|
||||
self.AddSubtitles()
|
||||
self.AddChapters()
|
||||
if not os.path.isfile(self.outputVideo):
|
||||
self.Run(self.merge + self.Extras)
|
||||
|
||||
return self.outputVideo
|
551
helpers/Parsers/Netflix/MSLClient.py
Normal file
551
helpers/Parsers/Netflix/MSLClient.py
Normal file
@ -0,0 +1,551 @@
|
||||
import base64, binascii, json, os, re, random, requests, string, time, traceback, logging
|
||||
from datetime import datetime
|
||||
from Cryptodome.Cipher import AES, PKCS1_OAEP
|
||||
from Cryptodome.Util import Padding
|
||||
from Cryptodome.Hash import HMAC, SHA256
|
||||
from Cryptodome.PublicKey import RSA
|
||||
from pywidevine.cdm import cdm, deviceconfig
|
||||
from configs.config import tool
|
||||
|
||||
class MSLClient:
|
||||
def __init__(self, profiles=None, wv_keyexchange=True, proxies=None):
|
||||
|
||||
self.session = requests.session()
|
||||
self.logger = logging.getLogger(__name__)
|
||||
if proxies:
|
||||
self.session.proxies.update(proxies)
|
||||
|
||||
self.nf_endpoints = {
|
||||
"manifest": "https://www.netflix.com/nq/msl_v1/cadmium/pbo_manifests/^1.0.0/router",
|
||||
"license": "https://www.netflix.com/nq/msl_v1/cadmium/pbo_licenses/^1.0.0/router",
|
||||
}
|
||||
|
||||
######################################################################
|
||||
|
||||
self.config = tool().config("NETFLIX")
|
||||
self.email = self.config["email"]
|
||||
self.password = self.config["password"]
|
||||
self.device = tool().devices()["NETFLIX-MANIFEST"]
|
||||
self.save_rsa_location = self.config["token_file"]
|
||||
self.languages = self.config["manifest_language"]
|
||||
self.license_path = None
|
||||
|
||||
######################################################################
|
||||
|
||||
if os.path.isfile(self.save_rsa_location):
|
||||
self.generatePrivateKey = RSA.importKey(
|
||||
json.loads(open(self.save_rsa_location, "r").read())["RSA_KEY"]
|
||||
)
|
||||
else:
|
||||
self.generatePrivateKey = RSA.generate(2048)
|
||||
|
||||
if wv_keyexchange:
|
||||
self.wv_keyexchange = True
|
||||
self.cdm = cdm.Cdm()
|
||||
self.cdm_session = None
|
||||
else:
|
||||
self.wv_keyexchange = False
|
||||
self.cdm = None
|
||||
self.cdm_session = None
|
||||
|
||||
self.manifest_challenge = '' # set desired wv data to overide wvexchange data
|
||||
|
||||
self.profiles = profiles
|
||||
|
||||
self.logger.debug("Using profiles: {}".format(self.profiles))
|
||||
|
||||
esn = self.config["androidEsn"]
|
||||
if esn is None:
|
||||
self.logger.error(
|
||||
'\nandroid esn not found, set esn with cdm systemID in config.py'
|
||||
)
|
||||
else:
|
||||
self.esn = esn
|
||||
|
||||
self.logger.debug("Using esn: " + self.esn)
|
||||
|
||||
self.messageid = random.randint(0, 2 ** 52)
|
||||
self.session_keys = {} #~
|
||||
self.header = {
|
||||
"sender": self.esn,
|
||||
"handshake": True,
|
||||
"nonreplayable": 2,
|
||||
"capabilities": {"languages": [], "compressionalgos": []},
|
||||
"recipient": "Netflix",
|
||||
"renewable": True,
|
||||
"messageid": self.messageid,
|
||||
"timestamp": time.time(),
|
||||
}
|
||||
|
||||
self.setRSA()
|
||||
|
||||
def get_header_extra(self):
|
||||
|
||||
if self.wv_keyexchange:
|
||||
self.cdm_session = self.cdm.open_session(
|
||||
None,
|
||||
deviceconfig.DeviceConfig(self.device),
|
||||
b"\x0A\x7A\x00\x6C\x38\x2B",
|
||||
True,
|
||||
)
|
||||
wv_request = base64.b64encode(
|
||||
self.cdm.get_license_request(self.cdm_session)
|
||||
).decode("utf-8")
|
||||
|
||||
self.header["keyrequestdata"] = [
|
||||
{
|
||||
"scheme": "WIDEVINE",
|
||||
"keydata": {
|
||||
"keyrequest": wv_request
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
else:
|
||||
self.header["keyrequestdata"] = [
|
||||
{
|
||||
"scheme": "ASYMMETRIC_WRAPPED",
|
||||
"keydata": {
|
||||
"publickey": base64.b64encode(
|
||||
self.generatePrivateKey.publickey().exportKey("DER")
|
||||
).decode("utf8"),
|
||||
"mechanism": "JWK_RSA",
|
||||
"keypairid": "rsaKeypairId",
|
||||
},
|
||||
}
|
||||
]
|
||||
|
||||
return self.header
|
||||
|
||||
def setRSA(self):
|
||||
if os.path.isfile(self.save_rsa_location):
|
||||
master_token = self.load_tokens()
|
||||
expires = master_token["expiration"]
|
||||
valid_until = datetime.utcfromtimestamp(int(expires))
|
||||
present_time = datetime.now()
|
||||
|
||||
difference = valid_until - present_time
|
||||
difference = difference.total_seconds() / 60 / 60
|
||||
if difference < 10:
|
||||
self.logger.debug("rsa file found. expired soon")
|
||||
self.session_keys["session_keys"] = self.generate_handshake()
|
||||
else:
|
||||
self.logger.debug("rsa file found")
|
||||
self.session_keys["session_keys"] = {
|
||||
"mastertoken": master_token["mastertoken"],
|
||||
"sequence_number": master_token["sequence_number"],
|
||||
"encryption_key": master_token["encryption_key"],
|
||||
"sign_key": master_token["sign_key"],
|
||||
}
|
||||
else:
|
||||
self.logger.debug("rsa file not found")
|
||||
self.session_keys["session_keys"] = self.generate_handshake()
|
||||
|
||||
def load_playlist(self, viewable_id):
|
||||
|
||||
payload = {
|
||||
"version": 2,
|
||||
"url": "/manifest", #"/licensedManifest"
|
||||
"id": int(time.time()),
|
||||
"languages": self.languages,
|
||||
"params": {
|
||||
#"challenge": self.manifest_challenge,
|
||||
"type": "standard",
|
||||
"viewableId": viewable_id,
|
||||
"profiles": self.profiles,
|
||||
"flavor": "STANDARD", #'PRE_FETCH'
|
||||
"drmType": "widevine",
|
||||
"usePsshBox": True,
|
||||
"useHttpsStreams": True,
|
||||
"supportsPreReleasePin": True,
|
||||
"supportsWatermark": True,
|
||||
'supportsUnequalizedDownloadables': True,
|
||||
'requestEligibleABTests': True,
|
||||
"isBranching": False,
|
||||
'isNonMember': False,
|
||||
'isUIAutoPlay': False,
|
||||
"imageSubtitleHeight": 1080,
|
||||
"uiVersion": "shakti-v4bf615c3",
|
||||
'uiPlatform': 'SHAKTI',
|
||||
"clientVersion": "6.0026.291.011",
|
||||
'desiredVmaf': 'plus_lts', # phone_plus_exp
|
||||
"showAllSubDubTracks": True,
|
||||
#"preferredTextLocale": "ar",
|
||||
#"preferredAudioLocale": "ar",
|
||||
#"maxSupportedLanguages": 2,
|
||||
"preferAssistiveAudio": False,
|
||||
"deviceSecurityLevel": "3000",
|
||||
'licenseType': 'standard',
|
||||
'titleSpecificData': {
|
||||
str(viewable_id): {
|
||||
'unletterboxed': True
|
||||
}
|
||||
},
|
||||
"videoOutputInfo": [
|
||||
{
|
||||
"type": "DigitalVideoOutputDescriptor |