Fotogramas / app.py
leonett's picture
Update app.py
41ddd18 verified
raw
history blame
6.8 kB
import gradio as gr
import cv2
import os
import zipfile
from PIL import Image, ImageOps
from datetime import datetime
import hashlib
def procesar_video(video):
try:
if isinstance(video, dict):
original_name = video.get("name", "video")
video_path = video.get("file", video.get("data"))
else:
original_name = os.path.basename(video)
video_path = video
allowed_extensions = ('.mp4', '.avi', '.mov', '.mkv')
if not original_name.lower().endswith(allowed_extensions):
raise gr.Error("Solo se permiten archivos de video (mp4, avi, mov, mkv)")
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
temp_dir = f"temp_{datetime.now().strftime('%Y%m%d%H%M%S')}"
os.makedirs(temp_dir, exist_ok=True)
# Extracción de todos los fotogramas
cap = cv2.VideoCapture(video_path)
frame_count = 0
frame_paths = []
while True:
ret, frame = cap.read()
if not ret:
break
frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
img = Image.fromarray(frame_rgb)
img_path = os.path.join(temp_dir, f"frame_{frame_count:04d}.jpg")
img.save(img_path)
frame_paths.append(img_path)
frame_count += 1
cap.release()
if frame_count == 0:
raise gr.Error("No se pudieron extraer fotogramas del video")
# Selección estratégica de 4 fotogramas equidistantes
n_seleccion = 4
step = max(1, frame_count // (n_seleccion + 1))
selected_indices = [step * (i+1) for i in range(n_seleccion)]
selected_frames = [frame_paths[min(i, len(frame_paths)-1)] for i in selected_indices]
# Creación de collage profesional
images = []
for img_path in selected_frames:
img = Image.open(img_path)
bordered_img = ImageOps.expand(img, border=2, fill='white') # Borde blanco
images.append(bordered_img)
# Configuración del diseño
img_w, img_h = images[0].size
margin = 30
border_size = 20
shadow_offset = 5
collage_width = (img_w * 2) + margin + (border_size * 2)
collage_height = (img_h * 2) + margin + (border_size * 2)
collage = Image.new('RGB',
(collage_width, collage_height),
(230, 230, 230)) # Fondo gris claro
# Posiciones con efecto de profundidad
positions = [
(border_size, border_size),
(border_size + img_w + margin, border_size),
(border_size, border_size + img_h + margin),
(border_size + img_w + margin, border_size + img_h + margin)
]
# Pegar imágenes con sombra
for i, img in enumerate(images):
# Sombra
shadow = Image.new('RGBA', (img_w + shadow_offset, img_h + shadow_offset), (0,0,0,50))
collage.paste(shadow, (positions[i][0]+shadow_offset, positions[i][1]+shadow_offset), shadow)
# Imagen principal
collage.paste(img, positions[i])
collage_path = os.path.join(temp_dir, "collage_forense.jpg")
collage.save(collage_path, quality=95, dpi=(300, 300))
# Generación del ZIP con cadena de custodia
base_name = os.path.splitext(original_name)[0]
zip_filename = f"{base_name}_Fotogramas.zip"
final_zip_path = os.path.join(temp_dir, zip_filename)
with zipfile.ZipFile(final_zip_path, mode="w") as zipf:
# Añadir todos los frames
for img_path in frame_paths:
zipf.write(img_path, os.path.basename(img_path))
# Archivo TXT con formato profesional
with open(video_path, "rb") as f:
video_hash = hashlib.md5(f.read()).hexdigest()
chain_content = (
"=== CADENA DE CUSTODIA DIGITAL ===\r\n\r\n"
f"• Archivo original: {original_name}\r\n"
f"• Fecha de procesamiento: {timestamp}\r\n"
f"• Fotogramas totales: {frame_count}\r\n"
f"• Hash MD5 video: {video_hash}\r\n"
f"• Fotogramas muestra: {', '.join([f'#{i+1}' for i in selected_indices])}\r\n\r\n"
"Este documento certifica la integridad del proceso de extracción.\n"
"Sistema Certificado por Peritos Forenses Digitales de Guatemala. \n"
"www.forensedigital.gt"
)
zipf.writestr("00_CADENA_CUSTODIA.txt", chain_content)
return collage_path, final_zip_path, temp_dir
except Exception as e:
raise gr.Error(f"Error en procesamiento: {str(e)}")
def limpiar_cache(temp_dir):
if temp_dir and os.path.exists(temp_dir):
for file in os.listdir(temp_dir):
os.remove(os.path.join(temp_dir, file))
os.rmdir(temp_dir)
with gr.Blocks(title="Extractior Forense de Fotogramas") as demo:
gr.Markdown("# 📷 Extractor Forense de Fotogramas de Videos")
gr.Markdown("**Herramienta certificada para extracción forense de fotogramas de videos**")
gr.Markdown("Desarrollado por José R. Leonett para el Grupo de Peritos Forenses Digitales de Guatemala - [www.forensedigital.gt](https://www.forensedigital.gt)")
with gr.Row():
with gr.Column():
video_input = gr.Video(
label="CARGAR VIDEO",
sources=["upload"],
format="mp4",
interactive=True
)
procesar_btn = gr.Button("🔍 INICIAR ANÁLISIS", interactive=False)
with gr.Column():
gr.Markdown("## Resultados:")
gallery_output = gr.Image(label="COLLAGE DE REFERENCIA", height=400)
download_file = gr.File(label="DESCARGAR EVIDENCIAS", visible=True)
temp_dir_state = gr.State()
zip_path_state = gr.State()
def habilitar_procesado(video):
return gr.update(interactive=True) if video else gr.update(interactive=False)
video_input.change(
fn=habilitar_procesado,
inputs=video_input,
outputs=procesar_btn,
queue=False
)
def procesar_y_mostrar(video):
if temp_dir_state.value:
limpiar_cache(temp_dir_state.value)
collage, zip_path, temp_dir = procesar_video(video)
return collage, zip_path, temp_dir, zip_path
procesar_btn.click(
fn=procesar_y_mostrar,
inputs=video_input,
outputs=[gallery_output, download_file, temp_dir_state, zip_path_state]
)
if __name__ == "__main__":
demo.launch(server_name="0.0.0.0", server_port=7860)