mirror of
https://cdm-project.com/Download-Tools/udemy-downloader.git
synced 2025-05-04 01:44: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!**
|
- **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!**
|
- **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.
|
- 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
|
# 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).
|
Windows is the primary development OS, but I've made an effort to support Linux also (Mac untested).
|
||||||
|
|
||||||
# Requirements
|
# 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 `.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`
|
- 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)
|
- 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)
|
- 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
|
## 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`
|
- 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
|
# 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]
|
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]
|
||||||
[--keep-vtt] [--skip-hls] [--info] [--id-as-course-name] [--save-to-file] [--load-from-file] [--log-level LOG_LEVEL] [-v]
|
[--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
|
Udemy Downloader
|
||||||
|
|
||||||
optional arguments:
|
options:
|
||||||
-h, --help show this help message and exit
|
-h, --help show this help message and exit
|
||||||
-c COURSE_URL, --course-url COURSE_URL
|
-c COURSE_URL, --course-url COURSE_URL
|
||||||
The URL of the course to download
|
The URL of the course to download
|
||||||
-b BEARER_TOKEN, --bearer BEARER_TOKEN
|
-b BEARER_TOKEN, --bearer BEARER_TOKEN
|
||||||
The Bearer token to use
|
The Bearer token to use
|
||||||
-q QUALITY, --quality QUALITY
|
-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
|
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
|
||||||
for each lecture
|
|
||||||
-l LANG, --lang LANG The language to download for captions, specify 'all' to download all captions (Default is 'en')
|
-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
|
-cd CONCURRENT_DOWNLOADS, --concurrent-downloads CONCURRENT_DOWNLOADS
|
||||||
The number of maximum concurrent downloads for segments (HLS and DASH, must be a number 1-30)
|
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)
|
--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
|
--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
|
--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
|
-sc, --subscription-course
|
||||||
after a certain amount of time)
|
Mark the course as a subscription based course, use this if you are having problems with the program auto detecting it
|
||||||
--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
|
--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
|
||||||
certain amount of time)
|
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
|
--log-level LOG_LEVEL
|
||||||
Logging level: one of DEBUG, INFO, ERROR, WARNING, CRITICAL (Default is INFO)
|
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
|
-v, --version show program's version number and exit
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -170,6 +172,12 @@ optional arguments:
|
|||||||
- `python main.py -c <Course URL> --log-level CRITICAL`
|
- `python main.py -c <Course URL> --log-level CRITICAL`
|
||||||
- Use course ID as the course name:
|
- Use course ID as the course name:
|
||||||
- `python main.py -c <Course URL> --id-as-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
|
If you encounter errors while downloading such as
|
||||||
|
|
||||||
|
87
main.py
87
main.py
@ -12,7 +12,6 @@ from html.parser import HTMLParser as compat_HTMLParser
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import IO
|
from typing import IO
|
||||||
|
|
||||||
import cloudscraper
|
|
||||||
import m3u8
|
import m3u8
|
||||||
import requests
|
import requests
|
||||||
import yt_dlp
|
import yt_dlp
|
||||||
@ -52,6 +51,9 @@ info = None
|
|||||||
keys = {}
|
keys = {}
|
||||||
id_as_course_name = False
|
id_as_course_name = False
|
||||||
is_subscription_course = False
|
is_subscription_course = False
|
||||||
|
use_h265 = False
|
||||||
|
h265_crf = 28
|
||||||
|
h265_preset = "medium"
|
||||||
|
|
||||||
|
|
||||||
# from https://stackoverflow.com/a/21978778/9785713
|
# 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
|
# this is the first function that is called, we parse the arguments, setup the logger, and ensure that required directories exist
|
||||||
def pre_run():
|
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
|
# make sure the directory exists
|
||||||
if not os.path.exists(DOWNLOAD_DIR):
|
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",
|
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(
|
parser.add_argument(
|
||||||
|
"-sc",
|
||||||
"--subscription-course",
|
"--subscription-course",
|
||||||
dest="is_subscription_course",
|
dest="is_subscription_course",
|
||||||
action="store_true",
|
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",
|
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(
|
parser.add_argument(
|
||||||
"--save-to-file",
|
"--save-to-file",
|
||||||
dest="save_to_file",
|
dest="save_to_file",
|
||||||
@ -177,6 +179,26 @@ def pre_run():
|
|||||||
type=str,
|
type=str,
|
||||||
help="Logging level: one of DEBUG, INFO, ERROR, WARNING, CRITICAL (Default is INFO)",
|
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__))
|
parser.add_argument("-v", "--version", action="version", version="You are running version {version}".format(version=__version__))
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
@ -215,6 +237,12 @@ def pre_run():
|
|||||||
course_url = args.course_url
|
course_url = args.course_url
|
||||||
if args.info:
|
if args.info:
|
||||||
info = 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:
|
||||||
if args.log_level.upper() == "DEBUG":
|
if args.log_level.upper() == "DEBUG":
|
||||||
LOG_LEVEL = logging.DEBUG
|
LOG_LEVEL = logging.DEBUG
|
||||||
@ -919,26 +947,6 @@ class UdemyAuth(object):
|
|||||||
self.password = password
|
self.password = password
|
||||||
self._cache = cache_session
|
self._cache = cache_session
|
||||||
self._session = 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=""):
|
def authenticate(self, bearer_token=""):
|
||||||
if bearer_token:
|
if bearer_token:
|
||||||
@ -990,11 +998,21 @@ def mux_process(video_title, video_filepath, audio_filepath, output_path):
|
|||||||
@author Jayapraveen
|
@author Jayapraveen
|
||||||
"""
|
"""
|
||||||
if os.name == "nt":
|
if os.name == "nt":
|
||||||
command = 'ffmpeg -nostdin -loglevel error -y -i "{}" -i "{}" -acodec copy -vcodec copy -fflags +bitexact -map_metadata -1 -metadata title="{}" "{}"'.format(
|
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
|
video_filepath, audio_filepath, video_title, output_path
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
command = 'nice -n 7 ffmpeg -nostdin -loglevel error -y -i "{}" -i "{}" -acodec copy -vcodec copy -fflags +bitexact -map_metadata -1 -metadata title="{}" "{}"'.format(
|
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
|
video_filepath, audio_filepath, video_title, output_path
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1320,17 +1338,28 @@ def process_lecture(lecture, lecture_path, lecture_file_name, chapter_dir):
|
|||||||
source_type = source.get("type")
|
source_type = source.get("type")
|
||||||
if source_type == "hls":
|
if source_type == "hls":
|
||||||
temp_filepath = lecture_path.replace(".mp4", ".%(ext)s")
|
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:
|
if disable_ipv6:
|
||||||
args.append("--downloader-args")
|
cmd.append("--downloader-args")
|
||||||
args.append('aria2c:"--disable-ipv6"')
|
cmd.append('aria2c:"--disable-ipv6"')
|
||||||
process = subprocess.Popen(args)
|
process = subprocess.Popen(cmd)
|
||||||
log_subprocess_output("YTDLP-STDOUT", process.stdout)
|
log_subprocess_output("YTDLP-STDOUT", process.stdout)
|
||||||
log_subprocess_output("YTDLP-STDERR", process.stderr)
|
log_subprocess_output("YTDLP-STDERR", process.stderr)
|
||||||
ret_code = process.wait()
|
ret_code = process.wait()
|
||||||
if ret_code == 0:
|
if ret_code == 0:
|
||||||
# os.rename(temp_filepath, lecture_path)
|
# os.rename(temp_filepath, lecture_path)
|
||||||
logger.info(" > HLS Download success")
|
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:
|
else:
|
||||||
ret_code = download_aria(url, chapter_dir, lecture_title + ".mp4")
|
ret_code = download_aria(url, chapter_dir, lecture_title + ".mp4")
|
||||||
logger.debug(f" > Download return code: {ret_code}")
|
logger.debug(f" > Download return code: {ret_code}")
|
||||||
|
@ -9,7 +9,6 @@ m3u8
|
|||||||
colorama
|
colorama
|
||||||
yt-dlp
|
yt-dlp
|
||||||
bitstring
|
bitstring
|
||||||
cloudscraper
|
|
||||||
unidecode
|
unidecode
|
||||||
beautifulsoup4
|
beautifulsoup4
|
||||||
lxml
|
lxml
|
||||||
|
Loading…
x
Reference in New Issue
Block a user