udemy-userAPI 0.2.5__tar.gz → 0.2.7__tar.gz
Sign up to get free protection for your applications and to get access to all the features.
- {udemy_userapi-0.2.5/udemy_userAPI.egg-info → udemy_userapi-0.2.7}/PKG-INFO +2 -2
- {udemy_userapi-0.2.5 → udemy_userapi-0.2.7}/README.md +1 -1
- {udemy_userapi-0.2.5 → udemy_userapi-0.2.7}/README_PYPI.md +1 -1
- {udemy_userapi-0.2.5 → udemy_userapi-0.2.7}/udemy_userAPI/__version__.py +1 -1
- {udemy_userapi-0.2.5 → udemy_userapi-0.2.7}/udemy_userAPI/authenticate.py +134 -6
- {udemy_userapi-0.2.5 → udemy_userapi-0.2.7}/udemy_userAPI/bultins.py +20 -12
- {udemy_userapi-0.2.5 → udemy_userapi-0.2.7}/udemy_userAPI/udemy.py +7 -6
- {udemy_userapi-0.2.5 → udemy_userapi-0.2.7/udemy_userAPI.egg-info}/PKG-INFO +2 -2
- {udemy_userapi-0.2.5 → udemy_userapi-0.2.7}/udemy_userAPI.egg-info/SOURCES.txt +0 -12
- udemy_userapi-0.2.7/udemy_userAPI.egg-info/top_level.txt +1 -0
- udemy_userapi-0.2.5/animation_consoles/__init__.py +0 -1
- udemy_userapi-0.2.5/animation_consoles/animation.py +0 -64
- udemy_userapi-0.2.5/ffmpeg_for_python/__config__.py +0 -118
- udemy_userapi-0.2.5/ffmpeg_for_python/__init__.py +0 -8
- udemy_userapi-0.2.5/ffmpeg_for_python/__utils.py +0 -78
- udemy_userapi-0.2.5/ffmpeg_for_python/__version__.py +0 -6
- udemy_userapi-0.2.5/ffmpeg_for_python/exeptions.py +0 -91
- udemy_userapi-0.2.5/ffmpeg_for_python/ffmpeg.py +0 -203
- udemy_userapi-0.2.5/m3u8_analyzer/M3u8Analyzer.py +0 -807
- udemy_userapi-0.2.5/m3u8_analyzer/__init__.py +0 -7
- udemy_userapi-0.2.5/m3u8_analyzer/__version__.py +0 -1
- udemy_userapi-0.2.5/m3u8_analyzer/exeptions.py +0 -82
- udemy_userapi-0.2.5/udemy_userAPI.egg-info/top_level.txt +0 -4
- {udemy_userapi-0.2.5 → udemy_userapi-0.2.7}/LICENSE +0 -0
- {udemy_userapi-0.2.5 → udemy_userapi-0.2.7}/MANIFEST.in +0 -0
- {udemy_userapi-0.2.5 → udemy_userapi-0.2.7}/setup.cfg +0 -0
- {udemy_userapi-0.2.5 → udemy_userapi-0.2.7}/setup.py +0 -0
- {udemy_userapi-0.2.5 → udemy_userapi-0.2.7}/udemy_userAPI/.cache/.udemy_userAPI +0 -0
- {udemy_userapi-0.2.5 → udemy_userapi-0.2.7}/udemy_userAPI/__init__.py +0 -0
- {udemy_userapi-0.2.5 → udemy_userapi-0.2.7}/udemy_userAPI/api.py +0 -0
- {udemy_userapi-0.2.5 → udemy_userapi-0.2.7}/udemy_userAPI/exeptions.py +0 -0
- {udemy_userapi-0.2.5 → udemy_userapi-0.2.7}/udemy_userAPI/mpd_analyzer/__init__.py +0 -0
- {udemy_userapi-0.2.5 → udemy_userapi-0.2.7}/udemy_userAPI/mpd_analyzer/bin.wvd +0 -0
- {udemy_userapi-0.2.5 → udemy_userapi-0.2.7}/udemy_userAPI/mpd_analyzer/mpd_parser.py +0 -0
- {udemy_userapi-0.2.5 → udemy_userapi-0.2.7}/udemy_userAPI/sections.py +0 -0
- {udemy_userapi-0.2.5 → udemy_userapi-0.2.7}/udemy_userAPI.egg-info/dependency_links.txt +0 -0
- {udemy_userapi-0.2.5 → udemy_userapi-0.2.7}/udemy_userAPI.egg-info/not-zip-safe +0 -0
- {udemy_userapi-0.2.5 → udemy_userapi-0.2.7}/udemy_userAPI.egg-info/requires.txt +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: udemy_userAPI
|
3
|
-
Version: 0.2.
|
3
|
+
Version: 0.2.7
|
4
4
|
Summary: Obtenha detalhes de cursos que o usuário esteja inscrito da plataforma Udemy,usando o EndPoint de usuário o mesmo que o navegador utiliza para acessar e redenrizar os cursos.
|
5
5
|
Author: PauloCesar-dev404
|
6
6
|
Author-email: paulocesar0073dev404@gmail.com
|
@@ -19,7 +19,7 @@ Requires-Dist: pywidevine
|
|
19
19
|
# udemy-userAPI
|
20
20
|
|
21
21
|
|
22
|
-

