udemy-userAPI 0.3.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- animation_consoles/__init__.py +1 -0
- animation_consoles/animation.py +64 -0
- ffmpeg_for_python/__config__.py +118 -0
- ffmpeg_for_python/__init__.py +8 -0
- ffmpeg_for_python/__utils.py +78 -0
- ffmpeg_for_python/__version__.py +6 -0
- ffmpeg_for_python/exeptions.py +91 -0
- ffmpeg_for_python/ffmpeg.py +203 -0
- m3u8_analyzer/M3u8Analyzer.py +807 -0
- m3u8_analyzer/__init__.py +7 -0
- m3u8_analyzer/__version__.py +1 -0
- m3u8_analyzer/exeptions.py +82 -0
- udemy_userAPI/.cache/.udemy_userAPI +0 -0
- udemy_userAPI/__init__.py +7 -0
- udemy_userAPI/__version__.py +6 -0
- udemy_userAPI/api.py +691 -0
- udemy_userAPI/authenticate.py +311 -0
- udemy_userAPI/bultins.py +495 -0
- udemy_userAPI/exeptions.py +22 -0
- udemy_userAPI/mpd_analyzer/__init__.py +3 -0
- udemy_userAPI/mpd_analyzer/bin.wvd +0 -0
- udemy_userAPI/mpd_analyzer/mpd_parser.py +224 -0
- udemy_userAPI/sections.py +117 -0
- udemy_userAPI/udemy.py +93 -0
- udemy_userAPI-0.3.2.dist-info/LICENSE +21 -0
- udemy_userAPI-0.3.2.dist-info/METADATA +34 -0
- udemy_userAPI-0.3.2.dist-info/RECORD +29 -0
- udemy_userAPI-0.3.2.dist-info/WHEEL +5 -0
- udemy_userAPI-0.3.2.dist-info/top_level.txt +1 -0
udemy_userAPI/bultins.py
ADDED
@@ -0,0 +1,495 @@
|
|
1
|
+
import json
|
2
|
+
from typing import Any
|
3
|
+
import requests
|
4
|
+
from .api import get_links, remove_tag, parser_chapers, extract_files, HEADERS_USER, assets_infor, get_add_files, \
|
5
|
+
get_files_aule, get_external_liks, extract, get_pssh, organize_streams, get_mpd_file, get_highest_resolution
|
6
|
+
from .sections import get_course_infor
|
7
|
+
from .mpd_analyzer import MPDParser
|
8
|
+
from .exeptions import LoginException
|
9
|
+
|
10
|
+
|
11
|
+
class DRM:
|
12
|
+
def __init__(self, license_token: str, get_media_sources: list):
|
13
|
+
"""
|
14
|
+
Inicializa o objeto DRM.
|
15
|
+
|
16
|
+
Args:
|
17
|
+
license_token (str): O token de licença.
|
18
|
+
get_media_sources (list): A lista de fontes de mídia.
|
19
|
+
"""
|
20
|
+
self.__mpd_file_path = None
|
21
|
+
self.__token = license_token
|
22
|
+
self.__dash_url = organize_streams(streams=get_media_sources).get('dash', {})
|
23
|
+
if not license_token or get_media_sources:
|
24
|
+
return
|
25
|
+
|
26
|
+
def get_key_for_lesson(self):
|
27
|
+
"""
|
28
|
+
Obtém as chaves para a aula.
|
29
|
+
|
30
|
+
Returns:
|
31
|
+
As chaves da aula ou None se não for possível obtê-las.
|
32
|
+
"""
|
33
|
+
try:
|
34
|
+
if self.__dash_url:
|
35
|
+
self.__mpd_file_path = get_mpd_file(mpd_url=self.__dash_url[0].get('src'))
|
36
|
+
parser = MPDParser(mpd_content=self.__mpd_file_path)
|
37
|
+
resolutions = get_highest_resolution(parser.get_all_video_resolutions())
|
38
|
+
parser.set_selected_resolution(resolution=resolutions)
|
39
|
+
init_url = parser.get_selected_video_init_url()
|
40
|
+
if init_url:
|
41
|
+
pssh = get_pssh(init_url=init_url)
|
42
|
+
if pssh:
|
43
|
+
keys = extract(pssh=pssh, license_token=self.__token)
|
44
|
+
if keys:
|
45
|
+
return keys
|
46
|
+
else:
|
47
|
+
return None
|
48
|
+
else:
|
49
|
+
return None
|
50
|
+
else:
|
51
|
+
return None
|
52
|
+
except Exception as e:
|
53
|
+
raise Exception(f"Não foi possível obter as chaves!\n{e}")
|
54
|
+
|
55
|
+
|
56
|
+
class Files:
|
57
|
+
def __init__(self, files: list[dict], id_course):
|
58
|
+
"""
|
59
|
+
Inicializa o objeto Files.
|
60
|
+
|
61
|
+
Args:
|
62
|
+
files (list[dict]): Lista de dicionários contendo os dados dos arquivos.
|
63
|
+
id_course: ID do curso.
|
64
|
+
"""
|
65
|
+
self.__data = files
|
66
|
+
self.__id_course = id_course
|
67
|
+
|
68
|
+
@property
|
69
|
+
def get_download_url(self) -> dict[str, Any | None] | list[dict[str, Any | None]]:
|
70
|
+
"""
|
71
|
+
Obtém a URL de download de um arquivo quando disponível.
|
72
|
+
|
73
|
+
Returns:
|
74
|
+
dict[str, Any | None] | list[dict[str, Any | None]]: URL de download do arquivo.
|
75
|
+
"""
|
76
|
+
from .authenticate import UdemyAuth
|
77
|
+
auth = UdemyAuth()
|
78
|
+
if not auth.verif_login():
|
79
|
+
raise LoginException("Sessão expirada!")
|
80
|
+
download_urls = []
|
81
|
+
for files in self.__data:
|
82
|
+
lecture_id = files.get('lecture_id', None)
|
83
|
+
asset_id = files.get('asset_id', None)
|
84
|
+
title = files.get("title", None)
|
85
|
+
lecture_title = files.get('lecture_title', None)
|
86
|
+
external_link = files.get('ExternalLink', None)
|
87
|
+
if external_link:
|
88
|
+
lnk = get_external_liks(course_id=self.__id_course, id_lecture=lecture_id, asset_id=asset_id)
|
89
|
+
dt_file = {'title-file': title,
|
90
|
+
'lecture_title': lecture_title,
|
91
|
+
'lecture_id': lecture_id,
|
92
|
+
'external_link': external_link,
|
93
|
+
'data-file': lnk.get('external_url', None)}
|
94
|
+
return dt_file
|
95
|
+
if asset_id and title and lecture_id and not external_link:
|
96
|
+
resp = requests.get(
|
97
|
+
f"https://www.udemy.com/api-2.0/users/me/subscribed-courses/{self.__id_course}/lectures/"
|
98
|
+
f"{lecture_id}/supplementary-assets/{asset_id}/?fields[asset]=download_urls",
|
99
|
+
headers=HEADERS_USER)
|
100
|
+
if resp.status_code == 200:
|
101
|
+
da = json.loads(resp.text)
|
102
|
+
# Para cada dict de um arquivo colocar seu título:
|
103
|
+
dt_file = {'title-file': title,
|
104
|
+
'lecture_title': lecture_title,
|
105
|
+
'lecture_id': lecture_id,
|
106
|
+
'external_link': external_link,
|
107
|
+
'data-file': da['download_urls']}
|
108
|
+
download_urls.append(dt_file)
|
109
|
+
return download_urls
|
110
|
+
|
111
|
+
|
112
|
+
class Lecture:
|
113
|
+
"""Cria objetos aula (lecture) do curso e extrai os dados."""
|
114
|
+
|
115
|
+
def __init__(self, data: dict, course_id: int, additional_files):
|
116
|
+
"""
|
117
|
+
Inicializa o objeto Lecture.
|
118
|
+
|
119
|
+
Args:
|
120
|
+
data (dict): Um dicionário contendo os dados da aula.
|
121
|
+
course_id (int): O ID do curso.
|
122
|
+
additional_files: Arquivos adicionais relacionados à aula.
|
123
|
+
"""
|
124
|
+
self.__course_id = course_id
|
125
|
+
self.__data = data
|
126
|
+
self.__additional_files = additional_files
|
127
|
+
self.__asset = self.__data.get("asset", {})
|
128
|
+
|
129
|
+
@property
|
130
|
+
def get_lecture_id(self) -> int:
|
131
|
+
"""
|
132
|
+
Obtém o ID da aula.
|
133
|
+
|
134
|
+
Returns:
|
135
|
+
int: O ID da aula.
|
136
|
+
"""
|
137
|
+
return self.__data.get('id', 0)
|
138
|
+
|
139
|
+
@property
|
140
|
+
def get_description(self) -> str:
|
141
|
+
"""
|
142
|
+
Obtém a descrição da aula.
|
143
|
+
|
144
|
+
Returns:
|
145
|
+
str: A descrição da aula.
|
146
|
+
"""
|
147
|
+
return remove_tag(str(self.__data.get('description')))
|
148
|
+
|
149
|
+
@property
|
150
|
+
def is_free(self) -> bool:
|
151
|
+
"""
|
152
|
+
Verifica se a aula é gratuita (aulas gratuitas estão disponíveis na apresentação do curso).
|
153
|
+
|
154
|
+
Returns:
|
155
|
+
bool: True se a aula for gratuita, False caso contrário.
|
156
|
+
"""
|
157
|
+
return self.__data.get('is_free', False)
|
158
|
+
|
159
|
+
@property
|
160
|
+
def get_thumbnail(self) -> dict:
|
161
|
+
"""
|
162
|
+
Obtém informações da miniatura (thumbnail) do vídeo.
|
163
|
+
|
164
|
+
Returns:
|
165
|
+
dict: Um dicionário contendo as URLs das miniaturas.
|
166
|
+
"""
|
167
|
+
thumbnail_sprite = self.__asset.get('thumbnail_sprite', {})
|
168
|
+
return {
|
169
|
+
'thumbnail_vtt_url': thumbnail_sprite.get('vtt_url'),
|
170
|
+
'thumbnail_img_url': thumbnail_sprite.get('img_url')
|
171
|
+
}
|
172
|
+
|
173
|
+
@property
|
174
|
+
def get_asset_type(self) -> str:
|
175
|
+
"""
|
176
|
+
Obtém o tipo de asset (Video, Article, etc.).
|
177
|
+
|
178
|
+
Returns:
|
179
|
+
str: O tipo de asset.
|
180
|
+
"""
|
181
|
+
return self.__asset.get('asset_type', 'Undefined')
|
182
|
+
|
183
|
+
@property
|
184
|
+
def get_media_sources(self) -> list:
|
185
|
+
"""
|
186
|
+
Obtém dados de streaming.
|
187
|
+
|
188
|
+
Returns:
|
189
|
+
list: Uma lista contendo as fontes de mídia.
|
190
|
+
"""
|
191
|
+
return self.__asset.get('media_sources')
|
192
|
+
|
193
|
+
@property
|
194
|
+
def get_captions(self) -> list:
|
195
|
+
"""
|
196
|
+
Obtém as legendas.
|
197
|
+
|
198
|
+
Returns:
|
199
|
+
list: Uma lista contendo as legendas.
|
200
|
+
"""
|
201
|
+
return self.__asset.get('captions')
|
202
|
+
|
203
|
+
@property
|
204
|
+
def get_external_url(self) -> list:
|
205
|
+
"""
|
206
|
+
Obtém links externos se houver.
|
207
|
+
|
208
|
+
Returns:
|
209
|
+
list: Uma lista contendo os links externos.
|
210
|
+
"""
|
211
|
+
return self.__asset.get('external_url')
|
212
|
+
|
213
|
+
@property
|
214
|
+
def get_media_license_token(self) -> str:
|
215
|
+
"""
|
216
|
+
Obtém o token de acesso à aula se houver.
|
217
|
+
|
218
|
+
Returns:
|
219
|
+
str: O token de acesso à aula.
|
220
|
+
"""
|
221
|
+
return self.__asset.get('media_license_token')
|
222
|
+
|
223
|
+
def course_is_drmed(self) -> DRM:
|
224
|
+
"""
|
225
|
+
Verifica se a aula possui DRM. Se sim, retorna as keys da aula.
|
226
|
+
|
227
|
+
Returns:
|
228
|
+
DRM: O objeto DRM contendo as keys da aula ou None.
|
229
|
+
"""
|
230
|
+
try:
|
231
|
+
d = DRM(license_token=self.get_media_license_token,
|
232
|
+
get_media_sources=self.get_media_sources)
|
233
|
+
return d
|
234
|
+
except Exception as e:
|
235
|
+
DeprecationWarning(e)
|
236
|
+
|
237
|
+
@property
|
238
|
+
def get_download_urls(self) -> list:
|
239
|
+
"""
|
240
|
+
Obtém URLs de download se houver.
|
241
|
+
|
242
|
+
Returns:
|
243
|
+
list: Uma lista contendo as URLs de download.
|
244
|
+
"""
|
245
|
+
return self.__asset.get('download_urls')
|
246
|
+
|
247
|
+
@property
|
248
|
+
def get_slide_urls(self) -> list:
|
249
|
+
"""
|
250
|
+
Obtém URLs de slides se houver.
|
251
|
+
|
252
|
+
Returns:
|
253
|
+
list: Uma lista contendo as URLs de slides.
|
254
|
+
"""
|
255
|
+
return self.__asset.get('slide_urls')
|
256
|
+
|
257
|
+
@property
|
258
|
+
def get_slides(self) -> list:
|
259
|
+
"""
|
260
|
+
Obtém slides se houver.
|
261
|
+
|
262
|
+
Returns:
|
263
|
+
list: Uma lista contendo os slides.
|
264
|
+
"""
|
265
|
+
return self.__asset.get('slides')
|
266
|
+
|
267
|
+
@property
|
268
|
+
def get_articles(self):
|
269
|
+
"""
|
270
|
+
Obtém os artigos relacionados à aula.
|
271
|
+
|
272
|
+
Returns:
|
273
|
+
Os artigos relacionados à aula.
|
274
|
+
"""
|
275
|
+
d = assets_infor(course_id=self.__course_id, id_lecture=self.get_lecture_id, assets_id=self.__asset.get("id"))
|
276
|
+
return d
|
277
|
+
|
278
|
+
@property
|
279
|
+
def get_resources(self):
|
280
|
+
"""
|
281
|
+
Obtém os recursos adicionais relacionados à aula.
|
282
|
+
|
283
|
+
Returns:
|
284
|
+
Os recursos adicionais relacionados à aula.
|
285
|
+
"""
|
286
|
+
files_add = get_files_aule(lecture_id_filter=self.get_lecture_id, data=self.__additional_files)
|
287
|
+
f = Files(files=files_add, id_course=self.__course_id).get_download_url
|
288
|
+
return f
|
289
|
+
|
290
|
+
|
291
|
+
class Course:
|
292
|
+
"""Recebe um dicionário com os dados do curso."""
|
293
|
+
|
294
|
+
def __init__(self, results: dict, course_id: int):
|
295
|
+
"""
|
296
|
+
Inicializa o objeto Course.
|
297
|
+
|
298
|
+
Args:
|
299
|
+
results (dict): Um dicionário contendo os dados do curso.
|
300
|
+
course_id (int): O ID do curso.
|
301
|
+
"""
|
302
|
+
self.__parser_chapers = parser_chapers(results=results)
|
303
|
+
self.__data = self.__parser_chapers
|
304
|
+
self.__course_id = course_id
|
305
|
+
self.__results = results
|
306
|
+
self.__additional_files_data = get_add_files(course_id)
|
307
|
+
self.__information = self.__load_infor_course()
|
308
|
+
|
309
|
+
def __load_infor_course(self) -> dict:
|
310
|
+
"""
|
311
|
+
Obtém as informações do curso.
|
312
|
+
|
313
|
+
Returns:
|
314
|
+
dict: Um dicionário contendo as informações do curso.
|
315
|
+
"""
|
316
|
+
data = get_course_infor(self.__course_id)
|
317
|
+
return data
|
318
|
+
|
319
|
+
@property
|
320
|
+
def title_course(self) -> str:
|
321
|
+
"""
|
322
|
+
Obtém o título do curso.
|
323
|
+
|
324
|
+
Returns:
|
325
|
+
str: O título do curso.
|
326
|
+
"""
|
327
|
+
return self.__information.get('title')
|
328
|
+
|
329
|
+
@property
|
330
|
+
def instructors(self) -> dict:
|
331
|
+
"""
|
332
|
+
Obtém informações dos instrutores.
|
333
|
+
|
334
|
+
Returns:
|
335
|
+
dict: Um dicionário contendo as informações dos instrutores.
|
336
|
+
"""
|
337
|
+
return self.__information.get("visible_instructors")
|
338
|
+
|
339
|
+
@property
|
340
|
+
def locale(self):
|
341
|
+
"""
|
342
|
+
Obtém informações de localidade do curso.
|
343
|
+
|
344
|
+
Returns:
|
345
|
+
str: As informações de localidade do curso.
|
346
|
+
"""
|
347
|
+
return self.__information.get('locale')
|
348
|
+
|
349
|
+
@property
|
350
|
+
def primary_category(self):
|
351
|
+
"""
|
352
|
+
Obtém a categoria primária.
|
353
|
+
|
354
|
+
Returns:
|
355
|
+
str: A categoria primária.
|
356
|
+
"""
|
357
|
+
return self.__information.get('primary_category')
|
358
|
+
|
359
|
+
@property
|
360
|
+
def primary_subcategory(self):
|
361
|
+
"""
|
362
|
+
Obtém a subcategoria primária.
|
363
|
+
|
364
|
+
Returns:
|
365
|
+
str: A subcategoria primária.
|
366
|
+
"""
|
367
|
+
return self.__information.get('primary_subcategory')
|
368
|
+
|
369
|
+
@property
|
370
|
+
def count_lectures(self) -> int:
|
371
|
+
"""
|
372
|
+
Obtém o número total de lectures no curso.
|
373
|
+
|
374
|
+
Returns:
|
375
|
+
int: O número total de lectures no curso.
|
376
|
+
"""
|
377
|
+
total_lectures = 0
|
378
|
+
for chapter in self.__data.values():
|
379
|
+
total_lectures += len(chapter.get('videos_in_chapter', []))
|
380
|
+
return total_lectures
|
381
|
+
|
382
|
+
@property
|
383
|
+
def count_chapters(self) -> int:
|
384
|
+
"""
|
385
|
+
Obtém o número total de chapters (sections) no curso.
|
386
|
+
|
387
|
+
Returns:
|
388
|
+
int: O número total de chapters (sections) no curso.
|
389
|
+
"""
|
390
|
+
return len(self.__data)
|
391
|
+
|
392
|
+
@property
|
393
|
+
def title_videos(self) -> list:
|
394
|
+
"""
|
395
|
+
Obtém uma lista com todos os títulos de vídeos no curso.
|
396
|
+
|
397
|
+
Returns:
|
398
|
+
list: Uma lista contendo os títulos de vídeos no curso.
|
399
|
+
"""
|
400
|
+
videos = []
|
401
|
+
for chapter in self.__data.values():
|
402
|
+
for video in chapter.get('videos_in_chapter', []):
|
403
|
+
title = video['video_title']
|
404
|
+
if title != "Files":
|
405
|
+
videos.append(title)
|
406
|
+
return videos
|
407
|
+
|
408
|
+
@property
|
409
|
+
def get_lectures(self) -> list:
|
410
|
+
"""
|
411
|
+
Obtém uma lista com todas as aulas.
|
412
|
+
|
413
|
+
Returns:
|
414
|
+
list: Uma lista contendo todas as aulas.
|
415
|
+
"""
|
416
|
+
videos = []
|
417
|
+
section_order = 1 # Iniciar a numeração das seções (capítulos)
|
418
|
+
|
419
|
+
for chapter in self.__data.values():
|
420
|
+
for index, video in enumerate(chapter.get('videos_in_chapter', [])):
|
421
|
+
section = f"{chapter.get('title_chapter')}" # Adicionar numeração da seção
|
422
|
+
title = video.get('video_title')
|
423
|
+
id_lecture = video.get('lecture_id')
|
424
|
+
id_asset = video.get('asset_id')
|
425
|
+
dt = {
|
426
|
+
'section': section,
|
427
|
+
'title': title,
|
428
|
+
'lecture_id': id_lecture,
|
429
|
+
'asset_id': id_asset,
|
430
|
+
'section_order': section_order
|
431
|
+
}
|
432
|
+
videos.append(dt)
|
433
|
+
section_order += 1 # Incrementar o número da seção após processar os vídeos do capítulo
|
434
|
+
return videos
|
435
|
+
|
436
|
+
def get_details_lecture(self, lecture_id: int) -> Lecture:
|
437
|
+
"""
|
438
|
+
Obtém detalhes de uma aula específica.
|
439
|
+
|
440
|
+
Args:
|
441
|
+
lecture_id (int): O ID da aula.
|
442
|
+
|
443
|
+
Returns:
|
444
|
+
Lecture: Um objeto Lecture contendo os detalhes da aula.
|
445
|
+
"""
|
446
|
+
links = get_links(course_id=self.__course_id, id_lecture=lecture_id)
|
447
|
+
additional_files = self.__load_assets()
|
448
|
+
lecture = Lecture(data=links, course_id=self.__course_id, additional_files=additional_files)
|
449
|
+
return lecture
|
450
|
+
|
451
|
+
@property
|
452
|
+
def get_additional_files(self) -> list:
|
453
|
+
"""
|
454
|
+
Retorna a lista de arquivos adicionais de um curso.
|
455
|
+
|
456
|
+
Returns:
|
457
|
+
list: Uma lista contendo os arquivos adicionais de um curso.
|
458
|
+
"""
|
459
|
+
supplementary_assets = []
|
460
|
+
for item in self.__additional_files_data.get('results', []):
|
461
|
+
if item.get('_class') == 'lecture':
|
462
|
+
id_l = item.get('id', {})
|
463
|
+
title = item.get('title', {})
|
464
|
+
assets = item.get('supplementary_assets', [])
|
465
|
+
for asset in assets:
|
466
|
+
supplementary_assets.append({
|
467
|
+
'lecture_id': id_l,
|
468
|
+
'lecture_title': title,
|
469
|
+
'asset': asset
|
470
|
+
})
|
471
|
+
files = extract_files(supplementary_assets)
|
472
|
+
files_objt = Files(files=files, id_course=self.__course_id).get_download_url
|
473
|
+
return files_objt
|
474
|
+
|
475
|
+
def __load_assets(self):
|
476
|
+
"""
|
477
|
+
Retorna a lista de arquivos adicionais de um curso.
|
478
|
+
|
479
|
+
Returns:
|
480
|
+
list: Uma lista contendo os arquivos adicionais de um curso.
|
481
|
+
"""
|
482
|
+
supplementary_assets = []
|
483
|
+
for item in self.__additional_files_data.get('results', []):
|
484
|
+
if item.get('_class') == 'lecture':
|
485
|
+
id_l = item.get('id')
|
486
|
+
title = item.get('title')
|
487
|
+
assets = item.get('supplementary_assets', [])
|
488
|
+
for asset in assets:
|
489
|
+
supplementary_assets.append({
|
490
|
+
'lecture_id': id_l,
|
491
|
+
'lecture_title': title,
|
492
|
+
'asset': asset
|
493
|
+
})
|
494
|
+
files = extract_files(supplementary_assets)
|
495
|
+
return files
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class UdemyUserApiExceptions(Exception):
|
2
|
+
def __init__(self, message="Udemy_UserApi Generic Error!"):
|
3
|
+
self.message = message
|
4
|
+
super().__init__(self.message)
|
5
|
+
|
6
|
+
|
7
|
+
class Upstreamconnecterror(Exception):
|
8
|
+
def __init__(self, message="Falha na conexão com o servidor!"):
|
9
|
+
self.message = message
|
10
|
+
super().__init__(self.message)
|
11
|
+
|
12
|
+
|
13
|
+
class UnhandledExceptions(Exception):
|
14
|
+
def __init__(self, message="Error Unhandled!"):
|
15
|
+
self.message = message
|
16
|
+
super().__init__(self.message)
|
17
|
+
|
18
|
+
|
19
|
+
class LoginException(Exception):
|
20
|
+
def __init__(self, message="Error Login!"):
|
21
|
+
self.message = message
|
22
|
+
super().__init__(self.message)
|
Binary file
|