[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