[FFmpeg-devel] [Read EXIF metadata 2/3] Add EXIF metadata parser to libavcodec.
Thilo Borgmann
thilo.borgmann at googlemail.com
Fri Aug 9 16:41:32 CEST 2013
2/3 rev 3
-Thilo
-------------- next part --------------
>From 1b2045a2a0d5754fa02dc5e7729ad7bc19998670 Mon Sep 17 00:00:00 2001
From: Thilo Borgmann <thilo.borgmann at googlemail.com>
Date: Fri, 9 Aug 2013 16:33:43 +0200
Subject: [PATCH 2/3] Add EXIF metadata parser to libavcodec.
---
libavcodec/exif.c | 228 +++++++++++++++++++++++++++++++++++++++++++++++++++++
libavcodec/exif.h | 168 +++++++++++++++++++++++++++++++++++++++
2 files changed, 396 insertions(+), 0 deletions(-)
create mode 100644 libavcodec/exif.c
create mode 100644 libavcodec/exif.h
diff --git a/libavcodec/exif.c b/libavcodec/exif.c
new file mode 100644
index 0000000..776c094
--- /dev/null
+++ b/libavcodec/exif.c
@@ -0,0 +1,228 @@
+/*
+ * EXIF metadata parser
+ * Copyright (c) 2013 Thilo Borgmann <thilo.borgmann _at_ googlemail.com>
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * @file
+ * EXIF metadata parser
+ * @author Thilo Borgmann <thilo.borgmann _at_ googlemail.com>
+ */
+
+#include "exif.h"
+
+
+static char *rationals2str(int *rp, int count, const char *sep)
+{
+ int i;
+ char *ap, *ap0;
+ uint64_t component_len;
+ if (!sep) sep = ", ";
+ component_len = 20LL+1LL+20LL + strlen(sep);
+ ap = av_malloc_array(count + 1, component_len);
+ ap0 = ap;
+ ap[0] = '\0';
+ for (i = 0; i < count; i++) {
+ unsigned l = snprintf(ap, component_len, "%i:%i%s", rp[2*i + 0], rp[2*i + 1], sep);
+ if (l >= component_len) {
+ av_free(ap0);
+ return NULL;
+ }
+ ap += l;
+ }
+ ap0[strlen(ap0) - strlen(sep)] = '\0';
+ return ap0;
+}
+
+
+static char *longs2str(int32_t *lp, int count, const char *sep)
+{
+ int i;
+ char *ap, *ap0;
+ uint64_t component_len;
+ if (!sep) sep = ", ";
+ component_len = 11LL + strlen(sep);
+ ap = av_malloc_array(count + 1, component_len);
+ ap0 = ap;
+ ap[0] = '\0';
+ for (i = 0; i < count; i++) {
+ unsigned l = snprintf(ap, component_len, "%i%s", lp[i], sep);
+ if (l >= component_len) {
+ av_free(ap0);
+ return NULL;
+ }
+ ap += l;
+ }
+ ap0[strlen(ap0) - strlen(sep)] = '\0';
+ return ap0;
+}
+
+
+static int exif_add_rational_metadata(int count, const char *name, const char *sep,
+ GetByteContext *gb, int le, AVDictionary **metadata)
+{
+ char *ap;
+ int i;
+ int32_t *rp;
+
+ if (count >= INT_MAX / sizeof(int64_t) || count <= 0)
+ return AVERROR_INVALIDDATA;
+ if (bytestream2_get_bytes_left(gb) < count * sizeof(int64_t))
+ return AVERROR_INVALIDDATA;
+
+ rp = av_malloc(2 * count * sizeof(int32_t));
+ if (!rp)
+ return AVERROR(ENOMEM);
+
+ for (i = 0; i < count; i++) {
+ rp[2*i + 0] = ff_tget_long(gb, le);
+ rp[2*i + 1] = ff_tget_long(gb, le);
+ }
+ ap = rationals2str(rp, count, sep);
+ av_freep(&rp);
+ if (!ap)
+ return AVERROR(ENOMEM);
+ av_dict_set(metadata, name, ap, AV_DICT_DONT_STRDUP_VAL);
+ return 0;
+}
+
+
+static int exif_add_long_metadata(int count, const char *name, const char *sep,
+ GetByteContext *gb, int le, AVDictionary **metadata)
+{
+ char *ap;
+ int i;
+ int32_t *lp;
+
+ if (count >= INT_MAX / sizeof(int32_t) || count <= 0)
+ return AVERROR_INVALIDDATA;
+ if (bytestream2_get_bytes_left(gb) < count * sizeof(int32_t))
+ return AVERROR_INVALIDDATA;
+
+ lp = av_malloc(count * sizeof(int32_t));
+ if (!lp)
+ return AVERROR(ENOMEM);
+
+ for (i = 0; i < count; i++) {
+ lp[i] = ff_tget_long(gb, le);
+ }
+ ap = longs2str(lp, count, sep);
+ av_freep(&lp);
+ if (!ap)
+ return AVERROR(ENOMEM);
+ av_dict_set(metadata, name, ap, AV_DICT_DONT_STRDUP_VAL);
+ return 0;
+}
+
+
+
+static const char *exif_get_tag_name(uint16_t id)
+{
+ int i;
+
+ for (i = 0; i < FF_ARRAY_ELEMS(tag_list); i++) {
+ if (tag_list[i].id == id)
+ return tag_list[i].name;
+ }
+
+ return NULL;
+}
+
+
+static int exif_add_metadata(int count, int type, const char *name, const char *sep,
+ GetByteContext *gb, int le, AVDictionary **metadata)
+{
+ switch(type) {
+ case TIFF_DOUBLE : return ff_tadd_doubles_metadata(count, name, sep, gb, le, metadata);
+ case TIFF_SHORT : return ff_tadd_shorts_metadata(count, name, sep, gb, le, metadata);
+ case TIFF_BYTE:
+ case TIFF_UNDEFINED:
+ case TIFF_STRING : return ff_tadd_string_metadata(count, name, gb, le, metadata);
+ case TIFF_SRATIONAL:
+ case TIFF_RATIONAL : return exif_add_rational_metadata(count, name, sep, gb, le, metadata);
+ case TIFF_SLONG :
+ case TIFF_LONG : return exif_add_long_metadata(count, name, sep, gb, le, metadata);
+ default : return AVERROR_INVALIDDATA;
+ };
+}
+
+
+static int exif_decode_tag(AVCodecContext *avctx, GetByteContext *gbytes, int le, AVDictionary **metadata)
+{
+ int ret, cur_pos;
+ unsigned id, count;
+ enum TiffTypes type;
+
+ ret = ff_tread_tag(avctx, gbytes, le, &id, &type, &count, &cur_pos);
+
+ if (ret < 0) {
+ return ret;
+ }
+
+ // read count values and add it metadata
+ // store metadata or proceed with next IFD
+ if (ff_tis_ifd(id)) {
+ ret = ff_exif_decode_ifd(avctx, gbytes, le, metadata);
+ } else {
+ const char *name = exif_get_tag_name(id);
+ char *use_name = (char*) name;
+
+ if (!use_name) {
+ use_name = av_malloc(7);
+ if (!use_name) {
+ return AVERROR(ENOMEM);
+ }
+ snprintf(use_name, 7, "0x%04X", id);
+ }
+
+ if (exif_add_metadata(count, type, use_name, NULL, gbytes, le, metadata)) {
+ avpriv_report_missing_feature(avctx, "Value type \"%d\" for EXIF tag %s (0x%X)", type, use_name, id);
+ }
+
+ if (!name) {
+ av_freep(&use_name);
+ }
+ }
+
+ bytestream2_seek(gbytes, cur_pos, SEEK_SET);
+
+ return ret;
+}
+
+
+int ff_exif_decode_ifd(AVCodecContext *avctx, GetByteContext *gbytes, int le, AVDictionary **metadata)
+{
+ int i, ret;
+ int entries;
+
+ entries = ff_tget_short(gbytes, le);
+
+ if (bytestream2_get_bytes_left(gbytes) < entries * 12) {
+ return AVERROR_INVALIDDATA;
+ }
+
+ for (i = 0; i < entries; i++) {
+ if ((ret = exif_decode_tag(avctx, gbytes, le, metadata)) < 0) {
+ return ret;
+ }
+ }
+
+ // return next IDF offset or 0x000000000 or a value < 0 for failure
+ return ff_tget_long(gbytes, le);
+}
diff --git a/libavcodec/exif.h b/libavcodec/exif.h
new file mode 100644
index 0000000..e4f37b5
--- /dev/null
+++ b/libavcodec/exif.h
@@ -0,0 +1,168 @@
+/*
+ * EXIF metadata parser
+ * Copyright (c) 2013 Thilo Borgmann <thilo.borgmann _at_ googlemail.com>
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * @file
+ * EXIF metadata parser
+ * @author Thilo Borgmann <thilo.borgmann _at_ googlemail.com>
+ */
+
+#ifndef AVCODEC_EXIF_H
+#define AVCODEC_EXIF_H
+
+#include "avcodec.h"
+#include "bytestream.h"
+#include "tiff.h"
+
+#define EXIF_TAG_NAME_LENGTH 32
+
+struct exif_tag {
+ char name[EXIF_TAG_NAME_LENGTH];
+ uint16_t id;
+};
+
+static const struct exif_tag tag_list[] = { // JEITA CP-3451 EXIF specification:
+ {"GPSVersionID", 0x00}, // <- Table 12 GPS Attribute Information
+ {"GPSLatitudeRef", 0x01},
+ {"GPSLatitude", 0x02},
+ {"GPSLongitudeRef", 0x03},
+ {"GPSLongitude", 0x04},
+ {"GPSAltitudeRef", 0x05},
+ {"GPSAltitude", 0x06},
+ {"GPSTimeStamp", 0x07},
+ {"GPSSatellites", 0x08},
+ {"GPSStatus", 0x09},
+ {"GPSMeasureMode", 0x0A},
+ {"GPSDOP", 0x0B},
+ {"GPSSpeedRef", 0x0C},
+ {"GPSSpeed", 0x0D},
+ {"GPSTrackRef", 0x0E},
+ {"GPSTrack", 0x0F},
+ {"GPSImgDirectionRef", 0x10},
+ {"GPSImgDirection", 0x11},
+ {"GPSMapDatum", 0x12},
+ {"GPSDestLatitudeRef", 0x13},
+ {"GPSDestLatitude", 0x14},
+ {"GPSDestLongitudeRef", 0x15},
+ {"GPSDestLongitude", 0x16},
+ {"GPSDestBearingRef", 0x17},
+ {"GPSDestBearing", 0x18},
+ {"GPSDestDistanceRef", 0x19},
+ {"GPSDestDistance", 0x1A},
+ {"GPSProcessingMethod", 0x1B},
+ {"GPSAreaInformation", 0x1C},
+ {"GPSDateStamp", 0x1D},
+ {"GPSDifferential", 0x1E},
+ {"ImageWidth", 0x100}, // <- Table 3 TIFF Rev. 6.0 Attribute Information Used in Exif
+ {"ImageLength", 0x101},
+ {"BitsPerSample", 0x102},
+ {"Compression", 0x103},
+ {"PhotometricInterpretation", 0x106},
+ {"Orientation", 0x112},
+ {"SamplesPerPixel", 0x115},
+ {"PlanarConfiguration", 0x11C},
+ {"YCbCrSubSampling", 0x212},
+ {"YCbCrPositioning", 0x213},
+ {"XResolution", 0x11A},
+ {"YResolution", 0x11B},
+ {"ResolutionUnit", 0x128},
+ {"StripOffsets", 0x111},
+ {"RowsPerStrip", 0x116},
+ {"StripByteCounts", 0x117},
+ {"JPEGInterchangeFormat", 0x201},
+ {"JPEGInterchangeFormatLength",0x202},
+ {"TransferFunction", 0x12D},
+ {"WhitePoint", 0x13E},
+ {"PrimaryChromaticities", 0x13F},
+ {"YCbCrCoefficients", 0x211},
+ {"ReferenceBlackWhite", 0x214},
+ {"DateTime", 0x132},
+ {"ImageDescription", 0x10E},
+ {"Make", 0x10F},
+ {"Model", 0x110},
+ {"Software", 0x131},
+ {"Artist", 0x13B},
+ {"Copyright", 0x8298},
+ {"ExifVersion", 0x9000}, // <- Table 4 Exif IFD Attribute Information (1)
+ {"FlashpixVersion", 0xA000},
+ {"ColorSpace", 0xA001},
+ {"ComponentsConfiguration", 0x9101},
+ {"CompressedBitsPerPixel", 0x9102},
+ {"PixelXDimension", 0xA002},
+ {"PixelYDimension", 0xA003},
+ {"MakerNote", 0x927C},
+ {"UserComment", 0x9286},
+ {"RelatedSoundFile", 0xA004},
+ {"DateTimeOriginal", 0x9003},
+ {"DateTimeDigitized", 0x9004},
+ {"SubSecTime", 0x9290},
+ {"SubSecTimeOriginal", 0x9291},
+ {"SubSecTimeDigitized", 0x9292},
+ {"ImageUniqueID", 0xA420},
+ {"ExposureTime", 0x829A}, // <- Table 5 Exif IFD Attribute Information (2)
+ {"FNumber", 0x829D},
+ {"ExposureProgram", 0x8822},
+ {"SpectralSensitivity", 0x8824},
+ {"ISOSpeedRatings", 0x8827},
+ {"OECF", 0x8828},
+ {"ShutterSpeedValue", 0x9201},
+ {"ApertureValue", 0x9202},
+ {"BrightnessValue", 0x9203},
+ {"ExposureBiasValue", 0x9204},
+ {"MaxApertureValue", 0x9205},
+ {"SubjectDistance", 0x9206},
+ {"MeteringMode", 0x9207},
+ {"LightSource", 0x9208},
+ {"Flash", 0x9209},
+ {"FocalLength", 0x920A},
+ {"SubjectArea", 0x9214},
+ {"FlashEnergy", 0xA20B},
+ {"SpatialFrequencyResponse", 0xA20C},
+ {"FocalPlaneXResolution", 0xA20E},
+ {"FocalPlaneYResolution", 0xA20F},
+ {"FocalPlaneResolutionUnit", 0xA210},
+ {"SubjectLocation", 0xA214},
+ {"ExposureIndex", 0xA215},
+ {"SensingMethod", 0xA217},
+ {"FileSource", 0xA300},
+ {"SceneType", 0xA301},
+ {"CFAPattern", 0xA302},
+ {"CustomRendered", 0xA401},
+ {"ExposureMode", 0xA402},
+ {"WhiteBalance", 0xA403},
+ {"DigitalZoomRatio", 0xA404},
+ {"FocalLengthIn35mmFilm", 0xA405},
+ {"SceneCaptureType", 0xA406},
+ {"GainControl", 0xA407},
+ {"Contrast", 0xA408},
+ {"Saturation", 0xA409},
+ {"Sharpness", 0xA40A},
+ {"DeviceSettingDescription", 0xA40B},
+ {"SubjectDistanceRange", 0xA40C}
+// {"InteroperabilityIndex", 0x1}, // <- Table 13 Interoperability IFD Attribute Information
+// {"", 0x0}
+};
+
+/** Recursively decodes all IFD's and
+ * adds included TAGS into the metadata dictionary. */
+int ff_exif_decode_ifd(AVCodecContext *avctx, GetByteContext *gbytes, int le, AVDictionary **metadata);
+
+#endif /* AVCODEC_EXIF_H */
--
1.7.4.3
More information about the ffmpeg-devel
mailing list