leonett commited on
Commit
f85a7b9
·
verified ·
1 Parent(s): bbe250f

Upload 3 files

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