[FFmpeg-devel] [RFC] First shot for EXIF metadata...

Thilo Borgmann thilo.borgmann at googlemail.com
Thu Jul 25 18:18:04 CEST 2013


Hi,

this patch is crappy and full of "don't do"-s and is really meant for getting
comments on the following question only:

=> Why is the metadata dictionary lost (from AVFrame->metadata)?

I compared setting it with what lavc/tiff.c does but cannot find the clue...

I test this with "ffprobe -show_frames <file>".
The pointer to the metadata ffprobe gets is NULL (ffprobe.c, in show_frame())
for a jpeg with EXIF, but valid for a TIFF with some tags...

Any help is appreciated, thanks!

-Thilo
-------------- next part --------------
diff --git a/ffprobe.c b/ffprobe.c
index d4adde0..c4268eb 100644
--- a/ffprobe.c
+++ b/ffprobe.c
@@ -1524,6 +1524,8 @@ static void show_frame(WriterContext *w, AVFrame *frame, AVStream *stream,
             print_str_opt("channel_layout", "unknown");
         break;
     }
+print_int("count metadata", av_dict_count(av_frame_get_metadata(frame)));
+print_int("      metadata", (int)(av_frame_get_metadata(frame)));
     show_tags(w, av_frame_get_metadata(frame), SECTION_ID_FRAME_TAGS);
 
     writer_print_section_footer(w);
diff --git a/libavcodec/mjpegdec.c b/libavcodec/mjpegdec.c
index 0e5ae74..670cbb9 100644
--- a/libavcodec/mjpegdec.c
+++ b/libavcodec/mjpegdec.c
@@ -39,6 +39,8 @@
 #include "mjpeg.h"
 #include "mjpegdec.h"
 #include "jpeglsdec.h"
+#include "tiff.h"
+#include "bytestream.h"
 
 
 static int build_vlc(VLC *vlc, const uint8_t *bits_table,
@@ -1359,6 +1361,370 @@ static int mjpeg_decode_dri(MJpegDecodeContext *s)
     return 0;
 }
 
