diff options
26 files changed, 605 insertions, 19 deletions
diff --git a/build/common.gypi b/build/common.gypi index 02851d7..bde2a3a 100644 --- a/build/common.gypi +++ b/build/common.gypi @@ -2236,6 +2236,14 @@ }, { # else: branding!="Chrome" 'defines': ['CHROMIUM_BUILD'], }], + ['OS=="mac"', { + 'xcode_settings': { + 'LD_RUNPATH_SEARCH_PATHS': [ + # To find dylibs such as libexif. + '@loader_path/.', + ], + }, + }], ['OS=="mac" and component=="shared_library"', { 'xcode_settings': { 'DYLIB_INSTALL_NAME_BASE': '@rpath', 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 b8ce165..845f51e 100644 --- a/chrome/browser/extensions/api/media_galleries/media_galleries_apitest.cc +++ b/chrome/browser/extensions/api/media_galleries/media_galleries_apitest.cc @@ -372,6 +372,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(); } @@ -607,6 +612,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 4d33e66..29b6813 100644 --- a/chrome/chrome.gyp +++ b/chrome/chrome.gyp @@ -250,6 +250,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', @@ -296,6 +297,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', @@ -356,6 +359,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..2c51ac7 100644 --- a/chrome/chrome.isolate +++ b/chrome/chrome.isolate @@ -45,6 +45,7 @@ ], 'isolate_dependency_tracked': [ '<(PRODUCT_DIR)/ffmpegsumo.so', + '<(PRODUCT_DIR)/libexif.dylib', ], 'isolate_dependency_untracked': [ '<(PRODUCT_DIR)/<(mac_product_name) Framework.framework/', @@ -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..7dcd347 100644 --- a/chrome/chrome_dll_bundle.gypi +++ b/chrome/chrome_dll_bundle.gypi @@ -37,6 +37,10 @@ '$(DYLIB_INSTALL_NAME_BASE:standardizepath)/$(WRAPPER_NAME)/$(PRODUCT_NAME)', 'INFOPLIST_FILE': 'app/framework-Info.plist', + 'LD_RUNPATH_SEARCH_PATHS': [ + # To find dylibs such as libexif. + '@loader_path/Libraries/.', + ], }, 'includes': [ 'chrome_nibs.gypi', @@ -138,10 +142,10 @@ ], 'copies': [ { - # Copy FFmpeg binaries for audio/video support. 'destination': '<(PRODUCT_DIR)/$(CONTENTS_FOLDER_PATH)/Libraries', 'files': [ - '<(PRODUCT_DIR)/ffmpegsumo.so', + '<(PRODUCT_DIR)/ffmpegsumo.so', # Copy FFmpeg binaries for audio/video support. + '<(PRODUCT_DIR)/libexif.dylib', ], }, { diff --git a/chrome/chrome_tests_unit.gypi b/chrome/chrome_tests_unit.gypi index bac3bef..4e62a12 100644 --- a/chrome/chrome_tests_unit.gypi +++ b/chrome/chrome_tests_unit.gypi @@ -1944,6 +1944,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. @@ -2614,6 +2615,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 941c7b7..a636881 100644 --- a/chrome/common/extensions/api/media_galleries.idl +++ b/chrome/common/extensions/api/media_galleries.idl @@ -104,13 +104,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/linux/sysroot_scripts/packagelist.debian.wheezy.amd64 b/chrome/installer/linux/sysroot_scripts/packagelist.debian.wheezy.amd64 index c93eccb..3573518 100644 --- a/chrome/installer/linux/sysroot_scripts/packagelist.debian.wheezy.amd64 +++ b/chrome/installer/linux/sysroot_scripts/packagelist.debian.wheezy.amd64 @@ -61,6 +61,8 @@ main/libc/libcap2/libcap-dev_2.22-1.2_amd64.deb main/libe/libexif/libexif12_0.6.20-3_amd64.deb main/libe/libexif/libexif-dev_0.6.20-3_amd64.deb main/libf/libffi/libffi5_3.0.10-3_amd64.deb +main/libe/libexif/libexif12_0.6.20-3_amd64.deb +main/libe/libexif/libexif-dev_0.6.20-3_amd64.deb main/libg/libgcrypt11/libgcrypt11_1.5.0-5+deb7u1_amd64.deb main/libg/libgcrypt11/libgcrypt11-dev_1.5.0-5+deb7u1_amd64.deb main/libg/libgnome-keyring/libgnome-keyring0_3.4.1-1_amd64.deb 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/interactive_ui_tests.isolate b/chrome/interactive_ui_tests.isolate index 492bcc2..d737995 100644 --- a/chrome/interactive_ui_tests.isolate +++ b/chrome/interactive_ui_tests.isolate @@ -57,6 +57,7 @@ 'variables': { 'isolate_dependency_tracked': [ '<(PRODUCT_DIR)/ffmpegsumo.so', + '<(PRODUCT_DIR)/libexif.dylib', '<(PRODUCT_DIR)/osmesa.so', ], 'isolate_dependency_untracked': [ @@ -83,6 +84,7 @@ '<(PRODUCT_DIR)/d3dcompiler_46.dll', '<(PRODUCT_DIR)/ffmpegsumo.dll', '<(PRODUCT_DIR)/libEGL.dll', + '<(PRODUCT_DIR)/libexif.dll', '<(PRODUCT_DIR)/libGLESv2.dll', '<(PRODUCT_DIR)/ppGoogleNaClPluginChrome.dll', '<(PRODUCT_DIR)/osmesa.dll', diff --git a/chrome/sync_integration_tests.isolate b/chrome/sync_integration_tests.isolate index 4736fe8..104fd2f 100644 --- a/chrome/sync_integration_tests.isolate +++ b/chrome/sync_integration_tests.isolate @@ -56,6 +56,7 @@ ], 'isolate_dependency_tracked': [ '<(PRODUCT_DIR)/ffmpegsumo.so', + '<(PRODUCT_DIR)/libexif.dylib', ], 'isolate_dependency_untracked': [ '<(PRODUCT_DIR)/<(mac_product_name) Framework.framework/', 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..72af0ae2 100644 --- a/chrome/tools/build/mac/TESTS +++ b/chrome/tools/build/mac/TESTS @@ -1,2 +1,3 @@ sync_integration_tests ffmpegsumo.so +libexif.dylib diff --git a/chrome/tools/build/mac/dump_product_syms b/chrome/tools/build/mac/dump_product_syms index ad816f5..1f70c4c 100755 --- a/chrome/tools/build/mac/dump_product_syms +++ b/chrome/tools/build/mac/dump_product_syms @@ -82,6 +82,7 @@ SRC_NAMES=( "crash_inspector" "crash_report_sender.app" "ffmpegsumo.so" + "libexif.dylib" "libplugin_carbon_interpose.dylib" "ppGoogleNaClPluginChrome.plugin" "remoting_host_plugin.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 03c0be9..f803337 100644 --- a/chrome/unit_tests.isolate +++ b/chrome/unit_tests.isolate @@ -83,6 +83,7 @@ 'variables': { 'isolate_dependency_tracked': [ '<(PRODUCT_DIR)/ffmpegsumo.so', + '<(PRODUCT_DIR)/libexif.dylib', '<(PRODUCT_DIR)/osmesa.so', ], 'isolate_dependency_untracked': [ @@ -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/media_galleries/image_metadata_extractor.cc b/chrome/utility/media_galleries/image_metadata_extractor.cc new file mode 100644 index 0000000..5329df3 --- /dev/null +++ b/chrome/utility/media_galleries/image_metadata_extractor.cc @@ -0,0 +1,279 @@ +// 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/memory/ref_counted.h" +#include "base/numerics/safe_conversions.h" +#include "base/strings/string_number_conversions.h" +#include "media/base/data_source.h" +#include "net/base/io_buffer.h" + +extern "C" { +#include <libexif/exif-data.h> +} // extern "C" + +namespace metadata { + +namespace { + +const int 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(), (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) || 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(), (uint8*)buffer->data(), + base::Bind(&FinishGetImageBytes, buffer, + base::Unretained(source), callback)); +} + +void ExtractInt(ExifData* data, ExifTag tag, int* result) { + DCHECK(result); + + ExifEntry* entry = exif_data_get_entry(data, tag); + + if (entry) { + ExifByteOrder order = exif_data_get_byte_order(data); + + switch (entry->format) { + case EXIF_FORMAT_SHORT: { + ExifShort v = exif_get_short(entry->data, order); + *result = base::checked_cast<int>(v); + break; + } + case EXIF_FORMAT_LONG: { + ExifLong v = exif_get_long(entry->data, order); + // Ignore values that don't fit in a signed int - probably 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(result); + + ExifEntry* entry = exif_data_get_entry(data, tag); + + if (entry) { + ExifByteOrder order = exif_data_get_byte_order(data); + + if (entry->format == EXIF_FORMAT_RATIONAL) { + ExifRational v = exif_get_rational(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(result); + + ExifEntry* entry = exif_data_get_entry(data, tag); + + if (entry) { + char buf[1024]; + exif_entry_get_value(entry, buf, sizeof(buf)); + *result = buf; + } +} + +} // namespace + +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 = exif_data_new_from_data( + (unsigned char*)buffer->data(), buffer->BytesRemaining()); + + if (!data) { + callback.Run(false); + return; + } + + ExtractInt(data, EXIF_TAG_IMAGE_WIDTH, &width_); + 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; + 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; + } + + ExtractDouble(data, EXIF_TAG_X_RESOLUTION, &x_resolution_); + ExtractDouble(data, EXIF_TAG_Y_RESOLUTION, &y_resolution_); + + ExtractString(data, EXIF_TAG_DATE_TIME, &date_); + + ExtractString(data, EXIF_TAG_MAKE, &camera_make_); + ExtractString(data, EXIF_TAG_MODEL, &camera_model_); + ExtractDouble(data, EXIF_TAG_EXPOSURE_TIME, &exposure_time_sec_); + + int flash_value = -1; + ExtractInt(data, EXIF_TAG_FLASH, &flash_value); + if (flash_value >= 0) { + flash_fired_ = (flash_value & 0x1) != 0; + } + + ExtractDouble(data, EXIF_TAG_FNUMBER, &f_number_); + ExtractDouble(data, EXIF_TAG_FOCAL_LENGTH, &focal_length_mm_); + ExtractInt(data, EXIF_TAG_ISO_SPEED_RATINGS, &iso_equivalent_); + + exif_data_free(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..6c059d0 --- /dev/null +++ b/chrome/utility/media_galleries/image_metadata_extractor.h @@ -0,0 +1,84 @@ +// 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 { + +class ImageMetadataExtractor { + public: + typedef base::Callback<void(bool)> DoneCallback; + + ImageMetadataExtractor(); + ~ImageMetadataExtractor(); + + // |callback| called with whether or not the extraction succeeded. Should + // only be called once. + void Extract(media::DataSource* source, const DoneCallback& callback); + + // Returns -1 if this could not be extracted. + int width() const; + int height() const; + + // In degrees. + int rotation() const; + + // In pixels per inch. + double x_resolution() const; + double y_resolution() const; + + 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: + 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..04d3b1d --- /dev/null +++ b/chrome/utility/media_galleries/image_metadata_extractor_unittest.cc @@ -0,0 +1,66 @@ +// 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) { + 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()); +} + +} // 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()); } diff --git a/content/content_browsertests.isolate b/content/content_browsertests.isolate index de3675f..8efed02 100644 --- a/content/content_browsertests.isolate +++ b/content/content_browsertests.isolate @@ -60,6 +60,7 @@ '<(PRODUCT_DIR)/clearkeycdmadapter.plugin', '<(PRODUCT_DIR)/content_resources.pak', '<(PRODUCT_DIR)/ffmpegsumo.so', + '<(PRODUCT_DIR)/libexif.dylib', '<(PRODUCT_DIR)/plugins/TestNestscapePlugin.plugin', '<(PRODUCT_DIR)/plugins/npapi_test_plugin.plugin', ], @@ -81,6 +82,7 @@ 'variables': { 'isolate_dependency_tracked': [ '<(PRODUCT_DIR)/ffmpegsumo.dll', + '<(PRODUCT_DIR)/libexif.dll', '<(PRODUCT_DIR)/chrome_100_percent.pak', '<(PRODUCT_DIR)/clearkeycdm.dll', '<(PRODUCT_DIR)/clearkeycdmadapter.dll', diff --git a/third_party/libexif/libexif.gyp b/third_party/libexif/libexif.gyp index 20aa227..5da9a1f 100644 --- a/third_party/libexif/libexif.gyp +++ b/third_party/libexif/libexif.gyp @@ -5,15 +5,16 @@ { 'variables': { 'conditions': [ - # TODO(kmadhusu): We are not ready to build this library on Android. - # Resolve the issues and build on Android. - ['os_posix==1 and OS!="mac"', { - 'use_system_libexif%': 0, - }, { # os_posix != 1 or OS == "mac" + ['OS == "linux"', { + 'use_system_libexif%': 1, + }, { # OS != "linux" 'use_system_libexif%': 0, }], ], }, + 'includes': [ + '../../build/util/version.gypi', + ], 'conditions': [ ['use_system_libexif==0', { 'targets': [ @@ -56,6 +57,19 @@ ], }, 'conditions': [ + ['clang==1', { + 'cflags': [ + '-Wno-enum-conversion', + '-Wno-switch' + ], + 'xcode_settings': { + 'WARNING_CFLAGS': [ + '-Wno-enum-conversion', + '-Wno-switch', + '-Wno-format', + ], + }, + }], ['os_posix==1 and OS!="mac"', { 'cflags!': ['-fvisibility=hidden'], }], @@ -66,26 +80,25 @@ 'mac_real_dsym': 1, }, }], - ], - 'xcode_settings': { + ], + 'xcode_settings': { 'GCC_SYMBOLS_PRIVATE_EXTERN': 'NO', # no -fvisibility=hidden - # TODO(kmadhusu): Copy this dylib to Versions folder. - # (Do something similar to libplugin_carbon_interpose.dylib). - 'DYLIB_INSTALL_NAME_BASE': '@executable_path/../../..', + 'DYLIB_INSTALL_NAME_BASE': '@rpath', }, }], ['OS=="win"', { 'product_name': 'libexif', - 'msvs_settings': { - 'VCLinkerTool': { - 'ModuleDefinitionFile': 'libexif.def', - }, - }, + 'sources': [ + 'libexif.def', + ], 'defines': [ # This seems like a hack, but this is what WebKit Win does. 'snprintf=_snprintf', 'inline=__inline', ], + 'msvs_disabled_warnings': [ + 4267, # size_t -> ExifLong truncation on amd64 + ], }], ], }, |