leonett commited on
Commit
e5be641
·
verified ·
1 Parent(s): 76dd19f

Upload 2 files

Browse files
Files changed (2) hide show
  1. app.py +158 -0
  2. requirements.txt +6 -0
app.py ADDED
@@ -0,0 +1,158 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import gradio as gr
3
+ import cv2
4
+ import os
5
+ import zipfile
6
+ from PIL import Image, ImageOps
7
+ from datetime import datetime
8
+ import hashlib
9
+ import shutil
10
+
11
+ TEMP_CACHE = None
12
+
13
+ def procesar_video(video_path):
14
+ try:
15
+ original_name = os.path.basename(video_path)
16
+ allowed_extensions = ('.mp4', '.avi', '.mov', '.mkv')
17
+ if not original_name.lower().endswith(allowed_extensions):
18
+ raise gr.Error("Solo se permiten archivos de video (mp4, avi, mov, mkv)")
19
+
20
+ timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
21
+ temp_dir = f"temp_{datetime.now().strftime('%Y%m%d%H%M%S')}"
22
+ os.makedirs(temp_dir, exist_ok=True)
23
+
24
+ cap = cv2.VideoCapture(video_path)
25
+ frame_count = 0
26
+ frame_paths = []
27
+ while True:
28
+ ret, frame = cap.read()
29
+ if not ret:
30
+ break
31
+ frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
32
+ img = Image.fromarray(frame_rgb)
33
+ img_path = os.path.join(temp_dir, f"frame_{frame_count:04d}.jpg")
34
+ img.save(img_path)
35
+ frame_paths.append(img_path)
36
+ frame_count += 1
37
+ cap.release()
38
+
39
+ if frame_count == 0:
40
+ raise gr.Error("No se pudieron extraer fotogramas del video")
41
+
42
+ n_seleccion = 4
43
+ step = max(1, frame_count // (n_seleccion + 1))
44
+ selected_indices = [step * (i+1) for i in range(n_seleccion)]
45
+ selected_frames = [frame_paths[min(i, len(frame_paths)-1)] for i in selected_indices]
46
+
47
+ images = []
48
+ for img_path in selected_frames:
49
+ img = Image.open(img_path)
50
+ bordered_img = ImageOps.expand(img, border=2, fill='white')
51
+ images.append(bordered_img)
52
+
53
+ img_w, img_h = images[0].size
54
+ margin = 30
55
+ border_size = 20
56
+ shadow_offset = 5
57
+
58
+ collage_width = (img_w * 2) + margin + (border_size * 2)
59
+ collage_height = (img_h * 2) + margin + (border_size * 2)
60
+
61
+ collage = Image.new('RGB', (collage_width, collage_height), (230, 230, 230))
62
+ positions = [
63
+ (border_size, border_size),
64
+ (border_size + img_w + margin, border_size),
65
+ (border_size, border_size + img_h + margin),
66
+ (border_size + img_w + margin, border_size + img_h + margin)
67
+ ]
68
+
69
+ for i, img in enumerate(images):
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
+ collage.paste(img, positions[i])
73
+
74
+ collage_path = os.path.join(temp_dir, "collage_forense.jpg")
75
+ collage.save(collage_path, quality=95, dpi=(300, 300))
76
+
77
+ base_name = os.path.splitext(original_name)[0]
78
+ zip_filename = f"{base_name}_Fotogramas.zip"
79
+ final_zip_path = os.path.join(temp_dir, zip_filename)
80
+
81
+ with zipfile.ZipFile(final_zip_path, mode="w") as zipf:
82
+ for img_path in frame_paths:
83
+ zipf.write(img_path, os.path.basename(img_path))
84
+
85
+ with open(video_path, "rb") as f:
86
+ video_hash = hashlib.md5(f.read()).hexdigest()
87
+
88
+ chain_content = (
89
+ "=== CADENA DE CUSTODIA DIGITAL ===\r\n\r\n"
90
+ f"• Archivo original: {original_name}\r\n"
91
+ f"• Fecha de procesamiento: {timestamp}\r\n"
92
+ f"• Fotogramas totales: {frame_count}\r\n"
93
+ f"• Hash MD5 video: {video_hash}\r\n"
94
+ f"• Fotogramas muestra: {', '.join([f'#{i+1}' for i in selected_indices])}\r\n\r\n"
95
+ "Este documento certifica la integridad del proceso de extracción.\n"
96
+ "Sistema Certificado por Peritos Forenses Digitales de Guatemala. \n"
97
+ "www.forensedigital.gt"
98
+ )
99
+ zipf.writestr("00_CADENA_CUSTODIA.txt", chain_content)
100
+
101
+ global TEMP_CACHE
102
+ TEMP_CACHE = temp_dir
103
+
104
+ return collage_path, final_zip_path
105
+
106
+ except Exception as e:
107
+ raise gr.Error(f"Error en procesamiento: {str(e)}")
108
+
109
+ def limpiar_cache():
110
+ global TEMP_CACHE
111
+ if TEMP_CACHE and os.path.exists(TEMP_CACHE):
112
+ shutil.rmtree(TEMP_CACHE)
113
+ TEMP_CACHE = None
114
+
115
+ with gr.Blocks(title="Extractor Forense de Fotogramas") as demo:
116
+ gr.Markdown("# 📷 Extractor Forense de Fotogramas de Videos")
117
+ gr.Markdown("""
118
+ **Herramienta certificada para extracción forense de fotogramas de videos**
119
+ (No se guarda ninguna información).
120
+ """)
121
+ gr.Markdown("Desarrollado por José R. Leonett para el Grupo de Peritos Forenses Digitales de Guatemala - [www.forensedigital.gt](https://www.forensedigital.gt)")
122
+
123
+ with gr.Row():
124
+ with gr.Column(scale=1):
125
+ video_input = gr.Video(
126
+ label="🎞️ VIDEO CARGADO",
127
+ format="mp4",
128
+ interactive=True,
129
+ height=320
130
+ )
131
+ procesar_btn = gr.Button("🔍 INICIAR ANÁLISIS", interactive=False)
132
+ with gr.Column(scale=1):
133
+ gallery_output = gr.Image(label="📸 COLLAGE DE REFERENCIA", height=400)
134
+ download_file = gr.File(label="📂 DESCARGAR EVIDENCIAS", visible=True)
135
+
136
+ def habilitar_procesado(video):
137
+ limpiar_cache()
138
+ return gr.update(interactive=True), None, None
139
+
140
+ video_input.change(
141
+ fn=habilitar_procesado,
142
+ inputs=video_input,
143
+ outputs=[procesar_btn, gallery_output, download_file],
144
+ queue=False
145
+ )
146
+
147
+ def procesar_y_mostrar(video):
148
+ collage, zip_path = procesar_video(video)
149
+ return collage, zip_path
150
+
151
+ procesar_btn.click(
152
+ fn=procesar_y_mostrar,
153
+ inputs=video_input,
154
+ outputs=[gallery_output, download_file]
155
+ )
156
+
157
+ if __name__ == "__main__":
158
+ demo.launch(server_name="0.0.0.0", server_port=7860)
requirements.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ gradio>=4.0.0
2
+ numpy>=1.26.0
3
+ opencv-python-headless>=4.8.0
4
+ opencv-python>=4.8.0
5
+ pillow>=10.0.0
6
+ matplotlib>=3.7.0