add h265 support

This commit is contained in:
Puyodead1 2023-03-06 17:16:00 -05:00
parent f321791819
commit cb906d5eaf
No known key found for this signature in database
GPG Key ID: A4FA4FEC0DD353FC
3 changed files with 85 additions and 49 deletions

View File

@ -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`
- ![keyfile example](https://i.imgur.com/e5aU0ng.png)
@ -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
View File

@ -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}")

View File

@ -9,7 +9,6 @@ m3u8
colorama
yt-dlp
bitstring
cloudscraper
unidecode
beautifulsoup4
lxml