leonett commited on
Commit
78ac94b
·
verified ·
1 Parent(s): 2436c8e

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +163 -0
app.py ADDED
@@ -0,0 +1,163 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import cv2
3
+ import os
4
+ import zipfile
5
+ from PIL import Image, ImageOps
6
+ from datetime import datetime
7
+ import hashlib
8
+
9
+ def procesar_video(video):
10
+ try:
11
+ video_path = video
12
+ original_name = os.path.basename(video_path)
13
+
14
+ timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
15
+ temp_dir = f"temp_{datetime.now().strftime('%Y%m%d%H%M%S')}"
16
+ os.makedirs(temp_dir, exist_ok=True)
17
+
18
+ # Extracción de todos los fotogramas
19
+ from moviepy.editor import VideoFileClip
20
+ clip = VideoFileClip(video_path)
21
+ frame_count = int(clip.fps * clip.duration)
22
+ frame_paths = []
23
+ for i, frame in enumerate(clip.iter_frames()):
24
+ img = Image.fromarray(frame)
25
+ img_path = os.path.join(temp_dir, f"frame_{i:04d}.jpg")
26
+ img.save(img_path)
27
+ frame_paths.append(img_path)
28
+ clip.close()
29
+
30
+ if frame_count == 0:
31
+ raise gr.Error("No se pudieron extraer fotogramas del video")
32
+
33
+ # Selección estratégica de 4 fotogramas equidistantes
34
+ n_seleccion = 4
35
+ step = max(1, frame_count // (n_seleccion + 1))
36
+ selected_indices = [step * (i+1) for i in range(n_seleccion)]
37
+ selected_frames = [frame_paths[min(i, len(frame_paths)-1)] for i in selected_indices]
38
+
39
+ # Creación de collage profesional
40
+ images = []
41
+ for img_path in selected_frames:
42
+ img = Image.open(img_path)
43
+ bordered_img = ImageOps.expand(img, border=2, fill='white') # Borde blanco
44
+ images.append(bordered_img)
45
+
46
+ # Configuración del diseño
47
+ img_w, img_h = images[0].size
48
+ margin = 30
49
+ border_size = 20
50
+ shadow_offset = 5
51
+
52
+ collage_width = (img_w * 2) + margin + (border_size * 2)
53
+ collage_height = (img_h * 2) + margin + (border_size * 2)
54
+
55
+ collage = Image.new('RGB',
56
+ (collage_width, collage_height),
57
+ (230, 230, 230)) # Fondo gris claro
58
+
59
+ # Posiciones con efecto de profundidad
60
+ positions = [
61
+ (border_size, border_size),
62
+ (border_size + img_w + margin, border_size),
63
+ (border_size, border_size + img_h + margin),
64
+ (border_size + img_w + margin, border_size + img_h + margin)
65
+ ]
66
+
67
+ # Pegar imágenes con sombra
68
+ for i, img in enumerate(images):
69
+ # Sombra
70
+ shadow = Image.new('RGBA', (img_w + shadow_offset, img_h + shadow_offset), (0,0,0,50))
71
+ collage.paste(shadow, (positions[i][0]+shadow_offset, positions[i][1]+shadow_offset), shadow)
72
+
73
+ # Imagen principal
74
+ collage.paste(img, positions[i])
75
+
76
+ collage_path = os.path.join(temp_dir, "collage_forense.jpg")
77
+ collage.save(collage_path, quality=95, dpi=(300, 300))
78
+
79
+ # Generación del ZIP con cadena de custodia
80
+ base_name = os.path.splitext(original_name)[0]
81
+ zip_filename = f"{base_name}_Fotogramas.zip"
82
+ final_zip_path = os.path.join(temp_dir, zip_filename)
83
+
84
+ with zipfile.ZipFile(final_zip_path, mode="w") as zipf:
85
+ # Añadir todos los frames
86
+ for img_path in frame_paths:
87
+ zipf.write(img_path, os.path.basename(img_path))
88
+
89
+ # Archivo TXT con formato profesional
90
+ with open(video_path, "rb") as f:
91
+ video_hash = hashlib.md5(f.read()).hexdigest()
92
+
93
+ chain_content = (
94
+ "=== CADENA DE CUSTODIA DIGITAL ===\r\n\r\n"
95
+ f"• Archivo original: {original_name}\r\n"
96
+ f"• Fecha de procesamiento: {timestamp}\r\n"
97
+ f"• Fotogramas totales: {frame_count}\r\n"
98
+ f"• Hash MD5 video: {video_hash}\r\n"
99
+ f"• Fotogramas muestra: {', '.join([f'#{i+1}' for i in selected_indices])}\r\n\r\n"
100
+ "Este documento certifica la integridad del proceso de extracción.\n"
101
+ "Sistema Certificado por Peritos Forenses Digitales de Guatemala. \n"
102
+ "www.forensedigital.gt"
103
+ )
104
+ zipf.writestr("00_CADENA_CUSTODIA.txt", chain_content)
105
+
106
+ return collage_path, final_zip_path, temp_dir
107
+
108
+ except Exception as e:
109
+ raise gr.Error(f"Error en procesamiento: {str(e)}")
110
+
111
+ def limpiar_cache(temp_dir):
112
+ if temp_dir and os.path.exists(temp_dir):
113
+ for file in os.listdir(temp_dir):
114
+ os.remove(os.path.join(temp_dir, file))
115
+ os.rmdir(temp_dir)
116
+
117
+ with gr.Blocks(title="Extractior Forense de Fotogramas") as demo:
118
+ gr.Markdown("# 📷 Extractor Forense de Fotogramas de Videos")
119
+ gr.Markdown("**Herramienta certificada para extracción forense de fotogramas de videos** (No se guarda ninguna información). Soporta todos los formatos de video compatibles con OpenCV/FFmpeg.")
120
+ gr.Markdown("Desarrollado por José R. Leonett para el Grupo de Peritos Forenses Digitales de Guatemala - [www.forensedigital.gt](https://www.forensedigital.gt)")
121
+
122
+ with gr.Row():
123
+ with gr.Column():
124
+ video_input = gr.Video(
125
+ label="CARGAR VIDEO",
126
+ sources=["upload"],
127
+ format="",
128
+ interactive=True
129
+ )
130
+ procesar_btn = gr.Button("🔍 INICIAR ANÁLISIS", interactive=False)
131
+ with gr.Column():
132
+ # gr.Markdown("## Resultados:")
133
+ gallery_output = gr.Image(label="COLLAGE DE REFERENCIA", height=400)
134
+ download_file = gr.File(label="DESCARGAR EVIDENCIAS", visible=True)
135
+
136
+ temp_dir_state = gr.State()
137
+ zip_path_state = gr.State()
138
+
139
+ def habilitar_procesado(video):
140
+ return gr.update(interactive=True) if video else gr.update(interactive=False)
141
+
142
+ video_input.change(
143
+ fn=habilitar_procesado,
144
+ inputs=video_input,
145
+ outputs=procesar_btn,
146
+ queue=False
147
+ )
148
+
149
+ def procesar_y_mostrar(video):
150
+ if temp_dir_state.value:
151
+ limpiar_cache(temp_dir_state.value)
152
+ collage, zip_path, temp_dir = procesar_video(video)
153
+ return collage, zip_path, temp_dir, zip_path
154
+
155
+ procesar_btn.click(
156
+ fn=procesar_y_mostrar,
157
+ inputs=video_input,
158
+ outputs=[gallery_output, download_file, temp_dir_state, zip_path_state]
159
+ )
160
+
161
+ if __name__ == "__main__":
162
+ demo.queue()
163
+ demo.launch(server_name="0.0.0.0", server_port=7860)