Lectura / Grabación metadatos EXIF

Descripción general:

[WPF - NET 10.0] - Lectura / Grabación metadatos EXIF. Como modificar los datos EXIF de una imagen


↑↑↑

A modo de introducción

Hace tiempo que andaba detrás de modificar los datos EXIF de mis fotografiás. Hasta ahora, había conseguido leer los datos EXIF, pero la modificación de los mismos se me había atragantado de forma recurrente todas las veces que lo había intentado

Por una de esas casualidades di con el chiste para modificar los datos EXIF de una imagen que en esencia consiste en graba una nueva imagen con los datos modificados.

El código que muestro a continuación es una versión de demostración de como se realiza el proceso y solamente modifica la fecha de toma. De una forma parecida se pueden modificar el resto de los metadatos de la imagen antes de grabar la nueva imagen.


↑↑↑

Código Demo [Vb,Net]

El siguiente código es completamente funcional, pero es solo un código DEMO de como se hace el trabajo. Como puedes apreciar, solo modifica la fecha, y no trata el resto de los metadatos, pero muestra como se lee una imagen, se modifica sus metadatos, y se graba la nueva imagen. Tampoco esta el control de errores y el control para no sobrescribir la imagen original


Public Class ProgramaExif 

    Private Shared Sub Main(ByVal args As String())
        Dim rutaImagen As String = "C:\Users\jms32\FOTOS\2023-05-11-QuesadaTiscar\Desktop\Nueva carpeta\2026_04_20_049_PT_VilarFormoso.jpg" 
        Dim rutaSalida As String = "C:\Users\jms32\FOTOS\2023-05-11-QuesadaTiscar\Desktop\Nueva carpeta\2026_04_20_049_PT_VilarFormoso_modificada.jpg" 
        Dim nuevaFecha As DateTime = New DateTime(2023,12,25,14,30,0)
        CambiarFechaExif(rutaImagen,rutaSalida,nuevaFecha)
        Console.WriteLine("Fecha EXIF modificada correctamente.")
    End Sub 

    Private Shared Sub CambiarFechaExif(ByVal rutaEntrada As String,ByVal rutaSalida As String,ByVal nuevaFecha As DateTime)
        ' leer la imagen 
        Using stream As FileStream = New FileStream(rutaEntrada,FileMode.Open,FileAccess.Read)
            Dim decoder As BitmapDecoder = BitmapDecoder.Create(stream,BitmapCreateOptions.PreservePixelFormat,BitmapCacheOption.OnLoad)
            Dim frame As BitmapFrame = decoder.Frames(0)

            ' leer los metadata de la imagen 
            Dim metadata As BitmapMetadata = CType(frame.Metadata.Clone(),BitmapMetadata)
            ' modificar la fecha de toma 
            metadata.DateTaken = nuevaFecha.ToString("yyyy:MM:dd HH:mm:ss")
            '--- 
            ' Grabar la nueva imagen 
            Dim encoder As BitmapEncoder = New JpegBitmapEncoder()
            encoder.Frames.Add(BitmapFrame.Create(frame,frame.Thumbnail,metadata,frame.ColorContexts))

            Using salida As FileStream = New FileStream(rutaSalida,FileMode.Create,FileAccess.Write)
                encoder.Save(salida)
            End Using 

        End Using 
    End Sub 
End Class 


Código Demo mejorado [Vb,Net]



