leonett commited on
Commit
9d690cb
·
verified ·
1 Parent(s): fb7ae91

Upload app.py

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