udemy-userAPI 0.3.5__py3-none-any.whl → 0.3.7__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.
@@ -1,807 +0,0 @@
1
- import os
2
- import re
3
- from typing import List, Tuple, Dict
4
- from urllib.parse import urljoin
5
- import requests
6
- from colorama import Fore, Style
7
- from .exeptions import M3u8Error, M3u8NetworkingError, M3u8FileError
8
-
9
- __author__ = 'PauloCesar0073-dev404'
10
-
11
-
12
- class M3u8Analyzer:
13
- def __init__(self):
14
- """
15
- análise e manipulação de streams M3U8 de maneira bruta
16
- """
17
- pass
18
-
19
- @staticmethod
20
- def get_m3u8(url_m3u8: str, headers: dict = None, save_in_file=None, timeout: int = None):
21
- """
22
- Obtém o conteúdo de um arquivo M3U8 a partir de uma URL HLS.
23
-
24
- Este método permite acessar, visualizar ou salvar playlists M3U8 utilizadas em transmissões de vídeo sob
25
- demanda.
26
-
27
- Args: url_m3u8 (str): A URL do arquivo M3U8 que você deseja acessar. headers (dict, optional): Cabeçalhos
28
- HTTP opcionais para a requisição. Se não forem fornecidos, serão usados cabeçalhos padrão. save_in_file (str,
29
- optional): Nome do arquivo para salvar o conteúdo M3U8. Se fornecido, o conteúdo da playlist será salvo no
30
- diretório atual com a extensão `.m3u8`. timeout (int, optional): Tempo máximo (em segundos) para aguardar uma
31
- resposta do servidor. O padrão é 20 segundos.
32
-
33
- Returns:
34
- str: O conteúdo do arquivo M3U8 como uma string se a requisição for bem-sucedida.
35
- None: Se a requisição falhar ou se o servidor não responder com sucesso.
36
-
37
- Raises:
38
- ValueError: Se a URL não for válida ou se os headers não forem um dicionário.
39
- ConnectionAbortedError: Se o servidor encerrar a conexão inesperadamente.
40
- ConnectionRefusedError: Se a conexão for recusada pelo servidor.
41
- TimeoutError: Se o tempo de espera pela resposta do servidor for excedido.
42
- ConnectionError: Se não for possível se conectar ao servidor por outros motivos.
43
-
44
- Example:
45
- ```python
46
- from m3u8_analyzer import M3u8Analyzer
47
-
48
- url = "https://example.com/playlist.m3u8"
49
- headers = {"User-Agent": "Mozilla/5.0"}
50
- playlist_content = M3u8Analyzer.get_m3u8(url, headers=headers, save_in_file="minha_playlist", timeout=30)
51
-
52
- if playlist_content:
53
- print("Playlist obtida com sucesso!")
54
- else:
55
- print("Falha ao obter a playlist.")
56
- ```
57
- """
58
-
59
- if headers:
60
- if not isinstance(headers, dict):
61
- raise M3u8Error("headers deve ser um dicionário válido!", errors=['headers not dict'])
62
- if not (url_m3u8.startswith('https://') or url_m3u8.startswith('http://')):
63
- raise M3u8Error(f"Este valor não se parece ser uma url válida!")
64
- try:
65
- time = 20
66
- respo = ''
67
- headers_default = {
68
- "Accept": "application/json, text/plain, */*",
69
- "Accept-Encoding": "gzip, deflate, br, zstd",
70
- "Accept-Language": "pt-BR,pt;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
71
- "Content-Length": "583",
72
- "Content-Type": "text/plain",
73
- "Sec-Fetch-Dest": "empty",
74
- "Sec-Fetch-Mode": "cors",
75
- "Sec-Fetch-Site": "same-origin",
76
- "Sec-Ch-Ua": "\"Not:A-Brand\";v=\"99\", \"Google Chrome\";v=\"118\", \"Chromium\";v=\"118\"",
77
- "Sec-Ch-Ua-Mobile": "?0",
78
- "Sec-Ch-Ua-Platform": "\"Windows\"",
79
- "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) "
80
- "Chrome/118.0.0.0 Safari/537.36"
81
- }
82
- session = requests.session()
83
- if timeout:
84
- time = timeout
85
- if not headers:
86
- headers = headers_default
87
- r = session.get(url_m3u8, timeout=time, headers=headers)
88
- if r.status_code == 200:
89
- # Verificar o conteúdo do arquivo
90
- if not "#EXTM3U" in r.text:
91
- raise M3u8Error("A URL fornecida não parece ser um arquivo M3U8 válido.")
92
- elif "#EXTM3U" in r.text:
93
- if save_in_file:
94
- local = os.getcwd()
95
- with open(fr"{local}\{save_in_file}.m3u8", 'a', encoding='utf-8') as e:
96
- e.write(r.text)
97
- return r.text
98
- else:
99
- return None
100
- else:
101
- return "NULL"
102
- except requests.exceptions.SSLError as e:
103
- raise M3u8NetworkingError(f"Erro SSL: {e}")
104
- except requests.exceptions.ProxyError as e:
105
- raise M3u8NetworkingError(f"Erro de proxy: {e}")
106
- except requests.exceptions.ConnectionError:
107
- raise M3u8NetworkingError("Erro: O servidor ou o servidor encerrou a conexão.")
108
- except requests.exceptions.HTTPError as e:
109
- raise M3u8NetworkingError(f"Erro HTTP: {e}")
110
- except requests.exceptions.Timeout:
111
- raise M3u8NetworkingError("Erro de tempo esgotado: A conexão com o servidor demorou muito para responder.")
112
- except requests.exceptions.TooManyRedirects:
113
- raise M3u8NetworkingError("Erro de redirecionamento: Muitos redirecionamentos.")
114
- except requests.exceptions.URLRequired:
115
- raise M3u8NetworkingError("Erro: URL é necessária para a solicitação.")
116
- except requests.exceptions.InvalidProxyURL as e:
117
- raise M3u8NetworkingError(f"Erro: URL de proxy inválida: {e}")
118
- except requests.exceptions.InvalidURL:
119
- raise M3u8NetworkingError("Erro: URL inválida fornecida.")
120
- except requests.exceptions.InvalidSchema:
121
- raise M3u8NetworkingError("Erro: URL inválida, esquema não suportado.")
122
- except requests.exceptions.MissingSchema:
123
- raise M3u8NetworkingError("Erro: URL inválida, esquema ausente.")
124
- except requests.exceptions.InvalidHeader as e:
125
- raise M3u8NetworkingError(f"Erro de cabeçalho inválido: {e}")
126
- except requests.exceptions.ChunkedEncodingError as e:
127
- raise M3u8NetworkingError(f"Erro de codificação em partes: {e}")
128
- except requests.exceptions.ContentDecodingError as e:
129
- raise M3u8NetworkingError(f"Erro de decodificação de conteúdo: {e}")
130
- except requests.exceptions.StreamConsumedError:
131
- raise M3u8NetworkingError("Erro: Fluxo de resposta já consumido.")
132
- except requests.exceptions.RetryError as e:
133
- raise M3u8NetworkingError(f"Erro de tentativa: {e}")
134
- except requests.exceptions.UnrewindableBodyError:
135
- raise M3u8NetworkingError("Erro: Corpo da solicitação não pode ser rebobinado.")
136
- except requests.exceptions.RequestException as e:
137
- raise M3u8NetworkingError(f"Erro de conexão: Não foi possível se conectar ao servidor. Detalhes: {e}")
138
- except requests.exceptions.BaseHTTPError as e:
139
- raise M3u8NetworkingError(f"Erro HTTP básico: {e}")
140
-
141
- @staticmethod
142
- def get_high_resolution(m3u8_content: str):
143
- """
144
- Obtém a maior resolução disponível em um arquivo M3U8 e o URL correspondente.
145
-
146
- Este método analisa o conteúdo de um arquivo M3U8 para identificar a maior resolução
147
- disponível entre os fluxos de vídeo listados. Também retorna o URL associado a essa
148
- maior resolução, se disponível.
149
-
150
- Args:
151
- m3u8_content (str): O conteúdo do arquivo M3U8 como uma string. Este conteúdo deve
152
- incluir as tags e atributos típicos de uma playlist HLS.
153
-
154
- Returns:
155
- tuple: Uma tupla contendo:
156
- - str: A maior resolução disponível no formato 'Largura x Altura' (ex.: '1920x1080').
157
- - str: O URL correspondente à maior resolução. Se o URL não for encontrado,
158
- retorna None.
159
- Se o tipo de playlist não contiver resoluções, retorna uma mensagem indicando
160
- o tipo de playlist.
161
-
162
- Raises:
163
- ValueError: Se o conteúdo do M3U8 não contiver resoluções e a função não conseguir
164
- determinar o tipo de playlist.
165
-
166
- Examples:
167
- ```python
168
- m3u8_content = '''
169
- #EXTM3U
170
- #EXT-X-STREAM-INF:BANDWIDTH=500000,RESOLUTION=640x360
171
- http://example.com/360p.m3u8
172
- #EXT-X-STREAM-INF:BANDWIDTH=1000000,RESOLUTION=1280x720
173
- http://example.com/720p.m3u8
174
- #EXT-X-STREAM-INF:BANDWIDTH=3000000,RESOLUTION=1920x1080
175
- http://example.com/1080p.m3u8
176
- '''
177
- result = M3u8Analyzer.get_high_resolution(m3u8_content)
178
- print(result) # Saída esperada: ('1920x1080', 'http://example.com/1080p.m3u8')
179
- ```
180
-
181
- ```python
182
- m3u8_content_no_resolutions = '''
183
- #EXTM3U
184
- #EXT-X-STREAM-INF:BANDWIDTH=500000
185
- http://example.com/360p.m3u8
186
- '''
187
- result = M3u8Analyzer.get_high_resolution(m3u8_content_no_resolutions)
188
- print(result) # Saída esperada: 'Playlist type: <TIPO DA PLAYLIST> not resolutions...'
189
- ```
190
- """
191
- resolutions = re.findall(r'RESOLUTION=(\d+x\d+)', m3u8_content)
192
- if not resolutions:
193
- tip = M3u8Analyzer.get_type_m3u8_content(m3u8_content=m3u8_content)
194
- return f"Playlist type: {Fore.LIGHTRED_EX}{tip}{Style.RESET_ALL} not resolutions..."
195
- max_resolution = max(resolutions, key=lambda res: int(res.split('x')[0]) * int(res.split('x')[1]))
196
- url = re.search(rf'#EXT-X-STREAM-INF:[^\n]*RESOLUTION={max_resolution}[^\n]*\n([^\n]+)', m3u8_content).group(1)
197
- if not url:
198
- return max_resolution, None
199
- if not max_resolution:
200
- return None, url
201
- else:
202
- return max_resolution, url
203
-
204
- @staticmethod
205
- def get_type_m3u8_content(m3u8_content: str) -> str:
206
- """
207
- Determina o tipo de conteúdo de um arquivo M3U8 (Master ou Segmentos).
208
-
209
- Este método analisa o conteúdo de um arquivo M3U8 para identificar se ele é do tipo
210
- 'Master', 'Master encrypted', 'Segments', 'Segments encrypted', 'Segments Master', ou
211
- 'Desconhecido'. A identificação é baseada na presença de tags e chaves específicas no
212
- conteúdo da playlist M3U8.
213
-
214
- Args:
215
- m3u8_content (str): O conteúdo do arquivo M3U8 como uma string. Pode ser uma URL ou o
216
- próprio conteúdo da playlist.
217
-
218
- Returns:
219
- str: O tipo de conteúdo identificado. Os possíveis valores são:
220
- - 'Master': Playlist mestre sem criptografia.
221
- - 'Master encrypted': Playlist mestre com criptografia.
222
- - 'Segments': Playlist de segmentos sem criptografia.
223
- - 'Segments encrypted': Playlist de segmentos com criptografia.
224
- - 'Segments .ts': Playlist de segmentos com URLs terminando em '.ts'.
225
- - 'Segments .m4s': Playlist de segmentos com URLs terminando em '.m4s'.
226
- - 'Segments Master': Playlist de segmentos com URLs variadas.
227
- - 'Desconhecido': Se o tipo não puder ser identificado.
228
-
229
- Examples:
230
- ```python
231
- m3u8_content_master = '''
232
- #EXTM3U
233
- #EXT-X-STREAM-INF:BANDWIDTH=500000
234
- http://example.com/master.m3u8
235
- '''
236
- result = M3u8Analyzer.get_type_m3u8_content(m3u8_content_master)
237
- print(result) # Saída esperada: 'Master'
238
-
239
- m3u8_content_master_encrypted = '''
240
- #EXTM3U
241
- #EXT-X-STREAM-INF:BANDWIDTH=500000
242
- #EXT-X-KEY:METHOD=AES-128,URI="http://example.com/key.key"
243
- http://example.com/master.m3u8
244
- '''
245
- result = M3u8Analyzer.get_type_m3u8_content(m3u8_content_master_encrypted)
246
- print(result) # Saída esperada: 'Master encrypted'
247
-
248
- m3u8_content_segments = '''
249
- #EXTM3U
250
- #EXTINF:10,
251
- http://example.com/segment1.ts
252
- #EXTINF:10,
253
- http://example.com/segment2.ts
254
- '''
255
- result = M3u8Analyzer.get_type_m3u8_content(m3u8_content_segments)
256
- print(result) # Saída esperada: 'Segments .ts'
257
-
258
- m3u8_content_unknown = '''
259
- #EXTM3U
260
- #EXTINF:10,
261
- http://example.com/unknown_segment
262
- '''
263
- result = M3u8Analyzer.get_type_m3u8_content(m3u8_content_unknown)
264
- print(result) # Saída esperada: 'Segments Master'
265
- ```
266
-
267
- Raises:
268
- Exception: Em caso de erro durante o processamento do conteúdo, o método retornará uma
269
- mensagem de erro descritiva.
270
- """
271
- try:
272
- conteudo = m3u8_content
273
- if '#EXT-X-STREAM-INF' in conteudo:
274
- if '#EXT-X-KEY' in conteudo:
275
- return 'Master encrypted'
276
- else:
277
- return 'Master'
278
- elif '#EXTINF' in conteudo:
279
- if '#EXT-X-KEY' in conteudo:
280
- return 'Segments encrypted'
281
- else:
282
- # Verifica se URLs dos segmentos possuem a extensão .ts ou .m4s
283
- segment_urls = re.findall(r'#EXTINF:[0-9.]+,\n([^\n]+)', conteudo)
284
- if all(url.endswith('.ts') for url in segment_urls):
285
- return 'Segments .ts'
286
- elif all(url.endswith('.m4s') for url in segment_urls):
287
- return 'Segments .m4s'
288
- else:
289
- return 'Segments Master'
290
- else:
291
- return 'Desconhecido'
292
- except re.error as e:
293
- raise M3u8FileError(f"Erro ao processar o conteúdo M3U8: {str(e)}")
294
- except Exception as e:
295
- raise M3u8FileError(f"Erro inesperado ao processar o conteúdo M3U8: {str(e)}")
296
-
297
- @staticmethod
298
- def get_player_playlist(m3u8_url: str) -> str:
299
- """
300
- Obtém o caminho do diretório base do arquivo M3U8, excluindo o nome do arquivo.
301
-
302
- Este método analisa a URL fornecida do arquivo M3U8 e retorna o caminho do diretório onde o arquivo M3U8 está localizado.
303
- A URL deve ser uma URL completa e o método irá extrair o caminho do diretório base.
304
-
305
- Args:
306
- m3u8_url (str): A URL completa do arquivo M3U8. Pode incluir o nome do arquivo e o caminho do diretório.
307
-
308
- Returns:
309
- str: O caminho do diretório onde o arquivo M3U8 está localizado. Se a URL não contiver um arquivo M3U8,
310
- retornará uma string vazia.
311
-
312
- Examples:
313
- ```python
314
- # Exemplo 1
315
- url = 'http://example.com/videos/playlist.m3u8'
316
- path = M3u8Analyzer.get_player_playlist(url)
317
- print(path) # Saída esperada: 'http://example.com/videos/'
318
-
319
- # Exemplo 2
320
- url = 'https://cdn.example.com/streams/segment.m3u8'
321
- path = M3u8Analyzer.get_player_playlist(url)
322
- print(path) # Saída esperada: 'https://cdn.example.com/streams/'
323
-
324
- # Exemplo 3
325
- url = 'https://example.com/playlist.m3u8'
326
- path = M3u8Analyzer.get_player_playlist(url)
327
- print(path) # Saída esperada: 'https://example.com/'
328
-
329
- # Exemplo 4
330
- url = 'https://example.com/videos/'
331
- path = M3u8Analyzer.get_player_playlist(url)
332
- print(path) # Saída esperada: ''
333
- ```
334
-
335
- """
336
- if m3u8_url.endswith('/'):
337
- m3u8_url = m3u8_url[:-1]
338
- partes = m3u8_url.split('/')
339
- for i, parte in enumerate(partes):
340
- if '.m3u8' in parte:
341
- return '/'.join(partes[:i]) + "/"
342
- return ''
343
-
344
- @staticmethod
345
- def get_audio_playlist(m3u8_content: str):
346
- """
347
- Extrai o URL da playlist de áudio de um conteúdo M3U8.
348
-
349
- Este método analisa o conteúdo fornecido de um arquivo M3U8 e retorna o URL da playlist de áudio incluída na playlist M3U8.
350
- O método busca a linha que contém a chave `#EXT-X-MEDIA` e extrai a URL associada ao áudio.
351
-
352
- Args:
353
- m3u8_content (str): Conteúdo da playlist M3U8 como uma string. Deve incluir informações sobre áudio se disponíveis.
354
-
355
- Returns:
356
- str: URL da playlist de áudio encontrada no conteúdo M3U8. Retorna `None` se a URL da playlist de áudio não for encontrada.
357
-
358
- Examples:
359
- ```python
360
- # Exemplo 1
361
- content = '''
362
- #EXTM3U
363
- #EXT-X-VERSION:3
364
- #EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio",NAME="English",DEFAULT=YES,URI="http://example.com/audio.m3u8"
365
- #EXT-X-STREAM-INF:BANDWIDTH=256000,AUDIO="audio"
366
- http://example.com/stream.m3u8
367
- '''
368
- url = M3u8Analyzer.get_audio_playlist(content)
369
- print(url) # Saída esperada: 'http://example.com/audio.m3u8'
370
-
371
- # Exemplo 2
372
- content = '''
373
- #EXTM3U
374
- #EXT-X-VERSION:3
375
- #EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio",NAME="French",DEFAULT=NO,URI="http://example.com/french_audio.m3u8"
376
- #EXT-X-STREAM-INF:BANDWIDTH=256000,AUDIO="audio"
377
- http://example.com/stream.m3u8
378
- '''
379
- url = M3u8Analyzer.get_audio_playlist(content)
380
- print(url) # Saída esperada: 'http://example.com/french_audio.m3u8'
381
-
382
- # Exemplo 3
383
- content = '''
384
- #EXTM3U
385
- #EXT-X-VERSION:3
386
- #EXT-X-STREAM-INF:BANDWIDTH=256000
387
- http://example.com/stream.m3u8
388
- '''
389
- url = M3u8Analyzer.get_audio_playlist(content)
390
- print(url) # Saída esperada: None
391
- ```
392
-
393
- """
394
- match = re.search(r'#EXT-X-MEDIA:.*URI="([^"]+)"(?:.*,IV=(0x[0-9A-Fa-f]+))?', m3u8_content)
395
- if match:
396
- return match.group(1)
397
- return None
398
-
399
- @staticmethod
400
- def get_segments(content: str, base_url: str) -> Dict[str, List[Tuple[str, str]]]:
401
- """
402
- Extrai URLs de segmentos de uma playlist M3U8 e fornece informações detalhadas sobre os segmentos.
403
-
404
- Este método analisa o conteúdo de uma playlist M3U8 para extrair URLs de segmentos, identificar resoluções associadas,
405
- e retornar um dicionário com informações sobre as URLs dos segmentos, a quantidade total de segmentos,
406
- e a ordem de cada URI. Completa URLs relativas com base na URL base da playlist.
407
-
408
- Args:
409
- content (str): Conteúdo da playlist M3U8 como uma string.
410
- base_url (str): A URL base da playlist M3U8 para completar os caminhos relativos dos segmentos.
411
-
412
- Returns:
413
- dict: Um dicionário com as seguintes chaves:
414
- - 'uris' (List[str]): Lista de URLs dos segmentos.
415
- - 'urls' (List[str]): Lista de URLs de stream extraídas do conteúdo.
416
- - 'len' (int): Contagem total de URLs de stream encontradas.
417
- - 'enumerated_uris' (List[Tuple[int, str]]): Lista de tuplas contendo a ordem e o URL de cada segmento.
418
- - 'resolutions' (Dict[str, str]): Dicionário mapeando resoluções para suas URLs correspondentes.
419
- - 'codecs' (List[str]): Lista de codecs identificados nas streams.
420
-
421
- Raises:
422
- ValueError: Se o conteúdo fornecido for uma URL em vez de uma string de conteúdo M3U8.
423
- """
424
- url_pattern = re.compile(r'^https?://', re.IGNORECASE)
425
-
426
- if url_pattern.match(content):
427
- raise ValueError("O conteúdo não deve ser uma URL, mas sim uma string de uma playlist M3U8.")
428
-
429
- if content == "NULL":
430
- raise ValueError("essa url não é de uma playlist m3u8!")
431
-
432
- # Separação das linhas da playlist, ignorando linhas vazias e comentários
433
- urls_segmentos = [linha for linha in content.splitlines() if linha and not linha.startswith('#')]
434
-
435
- # Completa as URLs relativas usando a base_url
436
- full_urls = [urljoin(base_url, url) for url in urls_segmentos]
437
-
438
- # Inicializa o dicionário para armazenar os dados dos segmentos
439
- data_segments = {
440
- 'uris': full_urls, # Armazena as URLs completas
441
- 'urls': [],
442
- 'len': 0,
443
- 'enumerated_uris': [(index + 1, url) for index, url in enumerate(full_urls)],
444
- 'resolutions': {},
445
- 'codecs': []
446
- }
447
-
448
- # Busca por resoluções na playlist e armazena suas URLs correspondentes
449
- resolution_pattern = r'RESOLUTION=(\d+x\d+)'
450
- resolutions = re.findall(resolution_pattern, content)
451
-
452
- codec_pattern = r'CODECS="([^"]+)"'
453
- codecs = re.findall(codec_pattern, content)
454
-
455
- for res in resolutions:
456
- match = re.search(rf'#EXT-X-STREAM-INF:[^\n]*RESOLUTION={re.escape(res)}[^\n]*\n([^\n]+)', content)
457
- if match:
458
- url = match.group(1)
459
- data_segments['urls'].append(urljoin(base_url, url)) # Completa a URL se for relativa
460
- data_segments['resolutions'][res] = urljoin(base_url, url)
461
-
462
- # Adiciona os codecs encontrados, evitando repetições
463
- for codec in codecs:
464
- if codec not in data_segments['codecs']:
465
- data_segments['codecs'].append(codec)
466
-
467
- # Adiciona a contagem de URLs de stream encontradas ao dicionário
468
- data_segments['len'] = len(data_segments['urls'])
469
-
470
- return data_segments
471
-
472
-
473
- class EncryptSuport:
474
- """
475
- suporte a operações de criptografia AES-128 e SAMPLE-AES relacionadas a M3U8.
476
- Fornece métodos para obter a URL da chave de criptografia e o IV (vetor de inicialização) associado,
477
- necessários para descriptografar conteúdos M3U8 protegidos por AES-128.
478
-
479
- Métodos:
480
- - get_url_key_m3u8: Extrai a URL da chave de criptografia e o IV de um conteúdo M3U8.
481
- """
482
-
483
- @staticmethod
484
- def get_url_key_m3u8(m3u8_content: str, player: str, headers=None):
485
- """
486
- Extrai a URL da chave de criptografia AES-128 e o IV (vetor de inicialização) de um conteúdo M3U8.
487
-
488
- Este método analisa o conteúdo M3U8 para localizar a URL da chave de criptografia e o IV, se disponível.
489
- Em seguida, faz uma requisição HTTP para obter a chave em formato hexadecimal.
490
-
491
- Args:
492
- m3u8_content (str): String contendo o conteúdo do arquivo M3U8.
493
- player (str): URL base para formar o URL completo da chave, se necessário.
494
- headers (dict, optional): Cabeçalhos HTTP opcionais para a requisição da chave. Se não fornecido,
495
- cabeçalhos padrão serão utilizados.
496
-
497
- Returns:
498
- dict: Um dicionário contendo as seguintes chaves:
499
- - 'key' (str): A chave de criptografia em hexadecimal.
500
- - 'iv' (str): O vetor de inicialização (IV) em hexadecimal, se disponível.
501
-
502
- Caso não seja possível extrair a URL da chave ou o IV, retorna None.
503
-
504
- Examples:
505
- ```python
506
- m3u8_content = '''
507
- #EXTM3U
508
- #EXT-X-KEY:METHOD=AES-128,URI="https://example.com/key.bin",IV=0x1234567890abcdef
509
- #EXTINF:10.0,
510
- http://example.com/segment1.ts
511
- '''
512
- player = "https://example.com"
513
- result = EncryptSuport.get_url_key_m3u8(m3u8_content, player)
514
- print(result)
515
- # Saída esperada:
516
- # {'key': 'aabbccddeeff...', 'iv': '1234567890abcdef'}
517
-
518
- # Com cabeçalhos personalizados
519
- headers = {
520
- "Authorization": "Bearer your_token"
521
- }
522
- result = EncryptSuport.get_url_key_m3u8(m3u8_content, player, headers=headers)
523
- print(result)
524
- ```
525
-
526
- Raises:
527
- requests.HTTPError: Se a requisição HTTP para a chave falhar.
528
- """
529
- pattern = r'#EXT-X-KEY:.*URI="([^"]+)"(?:.*,IV=(0x[0-9A-Fa-f]+))?'
530
- match = re.search(pattern, m3u8_content)
531
- data = {}
532
- if match:
533
- url_key = f"{player}{match.group(1)}"
534
- iv_hex = match.group(2)
535
- if not headers:
536
- headers_default = {
537
- "Accept": "application/json, text/plain, */*",
538
- "Accept-Encoding": "gzip, deflate, br, zstd",
539
- "Accept-Language": "pt-BR,pt;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
540
- "Content-Length": "583",
541
- "Content-Type": "text/plain",
542
- "Sec-Fetch-Dest": "empty",
543
- "Sec-Fetch-Mode": "cors",
544
- "Sec-Fetch-Site": "same-origin",
545
- "Sec-Ch-Ua": "\"Not:A-Brand\";v=\"99\", \"Google Chrome\";v=\"118\", \"Chromium\";v=\"118\"",
546
- "Sec-Ch-Ua-Mobile": "?0",
547
- "Sec-Ch-Ua-Platform": "\"Windows\"",
548
- "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)"
549
- "Chrome/118.0.0.0 Safari/537.36"
550
- }
551
- headers = headers_default
552
-
553
- try:
554
- resp = requests.get(url_key, headers=headers)
555
- resp.raise_for_status()
556
- key_bytes = resp.content
557
- key_hex = key_bytes.hex()
558
- data['key'] = key_hex
559
- if iv_hex:
560
- data['iv'] = iv_hex[2:] # Remove '0x' prefix
561
- return data
562
- except requests.exceptions.InvalidProxyURL as e:
563
- raise M3u8NetworkingError(f"Erro: URL de proxy inválida: {e}")
564
- except requests.exceptions.InvalidURL:
565
- raise M3u8NetworkingError("Erro: URL inválida fornecida.")
566
- except requests.exceptions.InvalidSchema:
567
- raise M3u8NetworkingError("Erro: URL inválida, esquema não suportado.")
568
- except requests.exceptions.MissingSchema:
569
- raise M3u8NetworkingError("Erro: URL inválida, esquema ausente.")
570
- except requests.exceptions.InvalidHeader as e:
571
- raise M3u8NetworkingError(f"Erro de cabeçalho inválido: {e}")
572
- except ValueError as e:
573
- raise M3u8FileError(f"Erro de valor: {e}")
574
- except requests.exceptions.ContentDecodingError as e:
575
- raise M3u8NetworkingError(f"Erro de decodificação de conteúdo: {e}")
576
- except requests.exceptions.BaseHTTPError as e:
577
- raise M3u8NetworkingError(f"Erro HTTP básico: {e}")
578
- except requests.exceptions.SSLError as e:
579
- raise M3u8NetworkingError(f"Erro SSL: {e}")
580
- except requests.exceptions.ProxyError as e:
581
- raise M3u8NetworkingError(f"Erro de proxy: {e}")
582
- except requests.exceptions.ConnectionError:
583
- raise M3u8NetworkingError("Erro: O servidor ou o servidor encerrou a conexão.")
584
- except requests.exceptions.HTTPError as e:
585
- raise M3u8NetworkingError(f"Erro HTTP: {e}")
586
- except requests.exceptions.Timeout:
587
- raise M3u8NetworkingError(
588
- "Erro de tempo esgotado: A conexão com o servidor demorou muito para responder.")
589
- except requests.exceptions.TooManyRedirects:
590
- raise M3u8NetworkingError("Erro de redirecionamento: Muitos redirecionamentos.")
591
- except requests.exceptions.URLRequired:
592
- raise M3u8NetworkingError("Erro: URL é necessária para a solicitação.")
593
- except requests.exceptions.ChunkedEncodingError as e:
594
- raise M3u8NetworkingError(f"Erro de codificação em partes: {e}")
595
- except requests.exceptions.StreamConsumedError:
596
- raise M3u8NetworkingError("Erro: Fluxo de resposta já consumido.")
597
- except requests.exceptions.RetryError as e:
598
- raise M3u8NetworkingError(f"Erro de tentativa: {e}")
599
- except requests.exceptions.UnrewindableBodyError:
600
- raise M3u8NetworkingError("Erro: Corpo da solicitação não pode ser rebobinado.")
601
- except requests.exceptions.RequestException as e:
602
- raise M3u8NetworkingError(
603
- f"Erro de conexão: Não foi possível se conectar ao servidor. Detalhes: {e}")
604
-
605
- else:
606
- return None
607
-
608
-
609
- class M3U8Playlist:
610
- """análise de maneira mais limpa de m3u8"""
611
-
612
- def __init__(self, url: str, headers: dict = None):
613
- self.__parsing = M3u8Analyzer()
614
- self.__url = url
615
- self.__version = ''
616
- self.__number_segments = []
617
- self.__uris = []
618
- self.__codecs = []
619
- self.__playlist_type = None
620
- self.__headers = headers
621
- if not (url.startswith('https://') or url.startswith('http://')):
622
- raise ValueError("O Manifesto deve ser uma URL HTTPS ou HTTP!")
623
-
624
- self.__load_playlist()
625
-
626
- def __load_playlist(self):
627
- """
628
- Método privado para carregar a playlist a partir de uma URL ou arquivo.
629
- """
630
- self.__parsing = M3u8Analyzer()
631
- self.__content = self.__parsing.get_m3u8(url_m3u8=self.__url, headers=self.__headers)
632
- # Simulação do carregamento de uma playlist para este exemplo:
633
- self.__uris = self.__parsing.get_segments(self.__content, self.__url).get('enumerated_uris')
634
- self.__number_segments = len(self.__uris)
635
- self.__playlist_type = self.__parsing.get_type_m3u8_content(self.__content)
636
- self.__version = self.__get_version_manifest(content=self.__content)
637
- self.__resolutions = self.__parsing.get_segments(self.__content, self.__url).get('resolutions')
638
- self.__codecs = self.__parsing.get_segments(self.__content, self.__url).get('codecs')
639
-
640
- def __get_version_manifest(self, content):
641
- """
642
- Obtém a versão do manifesto #EXTM em uma playlist m3u8.
643
- #EXT-X-VERSION:4
644
- #EXT-X-VERSION:3
645
- etc...
646
- :param content: Conteúdo da playlist m3u8.
647
- :return: A versão do manifesto encontrada ou None se não for encontrado.
648
- """
649
- # Expressão regular para encontrar o manifesto
650
- pattern = re.compile(r'#EXT-X-VERSION:(\d*)')
651
- match = pattern.search(content)
652
-
653
- if match:
654
- # Retorna a versão encontrada
655
- ver = f"#EXT-X-VERSION:{match.group(1)}"
656
- return ver
657
-
658
- else:
659
- return '#EXT-X-VERSION:Undefined' # Default para versão 1 se não houver número
660
-
661
- def get_codecs(self):
662
- """obter codecs na playlist"""
663
- return self.__codecs
664
-
665
- def info(self):
666
- """
667
- Retorna informações básicas sobre a playlist.
668
-
669
- Returns:
670
- dict: Informações sobre a URL, versão do manifesto, número de segmentos, tipo da playlist, se é criptografada e URIs dos segmentos.
671
- """
672
- info = {
673
- "url": self.__url,
674
- "version_manifest": self.__version,
675
- "number_of_segments": self.__number_segments,
676
- "playlist_type": self.__playlist_type,
677
- "codecs": self.__codecs,
678
- "encript": self.__is_encrypted(url=self.__url, headers=self.__headers),
679
- "uris": self.__uris,
680
- }
681
- return info
682
-
683
- def __is_encrypted(self, url, headers: dict = None):
684
- parser = M3u8Analyzer()
685
- m3u8_content = parser.get_m3u8(url)
686
- player = parser.get_player_playlist(url)
687
- try:
688
- cript = EncryptSuport.get_url_key_m3u8(m3u8_content=m3u8_content,
689
- player=player,
690
- headers=headers)
691
- except Exception as e:
692
- raise ValueError(f"erro {e}")
693
- return cript
694
-
695
- def this_encrypted(self):
696
- """
697
- Verifica se a playlist M3U8 está criptografada.
698
-
699
- Returns:
700
- bool: True se a playlist estiver criptografada, False caso contrário.
701
- """
702
- return self.__is_encrypted(url=self.__url, headers=self.__headers)
703
-
704
- def uris(self):
705
- """
706
- Retorna a lista de URIs dos segmentos.
707
-
708
- Returns:
709
- list: Lista de URIs dos segmentos.
710
- """
711
- return self.__uris
712
-
713
- def version_manifest(self):
714
- """
715
- Retorna a versão do manifesto da playlist M3U8.
716
-
717
- Returns:
718
- str: Versão do manifesto.
719
- """
720
- return self.__version
721
-
722
- def number_segments(self):
723
- """
724
- Retorna o número total de segmentos na playlist.
725
-
726
- Returns:
727
- int: Número de segmentos.
728
- """
729
- return self.__number_segments
730
-
731
- def playlist_type(self):
732
- """
733
- Retorna o tipo da playlist M3U8.
734
-
735
- Returns:
736
- str: Tipo da playlist.
737
- """
738
- return self.__playlist_type
739
-
740
- def get_resolutions(self):
741
- """
742
- Retorna as resoluções disponíveis na playlist M3U8.
743
-
744
- Returns:
745
- list: Lista de resoluções.
746
- """
747
- data = self.__resolutions
748
- resolutions = []
749
- for r in data:
750
- resolutions.append(r)
751
- return resolutions
752
-
753
- def filter_resolution(self, filtering: str):
754
- """
755
- Filtra e retorna a URL do segmento com a resolução especificada.
756
-
757
- Args:
758
- filtering (str): Resolução desejada (ex: '1920x1080').
759
-
760
- Returns:
761
- Optional[str]: URL do segmento correspondente à resolução, ou None se não encontrado.
762
- """
763
- data = self.__resolutions
764
- if filtering in data:
765
- return data.get(filtering)
766
- else:
767
- return None
768
-
769
-
770
- class Wrapper:
771
- """Classe para parsear playlists M3U8."""
772
-
773
- @staticmethod
774
- def parsing_m3u8(url: str, headers: dict = None) -> M3U8Playlist:
775
- """
776
- Cria uma instância de M3U8Playlist a partir de uma URL de playlist M3U8.
777
-
778
- Este método estático é utilizado para inicializar e retornar um objeto da classe `M3U8Playlist`,
779
- que fornece funcionalidades para análise e manipulação de playlists M3U8.
780
-
781
- Args:
782
- url (str): URL da playlist M3U8 que deve ser parseada.
783
- headers (Optional[dict]): Cabeçalhos HTTP adicionais para a requisição (opcional).
784
-
785
- Returns:
786
- M3U8Playlist: Uma instância da classe `M3U8Playlist` inicializada com a URL fornecida.
787
-
788
- Raises:
789
- ValueError: Se a URL não for uma URL válida.
790
-
791
- Examples:
792
- ```python
793
- url_playlist = "https://example.com/playlist.m3u8"
794
- headers = {
795
- "User-Agent": "CustomAgent/1.0"
796
- }
797
-
798
- playlist = ParsingM3u8.parsing_m3u8(url=url_playlist, headers=headers)
799
-
800
- print(playlist.info())
801
- ```
802
-
803
- Notes:
804
- - Certifique-se de que a URL fornecida é uma URL válida e acessível.
805
- - Se os cabeçalhos forem fornecidos, eles serão utilizados na requisição para obter o conteúdo da playlist.
806
- """
807
- return M3U8Playlist(url=url, headers=headers)