gstreamer/gst-libs/gst/tag/gstexiftag.c
Paulo Neves 5dd720e064 exiftag: Increase serialized geo precision
The serialization of double typed geographical
coordinates to DMS system supported by the exif
standards was previously truncated without need.

The previous code truncated the seconds part of
the coordinate to a fraction with denominator
equal to 1 causing a bug on the deserialization
when the test for the coordinate to be serialized
was more precise.

This patch applies a 10E6 multiplier to the numerator
equal to the denominator of the rational number.

Eg. Latitude = 89.5688643 Serialization

DMS Old code = 89/1 deg, 34/1 min, 7/1 sec
DMS New code = 89/1 deg, 34/1 min, 79114800UL/10000000UL

Deserialization

DMS Old code = 89.5686111111
DMS New code = 89.5688643

The new test tries to serialize a higher precision
coordinate.

The types of the coordinates are also guint32 instead
of gint like previously. guint32 is the type of the
fraction components in the exif.

https://bugzilla.gnome.org/show_bug.cgi?id=767537
2016-06-13 09:29:52 +03:00

2888 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;
guint32 degrees;
guint32 minutes;
guint32 seconds_numerator, seconds_denominator;
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_DEBUG ("Converting %lf degrees geo location to HMS", value);
degrees = (guint32) value;
value -= degrees;
minutes = (guint32) (value * 60);
value = (value * 60) - minutes;
seconds_denominator = 10000000UL;
seconds_numerator = (guint32) (value * 60 * seconds_denominator);
GST_DEBUG ("Converted rational geo location to %u/%u %u/%u %u/%u degrees ",
degrees, 1U, minutes, 1U, seconds_numerator, seconds_denominator);
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_numerator,
seconds_denominator);
}
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, &degrees_n) ||
!gst_byte_reader_get_uint32_le (&fractions_reader, &degrees_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, &degrees_n) ||
!gst_byte_reader_get_uint32_be (&fractions_reader, &degrees_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, &degrees);
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 degrees", 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