udemy-userAPI 0.3.10__py3-none-any.whl → 0.3.12__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.
- udemy_userAPI/__version__.py +1 -1
- udemy_userAPI/api.py +1 -1
- udemy_userAPI/authenticate.py +147 -27
- {udemy_userAPI-0.3.10.dist-info → udemy_userapi-0.3.12.dist-info}/METADATA +5 -4
- udemy_userapi-0.3.12.dist-info/RECORD +17 -0
- {udemy_userAPI-0.3.10.dist-info → udemy_userapi-0.3.12.dist-info}/WHEEL +1 -1
- animation_consoles/__init__.py +0 -1
- animation_consoles/animation.py +0 -64
- ffmpeg_for_python/__config__.py +0 -118
- ffmpeg_for_python/__init__.py +0 -8
- ffmpeg_for_python/__utils.py +0 -78
- ffmpeg_for_python/__version__.py +0 -6
- ffmpeg_for_python/exeptions.py +0 -91
- ffmpeg_for_python/ffmpeg.py +0 -203
- m3u8_analyzer/M3u8Analyzer.py +0 -807
- m3u8_analyzer/__init__.py +0 -7
- m3u8_analyzer/__version__.py +0 -1
- m3u8_analyzer/exeptions.py +0 -82
- udemy_userAPI-0.3.10.dist-info/RECORD +0 -29
- {udemy_userAPI-0.3.10.dist-info → udemy_userapi-0.3.12.dist-info/licenses}/LICENSE +0 -0
- {udemy_userAPI-0.3.10.dist-info → udemy_userapi-0.3.12.dist-info}/top_level.txt +0 -0
udemy_userAPI/__version__.py
CHANGED
udemy_userAPI/api.py
CHANGED
udemy_userAPI/authenticate.py
CHANGED
@@ -1,14 +1,47 @@
|
|
1
|
+
import http
|
1
2
|
import json
|
2
3
|
import os
|
3
4
|
import pickle
|
4
5
|
import traceback
|
6
|
+
from http.cookies import SimpleCookie
|
7
|
+
|
8
|
+
import cloudscraper
|
5
9
|
import requests
|
10
|
+
|
6
11
|
from .exeptions import UnhandledExceptions, UdemyUserApiExceptions, LoginException, Upstreamconnecterror
|
7
|
-
import cloudscraper
|
8
12
|
|
9
13
|
DEBUG = False
|
10
14
|
|
11
15
|
|
16
|
+
def convert_cook(cookie_string):
|
17
|
+
"""
|
18
|
+
Converte uma string de cookies para um dicionário usando http.cookies.
|
19
|
+
|
20
|
+
Args:
|
21
|
+
cookie_string (str): A string de cookies no formato "nome1=valor1; nome2=valor2".
|
22
|
+
|
23
|
+
Returns:
|
24
|
+
dict: Um dicionário onde as chaves são os nomes dos cookies e os valores são os seus respectivos valores.
|
25
|
+
Retorna um dicionário vazio se a string for inválida ou vazia.
|
26
|
+
"""
|
27
|
+
if not isinstance(cookie_string, str) or not cookie_string.strip():
|
28
|
+
print("A string de cookies fornecida é inválida ou vazia.")
|
29
|
+
return {}
|
30
|
+
|
31
|
+
cookie = SimpleCookie()
|
32
|
+
try:
|
33
|
+
# Carrega a string de cookies
|
34
|
+
cookie.load(cookie_string)
|
35
|
+
|
36
|
+
# Converte o objeto SimpleCookie para um dicionário regular
|
37
|
+
dicionario_cookies = {k: v.value for k, v in cookie.items()}
|
38
|
+
|
39
|
+
return dicionario_cookies
|
40
|
+
except http.cookies.CookieError as e:
|
41
|
+
print(f"Erro ao analisar a string de cookies: {e}")
|
42
|
+
return {}
|
43
|
+
|
44
|
+
|
12
45
|
class UdemyAuth:
|
13
46
|
def __init__(self):
|
14
47
|
"""
|
@@ -162,7 +195,7 @@ class UdemyAuth:
|
|
162
195
|
|
163
196
|
# Verifica a resposta para determinar se o login foi bem-sucedido
|
164
197
|
if "returnUrl" in r.text:
|
165
|
-
self.
|
198
|
+
self.__save_cookies(s.cookies)
|
166
199
|
else:
|
167
200
|
login_error = r.json().get("error", {}).get("data", {}).get("formErrors", [])[0]
|
168
201
|
if login_error[0] == "Y":
|
@@ -176,14 +209,101 @@ class UdemyAuth:
|
|
176
209
|
e = traceback.format_exc()
|
177
210
|
raise LoginException(e)
|
178
211
|
|
179
|
-
def
|
212
|
+
def login_direct_cookies(self, cookies: str):
|
213
|
+
"""
|
214
|
+
Realiza login via cookies diretamente. (Não é seguro para uso em produção).
|
215
|
+
|
216
|
+
Argumentos:
|
217
|
+
cookies: Pode ser o caminho do arquivo cookies (apenas .json ou .txt)
|
218
|
+
ou a string com os cookies.
|
219
|
+
|
220
|
+
Raises:
|
221
|
+
FileNotFoundError: Se o arquivo especificado não for encontrado.
|
222
|
+
ValueError: Se o arquivo não for .json ou .txt, ou se o JSON for inválido.
|
223
|
+
UnhandledExceptions: Para outros erros inesperados durante a leitura do arquivo.
|
224
|
+
LoginException: Se os cookies estiverem expirados, inválidos ou houver falha no login.
|
225
|
+
"""
|
226
|
+
cookies_content = ""
|
227
|
+
# Verifica se a string fornecida é um caminho de arquivo existente
|
228
|
+
if os.path.isfile(cookies):
|
229
|
+
file_extension = os.path.splitext(cookies)[1].lower() # Pega a extensão do arquivo
|
230
|
+
|
231
|
+
# Verifica se a extensão é permitida
|
232
|
+
if file_extension not in ['.json', '.txt']:
|
233
|
+
raise ValueError(
|
234
|
+
f"Erro: Apenas arquivos .json ou .txt são permitidos. "
|
235
|
+
f"Extensão recebida: '{file_extension}' para o arquivo '{cookies}'."
|
236
|
+
)
|
237
|
+
|
238
|
+
try:
|
239
|
+
with open(cookies, 'r', encoding='utf-8') as f:
|
240
|
+
cookies_content = f.read()
|
241
|
+
|
242
|
+
# Se for um arquivo JSON, tenta carregá-lo para validar
|
243
|
+
if file_extension == '.json':
|
244
|
+
try:
|
245
|
+
# Tenta carregar o JSON. Se for um JSON de cookies, ele pode estar
|
246
|
+
# em um formato específico. Aqui estamos apenas validando a sintaxe JSON.
|
247
|
+
json.loads(cookies_content)
|
248
|
+
except json.JSONDecodeError as e:
|
249
|
+
raise ValueError(f"Erro: O arquivo '{cookies}' contém JSON inválido: {e}")
|
250
|
+
|
251
|
+
except FileNotFoundError:
|
252
|
+
raise FileNotFoundError(f"Erro: O arquivo '{cookies}' não foi encontrado.")
|
253
|
+
except Exception as e:
|
254
|
+
raise UnhandledExceptions(f"Erro ao ler o arquivo '{cookies}': {e}")
|
255
|
+
else:
|
256
|
+
# Se não for um arquivo, assume-se que é a string de cookies diretamente
|
257
|
+
cookies_content = cookies
|
258
|
+
|
259
|
+
headers = {
|
260
|
+
"accept": "*/*",
|
261
|
+
"accept-language": "pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7",
|
262
|
+
"cache-control": "no-cache",
|
263
|
+
"pragma": "no-cache",
|
264
|
+
"sec-ch-ua": '"Chromium";v="118", "Google Chrome";v="118", "Not=A?Brand";v="99"',
|
265
|
+
"sec-ch-ua-mobile": "?0",
|
266
|
+
"sec-ch-ua-platform": '"Windows"',
|
267
|
+
"sec-fetch-dest": "empty",
|
268
|
+
"sec-fetch-mode": "cors",
|
269
|
+
"sec-fetch-site": "cross-site",
|
270
|
+
"Cookie": cookies_content,
|
271
|
+
"Referer": "https://www.udemy.com/"
|
272
|
+
}
|
273
|
+
url = 'https://www.udemy.com/api-2.0/contexts/me/?header=true'
|
274
|
+
|
275
|
+
try:
|
276
|
+
resp = requests.get(url=url, headers=headers)
|
277
|
+
resp.raise_for_status() # Lança um HTTPError para respostas de status de erro (4xx ou 5xx)
|
278
|
+
|
279
|
+
convert = resp.json() # Usa resp.json() para parsear diretamente o JSON
|
280
|
+
is_logged_in = convert.get('header', {}).get('isLoggedIn', None)
|
281
|
+
|
282
|
+
if not is_logged_in:
|
283
|
+
raise LoginException(
|
284
|
+
"Cookies expirados ou inválidos!")
|
285
|
+
self.__save_cookies(resp.cookies)
|
286
|
+
|
287
|
+
except requests.exceptions.HTTPError as e:
|
288
|
+
# Captura erros HTTP (ex: 401 Unauthorized, 403 Forbidden)
|
289
|
+
raise LoginException(f"Erro de HTTP durante o login: {e}. Resposta: {e.response.text}")
|
290
|
+
except requests.exceptions.ConnectionError as e:
|
291
|
+
raise UnhandledExceptions(f"Erro de conexão: {e}")
|
292
|
+
except requests.exceptions.Timeout as e:
|
293
|
+
raise UnhandledExceptions(f"Tempo limite da requisição excedido: {e}")
|
294
|
+
except json.JSONDecodeError as e:
|
295
|
+
raise UnhandledExceptions(f"Erro ao decodificar JSON da resposta da API: {e}. Resposta: {resp.text}")
|
296
|
+
except Exception as e:
|
297
|
+
raise UnhandledExceptions(f"Um erro inesperado ocorreu durante o login: {e}")
|
298
|
+
|
299
|
+
def __save_cookies(self, cookies):
|
180
300
|
try:
|
181
301
|
with open(fr'{self.__file_path}', 'wb') as f:
|
182
302
|
pickle.dump(cookies, f)
|
183
303
|
except Exception as e:
|
184
304
|
raise LoginException(e)
|
185
305
|
|
186
|
-
def
|
306
|
+
def load_cookies(self) -> str:
|
187
307
|
"""Carrega cookies e retorna-os em uma string formatada"""
|
188
308
|
try:
|
189
309
|
file = os.path.join(self.__file_path)
|
@@ -208,7 +328,7 @@ class UdemyAuth:
|
|
208
328
|
with open(self.__file_path, 'wb') as f:
|
209
329
|
f.write(b'')
|
210
330
|
|
211
|
-
def login_passwordless(self, email: str, locale: str = 'pt-BR'):
|
331
|
+
def login_passwordless(self, email: str, locale: str = 'pt-BR', otp_callback=None):
|
212
332
|
"""
|
213
333
|
Realiza login na Udemy usando autenticação de dois fatores (2FA).
|
214
334
|
|
@@ -219,6 +339,7 @@ class UdemyAuth:
|
|
219
339
|
Args:
|
220
340
|
email (str): Email do usuário.
|
221
341
|
locale (str): Localização do usuário (recomendado para receber mensagens no idioma local).
|
342
|
+
otp_callback (callable, opcional): Função para obter o código OTP (se None, usa input padrão).
|
222
343
|
|
223
344
|
Raises:
|
224
345
|
LoginException: Em caso de falha no processo de login.
|
@@ -227,23 +348,16 @@ class UdemyAuth:
|
|
227
348
|
try:
|
228
349
|
if self.verif_login():
|
229
350
|
raise UserWarning("Atenção, você já possui uma Sessão válida!")
|
230
|
-
# Inicializa uma sessão com proteção contra Cloudflare
|
231
|
-
session = cloudscraper.create_scraper()
|
232
351
|
|
233
|
-
|
352
|
+
session = cloudscraper.create_scraper()
|
234
353
|
signup_url = "https://www.udemy.com/join/signup-popup/"
|
235
354
|
headers = {"User-Agent": "okhttp/4.9.2 UdemyAndroid 8.9.2(499) (phone)"}
|
236
355
|
response = session.get(signup_url, headers=headers)
|
237
|
-
|
238
|
-
# Obtém o token CSRF dos cookies retornados
|
239
356
|
csrf_token = response.cookies.get("csrftoken")
|
240
357
|
if not csrf_token:
|
241
358
|
raise LoginException("Não foi possível obter o token CSRF.")
|
242
359
|
|
243
|
-
# Prepara os dados do login
|
244
360
|
data = {"email": email, "fullname": ""}
|
245
|
-
|
246
|
-
# Atualiza os cookies e cabeçalhos da sessão
|
247
361
|
session.cookies.update(response.cookies)
|
248
362
|
session.headers.update({
|
249
363
|
"User-Agent": "okhttp/4.9.2 UdemyAndroid 8.9.2(499) (phone)",
|
@@ -261,17 +375,20 @@ class UdemyAuth:
|
|
261
375
|
"Cache-Control": "no-cache",
|
262
376
|
})
|
263
377
|
|
264
|
-
# Faz a requisição para iniciar o login
|
265
378
|
login_url = "https://www.udemy.com/api-2.0/auth/code-generation/login/4.0/"
|
266
379
|
response = session.post(login_url, data=data, allow_redirects=False)
|
380
|
+
|
267
381
|
if 'error_message' in response.text:
|
268
382
|
erro_data: dict = response.json()
|
269
383
|
error_message = erro_data.get('error_message', {})
|
270
384
|
raise LoginException(error_message)
|
385
|
+
|
271
386
|
for attempt in range(3):
|
272
|
-
#
|
273
|
-
|
274
|
-
|
387
|
+
# Obtém o código OTP via callback ou terminal
|
388
|
+
if otp_callback and callable(otp_callback):
|
389
|
+
otp = otp_callback()
|
390
|
+
else:
|
391
|
+
otp = input("Digite o código de 6 dígitos enviado ao seu e-mail: ")
|
275
392
|
otp_login_url = "https://www.udemy.com/api-2.0/auth/udemy-passwordless/login/4.0/"
|
276
393
|
otp_data = {
|
277
394
|
"email": email,
|
@@ -280,33 +397,36 @@ class UdemyAuth:
|
|
280
397
|
"subscribeToEmails": "false",
|
281
398
|
"upow": J(email, 'login')
|
282
399
|
}
|
400
|
+
|
283
401
|
session.headers.update({
|
284
402
|
"Referer": f"https://www.udemy.com/join/passwordless-auth/?locale={locale}&next="
|
285
403
|
f"https%3A%2F%2Fwww.udemy.com%2Fmobile%2Fipad%2F&response_type=html"
|
286
404
|
})
|
405
|
+
|
287
406
|
response = session.post(otp_login_url, otp_data, allow_redirects=False)
|
288
|
-
|
407
|
+
|
289
408
|
if response.status_code == 200:
|
290
|
-
self.
|
409
|
+
self.__save_cookies(session.cookies)
|
410
|
+
break # Sai do loop se o login for bem-sucedido
|
291
411
|
else:
|
292
412
|
if 'error_message' in response.text:
|
293
413
|
erro_data: dict = response.json()
|
294
414
|
error_message = erro_data.get('error_message', {})
|
295
415
|
error_code = erro_data.get('error_code', {})
|
416
|
+
|
296
417
|
if error_code == '1538':
|
297
418
|
raise LoginException(error_message)
|
298
419
|
elif error_code == '2550':
|
299
|
-
|
300
|
-
|
420
|
+
### codigo errado....
|
421
|
+
raise LoginException(error_message)
|
301
422
|
elif error_code == '1330':
|
302
423
|
raise LoginException(error_message)
|
303
424
|
elif error_code == '1149':
|
304
|
-
raise LoginException(
|
425
|
+
raise LoginException(
|
426
|
+
f"Erro interno ao enviar os dados, veja os detalhes: '{error_message}'")
|
427
|
+
|
305
428
|
raise LoginException(response.text)
|
306
|
-
|
429
|
+
|
307
430
|
except Exception as e:
|
308
|
-
if DEBUG
|
309
|
-
error_details = traceback.format_exc()
|
310
|
-
else:
|
311
|
-
error_details = str(e)
|
431
|
+
error_details = traceback.format_exc() if DEBUG else str(e)
|
312
432
|
raise LoginException(error_details)
|
@@ -1,6 +1,6 @@
|
|
1
|
-
Metadata-Version: 2.
|
1
|
+
Metadata-Version: 2.4
|
2
2
|
Name: udemy_userAPI
|
3
|
-
Version: 0.3.
|
3
|
+
Version: 0.3.12
|
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
|
@@ -21,6 +21,7 @@ Dynamic: description
|
|
21
21
|
Dynamic: description-content-type
|
22
22
|
Dynamic: keywords
|
23
23
|
Dynamic: license
|
24
|
+
Dynamic: license-file
|
24
25
|
Dynamic: platform
|
25
26
|
Dynamic: project-url
|
26
27
|
Dynamic: requires-dist
|
@@ -29,10 +30,10 @@ Dynamic: summary
|
|
29
30
|
# udemy-userAPI
|
30
31
|
|
31
32
|
|
32
|
-

|
33
34
|

|
34
35
|
[](https://paulocesar-dev404.github.io/me-apoiando-online/)
|
35
|
-
[](
|
36
|
+
[]()
|
36
37
|
|
37
38
|
|
38
39
|
Obtenha detalhes de cursos da plataforma udemy com a api de usuário,usando esta lib
|
@@ -0,0 +1,17 @@
|
|
1
|
+
udemy_userAPI/__init__.py,sha256=BPle89xE_CMTKKe_Lw6jioYLgpH-q_Lpho2S-n1PIUA,206
|
2
|
+
udemy_userAPI/__version__.py,sha256=hsuWNfD58RhtiW3QQ8DEVA806KW5TK_7r4v9j6OIL2o,406
|
3
|
+
udemy_userAPI/api.py,sha256=Pxwy-UrqVCLX1tTLQQojb7nCjkHE9mLlnpO1OJuqlAI,29166
|
4
|
+
udemy_userAPI/authenticate.py,sha256=ObUh8cs-o7mC9Ovid1UoFvPS4m0nSe4fBk-E3XQqL1o,19511
|
5
|
+
udemy_userAPI/bultins.py,sha256=LZlyOjSGte6B6gNn7cjl6L2Q2T_CyXIqqfkOUzt4CV4,21996
|
6
|
+
udemy_userAPI/exeptions.py,sha256=kfnPdZpqYY8nd0gnl6_Vh-MIz-XupmmbRPIuFnyXupk,692
|
7
|
+
udemy_userAPI/sections.py,sha256=Q1PlVt2Bu5MSEP8g11-F_gilJDdhZq50TV1Bo400jcA,6389
|
8
|
+
udemy_userAPI/udemy.py,sha256=SpK0LI4hjO45nZDz5waw-Py-d0uulBb28TVjltyWBxM,2920
|
9
|
+
udemy_userAPI/.cache/.udemy_userAPI,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
10
|
+
udemy_userAPI/mpd_analyzer/__init__.py,sha256=i3JVWyvcFLaj5kPmx8c1PgjsLht7OUIQQClD4yqYbo8,102
|
11
|
+
udemy_userAPI/mpd_analyzer/bin.wvd,sha256=1rAJdCc120hQlX9qe5KUS628eY2ZHYxQSmyhGNefSzo,2956
|
12
|
+
udemy_userAPI/mpd_analyzer/mpd_parser.py,sha256=PgUkHc5x8FTuXFCuYkWPZr9TaO_nsKalb02EFYl_zeA,8926
|
13
|
+
udemy_userapi-0.3.12.dist-info/licenses/LICENSE,sha256=l4jdKYt8gSdDFOGr09vCKnMn_Im55XIcQKqTDEtFfNs,1095
|
14
|
+
udemy_userapi-0.3.12.dist-info/METADATA,sha256=dkTiWdFOxfJejB4wL_ZYwuq2iy6DKRQheCcydIlzC2o,1602
|
15
|
+
udemy_userapi-0.3.12.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
16
|
+
udemy_userapi-0.3.12.dist-info/top_level.txt,sha256=ijTINaSDRKhdahY_X7dmSRFTxBIwQErWv9ATCG55mog,14
|
17
|
+
udemy_userapi-0.3.12.dist-info/RECORD,,
|
animation_consoles/__init__.py
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
from .animation import AnimationConsole
|
animation_consoles/animation.py
DELETED
@@ -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)
|
ffmpeg_for_python/__config__.py
DELETED
@@ -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...")
|
ffmpeg_for_python/__init__.py
DELETED
ffmpeg_for_python/__utils.py
DELETED
@@ -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!")
|
ffmpeg_for_python/__version__.py
DELETED
@@ -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'
|