mirror of
https://cdm-project.com/Download-Tools/udemy-downloader.git
synced 2025-04-30 00:24:25 +02:00
204 lines
9.3 KiB
Python
204 lines
9.3 KiB
Python
#dashdrmmultisegmentdownloader
|
|
import os,requests,shutil,json,glob
|
|
from mpegdash.parser import MPEGDASHParser
|
|
from mpegdash.nodes import Descriptor
|
|
from mpegdash.utils import (
|
|
parse_attr_value, parse_child_nodes, parse_node_value,
|
|
write_attr_value, write_child_node, write_node_value
|
|
)
|
|
from utils import extract_kid
|
|
|
|
#global ids
|
|
retry = 3
|
|
download_dir = os.getcwd() + '\out_dir' # set the folder to output
|
|
working_dir = os.getcwd() + "\working_dir" # set the folder to download ephemeral files
|
|
keyfile_path = os.getcwd() + "\keyfile.json"
|
|
|
|
if not os.path.exists(working_dir):
|
|
os.makedirs(working_dir)
|
|
|
|
#Get the keys
|
|
with open(keyfile_path,'r') as keyfile:
|
|
keyfile = keyfile.read()
|
|
keyfile = json.loads(keyfile)
|
|
|
|
|
|
#Patching the Mpegdash lib for keyID
|
|
def __init__(self):
|
|
self.scheme_id_uri = '' # xs:anyURI (required)
|
|
self.value = None # xs:string
|
|
self.id = None # xs:string
|
|
self.key_id = None # xs:string
|
|
|
|
def parse(self, xmlnode):
|
|
self.scheme_id_uri = parse_attr_value(xmlnode, 'schemeIdUri', str)
|
|
self.value = parse_attr_value(xmlnode, 'value', str)
|
|
self.id = parse_attr_value(xmlnode, 'id', str)
|
|
self.key_id = parse_attr_value(xmlnode, 'cenc:default_KID', str)
|
|
|
|
def write(self, xmlnode):
|
|
write_attr_value(xmlnode, 'schemeIdUri', self.scheme_id_uri)
|
|
write_attr_value(xmlnode, 'value', self.value)
|
|
write_attr_value(xmlnode, 'id', self.id)
|
|
write_attr_value(xmlnode, 'cenc:default_KID', self.key_id)
|
|
|
|
Descriptor.__init__ = __init__
|
|
Descriptor.parse = parse
|
|
Descriptor.write = write
|
|
|
|
def durationtoseconds(period):
|
|
#Duration format in PTxDxHxMxS
|
|
if(period[:2] == "PT"):
|
|
period = period[2:]
|
|
day = int(period.split("D")[0] if 'D' in period else 0)
|
|
hour = int(period.split("H")[0].split("D")[-1] if 'H' in period else 0)
|
|
minute = int(period.split("M")[0].split("H")[-1] if 'M' in period else 0)
|
|
second = period.split("S")[0].split("M")[-1]
|
|
print("Total time: " + str(day) + " days " + str(hour) + " hours " + str(minute) + " minutes and " + str(second) + " seconds")
|
|
total_time = float(str((day * 24 * 60 * 60) + (hour * 60 * 60) + (minute * 60) + (int(second.split('.')[0]))) + '.' + str(int(second.split('.')[-1])))
|
|
return total_time
|
|
|
|
else:
|
|
print("Duration Format Error")
|
|
return None
|
|
|
|
def download_media(filename,url,epoch = 0):
|
|
if(os.path.isfile(filename)):
|
|
print("Segment already downloaded.. skipping..")
|
|
else:
|
|
media = requests.get(url, stream=True)
|
|
media_length = int(media.headers.get("content-length"))
|
|
if media.status_code == 200:
|
|
if(os.path.isfile(filename) and os.path.getsize(filename) >= media_length):
|
|
print("Segment already downloaded.. skipping write to disk..")
|
|
else:
|
|
try:
|
|
with open(filename, 'wb') as video_file:
|
|
shutil.copyfileobj(media.raw, video_file)
|
|
print("Segment downloaded: " + filename)
|
|
return False #Successfully downloaded the file
|
|
except:
|
|
print("Connection error: Reattempting download of segment..")
|
|
download_media(filename,url, epoch + 1)
|
|
|
|
if os.path.getsize(filename) >= media_length:
|
|
pass
|
|
else:
|
|
print("Segment is faulty.. Redownloading...")
|
|
download_media(filename,url, epoch + 1)
|
|
elif(media.status_code == 404):
|
|
print("Probably end hit!\n",url)
|
|
return True #Probably hit the last of the file
|
|
else:
|
|
if (epoch > retry):
|
|
exit("Error fetching segment, exceeded retry times.")
|
|
print("Error fetching segment file.. Redownloading...")
|
|
download_media(filename,url, epoch + 1)
|
|
|
|
def cleanup(path):
|
|
leftover_files = glob.glob(path + '/*.mp4', recursive=True)
|
|
mpd_files = glob.glob(path + '/*.mpd', recursive=True)
|
|
leftover_files = leftover_files + mpd_files
|
|
for file_list in leftover_files:
|
|
try:
|
|
os.remove(file_list)
|
|
except OSError:
|
|
print(f"Error deleting file: {file_list}")
|
|
|
|
def mux_process(video_title,outfile):
|
|
if os.name == "nt":
|
|
command = f"ffmpeg -y -i decrypted_audio.mp4 -i decrypted_video.mp4 -acodec copy -vcodec copy -fflags +bitexact -map_metadata -1 -metadata title=\"{video_title}\" -metadata creation_time=2020-00-00T70:05:30.000000Z \"{outfile}.mp4\""
|
|
else:
|
|
command = f"nice -n 7 ffmpeg -y -i decrypted_audio.mp4 -i decrypted_video.mp4 -acodec copy -vcodec copy -fflags +bitexact -map_metadata -1 -metadata title=\"{video_title}\" -metadata creation_time=2020-00-00T70:05:30.000000Z {outfile}.mp4"
|
|
os.system(command)
|
|
|
|
def decrypt(kid,filename):
|
|
try:
|
|
key = keyfile[kid.lower()]
|
|
except KeyError as error:
|
|
exit("Key not found")
|
|
if(os.name == "nt"):
|
|
os.system(f"mp4decrypt --key 1:{key} encrypted_{filename}.mp4 decrypted_{filename}.mp4")
|
|
else:
|
|
os.system(f"nice -n 7 mp4decrypt --key 1:{key} encrypted_{filename}.mp4 decrypted_{filename}.mp4")
|
|
|
|
|
|
def handle_irregular_segments(media_info,video_title,output_path):
|
|
no_segment,video_url,video_init,video_extension,no_segment,audio_url,audio_init,audio_extension = media_info
|
|
download_media("video_0.seg.mp4",video_init)
|
|
video_kid = extract_kid("video_0.seg.mp4")
|
|
print("KID for video file is: " + video_kid)
|
|
download_media("audio_0.seg.mp4",audio_init)
|
|
audio_kid = extract_kid("audio_0.seg.mp4")
|
|
print("KID for audio file is: " + audio_kid)
|
|
for count in range(1,no_segment):
|
|
video_segment_url = video_url.replace("$Number$",str(count))
|
|
audio_segment_url = audio_url.replace("$Number$",str(count))
|
|
video_status = download_media(f"video_{str(count)}.seg.{video_extension}",video_segment_url)
|
|
audio_status = download_media(f"audio_{str(count)}.seg.{audio_extension}",audio_segment_url)
|
|
if(video_status):
|
|
if os.name == "nt":
|
|
video_concat_command = "copy /b " + "+".join([f"video_{i}.seg.{video_extension}" for i in range(0,count)]) + " encrypted_video.mp4"
|
|
audio_concat_command = "copy /b " + "+".join([f"audio_{i}.seg.{audio_extension}" for i in range(0,count)]) + " encrypted_audio.mp4"
|
|
else:
|
|
video_concat_command = "cat " + " ".join([f"video_{i}.seg.{video_extension}" for i in range(0,count)]) + " > encrypted_video.mp4"
|
|
audio_concat_command = "cat " + " ".join([f"audio_{i}.seg.{audio_extension}" for i in range(0,count)]) + " > encrypted_audio.mp4"
|
|
print(video_concat_command)
|
|
print(audio_concat_command)
|
|
os.system(video_concat_command)
|
|
os.system(audio_concat_command)
|
|
decrypt(video_kid,"video")
|
|
decrypt(audio_kid,"audio")
|
|
mux_process(video_title,output_path)
|
|
break
|
|
|
|
|
|
def manifest_parser(mpd_url):
|
|
video = []
|
|
audio = []
|
|
manifest = requests.get(mpd_url).text
|
|
with open("manifest.mpd",'w') as manifest_handler:
|
|
manifest_handler.write(manifest)
|
|
mpd = MPEGDASHParser.parse("./manifest.mpd")
|
|
running_time = durationtoseconds(mpd.media_presentation_duration)
|
|
for period in mpd.periods:
|
|
for adapt_set in period.adaptation_sets:
|
|
print("Processing " + adapt_set.mime_type)
|
|
content_type = adapt_set.mime_type
|
|
repr = adapt_set.representations[-1] # Max Quality
|
|
for segment in repr.segment_templates:
|
|
if(segment.duration):
|
|
print("Media segments are of equal timeframe")
|
|
segment_time = segment.duration / segment.timescale
|
|
total_segments = running_time / segment_time
|
|
else:
|
|
print("Media segments are of inequal timeframe")
|
|
|
|
approx_no_segments = round(running_time / 6) + 20 # aproximate of 6 sec per segment
|
|
print("Expected No of segments:",approx_no_segments)
|
|
if(content_type == "audio/mp4"):
|
|
segment_extension = segment.media.split(".")[-1]
|
|
audio.append(approx_no_segments)
|
|
audio.append(segment.media)
|
|
audio.append(segment.initialization)
|
|
audio.append(segment_extension)
|
|
elif(content_type == "video/mp4"):
|
|
segment_extension = segment.media.split(".")[-1]
|
|
video.append(approx_no_segments)
|
|
video.append(segment.media)
|
|
video.append(segment.initialization)
|
|
video.append(segment_extension)
|
|
return video + audio
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
mpd = "mpd url"
|
|
base_url = mpd.split("index.mpd")[0]
|
|
os.chdir(working_dir)
|
|
media_info = manifest_parser(mpd)
|
|
video_title = "175. Inverse Transforming Vectors" # the video title that gets embeded into the mp4 file metadata
|
|
output_path = download_dir + "\\175. Inverse Transforming Vectors" # video title used in the filename, dont append .mp4
|
|
handle_irregular_segments(media_info,video_title,output_path)
|
|
cleanup(working_dir)
|