leonett commited on
Commit
d785524
verified
1 Parent(s): 2120b2a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +25 -85
app.py CHANGED
@@ -10,7 +10,6 @@ import hashlib
10
 
11
  def procesar_video(video):
12
  try:
13
- # Validar y obtener el nombre y ruta del video
14
  if isinstance(video, dict):
15
  original_name = video.get("name", "video")
16
  video_path = video.get("file", video.get("data"))
@@ -22,14 +21,11 @@ def procesar_video(video):
22
  if not original_name.lower().endswith(allowed_extensions):
23
  raise gr.Error("Solo se permiten archivos de video (mp4, avi, mov, mkv)")
24
 
25
- # Timestamp para la cadena de custodia
26
  timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
27
-
28
- # Crear directorio temporal
29
  temp_dir = f"temp_{datetime.now().strftime('%Y%m%d%H%M%S')}"
30
  os.makedirs(temp_dir, exist_ok=True)
31
 
32
- # Extraer TODOS los fotogramas del video
33
  cap = cv2.VideoCapture(video_path)
34
  frame_count = 0
35
  frame_paths = []
@@ -48,13 +44,13 @@ def procesar_video(video):
48
  if frame_count == 0:
49
  raise gr.Error("No se pudieron extraer fotogramas del video")
50
 
51
- # Crear collage de 4 fotogramas aleatorios con separaci贸n
52
  selected_frames = random.sample(frame_paths, min(4, len(frame_paths)))
53
  images = [Image.open(img) for img in selected_frames]
54
  margin = 10
