leonett commited on
Commit
32450de
verified
1 Parent(s): 57ef3c4

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +107 -45
app.py CHANGED
@@ -5,10 +5,12 @@ import random
5
  import zipfile
6
  from PIL import Image
7
  from datetime import datetime
 
 
8
 
9
  def procesar_video(video):
10
  try:
11
- # Determinar la ruta del video y el nombre original
12
  if isinstance(video, dict):
13
  original_name = video.get("name", "video")
14
  video_path = video.get("file", video.get("data"))
@@ -16,60 +18,103 @@ def procesar_video(video):
16
  original_name = os.path.basename(video)
17
  video_path = video
18
 
19
- # Configurar directorio temporal
 
 
 
 
 
 
 
20
  temp_dir = f"temp_{datetime.now().strftime('%Y%m%d%H%M%S')}"
21
  os.makedirs(temp_dir, exist_ok=True)
22
-
23
- # Leer el video y extraer TODOS los frames
24
  cap = cv2.VideoCapture(video_path)
25
  frame_count = 0
26
  frame_paths = []
27
-
28
  while True:
29
  ret, frame = cap.read()
30
  if not ret:
31
  break
32
-
33
- # Guardar cada frame
34
  frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
35
  img = Image.fromarray(frame_rgb)
36
  img_path = os.path.join(temp_dir, f"frame_{frame_count:04d}.jpg")
37
  img.save(img_path)
38
  frame_paths.append(img_path)
39
  frame_count += 1
40
-
41
  cap.release()
42
-
43
  if frame_count == 0:
44
- raise ValueError("No se pudieron extraer frames del video")
45
-
46
- # Seleccionar 4 frames aleatorios para el collage
47
  selected_frames = random.sample(frame_paths, min(4, len(frame_paths)))
48
-
49
- # Crear collage
50
  images = [Image.open(img) for img in selected_frames]
51
  collage = Image.new('RGB', (images[0].width * 2, images[0].height * 2))
52
-
53
  for i, img in enumerate(images):
54
  row = i // 2
55
  col = i % 2
56
- x = col * images[0].width
57
- y = row * images[0].height
58
- collage.paste(img, (x, y))
59
-
60
  collage_path = os.path.join(temp_dir, "collage.jpg")
61
  collage.save(collage_path)
62
-
63
- # Crear archivo ZIP con TODOS los frames usando el nombre original del video
64
- base_name = os.path.splitext(original_name)[0]
65
- zip_filename = f"{base_name}.zip"
66
- zip_path = os.path.join(temp_dir, zip_filename)
67
- with zipfile.ZipFile(zip_path, 'w') as zipf:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
  for img in frame_paths:
69
  zipf.write(img, os.path.basename(img))
 
 
 
 
 
 
 
 
 
 
 
70
 
71
- return collage_path, zip_path, temp_dir
72
-
73
  except Exception as e:
74
  raise gr.Error(f"Error al procesar el video: {str(e)}")
75
 
@@ -79,6 +124,9 @@ def limpiar_cache(temp_dir):
79
  os.remove(os.path.join(temp_dir, file))
80
  os.rmdir(temp_dir)
81
 
 
 
 
82
  with gr.Blocks(title="Extracci贸n de Fotogramas Forenses") as demo:
83
  gr.Markdown("# Herramienta de Extracci贸n de Fotogramas Forenses")
84
  gr.Markdown("**Carga un video para extraer TODOS los fotogramas y generar un collage de muestra.**")
@@ -86,43 +134,57 @@ with gr.Blocks(title="Extracci贸n de Fotogramas Forenses") as demo:
86
 
87
  with gr.Row():
88
  with gr.Column():
89
- video_input = gr.Video(label="Subir Video", interactive=True)
 
90
  procesar_btn = gr.Button("Procesar Fotogramas", interactive=False)
91
-
92
  with gr.Column():
93
  gallery_output = gr.Image(label="Collage de Muestra")
94
  download_btn = gr.Button("DESCARGAR FOTOGRAMAS", interactive=False)
95
  download_file = gr.File(label="Archivo ZIP generado", visible=False)
96
 
97
  # Estados para guardar el directorio temporal y la ruta del ZIP
98
- temp_dir_state = gr.State()
99
- zip_path_state = gr.State()
100
-
101
- # Habilita el bot贸n de procesar solo si se ha subido un video
102
- def habilitar_procesar(video):
103
- return gr.Button.update(interactive=True) if video else gr.Button.update(interactive=False)
104
 
