Spaces:
Running
Running
File size: 6,839 Bytes
ee2a1ac 41ddd18 ee2a1ac 32450de ee2a1ac 57ef3c4 ee2a1ac 57ef3c4 32450de ee2a1ac 32450de 41ddd18 ee2a1ac 4c40bba ee2a1ac 4c40bba ee2a1ac 32450de 4c40bba 41ddd18 32450de 41ddd18 d785524 41ddd18 af3b295 41ddd18 af3b295 41ddd18 4c40bba 41ddd18 af3b295 32450de 41ddd18 32450de 41ddd18 32450de ee2a1ac d785524 41ddd18 d785524 41ddd18 3eaeb3f d785524 41ddd18 3eaeb3f 41ddd18 d785524 41ddd18 d785524 32450de ee2a1ac 41ddd18 ee2a1ac 525c40c ee2a1ac 41ddd18 4f69190 ee2a1ac 3eaeb3f 41ddd18 525c40c 41ddd18 3eaeb3f 41ddd18 ee2a1ac e979870 41ddd18 525c40c 41ddd18 525c40c 41ddd18 525c40c 41ddd18 525c40c ee2a1ac 525c40c |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 |
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** (No se guarda ninguna información")
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) |