mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-09-08 13:18:52 +00:00
This tag match the EXIF_TAG_FOCAL_LENGTH_IN_35_MM_FILM exif tag and is stored on a short. Hence there is a precision loss compared to the GstTag which is a double value. https://bugzilla.gnome.org/show_bug.cgi?id=753930
2886 lines
87 KiB
C
2886 lines
87 KiB
C
/* GStreamer
|
|
* Copyright (C) 2010 Thiago Santos <thiago.sousa.santos@collabora.co.uk>
|
|
*
|
|
* gstexiftag.c: library for reading / modifying exif tags
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Library General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Library General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Library General Public
|
|
* License along with this library; if not, write to the
|
|
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
|
* Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
/**
|
|
* SECTION:gsttagexif
|
|
* @short_description: tag mappings and support functions for plugins
|
|
* dealing with exif tags
|
|
* @see_also: #GstTagList
|
|
*
|
|
* Contains utility function to parse #GstTagList<!-- -->s from exif
|
|
* buffers and to create exif buffers from #GstTagList<!-- -->s
|
|
*
|
|
* Note that next IFD fields on the created exif buffers are set to 0.
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
#include <gst/gsttagsetter.h>
|
|
#include <gst/base/gstbytewriter.h>
|
|
#include "gsttageditingprivate.h"
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <gst/math-compat.h>
|
|
|
|
/* Some useful constants */
|
|
#define TIFF_LITTLE_ENDIAN 0x4949
|
|
#define TIFF_BIG_ENDIAN 0x4D4D
|
|
#define TIFF_HEADER_SIZE 8
|
|
#define EXIF_TAG_ENTRY_SIZE (2 + 2 + 4 + 4)
|
|
|
|
/* Exif tag types */
|
|
#define EXIF_TYPE_BYTE 1
|
|
#define EXIF_TYPE_ASCII 2
|
|
#define EXIF_TYPE_SHORT 3
|
|
#define EXIF_TYPE_LONG 4
|
|
#define EXIF_TYPE_RATIONAL 5
|
|
#define EXIF_TYPE_UNDEFINED 7
|
|
#define EXIF_TYPE_SLONG 9
|
|
#define EXIF_TYPE_SRATIONAL 10
|
|
|
|
typedef struct _GstExifTagMatch GstExifTagMatch;
|
|
typedef struct _GstExifWriter GstExifWriter;
|
|
typedef struct _GstExifReader GstExifReader;
|
|
typedef struct _GstExifTagData GstExifTagData;
|
|
|
|
typedef void (*GstExifSerializationFunc) (GstExifWriter * writer,
|
|
const GstTagList * taglist, const GstExifTagMatch * exiftag);
|
|
|
|
/*
|
|
* Function used to deserialize tags that don't follow the usual
|
|
* deserialization conversions. Usually those that have 'Ref' complementary
|
|
* tags.
|
|
*
|
|
* Those functions receive a exif tag data in the parameters, plus the taglist
|
|
* and the reader and buffer if they need to get more information to build
|
|
* its tags. There are lots of parameters, but this is needed to make it
|
|
* versatile. Explanation of them follows:
|
|
*
|
|
* exif_reader: The #GstExifReader with the reading parameter and taglist for
|
|
* results.
|
|
* reader: The #GstByteReader pointing to the start of the next tag entry in
|
|
* the ifd, useful for tags that use other complementary tags.
|
|
* the buffer start
|
|
* exiftag: The #GstExifTagMatch that contains this tag info
|
|
* tagdata: values from the already parsed tag
|
|
*/
|
|
typedef gint (*GstExifDeserializationFunc) (GstExifReader * exif_reader,
|
|
GstByteReader * reader, const GstExifTagMatch * exiftag,
|
|
GstExifTagData * tagdata);
|
|
|
|
#define EXIF_SERIALIZATION_FUNC(name) \
|
|
static void serialize_ ## name (GstExifWriter * writer, \
|
|
const GstTagList * taglist, const GstExifTagMatch * exiftag)
|
|
|
|
#define EXIF_DESERIALIZATION_FUNC(name) \
|
|
static gint deserialize_ ## name (GstExifReader * exif_reader, \
|
|
GstByteReader * reader, const GstExifTagMatch * exiftag, \
|
|
GstExifTagData * tagdata)
|
|
|
|
#define EXIF_SERIALIZATION_DESERIALIZATION_FUNC(name) \
|
|
EXIF_SERIALIZATION_FUNC (name); \
|
|
EXIF_DESERIALIZATION_FUNC (name)
|
|
|
|
/*
|
|
* A common case among serialization/deserialization routines is that
|
|
* the gstreamer tag is a string (with a predefined set of allowed values)
|
|
* and exif is an int. These macros cover these cases
|
|
*/
|
|
#define EXIF_SERIALIZATION_MAP_STRING_TO_INT_FUNC(name,funcname) \
|
|
static void \
|
|
serialize_ ## name (GstExifWriter * writer, const GstTagList * taglist, \
|
|
const GstExifTagMatch * exiftag) \
|
|
{ \
|
|
gchar *str = NULL; \
|
|
gint exif_value; \
|
|
\
|
|
if (!gst_tag_list_get_string_index (taglist, exiftag->gst_tag, 0, &str)) { \
|
|
GST_WARNING ("No %s tag present in taglist", exiftag->gst_tag); \
|
|
return; \
|
|
} \
|
|
\
|
|
exif_value = __exif_tag_ ## funcname ## _to_exif_value (str); \
|
|
if (exif_value == -1) { \
|
|
g_free (str); \
|
|
return; \
|
|
} \
|
|
g_free (str); \
|
|
\
|
|
switch (exiftag->exif_type) { \
|
|
case EXIF_TYPE_SHORT: \
|
|
gst_exif_writer_write_short_tag (writer, exiftag->exif_tag, exif_value); \
|
|
break; \
|
|
case EXIF_TYPE_LONG: \
|
|
gst_exif_writer_write_long_tag (writer, exiftag->exif_tag, exif_value); \
|
|
break; \
|
|
case EXIF_TYPE_UNDEFINED: \
|
|
{ \
|
|
guint8 data = (guint8) exif_value; \
|
|
write_exif_undefined_tag (writer, exiftag->exif_tag, &data, 1); \
|
|
} \
|
|
break; \
|
|
default: \
|
|
g_assert_not_reached (); \
|
|
GST_WARNING ("Unmapped serialization for type %d", exiftag->exif_type); \
|
|
break; \
|
|
} \
|
|
}
|
|
|
|
#define EXIF_DESERIALIZATION_MAP_STRING_TO_INT_FUNC(name,funcname) \
|
|
static gint \
|
|
deserialize_ ## name (GstExifReader * exif_reader, \
|
|
GstByteReader * reader, const GstExifTagMatch * exiftag, \
|
|
GstExifTagData * tagdata) \
|
|
{ \
|
|
const gchar *str = NULL; \
|
|
gint value; \
|
|
\
|
|
GST_LOG ("Starting to parse %s tag in exif 0x%x", exiftag->gst_tag, \
|
|
exiftag->exif_tag); \
|
|
\
|
|
/* validate tag */ \
|
|
if (tagdata->count != 1) { \
|
|
GST_WARNING ("0x%X has unexpected count", tagdata->count); \
|
|
return 0; \
|
|
} \
|
|
\
|
|
if (tagdata->tag_type == EXIF_TYPE_SHORT) { \
|
|
if (exif_reader->byte_order == G_LITTLE_ENDIAN) { \
|
|
value = GST_READ_UINT16_LE (tagdata->offset_as_data); \
|
|
} else { \
|
|
value = GST_READ_UINT16_BE (tagdata->offset_as_data); \
|
|
} \
|
|
} else if (tagdata->tag_type == EXIF_TYPE_UNDEFINED) { \
|
|
value = GST_READ_UINT8 (tagdata->offset_as_data); \
|
|
} else { \
|
|
GST_WARNING ("0x%X has unexpected type %d", exiftag->exif_tag, \
|
|
tagdata->tag_type); \
|
|
return 0; \
|
|
} \
|
|
\
|
|
str = __exif_tag_## funcname ## _from_exif_value (value); \
|
|
if (str == NULL) { \
|
|
GST_WARNING ("Invalid value for tag 0x%X: %d", tagdata->tag, value); \
|
|
return 0; \
|
|
} \
|
|
gst_tag_list_add (exif_reader->taglist, GST_TAG_MERGE_REPLACE, \
|
|
exiftag->gst_tag, str, NULL); \
|
|
\
|
|
return 0; \
|
|
}
|
|
|
|
#define EXIF_SERIALIZATION_DESERIALIZATION_MAP_STRING_TO_INT_FUNC(name,funcname) \
|
|
EXIF_SERIALIZATION_MAP_STRING_TO_INT_FUNC(name,funcname); \
|
|
EXIF_DESERIALIZATION_MAP_STRING_TO_INT_FUNC(name,funcname);
|
|
|
|
struct _GstExifTagMatch
|
|
{
|
|
const gchar *gst_tag;
|
|
guint16 exif_tag;
|
|
guint16 exif_type;
|
|
|
|
/* for tags that need special handling */
|
|
guint16 complementary_tag;
|
|
GstExifSerializationFunc serialize;
|
|
GstExifDeserializationFunc deserialize;
|
|
};
|
|
|
|
struct _GstExifTagData
|
|
{
|
|
guint16 tag;
|
|
guint16 tag_type;
|
|
guint32 count;
|
|
guint32 offset;
|
|
const guint8 *offset_as_data;
|
|
};
|
|
|
|
/*
|
|
* Holds the info and variables necessary to write
|
|
* the exif tags properly
|
|
*/
|
|
struct _GstExifWriter
|
|
{
|
|
GstByteWriter tagwriter;
|
|
GstByteWriter datawriter;
|
|
|
|
gint byte_order;
|
|
guint tags_total;
|
|
};
|
|
|
|
struct _GstExifReader
|
|
{
|
|
GstTagList *taglist;
|
|
GstBuffer *buffer;
|
|
guint32 base_offset;
|
|
gint byte_order;
|
|
|
|
/* tags waiting for their complementary tags */
|
|
GSList *pending_tags;
|
|
};
|
|
|
|
EXIF_SERIALIZATION_DESERIALIZATION_FUNC (aperture_value);
|
|
EXIF_SERIALIZATION_DESERIALIZATION_FUNC (contrast);
|
|
EXIF_SERIALIZATION_DESERIALIZATION_FUNC (exposure_program);
|
|
EXIF_SERIALIZATION_DESERIALIZATION_FUNC (exposure_mode);
|
|
EXIF_SERIALIZATION_DESERIALIZATION_FUNC (flash);
|
|
EXIF_SERIALIZATION_DESERIALIZATION_FUNC (gain_control);
|
|
EXIF_SERIALIZATION_DESERIALIZATION_FUNC (geo_coordinate);
|
|
EXIF_SERIALIZATION_DESERIALIZATION_FUNC (geo_direction);
|
|
EXIF_SERIALIZATION_DESERIALIZATION_FUNC (geo_elevation);
|
|
EXIF_SERIALIZATION_DESERIALIZATION_FUNC (metering_mode);
|
|
EXIF_SERIALIZATION_DESERIALIZATION_FUNC (orientation);
|
|
EXIF_SERIALIZATION_DESERIALIZATION_FUNC (saturation);
|
|
EXIF_SERIALIZATION_DESERIALIZATION_FUNC (scene_capture_type);
|
|
EXIF_SERIALIZATION_DESERIALIZATION_FUNC (scene_type);
|
|
EXIF_SERIALIZATION_DESERIALIZATION_FUNC (sensitivity_type);
|
|
EXIF_SERIALIZATION_DESERIALIZATION_FUNC (sharpness);
|
|
EXIF_SERIALIZATION_DESERIALIZATION_FUNC (shutter_speed);
|
|
EXIF_SERIALIZATION_DESERIALIZATION_FUNC (source);
|
|
EXIF_SERIALIZATION_DESERIALIZATION_FUNC (speed);
|
|
EXIF_SERIALIZATION_DESERIALIZATION_FUNC (white_balance);
|
|
|
|
EXIF_DESERIALIZATION_FUNC (resolution);
|
|
EXIF_DESERIALIZATION_FUNC (add_to_pending_tags);
|
|
|
|
/* FIXME copyright tag has a weird "artist\0editor\0" format that is
|
|
* not yet handled */
|
|
|
|
/* exif tag numbers */
|
|
#define EXIF_TAG_GPS_LATITUDE_REF 0x1
|
|
#define EXIF_TAG_GPS_LATITUDE 0x2
|
|
#define EXIF_TAG_GPS_LONGITUDE_REF 0x3
|
|
#define EXIF_TAG_GPS_LONGITUDE 0x4
|
|
#define EXIF_TAG_GPS_ALTITUDE_REF 0x5
|
|
#define EXIF_TAG_GPS_ALTITUDE 0x6
|
|
#define EXIF_TAG_GPS_SPEED_REF 0xC
|
|
#define EXIF_TAG_GPS_SPEED 0xD
|
|
#define EXIF_TAG_GPS_TRACK_REF 0xE
|
|
#define EXIF_TAG_GPS_TRACK 0xF
|
|
#define EXIF_TAG_GPS_IMAGE_DIRECTION_REF 0x10
|
|
#define EXIF_TAG_GPS_IMAGE_DIRECTION 0x11
|
|
#define EXIF_TAG_GPS_HORIZONTAL_POSITIONING_ERROR 0x1F
|
|
#define EXIF_TAG_IMAGE_DESCRIPTION 0x10E
|
|
#define EXIF_TAG_MAKE 0x10F
|
|
#define EXIF_TAG_MODEL 0x110
|
|
#define EXIF_TAG_ORIENTATION 0x112
|
|
#define EXIF_TAG_XRESOLUTION 0x11A
|
|
#define EXIF_TAG_YRESOLUTION 0x11B
|
|
#define EXIF_TAG_RESOLUTION_UNIT 0x128
|
|
#define EXIF_TAG_SOFTWARE 0x131
|
|
#define EXIF_TAG_DATE_TIME 0x132
|
|
#define EXIF_TAG_ARTIST 0x13B
|
|
#define EXIF_TAG_COPYRIGHT 0x8298
|
|
#define EXIF_TAG_EXPOSURE_TIME 0x829A
|
|
#define EXIF_TAG_F_NUMBER 0x829D
|
|
#define EXIF_TAG_EXPOSURE_PROGRAM 0x8822
|
|
#define EXIF_TAG_PHOTOGRAPHIC_SENSITIVITY 0x8827
|
|
#define EXIF_TAG_SENSITIVITY_TYPE 0x8830
|
|
#define EXIF_TAG_ISO_SPEED 0x8833
|
|
#define EXIF_TAG_DATE_TIME_ORIGINAL 0x9003
|
|
#define EXIF_TAG_DATE_TIME_DIGITIZED 0x9004
|
|
#define EXIF_TAG_SHUTTER_SPEED_VALUE 0x9201
|
|
#define EXIF_TAG_APERTURE_VALUE 0x9202
|
|
#define EXIF_TAG_EXPOSURE_BIAS 0x9204
|
|
#define EXIF_TAG_METERING_MODE 0x9207
|
|
#define EXIF_TAG_FLASH 0x9209
|
|
#define EXIF_TAG_FOCAL_LENGTH 0x920A
|
|
#define EXIF_TAG_MAKER_NOTE 0x927C
|
|
#define EXIF_TAG_FILE_SOURCE 0xA300
|
|
#define EXIF_TAG_SCENE_TYPE 0xA301
|
|
#define EXIF_TAG_EXPOSURE_MODE 0xA402
|
|
#define EXIF_TAG_WHITE_BALANCE 0xA403
|
|
#define EXIF_TAG_DIGITAL_ZOOM_RATIO 0xA404
|
|
#define EXIF_TAG_FOCAL_LENGTH_IN_35_MM_FILM 0xa405
|
|
#define EXIF_TAG_SCENE_CAPTURE_TYPE 0xA406
|
|
#define EXIF_TAG_GAIN_CONTROL 0xA407
|
|
#define EXIF_TAG_CONTRAST 0xA408
|
|
#define EXIF_TAG_SATURATION 0xA409
|
|
#define EXIF_TAG_SHARPNESS 0xA40A
|
|
|
|
/* IFD pointer tags */
|
|
#define EXIF_IFD_TAG 0x8769
|
|
#define EXIF_GPS_IFD_TAG 0x8825
|
|
|
|
/* version tags */
|
|
#define EXIF_VERSION_TAG 0x9000
|
|
#define EXIF_FLASHPIX_VERSION_TAG 0xA000
|
|
|
|
/* useful macros for speed tag */
|
|
#define METERS_PER_SECOND_TO_KILOMETERS_PER_HOUR (3.6)
|
|
#define KILOMETERS_PER_HOUR_TO_METERS_PER_SECOND (1/3.6)
|
|
#define MILES_PER_HOUR_TO_METERS_PER_SECOND (0.44704)
|
|
#define KNOTS_TO_METERS_PER_SECOND (0.514444)
|
|
|
|
/*
|
|
* Should be kept in ascending id order
|
|
*
|
|
* {gst-tag, exif-tag, exig-type, complementary-exif-tag, serialization-func,
|
|
* deserialization-func}
|
|
*/
|
|
static const GstExifTagMatch tag_map_ifd0[] = {
|
|
{GST_TAG_IMAGE_HORIZONTAL_PPI, EXIF_TAG_XRESOLUTION, EXIF_TYPE_RATIONAL,
|
|
0, NULL, deserialize_add_to_pending_tags},
|
|
{GST_TAG_IMAGE_VERTICAL_PPI, EXIF_TAG_YRESOLUTION, EXIF_TYPE_RATIONAL,
|
|
0, NULL, deserialize_add_to_pending_tags},
|
|
{NULL, EXIF_TAG_RESOLUTION_UNIT, EXIF_TYPE_SHORT, 0, NULL,
|
|
deserialize_resolution},
|
|
{GST_TAG_DESCRIPTION, EXIF_TAG_IMAGE_DESCRIPTION, EXIF_TYPE_ASCII, 0, NULL,
|
|
NULL},
|
|
{GST_TAG_DEVICE_MANUFACTURER, EXIF_TAG_MAKE, EXIF_TYPE_ASCII, 0, NULL, NULL},
|
|
{GST_TAG_DEVICE_MODEL, EXIF_TAG_MODEL, EXIF_TYPE_ASCII, 0, NULL, NULL},
|
|
{GST_TAG_IMAGE_ORIENTATION, EXIF_TAG_ORIENTATION, EXIF_TYPE_SHORT, 0,
|
|
serialize_orientation,
|
|
deserialize_orientation},
|
|
{GST_TAG_APPLICATION_NAME, EXIF_TAG_SOFTWARE, EXIF_TYPE_ASCII, 0, NULL, NULL},
|
|
{GST_TAG_DATE_TIME, EXIF_TAG_DATE_TIME, EXIF_TYPE_ASCII, 0, NULL, NULL},
|
|
{GST_TAG_ARTIST, EXIF_TAG_ARTIST, EXIF_TYPE_ASCII, 0, NULL, NULL},
|
|
{GST_TAG_COPYRIGHT, EXIF_TAG_COPYRIGHT, EXIF_TYPE_ASCII, 0, NULL, NULL},
|
|
{NULL, EXIF_IFD_TAG, EXIF_TYPE_LONG, 0, NULL, NULL},
|
|
{NULL, EXIF_GPS_IFD_TAG, EXIF_TYPE_LONG, 0, NULL, NULL},
|
|
{NULL, 0, 0, 0, NULL, NULL}
|
|
};
|
|
|
|
static const GstExifTagMatch tag_map_exif[] = {
|
|
{GST_TAG_CAPTURING_SHUTTER_SPEED, EXIF_TAG_EXPOSURE_TIME, EXIF_TYPE_RATIONAL,
|
|
0,
|
|
NULL, NULL},
|
|
{GST_TAG_CAPTURING_FOCAL_RATIO, EXIF_TAG_F_NUMBER, EXIF_TYPE_RATIONAL, 0,
|
|
NULL,
|
|
NULL},
|
|
{GST_TAG_CAPTURING_EXPOSURE_PROGRAM, EXIF_TAG_EXPOSURE_PROGRAM,
|
|
EXIF_TYPE_SHORT, 0, serialize_exposure_program,
|
|
deserialize_exposure_program},
|
|
|
|
/* don't need the serializer as we always write the iso speed alone */
|
|
{GST_TAG_CAPTURING_ISO_SPEED, EXIF_TAG_PHOTOGRAPHIC_SENSITIVITY,
|
|
EXIF_TYPE_SHORT, 0, NULL,
|
|
deserialize_add_to_pending_tags},
|
|
|
|
{GST_TAG_CAPTURING_ISO_SPEED, EXIF_TAG_SENSITIVITY_TYPE, EXIF_TYPE_SHORT, 0,
|
|
serialize_sensitivity_type, deserialize_sensitivity_type},
|
|
{GST_TAG_CAPTURING_ISO_SPEED, EXIF_TAG_ISO_SPEED, EXIF_TYPE_LONG, 0, NULL,
|
|
NULL},
|
|
{NULL, EXIF_VERSION_TAG, EXIF_TYPE_UNDEFINED, 0, NULL, NULL},
|
|
{GST_TAG_DATE_TIME, EXIF_TAG_DATE_TIME_ORIGINAL, EXIF_TYPE_ASCII, 0, NULL,
|
|
NULL},
|
|
{GST_TAG_CAPTURING_SHUTTER_SPEED, EXIF_TAG_SHUTTER_SPEED_VALUE,
|
|
EXIF_TYPE_SRATIONAL, 0,
|
|
serialize_shutter_speed, deserialize_shutter_speed},
|
|
{GST_TAG_CAPTURING_FOCAL_RATIO, EXIF_TAG_APERTURE_VALUE, EXIF_TYPE_RATIONAL,
|
|
0,
|
|
serialize_aperture_value, deserialize_aperture_value},
|
|
{GST_TAG_CAPTURING_EXPOSURE_COMPENSATION, EXIF_TAG_EXPOSURE_BIAS,
|
|
EXIF_TYPE_SRATIONAL, 0, NULL, NULL},
|
|
{GST_TAG_CAPTURING_METERING_MODE, EXIF_TAG_METERING_MODE, EXIF_TYPE_SHORT, 0,
|
|
serialize_metering_mode, deserialize_metering_mode},
|
|
{GST_TAG_CAPTURING_FLASH_FIRED, EXIF_TAG_FLASH, EXIF_TYPE_SHORT, 0,
|
|
serialize_flash, deserialize_flash},
|
|
{GST_TAG_CAPTURING_FOCAL_LENGTH, EXIF_TAG_FOCAL_LENGTH, EXIF_TYPE_RATIONAL, 0,
|
|
NULL, NULL},
|
|
{GST_TAG_APPLICATION_DATA, EXIF_TAG_MAKER_NOTE, EXIF_TYPE_UNDEFINED, 0, NULL,
|
|
NULL},
|
|
{NULL, EXIF_FLASHPIX_VERSION_TAG, EXIF_TYPE_UNDEFINED, 0, NULL, NULL},
|
|
{GST_TAG_CAPTURING_SOURCE, EXIF_TAG_FILE_SOURCE, EXIF_TYPE_UNDEFINED,
|
|
0, serialize_source, deserialize_source},
|
|
{GST_TAG_CAPTURING_SOURCE, EXIF_TAG_SCENE_TYPE, EXIF_TYPE_UNDEFINED,
|
|
0, serialize_scene_type, deserialize_scene_type},
|
|
{GST_TAG_CAPTURING_EXPOSURE_MODE, EXIF_TAG_EXPOSURE_MODE, EXIF_TYPE_SHORT,
|
|
0, serialize_exposure_mode, deserialize_exposure_mode},
|
|
{GST_TAG_CAPTURING_WHITE_BALANCE, EXIF_TAG_WHITE_BALANCE, EXIF_TYPE_SHORT,
|
|
0, serialize_white_balance, deserialize_white_balance},
|
|
{GST_TAG_CAPTURING_DIGITAL_ZOOM_RATIO, EXIF_TAG_DIGITAL_ZOOM_RATIO,
|
|
EXIF_TYPE_RATIONAL, 0, NULL,
|
|
NULL},
|
|
{GST_TAG_CAPTURING_FOCAL_LENGTH_35_MM, EXIF_TAG_FOCAL_LENGTH_IN_35_MM_FILM,
|
|
EXIF_TYPE_SHORT, 0, NULL, NULL},
|
|
{GST_TAG_CAPTURING_SCENE_CAPTURE_TYPE, EXIF_TAG_SCENE_CAPTURE_TYPE,
|
|
EXIF_TYPE_SHORT, 0, serialize_scene_capture_type,
|
|
deserialize_scene_capture_type},
|
|
{GST_TAG_CAPTURING_GAIN_ADJUSTMENT, EXIF_TAG_GAIN_CONTROL,
|
|
EXIF_TYPE_SHORT, 0, serialize_gain_control,
|
|
deserialize_gain_control},
|
|
{GST_TAG_CAPTURING_CONTRAST, EXIF_TAG_CONTRAST, EXIF_TYPE_SHORT, 0,
|
|
serialize_contrast, deserialize_contrast},
|
|
{GST_TAG_CAPTURING_SATURATION, EXIF_TAG_SATURATION, EXIF_TYPE_SHORT, 0,
|
|
serialize_saturation, deserialize_saturation},
|
|
{GST_TAG_CAPTURING_SHARPNESS, EXIF_TAG_SHARPNESS, EXIF_TYPE_SHORT, 0,
|
|
serialize_sharpness, deserialize_sharpness},
|
|
{NULL, 0, 0, 0, NULL, NULL}
|
|
};
|
|
|
|
static const GstExifTagMatch tag_map_gps[] = {
|
|
{GST_TAG_GEO_LOCATION_LATITUDE, EXIF_TAG_GPS_LATITUDE, EXIF_TYPE_RATIONAL,
|
|
EXIF_TAG_GPS_LATITUDE_REF,
|
|
serialize_geo_coordinate, deserialize_geo_coordinate},
|
|
{GST_TAG_GEO_LOCATION_LONGITUDE, EXIF_TAG_GPS_LONGITUDE, EXIF_TYPE_RATIONAL,
|
|
EXIF_TAG_GPS_LONGITUDE_REF,
|
|
serialize_geo_coordinate, deserialize_geo_coordinate},
|
|
{GST_TAG_GEO_LOCATION_ELEVATION, EXIF_TAG_GPS_ALTITUDE, EXIF_TYPE_RATIONAL,
|
|
EXIF_TAG_GPS_ALTITUDE_REF,
|
|
serialize_geo_elevation, deserialize_geo_elevation},
|
|
{GST_TAG_GEO_LOCATION_MOVEMENT_SPEED, EXIF_TAG_GPS_SPEED, EXIF_TYPE_RATIONAL,
|
|
EXIF_TAG_GPS_SPEED_REF,
|
|
serialize_speed, deserialize_speed},
|
|
{GST_TAG_GEO_LOCATION_MOVEMENT_DIRECTION, EXIF_TAG_GPS_TRACK,
|
|
EXIF_TYPE_RATIONAL, EXIF_TAG_GPS_TRACK_REF,
|
|
serialize_geo_direction, deserialize_geo_direction},
|
|
{GST_TAG_GEO_LOCATION_CAPTURE_DIRECTION, EXIF_TAG_GPS_IMAGE_DIRECTION,
|
|
EXIF_TYPE_RATIONAL, EXIF_TAG_GPS_IMAGE_DIRECTION_REF,
|
|
serialize_geo_direction, deserialize_geo_direction},
|
|
{GST_TAG_GEO_LOCATION_HORIZONTAL_ERROR,
|
|
EXIF_TAG_GPS_HORIZONTAL_POSITIONING_ERROR,
|
|
EXIF_TYPE_RATIONAL, 0, NULL, NULL},
|
|
{NULL, 0, 0, 0, NULL, NULL}
|
|
};
|
|
|
|
/* GstExifReader functions */
|
|
static void
|
|
gst_exif_reader_init (GstExifReader * reader, gint byte_order,
|
|
GstBuffer * buf, guint32 base_offset)
|
|
{
|
|
ensure_exif_tags ();
|
|
|
|
reader->taglist = gst_tag_list_new_empty ();
|
|
reader->buffer = buf;
|
|
reader->base_offset = base_offset;
|
|
reader->byte_order = byte_order;
|
|
reader->pending_tags = NULL;
|
|
if (reader->byte_order != G_LITTLE_ENDIAN &&
|
|
reader->byte_order != G_BIG_ENDIAN) {
|
|
GST_WARNING ("Unexpected byte order %d, using system default: %d",
|
|
reader->byte_order, G_BYTE_ORDER);
|
|
reader->byte_order = G_BYTE_ORDER;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_exif_reader_add_pending_tag (GstExifReader * reader, GstExifTagData * data)
|
|
{
|
|
GstExifTagData *copy;
|
|
|
|
copy = g_slice_new (GstExifTagData);
|
|
memcpy (copy, data, sizeof (GstExifTagData));
|
|
|
|
reader->pending_tags = g_slist_prepend (reader->pending_tags, copy);
|
|
}
|
|
|
|
static GstExifTagData *
|
|
gst_exif_reader_get_pending_tag (GstExifReader * reader, gint tagid)
|
|
{
|
|
GSList *walker;
|
|
|
|
for (walker = reader->pending_tags; walker; walker = g_slist_next (walker)) {
|
|
GstExifTagData *data = (GstExifTagData *) walker->data;
|
|
if (data->tag == tagid)
|
|
return data;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static GstTagList *
|
|
gst_exif_reader_reset (GstExifReader * reader, gboolean return_taglist)
|
|
{
|
|
GstTagList *ret = NULL;
|
|
GSList *walker;
|
|
|
|
for (walker = reader->pending_tags; walker; walker = g_slist_next (walker)) {
|
|
GstExifTagData *data = (GstExifTagData *) walker->data;
|
|
|
|
g_slice_free (GstExifTagData, data);
|
|
}
|
|
g_slist_free (reader->pending_tags);
|
|
|
|
if (return_taglist) {
|
|
ret = reader->taglist;
|
|
reader->taglist = NULL;
|
|
}
|
|
|
|
if (reader->taglist) {
|
|
gst_tag_list_unref (reader->taglist);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* GstExifWriter functions */
|
|
|
|
static void
|
|
gst_exif_writer_init (GstExifWriter * writer, gint byte_order)
|
|
{
|
|
ensure_exif_tags ();
|
|
|
|
gst_byte_writer_init (&writer->tagwriter);
|
|
gst_byte_writer_init (&writer->datawriter);
|
|
|
|
writer->byte_order = byte_order;
|
|
writer->tags_total = 0;
|
|
if (writer->byte_order != G_LITTLE_ENDIAN &&
|
|
writer->byte_order != G_BIG_ENDIAN) {
|
|
GST_WARNING ("Unexpected byte order %d, using system default: %d",
|
|
writer->byte_order, G_BYTE_ORDER);
|
|
writer->byte_order = G_BYTE_ORDER;
|
|
}
|
|
}
|
|
|
|
static GstBuffer *
|
|
gst_exif_writer_reset_and_get_buffer (GstExifWriter * writer)
|
|
{
|
|
GstBuffer *header;
|
|
GstBuffer *data;
|
|
|
|
header = gst_byte_writer_reset_and_get_buffer (&writer->tagwriter);
|
|
data = gst_byte_writer_reset_and_get_buffer (&writer->datawriter);
|
|
|
|
return gst_buffer_append (header, data);
|
|
}
|
|
|
|
/*
|
|
* Given the exif tag with the passed id, returns the map index of the tag
|
|
* corresponding to it. If use_complementary is true, then the complementary
|
|
* are also used in the search.
|
|
*
|
|
* Returns -1 if not found
|
|
*/
|
|
static gint
|
|
exif_tag_map_find_reverse (guint16 exif_tag, const GstExifTagMatch * tag_map,
|
|
gboolean use_complementary)
|
|
{
|
|
gint i;
|
|
|
|
for (i = 0; tag_map[i].exif_tag != 0; i++) {
|
|
if (exif_tag == tag_map[i].exif_tag || (use_complementary &&
|
|
exif_tag == tag_map[i].complementary_tag)) {
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static gboolean
|
|
gst_tag_list_has_ifd_tags (const GstTagList * taglist,
|
|
const GstExifTagMatch * tag_map)
|
|
{
|
|
gint i;
|
|
|
|
for (i = 0; tag_map[i].exif_tag != 0; i++) {
|
|
if (tag_map[i].gst_tag == NULL) {
|
|
if (tag_map[i].exif_tag == EXIF_GPS_IFD_TAG &&
|
|
gst_tag_list_has_ifd_tags (taglist, tag_map_gps))
|
|
return TRUE;
|
|
if (tag_map[i].exif_tag == EXIF_IFD_TAG &&
|
|
gst_tag_list_has_ifd_tags (taglist, tag_map_exif))
|
|
return TRUE;
|
|
continue;
|
|
}
|
|
|
|
if (gst_tag_list_get_value_index (taglist, tag_map[i].gst_tag, 0)) {
|
|
return TRUE;
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* Writes the tag entry.
|
|
*
|
|
* The tag entry is the tag id, the tag type,
|
|
* the count and the offset.
|
|
*
|
|
* The offset is the on the amount of data writen so far, as one
|
|
* can't predict the total bytes that the tag entries will take.
|
|
* This means those fields requires being updated later.
|
|
*/
|
|
static void
|
|
gst_exif_writer_write_tag_header (GstExifWriter * writer,
|
|
guint16 exif_tag, guint16 exif_type, guint32 count, guint32 offset,
|
|
const guint32 * offset_data)
|
|
{
|
|
gboolean handled = TRUE;
|
|
|
|
GST_DEBUG ("Writing tag entry: id %x, type %u, count %u, offset %u",
|
|
exif_tag, exif_type, count, offset);
|
|
|
|
if (writer->byte_order == G_LITTLE_ENDIAN) {
|
|
handled &= gst_byte_writer_put_uint16_le (&writer->tagwriter, exif_tag);
|
|
handled &= gst_byte_writer_put_uint16_le (&writer->tagwriter, exif_type);
|
|
handled &= gst_byte_writer_put_uint32_le (&writer->tagwriter, count);
|
|
if (offset_data != NULL) {
|
|
handled &=
|
|
gst_byte_writer_put_data (&writer->tagwriter, (guint8 *) offset_data,
|
|
4);
|
|
} else {
|
|
handled &= gst_byte_writer_put_uint32_le (&writer->tagwriter, offset);
|
|
}
|
|
} else if (writer->byte_order == G_BIG_ENDIAN) {
|
|
handled &= gst_byte_writer_put_uint16_be (&writer->tagwriter, exif_tag);
|
|
handled &= gst_byte_writer_put_uint16_be (&writer->tagwriter, exif_type);
|
|
handled &= gst_byte_writer_put_uint32_be (&writer->tagwriter, count);
|
|
if (offset_data != NULL) {
|
|
handled &=
|
|
gst_byte_writer_put_data (&writer->tagwriter, (guint8 *) offset_data,
|
|
4);
|
|
} else {
|
|
handled &= gst_byte_writer_put_uint32_be (&writer->tagwriter, offset);
|
|
}
|
|
} else {
|
|
g_assert_not_reached ();
|
|
}
|
|
|
|
if (G_UNLIKELY (!handled))
|
|
GST_WARNING ("Error writing tag header");
|
|
|
|
writer->tags_total++;
|
|
}
|
|
|
|
static void
|
|
gst_exif_writer_write_rational_data (GstExifWriter * writer, guint32 frac_n,
|
|
guint32 frac_d)
|
|
{
|
|
gboolean handled = TRUE;
|
|
|
|
if (writer->byte_order == G_LITTLE_ENDIAN) {
|
|
handled &= gst_byte_writer_put_uint32_le (&writer->datawriter, frac_n);
|
|
handled &= gst_byte_writer_put_uint32_le (&writer->datawriter, frac_d);
|
|
} else {
|
|
handled &= gst_byte_writer_put_uint32_be (&writer->datawriter, frac_n);
|
|
handled &= gst_byte_writer_put_uint32_be (&writer->datawriter, frac_d);
|
|
}
|
|
|
|
if (G_UNLIKELY (!handled))
|
|
GST_WARNING ("Error writing rational data");
|
|
}
|
|
|
|
static void
|
|
gst_exif_writer_write_signed_rational_data (GstExifWriter * writer,
|
|
gint32 frac_n, gint32 frac_d)
|
|
{
|
|
gboolean handled = TRUE;
|
|
|
|
if (writer->byte_order == G_LITTLE_ENDIAN) {
|
|
handled &= gst_byte_writer_put_int32_le (&writer->datawriter, frac_n);
|
|
handled &= gst_byte_writer_put_int32_le (&writer->datawriter, frac_d);
|
|
} else {
|
|
handled &= gst_byte_writer_put_int32_be (&writer->datawriter, frac_n);
|
|
handled &= gst_byte_writer_put_int32_be (&writer->datawriter, frac_d);
|
|
}
|
|
|
|
if (G_UNLIKELY (!handled))
|
|
GST_WARNING ("Error writing signed rational data");
|
|
}
|
|
|
|
static void
|
|
gst_exif_writer_write_rational_tag (GstExifWriter * writer,
|
|
guint16 tag, guint32 frac_n, guint32 frac_d)
|
|
{
|
|
guint32 offset = gst_byte_writer_get_size (&writer->datawriter);
|
|
|
|
gst_exif_writer_write_tag_header (writer, tag, EXIF_TYPE_RATIONAL,
|
|
1, offset, NULL);
|
|
|
|
gst_exif_writer_write_rational_data (writer, frac_n, frac_d);
|
|
}
|
|
|
|
static void
|
|
gst_exif_writer_write_signed_rational_tag (GstExifWriter * writer,
|
|
guint16 tag, gint32 frac_n, gint32 frac_d)
|
|
{
|
|
guint32 offset = gst_byte_writer_get_size (&writer->datawriter);
|
|
|
|
gst_exif_writer_write_tag_header (writer, tag, EXIF_TYPE_SRATIONAL,
|
|
1, offset, NULL);
|
|
|
|
gst_exif_writer_write_signed_rational_data (writer, frac_n, frac_d);
|
|
}
|
|
|
|
static void
|
|
gst_exif_writer_write_rational_tag_from_double (GstExifWriter * writer,
|
|
guint16 tag, gdouble value)
|
|
{
|
|
gint frac_n;
|
|
gint frac_d;
|
|
|
|
gst_util_double_to_fraction (value, &frac_n, &frac_d);
|
|
|
|
gst_exif_writer_write_rational_tag (writer, tag, frac_n, frac_d);
|
|
}
|
|
|
|
static void
|
|
gst_exif_writer_write_signed_rational_tag_from_double (GstExifWriter * writer,
|
|
guint16 tag, gdouble value)
|
|
{
|
|
gint frac_n;
|
|
gint frac_d;
|
|
|
|
gst_util_double_to_fraction (value, &frac_n, &frac_d);
|
|
|
|
gst_exif_writer_write_signed_rational_tag (writer, tag, frac_n, frac_d);
|
|
}
|
|
|
|
static void
|
|
gst_exif_writer_write_byte_tag (GstExifWriter * writer, guint16 tag,
|
|
guint8 value)
|
|
{
|
|
guint32 offset = 0;
|
|
|
|
GST_WRITE_UINT8 ((guint8 *) & offset, value);
|
|
gst_exif_writer_write_tag_header (writer, tag, EXIF_TYPE_BYTE,
|
|
1, offset, &offset);
|
|
}
|
|
|
|
static void
|
|
gst_exif_writer_write_short_tag (GstExifWriter * writer, guint16 tag,
|
|
guint16 value)
|
|
{
|
|
guint32 offset = 0;
|
|
|
|
if (writer->byte_order == G_LITTLE_ENDIAN) {
|
|
GST_WRITE_UINT16_LE ((guint8 *) & offset, value);
|
|
} else {
|
|
GST_WRITE_UINT16_BE ((guint8 *) & offset, value);
|
|
}
|
|
|
|
gst_exif_writer_write_tag_header (writer, tag, EXIF_TYPE_SHORT,
|
|
1, offset, &offset);
|
|
}
|
|
|
|
static void
|
|
gst_exif_writer_write_long_tag (GstExifWriter * writer, guint16 tag,
|
|
guint32 value)
|
|
{
|
|
guint32 offset = 0;
|
|
if (writer->byte_order == G_LITTLE_ENDIAN) {
|
|
GST_WRITE_UINT32_LE ((guint8 *) & offset, value);
|
|
} else {
|
|
GST_WRITE_UINT32_BE ((guint8 *) & offset, value);
|
|
}
|
|
|
|
gst_exif_writer_write_tag_header (writer, tag, EXIF_TYPE_LONG,
|
|
1, offset, &offset);
|
|
}
|
|
|
|
|
|
static void
|
|
write_exif_undefined_tag (GstExifWriter * writer, guint16 tag,
|
|
const guint8 * data, gint size)
|
|
{
|
|
guint32 offset = 0;
|
|
|
|
if (size > 4) {
|
|
/* we only use the data offset here, later we add up the
|
|
* resulting tag headers offset and the base offset */
|
|
offset = gst_byte_writer_get_size (&writer->datawriter);
|
|
gst_exif_writer_write_tag_header (writer, tag, EXIF_TYPE_UNDEFINED,
|
|
size, offset, NULL);
|
|
if (!gst_byte_writer_put_data (&writer->datawriter, data, size)) {
|
|
GST_WARNING ("Error writing undefined tag");
|
|
}
|
|
} else {
|
|
/* small enough to go in the offset */
|
|
memcpy ((guint8 *) & offset, data, size);
|
|
gst_exif_writer_write_tag_header (writer, tag, EXIF_TYPE_UNDEFINED,
|
|
size, offset, &offset);
|
|
}
|
|
}
|
|
|
|
static inline gboolean
|
|
gst_exif_tag_str_is_ascii (const gchar * str, gsize * length)
|
|
{
|
|
gsize len = 0;
|
|
|
|
while (*str) {
|
|
if (*str++ & 0x80)
|
|
return FALSE;
|
|
++len;
|
|
}
|
|
*length = len;
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
write_exif_ascii_tag (GstExifWriter * writer, guint16 tag, const gchar * str)
|
|
{
|
|
guint32 offset = 0;
|
|
gchar *ascii_str;
|
|
gsize ascii_size;
|
|
GError *error = NULL;
|
|
|
|
if (gst_exif_tag_str_is_ascii (str, &ascii_size))
|
|
ascii_str = g_strndup (str, ascii_size);
|
|
else
|
|
ascii_str =
|
|
g_convert (str, -1, "latin1", "utf8", NULL, &ascii_size, &error);
|
|
|
|
if (error) {
|
|
GST_WARNING ("Failed to convert exif tag to ascii: 0x%x - %s. Error: %s",
|
|
tag, str, error->message);
|
|
g_error_free (error);
|
|
g_free (ascii_str);
|
|
return;
|
|
}
|
|
|
|
/* add the \0 at the end */
|
|
ascii_size++;
|
|
|
|
if (ascii_size > 4) {
|
|
/* we only use the data offset here, later we add up the
|
|
* resulting tag headers offset and the base offset */
|
|
offset = gst_byte_writer_get_size (&writer->datawriter);
|
|
gst_exif_writer_write_tag_header (writer, tag, EXIF_TYPE_ASCII,
|
|
ascii_size, offset, NULL);
|
|
gst_byte_writer_put_string (&writer->datawriter, ascii_str);
|
|
} else {
|
|
/* small enough to go in the offset */
|
|
memcpy ((guint8 *) & offset, ascii_str, ascii_size);
|
|
gst_exif_writer_write_tag_header (writer, tag, EXIF_TYPE_ASCII,
|
|
ascii_size, offset, &offset);
|
|
}
|
|
|
|
g_free (ascii_str);
|
|
}
|
|
|
|
static void
|
|
write_exif_ascii_tag_from_taglist (GstExifWriter * writer,
|
|
const GstTagList * taglist, const GstExifTagMatch * exiftag)
|
|
{
|
|
gchar *str = NULL;
|
|
gboolean cleanup = FALSE;
|
|
const GValue *value;
|
|
gint tag_size = gst_tag_list_get_tag_size (taglist, exiftag->gst_tag);
|
|
|
|
if (tag_size != 1) {
|
|
/* FIXME support this by serializing them with a ','? */
|
|
GST_WARNING ("Multiple string tags not supported yet");
|
|
return;
|
|
}
|
|
|
|
value = gst_tag_list_get_value_index (taglist, exiftag->gst_tag, 0);
|
|
|
|
/* do some conversion if needed */
|
|
switch (G_VALUE_TYPE (value)) {
|
|
case G_TYPE_STRING:
|
|
str = (gchar *) g_value_get_string (value);
|
|
break;
|
|
default:
|
|
if (G_VALUE_TYPE (value) == GST_TYPE_DATE_TIME) {
|
|
GstDateTime *dt = (GstDateTime *) g_value_get_boxed (value);
|
|
|
|
if (dt == NULL) {
|
|
GST_WARNING ("NULL datetime received");
|
|
break;
|
|
}
|
|
|
|
str = g_strdup_printf ("%04d:%02d:%02d %02d:%02d:%02d",
|
|
gst_date_time_get_year (dt), gst_date_time_get_month (dt),
|
|
gst_date_time_get_day (dt), gst_date_time_get_hour (dt),
|
|
gst_date_time_get_minute (dt), gst_date_time_get_second (dt));
|
|
|
|
cleanup = TRUE;
|
|
} else {
|
|
GST_WARNING ("Conversion from %s to ascii string not supported",
|
|
G_VALUE_TYPE_NAME (value));
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (str == NULL)
|
|
return;
|
|
|
|
write_exif_ascii_tag (writer, exiftag->exif_tag, str);
|
|
if (cleanup)
|
|
g_free (str);
|
|
}
|
|
|
|
static void
|
|
write_exif_undefined_tag_from_taglist (GstExifWriter * writer,
|
|
const GstTagList * taglist, const GstExifTagMatch * exiftag)
|
|
{
|
|
const GValue *value;
|
|
GstMapInfo info;
|
|
guint8 *data = NULL;
|
|
gsize size = 0;
|
|
gint tag_size = gst_tag_list_get_tag_size (taglist, exiftag->gst_tag);
|
|
GstSample *sample = NULL;
|
|
GstBuffer *buf = NULL;
|
|
|
|
if (tag_size != 1) {
|
|
GST_WARNING ("Only the first item in the taglist will be serialized");
|
|
return;
|
|
}
|
|
|
|
value = gst_tag_list_get_value_index (taglist, exiftag->gst_tag, 0);
|
|
|
|
/* do some conversion if needed */
|
|
switch (G_VALUE_TYPE (value)) {
|
|
case G_TYPE_STRING:
|
|
data = (guint8 *) g_value_get_string (value);
|
|
size = strlen ((gchar *) data); /* no need to +1, undefined doesn't require it */
|
|
break;
|
|
default:
|
|
if (G_VALUE_TYPE (value) == GST_TYPE_SAMPLE) {
|
|
sample = gst_value_get_sample (value);
|
|
buf = gst_sample_get_buffer (sample);
|
|
if (gst_buffer_map (buf, &info, GST_MAP_READ)) {
|
|
data = info.data;
|
|
size = info.size;
|
|
} else {
|
|
GST_WARNING ("Failed to map buffer for reading");
|
|
}
|
|
} else {
|
|
GST_WARNING ("Conversion from %s to raw data not supported",
|
|
G_VALUE_TYPE_NAME (value));
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (size > 0)
|
|
write_exif_undefined_tag (writer, exiftag->exif_tag, data, size);
|
|
|
|
if (buf)
|
|
gst_buffer_unmap (buf, &info);
|
|
}
|
|
|
|
static void
|
|
write_exif_rational_tag_from_taglist (GstExifWriter * writer,
|
|
const GstTagList * taglist, const GstExifTagMatch * exiftag)
|
|
{
|
|
const GValue *value;
|
|
gdouble num = 0;
|
|
gint tag_size = gst_tag_list_get_tag_size (taglist, exiftag->gst_tag);
|
|
|
|
if (tag_size != 1) {
|
|
GST_WARNING ("Only the first item in the taglist will be serialized");
|
|
return;
|
|
}
|
|
|
|
value = gst_tag_list_get_value_index (taglist, exiftag->gst_tag, 0);
|
|
|
|
/* do some conversion if needed */
|
|
switch (G_VALUE_TYPE (value)) {
|
|
case G_TYPE_DOUBLE:
|
|
num = g_value_get_double (value);
|
|
gst_exif_writer_write_rational_tag_from_double (writer, exiftag->exif_tag,
|
|
num);
|
|
break;
|
|
default:
|
|
if (G_VALUE_TYPE (value) == GST_TYPE_FRACTION) {
|
|
gst_exif_writer_write_rational_tag (writer, exiftag->exif_tag,
|
|
gst_value_get_fraction_numerator (value),
|
|
gst_value_get_fraction_denominator (value));
|
|
} else {
|
|
GST_WARNING ("Conversion from %s to rational not supported",
|
|
G_VALUE_TYPE_NAME (value));
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
write_exif_signed_rational_tag_from_taglist (GstExifWriter * writer,
|
|
const GstTagList * taglist, const GstExifTagMatch * exiftag)
|
|
{
|
|
const GValue *value;
|
|
gdouble num = 0;
|
|
gint tag_size = gst_tag_list_get_tag_size (taglist, exiftag->gst_tag);
|
|
|
|
if (tag_size != 1) {
|
|
GST_WARNING ("Only the first item in the taglist will be serialized");
|
|
return;
|
|
}
|
|
|
|
value = gst_tag_list_get_value_index (taglist, exiftag->gst_tag, 0);
|
|
|
|
/* do some conversion if needed */
|
|
switch (G_VALUE_TYPE (value)) {
|
|
case G_TYPE_DOUBLE:
|
|
num = g_value_get_double (value);
|
|
gst_exif_writer_write_signed_rational_tag_from_double (writer,
|
|
exiftag->exif_tag, num);
|
|
break;
|
|
default:
|
|
if (G_VALUE_TYPE (value) == GST_TYPE_FRACTION) {
|
|
gst_exif_writer_write_signed_rational_tag (writer, exiftag->exif_tag,
|
|
gst_value_get_fraction_numerator (value),
|
|
gst_value_get_fraction_denominator (value));
|
|
} else {
|
|
GST_WARNING ("Conversion from %s to signed rational not supported",
|
|
G_VALUE_TYPE_NAME (value));
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
write_exif_integer_tag_from_taglist (GstExifWriter * writer,
|
|
const GstTagList * taglist, const GstExifTagMatch * exiftag)
|
|
{
|
|
const GValue *value;
|
|
guint32 num = 0;
|
|
gint tag_size = gst_tag_list_get_tag_size (taglist, exiftag->gst_tag);
|
|
|
|
if (tag_size != 1) {
|
|
GST_WARNING ("Only the first item in the taglist will be serialized");
|
|
return;
|
|
}
|
|
|
|
value = gst_tag_list_get_value_index (taglist, exiftag->gst_tag, 0);
|
|
|
|
/* do some conversion if needed */
|
|
switch (G_VALUE_TYPE (value)) {
|
|
case G_TYPE_INT:
|
|
num = g_value_get_int (value);
|
|
break;
|
|
case G_TYPE_DOUBLE:
|
|
num = (gint) g_value_get_double (value);
|
|
break;
|
|
default:
|
|
GST_WARNING ("Conversion from %s to int not supported",
|
|
G_VALUE_TYPE_NAME (value));
|
|
break;
|
|
}
|
|
|
|
switch (exiftag->exif_type) {
|
|
case EXIF_TYPE_LONG:
|
|
gst_exif_writer_write_long_tag (writer, exiftag->exif_tag, num);
|
|
break;
|
|
case EXIF_TYPE_SHORT:
|
|
gst_exif_writer_write_short_tag (writer, exiftag->exif_tag, num);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
write_exif_tag_from_taglist (GstExifWriter * writer, const GstTagList * taglist,
|
|
const GstExifTagMatch * exiftag)
|
|
{
|
|
GST_DEBUG ("Writing tag %s", exiftag->gst_tag);
|
|
|
|
/* check for special handling */
|
|
if (exiftag->serialize) {
|
|
exiftag->serialize (writer, taglist, exiftag);
|
|
return;
|
|
}
|
|
|
|
switch (exiftag->exif_type) {
|
|
case EXIF_TYPE_ASCII:
|
|
write_exif_ascii_tag_from_taglist (writer, taglist, exiftag);
|
|
break;
|
|
case EXIF_TYPE_UNDEFINED:
|
|
write_exif_undefined_tag_from_taglist (writer, taglist, exiftag);
|
|
break;
|
|
case EXIF_TYPE_RATIONAL:
|
|
write_exif_rational_tag_from_taglist (writer, taglist, exiftag);
|
|
break;
|
|
case EXIF_TYPE_SRATIONAL:
|
|
write_exif_signed_rational_tag_from_taglist (writer, taglist, exiftag);
|
|
break;
|
|
case EXIF_TYPE_LONG:
|
|
case EXIF_TYPE_SHORT:
|
|
write_exif_integer_tag_from_taglist (writer, taglist, exiftag);
|
|
break;
|
|
default:
|
|
GST_WARNING ("Unhandled tag type %d", exiftag->exif_type);
|
|
}
|
|
}
|
|
|
|
static void
|
|
tagdata_copy (GstExifTagData * to, const GstExifTagData * from)
|
|
{
|
|
to->tag = from->tag;
|
|
to->tag_type = from->tag_type;
|
|
to->count = from->count;
|
|
to->offset = from->offset;
|
|
to->offset_as_data = from->offset_as_data;
|
|
}
|
|
|
|
static void
|
|
gst_exif_tag_rewrite_offsets (GstByteWriter * writer, gint byte_order,
|
|
guint32 offset, gint num_tags, GstByteWriter * inner_ifds_data)
|
|
{
|
|
GstByteReader *reader;
|
|
gint i;
|
|
guint16 aux = G_MAXUINT16;
|
|
gboolean handled = TRUE;
|
|
|
|
GST_LOG ("Rewriting tag entries offsets");
|
|
|
|
reader = (GstByteReader *) writer;
|
|
|
|
if (num_tags == -1) {
|
|
if (byte_order == G_LITTLE_ENDIAN) {
|
|
handled &= gst_byte_reader_get_uint16_le (reader, &aux);
|
|
} else {
|
|
handled &= gst_byte_reader_get_uint16_be (reader, &aux);
|
|
}
|
|
if (aux == G_MAXUINT16) {
|
|
GST_WARNING ("Failed to read number of tags, won't rewrite offsets");
|
|
return;
|
|
}
|
|
num_tags = (gint) aux;
|
|
}
|
|
|
|
g_return_if_fail (num_tags != -1);
|
|
|
|
GST_DEBUG ("number of tags %d", num_tags);
|
|
|
|
for (i = 0; i < num_tags; i++) {
|
|
guint16 type = 0;
|
|
guint32 cur_offset = 0;
|
|
gint byte_size = 0;
|
|
guint32 count = 0;
|
|
guint16 tag_id = 0;
|
|
|
|
g_assert (gst_byte_writer_get_pos (writer) <
|
|
gst_byte_writer_get_size (writer));
|
|
|
|
/* read the type */
|
|
if (byte_order == G_LITTLE_ENDIAN) {
|
|
if (!gst_byte_reader_get_uint16_le (reader, &tag_id))
|
|
break;
|
|
if (!gst_byte_reader_get_uint16_le (reader, &type))
|
|
break;
|
|
if (!gst_byte_reader_get_uint32_le (reader, &count))
|
|
break;
|
|
} else {
|
|
if (!gst_byte_reader_get_uint16_be (reader, &tag_id))
|
|
break;
|
|
if (!gst_byte_reader_get_uint16_be (reader, &type))
|
|
break;
|
|
if (!gst_byte_reader_get_uint32_be (reader, &count))
|
|
break;
|
|
}
|
|
|
|
GST_LOG ("Parsed tag %x of type %u and count %u", tag_id, type, count);
|
|
|
|
switch (type) {
|
|
case EXIF_TYPE_BYTE:
|
|
case EXIF_TYPE_ASCII:
|
|
case EXIF_TYPE_UNDEFINED:
|
|
byte_size = count;
|
|
break;
|
|
case EXIF_TYPE_SHORT:
|
|
byte_size = count * 2; /* 2 bytes */
|
|
break;
|
|
case EXIF_TYPE_LONG:
|
|
case EXIF_TYPE_SLONG:
|
|
byte_size = count * 4; /* 4 bytes */
|
|
break;
|
|
case EXIF_TYPE_RATIONAL:
|
|
case EXIF_TYPE_SRATIONAL:
|
|
byte_size = count * 8; /* 8 bytes */
|
|
break;
|
|
default:
|
|
g_assert_not_reached ();
|
|
break;
|
|
}
|
|
|
|
/* adjust the offset if needed */
|
|
if (byte_size > 4 || tag_id == EXIF_GPS_IFD_TAG || tag_id == EXIF_IFD_TAG) {
|
|
if (byte_order == G_LITTLE_ENDIAN) {
|
|
if (gst_byte_reader_peek_uint32_le (reader, &cur_offset)) {
|
|
handled &=
|
|
gst_byte_writer_put_uint32_le (writer, cur_offset + offset);
|
|
}
|
|
} else {
|
|
if (gst_byte_reader_peek_uint32_be (reader, &cur_offset)) {
|
|
handled &=
|
|
gst_byte_writer_put_uint32_be (writer, cur_offset + offset);
|
|
}
|
|
}
|
|
GST_DEBUG ("Rewriting tag offset from %u to (%u + %u) %u",
|
|
cur_offset, cur_offset, offset, cur_offset + offset);
|
|
|
|
if ((tag_id == EXIF_GPS_IFD_TAG || tag_id == EXIF_IFD_TAG) &&
|
|
inner_ifds_data != NULL) {
|
|
/* needs special handling */
|
|
if (!gst_byte_writer_set_pos (inner_ifds_data, cur_offset)) {
|
|
GST_WARNING ("Failed to position writer to rewrite inner ifd "
|
|
"offsets");
|
|
continue;
|
|
}
|
|
|
|
gst_exif_tag_rewrite_offsets (inner_ifds_data, byte_order, offset, -1,
|
|
NULL);
|
|
}
|
|
} else {
|
|
handled &= gst_byte_reader_skip (reader, 4);
|
|
GST_DEBUG ("No need to rewrite tag offset");
|
|
}
|
|
}
|
|
if (G_UNLIKELY (!handled))
|
|
GST_WARNING ("Error rewriting offsets");
|
|
|
|
GST_LOG ("Done rewriting offsets");
|
|
}
|
|
|
|
static void
|
|
parse_exif_ascii_tag (GstExifReader * reader, const GstExifTagMatch * tag,
|
|
guint32 count, guint32 offset, const guint8 * offset_as_data)
|
|
{
|
|
GType tagtype;
|
|
gchar *str;
|
|
gchar *utfstr;
|
|
guint32 real_offset;
|
|
GError *error = NULL;
|
|
|
|
if (count > 4) {
|
|
GstMapInfo info;
|
|
|
|
if (offset < reader->base_offset) {
|
|
GST_WARNING ("Offset is smaller (%u) than base offset (%u)", offset,
|
|
reader->base_offset);
|
|
return;
|
|
}
|
|
|
|
real_offset = offset - reader->base_offset;
|
|
|
|
if (!gst_buffer_map (reader->buffer, &info, GST_MAP_READ)) {
|
|
GST_WARNING ("Failed to map buffer for reading");
|
|
return;
|
|
}
|
|
if (real_offset >= info.size) {
|
|
GST_WARNING ("Invalid offset %u for buffer of size %" G_GSIZE_FORMAT
|
|
", not adding tag %s", real_offset, info.size, tag->gst_tag);
|
|
gst_buffer_unmap (reader->buffer, &info);
|
|
return;
|
|
}
|
|
|
|
str = g_strndup ((gchar *) (info.data + real_offset), count);
|
|
gst_buffer_unmap (reader->buffer, &info);
|
|
} else {
|
|
str = g_strndup ((gchar *) offset_as_data, count);
|
|
}
|
|
|
|
/* convert from ascii to utf8 */
|
|
if (g_utf8_validate (str, -1, NULL)) {
|
|
GST_DEBUG ("Exif string is already on utf8: %s", str);
|
|
utfstr = str;
|
|
} else {
|
|
GST_DEBUG ("Exif string isn't utf8, trying to convert from latin1: %s",
|
|
str);
|
|
utfstr = g_convert (str, count, "utf8", "latin1", NULL, NULL, &error);
|
|
if (error) {
|
|
GST_WARNING ("Skipping tag %d:%s. Failed to convert ascii string "
|
|
"to utf8 : %s - %s", tag->exif_tag, tag->gst_tag, str,
|
|
error->message);
|
|
g_error_free (error);
|
|
g_free (str);
|
|
return;
|
|
}
|
|
g_free (str);
|
|
}
|
|
|
|
tagtype = gst_tag_get_type (tag->gst_tag);
|
|
if (tagtype == GST_TYPE_DATE_TIME) {
|
|
gint year = 0, month = 1, day = 1, hour = 0, minute = 0, second = 0;
|
|
|
|
if (sscanf (utfstr, "%04d:%02d:%02d %02d:%02d:%02d", &year, &month, &day,
|
|
&hour, &minute, &second) > 0) {
|
|
GstDateTime *d;
|
|
|
|
d = gst_date_time_new_local_time (year, month, day, hour, minute, second);
|
|
gst_tag_list_add (reader->taglist, GST_TAG_MERGE_REPLACE,
|
|
tag->gst_tag, d, NULL);
|
|
gst_date_time_unref (d);
|
|
} else {
|
|
GST_WARNING ("Failed to parse %s into a datetime tag", utfstr);
|
|
}
|
|
} else if (tagtype == G_TYPE_STRING) {
|
|
if (utfstr[0] != '\0')
|
|
gst_tag_list_add (reader->taglist, GST_TAG_MERGE_REPLACE, tag->gst_tag,
|
|
utfstr, NULL);
|
|
} else {
|
|
GST_WARNING ("No parsing function associated to %x(%s)", tag->exif_tag,
|
|
tag->gst_tag);
|
|
}
|
|
g_free (utfstr);
|
|
}
|
|
|
|
static void
|
|
parse_exif_short_tag (GstExifReader * reader, const GstExifTagMatch * tag,
|
|
guint32 count, guint32 offset, const guint8 * offset_as_data)
|
|
{
|
|
GType tagtype;
|
|
guint16 value;
|
|
|
|
if (count > 1) {
|
|
GST_WARNING ("Short tags with more than one value are not supported");
|
|
return;
|
|
}
|
|
|
|
/* value is encoded into offset */
|
|
if (reader->byte_order == G_LITTLE_ENDIAN)
|
|
value = GST_READ_UINT16_LE (offset_as_data);
|
|
else
|
|
value = GST_READ_UINT16_BE (offset_as_data);
|
|
|
|
tagtype = gst_tag_get_type (tag->gst_tag);
|
|
if (tagtype == G_TYPE_INT) {
|
|
gst_tag_list_add (reader->taglist, GST_TAG_MERGE_REPLACE, tag->gst_tag,
|
|
value, NULL);
|
|
} else if (tagtype == G_TYPE_DOUBLE) {
|
|
gst_tag_list_add (reader->taglist, GST_TAG_MERGE_REPLACE, tag->gst_tag,
|
|
(gdouble) value, NULL);
|
|
} else {
|
|
GST_WARNING ("No parsing function associated to %x(%s)", tag->exif_tag,
|
|
tag->gst_tag);
|
|
}
|
|
}
|
|
|
|
static void
|
|
parse_exif_long_tag (GstExifReader * reader, const GstExifTagMatch * tag,
|
|
guint32 count, guint32 offset, const guint8 * offset_as_data)
|
|
{
|
|
GType tagtype;
|
|
|
|
if (count > 1) {
|
|
GST_WARNING ("Long tags with more than one value are not supported");
|
|
return;
|
|
}
|
|
|
|
tagtype = gst_tag_get_type (tag->gst_tag);
|
|
if (tagtype == G_TYPE_INT) {
|
|
gst_tag_list_add (reader->taglist, GST_TAG_MERGE_REPLACE, tag->gst_tag,
|
|
offset, NULL);
|
|
} else {
|
|
GST_WARNING ("No parsing function associated to %x(%s)", tag->exif_tag,
|
|
tag->gst_tag);
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
parse_exif_undefined_tag (GstExifReader * reader, const GstExifTagMatch * tag,
|
|
guint32 count, guint32 offset, const guint8 * offset_as_data)
|
|
{
|
|
GType tagtype;
|
|
guint8 *data;
|
|
guint32 real_offset;
|
|
|
|
if (count > 4) {
|
|
GstMapInfo info;
|
|
|
|
if (offset < reader->base_offset) {
|
|
GST_WARNING ("Offset is smaller (%u) than base offset (%u)", offset,
|
|
reader->base_offset);
|
|
return;
|
|
}
|
|
|
|
real_offset = offset - reader->base_offset;
|
|
|
|
if (!gst_buffer_map (reader->buffer, &info, GST_MAP_READ)) {
|
|
GST_WARNING ("Failed to map buffer for reading");
|
|
return;
|
|
}
|
|
|
|
if (real_offset >= info.size) {
|
|
GST_WARNING ("Invalid offset %u for buffer of size %" G_GSIZE_FORMAT
|
|
", not adding tag %s", real_offset, info.size, tag->gst_tag);
|
|
gst_buffer_unmap (reader->buffer, &info);
|
|
return;
|
|
}
|
|
|
|
/* +1 because it could be a string without the \0 */
|
|
data = malloc (sizeof (guint8) * count + 1);
|
|
memcpy (data, info.data + real_offset, count);
|
|
data[count] = 0;
|
|
|
|
gst_buffer_unmap (reader->buffer, &info);
|
|
} else {
|
|
data = malloc (sizeof (guint8) * count + 1);
|
|
memcpy (data, (guint8 *) offset_as_data, count);
|
|
data[count] = 0;
|
|
}
|
|
|
|
tagtype = gst_tag_get_type (tag->gst_tag);
|
|
if (tagtype == GST_TYPE_SAMPLE) {
|
|
GstSample *sample;
|
|
GstBuffer *buf;
|
|
|
|
buf = gst_buffer_new_wrapped (data, count);
|
|
data = NULL;
|
|
|
|
sample = gst_sample_new (buf, NULL, NULL, NULL);
|
|
gst_tag_list_add (reader->taglist, GST_TAG_MERGE_APPEND, tag->gst_tag,
|
|
sample, NULL);
|
|
gst_sample_unref (sample);
|
|
gst_buffer_unref (buf);
|
|
} else if (tagtype == G_TYPE_STRING) {
|
|
if (data[0] != '\0')
|
|
gst_tag_list_add (reader->taglist, GST_TAG_MERGE_REPLACE, tag->gst_tag,
|
|
data, NULL);
|
|
} else {
|
|
GST_WARNING ("No parsing function associated to %x(%s)", tag->exif_tag,
|
|
tag->gst_tag);
|
|
}
|
|
g_free (data);
|
|
}
|
|
|
|
static gboolean
|
|
exif_reader_read_rational_tag (GstExifReader * exif_reader,
|
|
guint32 count, guint32 offset, gboolean is_signed,
|
|
gint32 * _frac_n, gint32 * _frac_d)
|
|
{
|
|
GstByteReader data_reader;
|
|
guint32 real_offset;
|
|
gint32 frac_n = 0;
|
|
gint32 frac_d = 0;
|
|
GstMapInfo info;
|
|
|
|
if (count > 1) {
|
|
GST_WARNING ("Rationals with multiple entries are not supported");
|
|
}
|
|
if (offset < exif_reader->base_offset) {
|
|
GST_WARNING ("Offset is smaller (%u) than base offset (%u)", offset,
|
|
exif_reader->base_offset);
|
|
return FALSE;
|
|
}
|
|
|
|
real_offset = offset - exif_reader->base_offset;
|
|
|
|
if (!gst_buffer_map (exif_reader->buffer, &info, GST_MAP_READ)) {
|
|
GST_WARNING ("Failed to map buffer for reading");
|
|
return FALSE;
|
|
}
|
|
|
|
if (real_offset >= info.size) {
|
|
GST_WARNING ("Invalid offset %u for buffer of size %" G_GSIZE_FORMAT,
|
|
real_offset, info.size);
|
|
goto reader_fail;
|
|
}
|
|
|
|
gst_byte_reader_init (&data_reader, info.data, info.size);
|
|
if (!gst_byte_reader_set_pos (&data_reader, real_offset))
|
|
goto reader_fail;
|
|
|
|
if (!is_signed) {
|
|
guint32 aux_n = 0, aux_d = 0;
|
|
if (exif_reader->byte_order == G_LITTLE_ENDIAN) {
|
|
if (!gst_byte_reader_get_uint32_le (&data_reader, &aux_n) ||
|
|
!gst_byte_reader_get_uint32_le (&data_reader, &aux_d))
|
|
goto reader_fail;
|
|
} else {
|
|
if (!gst_byte_reader_get_uint32_be (&data_reader, &aux_n) ||
|
|
!gst_byte_reader_get_uint32_be (&data_reader, &aux_d))
|
|
goto reader_fail;
|
|
}
|
|
frac_n = (gint32) aux_n;
|
|
frac_d = (gint32) aux_d;
|
|
} else {
|
|
if (exif_reader->byte_order == G_LITTLE_ENDIAN) {
|
|
if (!gst_byte_reader_get_int32_le (&data_reader, &frac_n) ||
|
|
!gst_byte_reader_get_int32_le (&data_reader, &frac_d))
|
|
goto reader_fail;
|
|
} else {
|
|
if (!gst_byte_reader_get_int32_be (&data_reader, &frac_n) ||
|
|
!gst_byte_reader_get_int32_be (&data_reader, &frac_d))
|
|
goto reader_fail;
|
|
}
|
|
}
|
|
|
|
if (_frac_n)
|
|
*_frac_n = frac_n;
|
|
if (_frac_d)
|
|
*_frac_d = frac_d;
|
|
|
|
gst_buffer_unmap (exif_reader->buffer, &info);
|
|
|
|
return TRUE;
|
|
|
|
reader_fail:
|
|
GST_WARNING ("Failed to read from byte reader. (Buffer too short?)");
|
|
gst_buffer_unmap (exif_reader->buffer, &info);
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
parse_exif_rational_tag (GstExifReader * exif_reader,
|
|
const gchar * gst_tag, guint32 count, guint32 offset, gdouble multiplier,
|
|
gboolean is_signed)
|
|
{
|
|
GType type;
|
|
gint32 frac_n = 0;
|
|
gint32 frac_d = 1;
|
|
gdouble value;
|
|
|
|
GST_DEBUG ("Reading fraction for tag %s...", gst_tag);
|
|
if (!exif_reader_read_rational_tag (exif_reader, count, offset, is_signed,
|
|
&frac_n, &frac_d))
|
|
return;
|
|
GST_DEBUG ("Read fraction for tag %s: %d/%d", gst_tag, frac_n, frac_d);
|
|
|
|
type = gst_tag_get_type (gst_tag);
|
|
switch (type) {
|
|
case G_TYPE_DOUBLE:
|
|
gst_util_fraction_to_double (frac_n, frac_d, &value);
|
|
value *= multiplier;
|
|
GST_DEBUG ("Adding %s tag: %lf", gst_tag, value);
|
|
gst_tag_list_add (exif_reader->taglist, GST_TAG_MERGE_REPLACE, gst_tag,
|
|
value, NULL);
|
|
break;
|
|
default:
|
|
if (type == GST_TYPE_FRACTION) {
|
|
GValue fraction = { 0 };
|
|
|
|
g_value_init (&fraction, GST_TYPE_FRACTION);
|
|
gst_value_set_fraction (&fraction, frac_n * multiplier, frac_d);
|
|
gst_tag_list_add_value (exif_reader->taglist, GST_TAG_MERGE_REPLACE,
|
|
gst_tag, &fraction);
|
|
g_value_unset (&fraction);
|
|
} else {
|
|
GST_WARNING ("Can't convert from fraction into %s", g_type_name (type));
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
static GstBuffer *
|
|
write_exif_ifd (const GstTagList * taglist, guint byte_order,
|
|
guint32 base_offset, const GstExifTagMatch * tag_map)
|
|
{
|
|
GstExifWriter writer;
|
|
gint i;
|
|
gboolean handled = TRUE;
|
|
|
|
GST_DEBUG ("Formatting taglist %p as exif buffer. Byte order: %d, "
|
|
"base_offset: %u", taglist, byte_order, base_offset);
|
|
|
|
g_assert (byte_order == G_LITTLE_ENDIAN || byte_order == G_BIG_ENDIAN);
|
|
|
|
if (!gst_tag_list_has_ifd_tags (taglist, tag_map)) {
|
|
GST_DEBUG ("No tags for this ifd");
|
|
return NULL;
|
|
}
|
|
|
|
gst_exif_writer_init (&writer, byte_order);
|
|
|
|
/* write tag number as 0 */
|
|
handled &= gst_byte_writer_put_uint16_le (&writer.tagwriter, 0);
|
|
|
|
/* write both tag headers and data
|
|
* in ascending id order */
|
|
|
|
for (i = 0; tag_map[i].exif_tag != 0; i++) {
|
|
|
|
/* special cases have NULL gst tag */
|
|
if (tag_map[i].gst_tag == NULL) {
|
|
GstBuffer *inner_ifd = NULL;
|
|
const GstExifTagMatch *inner_tag_map = NULL;
|
|
|
|
GST_LOG ("Inner ifd tag: %x", tag_map[i].exif_tag);
|
|
|
|
if (tag_map[i].exif_tag == EXIF_GPS_IFD_TAG) {
|
|
inner_tag_map = tag_map_gps;
|
|
} else if (tag_map[i].exif_tag == EXIF_IFD_TAG) {
|
|
inner_tag_map = tag_map_exif;
|
|
} else if (tag_map[i].exif_tag == EXIF_VERSION_TAG) {
|
|
/* special case where we write the exif version */
|
|
write_exif_undefined_tag (&writer, EXIF_VERSION_TAG, (guint8 *) "0230",
|
|
4);
|
|
} else if (tag_map[i].exif_tag == EXIF_FLASHPIX_VERSION_TAG) {
|
|
/* special case where we write the flashpix version */
|
|
write_exif_undefined_tag (&writer, EXIF_FLASHPIX_VERSION_TAG,
|
|
(guint8 *) "0100", 4);
|
|
}
|
|
|
|
if (inner_tag_map) {
|
|
/* base offset and tagheader size are added when rewriting offset */
|
|
inner_ifd = write_exif_ifd (taglist, byte_order,
|
|
gst_byte_writer_get_size (&writer.datawriter), inner_tag_map);
|
|
}
|
|
|
|
if (inner_ifd) {
|
|
GstMapInfo info;
|
|
|
|
GST_DEBUG ("Adding inner ifd: %x", tag_map[i].exif_tag);
|
|
gst_exif_writer_write_tag_header (&writer, tag_map[i].exif_tag,
|
|
EXIF_TYPE_LONG, 1,
|
|
gst_byte_writer_get_size (&writer.datawriter), NULL);
|
|
|
|
if (gst_buffer_map (inner_ifd, &info, GST_MAP_READ)) {
|
|
handled &=
|
|
gst_byte_writer_put_data (&writer.datawriter, info.data,
|
|
info.size);
|
|
gst_buffer_unmap (inner_ifd, &info);
|
|
} else {
|
|
GST_WARNING ("Failed to map buffer for reading");
|
|
handled = FALSE;
|
|
}
|
|
gst_buffer_unref (inner_ifd);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
GST_LOG ("Checking tag %s", tag_map[i].gst_tag);
|
|
if (gst_tag_list_get_value_index (taglist, tag_map[i].gst_tag, 0) == NULL)
|
|
continue;
|
|
|
|
write_exif_tag_from_taglist (&writer, taglist, &tag_map[i]);
|
|
}
|
|
|
|
/* Add the next IFD offset, we just set it to 0 because
|
|
* there is no easy way to predict what it is going to be.
|
|
* The user might rewrite the value if needed */
|
|
handled &= gst_byte_writer_put_uint32_le (&writer.tagwriter, 0);
|
|
|
|
/* write the number of tags */
|
|
gst_byte_writer_set_pos (&writer.tagwriter, 0);
|
|
if (writer.byte_order == G_LITTLE_ENDIAN)
|
|
handled &=
|
|
gst_byte_writer_put_uint16_le (&writer.tagwriter, writer.tags_total);
|
|
else
|
|
handled &=
|
|
gst_byte_writer_put_uint16_be (&writer.tagwriter, writer.tags_total);
|
|
|
|
GST_DEBUG ("Number of tags rewritten to %d", writer.tags_total);
|
|
|
|
/* now that we know the tag headers size, we can add the offsets */
|
|
gst_exif_tag_rewrite_offsets (&writer.tagwriter, writer.byte_order,
|
|
base_offset + gst_byte_writer_get_size (&writer.tagwriter),
|
|
writer.tags_total, &writer.datawriter);
|
|
|
|
if (G_UNLIKELY (!handled)) {
|
|
GST_WARNING ("Error rewriting tags");
|
|
gst_buffer_unref (gst_exif_writer_reset_and_get_buffer (&writer));
|
|
return NULL;
|
|
}
|
|
|
|
return gst_exif_writer_reset_and_get_buffer (&writer);
|
|
}
|
|
|
|
static gboolean
|
|
parse_exif_tag_header (GstByteReader * reader, gint byte_order,
|
|
GstExifTagData * _tagdata)
|
|
{
|
|
g_assert (_tagdata);
|
|
|
|
/* read the fields */
|
|
if (byte_order == G_LITTLE_ENDIAN) {
|
|
if (!gst_byte_reader_get_uint16_le (reader, &_tagdata->tag) ||
|
|
!gst_byte_reader_get_uint16_le (reader, &_tagdata->tag_type) ||
|
|
!gst_byte_reader_get_uint32_le (reader, &_tagdata->count) ||
|
|
!gst_byte_reader_get_data (reader, 4, &_tagdata->offset_as_data)) {
|
|
return FALSE;
|
|
}
|
|
_tagdata->offset = GST_READ_UINT32_LE (_tagdata->offset_as_data);
|
|
} else {
|
|
if (!gst_byte_reader_get_uint16_be (reader, &_tagdata->tag) ||
|
|
!gst_byte_reader_get_uint16_be (reader, &_tagdata->tag_type) ||
|
|
!gst_byte_reader_get_uint32_be (reader, &_tagdata->count) ||
|
|
!gst_byte_reader_get_data (reader, 4, &_tagdata->offset_as_data)) {
|
|
return FALSE;
|
|
}
|
|
_tagdata->offset = GST_READ_UINT32_BE (_tagdata->offset_as_data);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
parse_exif_ifd (GstExifReader * exif_reader, gint buf_offset,
|
|
const GstExifTagMatch * tag_map)
|
|
{
|
|
GstByteReader reader;
|
|
guint16 entries = 0;
|
|
guint16 i;
|
|
GstMapInfo info;
|
|
|
|
g_return_val_if_fail (exif_reader->byte_order == G_LITTLE_ENDIAN
|
|
|| exif_reader->byte_order == G_BIG_ENDIAN, FALSE);
|
|
|
|
if (!gst_buffer_map (exif_reader->buffer, &info, GST_MAP_READ)) {
|
|
GST_WARNING ("Failed to map buffer for reading");
|
|
return FALSE;
|
|
}
|
|
|
|
gst_byte_reader_init (&reader, info.data, info.size);
|
|
if (!gst_byte_reader_set_pos (&reader, buf_offset))
|
|
goto invalid_offset;
|
|
|
|
/* read the IFD entries number */
|
|
if (exif_reader->byte_order == G_LITTLE_ENDIAN) {
|
|
if (!gst_byte_reader_get_uint16_le (&reader, &entries))
|
|
goto read_error;
|
|
} else {
|
|
if (!gst_byte_reader_get_uint16_be (&reader, &entries))
|
|
goto read_error;
|
|
}
|
|
GST_DEBUG ("Read number of entries: %u", entries);
|
|
|
|
/* iterate over the buffer and find the tags and stuff */
|
|
for (i = 0; i < entries; i++) {
|
|
GstExifTagData tagdata;
|
|
gint map_index;
|
|
|
|
GST_LOG ("Reading entry: %u", i);
|
|
|
|
if (!parse_exif_tag_header (&reader, exif_reader->byte_order, &tagdata))
|
|
goto read_error;
|
|
|
|
GST_DEBUG ("Parsed tag: id 0x%x, type %u, count %u, offset %u (0x%x)"
|
|
", buf size: %u", tagdata.tag, tagdata.tag_type, tagdata.count,
|
|
tagdata.offset, tagdata.offset, gst_byte_reader_get_size (&reader));
|
|
|
|
map_index = exif_tag_map_find_reverse (tagdata.tag, tag_map, TRUE);
|
|
if (map_index == -1) {
|
|
GST_WARNING ("Unmapped exif tag: 0x%x", tagdata.tag);
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* inner ifd tags handling, errors processing those are being ignored
|
|
* and we try to continue the parsing
|
|
*/
|
|
if (tagdata.tag == EXIF_GPS_IFD_TAG) {
|
|
parse_exif_ifd (exif_reader,
|
|
tagdata.offset - exif_reader->base_offset, tag_map_gps);
|
|
|
|
continue;
|
|
}
|
|
if (tagdata.tag == EXIF_IFD_TAG) {
|
|
parse_exif_ifd (exif_reader,
|
|
tagdata.offset - exif_reader->base_offset, tag_map_exif);
|
|
|
|
continue;
|
|
}
|
|
if (tagdata.tag == EXIF_VERSION_TAG ||
|
|
tagdata.tag == EXIF_FLASHPIX_VERSION_TAG) {
|
|
/* skip */
|
|
continue;
|
|
}
|
|
|
|
/* tags that need specialized deserialization */
|
|
if (tag_map[map_index].deserialize) {
|
|
i += tag_map[map_index].deserialize (exif_reader, &reader,
|
|
&tag_map[map_index], &tagdata);
|
|
continue;
|
|
}
|
|
|
|
switch (tagdata.tag_type) {
|
|
case EXIF_TYPE_ASCII:
|
|
parse_exif_ascii_tag (exif_reader, &tag_map[map_index],
|
|
tagdata.count, tagdata.offset, tagdata.offset_as_data);
|
|
break;
|
|
case EXIF_TYPE_RATIONAL:
|
|
parse_exif_rational_tag (exif_reader, tag_map[map_index].gst_tag,
|
|
tagdata.count, tagdata.offset, 1, FALSE);
|
|
break;
|
|
case EXIF_TYPE_SRATIONAL:
|
|
parse_exif_rational_tag (exif_reader, tag_map[map_index].gst_tag,
|
|
tagdata.count, tagdata.offset, 1, TRUE);
|
|
break;
|
|
case EXIF_TYPE_UNDEFINED:
|
|
parse_exif_undefined_tag (exif_reader, &tag_map[map_index],
|
|
tagdata.count, tagdata.offset, tagdata.offset_as_data);
|
|
break;
|
|
case EXIF_TYPE_LONG:
|
|
parse_exif_long_tag (exif_reader, &tag_map[map_index],
|
|
tagdata.count, tagdata.offset, tagdata.offset_as_data);
|
|
break;
|
|
case EXIF_TYPE_SHORT:
|
|
parse_exif_short_tag (exif_reader, &tag_map[map_index],
|
|
tagdata.count, tagdata.offset, tagdata.offset_as_data);
|
|
break;
|
|
default:
|
|
GST_WARNING ("Unhandled tag type: %u", tagdata.tag_type);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* check if the pending tags have something that can still be added */
|
|
{
|
|
GSList *walker;
|
|
GstExifTagData *data;
|
|
|
|
for (walker = exif_reader->pending_tags; walker;
|
|
walker = g_slist_next (walker)) {
|
|
data = (GstExifTagData *) walker->data;
|
|
switch (data->tag) {
|
|
case EXIF_TAG_XRESOLUTION:
|
|
parse_exif_rational_tag (exif_reader, GST_TAG_IMAGE_HORIZONTAL_PPI,
|
|
data->count, data->offset, 1, FALSE);
|
|
break;
|
|
case EXIF_TAG_YRESOLUTION:
|
|
parse_exif_rational_tag (exif_reader, GST_TAG_IMAGE_VERTICAL_PPI,
|
|
data->count, data->offset, 1, FALSE);
|
|
break;
|
|
default:
|
|
/* NOP */
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
gst_buffer_unmap (exif_reader->buffer, &info);
|
|
|
|
return TRUE;
|
|
|
|
invalid_offset:
|
|
{
|
|
GST_WARNING ("Buffer offset invalid when parsing exif ifd");
|
|
gst_buffer_unmap (exif_reader->buffer, &info);
|
|
return FALSE;
|
|
}
|
|
read_error:
|
|
{
|
|
GST_WARNING ("Failed to parse the exif ifd");
|
|
gst_buffer_unmap (exif_reader->buffer, &info);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* gst_tag_list_to_exif_buffer:
|
|
* @taglist: The taglist
|
|
* @byte_order: byte order used in writing (G_LITTLE_ENDIAN or G_BIG_ENDIAN)
|
|
* @base_offset: Offset from the tiff header first byte
|
|
*
|
|
* Formats the tags in taglist on exif format. The resulting buffer contains
|
|
* the tags IFD and is followed by the data pointed by the tag entries.
|
|
*
|
|
* Returns: A GstBuffer containing the tag entries followed by the tag data
|
|
*/
|
|
GstBuffer *
|
|
gst_tag_list_to_exif_buffer (const GstTagList * taglist, gint byte_order,
|
|
guint32 base_offset)
|
|
{
|
|
return write_exif_ifd (taglist, byte_order, base_offset, tag_map_ifd0);
|
|
}
|
|
|
|
/**
|
|
* gst_tag_list_to_exif_buffer_with_tiff_header:
|
|
* @taglist: The taglist
|
|
*
|
|
* Formats the tags in taglist into exif structure, a tiff header
|
|
* is put in the beginning of the buffer.
|
|
*
|
|
* Returns: A GstBuffer containing the data
|
|
*/
|
|
GstBuffer *
|
|
gst_tag_list_to_exif_buffer_with_tiff_header (const GstTagList * taglist)
|
|
{
|
|
GstBuffer *ifd, *res;
|
|
GstByteWriter writer;
|
|
GstMapInfo info;
|
|
gboolean handled = TRUE;
|
|
|
|
ifd = gst_tag_list_to_exif_buffer (taglist, G_BYTE_ORDER, 8);
|
|
if (ifd == NULL) {
|
|
GST_WARNING ("Failed to create exif buffer");
|
|
return NULL;
|
|
}
|
|
|
|
if (!gst_buffer_map (ifd, &info, GST_MAP_READ)) {
|
|
GST_WARNING ("Failed to map buffer for reading");
|
|
gst_buffer_unref (ifd);
|
|
return NULL;
|
|
}
|
|
|
|
/* TODO what is the correct endianness here? */
|
|
gst_byte_writer_init_with_size (&writer, info.size + TIFF_HEADER_SIZE, FALSE);
|
|
/* TIFF header */
|
|
if (G_BYTE_ORDER == G_LITTLE_ENDIAN) {
|
|
handled &= gst_byte_writer_put_uint16_le (&writer, TIFF_LITTLE_ENDIAN);
|
|
handled &= gst_byte_writer_put_uint16_le (&writer, 42);
|
|
handled &= gst_byte_writer_put_uint32_le (&writer, 8);
|
|
} else {
|
|
handled &= gst_byte_writer_put_uint16_be (&writer, TIFF_BIG_ENDIAN);
|
|
handled &= gst_byte_writer_put_uint16_be (&writer, 42);
|
|
handled &= gst_byte_writer_put_uint32_be (&writer, 8);
|
|
}
|
|
if (!gst_byte_writer_put_data (&writer, info.data, info.size)) {
|
|
GST_WARNING ("Byte writer size mismatch");
|
|
/* reaching here is a programming error because we should have a buffer
|
|
* large enough */
|
|
g_assert_not_reached ();
|
|
gst_buffer_unmap (ifd, &info);
|
|
gst_buffer_unref (ifd);
|
|
gst_byte_writer_reset (&writer);
|
|
return NULL;
|
|
}
|
|
gst_buffer_unmap (ifd, &info);
|
|
gst_buffer_unref (ifd);
|
|
|
|
res = gst_byte_writer_reset_and_get_buffer (&writer);
|
|
|
|
if (G_UNLIKELY (!handled)) {
|
|
GST_WARNING ("Error creating buffer");
|
|
gst_buffer_unref (res);
|
|
res = NULL;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
/**
|
|
* gst_tag_list_from_exif_buffer:
|
|
* @buffer: The exif buffer
|
|
* @byte_order: byte order of the data
|
|
* @base_offset: Offset from the tiff header to this buffer
|
|
*
|
|
* Parses the IFD and IFD tags data contained in the buffer and puts it
|
|
* on a taglist. The base_offset is used to subtract from the offset in
|
|
* the tag entries and be able to get the offset relative to the buffer
|
|
* start
|
|
*
|
|
* Returns: The parsed taglist
|
|
*/
|
|
GstTagList *
|
|
gst_tag_list_from_exif_buffer (GstBuffer * buffer, gint byte_order,
|
|
guint32 base_offset)
|
|
{
|
|
GstExifReader reader;
|
|
g_return_val_if_fail (byte_order == G_LITTLE_ENDIAN
|
|
|| byte_order == G_BIG_ENDIAN, NULL);
|
|
|
|
gst_exif_reader_init (&reader, byte_order, buffer, base_offset);
|
|
|
|
if (!parse_exif_ifd (&reader, 0, tag_map_ifd0))
|
|
goto read_error;
|
|
|
|
return gst_exif_reader_reset (&reader, TRUE);
|
|
|
|
read_error:
|
|
{
|
|
gst_exif_reader_reset (&reader, FALSE);
|
|
GST_WARNING ("Failed to parse the exif buffer");
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* gst_tag_list_from_exif_buffer_with_tiff_header:
|
|
* @buffer: The exif buffer
|
|
*
|
|
* Parses the exif tags starting with a tiff header structure.
|
|
*
|
|
* Returns: The taglist
|
|
*/
|
|
GstTagList *
|
|
gst_tag_list_from_exif_buffer_with_tiff_header (GstBuffer * buffer)
|
|
{
|
|
GstByteReader reader;
|
|
guint16 fortytwo = 42;
|
|
guint16 endianness = 0;
|
|
guint32 offset;
|
|
GstTagList *taglist = NULL;
|
|
GstBuffer *subbuffer;
|
|
GstMapInfo info, sinfo;
|
|
|
|
if (!gst_buffer_map (buffer, &info, GST_MAP_READ)) {
|
|
GST_WARNING ("Failed to map buffer for reading");
|
|
return NULL;
|
|
}
|
|
|
|
GST_LOG ("Parsing exif tags with tiff header of size %" G_GSIZE_FORMAT,
|
|
info.size);
|
|
|
|
gst_byte_reader_init (&reader, info.data, info.size);
|
|
|
|
GST_LOG ("Parsing the tiff header");
|
|
if (!gst_byte_reader_get_uint16_be (&reader, &endianness)) {
|
|
goto byte_reader_fail;
|
|
}
|
|
|
|
if (endianness == TIFF_LITTLE_ENDIAN) {
|
|
if (!gst_byte_reader_get_uint16_le (&reader, &fortytwo) ||
|
|
!gst_byte_reader_get_uint32_le (&reader, &offset))
|
|
goto byte_reader_fail;
|
|
} else if (endianness == TIFF_BIG_ENDIAN) {
|
|
if (!gst_byte_reader_get_uint16_be (&reader, &fortytwo) ||
|
|
!gst_byte_reader_get_uint32_be (&reader, &offset))
|
|
goto byte_reader_fail;
|
|
} else
|
|
goto invalid_endianness;
|
|
|
|
if (fortytwo != 42)
|
|
goto invalid_magic;
|
|
|
|
subbuffer = gst_buffer_new_and_alloc (info.size - (TIFF_HEADER_SIZE - 2));
|
|
|
|
if (!gst_buffer_map (subbuffer, &sinfo, GST_MAP_WRITE))
|
|
goto map_failed;
|
|
|
|
memcpy (sinfo.data, info.data + TIFF_HEADER_SIZE,
|
|
info.size - TIFF_HEADER_SIZE);
|
|
gst_buffer_unmap (subbuffer, &sinfo);
|
|
|
|
taglist = gst_tag_list_from_exif_buffer (subbuffer,
|
|
endianness == TIFF_LITTLE_ENDIAN ? G_LITTLE_ENDIAN : G_BIG_ENDIAN, 8);
|
|
|
|
gst_buffer_unref (subbuffer);
|
|
|
|
done:
|
|
gst_buffer_unmap (buffer, &info);
|
|
|
|
return taglist;
|
|
|
|
map_failed:
|
|
{
|
|
GST_WARNING ("Failed to map buffer for writing");
|
|
gst_buffer_unref (subbuffer);
|
|
goto done;
|
|
}
|
|
byte_reader_fail:
|
|
{
|
|
GST_WARNING ("Failed to read values from buffer");
|
|
goto done;
|
|
}
|
|
invalid_endianness:
|
|
{
|
|
GST_WARNING ("Invalid endianness number %u", endianness);
|
|
goto done;
|
|
}
|
|
invalid_magic:
|
|
{
|
|
GST_WARNING ("Invalid magic number %u, should be 42", fortytwo);
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
/* special serialization functions */
|
|
EXIF_SERIALIZATION_DESERIALIZATION_MAP_STRING_TO_INT_FUNC (contrast,
|
|
capturing_contrast);
|
|
EXIF_SERIALIZATION_DESERIALIZATION_MAP_STRING_TO_INT_FUNC (exposure_mode,
|
|
capturing_exposure_mode);
|
|
EXIF_SERIALIZATION_DESERIALIZATION_MAP_STRING_TO_INT_FUNC (exposure_program,
|
|
capturing_exposure_program);
|
|
EXIF_SERIALIZATION_DESERIALIZATION_MAP_STRING_TO_INT_FUNC (gain_control,
|
|
capturing_gain_adjustment);
|
|
EXIF_SERIALIZATION_DESERIALIZATION_MAP_STRING_TO_INT_FUNC (metering_mode,
|
|
capturing_metering_mode);
|
|
EXIF_SERIALIZATION_DESERIALIZATION_MAP_STRING_TO_INT_FUNC (orientation,
|
|
image_orientation);
|
|
EXIF_SERIALIZATION_DESERIALIZATION_MAP_STRING_TO_INT_FUNC (saturation,
|
|
capturing_saturation);
|
|
EXIF_SERIALIZATION_DESERIALIZATION_MAP_STRING_TO_INT_FUNC (scene_capture_type,
|
|
capturing_scene_capture_type);
|
|
EXIF_SERIALIZATION_DESERIALIZATION_MAP_STRING_TO_INT_FUNC (sharpness,
|
|
capturing_sharpness);
|
|
EXIF_SERIALIZATION_DESERIALIZATION_MAP_STRING_TO_INT_FUNC (source,
|
|
capturing_source);
|
|
EXIF_SERIALIZATION_DESERIALIZATION_MAP_STRING_TO_INT_FUNC (white_balance,
|
|
capturing_white_balance);
|
|
|
|
static void
|
|
serialize_geo_coordinate (GstExifWriter * writer, const GstTagList * taglist,
|
|
const GstExifTagMatch * exiftag)
|
|
{
|
|
gboolean latitude;
|
|
gdouble value;
|
|
gint degrees;
|
|
gint minutes;
|
|
gint seconds;
|
|
guint32 offset;
|
|
|
|
latitude = exiftag->exif_tag == EXIF_TAG_GPS_LATITUDE; /* exif tag for latitude */
|
|
if (!gst_tag_list_get_double (taglist, exiftag->gst_tag, &value)) {
|
|
GST_WARNING ("Failed to get double from tag list for tag: %s",
|
|
exiftag->gst_tag);
|
|
return;
|
|
}
|
|
|
|
/* first write the Latitude or Longitude Ref */
|
|
if (latitude) {
|
|
if (value >= 0) {
|
|
write_exif_ascii_tag (writer, exiftag->complementary_tag, "N");
|
|
} else {
|
|
value *= -1;
|
|
write_exif_ascii_tag (writer, exiftag->complementary_tag, "S");
|
|
}
|
|
} else {
|
|
if (value >= 0) {
|
|
write_exif_ascii_tag (writer, exiftag->complementary_tag, "E");
|
|
} else {
|
|
value *= -1;
|
|
write_exif_ascii_tag (writer, exiftag->complementary_tag, "W");
|
|
}
|
|
}
|
|
|
|
/* now write the degrees stuff */
|
|
GST_LOG ("Converting geo location %lf to degrees", value);
|
|
degrees = (gint) value;
|
|
value -= degrees;
|
|
minutes = (gint) (value * 60);
|
|
value = (value * 60) - minutes;
|
|
seconds = (gint) (value * 60);
|
|
GST_LOG ("Converted geo location to %d.%d'%d'' degrees", degrees,
|
|
minutes, seconds);
|
|
|
|
offset = gst_byte_writer_get_size (&writer->datawriter);
|
|
gst_exif_writer_write_tag_header (writer, exiftag->exif_tag,
|
|
EXIF_TYPE_RATIONAL, 3, offset, NULL);
|
|
gst_exif_writer_write_rational_data (writer, degrees, 1);
|
|
gst_exif_writer_write_rational_data (writer, minutes, 1);
|
|
gst_exif_writer_write_rational_data (writer, seconds, 1);
|
|
}
|
|
|
|
static gint
|
|
deserialize_geo_coordinate (GstExifReader * exif_reader,
|
|
GstByteReader * reader, const GstExifTagMatch * exiftag,
|
|
GstExifTagData * tagdata)
|
|
{
|
|
GstByteReader fractions_reader;
|
|
gint multiplier;
|
|
GstExifTagData next_tagdata;
|
|
gint ret = 0;
|
|
/* for the conversion */
|
|
guint32 degrees_n = 0;
|
|
guint32 degrees_d = 1;
|
|
guint32 minutes_n = 0;
|
|
guint32 minutes_d = 1;
|
|
guint32 seconds_n = 0;
|
|
guint32 seconds_d = 1;
|
|
gdouble degrees;
|
|
gdouble minutes;
|
|
gdouble seconds;
|
|
GstMapInfo info = { NULL };
|
|
|
|
GST_LOG ("Starting to parse %s tag in exif 0x%x", exiftag->gst_tag,
|
|
exiftag->exif_tag);
|
|
|
|
if (exiftag->complementary_tag != tagdata->tag) {
|
|
/* First should come the 'Ref' tags */
|
|
GST_WARNING ("Tag %d is not the 'Ref' tag for latitude nor longitude",
|
|
tagdata->tag);
|
|
return ret;
|
|
}
|
|
|
|
if (tagdata->offset_as_data[0] == 'N' || tagdata->offset_as_data[0] == 'E') {
|
|
multiplier = 1;
|
|
} else if (tagdata->offset_as_data[0] == 'S'
|
|
|| tagdata->offset_as_data[0] == 'W') {
|
|
multiplier = -1;
|
|
} else {
|
|
GST_WARNING ("Invalid LatitudeRef or LongitudeRef %c",
|
|
tagdata->offset_as_data[0]);
|
|
return ret;
|
|
}
|
|
|
|
/* now read the following tag that must be the latitude or longitude */
|
|
if (exif_reader->byte_order == G_LITTLE_ENDIAN) {
|
|
if (!gst_byte_reader_peek_uint16_le (reader, &next_tagdata.tag))
|
|
goto reader_fail;
|
|
} else {
|
|
if (!gst_byte_reader_peek_uint16_be (reader, &next_tagdata.tag))
|
|
goto reader_fail;
|
|
}
|
|
|
|
if (exiftag->exif_tag != next_tagdata.tag) {
|
|
GST_WARNING ("This is not a geo coordinate tag");
|
|
return ret;
|
|
}
|
|
|
|
/* read the remaining tag entry data */
|
|
if (!parse_exif_tag_header (reader, exif_reader->byte_order, &next_tagdata)) {
|
|
ret = -1;
|
|
goto reader_fail;
|
|
}
|
|
|
|
ret = 1;
|
|
|
|
/* some checking */
|
|
if (next_tagdata.tag_type != EXIF_TYPE_RATIONAL) {
|
|
GST_WARNING ("Invalid type %d for geo coordinate (latitude/longitude)",
|
|
next_tagdata.tag_type);
|
|
return ret;
|
|
}
|
|
if (next_tagdata.count != 3) {
|
|
GST_WARNING ("Geo coordinate should use 3 fractions, we have %u",
|
|
next_tagdata.count);
|
|
return ret;
|
|
}
|
|
|
|
if (!gst_buffer_map (exif_reader->buffer, &info, GST_MAP_READ)) {
|
|
GST_WARNING ("Failed to map buffer for reading");
|
|
return ret;
|
|
}
|
|
|
|
/* now parse the fractions */
|
|
gst_byte_reader_init (&fractions_reader, info.data, info.size);
|
|
|
|
if (!gst_byte_reader_set_pos (&fractions_reader,
|
|
next_tagdata.offset - exif_reader->base_offset))
|
|
goto reader_fail;
|
|
|
|
if (exif_reader->byte_order == G_LITTLE_ENDIAN) {
|
|
if (!gst_byte_reader_get_uint32_le (&fractions_reader, °rees_n) ||
|
|
!gst_byte_reader_get_uint32_le (&fractions_reader, °rees_d) ||
|
|
!gst_byte_reader_get_uint32_le (&fractions_reader, &minutes_n) ||
|
|
!gst_byte_reader_get_uint32_le (&fractions_reader, &minutes_d) ||
|
|
!gst_byte_reader_get_uint32_le (&fractions_reader, &seconds_n) ||
|
|
!gst_byte_reader_get_uint32_le (&fractions_reader, &seconds_d))
|
|
goto reader_fail;
|
|
} else {
|
|
if (!gst_byte_reader_get_uint32_be (&fractions_reader, °rees_n) ||
|
|
!gst_byte_reader_get_uint32_be (&fractions_reader, °rees_d) ||
|
|
!gst_byte_reader_get_uint32_be (&fractions_reader, &minutes_n) ||
|
|
!gst_byte_reader_get_uint32_be (&fractions_reader, &minutes_d) ||
|
|
!gst_byte_reader_get_uint32_be (&fractions_reader, &seconds_n) ||
|
|
!gst_byte_reader_get_uint32_be (&fractions_reader, &seconds_d))
|
|
goto reader_fail;
|
|
}
|
|
gst_buffer_unmap (exif_reader->buffer, &info);
|
|
|
|
GST_DEBUG ("Read degrees fraction for tag %s: %u/%u %u/%u %u/%u",
|
|
exiftag->gst_tag, degrees_n, degrees_d, minutes_n, minutes_d,
|
|
seconds_n, seconds_d);
|
|
|
|
gst_util_fraction_to_double (degrees_n, degrees_d, °rees);
|
|
gst_util_fraction_to_double (minutes_n, minutes_d, &minutes);
|
|
gst_util_fraction_to_double (seconds_n, seconds_d, &seconds);
|
|
|
|
minutes += seconds / 60;
|
|
degrees += minutes / 60;
|
|
degrees *= multiplier;
|
|
|
|
GST_DEBUG ("Adding %s tag: %lf", exiftag->gst_tag, degrees);
|
|
gst_tag_list_add (exif_reader->taglist, GST_TAG_MERGE_REPLACE,
|
|
exiftag->gst_tag, degrees, NULL);
|
|
|
|
return ret;
|
|
|
|
reader_fail:
|
|
GST_WARNING ("Failed to read fields from buffer (too short?)");
|
|
if (info.data)
|
|
gst_buffer_unmap (exif_reader->buffer, &info);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static void
|
|
serialize_geo_direction (GstExifWriter * writer, const GstTagList * taglist,
|
|
const GstExifTagMatch * exiftag)
|
|
{
|
|
gdouble value;
|
|
|
|
if (!gst_tag_list_get_double (taglist, exiftag->gst_tag, &value)) {
|
|
GST_WARNING ("Failed to get double from tag list for tag: %s",
|
|
exiftag->gst_tag);
|
|
return;
|
|
}
|
|
|
|
/* first write the direction ref */
|
|
write_exif_ascii_tag (writer, exiftag->complementary_tag, "T");
|
|
gst_exif_writer_write_rational_tag_from_double (writer,
|
|
exiftag->exif_tag, value);
|
|
}
|
|
|
|
static gint
|
|
deserialize_geo_direction (GstExifReader * exif_reader,
|
|
GstByteReader * reader, const GstExifTagMatch * exiftag,
|
|
GstExifTagData * tagdata)
|
|
{
|
|
GstExifTagData next_tagdata = { 0, };
|
|
gint ret = 0;
|
|
|
|
GST_LOG ("Starting to parse %s tag in exif 0x%x", exiftag->gst_tag,
|
|
exiftag->exif_tag);
|
|
|
|
if (exiftag->complementary_tag == tagdata->tag) {
|
|
/* First should come the 'Ref' tags */
|
|
if (tagdata->offset_as_data[0] == 'M') {
|
|
GST_WARNING ("Magnetic direction is not supported");
|
|
return ret;
|
|
} else if (tagdata->offset_as_data[0] == 'T') {
|
|
/* nop */
|
|
} else {
|
|
GST_WARNING ("Invalid Ref for direction or track %c",
|
|
tagdata->offset_as_data[0]);
|
|
return ret;
|
|
}
|
|
} else {
|
|
GST_DEBUG ("No Direction Ref, using default=T");
|
|
if (tagdata->tag == exiftag->exif_tag) {
|
|
/* this is the main tag */
|
|
tagdata_copy (&next_tagdata, tagdata);
|
|
}
|
|
}
|
|
|
|
if (next_tagdata.tag == 0) {
|
|
/* now read the following tag that must be the exif_tag */
|
|
if (exif_reader->byte_order == G_LITTLE_ENDIAN) {
|
|
if (!gst_byte_reader_peek_uint16_le (reader, &next_tagdata.tag))
|
|
goto reader_fail;
|
|
} else {
|
|
if (!gst_byte_reader_peek_uint16_be (reader, &next_tagdata.tag))
|
|
goto reader_fail;
|
|
}
|
|
|
|
if (exiftag->exif_tag != next_tagdata.tag) {
|
|
GST_WARNING ("Unexpected tag");
|
|
return ret;
|
|
}
|
|
|
|
/* read the remaining tag entry data */
|
|
if (!parse_exif_tag_header (reader, exif_reader->byte_order, &next_tagdata)) {
|
|
ret = -1;
|
|
goto reader_fail;
|
|
}
|
|
ret = 1;
|
|
}
|
|
|
|
/* some checking */
|
|
if (next_tagdata.tag_type != EXIF_TYPE_RATIONAL) {
|
|
GST_WARNING ("Invalid type %d for 0x%x", next_tagdata.tag_type,
|
|
next_tagdata.tag);
|
|
return ret;
|
|
}
|
|
if (next_tagdata.count != 1) {
|
|
GST_WARNING ("0x%x tag must have a single fraction, we have %u",
|
|
next_tagdata.tag_type, next_tagdata.count);
|
|
return ret;
|
|
}
|
|
|
|
parse_exif_rational_tag (exif_reader,
|
|
exiftag->gst_tag, next_tagdata.count, next_tagdata.offset, 1, FALSE);
|
|
|
|
return ret;
|
|
|
|
reader_fail:
|
|
GST_WARNING ("Failed to read fields from buffer (too short?)");
|
|
return ret;
|
|
}
|
|
|
|
|
|
static void
|
|
serialize_geo_elevation (GstExifWriter * writer, const GstTagList * taglist,
|
|
const GstExifTagMatch * exiftag)
|
|
{
|
|
gdouble value;
|
|
|
|
if (!gst_tag_list_get_double (taglist, exiftag->gst_tag, &value)) {
|
|
GST_WARNING ("Failed to get double from tag list for tag: %s",
|
|
exiftag->gst_tag);
|
|
return;
|
|
}
|
|
|
|
/* first write the Ref */
|
|
gst_exif_writer_write_byte_tag (writer,
|
|
exiftag->complementary_tag, value >= 0 ? 0 : 1);
|
|
|
|
if (value < 0)
|
|
value *= -1;
|
|
|
|
/* now the value */
|
|
gst_exif_writer_write_rational_tag_from_double (writer,
|
|
exiftag->exif_tag, value);
|
|
}
|
|
|
|
static gint
|
|
deserialize_geo_elevation (GstExifReader * exif_reader,
|
|
GstByteReader * reader, const GstExifTagMatch * exiftag,
|
|
GstExifTagData * tagdata)
|
|
{
|
|
GstExifTagData next_tagdata = { 0, };
|
|
gint multiplier = 1;
|
|
gint ret = 0;
|
|
|
|
GST_LOG ("Starting to parse %s tag in exif 0x%x", exiftag->gst_tag,
|
|
exiftag->exif_tag);
|
|
|
|
if (exiftag->complementary_tag == tagdata->tag) {
|
|
if (tagdata->offset_as_data[0] == 0) {
|
|
/* NOP */
|
|
} else if (tagdata->offset_as_data[0] == 1) {
|
|
multiplier = -1;
|
|
} else {
|
|
GST_WARNING ("Invalid GPSAltitudeRef %u", tagdata->offset_as_data[0]);
|
|
return ret;
|
|
}
|
|
} else {
|
|
GST_DEBUG ("No GPSAltitudeRef, using default=0");
|
|
if (tagdata->tag == exiftag->exif_tag) {
|
|
tagdata_copy (&next_tagdata, tagdata);
|
|
}
|
|
}
|
|
|
|
/* now read the following tag that must be the exif_tag */
|
|
if (next_tagdata.tag == 0) {
|
|
if (exif_reader->byte_order == G_LITTLE_ENDIAN) {
|
|
if (!gst_byte_reader_peek_uint16_le (reader, &next_tagdata.tag))
|
|
goto reader_fail;
|
|
} else {
|
|
if (!gst_byte_reader_peek_uint16_be (reader, &next_tagdata.tag))
|
|
goto reader_fail;
|
|
}
|
|
|
|
if (exiftag->exif_tag != next_tagdata.tag) {
|
|
GST_WARNING ("Unexpected tag");
|
|
return ret;
|
|
}
|
|
|
|
/* read the remaining tag entry data */
|
|
if (!parse_exif_tag_header (reader, exif_reader->byte_order, &next_tagdata)) {
|
|
ret = -1;
|
|
goto reader_fail;
|
|
}
|
|
ret = 1;
|
|
}
|
|
|
|
/* some checking */
|
|
if (next_tagdata.tag_type != EXIF_TYPE_RATIONAL) {
|
|
GST_WARNING ("Invalid type %d for 0x%x", next_tagdata.tag_type,
|
|
next_tagdata.tag);
|
|
return ret;
|
|
}
|
|
if (next_tagdata.count != 1) {
|
|
GST_WARNING ("0x%x tag must have a single fraction, we have %u",
|
|
next_tagdata.tag_type, next_tagdata.count);
|
|
return ret;
|
|
}
|
|
|
|
parse_exif_rational_tag (exif_reader,
|
|
exiftag->gst_tag, next_tagdata.count, next_tagdata.offset, multiplier,
|
|
FALSE);
|
|
|
|
return ret;
|
|
|
|
reader_fail:
|
|
GST_WARNING ("Failed to read fields from buffer (too short?)");
|
|
return ret;
|
|
}
|
|
|
|
|
|
static void
|
|
serialize_speed (GstExifWriter * writer, const GstTagList * taglist,
|
|
const GstExifTagMatch * exiftag)
|
|
{
|
|
gdouble value;
|
|
|
|
if (!gst_tag_list_get_double (taglist, exiftag->gst_tag, &value)) {
|
|
GST_WARNING ("Failed to get double from tag list for tag: %s",
|
|
exiftag->gst_tag);
|
|
return;
|
|
}
|
|
|
|
/* first write the Ref */
|
|
write_exif_ascii_tag (writer, exiftag->complementary_tag, "K");
|
|
|
|
/* now the value */
|
|
gst_exif_writer_write_rational_tag_from_double (writer,
|
|
exiftag->exif_tag, value * METERS_PER_SECOND_TO_KILOMETERS_PER_HOUR);
|
|
}
|
|
|
|
static gint
|
|
deserialize_speed (GstExifReader * exif_reader,
|
|
GstByteReader * reader, const GstExifTagMatch * exiftag,
|
|
GstExifTagData * tagdata)
|
|
{
|
|
GstExifTagData next_tagdata = { 0, };
|
|
gdouble multiplier = 1;
|
|
gint ret = 0;
|
|
|
|
GST_LOG ("Starting to parse %s tag in exif 0x%x", exiftag->gst_tag,
|
|
exiftag->exif_tag);
|
|
|
|
if (exiftag->complementary_tag == tagdata->tag) {
|
|
if (tagdata->offset_as_data[0] == 'K') {
|
|
multiplier = KILOMETERS_PER_HOUR_TO_METERS_PER_SECOND;
|
|
} else if (tagdata->offset_as_data[0] == 'M') {
|
|
multiplier = MILES_PER_HOUR_TO_METERS_PER_SECOND;
|
|
} else if (tagdata->offset_as_data[0] == 'N') {
|
|
multiplier = KNOTS_TO_METERS_PER_SECOND;
|
|
} else {
|
|
GST_WARNING ("Invalid GPSSpeedRed %c", tagdata->offset_as_data[0]);
|
|
return ret;
|
|
}
|
|
} else {
|
|
GST_DEBUG ("No GPSSpeedRef, using default=K");
|
|
multiplier = KILOMETERS_PER_HOUR_TO_METERS_PER_SECOND;
|
|
|
|
if (tagdata->tag == exiftag->exif_tag) {
|
|
tagdata_copy (&next_tagdata, tagdata);
|
|
}
|
|
}
|
|
|
|
/* now read the following tag that must be the exif_tag */
|
|
if (next_tagdata.tag == 0) {
|
|
if (exif_reader->byte_order == G_LITTLE_ENDIAN) {
|
|
if (!gst_byte_reader_peek_uint16_le (reader, &next_tagdata.tag))
|
|
goto reader_fail;
|
|
} else {
|
|
if (!gst_byte_reader_peek_uint16_be (reader, &next_tagdata.tag))
|
|
goto reader_fail;
|
|
}
|
|
|
|
if (exiftag->exif_tag != next_tagdata.tag) {
|
|
GST_WARNING ("Unexpected tag");
|
|
return ret;
|
|
}
|
|
|
|
/* read the remaining tag entry data */
|
|
if (!parse_exif_tag_header (reader, exif_reader->byte_order, &next_tagdata)) {
|
|
ret = -1;
|
|
goto reader_fail;
|
|
}
|
|
ret = 1;
|
|
}
|
|
|
|
|
|
/* some checking */
|
|
if (next_tagdata.tag_type != EXIF_TYPE_RATIONAL) {
|
|
GST_WARNING ("Invalid type %d for 0x%x", next_tagdata.tag_type,
|
|
next_tagdata.tag);
|
|
return ret;
|
|
}
|
|
if (next_tagdata.count != 1) {
|
|
GST_WARNING ("0x%x tag must have a single fraction, we have %u",
|
|
next_tagdata.tag_type, next_tagdata.count);
|
|
return ret;
|
|
}
|
|
|
|
parse_exif_rational_tag (exif_reader,
|
|
exiftag->gst_tag, next_tagdata.count, next_tagdata.offset, multiplier,
|
|
FALSE);
|
|
|
|
return ret;
|
|
|
|
reader_fail:
|
|
GST_WARNING ("Failed to read fields from buffer (too short?)");
|
|
return ret;
|
|
}
|
|
|
|
static void
|
|
serialize_shutter_speed (GstExifWriter * writer, const GstTagList * taglist,
|
|
const GstExifTagMatch * exiftag)
|
|
{
|
|
const GValue *value = NULL;
|
|
gdouble num;
|
|
|
|
value = gst_tag_list_get_value_index (taglist, exiftag->gst_tag, 0);
|
|
if (!value) {
|
|
GST_WARNING ("Failed to get shutter speed from from tag list");
|
|
return;
|
|
}
|
|
gst_util_fraction_to_double (gst_value_get_fraction_numerator (value),
|
|
gst_value_get_fraction_denominator (value), &num);
|
|
|
|
#ifdef HAVE_LOG2
|
|
num = -log2 (num);
|
|
#else
|
|
num = -log (num) / M_LN2;
|
|
#endif
|
|
|
|
/* now the value */
|
|
gst_exif_writer_write_signed_rational_tag_from_double (writer,
|
|
exiftag->exif_tag, num);
|
|
}
|
|
|
|
static gint
|
|
deserialize_shutter_speed (GstExifReader * exif_reader,
|
|
GstByteReader * reader, const GstExifTagMatch * exiftag,
|
|
GstExifTagData * tagdata)
|
|
{
|
|
gint32 frac_n, frac_d;
|
|
gdouble d;
|
|
GValue value = { 0 };
|
|
|
|
GST_LOG ("Starting to parse %s tag in exif 0x%x", exiftag->gst_tag,
|
|
exiftag->exif_tag);
|
|
|
|
if (!exif_reader_read_rational_tag (exif_reader, tagdata->count,
|
|
tagdata->offset, TRUE, &frac_n, &frac_d))
|
|
return 0;
|
|
|
|
gst_util_fraction_to_double (frac_n, frac_d, &d);
|
|
d = pow (2, -d);
|
|
gst_util_double_to_fraction (d, &frac_n, &frac_d);
|
|
|
|
g_value_init (&value, GST_TYPE_FRACTION);
|
|
gst_value_set_fraction (&value, frac_n, frac_d);
|
|
gst_tag_list_add_value (exif_reader->taglist, GST_TAG_MERGE_KEEP,
|
|
exiftag->gst_tag, &value);
|
|
g_value_unset (&value);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
serialize_aperture_value (GstExifWriter * writer, const GstTagList * taglist,
|
|
const GstExifTagMatch * exiftag)
|
|
{
|
|
gdouble num;
|
|
|
|
if (!gst_tag_list_get_double_index (taglist, exiftag->gst_tag, 0, &num)) {
|
|
GST_WARNING ("Failed to get focal ratio from from tag list");
|
|
return;
|
|
}
|
|
#ifdef HAVE_LOG2
|
|
num = 2 * log2 (num);
|
|
#else
|
|
num = 2 * (log (num) / M_LN2);
|
|
#endif
|
|
|
|
/* now the value */
|
|
gst_exif_writer_write_rational_tag_from_double (writer,
|
|
exiftag->exif_tag, num);
|
|
}
|
|
|
|
static gint
|
|
deserialize_aperture_value (GstExifReader * exif_reader,
|
|
GstByteReader * reader, const GstExifTagMatch * exiftag,
|
|
GstExifTagData * tagdata)
|
|
{
|
|
gint32 frac_n, frac_d;
|
|
gdouble d;
|
|
|
|
GST_LOG ("Starting to parse %s tag in exif 0x%x", exiftag->gst_tag,
|
|
exiftag->exif_tag);
|
|
|
|
if (!exif_reader_read_rational_tag (exif_reader, tagdata->count,
|
|
tagdata->offset, FALSE, &frac_n, &frac_d))
|
|
return 0;
|
|
|
|
gst_util_fraction_to_double (frac_n, frac_d, &d);
|
|
d = pow (2, d / 2);
|
|
|
|
gst_tag_list_add (exif_reader->taglist, GST_TAG_MERGE_KEEP,
|
|
exiftag->gst_tag, d, NULL);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
serialize_sensitivity_type (GstExifWriter * writer, const GstTagList * taglist,
|
|
const GstExifTagMatch * exiftag)
|
|
{
|
|
/* we only support ISOSpeed as the sensitivity type (3) */
|
|
gst_exif_writer_write_short_tag (writer, exiftag->exif_tag, 3);
|
|
}
|
|
|
|
static gint
|
|
deserialize_sensitivity_type (GstExifReader * exif_reader,
|
|
GstByteReader * reader, const GstExifTagMatch * exiftag,
|
|
GstExifTagData * tagdata)
|
|
{
|
|
GstExifTagData *sensitivity = NULL;
|
|
guint16 type_data;
|
|
|
|
if (exif_reader->byte_order == G_LITTLE_ENDIAN) {
|
|
type_data = GST_READ_UINT16_LE (tagdata->offset_as_data);
|
|
} else {
|
|
type_data = GST_READ_UINT16_BE (tagdata->offset_as_data);
|
|
}
|
|
|
|
if (type_data != 3) {
|
|
GST_WARNING ("We only support SensitivityType=3");
|
|
return 0;
|
|
}
|
|
|
|
/* check the pending tags for the PhotographicSensitivity tag */
|
|
sensitivity =
|
|
gst_exif_reader_get_pending_tag (exif_reader,
|
|
EXIF_TAG_PHOTOGRAPHIC_SENSITIVITY);
|
|
if (sensitivity == NULL) {
|
|
GST_WARNING ("PhotographicSensitivity tag not found");
|
|
return 0;
|
|
}
|
|
|
|
GST_LOG ("Starting to parse %s tag in exif 0x%x", exiftag->gst_tag,
|
|
exiftag->exif_tag);
|
|
|
|
gst_tag_list_add (exif_reader->taglist, GST_TAG_MERGE_KEEP,
|
|
GST_TAG_CAPTURING_ISO_SPEED, sensitivity->offset_as_data, NULL);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
serialize_flash (GstExifWriter * writer, const GstTagList * taglist,
|
|
const GstExifTagMatch * exiftag)
|
|
{
|
|
gboolean flash_fired;
|
|
const gchar *flash_mode;
|
|
guint16 tagvalue = 0;
|
|
|
|
if (!gst_tag_list_get_boolean_index (taglist, exiftag->gst_tag, 0,
|
|
&flash_fired)) {
|
|
GST_WARNING ("Failed to get flash fired from from tag list");
|
|
return;
|
|
}
|
|
|
|
if (flash_fired)
|
|
tagvalue = 1;
|
|
|
|
if (gst_tag_list_peek_string_index (taglist, GST_TAG_CAPTURING_FLASH_MODE, 0,
|
|
&flash_mode)) {
|
|
guint16 mode = 0;
|
|
if (strcmp (flash_mode, "auto") == 0) {
|
|
mode = 3;
|
|
} else if (strcmp (flash_mode, "always") == 0) {
|
|
mode = 1;
|
|
} else if (strcmp (flash_mode, "never") == 0) {
|
|
mode = 2;
|
|
}
|
|
|
|
tagvalue = tagvalue | (mode << 3);
|
|
} else {
|
|
GST_DEBUG ("flash-mode not available");
|
|
}
|
|
|
|
gst_exif_writer_write_short_tag (writer, exiftag->exif_tag, tagvalue);
|
|
}
|
|
|
|
static gint
|
|
deserialize_flash (GstExifReader * exif_reader,
|
|
GstByteReader * reader, const GstExifTagMatch * exiftag,
|
|
GstExifTagData * tagdata)
|
|
{
|
|
guint16 value = 0;
|
|
guint mode = 0;
|
|
const gchar *mode_str = NULL;
|
|
|
|
GST_LOG ("Starting to parse %s tag in exif 0x%x", exiftag->gst_tag,
|
|
exiftag->exif_tag);
|
|
|
|
if (exif_reader->byte_order == G_LITTLE_ENDIAN) {
|
|
value = GST_READ_UINT16_LE (tagdata->offset_as_data);
|
|
} else {
|
|
value = GST_READ_UINT16_BE (tagdata->offset_as_data);
|
|
}
|
|
|
|
/* check flash fired */
|
|
if (value & 0x1) {
|
|
gst_tag_list_add (exif_reader->taglist, GST_TAG_MERGE_REPLACE,
|
|
GST_TAG_CAPTURING_FLASH_FIRED, TRUE, NULL);
|
|
} else {
|
|
gst_tag_list_add (exif_reader->taglist, GST_TAG_MERGE_REPLACE,
|
|
GST_TAG_CAPTURING_FLASH_FIRED, FALSE, NULL);
|
|
}
|
|
|
|
mode = (value >> 3) & 0x3;
|
|
if (mode == 1) {
|
|
mode_str = "always";
|
|
} else if (mode == 2) {
|
|
mode_str = "never";
|
|
} else if (mode == 3) {
|
|
mode_str = "auto";
|
|
}
|
|
|
|
if (mode_str)
|
|
gst_tag_list_add (exif_reader->taglist, GST_TAG_MERGE_REPLACE,
|
|
GST_TAG_CAPTURING_FLASH_MODE, mode_str, NULL);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static gint
|
|
deserialize_resolution (GstExifReader * exif_reader,
|
|
GstByteReader * reader, const GstExifTagMatch * exiftag,
|
|
GstExifTagData * tagdata)
|
|
{
|
|
GstExifTagData *xres = NULL;
|
|
GstExifTagData *yres = NULL;
|
|
guint16 unit;
|
|
gdouble multiplier;
|
|
|
|
if (exif_reader->byte_order == G_LITTLE_ENDIAN) {
|
|
unit = GST_READ_UINT16_LE (tagdata->offset_as_data);
|
|
} else {
|
|
unit = GST_READ_UINT16_BE (tagdata->offset_as_data);
|
|
}
|
|
|
|
switch (unit) {
|
|
case 2: /* inch */
|
|
multiplier = 1;
|
|
break;
|
|
case 3: /* cm */
|
|
multiplier = 1 / 2.54;
|
|
break;
|
|
default:
|
|
GST_WARNING ("Invalid resolution unit, ignoring PPI tags");
|
|
return 0;
|
|
}
|
|
|
|
xres = gst_exif_reader_get_pending_tag (exif_reader, EXIF_TAG_XRESOLUTION);
|
|
if (xres) {
|
|
parse_exif_rational_tag (exif_reader, GST_TAG_IMAGE_HORIZONTAL_PPI,
|
|
xres->count, xres->offset, multiplier, FALSE);
|
|
}
|
|
yres = gst_exif_reader_get_pending_tag (exif_reader, EXIF_TAG_YRESOLUTION);
|
|
if (yres) {
|
|
parse_exif_rational_tag (exif_reader, GST_TAG_IMAGE_VERTICAL_PPI,
|
|
yres->count, yres->offset, multiplier, FALSE);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
serialize_scene_type (GstExifWriter * writer, const GstTagList * taglist,
|
|
const GstExifTagMatch * exiftag)
|
|
{
|
|
const gchar *str;
|
|
guint8 value = 0;
|
|
|
|
if (gst_tag_list_peek_string_index (taglist, GST_TAG_CAPTURING_SOURCE, 0,
|
|
&str)) {
|
|
if (strcmp (str, "dsc") == 0) {
|
|
value = 1;
|
|
}
|
|
}
|
|
|
|
if (value != 0)
|
|
write_exif_undefined_tag (writer, exiftag->exif_tag, &value, 1);
|
|
}
|
|
|
|
static gint
|
|
deserialize_scene_type (GstExifReader * exif_reader,
|
|
GstByteReader * reader, const GstExifTagMatch * exiftag,
|
|
GstExifTagData * tagdata)
|
|
{
|
|
guint8 value = 0;
|
|
|
|
GST_LOG ("Starting to parse %s tag in exif 0x%x", exiftag->gst_tag,
|
|
exiftag->exif_tag);
|
|
|
|
value = GST_READ_UINT8 (tagdata->offset_as_data);
|
|
|
|
if (value == 1) {
|
|
gst_tag_list_add (exif_reader->taglist, GST_TAG_MERGE_KEEP,
|
|
GST_TAG_CAPTURING_SOURCE, "dsc", NULL);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static gint
|
|
deserialize_add_to_pending_tags (GstExifReader * exif_reader,
|
|
GstByteReader * reader, const GstExifTagMatch * exiftag,
|
|
GstExifTagData * tagdata)
|
|
{
|
|
GST_LOG ("Adding %s tag in exif 0x%x to pending tags", exiftag->gst_tag,
|
|
exiftag->exif_tag);
|
|
|
|
/* add it to the pending tags, as we can only parse it when we find the
|
|
* SensitivityType tag */
|
|
gst_exif_reader_add_pending_tag (exif_reader, tagdata);
|
|
return 0;
|
|
}
|
|
|
|
#undef EXIF_SERIALIZATION_FUNC
|
|
#undef EXIF_DESERIALIZATION_FUNC
|
|
#undef EXIF_SERIALIZATION_DESERIALIZATION_FUNC
|