Update tools/video_encode_tool.py
Browse files- tools/video_encode_tool.py +83 -1
tools/video_encode_tool.py
CHANGED
|
@@ -116,7 +116,89 @@ class VideoEncodeTool:
|
|
| 116 |
raise VideoToolError(f"Falha ao criar vídeo de transição: {e.stderr}")
|
| 117 |
return output_path
|
| 118 |
|
| 119 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 120 |
"""
|
| 121 |
Concatena múltiplos clipes de vídeo em um único arquivo sem re-codificar.
|
| 122 |
"""
|
|
|
|
| 116 |
raise VideoToolError(f"Falha ao criar vídeo de transição: {e.stderr}")
|
| 117 |
return output_path
|
| 118 |
|
| 119 |
+
|
| 120 |
+
|
| 121 |
+
def concatenate_videos(self, video_paths: List[str], output_path: str, workspace_dir: str, overlap: int=1) -> str:
|
| 122 |
+
"""
|
| 123 |
+
Concatena múltiplos clipes de vídeo em um único arquivo,
|
| 124 |
+
removendo o último frame de cada vídeo exceto o último.
|
| 125 |
+
"""
|
| 126 |
+
if not video_paths:
|
| 127 |
+
raise VideoToolError("Nenhum fragmento de vídeo fornecido para concatenação.")
|
| 128 |
+
|
| 129 |
+
if len(video_paths) == 1:
|
| 130 |
+
shutil.copy(video_paths[0], output_path)
|
| 131 |
+
logger.info(f"Apenas um clipe fornecido. Copiado para '{output_path}'.")
|
| 132 |
+
return output_path
|
| 133 |
+
|
| 134 |
+
# Pasta temporária para cortes intermediários
|
| 135 |
+
temp_dir = os.path.join(workspace_dir, "temp_cut_videos")
|
| 136 |
+
os.makedirs(temp_dir, exist_ok=True)
|
| 137 |
+
|
| 138 |
+
processed_videos = []
|
| 139 |
+
|
| 140 |
+
try:
|
| 141 |
+
for i, path in enumerate(video_paths):
|
| 142 |
+
abs_path = os.path.abspath(path)
|
| 143 |
+
# Verifica o número total de frames usando ffprobe
|
| 144 |
+
probe_cmd = [
|
| 145 |
+
'ffprobe', '-v', 'error', '-select_streams', 'v:0',
|
| 146 |
+
'-count_frames', '-show_entries', 'stream=nb_read_frames',
|
| 147 |
+
'-of', 'default=nokey=1:noprint_wrappers=1', abs_path
|
| 148 |
+
]
|
| 149 |
+
result = subprocess.run(probe_cmd, capture_output=True, text=True, check=True)
|
| 150 |
+
total_frames = int(result.stdout.strip())
|
| 151 |
+
|
| 152 |
+
if i < len(video_paths) - 1:
|
| 153 |
+
# Remove o último frame do vídeo
|
| 154 |
+
cut_frames = total_frames - overlap
|
| 155 |
+
temp_output = os.path.join(temp_dir, f"cut_{i}.mp4")
|
| 156 |
+
|
| 157 |
+
# Cria uma versão sem o último frame (mantendo codec e fps)
|
| 158 |
+
cut_cmd = [
|
| 159 |
+
'ffmpeg', '-y', '-i', abs_path,
|
| 160 |
+
'-vf', f'select=lt(n\\,{cut_frames})', # mantém apenas n < total_frames-1
|
| 161 |
+
'-vsync', '0', '-c:v', 'libx264', '-preset', 'fast', '-crf', '18',
|
| 162 |
+
'-an', temp_output
|
| 163 |
+
]
|
| 164 |
+
subprocess.run(cut_cmd, check=True, capture_output=True, text=True)
|
| 165 |
+
processed_videos.append(temp_output)
|
| 166 |
+
logger.debug(f"Removido último frame do vídeo {i} ({cut_frames}/{total_frames}).")
|
| 167 |
+
else:
|
| 168 |
+
# Último vídeo permanece intacto
|
| 169 |
+
processed_videos.append(abs_path)
|
| 170 |
+
|
| 171 |
+
# Cria o arquivo de concatenação
|
| 172 |
+
list_file_path = os.path.join(workspace_dir, f"concat_list_{int(time.time())}.txt")
|
| 173 |
+
with open(list_file_path, 'w', encoding='utf-8') as f:
|
| 174 |
+
for p in processed_videos:
|
| 175 |
+
f.write(f"file '{os.path.abspath(p)}'\n")
|
| 176 |
+
|
| 177 |
+
# Executa concatenação final
|
| 178 |
+
cmd_list = [
|
| 179 |
+
'ffmpeg', '-y', '-f', 'concat', '-safe', '0',
|
| 180 |
+
'-i', list_file_path, '-c', 'copy', output_path
|
| 181 |
+
]
|
| 182 |
+
logger.info(f"Concatenando {len(processed_videos)} clipes em '{output_path}'...")
|
| 183 |
+
subprocess.run(cmd_list, check=True, capture_output=True, text=True)
|
| 184 |
+
logger.info("Concatenação FFmpeg bem-sucedida.")
|
| 185 |
+
return output_path
|
| 186 |
+
|
| 187 |
+
except subprocess.CalledProcessError as e:
|
| 188 |
+
logger.error(f"Erro ao processar vídeos: {e.stderr}")
|
| 189 |
+
raise VideoToolError("Falha durante concatenação dos vídeos.")
|
| 190 |
+
finally:
|
| 191 |
+
# Limpa arquivos temporários
|
| 192 |
+
for f in os.listdir(temp_dir):
|
| 193 |
+
os.remove(os.path.join(temp_dir, f))
|
| 194 |
+
os.rmdir(temp_dir)
|
| 195 |
+
|
| 196 |
+
list_file_path = os.path.join(workspace_dir, f"concat_list_{int(time.time())}.txt")
|
| 197 |
+
if os.path.exists(list_file_path):
|
| 198 |
+
os.remove(list_file_path)
|
| 199 |
+
|
| 200 |
+
|
| 201 |
+
def concatenate_videos2(self, video_paths: List[str], output_path: str, workspace_dir: str) -> str:
|
| 202 |
"""
|
| 203 |
Concatena múltiplos clipes de vídeo em um único arquivo sem re-codificar.
|
| 204 |
"""
|