105
- # Procesa el video, genera el collage y el ZIP y actualiza los estados
106
- def procesar_y_mostrar(video):
107
- if temp_dir_state.value:
108
- limpiar_cache(temp_dir_state.value)
109
- collage_path, zip_path, temp_dir = procesar_video(video)
110
- return collage_path, zip_path, temp_dir, zip_path, gr.Button.update(interactive=True)
 
 
 
 
 
 
 
111
 
112
  video_input.change(
113
- fn=habilitar_procesar,
114
  inputs=video_input,
115
- outputs=procesar_btn,
116
  queue=False
117
  )
118
 
 
 
 
 
 
 
 
 
 
 
 
119
  procesar_btn.click(
120
  fn=procesar_y_mostrar,
121
  inputs=video_input,
122
- outputs=[gallery_output, download_file, temp_dir_state, zip_path_state, download_btn],
123
  )
124
 
125
- # Al pulsar "DESCARGAR FOTOGRAMAS", se env铆a la ruta del ZIP almacenada en zip_path_state
126
  download_btn.click(
127
  fn=lambda zip_path: zip_path,
128
  inputs=zip_path_state,
 
5
  import zipfile
6
  from PIL import Image
7
  from datetime import datetime
8
+ import io
9
+ 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"))
 
18
  original_name = os.path.basename(video)
19
  video_path = video
20
 
21
+ allowed_extensions = ('.mp4', '.avi', '.mov', '.mkv')
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 = []
 
36
  while True:
37
  ret, frame = cap.read()
38
  if not ret:
39
  break
 
 
40
  frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
41
  img = Image.fromarray(frame_rgb)
42
  img_path = os.path.join(temp_dir, f"frame_{frame_count:04d}.jpg")
43
  img.save(img_path)
44
  frame_paths.append(img_path)
45
  frame_count += 1
 
46
  cap.release()
47
+
48
  if frame_count == 0:
49
+ raise gr.Error("No se pudieron extraer fotogramas del video")
50
+
51
+ # Crear collage de 4 fotogramas aleatorios
52
  selected_frames = random.sample(frame_paths, min(4, len(frame_paths)))
 
 
53
  images = [Image.open(img) for img in selected_frames]
54
  collage = Image.new('RGB', (images[0].width * 2, images[0].height * 2))
 
55
  for i, img in enumerate(images):
56
  row = i // 2
57
  col = i % 2
58
+ collage.paste(img, (col * images[0].width, row * images[0].height))
 
 
 
59
  collage_path = os.path.join(temp_dir, "collage.jpg")
60
  collage.save(collage_path)
61
+
62
+ # ===============================
63
+ # Generaci贸n de la cadena de custodia
64
+ # -------------------------------
65
+ # 1. Crear ZIP provisional en memoria con SOLO los fotogramas
66
+ provisional_zip = io.BytesIO()
67
+ with zipfile.ZipFile(provisional_zip, mode="w") as zipf:
68
+ for img in frame_paths:
69
+ zipf.write(img, os.path.basename(img))
70
+ provisional_zip.seek(0)
71
+ provisional_data = provisional_zip.getvalue()
72
+ md5_provisional = hashlib.md5(provisional_data).hexdigest()
73
+
74
+ # 2. Crear un ZIP provisional en memoria que incluya un archivo TXT con marcador de posici贸n para el MD5 final
75
+ final_zip_mem = io.BytesIO()
76
+ chain_placeholder = "MD5 final del ZIP: <pending>"
77
+ chain_initial = (
78
+ f"Nombre del archivo: {original_name}\n"
79
+ f"Fecha de carga y extracci贸n: {timestamp}\n"
80
+ f"N煤mero de fotogramas: {frame_count}\n"
81
+ f"MD5 provisional (frames): {md5_provisional}\n"
82
+ f"{chain_placeholder}\n"
83
+ )
84
+ with zipfile.ZipFile(final_zip_mem, mode="w") as zipf:
85
+ for img in frame_paths:
86
+ zipf.write(img, os.path.basename(img))
87
+ zipf.writestr("chain_of_custody.txt", chain_initial)
88
+ final_zip_mem.seek(0)
89
+ final_zip_data = final_zip_mem.getvalue()
90
+ md5_final = hashlib.md5(final_zip_data).hexdigest()
91
+
92
+ # 3. Reconstruir el ZIP final en memoria con el TXT actualizado (incluyendo ambos MD5)
93
+ final_chain = (
94
+ f"Nombre del archivo: {original_name}\n"
95
+ f"Fecha de carga y extracci贸n: {timestamp}\n"
96
+ f"N煤mero de fotogramas: {frame_count}\n"
97
+ f"MD5 provisional (frames): {md5_provisional}\n"
98
+ f"MD5 final del ZIP: {md5_final}\n"
99
+ )
100
+ final_zip_mem2 = io.BytesIO()
101
+ with zipfile.ZipFile(final_zip_mem2, mode="w") as zipf:
102
  for img in frame_paths:
103
  zipf.write(img, os.path.basename(img))
104
+ zipf.writestr("chain_of_custody.txt", final_chain)
105
+ final_zip_mem2.seek(0)
106
+ final_zip_data2 = final_zip_mem2.getvalue()
107
+
108
+ # Escribir el ZIP final a disco con el nombre basado en el video original
109
+ base_name = os.path.splitext(original_name)[0]
110
+ zip_filename = f"{base_name}.zip"
111
+ final_zip_path = os.path.join(temp_dir, zip_filename)
112
+ with open(final_zip_path, "wb") as f:
113
+ f.write(final_zip_data2)
114
+ # ===============================
115
 
116
+ return collage_path, final_zip_path, temp_dir
117
+
118
  except Exception as e:
119
  raise gr.Error(f"Error al procesar el video: {str(e)}")
120
 
 
124
  os.remove(os.path.join(temp_dir, file))
125
  os.rmdir(temp_dir)
126
 
127
+ # -------------------------------
128
+ # Interfaz Gradio
129
+ # -------------------------------
130
  with gr.Blocks(title="Extracci贸n de Fotogramas Forenses") as demo:
131
  gr.Markdown("# Herramienta de Extracci贸n de Fotogramas Forenses")
132
  gr.Markdown("**Carga un video para extraer TODOS los fotogramas y generar un collage de muestra.**")
 
134
 
135
  with gr.Row():
136
  with gr.Column():
137
+ video_input = gr.Video(label="Subir Video (solo archivos de video)", interactive=True)
138
+ # Ambos botones inician desactivados
139
  procesar_btn = gr.Button("Procesar Fotogramas", interactive=False)
 
140
  with gr.Column():
141
  gallery_output = gr.Image(label="Collage de Muestra")
142
  download_btn = gr.Button("DESCARGAR FOTOGRAMAS", interactive=False)
143
  download_file = gr.File(label="Archivo ZIP generado", visible=False)
144
 
145
  # Estados para guardar el directorio temporal y la ruta del ZIP
146
+ temp_dir_state = gr.State(None)
147
+ zip_path_state = gr.State(None)
 
 
 
 
148
 
149
+ # Al cargar un video: si es v谩lido, se activa el bot贸n "Procesar Fotogramas" y se desactiva "DESCARGAR FOTOGRAMAS"
150
+ def on_video_change(video):
151
+ if video is None:
152
+ return gr.Button.update(interactive=False), gr.Button.update(interactive=False)
153
+ if isinstance(video, dict):
154
+ original_name = video.get("name", "")
155
+ else:
156
+ original_name = os.path.basename(video)
157
+ allowed_extensions = ('.mp4', '.avi', '.mov', '.mkv')
158
+ if not original_name.lower().endswith(allowed_extensions):
159
+ raise gr.Error("Solo se permiten archivos de video (mp4, avi, mov, mkv)")
160
+ # Habilitar "Procesar Fotogramas" y deshabilitar "DESCARGAR FOTOGRAMAS"
161
+ return gr.Button.update(interactive=True), gr.Button.update(interactive=False)
162
 
163
  video_input.change(
164
+ fn=on_video_change,
165
  inputs=video_input,
166
+ outputs=[procesar_btn, download_btn],
167
  queue=False
168
  )
169
 
170
+ # Al pulsar "Procesar Fotogramas": se procesa el video, se genera el collage y el ZIP, se desactiva el bot贸n de procesar
171
+ # y se activa el bot贸n de descarga.
172
+ def procesar_y_mostrar(video):
173
+ # Desactivar el bot贸n de procesamiento para evitar reprocesos
174
+ btn_proc_update = gr.Button.update(interactive=False)
175
+ if temp_dir_state.value:
176
+ limpiar_cache(temp_dir_state.value)
177
+ collage_path, zip_path, temp_dir = procesar_video(video)
178
+ btn_download_update = gr.Button.update(interactive=True)
179
+ return collage_path, zip_path, temp_dir, zip_path, btn_download_update, btn_proc_update
180
+
181
  procesar_btn.click(
182
  fn=procesar_y_mostrar,
183
  inputs=video_input,
184
+ outputs=[gallery_output, download_file, temp_dir_state, zip_path_state, download_btn, procesar_btn],
185
  )
186
 
187
+ # Al pulsar "DESCARGAR FOTOGRAMAS": se env铆a la ruta del ZIP generado.
188
  download_btn.click(
189
  fn=lambda zip_path: zip_path,
190
  inputs=zip_path_state,