55
- collage_width = images[0].width * 2 + margin
56
- collage_height = images[0].height * 2 + margin
57
- collage = Image.new('RGB', (collage_width, collage_height), color=(255,255,255))
58
  positions = [
59
  (0, 0),
60
  (images[0].width + margin, 0),
@@ -66,61 +62,22 @@ def procesar_video(video):
66
  collage_path = os.path.join(temp_dir, "collage.jpg")
67
  collage.save(collage_path)
68
 
69
- # ===============================
70
- # Generaci贸n de la cadena de custodia
71
- # -------------------------------
72
- # 1. Crear ZIP provisional en memoria con SOLO los fotogramas
73
- provisional_zip = io.BytesIO()
74
- with zipfile.ZipFile(provisional_zip, mode="w") as zipf:
75
- for img in frame_paths:
76
- zipf.write(img, os.path.basename(img))
77
- provisional_zip.seek(0)
78
- provisional_data = provisional_zip.getvalue()
79
- md5_provisional = hashlib.md5(provisional_data).hexdigest()
80
-
81
- # 2. Crear un ZIP provisional en memoria que incluya un archivo TXT con marcador de posici贸n para el MD5 final
82
- final_zip_mem = io.BytesIO()
83
- chain_placeholder = "MD5 final del ZIP: <pending>"
84
- chain_initial = (
85
- f"Nombre del archivo: {original_name}\n"
86
- f"Fecha de carga y extracci贸n: {timestamp}\n"
87
- f"N煤mero de fotogramas: {frame_count}\n"
88
- f"MD5 provisional (frames): {md5_provisional}\n"
89
- f"{chain_placeholder}\n"
90
- )
91
- with zipfile.ZipFile(final_zip_mem, mode="w") as zipf:
92
- for img in frame_paths:
93
- zipf.write(img, os.path.basename(img))
94
- zipf.writestr("chain_of_custody.txt", chain_initial)
95
- final_zip_mem.seek(0)
96
- final_zip_data = final_zip_mem.getvalue()
97
- md5_final = hashlib.md5(final_zip_data).hexdigest()
98
-
99
- # 3. Reconstruir el ZIP final en memoria con el TXT actualizado (incluyendo ambos MD5)
100
- final_chain = (
101
- f"Nombre del archivo: {original_name}\n"
102
- f"Fecha de carga y extracci贸n: {timestamp}\n"
103
- f"N煤mero de fotogramas: {frame_count}\n"
104
- f"MD5 provisional (frames): {md5_provisional}\n"
105
- f"MD5 final del ZIP: {md5_final}\n"
106
- )
107
- final_zip_mem2 = io.BytesIO()
108
- with zipfile.ZipFile(final_zip_mem2, mode="w") as zipf:
109
- for img in frame_paths:
110
- zipf.write(img, os.path.basename(img))
111
- zipf.writestr("chain_of_custody.txt", final_chain)
112
- final_zip_mem2.seek(0)
113
- final_zip_data2 = final_zip_mem2.getvalue()
114
-
115
- # Escribir el ZIP final a disco con el nombre basado en el video original
116
  base_name = os.path.splitext(original_name)[0]
117
  zip_filename = f"{base_name}.zip"
118
  final_zip_path = os.path.join(temp_dir, zip_filename)
119
- with open(final_zip_path, "wb") as f:
120
- f.write(final_zip_data2)
121
- final_zip_path = os.path.abspath(final_zip_path)
122
- # ===============================
123
 
 
 
 
 
 
 
 
 
 
 
 
124
  return collage_path, final_zip_path, temp_dir
125
 
126
  except Exception as e:
@@ -132,40 +89,27 @@ def limpiar_cache(temp_dir):
132
  os.remove(os.path.join(temp_dir, file))
133
  os.rmdir(temp_dir)
134
 
135
- # -------------------------------
136
- # Interfaz Gradio
137
- # -------------------------------
138
  with gr.Blocks(title="Extracci贸n de Fotogramas Forenses") as demo:
139
- gr.Markdown("# 馃摲 Herramienta de Extracci贸n de Fotogramas Forenses")
140
  gr.Markdown("**Carga un video para extraer TODOS los fotogramas y generar un collage de muestra.**")
141
  gr.Markdown("Desarrollado por Jos茅 R. Leonett para el Grupo de Peritos Forenses Digitales de Guatemala - [www.forensedigital.gt](https://www.forensedigital.gt)")
142
 
143
  with gr.Row():
144
  with gr.Column():
145
- video_input = gr.Video(label="Subir Video (solo archivos de video)", interactive=True)
146
  procesar_btn = gr.Button("Procesar Fotogramas", interactive=False)
147
  with gr.Column():
148
  gallery_output = gr.Image(label="Collage de Muestra")
149
  download_btn = gr.Button("DESCARGAR FOTOGRAMAS", interactive=False)
150
  download_file = gr.File(label="Archivo ZIP generado", visible=False)
151
 
152
- # Estados para guardar el directorio temporal y la ruta del ZIP
153
  temp_dir_state = gr.State(None)
154
  zip_path_state = gr.State(None)
155
 
156
  def on_video_change(video):
157
- if not video:
158
- return gr.update(interactive=False), gr.update(interactive=False)
159
- if isinstance(video, dict) and video.get("name"):
160
- original_name = video["name"]
161
- elif isinstance(video, str) and video:
162
- original_name = os.path.basename(video)
163
- else:
164
- return gr.update(interactive=False), gr.update(interactive=False)
165
- allowed_extensions = ('.mp4', '.avi', '.mov', '.mkv')
166
- if not original_name.lower().endswith(allowed_extensions):
167
- raise gr.Error("Solo se permiten archivos de video (mp4, avi, mov, mkv)")
168
- return gr.update(interactive=True), gr.update(interactive=False)
169
 
170
  video_input.change(
171
  fn=on_video_change,
@@ -175,13 +119,10 @@ with gr.Blocks(title="Extracci贸n de Fotogramas Forenses") as demo:
175
  )
176
 
177
  def procesar_y_mostrar(video):
178
- btn_proc_update = gr.update(interactive=False)
179
  if temp_dir_state.value:
180
  limpiar_cache(temp_dir_state.value)
181
  collage_path, zip_path, temp_dir = procesar_video(video)
182
- btn_download_update = gr.update(interactive=True)
183
- # Se retorna zip_path tanto para actualizar el estado como para asignar el valor al componente de descarga
184
- return collage_path, zip_path, temp_dir, zip_path, btn_download_update, btn_proc_update
185
 
186
  procesar_btn.click(
187
  fn=procesar_y_mostrar,
@@ -190,10 +131,9 @@ with gr.Blocks(title="Extracci贸n de Fotogramas Forenses") as demo:
190
  )
191
 
192
  def trigger_download(zip_path):
193
- # Se abre el archivo ZIP, se leen sus bytes y se retorna un par (nombre, contenido)
194
  with open(zip_path, "rb") as f:
195
  file_bytes = f.read()
196
- return (os.path.basename(zip_path), file_bytes)
197
 
198
  download_btn.click(
199
  fn=trigger_download,
@@ -202,4 +142,4 @@ with gr.Blocks(title="Extracci贸n de Fotogramas Forenses") as demo:
202
  )
203
 
204
  if __name__ == "__main__":
205
- demo.launch()
 
10
 
11
  def procesar_video(video):
12
  try:
 
13
  if isinstance(video, dict):
14
  original_name = video.get("name", "video")
15
  video_path = video.get("file", video.get("data"))
 
21
  if not original_name.lower().endswith(allowed_extensions):
22
  raise gr.Error("Solo se permiten archivos de video (mp4, avi, mov, mkv)")
23
 
 
24
  timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
 
 
25
  temp_dir = f"temp_{datetime.now().strftime('%Y%m%d%H%M%S')}"
26
  os.makedirs(temp_dir, exist_ok=True)
27
 
28
+ # Extracci贸n de fotogramas
29
  cap = cv2.VideoCapture(video_path)
30
  frame_count = 0
31
  frame_paths = []
 
44
  if frame_count == 0:
45
  raise gr.Error("No se pudieron extraer fotogramas del video")
46
 
47
+ # Crear collage
48
  selected_frames = random.sample(frame_paths, min(4, len(frame_paths)))
49
  images = [Image.open(img) for img in selected_frames]
50
  margin = 10
51
+ collage = Image.new('RGB',
52
+ (images[0].width * 2 + margin, images[0].height * 2 + margin),
53
+ (255,255,255))
54
  positions = [
55
  (0, 0),
56
  (images[0].width + margin, 0),
 
62
  collage_path = os.path.join(temp_dir, "collage.jpg")
63
  collage.save(collage_path)
64
 
65
+ # Generaci贸n del ZIP con todos los fotogramas
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
  base_name = os.path.splitext(original_name)[0]
67
  zip_filename = f"{base_name}.zip"
68
  final_zip_path = os.path.join(temp_dir, zip_filename)
 
 
 
 
69
 
70
+ with zipfile.ZipFile(final_zip_path, mode="w") as zipf:
71
+ for img_path in frame_paths:
72
+ zipf.write(img_path, os.path.basename(img_path))
73
+
74
+ chain_content = (
75
+ f"Nombre del archivo: {original_name}\n"
76
+ f"Fecha de carga y extracci贸n: {timestamp}\n"
77
+ f"N煤mero de fotogramas: {frame_count}\n"
78
+ )
79
+ zipf.writestr("cadena_custodia.txt", chain_content)
80
+
81
  return collage_path, final_zip_path, temp_dir
82
 
83
  except Exception as e:
 
89
  os.remove(os.path.join(temp_dir, file))
90
  os.rmdir(temp_dir)
91
 
 
 
 
92
  with gr.Blocks(title="Extracci贸n de Fotogramas Forenses") as demo:
93
+ gr.Markdown("# Herramienta de Extracci贸n de Fotogramas Forenses")
94
  gr.Markdown("**Carga un video para extraer TODOS los fotogramas y generar un collage de muestra.**")
95
  gr.Markdown("Desarrollado por Jos茅 R. Leonett para el Grupo de Peritos Forenses Digitales de Guatemala - [www.forensedigital.gt](https://www.forensedigital.gt)")
96
 
97
  with gr.Row():
98
  with gr.Column():
99
+ video_input = gr.Video(label="Subir Video", interactive=True)
100
  procesar_btn = gr.Button("Procesar Fotogramas", interactive=False)
101
  with gr.Column():
102
  gallery_output = gr.Image(label="Collage de Muestra")
103
  download_btn = gr.Button("DESCARGAR FOTOGRAMAS", interactive=False)
104
  download_file = gr.File(label="Archivo ZIP generado", visible=False)
105
 
 
106
  temp_dir_state = gr.State(None)
107
  zip_path_state = gr.State(None)
108
 
109
  def on_video_change(video):
110
+ if video:
111
+ return gr.update(interactive=True), gr.update(interactive=False)
112
+ return gr.update(interactive=False), gr.update(interactive=False)
 
 
 
 
 
 
 
 
 
113
 
114
  video_input.change(
115
  fn=on_video_change,
 
119
  )
120
 
121
  def procesar_y_mostrar(video):
 
122
  if temp_dir_state.value:
123
  limpiar_cache(temp_dir_state.value)
124
  collage_path, zip_path, temp_dir = procesar_video(video)
125
+ return collage_path, zip_path, temp_dir, zip_path, gr.update(interactive=True), gr.update(interactive=False)
 
 
126
 
127
  procesar_btn.click(
128
  fn=procesar_y_mostrar,
 
131
  )
132
 
133
  def trigger_download(zip_path):
 
134
  with open(zip_path, "rb") as f:
135
  file_bytes = f.read()
136
+ return os.path.basename(zip_path), file_bytes
137
 
138
  download_btn.click(
139
  fn=trigger_download,
 
142
  )
143
 
144
  if __name__ == "__main__":
145
+ demo.launch()