Update tools/video_encode_tool.py
Browse files- tools/video_encode_tool.py +62 -48
tools/video_encode_tool.py
CHANGED
|
@@ -118,85 +118,99 @@ class VideoEncodeTool:
|
|
| 118 |
|
| 119 |
|
| 120 |
|
| 121 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 122 |
"""
|
| 123 |
-
Concatena múltiplos
|
| 124 |
-
|
| 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 |
-
|
| 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,
|
| 142 |
-
|
| 143 |
-
|
|
|
|
| 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',
|
| 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 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 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 |
-
|
| 169 |
-
processed_videos.append(abs_path)
|
| 170 |
|
| 171 |
-
# Cria
|
| 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
|
| 175 |
-
f.write(f"file '{os.path.abspath(
|
| 176 |
-
|
| 177 |
-
#
|
| 178 |
-
|
| 179 |
-
'ffmpeg
|
| 180 |
-
'-i
|
| 181 |
-
|
| 182 |
-
logger.info(f"
|
| 183 |
-
subprocess.run(
|
| 184 |
-
logger.info("Concatenação
|
| 185 |
return output_path
|
| 186 |
|
| 187 |
except subprocess.CalledProcessError as e:
|
| 188 |
-
logger.error(f"Erro
|
| 189 |
-
raise VideoToolError("Falha durante concatenação
|
| 190 |
finally:
|
| 191 |
# Limpa arquivos temporários
|
| 192 |
-
|
| 193 |
-
os.
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
|
|
|
|
|
|
| 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 |
"""
|
|
|
|
| 118 |
|
| 119 |
|
| 120 |
|
| 121 |
+
import os
|
| 122 |
+
import subprocess
|
| 123 |
+
import shutil
|
| 124 |
+
import time
|
| 125 |
+
from typing import List
|
| 126 |
+
import logging
|
| 127 |
+
|
| 128 |
+
logger = logging.getLogger(__name__)
|
| 129 |
+
|
| 130 |
+
class VideoToolError(Exception):
|
| 131 |
+
pass
|
| 132 |
+
|
| 133 |
+
class VideoEditor:
|
| 134 |
+
def concatenate_videos(self, video_paths: List[str], output_path: str, workspace_dir: str, star:int=0, overlap:int=1) -> str:
|
| 135 |
"""
|
| 136 |
+
Concatena múltiplos vídeos MP4, removendo exatamente o último frame
|
| 137 |
+
de cada vídeo, exceto o último, usando trim por frame.
|
| 138 |
"""
|
| 139 |
if not video_paths:
|
| 140 |
raise VideoToolError("Nenhum fragmento de vídeo fornecido para concatenação.")
|
| 141 |
|
| 142 |
if len(video_paths) == 1:
|
| 143 |
shutil.copy(video_paths[0], output_path)
|
| 144 |
+
logger.info(f"[Concat] Apenas um clipe fornecido. Copiado para '{output_path}'.")
|
| 145 |
return output_path
|
| 146 |
|
| 147 |
+
temp_dir = os.path.join(workspace_dir, "temp_trimmed_videos")
|
|
|
|
| 148 |
os.makedirs(temp_dir, exist_ok=True)
|
|
|
|
| 149 |
processed_videos = []
|
| 150 |
|
| 151 |
try:
|
| 152 |
+
for i, base in enumerate(video_paths):
|
| 153 |
+
abs_base = os.path.abspath(base)
|
| 154 |
+
|
| 155 |
+
# Obtém número total de frames via ffprobe
|
| 156 |
probe_cmd = [
|
| 157 |
'ffprobe', '-v', 'error', '-select_streams', 'v:0',
|
| 158 |
'-count_frames', '-show_entries', 'stream=nb_read_frames',
|
| 159 |
+
'-of', 'default=nokey=1:noprint_wrappers=1', abs_base
|
| 160 |
]
|
| 161 |
result = subprocess.run(probe_cmd, capture_output=True, text=True, check=True)
|
| 162 |
total_frames = int(result.stdout.strip())
|
| 163 |
+
logger.debug(f"[Trim] {os.path.basename(base)} → total_frames={total_frames}")
|
| 164 |
+
|
| 165 |
+
# Define limites de corte
|
| 166 |
+
start_frame = start
|
| 167 |
+
end_frame = total_frames if i == len(video_paths) - 1 else total_frames - overlap
|
| 168 |
|
| 169 |
+
# Se for o último vídeo, não corta o frame final
|
| 170 |
if i < len(video_paths) - 1:
|
| 171 |
+
video_podado = os.path.join(temp_dir, f"cut_{i}.mp4")
|
| 172 |
+
cmd_fim = (
|
| 173 |
+
f'ffmpeg -y -hide_banner -loglevel error -i "{abs_base}" '
|
| 174 |
+
f'-vf "trim=start_frame={start_frame}:end_frame={end_frame},setpts=PTS-STARTPTS" '
|
| 175 |
+
f'-an "{video_podado}"'
|
| 176 |
+
)
|
| 177 |
+
logger.debug(f"[CmdTrim] {cmd_fim}")
|
| 178 |
+
subprocess.run(cmd_fim, shell=True, check=True)
|
| 179 |
+
processed_videos.append(video_podado)
|
| 180 |
+
logger.debug(f"[TrimOK] {os.path.basename(base)} → -1 frame ({end_frame}/{total_frames})")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 181 |
else:
|
| 182 |
+
processed_videos.append(abs_base)
|
|
|
|
| 183 |
|
| 184 |
+
# Cria lista para concatenação
|
| 185 |
list_file_path = os.path.join(workspace_dir, f"concat_list_{int(time.time())}.txt")
|
| 186 |
with open(list_file_path, 'w', encoding='utf-8') as f:
|
| 187 |
+
for path in processed_videos:
|
| 188 |
+
f.write(f"file '{os.path.abspath(path)}'\n")
|
| 189 |
+
|
| 190 |
+
# Concat final
|
| 191 |
+
cmd_concat = (
|
| 192 |
+
f'ffmpeg -y -hide_banner -loglevel error -f concat -safe 0 '
|
| 193 |
+
f'-i "{list_file_path}" -c copy "{output_path}"'
|
| 194 |
+
)
|
| 195 |
+
logger.info(f"[Concat] Executando concatenação final: {cmd_concat}")
|
| 196 |
+
subprocess.run(cmd_concat, shell=True, check=True)
|
| 197 |
+
logger.info("[ConcatOK] Concatenação concluída com sucesso.")
|
| 198 |
return output_path
|
| 199 |
|
| 200 |
except subprocess.CalledProcessError as e:
|
| 201 |
+
logger.error(f"[ConcatERR] Erro FFmpeg: {e}")
|
| 202 |
+
raise VideoToolError("Falha durante a concatenação de vídeos.")
|
| 203 |
finally:
|
| 204 |
# Limpa arquivos temporários
|
| 205 |
+
if os.path.exists(temp_dir):
|
| 206 |
+
for f in os.listdir(temp_dir):
|
| 207 |
+
try:
|
| 208 |
+
os.remove(os.path.join(temp_dir, f))
|
| 209 |
+
except Exception:
|
| 210 |
+
pass
|
| 211 |
+
os.rmdir(temp_dir)
|
| 212 |
+
if 'list_file_path' in locals() and os.path.exists(list_file_path):
|
| 213 |
os.remove(list_file_path)
|
|
|
|
| 214 |
|
| 215 |
def concatenate_videos2(self, video_paths: List[str], output_path: str, workspace_dir: str) -> str:
|
| 216 |
"""
|