Manual de Usuario: Eliminar Metadatos de Imágenes

Introducción

Este script es una aplicación gráfica desarrollada en Python que permite eliminar los metadatos EXIF de imágenes. Los metadatos EXIF contienen información como la fecha de captura, ubicación GPS, configuración de la cámara y otros datos sensibles que pueden comprometer la privacidad.

La aplicación cuenta con una interfaz intuitiva que permite procesar:

Requisitos del Sistema

Requisitos Mínimos

Dependencias

El script requiere las siguientes librerías de Python:

Imágenes Soportadas

Formatos de imagen admitidos:

.jpg
.jpeg
.png
.bmp
.tiff
.tif

Instalación

  1. Asegúrese de tener Python 3 instalado en su sistema.
  2. Instale las dependencias ejecutando el siguiente comando en la terminal:
pip install PySide6 Pillow
  1. Descargue o copie el archivo eliminar_metadatos_imagen.py a una carpeta en su equipo.

Ejecución del Programa

Para ejecutar la aplicación:

  1. Abra una terminal o línea de comandos.
  2. Navegue a la carpeta donde se encuentra el archivo eliminar_metadatos_imagen.py.
  3. Ejecute el comando:
python eliminar_metadatos_imagen.py

Si está utilizando Python 3 explícitamente, use:

python3 eliminar_metadatos_imagen.py

Se abrirá una ventana gráfica con la interfaz de usuario.

Interfaz de Usuario

La aplicación consta de los siguientes elementos:

Uso del Programa

Procesar un Directorio Completo

  1. Haga clic en el botón "Examinar" junto al campo "Directorio".
  2. Seleccione la carpeta que contiene las imágenes a procesar.
  3. Haga clic en "Eliminar Metadatos de Directorio".
  4. Observe el progreso en la barra y los logs.

Procesar una Imagen Individual

  1. Haga clic en el botón "Examinar Archivo" junto al campo "Imagen".
  2. Seleccione el archivo de imagen deseado.
  3. Haga clic en "Eliminar Metadatos de Imagen".
  4. Revise los logs para ver qué metadatos se han eliminado.

Información del Proceso

Durante el procesamiento, el programa:

Nota: El proceso modifica directamente los archivos originales. No se crean copias de respaldo automáticas.

Advertencias Importantes

Solución de Problemas

Errores Comunes

Licencia y Derechos de Autor

Este script es software de código abierto distribuido bajo la licencia MIT. Puede ser modificado y distribuido libremente, siempre que se mantenga la atribución original.

Contacto y Soporte

Para reportar errores o solicitar mejoras, consulte la documentación del proyecto o contacte al desarrollador.

Versión del manual: 1.0

Última actualización: Septiembre 2025

Código Fuente

A continuación se proporciona el código completo del script en Python:

import os
import sys
from PySide6.QtWidgets import (
    QApplication, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit,
    QPushButton, QFileDialog, QTextEdit, QProgressBar
)
from PySide6.QtCore import Qt, QThread, Signal
from PIL import Image, ExifTags


class RemoveMetadataThread(QThread):
    progress = Signal(int)
    log = Signal(str)

    def __init__(self, files, directory_name="directorios"):
        super().__init__()
        self.files = files
        self.directory_name = directory_name

    def run(self):
        total_files = len(self.files)

        if total_files == 0:
            self.log.emit("No se encontraron imágenes.")
            return

        self.log.emit(f"Encontradas {total_files} imágenes en {self.directory_name}. Procesando...")

        for i, filepath in enumerate(self.files):
            filename = os.path.basename(filepath)
            try:
                self.log.emit(f"Procesando: {filename}")
                with Image.open(filepath) as img:
                    # Extraer metadatos EXIF
                    exif_data = {}
                    exif = img._getexif()
                    if exif:
                        for tag, value in exif.items():
                            tag_name = ExifTags.TAGS.get(tag, tag)
                            exif_data[tag_name] = value

                    if exif_data:
                        self.log.emit("Metadatos encontrados:")
                        for k, v in exif_data.items():
                            v_str = str(v)
                            if len(v_str) > 50:
                                v_str = v_str[:50] + "..."
                            self.log.emit(f"  {k}: {v_str}")
                    else:
                        self.log.emit("Sin metadatos EXIF encontrados.")

                    # Eliminar metadatos
                    img.save(filepath, exif=b'')
                    self.log.emit("Metadatos eliminados.")
                self.log.emit("-" * 40)
            except Exception as e:
                self.log.emit(f"Error procesando {filename}: {str(e)}")
                self.log.emit("-" * 40)
            self.progress.emit(int((i + 1) / total_files * 100))

        self.log.emit("Proceso completado.")