+
+////////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////////
+// exif patch
+////////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////////
+
+#define EXIF_TAG_NAME_LENGTH 32
+#define EXIF_TAGS            117
+
+struct exif_tag {
+    char      name[EXIF_TAG_NAME_LENGTH];
+    uint16_t  id;
+};
+
+struct exif_tag tag_list[EXIF_TAGS] = {              // 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}
+};
+
+
+static const char *get_tag_name(uint16_t id)
+{
+ // 0, 31, 60
+    for (int i = 0; i < EXIF_TAGS; i++) {
+        if (tag_list[i].id == id)
+            return tag_list[i].name;
+    }
+
+    return NULL;
+}
+
+
+static int tiff_read_header(GetByteContext *gbytes, int *le, int *ifd_offset)
+{
+    if (bytestream2_get_bytes_left(gbytes) < 8) {
+        return AVERROR_INVALIDDATA;
+    }
+
+    *le = bytestream2_get_le16u(gbytes);
+    if (*le == AV_RB16("II")) {
+        *le = 1;
+    } else if (*le == AV_RB16("MM")) {
+        *le = 0;
+    } else {
+        return AVERROR_INVALIDDATA;
+    }
+
+    if (tget_short(gbytes, *le) != 42) {
+        return AVERROR_INVALIDDATA;
+    }
+
+    *ifd_offset = tget_long(gbytes, *le);
+
+//av_log(0, AV_LOG_WARNING, "endian: %04X\n", *le);
+//av_log(0, AV_LOG_WARNING, "offset: %08X\n", *ifd_offset);
+
+    return 0;
+}
+
+static int tiff_read_ifd(GetByteContext *gbytes, int le, AVDictionary **metadata);
+
+static int exif_add_metadata(uint16_t id, const char *name, const char *string, AVDictionary **metadata)
+{
+    char *use_name   = (char*) name;
+    char *use_string = (char*) string;
+
+    if (!use_name) {
+        use_name = av_malloc(7);
+        if (!use_name) {
+            return AVERROR(ENOMEM);
+        }
+        snprintf(use_name, 7, "0x%04X", id);
+    }
+
+    if (!use_string) {
+        use_string = av_malloc(14);
+        if (!use_string) {
+            if (use_name) {
+                av_freep(&use_name);
+            }
+            return AVERROR(ENOMEM);
+        }
+        snprintf(use_string, 14, "unknown value");
+    }
+
+    av_dict_set(metadata, use_name, use_string, 0); // TODO: this stores the last element of the short list only
+                                                    //       and the first element of a list behind offset only
+
+    if (!name) {
+        av_freep(&use_name);
+    }
+
+    if (!string) {
+        av_freep(&use_string);
+    }
+
+    return 0;
+}
+
+static int tiff_read_tag(GetByteContext *gbytes, int le, AVDictionary **metadata)
+{
+    int i, id, count, value_offset, cur_pos;
+    enum TiffTypes type;
+    char string[64]   = "";
+    char *long_string = NULL;
+
+    id     = tget_short(gbytes, le);
+    type   = tget_short(gbytes, le);
+    count  = tget_long (gbytes, le);
+//av_log(0, AV_LOG_WARNING, "TAG at  %08X\n", bytestream2_tell(gbytes));
+//av_log(0, AV_LOG_WARNING, "id:         %04X\n", id);
+//av_log(0, AV_LOG_WARNING, "type:       %04X\n", type);
+//av_log(0, AV_LOG_WARNING, "count:  %08X\n",     count);
+
+    // read value in place
+    if (type == TIFF_STRING && count <= 4) {        // read short string
+        for (i = 0; i < count; i++) {
+            string[i] = bytestream2_get_byte(gbytes);
+        }
+        bytestream2_skip(gbytes, 4 - count);
+
+        string[4] = '\0';
+    } else if (type_sizes[type] * count <= 4) {     // read short value
+        int size = type_sizes[type];
+
+        for (i = 0; i < count; i++) {
+            if (type == TIFF_BYTE || size == 1) {
+                value_offset = bytestream2_get_byte(gbytes);
+            } else if (type == TIFF_SHORT || size == 2) {
+                value_offset = tget_short(gbytes, le);
+            } else if (type == TIFF_LONG || size == 4) {
+                value_offset = tget_long(gbytes, le);
+            } else {
+av_log(0, AV_LOG_WARNING, "unhandled short list value type:     %04X\n", type);
+                bytestream2_skip(gbytes, type_sizes[type]);
+            }
+
+            snprintf(string, 64, "%i", value_offset);
+        }
+        bytestream2_skip(gbytes, 4 - (type_sizes[type] * count));
+    } else {                                        // read from offset
+        value_offset = tget_long(gbytes, le);
+        cur_pos      = bytestream2_tell(gbytes);
+
+        bytestream2_seek(gbytes, value_offset, SEEK_SET);
+
+        if (type == TIFF_STRING || type == TIFF_UNDEFINED) {
+            long_string = av_malloc(count);
+            if (!long_string) {
+                return AVERROR(ENOMEM);
+            }
+            for (i = 0; i < count; i++) {
+                long_string[i] = bytestream2_get_byte(gbytes);
+            }
+            long_string[count-1] = '\0'; // manually set string[count-1] = '\0'
+                                         // although EXIF says it has to be in the stream
+                                         // and has to be counted for STRINGs
+                                         // currently UNDEFINEDs are handled like strings
+        } else if (type == TIFF_RATIONAL) {
+            uint32_t a, b;
+            a = tget_long(gbytes, le);
+            b = tget_long(gbytes, le);
+            if (b == 0) {
+                return AVERROR_INVALIDDATA;
+            }
+            snprintf(string, 64, "%d:%d", a, b);
+        } else if (type == TIFF_SRATIONAL) {
+            int32_t a, b;
+            a = tget_long(gbytes, le);
+            b = tget_long(gbytes, le);
+            if (b == 0) {
+                return AVERROR_INVALIDDATA;
+            }
+            snprintf(string, 64, "%i:%i", a, b);
+//        } else if (type == TIFF_DOUBLE) {
+//        } else if (type == TIFF_FLOAT) {
+        } else {
+av_log(0, AV_LOG_WARNING, "unhandled list value type:     %04X\n", type);
+        }
+
+        bytestream2_seek(gbytes, cur_pos, SEEK_SET);
+    }
+
+
+    if (id == 0x8769 || // EXIF IFD
+        id == 0x8825 || // GPS IFD
+        id == 0xA005) { // Interoperability IFD
+        cur_pos = bytestream2_tell(gbytes);
+        bytestream2_seek(gbytes, value_offset, SEEK_SET);
+        tiff_read_ifd(gbytes, le, metadata);
+        bytestream2_seek(gbytes, cur_pos, SEEK_SET);
+    } else {
+        const char *name = get_tag_name(id);
+        int err;
+
+        err = exif_add_metadata(id, name, long_string ? long_string : string, metadata);
+
+        av_freep(&long_string);
+
+        if (err) {
+            return err;
+        }
+
+//        if (name) {
+//av_log(0, AV_LOG_WARNING, "% 32s: %08X\n", name, value_offset);
+//        } else {
+//av_log(0, AV_LOG_WARNING, "% 25s (% 4X): %08X\n", "Unknwon tag", id, value_offset);
+//        }
+    }
+
+
+
+    return 0;
+}
+
+static int tiff_read_ifd(GetByteContext *gbytes, int le, AVDictionary **metadata)
+{
+    int i, ret;
+    int entries;
+
+//av_log(0, AV_LOG_WARNING, "mjpeg: reading IFD at %08X\n", bytestream2_tell(gbytes));
+
+    entries = tget_short(gbytes, le);
+
+    if (bytestream2_get_bytes_left(gbytes) < entries * 12) {
+        return AVERROR_INVALIDDATA;
+    }
+
+    for (i = 0; i < entries; i++) {
+        if ((ret = tiff_read_tag(gbytes, le, metadata)) < 0) {
+            return ret;
+        }
+    }
+
+    // next IDF offset or 0x000000000
+    ret = tget_long(gbytes, le);
+
+//av_log(0, AV_LOG_WARNING, "mjpeg: next IFD at %08X\n", ret);
+    return ret;
+}
+////////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////////
+// exif patch
+////////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////////
+
 static int mjpeg_decode_app(MJpegDecodeContext *s)
 {
     int len, id, i;
@@ -1477,6 +1843,48 @@ static int mjpeg_decode_app(MJpegDecodeContext *s)
         goto out;
     }
 
