summaryrefslogtreecommitdiffstats
path: root/chrome
diff options
context:
space:
mode:
authortommycli@chromium.org <tommycli@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-04-12 00:34:56 +0000
committertommycli@chromium.org <tommycli@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-04-12 00:34:56 +0000
commita0785560ac602a476e184f6cd6a1444e704fd542 (patch)
tree0bcaccf19f8183e9cd062eea059d79c581efcdee /chrome
parentc459ff92e7ee9612fa29573af89e9e2e9e4edf93 (diff)
downloadchromium_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')
-rw-r--r--chrome/browser/extensions/api/media_galleries/media_galleries_apitest.cc6
-rw-r--r--chrome/chrome.gyp6
-rw-r--r--chrome/chrome.isolate2
-rw-r--r--chrome/chrome_dll_bundle.gypi2
-rw-r--r--chrome/chrome_tests_unit.gypi2
-rw-r--r--chrome/common/extensions/api/media_galleries.idl15
-rw-r--r--chrome/installer/linux/debian/expected_deps1
-rw-r--r--chrome/installer/linux/rpm/expected_deps_i3861
-rw-r--r--chrome/installer/linux/rpm/expected_deps_x86_641
-rw-r--r--chrome/installer/mini_installer/chrome.release1
-rw-r--r--chrome/test/data/extensions/api_test/media_galleries/media_metadata/test.js24
-rw-r--r--chrome/tools/build/chromeos/FILES.cfg4
-rw-r--r--chrome/tools/build/mac/TESTS1
-rwxr-xr-xchrome/tools/build/mac/dump_product_syms1
-rw-r--r--chrome/tools/build/win/FILES.cfg5
-rw-r--r--chrome/unit_tests.isolate2
-rw-r--r--chrome/utility/chrome_content_utility_client.cc6
-rw-r--r--chrome/utility/media_galleries/image_metadata_extractor.cc456
-rw-r--r--chrome/utility/media_galleries/image_metadata_extractor.h94
-rw-r--r--chrome/utility/media_galleries/image_metadata_extractor_unittest.cc76
-rw-r--r--chrome/utility/media_galleries/media_metadata_parser.cc57
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());
}