udemy-userAPI 0.3.7__py3-none-any.whl → 0.3.8__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- animation_consoles/__init__.py +1 -0
- animation_consoles/animation.py +64 -0
- ffmpeg_for_python/__config__.py +118 -0
- ffmpeg_for_python/__init__.py +8 -0
- ffmpeg_for_python/__utils.py +78 -0
- ffmpeg_for_python/__version__.py +6 -0
- ffmpeg_for_python/exeptions.py +91 -0
- ffmpeg_for_python/ffmpeg.py +203 -0
- m3u8_analyzer/M3u8Analyzer.py +807 -0
- m3u8_analyzer/__init__.py +7 -0
- m3u8_analyzer/__version__.py +1 -0
- m3u8_analyzer/exeptions.py +82 -0
- udemy_userAPI/.cache/.udemy_userAPI +0 -0
- udemy_userAPI/__version__.py +1 -1
- udemy_userAPI/api.py +52 -30
- udemy_userAPI/bultins.py +6 -120
- udemy_userAPI/sections.py +42 -17
- udemy_userAPI/udemy.py +4 -13
- {udemy_userAPI-0.3.7.dist-info → udemy_userAPI-0.3.8.dist-info}/METADATA +2 -2
- udemy_userAPI-0.3.8.dist-info/RECORD +29 -0
- udemy_userAPI-0.3.7.dist-info/RECORD +0 -17
- {udemy_userAPI-0.3.7.dist-info → udemy_userAPI-0.3.8.dist-info}/LICENSE +0 -0
- {udemy_userAPI-0.3.7.dist-info → udemy_userAPI-0.3.8.dist-info}/WHEEL +0 -0
- {udemy_userAPI-0.3.7.dist-info → udemy_userAPI-0.3.8.dist-info}/top_level.txt +0 -0
@@ -0,0 +1 @@
|
|
1
|
+
from .animation import AnimationConsole
|
@@ -0,0 +1,64 @@
|
|
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)
|
@@ -0,0 +1,118 @@
|
|
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...")
|
@@ -0,0 +1,78 @@
|
|
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!")
|
@@ -0,0 +1,6 @@
|
|
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'
|
@@ -0,0 +1,91 @@
|
|
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
|
+
|
@@ -0,0 +1,203 @@
|
|
1
|
+
import os
|
2
|
+
import platform
|
3
|
+
import subprocess
|
4
|
+
import time
|
5
|
+
from typing import List
|
6
|
+
from .__config__ import Configurate
|
7
|
+
from .exeptions import wraper_erros, FFmpegExceptions
|
8
|
+
|
9
|
+
parser = Configurate()
|
10
|
+
parser.configure()
|
11
|
+
ffmpeg_binarie = os.path.join(parser.INSTALL_DIR, parser.FFMPEG_BINARY)
|
12
|
+
|
13
|
+
|
14
|
+
def ffmpeg(cls):
|
15
|
+
"""Decorador que instancia automaticamente a classe FFmpeg."""
|
16
|
+
|
17
|
+
def wrapper(*args, **kwargs):
|
18
|
+
# Cria e retorna uma instância da classe FFmpeg
|
19
|
+
instance = cls(*args, **kwargs)
|
20
|
+
return instance
|
21
|
+
|
22
|
+
return wrapper
|
23
|
+
|
24
|
+
|
25
|
+
@ffmpeg
|
26
|
+
class FFmpeg:
|
27
|
+
"""Wrapper para o binário FFmpeg, permitindo o uso de comandos FFmpeg via Python."""
|
28
|
+
|
29
|
+
def __init__(self):
|
30
|
+
# Verifica se o binário do FFmpeg existe
|
31
|
+
if not self.__verify_path(bin_path=ffmpeg_binarie, path_type='file'):
|
32
|
+
parser.install_bins()
|
33
|
+
self.__ffmpeg_path = str(ffmpeg_binarie)
|
34
|
+
self.__command = [self.__ffmpeg_path]
|
35
|
+
self.__overwrite_output = False
|
36
|
+
|
37
|
+
def args(self, arguments: List[str]) -> 'FFmpeg':
|
38
|
+
"""Adiciona múltiplos argumentos personalizados ao comando FFmpeg."""
|
39
|
+
if not all(isinstance(arg, str) for arg in arguments):
|
40
|
+
raise TypeError("All arguments should be provided as strings.")
|
41
|
+
self.__command.extend(arguments)
|
42
|
+
return self
|
43
|
+
|
44
|
+
def run(self, capture_output: bool = False):
|
45
|
+
"""Executa o comando FFmpeg construído.
|
46
|
+
|
47
|
+
Args:
|
48
|
+
capture_output (bool): Se verdadeiro, captura a saída e printa no console.
|
49
|
+
Se False, a saída é retornada assincronamente em tempo real.
|
50
|
+
|
51
|
+
Yields:
|
52
|
+
str: Cada linha da saída filtrada do FFmpeg quando capture_output é False.
|
53
|
+
"""
|
54
|
+
|
55
|
+
configs = self.__oculte_comands_your_system.get('startupinfo')
|
56
|
+
|
57
|
+
# Executa o comando utilizando subprocess
|
58
|
+
with subprocess.Popen(
|
59
|
+
self.__command,
|
60
|
+
startupinfo=configs,
|
61
|
+
stdout=subprocess.PIPE,
|
62
|
+
stderr=subprocess.PIPE,
|
63
|
+
stdin=subprocess.PIPE,
|
64
|
+
text=True,
|
65
|
+
bufsize=1, # Linha por linha
|
66
|
+
universal_newlines=True # Garante que novas linhas sejam interpretadas corretamente
|
67
|
+
) as process:
|
68
|
+
try:
|
69
|
+
# Lê a saída de erro padrão (stderr) em tempo real
|
70
|
+
for linha in process.stderr:
|
71
|
+
time.sleep(0.01) # Simulação de latência, opcional
|
72
|
+
|
73
|
+
# Filtra a linha de erro padrão
|
74
|
+
linha_filtrada = wraper_erros(linha)
|
75
|
+
|
76
|
+
if linha_filtrada:
|
77
|
+
# Se houver um erro detectado, o processo é encerrado
|
78
|
+
process.terminate()
|
79
|
+
raise FFmpegExceptions(message=f'Erro na execução do ffmpeg: "{linha_filtrada}"')
|
80
|
+
else:
|
81
|
+
if capture_output:
|
82
|
+
# Se `capture_output` estiver ativado, imprime a saída
|
83
|
+
print(linha.strip())
|
84
|
+
else:
|
85
|
+
# Retorna a linha assincronamente quando capture_output é False
|
86
|
+
yield linha.strip()
|
87
|
+
|
88
|
+
# Aguarda a conclusão do processo
|
89
|
+
process.wait()
|
90
|
+
|
91
|
+
except Exception as e:
|
92
|
+
process.terminate()
|
93
|
+
raise FFmpegExceptions(message=f'Erro no processo FFmpeg: {str(e)}')
|
94
|
+
|
95
|
+
def input(self, file_path: str) -> 'FFmpeg':
|
96
|
+
"""Define o arquivo de entrada para o FFmpeg."""
|
97
|
+
cmd = ['-i', file_path]
|
98
|
+
self.args(cmd)
|
99
|
+
return self
|
100
|
+
|
101
|
+
def output(self, output_path: str) -> 'FFmpeg':
|
102
|
+
"""Define o arquivo de saída para o FFmpeg e verifica se a sobrescrita está permitida."""
|
103
|
+
if os.path.exists(output_path):
|
104
|
+
if not self.__overwrite_output:
|
105
|
+
raise FFmpegExceptions(f"O arquivo de saída '{output_path}' já existe! Use 'overwrite_output' para "
|
106
|
+
f"sobrescrevê-lo.")
|
107
|
+
|
108
|
+
# Adiciona o arquivo de saída ao comando
|
109
|
+
cmd = [output_path]
|
110
|
+
self.args(cmd)
|
111
|
+
return self
|
112
|
+
|
113
|
+
@property
|
114
|
+
def overwrite_output(self):
|
115
|
+
"""
|
116
|
+
Adiciona o parâmetro '-y' ao comando FFmpeg, o que permite sobrescrever o arquivo de saída
|
117
|
+
caso ele já exista.
|
118
|
+
|
119
|
+
Importante: Esta propriedade deve ser definida antes dos parâmetros de entrada e saída
|
120
|
+
para garantir que o comando FFmpeg seja construído corretamente. Caso contrário, o comando
|
121
|
+
pode não ser executado como esperado.
|
122
|
+
|
123
|
+
Returns:
|
124
|
+
FFmpeg: Retorna a instância atual para encadeamento de métodos.
|
125
|
+
"""
|
126
|
+
self.__overwrite_output = True
|
127
|
+
cmd = ['-y']
|
128
|
+
self.args(cmd)
|
129
|
+
return self
|
130
|
+
|
131
|
+
@property
|
132
|
+
def hide_banner(self) -> 'FFmpeg':
|
133
|
+
"""oculta o baner do ffmpeg"""
|
134
|
+
self.__command.extend(['-hide_banner'])
|
135
|
+
return self
|
136
|
+
|
137
|
+
@property
|
138
|
+
def copy(self) -> 'FFmpeg':
|
139
|
+
"""Adiciona o parâmetro '-c copy"""
|
140
|
+
self.__command.extend(['-c', 'copy'])
|
141
|
+
return self
|
142
|
+
|
143
|
+
@property
|
144
|
+
def copy_codecs(self):
|
145
|
+
"""para remuxar"""
|
146
|
+
self.__command.extend(['-c:a', 'copy', '-c:v', 'copy'])
|
147
|
+
return self
|
148
|
+
|
149
|
+
def reset_ffmpeg(self) -> 'FFmpeg':
|
150
|
+
"""Reseta o comando para reutilização do objeto,isso é necessário em caso de uso em loops,
|
151
|
+
a cada execução execute o reset para ter certeza que estar limpo o cache de comandos"""
|
152
|
+
self.__command = [self.__ffmpeg_path]
|
153
|
+
return self
|
154
|
+
|
155
|
+
@staticmethod
|
156
|
+
def __verify_path(bin_path, path_type: str) -> bool:
|
157
|
+
"""
|
158
|
+
Verifica se um caminho de arquivo ou diretório existe.
|
159
|
+
|
160
|
+
Args:
|
161
|
+
bin_path : O caminho a ser verificado.
|
162
|
+
path_type (str): O tipo de caminho ('file' para arquivo, 'dir' para diretório).
|
163
|
+
|
164
|
+
Returns:
|
165
|
+
bool: True se o caminho existir e for do tipo especificado, False caso contrário.
|
166
|
+
"""
|
167
|
+
if path_type == 'file':
|
168
|
+
return os.path.isfile(bin_path)
|
169
|
+
elif path_type == 'dir':
|
170
|
+
return os.path.isdir(bin_path)
|
171
|
+
else:
|
172
|
+
raise ValueError("Invalid path_type. Use 'file' or 'dir'.")
|
173
|
+
|
174
|
+
@property
|
175
|
+
def __oculte_comands_your_system(self) -> dict:
|
176
|
+
"""Identifica o sistema do usuário e cria um dicionário de parâmetros para ocultar saídas de janelas e do
|
177
|
+
terminal."""
|
178
|
+
system_user = platform.system()
|
179
|
+
startupinfo_options = {}
|
180
|
+
|
181
|
+
if system_user == "Windows":
|
182
|
+
# Configuração específica para ocultar o terminal no Windows
|
183
|
+
startupinfo = subprocess.STARTUPINFO()
|
184
|
+
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
|
185
|
+
startupinfo.wShowWindow = subprocess.SW_HIDE
|
186
|
+
startupinfo_options['startupinfo'] = startupinfo
|
187
|
+
|
188
|
+
# Definindo stdout, stderr e stdin para DEVNULL para esconder saídas
|
189
|
+
startupinfo_options['stdout'] = subprocess.DEVNULL
|
190
|
+
startupinfo_options['stderr'] = subprocess.DEVNULL
|
191
|
+
startupinfo_options['stdin'] = subprocess.DEVNULL
|
192
|
+
|
193
|
+
elif system_user in ["Linux", "Darwin"]:
|
194
|
+
# Para Linux e macOS, ocultar stdout, stderr e stdin
|
195
|
+
startupinfo_options['stdout'] = subprocess.DEVNULL
|
196
|
+
startupinfo_options['stderr'] = subprocess.DEVNULL
|
197
|
+
startupinfo_options['stdin'] = subprocess.DEVNULL
|
198
|
+
|
199
|
+
else:
|
200
|
+
# Exceção para sistemas não suportados
|
201
|
+
raise NotImplementedError(f"O sistema {system_user} não é suportado para ocultação de comandos.")
|
202
|
+
|
203
|
+
return startupinfo_options
|