+    /* EXIF metadata */
+    if (s->start_code == APP1 && id == AV_RB32("Exif")) {
+        GetByteContext gbytes;
+        int ret, le, ifd_offset, bytes_read;
+        AVDictionary **metadata = avpriv_frame_get_metadatap(s->picture_ptr);
+
+        skip_bits(&s->gb, 16); // skip padding
+
+        // init byte wise reading
+        bytestream2_init(&gbytes, align_get_bits(&s->gb), get_bits_left(&s->gb) >> 3);
+
+        // read TIFF header (share code with tiff.c? (bytestream2 API)
+        ret = tiff_read_header(&gbytes, &le, &ifd_offset);
+        if (ret) {
+            av_log(s->avctx, AV_LOG_ERROR, "mjpeg: invalid TIFF header in Exif data\n");
+            return ret;
+        }
+
+        bytestream2_seek(&gbytes, ifd_offset, SEEK_SET);
+
+        // read IFD body's
+        while ((ifd_offset = tiff_read_ifd(&gbytes, le, metadata))) {
+            bytestream2_seek(&gbytes, ifd_offset, SEEK_SET);
+        }
+
+        bytes_read = bytestream2_tell(&gbytes);
+        skip_bits(&s->gb, bytes_read << 3);
+        len -= bytes_read;
+
+
+        if (av_dict_count(*metadata) > 0) {
+            AVDictionaryEntry *t = NULL;
+            av_log(s->avctx, AV_LOG_ERROR, "mjpeg: strored %d Exif data entries at (%8X : %8X):\n", av_dict_count(*metadata), metadata, *metadata);
+            while (t = av_dict_get(*metadata, "", t, AV_DICT_IGNORE_SUFFIX)) {
+                av_log(s->avctx, AV_LOG_ERROR, "mjpeg:\t%32s: %s\n", t->key, t->value);
+
+            }
+        }
+
+        goto out;
+    }
+
     /* Apple MJPEG-A */
     if ((s->start_code == APP1) && (len > (0x28 - 8))) {
         id   = get_bits_long(&s->gb, 32);
diff --git a/libavcodec/tiff.c b/libavcodec/tiff.c
index fdfa8f2..8c79667 100644
--- a/libavcodec/tiff.c
+++ b/libavcodec/tiff.c
@@ -70,13 +70,13 @@ typedef struct TiffContext {
     TiffGeoTag *geotags;
 } TiffContext;
 
-static unsigned tget_short(GetByteContext *gb, int le)
+unsigned tget_short(GetByteContext *gb, int le)
 {
     unsigned v = le ? bytestream2_get_le16(gb) : bytestream2_get_be16(gb);
     return v;
 }
 
-static unsigned tget_long(GetByteContext *gb, int le)
+unsigned tget_long(GetByteContext *gb, int le)
 {
     unsigned v = le ? bytestream2_get_le32(gb) : bytestream2_get_be32(gb);
     return v;
diff --git a/libavcodec/tiff.h b/libavcodec/tiff.h
index 3ea2158..8c58c96 100644
--- a/libavcodec/tiff.h
+++ b/libavcodec/tiff.h
@@ -31,6 +31,7 @@
 #define AVCODEC_TIFF_H
 
 #include <stdint.h>
+#include "bytestream.h"
 
 /** abridged list of TIFF tags */
 enum TiffTags {
@@ -190,4 +191,7 @@ typedef struct TiffGeoTagNameType {
     const enum TiffGeoTagType type;
 } TiffGeoTagNameType;
 
+unsigned tget_short(GetByteContext *gb, int le);
+unsigned tget_long(GetByteContext *gb, int le);
+
 #endif /* AVCODEC_TIFF_H */


More information about the ffmpeg-devel mailing list