udemy-userAPI 0.3.5__py3-none-any.whl → 0.3.7__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -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)