udemy-userAPI 0.3.9__py3-none-any.whl → 0.3.11__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- udemy_userAPI/__version__.py +1 -1
- udemy_userAPI/authenticate.py +22 -22
- udemy_userAPI/bultins.py +115 -4
- {udemy_userAPI-0.3.9.dist-info → udemy_userAPI-0.3.11.dist-info}/METADATA +2 -2
- udemy_userAPI-0.3.11.dist-info/RECORD +17 -0
- 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.9.dist-info/RECORD +0 -29
- {udemy_userAPI-0.3.9.dist-info → udemy_userAPI-0.3.11.dist-info}/LICENSE +0 -0
- {udemy_userAPI-0.3.9.dist-info → udemy_userAPI-0.3.11.dist-info}/WHEEL +0 -0
- {udemy_userAPI-0.3.9.dist-info → udemy_userAPI-0.3.11.dist-info}/top_level.txt +0 -0
udemy_userAPI/__version__.py
CHANGED
udemy_userAPI/authenticate.py
CHANGED
@@ -208,7 +208,7 @@ class UdemyAuth:
|
|
208
208
|
with open(self.__file_path, 'wb') as f:
|
209
209
|
f.write(b'')
|
210
210
|
|
211
|
-
def login_passwordless(self, email: str, locale: str = 'pt-BR'):
|
211
|
+
def login_passwordless(self, email: str, locale: str = 'pt-BR', otp_callback=None):
|
212
212
|
"""
|
213
213
|
Realiza login na Udemy usando autenticação de dois fatores (2FA).
|
214
214
|
|
@@ -219,6 +219,7 @@ class UdemyAuth:
|
|
219
219
|
Args:
|
220
220
|
email (str): Email do usuário.
|
221
221
|
locale (str): Localização do usuário (recomendado para receber mensagens no idioma local).
|
222
|
+
otp_callback (callable, opcional): Função para obter o código OTP (se None, usa input padrão).
|
222
223
|
|
223
224
|
Raises:
|
224
225
|
LoginException: Em caso de falha no processo de login.
|
@@ -227,23 +228,16 @@ class UdemyAuth:
|
|
227
228
|
try:
|
228
229
|
if self.verif_login():
|
229
230
|
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
231
|
|
233
|
-
|
232
|
+
session = cloudscraper.create_scraper()
|
234
233
|
signup_url = "https://www.udemy.com/join/signup-popup/"
|
235
234
|
headers = {"User-Agent": "okhttp/4.9.2 UdemyAndroid 8.9.2(499) (phone)"}
|
236
235
|
response = session.get(signup_url, headers=headers)
|
237
|
-
|
238
|
-
# Obtém o token CSRF dos cookies retornados
|
239
236
|
csrf_token = response.cookies.get("csrftoken")
|
240
237
|
if not csrf_token:
|
241
238
|
raise LoginException("Não foi possível obter o token CSRF.")
|
242
239
|
|
243
|
-
# Prepara os dados do login
|
244
240
|
data = {"email": email, "fullname": ""}
|
245
|
-
|
246
|
-
# Atualiza os cookies e cabeçalhos da sessão
|
247
241
|
session.cookies.update(response.cookies)
|
248
242
|
session.headers.update({
|
249
243
|
"User-Agent": "okhttp/4.9.2 UdemyAndroid 8.9.2(499) (phone)",
|
@@ -261,17 +255,20 @@ class UdemyAuth:
|
|
261
255
|
"Cache-Control": "no-cache",
|
262
256
|
})
|
263
257
|
|
264
|
-
# Faz a requisição para iniciar o login
|
265
258
|
login_url = "https://www.udemy.com/api-2.0/auth/code-generation/login/4.0/"
|
266
259
|
response = session.post(login_url, data=data, allow_redirects=False)
|
260
|
+
|
267
261
|
if 'error_message' in response.text:
|
268
262
|
erro_data: dict = response.json()
|
269
263
|
error_message = erro_data.get('error_message', {})
|
270
264
|
raise LoginException(error_message)
|
265
|
+
|
271
266
|
for attempt in range(3):
|
272
|
-
#
|
273
|
-
|
274
|
-
|
267
|
+
# Obtém o código OTP via callback ou terminal
|
268
|
+
if otp_callback and callable(otp_callback):
|
269
|
+
otp = otp_callback()
|
270
|
+
else:
|
271
|
+
otp = input("Digite o código de 6 dígitos enviado ao seu e-mail: ")
|
275
272
|
otp_login_url = "https://www.udemy.com/api-2.0/auth/udemy-passwordless/login/4.0/"
|
276
273
|
otp_data = {
|
277
274
|
"email": email,
|
@@ -280,33 +277,36 @@ class UdemyAuth:
|
|
280
277
|
"subscribeToEmails": "false",
|
281
278
|
"upow": J(email, 'login')
|
282
279
|
}
|
280
|
+
|
283
281
|
session.headers.update({
|
284
282
|
"Referer": f"https://www.udemy.com/join/passwordless-auth/?locale={locale}&next="
|
285
283
|
f"https%3A%2F%2Fwww.udemy.com%2Fmobile%2Fipad%2F&response_type=html"
|
286
284
|
})
|
285
|
+
|
287
286
|
response = session.post(otp_login_url, otp_data, allow_redirects=False)
|
288
|
-
|
287
|
+
|
289
288
|
if response.status_code == 200:
|
290
289
|
self._save_cookies(session.cookies)
|
290
|
+
break # Sai do loop se o login for bem-sucedido
|
291
291
|
else:
|
292
292
|
if 'error_message' in response.text:
|
293
293
|
erro_data: dict = response.json()
|
294
294
|
error_message = erro_data.get('error_message', {})
|
295
295
|
error_code = erro_data.get('error_code', {})
|
296
|
+
|
296
297
|
if error_code == '1538':
|
297
298
|
raise LoginException(error_message)
|
298
299
|
elif error_code == '2550':
|
299
|
-
|
300
|
-
|
300
|
+
### codigo errado....
|
301
|
+
raise LoginException(error_message)
|
301
302
|
elif error_code == '1330':
|
302
303
|
raise LoginException(error_message)
|
303
304
|
elif error_code == '1149':
|
304
|
-
raise LoginException(
|
305
|
+
raise LoginException(
|
306
|
+
f"Erro interno ao enviar os dados, veja os detalhes: '{error_message}'")
|
307
|
+
|
305
308
|
raise LoginException(response.text)
|
306
|
-
|
309
|
+
|
307
310
|
except Exception as e:
|
308
|
-
if DEBUG
|
309
|
-
error_details = traceback.format_exc()
|
310
|
-
else:
|
311
|
-
error_details = str(e)
|
311
|
+
error_details = traceback.format_exc() if DEBUG else str(e)
|
312
312
|
raise LoginException(error_details)
|
udemy_userAPI/bultins.py
CHANGED
@@ -49,7 +49,6 @@ class DRM:
|
|
49
49
|
except Exception as e:
|
50
50
|
raise Exception(f"Não foi possível obter as chaves!\n{e}")
|
51
51
|
|
52
|
-
|
53
52
|
class Files:
|
54
53
|
def __init__(self, files: list[dict], id_course):
|
55
54
|
"""
|
@@ -104,6 +103,112 @@ class Files:
|
|
104
103
|
'data-file': da['download_urls']}
|
105
104
|
download_urls.append(dt_file)
|
106
105
|
return download_urls
|
106
|
+
class Caption:
|
107
|
+
"""Representa uma legenda."""
|
108
|
+
|
109
|
+
def __init__(self, caption: dict):
|
110
|
+
"""
|
111
|
+
Inicializa uma instância de Caption.
|
112
|
+
|
113
|
+
Args:
|
114
|
+
caption (dict): Dados da legenda.
|
115
|
+
"""
|
116
|
+
self._caption = caption
|
117
|
+
|
118
|
+
@property
|
119
|
+
def locale(self) -> str:
|
120
|
+
"""Retorna o idioma."""
|
121
|
+
return self._caption.get('video_label', '')
|
122
|
+
|
123
|
+
@property
|
124
|
+
def status(self) -> str:
|
125
|
+
"""Retorna o status da legenda 1 ou 0"""
|
126
|
+
return self._caption.get('status')
|
127
|
+
|
128
|
+
@property
|
129
|
+
def title(self) -> str:
|
130
|
+
"""Retorna o título da legenda."""
|
131
|
+
return self._caption.get('title', '')
|
132
|
+
|
133
|
+
@property
|
134
|
+
def created(self) -> str:
|
135
|
+
"""Retorna a data de criação da legenda."""
|
136
|
+
return self._caption.get('created', '')
|
137
|
+
|
138
|
+
@property
|
139
|
+
def id(self) -> int:
|
140
|
+
"""Retorna o ID da legenda."""
|
141
|
+
return self._caption.get('id', 0)
|
142
|
+
|
143
|
+
@property
|
144
|
+
def url(self) -> str:
|
145
|
+
"""Retorna a URL da legenda."""
|
146
|
+
return self._caption.get('url', '')
|
147
|
+
|
148
|
+
@property
|
149
|
+
def content(self) -> str:
|
150
|
+
"""Obtém o conteúdo da legenda."""
|
151
|
+
if self.url:
|
152
|
+
r = requests.get(headers=HEADERS_USER, url=self.url)
|
153
|
+
if r.status_code == 200:
|
154
|
+
return r.text
|
155
|
+
else:
|
156
|
+
raise ConnectionError(
|
157
|
+
f'status_code: {r.status_code}, Não foi possível obter o conteúdo da legenda!'
|
158
|
+
)
|
159
|
+
else:
|
160
|
+
raise FileNotFoundError(
|
161
|
+
'Não foi possível obter a URL da legenda!'
|
162
|
+
)
|
163
|
+
|
164
|
+
class Captions:
|
165
|
+
"""Gerencia as legendas de um vídeo."""
|
166
|
+
|
167
|
+
def __init__(self, caption_data: list):
|
168
|
+
"""
|
169
|
+
Inicializa uma instância de Captions.
|
170
|
+
|
171
|
+
Args:
|
172
|
+
caption_data (list): Dados das legendas.
|
173
|
+
"""
|
174
|
+
self._caption_data = caption_data
|
175
|
+
|
176
|
+
def languages(self) -> list[dict]:
|
177
|
+
"""Retorna a lista de idiomas disponíveis na aula."""
|
178
|
+
langs = []
|
179
|
+
for caption in self._caption_data:
|
180
|
+
locale_id = caption.get('locale_id', '')
|
181
|
+
video_label = caption.get('video_label','')
|
182
|
+
if locale_id:
|
183
|
+
langs.append({'locale_id': locale_id,'locale':video_label})
|
184
|
+
return langs
|
185
|
+
|
186
|
+
def get_lang(self, locale_id: str) -> Caption:
|
187
|
+
"""
|
188
|
+
Obtém a legenda para o idioma especificado.
|
189
|
+
|
190
|
+
|
191
|
+
Args:
|
192
|
+
locale_id (str): ID do idioma,pode ser obtido no método -> 'languages'
|
193
|
+
|
194
|
+
Returns:
|
195
|
+
Caption: Objeto Caption.
|
196
|
+
|
197
|
+
Raises:
|
198
|
+
FileNotFoundError: Se o idioma não estiver disponível na aula.
|
199
|
+
"""
|
200
|
+
is_t = False
|
201
|
+
cpt = {}
|
202
|
+
for caption in self._caption_data:
|
203
|
+
if locale_id == caption.get('locale_id'):
|
204
|
+
is_t = True
|
205
|
+
cpt = caption
|
206
|
+
if not is_t:
|
207
|
+
raise FileNotFoundError(
|
208
|
+
'Esse idioma não está disponível nessa aula!'
|
209
|
+
)
|
210
|
+
c = Caption(caption=cpt)
|
211
|
+
return c
|
107
212
|
|
108
213
|
class Quiz:
|
109
214
|
"""Representa um quiz.
|
@@ -244,14 +349,20 @@ class Lecture:
|
|
244
349
|
return self.__asset.get('media_sources',[])
|
245
350
|
|
246
351
|
@property
|
247
|
-
def get_captions(self) ->
|
352
|
+
def get_captions(self) -> Captions:
|
248
353
|
"""
|
249
354
|
Obtém as legendas.
|
250
355
|
|
251
356
|
Returns:
|
252
|
-
|
357
|
+
Captions: Objeto para gerenciar as legendas.
|
253
358
|
"""
|
254
|
-
|
359
|
+
if self.__asset.get('captions', []):
|
360
|
+
c = Captions(caption_data=self.__asset.get('captions', []))
|
361
|
+
return c
|
362
|
+
else:
|
363
|
+
raise FileNotFoundError(
|
364
|
+
'Não foi encontrada legendas nessa aula!'
|
365
|
+
)
|
255
366
|
|
256
367
|
@property
|
257
368
|
def get_external_url(self) -> list:
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.2
|
2
2
|
Name: udemy_userAPI
|
3
|
-
Version: 0.3.
|
3
|
+
Version: 0.3.11
|
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
|
@@ -29,7 +29,7 @@ Dynamic: summary
|
|
29
29
|
# udemy-userAPI
|
30
30
|
|
31
31
|
|
32
|
-

|
33
33
|

|
34
34
|
[](https://paulocesar-dev404.github.io/me-apoiando-online/)
|
35
35
|
[](https://github.com/PauloCesar-dev404/udemy-userAPI/blob/main/docs/iniciando.md)
|
@@ -0,0 +1,17 @@
|
|
1
|
+
udemy_userAPI/__init__.py,sha256=BPle89xE_CMTKKe_Lw6jioYLgpH-q_Lpho2S-n1PIUA,206
|
2
|
+
udemy_userAPI/__version__.py,sha256=H_ODVq4mKyiFkG5i7lWTBSW7IGZEXM6ygiPLRFT_VtA,406
|
3
|
+
udemy_userAPI/api.py,sha256=GVvbbs3vFN-rF-qLBwiuHz77sjehwk8HjAI-Dey_A6c,29167
|
4
|
+
udemy_userAPI/authenticate.py,sha256=lKh4_UMT4zapnUzUSgM0HZoyZYX84w0MMZAaGvCMZQ4,14086
|
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.11.dist-info/LICENSE,sha256=l4jdKYt8gSdDFOGr09vCKnMn_Im55XIcQKqTDEtFfNs,1095
|
14
|
+
udemy_userAPI-0.3.11.dist-info/METADATA,sha256=HEgb5dgPW0dIQPdd2BHd4moLOuA1_y_8PWm1CapcxMY,1657
|
15
|
+
udemy_userAPI-0.3.11.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
16
|
+
udemy_userAPI-0.3.11.dist-info/top_level.txt,sha256=ijTINaSDRKhdahY_X7dmSRFTxBIwQErWv9ATCG55mog,14
|
17
|
+
udemy_userAPI-0.3.11.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'
|
ffmpeg_for_python/exeptions.py
DELETED
@@ -1,91 +0,0 @@
|
|
1
|
-
import time
|
2
|
-
|
3
|
-
INPUT_ERROR = [
|
4
|
-
'Error opening input: No such file or directory',
|
5
|
-
'Error opening input: Permission denied',
|
6
|
-
'Error opening input: Invalid argument',
|
7
|
-
'Error opening input: Protocol not found',
|
8
|
-
'Error opening input: Unsupported protocol',
|
9
|
-
'Error opening input: File format not recognized',
|
10
|
-
'Error opening input: Could not open file',
|
11
|
-
'Error opening input: Invalid data found when processing input',
|
12
|
-
'Error opening input: Input stream is empty',
|
13
|
-
'Error opening input: Cannot open file for reading',
|
14
|
-
'Error opening input: File is too short',
|
15
|
-
'Error opening input: End of file while parsing input',
|
16
|
-
'Error opening input: Codec not found',
|
17
|
-
'Error opening input: No decoder for codec',
|
18
|
-
'Error opening input: Stream not found',
|
19
|
-
'Error opening input: Stream codec not found',
|
20
|
-
'Error opening input: Stream index out of range',
|
21
|
-
'Error opening input: Invalid timestamp',
|
22
|
-
'Error opening input: Corrupt file',
|
23
|
-
'Error opening input: Unsupported codec',
|
24
|
-
'Error opening input: Failed to initialize filter',
|
25
|
-
'Error opening input: Error while opening codec',
|
26
|
-
'Error opening input: Device not found',
|
27
|
-
'Error opening input: Device or resource busy',
|
28
|
-
'Error opening input: Invalid option',
|
29
|
-
'Error opening input: Unable to seek',
|
30
|
-
'Error opening input: Input format not found'
|
31
|
-
]
|
32
|
-
OUTPUT_ERROR = [
|
33
|
-
'Error opening output file: No such file or directory',
|
34
|
-
'Error opening output file: Permission denied',
|
35
|
-
'Error opening output file: Invalid argument',
|
36
|
-
'Error opening output file: Unsupported protocol',
|
37
|
-
'Error opening output file: Protocol not found',
|
38
|
-
'Error opening output file: File format not recognized',
|
39
|
-
'Error opening output file: Could not open file for writing',
|
40
|
-
'Error opening output file: Disk full or quota exceeded',
|
41
|
-
'Error opening output file: Cannot create file',
|
42
|
-
'Error opening output file: Invalid data found when processing output',
|
43
|
-
'Error opening output file: Output stream not found',
|
44
|
-
'Error opening output file: Cannot write to file',
|
45
|
-
'Error opening output file: File already exists',
|
46
|
-
'Error opening output file: Unsupported codec',
|
47
|
-
'Error opening output file: Codec not found',
|
48
|
-
'Error opening output file: Cannot open codec for writing',
|
49
|
-
'Error opening output file: Failed to initialize filter',
|
50
|
-
'Error opening output file: Invalid option',
|
51
|
-
'Error opening output file: Invalid timestamp',
|
52
|
-
'Error opening output file: Corrupt file',
|
53
|
-
'Error opening output file: Device or resource busy',
|
54
|
-
'Error opening output file: Cannot seek',
|
55
|
-
'Error opening output file: Stream index out of range',
|
56
|
-
'Error opening output file: Stream codec not found'
|
57
|
-
]
|
58
|
-
ERROS = []
|
59
|
-
for er in INPUT_ERROR:
|
60
|
-
ERROS.append(er)
|
61
|
-
for er in OUTPUT_ERROR:
|
62
|
-
ERROS.append(er)
|
63
|
-
|
64
|
-
|
65
|
-
class FFmpegExceptions(Exception):
|
66
|
-
def __init__(self, message: str):
|
67
|
-
super().__init__(message)
|
68
|
-
|
69
|
-
def __str__(self):
|
70
|
-
"""
|
71
|
-
Retorna a representação em string da exceção.
|
72
|
-
|
73
|
-
Returns:
|
74
|
-
str: Mensagem de erro formatada com detalhes adicionais, se presentes.
|
75
|
-
"""
|
76
|
-
|
77
|
-
return super().__str__()
|
78
|
-
|
79
|
-
|
80
|
-
def wraper_erros(line: str):
|
81
|
-
"""Verifica se a linha de saida do ffmpeg está no dict de erros e retorna sua categoria"""
|
82
|
-
|
83
|
-
if "Error" in line:
|
84
|
-
erro = line.split('Error')[1]
|
85
|
-
return erro.strip()
|
86
|
-
elif 'already exists. Overwrite? [y/N]' in line:
|
87
|
-
erro = line.split('File')[1]
|
88
|
-
return erro.strip()
|
89
|
-
|
90
|
-
|
91
|
-
|