mirror of
https://cdm-project.com/Download-Tools/udemy-downloader.git
synced 2025-05-28 19:20:13 +02:00
Bug fixes
- Fixed captions not being downloaded - Fixed trying to load keyfile even if it doesn't exist - Moved asset and subtitle download processing into lecture processing function (in preparation of subtitle merging) - Fixed an error in ffmpeg command when not using h265 - no longer need to specify full path to UdemyDownloader.py, also updated readme to reflect this
This commit is contained in:
parent
ec6ac28d0b
commit
f3a32a2dd6
42
README.md
42
README.md
@ -104,41 +104,41 @@ optional arguments:
|
||||
```
|
||||
|
||||
- Passing a Bearer Token and Course ID as an argument
|
||||
- `python udemy_downloader\UdemyDownloader.py -c <Course URL> -b <Bearer Token>`
|
||||
- `python udemy_downloader\UdemyDownloader.py -c https://www.udemy.com/courses/myawesomecourse -b <Bearer Token>`
|
||||
- `python udemy_downloader -c <Course URL> -b <Bearer Token>`
|
||||
- `python udemy_downloader -c https://www.udemy.com/courses/myawesomecourse -b <Bearer Token>`
|
||||
- Download a specific quality
|
||||
- `python udemy_downloader\UdemyDownloader.py -c <Course URL> -q 720`
|
||||
- `python udemy_downloader -c <Course URL> -q 720`
|
||||
- Download assets along with lectures
|
||||
- `python udemy_downloader\UdemyDownloader.py -c <Course URL> --download-assets`
|
||||
- `python udemy_downloader -c <Course URL> --download-assets`
|
||||
- Download assets and specify a quality
|
||||
- `python udemy_downloader\UdemyDownloader.py -c <Course URL> -q 360 --download-assets`
|
||||
- `python udemy_downloader -c <Course URL> -q 360 --download-assets`
|
||||
- Download captions (Defaults to English)
|
||||
- `python udemy_downloader\UdemyDownloader.py -c <Course URL> --download-captions`
|
||||
- `python udemy_downloader -c <Course URL> --download-captions`
|
||||
- Download captions with specific language
|
||||
- `python udemy_downloader\UdemyDownloader.py -c <Course URL> --download-captions -l en` - English subtitles
|
||||
- `python udemy_downloader\UdemyDownloader.py -c <Course URL> --download-captions -l es` - Spanish subtitles
|
||||
- `python udemy_downloader\UdemyDownloader.py -c <Course URL> --download-captions -l it` - Italian subtitles
|
||||
- `python udemy_downloader\UdemyDownloader.py -c <Course URL> --download-captions -l pl` - Polish Subtitles
|
||||
- `python udemy_downloader\UdemyDownloader.py -c <Course URL> --download-captions -l all` - Downloads all subtitles
|
||||
- `python udemy_downloader -c <Course URL> --download-captions -l en` - English subtitles
|
||||
- `python udemy_downloader -c <Course URL> --download-captions -l es` - Spanish subtitles
|
||||
- `python udemy_downloader -c <Course URL> --download-captions -l it` - Italian subtitles
|
||||
- `python udemy_downloader -c <Course URL> --download-captions -l pl` - Polish Subtitles
|
||||
- `python udemy_downloader -c <Course URL> --download-captions -l all` - Downloads all subtitles
|
||||
- etc
|
||||
- Skip downloading lecture videos
|
||||
- `python udemy_downloader\UdemyDownloader.py -c <Course URL> --skip-lectures --download-captions` - Downloads only captions
|
||||
- `python udemy_downloader\UdemyDownloader.py -c <Course URL> --skip-lectures --download-assets` - Downloads only assets
|
||||
- `python udemy_downloader -c <Course URL> --skip-lectures --download-captions` - Downloads only captions
|
||||
- `python udemy_downloader -c <Course URL> --skip-lectures --download-assets` - Downloads only assets
|
||||
- Keep .VTT caption files:
|
||||
- `python udemy_downloader\UdemyDownloader.py -c <Course URL> --download-captions --keep-vtt`
|
||||
- `python udemy_downloader -c <Course URL> --download-captions --keep-vtt`
|
||||
- Skip parsing HLS Streams (HLS streams usually contain 1080p quality for Non-DRM lectures):
|
||||
- `python udemy_downloader\UdemyDownloader.py -c <Course URL> --skip-hls`
|
||||
- `python udemy_downloader -c <Course URL> --skip-hls`
|
||||
- Print course information only:
|
||||
- `python udemy_downloader\UdemyDownloader.py -c <Course URL> --info`
|
||||
- `python udemy_downloader -c <Course URL> --info`
|
||||
- Specify max number of concurrent downloads:
|
||||
- `python udemy_downloader\UdemyDownloader.py -c <Course URL> --concurrent-downloads 20`
|
||||
- `python udemy_downloader\UdemyDownloader.py -c <Course URL> -cd 20`
|
||||
- `python udemy_downloader -c <Course URL> --concurrent-downloads 20`
|
||||
- `python udemy_downloader -c <Course URL> -cd 20`
|
||||
- Encode in H.265:
|
||||
- `python udemy_downloader\UdemyDownloader.py -c <Course URL> --use-h265`
|
||||
- `python udemy_downloader -c <Course URL> --use-h265`
|
||||
- Encode in H.265 with custom CRF:
|
||||
- `python udemy_downloader\UdemyDownloader.py -c <Course URL> --use-h265 -h265-crf 20`
|
||||
- `python udemy_downloader -c <Course URL> --use-h265 -h265-crf 20`
|
||||
- Encode in H.265 with custom preset:
|
||||
- `python udemy_downloader\UdemyDownloader.py -c <Course URL> --use-h265 --h265-preset faster`
|
||||
- `python udemy_downloader -c <Course URL> --use-h265 --h265-preset faster`
|
||||
|
||||
# Credits
|
||||
|
||||
|
@ -51,6 +51,7 @@ _udemy_path = os.path.join(saved_dir, "_udemy.json")
|
||||
|
||||
udemy = None
|
||||
parser = None
|
||||
iknowwhatimdoing = False
|
||||
retry = 3
|
||||
_udemy = {}
|
||||
course_url = None
|
||||
@ -82,6 +83,7 @@ use_h265 = False
|
||||
h265_crf = 28
|
||||
h265_preset = "medium"
|
||||
|
||||
|
||||
def download_segments(url, format_id, video_title, output_path, lecture_file_name, chapter_dir):
|
||||
os.chdir(os.path.join(chapter_dir))
|
||||
file_name = lecture_file_name.replace("%", "").replace(".mp4", "")
|
||||
@ -103,7 +105,6 @@ def download_segments(url, format_id, video_title, output_path, lecture_file_nam
|
||||
print("Return code from the downloader was non-0 (error), skipping!")
|
||||
return
|
||||
|
||||
|
||||
# tries to decrypt audio and video, and then merge them
|
||||
try:
|
||||
# tries to decrypt audio
|
||||
@ -113,9 +114,11 @@ def download_segments(url, format_id, video_title, output_path, lecture_file_nam
|
||||
audio_key = keys[audio_kid.lower()]
|
||||
|
||||
print("> Decrypting audio...")
|
||||
ret_code = decrypt(audio_key, audio_filepath_enc, audio_filepath_dec)
|
||||
ret_code = decrypt(
|
||||
audio_key, audio_filepath_enc, audio_filepath_dec)
|
||||
if(ret_code != 0):
|
||||
print("WARN: Decrypting returned a non-0 result code which usually indicated an error!")
|
||||
print(
|
||||
"WARN: Decrypting returned a non-0 result code which usually indicated an error!")
|
||||
else:
|
||||
print("Decryption complete")
|
||||
except KeyError:
|
||||
@ -129,32 +132,35 @@ def download_segments(url, format_id, video_title, output_path, lecture_file_nam
|
||||
video_key = keys[video_kid.lower()]
|
||||
|
||||
print("> Decrypting video...")
|
||||
ret_code2 = decrypt(video_key, video_filepath_enc, video_filepath_dec)
|
||||
ret_code2 = decrypt(
|
||||
video_key, video_filepath_enc, video_filepath_dec)
|
||||
if(ret_code2 != 0):
|
||||
print("WARN: Decrypting returned a non-0 result code which usually indicated an error!")
|
||||
print(
|
||||
"WARN: Decrypting returned a non-0 result code which usually indicated an error!")
|
||||
else:
|
||||
print("Decryption complete")
|
||||
except KeyError:
|
||||
print("Video key not found!")
|
||||
raise RuntimeError("No video key")
|
||||
|
||||
|
||||
# tries to merge audio and video
|
||||
# this should run only if both audio and video decryption returned 0 codes
|
||||
print("> Merging audio and video files...")
|
||||
ret_code3 = merge(video_title=video_title, video_filepath=video_filepath_dec, audio_filepath=audio_filepath_dec, output_path=output_path, use_h265=use_h265, h265_crf=h265_crf, h265_preset=h265_preset)
|
||||
ret_code3 = merge(video_title=video_title, video_filepath=video_filepath_dec, audio_filepath=audio_filepath_dec,
|
||||
output_path=output_path, use_h265=use_h265, h265_crf=h265_crf, h265_preset=h265_preset)
|
||||
if(ret_code3 != 0):
|
||||
print("WARN: Merging returned a non-0 result code which usually indicated an error!")
|
||||
|
||||
print(
|
||||
"WARN: Merging returned a non-0 result code which usually indicated an error!")
|
||||
|
||||
if(ret_code == 0 and ret_code2 == 0 and ret_code3 == 0):
|
||||
print("> Cleaning up...")
|
||||
# remove all the temporary files left over after decryption and merging if there were no errors
|
||||
remove_files((video_filepath_enc, video_filepath_dec, audio_filepath_enc, audio_filepath_dec))
|
||||
remove_files((video_filepath_enc, video_filepath_dec,
|
||||
audio_filepath_enc, audio_filepath_dec))
|
||||
print("> Cleanup complete")
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
|
||||
os.chdir(home_dir)
|
||||
|
||||
|
||||
@ -200,7 +206,7 @@ def download_aria(url, file_dir, filename):
|
||||
print("Return code: " + str(ret_code))
|
||||
|
||||
|
||||
def process_caption(caption, lecture_title, lecture_dir, keep_vtt, tries=0):
|
||||
def process_caption(caption, lecture_title, lecture_dir, tries=0):
|
||||
filename = f"%s_%s.%s" % (sanitize(lecture_title), caption.get("language"),
|
||||
caption.get("extension"))
|
||||
filename_no_ext = f"%s_%s" % (sanitize(lecture_title),
|
||||
@ -241,6 +247,78 @@ def process_lecture(lecture, lecture_path, lecture_file_name, chapter_dir):
|
||||
is_encrypted = lecture.get("is_encrypted")
|
||||
lecture_sources = lecture.get("video_sources")
|
||||
|
||||
if dl_assets:
|
||||
assets = lecture.get("assets")
|
||||
print(" > Processing {} asset(s) for lecture...".format(
|
||||
len(assets)))
|
||||
|
||||
for asset in assets:
|
||||
asset_type = asset.get("type")
|
||||
filename = asset.get("filename")
|
||||
download_url = asset.get("download_url")
|
||||
asset_id = asset.get("id")
|
||||
|
||||
if asset_type == "article":
|
||||
print(
|
||||
"If you're seeing this message, that means that you reached a secret area that I haven't finished! jk I haven't implemented handling for this asset type, please report this at https://github.com/Puyodead1/udemy-downloader/issues so I can add it. When reporting, please provide the following information: "
|
||||
)
|
||||
print("AssetType: Article; AssetData: ", asset)
|
||||
# html_content = lecture.get("html_content")
|
||||
# lecture_path = os.path.join(
|
||||
# chapter_dir, "{}.html".format(sanitize(lecture_title)))
|
||||
# try:
|
||||
# with open(lecture_path, 'w') as f:
|
||||
# f.write(html_content)
|
||||
# f.close()
|
||||
# except Exception as e:
|
||||
# print("Failed to write html file: ", e)
|
||||
# continue
|
||||
elif asset_type == "video":
|
||||
print(
|
||||
"If you're seeing this message, that means that you reached a secret area that I haven't finished! jk I haven't implemented handling for this asset type, please report this at https://github.com/Puyodead1/udemy-downloader/issues so I can add it. When reporting, please provide the following information: "
|
||||
)
|
||||
print("AssetType: Video; AssetData: ", asset)
|
||||
elif asset_type == "audio" or asset_type == "e-book" or asset_type == "file" or asset_type == "presentation":
|
||||
try:
|
||||
download_aria(download_url, chapter_dir,
|
||||
f"{asset_id}-{filename}")
|
||||
except Exception as e:
|
||||
print("> Error downloading asset: ", e)
|
||||
continue
|
||||
elif asset_type == "external_link":
|
||||
filepath = os.path.join(chapter_dir, filename)
|
||||
savedirs, name = os.path.split(filepath)
|
||||
filename = u"external-assets-links.txt"
|
||||
filename = os.path.join(savedirs, filename)
|
||||
file_data = []
|
||||
if os.path.isfile(filename):
|
||||
file_data = [
|
||||
i.strip().lower()
|
||||
for i in open(filename,
|
||||
encoding="utf-8",
|
||||
errors="ignore") if i
|
||||
]
|
||||
|
||||
content = u"\n{}\n{}\n".format(name, download_url)
|
||||
if name.lower() not in file_data:
|
||||
with open(filename,
|
||||
'a',
|
||||
encoding="utf-8",
|
||||
errors="ignore") as f:
|
||||
f.write(content)
|
||||
f.close()
|
||||
|
||||
subtitles = lecture.get("subtitles")
|
||||
if dl_captions and subtitles:
|
||||
selected_subtitles = []
|
||||
print("Processing {} caption(s)...".format(len(subtitles)))
|
||||
for subtitle in subtitles:
|
||||
lang = subtitle.get("language")
|
||||
if lang == caption_locale or caption_locale == "all":
|
||||
selected_subtitles.append(subtitle)
|
||||
process_caption(subtitle, lecture_title, chapter_dir)
|
||||
print("Selected {} captions".format(len(selected_subtitles)))
|
||||
|
||||
if is_encrypted:
|
||||
if len(lecture_sources) > 0:
|
||||
source = lecture_sources[-1] # last index is the best quality
|
||||
@ -251,8 +329,8 @@ def process_lecture(lecture, lecture_path, lecture_file_name, chapter_dir):
|
||||
print(f" > Lecture '%s' has DRM, attempting to download" %
|
||||
lecture_title)
|
||||
download_segments(source.get("download_url"),
|
||||
source.get(
|
||||
"format_id"), lecture_title, lecture_path, lecture_file_name, chapter_dir)
|
||||
source.get(
|
||||
"format_id"), lecture_title, lecture_path, lecture_file_name, chapter_dir)
|
||||
else:
|
||||
print(f" > Lecture '%s' is missing media links" %
|
||||
lecture_title)
|
||||
@ -287,7 +365,6 @@ def process_lecture(lecture, lecture_path, lecture_file_name, chapter_dir):
|
||||
"aria2c", "-o", f"{temp_filepath}", f"{url}"
|
||||
]).wait()
|
||||
if ret_code == 0:
|
||||
# os.rename(temp_filepath, lecture_path)
|
||||
print(" > HLS Download success")
|
||||
else:
|
||||
download_aria(url, chapter_dir, lecture_title + ".mp4")
|
||||
@ -338,7 +415,6 @@ def parse():
|
||||
print(
|
||||
f" > Processing lecture {lecture_index} of {total_lectures}")
|
||||
if not skip_lectures:
|
||||
print(lecture_file_name)
|
||||
# Check if the lecture is already downloaded
|
||||
if os.path.isfile(lecture_path):
|
||||
print(
|
||||
@ -360,76 +436,8 @@ def parse():
|
||||
print(" > Failed to write html file: ", e)
|
||||
continue
|
||||
else:
|
||||
process_lecture(lecture, lecture_path, lecture_file_name, chapter_dir)
|
||||
|
||||
if dl_assets:
|
||||
assets = lecture.get("assets")
|
||||
print(" > Processing {} asset(s) for lecture...".format(
|
||||
len(assets)))
|
||||
|
||||
for asset in assets:
|
||||
asset_type = asset.get("type")
|
||||
filename = asset.get("filename")
|
||||
download_url = asset.get("download_url")
|
||||
asset_id = asset.get("id")
|
||||
|
||||
if asset_type == "article":
|
||||
print(
|
||||
"If you're seeing this message, that means that you reached a secret area that I haven't finished! jk I haven't implemented handling for this asset type, please report this at https://github.com/Puyodead1/udemy-downloader/issues so I can add it. When reporting, please provide the following information: "
|
||||
)
|
||||
print("AssetType: Article; AssetData: ", asset)
|
||||
# html_content = lecture.get("html_content")
|
||||
# lecture_path = os.path.join(
|
||||
# chapter_dir, "{}.html".format(sanitize(lecture_title)))
|
||||
# try:
|
||||
# with open(lecture_path, 'w') as f:
|
||||
# f.write(html_content)
|
||||
# f.close()
|
||||
# except Exception as e:
|
||||
# print("Failed to write html file: ", e)
|
||||
# continue
|
||||
elif asset_type == "video":
|
||||
print(
|
||||
"If you're seeing this message, that means that you reached a secret area that I haven't finished! jk I haven't implemented handling for this asset type, please report this at https://github.com/Puyodead1/udemy-downloader/issues so I can add it. When reporting, please provide the following information: "
|
||||
)
|
||||
print("AssetType: Video; AssetData: ", asset)
|
||||
elif asset_type == "audio" or asset_type == "e-book" or asset_type == "file" or asset_type == "presentation":
|
||||
try:
|
||||
download_aria(download_url, chapter_dir,
|
||||
f"{asset_id}-{filename}")
|
||||
except Exception as e:
|
||||
print("> Error downloading asset: ", e)
|
||||
continue
|
||||
elif asset_type == "external_link":
|
||||
filepath = os.path.join(chapter_dir, filename)
|
||||
savedirs, name = os.path.split(filepath)
|
||||
filename = u"external-assets-links.txt"
|
||||
filename = os.path.join(savedirs, filename)
|
||||
file_data = []
|
||||
if os.path.isfile(filename):
|
||||
file_data = [
|
||||
i.strip().lower()
|
||||
for i in open(filename,
|
||||
encoding="utf-8",
|
||||
errors="ignore") if i
|
||||
]
|
||||
|
||||
content = u"\n{}\n{}\n".format(name, download_url)
|
||||
if name.lower() not in file_data:
|
||||
with open(filename,
|
||||
'a',
|
||||
encoding="utf-8",
|
||||
errors="ignore") as f:
|
||||
f.write(content)
|
||||
f.close()
|
||||
|
||||
subtitles = lecture.get("subtitles")
|
||||
if dl_captions and subtitles:
|
||||
print("Processing {} caption(s)...".format(len(subtitles)))
|
||||
for subtitle in subtitles:
|
||||
lang = subtitle.get("language")
|
||||
if lang == caption_locale or caption_locale == "all":
|
||||
process_caption(subtitle, lecture_title, chapter_dir)
|
||||
process_lecture(lecture, lecture_path,
|
||||
lecture_file_name, chapter_dir)
|
||||
|
||||
|
||||
def process_course():
|
||||
@ -481,15 +489,15 @@ def process_course():
|
||||
|
||||
if isinstance(asset, dict):
|
||||
asset_type = (asset.get("asset_type").lower()
|
||||
or asset.get("assetType").lower)
|
||||
or asset.get("assetType").lower)
|
||||
if asset_type == "article":
|
||||
if isinstance(supp_assets,
|
||||
list) and len(supp_assets) > 0:
|
||||
list) and len(supp_assets) > 0:
|
||||
retVal = udemy._extract_supplementary_assets(
|
||||
supp_assets)
|
||||
elif asset_type == "video":
|
||||
if isinstance(supp_assets,
|
||||
list) and len(supp_assets) > 0:
|
||||
list) and len(supp_assets) > 0:
|
||||
retVal = udemy._extract_supplementary_assets(
|
||||
supp_assets)
|
||||
elif asset_type == "e-book":
|
||||
@ -641,6 +649,7 @@ def process_course():
|
||||
if entry
|
||||
])
|
||||
|
||||
|
||||
def get_course_information():
|
||||
global course_info, course_id, title, course_title, portal_name
|
||||
if(load_from_file):
|
||||
@ -658,6 +667,7 @@ def get_course_information():
|
||||
course_title = course_info.get("published_title")
|
||||
portal_name = course_info.get("portal_name")
|
||||
|
||||
|
||||
def get_course_content():
|
||||
global course_content
|
||||
if load_from_file:
|
||||
@ -666,18 +676,22 @@ def get_course_content():
|
||||
course_content = json.loads(f.read())
|
||||
else:
|
||||
print("course_content.json not found, falling back to fetching")
|
||||
course_content = udemy._extract_course_json(course_url, course_id, portal_name)
|
||||
course_content = udemy._extract_course_json(
|
||||
course_url, course_id, portal_name)
|
||||
else:
|
||||
course_content = udemy._extract_course_json(course_url, course_id, portal_name)
|
||||
course_content = udemy._extract_course_json(
|
||||
course_url, course_id, portal_name)
|
||||
|
||||
|
||||
def parse_data():
|
||||
global _udemy
|
||||
if load_from_file:
|
||||
if load_from_file and os.path.exists(_udemy_path):
|
||||
f = open(_udemy_path, 'r')
|
||||
_udemy = json.loads(f.read())
|
||||
else:
|
||||
process_course()
|
||||
|
||||
|
||||
def _print_course_info(course_data):
|
||||
print("\n\n\n\n")
|
||||
course_title = course_data.get("title")
|
||||
@ -746,6 +760,7 @@ def _print_course_info(course_data):
|
||||
if chapter_index != chapter_count:
|
||||
print("\n\n")
|
||||
|
||||
|
||||
def setup_parser():
|
||||
global parser
|
||||
parser = argparse.ArgumentParser(description='Udemy Downloader')
|
||||
@ -839,6 +854,12 @@ def setup_parser():
|
||||
default="medium",
|
||||
help="Set a custom preset value for H.265 encoding. FFMPEG default is medium",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--iknowwhatimdoing",
|
||||
dest="iknowwhatimdoing",
|
||||
action="store_true",
|
||||
help=argparse.SUPPRESS,
|
||||
)
|
||||
parser.add_argument(
|
||||
"--save-to-file",
|
||||
dest="save_to_file",
|
||||
@ -854,10 +875,10 @@ def setup_parser():
|
||||
parser.add_argument("-v", "--version", action="version",
|
||||
version='You are running version {version}'.format(version=__version__))
|
||||
|
||||
|
||||
|
||||
def process_args(args):
|
||||
global course_url, bearer_token, dl_assets, caption_locale, skip_lectures, quality, keep_vtt, skip_hls, print_info, load_from_file, save_to_file, concurrent_connections, use_h265, h265_crf, h265_preset
|
||||
|
||||
global course_url, bearer_token, dl_assets, dl_captions, caption_locale, skip_lectures, quality, keep_vtt, skip_hls, print_info, load_from_file, save_to_file, concurrent_connections, use_h265, h265_crf, h265_preset, iknowwhatimdoing
|
||||
|
||||
course_url = args.course_url
|
||||
if args.download_assets:
|
||||
dl_assets = True
|
||||
@ -893,6 +914,8 @@ def process_args(args):
|
||||
h265_crf = args.h265_crf
|
||||
if args.h265_preset:
|
||||
h265_preset = args.h265_preset
|
||||
if args.iknowwhatimdoing:
|
||||
iknowwhatimdoing = args.iknowwhatimdoing
|
||||
|
||||
if args.load_from_file:
|
||||
print(
|
||||
@ -907,6 +930,7 @@ def process_args(args):
|
||||
else:
|
||||
bearer_token = os.getenv("UDEMY_BEARER")
|
||||
|
||||
|
||||
def ensure_dependencies_installed():
|
||||
aria_ret_val = check_for_aria()
|
||||
if not aria_ret_val:
|
||||
@ -925,6 +949,7 @@ def ensure_dependencies_installed():
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def check_dirs():
|
||||
if not os.path.exists(saved_dir):
|
||||
os.makedirs(saved_dir)
|
||||
@ -932,29 +957,17 @@ def check_dirs():
|
||||
if not os.path.exists(download_dir):
|
||||
os.makedirs(download_dir)
|
||||
|
||||
def load_keys():
|
||||
|
||||
def try_load_keys():
|
||||
global keys
|
||||
f = open(keyfile_path, 'r')
|
||||
keys = json.loads(f.read())
|
||||
|
||||
|
||||
def UdemyDownloader():
|
||||
global udemy, course, resource
|
||||
check_dirs()
|
||||
|
||||
# warn that the keyfile is not found
|
||||
if not os.path.isfile(keyfile_path):
|
||||
print("!!! Keyfile not found! This means you probably didn't rename the keyfile correctly, DRM lecture decryption will fail! If you aren't downloading DRM encrypted courses, you can ignore this message. !!!")
|
||||
print("Waiting for 10 seconds...")
|
||||
time.sleep(10)
|
||||
|
||||
load_keys()
|
||||
|
||||
# ensure 3rd party binaries are installed
|
||||
ensure_dependencies_installed();
|
||||
|
||||
# loads the .env file
|
||||
load_dotenv()
|
||||
|
||||
# Creates a new parser and sets up the arguments
|
||||
setup_parser()
|
||||
|
||||
@ -962,6 +975,22 @@ def UdemyDownloader():
|
||||
args = parser.parse_args()
|
||||
process_args(args=args)
|
||||
|
||||
# warn that the keyfile is not found
|
||||
if not os.path.exists(keyfile_path):
|
||||
print("!!! Keyfile not found! This means you probably didn't rename the keyfile correctly, DRM lecture decryption will fail! If you aren't downloading DRM encrypted courses, you can ignore this message. !!!")
|
||||
if not iknowwhatimdoing:
|
||||
print("Waiting for 10 seconds...")
|
||||
time.sleep(10)
|
||||
|
||||
else:
|
||||
try_load_keys()
|
||||
|
||||
# ensure 3rd party binaries are installed
|
||||
ensure_dependencies_installed()
|
||||
|
||||
# loads the .env file
|
||||
load_dotenv()
|
||||
|
||||
udemy = Udemy(access_token=bearer_token)
|
||||
|
||||
print("> Fetching course information, this may take a minute...")
|
||||
@ -989,7 +1018,7 @@ def UdemyDownloader():
|
||||
'w') as f:
|
||||
f.write(json.dumps(course_content))
|
||||
print("Saved course content to file")
|
||||
|
||||
|
||||
course = course_content.get("results")
|
||||
resource = course_content.get("detail")
|
||||
|
||||
@ -1010,7 +1039,7 @@ def UdemyDownloader():
|
||||
|
||||
if save_to_file:
|
||||
with open(_udemy_path,
|
||||
'w') as f:
|
||||
'w') as f:
|
||||
f.write(json.dumps(_udemy))
|
||||
print("Saved parsed data to file")
|
||||
|
||||
@ -1021,4 +1050,4 @@ def UdemyDownloader():
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
UdemyDownloader()
|
||||
UdemyDownloader()
|
||||
|
4
udemy_downloader/__main__.py
Normal file
4
udemy_downloader/__main__.py
Normal file
@ -0,0 +1,4 @@
|
||||
from UdemyDownloader import UdemyDownloader
|
||||
|
||||
if __name__ == "__main__":
|
||||
UdemyDownloader()
|
@ -9,6 +9,7 @@ from mp4parse import F4VParser
|
||||
from widevine_pssh_pb2 import WidevinePsshData
|
||||
from sanitize import sanitize, slugify, SLUG_OK
|
||||
|
||||
|
||||
def extract_kid(mp4_file):
|
||||
"""
|
||||
Parameters
|
||||
@ -26,7 +27,8 @@ def extract_kid(mp4_file):
|
||||
boxes = F4VParser.parse(filename=mp4_file)
|
||||
for box in boxes:
|
||||
if box.header.box_type == 'moov':
|
||||
pssh_box = next(x for x in box.pssh if x.system_id == "edef8ba979d64acea3c827dcd51d21ed")
|
||||
pssh_box = next(x for x in box.pssh if x.system_id ==
|
||||
"edef8ba979d64acea3c827dcd51d21ed")
|
||||
hex = codecs.decode(pssh_box.payload, "hex")
|
||||
|
||||
pssh = WidevinePsshData()
|
||||
@ -37,6 +39,7 @@ def extract_kid(mp4_file):
|
||||
# No Moof or PSSH header found
|
||||
return None
|
||||
|
||||
|
||||
def _clean(text):
|
||||
ok = re.compile(r'[^\\/:*?!"<>|]')
|
||||
text = "".join(x if ok.match(x) else "_" for x in text)
|
||||
@ -49,6 +52,7 @@ def _sanitize(self, unsafetext):
|
||||
slugify(unsafetext, lower=False, spaces=True, ok=SLUG_OK + "().[]")))
|
||||
return text
|
||||
|
||||
|
||||
def durationtoseconds(period):
|
||||
"""
|
||||
@author Jayapraveen
|
||||
@ -74,6 +78,7 @@ def durationtoseconds(period):
|
||||
print("Duration Format Error")
|
||||
return None
|
||||
|
||||
|
||||
def cleanup(path):
|
||||
"""
|
||||
@author Jayapraveen
|
||||
@ -86,39 +91,47 @@ def cleanup(path):
|
||||
print(f"Error deleting file: {file_list}")
|
||||
os.removedirs(path)
|
||||
|
||||
|
||||
def remove_files(files):
|
||||
for file in files:
|
||||
os.remove(file)
|
||||
|
||||
|
||||
def merge(video_title, video_filepath, audio_filepath, output_path, use_h265, h265_crf, h265_preset):
|
||||
"""
|
||||
@author Jayapraveen
|
||||
"""
|
||||
if os.name == "nt":
|
||||
if use_h265:
|
||||
command = "ffmpeg -y -i \"{}\" -i \"{}\" -c:v libx265 -crf {} -preset {} -c:a copy -fflags +bitexact -map_metadata -1 -metadata title=\"{}\" \"{}\"".format(video_filepath, audio_filepath, h265_crf, h265_preset, video_title, output_path)
|
||||
command = "ffmpeg -y -i \"{}\" -i \"{}\" -c:v libx265 -crf {} -preset {} -c:a copy -fflags +bitexact -map_metadata -1 -metadata title=\"{}\" \"{}\"".format(
|
||||
video_filepath, audio_filepath, h265_crf, h265_preset, video_title, output_path)
|
||||
else:
|
||||
command = "ffmpeg -y -i \"{}\" -i \"{}\" -c:v copy -vtag hvc1 -c:a copy -fflags +bitexact -map_metadata -1 -metadata title=\"{}\" \"{}\"".format(video_filepath, audio_filepath, video_title, output_path)
|
||||
command = "ffmpeg -y -i \"{}\" -i \"{}\" -c:v copy -c:a copy -fflags +bitexact -map_metadata -1 -metadata title=\"{}\" \"{}\"".format(
|
||||
video_filepath, audio_filepath, video_title, output_path)
|
||||
else:
|
||||
if use_h265:
|
||||
command = "nide -n 7 ffmpeg -y -i \"{}\" -i \"{}\" -c:v libx265 -crf {} -preset {} -c:a copy -fflags +bitexact -map_metadata -1 -metadata title=\"{}\" \"{}\"".format(video_filepath, audio_filepath, h265_crf, h265_preset, video_title, output_path)
|
||||
command = "nide -n 7 ffmpeg -y -i \"{}\" -i \"{}\" -c:v libx265 -crf {} -preset {} -c:a copy -fflags +bitexact -map_metadata -1 -metadata title=\"{}\" \"{}\"".format(
|
||||
video_filepath, audio_filepath, h265_crf, h265_preset, video_title, output_path)
|
||||
else:
|
||||
command = "nide -n 7 ffmpeg -y -i \"{}\" -i \"{}\" -c:v copy -vtag hvc1 -c:a copy -fflags +bitexact -map_metadata -1 -metadata title=\"{}\" \"{}\"".format(video_filepath, audio_filepath, video_title, output_path)
|
||||
command = "nide -n 7 ffmpeg -y -i \"{}\" -i \"{}\" -c:v copy -c:a copy -fflags +bitexact -map_metadata -1 -metadata title=\"{}\" \"{}\"".format(
|
||||
video_filepath, audio_filepath, video_title, output_path)
|
||||
return os.system(command)
|
||||
|
||||
|
||||
def decrypt(key, in_filepath, out_filepath):
|
||||
"""
|
||||
@author Jayapraveen
|
||||
"""
|
||||
if (os.name == "nt"):
|
||||
ret_code = os.system(f"mp4decrypt --key 1:%s \"%s\" \"%s\"" %
|
||||
(key, in_filepath, out_filepath))
|
||||
(key, in_filepath, out_filepath))
|
||||
else:
|
||||
ret_code = os.system(f"nice -n 7 mp4decrypt --key 1:%s \"%s\" \"%s\"" %
|
||||
(key, in_filepath, out_filepath))
|
||||
(key, in_filepath, out_filepath))
|
||||
|
||||
return ret_code
|
||||
|
||||
|
||||
def check_for_aria():
|
||||
try:
|
||||
subprocess.Popen(["aria2c", "-v"],
|
||||
@ -161,4 +174,4 @@ def check_for_mp4decrypt():
|
||||
print(
|
||||
"> Unexpected exception while checking for MP4Decrypt, please tell the program author about this! ",
|
||||
e)
|
||||
return True
|
||||
return True
|
||||
|
Loading…
x
Reference in New Issue
Block a user