1 /*MT*
2 
3     MediaTomb - http://www.mediatomb.cc/
4 
5     libexif_handler.cc - this file is part of MediaTomb.
6 
7     Copyright (C) 2005 Gena Batyan <bgeradz@mediatomb.cc>,
8                        Sergey 'Jin' Bostandzhyan <jin@mediatomb.cc>
9 
10     Copyright (C) 2006-2010 Gena Batyan <bgeradz@mediatomb.cc>,
11                             Sergey 'Jin' Bostandzhyan <jin@mediatomb.cc>,
12                             Leonhard Wimmer <leo@mediatomb.cc>
13 
14     MediaTomb is free software; you can redistribute it and/or modify
15     it under the terms of the GNU General Public License version 2
16     as published by the Free Software Foundation.
17 
18     MediaTomb is distributed in the hope that it will be useful,
19     but WITHOUT ANY WARRANTY; without even the implied warranty of
20     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21     GNU General Public License for more details.
22 
23     You should have received a copy of the GNU General Public License
24     version 2 along with MediaTomb; if not, write to the Free Software
25     Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
26 
27     $Id$
28 */
29 
30 /// \file libexif_handler.cc
31 
32 #ifdef HAVE_LIBEXIF
33 #include "libexif_handler.h" // API
34 
35 #include <iohandler/file_io_handler.h>
36 
37 #include "cds_objects.h"
38 #include "config/config_manager.h"
39 #include "iohandler/mem_io_handler.h"
40 #include "util/string_converter.h"
41 #include "util/tools.h"
42 
43 /// \brief Sets resolution for a given resource index, item must be a JPEG image
setJpegResolutionResource(const std::shared_ptr<CdsItem> & item,std::size_t res_num)44 static void setJpegResolutionResource(const std::shared_ptr<CdsItem>& item, std::size_t res_num)
45 {
46     try {
47         auto fio_h = std::make_unique<FileIOHandler>(item->getLocation());
48         fio_h->open(UPNP_READ);
49         const std::string resolution = get_jpeg_resolution(std::move(fio_h));
50 
51         if (res_num >= item->getResourceCount())
52             throw_std_runtime_error("Invalid resource index");
53 
54         item->getResource(res_num)->addAttribute(R_RESOLUTION, resolution);
55     } catch (const std::runtime_error& e) {
56         log_error("Exception! {}", e.what());
57     }
58 }
59 
60 static const std::map<std::string, int> exifTagMap {
61     { "EXIF_TAG_INTEROPERABILITY_INDEX", EXIF_TAG_INTEROPERABILITY_INDEX },
62     { "EXIF_TAG_INTEROPERABILITY_VERSION", EXIF_TAG_INTEROPERABILITY_VERSION },
63     { "EXIF_TAG_IMAGE_WIDTH", EXIF_TAG_IMAGE_WIDTH },
64     { "EXIF_TAG_IMAGE_LENGTH", EXIF_TAG_IMAGE_LENGTH },
65     { "EXIF_TAG_BITS_PER_SAMPLE", EXIF_TAG_BITS_PER_SAMPLE },
66     { "EXIF_TAG_COMPRESSION", EXIF_TAG_COMPRESSION },
67     { "EXIF_TAG_PHOTOMETRIC_INTERPRETATION", EXIF_TAG_PHOTOMETRIC_INTERPRETATION },
68     { "EXIF_TAG_FILL_ORDER", EXIF_TAG_FILL_ORDER },
69     { "EXIF_TAG_DOCUMENT_NAME", EXIF_TAG_DOCUMENT_NAME },
70     { "EXIF_TAG_IMAGE_DESCRIPTION", EXIF_TAG_IMAGE_DESCRIPTION },
71     { "EXIF_TAG_MAKE", EXIF_TAG_MAKE },
72     { "EXIF_TAG_MODEL", EXIF_TAG_MODEL },
73     { "EXIF_TAG_STRIP_OFFSETS", EXIF_TAG_STRIP_OFFSETS },
74     { "EXIF_TAG_ORIENTATION", EXIF_TAG_ORIENTATION },
75     { "EXIF_TAG_SAMPLES_PER_PIXEL", EXIF_TAG_SAMPLES_PER_PIXEL },
76     { "EXIF_TAG_ROWS_PER_STRIP", EXIF_TAG_ROWS_PER_STRIP },
77     { "EXIF_TAG_STRIP_BYTE_COUNTS", EXIF_TAG_STRIP_BYTE_COUNTS },
78     { "EXIF_TAG_X_RESOLUTION", EXIF_TAG_X_RESOLUTION },
79     { "EXIF_TAG_Y_RESOLUTION", EXIF_TAG_Y_RESOLUTION },
80     { "EXIF_TAG_PLANAR_CONFIGURATION", EXIF_TAG_PLANAR_CONFIGURATION },
81     { "EXIF_TAG_RESOLUTION_UNIT", EXIF_TAG_RESOLUTION_UNIT },
82     { "EXIF_TAG_TRANSFER_FUNCTION", EXIF_TAG_TRANSFER_FUNCTION },
83     { "EXIF_TAG_SOFTWARE", EXIF_TAG_SOFTWARE },
84     { "EXIF_TAG_DATE_TIME", EXIF_TAG_DATE_TIME },
85     { "EXIF_TAG_ARTIST", EXIF_TAG_ARTIST },
86     { "EXIF_TAG_WHITE_POINT", EXIF_TAG_WHITE_POINT },
87     { "EXIF_TAG_PRIMARY_CHROMATICITIES", EXIF_TAG_PRIMARY_CHROMATICITIES },
88     { "EXIF_TAG_TRANSFER_RANGE", EXIF_TAG_TRANSFER_RANGE },
89     { "EXIF_TAG_JPEG_PROC", EXIF_TAG_JPEG_PROC },
90     { "EXIF_TAG_JPEG_INTERCHANGE_FORMAT", EXIF_TAG_JPEG_INTERCHANGE_FORMAT },
91     { "EXIF_TAG_JPEG_INTERCHANGE_FORMAT_LENGTH", EXIF_TAG_JPEG_INTERCHANGE_FORMAT_LENGTH },
92     { "EXIF_TAG_YCBCR_COEFFICIENTS", EXIF_TAG_YCBCR_COEFFICIENTS },
93     { "EXIF_TAG_YCBCR_SUB_SAMPLING", EXIF_TAG_YCBCR_SUB_SAMPLING },
94     { "EXIF_TAG_YCBCR_POSITIONING", EXIF_TAG_YCBCR_POSITIONING },
95     { "EXIF_TAG_REFERENCE_BLACK_WHITE", EXIF_TAG_REFERENCE_BLACK_WHITE },
96     { "EXIF_TAG_RELATED_IMAGE_FILE_FORMAT", EXIF_TAG_RELATED_IMAGE_FILE_FORMAT },
97     { "EXIF_TAG_RELATED_IMAGE_WIDTH", EXIF_TAG_RELATED_IMAGE_WIDTH },
98     { "EXIF_TAG_RELATED_IMAGE_LENGTH", EXIF_TAG_RELATED_IMAGE_LENGTH },
99     { "EXIF_TAG_CFA_REPEAT_PATTERN_DIM", EXIF_TAG_CFA_REPEAT_PATTERN_DIM },
100     { "EXIF_TAG_CFA_PATTERN", EXIF_TAG_CFA_PATTERN },
101     { "EXIF_TAG_BATTERY_LEVEL", EXIF_TAG_BATTERY_LEVEL },
102     { "EXIF_TAG_COPYRIGHT", EXIF_TAG_COPYRIGHT },
103     { "EXIF_TAG_EXPOSURE_TIME", EXIF_TAG_EXPOSURE_TIME },
104     { "EXIF_TAG_FNUMBER", EXIF_TAG_FNUMBER },
105     { "EXIF_TAG_IPTC_NAA", EXIF_TAG_IPTC_NAA },
106     { "EXIF_TAG_EXIF_IFD_POINTER", EXIF_TAG_EXIF_IFD_POINTER },
107     { "EXIF_TAG_INTER_COLOR_PROFILE", EXIF_TAG_INTER_COLOR_PROFILE },
108     { "EXIF_TAG_EXPOSURE_PROGRAM", EXIF_TAG_EXPOSURE_PROGRAM },
109     { "EXIF_TAG_SPECTRAL_SENSITIVITY", EXIF_TAG_SPECTRAL_SENSITIVITY },
110     { "EXIF_TAG_GPS_INFO_IFD_POINTER", EXIF_TAG_GPS_INFO_IFD_POINTER },
111     { "EXIF_TAG_ISO_SPEED_RATINGS", EXIF_TAG_ISO_SPEED_RATINGS },
112     { "EXIF_TAG_OECF", EXIF_TAG_OECF },
113     { "EXIF_TAG_EXIF_VERSION", EXIF_TAG_EXIF_VERSION },
114     { "EXIF_TAG_DATE_TIME_ORIGINAL", EXIF_TAG_DATE_TIME_ORIGINAL },
115     { "EXIF_TAG_DATE_TIME_DIGITIZED", EXIF_TAG_DATE_TIME_DIGITIZED },
116     { "EXIF_TAG_COMPONENTS_CONFIGURATION", EXIF_TAG_COMPONENTS_CONFIGURATION },
117     { "EXIF_TAG_COMPRESSED_BITS_PER_PIXEL", EXIF_TAG_COMPRESSED_BITS_PER_PIXEL },
118     { "EXIF_TAG_SHUTTER_SPEED_VALUE", EXIF_TAG_SHUTTER_SPEED_VALUE },
119     { "EXIF_TAG_APERTURE_VALUE", EXIF_TAG_APERTURE_VALUE },
120     { "EXIF_TAG_BRIGHTNESS_VALUE", EXIF_TAG_BRIGHTNESS_VALUE },
121     { "EXIF_TAG_EXPOSURE_BIAS_VALUE", EXIF_TAG_EXPOSURE_BIAS_VALUE },
122     { "EXIF_TAG_MAX_APERTURE_VALUE", EXIF_TAG_MAX_APERTURE_VALUE },
123     { "EXIF_TAG_SUBJECT_DISTANCE", EXIF_TAG_SUBJECT_DISTANCE },
124     { "EXIF_TAG_METERING_MODE", EXIF_TAG_METERING_MODE },
125     { "EXIF_TAG_LIGHT_SOURCE", EXIF_TAG_LIGHT_SOURCE },
126     { "EXIF_TAG_FLASH", EXIF_TAG_FLASH },
127     { "EXIF_TAG_FOCAL_LENGTH", EXIF_TAG_FOCAL_LENGTH },
128     { "EXIF_TAG_SUBJECT_AREA", EXIF_TAG_SUBJECT_AREA },
129     { "EXIF_TAG_MAKER_NOTE", EXIF_TAG_MAKER_NOTE },
130     { "EXIF_TAG_USER_COMMENT", EXIF_TAG_USER_COMMENT },
131     /* { "EXIF_TAG_SUBSEC_TIME", EXIF_TAG_SUBSEC_TIME }, */
132     { "EXIF_TAG_SUB_SEC_TIME_ORIGINAL", EXIF_TAG_SUB_SEC_TIME_ORIGINAL },
133     { "EXIF_TAG_SUB_SEC_TIME_DIGITIZED", EXIF_TAG_SUB_SEC_TIME_DIGITIZED },
134     { "EXIF_TAG_FLASH_PIX_VERSION", EXIF_TAG_FLASH_PIX_VERSION },
135     { "EXIF_TAG_COLOR_SPACE", EXIF_TAG_COLOR_SPACE },
136     { "EXIF_TAG_PIXEL_X_DIMENSION", EXIF_TAG_PIXEL_X_DIMENSION },
137     { "EXIF_TAG_PIXEL_Y_DIMENSION", EXIF_TAG_PIXEL_Y_DIMENSION },
138     { "EXIF_TAG_RELATED_SOUND_FILE", EXIF_TAG_RELATED_SOUND_FILE },
139     { "EXIF_TAG_INTEROPERABILITY_IFD_POINTER", EXIF_TAG_INTEROPERABILITY_IFD_POINTER },
140     { "EXIF_TAG_FLASH_ENERGY", EXIF_TAG_FLASH_ENERGY },
141     { "EXIF_TAG_SPATIAL_FREQUENCY_RESPONSE", EXIF_TAG_SPATIAL_FREQUENCY_RESPONSE },
142     { "EXIF_TAG_FOCAL_PLANE_X_RESOLUTION", EXIF_TAG_FOCAL_PLANE_X_RESOLUTION },
143     { "EXIF_TAG_FOCAL_PLANE_Y_RESOLUTION", EXIF_TAG_FOCAL_PLANE_Y_RESOLUTION },
144     { "EXIF_TAG_FOCAL_PLANE_RESOLUTION_UNIT", EXIF_TAG_FOCAL_PLANE_RESOLUTION_UNIT },
145     { "EXIF_TAG_SUBJECT_LOCATION", EXIF_TAG_SUBJECT_LOCATION },
146     { "EXIF_TAG_EXPOSURE_INDEX", EXIF_TAG_EXPOSURE_INDEX },
147     { "EXIF_TAG_SENSING_METHOD", EXIF_TAG_SENSING_METHOD },
148     { "EXIF_TAG_FILE_SOURCE", EXIF_TAG_FILE_SOURCE },
149     { "EXIF_TAG_SCENE_TYPE", EXIF_TAG_SCENE_TYPE },
150     { "EXIF_TAG_NEW_CFA_PATTERN", EXIF_TAG_NEW_CFA_PATTERN },
151     { "EXIF_TAG_CUSTOM_RENDERED", EXIF_TAG_CUSTOM_RENDERED },
152     { "EXIF_TAG_EXPOSURE_MODE", EXIF_TAG_CUSTOM_RENDERED },
153     { "EXIF_TAG_WHITE_BALANCE", EXIF_TAG_CUSTOM_RENDERED },
154     { "EXIF_TAG_DIGITAL_ZOOM_RATIO", EXIF_TAG_CUSTOM_RENDERED },
155     { "EXIF_TAG_FOCAL_LENGTH_IN_35MM_FILM", EXIF_TAG_FOCAL_LENGTH_IN_35MM_FILM },
156     { "EXIF_TAG_SCENE_CAPTURE_TYPE", EXIF_TAG_SCENE_CAPTURE_TYPE },
157     { "EXIF_TAG_GAIN_CONTROL", EXIF_TAG_GAIN_CONTROL },
158     { "EXIF_TAG_CONTRAST", EXIF_TAG_CONTRAST },
159     { "EXIF_TAG_SATURATION", EXIF_TAG_SATURATION },
160     { "EXIF_TAG_SHARPNESS", EXIF_TAG_SHARPNESS },
161     { "EXIF_TAG_DEVICE_SETTING_DESCRIPTION", EXIF_TAG_DEVICE_SETTING_DESCRIPTION },
162     { "EXIF_TAG_SUBJECT_DISTANCE_RANGE", EXIF_TAG_DEVICE_SETTING_DESCRIPTION },
163     { "EXIF_TAG_IMAGE_UNIQUE_ID", EXIF_TAG_IMAGE_UNIQUE_ID },
164 };
165 
getTagFromString(const std::string & tag)166 static int getTagFromString(const std::string& tag)
167 {
168     auto result = getValueOrDefault(exifTagMap, tag, -1);
169 
170     if (result == -1)
171         log_warning("Ignoring unknown libexif tag: {}", tag.c_str());
172     return result;
173 }
174 
process_ifd(ExifContent * content,const std::shared_ptr<CdsItem> & item,const std::unique_ptr<StringConverter> & sc,const std::vector<std::string> & auxtags,const std::map<std::string,std::string> & metatags)175 void LibExifHandler::process_ifd(ExifContent* content, const std::shared_ptr<CdsItem>& item,
176     const std::unique_ptr<StringConverter>& sc, const std::vector<std::string>& auxtags, const std::map<std::string, std::string>& metatags)
177 {
178     constexpr auto BUFLEN = 4096;
179     std::array<char, BUFLEN> exif_entry_buffer;
180 #define exif_egv(arg) exif_entry_get_value(arg, exif_entry_buffer.data(), BUFLEN)
181 
182     for (std::size_t i = 0; i < content->count; i++) {
183         ExifEntry* e = content->entries[i];
184 
185         // log_debug("Processing entry: {}", i);
186 
187         switch (e->tag) {
188         case EXIF_TAG_DATE_TIME_ORIGINAL: {
189             auto value = trimString(exif_egv(e));
190             if (!value.empty()) {
191                 value = sc->convert(value);
192                 // convert date to ISO 8601 as required in the UPnP spec
193                 // from YYYY:MM:DD to YYYY-MM-DD
194                 if (value.length() >= 11) {
195                     item->addMetaData(M_DATE, fmt::format("{}-{}-{}", value.substr(0, 4), value.substr(5, 2), value.substr(8, 2)));
196                 }
197             }
198             break;
199         }
200         case EXIF_TAG_USER_COMMENT: {
201             auto value = trimString(exif_egv(e));
202             if (!value.empty()) {
203                 item->addMetaData(M_DESCRIPTION, sc->convert(value));
204             }
205             break;
206         }
207         case EXIF_TAG_PIXEL_X_DIMENSION: {
208             auto value = trimString(exif_egv(e));
209             if (!value.empty()) {
210                 imageX = sc->convert(value);
211             }
212             break;
213         }
214         case EXIF_TAG_PIXEL_Y_DIMENSION: {
215             auto value = trimString(exif_egv(e));
216             if (!value.empty()) {
217                 imageY = sc->convert(value);
218             }
219             break;
220         }
221         default:
222             break;
223         }
224 
225         // if there are any metadata tags that the user wants - add them
226         for (auto&& [tag, key] : metatags) {
227             if (!tag.empty()) {
228                 if (e->tag == getTagFromString(tag)) {
229                     auto value = trimString(exif_egv(e));
230                     if (!value.empty()) {
231                         item->addMetaData(key, sc->convert(value));
232                         log_debug("Adding tag '{}' as '{}' with value '{}'", tag, key, value);
233                     }
234                 }
235             }
236         }
237         // if there are any auxilary tags that the user wants - add them
238         for (auto&& aux : auxtags) {
239             if (!aux.empty()) {
240                 if (e->tag == getTagFromString(aux)) {
241                     auto value = trimString(exif_egv(e));
242                     if (!value.empty()) {
243                         item->setAuxData(aux, sc->convert(value));
244                         // log_debug("Adding tag: {} with value {}", aux, value);
245                     }
246                 }
247             }
248         }
249     }
250 }
251 
fillMetadata(const std::shared_ptr<CdsObject> & obj)252 void LibExifHandler::fillMetadata(const std::shared_ptr<CdsObject>& obj)
253 {
254     auto item = std::dynamic_pointer_cast<CdsItem>(obj);
255     if (!item)
256         return;
257 
258     auto sc = StringConverter::m2i(CFG_IMPORT_LIBOPTS_EXIF_CHARSET, item->getLocation(), config);
259     ExifData* ed = exif_data_new_from_file(item->getLocation().c_str());
260 
261     if (!ed) {
262         log_debug("Exif data not found, attempting to set resolution internally...");
263         setJpegResolutionResource(item, 0);
264         return;
265     }
266 
267     auto aux = config->getArrayOption(CFG_IMPORT_LIBOPTS_EXIF_AUXDATA_TAGS_LIST);
268     auto meta = config->getDictionaryOption(CFG_IMPORT_LIBOPTS_EXIF_METADATA_TAGS_LIST);
269     for (auto&& i : ed->ifd) {
270         if (i)
271             process_ifd(i, item, sc, aux, meta);
272     }
273 
274     // we got the image resolution so we can add our resource
275     if (!imageX.empty() && !imageY.empty()) {
276         item->getResource(0)->addAttribute(R_RESOLUTION, fmt::format("{}x{}", imageX, imageY));
277     } else {
278         setJpegResolutionResource(item, 0);
279     }
280 
281     if (ed->size) {
282         try {
283             auto io_h = std::make_unique<MemIOHandler>(ed->data, ed->size);
284             io_h->open(UPNP_READ);
285             const std::string th_resolution = get_jpeg_resolution(std::move(io_h));
286             log_debug("RESOLUTION: {}", th_resolution);
287 
288             auto resource = std::make_shared<CdsResource>(CH_LIBEXIF);
289             resource->addAttribute(R_PROTOCOLINFO, renderProtocolInfo(item->getMimeType()));
290             resource->addAttribute(R_RESOLUTION, th_resolution);
291             resource->addParameter(RESOURCE_CONTENT_TYPE, EXIF_THUMBNAIL);
292             item->addResource(resource);
293         } catch (const std::runtime_error& e) {
294             log_error("Something bad happened! {}", e.what());
295         }
296     } // (ed->size)
297     exif_data_unref(ed);
298 }
299 
serveContent(const std::shared_ptr<CdsObject> & obj,int resNum)300 std::unique_ptr<IOHandler> LibExifHandler::serveContent(const std::shared_ptr<CdsObject>& obj, int resNum)
301 {
302     auto item = std::dynamic_pointer_cast<CdsItem>(obj);
303     if (!item)
304         return nullptr;
305 
306     auto res = item->getResource(resNum);
307 
308     std::string ctype = getValueOrDefault(res->getParameters(), RESOURCE_CONTENT_TYPE);
309     if (ctype != EXIF_THUMBNAIL)
310         throw_std_runtime_error("Got unknown content type: {}", ctype);
311 
312     ExifData* ed = exif_data_new_from_file(item->getLocation().c_str());
313     if (!ed)
314         throw_std_runtime_error("Resource {} has no exif information", resNum);
315 
316     if (!(ed->size))
317         throw_std_runtime_error("Resource {} has no exif thumbnail", resNum);
318 
319     auto io_handler = std::make_unique<MemIOHandler>(ed->data, ed->size);
320     exif_data_unref(ed);
321     return io_handler;
322 }
323 #endif // HAVE_LIBEXIF
324