class MetadataRemoverApp(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Eliminar Metadatos de Imágenes")
        self.setGeometry(300, 300, 600, 400)

        layout = QVBoxLayout()

        # Etiqueta y campo para el directorio
        dir_layout = QHBoxLayout()
        dir_layout.addWidget(QLabel("Directorio:"))
        self.dir_edit = QLineEdit()
        dir_layout.addWidget(self.dir_edit)
        browse_btn = QPushButton("Examinar")
        browse_btn.clicked.connect(self.browse_directory)
        dir_layout.addWidget(browse_btn)
        layout.addLayout(dir_layout)

        # Etiqueta y campo para imagen suelta
        file_layout = QHBoxLayout()
        file_layout.addWidget(QLabel("Imagen:"))
        self.file_edit = QLineEdit()
        file_layout.addWidget(self.file_edit)
        browse_file_btn = QPushButton("Examinar Archivo")
        browse_file_btn.clicked.connect(self.browse_file)
        file_layout.addWidget(browse_file_btn)
        layout.addLayout(file_layout)

        # Área de log
        self.log_text = QTextEdit()
        self.log_text.setReadOnly(True)
        layout.addWidget(self.log_text)

        # Barra de progreso
        self.progress_bar = QProgressBar()
        layout.addWidget(self.progress_bar)

        # Botones
        btn_layout = QHBoxLayout()
        self.process_btn = QPushButton("Eliminar Metadatos de Directorio")
        self.process_btn.clicked.connect(self.remove_metadata_directory)
        btn_layout.addWidget(self.process_btn)
        self.process_file_btn = QPushButton("Eliminar Metadatos de Imagen")
        self.process_file_btn.clicked.connect(self.remove_metadata_file)
        btn_layout.addWidget(self.process_file_btn)
        self.clear_btn = QPushButton("Limpiar")
        self.clear_btn.clicked.connect(self.clear_log)
        btn_layout.addWidget(self.clear_btn)
        layout.addLayout(btn_layout)

        self.setLayout(layout)

        # Hilo para procesamiento
        self.thread = None

    def browse_directory(self):
        directory = QFileDialog.getExistingDirectory(self, "Seleccionar Directorio")
        if directory:
            self.dir_edit.setText(directory)

    def browse_file(self):
        file_path, _ = QFileDialog.getOpenFileName(self, "Seleccionar Imagen", "", "Imágenes (*.jpg *.jpeg *.png *.bmp *.tiff *.tif)")
        if file_path:
            self.file_edit.setText(file_path)

    def remove_metadata_directory(self):
        directory = self.dir_edit.text().strip()
        if not directory:
            self.log_text.append("Por favor, selecciona un directorio.")
            return

        if not os.path.isdir(directory):
            self.log_text.append(f"El directorio '{directory}' no existe.")
            return

        image_extensions = {'.jpg', '.jpeg', '.png', '.bmp', '.tiff', '.tif'}
        files = [os.path.join(directory, f) for f in os.listdir(directory) if os.path.splitext(f)[1].lower() in image_extensions]

        self.process_btn.setEnabled(False)
        self.process_file_btn.setEnabled(False)
        self.log_text.clear()
        self.progress_bar.setValue(0)

        self.thread = RemoveMetadataThread(files, os.path.basename(directory))
        self.thread.progress.connect(self.progress_bar.setValue)
        self.thread.log.connect(self.log_text.append)
        self.thread.finished.connect(self.enable_buttons)
        self.thread.start()

    def remove_metadata_file(self):
        file_path = self.file_edit.text().strip()
        if not file_path:
            self.log_text.append("Por favor, selecciona una imagen.")
            return

        if not os.path.isfile(file_path):
            self.log_text.append(f"El archivo '{file_path}' no existe.")
            return

        image_extensions = {'.jpg', '.jpeg', '.png', '.bmp', '.tiff', '.tif'}
        if os.path.splitext(file_path)[1].lower() not in image_extensions:
            self.log_text.append(f"El archivo '{file_path}' no es una imagen soportada.")
            return

        self.process_btn.setEnabled(False)
        self.process_file_btn.setEnabled(False)
        self.log_text.clear()
        self.progress_bar.setValue(0)

        self.thread = RemoveMetadataThread([file_path], "imagen suelta")
        self.thread.progress.connect(self.progress_bar.setValue)
        self.thread.log.connect(self.log_text.append)
        self.thread.finished.connect(self.enable_buttons)
        self.thread.start()

    def clear_log(self):
        self.log_text.clear()

    def enable_buttons(self):
        self.process_btn.setEnabled(True)
        self.process_file_btn.setEnabled(True)


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MetadataRemoverApp()
    window.show()
    sys.exit(app.exec())