''' <summary> 
'''  programa DEMO que cambia la fecha de toma en los metadatos EXIF 
''' </summary> 
Public Class ProgramaExif 

  
    ''' <summary> 
    '''  Llama a la función que cambia una fecha de toma en los metadatos EXIF 
    '''  Genera el nombre del fichero de salida para evitar sobre escribir archivos 
    ''' </summary> 
    ''' <param name = "rutaEntrada">Ruta completa de la imagen original</param> 
    ''' <param name = "rutaSalida">Ruta completa de la imagen modificada</param> 
    ''' <returns>Una cadena que informa del resultado del cambio</returns> 
    Public Shared Function Main(
            ByVal rutaEntrada As String,
            ByVal nuevaFecha As DateTime)As String 

        '------------------------------------------------------- 
        ' Tarea realizada 
        ' Nombre rutaEntrada..........: c:\ruta\2026_04_20_049_PT_VilarFormoso.jpg 
        ' Nombre nuevoNombreModificado: c:\ruta\2026_04_20_049_PT_VilarFormoso_modificada.jpg 
        '------------------------------------------------------- 

        ' Proceso para componer el nuevo nombre de la imagen 
        Dim localRuta As String = System.IO.Path.GetDirectoryName(rutaEntrada)
        If localRuta.EndsWith(System.IO.Path.DirectorySeparatorChar) = False Then 
            localRuta = String.Format("{0}{1}",localRuta,System.IO.Path.DirectorySeparatorChar)
        End If 
        Dim localNombreSinExtension As String = System.IO.Path.GetFileNameWithoutExtension(rutaEntrada)
        Dim localExtensionConPunto As String = System.IO.Path.GetExtension(rutaEntrada)
        If localExtensionConPunto.StartsWith("." c) = False Then 
            localExtensionConPunto = "." c & localExtensionConPunto 
        End If 

        Dim NuevoNombreSinExtension As String = String.Format("{0}_ExifModificado",localNombreSinExtension)
        Dim nuevoNombreModificado As String = String.Format("{0}{1}{2}",localRuta,NuevoNombreSinExtension,localExtensionConPunto)

        Return CambiarFechaExifTres(rutaEntrada,nuevoNombreModificado,nuevaFecha)
    End Function 




    ''' <summary> 
    '''  Este código usa[System.Windows.Media.Imaging] para manipular los metadatos EXIF. 
    '''  Cambia la fecha EXIF "DateTimeOriginal" de una imagen JPEG. 
    ''' </summary> 
    ''' <param name = "rutaEntrada">Ruta completa de la imagen original</param> 
    ''' <param name = "rutaSalida">Ruta completa de la imagen modificada</param> 
    ''' <param name = "nuevaFecha">Nueva fecha y hora.</param> 
    ''' <returns>Una cadena que informa del resultado del cambio</returns> 
    ''' <remarks> 
    '''  ¡¡¡ ATENCIÓN !!! Este código usa[System.Windows.Media.Imaging], NO UTILIZA System.Drawing(GDI+) 
    '''  Este ejemplo funciona con imágenes JPEG que tengan metadatos EXIF. 
    '''  La propiedad DateTaken corresponde a la etiqueta EXIF 36867(DateTimeOriginal). 
    '''  Si la imagen no tiene metadatos, se pueden crear, pero algunos visores podrían no reconocerlos. 
    '''  Siempre trabaja sobre una copia de la imagen original para evitar pérdida de datos. 
    ''' </remarks> 
    Public Shared Function CambiarFechaExif(
            ByVal rutaEntrada As String,
            ByVal rutaSalida As String,
            ByVal nuevaFecha As DateTime)As String 

        Try 
            Using stream As FileStream = New System.IO.FileStream(rutaEntrada,
                                                                  FileMode.Open,
                                                                  FileAccess.Read)

                Dim decoder As BitmapDecoder = BitmapDecoder.Create(stream,
                                                                    BitmapCreateOptions.PreservePixelFormat,
                                                                    BitmapCacheOption.OnLoad)
                Dim frame As BitmapFrame = decoder.Frames(0)

                ' lectura de los metadatos de la imagen 
                Dim metadata As BitmapMetadata = CType(frame.Metadata.Clone(),BitmapMetadata)

                ' Modificación de la fecha de toma 
                metadata.DateTaken = nuevaFecha.ToString("yyyy-MM-dd HH:mm:ss")

                ' 
                '   '------------------------------------------ 
                '   '------------------------------------------ 
                '   ' REFACTORIZADO 
                '   ' Este código funciona pero la calidad de la imagen disminuye bastante. 
                '   ' La modificación realizada a continuación, permite que 
                '   ' la calidad de la imagen sea del 100%. 
                '   ' 
                '   ' Grabar la nueva imagen. 
                '   '   Dim encoder As BitmapEncoder = New JpegBitmapEncoder() 
                '   '   encoder.Frames.Add(BitmapFrame.Create(frame, 
                '   '                                         frame.Thumbnail, 
                '   '                                         metadata, 
                '   '                                         frame.ColorContexts)) 
                '   ' 
                '   '   Using salida As FileStream = New FileStream(rutaSalida, 
                '   '                                               FileMode.Create, 
                '   '                                               FileAccess.Write) 
                '   '       encoder.Save(salida) 
                '   '   End Using 
                '   '/ Eof refactorización 
                '   '------------------------------------------ 
                ' 
                '------------------ 
                ' Calidad total 100% de la imagen 
                Dim nuevoJpegEncoder As System.Windows.Media.Imaging.JpegBitmapEncoder 
                nuevoJpegEncoder = New System.Windows.Media.Imaging.JpegBitmapEncoder With 
                              { 
                               .QualityLevel = 100 
                              } 

                '------------------ 
                ' Codificar la imagen 
                Dim encoderGrabacion As System.Windows.Media.Imaging.BitmapEncoder 
                encoderGrabacion = nuevoJpegEncoder 
                encoderGrabacion.Frames.Add(BitmapFrame.Create(frameLectura,
                                                               frameLectura.Thumbnail,
                                                               metadataLectura,
                                                               frameLectura.ColorContexts))
                '------------------ 
                'Grabar la imagen en un nuevo archivo 
                ' 
                'La opción[CreateNew] 
                'Especifica que el sistema operativo debe crear un archivo nuevo. 
                'Si el archivo ya existe, se genera una excepción IOException. 
                ' 
                Using streamGrabacion As New FileStream(Me.RutaSalidaFI.FullName,
                                                        FileMode.CreateNew,
                                                        FileAccess.Write)

                    encoderGrabacion.Save(streamGrabacion)
                End Using 
            End Using 
            Return "Fecha cambiada sin problemas" 
        Catch ex As Exception 
            Return "Error cambiando fecha EXIF: " & ex.Message 
        End Try 

    End Function 


End Class