|
23
23
|

|
24
24
|
[](https://apoia.se/paulocesar-dev404)
|
25
25
|
[](https://github.com/PauloCesar-dev404/udemy-userAPI/wiki)
|
@@ -2,7 +2,7 @@
|
|
2
2
|
<img src="assets/udemy_userAPI-logo.png" alt="udemy_userAPI-logo" width="200"/>
|
3
3
|
|
4
4
|
|
5
|
-

|
6
6
|

|
7
7
|
[](https://apoia.se/paulocesar-dev404)
|
8
8
|
[](https://github.com/PauloCesar-dev404/udemy-userAPI/wiki)
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# udemy-userAPI
|
2
2
|
|
3
3
|
|
4
|
-

|
5
5
|

|
6
6
|
[](https://apoia.se/paulocesar-dev404)
|
7
7
|
[](https://github.com/PauloCesar-dev404/udemy-userAPI/wiki)
|
@@ -2,7 +2,10 @@ import json
|
|
2
2
|
import os
|
3
3
|
import pickle
|
4
4
|
import traceback
|
5
|
-
|
5
|
+
import hashlib
|
6
|
+
import hmac
|
7
|
+
import math
|
8
|
+
from datetime import datetime
|
6
9
|
import requests
|
7
10
|
from .exeptions import UnhandledExceptions, UdemyUserApiExceptions, LoginException
|
8
11
|
import cloudscraper
|
@@ -142,7 +145,7 @@ class UdemyAuth:
|
|
142
145
|
|
143
146
|
# Verifica a resposta para determinar se o login foi bem-sucedido
|
144
147
|
if "returnUrl" in r.text:
|
145
|
-
self.
|
148
|
+
self._save_cookies(s.cookies)
|
146
149
|
else:
|
147
150
|
login_error = r.json().get("error", {}).get("data", {}).get("formErrors", [])[0]
|
148
151
|
if login_error[0] == "Y":
|
@@ -151,14 +154,12 @@ class UdemyAuth:
|
|
151
154
|
raise LoginException("Email ou senha incorretos")
|
152
155
|
else:
|
153
156
|
raise UnhandledExceptions(login_error)
|
154
|
-
|
155
|
-
return s
|
156
157
|
except Exception as e:
|
157
158
|
if DEBUG:
|
158
159
|
e = traceback.format_exc()
|
159
160
|
raise LoginException(e)
|
160
161
|
|
161
|
-
def
|
162
|
+
def _save_cookies(self, cookies):
|
162
163
|
try:
|
163
164
|
with open(fr'{self.__file_path}', 'wb') as f:
|
164
165
|
pickle.dump(cookies, f)
|
@@ -188,4 +189,131 @@ class UdemyAuth:
|
|
188
189
|
|
189
190
|
def remove_cookies(self):
|
190
191
|
if os.path.exists(self.__file_path):
|
191
|
-
|
192
|
+
with open(self.__file_path, 'wb') as f:
|
193
|
+
f.write(b'')
|
194
|
+
|
195
|
+
def login_passwordless(self, email: str, locale: str = 'pt-BR'):
|
196
|
+
"""
|
197
|
+
Realiza login na Udemy usando autenticação de dois fatores (2FA).
|
198
|
+
|
199
|
+
Este método utiliza o fluxo de autenticação OAuth da Udemy para enviar um
|
200
|
+
código de verificação por e-mail ao usuário. Após inserir o código recebido,
|
201
|
+
o login é concluído.
|
202
|
+
|
203
|
+
:param email: Email do usuário.
|
204
|
+
:param locale: Localização do usuário (recomendado para receber mensagens no idioma local).
|
205
|
+
:raises LoginException: Em caso de falha no processo de login.
|
206
|
+
"""
|
207
|
+
try:
|
208
|
+
# Inicializa uma sessão com proteção contra Cloudflare
|
209
|
+
session = cloudscraper.create_scraper()
|
210
|
+
|
211
|
+
# Requisita a página de inscrição para obter o token CSRF
|
212
|
+
signup_url = "https://www.udemy.com/join/signup-popup/"
|
213
|
+
headers = {"User-Agent": "okhttp/4.9.2 UdemyAndroid 8.9.2(499) (phone)"}
|
214
|
+
response = session.get(signup_url, headers=headers)
|
215
|
+
|
216
|
+
# Obtém o token CSRF dos cookies retornados
|
217
|
+
csrf_token = response.cookies.get("csrftoken")
|
218
|
+
if not csrf_token:
|
219
|
+
raise LoginException("Não foi possível obter o token CSRF.")
|
220
|
+
|
221
|
+
# Prepara os dados do login
|
222
|
+
data = {"email": email, "fullname": ""}
|
223
|
+
|
224
|
+
# Atualiza os cookies e cabeçalhos da sessão
|
225
|
+
session.cookies.update(response.cookies)
|
226
|
+
session.headers.update({
|
227
|
+
"User-Agent": "okhttp/4.9.2 UdemyAndroid 8.9.2(499) (phone)",
|
228
|
+
"Accept": "application/json, text/plain, */*",
|
229
|
+
"Accept-Language": locale,
|
230
|
+
"Referer": f"https://www.udemy.com/join/passwordless-auth/?locale={locale.replace('-', '_')}&next="
|
231
|
+
f"https%3A%2F%2Fwww.udemy.com%2Fmobile%2Fipad%2F&response_type=html",
|
232
|
+
"Origin": "https://www.udemy.com",
|
233
|
+
"DNT": "1",
|
234
|
+
"Connection": "keep-alive",
|
235
|
+
"Sec-Fetch-Dest": "empty",
|
236
|
+
"Sec-Fetch-Mode": "cors",
|
237
|
+
"Sec-Fetch-Site": "same-origin",
|
238
|
+
"Pragma": "no-cache",
|
239
|
+
"Cache-Control": "no-cache",
|
240
|
+
})
|
241
|
+
|
242
|
+
# Faz a requisição para iniciar o login
|
243
|
+
login_url = "https://www.udemy.com/api-2.0/auth/code-generation/login/4.0/"
|
244
|
+
response = session.post(login_url, data=data, allow_redirects=False)
|
245
|
+
if 'error_message' in response.text:
|
246
|
+
erro_data: dict = response.json()
|
247
|
+
error_message = erro_data.get('error_message', {})
|
248
|
+
raise LoginException(error_message)
|
249
|
+
for attempt in range(3):
|
250
|
+
# Solicita o código OTP ao usuário
|
251
|
+
otp = input("Digite o código de 6 dígitos enviado ao seu e-mail: ")
|
252
|
+
# Realiza o login com o código OTP
|
253
|
+
otp_login_url = "https://www.udemy.com/api-2.0/auth/udemy-passwordless/login/4.0/"
|
254
|
+
otp_data = {
|
255
|
+
"email": email,
|
256
|
+
"fullname": "",
|
257
|
+
"otp": otp,
|
258
|
+
"subscribeToEmails": "false",
|
259
|
+
"upow": J(email, 'login')
|
260
|
+
}
|
261
|
+
session.headers.update({
|
262
|
+
"Referer": f"https://www.udemy.com/join/passwordless-auth/?locale={locale}&next="
|
263
|
+
f"https%3A%2F%2Fwww.udemy.com%2Fmobile%2Fipad%2F&response_type=html"
|
264
|
+
})
|
265
|
+
response = session.post(otp_login_url, otp_data, allow_redirects=False)
|
266
|
+
# Verifica se o login foi bem-sucedido
|
267
|
+
if response.status_code == 200:
|
268
|
+
self._save_cookies(session.cookies)
|
269
|
+
else:
|
270
|
+
if 'error_message' in response.text:
|
271
|
+
erro_data: dict = response.json()
|
272
|
+
error_message = erro_data.get('error_message', {})
|
273
|
+
error_code = erro_data.get('error_code', {})
|
274
|
+
if error_code == '1538':
|
275
|
+
raise LoginException(error_message)
|
276
|
+
elif error_code == '2550':
|
277
|
+
print(error_message)
|
278
|
+
continue
|
279
|
+
elif error_code == '1330':
|
280
|
+
raise LoginException(error_message)
|
281
|
+
elif error_code == '1149':
|
282
|
+
LoginException(f"Erro interno ao enviar os dados veja os detalhes: '{error_message}'")
|
283
|
+
raise LoginException(response.text)
|
284
|
+
break
|
285
|
+
except Exception as e:
|
286
|
+
if DEBUG:
|
287
|
+
error_details = traceback.format_exc()
|
288
|
+
else:
|
289
|
+
error_details = str(e)
|
290
|
+
raise LoginException(error_details)
|
291
|
+
|
292
|
+
|
293
|
+
def J(e, t):
|
294
|
+
r = datetime.now()
|
295
|
+
s = r.isoformat()[:10]
|
296
|
+
return s + X(e, s, t)
|
297
|
+
|
298
|
+
|
299
|
+
def X(e, t, r):
|
300
|
+
s = 0
|
301
|
+
while True:
|
302
|
+
o = ee(s)
|
303
|
+
a = hmac.new(r.encode(), (e + t + o).encode(), hashlib.sha256).digest()
|
304
|
+
if te(16, a):
|
305
|
+
return o
|
306
|
+
s += 1
|
307
|
+
|
308
|
+
|
309
|
+
def ee(e):
|
310
|
+
if e < 0:
|
311
|
+
return ""
|
312
|
+
return ee(e // 26 - 1) + chr(65 + e % 26)
|
313
|
+
|
314
|
+
|
315
|
+
def te(e, t):
|
316
|
+
r = math.ceil(e / 8)
|
317
|
+
s = t[:r]
|
318
|
+
o = ''.join(format(byte, '08b') for byte in s)
|
319
|
+
return o.startswith('0' * e)
|
@@ -10,9 +10,10 @@ from .mpd_analyzer import MPDParser
|
|
10
10
|
class DRM:
|
11
11
|
def __init__(self, license_token: str, get_media_sources: list):
|
12
12
|
self.__mpd_content = None
|
13
|
-
self.__dash_url = organize_streams(streams=get_media_sources).get('dash')
|
14
13
|
self.__token = license_token
|
15
|
-
self.
|
14
|
+
self.__dash_url = organize_streams(streams=get_media_sources).get('dash', {})
|
15
|
+
if not license_token or not get_media_sources or not self.__dash_url:
|
16
|
+
return
|
16
17
|
|
17
18
|
def get_key_for_lesson(self):
|
18
19
|
"""get keys for lesson"""
|
@@ -28,6 +29,12 @@ class DRM:
|
|
28
29
|
keys = extract(pssh=pssh, license_token=self.__token)
|
29
30
|
if keys:
|
30
31
|
return keys
|
32
|
+
else:
|
33
|
+
return None
|
34
|
+
else:
|
35
|
+
return None
|
36
|
+
else:
|
37
|
+
return None
|
31
38
|
|
32
39
|
|
33
40
|
class Files:
|
@@ -44,11 +51,13 @@ class Files:
|
|
44
51
|
lecture_id = files.get('lecture_id', None)
|
45
52
|
asset_id = files.get('asset_id', None)
|
46
53
|
title = files.get("title", None)
|
47
|
-
lecture_title = files.get('lecture_title')
|
48
|
-
external_link = files.get('ExternalLink')
|
54
|
+
lecture_title = files.get('lecture_title', None)
|
55
|
+
external_link = files.get('ExternalLink', None)
|
49
56
|
if external_link:
|
50
57
|
lnk = get_external_liks(course_id=self.__id_course, id_lecture=lecture_id, asset_id=asset_id)
|
51
|
-
dt_file = {'title-file': title,
|
58
|
+
dt_file = {'title-file': title,
|
59
|
+
'lecture_title': lecture_title,
|
60
|
+
'lecture_id': lecture_id,
|
52
61
|
'external_link': external_link,
|
53
62
|
'data-file': lnk.get('external_url', None)}
|
54
63
|
return dt_file
|
@@ -59,8 +68,9 @@ class Files:
|
|
59
68
|
headers=HEADERS_USER)
|
60
69
|
if resp.status_code == 200:
|
61
70
|
da = json.loads(resp.text)
|
62
|
-
|
63
|
-
|
71
|
+
dt_file = {'title-file': title,
|
72
|
+
'lecture_title': lecture_title,
|
73
|
+
'lecture_id': lecture_id,
|
64
74
|
'external_link': external_link,
|
65
75
|
'data-file': da['download_urls']}
|
66
76
|
download_urls.append(dt_file)
|
@@ -128,12 +138,10 @@ class Lecture:
|
|
128
138
|
def course_is_drmed(self) -> DRM:
|
129
139
|
"""verifica se a aula possui DRM se sim retorna as keys da aula...
|
130
140
|
retorna 'kid:key' or None"""
|
131
|
-
if self.__asset.get('course_is_drmed'):
|
141
|
+
if self.__asset.get('course_is_drmed', {}):
|
132
142
|
d = DRM(license_token=self.get_media_license_token,
|
133
143
|
get_media_sources=self.get_media_sources)
|
134
144
|
return d
|
135
|
-
else:
|
136
|
-
return self.__asset.get('course_is_drmed')
|
137
145
|
|
138
146
|
@property
|
139
147
|
def get_download_urls(self) -> list:
|
@@ -267,8 +275,8 @@ class Course:
|
|
267
275
|
for item in self.__additional_files_data.get('results', []):
|
268
276
|
# Check if the item is a lecture with supplementary assets
|
269
277
|
if item.get('_class') == 'lecture':
|
270
|
-
id = item.get('id')
|
271
|
-
title = item.get('title')
|
278
|
+
id = item.get('id', {})
|
279
|
+
title = item.get('title', {})
|
272
280
|
assets = item.get('supplementary_assets', [])
|
273
281
|
for asset in assets:
|
274
282
|
supplementary_assets.append({
|
@@ -12,22 +12,23 @@ class Udemy:
|
|
12
12
|
"""wrapper para api de usuario da plataforma udemy"""
|
13
13
|
|
14
14
|
def __init__(self):
|
15
|
-
"""
|
16
|
-
cookies de seção.
|
17
|
-
"""
|
18
15
|
self.__headers = HEADERS_USER
|
19
16
|
if verif_login is None:
|
20
17
|
raise LoginException("User Not Logged!")
|
21
18
|
|
22
|
-
|
23
|
-
|
19
|
+
@staticmethod
|
20
|
+
def my_subscribed_courses_by_plan() -> list[dict]:
|
21
|
+
"""obtém os cursos que o usuário esatá inscrito, obtidos atraves de planos(assinatura)
|
22
|
+
:return:
|
23
|
+
"""
|
24
24
|
try:
|
25
25
|
courses = get_courses_plan(tipe='plan')
|
26
26
|
return courses
|
27
27
|
except UdemyUserApiExceptions as e:
|
28
28
|
UnhandledExceptions(e)
|
29
29
|
|
30
|
-
|
30
|
+
@staticmethod
|
31
|
+
def my_subscribed_courses() -> list[dict]:
|
31
32
|
"""Obtém os cursos que o usuário está inscrito, excluindo listas vazias ou nulas"""
|
32
33
|
try:
|
33
34
|
# Obtém os cursos
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: udemy_userAPI
|
3
|
-
Version: 0.2.
|
3
|
+
Version: 0.2.7
|
4
4
|
Summary: Obtenha detalhes de cursos que o usuário esteja inscrito da plataforma Udemy,usando o EndPoint de usuário o mesmo que o navegador utiliza para acessar e redenrizar os cursos.
|
5
5
|
Author: PauloCesar-dev404
|
6
6
|
Author-email: paulocesar0073dev404@gmail.com
|
@@ -19,7 +19,7 @@ Requires-Dist: pywidevine
|
|
19
19
|
# udemy-userAPI
|
20
20
|
|
21
21
|
|
22
|
-

|
23
23
|

|
24
24
|
[](https://apoia.se/paulocesar-dev404)
|
25
25
|
[](https://github.com/PauloCesar-dev404/udemy-userAPI/wiki)
|
@@ -3,18 +3,6 @@ MANIFEST.in
|
|
3
3
|
README.md
|
4
4
|
README_PYPI.md
|
5
5
|
setup.py
|
6
|
-
animation_consoles/__init__.py
|
7
|
-
animation_consoles/animation.py
|
8
|
-
ffmpeg_for_python/__config__.py
|
9
|
-
ffmpeg_for_python/__init__.py
|
10
|
-
ffmpeg_for_python/__utils.py
|
11
|
-
ffmpeg_for_python/__version__.py
|
12
|
-
ffmpeg_for_python/exeptions.py
|
13
|
-
ffmpeg_for_python/ffmpeg.py
|
14
|
-
m3u8_analyzer/M3u8Analyzer.py
|
15
|
-
m3u8_analyzer/__init__.py
|
16
|
-
m3u8_analyzer/__version__.py
|
17
|
-
m3u8_analyzer/exeptions.py
|
18
6
|
udemy_userAPI/__init__.py
|
19
7
|
udemy_userAPI/__version__.py
|
20
8
|
udemy_userAPI/api.py
|
@@ -0,0 +1 @@
|
|
1
|
+
udemy_userAPI
|
@@ -1 +0,0 @@
|
|
1
|
-
from .animation import AnimationConsole
|
@@ -1,64 +0,0 @@
|
|
1
|
-
import sys
|
2
|
-
import time
|
3
|
-
import threading
|
4
|
-
from colorama import init, Fore, Style
|
5
|
-
|
6
|
-
# Inicializa o suporte a cores no Windows
|
7
|
-
init(autoreset=True)
|
8
|
-
|
9
|
-
|
10
|
-
class AnimationConsole:
|
11
|
-
def __init__(self, text="Loading", color=Fore.GREEN, color_frame=Fore.BLUE):
|
12
|
-
"""
|
13
|
-
Cria uma animação de loading com uma mensagem colorida no console.
|
14
|
-
:param text: Texto inicial da mensagem de loading.
|
15
|
-
:param color: Cor do texto, usando Fore do colorama.
|
16
|
-
"""
|
17
|
-
self._color_frame = color_frame
|
18
|
-
self._text = text
|
19
|
-
self._color = color
|
20
|
-
self._running = False
|
21
|
-
self._animation_thread = None
|
22
|
-
self._frames = ["-", "\\", "|", "/"]
|
23
|
-
self._index = 0
|
24
|
-
|
25
|
-
def start(self):
|
26
|
-
"""
|
27
|
-
Inicia a animação no console.
|
28
|
-
"""
|
29
|
-
if self._running:
|
30
|
-
return # Previne múltiplas execuções
|
31
|
-
self._running = True
|
32
|
-
self._animation_thread = threading.Thread(target=self._animate, daemon=True)
|
33
|
-
self._animation_thread.start()
|
34
|
-
|
35
|
-
def stop(self):
|
36
|
-
"""
|
37
|
-
Para a animação no console.
|
38
|
-
"""
|
39
|
-
self._running = False
|
40
|
-
if self._animation_thread:
|
41
|
-
self._animation_thread.join()
|
42
|
-
sys.stdout.write("\r" + " " * (len(self._text) + 20) + "\r") # Limpa a linha
|
43
|
-
|
44
|
-
def update_message(self, new_text, new_color=None):
|
45
|
-
"""
|
46
|
-
Atualiza a mensagem exibida junto à animação.
|
47
|
-
:param new_text: Novo texto a ser exibido.
|
48
|
-
:param new_color: Nova cor para o texto (opcional).
|
49
|
-
"""
|
50
|
-
self._text = new_text
|
51
|
-
if new_color:
|
52
|
-
self._color = new_color
|
53
|
-
|
54
|
-
def _animate(self):
|
55
|
-
"""
|
56
|
-
Animação interna do console.
|
57
|
-
"""
|
58
|
-
while self._running:
|
59
|
-
frame = self._frames[self._index]
|
60
|
-
self._index = (self._index + 1) % len(self._frames)
|
61
|
-
sys.stdout.write(
|
62
|
-
f"\r{self._color}{self._text}{Style.RESET_ALL} {self._color_frame}{frame}{Style.RESET_ALL}")
|
63
|
-
sys.stdout.flush()
|
64
|
-
time.sleep(0.1)
|
@@ -1,118 +0,0 @@
|
|
1
|
-
import sys
|
2
|
-
import os
|
3
|
-
import requests
|
4
|
-
import zipfile
|
5
|
-
import shutil
|
6
|
-
import stat
|
7
|
-
from .exeptions import *
|
8
|
-
from .__utils import URL_PLATAFOMR, system
|
9
|
-
|
10
|
-
lib_name = os.path.dirname(__file__)
|
11
|
-
URL_BASE_REPO = "https://raw.githubusercontent.com/PauloCesar-dev404/binarios/main/"
|
12
|
-
|
13
|
-
|
14
|
-
class Configurate:
|
15
|
-
"""Configura variáveis de ambiente no ambiente virtual ou globalmente."""
|
16
|
-
|
17
|
-
def __init__(self):
|
18
|
-
self.VERSION = self.__read_version
|
19
|
-
self.FFMPEG_URL = os.getenv('FFMPEG_URL')
|
20
|
-
self.FFMPEG_BINARY = os.getenv('FFMPEG_BINARY')
|
21
|
-
PATH = os.path.join(lib_name, 'ffmpeg-bin')
|
22
|
-
os.makedirs(PATH, exist_ok=True)
|
23
|
-
dirpath = PATH
|
24
|
-
self.INSTALL_DIR = os.getenv('INSTALL_DIR', dirpath)
|
25
|
-
self.configure()
|
26
|
-
|
27
|
-
def configure(self):
|
28
|
-
"""Configura as variáveis de ambiente com base no sistema operacional."""
|
29
|
-
if not self.FFMPEG_URL or not self.FFMPEG_BINARY:
|
30
|
-
platform_name = system
|
31
|
-
if platform_name == 'Windows':
|
32
|
-
self.FFMPEG_URL = URL_PLATAFOMR
|
33
|
-
self.FFMPEG_BINARY = 'ffmpeg.exe'
|
34
|
-
elif platform_name == 'Linux':
|
35
|
-
self.FFMPEG_URL = URL_PLATAFOMR
|
36
|
-
self.FFMPEG_BINARY = 'ffmpeg'
|
37
|
-
else:
|
38
|
-
raise DeprecationWarning(f"Arquitetura '{platform_name}' ainda não suportada...\n\n"
|
39
|
-
f"Versão atual da lib: {self.VERSION}")
|
40
|
-
os.environ['FFMPEG_URL'] = self.FFMPEG_URL
|
41
|
-
os.environ['FFMPEG_BINARY'] = self.FFMPEG_BINARY
|
42
|
-
|
43
|
-
if not os.getenv('INSTALL_DIR'):
|
44
|
-
os.environ['INSTALL_DIR'] = self.INSTALL_DIR
|
45
|
-
|
46
|
-
@property
|
47
|
-
def __read_version(self):
|
48
|
-
"""Lê a versão do arquivo __version__.py."""
|
49
|
-
version_file = os.path.join(os.path.dirname(os.path.abspath(__file__)).split('.')[0], '__version__.py')
|
50
|
-
if os.path.isfile(version_file):
|
51
|
-
with open(version_file, 'r') as file:
|
52
|
-
version_line = file.readline().strip()
|
53
|
-
if version_line.startswith('__version__'):
|
54
|
-
return version_line.split('=')[1].strip().strip("'")
|
55
|
-
return 'Unknown Version'
|
56
|
-
|
57
|
-
def __download_file(self, url: str, local_filename: str):
|
58
|
-
"""Baixa um arquivo do URL para o caminho local especificado."""
|
59
|
-
try:
|
60
|
-
response = requests.get(url, stream=True)
|
61
|
-
response.raise_for_status()
|
62
|
-
total_length = int(response.headers.get('content-length', 0))
|
63
|
-
|
64
|
-
with open(local_filename, 'wb') as f:
|
65
|
-
start_time = time.time()
|
66
|
-
downloaded = 0
|
67
|
-
|
68
|
-
for data in response.iter_content(chunk_size=4096):
|
69
|
-
downloaded += len(data)
|
70
|
-
f.write(data)
|
71
|
-
|
72
|
-
elapsed_time = time.time() - start_time
|
73
|
-
elapsed_time = max(elapsed_time, 0.001)
|
74
|
-
speed_kbps = (downloaded / 1024) / elapsed_time
|
75
|
-
percent_done = (downloaded / total_length) * 100
|
76
|
-
|
77
|
-
|
78
|
-
except requests.RequestException as e:
|
79
|
-
raise Exception(f"Erro durante o download: {e}")
|
80
|
-
|
81
|
-
def __extract_zip(self, zip_path: str, extract_to: str):
|
82
|
-
"""Descompacta o arquivo ZIP no diretório especificado."""
|
83
|
-
try:
|
84
|
-
with zipfile.ZipFile(zip_path, 'r') as zip_ref:
|
85
|
-
zip_ref.extractall(extract_to)
|
86
|
-
except zipfile.BadZipFile as e:
|
87
|
-
sys.stderr.write(f"Erro ao descompactar o arquivo: {e}\n")
|
88
|
-
raise
|
89
|
-
finally:
|
90
|
-
os.remove(zip_path)
|
91
|
-
|
92
|
-
def remove_file(self, file_path: str):
|
93
|
-
"""Remove o arquivo ou diretório especificado."""
|
94
|
-
if os.path.exists(file_path):
|
95
|
-
try:
|
96
|
-
shutil.rmtree(file_path, onerror=self.handle_remove_readonly)
|
97
|
-
except Exception as e:
|
98
|
-
print(f"Erro ao remover {file_path}: {e}")
|
99
|
-
raise
|
100
|
-
|
101
|
-
def install_bins(self):
|
102
|
-
"""Instala o ffmpeg baixando e descompactando o binário apropriado."""
|
103
|
-
zip_path = os.path.join(self.INSTALL_DIR, "ffmpeg.zip")
|
104
|
-
os.makedirs(self.INSTALL_DIR, exist_ok=True)
|
105
|
-
self.__download_file(self.FFMPEG_URL, zip_path)
|
106
|
-
self.__extract_zip(zip_path, self.INSTALL_DIR)
|
107
|
-
self.remove_file(zip_path)
|
108
|
-
os.environ["PATH"] += os.pathsep + self.INSTALL_DIR
|
109
|
-
return
|
110
|
-
|
111
|
-
def handle_remove_readonly(self, func, path, exc_info):
|
112
|
-
"""Callback para lidar com arquivos somente leitura."""
|
113
|
-
os.chmod(path, stat.S_IWRITE)
|
114
|
-
func(path)
|
115
|
-
|
116
|
-
|
117
|
-
if __name__ == "__main__":
|
118
|
-
FFmpegExceptions("erro de runtime...")
|
@@ -1,78 +0,0 @@
|
|
1
|
-
import platform
|
2
|
-
import os
|
3
|
-
|
4
|
-
URL_PLATAFOMR = ''
|
5
|
-
|
6
|
-
|
7
|
-
def get_processor_info():
|
8
|
-
system = platform.system()
|
9
|
-
architecture = platform.architecture()[0]
|
10
|
-
processor = ''
|
11
|
-
if system == "Windows":
|
12
|
-
processor = platform.processor()
|
13
|
-
elif system in ["Linux", "Darwin"]: # Darwin é o nome do sistema para macOS
|
14
|
-
try:
|
15
|
-
if system == "Linux":
|
16
|
-
# Obtém informações detalhadas do processador no Linux
|
17
|
-
with open("/proc/cpuinfo") as f:
|
18
|
-
cpuinfo = f.read()
|
19
|
-
if "model name" in cpuinfo:
|
20
|
-
processor = cpuinfo.split("model name")[1].split(":")[1].split("\n")[0].strip()
|
21
|
-
else:
|
22
|
-
processor = "Unknown"
|
23
|
-
elif system == "Darwin":
|
24
|
-
# Obtém informações detalhadas do processador no macOS
|
25
|
-
processor = os.popen("sysctl -n machdep.cpu.brand_string").read().strip()
|
26
|
-
except FileNotFoundError:
|
27
|
-
processor = "Unknown"
|
28
|
-
d = (f"System: {system} "
|
29
|
-
f"Architecture: {architecture} "
|
30
|
-
f"Processor: {processor} ")
|
31
|
-
return d
|
32
|
-
|
33
|
-
|
34
|
-
# Processa a informação do processador e limpa a string
|
35
|
-
data = (get_processor_info().replace('Architecture:', '').replace('System:', '').
|
36
|
-
replace('Processor:', '').strip().split())
|
37
|
-
|
38
|
-
# Remove entradas vazias e limpa espaços em branco
|
39
|
-
cleaned_data = [item.strip() for item in data if item.strip()]
|
40
|
-
|
41
|
-
# Garantindo que há pelo menos três elementos
|
42
|
-
if len(cleaned_data) >= 2:
|
43
|
-
system = cleaned_data[0]
|
44
|
-
architecture = cleaned_data[1]
|
45
|
-
processor = ' '.join(cleaned_data[2:]) # Junta o restante como o processador
|
46
|
-
|
47
|
-
URL_BASE_REPO = "https://raw.githubusercontent.com/PauloCesar-dev404/binarios/main/"
|
48
|
-
# Mapeamento para Linux
|
49
|
-
linux_mapping = {
|
50
|
-
"x86_64": "amd64",
|
51
|
-
"i686": "i686",
|
52
|
-
"arm64": "arm64",
|
53
|
-
"armhf": "armhf",
|
54
|
-
"armel": "armel"
|
55
|
-
}
|
56
|
-
# Formata a URL com base no sistema e arquitetura
|
57
|
-
if system == "Linux" and ('intel' in processor.lower() or 'amd' in processor.lower()):
|
58
|
-
url = f"{URL_BASE_REPO}linux/ffmpeg-7.0.2-{linux_mapping.get('x86_64')}.zip"
|
59
|
-
elif system == "Linux" and 'i686' in architecture.lower():
|
60
|
-
url = f"{URL_BASE_REPO}linux/ffmpeg-7.0.2-{linux_mapping.get('i686')}.zip"
|
61
|
-
elif system == "Linux" and 'arm64' in architecture.lower():
|
62
|
-
url = f"{URL_BASE_REPO}linux/ffmpeg-7.0.2-{linux_mapping.get('arm64')}.zip"
|
63
|
-
elif system == "Linux" and 'armhf' in architecture.lower():
|
64
|
-
url = f"{URL_BASE_REPO}linux/ffmpeg-7.0.2-{linux_mapping.get('armhf')}.zip"
|
65
|
-
elif system == "Linux" and 'armel' in architecture.lower():
|
66
|
-
url = f"{URL_BASE_REPO}linux/ffmpeg-7.0.2-{linux_mapping.get('armel')}.zip"
|
67
|
-
elif system == "Windows" and architecture == '64bit':
|
68
|
-
url = f"{URL_BASE_REPO}windows/win-ffmpeg-7.0.2-full-amd64-intel64.zip"
|
69
|
-
else:
|
70
|
-
url = f"Unsupported system or architecture"
|
71
|
-
|
72
|
-
URL_PLATAFOMR = url
|
73
|
-
|
74
|
-
else:
|
75
|
-
raise DeprecationWarning("Não foi possível obter seu sistema ....consulte o desenvolvedor!")
|
76
|
-
|
77
|
-
if __name__ == '__main__':
|
78
|
-
raise RuntimeError("este é uma função interna!")
|
@@ -1,6 +0,0 @@
|
|
1
|
-
__version__ = '0.3.5'
|
2
|
-
__lib_name__ = 'ffmpeg_for_python'
|
3
|
-
__autor__ = 'PauloCesar-dev404'
|
4
|
-
__repo__ = 'https://github.com/PauloCesar-dev404/ffmpeg-for-python'
|
5
|
-
__lib__ = f'https://raw.githubusercontent.com/PauloCesar-dev404/ffmpeg-for-python/main/{__lib_name__}-{__version__}-py3-none-any.whl'
|
6
|
-
__source__ = f'https://raw.githubusercontent.com/PauloCesar-dev404/ffmpeg-for-python/main/{__lib_name__}-{__version__}.tar.gz'
|