diff options
author | tommycli@chromium.org <tommycli@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-04-12 00:34:56 +0000 |
---|---|---|
committer | tommycli@chromium.org <tommycli@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-04-12 00:34:56 +0000 |
commit | a0785560ac602a476e184f6cd6a1444e704fd542 (patch) | |
tree | 0bcaccf19f8183e9cd062eea059d79c581efcdee /chrome | |
parent | c459ff92e7ee9612fa29573af89e9e2e9e4edf93 (diff) | |
download | chromium_src-a0785560ac602a476e184f6cd6a1444e704fd542.zip chromium_src-a0785560ac602a476e184f6cd6a1444e704fd542.tar.gz chromium_src-a0785560ac602a476e184f6cd6a1444e704fd542.tar.bz2 |
Reapply and refactor of: "Media Galleries API Metadata: Image metadata"
Original patch:
https://codereview.chromium.org/191583002
This takes the original patch and changes libexif from a load-time 'shared_library' to a runtime-loaded 'loadable_module'. This is based on rsesek's reasoning that it would reduce overall performance impact as well as not require a sandbox hole for Mac 10.6.
TBR=
BUG=318450
Review URL: https://codereview.chromium.org/226643007
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@263427 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome')
21 files changed, 760 insertions, 3 deletions
diff --git a/chrome/browser/extensions/api/media_galleries/media_galleries_apitest.cc b/chrome/browser/extensions/api/media_galleries/media_galleries_apitest.cc index d447324..06d2520 100644 --- a/chrome/browser/extensions/api/media_galleries/media_galleries_apitest.cc +++ b/chrome/browser/extensions/api/media_galleries/media_galleries_apitest.cc @@ -378,6 +378,11 @@ class MediaGalleriesPlatformAppBrowserTest : public PlatformAppBrowserTest { .AppendASCII("common"); } + base::FilePath GetWallpaperTestDataDir() const { + return test_data_dir_.AppendASCII("api_test") + .AppendASCII("wallpaper"); + } + int num_galleries() const { return ensure_media_directories_exists_->num_galleries(); } @@ -667,6 +672,7 @@ IN_PROC_BROWSER_TEST_F(MediaGalleriesPlatformAppBrowserTest, GetMetadata) { AddFileToSingleFakeGallery(media::GetTestDataFilePath("90rotation.mp4")); AddFileToSingleFakeGallery(media::GetTestDataFilePath("id3_png_test.mp3")); + AddFileToSingleFakeGallery(GetWallpaperTestDataDir().AppendASCII("test.jpg")); base::ListValue custom_args; #if defined(USE_PROPRIETARY_CODECS) diff --git a/chrome/chrome.gyp b/chrome/chrome.gyp index 44a47d3..ed51181 100644 --- a/chrome/chrome.gyp +++ b/chrome/chrome.gyp @@ -242,6 +242,7 @@ '../content/content.gyp:content_utility', '../media/media.gyp:media', '../skia/skia.gyp:skia', + '../third_party/libexif/libexif.gyp:libexif', '../third_party/libxml/libxml.gyp:libxml', 'common', '<(DEPTH)/chrome/chrome_resources.gyp:chrome_resources', @@ -288,6 +289,8 @@ 'utility/importer/nss_decryptor_win.h', 'utility/importer/safari_importer.h', 'utility/importer/safari_importer.mm', + 'utility/media_galleries/image_metadata_extractor.cc', + 'utility/media_galleries/image_metadata_extractor.h', 'utility/media_galleries/ipc_data_source.cc', 'utility/media_galleries/ipc_data_source.h', 'utility/media_galleries/itunes_pref_parser_win.cc', @@ -343,6 +346,9 @@ ], }], ['OS=="android"', { + 'dependencies!': [ + '../third_party/libexif/libexif.gyp:libexif', + ], 'sources/': [ ['exclude', '^utility/importer/'], ['exclude', '^utility/media_galleries/'], diff --git a/chrome/chrome.isolate b/chrome/chrome.isolate index 6a92211..dc2b0d1 100644 --- a/chrome/chrome.isolate +++ b/chrome/chrome.isolate @@ -44,6 +44,7 @@ '<(PRODUCT_DIR)/<(mac_product_name).app/Contents/MacOS/<(mac_product_name)', ], 'isolate_dependency_tracked': [ + '<(PRODUCT_DIR)/exif.so', '<(PRODUCT_DIR)/ffmpegsumo.so', ], 'isolate_dependency_untracked': [ @@ -64,6 +65,7 @@ '<(PRODUCT_DIR)/d3dcompiler_46.dll', '<(PRODUCT_DIR)/ffmpegsumo.dll', '<(PRODUCT_DIR)/libEGL.dll', + '<(PRODUCT_DIR)/libexif.dll', '<(PRODUCT_DIR)/libGLESv2.dll', '<(PRODUCT_DIR)/nacl64<(EXECUTABLE_SUFFIX)', '<(PRODUCT_DIR)/osmesa.dll', diff --git a/chrome/chrome_dll_bundle.gypi b/chrome/chrome_dll_bundle.gypi index 33aeb42..b168496 100644 --- a/chrome/chrome_dll_bundle.gypi +++ b/chrome/chrome_dll_bundle.gypi @@ -138,9 +138,9 @@ ], 'copies': [ { - # Copy FFmpeg binaries for audio/video support. 'destination': '<(PRODUCT_DIR)/$(CONTENTS_FOLDER_PATH)/Libraries', 'files': [ + '<(PRODUCT_DIR)/exif.so', '<(PRODUCT_DIR)/ffmpegsumo.so', ], }, diff --git a/chrome/chrome_tests_unit.gypi b/chrome/chrome_tests_unit.gypi index 9f91507..21114f6 100644 --- a/chrome/chrome_tests_unit.gypi +++ b/chrome/chrome_tests_unit.gypi @@ -1921,6 +1921,7 @@ 'utility/importer/firefox_importer_unittest_utils.h', 'utility/importer/firefox_importer_unittest_utils_mac.cc', 'utility/importer/safari_importer_unittest.mm', + 'utility/media_galleries/image_metadata_extractor_unittest.cc', # Duplicate these tests here because PathService has more items in # unit_tests than in base_unittests. @@ -2562,6 +2563,7 @@ ['exclude', '^browser/ui/webui/sync_promo'], ['exclude', '^tools/profile_reset/'], ['exclude', '^utility/importer/'], + ['exclude', '^utility/media_galleries/'], ], 'conditions': [ ['gtest_target_type == "shared_library"', { diff --git a/chrome/common/extensions/api/media_galleries.idl b/chrome/common/extensions/api/media_galleries.idl index ca3f9dd..0bf42d3 100644 --- a/chrome/common/extensions/api/media_galleries.idl +++ b/chrome/common/extensions/api/media_galleries.idl @@ -106,13 +106,26 @@ namespace mediaGalleries { long? height; long? width; + // Defined for images only. + double? xResolution; + double? yResolution; + // Defined for audio and video. In seconds. double? duration; // Defined for images and video. In degrees. long? rotation; - // Generic metadata tags. + // Defined for images only. + DOMString? cameraMake; + DOMString? cameraModel; + double? exposureTimeSeconds; + boolean? flashFired; + double? fNumber; + double? focalLengthMm; + double? isoEquivalent; + + // Defined for audio and video only. DOMString? album; DOMString? artist; DOMString? comment; diff --git a/chrome/installer/linux/debian/expected_deps b/chrome/installer/linux/debian/expected_deps index a0a49c9..15a7194 100644 --- a/chrome/installer/linux/debian/expected_deps +++ b/chrome/installer/linux/debian/expected_deps @@ -5,6 +5,7 @@ libcairo2 (>= 1.6.0) libcap2 (>= 2.10) libcups2 (>= 1.4.0) libdbus-1-3 (>= 1.2.14) +libexif12 libexpat1 (>= 1.95.8) libfontconfig1 (>= 2.8.0) libfreetype6 (>= 2.3.9) diff --git a/chrome/installer/linux/rpm/expected_deps_i386 b/chrome/installer/linux/rpm/expected_deps_i386 index 52fd504..920c3e3 100644 --- a/chrome/installer/linux/rpm/expected_deps_i386 +++ b/chrome/installer/linux/rpm/expected_deps_i386 @@ -24,6 +24,7 @@ libdbus-1.so.3 libdl.so.2 libdl.so.2(GLIBC_2.0) libdl.so.2(GLIBC_2.1) +libexif.so.12 libexpat.so.1 libfontconfig.so.1 libfreetype.so.6 diff --git a/chrome/installer/linux/rpm/expected_deps_x86_64 b/chrome/installer/linux/rpm/expected_deps_x86_64 index 1435e39..1e3f536 100644 --- a/chrome/installer/linux/rpm/expected_deps_x86_64 +++ b/chrome/installer/linux/rpm/expected_deps_x86_64 @@ -19,6 +19,7 @@ libcups.so.2()(64bit) libdbus-1.so.3()(64bit) libdl.so.2()(64bit) libdl.so.2(GLIBC_2.2.5)(64bit) +libexif.so.12()(64bit) libexpat.so.1()(64bit) libfontconfig.so.1()(64bit) libfreetype.so.6()(64bit) diff --git a/chrome/installer/mini_installer/chrome.release b/chrome/installer/mini_installer/chrome.release index 66ab4de..fcacc38 100644 --- a/chrome/installer/mini_installer/chrome.release +++ b/chrome/installer/mini_installer/chrome.release @@ -28,6 +28,7 @@ ffmpegsumo.dll: %(VersionDir)s\ icudt.dll: %(VersionDir)s\ icudtl.dat: %(VersionDir)s\ libEGL.dll: %(VersionDir)s\ +libexif.dll: %(VersionDir)s\ libGLESv2.dll: %(VersionDir)s\ nacl64.exe: %(VersionDir)s\ nacl_irt_x86_32.nexe: %(VersionDir)s\ diff --git a/chrome/test/data/extensions/api_test/media_galleries/media_metadata/test.js b/chrome/test/data/extensions/api_test/media_galleries/media_metadata/test.js index 140a9ce..aadad30 100644 --- a/chrome/test/data/extensions/api_test/media_galleries/media_metadata/test.js +++ b/chrome/test/data/extensions/api_test/media_galleries/media_metadata/test.js @@ -32,6 +32,27 @@ function ImageMIMETypeOnlyTest() { RunMetadataTest("test.jpg", {metadataType: 'mimeTypeOnly'}, verifyMetadata); } +function ImageTagsTest() { + function verifyMetadata(metadata) { + chrome.test.assertEq("image/jpeg", metadata.mimeType); + chrome.test.assertEq(5616, metadata.width); + chrome.test.assertEq(3744, metadata.height); + chrome.test.assertEq(0, metadata.rotation); + chrome.test.assertEq(300.0, metadata.xResolution); + chrome.test.assertEq(300.0, metadata.yResolution); + chrome.test.assertEq("Canon", metadata.cameraMake); + chrome.test.assertEq("Canon EOS 5D Mark II", metadata.cameraModel); + chrome.test.assertEq(0.01, metadata.exposureTimeSeconds); + chrome.test.assertFalse(metadata.flashFired); + chrome.test.assertEq(3.2, metadata.fNumber); + chrome.test.assertEq(100, metadata.focalLengthMm); + chrome.test.assertEq(1600, metadata.isoEquivalent); + chrome.test.succeed(); + } + + RunMetadataTest("test.jpg", {}, verifyMetadata); +} + function MP3MIMETypeOnlyTest() { function verifyMetadata(metadata) { chrome.test.assertEq("audio/mpeg", metadata.mimeType); @@ -92,7 +113,8 @@ chrome.test.getConfig(function(config) { // Should still be able to sniff MP3 MIME type without proprietary codecs. var testsToRun = [ - ImageMIMETypeOnlyTest + ImageMIMETypeOnlyTest, + ImageTagsTest ]; if (useProprietaryCodecs) { diff --git a/chrome/tools/build/chromeos/FILES.cfg b/chrome/tools/build/chromeos/FILES.cfg index 97e002a..2088df8 100644 --- a/chrome/tools/build/chromeos/FILES.cfg +++ b/chrome/tools/build/chromeos/FILES.cfg @@ -57,6 +57,10 @@ FILES = [ 'buildtype': ['dev', 'official'], }, { + 'filename': 'lib/libexif.so', + 'buildtype': ['dev', 'official'], + }, + { 'filename': 'lib/libpeerconnection.so', 'buildtype': ['dev', 'official'], 'optional': ['dev', 'official'], diff --git a/chrome/tools/build/mac/TESTS b/chrome/tools/build/mac/TESTS index 331ac56..4a81ea9 100644 --- a/chrome/tools/build/mac/TESTS +++ b/chrome/tools/build/mac/TESTS @@ -1,2 +1,3 @@ sync_integration_tests +exif.so ffmpegsumo.so diff --git a/chrome/tools/build/mac/dump_product_syms b/chrome/tools/build/mac/dump_product_syms index ad816f5..43cbfff 100755 --- a/chrome/tools/build/mac/dump_product_syms +++ b/chrome/tools/build/mac/dump_product_syms @@ -81,6 +81,7 @@ SRC_NAMES=( "${SRC_APP_NAME} Helper.app" "crash_inspector" "crash_report_sender.app" + "exif.so" "ffmpegsumo.so" "libplugin_carbon_interpose.dylib" "ppGoogleNaClPluginChrome.plugin" diff --git a/chrome/tools/build/win/FILES.cfg b/chrome/tools/build/win/FILES.cfg index df48704..9fe6352 100644 --- a/chrome/tools/build/win/FILES.cfg +++ b/chrome/tools/build/win/FILES.cfg @@ -100,6 +100,11 @@ FILES = [ 'optional': ['dev', 'official'], }, { + 'filename': 'libexif.dll', + 'buildtype': ['dev', 'official'], + 'filegroup': ['default', 'symsrc'], + }, + { 'filename': 'libpeerconnection.dll', 'buildtype': ['dev', 'official'], 'optional': ['dev', 'official'], diff --git a/chrome/unit_tests.isolate b/chrome/unit_tests.isolate index 9d0da24..15a24b7 100644 --- a/chrome/unit_tests.isolate +++ b/chrome/unit_tests.isolate @@ -82,6 +82,7 @@ ['OS=="mac"', { 'variables': { 'isolate_dependency_tracked': [ + '<(PRODUCT_DIR)/exif.so', '<(PRODUCT_DIR)/ffmpegsumo.so', '<(PRODUCT_DIR)/osmesa.so', ], @@ -111,6 +112,7 @@ 'isolate_dependency_tracked': [ '<(PRODUCT_DIR)/chrome_elf.dll', '<(PRODUCT_DIR)/ffmpegsumo.dll', + '<(PRODUCT_DIR)/libexif.dll', '<(PRODUCT_DIR)/osmesa.dll', ], 'isolate_dependency_untracked': [ diff --git a/chrome/utility/chrome_content_utility_client.cc b/chrome/utility/chrome_content_utility_client.cc index ebe8cef..e3fb09a 100644 --- a/chrome/utility/chrome_content_utility_client.cc +++ b/chrome/utility/chrome_content_utility_client.cc @@ -68,6 +68,7 @@ #endif // defined(OS_WIN) || defined(OS_MACOSX) #if !defined(OS_ANDROID) && !defined(OS_IOS) +#include "chrome/utility/media_galleries/image_metadata_extractor.h" #include "chrome/utility/media_galleries/ipc_data_source.h" #include "chrome/utility/media_galleries/media_metadata_parser.h" #endif // !defined(OS_ANDROID) && !defined(OS_IOS) @@ -437,6 +438,11 @@ void ChromeContentUtilityClient::PreSandboxStartup() { g_pdf_lib.Get().Init(); +#if !defined(OS_ANDROID) && !defined(OS_IOS) + // Initialize libexif for image metadata parsing. + metadata::ImageMetadataExtractor::InitializeLibrary(); +#endif // !defined(OS_ANDROID) && !defined(OS_IOS) + // Load media libraries for media file validation. base::FilePath media_path; PathService::Get(content::DIR_MEDIA_LIBS, &media_path); diff --git a/chrome/utility/media_galleries/image_metadata_extractor.cc b/chrome/utility/media_galleries/image_metadata_extractor.cc new file mode 100644 index 0000000..2b5fbfb --- /dev/null +++ b/chrome/utility/media_galleries/image_metadata_extractor.cc @@ -0,0 +1,456 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/utility/media_galleries/image_metadata_extractor.h" + +#include "base/bind.h" +#include "base/callback.h" +#include "base/files/file_path.h" +#include "base/lazy_instance.h" +#include "base/memory/ref_counted.h" +#include "base/numerics/safe_conversions.h" +#include "base/path_service.h" +#include "base/scoped_native_library.h" +#include "base/strings/string_number_conversions.h" +#include "content/public/common/content_paths.h" +#include "media/base/data_source.h" +#include "net/base/io_buffer.h" + +extern "C" { +#include <libexif/exif-data.h> +#include <libexif/exif-loader.h> +} // extern "C" + +namespace metadata { + +namespace { + +const size_t kMaxBufferSize = 50 * 1024 * 1024; // Arbitrary maximum of 50MB. + +void FinishGetImageBytes( + net::DrainableIOBuffer* buffer, + media::DataSource* source, + const base::Callback<void(net::DrainableIOBuffer*)>& callback, + int bytes_read) { + if (bytes_read == media::DataSource::kReadError) { + callback.Run(NULL); + return; + } + + buffer->DidConsume(bytes_read); + // Didn't get the whole file. Continue reading to get the rest. + if (buffer->BytesRemaining() > 0) { + source->Read(0, buffer->BytesRemaining(), + reinterpret_cast<uint8*>(buffer->data()), + base::Bind(&FinishGetImageBytes, make_scoped_refptr(buffer), + base::Unretained(source), callback)); + return; + } + + buffer->SetOffset(0); + callback.Run(make_scoped_refptr(buffer)); +} + +void GetImageBytes( + media::DataSource* source, + const base::Callback<void(net::DrainableIOBuffer*)>& callback) { + int64 size64 = 0; + if (!source->GetSize(&size64) || + base::saturated_cast<size_t>(size64) > kMaxBufferSize) { + return callback.Run(NULL); + } + int size = base::checked_cast<int>(size64); + + scoped_refptr<net::DrainableIOBuffer> buffer( + new net::DrainableIOBuffer(new net::IOBuffer(size), size)); + source->Read(0, buffer->BytesRemaining(), + reinterpret_cast<uint8*>(buffer->data()), + base::Bind(&FinishGetImageBytes, buffer, + base::Unretained(source), callback)); +} + +class ExifFunctions { + public: + ExifFunctions() : exif_loader_write_func_(NULL), + exif_loader_new_func_(NULL), + exif_loader_unref_func_(NULL), + exif_loader_get_data_func_(NULL), + exif_data_free_func_(NULL), + exif_data_get_byte_order_func_(NULL), + exif_get_short_func_(NULL), + exif_get_long_func_(NULL), + exif_get_rational_func_(NULL), + exif_entry_get_value_func_(NULL), + exif_content_get_entry_func_(NULL) { + } + + bool Initialize(const base::FilePath& module_dir) { + if (exif_lib_.is_valid()) + return true; + +#if defined(OS_WIN) + base::FilePath module_path = module_dir.AppendASCII("libexif.dll"); +#elif defined(OS_MACOSX) + base::FilePath module_path = module_dir.AppendASCII("exif.so"); +#elif defined(OS_CHROMEOS) + // On ChromeOS, we build and distribute our own version of libexif. + base::FilePath module_path = module_dir.AppendASCII("libexif.so"); +#else + // On Linux-like systems, we use the system libexif. + base::FilePath module_path = base::FilePath().AppendASCII("libexif.so.12"); +#endif + + base::ScopedNativeLibrary lib(base::LoadNativeLibrary(module_path, NULL)); + if (!lib.is_valid()) { + LOG(ERROR) << "Couldn't load libexif."; + return false; + } + + if (!GetFunctionPointer(lib, &exif_loader_write_func_, + "exif_loader_write") || + !GetFunctionPointer(lib, &exif_loader_new_func_, "exif_loader_new") || + !GetFunctionPointer(lib, &exif_loader_unref_func_, + "exif_loader_unref") || + !GetFunctionPointer(lib, &exif_loader_get_data_func_, + "exif_loader_get_data") || + !GetFunctionPointer(lib, &exif_data_free_func_, "exif_data_free") || + !GetFunctionPointer(lib, &exif_data_get_byte_order_func_, + "exif_data_get_byte_order") || + !GetFunctionPointer(lib, &exif_get_short_func_, "exif_get_short") || + !GetFunctionPointer(lib, &exif_get_long_func_, "exif_get_long") || + !GetFunctionPointer(lib, &exif_get_rational_func_, + "exif_get_rational") || + !GetFunctionPointer(lib, &exif_entry_get_value_func_, + "exif_entry_get_value") || + !GetFunctionPointer(lib, &exif_content_get_entry_func_, + "exif_content_get_entry")) { + return false; + } + + exif_lib_.Reset(lib.Release()); + return true; + } + + ExifData* ParseExifFromBuffer(unsigned char* buffer, unsigned int size) { + DCHECK(exif_lib_.is_valid()); + ExifLoader* loader = exif_loader_new_func_(); + exif_loader_write_func_(loader, buffer, size); + + ExifData* data = exif_loader_get_data_func_(loader); + + exif_loader_unref_func_(loader); + loader = NULL; + + return data; + } + + void ExifDataFree(ExifData* data) { + DCHECK(exif_lib_.is_valid()); + return exif_data_free_func_(data); + } + + void ExtractInt(ExifData* data, ExifTag tag, int* result) { + DCHECK(exif_lib_.is_valid()); + DCHECK(result); + + ExifEntry* entry = ExifContentGetEntry(data, tag); + if (!entry) + return; + + ExifByteOrder order = exif_data_get_byte_order_func_(data); + switch (entry->format) { + case EXIF_FORMAT_SHORT: { + ExifShort v = exif_get_short_func_(entry->data, order); + *result = base::checked_cast<int>(v); + break; + } + case EXIF_FORMAT_LONG: { + ExifLong v = exif_get_long_func_(entry->data, order); + // Ignore values that don't fit in a signed int - likely invalid data. + if (base::IsValueInRangeForNumericType<int>(v)) + *result = base::checked_cast<int>(v); + break; + } + default: { + // Ignore all other entry formats. + } + } + } + + void ExtractDouble(ExifData* data, ExifTag tag, double* result) { + DCHECK(exif_lib_.is_valid()); + DCHECK(result); + + ExifEntry* entry = ExifContentGetEntry(data, tag); + if (!entry) + return; + + ExifByteOrder order = exif_data_get_byte_order_func_(data); + + if (entry->format == EXIF_FORMAT_RATIONAL) { + ExifRational v = exif_get_rational_func_(entry->data, order); + *result = base::checked_cast<double>(v.numerator) / + base::checked_cast<double>(v.denominator); + } + } + + void ExtractString(ExifData* data, ExifTag tag, std::string* result) { + DCHECK(exif_lib_.is_valid()); + DCHECK(result); + + ExifEntry* entry = ExifContentGetEntry(data, tag); + if (!entry) + return; + + char buf[1024]; + exif_entry_get_value_func_(entry, buf, sizeof(buf)); + *result = buf; + } + + private: + // Exported by libexif. + typedef unsigned char (*ExifLoaderWriteFunc)( + ExifLoader *eld, unsigned char *buf, unsigned int len); + typedef ExifLoader* (*ExifLoaderNewFunc)(); + typedef void (*ExifLoaderUnrefFunc)(ExifLoader* loader); + typedef ExifData* (*ExifLoaderGetDataFunc)(ExifLoader* loader); + typedef void (*ExifDataFreeFunc)(ExifData* data); + typedef ExifByteOrder (*ExifDataGetByteOrderFunc)(ExifData* data); + typedef ExifShort (*ExifGetShortFunc)(const unsigned char *buf, + ExifByteOrder order); + typedef ExifLong (*ExifGetLongFunc)(const unsigned char *buf, + ExifByteOrder order); + typedef ExifRational (*ExifGetRationalFunc)(const unsigned char *buf, + ExifByteOrder order); + typedef const char* (*ExifEntryGetValueFunc)(ExifEntry *e, char *val, + unsigned int maxlen); + typedef ExifEntry* (*ExifContentGetEntryFunc)(ExifContent* content, + ExifTag tag); + + template<typename FunctionType> + bool GetFunctionPointer(const base::ScopedNativeLibrary& lib, + FunctionType* function, const char* name) { + DCHECK(lib.is_valid()); + DCHECK(function); + DCHECK(!(*function)); + *function = reinterpret_cast<FunctionType>( + lib.GetFunctionPointer(name)); + DLOG_IF(WARNING, !(*function)) << "Missing " << name; + return *function != NULL; + } + + // Redefines exif_content_get_entry macro in terms of function pointer. + ExifEntry* ExifContentGetEntry(ExifData* data, ExifTag tag) { + DCHECK(exif_lib_.is_valid()); + const ExifIfd ifds[] = + { EXIF_IFD_0, EXIF_IFD_1, EXIF_IFD_EXIF, EXIF_IFD_GPS }; + + for (size_t i = 0; i < arraysize(ifds); ++i) { + ExifEntry* entry = exif_content_get_entry_func_(data->ifd[ifds[i]], tag); + if (entry) + return entry; + } + + return NULL; + } + + ExifLoaderWriteFunc exif_loader_write_func_; + ExifLoaderNewFunc exif_loader_new_func_; + ExifLoaderUnrefFunc exif_loader_unref_func_; + ExifLoaderGetDataFunc exif_loader_get_data_func_; + ExifDataFreeFunc exif_data_free_func_; + ExifDataGetByteOrderFunc exif_data_get_byte_order_func_; + ExifGetShortFunc exif_get_short_func_; + ExifGetLongFunc exif_get_long_func_; + ExifGetRationalFunc exif_get_rational_func_; + ExifEntryGetValueFunc exif_entry_get_value_func_; + ExifContentGetEntryFunc exif_content_get_entry_func_; + + base::ScopedNativeLibrary exif_lib_; + DISALLOW_COPY_AND_ASSIGN(ExifFunctions); +}; + +static base::LazyInstance<ExifFunctions> g_exif_lib = LAZY_INSTANCE_INITIALIZER; + +} // namespace + +// static +bool ImageMetadataExtractor::InitializeLibrary() { + base::FilePath media_path; + if (!PathService::Get(content::DIR_MEDIA_LIBS, &media_path)) + return false; + return g_exif_lib.Get().Initialize(media_path); +} + +// static +bool ImageMetadataExtractor::InitializeLibraryForTesting() { + base::FilePath module_dir; + if (!PathService::Get(base::DIR_EXE, &module_dir)) + return false; + return g_exif_lib.Get().Initialize(module_dir); +} + +ImageMetadataExtractor::ImageMetadataExtractor() + : extracted_(false), + width_(-1), + height_(-1), + rotation_(-1), + x_resolution_(-1), + y_resolution_(-1), + exposure_time_sec_(-1), + flash_fired_(false), + f_number_(-1), + focal_length_mm_(-1), + iso_equivalent_(-1) { +} + +ImageMetadataExtractor::~ImageMetadataExtractor() { +} + +void ImageMetadataExtractor::Extract(media::DataSource* source, + const DoneCallback& callback) { + DCHECK(!extracted_); + + GetImageBytes(source, base::Bind(&ImageMetadataExtractor::FinishExtraction, + base::Unretained(this), callback)); + +} + +int ImageMetadataExtractor::width() const { + DCHECK(extracted_); + return width_; +} + +int ImageMetadataExtractor::height() const { + DCHECK(extracted_); + return height_; +} + +int ImageMetadataExtractor::rotation() const { + DCHECK(extracted_); + return rotation_; +} + +double ImageMetadataExtractor::x_resolution() const { + DCHECK(extracted_); + return x_resolution_; +} + +double ImageMetadataExtractor::y_resolution() const { + DCHECK(extracted_); + return y_resolution_; +} + +const std::string& ImageMetadataExtractor::date() const { + DCHECK(extracted_); + return date_; +} + +const std::string& ImageMetadataExtractor::camera_make() const { + DCHECK(extracted_); + return camera_make_; +} + +const std::string& ImageMetadataExtractor::camera_model() const { + DCHECK(extracted_); + return camera_model_; +} + +double ImageMetadataExtractor::exposure_time_sec() const { + DCHECK(extracted_); + return exposure_time_sec_; +} + +bool ImageMetadataExtractor::flash_fired() const { + DCHECK(extracted_); + return flash_fired_; +} + +double ImageMetadataExtractor::f_number() const { + DCHECK(extracted_); + return f_number_; +} + +double ImageMetadataExtractor::focal_length_mm() const { + DCHECK(extracted_); + return focal_length_mm_; +} + +int ImageMetadataExtractor::iso_equivalent() const { + DCHECK(extracted_); + return iso_equivalent_; +} + +void ImageMetadataExtractor::FinishExtraction( + const DoneCallback& callback, net::DrainableIOBuffer* buffer) { + if (!buffer) { + callback.Run(false); + return; + } + + ExifData* data = g_exif_lib.Get().ParseExifFromBuffer( + reinterpret_cast<unsigned char*>(buffer->data()), + buffer->BytesRemaining()); + + if (!data) { + callback.Run(false); + return; + } + + g_exif_lib.Get().ExtractInt(data, EXIF_TAG_IMAGE_WIDTH, &width_); + g_exif_lib.Get().ExtractInt(data, EXIF_TAG_IMAGE_LENGTH, &height_); + + // We ignore the mirrored-aspect of the mirrored-orientations and just + // indicate the rotation. Mirrored-orientations are very rare. + int orientation = 0; + g_exif_lib.Get().ExtractInt(data, EXIF_TAG_ORIENTATION, &orientation); + switch (orientation) { + case 1: + case 2: + rotation_ = 0; + break; + case 3: + case 4: + rotation_ = 180; + break; + case 5: + case 6: + rotation_ = 90; + break; + case 7: + case 8: + rotation_ = 270; + break; + } + + g_exif_lib.Get().ExtractDouble(data, EXIF_TAG_X_RESOLUTION, &x_resolution_); + g_exif_lib.Get().ExtractDouble(data, EXIF_TAG_Y_RESOLUTION, &y_resolution_); + + g_exif_lib.Get().ExtractString(data, EXIF_TAG_DATE_TIME, &date_); + + g_exif_lib.Get().ExtractString(data, EXIF_TAG_MAKE, &camera_make_); + g_exif_lib.Get().ExtractString(data, EXIF_TAG_MODEL, &camera_model_); + g_exif_lib.Get().ExtractDouble(data, EXIF_TAG_EXPOSURE_TIME, + &exposure_time_sec_); + + int flash_value = -1; + g_exif_lib.Get().ExtractInt(data, EXIF_TAG_FLASH, &flash_value); + if (flash_value >= 0) { + flash_fired_ = (flash_value & 0x1) != 0; + } + + g_exif_lib.Get().ExtractDouble(data, EXIF_TAG_FNUMBER, &f_number_); + g_exif_lib.Get().ExtractDouble(data, EXIF_TAG_FOCAL_LENGTH, + &focal_length_mm_); + g_exif_lib.Get().ExtractInt(data, EXIF_TAG_ISO_SPEED_RATINGS, + &iso_equivalent_); + + g_exif_lib.Get().ExifDataFree(data); + + extracted_ = true; + + callback.Run(true); +} + +} // namespace metadata diff --git a/chrome/utility/media_galleries/image_metadata_extractor.h b/chrome/utility/media_galleries/image_metadata_extractor.h new file mode 100644 index 0000000..6978761 --- /dev/null +++ b/chrome/utility/media_galleries/image_metadata_extractor.h @@ -0,0 +1,94 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_UTILITY_MEDIA_GALLERIES_IMAGE_METADATA_EXTRACTOR_H_ +#define CHROME_UTILITY_MEDIA_GALLERIES_IMAGE_METADATA_EXTRACTOR_H_ + +#include <string> + +#include "base/basictypes.h" +#include "base/callback_forward.h" + +namespace media { +class DataSource; +} + +namespace net { +class DrainableIOBuffer; +} + +namespace metadata { + +// Extracts a basic set of image metadata tags. Users must initialize the +// library before use. Each class instance is 'one-time-use', and cannot be used +// to extract metadata from multiple images. +class ImageMetadataExtractor { + public: + typedef base::Callback<void(bool)> DoneCallback; + + // One of these two is required before use of this class. + static bool InitializeLibrary(); + static bool InitializeLibraryForTesting(); + + ImageMetadataExtractor(); + ~ImageMetadataExtractor(); + + // |callback| called with whether or not the extraction succeeded. Should + // only be called once. + void Extract(media::DataSource* source, const DoneCallback& callback); + + // All below methods require Extract to have already succeeded. + // Returns -1 if file does not define a width or height. + int width() const; + int height() const; + + // In degrees. + int rotation() const; + + // In pixels per inch. + double x_resolution() const; + double y_resolution() const; + + // In the same string form as the original file. + const std::string& date() const; + + const std::string& camera_make() const; + const std::string& camera_model() const; + double exposure_time_sec() const; + bool flash_fired() const; + double f_number() const; + double focal_length_mm() const; + int iso_equivalent() const; + + private: + // Second half of the Extract method. + void FinishExtraction(const DoneCallback& callback, + net::DrainableIOBuffer* buffer); + + bool extracted_; + + int width_; + int height_; + + int rotation_; + + double x_resolution_; + double y_resolution_; + + std::string date_; + + std::string camera_make_; + std::string camera_model_; + double exposure_time_sec_; + bool flash_fired_; + double f_number_; + double focal_length_mm_; + int iso_equivalent_; + + DISALLOW_COPY_AND_ASSIGN(ImageMetadataExtractor); +}; + +} // namespace metadata + +#endif // CHROME_UTILITY_MEDIA_GALLERIES_IMAGE_METADATA_EXTRACTOR_H_ diff --git a/chrome/utility/media_galleries/image_metadata_extractor_unittest.cc b/chrome/utility/media_galleries/image_metadata_extractor_unittest.cc new file mode 100644 index 0000000..faba6a6 --- /dev/null +++ b/chrome/utility/media_galleries/image_metadata_extractor_unittest.cc @@ -0,0 +1,76 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/bind.h" +#include "base/files/file_path.h" +#include "base/path_service.h" +#include "base/run_loop.h" +#include "chrome/common/chrome_paths.h" +#include "chrome/utility/media_galleries/image_metadata_extractor.h" +#include "media/filters/file_data_source.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace metadata { + +void QuitLoop(base::RunLoop* loop, bool* output, bool success) { + loop->Quit(); + *output = success; +} + +base::FilePath GetTestDataFilePath(const std::string& filename) { + base::FilePath path; + EXPECT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &path)); + path = path.AppendASCII("extensions").AppendASCII("api_test") + .AppendASCII("wallpaper").AppendASCII(filename); + return path; +} + +scoped_ptr<ImageMetadataExtractor> GetExtractor( + const std::string& filename, + bool expected_result) { + EXPECT_TRUE(ImageMetadataExtractor::InitializeLibraryForTesting()); + + media::FileDataSource source; + base::FilePath test_path; + + EXPECT_TRUE(source.Initialize(GetTestDataFilePath(filename))); + + scoped_ptr<ImageMetadataExtractor> extractor(new ImageMetadataExtractor); + + base::RunLoop loop; + bool extracted = false; + extractor->Extract(&source, base::Bind(&QuitLoop, &loop, &extracted)); + EXPECT_EQ(expected_result, extracted); + + return extractor.Pass(); +} + +TEST(ImageMetadataExtractorTest, JPGFile) { + scoped_ptr<ImageMetadataExtractor> extractor = + GetExtractor("test.jpg", true); + + EXPECT_EQ(5616, extractor->width()); + EXPECT_EQ(3744, extractor->height()); + EXPECT_EQ(0, extractor->rotation()); + EXPECT_EQ(300.0, extractor->x_resolution()); + EXPECT_EQ(300.0, extractor->y_resolution()); + EXPECT_EQ("2012:03:01 17:06:07", extractor->date()); + EXPECT_EQ("Canon", extractor->camera_make()); + EXPECT_EQ("Canon EOS 5D Mark II", extractor->camera_model()); + EXPECT_EQ(0.01, extractor->exposure_time_sec()); + EXPECT_FALSE(extractor->flash_fired()); + EXPECT_EQ(3.2, extractor->f_number()); + EXPECT_EQ(100, extractor->focal_length_mm()); + EXPECT_EQ(1600, extractor->iso_equivalent()); +} + +TEST(ImageMetadataExtractorTest, PNGFile) { + GetExtractor("test.png", false); +} + +TEST(ImageMetadataExtractorTest, NonImageFile) { + GetExtractor("test.js", false); +} + +} // namespace metadata diff --git a/chrome/utility/media_galleries/media_metadata_parser.cc b/chrome/utility/media_galleries/media_metadata_parser.cc index 84b56af..07c54dc 100644 --- a/chrome/utility/media_galleries/media_metadata_parser.cc +++ b/chrome/utility/media_galleries/media_metadata_parser.cc @@ -11,6 +11,7 @@ #include "base/strings/string_util.h" #include "base/task_runner_util.h" #include "base/threading/thread.h" +#include "chrome/utility/media_galleries/image_metadata_extractor.h" #include "media/base/audio_video_metadata_extractor.h" #include "media/base/data_source.h" @@ -20,15 +21,28 @@ namespace { void SetStringScopedPtr(const std::string& value, scoped_ptr<std::string>* destination) { + DCHECK(destination); if (!value.empty()) destination->reset(new std::string(value)); } void SetIntScopedPtr(int value, scoped_ptr<int>* destination) { + DCHECK(destination); if (value >= 0) destination->reset(new int(value)); } +void SetDoubleScopedPtr(double value, scoped_ptr<double>* destination) { + DCHECK(destination); + if (value >= 0) + destination->reset(new double(value)); +} + +void SetBoolScopedPtr(bool value, scoped_ptr<bool>* destination) { + DCHECK(destination); + destination->reset(new bool(value)); +} + // This runs on |media_thread_|, as the underlying FFmpeg operation is // blocking, and the utility thread must not be blocked, so the media file // bytes can be sent from the browser process to the utility process. @@ -36,6 +50,7 @@ scoped_ptr<MediaMetadataParser::MediaMetadata> ParseAudioVideoMetadata( media::DataSource* source, scoped_ptr<MediaMetadataParser::MediaMetadata> metadata) { DCHECK(source); + DCHECK(metadata.get()); media::AudioVideoMetadataExtractor extractor; if (!extractor.Extract(source)) @@ -70,6 +85,39 @@ scoped_ptr<MediaMetadataParser::MediaMetadata> ParseAudioVideoMetadata( return metadata.Pass(); } +void FinishParseImageMetadata( + ImageMetadataExtractor* extractor, + scoped_ptr<MediaMetadataParser::MediaMetadata> metadata, + MediaMetadataParser::MetadataCallback callback, + bool extract_success) { + DCHECK(extractor); + DCHECK(metadata.get()); + + if (!extract_success) { + callback.Run(metadata.Pass()); + return; + } + + SetIntScopedPtr(extractor->height(), &metadata->height); + SetIntScopedPtr(extractor->width(), &metadata->width); + + SetIntScopedPtr(extractor->rotation(), &metadata->rotation); + + SetDoubleScopedPtr(extractor->x_resolution(), &metadata->x_resolution); + SetDoubleScopedPtr(extractor->y_resolution(), &metadata->y_resolution); + SetBoolScopedPtr(extractor->flash_fired(), &metadata->flash_fired); + SetStringScopedPtr(extractor->camera_make(), &metadata->camera_make); + SetStringScopedPtr(extractor->camera_model(), &metadata->camera_model); + SetDoubleScopedPtr(extractor->exposure_time_sec(), + &metadata->exposure_time_seconds); + + SetDoubleScopedPtr(extractor->f_number(), &metadata->f_number); + SetDoubleScopedPtr(extractor->focal_length_mm(), &metadata->focal_length_mm); + SetDoubleScopedPtr(extractor->iso_equivalent(), &metadata->iso_equivalent); + + callback.Run(metadata.Pass()); +} + } // namespace MediaMetadataParser::MediaMetadataParser(media::DataSource* source, @@ -96,6 +144,15 @@ void MediaMetadataParser::Start(const MetadataCallback& callback) { return; } + if (StartsWithASCII(mime_type_, "image/", true)) { + ImageMetadataExtractor* extractor = new ImageMetadataExtractor; + extractor->Extract( + source_, + base::Bind(&FinishParseImageMetadata, base::Owned(extractor), + base::Passed(&metadata), callback)); + return; + } + // TODO(tommycli): Implement for image mime types. callback.Run(metadata.Pass()); } |