ffmpeg-for-python 0.3__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.
- ffmpeg_for_python/__config__.py +147 -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-bin/__init__.py +0 -0
- ffmpeg_for_python/ffmpeg-bin/readme.md +0 -0
- ffmpeg_for_python/ffmpeg.py +178 -0
- ffmpeg_for_python-0.3.dist-info/METADATA +75 -0
- ffmpeg_for_python-0.3.dist-info/RECORD +12 -0
- ffmpeg_for_python-0.3.dist-info/WHEEL +5 -0
- ffmpeg_for_python-0.3.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import struct
|
|
2
|
+
import sys
|
|
3
|
+
import os
|
|
4
|
+
import requests
|
|
5
|
+
import zipfile
|
|
6
|
+
import shutil
|
|
7
|
+
import stat
|
|
8
|
+
from .exeptions import *
|
|
9
|
+
from .__utils import URL_PLATAFOMR,system
|
|
10
|
+
|
|
11
|
+
lib_name = 'ffmpeg_for_python'
|
|
12
|
+
URL_BASE_REPO = "https://raw.githubusercontent.com/PauloCesar-dev404/binarios/main/"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Configurate:
|
|
18
|
+
"""Configura variáveis de ambiente no ambiente virtual ou globalmente."""
|
|
19
|
+
|
|
20
|
+
def __init__(self):
|
|
21
|
+
self.VERSION = self.__read_version
|
|
22
|
+
self.FFMPEG_URL = os.getenv('FFMPEG_URL')
|
|
23
|
+
self.FFMPEG_BINARY = os.getenv('FFMPEG_BINARY')
|
|
24
|
+
PATH = os.path.join(self.is_venv, lib_name, 'ffmpeg-bin')
|
|
25
|
+
os.makedirs(PATH, exist_ok=True)
|
|
26
|
+
dirpath = PATH
|
|
27
|
+
self.INSTALL_DIR = os.getenv('INSTALL_DIR', dirpath)
|
|
28
|
+
self.VENV_PATH = os.getenv('VENV_PATH', self.INSTALL_DIR)
|
|
29
|
+
self.configure()
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
def is_venv(self):
|
|
33
|
+
"""Verifica se o script está sendo executado em um ambiente virtual e retorna o diretório de bibliotecas globais.
|
|
34
|
+
Se estiver em um ambiente virtual, retorna o diretório de bibliotecas do ambiente virtual. Caso contrário, retorna
|
|
35
|
+
o diretório de bibliotecas globais do Python global."""
|
|
36
|
+
if hasattr(sys, 'real_prefix') or (hasattr(sys, 'base_prefix') and sys.base_prefix != sys.prefix):
|
|
37
|
+
# Retorna o diretório de bibliotecas do ambiente virtual
|
|
38
|
+
return os.path.join(os.path.dirname(os.path.abspath(sys.executable)), 'Lib',
|
|
39
|
+
'site-packages') if os.name == 'nt' else os.path.join \
|
|
40
|
+
(os.path.dirname(os.path.abspath(sys.executable)), 'lib',
|
|
41
|
+
'python{0.major}.{0.minor}'.format(sys.version_info), 'site-packages')
|
|
42
|
+
else:
|
|
43
|
+
# Retorna o diretório de bibliotecas globais do Python global
|
|
44
|
+
return os.path.join(os.path.dirname(os.path.abspath(sys.executable)), 'Lib',
|
|
45
|
+
'site-packages') if os.name == 'nt' else os.path.join \
|
|
46
|
+
(os.path.dirname(os.path.abspath(sys.executable)), 'lib',
|
|
47
|
+
'python{0.major}.{0.minor}'.format(sys.version_info), 'site-packages')
|
|
48
|
+
|
|
49
|
+
def configure(self):
|
|
50
|
+
"""Configura as variáveis de ambiente com base no sistema operacional."""
|
|
51
|
+
if not self.FFMPEG_URL or not self.FFMPEG_BINARY:
|
|
52
|
+
platform_name = system
|
|
53
|
+
if platform_name == 'Windows':
|
|
54
|
+
self.FFMPEG_URL = URL_PLATAFOMR
|
|
55
|
+
self.FFMPEG_BINARY = 'ffmpeg.exe'
|
|
56
|
+
elif platform_name == 'Linux':
|
|
57
|
+
self.FFMPEG_URL = URL_PLATAFOMR
|
|
58
|
+
self.FFMPEG_BINARY = 'ffmpeg'
|
|
59
|
+
else:
|
|
60
|
+
raise DeprecationWarning(f"Arquitetura '{platform_name}' ainda não suportada...\n\n"
|
|
61
|
+
f"Versão atual da lib: {self.VERSION}")
|
|
62
|
+
os.environ['FFMPEG_URL'] = self.FFMPEG_URL
|
|
63
|
+
os.environ['FFMPEG_BINARY'] = self.FFMPEG_BINARY
|
|
64
|
+
|
|
65
|
+
if not os.getenv('INSTALL_DIR'):
|
|
66
|
+
os.environ['INSTALL_DIR'] = self.INSTALL_DIR
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def __read_version(self):
|
|
70
|
+
"""Lê a versão do arquivo __version__.py."""
|
|
71
|
+
version_file = os.path.join(os.path.dirname(os.path.abspath(__file__)).split('.')[0], '__version__.py')
|
|
72
|
+
if os.path.isfile(version_file):
|
|
73
|
+
with open(version_file, 'r') as file:
|
|
74
|
+
version_line = file.readline().strip()
|
|
75
|
+
if version_line.startswith('__version__'):
|
|
76
|
+
return version_line.split('=')[1].strip().strip("'")
|
|
77
|
+
return 'Unknown Version'
|
|
78
|
+
|
|
79
|
+
def __download_file(self, url: str, local_filename: str):
|
|
80
|
+
"""Baixa um arquivo do URL para o caminho local especificado."""
|
|
81
|
+
try:
|
|
82
|
+
response = requests.get(url, stream=True)
|
|
83
|
+
response.raise_for_status()
|
|
84
|
+
total_length = int(response.headers.get('content-length', 0))
|
|
85
|
+
|
|
86
|
+
with open(local_filename, 'wb') as f:
|
|
87
|
+
start_time = time.time()
|
|
88
|
+
downloaded = 0
|
|
89
|
+
|
|
90
|
+
for data in response.iter_content(chunk_size=4096):
|
|
91
|
+
downloaded += len(data)
|
|
92
|
+
f.write(data)
|
|
93
|
+
|
|
94
|
+
elapsed_time = time.time() - start_time
|
|
95
|
+
elapsed_time = max(elapsed_time, 0.001)
|
|
96
|
+
speed_kbps = (downloaded / 1024) / elapsed_time
|
|
97
|
+
percent_done = (downloaded / total_length) * 100
|
|
98
|
+
|
|
99
|
+
sys.stdout.write(
|
|
100
|
+
f"\rBaixando Binários do ffmpeg: {percent_done:.2f}% | Velocidade: {speed_kbps:.2f} KB/s | "
|
|
101
|
+
f"Tempo decorrido: {int(elapsed_time)}s")
|
|
102
|
+
sys.stdout.flush()
|
|
103
|
+
sys.stdout.write("\nDownload completo.\n")
|
|
104
|
+
sys.stdout.flush()
|
|
105
|
+
|
|
106
|
+
except requests.RequestException as e:
|
|
107
|
+
raise Exception(f"Erro durante o download: {e}")
|
|
108
|
+
|
|
109
|
+
def __extract_zip(self, zip_path: str, extract_to: str):
|
|
110
|
+
"""Descompacta o arquivo ZIP no diretório especificado."""
|
|
111
|
+
try:
|
|
112
|
+
with zipfile.ZipFile(zip_path, 'r') as zip_ref:
|
|
113
|
+
zip_ref.extractall(extract_to)
|
|
114
|
+
except zipfile.BadZipFile as e:
|
|
115
|
+
sys.stderr.write(f"Erro ao descompactar o arquivo: {e}\n")
|
|
116
|
+
raise
|
|
117
|
+
finally:
|
|
118
|
+
os.remove(zip_path)
|
|
119
|
+
|
|
120
|
+
def remove_file(self, file_path: str):
|
|
121
|
+
"""Remove o arquivo ou diretório especificado."""
|
|
122
|
+
if os.path.exists(file_path):
|
|
123
|
+
try:
|
|
124
|
+
shutil.rmtree(file_path, onerror=self.handle_remove_readonly)
|
|
125
|
+
except Exception as e:
|
|
126
|
+
print(f"Erro ao remover {file_path}: {e}")
|
|
127
|
+
raise
|
|
128
|
+
|
|
129
|
+
def install_bins(self):
|
|
130
|
+
"""Instala o ffmpeg baixando e descompactando o binário apropriado."""
|
|
131
|
+
zip_path = os.path.join(self.INSTALL_DIR, "ffmpeg.zip")
|
|
132
|
+
os.makedirs(self.INSTALL_DIR, exist_ok=True)
|
|
133
|
+
self.__download_file(self.FFMPEG_URL, zip_path)
|
|
134
|
+
self.__extract_zip(zip_path, self.INSTALL_DIR)
|
|
135
|
+
self.remove_file(zip_path)
|
|
136
|
+
bina = os.path.join(self.INSTALL_DIR, self.FFMPEG_BINARY)
|
|
137
|
+
os.environ["PATH"] += os.pathsep + self.INSTALL_DIR
|
|
138
|
+
return
|
|
139
|
+
|
|
140
|
+
def handle_remove_readonly(self, func, path, exc_info):
|
|
141
|
+
"""Callback para lidar com arquivos somente leitura."""
|
|
142
|
+
os.chmod(path, stat.S_IWRITE)
|
|
143
|
+
func(path)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
if __name__ == "__main__":
|
|
147
|
+
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'
|
|
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
|
+
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import platform
|
|
3
|
+
import subprocess
|
|
4
|
+
import time
|
|
5
|
+
from typing import List, Optional
|
|
6
|
+
from .exeptions import wraper_erros, FFmpegExceptions
|
|
7
|
+
from .__config__ import Configurate
|
|
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 padrão e de erro.
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
Optional[str]: A saída do comando, se capturada. Caso contrário, None.
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
configs = self.__oculte_comands_your_system.get('startupinfo')
|
|
55
|
+
# Armazenar a saída completa, se for necessário capturá-la
|
|
56
|
+
output = []
|
|
57
|
+
|
|
58
|
+
# Executa o comando utilizando subprocess
|
|
59
|
+
with subprocess.Popen(
|
|
60
|
+
self.__command,
|
|
61
|
+
startupinfo=configs,
|
|
62
|
+
stdout=subprocess.PIPE,
|
|
63
|
+
stderr=subprocess.PIPE,
|
|
64
|
+
stdin=subprocess.PIPE,
|
|
65
|
+
text=True,
|
|
66
|
+
bufsize=1, # Linha por linha
|
|
67
|
+
universal_newlines=True # Garante que novas linhas sejam interpretadas corretamente
|
|
68
|
+
) as process:
|
|
69
|
+
# Lê a saída de erro padrão (stderr) em tempo real
|
|
70
|
+
for linha in process.stderr:
|
|
71
|
+
time.sleep(0.01)
|
|
72
|
+
linha_filtrada = wraper_erros(linha)
|
|
73
|
+
if linha_filtrada:
|
|
74
|
+
process.terminate()
|
|
75
|
+
raise FFmpegExceptions(message=f'Erro na execução do ffmpeg: "{linha_filtrada}"')
|
|
76
|
+
else:
|
|
77
|
+
if capture_output:
|
|
78
|
+
print(linha.strip())
|
|
79
|
+
|
|
80
|
+
# Espera o processo terminar e captura a saída padrão (stdout)
|
|
81
|
+
stdout, stderr = process.communicate()
|
|
82
|
+
|
|
83
|
+
if capture_output:
|
|
84
|
+
output.extend(stdout.splitlines())
|
|
85
|
+
return '\n'.join(output)
|
|
86
|
+
else:
|
|
87
|
+
return stdout.strip() if stdout else None
|
|
88
|
+
|
|
89
|
+
def input(self, file_path: str) -> 'FFmpeg':
|
|
90
|
+
"""Define o arquivo de entrada para o FFmpeg."""
|
|
91
|
+
cmd = ['-i', file_path]
|
|
92
|
+
self.args(cmd)
|
|
93
|
+
return self
|
|
94
|
+
|
|
95
|
+
def output(self, output_path: str) -> 'FFmpeg':
|
|
96
|
+
"""Define o arquivo de saída para o FFmpeg e verifica se a sobrescrita está permitida."""
|
|
97
|
+
if os.path.exists(output_path):
|
|
98
|
+
if not self.__overwrite_output:
|
|
99
|
+
raise FFmpegExceptions(f"O arquivo de saída '{output_path}' já existe! Use 'overwrite_output' para "
|
|
100
|
+
f"sobrescrevê-lo.")
|
|
101
|
+
|
|
102
|
+
# Adiciona o arquivo de saída ao comando
|
|
103
|
+
cmd = [output_path]
|
|
104
|
+
self.args(cmd)
|
|
105
|
+
return self
|
|
106
|
+
|
|
107
|
+
@property
|
|
108
|
+
def overwrite_output(self):
|
|
109
|
+
"""
|
|
110
|
+
Adiciona o parâmetro '-y' ao comando FFmpeg, o que permite sobrescrever o arquivo de saída
|
|
111
|
+
caso ele já exista.
|
|
112
|
+
|
|
113
|
+
Importante: Esta propriedade deve ser definida antes dos parâmetros de entrada e saída
|
|
114
|
+
para garantir que o comando FFmpeg seja construído corretamente. Caso contrário, o comando
|
|
115
|
+
pode não ser executado como esperado.
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
FFmpeg: Retorna a instância atual para encadeamento de métodos.
|
|
119
|
+
"""
|
|
120
|
+
self.__overwrite_output = True
|
|
121
|
+
cmd = ['-y']
|
|
122
|
+
self.args(cmd)
|
|
123
|
+
return self
|
|
124
|
+
|
|
125
|
+
@property
|
|
126
|
+
def copy(self) -> 'FFmpeg':
|
|
127
|
+
"""Adiciona o parâmetro '-c copy' ao comando FFm"""
|
|
128
|
+
self.__command.extend(['-c', 'copy'])
|
|
129
|
+
return self
|
|
130
|
+
|
|
131
|
+
@property
|
|
132
|
+
def reset(self) -> 'FFmpeg':
|
|
133
|
+
"""Reseta o comando para reutilização do objeto."""
|
|
134
|
+
self.__command = [self.__ffmpeg_path]
|
|
135
|
+
return self
|
|
136
|
+
|
|
137
|
+
@staticmethod
|
|
138
|
+
def __verify_path(bin_path, path_type: str) -> bool:
|
|
139
|
+
"""
|
|
140
|
+
Verifica se um caminho de arquivo ou diretório existe.
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
bin_path : O caminho a ser verificado.
|
|
144
|
+
path_type (str): O tipo de caminho ('file' para arquivo, 'dir' para diretório).
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
bool: True se o caminho existir e for do tipo especificado, False caso contrário.
|
|
148
|
+
"""
|
|
149
|
+
if path_type == 'file':
|
|
150
|
+
return os.path.isfile(bin_path)
|
|
151
|
+
elif path_type == 'dir':
|
|
152
|
+
return os.path.isdir(bin_path)
|
|
153
|
+
else:
|
|
154
|
+
raise ValueError("Invalid path_type. Use 'file' or 'dir'.")
|
|
155
|
+
|
|
156
|
+
@property
|
|
157
|
+
def __oculte_comands_your_system(self):
|
|
158
|
+
"""Identifica o sistema do usuário e cria um dicionário de parâmetros para ocultar saídas de janelas."""
|
|
159
|
+
system_user = platform.system()
|
|
160
|
+
startupinfo_options = {}
|
|
161
|
+
|
|
162
|
+
if system_user == "Windows":
|
|
163
|
+
startupinfo = subprocess.STARTUPINFO()
|
|
164
|
+
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
|
|
165
|
+
startupinfo.wShowWindow = subprocess.SW_HIDE
|
|
166
|
+
startupinfo_options['startupinfo'] = startupinfo
|
|
167
|
+
|
|
168
|
+
elif system_user in ["Linux", "Darwin"]:
|
|
169
|
+
startupinfo_options['stdout'] = subprocess.DEVNULL
|
|
170
|
+
startupinfo_options['stderr'] = subprocess.DEVNULL
|
|
171
|
+
startupinfo_options['stdin'] = subprocess.DEVNULL
|
|
172
|
+
|
|
173
|
+
else:
|
|
174
|
+
raise NotImplementedError(f"System {system_user} not supported")
|
|
175
|
+
|
|
176
|
+
return startupinfo_options
|
|
177
|
+
|
|
178
|
+
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: ffmpeg-for-python
|
|
3
|
+
Version: 0.3
|
|
4
|
+
Summary: Wrapper para o binário FFmpeg
|
|
5
|
+
Author: PauloCesar-dev404
|
|
6
|
+
Author-email: paulocesar0073dev404@gmail.com
|
|
7
|
+
License: MIT
|
|
8
|
+
Project-URL: Código Fonte, https://raw.githubusercontent.com/PauloCesar-dev404/ffmpeg-for-python/main/ffmpeg_for_python-0.3.tar.gz
|
|
9
|
+
Project-URL: lib, https://raw.githubusercontent.com/PauloCesar-dev404/ffmpeg-for-python/main/ffmpeg_for_python-0.3-py3-none-any.whl
|
|
10
|
+
Project-URL: GitHub, https://github.com/PauloCesar-dev404/ffmpeg-for-python
|
|
11
|
+
Project-URL: Bugs/Melhorias, https://github.com/PauloCesar-dev404/ffmpeg-for-python/issues
|
|
12
|
+
Project-URL: Documentação, https://github.com/PauloCesar-dev404/ffmpeg-for-python/wiki
|
|
13
|
+
Keywords: ffmpeg
|
|
14
|
+
Platform: any
|
|
15
|
+
Requires: requests
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
|
|
18
|
+
# ffmpeg-for-python
|
|
19
|
+
|
|
20
|
+
Wrapper para o binário FFmpeg, permitindo o uso de comandos FFmpeg via Python. Para saber como usar FFmpeg, consulte a documentação no [site oficial](https://ffmpeg.org/ffmpeg.html)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
**conversão básica**
|
|
27
|
+
```python
|
|
28
|
+
|
|
29
|
+
from ffmpeg_for_python import FFmpeg, FFmpegExceptions
|
|
30
|
+
|
|
31
|
+
# Cria uma instância do wrapper FFmpeg
|
|
32
|
+
ffmpeg = FFmpeg()
|
|
33
|
+
# Caminho do arquivo de entrada e saída
|
|
34
|
+
input_video = 'input_video.mp4'
|
|
35
|
+
output_video = 'output_video.mkv'
|
|
36
|
+
|
|
37
|
+
# Define o arquivo de entrada e o arquivo de saída
|
|
38
|
+
(ffmpeg
|
|
39
|
+
.overwrite_output # Sobrescrever se existir
|
|
40
|
+
.input(input_video) # Vídeo de entrada
|
|
41
|
+
.output(output_video) # Vídeo final
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
# Executa o comando FFmpeg e exibe a saída
|
|
45
|
+
try:
|
|
46
|
+
ffmpeg.run(capture_output=True)
|
|
47
|
+
except FFmpegExceptions as e:
|
|
48
|
+
print("Erro ao executar FFmpeg:", e)
|
|
49
|
+
```
|
|
50
|
+
---
|
|
51
|
+
**remux audio e video**
|
|
52
|
+
```python
|
|
53
|
+
from ffmpeg_for_python import FFmpeg, FFmpegExceptions
|
|
54
|
+
# Cria uma instância do wrapper FFmpeg
|
|
55
|
+
ffmpeg = FFmpeg()
|
|
56
|
+
# Caminho dos arquivos de entrada e saída
|
|
57
|
+
input_video = 'input_video.mp4'
|
|
58
|
+
input_audio = 'input_audio.mp4'
|
|
59
|
+
output_video = 'output_video.mp4'
|
|
60
|
+
# Define os arquivos de entrada e o arquivo de saída
|
|
61
|
+
(ffmpeg
|
|
62
|
+
.overwrite_output # Sobrescrever se existir
|
|
63
|
+
.input(input_video) # Vídeo de entrada
|
|
64
|
+
.input(input_audio) # Áudio
|
|
65
|
+
.args(arguments=['-c:a', 'copy', '-c:v', 'copy']) # Parâmetros de cópia de áudio e vídeo
|
|
66
|
+
.output(output_video) # Vídeo final
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
# Executa o comando FFmpeg e exibe a saída
|
|
70
|
+
try:
|
|
71
|
+
ffmpeg.run(capture_output=True)
|
|
72
|
+
except FFmpegExceptions as e:
|
|
73
|
+
print("Erro ao executar FFmpeg:", e)
|
|
74
|
+
```
|
|
75
|
+
---
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
ffmpeg_for_python/__config__.py,sha256=B2LF1nf7v0FcJ3DDKCZRVPHUodU4XNfvnJwuIK0gF2U,6548
|
|
2
|
+
ffmpeg_for_python/__init__.py,sha256=-BMtoX8Yof_pnHra2OzoV3faxMubpMvUedMy8TqI8dc,214
|
|
3
|
+
ffmpeg_for_python/__utils.py,sha256=Qy3J5f4lOIPcSNbTwiawfiHjYPdZ_tq7hafStnnqwA4,3263
|
|
4
|
+
ffmpeg_for_python/__version__.py,sha256=uMlC0HJUZrkivMWKDpg07pgatHEw9b_n8TZevsGw4mk,420
|
|
5
|
+
ffmpeg_for_python/exeptions.py,sha256=tg-TBdaq_NHxZOCAhkMttzwtJVILPAQPLOKqofe5PPA,3627
|
|
6
|
+
ffmpeg_for_python/ffmpeg.py,sha256=KExeNnI1Qjq-lsQsc-ZeEY7gPCmQaLaoHE6RuBKMAk8,6536
|
|
7
|
+
ffmpeg_for_python/ffmpeg-bin/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
+
ffmpeg_for_python/ffmpeg-bin/readme.md,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
+
ffmpeg_for_python-0.3.dist-info/METADATA,sha256=pfc4-8H1mfXzEyHaSggyJFB3JbgcHAllnvaXYgFsEgE,2539
|
|
10
|
+
ffmpeg_for_python-0.3.dist-info/WHEEL,sha256=cVxcB9AmuTcXqmwrtPhNK88dr7IR_b6qagTj0UvIEbY,91
|
|
11
|
+
ffmpeg_for_python-0.3.dist-info/top_level.txt,sha256=7do-X2t_RBNs6_hpK7PTWKiLk1ZPGi3Y066m0xigWFU,18
|
|
12
|
+
ffmpeg_for_python-0.3.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ffmpeg_for_python
|