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