mirror of
https://cdm-project.com/Download-Tools/udemy-downloader.git
synced 2025-04-30 02:04:27 +02:00
add h265 support
This commit is contained in:
parent
f321791819
commit
cb906d5eaf
38
README.md
38
README.md
@ -13,13 +13,10 @@
|
||||
- **This tool will not work without decryption keys. Do not bother installing unless you already have keys or can obtain them!**
|
||||
- **Downloading courses is against Udemy's Terms of Service, I am NOT held responsible for your account getting suspended as a result from the use of this program!**
|
||||
- This program is WIP, the code is provided as-is and I am not held resposible for any legal issues resulting from the use of this program.
|
||||
- You can find information on downgrading the CDM version on the wiki, __please note that CDM version 2209 is already revoked as of 12/5/2021 and no longer works on Udemy as of 2/1/2022. This information is just to have it available.__
|
||||
- Chrome: https://github.com/Puyodead1/udemy-downloader/wiki/Downgrade-CDM-to-2209-on-Chrome-(Windows)
|
||||
- FireFox: https://github.com/Puyodead1/udemy-downloader/wiki/Downgrade-CDM-to-2209-on-FireFox-(Windows)
|
||||
|
||||
# Description
|
||||
|
||||
Utility script to download Udemy courses, has support for DRM videos but requires the user to aquire the decryption key (for legal reasons).<br>
|
||||
Utility script to download Udemy courses, has support for DRM videos but requires the user to acquire the decryption key (for legal reasons).<br>
|
||||
Windows is the primary development OS, but I've made an effort to support Linux also (Mac untested).
|
||||
|
||||
# Requirements
|
||||
@ -50,7 +47,7 @@ You will need to get a few things before you can use this program:
|
||||
- rename `.env.sample` to `.env` _(you only need to do this if you plan to use the .env file to store your bearer token)_
|
||||
- rename `keyfile.example.json` to `keyfile.json`
|
||||
|
||||
## Aquire Bearer Token
|
||||
## Acquire Bearer Token
|
||||
|
||||
- Firefox: [Udemy-DL Guide](https://github.com/r0oth3x49/udemy-dl/issues/389#issuecomment-491903900)
|
||||
- Chrome: [Udemy-DL Guide](https://github.com/r0oth3x49/udemy-dl/issues/389#issuecomment-492569372)
|
||||
@ -58,7 +55,7 @@ You will need to get a few things before you can use this program:
|
||||
|
||||
## Key ID and Key
|
||||
|
||||
It is up to you to aquire the key and key ID. Please **DO NOT** ask me for help acquiring these, decrypting DRM protected content can be considered piracy. The tool required for this has already been discused in a GitHub issue.
|
||||
It is up to you to acquire the key and key ID. Please **DO NOT** ask me for help acquiring these, decrypting DRM protected content can be considered piracy. The tool required for this has already been discused in a GitHub issue.
|
||||
|
||||
- Enter the key and key id in the `keyfile.json`
|
||||
- 
|
||||
@ -95,20 +92,19 @@ Note the link is `/course` not `/program-taking`. It is also important that the
|
||||
# Advanced Usage
|
||||
|
||||
```
|
||||
usage: main.py [-h] -c COURSE_URL [-b BEARER_TOKEN] [-q QUALITY] [-l LANG] [-cd CONCURRENT_DOWNLOADS] [--disable-ipv6] [--skip-lectures] [--download-assets] [--download-captions]
|
||||
[--keep-vtt] [--skip-hls] [--info] [--id-as-course-name] [--save-to-file] [--load-from-file] [--log-level LOG_LEVEL] [-v]
|
||||
usage: main.py [-h] -c COURSE_URL [-b BEARER_TOKEN] [-q QUALITY] [-l LANG] [-cd CONCURRENT_DOWNLOADS] [--disable-ipv6] [--skip-lectures] [--download-assets] [--download-captions] [--keep-vtt] [--skip-hls]
|
||||
[--info] [--id-as-course-name] [-sc] [--save-to-file] [--load-from-file] [--log-level LOG_LEVEL] [--use-h265] [--h265-crf H265_CRF] [--h265-preset H265_PRESET] [-v]
|
||||
|
||||
Udemy Downloader
|
||||
|
||||
optional arguments:
|
||||
options:
|
||||
-h, --help show this help message and exit
|
||||
-c COURSE_URL, --course-url COURSE_URL
|
||||
The URL of the course to download
|
||||
-b BEARER_TOKEN, --bearer BEARER_TOKEN
|
||||
The Bearer token to use
|
||||
-q QUALITY, --quality QUALITY
|
||||
Download specific video quality. If the requested quality isn't available, the closest quality will be used. If not specified, the best quality will be downloaded
|
||||
for each lecture
|
||||
Download specific video quality. If the requested quality isn't available, the closest quality will be used. If not specified, the best quality will be downloaded for each lecture
|
||||
-l LANG, --lang LANG The language to download for captions, specify 'all' to download all captions (Default is 'en')
|
||||
-cd CONCURRENT_DOWNLOADS, --concurrent-downloads CONCURRENT_DOWNLOADS
|
||||
The number of maximum concurrent downloads for segments (HLS and DASH, must be a number 1-30)
|
||||
@ -120,12 +116,18 @@ optional arguments:
|
||||
--skip-hls If specified, hls streams will be skipped (faster fetching) (hls streams usually contain 1080p quality for non-drm lectures)
|
||||
--info If specified, only course information will be printed, nothing will be downloaded
|
||||
--id-as-course-name If specified, the course id will be used in place of the course name for the output directory. This is a 'hack' to reduce the path length
|
||||
--save-to-file If specified, course content will be saved to a file that can be loaded later with --load-from-file, this can reduce processing time (Note that asset links expire
|
||||
after a certain amount of time)
|
||||
--load-from-file If specified, course content will be loaded from a previously saved file with --save-to-file, this can reduce processing time (Note that asset links expire after a
|
||||
certain amount of time)
|
||||
-sc, --subscription-course
|
||||
Mark the course as a subscription based course, use this if you are having problems with the program auto detecting it
|
||||
--save-to-file If specified, course content will be saved to a file that can be loaded later with --load-from-file, this can reduce processing time (Note that asset links expire after a certain
|
||||
amount of time)
|
||||
--load-from-file If specified, course content will be loaded from a previously saved file with --save-to-file, this can reduce processing time (Note that asset links expire after a certain amount of
|
||||
time)
|
||||
--log-level LOG_LEVEL
|
||||
Logging level: one of DEBUG, INFO, ERROR, WARNING, CRITICAL (Default is INFO)
|
||||
--use-h265 If specified, videos will be encoded with the H.265 codec
|
||||
--h265-crf H265_CRF Set a custom CRF value for H.265 encoding. FFMPEG default is 28
|
||||
--h265-preset H265_PRESET
|
||||
Set a custom preset value for H.265 encoding. FFMPEG default is medium
|
||||
-v, --version show program's version number and exit
|
||||
```
|
||||
|
||||
@ -170,6 +172,12 @@ optional arguments:
|
||||
- `python main.py -c <Course URL> --log-level CRITICAL`
|
||||
- Use course ID as the course name:
|
||||
- `python main.py -c <Course URL> --id-as-course-name`
|
||||
- Encode in H.265:
|
||||
- `python main.py -c <Course URL> --use-h265`
|
||||
- Encode in H.265 with custom CRF:
|
||||
- `python main.py -c <Course URL> --use-h265 -h265-crf 20`
|
||||
- Encode in H.265 with custom preset:
|
||||
- `python main.py -c <Course URL> --use-h265 --h265-preset faster`
|
||||
|
||||
If you encounter errors while downloading such as
|
||||
|
||||
|
95
main.py
95
main.py
@ -12,7 +12,6 @@ from html.parser import HTMLParser as compat_HTMLParser
|
||||
from pathlib import Path
|
||||
from typing import IO
|
||||
|
||||
import cloudscraper
|
||||
import m3u8
|
||||
import requests
|
||||
import yt_dlp
|
||||
@ -52,6 +51,9 @@ info = None
|
||||
keys = {}
|
||||
id_as_course_name = False
|
||||
is_subscription_course = False
|
||||
use_h265 = False
|
||||
h265_crf = 28
|
||||
h265_preset = "medium"
|
||||
|
||||
|
||||
# from https://stackoverflow.com/a/21978778/9785713
|
||||
@ -64,7 +66,7 @@ def log_subprocess_output(prefix: str, pipe: IO[bytes]):
|
||||
|
||||
# this is the first function that is called, we parse the arguments, setup the logger, and ensure that required directories exist
|
||||
def pre_run():
|
||||
global cookies, dl_assets, skip_lectures, dl_captions, caption_locale, quality, bearer_token, portal_name, course_name, keep_vtt, skip_hls, concurrent_downloads, disable_ipv6, load_from_file, save_to_file, bearer_token, course_url, info, logger, keys, id_as_course_name, is_subscription_course, LOG_LEVEL
|
||||
global cookies, dl_assets, skip_lectures, dl_captions, caption_locale, quality, bearer_token, portal_name, course_name, keep_vtt, skip_hls, concurrent_downloads, disable_ipv6, load_from_file, save_to_file, bearer_token, course_url, info, logger, keys, id_as_course_name, is_subscription_course, LOG_LEVEL, use_h265, h265_crf, h265_preset
|
||||
|
||||
# make sure the directory exists
|
||||
if not os.path.exists(DOWNLOAD_DIR):
|
||||
@ -153,12 +155,12 @@ def pre_run():
|
||||
help="If specified, the course id will be used in place of the course name for the output directory. This is a 'hack' to reduce the path length",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-sc",
|
||||
"--subscription-course",
|
||||
dest="is_subscription_course",
|
||||
action="store_true",
|
||||
help="Mark the course as a subscription based course, use this if you are having problems with the program auto detecting it",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--save-to-file",
|
||||
dest="save_to_file",
|
||||
@ -177,6 +179,26 @@ def pre_run():
|
||||
type=str,
|
||||
help="Logging level: one of DEBUG, INFO, ERROR, WARNING, CRITICAL (Default is INFO)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--use-h265",
|
||||
dest="use_h265",
|
||||
action="store_true",
|
||||
help="If specified, videos will be encoded with the H.265 codec",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--h265-crf",
|
||||
dest="h265_crf",
|
||||
type=int,
|
||||
default=28,
|
||||
help="Set a custom CRF value for H.265 encoding. FFMPEG default is 28",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--h265-preset",
|
||||
dest="h265_preset",
|
||||
type=str,
|
||||
default="medium",
|
||||
help="Set a custom preset value for H.265 encoding. FFMPEG default is medium",
|
||||
)
|
||||
parser.add_argument("-v", "--version", action="version", version="You are running version {version}".format(version=__version__))
|
||||
|
||||
args = parser.parse_args()
|
||||
@ -215,6 +237,12 @@ def pre_run():
|
||||
course_url = args.course_url
|
||||
if args.info:
|
||||
info = args.info
|
||||
if args.use_h265:
|
||||
use_h265 = True
|
||||
if args.h265_crf:
|
||||
h265_crf = args.h265_crf
|
||||
if args.h265_preset:
|
||||
h265_preset = args.h265_preset
|
||||
if args.log_level:
|
||||
if args.log_level.upper() == "DEBUG":
|
||||
LOG_LEVEL = logging.DEBUG
|
||||
@ -919,26 +947,6 @@ class UdemyAuth(object):
|
||||
self.password = password
|
||||
self._cache = cache_session
|
||||
self._session = Session()
|
||||
self._cloudsc = cloudscraper.create_scraper()
|
||||
|
||||
def _form_hidden_input(self, form_id):
|
||||
try:
|
||||
resp = self._cloudsc.get(LOGIN_URL)
|
||||
resp.raise_for_status()
|
||||
webpage = resp.text
|
||||
except conn_error as error:
|
||||
raise error
|
||||
else:
|
||||
login_form = hidden_inputs(
|
||||
search_regex(
|
||||
r'(?is)<form[^>]+?id=(["\'])%s\1[^>]*>(?P<form>.+?)</form>' % form_id,
|
||||
webpage,
|
||||
"%s form" % form_id,
|
||||
group="form",
|
||||
)
|
||||
)
|
||||
login_form.update({"email": self.username, "password": self.password})
|
||||
return login_form
|
||||
|
||||
def authenticate(self, bearer_token=""):
|
||||
if bearer_token:
|
||||
@ -990,13 +998,23 @@ def mux_process(video_title, video_filepath, audio_filepath, output_path):
|
||||
@author Jayapraveen
|
||||
"""
|
||||
if os.name == "nt":
|
||||
command = 'ffmpeg -nostdin -loglevel error -y -i "{}" -i "{}" -acodec copy -vcodec copy -fflags +bitexact -map_metadata -1 -metadata title="{}" "{}"'.format(
|
||||
video_filepath, audio_filepath, video_title, output_path
|
||||
)
|
||||
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
|
||||
)
|
||||
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
|
||||
)
|
||||
else:
|
||||
command = 'nice -n 7 ffmpeg -nostdin -loglevel error -y -i "{}" -i "{}" -acodec copy -vcodec copy -fflags +bitexact -map_metadata -1 -metadata title="{}" "{}"'.format(
|
||||
video_filepath, audio_filepath, video_title, output_path
|
||||
)
|
||||
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
|
||||
)
|
||||
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
|
||||
)
|
||||
|
||||
process = subprocess.Popen(command, shell=True)
|
||||
log_subprocess_output("FFMPEG-STDOUT", process.stdout)
|
||||
@ -1320,17 +1338,28 @@ def process_lecture(lecture, lecture_path, lecture_file_name, chapter_dir):
|
||||
source_type = source.get("type")
|
||||
if source_type == "hls":
|
||||
temp_filepath = lecture_path.replace(".mp4", ".%(ext)s")
|
||||
args = ["yt-dlp", "--force-generic-extractor", "--concurrent-fragments", f"{concurrent_downloads}", "--downloader", "aria2c", "-o", f"{temp_filepath}", f"{url}"]
|
||||
cmd = ["yt-dlp", "--force-generic-extractor", "--concurrent-fragments", f"{concurrent_downloads}", "--downloader", "aria2c", "-o", f"{temp_filepath}", f"{url}"]
|
||||
if disable_ipv6:
|
||||
args.append("--downloader-args")
|
||||
args.append('aria2c:"--disable-ipv6"')
|
||||
process = subprocess.Popen(args)
|
||||
cmd.append("--downloader-args")
|
||||
cmd.append('aria2c:"--disable-ipv6"')
|
||||
process = subprocess.Popen(cmd)
|
||||
log_subprocess_output("YTDLP-STDOUT", process.stdout)
|
||||
log_subprocess_output("YTDLP-STDERR", process.stderr)
|
||||
ret_code = process.wait()
|
||||
if ret_code == 0:
|
||||
# os.rename(temp_filepath, lecture_path)
|
||||
logger.info(" > HLS Download success")
|
||||
cmd = ["ffmpeg", "-y", "-i", lecture_path, "-c:v", "libx265", "-c:a", "copy", lecture_path + ".mp4"]
|
||||
process = subprocess.Popen(cmd)
|
||||
log_subprocess_output("FFMPEG-STDOUT", process.stdout)
|
||||
log_subprocess_output("FFMPEG-STDERR", process.stderr)
|
||||
ret_code = process.wait()
|
||||
if ret_code == 0:
|
||||
os.remove(lecture_path)
|
||||
os.remove(lecture_path + ".mp4", lecture_path)
|
||||
logger.info(" > Encoding complete")
|
||||
else:
|
||||
logger.error(" > Encoding returned non-zero return code")
|
||||
else:
|
||||
ret_code = download_aria(url, chapter_dir, lecture_title + ".mp4")
|
||||
logger.debug(f" > Download return code: {ret_code}")
|
||||
|
@ -9,7 +9,6 @@ m3u8
|
||||
colorama
|
||||
yt-dlp
|
||||
bitstring
|
||||
cloudscraper
|
||||
unidecode
|
||||
beautifulsoup4
|
||||
lxml
|
||||
|
Loading…
x
Reference in New Issue
Block a user